@korajs/store 1.0.0-beta.5 → 1.0.0-beta.7
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.
- package/dist/adapters/better-sqlite3.d.cts +1 -1
- package/dist/adapters/better-sqlite3.d.ts +1 -1
- package/dist/adapters/indexeddb.cjs +347 -132
- package/dist/adapters/indexeddb.cjs.map +1 -1
- package/dist/adapters/indexeddb.d.cts +4 -2
- package/dist/adapters/indexeddb.d.ts +4 -2
- package/dist/adapters/indexeddb.js +42 -5
- package/dist/adapters/indexeddb.js.map +1 -1
- package/dist/adapters/sqlite-wasm-worker.cjs.map +1 -1
- package/dist/adapters/sqlite-wasm-worker.js +250 -3
- package/dist/adapters/sqlite-wasm-worker.js.map +1 -1
- package/dist/adapters/sqlite-wasm.cjs +308 -129
- package/dist/adapters/sqlite-wasm.cjs.map +1 -1
- package/dist/adapters/sqlite-wasm.d.cts +18 -7
- package/dist/adapters/sqlite-wasm.d.ts +18 -7
- package/dist/adapters/sqlite-wasm.js +2 -2
- package/dist/{chunk-Q57CHIIN.js → chunk-RJIEKEN4.js} +310 -130
- package/dist/chunk-RJIEKEN4.js.map +1 -0
- package/dist/{chunk-COJSQBXW.js → chunk-VDBJQ75K.js} +1 -1
- package/dist/chunk-VDBJQ75K.js.map +1 -0
- package/dist/{chunk-RL43UYQZ.js → chunk-YT7MRFCP.js} +5 -5
- package/dist/index.cjs +26 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -4
- package/dist/index.d.ts +18 -4
- package/dist/index.js +44 -18
- package/dist/index.js.map +1 -1
- package/dist/internal.d.cts +3 -3
- package/dist/internal.d.ts +3 -3
- package/dist/internal.js +2 -2
- package/dist/{sqlite-wasm-channel-NGSBJR44.js → sqlite-wasm-channel-GGHA2BXU.js} +2 -2
- package/dist/{sqlite-wasm-channel-Bx57s8Cq.d.cts → sqlite-wasm-channel-L6o3bWQK.d.cts} +2 -2
- package/dist/{sqlite-wasm-channel-Bx57s8Cq.d.ts → sqlite-wasm-channel-L6o3bWQK.d.ts} +2 -2
- package/dist/{types-C1kigrWz.d.ts → types-BOO9EPt1.d.ts} +1 -1
- package/dist/{types-D1MzKVmk.d.ts → types-BslDOTk0.d.cts} +16 -1
- package/dist/{types-D1MzKVmk.d.cts → types-BslDOTk0.d.ts} +16 -1
- package/dist/{types-DAo9oduO.d.cts → types-C_GDavG-.d.cts} +1 -1
- package/package.json +2 -12
- package/dist/adapters/sqlite-wasm-shared-host.cjs +0 -345
- package/dist/adapters/sqlite-wasm-shared-host.cjs.map +0 -1
- package/dist/adapters/sqlite-wasm-shared-host.d.cts +0 -82
- package/dist/adapters/sqlite-wasm-shared-host.d.ts +0 -82
- package/dist/adapters/sqlite-wasm-shared-host.js +0 -61
- package/dist/adapters/sqlite-wasm-shared-host.js.map +0 -1
- package/dist/chunk-3Y2IERBF.js +0 -255
- package/dist/chunk-3Y2IERBF.js.map +0 -1
- package/dist/chunk-COJSQBXW.js.map +0 -1
- package/dist/chunk-Q57CHIIN.js.map +0 -1
- /package/dist/{chunk-RL43UYQZ.js.map → chunk-YT7MRFCP.js.map} +0 -0
- /package/dist/{sqlite-wasm-channel-NGSBJR44.js.map → sqlite-wasm-channel-GGHA2BXU.js.map} +0 -0
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import { a as WorkerRequest, b as WorkerResponse } from '../sqlite-wasm-channel-Bx57s8Cq.cjs';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Reusable SQLite WASM core, decoupled from any specific worker global.
|
|
5
|
-
*
|
|
6
|
-
* The dedicated worker ({@link file://./sqlite-wasm-worker.ts}) wires one core to
|
|
7
|
-
* `self.onmessage`/`self.postMessage`. The SharedWorker host
|
|
8
|
-
* ({@link file://./sqlite-wasm-shared-host.ts}) runs one core PER database inside
|
|
9
|
-
* a single SharedWorker scope, because a SharedWorker cannot spawn a nested
|
|
10
|
-
* `Worker` (the `Worker` constructor is not defined in `SharedWorkerGlobalScope`
|
|
11
|
-
* in Chromium), so it must run SQLite directly.
|
|
12
|
-
*
|
|
13
|
-
* The WASM module and the OPFS SyncAccessHandle pool are cached at module scope
|
|
14
|
-
* and shared across every core in the same worker/SharedWorker: the pool holds
|
|
15
|
-
* multiple database files keyed by filename, so installing it once and opening
|
|
16
|
-
* each database within it avoids the single-writer conflict that installing the
|
|
17
|
-
* pool twice in one scope would cause.
|
|
18
|
-
*
|
|
19
|
-
* This file cannot be unit-tested in Node (no WASM/OPFS); it is exercised through
|
|
20
|
-
* the browser E2E suite and its dispatch is covered via the shared-host tests.
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
/** Handle to a single database, dispatching the worker protocol for it. */
|
|
24
|
-
interface SqliteWasmCore {
|
|
25
|
-
handle(request: WorkerRequest): Promise<WorkerResponse>;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* SharedWorker host: runs SQLite WASM directly inside the SharedWorker, one
|
|
30
|
-
* database core per `dbName`, shared by every tab on the origin.
|
|
31
|
-
*
|
|
32
|
-
* A SharedWorker CANNOT spawn a nested `Worker` (the `Worker` constructor is not
|
|
33
|
-
* defined in `SharedWorkerGlobalScope` in Chromium), so the host does not delegate
|
|
34
|
-
* to a dedicated worker. It runs {@link createSqliteWasmCore} in its own scope.
|
|
35
|
-
* Because each request is answered by a Promise from the core, responses are
|
|
36
|
-
* correlated to their caller directly, with no inner-id bookkeeping.
|
|
37
|
-
*
|
|
38
|
-
* Bundle this file separately and pass its URL as `store.sharedWorkerUrl` alongside
|
|
39
|
-
* `workerUrl`. The entry that bundles it must set `__KORA_SQLITE_WASM_URL` (via a
|
|
40
|
-
* `?url` import of the sqlite wasm binary) the same way the dedicated worker entry
|
|
41
|
-
* does, so the core can locate the hashed WASM asset in production builds.
|
|
42
|
-
*
|
|
43
|
-
* @example
|
|
44
|
-
* ```typescript
|
|
45
|
-
* createApp({
|
|
46
|
-
* store: {
|
|
47
|
-
* workerUrl: '/sqlite-wasm-worker.js',
|
|
48
|
-
* sharedWorkerUrl: '/sqlite-wasm-shared-host.js',
|
|
49
|
-
* },
|
|
50
|
-
* })
|
|
51
|
-
* ```
|
|
52
|
-
*/
|
|
53
|
-
|
|
54
|
-
declare const SW_RESPONSE = "kora-sw-response";
|
|
55
|
-
interface SharedWorkerRpcResponse {
|
|
56
|
-
type: typeof SW_RESPONSE;
|
|
57
|
-
requestId: string;
|
|
58
|
-
response: WorkerResponse;
|
|
59
|
-
}
|
|
60
|
-
/** Minimal structural view of a connected client `MessagePort`. */
|
|
61
|
-
interface HostPort {
|
|
62
|
-
start(): void;
|
|
63
|
-
postMessage(message: SharedWorkerRpcResponse): void;
|
|
64
|
-
addEventListener(type: 'message' | 'messageerror', handler: (event: {
|
|
65
|
-
data: unknown;
|
|
66
|
-
}) => void): void;
|
|
67
|
-
}
|
|
68
|
-
interface SharedWorkerHostOptions {
|
|
69
|
-
/** Creates a database core. Injectable so the host routing can be unit-tested. */
|
|
70
|
-
createCore: () => SqliteWasmCore;
|
|
71
|
-
}
|
|
72
|
-
interface SharedWorkerHost {
|
|
73
|
-
/** Wire a newly connected client port into the host. */
|
|
74
|
-
connect(port: HostPort): void;
|
|
75
|
-
}
|
|
76
|
-
/**
|
|
77
|
-
* Creates the SharedWorker host routing core: one SQLite core per `dbName`, shared
|
|
78
|
-
* by every connected tab, with each request answered directly by the core.
|
|
79
|
-
*/
|
|
80
|
-
declare function createSharedWorkerHost(options: SharedWorkerHostOptions): SharedWorkerHost;
|
|
81
|
-
|
|
82
|
-
export { type HostPort, type SharedWorkerHost, type SharedWorkerHostOptions, createSharedWorkerHost };
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import { a as WorkerRequest, b as WorkerResponse } from '../sqlite-wasm-channel-Bx57s8Cq.js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Reusable SQLite WASM core, decoupled from any specific worker global.
|
|
5
|
-
*
|
|
6
|
-
* The dedicated worker ({@link file://./sqlite-wasm-worker.ts}) wires one core to
|
|
7
|
-
* `self.onmessage`/`self.postMessage`. The SharedWorker host
|
|
8
|
-
* ({@link file://./sqlite-wasm-shared-host.ts}) runs one core PER database inside
|
|
9
|
-
* a single SharedWorker scope, because a SharedWorker cannot spawn a nested
|
|
10
|
-
* `Worker` (the `Worker` constructor is not defined in `SharedWorkerGlobalScope`
|
|
11
|
-
* in Chromium), so it must run SQLite directly.
|
|
12
|
-
*
|
|
13
|
-
* The WASM module and the OPFS SyncAccessHandle pool are cached at module scope
|
|
14
|
-
* and shared across every core in the same worker/SharedWorker: the pool holds
|
|
15
|
-
* multiple database files keyed by filename, so installing it once and opening
|
|
16
|
-
* each database within it avoids the single-writer conflict that installing the
|
|
17
|
-
* pool twice in one scope would cause.
|
|
18
|
-
*
|
|
19
|
-
* This file cannot be unit-tested in Node (no WASM/OPFS); it is exercised through
|
|
20
|
-
* the browser E2E suite and its dispatch is covered via the shared-host tests.
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
/** Handle to a single database, dispatching the worker protocol for it. */
|
|
24
|
-
interface SqliteWasmCore {
|
|
25
|
-
handle(request: WorkerRequest): Promise<WorkerResponse>;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* SharedWorker host: runs SQLite WASM directly inside the SharedWorker, one
|
|
30
|
-
* database core per `dbName`, shared by every tab on the origin.
|
|
31
|
-
*
|
|
32
|
-
* A SharedWorker CANNOT spawn a nested `Worker` (the `Worker` constructor is not
|
|
33
|
-
* defined in `SharedWorkerGlobalScope` in Chromium), so the host does not delegate
|
|
34
|
-
* to a dedicated worker. It runs {@link createSqliteWasmCore} in its own scope.
|
|
35
|
-
* Because each request is answered by a Promise from the core, responses are
|
|
36
|
-
* correlated to their caller directly, with no inner-id bookkeeping.
|
|
37
|
-
*
|
|
38
|
-
* Bundle this file separately and pass its URL as `store.sharedWorkerUrl` alongside
|
|
39
|
-
* `workerUrl`. The entry that bundles it must set `__KORA_SQLITE_WASM_URL` (via a
|
|
40
|
-
* `?url` import of the sqlite wasm binary) the same way the dedicated worker entry
|
|
41
|
-
* does, so the core can locate the hashed WASM asset in production builds.
|
|
42
|
-
*
|
|
43
|
-
* @example
|
|
44
|
-
* ```typescript
|
|
45
|
-
* createApp({
|
|
46
|
-
* store: {
|
|
47
|
-
* workerUrl: '/sqlite-wasm-worker.js',
|
|
48
|
-
* sharedWorkerUrl: '/sqlite-wasm-shared-host.js',
|
|
49
|
-
* },
|
|
50
|
-
* })
|
|
51
|
-
* ```
|
|
52
|
-
*/
|
|
53
|
-
|
|
54
|
-
declare const SW_RESPONSE = "kora-sw-response";
|
|
55
|
-
interface SharedWorkerRpcResponse {
|
|
56
|
-
type: typeof SW_RESPONSE;
|
|
57
|
-
requestId: string;
|
|
58
|
-
response: WorkerResponse;
|
|
59
|
-
}
|
|
60
|
-
/** Minimal structural view of a connected client `MessagePort`. */
|
|
61
|
-
interface HostPort {
|
|
62
|
-
start(): void;
|
|
63
|
-
postMessage(message: SharedWorkerRpcResponse): void;
|
|
64
|
-
addEventListener(type: 'message' | 'messageerror', handler: (event: {
|
|
65
|
-
data: unknown;
|
|
66
|
-
}) => void): void;
|
|
67
|
-
}
|
|
68
|
-
interface SharedWorkerHostOptions {
|
|
69
|
-
/** Creates a database core. Injectable so the host routing can be unit-tested. */
|
|
70
|
-
createCore: () => SqliteWasmCore;
|
|
71
|
-
}
|
|
72
|
-
interface SharedWorkerHost {
|
|
73
|
-
/** Wire a newly connected client port into the host. */
|
|
74
|
-
connect(port: HostPort): void;
|
|
75
|
-
}
|
|
76
|
-
/**
|
|
77
|
-
* Creates the SharedWorker host routing core: one SQLite core per `dbName`, shared
|
|
78
|
-
* by every connected tab, with each request answered directly by the core.
|
|
79
|
-
*/
|
|
80
|
-
declare function createSharedWorkerHost(options: SharedWorkerHostOptions): SharedWorkerHost;
|
|
81
|
-
|
|
82
|
-
export { type HostPort, type SharedWorkerHost, type SharedWorkerHostOptions, createSharedWorkerHost };
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createSqliteWasmCore
|
|
3
|
-
} from "../chunk-3Y2IERBF.js";
|
|
4
|
-
|
|
5
|
-
// src/adapters/sqlite-wasm-shared-host.ts
|
|
6
|
-
var SW_REQUEST = "kora-sw-request";
|
|
7
|
-
var SW_RESPONSE = "kora-sw-response";
|
|
8
|
-
function createSharedWorkerHost(options) {
|
|
9
|
-
const cores = /* @__PURE__ */ new Map();
|
|
10
|
-
function coreFor(dbName) {
|
|
11
|
-
let core = cores.get(dbName);
|
|
12
|
-
if (!core) {
|
|
13
|
-
core = options.createCore();
|
|
14
|
-
cores.set(dbName, core);
|
|
15
|
-
}
|
|
16
|
-
return core;
|
|
17
|
-
}
|
|
18
|
-
function connect(port) {
|
|
19
|
-
port.start();
|
|
20
|
-
port.addEventListener("message", (event) => {
|
|
21
|
-
const data = event.data;
|
|
22
|
-
if (data?.type !== SW_REQUEST) {
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
const core = coreFor(data.dbName);
|
|
26
|
-
void core.handle(data.request).then(
|
|
27
|
-
(response) => {
|
|
28
|
-
port.postMessage({ type: SW_RESPONSE, requestId: data.requestId, response });
|
|
29
|
-
},
|
|
30
|
-
(error) => {
|
|
31
|
-
const message = error instanceof Error ? error.message : "SharedWorker core failed";
|
|
32
|
-
port.postMessage({
|
|
33
|
-
type: SW_RESPONSE,
|
|
34
|
-
requestId: data.requestId,
|
|
35
|
-
response: {
|
|
36
|
-
id: data.request.id,
|
|
37
|
-
type: "error",
|
|
38
|
-
message,
|
|
39
|
-
code: "SHARED_WORKER_CORE_ERROR"
|
|
40
|
-
}
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
);
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
return { connect };
|
|
47
|
-
}
|
|
48
|
-
if (typeof SharedWorkerGlobalScope !== "undefined" && typeof self !== "undefined" && self instanceof SharedWorkerGlobalScope) {
|
|
49
|
-
const host = createSharedWorkerHost({ createCore: createSqliteWasmCore });
|
|
50
|
-
self.addEventListener("connect", (event) => {
|
|
51
|
-
const port = event.ports[0];
|
|
52
|
-
if (!port) {
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
host.connect(port);
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
export {
|
|
59
|
-
createSharedWorkerHost
|
|
60
|
-
};
|
|
61
|
-
//# sourceMappingURL=sqlite-wasm-shared-host.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/adapters/sqlite-wasm-shared-host.ts"],"sourcesContent":["/// <reference lib=\"webworker\" />\n/**\n * SharedWorker host: runs SQLite WASM directly inside the SharedWorker, one\n * database core per `dbName`, shared by every tab on the origin.\n *\n * A SharedWorker CANNOT spawn a nested `Worker` (the `Worker` constructor is not\n * defined in `SharedWorkerGlobalScope` in Chromium), so the host does not delegate\n * to a dedicated worker. It runs {@link createSqliteWasmCore} in its own scope.\n * Because each request is answered by a Promise from the core, responses are\n * correlated to their caller directly, with no inner-id bookkeeping.\n *\n * Bundle this file separately and pass its URL as `store.sharedWorkerUrl` alongside\n * `workerUrl`. The entry that bundles it must set `__KORA_SQLITE_WASM_URL` (via a\n * `?url` import of the sqlite wasm binary) the same way the dedicated worker entry\n * does, so the core can locate the hashed WASM asset in production builds.\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'\nimport { type SqliteWasmCore, createSqliteWasmCore } from './sqlite-wasm-worker-core'\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/** 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/** Creates a database core. Injectable so the host routing can be unit-tested. */\n\tcreateCore: () => SqliteWasmCore\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 SQLite core per `dbName`, shared\n * by every connected tab, with each request answered directly by the core.\n */\nexport function createSharedWorkerHost(options: SharedWorkerHostOptions): SharedWorkerHost {\n\tconst cores = new Map<string, SqliteWasmCore>()\n\n\tfunction coreFor(dbName: string): SqliteWasmCore {\n\t\tlet core = cores.get(dbName)\n\t\tif (!core) {\n\t\t\tcore = options.createCore()\n\t\t\tcores.set(dbName, core)\n\t\t}\n\t\treturn core\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 core = coreFor(data.dbName)\n\t\t\tvoid core.handle(data.request).then(\n\t\t\t\t(response) => {\n\t\t\t\t\tport.postMessage({ type: SW_RESPONSE, requestId: data.requestId, response })\n\t\t\t\t},\n\t\t\t\t(error: unknown) => {\n\t\t\t\t\t// The core catches its own errors, so this only fires on an unexpected\n\t\t\t\t\t// throw. Relay it so the client never hangs waiting for a response.\n\t\t\t\t\tconst message = error instanceof Error ? error.message : 'SharedWorker core failed'\n\t\t\t\t\tport.postMessage({\n\t\t\t\t\t\ttype: SW_RESPONSE,\n\t\t\t\t\t\trequestId: data.requestId,\n\t\t\t\t\t\tresponse: {\n\t\t\t\t\t\t\tid: data.request.id,\n\t\t\t\t\t\t\ttype: 'error',\n\t\t\t\t\t\t\tmessage,\n\t\t\t\t\t\t\tcode: 'SHARED_WORKER_CORE_ERROR',\n\t\t\t\t\t\t},\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. The guard uses `instanceof SharedWorkerGlobalScope` so it is true\n// in a real SharedWorker and false everywhere else (Node test runtimes, the main\n// thread, a dedicated worker), which keeps `createSharedWorkerHost` unit-testable\n// in isolation. A connection is delivered as a `connect` event whose `ports[0]` is\n// the client MessagePort.\ndeclare const self: SharedWorkerGlobalScope\nif (\n\ttypeof SharedWorkerGlobalScope !== 'undefined' &&\n\ttypeof self !== 'undefined' &&\n\tself instanceof SharedWorkerGlobalScope\n) {\n\tconst host = createSharedWorkerHost({ createCore: createSqliteWasmCore })\n\n\tself.addEventListener('connect', (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":";;;;;AA8BA,IAAM,aAAa;AACnB,IAAM,cAAc;AAwCb,SAAS,uBAAuB,SAAoD;AAC1F,QAAM,QAAQ,oBAAI,IAA4B;AAE9C,WAAS,QAAQ,QAAgC;AAChD,QAAI,OAAO,MAAM,IAAI,MAAM;AAC3B,QAAI,CAAC,MAAM;AACV,aAAO,QAAQ,WAAW;AAC1B,YAAM,IAAI,QAAQ,IAAI;AAAA,IACvB;AACA,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,OAAO,QAAQ,KAAK,MAAM;AAChC,WAAK,KAAK,OAAO,KAAK,OAAO,EAAE;AAAA,QAC9B,CAAC,aAAa;AACb,eAAK,YAAY,EAAE,MAAM,aAAa,WAAW,KAAK,WAAW,SAAS,CAAC;AAAA,QAC5E;AAAA,QACA,CAAC,UAAmB;AAGnB,gBAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU;AACzD,eAAK,YAAY;AAAA,YAChB,MAAM;AAAA,YACN,WAAW,KAAK;AAAA,YAChB,UAAU;AAAA,cACT,IAAI,KAAK,QAAQ;AAAA,cACjB,MAAM;AAAA,cACN;AAAA,cACA,MAAM;AAAA,YACP;AAAA,UACD,CAAC;AAAA,QACF;AAAA,MACD;AAAA,IACD,CAAC;AAAA,EACF;AAEA,SAAO,EAAE,QAAQ;AAClB;AASA,IACC,OAAO,4BAA4B,eACnC,OAAO,SAAS,eAChB,gBAAgB,yBACf;AACD,QAAM,OAAO,uBAAuB,EAAE,YAAY,qBAAqB,CAAC;AAExE,OAAK,iBAAiB,WAAW,CAAC,UAA8B;AAC/D,UAAM,OAAO,MAAM,MAAM,CAAC;AAC1B,QAAI,CAAC,MAAM;AACV;AAAA,IACD;AACA,SAAK,QAAQ,IAA2B;AAAA,EACzC,CAAC;AACF;","names":[]}
|
package/dist/chunk-3Y2IERBF.js
DELETED
|
@@ -1,255 +0,0 @@
|
|
|
1
|
-
// src/adapters/sqlite-wasm-worker-core.ts
|
|
2
|
-
var OPFS_POOL_NAME = "kora-opfs";
|
|
3
|
-
var OPFS_INIT_TIMEOUT_MS = 1e4;
|
|
4
|
-
async function withTimeout(promise, timeoutMs, label) {
|
|
5
|
-
let timer;
|
|
6
|
-
try {
|
|
7
|
-
return await Promise.race([
|
|
8
|
-
promise,
|
|
9
|
-
new Promise((_, reject) => {
|
|
10
|
-
timer = setTimeout(
|
|
11
|
-
() => reject(new Error(`${label} timed out after ${timeoutMs}ms`)),
|
|
12
|
-
timeoutMs
|
|
13
|
-
);
|
|
14
|
-
})
|
|
15
|
-
]);
|
|
16
|
-
} finally {
|
|
17
|
-
if (timer !== void 0) clearTimeout(timer);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
function opfsDatabaseFilename(dbName) {
|
|
21
|
-
const base = dbName.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
22
|
-
return base.endsWith(".db") ? base : `${base}.db`;
|
|
23
|
-
}
|
|
24
|
-
function classifyOpfsFailure(error) {
|
|
25
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
26
|
-
if (/timed out|timeout/i.test(message)) {
|
|
27
|
-
return "timeout";
|
|
28
|
-
}
|
|
29
|
-
if (/lock|already|in use|in-use|NoModificationAllowed|acquire|SyncAccessHandle|being created/i.test(
|
|
30
|
-
message
|
|
31
|
-
)) {
|
|
32
|
-
return "lock-conflict";
|
|
33
|
-
}
|
|
34
|
-
return "unsupported";
|
|
35
|
-
}
|
|
36
|
-
var sqlite3Promise = null;
|
|
37
|
-
var opfsPoolPromise = null;
|
|
38
|
-
var opfsFallbackReason;
|
|
39
|
-
async function loadSqlite3() {
|
|
40
|
-
const sqlite3InitModule = (await import("@sqlite.org/sqlite-wasm")).default;
|
|
41
|
-
const wasmUrl = globalThis.__KORA_SQLITE_WASM_URL;
|
|
42
|
-
const initOptions = wasmUrl ? {
|
|
43
|
-
locateFile: (file) => file.endsWith(".wasm") ? wasmUrl : file
|
|
44
|
-
} : void 0;
|
|
45
|
-
const initFn = sqlite3InitModule;
|
|
46
|
-
return await withTimeout(initFn(initOptions), 6e4, "SQLite3 module init");
|
|
47
|
-
}
|
|
48
|
-
function getSqlite3() {
|
|
49
|
-
if (!sqlite3Promise) {
|
|
50
|
-
sqlite3Promise = loadSqlite3();
|
|
51
|
-
}
|
|
52
|
-
return sqlite3Promise;
|
|
53
|
-
}
|
|
54
|
-
async function installOpfsPool(sqlite3) {
|
|
55
|
-
if (!sqlite3.installOpfsSAHPoolVfs) {
|
|
56
|
-
opfsFallbackReason = "unsupported";
|
|
57
|
-
return null;
|
|
58
|
-
}
|
|
59
|
-
try {
|
|
60
|
-
return await withTimeout(
|
|
61
|
-
sqlite3.installOpfsSAHPoolVfs({ name: OPFS_POOL_NAME }),
|
|
62
|
-
OPFS_INIT_TIMEOUT_MS,
|
|
63
|
-
"OPFS VFS install"
|
|
64
|
-
);
|
|
65
|
-
} catch (error) {
|
|
66
|
-
opfsFallbackReason = classifyOpfsFailure(error);
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
function getOpfsPool(sqlite3) {
|
|
71
|
-
if (!opfsPoolPromise) {
|
|
72
|
-
opfsPoolPromise = installOpfsPool(sqlite3);
|
|
73
|
-
}
|
|
74
|
-
return opfsPoolPromise;
|
|
75
|
-
}
|
|
76
|
-
function createSqliteWasmCore() {
|
|
77
|
-
let db = null;
|
|
78
|
-
let sqlite3Api = null;
|
|
79
|
-
let persistent = false;
|
|
80
|
-
async function open(id, ddlStatements, dbName) {
|
|
81
|
-
try {
|
|
82
|
-
if (db) {
|
|
83
|
-
applyDdl(db, ddlStatements);
|
|
84
|
-
return { id, type: "success", data: buildOpenData() };
|
|
85
|
-
}
|
|
86
|
-
const sqlite3 = await getSqlite3();
|
|
87
|
-
sqlite3Api = sqlite3;
|
|
88
|
-
const pool = await getOpfsPool(sqlite3);
|
|
89
|
-
if (pool) {
|
|
90
|
-
db = new pool.OpfsSAHPoolDb(opfsDatabaseFilename(dbName ?? "kora-db"));
|
|
91
|
-
persistent = true;
|
|
92
|
-
} else {
|
|
93
|
-
db = new sqlite3.oo1.DB({ filename: ":memory:" });
|
|
94
|
-
persistent = false;
|
|
95
|
-
}
|
|
96
|
-
db.exec({ sql: "PRAGMA journal_mode = WAL" });
|
|
97
|
-
db.exec({ sql: "PRAGMA foreign_keys = ON" });
|
|
98
|
-
applyDdl(db, ddlStatements);
|
|
99
|
-
return { id, type: "success", data: buildOpenData() };
|
|
100
|
-
} catch (error) {
|
|
101
|
-
return { id, type: "error", message: error.message, code: "INIT_ERROR" };
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
function buildOpenData() {
|
|
105
|
-
return {
|
|
106
|
-
persistent,
|
|
107
|
-
...persistent ? {} : { fallbackReason: opfsFallbackReason ?? "unsupported" }
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
function applyDdl(target, ddlStatements) {
|
|
111
|
-
for (const sql of ddlStatements) {
|
|
112
|
-
if (sql.startsWith("--kora:safe-alter")) {
|
|
113
|
-
try {
|
|
114
|
-
target.exec({ sql: sql.replace("--kora:safe-alter\n", "") });
|
|
115
|
-
} catch (error) {
|
|
116
|
-
const msg = error.message || "";
|
|
117
|
-
if (!msg.includes("duplicate column name")) {
|
|
118
|
-
throw error;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
} else {
|
|
122
|
-
target.exec({ sql });
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
function execute(id, sql, params) {
|
|
127
|
-
if (!db) {
|
|
128
|
-
return { id, type: "error", message: "Database is not open", code: "DB_NOT_OPEN" };
|
|
129
|
-
}
|
|
130
|
-
try {
|
|
131
|
-
db.exec({ sql, bind: params });
|
|
132
|
-
return { id, type: "success" };
|
|
133
|
-
} catch (error) {
|
|
134
|
-
return { id, type: "error", message: error.message, code: "EXEC_ERROR" };
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
function query(id, sql, params) {
|
|
138
|
-
if (!db) {
|
|
139
|
-
return { id, type: "error", message: "Database is not open", code: "DB_NOT_OPEN" };
|
|
140
|
-
}
|
|
141
|
-
try {
|
|
142
|
-
const rows = [];
|
|
143
|
-
db.exec({
|
|
144
|
-
sql,
|
|
145
|
-
bind: params,
|
|
146
|
-
rowMode: "object",
|
|
147
|
-
callback: (row) => {
|
|
148
|
-
rows.push({ ...row });
|
|
149
|
-
}
|
|
150
|
-
});
|
|
151
|
-
return { id, type: "success", data: rows };
|
|
152
|
-
} catch (error) {
|
|
153
|
-
return { id, type: "error", message: error.message, code: "QUERY_ERROR" };
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
function close(id) {
|
|
157
|
-
if (db) {
|
|
158
|
-
db.close();
|
|
159
|
-
db = null;
|
|
160
|
-
}
|
|
161
|
-
return { id, type: "success" };
|
|
162
|
-
}
|
|
163
|
-
function migrate(id, statements) {
|
|
164
|
-
if (!db) {
|
|
165
|
-
return { id, type: "error", message: "Database is not open", code: "DB_NOT_OPEN" };
|
|
166
|
-
}
|
|
167
|
-
try {
|
|
168
|
-
for (const sql of statements) {
|
|
169
|
-
db.exec({ sql });
|
|
170
|
-
}
|
|
171
|
-
return { id, type: "success" };
|
|
172
|
-
} catch (error) {
|
|
173
|
-
return { id, type: "error", message: error.message, code: "MIGRATE_ERROR" };
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
function importData(id, data) {
|
|
177
|
-
if (!db) {
|
|
178
|
-
return { id, type: "error", message: "Database is not open", code: "DB_NOT_OPEN" };
|
|
179
|
-
}
|
|
180
|
-
const dbWithDeserialize = db;
|
|
181
|
-
if (typeof dbWithDeserialize.deserialize === "function") {
|
|
182
|
-
try {
|
|
183
|
-
dbWithDeserialize.deserialize(data);
|
|
184
|
-
return { id, type: "success" };
|
|
185
|
-
} catch (error) {
|
|
186
|
-
return { id, type: "error", message: error.message, code: "IMPORT_ERROR" };
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
if (!sqlite3Api || typeof sqlite3Api.capi?.sqlite3_deserialize === "undefined") {
|
|
190
|
-
return {
|
|
191
|
-
id,
|
|
192
|
-
type: "error",
|
|
193
|
-
message: "Import not supported in this SQLite WASM runtime",
|
|
194
|
-
code: "IMPORT_NOT_SUPPORTED"
|
|
195
|
-
};
|
|
196
|
-
}
|
|
197
|
-
return {
|
|
198
|
-
id,
|
|
199
|
-
type: "error",
|
|
200
|
-
message: "Import requires runtime-specific deserialize wiring and is unavailable in this worker build",
|
|
201
|
-
code: "IMPORT_NOT_SUPPORTED"
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
async function handle(request) {
|
|
205
|
-
try {
|
|
206
|
-
switch (request.type) {
|
|
207
|
-
case "open":
|
|
208
|
-
return await open(request.id, request.ddlStatements, request.dbName);
|
|
209
|
-
case "close":
|
|
210
|
-
return close(request.id);
|
|
211
|
-
case "execute":
|
|
212
|
-
return execute(request.id, request.sql, request.params);
|
|
213
|
-
case "query":
|
|
214
|
-
return query(request.id, request.sql, request.params);
|
|
215
|
-
case "begin":
|
|
216
|
-
return execute(request.id, "BEGIN");
|
|
217
|
-
case "commit":
|
|
218
|
-
return execute(request.id, "COMMIT");
|
|
219
|
-
case "rollback":
|
|
220
|
-
return execute(request.id, "ROLLBACK");
|
|
221
|
-
case "migrate":
|
|
222
|
-
return migrate(request.id, request.statements);
|
|
223
|
-
case "import":
|
|
224
|
-
return importData(request.id, request.data);
|
|
225
|
-
case "export":
|
|
226
|
-
return {
|
|
227
|
-
id: request.id,
|
|
228
|
-
type: "error",
|
|
229
|
-
message: "Export not yet supported in browser worker",
|
|
230
|
-
code: "EXPORT_NOT_SUPPORTED"
|
|
231
|
-
};
|
|
232
|
-
default:
|
|
233
|
-
return {
|
|
234
|
-
id: request.id,
|
|
235
|
-
type: "error",
|
|
236
|
-
message: "Unknown request type",
|
|
237
|
-
code: "UNKNOWN_REQUEST"
|
|
238
|
-
};
|
|
239
|
-
}
|
|
240
|
-
} catch (error) {
|
|
241
|
-
return {
|
|
242
|
-
id: request.id,
|
|
243
|
-
type: "error",
|
|
244
|
-
message: error.message,
|
|
245
|
-
code: "WORKER_ERROR"
|
|
246
|
-
};
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
return { handle };
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
export {
|
|
253
|
-
createSqliteWasmCore
|
|
254
|
-
};
|
|
255
|
-
//# sourceMappingURL=chunk-3Y2IERBF.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/adapters/sqlite-wasm-worker-core.ts"],"sourcesContent":["/// <reference lib=\"webworker\" />\n/**\n * Reusable SQLite WASM core, decoupled from any specific worker global.\n *\n * The dedicated worker ({@link file://./sqlite-wasm-worker.ts}) wires one core to\n * `self.onmessage`/`self.postMessage`. The SharedWorker host\n * ({@link file://./sqlite-wasm-shared-host.ts}) runs one core PER database inside\n * a single SharedWorker scope, because a SharedWorker cannot spawn a nested\n * `Worker` (the `Worker` constructor is not defined in `SharedWorkerGlobalScope`\n * in Chromium), so it must run SQLite directly.\n *\n * The WASM module and the OPFS SyncAccessHandle pool are cached at module scope\n * and shared across every core in the same worker/SharedWorker: the pool holds\n * multiple database files keyed by filename, so installing it once and opening\n * each database within it avoids the single-writer conflict that installing the\n * pool twice in one scope would cause.\n *\n * This file cannot be unit-tested in Node (no WASM/OPFS); it is exercised through\n * the browser E2E suite and its dispatch is covered via the shared-host 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\ninterface OpfsPool {\n\tOpfsSAHPoolDb: new (filename: string) => SqliteDb\n}\n\ninterface Sqlite3Api {\n\too1: { DB: new (opts: { filename: string }) => SqliteDb }\n\tinstallOpfsSAHPoolVfs?: (opts: { name: string }) => Promise<OpfsPool>\n\tcapi?: { sqlite3_deserialize?: unknown }\n}\n\n/** OPFS SAH pool name. Shared across all databases on the origin (the pool holds\n * multiple db files keyed by filename), so it must NOT be namespaced per database\n * or existing persisted data would be orphaned. */\nconst OPFS_POOL_NAME = 'kora-opfs'\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\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/**\n * Classify why the OPFS SyncAccessHandle pool could not be installed, so the\n * fallback to in-memory is explainable rather than silent.\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\n// Scope-global caches shared by every core in this worker/SharedWorker.\nlet sqlite3Promise: Promise<Sqlite3Api> | null = null\nlet opfsPoolPromise: Promise<OpfsPool | null> | null = null\nlet opfsFallbackReason: 'lock-conflict' | 'timeout' | 'unsupported' | undefined\n\nasync function loadSqlite3(): Promise<Sqlite3Api> {\n\tconst sqlite3InitModule = (await import('@sqlite.org/sqlite-wasm')).default\n\t// In production builds, Vite hashes asset filenames (e.g. sqlite3-[hash].wasm).\n\t// The sqlite3 module's default locateFile resolves the unhashed name, causing a\n\t// 404. The worker/host entry sets __KORA_SQLITE_WASM_URL via a `?url` import so\n\t// we can override locateFile with the correct hashed URL.\n\tconst wasmUrl = (globalThis as Record<string, unknown>).__KORA_SQLITE_WASM_URL as\n\t\t| string\n\t\t| undefined\n\tconst initOptions = wasmUrl\n\t\t? {\n\t\t\t\tlocateFile: (file: string): string => (file.endsWith('.wasm') ? wasmUrl : file),\n\t\t\t}\n\t\t: undefined\n\tconst initFn = sqlite3InitModule as unknown as (\n\t\topts?: Record<string, unknown>,\n\t) => Promise<unknown>\n\treturn (await withTimeout(initFn(initOptions), 60_000, 'SQLite3 module init')) as Sqlite3Api\n}\n\nfunction getSqlite3(): Promise<Sqlite3Api> {\n\tif (!sqlite3Promise) {\n\t\tsqlite3Promise = loadSqlite3()\n\t}\n\treturn sqlite3Promise\n}\n\nasync function installOpfsPool(sqlite3: Sqlite3Api): Promise<OpfsPool | null> {\n\tif (!sqlite3.installOpfsSAHPoolVfs) {\n\t\topfsFallbackReason = 'unsupported'\n\t\treturn null\n\t}\n\ttry {\n\t\treturn await withTimeout(\n\t\t\tsqlite3.installOpfsSAHPoolVfs({ name: OPFS_POOL_NAME }),\n\t\t\tOPFS_INIT_TIMEOUT_MS,\n\t\t\t'OPFS VFS install',\n\t\t)\n\t} catch (error) {\n\t\topfsFallbackReason = classifyOpfsFailure(error)\n\t\treturn null\n\t}\n}\n\nfunction getOpfsPool(sqlite3: Sqlite3Api): Promise<OpfsPool | null> {\n\tif (!opfsPoolPromise) {\n\t\topfsPoolPromise = installOpfsPool(sqlite3)\n\t}\n\treturn opfsPoolPromise\n}\n\n/** Handle to a single database, dispatching the worker protocol for it. */\nexport interface SqliteWasmCore {\n\thandle(request: WorkerRequest): Promise<WorkerResponse>\n}\n\n/**\n * Creates a core bound to one database. `handle` resolves with the response for\n * each request; the caller routes it back to whichever transport delivered the\n * request (the dedicated worker's `postMessage`, or a SharedWorker client port).\n */\nexport function createSqliteWasmCore(): SqliteWasmCore {\n\tlet db: SqliteDb | null = null\n\tlet sqlite3Api: Sqlite3Api | null = null\n\tlet persistent = false\n\n\tasync function open(\n\t\tid: number,\n\t\tddlStatements: string[],\n\t\tdbName?: string,\n\t): Promise<WorkerResponse> {\n\t\ttry {\n\t\t\t// A shared core (one per database in a SharedWorker) may receive `open`\n\t\t\t// from more than one tab. Re-run the idempotent DDL on the existing handle\n\t\t\t// rather than opening the database a second time.\n\t\t\tif (db) {\n\t\t\t\tapplyDdl(db, ddlStatements)\n\t\t\t\treturn { id, type: 'success', data: buildOpenData() }\n\t\t\t}\n\n\t\t\tconst sqlite3 = await getSqlite3()\n\t\t\tsqlite3Api = sqlite3\n\t\t\tconst pool = await getOpfsPool(sqlite3)\n\n\t\t\tif (pool) {\n\t\t\t\tdb = new pool.OpfsSAHPoolDb(opfsDatabaseFilename(dbName ?? 'kora-db'))\n\t\t\t\tpersistent = true\n\t\t\t} else {\n\t\t\t\tdb = new sqlite3.oo1.DB({ filename: ':memory:' })\n\t\t\t\tpersistent = false\n\t\t\t}\n\n\t\t\tdb.exec({ sql: 'PRAGMA journal_mode = WAL' })\n\t\t\tdb.exec({ sql: 'PRAGMA foreign_keys = ON' })\n\n\t\t\tapplyDdl(db, ddlStatements)\n\t\t\treturn { id, type: 'success', data: buildOpenData() }\n\t\t} catch (error) {\n\t\t\treturn { id, type: 'error', message: (error as Error).message, code: 'INIT_ERROR' }\n\t\t}\n\t}\n\n\tfunction buildOpenData(): { persistent: boolean; fallbackReason?: string } {\n\t\treturn {\n\t\t\tpersistent,\n\t\t\t...(persistent ? {} : { fallbackReason: opfsFallbackReason ?? 'unsupported' }),\n\t\t}\n\t}\n\n\tfunction applyDdl(target: SqliteDb, ddlStatements: string[]): void {\n\t\tfor (const sql of ddlStatements) {\n\t\t\tif (sql.startsWith('--kora:safe-alter')) {\n\t\t\t\ttry {\n\t\t\t\t\ttarget.exec({ sql: sql.replace('--kora:safe-alter\\n', '') })\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconst msg = (error as Error).message || ''\n\t\t\t\t\tif (!msg.includes('duplicate column name')) {\n\t\t\t\t\t\tthrow error\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\ttarget.exec({ sql })\n\t\t\t}\n\t\t}\n\t}\n\n\tfunction execute(id: number, sql: string, params?: unknown[]): WorkerResponse {\n\t\tif (!db) {\n\t\t\treturn { id, type: 'error', message: 'Database is not open', code: 'DB_NOT_OPEN' }\n\t\t}\n\t\ttry {\n\t\t\tdb.exec({ sql, bind: params })\n\t\t\treturn { id, type: 'success' }\n\t\t} catch (error) {\n\t\t\treturn { id, type: 'error', message: (error as Error).message, code: 'EXEC_ERROR' }\n\t\t}\n\t}\n\n\tfunction query(id: number, sql: string, params?: unknown[]): WorkerResponse {\n\t\tif (!db) {\n\t\t\treturn { id, type: 'error', message: 'Database is not open', code: 'DB_NOT_OPEN' }\n\t\t}\n\t\ttry {\n\t\t\tconst rows: Record<string, unknown>[] = []\n\t\t\tdb.exec({\n\t\t\t\tsql,\n\t\t\t\tbind: params,\n\t\t\t\trowMode: 'object',\n\t\t\t\tcallback: (row: Record<string, unknown>) => {\n\t\t\t\t\trows.push({ ...row })\n\t\t\t\t},\n\t\t\t})\n\t\t\treturn { id, type: 'success', data: rows }\n\t\t} catch (error) {\n\t\t\treturn { id, type: 'error', message: (error as Error).message, code: 'QUERY_ERROR' }\n\t\t}\n\t}\n\n\tfunction close(id: number): WorkerResponse {\n\t\tif (db) {\n\t\t\tdb.close()\n\t\t\tdb = null\n\t\t}\n\t\treturn { id, type: 'success' }\n\t}\n\n\tfunction migrate(id: number, statements: string[]): WorkerResponse {\n\t\tif (!db) {\n\t\t\treturn { id, type: 'error', message: 'Database is not open', code: 'DB_NOT_OPEN' }\n\t\t}\n\t\ttry {\n\t\t\tfor (const sql of statements) {\n\t\t\t\tdb.exec({ sql })\n\t\t\t}\n\t\t\treturn { id, type: 'success' }\n\t\t} catch (error) {\n\t\t\treturn { id, type: 'error', message: (error as Error).message, code: 'MIGRATE_ERROR' }\n\t\t}\n\t}\n\n\tfunction importData(id: number, data: Uint8Array): WorkerResponse {\n\t\tif (!db) {\n\t\t\treturn { id, type: 'error', message: 'Database is not open', code: 'DB_NOT_OPEN' }\n\t\t}\n\t\tconst dbWithDeserialize = db as SqliteDb & { deserialize?: (bytes: Uint8Array) => void }\n\t\tif (typeof dbWithDeserialize.deserialize === 'function') {\n\t\t\ttry {\n\t\t\t\tdbWithDeserialize.deserialize(data)\n\t\t\t\treturn { id, type: 'success' }\n\t\t\t} catch (error) {\n\t\t\t\treturn { id, type: 'error', message: (error as Error).message, code: 'IMPORT_ERROR' }\n\t\t\t}\n\t\t}\n\t\tif (!sqlite3Api || typeof sqlite3Api.capi?.sqlite3_deserialize === 'undefined') {\n\t\t\treturn {\n\t\t\t\tid,\n\t\t\t\ttype: 'error',\n\t\t\t\tmessage: 'Import not supported in this SQLite WASM runtime',\n\t\t\t\tcode: 'IMPORT_NOT_SUPPORTED',\n\t\t\t}\n\t\t}\n\t\treturn {\n\t\t\tid,\n\t\t\ttype: 'error',\n\t\t\tmessage:\n\t\t\t\t'Import requires runtime-specific deserialize wiring and is unavailable in this worker build',\n\t\t\tcode: 'IMPORT_NOT_SUPPORTED',\n\t\t}\n\t}\n\n\tasync function handle(request: WorkerRequest): Promise<WorkerResponse> {\n\t\ttry {\n\t\t\tswitch (request.type) {\n\t\t\t\tcase 'open':\n\t\t\t\t\treturn await open(request.id, request.ddlStatements, request.dbName)\n\t\t\t\tcase 'close':\n\t\t\t\t\treturn close(request.id)\n\t\t\t\tcase 'execute':\n\t\t\t\t\treturn execute(request.id, request.sql, request.params)\n\t\t\t\tcase 'query':\n\t\t\t\t\treturn query(request.id, request.sql, request.params)\n\t\t\t\tcase 'begin':\n\t\t\t\t\treturn execute(request.id, 'BEGIN')\n\t\t\t\tcase 'commit':\n\t\t\t\t\treturn execute(request.id, 'COMMIT')\n\t\t\t\tcase 'rollback':\n\t\t\t\t\treturn execute(request.id, 'ROLLBACK')\n\t\t\t\tcase 'migrate':\n\t\t\t\t\treturn migrate(request.id, request.statements)\n\t\t\t\tcase 'import':\n\t\t\t\t\treturn importData(request.id, request.data)\n\t\t\t\tcase 'export':\n\t\t\t\t\treturn {\n\t\t\t\t\t\tid: request.id,\n\t\t\t\t\t\ttype: 'error',\n\t\t\t\t\t\tmessage: 'Export not yet supported in browser worker',\n\t\t\t\t\t\tcode: 'EXPORT_NOT_SUPPORTED',\n\t\t\t\t\t}\n\t\t\t\tdefault:\n\t\t\t\t\treturn {\n\t\t\t\t\t\tid: (request as WorkerRequest).id,\n\t\t\t\t\t\ttype: 'error',\n\t\t\t\t\t\tmessage: 'Unknown request type',\n\t\t\t\t\t\tcode: 'UNKNOWN_REQUEST',\n\t\t\t\t\t}\n\t\t\t}\n\t\t} catch (error) {\n\t\t\treturn {\n\t\t\t\tid: request.id,\n\t\t\t\ttype: 'error',\n\t\t\t\tmessage: (error as Error).message,\n\t\t\t\tcode: 'WORKER_ERROR',\n\t\t\t}\n\t\t}\n\t}\n\n\treturn { handle }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/adapters/sqlite-wasm-channel.ts"],"sourcesContent":["/// <reference lib=\"dom\" />\nimport { WorkerTimeoutError } from '../errors'\n\n// === Message Protocol ===\n\n/**\n * Request message sent from the main thread to the SQLite WASM worker.\n * Each request carries a unique `id` for response correlation.\n */\nexport type WorkerRequest =\n\t| { id: number; type: 'open'; ddlStatements: string[]; dbName?: string }\n\t| { id: number; type: 'close' }\n\t| { id: number; type: 'execute'; sql: string; params?: unknown[] }\n\t| { id: number; type: 'query'; sql: string; params?: unknown[] }\n\t| { id: number; type: 'begin' }\n\t| { id: number; type: 'commit' }\n\t| { id: number; type: 'rollback' }\n\t| { id: number; type: 'migrate'; from: number; to: number; statements: string[] }\n\t| { id: number; type: 'export' }\n\t| { id: number; type: 'import'; data: Uint8Array }\n\n/**\n * Response message sent from the worker back to the main thread.\n * Matches the request `id` for correlation.\n */\nexport type WorkerResponse =\n\t| { id: number; type: 'success'; data?: unknown }\n\t| { id: number; type: 'error'; message: string; code: string; context?: Record<string, unknown> }\n\n// === WorkerBridge Interface ===\n\n/**\n * Abstraction over the communication channel with the SQLite WASM worker.\n * In browsers, this is backed by a real Web Worker via MessagePort.\n * In Node.js tests, this is backed by better-sqlite3 via MockWorkerBridge.\n */\nexport interface WorkerBridge {\n\t/** Send a request to the worker and wait for a response. */\n\tsend(request: WorkerRequest): Promise<WorkerResponse>\n\n\t/** Terminate the worker. Safe to call multiple times. */\n\tterminate(): void\n}\n\n// === Mutex ===\n\n// The Mutex now lives in its own module so non-browser adapters (better-sqlite3)\n// can serialize transactions without importing browser-worker code.\nexport { Mutex } from './mutex'\n\n// === WebWorkerBridge ===\n\n/**\n * WorkerBridge implementation for browser environments.\n * Communicates with an actual Web Worker running SQLite WASM.\n */\nexport class WebWorkerBridge implements WorkerBridge {\n\tprivate worker: Worker\n\tprivate pending = new Map<\n\t\tnumber,\n\t\t{ resolve: (r: WorkerResponse) => void; reject: (e: Error) => void }\n\t>()\n\tprivate nextId = 1\n\tprivate terminated = false\n\tprivate timeoutMs: number\n\n\t/**\n\t * @param workerUrl - URL to the sqlite-wasm-worker script\n\t * @param timeoutMs - Timeout for worker responses in milliseconds (default: 30000)\n\t */\n\tconstructor(workerUrl: string | URL, timeoutMs = 30000) {\n\t\tthis.timeoutMs = timeoutMs\n\t\tthis.worker = new Worker(workerUrl, { type: 'module' })\n\t\tthis.worker.onmessage = (event: MessageEvent<WorkerResponse>) => {\n\t\t\tconst response = event.data\n\t\t\tconst entry = this.pending.get(response.id)\n\t\t\tif (entry) {\n\t\t\t\tthis.pending.delete(response.id)\n\t\t\t\tentry.resolve(response)\n\t\t\t}\n\t\t}\n\t\tthis.worker.onerror = (event) => {\n\t\t\t// Reject all pending requests on worker error\n\t\t\tconst error = new Error(`Worker error: ${event.message}`)\n\t\t\tfor (const [id, entry] of this.pending) {\n\t\t\t\tthis.pending.delete(id)\n\t\t\t\tentry.reject(error)\n\t\t\t}\n\t\t}\n\t}\n\n\tasync send(request: WorkerRequest): Promise<WorkerResponse> {\n\t\tif (this.terminated) {\n\t\t\treturn {\n\t\t\t\tid: request.id,\n\t\t\t\ttype: 'error',\n\t\t\t\tmessage: 'Worker has been terminated',\n\t\t\t\tcode: 'WORKER_TERMINATED',\n\t\t\t}\n\t\t}\n\n\t\tconst id = this.nextId++\n\t\tconst req = { ...request, id }\n\n\t\treturn new Promise<WorkerResponse>((resolve, reject) => {\n\t\t\tconst timer = setTimeout(() => {\n\t\t\t\tthis.pending.delete(id)\n\t\t\t\treject(new WorkerTimeoutError(req.type, this.timeoutMs))\n\t\t\t}, this.timeoutMs)\n\n\t\t\tthis.pending.set(id, {\n\t\t\t\tresolve: (response) => {\n\t\t\t\t\tclearTimeout(timer)\n\t\t\t\t\tresolve(response)\n\t\t\t\t},\n\t\t\t\treject: (error) => {\n\t\t\t\t\tclearTimeout(timer)\n\t\t\t\t\treject(error)\n\t\t\t\t},\n\t\t\t})\n\n\t\t\tthis.worker.postMessage(req)\n\t\t})\n\t}\n\n\tterminate(): void {\n\t\tif (this.terminated) return\n\t\tthis.terminated = true\n\t\tthis.worker.terminate()\n\t\t// Reject any pending requests\n\t\tfor (const [id, entry] of this.pending) {\n\t\t\tthis.pending.delete(id)\n\t\t\tentry.reject(new Error('Worker terminated'))\n\t\t}\n\t}\n}\n"],"mappings":";;;;;AAwDO,IAAM,kBAAN,MAA8C;AAAA,EAC5C;AAAA,EACA,UAAU,oBAAI,IAGpB;AAAA,EACM,SAAS;AAAA,EACT,aAAa;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA,EAMR,YAAY,WAAyB,YAAY,KAAO;AACvD,SAAK,YAAY;AACjB,SAAK,SAAS,IAAI,OAAO,WAAW,EAAE,MAAM,SAAS,CAAC;AACtD,SAAK,OAAO,YAAY,CAAC,UAAwC;AAChE,YAAM,WAAW,MAAM;AACvB,YAAM,QAAQ,KAAK,QAAQ,IAAI,SAAS,EAAE;AAC1C,UAAI,OAAO;AACV,aAAK,QAAQ,OAAO,SAAS,EAAE;AAC/B,cAAM,QAAQ,QAAQ;AAAA,MACvB;AAAA,IACD;AACA,SAAK,OAAO,UAAU,CAAC,UAAU;AAEhC,YAAM,QAAQ,IAAI,MAAM,iBAAiB,MAAM,OAAO,EAAE;AACxD,iBAAW,CAAC,IAAI,KAAK,KAAK,KAAK,SAAS;AACvC,aAAK,QAAQ,OAAO,EAAE;AACtB,cAAM,OAAO,KAAK;AAAA,MACnB;AAAA,IACD;AAAA,EACD;AAAA,EAEA,MAAM,KAAK,SAAiD;AAC3D,QAAI,KAAK,YAAY;AACpB,aAAO;AAAA,QACN,IAAI,QAAQ;AAAA,QACZ,MAAM;AAAA,QACN,SAAS;AAAA,QACT,MAAM;AAAA,MACP;AAAA,IACD;AAEA,UAAM,KAAK,KAAK;AAChB,UAAM,MAAM,EAAE,GAAG,SAAS,GAAG;AAE7B,WAAO,IAAI,QAAwB,CAAC,SAAS,WAAW;AACvD,YAAM,QAAQ,WAAW,MAAM;AAC9B,aAAK,QAAQ,OAAO,EAAE;AACtB,eAAO,IAAI,mBAAmB,IAAI,MAAM,KAAK,SAAS,CAAC;AAAA,MACxD,GAAG,KAAK,SAAS;AAEjB,WAAK,QAAQ,IAAI,IAAI;AAAA,QACpB,SAAS,CAAC,aAAa;AACtB,uBAAa,KAAK;AAClB,kBAAQ,QAAQ;AAAA,QACjB;AAAA,QACA,QAAQ,CAAC,UAAU;AAClB,uBAAa,KAAK;AAClB,iBAAO,KAAK;AAAA,QACb;AAAA,MACD,CAAC;AAED,WAAK,OAAO,YAAY,GAAG;AAAA,IAC5B,CAAC;AAAA,EACF;AAAA,EAEA,YAAkB;AACjB,QAAI,KAAK,WAAY;AACrB,SAAK,aAAa;AAClB,SAAK,OAAO,UAAU;AAEtB,eAAW,CAAC,IAAI,KAAK,KAAK,KAAK,SAAS;AACvC,WAAK,QAAQ,OAAO,EAAE;AACtB,YAAM,OAAO,IAAI,MAAM,mBAAmB,CAAC;AAAA,IAC5C;AAAA,EACD;AACD;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/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":[]}
|
|
File without changes
|
|
File without changes
|