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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/dist/adapters/better-sqlite3.cjs.map +1 -1
  2. package/dist/adapters/better-sqlite3.d.cts +1 -1
  3. package/dist/adapters/better-sqlite3.d.ts +1 -1
  4. package/dist/adapters/better-sqlite3.js +1 -1
  5. package/dist/adapters/indexeddb.cjs +523 -148
  6. package/dist/adapters/indexeddb.cjs.map +1 -1
  7. package/dist/adapters/indexeddb.d.cts +4 -2
  8. package/dist/adapters/indexeddb.d.ts +4 -2
  9. package/dist/adapters/indexeddb.js +55 -11
  10. package/dist/adapters/indexeddb.js.map +1 -1
  11. package/dist/adapters/sqlite-wasm-worker.cjs +207 -187
  12. package/dist/adapters/sqlite-wasm-worker.cjs.map +1 -1
  13. package/dist/adapters/sqlite-wasm-worker.js +208 -188
  14. package/dist/adapters/sqlite-wasm-worker.js.map +1 -1
  15. package/dist/adapters/sqlite-wasm.cjs +474 -140
  16. package/dist/adapters/sqlite-wasm.cjs.map +1 -1
  17. package/dist/adapters/sqlite-wasm.d.cts +45 -8
  18. package/dist/adapters/sqlite-wasm.d.ts +45 -8
  19. package/dist/adapters/sqlite-wasm.js +3 -3
  20. package/dist/{backup-5XZ7THNJ.js → backup-5S7K2Z35.js} +3 -3
  21. package/dist/blob-fs.cjs +166 -0
  22. package/dist/blob-fs.cjs.map +1 -0
  23. package/dist/blob-fs.d.cts +29 -0
  24. package/dist/blob-fs.d.ts +29 -0
  25. package/dist/blob-fs.js +128 -0
  26. package/dist/blob-fs.js.map +1 -0
  27. package/dist/{chunk-EIVXWF4B.js → chunk-CYO3YKFL.js} +4 -3
  28. package/dist/chunk-CYO3YKFL.js.map +1 -0
  29. package/dist/{chunk-ETDJEH2I.js → chunk-GSJ7EEUC.js} +12 -1
  30. package/dist/chunk-GSJ7EEUC.js.map +1 -0
  31. package/dist/{chunk-5R4QMY3Z.js → chunk-HS6DBMST.js} +13 -10
  32. package/dist/chunk-HS6DBMST.js.map +1 -0
  33. package/dist/chunk-RJIEKEN4.js +751 -0
  34. package/dist/chunk-RJIEKEN4.js.map +1 -0
  35. package/dist/{chunk-QL2XVZRN.js → chunk-TUR7RGEP.js} +12 -1
  36. package/dist/{chunk-QL2XVZRN.js.map → chunk-TUR7RGEP.js.map} +1 -1
  37. package/dist/{chunk-IOEFNHHP.js → chunk-VDBJQ75K.js} +2 -2
  38. package/dist/chunk-VDBJQ75K.js.map +1 -0
  39. package/dist/chunk-W4MTTNEZ.js +55 -0
  40. package/dist/chunk-W4MTTNEZ.js.map +1 -0
  41. package/dist/{chunk-OUQDC5Z6.js → chunk-YT7MRFCP.js} +55 -35
  42. package/dist/chunk-YT7MRFCP.js.map +1 -0
  43. package/dist/content-addressed-blob-store-Dcj3SB9r.d.cts +60 -0
  44. package/dist/content-addressed-blob-store-Dcj3SB9r.d.ts +60 -0
  45. package/dist/{export-audit-VPGQWNR6.js → export-audit-ODGQTQIK.js} +3 -3
  46. package/dist/index.cjs +923 -131
  47. package/dist/index.cjs.map +1 -1
  48. package/dist/index.d.cts +443 -6
  49. package/dist/index.d.ts +443 -6
  50. package/dist/index.js +737 -49
  51. package/dist/index.js.map +1 -1
  52. package/dist/internal.cjs +69 -43
  53. package/dist/internal.cjs.map +1 -1
  54. package/dist/internal.d.cts +3 -3
  55. package/dist/internal.d.ts +3 -3
  56. package/dist/internal.js +3 -3
  57. package/dist/sqlite-wasm-channel-GGHA2BXU.js +12 -0
  58. package/dist/{sqlite-wasm-channel-DUDPclTE.d.cts → sqlite-wasm-channel-L6o3bWQK.d.cts} +1 -1
  59. package/dist/{sqlite-wasm-channel-DUDPclTE.d.ts → sqlite-wasm-channel-L6o3bWQK.d.ts} +1 -1
  60. package/dist/{types-CyjXwrjW.d.cts → types-BOO9EPt1.d.ts} +10 -3
  61. package/dist/{types-CJOVKxLw.d.ts → types-BslDOTk0.d.cts} +32 -2
  62. package/dist/{types-CJOVKxLw.d.cts → types-BslDOTk0.d.ts} +32 -2
  63. package/dist/{types-DAxCladl.d.ts → types-C_GDavG-.d.cts} +10 -3
  64. package/package.json +13 -12
  65. package/dist/adapters/sqlite-wasm-shared-host.cjs +0 -68
  66. package/dist/adapters/sqlite-wasm-shared-host.cjs.map +0 -1
  67. package/dist/adapters/sqlite-wasm-shared-host.d.cts +0 -2
  68. package/dist/adapters/sqlite-wasm-shared-host.d.ts +0 -2
  69. package/dist/adapters/sqlite-wasm-shared-host.js +0 -66
  70. package/dist/adapters/sqlite-wasm-shared-host.js.map +0 -1
  71. package/dist/chunk-5R4QMY3Z.js.map +0 -1
  72. package/dist/chunk-EIVXWF4B.js.map +0 -1
  73. package/dist/chunk-ETDJEH2I.js.map +0 -1
  74. package/dist/chunk-IOEFNHHP.js.map +0 -1
  75. package/dist/chunk-OUQDC5Z6.js.map +0 -1
  76. package/dist/chunk-RBPVEAVM.js +0 -425
  77. package/dist/chunk-RBPVEAVM.js.map +0 -1
  78. package/dist/sqlite-wasm-channel-H43KEZLM.js +0 -12
  79. /package/dist/{backup-5XZ7THNJ.js.map → backup-5S7K2Z35.js.map} +0 -0
  80. /package/dist/{export-audit-VPGQWNR6.js.map → export-audit-ODGQTQIK.js.map} +0 -0
  81. /package/dist/{sqlite-wasm-channel-H43KEZLM.js.map → sqlite-wasm-channel-GGHA2BXU.js.map} +0 -0
@@ -1,5 +1,5 @@
1
- import { OnDeleteAction, RelationDefinition, SchemaDefinition, HybridLogicalClock, Operation, CollectionDefinition, CausalTracker } from '@korajs/core';
2
- import { S as StorageAdapter, T as Transaction, L as LocalMutationHandler, C as CollectionRecord } from './types-CJOVKxLw.js';
1
+ import { OnDeleteAction, RelationDefinition, SchemaDefinition, HybridLogicalClock, Operation, CollectionDefinition, CausalTracker, SecretKeyProvider } from '@korajs/core';
2
+ import { S as StorageAdapter, T as Transaction, L as LocalMutationHandler, C as CollectionRecord } from './types-BslDOTk0.cjs';
3
3
 
4
4
  /**
5
5
  * A resolved relation reference: describes a relation that points TO a given collection.
@@ -129,7 +129,8 @@ declare class Collection {
129
129
  private readonly relationEnforcer;
130
130
  private mutationHandler;
131
131
  private readonly causalTracker;
132
- constructor(name: string, definition: CollectionDefinition, schema: SchemaDefinition, adapter: StorageAdapter, clock: HybridLogicalClock, nodeId: string, allocateSequenceNumber: () => Promise<number>, onMutation: MutationCallback, relationEnforcer: RelationEnforcer | null, mutationHandler: LocalMutationHandler | null, causalTracker: CausalTracker | null);
132
+ private readonly secretKeyProvider?;
133
+ constructor(name: string, definition: CollectionDefinition, schema: SchemaDefinition, adapter: StorageAdapter, clock: HybridLogicalClock, nodeId: string, allocateSequenceNumber: () => Promise<number>, onMutation: MutationCallback, relationEnforcer: RelationEnforcer | null, mutationHandler: LocalMutationHandler | null, causalTracker: CausalTracker | null, secretKeyProvider?: SecretKeyProvider | undefined);
133
134
  private mutationContext;
134
135
  insert(data: Record<string, unknown>): Promise<CollectionRecord>;
135
136
  findById(id: string): Promise<CollectionRecord | null>;
@@ -158,6 +159,12 @@ interface LocalMutationContext {
158
159
  readonly inTransaction: boolean;
159
160
  /** Additional parent op ids (e.g. referential cascade from a delete). */
160
161
  readonly extraCausalDeps?: string[];
162
+ /**
163
+ * Supplies the key used to encrypt `encrypted` secret fields at write time, so
164
+ * plaintext never enters the operation log. Null/absent for apps with no
165
+ * secret fields (or with only `hashed` secrets, which need no key).
166
+ */
167
+ readonly secretKeyProvider?: SecretKeyProvider;
161
168
  }
162
169
 
163
170
  export { Collection as C, type LocalMutationContext as L, RelationEnforcer as R };
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@korajs/store",
3
- "version": "1.0.0-beta.0",
3
+ "version": "1.0.0-beta.10",
4
4
  "description": "Local storage engine for Kora.js with SQLite WASM, IndexedDB, and reactive queries",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
7
7
  "module": "./dist/index.js",
8
8
  "types": "./dist/index.d.ts",
