@korajs/store 0.6.0 → 1.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/LICENSE +21 -0
  2. package/dist/adapters/better-sqlite3.cjs +94 -40
  3. package/dist/adapters/better-sqlite3.cjs.map +1 -1
  4. package/dist/adapters/better-sqlite3.d.cts +9 -1
  5. package/dist/adapters/better-sqlite3.d.ts +9 -1
  6. package/dist/adapters/better-sqlite3.js +62 -41
  7. package/dist/adapters/better-sqlite3.js.map +1 -1
  8. package/dist/adapters/indexeddb.cjs +24 -13
  9. package/dist/adapters/indexeddb.cjs.map +1 -1
  10. package/dist/adapters/indexeddb.d.cts +2 -2
  11. package/dist/adapters/indexeddb.d.ts +2 -2
  12. package/dist/adapters/indexeddb.js +4 -3
  13. package/dist/adapters/indexeddb.js.map +1 -1
  14. package/dist/adapters/sqlite-wasm.cjs +24 -13
  15. package/dist/adapters/sqlite-wasm.cjs.map +1 -1
  16. package/dist/adapters/sqlite-wasm.d.cts +27 -4
  17. package/dist/adapters/sqlite-wasm.d.ts +27 -4
  18. package/dist/adapters/sqlite-wasm.js +6 -4
  19. package/dist/{backup-JN3LB4YT.js → backup-ZV4Y424B.js} +3 -3
  20. package/dist/blob-fs.cjs +166 -0
  21. package/dist/blob-fs.cjs.map +1 -0
  22. package/dist/blob-fs.d.cts +29 -0
  23. package/dist/blob-fs.d.ts +29 -0
  24. package/dist/blob-fs.js +128 -0
  25. package/dist/blob-fs.js.map +1 -0
  26. package/dist/{chunk-56LL4XP6.js → chunk-6NKLDBTY.js} +52 -1
  27. package/dist/chunk-6NKLDBTY.js.map +1 -0
  28. package/dist/{chunk-AGN3CIPB.js → chunk-FFYA7J5H.js} +2 -2
  29. package/dist/{chunk-XO4LMPAH.js → chunk-IOEFNHHP.js} +2 -35
  30. package/dist/chunk-IOEFNHHP.js.map +1 -0
  31. package/dist/{chunk-LAWV6CFH.js → chunk-QL2XVZRN.js} +12 -1
  32. package/dist/chunk-QL2XVZRN.js.map +1 -0
  33. package/dist/{chunk-6AHYGQQT.js → chunk-RBPVEAVM.js} +4 -4
  34. package/dist/chunk-TVJEC5S2.js +40 -0
  35. package/dist/chunk-TVJEC5S2.js.map +1 -0
  36. package/dist/chunk-W4MTTNEZ.js +55 -0
  37. package/dist/chunk-W4MTTNEZ.js.map +1 -0
  38. package/dist/{chunk-EZSTD2XC.js → chunk-X57MWW35.js} +2 -2
  39. package/dist/{chunk-7BPETY5Y.js → chunk-ZLWAYQEQ.js} +128 -15
  40. package/dist/chunk-ZLWAYQEQ.js.map +1 -0
  41. package/dist/content-addressed-blob-store-Dcj3SB9r.d.cts +60 -0
  42. package/dist/content-addressed-blob-store-Dcj3SB9r.d.ts +60 -0
  43. package/dist/{export-audit-5GV6VRDZ.js → export-audit-AJ7RORNK.js} +3 -3
  44. package/dist/index.cjs +1245 -113
  45. package/dist/index.cjs.map +1 -1
  46. package/dist/index.d.cts +468 -7
  47. package/dist/index.d.ts +468 -7
  48. package/dist/index.js +975 -61
  49. package/dist/index.js.map +1 -1
  50. package/dist/internal.cjs +123 -33
  51. package/dist/internal.cjs.map +1 -1
  52. package/dist/internal.d.cts +3 -3
  53. package/dist/internal.d.ts +3 -3
  54. package/dist/internal.js +3 -3
  55. package/dist/{sqlite-wasm-channel-I9y_jObg.d.cts → sqlite-wasm-channel-DUDPclTE.d.cts} +2 -15
  56. package/dist/{sqlite-wasm-channel-I9y_jObg.d.ts → sqlite-wasm-channel-DUDPclTE.d.ts} +2 -15
  57. package/dist/sqlite-wasm-channel-H43KEZLM.js +12 -0
  58. package/dist/{types-CTr00g_d.d.cts → types-5h4Hqcwc.d.ts} +10 -3
  59. package/dist/{types-BMyHmwvn.d.ts → types-DfPiPwKr.d.cts} +10 -3
  60. package/dist/{types-DepnRDk_.d.cts → types-k6AZgiH4.d.cts} +48 -2
  61. package/dist/{types-DepnRDk_.d.ts → types-k6AZgiH4.d.ts} +48 -2
  62. package/package.json +15 -2
  63. package/dist/chunk-56LL4XP6.js.map +0 -1
  64. package/dist/chunk-7BPETY5Y.js.map +0 -1
  65. package/dist/chunk-LAWV6CFH.js.map +0 -1
  66. package/dist/chunk-XO4LMPAH.js.map +0 -1
  67. package/dist/sqlite-wasm-channel-HDDW6UBH.js +0 -10
  68. /package/dist/{backup-JN3LB4YT.js.map → backup-ZV4Y424B.js.map} +0 -0
  69. /package/dist/{chunk-AGN3CIPB.js.map → chunk-FFYA7J5H.js.map} +0 -0
  70. /package/dist/{chunk-6AHYGQQT.js.map → chunk-RBPVEAVM.js.map} +0 -0
  71. /package/dist/{chunk-EZSTD2XC.js.map → chunk-X57MWW35.js.map} +0 -0
  72. /package/dist/{export-audit-5GV6VRDZ.js.map → export-audit-AJ7RORNK.js.map} +0 -0
  73. /package/dist/{sqlite-wasm-channel-HDDW6UBH.js.map → sqlite-wasm-channel-H43KEZLM.js.map} +0 -0
package/dist/index.cjs CHANGED
@@ -30,6 +30,38 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
30
30
  ));
31
31
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
32
32
 
33
+ // src/serialization/op-data-encoding.ts
34
+ function encodeRichtextFieldsForOpData(data, fields) {
35
+ const result = { ...data };
36
+ for (const [key, value] of Object.entries(data)) {
37
+ if (fields[key]?.kind !== "richtext") {
38
+ continue;
39
+ }
40
+ if (typeof value === "string" || value instanceof Uint8Array || value instanceof ArrayBuffer) {
41
+ result[key] = (0, import_core4.encodeBytesForOpData)(value);
42
+ }
43
+ }
44
+ return result;
45
+ }
46
+ function decodeRichtextFieldsFromOpData(data, fields) {
47
+ const result = { ...data };
48
+ for (const [key, value] of Object.entries(data)) {
49
+ if (fields[key]?.kind !== "richtext" || value === null || value === void 0) {
50
+ continue;
51
+ }
52
+ result[key] = (0, import_core4.decodeBytesFromOpData)(value);
53
+ }
54
+ return result;
55
+ }
56
+ var import_core4, decodeRichtextOpDataValue;
57
+ var init_op_data_encoding = __esm({
58
+ "src/serialization/op-data-encoding.ts"() {
59
+ "use strict";
60
+ import_core4 = require("@korajs/core");
61
+ decodeRichtextOpDataValue = import_core4.decodeBytesFromOpData;
62
+ }
63
+ });
64
+
33
65
  // src/serialization/richtext-serializer.ts
