korajs 0.5.0 → 0.6.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/index.cjs CHANGED
@@ -30,38 +30,84 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
+ AppNotReadyError: () => import_core10.AppNotReadyError,
33
34
  HybridLogicalClock: () => import_core7.HybridLogicalClock,
34
35
  KoraError: () => import_core10.KoraError,
35
36
  MergeEngine: () => import_merge3.MergeEngine,
36
- SequenceManager: () => import_store6.SequenceManager,
37
- Store: () => import_store6.Store,
38
- SyncEngine: () => import_sync3.SyncEngine,
39
- TransactionContext: () => import_store7.TransactionContext,
40
- WebSocketTransport: () => import_sync3.WebSocketTransport,
37
+ SequenceManager: () => import_store7.SequenceManager,
38
+ Store: () => import_store7.Store,
39
+ SyncEngine: () => import_sync7.SyncEngine,
40
+ TransactionContext: () => import_store8.TransactionContext,
41
+ WebSocketTransport: () => import_sync7.WebSocketTransport,
41
42
  createApp: () => createApp,
42
43
  createOperation: () => import_core9.createOperation,
43
- decodeAuditExport: () => import_store5.decodeAuditExport,
44
+ decodeAuditExport: () => import_store6.decodeAuditExport,
44
45
  defineSchema: () => import_core6.defineSchema,
45
- exportBackup: () => import_store8.exportBackup,
46
+ exportBackup: () => import_store9.exportBackup,
46
47
  generateUUIDv7: () => import_core8.generateUUIDv7,
47
48
  migrate: () => import_core6.migrate,
48
49
  op: () => import_core11.op,
49
- readAuditExportManifest: () => import_store5.readAuditExportManifest,
50
- readBackupManifest: () => import_store8.readBackupManifest,
51
- restoreBackup: () => import_store8.restoreBackup,
50
+ readAuditExportManifest: () => import_store6.readAuditExportManifest,
51
+ readBackupManifest: () => import_store9.readBackupManifest,
52
+ restoreBackup: () => import_store9.restoreBackup,
52
53
  t: () => import_core6.t,
53
- verifyAuditExportChecksum: () => import_store5.verifyAuditExportChecksum,
54
- verifyBackupChecksum: () => import_store8.verifyBackupChecksum
54
+ verifyAuditExportChecksum: () => import_store6.verifyAuditExportChecksum,
55
+ verifyBackupChecksum: () => import_store9.verifyBackupChecksum
55
56
  });
56
57
  module.exports = __toCommonJS(index_exports);
57
58
 
58
59
  // src/create-app.ts
59
- var import_core5 = require("@korajs/core");
60
60
  var import_internal5 = require("@korajs/core/internal");
61
- var import_devtools = require("@korajs/devtools");
62
61
  var import_merge2 = require("@korajs/merge");
62
+ var import_store5 = require("@korajs/store");
63
+
64
+ // src/collection-accessor.ts
65
+ var import_core = require("@korajs/core");
66
+ function createCollectionAccessor(collectionName, getStore) {
67
+ const notReady = (action) => new import_core.AppNotReadyError(
68
+ `Cannot ${action} on collection "${collectionName}" before app.ready. Await app.ready or use <KoraProvider app={app}>.`
69
+ );
70
+ return {
71
+ async insert(data) {
72
+ const currentStore = getStore();
73
+ if (!currentStore) {
74
+ throw notReady("insert into");
75
+ }
76
+ return currentStore.collection(collectionName).insert(data);
77
+ },
78
+ async findById(id) {
79
+ const currentStore = getStore();
80
+ if (!currentStore) return null;
81
+ return currentStore.collection(collectionName).findById(id);
82
+ },
83
+ async update(id, data) {
84
+ const currentStore = getStore();
85
+ if (!currentStore) {
86
+ throw notReady("update");
87
+ }
88
+ return currentStore.collection(collectionName).update(id, data);
89
+ },
90
+ async delete(id) {
91
+ const currentStore = getStore();
92
+ if (!currentStore) {
93
+ throw notReady("delete from");
94
+ }
95
+ return currentStore.collection(collectionName).delete(id);
96
+ },
97
+ where(conditions) {
98
+ const currentStore = getStore();
99
+ if (!currentStore) {
100
+ throw notReady("query");
101
+ }
102
+ return currentStore.collection(collectionName).where(conditions);
103
+ }
104
+ };
105
+ }
106
+
107
+ // src/initialize-app.ts
108
+ var import_core4 = require("@korajs/core");
63
109
  var import_store4 = require("@korajs/store");
64
- var import_sync2 = require("@korajs/sync");
110
+ var import_sync3 = require("@korajs/sync");
65
111
 
66
112
  // src/adapter-resolver.ts
67
113
  function importOptionalPeer(specifier) {
@@ -111,17 +157,6 @@ async function createAdapter(type, dbName, workerUrl, emitter, workerResponseTim
111
157
  }
112
158
  }
113
159
 
114
- // src/app-not-ready-error.ts
115
- var import_core = require("@korajs/core");
116
- var AppNotReadyError = class extends import_core.KoraError {
117
- constructor(detail) {
118
- super(detail, "APP_NOT_READY", {
119
- fix: "Await app.ready, or wrap your UI in <KoraProvider app={app}> before calling useQuery()."
120
- });
121
- this.name = "AppNotReadyError";
122
- }
123
- };
124
-
125
160
  // src/apply-pipeline.ts
126
161
  var import_core3 = require("@korajs/core");
127
162
  var import_internal2 = require("@korajs/core/internal");
@@ -706,6 +741,15 @@ function wireAuditPersistence(store, emitter) {
706
741
  };
707
742
  }
708
743
 
744
+ // src/create-sync-transport.ts
745
+ var import_sync = require("@korajs/sync");
746
+ function createSyncTransport(sync) {
747
+ if (sync.transport === "http") {
748
+ return new import_sync.HttpLongPollingTransport();
749
+ }
750
+ return new import_sync.WebSocketTransport();
751
+ }
752
+
709
753
  // src/merge-aware-sync-store.ts
