@korajs/store 1.0.0-beta.5 → 1.0.0-beta.6
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 +309 -128
- 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 +7 -2
- 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 +304 -128
- 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-FKW56RVZ.js} +306 -129
- package/dist/chunk-FKW56RVZ.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 +16 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -4
- package/dist/index.d.ts +12 -4
- package/dist/index.js +34 -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 +1 -11
- 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
|
@@ -199,93 +199,15 @@ module.exports = __toCommonJS(sqlite_wasm_exports);
|
|
|
199
199
|
var import_core2 = require("@korajs/core");
|
|
200
200
|
init_errors();
|
|
201
201
|
|
|
202
|
-
// src/multi-tab/shared-worker-bridge.ts
|
|
203
|
-
init_errors();
|
|
204
|
-
var SW_REQUEST = "kora-sw-request";
|
|
205
|
-
var SW_RESPONSE = "kora-sw-response";
|
|
206
|
-
var SharedWorkerClientBridge = class {
|
|
207
|
-
constructor(sharedWorker, dbName, workerUrl, timeoutMs = 3e4) {
|
|
208
|
-
this.dbName = dbName;
|
|
209
|
-
this.workerUrl = workerUrl;
|
|
210
|
-
this.timeoutMs = timeoutMs;
|
|
211
|
-
this.port = sharedWorker.port;
|
|
212
|
-
this.port.start();
|
|
213
|
-
this.port.addEventListener("message", (event) => {
|
|
214
|
-
const data = event.data;
|
|
215
|
-
if (data?.type !== SW_RESPONSE) {
|
|
216
|
-
return;
|
|
217
|
-
}
|
|
218
|
-
const entry = this.pending.get(data.requestId);
|
|
219
|
-
if (entry) {
|
|
220
|
-
this.pending.delete(data.requestId);
|
|
221
|
-
entry.resolve(data.response);
|
|
222
|
-
}
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
|
-
dbName;
|
|
226
|
-
workerUrl;
|
|
227
|
-
port;
|
|
228
|
-
pending = /* @__PURE__ */ new Map();
|
|
229
|
-
timeoutMs;
|
|
230
|
-
terminated = false;
|
|
231
|
-
async send(request) {
|
|
232
|
-
if (this.terminated) {
|
|
233
|
-
return {
|
|
234
|
-
id: request.id,
|
|
235
|
-
type: "error",
|
|
236
|
-
message: "SharedWorker bridge terminated",
|
|
237
|
-
code: "BRIDGE_TERMINATED"
|
|
238
|
-
};
|
|
239
|
-
}
|
|
240
|
-
const requestId = crypto.randomUUID();
|
|
241
|
-
const msg = {
|
|
242
|
-
type: SW_REQUEST,
|
|
243
|
-
requestId,
|
|
244
|
-
dbName: this.dbName,
|
|
245
|
-
workerUrl: this.workerUrl,
|
|
246
|
-
request
|
|
247
|
-
};
|
|
248
|
-
return new Promise((resolve, reject) => {
|
|
249
|
-
const timer = setTimeout(() => {
|
|
250
|
-
this.pending.delete(requestId);
|
|
251
|
-
reject(new WorkerTimeoutError(`shared-worker:${request.type}`, this.timeoutMs));
|
|
252
|
-
}, this.timeoutMs);
|
|
253
|
-
this.pending.set(requestId, {
|
|
254
|
-
resolve: (response) => {
|
|
255
|
-
clearTimeout(timer);
|
|
256
|
-
resolve(response);
|
|
257
|
-
},
|
|
258
|
-
reject: (error) => {
|
|
259
|
-
clearTimeout(timer);
|
|
260
|
-
reject(error);
|
|
261
|
-
}
|
|
262
|
-
});
|
|
263
|
-
this.port.postMessage(msg);
|
|
264
|
-
});
|
|
265
|
-
}
|
|
266
|
-
terminate() {
|
|
267
|
-
if (this.terminated) {
|
|
268
|
-
return;
|
|
269
|
-
}
|
|
270
|
-
this.terminated = true;
|
|
271
|
-
this.port.close();
|
|
272
|
-
for (const [, entry] of this.pending) {
|
|
273
|
-
entry.reject(new Error("SharedWorker bridge terminated"));
|
|
274
|
-
}
|
|
275
|
-
this.pending.clear();
|
|
276
|
-
}
|
|
277
|
-
};
|
|
278
|
-
|
|
279
202
|
// src/multi-tab/tab-storage.ts
|
|
280
203
|
init_errors();
|
|
281
204
|
var RPC_REQUEST = "kora-worker-request";
|
|
282
205
|
var RPC_RESPONSE = "kora-worker-response";
|
|
206
|
+
var CLIENT_LEAVE = "kora-client-leave";
|
|
283
207
|
var LEADER_PING = "kora-leader-ping";
|
|
284
208
|
var LEADER_PONG = "kora-leader-pong";
|
|
285
209
|
var DEFAULT_LIVENESS_PROBE_MS = 2e3;
|
|
286
|
-
|
|
287
|
-
return typeof globalThis !== "undefined" && typeof SharedWorker !== "undefined";
|
|
288
|
-
}
|
|
210
|
+
var DEFAULT_TRANSACTION_IDLE_TIMEOUT_MS = 1e4;
|
|
289
211
|
function isMultiTabStorageSupported() {
|
|
290
212
|
return typeof globalThis !== "undefined" && typeof BroadcastChannel !== "undefined" && typeof navigator !== "undefined" && typeof navigator.locks?.request === "function";
|
|
291
213
|
}
|
|
@@ -347,10 +269,16 @@ function startLeaderRpcRelay(channelName, bridge) {
|
|
|
347
269
|
channel.postMessage({ type: LEADER_PONG });
|
|
348
270
|
return;
|
|
349
271
|
}
|
|
272
|
+
if (data?.type === CLIENT_LEAVE) {
|
|
273
|
+
if (hasReclaimClient(bridge)) {
|
|
274
|
+
bridge.reclaimClient(data.clientId, "client-left");
|
|
275
|
+
}
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
350
278
|
if (data?.type !== RPC_REQUEST) {
|
|
351
279
|
return;
|
|
352
280
|
}
|
|
353
|
-
void bridge.send(data.request).then((response) => {
|
|
281
|
+
void bridge.send(data.request, data.clientId).then((response) => {
|
|
354
282
|
const msg = {
|
|
355
283
|
type: RPC_RESPONSE,
|
|
356
284
|
requestId: data.requestId,
|
|
@@ -380,6 +308,10 @@ function startLeaderRpcRelay(channelName, bridge) {
|
|
|
380
308
|
}
|
|
381
309
|
var FollowerBroadcastBridge = class {
|
|
382
310
|
channel;
|
|
311
|
+
clientId = createClientId();
|
|
312
|
+
onPageHide = () => {
|
|
313
|
+
this.leaveLeader();
|
|
314
|
+
};
|
|
383
315
|
pending = /* @__PURE__ */ new Map();
|
|
384
316
|
timeoutMs;
|
|
385
317
|
livenessProbeMs;
|
|
@@ -388,6 +320,9 @@ var FollowerBroadcastBridge = class {
|
|
|
388
320
|
this.timeoutMs = timeoutMs;
|
|
389
321
|
this.livenessProbeMs = Math.min(livenessProbeMs, timeoutMs);
|
|
390
322
|
this.channel = new BroadcastChannel(channelName);
|
|
323
|
+
if (typeof addEventListener === "function") {
|
|
324
|
+
addEventListener("pagehide", this.onPageHide);
|
|
325
|
+
}
|
|
391
326
|
this.channel.addEventListener("message", (event) => {
|
|
392
327
|
const data = event.data;
|
|
393
328
|
if (data?.type !== RPC_RESPONSE) {
|
|
@@ -451,8 +386,13 @@ var FollowerBroadcastBridge = class {
|
|
|
451
386
|
code: "BRIDGE_TERMINATED"
|
|
452
387
|
};
|
|
453
388
|
}
|
|
454
|
-
const requestId =
|
|
455
|
-
const msg = {
|
|
389
|
+
const requestId = createClientId();
|
|
390
|
+
const msg = {
|
|
391
|
+
type: RPC_REQUEST,
|
|
392
|
+
requestId,
|
|
393
|
+
clientId: this.clientId,
|
|
394
|
+
request
|
|
395
|
+
};
|
|
456
396
|
return new Promise((resolve, reject) => {
|
|
457
397
|
const settle = (fn) => {
|
|
458
398
|
clearTimeout(timer);
|
|
@@ -482,16 +422,213 @@ var FollowerBroadcastBridge = class {
|
|
|
482
422
|
return;
|
|
483
423
|
}
|
|
484
424
|
this.terminated = true;
|
|
425
|
+
this.leaveLeader();
|
|
426
|
+
if (typeof removeEventListener === "function") {
|
|
427
|
+
removeEventListener("pagehide", this.onPageHide);
|
|
428
|
+
}
|
|
485
429
|
this.channel.close();
|
|
486
430
|
for (const [, entry] of this.pending) {
|
|
487
431
|
entry.reject(new Error("Follower bridge terminated"));
|
|
488
432
|
}
|
|
489
433
|
this.pending.clear();
|
|
490
434
|
}
|
|
435
|
+
leaveLeader() {
|
|
436
|
+
try {
|
|
437
|
+
const message = {
|
|
438
|
+
type: CLIENT_LEAVE,
|
|
439
|
+
clientId: this.clientId
|
|
440
|
+
};
|
|
441
|
+
this.channel.postMessage(message);
|
|
442
|
+
} catch {
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
var TransactionSerializingWorkerBridge = class {
|
|
447
|
+
inner;
|
|
448
|
+
leaderClientId = createClientId();
|
|
449
|
+
transactionIdleTimeoutMs;
|
|
450
|
+
queue = [];
|
|
451
|
+
activeTransactionClient = null;
|
|
452
|
+
abortedClients = /* @__PURE__ */ new Set();
|
|
453
|
+
transactionIdleTimer = null;
|
|
454
|
+
reclaiming = false;
|
|
455
|
+
processing = false;
|
|
456
|
+
terminated = false;
|
|
457
|
+
constructor(inner, transactionIdleTimeoutMs = DEFAULT_TRANSACTION_IDLE_TIMEOUT_MS) {
|
|
458
|
+
this.inner = inner;
|
|
459
|
+
this.transactionIdleTimeoutMs = transactionIdleTimeoutMs;
|
|
460
|
+
}
|
|
461
|
+
send(request, clientId = this.leaderClientId) {
|
|
462
|
+
if (this.terminated) {
|
|
463
|
+
return Promise.resolve({
|
|
464
|
+
id: request.id,
|
|
465
|
+
type: "error",
|
|
466
|
+
message: "Worker has been terminated",
|
|
467
|
+
code: "WORKER_TERMINATED"
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
if (this.abortedClients.has(clientId)) {
|
|
471
|
+
if (request.type === "begin") {
|
|
472
|
+
this.abortedClients.delete(clientId);
|
|
473
|
+
} else {
|
|
474
|
+
return Promise.resolve({
|
|
475
|
+
id: request.id,
|
|
476
|
+
type: "error",
|
|
477
|
+
message: "Previous transaction was aborted because the client stopped sending requests.",
|
|
478
|
+
code: "TRANSACTION_ABORTED"
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
return new Promise((resolve, reject) => {
|
|
483
|
+
this.queue.push({ clientId, request, resolve, reject });
|
|
484
|
+
void this.processQueue();
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
terminate() {
|
|
488
|
+
if (this.terminated) {
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
this.terminated = true;
|
|
492
|
+
this.clearTransactionIdleTimer();
|
|
493
|
+
this.inner.terminate();
|
|
494
|
+
const pending = this.queue.splice(0);
|
|
495
|
+
for (const entry of pending) {
|
|
496
|
+
entry.reject(new Error("Worker terminated"));
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
reclaimClient(clientId, reason) {
|
|
500
|
+
if (this.terminated) {
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
void this.reclaimClientNow(clientId, reason);
|
|
504
|
+
}
|
|
505
|
+
async processQueue() {
|
|
506
|
+
if (this.processing || this.reclaiming) {
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
this.processing = true;
|
|
510
|
+
try {
|
|
511
|
+
while (!this.terminated) {
|
|
512
|
+
const index = this.nextRunnableIndex();
|
|
513
|
+
if (index === -1) {
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
const [entry] = this.queue.splice(index, 1);
|
|
517
|
+
if (!entry) {
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
try {
|
|
521
|
+
const response = await this.inner.send(entry.request);
|
|
522
|
+
this.recordTransactionState(entry, response);
|
|
523
|
+
entry.resolve(response);
|
|
524
|
+
} catch (error) {
|
|
525
|
+
this.resetFailedTransaction(entry);
|
|
526
|
+
entry.reject(error instanceof Error ? error : new Error(String(error)));
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
} finally {
|
|
530
|
+
this.processing = false;
|
|
531
|
+
if (!this.terminated && !this.reclaiming && this.nextRunnableIndex() !== -1) {
|
|
532
|
+
void this.processQueue();
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
nextRunnableIndex() {
|
|
537
|
+
if (this.queue.length === 0) {
|
|
538
|
+
return -1;
|
|
539
|
+
}
|
|
540
|
+
if (this.activeTransactionClient === null) {
|
|
541
|
+
return 0;
|
|
542
|
+
}
|
|
543
|
+
return this.queue.findIndex((entry) => entry.clientId === this.activeTransactionClient);
|
|
544
|
+
}
|
|
545
|
+
recordTransactionState(entry, response) {
|
|
546
|
+
if (response.type === "error") {
|
|
547
|
+
this.resetFailedTransaction(entry);
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
if (entry.request.type === "begin") {
|
|
551
|
+
this.activeTransactionClient = entry.clientId;
|
|
552
|
+
this.armTransactionIdleTimer(entry.clientId);
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
if (this.activeTransactionClient === entry.clientId) {
|
|
556
|
+
this.armTransactionIdleTimer(entry.clientId);
|
|
557
|
+
}
|
|
558
|
+
if (this.activeTransactionClient === entry.clientId && (entry.request.type === "commit" || entry.request.type === "rollback")) {
|
|
559
|
+
this.activeTransactionClient = null;
|
|
560
|
+
this.clearTransactionIdleTimer();
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
resetFailedTransaction(entry) {
|
|
564
|
+
if (this.activeTransactionClient === entry.clientId) {
|
|
565
|
+
this.activeTransactionClient = null;
|
|
566
|
+
this.clearTransactionIdleTimer();
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
armTransactionIdleTimer(clientId) {
|
|
570
|
+
this.clearTransactionIdleTimer();
|
|
571
|
+
this.transactionIdleTimer = setTimeout(() => {
|
|
572
|
+
void this.reclaimClientNow(clientId, "transaction-idle-timeout");
|
|
573
|
+
}, this.transactionIdleTimeoutMs);
|
|
574
|
+
}
|
|
575
|
+
clearTransactionIdleTimer() {
|
|
576
|
+
if (this.transactionIdleTimer) {
|
|
577
|
+
clearTimeout(this.transactionIdleTimer);
|
|
578
|
+
this.transactionIdleTimer = null;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
async reclaimClientNow(clientId, reason) {
|
|
582
|
+
if (this.reclaiming || this.activeTransactionClient !== clientId) {
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
this.reclaiming = true;
|
|
586
|
+
this.clearTransactionIdleTimer();
|
|
587
|
+
this.activeTransactionClient = null;
|
|
588
|
+
this.abortedClients.add(clientId);
|
|
589
|
+
this.rejectQueuedClientRequests(clientId, reason);
|
|
590
|
+
try {
|
|
591
|
+
const response = await this.inner.send({ id: 0, type: "rollback" });
|
|
592
|
+
if (response.type === "error") {
|
|
593
|
+
}
|
|
594
|
+
} catch {
|
|
595
|
+
} finally {
|
|
596
|
+
this.reclaiming = false;
|
|
597
|
+
if (!this.terminated && this.nextRunnableIndex() !== -1) {
|
|
598
|
+
void this.processQueue();
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
rejectQueuedClientRequests(clientId, reason) {
|
|
603
|
+
const keep = [];
|
|
604
|
+
for (const entry of this.queue) {
|
|
605
|
+
if (entry.clientId === clientId) {
|
|
606
|
+
entry.resolve({
|
|
607
|
+
id: entry.request.id,
|
|
608
|
+
type: "error",
|
|
609
|
+
message: `Transaction client was reclaimed: ${reason}`,
|
|
610
|
+
code: "TRANSACTION_ABORTED"
|
|
611
|
+
});
|
|
612
|
+
} else {
|
|
613
|
+
keep.push(entry);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
this.queue = keep;
|
|
617
|
+
}
|
|
491
618
|
};
|
|
619
|
+
function hasReclaimClient(bridge) {
|
|
620
|
+
return typeof bridge.reclaimClient === "function";
|
|
621
|
+
}
|
|
622
|
+
function createClientId() {
|
|
623
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
624
|
+
return crypto.randomUUID();
|
|
625
|
+
}
|
|
626
|
+
return `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
627
|
+
}
|
|
492
628
|
|
|
493
629
|
// src/adapters/sqlite-wasm-adapter.ts
|
|
494
630
|
init_sqlite_wasm_channel();
|
|
631
|
+
var warnedSharedWorkerDeprecated = false;
|
|
495
632
|
var SqliteWasmAdapter = class {
|
|
496
633
|
bridge = null;
|
|
497
634
|
opened = false;
|
|
@@ -503,7 +640,8 @@ var SqliteWasmAdapter = class {
|
|
|
503
640
|
dbName;
|
|
504
641
|
emitter;
|
|
505
642
|
tabSession = null;
|
|
506
|
-
|
|
643
|
+
emitNonPersistentDiagnostic;
|
|
644
|
+
storageOpenState = null;
|
|
507
645
|
/** Retained so a follower promoted to leader can re-open its own worker. */
|
|
508
646
|
schema = null;
|
|
509
647
|
promoting = false;
|
|
@@ -514,50 +652,62 @@ var SqliteWasmAdapter = class {
|
|
|
514
652
|
this.workerResponseTimeoutMs = options.workerResponseTimeoutMs ?? 3e4;
|
|
515
653
|
this.dbName = options.dbName ?? "kora-db";
|
|
516
654
|
this.emitter = options.emitter;
|
|
655
|
+
this.emitNonPersistentDiagnostic = options.emitNonPersistentDiagnostic ?? true;
|
|
517
656
|
}
|
|
518
657
|
async open(schema) {
|
|
519
658
|
if (this.opened) return;
|
|
659
|
+
const ddlStatements = (0, import_core2.generateFullDDL)(schema);
|
|
520
660
|
if (this.injectedBridge) {
|
|
521
661
|
this.bridge = this.injectedBridge;
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
this.
|
|
530
|
-
|
|
531
|
-
this.
|
|
532
|
-
this.
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
);
|
|
547
|
-
this.bridge = followerBridge;
|
|
548
|
-
this.emitter?.emit({
|
|
549
|
-
type: "store:db-name-collision",
|
|
550
|
-
dbName: this.dbName,
|
|
551
|
-
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.`
|
|
552
|
-
});
|
|
553
|
-
await followerBridge.waitForLeader();
|
|
662
|
+
const response = await this.openCurrentBridge(ddlStatements);
|
|
663
|
+
this.reportStorageMode(response.data);
|
|
664
|
+
this.opened = true;
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
if (this.workerUrl) {
|
|
668
|
+
this.warnSharedWorkerDeprecated();
|
|
669
|
+
await this.openDurableWorkerBridge(schema);
|
|
670
|
+
const response = await this.openCurrentBridge(ddlStatements);
|
|
671
|
+
this.reportStorageMode(response.data);
|
|
672
|
+
this.opened = true;
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
throw new AdapterError(
|
|
676
|
+
'SqliteWasmAdapter requires either a bridge (for testing) or a workerUrl (for browsers). Pass { bridge: new MockWorkerBridge() } for tests, or { workerUrl: "/worker.js" } for browsers.'
|
|
677
|
+
);
|
|
678
|
+
}
|
|
679
|
+
async openDurableWorkerBridge(schema) {
|
|
680
|
+
if (!this.workerUrl) {
|
|
681
|
+
throw new AdapterError("Durable SQLite WASM storage requires workerUrl.");
|
|
682
|
+
}
|
|
683
|
+
this.schema = schema;
|
|
684
|
+
this.tabSession = await acquireTabStorageSession(this.dbName, {
|
|
685
|
+
onPromote: () => {
|
|
686
|
+
void this.promoteToLeader();
|
|
554
687
|
}
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
|
|
688
|
+
});
|
|
689
|
+
const { WebWorkerBridge: WebWorkerBridge2 } = await Promise.resolve().then(() => (init_sqlite_wasm_channel(), sqlite_wasm_channel_exports));
|
|
690
|
+
if (this.tabSession.role === "leader") {
|
|
691
|
+
const workerBridge = new TransactionSerializingWorkerBridge(
|
|
692
|
+
new WebWorkerBridge2(this.workerUrl, this.workerResponseTimeoutMs)
|
|
558
693
|
);
|
|
694
|
+
this.tabSession.stopRelay = startLeaderRpcRelay(this.tabSession.channelName, workerBridge);
|
|
695
|
+
this.bridge = workerBridge;
|
|
696
|
+
return;
|
|
559
697
|
}
|
|
560
|
-
const
|
|
698
|
+
const followerBridge = new FollowerBroadcastBridge(
|
|
699
|
+
this.tabSession.channelName,
|
|
700
|
+
this.workerResponseTimeoutMs
|
|
701
|
+
);
|
|
702
|
+
this.bridge = followerBridge;
|
|
703
|
+
this.emitter?.emit({
|
|
704
|
+
type: "store:db-name-collision",
|
|
705
|
+
dbName: this.dbName,
|
|
706
|
+
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.`
|
|
707
|
+
});
|
|
708
|
+
await followerBridge.waitForLeader();
|
|
709
|
+
}
|
|
710
|
+
async openCurrentBridge(ddlStatements) {
|
|
561
711
|
const response = await this.sendRequest({
|
|
562
712
|
id: 0,
|
|
563
713
|
type: "open",
|
|
@@ -570,8 +720,7 @@ var SqliteWasmAdapter = class {
|
|
|
570
720
|
dbName: this.dbName
|
|
571
721
|
});
|
|
572
722
|
}
|
|
573
|
-
|
|
574
|
-
this.opened = true;
|
|
723
|
+
return response;
|
|
575
724
|
}
|
|
576
725
|
/**
|
|
577
726
|
* Surface a `store:opfs-unavailable` diagnostic when the worker reported that
|
|
@@ -580,12 +729,12 @@ var SqliteWasmAdapter = class {
|
|
|
580
729
|
* bridge that does not report a mode, e.g. the Node mock) emits nothing.
|
|
581
730
|
*/
|
|
582
731
|
reportStorageMode(data) {
|
|
583
|
-
|
|
732
|
+
this.storageOpenState = parseStorageOpenState(data);
|
|
733
|
+
if (!this.emitNonPersistentDiagnostic || !this.emitter || !this.storageOpenState) {
|
|
584
734
|
return;
|
|
585
735
|
}
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
const reason = mode.fallbackReason ?? "unsupported";
|
|
736
|
+
if (this.storageOpenState.persistent === false) {
|
|
737
|
+
const reason = this.storageOpenState.fallbackReason ?? "unsupported";
|
|
589
738
|
this.emitter.emit({
|
|
590
739
|
type: "store:opfs-unavailable",
|
|
591
740
|
dbName: this.dbName,
|
|
@@ -594,6 +743,18 @@ var SqliteWasmAdapter = class {
|
|
|
594
743
|
});
|
|
595
744
|
}
|
|
596
745
|
}
|
|
746
|
+
getStorageOpenState() {
|
|
747
|
+
return this.storageOpenState;
|
|
748
|
+
}
|
|
749
|
+
warnSharedWorkerDeprecated() {
|
|
750
|
+
if (!this.sharedWorkerUrl || warnedSharedWorkerDeprecated) {
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
753
|
+
warnedSharedWorkerDeprecated = true;
|
|
754
|
+
console.warn(
|
|
755
|
+
"[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."
|
|
756
|
+
);
|
|
757
|
+
}
|
|
597
758
|
async close() {
|
|
598
759
|
if (!this.bridge) return;
|
|
599
760
|
try {
|
|
@@ -605,7 +766,6 @@ var SqliteWasmAdapter = class {
|
|
|
605
766
|
await this.tabSession.releaseLock();
|
|
606
767
|
}
|
|
607
768
|
this.tabSession = null;
|
|
608
|
-
this.sharedWorker = null;
|
|
609
769
|
this.bridge.terminate();
|
|
610
770
|
this.bridge = null;
|
|
611
771
|
this.opened = false;
|
|
@@ -624,7 +784,9 @@ var SqliteWasmAdapter = class {
|
|
|
624
784
|
}
|
|
625
785
|
this.promoting = true;
|
|
626
786
|
const { WebWorkerBridge: WebWorkerBridge2 } = await Promise.resolve().then(() => (init_sqlite_wasm_channel(), sqlite_wasm_channel_exports));
|
|
627
|
-
const workerBridge = new
|
|
787
|
+
const workerBridge = new TransactionSerializingWorkerBridge(
|
|
788
|
+
new WebWorkerBridge2(this.workerUrl, this.workerResponseTimeoutMs)
|
|
789
|
+
);
|
|
628
790
|
const previousBridge = this.bridge;
|
|
629
791
|
this.bridge = workerBridge;
|
|
630
792
|
if (this.tabSession) {
|
|
@@ -755,6 +917,20 @@ var SqliteWasmAdapter = class {
|
|
|
755
917
|
}
|
|
756
918
|
}
|
|
757
919
|
};
|
|
920
|
+
function parseStorageOpenState(data) {
|
|
921
|
+
if (typeof data !== "object" || data === null) {
|
|
922
|
+
return null;
|
|
923
|
+
}
|
|
924
|
+
const mode = data;
|
|
925
|
+
if (typeof mode.persistent !== "boolean") {
|
|
926
|
+
return null;
|
|
927
|
+
}
|
|
928
|
+
return {
|
|
929
|
+
persistent: mode.persistent,
|
|
930
|
+
mode: mode.persistent ? "opfs" : "memory",
|
|
931
|
+
...mode.fallbackReason ? { fallbackReason: mode.fallbackReason } : {}
|
|
932
|
+
};
|
|
933
|
+
}
|
|
758
934
|
|
|
759
935
|
// src/adapters/sqlite-wasm.ts
|
|
760
936
|
init_sqlite_wasm_channel();
|