@sqlite-sync/core 0.13.1 → 0.13.3

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 { $ as parametersAreEqual, $t as ColumnMetadata, A as WorkerState, An as SyncDbMigrator, At as tryCatch, B as verifySyncSchemaWithDriver, Bn as CrdtUpdateLogPayload, Bt as PreparedStatement, C as EventsPullRequest, Cn as TableColumns, Ct as TypedEvent, Dn as MigratableEvent, Dt as generateId, E as createCrdtSyncRemoteSource, En as t, Et as createTypedEventTarget, F as PendingCrdtEvent, Fn as CRDT_EVENT_NO_OP_PAYLOAD, Ft as ExecuteParams, G as DefinedSyncSchema, Gt as SqliteDriver, H as NewCrdtEventValidationResult, Hn as isNoOpCrdtEventPayload, Ht as SQLiteDbWrapper, I as createCrdtApplyFunction, In as CrdtEventOrigin, It as ExecuteResult, Jt as SqliteDriverStatement, K as defineSyncSchema, Kt as SqliteDriverHooks, L as createSQLiteCrdtApplyFunction, Ln as CrdtEventStatus, M as CrdtStorageMutator, Mn as createMigrator, Mt as KvStoreItem, N as SnapshotOptions, Nn as StoredValue, Nt as createKvStoreTableQuery, O as WorkerConfig, On as Migrations, Ot as jsonSafeParse, P as createCrdtStorageMutator, Pn as createStoredValue, Pt as createSQLiteKvStore, Q as SharedLiveQuerySnapshot, Qt as startPerformanceLogger, R as SchemaVerificationIssue, Rn as CrdtEventType, Rt as KyselyQueryFactory, Sn as TableBuilder, St as TypedBroadcastChannel, T as EventsPushResponse, Tn as WriteOrigin, Tt as createDeferredPromise, U as validateNewCrdtEvent, Un as dummyKysely, Ut as SQLiteTransactionWrapper, V as CrdtEventValidationError, Vn as PersistedCrdtEvent, Vt as QueryBuilderOutput, W as DefineSyncSchemaConfig, X as SQLiteReactiveDb, Xt as LogLevel, Y as admitClientEvents, Yt as SqliteDriverValue, Z as SharedLiveQuery, Zt as Logger, _n as ExportImportMode, an as compareHLC, at as createImportData, b as makeCrdtTable, bn as SqliteStorageType, bt as DeferredPromise, c as ResetStore, cn as CrdtTableConfig, dn as AiAccess, dt as createCrdtStorage, en as DatabaseIntrospection, et as xxhash, f as SyncedDbDatabase, fn as AnyColumnBuilder, ft as SystemDbConfig, gn as ColumnMeta, gt as applyWorkerDbSchema, h as UnsafeSyncedDbSqlExecutor, hn as ColumnKind, ht as applyMemoryDbSchema, in as HLCCounter, it as createExportData, j as createCrdtSyncProducer, jn as createMigrations, jt as tryCatchAsync, k as WorkerNotificationMessage, kn as MigrationsDb, kt as quoteId, l as SyncedDb, ln as ReadonlyTable, lt as OwnCrdtEvent, m as SyncedDbTransaction, mn as ColumnBuilder, mt as SystemMigrationContext, nn as introspectDb, nt as ImportDataResult, on as deserializeHLC, p as SyncedDbSqlExecutor, pn as AnyTableBuilder, pt as SystemMigration, q as AdmitClientEventsResult, qt as SqliteDriverRows, rn as HLC, rt as SyncedDbExport, s as ResetRequest, sn as serializeHLC, st as CrdtStorage, tn as TableMetadata, tt as ImportDataOptions, u as SyncedDbOptions, un as SyncDbSchema, ut as OwnCrdtSnapshot, vn as InferRow, w as EventsPushRequest, wn as TableOptions, wt as TypedEventTarget, x as CrdtSyncRemoteSource, xn as SyncSchemaTables, xt as DistributiveOmit, yn as PayloadValidationResult, z as formatSchemaVerificationIssues, zn as CrdtUpdateLogItem } from "./index-CH4lf3iX.js";
1
+ import { $ as parametersAreEqual, $t as startPerformanceLogger, A as WorkerState, An as MigrationsDb, At as tryCatch, B as verifySyncSchemaWithDriver, Bn as CrdtUpdateLogItem, C as EventsPullRequest, Cn as TableBuilder, Ct as TypedEvent, Dn as t, Dt as generateId, E as createCrdtSyncRemoteSource, En as WriteOrigin, Et as createTypedEventTarget, F as PendingCrdtEvent, Fn as createStoredValue, Ft as createSQLiteKvStore, G as DefinedSyncSchema, H as NewCrdtEventValidationResult, Hn as PersistedCrdtEvent, Ht as QueryBuilderOutput, I as createCrdtApplyFunction, In as CRDT_EVENT_NO_OP_PAYLOAD, It as ExecuteParams, Jt as SqliteDriverRows, K as defineSyncSchema, Kt as SqliteDriver, L as createSQLiteCrdtApplyFunction, Ln as CrdtEventOrigin, Lt as ExecuteResult, M as CrdtStorageMutator, Mn as createMigrations, Mt as KvStoreItem, N as SnapshotOptions, Nn as createMigrator, O as WorkerConfig, On as MigratableEvent, Ot as jsonSafeParse, P as createCrdtStorageMutator, Pn as StoredValue, Pt as createKvStoreTableQuery, Q as SharedLiveQuerySnapshot, Qt as Logger, R as SchemaVerificationIssue, Rn as CrdtEventStatus, Sn as SyncSchemaTables, St as TypedBroadcastChannel, T as EventsPushResponse, Tn as TableOptions, Tt as createDeferredPromise, U as validateNewCrdtEvent, Un as isNoOpCrdtEventPayload, Ut as SQLiteDbWrapper, V as CrdtEventValidationError, Vn as CrdtUpdateLogPayload, Vt as PreparedStatement, W as DefineSyncSchemaConfig, Wn as dummyKysely, Wt as SQLiteTransactionWrapper, X as SQLiteReactiveDb, Xt as SqliteDriverValue, Y as admitClientEvents, Yt as SqliteDriverStatement, Z as SharedLiveQuery, Zt as LogLevel, _n as ColumnMeta, an as HLCCounter, at as createImportData, b as makeCrdtTable, bn as PayloadValidationResult, bt as DeferredPromise, c as ResetStore, cn as serializeHLC, dn as SyncDbSchema, dt as createCrdtStorage, en as ColumnMetadata, et as xxhash, f as SyncedDbDatabase, fn as AiAccess, ft as SystemDbConfig, gn as ColumnKind, gt as applyWorkerDbSchema, h as UnsafeSyncedDbSqlExecutor, hn as ColumnBuilder, ht as applyMemoryDbSchema, in as HLC, it as createExportData, j as createCrdtSyncProducer, jn as SyncDbMigrator, jt as tryCatchAsync, k as WorkerNotificationMessage, kn as Migrations, kt as quoteId, l as SyncedDb, ln as CrdtTableConfig, lt as OwnCrdtEvent, m as SyncedDbTransaction, mn as AnyTableBuilder, mt as SystemMigrationContext, nn as TableMetadata, nt as ImportDataResult, on as compareHLC, p as SyncedDbSqlExecutor, pn as AnyColumnBuilder, pt as SystemMigration, q as AdmitClientEventsResult, qt as SqliteDriverHooks, rn as introspectDb, rt as SyncedDbExport, s as ResetRequest, sn as deserializeHLC, st as CrdtStorage, tn as DatabaseIntrospection, tt as ImportDataOptions, u as SyncedDbOptions, un as ReadonlyTable, ut as OwnCrdtSnapshot, vn as ExportImportMode, w as EventsPushRequest, wn as TableColumns, wt as TypedEventTarget, x as CrdtSyncRemoteSource, xn as SqliteStorageType, xt as DistributiveOmit, yn as InferRow, z as formatSchemaVerificationIssues, zn as CrdtEventType, zt as KyselyQueryFactory } from "./index-B3s_6aG6.js";
2
2
  export { type AdmitClientEventsResult, type AiAccess, type AnyColumnBuilder, type AnyTableBuilder, CRDT_EVENT_NO_OP_PAYLOAD, ColumnBuilder, type ColumnKind, type ColumnMeta, type ColumnMetadata, type CrdtEventOrigin, type CrdtEventStatus, type CrdtEventType, CrdtEventValidationError, type CrdtStorage, type CrdtStorageMutator, type CrdtSyncRemoteSource, type CrdtTableConfig, type CrdtUpdateLogItem, type CrdtUpdateLogPayload, type DatabaseIntrospection, type DeferredPromise, type DefineSyncSchemaConfig, type DefinedSyncSchema, type DistributiveOmit, type EventsPullRequest, type EventsPushRequest, type EventsPushResponse, type ExecuteParams, type ExecuteResult, type ExportImportMode, type HLC, HLCCounter, type ImportDataOptions, type ImportDataResult, type InferRow, type KvStoreItem, type KyselyQueryFactory, type LogLevel, type Logger, type MigratableEvent, type Migrations, type MigrationsDb, type NewCrdtEventValidationResult, type OwnCrdtEvent, type OwnCrdtSnapshot, type PayloadValidationResult, type PendingCrdtEvent, type PersistedCrdtEvent, type PreparedStatement, type QueryBuilderOutput, type ReadonlyTable, type ResetRequest, type ResetStore, SQLiteDbWrapper, SQLiteReactiveDb, type SQLiteTransactionWrapper, type SchemaVerificationIssue, type SharedLiveQuery, type SharedLiveQuerySnapshot, type SnapshotOptions, type SqliteDriver, type SqliteDriverHooks, type SqliteDriverRows, type SqliteDriverStatement, type SqliteDriverValue, type SqliteStorageType, type StoredValue, type SyncDbMigrator, type SyncDbSchema, type SyncSchemaTables, type SyncedDb, type SyncedDbDatabase, type SyncedDbExport, type SyncedDbOptions, type SyncedDbSqlExecutor, type SyncedDbTransaction, type SystemDbConfig, type SystemMigration, type SystemMigrationContext, TableBuilder, type TableColumns, type TableMetadata, type TableOptions, TypedBroadcastChannel, TypedEvent, type TypedEventTarget, type UnsafeSyncedDbSqlExecutor, type WorkerConfig, type WorkerNotificationMessage, type WorkerState, type WriteOrigin, admitClientEvents, applyMemoryDbSchema, applyWorkerDbSchema, compareHLC, createCrdtApplyFunction, createCrdtStorage, createCrdtStorageMutator, createCrdtSyncProducer, createCrdtSyncRemoteSource, createDeferredPromise, createExportData, createImportData, createKvStoreTableQuery, createMigrations, createMigrator, createSQLiteCrdtApplyFunction, createSQLiteKvStore, createStoredValue, createTypedEventTarget, defineSyncSchema, deserializeHLC, dummyKysely, formatSchemaVerificationIssues, generateId, introspectDb, isNoOpCrdtEventPayload, jsonSafeParse, makeCrdtTable, parametersAreEqual, quoteId, serializeHLC, startPerformanceLogger, t, tryCatch, tryCatchAsync, validateNewCrdtEvent, verifySyncSchemaWithDriver, xxhash };
