@korajs/store 1.0.0-beta.2 → 1.0.0-beta.5

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 (67) 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 +161 -35
  6. package/dist/adapters/indexeddb.cjs.map +1 -1
  7. package/dist/adapters/indexeddb.d.cts +2 -2
  8. package/dist/adapters/indexeddb.d.ts +2 -2
  9. package/dist/adapters/indexeddb.js +3 -3
  10. package/dist/adapters/sqlite-wasm-shared-host.cjs +333 -56
  11. package/dist/adapters/sqlite-wasm-shared-host.cjs.map +1 -1
  12. package/dist/adapters/sqlite-wasm-shared-host.d.cts +81 -1
  13. package/dist/adapters/sqlite-wasm-shared-host.d.ts +81 -1
  14. package/dist/adapters/sqlite-wasm-shared-host.js +51 -56
  15. package/dist/adapters/sqlite-wasm-shared-host.js.map +1 -1
  16. package/dist/adapters/sqlite-wasm-worker.cjs +206 -208
  17. package/dist/adapters/sqlite-wasm-worker.cjs.map +1 -1
  18. package/dist/adapters/sqlite-wasm-worker.js +8 -257
  19. package/dist/adapters/sqlite-wasm-worker.js.map +1 -1
  20. package/dist/adapters/sqlite-wasm.cjs +161 -35
  21. package/dist/adapters/sqlite-wasm.cjs.map +1 -1
  22. package/dist/adapters/sqlite-wasm.d.cts +14 -3
  23. package/dist/adapters/sqlite-wasm.d.ts +14 -3
  24. package/dist/adapters/sqlite-wasm.js +3 -3
  25. package/dist/{backup-IQE4QC7Q.js → backup-5S7K2Z35.js} +3 -3
  26. package/dist/chunk-3Y2IERBF.js +255 -0
  27. package/dist/chunk-3Y2IERBF.js.map +1 -0
  28. package/dist/{chunk-IOEFNHHP.js → chunk-COJSQBXW.js} +2 -2
  29. package/dist/{chunk-RCTG3IJG.js → chunk-CYO3YKFL.js} +2 -2
  30. package/dist/{chunk-6NKLDBTY.js → chunk-GSJ7EEUC.js} +2 -1
  31. package/dist/chunk-GSJ7EEUC.js.map +1 -0
  32. package/dist/{chunk-OCOGQRHW.js → chunk-HS6DBMST.js} +2 -2
  33. package/dist/{chunk-QUJGXSZ6.js → chunk-Q57CHIIN.js} +154 -37
  34. package/dist/chunk-Q57CHIIN.js.map +1 -0
  35. package/dist/{chunk-2VRPF25R.js → chunk-RL43UYQZ.js} +3 -3
  36. package/dist/{chunk-QL2XVZRN.js → chunk-TUR7RGEP.js} +12 -1
  37. package/dist/{chunk-QL2XVZRN.js.map → chunk-TUR7RGEP.js.map} +1 -1
  38. package/dist/{export-audit-MT2LTMIN.js → export-audit-ODGQTQIK.js} +3 -3
  39. package/dist/index.cjs +132 -3
  40. package/dist/index.cjs.map +1 -1
  41. package/dist/index.d.cts +37 -5
  42. package/dist/index.d.ts +37 -5
  43. package/dist/index.js +131 -12
  44. package/dist/index.js.map +1 -1
  45. package/dist/internal.cjs +1 -0
  46. package/dist/internal.cjs.map +1 -1
  47. package/dist/internal.d.cts +3 -3
  48. package/dist/internal.d.ts +3 -3
  49. package/dist/internal.js +3 -3
  50. package/dist/{sqlite-wasm-channel-DUDPclTE.d.cts → sqlite-wasm-channel-Bx57s8Cq.d.cts} +1 -1
  51. package/dist/{sqlite-wasm-channel-DUDPclTE.d.ts → sqlite-wasm-channel-Bx57s8Cq.d.ts} +1 -1
  52. package/dist/sqlite-wasm-channel-NGSBJR44.js +12 -0
  53. package/dist/{types-5h4Hqcwc.d.ts → types-C1kigrWz.d.ts} +1 -1
  54. package/dist/{types-k6AZgiH4.d.ts → types-D1MzKVmk.d.cts} +10 -0
  55. package/dist/{types-k6AZgiH4.d.cts → types-D1MzKVmk.d.ts} +10 -0
  56. package/dist/{types-DfPiPwKr.d.cts → types-DAo9oduO.d.cts} +1 -1
  57. package/package.json +2 -2
  58. package/dist/chunk-6NKLDBTY.js.map +0 -1
  59. package/dist/chunk-QUJGXSZ6.js.map +0 -1
  60. package/dist/sqlite-wasm-channel-H43KEZLM.js +0 -12
  61. /package/dist/{backup-IQE4QC7Q.js.map → backup-5S7K2Z35.js.map} +0 -0
  62. /package/dist/{chunk-IOEFNHHP.js.map → chunk-COJSQBXW.js.map} +0 -0
  63. /package/dist/{chunk-RCTG3IJG.js.map → chunk-CYO3YKFL.js.map} +0 -0
  64. /package/dist/{chunk-OCOGQRHW.js.map → chunk-HS6DBMST.js.map} +0 -0
  65. /package/dist/{chunk-2VRPF25R.js.map → chunk-RL43UYQZ.js.map} +0 -0
  66. /package/dist/{export-audit-MT2LTMIN.js.map → export-audit-ODGQTQIK.js.map} +0 -0
  67. /package/dist/{sqlite-wasm-channel-H43KEZLM.js.map → sqlite-wasm-channel-NGSBJR44.js.map} +0 -0
