@sqlite-sync/core 0.13.2 → 0.13.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,2 +1,2 @@
1
- import { Bt as KyselyStatementFactory, Gt as compileKyselyStatement, Nt as STATE_HASH_KV_KEY, Rt as InternalSQLiteWrapper, _ as crdtViewColumnsFromTable, _t as baseSystemMigrations, g as CRDT_CHANGE_INTENTS_TABLE, ot as CrdtChangeIntent, v as createCrdtViewStatements, vt as createSystemDbConfig, y as drainCrdtChangeIntents, yt as runSystemMigrations } from "./index-B3s_6aG6.js";
1
+ import { Bt as KyselyStatementFactory, Gt as compileKyselyStatement, Nt as STATE_HASH_KV_KEY, Rt as InternalSQLiteWrapper, _ as crdtViewColumnsFromTable, _t as baseSystemMigrations, g as CRDT_CHANGE_INTENTS_TABLE, ot as CrdtChangeIntent, v as createCrdtViewStatements, vt as createSystemDbConfig, y as drainCrdtChangeIntents, yt as runSystemMigrations } from "./index-ChmCuLQU.js";
2
2
  export { CRDT_CHANGE_INTENTS_TABLE, type CrdtChangeIntent, type InternalSQLiteWrapper, type KyselyStatementFactory, STATE_HASH_KV_KEY, baseSystemMigrations, compileKyselyStatement, crdtViewColumnsFromTable, createCrdtViewStatements, createSystemDbConfig, drainCrdtChangeIntents, runSystemMigrations };
package/dist/internal.js CHANGED
@@ -1,3 +1,3 @@
1
1
  import { _ as compileKyselyStatement, a as baseSystemMigrations, d as STATE_HASH_KV_KEY, l as runSystemMigrations, o as createSystemDbConfig } from "./system-schema-uHQdQTCk.js";
2
- import { i as drainCrdtChangeIntents, n as crdtViewColumnsFromTable, r as createCrdtViewStatements, t as CRDT_CHANGE_INTENTS_TABLE } from "./make-crdt-table-DYCXOYB7.js";
2
+ import { i as drainCrdtChangeIntents, n as crdtViewColumnsFromTable, r as createCrdtViewStatements, t as CRDT_CHANGE_INTENTS_TABLE } from "./make-crdt-table-Cu8WhRBk.js";
3
3
  export { CRDT_CHANGE_INTENTS_TABLE, STATE_HASH_KV_KEY, baseSystemMigrations, compileKyselyStatement, crdtViewColumnsFromTable, createCrdtViewStatements, createSystemDbConfig, drainCrdtChangeIntents, runSystemMigrations };
package/dist/local.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { C as EventsPullRequest, Jt as SqliteDriverRows, Kt as SqliteDriver, Qt as Logger, S as CreateRemoteSourceFactory, T as EventsPushResponse, Xt as SqliteDriverValue, Yt as SqliteDriverStatement, dn as SyncDbSchema, l as SyncedDb, qt as SqliteDriverHooks, w as EventsPushRequest } from "./index-B3s_6aG6.js";
2
- import { t as createWsRemoteSource } from "./ws-remote-source-Dgd9dmMl.js";
1
+ import { C as EventsPullRequest, Jt as SqliteDriverRows, Kt as SqliteDriver, Qt as Logger, S as CreateRemoteSourceFactory, T as EventsPushResponse, Xt as SqliteDriverValue, Yt as SqliteDriverStatement, dn as SyncDbSchema, l as SyncedDb, qt as SqliteDriverHooks, w as EventsPushRequest } from "./index-ChmCuLQU.js";
2
+ import { t as createWsRemoteSource } from "./ws-remote-source-BE0Yrurd.js";
3
3
  //#region src/local/local-synced-db.d.ts
