@minnowdb/core 0.7.0 → 0.7.1

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 (95) hide show
  1. package/dist/block-format/block.js +254 -325
  2. package/dist/block-format/checksum.js +30 -52
  3. package/dist/block-format/codecs.js +112 -125
  4. package/dist/block-format/column.js +187 -205
  5. package/dist/block-format/index.js +5 -1
  6. package/dist/block-format/physical.js +371 -387
  7. package/dist/block-format/types.js +4 -1
  8. package/dist/block-format/unicode.js +30 -39
  9. package/dist/date-value.js +67 -78
  10. package/dist/engine/artifact-cache.js +106 -116
  11. package/dist/engine/batch.js +36 -44
  12. package/dist/engine/buffered-writer.js +156 -158
  13. package/dist/engine/byte-estimates.js +20 -22
  14. package/dist/engine/cache-limits.js +24 -25
  15. package/dist/engine/cancellation.js +5 -3
  16. package/dist/engine/catalog.js +64 -73
  17. package/dist/engine/client.js +715 -780
  18. package/dist/engine/database.js +15941 -19313
  19. package/dist/engine/defaults.js +104 -111
  20. package/dist/engine/errors.js +113 -128
  21. package/dist/engine/fts.js +233 -280
  22. package/dist/engine/group-index.js +287 -321
  23. package/dist/engine/index.js +23 -12
  24. package/dist/engine/join-index.js +177 -186
  25. package/dist/engine/keyed-live.js +168 -173
  26. package/dist/engine/live-api.js +0 -1
  27. package/dist/engine/live-equal.js +32 -37
  28. package/dist/engine/live.js +603 -647
  29. package/dist/engine/memory.js +109 -111
  30. package/dist/engine/optimizer.d.ts +19 -1
  31. package/dist/engine/optimizer.js +2633 -2934
  32. package/dist/engine/point-read.js +122 -182
  33. package/dist/engine/query-api.js +12 -3
  34. package/dist/engine/query-cache.js +61 -69
  35. package/dist/engine/query.d.ts +6 -0
  36. package/dist/engine/query.js +8503 -9980
  37. package/dist/engine/result-wire.js +221 -252
  38. package/dist/engine/schema-wire.js +97 -112
  39. package/dist/engine/schema.js +1099 -1254
  40. package/dist/engine/sort-keys.js +315 -389
  41. package/dist/engine/sql-domains.js +646 -769
  42. package/dist/engine/sql-driver.js +0 -1
  43. package/dist/engine/sql-functions.js +1031 -1125
  44. package/dist/engine/sql-json.js +199 -218
  45. package/dist/engine/sql-semantics.js +572 -639
  46. package/dist/engine/typed-live.js +271 -293
  47. package/dist/engine/vector.js +4484 -5145
  48. package/dist/engine/worker-host.d.ts +8 -63
  49. package/dist/engine/worker-host.js +28 -1130
  50. package/dist/engine/worker-indexeddb.d.ts +1 -0
  51. package/dist/engine/worker-indexeddb.js +3 -0
  52. package/dist/engine/worker-memory.d.ts +1 -0
  53. package/dist/engine/worker-memory.js +3 -0
  54. package/dist/engine/worker-opfs.d.ts +1 -0
  55. package/dist/engine/worker-opfs.js +3 -0
  56. package/dist/engine/worker-server.d.ts +93 -0
  57. package/dist/engine/worker-server.js +999 -0
  58. package/dist/engine/worker-store-indexeddb.d.ts +2 -0
  59. package/dist/engine/worker-store-indexeddb.js +11 -0
  60. package/dist/engine/worker-store-memory.d.ts +2 -0
  61. package/dist/engine/worker-store-memory.js +6 -0
  62. package/dist/engine/worker-store-opfs.d.ts +2 -0
  63. package/dist/engine/worker-store-opfs.js +9 -0
  64. package/dist/engine/worker.js +0 -12
  65. package/dist/engine/write-block-planner.js +93 -100
  66. package/dist/plan/index.js +15 -16
  67. package/dist/plan/model.d.ts +6 -0
  68. package/dist/plan/model.js +20 -27
  69. package/dist/storage/index.js +0 -12
  70. package/dist/storage/indexeddb.js +11776 -13171
  71. package/dist/storage/memory.js +1198 -1241
  72. package/dist/storage/opfs/files.js +238 -295
  73. package/dist/storage/opfs/index.js +9 -3
  74. package/dist/storage/opfs/leader.js +4386 -4856
  75. package/dist/storage/opfs/rpc.js +223 -259
  76. package/dist/storage/opfs/snapshot-ledger.js +219 -260
  77. package/dist/storage/opfs/store.js +884 -1003
  78. package/dist/storage/persistence.js +59 -74
  79. package/dist/storage/snapshot-stream.js +788 -826
  80. package/dist/storage/snapshot.js +36 -37
  81. package/dist/storage/toolkit/extents.js +444 -520
  82. package/dist/storage/toolkit/index.js +28 -29
  83. package/dist/storage/toolkit/record-core.js +5792 -6377
  84. package/dist/storage/toolkit/sync-file.js +34 -42
  85. package/dist/storage/toolkit/wal.js +92 -127
  86. package/dist/storage/toolkit/wire.js +232 -256
  87. package/dist/storage/types.js +2948 -3250
  88. package/dist/testing/block-store-conformance.js +1556 -1629
  89. package/dist/testing/index.js +310 -291
  90. package/dist/testing/opfs-shim.js +280 -312
  91. package/dist/testing/simulator.js +496 -534
  92. package/dist/testing/sqllogictest.js +425 -365
  93. package/dist/transactions/index.js +1464 -1712
  94. package/dist/worker-protocol/index.js +70 -69
  95. package/package.json +20 -2
