@korajs/store 1.0.0-beta.0 → 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 (81) 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-5XZ7THNJ.js → backup-5S7K2Z35.js} +3 -3
  21. package/dist/blob-fs.cjs +166 -0
  22. package/dist/blob-fs.cjs.map +1 -0
  23. package/dist/blob-fs.d.cts +29 -0
  24. package/dist/blob-fs.d.ts +29 -0
  25. package/dist/blob-fs.js +128 -0
  26. package/dist/blob-fs.js.map +1 -0
  27. package/dist/{chunk-EIVXWF4B.js → chunk-CYO3YKFL.js} +4 -3
  28. package/dist/chunk-CYO3YKFL.js.map +1 -0
  29. package/dist/{chunk-ETDJEH2I.js → chunk-GSJ7EEUC.js} +12 -1
  30. package/dist/chunk-GSJ7EEUC.js.map +1 -0
  31. package/dist/{chunk-5R4QMY3Z.js → chunk-HS6DBMST.js} +13 -10
  32. package/dist/chunk-HS6DBMST.js.map +1 -0
  33. package/dist/chunk-RJIEKEN4.js +751 -0
  34. package/dist/chunk-RJIEKEN4.js.map +1 -0
  35. package/dist/{chunk-QL2XVZRN.js → chunk-TUR7RGEP.js} +12 -1
  36. package/dist/{chunk-QL2XVZRN.js.map → chunk-TUR7RGEP.js.map} +1 -1
  37. package/dist/{chunk-IOEFNHHP.js → chunk-VDBJQ75K.js} +2 -2
  38. package/dist/chunk-VDBJQ75K.js.map +1 -0
  39. package/dist/chunk-W4MTTNEZ.js +55 -0
  40. package/dist/chunk-W4MTTNEZ.js.map +1 -0
  41. package/dist/{chunk-OUQDC5Z6.js → chunk-YT7MRFCP.js} +55 -35
  42. package/dist/chunk-YT7MRFCP.js.map +1 -0
  43. package/dist/content-addressed-blob-store-Dcj3SB9r.d.cts +60 -0
  44. package/dist/content-addressed-blob-store-Dcj3SB9r.d.ts +60 -0
  45. package/dist/{export-audit-VPGQWNR6.js → export-audit-ODGQTQIK.js} +3 -3
  46. package/dist/index.cjs +923 -131
  47. package/dist/index.cjs.map +1 -1
  48. package/dist/index.d.cts +443 -6
  49. package/dist/index.d.ts +443 -6
  50. package/dist/index.js +737 -49
  51. package/dist/index.js.map +1 -1
  52. package/dist/internal.cjs +69 -43
  53. package/dist/internal.cjs.map +1 -1
  54. package/dist/internal.d.cts +3 -3
  55. package/dist/internal.d.ts +3 -3
  56. package/dist/internal.js +3 -3
  57. package/dist/sqlite-wasm-channel-GGHA2BXU.js +12 -0
  58. package/dist/{sqlite-wasm-channel-DUDPclTE.d.cts → sqlite-wasm-channel-L6o3bWQK.d.cts} +1 -1
  59. package/dist/{sqlite-wasm-channel-DUDPclTE.d.ts → sqlite-wasm-channel-L6o3bWQK.d.ts} +1 -1
  60. package/dist/{types-CyjXwrjW.d.cts → types-BOO9EPt1.d.ts} +10 -3
  61. package/dist/{types-CJOVKxLw.d.ts → types-BslDOTk0.d.cts} +32 -2
  62. package/dist/{types-CJOVKxLw.d.cts → types-BslDOTk0.d.ts} +32 -2
  63. package/dist/{types-DAxCladl.d.ts → types-C_GDavG-.d.cts} +10 -3
  64. package/package.json +13 -12
  65. package/dist/adapters/sqlite-wasm-shared-host.cjs +0 -68
  66. package/dist/adapters/sqlite-wasm-shared-host.cjs.map +0 -1
  67. package/dist/adapters/sqlite-wasm-shared-host.d.cts +0 -2
  68. package/dist/adapters/sqlite-wasm-shared-host.d.ts +0 -2
  69. package/dist/adapters/sqlite-wasm-shared-host.js +0 -66
  70. package/dist/adapters/sqlite-wasm-shared-host.js.map +0 -1
  71. package/dist/chunk-5R4QMY3Z.js.map +0 -1
  72. package/dist/chunk-EIVXWF4B.js.map +0 -1
  73. package/dist/chunk-ETDJEH2I.js.map +0 -1
  74. package/dist/chunk-IOEFNHHP.js.map +0 -1
  75. package/dist/chunk-OUQDC5Z6.js.map +0 -1
  76. package/dist/chunk-RBPVEAVM.js +0 -425
  77. package/dist/chunk-RBPVEAVM.js.map +0 -1
  78. package/dist/sqlite-wasm-channel-H43KEZLM.js +0 -12
  79. /package/dist/{backup-5XZ7THNJ.js.map → backup-5S7K2Z35.js.map} +0 -0
  80. /package/dist/{export-audit-VPGQWNR6.js.map → export-audit-ODGQTQIK.js.map} +0 -0
  81. /package/dist/{sqlite-wasm-channel-H43KEZLM.js.map → sqlite-wasm-channel-GGHA2BXU.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
  }