710
754
  var MergeAwareSyncStore = class {
711
755
  constructor(store, mergeEngine, emitter, options) {
@@ -777,7 +821,7 @@ function operationFromQueuePayload(payload) {
777
821
 
778
822
  // src/store-sync-state.ts
779
823
  var import_store3 = require("@korajs/store");
780
- var import_sync = require("@korajs/sync");
824
+ var import_sync2 = require("@korajs/sync");
781
825
  var StoreSyncStatePersistence = class {
782
826
  constructor(store, scope) {
783
827
  this.store = store;
@@ -800,19 +844,20 @@ var StoreSyncStatePersistence = class {
800
844
  }
801
845
  async getUnsyncedOperations(serverVector) {
802
846
  const ops = await this.store.getUnsyncedOperations(serverVector);
803
- return ops.filter((op2) => (0, import_sync.operationMatchesScope)(op2, this.scope));
847
+ return ops.filter((op2) => (0, import_sync2.operationMatchesScope)(op2, this.scope));
804
848
  }
805
849
  loadDeltaCursor() {
806
- return this.store.loadDeltaCursor().then((encoded) => (0, import_sync.decodeDeltaCursor)(encoded));
850
+ return this.store.loadDeltaCursor().then((encoded) => (0, import_sync2.decodeDeltaCursor)(encoded));
807
851
  }
808
852
  async saveDeltaCursor(cursor) {
809
- await this.store.saveDeltaCursor(cursor ? (0, import_sync.encodeDeltaCursor)(cursor) : null);
853
+ await this.store.saveDeltaCursor(cursor ? (0, import_sync2.encodeDeltaCursor)(cursor) : null);
810
854
  }
811
855
  };
812
856
 
813
857
  // src/sync-query-bridge.ts
814
858
  function queryDescriptorToSyncSubset(descriptor) {
815
859
  const where = {};
860
+ const skippedFields = [];
816
861
  for (const [field, value] of Object.entries(descriptor.where)) {
817
862
  if (value === null || value === void 0) {
818
863
  where[field] = value;
@@ -820,7 +865,14 @@ function queryDescriptorToSyncSubset(descriptor) {
820
865
  }
821
866
  if (typeof value !== "object" || Array.isArray(value)) {
822
867
  where[field] = value;
868
+ continue;
823
869
  }
870
+ skippedFields.push(field);
871
+ }
872
+ if (skippedFields.length > 0 && typeof console !== "undefined") {
873
+ console.warn(
874
+ `[Kora] Sync query subset omitted non-equality filters on ${descriptor.collection}: ${skippedFields.join(", ")}. Only plain equality WHERE clauses are registered for incremental sync.`
875
+ );
824
876
  }
825
877
  if (Object.keys(where).length === 0) {
826
878
  return null;
@@ -846,297 +898,148 @@ function createSyncQuerySubscriptionHook(getSyncEngine) {
846
898
  };
847
899
  }
848
900
 
849
- // src/sync-status-bridge.ts
850
- var OFFLINE_STATUS = Object.freeze({
851
- status: "offline",
852
- pendingOperations: 0,
853
- lastSyncedAt: null,
854
- lastSuccessfulPush: null,
855
- lastSuccessfulPull: null,
856
- conflicts: 0
857
- });
858
- var SYNC_STATUS_EVENT_TYPES = [
859
- "sync:connected",
860
- "sync:disconnected",
861
- "sync:schema-mismatch",
862
- "sync:auth-failed",
863
- "sync:sent",
864
- "sync:received",
865
- "sync:acknowledged",
866
- "sync:apply-failed",
867
- "sync:diagnostics",
868
- "sync:initial-sync-progress"
869
- ];
870
- function createSyncStatusBridge(emitter, getSyncEngine) {
871
- let currentStatus = OFFLINE_STATUS;
872
- const listeners = /* @__PURE__ */ new Set();
873
- const refresh = () => {
874
- const engine = getSyncEngine();
875
- const next = engine ? engine.getStatus() : OFFLINE_STATUS;
876
- const prevSerialized = JSON.stringify(currentStatus);
877
- const nextSerialized = JSON.stringify(next);
878
- if (prevSerialized === nextSerialized) {
879
- return;
901
+ // src/initialize-app.ts
902
+ async function initializeApp(config, emitter, mergeEngine) {
903
+ const adapterType = config.store?.adapter ?? detectAdapterType();
904
+ const dbName = config.store?.name ?? "kora-db";
905
+ const adapter = await createAdapter(
906
+ adapterType,
907
+ dbName,
908
+ config.store?.workerUrl,
909
+ emitter,
910
+ config.store?.workerResponseTimeoutMs,
911
+ config.store?.sharedWorkerUrl
912
+ );
913
+ const authBinding = config.sync?.authClient ?? null;
914
+ const authNodeId = authBinding?.resolveNodeId ? await authBinding.resolveNodeId() : void 0;
915
+ let syncEngine = null;
916
+ const store = new import_store4.Store({
917
+ schema: config.schema,
918
+ adapter,
919
+ emitter,
920
+ dbName,
921
+ nodeId: authNodeId,
922
+ isolation: authNodeId ? "shared" : config.store?.isolation,
923
+ ...config.sync ? { onQuerySubscribed: createSyncQuerySubscriptionHook(() => syncEngine) } : {}
924
+ });
925
+ await store.open();
926
+ let recordConflict;
927
+ const applyPipeline = new ApplyPipeline({
928
+ store,
929
+ mergeEngine,
930
+ emitter,
931
+ onMergeConflict: () => recordConflict?.()
932
+ });
933
+ store.setLocalMutationHandler(applyPipeline);
934
+ const unsubscribeAudit = wireAuditPersistence(store, emitter);
935
+ let unsubscribeSync = null;
936
+ if (config.sync) {
937
+ const transport = createSyncTransport(config.sync);
938
+ const mergeAwareStore = new MergeAwareSyncStore(store, mergeEngine, emitter, {
939
+ onMergeConflict: () => recordConflict?.()
940
+ });
941
+ let scopeMap = config.sync.scope ? (0, import_core4.buildScopeMap)(config.schema, config.sync.scope) : void 0;
942
+ if (authBinding?.resolveScopeMap) {
943
+ scopeMap = await authBinding.resolveScopeMap() ?? scopeMap;
880
944
  }
881
- currentStatus = next;
882
- for (const listener of listeners) {
883
- listener(currentStatus);
945
+ const syncAuth = authBinding?.auth ?? config.sync.auth;
946
+ const encryptor = config.sync.encryption?.enabled === true ? await import_sync3.SyncEncryptor.create(config.sync.encryption) : void 0;
947
+ syncEngine = new import_sync3.SyncEngine({
948
+ transport,
949
+ store: mergeAwareStore,
950
+ config: {
951
+ url: config.sync.url,
952
+ transport: config.sync.transport,
953
+ auth: syncAuth,
954
+ batchSize: config.sync.batchSize,
955
+ schemaVersion: config.sync.schemaVersion ?? config.schema.version,
956
+ scopeMap,
957
+ encryption: config.sync.encryption,
958
+ strictHandshake: config.sync.strictHandshake,
959
+ operationTransforms: config.sync.operationTransforms
960
+ },
961
+ emitter,
962
+ queueStorage: new StoreQueueStorage(adapter),
963
+ syncState: new StoreSyncStatePersistence(store, scopeMap),
964
+ encryptor
965
+ });
966
+ recordConflict = () => syncEngine?.recordConflict();
967
+ unsubscribeSync = emitter.on("operation:created", (event) => {
968
+ if (syncEngine) {
969
+ syncEngine.pushOperation(event.operation);
970
+ }
971
+ });
972
+ }
973
+ return {
974
+ store,
975
+ syncEngine,
976
+ unsubscribeSync,
977
+ unsubscribeAudit,
978
+ authBinding
979
+ };
980
+ }
981
+
982
+ // src/sequences-accessor.ts
983
+ function createSequencesAccessor(ready, getStore) {
984
+ const requireStore = async () => {
985
+ await ready;
986
+ const store = getStore();
987
+ if (!store) {
988
+ throw new Error("Store not initialized. Await app.ready before using sequences.");
884
989
  }
990
+ return store;
885
991
  };
886
- const unsubs = [];
887
- for (const type of SYNC_STATUS_EVENT_TYPES) {
888
- unsubs.push(emitter.on(type, refresh));
889
- }
890
- const bridge = {
891
- get status() {
892
- return currentStatus;
992
+ return {
993
+ async next(name, config) {
994
+ const store = await requireStore();
995
+ return store.getSequenceManager().next(name, config);
893
996
  },
894
- subscribe(listener) {
895
- listeners.add(listener);
896
- listener(currentStatus);
897
- return () => {
898
- listeners.delete(listener);
899
- };
997
+ async current(name, config) {
998
+ const store = await requireStore();
999
+ return store.getSequenceManager().current(name, config);
900
1000
  },
901
- refresh,
902
- destroy() {
903
- for (const unsub of unsubs) {
904
- unsub();
905
- }
906
- unsubs.length = 0;
907
- listeners.clear();
1001
+ async reset(name, config) {
1002
+ const store = await requireStore();
1003
+ return store.getSequenceManager().reset(name, config);
908
1004
  }
909
1005
  };
910
- refresh();
911
- return bridge;
912
1006
  }
913
1007
 
914
- // src/validate-config.ts
915
- var import_core4 = require("@korajs/core");
916
- function validateCreateAppConfig(config) {
917
- if (!config.schema) {
918
- throw new import_core4.SchemaValidationError("createApp requires a schema.", {
919
- fix: "Pass schema: defineSchema({ version: 1, collections: { ... } })"
920
- });
921
- }
922
- if (config.schema.version < 1) {
923
- throw new import_core4.SchemaValidationError("Schema version must be at least 1.", {
924
- version: config.schema.version
925
- });
926
- }
927
- const collectionNames = Object.keys(config.schema.collections);
928
- if (collectionNames.length === 0) {
929
- throw new import_core4.SchemaValidationError("Schema must define at least one collection.", {
930
- fix: "Add entries under collections in defineSchema()."
931
- });
932
- }
933
- if (config.sync) {
934
- validateSyncUrl(config.sync.url, config.sync.transport ?? "websocket");
935
- }
936
- const adapter = config.store?.adapter ?? detectAdapterType();
937
- const isBrowser = typeof globalThis !== "undefined" && typeof globalThis.window !== "undefined";
938
- if (isBrowser && (adapter === "sqlite-wasm" || adapter === "indexeddb") && !config.store?.workerUrl) {
939
- throw new import_core4.KoraError(
940
- "Browser storage requires store.workerUrl pointing to the SQLite WASM worker script.",
941
- "MISSING_WORKER_URL",
942
- {
943
- adapter,
944
- fix: 'Add store: { workerUrl: "/sqlite-wasm-worker.js" } (see create-kora-app templates).'
945
- }
946
- );
1008
+ // src/setup-devtools.ts
1009
+ var import_devtools = require("@korajs/devtools");
1010
+ function setupDevtools(config, emitter) {
1011
+ if (!config.devtools) {
1012
+ return { instrumenter: null, destroyOverlay: null };
947
1013
  }
948
- }
949
- function validateSyncUrl(url, transport) {
950
- if (!url || url.trim().length === 0) {
951
- throw new import_core4.KoraError("sync.url is required when sync is configured.", "INVALID_SYNC_URL", {
952
- fix: 'Pass sync: { url: "wss://your-server/kora" }.'
1014
+ const instrumenter = new import_devtools.Instrumenter(emitter, {
1015
+ bridgeEnabled: typeof globalThis !== "undefined" && "window" in globalThis
1016
+ });
1017
+ let destroyOverlay = null;
1018
+ if (typeof globalThis !== "undefined" && "window" in globalThis) {
1019
+ void import("@korajs/devtools/overlay").then(({ mountKoraDevtoolsOverlay }) => {
1020
+ destroyOverlay = mountKoraDevtoolsOverlay(instrumenter);
1021
+ }).catch(() => {
953
1022
  });
954
1023
  }
955
- try {
956
- const parsed = new URL(url);
957
- if (transport === "http") {
958
- if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
959
- throw new Error("bad protocol");
960
- }
961
- } else if (parsed.protocol !== "ws:" && parsed.protocol !== "wss:") {
962
- throw new Error("bad protocol");
963
- }
964
- } catch {
965
- throw new import_core4.KoraError(
966
- `Invalid sync URL "${url}" for transport "${transport}".`,
967
- "INVALID_SYNC_URL",
968
- {
969
- url,
970
- transport,
971
- fix: transport === "http" ? "Use an absolute http:// or https:// URL." : "Use an absolute ws:// or wss:// URL."
972
- }
973
- );
974
- }
1024
+ return { instrumenter, destroyOverlay: () => destroyOverlay?.() };
975
1025
  }
976
1026
 
977
- // src/create-app.ts
978
- function createApp(config) {
979
- validateCreateAppConfig(config);
980
- const emitter = new import_internal5.SimpleEventEmitter();
981
- const mergeEngine = new import_merge2.MergeEngine();
982
- if (config.onSyncEvent) {
983
- const handler = config.onSyncEvent;
984
- const syncTypes = [
985
- "sync:connected",
986
- "sync:disconnected",
987
- "sync:schema-mismatch",
988
- "sync:auth-failed",
989
- "sync:sent",
990
- "sync:received",
991
- "sync:acknowledged",
992
- "sync:apply-failed",
993
- "sync:diagnostics",
994
- "sync:bandwidth",
995
- "sync:initial-sync-progress"
996
- ];
997
- for (const type of syncTypes) {
998
- emitter.on(type, (event) => {
999
- handler(event);
1000
- });
1001
- }
1002
- }
1003
- let store = null;
1004
- let syncEngine = null;
1005
- let unsubscribeSync = null;
1006
- let unsubscribeAudit = null;
1007
- let reconnectionManager = null;
1008
- let connectionMonitor = null;
1009
- let instrumenter = null;
1010
- let intentionalDisconnect = false;
1011
- let qualityInterval = null;
1012
- let syncStatusBridge = null;
1013
- let destroyDevtoolsOverlay = null;
1014
- if (config.devtools) {
1015
- instrumenter = new import_devtools.Instrumenter(emitter, {
1016
- bridgeEnabled: typeof globalThis !== "undefined" && "window" in globalThis
1017
- });
1018
- if (typeof globalThis !== "undefined" && "window" in globalThis) {
1019
- void import("@korajs/devtools/overlay").then(({ mountKoraDevtoolsOverlay }) => {
1020
- if (instrumenter) {
1021
- destroyDevtoolsOverlay = mountKoraDevtoolsOverlay(instrumenter);
1022
- }
1023
- }).catch(() => {
1024
- });
1025
- }
1027
+ // src/sync-control.ts
1028
+ var import_sync4 = require("@korajs/sync");
1029
+ function createSyncControl(options) {
1030
+ const { config, ready, state } = options;
1031
+ if (!config.sync) {
1032
+ return null;
1026
1033
  }
1027
- const ready = initializeAsync(config, emitter, mergeEngine).then((result) => {
1028
- store = result.store;
1029
- syncEngine = result.syncEngine;
1030
- unsubscribeSync = result.unsubscribeSync;
1031
- unsubscribeAudit = result.unsubscribeAudit;
1032
- const authBinding = result.authBinding;
1033
- if (config.sync) {
1034
- syncStatusBridge = createSyncStatusBridge(emitter, () => syncEngine);
1035
- syncStatusBridge.refresh();
1036
- }
1037
- if (config.sync && syncEngine && authBinding?.subscribe) {
1038
- authBinding.subscribe(() => {
1039
- const engine = syncEngine;
1040
- if (!engine) {
1041
- return;
1042
- }
1043
- void (async () => {
1044
- const headers = await authBinding.auth();
1045
- if (!headers.token) {
1046
- await engine.stop();
1047
- return;
1048
- }
1049
- if (authBinding.resolveScopeMap) {
1050
- const nextScope = await authBinding.resolveScopeMap();
1051
- engine.updateScope(nextScope);
1052
- }
1053
- const status = engine.getStatus().status;
1054
- if (status !== "offline") {
1055
- await engine.stop();
1056
- }
1057
- await engine.start();
1058
- })();
1059
- });
1060
- }
1061
- if (config.sync && syncEngine) {
1062
- connectionMonitor = new import_sync2.ConnectionMonitor();
1063
- reconnectionManager = new import_sync2.ReconnectionManager({
1064
- initialDelay: config.sync.reconnectInterval,
1065
- maxDelay: config.sync.maxReconnectInterval
1066
- });
1067
- emitter.on("sync:sent", () => connectionMonitor?.recordActivity());
1068
- emitter.on("sync:received", () => connectionMonitor?.recordActivity());
1069
- emitter.on("sync:acknowledged", () => connectionMonitor?.recordActivity());
1070
- emitter.on("sync:connected", () => {
1071
- if (qualityInterval !== null) clearInterval(qualityInterval);
1072
- qualityInterval = setInterval(() => {
1073
- if (connectionMonitor) {
1074
- emitter.emit({ type: "connection:quality", quality: connectionMonitor.getQuality() });
1075
- }
1076
- }, 5e3);
1077
- });
1078
- emitter.on("sync:disconnected", () => {
1079
- connectionMonitor?.reset();
1080
- if (qualityInterval !== null) {
1081
- clearInterval(qualityInterval);
1082
- qualityInterval = null;
1083
- }
1084
- });
1085
- const browserGlobal = globalThis;
1086
- if (typeof browserGlobal.addEventListener === "function") {
1087
- const engine = syncEngine;
1088
- browserGlobal.addEventListener("online", () => {
1089
- if (intentionalDisconnect || config.sync?.autoReconnect === false) return;
1090
- reconnectionManager?.wake();
1091
- reconnectionManager?.reset();
1092
- void engine.retryNow();
1093
- });
1094
- }
1095
- emitter.on("sync:schema-mismatch", () => {
1096
- reconnectionManager?.stop();
1097
- intentionalDisconnect = true;
1098
- });
1099
- if (config.sync.autoReconnect !== false) {
1100
- const engine = syncEngine;
1101
- emitter.on("sync:disconnected", () => {
1102
- if (intentionalDisconnect || engine.isSchemaBlocked()) return;
1103
- if (reconnectionManager?.isRunning()) return;
1104
- engine.setReconnecting(true);
1105
- reconnectionManager?.stop();
1106
- reconnectionManager?.start(async () => {
1107
- try {
1108
- await engine.start();
1109
- engine.setReconnecting(false);
1110
- return true;
1111
- } catch {
1112
- return false;
1113
- }
1114
- }).then(() => {
1115
- engine.setReconnecting(false);
1116
- });
1117
- });
1118
- }
1119
- if (config.sync.autoConnect === true && syncEngine) {
1120
- void syncEngine.start().catch(() => {
1121
- });
1122
- }
1123
- }
1124
- });
1125
- const offlineSyncStatus = () => ({
1126
- status: "offline",
1127
- pendingOperations: 0,
1128
- lastSyncedAt: null,
1129
- lastSuccessfulPush: null,
1130
- lastSuccessfulPull: null,
1131
- conflicts: 0
1132
- });
1133
- const syncControl = config.sync ? {
1034
+ const offlineSyncStatus = () => import_sync4.OFFLINE_SYNC_STATUS;
1035
+ const bridgeStatus = () => state.syncStatusBridge?.status ?? offlineSyncStatus();
1036
+ return {
1134
1037
  get status() {
1135
- return syncStatusBridge?.status ?? offlineSyncStatus();
1038
+ return bridgeStatus();
1136
1039
  },
1137
1040
  subscribeStatus(listener) {
1138
- if (syncStatusBridge) {
1139
- return syncStatusBridge.subscribe(listener);
1041
+ if (state.syncStatusBridge) {
1042
+ return state.syncStatusBridge.subscribe(listener);
1140
1043
  }
1141
1044
  listener(offlineSyncStatus());
1142
1045
  return () => {
@@ -1144,41 +1047,41 @@ function createApp(config) {
1144
1047
  },
1145
1048
  async connect() {
1146
1049
  await ready;
1147
- if (syncEngine) {
1148
- intentionalDisconnect = false;
1149
- reconnectionManager?.stop();
1150
- reconnectionManager?.reset();
1151
- await syncEngine.start();
1152
- syncStatusBridge?.refresh();
1050
+ if (state.syncEngine) {
1051
+ state.intentionalDisconnect = false;
1052
+ state.reconnectionManager?.stop();
1053
+ state.reconnectionManager?.reset();
1054
+ await state.syncEngine.start();
1055
+ state.syncStatusBridge?.refresh();
1153
1056
  }
1154
1057
  },
1155
1058
  async disconnect() {
1156
1059
  await ready;
1157
- if (syncEngine) {
1158
- intentionalDisconnect = true;
1159
- reconnectionManager?.stop();
1160
- await syncEngine.stop();
1161
- syncStatusBridge?.refresh();
1060
+ if (state.syncEngine) {
1061
+ state.intentionalDisconnect = true;
1062
+ state.reconnectionManager?.stop();
1063
+ await state.syncEngine.stop();
1064
+ state.syncStatusBridge?.refresh();
1162
1065
  }
1163
1066
  },
1164
1067
  getStatus() {
1165
- if (syncEngine) {
1166
- return syncEngine.getStatus();
1068
+ if (state.syncEngine) {
1069
+ return state.syncEngine.getStatus();
1167
1070
  }
1168
1071
  return offlineSyncStatus();
1169
1072
  },
1170
1073
  async retryNow() {
1171
1074
  await ready;
1172
- if (syncEngine) {
1173
- await syncEngine.retryNow();
1075
+ if (state.syncEngine) {
1076
+ await state.syncEngine.retryNow();
1174
1077
  }
1175
1078
  },
1176
1079
  clearSchemaBlock() {
1177
- syncEngine?.clearSchemaBlock();
1080
+ state.syncEngine?.clearSchemaBlock();
1178
1081
  },
1179
1082
  exportDiagnostics() {
1180
- if (syncEngine) {
1181
- return syncEngine.exportDiagnostics();
1083
+ if (state.syncEngine) {
1084
+ return state.syncEngine.exportDiagnostics();
1182
1085
  }
1183
1086
  return {
1184
1087
  state: "disconnected",
@@ -1203,11 +1106,199 @@ function createApp(config) {
1203
1106
  timestamp: Date.now()
1204
1107
  };
1205
1108
  }
1206
- } : null;
1207
- async function executeTransaction(fn, mutationName) {
1208
- await ready;
1209
- if (!store) {
1210
- throw new Error("Store not initialized. Await app.ready before using transactions.");
1109
+ };
1110
+ }
1111
+
1112
+ // src/auth-sync-coordinator.ts
1113
+ var AuthSyncCoordinator = class {
1114
+ constructor(getEngine, authBinding) {
1115
+ this.getEngine = getEngine;
1116
+ this.authBinding = authBinding;
1117
+ }
1118
+ getEngine;
1119
+ authBinding;
1120
+ inFlight = null;
1121
+ pending = false;
1122
+ disposed = false;
1123
+ scheduleReconnect() {
1124
+ if (this.disposed) {
1125
+ return;
1126
+ }
1127
+ if (this.inFlight) {
1128
+ this.pending = true;
1129
+ return;
1130
+ }
1131
+ this.inFlight = this.run().finally(() => {
1132
+ this.inFlight = null;
1133
+ if (this.pending && !this.disposed) {
1134
+ this.pending = false;
1135
+ this.scheduleReconnect();
1136
+ }
1137
+ });
1138
+ }
1139
+ destroy() {
1140
+ this.disposed = true;
1141
+ this.pending = false;
1142
+ }
1143
+ async run() {
1144
+ const engine = this.getEngine();
1145
+ if (!engine) {
1146
+ return;
1147
+ }
1148
+ const headers = await this.authBinding.auth();
1149
+ if (!headers.token) {
1150
+ await engine.stop();
1151
+ return;
1152
+ }
1153
+ if (this.authBinding.resolveScopeMap) {
1154
+ const nextScope = await this.authBinding.resolveScopeMap();
1155
+ engine.updateScope(nextScope);
1156
+ }
1157
+ const status = engine.getStatus().status;
1158
+ if (status !== "offline") {
1159
+ await engine.stop();
1160
+ }
1161
+ await engine.start();
1162
+ }
1163
+ };
1164
+
1165
+ // src/sync-status-bridge.ts
1166
+ var import_sync5 = require("@korajs/sync");
1167
+ function createSyncStatusBridge(emitter, getSyncEngine) {
1168
+ const controller = (0, import_sync5.createSyncStatusController)({
1169
+ getSyncEngine,
1170
+ subscribeSyncStatus: null,
1171
+ events: emitter
1172
+ });
1173
+ return {
1174
+ get status() {
1175
+ return controller.getSnapshot();
1176
+ },
1177
+ subscribe(listener) {
1178
+ return controller.subscribe(() => {
1179
+ listener(controller.getSnapshot());
1180
+ });
1181
+ },
1182
+ refresh: () => controller.refresh(),
1183
+ destroy: () => controller.destroy()
1184
+ };
1185
+ }
1186
+
1187
+ // src/sync-lifecycle.ts
1188
+ var import_sync6 = require("@korajs/sync");
1189
+ function wireSyncLifecycleAfterReady(config, emitter, state, init) {
1190
+ state.syncEngine = init.syncEngine;
1191
+ if (!config.sync) {
1192
+ return;
1193
+ }
1194
+ state.syncStatusBridge = createSyncStatusBridge(emitter, () => state.syncEngine);
1195
+ state.syncStatusBridge.refresh();
1196
+ if (state.syncEngine && init.authBinding?.subscribe) {
1197
+ state.authSyncCoordinator = new AuthSyncCoordinator(() => state.syncEngine, init.authBinding);
1198
+ init.authBinding.subscribe(() => {
1199
+ state.authSyncCoordinator?.scheduleReconnect();
1200
+ });
1201
+ }
1202
+ if (!state.syncEngine) {
1203
+ return;
1204
+ }
1205
+ const syncEngine = state.syncEngine;
1206
+ state.connectionMonitor = new import_sync6.ConnectionMonitor();
1207
+ state.reconnectionManager = new import_sync6.ReconnectionManager({
1208
+ initialDelay: config.sync.reconnectInterval,
1209
+ maxDelay: config.sync.maxReconnectInterval
1210
+ });
1211
+ emitter.on("sync:sent", () => state.connectionMonitor?.recordActivity());
1212
+ emitter.on("sync:received", () => state.connectionMonitor?.recordActivity());
1213
+ emitter.on("sync:acknowledged", () => state.connectionMonitor?.recordActivity());
1214
+ emitter.on("sync:connected", () => {
1215
+ if (state.qualityInterval !== null) {
1216
+ clearInterval(state.qualityInterval);
1217
+ }
1218
+ state.qualityInterval = setInterval(() => {
1219
+ if (state.connectionMonitor) {
1220
+ emitter.emit({
1221
+ type: "connection:quality",
1222
+ quality: state.connectionMonitor.getQuality()
1223
+ });
1224
+ }
1225
+ }, 5e3);
1226
+ });
1227
+ emitter.on("sync:disconnected", () => {
1228
+ state.connectionMonitor?.reset();
1229
+ if (state.qualityInterval !== null) {
1230
+ clearInterval(state.qualityInterval);
1231
+ state.qualityInterval = null;
1232
+ }
1233
+ });
1234
+ const browserGlobal = globalThis;
1235
+ if (typeof browserGlobal.addEventListener === "function") {
1236
+ const onOnline = () => {
1237
+ if (state.intentionalDisconnect || config.sync?.autoReconnect === false) {
1238
+ return;
1239
+ }
1240
+ state.reconnectionManager?.wake();
1241
+ state.reconnectionManager?.reset();
1242
+ void syncEngine.retryNow();
1243
+ };
1244
+ browserGlobal.addEventListener("online", onOnline);
1245
+ state.removeOnlineListener = () => {
1246
+ browserGlobal.removeEventListener?.("online", onOnline);
1247
+ };
1248
+ }
1249
+ emitter.on("sync:schema-mismatch", () => {
1250
+ state.reconnectionManager?.stop();
1251
+ state.intentionalDisconnect = true;
1252
+ });
1253
+ if (config.sync.autoReconnect !== false) {
1254
+ emitter.on("sync:disconnected", () => {
1255
+ if (state.intentionalDisconnect || syncEngine.isSchemaBlocked()) {
1256
+ return;
1257
+ }
1258
+ if (state.reconnectionManager?.isRunning()) {
1259
+ return;
1260
+ }
1261
+ syncEngine.setReconnecting(true);
1262
+ state.reconnectionManager?.stop();
1263
+ state.reconnectionManager?.start(async () => {
1264
+ try {
1265
+ await syncEngine.start();
1266
+ syncEngine.setReconnecting(false);
1267
+ return true;
1268
+ } catch {
1269
+ return false;
1270
+ }
1271
+ }).then(() => {
1272
+ syncEngine.setReconnecting(false);
1273
+ });
1274
+ });
1275
+ }
1276
+ if (config.sync.autoConnect === true) {
1277
+ void syncEngine.start().catch(() => {
1278
+ });
1279
+ }
1280
+ }
1281
+ function teardownSyncLifecycle(state) {
1282
+ if (state.qualityInterval !== null) {
1283
+ clearInterval(state.qualityInterval);
1284
+ state.qualityInterval = null;
1285
+ }
1286
+ state.reconnectionManager?.stop();
1287
+ state.syncStatusBridge?.destroy();
1288
+ state.syncStatusBridge = null;
1289
+ state.authSyncCoordinator?.destroy();
1290
+ state.authSyncCoordinator = null;
1291
+ state.removeOnlineListener?.();
1292
+ state.removeOnlineListener = null;
1293
+ }
1294
+
1295
+ // src/transaction-executor.ts
1296
+ function createTransactionExecutor(config, ready, getStore) {
1297
+ return async (fn, mutationName) => {
1298
+ await ready;
1299
+ const store = getStore();
1300
+ if (!store) {
1301
+ throw new Error("Store not initialized. Await app.ready before using transactions.");
1211
1302
  }
1212
1303
  const collectionNames = Object.keys(config.schema.collections);
1213
1304
  return store.transaction(async (tx) => {
@@ -1226,29 +1317,130 @@ function createApp(config) {
1226
1317
  }
1227
1318
  await fn(proxy);
1228
1319
  });
1320
+ };
1321
+ }
1322
+
1323
+ // src/validate-config.ts
1324
+ var import_core5 = require("@korajs/core");
1325
+ function validateCreateAppConfig(config) {
1326
+ if (!config.schema) {
1327
+ throw new import_core5.SchemaValidationError("createApp requires a schema.", {
1328
+ fix: "Pass schema: defineSchema({ version: 1, collections: { ... } })"
1329
+ });
1229
1330
  }
1230
- const sequences = {
1231
- async next(name, config2) {
1232
- await ready;
1233
- if (!store) throw new Error("Store not initialized. Await app.ready before using sequences.");
1234
- return store.getSequenceManager().next(name, config2);
1235
- },
1236
- async current(name, config2) {
1237
- await ready;
1238
- if (!store) throw new Error("Store not initialized. Await app.ready before using sequences.");
1239
- return store.getSequenceManager().current(name, config2);
1240
- },
1241
- async reset(name, config2) {
1242
- await ready;
1243
- if (!store) throw new Error("Store not initialized. Await app.ready before using sequences.");
1244
- return store.getSequenceManager().reset(name, config2);
1331
+ if (config.schema.version < 1) {
1332
+ throw new import_core5.SchemaValidationError("Schema version must be at least 1.", {
1333
+ version: config.schema.version
1334
+ });
1335
+ }
1336
+ const collectionNames = Object.keys(config.schema.collections);
1337
+ if (collectionNames.length === 0) {
1338
+ throw new import_core5.SchemaValidationError("Schema must define at least one collection.", {
1339
+ fix: "Add entries under collections in defineSchema()."
1340
+ });
1341
+ }
1342
+ if (config.sync) {
1343
+ validateSyncUrl(config.sync.url, config.sync.transport ?? "websocket");
1344
+ }
1345
+ const adapter = config.store?.adapter ?? detectAdapterType();
1346
+ const isBrowser = typeof globalThis !== "undefined" && typeof globalThis.window !== "undefined";
1347
+ if (isBrowser && (adapter === "sqlite-wasm" || adapter === "indexeddb") && !config.store?.workerUrl) {
1348
+ throw new import_core5.KoraError(
1349
+ "Browser storage requires store.workerUrl pointing to the SQLite WASM worker script.",
1350
+ "MISSING_WORKER_URL",
1351
+ {
1352
+ adapter,
1353
+ fix: 'Add store: { workerUrl: "/sqlite-wasm-worker.js" } (see create-kora-app templates).'
1354
+ }
1355
+ );
1356
+ }
1357
+ }
1358
+ function validateSyncUrl(url, transport) {
1359
+ if (!url || url.trim().length === 0) {
1360
+ throw new import_core5.KoraError("sync.url is required when sync is configured.", "INVALID_SYNC_URL", {
1361
+ fix: 'Pass sync: { url: "wss://your-server/kora" }.'
1362
+ });
1363
+ }
1364
+ try {
1365
+ const parsed = new URL(url);
1366
+ if (transport === "http") {
1367
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
1368
+ throw new Error("bad protocol");
1369
+ }
1370
+ } else if (parsed.protocol !== "ws:" && parsed.protocol !== "wss:") {
1371
+ throw new Error("bad protocol");
1245
1372
  }
1373
+ } catch {
1374
+ throw new import_core5.KoraError(
1375
+ `Invalid sync URL "${url}" for transport "${transport}".`,
1376
+ "INVALID_SYNC_URL",
1377
+ {
1378
+ url,
1379
+ transport,
1380
+ fix: transport === "http" ? "Use an absolute http:// or https:// URL." : "Use an absolute ws:// or wss:// URL."
1381
+ }
1382
+ );
1383
+ }
1384
+ }
1385
+
1386
+ // src/wire-sync-event-forwarding.ts
1387
+ var SYNC_EVENT_TYPES = [
1388
+ "sync:connected",
1389
+ "sync:disconnected",
1390
+ "sync:schema-mismatch",
1391
+ "sync:auth-failed",
1392
+ "sync:sent",
1393
+ "sync:received",
1394
+ "sync:acknowledged",
1395
+ "sync:apply-failed",
1396
+ "sync:diagnostics",
1397
+ "sync:bandwidth",
1398
+ "sync:initial-sync-progress"
1399
+ ];
1400
+ function wireSyncEventForwarding(emitter, handler) {
1401
+ for (const type of SYNC_EVENT_TYPES) {
1402
+ emitter.on(type, (event) => {
1403
+ handler(event);
1404
+ });
1405
+ }
1406
+ }
1407
+
1408
+ // src/create-app.ts
1409
+ function createApp(config) {
1410
+ validateCreateAppConfig(config);
1411
+ const emitter = new import_internal5.SimpleEventEmitter();
1412
+ const mergeEngine = new import_merge2.MergeEngine();
1413
+ if (config.onSyncEvent) {
1414
+ wireSyncEventForwarding(emitter, config.onSyncEvent);
1415
+ }
1416
+ let store = null;
1417
+ let unsubscribeSync = null;
1418
+ let unsubscribeAudit = null;
1419
+ const syncState = {
1420
+ syncEngine: null,
1421
+ syncStatusBridge: null,
1422
+ authSyncCoordinator: null,
1423
+ reconnectionManager: null,
1424
+ connectionMonitor: null,
1425
+ qualityInterval: null,
1426
+ intentionalDisconnect: false,
1427
+ removeOnlineListener: null
1246
1428
  };
1429
+ const devtools = setupDevtools(config, emitter);
1430
+ const queryStoreCache = new import_store5.QueryStoreCache(config.store?.name ?? "kora-db");
1431
+ const ready = initializeApp(config, emitter, mergeEngine).then((init) => {
1432
+ store = init.store;
1433
+ unsubscribeSync = init.unsubscribeSync;
1434
+ unsubscribeAudit = init.unsubscribeAudit;
1435
+ wireSyncLifecycleAfterReady(config, emitter, syncState, init);
1436
+ });
1437
+ const getStore = () => store;
1438
+ const executeTransaction = createTransactionExecutor(config, ready, getStore);
1247
1439
  const app = {
1248
1440
  ready,
1249
1441
  events: emitter,
1250
- sync: syncControl,
1251
- sequences,
1442
+ sync: createSyncControl({ config, ready, state: syncState }),
1443
+ sequences: createSequencesAccessor(ready, getStore),
1252
1444
  getStore() {
1253
1445
  if (!store) {
1254
1446
  throw new Error("Store not initialized. Await app.ready before accessing the store.");
@@ -1256,30 +1448,23 @@ function createApp(config) {
1256
1448
  return store;
1257
1449
  },
1258
1450
  getSyncEngine() {
1259
- return syncEngine;
1451
+ return syncState.syncEngine;
1452
+ },
1453
+ getQueryStoreCache() {
1454
+ return queryStoreCache;
1260
1455
  },
1261
- async transaction(fn) {
1456
+ transaction(fn) {
1262
1457
  return executeTransaction(fn);
1263
1458
  },
1264
- async mutation(name, fn) {
1459
+ mutation(name, fn) {
1265
1460
  return executeTransaction(fn, name);
1266
1461
  },
1267
1462
  async close() {
1268
1463
  await ready;
1269
- intentionalDisconnect = true;
1270
- if (qualityInterval !== null) {
1271
- clearInterval(qualityInterval);
1272
- qualityInterval = null;
1273
- }
1274
- reconnectionManager?.stop();
1275
- if (destroyDevtoolsOverlay) {
1276
- destroyDevtoolsOverlay();
1277
- destroyDevtoolsOverlay = null;
1278
- }
1279
- if (instrumenter) {
1280
- instrumenter.destroy();
1281
- instrumenter = null;
1282
- }
1464
+ syncState.intentionalDisconnect = true;
1465
+ teardownSyncLifecycle(syncState);
1466
+ devtools.destroyOverlay?.();
1467
+ devtools.instrumenter?.destroy();
1283
1468
  if (unsubscribeSync) {
1284
1469
  unsubscribeSync();
1285
1470
  unsubscribeSync = null;
@@ -1288,14 +1473,11 @@ function createApp(config) {
1288
1473
  unsubscribeAudit();
1289
1474
  unsubscribeAudit = null;
1290
1475
  }
1291
- if (syncEngine) {
1292
- await syncEngine.stop();
1293
- syncEngine = null;
1294
- }
1295
- if (syncStatusBridge) {
1296
- syncStatusBridge.destroy();
1297
- syncStatusBridge = null;
1476
+ if (syncState.syncEngine) {
1477
+ await syncState.syncEngine.stop();
1478
+ syncState.syncEngine = null;
1298
1479
  }
1480
+ queryStoreCache.clear();
1299
1481
  if (store) {
1300
1482
  await store.close();
1301
1483
  store = null;
@@ -1334,7 +1516,7 @@ function createApp(config) {
1334
1516
  for (const collectionName of Object.keys(config.schema.collections)) {
1335
1517
  Object.defineProperty(app, collectionName, {
1336
1518
  get() {
1337
- return createCollectionAccessor(collectionName, () => store);
1519
+ return createCollectionAccessor(collectionName, getStore);
1338
1520
  },
1339
1521
  enumerable: true,
1340
1522
  configurable: false
@@ -1342,190 +1524,23 @@ function createApp(config) {
1342
1524
  }
1343
1525
  return app;
1344
1526
  }
1345
- async function initializeAsync(config, emitter, mergeEngine) {
1346
- const adapterType = config.store?.adapter ?? detectAdapterType();
1347
- const dbName = config.store?.name ?? "kora-db";
1348
- const adapter = await createAdapter(
1349
- adapterType,
1350
- dbName,
1351
- config.store?.workerUrl,
1352
- emitter,
1353
- config.store?.workerResponseTimeoutMs,
1354
- config.store?.sharedWorkerUrl
1355
- );
1356
- const authBinding = config.sync?.authClient;
1357
- const authNodeId = authBinding?.resolveNodeId ? await authBinding.resolveNodeId() : void 0;
1358
- let syncEngine = null;
1359
- const store = new import_store4.Store({
1360
- schema: config.schema,
1361
- adapter,
1362
- emitter,
1363
- dbName,
1364
- nodeId: authNodeId,
1365
- isolation: authNodeId ? "shared" : config.store?.isolation,
1366
- ...config.sync ? { onQuerySubscribed: createSyncQuerySubscriptionHook(() => syncEngine) } : {}
1367
- });
1368
- await store.open();
1369
- let recordConflict;
1370
- const applyPipeline = new ApplyPipeline({
1371
- store,
1372
- mergeEngine,
1373
- emitter,
1374
- onMergeConflict: () => recordConflict?.()
1375
- });
1376
- store.setLocalMutationHandler(applyPipeline);
1377
- const unsubscribeAudit = wireAuditPersistence(store, emitter);
1378
- let unsubscribeSync = null;
1379
- if (config.sync) {
1380
- const transport = createSyncTransport(config.sync);
1381
- const mergeAwareStore = new MergeAwareSyncStore(store, mergeEngine, emitter, {
1382
- onMergeConflict: () => recordConflict?.()
1383
- });
1384
- let scopeMap = config.sync.scope ? (0, import_core5.buildScopeMap)(config.schema, config.sync.scope) : void 0;
1385
- if (authBinding?.resolveScopeMap) {
1386
- scopeMap = await authBinding.resolveScopeMap() ?? scopeMap;
1387
- }
1388
- const syncAuth = authBinding?.auth ?? config.sync.auth;
1389
- const encryptor = config.sync.encryption?.enabled === true ? await import_sync2.SyncEncryptor.create(config.sync.encryption) : void 0;
1390
- syncEngine = new import_sync2.SyncEngine({
1391
- transport,
1392
- store: mergeAwareStore,
1393
- config: {
1394
- url: config.sync.url,
1395
- transport: config.sync.transport,
1396
- auth: syncAuth,
1397
- batchSize: config.sync.batchSize,
1398
- schemaVersion: config.sync.schemaVersion ?? config.schema.version,
1399
- scopeMap,
1400
- encryption: config.sync.encryption,
1401
- strictHandshake: config.sync.strictHandshake,
1402
- operationTransforms: config.sync.operationTransforms
1403
- },
1404
- emitter,
1405
- queueStorage: new StoreQueueStorage(adapter),
1406
- syncState: new StoreSyncStatePersistence(store, scopeMap),
1407
- encryptor
1408
- });
1409
- recordConflict = () => syncEngine?.recordConflict();
1410
- unsubscribeSync = emitter.on("operation:created", (event) => {
1411
- if (syncEngine) {
1412
- syncEngine.pushOperation(event.operation);
1413
- }
1414
- });
1415
- }
1416
- return {
1417
- store,
1418
- syncEngine,
1419
- unsubscribeSync,
1420
- unsubscribeAudit,
1421
- authBinding: config.sync?.authClient ?? null
1422
- };
1423
- }
1424
- function createSyncTransport(sync) {
1425
- if (sync.transport === "http") {
1426
- return new import_sync2.HttpLongPollingTransport();
1427
- }
1428
- return new import_sync2.WebSocketTransport();
1429
- }
1430
- function createCollectionAccessor(collectionName, getStore) {
1431
- return {
1432
- async insert(data) {
1433
- const currentStore = getStore();
1434
- if (!currentStore) {
1435
- throw new Error(`Cannot mutate collection "${collectionName}" before app.ready resolves.`);
1436
- }
1437
- return currentStore.collection(collectionName).insert(data);
1438
- },
1439
- async findById(id) {
1440
- const currentStore = getStore();
1441
- if (!currentStore) return null;
1442
- return currentStore.collection(collectionName).findById(id);
1443
- },
1444
- async update(id, data) {
1445
- const currentStore = getStore();
1446
- if (!currentStore) {
1447
- throw new Error(`Cannot mutate collection "${collectionName}" before app.ready resolves.`);
1448
- }
1449
- return currentStore.collection(collectionName).update(id, data);
1450
- },
1451
- async delete(id) {
1452
- const currentStore = getStore();
1453
- if (!currentStore) {
1454
- throw new Error(`Cannot mutate collection "${collectionName}" before app.ready resolves.`);
1455
- }
1456
- return currentStore.collection(collectionName).delete(id);
1457
- },
1458
- where(conditions) {
1459
- const currentStore = getStore();
1460
- if (!currentStore) {
1461
- return createPendingQueryBuilder(conditions);
1462
- }
1463
- return currentStore.collection(collectionName).where(conditions);
1464
- }
1465
- };
1466
- }
1467
- function createPendingQueryBuilder(initialWhere) {
1468
- const descriptor = {
1469
- collection: "__pending__",
1470
- where: { ...initialWhere },
1471
- orderBy: [],
1472
- limit: void 0,
1473
- offset: void 0
1474
- };
1475
- const builder = {
1476
- where(conditions) {
1477
- descriptor.where = { ...descriptor.where, ...conditions };
1478
- return this;
1479
- },
1480
- orderBy(field, direction = "asc") {
1481
- descriptor.orderBy.push({ field, direction });
1482
- return this;
1483
- },
1484
- limit(n) {
1485
- descriptor.limit = n;
1486
- return this;
1487
- },
1488
- offset(n) {
1489
- descriptor.offset = n;
1490
- return this;
1491
- },
1492
- async exec() {
1493
- throw new AppNotReadyError(
1494
- "Cannot execute a query before app.ready. Await app.ready or use <KoraProvider app={app}>."
1495
- );
1496
- },
1497
- async count() {
1498
- throw new AppNotReadyError(
1499
- "Cannot count query results before app.ready. Await app.ready or use <KoraProvider app={app}>."
1500
- );
1501
- },
1502
- subscribe(_callback) {
1503
- throw new AppNotReadyError(
1504
- "Cannot subscribe to a query before app.ready. Await app.ready or use <KoraProvider app={app}>."
1505
- );
1506
- },
1507
- getDescriptor() {
1508
- return { ...descriptor, where: { ...descriptor.where }, orderBy: [...descriptor.orderBy] };
1509
- }
1510
- };
1511
- return builder;
1512
- }
1513
1527
 
1514
1528
  // src/index.ts
1515
- var import_store5 = require("@korajs/store");
1529
+ var import_store6 = require("@korajs/store");
1516
1530
  var import_core6 = require("@korajs/core");
1517
1531
  var import_core7 = require("@korajs/core");
1518
1532
  var import_core8 = require("@korajs/core");
1519
1533
  var import_core9 = require("@korajs/core");
1520
1534
  var import_core10 = require("@korajs/core");
1521
1535
  var import_core11 = require("@korajs/core");
1522
- var import_store6 = require("@korajs/store");
1523
1536
  var import_store7 = require("@korajs/store");
1524
1537
  var import_store8 = require("@korajs/store");
1538
+ var import_store9 = require("@korajs/store");
1525
1539
  var import_merge3 = require("@korajs/merge");
1526
- var import_sync3 = require("@korajs/sync");
1540
+ var import_sync7 = require("@korajs/sync");
1527
1541
  // Annotate the CommonJS export names for ESM import in node:
1528
1542
  0 && (module.exports = {
1543
+ AppNotReadyError,
1529
1544
  HybridLogicalClock,
1530
1545
  KoraError,
1531
1546
  MergeEngine,