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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/dist/adapters/better-sqlite3.d.cts +1 -1
  2. package/dist/adapters/better-sqlite3.d.ts +1 -1
  3. package/dist/adapters/indexeddb.cjs +4 -1
  4. package/dist/adapters/indexeddb.cjs.map +1 -1
  5. package/dist/adapters/indexeddb.d.cts +2 -2
  6. package/dist/adapters/indexeddb.d.ts +2 -2
  7. package/dist/adapters/indexeddb.js +1 -1
  8. package/dist/adapters/sqlite-wasm-shared-host.cjs +292 -42
  9. package/dist/adapters/sqlite-wasm-shared-host.cjs.map +1 -1
  10. package/dist/adapters/sqlite-wasm-shared-host.d.cts +43 -25
  11. package/dist/adapters/sqlite-wasm-shared-host.d.ts +43 -25
  12. package/dist/adapters/sqlite-wasm-shared-host.js +33 -42
  13. package/dist/adapters/sqlite-wasm-shared-host.js.map +1 -1
  14. package/dist/adapters/sqlite-wasm-worker.cjs +206 -208
  15. package/dist/adapters/sqlite-wasm-worker.cjs.map +1 -1
  16. package/dist/adapters/sqlite-wasm-worker.js +8 -257
  17. package/dist/adapters/sqlite-wasm-worker.js.map +1 -1
  18. package/dist/adapters/sqlite-wasm.cjs +4 -1
  19. package/dist/adapters/sqlite-wasm.cjs.map +1 -1
  20. package/dist/adapters/sqlite-wasm.d.cts +3 -3
  21. package/dist/adapters/sqlite-wasm.d.ts +3 -3
  22. package/dist/adapters/sqlite-wasm.js +1 -1
  23. package/dist/{backup-IQE4QC7Q.js → backup-5S7K2Z35.js} +3 -3
  24. package/dist/chunk-3Y2IERBF.js +255 -0
  25. package/dist/chunk-3Y2IERBF.js.map +1 -0
  26. package/dist/{chunk-RCTG3IJG.js → chunk-CYO3YKFL.js} +2 -2
  27. package/dist/{chunk-6NKLDBTY.js → chunk-GSJ7EEUC.js} +2 -1
  28. package/dist/chunk-GSJ7EEUC.js.map +1 -0
  29. package/dist/{chunk-OCOGQRHW.js → chunk-HS6DBMST.js} +2 -2
  30. package/dist/{chunk-2ZRQGFEF.js → chunk-Q57CHIIN.js} +5 -2
  31. package/dist/chunk-Q57CHIIN.js.map +1 -0
  32. package/dist/{chunk-UXRWVXBP.js → chunk-RL43UYQZ.js} +2 -2
  33. package/dist/{export-audit-MT2LTMIN.js → export-audit-ODGQTQIK.js} +3 -3
  34. package/dist/index.cjs +120 -3
  35. package/dist/index.cjs.map +1 -1
  36. package/dist/index.d.cts +27 -4
  37. package/dist/index.d.ts +27 -4
  38. package/dist/index.js +128 -11
  39. package/dist/index.js.map +1 -1
  40. package/dist/internal.cjs +1 -0
  41. package/dist/internal.cjs.map +1 -1
  42. package/dist/internal.d.cts +3 -3
  43. package/dist/internal.d.ts +3 -3
  44. package/dist/internal.js +2 -2
  45. package/dist/{sqlite-wasm-channel-CNHRf-m2.d.cts → sqlite-wasm-channel-Bx57s8Cq.d.cts} +1 -1
  46. package/dist/{sqlite-wasm-channel-CNHRf-m2.d.ts → sqlite-wasm-channel-Bx57s8Cq.d.ts} +1 -1
  47. package/dist/{types-5h4Hqcwc.d.ts → types-C1kigrWz.d.ts} +1 -1
  48. package/dist/{types-k6AZgiH4.d.ts → types-D1MzKVmk.d.cts} +10 -0
  49. package/dist/{types-k6AZgiH4.d.cts → types-D1MzKVmk.d.ts} +10 -0
  50. package/dist/{types-DfPiPwKr.d.cts → types-DAo9oduO.d.cts} +1 -1
  51. package/package.json +2 -2
  52. package/dist/chunk-2ZRQGFEF.js.map +0 -1
  53. package/dist/chunk-6NKLDBTY.js.map +0 -1
  54. /package/dist/{backup-IQE4QC7Q.js.map → backup-5S7K2Z35.js.map} +0 -0
  55. /package/dist/{chunk-RCTG3IJG.js.map → chunk-CYO3YKFL.js.map} +0 -0
  56. /package/dist/{chunk-OCOGQRHW.js.map → chunk-HS6DBMST.js.map} +0 -0
  57. /package/dist/{chunk-UXRWVXBP.js.map → chunk-RL43UYQZ.js.map} +0 -0
  58. /package/dist/{export-audit-MT2LTMIN.js.map → export-audit-ODGQTQIK.js.map} +0 -0
@@ -42,6 +42,7 @@ function encodeRichtext(value) {
42
42
  }
43
43
  if (typeof value === "string") {
44
44
  const doc = new Y.Doc();
45
+ doc.clientID = 0;
45
46
  doc.getText(TEXT_KEY).insert(0, value);
46
47
  return Y.encodeStateAsUpdate(doc);
47
48
  }
@@ -253,4 +254,4 @@ export {
253
254
  deserializeOperation,
254
255
  deserializeOperationWithCollection
255
256
  };
256
- //# sourceMappingURL=chunk-6NKLDBTY.js.map
257
+ //# sourceMappingURL=chunk-GSJ7EEUC.js.map
@@ -0,0 +1 @@
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\t// Deterministic clientId so the same string encodes to the same bytes on every\n\t\t// device (a plain string is a replacement, not a collaborative actor). A random\n\t\t// clientId would make a string-derived richtext column diverge byte-wise across\n\t\t// replicas even when the text is identical.\n\t\tdoc.clientID = 0\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\tcase 'object':\n\t\tcase 'json':\n\t\tcase 'blob':\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 'object':\n\t\tcase 'json':\n\t\tcase 'blob':\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,6 +1,6 @@
1
1
  import {
2
2
  deserializeOperationWithCollection
3
- } from "./chunk-6NKLDBTY.js";
3
+ } from "./chunk-GSJ7EEUC.js";
4
4
 
5
5
  // src/backup/backup.ts
6
6
  import { quoteIdent } from "@korajs/core";
@@ -380,4 +380,4 @@ export {
380
380
  verifyBackupChecksum,
381
381
  restoreBackup
382
382
  };
