@sqlite-sync/core 0.12.0 → 0.12.1

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/index.js CHANGED
@@ -1,9 +1,9 @@
1
- import { C as introspectDb, D as dummyKysely, E as createImportData, S as startPerformanceLogger, T as createExportData, _ as formatSchemaVerificationIssues, a as createBroadcastChannels, b as createMigrator, c as isWorkerNotificationMessage, d as syncDbClientLockName, g as createSQLiteCrdtApplyFunction, h as createCrdtApplyFunction, m as createCrdtStorage, o as isWorkerErrorResponseMessage, p as createCrdtSyncProducer, t as RESET_REQUEST_TTL_MS, u as isWorkerResponseMessage, v as verifySyncSchema, w as xxhash, x as SQLiteDbWrapper, y as createMigrations } from "./reset-state-BQi96NLn.js";
2
- import { a as createDeferredPromise, c as generateId, f as quoteId, i as TypedEvent, l as jsonSafeParse, m as tryCatchAsync, n as isNoOpCrdtEventPayload, o as createTypedEventTarget, p as tryCatch, r as TypedBroadcastChannel, t as CRDT_EVENT_NO_OP_PAYLOAD, u as noop } from "./crdt-table-schema-2w_AIpsV.js";
3
- import { a as HLCCounter, c as serializeHLC, n as CrdtEventValidationError, o as compareHLC, r as validateNewCrdtEvent, s as deserializeHLC, t as createCrdtSyncRemoteSource } from "./crdt-sync-remote-source-Q6P6OiL0.js";
4
- import { a as memoryDbConfig, c as createKvStoreTableQuery, l as createSQLiteKvStore, n as applyWorkerDbSchema, t as applyMemoryDbSchema, u as createStoredValue } from "./system-schema-C2JZDv6L.js";
1
+ import { a as memoryDbConfig, c as createKvStoreTableQuery, d as SQLiteDbWrapper, h as dummyKysely, l as createSQLiteKvStore, m as introspectDb, n as applyWorkerDbSchema, p as startPerformanceLogger, t as applyMemoryDbSchema, u as createStoredValue } from "./system-schema-Dvl_BLId.js";
2
+ import { a as acquireLock, d as noop, 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-BRm_WdcW.js";
3
+ import { C as xxhash, S as createMigrator, T as createImportData, _ as createCrdtApplyFunction, a as acquireReplicaLock, b as verifySyncSchema, c as isWorkerErrorResponseMessage, f as isWorkerResponseMessage, g as createCrdtStorage, h as createCrdtSyncProducer, p as syncDbClientLockName, s as createBroadcastChannels, t as RESET_REQUEST_TTL_MS, u as isWorkerNotificationMessage, v as createSQLiteCrdtApplyFunction, w as createExportData, x as createMigrations, y as formatSchemaVerificationIssues } from "./reset-state-t0tTGqoh.js";
4
+ import { a as HLCCounter, c as serializeHLC, n as CrdtEventValidationError, o as compareHLC, r as validateNewCrdtEvent, s as deserializeHLC, t as createCrdtSyncRemoteSource } from "./crdt-sync-remote-source-CPXzmSIS.js";
5
5
  import { n as buildWriteOriginByName, t as admitClientEvents } from "./admit-client-events-BDrJw4SK.js";
6
- import { a as makeCrdtTable, o as registerCrdtIntentDrainer } from "./make-crdt-table-D8VQ-ptj.js";
6
+ import { a as makeCrdtTable, o as registerCrdtIntentDrainer } from "./make-crdt-table-C6l0F-o_.js";
7
7
  import sqlite3InitModule from "@sqlite.org/sqlite-wasm";
8
8
  //#region src/bound-map.ts
9
9
  var BoundMap = class {
@@ -689,18 +689,22 @@ const createWorkerDbClient = async ({ broadcastChannels, worker, config }) => {
689
689
  const queryWorker = (method, args) => {
690
690
  if (isDisposed) return Promise.reject(/* @__PURE__ */ new Error("Worker client disposed"));
691
691
  const requestId = crypto.randomUUID();
692
- const promise = createDeferredPromise({
693
- timeout: 3e4,
694
- onTimeout: () => workerRequestsMap.delete(requestId)
695
- });
696
- workerRequestsMap.set(requestId, promise);
697
692
  const request = {
698
693
  type: "request",
699
694
  requestId,
700
695
  method,
701
696
  args
702
697
  };
703
- broadcastChannels.requests.postMessage(request);
698
+ try {
699
+ broadcastChannels.requests.postMessage(request);
700
+ } catch (error) {
701
+ return Promise.reject(error);
702
+ }
703
+ const promise = createDeferredPromise({
704
+ timeout: 3e4,
705
+ onTimeout: () => workerRequestsMap.delete(requestId)
706
+ });
707
+ workerRequestsMap.set(requestId, promise);
704
708
  return promise.promise;
705
709
  };
706
710
  const handleWorkerResponse = (message) => {
@@ -734,14 +738,6 @@ const createWorkerDbClient = async ({ broadcastChannels, worker, config }) => {
734
738
  sync: () => queryWorker("sync", []),
735
739
  requestReload: (options) => queryWorker("requestReload", [options])
736
740
  };
737
- const statePromise = awaitWorkerState(eventTarget);
738
- postWorkerConfig(worker, config);
739
- rpc.postState().catch(noop);
740
- let { state: workerState, storageGeneration } = await statePromise;
741
- eventTarget.addEventListener("state-changed", (event) => {
742
- workerState = event.payload.state;
743
- storageGeneration = event.payload.storageGeneration;
744
- });
745
741
  const dispose = () => {
746
742
  isDisposed = true;
747
743
  broadcastChannels.responses.onmessage = null;
@@ -750,13 +746,25 @@ const createWorkerDbClient = async ({ broadcastChannels, worker, config }) => {
750
746
  workerRequestsMap.delete(id);
751
747
  }
752
748
  };
753
- return {
754
- ...rpc,
755
- subscribe: eventTarget.addEventListener,
756
- getState: () => workerState,
757
- getStorageGeneration: () => storageGeneration,
758
- dispose
759
- };
749
+ try {
750
+ postWorkerConfig(worker, config);
751
+ rpc.postState().catch(noop);
752
+ let { state: workerState, storageGeneration } = await awaitWorkerState(eventTarget);
753
+ eventTarget.addEventListener("state-changed", (event) => {
754
+ workerState = event.payload.state;
755
+ storageGeneration = event.payload.storageGeneration;
756
+ });
757
+ return {
758
+ ...rpc,
759
+ subscribe: eventTarget.addEventListener,
760
+ getState: () => workerState,
761
+ getStorageGeneration: () => storageGeneration,
762
+ dispose
763
+ };
764
+ } catch (error) {
765
+ dispose();
766
+ throw error;
767
+ }
760
768
  };
761
769
  function awaitWorkerState(eventTarget) {
762
770
  const promise = createDeferredPromise({ timeout: 15e3 });
@@ -822,173 +830,191 @@ async function createSyncedDb(options) {
822
830
  const instanceId = generateId();
823
831
  const tabId = generateId();
824
832
  const broadcastChannels = createBroadcastChannels(options.dbId);
825
- const clientLockAcquired = createDeferredPromise();
826
- const clientLockRelease = createDeferredPromise();
827
- navigator.locks.request(`${syncDbClientLockName}-${options.dbId}`, { mode: "shared" }, () => {
828
- clientLockAcquired.resolve();
829
- return clientLockRelease.promise;
830
- });
831
- await clientLockAcquired.promise;
832
- const workerClient = await createWorkerDbClient({
833
- worker: options.worker,
834
- config: {
835
- clientId: generateId(),
836
- dbId: options.dbId,
837
- props: options.workerProps
838
- },
839
- broadcastChannels
840
- });
841
- const hlcCounter = new HLCCounter(tabId, () => Date.now());
842
- const workerClientSnapshot = await workerClient.getSnapshot();
843
- const reactiveDb = await createSQLiteReactiveDb({
844
- snapshot: workerClientSnapshot.file,
845
- logger: defaultLogger
846
- });
847
- const memoryDbMigrator = {
848
- currentSchemaVersion: workerClientSnapshot.schemaVersion,
849
- latestSchemaVersion: workerClientSnapshot.schemaVersion,
850
- migrateDbToLatest: () => {
851
- throw new Error("Memory DB migrations are not implemented");
852
- },
853
- migrateEvent: (event, targetVersion) => {
854
- if (event.schema_version === targetVersion) return event;
855
- throw new Error("Memory DB migrations are not implemented");
856
- },
857
- migrateEvents: (events) => events
858
- };
859
- const { crdtStorage } = await createMemoryDb({
860
- nodeId: tabId,
861
- migrator: memoryDbMigrator,
862
- reactiveDb,
863
- hlcCounter,
864
- crdtTables: options.syncDbSchema.tablesConfig,
865
- syncDbSchema: options.syncDbSchema
866
- });
867
- const pullSyncId = createStoredValue({ initialValue: workerClientSnapshot.syncId });
868
- const pushSyncId = createStoredValue({ initialValue: 0 });
869
- const tabRemoteSource = createCrdtSyncRemoteSource({
870
- bufferSize: 500,
871
- pullSyncId,
872
- pushSyncId,
873
- storage: crdtStorage,
874
- nodeId: tabId,
875
- migrator: memoryDbMigrator,
876
- remoteFactory: ({ onEventsAvailable }) => {
877
- const subscription = workerClient.subscribe("new-event-chunk-applied", (event) => {
878
- onEventsAvailable({
879
- newSyncId: event.payload.newSyncId,
880
- remoteEventHlcSum: event.payload.eventHlcSum
881
- });
882
- });
883
- return {
884
- pullEvents: (request) => workerClient.pullEvents(request),
885
- pushEvents: (request) => workerClient.pushTabEvents(request),
886
- disconnect: () => {
887
- subscription.unsubscribe();
888
- }
889
- };
890
- }
891
- });
892
- tabRemoteSource.goOnline();
893
- const reloadRequestedSubscription = workerClient.subscribe("reload-requested", () => {
894
- globalThis.location?.reload();
895
- });
896
- const reloadIfStorageReplaced = (storageGeneration) => {
897
- if (storageGeneration !== workerClientSnapshot.storageGeneration) {
898
- storageReplacedSubscription.unsubscribe();
899
- globalThis.location?.reload();
900
- }
901
- };
902
- const storageReplacedSubscription = workerClient.subscribe("state-changed", (event) => {
903
- reloadIfStorageReplaced(event.payload.storageGeneration);
904
- });
905
- reloadIfStorageReplaced(workerClient.getStorageGeneration());
906
- const sync = () => {
907
- workerClient.sync().catch((error) => console.warn("Failed to start sync", error));
908
- };
909
- const syncWhenVisible = () => {
910
- if (globalThis.document?.visibilityState === "visible") sync();
911
- };
912
- const dropConnection = () => {
913
- workerClient.dropConnection().catch((error) => console.warn("Failed to drop connection", error));
914
- };
915
- globalThis.document?.addEventListener("visibilitychange", syncWhenVisible);
916
- globalThis.addEventListener?.("online", sync);
917
- globalThis.addEventListener?.("offline", dropConnection);
918
- perf.logEnd("createSyncedDb", "initialized", "info");
919
- const exportData = createExportData({
920
- reactiveDb,
921
- tablesConfig: options.syncDbSchema.tablesConfig,
922
- schemaVersion: workerClientSnapshot.schemaVersion
923
- });
924
- const importData = (data, opts) => workerClient.importData(data, opts);
925
- let isDisposed = false;
833
+ let releaseClientLock;
834
+ let disposeWorkerClient;
835
+ let releaseReplicaLock;
836
+ let disposeReactiveDb;
837
+ let disposeRemoteSource;
838
+ const cleanupCallbacks = [];
926
839
  let unregisterDevtools;
840
+ let isDisposed = false;
927
841
  const dispose = async () => {
928
842
  if (isDisposed) return;
929
843
  isDisposed = true;
930
844
  unregisterDevtools?.();
931
- globalThis.document?.removeEventListener("visibilitychange", syncWhenVisible);
932
- globalThis.removeEventListener?.("online", sync);
933
- globalThis.removeEventListener?.("offline", dropConnection);
934
- reloadRequestedSubscription.unsubscribe();
935
- storageReplacedSubscription.unsubscribe();
936
- clientLockRelease.resolve();
937
- await tabRemoteSource.dispose();
938
- broadcastChannels.requests.close();
939
- broadcastChannels.responses.close();
940
- workerClient.dispose();
941
- reactiveDb.dispose();
942
- };
943
- const syncedDb = {
944
- db: createSyncedDbDatabase(reactiveDb),
945
- state: {
946
- getState: workerClient.getState.bind(workerClient),
947
- subscribe: (onChange) => {
948
- const { unsubscribe } = workerClient.subscribe("state-changed", onChange);
949
- return unsubscribe;
950
- },
951
- goOnline: workerClient.goOnline.bind(workerClient),
952
- goOffline: workerClient.goOffline.bind(workerClient),
953
- sync
954
- },
955
- /**
956
- * Ask the elected worker to broadcast a page reload to all tabs for this dbId.
957
- *
958
- * With `clean: true` the worker durably records a reset request epoch before
959
- * broadcasting, so the worker elected on the next startup initializes with
960
- * `clearOnInit: true` and wipes the persisted DB. Destructive — use as a
961
- * recovery path when the durable worker DB may be de-synced.
962
- *
963
- * Pending in-memory tab events are not preserved, and the returned promise
964
- * may never settle in the caller — the page typically unloads first.
965
- */
966
- requestReload: async (options) => {
967
- await workerClient.requestReload(options);
968
- setTimeout(() => globalThis.location?.reload(), 250);
969
- },
970
- subscribe: workerClient.subscribe,
971
- exportData,
972
- importData,
973
- dispose,
974
- _internal: {
975
- executeAsync: workerClient.execute.bind(workerClient),
976
- getMemoryQueryTables: reactiveDb.getTablesUsed.bind(reactiveDb),
977
- getSharedLiveQueriesSnapshot: reactiveDb.getSharedLiveQueriesSnapshot.bind(reactiveDb),
978
- crdtTableNames: options.syncDbSchema.tablesConfig.map((table) => table.crdtTableName),
979
- crdtTablesConfig: options.syncDbSchema.tablesConfig,
980
- tables: options.syncDbSchema.tables,
981
- schemaVersion: workerClientSnapshot.schemaVersion,
982
- migrationVersions: Object.keys(options.syncDbSchema.migrations).map(Number).sort((a, b) => a - b)
845
+ for (const cleanup of cleanupCallbacks) cleanup();
846
+ try {
847
+ await disposeRemoteSource?.();
848
+ } finally {
849
+ broadcastChannels.requests.close();
850
+ broadcastChannels.responses.close();
851
+ disposeWorkerClient?.();
852
+ try {
853
+ disposeReactiveDb?.();
854
+ } finally {
855
+ await Promise.all([releaseClientLock?.(), releaseReplicaLock?.()]);
856
+ }
983
857
  }
984
858
  };
985
- unregisterDevtools = getOrCreateSQLiteSyncDevtoolsRegistry().register({
986
- instanceId,
987
- dbId: options.dbId,
988
- createdAt: Date.now(),
989
- instance: syncedDb
990
- });
991
- return syncedDb;
859
+ try {
860
+ releaseClientLock = await acquireLock(`${syncDbClientLockName}-${options.dbId}`, { mode: "shared" });
861
+ const workerClient = await createWorkerDbClient({
862
+ worker: options.worker,
863
+ config: {
864
+ clientId: generateId(),
865
+ dbId: options.dbId,
866
+ props: options.workerProps
867
+ },
868
+ broadcastChannels
869
+ });
870
+ disposeWorkerClient = () => workerClient.dispose();
871
+ const hlcCounter = new HLCCounter(tabId, () => Date.now());
872
+ releaseReplicaLock = await acquireReplicaLock({ dbId: options.dbId });
873
+ const workerClientSnapshot = await workerClient.getSnapshot();
874
+ const reactiveDb = await createSQLiteReactiveDb({
875
+ snapshot: workerClientSnapshot.file,
876
+ logger: defaultLogger
877
+ });
878
+ disposeReactiveDb = () => reactiveDb.dispose();
879
+ const memoryDbMigrator = {
880
+ currentSchemaVersion: workerClientSnapshot.schemaVersion,
881
+ latestSchemaVersion: workerClientSnapshot.schemaVersion,
882
+ migrateDbToLatest: () => {
883
+ throw new Error("Memory DB migrations are not implemented");
884
+ },
885
+ migrateEvent: (event, targetVersion) => {
886
+ if (event.schema_version === targetVersion) return event;
887
+ throw new Error("Memory DB migrations are not implemented");
888
+ },
889
+ migrateEvents: (events) => events
890
+ };
891
+ const { crdtStorage } = await createMemoryDb({
892
+ nodeId: tabId,
893
+ migrator: memoryDbMigrator,
894
+ reactiveDb,
895
+ hlcCounter,
896
+ crdtTables: options.syncDbSchema.tablesConfig,
897
+ syncDbSchema: options.syncDbSchema
898
+ });
899
+ const pullSyncId = createStoredValue({ initialValue: workerClientSnapshot.syncId });
900
+ const pushSyncId = createStoredValue({ initialValue: 0 });
901
+ const tabRemoteSource = createCrdtSyncRemoteSource({
902
+ bufferSize: 500,
903
+ pullSyncId,
904
+ pushSyncId,
905
+ storage: crdtStorage,
906
+ nodeId: tabId,
907
+ migrator: memoryDbMigrator,
908
+ remoteFactory: ({ onEventsAvailable }) => {
909
+ const subscription = workerClient.subscribe("new-event-chunk-applied", (event) => {
910
+ onEventsAvailable({
911
+ newSyncId: event.payload.newSyncId,
912
+ remoteEventHlcSum: event.payload.eventHlcSum
913
+ });
914
+ });
915
+ return {
916
+ pullEvents: (request) => workerClient.pullEvents(request),
917
+ pushEvents: (request) => workerClient.pushTabEvents(request),
918
+ disconnect: () => {
919
+ subscription.unsubscribe();
920
+ }
921
+ };
922
+ }
923
+ });
924
+ disposeRemoteSource = () => tabRemoteSource.dispose();
925
+ tabRemoteSource.goOnline();
926
+ const reloadRequestedSubscription = workerClient.subscribe("reload-requested", () => {
927
+ globalThis.location?.reload();
928
+ });
929
+ cleanupCallbacks.push(reloadRequestedSubscription.unsubscribe);
930
+ const reloadIfStorageReplaced = (storageGeneration) => {
931
+ if (storageGeneration !== workerClientSnapshot.storageGeneration) {
932
+ storageReplacedSubscription.unsubscribe();
933
+ globalThis.location?.reload();
934
+ }
935
+ };
936
+ const storageReplacedSubscription = workerClient.subscribe("state-changed", (event) => {
937
+ reloadIfStorageReplaced(event.payload.storageGeneration);
938
+ });
939
+ cleanupCallbacks.push(storageReplacedSubscription.unsubscribe);
940
+ reloadIfStorageReplaced(workerClient.getStorageGeneration());
941
+ const sync = () => {
942
+ workerClient.sync().catch((error) => console.warn("Failed to start sync", error));
943
+ };
944
+ const syncWhenVisible = () => {
945
+ if (globalThis.document?.visibilityState === "visible") sync();
946
+ };
947
+ const dropConnection = () => {
948
+ workerClient.dropConnection().catch((error) => console.warn("Failed to drop connection", error));
949
+ };
950
+ cleanupCallbacks.push(() => {
951
+ globalThis.document?.removeEventListener("visibilitychange", syncWhenVisible);
952
+ globalThis.removeEventListener?.("online", sync);
953
+ globalThis.removeEventListener?.("offline", dropConnection);
954
+ });
955
+ globalThis.document?.addEventListener("visibilitychange", syncWhenVisible);
956
+ globalThis.addEventListener?.("online", sync);
957
+ globalThis.addEventListener?.("offline", dropConnection);
958
+ perf.logEnd("createSyncedDb", "initialized", "info");
959
+ const exportData = createExportData({
960
+ reactiveDb,
961
+ tablesConfig: options.syncDbSchema.tablesConfig,
962
+ schemaVersion: workerClientSnapshot.schemaVersion
963
+ });
964
+ const importData = (data, opts) => workerClient.importData(data, opts);
965
+ const syncedDb = {
966
+ db: createSyncedDbDatabase(reactiveDb),
967
+ state: {
968
+ getState: workerClient.getState.bind(workerClient),
969
+ subscribe: (onChange) => {
970
+ const { unsubscribe } = workerClient.subscribe("state-changed", onChange);
971
+ return unsubscribe;
972
+ },
973
+ goOnline: workerClient.goOnline.bind(workerClient),
974
+ goOffline: workerClient.goOffline.bind(workerClient),
975
+ sync
976
+ },
977
+ /**
978
+ * Ask the elected worker to broadcast a page reload to all tabs for this dbId.
979
+ *
980
+ * With `clean: true` the worker durably records a reset request epoch before
981
+ * broadcasting, so the worker elected on the next startup initializes with
982
+ * `clearOnInit: true` and wipes the persisted DB. Destructive — use as a
983
+ * recovery path when the durable worker DB may be de-synced.
984
+ *
985
+ * Pending in-memory tab events are not preserved, and the returned promise
986
+ * may never settle in the caller — the page typically unloads first.
987
+ */
988
+ requestReload: async (options) => {
989
+ await workerClient.requestReload(options);
990
+ setTimeout(() => globalThis.location?.reload(), 250);
991
+ },
992
+ subscribe: workerClient.subscribe,
993
+ exportData,
994
+ importData,
995
+ dispose,
996
+ _internal: {
997
+ executeAsync: workerClient.execute.bind(workerClient),
998
+ getMemoryQueryTables: reactiveDb.getTablesUsed.bind(reactiveDb),
999
+ getSharedLiveQueriesSnapshot: reactiveDb.getSharedLiveQueriesSnapshot.bind(reactiveDb),
1000
+ crdtTableNames: options.syncDbSchema.tablesConfig.map((table) => table.crdtTableName),
1001
+ crdtTablesConfig: options.syncDbSchema.tablesConfig,
1002
+ tables: options.syncDbSchema.tables,
1003
+ schemaVersion: workerClientSnapshot.schemaVersion,
1004
+ migrationVersions: Object.keys(options.syncDbSchema.migrations).map(Number).sort((a, b) => a - b)
1005
+ }
1006
+ };
1007
+ unregisterDevtools = getOrCreateSQLiteSyncDevtoolsRegistry().register({
1008
+ instanceId,
1009
+ dbId: options.dbId,
1010
+ createdAt: Date.now(),
1011
+ instance: syncedDb
1012
+ });
1013
+ return syncedDb;
1014
+ } catch (error) {
1015
+ await dispose();
1016
+ throw error;
1017
+ }
992
1018
  }
993
1019
  //#endregion
994
1020
  export { CRDT_EVENT_NO_OP_PAYLOAD, ColumnBuilder, CrdtEventValidationError, HLCCounter, RESET_REQUEST_TTL_MS, SQLiteDbWrapper, SQLiteReactiveDb, TableBuilder, TypedBroadcastChannel, TypedEvent, admitClientEvents, applyMemoryDbSchema, applyWorkerDbSchema, compareHLC, createCrdtApplyFunction, createCrdtStorage, createCrdtStorageMutator, createCrdtSyncProducer, createCrdtSyncRemoteSource, createDeferredPromise, createExportData, createImportData, createKvStoreTableQuery, createMigrations, createMigrator, createSQLiteCrdtApplyFunction, createSQLiteKvStore, createStoredValue, createSyncedDb, createTypedEventTarget, defineSyncSchema, deserializeHLC, dummyKysely, formatSchemaVerificationIssues, generateId, introspectDb, isNoOpCrdtEventPayload, jsonSafeParse, makeCrdtTable, parametersAreEqual, quoteId, serializeHLC, startPerformanceLogger, t, tryCatch, tryCatchAsync, validateNewCrdtEvent, verifySyncSchema, xxhash };