34
66
  function encodeRichtext(value) {
35
67
  if (value === null || value === void 0) {
@@ -46,6 +78,10 @@ function encodeRichtext(value) {
46
78
  if (value instanceof ArrayBuffer) {
47
79
  return new Uint8Array(value);
48
80
  }
81
+ if ((0, import_core4.isKoraBytesValue)(value) || (0, import_core4.isLegacyNumericByteObject)(value)) {
82
+ const decoded = decodeRichtextOpDataValue(value);
83
+ return decoded instanceof Uint8Array ? decoded : null;
84
+ }
49
85
  throw new Error("Richtext value must be a string, Uint8Array, ArrayBuffer, null, or undefined.");
50
86
  }
51
87
  function decodeRichtext(value) {
@@ -84,6 +120,7 @@ var init_richtext_serializer = __esm({
84
120
  "src/serialization/richtext-serializer.ts"() {
85
121
  "use strict";
86
122
  Y = __toESM(require("yjs"), 1);
123
+ init_op_data_encoding();
87
124
  TEXT_KEY = "content";
88
125
  }
89
126
  });
@@ -147,7 +184,7 @@ function serializeOperation(op) {
147
184
  record_id: op.recordId,
148
185
  data: dataPayload ? JSON.stringify(dataPayload) : null,
149
186
  previous_data: op.previousData ? JSON.stringify(op.previousData) : null,
150
- timestamp: import_core4.HybridLogicalClock.serialize(op.timestamp),
187
+ timestamp: import_core5.HybridLogicalClock.serialize(op.timestamp),
151
188
  sequence_number: op.sequenceNumber,
152
189
  causal_deps: JSON.stringify(op.causalDeps),
153
190
  schema_version: op.schemaVersion
@@ -181,7 +218,7 @@ function deserializeOperation(row) {
181
218
  recordId: row.record_id,
182
219
  data,
183
220
  previousData: row.previous_data ? JSON.parse(row.previous_data) : null,
184
- timestamp: import_core4.HybridLogicalClock.deserialize(row.timestamp),
221
+ timestamp: import_core5.HybridLogicalClock.deserialize(row.timestamp),
185
222
  sequenceNumber: row.sequence_number,
186
223
  causalDeps: JSON.parse(row.causal_deps),
187
224
  schemaVersion: row.schema_version,
@@ -202,6 +239,9 @@ function serializeValue(value, descriptor) {
202
239
  case "boolean":
203
240
  return value ? 1 : 0;
204
241
  case "array":
242
+ case "object":
243
+ case "json":
244
+ case "blob":
205
245
  return JSON.stringify(value);
206
246
  case "richtext":
207
247
  return encodeRichtext(value);
@@ -218,17 +258,24 @@ function deserializeValue(value, descriptor) {
218
258
  return JSON.parse(value);
219
259
  }
220
260
  return value;
261
+ case "object":
262
+ case "json":
263
+ case "blob":
264
+ if (typeof value === "string") {
265
+ return JSON.parse(value);
266
+ }
267
+ return value;
221
268
  case "richtext":
222
269
  return decodeRichtext(value);
223
270
  default:
224
271
  return value;
225
272
  }
226
273
  }
227
- var import_core4, ATOMIC_OPS_KEY, TX_ID_KEY, MUTATION_NAME_KEY;
274
+ var import_core5, ATOMIC_OPS_KEY, TX_ID_KEY, MUTATION_NAME_KEY;
228
275
  var init_serializer = __esm({
229
276
  "src/serialization/serializer.ts"() {
230
277
  "use strict";
231
- import_core4 = require("@korajs/core");
278
+ import_core5 = require("@korajs/core");
232
279
  init_richtext_serializer();
233
280
  ATOMIC_OPS_KEY = "__kora_atomic_ops__";
234
281
  TX_ID_KEY = "__kora_tx_id__";
@@ -609,11 +656,11 @@ async function computeSha256(data) {
609
656
  const hashArray = Array.from(new Uint8Array(hashBuffer));
610
657
  return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
611
658
  }
612
- var import_core5, BACKUP_VERSION;
659
+ var import_core6, BACKUP_VERSION;
613
660
  var init_backup = __esm({
614
661
  "src/backup/backup.ts"() {
615
662
  "use strict";
616
- import_core5 = require("@korajs/core");
663
+ import_core6 = require("@korajs/core");
617
664
  init_serializer();
618
665
  BACKUP_VERSION = 1;
619
666
  }
@@ -670,7 +717,7 @@ __export(audit_trace_store_exports, {
670
717
  });
671
718
  function persistedAuditTraceFromEvent(event) {
672
719
  return {
673
- id: (0, import_core16.generateUUIDv7)(),
720
+ id: (0, import_core18.generateUUIDv7)(),
674
721
  recordedAt: Date.now(),
675
722
  eventType: event.type,
676
723
  constraint: event.type === "constraint:violated" ? event.constraint : void 0,
@@ -736,11 +783,11 @@ function auditTraceIndexFields(trace) {
736
783
  recordId: trace.operationA.recordId
737
784
  };
738
785
  }
739
- var import_core16;
786
+ var import_core18;
740
787
  var init_audit_trace_store = __esm({
741
788
  "src/audit/audit-trace-store.ts"() {
742
789
  "use strict";
743
- import_core16 = require("@korajs/core");
790
+ import_core18 = require("@korajs/core");
744
791
  init_audit_json();
745
792
  }
746
793
  });
@@ -936,10 +983,15 @@ var init_export_audit = __esm({
936
983
  var index_exports = {};
937
984
  __export(index_exports, {
938
985
  AdapterError: () => AdapterError,
986
+ BlobIntegrityError: () => BlobIntegrityError,
939
987
  COMPACTION_BASELINE_META_KEY: () => COMPACTION_BASELINE_META_KEY,
940
988
  Collection: () => Collection,
989
+ DEFAULT_CHUNK_SIZE: () => DEFAULT_CHUNK_SIZE,
941
990
  InvalidStateTransitionError: () => InvalidStateTransitionError,
942
991
  LAST_ACKED_SERVER_VECTOR_META_KEY: () => LAST_ACKED_SERVER_VECTOR_META_KEY,
992
+ MemoryBlobStore: () => MemoryBlobStore,
993
+ OpfsBlobStore: () => OpfsBlobStore,
994
+ OptimisticLockError: () => OptimisticLockError,
943
995
  PersistenceError: () => PersistenceError,
944
996
  QueryBuilder: () => QueryBuilder,
945
997
  QueryError: () => QueryError,
@@ -956,26 +1008,44 @@ __export(index_exports, {
956
1008
  WorkerTimeoutError: () => WorkerTimeoutError,
957
1009
  asRichTextSyncEngine: () => asRichTextSyncEngine,
958
1010
  assertQueryReady: () => assertQueryReady,
1011
+ chunkBlob: () => chunkBlob,
1012
+ collectBlobGarbage: () => collectBlobGarbage,
959
1013
  collectOperationsAheadOfServer: () => collectOperationsAheadOfServer,
960
1014
  compactOperationLog: () => compactOperationLog,
961
1015
  computeAckCompactionWatermark: () => computeAckCompactionWatermark,
1016
+ createChunkPortPair: () => createChunkPortPair,
1017
+ createMemoryServerBlobStore: () => createMemoryServerBlobStore,
1018
+ createOpfsBlobDirectory: () => createOpfsBlobDirectory,
1019
+ createOpfsBlobStore: () => createOpfsBlobStore,
1020
+ createRemoteChunkProvider: () => createRemoteChunkProvider,
962
1021
  createRichTextController: () => createRichTextController,
963
1022
  decodeAuditExport: () => decodeAuditExport,
964
1023
  decodeRichtext: () => decodeRichtext,
965
1024
  deserializeVersionVectorFromMeta: () => deserializeVersionVectorFromMeta,
966
1025
  encodeRichtext: () => encodeRichtext,
967
1026
  exportBackup: () => exportBackup,
1027
+ extractBlobRefs: () => extractBlobRefs,
1028
+ fetchBlobManifest: () => fetchBlobManifest,
968
1029
  getSharedQueryStoreCache: () => getSharedQueryStoreCache,
969
1030
  mergeVersionVectors: () => mergeVersionVectors,
1031
+ parseBlobManifest: () => parseBlobManifest,
970
1032
  persistedAuditTraceFromEvent: () => persistedAuditTraceFromEvent,
971
1033
  pluralize: () => pluralize,
1034
+ prepareBlobForSend: () => prepareBlobForSend,
1035
+ putBlobForTransfer: () => putBlobForTransfer,
972
1036
  readAuditExportManifest: () => readAuditExportManifest,
973
1037
  readBackupManifest: () => readBackupManifest,
1038
+ reassembleBlob: () => reassembleBlob,
1039
+ receiveBlob: () => receiveBlob,
1040
+ resolveBlobManifest: () => resolveBlobManifest,
974
1041
  restoreBackup: () => restoreBackup,
975
1042
  richtextStatesEqual: () => richtextStatesEqual,
976
1043
  richtextToPlainText: () => richtextToPlainText,
1044
+ serializeBlobManifest: () => serializeBlobManifest,
977
1045
  serializeVersionVectorToMeta: () => serializeVersionVectorToMeta,
1046
+ serveBlobChunks: () => serveBlobChunks,
978
1047
  singularize: () => singularize,
1048
+ toServerBlobCallbacks: () => toServerBlobCallbacks,
979
1049
  validateStateTransition: () => validateStateTransition,
980
1050
  validateUpdateStateMachine: () => validateUpdateStateMachine,
981
1051
  verifyAuditExportChecksum: () => verifyAuditExportChecksum,
@@ -1000,6 +1070,16 @@ var RecordNotFoundError = class extends import_core.KoraError {
1000
1070
  this.name = "RecordNotFoundError";
1001
1071
  }
1002
1072
  };
1073
+ var OptimisticLockError = class extends import_core.KoraError {
1074
+ constructor(collection, recordId) {
1075
+ super(
1076
+ `Row version changed while merging record "${recordId}" in "${collection}"; retry with fresh state`,
1077
+ "OPTIMISTIC_LOCK",
1078
+ { collection, recordId }
1079
+ );
1080
+ this.name = "OptimisticLockError";
1081
+ }
1082
+ };
1003
1083
  var AdapterError = class extends import_core.KoraError {
1004
1084
  constructor(message, context) {
1005
1085
  super(message, "ADAPTER_ERROR", context);
@@ -1176,27 +1256,27 @@ async function saveDeltaCursor(adapter, cursor) {
1176
1256
  }
1177
1257
 
1178
1258
  // src/store/store.ts
1179
- var import_core17 = require("@korajs/core");
1259
+ var import_core19 = require("@korajs/core");
1180
1260
  init_backup();
1181
1261
 
1182
1262
  // src/mutations/execute-delete.ts
1183
- var import_core7 = require("@korajs/core");
1263
+ var import_core8 = require("@korajs/core");
1184
1264
 
1185
1265
  // src/lww/row-version.ts
1186
- var import_core6 = require("@korajs/core");
1266
+ var import_core7 = require("@korajs/core");
1187
1267
  var EMPTY_ROW_VERSION = "";
1188
1268
  function serializeRowVersion(timestamp) {
1189
- return import_core6.HybridLogicalClock.serialize(timestamp);
1269
+ return import_core7.HybridLogicalClock.serialize(timestamp);
1190
1270
  }
1191
1271
  function rowVersionFromRecord(row) {
1192
1272
  const version = row._version;
1193
1273
  if (typeof version === "string" && version.length > 0) {
1194
- return import_core6.HybridLogicalClock.deserialize(version);
1274
+ return import_core7.HybridLogicalClock.deserialize(version);
1195
1275
  }
1196
1276
  return { wallTime: row._updated_at, logical: 0, nodeId: "" };
1197
1277
  }
1198
1278
  function isIncomingNewerThanRow(incoming, row) {
1199
- return import_core6.HybridLogicalClock.compare(incoming, rowVersionFromRecord(row)) > 0;
1279
+ return import_core7.HybridLogicalClock.compare(incoming, rowVersionFromRecord(row)) > 0;
1200
1280
  }
1201
1281
  function lwwVersionWhereClause(remoteVersion) {
1202
1282
  return {
@@ -1256,6 +1336,28 @@ function buildUpdateQuery(collection, id, changes) {
1256
1336
  const sql = `UPDATE ${collection} SET ${setClauses.join(", ")} WHERE id = ?`;
1257
1337
  return { sql, params };
1258
1338
  }
1339
+ function buildFieldFastForwardUpdateQuery(collection, id, fieldChanges, remoteVersion, wallTime, options) {
1340
+ const fieldClauses = Object.keys(fieldChanges).map((col) => `${col} = ?`);
1341
+ const setClauses = [
1342
+ ...fieldClauses,
1343
+ "_version = CASE WHEN _version >= ? THEN _version ELSE ? END",
1344
+ "_updated_at = CASE WHEN _updated_at >= ? THEN _updated_at ELSE ? END"
1345
+ ];
1346
+ const params = [
1347
+ ...Object.values(fieldChanges),
1348
+ remoteVersion,
1349
+ remoteVersion,
1350
+ wallTime,
1351
+ wallTime
1352
+ ];
1353
+ if (options?.maxCreatedAt !== void 0) {
1354
+ setClauses.push("_created_at = CASE WHEN _created_at >= ? THEN _created_at ELSE ? END");
1355
+ params.push(options.maxCreatedAt, options.maxCreatedAt);
1356
+ }
1357
+ params.push(id);
1358
+ const sql = `UPDATE ${collection} SET ${setClauses.join(", ")} WHERE id = ?`;
1359
+ return { sql, params };
1360
+ }
1259
1361
  function buildSoftDeleteQuery(collection, id, updatedAt, version) {
1260
1362
  if (version !== void 0) {
1261
1363
  return {
@@ -1268,13 +1370,6 @@ function buildSoftDeleteQuery(collection, id, updatedAt, version) {
1268
1370
  params: [updatedAt, id]
1269
1371
  };
1270
1372
  }
1271
- function buildLwwUpdateQuery(collection, id, changes, remoteVersion) {
1272
- const setClauses = Object.keys(changes).map((col) => `${col} = ?`);
1273
- const lww = lwwVersionWhereClause(remoteVersion);
1274
- const sql = `UPDATE ${collection} SET ${setClauses.join(", ")} WHERE id = ? AND ${lww.sql}`;
1275
- const params = [...Object.values(changes), id, ...lww.params];
1276
- return { sql, params };
1277
- }
1278
1373
  function buildLwwSoftDeleteQuery(collection, id, updatedAt, version) {
1279
1374
  const lww = lwwVersionWhereClause(version);
1280
1375
  return {
@@ -1439,7 +1534,7 @@ async function executeDelete(ctx, id, options) {
1439
1534
  await ctx.adapter.transaction(async (tx) => {
1440
1535
  if (!operation) {
1441
1536
  const sequenceNumber = await allocateNextSequenceInTransaction(tx, ctx.nodeId);
1442
- operation = await (0, import_core7.createOperation)(
1537
+ operation = await (0, import_core8.createOperation)(
1443
1538
  {
1444
1539
  nodeId: ctx.nodeId,
1445
1540
  type: "delete",
@@ -1486,28 +1581,90 @@ async function executeDelete(ctx, id, options) {
1486
1581
  }
1487
1582
 
1488
1583
  // src/mutations/execute-insert.ts
1489
- var import_core8 = require("@korajs/core");
1584
+ var import_core9 = require("@korajs/core");
1585
+
1586
+ // src/lww/field-versions.ts
1587
+ function parseFieldVersions(raw) {
1588
+ if (typeof raw !== "string" || raw.length === 0) {
1589
+ return {};
1590
+ }
1591
+ try {
1592
+ const parsed = JSON.parse(raw);
1593
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
1594
+ return {};
1595
+ }
1596
+ const result = {};
1597
+ for (const [key, value] of Object.entries(parsed)) {
1598
+ if (typeof value === "string") {
1599
+ result[key] = value;
1600
+ }
1601
+ }
1602
+ return result;
1603
+ } catch {
1604
+ return {};
1605
+ }
1606
+ }
1607
+ function serializeFieldVersions(versions) {
1608
+ return JSON.stringify(versions);
1609
+ }
1610
+ function fieldVersionsForFields(fieldNames, version) {
1611
+ const versions = {};
1612
+ for (const field of fieldNames) {
1613
+ versions[field] = version;
1614
+ }
1615
+ return versions;
1616
+ }
1617
+ function effectiveFieldVersion(current, field, rowVersion) {
1618
+ const own = current[field];
1619
+ if (own !== void 0) {
1620
+ return own;
1621
+ }
1622
+ return rowVersion ?? EMPTY_ROW_VERSION;
1623
+ }
1624
+ function resolvePerFieldLww(current, incomingFields, incomingVersion, rowVersion) {
1625
+ const winners = [];
1626
+ const merged = { ...current };
1627
+ for (const field of incomingFields) {
1628
+ const stored = effectiveFieldVersion(current, field, rowVersion);
1629
+ if (incomingVersion > stored) {
1630
+ winners.push(field);
1631
+ merged[field] = incomingVersion;
1632
+ }
1633
+ }
1634
+ return { winners, merged };
1635
+ }
1636
+
1637
+ // src/mutations/execute-insert.ts
1638
+ init_op_data_encoding();
1490
1639
  init_serializer();
1491
1640
  async function executeInsert(ctx, data) {
1492
- const validated = (0, import_core8.validateRecord)(ctx.collection, ctx.definition, data, "insert");
1493
- const recordId = (0, import_core8.generateUUIDv7)();
1641
+ const validated = (0, import_core9.validateRecord)(ctx.collection, ctx.definition, data, "insert");
1642
+ const recordId = (0, import_core9.generateUUIDv7)();
1494
1643
  for (const [fieldName, descriptor] of Object.entries(ctx.definition.fields)) {
1495
1644
  if (descriptor.auto && descriptor.kind === "timestamp") {
1496
1645
  validated[fieldName] = Date.now();
1497
1646
  }
1498
1647
  }
1648
+ const writeData = await (0, import_core9.transformSecretFieldsForWrite)(
1649
+ validated,
1650
+ ctx.definition,
1651
+ ctx.secretKeyProvider
1652
+ );
1499
1653
  const causalDeps = resolveCausalDeps(ctx);
1500
1654
  let operation;
1501
1655
  let record;
1502
1656
  await ctx.adapter.transaction(async (tx) => {
1503
1657
  const sequenceNumber = await allocateNextSequenceInTransaction(tx, ctx.nodeId);
1504
- operation = await (0, import_core8.createOperation)(
1658
+ operation = await (0, import_core9.createOperation)(
1505
1659
  {
1506
1660
  nodeId: ctx.nodeId,
1507
1661
  type: "insert",
1508
1662
  collection: ctx.collection,
1509
1663
  recordId,
1510
- data: { ...validated },
1664
+ // Binary richtext values are tagged as canonical JSON BEFORE the
1665
+ // operation is content-hashed, so the hash input, persisted JSON,
1666
+ // and wire payload are the identical value.
1667
+ data: encodeRichtextFieldsForOpData(writeData, ctx.definition.fields),
1511
1668
  previousData: null,
1512
1669
  sequenceNumber,
1513
1670
  causalDeps,
@@ -1516,14 +1673,19 @@ async function executeInsert(ctx, data) {
1516
1673
  ctx.clock
1517
1674
  );
1518
1675
  ctx.causalTracker?.afterOperation(ctx.collection, operation.id, ctx.inTransaction);
1519
- const serializedData = serializeRecord(validated, ctx.definition.fields);
1676
+ const serializedData = serializeRecord(writeData, ctx.definition.fields);
1520
1677
  const version = serializeRowVersion(operation.timestamp);
1521
1678
  record = {
1522
1679
  id: recordId,
1523
1680
  ...serializedData,
1524
1681
  _created_at: operation.timestamp.wallTime,
1525
1682
  _updated_at: operation.timestamp.wallTime,
1526
- _version: version
1683
+ _version: version,
1684
+ // Stamp every inserted field with this operation's version so later
1685
+ // per-field LWW compares against a real writer, not the fallback.
1686
+ _field_versions: serializeFieldVersions(
1687
+ fieldVersionsForFields(Object.keys(serializedData), version)
1688
+ )
1527
1689
  };
1528
1690
  const builtInsert = buildInsertQuery(ctx.collection, record);
1529
1691
  const opInsert = buildInsertQuery(
@@ -1536,19 +1698,20 @@ async function executeInsert(ctx, data) {
1536
1698
  ctx.onMutation(ctx.collection, operation);
1537
1699
  return {
1538
1700
  id: recordId,
1539
- ...validated,
1701
+ ...writeData,
1540
1702
  createdAt: operation.timestamp.wallTime,
1541
1703
  updatedAt: operation.timestamp.wallTime
1542
1704
  };
1543
1705
  }
1544
1706
 
1545
1707
  // src/mutations/execute-update.ts
1546
- var import_core10 = require("@korajs/core");
1708
+ var import_core11 = require("@korajs/core");
1709
+ init_op_data_encoding();
1547
1710
  init_serializer();
1548
1711
 
1549
1712
  // src/state-machine/state-validator.ts
1550
- var import_core9 = require("@korajs/core");
1551
- var InvalidStateTransitionError = class extends import_core9.KoraError {
1713
+ var import_core10 = require("@korajs/core");
1714
+ var InvalidStateTransitionError = class extends import_core10.KoraError {
1552
1715
  constructor(collection, recordId, field, fromState, toState, allowedStates) {
1553
1716
  super(
1554
1717
  `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)"}`,
@@ -1582,7 +1745,7 @@ function validateStateTransition(collectionName, recordId, stateMachine, current
1582
1745
  collection: collectionName,
1583
1746
  transitions: stateMachine.transitions
1584
1747
  };
1585
- const transitionResult = (0, import_core9.validateTransition)(constraint, currentState, newState);
1748
+ const transitionResult = (0, import_core10.validateTransition)(constraint, currentState, newState);
1586
1749
  if (transitionResult.valid) {
1587
1750
  return { valid: true, allowedStates: transitionResult.allowedTargets };
1588
1751
  }
@@ -1638,7 +1801,7 @@ async function executeUpdate(ctx, id, data) {
1638
1801
  if (!currentRow) {
1639
1802
  throw new RecordNotFoundError(ctx.collection, id);
1640
1803
  }
1641
- let validated = (0, import_core10.validateRecord)(ctx.collection, ctx.definition, data, "update");
1804
+ let validated = (0, import_core11.validateRecord)(ctx.collection, ctx.definition, data, "update");
1642
1805
  const currentRecord = deserializeRecord(currentRow, ctx.definition.fields);
1643
1806
  validated = validateUpdateStateMachine(
1644
1807
  ctx.collection,
@@ -1656,26 +1819,34 @@ async function executeUpdate(ctx, id, data) {
1656
1819
  for (const key of Object.keys(validated)) {
1657
1820
  const value = validated[key];
1658
1821
  previousData[key] = currentRecord[key];
1659
- if ((0, import_core10.isAtomicOp)(value)) {
1660
- resolvedData[key] = (0, import_core10.resolveAtomicOp)(currentRecord[key], value);
1661
- atomicOps[key] = (0, import_core10.toAtomicOp)(value);
1822
+ if ((0, import_core11.isAtomicOp)(value)) {
1823
+ resolvedData[key] = (0, import_core11.resolveAtomicOp)(currentRecord[key], value);
1824
+ atomicOps[key] = (0, import_core11.toAtomicOp)(value);
1662
1825
  } else {
1663
1826
  resolvedData[key] = value;
1664
1827
  }
1665
1828
  }
1829
+ const writeData = await (0, import_core11.transformSecretFieldsForWrite)(
1830
+ resolvedData,
1831
+ ctx.definition,
1832
+ ctx.secretKeyProvider
1833
+ );
1666
1834
  const hasAtomicOps = Object.keys(atomicOps).length > 0;
1667
1835
  const causalDeps = resolveCausalDeps(ctx);
1668
1836
  let operation;
1669
1837
  await ctx.adapter.transaction(async (tx) => {
1670
1838
  const sequenceNumber = await allocateNextSequenceInTransaction(tx, ctx.nodeId);
1671
- operation = await (0, import_core10.createOperation)(
1839
+ operation = await (0, import_core11.createOperation)(
1672
1840
  {
1673
1841
  nodeId: ctx.nodeId,
1674
1842
  type: "update",
1675
1843
  collection: ctx.collection,
1676
1844
  recordId: id,
1677
- data: { ...resolvedData },
1678
- previousData,
1845
+ // Binary richtext values (new and previous) are tagged as canonical
1846
+ // JSON BEFORE the operation is content-hashed, so the hash input,
1847
+ // persisted JSON, and wire payload are the identical value.
1848
+ data: encodeRichtextFieldsForOpData(writeData, ctx.definition.fields),
1849
+ previousData: encodeRichtextFieldsForOpData(previousData, ctx.definition.fields),
1679
1850
  sequenceNumber,
1680
1851
  causalDeps,
1681
1852
  schemaVersion: ctx.schema.version,
@@ -1684,12 +1855,17 @@ async function executeUpdate(ctx, id, data) {
1684
1855
  ctx.clock
1685
1856
  );
1686
1857
  ctx.causalTracker?.afterOperation(ctx.collection, operation.id, ctx.inTransaction);
1687
- const serializedChanges = serializeRecord(resolvedData, ctx.definition.fields);
1858
+ const serializedChanges = serializeRecord(writeData, ctx.definition.fields);
1688
1859
  const version = serializeRowVersion(operation.timestamp);
1860
+ const mergedFieldVersions = parseFieldVersions(currentRow._field_versions);
1861
+ for (const changedField of Object.keys(serializedChanges)) {
1862
+ mergedFieldVersions[changedField] = version;
1863
+ }
1689
1864
  const updateQuery = buildUpdateQuery(ctx.collection, id, {
1690
1865
  ...serializedChanges,
1691
1866
  _updated_at: operation.timestamp.wallTime,
1692
- _version: version
1867
+ _version: version,
1868
+ _field_versions: serializeFieldVersions(mergedFieldVersions)
1693
1869
  });
1694
1870
  const opInsert = buildInsertQuery(
1695
1871
  `_kora_ops_${ctx.collection}`,
@@ -1713,7 +1889,7 @@ async function executeUpdate(ctx, id, data) {
1713
1889
  // src/collection/collection.ts
1714
1890
  init_serializer();
1715
1891
  var Collection = class {
1716
- constructor(name, definition, schema, adapter, clock, nodeId, allocateSequenceNumber, onMutation, relationEnforcer, mutationHandler, causalTracker) {
1892
+ constructor(name, definition, schema, adapter, clock, nodeId, allocateSequenceNumber, onMutation, relationEnforcer, mutationHandler, causalTracker, secretKeyProvider) {
1717
1893
  this.name = name;
1718
1894
  this.definition = definition;
1719
1895
  this.schema = schema;
@@ -1725,6 +1901,7 @@ var Collection = class {
1725
1901
  this.relationEnforcer = relationEnforcer;
1726
1902
  this.mutationHandler = mutationHandler;
1727
1903
  this.causalTracker = causalTracker;
1904
+ this.secretKeyProvider = secretKeyProvider;
1728
1905
  }
1729
1906
  name;
1730
1907
  definition;
@@ -1737,6 +1914,7 @@ var Collection = class {
1737
1914
  relationEnforcer;
1738
1915
  mutationHandler;
1739
1916
  causalTracker;
1917
+ secretKeyProvider;
1740
1918
  mutationContext() {
1741
1919
  return {
1742
1920
  collection: this.name,
@@ -1749,7 +1927,8 @@ var Collection = class {
1749
1927
  onMutation: this.onMutation,
1750
1928
  relationEnforcer: this.relationEnforcer,
1751
1929
  causalTracker: this.causalTracker,
1752
- inTransaction: false
1930
+ inTransaction: false,
1931
+ secretKeyProvider: this.secretKeyProvider
1753
1932
  };
1754
1933
  }
1755
1934
  async insert(data) {
@@ -2089,7 +2268,7 @@ var QueryError2 = class extends Error {
2089
2268
  };
2090
2269
 
2091
2270
  // src/relations/relation-enforcer.ts
2092
- var import_core11 = require("@korajs/core");
2271
+ var import_core12 = require("@korajs/core");
2093
2272
  init_serializer();
2094
2273
 
2095
2274
  // src/relations/relation-lookup.ts
@@ -2114,7 +2293,7 @@ function getIncomingRelations(lookup, collection) {
2114
2293
  }
2115
2294
 
2116
2295
  // src/relations/relation-enforcer.ts
2117
- var ReferentialIntegrityError = class extends import_core11.KoraError {
2296
+ var ReferentialIntegrityError = class extends import_core12.KoraError {
2118
2297
  constructor(collection, recordId, referencingCollection, relationName, referencingCount) {
2119
2298
  super(
2120
2299
  `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.`,
@@ -2203,7 +2382,7 @@ var RelationEnforcer = class {
2203
2382
  for (const row of referencingRows) {
2204
2383
  const now = Date.now();
2205
2384
  const sequenceNumber = await allocateNextSequenceInTransaction(tx, this.nodeId);
2206
- const operation = await (0, import_core11.createOperation)(
2385
+ const operation = await (0, import_core12.createOperation)(
2207
2386
  {
2208
2387
  nodeId: this.nodeId,
2209
2388
  type: "delete",
@@ -2252,7 +2431,7 @@ var RelationEnforcer = class {
2252
2431
  const sequenceNumber = await allocateNextSequenceInTransaction(tx, this.nodeId);
2253
2432
  const updateData = { [foreignKeyField]: null };
2254
2433
  const previousData = { [foreignKeyField]: deletedRecordId };
2255
- const operation = await (0, import_core11.createOperation)(
2434
+ const operation = await (0, import_core12.createOperation)(
2256
2435
  {
2257
2436
  nodeId: this.nodeId,
2258
2437
  type: "update",
@@ -2313,8 +2492,9 @@ var RelationEnforcer = class {
2313
2492
  };
2314
2493
 
2315
2494
  // src/replay/replay-to.ts
2316
- var import_core12 = require("@korajs/core");
2495
+ var import_core13 = require("@korajs/core");
2317
2496
  var import_internal = require("@korajs/core/internal");
2497
+ init_op_data_encoding();
2318
2498
  function collectCausalClosure(allOps, targetOperationId) {
2319
2499
  const opMap = /* @__PURE__ */ new Map();
2320
2500
  for (const op of allOps) {
@@ -2322,7 +2502,7 @@ function collectCausalClosure(allOps, targetOperationId) {
2322
2502
  }
2323
2503
  const target = opMap.get(targetOperationId);
2324
2504
  if (!target) {
2325
- throw new import_core12.OperationError(
2505
+ throw new import_core13.OperationError(
2326
2506
  `Operation "${targetOperationId}" not found in the local operation log`,
2327
2507
  {
2328
2508
  operationId: targetOperationId
@@ -2354,7 +2534,7 @@ function buildReplaySnapshot(schema, allOps, targetOperationId) {
2354
2534
  const operationsApplied = collectCausalClosure(allOps, targetOperationId);
2355
2535
  const targetOperation = operationsApplied.find((op) => op.id === targetOperationId);
2356
2536
  if (!targetOperation) {
2357
- throw new import_core12.OperationError(`Operation "${targetOperationId}" not found after causal sort`, {
2537
+ throw new import_core13.OperationError(`Operation "${targetOperationId}" not found after causal sort`, {
2358
2538
  operationId: targetOperationId
2359
2539
  });
2360
2540
  }
@@ -2399,7 +2579,9 @@ function applyOperationToMemory(state, op, schema) {
2399
2579
  }
2400
2580
  colMap.set(op.recordId, {
2401
2581
  id: op.recordId,
2402
- fields: { ...op.data },
2582
+ // op.data stores binary richtext as tagged JSON; snapshots must
2583
+ // expose record-shaped values (Uint8Array/string).
2584
+ fields: decodeRichtextFieldsFromOpData(op.data, definition.fields),
2403
2585
  deleted: false,
2404
2586
  createdAt: wallTime,
2405
2587
  updatedAt: wallTime
@@ -2410,9 +2592,10 @@ function applyOperationToMemory(state, op, schema) {
2410
2592
  if (!op.data) {
2411
2593
  return;
2412
2594
  }
2595
+ const decodedData = decodeRichtextFieldsFromOpData(op.data, definition.fields);
2413
2596
  const existing = colMap.get(op.recordId);
2414
2597
  if (existing && !existing.deleted) {
2415
- existing.fields = { ...existing.fields, ...op.data };
2598
+ existing.fields = { ...existing.fields, ...decodedData };
2416
2599
  existing.updatedAt = wallTime;
2417
2600
  break;
2418
2601
  }
@@ -2421,7 +2604,7 @@ function applyOperationToMemory(state, op, schema) {
2421
2604
  }
2422
2605
  colMap.set(op.recordId, {
2423
2606
  id: op.recordId,
2424
- fields: { ...op.data },
2607
+ fields: decodedData,
2425
2608
  deleted: false,
2426
2609
  createdAt: wallTime,
2427
2610
  updatedAt: wallTime
@@ -2477,7 +2660,7 @@ function toCollectionRecord(record, definition) {
2477
2660
  }
2478
2661
 
2479
2662
  // src/sequences/sequence-manager.ts
2480
- var import_core13 = require("@korajs/core");
2663
+ var import_core14 = require("@korajs/core");
2481
2664
  var SequenceManager = class {
2482
2665
  adapter;
2483
2666
  nodeId;
@@ -2495,7 +2678,7 @@ var SequenceManager = class {
2495
2678
  async next(name, config) {
2496
2679
  const scope = config?.scope ?? "";
2497
2680
  const startAt = config?.startAt ?? 1;
2498
- const format = config?.format ?? (0, import_core13.defaultSequenceFormat)(name);
2681
+ const format = config?.format ?? (0, import_core14.defaultSequenceFormat)(name);
2499
2682
  let counter = 0;
2500
2683
  await this.adapter.transaction(async (tx) => {
2501
2684
  const rows = await tx.query(
@@ -2517,7 +2700,7 @@ var SequenceManager = class {
2517
2700
  );
2518
2701
  }
2519
2702
  });
2520
- return (0, import_core13.formatSequenceValue)(format, counter, this.nodeId);
2703
+ return (0, import_core14.formatSequenceValue)(format, counter, this.nodeId);
2521
2704
  }
2522
2705
  /**
2523
2706
  * Get the current counter value without incrementing.
@@ -2975,8 +3158,141 @@ var SubscriptionManager = class {
2975
3158
  }
2976
3159
  };
2977
3160
 
3161
+ // src/sync/rebase-unsynced-operations.ts
3162
+ var import_core15 = require("@korajs/core");
3163
+ var import_internal2 = require("@korajs/core/internal");
3164
+ init_serializer();
3165
+ var LOGICAL_COUNTER_LIMIT = import_core15.MAX_LOGICAL + 1;
3166
+ function emptyResult() {
3167
+ return { operations: [], idMapping: {}, rebasedCount: 0, newMaxTimestamp: null };
3168
+ }
3169
+ async function rebaseUnsyncedOperationsInLog(adapter, schema, unsyncedOpIds, correctedNowMs) {
3170
+ if (unsyncedOpIds.length === 0) {
3171
+ return emptyResult();
3172
+ }
3173
+ const rebaseIds = new Set(unsyncedOpIds);
3174
+ const rebased = [];
3175
+ let maxOtherSerializedTs = null;
3176
+ for (const collectionName of Object.keys(schema.collections)) {
3177
+ const rows = await adapter.query(`SELECT * FROM _kora_ops_${collectionName}`);
3178
+ for (const row of rows) {
3179
+ if (rebaseIds.has(row.id)) {
3180
+ rebased.push(deserializeOperationWithCollection(row, collectionName));
3181
+ } else if (maxOtherSerializedTs === null || row.timestamp > maxOtherSerializedTs) {
3182
+ maxOtherSerializedTs = row.timestamp;
3183
+ }
3184
+ }
3185
+ }
3186
+ if (rebased.length === 0) {
3187
+ return emptyResult();
3188
+ }
3189
+ const maxOtherWallTime = maxOtherSerializedTs === null ? Number.NEGATIVE_INFINITY : import_core15.HybridLogicalClock.deserialize(maxOtherSerializedTs).wallTime;
3190
+ const baseWallTime = Math.max(correctedNowMs, maxOtherWallTime + 1);
3191
+ rebased.sort((a, b) => import_core15.HybridLogicalClock.compare(a.timestamp, b.timestamp));
3192
+ const newTimestamps = rebased.map((op, i) => ({
3193
+ // Spill into wallTime past the 5-digit logical serialization limit so the
3194
+ // serialized order stays correct even for enormous offline queues.
3195
+ wallTime: baseWallTime + Math.floor(i / LOGICAL_COUNTER_LIMIT),
3196
+ logical: i % LOGICAL_COUNTER_LIMIT,
3197
+ nodeId: op.nodeId
3198
+ }));
3199
+ const idMapping = {};
3200
+ const newIds = [];
3201
+ for (let i = 0; i < rebased.length; i++) {
3202
+ const op = rebased[i];
3203
+ const ts = newTimestamps[i];
3204
+ if (!op || !ts) {
3205
+ continue;
3206
+ }
3207
+ const input = {
3208
+ nodeId: op.nodeId,
3209
+ type: op.type,
3210
+ collection: op.collection,
3211
+ recordId: op.recordId,
3212
+ data: op.data,
3213
+ previousData: op.previousData,
3214
+ sequenceNumber: op.sequenceNumber,
3215
+ causalDeps: op.causalDeps,
3216
+ schemaVersion: op.schemaVersion,
3217
+ ...op.atomicOps !== void 0 ? { atomicOps: op.atomicOps } : {}
3218
+ };
3219
+ const newId = await (0, import_internal2.computeOperationId)(input, import_core15.HybridLogicalClock.serialize(ts));
3220
+ idMapping[op.id] = newId;
3221
+ newIds.push(newId);
3222
+ }
3223
+ const newOperations = [];
3224
+ for (let i = 0; i < rebased.length; i++) {
3225
+ const op = rebased[i];
3226
+ const ts = newTimestamps[i];
3227
+ const newId = newIds[i];
3228
+ if (!op || !ts || newId === void 0) {
3229
+ continue;
3230
+ }
3231
+ newOperations.push({
3232
+ ...op,
3233
+ id: newId,
3234
+ timestamp: ts,
3235
+ causalDeps: op.causalDeps.map((dep) => idMapping[dep] ?? dep)
3236
+ });
3237
+ }
3238
+ await adapter.transaction(async (tx) => {
3239
+ for (let i = 0; i < rebased.length; i++) {
3240
+ const oldOp = rebased[i];
3241
+ const newOp = newOperations[i];
3242
+ if (!oldOp || !newOp) {
3243
+ continue;
3244
+ }
3245
+ const opsTable = `_kora_ops_${oldOp.collection}`;
3246
+ await tx.execute(`DELETE FROM ${opsTable} WHERE id = ?`, [oldOp.id]);
3247
+ const opInsert = buildInsertQuery(
3248
+ opsTable,
3249
+ serializeOperation(newOp)
3250
+ );
3251
+ await tx.execute(opInsert.sql, opInsert.params);
3252
+ const oldVersion = serializeRowVersion(oldOp.timestamp);
3253
+ const newVersion = serializeRowVersion(newOp.timestamp);
3254
+ await tx.execute(
3255
+ `UPDATE ${oldOp.collection} SET _version = ?, _updated_at = ? WHERE id = ? AND _version = ?`,
3256
+ [newVersion, newOp.timestamp.wallTime, oldOp.recordId, oldVersion]
3257
+ );
3258
+ const rows = await tx.query(
3259
+ `SELECT _field_versions FROM ${oldOp.collection} WHERE id = ?`,
3260
+ [oldOp.recordId]
3261
+ );
3262
+ const fieldVersions = parseFieldVersions(rows[0]?._field_versions);
3263
+ let changed = false;
3264
+ for (const [field, version] of Object.entries(fieldVersions)) {
3265
+ if (version === oldVersion) {
3266
+ fieldVersions[field] = newVersion;
3267
+ changed = true;
3268
+ }
3269
+ }
3270
+ if (changed) {
3271
+ await tx.execute(`UPDATE ${oldOp.collection} SET _field_versions = ? WHERE id = ?`, [
3272
+ serializeFieldVersions(fieldVersions),
3273
+ oldOp.recordId
3274
+ ]);
3275
+ }
3276
+ if (oldOp.type === "insert") {
3277
+ await tx.execute(
3278
+ `UPDATE ${oldOp.collection} SET _created_at = ? WHERE id = ? AND _created_at = ?`,
3279
+ [newOp.timestamp.wallTime, oldOp.recordId, oldOp.timestamp.wallTime]
3280
+ );
3281
+ }
3282
+ }
3283
+ });
3284
+ const newMaxTimestamp = newTimestamps[newTimestamps.length - 1] ?? null;
3285
+ return {
3286
+ operations: newOperations,
3287
+ idMapping,
3288
+ rebasedCount: newOperations.length,
3289
+ newMaxTimestamp
3290
+ };
3291
+ }
3292
+
2978
3293
  // src/transaction/transaction-context.ts
2979
- var import_core14 = require("@korajs/core");
3294
+ var import_core16 = require("@korajs/core");
3295
+ init_op_data_encoding();
2980
3296
  init_serializer();
2981
3297
  var TransactionContext = class {
2982
3298
  transactionId;
@@ -2987,7 +3303,7 @@ var TransactionContext = class {
2987
3303
  config;
2988
3304
  constructor(config) {
2989
3305
  this.config = config;
2990
- this.transactionId = (0, import_core14.generateUUIDv7)();
3306
+ this.transactionId = (0, import_core16.generateUUIDv7)();
2991
3307
  }
2992
3308
  /**
2993
3309
  * Set a human-readable mutation name for this transaction.
@@ -3102,8 +3418,8 @@ var TransactionContext = class {
3102
3418
  }
3103
3419
  async insert(collectionName, definition, data) {
3104
3420
  this.ensureActive();
3105
- const validated = (0, import_core14.validateRecord)(collectionName, definition, data, "insert");
3106
- const recordId = (0, import_core14.generateUUIDv7)();
3421
+ const validated = (0, import_core16.validateRecord)(collectionName, definition, data, "insert");
3422
+ const recordId = (0, import_core16.generateUUIDv7)();
3107
3423
  const now = Date.now();
3108
3424
  for (const [fieldName, descriptor] of Object.entries(definition.fields)) {
3109
3425
  if (descriptor.auto && descriptor.kind === "timestamp") {
@@ -3112,13 +3428,15 @@ var TransactionContext = class {
3112
3428
  }
3113
3429
  const sequenceNumber = await this.config.sequenceAllocator.allocate();
3114
3430
  const causalDeps = this.config.causalTracker?.nextCausalDeps(collectionName, true) ?? [];
3115
- const operation = await (0, import_core14.createOperation)(
3431
+ const operation = await (0, import_core16.createOperation)(
3116
3432
  {
3117
3433
  nodeId: this.config.nodeId,
3118
3434
  type: "insert",
3119
3435
  collection: collectionName,
3120
3436
  recordId,
3121
- data: { ...validated },
3437
+ // Binary richtext values are tagged as canonical JSON before the
3438
+ // operation is content-hashed (see encodeRichtextFieldsForOpData).
3439
+ data: encodeRichtextFieldsForOpData(validated, definition.fields),
3122
3440
  previousData: null,
3123
3441
  sequenceNumber,
3124
3442
  causalDeps,
@@ -3165,7 +3483,7 @@ var TransactionContext = class {
3165
3483
  if (!currentRecord) {
3166
3484
  throw new RecordNotFoundError(collectionName, id);
3167
3485
  }
3168
- const validated = (0, import_core14.validateRecord)(collectionName, definition, data, "update");
3486
+ const validated = (0, import_core16.validateRecord)(collectionName, definition, data, "update");
3169
3487
  const now = Date.now();
3170
3488
  const previousData = {};
3171
3489
  const resolvedData = {};
@@ -3173,9 +3491,9 @@ var TransactionContext = class {
3173
3491
  for (const key of Object.keys(validated)) {
3174
3492
  const value = validated[key];
3175
3493
  previousData[key] = currentRecord[key];
3176
- if ((0, import_core14.isAtomicOp)(value)) {
3177
- resolvedData[key] = (0, import_core14.resolveAtomicOp)(currentRecord[key], value);
3178
- atomicOps[key] = (0, import_core14.toAtomicOp)(value);
3494
+ if ((0, import_core16.isAtomicOp)(value)) {
3495
+ resolvedData[key] = (0, import_core16.resolveAtomicOp)(currentRecord[key], value);
3496
+ atomicOps[key] = (0, import_core16.toAtomicOp)(value);
3179
3497
  } else {
3180
3498
  resolvedData[key] = value;
3181
3499
  }
@@ -3183,14 +3501,16 @@ var TransactionContext = class {
3183
3501
  const hasAtomicOps = Object.keys(atomicOps).length > 0;
3184
3502
  const sequenceNumber = await this.config.sequenceAllocator.allocate();
3185
3503
  const causalDeps = this.config.causalTracker?.nextCausalDeps(collectionName, true) ?? [];
3186
- const operation = await (0, import_core14.createOperation)(
3504
+ const operation = await (0, import_core16.createOperation)(
3187
3505
  {
3188
3506
  nodeId: this.config.nodeId,
3189
3507
  type: "update",
3190
3508
  collection: collectionName,
3191
3509
  recordId: id,
3192
- data: { ...resolvedData },
3193
- previousData,
3510
+ // Binary richtext values (new and previous) are tagged as canonical
3511
+ // JSON before the operation is content-hashed.
3512
+ data: encodeRichtextFieldsForOpData(resolvedData, definition.fields),
3513
+ previousData: encodeRichtextFieldsForOpData(previousData, definition.fields),
3194
3514
  sequenceNumber,
3195
3515
  causalDeps,
3196
3516
  schemaVersion: this.config.schema.version,
@@ -3236,7 +3556,7 @@ var TransactionContext = class {
3236
3556
  const now = Date.now();
3237
3557
  const sequenceNumber = await this.config.sequenceAllocator.allocate();
3238
3558
  const causalDeps = this.config.causalTracker?.nextCausalDeps(collectionName, true) ?? [];
3239
- const operation = await (0, import_core14.createOperation)(
3559
+ const operation = await (0, import_core16.createOperation)(
3240
3560
  {
3241
3561
  nodeId: this.config.nodeId,
3242
3562
  type: "delete",
@@ -3297,14 +3617,16 @@ var TransactionContext = class {
3297
3617
  if (entry.operation.type === "insert" && entry.operation.data) {
3298
3618
  record = {
3299
3619
  id,
3300
- ...entry.operation.data,
3620
+ // op.data stores binary richtext as tagged JSON; the effective
3621
+ // record must expose record-shaped values (Uint8Array/string).
3622
+ ...decodeRichtextFieldsFromOpData(entry.operation.data, definition.fields),
3301
3623
  createdAt: entry.operation.timestamp.wallTime,
3302
3624
  updatedAt: entry.operation.timestamp.wallTime
3303
3625
  };
3304
3626
  } else if (entry.operation.type === "update" && entry.operation.data && record) {
3305
3627
  record = {
3306
3628
  ...record,
3307
- ...entry.operation.data,
3629
+ ...decodeRichtextFieldsFromOpData(entry.operation.data, definition.fields),
3308
3630
  updatedAt: entry.operation.timestamp.wallTime
3309
3631
  };
3310
3632
  } else if (entry.operation.type === "delete") {
@@ -3339,7 +3661,7 @@ var TransactionSequenceAllocator = class {
3339
3661
  };
3340
3662
 
3341
3663
  // src/store/tab-node-id.ts
3342
- var import_core15 = require("@korajs/core");
3664
+ var import_core17 = require("@korajs/core");
3343
3665
  function perTabStorageKey(dbName) {
3344
3666
  return `kora-tab-node-${dbName}`;
3345
3667
  }
@@ -3360,7 +3682,7 @@ function resolvePerTabNodeId(dbName) {
3360
3682
  if (existing) {
3361
3683
  return existing;
3362
3684
  }
3363
- const nodeId = (0, import_core15.generateUUIDv7)();
3685
+ const nodeId = (0, import_core17.generateUUIDv7)();
3364
3686
  savePerTabNodeId(dbName, nodeId);
3365
3687
  return nodeId;
3366
3688
  }
@@ -3370,7 +3692,7 @@ var Store = class {
3370
3692
  opened = false;
3371
3693
  nodeId = "";
3372
3694
  sequenceNumber = 0;
3373
- versionVector = (0, import_core17.createVersionVector)();
3695
+ versionVector = (0, import_core19.createVersionVector)();
3374
3696
  clock = null;
3375
3697
  collections = /* @__PURE__ */ new Map();
3376
3698
  subscriptionManager;
@@ -3384,6 +3706,7 @@ var Store = class {
3384
3706
  localMutationHandler;
3385
3707
  relationEnforcer = null;
3386
3708
  causalTracker = null;
3709
+ secretKeyProvider;
3387
3710
  constructor(config) {
3388
3711
  this.schema = config.schema;
3389
3712
  this.adapter = config.adapter;
@@ -3392,6 +3715,7 @@ var Store = class {
3392
3715
  this.isolation = config.isolation ?? "shared";
3393
3716
  this.emitter = config.emitter ?? null;
3394
3717
  this.localMutationHandler = config.localMutationHandler ?? null;
3718
+ this.secretKeyProvider = config.secretKeyProvider;
3395
3719
  this.subscriptionManager = new SubscriptionManager({
3396
3720
  onQuerySubscribed: config.onQuerySubscribed
3397
3721
  });
@@ -3404,8 +3728,8 @@ var Store = class {
3404
3728
  await this.adapter.open(this.schema);
3405
3729
  await this.runMigrationsIfNeeded();
3406
3730
  this.nodeId = await this.loadOrGenerateNodeId();
3407
- this.clock = new import_core17.HybridLogicalClock(this.nodeId);
3408
- this.causalTracker = new import_core17.CausalTracker();
3731
+ this.clock = new import_core19.HybridLogicalClock(this.nodeId);
3732
+ this.causalTracker = new import_core19.CausalTracker();
3409
3733
  this.sequenceManager = new SequenceManager(this.adapter, this.nodeId);
3410
3734
  this.sequenceNumber = await this.loadSequenceNumber();
3411
3735
  this.versionVector = await this.loadVersionVector();
@@ -3434,7 +3758,8 @@ var Store = class {
3434
3758
  },
3435
3759
  this.relationEnforcer,
3436
3760
  this.localMutationHandler,
3437
- this.causalTracker
3761
+ this.causalTracker,
3762
+ this.secretKeyProvider
3438
3763
  );
3439
3764
  this.collections.set(name, col);
3440
3765
  }
@@ -3491,6 +3816,14 @@ var Store = class {
3491
3816
  /**
3492
3817
  * Get the node ID for this store instance.
3493
3818
  */
3819
+ /**
3820
+ * Records the offset between server time and this device's clock
3821
+ * (serverTime - localTime), learned at sync handshake. Lets the store's HLC
3822
+ * validate remote timestamps correctly even when the local clock is wrong.
3823
+ */
3824
+ setClockReferenceOffset(offsetMs) {
3825
+ this.clock?.setReferenceOffset(offsetMs);
3826
+ }
3494
3827
  getNodeId() {
3495
3828
  this.ensureOpen();
3496
3829
  return this.nodeId;
@@ -3517,45 +3850,158 @@ var Store = class {
3517
3850
  if (this.clock) {
3518
3851
  this.clock.receive(op.timestamp);
3519
3852
  }
3520
- const remoteVersion = serializeRowVersion(op.timestamp);
3521
- const wallTime = op.timestamp.wallTime;
3853
+ const materializeTimestamp = options?.materializeTimestamp ?? op.timestamp;
3854
+ const remoteVersion = serializeRowVersion(materializeTimestamp);
3855
+ const wallTime = materializeTimestamp.wallTime;
3856
+ const materializeSource = options?.materializeData ?? op.data;
3522
3857
  await this.adapter.transaction(async (tx) => {
3523
- if (op.type === "insert" && op.data) {
3858
+ const checkGuard = (row) => {
3859
+ const guard = options?.guardRowState;
3860
+ if (!guard) {
3861
+ return;
3862
+ }
3863
+ const version = typeof row?._version === "string" ? row._version : null;
3864
+ const fieldVersions = typeof row?._field_versions === "string" ? row._field_versions : null;
3865
+ if (version !== guard.version || fieldVersions !== guard.fieldVersions) {
3866
+ throw new OptimisticLockError(collection, op.recordId);
3867
+ }
3868
+ };
3869
+ if (op.type === "insert" && materializeSource) {
3870
+ const serializedData = serializeRecord(materializeSource, definition.fields);
3524
3871
  const existing2 = await tx.query(
3525
- `SELECT _updated_at, _version FROM ${collection} WHERE id = ?`,
3872
+ `SELECT _updated_at, _version, _field_versions, _deleted FROM ${collection} WHERE id = ?`,
3526
3873
  [op.recordId]
3527
3874
  );
3528
3875
  const row = existing2[0];
3529
- if (row && !isIncomingNewerThanRow(op.timestamp, row)) {
3530
- } else {
3531
- const serializedData = serializeRecord(op.data, definition.fields);
3876
+ checkGuard(row);
3877
+ if (!row) {
3532
3878
  const record = {
3533
3879
  id: op.recordId,
3534
3880
  ...serializedData,
3535
3881
  _created_at: wallTime,
3536
3882
  _updated_at: wallTime,
3537
- _version: remoteVersion
3883
+ _version: remoteVersion,
3884
+ // Stamp every inserted field so per-field LWW has a real baseline.
3885
+ _field_versions: serializeFieldVersions(
3886
+ fieldVersionsForFields(Object.keys(serializedData), remoteVersion)
3887
+ )
3538
3888
  };
3539
3889
  const insertQuery = buildInsertQuery(collection, record);
3540
3890
  await tx.execute(insertQuery.sql, insertQuery.params);
3891
+ await this.foldOrphanedOperations(tx, collection, definition, op.recordId);
3892
+ } else if (row._deleted === 1) {
3893
+ if (isIncomingNewerThanRow(materializeTimestamp, row)) {
3894
+ const fieldChanges = {
3895
+ ...serializedData,
3896
+ _deleted: 0,
3897
+ _field_versions: serializeFieldVersions(
3898
+ fieldVersionsForFields(Object.keys(serializedData), remoteVersion)
3899
+ )
3900
+ };
3901
+ const upsert = buildFieldFastForwardUpdateQuery(
3902
+ collection,
3903
+ op.recordId,
3904
+ fieldChanges,
3905
+ remoteVersion,
3906
+ wallTime,
3907
+ { maxCreatedAt: wallTime }
3908
+ );
3909
+ await tx.execute(upsert.sql, upsert.params);
3910
+ }
3911
+ } else {
3912
+ const currentVersions = parseFieldVersions(row._field_versions);
3913
+ const rowVersion = typeof row._version === "string" ? row._version : void 0;
3914
+ const fieldChanges = {};
3915
+ let fieldVersionsJson;
3916
+ if (options?.forceMaterialize) {
3917
+ const merged = { ...currentVersions };
3918
+ for (const field of Object.keys(serializedData)) {
3919
+ fieldChanges[field] = serializedData[field];
3920
+ merged[field] = remoteVersion;
3921
+ }
3922
+ fieldVersionsJson = serializeFieldVersions(merged);
3923
+ } else {
3924
+ const { winners, merged } = resolvePerFieldLww(
3925
+ currentVersions,
3926
+ Object.keys(serializedData),
3927
+ remoteVersion,
3928
+ rowVersion
3929
+ );
3930
+ for (const field of winners) {
3931
+ fieldChanges[field] = serializedData[field];
3932
+ }
3933
+ fieldVersionsJson = serializeFieldVersions(merged);
3934
+ }
3935
+ const upsert = buildFieldFastForwardUpdateQuery(
3936
+ collection,
3937
+ op.recordId,
3938
+ { ...fieldChanges, _field_versions: fieldVersionsJson },
3939
+ remoteVersion,
3940
+ wallTime,
3941
+ { maxCreatedAt: wallTime }
3942
+ );
3943
+ await tx.execute(upsert.sql, upsert.params);
3541
3944
  }
3542
- } else if (op.type === "update" && op.data) {
3543
- const serializedChanges = serializeRecord(op.data, definition.fields);
3544
- const updatePayload = {
3545
- ...serializedChanges,
3546
- _updated_at: wallTime,
3547
- _version: remoteVersion
3548
- };
3549
- if (options?.reactivateIfDeleted) {
3550
- updatePayload._deleted = 0;
3551
- }
3552
- const updateQuery = buildLwwUpdateQuery(
3553
- collection,
3554
- op.recordId,
3555
- updatePayload,
3556
- remoteVersion
3945
+ } else if (op.type === "update" && materializeSource) {
3946
+ const serializedChanges = serializeRecord(materializeSource, definition.fields);
3947
+ const changedFields = Object.keys(serializedChanges);
3948
+ const currentRows = await tx.query(
3949
+ `SELECT _version, _field_versions FROM ${collection} WHERE id = ?`,
3950
+ [op.recordId]
3557
3951
  );
3558
- await tx.execute(updateQuery.sql, updateQuery.params);
3952
+ const currentRow = currentRows[0];
3953
+ checkGuard(currentRow);
3954
+ if (currentRow) {
3955
+ const currentVersions = parseFieldVersions(currentRow._field_versions);
3956
+ const rowVersion = typeof currentRow._version === "string" ? currentRow._version : void 0;
3957
+ if (options?.forceMaterialize) {
3958
+ const merged = { ...currentVersions };
3959
+ for (const field of changedFields) {
3960
+ merged[field] = remoteVersion;
3961
+ }
3962
+ const fieldChanges = {
3963
+ ...serializedChanges,
3964
+ _field_versions: serializeFieldVersions(merged)
3965
+ };
3966
+ if (options?.reactivateIfDeleted) {
3967
+ fieldChanges._deleted = 0;
3968
+ }
3969
+ const forceQuery = buildFieldFastForwardUpdateQuery(
3970
+ collection,
3971
+ op.recordId,
3972
+ fieldChanges,
3973
+ remoteVersion,
3974
+ wallTime
3975
+ );
3976
+ await tx.execute(forceQuery.sql, forceQuery.params);
3977
+ } else {
3978
+ const { winners, merged } = resolvePerFieldLww(
3979
+ currentVersions,
3980
+ changedFields,
3981
+ remoteVersion,
3982
+ rowVersion
3983
+ );
3984
+ if (winners.length > 0 || options?.reactivateIfDeleted) {
3985
+ const winningChanges = {
3986
+ _field_versions: serializeFieldVersions(merged)
3987
+ };
3988
+ for (const field of winners) {
3989
+ winningChanges[field] = serializedChanges[field];
3990
+ }
3991
+ if (options?.reactivateIfDeleted) {
3992
+ winningChanges._deleted = 0;
3993
+ }
3994
+ const forceQuery = buildFieldFastForwardUpdateQuery(
3995
+ collection,
3996
+ op.recordId,
3997
+ winningChanges,
3998
+ remoteVersion,
3999
+ wallTime
4000
+ );
4001
+ await tx.execute(forceQuery.sql, forceQuery.params);
4002
+ }
4003
+ }
4004
+ }
3559
4005
  } else if (op.type === "delete") {
3560
4006
  const deleteQuery = buildLwwSoftDeleteQuery(
3561
4007
  collection,
@@ -3583,6 +4029,67 @@ var Store = class {
3583
4029
  this.subscriptionManager.notify(collection, op);
3584
4030
  return "applied";
3585
4031
  }
4032
+ /**
4033
+ * Materialize update/delete operations that were logged for a record BEFORE
4034
+ * its insert arrived (reordered delivery). Runs inside the insert's write
4035
+ * transaction, folding each orphan in timestamp order through the exact
4036
+ * per-field LWW / LWW-delete rules the normal apply paths use, so the final
4037
+ * row state equals what a device that received the operations in causal
4038
+ * order computed. Called only when the insert created a previously-absent
4039
+ * row — in that case every logged update/delete for the record is
4040
+ * necessarily an orphan (rows are soft-deleted, never removed, so a row
4041
+ * that is absent now was never materialized).
4042
+ */
4043
+ async foldOrphanedOperations(tx, collection, definition, recordId) {
4044
+ const orphanRows = await tx.query(
4045
+ `SELECT * FROM _kora_ops_${collection} WHERE record_id = ? AND type IN ('update', 'delete')`,
4046
+ [recordId]
4047
+ );
4048
+ if (orphanRows.length === 0) {
4049
+ return;
4050
+ }
4051
+ const orphans = orphanRows.map((r) => deserializeOperationWithCollection(r, collection)).sort((a, b) => import_core19.HybridLogicalClock.compare(a.timestamp, b.timestamp));
4052
+ for (const orphan of orphans) {
4053
+ const orphanVersion = serializeRowVersion(orphan.timestamp);
4054
+ const orphanWall = orphan.timestamp.wallTime;
4055
+ if (orphan.type === "update" && orphan.data) {
4056
+ const orphanChanges = serializeRecord(orphan.data, definition.fields);
4057
+ const rows = await tx.query(
4058
+ `SELECT _version, _field_versions FROM ${collection} WHERE id = ?`,
4059
+ [recordId]
4060
+ );
4061
+ const current = rows[0];
4062
+ if (!current) {
4063
+ continue;
4064
+ }
4065
+ const { winners, merged } = resolvePerFieldLww(
4066
+ parseFieldVersions(current._field_versions),
4067
+ Object.keys(orphanChanges),
4068
+ orphanVersion,
4069
+ typeof current._version === "string" ? current._version : void 0
4070
+ );
4071
+ if (winners.length > 0) {
4072
+ const winningChanges = {
4073
+ _field_versions: serializeFieldVersions(merged)
4074
+ };
4075
+ for (const field of winners) {
4076
+ winningChanges[field] = orphanChanges[field];
4077
+ }
4078
+ const query = buildFieldFastForwardUpdateQuery(
4079
+ collection,
4080
+ recordId,
4081
+ winningChanges,
4082
+ orphanVersion,
4083
+ orphanWall
4084
+ );
4085
+ await tx.execute(query.sql, query.params);
4086
+ }
4087
+ } else if (orphan.type === "delete") {
4088
+ const query = buildLwwSoftDeleteQuery(collection, recordId, orphanWall, orphanVersion);
4089
+ await tx.execute(query.sql, query.params);
4090
+ }
4091
+ }
4092
+ }
3586
4093
  /**
3587
4094
  * Get operations from a node within a sequence number range.
3588
4095
  * Implements the OperationLog interface for computeDelta.
@@ -3713,7 +4220,8 @@ var Store = class {
3713
4220
  relationEnforcer: this.relationEnforcer,
3714
4221
  causalTracker: this.causalTracker,
3715
4222
  inTransaction: options?.inTransaction ?? false,
3716
- extraCausalDeps: options?.extraCausalDeps
4223
+ extraCausalDeps: options?.extraCausalDeps,
4224
+ secretKeyProvider: this.secretKeyProvider
3717
4225
  };
3718
4226
  }
3719
4227
  /**
@@ -3738,6 +4246,31 @@ var Store = class {
3738
4246
  deleted: row._deleted === 1
3739
4247
  };
3740
4248
  }
4249
+ /**
4250
+ * Raw version state of a materialized row, for optimistic-concurrency guarded
4251
+ * applies: capture this snapshot before computing a merge, pass it as
4252
+ * `guardRowState`, and the apply refuses to write if the row changed since.
4253
+ * Returns null when no row exists (which is itself a valid guard state — the
4254
+ * apply then requires the row to still be absent).
4255
+ */
4256
+ async getRowVersionState(collection, recordId) {
4257
+ this.ensureOpen();
4258
+ if (!this.schema.collections[collection]) {
4259
+ return null;
4260
+ }
4261
+ const rows = await this.adapter.query(
4262
+ `SELECT _version, _field_versions FROM ${collection} WHERE id = ?`,
4263
+ [recordId]
4264
+ );
4265
+ const row = rows[0];
4266
+ if (!row) {
4267
+ return null;
4268
+ }
4269
+ return {
4270
+ version: typeof row._version === "string" ? row._version : null,
4271
+ fieldVersions: typeof row._field_versions === "string" ? row._field_versions : null
4272
+ };
4273
+ }
3741
4274
  /**
3742
4275
  * Latest operation from this device for a record (used for delete-vs-update merge on sync).
3743
4276
  */
@@ -3780,6 +4313,30 @@ var Store = class {
3780
4313
  (nodeId, fromSeq, toSeq) => this.getOperationRange(nodeId, fromSeq, toSeq)
3781
4314
  );
3782
4315
  }
4316
+ /**
4317
+ * Re-stamp never-acknowledged local operations after a fast device clock was
4318
+ * corrected, so sync can resume immediately. Safe because unacknowledged
4319
+ * operations are private to this device (like unpushed git commits);
4320
+ * acknowledged/shared operations are immutable and never touched.
4321
+ *
4322
+ * After the rewrite the store's HLC is advanced past the highest new
4323
+ * timestamp so subsequent local writes keep sorting after the rebased ops.
4324
+ * No data-change notifications are emitted: materialized values are
4325
+ * unchanged, only version stamps move.
4326
+ */
4327
+ async rebaseUnsyncedOperations(unsyncedOpIds, correctedNowMs) {
4328
+ this.ensureOpen();
4329
+ const result = await rebaseUnsyncedOperationsInLog(
4330
+ this.adapter,
4331
+ this.schema,
4332
+ unsyncedOpIds,
4333
+ correctedNowMs
4334
+ );
4335
+ if (result.newMaxTimestamp && this.clock) {
4336
+ this.clock.advanceTo(result.newMaxTimestamp);
4337
+ }
4338
+ return result;
4339
+ }
3783
4340
  /**
3784
4341
  * Count of local operations ahead of the server version vector.
3785
4342
  */
@@ -3794,7 +4351,7 @@ var Store = class {
3794
4351
  async compact(strategy) {
3795
4352
  this.ensureOpen();
3796
4353
  if (strategy.mode === "never") {
3797
- return compactOperationLog(this.adapter, this.schema, strategy, (0, import_core17.createVersionVector)());
4354
+ return compactOperationLog(this.adapter, this.schema, strategy, (0, import_core19.createVersionVector)());
3798
4355
  }
3799
4356
  const serverVector = strategy.serverVector ?? await loadLastAckedServerVector(this.adapter);
3800
4357
  return compactOperationLog(this.adapter, this.schema, strategy, serverVector);
@@ -3829,7 +4386,7 @@ var Store = class {
3829
4386
  let latest = null;
3830
4387
  for (const row of rows) {
3831
4388
  const op = deserializeOperationWithCollection(row, collection);
3832
- if (!latest || import_core17.HybridLogicalClock.compare(op.timestamp, latest.timestamp) > 0) {
4389
+ if (!latest || import_core19.HybridLogicalClock.compare(op.timestamp, latest.timestamp) > 0) {
3833
4390
  latest = op;
3834
4391
  }
3835
4392
  }
@@ -3972,7 +4529,7 @@ var Store = class {
3972
4529
  for (let v = storedVersion + 1; v <= targetVersion; v++) {
3973
4530
  const migration = migrations[v];
3974
4531
  if (!migration) continue;
3975
- const sqlStatements = (0, import_core17.migrationStepsToSQL)(migration.steps);
4532
+ const sqlStatements = (0, import_core19.migrationStepsToSQL)(migration.steps);
3976
4533
  for (const sql of sqlStatements) {
3977
4534
  try {
3978
4535
  await this.adapter.execute(sql);
@@ -4050,7 +4607,7 @@ var Store = class {
4050
4607
  if (rows[0]) {
4051
4608
  return rows[0].value;
4052
4609
  }
4053
- const newNodeId = (0, import_core17.generateUUIDv7)();
4610
+ const newNodeId = (0, import_core19.generateUUIDv7)();
4054
4611
  await this.adapter.execute("INSERT INTO _kora_meta (key, value) VALUES ('node_id', ?)", [
4055
4612
  newNodeId
4056
4613
  ]);
@@ -4067,7 +4624,7 @@ var Store = class {
4067
4624
  const rows = await this.adapter.query(
4068
4625
  "SELECT node_id, sequence_number FROM _kora_version_vector"
4069
4626
  );
4070
- const vector = (0, import_core17.createVersionVector)();
4627
+ const vector = (0, import_core19.createVersionVector)();
4071
4628
  for (const row of rows) {
4072
4629
  vector.set(row.node_id, row.sequence_number);
4073
4630
  }
@@ -4141,12 +4698,12 @@ var QueryStore = class {
4141
4698
  };
4142
4699
 
4143
4700
  // src/reactivity/assert-query-ready.ts
4144
- var import_core18 = require("@korajs/core");
4701
+ var import_core20 = require("@korajs/core");
4145
4702
  var LEGACY_PENDING_COLLECTION = "__pending__";
4146
4703
  function assertQueryReady(query) {
4147
4704
  const descriptor = query.getDescriptor();
4148
4705
  if (descriptor.collection === LEGACY_PENDING_COLLECTION) {
4149
- throw new import_core18.AppNotReadyError(
4706
+ throw new import_core20.AppNotReadyError(
4150
4707
  "Cannot use useQuery() before app.ready. Await app.ready or wrap your UI in <KoraProvider app={app}>."
4151
4708
  );
4152
4709
  }
@@ -4567,13 +5124,570 @@ init_backup();
4567
5124
  init_audit_trace_store();
4568
5125
  init_audit_json();
4569
5126
  init_export_audit();
5127
+
5128
+ // src/blob/content-addressed-blob-store.ts
5129
+ var import_core21 = require("@korajs/core");
5130
+ var BlobIntegrityError = class extends Error {
5131
+ constructor(expectedHash, actualHash) {
5132
+ super(`Blob integrity check failed: content under hash ${expectedHash} hashes to ${actualHash}`);
5133
+ this.expectedHash = expectedHash;
5134
+ this.actualHash = actualHash;
5135
+ this.name = "BlobIntegrityError";
5136
+ }
5137
+ expectedHash;
5138
+ actualHash;
5139
+ };
5140
+ var MemoryBlobStore = class {
5141
+ blobs = /* @__PURE__ */ new Map();
5142
+ async put(bytes, metadata = {}) {
5143
+ const ref = await (0, import_core21.createBlobRef)(bytes, metadata);
5144
+ if (!this.blobs.has(ref.hash)) {
5145
+ const stored = new Uint8Array(bytes.byteLength);
5146
+ stored.set(bytes);
5147
+ this.blobs.set(ref.hash, stored);
5148
+ }
5149
+ return ref;
5150
+ }
5151
+ async get(hash) {
5152
+ const stored = this.blobs.get(hash);
5153
+ if (stored === void 0) {
5154
+ return null;
5155
+ }
5156
+ const actualHash = await (0, import_core21.hashBlob)(stored);
5157
+ if (actualHash !== hash) {
5158
+ throw new BlobIntegrityError(hash, actualHash);
5159
+ }
5160
+ const out = new Uint8Array(stored.byteLength);
5161
+ out.set(stored);
5162
+ return out;
5163
+ }
5164
+ async has(hash) {
5165
+ return this.blobs.has(hash);
5166
+ }
5167
+ async delete(hash) {
5168
+ return this.blobs.delete(hash);
5169
+ }
5170
+ async size() {
5171
+ return this.blobs.size;
5172
+ }
5173
+ async list() {
5174
+ return [...this.blobs.keys()];
5175
+ }
5176
+ };
5177
+
5178
+ // src/blob/opfs-blob-store.ts
5179
+ var import_core22 = require("@korajs/core");
5180
+ var OpfsBlobStore = class {
5181
+ constructor(dir) {
5182
+ this.dir = dir;
5183
+ }
5184
+ dir;
5185
+ shardFor(hash) {
5186
+ return hash.slice(0, 2);
5187
+ }
5188
+ async put(bytes, metadata = {}) {
5189
+ const ref = await (0, import_core22.createBlobRef)(bytes, metadata);
5190
+ const shard = this.shardFor(ref.hash);
5191
+ if (await this.dir.has(shard, ref.hash)) {
5192
+ return ref;
5193
+ }
5194
+ const out = new Uint8Array(bytes.byteLength);
5195
+ out.set(bytes);
5196
+ await this.dir.write(shard, ref.hash, out);
5197
+ return ref;
5198
+ }
5199
+ async get(hash) {
5200
+ const bytes = await this.dir.read(this.shardFor(hash), hash);
5201
+ if (bytes === null) {
5202
+ return null;
5203
+ }
5204
+ const actual = await (0, import_core22.hashBlob)(bytes);
5205
+ if (actual !== hash) {
5206
+ throw new BlobIntegrityError(hash, actual);
5207
+ }
5208
+ return bytes;
5209
+ }
5210
+ async has(hash) {
5211
+ return this.dir.has(this.shardFor(hash), hash);
5212
+ }
5213
+ async delete(hash) {
5214
+ return this.dir.remove(this.shardFor(hash), hash);
5215
+ }
5216
+ async size() {
5217
+ return this.dir.count();
5218
+ }
5219
+ async list() {
5220
+ return this.dir.keys();
5221
+ }
5222
+ };
5223
+ function getStorageManager() {
5224
+ const nav = globalThis.navigator;
5225
+ if (!nav?.storage || typeof nav.storage.getDirectory !== "function") {
5226
+ throw new Error(
5227
+ "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."
5228
+ );
5229
+ }
5230
+ return nav.storage;
5231
+ }
5232
+ function isNotFoundError(error) {
5233
+ return typeof error === "object" && error !== null && error.name === "NotFoundError";
5234
+ }
5235
+ async function createOpfsBlobDirectory(rootDirName = "kora-blobs") {
5236
+ const storage = getStorageManager();
5237
+ if (typeof storage.persist === "function") {
5238
+ try {
5239
+ await storage.persist();
5240
+ } catch {
5241
+ }
5242
+ }
5243
+ const opfsRoot = await storage.getDirectory();
5244
+ const root = await opfsRoot.getDirectoryHandle(rootDirName, { create: true });
5245
+ async function shardHandle(shard, create) {
5246
+ try {
5247
+ return await root.getDirectoryHandle(shard, { create });
5248
+ } catch (error) {
5249
+ if (isNotFoundError(error)) {
5250
+ return null;
5251
+ }
5252
+ throw error;
5253
+ }
5254
+ }
5255
+ return {
5256
+ async read(shard, name) {
5257
+ const dir = await shardHandle(shard, false);
5258
+ if (!dir) {
5259
+ return null;
5260
+ }
5261
+ let handle;
5262
+ try {
5263
+ handle = await dir.getFileHandle(name, { create: false });
5264
+ } catch (error) {
5265
+ if (isNotFoundError(error)) {
5266
+ return null;
5267
+ }
5268
+ throw error;
5269
+ }
5270
+ const file = await handle.getFile();
5271
+ return new Uint8Array(await file.arrayBuffer());
5272
+ },
5273
+ async write(shard, name, bytes) {
5274
+ const dir = await root.getDirectoryHandle(shard, { create: true });
5275
+ const handle = await dir.getFileHandle(name, { create: true });
5276
+ const writable = await handle.createWritable({ keepExistingData: false });
5277
+ try {
5278
+ await writable.write(bytes);
5279
+ } finally {
5280
+ await writable.close();
5281
+ }
5282
+ },
5283
+ async remove(shard, name) {
5284
+ const dir = await shardHandle(shard, false);
5285
+ if (!dir) {
5286
+ return false;
5287
+ }
5288
+ try {
5289
+ await dir.removeEntry(name);
5290
+ return true;
5291
+ } catch (error) {
5292
+ if (isNotFoundError(error)) {
5293
+ return false;
5294
+ }
5295
+ throw error;
5296
+ }
5297
+ },
5298
+ async has(shard, name) {
5299
+ const dir = await shardHandle(shard, false);
5300
+ if (!dir) {
5301
+ return false;
5302
+ }
5303
+ try {
5304
+ await dir.getFileHandle(name, { create: false });
5305
+ return true;
5306
+ } catch (error) {
5307
+ if (isNotFoundError(error)) {
5308
+ return false;
5309
+ }
5310
+ throw error;
5311
+ }
5312
+ },
5313
+ async count() {
5314
+ let total = 0;
5315
+ for await (const shard of root.keys()) {
5316
+ const dir = await shardHandle(shard, false);
5317
+ if (!dir) {
5318
+ continue;
5319
+ }
5320
+ for await (const name of dir.keys()) {
5321
+ if (!name.endsWith(".tmp")) {
5322
+ total++;
5323
+ }
5324
+ }
5325
+ }
5326
+ return total;
5327
+ },
5328
+ async keys() {
5329
+ const names = [];
5330
+ for await (const shard of root.keys()) {
5331
+ const dir = await shardHandle(shard, false);
5332
+ if (!dir) {
5333
+ continue;
5334
+ }
5335
+ for await (const name of dir.keys()) {
5336
+ if (!name.endsWith(".tmp")) {
5337
+ names.push(name);
5338
+ }
5339
+ }
5340
+ }
5341
+ return names;
5342
+ }
5343
+ };
5344
+ }
5345
+ async function createOpfsBlobStore(rootDirName = "kora-blobs") {
5346
+ return new OpfsBlobStore(await createOpfsBlobDirectory(rootDirName));
5347
+ }
5348
+
5349
+ // src/blob/blob-chunking.ts
5350
+ var import_core23 = require("@korajs/core");
5351
+ var DEFAULT_CHUNK_SIZE = 256 * 1024;
5352
+ async function chunkBlob(bytes, chunkSize = DEFAULT_CHUNK_SIZE, metadata = {}) {
5353
+ if (chunkSize <= 0) {
5354
+ throw new Error(`chunkBlob requires a positive chunkSize, got ${chunkSize}`);
5355
+ }
5356
+ const chunkHashes = [];
5357
+ const chunks = /* @__PURE__ */ new Map();
5358
+ for (let offset = 0; offset < bytes.byteLength; offset += chunkSize) {
5359
+ const end = Math.min(offset + chunkSize, bytes.byteLength);
5360
+ const chunk = new Uint8Array(end - offset);
5361
+ chunk.set(bytes.subarray(offset, end));
5362
+ const hash = await (0, import_core23.hashBlob)(chunk);
5363
+ chunkHashes.push(hash);
5364
+ if (!chunks.has(hash)) {
5365
+ chunks.set(hash, chunk);
5366
+ }
5367
+ }
5368
+ const blobHash = await (0, import_core23.hashBlob)(bytes);
5369
+ const manifest = {
5370
+ blobHash,
5371
+ size: bytes.byteLength,
5372
+ chunkSize,
5373
+ chunkHashes
5374
+ };
5375
+ if (metadata.mimeType !== void 0) {
5376
+ manifest.mimeType = metadata.mimeType;
5377
+ }
5378
+ if (metadata.filename !== void 0) {
5379
+ manifest.filename = metadata.filename;
5380
+ }
5381
+ return { manifest, chunks };
5382
+ }
5383
+ async function reassembleBlob(manifest, chunkStore) {
5384
+ const parts = [];
5385
+ let total = 0;
5386
+ for (const hash of manifest.chunkHashes) {
5387
+ const chunk = await chunkStore.get(hash);
5388
+ if (chunk === null) {
5389
+ throw new Error(`Cannot reassemble blob ${manifest.blobHash}: missing chunk ${hash}`);
5390
+ }
5391
+ parts.push(chunk);
5392
+ total += chunk.byteLength;
5393
+ }
5394
+ const out = new Uint8Array(total);
5395
+ let offset = 0;
5396
+ for (const part of parts) {
5397
+ out.set(part, offset);
5398
+ offset += part.byteLength;
5399
+ }
5400
+ const actual = await (0, import_core23.hashBlob)(out);
5401
+ if (actual !== manifest.blobHash) {
5402
+ throw new BlobIntegrityError(manifest.blobHash, actual);
5403
+ }
5404
+ return out;
5405
+ }
5406
+
5407
+ // src/blob/blob-transfer.ts
5408
+ var import_core24 = require("@korajs/core");
5409
+ async function receiveBlob(manifest, provider, stores) {
5410
+ let chunksFetched = 0;
5411
+ let chunksSkipped = 0;
5412
+ const seen = /* @__PURE__ */ new Set();
5413
+ for (const hash of manifest.chunkHashes) {
5414
+ if (seen.has(hash)) {
5415
+ continue;
5416
+ }
5417
+ seen.add(hash);
5418
+ if (await stores.chunkStore.has(hash)) {
5419
+ chunksSkipped++;
5420
+ continue;
5421
+ }
5422
+ const bytes = await provider.getChunk(hash);
5423
+ if (bytes === null) {
5424
+ throw new Error(`Blob transfer failed: provider could not supply chunk ${hash}`);
5425
+ }
5426
+ const actual = await (0, import_core24.hashBlob)(bytes);
5427
+ if (actual !== hash) {
5428
+ throw new BlobIntegrityError(hash, actual);
5429
+ }
5430
+ await stores.chunkStore.put(bytes);
5431
+ chunksFetched++;
5432
+ }
5433
+ const full = await reassembleBlob(manifest, stores.chunkStore);
5434
+ const ref = await stores.blobStore.put(full, {
5435
+ mimeType: manifest.mimeType,
5436
+ filename: manifest.filename
5437
+ });
5438
+ return { ref, chunksFetched, chunksSkipped };
5439
+ }
5440
+ async function prepareBlobForSend(bytes, chunkStore, options = {}) {
5441
+ const { manifest, chunks } = await chunkBlob(bytes, options.chunkSize, {
5442
+ mimeType: options.mimeType,
5443
+ filename: options.filename
5444
+ });
5445
+ for (const chunk of chunks.values()) {
5446
+ await chunkStore.put(chunk);
5447
+ }
5448
+ const provider = {
5449
+ getChunk: (hash) => chunkStore.get(hash)
5450
+ };
5451
+ return { manifest, provider };
5452
+ }
5453
+
5454
+ // src/blob/blob-manifest-transfer.ts
5455
+ var import_core25 = require("@korajs/core");
5456
+ function serializeBlobManifest(manifest) {
5457
+ const canonical = {
5458
+ blobHash: manifest.blobHash,
5459
+ size: manifest.size,
5460
+ chunkSize: manifest.chunkSize,
5461
+ chunkHashes: manifest.chunkHashes
5462
+ };
5463
+ if (manifest.mimeType !== void 0) {
5464
+ canonical.mimeType = manifest.mimeType;
5465
+ }
5466
+ if (manifest.filename !== void 0) {
5467
+ canonical.filename = manifest.filename;
5468
+ }
5469
+ return new TextEncoder().encode(JSON.stringify(canonical));
5470
+ }
5471
+ function parseBlobManifest(bytes) {
5472
+ const parsed = JSON.parse(new TextDecoder().decode(bytes));
5473
+ if (typeof parsed !== "object" || parsed === null) {
5474
+ throw new Error("Invalid blob manifest: not an object");
5475
+ }
5476
+ const obj = parsed;
5477
+ if (typeof obj.blobHash !== "string") {
5478
+ throw new Error("Invalid blob manifest: missing blobHash");
5479
+ }
5480
+ if (typeof obj.size !== "number" || typeof obj.chunkSize !== "number") {
5481
+ throw new Error("Invalid blob manifest: missing size or chunkSize");
5482
+ }
5483
+ if (!Array.isArray(obj.chunkHashes) || obj.chunkHashes.some((h) => typeof h !== "string")) {
5484
+ throw new Error("Invalid blob manifest: chunkHashes must be an array of strings");
5485
+ }
5486
+ const manifest = {
5487
+ blobHash: obj.blobHash,
5488
+ size: obj.size,
5489
+ chunkSize: obj.chunkSize,
5490
+ chunkHashes: obj.chunkHashes
5491
+ };
5492
+ if (typeof obj.mimeType === "string") {
5493
+ manifest.mimeType = obj.mimeType;
5494
+ }
5495
+ if (typeof obj.filename === "string") {
5496
+ manifest.filename = obj.filename;
5497
+ }
5498
+ return manifest;
5499
+ }
5500
+ async function putBlobForTransfer(store, bytes, options = {}) {
5501
+ const { manifest, chunks } = await chunkBlob(bytes, options.chunkSize, {
5502
+ ...options.mimeType !== void 0 ? { mimeType: options.mimeType } : {},
5503
+ ...options.filename !== void 0 ? { filename: options.filename } : {}
5504
+ });
5505
+ for (const chunk of chunks.values()) {
5506
+ await store.put(chunk);
5507
+ }
5508
+ const baseRef = await store.put(bytes, {
5509
+ ...options.mimeType !== void 0 ? { mimeType: options.mimeType } : {},
5510
+ ...options.filename !== void 0 ? { filename: options.filename } : {}
5511
+ });
5512
+ const manifestRef = await store.put(serializeBlobManifest(manifest));
5513
+ return { ref: { ...baseRef, manifestHash: manifestRef.hash }, manifest };
5514
+ }
5515
+ async function fetchBlobManifest(provider, manifestHash) {
5516
+ const bytes = await provider.getChunk(manifestHash);
5517
+ if (bytes === null) {
5518
+ throw new Error(`Blob manifest ${manifestHash} could not be fetched from the provider`);
5519
+ }
5520
+ const actual = await (0, import_core25.hashBlob)(bytes);
5521
+ if (actual !== manifestHash) {
5522
+ throw new BlobIntegrityError(manifestHash, actual);
5523
+ }
5524
+ return parseBlobManifest(bytes);
5525
+ }
5526
+ async function resolveBlobManifest(provider, ref) {
5527
+ if (ref.manifestHash === void 0) {
5528
+ throw new Error(
5529
+ `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.`
5530
+ );
5531
+ }
5532
+ return fetchBlobManifest(provider, ref.manifestHash);
5533
+ }
5534
+
5535
+ // src/blob/server-blob-callbacks.ts
5536
+ var import_core26 = require("@korajs/core");
5537
+ function toServerBlobCallbacks(store) {
5538
+ return {
5539
+ resolveBlobChunk: (hash) => store.get(hash),
5540
+ async persistBlobChunk(hash, bytes) {
5541
+ const actual = await (0, import_core26.hashBlob)(bytes);
5542
+ if (actual !== hash) {
5543
+ return;
5544
+ }
5545
+ await store.put(bytes);
5546
+ }
5547
+ };
5548
+ }
5549
+ function createMemoryServerBlobStore() {
5550
+ const store = new MemoryBlobStore();
5551
+ return { store, callbacks: toServerBlobCallbacks(store) };
5552
+ }
5553
+
5554
+ // src/blob/blob-gc.ts
5555
+ var import_core27 = require("@korajs/core");
5556
+ function extractBlobRefs(record) {
5557
+ const refs = [];
5558
+ for (const value of Object.values(record)) {
5559
+ if ((0, import_core27.isBlobRef)(value)) {
5560
+ refs.push(value);
5561
+ }
5562
+ }
5563
+ return refs;
5564
+ }
5565
+ async function collectBlobGarbage(store, liveRefs, options = {}) {
5566
+ const live = /* @__PURE__ */ new Set();
5567
+ for (const ref of liveRefs) {
5568
+ live.add(ref.hash);
5569
+ if (ref.manifestHash === void 0) {
5570
+ continue;
5571
+ }
5572
+ live.add(ref.manifestHash);
5573
+ try {
5574
+ const manifestBytes = await store.get(ref.manifestHash);
5575
+ if (manifestBytes !== null) {
5576
+ const manifest = parseBlobManifest(manifestBytes);
5577
+ for (const chunkHash of manifest.chunkHashes) {
5578
+ live.add(chunkHash);
5579
+ }
5580
+ }
5581
+ } catch {
5582
+ }
5583
+ }
5584
+ const all = await store.list();
5585
+ const collectedHashes = [];
5586
+ for (const hash of all) {
5587
+ if (live.has(hash)) {
5588
+ continue;
5589
+ }
5590
+ collectedHashes.push(hash);
5591
+ if (!options.dryRun) {
5592
+ await store.delete(hash);
5593
+ }
5594
+ }
5595
+ return {
5596
+ scanned: all.length,
5597
+ live: live.size,
5598
+ collected: collectedHashes.length,
5599
+ collectedHashes
5600
+ };
5601
+ }
5602
+
5603
+ // src/blob/blob-chunk-transport.ts
5604
+ var import_core28 = require("@korajs/core");
5605
+ function createRemoteChunkProvider(port, options = {}) {
5606
+ const timeoutMs = options.timeoutMs ?? 3e4;
5607
+ const pending = /* @__PURE__ */ new Map();
5608
+ port.onMessage((message) => {
5609
+ if (message.type !== "blob-chunk-response") {
5610
+ return;
5611
+ }
5612
+ const entry = pending.get(message.requestId);
5613
+ if (!entry) {
5614
+ return;
5615
+ }
5616
+ pending.delete(message.requestId);
5617
+ clearTimeout(entry.timer);
5618
+ entry.resolve(message.bytes);
5619
+ });
5620
+ return {
5621
+ pendingCount: () => pending.size,
5622
+ getChunk(hash) {
5623
+ const requestId = (0, import_core28.generateUUIDv7)();
5624
+ return new Promise((resolve, reject) => {
5625
+ const timer = setTimeout(() => {
5626
+ pending.delete(requestId);
5627
+ reject(new Error(`Blob chunk request for ${hash} timed out after ${timeoutMs}ms`));
5628
+ }, timeoutMs);
5629
+ pending.set(requestId, { resolve, reject, timer });
5630
+ port.send({ type: "blob-chunk-request", requestId, hash });
5631
+ });
5632
+ }
5633
+ };
5634
+ }
5635
+ function serveBlobChunks(port, blobStore) {
5636
+ port.onMessage((message) => {
5637
+ if (message.type !== "blob-chunk-request") {
5638
+ return;
5639
+ }
5640
+ void blobStore.get(message.hash).then(
5641
+ (bytes) => {
5642
+ port.send({ type: "blob-chunk-response", requestId: message.requestId, bytes });
5643
+ },
5644
+ () => {
5645
+ port.send({ type: "blob-chunk-response", requestId: message.requestId, bytes: null });
5646
+ }
5647
+ );
5648
+ });
5649
+ }
5650
+ function createChunkPortPair() {
5651
+ const handlersA = [];
5652
+ const handlersB = [];
5653
+ const a = {
5654
+ send(message) {
5655
+ queueMicrotask(() => {
5656
+ for (const handler of handlersB) {
5657
+ handler(message);
5658
+ }
5659
+ });
5660
+ },
5661
+ onMessage(handler) {
5662
+ handlersA.push(handler);
5663
+ }
5664
+ };
5665
+ const b = {
5666
+ send(message) {
5667
+ queueMicrotask(() => {
5668
+ for (const handler of handlersA) {
5669
+ handler(message);
5670
+ }
5671
+ });
5672
+ },
5673
+ onMessage(handler) {
5674
+ handlersB.push(handler);
5675
+ }
5676
+ };
5677
+ return { a, b };
5678
+ }
4570
5679
  // Annotate the CommonJS export names for ESM import in node:
4571
5680
  0 && (module.exports = {
4572
5681
  AdapterError,
5682
+ BlobIntegrityError,
4573
5683
  COMPACTION_BASELINE_META_KEY,
4574
5684
  Collection,
5685
+ DEFAULT_CHUNK_SIZE,
4575
5686
  InvalidStateTransitionError,
4576
5687
  LAST_ACKED_SERVER_VECTOR_META_KEY,
5688
+ MemoryBlobStore,
5689
+ OpfsBlobStore,
5690
+ OptimisticLockError,
4577
5691
  PersistenceError,
4578
5692
  QueryBuilder,
4579
5693
  QueryError,
@@ -4590,26 +5704,44 @@ init_export_audit();
4590
5704
  WorkerTimeoutError,
4591
5705
  asRichTextSyncEngine,
4592
5706
  assertQueryReady,
5707
+ chunkBlob,
5708
+ collectBlobGarbage,
4593
5709
  collectOperationsAheadOfServer,
4594
5710
  compactOperationLog,
4595
5711
  computeAckCompactionWatermark,
5712
+ createChunkPortPair,
5713
+ createMemoryServerBlobStore,
5714
+ createOpfsBlobDirectory,
5715
+ createOpfsBlobStore,
5716
+ createRemoteChunkProvider,
4596
5717
  createRichTextController,
4597
5718
  decodeAuditExport,
4598
5719
  decodeRichtext,
4599
5720
  deserializeVersionVectorFromMeta,
4600
5721
  encodeRichtext,
4601
5722
  exportBackup,
5723
+ extractBlobRefs,
5724
+ fetchBlobManifest,
4602
5725
  getSharedQueryStoreCache,
4603
5726
  mergeVersionVectors,
5727
+ parseBlobManifest,
4604
5728
  persistedAuditTraceFromEvent,
4605
5729
  pluralize,
5730
+ prepareBlobForSend,
5731
+ putBlobForTransfer,
4606
5732
  readAuditExportManifest,
4607
5733
  readBackupManifest,
5734
+ reassembleBlob,
5735
+ receiveBlob,
5736
+ resolveBlobManifest,
4608
5737
  restoreBackup,
4609
5738
  richtextStatesEqual,
4610
5739
  richtextToPlainText,
5740
+ serializeBlobManifest,
4611
5741
  serializeVersionVectorToMeta,
5742
+ serveBlobChunks,
4612
5743
  singularize,
5744
+ toServerBlobCallbacks,
4613
5745
  validateStateTransition,
4614
5746
  validateUpdateStateMachine,
4615
5747
  verifyAuditExportChecksum,