@korajs/store 1.0.0-beta.1 → 1.0.0-beta.10

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.
Files changed (71) hide show
  1. package/dist/adapters/better-sqlite3.cjs.map +1 -1
  2. package/dist/adapters/better-sqlite3.d.cts +1 -1
  3. package/dist/adapters/better-sqlite3.d.ts +1 -1
  4. package/dist/adapters/better-sqlite3.js +1 -1
  5. package/dist/adapters/indexeddb.cjs +523 -148
  6. package/dist/adapters/indexeddb.cjs.map +1 -1
  7. package/dist/adapters/indexeddb.d.cts +4 -2
  8. package/dist/adapters/indexeddb.d.ts +4 -2
  9. package/dist/adapters/indexeddb.js +55 -11
  10. package/dist/adapters/indexeddb.js.map +1 -1
  11. package/dist/adapters/sqlite-wasm-worker.cjs +207 -187
  12. package/dist/adapters/sqlite-wasm-worker.cjs.map +1 -1
  13. package/dist/adapters/sqlite-wasm-worker.js +208 -188
  14. package/dist/adapters/sqlite-wasm-worker.js.map +1 -1
  15. package/dist/adapters/sqlite-wasm.cjs +474 -140
  16. package/dist/adapters/sqlite-wasm.cjs.map +1 -1
  17. package/dist/adapters/sqlite-wasm.d.cts +45 -8
  18. package/dist/adapters/sqlite-wasm.d.ts +45 -8
  19. package/dist/adapters/sqlite-wasm.js +3 -3
  20. package/dist/{backup-ZV4Y424B.js → backup-5S7K2Z35.js} +3 -3
  21. package/dist/{chunk-FFYA7J5H.js → chunk-CYO3YKFL.js} +4 -3
  22. package/dist/chunk-CYO3YKFL.js.map +1 -0
  23. package/dist/{chunk-6NKLDBTY.js → chunk-GSJ7EEUC.js} +2 -1
  24. package/dist/chunk-GSJ7EEUC.js.map +1 -0
  25. package/dist/{chunk-X57MWW35.js → chunk-HS6DBMST.js} +13 -10
  26. package/dist/chunk-HS6DBMST.js.map +1 -0
  27. package/dist/chunk-RJIEKEN4.js +751 -0
  28. package/dist/chunk-RJIEKEN4.js.map +1 -0
  29. package/dist/{chunk-QL2XVZRN.js → chunk-TUR7RGEP.js} +12 -1
  30. package/dist/{chunk-QL2XVZRN.js.map → chunk-TUR7RGEP.js.map} +1 -1
  31. package/dist/{chunk-IOEFNHHP.js → chunk-VDBJQ75K.js} +2 -2
  32. package/dist/chunk-VDBJQ75K.js.map +1 -0
  33. package/dist/{chunk-ZLWAYQEQ.js → chunk-YT7MRFCP.js} +36 -32
  34. package/dist/chunk-YT7MRFCP.js.map +1 -0
  35. package/dist/{export-audit-AJ7RORNK.js → export-audit-ODGQTQIK.js} +3 -3
  36. package/dist/index.cjs +313 -144
  37. package/dist/index.cjs.map +1 -1
  38. package/dist/index.d.cts +51 -5
  39. package/dist/index.d.ts +51 -5
  40. package/dist/index.js +212 -59
  41. package/dist/index.js.map +1 -1
  42. package/dist/internal.cjs +46 -40
  43. package/dist/internal.cjs.map +1 -1
  44. package/dist/internal.d.cts +3 -3
  45. package/dist/internal.d.ts +3 -3
  46. package/dist/internal.js +3 -3
  47. package/dist/sqlite-wasm-channel-GGHA2BXU.js +12 -0
  48. package/dist/{sqlite-wasm-channel-DUDPclTE.d.cts → sqlite-wasm-channel-L6o3bWQK.d.cts} +1 -1
  49. package/dist/{sqlite-wasm-channel-DUDPclTE.d.ts → sqlite-wasm-channel-L6o3bWQK.d.ts} +1 -1
  50. package/dist/{types-5h4Hqcwc.d.ts → types-BOO9EPt1.d.ts} +1 -1
  51. package/dist/{types-k6AZgiH4.d.ts → types-BslDOTk0.d.cts} +26 -1
  52. package/dist/{types-k6AZgiH4.d.cts → types-BslDOTk0.d.ts} +26 -1
  53. package/dist/{types-DfPiPwKr.d.cts → types-C_GDavG-.d.cts} +1 -1
  54. package/package.json +2 -12
  55. package/dist/adapters/sqlite-wasm-shared-host.cjs +0 -68
  56. package/dist/adapters/sqlite-wasm-shared-host.cjs.map +0 -1
  57. package/dist/adapters/sqlite-wasm-shared-host.d.cts +0 -2
  58. package/dist/adapters/sqlite-wasm-shared-host.d.ts +0 -2
  59. package/dist/adapters/sqlite-wasm-shared-host.js +0 -66
  60. package/dist/adapters/sqlite-wasm-shared-host.js.map +0 -1
  61. package/dist/chunk-6NKLDBTY.js.map +0 -1
  62. package/dist/chunk-FFYA7J5H.js.map +0 -1
  63. package/dist/chunk-IOEFNHHP.js.map +0 -1
  64. package/dist/chunk-RBPVEAVM.js +0 -425
  65. package/dist/chunk-RBPVEAVM.js.map +0 -1
  66. package/dist/chunk-X57MWW35.js.map +0 -1
  67. package/dist/chunk-ZLWAYQEQ.js.map +0 -1
  68. package/dist/sqlite-wasm-channel-H43KEZLM.js +0 -12
  69. /package/dist/{backup-ZV4Y424B.js.map → backup-5S7K2Z35.js.map} +0 -0
  70. /package/dist/{export-audit-AJ7RORNK.js.map → export-audit-ODGQTQIK.js.map} +0 -0
  71. /package/dist/{sqlite-wasm-channel-H43KEZLM.js.map → sqlite-wasm-channel-GGHA2BXU.js.map} +0 -0
