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

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.cjs.map +1 -1
  2. package/dist/adapters/better-sqlite3.js +1 -1
  3. package/dist/adapters/indexeddb.cjs +196 -39
  4. package/dist/adapters/indexeddb.cjs.map +1 -1
  5. package/dist/adapters/indexeddb.d.cts +1 -1
  6. package/dist/adapters/indexeddb.d.ts +1 -1
  7. package/dist/adapters/indexeddb.js +15 -8
  8. package/dist/adapters/indexeddb.js.map +1 -1
  9. package/dist/adapters/sqlite-wasm-shared-host.cjs +80 -53
  10. package/dist/adapters/sqlite-wasm-shared-host.cjs.map +1 -1
  11. package/dist/adapters/sqlite-wasm-shared-host.d.cts +63 -1
  12. package/dist/adapters/sqlite-wasm-shared-host.d.ts +63 -1
  13. package/dist/adapters/sqlite-wasm-shared-host.js +56 -52
  14. package/dist/adapters/sqlite-wasm-shared-host.js.map +1 -1
  15. package/dist/adapters/sqlite-wasm-worker.cjs +25 -3
  16. package/dist/adapters/sqlite-wasm-worker.cjs.map +1 -1
  17. package/dist/adapters/sqlite-wasm-worker.js +25 -3
  18. package/dist/adapters/sqlite-wasm-worker.js.map +1 -1
  19. package/dist/adapters/sqlite-wasm.cjs +186 -34
  20. package/dist/adapters/sqlite-wasm.cjs.map +1 -1
  21. package/dist/adapters/sqlite-wasm.d.cts +29 -3
  22. package/dist/adapters/sqlite-wasm.d.ts +29 -3
  23. package/dist/adapters/sqlite-wasm.js +3 -3
  24. package/dist/{backup-ZV4Y424B.js → backup-IQE4QC7Q.js} +2 -2
  25. package/dist/{chunk-RBPVEAVM.js → chunk-2ZRQGFEF.js} +179 -36
  26. package/dist/chunk-2ZRQGFEF.js.map +1 -0
  27. package/dist/{chunk-IOEFNHHP.js → chunk-COJSQBXW.js} +2 -2
  28. package/dist/{chunk-X57MWW35.js → chunk-OCOGQRHW.js} +12 -9
  29. package/dist/chunk-OCOGQRHW.js.map +1 -0
  30. package/dist/{chunk-FFYA7J5H.js → chunk-RCTG3IJG.js} +3 -2
  31. package/dist/chunk-RCTG3IJG.js.map +1 -0
  32. package/dist/{chunk-QL2XVZRN.js → chunk-TUR7RGEP.js} +12 -1
  33. package/dist/{chunk-QL2XVZRN.js.map → chunk-TUR7RGEP.js.map} +1 -1
  34. package/dist/{chunk-ZLWAYQEQ.js → chunk-UXRWVXBP.js} +32 -28
  35. package/dist/chunk-UXRWVXBP.js.map +1 -0
  36. package/dist/{export-audit-AJ7RORNK.js → export-audit-MT2LTMIN.js} +2 -2
  37. package/dist/index.cjs +167 -141
  38. package/dist/index.cjs.map +1 -1
  39. package/dist/index.d.cts +10 -1
  40. package/dist/index.d.ts +10 -1
  41. package/dist/index.js +51 -41
  42. package/dist/index.js.map +1 -1
  43. package/dist/internal.cjs +45 -40
  44. package/dist/internal.cjs.map +1 -1
  45. package/dist/internal.js +2 -2
  46. package/dist/{sqlite-wasm-channel-DUDPclTE.d.cts → sqlite-wasm-channel-CNHRf-m2.d.cts} +1 -1
  47. package/dist/{sqlite-wasm-channel-DUDPclTE.d.ts → sqlite-wasm-channel-CNHRf-m2.d.ts} +1 -1
  48. package/dist/sqlite-wasm-channel-NGSBJR44.js +12 -0
  49. package/package.json +2 -2
  50. package/dist/chunk-FFYA7J5H.js.map +0 -1
  51. package/dist/chunk-RBPVEAVM.js.map +0 -1
  52. package/dist/chunk-X57MWW35.js.map +0 -1
  53. package/dist/chunk-ZLWAYQEQ.js.map +0 -1
  54. package/dist/sqlite-wasm-channel-H43KEZLM.js +0 -12
  55. /package/dist/{backup-ZV4Y424B.js.map → backup-IQE4QC7Q.js.map} +0 -0
  56. /package/dist/{chunk-IOEFNHHP.js.map → chunk-COJSQBXW.js.map} +0 -0
  57. /package/dist/{export-audit-AJ7RORNK.js.map → export-audit-MT2LTMIN.js.map} +0 -0
  58. /package/dist/{sqlite-wasm-channel-H43KEZLM.js.map → sqlite-wasm-channel-NGSBJR44.js.map} +0 -0
@@ -1,68 +1,95 @@
1
1
  "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
2
19
 
3
20
  // src/adapters/sqlite-wasm-shared-host.ts
21
+ var sqlite_wasm_shared_host_exports = {};
22
+ __export(sqlite_wasm_shared_host_exports, {
23
+ createSharedWorkerHost: () => createSharedWorkerHost
24
+ });
25
+ module.exports = __toCommonJS(sqlite_wasm_shared_host_exports);
4
26
  var SW_REQUEST = "kora-sw-request";
5
27
  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;
28
+ function createSharedWorkerHost(options) {
29
+ const pools = /* @__PURE__ */ new Map();
30
+ function getPool(dbName, workerUrl) {
31
+ const existing = pools.get(dbName);
32
+ if (existing) {
33
+ return existing;
19
34
  }
20
- entry.pending.delete(response.id);
21
- const port = findPortForRequest(requestId);
22
- if (port) {
35
+ const worker = options.spawnWorker(workerUrl);
36
+ const entry = { worker, nextInnerId: 1, pending: /* @__PURE__ */ new Map() };
37
+ worker.onmessage = (event) => {
38
+ const response = event.data;
39
+ const waiting = entry.pending.get(response.id);
40
+ if (!waiting) {
41
+ return;
42
+ }
43
+ entry.pending.delete(response.id);
23
44
  const msg = {
24
45
  type: SW_RESPONSE,
25
- requestId,
46
+ requestId: waiting.requestId,
26
47
  response
27
48
  };
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;
49
+ waiting.port.postMessage(msg);
50
+ };
51
+ pools.set(dbName, entry);
52
+ return entry;
43
53
  }
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);
54
+ function connect(port) {
55
+ port.start();
56
+ port.addEventListener("message", (event) => {
57
+ const data = event.data;
58
+ if (data?.type !== SW_REQUEST) {
59
+ return;
60
+ }
61
+ const entry = getPool(data.dbName, data.workerUrl);
62
+ const innerId = entry.nextInnerId;
63
+ entry.nextInnerId += 1;
64
+ entry.pending.set(innerId, { requestId: data.requestId, port });
65
+ entry.worker.postMessage({ ...data.request, id: innerId });
66
+ });
67
+ port.addEventListener("messageerror", () => {
68
+ for (const entry of pools.values()) {
69
+ for (const [innerId, waiting] of entry.pending) {
70
+ if (waiting.port === port) {
71
+ entry.pending.delete(innerId);
72
+ }
73
+ }
74
+ }
75
+ });
76
+ }
77
+ return { connect };
78
+ }
79
+ if (typeof self !== "undefined" && "onconnect" in self) {
80
+ const host = createSharedWorkerHost({
81
+ spawnWorker: (workerUrl) => new Worker(workerUrl, { type: "module" })
56
82
  });
57
- port.addEventListener("messageerror", () => {
58
- const ids = portRequestIds.get(port);
59
- if (!ids) {
83
+ self.onconnect = (event) => {
84
+ const port = event.ports[0];
85
+ if (!port) {
60
86
  return;
61
87
  }
62
- for (const requestId of ids) {
63
- requestPort.delete(requestId);
64
- }
65
- portRequestIds.delete(port);
66
- });
67
- };
88
+ host.connect(port);
89
+ };
90
+ }
91
+ // Annotate the CommonJS export names for ESM import in node:
92
+ 0 && (module.exports = {
93
+ createSharedWorkerHost
94
+ });
68
95
  //# sourceMappingURL=sqlite-wasm-shared-host.cjs.map
