@sqlite-sync/core 0.1.1 → 0.2.0

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.
package/dist/worker.js CHANGED
@@ -1,24 +1,25 @@
1
1
  import {
2
2
  SQLiteDbWrapper,
3
- applyKyselyEventsBatchFilters,
4
3
  applyWorkerDbSchema,
5
4
  createBroadcastChannels,
6
5
  createCrdtStorage,
7
6
  createCrdtSyncProducer,
8
7
  createCrdtSyncRemoteSource,
8
+ createIdbResetStore,
9
9
  createMigrator,
10
- createSQLiteCrdtApplyFunction,
11
- createSQLiteKvStore,
12
- createStoredValue,
10
+ createReloadRequestHandler,
11
+ createResetStateStore,
13
12
  isWorkerInitMessage,
14
13
  isWorkerRequestMessage,
15
14
  syncDbClientLockName,
16
- syncDbWorkerLockName
17
- } from "./chunk-627DSM2Q.js";
15
+ syncDbWorkerLockName,
16
+ workerDbConfig,
17
+ xxhash
18
+ } from "./chunk-O4WYEB4H.js";
18
19
  import {
19
20
  createDeferredPromise,
20
21
  jsonSafeParse
21
- } from "./chunk-UGF5IU53.js";
22
+ } from "./chunk-NHT3ELMN.js";
22
23
 
23
24
  // src/web-socket/ws-remote-source.ts