package/dist/index.js CHANGED
@@ -1,14 +1,8 @@
1
- import {
2
- exportBackup,
3
- readBackupManifest,
4
- restoreBackup,
5
- verifyBackupChecksum
6
- } from "./chunk-X57MWW35.js";
7
1
  import {
8
2
  decodeAuditExport,
9
3
  readAuditExportManifest,
10
4
  verifyAuditExportChecksum
11
- } from "./chunk-FFYA7J5H.js";
5
+ } from "./chunk-CYO3YKFL.js";
12
6
  import {
13
7
  persistedAuditTraceFromEvent
14
8
  } from "./chunk-MYSJN3DF.js";
@@ -39,7 +33,24 @@ import {
39
33
  serializeRowVersion,
40
34
  validateStateTransition,
41
35
  validateUpdateStateMachine
42
- } from "./chunk-ZLWAYQEQ.js";
36
+ } from "./chunk-YT7MRFCP.js";
37
+ import {
38
+ AdapterError,
39
+ NoLeaderError,
40
+ OptimisticLockError,
41
+ PersistenceError,
42
+ QueryError,
43
+ RecordNotFoundError,
44
+ StoreNotOpenError,
45
+ WorkerInitError,
46
+ WorkerTimeoutError
47
+ } from "./chunk-TUR7RGEP.js";
48
+ import {
49
+ exportBackup,
50
+ readBackupManifest,
51
+ restoreBackup,
52
+ verifyBackupChecksum
53
+ } from "./chunk-HS6DBMST.js";
43
54
  import {
44
55
  decodeRichtext,
45
56
  decodeRichtextFieldsFromOpData,
@@ -51,23 +62,13 @@ import {
51
62
  richtextToPlainText,
52
63
  serializeOperation,
53
64
  serializeRecord
54
- } from "./chunk-6NKLDBTY.js";
55
- import {
56
- AdapterError,
57
- OptimisticLockError,
58
- PersistenceError,
59
- QueryError,
60
- RecordNotFoundError,
61
- StoreNotOpenError,
62
- WorkerInitError,
63
- WorkerTimeoutError
64
- } from "./chunk-QL2XVZRN.js";
65
+ } from "./chunk-GSJ7EEUC.js";
65
66
 
66
67
  // src/compaction/types.ts
67
68
  var COMPACTION_BASELINE_META_KEY = "compaction_baseline_at";
68
69
 
69
70
  // src/compaction/compact-operation-log.ts