383
- //# sourceMappingURL=chunk-OCOGQRHW.js.map
383
+ //# sourceMappingURL=chunk-HS6DBMST.js.map
@@ -331,7 +331,10 @@ var SqliteWasmAdapter = class {
331
331
  } else if (this.sharedWorkerUrl && this.workerUrl && isSharedWorkerStorageSupported()) {
332
332
  const workerUrlString = typeof this.workerUrl === "string" ? this.workerUrl : this.workerUrl.href;
333
333
  const sharedUrl = typeof this.sharedWorkerUrl === "string" ? this.sharedWorkerUrl : this.sharedWorkerUrl.href;
334
- this.sharedWorker = new SharedWorker(sharedUrl, { name: `kora-sw-${this.dbName}` });
334
+ this.sharedWorker = new SharedWorker(sharedUrl, {
335
+ type: "module",
336
+ name: `kora-sw-${this.dbName}`
337
+ });
335
338
  this.bridge = new SharedWorkerClientBridge(this.sharedWorker, this.dbName, workerUrlString);
336
339
  } else if (this.workerUrl) {
337
340
  this.schema = schema;
@@ -565,4 +568,4 @@ var SqliteWasmAdapter = class {
565
568
  export {
566
569
  SqliteWasmAdapter
567
570
  };
568
- //# sourceMappingURL=chunk-2ZRQGFEF.js.map
571
+ //# sourceMappingURL=chunk-Q57CHIIN.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/adapters/sqlite-wasm-adapter.ts","../src/multi-tab/shared-worker-bridge.ts","../src/multi-tab/tab-storage.ts"],"sourcesContent":["import { generateFullDDL } from '@korajs/core'\nimport type { KoraEventEmitter, SchemaDefinition } from '@korajs/core'\nimport { AdapterError, StoreNotOpenError } from '../errors'\nimport { SharedWorkerClientBridge } from '../multi-tab/shared-worker-bridge'\nimport {\n\tFollowerBroadcastBridge,\n\tacquireTabStorageSession,\n\tisSharedWorkerStorageSupported,\n\tstartLeaderRpcRelay,\n} from '../multi-tab/tab-storage'\nimport type { MigrationPlan, StorageAdapter, Transaction } from '../types'\nimport { Mutex } from './sqlite-wasm-channel'\nimport type { WorkerBridge, WorkerRequest, WorkerResponse } from './sqlite-wasm-channel'\n\n/**\n * Options for creating a SqliteWasmAdapter.\n */\nexport interface SqliteWasmAdapterOptions {\n\t/**\n\t * Injected WorkerBridge for testing. If omitted, a WebWorkerBridge is created\n\t * in browser environments.\n\t */\n\tbridge?: WorkerBridge\n\n\t/**\n\t * Database name for persistence. Used as the OPFS file name or IDB key.\n\t */\n\tdbName?: string\n\n\t/**\n\t * URL to the sqlite-wasm-worker script. Required in browsers if no bridge is provided.\n\t */\n\tworkerUrl?: string | URL\n\n\t/** Timeout for worker / follower RPC responses. Defaults to 30000ms. */\n\tworkerResponseTimeoutMs?: number\n\n\t/**\n\t * Optional SharedWorker host script (`sqlite-wasm-shared-host`). When set and\n\t * {@link isSharedWorkerStorageSupported}, one DedicatedWorker per `dbName` is pooled per origin.\n\t * Requires `workerUrl` for the inner SQLite worker. Falls back to leader election when omitted.\n\t */\n\tsharedWorkerUrl?: string | URL\n\n\t/**\n\t * When set, storage diagnostics are emitted here: `store:opfs-unavailable` when\n\t * persistence silently degraded to a non-persistent in-memory database, and\n\t * `store:db-name-collision` when another runtime on this origin was already\n\t * using this database name.\n\t */\n\temitter?: KoraEventEmitter\n}\n\n/**\n * SQLite WASM adapter that communicates with a SQLite instance through a WorkerBridge.\n *\n * In browsers, the bridge is backed by a Web Worker running SQLite WASM with OPFS persistence.\n * In Node.js tests, the bridge is backed by MockWorkerBridge wrapping better-sqlite3.\n *\n * @example\n * ```typescript\n * // Browser usage\n * const adapter = new SqliteWasmAdapter({ workerUrl: '/sqlite-wasm-worker.js' })\n *\n * // Test usage with MockWorkerBridge\n * import { MockWorkerBridge } from './sqlite-wasm-mock-bridge'\n * const adapter = new SqliteWasmAdapter({ bridge: new MockWorkerBridge() })\n * ```\n */\nexport class SqliteWasmAdapter implements StorageAdapter {\n\tprivate bridge: WorkerBridge | null = null\n\tprivate opened = false\n\tprivate readonly mutex = new Mutex()\n\tprivate readonly injectedBridge: WorkerBridge | undefined\n\tprivate readonly workerUrl: string | URL | undefined\n\tprivate readonly sharedWorkerUrl: string | URL | undefined\n\tprivate readonly workerResponseTimeoutMs: number\n\tprivate readonly dbName: string\n\tprivate readonly emitter: KoraEventEmitter | undefined\n\tprivate tabSession: Awaited<ReturnType<typeof acquireTabStorageSession>> | null = null\n\tprivate sharedWorker: SharedWorker | null = null\n\t/** Retained so a follower promoted to leader can re-open its own worker. */\n\tprivate schema: SchemaDefinition | null = null\n\tprivate promoting = false\n\n\tconstructor(options: SqliteWasmAdapterOptions = {}) {\n\t\tthis.injectedBridge = options.bridge\n\t\tthis.workerUrl = options.workerUrl\n\t\tthis.sharedWorkerUrl = options.sharedWorkerUrl\n\t\tthis.workerResponseTimeoutMs = options.workerResponseTimeoutMs ?? 30_000\n\t\tthis.dbName = options.dbName ?? 'kora-db'\n\t\tthis.emitter = options.emitter\n\t}\n\n\tasync open(schema: SchemaDefinition): Promise<void> {\n\t\tif (this.opened) return\n\n\t\tif (this.injectedBridge) {\n\t\t\tthis.bridge = this.injectedBridge\n\t\t} else if (this.sharedWorkerUrl && this.workerUrl && isSharedWorkerStorageSupported()) {\n\t\t\tconst workerUrlString =\n\t\t\t\ttypeof this.workerUrl === 'string' ? this.workerUrl : this.workerUrl.href\n\t\t\tconst sharedUrl =\n\t\t\t\ttypeof this.sharedWorkerUrl === 'string' ? this.sharedWorkerUrl : this.sharedWorkerUrl.href\n\t\t\t// `type: 'module'` is required: the shared host is authored and served as an\n\t\t\t// ES module (it uses import). A classic SharedWorker cannot parse it and\n\t\t\t// fails silently, so `open` would hang forever.\n\t\t\tthis.sharedWorker = new SharedWorker(sharedUrl, {\n\t\t\t\ttype: 'module',\n\t\t\t\tname: `kora-sw-${this.dbName}`,\n\t\t\t})\n\t\t\tthis.bridge = new SharedWorkerClientBridge(this.sharedWorker, this.dbName, workerUrlString)\n\t\t} else if (this.workerUrl) {\n\t\t\tthis.schema = schema\n\t\t\tthis.tabSession = await acquireTabStorageSession(this.dbName, {\n\t\t\t\tonPromote: () => {\n\t\t\t\t\tvoid this.promoteToLeader()\n\t\t\t\t},\n\t\t\t})\n\t\t\tconst { WebWorkerBridge } = await import('./sqlite-wasm-channel')\n\n\t\t\tif (this.tabSession.role === 'leader') {\n\t\t\t\tconst workerBridge = new WebWorkerBridge(this.workerUrl, this.workerResponseTimeoutMs)\n\t\t\t\tthis.tabSession.stopRelay = startLeaderRpcRelay(this.tabSession.channelName, workerBridge)\n\t\t\t\tthis.bridge = workerBridge\n\t\t\t} else {\n\t\t\t\tconst followerBridge = new FollowerBroadcastBridge(\n\t\t\t\t\tthis.tabSession.channelName,\n\t\t\t\t\tthis.workerResponseTimeoutMs,\n\t\t\t\t)\n\t\t\t\tthis.bridge = followerBridge\n\t\t\t\t// Another runtime on this origin already owns this database name. That is\n\t\t\t\t// expected for multiple tabs of the same app (they share one leader),\n\t\t\t\t// but a bug if these are logically separate apps, so surface it so the\n\t\t\t\t// developer can give them distinct store names.\n\t\t\t\tthis.emitter?.emit({\n\t\t\t\t\ttype: 'store:db-name-collision',\n\t\t\t\t\tdbName: this.dbName,\n\t\t\t\t\tmessage: `Another runtime on this origin is already using database \"${this.dbName}\"; this runtime is sharing it as a follower. If these are separate apps, give each a distinct store name.`,\n\t\t\t\t})\n\t\t\t\t// Readiness handshake: give the leader relay a moment to answer before the\n\t\t\t\t// first RPC, so a follower opened during a leader's startup race retries the\n\t\t\t\t// handshake instead of firing into the void and waiting out the full timeout.\n\t\t\t\tawait followerBridge.waitForLeader()\n\t\t\t}\n\t\t} else {\n\t\t\tthrow new AdapterError(\n\t\t\t\t'SqliteWasmAdapter requires either a bridge (for testing) or a workerUrl (for browsers). ' +\n\t\t\t\t\t'Pass { bridge: new MockWorkerBridge() } for tests, or { workerUrl: \"/worker.js\" } for browsers.',\n\t\t\t)\n\t\t}\n\n\t\tconst ddlStatements = generateFullDDL(schema)\n\t\tconst response = await this.sendRequest({\n\t\t\tid: 0,\n\t\t\ttype: 'open',\n\t\t\tddlStatements,\n\t\t\tdbName: this.dbName,\n\t\t})\n\t\tif (response.type === 'error') {\n\t\t\tthrow new AdapterError(`Failed to open database: ${response.message}`, {\n\t\t\t\tcode: response.code,\n\t\t\t\tdbName: this.dbName,\n\t\t\t})\n\t\t}\n\t\tthis.reportStorageMode(response.data)\n\t\tthis.opened = true\n\t}\n\n\t/**\n\t * Surface a `store:opfs-unavailable` diagnostic when the worker reported that\n\t * persistence silently degraded to an in-memory database, so a data-loss\n\t * condition is observable instead of failing silently. A persistent open (or a\n\t * bridge that does not report a mode, e.g. the Node mock) emits nothing.\n\t */\n\tprivate reportStorageMode(data: unknown): void {\n\t\tif (!this.emitter || typeof data !== 'object' || data === null) {\n\t\t\treturn\n\t\t}\n\t\tconst mode = data as {\n\t\t\tpersistent?: boolean\n\t\t\tfallbackReason?: 'lock-conflict' | 'timeout' | 'unsupported'\n\t\t}\n\t\tif (mode.persistent === false) {\n\t\t\tconst reason = mode.fallbackReason ?? 'unsupported'\n\t\t\tthis.emitter.emit({\n\t\t\t\ttype: 'store:opfs-unavailable',\n\t\t\t\tdbName: this.dbName,\n\t\t\t\treason,\n\t\t\t\tmessage: `OPFS persistence is unavailable (${reason}) for database \"${this.dbName}\"; the store is running in memory and data will not survive a reload.`,\n\t\t\t})\n\t\t}\n\t}\n\n\tasync close(): Promise<void> {\n\t\tif (!this.bridge) return\n\n\t\ttry {\n\t\t\tawait this.sendRequest({ id: 0, type: 'close' })\n\t\t} finally {\n\t\t\tthis.tabSession?.stopRelay?.()\n\t\t\tthis.tabSession?.cancelPromotionWatch?.()\n\t\t\tif (this.tabSession?.releaseLock) {\n\t\t\t\tawait this.tabSession.releaseLock()\n\t\t\t}\n\t\t\tthis.tabSession = null\n\t\t\tthis.sharedWorker = null\n\t\t\tthis.bridge.terminate()\n\t\t\tthis.bridge = null\n\t\t\tthis.opened = false\n\t\t}\n\t}\n\n\t/**\n\t * Promotes a follower to leader after the previous leader released the storage\n\t * lock (its tab closed or crashed). The lock is only granted once the old leader\n\t * is gone, so opening our own worker here is safe from the OPFS single-writer\n\t * rule. In-flight follower RPCs are rejected and left to the caller / reactive\n\t * layer to retry against the new leader.\n\t */\n\tprivate async promoteToLeader(): Promise<void> {\n\t\tif (this.promoting || !this.opened || this.workerUrl === undefined || this.schema === null) {\n\t\t\treturn\n\t\t}\n\t\tthis.promoting = true\n\n\t\tconst { WebWorkerBridge } = await import('./sqlite-wasm-channel')\n\t\tconst workerBridge = new WebWorkerBridge(this.workerUrl, this.workerResponseTimeoutMs)\n\n\t\tconst previousBridge = this.bridge\n\t\tthis.bridge = workerBridge\n\t\tif (this.tabSession) {\n\t\t\tthis.tabSession.role = 'leader'\n\t\t\tthis.tabSession.stopRelay = startLeaderRpcRelay(this.tabSession.channelName, workerBridge)\n\t\t}\n\t\tpreviousBridge?.terminate()\n\n\t\t// Re-open against our own worker. DDL is idempotent, and the OPFS data the old\n\t\t// leader persisted is now readable by this worker.\n\t\tconst ddlStatements = generateFullDDL(this.schema)\n\t\tawait this.sendRequest({ id: 0, type: 'open', ddlStatements, dbName: this.dbName })\n\t}\n\n\tasync execute(sql: string, params?: unknown[]): Promise<void> {\n\t\tthis.guardOpen()\n\t\tconst response = await this.sendRequest({ id: 0, type: 'execute', sql, params })\n\t\tif (response.type === 'error') {\n\t\t\tthrow new AdapterError(`Execute failed: ${response.message}`, { sql, params })\n\t\t}\n\t}\n\n\tasync query<T>(sql: string, params?: unknown[]): Promise<T[]> {\n\t\tthis.guardOpen()\n\t\tconst response = await this.sendRequest({ id: 0, type: 'query', sql, params })\n\t\tif (response.type === 'error') {\n\t\t\tthrow new AdapterError(`Query failed: ${response.message}`, { sql, params })\n\t\t}\n\t\treturn (response.data as T[]) ?? []\n\t}\n\n\tasync transaction(fn: (tx: Transaction) => Promise<void>): Promise<void> {\n\t\tthis.guardOpen()\n\n\t\tconst release = await this.mutex.acquire()\n\t\ttry {\n\t\t\tawait this.sendChecked({ id: 0, type: 'begin' }, 'BEGIN transaction')\n\n\t\t\tconst tx: Transaction = {\n\t\t\t\texecute: async (sql: string, params?: unknown[]): Promise<void> => {\n\t\t\t\t\tconst response = await this.sendRequest({ id: 0, type: 'execute', sql, params })\n\t\t\t\t\tif (response.type === 'error') {\n\t\t\t\t\t\tthrow new AdapterError(`Transaction execute failed: ${response.message}`, {\n\t\t\t\t\t\t\tsql,\n\t\t\t\t\t\t\tparams,\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tquery: async <T>(sql: string, params?: unknown[]): Promise<T[]> => {\n\t\t\t\t\tconst response = await this.sendRequest({ id: 0, type: 'query', sql, params })\n\t\t\t\t\tif (response.type === 'error') {\n\t\t\t\t\t\tthrow new AdapterError(`Transaction query failed: ${response.message}`, { sql, params })\n\t\t\t\t\t}\n\t\t\t\t\treturn (response.data as T[]) ?? []\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tawait fn(tx)\n\t\t\tawait this.sendChecked({ id: 0, type: 'commit' }, 'COMMIT transaction')\n\t\t} catch (error) {\n\t\t\t// Attempt rollback, but don't mask the original error\n\t\t\ttry {\n\t\t\t\tawait this.sendRequest({ id: 0, type: 'rollback' })\n\t\t\t} catch {\n\t\t\t\t// Rollback failure is secondary to the original error\n\t\t\t}\n\t\t\tthrow error\n\t\t} finally {\n\t\t\trelease()\n\t\t}\n\t}\n\n\tasync migrate(from: number, to: number, migration: MigrationPlan): Promise<void> {\n\t\tthis.guardOpen()\n\n\t\tconst release = await this.mutex.acquire()\n\t\ttry {\n\t\t\tawait this.sendChecked({ id: 0, type: 'begin' }, 'BEGIN migration')\n\n\t\t\tfor (const sql of migration.statements) {\n\t\t\t\tconst response = await this.sendRequest({ id: 0, type: 'execute', sql })\n\t\t\t\tif (response.type === 'error') {\n\t\t\t\t\tthrow new AdapterError(`Migration from v${from} to v${to} failed: ${response.message}`, {\n\t\t\t\t\t\tfrom,\n\t\t\t\t\t\tto,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tawait this.sendChecked({ id: 0, type: 'commit' }, 'COMMIT migration')\n\t\t} catch (error) {\n\t\t\ttry {\n\t\t\t\tawait this.sendRequest({ id: 0, type: 'rollback' })\n\t\t\t} catch {\n\t\t\t\t// Rollback failure is secondary\n\t\t\t}\n\t\t\tif (error instanceof AdapterError) throw error\n\t\t\tthrow new AdapterError(\n\t\t\t\t`Migration from v${from} to v${to} failed: ${(error as Error).message}`,\n\t\t\t\t{ from, to },\n\t\t\t)\n\t\t} finally {\n\t\t\trelease()\n\t\t}\n\t}\n\n\t/**\n\t * Export the database as a Uint8Array (for IndexedDB persistence).\n\t * Only available when the database is open.\n\t */\n\tasync exportDatabase(): Promise<Uint8Array> {\n\t\tthis.guardOpen()\n\t\tconst response = await this.sendRequest({ id: 0, type: 'export' })\n\t\tif (response.type === 'error') {\n\t\t\tthrow new AdapterError(`Export failed: ${response.message}`)\n\t\t}\n\t\treturn response.data as Uint8Array\n\t}\n\n\t/**\n\t * Import a serialized database snapshot.\n\t */\n\tasync importDatabase(data: Uint8Array): Promise<void> {\n\t\tthis.guardOpen()\n\t\tconst response = await this.sendRequest({ id: 0, type: 'import', data })\n\t\tif (response.type === 'error') {\n\t\t\tthrow new AdapterError(`Import failed: ${response.message}`)\n\t\t}\n\t}\n\n\tprivate guardOpen(): void {\n\t\tif (!this.opened || !this.bridge) {\n\t\t\tthrow new StoreNotOpenError()\n\t\t}\n\t}\n\n\tprivate async sendRequest(request: WorkerRequest): Promise<WorkerResponse> {\n\t\t// guardOpen() is always called before sendRequest, so bridge is guaranteed non-null\n\t\tconst bridge = this.bridge\n\t\tif (!bridge) {\n\t\t\tthrow new StoreNotOpenError()\n\t\t}\n\t\treturn bridge.send(request)\n\t}\n\n\tprivate async sendChecked(request: WorkerRequest, description: string): Promise<void> {\n\t\tconst response = await this.sendRequest(request)\n\t\tif (response.type === 'error') {\n\t\t\tthrow new AdapterError(`${description} failed: ${response.message}`)\n\t\t}\n\t}\n}\n","/// <reference lib=\"dom\" />\nimport type { WorkerBridge, WorkerRequest, WorkerResponse } from '../adapters/sqlite-wasm-channel'\nimport { WorkerTimeoutError } from '../errors'\n\nconst SW_REQUEST = 'kora-sw-request'\nconst SW_RESPONSE = 'kora-sw-response'\n\nexport interface SharedWorkerRpcRequest {\n\ttype: typeof SW_REQUEST\n\trequestId: string\n\tdbName: string\n\tworkerUrl: string\n\trequest: WorkerRequest\n}\n\nexport interface SharedWorkerRpcResponse {\n\ttype: typeof SW_RESPONSE\n\trequestId: string\n\tresponse: WorkerResponse\n}\n\n/**\n * Forwards {@link WorkerBridge} RPC to a SharedWorker that hosts one DedicatedWorker per `dbName`.\n * Pair with the bundled `sqlite-wasm-shared-host` worker script.\n */\nexport class SharedWorkerClientBridge implements WorkerBridge {\n\tprivate readonly port: MessagePort\n\tprivate readonly pending = new Map<\n\t\tstring,\n\t\t{ resolve: (r: WorkerResponse) => void; reject: (e: Error) => void }\n\t>()\n\tprivate readonly timeoutMs: number\n\tprivate terminated = false\n\n\tconstructor(\n\t\tsharedWorker: SharedWorker,\n\t\tprivate readonly dbName: string,\n\t\tprivate readonly workerUrl: string,\n\t\ttimeoutMs = 30000,\n\t) {\n\t\tthis.timeoutMs = timeoutMs\n\t\tthis.port = sharedWorker.port\n\t\tthis.port.start()\n\t\tthis.port.addEventListener('message', (event: MessageEvent<SharedWorkerRpcResponse>) => {\n\t\t\tconst data = event.data\n\t\t\tif (data?.type !== SW_RESPONSE) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst entry = this.pending.get(data.requestId)\n\t\t\tif (entry) {\n\t\t\t\tthis.pending.delete(data.requestId)\n\t\t\t\tentry.resolve(data.response)\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: 'SharedWorker bridge terminated',\n\t\t\t\tcode: 'BRIDGE_TERMINATED',\n\t\t\t}\n\t\t}\n\n\t\tconst requestId = crypto.randomUUID()\n\t\tconst msg: SharedWorkerRpcRequest = {\n\t\t\ttype: SW_REQUEST,\n\t\t\trequestId,\n\t\t\tdbName: this.dbName,\n\t\t\tworkerUrl: this.workerUrl,\n\t\t\trequest,\n\t\t}\n\n\t\treturn new Promise<WorkerResponse>((resolve, reject) => {\n\t\t\tconst timer = setTimeout(() => {\n\t\t\t\tthis.pending.delete(requestId)\n\t\t\t\treject(new WorkerTimeoutError(`shared-worker:${request.type}`, this.timeoutMs))\n\t\t\t}, this.timeoutMs)\n\n\t\t\tthis.pending.set(requestId, {\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.port.postMessage(msg)\n\t\t})\n\t}\n\n\tterminate(): void {\n\t\tif (this.terminated) {\n\t\t\treturn\n\t\t}\n\t\tthis.terminated = true\n\t\tthis.port.close()\n\t\tfor (const [, entry] of this.pending) {\n\t\t\tentry.reject(new Error('SharedWorker bridge terminated'))\n\t\t}\n\t\tthis.pending.clear()\n\t}\n}\n","/**\n * Multi-tab SQLite storage coordination via `navigator.locks` and `BroadcastChannel`.\n *\n * One tab holds the exclusive `kora-leader-${dbName}` lock and owns the SQLite worker.\n * Other tabs send worker RPC over a named broadcast channel.\n */\n\nimport type { WorkerBridge, WorkerRequest, WorkerResponse } from '../adapters/sqlite-wasm-channel'\nimport { NoLeaderError, WorkerTimeoutError } from '../errors'\n\nconst RPC_REQUEST = 'kora-worker-request'\nconst RPC_RESPONSE = 'kora-worker-response'\nconst LEADER_PING = 'kora-leader-ping'\nconst LEADER_PONG = 'kora-leader-pong'\n\n/** Default delay before a stalled follower RPC probes the leader for liveness. */\nconst DEFAULT_LIVENESS_PROBE_MS = 2000\n\ninterface RpcRequestMessage {\n\ttype: typeof RPC_REQUEST\n\trequestId: string\n\trequest: WorkerRequest\n}\n\ninterface RpcResponseMessage {\n\ttype: typeof RPC_RESPONSE\n\trequestId: string\n\tresponse: WorkerResponse\n}\n\nexport type TabStorageRole = 'leader' | 'follower'\n\nexport interface AcquireTabStorageOptions {\n\t/**\n\t * Invoked when a follower is promoted to leader because the previous leader\n\t * released the lock (its tab closed or crashed). The adapter uses this to\n\t * rebuild its bridge as a leader. Never fires for a tab that started as leader.\n\t */\n\tonPromote?: () => void\n}\n\nexport interface TabStorageSession {\n\trole: TabStorageRole\n\tchannelName: string\n\t/** Leader only: release the navigator lock when closing the database. */\n\treleaseLock?: () => Promise<void>\n\t/** Leader only: stop the broadcast RPC relay. */\n\tstopRelay?: () => void\n\t/**\n\t * Follower only: resolves when this tab has been promoted to leader (the old\n\t * leader released the lock). The adapter awaits nothing here directly; it reacts\n\t * via {@link AcquireTabStorageOptions.onPromote}. Present so callers can cancel\n\t * the promotion watch on close.\n\t */\n\tcancelPromotionWatch?: () => void\n}\n\n/**\n * Returns whether a SharedWorker could host a single SQLite WASM instance per origin.\n * Not implemented yet — use {@link isMultiTabStorageSupported} + leader election today.\n */\nexport function isSharedWorkerStorageSupported(): boolean {\n\treturn typeof globalThis !== 'undefined' && typeof SharedWorker !== 'undefined'\n}\n\n/**\n * Returns whether multi-tab coordination APIs exist in this runtime.\n */\nexport function isMultiTabStorageSupported(): boolean {\n\treturn (\n\t\ttypeof globalThis !== 'undefined' &&\n\t\ttypeof BroadcastChannel !== 'undefined' &&\n\t\ttypeof navigator !== 'undefined' &&\n\t\ttypeof navigator.locks?.request === 'function'\n\t)\n}\n\n/**\n * Resolve leader vs follower for a database name.\n * Without lock APIs, every instance is treated as leader (single-tab / Node).\n *\n * A follower additionally queues a blocking request for the same lock. When the\n * current leader releases it (its tab closed or crashed), the browser grants the\n * lock to this follower and {@link AcquireTabStorageOptions.onPromote} fires so the\n * adapter can rebuild itself as the new leader. This is the automatic failover\n * path; a leader that is merely throttled (backgrounded but alive) keeps the lock,\n * and followers stay resilient through the liveness probe on the RPC bridge.\n */\nexport async function acquireTabStorageSession(\n\tdbName: string,\n\toptions?: AcquireTabStorageOptions,\n): Promise<TabStorageSession> {\n\tconst channelName = `kora-storage-${dbName}`\n\tconst lockName = `kora-leader-${dbName}`\n\n\tif (!isMultiTabStorageSupported()) {\n\t\treturn { role: 'leader', channelName }\n\t}\n\n\treturn new Promise<TabStorageSession>((resolve) => {\n\t\tlet releaseHeld: (() => void) | undefined\n\n\t\tvoid navigator.locks.request(lockName, { mode: 'exclusive', ifAvailable: true }, (lock) => {\n\t\t\tif (lock === null) {\n\t\t\t\tresolve(startFollowerSession(channelName, lockName, options))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresolve({\n\t\t\t\trole: 'leader',\n\t\t\t\tchannelName,\n\t\t\t\treleaseLock: async () => {\n\t\t\t\t\treleaseHeld?.()\n\t\t\t\t},\n\t\t\t})\n\n\t\t\treturn new Promise<void>((release) => {\n\t\t\t\treleaseHeld = release\n\t\t\t})\n\t\t})\n\t})\n}\n\n/**\n * Builds a follower session and starts watching for promotion. The follower holds\n * a queued lock request; when it is finally granted (old leader gone), it becomes\n * the leader for the rest of its lifetime and notifies via `onPromote`.\n */\nfunction startFollowerSession(\n\tchannelName: string,\n\tlockName: string,\n\toptions?: AcquireTabStorageOptions,\n): TabStorageSession {\n\tlet promoted = false\n\tlet releasePromotedLock: (() => void) | undefined\n\tconst abort = new AbortController()\n\n\tvoid navigator.locks\n\t\t.request(lockName, { mode: 'exclusive', signal: abort.signal }, () => {\n\t\t\t// Reaching here means the previous leader released the lock and this tab\n\t\t\t// won it. Hold it (never-resolving promise) so this tab is now the leader.\n\t\t\tpromoted = true\n\t\t\toptions?.onPromote?.()\n\t\t\treturn new Promise<void>((release) => {\n\t\t\t\treleasePromotedLock = release\n\t\t\t})\n\t\t})\n\t\t.catch(() => {\n\t\t\t// AbortError when the tab closes before promotion. Nothing to do.\n\t\t})\n\n\treturn {\n\t\trole: 'follower',\n\t\tchannelName,\n\t\tcancelPromotionWatch: () => {\n\t\t\tif (promoted) {\n\t\t\t\treleasePromotedLock?.()\n\t\t\t} else {\n\t\t\t\tabort.abort()\n\t\t\t}\n\t\t},\n\t}\n}\n\n/**\n * Leader tab: forward follower RPC to the real worker bridge.\n */\nexport function startLeaderRpcRelay(channelName: string, bridge: WorkerBridge): () => void {\n\tconst channel = new BroadcastChannel(channelName)\n\n\tconst onMessage = (\n\t\tevent: MessageEvent<RpcRequestMessage | { type: typeof LEADER_PING }>,\n\t): void => {\n\t\tconst data = event.data\n\t\t// Answer liveness probes immediately, without touching the worker, so a\n\t\t// follower can distinguish a slow-but-alive leader from an absent one.\n\t\tif (data?.type === LEADER_PING) {\n\t\t\tchannel.postMessage({ type: LEADER_PONG })\n\t\t\treturn\n\t\t}\n\t\tif (data?.type !== RPC_REQUEST) {\n\t\t\treturn\n\t\t}\n\n\t\tvoid bridge\n\t\t\t.send(data.request)\n\t\t\t.then((response) => {\n\t\t\t\tconst msg: RpcResponseMessage = {\n\t\t\t\t\ttype: RPC_RESPONSE,\n\t\t\t\t\trequestId: data.requestId,\n\t\t\t\t\tresponse,\n\t\t\t\t}\n\t\t\t\tchannel.postMessage(msg)\n\t\t\t})\n\t\t\t.catch((error: unknown) => {\n\t\t\t\tconst message = error instanceof Error ? error.message : 'Worker RPC failed'\n\t\t\t\tconst msg: RpcResponseMessage = {\n\t\t\t\t\ttype: RPC_RESPONSE,\n\t\t\t\t\trequestId: data.requestId,\n\t\t\t\t\tresponse: {\n\t\t\t\t\t\tid: data.request.id,\n\t\t\t\t\t\ttype: 'error',\n\t\t\t\t\t\tmessage,\n\t\t\t\t\t\tcode: 'LEADER_RPC_ERROR',\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t\tchannel.postMessage(msg)\n\t\t\t})\n\t}\n\n\tchannel.addEventListener('message', onMessage)\n\treturn () => {\n\t\tchannel.removeEventListener('message', onMessage)\n\t\tchannel.close()\n\t}\n}\n\n/**\n * Follower tab: proxy {@link WorkerBridge} over BroadcastChannel to the leader.\n */\nexport class FollowerBroadcastBridge implements WorkerBridge {\n\tprivate readonly channel: BroadcastChannel\n\tprivate readonly pending = new Map<\n\t\tstring,\n\t\t{ resolve: (r: WorkerResponse) => void; reject: (e: Error) => void }\n\t>()\n\tprivate readonly timeoutMs: number\n\tprivate readonly livenessProbeMs: number\n\tprivate terminated = false\n\n\tconstructor(channelName: string, timeoutMs = 30000, livenessProbeMs = DEFAULT_LIVENESS_PROBE_MS) {\n\t\tthis.timeoutMs = timeoutMs\n\t\tthis.livenessProbeMs = Math.min(livenessProbeMs, timeoutMs)\n\t\tthis.channel = new BroadcastChannel(channelName)\n\t\tthis.channel.addEventListener('message', (event: MessageEvent<RpcResponseMessage>) => {\n\t\t\tconst data = event.data\n\t\t\tif (data?.type !== RPC_RESPONSE) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst entry = this.pending.get(data.requestId)\n\t\t\tif (entry) {\n\t\t\t\tthis.pending.delete(data.requestId)\n\t\t\t\tentry.resolve(data.response)\n\t\t\t}\n\t\t})\n\t}\n\n\t/**\n\t * Readiness handshake: resolves `true` as soon as a live leader answers a ping,\n\t * or `false` if none answers within the budget. The adapter calls this before\n\t * its first RPC so a follower created before a leader relay is live retries the\n\t * handshake instead of firing into the void and waiting out the full timeout.\n\t */\n\tasync waitForLeader(timeoutMs = 3000, attempts = 3): Promise<boolean> {\n\t\tconst perAttempt = Math.max(50, Math.floor(timeoutMs / Math.max(1, attempts)))\n\t\tfor (let attempt = 0; attempt < attempts; attempt += 1) {\n\t\t\tif (this.terminated) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tif (await this.pingLeader(perAttempt)) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t\treturn false\n\t}\n\n\t/** Sends one liveness ping and resolves whether a leader answered in time. */\n\tprivate pingLeader(timeoutMs: number): Promise<boolean> {\n\t\tif (this.terminated) {\n\t\t\treturn Promise.resolve(false)\n\t\t}\n\t\treturn new Promise<boolean>((resolve) => {\n\t\t\tconst onPong = (event: MessageEvent<{ type: string }>): void => {\n\t\t\t\tif (event.data?.type === LEADER_PONG) {\n\t\t\t\t\tcleanup()\n\t\t\t\t\tresolve(true)\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst cleanup = (): void => {\n\t\t\t\tclearTimeout(timer)\n\t\t\t\tthis.channel.removeEventListener('message', onPong)\n\t\t\t}\n\t\t\tconst timer = setTimeout(() => {\n\t\t\t\tcleanup()\n\t\t\t\tresolve(false)\n\t\t\t}, timeoutMs)\n\t\t\tthis.channel.addEventListener('message', onPong)\n\t\t\tthis.channel.postMessage({ type: LEADER_PING })\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: 'Follower bridge terminated',\n\t\t\t\tcode: 'BRIDGE_TERMINATED',\n\t\t\t}\n\t\t}\n\n\t\tconst requestId = crypto.randomUUID()\n\t\tconst msg: RpcRequestMessage = { type: RPC_REQUEST, requestId, request }\n\n\t\treturn new Promise<WorkerResponse>((resolve, reject) => {\n\t\t\tconst settle = (fn: () => void): void => {\n\t\t\t\tclearTimeout(timer)\n\t\t\t\tclearTimeout(probe)\n\t\t\t\tthis.pending.delete(requestId)\n\t\t\t\tfn()\n\t\t\t}\n\n\t\t\tconst timer = setTimeout(() => {\n\t\t\t\tsettle(() => reject(new WorkerTimeoutError(`follower-rpc:${request.type}`, this.timeoutMs)))\n\t\t\t}, this.timeoutMs)\n\n\t\t\t// Liveness watchdog: if the request stalls, probe the leader. A confirmed\n\t\t\t// absent leader fails fast (NoLeaderError) instead of waiting out the full\n\t\t\t// timeout; a slow-but-alive leader keeps its remaining time.\n\t\t\tconst probe = setTimeout(() => {\n\t\t\t\tvoid this.pingLeader(this.livenessProbeMs).then((alive) => {\n\t\t\t\t\tif (!alive && this.pending.has(requestId)) {\n\t\t\t\t\t\tsettle(() => reject(new NoLeaderError(`follower-rpc:${request.type}`)))\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t}, this.livenessProbeMs)\n\n\t\t\tthis.pending.set(requestId, {\n\t\t\t\tresolve: (response) => settle(() => resolve(response)),\n\t\t\t\treject: (error) => settle(() => reject(error)),\n\t\t\t})\n\n\t\t\tthis.channel.postMessage(msg)\n\t\t})\n\t}\n\n\tterminate(): void {\n\t\tif (this.terminated) {\n\t\t\treturn\n\t\t}\n\t\tthis.terminated = true\n\t\tthis.channel.close()\n\t\tfor (const [, entry] of this.pending) {\n\t\t\tentry.reject(new Error('Follower bridge terminated'))\n\t\t}\n\t\tthis.pending.clear()\n\t}\n}\n"],"mappings":"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","names":[]}
@@ -3,7 +3,7 @@ import {
3
3
  encodeRichtextFieldsForOpData,
4
4
  serializeOperation,
5
5
  serializeRecord
6
- } from "./chunk-6NKLDBTY.js";
6
+ } from "./chunk-GSJ7EEUC.js";
7
7
  import {
8
8
  QueryError,
9
9
  RecordNotFoundError
@@ -684,4 +684,4 @@ export {
684
684
  validateUpdateStateMachine,
685
685
  executeUpdate
686
686
  };
687
- //# sourceMappingURL=chunk-UXRWVXBP.js.map
687
+ //# sourceMappingURL=chunk-RL43UYQZ.js.map
@@ -3,13 +3,13 @@ import {
3
3
  exportAudit,
4
4
  readAuditExportManifest,
5
5
  verifyAuditExportChecksum
6
- } from "./chunk-RCTG3IJG.js";
6
+ } from "./chunk-CYO3YKFL.js";
7
7
  import "./chunk-MYSJN3DF.js";
8
- import "./chunk-6NKLDBTY.js";
8
+ import "./chunk-GSJ7EEUC.js";
9
9
  export {
10
10
  decodeAuditExport,
11
11
  exportAudit,
12
12
  readAuditExportManifest,
13
13
  verifyAuditExportChecksum
14
14
  };
15
- //# sourceMappingURL=export-audit-MT2LTMIN.js.map
15
+ //# sourceMappingURL=export-audit-ODGQTQIK.js.map
package/dist/index.cjs CHANGED
@@ -69,6 +69,7 @@ function encodeRichtext(value) {
69
69
  }
70
70
  if (typeof value === "string") {
71
71
  const doc = new Y.Doc();
72
+ doc.clientID = 0;
72
73
  doc.getText(TEXT_KEY).insert(0, value);
73
74
  return Y.encodeStateAsUpdate(doc);
74
75
  }
@@ -1198,6 +1199,7 @@ function buildDeleteSql(table, withAge) {
1198
1199
  var import_core3 = require("@korajs/core");
1199
1200
  var LAST_ACKED_SERVER_VECTOR_META_KEY = "last_acked_server_vector";
1200
1201
  var DELTA_CURSOR_META_KEY = "delta_cursor";
1202
+ var DELIVERY_WATERMARK_META_KEY = "delivery_watermark";
1201
1203
  function serializeVersionVectorToMeta(vector) {
1202
1204
  const record = {};
1203
1205
  for (const [nodeId, seq] of vector) {
@@ -1270,6 +1272,45 @@ async function saveDeltaCursor(adapter, cursor) {
1270
1272
  cursor
1271
1273
  ]);
1272
1274
  }
1275
+ function deliveryWatermarkKey(signature) {
1276
+ return signature === "" ? DELIVERY_WATERMARK_META_KEY : `${DELIVERY_WATERMARK_META_KEY}:${signature}`;
1277
+ }
1278
+ async function loadDeliveryWatermark(adapter, signature) {
1279
+ const rows = await adapter.query("SELECT value FROM _kora_meta WHERE key = ?", [
1280
+ deliveryWatermarkKey(signature)
1281
+ ]);
1282
+ const value = rows[0]?.value;
1283
+ if (value === void 0 || value === null) {
1284
+ return 0;
1285
+ }
1286
+ const parsed = Number(value);
1287
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : 0;
1288
+ }
1289
+ async function saveDeliveryWatermark(adapter, signature, watermark) {
1290
+ await adapter.execute("INSERT OR REPLACE INTO _kora_meta (key, value) VALUES (?, ?)", [
1291
+ deliveryWatermarkKey(signature),
1292
+ String(watermark)
1293
+ ]);
1294
+ }
1295
+ async function deleteDeliveryWatermark(adapter, signature) {
1296
+ await adapter.execute("DELETE FROM _kora_meta WHERE key = ?", [deliveryWatermarkKey(signature)]);
1297
+ }
1298
+ async function loadAllDeliveryWatermarks(adapter) {
1299
+ const rows = await adapter.query(
1300
+ "SELECT key, value FROM _kora_meta WHERE key = ? OR key LIKE ?",
1301
+ [DELIVERY_WATERMARK_META_KEY, `${DELIVERY_WATERMARK_META_KEY}:%`]
1302
+ );
1303
+ const result = {};
1304
+ const prefix = `${DELIVERY_WATERMARK_META_KEY}:`;
1305
+ for (const row of rows) {
1306
+ const signature = row.key === DELIVERY_WATERMARK_META_KEY ? "" : row.key.slice(prefix.length);
1307
+ const parsed = Number(row.value);
1308
+ if (Number.isFinite(parsed) && parsed >= 0) {
1309
+ result[signature] = parsed;
1310
+ }
1311
+ }
1312
+ return result;
1313
+ }
1273
1314
 
1274
1315
  // src/store/store.ts
1275
1316
  var import_core23 = require("@korajs/core");
@@ -3891,7 +3932,8 @@ var Store = class {
3891
3932
  throw new OptimisticLockError(collection, op.recordId);
3892
3933
  }
3893
3934
  };
3894
- if (op.type === "insert" && materializeSource) {
3935
+ if (options?.logOnly) {
3936
+ } else if (op.type === "insert" && materializeSource) {
3895
3937
  const serializedData = serializeRecord(materializeSource, definition.fields);
3896
3938
  const existing2 = await tx.query(
3897
3939
  `SELECT _updated_at, _version, _field_versions, _deleted FROM ${collection} WHERE id = ?`,
@@ -3913,7 +3955,7 @@ var Store = class {
3913
3955
  };
3914
3956
  const insertQuery = buildInsertQuery(collection, record);
3915
3957
  await tx.execute(insertQuery.sql, insertQuery.params);
3916
- await this.foldOrphanedOperations(tx, collection, definition, op.recordId);
3958
+ await this.foldOrphanedOperations(tx, collection, definition, op.recordId, op);
3917
3959
  } else if (row._deleted === 1) {
3918
3960
  if (isIncomingNewerThanRow(materializeTimestamp, row)) {
3919
3961
  const fieldChanges = {
@@ -4065,7 +4107,7 @@ var Store = class {
4065
4107
  * necessarily an orphan (rows are soft-deleted, never removed, so a row
4066
4108
  * that is absent now was never materialized).
4067
4109
  */
4068
- async foldOrphanedOperations(tx, collection, definition, recordId) {
4110
+ async foldOrphanedOperations(tx, collection, definition, recordId, insertOp) {
4069
4111
  const orphanRows = await tx.query(
4070
4112
  `SELECT * FROM ${(0, import_core23.quoteIdent)(`_kora_ops_${collection}`)} WHERE record_id = ? AND type IN ('update', 'delete')`,
4071
4113
  [recordId]
@@ -4114,6 +4156,37 @@ var Store = class {
4114
4156
  await tx.execute(query.sql, query.params);
4115
4157
  }
4116
4158
  }
4159
+ const atomicFields = /* @__PURE__ */ new Set();
4160
+ for (const orphan of orphans) {
4161
+ if (orphan.type === "update" && orphan.atomicOps) {
4162
+ for (const field of Object.keys(orphan.atomicOps)) {
4163
+ atomicFields.add(field);
4164
+ }
4165
+ }
4166
+ }
4167
+ if (atomicFields.size > 0) {
4168
+ const ordered = [insertOp, ...orphans].sort(
4169
+ (a, b) => import_core23.HybridLogicalClock.compare(a.timestamp, b.timestamp)
4170
+ );
4171
+ const folded = (0, import_core23.replayOperationsForRecord)(ordered);
4172
+ if (folded) {
4173
+ const composed = {};
4174
+ for (const field of atomicFields) {
4175
+ if (field in folded) {
4176
+ composed[field] = folded[field];
4177
+ }
4178
+ }
4179
+ const serialized = serializeRecord(composed, definition.fields);
4180
+ const cols = Object.keys(serialized);
4181
+ if (cols.length > 0) {
4182
+ const setClause = cols.map((c) => `${(0, import_core23.quoteIdent)(c)} = ?`).join(", ");
4183
+ await tx.execute(`UPDATE ${(0, import_core23.quoteIdent)(collection)} SET ${setClause} WHERE id = ?`, [
4184
+ ...cols.map((c) => serialized[c]),
4185
+ recordId
4186
+ ]);
4187
+ }
4188
+ }
4189
+ }
4117
4190
  }
4118
4191
  /**
4119
4192
  * Get operations from a node within a sequence number range.
@@ -4327,6 +4400,35 @@ var Store = class {
4327
4400
  this.ensureOpen();
4328
4401
  await saveDeltaCursor(this.adapter, cursor);
4329
4402
  }
4403
+ /**
4404
+ * Load the persisted delivery watermark for a view signature (0 when none recorded).
4405
+ * Defaults to the unfiltered view so existing single-watermark reads keep working.
4406
+ */
4407
+ async loadDeliveryWatermark(signature = "") {
4408
+ this.ensureOpen();
4409
+ return loadDeliveryWatermark(this.adapter, signature);
4410
+ }
4411
+ /**
4412
+ * Persist the delivery watermark for a view signature so it survives a client restart.
4413
+ */
4414
+ async saveDeliveryWatermark(signature, watermark) {
4415
+ this.ensureOpen();
4416
+ await saveDeliveryWatermark(this.adapter, signature, watermark);
4417
+ }
4418
+ /**
4419
+ * Load every persisted view watermark, keyed by signature.
4420
+ */
4421
+ async loadAllDeliveryWatermarks() {
4422
+ this.ensureOpen();
4423
+ return loadAllDeliveryWatermarks(this.adapter);
4424
+ }
4425
+ /**
4426
+ * Delete a persisted view watermark (safe: the view back-fills from 0 when next visited).
4427
+ */
4428
+ async deleteDeliveryWatermark(signature) {
4429
+ this.ensureOpen();
4430
+ await deleteDeliveryWatermark(this.adapter, signature);
4431
+ }
4330
4432
  /**
4331
4433
  * Local operations not yet reflected on the server version vector.
4332
4434
  */
@@ -4417,6 +4519,21 @@ var Store = class {
4417
4519
  }
4418
4520
  return latest;
4419
4521
  }
4522
+ /**
4523
+ * All operations for a record, sorted in HLC total order (wallTime, logical,
4524
+ * nodeId). Used by the apply pipeline to materialize atomic-op fields by folding
4525
+ * the record's log, matching the server's materialization exactly.
4526
+ */
4527
+ async getOperationsForRecord(collection, recordId) {
4528
+ this.ensureOpen();
4529
+ const rows = await this.adapter.query(
4530
+ `SELECT * FROM ${(0, import_core23.quoteIdent)(`_kora_ops_${collection}`)} WHERE record_id = ?`,
4531
+ [recordId]
4532
+ );
4533
+ const ops = rows.map((row) => deserializeOperationWithCollection(row, collection));
4534
+ ops.sort((a, b) => import_core23.HybridLogicalClock.compare(a.timestamp, b.timestamp));
4535
+ return ops;
4536
+ }
4420
4537
  /** Expose the subscription manager for direct access (e.g., by QueryBuilder) */
4421
4538
  getSubscriptionManager() {
4422
4539
  return this.subscriptionManager;