@@ -1 +1 @@
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
+ {"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\n/**\n * Minimal structural view of the inner SQLite DedicatedWorker the host owns.\n * Declared here (rather than using the DOM `Worker` type directly) so the host\n * routing can be unit-tested with a mock worker.\n */\nexport interface HostWorker {\n\tpostMessage(message: WorkerRequest): void\n\tonmessage: ((event: { data: WorkerResponse }) => void) | null\n\tterminate?(): void\n}\n\n/** Minimal structural view of a connected client `MessagePort`. */\nexport interface HostPort {\n\tstart(): void\n\tpostMessage(message: SharedWorkerRpcResponse): void\n\taddEventListener(\n\t\ttype: 'message' | 'messageerror',\n\t\thandler: (event: { data: unknown }) => void,\n\t): void\n}\n\nexport interface SharedWorkerHostOptions {\n\t/** Spawns the inner SQLite worker for a database. Injectable for tests. */\n\tspawnWorker: (workerUrl: string) => HostWorker\n}\n\ninterface WorkerEntry {\n\tworker: HostWorker\n\t/**\n\t * Monotonic id assigned to each request FORWARDED to the inner worker. The\n\t * adapter always sends `request.id === 0` (uniqueness is a bridge concern), and\n\t * every tab shares one inner worker, so the host must mint its own unique id per\n\t * request or concurrent requests collide on the correlation key. This mirrors\n\t * `WebWorkerBridge`, which reassigns `id` before posting to its worker.\n\t */\n\tnextInnerId: number\n\t/** innerId -> the client waiting for that response. */\n\tpending: Map<number, { requestId: string; port: HostPort }>\n}\n\nexport interface SharedWorkerHost {\n\t/** Wire a newly connected client port into the host. */\n\tconnect(port: HostPort): void\n}\n\n/**\n * Creates the SharedWorker host routing core: one inner SQLite worker per\n * `dbName`, fanning requests from every connected tab through it and routing each\n * response back to the exact requester.\n *\n * Correlation is keyed on a host-minted inner id, never on the incoming\n * `request.id` (which is `0` for every request from every tab). This is what keeps\n * concurrent and multi-tab requests from resolving each other's responses.\n */\nexport function createSharedWorkerHost(options: SharedWorkerHostOptions): SharedWorkerHost {\n\tconst pools = new Map<string, WorkerEntry>()\n\n\tfunction getPool(dbName: string, workerUrl: string): WorkerEntry {\n\t\tconst existing = pools.get(dbName)\n\t\tif (existing) {\n\t\t\treturn existing\n\t\t}\n\n\t\tconst worker = options.spawnWorker(workerUrl)\n\t\tconst entry: WorkerEntry = { worker, nextInnerId: 1, pending: new Map() }\n\n\t\tworker.onmessage = (event: { data: WorkerResponse }): void => {\n\t\t\tconst response = event.data\n\t\t\tconst waiting = entry.pending.get(response.id)\n\t\t\tif (!waiting) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tentry.pending.delete(response.id)\n\t\t\tconst msg: SharedWorkerRpcResponse = {\n\t\t\t\ttype: SW_RESPONSE,\n\t\t\t\trequestId: waiting.requestId,\n\t\t\t\tresponse,\n\t\t\t}\n\t\t\twaiting.port.postMessage(msg)\n\t\t}\n\n\t\tpools.set(dbName, entry)\n\t\treturn entry\n\t}\n\n\tfunction connect(port: HostPort): void {\n\t\tport.start()\n\n\t\tport.addEventListener('message', (event: { data: unknown }): void => {\n\t\t\tconst data = event.data as SharedWorkerRpcRequest | undefined\n\t\t\tif (data?.type !== SW_REQUEST) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconst entry = getPool(data.dbName, data.workerUrl)\n\t\t\tconst innerId = entry.nextInnerId\n\t\t\tentry.nextInnerId += 1\n\t\t\tentry.pending.set(innerId, { requestId: data.requestId, port })\n\t\t\t// Reassign a unique inner id. The client bridge correlates on the outer\n\t\t\t// requestId, so rewriting request.id here is transparent to the caller.\n\t\t\tentry.worker.postMessage({ ...data.request, id: innerId })\n\t\t})\n\n\t\tport.addEventListener('messageerror', (): void => {\n\t\t\t// Best-effort cleanup: drop any in-flight requests routed to this port so a\n\t\t\t// disconnected tab does not leak pending entries.\n\t\t\tfor (const entry of pools.values()) {\n\t\t\t\tfor (const [innerId, waiting] of entry.pending) {\n\t\t\t\t\tif (waiting.port === port) {\n\t\t\t\t\t\tentry.pending.delete(innerId)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n\n\treturn { connect }\n}\n\n// Wire the host to the SharedWorker global only when running as an actual\n// SharedWorker. Guarding this keeps the module importable in Node test runtimes,\n// where `createSharedWorkerHost` is unit-tested directly with mock ports/workers.\ndeclare const self: SharedWorkerGlobalScope\nif (typeof self !== 'undefined' && 'onconnect' in self) {\n\tconst host = createSharedWorkerHost({\n\t\tspawnWorker: (workerUrl: string): HostWorker =>\n\t\t\tnew Worker(workerUrl, { type: 'module' }) as unknown as HostWorker,\n\t})\n\n\tself.onconnect = (event: MessageEvent): void => {\n\t\tconst port = event.ports[0]\n\t\tif (!port) {\n\t\t\treturn\n\t\t}\n\t\thost.connect(port as unknown as HostPort)\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAmBA,IAAM,aAAa;AACnB,IAAM,cAAc;AAsEb,SAAS,uBAAuB,SAAoD;AAC1F,QAAM,QAAQ,oBAAI,IAAyB;AAE3C,WAAS,QAAQ,QAAgB,WAAgC;AAChE,UAAM,WAAW,MAAM,IAAI,MAAM;AACjC,QAAI,UAAU;AACb,aAAO;AAAA,IACR;AAEA,UAAM,SAAS,QAAQ,YAAY,SAAS;AAC5C,UAAM,QAAqB,EAAE,QAAQ,aAAa,GAAG,SAAS,oBAAI,IAAI,EAAE;AAExE,WAAO,YAAY,CAAC,UAA0C;AAC7D,YAAM,WAAW,MAAM;AACvB,YAAM,UAAU,MAAM,QAAQ,IAAI,SAAS,EAAE;AAC7C,UAAI,CAAC,SAAS;AACb;AAAA,MACD;AACA,YAAM,QAAQ,OAAO,SAAS,EAAE;AAChC,YAAM,MAA+B;AAAA,QACpC,MAAM;AAAA,QACN,WAAW,QAAQ;AAAA,QACnB;AAAA,MACD;AACA,cAAQ,KAAK,YAAY,GAAG;AAAA,IAC7B;AAEA,UAAM,IAAI,QAAQ,KAAK;AACvB,WAAO;AAAA,EACR;AAEA,WAAS,QAAQ,MAAsB;AACtC,SAAK,MAAM;AAEX,SAAK,iBAAiB,WAAW,CAAC,UAAmC;AACpE,YAAM,OAAO,MAAM;AACnB,UAAI,MAAM,SAAS,YAAY;AAC9B;AAAA,MACD;AAEA,YAAM,QAAQ,QAAQ,KAAK,QAAQ,KAAK,SAAS;AACjD,YAAM,UAAU,MAAM;AACtB,YAAM,eAAe;AACrB,YAAM,QAAQ,IAAI,SAAS,EAAE,WAAW,KAAK,WAAW,KAAK,CAAC;AAG9D,YAAM,OAAO,YAAY,EAAE,GAAG,KAAK,SAAS,IAAI,QAAQ,CAAC;AAAA,IAC1D,CAAC;AAED,SAAK,iBAAiB,gBAAgB,MAAY;AAGjD,iBAAW,SAAS,MAAM,OAAO,GAAG;AACnC,mBAAW,CAAC,SAAS,OAAO,KAAK,MAAM,SAAS;AAC/C,cAAI,QAAQ,SAAS,MAAM;AAC1B,kBAAM,QAAQ,OAAO,OAAO;AAAA,UAC7B;AAAA,QACD;AAAA,MACD;AAAA,IACD,CAAC;AAAA,EACF;AAEA,SAAO,EAAE,QAAQ;AAClB;AAMA,IAAI,OAAO,SAAS,eAAe,eAAe,MAAM;AACvD,QAAM,OAAO,uBAAuB;AAAA,IACnC,aAAa,CAAC,cACb,IAAI,OAAO,WAAW,EAAE,MAAM,SAAS,CAAC;AAAA,EAC1C,CAAC;AAED,OAAK,YAAY,CAAC,UAA8B;AAC/C,UAAM,OAAO,MAAM,MAAM,CAAC;AAC1B,QAAI,CAAC,MAAM;AACV;AAAA,IACD;AACA,SAAK,QAAQ,IAA2B;AAAA,EACzC;AACD;","names":[]}
@@ -1,2 +1,64 @@
1
+ import { a as WorkerResponse, b as WorkerRequest } from '../sqlite-wasm-channel-CNHRf-m2.cjs';
1
2
 