package/dist/index.cjs CHANGED
@@ -69,6 +69,7 @@ function encodeRichtext(value) {
69
69
  }
70
70
  if (typeof value === "string") {
71
71
  const doc = new Y.Doc();
72
+ doc.clientID = 0;
72
73
  doc.getText(TEXT_KEY).insert(0, value);
73
74
  return Y.encodeStateAsUpdate(doc);
74
75
  }
@@ -995,6 +996,7 @@ __export(index_exports, {
995
996
  InvalidStateTransitionError: () => InvalidStateTransitionError,
996
997
  LAST_ACKED_SERVER_VECTOR_META_KEY: () => LAST_ACKED_SERVER_VECTOR_META_KEY,
997
998
  MemoryBlobStore: () => MemoryBlobStore,
999
+ NoLeaderError: () => NoLeaderError,
998
1000
  OpfsBlobStore: () => OpfsBlobStore,
999
1001
  OptimisticLockError: () => OptimisticLockError,
1000
1002
  PersistenceError: () => PersistenceError,
@@ -1113,6 +1115,16 @@ var WorkerTimeoutError = class extends import_core.KoraError {
1113
1115
  this.name = "WorkerTimeoutError";
1114
1116
  }
1115
1117
  };
1118
+ var NoLeaderError = class extends import_core.KoraError {
1119
+ constructor(operation) {
1120
+ super(
1121
+ `No live leader tab is answering multi-tab storage RPC for operation "${operation}"`,
1122
+ "NO_LEADER",
1123
+ { operation }
1124
+ );
1125
+ this.name = "NoLeaderError";
1126
+ }
1127
+ };
1116
1128
  var PersistenceError = class extends import_core.KoraError {
1117
1129
  constructor(message, context) {
1118
1130
  super(`Persistence error: ${message}`, "PERSISTENCE_ERROR", context);
@@ -1187,6 +1199,7 @@ function buildDeleteSql(table, withAge) {
1187
1199
  var import_core3 = require("@korajs/core");
1188
1200
  var LAST_ACKED_SERVER_VECTOR_META_KEY = "last_acked_server_vector";
1189
1201
  var DELTA_CURSOR_META_KEY = "delta_cursor";
1202
+ var DELIVERY_WATERMARK_META_KEY = "delivery_watermark";
1190
1203
  function serializeVersionVectorToMeta(vector) {
1191
1204
  const record = {};
1192
1205
  for (const [nodeId, seq] of vector) {
@@ -1259,6 +1272,45 @@ async function saveDeltaCursor(adapter, cursor) {
1259
1272
  cursor
1260
1273
  ]);
1261
1274
  }
1275
+ function deliveryWatermarkKey(signature) {
1276
+ return signature === "" ? DELIVERY_WATERMARK_META_KEY : `${DELIVERY_WATERMARK_META_KEY}:${signature}`;
1277
+ }
1278
+ async function loadDeliveryWatermark(adapter, signature) {
1279
+ const rows = await adapter.query("SELECT value FROM _kora_meta WHERE key = ?", [
1280
+ deliveryWatermarkKey(signature)
1281
+ ]);
1282
+ const value = rows[0]?.value;
1283
+ if (value === void 0 || value === null) {
1284
+ return 0;
1285
+ }
1286
+ const parsed = Number(value);
1287
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : 0;
1288
+ }
1289
+ async function saveDeliveryWatermark(adapter, signature, watermark) {
1290
+ await adapter.execute("INSERT OR REPLACE INTO _kora_meta (key, value) VALUES (?, ?)", [
1291
+ deliveryWatermarkKey(signature),
1292
+ String(watermark)
1293
+ ]);
1294
+ }
1295
+ async function deleteDeliveryWatermark(adapter, signature) {
1296
+ await adapter.execute("DELETE FROM _kora_meta WHERE key = ?", [deliveryWatermarkKey(signature)]);
1297
+ }
1298
+ async function loadAllDeliveryWatermarks(adapter) {
1299
+ const rows = await adapter.query(
1300
+ "SELECT key, value FROM _kora_meta WHERE key = ? OR key LIKE ?",
1301
+ [DELIVERY_WATERMARK_META_KEY, `${DELIVERY_WATERMARK_META_KEY}:%`]
1302
+ );
1303
+ const result = {};
1304
+ const prefix = `${DELIVERY_WATERMARK_META_KEY}:`;
1305
+ for (const row of rows) {
1306
+ const signature = row.key === DELIVERY_WATERMARK_META_KEY ? "" : row.key.slice(prefix.length);
1307
+ const parsed = Number(row.value);
1308
+ if (Number.isFinite(parsed) && parsed >= 0) {
1309
+ result[signature] = parsed;
1310
+ }
1311
+ }
1312
+ return result;
1313
+ }
1262
1314
 
1263
1315
  // src/store/store.ts
1264
1316
  var import_core23 = require("@korajs/core");
@@ -3880,7 +3932,8 @@ var Store = class {
3880
3932
  throw new OptimisticLockError(collection, op.recordId);
3881
3933
  }
3882
3934
  };
3883
- if (op.type === "insert" && materializeSource) {
3935
+ if (options?.logOnly) {
3936
+ } else if (op.type === "insert" && materializeSource) {
3884
3937
  const serializedData = serializeRecord(materializeSource, definition.fields);
3885
3938
  const existing2 = await tx.query(
3886
3939
  `SELECT _updated_at, _version, _field_versions, _deleted FROM ${collection} WHERE id = ?`,
@@ -3902,7 +3955,7 @@ var Store = class {
3902
3955
  };
3903
3956
  const insertQuery = buildInsertQuery(collection, record);
3904
3957
  await tx.execute(insertQuery.sql, insertQuery.params);
3905
- await this.foldOrphanedOperations(tx, collection, definition, op.recordId);
3958
+ await this.foldOrphanedOperations(tx, collection, definition, op.recordId, op);
3906
3959
  } else if (row._deleted === 1) {
3907
3960
  if (isIncomingNewerThanRow(materializeTimestamp, row)) {
3908
3961
  const fieldChanges = {
@@ -4054,7 +4107,7 @@ var Store = class {
4054
4107
  * necessarily an orphan (rows are soft-deleted, never removed, so a row
4055
4108
  * that is absent now was never materialized).
4056
4109
  */
4057
- async foldOrphanedOperations(tx, collection, definition, recordId) {
4110
+ async foldOrphanedOperations(tx, collection, definition, recordId, insertOp) {
4058
4111
  const orphanRows = await tx.query(
4059
4112
  `SELECT * FROM ${(0, import_core23.quoteIdent)(`_kora_ops_${collection}`)} WHERE record_id = ? AND type IN ('update', 'delete')`,
4060
4113
  [recordId]
@@ -4103,6 +4156,37 @@ var Store = class {
4103
4156
  await tx.execute(query.sql, query.params);
4104
4157
  }
4105
4158
  }
4159
+ const atomicFields = /* @__PURE__ */ new Set();
4160
+ for (const orphan of orphans) {
4161
+ if (orphan.type === "update" && orphan.atomicOps) {
4162
+ for (const field of Object.keys(orphan.atomicOps)) {
4163
+ atomicFields.add(field);
4164
+ }
4165
+ }
4166
+ }
4167
+ if (atomicFields.size > 0) {
4168
+ const ordered = [insertOp, ...orphans].sort(
4169
+ (a, b) => import_core23.HybridLogicalClock.compare(a.timestamp, b.timestamp)
4170
+ );
4171
+ const folded = (0, import_core23.replayOperationsForRecord)(ordered);
4172
+ if (folded) {
4173
+ const composed = {};
4174
+ for (const field of atomicFields) {
4175
+ if (field in folded) {
4176
+ composed[field] = folded[field];
4177
+ }
4178
+ }
4179
+ const serialized = serializeRecord(composed, definition.fields);
4180
+ const cols = Object.keys(serialized);
4181
+ if (cols.length > 0) {
4182
+ const setClause = cols.map((c) => `${(0, import_core23.quoteIdent)(c)} = ?`).join(", ");
4183
+ await tx.execute(`UPDATE ${(0, import_core23.quoteIdent)(collection)} SET ${setClause} WHERE id = ?`, [
4184
+ ...cols.map((c) => serialized[c]),
4185
+ recordId
4186
+ ]);
4187
+ }
4188
+ }
4189
+ }
4106
4190
  }
4107
4191
  /**
4108
4192
  * Get operations from a node within a sequence number range.
@@ -4316,6 +4400,35 @@ var Store = class {
4316
4400
  this.ensureOpen();
4317
4401
  await saveDeltaCursor(this.adapter, cursor);
4318
4402
  }
4403
+ /**
4404
+ * Load the persisted delivery watermark for a view signature (0 when none recorded).
4405
+ * Defaults to the unfiltered view so existing single-watermark reads keep working.
4406
+ */
4407
+ async loadDeliveryWatermark(signature = "") {
4408
+ this.ensureOpen();
4409
+ return loadDeliveryWatermark(this.adapter, signature);
4410
+ }
4411
+ /**
4412
+ * Persist the delivery watermark for a view signature so it survives a client restart.
4413
+ */
4414
+ async saveDeliveryWatermark(signature, watermark) {
4415
+ this.ensureOpen();
4416
+ await saveDeliveryWatermark(this.adapter, signature, watermark);
4417
+ }
4418
+ /**
4419
+ * Load every persisted view watermark, keyed by signature.
4420
+ */
4421
+ async loadAllDeliveryWatermarks() {
4422
+ this.ensureOpen();
4423
+ return loadAllDeliveryWatermarks(this.adapter);
4424
+ }
4425
+ /**
4426
+ * Delete a persisted view watermark (safe: the view back-fills from 0 when next visited).
4427
+ */
4428
+ async deleteDeliveryWatermark(signature) {
4429
+ this.ensureOpen();
4430
+ await deleteDeliveryWatermark(this.adapter, signature);
4431
+ }
4319
4432
  /**
4320
4433
  * Local operations not yet reflected on the server version vector.
4321
4434
  */
@@ -4406,6 +4519,21 @@ var Store = class {
4406
4519
  }
4407
4520
  return latest;
4408
4521
  }
4522
+ /**
4523
+ * All operations for a record, sorted in HLC total order (wallTime, logical,
4524
+ * nodeId). Used by the apply pipeline to materialize atomic-op fields by folding
4525
+ * the record's log, matching the server's materialization exactly.
4526
+ */
4527
+ async getOperationsForRecord(collection, recordId) {
4528
+ this.ensureOpen();
4529
+ const rows = await this.adapter.query(
4530
+ `SELECT * FROM ${(0, import_core23.quoteIdent)(`_kora_ops_${collection}`)} WHERE record_id = ?`,
4531
+ [recordId]
4532
+ );
4533
+ const ops = rows.map((row) => deserializeOperationWithCollection(row, collection));
4534
+ ops.sort((a, b) => import_core23.HybridLogicalClock.compare(a.timestamp, b.timestamp));
4535
+ return ops;
4536
+ }
4409
4537
  /** Expose the subscription manager for direct access (e.g., by QueryBuilder) */
4410
4538
  getSubscriptionManager() {
4411
4539
  return this.subscriptionManager;
@@ -5700,6 +5828,7 @@ function createChunkPortPair() {
5700
5828
  InvalidStateTransitionError,
5701
5829
  LAST_ACKED_SERVER_VECTOR_META_KEY,
5702
5830
  MemoryBlobStore,
5831
+ NoLeaderError,
5703
5832
  OpfsBlobStore,
5704
5833
  OptimisticLockError,
5705
5834
  PersistenceError,