4
4
  type LocalSyncedDbOptions<Database> = {
5
5
  dbId: string;
package/dist/local.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import { f as createKvStateHashStore, m as createSQLiteKvStore, n as applyLocalDbSchema, p as createKvStoreTableQuery, s as localDbConfig, t as LOCAL_KV_TABLE_NAME } from "./system-schema-uHQdQTCk.js";
2
2
  import { l as generateId, s as createTypedEventTarget } from "./crdt-table-schema-D6Yf61Ex.js";
3
- import { a as formatSchemaVerificationIssues, c as createMigrator, l as createExportData, n as createCrdtStorage, o as verifySyncSchemaWithDriver, t as createCrdtSyncProducer, u as createImportData } from "./crdt-sync-producer-DVEWad4t.js";
3
+ import { a as formatSchemaVerificationIssues, c as createMigrator, l as createExportData, n as createCrdtStorage, o as verifySyncSchemaWithDriver, t as createCrdtSyncProducer, u as createImportData } from "./crdt-sync-producer-t2jHxO38.js";
4
4
  import { o as HLCCounter, t as createCrdtSyncRemoteSource, u as xxhash } from "./crdt-sync-remote-source-yeTEkOjH.js";
5
- import { t as SQLiteReactiveDb } from "./sqlite-reactive-db-BwY3FoIy.js";
6
- import { a as makeCrdtTable, o as registerCrdtIntentDrainer } from "./make-crdt-table-DYCXOYB7.js";
5
+ import { t as SQLiteReactiveDb } from "./sqlite-reactive-db-C2BtEAeE.js";
6
+ import { a as makeCrdtTable, o as registerCrdtIntentDrainer } from "./make-crdt-table-Cu8WhRBk.js";
7
7
  import { n as getOrCreateSQLiteSyncDevtoolsRegistry, r as validateDbId, t as createSyncedDbDatabase } from "./synced-db-database-B5M7MrBD.js";
8
8
  import { i as runWorkerEventLogGc, r as formatStorageVersion, t as createWsRemoteSource } from "./ws-remote-source-BbPWrHB6.js";
9
9
  //#region src/local/local-synced-db.ts
@@ -73,7 +73,7 @@ async function createLocalSyncedDb(options) {
73
73
  const nodeId = generateId();
74
74
  const crdtStorage = createCrdtStorage({
75
75
  nodeId,
76
- initialLocalSyncId: getMaxSyncId(db),
76
+ initialLocalSyncId: Math.max(getMaxSyncId(db), pushSyncId.current),
77
77
  migrator,
78
78
  hlc: new HLCCounter(nodeId, () => Date.now()),
79
79
  db,
package/dist/local.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"local.js","names":[],"sources":["../src/local/local-synced-db.ts"],"sourcesContent":["import { validateDbId } from \"../db-id\";\nimport { getOrCreateSQLiteSyncDevtoolsRegistry } from \"../devtools-registry\";\nimport { createExportData, createImportData } from \"../export-import\";\nimport { xxhash } from \"../hash\";\nimport { HLCCounter } from \"../hlc\";\nimport type { Logger } from \"../logger\";\nimport { SQLiteReactiveDb } from \"../memory-db/sqlite-reactive-db\";\nimport { createMigrator } from \"../migrations/migrator\";\nimport {\n applyLocalDbSchema,\n LOCAL_KV_TABLE_NAME,\n type LocalDbSchema,\n localDbConfig,\n} from \"../migrations/system-schema\";\nimport { formatSchemaVerificationIssues, verifySyncSchemaWithDriver } from \"../schema/verify-sync-schema\";\nimport type { SyncDbSchema } from \"../sqlite-crdt/crdt-schema\";\nimport { 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 { makeCrdtTable, registerCrdtIntentDrainer } from \"../sqlite-crdt/make-crdt-table\";\nimport type { SqliteDriver } from \"../sqlite-driver\";\nimport { createKvStateHashStore, createKvStoreTableQuery, createSQLiteKvStore } from \"../sqlite-kv-store\";\nimport type { SyncedDb } from \"../sync-db\";\nimport { createSyncedDbDatabase } from \"../synced-db-database\";\nimport { createTypedEventTarget, generateId } from \"../utils\";\nimport { runWorkerEventLogGc } from \"../worker-db/event-log-gc\";\nimport { formatStorageVersion } from \"../worker-db/storage-version\";\nimport type { WorkerNotificationEvents, WorkerNotificationMessage } from \"../worker-db/worker-common\";\n\nexport type LocalSyncedDbOptions<Database> = {\n dbId: string;\n syncDbSchema: SyncDbSchema<Database>;\n /** Opens the persisted database. Called again after a reset deleted it. */\n openDriver: () => SqliteDriver | Promise<SqliteDriver>;\n /** Deletes the persisted database. Only called while no connection to it is open. */\n deleteDatabase: () => void | Promise<void>;\n createRemoteSource?: CreateRemoteSourceFactory;\n logger?: Logger;\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 local DB is deleted on startup.\n */\n storageVersion?: string;\n /**\n * Dev-time drift check: replays the full migration history on a throwaway\n * database opened by this callback and verifies the result matches the schema\n * declared with `defineSyncSchema`. On mismatch startup throws.\n */\n verifySchemaDriver?: () => SqliteDriver | Promise<SqliteDriver>;\n /**\n * Runs event-log garbage collection on startup. Disabled by default. GC keeps the\n * latest event rows and never removes pending events or local rows that have not\n * been pushed to the remote.\n */\n eventLogGc?: boolean;\n /** Runs once when the instance is disposed, including by `requestReload`. */\n onDispose?: () => void;\n};\n\nexport type LocalSyncedDb<Database> = SyncedDb<Database> & {\n state: SyncedDb<Database>[\"state\"] & {\n /** Drops the remote connection without retrying; call `sync` once the network is back. */\n dropConnection: () => void;\n };\n};\n\nconst STORAGE_VERSION_KEY = \"storage-version\";\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\n/**\n * A synced DB backed by a single persisted SQLite connection, for runtimes with one\n * JS context per database (React Native, Electron, Node). Reads and writes go to the\n * persisted database directly; there is no worker, tab coordination or in-memory copy.\n */\nexport async function createLocalSyncedDb<Database>(\n options: LocalSyncedDbOptions<Database>,\n): Promise<LocalSyncedDb<Database>> {\n validateDbId(options.dbId);\n const logger = options.logger ?? defaultLogger;\n\n if (options.verifySchemaDriver) {\n const issues = verifySyncSchemaWithDriver(options.syncDbSchema as SyncDbSchema, await options.verifySchemaDriver());\n if (issues.length > 0) {\n throw new Error(formatSchemaVerificationIssues(issues));\n }\n logger(\"local\", \"Schema verification passed: migrations match the declared schema\", \"info\");\n }\n\n await xxhash.ensureLoaded();\n\n const reactiveDb = await openLocalReactiveDb(options, logger);\n const db = reactiveDb.db as unknown as SQLiteReactiveDb<LocalDbSchema>[\"db\"];\n\n try {\n const { kvStore } = applyLocalDbSchema(db);\n\n const migrator = createMigrator({\n migrations: options.syncDbSchema.migrations,\n schemaVersion: kvStore.createNumberStoredValue(\"schema-version\", -1),\n updateLogTableName: localDbConfig.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 if (options.eventLogGc === true) {\n runWorkerEventLogGc({ db, dbConfig: localDbConfig, pushSyncId, logger });\n }\n\n for (const table of options.syncDbSchema.tablesConfig) {\n makeCrdtTable({ db, baseTableName: table.baseTableName, crdtTableName: table.crdtTableName, temp: true });\n }\n\n const nodeId = generateId();\n const crdtStorage = createCrdtStorage({\n nodeId,\n initialLocalSyncId: getMaxSyncId(db),\n migrator,\n hlc: new HLCCounter(nodeId, () => Date.now()),\n db,\n dbConfig: localDbConfig,\n stateHash,\n schema: options.syncDbSchema,\n });\n registerCrdtIntentDrainer({ reactiveDb, storage: crdtStorage });\n\n kvStore.set(STORAGE_VERSION_KEY, formatStorageVersion(options.storageVersion));\n\n const eventTarget = createTypedEventTarget<WorkerNotificationEvents>();\n const notify = (notification: WorkerNotificationMessage) => {\n eventTarget.dispatchEvent(notification.notificationType, notification as never);\n };\n\n createCrdtSyncProducer({\n storage: crdtStorage,\n broadcastEvents: (chunk) => {\n notify({\n notificationType: \"new-event-chunk-applied\",\n newSyncId: chunk.newSyncId,\n stateHash: chunk.stateHash,\n });\n },\n });\n\n const remoteSource = createCrdtSyncRemoteSource({\n bufferSize: PUSH_BATCH_SIZE,\n pullSyncId,\n pushSyncId,\n nodeId,\n storage: crdtStorage,\n migrator,\n remoteFactory: options.createRemoteSource,\n });\n\n let stateSnapshot = remoteSource.getState();\n remoteSource.addEventListener(\"state-changed\", () => {\n stateSnapshot = remoteSource.getState();\n notify({\n notificationType: \"state-changed\",\n state: stateSnapshot,\n storageGeneration: storageGeneration.current,\n });\n });\n remoteSource.addEventListener(\"de-sync-detected\", (event) => {\n notify({ notificationType: \"de-sync-detected\", reason: event.payload.reason });\n });\n remoteSource.addEventListener(\"remote-schema-version-mismatch\", (event) => {\n notify({\n notificationType: \"remote-schema-version-mismatch\",\n remoteSchemaVersion: event.payload.remoteSchemaVersion,\n localSchemaVersion: event.payload.localSchemaVersion,\n });\n });\n remoteSource.goOnline();\n\n let unregisterDevtools: (() => void) | undefined;\n let disposePromise: Promise<void> | null = null;\n const dispose = () => {\n disposePromise ??= (async () => {\n options.onDispose?.();\n unregisterDevtools?.();\n try {\n await remoteSource.dispose();\n await crdtStorage.internal.processEnqueuedEvents();\n } finally {\n reactiveDb.dispose();\n }\n })();\n return disposePromise;\n };\n\n const importData = createImportData({\n migrator,\n tablesConfig: options.syncDbSchema.tablesConfig,\n applyEvents: async (events) => {\n crdtStorage.applyOwnEvents(events);\n await crdtStorage.internal.processEnqueuedEvents();\n },\n });\n\n const sync = () => {\n remoteSource.syncWithRemote().catch((error) => console.warn(\"Failed to start sync\", error));\n };\n\n const syncedDb: LocalSyncedDb<Database> = {\n db: createSyncedDbDatabase(reactiveDb),\n state: {\n getState: () => stateSnapshot,\n subscribe: (onChange: () => void) => {\n const { unsubscribe } = eventTarget.addEventListener(\"state-changed\", onChange);\n return unsubscribe;\n },\n goOnline: () => remoteSource.goOnline(),\n goOffline: () => remoteSource.goOffline(\"DISCONNECTED\"),\n dropConnection: () => remoteSource.dropConnection(\"NETWORK_OFFLINE\"),\n sync,\n },\n /**\n * Disposes this instance and notifies `reload-requested` subscribers, which are\n * expected to create a new instance. With `clean: true` the persisted database is\n * deleted first, so the next instance starts empty and re-syncs from the remote.\n * Pending local events that were not pushed yet are lost with a clean reload.\n */\n requestReload: async ({ clean }: { clean: boolean }) => {\n await dispose();\n if (clean) {\n await options.deleteDatabase();\n }\n notify({ notificationType: \"reload-requested\", reloadEpoch: generateId(), clean });\n },\n subscribe: eventTarget.addEventListener,\n exportData: createExportData({\n reactiveDb,\n tablesConfig: options.syncDbSchema.tablesConfig,\n schemaVersion: migrator.currentSchemaVersion,\n }),\n importData: async (data, opts) => importData(data, opts),\n dispose,\n _internal: {\n executeAsync: async (query) => reactiveDb.db.execute(query),\n getMemoryQueryTables: reactiveDb.getTablesUsed.bind(reactiveDb),\n getSharedLiveQueriesSnapshot: reactiveDb.getSharedLiveQueriesSnapshot.bind(reactiveDb),\n crdtTableNames: options.syncDbSchema.tablesConfig.map((table) => table.crdtTableName),\n crdtTablesConfig: options.syncDbSchema.tablesConfig,\n tables: options.syncDbSchema.tables,\n schemaVersion: migrator.currentSchemaVersion,\n migrationVersions: Object.keys(options.syncDbSchema.migrations)\n .map(Number)\n .sort((a, b) => a - b),\n },\n };\n\n unregisterDevtools = getOrCreateSQLiteSyncDevtoolsRegistry().register({\n instanceId: generateId(),\n dbId: options.dbId,\n createdAt: Date.now(),\n instance: syncedDb,\n });\n\n return syncedDb;\n } catch (error) {\n reactiveDb.dispose();\n throw error;\n }\n}\n\nasync function openLocalReactiveDb<Database>(options: LocalSyncedDbOptions<Database>, logger: Logger) {\n const open = async () => {\n const driver = await options.openDriver();\n const reactiveDb = SQLiteReactiveDb.create<Database>({ driver, logger, loggerPrefix: \"local\" });\n try {\n reactiveDb.db.execute(\"PRAGMA journal_mode=WAL\", { loggerLevel: \"system\" });\n reactiveDb.db.execute(\"PRAGMA synchronous=NORMAL\", { loggerLevel: \"system\" });\n reactiveDb.db.executeKysely((kysely) => createKvStoreTableQuery(kysely.schema, LOCAL_KV_TABLE_NAME), {\n loggerLevel: \"system\",\n });\n } catch (error) {\n reactiveDb.dispose();\n throw error;\n }\n return reactiveDb;\n };\n\n const reactiveDb = await open();\n const currentVersion = formatStorageVersion(options.storageVersion);\n const storedVersion = createSQLiteKvStore({ db: reactiveDb.db, metaTableName: LOCAL_KV_TABLE_NAME }).get(\n STORAGE_VERSION_KEY,\n );\n if (storedVersion === null || storedVersion === currentVersion) {\n return reactiveDb;\n }\n\n logger(\"local\", `Storage version mismatch — resetting local DB to ${currentVersion}`, \"warning\");\n reactiveDb.dispose();\n await options.deleteDatabase();\n return open();\n}\n\nfunction getMaxSyncId(db: SQLiteReactiveDb<LocalDbSchema>[\"db\"]) {\n const [result] = db.executePrepared(\n \"get-max-sync-id\",\n {},\n (db) => db.selectFrom(\"crdt_events\").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":"local.js","names":[],"sources":["../src/local/local-synced-db.ts"],"sourcesContent":["import { validateDbId } from \"../db-id\";\nimport { getOrCreateSQLiteSyncDevtoolsRegistry } from \"../devtools-registry\";\nimport { createExportData, createImportData } from \"../export-import\";\nimport { xxhash } from \"../hash\";\nimport { HLCCounter } from \"../hlc\";\nimport type { Logger } from \"../logger\";\nimport { SQLiteReactiveDb } from \"../memory-db/sqlite-reactive-db\";\nimport { createMigrator } from \"../migrations/migrator\";\nimport {\n applyLocalDbSchema,\n LOCAL_KV_TABLE_NAME,\n type LocalDbSchema,\n localDbConfig,\n} from \"../migrations/system-schema\";\nimport { formatSchemaVerificationIssues, verifySyncSchemaWithDriver } from \"../schema/verify-sync-schema\";\nimport type { SyncDbSchema } from \"../sqlite-crdt/crdt-schema\";\nimport { 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 { makeCrdtTable, registerCrdtIntentDrainer } from \"../sqlite-crdt/make-crdt-table\";\nimport type { SqliteDriver } from \"../sqlite-driver\";\nimport { createKvStateHashStore, createKvStoreTableQuery, createSQLiteKvStore } from \"../sqlite-kv-store\";\nimport type { SyncedDb } from \"../sync-db\";\nimport { createSyncedDbDatabase } from \"../synced-db-database\";\nimport { createTypedEventTarget, generateId } from \"../utils\";\nimport { runWorkerEventLogGc } from \"../worker-db/event-log-gc\";\nimport { formatStorageVersion } from \"../worker-db/storage-version\";\nimport type { WorkerNotificationEvents, WorkerNotificationMessage } from \"../worker-db/worker-common\";\n\nexport type LocalSyncedDbOptions<Database> = {\n dbId: string;\n syncDbSchema: SyncDbSchema<Database>;\n /** Opens the persisted database. Called again after a reset deleted it. */\n openDriver: () => SqliteDriver | Promise<SqliteDriver>;\n /** Deletes the persisted database. Only called while no connection to it is open. */\n deleteDatabase: () => void | Promise<void>;\n createRemoteSource?: CreateRemoteSourceFactory;\n logger?: Logger;\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 local DB is deleted on startup.\n */\n storageVersion?: string;\n /**\n * Dev-time drift check: replays the full migration history on a throwaway\n * database opened by this callback and verifies the result matches the schema\n * declared with `defineSyncSchema`. On mismatch startup throws.\n */\n verifySchemaDriver?: () => SqliteDriver | Promise<SqliteDriver>;\n /**\n * Runs event-log garbage collection on startup. Disabled by default. GC keeps the\n * latest event rows and never removes pending events or local rows that have not\n * been pushed to the remote.\n */\n eventLogGc?: boolean;\n /** Runs once when the instance is disposed, including by `requestReload`. */\n onDispose?: () => void;\n};\n\nexport type LocalSyncedDb<Database> = SyncedDb<Database> & {\n state: SyncedDb<Database>[\"state\"] & {\n /** Drops the remote connection without retrying; call `sync` once the network is back. */\n dropConnection: () => void;\n };\n};\n\nconst STORAGE_VERSION_KEY = \"storage-version\";\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\n/**\n * A synced DB backed by a single persisted SQLite connection, for runtimes with one\n * JS context per database (React Native, Electron, Node). Reads and writes go to the\n * persisted database directly; there is no worker, tab coordination or in-memory copy.\n */\nexport async function createLocalSyncedDb<Database>(\n options: LocalSyncedDbOptions<Database>,\n): Promise<LocalSyncedDb<Database>> {\n validateDbId(options.dbId);\n const logger = options.logger ?? defaultLogger;\n\n if (options.verifySchemaDriver) {\n const issues = verifySyncSchemaWithDriver(options.syncDbSchema as SyncDbSchema, await options.verifySchemaDriver());\n if (issues.length > 0) {\n throw new Error(formatSchemaVerificationIssues(issues));\n }\n logger(\"local\", \"Schema verification passed: migrations match the declared schema\", \"info\");\n }\n\n await xxhash.ensureLoaded();\n\n const reactiveDb = await openLocalReactiveDb(options, logger);\n const db = reactiveDb.db as unknown as SQLiteReactiveDb<LocalDbSchema>[\"db\"];\n\n try {\n const { kvStore } = applyLocalDbSchema(db);\n\n const migrator = createMigrator({\n migrations: options.syncDbSchema.migrations,\n schemaVersion: kvStore.createNumberStoredValue(\"schema-version\", -1),\n updateLogTableName: localDbConfig.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 if (options.eventLogGc === true) {\n runWorkerEventLogGc({ db, dbConfig: localDbConfig, pushSyncId, logger });\n }\n\n for (const table of options.syncDbSchema.tablesConfig) {\n makeCrdtTable({ db, baseTableName: table.baseTableName, crdtTableName: table.crdtTableName, temp: true });\n }\n\n const nodeId = generateId();\n const crdtStorage = createCrdtStorage({\n nodeId,\n initialLocalSyncId: Math.max(getMaxSyncId(db), pushSyncId.current),\n migrator,\n hlc: new HLCCounter(nodeId, () => Date.now()),\n db,\n dbConfig: localDbConfig,\n stateHash,\n schema: options.syncDbSchema,\n });\n registerCrdtIntentDrainer({ reactiveDb, storage: crdtStorage });\n\n kvStore.set(STORAGE_VERSION_KEY, formatStorageVersion(options.storageVersion));\n\n const eventTarget = createTypedEventTarget<WorkerNotificationEvents>();\n const notify = (notification: WorkerNotificationMessage) => {\n eventTarget.dispatchEvent(notification.notificationType, notification as never);\n };\n\n createCrdtSyncProducer({\n storage: crdtStorage,\n broadcastEvents: (chunk) => {\n notify({\n notificationType: \"new-event-chunk-applied\",\n newSyncId: chunk.newSyncId,\n stateHash: chunk.stateHash,\n });\n },\n });\n\n const remoteSource = createCrdtSyncRemoteSource({\n bufferSize: PUSH_BATCH_SIZE,\n pullSyncId,\n pushSyncId,\n nodeId,\n storage: crdtStorage,\n migrator,\n remoteFactory: options.createRemoteSource,\n });\n\n let stateSnapshot = remoteSource.getState();\n remoteSource.addEventListener(\"state-changed\", () => {\n stateSnapshot = remoteSource.getState();\n notify({\n notificationType: \"state-changed\",\n state: stateSnapshot,\n storageGeneration: storageGeneration.current,\n });\n });\n remoteSource.addEventListener(\"de-sync-detected\", (event) => {\n notify({ notificationType: \"de-sync-detected\", reason: event.payload.reason });\n });\n remoteSource.addEventListener(\"remote-schema-version-mismatch\", (event) => {\n notify({\n notificationType: \"remote-schema-version-mismatch\",\n remoteSchemaVersion: event.payload.remoteSchemaVersion,\n localSchemaVersion: event.payload.localSchemaVersion,\n });\n });\n remoteSource.goOnline();\n\n let unregisterDevtools: (() => void) | undefined;\n let disposePromise: Promise<void> | null = null;\n const dispose = () => {\n disposePromise ??= (async () => {\n options.onDispose?.();\n unregisterDevtools?.();\n try {\n await remoteSource.dispose();\n await crdtStorage.internal.processEnqueuedEvents();\n } finally {\n reactiveDb.dispose();\n }\n })();\n return disposePromise;\n };\n\n const importData = createImportData({\n migrator,\n tablesConfig: options.syncDbSchema.tablesConfig,\n applyEvents: async (events) => {\n crdtStorage.applyOwnEvents(events);\n await crdtStorage.internal.processEnqueuedEvents();\n },\n });\n\n const sync = () => {\n remoteSource.syncWithRemote().catch((error) => console.warn(\"Failed to start sync\", error));\n };\n\n const syncedDb: LocalSyncedDb<Database> = {\n db: createSyncedDbDatabase(reactiveDb),\n state: {\n getState: () => stateSnapshot,\n subscribe: (onChange: () => void) => {\n const { unsubscribe } = eventTarget.addEventListener(\"state-changed\", onChange);\n return unsubscribe;\n },\n goOnline: () => remoteSource.goOnline(),\n goOffline: () => remoteSource.goOffline(\"DISCONNECTED\"),\n dropConnection: () => remoteSource.dropConnection(\"NETWORK_OFFLINE\"),\n sync,\n },\n /**\n * Disposes this instance and notifies `reload-requested` subscribers, which are\n * expected to create a new instance. With `clean: true` the persisted database is\n * deleted first, so the next instance starts empty and re-syncs from the remote.\n * Pending local events that were not pushed yet are lost with a clean reload.\n */\n requestReload: async ({ clean }: { clean: boolean }) => {\n await dispose();\n if (clean) {\n await options.deleteDatabase();\n }\n notify({ notificationType: \"reload-requested\", reloadEpoch: generateId(), clean });\n },\n subscribe: eventTarget.addEventListener,\n exportData: createExportData({\n reactiveDb,\n tablesConfig: options.syncDbSchema.tablesConfig,\n schemaVersion: migrator.currentSchemaVersion,\n }),\n importData: async (data, opts) => importData(data, opts),\n dispose,\n _internal: {\n executeAsync: async (query) => reactiveDb.db.execute(query),\n getMemoryQueryTables: reactiveDb.getTablesUsed.bind(reactiveDb),\n getSharedLiveQueriesSnapshot: reactiveDb.getSharedLiveQueriesSnapshot.bind(reactiveDb),\n crdtTableNames: options.syncDbSchema.tablesConfig.map((table) => table.crdtTableName),\n crdtTablesConfig: options.syncDbSchema.tablesConfig,\n tables: options.syncDbSchema.tables,\n schemaVersion: migrator.currentSchemaVersion,\n migrationVersions: Object.keys(options.syncDbSchema.migrations)\n .map(Number)\n .sort((a, b) => a - b),\n },\n };\n\n unregisterDevtools = getOrCreateSQLiteSyncDevtoolsRegistry().register({\n instanceId: generateId(),\n dbId: options.dbId,\n createdAt: Date.now(),\n instance: syncedDb,\n });\n\n return syncedDb;\n } catch (error) {\n reactiveDb.dispose();\n throw error;\n }\n}\n\nasync function openLocalReactiveDb<Database>(options: LocalSyncedDbOptions<Database>, logger: Logger) {\n const open = async () => {\n const driver = await options.openDriver();\n const reactiveDb = SQLiteReactiveDb.create<Database>({ driver, logger, loggerPrefix: \"local\" });\n try {\n reactiveDb.db.execute(\"PRAGMA journal_mode=WAL\", { loggerLevel: \"system\" });\n reactiveDb.db.execute(\"PRAGMA synchronous=NORMAL\", { loggerLevel: \"system\" });\n reactiveDb.db.executeKysely((kysely) => createKvStoreTableQuery(kysely.schema, LOCAL_KV_TABLE_NAME), {\n loggerLevel: \"system\",\n });\n } catch (error) {\n reactiveDb.dispose();\n throw error;\n }\n return reactiveDb;\n };\n\n const reactiveDb = await open();\n const currentVersion = formatStorageVersion(options.storageVersion);\n const storedVersion = createSQLiteKvStore({ db: reactiveDb.db, metaTableName: LOCAL_KV_TABLE_NAME }).get(\n STORAGE_VERSION_KEY,\n );\n if (storedVersion === null || storedVersion === currentVersion) {\n return reactiveDb;\n }\n\n logger(\"local\", `Storage version mismatch — resetting local DB to ${currentVersion}`, \"warning\");\n reactiveDb.dispose();\n await options.deleteDatabase();\n return open();\n}\n\nfunction getMaxSyncId(db: SQLiteReactiveDb<LocalDbSchema>[\"db\"]) {\n const [result] = db.executePrepared(\n \"get-max-sync-id\",\n {},\n (db) => db.selectFrom(\"crdt_events\").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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"}
@@ -121,12 +121,12 @@ function registerCrdtIntentDrainer({ reactiveDb, storage }) {
121
121
  processedIntentMutationVersion = reactiveDb.getTableMutationVersion(CRDT_CHANGE_INTENTS_TABLE);
122
122
  }
123
123
  });
124
- reactiveDb.addEventListener("transaction-committed", () => {
124
+ reactiveDb.afterCommit = () => {
125
125
  if (eventApplied) {
126
126
  eventApplied = false;
127
127
  storage.internal.processEnqueuedEvents();
128
128
  }
129
- });
129
+ };
130
130
  }
131
131
  function drainCrdtChangeIntents({ tx, storage }) {
132
132
  const intents = tx.executePreparedRaw({
@@ -141,4 +141,4 @@ function drainCrdtChangeIntents({ tx, storage }) {
141
141
  //#endregion
142
142
  export { makeCrdtTable as a, drainCrdtChangeIntents as i, crdtViewColumnsFromTable as n, registerCrdtIntentDrainer as o, createCrdtViewStatements as r, CRDT_CHANGE_INTENTS_TABLE as t };
143
143
 
144
- //# sourceMappingURL=make-crdt-table-DYCXOYB7.js.map
144
+ //# sourceMappingURL=make-crdt-table-Cu8WhRBk.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"make-crdt-table-DYCXOYB7.js","names":[],"sources":["../src/sqlite-crdt/make-crdt-table.ts"],"sourcesContent":["import type { SQLiteReactiveDb } from \"../memory-db/sqlite-reactive-db\";\nimport type { AnyTableBuilder } from \"../schema/table-builder\";\nimport type { InternalSQLiteTransactionWrapper, SQLiteDbWrapper } from \"../sqlite-db-wrapper\";\nimport { quoteId } from \"../utils\";\nimport type { CrdtChangeIntent, InternalCrdtStorage } from \"./crdt-storage\";\n\nexport const CRDT_CHANGE_INTENTS_TABLE = \"crdt_change_intents\";\n\nexport function makeCrdtTable({\n db,\n baseTableName,\n crdtTableName,\n temp,\n}: {\n db: SQLiteDbWrapper<any>;\n baseTableName: string;\n crdtTableName: string;\n temp?: boolean;\n}) {\n const tableSchema = db.dbSchema[baseTableName];\n\n if (!tableSchema) {\n throw new Error(`Table ${baseTableName} not found`);\n }\n\n const columns = new Map(tableSchema.columns.map((column) => [column.name, column]));\n\n const idColumn = columns.get(\"id\");\n if (!idColumn) {\n throw new Error(\n `Table \"${baseTableName}\" is missing a required \"id\" column. CRDT tables must have an \"id\" column to identify items.`,\n );\n }\n if (idColumn.dataType.toUpperCase() !== \"TEXT\") {\n throw new Error(\n `Table \"${baseTableName}\": \"id\" column must be of type TEXT, got \"${idColumn.dataType}\". CRDT item IDs are stored as strings.`,\n );\n }\n\n const tombstoneColumn = columns.get(\"tombstone\");\n if (!tombstoneColumn) {\n throw new Error(\n `Table \"${baseTableName}\" is missing a required \"tombstone\" column. CRDT tables must have a \"tombstone\" INTEGER column for soft deletes.`,\n );\n }\n const tombstoneType = tombstoneColumn.dataType.toUpperCase();\n if (tombstoneType !== \"INTEGER\" && tombstoneType !== \"BOOLEAN\") {\n throw new Error(\n `Table \"${baseTableName}\": \"tombstone\" column must be of type INTEGER or BOOLEAN, got \"${tombstoneColumn.dataType}\". It is compared as 0/1 for soft deletes.`,\n );\n }\n\n for (const sql of createCrdtViewStatements({\n baseTableName,\n crdtTableName,\n columns: tableSchema.columns.map((column) => ({ name: column.name, nullable: column.isNullable })),\n temp,\n })) {\n db.execute(sql, { loggerLevel: \"system\" });\n }\n}\n\n// Workerd caps expr depth at 100; a left-deep `||` chain fails around 34 columns.\nfunction concatSql(parts: readonly string[]): string {\n if (parts.length === 0) {\n return \"''\";\n }\n if (parts.length === 1) {\n return parts[0];\n }\n const mid = parts.length >> 1;\n return `(${concatSql(parts.slice(0, mid))})||(${concatSql(parts.slice(mid))})`;\n}\n\nfunction sqlStringLiteral(value: string) {\n return `'${value.replaceAll(\"'\", \"''\")}'`;\n}\n\nexport type CrdtViewColumn = {\n name: string;\n nullable: boolean;\n};\n\nexport function crdtViewColumnsFromTable(table: AnyTableBuilder): CrdtViewColumn[] {\n return Object.entries(table.columns).map(([name, meta]) => ({ name, nullable: meta.nullable }));\n}\n\n// Columns omitted from an insert are NULL in an INSTEAD OF trigger. NULLs of NOT NULL columns are\n// left out of the payload so applying it falls back to the table DEFAULT on every replica.\n// The tombstone is left out: applying a create always sets it to 0.\nfunction createdPayloadSql(columns: CrdtViewColumn[]) {\n const fields = columns\n .filter((column) => column.name !== \"tombstone\")\n .map((column) => {\n const quoted = quoteId(column.name);\n const field = `${sqlStringLiteral(`${JSON.stringify(column.name)}:`)}||json_quote(new.${quoted})||','`;\n return column.nullable ? field : `case when new.${quoted} is null then '' else ${field} end`;\n });\n return concatSql([\"'{'\", `rtrim(${concatSql(fields)}, ',')`, \"'}'\"]);\n}\n\nfunction sparseUpdatePayloadSql(columnNames: string[]) {\n const changedFields = columnNames\n .filter((columnName) => columnName !== \"id\")\n .map((columnName) => {\n const quoted = quoteId(columnName);\n const key = sqlStringLiteral(`${JSON.stringify(columnName)}:`);\n return `case when old.${quoted} collate binary is not new.${quoted} then ${key}||json_quote(new.${quoted})||',' else '' end`;\n });\n return concatSql([\"'{'\", `rtrim(${concatSql(changedFields)}, ',')`, \"'}'\"]);\n}\n\nexport function createCrdtViewStatements({\n baseTableName,\n crdtTableName,\n columns,\n temp = false,\n}: {\n baseTableName: string;\n crdtTableName: string;\n columns: CrdtViewColumn[];\n /**\n * Create the view, its triggers and the intents table in the connection's temp schema,\n * so a persisted database never stores them and they always match the current schema.\n */\n temp?: boolean;\n}) {\n const createdPayload = createdPayloadSql(columns);\n const updatePayload = sparseUpdatePayloadSql(columns.map((column) => column.name));\n const tempKeyword = temp ? \"temp \" : \"\";\n\n return [\n `create ${tempKeyword}table if not exists ${quoteId(CRDT_CHANGE_INTENTS_TABLE)} (\n \"seq\" integer primary key,\n \"dataset\" text not null,\n \"type\" text not null,\n \"item_id\" text not null,\n \"new_item_id\" text,\n \"payload_json\" text not null\n)`,\n `create ${tempKeyword}view ${quoteId(crdtTableName)} as\nselect * from ${quoteId(baseTableName)}\nwhere tombstone = 0`,\n `create ${tempKeyword}trigger ${quoteId(`${crdtTableName}_created`)}\ninstead of insert on ${quoteId(crdtTableName)}\nfor each row\nbegin\n insert into ${quoteId(CRDT_CHANGE_INTENTS_TABLE)} (\n \"dataset\", \"type\", \"item_id\", \"new_item_id\", \"payload_json\"\n ) values (\n ${sqlStringLiteral(baseTableName)}, 'item-created', new.\"id\", null, ${createdPayload}\n );\nend`,\n `create ${tempKeyword}trigger ${quoteId(`${crdtTableName}_updated`)}\ninstead of update on ${quoteId(crdtTableName)}\nfor each row\nbegin\n insert into ${quoteId(CRDT_CHANGE_INTENTS_TABLE)} (\n \"dataset\", \"type\", \"item_id\", \"new_item_id\", \"payload_json\"\n ) values (\n ${sqlStringLiteral(baseTableName)}, 'item-updated', old.\"id\", new.\"id\", ${updatePayload}\n );\nend`,\n `create ${tempKeyword}trigger ${quoteId(`${crdtTableName}_deleted`)}\ninstead of delete on ${quoteId(crdtTableName)}\nfor each row\nwhen old.\"tombstone\" = 0\nbegin\n insert into ${quoteId(CRDT_CHANGE_INTENTS_TABLE)} (\n \"dataset\", \"type\", \"item_id\", \"new_item_id\", \"payload_json\"\n ) values (\n ${sqlStringLiteral(baseTableName)}, 'item-deleted', old.\"id\", null, '{}'\n );\nend`,\n ];\n}\n\nexport function registerCrdtIntentDrainer({\n reactiveDb,\n storage,\n}: {\n reactiveDb: SQLiteReactiveDb<any>;\n storage: InternalCrdtStorage;\n}) {\n let eventApplied = false;\n let processedIntentMutationVersion = reactiveDb.getTableMutationVersion(CRDT_CHANGE_INTENTS_TABLE);\n\n reactiveDb.db.setAfterMutatingStatement((tx) => {\n if (\n reactiveDb.hasSynchronousHooks &&\n reactiveDb.getTableMutationVersion(CRDT_CHANGE_INTENTS_TABLE) === processedIntentMutationVersion\n ) {\n return;\n }\n\n try {\n if (drainCrdtChangeIntents({ tx, storage })) {\n eventApplied = true;\n }\n } finally {\n processedIntentMutationVersion = reactiveDb.getTableMutationVersion(CRDT_CHANGE_INTENTS_TABLE);\n }\n });\n\n // A rolled-back drain leaves `eventApplied` set on purpose: with asynchronous hooks the\n // rollback can be delivered after a later transaction drained again, and clearing the flag\n // then would strand that transaction's events. Processing with nothing pending is a no-op.\n reactiveDb.addEventListener(\"transaction-committed\", () => {\n if (eventApplied) {\n eventApplied = false;\n void storage.internal.processEnqueuedEvents();\n }\n });\n}\n\nexport function drainCrdtChangeIntents({\n tx,\n storage,\n}: {\n tx: InternalSQLiteTransactionWrapper<any>;\n storage: InternalCrdtStorage;\n}) {\n const intents = tx.executePreparedRaw<[], CrdtChangeIntent>({\n key: \"drain-crdt-change-intents\",\n sql: `delete from ${quoteId(CRDT_CHANGE_INTENTS_TABLE)} returning *`,\n meta: { loggerLevel: \"system\" },\n });\n intents.sort((a, b) => a.seq - b.seq);\n\n const { appliedEvents } = storage.internal.applyOwnIntentsFromTransaction(tx, intents);\n return appliedEvents > 0;\n}\n"],"mappings":";;AAMA,MAAa,4BAA4B;AAEzC,SAAgB,cAAc,EAC5B,IACA,eACA,eACA,QAMC;CACD,MAAM,cAAc,GAAG,SAAS;CAEhC,IAAI,CAAC,aACH,MAAM,IAAI,MAAM,SAAS,cAAc,WAAW;CAGpD,MAAM,UAAU,IAAI,IAAI,YAAY,QAAQ,KAAK,WAAW,CAAC,OAAO,MAAM,MAAM,CAAC,CAAC;CAElF,MAAM,WAAW,QAAQ,IAAI,IAAI;CACjC,IAAI,CAAC,UACH,MAAM,IAAI,MACR,UAAU,cAAc,6FAC1B;CAEF,IAAI,SAAS,SAAS,YAAY,MAAM,QACtC,MAAM,IAAI,MACR,UAAU,cAAc,4CAA4C,SAAS,SAAS,wCACxF;CAGF,MAAM,kBAAkB,QAAQ,IAAI,WAAW;CAC/C,IAAI,CAAC,iBACH,MAAM,IAAI,MACR,UAAU,cAAc,iHAC1B;CAEF,MAAM,gBAAgB,gBAAgB,SAAS,YAAY;CAC3D,IAAI,kBAAkB,aAAa,kBAAkB,WACnD,MAAM,IAAI,MACR,UAAU,cAAc,iEAAiE,gBAAgB,SAAS,2CACpH;CAGF,KAAK,MAAM,OAAO,yBAAyB;EACzC;EACA;EACA,SAAS,YAAY,QAAQ,KAAK,YAAY;GAAE,MAAM,OAAO;GAAM,UAAU,OAAO;EAAW,EAAE;EACjG;CACF,CAAC,GACC,GAAG,QAAQ,KAAK,EAAE,aAAa,SAAS,CAAC;AAE7C;AAGA,SAAS,UAAU,OAAkC;CACnD,IAAI,MAAM,WAAW,GACnB,OAAO;CAET,IAAI,MAAM,WAAW,GACnB,OAAO,MAAM;CAEf,MAAM,MAAM,MAAM,UAAU;CAC5B,OAAO,IAAI,UAAU,MAAM,MAAM,GAAG,GAAG,CAAC,EAAE,MAAM,UAAU,MAAM,MAAM,GAAG,CAAC,EAAE;AAC9E;AAEA,SAAS,iBAAiB,OAAe;CACvC,OAAO,IAAI,MAAM,WAAW,KAAK,IAAI,EAAE;AACzC;AAOA,SAAgB,yBAAyB,OAA0C;CACjF,OAAO,OAAO,QAAQ,MAAM,OAAO,CAAC,CAAC,KAAK,CAAC,MAAM,WAAW;EAAE;EAAM,UAAU,KAAK;CAAS,EAAE;AAChG;AAKA,SAAS,kBAAkB,SAA2B;CAQpD,OAAO,UAAU;EAAC;EAAO,SAAS,UAPnB,QACZ,QAAQ,WAAW,OAAO,SAAS,WAAW,CAAC,CAC/C,KAAK,WAAW;GACf,MAAM,SAAS,QAAQ,OAAO,IAAI;GAClC,MAAM,QAAQ,GAAG,iBAAiB,GAAG,KAAK,UAAU,OAAO,IAAI,EAAE,EAAE,EAAE,mBAAmB,OAAO;GAC/F,OAAO,OAAO,WAAW,QAAQ,iBAAiB,OAAO,wBAAwB,MAAM;EACzF,CAC+C,CAAC,EAAE;EAAS;CAAK,CAAC;AACrE;AAEA,SAAS,uBAAuB,aAAuB;CAQrD,OAAO,UAAU;EAAC;EAAO,SAAS,UAPZ,YACnB,QAAQ,eAAe,eAAe,IAAI,CAAC,CAC3C,KAAK,eAAe;GACnB,MAAM,SAAS,QAAQ,UAAU;GAEjC,OAAO,iBAAiB,OAAO,6BAA6B,OAAO,QADvD,iBAAiB,GAAG,KAAK,UAAU,UAAU,EAAE,EACkB,EAAE,mBAAmB,OAAO;EAC3G,CACsD,CAAC,EAAE;EAAS;CAAK,CAAC;AAC5E;AAEA,SAAgB,yBAAyB,EACvC,eACA,eACA,SACA,OAAO,SAUN;CACD,MAAM,iBAAiB,kBAAkB,OAAO;CAChD,MAAM,gBAAgB,uBAAuB,QAAQ,KAAK,WAAW,OAAO,IAAI,CAAC;CACjF,MAAM,cAAc,OAAO,UAAU;CAErC,OAAO;EACL,UAAU,YAAY,sBAAsB,QAAQ,yBAAyB,EAAE;;;;;;;;EAQ/E,UAAU,YAAY,OAAO,QAAQ,aAAa,EAAE;gBACxC,QAAQ,aAAa,EAAE;;EAEnC,UAAU,YAAY,UAAU,QAAQ,GAAG,cAAc,SAAS,EAAE;uBACjD,QAAQ,aAAa,EAAE;;;gBAG9B,QAAQ,yBAAyB,EAAE;;;MAG7C,iBAAiB,aAAa,EAAE,oCAAoC,eAAe;;;EAGrF,UAAU,YAAY,UAAU,QAAQ,GAAG,cAAc,SAAS,EAAE;uBACjD,QAAQ,aAAa,EAAE;;;gBAG9B,QAAQ,yBAAyB,EAAE;;;MAG7C,iBAAiB,aAAa,EAAE,wCAAwC,cAAc;;;EAGxF,UAAU,YAAY,UAAU,QAAQ,GAAG,cAAc,SAAS,EAAE;uBACjD,QAAQ,aAAa,EAAE;;;;gBAI9B,QAAQ,yBAAyB,EAAE;;;MAG7C,iBAAiB,aAAa,EAAE;;;CAGpC;AACF;AAEA,SAAgB,0BAA0B,EACxC,YACA,WAIC;CACD,IAAI,eAAe;CACnB,IAAI,iCAAiC,WAAW,wBAAwB,yBAAyB;CAEjG,WAAW,GAAG,2BAA2B,OAAO;EAC9C,IACE,WAAW,uBACX,WAAW,wBAAA,qBAAiD,MAAM,gCAElE;EAGF,IAAI;GACF,IAAI,uBAAuB;IAAE;IAAI;GAAQ,CAAC,GACxC,eAAe;EAEnB,UAAU;GACR,iCAAiC,WAAW,wBAAwB,yBAAyB;EAC/F;CACF,CAAC;CAKD,WAAW,iBAAiB,+BAA+B;EACzD,IAAI,cAAc;GAChB,eAAe;GACf,QAAa,SAAS,sBAAsB;EAC9C;CACF,CAAC;AACH;AAEA,SAAgB,uBAAuB,EACrC,IACA,WAIC;CACD,MAAM,UAAU,GAAG,mBAAyC;EAC1D,KAAK;EACL,KAAK,eAAe,QAAQ,yBAAyB,EAAE;EACvD,MAAM,EAAE,aAAa,SAAS;CAChC,CAAC;CACD,QAAQ,MAAM,GAAG,MAAM,EAAE,MAAM,EAAE,GAAG;CAEpC,MAAM,EAAE,kBAAkB,QAAQ,SAAS,+BAA+B,IAAI,OAAO;CACrF,OAAO,gBAAgB;AACzB"}
1
+ {"version":3,"file":"make-crdt-table-Cu8WhRBk.js","names":[],"sources":["../src/sqlite-crdt/make-crdt-table.ts"],"sourcesContent":["import type { SQLiteReactiveDb } from \"../memory-db/sqlite-reactive-db\";\nimport type { AnyTableBuilder } from \"../schema/table-builder\";\nimport type { InternalSQLiteTransactionWrapper, SQLiteDbWrapper } from \"../sqlite-db-wrapper\";\nimport { quoteId } from \"../utils\";\nimport type { CrdtChangeIntent, InternalCrdtStorage } from \"./crdt-storage\";\n\nexport const CRDT_CHANGE_INTENTS_TABLE = \"crdt_change_intents\";\n\nexport function makeCrdtTable({\n db,\n baseTableName,\n crdtTableName,\n temp,\n}: {\n db: SQLiteDbWrapper<any>;\n baseTableName: string;\n crdtTableName: string;\n temp?: boolean;\n}) {\n const tableSchema = db.dbSchema[baseTableName];\n\n if (!tableSchema) {\n throw new Error(`Table ${baseTableName} not found`);\n }\n\n const columns = new Map(tableSchema.columns.map((column) => [column.name, column]));\n\n const idColumn = columns.get(\"id\");\n if (!idColumn) {\n throw new Error(\n `Table \"${baseTableName}\" is missing a required \"id\" column. CRDT tables must have an \"id\" column to identify items.`,\n );\n }\n if (idColumn.dataType.toUpperCase() !== \"TEXT\") {\n throw new Error(\n `Table \"${baseTableName}\": \"id\" column must be of type TEXT, got \"${idColumn.dataType}\". CRDT item IDs are stored as strings.`,\n );\n }\n\n const tombstoneColumn = columns.get(\"tombstone\");\n if (!tombstoneColumn) {\n throw new Error(\n `Table \"${baseTableName}\" is missing a required \"tombstone\" column. CRDT tables must have a \"tombstone\" INTEGER column for soft deletes.`,\n );\n }\n const tombstoneType = tombstoneColumn.dataType.toUpperCase();\n if (tombstoneType !== \"INTEGER\" && tombstoneType !== \"BOOLEAN\") {\n throw new Error(\n `Table \"${baseTableName}\": \"tombstone\" column must be of type INTEGER or BOOLEAN, got \"${tombstoneColumn.dataType}\". It is compared as 0/1 for soft deletes.`,\n );\n }\n\n for (const sql of createCrdtViewStatements({\n baseTableName,\n crdtTableName,\n columns: tableSchema.columns.map((column) => ({ name: column.name, nullable: column.isNullable })),\n temp,\n })) {\n db.execute(sql, { loggerLevel: \"system\" });\n }\n}\n\n// Workerd caps expr depth at 100; a left-deep `||` chain fails around 34 columns.\nfunction concatSql(parts: readonly string[]): string {\n if (parts.length === 0) {\n return \"''\";\n }\n if (parts.length === 1) {\n return parts[0];\n }\n const mid = parts.length >> 1;\n return `(${concatSql(parts.slice(0, mid))})||(${concatSql(parts.slice(mid))})`;\n}\n\nfunction sqlStringLiteral(value: string) {\n return `'${value.replaceAll(\"'\", \"''\")}'`;\n}\n\nexport type CrdtViewColumn = {\n name: string;\n nullable: boolean;\n};\n\nexport function crdtViewColumnsFromTable(table: AnyTableBuilder): CrdtViewColumn[] {\n return Object.entries(table.columns).map(([name, meta]) => ({ name, nullable: meta.nullable }));\n}\n\n// Columns omitted from an insert are NULL in an INSTEAD OF trigger. NULLs of NOT NULL columns are\n// left out of the payload so applying it falls back to the table DEFAULT on every replica.\n// The tombstone is left out: applying a create always sets it to 0.\nfunction createdPayloadSql(columns: CrdtViewColumn[]) {\n const fields = columns\n .filter((column) => column.name !== \"tombstone\")\n .map((column) => {\n const quoted = quoteId(column.name);\n const field = `${sqlStringLiteral(`${JSON.stringify(column.name)}:`)}||json_quote(new.${quoted})||','`;\n return column.nullable ? field : `case when new.${quoted} is null then '' else ${field} end`;\n });\n return concatSql([\"'{'\", `rtrim(${concatSql(fields)}, ',')`, \"'}'\"]);\n}\n\nfunction sparseUpdatePayloadSql(columnNames: string[]) {\n const changedFields = columnNames\n .filter((columnName) => columnName !== \"id\")\n .map((columnName) => {\n const quoted = quoteId(columnName);\n const key = sqlStringLiteral(`${JSON.stringify(columnName)}:`);\n return `case when old.${quoted} collate binary is not new.${quoted} then ${key}||json_quote(new.${quoted})||',' else '' end`;\n });\n return concatSql([\"'{'\", `rtrim(${concatSql(changedFields)}, ',')`, \"'}'\"]);\n}\n\nexport function createCrdtViewStatements({\n baseTableName,\n crdtTableName,\n columns,\n temp = false,\n}: {\n baseTableName: string;\n crdtTableName: string;\n columns: CrdtViewColumn[];\n /**\n * Create the view, its triggers and the intents table in the connection's temp schema,\n * so a persisted database never stores them and they always match the current schema.\n */\n temp?: boolean;\n}) {\n const createdPayload = createdPayloadSql(columns);\n const updatePayload = sparseUpdatePayloadSql(columns.map((column) => column.name));\n const tempKeyword = temp ? \"temp \" : \"\";\n\n return [\n `create ${tempKeyword}table if not exists ${quoteId(CRDT_CHANGE_INTENTS_TABLE)} (\n \"seq\" integer primary key,\n \"dataset\" text not null,\n \"type\" text not null,\n \"item_id\" text not null,\n \"new_item_id\" text,\n \"payload_json\" text not null\n)`,\n `create ${tempKeyword}view ${quoteId(crdtTableName)} as\nselect * from ${quoteId(baseTableName)}\nwhere tombstone = 0`,\n `create ${tempKeyword}trigger ${quoteId(`${crdtTableName}_created`)}\ninstead of insert on ${quoteId(crdtTableName)}\nfor each row\nbegin\n insert into ${quoteId(CRDT_CHANGE_INTENTS_TABLE)} (\n \"dataset\", \"type\", \"item_id\", \"new_item_id\", \"payload_json\"\n ) values (\n ${sqlStringLiteral(baseTableName)}, 'item-created', new.\"id\", null, ${createdPayload}\n );\nend`,\n `create ${tempKeyword}trigger ${quoteId(`${crdtTableName}_updated`)}\ninstead of update on ${quoteId(crdtTableName)}\nfor each row\nbegin\n insert into ${quoteId(CRDT_CHANGE_INTENTS_TABLE)} (\n \"dataset\", \"type\", \"item_id\", \"new_item_id\", \"payload_json\"\n ) values (\n ${sqlStringLiteral(baseTableName)}, 'item-updated', old.\"id\", new.\"id\", ${updatePayload}\n );\nend`,\n `create ${tempKeyword}trigger ${quoteId(`${crdtTableName}_deleted`)}\ninstead of delete on ${quoteId(crdtTableName)}\nfor each row\nwhen old.\"tombstone\" = 0\nbegin\n insert into ${quoteId(CRDT_CHANGE_INTENTS_TABLE)} (\n \"dataset\", \"type\", \"item_id\", \"new_item_id\", \"payload_json\"\n ) values (\n ${sqlStringLiteral(baseTableName)}, 'item-deleted', old.\"id\", null, '{}'\n );\nend`,\n ];\n}\n\nexport function registerCrdtIntentDrainer({\n reactiveDb,\n storage,\n}: {\n reactiveDb: SQLiteReactiveDb<any>;\n storage: InternalCrdtStorage;\n}) {\n let eventApplied = false;\n let processedIntentMutationVersion = reactiveDb.getTableMutationVersion(CRDT_CHANGE_INTENTS_TABLE);\n\n reactiveDb.db.setAfterMutatingStatement((tx) => {\n if (\n reactiveDb.hasSynchronousHooks &&\n reactiveDb.getTableMutationVersion(CRDT_CHANGE_INTENTS_TABLE) === processedIntentMutationVersion\n ) {\n return;\n }\n\n try {\n if (drainCrdtChangeIntents({ tx, storage })) {\n eventApplied = true;\n }\n } finally {\n processedIntentMutationVersion = reactiveDb.getTableMutationVersion(CRDT_CHANGE_INTENTS_TABLE);\n }\n });\n\n // A rolled-back drain leaves `eventApplied` set on purpose: with asynchronous hooks the\n // rollback can be delivered after a later transaction drained again, and clearing the flag\n // then would strand that transaction's events. Processing with nothing pending is a no-op.\n reactiveDb.afterCommit = () => {\n if (eventApplied) {\n eventApplied = false;\n void storage.internal.processEnqueuedEvents();\n }\n };\n}\n\nexport function drainCrdtChangeIntents({\n tx,\n storage,\n}: {\n tx: InternalSQLiteTransactionWrapper<any>;\n storage: InternalCrdtStorage;\n}) {\n const intents = tx.executePreparedRaw<[], CrdtChangeIntent>({\n key: \"drain-crdt-change-intents\",\n sql: `delete from ${quoteId(CRDT_CHANGE_INTENTS_TABLE)} returning *`,\n meta: { loggerLevel: \"system\" },\n });\n intents.sort((a, b) => a.seq - b.seq);\n\n const { appliedEvents } = storage.internal.applyOwnIntentsFromTransaction(tx, intents);\n return appliedEvents > 0;\n}\n"],"mappings":";;AAMA,MAAa,4BAA4B;AAEzC,SAAgB,cAAc,EAC5B,IACA,eACA,eACA,QAMC;CACD,MAAM,cAAc,GAAG,SAAS;CAEhC,IAAI,CAAC,aACH,MAAM,IAAI,MAAM,SAAS,cAAc,WAAW;CAGpD,MAAM,UAAU,IAAI,IAAI,YAAY,QAAQ,KAAK,WAAW,CAAC,OAAO,MAAM,MAAM,CAAC,CAAC;CAElF,MAAM,WAAW,QAAQ,IAAI,IAAI;CACjC,IAAI,CAAC,UACH,MAAM,IAAI,MACR,UAAU,cAAc,6FAC1B;CAEF,IAAI,SAAS,SAAS,YAAY,MAAM,QACtC,MAAM,IAAI,MACR,UAAU,cAAc,4CAA4C,SAAS,SAAS,wCACxF;CAGF,MAAM,kBAAkB,QAAQ,IAAI,WAAW;CAC/C,IAAI,CAAC,iBACH,MAAM,IAAI,MACR,UAAU,cAAc,iHAC1B;CAEF,MAAM,gBAAgB,gBAAgB,SAAS,YAAY;CAC3D,IAAI,kBAAkB,aAAa,kBAAkB,WACnD,MAAM,IAAI,MACR,UAAU,cAAc,iEAAiE,gBAAgB,SAAS,2CACpH;CAGF,KAAK,MAAM,OAAO,yBAAyB;EACzC;EACA;EACA,SAAS,YAAY,QAAQ,KAAK,YAAY;GAAE,MAAM,OAAO;GAAM,UAAU,OAAO;EAAW,EAAE;EACjG;CACF,CAAC,GACC,GAAG,QAAQ,KAAK,EAAE,aAAa,SAAS,CAAC;AAE7C;AAGA,SAAS,UAAU,OAAkC;CACnD,IAAI,MAAM,WAAW,GACnB,OAAO;CAET,IAAI,MAAM,WAAW,GACnB,OAAO,MAAM;CAEf,MAAM,MAAM,MAAM,UAAU;CAC5B,OAAO,IAAI,UAAU,MAAM,MAAM,GAAG,GAAG,CAAC,EAAE,MAAM,UAAU,MAAM,MAAM,GAAG,CAAC,EAAE;AAC9E;AAEA,SAAS,iBAAiB,OAAe;CACvC,OAAO,IAAI,MAAM,WAAW,KAAK,IAAI,EAAE;AACzC;AAOA,SAAgB,yBAAyB,OAA0C;CACjF,OAAO,OAAO,QAAQ,MAAM,OAAO,CAAC,CAAC,KAAK,CAAC,MAAM,WAAW;EAAE;EAAM,UAAU,KAAK;CAAS,EAAE;AAChG;AAKA,SAAS,kBAAkB,SAA2B;CAQpD,OAAO,UAAU;EAAC;EAAO,SAAS,UAPnB,QACZ,QAAQ,WAAW,OAAO,SAAS,WAAW,CAAC,CAC/C,KAAK,WAAW;GACf,MAAM,SAAS,QAAQ,OAAO,IAAI;GAClC,MAAM,QAAQ,GAAG,iBAAiB,GAAG,KAAK,UAAU,OAAO,IAAI,EAAE,EAAE,EAAE,mBAAmB,OAAO;GAC/F,OAAO,OAAO,WAAW,QAAQ,iBAAiB,OAAO,wBAAwB,MAAM;EACzF,CAC+C,CAAC,EAAE;EAAS;CAAK,CAAC;AACrE;AAEA,SAAS,uBAAuB,aAAuB;CAQrD,OAAO,UAAU;EAAC;EAAO,SAAS,UAPZ,YACnB,QAAQ,eAAe,eAAe,IAAI,CAAC,CAC3C,KAAK,eAAe;GACnB,MAAM,SAAS,QAAQ,UAAU;GAEjC,OAAO,iBAAiB,OAAO,6BAA6B,OAAO,QADvD,iBAAiB,GAAG,KAAK,UAAU,UAAU,EAAE,EACkB,EAAE,mBAAmB,OAAO;EAC3G,CACsD,CAAC,EAAE;EAAS;CAAK,CAAC;AAC5E;AAEA,SAAgB,yBAAyB,EACvC,eACA,eACA,SACA,OAAO,SAUN;CACD,MAAM,iBAAiB,kBAAkB,OAAO;CAChD,MAAM,gBAAgB,uBAAuB,QAAQ,KAAK,WAAW,OAAO,IAAI,CAAC;CACjF,MAAM,cAAc,OAAO,UAAU;CAErC,OAAO;EACL,UAAU,YAAY,sBAAsB,QAAQ,yBAAyB,EAAE;;;;;;;;EAQ/E,UAAU,YAAY,OAAO,QAAQ,aAAa,EAAE;gBACxC,QAAQ,aAAa,EAAE;;EAEnC,UAAU,YAAY,UAAU,QAAQ,GAAG,cAAc,SAAS,EAAE;uBACjD,QAAQ,aAAa,EAAE;;;gBAG9B,QAAQ,yBAAyB,EAAE;;;MAG7C,iBAAiB,aAAa,EAAE,oCAAoC,eAAe;;;EAGrF,UAAU,YAAY,UAAU,QAAQ,GAAG,cAAc,SAAS,EAAE;uBACjD,QAAQ,aAAa,EAAE;;;gBAG9B,QAAQ,yBAAyB,EAAE;;;MAG7C,iBAAiB,aAAa,EAAE,wCAAwC,cAAc;;;EAGxF,UAAU,YAAY,UAAU,QAAQ,GAAG,cAAc,SAAS,EAAE;uBACjD,QAAQ,aAAa,EAAE;;;;gBAI9B,QAAQ,yBAAyB,EAAE;;;MAG7C,iBAAiB,aAAa,EAAE;;;CAGpC;AACF;AAEA,SAAgB,0BAA0B,EACxC,YACA,WAIC;CACD,IAAI,eAAe;CACnB,IAAI,iCAAiC,WAAW,wBAAwB,yBAAyB;CAEjG,WAAW,GAAG,2BAA2B,OAAO;EAC9C,IACE,WAAW,uBACX,WAAW,wBAAA,qBAAiD,MAAM,gCAElE;EAGF,IAAI;GACF,IAAI,uBAAuB;IAAE;IAAI;GAAQ,CAAC,GACxC,eAAe;EAEnB,UAAU;GACR,iCAAiC,WAAW,wBAAwB,yBAAyB;EAC/F;CACF,CAAC;CAKD,WAAW,oBAAoB;EAC7B,IAAI,cAAc;GAChB,eAAe;GACf,QAAa,SAAS,sBAAsB;EAC9C;CACF;AACF;AAEA,SAAgB,uBAAuB,EACrC,IACA,WAIC;CACD,MAAM,UAAU,GAAG,mBAAyC;EAC1D,KAAK;EACL,KAAK,eAAe,QAAQ,yBAAyB,EAAE;EACvD,MAAM,EAAE,aAAa,SAAS;CAChC,CAAC;CACD,QAAQ,MAAM,GAAG,MAAM,EAAE,MAAM,EAAE,GAAG;CAEpC,MAAM,EAAE,kBAAkB,QAAQ,SAAS,+BAA+B,IAAI,OAAO;CACrF,OAAO,gBAAgB;AACzB"}
@@ -1,5 +1,5 @@
1
1
  import { a as acquireLock, l as generateId, r as TypedBroadcastChannel } from "./crdt-table-schema-D6Yf61Ex.js";
2
- import { o as verifySyncSchemaWithDriver } from "./crdt-sync-producer-DVEWad4t.js";
2
+ import { o as verifySyncSchemaWithDriver } from "./crdt-sync-producer-t2jHxO38.js";
3
3
  import sqlite3InitModule from "@sqlite.org/sqlite-wasm";
4
4
  import { createStore, del, get, set } from "idb-keyval";
5
5
  //#region src/wasm-driver.ts
@@ -217,4 +217,4 @@ function createReloadRequestHandler({ resetState, broadcast, generateEpoch = gen
217
217
  //#endregion
218
218
  export { createWasmSqliteDriver as _, acquireReplicaLock as a, isWorkerErrorResponseMessage as c, isWorkerRequestMessage as d, isWorkerResponseMessage as f, createWasmMemoryDriver as g, verifySyncSchema as h, createResetStateStore as i, isWorkerInitMessage as l, syncDbWorkerLockName as m, createIdbResetStore as n, runWithNoActiveReplicas as o, syncDbClientLockName as p, createReloadRequestHandler as r, createBroadcastChannels as s, RESET_REQUEST_TTL_MS as t, isWorkerNotificationMessage as u, loadSqliteWasm as v };
219
219
 
220
- //# sourceMappingURL=reset-state-DiuE5Yuu.js.map
220
+ //# sourceMappingURL=reset-state-Bz1sfbf3.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"reset-state-DiuE5Yuu.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":"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"}
1
+ {"version":3,"file":"reset-state-Bz1sfbf3.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":"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"}
package/dist/server.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { D as EventsPullResponse, J as ClientDatasetRejectionReason, On as MigratableEvent, Ot as jsonSafeParse, T as EventsPushResponse, dn as SyncDbSchema, jn as SyncDbMigrator } from "./index-B3s_6aG6.js";
1
+ import { D as EventsPullResponse, J as ClientDatasetRejectionReason, On as MigratableEvent, Ot as jsonSafeParse, T as EventsPushResponse, dn as SyncDbSchema, jn as SyncDbMigrator } from "./index-ChmCuLQU.js";
2
2
  import { z } from "zod";
3
3
  //#region src/server/admit-client-push.d.ts
4
4
  type PushedCrdtEvent = MigratableEvent & {
@@ -10,6 +10,8 @@ var SQLiteReactiveDb = class SQLiteReactiveDb {
10
10
  isDisposed = false;
11
11
  mutationVersion = 0;
12
12
  tableMutationVersions = /* @__PURE__ */ new Map();
13
+ /** Runs inside the commit hook after a transaction that changed rows, so it must not use the database. */
14
+ afterCommit = null;
13
15
  constructor(opts) {
14
16
  this.driver = opts.driver;
15
17
  this.logger = opts.logger;
@@ -223,13 +225,12 @@ var SQLiteReactiveDb = class SQLiteReactiveDb {
223
225
  onRollback: () => {
224
226
  if (this.isDisposed || updateQueue.size === 0) return;
225
227
  updateQueue.clear();
226
- this.eventTarget.dispatchEvent("transaction-rolled-back", void 0);
227
228
  },
228
229
  onCommit: () => {
229
230
  if (this.isDisposed || updateQueue.size === 0) return;
230
231
  const tables = updateQueue;
231
232
  updateQueue = /* @__PURE__ */ new Set();
232
- this.eventTarget.dispatchEvent("transaction-committed", void 0);
233
+ this.afterCommit?.();
233
234
  queueMicrotask(() => {
234
235
  if (!this.isDisposed) this.notifyTableSubscribers(tables);
235
236
  });
@@ -267,20 +268,24 @@ var SQLiteReactiveDb = class SQLiteReactiveDb {
267
268
  };
268
269
  function createDebouncedCallback(callback, delay) {
269
270
  let timeout = null;
270
- let shouldCallWithoutDelay = true;
271
+ let pendingArgs = null;
272
+ const run = (args) => {
273
+ timeout = setTimeout(() => {
274
+ timeout = null;
275
+ if (pendingArgs) {
276
+ const args = pendingArgs;
277
+ pendingArgs = null;
278
+ run(args);
279
+ }
280
+ }, delay);
281
+ callback(...args);
282
+ };
271
283
  return (...args) => {
272
- if (shouldCallWithoutDelay) {
273
- callback(...args);
274
- shouldCallWithoutDelay = false;
284
+ if (timeout) {
285
+ pendingArgs = args;
275
286
  return;
276
287
  }
277
- const effect = () => {
278
- timeout = null;
279
- shouldCallWithoutDelay = true;
280
- return callback(...args);
281
- };
282
- if (timeout) clearTimeout(timeout);
283
- timeout = setTimeout(effect, delay);
288
+ run(args);
284
289
  };
285
290
  }
286
291
  function parametersAreEqual(a, b) {
@@ -293,4 +298,4 @@ function parametersAreEqual(a, b) {
293
298
  //#endregion
294
299
  export { parametersAreEqual as n, SQLiteReactiveDb as t };
295
300
 
296
- //# sourceMappingURL=sqlite-reactive-db-BwY3FoIy.js.map
301
+ //# sourceMappingURL=sqlite-reactive-db-C2BtEAeE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlite-reactive-db-C2BtEAeE.js","names":[],"sources":["../src/memory-db/sqlite-reactive-db.ts"],"sourcesContent":["import { BoundMap } from \"../bound-map\";\nimport { type Logger, startPerformanceLogger } from \"../logger\";\nimport { type PreparedStatement, SQLiteDbWrapper } from \"../sqlite-db-wrapper\";\nimport type { SqliteDriver } from \"../sqlite-driver\";\nimport { createTypedEventTarget, type TypedEvent } from \"../utils\";\n\ntype TableName<Database> = keyof Database extends string ? keyof Database : never;\n\nexport type SQLiteReactiveDbOptions = {\n driver: SqliteDriver;\n snapshot?: Uint8Array<ArrayBufferLike>;\n logger: Logger;\n loggerPrefix?: string;\n};\n\ntype EventsMap = {\n \"any-table-changed\": undefined;\n} & Record<`table:${string}`, void>;\n\ntype LiveQuery<TResult> = {\n getRows: () => TResult[];\n refresh: () => void;\n subscribe: (onchange: () => void) => () => void;\n};\n\nexport type SharedLiveQuery<TResult> = LiveQuery<TResult> & {\n readonly sql: string;\n readonly parameters: readonly unknown[];\n getSubscriberCount: () => number;\n};\n\ntype SharedLiveQueryEntry<TResult> = SharedLiveQuery<TResult> & {\n listeners: Set<() => void>;\n unsubscribeFromLiveQuery: (() => void) | null;\n cleanupTimeout: ReturnType<typeof setTimeout> | null;\n};\n\nexport type SharedLiveQuerySnapshot = {\n sql: string;\n parameters: readonly unknown[];\n subscriberCount: number;\n};\n\nexport class SQLiteReactiveDb<Database> {\n readonly db: SQLiteDbWrapper<Database>;\n private readonly driver: SqliteDriver;\n\n private readonly logger: Logger;\n\n private tablesUsedStatement: PreparedStatement<[string], { name: string; isWrite: boolean }> | null = null;\n\n private eventTarget = createTypedEventTarget<EventsMap>();\n private isDisposed = false;\n private mutationVersion = 0;\n private tableMutationVersions = new Map<string, number>();\n /** Runs inside the commit hook after a transaction that changed rows, so it must not use the database. */\n afterCommit: (() => void) | null = null;\n\n private constructor(opts: SQLiteReactiveDbOptions) {\n this.driver = opts.driver;\n this.logger = opts.logger;\n\n this.db = new SQLiteDbWrapper({\n driver: opts.driver,\n logger: this.logger,\n loggerPrefix: opts.loggerPrefix ?? \"memory\",\n });\n }\n\n static create<Database>(opts: SQLiteReactiveDbOptions) {\n const perf = startPerformanceLogger(opts.logger, \"system\");\n\n const db = new SQLiteReactiveDb<Database>(opts);\n\n if (opts.snapshot) {\n db.useSnapshot(opts.snapshot);\n }\n db.registerDbHooks();\n\n perf.logEnd(\"createSQLiteMemoryDb\", \"success\", \"system\");\n\n return db;\n }\n\n /**\n * When false, table mutation versions only advance after the writing statement\n * returns, so they cannot tell whether that statement wrote to a table.\n */\n get hasSynchronousHooks() {\n return this.driver.synchronousHooks;\n }\n\n private liveQueryStatements = new BoundMap<string, PreparedStatement<any[], unknown>>({\n maxSize: 100,\n onRemove(_, value) {\n value.finalize();\n },\n });\n\n private sharedLiveQueries = new Map<string, SharedLiveQueryEntry<unknown>[]>();\n\n createLiveQuery<TResult>(query: { sql: string; parameters: readonly unknown[] }) {\n let rowsVersion = 0;\n\n const fetchRows = (parameters: readonly unknown[]) => {\n let statement = this.liveQueryStatements.get(query.sql);\n if (!statement) {\n statement = this.db.prepare<any[], any>(query.sql);\n this.liveQueryStatements.set(query.sql, statement);\n }\n rowsVersion = this.mutationVersion;\n return statement.execute(parameters as any) as TResult[];\n };\n\n let rows: TResult[] | null = null;\n\n const getRows = () => {\n if (!rows) {\n rows = fetchRows(query.parameters);\n }\n return rows;\n };\n\n let subscriber: (() => void) | null = null;\n\n let lastParameters: readonly unknown[] = query.parameters;\n const refresh = (parameters?: readonly unknown[]) => {\n if (parameters) {\n lastParameters = parameters;\n }\n rows = fetchRows(lastParameters);\n subscriber?.();\n };\n\n const subscribe = (onchange: () => void) => {\n if (subscriber) {\n throw new Error(\"Subscriber already exists\");\n }\n\n subscriber = onchange;\n const subscription = this.subscribeToQueryChanges({\n sql: query.sql,\n onDataChange: refresh,\n });\n\n // Tables may have changed while nothing was subscribed, e.g. between React's render and commit.\n if (rows && this.mutationVersion !== rowsVersion) {\n refresh();\n }\n\n return () => {\n subscription.unsubscribe();\n subscriber = null;\n };\n };\n\n return { getRows, refresh, subscribe };\n }\n\n getSharedLiveQuery<TResult>(query: { sql: string; parameters: readonly unknown[] }) {\n const existingEntry = this.sharedLiveQueries\n .get(query.sql)\n ?.find((entry) => parametersAreEqual(entry.parameters, query.parameters));\n if (existingEntry) {\n if (existingEntry.listeners.size === 0) {\n this.scheduleSharedLiveQueryCleanup(existingEntry);\n }\n return existingEntry as SharedLiveQuery<TResult>;\n }\n\n const liveQuery = this.createLiveQuery<TResult>(query);\n const entry: SharedLiveQueryEntry<TResult> = {\n sql: query.sql,\n parameters: query.parameters,\n listeners: new Set(),\n unsubscribeFromLiveQuery: null,\n cleanupTimeout: null,\n getRows: () => liveQuery.getRows(),\n refresh: () => {\n liveQuery.refresh();\n },\n getSubscriberCount: () => entry.listeners.size,\n subscribe: (onchange) => {\n this.cancelSharedLiveQueryCleanup(entry);\n this.registerSharedLiveQueryEntry(entry as SharedLiveQueryEntry<unknown>);\n entry.listeners.add(onchange);\n\n if (!entry.unsubscribeFromLiveQuery) {\n entry.unsubscribeFromLiveQuery = liveQuery.subscribe(() => {\n for (const listener of entry.listeners) {\n listener();\n }\n });\n }\n\n return () => {\n entry.listeners.delete(onchange);\n\n if (entry.listeners.size === 0) {\n entry.unsubscribeFromLiveQuery?.();\n entry.unsubscribeFromLiveQuery = null;\n this.scheduleSharedLiveQueryCleanup(entry);\n }\n };\n },\n };\n\n this.registerSharedLiveQueryEntry(entry as SharedLiveQueryEntry<unknown>);\n\n // Evict the entry if no subscriber attaches by the next tick.\n this.scheduleSharedLiveQueryCleanup(entry as SharedLiveQueryEntry<unknown>);\n\n return entry;\n }\n\n private registerSharedLiveQueryEntry(entry: SharedLiveQueryEntry<unknown>) {\n const matchingSqlEntries = this.sharedLiveQueries.get(entry.sql) ?? [];\n if (!matchingSqlEntries.includes(entry)) {\n matchingSqlEntries.push(entry);\n this.sharedLiveQueries.set(entry.sql, matchingSqlEntries);\n }\n }\n\n getSharedLiveQueriesSnapshot(): SharedLiveQuerySnapshot[] {\n const snapshot: SharedLiveQuerySnapshot[] = [];\n for (const entries of this.sharedLiveQueries.values()) {\n for (const entry of entries) {\n snapshot.push({\n sql: entry.sql,\n parameters: entry.parameters,\n subscriberCount: entry.listeners.size,\n });\n }\n }\n return snapshot;\n }\n\n private scheduleSharedLiveQueryCleanup(entry: SharedLiveQueryEntry<unknown>) {\n this.cancelSharedLiveQueryCleanup(entry);\n entry.cleanupTimeout = setTimeout(() => {\n entry.cleanupTimeout = null;\n this.releaseSharedLiveQuery(entry);\n }, 0);\n }\n\n private cancelSharedLiveQueryCleanup(entry: SharedLiveQueryEntry<unknown>) {\n if (entry.cleanupTimeout) {\n clearTimeout(entry.cleanupTimeout);\n entry.cleanupTimeout = null;\n }\n }\n\n private releaseSharedLiveQuery(entry: SharedLiveQueryEntry<unknown>) {\n if (entry.listeners.size > 0 || entry.unsubscribeFromLiveQuery) {\n return;\n }\n\n const matchingSqlEntries = this.sharedLiveQueries.get(entry.sql);\n if (!matchingSqlEntries) {\n return;\n }\n\n const nextEntries = matchingSqlEntries.filter((candidate) => candidate !== entry);\n if (nextEntries.length > 0) {\n this.sharedLiveQueries.set(entry.sql, nextEntries);\n } else {\n this.sharedLiveQueries.delete(entry.sql);\n }\n }\n\n subscribeToQueryChanges(params: { sql: string; onDataChange: () => void }) {\n const { sql, onDataChange } = params;\n\n const tables = this.getTablesUsed(sql);\n const readTables = new Set<string>();\n for (const table of tables) {\n if (!readTables.has(table.name)) {\n readTables.add(table.name);\n } else if (table.isWrite) {\n throw new Error(\"This query writes and reads from the same table. This may cause infinite loops.\");\n }\n }\n\n const notifyDataChange = createDebouncedCallback(() => {\n onDataChange();\n }, 30);\n\n for (const table of readTables) {\n this.eventTarget.addEventListener(`table:${table}`, notifyDataChange);\n }\n this.eventTarget.addEventListener(\"any-table-changed\", notifyDataChange);\n\n return {\n unsubscribe: () => {\n for (const table of readTables) {\n this.eventTarget.removeEventListener(`table:${table}`, notifyDataChange);\n }\n this.eventTarget.removeEventListener(\"any-table-changed\", notifyDataChange);\n },\n };\n }\n\n subscribeToTableChanges(table: string, onChanges: () => void) {\n this.eventTarget.addEventListener(`table:${table}`, onChanges);\n this.eventTarget.addEventListener(\"any-table-changed\", onChanges);\n return {\n unsubscribe: () => {\n this.eventTarget.removeEventListener(`table:${table}`, onChanges);\n this.eventTarget.removeEventListener(\"any-table-changed\", onChanges);\n },\n };\n }\n\n getTablesUsed(query: string) {\n if (!this.tablesUsedStatement) {\n this.tablesUsedStatement = this.db.prepare<[string], { name: string; isWrite: boolean }>(\n \"select t.tbl_name as name, u.wr as isWrite from tables_used(?) as u inner join sqlite_master as t on t.name = u.name where u.schema = 'main'\",\n { loggerLevel: \"system\" },\n );\n }\n\n const tables = this.tablesUsedStatement.execute([query]);\n\n if (tables.length === 0 && query.toLowerCase().includes(\"delete\")) {\n // tables_used function does not work with delete queries that clear entire tables\n tables.push(...this.getClearedTables(query));\n }\n\n return tables;\n }\n\n private getClearedTables(query: string) {\n const operations = this.db.execute<{\n opcode: string;\n p1: number;\n p2: number;\n }>(`EXPLAIN ${query.split(\";\")[0]}`, { loggerLevel: \"system\" }).rows;\n\n const clearedTablesRootPages = new Set<number>();\n for (const operation of operations) {\n if (operation.opcode === \"Clear\" && operation.p2 === 0) {\n clearedTablesRootPages.add(operation.p1);\n }\n }\n\n if (clearedTablesRootPages.size === 0) {\n return [];\n }\n\n const tableNames = this.db.execute<{ name: string; isWrite: boolean }>(\n `select t.tbl_name as name, true as isWrite from sqlite_master as t where t.rootpage in (${Array.from(\n clearedTablesRootPages,\n ).join(\",\")})`,\n { loggerLevel: \"system\" },\n ).rows;\n\n return tableNames;\n }\n\n addEventListener<K extends keyof EventsMap>(type: K, listener: (event: TypedEvent<EventsMap[K]>) => void) {\n this.eventTarget.addEventListener(type, listener);\n }\n\n removeEventListener<K extends keyof EventsMap>(type: K, listener: (event: TypedEvent<EventsMap[K]>) => void) {\n this.eventTarget.removeEventListener(type, listener);\n }\n\n getTableMutationVersion(table: string) {\n return this.tableMutationVersions.get(table) ?? 0;\n }\n\n private notifyTableSubscribers(tables: (TableName<Database> | (string & {}))[] | Set<string> | null = null) {\n if (!tables) {\n this.eventTarget.dispatchEvent(\"any-table-changed\", undefined);\n return;\n }\n\n for (const table of tables) {\n this.eventTarget.dispatchEvent(`table:${table}`, undefined);\n }\n }\n\n private registerDbHooks() {\n let updateQueue = new Set<string>();\n\n // Asynchronous drivers can deliver hooks that were queued before dispose.\n this.driver.setHooks({\n onUpdate: (table) => {\n if (this.isDisposed) {\n return;\n }\n updateQueue.add(table);\n this.tableMutationVersions.set(table, ++this.mutationVersion);\n },\n onRollback: () => {\n if (this.isDisposed || updateQueue.size === 0) {\n return;\n }\n\n updateQueue.clear();\n },\n onCommit: () => {\n if (this.isDisposed || updateQueue.size === 0) {\n return;\n }\n\n const tables = updateQueue;\n updateQueue = new Set<string>();\n this.afterCommit?.();\n\n queueMicrotask(() => {\n if (!this.isDisposed) {\n this.notifyTableSubscribers(tables);\n }\n });\n },\n });\n }\n\n createSnapshot() {\n const perf = startPerformanceLogger(this.logger);\n const snapshot = this.db.createSnapshot();\n perf.logEnd(\"createSnapshot\", `snapshot size: ${snapshot.byteLength}`, \"info\");\n\n return snapshot;\n }\n\n useSnapshot(snapshot: Uint8Array<ArrayBufferLike>) {\n this.db.useSnapshot(snapshot);\n this.mutationVersion++;\n this.notifyTableSubscribers();\n }\n\n dispose() {\n this.isDisposed = true;\n for (const entries of this.sharedLiveQueries.values()) {\n for (const entry of entries) {\n this.cancelSharedLiveQueryCleanup(entry);\n entry.unsubscribeFromLiveQuery?.();\n entry.unsubscribeFromLiveQuery = null;\n entry.listeners.clear();\n }\n }\n this.sharedLiveQueries.clear();\n this.liveQueryStatements.clear();\n if (this.tablesUsedStatement) {\n this.tablesUsedStatement.finalize();\n this.tablesUsedStatement = null;\n }\n this.driver.setHooks(null);\n this.db.close();\n }\n}\n\n// Leading + trailing throttle: a call after an idle window runs immediately, and calls\n// within the window collapse into one trailing call that opens the next window.\nfunction createDebouncedCallback<TArgs extends unknown[]>(callback: (...args: TArgs) => void, delay: number) {\n let timeout: unknown | null = null;\n let pendingArgs: TArgs | null = null;\n\n const run = (args: TArgs) => {\n timeout = setTimeout(() => {\n timeout = null;\n if (pendingArgs) {\n const args = pendingArgs;\n pendingArgs = null;\n run(args);\n }\n }, delay);\n callback(...args);\n };\n\n return (...args: TArgs) => {\n if (timeout) {\n pendingArgs = args;\n return;\n }\n run(args);\n };\n}\n\nexport function parametersAreEqual(a: readonly unknown[] | undefined, b: readonly unknown[] | undefined): boolean {\n if (a === b) return true;\n if (!a || !b) return false;\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (!Object.is(a[i], b[i])) {\n return false;\n }\n }\n return true;\n}\n"],"mappings":"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"}
package/dist/worker.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { C as EventsPullRequest, O as WorkerConfig, Qt as Logger, S as CreateRemoteSourceFactory, T as EventsPushResponse, c as ResetStore, ct as GetEventsBatch, dn as SyncDbSchema, w as EventsPushRequest } from "./index-B3s_6aG6.js";
2
- import { t as createWsRemoteSource } from "./ws-remote-source-Dgd9dmMl.js";
1
+ import { C as EventsPullRequest, O as WorkerConfig, Qt as Logger, S as CreateRemoteSourceFactory, T as EventsPushResponse, c as ResetStore, ct as GetEventsBatch, dn as SyncDbSchema, w as EventsPushRequest } from "./index-ChmCuLQU.js";
2
+ import { t as createWsRemoteSource } from "./ws-remote-source-BE0Yrurd.js";
3
3
  //#region src/worker-db/db-worker.d.ts
4
4
  declare function getWorkerConfig<Props = never>(): Promise<WorkerConfig<Props>>;
5
5
  type WorkerOptions = {
package/dist/worker.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import { f as createKvStateHashStore, g as SQLiteDbWrapper, i as applyWorkerDbSchema, u as workerDbConfig } from "./system-schema-uHQdQTCk.js";
2
2
  import { l as generateId, o as createDeferredPromise } from "./crdt-table-schema-D6Yf61Ex.js";
3
- import { a as formatSchemaVerificationIssues, c as createMigrator, n as createCrdtStorage, t as createCrdtSyncProducer, u as createImportData } from "./crdt-sync-producer-DVEWad4t.js";
3
+ import { a as formatSchemaVerificationIssues, c as createMigrator, n as createCrdtStorage, t as createCrdtSyncProducer, u as createImportData } from "./crdt-sync-producer-t2jHxO38.js";
4
4
  import { o as HLCCounter, t as createCrdtSyncRemoteSource, u as xxhash } from "./crdt-sync-remote-source-yeTEkOjH.js";
5
- import { _ as createWasmSqliteDriver, d as isWorkerRequestMessage, h as verifySyncSchema, i as createResetStateStore, l as isWorkerInitMessage, m as syncDbWorkerLockName, n as createIdbResetStore, o as runWithNoActiveReplicas, p as syncDbClientLockName, r as createReloadRequestHandler, s as createBroadcastChannels } from "./reset-state-DiuE5Yuu.js";
5
+ import { _ as createWasmSqliteDriver, d as isWorkerRequestMessage, h as verifySyncSchema, i as createResetStateStore, l as isWorkerInitMessage, m as syncDbWorkerLockName, n as createIdbResetStore, o as runWithNoActiveReplicas, p as syncDbClientLockName, r as createReloadRequestHandler, s as createBroadcastChannels } from "./reset-state-Bz1sfbf3.js";
6
6
  import { i as runWorkerEventLogGc, n as createStorageVersionStore, t as createWsRemoteSource } from "./ws-remote-source-BbPWrHB6.js";
7
7
  import sqlite3InitModule from "@sqlite.org/sqlite-wasm";
8
8
  //#region src/worker-db/db-worker.ts
@@ -95,7 +95,7 @@ async function createDbWorker(config, opts) {
95
95
  });
96
96
  const crdtStorage = createCrdtStorage({
97
97
  nodeId: config.clientId,
98
- initialLocalSyncId: getMaxSyncId(db, "none"),
98
+ initialLocalSyncId: Math.max(getMaxSyncId(db, "none"), pushSyncId.current),
99
99
  migrator,
100
100
  hlc: new HLCCounter(config.clientId, () => Date.now()),
101
101
  db,
@@ -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 === true) {\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: getMaxSyncId(db, \"none\"),\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. Disabled by default;\n * set `eventLogGc: true` to enable. 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 === true) {\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. Disabled by default;\n * set `eventLogGc: true` to enable. 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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"}