diff --git a/apps/otel-proxy/package.json b/apps/otel-proxy/package.json index 43257d564f..1185f41cf8 100644 --- a/apps/otel-proxy/package.json +++ b/apps/otel-proxy/package.json @@ -17,10 +17,10 @@ }, "devDependencies": { "@types/node": "^22.19.19", - "@vitest/coverage-v8": "^5.0.0", + "@vitest/coverage-v8": "^5.0.5", "oxfmt": "^0.74.0", "typescript": "~7.0.2", - "vitest": "^5.0.0" + "vitest": "^5.0.5" }, "packageManager": "pnpm@11.11.0" } diff --git a/apps/zbugs/package.json b/apps/zbugs/package.json index 1f27a167c3..af8e70deb8 100644 --- a/apps/zbugs/package.json +++ b/apps/zbugs/package.json @@ -103,7 +103,7 @@ "vite": "^8.3.4", "vite-bundle-analyzer": "^1.3.8", "vite-plugin-svgr": "^5.2.0", - "vitest": "^5.0.0", + "vitest": "^5.0.5", "zero-cache": "workspace:*", "zqlite": "workspace:*" }, diff --git a/package.json b/package.json index 8f05986605..53ec14a5bc 100644 --- a/package.json +++ b/package.json @@ -30,9 +30,9 @@ "@emnapi/core": "1.10.0", "@emnapi/runtime": "1.10.0", "@types/node": "^22.19.19", - "@vitest/browser": "^5.0.0", - "@vitest/browser-playwright": "^5.0.0", - "@vitest/coverage-v8": "^5.0.0", + "@vitest/browser": "^5.0.5", + "@vitest/browser-playwright": "^5.0.5", + "@vitest/coverage-v8": "^5.0.5", "baseline-browser-mapping": "^2.11.29", "oxfmt": "^0.74.0", "oxlint": "^1.80.0", @@ -42,7 +42,7 @@ "tsnapi": "^0.3.3", "turbo": "^2.9.14", "typescript": "~7.0.2", - "vitest": "^5.0.0", + "vitest": "^5.0.5", "zx": "^8.8.5" }, "engines": { diff --git a/packages/analyze-query/package.json b/packages/analyze-query/package.json index b712ed752c..ebf4326b4a 100644 --- a/packages/analyze-query/package.json +++ b/packages/analyze-query/package.json @@ -27,7 +27,7 @@ "otel": "workspace:*", "oxfmt": "^0.74.0", "shared": "workspace:*", - "vitest": "^5.0.0", + "vitest": "^5.0.5", "zero-cache": "workspace:*", "zero-client": "workspace:*", "zero-protocol": "workspace:*", diff --git a/packages/ast-to-zql/package.json b/packages/ast-to-zql/package.json index 3c8a15ac78..66368d4597 100644 --- a/packages/ast-to-zql/package.json +++ b/packages/ast-to-zql/package.json @@ -20,7 +20,7 @@ "devDependencies": { "@faker-js/faker": "^10.6.0", "shared": "workspace:*", - "vitest": "^5.0.0", + "vitest": "^5.0.5", "zero-cache": "workspace:*", "zero-protocol": "workspace:*", "zero-schema": "workspace:*", diff --git a/packages/datadog/package.json b/packages/datadog/package.json index cca6e9debe..555b202f7a 100644 --- a/packages/datadog/package.json +++ b/packages/datadog/package.json @@ -21,7 +21,7 @@ "oxfmt": "^0.74.0", "shared": "workspace:*", "typescript": "~7.0.2", - "vitest": "^5.0.0" + "vitest": "^5.0.5" }, "packageManager": "pnpm@11.11.0" } diff --git a/packages/replicache/package.json b/packages/replicache/package.json index aae67c9467..27a6f23d78 100644 --- a/packages/replicache/package.json +++ b/packages/replicache/package.json @@ -96,7 +96,7 @@ "react-native": "^0.88.0", "shared": "workspace:*", "typescript": "~7.0.2", - "vitest": "^5.0.0", + "vitest": "^5.0.5", "xbytes": "^1.9.1" }, "peerDependencies": { diff --git a/packages/replicache/src/deleted-clients.test.ts b/packages/replicache/src/deleted-clients.test.ts index df90636a89..1141354091 100644 --- a/packages/replicache/src/deleted-clients.test.ts +++ b/packages/replicache/src/deleted-clients.test.ts @@ -88,6 +88,30 @@ describe('normalizeDeletedClients', () => { expect(result).toEqual(input); }); + + test('does not need Array.prototype.toSorted', () => { + // Hermes, the React Native JS engine, does not implement it. + const toSorted = Object.getOwnPropertyDescriptor( + Array.prototype, + 'toSorted', + ); + Reflect.deleteProperty(Array.prototype, 'toSorted'); + try { + const input: DeletedClients = [ + {clientGroupID: 'group-b', clientID: 'client-1'}, + {clientGroupID: 'group-a', clientID: 'client-2'}, + {clientGroupID: 'group-b', clientID: 'client-1'}, + ]; + expect(normalizeDeletedClients(input)).toEqual([ + {clientGroupID: 'group-a', clientID: 'client-2'}, + {clientGroupID: 'group-b', clientID: 'client-1'}, + ]); + } finally { + if (toSorted) { + Object.defineProperty(Array.prototype, 'toSorted', toSorted); + } + } + }); }); describe('mergeDeletedClients', () => { diff --git a/packages/replicache/src/deleted-clients.ts b/packages/replicache/src/deleted-clients.ts index bd8bed5c91..217e8f8a3f 100644 --- a/packages/replicache/src/deleted-clients.ts +++ b/packages/replicache/src/deleted-clients.ts @@ -1,3 +1,4 @@ +import {toSorted} from '../../shared/src/iterables.ts'; import {stringCompare} from '../../shared/src/string-compare.ts'; import * as v from '../../shared/src/valita.ts'; import type {Read, Write} from './dag/store.ts'; @@ -43,9 +44,10 @@ function compare(a: ClientIDPair, b: ClientIDPair): number { export function normalizeDeletedClients( deletedClients: DeletedClients, ): DeletedClients { - return deletedClients - .toSorted(compare) - .filter((item, i, arr) => i === 0 || compare(item, arr[i - 1]) !== 0); + // Not Array.prototype.toSorted: Hermes (React Native) does not implement it. + return toSorted(deletedClients, compare).filter( + (item, i, arr) => i === 0 || compare(item, arr[i - 1]) !== 0, + ); } export function mergeDeletedClients( diff --git a/packages/replicache/src/kv/sqlite-store.ts b/packages/replicache/src/kv/sqlite-store.ts index dc1d68f91d..087f2fca40 100644 --- a/packages/replicache/src/kv/sqlite-store.ts +++ b/packages/replicache/src/kv/sqlite-store.ts @@ -151,13 +151,23 @@ export class SQLiteStore implements Store { const {lock, db} = this.#entry; // Wait for all readers and writers to finish. const writeRelease = await lock.write(); + try { + // An overlapping close() of this store may have finished while this one + // was waiting for the lock. Decrementing again would drop a reference + // held by another store on the same file and close it underneath that + // store. + if (this.#closed) { + return; + } - // Handle reference counting for shared stores - only close database - // when this is the last store instance using it - decrementStoreRefCount(this.#filename, db); + // Handle reference counting for shared stores - only close database + // when this is the last store instance using it + decrementStoreRefCount(this.#filename, db); - this.#closed = true; - writeRelease(); + this.#closed = true; + } finally { + writeRelease(); + } } get closed(): boolean { diff --git a/packages/replicache/src/kv/zero-sqlite/store.test.node.ts b/packages/replicache/src/kv/zero-sqlite/store.test.node.ts index 92e97e0a67..f25c5f0b2d 100644 --- a/packages/replicache/src/kv/zero-sqlite/store.test.node.ts +++ b/packages/replicache/src/kv/zero-sqlite/store.test.node.ts @@ -82,6 +82,24 @@ test('entry rejects NULL keys', async () => { } }); +test('overlapping close() calls keep other stores on the same file open', async () => { + // Both calls pass the closed check before either takes the lock, so the + // second one must not drop the reference held by the other store. + const name = 'zero-sqlite-overlapping-close'; + const closing = createStore(name); + const other = createStore(name); + await withWrite(other, async wt => { + await wt.put('k', 'v'); + }); + + await Promise.all([closing.close(), closing.close()]); + + await withRead(other, async rt => { + expect(await rt.get('k')).toBe('v'); + }); + await other.close(); +}); + test('setupDatabase pragmas take effect on a real SQLite connection', () => { // The pragma tests in sqlite-store.test.node.ts only record the SQL sent to a // fake delegate. SQLite ignores page_size silently when it is issued too late, diff --git a/packages/replicache/src/replicache-impl.test.ts b/packages/replicache/src/replicache-impl.test.ts index a6427ce838..32781327ab 100644 --- a/packages/replicache/src/replicache-impl.test.ts +++ b/packages/replicache/src/replicache-impl.test.ts @@ -1,6 +1,8 @@ import type {LogLevel, LogSink} from '@rocicorp/logger'; import {resolver} from '@rocicorp/resolver'; import {describe, expect, onTestFinished, test, vi} from 'vitest'; +import {MemStore} from './kv/mem-store.ts'; +import type {StoreProvider} from './kv/store.ts'; import {refresh} from './persist/refresh.ts'; import {ReplicacheImpl} from './replicache-impl.ts'; import type {ReplicacheOptions, ZeroOption} from './replicache-options.ts'; @@ -102,4 +104,51 @@ describe('ReplicacheImpl', () => { await impl.close(); }); + + test('a store that fails to close does not block reopening the same name', async () => { + // `#open` waits on `closingInstances` for a previous instance of the same + // name. If that instance's `close()` throws before it clears its entry, + // the wait never ends and every later instance of the name hangs in open. + const name = 'test-close-failure-does-not-block-reopen'; + const closeError = new Error('the store could not be closed'); + + let failClose = true; + const inners: MemStore[] = []; + const kvStore: StoreProvider = { + create: storeName => { + const inner = new MemStore(storeName); + inners.push(inner); + return { + read: () => inner.read(), + write: () => inner.write(), + close: () => (failClose ? Promise.reject(closeError) : inner.close()), + get closed() { + return inner.closed; + }, + }; + }, + drop: () => Promise.resolve(), + }; + + const impl = new ReplicacheImpl( + {name, kvStore}, + {enablePullAndPushInOpen: false}, + ); + await impl.clientGroupID; + await expect(impl.close()).rejects.toBe(closeError); + + failClose = false; + let opened = false; + const next = new ReplicacheImpl( + {name, kvStore}, + {enablePullAndPushInOpen: false}, + ); + void next.clientGroupID.then(() => { + opened = true; + }); + await vi.waitFor(() => expect(opened).toBe(true)); + + await next.close(); + await Promise.all(inners.map(inner => inner.close())); + }); }); diff --git a/packages/replicache/src/replicache-impl.ts b/packages/replicache/src/replicache-impl.ts index b901679374..d3f13fc9c6 100644 --- a/packages/replicache/src/replicache-impl.ts +++ b/packages/replicache/src/replicache-impl.ts @@ -952,28 +952,33 @@ export class ReplicacheImpl { const {promise, resolve} = resolver(); closingInstances.set(this.name, promise); - this.#closeAbortController.abort(); + // A later instance of this name waits on `promise` in `#open`, so it must + // settle even if closing a store throws. + try { + this.#closeAbortController.abort(); - getBrowserGlobal('document')?.removeEventListener( - 'visibilitychange', - this.#onVisibilityChange, - ); + getBrowserGlobal('document')?.removeEventListener( + 'visibilitychange', + this.#onVisibilityChange, + ); - await Promise.race([this.#ready, this.#openFailed.promise]); - const closingPromises = [ - this.memdag.close(), - this.perdag.close(), - this.#idbDatabases.close(), - ]; + await Promise.race([this.#ready, this.#openFailed.promise]); + const closingPromises = [ + this.memdag.close(), + this.perdag.close(), + this.#idbDatabases.close(), + ]; - this.#pullConnectionLoop.close(); - this.#pushConnectionLoop.close(); + this.#pullConnectionLoop.close(); + this.#pushConnectionLoop.close(); - this.#subscriptions.clear(); + this.#subscriptions.clear(); - await Promise.all(closingPromises); - closingInstances.delete(this.name); - resolve(); + await Promise.all(closingPromises); + } finally { + closingInstances.delete(this.name); + resolve(); + } } async maybeEndPull(syncHead: Hash, requestID: string): Promise { diff --git a/packages/shared/package.json b/packages/shared/package.json index 04fb838eef..166020efcd 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -52,7 +52,7 @@ "playwright": "^1.60.0", "type-fest": "^4.41.0", "typescript": "~7.0.2", - "vitest": "^5.0.0" + "vitest": "^5.0.5" }, "packageManager": "pnpm@11.11.0" } diff --git a/packages/z2s/package.json b/packages/z2s/package.json index fff890f93d..7c9bedc69f 100644 --- a/packages/z2s/package.json +++ b/packages/z2s/package.json @@ -34,7 +34,7 @@ "pg": "^8.20.0", "postgres": "3.4.7", "shared": "workspace:*", - "vitest": "^5.0.0", + "vitest": "^5.0.5", "zero-cache": "workspace:*", "zero-protocol": "workspace:*", "zero-schema": "workspace:*", diff --git a/packages/z2s/src/compiler.ts b/packages/z2s/src/compiler.ts index 9ec10b3e9f..40b702b792 100644 --- a/packages/z2s/src/compiler.ts +++ b/packages/z2s/src/compiler.ts @@ -727,18 +727,18 @@ function selectIdent(server: ServerSpec, column: QualifiedColumn): SQLQuery { if (!serverColumnSchema.isEnum) { let needsNormalization = false; switch (serverType) { - case 'timestamptz': - // @ts-expect-error Fallthrough intended case 'timetz': + // @ts-expect-error Fallthrough intended + case 'time with time zone': needsNormalization = true; // fallthrough case 'date': case 'time': case 'time without time zone': - case 'time with time zone': case 'timestamp': case 'timestamp without time zone': + case 'timestamptz': case 'timestamp with time zone': { // EXTRACT(EPOCH FROM timetz) can be negative when the UTC offset is // positive (e.g. 01:00+02 = 23:00 UTC prev day = -3600s). Wrap with diff --git a/packages/zero-cache/package.json b/packages/zero-cache/package.json index f4ecaf61e2..cbe44c96d6 100644 --- a/packages/zero-cache/package.json +++ b/packages/zero-cache/package.json @@ -96,7 +96,7 @@ "shared": "workspace:*", "testcontainers": "^10.28.0", "typescript": "~7.0.2", - "vitest": "^5.0.0", + "vitest": "^5.0.5", "zero-permissions": "workspace:*", "zero-protocol": "workspace:*", "zero-schema": "workspace:*", diff --git a/packages/zero-cache/src/config/zero-config.ts b/packages/zero-cache/src/config/zero-config.ts index 575e4c153e..646f37b470 100644 --- a/packages/zero-cache/src/config/zero-config.ts +++ b/packages/zero-cache/src/config/zero-config.ts @@ -1256,8 +1256,8 @@ export const zeroOptions = { ``, `When enabled, lag reports are used to gate readiness of servers coming online by waiting`, `for lag to fall below the {bold ZERO_REPLICATION_LAG_INITIAL_READINESS_THRESHOLD_MS}.`, - `As such, the reporting interval determines the time for which a new server waits for`, - `the next report to determine readiness.`, + `A report is requested as soon as a new server has caught up, so its readiness does not`, + `wait for the next report interval.`, ``, `A negative or 0 value disables lag reporting and the associated readiness gating.`, ``, diff --git a/packages/zero-cache/src/server/change-streamer.ts b/packages/zero-cache/src/server/change-streamer.ts index 8197833aea..a4fbb6986d 100644 --- a/packages/zero-cache/src/server/change-streamer.ts +++ b/packages/zero-cache/src/server/change-streamer.ts @@ -16,12 +16,14 @@ import type {PgChangeLogPurgeLocker} from '../services/change-source/common/repl import {initializeCustomChangeSource} from '../services/change-source/custom/change-source.ts'; import {initializePostgresChangeSource} from '../services/change-source/pg/change-source-init.ts'; import {createBackupCleanupMonitor} from '../services/change-streamer/backup-cleanup-monitor-factory.ts'; +import type {BackupMonitor} from '../services/change-streamer/backup-monitor.ts'; import {ChangeStreamerHttpServer} from '../services/change-streamer/change-streamer-http.ts'; import {initializeStreamer} from '../services/change-streamer/change-streamer-service.ts'; import type {ChangeStreamerService} from '../services/change-streamer/change-streamer.ts'; import {DEFAULT_READINESS_GRACE_PERIOD_MS} from '../services/change-streamer/restore-admission.ts'; import {initChangeStreamerSchema} from '../services/change-streamer/schema/init.ts'; import {AutoResetSignal} from '../services/change-streamer/schema/tables.ts'; +import {ServerStartMetrics} from '../services/change-streamer/server-start-metrics.ts'; import { PurgeLocker, type PurgeLock, @@ -133,6 +135,7 @@ export default async function runWorker( let purgeLock = null as PurgeLock | null; let changeStreamer: ChangeStreamerService | undefined; + let backupMonitor: BackupMonitor | undefined; let backupURL: string | undefined; const context = getServerContext(config); @@ -164,6 +167,7 @@ export default async function runWorker( ); } + const startMetrics = new ServerStartMetrics(); let waitForFirstBackupBeforeServing = false; let newBackupLineage = false; for (const first of [true, false]) { @@ -210,6 +214,7 @@ export default async function runWorker( waitForBackupBeforeServing, newBackupLineage: initNewBackupLineage, pgChangeLogBehindSlot, + startTimings, } = upstream.type === 'pg' ? await initializePostgresChangeSource( lc, @@ -245,6 +250,24 @@ export default async function runWorker( const replicationStatusPublisher = ReplicationStatusPublisher.forReplicaFile(replica.file); + // Created before the change-streamer, which tracks its watermarks (and + // requests prompt backups from it) from construction. It is run (and + // stopped) as a service below. It monitors the backup-replicator's + // destination (see `backupURLOverride` below). + backupMonitor = createBackupCleanupMonitor({ + lc, + config: destinationBackupURL + ? { + ...config, + litestream: { + ...config.litestream, + backupURLOverride: destinationBackupURL, + }, + } + : config, + replicaFile: replica.file, + }); + changeStreamer = await initializeStreamer( lc, shard, @@ -332,11 +355,16 @@ export default async function runWorker( retentionMs: sqliteChangeLogRetentionMs, } : undefined, + backupMonitor, }, setTimeout, ); backupURL = destinationBackupURL; waitForFirstBackupBeforeServing = waitForBackupBeforeServing; + startMetrics.setKind(startTimings?.kind); + startMetrics.record('slot_create', startTimings?.slotCreateMs); + startMetrics.record('fork_wait', startTimings?.forkWaitMs); + startMetrics.record('restore', startTimings?.restoreMs); newBackupLineage = initNewBackupLineage; break; } catch (e) { @@ -354,6 +382,9 @@ export default async function runWorker( // UPSTREAM_DB: CREATE_REPLICATION_SLOT waits for all older // transactions to finish, including this lock's open transaction. await cleanup.release(); + // Not yet started (the change-streamer starts it), so nothing to wait on. + void backupMonitor?.stop(); + backupMonitor = undefined; if (first && e instanceof AutoResetSignal) { lc.warn?.(`resetting replica ${replica.file}`, e); // TODO: Make deleteLiteDB work with litestream. It will probably have to be @@ -378,12 +409,19 @@ export default async function runWorker( } // impossible: upstream must have advanced in order for replication to be stuck. assert(changeStreamer, `resetting replica did not advance replicaVersion`); + assert(backupMonitor, `backup monitor is created with the change-streamer`); // The backup-replicator is stopped with the other services, i.e. after the // ChangeStreamerHttpServer has drained its subscribers. const processes = new SupportingProcesses(lc, parent); const profileSubWorkers: Worker[] = []; const {promise: replicatorReady, resolve} = resolver(); + if (backupURL) { + const catchUpStart = performance.now(); + void replicatorReady.then(() => + startMetrics.record('catch_up', performance.now() - catchUpStart), + ); + } if (!backupURL) { resolve(); // No backup replicator to wait for, so resolve immediately. } else { @@ -412,13 +450,6 @@ export default async function runWorker( ); } - const backupMonitor = createBackupCleanupMonitor({ - lc, - config, - replicaFile: replica.file, - changeStreamer, - }); - let backupReady = promiseVoid; if (waitForFirstBackupBeforeServing) { const start = performance.now(); @@ -427,6 +458,7 @@ export default async function runWorker( backupReady = backupMonitor.firstBackupReceived().then(() => { const elapsed = performance.now() - start; lc.info?.(`initial backup confirmed after ${elapsed.toFixed(2)}ms`); + startMetrics.record('backup_confirm', elapsed); }); } @@ -466,7 +498,10 @@ export default async function runWorker( changeStreamer, ); - void readinessGate.then(() => parent.send(['ready', {ready: true}])); + void readinessGate.then(() => { + startMetrics.ready(); + parent.send(['ready', {ready: true}]); + }); // Note: The changeStreamer itself is not started here; it is started by the // changeStreamerWebServer after a delay to ensure that routing diff --git a/packages/zero-cache/src/services/change-source/change-source.ts b/packages/zero-cache/src/services/change-source/change-source.ts index c7ea5e4f46..67e80f691a 100644 --- a/packages/zero-cache/src/services/change-source/change-source.ts +++ b/packages/zero-cache/src/services/change-source/change-source.ts @@ -27,6 +27,14 @@ export interface ChangeSource { nextSendTimeMs: number; } | null> | null; + /** + * Requests that a lag report be initiated now rather than at the next + * report interval, e.g. so that a subscriber that has just caught up can + * determine its readiness promptly. Optional; a no-op if lag reporting is + * not supported / enabled, or if a report is already in flight. + */ + requestLagReport?(): void; + /** * Starts a stream of changes starting after the specific watermark, * with a corresponding sink for upstream acknowledgements. diff --git a/packages/zero-cache/src/services/change-source/common/replica-restore.ts b/packages/zero-cache/src/services/change-source/common/replica-restore.ts index c40b04ed4a..91d8705f9b 100644 --- a/packages/zero-cache/src/services/change-source/common/replica-restore.ts +++ b/packages/zero-cache/src/services/change-source/common/replica-restore.ts @@ -106,6 +106,41 @@ export type InitializeResult = { * litestream state (see {@link ResumableBackup}). */ newBackupLineage: boolean; + + /** + * How the replica was initialized, and the durations of its phases, for + * the replication-manager's start metrics (see `ServerStartMetrics`). + * Absent for change sources that do not report them. + */ + startTimings?: InitializeTimings | undefined; +}; + +/** + * How a replica was initialized (see {@link InitializeResult.startTimings}): + * - `fork`: restored from the backup of a running replica, with a new slot + * - `resume`: restored from the backup of an orphaned replica, with its slot + * - `rmv1`: restored from the backup of the replica whose slot it takes over + * - `initial_sync`: initially synced + */ +export type InitializeKind = 'fork' | 'resume' | 'rmv1' | 'initial_sync'; + +export type InitializeTimings = { + kind: InitializeKind; + /** + * The time spent creating the new replica and its slot, including retries + * while slot creation is blocked by upstream transactions (which only the + * upstream's owner controls). Absent if no slot was created, e.g. when + * resuming an orphaned replica without first having prepared a fork. + */ + slotCreateMs?: number | undefined; + /** + * The time spent selecting the replica to fork (or resume) and claiming its + * slot, i.e. before restoring, including any wait for the source replica to + * pass the new slot's position. It excludes {@link slotCreateMs}. + */ + forkWaitMs?: number | undefined; + /** The time spent restoring the replica from its backup. */ + restoreMs?: number | undefined; }; /** diff --git a/packages/zero-cache/src/services/change-source/pg/change-source-init.ts b/packages/zero-cache/src/services/change-source/pg/change-source-init.ts index 8975b10896..7df8505336 100644 --- a/packages/zero-cache/src/services/change-source/pg/change-source-init.ts +++ b/packages/zero-cache/src/services/change-source/pg/change-source-init.ts @@ -19,6 +19,7 @@ import { restoreReplica, type ConstrainingPurgeLock, type InitializeResult, + type InitializeTimings, type RestoreOptions, type ResumableBackup, } from '../common/replica-restore.ts'; @@ -110,6 +111,7 @@ export async function initializePostgresChangeSource( pgChangeLogBehindSlot, resumableBackup, slotWatermark, + timings, } = slotPerReplica ? await forkOrResumeReplica( lc, @@ -230,6 +232,9 @@ export async function initializePostgresChangeSource( backupPath !== (restoredReplica?.backupPath ?? null), pgChangeLogBehindSlot, newBackupLineage, + startTimings: initialSyncedReplica + ? {kind: 'initial_sync'} + : (timings ?? {kind: slotPerReplica ? 'fork' : 'rmv1'}), }; } finally { await db.end(); @@ -251,6 +256,7 @@ type RestoredReplica = { * change-log's head for acked keepalives. */ slotWatermark?: string | undefined; + timings?: InitializeTimings | undefined; }; // RMv1: Selects a replica to restore from and returns it, with the @@ -289,6 +295,7 @@ async function selectAndRestoreReplica( `restoring replica from ${backupURL} (${slot}@${confirmedFlushLsn})`, {replicas}, ); + const restoreStart = performance.now(); const resumableBackup = await restoreReplica( lc, {...litestream, backupURL}, // includes the replica's backup sub-path @@ -298,9 +305,15 @@ async function selectAndRestoreReplica( ? {type: 'fork', baseURL: backupBaseURL} : undefined, ); - return {replica, purgeLock, resumableBackup}; + const restoreMs = performance.now() - restoreStart; + return { + replica, + purgeLock, + resumableBackup, + timings: {kind: 'rmv1', restoreMs}, + }; } - return {replica, purgeLock}; + return {replica, purgeLock, timings: {kind: 'rmv1'}}; } // RMv2: Restores from an active replica and creates a new replica / slot @@ -317,6 +330,7 @@ async function forkOrResumeReplica( {litestream, acquirePurgeLock, cleanup}: RestoreOptions, gracePeriodMs: number, ): Promise { + const forkWaitStart = performance.now(); const result = await getSourceAndDestinationReplicas( lc, sql, @@ -324,12 +338,20 @@ async function forkOrResumeReplica( epoch, slotFailover, gracePeriodMs, + // The PG change-log is checked against the slot's position (see below) + // only when this replication-manager maintains it. + acquirePurgeLock !== undefined, ); if (!result) { // can't restore, must initial-sync return {replica: undefined, purgeLock: null}; } - const {restoreFrom, replicateTo, slotLSN, slotSession} = result; + const {restoreFrom, replicateTo, slotLSN, slotSession, slotCreateMs} = result; + const timings: InitializeTimings = { + kind: restoreFrom.id === replicateTo.id ? 'resume' : 'fork', + slotCreateMs, + forkWaitMs: performance.now() - forkWaitStart - (slotCreateMs ?? 0), + }; // A resumed slot (i.e. of the same replica) that was acked only once // transactions were stored in the PG change-log (RMv1.5) cannot have been // acked past a transaction missing from the change-log. Its position past @@ -355,7 +377,8 @@ async function forkOrResumeReplica( // by a replication-manager that acks the slot only once a transaction has // been stored in the change-log): the fork waits for the source replica's // slot to pass the position after the creation of the new replica, which is - // a transaction (in the publication) after the new slot's position. + // a transaction (in the publication) after the new slot's position (see + // `waitForStoredTxPastSlot` in getSourceAndDestinationReplicas()). // // For a resumed slot, the slot can also have been acked past the head of a // maintained change-log, for keepalives (e.g. for changes outside of the @@ -382,6 +405,7 @@ async function forkOrResumeReplica( `restoring replica from ${backupURL} (${slot}@${confirmedFlushLsn})`, {restoreFrom, replicateTo}, ); + const restoreStart = performance.now(); const resumableBackup = await restoreReplica( lc, {...litestream, backupURL}, // includes the replica's backup sub-path @@ -391,12 +415,14 @@ async function forkOrResumeReplica( ? {type: 'fork', baseURL: backupBaseURL} : undefined, ); + timings.restoreMs = performance.now() - restoreStart; return { replica: replicateTo, purgeLock, pgChangeLogBehindSlot, resumableBackup, slotWatermark, + timings, }; } return { @@ -404,6 +430,7 @@ async function forkOrResumeReplica( purgeLock, pgChangeLogBehindSlot, slotWatermark, + timings, }; } @@ -477,6 +504,13 @@ export async function getSourceAndDestinationReplicas( epoch: number, slotFailover: boolean, gracePeriodMs: number, + /** + * Whether a fork waits for the source replica's slot to pass a transaction + * after the destination slot's position (rather than just the position), + * which is necessary for the PG change-log to be resumable from the + * destination slot (see forkOrResumeReplica()). + */ + waitForStoredTxPastSlot: boolean, pollIntervalMs = REPLICA_POLL_INTERVAL_MS, ): Promise< | { @@ -489,12 +523,18 @@ export async function getSourceAndDestinationReplicas( * over by the PostgresChangeSource. Destroying it releases the slot. */ slotSession: Readable; + /** + * The time spent creating the destination replica and slot, if one was + * created (even if it was then abandoned for a resumption). + */ + slotCreateMs?: number | undefined; } | undefined > { const inactiveSince = new Map(); // tracks replica inactivity let destination: ReplicationSlotResult | undefined; - // The position after the creation of the destination replica. + let slotCreateMs: number | undefined; + // The position that the source replica's slot must reach to be forked. let forkLSN: LSN | undefined; const lastLogged = new Map(); const shouldLog = (key: string, intervalMs: number) => { @@ -548,32 +588,48 @@ export async function getSourceAndDestinationReplicas( const {active, confirmedFlushLsn} = replica; if (active) { // Create a replication slot to fork the active replica. - destination ??= await createReplicaAndSlot( - lc, - sql, - 'fork-replica-session', - shard, - epoch, - Date.now().toString(), // replicaID - slotFailover, - { - backupPath: null, // set only after the backup has been confirmed - backupV5: true, // RMv2 requires backupV5 - // The slot is not acked until it is taken over, which sets - // this. Until then, it is unknown. - pgChangeLog: false, - }, - () => promiseVoid, - ReplicaStage.Restore, - ); - // The replica row of the destination is inserted in a transaction - // (in the publication) after the destination slot's position. - // Waiting for the source replica's slot to pass it (rather than - // just the destination slot's position) ensures that a source + if (!destination) { + const slotCreateStart = performance.now(); + destination = await createReplicaAndSlot( + lc, + sql, + 'fork-replica-session', + shard, + epoch, + Date.now().toString(), // replicaID + slotFailover, + { + backupPath: null, // set only after the backup has been confirmed + backupV5: true, // RMv2 requires backupV5 + // The slot is not acked until it is taken over, which sets + // this. Until then, it is unknown. + pgChangeLog: false, + }, + () => promiseVoid, + ReplicaStage.Restore, + ); + slotCreateMs = performance.now() - slotCreateStart; + } + // The source replica acks its slot only once its changes are + // backed up (and, if it maintains the PG change-log, stored), so + // its slot reaching the destination slot's position means that + // the destination can be restored from its backup. + // + // If the PG change-log is to be resumed from the destination slot, + // the fork waits for the source to pass the position after the + // creation of the destination replica instead. Its row is + // inserted in a transaction (in the publication) after the + // destination slot's position, so this ensures that a source // replica that maintains the PG change-log has stored a - // transaction past the destination slot's position, i.e. that the - // change-log can be resumed from the destination slot. - forkLSN ??= await currentWalLSN(sql); + // transaction past the destination slot's position, rather than + // only acked keepalives past it. + // + // Otherwise, the ack of that transaction (at its commit LSN, which + // precedes the position after it) suffices, which the fork request + // (see fork-request.ts) prompts the source to make promptly. + forkLSN ??= waitForStoredTxPastSlot + ? await currentWalLSN(sql) + : destination.slot.consistent_point; if (toBigInt(confirmedFlushLsn) < toBigInt(forkLSN)) { if (shouldLog(`fork:${replica.id}`, PROGRESS_LOG_INTERVAL_MS)) { lc.info?.( @@ -610,6 +666,7 @@ export async function getSourceAndDestinationReplicas( replicateTo, slotLSN: destination.slot.consistent_point, slotSession: destination.initialSession, + slotCreateMs, }; } } @@ -672,6 +729,7 @@ export async function getSourceAndDestinationReplicas( // The slot is inactive, so its position is fixed. slotLSN: replica.confirmedFlushLsn, slotSession: reserved.reservation, + slotCreateMs, }; } } diff --git a/packages/zero-cache/src/services/change-source/pg/change-source.ts b/packages/zero-cache/src/services/change-source/pg/change-source.ts index b458ef508a..03ec514ea7 100644 --- a/packages/zero-cache/src/services/change-source/pg/change-source.ts +++ b/packages/zero-cache/src/services/change-source/pg/change-source.ts @@ -63,6 +63,11 @@ import type { } from '../protocol/current/downstream.ts'; import type {ColumnMetadata, TableMetadata} from './backfill-metadata.ts'; import {streamBackfill} from './backfill-stream.ts'; +import { + forkRequestMessagePrefix, + forkRequestSchema, + type ParsedForkRequest, +} from './fork-request.ts'; import {type ServerContext} from './initial-sync.ts'; import type { Message, @@ -218,6 +223,10 @@ export class PostgresChangeSource implements ChangeSource { return null; } + requestLagReport() { + this.#lagReporter?.requestReport(); + } + async startStream( clientWatermark: string, backfillRequests: BackfillRequest[] = [], @@ -301,6 +310,8 @@ export class PostgresChangeSource implements ChangeSource { this.#replica, ); + const forkRequestPrefix = forkRequestMessagePrefix(this.#shard); + /** * Determines if the incoming message is transactional, otherwise handling * non-transactional messages with a downstream status message. @@ -323,6 +334,18 @@ export class PostgresChangeSource implements ChangeSource { changes.pushStatus(status); } + if ( + msg.tag === 'message' && + !msg.transactional && + msg.prefix === forkRequestPrefix + ) { + const status = this.#forkRequestStatus(msg, lsn); + if (status) { + changes.pushStatus(status); + } + return false; + } + if (msg.tag === 'keepalive') { changes.pushStatus([ 'status', @@ -428,6 +451,40 @@ export class PostgresChangeSource implements ChangeSource { }; } + /** + * Translates a {@link ParsedForkRequest} into a status message that requests a + * prompt backup through the new replica's slot position, the ack of which + * the fork waits on. The status is at the message's position, which + * precedes the transaction that inserts the new replica's row, so + * watermarks never move backwards. + * + * A malformed request (including its `consistentPoint` LSN, which the + * schema converts) is ignored, as it only serves to expedite the fork. + */ + #forkRequestStatus( + msg: MessageMessage, + lsn: bigint, + ): DownstreamStatusMessage | undefined { + let request: ParsedForkRequest; + try { + request = parseLogicalMessageContent(this.#lc, msg, forkRequestSchema); + } catch { + return undefined; // logged by parseLogicalMessageContent() + } + const {syncBackupThrough} = request; + this.#lc.info?.( + `received fork request from ${request.replicaID}@${request.slot}. ` + + `Requesting backup through ${request.consistentPoint} ` + + `(${syncBackupThrough}).`, + {request}, + ); + return [ + 'status', + {ack: false, syncBackupThrough}, + {watermark: majorVersionToString(lsn)}, + ]; + } + /** * Warns if the stream is about to start from before the slot's * `confirmed_flush_lsn`, from which Postgres starts streaming instead. @@ -811,6 +868,25 @@ export class LagReporter { this.#lastAcceptedSendTimeMs = 0; } + /** + * Initiates the next report now rather than at its scheduled time, e.g. so + * that a subscriber that has just caught up receives a report without + * waiting out the report interval. + * + * This is a no-op while a report is in flight, as its receipt serves the + * same purpose, which also coalesces concurrent requests. It is also a no-op + * when no report is scheduled (e.g. after {@link stop}), so that a request + * never starts reporting on its own. + */ + requestReport() { + if (this.#expectingLagReport !== null || this.#timer === undefined) { + return; + } + clearTimeout(this.#timer); + this.#timer = undefined; + void this.#initiateScheduledReport(); + } + #scheduleNextReportWithJitter(delayMs: number) { const jitter = Math.floor(Math.random() * this.#reportIntervalJitterMs) - @@ -819,17 +895,21 @@ export class LagReporter { this.#expectingLagReport = null; clearTimeout(this.#timer); - this.#timer = setTimeout(async () => { + this.#timer = setTimeout(() => { this.#timer = undefined; - try { - await this.initiateLagReport(); - } catch (e) { - this.#lc.warn?.(`error initiating lag report`, e); - this.#scheduleNextReportWithJitter(this.#reportIntervalMs); - } + void this.#initiateScheduledReport(); }, delayMs); } + async #initiateScheduledReport() { + try { + await this.initiateLagReport(); + } catch (e) { + this.#lc.warn?.(`error initiating lag report`, e); + this.#scheduleNextReportWithJitter(this.#reportIntervalMs); + } + } + #scheduleMissingReportRetry(reportID: string) { clearTimeout(this.#timer); this.#timer = setTimeout(async () => { diff --git a/packages/zero-cache/src/services/change-source/pg/fork-request.ts b/packages/zero-cache/src/services/change-source/pg/fork-request.ts new file mode 100644 index 0000000000..ac8ce0d0ef --- /dev/null +++ b/packages/zero-cache/src/services/change-source/pg/fork-request.ts @@ -0,0 +1,97 @@ +import type {LogContext} from '@rocicorp/logger'; +import * as v from '../../../../../shared/src/valita.ts'; +import {PG_17} from '../../../types/pg-versions.ts'; +import type {PostgresDB} from '../../../types/pg.ts'; +import type {ShardID} from '../../../types/shards.ts'; +import {toStateVersionString} from './lsn.ts'; + +/** + * A fork request is a (non-transactional) logical message that a new + * replication-manager emits once it has created the replication slot of a + * replica that it is about to fork from a running replica, i.e. restore from + * its backup. + * + * The fork waits for the running replica to have backed up (and acked) its + * replication slot past the new slot's `consistentPoint`. A running + * replication-manager otherwise backs up at its regular (e.g. 15 second) + * cadence, which would delay the fork by as much. Upon receiving a fork + * request in its replication stream, it instead backs up through the + * `consistentPoint` as soon as its replica reaches it (see + * `syncBackupThrough` in the change source protocol). + * + * The message is non-transactional so that it is streamed, and handled, as + * soon as it is decoded, ahead of the transaction that inserts the new + * replica's row (which reaches the running replica past the + * `consistentPoint`). Replication-managers that do not recognize the prefix + * ignore it, as they do any non-transactional message of another prefix. + */ +const forkRequestMessageSchema = v.object({ + replicaID: v.string(), + slot: v.string(), + consistentPoint: v.string(), +}); + +/** A fork request as it is emitted (see {@link emitForkRequest}). */ +export type ForkRequest = v.Infer; + +/** + * Parses a {@link ForkRequest}, converting its `consistentPoint` (an LSN) to + * the watermark through which to back up (`syncBackupThrough`). A malformed + * LSN fails the parse, like any other malformed request, so that the request + * is ignored rather than failing the replication stream. + */ +export const forkRequestSchema = forkRequestMessageSchema.chain(request => { + try { + return v.ok({ + ...request, + syncBackupThrough: toStateVersionString(request.consistentPoint), + }); + } catch (e) { + return v.err( + `malformed consistentPoint "${request.consistentPoint}": ${String(e)}`, + ); + } +}); + +export type ParsedForkRequest = v.Infer; + +export function forkRequestMessagePrefix({appID, shardNum}: ShardID) { + return `${appID}/${shardNum}/fork/v1`; +} + +/** + * Emits a {@link ForkRequest}. This only expedites the fork, which is correct + * without it, so failures (e.g. a missing EXECUTE privilege on + * `pg_logical_emit_message`) are logged rather than thrown. + * + * The message is flushed on PG 17+. Earlier versions lack the `flush` + * parameter, in which case the subsequent commit of the new replica's row + * flushes it. + */ +export async function emitForkRequest( + lc: LogContext, + sql: PostgresDB, + shard: ShardID, + request: ForkRequest, +): Promise { + const prefix = forkRequestMessagePrefix(shard); + const content = JSON.stringify(request); + try { + const [{pgVersion}] = await sql<{pgVersion: number}[]>` + SELECT current_setting('server_version_num')::int AS "pgVersion"`; + if (pgVersion >= PG_17) { + await sql`SELECT pg_logical_emit_message( + false, ${prefix}::text, ${content}::text, true)`; + } else { + await sql`SELECT pg_logical_emit_message( + false, ${prefix}::text, ${content}::text)`; + } + lc.info?.(`requested fork at ${request.consistentPoint}`, request); + } catch (e) { + lc.warn?.( + `unable to emit fork request. The fork waits for the next regular ` + + `backup of the replica it forks.`, + e, + ); + } +} diff --git a/packages/zero-cache/src/services/change-source/pg/replication-slots.ts b/packages/zero-cache/src/services/change-source/pg/replication-slots.ts index dda0f47641..4543182486 100644 --- a/packages/zero-cache/src/services/change-source/pg/replication-slots.ts +++ b/packages/zero-cache/src/services/change-source/pg/replication-slots.ts @@ -16,6 +16,7 @@ import {isPostgresError, type PostgresDB} from '../../../types/pg.ts'; import {upstreamSchema, type ShardID} from '../../../types/shards.ts'; import {orTimeout} from '../../../types/timeout.ts'; import {AutoResetSignal} from '../../change-streamer/schema/tables.ts'; +import {emitForkRequest} from './fork-request.ts'; import { createReplicationSessionFor, endSession, @@ -427,6 +428,17 @@ export async function createReplicaAndSlot( lsn: String(toBigInt(slot.consistent_point)), }); + if (stage === Restore) { + // Expedites the fork, which waits on the source replica's backup of + // the new slot's position (see fork-request.ts). Emitted before the + // replica's row is inserted, which flushes it, and outside of that + // transaction so that a failure to emit it does not fail the fork. + await emitForkRequest(lc, sql, shard, { + replicaID, + slot: slot.slot_name, + consistentPoint: slot.consistent_point, + }); + } const replica = await runTx(sql, async tx => { await createReplica( tx, diff --git a/packages/zero-cache/src/services/change-source/protocol/current/status.ts b/packages/zero-cache/src/services/change-source/protocol/current/status.ts index 2f99750265..b5eda57829 100644 --- a/packages/zero-cache/src/services/change-source/protocol/current/status.ts +++ b/packages/zero-cache/src/services/change-source/protocol/current/status.ts @@ -15,6 +15,11 @@ export const downstreamStatusSchema = v.object({ // Contains a lag report for recording end to end latency metrics. lagReport: changeSourceReportSchema.optional(), + + // Requests that the consumer back up its changes through this watermark + // promptly, rather than at its regular backup cadence, as the change source + // is waiting on the resulting ack (e.g. to fork a new replica from it). + syncBackupThrough: v.string().optional(), }); export type DownstreamStatus = v.Infer; diff --git a/packages/zero-cache/src/services/change-streamer/backup-cleanup-monitor-factory.ts b/packages/zero-cache/src/services/change-streamer/backup-cleanup-monitor-factory.ts index 201c2050c6..6ac70d0d34 100644 --- a/packages/zero-cache/src/services/change-streamer/backup-cleanup-monitor-factory.ts +++ b/packages/zero-cache/src/services/change-streamer/backup-cleanup-monitor-factory.ts @@ -1,12 +1,10 @@ import type {LogContext} from '@rocicorp/logger'; import type {NormalizedZeroConfig} from '../../config/normalize.ts'; -import type {Source} from '../../types/streams.ts'; import { backupDestinationURL, getLastBackupTime, } from '../litestream/commands.ts'; -import {type BackedUpWatermark, BackupMonitor} from './backup-monitor.ts'; -import type {ChangeStreamerService} from './change-streamer.ts'; +import {BackupMonitor, type BackupPoller} from './backup-monitor.ts'; import {LitestreamSyncRequester} from './litestream-sync-requester.ts'; import { type BackupStateVerifier, @@ -18,7 +16,6 @@ export type BackupCleanupMonitorFactoryOptions = { lc: LogContext; config: NormalizedZeroConfig; replicaFile: string; - changeStreamer: ChangeStreamerService; verifyBackupState?: BackupStateVerifier | undefined; }; @@ -26,31 +23,30 @@ export function createBackupCleanupMonitor({ lc, config, replicaFile, - changeStreamer, verifyBackupState, }: BackupCleanupMonitorFactoryOptions): BackupMonitor { const {litestream, replica} = config; const backupURL = backupDestinationURL(litestream); - let stream: Source; + let poller: BackupPoller; if (!backupURL) { - stream = new ReplicaPoller(lc, replicaFile).start(); + poller = new ReplicaPoller(lc, replicaFile); } else if (config.litestream.backupUsingV5) { - stream = new LitestreamSyncRequester(lc, replicaFile, { + poller = new LitestreamSyncRequester(lc, replicaFile, { intervalMs: litestream.incrementalBackupIntervalSeconds * 1000, - }).start(); + }); } else { const {port: metricsPort} = litestream; - stream = new Litestream3PrometheusPoller( + poller = new Litestream3PrometheusPoller( lc, replicaFile, backupURL, `http://localhost:${metricsPort}/metrics`, verifyBackupState ?? (() => getLastBackupTime(lc, litestream, replica.file)), - ).start(); + ); } - return new BackupMonitor(lc, stream, changeStreamer, replicaFile); + return new BackupMonitor(lc, poller, replicaFile); } diff --git a/packages/zero-cache/src/services/change-streamer/backup-monitor.ts b/packages/zero-cache/src/services/change-streamer/backup-monitor.ts index 62e5ab258d..ad2294cfef 100644 --- a/packages/zero-cache/src/services/change-streamer/backup-monitor.ts +++ b/packages/zero-cache/src/services/change-streamer/backup-monitor.ts @@ -4,8 +4,8 @@ import {promiseVoid} from '../../../../shared/src/resolved-promises.ts'; import {Database} from '../../../../zqlite/src/db.ts'; import {getOrCreateGauge} from '../../observability/metrics.ts'; import type {Source} from '../../types/streams.ts'; +import {Subscription} from '../../types/subscription.ts'; import type {SingletonService} from '../service.ts'; -import type {ChangeStreamerService} from './change-streamer.ts'; export type BackedUpWatermark = { watermark: string; @@ -13,14 +13,52 @@ export type BackedUpWatermark = { backupTimeMs: number; }; +/** + * Determines which watermarks of the replica are backed up, by whatever means + * the backup (or its absence) allows. + */ +export interface BackupPoller { + /** + * Starts polling, returning the stream of backed-up watermarks. Must only + * be called once. + */ + start(): Source; + + /** + * Requests that the replica be backed up through `watermark` promptly, + * rather than at the poller's regular cadence, once the replica has reached + * it. The resulting watermark is emitted on the stream as usual. Pollers + * that cannot initiate backups do not implement this. + */ + requestSync?(watermark: string): void; +} + +/** + * The interface through which the change-streamer consumes backed-up + * watermarks and requests prompt backups (see {@link BackupMonitor}). + */ +export type BackupSource = Pick; + +/** + * Relays the watermarks backed up by a {@link BackupPoller} to the + * change-streamer, which consumes them (see {@link backups}) and requests + * prompt backups (see {@link requestSync}) as needed. + */ export class BackupMonitor implements SingletonService { readonly id = 'backup-monitor'; readonly #lc: LogContext; - readonly #watermarks: Source; - readonly #changeStreamer: ChangeStreamerService; + readonly #poller: BackupPoller; readonly #replicaFile: string; readonly #firstBackupReceived = resolver(); + // Backed-up watermarks only move forward, so the consumer only needs the + // latest one. Coalescing keeps at most one outstanding, so that the monitor + // neither waits on, nor queues for, the consumer. + readonly #backups = Subscription.create({ + coalesce: curr => curr, + }); + #watermarks: Source | undefined; + #stopped = false; #latestBackup: BackedUpWatermark | undefined; #firstBackupResolved = false; /** @@ -31,15 +69,9 @@ export class BackupMonitor implements SingletonService { */ #coverageTarget: string | undefined; - constructor( - lc: LogContext, - watermarks: Source, - changeStreamer: ChangeStreamerService, - replicaFile: string, - ) { + constructor(lc: LogContext, poller: BackupPoller, replicaFile: string) { this.#lc = lc; - this.#watermarks = watermarks; - this.#changeStreamer = changeStreamer; + this.#poller = poller; this.#replicaFile = replicaFile; } @@ -47,36 +79,72 @@ export class BackupMonitor implements SingletonService { return this.#firstBackupReceived.promise; } + /** + * The stream of backed-up watermarks, each later than the last, coalesced + * to the latest one that has not been consumed. It ends when the monitor + * stops. + */ + backups(): Source { + return this.#backups; + } + + /** + * Requests that the replica be backed up through `watermark` promptly, if + * the poller supports it (see {@link BackupPoller.requestSync}). A no-op + * unless the monitor is running. + */ + requestSync(watermark: string) { + if ( + !this.#watermarks || + this.#stopped || + (this.#latestBackup && this.#latestBackup.watermark >= watermark) + ) { + return; + } + if (this.#poller.requestSync) { + this.#lc.info?.(`requesting backup through ${watermark}`); + this.#poller.requestSync(watermark); + } + } + async run() { + if (this.#stopped) { + return; + } this.#lc.info?.('starting backup monitor'); this.#initBackupLagMetric(); this.#coverageTarget = this.#readReplicaStateVersion(); - for await (const backedUp of this.#watermarks) { - if (this.#latestBackup) { - if (backedUp.watermark < this.#latestBackup.watermark) { - this.#lc.warn?.( - `ignoring earlier backup watermark ${backedUp.watermark}`, - {backedUp}, - ); - continue; - } - if (backedUp.watermark === this.#latestBackup.watermark) { - this.#lc.debug?.( - `ignoring redundant backup watermark ${backedUp.watermark}`, - {backedUp}, - ); - continue; + this.#watermarks = this.#poller.start(); + try { + for await (const backedUp of this.#watermarks) { + if (this.#latestBackup) { + if (backedUp.watermark < this.#latestBackup.watermark) { + this.#lc.warn?.( + `ignoring earlier backup watermark ${backedUp.watermark}`, + {backedUp}, + ); + continue; + } + if (backedUp.watermark === this.#latestBackup.watermark) { + this.#lc.debug?.( + `ignoring redundant backup watermark ${backedUp.watermark}`, + {backedUp}, + ); + continue; + } } + this.#lc.info?.(`received backup watermark ${backedUp.watermark}`, { + backedUp, + }); + this.#latestBackup = backedUp; + this.#backups.push(backedUp); + this.#checkFirstBackupCovers(backedUp); } - this.#lc.info?.(`received backup watermark ${backedUp.watermark}`, { - backedUp, - }); - this.#latestBackup = backedUp; - this.#changeStreamer.trackBackupWatermark(backedUp.watermark); - this.#checkFirstBackupCovers(backedUp); + this.#lc.info?.('watermark stream closed. BackupMonitor stopped.'); + } finally { + this.#backups.cancel(); } - this.#lc.info?.('watermark stream closed. BackupMonitor stopped.'); } /** @@ -131,7 +199,9 @@ export class BackupMonitor implements SingletonService { } stop(): Promise { - this.#watermarks.cancel(); + this.#stopped = true; + this.#watermarks?.cancel(); + this.#backups.cancel(); return promiseVoid; } diff --git a/packages/zero-cache/src/services/change-streamer/change-streamer-service.ts b/packages/zero-cache/src/services/change-streamer/change-streamer-service.ts index d2822cbf5e..00c5be306d 100644 --- a/packages/zero-cache/src/services/change-streamer/change-streamer-service.ts +++ b/packages/zero-cache/src/services/change-streamer/change-streamer-service.ts @@ -39,6 +39,7 @@ import { UnrecoverableError, } from '../running-state.ts'; import {BackfillStateTracker} from './backfill-state-tracker.ts'; +import type {BackupSource} from './backup-monitor.ts'; import type {PreSerializedBatch} from './broadcast.ts'; import {serializeChangeStreamDataWithChange} from './change-log-codec.ts'; import { @@ -93,7 +94,7 @@ import { type TuningOptions as StorerOptions, } from './storer.ts'; import type {LocalReplica} from './subscribe.ts'; -import {Subscriber} from './subscriber.ts'; +import {Subscriber, type LagReports} from './subscriber.ts'; import {UpstreamAcker} from './upstream-acker.ts'; export type BackupConfig = { @@ -217,6 +218,14 @@ export type TuningOptions = StorerOptions & { | undefined; /** Supplied only in `serve` mode. A zero percentage keeps every read on PG. */ sqliteChangeLogServe?: SQLiteChangeLogServeOptions | undefined; + /** + * The source of backed-up watermarks (i.e. the `BackupMonitor`), which the + * change-streamer tracks from construction, and from which it requests + * prompt backups when the change source asks for them (e.g. for a replica + * fork). Its lifecycle (e.g. the monitor's run() and stop()) is managed by + * the caller. Absent, no backups are tracked. + */ + backupMonitor?: BackupSource | undefined; }; /** @@ -468,6 +477,7 @@ class ChangeStreamerImpl implements ChangeStreamerService { readonly #purgeScheduler: SQLiteChangeLogPurgeScheduler | undefined; readonly #comparator: SQLiteChangeLogComparator | undefined; readonly #acker: UpstreamAcker; + readonly #backupMonitor: BackupSource | undefined; readonly #initializer: ChangeLogInitializer; readonly #readRouter: SQLiteChangeLogReadRouter | undefined; @@ -528,10 +538,11 @@ class ChangeStreamerImpl implements ChangeStreamerService { ); #latestLagReportCommitTimeMs = 0; - // Resolves to whether lag reporting is disabled, once the lag reporter - // has been started (or failed to start) in run(). Subscribers use this to - // decide whether to send a stand-in signal when they are caught up. - readonly #lagReportingDisabled = resolver(); + // Resolves, once the lag reporter has been started (or failed to start) in + // run(), to the means of requesting lag reports, or to `undefined` if lag + // reporting is disabled. Subscribers request a report when they are caught + // up, or send a stand-in signal if it is disabled. + readonly #lagReports = resolver(); #backupWatermark: string | undefined; #pgPurgedWatermark: string = ''; /** @@ -793,6 +804,22 @@ class ChangeStreamerImpl implements ChangeStreamerService { this.#purgeLock = initialPurgeLock; this.#autoReset = autoReset; this.#state = new RunningState(this.id, undefined, setTimeoutFn); + + this.#backupMonitor = opts.backupMonitor; + if (this.#backupMonitor) { + // Backups are tracked from construction rather than from run(): in + // RMv1, run() is only called once the server is ready, which waits for + // the first backup (see BackupMonitor.firstBackupReceived()). + void this.#trackBackups(this.#backupMonitor); + } + } + + async #trackBackups(monitor: BackupSource) { + // The stream ends when the monitor stops. A failed monitor shuts down the + // server, as it runs as a service of its own. + for await (const {watermark} of monitor.backups()) { + this.#trackBackupWatermark(watermark); + } } /** @@ -809,7 +836,11 @@ class ChangeStreamerImpl implements ChangeStreamerService { this.#forwarder.startProgressMonitor(); const lagReportInit = await this.#source.startLagReporter(); - this.#lagReportingDisabled.resolve(!lagReportInit); + this.#lagReports.resolve( + lagReportInit + ? {request: () => this.#source.requestLagReport?.()} + : undefined, + ); if (lagReportInit) { // Record the commit time of the initiated lag report (i.e. "head") // for the purpose of skipping over any lag reports that are re-streamed @@ -891,19 +922,25 @@ class ChangeStreamerImpl implements ChangeStreamerService { this.#acker.reset(stream.acks, lastWatermark); - for await (const change of stream.changes) { + streamLoop: for await (const change of stream.changes) { this.#acker.trackDownstream(change); const [type, msg] = change; - if (type === 'status' && watermark === null && this.#mayRestart()) { - // A restart can happen at a status message outside of a - // transaction, e.g. when the replication stream is otherwise idle. - // Exiting the iteration cancels the stream. - break; - } switch (type) { case 'status': + if (msg.syncBackupThrough) { + // e.g. a new replica being forked from this one is waiting on + // the backup (see fork-request.ts). Requested before a possible + // restart at this status, which would otherwise drop it. + this.#backupMonitor?.requestSync(msg.syncBackupThrough); + } if (watermark === null) { + if (this.#mayRestart()) { + // A restart can happen at a status message outside of a + // transaction, e.g. when the replication stream is otherwise idle. + // Exiting the iteration cancels the stream. + break streamLoop; + } // Allows ownership to be assumed on an idle upstream. storer?.status(change[2].watermark); } @@ -1120,7 +1157,7 @@ class ChangeStreamerImpl implements ChangeStreamerService { downstream, { wsBatched, - lagReportingDisabled: this.#lagReportingDisabled.promise, + lagReports: this.#lagReports.promise, drainable: mode === 'serving' && ctx.drainable, ...this.#backfills.subscriberOptions(lc, ctx), }, @@ -1387,7 +1424,17 @@ class ChangeStreamerImpl implements ChangeStreamerService { return this.#drainTracker.drain(); } - trackBackupWatermark(watermark: string) { + /** + * Tracks the watermark up to which the replica has been backed up. This + * serves two purposes: + * - It serves as the maximum watermark up to which the change-log can be + * purged. Note that the change-streamer also takes into account the + * positions of its subscribers and snapshot reservations when purging + * changes in the change-log. + * - In RMv2, this ACK's the upstream change-source to allow its buffer of + * changes (e.g. the replication_slot) to advance. + */ + #trackBackupWatermark(watermark: string) { this.#backupWatermark = watermark; this.#acker.trackBackup(watermark); // The durable backup floor is an independent input to each change-log @@ -1694,8 +1741,9 @@ class ChangeStreamerImpl implements ChangeStreamerService { this.#state.stop(this.#lc, err); // Nothing is served once stopped. this.#drainTracker.stop(); - // No-op if already resolved by run(). - this.#lagReportingDisabled.resolve(false); + // No-op if already resolved by run(). Resolving to a requester that does + // nothing (rather than to `undefined`) sends nothing to subscribers. + this.#lagReports.resolve({request: () => {}}); this.#stream?.changes.cancel(); this.#purgeScheduler?.stop(); this.#comparator?.stop(); diff --git a/packages/zero-cache/src/services/change-streamer/change-streamer.ts b/packages/zero-cache/src/services/change-streamer/change-streamer.ts index b575a625d3..08f6d32297 100644 --- a/packages/zero-cache/src/services/change-streamer/change-streamer.ts +++ b/packages/zero-cache/src/services/change-streamer/change-streamer.ts @@ -323,7 +323,7 @@ export interface ChangeStreamerService * Starts a snapshot reservation to preserve change-log entries while * a soon-to-be-subscriber downloads the current replica backup. Once * the change-streamer knows that a backup is available - * (via {@link trackBackupWatermark}), it sends a confirmation + * (i.e. from its backup source), it sends a confirmation * SnapshotMessage and reserves the corresponding entries in the change-log * until the client with that `taskID` subscribes, or if the snapshot * connection terminates. @@ -339,16 +339,4 @@ export interface ChangeStreamerService * Subscribers continue to be served in the meantime. */ drain?(): Promise; - - /** - * Informs the change-streamer of the watermark up to which the replica has - * been backed up. This serves two purposes: - * - It serves as the maximum watermark up to which the change-log can be - * purged. Note that the change-streamer also takes into account the - * positions of its subscribers and snapshot reservations when purging - * changes in the change-log. - * - In RMv2, this ACK's the upstream change-source to allow its buffer of - * changes (e.g. the replication_slot) to advance. - */ - trackBackupWatermark(watermark: string): void; } diff --git a/packages/zero-cache/src/services/change-streamer/litestream-sync-requester.ts b/packages/zero-cache/src/services/change-streamer/litestream-sync-requester.ts index 2f400ae9d6..abfef5b567 100644 --- a/packages/zero-cache/src/services/change-streamer/litestream-sync-requester.ts +++ b/packages/zero-cache/src/services/change-streamer/litestream-sync-requester.ts @@ -1,12 +1,13 @@ import type {LogContext} from '@rocicorp/logger'; import {assert} from '../../../../shared/src/asserts.ts'; +import {must} from '../../../../shared/src/must.ts'; import * as v from '../../../../shared/src/valita.ts'; import {Database, type Statement} from '../../../../zqlite/src/db.ts'; import type {Source} from '../../types/streams.ts'; import {Subscription} from '../../types/subscription.ts'; import {LitestreamController} from '../litestream/litestream-controller.ts'; import {RunningState} from '../running-state.ts'; -import type {BackedUpWatermark} from './backup-monitor.ts'; +import type {BackedUpWatermark, BackupPoller} from './backup-monitor.ts'; /** * How long litestream waits for a requested sync (and its upload) before @@ -35,6 +36,14 @@ const ERROR_LOG_INTERVAL_MS = 60_000; */ const MIN_RETRY_DELAY_MS = 250; +/** + * How often the local replica is polled while a requested sync (see + * {@link LitestreamSyncRequester.requestSync}) waits for the replica to reach + * the requested watermark. Each poll is a single read of the replica's + * watermark, which the replica typically reaches within a poll or two. + */ +export const EXPEDITED_POLL_INTERVAL_MS = 50; + const localWatermarkSchema = v.object({ stateVersion: v.string(), writeTimeMs: v.number(), @@ -76,17 +85,25 @@ export type SyncRequesterConfig = { * are retried with a backoff that starts short and, once it grows to the * interval, defers to the regular ticks. Nothing is published until a request * succeeds. + * + * A sync through a specific watermark can also be requested out of band (see + * {@link requestSync}), e.g. when a replica fork waits on its backup. */ -export class LitestreamSyncRequester { +export class LitestreamSyncRequester implements BackupPoller { readonly #lc: LogContext; readonly #state = new RunningState('litestream-sync-requester'); readonly #config: SyncRequesterConfig; readonly #litestream: SyncController; - readonly #readLocalWatermark: Statement; + readonly #replicaFile: string; readonly #stream: Subscription; + #db: Database | undefined; + #readLocalWatermark: Statement | undefined; #timer: NodeJS.Timeout | undefined; #retryTimer: NodeJS.Timeout | undefined; + // The target of a requested sync, and when it stops being expedited. + #expedited: {target: string; deadlineMs: number} | undefined; + #expediteTimer: NodeJS.Timeout | undefined; #retryDelayMs = MIN_RETRY_DELAY_MS; #inFlight: Promise | undefined; #backedUp: string | undefined; @@ -101,18 +118,15 @@ export class LitestreamSyncRequester { this.#lc = lc.withContext('component', 'litestream-sync-requester'); this.#config = config; this.#litestream = litestream; - - const db = new Database(this.#lc, replicaFile, {readonly: true}); - this.#readLocalWatermark = db.prepare( - `SELECT stateVersion, writeTimeMs FROM "_zero.replicationState"`, - ); + this.#replicaFile = replicaFile; this.#stream = Subscription.create({ cleanup: () => { clearInterval(this.#timer); clearTimeout(this.#retryTimer); + clearTimeout(this.#expediteTimer); this.#state.stop(this.#lc); this.#litestream.close(); - db.close(); + this.#db?.close(); }, }); } @@ -120,9 +134,16 @@ export class LitestreamSyncRequester { /** * Starts requesting syncs and pushes backed-up watermarks to the returned * stream. Must only be called once. + * + * The replica is only opened here, so that a requester that is never + * started holds no resources. */ start(): Source { assert(this.#timer === undefined, `Already called start()`); + this.#db = new Database(this.#lc, this.#replicaFile, {readonly: true}); + this.#readLocalWatermark = this.#db.prepare( + `SELECT stateVersion, writeTimeMs FROM "_zero.replicationState"`, + ); this.#lc.info?.( `requesting litestream syncs every ${this.#config.intervalMs} ms`, ); @@ -140,11 +161,8 @@ export class LitestreamSyncRequester { if (!this.#state.shouldRun() || this.#inFlight) { return this.#inFlight ?? Promise.resolve(); } - let local: LocalWatermark; - try { - local = v.parse(this.#readLocalWatermark.get(), localWatermarkSchema); - } catch (e) { - this.#lc.warn?.(`unable to read local watermark`, e); + const local = this.#readLocal(); + if (!local) { return Promise.resolve(); } this.#inFlight = this.#requestSync(local).finally(() => { @@ -153,6 +171,88 @@ export class LitestreamSyncRequester { return this.#inFlight; }; + #readLocal(): LocalWatermark | undefined { + try { + return v.parse( + must(this.#readLocalWatermark, `start() was not called`).get(), + localWatermarkSchema, + ); + } catch (e) { + this.#lc.warn?.(`unable to read local watermark`, e); + return undefined; + } + } + + /** + * Requests a sync through `watermark` as soon as the local replica has + * reached it, rather than at the next regular tick. + * + * The replica is polled every {@link EXPEDITED_POLL_INTERVAL_MS} until its + * watermark reaches the target, after which a sync is requested + * immediately (or once the outstanding request, which may predate the + * target, completes). A failed request is retried with the usual backoff. + * Concurrent requests are coalesced into one, for the latest target. + * + * If the replica does not reach the target within the configured interval, + * the request is dropped, as the regular ticks then cover it anyway. + */ + requestSync(watermark: string) { + if ( + !this.#state.shouldRun() || + (this.#backedUp !== undefined && this.#backedUp >= watermark) + ) { + return; + } + const prev = this.#expedited?.target; + this.#expedited = { + target: prev !== undefined && prev > watermark ? prev : watermark, + deadlineMs: Date.now() + this.#config.intervalMs, + }; + this.#scheduleExpedite(0); + } + + #scheduleExpedite(delayMs: number) { + clearTimeout(this.#expediteTimer); + this.#expediteTimer = setTimeout(this.#expedite, delayMs); + } + + readonly #expedite = () => { + this.#expediteTimer = undefined; + const expedited = this.#expedited; + if (!expedited || !this.#state.shouldRun()) { + return; + } + const {target, deadlineMs} = expedited; + if (this.#backedUp !== undefined && this.#backedUp >= target) { + this.#expedited = undefined; // e.g. by a regular tick + return; + } + if (Date.now() >= deadlineMs) { + this.#lc.info?.( + `replica did not reach ${target} within ${this.#config.intervalMs} ms. ` + + `Deferring its backup to the regular syncs.`, + ); + this.#expedited = undefined; + return; + } + if (this.#inFlight) { + // The outstanding request may have read the local watermark before the + // replica reached the target. Check again once it completes. + void this.#inFlight.finally(() => this.#scheduleExpedite(0)); + return; + } + const local = this.#readLocal(); + if (!local || local.stateVersion < target) { + this.#scheduleExpedite(EXPEDITED_POLL_INTERVAL_MS); + return; + } + // A single request covers the target. If it fails, the usual backoff + // retries it. + this.#expedited = undefined; + this.#lc.info?.(`requesting sync through ${target}`); + void this.tick(); + }; + async #requestSync(local: LocalWatermark) { // `local` must be read before the request is issued: litestream only // guarantees that transactions committed before the request are synced. diff --git a/packages/zero-cache/src/services/change-streamer/litestream3-prometheus-poller.ts b/packages/zero-cache/src/services/change-streamer/litestream3-prometheus-poller.ts index 1688ac1406..5f59333803 100644 --- a/packages/zero-cache/src/services/change-streamer/litestream3-prometheus-poller.ts +++ b/packages/zero-cache/src/services/change-streamer/litestream3-prometheus-poller.ts @@ -12,7 +12,7 @@ import { litestreamMonitorMetricAttrs, } from '../litestream/metrics.ts'; import {RunningState, UnrecoverableError} from '../running-state.ts'; -import type {BackedUpWatermark} from './backup-monitor.ts'; +import type {BackedUpWatermark, BackupPoller} from './backup-monitor.ts'; export const CHECK_INTERVAL_MS = 10_000; @@ -97,7 +97,7 @@ export type BackupStateVerifier = () => Promise; * {@link WEDGED_SHUTDOWN_GRACE_MS}, the backup is treated as wedged and the * process is shut down rather than risk corrupting the backup. */ -export class Litestream3PrometheusPoller { +export class Litestream3PrometheusPoller implements BackupPoller { readonly #lc: LogContext; readonly #replicaFile: string; readonly #backupURL: string; diff --git a/packages/zero-cache/src/services/change-streamer/replica-poller.ts b/packages/zero-cache/src/services/change-streamer/replica-poller.ts index 23c6e2c54a..adc76523e0 100644 --- a/packages/zero-cache/src/services/change-streamer/replica-poller.ts +++ b/packages/zero-cache/src/services/change-streamer/replica-poller.ts @@ -6,7 +6,7 @@ import {StatementRunner} from '../../db/statements.ts'; import type {Source} from '../../types/streams.ts'; import {Subscription} from '../../types/subscription.ts'; import {getReplicationState} from '../replicator/schema/replication-state.ts'; -import type {BackedUpWatermark} from './backup-monitor.ts'; +import type {BackedUpWatermark, BackupPoller} from './backup-monitor.ts'; const CHECK_INTERVAL_MS = 10 * 1000; @@ -15,7 +15,7 @@ const CHECK_INTERVAL_MS = 10 * 1000; * 30 seconds and schedules cleanup when the watermark * (i.e. stateVersion) moves forward. */ -export class ReplicaPoller { +export class ReplicaPoller implements BackupPoller { readonly id = 'replica-poller'; readonly #lc: LogContext; readonly #replicaFile: string; diff --git a/packages/zero-cache/src/services/change-streamer/server-start-metrics.ts b/packages/zero-cache/src/services/change-streamer/server-start-metrics.ts new file mode 100644 index 0000000000..553ac1798d --- /dev/null +++ b/packages/zero-cache/src/services/change-streamer/server-start-metrics.ts @@ -0,0 +1,75 @@ +import {getOrCreateGauge} from '../../observability/metrics.ts'; +import type {InitializeKind} from '../change-source/common/replica-restore.ts'; + +/** + * The phases of a replication-manager's start: + * - `slot_create`: creating the new replica's slot, including retries while + * it is blocked by upstream transactions. This is separate from `fork_wait` + * because the upstream (e.g. a long-running transaction), not zero-cache or + * its capacity, determines how long it takes. + * - `fork_wait`: selecting the replica to restore from (and claiming its + * slot), including the wait for a forked replica to pass the new slot's + * position, excluding `slot_create` + * - `restore`: restoring the replica from its backup + * - `catch_up`: from initialization to the backup-replicator being caught up + * (i.e. its replication lag readiness) + * - `backup_confirm`: waiting for the first backup to cover the replica + * - `total`: from the start of the (change-streamer) process to readiness + * + * The phases are not exhaustive (e.g. connecting to and preparing the + * upstream), and `catch_up` and `backup_confirm` overlap. + */ +export type ServerStartPhase = + | 'slot_create' + | 'fork_wait' + | 'restore' + | 'catch_up' + | 'backup_confirm' + | 'total'; + +/** + * Records where a replication-manager's start goes, reported once it is + * ready by the `zero.replication.server_start` gauge, in seconds, labeled + * by `phase` and `kind` (see {@link InitializeKind}). This is for diagnosis, + * e.g. of why a start is slow, as the process only starts once. + */ +export class ServerStartMetrics { + readonly #phasesMs = new Map(); + #kind: InitializeKind | undefined; + #ready = false; + + setKind(kind: InitializeKind | undefined) { + this.#kind = kind; + } + + record(phase: ServerStartPhase, ms: number | undefined) { + if (ms !== undefined && !this.#ready) { + this.#phasesMs.set(phase, ms); + } + } + + /** + * Records the `total` (by default, the time since the process started) and + * starts reporting the gauge. Phases recorded afterwards are ignored. + */ + ready(totalMs = performance.now()) { + if (this.#ready) { + return; + } + this.record('total', totalMs); + this.#ready = true; + const kind = this.#kind ?? 'unknown'; + getOrCreateGauge('replication', 'server_start', { + description: + 'The duration of each phase of the replication-manager start ' + + '(slot_create, fork_wait, restore, catch_up, backup_confirm, and the total ' + + 'from process start to readiness), by the kind of initialization ' + + '(fork, resume, rmv1, initial_sync).', + unit: 's', + }).addCallback(o => { + for (const [phase, ms] of this.#phasesMs) { + o.observe(ms / 1000, {phase, kind}); + } + }); + } +} diff --git a/packages/zero-cache/src/services/change-streamer/subscriber.ts b/packages/zero-cache/src/services/change-streamer/subscriber.ts index e15858467b..d0e949fa39 100644 --- a/packages/zero-cache/src/services/change-streamer/subscriber.ts +++ b/packages/zero-cache/src/services/change-streamer/subscriber.ts @@ -57,13 +57,16 @@ export type SubscriberOptions = { onBackfillIgnored?: ((columns: string[]) => void) | undefined; /** - * Resolves to whether lag reporting is disabled upstream. If so, a - * {@link LAG_REPORTING_DISABLED} status is sent once the subscriber is - * aligned, as a stand-in for the lag reports that a subscriber would - * otherwise use to determine that it is caught up (e.g. to advertise - * readiness to serve traffic). + * Resolves, once it is known whether lag reporting is enabled upstream, to + * the means of requesting a lag report, or to `undefined` if it is disabled. + * + * Subscribers use lag reports to determine that they are caught up (e.g. to + * advertise readiness to serve traffic). Once the subscriber is aligned, a + * report is therefore requested, so that it is received without waiting out + * the report interval. If lag reporting is disabled, a + * {@link LAG_REPORTING_DISABLED} status is sent as a stand-in instead. */ - lagReportingDisabled?: Promise | undefined; + lagReports?: Promise | undefined; /** * Whether the subscriber switches over to another replication-manager @@ -131,7 +134,7 @@ export class Subscriber { readonly #backfills: BackfillState | undefined; readonly #onAligned: ((subscriber: Subscriber) => void) | undefined; readonly #onBackfillIgnored: ((columns: string[]) => void) | undefined; - readonly #lagReportingDisabled: Promise | undefined; + readonly #lagReports: Promise | undefined; #aligned = false; // The watermark to be acked for the subscriber to be caught up, set when // it is aligned. @@ -163,7 +166,7 @@ export class Subscriber { this.#backfills = options.backfills; this.#onAligned = options.onAligned; this.#onBackfillIgnored = options.onBackfillIgnored; - this.#lagReportingDisabled = options.lagReportingDisabled; + this.#lagReports = options.lagReports; } /** @@ -672,8 +675,10 @@ export class Subscriber { this.#onAligned?.(this); this.#caughtUpWatermark = this.#watermark; this.#checkCaughtUp(); - void this.#lagReportingDisabled?.then(disabled => { - if (disabled) { + void this.#lagReports?.then(lagReports => { + if (lagReports) { + lagReports.request(); + } else { this.sendStatus(LAG_REPORTING_DISABLED); } }); @@ -764,6 +769,15 @@ class ByteBackpressureGate { } } +/** Requests lag reports from upstream (see {@link SubscriberOptions.lagReports}). */ +export type LagReports = { + /** + * Requests that a lag report be sent now rather than at the next report + * interval. Requests are coalesced upstream, e.g. with a report in flight. + */ + request(): void; +}; + /** * Sent to an aligned subscriber when lag reporting is disabled upstream, * signaling that the subscriber is caught up. This is a lag report with a diff --git a/packages/zero-cache/src/services/change-streamer/test-utils.ts b/packages/zero-cache/src/services/change-streamer/test-utils.ts index b3de4a6fce..4cc4bf942f 100644 --- a/packages/zero-cache/src/services/change-streamer/test-utils.ts +++ b/packages/zero-cache/src/services/change-streamer/test-utils.ts @@ -1,5 +1,10 @@ import {Subscription} from '../../types/subscription.ts'; import type {ReplicatorMode} from '../replicator/replicator.ts'; +import type { + BackedUpWatermark, + BackupMonitor, + BackupSource, +} from './backup-monitor.ts'; import type {PreSerializedBatch} from './broadcast.ts'; import {PROTOCOL_VERSION, type Downstream} from './change-streamer.ts'; import {Subscriber, type SubscriberOptions} from './subscriber.ts'; @@ -41,3 +46,53 @@ export function createSubscriber( return [subscriber, received, sub]; } + +/** + * Consumes the monitor's backups as the change-streamer does, passing each + * watermark to `track`, and returns the monitor's run(). + */ +export function consume( + monitor: BackupMonitor, + track: (watermark: string) => void, +) { + void (async () => { + for await (const {watermark} of monitor.backups()) { + track(watermark); + } + })(); + return monitor.run(); +} + +/** + * A {@link BackupSource} through which a test reports backed-up watermarks to + * the change-streamer constructed with it (as its `backupMonitor`). + */ +export class FakeBackups implements BackupSource { + readonly #backups = Subscription.create({ + coalesce: curr => curr, + }); + readonly syncRequests: string[] = []; + + backups() { + return this.#backups; + } + + requestSync(watermark: string) { + this.syncRequests.push(watermark); + } + + /** + * Reports that the replica has been backed up through `watermark`, and + * resolves once the change-streamer has tracked it, i.e. requested the next + * one after processing it. + */ + async track(watermark: string) { + const result = await this.#backups.push({ + watermark, + backupTimeMs: Date.now(), + }).result; + if (result !== 'consumed') { + throw new Error(`backup watermark ${watermark} was ${result}`); + } + } +} diff --git a/packages/zero-client/package.json b/packages/zero-client/package.json index f5226195c6..5b0a696886 100644 --- a/packages/zero-client/package.json +++ b/packages/zero-client/package.json @@ -41,7 +41,7 @@ "replicache": "workspace:*", "shared": "workspace:*", "typescript": "~7.0.2", - "vitest": "^5.0.0", + "vitest": "^5.0.5", "zero-permissions": "workspace:*", "zero-protocol": "workspace:*", "zero-schema": "workspace:*", diff --git a/packages/zero-conformance/package.json b/packages/zero-conformance/package.json index 71bc2cab7d..523671b44f 100644 --- a/packages/zero-conformance/package.json +++ b/packages/zero-conformance/package.json @@ -21,7 +21,7 @@ "replicache": "workspace:*", "shared": "workspace:*", "typescript": "~7.0.2", - "vitest": "^5.0.0", + "vitest": "^5.0.5", "zero-client": "workspace:*", "zero-protocol": "workspace:*", "zero-schema": "workspace:*", diff --git a/packages/zero-events/package.json b/packages/zero-events/package.json index 9c52596169..8fe949cefa 100644 --- a/packages/zero-events/package.json +++ b/packages/zero-events/package.json @@ -38,7 +38,7 @@ "oxfmt": "^0.74.0", "shared": "workspace:*", "typescript": "~7.0.2", - "vitest": "^5.0.0" + "vitest": "^5.0.5" }, "packageManager": "pnpm@11.11.0" } diff --git a/packages/zero-permissions/package.json b/packages/zero-permissions/package.json index 46a9284798..d0be1bc5c2 100644 --- a/packages/zero-permissions/package.json +++ b/packages/zero-permissions/package.json @@ -24,7 +24,7 @@ "devDependencies": { "oxfmt": "^0.74.0", "typescript": "~7.0.2", - "vitest": "^5.0.0" + "vitest": "^5.0.5" }, "packageManager": "pnpm@11.11.0" } diff --git a/packages/zero-pg/package.json b/packages/zero-pg/package.json index c83725e42e..f4e060c482 100644 --- a/packages/zero-pg/package.json +++ b/packages/zero-pg/package.json @@ -20,7 +20,7 @@ }, "devDependencies": { "oxfmt": "^0.74.0", - "vitest": "^5.0.0", + "vitest": "^5.0.5", "zero-cache": "workspace:*", "zero-server": "workspace:*" }, diff --git a/packages/zero-protocol/package.json b/packages/zero-protocol/package.json index bcd09f1352..9abf8215bf 100644 --- a/packages/zero-protocol/package.json +++ b/packages/zero-protocol/package.json @@ -23,7 +23,7 @@ "oxfmt": "^0.74.0", "shared": "workspace:*", "typescript": "~7.0.2", - "vitest": "^5.0.0", + "vitest": "^5.0.5", "zero-types": "workspace:*" }, "packageManager": "pnpm@11.11.0" diff --git a/packages/zero-protocol/src/ast.ts b/packages/zero-protocol/src/ast.ts index 316dfc5cae..998369e0f2 100644 --- a/packages/zero-protocol/src/ast.ts +++ b/packages/zero-protocol/src/ast.ts @@ -9,6 +9,7 @@ import {compareUTF8} from 'compare-utf8'; import {assert} from '../../shared/src/asserts.ts'; import {isPlainObject} from '../../shared/src/deep-merge.ts'; +import {toSorted} from '../../shared/src/iterables.ts'; import {getOrInsertComputed} from '../../shared/src/map.ts'; import {must} from '../../shared/src/must.ts'; import * as v from '../../shared/src/valita.ts'; @@ -653,7 +654,9 @@ function normalizeRelated( ): readonly CorrelatedSubquery[] { for (let i = 1; i < related.length; i++) { if (cmpRelated(related[i - 1], related[i]) > 0) { - return related.toSorted(cmpRelated); + // Not Array.prototype.toSorted: Hermes (React Native) does not + // implement it. + return toSorted(related, cmpRelated); } } return related; diff --git a/packages/zero-react/package.json b/packages/zero-react/package.json index d31012c53b..f678b5ede0 100644 --- a/packages/zero-react/package.json +++ b/packages/zero-react/package.json @@ -31,7 +31,7 @@ "oxfmt": "^0.74.0", "react": "^19.3.1", "shared": "workspace:*", - "vitest": "^5.0.0", + "vitest": "^5.0.5", "zero-client": "workspace:*", "zero-schema": "workspace:*", "zql": "workspace:*" diff --git a/packages/zero-schema/package.json b/packages/zero-schema/package.json index 9efe458f8d..d2fa034d90 100644 --- a/packages/zero-schema/package.json +++ b/packages/zero-schema/package.json @@ -18,7 +18,7 @@ "devDependencies": { "oxfmt": "^0.74.0", "shared": "workspace:*", - "vitest": "^5.0.0", + "vitest": "^5.0.5", "zero-protocol": "workspace:*", "zero-types": "workspace:*" }, diff --git a/packages/zero-server/package.json b/packages/zero-server/package.json index ca1e136f98..cce3fb4762 100644 --- a/packages/zero-server/package.json +++ b/packages/zero-server/package.json @@ -34,7 +34,7 @@ "postgres": "3.4.7", "prisma": "^7.8.0", "shared": "workspace:*", - "vitest": "^5.0.0", + "vitest": "^5.0.5", "z2s": "workspace:*", "zero-cache": "workspace:*", "zero-protocol": "workspace:*", diff --git a/packages/zero-solid/package.json b/packages/zero-solid/package.json index 9f0e0eb0ea..a98f494a6b 100644 --- a/packages/zero-solid/package.json +++ b/packages/zero-solid/package.json @@ -28,7 +28,7 @@ "shared": "workspace:*", "solid-js": "^1.9.17", "vite-plugin-solid": "^2.11.12", - "vitest": "^5.0.0", + "vitest": "^5.0.5", "zero-client": "workspace:*", "zql": "workspace:*" }, diff --git a/packages/zero-swift-cli/package.json b/packages/zero-swift-cli/package.json index 13771efae4..b34c8e5664 100644 --- a/packages/zero-swift-cli/package.json +++ b/packages/zero-swift-cli/package.json @@ -39,7 +39,7 @@ "@types/node": "^22.19.19", "esbuild": "0.28.2", "oxfmt": "^0.74.0", - "vitest": "^5.0.0", + "vitest": "^5.0.5", "zero-schema": "workspace:*", "zero-types": "workspace:*", "zql": "workspace:*" diff --git a/packages/zero-types/package.json b/packages/zero-types/package.json index 3047f9438d..680c298ccf 100644 --- a/packages/zero-types/package.json +++ b/packages/zero-types/package.json @@ -18,7 +18,7 @@ "oxfmt": "^0.74.0", "shared": "workspace:*", "typescript": "~7.0.2", - "vitest": "^5.0.0" + "vitest": "^5.0.5" }, "packageManager": "pnpm@11.11.0" } diff --git a/packages/zero/package.json b/packages/zero/package.json index 9eec85278a..9d869fd260 100644 --- a/packages/zero/package.json +++ b/packages/zero/package.json @@ -182,7 +182,7 @@ "typedoc": "^0.28.19", "typescript": "~7.0.2", "vite": "^8.3.4", - "vitest": "^5.0.0", + "vitest": "^5.0.5", "zero-cache": "workspace:*", "zero-client": "workspace:*", "zero-pg": "workspace:*", diff --git a/packages/zql/package.json b/packages/zql/package.json index ecd3e082f5..0174def9e9 100644 --- a/packages/zql/package.json +++ b/packages/zql/package.json @@ -29,7 +29,7 @@ "oxfmt": "^0.74.0", "shared": "workspace:*", "typescript": "~7.0.2", - "vitest": "^5.0.0", + "vitest": "^5.0.5", "zero-protocol": "workspace:*", "zero-types": "workspace:*" }, diff --git a/packages/zqlite/package.json b/packages/zqlite/package.json index 650fcb479a..117fd5adfd 100644 --- a/packages/zqlite/package.json +++ b/packages/zqlite/package.json @@ -35,7 +35,7 @@ "oxfmt": "^0.74.0", "shared": "workspace:*", "typescript": "~7.0.2", - "vitest": "^5.0.0", + "vitest": "^5.0.5", "zero-protocol": "workspace:*", "zero-schema": "workspace:*", "zero-types": "workspace:*", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4c602b5568..ca7b3357a8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -67,14 +67,14 @@ importers: specifier: ^22.19.19 version: 22.19.19 '@vitest/browser': - specifier: ^5.0.0 - version: 5.0.0(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1))(vitest@5.0.0) + specifier: ^5.0.5 + version: 5.0.5(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1))(vitest@5.0.5) '@vitest/browser-playwright': - specifier: ^5.0.0 - version: 5.0.0(playwright@1.60.0)(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1))(vitest@5.0.0) + specifier: ^5.0.5 + version: 5.0.5(playwright@1.60.0)(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1))(vitest@5.0.5) '@vitest/coverage-v8': - specifier: ^5.0.0 - version: 5.0.0(@vitest/browser@5.0.0)(vitest@5.0.0) + specifier: ^5.0.5 + version: 5.0.5(@vitest/browser@5.0.5)(vitest@5.0.5) baseline-browser-mapping: specifier: ^2.11.29 version: 2.11.29 @@ -95,7 +95,7 @@ importers: version: 14.3.1 tsnapi: specifier: ^0.3.3 - version: 0.3.3(vitest@5.0.0) + version: 0.3.3(vitest@5.0.5) turbo: specifier: ^2.9.14 version: 2.9.14 @@ -103,8 +103,8 @@ importers: specifier: ~7.0.2 version: 7.0.2 vitest: - specifier: ^5.0.0 - version: 5.0.0(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@5.0.0)(@vitest/coverage-v8@5.0.0)(@vitest/ui@5.0.0)(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) + specifier: ^5.0.5 + version: 5.0.5(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@5.0.5)(@vitest/coverage-v8@5.0.5)(@vitest/ui@5.0.5)(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) zx: specifier: ^8.8.5 version: 8.8.5 @@ -115,8 +115,8 @@ importers: specifier: ^22.19.19 version: 22.19.19 '@vitest/coverage-v8': - specifier: ^5.0.0 - version: 5.0.0(@vitest/browser@5.0.0)(vitest@5.0.0) + specifier: ^5.0.5 + version: 5.0.5(@vitest/browser@5.0.5)(vitest@5.0.5) oxfmt: specifier: ^0.74.0 version: 0.74.0 @@ -124,8 +124,8 @@ importers: specifier: ~7.0.2 version: 7.0.2 vitest: - specifier: ^5.0.0 - version: 5.0.0(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@5.0.0)(@vitest/coverage-v8@5.0.0)(@vitest/ui@5.0.0)(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) + specifier: ^5.0.5 + version: 5.0.5(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@5.0.5)(@vitest/coverage-v8@5.0.5)(@vitest/ui@5.0.5)(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) apps/zbugs: dependencies: @@ -276,7 +276,7 @@ importers: version: 0.31.10 drizzle-orm: specifier: ^0.45.2 - version: 0.45.2(@electric-sql/pglite@0.4.1)(@op-engineering/op-sqlite@18.2.5(react-native@0.88.0(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.1))(react@19.3.1))(@opentelemetry/api@1.9.1)(@prisma/client@7.8.0(prisma@7.8.0(@types/node@22.19.19)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(magicast@0.5.4)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(typescript@7.0.2))(typescript@7.0.2))(@types/pg@8.20.0)(expo-sqlite@58.0.13(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.1))(react@19.3.1))(kysely@0.28.17)(mysql2@3.24.6(@types/node@22.19.19))(pg@8.20.0)(postgres@3.4.7(patch_hash=1ab70f82038a98cb34611424c35af68a1d87aa3bd8ed78a7f863c628ae3aeac7))(prisma@7.8.0(@types/node@22.19.19)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(magicast@0.5.4)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(typescript@7.0.2)) + version: 0.45.2(@electric-sql/pglite@0.4.1)(@op-engineering/op-sqlite@18.2.5(react-native@0.88.0(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.1))(react@19.3.1))(@opentelemetry/api@1.9.1)(@prisma/client@7.8.0(prisma@7.8.0(@types/node@22.19.19)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(magicast@0.5.5)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(typescript@7.0.2))(typescript@7.0.2))(@types/pg@8.20.0)(expo-sqlite@58.0.13(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.1))(react@19.3.1))(kysely@0.28.17)(mysql2@3.24.6(@types/node@22.19.19))(pg@8.20.0)(postgres@3.4.7(patch_hash=1ab70f82038a98cb34611424c35af68a1d87aa3bd8ed78a7f863c628ae3aeac7))(prisma@7.8.0(@types/node@22.19.19)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(magicast@0.5.5)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(typescript@7.0.2)) oxfmt: specifier: ^0.74.0 version: 0.74.0 @@ -305,8 +305,8 @@ importers: specifier: ^5.2.0 version: 5.2.0(typescript@7.0.2)(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@1.21.7)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.0)) vitest: - specifier: ^5.0.0 - version: 5.0.0(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@5.0.0)(@vitest/coverage-v8@5.0.0)(@vitest/ui@5.0.0)(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@1.21.7)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.0)) + specifier: ^5.0.5 + version: 5.0.5(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@5.0.5)(@vitest/coverage-v8@5.0.5)(@vitest/ui@5.0.5)(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@1.21.7)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.0)) zero-cache: specifier: workspace:* version: link:../../packages/zero-cache @@ -449,8 +449,8 @@ importers: specifier: workspace:* version: link:../shared vitest: - specifier: ^5.0.0 - version: 5.0.0(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@5.0.0)(@vitest/coverage-v8@5.0.0)(@vitest/ui@5.0.0)(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) + specifier: ^5.0.5 + version: 5.0.5(@opentelemetry/api@1.9.1)(@types/node@22.20.5)(@vitest/browser-playwright@5.0.5)(@vitest/coverage-v8@5.0.5)(@vitest/ui@5.0.5)(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) zero-cache: specifier: workspace:* version: link:../zero-cache @@ -480,8 +480,8 @@ importers: specifier: workspace:* version: link:../shared vitest: - specifier: ^5.0.0 - version: 5.0.0(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@5.0.0)(@vitest/coverage-v8@5.0.0)(@vitest/ui@5.0.0)(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) + specifier: ^5.0.5 + version: 5.0.5(@opentelemetry/api@1.9.1)(@types/node@22.20.5)(@vitest/browser-playwright@5.0.5)(@vitest/coverage-v8@5.0.5)(@vitest/ui@5.0.5)(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) zero-cache: specifier: workspace:* version: link:../zero-cache @@ -516,8 +516,8 @@ importers: specifier: ~7.0.2 version: 7.0.2 vitest: - specifier: ^5.0.0 - version: 5.0.0(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@5.0.0)(@vitest/coverage-v8@5.0.0)(@vitest/ui@5.0.0)(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) + specifier: ^5.0.5 + version: 5.0.5(@opentelemetry/api@1.9.1)(@types/node@22.20.5)(@vitest/browser-playwright@5.0.5)(@vitest/coverage-v8@5.0.5)(@vitest/ui@5.0.5)(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) packages/otel: dependencies: @@ -614,8 +614,8 @@ importers: specifier: ~7.0.2 version: 7.0.2 vitest: - specifier: ^5.0.0 - version: 5.0.0(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@5.0.0)(@vitest/coverage-v8@5.0.0)(@vitest/ui@5.0.0)(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) + specifier: ^5.0.5 + version: 5.0.5(@opentelemetry/api@1.9.1)(@types/node@22.20.5)(@vitest/browser-playwright@5.0.5)(@vitest/coverage-v8@5.0.5)(@vitest/ui@5.0.5)(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) xbytes: specifier: ^1.9.1 version: 1.9.1 @@ -824,8 +824,8 @@ importers: specifier: ~7.0.2 version: 7.0.2 vitest: - specifier: ^5.0.0 - version: 5.0.0(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@5.0.0)(@vitest/coverage-v8@5.0.0)(@vitest/ui@5.0.0)(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) + specifier: ^5.0.5 + version: 5.0.5(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@5.0.5)(@vitest/coverage-v8@5.0.5)(@vitest/ui@5.0.5)(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) packages/z2s: dependencies: @@ -855,8 +855,8 @@ importers: specifier: workspace:* version: link:../shared vitest: - specifier: ^5.0.0 - version: 5.0.0(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@5.0.0)(@vitest/coverage-v8@5.0.0)(@vitest/ui@5.0.0)(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) + specifier: ^5.0.5 + version: 5.0.5(@opentelemetry/api@1.9.1)(@types/node@22.20.5)(@vitest/browser-playwright@5.0.5)(@vitest/coverage-v8@5.0.5)(@vitest/ui@5.0.5)(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) zero-cache: specifier: workspace:* version: link:../zero-cache @@ -1060,8 +1060,8 @@ importers: specifier: ^8.3.4 version: 8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.0) vitest: - specifier: ^5.0.0 - version: 5.0.0(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@5.0.0)(@vitest/coverage-v8@5.0.0)(@vitest/ui@5.0.0)(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.0)) + specifier: ^5.0.5 + version: 5.0.5(@opentelemetry/api@1.9.1)(@types/node@22.20.5)(@vitest/browser-playwright@5.0.5)(@vitest/coverage-v8@5.0.5)(@vitest/ui@5.0.5)(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.0)) zero-cache: specifier: workspace:* version: link:../zero-cache @@ -1259,8 +1259,8 @@ importers: specifier: ~7.0.2 version: 7.0.2 vitest: - specifier: ^5.0.0 - version: 5.0.0(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@5.0.0)(@vitest/coverage-v8@5.0.0)(@vitest/ui@5.0.0)(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) + specifier: ^5.0.5 + version: 5.0.5(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@5.0.5)(@vitest/coverage-v8@5.0.5)(@vitest/ui@5.0.5)(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) zero-permissions: specifier: workspace:* version: link:../zero-permissions @@ -1314,8 +1314,8 @@ importers: specifier: ~7.0.2 version: 7.0.2 vitest: - specifier: ^5.0.0 - version: 5.0.0(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@5.0.0)(@vitest/coverage-v8@5.0.0)(@vitest/ui@5.0.0)(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) + specifier: ^5.0.5 + version: 5.0.5(@opentelemetry/api@1.9.1)(@types/node@22.20.5)(@vitest/browser-playwright@5.0.5)(@vitest/coverage-v8@5.0.5)(@vitest/ui@5.0.5)(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) zero-permissions: specifier: workspace:* version: link:../zero-permissions @@ -1350,8 +1350,8 @@ importers: specifier: ~7.0.2 version: 7.0.2 vitest: - specifier: ^5.0.0 - version: 5.0.0(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@5.0.0)(@vitest/coverage-v8@5.0.0)(@vitest/ui@5.0.0)(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) + specifier: ^5.0.5 + version: 5.0.5(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@5.0.5)(@vitest/coverage-v8@5.0.5)(@vitest/ui@5.0.5)(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) zero-client: specifier: workspace:* version: link:../zero-client @@ -1383,8 +1383,8 @@ importers: specifier: ~7.0.2 version: 7.0.2 vitest: - specifier: ^5.0.0 - version: 5.0.0(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@5.0.0)(@vitest/coverage-v8@5.0.0)(@vitest/ui@5.0.0)(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) + specifier: ^5.0.5 + version: 5.0.5(@opentelemetry/api@1.9.1)(@types/node@22.20.5)(@vitest/browser-playwright@5.0.5)(@vitest/coverage-v8@5.0.5)(@vitest/ui@5.0.5)(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) packages/zero-permissions: dependencies: @@ -1411,8 +1411,8 @@ importers: specifier: ~7.0.2 version: 7.0.2 vitest: - specifier: ^5.0.0 - version: 5.0.0(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@5.0.0)(@vitest/coverage-v8@5.0.0)(@vitest/ui@5.0.0)(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) + specifier: ^5.0.5 + version: 5.0.5(@opentelemetry/api@1.9.1)(@types/node@22.20.5)(@vitest/browser-playwright@5.0.5)(@vitest/coverage-v8@5.0.5)(@vitest/ui@5.0.5)(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) packages/zero-pg: dependencies: @@ -1424,8 +1424,8 @@ importers: specifier: ^0.74.0 version: 0.74.0 vitest: - specifier: ^5.0.0 - version: 5.0.0(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@5.0.0)(@vitest/coverage-v8@5.0.0)(@vitest/ui@5.0.0)(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) + specifier: ^5.0.5 + version: 5.0.5(@opentelemetry/api@1.9.1)(@types/node@22.20.5)(@vitest/browser-playwright@5.0.5)(@vitest/coverage-v8@5.0.5)(@vitest/ui@5.0.5)(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) zero-cache: specifier: workspace:* version: link:../zero-cache @@ -1455,8 +1455,8 @@ importers: specifier: ~7.0.2 version: 7.0.2 vitest: - specifier: ^5.0.0 - version: 5.0.0(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@5.0.0)(@vitest/coverage-v8@5.0.0)(@vitest/ui@5.0.0)(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) + specifier: ^5.0.5 + version: 5.0.5(@opentelemetry/api@1.9.1)(@types/node@22.20.5)(@vitest/browser-playwright@5.0.5)(@vitest/coverage-v8@5.0.5)(@vitest/ui@5.0.5)(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) zero-types: specifier: workspace:* version: link:../zero-types @@ -1489,8 +1489,8 @@ importers: specifier: workspace:* version: link:../shared vitest: - specifier: ^5.0.0 - version: 5.0.0(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@5.0.0)(@vitest/coverage-v8@5.0.0)(@vitest/ui@5.0.0)(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) + specifier: ^5.0.5 + version: 5.0.5(@opentelemetry/api@1.9.1)(@types/node@22.20.5)(@vitest/browser-playwright@5.0.5)(@vitest/coverage-v8@5.0.5)(@vitest/ui@5.0.5)(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) zero-client: specifier: workspace:* version: link:../zero-client @@ -1526,8 +1526,8 @@ importers: specifier: workspace:* version: link:../shared vitest: - specifier: ^5.0.0 - version: 5.0.0(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@5.0.0)(@vitest/coverage-v8@5.0.0)(@vitest/ui@5.0.0)(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) + specifier: ^5.0.5 + version: 5.0.5(@opentelemetry/api@1.9.1)(@types/node@22.20.5)(@vitest/browser-playwright@5.0.5)(@vitest/coverage-v8@5.0.5)(@vitest/ui@5.0.5)(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) zero-protocol: specifier: workspace:* version: link:../zero-protocol @@ -1546,7 +1546,7 @@ importers: version: 7.8.0 '@prisma/client': specifier: ^7.8.0 - version: 7.8.0(prisma@7.8.0(@types/node@22.20.5)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(magicast@0.5.4)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(typescript@6.0.3))(typescript@6.0.3) + version: 7.8.0(prisma@7.8.0(@types/node@22.20.5)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(magicast@0.5.5)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(typescript@6.0.3))(typescript@6.0.3) '@standard-schema/spec': specifier: ^1.1.0 version: 1.1.0 @@ -1555,7 +1555,7 @@ importers: version: 8.20.0 drizzle-orm: specifier: ^0.45.2 - version: 0.45.2(@electric-sql/pglite@0.4.1)(@op-engineering/op-sqlite@18.2.5(react-native@0.88.0(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.1))(react@19.3.1))(@opentelemetry/api@1.9.1)(@prisma/client@7.8.0(prisma@7.8.0(@types/node@22.20.5)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(magicast@0.5.4)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(typescript@6.0.3))(typescript@6.0.3))(@types/pg@8.20.0)(expo-sqlite@58.0.13(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.1))(react@19.3.1))(kysely@0.28.17)(mysql2@3.24.6(@types/node@22.20.5))(pg@8.20.0)(postgres@3.4.7(patch_hash=1ab70f82038a98cb34611424c35af68a1d87aa3bd8ed78a7f863c628ae3aeac7))(prisma@7.8.0(@types/node@22.20.5)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(magicast@0.5.4)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(typescript@6.0.3)) + version: 0.45.2(@electric-sql/pglite@0.4.1)(@op-engineering/op-sqlite@18.2.5(react-native@0.88.0(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.1))(react@19.3.1))(@opentelemetry/api@1.9.1)(@prisma/client@7.8.0(prisma@7.8.0(@types/node@22.20.5)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(magicast@0.5.5)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(typescript@6.0.3))(typescript@6.0.3))(@types/pg@8.20.0)(expo-sqlite@58.0.13(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.1))(react@19.3.1))(kysely@0.28.17)(mysql2@3.24.6(@types/node@22.20.5))(pg@8.20.0)(postgres@3.4.7(patch_hash=1ab70f82038a98cb34611424c35af68a1d87aa3bd8ed78a7f863c628ae3aeac7))(prisma@7.8.0(@types/node@22.20.5)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(magicast@0.5.5)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(typescript@6.0.3)) kysely: specifier: ^0.28.17 version: 0.28.17 @@ -1570,13 +1570,13 @@ importers: version: 3.4.7(patch_hash=1ab70f82038a98cb34611424c35af68a1d87aa3bd8ed78a7f863c628ae3aeac7) prisma: specifier: ^7.8.0 - version: 7.8.0(@types/node@22.20.5)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(magicast@0.5.4)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(typescript@6.0.3) + version: 7.8.0(@types/node@22.20.5)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(magicast@0.5.5)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(typescript@6.0.3) shared: specifier: workspace:* version: link:../shared vitest: - specifier: ^5.0.0 - version: 5.0.0(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@5.0.0)(@vitest/coverage-v8@5.0.0)(@vitest/ui@5.0.0)(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) + specifier: ^5.0.5 + version: 5.0.5(@opentelemetry/api@1.9.1)(@types/node@22.20.5)(@vitest/browser-playwright@5.0.5)(@vitest/coverage-v8@5.0.5)(@vitest/ui@5.0.5)(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) z2s: specifier: workspace:* version: link:../z2s @@ -1617,8 +1617,8 @@ importers: specifier: ^2.11.12 version: 2.11.12(solid-js@1.9.17)(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) vitest: - specifier: ^5.0.0 - version: 5.0.0(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@5.0.0)(@vitest/coverage-v8@5.0.0)(@vitest/ui@5.0.0)(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) + specifier: ^5.0.5 + version: 5.0.5(@opentelemetry/api@1.9.1)(@types/node@22.20.5)(@vitest/browser-playwright@5.0.5)(@vitest/coverage-v8@5.0.5)(@vitest/ui@5.0.5)(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) zero-client: specifier: workspace:* version: link:../zero-client @@ -1648,8 +1648,8 @@ importers: specifier: ^0.74.0 version: 0.74.0 vitest: - specifier: ^5.0.0 - version: 5.0.0(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@5.0.0)(@vitest/coverage-v8@5.0.0)(@vitest/ui@5.0.0)(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) + specifier: ^5.0.5 + version: 5.0.5(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@5.0.5)(@vitest/coverage-v8@5.0.5)(@vitest/ui@5.0.5)(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) zero-schema: specifier: workspace:* version: link:../zero-schema @@ -1672,8 +1672,8 @@ importers: specifier: ~7.0.2 version: 7.0.2 vitest: - specifier: ^5.0.0 - version: 5.0.0(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@5.0.0)(@vitest/coverage-v8@5.0.0)(@vitest/ui@5.0.0)(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) + specifier: ^5.0.5 + version: 5.0.5(@opentelemetry/api@1.9.1)(@types/node@22.20.5)(@vitest/browser-playwright@5.0.5)(@vitest/coverage-v8@5.0.5)(@vitest/ui@5.0.5)(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) packages/zql: dependencies: @@ -1712,8 +1712,8 @@ importers: specifier: ~7.0.2 version: 7.0.2 vitest: - specifier: ^5.0.0 - version: 5.0.0(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@5.0.0)(@vitest/coverage-v8@5.0.0)(@vitest/ui@5.0.0)(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) + specifier: ^5.0.5 + version: 5.0.5(@opentelemetry/api@1.9.1)(@types/node@22.20.5)(@vitest/browser-playwright@5.0.5)(@vitest/coverage-v8@5.0.5)(@vitest/ui@5.0.5)(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) zero-protocol: specifier: workspace:* version: link:../zero-protocol @@ -1751,8 +1751,8 @@ importers: specifier: ~7.0.2 version: 7.0.2 vitest: - specifier: ^5.0.0 - version: 5.0.0(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@5.0.0)(@vitest/coverage-v8@5.0.0)(@vitest/ui@5.0.0)(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) + specifier: ^5.0.5 + version: 5.0.5(@opentelemetry/api@1.9.1)(@types/node@22.20.5)(@vitest/browser-playwright@5.0.5)(@vitest/coverage-v8@5.0.5)(@vitest/ui@5.0.5)(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) zero-cache: specifier: workspace:* version: link:../zero-cache @@ -1814,8 +1814,8 @@ importers: specifier: ~7.0.2 version: 7.0.2 vitest: - specifier: ^5.0.0 - version: 5.0.0(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@5.0.0)(@vitest/coverage-v8@5.0.0)(@vitest/ui@5.0.0)(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) + specifier: ^5.0.5 + version: 5.0.5(@opentelemetry/api@1.9.1)(@types/node@22.20.5)(@vitest/browser-playwright@5.0.5)(@vitest/coverage-v8@5.0.5)(@vitest/ui@5.0.5)(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) z2s: specifier: workspace:* version: link:../z2s @@ -1881,8 +1881,8 @@ importers: specifier: ~7.0.2 version: 7.0.2 vitest: - specifier: ^5.0.0 - version: 5.0.0(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@5.0.0)(@vitest/coverage-v8@5.0.0)(@vitest/ui@5.0.0)(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) + specifier: ^5.0.5 + version: 5.0.5(@opentelemetry/api@1.9.1)(@types/node@22.20.5)(@vitest/browser-playwright@5.0.5)(@vitest/coverage-v8@5.0.5)(@vitest/ui@5.0.5)(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) zero-protocol: specifier: workspace:* version: link:../zero-protocol @@ -1912,8 +1912,8 @@ importers: specifier: ~7.0.2 version: 7.0.2 vitest: - specifier: ^5.0.0 - version: 5.0.0(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@5.0.0)(@vitest/coverage-v8@5.0.0)(@vitest/ui@5.0.0)(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) + specifier: ^5.0.5 + version: 5.0.5(@opentelemetry/api@1.9.1)(@types/node@22.20.5)(@vitest/browser-playwright@5.0.5)(@vitest/coverage-v8@5.0.5)(@vitest/ui@5.0.5)(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) prod/sst: dependencies: @@ -1943,8 +1943,8 @@ importers: specifier: ~7.0.2 version: 7.0.2 vitest: - specifier: ^5.0.0 - version: 5.0.0(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@5.0.0)(@vitest/coverage-v8@5.0.0)(@vitest/ui@5.0.0)(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) + specifier: ^5.0.5 + version: 5.0.5(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@5.0.5)(@vitest/coverage-v8@5.0.5)(@vitest/ui@5.0.5)(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) tools/client-simulator: dependencies: @@ -2014,8 +2014,8 @@ importers: specifier: ~7.0.2 version: 7.0.2 vitest: - specifier: ^5.0.0 - version: 5.0.0(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@5.0.0)(@vitest/coverage-v8@5.0.0)(@vitest/ui@5.0.0)(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) + specifier: ^5.0.5 + version: 5.0.5(@opentelemetry/api@1.9.1)(@types/node@22.20.5)(@vitest/browser-playwright@5.0.5)(@vitest/coverage-v8@5.0.5)(@vitest/ui@5.0.5)(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) tools/process-tracker: dependencies: @@ -6717,38 +6717,38 @@ packages: babel-plugin-react-compiler: optional: true - '@vitest/browser-playwright@5.0.0': - resolution: {integrity: sha512-N+gED9y4/8pypaHjz/x0ah3CjoBr+N0hWWT+Gq4VXtMzyB+rUIdHni0ulzpD1j4H77iWy5Jg8PRMlVn6kjxo6g==} + '@vitest/browser-playwright@5.0.5': + resolution: {integrity: sha512-lcdThFUUcMV9mGGV1SUiZXPxfjgW4CssA7o3STxzh8WK0tT2r526kc12tZIBRZfoLMwkhxjLmMXkWXONzdsDQw==} peerDependencies: playwright: ^1.55.1 - vitest: 5.0.0 + vitest: 5.0.5 - '@vitest/browser@5.0.0': - resolution: {integrity: sha512-JC9FG5xIRxPHXJPcdCaluIJcEoeM0IwGQ3xneuJk09LXKHRNs40BqWDWymQikbb20yOpYvzpKzmYgPRuSzKmvg==} + '@vitest/browser@5.0.5': + resolution: {integrity: sha512-IDMoZX/iZ8rkilw7weaSgN8tJ7iZ9hUv0wYoKXyiyjWjWFwLSWAY707DcVTgxTYOudWC1E3HA9LYn7kVa9a7uQ==} peerDependencies: - vitest: 5.0.0 + vitest: 5.0.5 - '@vitest/coverage-v8@5.0.0': - resolution: {integrity: sha512-toMg6PZGCIa/lQNCDoASrfb1ly4hsUKXFtFYC9kD4t78o5Y6LyNJU7AENt8eHPr3quYdxaxK7hj2mnbFfUk9NA==} + '@vitest/coverage-v8@5.0.5': + resolution: {integrity: sha512-4KpkGdIrSqBihx/JyP/e+S+IKB98etg1XDQiLAxksLfcShnwi1VReGeOT9NI43hLHbknosKp/1CCLn9Zgcs8mg==} peerDependencies: - '@vitest/browser': 5.0.0 - vitest: 5.0.0 + '@vitest/browser': 5.0.5 + vitest: 5.0.5 peerDependenciesMeta: '@vitest/browser': optional: true - '@vitest/istanbul-lib-coverage@1.0.1': - resolution: {integrity: sha512-k3DJZ8LhMBK9NS4SclF1ASD3OgXEWDorbIcPTRDK0/Zae6fRvu+fJRxtFdLfHsa9Y24beCdPnoNZ4LviTNstfA==} + '@vitest/istanbul-lib-coverage@1.0.2': + resolution: {integrity: sha512-9J/JMwOf9AoJhAywhrn7ScKTL38hsWQP/qPG60OtaAFcQ5OXPwKsxZFlbnuCKmZ61m8/lGgHYnFpdyQZUvG/iA==} engines: {node: '>=22'} - '@vitest/istanbul-lib-report@1.0.1': - resolution: {integrity: sha512-1EOLRfsTMnyAr3+kEAsP4o9dhaDlGPpD7H5iLBBeq//YpNB1VIahkPhB+eRp9N2Dkfw8oySROjE3yf9XDeaIkQ==} + '@vitest/istanbul-lib-report@1.0.2': + resolution: {integrity: sha512-gUsfXZJbzPamoIY5TvHFiMMoXESBrUMo+xqaj+rYrWI69+EnvRlYBlP96ZnHPY4vX8kyUpgAnFUCg5wZG/HkDQ==} engines: {node: '>=22'} - '@vitest/mocker@5.0.0': - resolution: {integrity: sha512-66PGTMIiVJP3t4a5yxU9qPtf7MdTBs8jmToMvy+HVflB3Yy13WJZTtPePdvU+wjRV02SKK5doLbSA6o9pwOmiA==} + '@vitest/mocker@5.0.5': + resolution: {integrity: sha512-xLL3lkDegJZw+MGoGaCCsFayDZthGgpAtr3Vziyfc4p7xH89K7KRPcRBBh/kEYm0Aqkt5DtTmkYpBtRV/FrHZg==} peerDependencies: - msw: ^2.4.9 + msw: ^2.4.9 || ^3.0.0 vite: ^6.0.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: msw: @@ -6759,22 +6759,22 @@ packages: '@vitest/pretty-format@4.1.6': resolution: {integrity: sha512-h5SxD/IzNhZYnrSZRsUZQIC+vD0GY8cUvq0iwsmkFKixRCKLLWqCXa/FIQ4S1R+sI+PGoojkHsdNrbZiM9Qpgw==} - '@vitest/pretty-format@5.0.0': - resolution: {integrity: sha512-PVRNuB3wpReb4SQEs4zTKM4KWFhQ5pw3spE8naoDJNB5T5aWRzGKHwXcLUllr0WeOTXpB6bSr3CJLo5+7XQSSQ==} + '@vitest/pretty-format@5.0.5': + resolution: {integrity: sha512-24sSdIlMA9DVPXnYiB9siwU0bTd887Y9jX3+vabztCSsvHwo7ipvJ/a2L3Rljw8+2rK1f5fCj3h5U9I/Jsqgbw==} - '@vitest/spy@5.0.0': - resolution: {integrity: sha512-uy+luWBAPw9XfthoHi5AkfHUnuPYEESjl0p/r+meoBnU8bxg5GDQ3Ey8MjcJ6sqahkL4PFyrvfMJJBw7LbU06g==} + '@vitest/spy@5.0.5': + resolution: {integrity: sha512-lwJRExuH417257Q5wv9QYugN9IMJkoxcrKrYxGPQkBzl7wlNpTUxD/5yNX4ESKVUIShKPgkGchYPFMWOOk0wzw==} - '@vitest/ui@5.0.0': - resolution: {integrity: sha512-h2FIFwggCY2GxUd2UdQoYNVQkOIqEQLPhNREcl3FUiRsdzQep7NWwYbSmhGEA9nFLPDq5pXzRMcBZQU8Py83sg==} + '@vitest/ui@5.0.5': + resolution: {integrity: sha512-dCoGUOdgmCaSB2g+WQ/spPCBkytAWdNUkFSXY4z+eYp2d8JEOY8v0uDRaJTG9qf/kTlTVZ4CuYMYMz/f1HS/qQ==} peerDependencies: - vitest: 5.0.0 + vitest: 5.0.5 '@vitest/utils@4.1.6': resolution: {integrity: sha512-FxIY+U81R3LGKCxaHHFRQ5+g6/iRgGLmeHWdp2Amj4ljQRrEIWHmZyDfDYBRZlpyqA7qKxtS9DD1dhk8RnRIVQ==} - '@vitest/utils@5.0.0': - resolution: {integrity: sha512-dO++xL3vDfvhTAVimfkuQUA3k+JClIF1i1vAkPqpcGAthRmeWnXmHB7YPViPvgCwviX8u7Y5W1u2N//AaQr3fw==} + '@vitest/utils@5.0.5': + resolution: {integrity: sha512-/rBu7gzLDQI/SC79Tn98b5DwOgaHU/Gt4I9OVeP3YofzNaYtbImRCOJQmT1S6vOJg9gEIlWC+r3QxKkjuSa1nQ==} '@webassemblyjs/ast@1.14.1': resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==} @@ -7059,8 +7059,8 @@ packages: resolution: {integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==} engines: {node: '>=4'} - ast-v8-to-istanbul@1.0.6: - resolution: {integrity: sha512-fvpl29helSO2w/z7utIbrkNXILdrLwDwAMH2I/zPKlGf5244+gf+B4cyS1sANcrPY2h+hWCGSgC8N61s/+AF9A==} + ast-v8-to-istanbul@1.0.7: + resolution: {integrity: sha512-kFL68AG6ajd8fg248zwM9GQrUWEp79gsmjum34OEXjs4yHuUMZfYKwOLW9GMmB4oNvVrj+EAGxsP7ye2UR9UlA==} astring@1.9.0: resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==} @@ -7455,8 +7455,8 @@ packages: ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} - chai@6.2.2: - resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + chai@6.3.0: + resolution: {integrity: sha512-XWAtwJ6OHO+tj0EKCs0Y2UamnyOxseZWltU4x2U2wh8g4AigdjwvtUjvLP2tqkA/avxHEtzxNaqGq/YGNwckKg==} engines: {node: '>=18'} chalk-template@0.4.0: @@ -9808,11 +9808,11 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - magic-string@1.3.1: - resolution: {integrity: sha512-rm91zr2Ou+XueDTohjQQjdQEcYM6zVi8KVUCG8Ec3vHwUEKrhSdCNyfuIywkA6hcCAteIn0ZOtAHA6eGpiX+Pg==} + magic-string@1.4.3: + resolution: {integrity: sha512-z12OxmaPGE0F4xlpGdjuAUckipLpQlTAuAmyGhNoD7ODpYUzvDfvtxUbjM/jwznyP178oju/KDdyi220wNJ0RQ==} - magicast@0.5.4: - resolution: {integrity: sha512-llBEhWm1SacoRwgHUoQJYtwp4PBLF4faQi5TCpIGyGs9n4y5+juI0tDgyKIfpqxckRHaHzouUEph3THklWh03w==} + magicast@0.5.5: + resolution: {integrity: sha512-UicdXN8zQ3JHlxVq+28afMXPr1z7WNY6+7EJnzTdQWkTAlMLF5fNCCKxJHBQwGaNGR11581EiQmQzx73+MvszA==} makeerror@1.0.12: resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} @@ -10406,8 +10406,8 @@ packages: obuf@1.1.2: resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==} - obug@2.2.1: - resolution: {integrity: sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==} + obug@3.0.0: + resolution: {integrity: sha512-5vvB5+W7ePv+p3uqxi+RcW1XAzLW0/hxt3/4X4Lc4qHudzOhmBiBwOY6DRob4WnanAEGvNcLjF+KNOufrUoEQw==} engines: {node: '>=12.20.0'} ohash@2.0.11: @@ -12053,9 +12053,6 @@ packages: resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==} engines: {node: '>= 0.4'} - siginfo@2.0.0: - resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} - signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} @@ -12217,9 +12214,6 @@ packages: resolution: {integrity: sha512-4gcCaHy6v28+NFALic5PwrVMNHDIaCU00Mw6yjbnz8yczY6OaVrWY5YucdQaynOf7u544VNVe8k5/1c1/5Zk8Q==} hasBin: true - stackback@0.0.2: - resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - stackframe@1.3.4: resolution: {integrity: sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==} @@ -12241,8 +12235,8 @@ packages: std-env@3.10.0: resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} - std-env@4.2.0: - resolution: {integrity: sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==} + std-env@4.3.0: + resolution: {integrity: sha512-OtU/EgQ1kIm5KwqQpBC6ZEMXrZRui11w8zgfTWp8cdO9B8OaPsbA8bTHO2P+HNo1VlUTGMVBwPhydu6poeXiag==} stream-buffers@2.2.0: resolution: {integrity: sha512-uyQK/mx5QjHun80FLJTfaWE7JtwfRMKBLkMne6udYOmvH0CawotVa7TfgYHzAnpphn4+TweIx1QKMnRIbipmUg==} @@ -12535,12 +12529,12 @@ packages: tiny-warning@1.0.3: resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==} - tinybench@6.1.4: - resolution: {integrity: sha512-9APumHG7r4yOk4X4WlkmE71aZcv1gvin1czO3OQ1U9iJcFA5Ja/ygyb0vPOVHTthFozUYs8CLoLUlM8grb2lTQ==} + tinybench@6.2.2: + resolution: {integrity: sha512-nIZ2N2dFpjBsg2hgitKu7mA+FvsIJuGJOwysRngmVRRmWgR/UyPjwJj8c/fT/dhm8EkHnF5EOogRf+0Z2qrimA==} engines: {node: '>=20.0.0'} - tinyexec@1.3.0: - resolution: {integrity: sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==} + tinyexec@1.3.1: + resolution: {integrity: sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==} engines: {node: '>=18'} tinyglobby@0.2.16: @@ -12563,6 +12557,10 @@ packages: resolution: {integrity: sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==} engines: {node: '>=14.0.0'} + tinyrainbow@3.2.0: + resolution: {integrity: sha512-LgO3D9yZJjApUiuUfl9iFAwrtaX4+lok3wJIqttGoKCHlWUqHqbQpnxCf82L8FjgKsh4iGo78hqJwgL8F6To2A==} + engines: {node: '>=14.0.0'} + tmp@0.2.7: resolution: {integrity: sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==} engines: {node: '>=14.14'} @@ -12966,20 +12964,20 @@ packages: vite: optional: true - vitest@5.0.0: - resolution: {integrity: sha512-gpsMNoRhMjMktVxPtstOH4/PJuPyovVaMDr4oDilXaGH1EcqM2OE96SoHT2VIQ6fTGtTjqmHDrEu2X9RQiXf8Q==} + vitest@5.0.5: + resolution: {integrity: sha512-cPODOMF8YnkiiYtDdHo6n4wWmBrOBBbOIYH2d33M4l3CdNn72h+qMmO6uXffUN6A5AZFXNND/d+krS9mgkXRfg==} engines: {node: ^22.12.0 || ^24.0.0 || >=26.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@opentelemetry/api': ^1.9.0 '@types/node': ^22.19.19 - '@vitest/browser-playwright': 5.0.0 - '@vitest/browser-preview': 5.0.0 + '@vitest/browser-playwright': 5.0.5 + '@vitest/browser-preview': 5.0.5 '@vitest/browser-webdriverio': ^5.0.0-beta.5 || >=5.0.0 - '@vitest/coverage-istanbul': 5.0.0 - '@vitest/coverage-v8': 5.0.0 - '@vitest/ui': 5.0.0 + '@vitest/coverage-istanbul': 5.0.5 + '@vitest/coverage-v8': 5.0.5 + '@vitest/ui': 5.0.5 happy-dom: '*' jsdom: '*' vite: ^6.4.0 || ^7.0.0 || ^8.0.0 @@ -13119,9 +13117,9 @@ packages: engines: {node: ^16.13.0 || >=18.0.0} hasBin: true - why-is-node-running@2.3.0: - resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} - engines: {node: '>=8'} + why-is-node-running@3.2.1: + resolution: {integrity: sha512-Tb2FUhB4vUsGQlfSquQLYkApkuPAFQXGFzxWKHHumVz2dK+X1RUm/HnID4+TfIGYJ1kTcwOaCk/buYCEJr6YjQ==} + engines: {node: '>=20.11'} hasBin: true widest-line@4.0.1: @@ -16623,10 +16621,10 @@ snapshots: '@types/ws': 8.18.2 duplexify: 3.7.1 inherits: 2.0.4 - isomorphic-ws: 4.0.1(ws@7.5.13) + isomorphic-ws: 4.0.1(ws@8.22.0) readable-stream: 2.3.8 safe-buffer: 5.2.1 - ws: 7.5.13 + ws: 8.22.0 xtend: 4.0.2 transitivePeerDependencies: - bufferutil @@ -18144,24 +18142,24 @@ snapshots: '@prisma/client-runtime-utils@7.8.0': {} - '@prisma/client@7.8.0(prisma@7.8.0(@types/node@22.19.19)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(magicast@0.5.4)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(typescript@7.0.2))(typescript@7.0.2)': + '@prisma/client@7.8.0(prisma@7.8.0(@types/node@22.19.19)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(magicast@0.5.5)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(typescript@7.0.2))(typescript@7.0.2)': dependencies: '@prisma/client-runtime-utils': 7.8.0 optionalDependencies: - prisma: 7.8.0(@types/node@22.19.19)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(magicast@0.5.4)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(typescript@7.0.2) + prisma: 7.8.0(@types/node@22.19.19)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(magicast@0.5.5)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(typescript@7.0.2) typescript: 7.0.2 optional: true - '@prisma/client@7.8.0(prisma@7.8.0(@types/node@22.20.5)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(magicast@0.5.4)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(typescript@6.0.3))(typescript@6.0.3)': + '@prisma/client@7.8.0(prisma@7.8.0(@types/node@22.20.5)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(magicast@0.5.5)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(typescript@6.0.3))(typescript@6.0.3)': dependencies: '@prisma/client-runtime-utils': 7.8.0 optionalDependencies: - prisma: 7.8.0(@types/node@22.20.5)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(magicast@0.5.4)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(typescript@6.0.3) + prisma: 7.8.0(@types/node@22.20.5)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(magicast@0.5.5)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(typescript@6.0.3) typescript: 6.0.3 - '@prisma/config@7.8.0(magicast@0.5.4)': + '@prisma/config@7.8.0(magicast@0.5.5)': dependencies: - c12: 3.3.4(magicast@0.5.4) + c12: 3.3.4(magicast@0.5.5) deepmerge-ts: 8.0.2 effect: 3.20.0 empathic: 2.0.0 @@ -19313,13 +19311,13 @@ snapshots: optionalDependencies: babel-plugin-react-compiler: 1.0.0 - '@vitest/browser-playwright@5.0.0(playwright@1.60.0)(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@1.21.7)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.0))(vitest@5.0.0)': + '@vitest/browser-playwright@5.0.5(playwright@1.60.0)(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@1.21.7)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.0))(vitest@5.0.5)': dependencies: - '@vitest/browser': 5.0.0(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@1.21.7)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.0))(vitest@5.0.0) - '@vitest/mocker': 5.0.0(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@1.21.7)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.0)) + '@vitest/browser': 5.0.5(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@1.21.7)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.0))(vitest@5.0.5) + '@vitest/mocker': 5.0.5(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@1.21.7)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.0)) playwright: 1.60.0 - tinyrainbow: 3.1.1 - vitest: 5.0.0(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@5.0.0)(@vitest/coverage-v8@5.0.0)(@vitest/ui@5.0.0)(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@1.21.7)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.0)) + tinyrainbow: 3.2.0 + vitest: 5.0.5(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@5.0.5)(@vitest/coverage-v8@5.0.5)(@vitest/ui@5.0.5)(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@1.21.7)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.0)) transitivePeerDependencies: - bufferutil - msw @@ -19327,13 +19325,13 @@ snapshots: - vite optional: true - '@vitest/browser-playwright@5.0.0(playwright@1.60.0)(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.0))(vitest@5.0.0)': + '@vitest/browser-playwright@5.0.5(playwright@1.60.0)(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.0))(vitest@5.0.5)': dependencies: - '@vitest/browser': 5.0.0(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.0))(vitest@5.0.0) - '@vitest/mocker': 5.0.0(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.0)) + '@vitest/browser': 5.0.5(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.0))(vitest@5.0.5) + '@vitest/mocker': 5.0.5(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.0)) playwright: 1.60.0 - tinyrainbow: 3.1.1 - vitest: 5.0.0(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@5.0.0)(@vitest/coverage-v8@5.0.0)(@vitest/ui@5.0.0)(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.0)) + tinyrainbow: 3.2.0 + vitest: 5.0.5(@opentelemetry/api@1.9.1)(@types/node@22.20.5)(@vitest/browser-playwright@5.0.5)(@vitest/coverage-v8@5.0.5)(@vitest/ui@5.0.5)(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.0)) transitivePeerDependencies: - bufferutil - msw @@ -19341,26 +19339,26 @@ snapshots: - vite optional: true - '@vitest/browser-playwright@5.0.0(playwright@1.60.0)(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1))(vitest@5.0.0)': + '@vitest/browser-playwright@5.0.5(playwright@1.60.0)(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1))(vitest@5.0.5)': dependencies: - '@vitest/browser': 5.0.0(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1))(vitest@5.0.0) - '@vitest/mocker': 5.0.0(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) + '@vitest/browser': 5.0.5(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1))(vitest@5.0.5) + '@vitest/mocker': 5.0.5(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) playwright: 1.60.0 - tinyrainbow: 3.1.1 - vitest: 5.0.0(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@5.0.0)(@vitest/coverage-v8@5.0.0)(@vitest/ui@5.0.0)(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) + tinyrainbow: 3.2.0 + vitest: 5.0.5(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@5.0.5)(@vitest/coverage-v8@5.0.5)(@vitest/ui@5.0.5)(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) transitivePeerDependencies: - bufferutil - msw - utf-8-validate - vite - '@vitest/browser-playwright@5.0.0(playwright@1.60.0)(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1))(vitest@5.0.0)': + '@vitest/browser-playwright@5.0.5(playwright@1.60.0)(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1))(vitest@5.0.5)': dependencies: - '@vitest/browser': 5.0.0(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1))(vitest@5.0.0) - '@vitest/mocker': 5.0.0(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) + '@vitest/browser': 5.0.5(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1))(vitest@5.0.5) + '@vitest/mocker': 5.0.5(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) playwright: 1.60.0 - tinyrainbow: 3.1.1 - vitest: 5.0.0(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@5.0.0)(@vitest/coverage-v8@5.0.0)(@vitest/ui@5.0.0)(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) + tinyrainbow: 3.2.0 + vitest: 5.0.5(@opentelemetry/api@1.9.1)(@types/node@22.20.5)(@vitest/browser-playwright@5.0.5)(@vitest/coverage-v8@5.0.5)(@vitest/ui@5.0.5)(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) transitivePeerDependencies: - bufferutil - msw @@ -19368,17 +19366,17 @@ snapshots: - vite optional: true - '@vitest/browser@5.0.0(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@1.21.7)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.0))(vitest@5.0.0)': + '@vitest/browser@5.0.5(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@1.21.7)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.0))(vitest@5.0.5)': dependencies: '@blazediff/core': 1.10.0 - '@vitest/mocker': 5.0.0(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@1.21.7)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.0)) - '@vitest/ui': 5.0.0(vitest@5.0.0) - '@vitest/utils': 5.0.0 - magic-string: 1.3.1 + '@vitest/mocker': 5.0.5(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@1.21.7)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.0)) + '@vitest/ui': 5.0.5(vitest@5.0.5) + '@vitest/utils': 5.0.5 + magic-string: 1.4.3 pngjs: 7.0.0 sirv: 3.0.2 - tinyrainbow: 3.1.1 - vitest: 5.0.0(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@5.0.0)(@vitest/coverage-v8@5.0.0)(@vitest/ui@5.0.0)(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@1.21.7)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.0)) + tinyrainbow: 3.2.0 + vitest: 5.0.5(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@5.0.5)(@vitest/coverage-v8@5.0.5)(@vitest/ui@5.0.5)(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@1.21.7)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.0)) ws: 8.22.0 transitivePeerDependencies: - bufferutil @@ -19387,17 +19385,17 @@ snapshots: - vite optional: true - '@vitest/browser@5.0.0(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.0))(vitest@5.0.0)': + '@vitest/browser@5.0.5(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.0))(vitest@5.0.5)': dependencies: '@blazediff/core': 1.10.0 - '@vitest/mocker': 5.0.0(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.0)) - '@vitest/ui': 5.0.0(vitest@5.0.0) - '@vitest/utils': 5.0.0 - magic-string: 1.3.1 + '@vitest/mocker': 5.0.5(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.0)) + '@vitest/ui': 5.0.5(vitest@5.0.5) + '@vitest/utils': 5.0.5 + magic-string: 1.4.3 pngjs: 7.0.0 sirv: 3.0.2 - tinyrainbow: 3.1.1 - vitest: 5.0.0(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@5.0.0)(@vitest/coverage-v8@5.0.0)(@vitest/ui@5.0.0)(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.0)) + tinyrainbow: 3.2.0 + vitest: 5.0.5(@opentelemetry/api@1.9.1)(@types/node@22.20.5)(@vitest/browser-playwright@5.0.5)(@vitest/coverage-v8@5.0.5)(@vitest/ui@5.0.5)(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.0)) ws: 8.22.0 transitivePeerDependencies: - bufferutil @@ -19406,17 +19404,17 @@ snapshots: - vite optional: true - '@vitest/browser@5.0.0(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1))(vitest@5.0.0)': + '@vitest/browser@5.0.5(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1))(vitest@5.0.5)': dependencies: '@blazediff/core': 1.10.0 - '@vitest/mocker': 5.0.0(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) - '@vitest/ui': 5.0.0(vitest@5.0.0) - '@vitest/utils': 5.0.0 - magic-string: 1.3.1 + '@vitest/mocker': 5.0.5(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) + '@vitest/ui': 5.0.5(vitest@5.0.5) + '@vitest/utils': 5.0.5 + magic-string: 1.4.3 pngjs: 7.0.0 sirv: 3.0.2 - tinyrainbow: 3.1.1 - vitest: 5.0.0(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@5.0.0)(@vitest/coverage-v8@5.0.0)(@vitest/ui@5.0.0)(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) + tinyrainbow: 3.2.0 + vitest: 5.0.5(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@5.0.5)(@vitest/coverage-v8@5.0.5)(@vitest/ui@5.0.5)(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) ws: 8.22.0 transitivePeerDependencies: - bufferutil @@ -19424,17 +19422,17 @@ snapshots: - utf-8-validate - vite - '@vitest/browser@5.0.0(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1))(vitest@5.0.0)': + '@vitest/browser@5.0.5(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1))(vitest@5.0.5)': dependencies: '@blazediff/core': 1.10.0 - '@vitest/mocker': 5.0.0(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) - '@vitest/ui': 5.0.0(vitest@5.0.0) - '@vitest/utils': 5.0.0 - magic-string: 1.3.1 + '@vitest/mocker': 5.0.5(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) + '@vitest/ui': 5.0.5(vitest@5.0.5) + '@vitest/utils': 5.0.5 + magic-string: 1.4.3 pngjs: 7.0.0 sirv: 3.0.2 - tinyrainbow: 3.1.1 - vitest: 5.0.0(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@5.0.0)(@vitest/coverage-v8@5.0.0)(@vitest/ui@5.0.0)(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) + tinyrainbow: 3.2.0 + vitest: 5.0.5(@opentelemetry/api@1.9.1)(@types/node@22.20.5)(@vitest/browser-playwright@5.0.5)(@vitest/coverage-v8@5.0.5)(@vitest/ui@5.0.5)(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) ws: 8.22.0 transitivePeerDependencies: - bufferutil @@ -19443,59 +19441,59 @@ snapshots: - vite optional: true - '@vitest/coverage-v8@5.0.0(@vitest/browser@5.0.0)(vitest@5.0.0)': + '@vitest/coverage-v8@5.0.5(@vitest/browser@5.0.5)(vitest@5.0.5)': dependencies: '@bcoe/v8-coverage': 1.0.2 - '@vitest/istanbul-lib-coverage': 1.0.1 - '@vitest/istanbul-lib-report': 1.0.1 - ast-v8-to-istanbul: 1.0.6 - magicast: 0.5.4 - obug: 2.2.1 - std-env: 4.2.0 - tinyrainbow: 3.1.1 - vitest: 5.0.0(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@5.0.0)(@vitest/coverage-v8@5.0.0)(@vitest/ui@5.0.0)(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) + '@vitest/istanbul-lib-coverage': 1.0.2 + '@vitest/istanbul-lib-report': 1.0.2 + ast-v8-to-istanbul: 1.0.7 + magicast: 0.5.5 + obug: 3.0.0 + std-env: 4.3.0 + tinyrainbow: 3.2.0 + vitest: 5.0.5(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@5.0.5)(@vitest/coverage-v8@5.0.5)(@vitest/ui@5.0.5)(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) optionalDependencies: - '@vitest/browser': 5.0.0(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1))(vitest@5.0.0) + '@vitest/browser': 5.0.5(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1))(vitest@5.0.5) - '@vitest/istanbul-lib-coverage@1.0.1': {} + '@vitest/istanbul-lib-coverage@1.0.2': {} - '@vitest/istanbul-lib-report@1.0.1': + '@vitest/istanbul-lib-report@1.0.2': dependencies: - '@vitest/istanbul-lib-coverage': 1.0.1 + '@vitest/istanbul-lib-coverage': 1.0.2 - '@vitest/mocker@5.0.0(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@1.21.7)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.0))': + '@vitest/mocker@5.0.5(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@1.21.7)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.0))': dependencies: '@jridgewell/trace-mapping': 0.3.31 - '@vitest/spy': 5.0.0 + '@vitest/spy': 5.0.5 estree-walker: 3.0.3 - magic-string: 1.3.1 + magic-string: 1.4.3 optionalDependencies: vite: 8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@1.21.7)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.0) - '@vitest/mocker@5.0.0(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.0))': + '@vitest/mocker@5.0.5(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.0))': dependencies: '@jridgewell/trace-mapping': 0.3.31 - '@vitest/spy': 5.0.0 + '@vitest/spy': 5.0.5 estree-walker: 3.0.3 - magic-string: 1.3.1 + magic-string: 1.4.3 optionalDependencies: vite: 8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.0) - '@vitest/mocker@5.0.0(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1))': + '@vitest/mocker@5.0.5(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1))': dependencies: '@jridgewell/trace-mapping': 0.3.31 - '@vitest/spy': 5.0.0 + '@vitest/spy': 5.0.5 estree-walker: 3.0.3 - magic-string: 1.3.1 + magic-string: 1.4.3 optionalDependencies: vite: 8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1) - '@vitest/mocker@5.0.0(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1))': + '@vitest/mocker@5.0.5(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1))': dependencies: '@jridgewell/trace-mapping': 0.3.31 - '@vitest/spy': 5.0.0 + '@vitest/spy': 5.0.5 estree-walker: 3.0.3 - magic-string: 1.3.1 + magic-string: 1.4.3 optionalDependencies: vite: 8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1) @@ -19503,21 +19501,21 @@ snapshots: dependencies: tinyrainbow: 3.1.1 - '@vitest/pretty-format@5.0.0': + '@vitest/pretty-format@5.0.5': dependencies: - tinyrainbow: 3.1.1 + tinyrainbow: 3.2.0 - '@vitest/spy@5.0.0': {} + '@vitest/spy@5.0.5': {} - '@vitest/ui@5.0.0(vitest@5.0.0)': + '@vitest/ui@5.0.5(vitest@5.0.5)': dependencies: - '@vitest/utils': 5.0.0 + '@vitest/utils': 5.0.5 fflate: 0.8.3 flatted: 3.4.4 pathe: 2.0.3 sirv: 3.0.2 - tinyrainbow: 3.1.1 - vitest: 5.0.0(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@5.0.0)(@vitest/coverage-v8@5.0.0)(@vitest/ui@5.0.0)(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) + tinyrainbow: 3.2.0 + vitest: 5.0.5(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@5.0.5)(@vitest/coverage-v8@5.0.5)(@vitest/ui@5.0.5)(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) '@vitest/utils@4.1.6': dependencies: @@ -19525,11 +19523,11 @@ snapshots: convert-source-map: 2.0.0 tinyrainbow: 3.1.0 - '@vitest/utils@5.0.0': + '@vitest/utils@5.0.5': dependencies: - '@vitest/pretty-format': 5.0.0 + '@vitest/pretty-format': 5.0.5 convert-source-map: 2.0.0 - tinyrainbow: 3.1.1 + tinyrainbow: 3.2.0 '@webassemblyjs/ast@1.14.1': dependencies: @@ -19850,7 +19848,7 @@ snapshots: dependencies: tslib: 2.8.1 - ast-v8-to-istanbul@1.0.6: + ast-v8-to-istanbul@1.0.7: dependencies: '@jridgewell/trace-mapping': 0.3.31 estree-walker: 3.0.3 @@ -20233,7 +20231,7 @@ snapshots: bytestreamjs@2.0.1: {} - c12@3.3.4(magicast@0.5.4): + c12@3.3.4(magicast@0.5.5): dependencies: chokidar: 5.0.0 confbox: 0.2.4 @@ -20248,7 +20246,7 @@ snapshots: pkg-types: 2.3.1 rc9: 3.0.1 optionalDependencies: - magicast: 0.5.4 + magicast: 0.5.5 cac@7.0.0: {} @@ -20309,7 +20307,7 @@ snapshots: ccount@2.0.1: {} - chai@6.2.2: {} + chai@6.3.0: {} chalk-template@0.4.0: dependencies: @@ -21040,33 +21038,33 @@ snapshots: esbuild: 0.28.2 tsx: 4.22.0 - drizzle-orm@0.45.2(@electric-sql/pglite@0.4.1)(@op-engineering/op-sqlite@18.2.5(react-native@0.88.0(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.1))(react@19.3.1))(@opentelemetry/api@1.9.1)(@prisma/client@7.8.0(prisma@7.8.0(@types/node@22.19.19)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(magicast@0.5.4)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(typescript@7.0.2))(typescript@7.0.2))(@types/pg@8.20.0)(expo-sqlite@58.0.13(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.1))(react@19.3.1))(kysely@0.28.17)(mysql2@3.24.6(@types/node@22.19.19))(pg@8.20.0)(postgres@3.4.7(patch_hash=1ab70f82038a98cb34611424c35af68a1d87aa3bd8ed78a7f863c628ae3aeac7))(prisma@7.8.0(@types/node@22.19.19)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(magicast@0.5.4)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(typescript@7.0.2)): + drizzle-orm@0.45.2(@electric-sql/pglite@0.4.1)(@op-engineering/op-sqlite@18.2.5(react-native@0.88.0(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.1))(react@19.3.1))(@opentelemetry/api@1.9.1)(@prisma/client@7.8.0(prisma@7.8.0(@types/node@22.19.19)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(magicast@0.5.5)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(typescript@7.0.2))(typescript@7.0.2))(@types/pg@8.20.0)(expo-sqlite@58.0.13(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.1))(react@19.3.1))(kysely@0.28.17)(mysql2@3.24.6(@types/node@22.19.19))(pg@8.20.0)(postgres@3.4.7(patch_hash=1ab70f82038a98cb34611424c35af68a1d87aa3bd8ed78a7f863c628ae3aeac7))(prisma@7.8.0(@types/node@22.19.19)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(magicast@0.5.5)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(typescript@7.0.2)): optionalDependencies: '@electric-sql/pglite': 0.4.1 '@op-engineering/op-sqlite': 18.2.5(react-native@0.88.0(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.1))(react@19.3.1) '@opentelemetry/api': 1.9.1 - '@prisma/client': 7.8.0(prisma@7.8.0(@types/node@22.19.19)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(magicast@0.5.4)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(typescript@7.0.2))(typescript@7.0.2) + '@prisma/client': 7.8.0(prisma@7.8.0(@types/node@22.19.19)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(magicast@0.5.5)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(typescript@7.0.2))(typescript@7.0.2) '@types/pg': 8.20.0 expo-sqlite: 58.0.13(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.1))(react@19.3.1) kysely: 0.28.17 mysql2: 3.24.6(@types/node@22.19.19) pg: 8.20.0 postgres: 3.4.7(patch_hash=1ab70f82038a98cb34611424c35af68a1d87aa3bd8ed78a7f863c628ae3aeac7) - prisma: 7.8.0(@types/node@22.19.19)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(magicast@0.5.4)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(typescript@7.0.2) + prisma: 7.8.0(@types/node@22.19.19)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(magicast@0.5.5)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(typescript@7.0.2) - drizzle-orm@0.45.2(@electric-sql/pglite@0.4.1)(@op-engineering/op-sqlite@18.2.5(react-native@0.88.0(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.1))(react@19.3.1))(@opentelemetry/api@1.9.1)(@prisma/client@7.8.0(prisma@7.8.0(@types/node@22.20.5)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(magicast@0.5.4)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(typescript@6.0.3))(typescript@6.0.3))(@types/pg@8.20.0)(expo-sqlite@58.0.13(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.1))(react@19.3.1))(kysely@0.28.17)(mysql2@3.24.6(@types/node@22.20.5))(pg@8.20.0)(postgres@3.4.7(patch_hash=1ab70f82038a98cb34611424c35af68a1d87aa3bd8ed78a7f863c628ae3aeac7))(prisma@7.8.0(@types/node@22.20.5)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(magicast@0.5.4)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(typescript@6.0.3)): + drizzle-orm@0.45.2(@electric-sql/pglite@0.4.1)(@op-engineering/op-sqlite@18.2.5(react-native@0.88.0(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.1))(react@19.3.1))(@opentelemetry/api@1.9.1)(@prisma/client@7.8.0(prisma@7.8.0(@types/node@22.20.5)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(magicast@0.5.5)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(typescript@6.0.3))(typescript@6.0.3))(@types/pg@8.20.0)(expo-sqlite@58.0.13(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.1))(react@19.3.1))(kysely@0.28.17)(mysql2@3.24.6(@types/node@22.20.5))(pg@8.20.0)(postgres@3.4.7(patch_hash=1ab70f82038a98cb34611424c35af68a1d87aa3bd8ed78a7f863c628ae3aeac7))(prisma@7.8.0(@types/node@22.20.5)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(magicast@0.5.5)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(typescript@6.0.3)): optionalDependencies: '@electric-sql/pglite': 0.4.1 '@op-engineering/op-sqlite': 18.2.5(react-native@0.88.0(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.1))(react@19.3.1) '@opentelemetry/api': 1.9.1 - '@prisma/client': 7.8.0(prisma@7.8.0(@types/node@22.20.5)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(magicast@0.5.4)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(typescript@6.0.3))(typescript@6.0.3) + '@prisma/client': 7.8.0(prisma@7.8.0(@types/node@22.20.5)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(magicast@0.5.5)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(typescript@6.0.3))(typescript@6.0.3) '@types/pg': 8.20.0 expo-sqlite: 58.0.13(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.1))(react@19.3.1) kysely: 0.28.17 mysql2: 3.24.6(@types/node@22.20.5) pg: 8.20.0 postgres: 3.4.7(patch_hash=1ab70f82038a98cb34611424c35af68a1d87aa3bd8ed78a7f863c628ae3aeac7) - prisma: 7.8.0(@types/node@22.20.5)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(magicast@0.5.4)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(typescript@6.0.3) + prisma: 7.8.0(@types/node@22.20.5)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(magicast@0.5.5)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(typescript@6.0.3) dunder-proto@1.0.1: dependencies: @@ -22427,10 +22425,6 @@ snapshots: isobject@3.0.1: {} - isomorphic-ws@4.0.1(ws@7.5.13): - dependencies: - ws: 7.5.13 - isomorphic-ws@4.0.1(ws@8.22.0): dependencies: ws: 8.22.0 @@ -22704,11 +22698,11 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 - magic-string@1.3.1: + magic-string@1.4.3: dependencies: '@jridgewell/sourcemap-codec': 1.6.0 - magicast@0.5.4: + magicast@0.5.5: dependencies: '@babel/parser': 7.29.9 '@babel/types': 7.29.8 @@ -23739,7 +23733,7 @@ snapshots: obuf@1.1.2: {} - obug@2.2.1: {} + obug@3.0.0: {} ohash@2.0.11: {} @@ -24845,9 +24839,9 @@ snapshots: clsx: 2.1.1 react: 19.3.1 - prisma@7.8.0(@types/node@22.19.19)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(magicast@0.5.4)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(typescript@7.0.2): + prisma@7.8.0(@types/node@22.19.19)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(magicast@0.5.5)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(typescript@7.0.2): dependencies: - '@prisma/config': 7.8.0(magicast@0.5.4) + '@prisma/config': 7.8.0(magicast@0.5.5) '@prisma/dev': 0.24.3(typescript@7.0.2) '@prisma/engines': 7.8.0 '@prisma/studio-core': 0.27.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) @@ -24864,9 +24858,9 @@ snapshots: - react-dom optional: true - prisma@7.8.0(@types/node@22.20.5)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(magicast@0.5.4)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(typescript@6.0.3): + prisma@7.8.0(@types/node@22.20.5)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(magicast@0.5.5)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(typescript@6.0.3): dependencies: - '@prisma/config': 7.8.0(magicast@0.5.4) + '@prisma/config': 7.8.0(magicast@0.5.5) '@prisma/dev': 0.24.3(typescript@6.0.3) '@prisma/engines': 7.8.0 '@prisma/studio-core': 0.27.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) @@ -25721,8 +25715,6 @@ snapshots: side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 - siginfo@2.0.0: {} - signal-exit@3.0.7: {} signal-exit@4.1.0: {} @@ -25906,8 +25898,6 @@ snapshots: sst-linux-x64: 3.8.9 sst-linux-x86: 3.8.9 - stackback@0.0.2: {} - stackframe@1.3.4: {} stacktrace-parser@0.1.11: @@ -25922,7 +25912,7 @@ snapshots: std-env@3.10.0: {} - std-env@4.2.0: {} + std-env@4.3.0: {} stream-buffers@2.2.0: {} @@ -26251,9 +26241,9 @@ snapshots: tiny-warning@1.0.3: {} - tinybench@6.1.4: {} + tinybench@6.2.2: {} - tinyexec@1.3.0: {} + tinyexec@1.3.1: {} tinyglobby@0.2.16: dependencies: @@ -26271,6 +26261,8 @@ snapshots: tinyrainbow@3.1.1: {} + tinyrainbow@3.2.0: {} + tmp@0.2.7: {} tmpl@1.0.5: {} @@ -26303,7 +26295,7 @@ snapshots: tslib@2.8.1: {} - tsnapi@0.3.3(vitest@5.0.0): + tsnapi@0.3.3(vitest@5.0.5): dependencies: '@vitest/utils': 4.1.6 cac: 7.0.0 @@ -26311,7 +26303,7 @@ snapshots: oxc-parser: 0.129.0 tinyglobby: 0.2.16 optionalDependencies: - vitest: 5.0.0(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@5.0.0)(@vitest/coverage-v8@5.0.0)(@vitest/ui@5.0.0)(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) + vitest: 5.0.5(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@5.0.5)(@vitest/coverage-v8@5.0.5)(@vitest/ui@5.0.5)(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) tsx@4.22.0: dependencies: @@ -26695,103 +26687,103 @@ snapshots: optionalDependencies: vite: 8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1) - vitest@5.0.0(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@5.0.0)(@vitest/coverage-v8@5.0.0)(@vitest/ui@5.0.0)(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@1.21.7)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.0)): + vitest@5.0.5(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@5.0.5)(@vitest/coverage-v8@5.0.5)(@vitest/ui@5.0.5)(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@1.21.7)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.0)): dependencies: '@types/chai': 5.2.3 - '@vitest/mocker': 5.0.0(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@1.21.7)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.0)) - chai: 6.2.2 + '@vitest/mocker': 5.0.5(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@1.21.7)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.0)) + chai: 6.3.0 es-module-lexer: 2.3.2 expect-type: 1.4.0 - magic-string: 1.3.1 - obug: 2.2.1 + magic-string: 1.4.3 + obug: 3.0.0 picomatch: 4.0.7 - std-env: 4.2.0 - tinybench: 6.1.4 - tinyexec: 1.3.0 + std-env: 4.3.0 + tinybench: 6.2.2 + tinyexec: 1.3.1 tinyglobby: 0.2.17 vite: 8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@1.21.7)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.0) - why-is-node-running: 2.3.0 + why-is-node-running: 3.2.1 optionalDependencies: '@opentelemetry/api': 1.9.1 '@types/node': 22.19.19 - '@vitest/browser-playwright': 5.0.0(playwright@1.60.0)(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@1.21.7)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.0))(vitest@5.0.0) - '@vitest/coverage-v8': 5.0.0(@vitest/browser@5.0.0)(vitest@5.0.0) - '@vitest/ui': 5.0.0(vitest@5.0.0) + '@vitest/browser-playwright': 5.0.5(playwright@1.60.0)(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@1.21.7)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.0))(vitest@5.0.5) + '@vitest/coverage-v8': 5.0.5(@vitest/browser@5.0.5)(vitest@5.0.5) + '@vitest/ui': 5.0.5(vitest@5.0.5) transitivePeerDependencies: - msw - vitest@5.0.0(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@5.0.0)(@vitest/coverage-v8@5.0.0)(@vitest/ui@5.0.0)(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.0)): + vitest@5.0.5(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@5.0.5)(@vitest/coverage-v8@5.0.5)(@vitest/ui@5.0.5)(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)): dependencies: '@types/chai': 5.2.3 - '@vitest/mocker': 5.0.0(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.0)) - chai: 6.2.2 + '@vitest/mocker': 5.0.5(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) + chai: 6.3.0 es-module-lexer: 2.3.2 expect-type: 1.4.0 - magic-string: 1.3.1 - obug: 2.2.1 + magic-string: 1.4.3 + obug: 3.0.0 picomatch: 4.0.7 - std-env: 4.2.0 - tinybench: 6.1.4 - tinyexec: 1.3.0 + std-env: 4.3.0 + tinybench: 6.2.2 + tinyexec: 1.3.1 tinyglobby: 0.2.17 - vite: 8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.0) - why-is-node-running: 2.3.0 + vite: 8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1) + why-is-node-running: 3.2.1 optionalDependencies: '@opentelemetry/api': 1.9.1 '@types/node': 22.19.19 - '@vitest/browser-playwright': 5.0.0(playwright@1.60.0)(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.0))(vitest@5.0.0) - '@vitest/coverage-v8': 5.0.0(@vitest/browser@5.0.0)(vitest@5.0.0) - '@vitest/ui': 5.0.0(vitest@5.0.0) + '@vitest/browser-playwright': 5.0.5(playwright@1.60.0)(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1))(vitest@5.0.5) + '@vitest/coverage-v8': 5.0.5(@vitest/browser@5.0.5)(vitest@5.0.5) + '@vitest/ui': 5.0.5(vitest@5.0.5) transitivePeerDependencies: - msw - vitest@5.0.0(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@5.0.0)(@vitest/coverage-v8@5.0.0)(@vitest/ui@5.0.0)(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)): + vitest@5.0.5(@opentelemetry/api@1.9.1)(@types/node@22.20.5)(@vitest/browser-playwright@5.0.5)(@vitest/coverage-v8@5.0.5)(@vitest/ui@5.0.5)(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.0)): dependencies: '@types/chai': 5.2.3 - '@vitest/mocker': 5.0.0(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) - chai: 6.2.2 + '@vitest/mocker': 5.0.5(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.0)) + chai: 6.3.0 es-module-lexer: 2.3.2 expect-type: 1.4.0 - magic-string: 1.3.1 - obug: 2.2.1 + magic-string: 1.4.3 + obug: 3.0.0 picomatch: 4.0.7 - std-env: 4.2.0 - tinybench: 6.1.4 - tinyexec: 1.3.0 + std-env: 4.3.0 + tinybench: 6.2.2 + tinyexec: 1.3.1 tinyglobby: 0.2.17 - vite: 8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1) - why-is-node-running: 2.3.0 + vite: 8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.0) + why-is-node-running: 3.2.1 optionalDependencies: '@opentelemetry/api': 1.9.1 - '@types/node': 22.19.19 - '@vitest/browser-playwright': 5.0.0(playwright@1.60.0)(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1))(vitest@5.0.0) - '@vitest/coverage-v8': 5.0.0(@vitest/browser@5.0.0)(vitest@5.0.0) - '@vitest/ui': 5.0.0(vitest@5.0.0) + '@types/node': 22.20.5 + '@vitest/browser-playwright': 5.0.5(playwright@1.60.0)(vite@8.3.4(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.0))(vitest@5.0.5) + '@vitest/coverage-v8': 5.0.5(@vitest/browser@5.0.5)(vitest@5.0.5) + '@vitest/ui': 5.0.5(vitest@5.0.5) transitivePeerDependencies: - msw - vitest@5.0.0(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@5.0.0)(@vitest/coverage-v8@5.0.0)(@vitest/ui@5.0.0)(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)): + vitest@5.0.5(@opentelemetry/api@1.9.1)(@types/node@22.20.5)(@vitest/browser-playwright@5.0.5)(@vitest/coverage-v8@5.0.5)(@vitest/ui@5.0.5)(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)): dependencies: '@types/chai': 5.2.3 - '@vitest/mocker': 5.0.0(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) - chai: 6.2.2 + '@vitest/mocker': 5.0.5(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1)) + chai: 6.3.0 es-module-lexer: 2.3.2 expect-type: 1.4.0 - magic-string: 1.3.1 - obug: 2.2.1 + magic-string: 1.4.3 + obug: 3.0.0 picomatch: 4.0.7 - std-env: 4.2.0 - tinybench: 6.1.4 - tinyexec: 1.3.0 + std-env: 4.3.0 + tinybench: 6.2.2 + tinyexec: 1.3.1 tinyglobby: 0.2.17 vite: 8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1) - why-is-node-running: 2.3.0 + why-is-node-running: 3.2.1 optionalDependencies: '@opentelemetry/api': 1.9.1 - '@types/node': 22.19.19 - '@vitest/browser-playwright': 5.0.0(playwright@1.60.0)(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1))(vitest@5.0.0) - '@vitest/coverage-v8': 5.0.0(@vitest/browser@5.0.0)(vitest@5.0.0) - '@vitest/ui': 5.0.0(vitest@5.0.0) + '@types/node': 22.20.5 + '@vitest/browser-playwright': 5.0.5(playwright@1.60.0)(vite@8.3.4(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.0)(yaml@2.9.1))(vitest@5.0.5) + '@vitest/coverage-v8': 5.0.5(@vitest/browser@5.0.5)(vitest@5.0.5) + '@vitest/ui': 5.0.5(vitest@5.0.5) transitivePeerDependencies: - msw @@ -26981,10 +26973,7 @@ snapshots: dependencies: isexe: 3.1.5 - why-is-node-running@2.3.0: - dependencies: - siginfo: 2.0.0 - stackback: 0.0.2 + why-is-node-running@3.2.1: {} widest-line@4.0.1: dependencies: diff --git a/scripts/package.json b/scripts/package.json index 1734584a8a..9c88fc4763 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -17,7 +17,7 @@ "@types/node": "^22.19.19", "oxfmt": "^0.74.0", "typescript": "~7.0.2", - "vitest": "^5.0.0" + "vitest": "^5.0.5" }, "engines": { "node": "^22.12.0 || ^24.0.0 || >=26.0.0" diff --git a/tools/oxlint-plugin-zero/package.json b/tools/oxlint-plugin-zero/package.json index fb644a14d3..799a9ae802 100644 --- a/tools/oxlint-plugin-zero/package.json +++ b/tools/oxlint-plugin-zero/package.json @@ -18,7 +18,7 @@ "oxfmt": "^0.74.0", "oxlint": "^1.80.0", "typescript": "~7.0.2", - "vitest": "^5.0.0" + "vitest": "^5.0.5" }, "engines": { "node": ">=22"