@@ -1,1133 +1,31 @@
1
- import { MAX_SNAPSHOT_STREAM_CHUNK_BYTES } from "../storage/snapshot.js";
2
- import { parseRpcRequest, MAX_DATABASE_RPC_IN_FLIGHT, rpcEvent, rpcFailure, rpcResult, serializeError, } from "../worker-protocol/index.js";
3
- import { MinnowDatabase, } from "./database.js";
4
- import { encodeQueryResult, encodeQueryRows } from "./result-wire.js";
5
- import { deserializeSchema, serializeMigrationSteps } from "./schema-wire.js";
6
- /**
7
- * The staged-mutation ops a client may name inside a "stage" call. The op arrives as wire data,
8
- * so it must be checked against this list before indexing into the session — otherwise any
9
- * property name (including Object.prototype members like "constructor") would be reachable.
10
- */
11
- const stageOps = ["insertBatch", "upsertBatch", "updateBatch", "deleteBatch"];
12
- function isStageOp(value) {
13
- return typeof value === "string" && stageOps.includes(value);
1
+ import { attachWorkerHost } from "./worker-server.js";
2
+ import { exposeDatabase, singleStoreFactory, MAX_WORKER_HANDLES_PER_CONNECTION } from "./worker-server.js";
3
+ function attachDatabaseWorker(scope, options = {}) {
4
+ attachWorkerHost(scope, options.createStore ?? createStore, options);
14
5
  }
15
- /**
16
- * Root methods whose arguments and results are already structured-clone-safe. `satisfies` makes
17
- * a removed or renamed database method a compile error, while the runtime guard below fails
18
- * closed if a caller supplies a malformed database object through the manual host seam.
19
- */
20
- const directRootMethods = [
21
- "createTable",
22
- "createView",
23
- "dropView",
24
- "dropColumn",
25
- "dropTable",
26
- "createIndex",
27
- "dropIndex",
28
- "buildFtsIndex",
29
- "introspect",
30
- "listTables",
31
- "insertBatch",
32
- "insert",
33
- "upsertBatch",
34
- "upsert",
35
- "updateBatch",
36
- "update",
37
- "deleteBatch",
38
- "delete",
39
- "runStatement",
40
- "explain",
41
- "listVisibleSegmentPage",
42
- "cleanupQuerySpill",
43
- "compactTable",
44
- "compactTableStep",
45
- "resumeCompactionJob",
46
- "listCompactionJobs",
47
- "cancelCompactionJob",
48
- "collectGarbage",
49
- "collectGarbageStep",
50
- "resumeGarbageCollectionJob",
51
- "bufferPoolStats",
52
- "maintenanceStatus",
53
- "checkIntegrity",
54
- "storageStats",
55
- "inspectInterruptedImport",
56
- "abortInterruptedImport",
57
- "listGarbageCollectionJobs",
58
- ];
59
- function isDirectRootMethod(value) {
60
- return directRootMethods.includes(value);
61
- }
62
- /**
63
- * A call result that travels as a columnar frame with its buffers transferred. Only query
64
- * results take this path: rows of objects are what structured clone is slowest at, so they are
65
- * pivoted into per-column arrays here and rebuilt by the client.
66
- */
67
- class ColumnarResult {
68
- encoded;
69
- constructor(encoded) {
70
- this.encoded = encoded;
71
- }
72
- }
73
- class CursorBatchResult {
74
- encoded;
75
- constructor(encoded) {
76
- this.encoded = encoded;
77
- }
78
- }
79
- class SnapshotChunkResult {
80
- chunk;
81
- constructor(chunk) {
82
- this.chunk = chunk;
83
- }
84
- }
85
- class WriteScopeAbortedError extends Error {
86
- name = "WriteScopeAbortedError";
87
- constructor() {
88
- super("Write scope aborted");
89
- }
90
- }
91
- /** One-chunk rendezvous. The sender is acknowledged only when the decoder asks for more. */
92
- class SnapshotImportChunkQueue {
93
- #queued;
94
- #consumed;
95
- #reader;
96
- #finished = false;
97
- #failure;
98
- [Symbol.asyncIterator]() {
99
- return this;
100
- }
101
- push(chunk) {
102
- if (chunk.byteLength > MAX_SNAPSHOT_STREAM_CHUNK_BYTES) {
103
- throw new RangeError("Snapshot transfer chunk exceeds the 1 MiB limit");
104
- }
105
- if (this.#failure !== undefined)
106
- return Promise.reject(snapshotQueueError(this.#failure));
107
- if (this.#finished)
108
- return Promise.reject(new Error("Snapshot import transfer is closed"));
109
- if (this.#queued !== undefined) {
110
- return Promise.reject(new Error("Snapshot import transfer already has a queued chunk"));
111
- }
112
- return new Promise((resolve, reject) => {
113
- const reader = this.#reader;
114
- if (reader === undefined) {
115
- this.#queued = { chunk, resolve, reject };
116
- return;
117
- }
118
- this.#reader = undefined;
119
- this.#consumed = { resolve, reject };
120
- reader.resolve({ done: false, value: chunk });
121
- });
122
- }
123
- async next() {
124
- this.#consumed?.resolve();
125
- this.#consumed = undefined;
126
- if (this.#failure !== undefined)
127
- throw snapshotQueueError(this.#failure);
128
- const queued = this.#queued;
129
- if (queued !== undefined) {
130
- this.#queued = undefined;
131
- this.#consumed = { resolve: queued.resolve, reject: queued.reject };
132
- return { done: false, value: queued.chunk };
133
- }
134
- if (this.#finished)
135
- return { done: true, value: undefined };
136
- return new Promise((resolve, reject) => {
137
- this.#reader = { resolve, reject };
138
- });
139
- }
140
- async return() {
141
- this.finish();
142
- return { done: true, value: undefined };
143
- }
144
- finish() {
145
- if (this.#finished)
146
- return;
147
- this.#finished = true;
148
- if (this.#queued === undefined) {
149
- const reader = this.#reader;
150
- this.#reader = undefined;
151
- reader?.resolve({ done: true, value: undefined });
152
- }
153
- }
154
- fail(error) {
155
- if (this.#failure !== undefined)
156
- return;
157
- this.#failure = error;
158
- this.#finished = true;
159
- this.#queued?.reject(error);
160
- this.#queued = undefined;
161
- this.#consumed?.reject(error);
162
- this.#consumed = undefined;
163
- this.#reader?.reject(error);
164
- this.#reader = undefined;
165
- }
166
- }
167
- function snapshotQueueError(value) {
168
- return value instanceof Error ? value : new Error("Snapshot transfer failed", { cause: value });
169
- }
170
- function abortError(message) {
171
- const error = new Error(message);
172
- error.name = "AbortError";
173
- return error;
174
- }
175
- /** Hard per-connection ceiling for resident worker resources abandoned by a client. */
176
- export const MAX_WORKER_HANDLES_PER_CONNECTION = 256;
177
- const DEFAULT_WRITE_HANDLE_IDLE_TIMEOUT_MS = 30_000;
178
- const MAX_TIMER_DELAY_MS = 2_147_483_647;
179
- /** Shared by RPC methods that do not expose cancellation, avoiding one controller per call. */
180
- const passiveRpcSignal = new AbortController().signal;
181
- class DatabaseRpcServer {
182
- database;
183
- scope;
184
- options;
185
- #handles = new Map();
186
- #reservedHandleIds = new Set();
187
- #openingHandlePromises = new Set();
188
- #settlingWritePromises = new Set();
189
- #requestAborts = new Map();
190
- #disposed = false;
191
- #inFlightRpcCount = 0;
192
- #inFlightRpcDrain;
193
- #resolveInFlightRpcDrain;
194
- #writeHandleIdleTimeoutMs;
195
- constructor(database, scope, options) {
196
- this.database = database;
197
- this.scope = scope;
198
- this.options = options;
199
- this.#writeHandleIdleTimeoutMs =
200
- options.writeHandleIdleTimeoutMs ?? DEFAULT_WRITE_HANDLE_IDLE_TIMEOUT_MS;
201
- if (!Number.isSafeInteger(this.#writeHandleIdleTimeoutMs) ||
202
- this.#writeHandleIdleTimeoutMs <= 0 ||
203
- this.#writeHandleIdleTimeoutMs > MAX_TIMER_DELAY_MS) {
204
- throw new RangeError(`Worker write-handle idle timeout must be a positive whole number no greater than ${String(MAX_TIMER_DELAY_MS)}`);
205
- }
206
- }
207
- async handle(request) {
208
- if (request.kind === "rpc-init") {
209
- this.scope.postMessage(rpcResult(request.requestId, { ready: true }));
210
- return;
211
- }
212
- if (request.kind === "rpc-cancel") {
213
- this.#requestAborts
214
- .get(request.requestId)
215
- ?.abort(abortError("Database request was cancelled"));
216
- return;
217
- }
218
- const bypassLimit = request.method === "dispose";
219
- if (!bypassLimit && this.#inFlightRpcCount >= MAX_DATABASE_RPC_IN_FLIGHT) {
220
- this.scope.postMessage(rpcFailure(request.requestId, new RangeError(`A database worker connection cannot hold more than ${String(MAX_DATABASE_RPC_IN_FLIGHT)} in-flight requests`)));
221
- return;
222
- }
223
- if (!bypassLimit)
224
- this.#inFlightRpcCount += 1;
225
- const abort = request.method === "query" || request.method === "execute"
226
- ? new AbortController()
227
- : undefined;
228
- if (abort !== undefined)
229
- this.#requestAborts.set(request.requestId, abort);
230
- const context = {
231
- requestId: request.requestId,
232
- signal: abort?.signal ?? passiveRpcSignal,
233
- };
234
- try {
235
- if (this.#disposed)
236
- throw new Error("Database connection is disposed");
237
- const result = request.handleId === null
238
- ? await this.#callRoot(request.method, request.args, context)
239
- : await this.#callHandle(request.handleId, request.method, request.args, context);
240
- if (result instanceof ColumnarResult) {
241
- this.scope.postMessage(rpcResult(request.requestId, result.encoded.payload), {
242
- transfer: result.encoded.transfer,
243
- });
244
- }
245
- else if (result instanceof CursorBatchResult) {
246
- this.scope.postMessage(rpcResult(request.requestId, result.encoded === undefined
247
- ? { done: true }
248
- : { done: false, batch: result.encoded.payload }), result.encoded === undefined ? undefined : { transfer: result.encoded.transfer });
249
- }
250
- else if (result instanceof SnapshotChunkResult) {
251
- this.scope.postMessage(rpcResult(request.requestId, { done: false, chunk: result.chunk }), {
252
- transfer: [result.chunk.buffer],
253
- });
254
- }
255
- else {
256
- this.scope.postMessage(rpcResult(request.requestId, result));
257
- }
258
- }
259
- catch (error) {
260
- this.scope.postMessage(rpcFailure(request.requestId, error));
261
- }
262
- finally {
263
- if (abort !== undefined)
264
- this.#requestAborts.delete(request.requestId);
265
- if (!bypassLimit)
266
- this.#inFlightRpcCount -= 1;
267
- if (this.#inFlightRpcCount === 0 && this.#resolveInFlightRpcDrain !== undefined) {
268
- const resolve = this.#resolveInFlightRpcDrain;
269
- this.#inFlightRpcDrain = undefined;
270
- this.#resolveInFlightRpcDrain = undefined;
271
- resolve();
272
- }
273
- }
274
- }
275
- async #callRoot(method, args, context) {
276
- const database = this.database;
277
- if (isDirectRootMethod(method)) {
278
- const operation = database[method];
279
- if (operation === undefined) {
280
- throw new Error(`Database root method is unavailable: ${method}`);
281
- }
282
- return operation.apply(this.database, args);
283
- }
284
- switch (method) {
285
- case "query": {
286
- const [sql, options, reportStats = false] = args;
287
- return new ColumnarResult(encodeQueryResult(await this.database.query(sql, {
288
- ...options,
289
- signal: context.signal,
290
- ...(reportStats
291
- ? {
292
- onStats: (stats) => {
293
- this.scope.postMessage(rpcEvent(context.requestId, "stats", stats));
294
- },
295
- }
296
- : {}),
297
- })));
298
- }
299
- case "execute": {
300
- const [sql, params, options, reportStats = false] = args;
301
- return this.database.execute(sql, params, {
302
- ...options,
303
- signal: context.signal,
304
- ...(reportStats
305
- ? {
306
- onStats: (stats) => {
307
- this.scope.postMessage(rpcEvent(context.requestId, "stats", stats));
308
- },
309
- }
310
- : {}),
311
- });
312
- }
313
- case "queryCursorOpen": {
314
- const handleId = this.#claimHandleId(args[0]);
315
- const [sql, options, reportStats = false] = args.slice(1);
316
- try {
317
- const iterator = this.database.queryCursor(sql, {
318
- ...options,
319
- ...(reportStats
320
- ? {
321
- onStats: (stats) => {
322
- this.scope.postMessage(rpcEvent(handleId, "stats", stats));
323
- },
324
- }
325
- : {}),
326
- });
327
- this.#publishHandle(handleId, { type: "query-cursor", iterator });
328
- }
329
- catch (error) {
330
- this.#releaseHandleId(handleId);
331
- throw error;
332
- }
333
- return { handleId };
334
- }
335
- case "run": {
336
- const query = args[0];
337
- return new ColumnarResult(encodeQueryRows(await this.database.run(query)));
338
- }
339
- case "readTable": {
340
- // A table's rows are uniform — every row carries every read column — so they take the
341
- // same columnar frame a query result does.
342
- const [tableName, versionOrOptions] = args;
343
- const rows = await this.database.readTable(tableName, versionOrOptions);
344
- return new ColumnarResult(encodeQueryRows(rows));
345
- }
346
- case "migrate": {
347
- const result = await this.database.migrate(deserializeSchema(args[0]), args[1] ?? {});
348
- return {
349
- createdTables: result.createdTables,
350
- alteredTables: result.alteredTables,
351
- droppedTables: result.droppedTables,
352
- replacedViews: result.replacedViews,
353
- droppedViews: result.droppedViews,
354
- steps: serializeMigrationSteps(result.steps),
355
- };
356
- }
357
- case "writeOpen":
358
- return this.#trackOpeningHandle(this.#openWriteHandle());
359
- case "snapshotOpen":
360
- return this.#trackOpeningHandle(this.#openSnapshotHandle());
361
- case "exportSnapshotOpen": {
362
- const handleId = this.#claimGeneratedHandleId();
363
- const iterator = this.database.exportSnapshotStream()[Symbol.asyncIterator]();
364
- try {
365
- this.#publishHandle(handleId, {
366
- type: "snapshot-export",
367
- iterator,
368
- reading: false,
369
- done: false,
370
- });
371
- }
372
- catch (error) {
373
- await iterator.return(undefined);
374
- this.#releaseHandleId(handleId);
375
- throw error;
376
- }
377
- return { handleId };
378
- }
379
- case "importSnapshotOpen": {
380
- // Named by the client, like the other event-producing handles, so its progress route
381
- // exists before the first frame the load can emit.
382
- const handleId = this.#claimHandleId(args[0]);
383
- const queue = new SnapshotImportChunkQueue();
384
- const abort = new AbortController();
385
- const task = this.database.importSnapshotStream(queue, {
386
- signal: abort.signal,
387
- onProgress: (progress) => {
388
- this.scope.postMessage(rpcEvent(handleId, "progress", progress));
389
- },
390
- });
391
- void task.catch((error) => queue.fail(error));
392
- try {
393
- this.#publishHandle(handleId, { type: "snapshot-import", queue, task, abort });
394
- }
395
- catch (error) {
396
- abort.abort(error);
397
- queue.fail(error);
398
- await task.catch(() => undefined);
399
- this.#releaseHandleId(handleId);
400
- throw error;
401
- }
402
- return { handleId };
403
- }
404
- case "bufferedWriter": {
405
- // Event-producing handles are named by the client so its event routes exist before the
406
- // first frame the handle can emit.
407
- const handleId = this.#claimHandleId(args[0]);
408
- let writer;
409
- try {
410
- writer = this.database.bufferedWriter(args[1], {
411
- ...args[2],
412
- onError: (error) => {
413
- this.scope.postMessage(rpcEvent(handleId, "error", serializeError(error)));
414
- },
415
- });
416
- this.#publishHandle(handleId, { type: "writer", writer });
417
- }
418
- catch (error) {
419
- await writer?.close().catch(() => undefined);
420
- this.#releaseHandleId(handleId);
421
- throw error;
422
- }
423
- return { handleId };
424
- }
425
- case "liveQueries": {
426
- const handleId = this.#claimHandleId(args[0]);
427
- const { pollIntervalMs, channelName } = (args[1] ?? {});
428
- // The database owns channelName resolution (and closes the channel with the set).
429
- let set;
430
- try {
431
- set = this.database.liveQueries({
432
- ...(channelName === undefined ? {} : { channelName }),
433
- ...(pollIntervalMs === undefined ? {} : { pollIntervalMs }),
434
- });
435
- this.#publishHandle(handleId, { type: "live-set", set, subscriptionIds: new Set() });
436
- }
437
- catch (error) {
438
- set?.close();
439
- this.#releaseHandleId(handleId);
440
- throw error;
441
- }
442
- return { handleId };
443
- }
444
- case "dispose": {
445
- await this.#dispose();
446
- return { disposed: true };
447
- }
448
- case "setVisibility": {
449
- this.options.onVisibility?.(args[0] === true);
450
- return { acknowledged: true };
451
- }
452
- default:
453
- throw new Error(`Unsupported database method: ${method}`);
454
- }
455
- }
456
- async #openWriteHandle() {
457
- // Same deferred-callback shape as snapshotOpen: the scoped write() stays pending until the
458
- // client commits or aborts, so its staged transaction spans the RPC session.
459
- const handleId = this.#claimGeneratedHandleId();
460
- let sessionResolve;
461
- let sessionReject;
462
- const sessionReady = new Promise((resolveSession, rejectSession) => {
463
- sessionResolve = resolveSession;
464
- sessionReject = rejectSession;
465
- });
466
- let finish;
467
- const done = this.database
468
- .write(async (session) => {
469
- sessionResolve(session);
470
- await new Promise((resolveCommit, rejectAbort) => {
471
- finish = (commit) => {
472
- if (commit)
473
- resolveCommit();
474
- else
475
- rejectAbort(new WriteScopeAbortedError());
476
- };
477
- });
478
- })
479
- .then((outcome) => ({ version: outcome.version }));
480
- // beginDeferred can fail before the callback starts (for example at the durable reader-lease
481
- // ceiling). Reject the open rendezvous too and observe `done` immediately.
482
- void done.catch((error) => sessionReject(error));
483
- try {
484
- const session = await sessionReady;
485
- if (finish === undefined)
486
- throw new Error("Write scope did not install its settlement");
487
- this.#publishHandle(handleId, {
488
- type: "write",
489
- session,
490
- finish,
491
- done,
492
- activeCalls: 0,
493
- activeCallDone: undefined,
494
- finishActiveCall: undefined,
495
- open: true,
496
- idleTimer: undefined,
497
- });
498
- return { handleId };
499
- }
500
- catch (error) {
501
- // Publication can lose a race with connection disposal. If the callback started, abort and
502
- // join it before the opening task releases its lifecycle slot.
503
- finish?.(false);
504
- await done.catch(() => undefined);
505
- this.#releaseHandleId(handleId);
506
- throw error;
507
- }
508
- }
509
- async #openSnapshotHandle() {
510
- // The scoped snapshot() holds its lease until the callback settles; the handle keeps the
511
- // callback pending until close, so the pin spans the RPC session.
512
- const handleId = this.#claimGeneratedHandleId();
513
- let release;
514
- let resolveVersion;
515
- let rejectVersion;
516
- const versionReady = new Promise((resolve, reject) => {
517
- resolveVersion = resolve;
518
- rejectVersion = reject;
519
- });
520
- const done = this.database.snapshot(async (session) => {
521
- resolveVersion(session.version);
522
- await new Promise((resolveRelease) => {
523
- release = resolveRelease;
524
- });
525
- });
526
- // Lease admission may fail before the callback installs `release`. Preserve that typed error
527
- // through the rendezvous and keep the task observed from creation.
528
- void done.catch((error) => rejectVersion(error));
529
- try {
530
- const version = await versionReady;
531
- if (release === undefined)
532
- throw new Error("Snapshot scope did not install its release");
533
- this.#publishHandle(handleId, { type: "snapshot", release, done });
534
- return { handleId, version };
535
- }
536
- catch (error) {
537
- release?.();
538
- await done.catch(() => undefined);
539
- this.#releaseHandleId(handleId);
540
- throw error;
541
- }
542
- }
543
- #trackOpeningHandle(opening) {
544
- this.#openingHandlePromises.add(opening);
545
- void opening.then(() => this.#openingHandlePromises.delete(opening), () => this.#openingHandlePromises.delete(opening));
546
- return opening;
547
- }
548
- async #callHandle(handleId, method, args, context) {
549
- const handle = this.#handles.get(handleId);
550
- if (handle === undefined)
551
- throw new Error(`Unknown handle: ${handleId}`);
552
- if (handle.type === "write") {
553
- this.#beginWriteHandleCall(handle);
554
- try {
555
- return await this.#callWriteHandle(handleId, handle, method, args, context);
556
- }
557
- finally {
558
- this.#endWriteHandleCall(handleId, handle);
559
- }
560
- }
561
- switch (handle.type) {
562
- case "snapshot": {
563
- if (method !== "close")
564
- throw new Error(`Unsupported snapshot method: ${method}`);
565
- handle.release();
566
- this.#handles.delete(handleId);
567
- await handle.done;
568
- return undefined;
569
- }
570
- case "snapshot-export":
571
- return this.#callSnapshotExport(handleId, handle, method, args);
572
- case "snapshot-import":
573
- return this.#callSnapshotImport(handleId, handle, method, args);
574
- case "writer":
575
- return this.#callWriter(handleId, handle.writer, method, args);
576
- case "query-cursor": {
577
- if (method === "next") {
578
- const next = await handle.iterator.next();
579
- if (next.done) {
580
- this.#handles.delete(handleId);
581
- return new CursorBatchResult(undefined);
582
- }
583
- return new CursorBatchResult(encodeQueryResult(next.value));
584
- }
585
- if (method === "close") {
586
- await handle.iterator.return?.();
587
- this.#handles.delete(handleId);
588
- return undefined;
589
- }
590
- throw new Error(`Unsupported query cursor method: ${method}`);
591
- }
592
- case "live-set":
593
- return this.#callLiveSet(handleId, handle, method, args);
594
- case "live-subscription": {
595
- if (method !== "close")
596
- throw new Error(`Unsupported subscription method: ${method}`);
597
- handle.subscription.close();
598
- this.#handles.delete(handleId);
599
- const parent = this.#handles.get(handle.setId);
600
- if (parent?.type === "live-set")
601
- parent.subscriptionIds.delete(handleId);
602
- return undefined;
603
- }
604
- }
605
- }
606
- async #callWriteHandle(handleId, handle, method, args, context) {
607
- if (method === "query") {
608
- const [sql, options, reportStats = false] = args;
609
- return new ColumnarResult(encodeQueryResult(await handle.session.query(sql, {
610
- ...options,
611
- signal: context.signal,
612
- ...(reportStats
613
- ? {
614
- onStats: (stats) => {
615
- this.scope.postMessage(rpcEvent(context.requestId, "stats", stats));
616
- },
617
- }
618
- : {}),
619
- })));
620
- }
621
- if (method === "execute") {
622
- const [sql, params] = args;
623
- return handle.session.execute(sql, params);
624
- }
625
- if (method === "stage") {
626
- const [op, tableName, input] = args;
627
- if (!isStageOp(op)) {
628
- throw new Error(`Unsupported write stage operation: ${String(op)}`);
629
- }
630
- return handle.session[op](tableName, input);
631
- }
632
- if (method === "commit")
633
- return this.#settleWriteHandle(handleId, handle, true);
634
- if (method === "abort")
635
- return this.#settleWriteHandle(handleId, handle, false);
636
- throw new Error(`Unsupported write method: ${method}`);
637
- }
638
- /**
639
- * Hands one slice of an encoded snapshot back. The slice is copied rather than viewed: a
640
- * subarray shares the whole buffer, and structured clone copies the buffer behind a view, not
641
- * the window into it — so every read would post the entire snapshot again.
642
- */
643
- async #callSnapshotExport(handleId, handle, method, args) {
644
- switch (method) {
645
- case "read": {
646
- if (args.length !== 0)
647
- throw new TypeError("Snapshot stream read takes no arguments");
648
- if (handle.reading)
649
- throw new Error("Snapshot stream already has a read in flight");
650
- if (handle.done)
651
- return { done: true };
652
- handle.reading = true;
653
- try {
654
- const next = await handle.iterator.next();
655
- if (next.done === true) {
656
- handle.done = true;
657
- return { done: true };
658
- }
659
- // The iterator may yield a view into a 64 MiB block. Transfer only this bounded slice;
660
- // detaching the larger backing buffer would corrupt subsequent slices.
661
- const chunk = new Uint8Array(next.value.byteLength);
662
- chunk.set(next.value);
663
- return new SnapshotChunkResult(chunk);
664
- }
665
- finally {
666
- handle.reading = false;
667
- }
668
- }
669
- case "close": {
670
- await handle.iterator.return?.();
671
- this.#handles.delete(handleId);
672
- return undefined;
673
- }
674
- default:
675
- throw new Error(`Unsupported snapshot export method: ${method}`);
676
- }
677
- }
678
- /** Collects an uploaded snapshot chunk by chunk, then loads it, reporting progress as events. */
679
- async #callSnapshotImport(handleId, handle, method, args) {
680
- switch (method) {
681
- case "write": {
682
- const chunk = args[0];
683
- if (!(chunk instanceof Uint8Array)) {
684
- throw new TypeError("Snapshot chunk must be a Uint8Array");
685
- }
686
- await handle.queue.push(chunk);
687
- return undefined;
688
- }
689
- case "finish": {
690
- try {
691
- handle.queue.finish();
692
- await handle.task;
693
- }
694
- finally {
695
- this.#handles.delete(handleId);
696
- }
697
- return undefined;
698
- }
699
- case "close": {
700
- handle.queue.fail(new Error("Snapshot import transfer was cancelled"));
701
- await handle.task.catch(() => undefined);
702
- this.#handles.delete(handleId);
703
- return undefined;
704
- }
705
- case "cancel": {
706
- handle.abort.abort(new Error("Snapshot import was cancelled"));
707
- handle.queue.fail(handle.abort.signal.reason);
708
- await handle.task.catch(() => undefined);
709
- this.#handles.delete(handleId);
710
- return undefined;
711
- }
712
- default:
713
- throw new Error(`Unsupported snapshot import method: ${method}`);
714
- }
715
- }
716
- async #callWriter(handleId, writer, method, args) {
717
- switch (method) {
718
- case "add":
719
- return writer.add(args[0]);
720
- case "flush":
721
- return writer.flush();
722
- case "requestFlush":
723
- writer.requestFlush();
724
- return undefined;
725
- case "discard":
726
- return writer.discard();
727
- case "stats":
728
- return { pendingRowCount: writer.pendingRowCount, estimatedBytes: writer.estimatedBytes };
729
- case "close": {
730
- const result = await writer.close();
731
- this.#handles.delete(handleId);
732
- return result;
733
- }
734
- default:
735
- throw new Error(`Unsupported buffered writer method: ${method}`);
736
- }
737
- }
738
- async #callLiveSet(handleId, handle, method, args) {
739
- switch (method) {
740
- case "subscribe": {
741
- // The client names the subscription so it can route the initial change event, which is
742
- // posted before this call's own result frame.
743
- const subscriptionId = this.#claimHandleId(args[0]);
744
- const query = args[1];
745
- let subscription;
746
- try {
747
- subscription = await handle.set.subscribe(query, {
748
- onChange: (result) => {
749
- const encoded = encodeQueryResult(result);
750
- this.scope.postMessage(rpcEvent(subscriptionId, "change", encoded.payload), {
751
- transfer: encoded.transfer,
752
- });
753
- },
754
- onError: (error) => {
755
- this.scope.postMessage(rpcEvent(subscriptionId, "error", serializeError(error)));
756
- },
757
- onComplete: () => {
758
- this.scope.postMessage(rpcEvent(subscriptionId, "complete", null));
759
- this.#handles.delete(subscriptionId);
760
- const parent = this.#handles.get(handleId);
761
- if (parent?.type === "live-set")
762
- parent.subscriptionIds.delete(subscriptionId);
763
- },
764
- });
765
- this.#publishHandle(subscriptionId, {
766
- type: "live-subscription",
767
- subscription,
768
- setId: handleId,
769
- });
770
- }
771
- catch (error) {
772
- subscription?.close();
773
- this.#releaseHandleId(subscriptionId);
774
- throw error;
775
- }
776
- handle.subscriptionIds.add(subscriptionId);
777
- return { dependencyTableIds: subscription.dependencyTableIds };
778
- }
779
- case "observe": {
780
- const subscriptionId = this.#claimHandleId(args[0]);
781
- const query = args[1];
782
- let subscription;
783
- try {
784
- subscription = await handle.set.observe(query, {
785
- onInvalidate: (invalidation) => {
786
- this.scope.postMessage(rpcEvent(subscriptionId, "invalidate", invalidation));
787
- },
788
- onError: (error) => {
789
- this.scope.postMessage(rpcEvent(subscriptionId, "error", serializeError(error)));
790
- },
791
- onComplete: () => {
792
- this.scope.postMessage(rpcEvent(subscriptionId, "complete", null));
793
- this.#handles.delete(subscriptionId);
794
- const parent = this.#handles.get(handleId);
795
- if (parent?.type === "live-set")
796
- parent.subscriptionIds.delete(subscriptionId);
797
- },
798
- });
799
- this.#publishHandle(subscriptionId, {
800
- type: "live-subscription",
801
- subscription,
802
- setId: handleId,
803
- });
804
- }
805
- catch (error) {
806
- subscription?.close();
807
- this.#releaseHandleId(subscriptionId);
808
- throw error;
809
- }
810
- handle.subscriptionIds.add(subscriptionId);
811
- return { dependencyTableIds: subscription.dependencyTableIds };
812
- }
813
- case "stats":
814
- return handle.set.stats;
815
- case "notifyLocalCommit":
816
- handle.set.notifyLocalCommit();
817
- return undefined;
818
- case "refresh":
819
- return handle.set.refresh();
820
- case "close":
821
- this.#closeLiveSet(handleId, handle);
822
- return undefined;
823
- default:
824
- throw new Error(`Unsupported live query method: ${method}`);
825
- }
826
- }
827
- #claimHandleId(value) {
828
- if (typeof value !== "string" || value.length === 0) {
829
- throw new TypeError("Handle ID must be a non-empty string");
830
- }
831
- if (this.#handles.has(value) || this.#reservedHandleIds.has(value)) {
832
- throw new Error(`Handle ID already exists: ${value}`);
833
- }
834
- if (this.#handles.size + this.#reservedHandleIds.size + this.#settlingWritePromises.size >=
835
- MAX_WORKER_HANDLES_PER_CONNECTION) {
836
- throw new Error(`Database connection cannot hold more than ${String(MAX_WORKER_HANDLES_PER_CONNECTION)} open handles (active or settling)`);
837
- }
838
- this.#reservedHandleIds.add(value);
839
- return value;
840
- }
841
- #claimGeneratedHandleId() {
842
- for (;;) {
843
- const id = crypto.randomUUID();
844
- if (!this.#handles.has(id) && !this.#reservedHandleIds.has(id))
845
- return this.#claimHandleId(id);
846
- }
847
- }
848
- #publishHandle(id, handle) {
849
- if (this.#disposed)
850
- throw new Error("Database connection is disposed");
851
- if (!this.#reservedHandleIds.delete(id))
852
- throw new Error(`Handle ID was not reserved: ${id}`);
853
- this.#handles.set(id, handle);
854
- if (handle.type === "write")
855
- this.#armWriteHandleIdleTimer(id, handle);
856
- }
857
- #releaseHandleId(id) {
858
- this.#reservedHandleIds.delete(id);
859
- }
860
- #beginWriteHandleCall(handle) {
861
- if (!handle.open)
862
- throw new Error("Write handle is closed");
863
- if (handle.activeCalls !== 0) {
864
- throw new Error("Write handle already has a call in flight");
865
- }
866
- handle.activeCalls = 1;
867
- handle.activeCallDone = new Promise((resolve) => {
868
- handle.finishActiveCall = resolve;
869
- });
870
- if (handle.idleTimer !== undefined) {
871
- clearTimeout(handle.idleTimer);
872
- handle.idleTimer = undefined;
873
- }
874
- }
875
- #endWriteHandleCall(handleId, handle) {
876
- handle.activeCalls -= 1;
877
- if (handle.activeCalls < 0)
878
- throw new Error("Write handle activity count underflow");
879
- const finishActiveCall = handle.finishActiveCall;
880
- handle.activeCallDone = undefined;
881
- handle.finishActiveCall = undefined;
882
- finishActiveCall?.();
883
- if (handle.open && handle.activeCalls === 0)
884
- this.#armWriteHandleIdleTimer(handleId, handle);
885
- }
886
- #armWriteHandleIdleTimer(handleId, handle) {
887
- if (!handle.open || handle.activeCalls !== 0 || this.#handles.get(handleId) !== handle)
888
- return;
889
- if (handle.idleTimer !== undefined)
890
- clearTimeout(handle.idleTimer);
891
- handle.idleTimer = setTimeout(() => {
892
- handle.idleTimer = undefined;
893
- if (!handle.open || handle.activeCalls !== 0 || this.#handles.get(handleId) !== handle)
894
- return;
895
- // Delete first: a late commit or stage frame must fail rather than revive an expired scope
896
- // while its asynchronous durable rollback is still joining.
897
- void this.#settleWriteHandle(handleId, handle, false).catch(() => undefined);
898
- }, this.#writeHandleIdleTimeoutMs);
899
- unrefTimer(handle.idleTimer);
900
- }
901
- async #settleWriteHandle(handleId, handle, commit) {
902
- if (!handle.open)
903
- throw new Error("Write handle is closed");
904
- handle.open = false;
905
- if (handle.idleTimer !== undefined) {
906
- clearTimeout(handle.idleTimer);
907
- handle.idleTimer = undefined;
908
- }
909
- if (this.#handles.get(handleId) === handle)
910
- this.#handles.delete(handleId);
911
- handle.finish(commit);
912
- const settlement = handle.done.catch((error) => {
913
- if (!commit && error instanceof WriteScopeAbortedError)
914
- return undefined;
915
- throw error;
916
- });
917
- this.#settlingWritePromises.add(settlement);
918
- void settlement.then(() => this.#settlingWritePromises.delete(settlement), () => this.#settlingWritePromises.delete(settlement));
919
- return settlement;
920
- }
921
- #closeLiveSet(handleId, handle) {
922
- handle.set.close();
923
- for (const subscriptionId of handle.subscriptionIds)
924
- this.#handles.delete(subscriptionId);
925
- this.#handles.delete(handleId);
926
- }
927
- async #dispose() {
928
- this.#disposed = true;
929
- this.#reservedHandleIds.clear();
930
- const handles = [...this.#handles.entries()];
931
- this.#handles.clear();
932
- const handleCleanup = [];
933
- // Stop every abandoned-write deadline before waiting on active calls. The cleared handle map
934
- // also prevents their completions from rearming one while disposal is in progress.
935
- for (const [, handle] of handles) {
936
- if (handle.type === "write" && handle.idleTimer !== undefined) {
937
- clearTimeout(handle.idleTimer);
938
- handle.idleTimer = undefined;
939
- }
940
- }
941
- // Signal every handle before awaiting any cleanup. Imports and iterators can have an admitted
942
- // RPC blocked inside them; closing sequentially could wait on the first while never reaching
943
- // the signal that lets a later one finish.
944
- for (const [handleId, handle] of handles) {
945
- try {
946
- if (handle.type === "snapshot") {
947
- handle.release();
948
- handleCleanup.push(handle.done.catch(() => undefined));
949
- }
950
- else if (handle.type === "write") {
951
- // A session operation owns the transaction revision until it returns. Aborting sooner
952
- // can race its final journal update and leave an active owner until TTL. Once it exits,
953
- // abort only if commit/abort/idle expiry has not already chosen a terminal outcome.
954
- handleCleanup.push((async () => {
955
- await handle.activeCallDone;
956
- if (handle.open) {
957
- await this.#settleWriteHandle(handleId, handle, false).catch(() => undefined);
958
- }
959
- })());
960
- }
961
- else if (handle.type === "writer")
962
- handleCleanup.push(handle.writer.close());
963
- else if (handle.type === "query-cursor") {
964
- handleCleanup.push(Promise.resolve(handle.iterator.return?.()));
965
- }
966
- else if (handle.type === "snapshot-export") {
967
- handleCleanup.push(Promise.resolve(handle.iterator.return?.()));
968
- }
969
- else if (handle.type === "snapshot-import") {
970
- handle.abort.abort(new Error("Database connection was disposed"));
971
- handle.queue.fail(new Error("Database connection was disposed"));
972
- handleCleanup.push(handle.task.catch(() => undefined));
973
- }
974
- else if (handle.type === "live-set")
975
- this.#closeLiveSet(handleId, handle);
976
- }
977
- catch {
978
- // Dispose every handle even when one close fails; the client is already gone.
979
- }
980
- }
981
- await Promise.allSettled(handleCleanup);
982
- // Commit/abort/expiry removes its handle before durable settlement completes so late RPCs
983
- // fail closed. Join those detached operations before closing the database or injected store.
984
- while (this.#settlingWritePromises.size > 0) {
985
- await Promise.allSettled([...this.#settlingWritePromises]);
986
- }
987
- // A handle whose lease admission was already in flight when disposal began is not yet in the
988
- // handle map. Its opener observes the disposed publication, releases any admitted lease, and
989
- // settles only after that cleanup is complete.
990
- while (this.#openingHandlePromises.size > 0) {
991
- await Promise.allSettled([...this.#openingHandlePromises]);
992
- }
993
- await this.#waitForInFlightRpcs();
994
- await this.database.close();
995
- await this.options.onDispose?.();
996
- }
997
- #waitForInFlightRpcs() {
998
- if (this.#inFlightRpcCount === 0)
999
- return Promise.resolve();
1000
- this.#inFlightRpcDrain ??= new Promise((resolve) => {
1001
- this.#resolveInFlightRpcDrain = resolve;
1002
- });
1003
- return this.#inFlightRpcDrain;
1004
- }
1005
- }
1006
- /**
1007
- * Answers client RPC frames against a database you constructed yourself. Use this in a custom
1008
- * worker entry when you need non-cloneable construction options (a custom store, `now`,
1009
- * `createId`) — the client's init frame is acknowledged without reconstructing anything.
1010
- */
1011
- export function exposeDatabase(database, scope, options = {}) {
1012
- const server = new DatabaseRpcServer(database, scope, options);
1013
- scope.addEventListener("message", (event) => {
1014
- let request;
1015
- try {
1016
- request = parseRpcRequest(event.data);
1017
- }
1018
- catch (error) {
1019
- const requestId = requestIdOf(event.data);
1020
- if (requestId !== undefined)
1021
- scope.postMessage(rpcFailure(requestId, error));
1022
- return;
1023
- }
1024
- if (request !== null)
1025
- void server.handle(request);
1026
- });
1027
- }
1028
- /**
1029
- * Full worker-side wiring: waits for the client's init frame, builds the store it describes,
1030
- * constructs the database, and exposes it. The store is closed when the client disposes.
1031
- */
1032
- export function attachDatabaseWorker(scope, options = {}) {
1033
- let initialized;
1034
- let initFailure;
1035
- scope.addEventListener("message", (event) => {
1036
- let request;
1037
- try {
1038
- request = parseRpcRequest(event.data);
1039
- }
1040
- catch (error) {
1041
- const requestId = requestIdOf(event.data);
1042
- if (requestId !== undefined)
1043
- scope.postMessage(rpcFailure(requestId, error));
1044
- return;
1045
- }
1046
- if (request === null)
1047
- return;
1048
- if (request.kind === "rpc-init" && initialized === undefined) {
1049
- initFailure = undefined;
1050
- const attempt = createServer(scope, request.payload, options);
1051
- initialized = attempt;
1052
- attempt.catch((error) => {
1053
- // Leave the worker reusable: a failed store open (quota, blocked upgrade) may be
1054
- // retried. Remember why it failed — the client pipelines calls behind init, so the
1055
- // ones that land after this reset must carry the real reason, not a generic refusal.
1056
- if (initialized === attempt) {
1057
- initialized = undefined;
1058
- initFailure = error;
1059
- }
1060
- });
1061
- }
1062
- const pending = initialized;
1063
- if (pending === undefined) {
1064
- const failure = initFailure === undefined
1065
- ? new Error("Database is not initialized: send init first")
1066
- : new Error(`Database initialization failed: ${initFailure instanceof Error
1067
- ? `${initFailure.name}: ${initFailure.message}`
1068
- : typeof initFailure === "string"
1069
- ? initFailure
1070
- : "unknown reason"}`, { cause: initFailure });
1071
- scope.postMessage(rpcFailure(request.requestId, failure));
1072
- return;
1073
- }
1074
- void pending
1075
- .then((server) => server.handle(request))
1076
- .catch((error) => {
1077
- scope.postMessage(rpcFailure(request.requestId, error));
1078
- });
1079
- });
1080
- }
1081
- async function createServer(scope, payload, options) {
1082
- const store = await createStore(payload.store, options);
1083
- const database = new MinnowDatabase(store, payload.options ?? {});
1084
- return new DatabaseRpcServer(database, scope, {
1085
- ...(payload.options?.transactionIdleTimeoutMs === undefined
1086
- ? {}
1087
- : { writeHandleIdleTimeoutMs: payload.options.transactionIdleTimeoutMs }),
1088
- onDispose: () => store.close(),
1089
- onVisibility: (visible) => {
1090
- store.setForeground?.(visible);
1091
- },
1092
- });
1093
- }
1094
- /**
1095
- * The composition root: the one place adapters are named. Each loads only when its descriptor
1096
- * asks for it — a bundler splits the adapters into their own chunks, so an application's worker
1097
- * downloads the store it opens and none of the others.
1098
- */
1099
6
  async function createStore(descriptor, options) {
1100
- if (descriptor.kind === "memory") {
1101
- const { MemoryBlockStore } = await import("../storage/memory.js");
1102
- return new MemoryBlockStore();
1103
- }
1104
- if (descriptor.kind === "opfs") {
1105
- const { OpfsBlockStore } = await import("../storage/opfs/index.js");
1106
- return OpfsBlockStore.open({
1107
- name: descriptor.name,
1108
- ...(descriptor.durability === undefined ? {} : { durability: descriptor.durability }),
1109
- });
1110
- }
1111
- const { IndexedDbBlockStore } = await import("../storage/indexeddb.js");
1112
- return IndexedDbBlockStore.open({
1113
- name: descriptor.name,
1114
- ...(descriptor.durability === undefined ? {} : { durability: descriptor.durability }),
1115
- ...(descriptor.uniqueKeyCacheBytes === undefined
1116
- ? {}
1117
- : { uniqueKeyCacheBytes: descriptor.uniqueKeyCacheBytes }),
1118
- ...(options.indexedDB === undefined ? {} : { indexedDB: options.indexedDB }),
7
+ if (descriptor.kind === "memory") {
8
+ const { MemoryBlockStore } = await import("../storage/memory.js");
9
+ return new MemoryBlockStore();
10
+ }
11
+ if (descriptor.kind === "opfs") {
12
+ const { OpfsBlockStore } = await import("../storage/opfs/index.js");
13
+ return OpfsBlockStore.open({
14
+ name: descriptor.name,
15
+ ...descriptor.durability === void 0 ? {} : { durability: descriptor.durability }
1119
16
  });
1120
- }
1121
- function requestIdOf(value) {
1122
- if (typeof value === "object" && value !== null && "requestId" in value) {
1123
- const requestId = value.requestId;
1124
- if (typeof requestId === "string")
1125
- return requestId;
1126
- }
1127
- return undefined;
1128
- }
1129
- /** A worker cleanup deadline must not keep a Node process alive by itself. */
1130
- function unrefTimer(timer) {
1131
- const candidate = timer;
1132
- candidate.unref?.();
1133
- }
17
+ }
18
+ const { IndexedDbBlockStore } = await import("../storage/indexeddb.js");
19
+ return IndexedDbBlockStore.open({
20
+ name: descriptor.name,
21
+ ...descriptor.durability === void 0 ? {} : { durability: descriptor.durability },
22
+ ...descriptor.uniqueKeyCacheBytes === void 0 ? {} : { uniqueKeyCacheBytes: descriptor.uniqueKeyCacheBytes },
23
+ ...options.indexedDB === void 0 ? {} : { indexedDB: options.indexedDB }
24
+ });
25
+ }
26
+ export {
27
+ MAX_WORKER_HANDLES_PER_CONNECTION,
28
+ attachDatabaseWorker,
29
+ exposeDatabase,
30
+ singleStoreFactory
31
+ };