@korajs/store 1.0.0-beta.4 → 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 -125
- 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 +206 -208
- package/dist/adapters/sqlite-wasm-worker.cjs.map +1 -1
- package/dist/adapters/sqlite-wasm-worker.js +206 -208
- package/dist/adapters/sqlite-wasm-worker.js.map +1 -1
- package/dist/adapters/sqlite-wasm.cjs +304 -125
- 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/{backup-IQE4QC7Q.js → backup-5S7K2Z35.js} +3 -3
- package/dist/{chunk-RCTG3IJG.js → chunk-CYO3YKFL.js} +2 -2
- package/dist/{chunk-2ZRQGFEF.js → chunk-FKW56RVZ.js} +306 -126
- package/dist/chunk-FKW56RVZ.js.map +1 -0
- package/dist/{chunk-6NKLDBTY.js → chunk-GSJ7EEUC.js} +2 -1
- package/dist/chunk-GSJ7EEUC.js.map +1 -0
- package/dist/{chunk-OCOGQRHW.js → chunk-HS6DBMST.js} +2 -2
- package/dist/{chunk-COJSQBXW.js → chunk-VDBJQ75K.js} +1 -1
- package/dist/chunk-VDBJQ75K.js.map +1 -0
- package/dist/{chunk-UXRWVXBP.js → chunk-YT7MRFCP.js} +6 -6
- package/dist/{export-audit-MT2LTMIN.js → export-audit-ODGQTQIK.js} +3 -3
- package/dist/index.cjs +136 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +35 -4
- package/dist/index.d.ts +35 -4
- package/dist/index.js +160 -27
- package/dist/index.js.map +1 -1
- package/dist/internal.cjs +1 -0
- package/dist/internal.cjs.map +1 -1
- package/dist/internal.d.cts +3 -3
- package/dist/internal.d.ts +3 -3
- package/dist/internal.js +3 -3
- package/dist/{sqlite-wasm-channel-NGSBJR44.js → sqlite-wasm-channel-GGHA2BXU.js} +2 -2
- package/dist/{sqlite-wasm-channel-CNHRf-m2.d.cts → sqlite-wasm-channel-L6o3bWQK.d.cts} +2 -2
- package/dist/{sqlite-wasm-channel-CNHRf-m2.d.ts → sqlite-wasm-channel-L6o3bWQK.d.ts} +2 -2
- package/dist/{types-5h4Hqcwc.d.ts → types-BOO9EPt1.d.ts} +1 -1
- package/dist/{types-k6AZgiH4.d.ts → types-BslDOTk0.d.cts} +26 -1
- package/dist/{types-k6AZgiH4.d.cts → types-BslDOTk0.d.ts} +26 -1
- package/dist/{types-DfPiPwKr.d.cts → types-C_GDavG-.d.cts} +1 -1
- package/package.json +2 -12
- package/dist/adapters/sqlite-wasm-shared-host.cjs +0 -95
- package/dist/adapters/sqlite-wasm-shared-host.cjs.map +0 -1
- package/dist/adapters/sqlite-wasm-shared-host.d.cts +0 -64
- package/dist/adapters/sqlite-wasm-shared-host.d.ts +0 -64
- package/dist/adapters/sqlite-wasm-shared-host.js +0 -70
- package/dist/adapters/sqlite-wasm-shared-host.js.map +0 -1
- package/dist/chunk-2ZRQGFEF.js.map +0 -1
- package/dist/chunk-6NKLDBTY.js.map +0 -1
- package/dist/chunk-COJSQBXW.js.map +0 -1
- /package/dist/{backup-IQE4QC7Q.js.map → backup-5S7K2Z35.js.map} +0 -0
- /package/dist/{chunk-RCTG3IJG.js.map → chunk-CYO3YKFL.js.map} +0 -0
- /package/dist/{chunk-OCOGQRHW.js.map → chunk-HS6DBMST.js.map} +0 -0
- /package/dist/{chunk-UXRWVXBP.js.map → chunk-YT7MRFCP.js.map} +0 -0
- /package/dist/{export-audit-MT2LTMIN.js.map → export-audit-ODGQTQIK.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
|
+
}
|
|
491
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
|
+
}
|
|
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,47 +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
|
-
this.
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
dbName: this.dbName,
|
|
548
|
-
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.`
|
|
549
|
-
});
|
|
550
|
-
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();
|
|
551
687
|
}
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
|
|
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)
|
|
555
693
|
);
|
|
694
|
+
this.tabSession.stopRelay = startLeaderRpcRelay(this.tabSession.channelName, workerBridge);
|
|
695
|
+
this.bridge = workerBridge;
|
|
696
|
+
return;
|
|
556
697
|
}
|
|
557
|
-
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) {
|
|
558
711
|
const response = await this.sendRequest({
|
|
559
712
|
id: 0,
|
|
560
713
|
type: "open",
|
|
@@ -567,8 +720,7 @@ var SqliteWasmAdapter = class {
|
|
|
567
720
|
dbName: this.dbName
|
|
568
721
|
});
|
|
569
722
|
}
|
|
570
|
-
|
|
571
|
-
this.opened = true;
|
|
723
|
+
return response;
|
|
572
724
|
}
|
|
573
725
|
/**
|
|
574
726
|
* Surface a `store:opfs-unavailable` diagnostic when the worker reported that
|
|
@@ -577,12 +729,12 @@ var SqliteWasmAdapter = class {
|
|
|
577
729
|
* bridge that does not report a mode, e.g. the Node mock) emits nothing.
|
|
578
730
|
*/
|
|
579
731
|
reportStorageMode(data) {
|
|
580
|
-
|
|
732
|
+
this.storageOpenState = parseStorageOpenState(data);
|
|
733
|
+
if (!this.emitNonPersistentDiagnostic || !this.emitter || !this.storageOpenState) {
|
|
581
734
|
return;
|
|
582
735
|
}
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
const reason = mode.fallbackReason ?? "unsupported";
|
|
736
|
+
if (this.storageOpenState.persistent === false) {
|
|
737
|
+
const reason = this.storageOpenState.fallbackReason ?? "unsupported";
|
|
586
738
|
this.emitter.emit({
|
|
587
739
|
type: "store:opfs-unavailable",
|
|
588
740
|
dbName: this.dbName,
|
|
@@ -591,6 +743,18 @@ var SqliteWasmAdapter = class {
|
|
|
591
743
|
});
|
|
592
744
|
}
|
|
593
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
|
+
}
|
|
594
758
|
async close() {
|
|
595
759
|
if (!this.bridge) return;
|
|
596
760
|
try {
|
|
@@ -602,7 +766,6 @@ var SqliteWasmAdapter = class {
|
|
|
602
766
|
await this.tabSession.releaseLock();
|
|
603
767
|
}
|
|
604
768
|
this.tabSession = null;
|
|
605
|
-
this.sharedWorker = null;
|
|
606
769
|
this.bridge.terminate();
|
|
607
770
|
this.bridge = null;
|
|
608
771
|
this.opened = false;
|
|
@@ -621,7 +784,9 @@ var SqliteWasmAdapter = class {
|
|
|
621
784
|
}
|
|
622
785
|
this.promoting = true;
|
|
623
786
|
const { WebWorkerBridge: WebWorkerBridge2 } = await Promise.resolve().then(() => (init_sqlite_wasm_channel(), sqlite_wasm_channel_exports));
|
|
624
|
-
const workerBridge = new
|
|
787
|
+
const workerBridge = new TransactionSerializingWorkerBridge(
|
|
788
|
+
new WebWorkerBridge2(this.workerUrl, this.workerResponseTimeoutMs)
|
|
789
|
+
);
|
|
625
790
|
const previousBridge = this.bridge;
|
|
626
791
|
this.bridge = workerBridge;
|
|
627
792
|
if (this.tabSession) {
|
|
@@ -752,6 +917,20 @@ var SqliteWasmAdapter = class {
|
|
|
752
917
|
}
|
|
753
918
|
}
|
|
754
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
|
+
}
|
|
755
934
|
|
|
756
935
|
// src/adapters/sqlite-wasm.ts
|
|
757
936
|
init_sqlite_wasm_channel();
|