@@ -1,9 +1,9 @@
1
- import { b as dummyKysely, f as createKvStoreTableQuery, h as SQLiteDbWrapper, i as applyWorkerDbSchema, m as createStoredValue, p as createSQLiteKvStore, r as applyMemoryDbSchema, v as startPerformanceLogger, y as introspectDb } from "./system-schema-CXf3FmxN.js";
1
+ import { b as introspectDb, g as SQLiteDbWrapper, h as createStoredValue, i as applyWorkerDbSchema, m as createSQLiteKvStore, p as createKvStoreTableQuery, r as applyMemoryDbSchema, x as dummyKysely, y as startPerformanceLogger } from "./system-schema-uHQdQTCk.js";
2
2
  import { h as tryCatchAsync, i as TypedEvent, l as generateId, m as tryCatch, n as isNoOpCrdtEventPayload, o as createDeferredPromise, p as quoteId, r as TypedBroadcastChannel, s as createTypedEventTarget, t as CRDT_EVENT_NO_OP_PAYLOAD, u as jsonSafeParse } from "./crdt-table-schema-D6Yf61Ex.js";
3
- import { a as formatSchemaVerificationIssues, c as createMigrator, i as createSQLiteCrdtApplyFunction, l as createExportData, n as createCrdtStorage, o as verifySyncSchemaWithDriver, r as createCrdtApplyFunction, s as createMigrations, t as createCrdtSyncProducer, u as createImportData } from "./crdt-sync-producer-BY-WobgO.js";
3
+ import { a as formatSchemaVerificationIssues, c as createMigrator, i as createSQLiteCrdtApplyFunction, l as createExportData, n as createCrdtStorage, o as verifySyncSchemaWithDriver, r as createCrdtApplyFunction, s as createMigrations, t as createCrdtSyncProducer, u as createImportData } from "./crdt-sync-producer-t2jHxO38.js";
4
4
  import { c as deserializeHLC, i as validateNewCrdtEvent, l as serializeHLC, o as HLCCounter, r as CrdtEventValidationError, s as compareHLC, t as createCrdtSyncRemoteSource, u as xxhash } from "./crdt-sync-remote-source-yeTEkOjH.js";
