@sqlite-sync/core 0.14.2 → 0.14.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"worker.js","names":[],"sources":["../src/worker-db/db-worker.ts"],"sourcesContent":["import sqlite3InitModule from \"@sqlite.org/sqlite-wasm\";\nimport { createImportData } from \"../export-import\";\nimport { xxhash } from \"../hash\";\nimport { HLCCounter } from \"../hlc\";\nimport type { Logger } from \"../logger\";\nimport { createMigrator, type SyncDbMigrator } from \"../migrations/migrator\";\nimport { applyWorkerDbSchema, type WorkerDbSchema, workerDbConfig } from \"../migrations/system-schema\";\nimport { formatSchemaVerificationIssues } from \"../schema/verify-sync-schema\";\nimport { verifySyncSchema } from \"../schema/verify-sync-schema-wasm\";\nimport type { SyncDbSchema } from \"../sqlite-crdt/crdt-schema\";\nimport { type CrdtStorage, createCrdtStorage } from \"../sqlite-crdt/crdt-storage\";\nimport { createCrdtSyncProducer } from \"../sqlite-crdt/crdt-sync-producer\";\nimport {\n type CreateRemoteSourceFactory,\n createCrdtSyncRemoteSource,\n PUSH_BATCH_SIZE,\n} from \"../sqlite-crdt/crdt-sync-remote-source\";\nimport type { CrdtEventStatus } from \"../sqlite-crdt/crdt-table-schema\";\nimport { SQLiteDbWrapper } from \"../sqlite-db-wrapper\";\nimport { createKvStateHashStore, type KvStore } from \"../sqlite-kv-store\";\nimport { createDeferredPromise, generateId } from \"../utils\";\nimport { createWasmSqliteDriver } from \"../wasm-driver\";\nimport { runWorkerEventLogGc } from \"./event-log-gc\";\nimport { runWithNoActiveReplicas } from \"./replica-lock\";\nimport { createIdbResetStore, createReloadRequestHandler, createResetStateStore, type ResetStore } from \"./reset-state\";\nimport { createStorageVersionStore } from \"./storage-version\";\nimport {\n createBroadcastChannels,\n isWorkerInitMessage,\n isWorkerRequestMessage,\n syncDbClientLockName,\n syncDbWorkerLockName,\n type WorkerConfig,\n type WorkerErrorResponseMessage,\n type WorkerNotificationMessage,\n type WorkerResponseMessage,\n type WorkerRpc,\n} from \"./worker-common\";\n\nconst defaultLogger: Logger = (type, message, level = \"info\") => {\n const logMessage = `[${type}] ${message}`;\n switch (level) {\n case \"info\":\n console.log(logMessage);\n break;\n case \"warning\":\n console.warn(logMessage);\n break;\n case \"error\":\n console.error(logMessage);\n break;\n case \"trace\":\n console.trace(logMessage);\n break;\n }\n};\n\nasync function createDbWorker(config: WorkerConfig, opts: WorkerOptions) {\n const broadcastChannels = createBroadcastChannels(config.dbId);\n const logger = opts.logger ?? defaultLogger;\n\n if (opts.verifySchema) {\n const issues = await verifySyncSchema(opts.syncDbSchema);\n if (issues.length > 0) {\n throw new Error(formatSchemaVerificationIssues(issues));\n }\n logger(\"worker\", \"Schema verification passed: migrations match the declared schema\", \"info\");\n }\n\n const [sqlite3] = await Promise.all([sqlite3InitModule(), xxhash.ensureLoaded()]);\n\n const resetStore = opts.resetStore ?? createIdbResetStore();\n const resetState = createResetStateStore({\n store: resetStore,\n dbId: config.dbId,\n });\n const storageVersion = createStorageVersionStore({\n store: resetStore,\n dbId: config.dbId,\n appStorageVersion: opts.storageVersion,\n });\n const [pendingReset, versionStatus] = await Promise.all([\n resetState.resolvePendingReset(),\n storageVersion.getVersionStatus(),\n ]);\n const isVersionMismatch = versionStatus !== \"current\";\n\n if (versionStatus === \"missing\") {\n logger(\"worker\", `Initializing local DB storage version ${storageVersion.currentVersion}`, \"info\");\n } else if (versionStatus === \"outdated\") {\n logger(\"worker\", `Storage version mismatch — resetting local DB to ${storageVersion.currentVersion}`, \"warning\");\n }\n\n const pool = await sqlite3.installOpfsSAHPoolVfs({\n name: config.dbId,\n directory: `.${config.dbId}`,\n clearOnInit: !!pendingReset || isVersionMismatch,\n initialCapacity: 8,\n });\n\n const db = new SQLiteDbWrapper<WorkerDbSchema>({\n driver: createWasmSqliteDriver(sqlite3, new pool.OpfsSAHPoolDb(`/${config.dbId}-main.db`)),\n logger: logger,\n loggerPrefix: \"worker\",\n });\n\n db.execute(\"PRAGMA locking_mode=exclusive\", { loggerLevel: \"system\" });\n db.execute(\"PRAGMA journal_mode=WAL\", { loggerLevel: \"system\" });\n db.execute(\"PRAGMA synchronous=NORMAL\", { loggerLevel: \"system\" });\n\n db.execute(`ATTACH DATABASE '/${config.dbId}-worker.db' as worker`, { loggerLevel: \"system\" });\n db.execute(\"PRAGMA worker.locking_mode=exclusive\", { loggerLevel: \"system\" });\n db.execute(\"PRAGMA worker.journal_mode=WAL\", { loggerLevel: \"system\" });\n db.execute(\"PRAGMA worker.synchronous=NORMAL\", { loggerLevel: \"system\" });\n\n const { kvStore } = applyWorkerDbSchema(db);\n\n const migrator = createMigrator({\n migrations: opts.syncDbSchema.migrations,\n schemaVersion: kvStore.createNumberStoredValue(\"schema-version\", -1),\n updateLogTableName: workerDbConfig.updateLogTable.fullIdentifier,\n });\n migrator.migrateDbToLatest({\n startTransaction: (callback) => {\n db.executeTransaction((tx) =>\n callback({ execute: (sql, parameters, meta) => tx.execute({ sql, parameters }, meta) }),\n );\n },\n });\n db.invalidateDbSchema();\n\n const pullSyncId = kvStore.createNumberStoredValue(\"pull-sync-id\", -1);\n const pushSyncId = kvStore.createNumberStoredValue(\"push-sync-id\", -1);\n const stateHash = createKvStateHashStore(kvStore);\n const storageGeneration = kvStore.createStringStoredValue(\"storage-generation\", \"\");\n if (!storageGeneration.current) {\n storageGeneration.current = generateId();\n }\n\n // Record the applied reset epoch / storage version only after the wiped DB\n // initialized successfully, so a failed init can be retried by a later\n // elected worker, while a later election does not wipe again.\n if (pendingReset) {\n await resetState.markResetApplied(pendingReset.epoch);\n }\n if (isVersionMismatch) {\n await storageVersion.markCurrentVersionApplied();\n }\n\n if (opts.eventLogGc !== false) {\n await runWithNoActiveReplicas({\n dbId: config.dbId,\n run: () => {\n runWorkerEventLogGc({\n db,\n dbConfig: workerDbConfig,\n pushSyncId,\n logger,\n });\n },\n });\n }\n\n const crdtStorage = createCrdtStorage({\n nodeId: config.clientId,\n initialLocalSyncId: Math.max(getMaxSyncId(db, \"none\"), pushSyncId.current),\n migrator,\n hlc: new HLCCounter(config.clientId, () => Date.now()),\n db,\n dbConfig: workerDbConfig,\n stateHash,\n schema: opts.syncDbSchema,\n });\n\n createCrdtSyncProducer({\n storage: crdtStorage,\n broadcastEvents: (chunk) => {\n broadcastChannels.responses.postMessage({\n notificationType: \"new-event-chunk-applied\",\n newSyncId: chunk.newSyncId,\n stateHash: chunk.stateHash,\n });\n },\n });\n\n const remoteSource = createRemoteSource({\n crdtStorage,\n migrator,\n clientId: config.clientId,\n pullSyncId,\n pushSyncId,\n remoteFactory: opts.createRemoteSource,\n });\n remoteSource.goOnline();\n\n const broadcastNotification = (notification: WorkerNotificationMessage) => {\n broadcastChannels.responses.postMessage(notification);\n };\n\n const postState = () => {\n broadcastNotification({\n notificationType: \"state-changed\",\n state: remoteSource.getState(),\n storageGeneration: storageGeneration.current,\n });\n };\n const stateChangedSubscription = remoteSource.addEventListener(\"state-changed\", () => {\n postState();\n });\n const deSyncDetectedSubscription = remoteSource.addEventListener(\"de-sync-detected\", (event) => {\n broadcastNotification({\n notificationType: \"de-sync-detected\",\n reason: event.payload.reason,\n });\n });\n const remoteSchemaVersionMismatchSubscription = remoteSource.addEventListener(\n \"remote-schema-version-mismatch\",\n (event) => {\n broadcastNotification({\n notificationType: \"remote-schema-version-mismatch\",\n remoteSchemaVersion: event.payload.remoteSchemaVersion,\n localSchemaVersion: event.payload.localSchemaVersion,\n });\n },\n );\n\n const rpcTarget: WorkerRpc = {\n execute: (query) => db.execute(query),\n getSnapshot: () => {\n const appliedSyncId = getMaxSyncId(db, \"pending\");\n db.execute(\"PRAGMA journal_mode=off\", { loggerLevel: \"system\" });\n const file = db.createSnapshot();\n db.execute(\"PRAGMA journal_mode=WAL\", { loggerLevel: \"system\" });\n return {\n file,\n syncId: appliedSyncId,\n schemaVersion: migrator.currentSchemaVersion,\n storageGeneration: storageGeneration.current,\n };\n },\n importData: createImportData({\n migrator,\n tablesConfig: opts.syncDbSchema.tablesConfig,\n applyEvents: async (events) => {\n crdtStorage.applyOwnEvents(events);\n await crdtStorage.internal.processEnqueuedEvents();\n },\n }),\n postState,\n pushTabEvents: (request) => {\n const { beforeSyncId, afterSyncId } = crdtStorage.enqueueLocalEvents(request.events, request.nodeId);\n return {\n ok: true,\n beforeSyncId,\n afterSyncId,\n };\n },\n pullEvents: (request) => {\n return crdtStorage.getEventsBatch({\n afterSyncId: request.afterSyncId,\n status: \"applied\",\n excludeNodeId: request.excludeNodeId ?? \"\",\n limit: 100,\n });\n },\n goOnline: () => remoteSource.goOnline(),\n goOffline: () => remoteSource.goOffline(\"DISCONNECTED\"),\n dropConnection: () => remoteSource.dropConnection(\"NETWORK_OFFLINE\"),\n sync: () => {\n remoteSource.syncWithRemote().catch((error) => console.warn(\"Sync failed\", error));\n },\n requestReload: createReloadRequestHandler({\n resetState,\n broadcast: broadcastNotification,\n }),\n };\n\n broadcastChannels.requests.onmessage = (event) => {\n const message = event.data;\n\n if (!isWorkerRequestMessage(message)) {\n return;\n }\n\n const sendError = (error: unknown) => {\n const response: WorkerErrorResponseMessage = {\n type: \"error-response\",\n requestId: message.requestId,\n error: error instanceof Error ? error.message : String(error),\n };\n broadcastChannels.responses.postMessage(response);\n };\n\n try {\n const method = rpcTarget[message.method] as () => ReturnType<WorkerRpc[keyof WorkerRpc]>;\n const data = method.apply(null, message.args as []);\n\n if (data instanceof Promise) {\n data\n .then((result) => {\n const response: WorkerResponseMessage = {\n type: \"response\",\n requestId: message.requestId,\n data: result,\n };\n broadcastChannels.responses.postMessage(response);\n })\n .catch(sendError);\n } else {\n const response: WorkerResponseMessage = {\n type: \"response\",\n requestId: message.requestId,\n data,\n };\n broadcastChannels.responses.postMessage(response);\n }\n } catch (error) {\n sendError(error);\n }\n };\n\n rpcTarget.postState();\n\n return async () => {\n stateChangedSubscription.unsubscribe();\n deSyncDetectedSubscription.unsubscribe();\n remoteSchemaVersionMismatchSubscription.unsubscribe();\n await remoteSource.dispose();\n broadcastChannels.requests.close();\n broadcastChannels.responses.close();\n db.close();\n };\n}\n\ntype InitRemoteOptions = {\n clientId: string;\n crdtStorage: CrdtStorage;\n migrator: SyncDbMigrator;\n pullSyncId: ReturnType<KvStore[\"createNumberStoredValue\"]>;\n pushSyncId: ReturnType<KvStore[\"createNumberStoredValue\"]>;\n remoteFactory?: CreateRemoteSourceFactory;\n};\n\nfunction createRemoteSource({\n clientId,\n crdtStorage,\n migrator,\n pullSyncId,\n pushSyncId,\n remoteFactory,\n}: InitRemoteOptions) {\n return createCrdtSyncRemoteSource({\n bufferSize: PUSH_BATCH_SIZE,\n pullSyncId,\n pushSyncId,\n nodeId: clientId,\n storage: crdtStorage,\n migrator,\n remoteFactory,\n });\n}\n\nexport async function getWorkerConfig<Props = never>(): Promise<WorkerConfig<Props>> {\n let configSet = false;\n const responsePromise = createDeferredPromise<WorkerConfig>();\n\n self.onmessage = (event: MessageEvent<unknown>) => {\n if (configSet) {\n console.error(\"Worker config already set\");\n return;\n }\n\n const message = event.data;\n if (!isWorkerInitMessage(message)) {\n return;\n }\n\n responsePromise.resolve(message.config);\n configSet = true;\n };\n\n return responsePromise.promise;\n}\n\ntype WorkerOptions = {\n syncDbSchema: SyncDbSchema;\n logger?: Logger;\n createRemoteSource?: CreateRemoteSourceFactory;\n workerConfig?: WorkerConfig;\n /** Durable storage for reset state. Defaults to an IndexedDB-backed store. */\n resetStore?: ResetStore;\n /**\n * App-provided storage version, combined with the library's internal storage\n * version. Bump it when deploying a code change that old persisted local DBs\n * cannot survive — on mismatch the elected worker wipes the local DB on startup.\n */\n storageVersion?: string;\n /**\n * Dev-time drift check: replays the full migration history on a throwaway\n * in-memory database and verifies the result matches the schema declared with\n * `defineSyncSchema`. On mismatch the worker throws and refuses to start.\n * Enable in development only, e.g. `verifySchema: import.meta.env.DEV`.\n */\n verifySchema?: boolean;\n /**\n * Runs local worker event-log garbage collection on startup. Enabled by default;\n * set `eventLogGc: false` to disable. GC keeps the latest event rows and never removes\n * pending events or local rows that have not been pushed to the remote.\n * Skipped while an existing tab holds an in-memory replica of this DB.\n */\n eventLogGc?: boolean;\n};\n\nexport async function startDbWorker(opts: WorkerOptions) {\n const config = opts.workerConfig ?? (await getWorkerConfig());\n\n await navigator.locks.request(`${syncDbWorkerLockName}-${config.dbId}`, { mode: \"exclusive\" }, async (lock) => {\n if (!lock) {\n return;\n }\n\n const cleanup = await createDbWorker(config, opts);\n\n const clientLockName = `${syncDbClientLockName}-${config.dbId}`;\n await new Promise<void>((resolve) => {\n const interval = setInterval(async () => {\n const { held } = await navigator.locks.query();\n const hasClients = held?.some((l) => l.name === clientLockName && l.mode === \"shared\");\n if (!hasClients) {\n clearInterval(interval);\n resolve();\n }\n }, 5_000);\n });\n\n await cleanup();\n });\n\n self.close();\n}\n\nfunction getMaxSyncId(db: SQLiteDbWrapper<WorkerDbSchema>, excludingStatus: \"none\" | \"pending\") {\n const [result] = db.executePrepared(\n \"get-max-sync-id\",\n { excludingStatus: excludingStatus as CrdtEventStatus },\n (db, params) =>\n db\n .selectFrom(\"worker.crdt_events\")\n .where(\"status\", \"!=\", params(\"excludingStatus\"))\n .select((eb) => eb.fn.max(\"sync_id\").as(\"sync_id\")),\n { loggerLevel: \"system\" },\n );\n\n return result?.sync_id ?? 0;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"worker.js","names":[],"sources":["../src/worker-db/db-worker.ts"],"sourcesContent":["import sqlite3InitModule from \"@sqlite.org/sqlite-wasm\";\nimport { createImportData } from \"../export-import\";\nimport { xxhash } from \"../hash\";\nimport { HLCCounter } from \"../hlc\";\nimport type { Logger } from \"../logger\";\nimport { createMigrator, type SyncDbMigrator } from \"../migrations/migrator\";\nimport { applyWorkerDbSchema, type WorkerDbSchema, workerDbConfig } from \"../migrations/system-schema\";\nimport { formatSchemaVerificationIssues } from \"../schema/verify-sync-schema\";\nimport { verifySyncSchema } from \"../schema/verify-sync-schema-wasm\";\nimport type { SyncDbSchema } from \"../sqlite-crdt/crdt-schema\";\nimport { type CrdtStorage, createCrdtStorage } from \"../sqlite-crdt/crdt-storage\";\nimport { createCrdtSyncProducer } from \"../sqlite-crdt/crdt-sync-producer\";\nimport {\n type CreateRemoteSourceFactory,\n createCrdtSyncRemoteSource,\n PUSH_BATCH_SIZE,\n} from \"../sqlite-crdt/crdt-sync-remote-source\";\nimport type { CrdtEventStatus } from \"../sqlite-crdt/crdt-table-schema\";\nimport { SQLiteDbWrapper } from \"../sqlite-db-wrapper\";\nimport { createKvStateHashStore, type KvStore } from \"../sqlite-kv-store\";\nimport { createDeferredPromise, generateId } from \"../utils\";\nimport { createWasmSqliteDriver } from \"../wasm-driver\";\nimport { runWorkerEventLogGc } from \"./event-log-gc\";\nimport { runWithNoActiveReplicas } from \"./replica-lock\";\nimport { createIdbResetStore, createReloadRequestHandler, createResetStateStore, type ResetStore } from \"./reset-state\";\nimport { createStorageVersionStore } from \"./storage-version\";\nimport {\n createBroadcastChannels,\n isWorkerInitMessage,\n isWorkerRequestMessage,\n syncDbClientLockName,\n syncDbWorkerLockName,\n type WorkerConfig,\n type WorkerErrorResponseMessage,\n type WorkerNotificationMessage,\n type WorkerResponseMessage,\n type WorkerRpc,\n} from \"./worker-common\";\n\nconst defaultLogger: Logger = (type, message, level = \"info\") => {\n const logMessage = `[${type}] ${message}`;\n switch (level) {\n case \"info\":\n console.log(logMessage);\n break;\n case \"warning\":\n console.warn(logMessage);\n break;\n case \"error\":\n console.error(logMessage);\n break;\n case \"trace\":\n console.trace(logMessage);\n break;\n }\n};\n\nasync function createDbWorker(config: WorkerConfig, opts: WorkerOptions) {\n const broadcastChannels = createBroadcastChannels(config.dbId);\n const logger = opts.logger ?? defaultLogger;\n\n if (opts.verifySchema) {\n const issues = await verifySyncSchema(opts.syncDbSchema);\n if (issues.length > 0) {\n throw new Error(formatSchemaVerificationIssues(issues));\n }\n logger(\"worker\", \"Schema verification passed: migrations match the declared schema\", \"info\");\n }\n\n const [sqlite3] = await Promise.all([sqlite3InitModule(), xxhash.ensureLoaded()]);\n\n const resetStore = opts.resetStore ?? createIdbResetStore();\n const resetState = createResetStateStore({\n store: resetStore,\n dbId: config.dbId,\n });\n const storageVersion = createStorageVersionStore({\n store: resetStore,\n dbId: config.dbId,\n appStorageVersion: opts.storageVersion,\n });\n const [pendingReset, versionStatus] = await Promise.all([\n resetState.resolvePendingReset(),\n storageVersion.getVersionStatus(),\n ]);\n const isVersionMismatch = versionStatus !== \"current\";\n\n if (versionStatus === \"missing\") {\n logger(\"worker\", `Initializing local DB storage version ${storageVersion.currentVersion}`, \"info\");\n } else if (versionStatus === \"outdated\") {\n logger(\"worker\", `Storage version mismatch — resetting local DB to ${storageVersion.currentVersion}`, \"warning\");\n }\n\n const pool = await sqlite3.installOpfsSAHPoolVfs({\n name: config.dbId,\n directory: `.${config.dbId}`,\n clearOnInit: !!pendingReset || isVersionMismatch,\n initialCapacity: 8,\n });\n\n const db = new SQLiteDbWrapper<WorkerDbSchema>({\n driver: createWasmSqliteDriver(sqlite3, new pool.OpfsSAHPoolDb(`/${config.dbId}-main.db`)),\n logger: logger,\n loggerPrefix: \"worker\",\n });\n\n db.execute(\"PRAGMA locking_mode=exclusive\", { loggerLevel: \"system\" });\n db.execute(\"PRAGMA journal_mode=WAL\", { loggerLevel: \"system\" });\n db.execute(\"PRAGMA synchronous=NORMAL\", { loggerLevel: \"system\" });\n\n db.execute(`ATTACH DATABASE '/${config.dbId}-worker.db' as worker`, { loggerLevel: \"system\" });\n db.execute(\"PRAGMA worker.locking_mode=exclusive\", { loggerLevel: \"system\" });\n db.execute(\"PRAGMA worker.journal_mode=WAL\", { loggerLevel: \"system\" });\n db.execute(\"PRAGMA worker.synchronous=NORMAL\", { loggerLevel: \"system\" });\n\n const { kvStore } = applyWorkerDbSchema(db);\n\n const migrator = createMigrator({\n migrations: opts.syncDbSchema.migrations,\n schemaVersion: kvStore.createNumberStoredValue(\"schema-version\", -1),\n updateLogTableName: workerDbConfig.updateLogTable.fullIdentifier,\n });\n migrator.migrateDbToLatest({\n startTransaction: (callback) => {\n db.executeTransaction((tx) =>\n callback({ execute: (sql, parameters, meta) => tx.execute({ sql, parameters }, meta) }),\n );\n },\n });\n db.invalidateDbSchema();\n\n const pullSyncId = kvStore.createNumberStoredValue(\"pull-sync-id\", -1);\n const pushSyncId = kvStore.createNumberStoredValue(\"push-sync-id\", -1);\n const stateHash = createKvStateHashStore(kvStore);\n const storageGeneration = kvStore.createStringStoredValue(\"storage-generation\", \"\");\n if (!storageGeneration.current) {\n storageGeneration.current = generateId();\n }\n\n // Record the applied reset epoch / storage version only after the wiped DB\n // initialized successfully, so a failed init can be retried by a later\n // elected worker, while a later election does not wipe again.\n if (pendingReset) {\n await resetState.markResetApplied(pendingReset.epoch);\n }\n if (isVersionMismatch) {\n await storageVersion.markCurrentVersionApplied();\n }\n\n if (opts.eventLogGc !== false) {\n await runWithNoActiveReplicas({\n dbId: config.dbId,\n run: () => {\n runWorkerEventLogGc({\n db,\n dbConfig: workerDbConfig,\n pushSyncId,\n logger,\n });\n },\n });\n }\n\n const crdtStorage = createCrdtStorage({\n nodeId: config.clientId,\n initialLocalSyncId: Math.max(getMaxSyncId(db, \"none\"), pushSyncId.current),\n migrator,\n hlc: new HLCCounter(config.clientId, () => Date.now()),\n db,\n dbConfig: workerDbConfig,\n stateHash,\n schema: opts.syncDbSchema,\n });\n\n createCrdtSyncProducer({\n storage: crdtStorage,\n broadcastEvents: (chunk) => {\n broadcastChannels.responses.postMessage({\n notificationType: \"new-event-chunk-applied\",\n newSyncId: chunk.newSyncId,\n stateHash: chunk.stateHash,\n });\n },\n });\n\n const remoteSource = createRemoteSource({\n crdtStorage,\n migrator,\n clientId: config.clientId,\n pullSyncId,\n pushSyncId,\n remoteFactory: opts.createRemoteSource,\n });\n remoteSource.goOnline();\n\n const broadcastNotification = (notification: WorkerNotificationMessage) => {\n broadcastChannels.responses.postMessage(notification);\n };\n\n const postState = () => {\n broadcastNotification({\n notificationType: \"state-changed\",\n state: remoteSource.getState(),\n storageGeneration: storageGeneration.current,\n });\n };\n const stateChangedSubscription = remoteSource.addEventListener(\"state-changed\", () => {\n postState();\n });\n const deSyncDetectedSubscription = remoteSource.addEventListener(\"de-sync-detected\", (event) => {\n broadcastNotification({\n notificationType: \"de-sync-detected\",\n reason: event.payload.reason,\n });\n });\n const remoteSchemaVersionMismatchSubscription = remoteSource.addEventListener(\n \"remote-schema-version-mismatch\",\n (event) => {\n broadcastNotification({\n notificationType: \"remote-schema-version-mismatch\",\n remoteSchemaVersion: event.payload.remoteSchemaVersion,\n localSchemaVersion: event.payload.localSchemaVersion,\n });\n },\n );\n\n const rpcTarget: WorkerRpc = {\n execute: (query) => db.execute(query),\n getSnapshot: () => {\n const appliedSyncId = getMaxSyncId(db, \"pending\");\n db.execute(\"PRAGMA journal_mode=off\", { loggerLevel: \"system\" });\n const file = db.createSnapshot();\n db.execute(\"PRAGMA journal_mode=WAL\", { loggerLevel: \"system\" });\n return {\n file,\n syncId: appliedSyncId,\n schemaVersion: migrator.currentSchemaVersion,\n storageGeneration: storageGeneration.current,\n };\n },\n importData: createImportData({\n migrator,\n tablesConfig: opts.syncDbSchema.tablesConfig,\n applyEvents: async (events) => {\n crdtStorage.applyOwnEvents(events);\n await crdtStorage.internal.processEnqueuedEvents();\n },\n }),\n postState,\n pushTabEvents: (request) => {\n const { beforeSyncId, afterSyncId } = crdtStorage.enqueueLocalEvents(request.events, request.nodeId);\n return {\n ok: true,\n beforeSyncId,\n afterSyncId,\n };\n },\n pullEvents: (request) => {\n return crdtStorage.getEventsBatch({\n afterSyncId: request.afterSyncId,\n status: \"applied\",\n excludeNodeId: request.excludeNodeId ?? \"\",\n limit: 100,\n });\n },\n goOnline: () => remoteSource.goOnline(),\n goOffline: () => remoteSource.goOffline(\"DISCONNECTED\"),\n dropConnection: () => remoteSource.dropConnection(\"NETWORK_OFFLINE\"),\n sync: () => {\n remoteSource.syncWithRemote().catch((error) => console.warn(\"Sync failed\", error));\n },\n // Tab events are pushed to the remote only once they are applied here.\n catchUp: (options) => remoteSource.catchUp(options, () => crdtStorage.internal.processEnqueuedEvents()),\n requestReload: createReloadRequestHandler({\n resetState,\n broadcast: broadcastNotification,\n }),\n };\n\n broadcastChannels.requests.onmessage = (event) => {\n const message = event.data;\n\n if (!isWorkerRequestMessage(message)) {\n return;\n }\n\n const sendError = (error: unknown) => {\n const response: WorkerErrorResponseMessage = {\n type: \"error-response\",\n requestId: message.requestId,\n error: error instanceof Error ? error.message : String(error),\n };\n broadcastChannels.responses.postMessage(response);\n };\n\n try {\n const method = rpcTarget[message.method] as () => ReturnType<WorkerRpc[keyof WorkerRpc]>;\n const data = method.apply(null, message.args as []);\n\n if (data instanceof Promise) {\n data\n .then((result) => {\n const response: WorkerResponseMessage = {\n type: \"response\",\n requestId: message.requestId,\n data: result,\n };\n broadcastChannels.responses.postMessage(response);\n })\n .catch(sendError);\n } else {\n const response: WorkerResponseMessage = {\n type: \"response\",\n requestId: message.requestId,\n data,\n };\n broadcastChannels.responses.postMessage(response);\n }\n } catch (error) {\n sendError(error);\n }\n };\n\n rpcTarget.postState();\n\n return async () => {\n stateChangedSubscription.unsubscribe();\n deSyncDetectedSubscription.unsubscribe();\n remoteSchemaVersionMismatchSubscription.unsubscribe();\n await remoteSource.dispose();\n broadcastChannels.requests.close();\n broadcastChannels.responses.close();\n db.close();\n };\n}\n\ntype InitRemoteOptions = {\n clientId: string;\n crdtStorage: CrdtStorage;\n migrator: SyncDbMigrator;\n pullSyncId: ReturnType<KvStore[\"createNumberStoredValue\"]>;\n pushSyncId: ReturnType<KvStore[\"createNumberStoredValue\"]>;\n remoteFactory?: CreateRemoteSourceFactory;\n};\n\nfunction createRemoteSource({\n clientId,\n crdtStorage,\n migrator,\n pullSyncId,\n pushSyncId,\n remoteFactory,\n}: InitRemoteOptions) {\n return createCrdtSyncRemoteSource({\n bufferSize: PUSH_BATCH_SIZE,\n pullSyncId,\n pushSyncId,\n nodeId: clientId,\n storage: crdtStorage,\n migrator,\n remoteFactory,\n });\n}\n\nexport async function getWorkerConfig<Props = never>(): Promise<WorkerConfig<Props>> {\n let configSet = false;\n const responsePromise = createDeferredPromise<WorkerConfig>();\n\n self.onmessage = (event: MessageEvent<unknown>) => {\n if (configSet) {\n console.error(\"Worker config already set\");\n return;\n }\n\n const message = event.data;\n if (!isWorkerInitMessage(message)) {\n return;\n }\n\n responsePromise.resolve(message.config);\n configSet = true;\n };\n\n return responsePromise.promise;\n}\n\ntype WorkerOptions = {\n syncDbSchema: SyncDbSchema;\n logger?: Logger;\n createRemoteSource?: CreateRemoteSourceFactory;\n workerConfig?: WorkerConfig;\n /** Durable storage for reset state. Defaults to an IndexedDB-backed store. */\n resetStore?: ResetStore;\n /**\n * App-provided storage version, combined with the library's internal storage\n * version. Bump it when deploying a code change that old persisted local DBs\n * cannot survive — on mismatch the elected worker wipes the local DB on startup.\n */\n storageVersion?: string;\n /**\n * Dev-time drift check: replays the full migration history on a throwaway\n * in-memory database and verifies the result matches the schema declared with\n * `defineSyncSchema`. On mismatch the worker throws and refuses to start.\n * Enable in development only, e.g. `verifySchema: import.meta.env.DEV`.\n */\n verifySchema?: boolean;\n /**\n * Runs local worker event-log garbage collection on startup. Enabled by default;\n * set `eventLogGc: false` to disable. GC keeps the latest event rows and never removes\n * pending events or local rows that have not been pushed to the remote.\n * Skipped while an existing tab holds an in-memory replica of this DB.\n */\n eventLogGc?: boolean;\n};\n\nexport async function startDbWorker(opts: WorkerOptions) {\n const config = opts.workerConfig ?? (await getWorkerConfig());\n\n await navigator.locks.request(`${syncDbWorkerLockName}-${config.dbId}`, { mode: \"exclusive\" }, async (lock) => {\n if (!lock) {\n return;\n }\n\n const cleanup = await createDbWorker(config, opts);\n\n const clientLockName = `${syncDbClientLockName}-${config.dbId}`;\n await new Promise<void>((resolve) => {\n const interval = setInterval(async () => {\n const { held } = await navigator.locks.query();\n const hasClients = held?.some((l) => l.name === clientLockName && l.mode === \"shared\");\n if (!hasClients) {\n clearInterval(interval);\n resolve();\n }\n }, 5_000);\n });\n\n await cleanup();\n });\n\n self.close();\n}\n\nfunction getMaxSyncId(db: SQLiteDbWrapper<WorkerDbSchema>, excludingStatus: \"none\" | \"pending\") {\n const [result] = db.executePrepared(\n \"get-max-sync-id\",\n { excludingStatus: excludingStatus as CrdtEventStatus },\n (db, params) =>\n db\n .selectFrom(\"worker.crdt_events\")\n .where(\"status\", \"!=\", params(\"excludingStatus\"))\n .select((eb) => eb.fn.max(\"sync_id\").as(\"sync_id\")),\n { loggerLevel: \"system\" },\n );\n\n return result?.sync_id ?? 0;\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { S as CreateRemoteSourceFactory } from "./index-BgkTUdlX.js";
1
+ import { w as CreateRemoteSourceFactory } from "./index-C1E8xMH1.js";
2
2
  //#region src/web-socket/ws-remote-source.d.ts
3
3
  type WsRemoteSourceConfig = {
4
4
  createWebSocket: () => Pick<WebSocket, "onmessage" | "close" | "addEventListener" | "removeEventListener"> & {
@@ -9,4 +9,4 @@ type WsRemoteSourceConfig = {
9
9
  declare const createWsRemoteSource: ({ createWebSocket }: WsRemoteSourceConfig) => CreateRemoteSourceFactory;
10
10
  //#endregion
11
11
  export { createWsRemoteSource as t };
12
- //# sourceMappingURL=ws-remote-source-BJFkwEyQ.d.ts.map
12
+ //# sourceMappingURL=ws-remote-source-Bk6NYwML.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sqlite-sync/core",
3
- "version": "0.14.2",
3
+ "version": "0.14.4",
4
4
  "description": "SQLite synchronization library with CRDT support",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1 +0,0 @@
1
- {"version":3,"file":"crdt-sync-remote-source-yeTEkOjH.js","names":[],"sources":["../src/xxh64.ts","../src/hash.ts","../src/hlc.ts","../src/schema/validate-crdt-event.ts","../src/sqlite-crdt/state-hash.ts","../src/sqlite-crdt/crdt-sync-remote-source.ts"],"sourcesContent":["// XXH64 over 32-bit halves kept in locals: on Hermes both BigInt arithmetic and allocating per step are slow.\n// Must stay bit-for-bit compatible with xxhash-wasm, since replicas compare checksums across platforms.\n\nconst P1_HI = 0x9e3779b1;\nconst P1_LO = 0x85ebca87;\nconst P2_HI = 0xc2b2ae3d;\nconst P2_LO = 0x27d4eb4f;\nconst P3_HI = 0x165667b1;\nconst P3_LO = 0x9e3779f9;\nconst P4_HI = 0x85ebca77;\nconst P4_LO = 0xc2b2ae63;\nconst P5_HI = 0x27d4eb2f;\nconst P5_LO = 0x165667c5;\n\nconst TWO_32 = 0x100000000;\n\n// The 64-bit helpers below return their result through these instead of allocating.\nlet hi = 0;\nlet lo = 0;\n\nfunction add(aHi: number, aLo: number, bHi: number, bLo: number) {\n const sum = aLo + bLo;\n hi = (aHi + bHi + (sum >= TWO_32 ? 1 : 0)) >>> 0;\n lo = sum >>> 0;\n}\n\nfunction sub(aHi: number, aLo: number, bHi: number, bLo: number) {\n const difference = aLo - bLo;\n hi = (aHi - bHi - (difference < 0 ? 1 : 0)) >>> 0;\n lo = difference >>> 0;\n}\n\nfunction mul(aHi: number, aLo: number, bHi: number, bLo: number) {\n const a0 = aLo & 0xffff;\n const a1 = aLo >>> 16;\n const b0 = bLo & 0xffff;\n const b1 = bLo >>> 16;\n\n const mid = a0 * b1 + a1 * b0;\n const low = a0 * b0 + (mid % 0x10000) * 0x10000;\n const carry = Math.floor(low / TWO_32);\n hi = (a1 * b1 + Math.floor(mid / 0x10000) + carry + Math.imul(aHi, bLo) + Math.imul(aLo, bHi)) >>> 0;\n lo = low >>> 0;\n}\n\n/** `bits` must be between 1 and 31. */\nfunction rotl(h: number, l: number, bits: number) {\n hi = ((h << bits) | (l >>> (32 - bits))) >>> 0;\n lo = ((l << bits) | (h >>> (32 - bits))) >>> 0;\n}\n\nfunction round(accHi: number, accLo: number, inputHi: number, inputLo: number) {\n mul(inputHi, inputLo, P2_HI, P2_LO);\n add(accHi, accLo, hi, lo);\n rotl(hi, lo, 31);\n mul(hi, lo, P1_HI, P1_LO);\n}\n\nfunction mergeRound(accHi: number, accLo: number, valueHi: number, valueLo: number) {\n round(0, 0, valueHi, valueLo);\n mul((accHi ^ hi) >>> 0, (accLo ^ lo) >>> 0, P1_HI, P1_LO);\n add(hi, lo, P4_HI, P4_LO);\n}\n\nfunction read32(bytes: Uint8Array, offset: number) {\n return (bytes[offset] | (bytes[offset + 1] << 8) | (bytes[offset + 2] << 16) | (bytes[offset + 3] << 24)) >>> 0;\n}\n\nexport function xxh64(bytes: Uint8Array, seed: bigint): bigint {\n const seedHi = Number((seed >> 32n) & 0xffffffffn);\n const seedLo = Number(seed & 0xffffffffn);\n const length = bytes.length;\n let offset = 0;\n let hashHi: number;\n let hashLo: number;\n\n if (length >= 32) {\n add(seedHi, seedLo, P1_HI, P1_LO);\n add(hi, lo, P2_HI, P2_LO);\n let v1Hi = hi;\n let v1Lo = lo;\n add(seedHi, seedLo, P2_HI, P2_LO);\n let v2Hi = hi;\n let v2Lo = lo;\n let v3Hi = seedHi;\n let v3Lo = seedLo;\n sub(seedHi, seedLo, P1_HI, P1_LO);\n let v4Hi = hi;\n let v4Lo = lo;\n\n const limit = length - 32;\n while (offset <= limit) {\n round(v1Hi, v1Lo, read32(bytes, offset + 4), read32(bytes, offset));\n v1Hi = hi;\n v1Lo = lo;\n round(v2Hi, v2Lo, read32(bytes, offset + 12), read32(bytes, offset + 8));\n v2Hi = hi;\n v2Lo = lo;\n round(v3Hi, v3Lo, read32(bytes, offset + 20), read32(bytes, offset + 16));\n v3Hi = hi;\n v3Lo = lo;\n round(v4Hi, v4Lo, read32(bytes, offset + 28), read32(bytes, offset + 24));\n v4Hi = hi;\n v4Lo = lo;\n offset += 32;\n }\n\n rotl(v1Hi, v1Lo, 1);\n hashHi = hi;\n hashLo = lo;\n rotl(v2Hi, v2Lo, 7);\n add(hashHi, hashLo, hi, lo);\n hashHi = hi;\n hashLo = lo;\n rotl(v3Hi, v3Lo, 12);\n add(hashHi, hashLo, hi, lo);\n hashHi = hi;\n hashLo = lo;\n rotl(v4Hi, v4Lo, 18);\n add(hashHi, hashLo, hi, lo);\n\n mergeRound(hi, lo, v1Hi, v1Lo);\n mergeRound(hi, lo, v2Hi, v2Lo);\n mergeRound(hi, lo, v3Hi, v3Lo);\n mergeRound(hi, lo, v4Hi, v4Lo);\n hashHi = hi;\n hashLo = lo;\n } else {\n add(seedHi, seedLo, P5_HI, P5_LO);\n hashHi = hi;\n hashLo = lo;\n }\n\n add(hashHi, hashLo, Math.floor(length / TWO_32), length >>> 0);\n hashHi = hi;\n hashLo = lo;\n\n while (offset + 8 <= length) {\n round(0, 0, read32(bytes, offset + 4), read32(bytes, offset));\n rotl((hashHi ^ hi) >>> 0, (hashLo ^ lo) >>> 0, 27);\n mul(hi, lo, P1_HI, P1_LO);\n add(hi, lo, P4_HI, P4_LO);\n hashHi = hi;\n hashLo = lo;\n offset += 8;\n }\n\n if (offset + 4 <= length) {\n mul(0, read32(bytes, offset), P1_HI, P1_LO);\n rotl((hashHi ^ hi) >>> 0, (hashLo ^ lo) >>> 0, 23);\n mul(hi, lo, P2_HI, P2_LO);\n add(hi, lo, P3_HI, P3_LO);\n hashHi = hi;\n hashLo = lo;\n offset += 4;\n }\n\n while (offset < length) {\n mul(0, bytes[offset], P5_HI, P5_LO);\n rotl((hashHi ^ hi) >>> 0, (hashLo ^ lo) >>> 0, 11);\n mul(hi, lo, P1_HI, P1_LO);\n hashHi = hi;\n hashLo = lo;\n offset++;\n }\n\n mul(hashHi, (hashLo ^ (hashHi >>> 1)) >>> 0, P2_HI, P2_LO);\n hashHi = hi;\n hashLo = lo;\n mul((hashHi ^ (hashHi >>> 29)) >>> 0, (hashLo ^ ((hashLo >>> 29) | (hashHi << 3))) >>> 0, P3_HI, P3_LO);\n hashHi = hi;\n hashLo = (lo ^ hi) >>> 0;\n\n return (BigInt(hashHi) << 32n) | BigInt(hashLo);\n}\n\nconst textEncoder = typeof TextEncoder === \"undefined\" ? null : new TextEncoder();\n\nexport function encodeUtf8(value: string): Uint8Array {\n return textEncoder ? textEncoder.encode(value) : encodeUtf8Fallback(value);\n}\n\n/** Matches TextEncoder, including U+FFFD for lone surrogates. */\nexport function encodeUtf8Fallback(value: string): Uint8Array {\n const bytes: number[] = [];\n for (let index = 0; index < value.length; index++) {\n let codePoint = value.charCodeAt(index);\n\n if (codePoint >= 0xd800 && codePoint <= 0xdbff) {\n const next = index + 1 < value.length ? value.charCodeAt(index + 1) : 0;\n if (next >= 0xdc00 && next <= 0xdfff) {\n codePoint = 0x10000 + ((codePoint - 0xd800) << 10) + (next - 0xdc00);\n index++;\n } else {\n codePoint = 0xfffd;\n }\n } else if (codePoint >= 0xdc00 && codePoint <= 0xdfff) {\n codePoint = 0xfffd;\n }\n\n if (codePoint < 0x80) {\n bytes.push(codePoint);\n } else if (codePoint < 0x800) {\n bytes.push(0xc0 | (codePoint >> 6), 0x80 | (codePoint & 0x3f));\n } else if (codePoint < 0x10000) {\n bytes.push(0xe0 | (codePoint >> 12), 0x80 | ((codePoint >> 6) & 0x3f), 0x80 | (codePoint & 0x3f));\n } else {\n bytes.push(\n 0xf0 | (codePoint >> 18),\n 0x80 | ((codePoint >> 12) & 0x3f),\n 0x80 | ((codePoint >> 6) & 0x3f),\n 0x80 | (codePoint & 0x3f),\n );\n }\n }\n return Uint8Array.from(bytes);\n}\n","import initXxhash, { type XXHashAPI } from \"xxhash-wasm\";\nimport { encodeUtf8, xxh64 } from \"./xxh64\";\n\ntype Hasher = {\n h64: (input: string, seed: bigint) => bigint;\n};\n\nlet loadPromise: Promise<void> | null = null;\nlet hasher: Hasher | null = null;\n\nconst jsHasher: Hasher = {\n h64: (input, seed) => xxh64(encodeUtf8(input), seed),\n};\n\nfunction createWasmHasher(api: XXHashAPI): Hasher {\n return {\n h64: (input, seed) => api.h64(input, seed),\n };\n}\n\nfunction ensureLoaded(): Promise<void> {\n if (!loadPromise) {\n if (typeof WebAssembly === \"undefined\") {\n hasher = jsHasher;\n loadPromise = Promise.resolve();\n } else {\n // Instantiation can still fail, e.g. under a CSP without 'wasm-unsafe-eval'; the JS hasher gives the same hashes.\n loadPromise = initXxhash().then(\n (api) => {\n hasher = createWasmHasher(api);\n },\n () => {\n hasher = jsHasher;\n },\n );\n }\n }\n return loadPromise;\n}\n\nfunction getHasher() {\n if (!hasher) {\n throw new Error(\"xxhash is not initialized; call xxhash.ensureLoaded() first\");\n }\n return hasher;\n}\n\nfunction h64(input: string, seed = 0n): bigint {\n return getHasher().h64(input, seed);\n}\n\nexport const xxhash = {\n ensureLoaded,\n h64,\n};\n","export interface HLC {\n timestamp: number;\n counter: number;\n nodeId: string;\n}\n\nconst MAX_COUNTER = 36 ** 5 - 1; // 60,466,175 — max value that fits in 5-char base36\nexport const DEFAULT_MAX_DRIFT_MS = 6 * 60 * 60 * 1000; // 6 hours\n\nexport class HLCCounter {\n private timestamp: number;\n private counter: number;\n private nodeId: string;\n\n private readonly getTimestamp: () => number;\n private readonly maxDrift: number;\n\n constructor(nodeId: string, getTimestamp: () => number, maxDrift: number = DEFAULT_MAX_DRIFT_MS) {\n this.timestamp = getTimestamp();\n this.counter = 0;\n this.nodeId = nodeId;\n this.getTimestamp = getTimestamp;\n this.maxDrift = maxDrift;\n }\n\n getCurrentHLC(): HLC {\n return {\n timestamp: this.timestamp,\n counter: this.counter,\n nodeId: this.nodeId,\n };\n }\n\n getNextHLC(): HLC {\n const now = this.getTimestamp();\n\n if (now > this.timestamp) {\n this.timestamp = now;\n this.counter = 0;\n return this.getCurrentHLC();\n }\n\n this.counter++;\n this.rollOverCounter();\n return this.getCurrentHLC();\n }\n\n /** Restore already-persisted causal state, regardless of wall-clock drift. */\n restoreHLC(hlc: HLC) {\n if (hlc.timestamp > this.timestamp) {\n this.timestamp = hlc.timestamp;\n this.counter = hlc.counter;\n } else if (hlc.timestamp === this.timestamp) {\n this.counter = Math.max(this.counter, hlc.counter);\n }\n }\n\n mergeHLC(hlc: HLC) {\n const now = this.getTimestamp();\n if (hlc.timestamp - now > this.maxDrift) {\n console.warn(\n `HLC: ignoring far-future timestamp (remote=${hlc.timestamp}, local=${now}, drift=${hlc.timestamp - now}ms)`,\n );\n return;\n }\n\n if (this.timestamp === hlc.timestamp) {\n this.counter = Math.max(this.counter, hlc.counter) + 1;\n } else if (this.timestamp > hlc.timestamp) {\n this.counter++;\n } else {\n this.timestamp = hlc.timestamp;\n this.counter = hlc.counter + 1;\n }\n this.rollOverCounter();\n }\n\n private rollOverCounter() {\n if (this.counter > MAX_COUNTER) {\n this.timestamp += 1;\n this.counter = 0;\n }\n }\n}\n\nexport function serializeHLC(hlc: HLC) {\n return `${hlc.timestamp.toString().padStart(15, \"0\")}:${hlc.counter.toString(36).padStart(5, \"0\")}:${hlc.nodeId}`;\n}\n\nexport function deserializeHLC(serialized: string): HLC {\n const parts = serialized.split(\":\");\n if (parts.length < 3) {\n throw new Error(`Invalid HLC format: expected at least 3 colon-separated segments, got ${parts.length}`);\n }\n\n const timestamp = parseInt(parts[0], 10);\n const counter = parseInt(parts[1], 36);\n\n if (Number.isNaN(timestamp) || Number.isNaN(counter)) {\n throw new Error(`Invalid HLC values: timestamp=${parts[0]}, counter=${parts[1]}`);\n }\n\n return {\n timestamp,\n counter,\n nodeId: parts.slice(2).join(\":\"),\n };\n}\n\nexport function compareHLC(one: HLC, two: HLC) {\n if (one.timestamp === two.timestamp) {\n if (one.counter === two.counter) {\n if (one.nodeId === two.nodeId) {\n return 0;\n }\n return one.nodeId < two.nodeId ? -1 : 1;\n }\n return one.counter - two.counter;\n }\n return one.timestamp - two.timestamp;\n}\n","import type { SyncDbSchema } from \"../sqlite-crdt/crdt-schema\";\nimport type { CrdtEventType, NewCrdtEvent } from \"../sqlite-crdt/crdt-table-schema\";\nimport type { AnyTableBuilder } from \"./table-builder\";\n\nexport type NewCrdtEventValidationResult =\n | { success: true; event: NewCrdtEvent }\n | { success: false; errors: string[] };\n\nexport class CrdtEventValidationError extends Error {\n constructor(readonly errors: string[]) {\n super(`Invalid CRDT events: ${errors.join(\"; \")}`);\n this.name = \"CrdtEventValidationError\";\n }\n}\n\nconst crdtEventTypes = [\"item-created\", \"item-updated\", \"item-deleted\"] as const;\n\nexport function validateNewCrdtEvent(\n schema: Pick<SyncDbSchema, \"tables\" | \"tablesConfig\">,\n input: unknown,\n): NewCrdtEventValidationResult {\n const errors: string[] = [];\n\n if (!isRecord(input)) {\n return { success: false, errors: [\"Event must be an object\"] } satisfies NewCrdtEventValidationResult;\n }\n\n const type = input.type;\n const dataset = input.dataset;\n const itemId = input.item_id;\n\n let payload = input.payload;\n let payloadParseFailed = false;\n if (typeof payload === \"string\") {\n try {\n payload = JSON.parse(payload);\n } catch {\n payloadParseFailed = true;\n errors.push(`Event payload is not valid JSON`);\n }\n }\n\n if (!isCrdtEventType(type)) {\n errors.push(`Invalid event type \"${String(type)}\"`);\n }\n if (typeof dataset !== \"string\") {\n errors.push(`Event dataset must be a string`);\n }\n if (typeof itemId !== \"string\") {\n errors.push(`Event item_id must be a string`);\n }\n if (!payloadParseFailed && !isRecord(payload)) {\n errors.push(`Event payload must be an object`);\n }\n\n const datasetMatch = typeof dataset === \"string\" ? findTableForDataset(schema, dataset) : null;\n if (typeof dataset === \"string\" && !datasetMatch) {\n errors.push(`Unknown dataset \"${dataset}\"`);\n }\n\n if (isCrdtEventType(type) && datasetMatch && isRecord(payload)) {\n switch (type) {\n case \"item-created\": {\n const result = datasetMatch.table.validatePayload(payload, { event: \"item-created\" });\n if (!result.success) {\n errors.push(...result.errors.map((error) => `payload: ${error}`));\n }\n if (typeof itemId === \"string\" && \"id\" in payload && payload.id !== itemId) {\n errors.push(`payload: Column \"id\" must match item_id \"${itemId}\"`);\n }\n break;\n }\n case \"item-updated\": {\n const result = datasetMatch.table.validatePayload(payload, { event: \"item-updated\" });\n if (!result.success) {\n errors.push(...result.errors.map((error) => `payload: ${error}`));\n }\n break;\n }\n case \"item-deleted\":\n if (Object.keys(payload).length > 0) {\n errors.push(`payload: item-deleted events must have an empty payload`);\n }\n break;\n }\n }\n\n if (errors.length > 0) {\n return { success: false, errors } satisfies NewCrdtEventValidationResult;\n }\n\n return {\n success: true,\n event: {\n type: type as CrdtEventType,\n dataset: datasetMatch?.baseTableName ?? (dataset as string),\n item_id: itemId as string,\n payload: payload as Record<string, unknown>,\n },\n } satisfies NewCrdtEventValidationResult;\n}\n\nfunction findTableForDataset(\n schema: Pick<SyncDbSchema, \"tables\" | \"tablesConfig\">,\n dataset: string,\n): { table: AnyTableBuilder; baseTableName: string } | null {\n const config = schema.tablesConfig.find(\n (tableConfig) => tableConfig.baseTableName === dataset || tableConfig.crdtTableName === dataset,\n );\n if (config) {\n const table = schema.tables[config.crdtTableName];\n return table ? { table, baseTableName: config.baseTableName } : null;\n }\n const table = Object.hasOwn(schema.tables, dataset) ? schema.tables[dataset] : undefined;\n return table ? { table, baseTableName: dataset } : null;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction isCrdtEventType(value: unknown): value is CrdtEventType {\n return typeof value === \"string\" && crdtEventTypes.includes(value as CrdtEventType);\n}\n","import { xxhash } from \"../hash\";\nimport type { InternalSQLiteWrapper } from \"../sqlite-db-wrapper\";\nimport { quoteId } from \"../utils\";\nimport type { SyncDbSchema } from \"./crdt-schema\";\nimport type { CrdtUpdateLogItem, CrdtUpdateLogPayload } from \"./crdt-table-schema\";\n\nconst STATE_HASH_PATTERN = /^(-?\\d+):([0-9a-f]{16})$/;\n\n/**\n * Persistence for the state hash. Reads and writes must go through the same\n * transaction that changes the update log, so a rollback reverts both.\n */\nexport type StateHashStore = {\n read: () => string | null;\n write: (value: string) => void;\n};\n\nexport function formatStateHash(schemaVersion: number, sum: bigint) {\n return `${schemaVersion}:${sum.toString(16).padStart(16, \"0\")}`;\n}\n\nexport function parseStateHash(value: string | null | undefined) {\n const match = value ? STATE_HASH_PATTERN.exec(value) : null;\n if (!match) {\n return null;\n }\n return { schemaVersion: Number(match[1]), sum: BigInt(`0x${match[2]}`) };\n}\n\n/** Returns the hashed columns for a dataset, or null to hash whichever cells the update log holds. */\nexport type StateHashColumns = (dataset: string) => readonly string[] | null;\n\nexport type StateHashAccumulator = ReturnType<typeof createStateHashAccumulator>;\n\n/**\n * The state hash is a sum of per-cell hashes over the CRDT update log, tagged with\n * the schema version it was computed for. It depends only on current state, not on\n * history, so replicas that converged agree on it no matter how they got there.\n *\n * Cells are the dataset's schema columns except `id`. A cell missing from the\n * update log hashes as written by the item's latest create (the HLC stored under\n * `id`). Older builds filled column defaults into creates migrated through an\n * add-column migration, so replicas that replayed history hold such cells while\n * replicas that applied the creates earlier never stored them.\n */\nexport function createStateHashAccumulator({ sum, getColumns }: { sum: bigint; getColumns: StateHashColumns }) {\n let current = sum;\n\n const cellHash = (dataset: string, itemId: string, column: string, hlc: string) =>\n xxhash.h64(`${dataset}\\u0000${itemId}\\u0000${column}\\u0000${hlc}`);\n\n const columnsOf = (dataset: string, meta: CrdtUpdateLogPayload) =>\n getColumns(dataset) ?? Object.keys(meta).filter((column) => column !== \"id\");\n\n return {\n get sum() {\n return current;\n },\n addItem(dataset: string, itemId: string, meta: CrdtUpdateLogPayload) {\n const createHlc = meta.id ?? \"\";\n for (const column of columnsOf(dataset, meta)) {\n current = BigInt.asUintN(64, current + cellHash(dataset, itemId, column, meta[column] ?? createHlc));\n }\n },\n /**\n * `meta` is the item's update log after the write. `replacedCells` holds each written\n * cell's HLC from before it, and `previousCreateHlc` the item's create HLC from before.\n */\n updateItem(\n dataset: string,\n itemId: string,\n meta: CrdtUpdateLogPayload,\n previousCreateHlc: string | undefined,\n replacedCells: readonly [column: string, previousHlc: string | undefined][],\n ) {\n const columns = getColumns(dataset);\n for (const [column, previousHlc] of replacedCells) {\n if (columns ? !columns.includes(column) : column === \"id\") {\n continue;\n }\n const previous = previousHlc ?? (columns ? (previousCreateHlc ?? \"\") : undefined);\n if (previous !== undefined) {\n current = BigInt.asUintN(64, current - cellHash(dataset, itemId, column, previous));\n }\n current = BigInt.asUintN(64, current + cellHash(dataset, itemId, column, meta[column]));\n }\n\n if (!columns || meta.id === previousCreateHlc) {\n return;\n }\n for (const column of columns) {\n if (meta[column] === undefined) {\n current = BigInt.asUintN(\n 64,\n current -\n cellHash(dataset, itemId, column, previousCreateHlc ?? \"\") +\n cellHash(dataset, itemId, column, meta.id ?? \"\"),\n );\n }\n }\n },\n };\n}\n\nexport type StateHashTracker = ReturnType<typeof createStateHashTracker>;\n\n/** Keeps the stored state hash in step with the update log. Without a store it does nothing. */\nexport function createStateHashTracker({\n db,\n updateLogTable,\n schema,\n getSchemaVersion,\n store,\n}: {\n db: InternalSQLiteWrapper<any>;\n updateLogTable: string;\n schema: Pick<SyncDbSchema, \"tables\" | \"tablesConfig\"> | undefined;\n getSchemaVersion: () => number;\n store: StateHashStore | null;\n}) {\n const columnsByDataset = new Map(\n (schema?.tablesConfig ?? []).flatMap((config) => {\n const table = schema?.tables[config.crdtTableName];\n return table ? [[config.baseTableName, Object.keys(table.columns).filter((column) => column !== \"id\")]] : [];\n }),\n );\n const getColumns = (dataset: string) => columnsByDataset.get(dataset) ?? null;\n let active: StateHashAccumulator | null = null;\n\n const rebuild = () => {\n const accumulator = createStateHashAccumulator({ sum: 0n, getColumns });\n const batchSize = 1000;\n let after: [string, string] = [\"\", \"\"];\n for (;;) {\n const rows = db.executePreparedRaw<[string, string, number], CrdtUpdateLogItem>({\n key: \"get-update-log-batch\",\n sql: `select \"dataset\", \"item_id\", \"payload\" from ${quoteId(updateLogTable)}\nwhere (\"item_id\", \"dataset\") > (?, ?)\norder by \"item_id\", \"dataset\"\nlimit ?`,\n params: [...after, batchSize],\n meta: { loggerLevel: \"system\" },\n });\n\n for (const row of rows) {\n accumulator.addItem(row.dataset, row.item_id, JSON.parse(row.payload));\n }\n\n const lastRow = rows[rows.length - 1];\n if (!lastRow || rows.length < batchSize) {\n break;\n }\n after = [lastRow.item_id, lastRow.dataset];\n }\n return accumulator.sum;\n };\n\n // Hashes computed for another schema version cover a different set of columns,\n // and migrations rewrite the update log directly, so those are rebuilt.\n const readSum = (store: StateHashStore) => {\n const stored = parseStateHash(store.read());\n if (stored && stored.schemaVersion === getSchemaVersion()) {\n return stored.sum;\n }\n const sum = rebuild();\n store.write(formatStateHash(getSchemaVersion(), sum));\n return sum;\n };\n\n return {\n /** The accumulator of the running `track` call, for apply functions to add cells to. */\n get active() {\n return active;\n },\n read: () => store?.read() ?? null,\n /** Rebuilds a missing or outdated hash. Must run inside a transaction. */\n initialize: () => {\n if (store) {\n readSum(store);\n }\n },\n /** Must run inside the transaction that changes the update log, so a rollback reverts the hash too. */\n track: <T>(callback: () => T): T => {\n if (!store || active) {\n return callback();\n }\n const initialSum = readSum(store);\n const accumulator = createStateHashAccumulator({ sum: initialSum, getColumns });\n active = accumulator;\n try {\n const result = callback();\n if (accumulator.sum !== initialSum) {\n store.write(formatStateHash(getSchemaVersion(), accumulator.sum));\n }\n return result;\n } finally {\n active = null;\n }\n },\n };\n}\n","import type { SyncDbMigrator } from \"../migrations/migrator\";\nimport { createDeferredPromise, createTypedEventTarget, ensureSingletonExecution, tryCatchAsync } from \"../utils\";\nimport type { EventsPullResponse, WorkerState } from \"../worker-db/worker-common\";\nimport type { PendingCrdtEvent } from \"./apply-crdt-event\";\nimport type { CrdtStorage } from \"./crdt-storage\";\nimport { parseStateHash } from \"./state-hash\";\nimport type { StoredValue } from \"./stored-value\";\n\ntype CrdtSyncRemoteSourceConfig = {\n bufferSize: number;\n storage: CrdtStorage;\n migrator: SyncDbMigrator;\n pullSyncId: StoredValue<number>;\n pushSyncId: StoredValue<number>;\n nodeId: string;\n remoteFactory?: CreateRemoteSourceFactory;\n};\n\nexport type EventsPullRequest = {\n afterSyncId: number;\n excludeNodeId?: string;\n};\n\nexport const PUSH_BATCH_SIZE = 75;\nexport const MAX_PUSH_EVENTS = 100;\n\nexport type EventsPushRequest = {\n nodeId: string;\n events: (PendingCrdtEvent & { schema_version: number })[];\n};\nexport type EventsPushResponse = {\n ok: boolean;\n /** Remote sync_id right before the pushed events were enqueued. */\n beforeSyncId?: number;\n /** Remote sync_id right after the pushed events were enqueued. */\n afterSyncId?: number;\n};\n\nconst DISCONNECT_WATCHDOG_MS = 15_000;\nconst REQUEST_TIMEOUT_MS = 10_000;\nconst RECONNECT_BASE_DELAY_MS = 1_000;\nconst RECONNECT_MAX_DELAY_MS = 30_000;\n// Backoff restarts only after a connection stays up this long, so a remote that accepts and then drops\n// connections right away is not hammered.\nconst RECONNECT_MIN_UPTIME_MS = 5_000;\n// Pings a remote that has sent nothing for this long, to catch connections that died without closing.\nconst HEARTBEAT_IDLE_MS = 25_000;\n\nexport type CrdtSyncRemoteSource = ReturnType<typeof createCrdtSyncRemoteSource>;\n\nexport type EventsAvailable = {\n newSyncId: number;\n remoteStateHash: string | null;\n};\n\n/**\n * Opens one connection to the remote. Each call must return a new source: the library calls `disconnect` on every\n * returned source exactly once. Call `onClosed` when the connection ends on its own, at any time including during\n * this call; the library then reconnects with backoff. Requests may wait for the connection to open and should\n * reject once it closes. The library times requests out and shows the remote as online after the first one succeeds.\n * With `ping`, the library also checks idle connections and replaces one whose ping fails or times out. It calls `ping`\n * only on an online connection, one at a time, and never after the connection closes; a ping does not need to settle\n * once the source has closed.\n */\nexport type CreateRemoteSourceFactory = (opts: {\n onEventsAvailable: (event: EventsAvailable) => void;\n onClosed: (error?: unknown) => void;\n}) => RemoteSource;\n\ntype RemoteSource = {\n pullEvents: (request: EventsPullRequest) => Promise<EventsPullResponse>;\n pushEvents: (request: EventsPushRequest) => Promise<EventsPushResponse>;\n ping?: () => Promise<void>;\n disconnect?: () => void | Promise<void>;\n};\n\nexport class SchemaVersionMismatchError extends Error {\n constructor(\n public remoteSchemaVersion: number,\n public localSchemaVersion: number,\n ) {\n super(`Schema version mismatch: remote ${remoteSchemaVersion} != local ${localSchemaVersion}`);\n this.name = \"SchemaVersionMismatchError\";\n }\n}\n\ntype RemoteSourceState =\n | {\n type: \"connecting\";\n deSynced: boolean;\n schemaVersionMismatched: boolean;\n }\n | {\n type: \"offline\";\n reason: OfflineReason;\n deSynced: boolean;\n schemaVersionMismatched: boolean;\n }\n | {\n type: \"online\";\n deSynced: boolean;\n schemaVersionMismatched: boolean;\n };\n\nexport type OfflineReason =\n | \"NOT_INITIALIZED\"\n | \"INITIALIZATION_FAILED\"\n | \"REMOTE_PUSH_ERROR\"\n | \"REMOTE_PULL_ERROR\"\n | \"CONNECTION_LOST\"\n | \"NETWORK_OFFLINE\"\n | \"DISCONNECTED\";\n\nexport type DeSyncDetectedReason = \"CHECKSUM_MISMATCH\" | \"ERROR_APPLYING_REMOTE_EVENT\";\n\nexport const createCrdtSyncRemoteSource = ({\n bufferSize,\n storage,\n migrator,\n pullSyncId,\n pushSyncId,\n nodeId,\n remoteFactory,\n}: CrdtSyncRemoteSourceConfig) => {\n const eventTarget = createTypedEventTarget<{\n \"state-changed\": RemoteSourceState[\"type\"];\n \"de-sync-detected\": {\n reason: DeSyncDetectedReason;\n };\n \"remote-schema-version-mismatch\": {\n remoteSchemaVersion: number;\n localSchemaVersion: number;\n };\n }>();\n\n let remoteState: RemoteSourceState = {\n type: \"offline\",\n reason: \"NOT_INITIALIZED\",\n deSynced: false,\n schemaVersionMismatched: false,\n };\n\n const patchRemoteState = (state: Partial<RemoteSourceState>) => {\n const previous = remoteState;\n remoteState = { ...remoteState, ...state } as RemoteSourceState;\n if (\n remoteState.type !== previous.type ||\n remoteState.deSynced !== previous.deSynced ||\n remoteState.schemaVersionMismatched !== previous.schemaVersionMismatched\n ) {\n eventTarget.dispatchEvent(\"state-changed\", remoteState.type);\n }\n };\n\n let current: Connection | null = null;\n let disposed = false;\n let wantsOnline = false;\n let reconnectTimer: ReturnType<typeof setTimeout> | undefined;\n let reconnectAttempt = 0;\n\n const disconnectSource = (source: RemoteSource) => {\n const watchdog = setTimeout(() => {\n console.error(`Remote source did not disconnect within ${DISCONNECT_WATCHDOG_MS}ms`);\n }, DISCONNECT_WATCHDOG_MS);\n void tryCatchAsync(async () => await source.disconnect?.()).then((result) => {\n clearTimeout(watchdog);\n if (!result.success) {\n console.warn(\"Error while disconnecting from remote source\", result.error);\n }\n });\n };\n\n const closeConnection = (connection: Connection, reason: OfflineReason, { reconnect = false } = {}) => {\n if (current !== connection) {\n return false;\n }\n current = null;\n if (reconnect && scheduleReconnect()) {\n patchRemoteState({ type: \"connecting\" });\n } else {\n patchRemoteState({ type: \"offline\", reason });\n }\n connection.close();\n return true;\n };\n\n const cancelReconnect = () => {\n clearTimeout(reconnectTimer);\n reconnectTimer = undefined;\n };\n\n // Retrying after a schema mismatch would fail the same way.\n const scheduleReconnect = () => {\n if (!wantsOnline || current || reconnectTimer || remoteState.schemaVersionMismatched) {\n return false;\n }\n const maxDelay = Math.min(RECONNECT_MAX_DELAY_MS, RECONNECT_BASE_DELAY_MS * 2 ** reconnectAttempt);\n reconnectAttempt++;\n const delay = maxDelay / 2 + (Math.random() * maxDelay) / 2;\n reconnectTimer = setTimeout(() => {\n reconnectTimer = undefined;\n void connect();\n }, delay);\n return true;\n };\n\n type Connection = ReturnType<typeof createConnection>;\n const createConnection = (factory: CreateRemoteSourceFactory) => {\n const controller = new AbortController();\n const { signal } = controller;\n const ready = createDeferredPromise<void>();\n let source: RemoteSource | null = null;\n let establishedAt: number | null = null;\n let lastActivityAt = Date.now();\n let heartbeatTimer: ReturnType<typeof setTimeout> | undefined;\n\n const untilClosed = <T>(operation: () => Promise<T>, timeoutMs?: number) =>\n new Promise<T>((resolve, reject) => {\n const onAbort = () => reject(new Error(\"Remote connection closed\"));\n if (signal.aborted) {\n onAbort();\n return;\n }\n signal.addEventListener(\"abort\", onAbort, { once: true });\n const timeout =\n timeoutMs === undefined\n ? undefined\n : setTimeout(() => reject(new Error(`Remote request timed out after ${timeoutMs}ms`)), timeoutMs);\n operation()\n .then(resolve, reject)\n .finally(() => {\n clearTimeout(timeout);\n signal.removeEventListener(\"abort\", onAbort);\n });\n });\n\n const request = async <T>(operation: () => Promise<T>) => {\n const response = await untilClosed(operation, REQUEST_TIMEOUT_MS);\n lastActivityAt = Date.now();\n if (establishedAt === null && !signal.aborted) {\n establishedAt = Date.now();\n patchRemoteState({ type: \"online\", schemaVersionMismatched: false });\n ready.resolve();\n scheduleHeartbeat();\n }\n return response;\n };\n\n const fail = (reason: OfflineReason, { reconnect = true } = {}) => {\n if (current !== connection) {\n return;\n }\n if (establishedAt !== null && Date.now() - establishedAt >= RECONNECT_MIN_UPTIME_MS) {\n reconnectAttempt = 0;\n }\n closeConnection(connection, establishedAt === null ? \"INITIALIZATION_FAILED\" : reason, { reconnect });\n };\n\n // A close during the factory call aborts before `source` is set, so the abort check below disconnects it.\n const start = () => {\n if (signal.aborted) {\n return;\n }\n\n let created: RemoteSource;\n try {\n created = factory({\n onEventsAvailable: ({ newSyncId, remoteStateHash }) => {\n lastActivityAt = Date.now();\n pull({ remoteSyncId: newSyncId, remoteStateHash, includeSelf: false });\n },\n onClosed: (error) => {\n if (signal.aborted) {\n return;\n }\n if (error !== undefined) {\n console.warn(\"Remote connection closed\", error);\n }\n fail(\"CONNECTION_LOST\");\n },\n });\n } catch (error) {\n console.warn(\"Failed to create remote source\", error);\n fail(\"INITIALIZATION_FAILED\");\n return;\n }\n\n if (signal.aborted) {\n disconnectSource(created);\n return;\n }\n source = created;\n void sync();\n };\n\n const scheduleHeartbeat = () => {\n const ping = source?.ping?.bind(source);\n if (!ping || signal.aborted) {\n return;\n }\n heartbeatTimer = setTimeout(\n async () => {\n if (Date.now() - lastActivityAt >= HEARTBEAT_IDLE_MS) {\n try {\n await request(ping);\n } catch (error) {\n if (!signal.aborted) {\n console.warn(\"Remote did not answer a ping\", error);\n fail(\"CONNECTION_LOST\");\n }\n return;\n }\n }\n scheduleHeartbeat();\n },\n Math.max(0, lastActivityAt + HEARTBEAT_IDLE_MS - Date.now()),\n );\n };\n\n type ActivePull = { promise: Promise<void>; requestedSyncId: number | null };\n let activePull: ActivePull | null = null;\n const pull = (request?: {\n remoteSyncId?: number;\n remoteStateHash?: string | null;\n includeSelf?: boolean;\n }): Promise<void> => {\n if (!source || signal.aborted) {\n return Promise.resolve();\n }\n const activeSource = source;\n\n const remoteSyncId = request?.remoteSyncId;\n\n if (remoteSyncId !== undefined && remoteSyncId <= pullSyncId.current) {\n // We are already caught up to this broadcast, so there is nothing to pull.\n // This is the quiescent moment to verify we have not diverged from the\n // remote (the check is a no-op unless we are exactly aligned: remoteSyncId\n // === pullSyncId.current).\n checkRemoteConsistency(remoteSyncId, request?.remoteStateHash ?? null);\n return Promise.resolve();\n }\n\n if (activePull) {\n if (remoteSyncId !== undefined && (!activePull.requestedSyncId || activePull.requestedSyncId < remoteSyncId)) {\n activePull.requestedSyncId = remoteSyncId;\n }\n return activePull.promise;\n }\n\n const nextPull: ActivePull = { promise: Promise.resolve(), requestedSyncId: null };\n nextPull.promise = pullAllEvents(activeSource, {\n afterSyncId: pullSyncId.current,\n excludeNodeId: request?.includeSelf ? undefined : nodeId,\n })\n .catch((error) => {\n if (signal.aborted) {\n return;\n }\n console.error(\"Error pulling events. Going offline.\", error);\n fail(\"REMOTE_PULL_ERROR\");\n })\n .finally(() => {\n if (activePull === nextPull) {\n activePull = null;\n }\n\n const nextTarget = nextPull.requestedSyncId;\n if (nextTarget && nextTarget > pullSyncId.current) {\n pull({ remoteSyncId: nextTarget });\n }\n });\n activePull = nextPull;\n return nextPull.promise;\n };\n\n const pullAllEvents = async (activeSource: RemoteSource, opts: EventsPullRequest) => {\n let hasMore = true;\n let afterSyncId = opts.afterSyncId;\n while (hasMore) {\n const response = await request(() =>\n activeSource.pullEvents({\n ...opts,\n afterSyncId,\n }),\n );\n if (signal.aborted) {\n return;\n }\n hasMore = response.hasMore;\n afterSyncId = response.nextSyncId;\n\n if (response.events) {\n const { processed } = storage.enqueueRemoteEvents(\n response.events.map((x) => {\n if (x.schema_version > migrator.currentSchemaVersion) {\n eventTarget.dispatchEvent(\"remote-schema-version-mismatch\", {\n remoteSchemaVersion: x.schema_version,\n localSchemaVersion: migrator.currentSchemaVersion,\n });\n if (!remoteState.schemaVersionMismatched) {\n patchRemoteState({ schemaVersionMismatched: true });\n }\n throw new SchemaVersionMismatchError(x.schema_version, migrator.currentSchemaVersion);\n }\n return x;\n }),\n );\n await untilClosed(() => processed);\n if (signal.aborted) {\n return;\n }\n }\n if (response.nextSyncId <= pullSyncId.current) {\n break;\n }\n if (response.nextSyncId > pullSyncId.current) {\n pullSyncId.current = response.nextSyncId;\n }\n }\n };\n\n const push = ensureSingletonExecution(async () => {\n while (source && !signal.aborted) {\n const activeSource = source;\n const eventsBatch = storage.getEventsBatch({\n status: \"applied\",\n afterSyncId: pushSyncId.current,\n excludeOrigin: \"remote\",\n limit: bufferSize,\n });\n if (eventsBatch.events.length === 0) {\n break;\n }\n\n let response: EventsPushResponse;\n try {\n response = await request(() =>\n activeSource.pushEvents({\n nodeId,\n events: eventsBatch.events.map((event) => ({\n schema_version: event.schema_version,\n timestamp: event.timestamp,\n type: event.type,\n dataset: event.dataset,\n item_id: event.item_id,\n payload: event.payload,\n })),\n }),\n );\n } catch (error) {\n if (!signal.aborted) {\n console.error(\"Error pushing events. Going offline.\", error);\n fail(\"REMOTE_PUSH_ERROR\");\n }\n return;\n }\n\n if (signal.aborted) {\n return;\n }\n\n if (!response.ok) {\n // The remote would reject the same batch again, so only an explicit sync retries it.\n console.error(\"Remote rejected pushed events. Going offline.\");\n fail(\"REMOTE_PUSH_ERROR\", { reconnect: false });\n return;\n }\n\n pushSyncId.current = eventsBatch.nextSyncId;\n\n // Fast-forward the pull cursor: the remote assigns sync ids for the pushed\n // events synchronously, so (beforeSyncId, afterSyncId] contains only this\n // node's own events. If we are caught up to at least beforeSyncId, the skipped\n // range (pullSyncId, afterSyncId] contains only our own events, so there is\n // nothing to pull up to afterSyncId.\n if (\n response.beforeSyncId !== undefined &&\n response.afterSyncId !== undefined &&\n response.beforeSyncId <= pullSyncId.current &&\n response.afterSyncId > pullSyncId.current\n ) {\n pullSyncId.current = response.afterSyncId;\n }\n if (!eventsBatch.hasMore) {\n break;\n }\n }\n });\n\n // Waits out in-flight operations first: they may have started before the caller's request.\n const sync = async () => {\n await activePull?.promise;\n await pull();\n await push.promise();\n await push();\n };\n\n const close = () => {\n controller.abort();\n clearTimeout(heartbeatTimer);\n ready.resolve();\n if (source) {\n disconnectSource(source);\n }\n };\n\n const connection = { ready: ready.promise, start, close, push, sync };\n return connection;\n };\n\n const connect = (): Promise<void> => {\n if (disposed) {\n return Promise.resolve();\n }\n if (current) {\n return current.ready;\n }\n if (!remoteFactory) {\n console.warn(\"Remote source factory not provided. Going offline.\");\n patchRemoteState({ type: \"offline\", reason: \"NOT_INITIALIZED\" });\n return Promise.resolve();\n }\n\n cancelReconnect();\n wantsOnline = true;\n const connection = createConnection(remoteFactory);\n current = connection;\n patchRemoteState({ type: \"connecting\" });\n connection.start();\n return connection.ready;\n };\n\n const goOnline = () => {\n reconnectAttempt = 0;\n return connect();\n };\n\n // Unlike goOffline, leaves syncWithRemote free to reconnect, but does not retry on its own: the caller\n // knows the network is gone and calls syncWithRemote once it is back.\n const dropConnection = (reason: OfflineReason) => {\n cancelReconnect();\n if (current) {\n closeConnection(current, reason);\n } else if (remoteState.type === \"connecting\") {\n patchRemoteState({ type: \"offline\", reason });\n }\n };\n\n const goOffline = async (reason: OfflineReason) => {\n wantsOnline = false;\n dropConnection(reason);\n };\n\n // Reconnects right away, skipping any backoff, unless goOffline or a schema mismatch took us offline.\n const syncWithRemote = async () => {\n if (current) {\n await current.sync();\n } else if (wantsOnline && !remoteState.schemaVersionMismatched) {\n await goOnline();\n }\n };\n\n // De-sync detection: when we are exactly caught up to the remote's broadcast\n // sync id and fully quiescent, we hold the same events as the remote, so our\n // materialized CRDT state and its hash must match. A mismatch means the nodes\n // have diverged.\n const checkRemoteConsistency = (remoteSyncId: number, remoteStateHash: string | null) => {\n // A remote that does not report a state hash gives us nothing to compare against.\n if (remoteStateHash == null) {\n return;\n }\n\n // Only meaningful when we are exactly caught up: if we are behind we still\n // need to pull; if we are ahead our state covers events the remote hash\n // does not.\n if (remoteSyncId !== pullSyncId.current) {\n return;\n }\n\n // Quiescence: the hashes only match when nothing is left to apply locally and\n // no local applied events are still waiting to be pushed (those are in our\n // state but the remote has not seen them yet).\n if (!storage.checkIsQuiescent(pushSyncId.current)) {\n return;\n }\n\n const localStateHash = storage.getStateHash();\n if (localStateHash === null || localStateHash === remoteStateHash) {\n return;\n }\n\n // Hashes for different schema versions cover different columns, e.g. while\n // the remote has been migrated by a deploy that this client has not loaded yet.\n const local = parseStateHash(localStateHash);\n const remote = parseStateHash(remoteStateHash);\n if (!local || !remote || local.schemaVersion !== remote.schemaVersion) {\n return;\n }\n\n eventTarget.dispatchEvent(\"de-sync-detected\", { reason: \"CHECKSUM_MISMATCH\" });\n console.warn(\n `[sqlite-sync] De-sync detected at syncId ${remoteSyncId}: local state hash ${localStateHash} != remote ${remoteStateHash}. Local and remote have diverged despite being caught up.`,\n );\n if (!remoteState.deSynced) {\n patchRemoteState({ deSynced: true });\n }\n };\n\n const eventsAppliedSubscription = storage.addEventListener(\"events-applied\", () => {\n current?.push();\n });\n\n const remoteEventApplyFailedSubscription = storage.addEventListener(\"remote-event-apply-failed\", () => {\n eventTarget.dispatchEvent(\"de-sync-detected\", { reason: \"ERROR_APPLYING_REMOTE_EVENT\" });\n if (!remoteState.deSynced) {\n patchRemoteState({ deSynced: true });\n }\n });\n\n const getState = (): WorkerState => ({\n remoteState: remoteState.type,\n deSynced: remoteState.deSynced,\n schemaVersionMismatched: remoteState.schemaVersionMismatched,\n });\n\n const dispose = async () => {\n disposed = true;\n eventsAppliedSubscription.unsubscribe();\n remoteEventApplyFailedSubscription.unsubscribe();\n await goOffline(\"DISCONNECTED\");\n };\n\n return {\n goOnline,\n goOffline,\n dropConnection,\n syncWithRemote,\n getState,\n dispose,\n addEventListener: eventTarget.addEventListener,\n removeEventListener: eventTarget.removeEventListener,\n };\n};\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"reset-state-DAPyJglN.js","names":[],"sources":["../src/wasm-driver.ts","../src/schema/verify-sync-schema-wasm.ts","../src/worker-db/worker-common.ts","../src/worker-db/replica-lock.ts","../src/worker-db/reset-state.ts"],"sourcesContent":["import sqlite3InitModule, {\n type BindableValue,\n type Database,\n type Sqlite3Static,\n type SqlValue,\n} from \"@sqlite.org/sqlite-wasm\";\nimport type { SqliteDriver, SqliteDriverStatement, SqliteDriverValue } from \"./sqlite-driver\";\n\nlet sqliteModulePromise: Promise<Sqlite3Static> | null = null;\n\nexport function loadSqliteWasm() {\n sqliteModulePromise ??= sqlite3InitModule();\n return sqliteModulePromise;\n}\n\nexport async function createWasmMemoryDriver() {\n const sqlite3 = await loadSqliteWasm();\n return createWasmSqliteDriver(sqlite3, new sqlite3.oo1.DB({ filename: \":memory:\" }));\n}\n\nexport function createWasmSqliteDriver(sqlite3: Sqlite3Static, db: Database): SqliteDriver {\n const { capi } = sqlite3;\n\n const prepare = (sql: string): SqliteDriverStatement => {\n const statement = db.prepare(sql);\n const pointer = statement.pointer;\n if (!pointer) {\n throw new Error(\"Prepared statement is already finalized\");\n }\n\n return {\n readonly: capi.sqlite3_stmt_readonly(pointer) !== 0,\n run: (parameters) => {\n try {\n if (parameters.length > 0) {\n statement.bind(parameters as BindableValue[]);\n }\n\n const rows: SqliteDriverValue[][] = [];\n let columns: string[] | undefined;\n while (statement.step()) {\n columns ??= statement.getColumnNames([]);\n rows.push(statement.get([]) as SqliteDriverValue[]);\n }\n return { columns: columns ?? [], rows };\n } finally {\n statement.reset(true);\n }\n },\n finalize: () => {\n statement.finalize();\n },\n };\n };\n\n return {\n prepare,\n synchronousHooks: true,\n totalChanges: () => capi.sqlite3_total_changes(db),\n setHooks: (hooks) => {\n if (!hooks) {\n capi.sqlite3_update_hook(db, 0 as never, 0);\n capi.sqlite3_rollback_hook(db, 0, 0);\n capi.sqlite3_commit_hook(db, 0, 0);\n return;\n }\n\n capi.sqlite3_update_hook(db, (_ctx, _opId, _db, table) => hooks.onUpdate(table), 0);\n capi.sqlite3_rollback_hook(\n db,\n () => {\n hooks.onRollback();\n return 0;\n },\n 0,\n );\n capi.sqlite3_commit_hook(\n db,\n () => {\n hooks.onCommit();\n return 0;\n },\n 0,\n );\n },\n serialize: () => capi.sqlite3_js_db_export(db),\n deserialize: (snapshot) => {\n const dataPointer = sqlite3.wasm.allocFromTypedArray(snapshot);\n const resultCode = capi.sqlite3_deserialize(\n db,\n \"main\",\n dataPointer,\n snapshot.byteLength,\n snapshot.byteLength,\n capi.SQLITE_DESERIALIZE_FREEONCLOSE | capi.SQLITE_DESERIALIZE_RESIZEABLE,\n );\n db.checkRc(resultCode);\n },\n createScalarFunction: ({ name, callback, arity, deterministic, directOnly, innocuous }) => {\n db.createFunction({\n name,\n xFunc: (_, ...args) => callback(...(args as SqliteDriverValue[])) as SqlValue,\n arity,\n deterministic,\n directOnly,\n innocuous,\n });\n },\n close: () => {\n db.close();\n },\n };\n}\n","import type { SyncDbSchema } from \"../sqlite-crdt/crdt-schema\";\nimport { createWasmMemoryDriver } from \"../wasm-driver\";\nimport { type SchemaVerificationIssue, verifySyncSchemaWithDriver } from \"./verify-sync-schema\";\n\n/** {@link verifySyncSchemaWithDriver} on a throwaway in-memory sqlite-wasm database. */\nexport async function verifySyncSchema(schema: SyncDbSchema): Promise<SchemaVerificationIssue[]> {\n return verifySyncSchemaWithDriver(schema, await createWasmMemoryDriver());\n}\n","import type { ImportDataOptions, ImportDataResult, SyncedDbExport } from \"../export-import\";\nimport type {\n DeSyncDetectedReason,\n EventsPullRequest,\n EventsPushRequest,\n EventsPushResponse,\n} from \"../sqlite-crdt/crdt-sync-remote-source\";\nimport type { CrdtEventType } from \"../sqlite-crdt/crdt-table-schema\";\nimport type { ExecuteParams, ExecuteResult } from \"../sqlite-db-wrapper\";\nimport { TypedBroadcastChannel } from \"../utils\";\n\nexport const syncDbWorkerLockName = \"sync-db-worker-lock\";\nexport const syncDbClientLockName = \"sync-db-client-lock\";\n\nexport type WorkerNotificationMessage =\n | {\n notificationType: \"new-event-chunk-applied\";\n newSyncId: number;\n /** Worker's state hash after applying up to `newSyncId`. */\n stateHash: string | null;\n }\n | {\n notificationType: \"state-changed\";\n state: WorkerState;\n /** Id of the worker's persisted database. A wiped database gets a new one. */\n storageGeneration: string;\n }\n | {\n notificationType: \"reload-requested\";\n reloadEpoch: string;\n clean: boolean;\n }\n | {\n notificationType: \"de-sync-detected\";\n reason: DeSyncDetectedReason;\n }\n | {\n notificationType: \"remote-schema-version-mismatch\";\n remoteSchemaVersion: number;\n localSchemaVersion: number;\n };\n\nexport type WorkerNotificationEvents = {\n [K in WorkerNotificationMessage[\"notificationType\"]]: Extract<WorkerNotificationMessage, { notificationType: K }>;\n};\n\nexport type WorkerState = {\n remoteState: \"online\" | \"offline\" | \"connecting\";\n deSynced: boolean;\n schemaVersionMismatched: boolean;\n};\n\nexport type GetSnapshotResponse = {\n file: Uint8Array<ArrayBufferLike>;\n syncId: number;\n schemaVersion: number;\n storageGeneration: string;\n};\n\nexport type EventsPullResponse = {\n events: {\n schema_version: number;\n type: CrdtEventType;\n timestamp: string;\n dataset: string;\n item_id: string;\n payload: string;\n }[];\n hasMore: boolean;\n nextSyncId: number;\n};\n\nexport interface WorkerRpc {\n getSnapshot: () => GetSnapshotResponse;\n importData: (data: SyncedDbExport, opts?: ImportDataOptions) => ImportDataResult | Promise<ImportDataResult>;\n pushTabEvents: (request: EventsPushRequest) => EventsPushResponse;\n execute: (query: ExecuteParams) => ExecuteResult<unknown>;\n pullEvents: (params: EventsPullRequest) => EventsPullResponse;\n postState: () => void;\n goOnline: () => Promise<void>;\n goOffline: () => void;\n dropConnection: () => void;\n sync: () => void;\n requestReload: (options: { clean: boolean }) => Promise<void>;\n}\n\nexport type WorkerRequestMethod = keyof WorkerRpc;\n\nexport type WorkerRequestMessage<TMethod extends WorkerRequestMethod = WorkerRequestMethod> = {\n type: \"request\";\n requestId: string;\n method: TMethod;\n args: Parameters<WorkerRpc[TMethod]>;\n};\n\nexport type WorkerResponseMessage<TMethod extends WorkerRequestMethod = WorkerRequestMethod> = {\n type: \"response\";\n requestId: string;\n data: ReturnType<WorkerRpc[TMethod]>;\n};\n\nexport type WorkerErrorResponseMessage = {\n type: \"error-response\";\n requestId: string;\n error: string;\n};\n\nexport type AsyncRpc<T> = {\n [K in keyof T]: T[K] extends (...args: infer U) => infer V ? (...args: U) => Promise<Awaited<V>> : never;\n};\n\nexport const broadcastChannelNames = {\n requests: \"sync-db-worker-requests\",\n responses: \"sync-db-worker-responses\",\n} as const;\n\nexport type WorkerBroadcastChannels = {\n requests: TypedBroadcastChannel<WorkerRequestMessage>;\n responses: TypedBroadcastChannel<WorkerResponseMessage | WorkerErrorResponseMessage | WorkerNotificationMessage>;\n};\n\nexport const createBroadcastChannels = (prefix: string): WorkerBroadcastChannels => {\n return {\n requests: new TypedBroadcastChannel(`${prefix}-${broadcastChannelNames.requests}`),\n responses: new TypedBroadcastChannel(`${prefix}-${broadcastChannelNames.responses}`),\n };\n};\n\nexport type WorkerConfig<Props = any> = {\n dbId: string;\n clientId: string;\n props: Props;\n};\n\nexport type WorkerInitMessage = {\n type: \"init\";\n config: WorkerConfig;\n};\n\nexport function isWorkerInitMessage(message: unknown): message is WorkerInitMessage {\n return typeof message === \"object\" && message !== null && \"type\" in message && message.type === \"init\";\n}\n\nexport function isWorkerRequestMessage(message: unknown): message is WorkerRequestMessage {\n return typeof message === \"object\" && message !== null && \"type\" in message && message.type === \"request\";\n}\n\nexport function isWorkerResponseMessage(message: unknown): message is WorkerResponseMessage {\n return (\n typeof message === \"object\" && message !== null && \"type\" in message && \"requestId\" in message && \"data\" in message\n );\n}\n\nexport function isWorkerErrorResponseMessage(message: unknown): message is WorkerErrorResponseMessage {\n return typeof message === \"object\" && message !== null && \"type\" in message && message.type === \"error-response\";\n}\n\nexport function isWorkerNotificationMessage(message: unknown): message is WorkerNotificationMessage {\n return typeof message === \"object\" && message !== null && \"notificationType\" in message && !!message.notificationType;\n}\n","import { acquireLock } from \"../utils\";\n\nconst replicaLockName = (dbId: string) => `sync-db-replica-lock-${dbId}`;\n\nexport function acquireReplicaLock({ dbId }: { dbId: string }) {\n return acquireLock(replicaLockName(dbId), { mode: \"shared\" });\n}\n\nexport function runWithNoActiveReplicas({ dbId, run }: { dbId: string; run: () => void | Promise<void> }) {\n return navigator.locks.request(replicaLockName(dbId), { mode: \"exclusive\", ifAvailable: true }, async (lock) => {\n if (!lock) {\n return false;\n }\n await run();\n return true;\n });\n}\n","import { createStore, del, get, set } from \"idb-keyval\";\nimport { generateId } from \"../utils\";\nimport type { WorkerNotificationMessage } from \"./worker-common\";\n\nexport type ResetRequest = {\n epoch: string;\n requestedAt: number;\n};\n\n/** Durable async key-value storage for reset state. Injectable for tests. */\nexport type ResetStore = {\n get: <T>(key: string) => Promise<T | undefined>;\n set: (key: string, value: unknown) => Promise<void>;\n delete: (key: string) => Promise<void>;\n};\n\n/** Default IndexedDB-backed reset store (idb-keyval over a dedicated database). */\nexport function createIdbResetStore(): ResetStore {\n const store = createStore(\"sqlite-sync\", \"kv\");\n return {\n get: (key) => get(key, store),\n set: (key, value) => set(key, value, store),\n delete: (key) => del(key, store),\n };\n}\n\n/**\n * A clean reset is a recovery action for a de-sync detected now. If the reload\n * never happens (broadcast lost, tab crashed mid-reload, browser killed the page),\n * the request must not fire on an arbitrary later cold start and silently wipe\n * local-only writes accumulated since.\n */\nexport const RESET_REQUEST_TTL_MS = 10 * 60 * 1000; // 10 minutes\n\nconst resetRequestKey = (dbId: string) => `sqlite-sync-reset-request-${dbId}`;\nconst resetAppliedKey = (dbId: string) => `sqlite-sync-reset-applied-${dbId}`;\n\ntype ResetStateStoreOptions = {\n store: ResetStore;\n dbId: string;\n now?: () => number;\n};\n\nexport type ResetStateStore = ReturnType<typeof createResetStateStore>;\n\n/**\n * Worker-owned durable reset state. The reset decision must be owned by the\n * elected worker, not by tabs during `createSyncedDb` — otherwise a later\n * worker election could repeat an already-applied wipe.\n */\nexport function createResetStateStore({ store, dbId, now = () => Date.now() }: ResetStateStoreOptions) {\n const requestKey = resetRequestKey(dbId);\n const appliedKey = resetAppliedKey(dbId);\n\n return {\n async writeResetRequest(epoch: string): Promise<ResetRequest> {\n const request: ResetRequest = { epoch, requestedAt: now() };\n await store.set(requestKey, request);\n return request;\n },\n /**\n * Read the pending reset request after winning the worker election.\n * Returns the request only when it has not been applied yet and is within\n * the TTL. Stale requests are deleted so they cannot fire on a later cold start.\n */\n async resolvePendingReset(): Promise<ResetRequest | undefined> {\n const request = await store.get<ResetRequest>(requestKey);\n if (!request) {\n return undefined;\n }\n\n if (now() - request.requestedAt > RESET_REQUEST_TTL_MS) {\n await store.delete(requestKey);\n return undefined;\n }\n\n const appliedEpoch = await store.get<string>(appliedKey);\n if (request.epoch === appliedEpoch) {\n return undefined;\n }\n\n return request;\n },\n /**\n * Record the epoch as applied. Must be called only after the worker has\n * successfully initialized with `clearOnInit: true`, so a failed init can\n * be retried by a later elected worker.\n */\n async markResetApplied(epoch: string): Promise<void> {\n await store.set(appliedKey, epoch);\n },\n };\n}\n\ntype ReloadRequestHandlerOptions = {\n resetState: ResetStateStore;\n broadcast: (message: WorkerNotificationMessage) => void;\n generateEpoch?: () => string;\n};\n\n/**\n * Worker-side `requestReload` RPC implementation. For `clean: true` the reset\n * request is durably stored before broadcasting and before the RPC resolves,\n * so the epoch survives no matter which path triggers the reload.\n */\nexport function createReloadRequestHandler({\n resetState,\n broadcast,\n generateEpoch = generateId,\n}: ReloadRequestHandlerOptions) {\n return async (options: { clean: boolean }): Promise<void> => {\n const reloadEpoch = generateEpoch();\n\n if (options.clean) {\n await resetState.writeResetRequest(reloadEpoch);\n }\n\n broadcast({\n notificationType: \"reload-requested\",\n reloadEpoch,\n clean: options.clean,\n });\n };\n}\n"],"mappings":";;;;;AAQA,IAAI,sBAAqD;AAEzD,SAAgB,iBAAiB;CAC/B,wBAAwB,kBAAkB;CAC1C,OAAO;AACT;AAEA,eAAsB,yBAAyB;CAC7C,MAAM,UAAU,MAAM,eAAe;CACrC,OAAO,uBAAuB,SAAS,IAAI,QAAQ,IAAI,GAAG,EAAE,UAAU,WAAW,CAAC,CAAC;AACrF;AAEA,SAAgB,uBAAuB,SAAwB,IAA4B;CACzF,MAAM,EAAE,SAAS;CAEjB,MAAM,WAAW,QAAuC;EACtD,MAAM,YAAY,GAAG,QAAQ,GAAG;EAChC,MAAM,UAAU,UAAU;EAC1B,IAAI,CAAC,SACH,MAAM,IAAI,MAAM,yCAAyC;EAG3D,OAAO;GACL,UAAU,KAAK,sBAAsB,OAAO,MAAM;GAClD,MAAM,eAAe;IACnB,IAAI;KACF,IAAI,WAAW,SAAS,GACtB,UAAU,KAAK,UAA6B;KAG9C,MAAM,OAA8B,CAAC;KACrC,IAAI;KACJ,OAAO,UAAU,KAAK,GAAG;MACvB,YAAY,UAAU,eAAe,CAAC,CAAC;MACvC,KAAK,KAAK,UAAU,IAAI,CAAC,CAAC,CAAwB;KACpD;KACA,OAAO;MAAE,SAAS,WAAW,CAAC;MAAG;KAAK;IACxC,UAAU;KACR,UAAU,MAAM,IAAI;IACtB;GACF;GACA,gBAAgB;IACd,UAAU,SAAS;GACrB;EACF;CACF;CAEA,OAAO;EACL;EACA,kBAAkB;EAClB,oBAAoB,KAAK,sBAAsB,EAAE;EACjD,WAAW,UAAU;GACnB,IAAI,CAAC,OAAO;IACV,KAAK,oBAAoB,IAAI,GAAY,CAAC;IAC1C,KAAK,sBAAsB,IAAI,GAAG,CAAC;IACnC,KAAK,oBAAoB,IAAI,GAAG,CAAC;IACjC;GACF;GAEA,KAAK,oBAAoB,KAAK,MAAM,OAAO,KAAK,UAAU,MAAM,SAAS,KAAK,GAAG,CAAC;GAClF,KAAK,sBACH,UACM;IACJ,MAAM,WAAW;IACjB,OAAO;GACT,GACA,CACF;GACA,KAAK,oBACH,UACM;IACJ,MAAM,SAAS;IACf,OAAO;GACT,GACA,CACF;EACF;EACA,iBAAiB,KAAK,qBAAqB,EAAE;EAC7C,cAAc,aAAa;GACzB,MAAM,cAAc,QAAQ,KAAK,oBAAoB,QAAQ;GAC7D,MAAM,aAAa,KAAK,oBACtB,IACA,QACA,aACA,SAAS,YACT,SAAS,YACT,KAAK,iCAAiC,KAAK,6BAC7C;GACA,GAAG,QAAQ,UAAU;EACvB;EACA,uBAAuB,EAAE,MAAM,UAAU,OAAO,eAAe,YAAY,gBAAgB;GACzF,GAAG,eAAe;IAChB;IACA,QAAQ,GAAG,GAAG,SAAS,SAAS,GAAI,IAA4B;IAChE;IACA;IACA;IACA;GACF,CAAC;EACH;EACA,aAAa;GACX,GAAG,MAAM;EACX;CACF;AACF;;;;AC3GA,eAAsB,iBAAiB,QAA0D;CAC/F,OAAO,2BAA2B,QAAQ,MAAM,uBAAuB,CAAC;AAC1E;;;ACIA,MAAa,uBAAuB;AACpC,MAAa,uBAAuB;AAmGpC,MAAa,wBAAwB;CACnC,UAAU;CACV,WAAW;AACb;AAOA,MAAa,2BAA2B,WAA4C;CAClF,OAAO;EACL,UAAU,IAAI,sBAAsB,GAAG,OAAO,GAAG,sBAAsB,UAAU;EACjF,WAAW,IAAI,sBAAsB,GAAG,OAAO,GAAG,sBAAsB,WAAW;CACrF;AACF;AAaA,SAAgB,oBAAoB,SAAgD;CAClF,OAAO,OAAO,YAAY,YAAY,YAAY,QAAQ,UAAU,WAAW,QAAQ,SAAS;AAClG;AAEA,SAAgB,uBAAuB,SAAmD;CACxF,OAAO,OAAO,YAAY,YAAY,YAAY,QAAQ,UAAU,WAAW,QAAQ,SAAS;AAClG;AAEA,SAAgB,wBAAwB,SAAoD;CAC1F,OACE,OAAO,YAAY,YAAY,YAAY,QAAQ,UAAU,WAAW,eAAe,WAAW,UAAU;AAEhH;AAEA,SAAgB,6BAA6B,SAAyD;CACpG,OAAO,OAAO,YAAY,YAAY,YAAY,QAAQ,UAAU,WAAW,QAAQ,SAAS;AAClG;AAEA,SAAgB,4BAA4B,SAAwD;CAClG,OAAO,OAAO,YAAY,YAAY,YAAY,QAAQ,sBAAsB,WAAW,CAAC,CAAC,QAAQ;AACvG;;;AC7JA,MAAM,mBAAmB,SAAiB,wBAAwB;AAElE,SAAgB,mBAAmB,EAAE,QAA0B;CAC7D,OAAO,YAAY,gBAAgB,IAAI,GAAG,EAAE,MAAM,SAAS,CAAC;AAC9D;AAEA,SAAgB,wBAAwB,EAAE,MAAM,OAA0D;CACxG,OAAO,UAAU,MAAM,QAAQ,gBAAgB,IAAI,GAAG;EAAE,MAAM;EAAa,aAAa;CAAK,GAAG,OAAO,SAAS;EAC9G,IAAI,CAAC,MACH,OAAO;EAET,MAAM,IAAI;EACV,OAAO;CACT,CAAC;AACH;;;;ACCA,SAAgB,sBAAkC;CAChD,MAAM,QAAQ,YAAY,eAAe,IAAI;CAC7C,OAAO;EACL,MAAM,QAAQ,IAAI,KAAK,KAAK;EAC5B,MAAM,KAAK,UAAU,IAAI,KAAK,OAAO,KAAK;EAC1C,SAAS,QAAQ,IAAI,KAAK,KAAK;CACjC;AACF;;;;;;;AAQA,MAAa,uBAAuB;AAEpC,MAAM,mBAAmB,SAAiB,6BAA6B;AACvE,MAAM,mBAAmB,SAAiB,6BAA6B;;;;;;AAevE,SAAgB,sBAAsB,EAAE,OAAO,MAAM,YAAY,KAAK,IAAI,KAA6B;CACrG,MAAM,aAAa,gBAAgB,IAAI;CACvC,MAAM,aAAa,gBAAgB,IAAI;CAEvC,OAAO;EACL,MAAM,kBAAkB,OAAsC;GAC5D,MAAM,UAAwB;IAAE;IAAO,aAAa,IAAI;GAAE;GAC1D,MAAM,MAAM,IAAI,YAAY,OAAO;GACnC,OAAO;EACT;;;;;;EAMA,MAAM,sBAAyD;GAC7D,MAAM,UAAU,MAAM,MAAM,IAAkB,UAAU;GACxD,IAAI,CAAC,SACH;GAGF,IAAI,IAAI,IAAI,QAAQ,cAAA,KAAoC;IACtD,MAAM,MAAM,OAAO,UAAU;IAC7B;GACF;GAEA,MAAM,eAAe,MAAM,MAAM,IAAY,UAAU;GACvD,IAAI,QAAQ,UAAU,cACpB;GAGF,OAAO;EACT;;;;;;EAMA,MAAM,iBAAiB,OAA8B;GACnD,MAAM,MAAM,IAAI,YAAY,KAAK;EACnC;CACF;AACF;;;;;;AAaA,SAAgB,2BAA2B,EACzC,YACA,WACA,gBAAgB,cACc;CAC9B,OAAO,OAAO,YAA+C;EAC3D,MAAM,cAAc,cAAc;EAElC,IAAI,QAAQ,OACV,MAAM,WAAW,kBAAkB,WAAW;EAGhD,UAAU;GACR,kBAAkB;GAClB;GACA,OAAO,QAAQ;EACjB,CAAC;CACH;AACF"}