@korajs/store 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/dist/adapters/better-sqlite3.d.cts +1 -1
  2. package/dist/adapters/better-sqlite3.d.ts +1 -1
  3. package/dist/adapters/indexeddb.cjs +410 -17
  4. package/dist/adapters/indexeddb.cjs.map +1 -1
  5. package/dist/adapters/indexeddb.d.cts +23 -5
  6. package/dist/adapters/indexeddb.d.ts +23 -5
  7. package/dist/adapters/indexeddb.js +163 -18
  8. package/dist/adapters/indexeddb.js.map +1 -1
  9. package/dist/adapters/sqlite-wasm-shared-host.cjs +68 -0
  10. package/dist/adapters/sqlite-wasm-shared-host.cjs.map +1 -0
  11. package/dist/adapters/sqlite-wasm-shared-host.d.cts +2 -0
  12. package/dist/adapters/sqlite-wasm-shared-host.d.ts +2 -0
  13. package/dist/adapters/sqlite-wasm-shared-host.js +66 -0
  14. package/dist/adapters/sqlite-wasm-shared-host.js.map +1 -0
  15. package/dist/adapters/sqlite-wasm-worker.cjs +34 -5
  16. package/dist/adapters/sqlite-wasm-worker.cjs.map +1 -1
  17. package/dist/adapters/sqlite-wasm-worker.js +34 -5
  18. package/dist/adapters/sqlite-wasm-worker.js.map +1 -1
  19. package/dist/adapters/sqlite-wasm.cjs +249 -2
  20. package/dist/adapters/sqlite-wasm.cjs.map +1 -1
  21. package/dist/adapters/sqlite-wasm.d.cts +15 -3
  22. package/dist/adapters/sqlite-wasm.d.ts +15 -3
  23. package/dist/adapters/sqlite-wasm.js +2 -2
  24. package/dist/audit-trace-store-AM4EWQNZ.js +15 -0
  25. package/dist/backup-JN3LB4YT.js +14 -0
  26. package/dist/backup-JN3LB4YT.js.map +1 -0
  27. package/dist/chunk-56LL4XP6.js +205 -0
  28. package/dist/chunk-56LL4XP6.js.map +1 -0
  29. package/dist/chunk-6AHYGQQT.js +425 -0
  30. package/dist/chunk-6AHYGQQT.js.map +1 -0
  31. package/dist/chunk-7BPETY5Y.js +570 -0
  32. package/dist/chunk-7BPETY5Y.js.map +1 -0
  33. package/dist/chunk-AGN3CIPB.js +188 -0
  34. package/dist/chunk-AGN3CIPB.js.map +1 -0
  35. package/dist/chunk-EZSTD2XC.js +380 -0
  36. package/dist/chunk-EZSTD2XC.js.map +1 -0
  37. package/dist/chunk-MYSJN3DF.js +117 -0
  38. package/dist/chunk-MYSJN3DF.js.map +1 -0
  39. package/dist/{chunk-DXKLAQ6P.js → chunk-XO4LMPAH.js} +1 -1
  40. package/dist/chunk-XO4LMPAH.js.map +1 -0
  41. package/dist/export-audit-5GV6VRDZ.js +15 -0
  42. package/dist/export-audit-5GV6VRDZ.js.map +1 -0
  43. package/dist/index.cjs +2183 -587
  44. package/dist/index.cjs.map +1 -1
  45. package/dist/index.d.cts +423 -187
  46. package/dist/index.d.ts +423 -187
  47. package/dist/index.js +886 -726
  48. package/dist/index.js.map +1 -1
  49. package/dist/internal.cjs +789 -0
  50. package/dist/internal.cjs.map +1 -0
  51. package/dist/internal.d.cts +154 -0
  52. package/dist/internal.d.ts +154 -0
  53. package/dist/internal.js +48 -0
  54. package/dist/internal.js.map +1 -0
  55. package/dist/{sqlite-wasm-channel-46AOWNPM.js → sqlite-wasm-channel-HDDW6UBH.js} +2 -2
  56. package/dist/sqlite-wasm-channel-HDDW6UBH.js.map +1 -0
  57. package/dist/{sqlite-wasm-channel-Lakjuk2E.d.cts → sqlite-wasm-channel-I9y_jObg.d.cts} +1 -0
  58. package/dist/{sqlite-wasm-channel-Lakjuk2E.d.ts → sqlite-wasm-channel-I9y_jObg.d.ts} +1 -0
  59. package/dist/types-BMyHmwvn.d.ts +163 -0
  60. package/dist/types-CTr00g_d.d.cts +163 -0
  61. package/dist/types-DepnRDk_.d.cts +212 -0
  62. package/dist/types-DepnRDk_.d.ts +212 -0
  63. package/package.json +23 -2
  64. package/dist/chunk-DXKLAQ6P.js.map +0 -1
  65. package/dist/chunk-ZP5AXQ3Z.js +0 -179
  66. package/dist/chunk-ZP5AXQ3Z.js.map +0 -1
  67. package/dist/types-DF-KDSK1.d.cts +0 -106
  68. package/dist/types-DF-KDSK1.d.ts +0 -106
  69. /package/dist/{sqlite-wasm-channel-46AOWNPM.js.map → audit-trace-store-AM4EWQNZ.js.map} +0 -0
package/dist/index.cjs CHANGED
@@ -5,6 +5,9 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __esm = (fn, res) => function __init() {
9
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
+ };
8
11
  var __export = (target, all) => {
9
12
  for (var name in all)
10
13
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -27,243 +30,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
27
30
  ));
28
31
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
32
 
30
- // src/index.ts
31
- var index_exports = {};
32
- __export(index_exports, {
33
- AdapterError: () => AdapterError,
34
- Collection: () => Collection,
35
- InvalidStateTransitionError: () => InvalidStateTransitionError,
36
- PersistenceError: () => PersistenceError,
37
- QueryBuilder: () => QueryBuilder,
38
- QueryError: () => QueryError,
39
- RecordNotFoundError: () => RecordNotFoundError,
40
- SequenceManager: () => SequenceManager,
41
- Store: () => Store,
42
- StoreNotOpenError: () => StoreNotOpenError,
43
- SubscriptionBloomFilter: () => SubscriptionBloomFilter,
44
- SubscriptionManager: () => SubscriptionManager,
45
- TransactionContext: () => TransactionContext,
46
- WorkerInitError: () => WorkerInitError,
47
- WorkerTimeoutError: () => WorkerTimeoutError,
48
- decodeRichtext: () => decodeRichtext,
49
- encodeRichtext: () => encodeRichtext,
50
- pluralize: () => pluralize,
51
- richtextToPlainText: () => richtextToPlainText,
52
- singularize: () => singularize,
53
- validateStateTransition: () => validateStateTransition,
54
- validateUpdateStateMachine: () => validateUpdateStateMachine
55
- });
56
- module.exports = __toCommonJS(index_exports);
57
-
58
- // src/errors.ts
59
- var import_core = require("@korajs/core");
60
- var QueryError = class extends import_core.KoraError {
61
- constructor(message, context) {
62
- super(message, "QUERY_ERROR", context);
63
- this.name = "QueryError";
64
- }
65
- };
66
- var RecordNotFoundError = class extends import_core.KoraError {
67
- constructor(collection, recordId) {
68
- super(`Record "${recordId}" not found in collection "${collection}"`, "RECORD_NOT_FOUND", {
69
- collection,
70
- recordId
71
- });
72
- this.name = "RecordNotFoundError";
73
- }
74
- };
75
- var AdapterError = class extends import_core.KoraError {
76
- constructor(message, context) {
77
- super(message, "ADAPTER_ERROR", context);
78
- this.name = "AdapterError";
79
- }
80
- };
81
- var StoreNotOpenError = class extends import_core.KoraError {
82
- constructor() {
83
- super("Store is not open. Call store.open() before performing operations.", "STORE_NOT_OPEN");
84
- this.name = "StoreNotOpenError";
85
- }
86
- };
87
- var WorkerInitError = class extends import_core.KoraError {
88
- constructor(message, context) {
89
- super(`Worker initialization failed: ${message}`, "WORKER_INIT_ERROR", context);
90
- this.name = "WorkerInitError";
91
- }
92
- };
93
- var WorkerTimeoutError = class extends import_core.KoraError {
94
- constructor(operation, timeoutMs) {
95
- super(
96
- `Worker did not respond within ${timeoutMs}ms for operation "${operation}"`,
97
- "WORKER_TIMEOUT",
98
- { operation, timeoutMs }
99
- );
100
- this.name = "WorkerTimeoutError";
101
- }
102
- };
103
- var PersistenceError = class extends import_core.KoraError {
104
- constructor(message, context) {
105
- super(`Persistence error: ${message}`, "PERSISTENCE_ERROR", context);
106
- this.name = "PersistenceError";
107
- }
108
- };
109
-
110
- // src/store/store.ts
111
- var import_core8 = require("@korajs/core");
112
-
113
- // src/collection/collection.ts
114
- var import_core4 = require("@korajs/core");
115
-
116
- // src/query/sql-builder.ts
117
- function buildSelectQuery(descriptor, fields) {
118
- const params = [];
119
- const parts = [`SELECT * FROM ${descriptor.collection}`];
120
- const whereClause = buildWhereClauseParts(descriptor.where, fields, params);
121
- const deletedFilter = "_deleted = 0";
122
- if (whereClause) {
123
- parts.push(`WHERE ${deletedFilter} AND ${whereClause}`);
124
- } else {
125
- parts.push(`WHERE ${deletedFilter}`);
126
- }
127
- if (descriptor.orderBy.length > 0) {
128
- const orderParts = descriptor.orderBy.map((o) => {
129
- validateFieldName(o.field, fields);
130
- return `${o.field} ${o.direction.toUpperCase()}`;
131
- });
132
- parts.push(`ORDER BY ${orderParts.join(", ")}`);
133
- }
134
- if (descriptor.limit !== void 0) {
135
- parts.push(`LIMIT ${descriptor.limit}`);
136
- }
137
- if (descriptor.offset !== void 0) {
138
- parts.push(`OFFSET ${descriptor.offset}`);
139
- }
140
- return { sql: parts.join(" "), params };
141
- }
142
- function buildCountQuery(descriptor, fields) {
143
- const params = [];
144
- const parts = [`SELECT COUNT(*) as count FROM ${descriptor.collection}`];
145
- const whereClause = buildWhereClauseParts(descriptor.where, fields, params);
146
- const deletedFilter = "_deleted = 0";
147
- if (whereClause) {
148
- parts.push(`WHERE ${deletedFilter} AND ${whereClause}`);
149
- } else {
150
- parts.push(`WHERE ${deletedFilter}`);
151
- }
152
- return { sql: parts.join(" "), params };
153
- }
154
- function buildInsertQuery(collection, record) {
155
- const columns = Object.keys(record);
156
- const placeholders = columns.map(() => "?");
157
- const params = Object.values(record);
158
- const sql = `INSERT INTO ${collection} (${columns.join(", ")}) VALUES (${placeholders.join(", ")})`;
159
- return { sql, params };
160
- }
161
- function buildUpdateQuery(collection, id, changes) {
162
- const setClauses = Object.keys(changes).map((col) => `${col} = ?`);
163
- const params = [...Object.values(changes), id];
164
- const sql = `UPDATE ${collection} SET ${setClauses.join(", ")} WHERE id = ?`;
165
- return { sql, params };
166
- }
167
- function buildSoftDeleteQuery(collection, id, updatedAt) {
168
- return {
169
- sql: `UPDATE ${collection} SET _deleted = 1, _updated_at = ? WHERE id = ?`,
170
- params: [updatedAt, id]
171
- };
172
- }
173
- var VALID_OPERATORS = /* @__PURE__ */ new Set(["$eq", "$ne", "$gt", "$gte", "$lt", "$lte", "$in"]);
174
- function buildWhereClauseParts(where, fields, params) {
175
- const conditions = [];
176
- for (const [fieldName, value] of Object.entries(where)) {
177
- validateFieldName(fieldName, fields);
178
- const descriptor = fields[fieldName];
179
- if (value !== null && typeof value === "object" && !Array.isArray(value)) {
180
- const ops = value;
181
- for (const [op, opValue] of Object.entries(ops)) {
182
- if (!VALID_OPERATORS.has(op)) {
183
- throw new QueryError(`Unknown operator "${op}" on field "${fieldName}"`, {
184
- field: fieldName,
185
- operator: op,
186
- validOperators: [...VALID_OPERATORS]
187
- });
188
- }
189
- conditions.push(buildOperatorCondition(fieldName, op, opValue, descriptor, params));
190
- }
191
- } else {
192
- conditions.push(buildOperatorCondition(fieldName, "$eq", value, descriptor, params));
193
- }
194
- }
195
- if (conditions.length === 0) return null;
196
- return conditions.join(" AND ");
197
- }
198
- function buildOperatorCondition(fieldName, operator, value, descriptor, params) {
199
- const sqlValue = descriptor?.kind === "boolean" && typeof value === "boolean" ? value ? 1 : 0 : value;
200
- switch (operator) {
201
- case "$eq":
202
- if (sqlValue === null) {
203
- return `${fieldName} IS NULL`;
204
- }
205
- params.push(sqlValue);
206
- return `${fieldName} = ?`;
207
- case "$ne":
208
- if (sqlValue === null) {
209
- return `${fieldName} IS NOT NULL`;
210
- }
211
- params.push(sqlValue);
212
- return `${fieldName} != ?`;
213
- case "$gt":
214
- params.push(sqlValue);
215
- return `${fieldName} > ?`;
216
- case "$gte":
217
- params.push(sqlValue);
218
- return `${fieldName} >= ?`;
219
- case "$lt":
220
- params.push(sqlValue);
221
- return `${fieldName} < ?`;
222
- case "$lte":
223
- params.push(sqlValue);
224
- return `${fieldName} <= ?`;
225
- case "$in": {
226
- if (!Array.isArray(sqlValue)) {
227
- throw new QueryError(`$in operator requires an array value for field "${fieldName}"`, {
228
- field: fieldName,
229
- received: typeof sqlValue
230
- });
231
- }
232
- const placeholders = sqlValue.map(() => "?");
233
- for (const item of sqlValue) {
234
- params.push(
235
- descriptor?.kind === "boolean" && typeof item === "boolean" ? item ? 1 : 0 : item
236
- );
237
- }
238
- return `${fieldName} IN (${placeholders.join(", ")})`;
239
- }
240
- default:
241
- throw new QueryError(`Unknown operator "${operator}"`, { operator });
242
- }
243
- }
244
- function validateFieldName(fieldName, fields) {
245
- const allowedFields = /* @__PURE__ */ new Set([
246
- ...Object.keys(fields),
247
- "id",
248
- "createdAt",
249
- "updatedAt",
250
- "_created_at",
251
- "_updated_at"
252
- ]);
253
- if (!allowedFields.has(fieldName)) {
254
- throw new QueryError(
255
- `Unknown field "${fieldName}" in query. Available fields: ${[...allowedFields].join(", ")}`,
256
- { field: fieldName }
257
- );
258
- }
259
- }
260
-
261
- // src/serialization/serializer.ts
262
- var import_core2 = require("@korajs/core");
263
-
264
33
  // src/serialization/richtext-serializer.ts