5
- import { n as parametersAreEqual, t as SQLiteReactiveDb } from "./sqlite-reactive-db-DLS1xRZp.js";
5
+ import { n as parametersAreEqual, t as SQLiteReactiveDb } from "./sqlite-reactive-db-BwY3FoIy.js";
6
6
  import { t as admitClientEvents } from "./admit-client-events-BDrJw4SK.js";
7
- import { a as defineSyncSchema, i as t, n as ColumnBuilder, r as TableBuilder, t as createCrdtStorageMutator } from "./index.common-C8W9VKSY.js";
7
+ import { a as defineSyncSchema, i as t, n as ColumnBuilder, r as TableBuilder, t as createCrdtStorageMutator } from "./index.common-Dp8CNMk4.js";
8
8
  import { a as makeCrdtTable } from "./make-crdt-table-DYCXOYB7.js";
9
9
  export { CRDT_EVENT_NO_OP_PAYLOAD, ColumnBuilder, CrdtEventValidationError, HLCCounter, SQLiteDbWrapper, SQLiteReactiveDb, TableBuilder, TypedBroadcastChannel, TypedEvent, admitClientEvents, applyMemoryDbSchema, applyWorkerDbSchema, compareHLC, createCrdtApplyFunction, createCrdtStorage, createCrdtStorageMutator, createCrdtSyncProducer, createCrdtSyncRemoteSource, createDeferredPromise, createExportData, createImportData, createKvStoreTableQuery, createMigrations, createMigrator, createSQLiteCrdtApplyFunction, createSQLiteKvStore, createStoredValue, createTypedEventTarget, defineSyncSchema, deserializeHLC, dummyKysely, formatSchemaVerificationIssues, generateId, introspectDb, isNoOpCrdtEventPayload, jsonSafeParse, makeCrdtTable, parametersAreEqual, quoteId, serializeHLC, startPerformanceLogger, t, tryCatch, tryCatchAsync, validateNewCrdtEvent, verifySyncSchemaWithDriver, xxhash };
@@ -1,2 +1,2 @@
1
- import { Lt as InternalSQLiteWrapper, Wt as compileKyselyStatement, _ 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, zt as KyselyStatementFactory } from "./index-CH4lf3iX.js";
2
- export { CRDT_CHANGE_INTENTS_TABLE, type CrdtChangeIntent, type InternalSQLiteWrapper, type KyselyStatementFactory, baseSystemMigrations, compileKyselyStatement, crdtViewColumnsFromTable, createCrdtViewStatements, createSystemDbConfig, drainCrdtChangeIntents, runSystemMigrations };
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";
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
- import { a as baseSystemMigrations, g as compileKyselyStatement, l as runSystemMigrations, o as createSystemDbConfig } from "./system-schema-CXf3FmxN.js";
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
2
  import { i as drainCrdtChangeIntents, n as crdtViewColumnsFromTable, r as createCrdtViewStatements, t as CRDT_CHANGE_INTENTS_TABLE } from "./make-crdt-table-DYCXOYB7.js";
3
- export { CRDT_CHANGE_INTENTS_TABLE, baseSystemMigrations, compileKyselyStatement, crdtViewColumnsFromTable, createCrdtViewStatements, createSystemDbConfig, drainCrdtChangeIntents, runSystemMigrations };
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, Gt as SqliteDriver, Jt as SqliteDriverStatement, Kt as SqliteDriverHooks, S as CreateRemoteSourceFactory, T as EventsPushResponse, Yt as SqliteDriverValue, Zt as Logger, l as SyncedDb, qt as SqliteDriverRows, un as SyncDbSchema, w as EventsPushRequest } from "./index-CH4lf3iX.js";
2
- import { t as createWsRemoteSource } from "./ws-remote-source-DFBhDc6Z.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-B3s_6aG6.js";
2
+ import { t as createWsRemoteSource } from "./ws-remote-source-Dgd9dmMl.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,8 +1,8 @@
1
- import { d as createKvStateHashStore, f as createKvStoreTableQuery, n as applyLocalDbSchema, p as createSQLiteKvStore, s as localDbConfig, t as LOCAL_KV_TABLE_NAME } from "./system-schema-CXf3FmxN.js";
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-BY-WobgO.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-DLS1xRZp.js";
5
+ import { t as SQLiteReactiveDb } from "./sqlite-reactive-db-BwY3FoIy.js";
6
6
  import { a as makeCrdtTable, o as registerCrdtIntentDrainer } from "./make-crdt-table-DYCXOYB7.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";
@@ -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,
@@ -109,10 +109,12 @@ async function createLocalSyncedDb(options) {
109
109
  migrator,
110
110
  remoteFactory: options.createRemoteSource
111
111
  });
112
+ let stateSnapshot = remoteSource.getState();
112
113
  remoteSource.addEventListener("state-changed", () => {
114
+ stateSnapshot = remoteSource.getState();
113
115
  notify({
114
116
  notificationType: "state-changed",
115
- state: remoteSource.getState(),
117
+ state: stateSnapshot,
116
118
  storageGeneration: storageGeneration.current
117
119
  });
118
120
  });
