@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
|
@@ -11,91 +11,14 @@ import {
|
|
|
11
11
|
// src/adapters/sqlite-wasm-adapter.ts
|
|
12
12
|
import { generateFullDDL } from "@korajs/core";
|
|
13
13
|
|
|
14
|
-
// src/multi-tab/shared-worker-bridge.ts
|
|
15
|
-
var SW_REQUEST = "kora-sw-request";
|
|
16
|
-
var SW_RESPONSE = "kora-sw-response";
|
|
17
|
-
var SharedWorkerClientBridge = class {
|
|
18
|
-
constructor(sharedWorker, dbName, workerUrl, timeoutMs = 3e4) {
|
|
19
|
-
this.dbName = dbName;
|
|
20
|
-
this.workerUrl = workerUrl;
|
|
21
|
-
this.timeoutMs = timeoutMs;
|
|
22
|
-
this.port = sharedWorker.port;
|
|
23
|
-
this.port.start();
|
|
24
|
-
this.port.addEventListener("message", (event) => {
|
|
25
|
-
const data = event.data;
|
|
26
|
-
if (data?.type !== SW_RESPONSE) {
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
const entry = this.pending.get(data.requestId);
|
|
30
|
-
if (entry) {
|
|
31
|
-
this.pending.delete(data.requestId);
|
|
32
|
-
entry.resolve(data.response);
|
|
33
|
-
}
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
dbName;
|
|
37
|
-
workerUrl;
|
|
38
|
-
port;
|
|
39
|
-
pending = /* @__PURE__ */ new Map();
|
|
40
|
-
timeoutMs;
|
|
41
|
-
terminated = false;
|
|
42
|
-
async send(request) {
|
|
43
|
-
if (this.terminated) {
|
|
44
|
-
return {
|
|
45
|
-
id: request.id,
|
|
46
|
-
type: "error",
|
|
47
|
-
message: "SharedWorker bridge terminated",
|
|
48
|
-
code: "BRIDGE_TERMINATED"
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
const requestId = crypto.randomUUID();
|
|
52
|
-
const msg = {
|
|
53
|
-
type: SW_REQUEST,
|
|
54
|
-
requestId,
|
|
55
|
-
dbName: this.dbName,
|
|
56
|
-
workerUrl: this.workerUrl,
|
|
57
|
-
request
|
|
58
|
-
};
|
|
59
|
-
return new Promise((resolve, reject) => {
|
|
60
|
-
const timer = setTimeout(() => {
|
|
61
|
-
this.pending.delete(requestId);
|
|
62
|
-
reject(new WorkerTimeoutError(`shared-worker:${request.type}`, this.timeoutMs));
|
|
63
|
-
}, this.timeoutMs);
|
|
64
|
-
this.pending.set(requestId, {
|
|
65
|
-
resolve: (response) => {
|
|
66
|
-
clearTimeout(timer);
|
|
67
|
-
resolve(response);
|
|
68
|
-
},
|
|
69
|
-
reject: (error) => {
|
|
70
|
-
clearTimeout(timer);
|
|
71
|
-
reject(error);
|
|
72
|
-
}
|
|
73
|
-
});
|
|
74
|
-
this.port.postMessage(msg);
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
terminate() {
|
|
78
|
-
if (this.terminated) {
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
this.terminated = true;
|
|
82
|
-
this.port.close();
|
|
83
|
-
for (const [, entry] of this.pending) {
|
|
84
|
-
entry.reject(new Error("SharedWorker bridge terminated"));
|
|
85
|
-
}
|
|
86
|
-
this.pending.clear();
|
|
87
|
-
}
|
|
88
|
-
};
|
|
89
|
-
|
|
90
14
|
// src/multi-tab/tab-storage.ts
|
|
91
15
|
var RPC_REQUEST = "kora-worker-request";
|
|
92
16
|
var RPC_RESPONSE = "kora-worker-response";
|
|
17
|
+
var CLIENT_LEAVE = "kora-client-leave";
|
|
93
18
|
var LEADER_PING = "kora-leader-ping";
|
|
94
19
|
var LEADER_PONG = "kora-leader-pong";
|
|
95
20
|
var DEFAULT_LIVENESS_PROBE_MS = 2e3;
|
|
96
|
-
|
|
97
|
-
return typeof globalThis !== "undefined" && typeof SharedWorker !== "undefined";
|
|
98
|
-
}
|
|
21
|
+
var DEFAULT_TRANSACTION_IDLE_TIMEOUT_MS = 1e4;
|
|
99
22
|
function isMultiTabStorageSupported() {
|
|
100
23
|
return typeof globalThis !== "undefined" && typeof BroadcastChannel !== "undefined" && typeof navigator !== "undefined" && typeof navigator.locks?.request === "function";
|
|
101
24
|
}
|
|
@@ -157,10 +80,16 @@ function startLeaderRpcRelay(channelName, bridge) {
|
|
|
157
80
|
channel.postMessage({ type: LEADER_PONG });
|
|
158
81
|
return;
|
|
159
82
|
}
|
|
83
|
+
if (data?.type === CLIENT_LEAVE) {
|
|
84
|
+
if (hasReclaimClient(bridge)) {
|
|
85
|
+
bridge.reclaimClient(data.clientId, "client-left");
|
|
86
|
+
}
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
160
89
|
if (data?.type !== RPC_REQUEST) {
|
|
161
90
|
return;
|
|
162
91
|
}
|
|
163
|
-
void bridge.send(data.request).then((response) => {
|
|
92
|
+
void bridge.send(data.request, data.clientId).then((response) => {
|
|
164
93
|
const msg = {
|
|
165
94
|
type: RPC_RESPONSE,
|
|
166
95
|
requestId: data.requestId,
|
|
@@ -190,6 +119,10 @@ function startLeaderRpcRelay(channelName, bridge) {
|
|
|
190
119
|
}
|
|
191
120
|
var FollowerBroadcastBridge = class {
|
|
192
121
|
channel;
|
|
122
|
+
clientId = createClientId();
|
|
123
|
+
onPageHide = () => {
|
|
124
|
+
this.leaveLeader();
|
|
125
|
+
};
|
|
193
126
|
pending = /* @__PURE__ */ new Map();
|
|
194
127
|
timeoutMs;
|
|
195
128
|
livenessProbeMs;
|
|
@@ -198,6 +131,9 @@ var FollowerBroadcastBridge = class {
|
|
|
198
131
|
this.timeoutMs = timeoutMs;
|
|
199
132
|
this.livenessProbeMs = Math.min(livenessProbeMs, timeoutMs);
|
|
200
133
|
this.channel = new BroadcastChannel(channelName);
|
|
134
|
+
if (typeof addEventListener === "function") {
|
|
135
|
+
addEventListener("pagehide", this.onPageHide);
|
|
136
|
+
}
|
|
201
137
|
this.channel.addEventListener("message", (event) => {
|
|
202
138
|
const data = event.data;
|
|
203
139
|
if (data?.type !== RPC_RESPONSE) {
|
|
@@ -261,8 +197,13 @@ var FollowerBroadcastBridge = class {
|
|
|
261
197
|
code: "BRIDGE_TERMINATED"
|
|
262
198
|
};
|
|
263
199
|
}
|
|
264
|
-
const requestId =
|
|
265
|
-
const msg = {
|
|
200
|
+
const requestId = createClientId();
|
|
201
|
+
const msg = {
|
|
202
|
+
type: RPC_REQUEST,
|
|
203
|
+
requestId,
|
|
204
|
+
clientId: this.clientId,
|
|
205
|
+
request
|
|
206
|
+
};
|
|
266
207
|
return new Promise((resolve, reject) => {
|
|
267
208
|
const settle = (fn) => {
|
|
268
209
|
clearTimeout(timer);
|
|
@@ -292,15 +233,212 @@ var FollowerBroadcastBridge = class {
|
|
|
292
233
|
return;
|
|
293
234
|
}
|
|
294
235
|
this.terminated = true;
|
|
236
|
+
this.leaveLeader();
|
|
237
|
+
if (typeof removeEventListener === "function") {
|
|
238
|
+
removeEventListener("pagehide", this.onPageHide);
|
|
239
|
+
}
|
|
295
240
|
this.channel.close();
|
|
296
241
|
for (const [, entry] of this.pending) {
|
|
297
242
|
entry.reject(new Error("Follower bridge terminated"));
|
|
298
243
|
}
|
|
299
244
|
this.pending.clear();
|
|
300
245
|
}
|
|
246
|
+
leaveLeader() {
|
|
247
|
+
try {
|
|
248
|
+
const message = {
|
|
249
|
+
type: CLIENT_LEAVE,
|
|
250
|
+
clientId: this.clientId
|
|
251
|
+
};
|
|
252
|
+
this.channel.postMessage(message);
|
|
253
|
+
} catch {
|
|
254
|
+
}
|
|
255
|
+
}
|
|
301
256
|
};
|
|
257
|
+
var TransactionSerializingWorkerBridge = class {
|
|
258
|
+
inner;
|
|
259
|
+
leaderClientId = createClientId();
|
|
260
|
+
transactionIdleTimeoutMs;
|
|
261
|
+
queue = [];
|
|
262
|
+
activeTransactionClient = null;
|
|
263
|
+
abortedClients = /* @__PURE__ */ new Set();
|
|
264
|
+
transactionIdleTimer = null;
|
|
265
|
+
reclaiming = false;
|
|
266
|
+
processing = false;
|
|
267
|
+
terminated = false;
|
|
268
|
+
constructor(inner, transactionIdleTimeoutMs = DEFAULT_TRANSACTION_IDLE_TIMEOUT_MS) {
|
|
269
|
+
this.inner = inner;
|
|
270
|
+
this.transactionIdleTimeoutMs = transactionIdleTimeoutMs;
|
|
271
|
+
}
|
|
272
|
+
send(request, clientId = this.leaderClientId) {
|
|
273
|
+
if (this.terminated) {
|
|
274
|
+
return Promise.resolve({
|
|
275
|
+
id: request.id,
|
|
276
|
+
type: "error",
|
|
277
|
+
message: "Worker has been terminated",
|
|
278
|
+
code: "WORKER_TERMINATED"
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
if (this.abortedClients.has(clientId)) {
|
|
282
|
+
if (request.type === "begin") {
|
|
283
|
+
this.abortedClients.delete(clientId);
|
|
284
|
+
} else {
|
|
285
|
+
return Promise.resolve({
|
|
286
|
+
id: request.id,
|
|
287
|
+
type: "error",
|
|
288
|
+
message: "Previous transaction was aborted because the client stopped sending requests.",
|
|
289
|
+
code: "TRANSACTION_ABORTED"
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return new Promise((resolve, reject) => {
|
|
294
|
+
this.queue.push({ clientId, request, resolve, reject });
|
|
295
|
+
void this.processQueue();
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
terminate() {
|
|
299
|
+
if (this.terminated) {
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
this.terminated = true;
|
|
303
|
+
this.clearTransactionIdleTimer();
|
|
304
|
+
this.inner.terminate();
|
|
305
|
+
const pending = this.queue.splice(0);
|
|
306
|
+
for (const entry of pending) {
|
|
307
|
+
entry.reject(new Error("Worker terminated"));
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
reclaimClient(clientId, reason) {
|
|
311
|
+
if (this.terminated) {
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
void this.reclaimClientNow(clientId, reason);
|
|
315
|
+
}
|
|
316
|
+
async processQueue() {
|
|
317
|
+
if (this.processing || this.reclaiming) {
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
this.processing = true;
|
|
321
|
+
try {
|
|
322
|
+
while (!this.terminated) {
|
|
323
|
+
const index = this.nextRunnableIndex();
|
|
324
|
+
if (index === -1) {
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
const [entry] = this.queue.splice(index, 1);
|
|
328
|
+
if (!entry) {
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
try {
|
|
332
|
+
const response = await this.inner.send(entry.request);
|
|
333
|
+
this.recordTransactionState(entry, response);
|
|
334
|
+
entry.resolve(response);
|
|
335
|
+
} catch (error) {
|
|
336
|
+
this.resetFailedTransaction(entry);
|
|
337
|
+
entry.reject(error instanceof Error ? error : new Error(String(error)));
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
} finally {
|
|
341
|
+
this.processing = false;
|
|
342
|
+
if (!this.terminated && !this.reclaiming && this.nextRunnableIndex() !== -1) {
|
|
343
|
+
void this.processQueue();
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
nextRunnableIndex() {
|
|
348
|
+
if (this.queue.length === 0) {
|
|
349
|
+
return -1;
|
|
350
|
+
}
|
|
351
|
+
if (this.activeTransactionClient === null) {
|
|
352
|
+
return 0;
|
|
353
|
+
}
|
|
354
|
+
return this.queue.findIndex((entry) => entry.clientId === this.activeTransactionClient);
|
|
355
|
+
}
|
|
356
|
+
recordTransactionState(entry, response) {
|
|
357
|
+
if (response.type === "error") {
|
|
358
|
+
this.resetFailedTransaction(entry);
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
if (entry.request.type === "begin") {
|
|
362
|
+
this.activeTransactionClient = entry.clientId;
|
|
363
|
+
this.armTransactionIdleTimer(entry.clientId);
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
if (this.activeTransactionClient === entry.clientId) {
|
|
367
|
+
this.armTransactionIdleTimer(entry.clientId);
|
|
368
|
+
}
|
|
369
|
+
if (this.activeTransactionClient === entry.clientId && (entry.request.type === "commit" || entry.request.type === "rollback")) {
|
|
370
|
+
this.activeTransactionClient = null;
|
|
371
|
+
this.clearTransactionIdleTimer();
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
resetFailedTransaction(entry) {
|
|
375
|
+
if (this.activeTransactionClient === entry.clientId) {
|
|
376
|
+
this.activeTransactionClient = null;
|
|
377
|
+
this.clearTransactionIdleTimer();
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
armTransactionIdleTimer(clientId) {
|
|
381
|
+
this.clearTransactionIdleTimer();
|
|
382
|
+
this.transactionIdleTimer = setTimeout(() => {
|
|
383
|
+
void this.reclaimClientNow(clientId, "transaction-idle-timeout");
|
|
384
|
+
}, this.transactionIdleTimeoutMs);
|
|
385
|
+
}
|
|
386
|
+
clearTransactionIdleTimer() {
|
|
387
|
+
if (this.transactionIdleTimer) {
|
|
388
|
+
clearTimeout(this.transactionIdleTimer);
|
|
389
|
+
this.transactionIdleTimer = null;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
async reclaimClientNow(clientId, reason) {
|
|
393
|
+
if (this.reclaiming || this.activeTransactionClient !== clientId) {
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
this.reclaiming = true;
|
|
397
|
+
this.clearTransactionIdleTimer();
|
|
398
|
+
this.activeTransactionClient = null;
|
|
399
|
+
this.abortedClients.add(clientId);
|
|
400
|
+
this.rejectQueuedClientRequests(clientId, reason);
|
|
401
|
+
try {
|
|
402
|
+
const response = await this.inner.send({ id: 0, type: "rollback" });
|
|
403
|
+
if (response.type === "error") {
|
|
404
|
+
}
|
|
405
|
+
} catch {
|
|
406
|
+
} finally {
|
|
407
|
+
this.reclaiming = false;
|
|
408
|
+
if (!this.terminated && this.nextRunnableIndex() !== -1) {
|
|
409
|
+
void this.processQueue();
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
rejectQueuedClientRequests(clientId, reason) {
|
|
414
|
+
const keep = [];
|
|
415
|
+
for (const entry of this.queue) {
|
|
416
|
+
if (entry.clientId === clientId) {
|
|
417
|
+
entry.resolve({
|
|
418
|
+
id: entry.request.id,
|
|
419
|
+
type: "error",
|
|
420
|
+
message: `Transaction client was reclaimed: ${reason}`,
|
|
421
|
+
code: "TRANSACTION_ABORTED"
|
|
422
|
+
});
|
|
423
|
+
} else {
|
|
424
|
+
keep.push(entry);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
this.queue = keep;
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
function hasReclaimClient(bridge) {
|
|
431
|
+
return typeof bridge.reclaimClient === "function";
|
|
432
|
+
}
|
|
433
|
+
function createClientId() {
|
|
434
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
435
|
+
return crypto.randomUUID();
|
|
436
|
+
}
|
|
437
|
+
return `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
438
|
+
}
|
|
302
439
|
|
|
303
440
|
// src/adapters/sqlite-wasm-adapter.ts
|
|
441
|
+
var warnedSharedWorkerDeprecated = false;
|
|
304
442
|
var SqliteWasmAdapter = class {
|
|
305
443
|
bridge = null;
|
|
306
444
|
opened = false;
|
|
@@ -312,7 +450,8 @@ var SqliteWasmAdapter = class {
|
|
|
312
450
|
dbName;
|
|
313
451
|
emitter;
|
|
314
452
|
tabSession = null;
|
|
315
|
-
|
|
453
|
+
emitNonPersistentDiagnostic;
|
|
454
|
+
storageOpenState = null;
|
|
316
455
|
/** Retained so a follower promoted to leader can re-open its own worker. */
|
|
317
456
|
schema = null;
|
|
318
457
|
promoting = false;
|
|
@@ -323,50 +462,62 @@ var SqliteWasmAdapter = class {
|
|
|
323
462
|
this.workerResponseTimeoutMs = options.workerResponseTimeoutMs ?? 3e4;
|
|
324
463
|
this.dbName = options.dbName ?? "kora-db";
|
|
325
464
|
this.emitter = options.emitter;
|
|
465
|
+
this.emitNonPersistentDiagnostic = options.emitNonPersistentDiagnostic ?? true;
|
|
326
466
|
}
|
|
327
467
|
async open(schema) {
|
|
328
468
|
if (this.opened) return;
|
|
469
|
+
const ddlStatements = generateFullDDL(schema);
|
|
329
470
|
if (this.injectedBridge) {
|
|
330
471
|
this.bridge = this.injectedBridge;
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
this.
|
|
339
|
-
|
|
340
|
-
this.
|
|
341
|
-
this.
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
);
|
|
356
|
-
this.bridge = followerBridge;
|
|
357
|
-
this.emitter?.emit({
|
|
358
|
-
type: "store:db-name-collision",
|
|
359
|
-
dbName: this.dbName,
|
|
360
|
-
message: `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.`
|
|
361
|
-
});
|
|
362
|
-
await followerBridge.waitForLeader();
|
|
472
|
+
const response = await this.openCurrentBridge(ddlStatements);
|
|
473
|
+
this.reportStorageMode(response.data);
|
|
474
|
+
this.opened = true;
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
if (this.workerUrl) {
|
|
478
|
+
this.warnSharedWorkerDeprecated();
|
|
479
|
+
await this.openDurableWorkerBridge(schema);
|
|
480
|
+
const response = await this.openCurrentBridge(ddlStatements);
|
|
481
|
+
this.reportStorageMode(response.data);
|
|
482
|
+
this.opened = true;
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
throw new AdapterError(
|
|
486
|
+
'SqliteWasmAdapter requires either a bridge (for testing) or a workerUrl (for browsers). Pass { bridge: new MockWorkerBridge() } for tests, or { workerUrl: "/worker.js" } for browsers.'
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
async openDurableWorkerBridge(schema) {
|
|
490
|
+
if (!this.workerUrl) {
|
|
491
|
+
throw new AdapterError("Durable SQLite WASM storage requires workerUrl.");
|
|
492
|
+
}
|
|
493
|
+
this.schema = schema;
|
|
494
|
+
this.tabSession = await acquireTabStorageSession(this.dbName, {
|
|
495
|
+
onPromote: () => {
|
|
496
|
+
void this.promoteToLeader();
|
|
363
497
|
}
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
|
|
498
|
+
});
|
|
499
|
+
const { WebWorkerBridge } = await import("./sqlite-wasm-channel-GGHA2BXU.js");
|
|
500
|
+
if (this.tabSession.role === "leader") {
|
|
501
|
+
const workerBridge = new TransactionSerializingWorkerBridge(
|
|
502
|
+
new WebWorkerBridge(this.workerUrl, this.workerResponseTimeoutMs)
|
|
367
503
|
);
|
|
504
|
+
this.tabSession.stopRelay = startLeaderRpcRelay(this.tabSession.channelName, workerBridge);
|
|
505
|
+
this.bridge = workerBridge;
|
|
506
|
+
return;
|
|
368
507
|
}
|
|
369
|
-
const
|
|
508
|
+
const followerBridge = new FollowerBroadcastBridge(
|
|
509
|
+
this.tabSession.channelName,
|
|
510
|
+
this.workerResponseTimeoutMs
|
|
511
|
+
);
|
|
512
|
+
this.bridge = followerBridge;
|
|
513
|
+
this.emitter?.emit({
|
|
514
|
+
type: "store:db-name-collision",
|
|
515
|
+
dbName: this.dbName,
|
|
516
|
+
message: `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.`
|
|
517
|
+
});
|
|
518
|
+
await followerBridge.waitForLeader();
|
|
519
|
+
}
|
|
520
|
+
async openCurrentBridge(ddlStatements) {
|
|
370
521
|
const response = await this.sendRequest({
|
|
371
522
|
id: 0,
|
|
372
523
|
type: "open",
|
|
@@ -379,8 +530,7 @@ var SqliteWasmAdapter = class {
|
|
|
379
530
|
dbName: this.dbName
|
|
380
531
|
});
|
|
381
532
|
}
|
|
382
|
-
|
|
383
|
-
this.opened = true;
|
|
533
|
+
return response;
|
|
384
534
|
}
|
|
385
535
|
/**
|
|
386
536
|
* Surface a `store:opfs-unavailable` diagnostic when the worker reported that
|
|
@@ -389,12 +539,12 @@ var SqliteWasmAdapter = class {
|
|
|
389
539
|
* bridge that does not report a mode, e.g. the Node mock) emits nothing.
|
|
390
540
|
*/
|
|
391
541
|
reportStorageMode(data) {
|
|
392
|
-
|
|
542
|
+
this.storageOpenState = parseStorageOpenState(data);
|
|
543
|
+
if (!this.emitNonPersistentDiagnostic || !this.emitter || !this.storageOpenState) {
|
|
393
544
|
return;
|
|
394
545
|
}
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
const reason = mode.fallbackReason ?? "unsupported";
|
|
546
|
+
if (this.storageOpenState.persistent === false) {
|
|
547
|
+
const reason = this.storageOpenState.fallbackReason ?? "unsupported";
|
|
398
548
|
this.emitter.emit({
|
|
399
549
|
type: "store:opfs-unavailable",
|
|
400
550
|
dbName: this.dbName,
|
|
@@ -403,6 +553,18 @@ var SqliteWasmAdapter = class {
|
|
|
403
553
|
});
|
|
404
554
|
}
|
|
405
555
|
}
|
|
556
|
+
getStorageOpenState() {
|
|
557
|
+
return this.storageOpenState;
|
|
558
|
+
}
|
|
559
|
+
warnSharedWorkerDeprecated() {
|
|
560
|
+
if (!this.sharedWorkerUrl || warnedSharedWorkerDeprecated) {
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
warnedSharedWorkerDeprecated = true;
|
|
564
|
+
console.warn(
|
|
565
|
+
"[kora] sharedWorkerUrl is deprecated and ignored: SharedWorker-hosted SQLite cannot use OPFS and is never durable. Kora uses the durable dedicated-worker leader/follower path for multi-tab storage."
|
|
566
|
+
);
|
|
567
|
+
}
|
|
406
568
|
async close() {
|
|
407
569
|
if (!this.bridge) return;
|
|
408
570
|
try {
|
|
@@ -414,7 +576,6 @@ var SqliteWasmAdapter = class {
|
|
|
414
576
|
await this.tabSession.releaseLock();
|
|
415
577
|
}
|
|
416
578
|
this.tabSession = null;
|
|
417
|
-
this.sharedWorker = null;
|
|
418
579
|
this.bridge.terminate();
|
|
419
580
|
this.bridge = null;
|
|
420
581
|
this.opened = false;
|
|
@@ -432,8 +593,10 @@ var SqliteWasmAdapter = class {
|
|
|
432
593
|
return;
|
|
433
594
|
}
|
|
434
595
|
this.promoting = true;
|
|
435
|
-
const { WebWorkerBridge } = await import("./sqlite-wasm-channel-
|
|
436
|
-
const workerBridge = new
|
|
596
|
+
const { WebWorkerBridge } = await import("./sqlite-wasm-channel-GGHA2BXU.js");
|
|
597
|
+
const workerBridge = new TransactionSerializingWorkerBridge(
|
|
598
|
+
new WebWorkerBridge(this.workerUrl, this.workerResponseTimeoutMs)
|
|
599
|
+
);
|
|
437
600
|
const previousBridge = this.bridge;
|
|
438
601
|
this.bridge = workerBridge;
|
|
439
602
|
if (this.tabSession) {
|
|
@@ -531,7 +694,10 @@ var SqliteWasmAdapter = class {
|
|
|
531
694
|
this.guardOpen();
|
|
532
695
|
const response = await this.sendRequest({ id: 0, type: "export" });
|
|
533
696
|
if (response.type === "error") {
|
|
534
|
-
throw new AdapterError(`Export failed: ${response.message}
|
|
697
|
+
throw new AdapterError(`Export failed: ${response.message}`, {
|
|
698
|
+
code: response.code,
|
|
699
|
+
context: response.context
|
|
700
|
+
});
|
|
535
701
|
}
|
|
536
702
|
return response.data;
|
|
537
703
|
}
|
|
@@ -564,8 +730,22 @@ var SqliteWasmAdapter = class {
|
|
|
564
730
|
}
|
|
565
731
|
}
|
|
566
732
|
};
|
|
733
|
+
function parseStorageOpenState(data) {
|
|
734
|
+
if (typeof data !== "object" || data === null) {
|
|
735
|
+
return null;
|
|
736
|
+
}
|
|
737
|
+
const mode = data;
|
|
738
|
+
if (typeof mode.persistent !== "boolean") {
|
|
739
|
+
return null;
|
|
740
|
+
}
|
|
741
|
+
return {
|
|
742
|
+
persistent: mode.persistent,
|
|
743
|
+
mode: mode.persistent ? "opfs" : "memory",
|
|
744
|
+
...mode.fallbackReason ? { fallbackReason: mode.fallbackReason } : {}
|
|
745
|
+
};
|
|
746
|
+
}
|
|
567
747
|
|
|
568
748
|
export {
|
|
569
749
|
SqliteWasmAdapter
|
|
570
750
|
};
|
|
571
|
-
//# sourceMappingURL=chunk-
|
|
751
|
+
//# sourceMappingURL=chunk-RJIEKEN4.js.map
|