@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.
Files changed (50) hide show
  1. package/dist/adapters/better-sqlite3.d.cts +1 -1
  2. package/dist/adapters/better-sqlite3.d.ts +1 -1
  3. package/dist/adapters/indexeddb.cjs +347 -132
  4. package/dist/adapters/indexeddb.cjs.map +1 -1
  5. package/dist/adapters/indexeddb.d.cts +4 -2
  6. package/dist/adapters/indexeddb.d.ts +4 -2
  7. package/dist/adapters/indexeddb.js +42 -5
  8. package/dist/adapters/indexeddb.js.map +1 -1
  9. package/dist/adapters/sqlite-wasm-worker.cjs.map +1 -1
  10. package/dist/adapters/sqlite-wasm-worker.js +250 -3
  11. package/dist/adapters/sqlite-wasm-worker.js.map +1 -1
  12. package/dist/adapters/sqlite-wasm.cjs +308 -129
  13. package/dist/adapters/sqlite-wasm.cjs.map +1 -1
  14. package/dist/adapters/sqlite-wasm.d.cts +18 -7
  15. package/dist/adapters/sqlite-wasm.d.ts +18 -7
  16. package/dist/adapters/sqlite-wasm.js +2 -2
  17. package/dist/{chunk-Q57CHIIN.js → chunk-RJIEKEN4.js} +310 -130
  18. package/dist/chunk-RJIEKEN4.js.map +1 -0
  19. package/dist/{chunk-COJSQBXW.js → chunk-VDBJQ75K.js} +1 -1
  20. package/dist/chunk-VDBJQ75K.js.map +1 -0
  21. package/dist/{chunk-RL43UYQZ.js → chunk-YT7MRFCP.js} +5 -5
  22. package/dist/index.cjs +26 -0
  23. package/dist/index.cjs.map +1 -1
  24. package/dist/index.d.cts +18 -4
  25. package/dist/index.d.ts +18 -4
  26. package/dist/index.js +44 -18
  27. package/dist/index.js.map +1 -1
  28. package/dist/internal.d.cts +3 -3
  29. package/dist/internal.d.ts +3 -3
  30. package/dist/internal.js +2 -2
  31. package/dist/{sqlite-wasm-channel-NGSBJR44.js → sqlite-wasm-channel-GGHA2BXU.js} +2 -2
  32. package/dist/{sqlite-wasm-channel-Bx57s8Cq.d.cts → sqlite-wasm-channel-L6o3bWQK.d.cts} +2 -2
  33. package/dist/{sqlite-wasm-channel-Bx57s8Cq.d.ts → sqlite-wasm-channel-L6o3bWQK.d.ts} +2 -2
  34. package/dist/{types-C1kigrWz.d.ts → types-BOO9EPt1.d.ts} +1 -1
  35. package/dist/{types-D1MzKVmk.d.ts → types-BslDOTk0.d.cts} +16 -1
  36. package/dist/{types-D1MzKVmk.d.cts → types-BslDOTk0.d.ts} +16 -1
  37. package/dist/{types-DAo9oduO.d.cts → types-C_GDavG-.d.cts} +1 -1
  38. package/package.json +2 -12
  39. package/dist/adapters/sqlite-wasm-shared-host.cjs +0 -345
  40. package/dist/adapters/sqlite-wasm-shared-host.cjs.map +0 -1
  41. package/dist/adapters/sqlite-wasm-shared-host.d.cts +0 -82
  42. package/dist/adapters/sqlite-wasm-shared-host.d.ts +0 -82
  43. package/dist/adapters/sqlite-wasm-shared-host.js +0 -61
  44. package/dist/adapters/sqlite-wasm-shared-host.js.map +0 -1
  45. package/dist/chunk-3Y2IERBF.js +0 -255
  46. package/dist/chunk-3Y2IERBF.js.map +0 -1
  47. package/dist/chunk-COJSQBXW.js.map +0 -1
  48. package/dist/chunk-Q57CHIIN.js.map +0 -1
  49. /package/dist/{chunk-RL43UYQZ.js.map → chunk-YT7MRFCP.js.map} +0 -0
  50. /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
- function isSharedWorkerStorageSupported() {
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 = crypto.randomUUID();
265
- const msg = { type: RPC_REQUEST, requestId, request };
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
- sharedWorker = null;
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
- } else if (this.sharedWorkerUrl && this.workerUrl && isSharedWorkerStorageSupported()) {
332
- const workerUrlString = typeof this.workerUrl === "string" ? this.workerUrl : this.workerUrl.href;
333
- const sharedUrl = typeof this.sharedWorkerUrl === "string" ? this.sharedWorkerUrl : this.sharedWorkerUrl.href;
334
- this.sharedWorker = new SharedWorker(sharedUrl, {
335
- type: "module",
336
- name: `kora-sw-${this.dbName}`
337
- });
338
- this.bridge = new SharedWorkerClientBridge(this.sharedWorker, this.dbName, workerUrlString);
339
- } else if (this.workerUrl) {
340
- this.schema = schema;
341
- this.tabSession = await acquireTabStorageSession(this.dbName, {
342
- onPromote: () => {
343
- void this.promoteToLeader();
344
- }
345
- });
346
- const { WebWorkerBridge } = await import("./sqlite-wasm-channel-NGSBJR44.js");
347
- if (this.tabSession.role === "leader") {
348
- const workerBridge = new WebWorkerBridge(this.workerUrl, this.workerResponseTimeoutMs);
349
- this.tabSession.stopRelay = startLeaderRpcRelay(this.tabSession.channelName, workerBridge);
350
- this.bridge = workerBridge;
351
- } else {
352
- const followerBridge = new FollowerBroadcastBridge(
353
- this.tabSession.channelName,
354
- this.workerResponseTimeoutMs
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
- } else {
365
- throw new AdapterError(
366
- 'SqliteWasmAdapter requires either a bridge (for testing) or a workerUrl (for browsers). Pass { bridge: new MockWorkerBridge() } for tests, or { workerUrl: "/worker.js" } for browsers.'
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 ddlStatements = generateFullDDL(schema);
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
- this.reportStorageMode(response.data);
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
- if (!this.emitter || typeof data !== "object" || data === null) {
542
+ this.storageOpenState = parseStorageOpenState(data);
543
+ if (!this.emitNonPersistentDiagnostic || !this.emitter || !this.storageOpenState) {
393
544
  return;
394
545
  }
395
- const mode = data;
396
- if (mode.persistent === false) {
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-NGSBJR44.js");
436
- const workerBridge = new WebWorkerBridge(this.workerUrl, this.workerResponseTimeoutMs);
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-Q57CHIIN.js.map
751
+ //# sourceMappingURL=chunk-RJIEKEN4.js.map