@@ -159,7 +161,7 @@ async function createLocalSyncedDb(options) {
159
161
  const syncedDb = {
160
162
  db: createSyncedDbDatabase(reactiveDb),
161
163
  state: {
162
- getState: () => remoteSource.getState(),
164
+ getState: () => stateSnapshot,
163
165
  subscribe: (onChange) => {
164
166
  const { unsubscribe } = eventTarget.addEventListener("state-changed", onChange);
165
167
  return unsubscribe;
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 remoteSource.addEventListener(\"state-changed\", () => {\n notify({\n notificationType: \"state-changed\",\n state: remoteSource.getState(),\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: () => remoteSource.getState(),\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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"}
@@ -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-BY-WobgO.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-CPOi_45B.js.map
220
+ //# sourceMappingURL=reset-state-Bz1sfbf3.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"reset-state-CPOi_45B.js","names":[],"sources":["../src/wasm-driver.ts","../src/schema/verify-sync-schema-wasm.ts","../src/worker-db/worker-common.ts","../src/worker-db/replica-lock.ts","../src/worker-db/reset-state.ts"],"sourcesContent":["import sqlite3InitModule, {\n type BindableValue,\n type Database,\n type Sqlite3Static,\n type SqlValue,\n} from \"@sqlite.org/sqlite-wasm\";\nimport type { SqliteDriver, SqliteDriverStatement, SqliteDriverValue } from \"./sqlite-driver\";\n\nlet sqliteModulePromise: Promise<Sqlite3Static> | null = null;\n\nexport function loadSqliteWasm() {\n sqliteModulePromise ??= sqlite3InitModule();\n return sqliteModulePromise;\n}\n\nexport async function createWasmMemoryDriver() {\n const sqlite3 = await loadSqliteWasm();\n return createWasmSqliteDriver(sqlite3, new sqlite3.oo1.DB({ filename: \":memory:\" }));\n}\n\nexport function createWasmSqliteDriver(sqlite3: Sqlite3Static, db: Database): SqliteDriver {\n const { capi } = sqlite3;\n\n const prepare = (sql: string): SqliteDriverStatement => {\n const statement = db.prepare(sql);\n const pointer = statement.pointer;\n if (!pointer) {\n throw new Error(\"Prepared statement is already finalized\");\n }\n\n return {\n readonly: capi.sqlite3_stmt_readonly(pointer) !== 0,\n run: (parameters) => {\n try {\n if (parameters.length > 0) {\n statement.bind(parameters as BindableValue[]);\n }\n\n const rows: SqliteDriverValue[][] = [];\n let columns: string[] | undefined;\n while (statement.step()) {\n columns ??= statement.getColumnNames([]);\n rows.push(statement.get([]) as SqliteDriverValue[]);\n }\n return { columns: columns ?? [], rows };\n } finally {\n statement.reset(true);\n }\n },\n finalize: () => {\n statement.finalize();\n },\n };\n };\n\n return {\n prepare,\n synchronousHooks: true,\n totalChanges: () => capi.sqlite3_total_changes(db),\n setHooks: (hooks) => {\n if (!hooks) {\n capi.sqlite3_update_hook(db, 0 as never, 0);\n capi.sqlite3_rollback_hook(db, 0, 0);\n capi.sqlite3_commit_hook(db, 0, 0);\n return;\n }\n\n capi.sqlite3_update_hook(db, (_ctx, _opId, _db, table) => hooks.onUpdate(table), 0);\n capi.sqlite3_rollback_hook(\n db,\n () => {\n hooks.onRollback();\n return 0;\n },\n 0,\n );\n capi.sqlite3_commit_hook(\n db,\n () => {\n hooks.onCommit();\n return 0;\n },\n 0,\n );\n },\n serialize: () => capi.sqlite3_js_db_export(db),\n deserialize: (snapshot) => {\n const dataPointer = sqlite3.wasm.allocFromTypedArray(snapshot);\n const resultCode = capi.sqlite3_deserialize(\n db,\n \"main\",\n dataPointer,\n snapshot.byteLength,\n snapshot.byteLength,\n capi.SQLITE_DESERIALIZE_FREEONCLOSE | capi.SQLITE_DESERIALIZE_RESIZEABLE,\n );\n db.checkRc(resultCode);\n },\n createScalarFunction: ({ name, callback, arity, deterministic, directOnly, innocuous }) => {\n db.createFunction({\n name,\n xFunc: (_, ...args) => callback(...(args as SqliteDriverValue[])) as SqlValue,\n arity,\n deterministic,\n directOnly,\n innocuous,\n });\n },\n close: () => {\n db.close();\n },\n };\n}\n","import type { SyncDbSchema } from \"../sqlite-crdt/crdt-schema\";\nimport { createWasmMemoryDriver } from \"../wasm-driver\";\nimport { type SchemaVerificationIssue, verifySyncSchemaWithDriver } from \"./verify-sync-schema\";\n\n/** {@link verifySyncSchemaWithDriver} on a throwaway in-memory sqlite-wasm database. */\nexport async function verifySyncSchema(schema: SyncDbSchema): Promise<SchemaVerificationIssue[]> {\n return verifySyncSchemaWithDriver(schema, await createWasmMemoryDriver());\n}\n","import type { ImportDataOptions, ImportDataResult, SyncedDbExport } from \"../export-import\";\nimport type {\n DeSyncDetectedReason,\n EventsPullRequest,\n EventsPushRequest,\n EventsPushResponse,\n} from \"../sqlite-crdt/crdt-sync-remote-source\";\nimport type { CrdtEventType } from \"../sqlite-crdt/crdt-table-schema\";\nimport type { ExecuteParams, ExecuteResult } from \"../sqlite-db-wrapper\";\nimport { TypedBroadcastChannel } from \"../utils\";\n\nexport const syncDbWorkerLockName = \"sync-db-worker-lock\";\nexport const syncDbClientLockName = \"sync-db-client-lock\";\n\nexport type WorkerNotificationMessage =\n | {\n notificationType: \"new-event-chunk-applied\";\n newSyncId: number;\n /** Worker's state hash after applying up to `newSyncId`. */\n stateHash: string | null;\n }\n | {\n notificationType: \"state-changed\";\n state: WorkerState;\n /** Id of the worker's persisted database. A wiped database gets a new one. */\n storageGeneration: string;\n }\n | {\n notificationType: \"reload-requested\";\n reloadEpoch: string;\n clean: boolean;\n }\n | {\n notificationType: \"de-sync-detected\";\n reason: DeSyncDetectedReason;\n }\n | {\n notificationType: \"remote-schema-version-mismatch\";\n remoteSchemaVersion: number;\n localSchemaVersion: number;\n };\n\nexport type WorkerNotificationEvents = {\n [K in WorkerNotificationMessage[\"notificationType\"]]: Extract<WorkerNotificationMessage, { notificationType: K }>;\n};\n\nexport type WorkerState = {\n remoteState: \"online\" | \"offline\" | \"connecting\";\n deSynced: boolean;\n schemaVersionMismatched: boolean;\n};\n\nexport type GetSnapshotResponse = {\n file: Uint8Array<ArrayBufferLike>;\n syncId: number;\n schemaVersion: number;\n storageGeneration: string;\n};\n\nexport type EventsPullResponse = {\n events: {\n schema_version: number;\n type: CrdtEventType;\n timestamp: string;\n dataset: string;\n item_id: string;\n payload: string;\n }[];\n hasMore: boolean;\n nextSyncId: number;\n};\n\nexport interface WorkerRpc {\n getSnapshot: () => GetSnapshotResponse;\n importData: (data: SyncedDbExport, opts?: ImportDataOptions) => ImportDataResult | Promise<ImportDataResult>;\n pushTabEvents: (request: EventsPushRequest) => EventsPushResponse;\n execute: (query: ExecuteParams) => ExecuteResult<unknown>;\n pullEvents: (params: EventsPullRequest) => EventsPullResponse;\n postState: () => void;\n goOnline: () => Promise<void>;\n goOffline: () => void;\n dropConnection: () => void;\n sync: () => void;\n requestReload: (options: { clean: boolean }) => Promise<void>;\n}\n\nexport type WorkerRequestMethod = keyof WorkerRpc;\n\nexport type WorkerRequestMessage<TMethod extends WorkerRequestMethod = WorkerRequestMethod> = {\n type: \"request\";\n requestId: string;\n method: TMethod;\n args: Parameters<WorkerRpc[TMethod]>;\n};\n\nexport type WorkerResponseMessage<TMethod extends WorkerRequestMethod = WorkerRequestMethod> = {\n type: \"response\";\n requestId: string;\n data: ReturnType<WorkerRpc[TMethod]>;\n};\n\nexport type WorkerErrorResponseMessage = {\n type: \"error-response\";\n requestId: string;\n error: string;\n};\n\nexport type AsyncRpc<T> = {\n [K in keyof T]: T[K] extends (...args: infer U) => infer V ? (...args: U) => Promise<Awaited<V>> : never;\n};\n\nexport const broadcastChannelNames = {\n requests: \"sync-db-worker-requests\",\n responses: \"sync-db-worker-responses\",\n} as const;\n\nexport type WorkerBroadcastChannels = {\n requests: TypedBroadcastChannel<WorkerRequestMessage>;\n responses: TypedBroadcastChannel<WorkerResponseMessage | WorkerErrorResponseMessage | WorkerNotificationMessage>;\n};\n\nexport const createBroadcastChannels = (prefix: string): WorkerBroadcastChannels => {\n return {\n requests: new TypedBroadcastChannel(`${prefix}-${broadcastChannelNames.requests}`),\n responses: new TypedBroadcastChannel(`${prefix}-${broadcastChannelNames.responses}`),\n };\n};\n\nexport type WorkerConfig<Props = any> = {\n dbId: string;\n clientId: string;\n props: Props;\n};\n\nexport type WorkerInitMessage = {\n type: \"init\";\n config: WorkerConfig;\n};\n\nexport function isWorkerInitMessage(message: unknown): message is WorkerInitMessage {\n return typeof message === \"object\" && message !== null && \"type\" in message && message.type === \"init\";\n}\n\nexport function isWorkerRequestMessage(message: unknown): message is WorkerRequestMessage {\n return typeof message === \"object\" && message !== null && \"type\" in message && message.type === \"request\";\n}\n\nexport function isWorkerResponseMessage(message: unknown): message is WorkerResponseMessage {\n return (\n typeof message === \"object\" && message !== null && \"type\" in message && \"requestId\" in message && \"data\" in message\n );\n}\n\nexport function isWorkerErrorResponseMessage(message: unknown): message is WorkerErrorResponseMessage {\n return typeof message === \"object\" && message !== null && \"type\" in message && message.type === \"error-response\";\n}\n\nexport function isWorkerNotificationMessage(message: unknown): message is WorkerNotificationMessage {\n return typeof message === \"object\" && message !== null && \"notificationType\" in message && !!message.notificationType;\n}\n","import { acquireLock } from \"../utils\";\n\nconst replicaLockName = (dbId: string) => `sync-db-replica-lock-${dbId}`;\n\nexport function acquireReplicaLock({ dbId }: { dbId: string }) {\n return acquireLock(replicaLockName(dbId), { mode: \"shared\" });\n}\n\nexport function runWithNoActiveReplicas({ dbId, run }: { dbId: string; run: () => void | Promise<void> }) {\n return navigator.locks.request(replicaLockName(dbId), { mode: \"exclusive\", ifAvailable: true }, async (lock) => {\n if (!lock) {\n return false;\n }\n await run();\n return true;\n });\n}\n","import { createStore, del, get, set } from \"idb-keyval\";\nimport { generateId } from \"../utils\";\nimport type { WorkerNotificationMessage } from \"./worker-common\";\n\nexport type ResetRequest = {\n epoch: string;\n requestedAt: number;\n};\n\n/** Durable async key-value storage for reset state. Injectable for tests. */\nexport type ResetStore = {\n get: <T>(key: string) => Promise<T | undefined>;\n set: (key: string, value: unknown) => Promise<void>;\n delete: (key: string) => Promise<void>;\n};\n\n/** Default IndexedDB-backed reset store (idb-keyval over a dedicated database). */\nexport function createIdbResetStore(): ResetStore {\n const store = createStore(\"sqlite-sync\", \"kv\");\n return {\n get: (key) => get(key, store),\n set: (key, value) => set(key, value, store),\n delete: (key) => del(key, store),\n };\n}\n\n/**\n * A clean reset is a recovery action for a de-sync detected now. If the reload\n * never happens (broadcast lost, tab crashed mid-reload, browser killed the page),\n * the request must not fire on an arbitrary later cold start and silently wipe\n * local-only writes accumulated since.\n */\nexport const RESET_REQUEST_TTL_MS = 10 * 60 * 1000; // 10 minutes\n\nconst resetRequestKey = (dbId: string) => `sqlite-sync-reset-request-${dbId}`;\nconst resetAppliedKey = (dbId: string) => `sqlite-sync-reset-applied-${dbId}`;\n\ntype ResetStateStoreOptions = {\n store: ResetStore;\n dbId: string;\n now?: () => number;\n};\n\nexport type ResetStateStore = ReturnType<typeof createResetStateStore>;\n\n/**\n * Worker-owned durable reset state. The reset decision must be owned by the\n * elected worker, not by tabs during `createSyncedDb` — otherwise a later\n * worker election could repeat an already-applied wipe.\n */\nexport function createResetStateStore({ store, dbId, now = () => Date.now() }: ResetStateStoreOptions) {\n const requestKey = resetRequestKey(dbId);\n const appliedKey = resetAppliedKey(dbId);\n\n return {\n async writeResetRequest(epoch: string): Promise<ResetRequest> {\n const request: ResetRequest = { epoch, requestedAt: now() };\n await store.set(requestKey, request);\n return request;\n },\n /**\n * Read the pending reset request after winning the worker election.\n * Returns the request only when it has not been applied yet and is within\n * the TTL. Stale requests are deleted so they cannot fire on a later cold start.\n */\n async resolvePendingReset(): Promise<ResetRequest | undefined> {\n const request = await store.get<ResetRequest>(requestKey);\n if (!request) {\n return undefined;\n }\n\n if (now() - request.requestedAt > RESET_REQUEST_TTL_MS) {\n await store.delete(requestKey);\n return undefined;\n }\n\n const appliedEpoch = await store.get<string>(appliedKey);\n if (request.epoch === appliedEpoch) {\n return undefined;\n }\n\n return request;\n },\n /**\n * Record the epoch as applied. Must be called only after the worker has\n * successfully initialized with `clearOnInit: true`, so a failed init can\n * be retried by a later elected worker.\n */\n async markResetApplied(epoch: string): Promise<void> {\n await store.set(appliedKey, epoch);\n },\n };\n}\n\ntype ReloadRequestHandlerOptions = {\n resetState: ResetStateStore;\n broadcast: (message: WorkerNotificationMessage) => void;\n generateEpoch?: () => string;\n};\n\n/**\n * Worker-side `requestReload` RPC implementation. For `clean: true` the reset\n * request is durably stored before broadcasting and before the RPC resolves,\n * so the epoch survives no matter which path triggers the reload.\n */\nexport function createReloadRequestHandler({\n resetState,\n broadcast,\n generateEpoch = generateId,\n}: ReloadRequestHandlerOptions) {\n return async (options: { clean: boolean }): Promise<void> => {\n const reloadEpoch = generateEpoch();\n\n if (options.clean) {\n await resetState.writeResetRequest(reloadEpoch);\n }\n\n broadcast({\n notificationType: \"reload-requested\",\n reloadEpoch,\n clean: options.clean,\n });\n };\n}\n"],"mappings":";;;;;AAQA,IAAI,sBAAqD;AAEzD,SAAgB,iBAAiB;CAC/B,wBAAwB,kBAAkB;CAC1C,OAAO;AACT;AAEA,eAAsB,yBAAyB;CAC7C,MAAM,UAAU,MAAM,eAAe;CACrC,OAAO,uBAAuB,SAAS,IAAI,QAAQ,IAAI,GAAG,EAAE,UAAU,WAAW,CAAC,CAAC;AACrF;AAEA,SAAgB,uBAAuB,SAAwB,IAA4B;CACzF,MAAM,EAAE,SAAS;CAEjB,MAAM,WAAW,QAAuC;EACtD,MAAM,YAAY,GAAG,QAAQ,GAAG;EAChC,MAAM,UAAU,UAAU;EAC1B,IAAI,CAAC,SACH,MAAM,IAAI,MAAM,yCAAyC;EAG3D,OAAO;GACL,UAAU,KAAK,sBAAsB,OAAO,MAAM;GAClD,MAAM,eAAe;IACnB,IAAI;KACF,IAAI,WAAW,SAAS,GACtB,UAAU,KAAK,UAA6B;KAG9C,MAAM,OAA8B,CAAC;KACrC,IAAI;KACJ,OAAO,UAAU,KAAK,GAAG;MACvB,YAAY,UAAU,eAAe,CAAC,CAAC;MACvC,KAAK,KAAK,UAAU,IAAI,CAAC,CAAC,CAAwB;KACpD;KACA,OAAO;MAAE,SAAS,WAAW,CAAC;MAAG;KAAK;IACxC,UAAU;KACR,UAAU,MAAM,IAAI;IACtB;GACF;GACA,gBAAgB;IACd,UAAU,SAAS;GACrB;EACF;CACF;CAEA,OAAO;EACL;EACA,kBAAkB;EAClB,oBAAoB,KAAK,sBAAsB,EAAE;EACjD,WAAW,UAAU;GACnB,IAAI,CAAC,OAAO;IACV,KAAK,oBAAoB,IAAI,GAAY,CAAC;IAC1C,KAAK,sBAAsB,IAAI,GAAG,CAAC;IACnC,KAAK,oBAAoB,IAAI,GAAG,CAAC;IACjC;GACF;GAEA,KAAK,oBAAoB,KAAK,MAAM,OAAO,KAAK,UAAU,MAAM,SAAS,KAAK,GAAG,CAAC;GAClF,KAAK,sBACH,UACM;IACJ,MAAM,WAAW;IACjB,OAAO;GACT,GACA,CACF;GACA,KAAK,oBACH,UACM;IACJ,MAAM,SAAS;IACf,OAAO;GACT,GACA,CACF;EACF;EACA,iBAAiB,KAAK,qBAAqB,EAAE;EAC7C,cAAc,aAAa;GACzB,MAAM,cAAc,QAAQ,KAAK,oBAAoB,QAAQ;GAC7D,MAAM,aAAa,KAAK,oBACtB,IACA,QACA,aACA,SAAS,YACT,SAAS,YACT,KAAK,iCAAiC,KAAK,6BAC7C;GACA,GAAG,QAAQ,UAAU;EACvB;EACA,uBAAuB,EAAE,MAAM,UAAU,OAAO,eAAe,YAAY,gBAAgB;GACzF,GAAG,eAAe;IAChB;IACA,QAAQ,GAAG,GAAG,SAAS,SAAS,GAAI,IAA4B;IAChE;IACA;IACA;IACA;GACF,CAAC;EACH;EACA,aAAa;GACX,GAAG,MAAM;EACX;CACF;AACF;;;;AC3GA,eAAsB,iBAAiB,QAA0D;CAC/F,OAAO,2BAA2B,QAAQ,MAAM,uBAAuB,CAAC;AAC1E;;;ACIA,MAAa,uBAAuB;AACpC,MAAa,uBAAuB;AAmGpC,MAAa,wBAAwB;CACnC,UAAU;CACV,WAAW;AACb;AAOA,MAAa,2BAA2B,WAA4C;CAClF,OAAO;EACL,UAAU,IAAI,sBAAsB,GAAG,OAAO,GAAG,sBAAsB,UAAU;EACjF,WAAW,IAAI,sBAAsB,GAAG,OAAO,GAAG,sBAAsB,WAAW;CACrF;AACF;AAaA,SAAgB,oBAAoB,SAAgD;CAClF,OAAO,OAAO,YAAY,YAAY,YAAY,QAAQ,UAAU,WAAW,QAAQ,SAAS;AAClG;AAEA,SAAgB,uBAAuB,SAAmD;CACxF,OAAO,OAAO,YAAY,YAAY,YAAY,QAAQ,UAAU,WAAW,QAAQ,SAAS;AAClG;AAEA,SAAgB,wBAAwB,SAAoD;CAC1F,OACE,OAAO,YAAY,YAAY,YAAY,QAAQ,UAAU,WAAW,eAAe,WAAW,UAAU;AAEhH;AAEA,SAAgB,6BAA6B,SAAyD;CACpG,OAAO,OAAO,YAAY,YAAY,YAAY,QAAQ,UAAU,WAAW,QAAQ,SAAS;AAClG;AAEA,SAAgB,4BAA4B,SAAwD;CAClG,OAAO,OAAO,YAAY,YAAY,YAAY,QAAQ,sBAAsB,WAAW,CAAC,CAAC,QAAQ;AACvG;;;AC7JA,MAAM,mBAAmB,SAAiB,wBAAwB;AAElE,SAAgB,mBAAmB,EAAE,QAA0B;CAC7D,OAAO,YAAY,gBAAgB,IAAI,GAAG,EAAE,MAAM,SAAS,CAAC;AAC9D;AAEA,SAAgB,wBAAwB,EAAE,MAAM,OAA0D;CACxG,OAAO,UAAU,MAAM,QAAQ,gBAAgB,IAAI,GAAG;EAAE,MAAM;EAAa,aAAa;CAAK,GAAG,OAAO,SAAS;EAC9G,IAAI,CAAC,MACH,OAAO;EAET,MAAM,IAAI;EACV,OAAO;CACT,CAAC;AACH;;;;ACCA,SAAgB,sBAAkC;CAChD,MAAM,QAAQ,YAAY,eAAe,IAAI;CAC7C,OAAO;EACL,MAAM,QAAQ,IAAI,KAAK,KAAK;EAC5B,MAAM,KAAK,UAAU,IAAI,KAAK,OAAO,KAAK;EAC1C,SAAS,QAAQ,IAAI,KAAK,KAAK;CACjC;AACF;;;;;;;AAQA,MAAa,uBAAuB;AAEpC,MAAM,mBAAmB,SAAiB,6BAA6B;AACvE,MAAM,mBAAmB,SAAiB,6BAA6B;;;;;;AAevE,SAAgB,sBAAsB,EAAE,OAAO,MAAM,YAAY,KAAK,IAAI,KAA6B;CACrG,MAAM,aAAa,gBAAgB,IAAI;CACvC,MAAM,aAAa,gBAAgB,IAAI;CAEvC,OAAO;EACL,MAAM,kBAAkB,OAAsC;GAC5D,MAAM,UAAwB;IAAE;IAAO,aAAa,IAAI;GAAE;GAC1D,MAAM,MAAM,IAAI,YAAY,OAAO;GACnC,OAAO;EACT;;;;;;EAMA,MAAM,sBAAyD;GAC7D,MAAM,UAAU,MAAM,MAAM,IAAkB,UAAU;GACxD,IAAI,CAAC,SACH;GAGF,IAAI,IAAI,IAAI,QAAQ,cAAA,KAAoC;IACtD,MAAM,MAAM,OAAO,UAAU;IAC7B;GACF;GAEA,MAAM,eAAe,MAAM,MAAM,IAAY,UAAU;GACvD,IAAI,QAAQ,UAAU,cACpB;GAGF,OAAO;EACT;;;;;;EAMA,MAAM,iBAAiB,OAA8B;GACnD,MAAM,MAAM,IAAI,YAAY,KAAK;EACnC;CACF;AACF;;;;;;AAaA,SAAgB,2BAA2B,EACzC,YACA,WACA,gBAAgB,cACc;CAC9B,OAAO,OAAO,YAA+C;EAC3D,MAAM,cAAc,cAAc;EAElC,IAAI,QAAQ,OACV,MAAM,WAAW,kBAAkB,WAAW;EAGhD,UAAU;GACR,kBAAkB;GAClB;GACA,OAAO,QAAQ;EACjB,CAAC;CACH;AACF"}
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 { An as SyncDbMigrator, D as EventsPullResponse, Dn as MigratableEvent, J as ClientDatasetRejectionReason, Ot as jsonSafeParse, T as EventsPushResponse, un as SyncDbSchema } from "./index-CH4lf3iX.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-B3s_6aG6.js";
2
2
  import { z } from "zod";
3
3
  //#region src/server/admit-client-push.d.ts
4
4
  type PushedCrdtEvent = MigratableEvent & {
@@ -1,4 +1,4 @@
1
- import { _ as BoundMap, h as SQLiteDbWrapper, v as startPerformanceLogger } from "./system-schema-CXf3FmxN.js";
1
+ import { g as SQLiteDbWrapper, v as BoundMap, y as startPerformanceLogger } from "./system-schema-uHQdQTCk.js";
2
2
  import { s as createTypedEventTarget } from "./crdt-table-schema-D6Yf61Ex.js";
3
3
  //#region src/memory-db/sqlite-reactive-db.ts
4
4
  var SQLiteReactiveDb = class SQLiteReactiveDb {
@@ -293,4 +293,4 @@ function parametersAreEqual(a, b) {
293
293
  //#endregion
294
294
  export { parametersAreEqual as n, SQLiteReactiveDb as t };
295
295
 
296
- //# sourceMappingURL=sqlite-reactive-db-DLS1xRZp.js.map
296
+ //# sourceMappingURL=sqlite-reactive-db-BwY3FoIy.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"sqlite-reactive-db-DLS1xRZp.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 \"transaction-committed\": undefined;\n \"transaction-rolled-back\": undefined;\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\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 this.eventTarget.dispatchEvent(\"transaction-rolled-back\", undefined);\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.eventTarget.dispatchEvent(\"transaction-committed\", undefined);\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\nfunction createDebouncedCallback<TArgs extends unknown[]>(callback: (...args: TArgs) => void, delay: number) {\n let timeout: unknown | null = null;\n let shouldCallWithoutDelay = true;\n\n return (...args: TArgs) => {\n if (shouldCallWithoutDelay) {\n callback(...args);\n shouldCallWithoutDelay = false;\n return;\n }\n\n const effect = () => {\n timeout = null;\n shouldCallWithoutDelay = true;\n return callback(...args);\n };\n\n if (timeout) {\n clearTimeout(timeout as any);\n }\n\n timeout = setTimeout(effect, delay);\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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"}
1
+ {"version":3,"file":"sqlite-reactive-db-BwY3FoIy.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 \"transaction-committed\": undefined;\n \"transaction-rolled-back\": undefined;\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\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 this.eventTarget.dispatchEvent(\"transaction-rolled-back\", undefined);\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.eventTarget.dispatchEvent(\"transaction-committed\", undefined);\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\nfunction createDebouncedCallback<TArgs extends unknown[]>(callback: (...args: TArgs) => void, delay: number) {\n let timeout: unknown | null = null;\n let shouldCallWithoutDelay = true;\n\n return (...args: TArgs) => {\n if (shouldCallWithoutDelay) {\n callback(...args);\n shouldCallWithoutDelay = false;\n return;\n }\n\n const effect = () => {\n timeout = null;\n shouldCallWithoutDelay = true;\n return callback(...args);\n };\n\n if (timeout) {\n clearTimeout(timeout as any);\n }\n\n timeout = setTimeout(effect, delay);\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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"}
@@ -446,7 +446,7 @@ function createSQLiteKvStore({ db, metaTableName }) {
446
446
  })
447
447
  };
448
448
  }
449
- const STATE_HASH_KV_KEY = "crdt.consistency.state_hash.v1";
449
+ const STATE_HASH_KV_KEY = "crdt.consistency.state_hash.v2";
450
450
  function createKvStateHashStore(kvStore) {
451
451
  return {
452
452
  read: () => kvStore.get(STATE_HASH_KV_KEY),
@@ -539,6 +539,19 @@ FROM (
539
539
  WHERE "latest"."dataset" = "log"."dataset" AND "latest"."item_id" = "log"."item_id"
540
540
  AND "latest"."timestamp" > coalesce(json_extract("log"."payload", '$.id'), '')`);
541
541
  }
542
+ },
543
+ {
544
+ version: 6,
545
+ up: (ctx) => {
546
+ const updateLog = quoteId(ctx.updateLogTable.fullIdentifier);
547
+ const isStale = `json_extract("payload", '$.tombstone') < json_extract("payload", '$.id')`;
548
+ const { rows } = ctx.execute(`SELECT DISTINCT "log"."dataset" AS "dataset" FROM ${updateLog} AS "log"
549
+ JOIN "sqlite_master" AS "master" ON "master"."type" = 'table' AND "master"."name" = "log"."dataset"
550
+ WHERE ${isStale}`);
551
+ for (const { dataset } of rows) ctx.execute(`UPDATE ${quoteId(dataset)} SET "tombstone" = 0
552
+ WHERE "tombstone" <> 0 AND "id" IN (SELECT "item_id" FROM ${updateLog} WHERE "dataset" = ? AND ${isStale})`, [dataset]);
553
+ ctx.execute(`UPDATE ${updateLog} SET "payload" = json_set("payload", '$.tombstone', json_extract("payload", '$.id')) WHERE ${isStale}`);
554
+ }
542
555
  }
543
556
  ];
544
557
  function runSystemMigrations(opts) {
@@ -573,7 +586,10 @@ function applySystemDbSchema(db, { kvTableName, dbConfig }) {
573
586
  migrations: baseSystemMigrations,
574
587
  version: kvStore.createNumberStoredValue("internal-schema-version", -1),
575
588
  dbConfig,
576
- execute: (sql) => db.execute(sql, { loggerLevel: "system" }),
589
+ execute: (sql, parameters = []) => db.execute({
590
+ sql,
591
+ parameters
592
+ }, { loggerLevel: "system" }),
577
593
  transaction: (callback) => db.executeTransaction(callback)
578
594
  });
579
595
  return { kvStore };
@@ -602,6 +618,6 @@ function applyMemoryDbSchema(db) {
602
618
  )`, { loggerLevel: "system" });
603
619
  }
604
620
  //#endregion
605
- export { BoundMap as _, baseSystemMigrations as a, dummyKysely as b, memoryDbConfig as c, createKvStateHashStore as d, createKvStoreTableQuery as f, compileKyselyStatement as g, SQLiteDbWrapper as h, applyWorkerDbSchema as i, runSystemMigrations as l, createStoredValue as m, applyLocalDbSchema as n, createSystemDbConfig as o, createSQLiteKvStore as p, applyMemoryDbSchema as r, localDbConfig as s, LOCAL_KV_TABLE_NAME as t, workerDbConfig as u, startPerformanceLogger as v, introspectDb as y };
621
+ export { compileKyselyStatement as _, baseSystemMigrations as a, introspectDb as b, memoryDbConfig as c, STATE_HASH_KV_KEY as d, createKvStateHashStore as f, SQLiteDbWrapper as g, createStoredValue as h, applyWorkerDbSchema as i, runSystemMigrations as l, createSQLiteKvStore as m, applyLocalDbSchema as n, createSystemDbConfig as o, createKvStoreTableQuery as p, applyMemoryDbSchema as r, localDbConfig as s, LOCAL_KV_TABLE_NAME as t, workerDbConfig as u, BoundMap as v, dummyKysely as x, startPerformanceLogger as y };
606
622
 
607
- //# sourceMappingURL=system-schema-CXf3FmxN.js.map
623
+ //# sourceMappingURL=system-schema-uHQdQTCk.js.map