265
- var Y = __toESM(require("yjs"), 1);
266
- var TEXT_KEY = "content";
267
34
  function encodeRichtext(value) {
268
35
  if (value === null || value === void 0) {
269
36
  return null;
@@ -305,6 +72,21 @@ function richtextToPlainText(value) {
305
72
  Y.applyUpdate(doc, encoded);
306
73
  return doc.getText(TEXT_KEY).toString();
307
74
  }
75
+ function richtextStatesEqual(a, b) {
76
+ try {
77
+ return richtextToPlainText(a) === richtextToPlainText(b);
78
+ } catch {
79
+ return false;
80
+ }
81
+ }
82
+ var Y, TEXT_KEY;
83
+ var init_richtext_serializer = __esm({
84
+ "src/serialization/richtext-serializer.ts"() {
85
+ "use strict";
86
+ Y = __toESM(require("yjs"), 1);
87
+ TEXT_KEY = "content";
88
+ }
89
+ });
308
90
 
309
91
  // src/serialization/serializer.ts
310
92
  function serializeRecord(data, fields) {
@@ -335,9 +117,6 @@ function deserializeRecord(row, fields) {
335
117
  }
336
118
  return result;
337
119
  }
338
- var ATOMIC_OPS_KEY = "__kora_atomic_ops__";
339
- var TX_ID_KEY = "__kora_tx_id__";
340
- var MUTATION_NAME_KEY = "__kora_mutation__";
341
120
  function serializeOperation(op) {
342
121
  const hasMetadata = op.transactionId !== void 0 || op.mutationName !== void 0;
343
122
  let dataPayload = null;
@@ -368,7 +147,7 @@ function serializeOperation(op) {
368
147
  record_id: op.recordId,
369
148
  data: dataPayload ? JSON.stringify(dataPayload) : null,
370
149
  previous_data: op.previousData ? JSON.stringify(op.previousData) : null,
371
- timestamp: import_core2.HybridLogicalClock.serialize(op.timestamp),
150
+ timestamp: import_core4.HybridLogicalClock.serialize(op.timestamp),
372
151
  sequence_number: op.sequenceNumber,
373
152
  causal_deps: JSON.stringify(op.causalDeps),
374
153
  schema_version: op.schemaVersion
@@ -402,7 +181,7 @@ function deserializeOperation(row) {
402
181
  recordId: row.record_id,
403
182
  data,
404
183
  previousData: row.previous_data ? JSON.parse(row.previous_data) : null,
405
- timestamp: import_core2.HybridLogicalClock.deserialize(row.timestamp),
184
+ timestamp: import_core4.HybridLogicalClock.deserialize(row.timestamp),
406
185
  sequenceNumber: row.sequence_number,
407
186
  causalDeps: JSON.parse(row.causal_deps),
408
187
  schemaVersion: row.schema_version,
@@ -445,28 +224,1343 @@ function deserializeValue(value, descriptor) {
445
224
  return value;
446
225
  }
447
226
  }
448
-
449
- // src/state-machine/state-validator.ts
450
- var import_core3 = require("@korajs/core");
451
- var InvalidStateTransitionError = class extends import_core3.KoraError {
452
- constructor(collection, recordId, field, fromState, toState, allowedStates) {
453
- super(
454
- `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)"}`,
455
- "INVALID_STATE_TRANSITION",
456
- { collection, recordId, field, fromState, toState, allowedStates }
457
- );
458
- this.collection = collection;
459
- this.recordId = recordId;
460
- this.field = field;
461
- this.fromState = fromState;
462
- this.toState = toState;
463
- this.allowedStates = allowedStates;
464
- this.name = "InvalidStateTransitionError";
227
+ var import_core4, ATOMIC_OPS_KEY, TX_ID_KEY, MUTATION_NAME_KEY;
228
+ var init_serializer = __esm({
229
+ "src/serialization/serializer.ts"() {
230
+ "use strict";
231
+ import_core4 = require("@korajs/core");
232
+ init_richtext_serializer();
233
+ ATOMIC_OPS_KEY = "__kora_atomic_ops__";
234
+ TX_ID_KEY = "__kora_tx_id__";
235
+ MUTATION_NAME_KEY = "__kora_mutation__";
465
236
  }
466
- collection;
467
- recordId;
468
- field;
469
- fromState;
237
+ });
238
+
239
+ // src/backup/backup.ts
240
+ var backup_exports = {};
241
+ __export(backup_exports, {
242
+ exportBackup: () => exportBackup,
243
+ readBackupManifest: () => readBackupManifest,
244
+ restoreBackup: () => restoreBackup,
245
+ verifyBackupChecksum: () => verifyBackupChecksum
246
+ });
247
+ function encodeSection(name, content) {
248
+ const nameBytes = new TextEncoder().encode(name);
249
+ const header = new Uint8Array(8);
250
+ const dv = new DataView(header.buffer);
251
+ dv.setUint32(0, nameBytes.length, true);
252
+ dv.setUint32(4, content.length, true);
253
+ const result = new Uint8Array(header.length + nameBytes.length + content.length);
254
+ result.set(header, 0);
255
+ result.set(nameBytes, 8);
256
+ result.set(content, 8 + nameBytes.length);
257
+ return result;
258
+ }
259
+ function encodeJsonSection(name, data) {
260
+ const json = JSON.stringify(data);
261
+ return encodeSection(name, new TextEncoder().encode(json));
262
+ }
263
+ function readUint32(view, offset) {
264
+ return view.getUint32(offset, true);
265
+ }
266
+ function parseSections(data) {
267
+ const sections = [];
268
+ const dv = new DataView(data.buffer, data.byteOffset, data.byteLength);
269
+ let offset = 0;
270
+ while (offset + 8 <= data.byteLength) {
271
+ const nameLen = readUint32(dv, offset);
272
+ const contentLen = readUint32(dv, offset + 4);
273
+ offset += 8;
274
+ if (offset + nameLen + contentLen > data.byteLength) break;
275
+ const name = new TextDecoder().decode(data.slice(offset, offset + nameLen));
276
+ offset += nameLen;
277
+ const content = data.slice(offset, offset + contentLen);
278
+ offset += contentLen;
279
+ sections.push({ name, content });
280
+ }
281
+ return sections;
282
+ }
283
+ function findSection(sections, name) {
284
+ for (const s of sections) {
285
+ if (s.name === name) return s.content;
286
+ }
287
+ return null;
288
+ }
289
+ function parseJsonSection(sections, name) {
290
+ const content = findSection(sections, name);
291
+ if (!content) return null;
292
+ return JSON.parse(new TextDecoder().decode(content));
293
+ }
294
+ async function exportBackup(adapter, schema, nodeId, schemaVersion, options) {
295
+ const onProgress = options?.onProgress ?? (() => {
296
+ });
297
+ const includeRecords = options?.includeRecords ?? true;
298
+ const collectionFilter = options?.collections;
299
+ const collections = collectionFilter ? Object.keys(schema.collections).filter((c) => collectionFilter.includes(c)) : Object.keys(schema.collections);
300
+ onProgress({ phase: "reading", progress: 0, message: "Reading version vector" });
301
+ const versionVector = await readVersionVector(adapter);
302
+ onProgress({ phase: "reading", progress: 0.1, message: "Reading metadata" });
303
+ const meta = await readMeta(adapter);
304
+ onProgress({ phase: "reading", progress: 0.2, message: "Reading operations" });
305
+ const operations = await readAllOperations(adapter, schema);
306
+ onProgress({ phase: "reading", progress: 0.5, message: "Reading records" });
307
+ const sections = [];
308
+ let allContentForChecksum = new Uint8Array(0);
309
+ const addSection = (name, data) => {
310
+ sections.push(data);
311
+ const newLen = allContentForChecksum.length + data.length;
312
+ const combined = new Uint8Array(newLen);
313
+ combined.set(allContentForChecksum, 0);
314
+ combined.set(data, allContentForChecksum.length);
315
+ allContentForChecksum = combined;
316
+ };
317
+ const vvObj = {};
318
+ for (const [nid, seq] of versionVector) {
319
+ vvObj[nid] = seq;
320
+ }
321
+ addSection("version_vector", encodeJsonSection("version_vector", vvObj));
322
+ const metaObj = {};
323
+ for (const row of meta) {
324
+ metaObj[row.key] = row.value;
325
+ }
326
+ addSection("meta", encodeJsonSection("meta", metaObj));
327
+ const opLines = `${operations.map((op) => JSON.stringify(op)).join("\n")}
328
+ `;
329
+ addSection("operations", encodeSection("operations", new TextEncoder().encode(opLines)));
330
+ let totalRecords = 0;
331
+ if (includeRecords) {
332
+ for (const col of collections) {
333
+ const records = await readCollectionRecords(adapter, col);
334
+ if (records.length > 0) {
335
+ const lines = `${records.map((r) => JSON.stringify(r)).join("\n")}
336
+ `;
337
+ const sectionName = `records:${col}`;
338
+ addSection(sectionName, encodeSection(sectionName, new TextEncoder().encode(lines)));
339
+ totalRecords += records.length;
340
+ }
341
+ }
342
+ }
343
+ onProgress({ phase: "reading", progress: 0.9, message: "Computing checksum" });
344
+ const checksumHex = await computeSha256(allContentForChecksum);
345
+ onProgress({ phase: "writing", progress: 1, message: "Finalizing" });
346
+ const manifest = {
347
+ version: BACKUP_VERSION,
348
+ createdAt: Date.now(),
349
+ nodeId,
350
+ schemaVersion,
351
+ operationCount: operations.length,
352
+ collections,
353
+ includesRecords: includeRecords,
354
+ checksum: checksumHex
355
+ };
356
+ const header = new Uint8Array(0);
357
+ const manifestSection = encodeJsonSection("manifest", manifest);
358
+ const checksumSection = encodeSection("checksum", new TextEncoder().encode(checksumHex));
359
+ const totalLen = manifestSection.length + allContentForChecksum.length + checksumSection.length;
360
+ const result = new Uint8Array(totalLen);
361
+ let pos = 0;
362
+ result.set(manifestSection, pos);
363
+ pos += manifestSection.length;
364
+ result.set(allContentForChecksum, pos);
365
+ pos += allContentForChecksum.length;
366
+ result.set(checksumSection, pos);
367
+ return result;
368
+ }
369
+ function readBackupManifest(data) {
370
+ const sections = parseSections(data);
371
+ const manifest = parseJsonSection(sections, "manifest");
372
+ if (!manifest) {
373
+ throw new Error("Invalid backup: manifest section not found");
374
+ }
375
+ return manifest;
376
+ }
377
+ async function verifyBackupChecksum(data) {
378
+ const sections = parseSections(data);
379
+ const manifest = parseJsonSection(sections, "manifest");
380
+ if (!manifest) return false;
381
+ const storedChecksum = manifest.checksum;
382
+ if (!storedChecksum) return false;
383
+ const manifestSection = findSection(sections, "manifest");
384
+ if (!manifestSection) return false;
385
+ const checksumSection = findSection(sections, "checksum");
386
+ const checksumBytes = checksumSection ? new TextEncoder().encode(manifest.checksum) : new Uint8Array(0);
387
+ const contentSections = sections.filter((s) => s.name !== "manifest" && s.name !== "checksum");
388
+ let contentForHash = new Uint8Array(0);
389
+ for (const section of contentSections) {
390
+ const sectionBytes = encodeSection(section.name, section.content);
391
+ const newLen = contentForHash.length + sectionBytes.length;
392
+ const combined = new Uint8Array(newLen);
393
+ combined.set(contentForHash, 0);
394
+ combined.set(sectionBytes, contentForHash.length);
395
+ contentForHash = combined;
396
+ }
397
+ const computed = await computeSha256(contentForHash);
398
+ return computed === storedChecksum;
399
+ }
400
+ async function restoreBackup(adapter, schema, data, options) {
401
+ const startTime = Date.now();
402
+ const onProgress = options?.onProgress ?? (() => {
403
+ });
404
+ const collectionFilter = options?.collections;
405
+ onProgress({ phase: "verifying", progress: 0, message: "Parsing backup" });
406
+ const sections = parseSections(data);
407
+ const manifest = parseJsonSection(sections, "manifest");
408
+ if (!manifest) {
409
+ return {
410
+ operationsRestored: 0,
411
+ recordsRestored: 0,
412
+ success: false,
413
+ error: "Invalid backup: manifest section not found",
414
+ duration: Date.now() - startTime
415
+ };
416
+ }
417
+ if (manifest.version !== BACKUP_VERSION) {
418
+ return {
419
+ operationsRestored: 0,
420
+ recordsRestored: 0,
421
+ success: false,
422
+ error: `Unsupported backup version: ${manifest.version}`,
423
+ duration: Date.now() - startTime
424
+ };
425
+ }
426
+ onProgress({ phase: "verifying", progress: 0.2, message: "Verifying checksum" });
427
+ const valid = await verifyBackupChecksum(data);
428
+ if (!valid) {
429
+ return {
430
+ operationsRestored: 0,
431
+ recordsRestored: 0,
432
+ success: false,
433
+ error: "Backup checksum mismatch: data may be corrupted",
434
+ duration: Date.now() - startTime
435
+ };
436
+ }
437
+ onProgress({ phase: "restoring", progress: 0.3, message: "Loading operations" });
438
+ const opsContent = findSection(sections, "operations");
439
+ let operations = [];
440
+ if (opsContent) {
441
+ const text = new TextDecoder().decode(opsContent);
442
+ const lines = text.trim().split("\n").filter((l) => l.length > 0);
443
+ operations = lines.map((line) => JSON.parse(line));
444
+ }
445
+ if (collectionFilter) {
446
+ operations = operations.filter((op) => collectionFilter.includes(op.collection));
447
+ }
448
+ const vvData = parseJsonSection(sections, "version_vector");
449
+ const metaData = parseJsonSection(sections, "meta");
450
+ onProgress({ phase: "restoring", progress: 0.4, message: "Applying operations" });
451
+ if (options?.merge) {
452
+ await adapter.transaction(async (tx) => {
453
+ if (vvData) {
454
+ for (const [nid, seq] of Object.entries(vvData)) {
455
+ await tx.execute(
456
+ "INSERT OR REPLACE INTO _kora_version_vector (node_id, sequence_number) VALUES (?, ?)",
457
+ [nid, seq]
458
+ );
459
+ }
460
+ }
461
+ if (metaData) {
462
+ for (const [key, value] of Object.entries(metaData)) {
463
+ await tx.execute("INSERT OR REPLACE INTO _kora_meta (key, value) VALUES (?, ?)", [
464
+ key,
465
+ value
466
+ ]);
467
+ }
468
+ }
469
+ for (const op of operations) {
470
+ const collection = op.collection;
471
+ const opRow = {
472
+ id: op.id,
473
+ node_id: op.nodeId,
474
+ type: op.type,
475
+ record_id: op.recordId,
476
+ data: op.data !== null ? JSON.stringify(op.data) : null,
477
+ previous_data: op.previousData !== null ? JSON.stringify(op.previousData) : null,
478
+ timestamp: JSON.stringify(op.timestamp),
479
+ sequence_number: op.sequenceNumber,
480
+ causal_deps: JSON.stringify(op.causalDeps),
481
+ schema_version: op.schemaVersion
482
+ };
483
+ await tx.execute(
484
+ `INSERT OR IGNORE INTO _kora_ops_${collection} (id, node_id, type, record_id, data, previous_data, timestamp, sequence_number, causal_deps, schema_version) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
485
+ [
486
+ opRow.id,
487
+ opRow.node_id,
488
+ opRow.type,
489
+ opRow.record_id,
490
+ opRow.data,
491
+ opRow.previous_data,
492
+ opRow.timestamp,
493
+ opRow.sequence_number,
494
+ opRow.causal_deps,
495
+ opRow.schema_version
496
+ ]
497
+ );
498
+ }
499
+ });
500
+ } else {
501
+ await adapter.transaction(async (tx) => {
502
+ for (const collection of Object.keys(schema.collections)) {
503
+ await tx.execute(`DELETE FROM _kora_ops_${collection}`);
504
+ await tx.execute(`DELETE FROM ${collection}`);
505
+ }
506
+ await tx.execute("DELETE FROM _kora_version_vector");
507
+ await tx.execute("DELETE FROM _kora_meta");
508
+ if (vvData) {
509
+ for (const [nid, seq] of Object.entries(vvData)) {
510
+ await tx.execute(
511
+ "INSERT INTO _kora_version_vector (node_id, sequence_number) VALUES (?, ?)",
512
+ [nid, seq]
513
+ );
514
+ }
515
+ }
516
+ if (metaData) {
517
+ for (const [key, value] of Object.entries(metaData)) {
518
+ await tx.execute("INSERT INTO _kora_meta (key, value) VALUES (?, ?)", [key, value]);
519
+ }
520
+ }
521
+ for (const op of operations) {
522
+ const collection = op.collection;
523
+ await tx.execute(
524
+ `INSERT INTO _kora_ops_${collection} (id, node_id, type, record_id, data, previous_data, timestamp, sequence_number, causal_deps, schema_version) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
525
+ [
526
+ op.id,
527
+ op.nodeId,
528
+ op.type,
529
+ op.recordId,
530
+ op.data !== null ? JSON.stringify(op.data) : null,
531
+ op.previousData !== null ? JSON.stringify(op.previousData) : null,
532
+ JSON.stringify(op.timestamp),
533
+ op.sequenceNumber,
534
+ JSON.stringify(op.causalDeps),
535
+ op.schemaVersion
536
+ ]
537
+ );
538
+ }
539
+ });
540
+ }
541
+ onProgress({ phase: "restoring", progress: 0.8, message: "Restoring records" });
542
+ let recordsRestored = 0;
543
+ if (manifest.includesRecords) {
544
+ for (const col of manifest.collections) {
545
+ if (collectionFilter && !collectionFilter.includes(col)) continue;
546
+ const sectionContent = findSection(sections, `records:${col}`);
547
+ if (!sectionContent) continue;
548
+ const text = new TextDecoder().decode(sectionContent);
549
+ const lines = text.trim().split("\n").filter((l) => l.length > 0);
550
+ const records = lines.map((line) => JSON.parse(line));
551
+ if (records.length === 0) continue;
552
+ await adapter.transaction(async (tx) => {
553
+ for (const record of records) {
554
+ const keys = Object.keys(record);
555
+ const placeholders = keys.map(() => "?").join(", ");
556
+ const values = keys.map((k) => {
557
+ const v = record[k];
558
+ if (typeof v === "boolean") return v ? 1 : 0;
559
+ if (Array.isArray(v) || typeof v === "object" && v !== null) return JSON.stringify(v);
560
+ return v;
561
+ });
562
+ await tx.execute(
563
+ `INSERT OR REPLACE INTO ${col} (${keys.join(", ")}) VALUES (${placeholders})`,
564
+ values
565
+ );
566
+ }
567
+ });
568
+ recordsRestored += records.length;
569
+ }
570
+ }
571
+ onProgress({ phase: "restoring", progress: 1, message: "Done" });
572
+ return {
573
+ operationsRestored: operations.length,
574
+ recordsRestored,
575
+ success: true,
576
+ duration: Date.now() - startTime
577
+ };
578
+ }
579
+ async function readVersionVector(adapter) {
580
+ const rows = await adapter.query(
581
+ "SELECT node_id, sequence_number FROM _kora_version_vector"
582
+ );
583
+ const vv = /* @__PURE__ */ new Map();
584
+ for (const row of rows) {
585
+ vv.set(row.node_id, row.sequence_number);
586
+ }
587
+ return vv;
588
+ }
589
+ async function readMeta(adapter) {
590
+ return adapter.query("SELECT key, value FROM _kora_meta");
591
+ }
592
+ async function readAllOperations(adapter, schema) {
593
+ const allOps = [];
594
+ for (const collectionName of Object.keys(schema.collections)) {
595
+ const rows = await adapter.query(
596
+ `SELECT * FROM _kora_ops_${collectionName} ORDER BY sequence_number ASC`
597
+ );
598
+ for (const row of rows) {
599
+ allOps.push(deserializeOperationWithCollection(row, collectionName));
600
+ }
601
+ }
602
+ return allOps;
603
+ }
604
+ async function readCollectionRecords(adapter, collection) {
605
+ return adapter.query(`SELECT * FROM ${collection} WHERE _deleted = 0`);
606
+ }
607
+ async function computeSha256(data) {
608
+ const hashBuffer = await crypto.subtle.digest("SHA-256", data);
609
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
610
+ return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
611
+ }
612
+ var import_core5, BACKUP_VERSION;
613
+ var init_backup = __esm({
614
+ "src/backup/backup.ts"() {
615
+ "use strict";
616
+ import_core5 = require("@korajs/core");
617
+ init_serializer();
618
+ BACKUP_VERSION = 1;
619
+ }
620
+ });
621
+
622
+ // src/audit/audit-json.ts
623
+ function auditJsonReplacer(_key, value) {
624
+ if (value instanceof Uint8Array) {
625
+ let binary = "";
626
+ for (let i = 0; i < value.length; i++) {
627
+ binary += String.fromCharCode(value[i]);
628
+ }
629
+ return { [WIRE_BYTES_KEY]: btoa(binary) };
630
+ }
631
+ return value;
632
+ }
633
+ function auditJsonReviver(_key, value) {
634
+ if (value !== null && typeof value === "object" && WIRE_BYTES_KEY in value && typeof value[WIRE_BYTES_KEY] === "string") {
635
+ const encoded = value[WIRE_BYTES_KEY];
636
+ if (typeof encoded !== "string") {
637
+ return value;
638
+ }
639
+ const binary = atob(encoded);
640
+ const bytes = new Uint8Array(binary.length);
641
+ for (let i = 0; i < binary.length; i++) {
642
+ bytes[i] = binary.charCodeAt(i);
643
+ }
644
+ return bytes;
645
+ }
646
+ return value;
647
+ }
648
+ function serializeAuditJson(value) {
649
+ return JSON.stringify(value, auditJsonReplacer);
650
+ }
651
+ function deserializeAuditJson(json) {
652
+ return JSON.parse(json, auditJsonReviver);
653
+ }
654
+ var WIRE_BYTES_KEY;
655
+ var init_audit_json = __esm({
656
+ "src/audit/audit-json.ts"() {
657
+ "use strict";
658
+ WIRE_BYTES_KEY = "__kora_bytes__";
659
+ }
660
+ });
661
+
662
+ // src/audit/audit-trace-store.ts
663
+ var audit_trace_store_exports = {};
664
+ __export(audit_trace_store_exports, {
665
+ appendAuditTrace: () => appendAuditTrace,
666
+ auditTraceIndexFields: () => auditTraceIndexFields,
667
+ countAuditTraces: () => countAuditTraces,
668
+ persistedAuditTraceFromEvent: () => persistedAuditTraceFromEvent,
669
+ readAuditTraces: () => readAuditTraces
670
+ });
671
+ function persistedAuditTraceFromEvent(event) {
672
+ return {
673
+ id: (0, import_core16.generateUUIDv7)(),
674
+ recordedAt: Date.now(),
675
+ eventType: event.type,
676
+ constraint: event.type === "constraint:violated" ? event.constraint : void 0,
677
+ trace: event.trace
678
+ };
679
+ }
680
+ async function appendAuditTrace(adapter, trace) {
681
+ const collection = trace.trace.operationA.collection;
682
+ const recordId = trace.trace.operationA.recordId;
683
+ await adapter.execute(
684
+ `INSERT INTO _kora_audit_traces (
685
+ id, recorded_at, event_type, collection, record_id, field, strategy, tier, constraint_name, trace_json
686
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
687
+ [
688
+ trace.id,
689
+ trace.recordedAt,
690
+ trace.eventType,
691
+ collection,
692
+ recordId,
693
+ trace.trace.field,
694
+ trace.trace.strategy,
695
+ trace.trace.tier,
696
+ trace.constraint ?? null,
697
+ serializeAuditJson(trace)
698
+ ]
699
+ );
700
+ }
701
+ async function readAuditTraces(adapter, query) {
702
+ const conditions = [];
703
+ const params = [];
704
+ if (query?.collections && query.collections.length > 0) {
705
+ const placeholders = query.collections.map(() => "?").join(", ");
706
+ conditions.push(`collection IN (${placeholders})`);
707
+ params.push(...query.collections);
708
+ }
709
+ if (query?.since !== void 0) {
710
+ conditions.push("recorded_at >= ?");
711
+ params.push(query.since);
712
+ }
713
+ if (query?.until !== void 0) {
714
+ conditions.push("recorded_at <= ?");
715
+ params.push(query.until);
716
+ }
717
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
718
+ const rows = await adapter.query(
719
+ `SELECT * FROM _kora_audit_traces ${whereClause} ORDER BY recorded_at ASC, id ASC`,
720
+ params
721
+ );
722
+ return rows.map(rowToPersistedTrace);
723
+ }
724
+ function rowToPersistedTrace(row) {
725
+ return deserializeAuditJson(row.trace_json);
726
+ }
727
+ async function countAuditTraces(adapter) {
728
+ const rows = await adapter.query(
729
+ "SELECT COUNT(*) as count FROM _kora_audit_traces"
730
+ );
731
+ return rows[0]?.count ?? 0;
732
+ }
733
+ function auditTraceIndexFields(trace) {
734
+ return {
735
+ collection: trace.operationA.collection,
736
+ recordId: trace.operationA.recordId
737
+ };
738
+ }
739
+ var import_core16;
740
+ var init_audit_trace_store = __esm({
741
+ "src/audit/audit-trace-store.ts"() {
742
+ "use strict";
743
+ import_core16 = require("@korajs/core");
744
+ init_audit_json();
745
+ }
746
+ });
747
+
748
+ // src/audit/export-audit.ts
749
+ var export_audit_exports = {};
750
+ __export(export_audit_exports, {
751
+ decodeAuditExport: () => decodeAuditExport,
752
+ exportAudit: () => exportAudit,
753
+ readAuditExportManifest: () => readAuditExportManifest,
754
+ verifyAuditExportChecksum: () => verifyAuditExportChecksum
755
+ });
756
+ function encodeSection2(name, content) {
757
+ const nameBytes = new TextEncoder().encode(name);
758
+ const header = new Uint8Array(8);
759
+ const view = new DataView(header.buffer);
760
+ view.setUint32(0, nameBytes.length, true);
761
+ view.setUint32(4, content.length, true);
762
+ const result = new Uint8Array(header.length + nameBytes.length + content.length);
763
+ result.set(header, 0);
764
+ result.set(nameBytes, 8);
765
+ result.set(content, 8 + nameBytes.length);
766
+ return result;
767
+ }
768
+ function encodeJsonSection2(name, data) {
769
+ return encodeSection2(name, new TextEncoder().encode(serializeAuditJson(data)));
770
+ }
771
+ function encodeNdjsonSection(name, records) {
772
+ const lines = `${records.map((record) => serializeAuditJson(record)).join("\n")}
773
+ `;
774
+ return encodeSection2(name, new TextEncoder().encode(lines));
775
+ }
776
+ function readUint322(view, offset) {
777
+ return view.getUint32(offset, true);
778
+ }
779
+ function parseSections2(data) {
780
+ const sections = [];
781
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
782
+ let offset = 0;
783
+ while (offset + 8 <= data.byteLength) {
784
+ const nameLen = readUint322(view, offset);
785
+ const contentLen = readUint322(view, offset + 4);
786
+ offset += 8;
787
+ if (offset + nameLen + contentLen > data.byteLength) {
788
+ break;
789
+ }
790
+ const name = new TextDecoder().decode(data.slice(offset, offset + nameLen));
791
+ offset += nameLen;
792
+ const content = data.slice(offset, offset + contentLen);
793
+ offset += contentLen;
794
+ sections.push({ name, content });
795
+ }
796
+ return sections;
797
+ }
798
+ function findSection2(sections, name) {
799
+ for (const section of sections) {
800
+ if (section.name === name) {
801
+ return section.content;
802
+ }
803
+ }
804
+ return null;
805
+ }
806
+ async function computeSha2562(data) {
807
+ const hashBuffer = await crypto.subtle.digest("SHA-256", data);
808
+ return Array.from(new Uint8Array(hashBuffer)).map((byte) => byte.toString(16).padStart(2, "0")).join("");
809
+ }
810
+ async function readAllOperations2(adapter, schema, collectionFilter) {
811
+ const collections = collectionFilter ? Object.keys(schema.collections).filter((name) => collectionFilter.includes(name)) : Object.keys(schema.collections);
812
+ const allOps = [];
813
+ for (const collectionName of collections) {
814
+ const rows = await adapter.query(
815
+ `SELECT * FROM _kora_ops_${collectionName} ORDER BY sequence_number ASC`
816
+ );
817
+ for (const row of rows) {
818
+ allOps.push(deserializeOperationWithCollection(row, collectionName));
819
+ }
820
+ }
821
+ return allOps;
822
+ }
823
+ function filterOperationsByCollections(operations, collectionFilter) {
824
+ if (!collectionFilter || collectionFilter.length === 0) {
825
+ return operations;
826
+ }
827
+ const allowed = new Set(collectionFilter);
828
+ return operations.filter((op) => allowed.has(op.collection));
829
+ }
830
+ async function exportAudit(adapter, schema, nodeId, schemaVersion, options) {
831
+ const onProgress = options?.onProgress ?? (() => {
832
+ });
833
+ const traceQuery = {
834
+ collections: options?.collections,
835
+ since: options?.since,
836
+ until: options?.until
837
+ };
838
+ onProgress({ phase: "reading", progress: 0, message: "Reading operations" });
839
+ const operations = filterOperationsByCollections(
840
+ await readAllOperations2(adapter, schema, options?.collections),
841
+ options?.collections
842
+ );
843
+ onProgress({ phase: "reading", progress: 0.4, message: "Reading merge traces" });
844
+ const mergeTraces = await readAuditTraces(adapter, traceQuery);
845
+ onProgress({ phase: "writing", progress: 0.7, message: "Encoding audit export" });
846
+ const sections = [];
847
+ let contentForChecksum = new Uint8Array(0);
848
+ const addSection = (section) => {
849
+ sections.push(section);
850
+ const combined = new Uint8Array(contentForChecksum.length + section.length);
851
+ combined.set(contentForChecksum, 0);
852
+ combined.set(section, contentForChecksum.length);
853
+ contentForChecksum = combined;
854
+ };
855
+ addSection(encodeNdjsonSection("operations", operations));
856
+ addSection(encodeNdjsonSection("merge_traces", mergeTraces));
857
+ onProgress({ phase: "verifying", progress: 0.9, message: "Computing checksum" });
858
+ const checksum = await computeSha2562(contentForChecksum);
859
+ const manifest = {
860
+ version: 1,
861
+ exportedAt: Date.now(),
862
+ nodeId,
863
+ schemaVersion,
864
+ operationCount: operations.length,
865
+ mergeTraceCount: mergeTraces.length,
866
+ checksum
867
+ };
868
+ const manifestSection = encodeJsonSection2("manifest", manifest);
869
+ const checksumSection = encodeSection2("checksum", new TextEncoder().encode(checksum));
870
+ onProgress({ phase: "writing", progress: 1, message: "Finalizing" });
871
+ const parts = [manifestSection, ...sections, checksumSection];
872
+ const finalOutput = new Uint8Array(parts.reduce((sum, part) => sum + part.length, 0));
873
+ let offset = 0;
874
+ for (const part of parts) {
875
+ finalOutput.set(part, offset);
876
+ offset += part.length;
877
+ }
878
+ return finalOutput;
879
+ }
880
+ function decodeAuditExport(data) {
881
+ const sections = parseSections2(data);
882
+ const manifest = JSON.parse(
883
+ new TextDecoder().decode(findSection2(sections, "manifest") ?? new Uint8Array())
884
+ );
885
+ const operationsContent = findSection2(sections, "operations");
886
+ const mergeTracesContent = findSection2(sections, "merge_traces");
887
+ const operations = parseNdjsonSection(operationsContent);
888
+ const mergeTraces = parseNdjsonSection(mergeTracesContent);
889
+ return { manifest, operations, mergeTraces };
890
+ }
891
+ function readAuditExportManifest(data) {
892
+ const sections = parseSections2(data);
893
+ const manifestContent = findSection2(sections, "manifest");
894
+ if (!manifestContent) {
895
+ throw new Error("Invalid audit export: missing manifest section");
896
+ }
897
+ return JSON.parse(new TextDecoder().decode(manifestContent));
898
+ }
899
+ async function verifyAuditExportChecksum(data) {
900
+ const sections = parseSections2(data);
901
+ const manifest = readAuditExportManifest(data);
902
+ const contentSections = sections.filter(
903
+ (section) => section.name !== "manifest" && section.name !== "checksum"
904
+ );
905
+ let content = new Uint8Array(0);
906
+ for (const section of contentSections) {
907
+ const encoded = encodeSection2(section.name, section.content);
908
+ const combined = new Uint8Array(content.length + encoded.length);
909
+ combined.set(content, 0);
910
+ combined.set(encoded, content.length);
911
+ content = combined;
912
+ }
913
+ const checksum = await computeSha2562(content);
914
+ return checksum === manifest.checksum;
915
+ }
916
+ function parseNdjsonSection(content) {
917
+ if (!content || content.length === 0) {
918
+ return [];
919
+ }
920
+ const text = new TextDecoder().decode(content).trim();
921
+ if (text.length === 0) {
922
+ return [];
923
+ }
924
+ return text.split("\n").map((line) => deserializeAuditJson(line));
925
+ }
926
+ var init_export_audit = __esm({
927
+ "src/audit/export-audit.ts"() {
928
+ "use strict";
929
+ init_serializer();
930
+ init_audit_json();
931
+ init_audit_trace_store();
932
+ }
933
+ });
934
+
935
+ // src/index.ts
936
+ var index_exports = {};
937
+ __export(index_exports, {
938
+ AdapterError: () => AdapterError,
939
+ COMPACTION_BASELINE_META_KEY: () => COMPACTION_BASELINE_META_KEY,
940
+ Collection: () => Collection,
941
+ InvalidStateTransitionError: () => InvalidStateTransitionError,
942
+ LAST_ACKED_SERVER_VECTOR_META_KEY: () => LAST_ACKED_SERVER_VECTOR_META_KEY,
943
+ PersistenceError: () => PersistenceError,
944
+ QueryBuilder: () => QueryBuilder,
945
+ QueryError: () => QueryError,
946
+ RecordNotFoundError: () => RecordNotFoundError,
947
+ SequenceManager: () => SequenceManager,
948
+ Store: () => Store,
949
+ StoreNotOpenError: () => StoreNotOpenError,
950
+ SubscriptionBloomFilter: () => SubscriptionBloomFilter,
951
+ SubscriptionManager: () => SubscriptionManager,
952
+ TransactionContext: () => TransactionContext,
953
+ WorkerInitError: () => WorkerInitError,
954
+ WorkerTimeoutError: () => WorkerTimeoutError,
955
+ collectOperationsAheadOfServer: () => collectOperationsAheadOfServer,
956
+ compactOperationLog: () => compactOperationLog,
957
+ computeAckCompactionWatermark: () => computeAckCompactionWatermark,
958
+ decodeAuditExport: () => decodeAuditExport,
959
+ decodeRichtext: () => decodeRichtext,
960
+ deserializeVersionVectorFromMeta: () => deserializeVersionVectorFromMeta,
961
+ encodeRichtext: () => encodeRichtext,
962
+ exportBackup: () => exportBackup,
963
+ mergeVersionVectors: () => mergeVersionVectors,
964
+ persistedAuditTraceFromEvent: () => persistedAuditTraceFromEvent,
965
+ pluralize: () => pluralize,
966
+ readAuditExportManifest: () => readAuditExportManifest,
967
+ readBackupManifest: () => readBackupManifest,
968
+ restoreBackup: () => restoreBackup,
969
+ richtextStatesEqual: () => richtextStatesEqual,
970
+ richtextToPlainText: () => richtextToPlainText,
971
+ serializeVersionVectorToMeta: () => serializeVersionVectorToMeta,
972
+ singularize: () => singularize,
973
+ validateStateTransition: () => validateStateTransition,
974
+ validateUpdateStateMachine: () => validateUpdateStateMachine,
975
+ verifyAuditExportChecksum: () => verifyAuditExportChecksum,
976
+ verifyBackupChecksum: () => verifyBackupChecksum
977
+ });
978
+ module.exports = __toCommonJS(index_exports);
979
+
980
+ // src/errors.ts
981
+ var import_core = require("@korajs/core");
982
+ var QueryError = class extends import_core.KoraError {
983
+ constructor(message, context) {
984
+ super(message, "QUERY_ERROR", context);
985
+ this.name = "QueryError";
986
+ }
987
+ };
988
+ var RecordNotFoundError = class extends import_core.KoraError {
989
+ constructor(collection, recordId) {
990
+ super(`Record "${recordId}" not found in collection "${collection}"`, "RECORD_NOT_FOUND", {
991
+ collection,
992
+ recordId
993
+ });
994
+ this.name = "RecordNotFoundError";
995
+ }
996
+ };
997
+ var AdapterError = class extends import_core.KoraError {
998
+ constructor(message, context) {
999
+ super(message, "ADAPTER_ERROR", context);
1000
+ this.name = "AdapterError";
1001
+ }
1002
+ };
1003
+ var StoreNotOpenError = class extends import_core.KoraError {
1004
+ constructor() {
1005
+ super("Store is not open. Call store.open() before performing operations.", "STORE_NOT_OPEN");
1006
+ this.name = "StoreNotOpenError";
1007
+ }
1008
+ };
1009
+ var WorkerInitError = class extends import_core.KoraError {
1010
+ constructor(message, context) {
1011
+ super(`Worker initialization failed: ${message}`, "WORKER_INIT_ERROR", context);
1012
+ this.name = "WorkerInitError";
1013
+ }
1014
+ };
1015
+ var WorkerTimeoutError = class extends import_core.KoraError {
1016
+ constructor(operation, timeoutMs) {
1017
+ super(
1018
+ `Worker did not respond within ${timeoutMs}ms for operation "${operation}"`,
1019
+ "WORKER_TIMEOUT",
1020
+ { operation, timeoutMs }
1021
+ );
1022
+ this.name = "WorkerTimeoutError";
1023
+ }
1024
+ };
1025
+ var PersistenceError = class extends import_core.KoraError {
1026
+ constructor(message, context) {
1027
+ super(`Persistence error: ${message}`, "PERSISTENCE_ERROR", context);
1028
+ this.name = "PersistenceError";
1029
+ }
1030
+ };
1031
+
1032
+ // src/compaction/types.ts
1033
+ var COMPACTION_BASELINE_META_KEY = "compaction_baseline_at";
1034
+
1035
+ // src/compaction/compact-operation-log.ts
1036
+ var import_core2 = require("@korajs/core");
1037
+ function computeAckCompactionWatermark(serverVector) {
1038
+ const watermark = (0, import_core2.createVersionVector)();
1039
+ for (const [nodeId, seq] of serverVector) {
1040
+ if (seq > 0) {
1041
+ watermark.set(nodeId, seq);
1042
+ }
1043
+ }
1044
+ return watermark;
1045
+ }
1046
+ async function compactOperationLog(adapter, schema, strategy, serverVector) {
1047
+ if (strategy.mode === "never") {
1048
+ return { deletedCount: 0, watermark: (0, import_core2.createVersionVector)() };
1049
+ }
1050
+ const watermark = computeAckCompactionWatermark(serverVector);
1051
+ if (watermark.size === 0) {
1052
+ return { deletedCount: 0, watermark };
1053
+ }
1054
+ const ageCutoffWall = strategy.mode === "after-days" ? Date.now() - strategy.days * 24 * 60 * 60 * 1e3 : null;
1055
+ const ageCutoffSerialized = ageCutoffWall !== null ? import_core2.HybridLogicalClock.serialize({
1056
+ wallTime: ageCutoffWall,
1057
+ logical: 0,
1058
+ nodeId: ""
1059
+ }).slice(0, 15) : null;
1060
+ let deletedCount = 0;
1061
+ const collectionNames = Object.keys(schema.collections);
1062
+ await adapter.transaction(async (tx) => {
1063
+ for (const collectionName of collectionNames) {
1064
+ const table = `_kora_ops_${collectionName}`;
1065
+ for (const [nodeId, maxSeq] of watermark) {
1066
+ if (maxSeq <= 0) continue;
1067
+ const countSql = buildCountSql(table, ageCutoffSerialized !== null);
1068
+ const countParams = ageCutoffSerialized !== null ? [nodeId, maxSeq, ageCutoffSerialized] : [nodeId, maxSeq];
1069
+ const countRows = await tx.query(countSql, countParams);
1070
+ deletedCount += countRows[0]?.count ?? 0;
1071
+ const deleteSql = buildDeleteSql(table, ageCutoffSerialized !== null);
1072
+ await tx.execute(deleteSql, countParams);
1073
+ }
1074
+ }
1075
+ await tx.execute("INSERT OR REPLACE INTO _kora_meta (key, value) VALUES (?, ?)", [
1076
+ COMPACTION_BASELINE_META_KEY,
1077
+ String(Date.now())
1078
+ ]);
1079
+ });
1080
+ return { deletedCount, watermark };
1081
+ }
1082
+ function buildCountSql(table, withAge) {
1083
+ if (withAge) {
1084
+ return `SELECT COUNT(*) as count FROM ${table} WHERE node_id = ? AND sequence_number <= ? AND SUBSTR(timestamp, 1, 15) < ?`;
1085
+ }
1086
+ return `SELECT COUNT(*) as count FROM ${table} WHERE node_id = ? AND sequence_number <= ?`;
1087
+ }
1088
+ function buildDeleteSql(table, withAge) {
1089
+ if (withAge) {
1090
+ return `DELETE FROM ${table} WHERE node_id = ? AND sequence_number <= ? AND SUBSTR(timestamp, 1, 15) < ?`;
1091
+ }
1092
+ return `DELETE FROM ${table} WHERE node_id = ? AND sequence_number <= ?`;
1093
+ }
1094
+
1095
+ // src/sync/sync-state.ts
1096
+ var import_core3 = require("@korajs/core");
1097
+ var LAST_ACKED_SERVER_VECTOR_META_KEY = "last_acked_server_vector";
1098
+ var DELTA_CURSOR_META_KEY = "delta_cursor";
1099
+ function serializeVersionVectorToMeta(vector) {
1100
+ const record = {};
1101
+ for (const [nodeId, seq] of vector) {
1102
+ record[nodeId] = seq;
1103
+ }
1104
+ return JSON.stringify(record);
1105
+ }
1106
+ function deserializeVersionVectorFromMeta(value) {
1107
+ const parsed = JSON.parse(value);
1108
+ const vector = (0, import_core3.createVersionVector)();
1109
+ for (const [nodeId, seq] of Object.entries(parsed)) {
1110
+ if (typeof seq === "number" && seq >= 0) {
1111
+ vector.set(nodeId, seq);
1112
+ }
1113
+ }
1114
+ return vector;
1115
+ }
1116
+ function mergeVersionVectors(a, b) {
1117
+ const merged = new Map(a);
1118
+ for (const [nodeId, seq] of b) {
1119
+ merged.set(nodeId, Math.max(merged.get(nodeId) ?? 0, seq));
1120
+ }
1121
+ return merged;
1122
+ }
1123
+ async function collectOperationsAheadOfServer(localVector, serverVector, fetchRange) {
1124
+ const missing = [];
1125
+ for (const [nodeId, localSeq] of localVector) {
1126
+ const serverSeq = serverVector.get(nodeId) ?? 0;
1127
+ if (localSeq > serverSeq) {
1128
+ const ops = await fetchRange(nodeId, serverSeq + 1, localSeq);
1129
+ missing.push(...ops);
1130
+ }
1131
+ }
1132
+ missing.sort((a, b) => a.sequenceNumber - b.sequenceNumber);
1133
+ return missing;
1134
+ }
1135
+ async function loadLastAckedServerVector(adapter) {
1136
+ const rows = await adapter.query("SELECT value FROM _kora_meta WHERE key = ?", [
1137
+ LAST_ACKED_SERVER_VECTOR_META_KEY
1138
+ ]);
1139
+ const row = rows[0];
1140
+ if (!row?.value) {
1141
+ return (0, import_core3.createVersionVector)();
1142
+ }
1143
+ try {
1144
+ return deserializeVersionVectorFromMeta(row.value);
1145
+ } catch {
1146
+ return (0, import_core3.createVersionVector)();
1147
+ }
1148
+ }
1149
+ async function saveLastAckedServerVector(adapter, vector) {
1150
+ await adapter.execute("INSERT OR REPLACE INTO _kora_meta (key, value) VALUES (?, ?)", [
1151
+ LAST_ACKED_SERVER_VECTOR_META_KEY,
1152
+ serializeVersionVectorToMeta(vector)
1153
+ ]);
1154
+ }
1155
+ async function loadDeltaCursor(adapter) {
1156
+ const rows = await adapter.query("SELECT value FROM _kora_meta WHERE key = ?", [
1157
+ DELTA_CURSOR_META_KEY
1158
+ ]);
1159
+ return rows[0]?.value ?? null;
1160
+ }
1161
+ async function saveDeltaCursor(adapter, cursor) {
1162
+ if (cursor === null) {
1163
+ await adapter.execute("DELETE FROM _kora_meta WHERE key = ?", [DELTA_CURSOR_META_KEY]);
1164
+ return;
1165
+ }
1166
+ await adapter.execute("INSERT OR REPLACE INTO _kora_meta (key, value) VALUES (?, ?)", [
1167
+ DELTA_CURSOR_META_KEY,
1168
+ cursor
1169
+ ]);
1170
+ }
1171
+
1172
+ // src/store/store.ts
1173
+ var import_core17 = require("@korajs/core");
1174
+ init_backup();
1175
+
1176
+ // src/mutations/execute-delete.ts
1177
+ var import_core7 = require("@korajs/core");
1178
+
1179
+ // src/lww/row-version.ts
1180
+ var import_core6 = require("@korajs/core");
1181
+ var EMPTY_ROW_VERSION = "";
1182
+ function serializeRowVersion(timestamp) {
1183
+ return import_core6.HybridLogicalClock.serialize(timestamp);
1184
+ }
1185
+ function rowVersionFromRecord(row) {
1186
+ const version = row._version;
1187
+ if (typeof version === "string" && version.length > 0) {
1188
+ return import_core6.HybridLogicalClock.deserialize(version);
1189
+ }
1190
+ return { wallTime: row._updated_at, logical: 0, nodeId: "" };
1191
+ }
1192
+ function isIncomingNewerThanRow(incoming, row) {
1193
+ return import_core6.HybridLogicalClock.compare(incoming, rowVersionFromRecord(row)) > 0;
1194
+ }
1195
+ function lwwVersionWhereClause(remoteVersion) {
1196
+ return {
1197
+ sql: "(_version = ? OR _version < ?)",
1198
+ params: [EMPTY_ROW_VERSION, remoteVersion]
1199
+ };
1200
+ }
1201
+
1202
+ // src/query/sql-builder.ts
1203
+ function buildSelectQuery(descriptor, fields) {
1204
+ const params = [];
1205
+ const parts = [`SELECT * FROM ${descriptor.collection}`];
1206
+ const whereClause = buildWhereClauseParts(descriptor.where, fields, params);
1207
+ const deletedFilter = "_deleted = 0";
1208
+ if (whereClause) {
1209
+ parts.push(`WHERE ${deletedFilter} AND ${whereClause}`);
1210
+ } else {
1211
+ parts.push(`WHERE ${deletedFilter}`);
1212
+ }
1213
+ if (descriptor.orderBy.length > 0) {
1214
+ const orderParts = descriptor.orderBy.map((o) => {
1215
+ validateFieldName(o.field, fields);
1216
+ return `${o.field} ${o.direction.toUpperCase()}`;
1217
+ });
1218
+ parts.push(`ORDER BY ${orderParts.join(", ")}`);
1219
+ }
1220
+ if (descriptor.limit !== void 0) {
1221
+ parts.push(`LIMIT ${descriptor.limit}`);
1222
+ }
1223
+ if (descriptor.offset !== void 0) {
1224
+ parts.push(`OFFSET ${descriptor.offset}`);
1225
+ }
1226
+ return { sql: parts.join(" "), params };
1227
+ }
1228
+ function buildCountQuery(descriptor, fields) {
1229
+ const params = [];
1230
+ const parts = [`SELECT COUNT(*) as count FROM ${descriptor.collection}`];
1231
+ const whereClause = buildWhereClauseParts(descriptor.where, fields, params);
1232
+ const deletedFilter = "_deleted = 0";
1233
+ if (whereClause) {
1234
+ parts.push(`WHERE ${deletedFilter} AND ${whereClause}`);
1235
+ } else {
1236
+ parts.push(`WHERE ${deletedFilter}`);
1237
+ }
1238
+ return { sql: parts.join(" "), params };
1239
+ }
1240
+ function buildInsertQuery(collection, record) {
1241
+ const columns = Object.keys(record);
1242
+ const placeholders = columns.map(() => "?");
1243
+ const params = Object.values(record);
1244
+ const sql = `INSERT INTO ${collection} (${columns.join(", ")}) VALUES (${placeholders.join(", ")})`;
1245
+ return { sql, params };
1246
+ }
1247
+ function buildUpdateQuery(collection, id, changes) {
1248
+ const setClauses = Object.keys(changes).map((col) => `${col} = ?`);
1249
+ const params = [...Object.values(changes), id];
1250
+ const sql = `UPDATE ${collection} SET ${setClauses.join(", ")} WHERE id = ?`;
1251
+ return { sql, params };
1252
+ }
1253
+ function buildSoftDeleteQuery(collection, id, updatedAt, version) {
1254
+ if (version !== void 0) {
1255
+ return {
1256
+ sql: `UPDATE ${collection} SET _deleted = 1, _updated_at = ?, _version = ? WHERE id = ?`,
1257
+ params: [updatedAt, version, id]
1258
+ };
1259
+ }
1260
+ return {
1261
+ sql: `UPDATE ${collection} SET _deleted = 1, _updated_at = ? WHERE id = ?`,
1262
+ params: [updatedAt, id]
1263
+ };
1264
+ }
1265
+ function buildLwwUpdateQuery(collection, id, changes, remoteVersion) {
1266
+ const setClauses = Object.keys(changes).map((col) => `${col} = ?`);
1267
+ const lww = lwwVersionWhereClause(remoteVersion);
1268
+ const sql = `UPDATE ${collection} SET ${setClauses.join(", ")} WHERE id = ? AND ${lww.sql}`;
1269
+ const params = [...Object.values(changes), id, ...lww.params];
1270
+ return { sql, params };
1271
+ }
1272
+ function buildLwwSoftDeleteQuery(collection, id, updatedAt, version) {
1273
+ const lww = lwwVersionWhereClause(version);
1274
+ return {
1275
+ sql: `UPDATE ${collection} SET _deleted = 1, _updated_at = ?, _version = ? WHERE id = ? AND ${lww.sql}`,
1276
+ params: [updatedAt, version, id, ...lww.params]
1277
+ };
1278
+ }
1279
+ var VALID_OPERATORS = /* @__PURE__ */ new Set(["$eq", "$ne", "$gt", "$gte", "$lt", "$lte", "$in"]);
1280
+ function buildWhereClauseParts(where, fields, params) {
1281
+ const conditions = [];
1282
+ for (const [fieldName, value] of Object.entries(where)) {
1283
+ validateFieldName(fieldName, fields);
1284
+ const descriptor = fields[fieldName];
1285
+ if (value !== null && typeof value === "object" && !Array.isArray(value)) {
1286
+ const ops = value;
1287
+ for (const [op, opValue] of Object.entries(ops)) {
1288
+ if (!VALID_OPERATORS.has(op)) {
1289
+ throw new QueryError(`Unknown operator "${op}" on field "${fieldName}"`, {
1290
+ field: fieldName,
1291
+ operator: op,
1292
+ validOperators: [...VALID_OPERATORS]
1293
+ });
1294
+ }
1295
+ conditions.push(buildOperatorCondition(fieldName, op, opValue, descriptor, params));
1296
+ }
1297
+ } else {
1298
+ conditions.push(buildOperatorCondition(fieldName, "$eq", value, descriptor, params));
1299
+ }
1300
+ }
1301
+ if (conditions.length === 0) return null;
1302
+ return conditions.join(" AND ");
1303
+ }
1304
+ function buildOperatorCondition(fieldName, operator, value, descriptor, params) {
1305
+ const sqlValue = descriptor?.kind === "boolean" && typeof value === "boolean" ? value ? 1 : 0 : value;
1306
+ switch (operator) {
1307
+ case "$eq":
1308
+ if (sqlValue === null) {
1309
+ return `${fieldName} IS NULL`;
1310
+ }
1311
+ params.push(sqlValue);
1312
+ return `${fieldName} = ?`;
1313
+ case "$ne":
1314
+ if (sqlValue === null) {
1315
+ return `${fieldName} IS NOT NULL`;
1316
+ }
1317
+ params.push(sqlValue);
1318
+ return `${fieldName} != ?`;
1319
+ case "$gt":
1320
+ params.push(sqlValue);
1321
+ return `${fieldName} > ?`;
1322
+ case "$gte":
1323
+ params.push(sqlValue);
1324
+ return `${fieldName} >= ?`;
1325
+ case "$lt":
1326
+ params.push(sqlValue);
1327
+ return `${fieldName} < ?`;
1328
+ case "$lte":
1329
+ params.push(sqlValue);
1330
+ return `${fieldName} <= ?`;
1331
+ case "$in": {
1332
+ if (!Array.isArray(sqlValue)) {
1333
+ throw new QueryError(`$in operator requires an array value for field "${fieldName}"`, {
1334
+ field: fieldName,
1335
+ received: typeof sqlValue
1336
+ });
1337
+ }
1338
+ const placeholders = sqlValue.map(() => "?");
1339
+ for (const item of sqlValue) {
1340
+ params.push(
1341
+ descriptor?.kind === "boolean" && typeof item === "boolean" ? item ? 1 : 0 : item
1342
+ );
1343
+ }
1344
+ return `${fieldName} IN (${placeholders.join(", ")})`;
1345
+ }
1346
+ default:
1347
+ throw new QueryError(`Unknown operator "${operator}"`, { operator });
1348
+ }
1349
+ }
1350
+ function validateFieldName(fieldName, fields) {
1351
+ const allowedFields = /* @__PURE__ */ new Set([
1352
+ ...Object.keys(fields),
1353
+ "id",
1354
+ "createdAt",
1355
+ "updatedAt",
1356
+ "_created_at",
1357
+ "_updated_at"
1358
+ ]);
1359
+ if (!allowedFields.has(fieldName)) {
1360
+ throw new QueryError(
1361
+ `Unknown field "${fieldName}" in query. Available fields: ${[...allowedFields].join(", ")}`,
1362
+ { field: fieldName }
1363
+ );
1364
+ }
1365
+ }
1366
+
1367
+ // src/mutations/execute-delete.ts
1368
+ init_serializer();
1369
+
1370
+ // src/store/sequence-allocator.ts
1371
+ async function allocateNextSequenceNumber(adapter, nodeId) {
1372
+ const rows = await adapter.query(
1373
+ `INSERT INTO _kora_version_vector (node_id, sequence_number) VALUES (?, 1)
1374
+ ON CONFLICT(node_id) DO UPDATE SET sequence_number = sequence_number + 1
1375
+ RETURNING sequence_number`,
1376
+ [nodeId]
1377
+ );
1378
+ const seq = rows[0]?.sequence_number;
1379
+ if (seq === void 0) {
1380
+ throw new Error(`Failed to allocate sequence number for node "${nodeId}"`);
1381
+ }
1382
+ return seq;
1383
+ }
1384
+ async function allocateNextSequenceInTransaction(tx, nodeId) {
1385
+ const rows = await tx.query(
1386
+ `INSERT INTO _kora_version_vector (node_id, sequence_number) VALUES (?, 1)
1387
+ ON CONFLICT(node_id) DO UPDATE SET sequence_number = sequence_number + 1
1388
+ RETURNING sequence_number`,
1389
+ [nodeId]
1390
+ );
1391
+ const seq = rows[0]?.sequence_number;
1392
+ if (seq === void 0) {
1393
+ throw new Error(`Failed to allocate sequence number for node "${nodeId}" in transaction`);
1394
+ }
1395
+ return seq;
1396
+ }
1397
+ async function readSequenceNumber(adapter, nodeId) {
1398
+ const rows = await adapter.query(
1399
+ "SELECT sequence_number FROM _kora_version_vector WHERE node_id = ?",
1400
+ [nodeId]
1401
+ );
1402
+ return rows[0]?.sequence_number ?? 0;
1403
+ }
1404
+
1405
+ // src/mutations/resolve-causal-deps.ts
1406
+ function resolveCausalDeps(ctx) {
1407
+ const merged = [];
1408
+ for (const id of ctx.extraCausalDeps ?? []) {
1409
+ if (!merged.includes(id)) {
1410
+ merged.push(id);
1411
+ }
1412
+ }
1413
+ for (const id of ctx.causalTracker?.nextCausalDeps(ctx.collection, ctx.inTransaction) ?? []) {
1414
+ if (!merged.includes(id)) {
1415
+ merged.push(id);
1416
+ }
1417
+ }
1418
+ return merged;
1419
+ }
1420
+
1421
+ // src/mutations/execute-delete.ts
1422
+ async function executeDelete(ctx, id, options) {
1423
+ const currentRows = await ctx.adapter.query(
1424
+ `SELECT * FROM ${ctx.collection} WHERE id = ? AND _deleted = 0`,
1425
+ [id]
1426
+ );
1427
+ if (!currentRows[0]) {
1428
+ throw new RecordNotFoundError(ctx.collection, id);
1429
+ }
1430
+ const causalDeps = resolveCausalDeps(ctx);
1431
+ let operation = options?.operation;
1432
+ const cascadedOps = [];
1433
+ await ctx.adapter.transaction(async (tx) => {
1434
+ if (!operation) {
1435
+ const sequenceNumber = await allocateNextSequenceInTransaction(tx, ctx.nodeId);
1436
+ operation = await (0, import_core7.createOperation)(
1437
+ {
1438
+ nodeId: ctx.nodeId,
1439
+ type: "delete",
1440
+ collection: ctx.collection,
1441
+ recordId: id,
1442
+ data: null,
1443
+ previousData: null,
1444
+ sequenceNumber,
1445
+ causalDeps,
1446
+ schemaVersion: ctx.schema.version
1447
+ },
1448
+ ctx.clock
1449
+ );
1450
+ ctx.causalTracker?.afterOperation(ctx.collection, operation.id, ctx.inTransaction);
1451
+ }
1452
+ if (ctx.relationEnforcer && !options?.skipReferentialEnforcement) {
1453
+ const enforcementResult = await ctx.relationEnforcer.enforceDelete(ctx.collection, id, tx, [
1454
+ operation.id
1455
+ ]);
1456
+ cascadedOps.push(...enforcementResult.operations);
1457
+ }
1458
+ const version = serializeRowVersion(operation.timestamp);
1459
+ const deleteQuery = buildSoftDeleteQuery(
1460
+ ctx.collection,
1461
+ id,
1462
+ operation.timestamp.wallTime,
1463
+ version
1464
+ );
1465
+ const opInsert = buildInsertQuery(
1466
+ `_kora_ops_${ctx.collection}`,
1467
+ serializeOperation(operation)
1468
+ );
1469
+ await tx.execute(deleteQuery.sql, deleteQuery.params);
1470
+ await tx.execute(opInsert.sql, opInsert.params);
1471
+ });
1472
+ if (!operation) {
1473
+ throw new Error("Delete operation was not created");
1474
+ }
1475
+ ctx.onMutation(ctx.collection, operation);
1476
+ for (const cascadedOp of cascadedOps) {
1477
+ ctx.onMutation(cascadedOp.collection, cascadedOp);
1478
+ }
1479
+ return [operation, ...cascadedOps];
1480
+ }
1481
+
1482
+ // src/mutations/execute-insert.ts
1483
+ var import_core8 = require("@korajs/core");
1484
+ init_serializer();
1485
+ async function executeInsert(ctx, data) {
1486
+ const validated = (0, import_core8.validateRecord)(ctx.collection, ctx.definition, data, "insert");
1487
+ const recordId = (0, import_core8.generateUUIDv7)();
1488
+ for (const [fieldName, descriptor] of Object.entries(ctx.definition.fields)) {
1489
+ if (descriptor.auto && descriptor.kind === "timestamp") {
1490
+ validated[fieldName] = Date.now();
1491
+ }
1492
+ }
1493
+ const causalDeps = resolveCausalDeps(ctx);
1494
+ let operation;
1495
+ let record;
1496
+ await ctx.adapter.transaction(async (tx) => {
1497
+ const sequenceNumber = await allocateNextSequenceInTransaction(tx, ctx.nodeId);
1498
+ operation = await (0, import_core8.createOperation)(
1499
+ {
1500
+ nodeId: ctx.nodeId,
1501
+ type: "insert",
1502
+ collection: ctx.collection,
1503
+ recordId,
1504
+ data: { ...validated },
1505
+ previousData: null,
1506
+ sequenceNumber,
1507
+ causalDeps,
1508
+ schemaVersion: ctx.schema.version
1509
+ },
1510
+ ctx.clock
1511
+ );
1512
+ ctx.causalTracker?.afterOperation(ctx.collection, operation.id, ctx.inTransaction);
1513
+ const serializedData = serializeRecord(validated, ctx.definition.fields);
1514
+ const version = serializeRowVersion(operation.timestamp);
1515
+ record = {
1516
+ id: recordId,
1517
+ ...serializedData,
1518
+ _created_at: operation.timestamp.wallTime,
1519
+ _updated_at: operation.timestamp.wallTime,
1520
+ _version: version
1521
+ };
1522
+ const builtInsert = buildInsertQuery(ctx.collection, record);
1523
+ const opInsert = buildInsertQuery(
1524
+ `_kora_ops_${ctx.collection}`,
1525
+ serializeOperation(operation)
1526
+ );
1527
+ await tx.execute(builtInsert.sql, builtInsert.params);
1528
+ await tx.execute(opInsert.sql, opInsert.params);
1529
+ });
1530
+ ctx.onMutation(ctx.collection, operation);
1531
+ return {
1532
+ id: recordId,
1533
+ ...validated,
1534
+ createdAt: operation.timestamp.wallTime,
1535
+ updatedAt: operation.timestamp.wallTime
1536
+ };
1537
+ }
1538
+
1539
+ // src/mutations/execute-update.ts
1540
+ var import_core10 = require("@korajs/core");
1541
+ init_serializer();
1542
+
1543
+ // src/state-machine/state-validator.ts
1544
+ var import_core9 = require("@korajs/core");
1545
+ var InvalidStateTransitionError = class extends import_core9.KoraError {
1546
+ constructor(collection, recordId, field, fromState, toState, allowedStates) {
1547
+ super(
1548
+ `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)"}`,
1549
+ "INVALID_STATE_TRANSITION",
1550
+ { collection, recordId, field, fromState, toState, allowedStates }
1551
+ );
1552
+ this.collection = collection;
1553
+ this.recordId = recordId;
1554
+ this.field = field;
1555
+ this.fromState = fromState;
1556
+ this.toState = toState;
1557
+ this.allowedStates = allowedStates;
1558
+ this.name = "InvalidStateTransitionError";
1559
+ }
1560
+ collection;
1561
+ recordId;
1562
+ field;
1563
+ fromState;
470
1564
  toState;
471
1565
  allowedStates;
472
1566
  };
@@ -482,7 +1576,7 @@ function validateStateTransition(collectionName, recordId, stateMachine, current
482
1576
  collection: collectionName,
483
1577
  transitions: stateMachine.transitions
484
1578
  };
485
- const transitionResult = (0, import_core3.validateTransition)(constraint, currentState, newState);
1579
+ const transitionResult = (0, import_core9.validateTransition)(constraint, currentState, newState);
486
1580
  if (transitionResult.valid) {
487
1581
  return { valid: true, allowedStates: transitionResult.allowedTargets };
488
1582
  }
@@ -528,18 +1622,103 @@ function validateUpdateStateMachine(collectionName, recordId, collectionDef, cur
528
1622
  return filtered;
529
1623
  }
530
1624
 
1625
+ // src/mutations/execute-update.ts
1626
+ async function executeUpdate(ctx, id, data) {
1627
+ const currentRows = await ctx.adapter.query(
1628
+ `SELECT * FROM ${ctx.collection} WHERE id = ? AND _deleted = 0`,
1629
+ [id]
1630
+ );
1631
+ const currentRow = currentRows[0];
1632
+ if (!currentRow) {
1633
+ throw new RecordNotFoundError(ctx.collection, id);
1634
+ }
1635
+ let validated = (0, import_core10.validateRecord)(ctx.collection, ctx.definition, data, "update");
1636
+ const currentRecord = deserializeRecord(currentRow, ctx.definition.fields);
1637
+ validated = validateUpdateStateMachine(
1638
+ ctx.collection,
1639
+ id,
1640
+ ctx.definition,
1641
+ currentRecord,
1642
+ validated
1643
+ );
1644
+ if (Object.keys(validated).length === 0) {
1645
+ return { ...currentRecord };
1646
+ }
1647
+ const previousData = {};
1648
+ const resolvedData = {};
1649
+ const atomicOps = {};
1650
+ for (const key of Object.keys(validated)) {
1651
+ const value = validated[key];
1652
+ previousData[key] = currentRecord[key];
1653
+ if ((0, import_core10.isAtomicOp)(value)) {
1654
+ resolvedData[key] = (0, import_core10.resolveAtomicOp)(currentRecord[key], value);
1655
+ atomicOps[key] = (0, import_core10.toAtomicOp)(value);
1656
+ } else {
1657
+ resolvedData[key] = value;
1658
+ }
1659
+ }
1660
+ const hasAtomicOps = Object.keys(atomicOps).length > 0;
1661
+ const causalDeps = resolveCausalDeps(ctx);
1662
+ let operation;
1663
+ await ctx.adapter.transaction(async (tx) => {
1664
+ const sequenceNumber = await allocateNextSequenceInTransaction(tx, ctx.nodeId);
1665
+ operation = await (0, import_core10.createOperation)(
1666
+ {
1667
+ nodeId: ctx.nodeId,
1668
+ type: "update",
1669
+ collection: ctx.collection,
1670
+ recordId: id,
1671
+ data: { ...resolvedData },
1672
+ previousData,
1673
+ sequenceNumber,
1674
+ causalDeps,
1675
+ schemaVersion: ctx.schema.version,
1676
+ ...hasAtomicOps ? { atomicOps } : {}
1677
+ },
1678
+ ctx.clock
1679
+ );
1680
+ ctx.causalTracker?.afterOperation(ctx.collection, operation.id, ctx.inTransaction);
1681
+ const serializedChanges = serializeRecord(resolvedData, ctx.definition.fields);
1682
+ const version = serializeRowVersion(operation.timestamp);
1683
+ const updateQuery = buildUpdateQuery(ctx.collection, id, {
1684
+ ...serializedChanges,
1685
+ _updated_at: operation.timestamp.wallTime,
1686
+ _version: version
1687
+ });
1688
+ const opInsert = buildInsertQuery(
1689
+ `_kora_ops_${ctx.collection}`,
1690
+ serializeOperation(operation)
1691
+ );
1692
+ await tx.execute(updateQuery.sql, updateQuery.params);
1693
+ await tx.execute(opInsert.sql, opInsert.params);
1694
+ });
1695
+ ctx.onMutation(ctx.collection, operation);
1696
+ const rows = await ctx.adapter.query(
1697
+ `SELECT * FROM ${ctx.collection} WHERE id = ? AND _deleted = 0`,
1698
+ [id]
1699
+ );
1700
+ const row = rows[0];
1701
+ if (!row) {
1702
+ throw new RecordNotFoundError(ctx.collection, id);
1703
+ }
1704
+ return deserializeRecord(row, ctx.definition.fields);
1705
+ }
1706
+
531
1707
  // src/collection/collection.ts
1708
+ init_serializer();
532
1709
  var Collection = class {
533
- constructor(name, definition, schema, adapter, clock, nodeId, getSequenceNumber, onMutation, relationEnforcer) {
1710
+ constructor(name, definition, schema, adapter, clock, nodeId, allocateSequenceNumber, onMutation, relationEnforcer, mutationHandler, causalTracker) {
534
1711
  this.name = name;
535
1712
  this.definition = definition;
536
1713
  this.schema = schema;
537
1714
  this.adapter = adapter;
538
1715
  this.clock = clock;
539
1716
  this.nodeId = nodeId;
540
- this.getSequenceNumber = getSequenceNumber;
1717
+ this.allocateSequenceNumber = allocateSequenceNumber;
541
1718
  this.onMutation = onMutation;
542
- this.relationEnforcer = relationEnforcer ?? null;
1719
+ this.relationEnforcer = relationEnforcer;
1720
+ this.mutationHandler = mutationHandler;
1721
+ this.causalTracker = causalTracker;
543
1722
  }
544
1723
  name;
545
1724
  definition;
@@ -547,72 +1726,32 @@ var Collection = class {
547
1726
  adapter;
548
1727
  clock;
549
1728
  nodeId;
550
- getSequenceNumber;
1729
+ allocateSequenceNumber;
551
1730
  onMutation;
552
1731
  relationEnforcer;
553
- /**
554
- * Insert a new record into the collection.
555
- * Generates a UUID v7 for the id, validates data, and persists atomically.
556
- *
557
- * @param data - The record data (auto fields and defaults are applied automatically)
558
- * @returns The inserted record with id, createdAt, updatedAt
559
- */
560
- async insert(data) {
561
- const validated = (0, import_core4.validateRecord)(this.name, this.definition, data, "insert");
562
- const recordId = (0, import_core4.generateUUIDv7)();
563
- const now = Date.now();
564
- for (const [fieldName, descriptor] of Object.entries(this.definition.fields)) {
565
- if (descriptor.auto && descriptor.kind === "timestamp") {
566
- validated[fieldName] = now;
567
- }
568
- }
569
- const sequenceNumber = this.getSequenceNumber();
570
- const operation = await (0, import_core4.createOperation)(
571
- {
572
- nodeId: this.nodeId,
573
- type: "insert",
574
- collection: this.name,
575
- recordId,
576
- data: { ...validated },
577
- previousData: null,
578
- sequenceNumber,
579
- causalDeps: [],
580
- schemaVersion: this.schema.version
581
- },
582
- this.clock
583
- );
584
- const serializedData = serializeRecord(validated, this.definition.fields);
585
- const record = {
586
- id: recordId,
587
- ...serializedData,
588
- _created_at: now,
589
- _updated_at: now
590
- };
591
- const insertQuery = buildInsertQuery(this.name, record);
592
- const opRow = serializeOperation(operation);
593
- const opInsert = buildInsertQuery(
594
- `_kora_ops_${this.name}`,
595
- opRow
596
- );
597
- await this.adapter.transaction(async (tx) => {
598
- await tx.execute(insertQuery.sql, insertQuery.params);
599
- await tx.execute(opInsert.sql, opInsert.params);
600
- await tx.execute(
601
- "INSERT OR REPLACE INTO _kora_version_vector (node_id, sequence_number) VALUES (?, ?)",
602
- [this.nodeId, sequenceNumber]
603
- );
604
- });
605
- this.onMutation(this.name, operation);
1732
+ mutationHandler;
1733
+ causalTracker;
1734
+ mutationContext() {
606
1735
  return {
607
- id: recordId,
608
- ...validated,
609
- createdAt: now,
610
- updatedAt: now
1736
+ collection: this.name,
1737
+ definition: this.definition,
1738
+ schema: this.schema,
1739
+ adapter: this.adapter,
1740
+ clock: this.clock,
1741
+ nodeId: this.nodeId,
1742
+ allocateSequenceNumber: this.allocateSequenceNumber,
1743
+ onMutation: this.onMutation,
1744
+ relationEnforcer: this.relationEnforcer,
1745
+ causalTracker: this.causalTracker,
1746
+ inTransaction: false
611
1747
  };
612
1748
  }
613
- /**
614
- * Find a record by its ID. Returns null if not found or soft-deleted.
615
- */
1749
+ async insert(data) {
1750
+ if (this.mutationHandler) {
1751
+ return this.mutationHandler.insert(this.name, data);
1752
+ }
1753
+ return executeInsert(this.mutationContext(), data);
1754
+ }
616
1755
  async findById(id) {
617
1756
  const rows = await this.adapter.query(
618
1757
  `SELECT * FROM ${this.name} WHERE id = ? AND _deleted = 0`,
@@ -622,175 +1761,34 @@ var Collection = class {
622
1761
  if (!row) return null;
623
1762
  return deserializeRecord(row, this.definition.fields);
624
1763
  }
625
- /**
626
- * Update an existing record. Only the provided fields are changed.
627
- *
628
- * @param id - The record ID to update
629
- * @param data - Partial data with only the fields to change
630
- * @returns The updated record
631
- * @throws {RecordNotFoundError} If the record doesn't exist or is deleted
632
- */
633
1764
  async update(id, data) {
634
- const currentRows = await this.adapter.query(
635
- `SELECT * FROM ${this.name} WHERE id = ? AND _deleted = 0`,
636
- [id]
637
- );
638
- const currentRow = currentRows[0];
639
- if (!currentRow) {
640
- throw new RecordNotFoundError(this.name, id);
641
- }
642
- let validated = (0, import_core4.validateRecord)(this.name, this.definition, data, "update");
643
- const now = Date.now();
644
- const currentRecord = deserializeRecord(currentRow, this.definition.fields);
645
- validated = validateUpdateStateMachine(
646
- this.name,
647
- id,
648
- this.definition,
649
- currentRecord,
650
- validated
651
- );
652
- if (Object.keys(validated).length === 0) {
653
- const fullRecord = await this.findById(id);
654
- if (!fullRecord) {
655
- throw new RecordNotFoundError(this.name, id);
656
- }
657
- return fullRecord;
1765
+ if (this.mutationHandler) {
1766
+ return this.mutationHandler.update(this.name, id, data);
658
1767
  }
659
- const previousData = {};
660
- const resolvedData = {};
661
- const atomicOps = {};
662
- for (const key of Object.keys(validated)) {
663
- const value = validated[key];
664
- previousData[key] = currentRecord[key];
665
- if ((0, import_core4.isAtomicOp)(value)) {
666
- resolvedData[key] = (0, import_core4.resolveAtomicOp)(currentRecord[key], value);
667
- atomicOps[key] = (0, import_core4.toAtomicOp)(value);
668
- } else {
669
- resolvedData[key] = value;
670
- }
671
- }
672
- const hasAtomicOps = Object.keys(atomicOps).length > 0;
673
- const sequenceNumber = this.getSequenceNumber();
674
- const operation = await (0, import_core4.createOperation)(
675
- {
676
- nodeId: this.nodeId,
677
- type: "update",
678
- collection: this.name,
679
- recordId: id,
680
- data: { ...resolvedData },
681
- previousData,
682
- sequenceNumber,
683
- causalDeps: [],
684
- schemaVersion: this.schema.version,
685
- ...hasAtomicOps ? { atomicOps } : {}
686
- },
687
- this.clock
688
- );
689
- const serializedChanges = serializeRecord(resolvedData, this.definition.fields);
690
- const updateQuery = buildUpdateQuery(this.name, id, {
691
- ...serializedChanges,
692
- _updated_at: now
693
- });
694
- const opRow = serializeOperation(operation);
695
- const opInsert = buildInsertQuery(
696
- `_kora_ops_${this.name}`,
697
- opRow
698
- );
699
- await this.adapter.transaction(async (tx) => {
700
- await tx.execute(updateQuery.sql, updateQuery.params);
701
- await tx.execute(opInsert.sql, opInsert.params);
702
- await tx.execute(
703
- "INSERT OR REPLACE INTO _kora_version_vector (node_id, sequence_number) VALUES (?, ?)",
704
- [this.nodeId, sequenceNumber]
705
- );
706
- });
707
- this.onMutation(this.name, operation);
708
- const updatedRow = await this.findById(id);
709
- if (!updatedRow) {
710
- throw new RecordNotFoundError(this.name, id);
711
- }
712
- return updatedRow;
1768
+ return executeUpdate(this.mutationContext(), id, data);
713
1769
  }
714
- /**
715
- * Soft-delete a record by its ID.
716
- *
717
- * When a RelationEnforcer is configured, referential integrity policies
718
- * are enforced within the same transaction:
719
- * - **cascade**: Recursively deletes all referencing records
720
- * - **set-null**: Sets foreign keys to null on referencing records
721
- * - **restrict**: Throws ReferentialIntegrityError if references exist
722
- * - **no-action**: Does nothing (dangling references are left)
723
- *
724
- * All cascaded operations are created atomically within the same transaction
725
- * and share causal dependencies with the original delete.
726
- *
727
- * @param id - The record ID to delete
728
- * @throws {RecordNotFoundError} If the record doesn't exist or is already deleted
729
- * @throws {ReferentialIntegrityError} If a 'restrict' policy is violated
730
- */
731
- async delete(id) {
732
- const currentRows = await this.adapter.query(
733
- `SELECT * FROM ${this.name} WHERE id = ? AND _deleted = 0`,
734
- [id]
735
- );
736
- if (!currentRows[0]) {
737
- throw new RecordNotFoundError(this.name, id);
738
- }
739
- const now = Date.now();
740
- const sequenceNumber = this.getSequenceNumber();
741
- const operation = await (0, import_core4.createOperation)(
742
- {
743
- nodeId: this.nodeId,
744
- type: "delete",
745
- collection: this.name,
746
- recordId: id,
747
- data: null,
748
- previousData: null,
749
- sequenceNumber,
750
- causalDeps: [],
751
- schemaVersion: this.schema.version
752
- },
753
- this.clock
754
- );
755
- const deleteQuery = buildSoftDeleteQuery(this.name, id, now);
756
- const opRow = serializeOperation(operation);
757
- const opInsert = buildInsertQuery(
758
- `_kora_ops_${this.name}`,
759
- opRow
760
- );
761
- const cascadedOps = [];
762
- await this.adapter.transaction(async (tx) => {
763
- if (this.relationEnforcer) {
764
- const enforcementResult = await this.relationEnforcer.enforceDelete(
765
- this.name,
766
- id,
767
- tx,
768
- [operation.id]
769
- );
770
- cascadedOps.push(...enforcementResult.operations);
771
- }
772
- await tx.execute(deleteQuery.sql, deleteQuery.params);
773
- await tx.execute(opInsert.sql, opInsert.params);
774
- await tx.execute(
775
- "INSERT OR REPLACE INTO _kora_version_vector (node_id, sequence_number) VALUES (?, ?)",
776
- [this.nodeId, sequenceNumber]
777
- );
778
- });
779
- this.onMutation(this.name, operation);
780
- for (const cascadedOp of cascadedOps) {
781
- this.onMutation(cascadedOp.collection, cascadedOp);
1770
+ async delete(id) {
1771
+ if (this.mutationHandler) {
1772
+ await this.mutationHandler.delete(this.name, id);
1773
+ return;
782
1774
  }
1775
+ await executeDelete(this.mutationContext(), id);
783
1776
  }
784
- /** Get the collection name */
785
1777
  getName() {
786
1778
  return this.name;
787
1779
  }
788
- /** Get the collection definition */
789
1780
  getDefinition() {
790
1781
  return this.definition;
791
1782
  }
1783
+ /** Replace the mutation handler (e.g. after ApplyPipeline is wired in createApp). */
1784
+ setMutationHandler(handler) {
1785
+ this.mutationHandler = handler;
1786
+ }
792
1787
  };
793
1788
 
1789
+ // src/query/query-builder.ts
1790
+ init_serializer();
1791
+
794
1792
  // src/query/pluralize.ts
795
1793
  function isVowel(char) {
796
1794
  if (!char) return false;
@@ -1085,7 +2083,8 @@ var QueryError2 = class extends Error {
1085
2083
  };
1086
2084
 
1087
2085
  // src/relations/relation-enforcer.ts
1088
- var import_core5 = require("@korajs/core");
2086
+ var import_core11 = require("@korajs/core");
2087
+ init_serializer();
1089
2088
 
1090
2089
  // src/relations/relation-lookup.ts
1091
2090
  function buildRelationLookup(schema) {
@@ -1109,7 +2108,7 @@ function getIncomingRelations(lookup, collection) {
1109
2108
  }
1110
2109
 
1111
2110
  // src/relations/relation-enforcer.ts
1112
- var ReferentialIntegrityError = class extends import_core5.KoraError {
2111
+ var ReferentialIntegrityError = class extends import_core11.KoraError {
1113
2112
  constructor(collection, recordId, referencingCollection, relationName, referencingCount) {
1114
2113
  super(
1115
2114
  `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.`,
@@ -1131,13 +2130,11 @@ var RelationEnforcer = class {
1131
2130
  adapter;
1132
2131
  clock;
1133
2132
  nodeId;
1134
- getSequenceNumber;
1135
2133
  constructor(config) {
1136
2134
  this.schema = config.schema;
1137
2135
  this.adapter = config.adapter;
1138
2136
  this.clock = config.clock;
1139
2137
  this.nodeId = config.nodeId;
1140
- this.getSequenceNumber = config.getSequenceNumber;
1141
2138
  this.lookup = buildRelationLookup(config.schema);
1142
2139
  }
1143
2140
  /**
@@ -1199,8 +2196,8 @@ var RelationEnforcer = class {
1199
2196
  const operations = [];
1200
2197
  for (const row of referencingRows) {
1201
2198
  const now = Date.now();
1202
- const sequenceNumber = this.getSequenceNumber();
1203
- const operation = await (0, import_core5.createOperation)(
2199
+ const sequenceNumber = await allocateNextSequenceInTransaction(tx, this.nodeId);
2200
+ const operation = await (0, import_core11.createOperation)(
1204
2201
  {
1205
2202
  nodeId: this.nodeId,
1206
2203
  type: "delete",
@@ -1216,16 +2213,11 @@ var RelationEnforcer = class {
1216
2213
  );
1217
2214
  const deleteQuery = buildSoftDeleteQuery(sourceCollection, row.id, now);
1218
2215
  await tx.execute(deleteQuery.sql, deleteQuery.params);
1219
- const opRow = serializeOperation(operation);
1220
2216
  const opInsert = buildInsertQuery(
1221
2217
  `_kora_ops_${sourceCollection}`,
1222
- opRow
2218
+ serializeOperation(operation)
1223
2219
  );
1224
2220
  await tx.execute(opInsert.sql, opInsert.params);
1225
- await tx.execute(
1226
- "INSERT OR REPLACE INTO _kora_version_vector (node_id, sequence_number) VALUES (?, ?)",
1227
- [this.nodeId, sequenceNumber]
1228
- );
1229
2221
  operations.push(operation);
1230
2222
  const cascadeResult = await this.enforceDelete(sourceCollection, row.id, tx, [operation.id]);
1231
2223
  operations.push(...cascadeResult.operations);
@@ -1251,10 +2243,10 @@ var RelationEnforcer = class {
1251
2243
  const operations = [];
1252
2244
  for (const row of referencingRows) {
1253
2245
  const now = Date.now();
1254
- const sequenceNumber = this.getSequenceNumber();
2246
+ const sequenceNumber = await allocateNextSequenceInTransaction(tx, this.nodeId);
1255
2247
  const updateData = { [foreignKeyField]: null };
1256
2248
  const previousData = { [foreignKeyField]: deletedRecordId };
1257
- const operation = await (0, import_core5.createOperation)(
2249
+ const operation = await (0, import_core11.createOperation)(
1258
2250
  {
1259
2251
  nodeId: this.nodeId,
1260
2252
  type: "update",
@@ -1280,10 +2272,6 @@ var RelationEnforcer = class {
1280
2272
  opRow
1281
2273
  );
1282
2274
  await tx.execute(opInsert.sql, opInsert.params);
1283
- await tx.execute(
1284
- "INSERT OR REPLACE INTO _kora_version_vector (node_id, sequence_number) VALUES (?, ?)",
1285
- [this.nodeId, sequenceNumber]
1286
- );
1287
2275
  operations.push(operation);
1288
2276
  }
1289
2277
  return operations;
@@ -1318,8 +2306,172 @@ var RelationEnforcer = class {
1318
2306
  }
1319
2307
  };
1320
2308
 
2309
+ // src/replay/replay-to.ts
2310
+ var import_core12 = require("@korajs/core");
2311
+ var import_internal = require("@korajs/core/internal");
2312
+ function collectCausalClosure(allOps, targetOperationId) {
2313
+ const opMap = /* @__PURE__ */ new Map();
2314
+ for (const op of allOps) {
2315
+ opMap.set(op.id, op);
2316
+ }
2317
+ const target = opMap.get(targetOperationId);
2318
+ if (!target) {
2319
+ throw new import_core12.OperationError(
2320
+ `Operation "${targetOperationId}" not found in the local operation log`,
2321
+ {
2322
+ operationId: targetOperationId
2323
+ }
2324
+ );
2325
+ }
2326
+ const included = /* @__PURE__ */ new Set();
2327
+ const stack = [targetOperationId];
2328
+ while (stack.length > 0) {
2329
+ const id = stack.pop();
2330
+ if (id === void 0 || included.has(id)) {
2331
+ continue;
2332
+ }
2333
+ included.add(id);
2334
+ const op = opMap.get(id);
2335
+ if (!op) {
2336
+ continue;
2337
+ }
2338
+ for (const depId of op.causalDeps) {
2339
+ if (opMap.has(depId)) {
2340
+ stack.push(depId);
2341
+ }
2342
+ }
2343
+ }
2344
+ const subset = allOps.filter((op) => included.has(op.id));
2345
+ return (0, import_internal.topologicalSort)(subset);
2346
+ }
2347
+ function buildReplaySnapshot(schema, allOps, targetOperationId) {
2348
+ const operationsApplied = collectCausalClosure(allOps, targetOperationId);
2349
+ const targetOperation = operationsApplied.find((op) => op.id === targetOperationId);
2350
+ if (!targetOperation) {
2351
+ throw new import_core12.OperationError(`Operation "${targetOperationId}" not found after causal sort`, {
2352
+ operationId: targetOperationId
2353
+ });
2354
+ }
2355
+ const memory = /* @__PURE__ */ new Map();
2356
+ for (const op of operationsApplied) {
2357
+ applyOperationToMemory(memory, op, schema);
2358
+ }
2359
+ const collections = materializeCollections(schema, memory);
2360
+ return {
2361
+ targetOperation,
2362
+ operationsApplied,
2363
+ collections,
2364
+ findRecord(collection, recordId) {
2365
+ const colMap = memory.get(collection);
2366
+ const record = colMap?.get(recordId);
2367
+ if (!record || record.deleted) {
2368
+ return null;
2369
+ }
2370
+ const definition = schema.collections[collection];
2371
+ if (!definition) {
2372
+ return null;
2373
+ }
2374
+ return toCollectionRecord(record, definition);
2375
+ }
2376
+ };
2377
+ }
2378
+ function applyOperationToMemory(state, op, schema) {
2379
+ const definition = schema.collections[op.collection];
2380
+ if (!definition) {
2381
+ return;
2382
+ }
2383
+ let colMap = state.get(op.collection);
2384
+ if (!colMap) {
2385
+ colMap = /* @__PURE__ */ new Map();
2386
+ state.set(op.collection, colMap);
2387
+ }
2388
+ const wallTime = op.timestamp.wallTime;
2389
+ switch (op.type) {
2390
+ case "insert": {
2391
+ if (!op.data) {
2392
+ return;
2393
+ }
2394
+ colMap.set(op.recordId, {
2395
+ id: op.recordId,
2396
+ fields: { ...op.data },
2397
+ deleted: false,
2398
+ createdAt: wallTime,
2399
+ updatedAt: wallTime
2400
+ });
2401
+ break;
2402
+ }
2403
+ case "update": {
2404
+ if (!op.data) {
2405
+ return;
2406
+ }
2407
+ const existing = colMap.get(op.recordId);
2408
+ if (existing && !existing.deleted) {
2409
+ existing.fields = { ...existing.fields, ...op.data };
2410
+ existing.updatedAt = wallTime;
2411
+ break;
2412
+ }
2413
+ if (existing?.deleted) {
2414
+ return;
2415
+ }
2416
+ colMap.set(op.recordId, {
2417
+ id: op.recordId,
2418
+ fields: { ...op.data },
2419
+ deleted: false,
2420
+ createdAt: wallTime,
2421
+ updatedAt: wallTime
2422
+ });
2423
+ break;
2424
+ }
2425
+ case "delete": {
2426
+ const existing = colMap.get(op.recordId);
2427
+ if (existing) {
2428
+ existing.deleted = true;
2429
+ existing.updatedAt = wallTime;
2430
+ return;
2431
+ }
2432
+ colMap.set(op.recordId, {
2433
+ id: op.recordId,
2434
+ fields: {},
2435
+ deleted: true,
2436
+ createdAt: wallTime,
2437
+ updatedAt: wallTime
2438
+ });
2439
+ break;
2440
+ }
2441
+ }
2442
+ }
2443
+ function materializeCollections(schema, memory) {
2444
+ const collections = {};
2445
+ for (const [collectionName, definition] of Object.entries(schema.collections)) {
2446
+ const colMap = memory.get(collectionName);
2447
+ const records = [];
2448
+ if (colMap) {
2449
+ for (const record of colMap.values()) {
2450
+ if (!record.deleted) {
2451
+ records.push(toCollectionRecord(record, definition));
2452
+ }
2453
+ }
2454
+ }
2455
+ collections[collectionName] = records;
2456
+ }
2457
+ return collections;
2458
+ }
2459
+ function toCollectionRecord(record, definition) {
2460
+ const result = {
2461
+ id: record.id,
2462
+ createdAt: record.createdAt,
2463
+ updatedAt: record.updatedAt
2464
+ };
2465
+ for (const [fieldName] of Object.entries(definition.fields)) {
2466
+ if (fieldName in record.fields) {
2467
+ result[fieldName] = record.fields[fieldName];
2468
+ }
2469
+ }
2470
+ return result;
2471
+ }
2472
+
1321
2473
  // src/sequences/sequence-manager.ts
1322
- var import_core6 = require("@korajs/core");
2474
+ var import_core13 = require("@korajs/core");
1323
2475
  var SequenceManager = class {
1324
2476
  adapter;
1325
2477
  nodeId;
@@ -1337,7 +2489,7 @@ var SequenceManager = class {
1337
2489
  async next(name, config) {
1338
2490
  const scope = config?.scope ?? "";
1339
2491
  const startAt = config?.startAt ?? 1;
1340
- const format = config?.format ?? (0, import_core6.defaultSequenceFormat)(name);
2492
+ const format = config?.format ?? (0, import_core13.defaultSequenceFormat)(name);
1341
2493
  let counter = 0;
1342
2494
  await this.adapter.transaction(async (tx) => {
1343
2495
  const rows = await tx.query(
@@ -1359,7 +2511,7 @@ var SequenceManager = class {
1359
2511
  );
1360
2512
  }
1361
2513
  });
1362
- return (0, import_core6.formatSequenceValue)(format, counter, this.nodeId);
2514
+ return (0, import_core13.formatSequenceValue)(format, counter, this.nodeId);
1363
2515
  }
1364
2516
  /**
1365
2517
  * Get the current counter value without incrementing.
@@ -1401,6 +2553,9 @@ var SequenceManager = class {
1401
2553
  }
1402
2554
  };
1403
2555
 
2556
+ // src/store/store.ts
2557
+ init_serializer();
2558
+
1404
2559
  // src/subscription/bloom-filter.ts
1405
2560
  var FNV_OFFSET_BASIS = 2166136261;
1406
2561
  var FNV_PRIME = 16777619;
@@ -1561,6 +2716,7 @@ var SubscriptionManager = class {
1561
2716
  subscriptions = /* @__PURE__ */ new Map();
1562
2717
  pendingCollections = /* @__PURE__ */ new Set();
1563
2718
  flushScheduled = false;
2719
+ onQuerySubscribed;
1564
2720
  // Bloom filter state
1565
2721
  bloomFilter = null;
1566
2722
  bloomDirty = false;
@@ -1577,6 +2733,7 @@ var SubscriptionManager = class {
1577
2733
  this.bloomThreshold = options?.bloomThreshold ?? DEFAULT_BLOOM_THRESHOLD;
1578
2734
  this.bloomExpectedItems = options?.bloomExpectedItems ?? DEFAULT_BLOOM_EXPECTED_ITEMS;
1579
2735
  this.bloomFalsePositiveRate = options?.bloomFalsePositiveRate ?? DEFAULT_BLOOM_FALSE_POSITIVE_RATE;
2736
+ this.onQuerySubscribed = options?.onQuerySubscribed;
1580
2737
  }
1581
2738
  /**
1582
2739
  * Register a new subscription.
@@ -1597,9 +2754,11 @@ var SubscriptionManager = class {
1597
2754
  };
1598
2755
  this.subscriptions.set(id, subscription);
1599
2756
  this.bloomDirty = true;
2757
+ const externalCleanup = this.onQuerySubscribed?.(descriptor);
1600
2758
  return () => {
1601
2759
  this.subscriptions.delete(id);
1602
2760
  this.bloomDirty = true;
2761
+ externalCleanup?.();
1603
2762
  };
1604
2763
  }
1605
2764
  /**
@@ -1620,6 +2779,7 @@ var SubscriptionManager = class {
1620
2779
  };
1621
2780
  this.subscriptions.set(id, subscription);
1622
2781
  this.bloomDirty = true;
2782
+ const externalCleanup = this.onQuerySubscribed?.(descriptor);
1623
2783
  executeFn().then((results) => {
1624
2784
  if (this.subscriptions.has(id)) {
1625
2785
  subscription.lastResults = results;
@@ -1629,6 +2789,7 @@ var SubscriptionManager = class {
1629
2789
  return () => {
1630
2790
  this.subscriptions.delete(id);
1631
2791
  this.bloomDirty = true;
2792
+ externalCleanup?.();
1632
2793
  };
1633
2794
  }
1634
2795
  /**
@@ -1764,10 +2925,7 @@ var SubscriptionManager = class {
1764
2925
  * any included collection dependencies.
1765
2926
  */
1766
2927
  rebuildBloomFilter() {
1767
- const filter = new SubscriptionBloomFilter(
1768
- this.bloomExpectedItems,
1769
- this.bloomFalsePositiveRate
1770
- );
2928
+ const filter = new SubscriptionBloomFilter(this.bloomExpectedItems, this.bloomFalsePositiveRate);
1771
2929
  for (const sub of this.subscriptions.values()) {
1772
2930
  filter.add(sub.descriptor.collection);
1773
2931
  if (sub.descriptor.includeCollections) {
@@ -1787,18 +2945,33 @@ var SubscriptionManager = class {
1787
2945
  });
1788
2946
  }
1789
2947
  /**
1790
- * Compare two result sets. Uses length check + JSON comparison as pragmatic approach.
1791
- * Sufficient for typical query results. Can be optimized to id-based diffing if profiling shows need.
2948
+ * Compare two result sets. Uses ID-based comparison first (same length, same IDs),
2949
+ * then deep comparison of remaining fields when IDs match.
2950
+ * Avoids JSON.stringify which is O(n) and fragile to field ordering differences.
1792
2951
  */
1793
2952
  resultsEqual(prev, next) {
1794
2953
  if (prev.length !== next.length) return false;
1795
2954
  if (prev.length === 0) return true;
1796
- return JSON.stringify(prev) === JSON.stringify(next);
2955
+ for (let i = 0; i < prev.length; i++) {
2956
+ if (prev[i]?.id !== next[i]?.id) return false;
2957
+ }
2958
+ for (let i = 0; i < prev.length; i++) {
2959
+ const a = prev[i];
2960
+ const b = next[i];
2961
+ const keysA = Object.keys(a);
2962
+ const keysB = Object.keys(b);
2963
+ if (keysA.length !== keysB.length) return false;
2964
+ for (const key of keysA) {
2965
+ if (a[key] !== b[key]) return false;
2966
+ }
2967
+ }
2968
+ return true;
1797
2969
  }
1798
2970
  };
1799
2971
 
1800
2972
  // src/transaction/transaction-context.ts
1801
- var import_core7 = require("@korajs/core");
2973
+ var import_core14 = require("@korajs/core");
2974
+ init_serializer();
1802
2975
  var TransactionContext = class {
1803
2976
  transactionId;
1804
2977
  mutationName;
@@ -1808,7 +2981,7 @@ var TransactionContext = class {
1808
2981
  config;
1809
2982
  constructor(config) {
1810
2983
  this.config = config;
1811
- this.transactionId = (0, import_core7.generateUUIDv7)();
2984
+ this.transactionId = (0, import_core14.generateUUIDv7)();
1812
2985
  }
1813
2986
  /**
1814
2987
  * Set a human-readable mutation name for this transaction.
@@ -1855,16 +3028,48 @@ var TransactionContext = class {
1855
3028
  if (this.buffer.length === 0) {
1856
3029
  return { operations: [], affectedCollections: /* @__PURE__ */ new Set() };
1857
3030
  }
3031
+ const handler = this.config.localMutationHandler;
3032
+ if (handler?.commitTransaction) {
3033
+ return handler.commitTransaction({
3034
+ entries: this.buffer.map((entry) => ({
3035
+ operation: entry.operation,
3036
+ commands: entry.commands,
3037
+ collection: entry.collection
3038
+ })),
3039
+ transactionId: this.transactionId,
3040
+ ...this.mutationName !== void 0 ? { mutationName: this.mutationName } : {}
3041
+ });
3042
+ }
1858
3043
  const operations = [];
1859
3044
  const affectedCollections = /* @__PURE__ */ new Set();
1860
3045
  await this.config.adapter.transaction(async (tx) => {
1861
3046
  for (const entry of this.buffer) {
3047
+ if (entry.operation.type === "delete" && this.config.relationEnforcer) {
3048
+ const cascadeResult = await this.config.relationEnforcer.enforceDelete(
3049
+ entry.collection,
3050
+ entry.operation.recordId,
3051
+ tx,
3052
+ [entry.operation.id]
3053
+ );
3054
+ for (const cascadedOp of cascadeResult.operations) {
3055
+ operations.push(cascadedOp);
3056
+ affectedCollections.add(cascadedOp.collection);
3057
+ this.config.causalTracker?.afterOperation(cascadedOp.collection, cascadedOp.id, true);
3058
+ }
3059
+ }
1862
3060
  for (const cmd of entry.commands) {
1863
3061
  await tx.execute(cmd.sql, cmd.params);
1864
3062
  }
1865
3063
  operations.push(entry.operation);
1866
3064
  affectedCollections.add(entry.collection);
1867
3065
  }
3066
+ const finalSeq = this.config.sequenceAllocator.getHighWaterMark();
3067
+ if (finalSeq > 0) {
3068
+ await tx.execute(
3069
+ "INSERT OR REPLACE INTO _kora_version_vector (node_id, sequence_number) VALUES (?, ?)",
3070
+ [this.config.nodeId, finalSeq]
3071
+ );
3072
+ }
1868
3073
  });
1869
3074
  return { operations, affectedCollections };
1870
3075
  }
@@ -1891,16 +3096,17 @@ var TransactionContext = class {
1891
3096
  }
1892
3097
  async insert(collectionName, definition, data) {
1893
3098
  this.ensureActive();
1894
- const validated = (0, import_core7.validateRecord)(collectionName, definition, data, "insert");
1895
- const recordId = (0, import_core7.generateUUIDv7)();
3099
+ const validated = (0, import_core14.validateRecord)(collectionName, definition, data, "insert");
3100
+ const recordId = (0, import_core14.generateUUIDv7)();
1896
3101
  const now = Date.now();
1897
3102
  for (const [fieldName, descriptor] of Object.entries(definition.fields)) {
1898
3103
  if (descriptor.auto && descriptor.kind === "timestamp") {
1899
3104
  validated[fieldName] = now;
1900
3105
  }
1901
3106
  }
1902
- const sequenceNumber = this.config.getSequenceNumber();
1903
- const operation = await (0, import_core7.createOperation)(
3107
+ const sequenceNumber = await this.config.sequenceAllocator.allocate();
3108
+ const causalDeps = this.config.causalTracker?.nextCausalDeps(collectionName, true) ?? [];
3109
+ const operation = await (0, import_core14.createOperation)(
1904
3110
  {
1905
3111
  nodeId: this.config.nodeId,
1906
3112
  type: "insert",
@@ -1909,19 +3115,22 @@ var TransactionContext = class {
1909
3115
  data: { ...validated },
1910
3116
  previousData: null,
1911
3117
  sequenceNumber,
1912
- causalDeps: [],
3118
+ causalDeps,
1913
3119
  schemaVersion: this.config.schema.version,
1914
3120
  transactionId: this.transactionId,
1915
3121
  ...this.mutationName !== void 0 ? { mutationName: this.mutationName } : {}
1916
3122
  },
1917
3123
  this.config.clock
1918
3124
  );
3125
+ this.config.causalTracker?.afterOperation(collectionName, operation.id, true);
1919
3126
  const serializedData = serializeRecord(validated, definition.fields);
3127
+ const version = serializeRowVersion(operation.timestamp);
1920
3128
  const record = {
1921
3129
  id: recordId,
1922
3130
  ...serializedData,
1923
- _created_at: now,
1924
- _updated_at: now
3131
+ _created_at: operation.timestamp.wallTime,
3132
+ _updated_at: operation.timestamp.wallTime,
3133
+ _version: version
1925
3134
  };
1926
3135
  const insertQuery = buildInsertQuery(collectionName, record);
1927
3136
  const opRow = serializeOperation(operation);
@@ -1934,11 +3143,7 @@ var TransactionContext = class {
1934
3143
  collection: collectionName,
1935
3144
  commands: [
1936
3145
  { sql: insertQuery.sql, params: insertQuery.params },
1937
- { sql: opInsert.sql, params: opInsert.params },
1938
- {
1939
- sql: "INSERT OR REPLACE INTO _kora_version_vector (node_id, sequence_number) VALUES (?, ?)",
1940
- params: [this.config.nodeId, sequenceNumber]
1941
- }
3146
+ { sql: opInsert.sql, params: opInsert.params }
1942
3147
  ]
1943
3148
  });
1944
3149
  return {
@@ -1954,7 +3159,7 @@ var TransactionContext = class {
1954
3159
  if (!currentRecord) {
1955
3160
  throw new RecordNotFoundError(collectionName, id);
1956
3161
  }
1957
- const validated = (0, import_core7.validateRecord)(collectionName, definition, data, "update");
3162
+ const validated = (0, import_core14.validateRecord)(collectionName, definition, data, "update");
1958
3163
  const now = Date.now();
1959
3164
  const previousData = {};
1960
3165
  const resolvedData = {};
@@ -1962,16 +3167,17 @@ var TransactionContext = class {
1962
3167
  for (const key of Object.keys(validated)) {
1963
3168
  const value = validated[key];
1964
3169
  previousData[key] = currentRecord[key];
1965
- if ((0, import_core7.isAtomicOp)(value)) {
1966
- resolvedData[key] = (0, import_core7.resolveAtomicOp)(currentRecord[key], value);
1967
- atomicOps[key] = (0, import_core7.toAtomicOp)(value);
3170
+ if ((0, import_core14.isAtomicOp)(value)) {
3171
+ resolvedData[key] = (0, import_core14.resolveAtomicOp)(currentRecord[key], value);
3172
+ atomicOps[key] = (0, import_core14.toAtomicOp)(value);
1968
3173
  } else {
1969
3174
  resolvedData[key] = value;
1970
3175
  }
1971
3176
  }
1972
3177
  const hasAtomicOps = Object.keys(atomicOps).length > 0;
1973
- const sequenceNumber = this.config.getSequenceNumber();
1974
- const operation = await (0, import_core7.createOperation)(
3178
+ const sequenceNumber = await this.config.sequenceAllocator.allocate();
3179
+ const causalDeps = this.config.causalTracker?.nextCausalDeps(collectionName, true) ?? [];
3180
+ const operation = await (0, import_core14.createOperation)(
1975
3181
  {
1976
3182
  nodeId: this.config.nodeId,
1977
3183
  type: "update",
@@ -1980,7 +3186,7 @@ var TransactionContext = class {
1980
3186
  data: { ...resolvedData },
1981
3187
  previousData,
1982
3188
  sequenceNumber,
1983
- causalDeps: [],
3189
+ causalDeps,
1984
3190
  schemaVersion: this.config.schema.version,
1985
3191
  transactionId: this.transactionId,
1986
3192
  ...this.mutationName !== void 0 ? { mutationName: this.mutationName } : {},
@@ -1988,10 +3194,13 @@ var TransactionContext = class {
1988
3194
  },
1989
3195
  this.config.clock
1990
3196
  );
3197
+ this.config.causalTracker?.afterOperation(collectionName, operation.id, true);
1991
3198
  const serializedChanges = serializeRecord(resolvedData, definition.fields);
3199
+ const version = serializeRowVersion(operation.timestamp);
1992
3200
  const updateQuery = buildUpdateQuery(collectionName, id, {
1993
3201
  ...serializedChanges,
1994
- _updated_at: now
3202
+ _updated_at: operation.timestamp.wallTime,
3203
+ _version: version
1995
3204
  });
1996
3205
  const opRow = serializeOperation(operation);
1997
3206
  const opInsert = buildInsertQuery(
@@ -2003,11 +3212,7 @@ var TransactionContext = class {
2003
3212
  collection: collectionName,
2004
3213
  commands: [
2005
3214
  { sql: updateQuery.sql, params: updateQuery.params },
2006
- { sql: opInsert.sql, params: opInsert.params },
2007
- {
2008
- sql: "INSERT OR REPLACE INTO _kora_version_vector (node_id, sequence_number) VALUES (?, ?)",
2009
- params: [this.config.nodeId, sequenceNumber]
2010
- }
3215
+ { sql: opInsert.sql, params: opInsert.params }
2011
3216
  ]
2012
3217
  });
2013
3218
  return {
@@ -2023,8 +3228,9 @@ var TransactionContext = class {
2023
3228
  throw new RecordNotFoundError(collectionName, id);
2024
3229
  }
2025
3230
  const now = Date.now();
2026
- const sequenceNumber = this.config.getSequenceNumber();
2027
- const operation = await (0, import_core7.createOperation)(
3231
+ const sequenceNumber = await this.config.sequenceAllocator.allocate();
3232
+ const causalDeps = this.config.causalTracker?.nextCausalDeps(collectionName, true) ?? [];
3233
+ const operation = await (0, import_core14.createOperation)(
2028
3234
  {
2029
3235
  nodeId: this.config.nodeId,
2030
3236
  type: "delete",
@@ -2033,13 +3239,14 @@ var TransactionContext = class {
2033
3239
  data: null,
2034
3240
  previousData: null,
2035
3241
  sequenceNumber,
2036
- causalDeps: [],
3242
+ causalDeps,
2037
3243
  schemaVersion: this.config.schema.version,
2038
3244
  transactionId: this.transactionId,
2039
3245
  ...this.mutationName !== void 0 ? { mutationName: this.mutationName } : {}
2040
3246
  },
2041
3247
  this.config.clock
2042
3248
  );
3249
+ this.config.causalTracker?.afterOperation(collectionName, operation.id, true);
2043
3250
  const deleteQuery = buildSoftDeleteQuery(collectionName, id, now);
2044
3251
  const opRow = serializeOperation(operation);
2045
3252
  const opInsert = buildInsertQuery(
@@ -2051,11 +3258,7 @@ var TransactionContext = class {
2051
3258
  collection: collectionName,
2052
3259
  commands: [
2053
3260
  { sql: deleteQuery.sql, params: deleteQuery.params },
2054
- { sql: opInsert.sql, params: opInsert.params },
2055
- {
2056
- sql: "INSERT OR REPLACE INTO _kora_version_vector (node_id, sequence_number) VALUES (?, ?)",
2057
- params: [this.config.nodeId, sequenceNumber]
2058
- }
3261
+ { sql: opInsert.sql, params: opInsert.params }
2059
3262
  ]
2060
3263
  });
2061
3264
  }
@@ -2106,25 +3309,86 @@ var TransactionContext = class {
2106
3309
  }
2107
3310
  };
2108
3311
 
3312
+ // src/transaction/transaction-sequence.ts
3313
+ var TransactionSequenceAllocator = class {
3314
+ constructor(adapter, nodeId) {
3315
+ this.adapter = adapter;
3316
+ this.nodeId = nodeId;
3317
+ }
3318
+ adapter;
3319
+ nodeId;
3320
+ loaded = false;
3321
+ watermark = 0;
3322
+ async allocate() {
3323
+ if (!this.loaded) {
3324
+ this.watermark = await readSequenceNumber(this.adapter, this.nodeId);
3325
+ this.loaded = true;
3326
+ }
3327
+ this.watermark++;
3328
+ return this.watermark;
3329
+ }
3330
+ getHighWaterMark() {
3331
+ return this.watermark;
3332
+ }
3333
+ };
3334
+
3335
+ // src/store/tab-node-id.ts
3336
+ var import_core15 = require("@korajs/core");
3337
+ function perTabStorageKey(dbName) {
3338
+ return `kora-tab-node-${dbName}`;
3339
+ }
3340
+ function loadPerTabNodeId(dbName) {
3341
+ if (typeof sessionStorage === "undefined") {
3342
+ return null;
3343
+ }
3344
+ return sessionStorage.getItem(perTabStorageKey(dbName));
3345
+ }
3346
+ function savePerTabNodeId(dbName, nodeId) {
3347
+ if (typeof sessionStorage === "undefined") {
3348
+ return;
3349
+ }
3350
+ sessionStorage.setItem(perTabStorageKey(dbName), nodeId);
3351
+ }
3352
+ function resolvePerTabNodeId(dbName) {
3353
+ const existing = loadPerTabNodeId(dbName);
3354
+ if (existing) {
3355
+ return existing;
3356
+ }
3357
+ const nodeId = (0, import_core15.generateUUIDv7)();
3358
+ savePerTabNodeId(dbName, nodeId);
3359
+ return nodeId;
3360
+ }
3361
+
2109
3362
  // src/store/store.ts
2110
3363
  var Store = class {
2111
3364
  opened = false;
2112
3365
  nodeId = "";
2113
3366
  sequenceNumber = 0;
2114
- versionVector = (0, import_core8.createVersionVector)();
3367
+ versionVector = (0, import_core17.createVersionVector)();
2115
3368
  clock = null;
2116
3369
  collections = /* @__PURE__ */ new Map();
2117
- subscriptionManager = new SubscriptionManager();
3370
+ subscriptionManager;
2118
3371
  sequenceManager = null;
2119
3372
  schema;
2120
3373
  adapter;
2121
3374
  configNodeId;
3375
+ dbName;
3376
+ isolation;
2122
3377
  emitter;
3378
+ localMutationHandler;
3379
+ relationEnforcer = null;
3380
+ causalTracker = null;
2123
3381
  constructor(config) {
2124
3382
  this.schema = config.schema;
2125
3383
  this.adapter = config.adapter;
2126
3384
  this.configNodeId = config.nodeId;
3385
+ this.dbName = config.dbName ?? "kora-db";
3386
+ this.isolation = config.isolation ?? "shared";
2127
3387
  this.emitter = config.emitter ?? null;
3388
+ this.localMutationHandler = config.localMutationHandler ?? null;
3389
+ this.subscriptionManager = new SubscriptionManager({
3390
+ onQuerySubscribed: config.onQuerySubscribed
3391
+ });
2128
3392
  }
2129
3393
  /**
2130
3394
  * Open the store: initialize the database, load or generate a node ID,
@@ -2134,18 +3398,18 @@ var Store = class {
2134
3398
  await this.adapter.open(this.schema);
2135
3399
  await this.runMigrationsIfNeeded();
2136
3400
  this.nodeId = await this.loadOrGenerateNodeId();
2137
- this.clock = new import_core8.HybridLogicalClock(this.nodeId);
3401
+ this.clock = new import_core17.HybridLogicalClock(this.nodeId);
3402
+ this.causalTracker = new import_core17.CausalTracker();
2138
3403
  this.sequenceManager = new SequenceManager(this.adapter, this.nodeId);
2139
3404
  this.sequenceNumber = await this.loadSequenceNumber();
2140
3405
  this.versionVector = await this.loadVersionVector();
2141
3406
  const hasRelations = Object.keys(this.schema.relations).length > 0;
2142
- const relationEnforcer = hasRelations ? new RelationEnforcer({
3407
+ this.relationEnforcer = hasRelations ? new RelationEnforcer({
2143
3408
  schema: this.schema,
2144
3409
  adapter: this.adapter,
2145
3410
  clock: this.clock,
2146
- nodeId: this.nodeId,
2147
- getSequenceNumber: () => this.nextSequenceNumber()
2148
- }) : void 0;
3411
+ nodeId: this.nodeId
3412
+ }) : null;
2149
3413
  for (const [name, definition] of Object.entries(this.schema.collections)) {
2150
3414
  const col = new Collection(
2151
3415
  name,
@@ -2154,14 +3418,17 @@ var Store = class {
2154
3418
  this.adapter,
2155
3419
  this.clock,
2156
3420
  this.nodeId,
2157
- () => this.nextSequenceNumber(),
3421
+ () => this.allocateSequenceNumber(),
2158
3422
  (collectionName, operation) => {
3423
+ this.recordOperationSequence(operation);
2159
3424
  this.subscriptionManager.notify(collectionName, operation);
2160
3425
  if (this.emitter) {
2161
3426
  this.emitter.emit({ type: "operation:created", operation });
2162
3427
  }
2163
3428
  },
2164
- relationEnforcer
3429
+ this.relationEnforcer,
3430
+ this.localMutationHandler,
3431
+ this.causalTracker
2165
3432
  );
2166
3433
  this.collections.set(name, col);
2167
3434
  }
@@ -2227,7 +3494,7 @@ var Store = class {
2227
3494
  * Checks for duplicates, applies to the data table, persists the operation,
2228
3495
  * and updates the version vector.
2229
3496
  */
2230
- async applyRemoteOperation(op) {
3497
+ async applyRemoteOperation(op, options) {
2231
3498
  this.ensureOpen();
2232
3499
  const collection = op.collection;
2233
3500
  const definition = this.schema.collections[collection];
@@ -2244,27 +3511,52 @@ var Store = class {
2244
3511
  if (this.clock) {
2245
3512
  this.clock.receive(op.timestamp);
2246
3513
  }
3514
+ const remoteVersion = serializeRowVersion(op.timestamp);
3515
+ const wallTime = op.timestamp.wallTime;
2247
3516
  await this.adapter.transaction(async (tx) => {
2248
3517
  if (op.type === "insert" && op.data) {
2249
- const serializedData = serializeRecord(op.data, definition.fields);
2250
- const now = op.timestamp.wallTime;
2251
- const record = {
2252
- id: op.recordId,
2253
- ...serializedData,
2254
- _created_at: now,
2255
- _updated_at: now
2256
- };
2257
- const insertQuery = buildInsertQuery(collection, record);
2258
- await tx.execute(insertQuery.sql, insertQuery.params);
3518
+ const existing2 = await tx.query(
3519
+ `SELECT _updated_at, _version FROM ${collection} WHERE id = ?`,
3520
+ [op.recordId]
3521
+ );
3522
+ const row = existing2[0];
3523
+ if (row && !isIncomingNewerThanRow(op.timestamp, row)) {
3524
+ } else {
3525
+ const serializedData = serializeRecord(op.data, definition.fields);
3526
+ const record = {
3527
+ id: op.recordId,
3528
+ ...serializedData,
3529
+ _created_at: wallTime,
3530
+ _updated_at: wallTime,
3531
+ _version: remoteVersion
3532
+ };
3533
+ const insertQuery = buildInsertQuery(collection, record);
3534
+ await tx.execute(insertQuery.sql, insertQuery.params);
3535
+ }
2259
3536
  } else if (op.type === "update" && op.data) {
2260
3537
  const serializedChanges = serializeRecord(op.data, definition.fields);
2261
- const updateQuery = buildUpdateQuery(collection, op.recordId, {
3538
+ const updatePayload = {
2262
3539
  ...serializedChanges,
2263
- _updated_at: op.timestamp.wallTime
2264
- });
3540
+ _updated_at: wallTime,
3541
+ _version: remoteVersion
3542
+ };
3543
+ if (options?.reactivateIfDeleted) {
3544
+ updatePayload._deleted = 0;
3545
+ }
3546
+ const updateQuery = buildLwwUpdateQuery(
3547
+ collection,
3548
+ op.recordId,
3549
+ updatePayload,
3550
+ remoteVersion
3551
+ );
2265
3552
  await tx.execute(updateQuery.sql, updateQuery.params);
2266
3553
  } else if (op.type === "delete") {
2267
- const deleteQuery = buildSoftDeleteQuery(collection, op.recordId, op.timestamp.wallTime);
3554
+ const deleteQuery = buildLwwSoftDeleteQuery(
3555
+ collection,
3556
+ op.recordId,
3557
+ wallTime,
3558
+ remoteVersion
3559
+ );
2268
3560
  await tx.execute(deleteQuery.sql, deleteQuery.params);
2269
3561
  }
2270
3562
  const opRow = serializeOperation(op);
@@ -2289,11 +3581,11 @@ var Store = class {
2289
3581
  * Get operations from a node within a sequence number range.
2290
3582
  * Implements the OperationLog interface for computeDelta.
2291
3583
  */
2292
- getRange(nodeId, fromSeq, toSeq) {
2293
- return [];
3584
+ async getRange(nodeId, fromSeq, toSeq) {
3585
+ return this.getOperationRange(nodeId, fromSeq, toSeq);
2294
3586
  }
2295
3587
  /**
2296
- * Async version of getRange for use by the sync layer.
3588
+ * Get operations from a node within a sequence number range.
2297
3589
  */
2298
3590
  async getOperationRange(nodeId, fromSeq, toSeq) {
2299
3591
  this.ensureOpen();
@@ -2310,12 +3602,233 @@ var Store = class {
2310
3602
  allOps.sort((a, b) => a.sequenceNumber - b.sequenceNumber);
2311
3603
  return allOps;
2312
3604
  }
3605
+ /**
3606
+ * Load every operation from the local append-only log across all collections.
3607
+ * Used by sync delta computation, backup export, and time-travel replay.
3608
+ */
3609
+ async getAllOperations() {
3610
+ this.ensureOpen();
3611
+ const allOps = [];
3612
+ for (const collectionName of Object.keys(this.schema.collections)) {
3613
+ const rows = await this.adapter.query(
3614
+ `SELECT * FROM _kora_ops_${collectionName} ORDER BY sequence_number ASC`
3615
+ );
3616
+ for (const row of rows) {
3617
+ allOps.push(deserializeOperationWithCollection(row, collectionName));
3618
+ }
3619
+ }
3620
+ return allOps;
3621
+ }
3622
+ /**
3623
+ * Rebuild an in-memory snapshot of materialized state at a causal cut in the op log.
3624
+ * Does not mutate the live store — intended for DevTools time-travel inspection.
3625
+ *
3626
+ * @param operationId - Content-addressed id of the operation to replay through (inclusive)
3627
+ * @throws {OperationError} When the operation id is not present in the local log
3628
+ */
3629
+ async replayTo(operationId) {
3630
+ this.ensureOpen();
3631
+ const start = Date.now();
3632
+ const allOps = await this.getAllOperations();
3633
+ const snapshot = buildReplaySnapshot(this.schema, allOps, operationId);
3634
+ if (this.emitter) {
3635
+ this.emitter.emit({
3636
+ type: "replay:completed",
3637
+ targetOperationId: operationId,
3638
+ operationsApplied: snapshot.operationsApplied.length,
3639
+ duration: Date.now() - start
3640
+ });
3641
+ }
3642
+ return snapshot;
3643
+ }
3644
+ /**
3645
+ * Persist a merge trace to the durable audit log.
3646
+ */
3647
+ async appendAuditTrace(trace) {
3648
+ this.ensureOpen();
3649
+ const { appendAuditTrace: append } = await Promise.resolve().then(() => (init_audit_trace_store(), audit_trace_store_exports));
3650
+ await append(this.adapter, trace);
3651
+ }
3652
+ /**
3653
+ * Read persisted audit traces with optional filters.
3654
+ */
3655
+ async getAuditTraces(query) {
3656
+ this.ensureOpen();
3657
+ const { readAuditTraces: readAuditTraces2 } = await Promise.resolve().then(() => (init_audit_trace_store(), audit_trace_store_exports));
3658
+ return readAuditTraces2(this.adapter, query);
3659
+ }
3660
+ /**
3661
+ * Export operations and merge traces as a portable audit bundle.
3662
+ */
3663
+ async exportAudit(options) {
3664
+ this.ensureOpen();
3665
+ const { exportAudit: doExport } = await Promise.resolve().then(() => (init_export_audit(), export_audit_exports));
3666
+ return doExport(this.adapter, this.schema, this.nodeId, this.schema.version, options);
3667
+ }
2313
3668
  /**
2314
3669
  * Get the schema definition.
2315
3670
  */
2316
3671
  getSchema() {
2317
3672
  return this.schema;
2318
3673
  }
3674
+ /**
3675
+ * Route local CRUD through the unified apply pipeline (korajs ApplyPipeline).
3676
+ */
3677
+ setLocalMutationHandler(handler) {
3678
+ this.localMutationHandler = handler;
3679
+ for (const col of this.collections.values()) {
3680
+ col.setMutationHandler(handler);
3681
+ }
3682
+ }
3683
+ /**
3684
+ * Build mutation context for a collection (used by ApplyPipeline side effects).
3685
+ */
3686
+ createMutationContext(collection, options) {
3687
+ this.ensureOpen();
3688
+ const definition = this.schema.collections[collection];
3689
+ if (!definition || !this.clock) {
3690
+ throw new StoreNotOpenError();
3691
+ }
3692
+ return {
3693
+ collection,
3694
+ definition,
3695
+ schema: this.schema,
3696
+ adapter: this.adapter,
3697
+ clock: this.clock,
3698
+ nodeId: this.nodeId,
3699
+ allocateSequenceNumber: () => this.allocateSequenceNumber(),
3700
+ onMutation: (collectionName, operation) => {
3701
+ this.recordOperationSequence(operation);
3702
+ this.subscriptionManager.notify(collectionName, operation);
3703
+ if (this.emitter) {
3704
+ this.emitter.emit({ type: "operation:created", operation });
3705
+ }
3706
+ },
3707
+ relationEnforcer: this.relationEnforcer,
3708
+ causalTracker: this.causalTracker,
3709
+ inTransaction: options?.inTransaction ?? false,
3710
+ extraCausalDeps: options?.extraCausalDeps
3711
+ };
3712
+ }
3713
+ /**
3714
+ * Load a materialized row by ID, including soft-deleted tombstones.
3715
+ */
3716
+ async findMaterializedRow(collection, recordId) {
3717
+ this.ensureOpen();
3718
+ const definition = this.schema.collections[collection];
3719
+ if (!definition) {
3720
+ return null;
3721
+ }
3722
+ const rows = await this.adapter.query(
3723
+ `SELECT * FROM ${collection} WHERE id = ?`,
3724
+ [recordId]
3725
+ );
3726
+ const row = rows[0];
3727
+ if (!row) {
3728
+ return null;
3729
+ }
3730
+ return {
3731
+ record: deserializeRecord(row, definition.fields),
3732
+ deleted: row._deleted === 1
3733
+ };
3734
+ }
3735
+ /**
3736
+ * Latest operation from this device for a record (used for delete-vs-update merge on sync).
3737
+ */
3738
+ /**
3739
+ * Load the last server version vector acknowledged by this client (persisted in `_kora_meta`).
3740
+ */
3741
+ async loadLastAckedServerVector() {
3742
+ this.ensureOpen();
3743
+ return loadLastAckedServerVector(this.adapter);
3744
+ }
3745
+ /**
3746
+ * Persist the last server version vector this client believes the server has applied.
3747
+ */
3748
+ async saveLastAckedServerVector(vector) {
3749
+ this.ensureOpen();
3750
+ await saveLastAckedServerVector(this.adapter, vector);
3751
+ }
3752
+ /**
3753
+ * Load persisted delta cursor for resuming paginated initial sync.
3754
+ */
3755
+ async loadDeltaCursor() {
3756
+ this.ensureOpen();
3757
+ return loadDeltaCursor(this.adapter);
3758
+ }
3759
+ /**
3760
+ * Persist or clear the delta cursor for paginated initial sync resume.
3761
+ */
3762
+ async saveDeltaCursor(cursor) {
3763
+ this.ensureOpen();
3764
+ await saveDeltaCursor(this.adapter, cursor);
3765
+ }
3766
+ /**
3767
+ * Local operations not yet reflected on the server version vector.
3768
+ */
3769
+ async getUnsyncedOperations(serverVector) {
3770
+ this.ensureOpen();
3771
+ return collectOperationsAheadOfServer(
3772
+ this.getVersionVector(),
3773
+ serverVector,
3774
+ (nodeId, fromSeq, toSeq) => this.getOperationRange(nodeId, fromSeq, toSeq)
3775
+ );
3776
+ }
3777
+ /**
3778
+ * Count of local operations ahead of the server version vector.
3779
+ */
3780
+ async countUnsyncedOperations(serverVector) {
3781
+ const ops = await this.getUnsyncedOperations(serverVector);
3782
+ return ops.length;
3783
+ }
3784
+ /**
3785
+ * Compact the local operation log using materialized rows as the baseline.
3786
+ * Only removes ops the server has acknowledged (per {@link CompactionStrategy}).
3787
+ */
3788
+ async compact(strategy) {
3789
+ this.ensureOpen();
3790
+ if (strategy.mode === "never") {
3791
+ return compactOperationLog(this.adapter, this.schema, strategy, (0, import_core17.createVersionVector)());
3792
+ }
3793
+ const serverVector = strategy.serverVector ?? await loadLastAckedServerVector(this.adapter);
3794
+ return compactOperationLog(this.adapter, this.schema, strategy, serverVector);
3795
+ }
3796
+ /**
3797
+ * Merge session remote vector with persisted last-acked vector (max per node).
3798
+ */
3799
+ mergeServerVectors(sessionVector, persistedVector) {
3800
+ return mergeVersionVectors(persistedVector, sessionVector);
3801
+ }
3802
+ async getLatestLocalOperationForRecord(collection, recordId) {
3803
+ this.ensureOpen();
3804
+ const rows = await this.adapter.query(
3805
+ `SELECT * FROM _kora_ops_${collection} WHERE node_id = ? AND record_id = ? ORDER BY sequence_number DESC LIMIT 1`,
3806
+ [this.nodeId, recordId]
3807
+ );
3808
+ const row = rows[0];
3809
+ if (!row) {
3810
+ return null;
3811
+ }
3812
+ return deserializeOperationWithCollection(row, collection);
3813
+ }
3814
+ /**
3815
+ * Latest operation for a record from any node (for 3-way merge when local op log is empty).
3816
+ */
3817
+ async getLatestOperationForRecord(collection, recordId) {
3818
+ this.ensureOpen();
3819
+ const rows = await this.adapter.query(
3820
+ `SELECT * FROM _kora_ops_${collection} WHERE record_id = ?`,
3821
+ [recordId]
3822
+ );
3823
+ let latest = null;
3824
+ for (const row of rows) {
3825
+ const op = deserializeOperationWithCollection(row, collection);
3826
+ if (!latest || import_core17.HybridLogicalClock.compare(op.timestamp, latest.timestamp) > 0) {
3827
+ latest = op;
3828
+ }
3829
+ }
3830
+ return latest;
3831
+ }
2319
3832
  /** Expose the subscription manager for direct access (e.g., by QueryBuilder) */
2320
3833
  getSubscriptionManager() {
2321
3834
  return this.subscriptionManager;
@@ -2348,7 +3861,10 @@ var Store = class {
2348
3861
  adapter: this.adapter,
2349
3862
  clock: this.clock,
2350
3863
  nodeId: this.nodeId,
2351
- getSequenceNumber: () => this.nextSequenceNumber()
3864
+ sequenceAllocator: new TransactionSequenceAllocator(this.adapter, this.nodeId),
3865
+ relationEnforcer: this.relationEnforcer,
3866
+ causalTracker: this.causalTracker,
3867
+ localMutationHandler: this.localMutationHandler
2352
3868
  });
2353
3869
  }
2354
3870
  /**
@@ -2363,10 +3879,13 @@ var Store = class {
2363
3879
  */
2364
3880
  async transaction(fn) {
2365
3881
  const tx = this.createTransaction();
3882
+ this.causalTracker?.beginTransaction();
2366
3883
  try {
2367
3884
  await fn(tx);
2368
- const { operations, affectedCollections } = await tx.commit();
3885
+ const { operations } = await tx.commit();
3886
+ this.causalTracker?.clearTransaction();
2369
3887
  for (const op of operations) {
3888
+ this.recordOperationSequence(op);
2370
3889
  this.subscriptionManager.notify(op.collection, op);
2371
3890
  if (this.emitter) {
2372
3891
  this.emitter.emit({ type: "operation:created", operation: op });
@@ -2375,13 +3894,57 @@ var Store = class {
2375
3894
  return operations;
2376
3895
  } catch (error) {
2377
3896
  tx.rollback();
3897
+ this.causalTracker?.clearTransaction();
2378
3898
  throw error;
2379
3899
  }
2380
3900
  }
2381
- nextSequenceNumber() {
2382
- this.sequenceNumber++;
2383
- this.versionVector.set(this.nodeId, this.sequenceNumber);
2384
- return this.sequenceNumber;
3901
+ /**
3902
+ * Export all data as a portable backup binary.
3903
+ * Includes operations, version vector, metadata, and optionally materialized records.
3904
+ *
3905
+ * @param options - Backup options (includeRecords, collections, onProgress)
3906
+ * @returns Backup as a Uint8Array
3907
+ */
3908
+ async exportBackup(options) {
3909
+ this.ensureOpen();
3910
+ const { exportBackup: doExport } = await Promise.resolve().then(() => (init_backup(), backup_exports));
3911
+ return doExport(this.adapter, this.schema, this.nodeId, this.schema.version, options);
3912
+ }
3913
+ /**
3914
+ * Restore data from a backup binary.
3915
+ *
3916
+ * @param data - The backup data
3917
+ * @param options - Restore options (merge, collections, onProgress)
3918
+ * @returns Result of the restore operation
3919
+ */
3920
+ async importBackup(data, options) {
3921
+ this.ensureOpen();
3922
+ const { restoreBackup: doRestore } = await Promise.resolve().then(() => (init_backup(), backup_exports));
3923
+ return doRestore(this.adapter, this.schema, data, options);
3924
+ }
3925
+ /**
3926
+ * Read backup manifest without loading the entire backup.
3927
+ *
3928
+ * @param data - The raw backup data
3929
+ * @returns The backup manifest
3930
+ */
3931
+ static readBackupManifest(data) {
3932
+ return readBackupManifest(data);
3933
+ }
3934
+ recordOperationSequence(operation) {
3935
+ const prev = this.versionVector.get(operation.nodeId) ?? 0;
3936
+ if (operation.sequenceNumber > prev) {
3937
+ this.versionVector.set(operation.nodeId, operation.sequenceNumber);
3938
+ }
3939
+ if (operation.nodeId === this.nodeId) {
3940
+ this.sequenceNumber = Math.max(this.sequenceNumber, operation.sequenceNumber);
3941
+ }
3942
+ }
3943
+ async allocateSequenceNumber() {
3944
+ const seq = await allocateNextSequenceNumber(this.adapter, this.nodeId);
3945
+ this.sequenceNumber = seq;
3946
+ this.versionVector.set(this.nodeId, seq);
3947
+ return seq;
2385
3948
  }
2386
3949
  /**
2387
3950
  * Check the stored schema version and run any pending migrations.
@@ -2403,7 +3966,7 @@ var Store = class {
2403
3966
  for (let v = storedVersion + 1; v <= targetVersion; v++) {
2404
3967
  const migration = migrations[v];
2405
3968
  if (!migration) continue;
2406
- const sqlStatements = (0, import_core8.migrationStepsToSQL)(migration.steps);
3969
+ const sqlStatements = (0, import_core17.migrationStepsToSQL)(migration.steps);
2407
3970
  for (const sql of sqlStatements) {
2408
3971
  try {
2409
3972
  await this.adapter.execute(sql);
@@ -2464,19 +4027,24 @@ var Store = class {
2464
4027
  }
2465
4028
  async loadOrGenerateNodeId() {
2466
4029
  if (this.configNodeId) {
2467
- await this.adapter.execute(
2468
- "INSERT OR REPLACE INTO _kora_meta (key, value) VALUES ('node_id', ?)",
2469
- [this.configNodeId]
2470
- );
4030
+ if (this.isolation !== "per-tab") {
4031
+ await this.adapter.execute(
4032
+ "INSERT OR REPLACE INTO _kora_meta (key, value) VALUES ('node_id', ?)",
4033
+ [this.configNodeId]
4034
+ );
4035
+ }
2471
4036
  return this.configNodeId;
2472
4037
  }
4038
+ if (this.isolation === "per-tab") {
4039
+ return resolvePerTabNodeId(this.dbName);
4040
+ }
2473
4041
  const rows = await this.adapter.query(
2474
4042
  "SELECT value FROM _kora_meta WHERE key = 'node_id'"
2475
4043
  );
2476
4044
  if (rows[0]) {
2477
4045
  return rows[0].value;
2478
4046
  }
2479
- const newNodeId = (0, import_core8.generateUUIDv7)();
4047
+ const newNodeId = (0, import_core17.generateUUIDv7)();
2480
4048
  await this.adapter.execute("INSERT INTO _kora_meta (key, value) VALUES ('node_id', ?)", [
2481
4049
  newNodeId
2482
4050
  ]);
@@ -2493,7 +4061,7 @@ var Store = class {
2493
4061
  const rows = await this.adapter.query(
2494
4062
  "SELECT node_id, sequence_number FROM _kora_version_vector"
2495
4063
  );
2496
- const vector = (0, import_core8.createVersionVector)();
4064
+ const vector = (0, import_core17.createVersionVector)();
2497
4065
  for (const row of rows) {
2498
4066
  vector.set(row.node_id, row.sequence_number);
2499
4067
  }
@@ -2505,11 +4073,24 @@ var Store = class {
2505
4073
  }
2506
4074
  }
2507
4075
  };
4076
+
4077
+ // src/index.ts
4078
+ init_richtext_serializer();
4079
+
4080
+ // src/backup/index.ts
4081
+ init_backup();
4082
+
4083
+ // src/audit/index.ts
4084
+ init_audit_trace_store();
4085
+ init_audit_json();
4086
+ init_export_audit();
2508
4087
  // Annotate the CommonJS export names for ESM import in node:
2509
4088
  0 && (module.exports = {
2510
4089
  AdapterError,
4090
+ COMPACTION_BASELINE_META_KEY,
2511
4091
  Collection,
2512
4092
  InvalidStateTransitionError,
4093
+ LAST_ACKED_SERVER_VECTOR_META_KEY,
2513
4094
  PersistenceError,
2514
4095
  QueryBuilder,
2515
4096
  QueryError,
@@ -2522,12 +4103,27 @@ var Store = class {
2522
4103
  TransactionContext,
2523
4104
  WorkerInitError,
2524
4105
  WorkerTimeoutError,
4106
+ collectOperationsAheadOfServer,
4107
+ compactOperationLog,
4108
+ computeAckCompactionWatermark,
4109
+ decodeAuditExport,
2525
4110
  decodeRichtext,
4111
+ deserializeVersionVectorFromMeta,
2526
4112
  encodeRichtext,
4113
+ exportBackup,
4114
+ mergeVersionVectors,
4115
+ persistedAuditTraceFromEvent,
2527
4116
  pluralize,
4117
+ readAuditExportManifest,
4118
+ readBackupManifest,
4119
+ restoreBackup,
4120
+ richtextStatesEqual,
2528
4121
  richtextToPlainText,
4122
+ serializeVersionVectorToMeta,
2529
4123
  singularize,
2530
4124
  validateStateTransition,
2531
- validateUpdateStateMachine
4125
+ validateUpdateStateMachine,
4126
+ verifyAuditExportChecksum,
4127
+ verifyBackupChecksum
2532
4128
  });
2533
4129
  //# sourceMappingURL=index.cjs.map