2
- export { }
3
+ /**
4
+ * SharedWorker host: one DedicatedWorker (SQLite WASM) per `dbName` per browser origin.
5
+ *
6
+ * Bundle this file separately and pass its URL as `store.sharedWorkerUrl` alongside `workerUrl`.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * createApp({
11
+ * store: {
12
+ * workerUrl: '/sqlite-wasm-worker.js',
13
+ * sharedWorkerUrl: '/sqlite-wasm-shared-host.js',
14
+ * },
15
+ * })
16
+ * ```
17
+ */
18
+
19
+ declare const SW_RESPONSE = "kora-sw-response";
20
+ interface SharedWorkerRpcResponse {
21
+ type: typeof SW_RESPONSE;
22
+ requestId: string;
23
+ response: WorkerResponse;
24
+ }
25
+ /**
26
+ * Minimal structural view of the inner SQLite DedicatedWorker the host owns.
27
+ * Declared here (rather than using the DOM `Worker` type directly) so the host
28
+ * routing can be unit-tested with a mock worker.
29
+ */
30
+ interface HostWorker {
31
+ postMessage(message: WorkerRequest): void;
32
+ onmessage: ((event: {
33
+ data: WorkerResponse;
34
+ }) => void) | null;
35
+ terminate?(): void;
36
+ }
37
+ /** Minimal structural view of a connected client `MessagePort`. */
38
+ interface HostPort {
39
+ start(): void;
40
+ postMessage(message: SharedWorkerRpcResponse): void;
41
+ addEventListener(type: 'message' | 'messageerror', handler: (event: {
42
+ data: unknown;
43
+ }) => void): void;
44
+ }
45
+ interface SharedWorkerHostOptions {
46
+ /** Spawns the inner SQLite worker for a database. Injectable for tests. */
47
+ spawnWorker: (workerUrl: string) => HostWorker;
48
+ }
49
+ interface SharedWorkerHost {
50
+ /** Wire a newly connected client port into the host. */
51
+ connect(port: HostPort): void;
52
+ }
53
+ /**
54
+ * Creates the SharedWorker host routing core: one inner SQLite worker per
55
+ * `dbName`, fanning requests from every connected tab through it and routing each
56
+ * response back to the exact requester.
57
+ *
58
+ * Correlation is keyed on a host-minted inner id, never on the incoming
59
+ * `request.id` (which is `0` for every request from every tab). This is what keeps
60
+ * concurrent and multi-tab requests from resolving each other's responses.
61
+ */
62
+ declare function createSharedWorkerHost(options: SharedWorkerHostOptions): SharedWorkerHost;
63
+
64
+ export { type HostPort, type HostWorker, type SharedWorkerHost, type SharedWorkerHostOptions, createSharedWorkerHost };
@@ -1,2 +1,64 @@
1
+ import { a as WorkerResponse, b as WorkerRequest } from '../sqlite-wasm-channel-CNHRf-m2.js';
1
2
 
2
- export { }
3
+ /**
4
+ * SharedWorker host: one DedicatedWorker (SQLite WASM) per `dbName` per browser origin.
5
+ *
6
+ * Bundle this file separately and pass its URL as `store.sharedWorkerUrl` alongside `workerUrl`.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * createApp({
11
+ * store: {
12
+ * workerUrl: '/sqlite-wasm-worker.js',
13
+ * sharedWorkerUrl: '/sqlite-wasm-shared-host.js',
14
+ * },
15
+ * })
16
+ * ```
17
+ */
18
+
19
+ declare const SW_RESPONSE = "kora-sw-response";
20
+ interface SharedWorkerRpcResponse {
21
+ type: typeof SW_RESPONSE;
22
+ requestId: string;
23
+ response: WorkerResponse;
24
+ }
25
+ /**
26
+ * Minimal structural view of the inner SQLite DedicatedWorker the host owns.
27
+ * Declared here (rather than using the DOM `Worker` type directly) so the host
28
+ * routing can be unit-tested with a mock worker.
29
+ */
30
+ interface HostWorker {
31
+ postMessage(message: WorkerRequest): void;
32
+ onmessage: ((event: {
33
+ data: WorkerResponse;
34
+ }) => void) | null;
35
+ terminate?(): void;
36
+ }
37
+ /** Minimal structural view of a connected client `MessagePort`. */
38
+ interface HostPort {
39
+ start(): void;
40
+ postMessage(message: SharedWorkerRpcResponse): void;
41
+ addEventListener(type: 'message' | 'messageerror', handler: (event: {
42
+ data: unknown;
43
+ }) => void): void;
44
+ }
45
+ interface SharedWorkerHostOptions {
46
+ /** Spawns the inner SQLite worker for a database. Injectable for tests. */
47
+ spawnWorker: (workerUrl: string) => HostWorker;
48
+ }
49
+ interface SharedWorkerHost {
50
+ /** Wire a newly connected client port into the host. */
51
+ connect(port: HostPort): void;
52
+ }
53
+ /**
54
+ * Creates the SharedWorker host routing core: one inner SQLite worker per
55
+ * `dbName`, fanning requests from every connected tab through it and routing each
56
+ * response back to the exact requester.
57
+ *
58
+ * Correlation is keyed on a host-minted inner id, never on the incoming
59
+ * `request.id` (which is `0` for every request from every tab). This is what keeps
60
+ * concurrent and multi-tab requests from resolving each other's responses.
61
+ */
62
+ declare function createSharedWorkerHost(options: SharedWorkerHostOptions): SharedWorkerHost;
63
+
64
+ export { type HostPort, type HostWorker, type SharedWorkerHost, type SharedWorkerHostOptions, createSharedWorkerHost };
@@ -1,66 +1,70 @@
1
1
  // src/adapters/sqlite-wasm-shared-host.ts
2
2
  var SW_REQUEST = "kora-sw-request";