@@ -239,6 +240,9 @@ function serializeValue(value, descriptor) {
239
240
  case "boolean":
240
241
  return value ? 1 : 0;
241
242
  case "array":
243
+ case "object":
244
+ case "json":
245
+ case "blob":
242
246
  return JSON.stringify(value);
243
247
  case "richtext":
244
248
  return encodeRichtext(value);
@@ -255,6 +259,13 @@ function deserializeValue(value, descriptor) {
255
259
  return JSON.parse(value);
256
260
  }
257
261
  return value;
262
+ case "object":
263
+ case "json":
264
+ case "blob":
265
+ if (typeof value === "string") {
266
+ return JSON.parse(value);
267
+ }
268
+ return value;
258
269
  case "richtext":
259
270
  return decodeRichtext(value);
260
271
  default:
@@ -518,7 +529,7 @@ async function restoreBackup(adapter, schema, data, options) {
518
529
  schema_version: op.schemaVersion
519
530
  };
520
531
  await tx.execute(
521
- `INSERT OR IGNORE INTO _kora_ops_${collection} (id, node_id, type, record_id, data, previous_data, timestamp, sequence_number, causal_deps, schema_version) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
532
+ `INSERT OR IGNORE INTO ${(0, import_core6.quoteIdent)(`_kora_ops_${collection}`)} (id, node_id, type, record_id, data, previous_data, timestamp, sequence_number, causal_deps, schema_version) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
522
533
  [
523
534
  opRow.id,
524
535
  opRow.node_id,
@@ -537,8 +548,8 @@ async function restoreBackup(adapter, schema, data, options) {
537
548
  } else {
538
549
  await adapter.transaction(async (tx) => {
539
550
  for (const collection of Object.keys(schema.collections)) {
540
- await tx.execute(`DELETE FROM _kora_ops_${collection}`);
541
- await tx.execute(`DELETE FROM ${collection}`);
551
+ await tx.execute(`DELETE FROM ${(0, import_core6.quoteIdent)(`_kora_ops_${collection}`)}`);
552
+ await tx.execute(`DELETE FROM ${(0, import_core6.quoteIdent)(collection)}`);
542
553
  }
543
554
  await tx.execute("DELETE FROM _kora_version_vector");
544
555
  await tx.execute("DELETE FROM _kora_meta");
@@ -558,7 +569,7 @@ async function restoreBackup(adapter, schema, data, options) {
558
569
  for (const op of operations) {
559
570
  const collection = op.collection;
560
571
  await tx.execute(
561
- `INSERT INTO _kora_ops_${collection} (id, node_id, type, record_id, data, previous_data, timestamp, sequence_number, causal_deps, schema_version) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
572
+ `INSERT INTO ${(0, import_core6.quoteIdent)(`_kora_ops_${collection}`)} (id, node_id, type, record_id, data, previous_data, timestamp, sequence_number, causal_deps, schema_version) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
562
573
  [
563
574
  op.id,
564
575
  op.nodeId,
@@ -590,6 +601,7 @@ async function restoreBackup(adapter, schema, data, options) {
590
601
  for (const record of records) {
591
602
  const keys = Object.keys(record);
592
603
  const placeholders = keys.map(() => "?").join(", ");
604
+ const quotedKeys = keys.map((k) => (0, import_core6.quoteIdent)(k)).join(", ");
593
605
  const values = keys.map((k) => {
594
606
  const v = record[k];
595
607
  if (typeof v === "boolean") return v ? 1 : 0;
@@ -597,7 +609,7 @@ async function restoreBackup(adapter, schema, data, options) {
597
609
  return v;
598
610
  });
599
611
  await tx.execute(
600
- `INSERT OR REPLACE INTO ${col} (${keys.join(", ")}) VALUES (${placeholders})`,
612
+ `INSERT OR REPLACE INTO ${(0, import_core6.quoteIdent)(col)} (${quotedKeys}) VALUES (${placeholders})`,
601
613
  values
602
614
  );
603
615
  }
@@ -630,7 +642,7 @@ async function readAllOperations(adapter, schema) {
630
642
  const allOps = [];
631
643
  for (const collectionName of Object.keys(schema.collections)) {
632
644
  const rows = await adapter.query(
633
- `SELECT * FROM _kora_ops_${collectionName} ORDER BY sequence_number ASC`
645
+ `SELECT * FROM ${(0, import_core6.quoteIdent)(`_kora_ops_${collectionName}`)} ORDER BY sequence_number ASC`
634
646
  );
635
647
  for (const row of rows) {
636
648
  allOps.push(deserializeOperationWithCollection(row, collectionName));
@@ -639,7 +651,9 @@ async function readAllOperations(adapter, schema) {
639
651
  return allOps;
640
652
  }
641
653
  async function readCollectionRecords(adapter, collection) {
642
- return adapter.query(`SELECT * FROM ${collection} WHERE _deleted = 0`);
654
+ return adapter.query(
655
+ `SELECT * FROM ${(0, import_core6.quoteIdent)(collection)} WHERE _deleted = 0`
656
+ );
643
657
  }
644
658
  async function computeSha256(data) {
645
659
  const hashBuffer = await crypto.subtle.digest("SHA-256", data);
@@ -707,7 +721,7 @@ __export(audit_trace_store_exports, {
707
721
  });
708
722
  function persistedAuditTraceFromEvent(event) {
709
723
  return {
710
- id: (0, import_core18.generateUUIDv7)(),
724
+ id: (0, import_core21.generateUUIDv7)(),
711
725
  recordedAt: Date.now(),
712
726
  eventType: event.type,
713
727
  constraint: event.type === "constraint:violated" ? event.constraint : void 0,
@@ -773,11 +787,11 @@ function auditTraceIndexFields(trace) {
773
787
  recordId: trace.operationA.recordId
774
788
  };
775
789
  }
776
- var import_core18;
790
+ var import_core21;
777
791
  var init_audit_trace_store = __esm({
778
792
  "src/audit/audit-trace-store.ts"() {
779
793
  "use strict";
780
- import_core18 = require("@korajs/core");
794
+ import_core21 = require("@korajs/core");
781
795
  init_audit_json();
782
796
  }
783
797
  });
@@ -849,7 +863,7 @@ async function readAllOperations2(adapter, schema, collectionFilter) {
849
863
  const allOps = [];
850
864
  for (const collectionName of collections) {
851
865
  const rows = await adapter.query(
852
- `SELECT * FROM _kora_ops_${collectionName} ORDER BY sequence_number ASC`
866
+ `SELECT * FROM ${(0, import_core22.quoteIdent)(`_kora_ops_${collectionName}`)} ORDER BY sequence_number ASC`
853
867
  );
854
868
  for (const row of rows) {
855
869
  allOps.push(deserializeOperationWithCollection(row, collectionName));
@@ -960,9 +974,11 @@ function parseNdjsonSection(content) {
960
974
  }
961
975
  return text.split("\n").map((line) => deserializeAuditJson(line));
962
976
  }
977
+ var import_core22;
963
978
  var init_export_audit = __esm({
964
979
  "src/audit/export-audit.ts"() {
965
980
  "use strict";
981
+ import_core22 = require("@korajs/core");
966
982
  init_serializer();
967
983
  init_audit_json();
968
984
  init_audit_trace_store();
@@ -973,10 +989,15 @@ var init_export_audit = __esm({
973
989
  var index_exports = {};
974
990
  __export(index_exports, {
975
991
  AdapterError: () => AdapterError,
992
+ BlobIntegrityError: () => BlobIntegrityError,
976
993
  COMPACTION_BASELINE_META_KEY: () => COMPACTION_BASELINE_META_KEY,
977
994
  Collection: () => Collection,
995
+ DEFAULT_CHUNK_SIZE: () => DEFAULT_CHUNK_SIZE,
978
996
  InvalidStateTransitionError: () => InvalidStateTransitionError,
979
997
  LAST_ACKED_SERVER_VECTOR_META_KEY: () => LAST_ACKED_SERVER_VECTOR_META_KEY,
998
+ MemoryBlobStore: () => MemoryBlobStore,
999
+ NoLeaderError: () => NoLeaderError,
1000
+ OpfsBlobStore: () => OpfsBlobStore,
980
1001
  OptimisticLockError: () => OptimisticLockError,
981
1002
  PersistenceError: () => PersistenceError,
982
1003
  QueryBuilder: () => QueryBuilder,
@@ -994,26 +1015,44 @@ __export(index_exports, {
994
1015
  WorkerTimeoutError: () => WorkerTimeoutError,
995
1016
  asRichTextSyncEngine: () => asRichTextSyncEngine,
996
1017
  assertQueryReady: () => assertQueryReady,
1018
+ chunkBlob: () => chunkBlob,
1019
+ collectBlobGarbage: () => collectBlobGarbage,
997
1020
  collectOperationsAheadOfServer: () => collectOperationsAheadOfServer,
998
1021
  compactOperationLog: () => compactOperationLog,
999
1022
  computeAckCompactionWatermark: () => computeAckCompactionWatermark,
1023
+ createChunkPortPair: () => createChunkPortPair,
1024
+ createMemoryServerBlobStore: () => createMemoryServerBlobStore,
1025
+ createOpfsBlobDirectory: () => createOpfsBlobDirectory,
1026
+ createOpfsBlobStore: () => createOpfsBlobStore,
1027
+ createRemoteChunkProvider: () => createRemoteChunkProvider,
1000
1028
  createRichTextController: () => createRichTextController,
1001
1029
  decodeAuditExport: () => decodeAuditExport,
1002
1030
  decodeRichtext: () => decodeRichtext,
1003
1031
  deserializeVersionVectorFromMeta: () => deserializeVersionVectorFromMeta,
1004
1032
  encodeRichtext: () => encodeRichtext,
1005
1033
  exportBackup: () => exportBackup,
1034
+ extractBlobRefs: () => extractBlobRefs,
1035
+ fetchBlobManifest: () => fetchBlobManifest,
1006
1036
  getSharedQueryStoreCache: () => getSharedQueryStoreCache,
1007
1037
  mergeVersionVectors: () => mergeVersionVectors,
1038
+ parseBlobManifest: () => parseBlobManifest,
1008
1039
  persistedAuditTraceFromEvent: () => persistedAuditTraceFromEvent,
1009
1040
  pluralize: () => pluralize,
1041
+ prepareBlobForSend: () => prepareBlobForSend,
1042
+ putBlobForTransfer: () => putBlobForTransfer,
1010
1043
  readAuditExportManifest: () => readAuditExportManifest,
1011
1044
  readBackupManifest: () => readBackupManifest,
1045
+ reassembleBlob: () => reassembleBlob,
1046
+ receiveBlob: () => receiveBlob,
1047
+ resolveBlobManifest: () => resolveBlobManifest,
1012
1048
  restoreBackup: () => restoreBackup,
1013
1049
  richtextStatesEqual: () => richtextStatesEqual,
1014
1050
  richtextToPlainText: () => richtextToPlainText,
1051
+ serializeBlobManifest: () => serializeBlobManifest,
1015
1052
  serializeVersionVectorToMeta: () => serializeVersionVectorToMeta,
1053
+ serveBlobChunks: () => serveBlobChunks,
1016
1054
  singularize: () => singularize,
1055
+ toServerBlobCallbacks: () => toServerBlobCallbacks,
1017
1056
  validateStateTransition: () => validateStateTransition,
1018
1057
  validateUpdateStateMachine: () => validateUpdateStateMachine,
1019
1058
  verifyAuditExportChecksum: () => verifyAuditExportChecksum,
@@ -1076,6 +1115,16 @@ var WorkerTimeoutError = class extends import_core.KoraError {
1076
1115
  this.name = "WorkerTimeoutError";
1077
1116
  }
1078
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
+ };
1079
1128
  var PersistenceError = class extends import_core.KoraError {
1080
1129
  constructor(message, context) {
1081
1130
  super(`Persistence error: ${message}`, "PERSISTENCE_ERROR", context);
@@ -1115,7 +1164,7 @@ async function compactOperationLog(adapter, schema, strategy, serverVector) {
1115
1164
  const collectionNames = Object.keys(schema.collections);
1116
1165
  await adapter.transaction(async (tx) => {
1117
1166
  for (const collectionName of collectionNames) {
1118
- const table = `_kora_ops_${collectionName}`;
1167
+ const table = (0, import_core2.quoteIdent)(`_kora_ops_${collectionName}`);
1119
1168
  for (const [nodeId, maxSeq] of watermark) {
1120
1169
  if (maxSeq <= 0) continue;
1121
1170
  const countSql = buildCountSql(table, ageCutoffSerialized !== null);
@@ -1150,6 +1199,7 @@ function buildDeleteSql(table, withAge) {
1150
1199
  var import_core3 = require("@korajs/core");
1151
1200
  var LAST_ACKED_SERVER_VECTOR_META_KEY = "last_acked_server_vector";
1152
1201
  var DELTA_CURSOR_META_KEY = "delta_cursor";
1202
+ var DELIVERY_WATERMARK_META_KEY = "delivery_watermark";
1153
1203
  function serializeVersionVectorToMeta(vector) {
1154
1204
  const record = {};
1155
1205
  for (const [nodeId, seq] of vector) {
@@ -1222,13 +1272,55 @@ async function saveDeltaCursor(adapter, cursor) {
1222
1272
  cursor
1223
1273
  ]);
1224
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
+ }
1225
1314
 
1226
1315
  // src/store/store.ts
1227
- var import_core19 = require("@korajs/core");
1316
+ var import_core23 = require("@korajs/core");
1228
1317
  init_backup();
1229
1318
 
1319
+ // src/collection/collection.ts
1320
+ var import_core13 = require("@korajs/core");
1321
+
1230
1322
  // src/mutations/execute-delete.ts
1231
- var import_core8 = require("@korajs/core");
1323
+ var import_core9 = require("@korajs/core");
1232
1324
 
1233
1325
  // src/lww/row-version.ts
1234
1326
  var import_core7 = require("@korajs/core");
@@ -1254,9 +1346,10 @@ function lwwVersionWhereClause(remoteVersion) {
1254
1346
  }
1255
1347
 
1256
1348
  // src/query/sql-builder.ts
1349
+ var import_core8 = require("@korajs/core");
1257
1350
  function buildSelectQuery(descriptor, fields) {
1258
1351
  const params = [];
1259
- const parts = [`SELECT * FROM ${descriptor.collection}`];
1352
+ const parts = [`SELECT * FROM ${(0, import_core8.quoteIdent)(descriptor.collection)}`];
1260
1353
  const whereClause = buildWhereClauseParts(descriptor.where, fields, params);
1261
1354
  const deletedFilter = "_deleted = 0";
1262
1355
  if (whereClause) {
@@ -1267,7 +1360,7 @@ function buildSelectQuery(descriptor, fields) {
1267
1360
  if (descriptor.orderBy.length > 0) {
1268
1361
  const orderParts = descriptor.orderBy.map((o) => {
1269
1362
  validateFieldName(o.field, fields);
1270
- return `${o.field} ${o.direction.toUpperCase()}`;
1363
+ return `${(0, import_core8.quoteIdent)(o.field)} ${o.direction.toUpperCase()}`;
1271
1364
  });
1272
1365
  parts.push(`ORDER BY ${orderParts.join(", ")}`);
1273
1366
  }
@@ -1281,7 +1374,7 @@ function buildSelectQuery(descriptor, fields) {
1281
1374
  }
1282
1375
  function buildCountQuery(descriptor, fields) {
1283
1376
  const params = [];
1284
- const parts = [`SELECT COUNT(*) as count FROM ${descriptor.collection}`];
1377
+ const parts = [`SELECT COUNT(*) as count FROM ${(0, import_core8.quoteIdent)(descriptor.collection)}`];
1285
1378
  const whereClause = buildWhereClauseParts(descriptor.where, fields, params);
1286
1379
  const deletedFilter = "_deleted = 0";
1287
1380
  if (whereClause) {
@@ -1295,17 +1388,18 @@ function buildInsertQuery(collection, record) {
1295
1388
  const columns = Object.keys(record);
1296
1389
  const placeholders = columns.map(() => "?");
1297
1390
  const params = Object.values(record);
1298
- const sql = `INSERT INTO ${collection} (${columns.join(", ")}) VALUES (${placeholders.join(", ")})`;
1391
+ const quotedColumns = columns.map((col) => (0, import_core8.quoteIdent)(col));
1392
+ const sql = `INSERT INTO ${(0, import_core8.quoteIdent)(collection)} (${quotedColumns.join(", ")}) VALUES (${placeholders.join(", ")})`;
1299
1393
  return { sql, params };
1300
1394
  }
1301
1395
  function buildUpdateQuery(collection, id, changes) {
1302
- const setClauses = Object.keys(changes).map((col) => `${col} = ?`);
1396
+ const setClauses = Object.keys(changes).map((col) => `${(0, import_core8.quoteIdent)(col)} = ?`);
1303
1397
  const params = [...Object.values(changes), id];
1304
- const sql = `UPDATE ${collection} SET ${setClauses.join(", ")} WHERE id = ?`;
1398
+ const sql = `UPDATE ${(0, import_core8.quoteIdent)(collection)} SET ${setClauses.join(", ")} WHERE id = ?`;
1305
1399
  return { sql, params };
1306
1400
  }
1307
1401
  function buildFieldFastForwardUpdateQuery(collection, id, fieldChanges, remoteVersion, wallTime, options) {
1308
- const fieldClauses = Object.keys(fieldChanges).map((col) => `${col} = ?`);
1402
+ const fieldClauses = Object.keys(fieldChanges).map((col) => `${(0, import_core8.quoteIdent)(col)} = ?`);
1309
1403
  const setClauses = [
1310
1404
  ...fieldClauses,
1311
1405
  "_version = CASE WHEN _version >= ? THEN _version ELSE ? END",
@@ -1323,25 +1417,25 @@ function buildFieldFastForwardUpdateQuery(collection, id, fieldChanges, remoteVe
1323
1417
  params.push(options.maxCreatedAt, options.maxCreatedAt);
1324
1418
  }
1325
1419
  params.push(id);
1326
- const sql = `UPDATE ${collection} SET ${setClauses.join(", ")} WHERE id = ?`;
1420
+ const sql = `UPDATE ${(0, import_core8.quoteIdent)(collection)} SET ${setClauses.join(", ")} WHERE id = ?`;
1327
1421
  return { sql, params };
1328
1422
  }
1329
1423
  function buildSoftDeleteQuery(collection, id, updatedAt, version) {
1330
1424
  if (version !== void 0) {
1331
1425
  return {
1332
- sql: `UPDATE ${collection} SET _deleted = 1, _updated_at = ?, _version = ? WHERE id = ?`,
1426
+ sql: `UPDATE ${(0, import_core8.quoteIdent)(collection)} SET _deleted = 1, _updated_at = ?, _version = ? WHERE id = ?`,
1333
1427
  params: [updatedAt, version, id]
1334
1428
  };
1335
1429
  }
1336
1430
  return {
1337
- sql: `UPDATE ${collection} SET _deleted = 1, _updated_at = ? WHERE id = ?`,
1431
+ sql: `UPDATE ${(0, import_core8.quoteIdent)(collection)} SET _deleted = 1, _updated_at = ? WHERE id = ?`,
1338
1432
  params: [updatedAt, id]
1339
1433
  };
1340
1434
  }
1341
1435
  function buildLwwSoftDeleteQuery(collection, id, updatedAt, version) {
1342
1436
  const lww = lwwVersionWhereClause(version);
1343
1437
  return {
1344
- sql: `UPDATE ${collection} SET _deleted = 1, _updated_at = ?, _version = ? WHERE id = ? AND ${lww.sql}`,
1438
+ sql: `UPDATE ${(0, import_core8.quoteIdent)(collection)} SET _deleted = 1, _updated_at = ?, _version = ? WHERE id = ? AND ${lww.sql}`,
1345
1439
  params: [updatedAt, version, id, ...lww.params]
1346
1440
  };
1347
1441
  }
@@ -1372,31 +1466,32 @@ function buildWhereClauseParts(where, fields, params) {
1372
1466
  }
1373
1467
  function buildOperatorCondition(fieldName, operator, value, descriptor, params) {
1374
1468
  const sqlValue = descriptor?.kind === "boolean" && typeof value === "boolean" ? value ? 1 : 0 : value;
1469
+ const column = (0, import_core8.quoteIdent)(fieldName);
1375
1470
  switch (operator) {
1376
1471
  case "$eq":
1377
1472
  if (sqlValue === null) {
1378
- return `${fieldName} IS NULL`;
1473
+ return `${column} IS NULL`;
1379
1474
  }
1380
1475
  params.push(sqlValue);
1381
- return `${fieldName} = ?`;
1476
+ return `${column} = ?`;
1382
1477
  case "$ne":
1383
1478
  if (sqlValue === null) {
1384
- return `${fieldName} IS NOT NULL`;
1479
+ return `${column} IS NOT NULL`;
1385
1480
  }
1386
1481
  params.push(sqlValue);
1387
- return `${fieldName} != ?`;
1482
+ return `${column} != ?`;
1388
1483
  case "$gt":
1389
1484
  params.push(sqlValue);
1390
- return `${fieldName} > ?`;
1485
+ return `${column} > ?`;
1391
1486
  case "$gte":
1392
1487
  params.push(sqlValue);
1393
- return `${fieldName} >= ?`;
1488
+ return `${column} >= ?`;
1394
1489
  case "$lt":
1395
1490
  params.push(sqlValue);
1396
- return `${fieldName} < ?`;
1491
+ return `${column} < ?`;
1397
1492
  case "$lte":
1398
1493
  params.push(sqlValue);
1399
- return `${fieldName} <= ?`;
1494
+ return `${column} <= ?`;
1400
1495
  case "$in": {
1401
1496
  if (!Array.isArray(sqlValue)) {
1402
1497
  throw new QueryError(`$in operator requires an array value for field "${fieldName}"`, {
@@ -1410,7 +1505,7 @@ function buildOperatorCondition(fieldName, operator, value, descriptor, params)
1410
1505
  descriptor?.kind === "boolean" && typeof item === "boolean" ? item ? 1 : 0 : item
1411
1506
  );
1412
1507
  }
1413
- return `${fieldName} IN (${placeholders.join(", ")})`;
1508
+ return `${column} IN (${placeholders.join(", ")})`;
1414
1509
  }
1415
1510
  default:
1416
1511
  throw new QueryError(`Unknown operator "${operator}"`, { operator });
@@ -1490,7 +1585,7 @@ function resolveCausalDeps(ctx) {
1490
1585
  // src/mutations/execute-delete.ts
1491
1586
  async function executeDelete(ctx, id, options) {
1492
1587
  const currentRows = await ctx.adapter.query(
1493
- `SELECT * FROM ${ctx.collection} WHERE id = ? AND _deleted = 0`,
1588
+ `SELECT * FROM ${(0, import_core9.quoteIdent)(ctx.collection)} WHERE id = ? AND _deleted = 0`,
1494
1589
  [id]
1495
1590
  );
1496
1591
  if (!currentRows[0]) {
@@ -1502,7 +1597,7 @@ async function executeDelete(ctx, id, options) {
1502
1597
  await ctx.adapter.transaction(async (tx) => {
1503
1598
  if (!operation) {
1504
1599
  const sequenceNumber = await allocateNextSequenceInTransaction(tx, ctx.nodeId);
1505
- operation = await (0, import_core8.createOperation)(
1600
+ operation = await (0, import_core9.createOperation)(
1506
1601
  {
1507
1602
  nodeId: ctx.nodeId,
1508
1603
  type: "delete",
@@ -1549,7 +1644,7 @@ async function executeDelete(ctx, id, options) {
1549
1644
  }
1550
1645
 
1551
1646
  // src/mutations/execute-insert.ts
1552
- var import_core9 = require("@korajs/core");
1647
+ var import_core10 = require("@korajs/core");
1553
1648
 
1554
1649
  // src/lww/field-versions.ts
1555
1650
  function parseFieldVersions(raw) {
@@ -1606,19 +1701,24 @@ function resolvePerFieldLww(current, incomingFields, incomingVersion, rowVersion
1606
1701
  init_op_data_encoding();
1607
1702
  init_serializer();
1608
1703
  async function executeInsert(ctx, data) {
1609
- const validated = (0, import_core9.validateRecord)(ctx.collection, ctx.definition, data, "insert");
1610
- const recordId = (0, import_core9.generateUUIDv7)();
1704
+ const validated = (0, import_core10.validateRecord)(ctx.collection, ctx.definition, data, "insert");
1705
+ const recordId = (0, import_core10.generateUUIDv7)();
1611
1706
  for (const [fieldName, descriptor] of Object.entries(ctx.definition.fields)) {
1612
1707
  if (descriptor.auto && descriptor.kind === "timestamp") {
1613
1708
  validated[fieldName] = Date.now();
1614
1709
  }
1615
1710
  }
1711
+ const writeData = await (0, import_core10.transformSecretFieldsForWrite)(
1712
+ validated,
1713
+ ctx.definition,
1714
+ ctx.secretKeyProvider
1715
+ );
1616
1716
  const causalDeps = resolveCausalDeps(ctx);
1617
1717
  let operation;
1618
1718
  let record;
1619
1719
  await ctx.adapter.transaction(async (tx) => {
1620
1720
  const sequenceNumber = await allocateNextSequenceInTransaction(tx, ctx.nodeId);
1621
- operation = await (0, import_core9.createOperation)(
1721
+ operation = await (0, import_core10.createOperation)(
1622
1722
  {
1623
1723
  nodeId: ctx.nodeId,
1624
1724
  type: "insert",
@@ -1627,7 +1727,7 @@ async function executeInsert(ctx, data) {
1627
1727
  // Binary richtext values are tagged as canonical JSON BEFORE the
1628
1728
  // operation is content-hashed, so the hash input, persisted JSON,
1629
1729
  // and wire payload are the identical value.
1630
- data: encodeRichtextFieldsForOpData(validated, ctx.definition.fields),
1730
+ data: encodeRichtextFieldsForOpData(writeData, ctx.definition.fields),
1631
1731
  previousData: null,
1632
1732
  sequenceNumber,
1633
1733
  causalDeps,
@@ -1636,7 +1736,7 @@ async function executeInsert(ctx, data) {
1636
1736
  ctx.clock
1637
1737
  );
1638
1738
  ctx.causalTracker?.afterOperation(ctx.collection, operation.id, ctx.inTransaction);
1639
- const serializedData = serializeRecord(validated, ctx.definition.fields);
1739
+ const serializedData = serializeRecord(writeData, ctx.definition.fields);
1640
1740
  const version = serializeRowVersion(operation.timestamp);
1641
1741
  record = {
1642
1742
  id: recordId,
@@ -1661,20 +1761,20 @@ async function executeInsert(ctx, data) {
1661
1761
  ctx.onMutation(ctx.collection, operation);
1662
1762
  return {
1663
1763
  id: recordId,
1664
- ...validated,
1764
+ ...writeData,
1665
1765
  createdAt: operation.timestamp.wallTime,
1666
1766
  updatedAt: operation.timestamp.wallTime
1667
1767
  };
1668
1768
  }
1669
1769
 
1670
1770
  // src/mutations/execute-update.ts
1671
- var import_core11 = require("@korajs/core");
1771
+ var import_core12 = require("@korajs/core");
1672
1772
  init_op_data_encoding();
1673
1773
  init_serializer();
1674
1774
 
1675
1775
  // src/state-machine/state-validator.ts
1676
- var import_core10 = require("@korajs/core");
1677
- var InvalidStateTransitionError = class extends import_core10.KoraError {
1776
+ var import_core11 = require("@korajs/core");
1777
+ var InvalidStateTransitionError = class extends import_core11.KoraError {
1678
1778
  constructor(collection, recordId, field, fromState, toState, allowedStates) {
1679
1779
  super(
1680
1780
  `Invalid state transition in collection "${collection}": cannot transition field "${field}" from "${fromState}" to "${toState}". Allowed transitions from "${fromState}": ${allowedStates.length > 0 ? allowedStates.join(", ") : "(none -- terminal state)"}`,
@@ -1708,7 +1808,7 @@ function validateStateTransition(collectionName, recordId, stateMachine, current
1708
1808
  collection: collectionName,
1709
1809
  transitions: stateMachine.transitions
1710
1810
  };
1711
- const transitionResult = (0, import_core10.validateTransition)(constraint, currentState, newState);
1811
+ const transitionResult = (0, import_core11.validateTransition)(constraint, currentState, newState);
1712
1812
  if (transitionResult.valid) {
1713
1813
  return { valid: true, allowedStates: transitionResult.allowedTargets };
1714
1814
  }
@@ -1757,14 +1857,14 @@ function validateUpdateStateMachine(collectionName, recordId, collectionDef, cur
1757
1857
  // src/mutations/execute-update.ts
1758
1858
  async function executeUpdate(ctx, id, data) {
1759
1859
  const currentRows = await ctx.adapter.query(
1760
- `SELECT * FROM ${ctx.collection} WHERE id = ? AND _deleted = 0`,
1860
+ `SELECT * FROM ${(0, import_core12.quoteIdent)(ctx.collection)} WHERE id = ? AND _deleted = 0`,
1761
1861
  [id]
1762
1862
  );
1763
1863
  const currentRow = currentRows[0];
1764
1864
  if (!currentRow) {
1765
1865
  throw new RecordNotFoundError(ctx.collection, id);
1766
1866
  }
1767
- let validated = (0, import_core11.validateRecord)(ctx.collection, ctx.definition, data, "update");
1867
+ let validated = (0, import_core12.validateRecord)(ctx.collection, ctx.definition, data, "update");
1768
1868
  const currentRecord = deserializeRecord(currentRow, ctx.definition.fields);
1769
1869
  validated = validateUpdateStateMachine(
1770
1870
  ctx.collection,
@@ -1782,19 +1882,24 @@ async function executeUpdate(ctx, id, data) {
1782
1882
  for (const key of Object.keys(validated)) {
1783
1883
  const value = validated[key];
1784
1884
  previousData[key] = currentRecord[key];
1785
- if ((0, import_core11.isAtomicOp)(value)) {
1786
- resolvedData[key] = (0, import_core11.resolveAtomicOp)(currentRecord[key], value);
1787
- atomicOps[key] = (0, import_core11.toAtomicOp)(value);
1885
+ if ((0, import_core12.isAtomicOp)(value)) {
1886
+ resolvedData[key] = (0, import_core12.resolveAtomicOp)(currentRecord[key], value);
1887
+ atomicOps[key] = (0, import_core12.toAtomicOp)(value);
1788
1888
  } else {
1789
1889
  resolvedData[key] = value;
1790
1890
  }
1791
1891
  }
1892
+ const writeData = await (0, import_core12.transformSecretFieldsForWrite)(
1893
+ resolvedData,
1894
+ ctx.definition,
1895
+ ctx.secretKeyProvider
1896
+ );
1792
1897
  const hasAtomicOps = Object.keys(atomicOps).length > 0;
1793
1898
  const causalDeps = resolveCausalDeps(ctx);
1794
1899
  let operation;
1795
1900
  await ctx.adapter.transaction(async (tx) => {
1796
1901
  const sequenceNumber = await allocateNextSequenceInTransaction(tx, ctx.nodeId);
1797
- operation = await (0, import_core11.createOperation)(
1902
+ operation = await (0, import_core12.createOperation)(
1798
1903
  {
1799
1904
  nodeId: ctx.nodeId,
1800
1905
  type: "update",
@@ -1803,7 +1908,7 @@ async function executeUpdate(ctx, id, data) {
1803
1908
  // Binary richtext values (new and previous) are tagged as canonical
1804
1909
  // JSON BEFORE the operation is content-hashed, so the hash input,
1805
1910
  // persisted JSON, and wire payload are the identical value.
1806
- data: encodeRichtextFieldsForOpData(resolvedData, ctx.definition.fields),
1911
+ data: encodeRichtextFieldsForOpData(writeData, ctx.definition.fields),
1807
1912
  previousData: encodeRichtextFieldsForOpData(previousData, ctx.definition.fields),
1808
1913
  sequenceNumber,
1809
1914
  causalDeps,
@@ -1813,7 +1918,7 @@ async function executeUpdate(ctx, id, data) {
1813
1918
  ctx.clock
1814
1919
  );
1815
1920
  ctx.causalTracker?.afterOperation(ctx.collection, operation.id, ctx.inTransaction);
1816
- const serializedChanges = serializeRecord(resolvedData, ctx.definition.fields);
1921
+ const serializedChanges = serializeRecord(writeData, ctx.definition.fields);
1817
1922
  const version = serializeRowVersion(operation.timestamp);
1818
1923
  const mergedFieldVersions = parseFieldVersions(currentRow._field_versions);
1819
1924
  for (const changedField of Object.keys(serializedChanges)) {
@@ -1834,7 +1939,7 @@ async function executeUpdate(ctx, id, data) {
1834
1939
  });
1835
1940
  ctx.onMutation(ctx.collection, operation);
1836
1941
  const rows = await ctx.adapter.query(
1837
- `SELECT * FROM ${ctx.collection} WHERE id = ? AND _deleted = 0`,
1942
+ `SELECT * FROM ${(0, import_core12.quoteIdent)(ctx.collection)} WHERE id = ? AND _deleted = 0`,
1838
1943
  [id]
1839
1944
  );
1840
1945
  const row = rows[0];
@@ -1847,7 +1952,7 @@ async function executeUpdate(ctx, id, data) {
1847
1952
  // src/collection/collection.ts
1848
1953
  init_serializer();
1849
1954
  var Collection = class {
1850
- constructor(name, definition, schema, adapter, clock, nodeId, allocateSequenceNumber, onMutation, relationEnforcer, mutationHandler, causalTracker) {
1955
+ constructor(name, definition, schema, adapter, clock, nodeId, allocateSequenceNumber, onMutation, relationEnforcer, mutationHandler, causalTracker, secretKeyProvider) {
1851
1956
  this.name = name;
1852
1957
  this.definition = definition;
1853
1958
  this.schema = schema;
@@ -1859,6 +1964,7 @@ var Collection = class {
1859
1964
  this.relationEnforcer = relationEnforcer;
1860
1965
  this.mutationHandler = mutationHandler;
1861
1966
  this.causalTracker = causalTracker;
1967
+ this.secretKeyProvider = secretKeyProvider;
1862
1968
  }
1863
1969
  name;
1864
1970
  definition;
@@ -1871,6 +1977,7 @@ var Collection = class {
1871
1977
  relationEnforcer;
1872
1978
  mutationHandler;
1873
1979
  causalTracker;
1980
+ secretKeyProvider;
1874
1981
  mutationContext() {
1875
1982
  return {
1876
1983
  collection: this.name,
@@ -1883,7 +1990,8 @@ var Collection = class {
1883
1990
  onMutation: this.onMutation,
1884
1991
  relationEnforcer: this.relationEnforcer,
1885
1992
  causalTracker: this.causalTracker,
1886
- inTransaction: false
1993
+ inTransaction: false,
1994
+ secretKeyProvider: this.secretKeyProvider
1887
1995
  };
1888
1996
  }
1889
1997
  async insert(data) {
@@ -1894,7 +2002,7 @@ var Collection = class {
1894
2002
  }
1895
2003
  async findById(id) {
1896
2004
  const rows = await this.adapter.query(
1897
- `SELECT * FROM ${this.name} WHERE id = ? AND _deleted = 0`,
2005
+ `SELECT * FROM ${(0, import_core13.quoteIdent)(this.name)} WHERE id = ? AND _deleted = 0`,
1898
2006
  [id]
1899
2007
  );
1900
2008
  const row = rows[0];
@@ -1927,6 +2035,7 @@ var Collection = class {
1927
2035
  };
1928
2036
 
1929
2037
  // src/query/query-builder.ts
2038
+ var import_core14 = require("@korajs/core");
1930
2039
  init_serializer();
1931
2040
 
1932
2041
  // src/query/pluralize.ts
@@ -2158,7 +2267,7 @@ var QueryBuilder = class _QueryBuilder {
2158
2267
  const relatedDef = this.schema?.collections[relatedCollection];
2159
2268
  if (!relatedDef) return;
2160
2269
  const placeholders = uniqueFks.map(() => "?").join(", ");
2161
- const sql = `SELECT * FROM ${relatedCollection} WHERE id IN (${placeholders}) AND _deleted = 0`;
2270
+ const sql = `SELECT * FROM ${(0, import_core14.quoteIdent)(relatedCollection)} WHERE id IN (${placeholders}) AND _deleted = 0`;
2162
2271
  const rows = await this.adapter.query(sql, uniqueFks);
2163
2272
  const relatedRecords = rows.map((row) => deserializeRecord(row, relatedDef.fields));
2164
2273
  const lookup = /* @__PURE__ */ new Map();
@@ -2181,7 +2290,7 @@ var QueryBuilder = class _QueryBuilder {
2181
2290
  if (!relatedDef) return;
2182
2291
  const fkField = relation.field;
2183
2292
  const placeholders = primaryIds.map(() => "?").join(", ");
2184
- const sql = `SELECT * FROM ${relatedCollection} WHERE ${fkField} IN (${placeholders}) AND _deleted = 0`;
2293
+ const sql = `SELECT * FROM ${(0, import_core14.quoteIdent)(relatedCollection)} WHERE ${(0, import_core14.quoteIdent)(fkField)} IN (${placeholders}) AND _deleted = 0`;
2185
2294
  const rows = await this.adapter.query(sql, primaryIds);
2186
2295
  const relatedRecords = rows.map((row) => deserializeRecord(row, relatedDef.fields));
2187
2296
  const grouped = /* @__PURE__ */ new Map();
@@ -2223,7 +2332,7 @@ var QueryError2 = class extends Error {
2223
2332
  };
2224
2333
 
2225
2334
  // src/relations/relation-enforcer.ts
2226
- var import_core12 = require("@korajs/core");
2335
+ var import_core15 = require("@korajs/core");
2227
2336
  init_serializer();
2228
2337
 
2229
2338
  // src/relations/relation-lookup.ts
@@ -2248,7 +2357,7 @@ function getIncomingRelations(lookup, collection) {
2248
2357
  }
2249
2358
 
2250
2359
  // src/relations/relation-enforcer.ts
2251
- var ReferentialIntegrityError = class extends import_core12.KoraError {
2360
+ var ReferentialIntegrityError = class extends import_core15.KoraError {
2252
2361
  constructor(collection, recordId, referencingCollection, relationName, referencingCount) {
2253
2362
  super(
2254
2363
  `Cannot delete record "${recordId}" from "${collection}": ${referencingCount} record(s) in "${referencingCollection}" reference it via relation "${relationName}" with onDelete: 'restrict'. Delete or reassign the referencing records first.`,
@@ -2327,7 +2436,7 @@ var RelationEnforcer = class {
2327
2436
  async enforceCascade(incoming, deletedRecordId, tx, causalDeps) {
2328
2437
  const { sourceCollection, foreignKeyField } = incoming;
2329
2438
  const referencingRows = await tx.query(
2330
- `SELECT id FROM ${sourceCollection} WHERE ${foreignKeyField} = ? AND _deleted = 0`,
2439
+ `SELECT id FROM ${(0, import_core15.quoteIdent)(sourceCollection)} WHERE ${(0, import_core15.quoteIdent)(foreignKeyField)} = ? AND _deleted = 0`,
2331
2440
  [deletedRecordId]
2332
2441
  );
2333
2442
  if (referencingRows.length === 0) {
@@ -2337,7 +2446,7 @@ var RelationEnforcer = class {
2337
2446
  for (const row of referencingRows) {
2338
2447
  const now = Date.now();
2339
2448
  const sequenceNumber = await allocateNextSequenceInTransaction(tx, this.nodeId);
2340
- const operation = await (0, import_core12.createOperation)(
2449
+ const operation = await (0, import_core15.createOperation)(
2341
2450
  {
2342
2451
  nodeId: this.nodeId,
2343
2452
  type: "delete",
@@ -2374,7 +2483,7 @@ var RelationEnforcer = class {
2374
2483
  return [];
2375
2484
  }
2376
2485
  const referencingRows = await tx.query(
2377
- `SELECT id FROM ${sourceCollection} WHERE ${foreignKeyField} = ? AND _deleted = 0`,
2486
+ `SELECT id FROM ${(0, import_core15.quoteIdent)(sourceCollection)} WHERE ${(0, import_core15.quoteIdent)(foreignKeyField)} = ? AND _deleted = 0`,
2378
2487
  [deletedRecordId]
2379
2488
  );
2380
2489
  if (referencingRows.length === 0) {
@@ -2386,7 +2495,7 @@ var RelationEnforcer = class {
2386
2495
  const sequenceNumber = await allocateNextSequenceInTransaction(tx, this.nodeId);
2387
2496
  const updateData = { [foreignKeyField]: null };
2388
2497
  const previousData = { [foreignKeyField]: deletedRecordId };
2389
- const operation = await (0, import_core12.createOperation)(
2498
+ const operation = await (0, import_core15.createOperation)(
2390
2499
  {
2391
2500
  nodeId: this.nodeId,
2392
2501
  type: "update",
@@ -2422,7 +2531,7 @@ var RelationEnforcer = class {
2422
2531
  async enforceRestrict(incoming, deletedRecordId, tx) {
2423
2532
  const { sourceCollection, foreignKeyField, relationName } = incoming;
2424
2533
  const countRows = await tx.query(
2425
- `SELECT COUNT(*) as cnt FROM ${sourceCollection} WHERE ${foreignKeyField} = ? AND _deleted = 0`,
2534
+ `SELECT COUNT(*) as cnt FROM ${(0, import_core15.quoteIdent)(sourceCollection)} WHERE ${(0, import_core15.quoteIdent)(foreignKeyField)} = ? AND _deleted = 0`,
2426
2535
  [deletedRecordId]
2427
2536
  );
2428
2537
  const count = countRows[0]?.cnt ?? 0;
@@ -2447,7 +2556,7 @@ var RelationEnforcer = class {
2447
2556
  };
2448
2557
 
2449
2558
  // src/replay/replay-to.ts
2450
- var import_core13 = require("@korajs/core");
2559
+ var import_core16 = require("@korajs/core");
2451
2560
  var import_internal = require("@korajs/core/internal");
2452
2561
  init_op_data_encoding();
2453
2562
  function collectCausalClosure(allOps, targetOperationId) {
@@ -2457,7 +2566,7 @@ function collectCausalClosure(allOps, targetOperationId) {
2457
2566
  }
2458
2567
  const target = opMap.get(targetOperationId);
2459
2568
  if (!target) {
2460
- throw new import_core13.OperationError(
2569
+ throw new import_core16.OperationError(
2461
2570
  `Operation "${targetOperationId}" not found in the local operation log`,
2462
2571
  {
2463
2572
  operationId: targetOperationId
@@ -2489,7 +2598,7 @@ function buildReplaySnapshot(schema, allOps, targetOperationId) {
2489
2598
  const operationsApplied = collectCausalClosure(allOps, targetOperationId);
2490
2599
  const targetOperation = operationsApplied.find((op) => op.id === targetOperationId);
2491
2600
  if (!targetOperation) {
2492
- throw new import_core13.OperationError(`Operation "${targetOperationId}" not found after causal sort`, {
2601
+ throw new import_core16.OperationError(`Operation "${targetOperationId}" not found after causal sort`, {
2493
2602
  operationId: targetOperationId
2494
2603
  });
2495
2604
  }
@@ -2615,7 +2724,7 @@ function toCollectionRecord(record, definition) {
2615
2724
  }
2616
2725
 
2617
2726
  // src/sequences/sequence-manager.ts
2618
- var import_core14 = require("@korajs/core");
2727
+ var import_core17 = require("@korajs/core");
2619
2728
  var SequenceManager = class {
2620
2729
  adapter;
2621
2730
  nodeId;
@@ -2633,7 +2742,7 @@ var SequenceManager = class {
2633
2742
  async next(name, config) {
2634
2743
  const scope = config?.scope ?? "";
2635
2744
  const startAt = config?.startAt ?? 1;
2636
- const format = config?.format ?? (0, import_core14.defaultSequenceFormat)(name);
2745
+ const format = config?.format ?? (0, import_core17.defaultSequenceFormat)(name);
2637
2746
  let counter = 0;
2638
2747
  await this.adapter.transaction(async (tx) => {
2639
2748
  const rows = await tx.query(
@@ -2655,7 +2764,7 @@ var SequenceManager = class {
2655
2764
  );
2656
2765
  }
2657
2766
  });
2658
- return (0, import_core14.formatSequenceValue)(format, counter, this.nodeId);
2767
+ return (0, import_core17.formatSequenceValue)(format, counter, this.nodeId);
2659
2768
  }
2660
2769
  /**
2661
2770
  * Get the current counter value without incrementing.
@@ -3114,10 +3223,10 @@ var SubscriptionManager = class {
3114
3223
  };
3115
3224
 
3116
3225
  // src/sync/rebase-unsynced-operations.ts
3117
- var import_core15 = require("@korajs/core");
3226
+ var import_core18 = require("@korajs/core");
3118
3227
  var import_internal2 = require("@korajs/core/internal");
3119
3228
  init_serializer();
3120
- var LOGICAL_COUNTER_LIMIT = import_core15.MAX_LOGICAL + 1;
3229
+ var LOGICAL_COUNTER_LIMIT = import_core18.MAX_LOGICAL + 1;
3121
3230
  function emptyResult() {
3122
3231
  return { operations: [], idMapping: {}, rebasedCount: 0, newMaxTimestamp: null };
3123
3232
  }
@@ -3129,7 +3238,9 @@ async function rebaseUnsyncedOperationsInLog(adapter, schema, unsyncedOpIds, cor
3129
3238
  const rebased = [];
3130
3239
  let maxOtherSerializedTs = null;
3131
3240
  for (const collectionName of Object.keys(schema.collections)) {
3132
- const rows = await adapter.query(`SELECT * FROM _kora_ops_${collectionName}`);
3241
+ const rows = await adapter.query(
3242
+ `SELECT * FROM ${(0, import_core18.quoteIdent)(`_kora_ops_${collectionName}`)}`
3243
+ );
3133
3244
  for (const row of rows) {
3134
3245
  if (rebaseIds.has(row.id)) {
3135
3246
  rebased.push(deserializeOperationWithCollection(row, collectionName));
@@ -3141,9 +3252,9 @@ async function rebaseUnsyncedOperationsInLog(adapter, schema, unsyncedOpIds, cor
3141
3252
  if (rebased.length === 0) {
3142
3253
  return emptyResult();
3143
3254
  }
3144
- const maxOtherWallTime = maxOtherSerializedTs === null ? Number.NEGATIVE_INFINITY : import_core15.HybridLogicalClock.deserialize(maxOtherSerializedTs).wallTime;
3255
+ const maxOtherWallTime = maxOtherSerializedTs === null ? Number.NEGATIVE_INFINITY : import_core18.HybridLogicalClock.deserialize(maxOtherSerializedTs).wallTime;
3145
3256
  const baseWallTime = Math.max(correctedNowMs, maxOtherWallTime + 1);
3146
- rebased.sort((a, b) => import_core15.HybridLogicalClock.compare(a.timestamp, b.timestamp));
3257
+ rebased.sort((a, b) => import_core18.HybridLogicalClock.compare(a.timestamp, b.timestamp));
3147
3258
  const newTimestamps = rebased.map((op, i) => ({
3148
3259
  // Spill into wallTime past the 5-digit logical serialization limit so the
3149
3260
  // serialized order stays correct even for enormous offline queues.
@@ -3171,7 +3282,7 @@ async function rebaseUnsyncedOperationsInLog(adapter, schema, unsyncedOpIds, cor
3171
3282
  schemaVersion: op.schemaVersion,
3172
3283
  ...op.atomicOps !== void 0 ? { atomicOps: op.atomicOps } : {}
3173
3284
  };
3174
- const newId = await (0, import_internal2.computeOperationId)(input, import_core15.HybridLogicalClock.serialize(ts));
3285
+ const newId = await (0, import_internal2.computeOperationId)(input, import_core18.HybridLogicalClock.serialize(ts));
3175
3286
  idMapping[op.id] = newId;
3176
3287
  newIds.push(newId);
3177
3288
  }
@@ -3198,7 +3309,7 @@ async function rebaseUnsyncedOperationsInLog(adapter, schema, unsyncedOpIds, cor
3198
3309
  continue;
3199
3310
  }
3200
3311
  const opsTable = `_kora_ops_${oldOp.collection}`;
3201
- await tx.execute(`DELETE FROM ${opsTable} WHERE id = ?`, [oldOp.id]);
3312
+ await tx.execute(`DELETE FROM ${(0, import_core18.quoteIdent)(opsTable)} WHERE id = ?`, [oldOp.id]);
3202
3313
  const opInsert = buildInsertQuery(
3203
3314
  opsTable,
3204
3315
  serializeOperation(newOp)
@@ -3207,11 +3318,11 @@ async function rebaseUnsyncedOperationsInLog(adapter, schema, unsyncedOpIds, cor
3207
3318
  const oldVersion = serializeRowVersion(oldOp.timestamp);
3208
3319
  const newVersion = serializeRowVersion(newOp.timestamp);
3209
3320
  await tx.execute(
3210
- `UPDATE ${oldOp.collection} SET _version = ?, _updated_at = ? WHERE id = ? AND _version = ?`,
3321
+ `UPDATE ${(0, import_core18.quoteIdent)(oldOp.collection)} SET _version = ?, _updated_at = ? WHERE id = ? AND _version = ?`,
3211
3322
  [newVersion, newOp.timestamp.wallTime, oldOp.recordId, oldVersion]
3212
3323
  );
3213
3324
  const rows = await tx.query(
3214
- `SELECT _field_versions FROM ${oldOp.collection} WHERE id = ?`,
3325
+ `SELECT _field_versions FROM ${(0, import_core18.quoteIdent)(oldOp.collection)} WHERE id = ?`,
3215
3326
  [oldOp.recordId]
3216
3327
  );
3217
3328
  const fieldVersions = parseFieldVersions(rows[0]?._field_versions);
@@ -3223,14 +3334,14 @@ async function rebaseUnsyncedOperationsInLog(adapter, schema, unsyncedOpIds, cor
3223
3334
  }
3224
3335
  }
3225
3336
  if (changed) {
3226
- await tx.execute(`UPDATE ${oldOp.collection} SET _field_versions = ? WHERE id = ?`, [
3227
- serializeFieldVersions(fieldVersions),
3228
- oldOp.recordId
3229
- ]);
3337
+ await tx.execute(
3338
+ `UPDATE ${(0, import_core18.quoteIdent)(oldOp.collection)} SET _field_versions = ? WHERE id = ?`,
3339
+ [serializeFieldVersions(fieldVersions), oldOp.recordId]
3340
+ );
3230
3341
  }
3231
3342
  if (oldOp.type === "insert") {
3232
3343
  await tx.execute(
3233
- `UPDATE ${oldOp.collection} SET _created_at = ? WHERE id = ? AND _created_at = ?`,
3344
+ `UPDATE ${(0, import_core18.quoteIdent)(oldOp.collection)} SET _created_at = ? WHERE id = ? AND _created_at = ?`,
3234
3345
  [newOp.timestamp.wallTime, oldOp.recordId, oldOp.timestamp.wallTime]
3235
3346
  );
3236
3347
  }
@@ -3246,7 +3357,7 @@ async function rebaseUnsyncedOperationsInLog(adapter, schema, unsyncedOpIds, cor
3246
3357
  }
3247
3358
 
3248
3359
  // src/transaction/transaction-context.ts
3249
- var import_core16 = require("@korajs/core");
3360
+ var import_core19 = require("@korajs/core");
3250
3361
  init_op_data_encoding();
3251
3362
  init_serializer();
3252
3363
  var TransactionContext = class {
@@ -3258,7 +3369,7 @@ var TransactionContext = class {
3258
3369
  config;
3259
3370
  constructor(config) {
3260
3371
  this.config = config;
3261
- this.transactionId = (0, import_core16.generateUUIDv7)();
3372
+ this.transactionId = (0, import_core19.generateUUIDv7)();
3262
3373
  }
3263
3374
  /**
3264
3375
  * Set a human-readable mutation name for this transaction.
@@ -3373,8 +3484,8 @@ var TransactionContext = class {
3373
3484
  }
3374
3485
  async insert(collectionName, definition, data) {
3375
3486
  this.ensureActive();
3376
- const validated = (0, import_core16.validateRecord)(collectionName, definition, data, "insert");
3377
- const recordId = (0, import_core16.generateUUIDv7)();
3487
+ const validated = (0, import_core19.validateRecord)(collectionName, definition, data, "insert");
3488
+ const recordId = (0, import_core19.generateUUIDv7)();
3378
3489
  const now = Date.now();
3379
3490
  for (const [fieldName, descriptor] of Object.entries(definition.fields)) {
3380
3491
  if (descriptor.auto && descriptor.kind === "timestamp") {
@@ -3383,7 +3494,7 @@ var TransactionContext = class {
3383
3494
  }
3384
3495
  const sequenceNumber = await this.config.sequenceAllocator.allocate();
3385
3496
  const causalDeps = this.config.causalTracker?.nextCausalDeps(collectionName, true) ?? [];
3386
- const operation = await (0, import_core16.createOperation)(
3497
+ const operation = await (0, import_core19.createOperation)(
3387
3498
  {
3388
3499
  nodeId: this.config.nodeId,
3389
3500
  type: "insert",
@@ -3438,7 +3549,7 @@ var TransactionContext = class {
3438
3549
  if (!currentRecord) {
3439
3550
  throw new RecordNotFoundError(collectionName, id);
3440
3551
  }
3441
- const validated = (0, import_core16.validateRecord)(collectionName, definition, data, "update");
3552
+ const validated = (0, import_core19.validateRecord)(collectionName, definition, data, "update");
3442
3553
  const now = Date.now();
3443
3554
  const previousData = {};
3444
3555
  const resolvedData = {};
@@ -3446,9 +3557,9 @@ var TransactionContext = class {
3446
3557
  for (const key of Object.keys(validated)) {
3447
3558
  const value = validated[key];
3448
3559
  previousData[key] = currentRecord[key];
3449
- if ((0, import_core16.isAtomicOp)(value)) {
3450
- resolvedData[key] = (0, import_core16.resolveAtomicOp)(currentRecord[key], value);
3451
- atomicOps[key] = (0, import_core16.toAtomicOp)(value);
3560
+ if ((0, import_core19.isAtomicOp)(value)) {
3561
+ resolvedData[key] = (0, import_core19.resolveAtomicOp)(currentRecord[key], value);
3562
+ atomicOps[key] = (0, import_core19.toAtomicOp)(value);
3452
3563
  } else {
3453
3564
  resolvedData[key] = value;
3454
3565
  }
@@ -3456,7 +3567,7 @@ var TransactionContext = class {
3456
3567
  const hasAtomicOps = Object.keys(atomicOps).length > 0;
3457
3568
  const sequenceNumber = await this.config.sequenceAllocator.allocate();
3458
3569
  const causalDeps = this.config.causalTracker?.nextCausalDeps(collectionName, true) ?? [];
3459
- const operation = await (0, import_core16.createOperation)(
3570
+ const operation = await (0, import_core19.createOperation)(
3460
3571
  {
3461
3572
  nodeId: this.config.nodeId,
3462
3573
  type: "update",
@@ -3511,7 +3622,7 @@ var TransactionContext = class {
3511
3622
  const now = Date.now();
3512
3623
  const sequenceNumber = await this.config.sequenceAllocator.allocate();
3513
3624
  const causalDeps = this.config.causalTracker?.nextCausalDeps(collectionName, true) ?? [];
3514
- const operation = await (0, import_core16.createOperation)(
3625
+ const operation = await (0, import_core19.createOperation)(
3515
3626
  {
3516
3627
  nodeId: this.config.nodeId,
3517
3628
  type: "delete",
@@ -3561,7 +3672,7 @@ var TransactionContext = class {
3561
3672
  }
3562
3673
  }
3563
3674
  const rows = await this.config.adapter.query(
3564
- `SELECT * FROM ${collectionName} WHERE id = ? AND _deleted = 0`,
3675
+ `SELECT * FROM ${(0, import_core19.quoteIdent)(collectionName)} WHERE id = ? AND _deleted = 0`,
3565
3676
  [id]
3566
3677
  );
3567
3678
  let record = rows[0] ? deserializeRecord(rows[0], definition.fields) : null;
@@ -3616,7 +3727,7 @@ var TransactionSequenceAllocator = class {
3616
3727
  };
3617
3728
 
3618
3729
  // src/store/tab-node-id.ts
3619
- var import_core17 = require("@korajs/core");
3730
+ var import_core20 = require("@korajs/core");
3620
3731
  function perTabStorageKey(dbName) {
3621
3732
  return `kora-tab-node-${dbName}`;
3622
3733
  }
@@ -3637,7 +3748,7 @@ function resolvePerTabNodeId(dbName) {
3637
3748
  if (existing) {
3638
3749
  return existing;
3639
3750
  }
3640
- const nodeId = (0, import_core17.generateUUIDv7)();
3751
+ const nodeId = (0, import_core20.generateUUIDv7)();
3641
3752
  savePerTabNodeId(dbName, nodeId);
3642
3753
  return nodeId;
3643
3754
  }
@@ -3647,7 +3758,7 @@ var Store = class {
3647
3758
  opened = false;
3648
3759
  nodeId = "";
3649
3760
  sequenceNumber = 0;
3650
- versionVector = (0, import_core19.createVersionVector)();
3761
+ versionVector = (0, import_core23.createVersionVector)();
3651
3762
  clock = null;
3652
3763
  collections = /* @__PURE__ */ new Map();
3653
3764
  subscriptionManager;
@@ -3661,6 +3772,7 @@ var Store = class {
3661
3772
  localMutationHandler;
3662
3773
  relationEnforcer = null;
3663
3774
  causalTracker = null;
3775
+ secretKeyProvider;
3664
3776
  constructor(config) {
3665
3777
  this.schema = config.schema;
3666
3778
  this.adapter = config.adapter;
@@ -3669,6 +3781,7 @@ var Store = class {
3669
3781
  this.isolation = config.isolation ?? "shared";
3670
3782
  this.emitter = config.emitter ?? null;
3671
3783
  this.localMutationHandler = config.localMutationHandler ?? null;
3784
+ this.secretKeyProvider = config.secretKeyProvider;
3672
3785
  this.subscriptionManager = new SubscriptionManager({
3673
3786
  onQuerySubscribed: config.onQuerySubscribed
3674
3787
  });
@@ -3681,8 +3794,8 @@ var Store = class {
3681
3794
  await this.adapter.open(this.schema);
3682
3795
  await this.runMigrationsIfNeeded();
3683
3796
  this.nodeId = await this.loadOrGenerateNodeId();
3684
- this.clock = new import_core19.HybridLogicalClock(this.nodeId);
3685
- this.causalTracker = new import_core19.CausalTracker();
3797
+ this.clock = new import_core23.HybridLogicalClock(this.nodeId);
3798
+ this.causalTracker = new import_core23.CausalTracker();
3686
3799
  this.sequenceManager = new SequenceManager(this.adapter, this.nodeId);
3687
3800
  this.sequenceNumber = await this.loadSequenceNumber();
3688
3801
  this.versionVector = await this.loadVersionVector();
@@ -3711,7 +3824,8 @@ var Store = class {
3711
3824
  },
3712
3825
  this.relationEnforcer,
3713
3826
  this.localMutationHandler,
3714
- this.causalTracker
3827
+ this.causalTracker,
3828
+ this.secretKeyProvider
3715
3829
  );
3716
3830
  this.collections.set(name, col);
3717
3831
  }
@@ -3793,7 +3907,7 @@ var Store = class {
3793
3907
  return "skipped";
3794
3908
  }
3795
3909
  const existing = await this.adapter.query(
3796
- `SELECT id FROM _kora_ops_${collection} WHERE id = ?`,
3910
+ `SELECT id FROM ${(0, import_core23.quoteIdent)(`_kora_ops_${collection}`)} WHERE id = ?`,
3797
3911
  [op.id]
3798
3912
  );
3799
3913
  if (existing.length > 0) {
@@ -3818,7 +3932,8 @@ var Store = class {
3818
3932
  throw new OptimisticLockError(collection, op.recordId);
3819
3933
  }
3820
3934
  };
3821
- if (op.type === "insert" && materializeSource) {
3935
+ if (options?.logOnly) {
3936
+ } else if (op.type === "insert" && materializeSource) {
3822
3937
  const serializedData = serializeRecord(materializeSource, definition.fields);
3823
3938
  const existing2 = await tx.query(
3824
3939
  `SELECT _updated_at, _version, _field_versions, _deleted FROM ${collection} WHERE id = ?`,
@@ -3840,7 +3955,7 @@ var Store = class {
3840
3955
  };
3841
3956
  const insertQuery = buildInsertQuery(collection, record);
3842
3957
  await tx.execute(insertQuery.sql, insertQuery.params);
3843
- await this.foldOrphanedOperations(tx, collection, definition, op.recordId);
3958
+ await this.foldOrphanedOperations(tx, collection, definition, op.recordId, op);
3844
3959
  } else if (row._deleted === 1) {
3845
3960
  if (isIncomingNewerThanRow(materializeTimestamp, row)) {
3846
3961
  const fieldChanges = {
@@ -3898,7 +4013,7 @@ var Store = class {
3898
4013
  const serializedChanges = serializeRecord(materializeSource, definition.fields);
3899
4014
  const changedFields = Object.keys(serializedChanges);
3900
4015
  const currentRows = await tx.query(
3901
- `SELECT _version, _field_versions FROM ${collection} WHERE id = ?`,
4016
+ `SELECT _version, _field_versions FROM ${(0, import_core23.quoteIdent)(collection)} WHERE id = ?`,
3902
4017
  [op.recordId]
3903
4018
  );
3904
4019
  const currentRow = currentRows[0];
@@ -3992,22 +4107,22 @@ var Store = class {
3992
4107
  * necessarily an orphan (rows are soft-deleted, never removed, so a row
3993
4108
  * that is absent now was never materialized).
3994
4109
  */
3995
- async foldOrphanedOperations(tx, collection, definition, recordId) {
4110
+ async foldOrphanedOperations(tx, collection, definition, recordId, insertOp) {
3996
4111
  const orphanRows = await tx.query(
3997
- `SELECT * FROM _kora_ops_${collection} WHERE record_id = ? AND type IN ('update', 'delete')`,
4112
+ `SELECT * FROM ${(0, import_core23.quoteIdent)(`_kora_ops_${collection}`)} WHERE record_id = ? AND type IN ('update', 'delete')`,
3998
4113
  [recordId]
3999
4114
  );
4000
4115
  if (orphanRows.length === 0) {
4001
4116
  return;
4002
4117
  }
4003
- const orphans = orphanRows.map((r) => deserializeOperationWithCollection(r, collection)).sort((a, b) => import_core19.HybridLogicalClock.compare(a.timestamp, b.timestamp));
4118
+ const orphans = orphanRows.map((r) => deserializeOperationWithCollection(r, collection)).sort((a, b) => import_core23.HybridLogicalClock.compare(a.timestamp, b.timestamp));
4004
4119
  for (const orphan of orphans) {
4005
4120
  const orphanVersion = serializeRowVersion(orphan.timestamp);
4006
4121
  const orphanWall = orphan.timestamp.wallTime;
4007
4122
  if (orphan.type === "update" && orphan.data) {
4008
4123
  const orphanChanges = serializeRecord(orphan.data, definition.fields);
4009
4124
  const rows = await tx.query(
4010
- `SELECT _version, _field_versions FROM ${collection} WHERE id = ?`,
4125
+ `SELECT _version, _field_versions FROM ${(0, import_core23.quoteIdent)(collection)} WHERE id = ?`,
4011
4126
  [recordId]
4012
4127
  );
4013
4128
  const current = rows[0];
@@ -4041,6 +4156,37 @@ var Store = class {
4041
4156
  await tx.execute(query.sql, query.params);
4042
4157
  }
4043
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
+ }
4044
4190
  }
4045
4191
  /**
4046
4192
  * Get operations from a node within a sequence number range.
@@ -4057,7 +4203,7 @@ var Store = class {
4057
4203
  const allOps = [];
4058
4204
  for (const collectionName of Object.keys(this.schema.collections)) {
4059
4205
  const rows = await this.adapter.query(
4060
- `SELECT * FROM _kora_ops_${collectionName} WHERE node_id = ? AND sequence_number >= ? AND sequence_number <= ? ORDER BY sequence_number ASC`,
4206
+ `SELECT * FROM ${(0, import_core23.quoteIdent)(`_kora_ops_${collectionName}`)} WHERE node_id = ? AND sequence_number >= ? AND sequence_number <= ? ORDER BY sequence_number ASC`,
4061
4207
  [nodeId, fromSeq, toSeq]
4062
4208
  );
4063
4209
  for (const row of rows) {
@@ -4076,7 +4222,7 @@ var Store = class {
4076
4222
  const allOps = [];
4077
4223
  for (const collectionName of Object.keys(this.schema.collections)) {
4078
4224
  const rows = await this.adapter.query(
4079
- `SELECT * FROM _kora_ops_${collectionName} ORDER BY sequence_number ASC`
4225
+ `SELECT * FROM ${(0, import_core23.quoteIdent)(`_kora_ops_${collectionName}`)} ORDER BY sequence_number ASC`
4080
4226
  );
4081
4227
  for (const row of rows) {
4082
4228
  allOps.push(deserializeOperationWithCollection(row, collectionName));
@@ -4145,6 +4291,22 @@ var Store = class {
4145
4291
  col.setMutationHandler(handler);
4146
4292
  }
4147
4293
  }
4294
+ /**
4295
+ * Notify this store that another same-origin runtime committed an operation to
4296
+ * the shared local database. The operation has already been persisted and
4297
+ * materialized by the database owner; this method only advances in-memory causal
4298
+ * watermarks and invalidates local reactive queries so already-open tabs stay
4299
+ * live without reapplying or rewriting the operation.
4300
+ */
4301
+ notifyExternalOperation(operation) {
4302
+ this.ensureOpen();
4303
+ const definition = this.schema.collections[operation.collection];
4304
+ if (!definition) {
4305
+ return;
4306
+ }
4307
+ this.recordOperationSequence(operation);
4308
+ this.subscriptionManager.notify(operation.collection, operation);
4309
+ }
4148
4310
  /**
4149
4311
  * Build mutation context for a collection (used by ApplyPipeline side effects).
4150
4312
  */
@@ -4172,7 +4334,8 @@ var Store = class {
4172
4334
  relationEnforcer: this.relationEnforcer,
4173
4335
  causalTracker: this.causalTracker,
4174
4336
  inTransaction: options?.inTransaction ?? false,
4175
- extraCausalDeps: options?.extraCausalDeps
4337
+ extraCausalDeps: options?.extraCausalDeps,
4338
+ secretKeyProvider: this.secretKeyProvider
4176
4339
  };
4177
4340
  }
4178
4341
  /**
@@ -4185,7 +4348,7 @@ var Store = class {
4185
4348
  return null;
4186
4349
  }
4187
4350
  const rows = await this.adapter.query(
4188
- `SELECT * FROM ${collection} WHERE id = ?`,
4351
+ `SELECT * FROM ${(0, import_core23.quoteIdent)(collection)} WHERE id = ?`,
4189
4352
  [recordId]
4190
4353
  );
4191
4354
  const row = rows[0];
@@ -4210,7 +4373,7 @@ var Store = class {
4210
4373
  return null;
4211
4374
  }
4212
4375
  const rows = await this.adapter.query(
4213
- `SELECT _version, _field_versions FROM ${collection} WHERE id = ?`,
4376
+ `SELECT _version, _field_versions FROM ${(0, import_core23.quoteIdent)(collection)} WHERE id = ?`,
4214
4377
  [recordId]
4215
4378
  );
4216
4379
  const row = rows[0];
@@ -4253,6 +4416,35 @@ var Store = class {
4253
4416
  this.ensureOpen();
4254
4417
  await saveDeltaCursor(this.adapter, cursor);
4255
4418
  }
4419
+ /**
4420
+ * Load the persisted delivery watermark for a view signature (0 when none recorded).
4421
+ * Defaults to the unfiltered view so existing single-watermark reads keep working.
4422
+ */
4423
+ async loadDeliveryWatermark(signature = "") {
4424
+ this.ensureOpen();
4425
+ return loadDeliveryWatermark(this.adapter, signature);
4426
+ }
4427
+ /**
4428
+ * Persist the delivery watermark for a view signature so it survives a client restart.
4429
+ */
4430
+ async saveDeliveryWatermark(signature, watermark) {
4431
+ this.ensureOpen();
4432
+ await saveDeliveryWatermark(this.adapter, signature, watermark);
4433
+ }
4434
+ /**
4435
+ * Load every persisted view watermark, keyed by signature.
4436
+ */
4437
+ async loadAllDeliveryWatermarks() {
4438
+ this.ensureOpen();
4439
+ return loadAllDeliveryWatermarks(this.adapter);
4440
+ }
4441
+ /**
4442
+ * Delete a persisted view watermark (safe: the view back-fills from 0 when next visited).
4443
+ */
4444
+ async deleteDeliveryWatermark(signature) {
4445
+ this.ensureOpen();
4446
+ await deleteDeliveryWatermark(this.adapter, signature);
4447
+ }
4256
4448
  /**
4257
4449
  * Local operations not yet reflected on the server version vector.
4258
4450
  */
@@ -4302,7 +4494,7 @@ var Store = class {
4302
4494
  async compact(strategy) {
4303
4495
  this.ensureOpen();
4304
4496
  if (strategy.mode === "never") {
4305
- return compactOperationLog(this.adapter, this.schema, strategy, (0, import_core19.createVersionVector)());
4497
+ return compactOperationLog(this.adapter, this.schema, strategy, (0, import_core23.createVersionVector)());
4306
4498
  }
4307
4499
  const serverVector = strategy.serverVector ?? await loadLastAckedServerVector(this.adapter);
4308
4500
  return compactOperationLog(this.adapter, this.schema, strategy, serverVector);
@@ -4316,7 +4508,7 @@ var Store = class {
4316
4508
  async getLatestLocalOperationForRecord(collection, recordId) {
4317
4509
  this.ensureOpen();
4318
4510
  const rows = await this.adapter.query(
4319
- `SELECT * FROM _kora_ops_${collection} WHERE node_id = ? AND record_id = ? ORDER BY sequence_number DESC LIMIT 1`,
4511
+ `SELECT * FROM ${(0, import_core23.quoteIdent)(`_kora_ops_${collection}`)} WHERE node_id = ? AND record_id = ? ORDER BY sequence_number DESC LIMIT 1`,
4320
4512
  [this.nodeId, recordId]
4321
4513
  );
4322
4514
  const row = rows[0];
@@ -4331,18 +4523,33 @@ var Store = class {
4331
4523
  async getLatestOperationForRecord(collection, recordId) {
4332
4524
  this.ensureOpen();
4333
4525
  const rows = await this.adapter.query(
4334
- `SELECT * FROM _kora_ops_${collection} WHERE record_id = ?`,
4526
+ `SELECT * FROM ${(0, import_core23.quoteIdent)(`_kora_ops_${collection}`)} WHERE record_id = ?`,
4335
4527
  [recordId]
4336
4528
  );
4337
4529
  let latest = null;
4338
4530
  for (const row of rows) {
4339
4531
  const op = deserializeOperationWithCollection(row, collection);
4340
- if (!latest || import_core19.HybridLogicalClock.compare(op.timestamp, latest.timestamp) > 0) {
4532
+ if (!latest || import_core23.HybridLogicalClock.compare(op.timestamp, latest.timestamp) > 0) {
4341
4533
  latest = op;
4342
4534
  }
4343
4535
  }
4344
4536
  return latest;
4345
4537
  }
4538
+ /**
4539
+ * All operations for a record, sorted in HLC total order (wallTime, logical,
4540
+ * nodeId). Used by the apply pipeline to materialize atomic-op fields by folding
4541
+ * the record's log, matching the server's materialization exactly.
4542
+ */
4543
+ async getOperationsForRecord(collection, recordId) {
4544
+ this.ensureOpen();
4545
+ const rows = await this.adapter.query(
4546
+ `SELECT * FROM ${(0, import_core23.quoteIdent)(`_kora_ops_${collection}`)} WHERE record_id = ?`,
4547
+ [recordId]
4548
+ );
4549
+ const ops = rows.map((row) => deserializeOperationWithCollection(row, collection));
4550
+ ops.sort((a, b) => import_core23.HybridLogicalClock.compare(a.timestamp, b.timestamp));
4551
+ return ops;
4552
+ }
4346
4553
  /** Expose the subscription manager for direct access (e.g., by QueryBuilder) */
4347
4554
  getSubscriptionManager() {
4348
4555
  return this.subscriptionManager;
@@ -4480,7 +4687,7 @@ var Store = class {
4480
4687
  for (let v = storedVersion + 1; v <= targetVersion; v++) {
4481
4688
  const migration = migrations[v];
4482
4689
  if (!migration) continue;
4483
- const sqlStatements = (0, import_core19.migrationStepsToSQL)(migration.steps);
4690
+ const sqlStatements = (0, import_core23.migrationStepsToSQL)(migration.steps);
4484
4691
  for (const sql of sqlStatements) {
4485
4692
  try {
4486
4693
  await this.adapter.execute(sql);
@@ -4518,14 +4725,14 @@ var Store = class {
4518
4725
  */
4519
4726
  async runBackfill(collection, transform) {
4520
4727
  const rows = await this.adapter.query(
4521
- `SELECT * FROM ${collection} WHERE _deleted = 0`
4728
+ `SELECT * FROM ${(0, import_core23.quoteIdent)(collection)} WHERE _deleted = 0`
4522
4729
  );
4523
4730
  await this.adapter.transaction(async (tx) => {
4524
4731
  for (const row of rows) {
4525
4732
  const updates = transform(row);
4526
4733
  const fields = Object.keys(updates);
4527
4734
  if (fields.length === 0) continue;
4528
- const setClauses = fields.map((f) => `${f} = ?`).join(", ");
4735
+ const setClauses = fields.map((f) => `${(0, import_core23.quoteIdent)(f)} = ?`).join(", ");
4529
4736
  const values = fields.map((f) => {
4530
4737
  const val = updates[f];
4531
4738
  if (typeof val === "boolean") return val ? 1 : 0;
@@ -4535,7 +4742,7 @@ var Store = class {
4535
4742
  return val;
4536
4743
  });
4537
4744
  values.push(row.id);
4538
- await tx.execute(`UPDATE ${collection} SET ${setClauses} WHERE id = ?`, values);
4745
+ await tx.execute(`UPDATE ${(0, import_core23.quoteIdent)(collection)} SET ${setClauses} WHERE id = ?`, values);
4539
4746
  }
4540
4747
  });
4541
4748
  }
@@ -4558,7 +4765,7 @@ var Store = class {
4558
4765
  if (rows[0]) {
4559
4766
  return rows[0].value;
4560
4767
  }
4561
- const newNodeId = (0, import_core19.generateUUIDv7)();
4768
+ const newNodeId = (0, import_core23.generateUUIDv7)();
4562
4769
  await this.adapter.execute("INSERT INTO _kora_meta (key, value) VALUES ('node_id', ?)", [
4563
4770
  newNodeId
4564
4771
  ]);
@@ -4575,7 +4782,7 @@ var Store = class {
4575
4782
  const rows = await this.adapter.query(
4576
4783
  "SELECT node_id, sequence_number FROM _kora_version_vector"
4577
4784
  );
4578
- const vector = (0, import_core19.createVersionVector)();
4785
+ const vector = (0, import_core23.createVersionVector)();
4579
4786
  for (const row of rows) {
4580
4787
  vector.set(row.node_id, row.sequence_number);
4581
4788
  }
@@ -4595,6 +4802,7 @@ var QueryStore = class {
4595
4802
  listeners = /* @__PURE__ */ new Set();
4596
4803
  unsubscribeQuery = null;
4597
4804
  active = false;
4805
+ hasEmittedSnapshot = false;
4598
4806
  queryBuilder;
4599
4807
  constructor(queryBuilder) {
4600
4808
  this.queryBuilder = queryBuilder;
@@ -4620,16 +4828,25 @@ var QueryStore = class {
4620
4828
  getSnapshot = () => {
4621
4829
  return this.snapshot;
4622
4830
  };
4831
+ /**
4832
+ * Whether the underlying query subscription has delivered at least one
4833
+ * authoritative result for the current subscription lifecycle.
4834
+ */
4835
+ hasSnapshot = () => {
4836
+ return this.hasEmittedSnapshot;
4837
+ };
4623
4838
  /** Tear down listeners and the underlying query subscription. */
4624
4839
  destroy() {
4625
4840
  this.stopSubscription();
4626
4841
  this.listeners.clear();
4627
4842
  this.snapshot = EMPTY_ARRAY;
4843
+ this.hasEmittedSnapshot = false;
4628
4844
  }
4629
4845
  startSubscription() {
4630
4846
  this.active = true;
4631
4847
  this.unsubscribeQuery = this.queryBuilder.subscribe((results) => {
4632
4848
  if (!this.active) return;
4849
+ this.hasEmittedSnapshot = true;
4633
4850
  this.snapshot = Object.freeze([...results]);
4634
4851
  this.notifyListeners();
4635
4852
  });
@@ -4649,12 +4866,12 @@ var QueryStore = class {
4649
4866
  };
4650
4867
 
4651
4868
  // src/reactivity/assert-query-ready.ts
4652
- var import_core20 = require("@korajs/core");
4869
+ var import_core24 = require("@korajs/core");
4653
4870
  var LEGACY_PENDING_COLLECTION = "__pending__";
4654
4871
  function assertQueryReady(query) {
4655
4872
  const descriptor = query.getDescriptor();
4656
4873
  if (descriptor.collection === LEGACY_PENDING_COLLECTION) {
4657
- throw new import_core20.AppNotReadyError(
4874
+ throw new import_core24.AppNotReadyError(
4658
4875
  "Cannot use useQuery() before app.ready. Await app.ready or wrap your UI in <KoraProvider app={app}>."
4659
4876
  );
4660
4877
  }
@@ -5075,13 +5292,570 @@ init_backup();
5075
5292
  init_audit_trace_store();
5076
5293
  init_audit_json();
5077
5294
  init_export_audit();
5295
+
5296
+ // src/blob/content-addressed-blob-store.ts
5297
+ var import_core25 = require("@korajs/core");
5298
+ var BlobIntegrityError = class extends Error {
5299
+ constructor(expectedHash, actualHash) {
5300
+ super(`Blob integrity check failed: content under hash ${expectedHash} hashes to ${actualHash}`);
5301
+ this.expectedHash = expectedHash;
5302
+ this.actualHash = actualHash;
5303
+ this.name = "BlobIntegrityError";
5304
+ }
5305
+ expectedHash;
5306
+ actualHash;
5307
+ };
5308
+ var MemoryBlobStore = class {
5309
+ blobs = /* @__PURE__ */ new Map();
5310
+ async put(bytes, metadata = {}) {
5311
+ const ref = await (0, import_core25.createBlobRef)(bytes, metadata);
5312
+ if (!this.blobs.has(ref.hash)) {
5313
+ const stored = new Uint8Array(bytes.byteLength);
5314
+ stored.set(bytes);
5315
+ this.blobs.set(ref.hash, stored);
5316
+ }
5317
+ return ref;
5318
+ }
5319
+ async get(hash) {
5320
+ const stored = this.blobs.get(hash);
5321
+ if (stored === void 0) {
5322
+ return null;
5323
+ }
5324
+ const actualHash = await (0, import_core25.hashBlob)(stored);
5325
+ if (actualHash !== hash) {
5326
+ throw new BlobIntegrityError(hash, actualHash);
5327
+ }
5328
+ const out = new Uint8Array(stored.byteLength);
5329
+ out.set(stored);
5330
+ return out;
5331
+ }
5332
+ async has(hash) {
5333
+ return this.blobs.has(hash);
5334
+ }
5335
+ async delete(hash) {
5336
+ return this.blobs.delete(hash);
5337
+ }
5338
+ async size() {
5339
+ return this.blobs.size;
5340
+ }
5341
+ async list() {
5342
+ return [...this.blobs.keys()];
5343
+ }
5344
+ };
5345
+
5346
+ // src/blob/opfs-blob-store.ts
5347
+ var import_core26 = require("@korajs/core");
5348
+ var OpfsBlobStore = class {
5349
+ constructor(dir) {
5350
+ this.dir = dir;
5351
+ }
5352
+ dir;
5353
+ shardFor(hash) {
5354
+ return hash.slice(0, 2);
5355
+ }
5356
+ async put(bytes, metadata = {}) {
5357
+ const ref = await (0, import_core26.createBlobRef)(bytes, metadata);
5358
+ const shard = this.shardFor(ref.hash);
5359
+ if (await this.dir.has(shard, ref.hash)) {
5360
+ return ref;
5361
+ }
5362
+ const out = new Uint8Array(bytes.byteLength);
5363
+ out.set(bytes);
5364
+ await this.dir.write(shard, ref.hash, out);
5365
+ return ref;
5366
+ }
5367
+ async get(hash) {
5368
+ const bytes = await this.dir.read(this.shardFor(hash), hash);
5369
+ if (bytes === null) {
5370
+ return null;
5371
+ }
5372
+ const actual = await (0, import_core26.hashBlob)(bytes);
5373
+ if (actual !== hash) {
5374
+ throw new BlobIntegrityError(hash, actual);
5375
+ }
5376
+ return bytes;
5377
+ }
5378
+ async has(hash) {
5379
+ return this.dir.has(this.shardFor(hash), hash);
5380
+ }
5381
+ async delete(hash) {
5382
+ return this.dir.remove(this.shardFor(hash), hash);
5383
+ }
5384
+ async size() {
5385
+ return this.dir.count();
5386
+ }
5387
+ async list() {
5388
+ return this.dir.keys();
5389
+ }
5390
+ };
5391
+ function getStorageManager() {
5392
+ const nav = globalThis.navigator;
5393
+ if (!nav?.storage || typeof nav.storage.getDirectory !== "function") {
5394
+ throw new Error(
5395
+ "OPFS is unavailable in this environment. The OPFS blob store requires a browser with Origin Private File System support; use MemoryBlobStore or a server-side store elsewhere."
5396
+ );
5397
+ }
5398
+ return nav.storage;
5399
+ }
5400
+ function isNotFoundError(error) {
5401
+ return typeof error === "object" && error !== null && error.name === "NotFoundError";
5402
+ }
5403
+ async function createOpfsBlobDirectory(rootDirName = "kora-blobs") {
5404
+ const storage = getStorageManager();
5405
+ if (typeof storage.persist === "function") {
5406
+ try {
5407
+ await storage.persist();
5408
+ } catch {
5409
+ }
5410
+ }
5411
+ const opfsRoot = await storage.getDirectory();
5412
+ const root = await opfsRoot.getDirectoryHandle(rootDirName, { create: true });
5413
+ async function shardHandle(shard, create) {
5414
+ try {
5415
+ return await root.getDirectoryHandle(shard, { create });
5416
+ } catch (error) {
5417
+ if (isNotFoundError(error)) {
5418
+ return null;
5419
+ }
5420
+ throw error;
5421
+ }
5422
+ }
5423
+ return {
5424
+ async read(shard, name) {
5425
+ const dir = await shardHandle(shard, false);
5426
+ if (!dir) {
5427
+ return null;
5428
+ }
5429
+ let handle;
5430
+ try {
5431
+ handle = await dir.getFileHandle(name, { create: false });
5432
+ } catch (error) {
5433
+ if (isNotFoundError(error)) {
5434
+ return null;
5435
+ }
5436
+ throw error;
5437
+ }
5438
+ const file = await handle.getFile();
5439
+ return new Uint8Array(await file.arrayBuffer());
5440
+ },
5441
+ async write(shard, name, bytes) {
5442
+ const dir = await root.getDirectoryHandle(shard, { create: true });
5443
+ const handle = await dir.getFileHandle(name, { create: true });
5444
+ const writable = await handle.createWritable({ keepExistingData: false });
5445
+ try {
5446
+ await writable.write(bytes);
5447
+ } finally {
5448
+ await writable.close();
5449
+ }
5450
+ },
5451
+ async remove(shard, name) {
5452
+ const dir = await shardHandle(shard, false);
5453
+ if (!dir) {
5454
+ return false;
5455
+ }
5456
+ try {
5457
+ await dir.removeEntry(name);
5458
+ return true;
5459
+ } catch (error) {
5460
+ if (isNotFoundError(error)) {
5461
+ return false;
5462
+ }
5463
+ throw error;
5464
+ }
5465
+ },
5466
+ async has(shard, name) {
5467
+ const dir = await shardHandle(shard, false);
5468
+ if (!dir) {
5469
+ return false;
5470
+ }
5471
+ try {
5472
+ await dir.getFileHandle(name, { create: false });
5473
+ return true;
5474
+ } catch (error) {
5475
+ if (isNotFoundError(error)) {
5476
+ return false;
5477
+ }
5478
+ throw error;
5479
+ }
5480
+ },
5481
+ async count() {
5482
+ let total = 0;
5483
+ for await (const shard of root.keys()) {
5484
+ const dir = await shardHandle(shard, false);
5485
+ if (!dir) {
5486
+ continue;
5487
+ }
5488
+ for await (const name of dir.keys()) {
5489
+ if (!name.endsWith(".tmp")) {
5490
+ total++;
5491
+ }
5492
+ }
5493
+ }
5494
+ return total;
5495
+ },
5496
+ async keys() {
5497
+ const names = [];
5498
+ for await (const shard of root.keys()) {
5499
+ const dir = await shardHandle(shard, false);
5500
+ if (!dir) {
5501
+ continue;
5502
+ }
5503
+ for await (const name of dir.keys()) {
5504
+ if (!name.endsWith(".tmp")) {
5505
+ names.push(name);
5506
+ }
5507
+ }
5508
+ }
5509
+ return names;
5510
+ }
5511
+ };
5512
+ }
5513
+ async function createOpfsBlobStore(rootDirName = "kora-blobs") {
5514
+ return new OpfsBlobStore(await createOpfsBlobDirectory(rootDirName));
5515
+ }
5516
+
5517
+ // src/blob/blob-chunking.ts
5518
+ var import_core27 = require("@korajs/core");
5519
+ var DEFAULT_CHUNK_SIZE = 256 * 1024;
5520
+ async function chunkBlob(bytes, chunkSize = DEFAULT_CHUNK_SIZE, metadata = {}) {
5521
+ if (chunkSize <= 0) {
5522
+ throw new Error(`chunkBlob requires a positive chunkSize, got ${chunkSize}`);
5523
+ }
5524
+ const chunkHashes = [];
5525
+ const chunks = /* @__PURE__ */ new Map();
5526
+ for (let offset = 0; offset < bytes.byteLength; offset += chunkSize) {
5527
+ const end = Math.min(offset + chunkSize, bytes.byteLength);
5528
+ const chunk = new Uint8Array(end - offset);
5529
+ chunk.set(bytes.subarray(offset, end));
5530
+ const hash = await (0, import_core27.hashBlob)(chunk);
5531
+ chunkHashes.push(hash);
5532
+ if (!chunks.has(hash)) {
5533
+ chunks.set(hash, chunk);
5534
+ }
5535
+ }
5536
+ const blobHash = await (0, import_core27.hashBlob)(bytes);
5537
+ const manifest = {
5538
+ blobHash,
5539
+ size: bytes.byteLength,
5540
+ chunkSize,
5541
+ chunkHashes
5542
+ };
5543
+ if (metadata.mimeType !== void 0) {
5544
+ manifest.mimeType = metadata.mimeType;
5545
+ }
5546
+ if (metadata.filename !== void 0) {
5547
+ manifest.filename = metadata.filename;
5548
+ }
5549
+ return { manifest, chunks };
5550
+ }
5551
+ async function reassembleBlob(manifest, chunkStore) {
5552
+ const parts = [];
5553
+ let total = 0;
5554
+ for (const hash of manifest.chunkHashes) {
5555
+ const chunk = await chunkStore.get(hash);
5556
+ if (chunk === null) {
5557
+ throw new Error(`Cannot reassemble blob ${manifest.blobHash}: missing chunk ${hash}`);
5558
+ }
5559
+ parts.push(chunk);
5560
+ total += chunk.byteLength;
5561
+ }
5562
+ const out = new Uint8Array(total);
5563
+ let offset = 0;
5564
+ for (const part of parts) {
5565
+ out.set(part, offset);
5566
+ offset += part.byteLength;
5567
+ }
5568
+ const actual = await (0, import_core27.hashBlob)(out);
5569
+ if (actual !== manifest.blobHash) {
5570
+ throw new BlobIntegrityError(manifest.blobHash, actual);
5571
+ }
5572
+ return out;
5573
+ }
5574
+
5575
+ // src/blob/blob-transfer.ts
5576
+ var import_core28 = require("@korajs/core");
5577
+ async function receiveBlob(manifest, provider, stores) {
5578
+ let chunksFetched = 0;
5579
+ let chunksSkipped = 0;
5580
+ const seen = /* @__PURE__ */ new Set();
5581
+ for (const hash of manifest.chunkHashes) {
5582
+ if (seen.has(hash)) {
5583
+ continue;
5584
+ }
5585
+ seen.add(hash);
5586
+ if (await stores.chunkStore.has(hash)) {
5587
+ chunksSkipped++;
5588
+ continue;
5589
+ }
5590
+ const bytes = await provider.getChunk(hash);
5591
+ if (bytes === null) {
5592
+ throw new Error(`Blob transfer failed: provider could not supply chunk ${hash}`);
5593
+ }
5594
+ const actual = await (0, import_core28.hashBlob)(bytes);
5595
+ if (actual !== hash) {
5596
+ throw new BlobIntegrityError(hash, actual);
5597
+ }
5598
+ await stores.chunkStore.put(bytes);
5599
+ chunksFetched++;
5600
+ }
5601
+ const full = await reassembleBlob(manifest, stores.chunkStore);
5602
+ const ref = await stores.blobStore.put(full, {
5603
+ mimeType: manifest.mimeType,
5604
+ filename: manifest.filename
5605
+ });
5606
+ return { ref, chunksFetched, chunksSkipped };
5607
+ }
5608
+ async function prepareBlobForSend(bytes, chunkStore, options = {}) {
5609
+ const { manifest, chunks } = await chunkBlob(bytes, options.chunkSize, {
5610
+ mimeType: options.mimeType,
5611
+ filename: options.filename
5612
+ });
5613
+ for (const chunk of chunks.values()) {
5614
+ await chunkStore.put(chunk);
5615
+ }
5616
+ const provider = {
5617
+ getChunk: (hash) => chunkStore.get(hash)
5618
+ };
5619
+ return { manifest, provider };
5620
+ }
5621
+
5622
+ // src/blob/blob-manifest-transfer.ts
5623
+ var import_core29 = require("@korajs/core");
5624
+ function serializeBlobManifest(manifest) {
5625
+ const canonical = {
5626
+ blobHash: manifest.blobHash,
5627
+ size: manifest.size,
5628
+ chunkSize: manifest.chunkSize,
5629
+ chunkHashes: manifest.chunkHashes
5630
+ };
5631
+ if (manifest.mimeType !== void 0) {
5632
+ canonical.mimeType = manifest.mimeType;
5633
+ }
5634
+ if (manifest.filename !== void 0) {
5635
+ canonical.filename = manifest.filename;
5636
+ }
5637
+ return new TextEncoder().encode(JSON.stringify(canonical));
5638
+ }
5639
+ function parseBlobManifest(bytes) {
5640
+ const parsed = JSON.parse(new TextDecoder().decode(bytes));
5641
+ if (typeof parsed !== "object" || parsed === null) {
5642
+ throw new Error("Invalid blob manifest: not an object");
5643
+ }
5644
+ const obj = parsed;
5645
+ if (typeof obj.blobHash !== "string") {
5646
+ throw new Error("Invalid blob manifest: missing blobHash");
5647
+ }
5648
+ if (typeof obj.size !== "number" || typeof obj.chunkSize !== "number") {
5649
+ throw new Error("Invalid blob manifest: missing size or chunkSize");
5650
+ }
5651
+ if (!Array.isArray(obj.chunkHashes) || obj.chunkHashes.some((h) => typeof h !== "string")) {
5652
+ throw new Error("Invalid blob manifest: chunkHashes must be an array of strings");
5653
+ }
5654
+ const manifest = {
5655
+ blobHash: obj.blobHash,
5656
+ size: obj.size,
5657
+ chunkSize: obj.chunkSize,
5658
+ chunkHashes: obj.chunkHashes
5659
+ };
5660
+ if (typeof obj.mimeType === "string") {
5661
+ manifest.mimeType = obj.mimeType;
5662
+ }
5663
+ if (typeof obj.filename === "string") {
5664
+ manifest.filename = obj.filename;
5665
+ }
5666
+ return manifest;
5667
+ }
5668
+ async function putBlobForTransfer(store, bytes, options = {}) {
5669
+ const { manifest, chunks } = await chunkBlob(bytes, options.chunkSize, {
5670
+ ...options.mimeType !== void 0 ? { mimeType: options.mimeType } : {},
5671
+ ...options.filename !== void 0 ? { filename: options.filename } : {}
5672
+ });
5673
+ for (const chunk of chunks.values()) {
5674
+ await store.put(chunk);
5675
+ }
5676
+ const baseRef = await store.put(bytes, {
5677
+ ...options.mimeType !== void 0 ? { mimeType: options.mimeType } : {},
5678
+ ...options.filename !== void 0 ? { filename: options.filename } : {}
5679
+ });
5680
+ const manifestRef = await store.put(serializeBlobManifest(manifest));
5681
+ return { ref: { ...baseRef, manifestHash: manifestRef.hash }, manifest };
5682
+ }
5683
+ async function fetchBlobManifest(provider, manifestHash) {
5684
+ const bytes = await provider.getChunk(manifestHash);
5685
+ if (bytes === null) {
5686
+ throw new Error(`Blob manifest ${manifestHash} could not be fetched from the provider`);
5687
+ }
5688
+ const actual = await (0, import_core29.hashBlob)(bytes);
5689
+ if (actual !== manifestHash) {
5690
+ throw new BlobIntegrityError(manifestHash, actual);
5691
+ }
5692
+ return parseBlobManifest(bytes);
5693
+ }
5694
+ async function resolveBlobManifest(provider, ref) {
5695
+ if (ref.manifestHash === void 0) {
5696
+ throw new Error(
5697
+ `Blob ${ref.hash} has no manifest to resolve. Store it with putBlobForTransfer (or app.blobs.put) so its reference carries a manifestHash, or pull with an explicit manifest.`
5698
+ );
5699
+ }
5700
+ return fetchBlobManifest(provider, ref.manifestHash);
5701
+ }
5702
+
5703
+ // src/blob/server-blob-callbacks.ts
5704
+ var import_core30 = require("@korajs/core");
5705
+ function toServerBlobCallbacks(store) {
5706
+ return {
5707
+ resolveBlobChunk: (hash) => store.get(hash),
5708
+ async persistBlobChunk(hash, bytes) {
5709
+ const actual = await (0, import_core30.hashBlob)(bytes);
5710
+ if (actual !== hash) {
5711
+ return;
5712
+ }
5713
+ await store.put(bytes);
5714
+ }
5715
+ };
5716
+ }
5717
+ function createMemoryServerBlobStore() {
5718
+ const store = new MemoryBlobStore();
5719
+ return { store, callbacks: toServerBlobCallbacks(store) };
5720
+ }
5721
+
5722
+ // src/blob/blob-gc.ts
5723
+ var import_core31 = require("@korajs/core");
5724
+ function extractBlobRefs(record) {
5725
+ const refs = [];
5726
+ for (const value of Object.values(record)) {
5727
+ if ((0, import_core31.isBlobRef)(value)) {
5728
+ refs.push(value);
5729
+ }
5730
+ }
5731
+ return refs;
5732
+ }
5733
+ async function collectBlobGarbage(store, liveRefs, options = {}) {
5734
+ const live = /* @__PURE__ */ new Set();
5735
+ for (const ref of liveRefs) {
5736
+ live.add(ref.hash);
5737
+ if (ref.manifestHash === void 0) {
5738
+ continue;
5739
+ }
5740
+ live.add(ref.manifestHash);
5741
+ try {
5742
+ const manifestBytes = await store.get(ref.manifestHash);
5743
+ if (manifestBytes !== null) {
5744
+ const manifest = parseBlobManifest(manifestBytes);
5745
+ for (const chunkHash of manifest.chunkHashes) {
5746
+ live.add(chunkHash);
5747
+ }
5748
+ }
5749
+ } catch {
5750
+ }
5751
+ }
5752
+ const all = await store.list();
5753
+ const collectedHashes = [];
5754
+ for (const hash of all) {
5755
+ if (live.has(hash)) {
5756
+ continue;
5757
+ }
5758
+ collectedHashes.push(hash);
5759
+ if (!options.dryRun) {
5760
+ await store.delete(hash);
5761
+ }
5762
+ }
5763
+ return {
5764
+ scanned: all.length,
5765
+ live: live.size,
5766
+ collected: collectedHashes.length,
5767
+ collectedHashes
5768
+ };
5769
+ }
5770
+
5771
+ // src/blob/blob-chunk-transport.ts
5772
+ var import_core32 = require("@korajs/core");
5773
+ function createRemoteChunkProvider(port, options = {}) {
5774
+ const timeoutMs = options.timeoutMs ?? 3e4;
5775
+ const pending = /* @__PURE__ */ new Map();
5776
+ port.onMessage((message) => {
5777
+ if (message.type !== "blob-chunk-response") {
5778
+ return;
5779
+ }
5780
+ const entry = pending.get(message.requestId);
5781
+ if (!entry) {
5782
+ return;
5783
+ }
5784
+ pending.delete(message.requestId);
5785
+ clearTimeout(entry.timer);
5786
+ entry.resolve(message.bytes);
5787
+ });
5788
+ return {
5789
+ pendingCount: () => pending.size,
5790
+ getChunk(hash) {
5791
+ const requestId = (0, import_core32.generateUUIDv7)();
5792
+ return new Promise((resolve, reject) => {
5793
+ const timer = setTimeout(() => {
5794
+ pending.delete(requestId);
5795
+ reject(new Error(`Blob chunk request for ${hash} timed out after ${timeoutMs}ms`));
5796
+ }, timeoutMs);
5797
+ pending.set(requestId, { resolve, reject, timer });
5798
+ port.send({ type: "blob-chunk-request", requestId, hash });
5799
+ });
5800
+ }
5801
+ };
5802
+ }
5803
+ function serveBlobChunks(port, blobStore) {
5804
+ port.onMessage((message) => {
5805
+ if (message.type !== "blob-chunk-request") {
5806
+ return;
5807
+ }
5808
+ void blobStore.get(message.hash).then(
5809
+ (bytes) => {
5810
+ port.send({ type: "blob-chunk-response", requestId: message.requestId, bytes });
5811
+ },
5812
+ () => {
5813
+ port.send({ type: "blob-chunk-response", requestId: message.requestId, bytes: null });
5814
+ }
5815
+ );
5816
+ });
5817
+ }
5818
+ function createChunkPortPair() {
5819
+ const handlersA = [];
5820
+ const handlersB = [];
5821
+ const a = {
5822
+ send(message) {
5823
+ queueMicrotask(() => {
5824
+ for (const handler of handlersB) {
5825
+ handler(message);
5826
+ }
5827
+ });
5828
+ },
5829
+ onMessage(handler) {
5830
+ handlersA.push(handler);
5831
+ }
5832
+ };
5833
+ const b = {
5834
+ send(message) {
5835
+ queueMicrotask(() => {
5836
+ for (const handler of handlersA) {
5837
+ handler(message);
5838
+ }
5839
+ });
5840
+ },
5841
+ onMessage(handler) {
5842
+ handlersB.push(handler);
5843
+ }
5844
+ };
5845
+ return { a, b };
5846
+ }
5078
5847
  // Annotate the CommonJS export names for ESM import in node:
5079
5848
  0 && (module.exports = {
5080
5849
  AdapterError,
5850
+ BlobIntegrityError,
5081
5851
  COMPACTION_BASELINE_META_KEY,
5082
5852
  Collection,
5853
+ DEFAULT_CHUNK_SIZE,
5083
5854
  InvalidStateTransitionError,
5084
5855
  LAST_ACKED_SERVER_VECTOR_META_KEY,
5856
+ MemoryBlobStore,
5857
+ NoLeaderError,
5858
+ OpfsBlobStore,
5085
5859
  OptimisticLockError,
5086
5860
  PersistenceError,
5087
5861
  QueryBuilder,
@@ -5099,26 +5873,44 @@ init_export_audit();
5099
5873
  WorkerTimeoutError,
5100
5874
  asRichTextSyncEngine,
5101
5875
  assertQueryReady,
5876
+ chunkBlob,
5877
+ collectBlobGarbage,
5102
5878
  collectOperationsAheadOfServer,
5103
5879
  compactOperationLog,
5104
5880
  computeAckCompactionWatermark,
5881
+ createChunkPortPair,
5882
+ createMemoryServerBlobStore,
5883
+ createOpfsBlobDirectory,
5884
+ createOpfsBlobStore,
5885
+ createRemoteChunkProvider,
5105
5886
  createRichTextController,
5106
5887
  decodeAuditExport,
5107
5888
  decodeRichtext,
5108
5889
  deserializeVersionVectorFromMeta,
5109
5890
  encodeRichtext,
5110
5891
  exportBackup,
5892
+ extractBlobRefs,
5893
+ fetchBlobManifest,
5111
5894
  getSharedQueryStoreCache,
5112
5895
  mergeVersionVectors,
5896
+ parseBlobManifest,
5113
5897
  persistedAuditTraceFromEvent,
5114
5898
  pluralize,
5899
+ prepareBlobForSend,
5900
+ putBlobForTransfer,
5115
5901
  readAuditExportManifest,
5116
5902
  readBackupManifest,
5903
+ reassembleBlob,
5904
+ receiveBlob,
5905
+ resolveBlobManifest,
5117
5906
  restoreBackup,
5118
5907
  richtextStatesEqual,
5119
5908
  richtextToPlainText,
5909
+ serializeBlobManifest,
5120
5910
  serializeVersionVectorToMeta,
5911
+ serveBlobChunks,
5121
5912
  singularize,
5913
+ toServerBlobCallbacks,
5122
5914
  validateStateTransition,
5123
5915
  validateUpdateStateMachine,
5124
5916
  verifyAuditExportChecksum,