70
- import { HybridLogicalClock, createVersionVector } from "@korajs/core";
71
+ import { HybridLogicalClock, createVersionVector, quoteIdent } from "@korajs/core";
71
72
  function computeAckCompactionWatermark(serverVector) {
72
73
  const watermark = createVersionVector();
73
74
  for (const [nodeId, seq] of serverVector) {
@@ -95,7 +96,7 @@ async function compactOperationLog(adapter, schema, strategy, serverVector) {
95
96
  const collectionNames = Object.keys(schema.collections);
96
97
  await adapter.transaction(async (tx) => {
97
98
  for (const collectionName of collectionNames) {
98
- const table = `_kora_ops_${collectionName}`;
99
+ const table = quoteIdent(`_kora_ops_${collectionName}`);
99
100
  for (const [nodeId, maxSeq] of watermark) {
100
101
  if (maxSeq <= 0) continue;
101
102
  const countSql = buildCountSql(table, ageCutoffSerialized !== null);
@@ -130,6 +131,7 @@ function buildDeleteSql(table, withAge) {
130
131
  import { createVersionVector as createVersionVector2 } from "@korajs/core";
131
132
  var LAST_ACKED_SERVER_VECTOR_META_KEY = "last_acked_server_vector";
132
133
  var DELTA_CURSOR_META_KEY = "delta_cursor";
134
+ var DELIVERY_WATERMARK_META_KEY = "delivery_watermark";
133
135
  function serializeVersionVectorToMeta(vector) {
134
136
  const record = {};
135
137
  for (const [nodeId, seq] of vector) {
@@ -202,6 +204,45 @@ async function saveDeltaCursor(adapter, cursor) {
202
204
  cursor
203
205
  ]);
204
206
  }
207
+ function deliveryWatermarkKey(signature) {
208
+ return signature === "" ? DELIVERY_WATERMARK_META_KEY : `${DELIVERY_WATERMARK_META_KEY}:${signature}`;
209
+ }
210
+ async function loadDeliveryWatermark(adapter, signature) {
211
+ const rows = await adapter.query("SELECT value FROM _kora_meta WHERE key = ?", [
212
+ deliveryWatermarkKey(signature)
213
+ ]);
214
+ const value = rows[0]?.value;
215
+ if (value === void 0 || value === null) {
216
+ return 0;
217
+ }
218
+ const parsed = Number(value);
219
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : 0;
220
+ }
221
+ async function saveDeliveryWatermark(adapter, signature, watermark) {
222
+ await adapter.execute("INSERT OR REPLACE INTO _kora_meta (key, value) VALUES (?, ?)", [
223
+ deliveryWatermarkKey(signature),
224
+ String(watermark)
225
+ ]);
226
+ }
227
+ async function deleteDeliveryWatermark(adapter, signature) {
228
+ await adapter.execute("DELETE FROM _kora_meta WHERE key = ?", [deliveryWatermarkKey(signature)]);
229
+ }
230
+ async function loadAllDeliveryWatermarks(adapter) {
231
+ const rows = await adapter.query(
232
+ "SELECT key, value FROM _kora_meta WHERE key = ? OR key LIKE ?",
233
+ [DELIVERY_WATERMARK_META_KEY, `${DELIVERY_WATERMARK_META_KEY}:%`]
234
+ );
235
+ const result = {};
236
+ const prefix = `${DELIVERY_WATERMARK_META_KEY}:`;
237
+ for (const row of rows) {
238
+ const signature = row.key === DELIVERY_WATERMARK_META_KEY ? "" : row.key.slice(prefix.length);
239
+ const parsed = Number(row.value);
240
+ if (Number.isFinite(parsed) && parsed >= 0) {
241
+ result[signature] = parsed;
242
+ }
243
+ }
244
+ return result;
245
+ }
205
246
 
206
247
  // src/store/store.ts
207
248
  import {
@@ -209,10 +250,13 @@ import {
209
250
  HybridLogicalClock as HybridLogicalClock3,
210
251
  createVersionVector as createVersionVector3,
211
252
  generateUUIDv7 as generateUUIDv73,
212
- migrationStepsToSQL
253
+ migrationStepsToSQL,
254
+ quoteIdent as quoteIdent7,
255
+ replayOperationsForRecord
213
256
  } from "@korajs/core";
214
257
 
215
258
  // src/collection/collection.ts
259
+ import { quoteIdent as quoteIdent2 } from "@korajs/core";
216
260
  var Collection = class {
217
261
  constructor(name, definition, schema, adapter, clock, nodeId, allocateSequenceNumber, onMutation, relationEnforcer, mutationHandler, causalTracker, secretKeyProvider) {
218
262
  this.name = name;
@@ -264,7 +308,7 @@ var Collection = class {
264
308
  }
265
309
  async findById(id) {
266
310
  const rows = await this.adapter.query(
267
- `SELECT * FROM ${this.name} WHERE id = ? AND _deleted = 0`,
311
+ `SELECT * FROM ${quoteIdent2(this.name)} WHERE id = ? AND _deleted = 0`,
268
312
  [id]
269
313
  );
270
314
  const row = rows[0];
@@ -296,6 +340,9 @@ var Collection = class {
296
340
  }
297
341
  };
298
342
 
343
+ // src/query/query-builder.ts
344
+ import { quoteIdent as quoteIdent3 } from "@korajs/core";
345
+
299
346
  // src/query/pluralize.ts
300
347
  function isVowel(char) {
301
348
  if (!char) return false;
@@ -525,7 +572,7 @@ var QueryBuilder = class _QueryBuilder {
525
572
  const relatedDef = this.schema?.collections[relatedCollection];
526
573
  if (!relatedDef) return;
527
574
  const placeholders = uniqueFks.map(() => "?").join(", ");
528
- const sql = `SELECT * FROM ${relatedCollection} WHERE id IN (${placeholders}) AND _deleted = 0`;
575
+ const sql = `SELECT * FROM ${quoteIdent3(relatedCollection)} WHERE id IN (${placeholders}) AND _deleted = 0`;
529
576
  const rows = await this.adapter.query(sql, uniqueFks);
530
577
  const relatedRecords = rows.map((row) => deserializeRecord(row, relatedDef.fields));
531
578
  const lookup = /* @__PURE__ */ new Map();
@@ -548,7 +595,7 @@ var QueryBuilder = class _QueryBuilder {
548
595
  if (!relatedDef) return;
549
596
  const fkField = relation.field;
550
597
  const placeholders = primaryIds.map(() => "?").join(", ");
551
- const sql = `SELECT * FROM ${relatedCollection} WHERE ${fkField} IN (${placeholders}) AND _deleted = 0`;
598
+ const sql = `SELECT * FROM ${quoteIdent3(relatedCollection)} WHERE ${quoteIdent3(fkField)} IN (${placeholders}) AND _deleted = 0`;
552
599
  const rows = await this.adapter.query(sql, primaryIds);
553
600
  const relatedRecords = rows.map((row) => deserializeRecord(row, relatedDef.fields));
554
601
  const grouped = /* @__PURE__ */ new Map();
@@ -590,7 +637,7 @@ var QueryError2 = class extends Error {
590
637
  };
591
638
 
592
639
  // src/relations/relation-enforcer.ts
593
- import { KoraError, createOperation } from "@korajs/core";
640
+ import { KoraError, createOperation, quoteIdent as quoteIdent4 } from "@korajs/core";
594
641
 
595
642
  // src/relations/relation-lookup.ts
596
643
  function buildRelationLookup(schema) {
@@ -693,7 +740,7 @@ var RelationEnforcer = class {
693
740
  async enforceCascade(incoming, deletedRecordId, tx, causalDeps) {
694
741
  const { sourceCollection, foreignKeyField } = incoming;
695
742
  const referencingRows = await tx.query(
696
- `SELECT id FROM ${sourceCollection} WHERE ${foreignKeyField} = ? AND _deleted = 0`,
743
+ `SELECT id FROM ${quoteIdent4(sourceCollection)} WHERE ${quoteIdent4(foreignKeyField)} = ? AND _deleted = 0`,
697
744
  [deletedRecordId]
698
745
  );
699
746
  if (referencingRows.length === 0) {
@@ -740,7 +787,7 @@ var RelationEnforcer = class {
740
787
  return [];
741
788
  }
742
789
  const referencingRows = await tx.query(
743
- `SELECT id FROM ${sourceCollection} WHERE ${foreignKeyField} = ? AND _deleted = 0`,
790
+ `SELECT id FROM ${quoteIdent4(sourceCollection)} WHERE ${quoteIdent4(foreignKeyField)} = ? AND _deleted = 0`,
744
791
  [deletedRecordId]
745
792
  );
746
793
  if (referencingRows.length === 0) {
@@ -788,7 +835,7 @@ var RelationEnforcer = class {
788
835
  async enforceRestrict(incoming, deletedRecordId, tx) {
789
836
  const { sourceCollection, foreignKeyField, relationName } = incoming;
790
837
  const countRows = await tx.query(
791
- `SELECT COUNT(*) as cnt FROM ${sourceCollection} WHERE ${foreignKeyField} = ? AND _deleted = 0`,
838
+ `SELECT COUNT(*) as cnt FROM ${quoteIdent4(sourceCollection)} WHERE ${quoteIdent4(foreignKeyField)} = ? AND _deleted = 0`,
792
839
  [deletedRecordId]
793
840
  );
794
841
  const count = countRows[0]?.cnt ?? 0;
@@ -1476,7 +1523,7 @@ var SubscriptionManager = class {
1476
1523
  };
1477
1524
 
1478
1525
  // src/sync/rebase-unsynced-operations.ts
1479
- import { HybridLogicalClock as HybridLogicalClock2, MAX_LOGICAL } from "@korajs/core";
1526
+ import { HybridLogicalClock as HybridLogicalClock2, MAX_LOGICAL, quoteIdent as quoteIdent5 } from "@korajs/core";
1480
1527
  import { computeOperationId } from "@korajs/core/internal";
1481
1528
  var LOGICAL_COUNTER_LIMIT = MAX_LOGICAL + 1;
1482
1529
  function emptyResult() {
@@ -1490,7 +1537,9 @@ async function rebaseUnsyncedOperationsInLog(adapter, schema, unsyncedOpIds, cor
1490
1537
  const rebased = [];
1491
1538
  let maxOtherSerializedTs = null;
1492
1539
  for (const collectionName of Object.keys(schema.collections)) {
1493
- const rows = await adapter.query(`SELECT * FROM _kora_ops_${collectionName}`);
1540
+ const rows = await adapter.query(
1541
+ `SELECT * FROM ${quoteIdent5(`_kora_ops_${collectionName}`)}`
1542
+ );
1494
1543
  for (const row of rows) {
1495
1544
  if (rebaseIds.has(row.id)) {
1496
1545
  rebased.push(deserializeOperationWithCollection(row, collectionName));
@@ -1559,7 +1608,7 @@ async function rebaseUnsyncedOperationsInLog(adapter, schema, unsyncedOpIds, cor
1559
1608
  continue;
1560
1609
  }
1561
1610
  const opsTable = `_kora_ops_${oldOp.collection}`;
1562
- await tx.execute(`DELETE FROM ${opsTable} WHERE id = ?`, [oldOp.id]);
1611
+ await tx.execute(`DELETE FROM ${quoteIdent5(opsTable)} WHERE id = ?`, [oldOp.id]);
1563
1612
  const opInsert = buildInsertQuery(
1564
1613
  opsTable,
1565
1614
  serializeOperation(newOp)
@@ -1568,11 +1617,11 @@ async function rebaseUnsyncedOperationsInLog(adapter, schema, unsyncedOpIds, cor
1568
1617
  const oldVersion = serializeRowVersion(oldOp.timestamp);
1569
1618
  const newVersion = serializeRowVersion(newOp.timestamp);
1570
1619
  await tx.execute(
1571
- `UPDATE ${oldOp.collection} SET _version = ?, _updated_at = ? WHERE id = ? AND _version = ?`,
1620
+ `UPDATE ${quoteIdent5(oldOp.collection)} SET _version = ?, _updated_at = ? WHERE id = ? AND _version = ?`,
1572
1621
  [newVersion, newOp.timestamp.wallTime, oldOp.recordId, oldVersion]
1573
1622
  );
1574
1623
  const rows = await tx.query(
1575
- `SELECT _field_versions FROM ${oldOp.collection} WHERE id = ?`,
1624
+ `SELECT _field_versions FROM ${quoteIdent5(oldOp.collection)} WHERE id = ?`,
1576
1625
  [oldOp.recordId]
1577
1626
  );
1578
1627
  const fieldVersions = parseFieldVersions(rows[0]?._field_versions);
@@ -1584,14 +1633,14 @@ async function rebaseUnsyncedOperationsInLog(adapter, schema, unsyncedOpIds, cor
1584
1633
  }
1585
1634
  }
1586
1635
  if (changed) {
1587
- await tx.execute(`UPDATE ${oldOp.collection} SET _field_versions = ? WHERE id = ?`, [
1588
- serializeFieldVersions(fieldVersions),
1589
- oldOp.recordId
1590
- ]);
1636
+ await tx.execute(
1637
+ `UPDATE ${quoteIdent5(oldOp.collection)} SET _field_versions = ? WHERE id = ?`,
1638
+ [serializeFieldVersions(fieldVersions), oldOp.recordId]
1639
+ );
1591
1640
  }
1592
1641
  if (oldOp.type === "insert") {
1593
1642
  await tx.execute(
1594
- `UPDATE ${oldOp.collection} SET _created_at = ? WHERE id = ? AND _created_at = ?`,
1643
+ `UPDATE ${quoteIdent5(oldOp.collection)} SET _created_at = ? WHERE id = ? AND _created_at = ?`,
1595
1644
  [newOp.timestamp.wallTime, oldOp.recordId, oldOp.timestamp.wallTime]
1596
1645
  );
1597
1646
  }
@@ -1611,6 +1660,7 @@ import {
1611
1660
  createOperation as createOperation2,
1612
1661
  generateUUIDv7,
1613
1662
  isAtomicOp,
1663
+ quoteIdent as quoteIdent6,
1614
1664
  resolveAtomicOp,
1615
1665
  toAtomicOp,
1616
1666
  validateRecord
@@ -1927,7 +1977,7 @@ var TransactionContext = class {
1927
1977
  }
1928
1978
  }
1929
1979
  const rows = await this.config.adapter.query(
1930
- `SELECT * FROM ${collectionName} WHERE id = ? AND _deleted = 0`,
1980
+ `SELECT * FROM ${quoteIdent6(collectionName)} WHERE id = ? AND _deleted = 0`,
1931
1981
  [id]
1932
1982
  );
1933
1983
  let record = rows[0] ? deserializeRecord(rows[0], definition.fields) : null;
@@ -2162,7 +2212,7 @@ var Store = class {
2162
2212
  return "skipped";
2163
2213
  }
2164
2214
  const existing = await this.adapter.query(
2165
- `SELECT id FROM _kora_ops_${collection} WHERE id = ?`,
2215
+ `SELECT id FROM ${quoteIdent7(`_kora_ops_${collection}`)} WHERE id = ?`,
2166
2216
  [op.id]
2167
2217
  );
2168
2218
  if (existing.length > 0) {
@@ -2187,7 +2237,8 @@ var Store = class {
2187
2237
  throw new OptimisticLockError(collection, op.recordId);
2188
2238
  }
2189
2239
  };
2190
- if (op.type === "insert" && materializeSource) {
2240
+ if (options?.logOnly) {
2241
+ } else if (op.type === "insert" && materializeSource) {
2191
2242
  const serializedData = serializeRecord(materializeSource, definition.fields);
2192
2243
  const existing2 = await tx.query(
2193
2244
  `SELECT _updated_at, _version, _field_versions, _deleted FROM ${collection} WHERE id = ?`,
@@ -2209,7 +2260,7 @@ var Store = class {
2209
2260
  };
2210
2261
  const insertQuery = buildInsertQuery(collection, record);
2211
2262
  await tx.execute(insertQuery.sql, insertQuery.params);
2212
- await this.foldOrphanedOperations(tx, collection, definition, op.recordId);
2263
+ await this.foldOrphanedOperations(tx, collection, definition, op.recordId, op);
2213
2264
  } else if (row._deleted === 1) {
2214
2265
  if (isIncomingNewerThanRow(materializeTimestamp, row)) {
2215
2266
  const fieldChanges = {
@@ -2267,7 +2318,7 @@ var Store = class {
2267
2318
  const serializedChanges = serializeRecord(materializeSource, definition.fields);
2268
2319
  const changedFields = Object.keys(serializedChanges);
2269
2320
  const currentRows = await tx.query(
2270
- `SELECT _version, _field_versions FROM ${collection} WHERE id = ?`,
2321
+ `SELECT _version, _field_versions FROM ${quoteIdent7(collection)} WHERE id = ?`,
2271
2322
  [op.recordId]
2272
2323
  );
2273
2324
  const currentRow = currentRows[0];
@@ -2361,9 +2412,9 @@ var Store = class {
2361
2412
  * necessarily an orphan (rows are soft-deleted, never removed, so a row
2362
2413
  * that is absent now was never materialized).
2363
2414
  */
2364
- async foldOrphanedOperations(tx, collection, definition, recordId) {
2415
+ async foldOrphanedOperations(tx, collection, definition, recordId, insertOp) {
2365
2416
  const orphanRows = await tx.query(
2366
- `SELECT * FROM _kora_ops_${collection} WHERE record_id = ? AND type IN ('update', 'delete')`,
2417
+ `SELECT * FROM ${quoteIdent7(`_kora_ops_${collection}`)} WHERE record_id = ? AND type IN ('update', 'delete')`,
2367
2418
  [recordId]
2368
2419
  );
2369
2420
  if (orphanRows.length === 0) {
@@ -2376,7 +2427,7 @@ var Store = class {
2376
2427
  if (orphan.type === "update" && orphan.data) {
2377
2428
  const orphanChanges = serializeRecord(orphan.data, definition.fields);
2378
2429
  const rows = await tx.query(
2379
- `SELECT _version, _field_versions FROM ${collection} WHERE id = ?`,
2430
+ `SELECT _version, _field_versions FROM ${quoteIdent7(collection)} WHERE id = ?`,
2380
2431
  [recordId]
2381
2432
  );
2382
2433
  const current = rows[0];
@@ -2410,6 +2461,37 @@ var Store = class {
2410
2461
  await tx.execute(query.sql, query.params);
2411
2462
  }
2412
2463
  }
2464
+ const atomicFields = /* @__PURE__ */ new Set();
2465
+ for (const orphan of orphans) {
2466
+ if (orphan.type === "update" && orphan.atomicOps) {
2467
+ for (const field of Object.keys(orphan.atomicOps)) {
2468
+ atomicFields.add(field);
2469
+ }
2470
+ }
2471
+ }
2472
+ if (atomicFields.size > 0) {
2473
+ const ordered = [insertOp, ...orphans].sort(
2474
+ (a, b) => HybridLogicalClock3.compare(a.timestamp, b.timestamp)
2475
+ );
2476
+ const folded = replayOperationsForRecord(ordered);
2477
+ if (folded) {
2478
+ const composed = {};
2479
+ for (const field of atomicFields) {
2480
+ if (field in folded) {
2481
+ composed[field] = folded[field];
2482
+ }
2483
+ }
2484
+ const serialized = serializeRecord(composed, definition.fields);
2485
+ const cols = Object.keys(serialized);
2486
+ if (cols.length > 0) {
2487
+ const setClause = cols.map((c) => `${quoteIdent7(c)} = ?`).join(", ");
2488
+ await tx.execute(`UPDATE ${quoteIdent7(collection)} SET ${setClause} WHERE id = ?`, [
2489
+ ...cols.map((c) => serialized[c]),
2490
+ recordId
2491
+ ]);
2492
+ }
2493
+ }
2494
+ }
2413
2495
  }
2414
2496
  /**
2415
2497
  * Get operations from a node within a sequence number range.
@@ -2426,7 +2508,7 @@ var Store = class {
2426
2508
  const allOps = [];
2427
2509
  for (const collectionName of Object.keys(this.schema.collections)) {
2428
2510
  const rows = await this.adapter.query(
2429
- `SELECT * FROM _kora_ops_${collectionName} WHERE node_id = ? AND sequence_number >= ? AND sequence_number <= ? ORDER BY sequence_number ASC`,
2511
+ `SELECT * FROM ${quoteIdent7(`_kora_ops_${collectionName}`)} WHERE node_id = ? AND sequence_number >= ? AND sequence_number <= ? ORDER BY sequence_number ASC`,
2430
2512
  [nodeId, fromSeq, toSeq]
2431
2513
  );
2432
2514
  for (const row of rows) {
@@ -2445,7 +2527,7 @@ var Store = class {
2445
2527
  const allOps = [];
2446
2528
  for (const collectionName of Object.keys(this.schema.collections)) {
2447
2529
  const rows = await this.adapter.query(
2448
- `SELECT * FROM _kora_ops_${collectionName} ORDER BY sequence_number ASC`
2530
+ `SELECT * FROM ${quoteIdent7(`_kora_ops_${collectionName}`)} ORDER BY sequence_number ASC`
2449
2531
  );
2450
2532
  for (const row of rows) {
2451
2533
  allOps.push(deserializeOperationWithCollection(row, collectionName));
@@ -2496,7 +2578,7 @@ var Store = class {
2496
2578
  */
2497
2579
  async exportAudit(options) {
2498
2580
  this.ensureOpen();
2499
- const { exportAudit: doExport } = await import("./export-audit-AJ7RORNK.js");
2581
+ const { exportAudit: doExport } = await import("./export-audit-ODGQTQIK.js");
2500
2582
  return doExport(this.adapter, this.schema, this.nodeId, this.schema.version, options);
2501
2583
  }
2502
2584
  /**
@@ -2514,6 +2596,22 @@ var Store = class {
2514
2596
  col.setMutationHandler(handler);
2515
2597
  }
2516
2598
  }
2599
+ /**
2600
+ * Notify this store that another same-origin runtime committed an operation to
2601
+ * the shared local database. The operation has already been persisted and
2602
+ * materialized by the database owner; this method only advances in-memory causal
2603
+ * watermarks and invalidates local reactive queries so already-open tabs stay
2604
+ * live without reapplying or rewriting the operation.
2605
+ */
2606
+ notifyExternalOperation(operation) {
2607
+ this.ensureOpen();
2608
+ const definition = this.schema.collections[operation.collection];
2609
+ if (!definition) {
2610
+ return;
2611
+ }
2612
+ this.recordOperationSequence(operation);
2613
+ this.subscriptionManager.notify(operation.collection, operation);
2614
+ }
2517
2615
  /**
2518
2616
  * Build mutation context for a collection (used by ApplyPipeline side effects).
2519
2617
  */
@@ -2555,7 +2653,7 @@ var Store = class {
2555
2653
  return null;
2556
2654
  }
2557
2655
  const rows = await this.adapter.query(
2558
- `SELECT * FROM ${collection} WHERE id = ?`,
2656
+ `SELECT * FROM ${quoteIdent7(collection)} WHERE id = ?`,
2559
2657
  [recordId]
2560
2658
  );
2561
2659
  const row = rows[0];
@@ -2580,7 +2678,7 @@ var Store = class {
2580
2678
  return null;
2581
2679
  }
2582
2680
  const rows = await this.adapter.query(
2583
- `SELECT _version, _field_versions FROM ${collection} WHERE id = ?`,
2681
+ `SELECT _version, _field_versions FROM ${quoteIdent7(collection)} WHERE id = ?`,
2584
2682
  [recordId]
2585
2683
  );
2586
2684
  const row = rows[0];
@@ -2623,6 +2721,35 @@ var Store = class {
2623
2721
  this.ensureOpen();
2624
2722
  await saveDeltaCursor(this.adapter, cursor);
2625
2723
  }
2724
+ /**
2725
+ * Load the persisted delivery watermark for a view signature (0 when none recorded).
2726
+ * Defaults to the unfiltered view so existing single-watermark reads keep working.
2727
+ */
2728
+ async loadDeliveryWatermark(signature = "") {
2729
+ this.ensureOpen();
2730
+ return loadDeliveryWatermark(this.adapter, signature);
2731
+ }
2732
+ /**
2733
+ * Persist the delivery watermark for a view signature so it survives a client restart.
2734
+ */
2735
+ async saveDeliveryWatermark(signature, watermark) {
2736
+ this.ensureOpen();
2737
+ await saveDeliveryWatermark(this.adapter, signature, watermark);
2738
+ }
2739
+ /**
2740
+ * Load every persisted view watermark, keyed by signature.
2741
+ */
2742
+ async loadAllDeliveryWatermarks() {
2743
+ this.ensureOpen();
2744
+ return loadAllDeliveryWatermarks(this.adapter);
2745
+ }
2746
+ /**
2747
+ * Delete a persisted view watermark (safe: the view back-fills from 0 when next visited).
2748
+ */
2749
+ async deleteDeliveryWatermark(signature) {
2750
+ this.ensureOpen();
2751
+ await deleteDeliveryWatermark(this.adapter, signature);
2752
+ }
2626
2753
  /**
2627
2754
  * Local operations not yet reflected on the server version vector.
2628
2755
  */
@@ -2686,7 +2813,7 @@ var Store = class {
2686
2813
  async getLatestLocalOperationForRecord(collection, recordId) {
2687
2814
  this.ensureOpen();
2688
2815
  const rows = await this.adapter.query(
2689
- `SELECT * FROM _kora_ops_${collection} WHERE node_id = ? AND record_id = ? ORDER BY sequence_number DESC LIMIT 1`,
2816
+ `SELECT * FROM ${quoteIdent7(`_kora_ops_${collection}`)} WHERE node_id = ? AND record_id = ? ORDER BY sequence_number DESC LIMIT 1`,
2690
2817
  [this.nodeId, recordId]
2691
2818
  );
2692
2819
  const row = rows[0];
@@ -2701,7 +2828,7 @@ var Store = class {
2701
2828
  async getLatestOperationForRecord(collection, recordId) {
2702
2829
  this.ensureOpen();
2703
2830
  const rows = await this.adapter.query(
2704
- `SELECT * FROM _kora_ops_${collection} WHERE record_id = ?`,
2831
+ `SELECT * FROM ${quoteIdent7(`_kora_ops_${collection}`)} WHERE record_id = ?`,
2705
2832
  [recordId]
2706
2833
  );
2707
2834
  let latest = null;
@@ -2713,6 +2840,21 @@ var Store = class {
2713
2840
  }
2714
2841
  return latest;
2715
2842
  }
2843
+ /**
2844
+ * All operations for a record, sorted in HLC total order (wallTime, logical,
2845
+ * nodeId). Used by the apply pipeline to materialize atomic-op fields by folding
2846
+ * the record's log, matching the server's materialization exactly.
2847
+ */
2848
+ async getOperationsForRecord(collection, recordId) {
2849
+ this.ensureOpen();
2850
+ const rows = await this.adapter.query(
2851
+ `SELECT * FROM ${quoteIdent7(`_kora_ops_${collection}`)} WHERE record_id = ?`,
2852
+ [recordId]
2853
+ );
2854
+ const ops = rows.map((row) => deserializeOperationWithCollection(row, collection));
2855
+ ops.sort((a, b) => HybridLogicalClock3.compare(a.timestamp, b.timestamp));
2856
+ return ops;
2857
+ }
2716
2858
  /** Expose the subscription manager for direct access (e.g., by QueryBuilder) */
2717
2859
  getSubscriptionManager() {
2718
2860
  return this.subscriptionManager;
@@ -2791,7 +2933,7 @@ var Store = class {
2791
2933
  */
2792
2934
  async exportBackup(options) {
2793
2935
  this.ensureOpen();
2794
- const { exportBackup: doExport } = await import("./backup-ZV4Y424B.js");
2936
+ const { exportBackup: doExport } = await import("./backup-5S7K2Z35.js");
2795
2937
  return doExport(this.adapter, this.schema, this.nodeId, this.schema.version, options);
2796
2938
  }
2797
2939
  /**
@@ -2803,7 +2945,7 @@ var Store = class {
2803
2945
  */
2804
2946
  async importBackup(data, options) {
2805
2947
  this.ensureOpen();
2806
- const { restoreBackup: doRestore } = await import("./backup-ZV4Y424B.js");
2948
+ const { restoreBackup: doRestore } = await import("./backup-5S7K2Z35.js");
2807
2949
  return doRestore(this.adapter, this.schema, data, options);
2808
2950
  }
2809
2951
  /**
@@ -2888,14 +3030,14 @@ var Store = class {
2888
3030
  */
2889
3031
  async runBackfill(collection, transform) {
2890
3032
  const rows = await this.adapter.query(
2891
- `SELECT * FROM ${collection} WHERE _deleted = 0`
3033
+ `SELECT * FROM ${quoteIdent7(collection)} WHERE _deleted = 0`
2892
3034
  );
2893
3035
  await this.adapter.transaction(async (tx) => {
2894
3036
  for (const row of rows) {
2895
3037
  const updates = transform(row);
2896
3038
  const fields = Object.keys(updates);
2897
3039
  if (fields.length === 0) continue;
2898
- const setClauses = fields.map((f) => `${f} = ?`).join(", ");
3040
+ const setClauses = fields.map((f) => `${quoteIdent7(f)} = ?`).join(", ");
2899
3041
  const values = fields.map((f) => {
2900
3042
  const val = updates[f];
2901
3043
  if (typeof val === "boolean") return val ? 1 : 0;
@@ -2905,7 +3047,7 @@ var Store = class {
2905
3047
  return val;
2906
3048
  });
2907
3049
  values.push(row.id);
2908
- await tx.execute(`UPDATE ${collection} SET ${setClauses} WHERE id = ?`, values);
3050
+ await tx.execute(`UPDATE ${quoteIdent7(collection)} SET ${setClauses} WHERE id = ?`, values);
2909
3051
  }
2910
3052
  });
2911
3053
  }
@@ -2965,6 +3107,7 @@ var QueryStore = class {
2965
3107
  listeners = /* @__PURE__ */ new Set();
2966
3108
  unsubscribeQuery = null;
2967
3109
  active = false;
3110
+ hasEmittedSnapshot = false;
2968
3111
  queryBuilder;
2969
3112
  constructor(queryBuilder) {
2970
3113
  this.queryBuilder = queryBuilder;
@@ -2990,16 +3133,25 @@ var QueryStore = class {
2990
3133
  getSnapshot = () => {
2991
3134
  return this.snapshot;
2992
3135
  };
3136
+ /**
3137
+ * Whether the underlying query subscription has delivered at least one
3138
+ * authoritative result for the current subscription lifecycle.
3139
+ */
3140
+ hasSnapshot = () => {
3141
+ return this.hasEmittedSnapshot;
3142
+ };
2993
3143
  /** Tear down listeners and the underlying query subscription. */
2994
3144
  destroy() {
2995
3145
  this.stopSubscription();
2996
3146
  this.listeners.clear();
2997
3147
  this.snapshot = EMPTY_ARRAY;
3148
+ this.hasEmittedSnapshot = false;
2998
3149
  }
2999
3150
  startSubscription() {
3000
3151
  this.active = true;
3001
3152
  this.unsubscribeQuery = this.queryBuilder.subscribe((results) => {
3002
3153
  if (!this.active) return;
3154
+ this.hasEmittedSnapshot = true;
3003
3155
  this.snapshot = Object.freeze([...results]);
3004
3156
  this.notifyListeners();
3005
3157
  });
@@ -3944,6 +4096,7 @@ export {
3944
4096
  InvalidStateTransitionError,
3945
4097
  LAST_ACKED_SERVER_VECTOR_META_KEY,
3946
4098
  MemoryBlobStore,
4099
+ NoLeaderError,
3947
4100
  OpfsBlobStore,
3948
4101
  OptimisticLockError,
3949
4102
  PersistenceError,