3
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;
4
+ function createSharedWorkerHost(options) {
5
+ const pools = /* @__PURE__ */ new Map();
6
+ function getPool(dbName, workerUrl) {
7
+ const existing = pools.get(dbName);
8
+ if (existing) {
9
+ return existing;
17
10
  }
18
- entry.pending.delete(response.id);
19
- const port = findPortForRequest(requestId);
20
- if (port) {
11
+ const worker = options.spawnWorker(workerUrl);
12
+ const entry = { worker, nextInnerId: 1, pending: /* @__PURE__ */ new Map() };
13
+ worker.onmessage = (event) => {
14
+ const response = event.data;
15
+ const waiting = entry.pending.get(response.id);
16
+ if (!waiting) {
17
+ return;
18
+ }
19
+ entry.pending.delete(response.id);
21
20
  const msg = {
22
21
  type: SW_RESPONSE,
23
- requestId,
22
+ requestId: waiting.requestId,
24
23
  response
25
24
  };
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;
25
+ waiting.port.postMessage(msg);
26
+ };
27
+ pools.set(dbName, entry);
28
+ return entry;
41
29
  }
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);
30
+ function connect(port) {
31
+ port.start();
32
+ port.addEventListener("message", (event) => {
33
+ const data = event.data;
34
+ if (data?.type !== SW_REQUEST) {
35
+ return;
36
+ }
37
+ const entry = getPool(data.dbName, data.workerUrl);
38
+ const innerId = entry.nextInnerId;
39
+ entry.nextInnerId += 1;
40
+ entry.pending.set(innerId, { requestId: data.requestId, port });
41
+ entry.worker.postMessage({ ...data.request, id: innerId });
42
+ });
43
+ port.addEventListener("messageerror", () => {
44
+ for (const entry of pools.values()) {
45
+ for (const [innerId, waiting] of entry.pending) {
46
+ if (waiting.port === port) {
47
+ entry.pending.delete(innerId);
48
+ }
49
+ }
50
+ }
51
+ });
52
+ }
53
+ return { connect };
54
+ }
55
+ if (typeof self !== "undefined" && "onconnect" in self) {
56
+ const host = createSharedWorkerHost({
57
+ spawnWorker: (workerUrl) => new Worker(workerUrl, { type: "module" })
54
58
  });
55
- port.addEventListener("messageerror", () => {
56
- const ids = portRequestIds.get(port);
57
- if (!ids) {
59
+ self.onconnect = (event) => {
60
+ const port = event.ports[0];
61
+ if (!port) {
58
62
  return;
59
63
  }
60
- for (const requestId of ids) {
61
- requestPort.delete(requestId);
62
- }
63
- portRequestIds.delete(port);
64
- });
64
+ host.connect(port);
65
+ };
66
+ }
67
+ export {
68
+ createSharedWorkerHost
65
69
  };
66
70
  //# sourceMappingURL=sqlite-wasm-shared-host.js.map
@@ -1 +1 @@
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
+ {"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\n/**\n * Minimal structural view of the inner SQLite DedicatedWorker the host owns.\n * Declared here (rather than using the DOM `Worker` type directly) so the host\n * routing can be unit-tested with a mock worker.\n */\nexport interface HostWorker {\n\tpostMessage(message: WorkerRequest): void\n\tonmessage: ((event: { data: WorkerResponse }) => void) | null\n\tterminate?(): void\n}\n\n/** Minimal structural view of a connected client `MessagePort`. */\nexport interface HostPort {\n\tstart(): void\n\tpostMessage(message: SharedWorkerRpcResponse): void\n\taddEventListener(\n\t\ttype: 'message' | 'messageerror',\n\t\thandler: (event: { data: unknown }) => void,\n\t): void\n}\n\nexport interface SharedWorkerHostOptions {\n\t/** Spawns the inner SQLite worker for a database. Injectable for tests. */\n\tspawnWorker: (workerUrl: string) => HostWorker\n}\n\ninterface WorkerEntry {\n\tworker: HostWorker\n\t/**\n\t * Monotonic id assigned to each request FORWARDED to the inner worker. The\n\t * adapter always sends `request.id === 0` (uniqueness is a bridge concern), and\n\t * every tab shares one inner worker, so the host must mint its own unique id per\n\t * request or concurrent requests collide on the correlation key. This mirrors\n\t * `WebWorkerBridge`, which reassigns `id` before posting to its worker.\n\t */\n\tnextInnerId: number\n\t/** innerId -> the client waiting for that response. */\n\tpending: Map<number, { requestId: string; port: HostPort }>\n}\n\nexport interface SharedWorkerHost {\n\t/** Wire a newly connected client port into the host. */\n\tconnect(port: HostPort): void\n}\n\n/**\n * Creates the SharedWorker host routing core: one inner SQLite worker per\n * `dbName`, fanning requests from every connected tab through it and routing each\n * response back to the exact requester.\n *\n * Correlation is keyed on a host-minted inner id, never on the incoming\n * `request.id` (which is `0` for every request from every tab). This is what keeps\n * concurrent and multi-tab requests from resolving each other's responses.\n */\nexport function createSharedWorkerHost(options: SharedWorkerHostOptions): SharedWorkerHost {\n\tconst pools = new Map<string, WorkerEntry>()\n\n\tfunction getPool(dbName: string, workerUrl: string): WorkerEntry {\n\t\tconst existing = pools.get(dbName)\n\t\tif (existing) {\n\t\t\treturn existing\n\t\t}\n\n\t\tconst worker = options.spawnWorker(workerUrl)\n\t\tconst entry: WorkerEntry = { worker, nextInnerId: 1, pending: new Map() }\n\n\t\tworker.onmessage = (event: { data: WorkerResponse }): void => {\n\t\t\tconst response = event.data\n\t\t\tconst waiting = entry.pending.get(response.id)\n\t\t\tif (!waiting) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tentry.pending.delete(response.id)\n\t\t\tconst msg: SharedWorkerRpcResponse = {\n\t\t\t\ttype: SW_RESPONSE,\n\t\t\t\trequestId: waiting.requestId,\n\t\t\t\tresponse,\n\t\t\t}\n\t\t\twaiting.port.postMessage(msg)\n\t\t}\n\n\t\tpools.set(dbName, entry)\n\t\treturn entry\n\t}\n\n\tfunction connect(port: HostPort): void {\n\t\tport.start()\n\n\t\tport.addEventListener('message', (event: { data: unknown }): void => {\n\t\t\tconst data = event.data as SharedWorkerRpcRequest | undefined\n\t\t\tif (data?.type !== SW_REQUEST) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconst entry = getPool(data.dbName, data.workerUrl)\n\t\t\tconst innerId = entry.nextInnerId\n\t\t\tentry.nextInnerId += 1\n\t\t\tentry.pending.set(innerId, { requestId: data.requestId, port })\n\t\t\t// Reassign a unique inner id. The client bridge correlates on the outer\n\t\t\t// requestId, so rewriting request.id here is transparent to the caller.\n\t\t\tentry.worker.postMessage({ ...data.request, id: innerId })\n\t\t})\n\n\t\tport.addEventListener('messageerror', (): void => {\n\t\t\t// Best-effort cleanup: drop any in-flight requests routed to this port so a\n\t\t\t// disconnected tab does not leak pending entries.\n\t\t\tfor (const entry of pools.values()) {\n\t\t\t\tfor (const [innerId, waiting] of entry.pending) {\n\t\t\t\t\tif (waiting.port === port) {\n\t\t\t\t\t\tentry.pending.delete(innerId)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n\n\treturn { connect }\n}\n\n// Wire the host to the SharedWorker global only when running as an actual\n// SharedWorker. Guarding this keeps the module importable in Node test runtimes,\n// where `createSharedWorkerHost` is unit-tested directly with mock ports/workers.\ndeclare const self: SharedWorkerGlobalScope\nif (typeof self !== 'undefined' && 'onconnect' in self) {\n\tconst host = createSharedWorkerHost({\n\t\tspawnWorker: (workerUrl: string): HostWorker =>\n\t\t\tnew Worker(workerUrl, { type: 'module' }) as unknown as HostWorker,\n\t})\n\n\tself.onconnect = (event: MessageEvent): void => {\n\t\tconst port = event.ports[0]\n\t\tif (!port) {\n\t\t\treturn\n\t\t}\n\t\thost.connect(port as unknown as HostPort)\n\t}\n}\n"],"mappings":";AAmBA,IAAM,aAAa;AACnB,IAAM,cAAc;AAsEb,SAAS,uBAAuB,SAAoD;AAC1F,QAAM,QAAQ,oBAAI,IAAyB;AAE3C,WAAS,QAAQ,QAAgB,WAAgC;AAChE,UAAM,WAAW,MAAM,IAAI,MAAM;AACjC,QAAI,UAAU;AACb,aAAO;AAAA,IACR;AAEA,UAAM,SAAS,QAAQ,YAAY,SAAS;AAC5C,UAAM,QAAqB,EAAE,QAAQ,aAAa,GAAG,SAAS,oBAAI,IAAI,EAAE;AAExE,WAAO,YAAY,CAAC,UAA0C;AAC7D,YAAM,WAAW,MAAM;AACvB,YAAM,UAAU,MAAM,QAAQ,IAAI,SAAS,EAAE;AAC7C,UAAI,CAAC,SAAS;AACb;AAAA,MACD;AACA,YAAM,QAAQ,OAAO,SAAS,EAAE;AAChC,YAAM,MAA+B;AAAA,QACpC,MAAM;AAAA,QACN,WAAW,QAAQ;AAAA,QACnB;AAAA,MACD;AACA,cAAQ,KAAK,YAAY,GAAG;AAAA,IAC7B;AAEA,UAAM,IAAI,QAAQ,KAAK;AACvB,WAAO;AAAA,EACR;AAEA,WAAS,QAAQ,MAAsB;AACtC,SAAK,MAAM;AAEX,SAAK,iBAAiB,WAAW,CAAC,UAAmC;AACpE,YAAM,OAAO,MAAM;AACnB,UAAI,MAAM,SAAS,YAAY;AAC9B;AAAA,MACD;AAEA,YAAM,QAAQ,QAAQ,KAAK,QAAQ,KAAK,SAAS;AACjD,YAAM,UAAU,MAAM;AACtB,YAAM,eAAe;AACrB,YAAM,QAAQ,IAAI,SAAS,EAAE,WAAW,KAAK,WAAW,KAAK,CAAC;AAG9D,YAAM,OAAO,YAAY,EAAE,GAAG,KAAK,SAAS,IAAI,QAAQ,CAAC;AAAA,IAC1D,CAAC;AAED,SAAK,iBAAiB,gBAAgB,MAAY;AAGjD,iBAAW,SAAS,MAAM,OAAO,GAAG;AACnC,mBAAW,CAAC,SAAS,OAAO,KAAK,MAAM,SAAS;AAC/C,cAAI,QAAQ,SAAS,MAAM;AAC1B,kBAAM,QAAQ,OAAO,OAAO;AAAA,UAC7B;AAAA,QACD;AAAA,MACD;AAAA,IACD,CAAC;AAAA,EACF;AAEA,SAAO,EAAE,QAAQ;AAClB;AAMA,IAAI,OAAO,SAAS,eAAe,eAAe,MAAM;AACvD,QAAM,OAAO,uBAAuB;AAAA,IACnC,aAAa,CAAC,cACb,IAAI,OAAO,WAAW,EAAE,MAAM,SAAS,CAAC;AAAA,EAC1C,CAAC;AAED,OAAK,YAAY,CAAC,UAA8B;AAC/C,UAAM,OAAO,MAAM,MAAM,CAAC;AAC1B,QAAI,CAAC,MAAM;AACV;AAAA,IACD;AACA,SAAK,QAAQ,IAA2B;AAAA,EACzC;AACD;","names":[]}
@@ -99,6 +99,7 @@ async function handleOpen(id, ddlStatements, dbName) {
99
99
  );