9
9
  "exports": {
10
+ "./package.json": "./package.json",
10
11
  ".": {
11
12
  "import": {
12
13
  "types": "./dist/index.d.ts",
@@ -47,16 +48,6 @@
47
48
  "default": "./dist/adapters/sqlite-wasm-worker.cjs"
48
49
  }
49
50
  },
50
- "./sqlite-wasm/shared-host": {
51
- "import": {
52
- "types": "./dist/adapters/sqlite-wasm-shared-host.d.ts",
53
- "default": "./dist/adapters/sqlite-wasm-shared-host.js"
54
- },
55
- "require": {
56
- "types": "./dist/adapters/sqlite-wasm-shared-host.d.cts",
57
- "default": "./dist/adapters/sqlite-wasm-shared-host.cjs"
58
- }
59
- },
60
51
  "./indexeddb": {
61
52
  "import": {
62
53
  "types": "./dist/adapters/indexeddb.d.ts",
@@ -67,6 +58,16 @@
67
58
  "default": "./dist/adapters/indexeddb.cjs"
68
59
  }
69
60
  },
61
+ "./blob-fs": {
62
+ "import": {
63
+ "types": "./dist/blob-fs.d.ts",
64
+ "default": "./dist/blob-fs.js"
65
+ },
66
+ "require": {
67
+ "types": "./dist/blob-fs.d.cts",
68
+ "default": "./dist/blob-fs.cjs"
69
+ }
70
+ },
70
71
  "./internal": {
71
72
  "import": {
72
73
  "types": "./dist/internal.d.ts",
@@ -83,7 +84,7 @@
83
84
  ],
84
85
  "dependencies": {
85
86
  "yjs": "^13.6.30",
86
- "@korajs/core": "1.0.0-beta.0"
87
+ "@korajs/core": "1.0.0-beta.10"
87
88
  },
88
89
  "peerDependencies": {
89
90
  "@sqlite.org/sqlite-wasm": ">=3.51.0-build1"
@@ -1,68 +0,0 @@
1
- "use strict";
2
-
3
- // src/adapters/sqlite-wasm-shared-host.ts
4
- var SW_REQUEST = "kora-sw-request";
5
- var SW_RESPONSE = "kora-sw-response";
6
- var pools = /* @__PURE__ */ new Map();
7
- function getPool(dbName, workerUrl) {
8
- const existing = pools.get(dbName);
9
- if (existing) {
10
- return existing;
11
- }
12
- const worker = new Worker(workerUrl, { type: "module" });
13
- const entry = { worker, pending: /* @__PURE__ */ new Map() };
14
- worker.onmessage = (event) => {
15
- const response = event.data;
16
- const requestId = entry.pending.get(response.id);
17
- if (!requestId) {
18
- return;
19
- }
20
- entry.pending.delete(response.id);
21
- const port = findPortForRequest(requestId);
22
- if (port) {
23
- const msg = {
24
- type: SW_RESPONSE,
25
- requestId,
26
- response
27
- };
28
- port.postMessage(msg);
29
- }
30
- };
31
- pools.set(dbName, entry);
32
- return entry;
33
- }
34
- var portRequestIds = /* @__PURE__ */ new Map();
35
- var requestPort = /* @__PURE__ */ new Map();
36
- function findPortForRequest(requestId) {
37
- return requestPort.get(requestId);
38
- }
39
- self.onconnect = (event) => {
40
- const port = event.ports[0];
41
- if (!port) {
42
- return;
43
- }
44
- port.start();
45
- portRequestIds.set(port, /* @__PURE__ */ new Set());
46
- port.addEventListener("message", (messageEvent) => {
47
- const data = messageEvent.data;
48
- if (data?.type !== SW_REQUEST) {
49
- return;
50
- }
51
- const pool = getPool(data.dbName, data.workerUrl);
52
- pool.pending.set(data.request.id, data.requestId);
53
- requestPort.set(data.requestId, port);
54
- portRequestIds.get(port)?.add(data.requestId);
55
- pool.worker.postMessage(data.request);
56
- });
57
- port.addEventListener("messageerror", () => {
58
- const ids = portRequestIds.get(port);
59
- if (!ids) {
60
- return;
61
- }
62
- for (const requestId of ids) {
63
- requestPort.delete(requestId);
64
- }
65
- portRequestIds.delete(port);
66
- });
67
- };
68
- //# sourceMappingURL=sqlite-wasm-shared-host.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../src/adapters/sqlite-wasm-shared-host.ts"],"sourcesContent":["/// <reference lib=\"webworker\" />\n/**\n * SharedWorker host: one DedicatedWorker (SQLite WASM) per `dbName` per browser origin.\n *\n * Bundle this file separately and pass its URL as `store.sharedWorkerUrl` alongside `workerUrl`.\n *\n * @example\n * ```typescript\n * createApp({\n * store: {\n * workerUrl: '/sqlite-wasm-worker.js',\n * sharedWorkerUrl: '/sqlite-wasm-shared-host.js',\n * },\n * })\n * ```\n */\n\nimport type { WorkerRequest, WorkerResponse } from './sqlite-wasm-channel'\n\nconst SW_REQUEST = 'kora-sw-request'\nconst SW_RESPONSE = 'kora-sw-response'\n\ninterface SharedWorkerRpcRequest {\n\ttype: typeof SW_REQUEST\n\trequestId: string\n\tdbName: string\n\tworkerUrl: string\n\trequest: WorkerRequest\n}\n\ninterface SharedWorkerRpcResponse {\n\ttype: typeof SW_RESPONSE\n\trequestId: string\n\tresponse: WorkerResponse\n}\n\ninterface WorkerEntry {\n\tworker: Worker\n\tpending: Map<number, string>\n}\n\ndeclare const self: SharedWorkerGlobalScope\n\nconst pools = new Map<string, WorkerEntry>()\n\nfunction getPool(dbName: string, workerUrl: string): WorkerEntry {\n\tconst existing = pools.get(dbName)\n\tif (existing) {\n\t\treturn existing\n\t}\n\n\tconst worker = new Worker(workerUrl, { type: 'module' })\n\tconst entry: WorkerEntry = { worker, pending: new Map() }\n\n\tworker.onmessage = (event: MessageEvent<WorkerResponse>) => {\n\t\tconst response = event.data\n\t\tconst requestId = entry.pending.get(response.id)\n\t\tif (!requestId) {\n\t\t\treturn\n\t\t}\n\t\tentry.pending.delete(response.id)\n\t\tconst port = findPortForRequest(requestId)\n\t\tif (port) {\n\t\t\tconst msg: SharedWorkerRpcResponse = {\n\t\t\t\ttype: SW_RESPONSE,\n\t\t\t\trequestId,\n\t\t\t\tresponse,\n\t\t\t}\n\t\t\tport.postMessage(msg)\n\t\t}\n\t}\n\n\tpools.set(dbName, entry)\n\treturn entry\n}\n\nconst portRequestIds = new Map<MessagePort, Set<string>>()\nconst requestPort = new Map<string, MessagePort>()\n\nfunction findPortForRequest(requestId: string): MessagePort | undefined {\n\treturn requestPort.get(requestId)\n}\n\nself.onconnect = (event: MessageEvent): void => {\n\tconst port = event.ports[0]\n\tif (!port) {\n\t\treturn\n\t}\n\n\tport.start()\n\tportRequestIds.set(port, new Set())\n\n\tport.addEventListener('message', (messageEvent: MessageEvent<SharedWorkerRpcRequest>) => {\n\t\tconst data = messageEvent.data\n\t\tif (data?.type !== SW_REQUEST) {\n\t\t\treturn\n\t\t}\n\n\t\tconst pool = getPool(data.dbName, data.workerUrl)\n\t\tpool.pending.set(data.request.id, data.requestId)\n\t\trequestPort.set(data.requestId, port)\n\t\tportRequestIds.get(port)?.add(data.requestId)\n\n\t\tpool.worker.postMessage(data.request)\n\t})\n\n\tport.addEventListener('messageerror', () => {\n\t\tconst ids = portRequestIds.get(port)\n\t\tif (!ids) {\n\t\t\treturn\n\t\t}\n\t\tfor (const requestId of ids) {\n\t\t\trequestPort.delete(requestId)\n\t\t}\n\t\tportRequestIds.delete(port)\n\t})\n}\n"],"mappings":";;;AAmBA,IAAM,aAAa;AACnB,IAAM,cAAc;AAuBpB,IAAM,QAAQ,oBAAI,IAAyB;AAE3C,SAAS,QAAQ,QAAgB,WAAgC;AAChE,QAAM,WAAW,MAAM,IAAI,MAAM;AACjC,MAAI,UAAU;AACb,WAAO;AAAA,EACR;AAEA,QAAM,SAAS,IAAI,OAAO,WAAW,EAAE,MAAM,SAAS,CAAC;AACvD,QAAM,QAAqB,EAAE,QAAQ,SAAS,oBAAI,IAAI,EAAE;AAExD,SAAO,YAAY,CAAC,UAAwC;AAC3D,UAAM,WAAW,MAAM;AACvB,UAAM,YAAY,MAAM,QAAQ,IAAI,SAAS,EAAE;AAC/C,QAAI,CAAC,WAAW;AACf;AAAA,IACD;AACA,UAAM,QAAQ,OAAO,SAAS,EAAE;AAChC,UAAM,OAAO,mBAAmB,SAAS;AACzC,QAAI,MAAM;AACT,YAAM,MAA+B;AAAA,QACpC,MAAM;AAAA,QACN;AAAA,QACA;AAAA,MACD;AACA,WAAK,YAAY,GAAG;AAAA,IACrB;AAAA,EACD;AAEA,QAAM,IAAI,QAAQ,KAAK;AACvB,SAAO;AACR;AAEA,IAAM,iBAAiB,oBAAI,IAA8B;AACzD,IAAM,cAAc,oBAAI,IAAyB;AAEjD,SAAS,mBAAmB,WAA4C;AACvE,SAAO,YAAY,IAAI,SAAS;AACjC;AAEA,KAAK,YAAY,CAAC,UAA8B;AAC/C,QAAM,OAAO,MAAM,MAAM,CAAC;AAC1B,MAAI,CAAC,MAAM;AACV;AAAA,EACD;AAEA,OAAK,MAAM;AACX,iBAAe,IAAI,MAAM,oBAAI,IAAI,CAAC;AAElC,OAAK,iBAAiB,WAAW,CAAC,iBAAuD;AACxF,UAAM,OAAO,aAAa;AAC1B,QAAI,MAAM,SAAS,YAAY;AAC9B;AAAA,IACD;AAEA,UAAM,OAAO,QAAQ,KAAK,QAAQ,KAAK,SAAS;AAChD,SAAK,QAAQ,IAAI,KAAK,QAAQ,IAAI,KAAK,SAAS;AAChD,gBAAY,IAAI,KAAK,WAAW,IAAI;AACpC,mBAAe,IAAI,IAAI,GAAG,IAAI,KAAK,SAAS;AAE5C,SAAK,OAAO,YAAY,KAAK,OAAO;AAAA,EACrC,CAAC;AAED,OAAK,iBAAiB,gBAAgB,MAAM;AAC3C,UAAM,MAAM,eAAe,IAAI,IAAI;AACnC,QAAI,CAAC,KAAK;AACT;AAAA,IACD;AACA,eAAW,aAAa,KAAK;AAC5B,kBAAY,OAAO,SAAS;AAAA,IAC7B;AACA,mBAAe,OAAO,IAAI;AAAA,EAC3B,CAAC;AACF;","names":[]}
@@ -1,2 +0,0 @@
1
-
2
- export { }
@@ -1,2 +0,0 @@
1
-
2
- export { }
@@ -1,66 +0,0 @@
1
- // src/adapters/sqlite-wasm-shared-host.ts
2
- var SW_REQUEST = "kora-sw-request";
3
- var SW_RESPONSE = "kora-sw-response";
4
- var pools = /* @__PURE__ */ new Map();
5
- function getPool(dbName, workerUrl) {
6
- const existing = pools.get(dbName);
7
- if (existing) {
8
- return existing;
9
- }
10
- const worker = new Worker(workerUrl, { type: "module" });
11
- const entry = { worker, pending: /* @__PURE__ */ new Map() };
12
- worker.onmessage = (event) => {
13
- const response = event.data;
14
- const requestId = entry.pending.get(response.id);
15
- if (!requestId) {
16
- return;
17
- }
18
- entry.pending.delete(response.id);
19
- const port = findPortForRequest(requestId);
20
- if (port) {
21
- const msg = {
22
- type: SW_RESPONSE,
23
- requestId,
24
- response
25
- };
26
- port.postMessage(msg);
27
- }
28
- };
29
- pools.set(dbName, entry);
30
- return entry;
31
- }
32
- var portRequestIds = /* @__PURE__ */ new Map();
33
- var requestPort = /* @__PURE__ */ new Map();
34
- function findPortForRequest(requestId) {
35
- return requestPort.get(requestId);
36
- }
37
- self.onconnect = (event) => {
38
- const port = event.ports[0];
39
- if (!port) {
40
- return;
41
- }
42
- port.start();
43
- portRequestIds.set(port, /* @__PURE__ */ new Set());
44
- port.addEventListener("message", (messageEvent) => {
45
- const data = messageEvent.data;
46
- if (data?.type !== SW_REQUEST) {
47
- return;
48
- }
49
- const pool = getPool(data.dbName, data.workerUrl);
50
- pool.pending.set(data.request.id, data.requestId);
51
- requestPort.set(data.requestId, port);
52
- portRequestIds.get(port)?.add(data.requestId);
53
- pool.worker.postMessage(data.request);
54
- });
55
- port.addEventListener("messageerror", () => {
56
- const ids = portRequestIds.get(port);
57
- if (!ids) {
58
- return;
59
- }
60
- for (const requestId of ids) {
61
- requestPort.delete(requestId);
62
- }
63
- portRequestIds.delete(port);
64
- });
65
- };
66
- //# sourceMappingURL=sqlite-wasm-shared-host.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../src/adapters/sqlite-wasm-shared-host.ts"],"sourcesContent":["/// <reference lib=\"webworker\" />\n/**\n * SharedWorker host: one DedicatedWorker (SQLite WASM) per `dbName` per browser origin.\n *\n * Bundle this file separately and pass its URL as `store.sharedWorkerUrl` alongside `workerUrl`.\n *\n * @example\n * ```typescript\n * createApp({\n * store: {\n * workerUrl: '/sqlite-wasm-worker.js',\n * sharedWorkerUrl: '/sqlite-wasm-shared-host.js',\n * },\n * })\n * ```\n */\n\nimport type { WorkerRequest, WorkerResponse } from './sqlite-wasm-channel'\n\nconst SW_REQUEST = 'kora-sw-request'\nconst SW_RESPONSE = 'kora-sw-response'\n\ninterface SharedWorkerRpcRequest {\n\ttype: typeof SW_REQUEST\n\trequestId: string\n\tdbName: string\n\tworkerUrl: string\n\trequest: WorkerRequest\n}\n\ninterface SharedWorkerRpcResponse {\n\ttype: typeof SW_RESPONSE\n\trequestId: string\n\tresponse: WorkerResponse\n}\n\ninterface WorkerEntry {\n\tworker: Worker\n\tpending: Map<number, string>\n}\n\ndeclare const self: SharedWorkerGlobalScope\n\nconst pools = new Map<string, WorkerEntry>()\n\nfunction getPool(dbName: string, workerUrl: string): WorkerEntry {\n\tconst existing = pools.get(dbName)\n\tif (existing) {\n\t\treturn existing\n\t}\n\n\tconst worker = new Worker(workerUrl, { type: 'module' })\n\tconst entry: WorkerEntry = { worker, pending: new Map() }\n\n\tworker.onmessage = (event: MessageEvent<WorkerResponse>) => {\n\t\tconst response = event.data\n\t\tconst requestId = entry.pending.get(response.id)\n\t\tif (!requestId) {\n\t\t\treturn\n\t\t}\n\t\tentry.pending.delete(response.id)\n\t\tconst port = findPortForRequest(requestId)\n\t\tif (port) {\n\t\t\tconst msg: SharedWorkerRpcResponse = {\n\t\t\t\ttype: SW_RESPONSE,\n\t\t\t\trequestId,\n\t\t\t\tresponse,\n\t\t\t}\n\t\t\tport.postMessage(msg)\n\t\t}\n\t}\n\n\tpools.set(dbName, entry)\n\treturn entry\n}\n\nconst portRequestIds = new Map<MessagePort, Set<string>>()\nconst requestPort = new Map<string, MessagePort>()\n\nfunction findPortForRequest(requestId: string): MessagePort | undefined {\n\treturn requestPort.get(requestId)\n}\n\nself.onconnect = (event: MessageEvent): void => {\n\tconst port = event.ports[0]\n\tif (!port) {\n\t\treturn\n\t}\n\n\tport.start()\n\tportRequestIds.set(port, new Set())\n\n\tport.addEventListener('message', (messageEvent: MessageEvent<SharedWorkerRpcRequest>) => {\n\t\tconst data = messageEvent.data\n\t\tif (data?.type !== SW_REQUEST) {\n\t\t\treturn\n\t\t}\n\n\t\tconst pool = getPool(data.dbName, data.workerUrl)\n\t\tpool.pending.set(data.request.id, data.requestId)\n\t\trequestPort.set(data.requestId, port)\n\t\tportRequestIds.get(port)?.add(data.requestId)\n\n\t\tpool.worker.postMessage(data.request)\n\t})\n\n\tport.addEventListener('messageerror', () => {\n\t\tconst ids = portRequestIds.get(port)\n\t\tif (!ids) {\n\t\t\treturn\n\t\t}\n\t\tfor (const requestId of ids) {\n\t\t\trequestPort.delete(requestId)\n\t\t}\n\t\tportRequestIds.delete(port)\n\t})\n}\n"],"mappings":";AAmBA,IAAM,aAAa;AACnB,IAAM,cAAc;AAuBpB,IAAM,QAAQ,oBAAI,IAAyB;AAE3C,SAAS,QAAQ,QAAgB,WAAgC;AAChE,QAAM,WAAW,MAAM,IAAI,MAAM;AACjC,MAAI,UAAU;AACb,WAAO;AAAA,EACR;AAEA,QAAM,SAAS,IAAI,OAAO,WAAW,EAAE,MAAM,SAAS,CAAC;AACvD,QAAM,QAAqB,EAAE,QAAQ,SAAS,oBAAI,IAAI,EAAE;AAExD,SAAO,YAAY,CAAC,UAAwC;AAC3D,UAAM,WAAW,MAAM;AACvB,UAAM,YAAY,MAAM,QAAQ,IAAI,SAAS,EAAE;AAC/C,QAAI,CAAC,WAAW;AACf;AAAA,IACD;AACA,UAAM,QAAQ,OAAO,SAAS,EAAE;AAChC,UAAM,OAAO,mBAAmB,SAAS;AACzC,QAAI,MAAM;AACT,YAAM,MAA+B;AAAA,QACpC,MAAM;AAAA,QACN;AAAA,QACA;AAAA,MACD;AACA,WAAK,YAAY,GAAG;AAAA,IACrB;AAAA,EACD;AAEA,QAAM,IAAI,QAAQ,KAAK;AACvB,SAAO;AACR;AAEA,IAAM,iBAAiB,oBAAI,IAA8B;AACzD,IAAM,cAAc,oBAAI,IAAyB;AAEjD,SAAS,mBAAmB,WAA4C;AACvE,SAAO,YAAY,IAAI,SAAS;AACjC;AAEA,KAAK,YAAY,CAAC,UAA8B;AAC/C,QAAM,OAAO,MAAM,MAAM,CAAC;AAC1B,MAAI,CAAC,MAAM;AACV;AAAA,EACD;AAEA,OAAK,MAAM;AACX,iBAAe,IAAI,MAAM,oBAAI,IAAI,CAAC;AAElC,OAAK,iBAAiB,WAAW,CAAC,iBAAuD;AACxF,UAAM,OAAO,aAAa;AAC1B,QAAI,MAAM,SAAS,YAAY;AAC9B;AAAA,IACD;AAEA,UAAM,OAAO,QAAQ,KAAK,QAAQ,KAAK,SAAS;AAChD,SAAK,QAAQ,IAAI,KAAK,QAAQ,IAAI,KAAK,SAAS;AAChD,gBAAY,IAAI,KAAK,WAAW,IAAI;AACpC,mBAAe,IAAI,IAAI,GAAG,IAAI,KAAK,SAAS;AAE5C,SAAK,OAAO,YAAY,KAAK,OAAO;AAAA,EACrC,CAAC;AAED,OAAK,iBAAiB,gBAAgB,MAAM;AAC3C,UAAM,MAAM,eAAe,IAAI,IAAI;AACnC,QAAI,CAAC,KAAK;AACT;AAAA,IACD;AACA,eAAW,aAAa,KAAK;AAC5B,kBAAY,OAAO,SAAS;AAAA,IAC7B;AACA,mBAAe,OAAO,IAAI;AAAA,EAC3B,CAAC;AACF;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/backup/backup.ts"],"sourcesContent":["import { generateUUIDv7 } from '@korajs/core'\nimport type { Operation, SchemaDefinition, VersionVector } from '@korajs/core'\nimport { deserializeOperationWithCollection } from '../serialization/serializer'\nimport type { OperationRow, StorageAdapter } from '../types'\nimport type {\n\tBackupManifest,\n\tBackupOptions,\n\tBackupProgress,\n\tRestoreOptions,\n\tRestoreResult,\n} from './types'\n\n// ── Section I/O helpers ──────────────────────────────────────────────────────\n\nfunction encodeSection(name: string, content: Uint8Array): Uint8Array {\n\tconst nameBytes = new TextEncoder().encode(name)\n\tconst header = new Uint8Array(8)\n\tconst dv = new DataView(header.buffer)\n\tdv.setUint32(0, nameBytes.length, true)\n\tdv.setUint32(4, content.length, true)\n\n\tconst result = new Uint8Array(header.length + nameBytes.length + content.length)\n\tresult.set(header, 0)\n\tresult.set(nameBytes, 8)\n\tresult.set(content, 8 + nameBytes.length)\n\treturn result\n}\n\nfunction encodeJsonSection(name: string, data: unknown): Uint8Array {\n\tconst json = JSON.stringify(data)\n\treturn encodeSection(name, new TextEncoder().encode(json))\n}\n\nfunction encodeNdjsonSection(name: string, records: unknown[]): Uint8Array {\n\tconst lines = `${records.map((r) => JSON.stringify(r)).join('\\n')}\\n`\n\treturn encodeSection(name, new TextEncoder().encode(lines))\n}\n\nfunction readUint32(view: DataView, offset: number): number {\n\treturn view.getUint32(offset, true)\n}\n\ninterface Section {\n\tname: string\n\tcontent: Uint8Array\n}\n\nfunction parseSections(data: Uint8Array): Section[] {\n\tconst sections: Section[] = []\n\tconst dv = new DataView(data.buffer, data.byteOffset, data.byteLength)\n\tlet offset = 0\n\n\twhile (offset + 8 <= data.byteLength) {\n\t\tconst nameLen = readUint32(dv, offset)\n\t\tconst contentLen = readUint32(dv, offset + 4)\n\t\toffset += 8\n\n\t\tif (offset + nameLen + contentLen > data.byteLength) break\n\n\t\tconst name = new TextDecoder().decode(data.slice(offset, offset + nameLen))\n\t\toffset += nameLen\n\n\t\tconst content = data.slice(offset, offset + contentLen)\n\t\toffset += contentLen\n\n\t\tsections.push({ name, content })\n\t}\n\n\treturn sections\n}\n\nfunction findSection(sections: Section[], name: string): Uint8Array | null {\n\tfor (const s of sections) {\n\t\tif (s.name === name) return s.content\n\t}\n\treturn null\n}\n\nfunction parseJsonSection<T>(sections: Section[], name: string): T | null {\n\tconst content = findSection(sections, name)\n\tif (!content) return null\n\treturn JSON.parse(new TextDecoder().decode(content)) as T\n}\n\n/**\n * Export format version. Bump on breaking changes.\n */\nconst BACKUP_VERSION = 1\n\n/**\n * Export a backup from a store.\n *\n * Reads all data via the adapter and produces a portable binary backup.\n *\n * @param adapter - The storage adapter\n * @param schema - The schema definition\n * @param nodeId - Current node ID\n * @param schemaVersion - Schema version\n * @param options - Backup options\n * @returns Backup as a Uint8Array\n */\nexport async function exportBackup(\n\tadapter: StorageAdapter,\n\tschema: SchemaDefinition,\n\tnodeId: string,\n\tschemaVersion: number,\n\toptions?: BackupOptions,\n): Promise<Uint8Array> {\n\tconst onProgress = options?.onProgress ?? (() => {})\n\tconst includeRecords = options?.includeRecords ?? true\n\tconst collectionFilter = options?.collections\n\tconst collections = collectionFilter\n\t\t? Object.keys(schema.collections).filter((c) => collectionFilter.includes(c))\n\t\t: Object.keys(schema.collections)\n\n\tonProgress({ phase: 'reading', progress: 0, message: 'Reading version vector' })\n\tconst versionVector = await readVersionVector(adapter)\n\tonProgress({ phase: 'reading', progress: 0.1, message: 'Reading metadata' })\n\tconst meta = await readMeta(adapter)\n\tonProgress({ phase: 'reading', progress: 0.2, message: 'Reading operations' })\n\tconst operations = await readAllOperations(adapter, schema)\n\tonProgress({ phase: 'reading', progress: 0.5, message: 'Reading records' })\n\n\tconst sections: Uint8Array[] = []\n\tlet allContentForChecksum = new Uint8Array(0)\n\n\t// Helper: add a section and update running checksum content\n\tconst addSection = (name: string, data: Uint8Array) => {\n\t\tsections.push(data)\n\t\tconst newLen = allContentForChecksum.length + data.length\n\t\tconst combined = new Uint8Array(newLen)\n\t\tcombined.set(allContentForChecksum, 0)\n\t\tcombined.set(data, allContentForChecksum.length)\n\t\tallContentForChecksum = combined\n\t}\n\n\t// Version vector section\n\tconst vvObj: Record<string, number> = {}\n\tfor (const [nid, seq] of versionVector) {\n\t\tvvObj[nid] = seq\n\t}\n\taddSection('version_vector', encodeJsonSection('version_vector', vvObj))\n\n\t// Meta section\n\tconst metaObj: Record<string, string> = {}\n\tfor (const row of meta) {\n\t\tmetaObj[row.key] = row.value\n\t}\n\taddSection('meta', encodeJsonSection('meta', metaObj))\n\n\t// Operations section\n\tconst opLines = `${operations.map((op) => JSON.stringify(op)).join('\\n')}\\n`\n\taddSection('operations', encodeSection('operations', new TextEncoder().encode(opLines)))\n\n\t// Records sections (optional)\n\tlet totalRecords = 0\n\tif (includeRecords) {\n\t\tfor (const col of collections) {\n\t\t\tconst records = await readCollectionRecords(adapter, col)\n\t\t\tif (records.length > 0) {\n\t\t\t\tconst lines = `${records.map((r) => JSON.stringify(r)).join('\\n')}\\n`\n\t\t\t\tconst sectionName = `records:${col}`\n\t\t\t\taddSection(sectionName, encodeSection(sectionName, new TextEncoder().encode(lines)))\n\t\t\t\ttotalRecords += records.length\n\t\t\t}\n\t\t}\n\t}\n\tonProgress({ phase: 'reading', progress: 0.9, message: 'Computing checksum' })\n\n\t// Compute SHA-256 checksum over all content sections\n\tconst checksumHex = await computeSha256(allContentForChecksum)\n\tonProgress({ phase: 'writing', progress: 1, message: 'Finalizing' })\n\n\t// Build manifest\n\tconst manifest: BackupManifest = {\n\t\tversion: BACKUP_VERSION,\n\t\tcreatedAt: Date.now(),\n\t\tnodeId,\n\t\tschemaVersion,\n\t\toperationCount: operations.length,\n\t\tcollections,\n\t\tincludesRecords: includeRecords,\n\t\tchecksum: checksumHex,\n\t}\n\n\t// Final output: header + manifest + all content sections + checksum section\n\tconst header = new Uint8Array(0)\n\n\t// Merge manifest + all sections + checksum section\n\tconst manifestSection = encodeJsonSection('manifest', manifest)\n\tconst checksumSection = encodeSection('checksum', new TextEncoder().encode(checksumHex))\n\n\tconst totalLen = manifestSection.length + allContentForChecksum.length + checksumSection.length\n\tconst result = new Uint8Array(totalLen)\n\tlet pos = 0\n\tresult.set(manifestSection, pos)\n\tpos += manifestSection.length\n\tresult.set(allContentForChecksum, pos)\n\tpos += allContentForChecksum.length\n\tresult.set(checksumSection, pos)\n\n\treturn result\n}\n\n/**\n * Read backup manifest without loading the entire backup.\n * Parses only the first section (which is always the manifest).\n *\n * @param data - The raw backup data\n * @returns The backup manifest\n */\nexport function readBackupManifest(data: Uint8Array): BackupManifest {\n\tconst sections = parseSections(data)\n\tconst manifest = parseJsonSection<BackupManifest>(sections, 'manifest')\n\tif (!manifest) {\n\t\tthrow new Error('Invalid backup: manifest section not found')\n\t}\n\treturn manifest\n}\n\n/**\n * Validate the backup checksum.\n *\n * @param data - The raw backup data\n * @returns True if the checksum is valid\n */\nexport async function verifyBackupChecksum(data: Uint8Array): Promise<boolean> {\n\tconst sections = parseSections(data)\n\tconst manifest = parseJsonSection<BackupManifest>(sections, 'manifest')\n\tif (!manifest) return false\n\n\tconst storedChecksum = manifest.checksum\n\tif (!storedChecksum) return false\n\n\t// Recompute checksum over content sections (everything after manifest, before checksum)\n\tconst manifestSection = findSection(sections, 'manifest')\n\tif (!manifestSection) return false\n\n\t// Find the checksum section\n\tconst checksumSection = findSection(sections, 'checksum')\n\n\t// The content spans from end of manifest section to start of checksum section\n\tconst checksumBytes = checksumSection\n\t\t? new TextEncoder().encode(manifest.checksum)\n\t\t: new Uint8Array(0)\n\n\t// Rebuild all content sections and hash them\n\tconst contentSections = sections.filter((s) => s.name !== 'manifest' && s.name !== 'checksum')\n\n\tlet contentForHash = new Uint8Array(0)\n\tfor (const section of contentSections) {\n\t\tconst sectionBytes = encodeSection(section.name, section.content)\n\t\tconst newLen = contentForHash.length + sectionBytes.length\n\t\tconst combined = new Uint8Array(newLen)\n\t\tcombined.set(contentForHash, 0)\n\t\tcombined.set(sectionBytes, contentForHash.length)\n\t\tcontentForHash = combined\n\t}\n\n\tconst computed = await computeSha256(contentForHash)\n\treturn computed === storedChecksum\n}\n\n/**\n * Restore a backup into a store.\n *\n * @param adapter - The storage adapter\n * @param schema - The schema definition\n * @param data - The backup data\n * @param options - Restore options\n * @returns Result of the restore operation\n */\nexport async function restoreBackup(\n\tadapter: StorageAdapter,\n\tschema: SchemaDefinition,\n\tdata: Uint8Array,\n\toptions?: RestoreOptions,\n): Promise<RestoreResult> {\n\tconst startTime = Date.now()\n\tconst onProgress = options?.onProgress ?? (() => {})\n\tconst collectionFilter = options?.collections\n\n\tonProgress({ phase: 'verifying', progress: 0, message: 'Parsing backup' })\n\n\tconst sections = parseSections(data)\n\tconst manifest = parseJsonSection<BackupManifest>(sections, 'manifest')\n\tif (!manifest) {\n\t\treturn {\n\t\t\toperationsRestored: 0,\n\t\t\trecordsRestored: 0,\n\t\t\tsuccess: false,\n\t\t\terror: 'Invalid backup: manifest section not found',\n\t\t\tduration: Date.now() - startTime,\n\t\t}\n\t}\n\n\t// Validate format version\n\tif (manifest.version !== BACKUP_VERSION) {\n\t\treturn {\n\t\t\toperationsRestored: 0,\n\t\t\trecordsRestored: 0,\n\t\t\tsuccess: false,\n\t\t\terror: `Unsupported backup version: ${manifest.version}`,\n\t\t\tduration: Date.now() - startTime,\n\t\t}\n\t}\n\n\tonProgress({ phase: 'verifying', progress: 0.2, message: 'Verifying checksum' })\n\n\t// Verify checksum\n\tconst valid = await verifyBackupChecksum(data)\n\tif (!valid) {\n\t\treturn {\n\t\t\toperationsRestored: 0,\n\t\t\trecordsRestored: 0,\n\t\t\tsuccess: false,\n\t\t\terror: 'Backup checksum mismatch: data may be corrupted',\n\t\t\tduration: Date.now() - startTime,\n\t\t}\n\t}\n\n\tonProgress({ phase: 'restoring', progress: 0.3, message: 'Loading operations' })\n\n\t// Parse operations\n\tconst opsContent = findSection(sections, 'operations')\n\tlet operations: Operation[] = []\n\tif (opsContent) {\n\t\tconst text = new TextDecoder().decode(opsContent)\n\t\tconst lines = text\n\t\t\t.trim()\n\t\t\t.split('\\n')\n\t\t\t.filter((l) => l.length > 0)\n\t\toperations = lines.map((line) => JSON.parse(line) as Operation)\n\t}\n\n\t// Filter collections if requested\n\tif (collectionFilter) {\n\t\toperations = operations.filter((op) => collectionFilter.includes(op.collection))\n\t}\n\n\t// Load version vector\n\tconst vvData = parseJsonSection<Record<string, number>>(sections, 'version_vector')\n\n\t// Load meta\n\tconst metaData = parseJsonSection<Record<string, string>>(sections, 'meta')\n\n\tonProgress({ phase: 'restoring', progress: 0.4, message: 'Applying operations' })\n\n\tif (options?.merge) {\n\t\t// Merge mode: replay operations through applyRemoteOperation.\n\t\t// This won't work directly since adapter doesn't have applyRemoteOperation.\n\t\t// Instead, we import the raw data into the adapter.\n\t\tawait adapter.transaction(async (tx) => {\n\t\t\t// Import version vector\n\t\t\tif (vvData) {\n\t\t\t\tfor (const [nid, seq] of Object.entries(vvData)) {\n\t\t\t\t\tawait tx.execute(\n\t\t\t\t\t\t'INSERT OR REPLACE INTO _kora_version_vector (node_id, sequence_number) VALUES (?, ?)',\n\t\t\t\t\t\t[nid, seq],\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Import meta\n\t\t\tif (metaData) {\n\t\t\t\tfor (const [key, value] of Object.entries(metaData)) {\n\t\t\t\t\tawait tx.execute('INSERT OR REPLACE INTO _kora_meta (key, value) VALUES (?, ?)', [\n\t\t\t\t\t\tkey,\n\t\t\t\t\t\tvalue,\n\t\t\t\t\t])\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Import operations into _kora_ops_ tables\n\t\t\tfor (const op of operations) {\n\t\t\t\tconst collection = op.collection\n\t\t\t\tconst opRow = {\n\t\t\t\t\tid: op.id,\n\t\t\t\t\tnode_id: op.nodeId,\n\t\t\t\t\ttype: op.type,\n\t\t\t\t\trecord_id: op.recordId,\n\t\t\t\t\tdata: op.data !== null ? JSON.stringify(op.data) : null,\n\t\t\t\t\tprevious_data: op.previousData !== null ? JSON.stringify(op.previousData) : null,\n\t\t\t\t\ttimestamp: JSON.stringify(op.timestamp),\n\t\t\t\t\tsequence_number: op.sequenceNumber,\n\t\t\t\t\tcausal_deps: JSON.stringify(op.causalDeps),\n\t\t\t\t\tschema_version: op.schemaVersion,\n\t\t\t\t}\n\n\t\t\t\tawait tx.execute(\n\t\t\t\t\t`INSERT OR IGNORE INTO _kora_ops_${collection} (id, node_id, type, record_id, data, previous_data, timestamp, sequence_number, causal_deps, schema_version) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n\t\t\t\t\t[\n\t\t\t\t\t\topRow.id,\n\t\t\t\t\t\topRow.node_id,\n\t\t\t\t\t\topRow.type,\n\t\t\t\t\t\topRow.record_id,\n\t\t\t\t\t\topRow.data,\n\t\t\t\t\t\topRow.previous_data,\n\t\t\t\t\t\topRow.timestamp,\n\t\t\t\t\t\topRow.sequence_number,\n\t\t\t\t\t\topRow.causal_deps,\n\t\t\t\t\t\topRow.schema_version,\n\t\t\t\t\t],\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t} else {\n\t\t// Replace mode: clear existing data and bulk import\n\t\tawait adapter.transaction(async (tx) => {\n\t\t\t// Clear existing data\n\t\t\tfor (const collection of Object.keys(schema.collections)) {\n\t\t\t\tawait tx.execute(`DELETE FROM _kora_ops_${collection}`)\n\t\t\t\tawait tx.execute(`DELETE FROM ${collection}`)\n\t\t\t}\n\t\t\tawait tx.execute('DELETE FROM _kora_version_vector')\n\t\t\tawait tx.execute('DELETE FROM _kora_meta')\n\n\t\t\t// Import version vector\n\t\t\tif (vvData) {\n\t\t\t\tfor (const [nid, seq] of Object.entries(vvData)) {\n\t\t\t\t\tawait tx.execute(\n\t\t\t\t\t\t'INSERT INTO _kora_version_vector (node_id, sequence_number) VALUES (?, ?)',\n\t\t\t\t\t\t[nid, seq],\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Import meta\n\t\t\tif (metaData) {\n\t\t\t\tfor (const [key, value] of Object.entries(metaData)) {\n\t\t\t\t\tawait tx.execute('INSERT INTO _kora_meta (key, value) VALUES (?, ?)', [key, value])\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Import operations\n\t\t\tfor (const op of operations) {\n\t\t\t\tconst collection = op.collection\n\t\t\t\tawait tx.execute(\n\t\t\t\t\t`INSERT INTO _kora_ops_${collection} (id, node_id, type, record_id, data, previous_data, timestamp, sequence_number, causal_deps, schema_version) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n\t\t\t\t\t[\n\t\t\t\t\t\top.id,\n\t\t\t\t\t\top.nodeId,\n\t\t\t\t\t\top.type,\n\t\t\t\t\t\top.recordId,\n\t\t\t\t\t\top.data !== null ? JSON.stringify(op.data) : null,\n\t\t\t\t\t\top.previousData !== null ? JSON.stringify(op.previousData) : null,\n\t\t\t\t\t\tJSON.stringify(op.timestamp),\n\t\t\t\t\t\top.sequenceNumber,\n\t\t\t\t\t\tJSON.stringify(op.causalDeps),\n\t\t\t\t\t\top.schemaVersion,\n\t\t\t\t\t],\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t}\n\n\tonProgress({ phase: 'restoring', progress: 0.8, message: 'Restoring records' })\n\n\t// Restore records sections (optional)\n\tlet recordsRestored = 0\n\tif (manifest.includesRecords) {\n\t\tfor (const col of manifest.collections) {\n\t\t\tif (collectionFilter && !collectionFilter.includes(col)) continue\n\n\t\t\tconst sectionContent = findSection(sections, `records:${col}`)\n\t\t\tif (!sectionContent) continue\n\n\t\t\tconst text = new TextDecoder().decode(sectionContent)\n\t\t\tconst lines = text\n\t\t\t\t.trim()\n\t\t\t\t.split('\\n')\n\t\t\t\t.filter((l) => l.length > 0)\n\t\t\tconst records = lines.map((line) => JSON.parse(line) as Record<string, unknown>)\n\n\t\t\tif (records.length === 0) continue\n\n\t\t\tawait adapter.transaction(async (tx) => {\n\t\t\t\tfor (const record of records) {\n\t\t\t\t\tconst keys = Object.keys(record)\n\t\t\t\t\tconst placeholders = keys.map(() => '?').join(', ')\n\t\t\t\t\tconst values = keys.map((k) => {\n\t\t\t\t\t\tconst v = record[k]\n\t\t\t\t\t\tif (typeof v === 'boolean') return v ? 1 : 0\n\t\t\t\t\t\tif (Array.isArray(v) || (typeof v === 'object' && v !== null)) return JSON.stringify(v)\n\t\t\t\t\t\treturn v\n\t\t\t\t\t})\n\n\t\t\t\t\tawait tx.execute(\n\t\t\t\t\t\t`INSERT OR REPLACE INTO ${col} (${keys.join(', ')}) VALUES (${placeholders})`,\n\t\t\t\t\t\tvalues,\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t})\n\n\t\t\trecordsRestored += records.length\n\t\t}\n\t}\n\n\tonProgress({ phase: 'restoring', progress: 1, message: 'Done' })\n\n\treturn {\n\t\toperationsRestored: operations.length,\n\t\trecordsRestored,\n\t\tsuccess: true,\n\t\tduration: Date.now() - startTime,\n\t}\n}\n\n// ── Reading helpers ──────────────────────────────────────────────────────────\n\nasync function readVersionVector(adapter: StorageAdapter): Promise<Map<string, number>> {\n\tconst rows = await adapter.query<{ node_id: string; sequence_number: number }>(\n\t\t'SELECT node_id, sequence_number FROM _kora_version_vector',\n\t)\n\tconst vv = new Map<string, number>()\n\tfor (const row of rows) {\n\t\tvv.set(row.node_id, row.sequence_number)\n\t}\n\treturn vv\n}\n\ninterface MetaRow {\n\tkey: string\n\tvalue: string\n}\n\nasync function readMeta(adapter: StorageAdapter): Promise<MetaRow[]> {\n\treturn adapter.query<MetaRow>('SELECT key, value FROM _kora_meta')\n}\n\nasync function readAllOperations(\n\tadapter: StorageAdapter,\n\tschema: SchemaDefinition,\n): Promise<Operation[]> {\n\tconst allOps: Operation[] = []\n\n\tfor (const collectionName of Object.keys(schema.collections)) {\n\t\tconst rows = await adapter.query<OperationRow>(\n\t\t\t`SELECT * FROM _kora_ops_${collectionName} ORDER BY sequence_number ASC`,\n\t\t)\n\n\t\tfor (const row of rows) {\n\t\t\tallOps.push(deserializeOperationWithCollection(row, collectionName))\n\t\t}\n\t}\n\n\treturn allOps\n}\n\nasync function readCollectionRecords(\n\tadapter: StorageAdapter,\n\tcollection: string,\n): Promise<Record<string, unknown>[]> {\n\treturn adapter.query<Record<string, unknown>>(`SELECT * FROM ${collection} WHERE _deleted = 0`)\n}\n\n// ── Crypto helpers ───────────────────────────────────────────────────────────\n\nasync function computeSha256(data: Uint8Array<ArrayBuffer>): Promise<string> {\n\tconst hashBuffer = await crypto.subtle.digest('SHA-256', data)\n\tconst hashArray = Array.from(new Uint8Array(hashBuffer))\n\treturn hashArray.map((b) => b.toString(16).padStart(2, '0')).join('')\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/audit/export-audit.ts"],"sourcesContent":["import type { Operation, SchemaDefinition } from '@korajs/core'\nimport { deserializeOperationWithCollection } from '../serialization/serializer'\nimport type { StorageAdapter } from '../types'\nimport type { OperationRow } from '../types'\nimport { deserializeAuditJson, serializeAuditJson } from './audit-json'\nimport { readAuditTraces } from './audit-trace-store'\nimport type {\n\tAuditExportManifest,\n\tAuditExportOptions,\n\tAuditExportPayload,\n\tAuditTraceQuery,\n\tPersistedAuditTrace,\n} from './types'\n\nfunction encodeSection(name: string, content: Uint8Array): Uint8Array {\n\tconst nameBytes = new TextEncoder().encode(name)\n\tconst header = new Uint8Array(8)\n\tconst view = new DataView(header.buffer)\n\tview.setUint32(0, nameBytes.length, true)\n\tview.setUint32(4, content.length, true)\n\n\tconst result = new Uint8Array(header.length + nameBytes.length + content.length)\n\tresult.set(header, 0)\n\tresult.set(nameBytes, 8)\n\tresult.set(content, 8 + nameBytes.length)\n\treturn result\n}\n\nfunction encodeJsonSection(name: string, data: unknown): Uint8Array {\n\treturn encodeSection(name, new TextEncoder().encode(serializeAuditJson(data)))\n}\n\nfunction encodeNdjsonSection(name: string, records: unknown[]): Uint8Array {\n\tconst lines = `${records.map((record) => serializeAuditJson(record)).join('\\n')}\\n`\n\treturn encodeSection(name, new TextEncoder().encode(lines))\n}\n\nfunction readUint32(view: DataView, offset: number): number {\n\treturn view.getUint32(offset, true)\n}\n\ninterface Section {\n\tname: string\n\tcontent: Uint8Array\n}\n\nfunction parseSections(data: Uint8Array): Section[] {\n\tconst sections: Section[] = []\n\tconst view = new DataView(data.buffer, data.byteOffset, data.byteLength)\n\tlet offset = 0\n\n\twhile (offset + 8 <= data.byteLength) {\n\t\tconst nameLen = readUint32(view, offset)\n\t\tconst contentLen = readUint32(view, offset + 4)\n\t\toffset += 8\n\n\t\tif (offset + nameLen + contentLen > data.byteLength) {\n\t\t\tbreak\n\t\t}\n\n\t\tconst name = new TextDecoder().decode(data.slice(offset, offset + nameLen))\n\t\toffset += nameLen\n\n\t\tconst content = data.slice(offset, offset + contentLen)\n\t\toffset += contentLen\n\n\t\tsections.push({ name, content })\n\t}\n\n\treturn sections\n}\n\nfunction findSection(sections: Section[], name: string): Uint8Array | null {\n\tfor (const section of sections) {\n\t\tif (section.name === name) {\n\t\t\treturn section.content\n\t\t}\n\t}\n\treturn null\n}\n\nasync function computeSha256(data: Uint8Array<ArrayBuffer>): Promise<string> {\n\tconst hashBuffer = await crypto.subtle.digest('SHA-256', data)\n\treturn Array.from(new Uint8Array(hashBuffer))\n\t\t.map((byte) => byte.toString(16).padStart(2, '0'))\n\t\t.join('')\n}\n\nasync function readAllOperations(\n\tadapter: StorageAdapter,\n\tschema: SchemaDefinition,\n\tcollectionFilter?: string[],\n): Promise<Operation[]> {\n\tconst collections = collectionFilter\n\t\t? Object.keys(schema.collections).filter((name) => collectionFilter.includes(name))\n\t\t: Object.keys(schema.collections)\n\n\tconst allOps: Operation[] = []\n\tfor (const collectionName of collections) {\n\t\tconst rows = await adapter.query<OperationRow>(\n\t\t\t`SELECT * FROM _kora_ops_${collectionName} ORDER BY sequence_number ASC`,\n\t\t)\n\n\t\tfor (const row of rows) {\n\t\t\tallOps.push(deserializeOperationWithCollection(row, collectionName))\n\t\t}\n\t}\n\n\treturn allOps\n}\n\nfunction filterOperationsByCollections(\n\toperations: Operation[],\n\tcollectionFilter?: string[],\n): Operation[] {\n\tif (!collectionFilter || collectionFilter.length === 0) {\n\t\treturn operations\n\t}\n\tconst allowed = new Set(collectionFilter)\n\treturn operations.filter((op) => allowed.has(op.collection))\n}\n\n/**\n * Export operations and persisted merge traces as a portable binary audit bundle.\n */\nexport async function exportAudit(\n\tadapter: StorageAdapter,\n\tschema: SchemaDefinition,\n\tnodeId: string,\n\tschemaVersion: number,\n\toptions?: AuditExportOptions,\n): Promise<Uint8Array> {\n\tconst onProgress = options?.onProgress ?? (() => {})\n\tconst traceQuery: AuditTraceQuery = {\n\t\tcollections: options?.collections,\n\t\tsince: options?.since,\n\t\tuntil: options?.until,\n\t}\n\n\tonProgress({ phase: 'reading', progress: 0, message: 'Reading operations' })\n\tconst operations = filterOperationsByCollections(\n\t\tawait readAllOperations(adapter, schema, options?.collections),\n\t\toptions?.collections,\n\t)\n\n\tonProgress({ phase: 'reading', progress: 0.4, message: 'Reading merge traces' })\n\tconst mergeTraces = await readAuditTraces(adapter, traceQuery)\n\n\tonProgress({ phase: 'writing', progress: 0.7, message: 'Encoding audit export' })\n\tconst sections: Uint8Array[] = []\n\tlet contentForChecksum = new Uint8Array(0)\n\n\tconst addSection = (section: Uint8Array): void => {\n\t\tsections.push(section)\n\t\tconst combined = new Uint8Array(contentForChecksum.length + section.length)\n\t\tcombined.set(contentForChecksum, 0)\n\t\tcombined.set(section, contentForChecksum.length)\n\t\tcontentForChecksum = combined\n\t}\n\n\taddSection(encodeNdjsonSection('operations', operations))\n\taddSection(encodeNdjsonSection('merge_traces', mergeTraces))\n\n\tonProgress({ phase: 'verifying', progress: 0.9, message: 'Computing checksum' })\n\tconst checksum = await computeSha256(contentForChecksum as Uint8Array<ArrayBuffer>)\n\n\tconst manifest: AuditExportManifest = {\n\t\tversion: 1,\n\t\texportedAt: Date.now(),\n\t\tnodeId,\n\t\tschemaVersion,\n\t\toperationCount: operations.length,\n\t\tmergeTraceCount: mergeTraces.length,\n\t\tchecksum,\n\t}\n\n\tconst manifestSection = encodeJsonSection('manifest', manifest)\n\tconst checksumSection = encodeSection('checksum', new TextEncoder().encode(checksum))\n\n\tonProgress({ phase: 'writing', progress: 1, message: 'Finalizing' })\n\n\tconst parts = [manifestSection, ...sections, checksumSection]\n\tconst finalOutput = new Uint8Array(parts.reduce((sum, part) => sum + part.length, 0))\n\tlet offset = 0\n\tfor (const part of parts) {\n\t\tfinalOutput.set(part, offset)\n\t\toffset += part.length\n\t}\n\n\treturn finalOutput\n}\n\n/**\n * Decode an audit export binary into structured data.\n */\nexport function decodeAuditExport(data: Uint8Array): AuditExportPayload {\n\tconst sections = parseSections(data)\n\tconst manifest = JSON.parse(\n\t\tnew TextDecoder().decode(findSection(sections, 'manifest') ?? new Uint8Array()),\n\t) as AuditExportManifest\n\n\tconst operationsContent = findSection(sections, 'operations')\n\tconst mergeTracesContent = findSection(sections, 'merge_traces')\n\n\tconst operations = parseNdjsonSection<Operation>(operationsContent)\n\tconst mergeTraces = parseNdjsonSection<PersistedAuditTrace>(mergeTracesContent)\n\n\treturn { manifest, operations, mergeTraces }\n}\n\n/**\n * Read only the manifest from an audit export without decoding all sections.\n */\nexport function readAuditExportManifest(data: Uint8Array): AuditExportManifest {\n\tconst sections = parseSections(data)\n\tconst manifestContent = findSection(sections, 'manifest')\n\tif (!manifestContent) {\n\t\tthrow new Error('Invalid audit export: missing manifest section')\n\t}\n\treturn JSON.parse(new TextDecoder().decode(manifestContent)) as AuditExportManifest\n}\n\n/**\n * Verify the checksum of an audit export file.\n */\nexport async function verifyAuditExportChecksum(data: Uint8Array): Promise<boolean> {\n\tconst sections = parseSections(data)\n\tconst manifest = readAuditExportManifest(data)\n\tconst contentSections = sections.filter(\n\t\t(section) => section.name !== 'manifest' && section.name !== 'checksum',\n\t)\n\n\tlet content = new Uint8Array(0)\n\tfor (const section of contentSections) {\n\t\tconst encoded = encodeSection(section.name, section.content)\n\t\tconst combined = new Uint8Array(content.length + encoded.length)\n\t\tcombined.set(content, 0)\n\t\tcombined.set(encoded, content.length)\n\t\tcontent = combined\n\t}\n\n\tconst checksum = await computeSha256(content as Uint8Array<ArrayBuffer>)\n\treturn checksum === manifest.checksum\n}\n\nfunction parseNdjsonSection<T>(content: Uint8Array | null): T[] {\n\tif (!content || content.length === 0) {\n\t\treturn []\n\t}\n\tconst text = new TextDecoder().decode(content).trim()\n\tif (text.length === 0) {\n\t\treturn []\n\t}\n\treturn text.split('\\n').map((line) => deserializeAuditJson<T>(line))\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/serialization/richtext-serializer.ts","../src/serialization/op-data-encoding.ts","../src/serialization/serializer.ts"],"sourcesContent":["import * as Y from 'yjs'\nimport {\n\ttype KoraBytesValue,\n\tdecodeRichtextOpDataValue,\n\tisKoraBytesValue,\n\tisLegacyNumericByteObject,\n} from './op-data-encoding'\n\nconst TEXT_KEY = 'content'\n\nexport type RichtextInput = string | Uint8Array | ArrayBuffer | null | undefined\n\n/**\n * Encodes richtext values into Yjs document updates.\n *\n * Also accepts the tagged `{ $koraBytes }` form that binary richtext values\n * take inside `op.data` (and, for old dev databases, the pre-fix numeric-key\n * object shape), so every consumer that writes op-data values to columns\n * inherits the decoding. Plain strings keep their exact historical behavior:\n * encoded as a fresh Yjs document containing the string.\n */\nexport function encodeRichtext(value: RichtextInput | KoraBytesValue): Uint8Array | null {\n\tif (value === null || value === undefined) {\n\t\treturn null\n\t}\n\n\tif (typeof value === 'string') {\n\t\tconst doc = new Y.Doc()\n\t\tdoc.getText(TEXT_KEY).insert(0, value)\n\t\treturn Y.encodeStateAsUpdate(doc)\n\t}\n\n\tif (value instanceof Uint8Array) {\n\t\treturn value\n\t}\n\n\tif (value instanceof ArrayBuffer) {\n\t\treturn new Uint8Array(value)\n\t}\n\n\tif (isKoraBytesValue(value) || isLegacyNumericByteObject(value)) {\n\t\tconst decoded = decodeRichtextOpDataValue(value)\n\t\t// Both accepted shapes decode to bytes; strings were handled above.\n\t\treturn decoded instanceof Uint8Array ? decoded : null\n\t}\n\n\tthrow new Error('Richtext value must be a string, Uint8Array, ArrayBuffer, null, or undefined.')\n}\n\n/**\n * Decodes driver-provided richtext values into Uint8Array.\n */\nexport function decodeRichtext(value: unknown): Uint8Array | null {\n\tif (value === null || value === undefined) {\n\t\treturn null\n\t}\n\n\tif (typeof Buffer !== 'undefined' && Buffer.isBuffer(value)) {\n\t\treturn new Uint8Array(value)\n\t}\n\n\tif (value instanceof Uint8Array) {\n\t\treturn value\n\t}\n\n\tif (value instanceof ArrayBuffer) {\n\t\treturn new Uint8Array(value)\n\t}\n\n\tthrow new Error(\n\t\t'Richtext storage value must be Uint8Array, ArrayBuffer, Buffer, null, or undefined.',\n\t)\n}\n\n/**\n * Reads plain text from a richtext Yjs state update.\n */\nexport function richtextToPlainText(value: RichtextInput): string {\n\tconst encoded = encodeRichtext(value)\n\tif (!encoded) return ''\n\n\tconst doc = new Y.Doc()\n\tY.applyUpdate(doc, encoded)\n\treturn doc.getText(TEXT_KEY).toString()\n}\n\n/**\n * Compares two richtext values by decoded plain text.\n * Yjs update bytes are not stable across separate encodes of the same string.\n */\nexport function richtextStatesEqual(a: unknown, b: unknown): boolean {\n\ttry {\n\t\treturn richtextToPlainText(a as RichtextInput) === richtextToPlainText(b as RichtextInput)\n\t} catch {\n\t\treturn false\n\t}\n}\n","import {\n\ttype KoraBytesValue,\n\tbase64ToBytes,\n\tbytesToBase64,\n\tdecodeBytesFromOpData,\n\tencodeBytesForOpData,\n\tisKoraBytesValue,\n\tisLegacyNumericByteObject,\n} from '@korajs/core'\nimport type { FieldDescriptor } from '@korajs/core'\n\n/**\n * The canonical binary op-data encoding lives in `@korajs/core` because\n * `op.data` is a core concept shared by store (creation, persistence) and merge\n * (CRDT merge). These re-exports keep existing store-internal import paths\n * working while the single source of truth is the core module.\n */\nexport type { KoraBytesValue }\nexport { base64ToBytes, bytesToBase64, isKoraBytesValue, isLegacyNumericByteObject }\n\n/** @deprecated Use {@link encodeRichtextForOpData}; kept for existing imports. */\nexport const encodeRichtextForOpData = encodeBytesForOpData\n/** @deprecated Use {@link decodeRichtextOpDataValue}; kept for existing imports. */\nexport const decodeRichtextOpDataValue = decodeBytesFromOpData\n\n/**\n * Apply {@link encodeBytesForOpData} to every richtext-typed field in an\n * operation's data payload. Called at operation creation, BEFORE the payload\n * is content-hashed, so the hash input, persisted JSON, wire payload, and the\n * value the merge engine sees are the identical canonical value. Non-richtext\n * fields are untouched.\n */\nexport function encodeRichtextFieldsForOpData(\n\tdata: Record<string, unknown>,\n\tfields: Record<string, FieldDescriptor>,\n): Record<string, unknown> {\n\tconst result: Record<string, unknown> = { ...data }\n\tfor (const [key, value] of Object.entries(data)) {\n\t\tif (fields[key]?.kind !== 'richtext') {\n\t\t\tcontinue\n\t\t}\n\t\tif (typeof value === 'string' || value instanceof Uint8Array || value instanceof ArrayBuffer) {\n\t\t\tresult[key] = encodeBytesForOpData(value)\n\t\t}\n\t}\n\treturn result\n}\n\n/**\n * Reverse of {@link encodeRichtextFieldsForOpData}: decode tagged (or legacy\n * numeric-key) richtext values in an op-data payload back to record-shaped\n * values (string or Uint8Array). Null/undefined and plain strings pass\n * through, preserving pre-fix behavior exactly.\n */\nexport function decodeRichtextFieldsFromOpData(\n\tdata: Record<string, unknown>,\n\tfields: Record<string, FieldDescriptor>,\n): Record<string, unknown> {\n\tconst result: Record<string, unknown> = { ...data }\n\tfor (const [key, value] of Object.entries(data)) {\n\t\tif (fields[key]?.kind !== 'richtext' || value === null || value === undefined) {\n\t\t\tcontinue\n\t\t}\n\t\tresult[key] = decodeBytesFromOpData(value)\n\t}\n\treturn result\n}\n","import { HybridLogicalClock } from '@korajs/core'\nimport type { CollectionDefinition, FieldDescriptor, Operation } from '@korajs/core'\nimport type { CollectionRecord, OperationRow, RawCollectionRow } from '../types'\nimport { decodeRichtext, encodeRichtext } from './richtext-serializer'\n\n/**\n * Serialize a JS record to SQL-compatible values for INSERT/UPDATE.\n * Transforms: boolean → 0/1, array → JSON string, richtext → Yjs binary update.\n *\n * @param data - The record data with JS-native types\n * @param fields - The field descriptors from the schema\n * @returns An object with SQL-compatible values\n */\nexport function serializeRecord(\n\tdata: Record<string, unknown>,\n\tfields: Record<string, FieldDescriptor>,\n): Record<string, unknown> {\n\tconst result: Record<string, unknown> = {}\n\tfor (const [key, value] of Object.entries(data)) {\n\t\tconst descriptor = fields[key]\n\t\tif (!descriptor) {\n\t\t\tresult[key] = value\n\t\t\tcontinue\n\t\t}\n\t\tresult[key] = serializeValue(value, descriptor)\n\t}\n\treturn result\n}\n\n/**\n * Deserialize a SQL row to JS-native types for the application layer.\n * Transforms: 0/1 → boolean, JSON string → array, strips _deleted, maps _created_at/_updated_at.\n *\n * @param row - The raw SQL row\n * @param fields - The field descriptors from the schema\n * @returns A CollectionRecord with JS-native types\n */\nexport function deserializeRecord(\n\trow: RawCollectionRow,\n\tfields: Record<string, FieldDescriptor>,\n): CollectionRecord {\n\tconst result: CollectionRecord = {\n\t\tid: row.id,\n\t\tcreatedAt: row._created_at,\n\t\tupdatedAt: row._updated_at,\n\t}\n\n\tfor (const [key, descriptor] of Object.entries(fields)) {\n\t\tconst rawValue = row[key]\n\t\tif (rawValue === undefined || rawValue === null) {\n\t\t\tresult[key] = rawValue ?? null\n\t\t\tcontinue\n\t\t}\n\t\tresult[key] = deserializeValue(rawValue, descriptor)\n\t}\n\n\treturn result\n}\n\n/**\n * Internal key used to embed atomicOps metadata in the data JSON column.\n * This avoids adding a new column to the ops table (no migration needed).\n */\nconst ATOMIC_OPS_KEY = '__kora_atomic_ops__'\n\n/**\n * Internal key used to embed transactionId in the data JSON column.\n */\nconst TX_ID_KEY = '__kora_tx_id__'\n\n/**\n * Internal key used to embed mutationName in the data JSON column.\n */\nconst MUTATION_NAME_KEY = '__kora_mutation__'\n\n/**\n * Serialize an Operation to a row for the operations log table.\n *\n * @param op - The operation to serialize\n * @returns An OperationRow suitable for SQL INSERT\n */\nexport function serializeOperation(op: Operation): OperationRow {\n\tconst hasMetadata = op.transactionId !== undefined || op.mutationName !== undefined\n\tlet dataPayload: Record<string, unknown> | null = null\n\tif (op.data) {\n\t\t// Embed metadata in the data JSON when present\n\t\tdataPayload = { ...op.data }\n\t\tif (op.atomicOps !== undefined && Object.keys(op.atomicOps).length > 0) {\n\t\t\tdataPayload[ATOMIC_OPS_KEY] = op.atomicOps\n\t\t}\n\t\tif (op.transactionId !== undefined) {\n\t\t\tdataPayload[TX_ID_KEY] = op.transactionId\n\t\t}\n\t\tif (op.mutationName !== undefined) {\n\t\t\tdataPayload[MUTATION_NAME_KEY] = op.mutationName\n\t\t}\n\t} else if (hasMetadata) {\n\t\t// For delete operations (data is null), we still need to store metadata\n\t\tdataPayload = {}\n\t\tif (op.transactionId !== undefined) {\n\t\t\tdataPayload[TX_ID_KEY] = op.transactionId\n\t\t}\n\t\tif (op.mutationName !== undefined) {\n\t\t\tdataPayload[MUTATION_NAME_KEY] = op.mutationName\n\t\t}\n\t}\n\n\treturn {\n\t\tid: op.id,\n\t\tnode_id: op.nodeId,\n\t\ttype: op.type,\n\t\trecord_id: op.recordId,\n\t\tdata: dataPayload ? JSON.stringify(dataPayload) : null,\n\t\tprevious_data: op.previousData ? JSON.stringify(op.previousData) : null,\n\t\ttimestamp: HybridLogicalClock.serialize(op.timestamp),\n\t\tsequence_number: op.sequenceNumber,\n\t\tcausal_deps: JSON.stringify(op.causalDeps),\n\t\tschema_version: op.schemaVersion,\n\t}\n}\n\n/**\n * Deserialize a row from the operations log table back to an Operation.\n *\n * @param row - The raw operation row from SQL\n * @returns The deserialized Operation object\n */\nexport function deserializeOperation(row: OperationRow): Operation {\n\tlet data: Record<string, unknown> | null = null\n\tlet atomicOps: Record<string, unknown> | undefined\n\tlet transactionId: string | undefined\n\tlet mutationName: string | undefined\n\n\tif (row.data) {\n\t\tconst parsed = JSON.parse(row.data) as Record<string, unknown>\n\t\t// Extract embedded metadata keys\n\t\tif (ATOMIC_OPS_KEY in parsed) {\n\t\t\tatomicOps = parsed[ATOMIC_OPS_KEY] as Record<string, unknown>\n\t\t}\n\t\tif (TX_ID_KEY in parsed) {\n\t\t\ttransactionId = parsed[TX_ID_KEY] as string\n\t\t}\n\t\tif (MUTATION_NAME_KEY in parsed) {\n\t\t\tmutationName = parsed[MUTATION_NAME_KEY] as string\n\t\t}\n\t\t// Remove metadata keys from data\n\t\tconst { [ATOMIC_OPS_KEY]: _a, [TX_ID_KEY]: _t, [MUTATION_NAME_KEY]: _m, ...rest } = parsed\n\t\tdata = Object.keys(rest).length > 0 ? rest : null\n\t}\n\n\treturn {\n\t\tid: row.id,\n\t\tnodeId: row.node_id,\n\t\ttype: row.type as Operation['type'],\n\t\tcollection: '', // Collection name is derived from the table name by the caller\n\t\trecordId: row.record_id,\n\t\tdata,\n\t\tpreviousData: row.previous_data\n\t\t\t? (JSON.parse(row.previous_data) as Record<string, unknown>)\n\t\t\t: null,\n\t\ttimestamp: HybridLogicalClock.deserialize(row.timestamp),\n\t\tsequenceNumber: row.sequence_number,\n\t\tcausalDeps: JSON.parse(row.causal_deps) as string[],\n\t\tschemaVersion: row.schema_version,\n\t\t...(atomicOps !== undefined ? { atomicOps: atomicOps as Operation['atomicOps'] } : {}),\n\t\t...(transactionId !== undefined ? { transactionId } : {}),\n\t\t...(mutationName !== undefined ? { mutationName } : {}),\n\t}\n}\n\n/**\n * Deserialize an operation row with collection name already known.\n */\nexport function deserializeOperationWithCollection(\n\trow: OperationRow,\n\tcollection: string,\n): Operation {\n\tconst op = deserializeOperation(row)\n\treturn { ...op, collection }\n}\n\nfunction serializeValue(value: unknown, descriptor: FieldDescriptor): unknown {\n\tif (value === null || value === undefined) {\n\t\treturn null\n\t}\n\n\tswitch (descriptor.kind) {\n\t\tcase 'boolean':\n\t\t\treturn value ? 1 : 0\n\t\tcase 'array':\n\t\t\treturn JSON.stringify(value)\n\t\tcase 'richtext':\n\t\t\t// May be a record-shaped value (string/bytes) or an op-data value\n\t\t\t// (tagged { $koraBytes }) — encodeRichtext accepts every form.\n\t\t\treturn encodeRichtext(value as Parameters<typeof encodeRichtext>[0])\n\t\tdefault:\n\t\t\treturn value\n\t}\n}\n\nfunction deserializeValue(value: unknown, descriptor: FieldDescriptor): unknown {\n\tswitch (descriptor.kind) {\n\t\tcase 'boolean':\n\t\t\treturn value === 1 || value === true\n\t\tcase 'array':\n\t\t\tif (typeof value === 'string') {\n\t\t\t\treturn JSON.parse(value) as unknown[]\n\t\t\t}\n\t\t\treturn value\n\t\tcase 'richtext':\n\t\t\treturn decodeRichtext(value)\n\t\tdefault:\n\t\t\treturn value\n\t}\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/adapters/sqlite-wasm-channel.ts"],"sourcesContent":["/// <reference lib=\"dom\" />\nimport { WorkerTimeoutError } from '../errors'\n\n// === Message Protocol ===\n\n/**\n * Request message sent from the main thread to the SQLite WASM worker.\n * Each request carries a unique `id` for response correlation.\n */\nexport type WorkerRequest =\n\t| { id: number; type: 'open'; ddlStatements: string[]; dbName?: string }\n\t| { id: number; type: 'close' }\n\t| { id: number; type: 'execute'; sql: string; params?: unknown[] }\n\t| { id: number; type: 'query'; sql: string; params?: unknown[] }\n\t| { id: number; type: 'begin' }\n\t| { id: number; type: 'commit' }\n\t| { id: number; type: 'rollback' }\n\t| { id: number; type: 'migrate'; from: number; to: number; statements: string[] }\n\t| { id: number; type: 'export' }\n\t| { id: number; type: 'import'; data: Uint8Array }\n\n/**\n * Response message sent from the worker back to the main thread.\n * Matches the request `id` for correlation.\n */\nexport type WorkerResponse =\n\t| { id: number; type: 'success'; data?: unknown }\n\t| { id: number; type: 'error'; message: string; code: string; context?: Record<string, unknown> }\n\n// === WorkerBridge Interface ===\n\n/**\n * Abstraction over the communication channel with the SQLite WASM worker.\n * In browsers, this is backed by a real Web Worker via MessagePort.\n * In Node.js tests, this is backed by better-sqlite3 via MockWorkerBridge.\n */\nexport interface WorkerBridge {\n\t/** Send a request to the worker and wait for a response. */\n\tsend(request: WorkerRequest): Promise<WorkerResponse>\n\n\t/** Terminate the worker. Safe to call multiple times. */\n\tterminate(): void\n}\n\n// === Mutex ===\n\n// The Mutex now lives in its own module so non-browser adapters (better-sqlite3)\n// can serialize transactions without importing browser-worker code.\nexport { Mutex } from './mutex'\n\n// === WebWorkerBridge ===\n\n/**\n * WorkerBridge implementation for browser environments.\n * Communicates with an actual Web Worker running SQLite WASM.\n */\nexport class WebWorkerBridge implements WorkerBridge {\n\tprivate worker: Worker\n\tprivate pending = new Map<\n\t\tnumber,\n\t\t{ resolve: (r: WorkerResponse) => void; reject: (e: Error) => void }\n\t>()\n\tprivate nextId = 1\n\tprivate terminated = false\n\tprivate timeoutMs: number\n\n\t/**\n\t * @param workerUrl - URL to the sqlite-wasm-worker script\n\t * @param timeoutMs - Timeout for worker responses in milliseconds (default: 30000)\n\t */\n\tconstructor(workerUrl: string | URL, timeoutMs = 30000) {\n\t\tthis.timeoutMs = timeoutMs\n\t\tthis.worker = new Worker(workerUrl, { type: 'module' })\n\t\tthis.worker.onmessage = (event: MessageEvent<WorkerResponse>) => {\n\t\t\tconst response = event.data\n\t\t\tconst entry = this.pending.get(response.id)\n\t\t\tif (entry) {\n\t\t\t\tthis.pending.delete(response.id)\n\t\t\t\tentry.resolve(response)\n\t\t\t}\n\t\t}\n\t\tthis.worker.onerror = (event) => {\n\t\t\t// Reject all pending requests on worker error\n\t\t\tconst error = new Error(`Worker error: ${event.message}`)\n\t\t\tfor (const [id, entry] of this.pending) {\n\t\t\t\tthis.pending.delete(id)\n\t\t\t\tentry.reject(error)\n\t\t\t}\n\t\t}\n\t}\n\n\tasync send(request: WorkerRequest): Promise<WorkerResponse> {\n\t\tif (this.terminated) {\n\t\t\treturn {\n\t\t\t\tid: request.id,\n\t\t\t\ttype: 'error',\n\t\t\t\tmessage: 'Worker has been terminated',\n\t\t\t\tcode: 'WORKER_TERMINATED',\n\t\t\t}\n\t\t}\n\n\t\tconst id = this.nextId++\n\t\tconst req = { ...request, id }\n\n\t\treturn new Promise<WorkerResponse>((resolve, reject) => {\n\t\t\tconst timer = setTimeout(() => {\n\t\t\t\tthis.pending.delete(id)\n\t\t\t\treject(new WorkerTimeoutError(req.type, this.timeoutMs))\n\t\t\t}, this.timeoutMs)\n\n\t\t\tthis.pending.set(id, {\n\t\t\t\tresolve: (response) => {\n\t\t\t\t\tclearTimeout(timer)\n\t\t\t\t\tresolve(response)\n\t\t\t\t},\n\t\t\t\treject: (error) => {\n\t\t\t\t\tclearTimeout(timer)\n\t\t\t\t\treject(error)\n\t\t\t\t},\n\t\t\t})\n\n\t\t\tthis.worker.postMessage(req)\n\t\t})\n\t}\n\n\tterminate(): void {\n\t\tif (this.terminated) return\n\t\tthis.terminated = true\n\t\tthis.worker.terminate()\n\t\t// Reject any pending requests\n\t\tfor (const [id, entry] of this.pending) {\n\t\t\tthis.pending.delete(id)\n\t\t\tentry.reject(new Error('Worker terminated'))\n\t\t}\n\t}\n}\n"],"mappings":";;;;;AAwDO,IAAM,kBAAN,MAA8C;AAAA,EAC5C;AAAA,EACA,UAAU,oBAAI,IAGpB;AAAA,EACM,SAAS;AAAA,EACT,aAAa;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA,EAMR,YAAY,WAAyB,YAAY,KAAO;AACvD,SAAK,YAAY;AACjB,SAAK,SAAS,IAAI,OAAO,WAAW,EAAE,MAAM,SAAS,CAAC;AACtD,SAAK,OAAO,YAAY,CAAC,UAAwC;AAChE,YAAM,WAAW,MAAM;AACvB,YAAM,QAAQ,KAAK,QAAQ,IAAI,SAAS,EAAE;AAC1C,UAAI,OAAO;AACV,aAAK,QAAQ,OAAO,SAAS,EAAE;AAC/B,cAAM,QAAQ,QAAQ;AAAA,MACvB;AAAA,IACD;AACA,SAAK,OAAO,UAAU,CAAC,UAAU;AAEhC,YAAM,QAAQ,IAAI,MAAM,iBAAiB,MAAM,OAAO,EAAE;AACxD,iBAAW,CAAC,IAAI,KAAK,KAAK,KAAK,SAAS;AACvC,aAAK,QAAQ,OAAO,EAAE;AACtB,cAAM,OAAO,KAAK;AAAA,MACnB;AAAA,IACD;AAAA,EACD;AAAA,EAEA,MAAM,KAAK,SAAiD;AAC3D,QAAI,KAAK,YAAY;AACpB,aAAO;AAAA,QACN,IAAI,QAAQ;AAAA,QACZ,MAAM;AAAA,QACN,SAAS;AAAA,QACT,MAAM;AAAA,MACP;AAAA,IACD;AAEA,UAAM,KAAK,KAAK;AAChB,UAAM,MAAM,EAAE,GAAG,SAAS,GAAG;AAE7B,WAAO,IAAI,QAAwB,CAAC,SAAS,WAAW;AACvD,YAAM,QAAQ,WAAW,MAAM;AAC9B,aAAK,QAAQ,OAAO,EAAE;AACtB,eAAO,IAAI,mBAAmB,IAAI,MAAM,KAAK,SAAS,CAAC;AAAA,MACxD,GAAG,KAAK,SAAS;AAEjB,WAAK,QAAQ,IAAI,IAAI;AAAA,QACpB,SAAS,CAAC,aAAa;AACtB,uBAAa,KAAK;AAClB,kBAAQ,QAAQ;AAAA,QACjB;AAAA,QACA,QAAQ,CAAC,UAAU;AAClB,uBAAa,KAAK;AAClB,iBAAO,KAAK;AAAA,QACb;AAAA,MACD,CAAC;AAED,WAAK,OAAO,YAAY,GAAG;AAAA,IAC5B,CAAC;AAAA,EACF;AAAA,EAEA,YAAkB;AACjB,QAAI,KAAK,WAAY;AACrB,SAAK,aAAa;AAClB,SAAK,OAAO,UAAU;AAEtB,eAAW,CAAC,IAAI,KAAK,KAAK,KAAK,SAAS;AACvC,WAAK,QAAQ,OAAO,EAAE;AACtB,YAAM,OAAO,IAAI,MAAM,mBAAmB,CAAC;AAAA,IAC5C;AAAA,EACD;AACD;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/lww/row-version.ts","../src/query/sql-builder.ts","../src/mutations/resolve-causal-deps.ts","../src/mutations/execute-delete.ts","../src/store/sequence-allocator.ts","../src/mutations/execute-insert.ts","../src/lww/field-versions.ts","../src/state-machine/state-validator.ts","../src/mutations/execute-update.ts"],"sourcesContent":["import { type HLCTimestamp, HybridLogicalClock } from '@korajs/core'\nimport type { RawCollectionRow } from '../types'\n\n/** Empty _version means legacy rows (compare by wallTime only via fallback). */\nexport const EMPTY_ROW_VERSION = ''\n\n/**\n * Serialize an HLC timestamp for storage in the `_version` column.\n * Lexicographic order matches {@link HybridLogicalClock.compare}.\n */\nexport function serializeRowVersion(timestamp: HLCTimestamp): string {\n\treturn HybridLogicalClock.serialize(timestamp)\n}\n\n/**\n * Read the version stamp stored on a materialized row.\n * Legacy rows without `_version` fall back to wallTime with logical 0.\n */\nexport function rowVersionFromRecord(\n\trow: Pick<RawCollectionRow, '_updated_at' | '_version'>,\n): HLCTimestamp {\n\tconst version = row._version\n\tif (typeof version === 'string' && version.length > 0) {\n\t\treturn HybridLogicalClock.deserialize(version)\n\t}\n\treturn { wallTime: row._updated_at, logical: 0, nodeId: '' }\n}\n\n/**\n * Returns true when `incoming` is strictly newer than the version on `row`.\n */\nexport function isIncomingNewerThanRow(\n\tincoming: HLCTimestamp,\n\trow: Pick<RawCollectionRow, '_updated_at' | '_version'>,\n): boolean {\n\treturn HybridLogicalClock.compare(incoming, rowVersionFromRecord(row)) > 0\n}\n\n/**\n * SQL fragment and params for LWW guards: apply only when row is missing or older.\n * Uses serialized `_version` for total order (matches HLC compare).\n */\nexport function lwwVersionWhereClause(remoteVersion: string): { sql: string; params: string[] } {\n\treturn {\n\t\tsql: '(_version = ? OR _version < ?)',\n\t\tparams: [EMPTY_ROW_VERSION, remoteVersion],\n\t}\n}\n","import type { CollectionDefinition, FieldDescriptor } from '@korajs/core'\nimport { QueryError } from '../errors'\nimport { lwwVersionWhereClause } from '../lww/row-version'\nimport type { QueryDescriptor, WhereOperators } from '../types'\n\n/**\n * Result of building a SQL query: the parameterized SQL string and its bound values.\n */\nexport interface SqlQuery {\n\tsql: string\n\tparams: unknown[]\n}\n\n/**\n * Build a SELECT query from a QueryDescriptor.\n * Automatically adds `WHERE _deleted = 0` to exclude soft-deleted records.\n *\n * @param descriptor - The query descriptor\n * @param fields - The field descriptors from the collection schema\n * @returns A parameterized SQL query\n */\nexport function buildSelectQuery(\n\tdescriptor: QueryDescriptor,\n\tfields: Record<string, FieldDescriptor>,\n): SqlQuery {\n\tconst params: unknown[] = []\n\tconst parts = [`SELECT * FROM ${descriptor.collection}`]\n\n\tconst whereClause = buildWhereClauseParts(descriptor.where, fields, params)\n\t// Always filter out soft-deleted records\n\tconst deletedFilter = '_deleted = 0'\n\tif (whereClause) {\n\t\tparts.push(`WHERE ${deletedFilter} AND ${whereClause}`)\n\t} else {\n\t\tparts.push(`WHERE ${deletedFilter}`)\n\t}\n\n\tif (descriptor.orderBy.length > 0) {\n\t\tconst orderParts = descriptor.orderBy.map((o) => {\n\t\t\tvalidateFieldName(o.field, fields)\n\t\t\treturn `${o.field} ${o.direction.toUpperCase()}`\n\t\t})\n\t\tparts.push(`ORDER BY ${orderParts.join(', ')}`)\n\t}\n\n\tif (descriptor.limit !== undefined) {\n\t\tparts.push(`LIMIT ${descriptor.limit}`)\n\t}\n\n\tif (descriptor.offset !== undefined) {\n\t\tparts.push(`OFFSET ${descriptor.offset}`)\n\t}\n\n\treturn { sql: parts.join(' '), params }\n}\n\n/**\n * Build a COUNT query from a QueryDescriptor.\n * Automatically adds `WHERE _deleted = 0`.\n *\n * @param descriptor - The query descriptor\n * @param fields - The field descriptors from the collection schema\n * @returns A parameterized SQL query that returns { count: number }\n */\nexport function buildCountQuery(\n\tdescriptor: QueryDescriptor,\n\tfields: Record<string, FieldDescriptor>,\n): SqlQuery {\n\tconst params: unknown[] = []\n\tconst parts = [`SELECT COUNT(*) as count FROM ${descriptor.collection}`]\n\n\tconst whereClause = buildWhereClauseParts(descriptor.where, fields, params)\n\tconst deletedFilter = '_deleted = 0'\n\tif (whereClause) {\n\t\tparts.push(`WHERE ${deletedFilter} AND ${whereClause}`)\n\t} else {\n\t\tparts.push(`WHERE ${deletedFilter}`)\n\t}\n\n\treturn { sql: parts.join(' '), params }\n}\n\n/**\n * Build an INSERT query for a collection record.\n *\n * @param collection - The collection name\n * @param record - The record data (already serialized with id, _created_at, _updated_at)\n * @returns A parameterized SQL query\n */\nexport function buildInsertQuery(collection: string, record: Record<string, unknown>): SqlQuery {\n\tconst columns = Object.keys(record)\n\tconst placeholders = columns.map(() => '?')\n\tconst params = Object.values(record)\n\n\tconst sql = `INSERT INTO ${collection} (${columns.join(', ')}) VALUES (${placeholders.join(', ')})`\n\treturn { sql, params }\n}\n\n/**\n * Build an UPDATE query for a collection record.\n *\n * @param collection - The collection name\n * @param id - The record ID\n * @param changes - The fields to update (already serialized)\n * @returns A parameterized SQL query\n */\nexport function buildUpdateQuery(\n\tcollection: string,\n\tid: string,\n\tchanges: Record<string, unknown>,\n): SqlQuery {\n\tconst setClauses = Object.keys(changes).map((col) => `${col} = ?`)\n\tconst params = [...Object.values(changes), id]\n\n\tconst sql = `UPDATE ${collection} SET ${setClauses.join(', ')} WHERE id = ?`\n\treturn { sql, params }\n}\n\n/**\n * Build a field-level fast-forward UPDATE: writes the changed field values\n * unconditionally (the caller has already proven per-field that the local value\n * still equals the base the remote wrote from, so the remote is the newest known\n * writer of those fields), while advancing the row's `_version` / `_updated_at`\n * watermark MONOTONICALLY — never regressing it below its current value.\n *\n * This is what makes per-field LWW correct on a per-row `_version` column: a\n * concurrent edit to a different field may have pushed `_version` past this\n * operation's timestamp, but that must not cause this operation's own field\n * change to be dropped, nor may materializing it move the watermark backward\n * (which would let a genuinely stale later operation win).\n *\n * @param collection - The collection name\n * @param id - The record ID\n * @param fieldChanges - Serialized field values to write unconditionally (must NOT include _version / _updated_at)\n * @param remoteVersion - The operation's serialized row version (lexicographically comparable)\n * @param wallTime - The operation's wall-clock time in ms\n * @param options - Optional extras. `maxCreatedAt` advances `_created_at` to the\n * greater of its current value and the given wall time — used for insert\n * collisions so every node converges on the max insert wall time regardless\n * of arrival order.\n */\nexport function buildFieldFastForwardUpdateQuery(\n\tcollection: string,\n\tid: string,\n\tfieldChanges: Record<string, unknown>,\n\tremoteVersion: string,\n\twallTime: number,\n\toptions?: { maxCreatedAt?: number },\n): SqlQuery {\n\tconst fieldClauses = Object.keys(fieldChanges).map((col) => `${col} = ?`)\n\t// _version is a lexicographically-sortable string; _updated_at is a number.\n\t// Keep the greater of the current and incoming value for each (monotonic).\n\tconst setClauses = [\n\t\t...fieldClauses,\n\t\t'_version = CASE WHEN _version >= ? THEN _version ELSE ? END',\n\t\t'_updated_at = CASE WHEN _updated_at >= ? THEN _updated_at ELSE ? END',\n\t]\n\tconst params: unknown[] = [\n\t\t...Object.values(fieldChanges),\n\t\tremoteVersion,\n\t\tremoteVersion,\n\t\twallTime,\n\t\twallTime,\n\t]\n\tif (options?.maxCreatedAt !== undefined) {\n\t\tsetClauses.push('_created_at = CASE WHEN _created_at >= ? THEN _created_at ELSE ? END')\n\t\tparams.push(options.maxCreatedAt, options.maxCreatedAt)\n\t}\n\tparams.push(id)\n\tconst sql = `UPDATE ${collection} SET ${setClauses.join(', ')} WHERE id = ?`\n\treturn { sql, params }\n}\n\n/**\n * Build a soft-delete query (SET _deleted = 1).\n *\n * @param collection - The collection name\n * @param id - The record ID\n * @param updatedAt - The timestamp to set on _updated_at\n * @returns A parameterized SQL query\n */\nexport function buildSoftDeleteQuery(\n\tcollection: string,\n\tid: string,\n\tupdatedAt: number,\n\tversion?: string,\n): SqlQuery {\n\tif (version !== undefined) {\n\t\treturn {\n\t\t\tsql: `UPDATE ${collection} SET _deleted = 1, _updated_at = ?, _version = ? WHERE id = ?`,\n\t\t\tparams: [updatedAt, version, id],\n\t\t}\n\t}\n\treturn {\n\t\tsql: `UPDATE ${collection} SET _deleted = 1, _updated_at = ? WHERE id = ?`,\n\t\tparams: [updatedAt, id],\n\t}\n}\n\n/**\n * Build an UPDATE that applies only when the row is missing or older than `remoteVersion`\n * (serialized HLC). Prevents stale remote sync from overwriting newer local materialized state.\n */\nexport function buildLwwUpdateQuery(\n\tcollection: string,\n\tid: string,\n\tchanges: Record<string, unknown>,\n\tremoteVersion: string,\n): SqlQuery {\n\tconst setClauses = Object.keys(changes).map((col) => `${col} = ?`)\n\tconst lww = lwwVersionWhereClause(remoteVersion)\n\tconst sql = `UPDATE ${collection} SET ${setClauses.join(', ')} WHERE id = ? AND ${lww.sql}`\n\tconst params = [...Object.values(changes), id, ...lww.params]\n\treturn { sql, params }\n}\n\n/**\n * Build a soft-delete that applies only when the row is older than `remoteVersion`.\n */\nexport function buildLwwSoftDeleteQuery(\n\tcollection: string,\n\tid: string,\n\tupdatedAt: number,\n\tversion: string,\n): SqlQuery {\n\tconst lww = lwwVersionWhereClause(version)\n\treturn {\n\t\tsql: `UPDATE ${collection} SET _deleted = 1, _updated_at = ?, _version = ? WHERE id = ? AND ${lww.sql}`,\n\t\tparams: [updatedAt, version, id, ...lww.params],\n\t}\n}\n\n/**\n * Build a WHERE clause from conditions, validating field names against the schema.\n *\n * @param where - The where conditions\n * @param fields - The field descriptors from the collection schema\n * @returns The SQL WHERE clause string and params, or null if no conditions\n */\nexport function buildWhereClause(\n\twhere: Record<string, unknown>,\n\tfields: Record<string, FieldDescriptor>,\n): SqlQuery | null {\n\tconst params: unknown[] = []\n\tconst result = buildWhereClauseParts(where, fields, params)\n\tif (!result) return null\n\treturn { sql: result, params }\n}\n\n// --- Internal helpers ---\n\nconst VALID_OPERATORS = new Set(['$eq', '$ne', '$gt', '$gte', '$lt', '$lte', '$in'])\n\nfunction buildWhereClauseParts(\n\twhere: Record<string, unknown>,\n\tfields: Record<string, FieldDescriptor>,\n\tparams: unknown[],\n): string | null {\n\tconst conditions: string[] = []\n\n\tfor (const [fieldName, value] of Object.entries(where)) {\n\t\tvalidateFieldName(fieldName, fields)\n\t\tconst descriptor = fields[fieldName]\n\n\t\tif (value !== null && typeof value === 'object' && !Array.isArray(value)) {\n\t\t\t// Operator object: { $gt: 5, $lt: 10 }\n\t\t\tconst ops = value as WhereOperators\n\t\t\tfor (const [op, opValue] of Object.entries(ops)) {\n\t\t\t\tif (!VALID_OPERATORS.has(op)) {\n\t\t\t\t\tthrow new QueryError(`Unknown operator \"${op}\" on field \"${fieldName}\"`, {\n\t\t\t\t\t\tfield: fieldName,\n\t\t\t\t\t\toperator: op,\n\t\t\t\t\t\tvalidOperators: [...VALID_OPERATORS],\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\tconditions.push(buildOperatorCondition(fieldName, op, opValue, descriptor, params))\n\t\t\t}\n\t\t} else {\n\t\t\t// Shorthand: { completed: false } means { completed: { $eq: false } }\n\t\t\tconditions.push(buildOperatorCondition(fieldName, '$eq', value, descriptor, params))\n\t\t}\n\t}\n\n\tif (conditions.length === 0) return null\n\treturn conditions.join(' AND ')\n}\n\nfunction buildOperatorCondition(\n\tfieldName: string,\n\toperator: string,\n\tvalue: unknown,\n\tdescriptor: FieldDescriptor | undefined,\n\tparams: unknown[],\n): string {\n\t// Serialize boolean values to 0/1 for SQL comparison\n\tconst sqlValue =\n\t\tdescriptor?.kind === 'boolean' && typeof value === 'boolean' ? (value ? 1 : 0) : value\n\n\tswitch (operator) {\n\t\tcase '$eq':\n\t\t\tif (sqlValue === null) {\n\t\t\t\treturn `${fieldName} IS NULL`\n\t\t\t}\n\t\t\tparams.push(sqlValue)\n\t\t\treturn `${fieldName} = ?`\n\t\tcase '$ne':\n\t\t\tif (sqlValue === null) {\n\t\t\t\treturn `${fieldName} IS NOT NULL`\n\t\t\t}\n\t\t\tparams.push(sqlValue)\n\t\t\treturn `${fieldName} != ?`\n\t\tcase '$gt':\n\t\t\tparams.push(sqlValue)\n\t\t\treturn `${fieldName} > ?`\n\t\tcase '$gte':\n\t\t\tparams.push(sqlValue)\n\t\t\treturn `${fieldName} >= ?`\n\t\tcase '$lt':\n\t\t\tparams.push(sqlValue)\n\t\t\treturn `${fieldName} < ?`\n\t\tcase '$lte':\n\t\t\tparams.push(sqlValue)\n\t\t\treturn `${fieldName} <= ?`\n\t\tcase '$in': {\n\t\t\tif (!Array.isArray(sqlValue)) {\n\t\t\t\tthrow new QueryError(`$in operator requires an array value for field \"${fieldName}\"`, {\n\t\t\t\t\tfield: fieldName,\n\t\t\t\t\treceived: typeof sqlValue,\n\t\t\t\t})\n\t\t\t}\n\t\t\tconst placeholders = sqlValue.map(() => '?')\n\t\t\tfor (const item of sqlValue) {\n\t\t\t\tparams.push(\n\t\t\t\t\tdescriptor?.kind === 'boolean' && typeof item === 'boolean' ? (item ? 1 : 0) : item,\n\t\t\t\t)\n\t\t\t}\n\t\t\treturn `${fieldName} IN (${placeholders.join(', ')})`\n\t\t}\n\t\tdefault:\n\t\t\tthrow new QueryError(`Unknown operator \"${operator}\"`, { operator })\n\t}\n}\n\nfunction validateFieldName(fieldName: string, fields: Record<string, FieldDescriptor>): void {\n\t// Allow schema fields plus metadata fields that map to query-able columns\n\tconst allowedFields = new Set([\n\t\t...Object.keys(fields),\n\t\t'id',\n\t\t'createdAt',\n\t\t'updatedAt',\n\t\t'_created_at',\n\t\t'_updated_at',\n\t])\n\tif (!allowedFields.has(fieldName)) {\n\t\tthrow new QueryError(\n\t\t\t`Unknown field \"${fieldName}\" in query. Available fields: ${[...allowedFields].join(', ')}`,\n\t\t\t{ field: fieldName },\n\t\t)\n\t}\n}\n","import type { LocalMutationContext } from './types'\n\n/**\n * Merge explicit parent deps (e.g. referential cascade) with tracker-derived deps.\n */\nexport function resolveCausalDeps(ctx: LocalMutationContext): string[] {\n\tconst merged: string[] = []\n\tfor (const id of ctx.extraCausalDeps ?? []) {\n\t\tif (!merged.includes(id)) {\n\t\t\tmerged.push(id)\n\t\t}\n\t}\n\tfor (const id of ctx.causalTracker?.nextCausalDeps(ctx.collection, ctx.inTransaction) ?? []) {\n\t\tif (!merged.includes(id)) {\n\t\t\tmerged.push(id)\n\t\t}\n\t}\n\treturn merged\n}\n","import { createOperation } from '@korajs/core'\nimport type { Operation } from '@korajs/core'\nimport { RecordNotFoundError } from '../errors'\nimport { serializeRowVersion } from '../lww/row-version'\nimport { buildInsertQuery, buildSoftDeleteQuery } from '../query/sql-builder'\nimport { serializeOperation } from '../serialization/serializer'\nimport { allocateNextSequenceInTransaction } from '../store/sequence-allocator'\nimport type { RawCollectionRow } from '../types'\nimport { resolveCausalDeps } from './resolve-causal-deps'\nimport type { LocalMutationContext } from './types'\n\nexport interface ExecuteDeleteOptions {\n\t/** When true, skip RelationEnforcer (caller already enforced referential integrity). */\n\tskipReferentialEnforcement?: boolean\n\t/** Pre-built delete operation (avoids duplicate sequence allocation). */\n\toperation?: Operation\n}\n\n/**\n * Soft-delete a record and persist the operation log entry atomically.\n * Returns the primary delete operation and any cascaded side-effect operations.\n */\nexport async function executeDelete(\n\tctx: LocalMutationContext,\n\tid: string,\n\toptions?: ExecuteDeleteOptions,\n): Promise<Operation[]> {\n\tconst currentRows = await ctx.adapter.query<RawCollectionRow>(\n\t\t`SELECT * FROM ${ctx.collection} WHERE id = ? AND _deleted = 0`,\n\t\t[id],\n\t)\n\tif (!currentRows[0]) {\n\t\tthrow new RecordNotFoundError(ctx.collection, id)\n\t}\n\n\tconst causalDeps = resolveCausalDeps(ctx)\n\tlet operation = options?.operation\n\tconst cascadedOps: Operation[] = []\n\n\tawait ctx.adapter.transaction(async (tx) => {\n\t\tif (!operation) {\n\t\t\tconst sequenceNumber = await allocateNextSequenceInTransaction(tx, ctx.nodeId)\n\t\t\toperation = await createOperation(\n\t\t\t\t{\n\t\t\t\t\tnodeId: ctx.nodeId,\n\t\t\t\t\ttype: 'delete',\n\t\t\t\t\tcollection: ctx.collection,\n\t\t\t\t\trecordId: id,\n\t\t\t\t\tdata: null,\n\t\t\t\t\tpreviousData: null,\n\t\t\t\t\tsequenceNumber,\n\t\t\t\t\tcausalDeps,\n\t\t\t\t\tschemaVersion: ctx.schema.version,\n\t\t\t\t},\n\t\t\t\tctx.clock,\n\t\t\t)\n\t\t\tctx.causalTracker?.afterOperation(ctx.collection, operation.id, ctx.inTransaction)\n\t\t}\n\n\t\tif (ctx.relationEnforcer && !options?.skipReferentialEnforcement) {\n\t\t\tconst enforcementResult = await ctx.relationEnforcer.enforceDelete(ctx.collection, id, tx, [\n\t\t\t\toperation.id,\n\t\t\t])\n\t\t\tcascadedOps.push(...enforcementResult.operations)\n\t\t}\n\n\t\tconst version = serializeRowVersion(operation.timestamp)\n\t\tconst deleteQuery = buildSoftDeleteQuery(\n\t\t\tctx.collection,\n\t\t\tid,\n\t\t\toperation.timestamp.wallTime,\n\t\t\tversion,\n\t\t)\n\t\tconst opInsert = buildInsertQuery(\n\t\t\t`_kora_ops_${ctx.collection}`,\n\t\t\tserializeOperation(operation) as unknown as Record<string, unknown>,\n\t\t)\n\n\t\tawait tx.execute(deleteQuery.sql, deleteQuery.params)\n\t\tawait tx.execute(opInsert.sql, opInsert.params)\n\t})\n\n\tif (!operation) {\n\t\tthrow new Error('Delete operation was not created')\n\t}\n\n\tctx.onMutation(ctx.collection, operation)\n\tfor (const cascadedOp of cascadedOps) {\n\t\tctx.onMutation(cascadedOp.collection, cascadedOp)\n\t}\n\n\treturn [operation, ...cascadedOps]\n}\n","import type { StorageAdapter, Transaction } from '../types'\n\ninterface SequenceRow {\n\tsequence_number: number\n}\n\n/**\n * Atomically increment and return the next sequence number for a node.\n * Uses SQLite UPSERT + RETURNING so concurrent tabs do not reuse sequence slots.\n */\nexport async function allocateNextSequenceNumber(\n\tadapter: StorageAdapter,\n\tnodeId: string,\n): Promise<number> {\n\tconst rows = await adapter.query<SequenceRow>(\n\t\t`INSERT INTO _kora_version_vector (node_id, sequence_number) VALUES (?, 1)\n ON CONFLICT(node_id) DO UPDATE SET sequence_number = sequence_number + 1\n RETURNING sequence_number`,\n\t\t[nodeId],\n\t)\n\tconst seq = rows[0]?.sequence_number\n\tif (seq === undefined) {\n\t\tthrow new Error(`Failed to allocate sequence number for node \"${nodeId}\"`)\n\t}\n\treturn seq\n}\n\n/**\n * Allocate the next sequence inside an existing storage transaction.\n */\nexport async function allocateNextSequenceInTransaction(\n\ttx: Transaction,\n\tnodeId: string,\n): Promise<number> {\n\tconst rows = await tx.query<SequenceRow>(\n\t\t`INSERT INTO _kora_version_vector (node_id, sequence_number) VALUES (?, 1)\n ON CONFLICT(node_id) DO UPDATE SET sequence_number = sequence_number + 1\n RETURNING sequence_number`,\n\t\t[nodeId],\n\t)\n\tconst seq = rows[0]?.sequence_number\n\tif (seq === undefined) {\n\t\tthrow new Error(`Failed to allocate sequence number for node \"${nodeId}\" in transaction`)\n\t}\n\treturn seq\n}\n\n/**\n * Read the current sequence for a node without incrementing.\n */\nexport async function readSequenceNumber(adapter: StorageAdapter, nodeId: string): Promise<number> {\n\tconst rows = await adapter.query<SequenceRow>(\n\t\t'SELECT sequence_number FROM _kora_version_vector WHERE node_id = ?',\n\t\t[nodeId],\n\t)\n\treturn rows[0]?.sequence_number ?? 0\n}\n","import { createOperation, generateUUIDv7, validateRecord } from '@korajs/core'\nimport { fieldVersionsForFields, serializeFieldVersions } from '../lww/field-versions'\nimport { serializeRowVersion } from '../lww/row-version'\nimport { buildInsertQuery } from '../query/sql-builder'\nimport { encodeRichtextFieldsForOpData } from '../serialization/op-data-encoding'\nimport { serializeOperation, serializeRecord } from '../serialization/serializer'\nimport { allocateNextSequenceInTransaction } from '../store/sequence-allocator'\nimport type { CollectionRecord } from '../types'\nimport { resolveCausalDeps } from './resolve-causal-deps'\nimport type { LocalMutationContext } from './types'\n\n/**\n * Insert a record and persist the operation log entry atomically.\n */\nexport async function executeInsert(\n\tctx: LocalMutationContext,\n\tdata: Record<string, unknown>,\n): Promise<CollectionRecord> {\n\tconst validated = validateRecord(ctx.collection, ctx.definition, data, 'insert')\n\tconst recordId = generateUUIDv7()\n\n\tfor (const [fieldName, descriptor] of Object.entries(ctx.definition.fields)) {\n\t\tif (descriptor.auto && descriptor.kind === 'timestamp') {\n\t\t\tvalidated[fieldName] = Date.now()\n\t\t}\n\t}\n\n\tconst causalDeps = resolveCausalDeps(ctx)\n\n\tlet operation!: Awaited<ReturnType<typeof createOperation>>\n\tlet record!: Record<string, unknown>\n\n\tawait ctx.adapter.transaction(async (tx) => {\n\t\tconst sequenceNumber = await allocateNextSequenceInTransaction(tx, ctx.nodeId)\n\t\toperation = await createOperation(\n\t\t\t{\n\t\t\t\tnodeId: ctx.nodeId,\n\t\t\t\ttype: 'insert',\n\t\t\t\tcollection: ctx.collection,\n\t\t\t\trecordId,\n\t\t\t\t// Binary richtext values are tagged as canonical JSON BEFORE the\n\t\t\t\t// operation is content-hashed, so the hash input, persisted JSON,\n\t\t\t\t// and wire payload are the identical value.\n\t\t\t\tdata: encodeRichtextFieldsForOpData(validated, ctx.definition.fields),\n\t\t\t\tpreviousData: null,\n\t\t\t\tsequenceNumber,\n\t\t\t\tcausalDeps,\n\t\t\t\tschemaVersion: ctx.schema.version,\n\t\t\t},\n\t\t\tctx.clock,\n\t\t)\n\t\tctx.causalTracker?.afterOperation(ctx.collection, operation.id, ctx.inTransaction)\n\n\t\tconst serializedData = serializeRecord(validated, ctx.definition.fields)\n\t\tconst version = serializeRowVersion(operation.timestamp)\n\t\trecord = {\n\t\t\tid: recordId,\n\t\t\t...serializedData,\n\t\t\t_created_at: operation.timestamp.wallTime,\n\t\t\t_updated_at: operation.timestamp.wallTime,\n\t\t\t_version: version,\n\t\t\t// Stamp every inserted field with this operation's version so later\n\t\t\t// per-field LWW compares against a real writer, not the fallback.\n\t\t\t_field_versions: serializeFieldVersions(\n\t\t\t\tfieldVersionsForFields(Object.keys(serializedData), version),\n\t\t\t),\n\t\t}\n\n\t\tconst builtInsert = buildInsertQuery(ctx.collection, record)\n\t\tconst opInsert = buildInsertQuery(\n\t\t\t`_kora_ops_${ctx.collection}`,\n\t\t\tserializeOperation(operation) as unknown as Record<string, unknown>,\n\t\t)\n\n\t\tawait tx.execute(builtInsert.sql, builtInsert.params)\n\t\tawait tx.execute(opInsert.sql, opInsert.params)\n\t})\n\n\tctx.onMutation(ctx.collection, operation)\n\n\treturn {\n\t\tid: recordId,\n\t\t...validated,\n\t\tcreatedAt: operation.timestamp.wallTime,\n\t\tupdatedAt: operation.timestamp.wallTime,\n\t}\n}\n","import { EMPTY_ROW_VERSION } from './row-version'\n\n/**\n * Per-field last-writer versions for a materialized row.\n *\n * Each entry maps a field name to a serialized HLC timestamp (the same\n * lexicographically-sortable form used by the row `_version` column). Because\n * the serialized form sorts identically to {@link HybridLogicalClock.compare},\n * per-field last-write-wins is a plain string comparison: the greater string is\n * the newer writer. This makes field-level convergence deterministic and\n * independent of the order operations happen to arrive in.\n */\nexport type FieldVersions = Record<string, string>\n\n/**\n * Parse the `_field_versions` JSON column into a map. Tolerates null, empty,\n * and malformed values by returning an empty map — a row that predates\n * per-field tracking simply has no field versions and falls back to `_version`.\n */\nexport function parseFieldVersions(raw: unknown): FieldVersions {\n\tif (typeof raw !== 'string' || raw.length === 0) {\n\t\treturn {}\n\t}\n\ttry {\n\t\tconst parsed = JSON.parse(raw) as unknown\n\t\tif (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {\n\t\t\treturn {}\n\t\t}\n\t\tconst result: FieldVersions = {}\n\t\tfor (const [key, value] of Object.entries(parsed as Record<string, unknown>)) {\n\t\t\tif (typeof value === 'string') {\n\t\t\t\tresult[key] = value\n\t\t\t}\n\t\t}\n\t\treturn result\n\t} catch {\n\t\treturn {}\n\t}\n}\n\n/**\n * Serialize a field-version map for storage in the `_field_versions` column.\n */\nexport function serializeFieldVersions(versions: FieldVersions): string {\n\treturn JSON.stringify(versions)\n}\n\n/**\n * Build the initial field-version map for an insert: every supplied field is\n * stamped with the operation's version.\n */\nexport function fieldVersionsForFields(\n\tfieldNames: Iterable<string>,\n\tversion: string,\n): FieldVersions {\n\tconst versions: FieldVersions = {}\n\tfor (const field of fieldNames) {\n\t\tversions[field] = version\n\t}\n\treturn versions\n}\n\n/**\n * The effective stored version for a field: its own tracked version if present,\n * otherwise the row-level `_version` (so rows written before per-field tracking\n * still compare correctly), otherwise the empty sentinel (older than anything).\n */\nexport function effectiveFieldVersion(\n\tcurrent: FieldVersions,\n\tfield: string,\n\trowVersion: string | undefined,\n): string {\n\tconst own = current[field]\n\tif (own !== undefined) {\n\t\treturn own\n\t}\n\treturn rowVersion ?? EMPTY_ROW_VERSION\n}\n\n/**\n * Result of resolving an incoming operation against the current per-field\n * versions of a row.\n */\nexport interface PerFieldLwwResult {\n\t/** Fields the incoming operation wins (strictly newer than the stored version). */\n\twinners: string[]\n\t/** The merged field-version map to persist (winners advanced to `incomingVersion`). */\n\tmerged: FieldVersions\n}\n\n/**\n * Resolve per-field last-write-wins for an incoming operation.\n *\n * For each incoming field, the operation wins only when its version is strictly\n * greater than the field's effective stored version. Ties (identical version,\n * i.e. the same operation re-applied) are losses, keeping the resolution\n * idempotent. The comparison is a total order, so every node resolves the same\n * winner regardless of the order operations arrive.\n *\n * @param current - Parsed `_field_versions` of the target row\n * @param incomingFields - Field names carried by the incoming operation\n * @param incomingVersion - Serialized HLC version of the incoming operation\n * @param rowVersion - The row's `_version` (fallback for untracked fields)\n */\nexport function resolvePerFieldLww(\n\tcurrent: FieldVersions,\n\tincomingFields: Iterable<string>,\n\tincomingVersion: string,\n\trowVersion: string | undefined,\n): PerFieldLwwResult {\n\tconst winners: string[] = []\n\tconst merged: FieldVersions = { ...current }\n\tfor (const field of incomingFields) {\n\t\tconst stored = effectiveFieldVersion(current, field, rowVersion)\n\t\tif (incomingVersion > stored) {\n\t\t\twinners.push(field)\n\t\t\tmerged[field] = incomingVersion\n\t\t}\n\t\t// Losing fields are left untouched: their existing tracked version (or the\n\t\t// row-level `_version` fallback) already reflects the newer writer.\n\t}\n\treturn { winners, merged }\n}\n","import { KoraError, validateTransition } from '@korajs/core'\nimport type {\n\tCollectionDefinition,\n\tStateMachineConstraint,\n\tStateMachineDefinition,\n} from '@korajs/core'\n\n/**\n * Error thrown when a local mutation attempts an invalid state transition.\n *\n * Contains enough context to debug without reproduction:\n * the collection, record, field, current state, attempted state, and allowed transitions.\n */\nexport class InvalidStateTransitionError extends KoraError {\n\tconstructor(\n\t\tpublic readonly collection: string,\n\t\tpublic readonly recordId: string,\n\t\tpublic readonly field: string,\n\t\tpublic readonly fromState: string,\n\t\tpublic readonly toState: string,\n\t\tpublic readonly allowedStates: string[],\n\t) {\n\t\tsuper(\n\t\t\t`Invalid state transition in collection \"${collection}\": ` +\n\t\t\t\t`cannot transition field \"${field}\" from \"${fromState}\" to \"${toState}\". ` +\n\t\t\t\t`Allowed transitions from \"${fromState}\": ${allowedStates.length > 0 ? allowedStates.join(', ') : '(none -- terminal state)'}`,\n\t\t\t'INVALID_STATE_TRANSITION',\n\t\t\t{ collection, recordId, field, fromState, toState, allowedStates },\n\t\t)\n\t\tthis.name = 'InvalidStateTransitionError'\n\t}\n}\n\n/**\n * Validates a state machine transition for a local mutation (insert or update).\n *\n * For inserts: validates that the initial state (from the data or the default value)\n * is a known state in the state machine.\n *\n * For updates: looks up the current state field value and checks whether\n * the transition to the new value is allowed.\n *\n * @param collectionName - Name of the collection\n * @param recordId - The record being mutated\n * @param stateMachine - The state machine definition\n * @param currentState - The current value of the state field (null for inserts)\n * @param newState - The new value being set for the state field\n * @returns An object indicating whether the transition is valid, and if not, the allowed states.\n * When `onInvalidTransition` is 'last-valid-state', callers should suppress the field update\n * rather than throwing.\n */\nexport function validateStateTransition(\n\tcollectionName: string,\n\trecordId: string,\n\tstateMachine: StateMachineDefinition,\n\tcurrentState: string | null,\n\tnewState: string,\n): { valid: boolean; allowedStates: string[] } {\n\t// For inserts, any valid enum value is acceptable as the initial state\n\t// (schema validation already ensures the value is a valid enum value)\n\tif (currentState === null) {\n\t\treturn { valid: true, allowedStates: [] }\n\t}\n\n\t// Same-state transitions are always valid (idempotent updates)\n\tif (currentState === newState) {\n\t\treturn { valid: true, allowedStates: stateMachine.transitions[currentState] ?? [] }\n\t}\n\n\tconst constraint: StateMachineConstraint = {\n\t\tfield: stateMachine.field,\n\t\tcollection: collectionName,\n\t\ttransitions: stateMachine.transitions,\n\t}\n\tconst transitionResult = validateTransition(constraint, currentState, newState)\n\tif (transitionResult.valid) {\n\t\treturn { valid: true, allowedStates: transitionResult.allowedTargets }\n\t}\n\n\tconst allowedStates = transitionResult.allowedTargets\n\n\tif (stateMachine.onInvalidTransition === 'reject') {\n\t\tthrow new InvalidStateTransitionError(\n\t\t\tcollectionName,\n\t\t\trecordId,\n\t\t\tstateMachine.field,\n\t\t\tcurrentState,\n\t\t\tnewState,\n\t\t\tallowedStates,\n\t\t)\n\t}\n\n\t// 'last-valid-state': return invalid so the caller can suppress the field update\n\treturn { valid: false, allowedStates }\n}\n\n/**\n * Checks whether a collection update data object contains a change to the state machine field,\n * and if so, validates the transition.\n *\n * If the state field is not in the update data, returns the data unchanged.\n * If the transition is invalid and mode is 'last-valid-state', removes the field from the data.\n * If the transition is invalid and mode is 'reject', throws InvalidStateTransitionError.\n *\n * @param collectionName - Name of the collection\n * @param recordId - The record being updated\n * @param collectionDef - The collection definition from the schema\n * @param currentRecord - The current record data (must include the state field)\n * @param updateData - The partial update data\n * @returns The update data, potentially with the state field removed if invalid and mode is 'last-valid-state'\n */\nexport function validateUpdateStateMachine(\n\tcollectionName: string,\n\trecordId: string,\n\tcollectionDef: CollectionDefinition,\n\tcurrentRecord: Record<string, unknown>,\n\tupdateData: Record<string, unknown>,\n): Record<string, unknown> {\n\tconst stateMachine = collectionDef.stateMachine\n\tif (stateMachine === undefined) {\n\t\treturn updateData\n\t}\n\n\tconst stateField = stateMachine.field\n\tif (!(stateField in updateData)) {\n\t\t// State field not being changed -- no validation needed\n\t\treturn updateData\n\t}\n\n\tconst currentState = currentRecord[stateField]\n\tconst newState = updateData[stateField]\n\n\t// Both values must be strings for state machine validation\n\tif (typeof currentState !== 'string' || typeof newState !== 'string') {\n\t\treturn updateData\n\t}\n\n\tconst result = validateStateTransition(\n\t\tcollectionName,\n\t\trecordId,\n\t\tstateMachine,\n\t\tcurrentState,\n\t\tnewState,\n\t)\n\n\tif (result.valid) {\n\t\treturn updateData\n\t}\n\n\t// Mode is 'last-valid-state': silently remove the state field from the update\n\tconst filtered = { ...updateData }\n\tdelete filtered[stateField]\n\n\t// If no fields remain after removing the state field, the update becomes a no-op.\n\t// Return the empty object -- the caller can decide whether to proceed.\n\treturn filtered\n}\n","import type { AtomicOp } from '@korajs/core'\nimport {\n\tcreateOperation,\n\tisAtomicOp,\n\tresolveAtomicOp,\n\ttoAtomicOp,\n\tvalidateRecord,\n} from '@korajs/core'\nimport { RecordNotFoundError } from '../errors'\nimport { parseFieldVersions, serializeFieldVersions } from '../lww/field-versions'\nimport { serializeRowVersion } from '../lww/row-version'\nimport { buildInsertQuery, buildUpdateQuery } from '../query/sql-builder'\nimport { encodeRichtextFieldsForOpData } from '../serialization/op-data-encoding'\nimport { deserializeRecord, serializeOperation, serializeRecord } from '../serialization/serializer'\nimport { validateUpdateStateMachine } from '../state-machine/state-validator'\nimport { allocateNextSequenceInTransaction } from '../store/sequence-allocator'\nimport type { CollectionRecord, RawCollectionRow } from '../types'\nimport { resolveCausalDeps } from './resolve-causal-deps'\nimport type { LocalMutationContext } from './types'\n\n/**\n * Update a record and persist the operation log entry atomically.\n */\nexport async function executeUpdate(\n\tctx: LocalMutationContext,\n\tid: string,\n\tdata: Record<string, unknown>,\n): Promise<CollectionRecord> {\n\tconst currentRows = await ctx.adapter.query<RawCollectionRow>(\n\t\t`SELECT * FROM ${ctx.collection} WHERE id = ? AND _deleted = 0`,\n\t\t[id],\n\t)\n\tconst currentRow = currentRows[0]\n\tif (!currentRow) {\n\t\tthrow new RecordNotFoundError(ctx.collection, id)\n\t}\n\n\tlet validated = validateRecord(ctx.collection, ctx.definition, data, 'update')\n\tconst currentRecord = deserializeRecord(currentRow, ctx.definition.fields)\n\tvalidated = validateUpdateStateMachine(\n\t\tctx.collection,\n\t\tid,\n\t\tctx.definition,\n\t\tcurrentRecord,\n\t\tvalidated,\n\t)\n\n\tif (Object.keys(validated).length === 0) {\n\t\treturn { ...currentRecord }\n\t}\n\n\tconst previousData: Record<string, unknown> = {}\n\tconst resolvedData: Record<string, unknown> = {}\n\tconst atomicOps: Record<string, AtomicOp> = {}\n\n\tfor (const key of Object.keys(validated)) {\n\t\tconst value = validated[key]\n\t\tpreviousData[key] = currentRecord[key]\n\n\t\tif (isAtomicOp(value)) {\n\t\t\tresolvedData[key] = resolveAtomicOp(currentRecord[key], value)\n\t\t\tatomicOps[key] = toAtomicOp(value)\n\t\t} else {\n\t\t\tresolvedData[key] = value\n\t\t}\n\t}\n\n\tconst hasAtomicOps = Object.keys(atomicOps).length > 0\n\tconst causalDeps = resolveCausalDeps(ctx)\n\tlet operation!: Awaited<ReturnType<typeof createOperation>>\n\n\tawait ctx.adapter.transaction(async (tx) => {\n\t\tconst sequenceNumber = await allocateNextSequenceInTransaction(tx, ctx.nodeId)\n\t\toperation = await createOperation(\n\t\t\t{\n\t\t\t\tnodeId: ctx.nodeId,\n\t\t\t\ttype: 'update',\n\t\t\t\tcollection: ctx.collection,\n\t\t\t\trecordId: id,\n\t\t\t\t// Binary richtext values (new and previous) are tagged as canonical\n\t\t\t\t// JSON BEFORE the operation is content-hashed, so the hash input,\n\t\t\t\t// persisted JSON, and wire payload are the identical value.\n\t\t\t\tdata: encodeRichtextFieldsForOpData(resolvedData, ctx.definition.fields),\n\t\t\t\tpreviousData: encodeRichtextFieldsForOpData(previousData, ctx.definition.fields),\n\t\t\t\tsequenceNumber,\n\t\t\t\tcausalDeps,\n\t\t\t\tschemaVersion: ctx.schema.version,\n\t\t\t\t...(hasAtomicOps ? { atomicOps } : {}),\n\t\t\t},\n\t\t\tctx.clock,\n\t\t)\n\t\tctx.causalTracker?.afterOperation(ctx.collection, operation.id, ctx.inTransaction)\n\n\t\tconst serializedChanges = serializeRecord(resolvedData, ctx.definition.fields)\n\t\tconst version = serializeRowVersion(operation.timestamp)\n\t\t// A local edit is always the newest writer of the fields it touches (the\n\t\t// HLC advances past every timestamp this device has seen), so stamp each\n\t\t// changed field with this operation's version.\n\t\tconst mergedFieldVersions = parseFieldVersions(currentRow._field_versions)\n\t\tfor (const changedField of Object.keys(serializedChanges)) {\n\t\t\tmergedFieldVersions[changedField] = version\n\t\t}\n\t\tconst updateQuery = buildUpdateQuery(ctx.collection, id, {\n\t\t\t...serializedChanges,\n\t\t\t_updated_at: operation.timestamp.wallTime,\n\t\t\t_version: version,\n\t\t\t_field_versions: serializeFieldVersions(mergedFieldVersions),\n\t\t})\n\t\tconst opInsert = buildInsertQuery(\n\t\t\t`_kora_ops_${ctx.collection}`,\n\t\t\tserializeOperation(operation) as unknown as Record<string, unknown>,\n\t\t)\n\n\t\tawait tx.execute(updateQuery.sql, updateQuery.params)\n\t\tawait tx.execute(opInsert.sql, opInsert.params)\n\t})\n\n\tctx.onMutation(ctx.collection, operation)\n\n\tconst rows = await ctx.adapter.query<RawCollectionRow>(\n\t\t`SELECT * FROM ${ctx.collection} WHERE id = ? AND _deleted = 0`,\n\t\t[id],\n\t)\n\tconst row = rows[0]\n\tif (!row) {\n\t\tthrow new RecordNotFoundError(ctx.collection, id)\n\t}\n\treturn deserializeRecord(row, ctx.definition.fields)\n}\n"],"mappings":"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","names":["createOperation","createOperation","createOperation","validateRecord","validateRecord","createOperation"]}