24
25
  var createWsRemoteSource = ({ createWebSocket }) => {
@@ -87,7 +88,7 @@ var createWsRemoteSource = ({ createWebSocket }) => {
87
88
  break;
88
89
  }
89
90
  case "events-applied":
90
- onEventsAvailable(message.newSyncId);
91
+ onEventsAvailable({ newSyncId: message.newSyncId, remoteEventHlcSum: message.eventHlcSum });
91
92
  break;
92
93
  default:
93
94
  message;
@@ -106,6 +107,34 @@ var createWsRemoteSource = ({ createWebSocket }) => {
106
107
 
107
108
  // src/worker-db/db-worker.ts
108
109
  import sqlite3InitModule from "@sqlite.org/sqlite-wasm";
110
+
111
+ // src/worker-db/storage-version.ts
112
+ var LIB_STORAGE_VERSION = 1;
113
+ var storageVersionKey = (dbId) => `sqlite-sync-storage-version-${dbId}`;
114
+ function formatStorageVersion(appStorageVersion) {
115
+ return appStorageVersion === void 0 ? `lib-v${LIB_STORAGE_VERSION}` : `lib-v${LIB_STORAGE_VERSION}:app-${appStorageVersion}`;
116
+ }
117
+ function createStorageVersionStore({ store, dbId, appStorageVersion }) {
118
+ const key = storageVersionKey(dbId);
119
+ const currentVersion = formatStorageVersion(appStorageVersion);
120
+ return {
121
+ currentVersion,
122
+ async isVersionMismatch() {
123
+ const storedVersion = await store.get(key);
124
+ return storedVersion !== currentVersion;
125
+ },
126
+ /**
127
+ * Record the current version. Must be called only after the worker has
128
+ * successfully initialized with the wiped DB, so a failed init can be
129
+ * retried by a later elected worker.
130
+ */
131
+ async markCurrentVersionApplied() {
132
+ await store.set(key, currentVersion);
133
+ }
134
+ };
135
+ }
136
+
137
+ // src/worker-db/db-worker.ts
109
138
  var defaultLogger = (type, message, level = "info") => {
110
139
  const logMessage = `[${type}] ${message}`;
111
140
  switch (level) {
@@ -126,11 +155,28 @@ var defaultLogger = (type, message, level = "info") => {
126
155
  async function createDbWorker(config, opts) {
127
156
  const broadcastChannels = createBroadcastChannels(config.dbId);
128
157
  const logger = opts.logger ?? defaultLogger;
129
- const sqlite3 = await sqlite3InitModule();
158
+ const [sqlite3] = await Promise.all([sqlite3InitModule(), xxhash.ensureLoaded()]);
159
+ const resetStore = opts.resetStore ?? createIdbResetStore();
160
+ const resetState = createResetStateStore({
161
+ store: resetStore,
162
+ dbId: config.dbId
163
+ });
164
+ const storageVersion = createStorageVersionStore({
165
+ store: resetStore,
166
+ dbId: config.dbId,
167
+ appStorageVersion: opts.storageVersion
168
+ });
169
+ const [pendingReset, isVersionMismatch] = await Promise.all([
170
+ resetState.resolvePendingReset(),
171
+ storageVersion.isVersionMismatch()
172
+ ]);
173
+ if (isVersionMismatch) {
174
+ logger("worker", `Storage version mismatch \u2014 resetting local DB to ${storageVersion.currentVersion}`, "warning");
175
+ }
130
176
  const pool = await sqlite3.installOpfsSAHPoolVfs({
131
177
  name: config.dbId,
132
178
  directory: `.${config.dbId}`,
133
- clearOnInit: config.clearOnInit,
179
+ clearOnInit: !!pendingReset || isVersionMismatch,
134
180
  initialCapacity: 8
135
181
  });
136
182
  const db = new SQLiteDbWrapper({
@@ -146,15 +192,11 @@ async function createDbWorker(config, opts) {
146
192
  db.execute("PRAGMA worker.locking_mode=exclusive", { loggerLevel: "system" });
147
193
  db.execute("PRAGMA worker.journal_mode=WAL", { loggerLevel: "system" });
148
194
  db.execute("PRAGMA worker.synchronous=NORMAL", { loggerLevel: "system" });
149
- applyWorkerDbSchema(db);
150
- const kvStore = createSQLiteKvStore({
151
- db,
152
- metaTableName: "worker.kv"
153
- });
195
+ const { kvStore } = applyWorkerDbSchema(db);
154
196
  const migrator = createMigrator({
155
197
  migrations: opts.syncDbSchema.migrations,
156
198
  schemaVersion: kvStore.createNumberStoredValue("schema-version", -1),
157
- updateLogTableName: '"crdt_update_log"'
199
+ updateLogTableName: workerDbConfig.updateLogTable.fullIdentifier
158
200
  });
159
201
  migrator.migrateDbToLatest({
160
202
  startTransaction: (callback) => {
@@ -162,12 +204,15 @@ async function createDbWorker(config, opts) {
162
204
  }
163
205
  });
164
206
  db.invalidateDbSchema();
165
- const localSyncId = createStoredValue({
166
- initialValue: getLatestSyncId(db)
167
- });
207
+ if (pendingReset) {
208
+ await resetState.markResetApplied(pendingReset.epoch);
209
+ }
210
+ if (isVersionMismatch) {
211
+ await storageVersion.markCurrentVersionApplied();
212
+ }
168
213
  const crdtStorage = createCrdtStorage({
169
214
  nodeId: config.clientId,
170
- syncId: localSyncId,
215
+ initialLocalSyncId: getMaxSyncId(db, "none"),
171
216
  migrator,
172
217
  hlc: {
173
218
  getNextHLC() {
@@ -177,32 +222,20 @@ async function createDbWorker(config, opts) {
177
222
  return;
178
223
  }
179
224
  },
180
- transaction: (callback) => db.executeTransaction(callback),
181
- handleCrdtEventApply: createSQLiteCrdtApplyFunction({
182
- db,
183
- updateLogTableName: "crdt_update_log"
184
- }),
185
- persistEvent: (event) => persistEvent(db, event),
186
- getEventsBatch: (opts2) => getEventsBatch(db, opts2),
187
- updateEvent: (syncId, update) => updateEvent(db, syncId, update)
225
+ db,
226
+ dbConfig: workerDbConfig,
227
+ eventHlcAccumulator: kvStore.createStringStoredValue("crdt.consistency.event_hlc_sum.v2", "")
188
228
  });
189
229
  createCrdtSyncProducer({
190
230
  storage: crdtStorage,
191
231
  broadcastEvents: (chunk) => {
192
232
  broadcastChannels.responses.postMessage({
193
233
  notificationType: "new-event-chunk-applied",
194
- newSyncId: chunk.newSyncId
234
+ newSyncId: chunk.newSyncId,
235
+ eventHlcSum: chunk.eventHlcSum
195
236
  });
196
237
  }
197
238
  });
198
- const postState = () => {
199
- broadcastChannels.responses.postMessage({
200
- notificationType: "state-changed",
201
- state: {
202
- remoteState: remoteSource.getState()
203
- }
204
- });
205
- };
206
239
  const remoteSource = createRemoteSource({
207
240
  kvStore,
208
241
  crdtStorage,
@@ -211,38 +244,72 @@ async function createDbWorker(config, opts) {
211
244
  remoteFactory: opts.createRemoteSource
212
245
  });
213
246
  remoteSource.goOnline();
214
- remoteSource.addEventListener("state-changed", () => {
247
+ const broadcastNotification = (notification) => {
248
+ broadcastChannels.responses.postMessage(notification);
249
+ };
250
+ const postState = () => {
251
+ broadcastNotification({
252
+ notificationType: "state-changed",
253
+ state: {
254
+ remoteState: remoteSource.getState()
255
+ }
256
+ });
257
+ };
258
+ const stateChangedSubscription = remoteSource.addEventListener("state-changed", () => {
215
259
  postState();
216
260
  });
261
+ const deSyncDetectedSubscription = remoteSource.addEventListener("de-sync-detected", (event) => {
262
+ broadcastNotification({
263
+ notificationType: "de-sync-detected",
264
+ reason: event.payload.reason
265
+ });
266
+ });
267
+ const remoteSchemaVersionMismatchSubscription = remoteSource.addEventListener(
268
+ "remote-schema-version-mismatch",
269
+ (event) => {
270
+ broadcastNotification({
271
+ notificationType: "remote-schema-version-mismatch",
272
+ remoteSchemaVersion: event.payload.remoteSchemaVersion,
273
+ localSchemaVersion: event.payload.localSchemaVersion
274
+ });
275
+ }
276
+ );
217
277
  const rpcTarget = {
218
278
  execute: (query) => db.execute(query),
219
279
  getSnapshot: () => {
280
+ const appliedSyncId = getMaxSyncId(db, "pending");
220
281
  db.execute("PRAGMA journal_mode=off", { loggerLevel: "system" });
221
282
  const file = db.createSnapshot();
222
283
  db.execute("PRAGMA journal_mode=WAL", { loggerLevel: "system" });
223
284
  return {
224
285
  file,
225
- syncId: localSyncId.current,
286
+ syncId: appliedSyncId,
226
287
  schemaVersion: migrator.currentSchemaVersion
227
288
  };
228
289
  },
229
290
  postState,
230
291
  pushTabEvents: (request) => {
231
- crdtStorage.enqueueLocalEvents(request.events, request.nodeId);
292
+ const { beforeSyncId, afterSyncId } = crdtStorage.enqueueLocalEvents(request.events, request.nodeId);
232
293
  return {
233
- ok: true
294
+ ok: true,
295
+ beforeSyncId,
296
+ afterSyncId
234
297
  };
235
298
  },
236
299
  pullEvents: (request) => {
237
300
  return crdtStorage.getEventsBatch({
238
301
  afterSyncId: request.afterSyncId,
239
302
  status: "applied",
240
- excludeNodeId: request.excludeNodeId,
303
+ excludeNodeId: request.excludeNodeId ?? "",
241
304
  limit: 100
242
305
  });
243
306
  },
244
307
  goOnline: () => remoteSource.goOnline(),
245
- goOffline: () => remoteSource.goOffline("DISCONNECTED")
308
+ goOffline: () => remoteSource.goOffline("DISCONNECTED"),
309
+ requestReload: createReloadRequestHandler({
310
+ resetState,
311
+ broadcast: broadcastNotification
312
+ })
246
313
  };
247
314
  broadcastChannels.requests.onmessage = (event) => {
248
315
  const message = event.data;
@@ -283,6 +350,9 @@ async function createDbWorker(config, opts) {
283
350
  };
284
351
  rpcTarget.postState();
285
352
  return async () => {
353
+ stateChangedSubscription.unsubscribe();
354
+ deSyncDetectedSubscription.unsubscribe();
355
+ remoteSchemaVersionMismatchSubscription.unsubscribe();
286
356
  await remoteSource.dispose();
287
357
  broadcastChannels.requests.close();
288
358
  broadcastChannels.responses.close();
@@ -339,54 +409,14 @@ async function startDbWorker(opts) {
339
409
  });
340
410
  self.close();
341
411
  }
342
- function getLatestSyncId(db) {
343
- const result = db.executePrepared(
344
- "get-latest-sync-id",
345
- {},
346
- (db2) => db2.selectFrom("worker.crdt_events").select((eb) => eb.fn.max("sync_id").as("sync_id")),
347
- { loggerLevel: "system" }
348
- );
349
- return result[0]?.sync_id ?? 0;
350
- }
351
- function persistEvent(db, event) {
352
- db.executePrepared(
353
- "persist-crdt-event",
354
- event,
355
- (db2, params) => db2.insertInto("worker.crdt_events").values({
356
- type: params("type"),
357
- dataset: params("dataset"),
358
- item_id: params("item_id"),
359
- payload: params("payload"),
360
- schema_version: params("schema_version"),
361
- sync_id: params("sync_id"),
362
- status: params("status"),
363
- timestamp: params("timestamp"),
364
- origin: params("origin"),
365
- source_node_id: params("source_node_id")
366
- }),
367
- { loggerLevel: "system" }
368
- );
369
- }
370
- function getEventsBatch(db, opts) {
371
- return db.executeKysely(
372
- (db2) => applyKyselyEventsBatchFilters(db2.selectFrom("worker.crdt_events").selectAll(), opts),
373
- { loggerLevel: "system" }
374
- ).rows;
375
- }
376
- function updateEvent(db, syncId, update) {
377
- db.executePrepared(
378
- "update-crdt-event",
379
- { syncId, ...update },
380
- (db2, params) => db2.updateTable("worker.crdt_events").set({
381
- status: params("status"),
382
- schema_version: params("schema_version"),
383
- type: params("type"),
384
- dataset: params("dataset"),
385
- item_id: params("item_id"),
386
- payload: params("payload")
387
- }).where("sync_id", "=", params("syncId")),
412
+ function getMaxSyncId(db, excludingStatus) {
413
+ const [result] = db.executePrepared(
414
+ "get-max-sync-id",
415
+ { excludingStatus },
416
+ (db2, params) => db2.selectFrom("worker.crdt_events").where("status", "!=", params("excludingStatus")).select((eb) => eb.fn.max("sync_id").as("sync_id")),
388
417
  { loggerLevel: "system" }
389
418
  );
419
+ return result?.sync_id ?? 0;
390
420
  }
391
421
  export {
392
422
  createWsRemoteSource,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/web-socket/ws-remote-source.ts","../src/worker-db/db-worker.ts"],"sourcesContent":["import type { SyncServerMessage, SyncServerRequest } from \"../server\";\nimport type { CreateRemoteSourceFactory } from \"../sqlite-crdt/crdt-sync-remote-source\";\nimport { createDeferredPromise, type DeferredPromise, jsonSafeParse } from \"../utils\";\nimport type { EventsPullRequest, EventsPushRequest, EventsPushResponse, GetEventsBatch } from \"../worker\";\n\ntype WsRemoteSourceConfig = {\n createWebSocket: () => Pick<WebSocket, \"send\" | \"onmessage\" | \"close\" | \"addEventListener\">;\n};\n\nexport const createWsRemoteSource = ({ createWebSocket }: WsRemoteSourceConfig): CreateRemoteSourceFactory => {\n return async ({ onEventsAvailable }) => {\n const socket = createWebSocket();\n\n const openPromise = createDeferredPromise<void>({\n timeout: 5000,\n onTimeout: () => {\n socket.close();\n },\n });\n socket.addEventListener(\"open\", () => {\n openPromise.resolve(undefined);\n });\n await openPromise.promise;\n\n const requestsMap = new Map<string, DeferredPromise<unknown>>();\n\n const pushEvents = async (request: EventsPushRequest): Promise<EventsPushResponse> => {\n const requestId = crypto.randomUUID();\n const promise = createDeferredPromise<EventsPushResponse>({ timeout: 5000 });\n requestsMap.set(requestId, promise as DeferredPromise<unknown>);\n\n const wsRequest: SyncServerRequest = {\n type: \"push-events\",\n requestId,\n nodeId: request.nodeId,\n events: request.events,\n };\n socket.send(JSON.stringify(wsRequest));\n\n return promise.promise;\n };\n\n const pullEvents = async (request: EventsPullRequest): Promise<GetEventsBatch> => {\n const requestId = crypto.randomUUID();\n const promise = createDeferredPromise<GetEventsBatch>({ timeout: 2000 });\n requestsMap.set(requestId, promise as DeferredPromise<unknown>);\n\n const wsRequest: SyncServerRequest = {\n type: \"pull-events\",\n requestId,\n afterSyncId: request.afterSyncId,\n excludeNodeId: request.excludeNodeId,\n };\n socket.send(JSON.stringify(wsRequest));\n\n return promise.promise;\n };\n\n socket.onmessage = (event) => {\n const result = jsonSafeParse<SyncServerMessage>(event.data);\n\n if (!result.success || !(\"type\" in result.data) || !result.data.type) {\n return;\n }\n\n const message = result.data;\n\n switch (message.type) {\n case \"events-pull-response\": {\n const promise = requestsMap.get(message.requestId);\n if (!promise) {\n return;\n }\n promise.resolve(message.data);\n requestsMap.delete(message.requestId);\n break;\n }\n case \"events-push-response\": {\n const promise = requestsMap.get(message.requestId);\n if (!promise) {\n return;\n }\n promise.resolve(message.data);\n requestsMap.delete(message.requestId);\n break;\n }\n case \"events-applied\":\n onEventsAvailable(message.newSyncId);\n break;\n default:\n message satisfies never;\n return;\n }\n };\n\n return {\n pushEvents,\n pullEvents,\n disconnect: () => {\n socket.close();\n },\n };\n };\n};\n","import sqlite3InitModule from \"@sqlite.org/sqlite-wasm\";\nimport type { Logger } from \"../logger\";\nimport { createMigrator, type SyncDbMigrator } from \"../migrations/migrator\";\nimport { applyWorkerDbSchema, type WorkerDbSchema } from \"../migrations/system-schema\";\nimport { createSQLiteCrdtApplyFunction } from \"../sqlite-crdt/apply-crdt-event\";\nimport type { SyncDbSchema } from \"../sqlite-crdt/crdt-schema\";\nimport {\n type CrdtStorage,\n createCrdtStorage,\n type EventUpdate,\n type GetEventsOptions,\n} from \"../sqlite-crdt/crdt-storage\";\nimport { createCrdtSyncProducer } from \"../sqlite-crdt/crdt-sync-producer\";\nimport { type CreateRemoteSourceFactory, createCrdtSyncRemoteSource } from \"../sqlite-crdt/crdt-sync-remote-source\";\nimport type { PersistedCrdtEvent } from \"../sqlite-crdt/crdt-table-schema\";\nimport { applyKyselyEventsBatchFilters } from \"../sqlite-crdt/events-batch-filters\";\nimport { createStoredValue } from \"../sqlite-crdt/stored-value\";\nimport { SQLiteDbWrapper } from \"../sqlite-db-wrapper\";\nimport { createSQLiteKvStore, type KvStore } from \"../sqlite-kv-store\";\nimport { createDeferredPromise } from \"../utils\";\nimport {\n createBroadcastChannels,\n isWorkerInitMessage,\n isWorkerRequestMessage,\n syncDbClientLockName,\n syncDbWorkerLockName,\n type WorkerConfig,\n type WorkerErrorResponseMessage,\n type WorkerResponseMessage,\n type WorkerRpc,\n} from \"./worker-common\";\n\nconst defaultLogger: Logger = (type, message, level = \"info\") => {\n const logMessage = `[${type}] ${message}`;\n switch (level) {\n case \"info\":\n console.log(logMessage);\n break;\n case \"warning\":\n console.warn(logMessage);\n break;\n case \"error\":\n console.error(logMessage);\n break;\n case \"trace\":\n console.trace(logMessage);\n break;\n }\n};\n\nasync function createDbWorker(config: WorkerConfig, opts: WorkerOptions) {\n const broadcastChannels = createBroadcastChannels(config.dbId);\n const logger = opts.logger ?? defaultLogger;\n\n const sqlite3 = await sqlite3InitModule();\n\n const pool = await sqlite3.installOpfsSAHPoolVfs({\n name: config.dbId,\n directory: `.${config.dbId}`,\n clearOnInit: config.clearOnInit,\n initialCapacity: 8,\n });\n\n const db = new SQLiteDbWrapper<WorkerDbSchema>({\n db: () => new pool.OpfsSAHPoolDb(`/${config.dbId}-main.db`),\n logger: logger,\n loggerPrefix: \"worker\",\n sqlite3,\n });\n\n db.execute(\"PRAGMA locking_mode=exclusive\", { loggerLevel: \"system\" });\n db.execute(\"PRAGMA journal_mode=WAL\", { loggerLevel: \"system\" });\n db.execute(\"PRAGMA synchronous=NORMAL\", { loggerLevel: \"system\" });\n\n db.execute(`ATTACH DATABASE '/${config.dbId}-worker.db' as worker`, { loggerLevel: \"system\" });\n db.execute(\"PRAGMA worker.locking_mode=exclusive\", { loggerLevel: \"system\" });\n db.execute(\"PRAGMA worker.journal_mode=WAL\", { loggerLevel: \"system\" });\n db.execute(\"PRAGMA worker.synchronous=NORMAL\", { loggerLevel: \"system\" });\n\n applyWorkerDbSchema(db);\n\n const kvStore = createSQLiteKvStore({\n db,\n metaTableName: \"worker.kv\",\n });\n\n const migrator = createMigrator({\n migrations: opts.syncDbSchema.migrations,\n schemaVersion: kvStore.createNumberStoredValue(\"schema-version\", -1),\n updateLogTableName: '\"crdt_update_log\"',\n });\n migrator.migrateDbToLatest({\n startTransaction: (callback) => {\n db.executeTransaction((tx) => callback({ execute: (sql, parameters) => tx.execute({ sql, parameters }) }));\n },\n });\n db.invalidateDbSchema();\n\n const localSyncId = createStoredValue({\n initialValue: getLatestSyncId(db),\n });\n\n const crdtStorage = createCrdtStorage({\n nodeId: config.clientId,\n syncId: localSyncId,\n migrator,\n hlc: {\n getNextHLC() {\n throw new Error(\"Worker DB should not call getNextHLC\");\n },\n mergeHLC() {\n return;\n },\n },\n transaction: (callback) => db.executeTransaction(callback),\n handleCrdtEventApply: createSQLiteCrdtApplyFunction({\n db,\n updateLogTableName: \"crdt_update_log\",\n }),\n persistEvent: (event) => persistEvent(db, event),\n getEventsBatch: (opts) => getEventsBatch(db, opts),\n updateEvent: (syncId, update) => updateEvent(db, syncId, update),\n });\n\n createCrdtSyncProducer({\n storage: crdtStorage,\n broadcastEvents: (chunk) => {\n broadcastChannels.responses.postMessage({\n notificationType: \"new-event-chunk-applied\",\n newSyncId: chunk.newSyncId,\n });\n },\n });\n\n const postState = () => {\n broadcastChannels.responses.postMessage({\n notificationType: \"state-changed\",\n state: {\n remoteState: remoteSource.getState(),\n },\n });\n };\n\n const remoteSource = createRemoteSource({\n kvStore,\n crdtStorage,\n migrator,\n clientId: config.clientId,\n remoteFactory: opts.createRemoteSource,\n });\n remoteSource.goOnline();\n\n remoteSource.addEventListener(\"state-changed\", () => {\n postState();\n });\n\n const rpcTarget: WorkerRpc = {\n execute: (query) => db.execute(query),\n getSnapshot: () => {\n db.execute(\"PRAGMA journal_mode=off\", { loggerLevel: \"system\" });\n const file = db.createSnapshot();\n db.execute(\"PRAGMA journal_mode=WAL\", { loggerLevel: \"system\" });\n return {\n file,\n syncId: localSyncId.current,\n schemaVersion: migrator.currentSchemaVersion,\n };\n },\n postState,\n pushTabEvents: (request) => {\n crdtStorage.enqueueLocalEvents(request.events, request.nodeId);\n return {\n ok: true,\n };\n },\n pullEvents: (request) => {\n return crdtStorage.getEventsBatch({\n afterSyncId: request.afterSyncId,\n status: \"applied\",\n excludeNodeId: request.excludeNodeId,\n limit: 100,\n });\n },\n goOnline: () => remoteSource.goOnline(),\n goOffline: () => remoteSource.goOffline(\"DISCONNECTED\"),\n };\n\n broadcastChannels.requests.onmessage = (event) => {\n const message = event.data;\n\n if (!isWorkerRequestMessage(message)) {\n return;\n }\n\n const sendError = (error: unknown) => {\n const response: WorkerErrorResponseMessage = {\n type: \"error-response\",\n requestId: message.requestId,\n error: error instanceof Error ? error.message : String(error),\n };\n broadcastChannels.responses.postMessage(response);\n };\n\n try {\n const method = rpcTarget[message.method] as () => ReturnType<WorkerRpc[keyof WorkerRpc]>;\n const data = method.apply(null, message.args as []);\n\n if (data instanceof Promise) {\n data\n .then((result) => {\n const response: WorkerResponseMessage = {\n type: \"response\",\n requestId: message.requestId,\n data: result,\n };\n broadcastChannels.responses.postMessage(response);\n })\n .catch(sendError);\n } else {\n const response: WorkerResponseMessage = {\n type: \"response\",\n requestId: message.requestId,\n data,\n };\n broadcastChannels.responses.postMessage(response);\n }\n } catch (error) {\n sendError(error);\n }\n };\n\n rpcTarget.postState();\n\n return async () => {\n await remoteSource.dispose();\n broadcastChannels.requests.close();\n broadcastChannels.responses.close();\n db.close();\n };\n}\n\ntype InitRemoteOptions = {\n kvStore: KvStore;\n clientId: string;\n crdtStorage: CrdtStorage;\n migrator: SyncDbMigrator;\n remoteFactory?: CreateRemoteSourceFactory;\n};\n\nfunction createRemoteSource({ kvStore, clientId, crdtStorage, migrator, remoteFactory }: InitRemoteOptions) {\n return createCrdtSyncRemoteSource({\n bufferSize: 50,\n pullSyncId: kvStore.createNumberStoredValue(\"pull-sync-id\", -1),\n pushSyncId: kvStore.createNumberStoredValue(\"push-sync-id\", -1),\n nodeId: clientId,\n storage: crdtStorage,\n migrator,\n remoteFactory,\n });\n}\n\nexport async function getWorkerConfig<Props = never>(): Promise<WorkerConfig<Props>> {\n let configSet = false;\n const responsePromise = createDeferredPromise<WorkerConfig>();\n\n self.onmessage = (event: MessageEvent<unknown>) => {\n if (configSet) {\n console.error(\"Worker config already set\");\n return;\n }\n\n const message = event.data;\n if (!isWorkerInitMessage(message)) {\n return;\n }\n\n responsePromise.resolve(message.config);\n configSet = true;\n };\n\n return responsePromise.promise;\n}\n\ntype WorkerOptions = {\n syncDbSchema: SyncDbSchema;\n logger?: Logger;\n createRemoteSource?: CreateRemoteSourceFactory;\n workerConfig?: WorkerConfig;\n};\n\nexport async function startDbWorker(opts: WorkerOptions) {\n const config = opts.workerConfig ?? (await getWorkerConfig());\n\n await navigator.locks.request(`${syncDbWorkerLockName}-${config.dbId}`, { mode: \"exclusive\" }, async (lock) => {\n if (!lock) {\n return;\n }\n\n const cleanup = await createDbWorker(config, opts);\n\n const clientLockName = `${syncDbClientLockName}-${config.dbId}`;\n await new Promise<void>((resolve) => {\n const interval = setInterval(async () => {\n const { held } = await navigator.locks.query();\n const hasClients = held?.some((l) => l.name === clientLockName && l.mode === \"shared\");\n if (!hasClients) {\n clearInterval(interval);\n resolve();\n }\n }, 5_000);\n });\n\n await cleanup();\n });\n\n self.close();\n}\n\nfunction getLatestSyncId(db: SQLiteDbWrapper<WorkerDbSchema>) {\n const result = db.executePrepared(\n \"get-latest-sync-id\",\n {},\n (db) => db.selectFrom(\"worker.crdt_events\").select((eb) => eb.fn.max(\"sync_id\").as(\"sync_id\")),\n { loggerLevel: \"system\" },\n );\n return result[0]?.sync_id ?? 0;\n}\n\nfunction persistEvent(db: SQLiteDbWrapper<WorkerDbSchema>, event: PersistedCrdtEvent) {\n db.executePrepared(\n \"persist-crdt-event\",\n event,\n (db, params) =>\n db.insertInto(\"worker.crdt_events\").values({\n type: params(\"type\"),\n dataset: params(\"dataset\"),\n item_id: params(\"item_id\"),\n payload: params(\"payload\"),\n schema_version: params(\"schema_version\"),\n sync_id: params(\"sync_id\"),\n status: params(\"status\"),\n timestamp: params(\"timestamp\"),\n origin: params(\"origin\"),\n source_node_id: params(\"source_node_id\"),\n }),\n { loggerLevel: \"system\" },\n );\n}\n\nfunction getEventsBatch(db: SQLiteDbWrapper<WorkerDbSchema>, opts: GetEventsOptions) {\n return db.executeKysely(\n (db) => applyKyselyEventsBatchFilters(db.selectFrom(\"worker.crdt_events\").selectAll(), opts),\n { loggerLevel: \"system\" },\n ).rows;\n}\n\nfunction updateEvent(db: SQLiteDbWrapper<WorkerDbSchema>, syncId: number, update: EventUpdate) {\n db.executePrepared(\n \"update-crdt-event\",\n { syncId, ...update },\n (db, params) =>\n db\n .updateTable(\"worker.crdt_events\")\n .set({\n status: params(\"status\"),\n schema_version: params(\"schema_version\"),\n type: params(\"type\"),\n dataset: params(\"dataset\"),\n item_id: params(\"item_id\"),\n payload: params(\"payload\"),\n })\n .where(\"sync_id\", \"=\", params(\"syncId\")),\n { loggerLevel: \"system\" },\n );\n}\n"],"mappings":"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","names":["opts","db"]}
1
+ {"version":3,"sources":["../src/web-socket/ws-remote-source.ts","../src/worker-db/db-worker.ts","../src/worker-db/storage-version.ts"],"sourcesContent":["import type { SyncServerMessage, SyncServerRequest } from \"../server\";\nimport type { CreateRemoteSourceFactory } from \"../sqlite-crdt/crdt-sync-remote-source\";\nimport { createDeferredPromise, type DeferredPromise, jsonSafeParse } from \"../utils\";\nimport type { EventsPullRequest, EventsPushRequest, EventsPushResponse, GetEventsBatch } from \"../worker\";\n\ntype WsRemoteSourceConfig = {\n createWebSocket: () => Pick<WebSocket, \"send\" | \"onmessage\" | \"close\" | \"addEventListener\">;\n};\n\nexport const createWsRemoteSource = ({ createWebSocket }: WsRemoteSourceConfig): CreateRemoteSourceFactory => {\n return async ({ onEventsAvailable }) => {\n const socket = createWebSocket();\n\n const openPromise = createDeferredPromise<void>({\n timeout: 5000,\n onTimeout: () => {\n socket.close();\n },\n });\n socket.addEventListener(\"open\", () => {\n openPromise.resolve(undefined);\n });\n await openPromise.promise;\n\n const requestsMap = new Map<string, DeferredPromise<unknown>>();\n\n const pushEvents = async (request: EventsPushRequest): Promise<EventsPushResponse> => {\n const requestId = crypto.randomUUID();\n const promise = createDeferredPromise<EventsPushResponse>({ timeout: 5000 });\n requestsMap.set(requestId, promise as DeferredPromise<unknown>);\n\n const wsRequest: SyncServerRequest = {\n type: \"push-events\",\n requestId,\n nodeId: request.nodeId,\n events: request.events,\n };\n socket.send(JSON.stringify(wsRequest));\n\n return promise.promise;\n };\n\n const pullEvents = async (request: EventsPullRequest): Promise<GetEventsBatch> => {\n const requestId = crypto.randomUUID();\n const promise = createDeferredPromise<GetEventsBatch>({ timeout: 2000 });\n requestsMap.set(requestId, promise as DeferredPromise<unknown>);\n\n const wsRequest: SyncServerRequest = {\n type: \"pull-events\",\n requestId,\n afterSyncId: request.afterSyncId,\n excludeNodeId: request.excludeNodeId,\n };\n socket.send(JSON.stringify(wsRequest));\n\n return promise.promise;\n };\n\n socket.onmessage = (event) => {\n const result = jsonSafeParse<SyncServerMessage>(event.data);\n\n if (!result.success || !(\"type\" in result.data) || !result.data.type) {\n return;\n }\n\n const message = result.data;\n\n switch (message.type) {\n case \"events-pull-response\": {\n const promise = requestsMap.get(message.requestId);\n if (!promise) {\n return;\n }\n promise.resolve(message.data);\n requestsMap.delete(message.requestId);\n break;\n }\n case \"events-push-response\": {\n const promise = requestsMap.get(message.requestId);\n if (!promise) {\n return;\n }\n promise.resolve(message.data);\n requestsMap.delete(message.requestId);\n break;\n }\n case \"events-applied\":\n onEventsAvailable({ newSyncId: message.newSyncId, remoteEventHlcSum: message.eventHlcSum });\n break;\n default:\n message satisfies never;\n return;\n }\n };\n\n return {\n pushEvents,\n pullEvents,\n disconnect: () => {\n socket.close();\n },\n };\n };\n};\n","import sqlite3InitModule from \"@sqlite.org/sqlite-wasm\";\nimport { xxhash } from \"../hash\";\nimport type { Logger } from \"../logger\";\nimport { createMigrator, type SyncDbMigrator } from \"../migrations/migrator\";\nimport { applyWorkerDbSchema, type WorkerDbSchema, workerDbConfig } from \"../migrations/system-schema\";\nimport type { SyncDbSchema } from \"../sqlite-crdt/crdt-schema\";\nimport { type CrdtStorage, createCrdtStorage } from \"../sqlite-crdt/crdt-storage\";\nimport { createCrdtSyncProducer } from \"../sqlite-crdt/crdt-sync-producer\";\nimport { type CreateRemoteSourceFactory, createCrdtSyncRemoteSource } from \"../sqlite-crdt/crdt-sync-remote-source\";\nimport type { CrdtEventStatus } from \"../sqlite-crdt/crdt-table-schema\";\nimport { SQLiteDbWrapper } from \"../sqlite-db-wrapper\";\nimport type { KvStore } from \"../sqlite-kv-store\";\nimport { createDeferredPromise } from \"../utils\";\nimport { createIdbResetStore, createReloadRequestHandler, createResetStateStore, type ResetStore } from \"./reset-state\";\nimport { createStorageVersionStore } from \"./storage-version\";\nimport {\n createBroadcastChannels,\n isWorkerInitMessage,\n isWorkerRequestMessage,\n syncDbClientLockName,\n syncDbWorkerLockName,\n type WorkerConfig,\n type WorkerErrorResponseMessage,\n type WorkerNotificationMessage,\n type WorkerResponseMessage,\n type WorkerRpc,\n} from \"./worker-common\";\n\nconst defaultLogger: Logger = (type, message, level = \"info\") => {\n const logMessage = `[${type}] ${message}`;\n switch (level) {\n case \"info\":\n console.log(logMessage);\n break;\n case \"warning\":\n console.warn(logMessage);\n break;\n case \"error\":\n console.error(logMessage);\n break;\n case \"trace\":\n console.trace(logMessage);\n break;\n }\n};\n\nasync function createDbWorker(config: WorkerConfig, opts: WorkerOptions) {\n const broadcastChannels = createBroadcastChannels(config.dbId);\n const logger = opts.logger ?? defaultLogger;\n\n const [sqlite3] = await Promise.all([sqlite3InitModule(), xxhash.ensureLoaded()]);\n\n const resetStore = opts.resetStore ?? createIdbResetStore();\n const resetState = createResetStateStore({\n store: resetStore,\n dbId: config.dbId,\n });\n const storageVersion = createStorageVersionStore({\n store: resetStore,\n dbId: config.dbId,\n appStorageVersion: opts.storageVersion,\n });\n const [pendingReset, isVersionMismatch] = await Promise.all([\n resetState.resolvePendingReset(),\n storageVersion.isVersionMismatch(),\n ]);\n\n if (isVersionMismatch) {\n logger(\"worker\", `Storage version mismatch — resetting local DB to ${storageVersion.currentVersion}`, \"warning\");\n }\n\n const pool = await sqlite3.installOpfsSAHPoolVfs({\n name: config.dbId,\n directory: `.${config.dbId}`,\n clearOnInit: !!pendingReset || isVersionMismatch,\n initialCapacity: 8,\n });\n\n const db = new SQLiteDbWrapper<WorkerDbSchema>({\n db: () => new pool.OpfsSAHPoolDb(`/${config.dbId}-main.db`),\n logger: logger,\n loggerPrefix: \"worker\",\n sqlite3,\n });\n\n db.execute(\"PRAGMA locking_mode=exclusive\", { loggerLevel: \"system\" });\n db.execute(\"PRAGMA journal_mode=WAL\", { loggerLevel: \"system\" });\n db.execute(\"PRAGMA synchronous=NORMAL\", { loggerLevel: \"system\" });\n\n db.execute(`ATTACH DATABASE '/${config.dbId}-worker.db' as worker`, { loggerLevel: \"system\" });\n db.execute(\"PRAGMA worker.locking_mode=exclusive\", { loggerLevel: \"system\" });\n db.execute(\"PRAGMA worker.journal_mode=WAL\", { loggerLevel: \"system\" });\n db.execute(\"PRAGMA worker.synchronous=NORMAL\", { loggerLevel: \"system\" });\n\n const { kvStore } = applyWorkerDbSchema(db);\n\n const migrator = createMigrator({\n migrations: opts.syncDbSchema.migrations,\n schemaVersion: kvStore.createNumberStoredValue(\"schema-version\", -1),\n updateLogTableName: workerDbConfig.updateLogTable.fullIdentifier,\n });\n migrator.migrateDbToLatest({\n startTransaction: (callback) => {\n db.executeTransaction((tx) => callback({ execute: (sql, parameters) => tx.execute({ sql, parameters }) }));\n },\n });\n db.invalidateDbSchema();\n\n // Record the applied reset epoch / storage version only after the wiped DB\n // initialized successfully, so a failed init can be retried by a later\n // elected worker, while a later election does not wipe again.\n if (pendingReset) {\n await resetState.markResetApplied(pendingReset.epoch);\n }\n if (isVersionMismatch) {\n await storageVersion.markCurrentVersionApplied();\n }\n\n const crdtStorage = createCrdtStorage({\n nodeId: config.clientId,\n initialLocalSyncId: getMaxSyncId(db, \"none\"),\n migrator,\n hlc: {\n getNextHLC() {\n throw new Error(\"Worker DB should not call getNextHLC\");\n },\n mergeHLC() {\n return;\n },\n },\n db,\n dbConfig: workerDbConfig,\n eventHlcAccumulator: kvStore.createStringStoredValue(\"crdt.consistency.event_hlc_sum.v2\", \"\"),\n });\n\n createCrdtSyncProducer({\n storage: crdtStorage,\n broadcastEvents: (chunk) => {\n broadcastChannels.responses.postMessage({\n notificationType: \"new-event-chunk-applied\",\n newSyncId: chunk.newSyncId,\n eventHlcSum: chunk.eventHlcSum,\n });\n },\n });\n\n const remoteSource = createRemoteSource({\n kvStore,\n crdtStorage,\n migrator,\n clientId: config.clientId,\n remoteFactory: opts.createRemoteSource,\n });\n remoteSource.goOnline();\n\n const broadcastNotification = (notification: WorkerNotificationMessage) => {\n broadcastChannels.responses.postMessage(notification);\n };\n\n const postState = () => {\n broadcastNotification({\n notificationType: \"state-changed\",\n state: {\n remoteState: remoteSource.getState(),\n },\n });\n };\n const stateChangedSubscription = remoteSource.addEventListener(\"state-changed\", () => {\n postState();\n });\n const deSyncDetectedSubscription = remoteSource.addEventListener(\"de-sync-detected\", (event) => {\n broadcastNotification({\n notificationType: \"de-sync-detected\",\n reason: event.payload.reason,\n });\n });\n const remoteSchemaVersionMismatchSubscription = remoteSource.addEventListener(\n \"remote-schema-version-mismatch\",\n (event) => {\n broadcastNotification({\n notificationType: \"remote-schema-version-mismatch\",\n remoteSchemaVersion: event.payload.remoteSchemaVersion,\n localSchemaVersion: event.payload.localSchemaVersion,\n });\n },\n );\n\n const rpcTarget: WorkerRpc = {\n execute: (query) => db.execute(query),\n getSnapshot: () => {\n const appliedSyncId = getMaxSyncId(db, \"pending\");\n db.execute(\"PRAGMA journal_mode=off\", { loggerLevel: \"system\" });\n const file = db.createSnapshot();\n db.execute(\"PRAGMA journal_mode=WAL\", { loggerLevel: \"system\" });\n return {\n file,\n syncId: appliedSyncId,\n schemaVersion: migrator.currentSchemaVersion,\n };\n },\n postState,\n pushTabEvents: (request) => {\n const { beforeSyncId, afterSyncId } = crdtStorage.enqueueLocalEvents(request.events, request.nodeId);\n return {\n ok: true,\n beforeSyncId,\n afterSyncId,\n };\n },\n pullEvents: (request) => {\n return crdtStorage.getEventsBatch({\n afterSyncId: request.afterSyncId,\n status: \"applied\",\n excludeNodeId: request.excludeNodeId ?? \"\",\n limit: 100,\n });\n },\n goOnline: () => remoteSource.goOnline(),\n goOffline: () => remoteSource.goOffline(\"DISCONNECTED\"),\n requestReload: createReloadRequestHandler({\n resetState,\n broadcast: broadcastNotification,\n }),\n };\n\n broadcastChannels.requests.onmessage = (event) => {\n const message = event.data;\n\n if (!isWorkerRequestMessage(message)) {\n return;\n }\n\n const sendError = (error: unknown) => {\n const response: WorkerErrorResponseMessage = {\n type: \"error-response\",\n requestId: message.requestId,\n error: error instanceof Error ? error.message : String(error),\n };\n broadcastChannels.responses.postMessage(response);\n };\n\n try {\n const method = rpcTarget[message.method] as () => ReturnType<WorkerRpc[keyof WorkerRpc]>;\n const data = method.apply(null, message.args as []);\n\n if (data instanceof Promise) {\n data\n .then((result) => {\n const response: WorkerResponseMessage = {\n type: \"response\",\n requestId: message.requestId,\n data: result,\n };\n broadcastChannels.responses.postMessage(response);\n })\n .catch(sendError);\n } else {\n const response: WorkerResponseMessage = {\n type: \"response\",\n requestId: message.requestId,\n data,\n };\n broadcastChannels.responses.postMessage(response);\n }\n } catch (error) {\n sendError(error);\n }\n };\n\n rpcTarget.postState();\n\n return async () => {\n stateChangedSubscription.unsubscribe();\n deSyncDetectedSubscription.unsubscribe();\n remoteSchemaVersionMismatchSubscription.unsubscribe();\n await remoteSource.dispose();\n broadcastChannels.requests.close();\n broadcastChannels.responses.close();\n db.close();\n };\n}\n\ntype InitRemoteOptions = {\n kvStore: KvStore;\n clientId: string;\n crdtStorage: CrdtStorage;\n migrator: SyncDbMigrator;\n remoteFactory?: CreateRemoteSourceFactory;\n};\n\nfunction createRemoteSource({ kvStore, clientId, crdtStorage, migrator, remoteFactory }: InitRemoteOptions) {\n return createCrdtSyncRemoteSource({\n bufferSize: 50,\n pullSyncId: kvStore.createNumberStoredValue(\"pull-sync-id\", -1),\n pushSyncId: kvStore.createNumberStoredValue(\"push-sync-id\", -1),\n nodeId: clientId,\n storage: crdtStorage,\n migrator,\n remoteFactory,\n });\n}\n\nexport async function getWorkerConfig<Props = never>(): Promise<WorkerConfig<Props>> {\n let configSet = false;\n const responsePromise = createDeferredPromise<WorkerConfig>();\n\n self.onmessage = (event: MessageEvent<unknown>) => {\n if (configSet) {\n console.error(\"Worker config already set\");\n return;\n }\n\n const message = event.data;\n if (!isWorkerInitMessage(message)) {\n return;\n }\n\n responsePromise.resolve(message.config);\n configSet = true;\n };\n\n return responsePromise.promise;\n}\n\ntype WorkerOptions = {\n syncDbSchema: SyncDbSchema;\n logger?: Logger;\n createRemoteSource?: CreateRemoteSourceFactory;\n workerConfig?: WorkerConfig;\n /** Durable storage for reset state. Defaults to an IndexedDB-backed store. */\n resetStore?: ResetStore;\n /**\n * App-provided storage version, combined with the library's internal storage\n * version. Bump it when deploying a code change that old persisted local DBs\n * cannot survive — on mismatch the elected worker wipes the local DB on startup.\n */\n storageVersion?: string;\n};\n\nexport async function startDbWorker(opts: WorkerOptions) {\n const config = opts.workerConfig ?? (await getWorkerConfig());\n\n await navigator.locks.request(`${syncDbWorkerLockName}-${config.dbId}`, { mode: \"exclusive\" }, async (lock) => {\n if (!lock) {\n return;\n }\n\n const cleanup = await createDbWorker(config, opts);\n\n const clientLockName = `${syncDbClientLockName}-${config.dbId}`;\n await new Promise<void>((resolve) => {\n const interval = setInterval(async () => {\n const { held } = await navigator.locks.query();\n const hasClients = held?.some((l) => l.name === clientLockName && l.mode === \"shared\");\n if (!hasClients) {\n clearInterval(interval);\n resolve();\n }\n }, 5_000);\n });\n\n await cleanup();\n });\n\n self.close();\n}\n\nfunction getMaxSyncId(db: SQLiteDbWrapper<WorkerDbSchema>, excludingStatus: \"none\" | \"pending\") {\n const [result] = db.executePrepared(\n \"get-max-sync-id\",\n { excludingStatus: excludingStatus as CrdtEventStatus },\n (db, params) =>\n db\n .selectFrom(\"worker.crdt_events\")\n .where(\"status\", \"!=\", params(\"excludingStatus\"))\n .select((eb) => eb.fn.max(\"sync_id\").as(\"sync_id\")),\n { loggerLevel: \"system\" },\n );\n\n return result?.sync_id ?? 0;\n}\n","import type { ResetStore } from \"./reset-state\";\n\n/**\n * Internal persisted-storage format version. Bump when the library changes\n * the worker DB layout in a way old persisted databases cannot survive —\n * every client resets its local DB on the next worker start.\n */\nexport const LIB_STORAGE_VERSION = 1;\n\nconst storageVersionKey = (dbId: string) => `sqlite-sync-storage-version-${dbId}`;\n\nexport function formatStorageVersion(appStorageVersion: string | undefined): string {\n return appStorageVersion === undefined\n ? `lib-v${LIB_STORAGE_VERSION}`\n : `lib-v${LIB_STORAGE_VERSION}:app-${appStorageVersion}`;\n}\n\ntype StorageVersionStoreOptions = {\n store: ResetStore;\n dbId: string;\n /** Dev-provided app storage version, combined with the internal lib version. */\n appStorageVersion?: string;\n};\n\nexport type StorageVersionStore = ReturnType<typeof createStorageVersionStore>;\n\n/**\n * Worker-owned durable storage version. The current version combines the\n * internal lib version with the dev-provided app version; when the stored\n * version does not match (including a missing record), the elected worker\n * initializes with `clearOnInit: true`. Wiping on a missing record is\n * harmless for fresh installs and correctly resets databases persisted\n * before versioning existed.\n */\nexport function createStorageVersionStore({ store, dbId, appStorageVersion }: StorageVersionStoreOptions) {\n const key = storageVersionKey(dbId);\n const currentVersion = formatStorageVersion(appStorageVersion);\n\n return {\n currentVersion,\n async isVersionMismatch(): Promise<boolean> {\n const storedVersion = await store.get<string>(key);\n return storedVersion !== currentVersion;\n },\n /**\n * Record the current version. Must be called only after the worker has\n * successfully initialized with the wiped DB, so a failed init can be\n * retried by a later elected worker.\n */\n async markCurrentVersionApplied(): Promise<void> {\n await store.set(key, currentVersion);\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AASO,IAAM,uBAAuB,CAAC,EAAE,gBAAgB,MAAuD;AAC5G,SAAO,OAAO,EAAE,kBAAkB,MAAM;AACtC,UAAM,SAAS,gBAAgB;AAE/B,UAAM,cAAc,sBAA4B;AAAA,MAC9C,SAAS;AAAA,MACT,WAAW,MAAM;AACf,eAAO,MAAM;AAAA,MACf;AAAA,IACF,CAAC;AACD,WAAO,iBAAiB,QAAQ,MAAM;AACpC,kBAAY,QAAQ,MAAS;AAAA,IAC/B,CAAC;AACD,UAAM,YAAY;AAElB,UAAM,cAAc,oBAAI,IAAsC;AAE9D,UAAM,aAAa,OAAO,YAA4D;AACpF,YAAM,YAAY,OAAO,WAAW;AACpC,YAAM,UAAU,sBAA0C,EAAE,SAAS,IAAK,CAAC;AAC3E,kBAAY,IAAI,WAAW,OAAmC;AAE9D,YAAM,YAA+B;AAAA,QACnC,MAAM;AAAA,QACN;AAAA,QACA,QAAQ,QAAQ;AAAA,QAChB,QAAQ,QAAQ;AAAA,MAClB;AACA,aAAO,KAAK,KAAK,UAAU,SAAS,CAAC;AAErC,aAAO,QAAQ;AAAA,IACjB;AAEA,UAAM,aAAa,OAAO,YAAwD;AAChF,YAAM,YAAY,OAAO,WAAW;AACpC,YAAM,UAAU,sBAAsC,EAAE,SAAS,IAAK,CAAC;AACvE,kBAAY,IAAI,WAAW,OAAmC;AAE9D,YAAM,YAA+B;AAAA,QACnC,MAAM;AAAA,QACN;AAAA,QACA,aAAa,QAAQ;AAAA,QACrB,eAAe,QAAQ;AAAA,MACzB;AACA,aAAO,KAAK,KAAK,UAAU,SAAS,CAAC;AAErC,aAAO,QAAQ;AAAA,IACjB;AAEA,WAAO,YAAY,CAAC,UAAU;AAC5B,YAAM,SAAS,cAAiC,MAAM,IAAI;AAE1D,UAAI,CAAC,OAAO,WAAW,EAAE,UAAU,OAAO,SAAS,CAAC,OAAO,KAAK,MAAM;AACpE;AAAA,MACF;AAEA,YAAM,UAAU,OAAO;AAEvB,cAAQ,QAAQ,MAAM;AAAA,QACpB,KAAK,wBAAwB;AAC3B,gBAAM,UAAU,YAAY,IAAI,QAAQ,SAAS;AACjD,cAAI,CAAC,SAAS;AACZ;AAAA,UACF;AACA,kBAAQ,QAAQ,QAAQ,IAAI;AAC5B,sBAAY,OAAO,QAAQ,SAAS;AACpC;AAAA,QACF;AAAA,QACA,KAAK,wBAAwB;AAC3B,gBAAM,UAAU,YAAY,IAAI,QAAQ,SAAS;AACjD,cAAI,CAAC,SAAS;AACZ;AAAA,UACF;AACA,kBAAQ,QAAQ,QAAQ,IAAI;AAC5B,sBAAY,OAAO,QAAQ,SAAS;AACpC;AAAA,QACF;AAAA,QACA,KAAK;AACH,4BAAkB,EAAE,WAAW,QAAQ,WAAW,mBAAmB,QAAQ,YAAY,CAAC;AAC1F;AAAA,QACF;AACE;AACA;AAAA,MACJ;AAAA,IACF;AAEA,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA,YAAY,MAAM;AAChB,eAAO,MAAM;AAAA,MACf;AAAA,IACF;AAAA,EACF;AACF;;;ACvGA,OAAO,uBAAuB;;;ACOvB,IAAM,sBAAsB;AAEnC,IAAM,oBAAoB,CAAC,SAAiB,+BAA+B,IAAI;AAExE,SAAS,qBAAqB,mBAA+C;AAClF,SAAO,sBAAsB,SACzB,QAAQ,mBAAmB,KAC3B,QAAQ,mBAAmB,QAAQ,iBAAiB;AAC1D;AAmBO,SAAS,0BAA0B,EAAE,OAAO,MAAM,kBAAkB,GAA+B;AACxG,QAAM,MAAM,kBAAkB,IAAI;AAClC,QAAM,iBAAiB,qBAAqB,iBAAiB;AAE7D,SAAO;AAAA,IACL;AAAA,IACA,MAAM,oBAAsC;AAC1C,YAAM,gBAAgB,MAAM,MAAM,IAAY,GAAG;AACjD,aAAO,kBAAkB;AAAA,IAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAMA,MAAM,4BAA2C;AAC/C,YAAM,MAAM,IAAI,KAAK,cAAc;AAAA,IACrC;AAAA,EACF;AACF;;;ADzBA,IAAM,gBAAwB,CAAC,MAAM,SAAS,QAAQ,WAAW;AAC/D,QAAM,aAAa,IAAI,IAAI,KAAK,OAAO;AACvC,UAAQ,OAAO;AAAA,IACb,KAAK;AACH,cAAQ,IAAI,UAAU;AACtB;AAAA,IACF,KAAK;AACH,cAAQ,KAAK,UAAU;AACvB;AAAA,IACF,KAAK;AACH,cAAQ,MAAM,UAAU;AACxB;AAAA,IACF,KAAK;AACH,cAAQ,MAAM,UAAU;AACxB;AAAA,EACJ;AACF;AAEA,eAAe,eAAe,QAAsB,MAAqB;AACvE,QAAM,oBAAoB,wBAAwB,OAAO,IAAI;AAC7D,QAAM,SAAS,KAAK,UAAU;AAE9B,QAAM,CAAC,OAAO,IAAI,MAAM,QAAQ,IAAI,CAAC,kBAAkB,GAAG,OAAO,aAAa,CAAC,CAAC;AAEhF,QAAM,aAAa,KAAK,cAAc,oBAAoB;AAC1D,QAAM,aAAa,sBAAsB;AAAA,IACvC,OAAO;AAAA,IACP,MAAM,OAAO;AAAA,EACf,CAAC;AACD,QAAM,iBAAiB,0BAA0B;AAAA,IAC/C,OAAO;AAAA,IACP,MAAM,OAAO;AAAA,IACb,mBAAmB,KAAK;AAAA,EAC1B,CAAC;AACD,QAAM,CAAC,cAAc,iBAAiB,IAAI,MAAM,QAAQ,IAAI;AAAA,IAC1D,WAAW,oBAAoB;AAAA,IAC/B,eAAe,kBAAkB;AAAA,EACnC,CAAC;AAED,MAAI,mBAAmB;AACrB,WAAO,UAAU,yDAAoD,eAAe,cAAc,IAAI,SAAS;AAAA,EACjH;AAEA,QAAM,OAAO,MAAM,QAAQ,sBAAsB;AAAA,IAC/C,MAAM,OAAO;AAAA,IACb,WAAW,IAAI,OAAO,IAAI;AAAA,IAC1B,aAAa,CAAC,CAAC,gBAAgB;AAAA,IAC/B,iBAAiB;AAAA,EACnB,CAAC;AAED,QAAM,KAAK,IAAI,gBAAgC;AAAA,IAC7C,IAAI,MAAM,IAAI,KAAK,cAAc,IAAI,OAAO,IAAI,UAAU;AAAA,IAC1D;AAAA,IACA,cAAc;AAAA,IACd;AAAA,EACF,CAAC;AAED,KAAG,QAAQ,iCAAiC,EAAE,aAAa,SAAS,CAAC;AACrE,KAAG,QAAQ,2BAA2B,EAAE,aAAa,SAAS,CAAC;AAC/D,KAAG,QAAQ,6BAA6B,EAAE,aAAa,SAAS,CAAC;AAEjE,KAAG,QAAQ,qBAAqB,OAAO,IAAI,yBAAyB,EAAE,aAAa,SAAS,CAAC;AAC7F,KAAG,QAAQ,wCAAwC,EAAE,aAAa,SAAS,CAAC;AAC5E,KAAG,QAAQ,kCAAkC,EAAE,aAAa,SAAS,CAAC;AACtE,KAAG,QAAQ,oCAAoC,EAAE,aAAa,SAAS,CAAC;AAExE,QAAM,EAAE,QAAQ,IAAI,oBAAoB,EAAE;AAE1C,QAAM,WAAW,eAAe;AAAA,IAC9B,YAAY,KAAK,aAAa;AAAA,IAC9B,eAAe,QAAQ,wBAAwB,kBAAkB,EAAE;AAAA,IACnE,oBAAoB,eAAe,eAAe;AAAA,EACpD,CAAC;AACD,WAAS,kBAAkB;AAAA,IACzB,kBAAkB,CAAC,aAAa;AAC9B,SAAG,mBAAmB,CAAC,OAAO,SAAS,EAAE,SAAS,CAAC,KAAK,eAAe,GAAG,QAAQ,EAAE,KAAK,WAAW,CAAC,EAAE,CAAC,CAAC;AAAA,IAC3G;AAAA,EACF,CAAC;AACD,KAAG,mBAAmB;AAKtB,MAAI,cAAc;AAChB,UAAM,WAAW,iBAAiB,aAAa,KAAK;AAAA,EACtD;AACA,MAAI,mBAAmB;AACrB,UAAM,eAAe,0BAA0B;AAAA,EACjD;AAEA,QAAM,cAAc,kBAAkB;AAAA,IACpC,QAAQ,OAAO;AAAA,IACf,oBAAoB,aAAa,IAAI,MAAM;AAAA,IAC3C;AAAA,IACA,KAAK;AAAA,MACH,aAAa;AACX,cAAM,IAAI,MAAM,sCAAsC;AAAA,MACxD;AAAA,MACA,WAAW;AACT;AAAA,MACF;AAAA,IACF;AAAA,IACA;AAAA,IACA,UAAU;AAAA,IACV,qBAAqB,QAAQ,wBAAwB,qCAAqC,EAAE;AAAA,EAC9F,CAAC;AAED,yBAAuB;AAAA,IACrB,SAAS;AAAA,IACT,iBAAiB,CAAC,UAAU;AAC1B,wBAAkB,UAAU,YAAY;AAAA,QACtC,kBAAkB;AAAA,QAClB,WAAW,MAAM;AAAA,QACjB,aAAa,MAAM;AAAA,MACrB,CAAC;AAAA,IACH;AAAA,EACF,CAAC;AAED,QAAM,eAAe,mBAAmB;AAAA,IACtC;AAAA,IACA;AAAA,IACA;AAAA,IACA,UAAU,OAAO;AAAA,IACjB,eAAe,KAAK;AAAA,EACtB,CAAC;AACD,eAAa,SAAS;AAEtB,QAAM,wBAAwB,CAAC,iBAA4C;AACzE,sBAAkB,UAAU,YAAY,YAAY;AAAA,EACtD;AAEA,QAAM,YAAY,MAAM;AACtB,0BAAsB;AAAA,MACpB,kBAAkB;AAAA,MAClB,OAAO;AAAA,QACL,aAAa,aAAa,SAAS;AAAA,MACrC;AAAA,IACF,CAAC;AAAA,EACH;AACA,QAAM,2BAA2B,aAAa,iBAAiB,iBAAiB,MAAM;AACpF,cAAU;AAAA,EACZ,CAAC;AACD,QAAM,6BAA6B,aAAa,iBAAiB,oBAAoB,CAAC,UAAU;AAC9F,0BAAsB;AAAA,MACpB,kBAAkB;AAAA,MAClB,QAAQ,MAAM,QAAQ;AAAA,IACxB,CAAC;AAAA,EACH,CAAC;AACD,QAAM,0CAA0C,aAAa;AAAA,IAC3D;AAAA,IACA,CAAC,UAAU;AACT,4BAAsB;AAAA,QACpB,kBAAkB;AAAA,QAClB,qBAAqB,MAAM,QAAQ;AAAA,QACnC,oBAAoB,MAAM,QAAQ;AAAA,MACpC,CAAC;AAAA,IACH;AAAA,EACF;AAEA,QAAM,YAAuB;AAAA,IAC3B,SAAS,CAAC,UAAU,GAAG,QAAQ,KAAK;AAAA,IACpC,aAAa,MAAM;AACjB,YAAM,gBAAgB,aAAa,IAAI,SAAS;AAChD,SAAG,QAAQ,2BAA2B,EAAE,aAAa,SAAS,CAAC;AAC/D,YAAM,OAAO,GAAG,eAAe;AAC/B,SAAG,QAAQ,2BAA2B,EAAE,aAAa,SAAS,CAAC;AAC/D,aAAO;AAAA,QACL;AAAA,QACA,QAAQ;AAAA,QACR,eAAe,SAAS;AAAA,MAC1B;AAAA,IACF;AAAA,IACA;AAAA,IACA,eAAe,CAAC,YAAY;AAC1B,YAAM,EAAE,cAAc,YAAY,IAAI,YAAY,mBAAmB,QAAQ,QAAQ,QAAQ,MAAM;AACnG,aAAO;AAAA,QACL,IAAI;AAAA,QACJ;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,IACA,YAAY,CAAC,YAAY;AACvB,aAAO,YAAY,eAAe;AAAA,QAChC,aAAa,QAAQ;AAAA,QACrB,QAAQ;AAAA,QACR,eAAe,QAAQ,iBAAiB;AAAA,QACxC,OAAO;AAAA,MACT,CAAC;AAAA,IACH;AAAA,IACA,UAAU,MAAM,aAAa,SAAS;AAAA,IACtC,WAAW,MAAM,aAAa,UAAU,cAAc;AAAA,IACtD,eAAe,2BAA2B;AAAA,MACxC;AAAA,MACA,WAAW;AAAA,IACb,CAAC;AAAA,EACH;AAEA,oBAAkB,SAAS,YAAY,CAAC,UAAU;AAChD,UAAM,UAAU,MAAM;AAEtB,QAAI,CAAC,uBAAuB,OAAO,GAAG;AACpC;AAAA,IACF;AAEA,UAAM,YAAY,CAAC,UAAmB;AACpC,YAAM,WAAuC;AAAA,QAC3C,MAAM;AAAA,QACN,WAAW,QAAQ;AAAA,QACnB,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,MAC9D;AACA,wBAAkB,UAAU,YAAY,QAAQ;AAAA,IAClD;AAEA,QAAI;AACF,YAAM,SAAS,UAAU,QAAQ,MAAM;AACvC,YAAM,OAAO,OAAO,MAAM,MAAM,QAAQ,IAAU;AAElD,UAAI,gBAAgB,SAAS;AAC3B,aACG,KAAK,CAAC,WAAW;AAChB,gBAAM,WAAkC;AAAA,YACtC,MAAM;AAAA,YACN,WAAW,QAAQ;AAAA,YACnB,MAAM;AAAA,UACR;AACA,4BAAkB,UAAU,YAAY,QAAQ;AAAA,QAClD,CAAC,EACA,MAAM,SAAS;AAAA,MACpB,OAAO;AACL,cAAM,WAAkC;AAAA,UACtC,MAAM;AAAA,UACN,WAAW,QAAQ;AAAA,UACnB;AAAA,QACF;AACA,0BAAkB,UAAU,YAAY,QAAQ;AAAA,MAClD;AAAA,IACF,SAAS,OAAO;AACd,gBAAU,KAAK;AAAA,IACjB;AAAA,EACF;AAEA,YAAU,UAAU;AAEpB,SAAO,YAAY;AACjB,6BAAyB,YAAY;AACrC,+BAA2B,YAAY;AACvC,4CAAwC,YAAY;AACpD,UAAM,aAAa,QAAQ;AAC3B,sBAAkB,SAAS,MAAM;AACjC,sBAAkB,UAAU,MAAM;AAClC,OAAG,MAAM;AAAA,EACX;AACF;AAUA,SAAS,mBAAmB,EAAE,SAAS,UAAU,aAAa,UAAU,cAAc,GAAsB;AAC1G,SAAO,2BAA2B;AAAA,IAChC,YAAY;AAAA,IACZ,YAAY,QAAQ,wBAAwB,gBAAgB,EAAE;AAAA,IAC9D,YAAY,QAAQ,wBAAwB,gBAAgB,EAAE;AAAA,IAC9D,QAAQ;AAAA,IACR,SAAS;AAAA,IACT;AAAA,IACA;AAAA,EACF,CAAC;AACH;AAEA,eAAsB,kBAA+D;AACnF,MAAI,YAAY;AAChB,QAAM,kBAAkB,sBAAoC;AAE5D,OAAK,YAAY,CAAC,UAAiC;AACjD,QAAI,WAAW;AACb,cAAQ,MAAM,2BAA2B;AACzC;AAAA,IACF;AAEA,UAAM,UAAU,MAAM;AACtB,QAAI,CAAC,oBAAoB,OAAO,GAAG;AACjC;AAAA,IACF;AAEA,oBAAgB,QAAQ,QAAQ,MAAM;AACtC,gBAAY;AAAA,EACd;AAEA,SAAO,gBAAgB;AACzB;AAiBA,eAAsB,cAAc,MAAqB;AACvD,QAAM,SAAS,KAAK,gBAAiB,MAAM,gBAAgB;AAE3D,QAAM,UAAU,MAAM,QAAQ,GAAG,oBAAoB,IAAI,OAAO,IAAI,IAAI,EAAE,MAAM,YAAY,GAAG,OAAO,SAAS;AAC7G,QAAI,CAAC,MAAM;AACT;AAAA,IACF;AAEA,UAAM,UAAU,MAAM,eAAe,QAAQ,IAAI;AAEjD,UAAM,iBAAiB,GAAG,oBAAoB,IAAI,OAAO,IAAI;AAC7D,UAAM,IAAI,QAAc,CAAC,YAAY;AACnC,YAAM,WAAW,YAAY,YAAY;AACvC,cAAM,EAAE,KAAK,IAAI,MAAM,UAAU,MAAM,MAAM;AAC7C,cAAM,aAAa,MAAM,KAAK,CAAC,MAAM,EAAE,SAAS,kBAAkB,EAAE,SAAS,QAAQ;AACrF,YAAI,CAAC,YAAY;AACf,wBAAc,QAAQ;AACtB,kBAAQ;AAAA,QACV;AAAA,MACF,GAAG,GAAK;AAAA,IACV,CAAC;AAED,UAAM,QAAQ;AAAA,EAChB,CAAC;AAED,OAAK,MAAM;AACb;AAEA,SAAS,aAAa,IAAqC,iBAAqC;AAC9F,QAAM,CAAC,MAAM,IAAI,GAAG;AAAA,IAClB;AAAA,IACA,EAAE,gBAAoD;AAAA,IACtD,CAACA,KAAI,WACHA,IACG,WAAW,oBAAoB,EAC/B,MAAM,UAAU,MAAM,OAAO,iBAAiB,CAAC,EAC/C,OAAO,CAAC,OAAO,GAAG,GAAG,IAAI,SAAS,EAAE,GAAG,SAAS,CAAC;AAAA,IACtD,EAAE,aAAa,SAAS;AAAA,EAC1B;AAEA,SAAO,QAAQ,WAAW;AAC5B;","names":["db"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sqlite-sync/core",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "SQLite synchronization library with CRDT support",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -38,18 +38,22 @@
38
38
  ],
39
39
  "dependencies": {
40
40
  "@sqlite.org/sqlite-wasm": "^3.51.2-build5",
41
+ "idb-keyval": "^6.2.5",
41
42
  "kysely": "^0.28.10",
42
43
  "retry-as-promised": "^7.1.1",
44
+ "xxhash-wasm": "^1.1.0",
43
45
  "zod": "^4.3.6"
44
46
  },
45
47
  "devDependencies": {
46
48
  "@types/node": "^22.10.2",
47
49
  "tsup": "^8.3.5",
48
- "typescript": "~5.9.3"
50
+ "typescript": "~5.9.3",
51
+ "vitest": "^4.0.18"
49
52
  },
50
53
  "scripts": {
51
54
  "build": "tsup",
52
55
  "dev": "tsup --watch",
53
- "typecheck": "tsc --noEmit"
56
+ "test": "vitest run",
57
+ "typecheck": "tsgo --noEmit"
54
58
  }
55
59
  }