100
100
  sqlite3Api = sqlite3;
101
101
  let useOpfs = false;
102
+ let fallbackReason;
102
103
  if (sqlite3.installOpfsSAHPoolVfs) {
103
104
  try {
104
105
  const pool = await withTimeout(
@@ -108,9 +109,14 @@ async function handleOpen(id, ddlStatements, dbName) {
108
109
  );
109
110
  db = new pool.OpfsSAHPoolDb(opfsDatabaseFilename(dbName ?? "kora-db"));
110
111
  useOpfs = true;
111
- } catch {
112
- console.warn("[kora] OPFS unavailable, falling back to in-memory SQLite");
112
+ } catch (error) {
113
+ fallbackReason = classifyOpfsFailure(error);
114
+ console.warn(
115
+ `[kora] OPFS unavailable (${fallbackReason}), falling back to in-memory SQLite`
116
+ );
113
117
  }
118
+ } else {
119
+ fallbackReason = "unsupported";
114
120
  }
115
121
  if (!useOpfs) {
116
122
  db = new sqlite3.oo1.DB({ filename: ":memory:" });
@@ -131,7 +137,11 @@ async function handleOpen(id, ddlStatements, dbName) {
131
137
  db?.exec({ sql });
132
138
  }
133
139
  }
134
- sendResponse({ id, type: "success" });
140
+ sendResponse({
141
+ id,
142
+ type: "success",
143
+ data: { persistent: useOpfs, ...fallbackReason ? { fallbackReason } : {} }
144
+ });
135
145
  } catch (error) {
136
146
  sendResponse({
137
147
  id,
@@ -141,6 +151,18 @@ async function handleOpen(id, ddlStatements, dbName) {
141
151
  });
142
152
  }
143
153
  }
154
+ function classifyOpfsFailure(error) {
155
+ const message = error instanceof Error ? error.message : String(error);
156
+ if (/timed out|timeout/i.test(message)) {
157
+ return "timeout";
158
+ }
159
+ if (/lock|already|in use|in-use|NoModificationAllowed|acquire|SyncAccessHandle|being created/i.test(
160
+ message
161
+ )) {
162
+ return "lock-conflict";
163
+ }
164
+ return "unsupported";
165
+ }
144
166
  function handleClose(id) {
145
167
  if (db) {
146
168
  db.close();
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/adapters/sqlite-wasm-worker.ts"],"sourcesContent":["/// <reference lib=\"webworker\" />\n/**\n * Web Worker script for running SQLite WASM.\n *\n * This file is intended to run inside a Web Worker in browsers.\n * It loads @sqlite.org/sqlite-wasm, initializes SQLite with OPFS persistence\n * (falling back to in-memory if unavailable), and processes messages from\n * the main thread via the WorkerRequest/WorkerResponse protocol.\n *\n * This script cannot be tested in Node.js — it is validated in E2E browser tests.\n */\n\nimport type { WorkerRequest, WorkerResponse } from './sqlite-wasm-channel'\n\ninterface SqliteDb {\n\texec(opts: {\n\t\tsql: string\n\t\tbind?: unknown[]\n\t\treturnValue?: string\n\t\trowMode?: string\n\t\tcallback?: (row: Record<string, unknown>) => void\n\t}): void\n\tclose(): void\n\tdeserialize?: (data: Uint8Array) => void\n}\n\ndeclare const self: DedicatedWorkerGlobalScope\n\nlet db: SqliteDb | null = null\nlet sqlite3Api: unknown = null\n\nfunction sendResponse(response: WorkerResponse): void {\n\tself.postMessage(response)\n}\n\nfunction handleExecute(id: number, sql: string, params?: unknown[]): void {\n\tif (!db) {\n\t\tsendResponse({ id, type: 'error', message: 'Database is not open', code: 'DB_NOT_OPEN' })\n\t\treturn\n\t}\n\ttry {\n\t\tdb.exec({ sql, bind: params })\n\t\tsendResponse({ id, type: 'success' })\n\t} catch (error) {\n\t\tsendResponse({ id, type: 'error', message: (error as Error).message, code: 'EXEC_ERROR' })\n\t}\n}\n\nfunction handleQuery(id: number, sql: string, params?: unknown[]): void {\n\tif (!db) {\n\t\tsendResponse({ id, type: 'error', message: 'Database is not open', code: 'DB_NOT_OPEN' })\n\t\treturn\n\t}\n\ttry {\n\t\tconst rows: Record<string, unknown>[] = []\n\t\tdb.exec({\n\t\t\tsql,\n\t\t\tbind: params,\n\t\t\trowMode: 'object',\n\t\t\tcallback: (row: Record<string, unknown>) => {\n\t\t\t\trows.push({ ...row })\n\t\t\t},\n\t\t})\n\t\tsendResponse({ id, type: 'success', data: rows })\n\t} catch (error) {\n\t\tsendResponse({ id, type: 'error', message: (error as Error).message, code: 'QUERY_ERROR' })\n\t}\n}\n\nfunction opfsDatabaseFilename(dbName: string): string {\n\tconst base = dbName.replace(/[^a-zA-Z0-9._-]/g, '_')\n\treturn base.endsWith('.db') ? base : `${base}.db`\n}\n\n/** Headless browsers and some profiles hang on OPFS VFS install; fall back to memory. */\nconst OPFS_INIT_TIMEOUT_MS = 10_000\n\nasync function withTimeout<T>(promise: Promise<T>, timeoutMs: number, label: string): Promise<T> {\n\tlet timer: ReturnType<typeof setTimeout> | undefined\n\ttry {\n\t\treturn await Promise.race([\n\t\t\tpromise,\n\t\t\tnew Promise<T>((_, reject) => {\n\t\t\t\ttimer = setTimeout(\n\t\t\t\t\t() => reject(new Error(`${label} timed out after ${timeoutMs}ms`)),\n\t\t\t\t\ttimeoutMs,\n\t\t\t\t)\n\t\t\t}),\n\t\t])\n\t} finally {\n\t\tif (timer !== undefined) clearTimeout(timer)\n\t}\n}\n\nasync function handleOpen(id: number, ddlStatements: string[], dbName?: string): Promise<void> {\n\ttry {\n\t\tconst sqlite3InitModule = (await import('@sqlite.org/sqlite-wasm')).default\n\n\t\t// In production builds, Vite hashes asset filenames (e.g. sqlite3-[hash].wasm).\n\t\t// The sqlite3 module's default locateFile resolves the unhashed name, causing a 404.\n\t\t// Templates set __KORA_SQLITE_WASM_URL via `import wasmUrl from '...sqlite3.wasm?url'`\n\t\t// so we can override locateFile with the correct hashed URL.\n\t\tconst wasmUrl = (globalThis as Record<string, unknown>).__KORA_SQLITE_WASM_URL as\n\t\t\t| string\n\t\t\t| undefined\n\t\tconst initOptions = wasmUrl\n\t\t\t? {\n\t\t\t\t\tlocateFile: (file: string) => {\n\t\t\t\t\t\tif (file.endsWith('.wasm')) return wasmUrl\n\t\t\t\t\t\treturn file\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t: undefined\n\n\t\t// sqlite3InitModule accepts Emscripten Module overrides but the types don't reflect this\n\t\tconst initFn = sqlite3InitModule as unknown as (\n\t\t\topts?: Record<string, unknown>,\n\t\t) => Promise<unknown>\n\t\tconst sqlite3 = (await withTimeout(\n\t\t\tinitFn(initOptions),\n\t\t\t60_000,\n\t\t\t'SQLite3 module init',\n\t\t)) as Awaited<ReturnType<typeof sqlite3InitModule>>\n\t\tsqlite3Api = sqlite3\n\n\t\t// Try OPFS persistence first\n\t\tlet useOpfs = false\n\t\tif (sqlite3.installOpfsSAHPoolVfs) {\n\t\t\ttry {\n\t\t\t\tconst pool = await withTimeout(\n\t\t\t\t\tsqlite3.installOpfsSAHPoolVfs({ name: 'kora-opfs' }),\n\t\t\t\t\tOPFS_INIT_TIMEOUT_MS,\n\t\t\t\t\t'OPFS VFS install',\n\t\t\t\t)\n\t\t\t\tdb = new pool.OpfsSAHPoolDb(opfsDatabaseFilename(dbName ?? 'kora-db'))\n\t\t\t\tuseOpfs = true\n\t\t\t} catch {\n\t\t\t\t// OPFS unavailable or too slow (common in headless CI), fall back to in-memory\n\t\t\t\tconsole.warn('[kora] OPFS unavailable, falling back to in-memory SQLite')\n\t\t\t}\n\t\t}\n\n\t\tif (!useOpfs) {\n\t\t\tdb = new sqlite3.oo1.DB({ filename: ':memory:' })\n\t\t}\n\n\t\t// Set pragmas\n\t\tdb?.exec({ sql: 'PRAGMA journal_mode = WAL' })\n\t\tdb?.exec({ sql: 'PRAGMA foreign_keys = ON' })\n\n\t\t// Execute DDL statements\n\t\tfor (const sql of ddlStatements) {\n\t\t\tif (sql.startsWith('--kora:safe-alter')) {\n\t\t\t\t// Safe ALTER TABLE — ignore \"duplicate column name\" errors for existing columns\n\t\t\t\ttry {\n\t\t\t\t\tdb?.exec({ sql: sql.replace('--kora:safe-alter\\n', '') })\n\t\t\t\t} catch (e) {\n\t\t\t\t\tconst msg = (e as Error).message || ''\n\t\t\t\t\tif (!msg.includes('duplicate column name')) {\n\t\t\t\t\t\tthrow e\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tdb?.exec({ sql })\n\t\t\t}\n\t\t}\n\n\t\tsendResponse({ id, type: 'success' })\n\t} catch (error) {\n\t\tsendResponse({\n\t\t\tid,\n\t\t\ttype: 'error',\n\t\t\tmessage: (error as Error).message,\n\t\t\tcode: 'INIT_ERROR',\n\t\t})\n\t}\n}\n\nfunction handleClose(id: number): void {\n\tif (db) {\n\t\tdb.close()\n\t\tdb = null\n\t}\n\tsendResponse({ id, type: 'success' })\n}\n\nfunction handleImport(id: number, data: Uint8Array): void {\n\tif (!db) {\n\t\tsendResponse({ id, type: 'error', message: 'Database is not open', code: 'DB_NOT_OPEN' })\n\t\treturn\n\t}\n\n\tconst dbWithDeserialize = db as SqliteDb & { deserialize?: (bytes: Uint8Array) => void }\n\tif (typeof dbWithDeserialize.deserialize === 'function') {\n\t\ttry {\n\t\t\tdbWithDeserialize.deserialize(data)\n\t\t\tsendResponse({ id, type: 'success' })\n\t\t\treturn\n\t\t} catch (error) {\n\t\t\tsendResponse({ id, type: 'error', message: (error as Error).message, code: 'IMPORT_ERROR' })\n\t\t\treturn\n\t\t}\n\t}\n\n\tconst sqlite3 = sqlite3Api as {\n\t\too1?: { DB?: new (...args: unknown[]) => SqliteDb }\n\t\tcapi?: { sqlite3_deserialize?: unknown }\n\t} | null\n\n\tif (!sqlite3 || typeof sqlite3.capi?.sqlite3_deserialize === 'undefined') {\n\t\tsendResponse({\n\t\t\tid,\n\t\t\ttype: 'error',\n\t\t\tmessage: 'Import not supported in this SQLite WASM runtime',\n\t\t\tcode: 'IMPORT_NOT_SUPPORTED',\n\t\t})\n\t\treturn\n\t}\n\n\tsendResponse({\n\t\tid,\n\t\ttype: 'error',\n\t\tmessage:\n\t\t\t'Import requires runtime-specific deserialize wiring and is unavailable in this worker build',\n\t\tcode: 'IMPORT_NOT_SUPPORTED',\n\t})\n}\n\nfunction handleMessage(request: WorkerRequest): void {\n\ttry {\n\t\tswitch (request.type) {\n\t\t\tcase 'open':\n\t\t\t\t// open is async due to WASM loading\n\t\t\t\thandleOpen(request.id, request.ddlStatements, request.dbName)\n\t\t\t\treturn\n\t\t\tcase 'close':\n\t\t\t\thandleClose(request.id)\n\t\t\t\treturn\n\t\t\tcase 'execute':\n\t\t\t\thandleExecute(request.id, request.sql, request.params)\n\t\t\t\treturn\n\t\t\tcase 'query':\n\t\t\t\thandleQuery(request.id, request.sql, request.params)\n\t\t\t\treturn\n\t\t\tcase 'begin':\n\t\t\t\thandleExecute(request.id, 'BEGIN')\n\t\t\t\treturn\n\t\t\tcase 'commit':\n\t\t\t\thandleExecute(request.id, 'COMMIT')\n\t\t\t\treturn\n\t\t\tcase 'rollback':\n\t\t\t\thandleExecute(request.id, 'ROLLBACK')\n\t\t\t\treturn\n\t\t\tcase 'migrate':\n\t\t\t\tif (!db) {\n\t\t\t\t\tsendResponse({\n\t\t\t\t\t\tid: request.id,\n\t\t\t\t\t\ttype: 'error',\n\t\t\t\t\t\tmessage: 'Database is not open',\n\t\t\t\t\t\tcode: 'DB_NOT_OPEN',\n\t\t\t\t\t})\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\tfor (const sql of request.statements) {\n\t\t\t\t\t\tdb.exec({ sql })\n\t\t\t\t\t}\n\t\t\t\t\tsendResponse({ id: request.id, type: 'success' })\n\t\t\t\t} catch (error) {\n\t\t\t\t\tsendResponse({\n\t\t\t\t\t\tid: request.id,\n\t\t\t\t\t\ttype: 'error',\n\t\t\t\t\t\tmessage: (error as Error).message,\n\t\t\t\t\t\tcode: 'MIGRATE_ERROR',\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\tcase 'export':\n\t\t\t\t// Export is not trivially supported via the oo1 API in the browser.\n\t\t\t\t// In a real implementation, we'd use the C API's sqlite3_serialize.\n\t\t\t\tsendResponse({\n\t\t\t\t\tid: request.id,\n\t\t\t\t\ttype: 'error',\n\t\t\t\t\tmessage: 'Export not yet supported in browser worker',\n\t\t\t\t\tcode: 'EXPORT_NOT_SUPPORTED',\n\t\t\t\t})\n\t\t\t\treturn\n\t\t\tcase 'import':\n\t\t\t\thandleImport(request.id, request.data)\n\t\t\t\treturn\n\t\t\tdefault:\n\t\t\t\tsendResponse({\n\t\t\t\t\tid: (request as WorkerRequest).id,\n\t\t\t\t\ttype: 'error',\n\t\t\t\t\tmessage: 'Unknown request type',\n\t\t\t\t\tcode: 'UNKNOWN_REQUEST',\n\t\t\t\t})\n\t\t}\n\t} catch (error) {\n\t\tsendResponse({\n\t\t\tid: request.id,\n\t\t\ttype: 'error',\n\t\t\tmessage: (error as Error).message,\n\t\t\tcode: 'WORKER_ERROR',\n\t\t})\n\t}\n}\n\n// Listen for messages from the main thread\nself.onmessage = (event: MessageEvent<WorkerRequest>) => {\n\thandleMessage(event.data)\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/adapters/sqlite-wasm-worker.ts"],"sourcesContent":["/// <reference lib=\"webworker\" />\n/**\n * Web Worker script for running SQLite WASM.\n *\n * This file is intended to run inside a Web Worker in browsers.\n * It loads @sqlite.org/sqlite-wasm, initializes SQLite with OPFS persistence\n * (falling back to in-memory if unavailable), and processes messages from\n * the main thread via the WorkerRequest/WorkerResponse protocol.\n *\n * This script cannot be tested in Node.js — it is validated in E2E browser tests.\n */\n\nimport type { WorkerRequest, WorkerResponse } from './sqlite-wasm-channel'\n\ninterface SqliteDb {\n\texec(opts: {\n\t\tsql: string\n\t\tbind?: unknown[]\n\t\treturnValue?: string\n\t\trowMode?: string\n\t\tcallback?: (row: Record<string, unknown>) => void\n\t}): void\n\tclose(): void\n\tdeserialize?: (data: Uint8Array) => void\n}\n\ndeclare const self: DedicatedWorkerGlobalScope\n\nlet db: SqliteDb | null = null\nlet sqlite3Api: unknown = null\n\nfunction sendResponse(response: WorkerResponse): void {\n\tself.postMessage(response)\n}\n\nfunction handleExecute(id: number, sql: string, params?: unknown[]): void {\n\tif (!db) {\n\t\tsendResponse({ id, type: 'error', message: 'Database is not open', code: 'DB_NOT_OPEN' })\n\t\treturn\n\t}\n\ttry {\n\t\tdb.exec({ sql, bind: params })\n\t\tsendResponse({ id, type: 'success' })\n\t} catch (error) {\n\t\tsendResponse({ id, type: 'error', message: (error as Error).message, code: 'EXEC_ERROR' })\n\t}\n}\n\nfunction handleQuery(id: number, sql: string, params?: unknown[]): void {\n\tif (!db) {\n\t\tsendResponse({ id, type: 'error', message: 'Database is not open', code: 'DB_NOT_OPEN' })\n\t\treturn\n\t}\n\ttry {\n\t\tconst rows: Record<string, unknown>[] = []\n\t\tdb.exec({\n\t\t\tsql,\n\t\t\tbind: params,\n\t\t\trowMode: 'object',\n\t\t\tcallback: (row: Record<string, unknown>) => {\n\t\t\t\trows.push({ ...row })\n\t\t\t},\n\t\t})\n\t\tsendResponse({ id, type: 'success', data: rows })\n\t} catch (error) {\n\t\tsendResponse({ id, type: 'error', message: (error as Error).message, code: 'QUERY_ERROR' })\n\t}\n}\n\nfunction opfsDatabaseFilename(dbName: string): string {\n\tconst base = dbName.replace(/[^a-zA-Z0-9._-]/g, '_')\n\treturn base.endsWith('.db') ? base : `${base}.db`\n}\n\n/** Headless browsers and some profiles hang on OPFS VFS install; fall back to memory. */\nconst OPFS_INIT_TIMEOUT_MS = 10_000\n\nasync function withTimeout<T>(promise: Promise<T>, timeoutMs: number, label: string): Promise<T> {\n\tlet timer: ReturnType<typeof setTimeout> | undefined\n\ttry {\n\t\treturn await Promise.race([\n\t\t\tpromise,\n\t\t\tnew Promise<T>((_, reject) => {\n\t\t\t\ttimer = setTimeout(\n\t\t\t\t\t() => reject(new Error(`${label} timed out after ${timeoutMs}ms`)),\n\t\t\t\t\ttimeoutMs,\n\t\t\t\t)\n\t\t\t}),\n\t\t])\n\t} finally {\n\t\tif (timer !== undefined) clearTimeout(timer)\n\t}\n}\n\nasync function handleOpen(id: number, ddlStatements: string[], dbName?: string): Promise<void> {\n\ttry {\n\t\tconst sqlite3InitModule = (await import('@sqlite.org/sqlite-wasm')).default\n\n\t\t// In production builds, Vite hashes asset filenames (e.g. sqlite3-[hash].wasm).\n\t\t// The sqlite3 module's default locateFile resolves the unhashed name, causing a 404.\n\t\t// Templates set __KORA_SQLITE_WASM_URL via `import wasmUrl from '...sqlite3.wasm?url'`\n\t\t// so we can override locateFile with the correct hashed URL.\n\t\tconst wasmUrl = (globalThis as Record<string, unknown>).__KORA_SQLITE_WASM_URL as\n\t\t\t| string\n\t\t\t| undefined\n\t\tconst initOptions = wasmUrl\n\t\t\t? {\n\t\t\t\t\tlocateFile: (file: string) => {\n\t\t\t\t\t\tif (file.endsWith('.wasm')) return wasmUrl\n\t\t\t\t\t\treturn file\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t: undefined\n\n\t\t// sqlite3InitModule accepts Emscripten Module overrides but the types don't reflect this\n\t\tconst initFn = sqlite3InitModule as unknown as (\n\t\t\topts?: Record<string, unknown>,\n\t\t) => Promise<unknown>\n\t\tconst sqlite3 = (await withTimeout(\n\t\t\tinitFn(initOptions),\n\t\t\t60_000,\n\t\t\t'SQLite3 module init',\n\t\t)) as Awaited<ReturnType<typeof sqlite3InitModule>>\n\t\tsqlite3Api = sqlite3\n\n\t\t// Try OPFS persistence first\n\t\tlet useOpfs = false\n\t\tlet fallbackReason: 'lock-conflict' | 'timeout' | 'unsupported' | undefined\n\t\tif (sqlite3.installOpfsSAHPoolVfs) {\n\t\t\ttry {\n\t\t\t\tconst pool = await withTimeout(\n\t\t\t\t\tsqlite3.installOpfsSAHPoolVfs({ name: 'kora-opfs' }),\n\t\t\t\t\tOPFS_INIT_TIMEOUT_MS,\n\t\t\t\t\t'OPFS VFS install',\n\t\t\t\t)\n\t\t\t\tdb = new pool.OpfsSAHPoolDb(opfsDatabaseFilename(dbName ?? 'kora-db'))\n\t\t\t\tuseOpfs = true\n\t\t\t} catch (error) {\n\t\t\t\t// OPFS unavailable. Classify WHY so the main thread can surface a clear\n\t\t\t\t// diagnostic instead of a silent, non-persistent in-memory fallback that\n\t\t\t\t// loses the user's data on reload.\n\t\t\t\tfallbackReason = classifyOpfsFailure(error)\n\t\t\t\tconsole.warn(\n\t\t\t\t\t`[kora] OPFS unavailable (${fallbackReason}), falling back to in-memory SQLite`,\n\t\t\t\t)\n\t\t\t}\n\t\t} else {\n\t\t\tfallbackReason = 'unsupported'\n\t\t}\n\n\t\tif (!useOpfs) {\n\t\t\tdb = new sqlite3.oo1.DB({ filename: ':memory:' })\n\t\t}\n\n\t\t// Set pragmas\n\t\tdb?.exec({ sql: 'PRAGMA journal_mode = WAL' })\n\t\tdb?.exec({ sql: 'PRAGMA foreign_keys = ON' })\n\n\t\t// Execute DDL statements\n\t\tfor (const sql of ddlStatements) {\n\t\t\tif (sql.startsWith('--kora:safe-alter')) {\n\t\t\t\t// Safe ALTER TABLE — ignore \"duplicate column name\" errors for existing columns\n\t\t\t\ttry {\n\t\t\t\t\tdb?.exec({ sql: sql.replace('--kora:safe-alter\\n', '') })\n\t\t\t\t} catch (e) {\n\t\t\t\t\tconst msg = (e as Error).message || ''\n\t\t\t\t\tif (!msg.includes('duplicate column name')) {\n\t\t\t\t\t\tthrow e\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tdb?.exec({ sql })\n\t\t\t}\n\t\t}\n\n\t\t// Report the storage mode so the adapter can emit `store:opfs-unavailable`\n\t\t// when persistence silently degraded to in-memory.\n\t\tsendResponse({\n\t\t\tid,\n\t\t\ttype: 'success',\n\t\t\tdata: { persistent: useOpfs, ...(fallbackReason ? { fallbackReason } : {}) },\n\t\t})\n\t} catch (error) {\n\t\tsendResponse({\n\t\t\tid,\n\t\t\ttype: 'error',\n\t\t\tmessage: (error as Error).message,\n\t\t\tcode: 'INIT_ERROR',\n\t\t})\n\t}\n}\n\n/**\n * Classify why the OPFS SyncAccessHandle pool could not be installed, so the\n * fallback to in-memory is explainable rather than silent.\n *\n * A `lock-conflict` is the important case: another runtime on the same origin\n * (another tab or a logically separate app that reused the database name) already\n * holds the pool's access handles, so this runtime cannot persist.\n */\nfunction classifyOpfsFailure(error: unknown): 'lock-conflict' | 'timeout' | 'unsupported' {\n\tconst message = error instanceof Error ? error.message : String(error)\n\tif (/timed out|timeout/i.test(message)) {\n\t\treturn 'timeout'\n\t}\n\tif (\n\t\t/lock|already|in use|in-use|NoModificationAllowed|acquire|SyncAccessHandle|being created/i.test(\n\t\t\tmessage,\n\t\t)\n\t) {\n\t\treturn 'lock-conflict'\n\t}\n\treturn 'unsupported'\n}\n\nfunction handleClose(id: number): void {\n\tif (db) {\n\t\tdb.close()\n\t\tdb = null\n\t}\n\tsendResponse({ id, type: 'success' })\n}\n\nfunction handleImport(id: number, data: Uint8Array): void {\n\tif (!db) {\n\t\tsendResponse({ id, type: 'error', message: 'Database is not open', code: 'DB_NOT_OPEN' })\n\t\treturn\n\t}\n\n\tconst dbWithDeserialize = db as SqliteDb & { deserialize?: (bytes: Uint8Array) => void }\n\tif (typeof dbWithDeserialize.deserialize === 'function') {\n\t\ttry {\n\t\t\tdbWithDeserialize.deserialize(data)\n\t\t\tsendResponse({ id, type: 'success' })\n\t\t\treturn\n\t\t} catch (error) {\n\t\t\tsendResponse({ id, type: 'error', message: (error as Error).message, code: 'IMPORT_ERROR' })\n\t\t\treturn\n\t\t}\n\t}\n\n\tconst sqlite3 = sqlite3Api as {\n\t\too1?: { DB?: new (...args: unknown[]) => SqliteDb }\n\t\tcapi?: { sqlite3_deserialize?: unknown }\n\t} | null\n\n\tif (!sqlite3 || typeof sqlite3.capi?.sqlite3_deserialize === 'undefined') {\n\t\tsendResponse({\n\t\t\tid,\n\t\t\ttype: 'error',\n\t\t\tmessage: 'Import not supported in this SQLite WASM runtime',\n\t\t\tcode: 'IMPORT_NOT_SUPPORTED',\n\t\t})\n\t\treturn\n\t}\n\n\tsendResponse({\n\t\tid,\n\t\ttype: 'error',\n\t\tmessage:\n\t\t\t'Import requires runtime-specific deserialize wiring and is unavailable in this worker build',\n\t\tcode: 'IMPORT_NOT_SUPPORTED',\n\t})\n}\n\nfunction handleMessage(request: WorkerRequest): void {\n\ttry {\n\t\tswitch (request.type) {\n\t\t\tcase 'open':\n\t\t\t\t// open is async due to WASM loading\n\t\t\t\thandleOpen(request.id, request.ddlStatements, request.dbName)\n\t\t\t\treturn\n\t\t\tcase 'close':\n\t\t\t\thandleClose(request.id)\n\t\t\t\treturn\n\t\t\tcase 'execute':\n\t\t\t\thandleExecute(request.id, request.sql, request.params)\n\t\t\t\treturn\n\t\t\tcase 'query':\n\t\t\t\thandleQuery(request.id, request.sql, request.params)\n\t\t\t\treturn\n\t\t\tcase 'begin':\n\t\t\t\thandleExecute(request.id, 'BEGIN')\n\t\t\t\treturn\n\t\t\tcase 'commit':\n\t\t\t\thandleExecute(request.id, 'COMMIT')\n\t\t\t\treturn\n\t\t\tcase 'rollback':\n\t\t\t\thandleExecute(request.id, 'ROLLBACK')\n\t\t\t\treturn\n\t\t\tcase 'migrate':\n\t\t\t\tif (!db) {\n\t\t\t\t\tsendResponse({\n\t\t\t\t\t\tid: request.id,\n\t\t\t\t\t\ttype: 'error',\n\t\t\t\t\t\tmessage: 'Database is not open',\n\t\t\t\t\t\tcode: 'DB_NOT_OPEN',\n\t\t\t\t\t})\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\tfor (const sql of request.statements) {\n\t\t\t\t\t\tdb.exec({ sql })\n\t\t\t\t\t}\n\t\t\t\t\tsendResponse({ id: request.id, type: 'success' })\n\t\t\t\t} catch (error) {\n\t\t\t\t\tsendResponse({\n\t\t\t\t\t\tid: request.id,\n\t\t\t\t\t\ttype: 'error',\n\t\t\t\t\t\tmessage: (error as Error).message,\n\t\t\t\t\t\tcode: 'MIGRATE_ERROR',\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\tcase 'export':\n\t\t\t\t// Export is not trivially supported via the oo1 API in the browser.\n\t\t\t\t// In a real implementation, we'd use the C API's sqlite3_serialize.\n\t\t\t\tsendResponse({\n\t\t\t\t\tid: request.id,\n\t\t\t\t\ttype: 'error',\n\t\t\t\t\tmessage: 'Export not yet supported in browser worker',\n\t\t\t\t\tcode: 'EXPORT_NOT_SUPPORTED',\n\t\t\t\t})\n\t\t\t\treturn\n\t\t\tcase 'import':\n\t\t\t\thandleImport(request.id, request.data)\n\t\t\t\treturn\n\t\t\tdefault:\n\t\t\t\tsendResponse({\n\t\t\t\t\tid: (request as WorkerRequest).id,\n\t\t\t\t\ttype: 'error',\n\t\t\t\t\tmessage: 'Unknown request type',\n\t\t\t\t\tcode: 'UNKNOWN_REQUEST',\n\t\t\t\t})\n\t\t}\n\t} catch (error) {\n\t\tsendResponse({\n\t\t\tid: request.id,\n\t\t\ttype: 'error',\n\t\t\tmessage: (error as Error).message,\n\t\t\tcode: 'WORKER_ERROR',\n\t\t})\n\t}\n}\n\n// Listen for messages from the main thread\nself.onmessage = (event: MessageEvent<WorkerRequest>) => {\n\thandleMessage(event.data)\n}\n"],"mappings":"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","names":[]}