@moyodb/sdk 1.0.1 → 1.1.0

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 (69) hide show
  1. package/dist/assets/browser-worker-DP6Mjz7c.js +4 -0
  2. package/dist/assets/browser-worker-DP6Mjz7c.js.map +1 -0
  3. package/dist/assets/shared-worker-DKzhauVf.js +2 -0
  4. package/dist/assets/shared-worker-DKzhauVf.js.map +1 -0
  5. package/dist/browser-worker.d.ts +2 -0
  6. package/dist/browser-worker.d.ts.map +1 -0
  7. package/dist/change-events.d.ts +1 -1
  8. package/dist/chunks/indexing-BzmFhWXs.js +1576 -0
  9. package/dist/chunks/indexing-BzmFhWXs.js.map +1 -0
  10. package/dist/codec.d.ts +1 -1
  11. package/dist/compression.d.ts +1 -1
  12. package/dist/demo.d.ts +2 -1
  13. package/dist/demo.d.ts.map +1 -1
  14. package/dist/engine/README.md +62 -16
  15. package/dist/engine/moyodb_engine.js +1 -1
  16. package/dist/engine/moyodb_engine_bg.wasm +0 -0
  17. package/dist/engine/package.json +1 -1
  18. package/dist/errors.d.ts +3 -0
  19. package/dist/errors.d.ts.map +1 -1
  20. package/dist/index.d.ts +11 -5
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/indexing.d.ts +1 -1
  23. package/dist/moyodb-sdk.js +2026 -543
  24. package/dist/moyodb-sdk.js.map +1 -1
  25. package/dist/node-engine.js +2213 -0
  26. package/dist/node-engine.js.map +1 -0
  27. package/dist/node-storage.d.mts +12 -0
  28. package/dist/node-storage.mjs +634 -0
  29. package/dist/node-worker-client.d.ts +19 -0
  30. package/dist/node-worker-client.d.ts.map +1 -0
  31. package/dist/node-worker.mjs +118 -0
  32. package/dist/node.d.ts +19 -0
  33. package/dist/node.d.ts.map +1 -0
  34. package/dist/node.js +3194 -0
  35. package/dist/node.js.map +1 -0
  36. package/dist/records.d.ts +75 -0
  37. package/dist/records.d.ts.map +1 -0
  38. package/dist/registry.d.ts +12 -4
  39. package/dist/registry.d.ts.map +1 -1
  40. package/dist/shared-worker-client.d.ts +24 -0
  41. package/dist/shared-worker-client.d.ts.map +1 -0
  42. package/dist/shared-worker-protocol.d.ts +16 -0
  43. package/dist/shared-worker-protocol.d.ts.map +1 -0
  44. package/dist/shared-worker.d.ts +33 -0
  45. package/dist/shared-worker.d.ts.map +1 -0
  46. package/dist/sql-parser.d.ts +9 -0
  47. package/dist/sql-parser.d.ts.map +1 -0
  48. package/dist/sql-types.d.ts +82 -0
  49. package/dist/sql-types.d.ts.map +1 -0
  50. package/dist/sql.d.ts +32 -0
  51. package/dist/sql.d.ts.map +1 -0
  52. package/dist/subscriptions.d.ts +2 -2
  53. package/dist/subscriptions.d.ts.map +1 -1
  54. package/dist/types.d.ts +3 -0
  55. package/dist/types.d.ts.map +1 -1
  56. package/dist/worker-api.d.ts +2 -2
  57. package/dist/worker-api.d.ts.map +1 -1
  58. package/dist/worker-client.d.ts +24 -5
  59. package/dist/worker-client.d.ts.map +1 -1
  60. package/dist/worker-protocol.d.ts +8 -3
  61. package/dist/worker-protocol.d.ts.map +1 -1
  62. package/dist/worker-server.d.ts +10 -2
  63. package/dist/worker-server.d.ts.map +1 -1
  64. package/dist/worker.d.ts +263 -1
  65. package/dist/worker.d.ts.map +1 -1
  66. package/package.json +13 -6
  67. package/README.md +0 -59
  68. package/dist/assets/worker-KDc8Khl_.js +0 -4
  69. package/dist/assets/worker-KDc8Khl_.js.map +0 -1
package/dist/node.js ADDED
@@ -0,0 +1,3194 @@
1
+ import { $ as StoreExistsError, A as prefixRange, B as DatabaseClosedError, C as compoundKey, Ct as isWorkerProtocolReadyEnvelope, D as indexKey, Dt as isWorkerProtocolResponseBatchMessage, E as encodeIndexScalar, F as utf8Encode, G as InvalidRangeError, H as InjectedFailureError, Ht as compareStringsByCodeUnit, I as ChangeFeedCompactedError, J as ReadonlyTransactionError, K as KeyTooLargeError, L as ConstraintError, M as splitCompoundKey, Mt as prepareWorkerCommandPayload, N as u64Key, O as jsonDecode, Ot as isWorkerProtocolResponseMessage, P as utf8Decode, Q as StorageError, R as CorruptionError, S as toPublicIndexDefinitions, T as encodeCompoundKeyParts, U as InternalError, Ut as isRecord$1, V as IndexNotFoundError, Vt as workerProtocolError, W as InvalidOpenOptionsError, X as ReservedStoreNameError, Y as RecoveryRequiredError, Z as SerializationError, _ as indexRangeToPhysicalRange, a as createIndexKeyExtractor, at as UnsupportedPlatformError, b as normalizeIndexDefinitions, c as decodeIndexedDocument, ct as WriteTransactionAlreadyOpenError, d as encodeIndexMetadataValue, et as StoreNameTooLongError, f as extractLogicalIndexKey, ft as WORKER_PROTOCOL_REQUEST, g as indexKeyExactRange, gt as captureWorkerPayload, h as indexDefinitionIdentity, i as compareNormalizedIndexDefinitions, it as UniqueIndexConstraintError, j as prefixSuccessor, k as jsonEncode, l as encodeIndexEntryKey, lt as normalizeError, m as findIndexDefinition, n as assertPublicStoreName, nt as TransactionClosedError, o as decodeIndexEntryKey, ot as ValueTooLargeError, p as extractLogicalIndexKeyFromDocument, pt as WORKER_PROTOCOL_REQUEST_BATCH, q as MoyoDbError, r as cloneNormalizedIndexDefinitions, rt as TransactionConflictError, s as decodeIndexMetadataValue, st as VersionError, t as INDEX_METADATA_STORE, tt as StoreNotFoundError, u as encodeIndexMetadataKey, v as indexesForStore, vt as decodeWorkerResponsePayload, w as compoundKeyRange, wt as isWorkerProtocolReadyMessage, x as serializeNormalizedIndexDefinitions, y as isInternalStoreName, yt as deserializeWorkerError, z as DatabaseBusyError } from "./chunks/indexing-BzmFhWXs.js";
2
+ import { createHash, randomUUID } from "node:crypto";
3
+ import { mkdir, realpath } from "node:fs/promises";
4
+ import { dirname, join, resolve } from "node:path";
5
+ import { Worker as Worker$1 } from "node:worker_threads";
6
+ import fs from "node:fs";
7
+ //#region src/worker-client.ts
8
+ function clearPendingTimeout(pending) {
9
+ if (pending.timeout) clearTimeout(pending.timeout);
10
+ }
11
+ var WorkerProtocolClient = class {
12
+ worker;
13
+ nextRequestId = 1;
14
+ pending = /* @__PURE__ */ new Map();
15
+ closed = false;
16
+ readyResolved = false;
17
+ readyResolve = null;
18
+ readyReject = null;
19
+ ready;
20
+ requestTimeoutMs;
21
+ fatalHandler = null;
22
+ closeReason = null;
23
+ batching = false;
24
+ queuedRequests = [];
25
+ queuedBytes = 0;
26
+ flushScheduled = false;
27
+ awaitingReady = 0;
28
+ readyTimeout = null;
29
+ constructor(worker, options = {}) {
30
+ this.worker = worker;
31
+ this.requestTimeoutMs = options.requestTimeoutMs ?? 0;
32
+ this.ready = new Promise((resolve, reject) => {
33
+ this.readyResolve = resolve;
34
+ this.readyReject = reject;
35
+ });
36
+ if ((options.readyTimeoutMs ?? 0) > 0) this.readyTimeout = setTimeout(() => {
37
+ this.fail(workerProtocolError("WorkerReadyTimeoutError", "worker did not become ready"));
38
+ }, options.readyTimeoutMs);
39
+ this.ready.catch(() => void 0);
40
+ this.worker.addEventListener("message", this.handleMessage);
41
+ this.worker.addEventListener("messageerror", this.handleMessageError);
42
+ this.worker.addEventListener("error", this.handleError);
43
+ }
44
+ setFatalHandler(handler) {
45
+ this.fatalHandler = handler;
46
+ }
47
+ whenReady() {
48
+ return this.ready;
49
+ }
50
+ dispose(reason = workerProtocolError("WorkerTerminatedError", "worker transport was closed")) {
51
+ this.disposeInternal(reason, false);
52
+ }
53
+ fail(reason) {
54
+ this.disposeInternal(reason, true);
55
+ }
56
+ open(request) {
57
+ return this.request("open", [request]);
58
+ }
59
+ close() {
60
+ return this.request("close", []);
61
+ }
62
+ destroy() {
63
+ return this.request("destroy", []);
64
+ }
65
+ deleteDB(dbName) {
66
+ return this.request("deleteDB", [dbName]);
67
+ }
68
+ begin(mode) {
69
+ return this.request("begin", [mode]);
70
+ }
71
+ commit(txId) {
72
+ return this.request("commit", [txId]);
73
+ }
74
+ rollback(txId) {
75
+ return this.request("rollback", [txId]);
76
+ }
77
+ autocommit(mode, command, args) {
78
+ return this.send("autocommit", [
79
+ mode,
80
+ command,
81
+ args
82
+ ], command);
83
+ }
84
+ createStore(txId, name, options) {
85
+ return this.request("createStore", [
86
+ txId,
87
+ name,
88
+ options
89
+ ]);
90
+ }
91
+ dropStore(txId, name) {
92
+ return this.request("dropStore", [txId, name]);
93
+ }
94
+ clearStore(txId, name) {
95
+ return this.request("clearStore", [txId, name]);
96
+ }
97
+ get(txId, store, key) {
98
+ return this.request("get", [
99
+ txId,
100
+ store,
101
+ key
102
+ ]);
103
+ }
104
+ getMany(txId, store, keys) {
105
+ return this.request("getMany", [
106
+ txId,
107
+ store,
108
+ keys
109
+ ]);
110
+ }
111
+ has(txId, store, key) {
112
+ return this.request("has", [
113
+ txId,
114
+ store,
115
+ key
116
+ ]);
117
+ }
118
+ put(txId, store, key, value, options) {
119
+ return this.request("put", [
120
+ txId,
121
+ store,
122
+ key,
123
+ value,
124
+ options
125
+ ]);
126
+ }
127
+ putMany(txId, store, entries, options) {
128
+ return this.request("putMany", [
129
+ txId,
130
+ store,
131
+ entries,
132
+ options
133
+ ]);
134
+ }
135
+ delete(txId, store, key) {
136
+ return this.request("delete", [
137
+ txId,
138
+ store,
139
+ key
140
+ ]);
141
+ }
142
+ deleteMany(txId, store, keys) {
143
+ return this.request("deleteMany", [
144
+ txId,
145
+ store,
146
+ keys
147
+ ]);
148
+ }
149
+ applyBatch(txId, store, ops) {
150
+ return this.request("applyBatch", [
151
+ txId,
152
+ store,
153
+ ops
154
+ ]);
155
+ }
156
+ scan(txId, store, range) {
157
+ return this.request("scan", [
158
+ txId,
159
+ store,
160
+ range
161
+ ]);
162
+ }
163
+ getByIndex(txId, store, indexName, key) {
164
+ return this.request("getByIndex", [
165
+ txId,
166
+ store,
167
+ indexName,
168
+ key
169
+ ]);
170
+ }
171
+ scanByIndex(txId, store, indexName, range) {
172
+ return this.request("scanByIndex", [
173
+ txId,
174
+ store,
175
+ indexName,
176
+ range
177
+ ]);
178
+ }
179
+ scanByIndexPage(txId, store, indexName, range, cursor, limit) {
180
+ return this.request("scanByIndexPage", [
181
+ txId,
182
+ store,
183
+ indexName,
184
+ range,
185
+ cursor,
186
+ limit
187
+ ]);
188
+ }
189
+ getIndexes(txId) {
190
+ return this.request("getIndexes", txId === void 0 ? [] : [txId]);
191
+ }
192
+ reconcileIndexes(txId, indexes) {
193
+ return this.request("reconcileIndexes", [txId, indexes]);
194
+ }
195
+ listStores() {
196
+ return this.request("listStores", []);
197
+ }
198
+ getVersion() {
199
+ return this.request("getVersion", []);
200
+ }
201
+ changesSince(txId, options) {
202
+ return this.request("changesSince", [txId, options]);
203
+ }
204
+ setSchemaVersion(txId, version) {
205
+ return this.request("setSchemaVersion", [txId, version]);
206
+ }
207
+ exportSnapshot(options) {
208
+ return this.request("exportSnapshot", [options]);
209
+ }
210
+ importSnapshot(data) {
211
+ return this.request("importSnapshot", [data]);
212
+ }
213
+ reset() {
214
+ return this.request("reset", []);
215
+ }
216
+ compact() {
217
+ return this.request("compact", []);
218
+ }
219
+ rebuild() {
220
+ return this.request("rebuild", []);
221
+ }
222
+ stats() {
223
+ return this.request("stats", []);
224
+ }
225
+ storageInfo() {
226
+ return this.request("storageInfo", []);
227
+ }
228
+ requestPersistence() {
229
+ return this.request("requestPersistence", []);
230
+ }
231
+ setFailpoint(failpoint) {
232
+ return this.request("setFailpoint", [failpoint]);
233
+ }
234
+ async request(command, args) {
235
+ return await this.send(command, args, command);
236
+ }
237
+ async send(command, args, responseCommand) {
238
+ this.ensureOpen();
239
+ this.awaitingReady += 1;
240
+ try {
241
+ await this.ready;
242
+ } finally {
243
+ this.awaitingReady -= 1;
244
+ }
245
+ this.ensureOpen();
246
+ const shouldQueue = this.batching && (this.awaitingReady > 0 || this.queuedRequests.length > 0);
247
+ const id = this.nextRequestId;
248
+ this.nextRequestId += 1;
249
+ const prepared = prepareWorkerCommandPayload(command, args);
250
+ const message = {
251
+ type: WORKER_PROTOCOL_REQUEST,
252
+ version: 1,
253
+ id,
254
+ command,
255
+ args: prepared.args
256
+ };
257
+ return await new Promise((resolve, reject) => {
258
+ const timeout = this.requestTimeoutMs > 0 ? setTimeout(() => {
259
+ this.pending.delete(id);
260
+ reject(workerProtocolError("WorkerRequestTimeoutError", `worker command ${command} timed out after ${this.requestTimeoutMs}ms`));
261
+ }, this.requestTimeoutMs) : null;
262
+ this.pending.set(id, {
263
+ command: responseCommand,
264
+ resolve: (value) => resolve(value),
265
+ reject,
266
+ timeout
267
+ });
268
+ try {
269
+ if (shouldQueue) this.queueRequest(message, prepared.transfer);
270
+ else if (prepared.transfer.length > 0) this.worker.postMessage(message, prepared.transfer);
271
+ else this.worker.postMessage(message);
272
+ } catch (error) {
273
+ this.rejectPending(id, error instanceof Error ? error : new Error(String(error)));
274
+ }
275
+ });
276
+ }
277
+ queueRequest(message, transfer) {
278
+ const captured = captureWorkerPayload(message, transfer);
279
+ if (!this.pending.has(message.id)) return;
280
+ if (this.queuedRequests.length > 0 && this.queuedBytes + captured.byteLength > 1048576) this.flushRequests();
281
+ this.queuedRequests.push({
282
+ message: captured.value,
283
+ transfer: captured.transfer,
284
+ byteLength: captured.byteLength
285
+ });
286
+ this.queuedBytes += captured.byteLength;
287
+ if (!this.flushScheduled) {
288
+ this.flushScheduled = true;
289
+ queueMicrotask(() => {
290
+ this.flushScheduled = false;
291
+ this.flushRequests();
292
+ });
293
+ }
294
+ if (this.queuedRequests.length >= 128 || this.queuedBytes >= 1048576) this.flushRequests();
295
+ }
296
+ flushRequests() {
297
+ const queued = this.queuedRequests;
298
+ this.queuedRequests = [];
299
+ this.queuedBytes = 0;
300
+ if (this.closed) return;
301
+ const active = queued.filter((entry) => this.pending.has(entry.message.id));
302
+ if (active.length === 0) return;
303
+ const message = active.length === 1 ? active[0].message : {
304
+ type: WORKER_PROTOCOL_REQUEST_BATCH,
305
+ version: 1,
306
+ requests: active.map((entry) => entry.message)
307
+ };
308
+ const transfer = [];
309
+ for (const entry of active) for (const buffer of entry.transfer) transfer.push(buffer);
310
+ try {
311
+ if (transfer.length > 0) this.worker.postMessage(message, transfer);
312
+ else this.worker.postMessage(message);
313
+ } catch (error) {
314
+ const reason = error instanceof Error ? error : new Error(String(error));
315
+ for (const entry of active) this.rejectPending(entry.message.id, reason);
316
+ }
317
+ }
318
+ ensureOpen() {
319
+ if (this.closed) throw this.closeReason ?? workerProtocolError("WorkerTerminatedError", "worker transport is closed");
320
+ }
321
+ handleMessage = (event) => {
322
+ const data = event.data;
323
+ if (isWorkerProtocolReadyMessage(data)) {
324
+ if (this.readyResolved) return;
325
+ this.batching = data.batching === 1;
326
+ this.readyResolved = true;
327
+ if (this.readyTimeout) clearTimeout(this.readyTimeout);
328
+ this.readyTimeout = null;
329
+ this.readyResolve?.();
330
+ this.readyResolve = null;
331
+ this.readyReject = null;
332
+ return;
333
+ }
334
+ if (isWorkerProtocolReadyEnvelope(data)) {
335
+ this.fail(workerProtocolError("WorkerProtocolError", "incompatible worker protocol ready message"));
336
+ return;
337
+ }
338
+ if (isWorkerProtocolResponseBatchMessage(data)) {
339
+ for (const response of data.responses) this.handleResponse(response);
340
+ return;
341
+ }
342
+ if (isRecord$1(data) && data.type === "moyodb:worker-protocol:response-batch" && data.version === 1) {
343
+ this.fail(workerProtocolError("WorkerProtocolError", "invalid worker response batch"));
344
+ return;
345
+ }
346
+ this.handleResponse(data);
347
+ };
348
+ handleResponse(data) {
349
+ if (!isWorkerProtocolResponseMessage(data)) {
350
+ if (isRecord$1(data) && data.type === "moyodb:worker-protocol:response" && data.version === 1 && typeof data.id === "number" && Number.isSafeInteger(data.id)) this.rejectPending(data.id, workerProtocolError("WorkerProtocolError", "invalid worker response"));
351
+ return;
352
+ }
353
+ const pending = this.pending.get(data.id);
354
+ if (!pending) return;
355
+ this.pending.delete(data.id);
356
+ clearPendingTimeout(pending);
357
+ try {
358
+ if (data.ok) pending.resolve(decodeWorkerResponsePayload(pending.command, data.result));
359
+ else pending.reject(deserializeWorkerError(data.error));
360
+ } catch (error) {
361
+ pending.reject(error instanceof Error && error.name === "WorkerProtocolError" ? error : workerProtocolError("WorkerProtocolError", "worker response could not be decoded"));
362
+ }
363
+ }
364
+ handleMessageError = () => {
365
+ this.fail(workerProtocolError("WorkerMessageError", "worker message could not be deserialized"));
366
+ };
367
+ handleError = (event) => {
368
+ const error = event.error instanceof Error ? event.error : workerProtocolError("WorkerError", event.message || "worker runtime failed");
369
+ this.fail(error);
370
+ };
371
+ rejectPending(id, error) {
372
+ const pending = this.pending.get(id);
373
+ if (!pending) return;
374
+ this.pending.delete(id);
375
+ clearPendingTimeout(pending);
376
+ pending.reject(error);
377
+ }
378
+ disposeInternal(reason, notifyFatal) {
379
+ if (this.closed) return;
380
+ this.closed = true;
381
+ this.closeReason = reason;
382
+ this.worker.removeEventListener("message", this.handleMessage);
383
+ this.worker.removeEventListener("messageerror", this.handleMessageError);
384
+ this.worker.removeEventListener("error", this.handleError);
385
+ if (this.readyTimeout) clearTimeout(this.readyTimeout);
386
+ this.readyTimeout = null;
387
+ if (!this.readyResolved) this.readyReject?.(reason);
388
+ this.readyResolve = null;
389
+ this.readyReject = null;
390
+ this.queuedRequests = [];
391
+ this.queuedBytes = 0;
392
+ for (const pending of this.pending.values()) {
393
+ clearPendingTimeout(pending);
394
+ pending.reject(reason);
395
+ }
396
+ this.pending.clear();
397
+ if (notifyFatal) this.fatalHandler?.(reason);
398
+ }
399
+ };
400
+ //#endregion
401
+ //#region src/registry.ts
402
+ var VALID_FAILPOINTS = /* @__PURE__ */ new Set([
403
+ "after_wal_flush",
404
+ "after_main_flush",
405
+ "before_superblock_flush"
406
+ ]);
407
+ var DEFAULT_CHANGE_FEED_RETAIN_TXIDS = 1e5;
408
+ var registry$1 = /* @__PURE__ */ new Map();
409
+ var lifecycleOperations$1 = /* @__PURE__ */ new Map();
410
+ function queueLifecycleOperation$1(dbName, operation) {
411
+ const result = (lifecycleOperations$1.get(dbName) ?? Promise.resolve()).then(operation);
412
+ const settled = result.then(() => void 0, () => void 0);
413
+ lifecycleOperations$1.set(dbName, settled);
414
+ settled.then(() => {
415
+ if (lifecycleOperations$1.get(dbName) === settled) lifecycleOperations$1.delete(dbName);
416
+ });
417
+ return result;
418
+ }
419
+ function requirePlainOptionsObject(options) {
420
+ if (options === null || typeof options !== "object" || Array.isArray(options)) throw new InvalidOpenOptionsError("open options must be an object");
421
+ }
422
+ function normalizeBoolean(value, field, defaultValue) {
423
+ if (value === void 0) return defaultValue;
424
+ if (typeof value !== "boolean") throw new InvalidOpenOptionsError(`${field} must be a boolean`);
425
+ return value;
426
+ }
427
+ function normalizeNonNegativeInteger(value, field, defaultValue) {
428
+ if (value === void 0) return defaultValue;
429
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) throw new InvalidOpenOptionsError(`${field} must be a non-negative safe integer`);
430
+ return value;
431
+ }
432
+ function normalizeCachePages(value) {
433
+ return Math.max(1, normalizeNonNegativeInteger(value, "cachePages", 256));
434
+ }
435
+ function normalizeFailpoint(value) {
436
+ if (value === void 0 || value === null) return null;
437
+ if (typeof value !== "string" || !VALID_FAILPOINTS.has(value)) throw new InvalidOpenOptionsError(`debugFailpoint must be one of: ${Array.from(VALID_FAILPOINTS).join(", ")}`);
438
+ return value;
439
+ }
440
+ function normalizeChangeFeed(value) {
441
+ if (value === void 0) return null;
442
+ if (value === null || typeof value !== "object" || Array.isArray(value)) throw new InvalidOpenOptionsError("changeFeed must be an object");
443
+ const settings = value;
444
+ const enabled = normalizeBoolean(settings.enabled, "changeFeed.enabled", true);
445
+ let retainTxids = DEFAULT_CHANGE_FEED_RETAIN_TXIDS;
446
+ if (settings.retainTxids === null) retainTxids = null;
447
+ else if (settings.retainTxids !== void 0) {
448
+ retainTxids = normalizeNonNegativeInteger(settings.retainTxids, "changeFeed.retainTxids", 0);
449
+ if (retainTxids === 0) throw new InvalidOpenOptionsError("changeFeed.retainTxids must be positive; disable the feed instead");
450
+ }
451
+ return {
452
+ enabled,
453
+ retainTxids
454
+ };
455
+ }
456
+ function normalizeWorkerMode(value) {
457
+ if (value === void 0) return "dedicated";
458
+ if (value !== "dedicated" && value !== "shared") throw new InvalidOpenOptionsError("workerMode must be dedicated or shared");
459
+ return value;
460
+ }
461
+ function normalizeOptions(options = {}) {
462
+ requirePlainOptionsObject(options);
463
+ return {
464
+ workerMode: normalizeWorkerMode(options.workerMode),
465
+ createIfMissing: normalizeBoolean(options.createIfMissing, "createIfMissing", true),
466
+ ownerWaitMs: normalizeNonNegativeInteger(options.ownerWaitMs, "ownerWaitMs", 0),
467
+ requestPersistence: normalizeBoolean(options.requestPersistence, "requestPersistence", true),
468
+ cachePages: normalizeCachePages(options.cachePages),
469
+ debugFailpoint: normalizeFailpoint(options.debugFailpoint),
470
+ changeFeed: normalizeChangeFeed(options.changeFeed)
471
+ };
472
+ }
473
+ function sameChangeFeed(left, right) {
474
+ return left?.enabled === right?.enabled && left?.retainTxids === right?.retainTxids;
475
+ }
476
+ function assertCompatibleOptions(dbName, current, next) {
477
+ if (current.workerMode !== next.workerMode) throw new InvalidOpenOptionsError(`database ${dbName} is already open with workerMode=${current.workerMode}`);
478
+ if (current.cachePages !== next.cachePages) throw new InvalidOpenOptionsError(`database ${dbName} is already open in this tab with cachePages=${current.cachePages}; requested cachePages=${next.cachePages}`);
479
+ if (next.changeFeed !== null && !sameChangeFeed(current.changeFeed, next.changeFeed)) throw new InvalidOpenOptionsError(`database ${dbName} is already open in this tab with a different changeFeed policy`);
480
+ }
481
+ function emitListeners(listeners) {
482
+ for (const listener of Array.from(listeners)) listener();
483
+ }
484
+ function subscribeTransactionsInvalidated(entry, listener) {
485
+ entry.txInvalidationListeners.add(listener);
486
+ return () => {
487
+ entry.txInvalidationListeners.delete(listener);
488
+ };
489
+ }
490
+ function subscribeHandleInvalidated(entry, listener) {
491
+ entry.handleInvalidationListeners.add(listener);
492
+ return () => {
493
+ entry.handleInvalidationListeners.delete(listener);
494
+ };
495
+ }
496
+ function invalidateTransactions(entry) {
497
+ emitListeners(entry.txInvalidationListeners);
498
+ }
499
+ function invalidateEntry$1(entry) {
500
+ if (entry.invalidated) return;
501
+ entry.invalidated = true;
502
+ invalidateTransactions(entry);
503
+ emitListeners(entry.handleInvalidationListeners);
504
+ }
505
+ async function releaseDbWorker(entry) {
506
+ if (entry.release) return entry.release();
507
+ return queueLifecycleOperation$1(entry.dbName, async () => {
508
+ if (entry.invalidated || registry$1.get(entry.dbName) !== entry) return;
509
+ entry.refs -= 1;
510
+ if (entry.refs > 0) return;
511
+ registry$1.delete(entry.dbName);
512
+ try {
513
+ await entry.proxy.close();
514
+ } finally {
515
+ entry.persistenceBridge.close();
516
+ entry.proxy.dispose(/* @__PURE__ */ new Error("worker was released"));
517
+ await entry.worker.terminate();
518
+ }
519
+ });
520
+ }
521
+ async function destroyDbWorker(entry) {
522
+ if (entry.destroy) return entry.destroy();
523
+ return queueLifecycleOperation$1(entry.dbName, () => destroyEntry$1(entry));
524
+ }
525
+ async function destroyEntry$1(entry) {
526
+ if (entry.invalidated || registry$1.get(entry.dbName) !== entry) throw new DatabaseClosedError();
527
+ if (entry.options.workerMode === "shared") {
528
+ try {
529
+ await entry.proxy.destroy();
530
+ } catch (error) {
531
+ throw normalizeError(error);
532
+ }
533
+ registry$1.delete(entry.dbName);
534
+ invalidateEntry$1(entry);
535
+ entry.refs = 0;
536
+ entry.persistenceBridge.close();
537
+ entry.proxy.dispose(/* @__PURE__ */ new Error("worker was released"));
538
+ await entry.worker.terminate();
539
+ return;
540
+ }
541
+ registry$1.delete(entry.dbName);
542
+ invalidateEntry$1(entry);
543
+ entry.refs = 0;
544
+ try {
545
+ await entry.proxy.destroy();
546
+ } catch (error) {
547
+ throw normalizeError(error);
548
+ } finally {
549
+ entry.persistenceBridge.close();
550
+ entry.proxy.dispose(/* @__PURE__ */ new Error("worker was released"));
551
+ await entry.worker.terminate();
552
+ }
553
+ }
554
+ //#endregion
555
+ //#region src/change-events.ts
556
+ function isDbChange(value) {
557
+ if (!isRecord$1(value) || !(value.key instanceof Uint8Array)) return false;
558
+ if (value.kind === "put" || value.kind === "delete") return true;
559
+ return (value.kind === "clear" || value.kind === "drop") && value.key.byteLength === 0;
560
+ }
561
+ function isStoreChangeSet(value) {
562
+ return isRecord$1(value) && typeof value.store === "string" && Array.isArray(value.changes) && value.changes.every(isDbChange);
563
+ }
564
+ function isCommitAppliedEvent(value) {
565
+ return isRecord$1(value) && value.type === "commit_applied" && typeof value.dbName === "string" && typeof value.txid === "number" && Array.isArray(value.stores) && value.stores.every(isStoreChangeSet);
566
+ }
567
+ //#endregion
568
+ //#region src/subscriptions.ts
569
+ function cloneChanges(changes) {
570
+ return changes.map((change) => ({
571
+ key: change.key.slice(),
572
+ kind: change.kind
573
+ }));
574
+ }
575
+ function hasKeyPrefix(key, prefix) {
576
+ if (prefix.byteLength > key.byteLength) return false;
577
+ for (let index = 0; index < prefix.byteLength; index += 1) if (key[index] !== prefix[index]) return false;
578
+ return true;
579
+ }
580
+ function normalizeSubscriptionSpec(arg1, arg2, arg3) {
581
+ if (typeof arg1 === "function") {
582
+ if (arg2 !== void 0 || arg3 !== void 0) throw new TypeError("subscribe(callback) accepts exactly one callback argument");
583
+ return {
584
+ store: null,
585
+ keyPrefix: null,
586
+ callback: arg1
587
+ };
588
+ }
589
+ if (typeof arg1 !== "string") throw new TypeError("subscribe() storeName must be a string");
590
+ assertPublicStoreName(arg1);
591
+ if (typeof arg2 === "function") {
592
+ if (arg3 !== void 0) throw new TypeError("subscribe(storeName, callback) accepts exactly two arguments");
593
+ return {
594
+ store: arg1,
595
+ keyPrefix: null,
596
+ callback: arg2
597
+ };
598
+ }
599
+ if (arg2 instanceof Uint8Array && typeof arg3 === "function") return {
600
+ store: arg1,
601
+ keyPrefix: arg2.slice(),
602
+ callback: arg3
603
+ };
604
+ throw new TypeError("subscribe() expects one of: (callback), (storeName, callback), (storeName, keyPrefix, callback)");
605
+ }
606
+ var SubscriptionHub = class {
607
+ #dbName;
608
+ #channelName;
609
+ #channel = null;
610
+ #entries = /* @__PURE__ */ new Set();
611
+ #closed = false;
612
+ constructor(dbName, channelName = dbName) {
613
+ this.#dbName = dbName;
614
+ this.#channelName = channelName;
615
+ }
616
+ subscribe(arg1, arg2, arg3) {
617
+ if (this.#closed) throw new DatabaseClosedError();
618
+ const spec = normalizeSubscriptionSpec(arg1, arg2, arg3);
619
+ const entry = {
620
+ active: true,
621
+ store: spec.store,
622
+ keyPrefix: spec.keyPrefix,
623
+ callback: spec.callback
624
+ };
625
+ this.#ensureChannel();
626
+ this.#entries.add(entry);
627
+ return () => {
628
+ if (!entry.active) return;
629
+ entry.active = false;
630
+ this.#entries.delete(entry);
631
+ if (this.#entries.size === 0) this.#disposeChannel();
632
+ };
633
+ }
634
+ close() {
635
+ if (this.#closed) return;
636
+ this.#closed = true;
637
+ for (const entry of this.#entries) entry.active = false;
638
+ this.#entries.clear();
639
+ this.#disposeChannel();
640
+ }
641
+ #ensureChannel() {
642
+ if (this.#channel !== null || this.#closed) return;
643
+ const channel = new BroadcastChannel(`db:${this.#channelName}:events`);
644
+ channel.onmessage = (event) => {
645
+ this.#handleMessage(event.data);
646
+ };
647
+ this.#channel = channel;
648
+ }
649
+ #disposeChannel() {
650
+ this.#channel?.close();
651
+ this.#channel = null;
652
+ }
653
+ #handleMessage(payload) {
654
+ if (!isCommitAppliedEvent(payload) || payload.dbName !== this.#dbName || this.#entries.size === 0) return;
655
+ const entries = Array.from(this.#entries);
656
+ for (const storeEvent of payload.stores) for (const entry of entries) {
657
+ if (!entry.active) continue;
658
+ if (entry.store !== null && entry.store !== storeEvent.store) continue;
659
+ let changes;
660
+ const keyPrefix = entry.keyPrefix;
661
+ if (keyPrefix === null) changes = cloneChanges(storeEvent.changes);
662
+ else {
663
+ changes = cloneChanges(storeEvent.changes.filter((change) => change.kind === "clear" || change.kind === "drop" || hasKeyPrefix(change.key, keyPrefix)));
664
+ if (changes.length === 0) continue;
665
+ }
666
+ try {
667
+ entry.callback(storeEvent.store, changes, payload.txid);
668
+ } catch (error) {
669
+ queueMicrotask(() => {
670
+ throw error;
671
+ });
672
+ }
673
+ }
674
+ }
675
+ };
676
+ //#endregion
677
+ //#region src/records.ts
678
+ var fatalDecoder = new TextDecoder("utf-8", { fatal: true });
679
+ var NUMBER_MASK = (1n << 64n) - 1n;
680
+ var NUMBER_SIGN = 1n << 63n;
681
+ function sameBytes(left, right) {
682
+ return left.length === right.length && left.every((byte, index) => byte === right[index]);
683
+ }
684
+ function decodeScalarKey(bytes) {
685
+ if (!(bytes instanceof Uint8Array) || bytes.length === 0) throw new TypeError("invalid scalar key encoding");
686
+ let value;
687
+ switch (bytes[0]) {
688
+ case 16:
689
+ value = null;
690
+ break;
691
+ case 32:
692
+ value = false;
693
+ break;
694
+ case 33:
695
+ value = true;
696
+ break;
697
+ case 48: {
698
+ if (bytes.length !== 9) throw new TypeError("invalid number key encoding");
699
+ const ordered = new DataView(bytes.buffer, bytes.byteOffset + 1, 8).getBigUint64(0, false);
700
+ const bits = (ordered & NUMBER_SIGN) !== 0n ? ordered ^ NUMBER_SIGN : ordered ^ NUMBER_MASK;
701
+ const decoded = /* @__PURE__ */ new DataView(/* @__PURE__ */ new ArrayBuffer(8));
702
+ decoded.setBigUint64(0, bits, false);
703
+ value = decoded.getFloat64(0, false);
704
+ break;
705
+ }
706
+ case 64:
707
+ value = fatalDecoder.decode(bytes.subarray(1));
708
+ break;
709
+ case 80:
710
+ value = bytes.slice(1);
711
+ break;
712
+ default: throw new TypeError("invalid scalar key type");
713
+ }
714
+ if (!sameBytes(encodeIndexScalar(value), bytes)) throw new TypeError("noncanonical scalar key encoding");
715
+ return value;
716
+ }
717
+ var scalarKeyCodec = Object.freeze({
718
+ encode: encodeIndexScalar,
719
+ decode: decodeScalarKey
720
+ });
721
+ function typedKeyCodec(type, accepts) {
722
+ function check(value) {
723
+ if (!accepts(value)) throw new TypeError(`key must be ${type}`);
724
+ return value;
725
+ }
726
+ return Object.freeze({
727
+ encode(value) {
728
+ return encodeIndexScalar(check(value));
729
+ },
730
+ decode(bytes) {
731
+ return check(decodeScalarKey(bytes));
732
+ }
733
+ });
734
+ }
735
+ var keyCodecs = Object.freeze({
736
+ string: typedKeyCodec("a string", (value) => typeof value === "string"),
737
+ number: typedKeyCodec("a finite number", (value) => typeof value === "number"),
738
+ boolean: typedKeyCodec("a boolean", (value) => typeof value === "boolean"),
739
+ null: typedKeyCodec("null", (value) => value === null),
740
+ bytes: typedKeyCodec("Uint8Array", (value) => value instanceof Uint8Array)
741
+ });
742
+ function compoundKeyCodec(...parts) {
743
+ return Object.freeze({
744
+ encode(value) {
745
+ if (!Array.isArray(value) || value.length !== parts.length) throw new TypeError(`compound key must contain ${parts.length} parts`);
746
+ return encodeCompoundKeyParts(parts.map((codec, index) => codec.encode(value[index])));
747
+ },
748
+ decode(bytes) {
749
+ const encoded = splitCompoundKey(bytes);
750
+ if (encoded.length !== parts.length) throw new TypeError(`compound key must contain ${parts.length} parts`);
751
+ return parts.map((codec, index) => codec.decode(encoded[index]));
752
+ }
753
+ });
754
+ }
755
+ function assertJsonValue(value) {
756
+ const pending = [{ value }];
757
+ const active = /* @__PURE__ */ new Set();
758
+ while (pending.length > 0) {
759
+ const entry = pending.pop();
760
+ const current = entry.value;
761
+ if (entry.leave) {
762
+ active.delete(current);
763
+ continue;
764
+ }
765
+ if (current === null || typeof current === "string" || typeof current === "boolean") continue;
766
+ if (typeof current === "number" && Number.isFinite(current) && !Object.is(current, -0)) continue;
767
+ if (typeof current !== "object" || active.has(current)) throw new TypeError("record must contain JSON values without cycles, nonfinite numbers or negative zero");
768
+ const array = Array.isArray(current);
769
+ if (Object.getPrototypeOf(current) !== (array ? Array.prototype : Object.prototype)) throw new TypeError("record objects must be plain objects or arrays");
770
+ active.add(current);
771
+ pending.push({
772
+ value: current,
773
+ leave: true
774
+ });
775
+ const keys = Reflect.ownKeys(current);
776
+ if (array && keys.length !== current.length + 1) throw new TypeError("record arrays must be dense and have no extra properties");
777
+ for (const key of keys) {
778
+ if (array && key === "length") continue;
779
+ if (typeof key !== "string") throw new TypeError("record properties must have string keys");
780
+ if (array && (!/^(?:0|[1-9]\d*)$/.test(key) || Number(key) >= current.length)) throw new TypeError("record arrays must have no extra properties");
781
+ const descriptor = Object.getOwnPropertyDescriptor(current, key);
782
+ if (!descriptor || !descriptor.enumerable || !Object.hasOwn(descriptor, "value")) throw new TypeError("record properties must be enumerable data properties");
783
+ pending.push({ value: descriptor.value });
784
+ }
785
+ }
786
+ }
787
+ /** A type guard checks the application schema. Without one, only the JSON domain is checked. */
788
+ function jsonRecordCodec(validate) {
789
+ function check(value) {
790
+ assertJsonValue(value);
791
+ if (validate) {
792
+ if (!validate(value)) throw new TypeError("record does not match its schema");
793
+ assertJsonValue(value);
794
+ }
795
+ return value;
796
+ }
797
+ return Object.freeze({
798
+ encode(value) {
799
+ return jsonEncode(check(value));
800
+ },
801
+ decode(bytes) {
802
+ return check(jsonDecode(bytes));
803
+ }
804
+ });
805
+ }
806
+ function encodeRange(codec, range) {
807
+ const encoded = {};
808
+ for (const bound of [
809
+ "gt",
810
+ "gte",
811
+ "lt",
812
+ "lte"
813
+ ]) if (Object.hasOwn(range, bound)) encoded[bound] = codec.encode(range[bound]);
814
+ if (range.reverse !== void 0) encoded.reverse = range.reverse;
815
+ if (range.limit !== void 0) encoded.limit = range.limit;
816
+ return encoded;
817
+ }
818
+ async function withRecordTransaction(db, mode, action) {
819
+ const transaction = await db.begin(mode);
820
+ let result;
821
+ try {
822
+ result = await action(transaction);
823
+ } catch (error) {
824
+ try {
825
+ await transaction.rollback();
826
+ } catch (rollbackError) {
827
+ if (!(rollbackError instanceof TransactionClosedError)) throw new AggregateError([error, rollbackError], "record transaction and rollback failed", { cause: rollbackError });
828
+ }
829
+ throw error;
830
+ }
831
+ if (mode === "readwrite") await transaction.commit();
832
+ else await transaction.rollback();
833
+ return result;
834
+ }
835
+ function useTransaction(source, mode, action) {
836
+ return "begin" in source ? withRecordTransaction(source, mode, action) : action(source);
837
+ }
838
+ var RecordIndex = class {
839
+ name;
840
+ #source;
841
+ #store;
842
+ #indexKey;
843
+ #codecs;
844
+ constructor(source, store, name, indexKey, codecs) {
845
+ this.#source = source;
846
+ this.#store = store;
847
+ this.name = name;
848
+ this.#indexKey = indexKey;
849
+ this.#codecs = codecs;
850
+ }
851
+ async get(key) {
852
+ const encoded = this.#indexKey.encode(key);
853
+ return useTransaction(this.#source, "readonly", async (transaction) => {
854
+ const value = await transaction.getByIndex(this.#store, this.name, encoded);
855
+ return value === null ? null : this.#codecs.value.decode(value);
856
+ });
857
+ }
858
+ async scan(range = {}) {
859
+ const encoded = encodeRange(this.#indexKey, range);
860
+ return useTransaction(this.#source, "readonly", async (transaction) => {
861
+ const rows = [];
862
+ for await (const [key, value] of transaction.scanByIndex(this.#store, this.name, encoded)) rows.push({
863
+ key: this.#codecs.key.decode(key),
864
+ value: this.#codecs.value.decode(value)
865
+ });
866
+ return rows;
867
+ });
868
+ }
869
+ };
870
+ var RecordStore = class RecordStore {
871
+ name;
872
+ #source;
873
+ #codecs;
874
+ constructor(source, name, codecs) {
875
+ this.#source = source;
876
+ this.name = name;
877
+ this.#codecs = codecs;
878
+ }
879
+ async get(key) {
880
+ const value = await this.#source.get(this.name, this.#codecs.key.encode(key));
881
+ return value === null ? null : this.#codecs.value.decode(value);
882
+ }
883
+ async getMany(keys) {
884
+ return (await this.#source.getMany(this.name, keys.map((key) => this.#codecs.key.encode(key)))).map((value) => value === null ? null : this.#codecs.value.decode(value));
885
+ }
886
+ async has(key) {
887
+ return this.#source.has(this.name, this.#codecs.key.encode(key));
888
+ }
889
+ async put(key, value, options) {
890
+ await this.#source.put(this.name, this.#codecs.key.encode(key), this.#codecs.value.encode(value), options);
891
+ }
892
+ async putMany(entries, options) {
893
+ const encoded = entries.map(([key, value]) => [this.#codecs.key.encode(key), this.#codecs.value.encode(value)]);
894
+ await useTransaction(this.#source, "readwrite", (transaction) => transaction.putMany(this.name, encoded, options));
895
+ }
896
+ async delete(key) {
897
+ return this.#source.delete(this.name, this.#codecs.key.encode(key));
898
+ }
899
+ async deleteMany(keys) {
900
+ const encoded = keys.map((key) => this.#codecs.key.encode(key));
901
+ await useTransaction(this.#source, "readwrite", (transaction) => transaction.deleteMany(this.name, encoded));
902
+ }
903
+ async applyBatch(operations) {
904
+ const encoded = operations.map((operation) => {
905
+ const kind = operation.kind;
906
+ if (kind !== "put" && kind !== "delete") throw new TypeError("record batch operation must be put or delete");
907
+ const key = this.#codecs.key.encode(operation.key);
908
+ if (operation.kind === "delete") return {
909
+ kind: "delete",
910
+ key
911
+ };
912
+ return {
913
+ kind: "put",
914
+ key,
915
+ value: this.#codecs.value.encode(operation.value)
916
+ };
917
+ });
918
+ await useTransaction(this.#source, "readwrite", (transaction) => transaction.applyBatch(this.name, encoded));
919
+ }
920
+ async scan(range = {}) {
921
+ return (await this.#source.scan(this.name, encodeRange(this.#codecs.key, range))).map(({ key, value }) => ({
922
+ key: this.#codecs.key.decode(key),
923
+ value: this.#codecs.value.decode(value)
924
+ }));
925
+ }
926
+ async clear() {
927
+ await this.#source.clearStore(this.name);
928
+ }
929
+ index(name, key) {
930
+ return new RecordIndex(this.#source, this.name, name, key, this.#codecs);
931
+ }
932
+ transaction(mode, action) {
933
+ if (!("begin" in this.#source)) return Promise.reject(/* @__PURE__ */ new TypeError("record store already belongs to a transaction"));
934
+ return withRecordTransaction(this.#source, mode, (transaction) => action(new RecordStore(transaction, this.name, this.#codecs), transaction));
935
+ }
936
+ };
937
+ /** Bind codecs to an existing store. The database still enforces store existence. */
938
+ function openRecordStore(source, name, codecs) {
939
+ return new RecordStore(source, name, codecs);
940
+ }
941
+ async function createRecordStore(source, name, codecs, options) {
942
+ await source.createStore(name, options);
943
+ return openRecordStore(source, name, codecs);
944
+ }
945
+ //#endregion
946
+ //#region src/sql-types.ts
947
+ var SQL_INTEGER_MIN = -(1n << 63n);
948
+ var SQL_INTEGER_MAX = (1n << 63n) - 1n;
949
+ //#endregion
950
+ //#region src/sql-parser.ts
951
+ var MAX_SQL_LENGTH = 1048576;
952
+ var MAX_TOKENS = 16384;
953
+ var MAX_NESTING = 64;
954
+ var MAX_PREDICATES = 128;
955
+ var MAX_IDENTIFIER_LENGTH = 128;
956
+ var MAX_NUMBER_LENGTH = 1024;
957
+ var FORBIDDEN_IDENTIFIERS = /* @__PURE__ */ new Set([
958
+ "__proto__",
959
+ "prototype",
960
+ "constructor"
961
+ ]);
962
+ var COLUMN_TYPES$1 = /* @__PURE__ */ new Set([
963
+ "INTEGER",
964
+ "REAL",
965
+ "TEXT",
966
+ "BOOLEAN",
967
+ "BLOB"
968
+ ]);
969
+ var KEYWORDS = /* @__PURE__ */ new Set([
970
+ "CREATE",
971
+ "TABLE",
972
+ "IF",
973
+ "NOT",
974
+ "EXISTS",
975
+ "DROP",
976
+ "INSERT",
977
+ "INTO",
978
+ "VALUES",
979
+ "SELECT",
980
+ "FROM",
981
+ "WHERE",
982
+ "ORDER",
983
+ "BY",
984
+ "ASC",
985
+ "DESC",
986
+ "LIMIT",
987
+ "OFFSET",
988
+ "UPDATE",
989
+ "SET",
990
+ "DELETE",
991
+ "PRIMARY",
992
+ "KEY",
993
+ "NULL",
994
+ "TRUE",
995
+ "FALSE",
996
+ "IS",
997
+ "AND",
998
+ "OR",
999
+ "JOIN",
1000
+ "LEFT",
1001
+ "RIGHT",
1002
+ "INNER",
1003
+ "OUTER",
1004
+ "CROSS",
1005
+ "ON",
1006
+ "AS",
1007
+ "DISTINCT",
1008
+ "GROUP",
1009
+ "HAVING",
1010
+ "UNION",
1011
+ "ALL",
1012
+ "IN",
1013
+ "LIKE",
1014
+ "BETWEEN",
1015
+ "DEFAULT",
1016
+ "UNIQUE",
1017
+ "CHECK",
1018
+ "REFERENCES",
1019
+ "CONSTRAINT",
1020
+ "FOREIGN",
1021
+ "AUTOINCREMENT",
1022
+ "RETURNING",
1023
+ "CONFLICT",
1024
+ ...COLUMN_TYPES$1
1025
+ ]);
1026
+ var SqlSyntaxError = class extends SyntaxError {
1027
+ position;
1028
+ constructor(message, position) {
1029
+ super(`${message} at position ${position}`);
1030
+ this.name = "SqlSyntaxError";
1031
+ this.position = position;
1032
+ }
1033
+ };
1034
+ function parseSql(sql) {
1035
+ if (typeof sql !== "string") throw new TypeError("SQL must be a string");
1036
+ if (sql.length > MAX_SQL_LENGTH) throw new SqlSyntaxError(`SQL exceeds ${MAX_SQL_LENGTH} characters`, MAX_SQL_LENGTH);
1037
+ return new Parser(tokenize(sql)).parse();
1038
+ }
1039
+ function bindSqlParameters(parsed, parameters = []) {
1040
+ if (!Array.isArray(parameters)) throw new TypeError("SQL parameters must be an array");
1041
+ if (parameters.length !== parsed.parameterCount) throw new RangeError(`SQL expects ${parsed.parameterCount} parameters; received ${parameters.length}`);
1042
+ return Array.from(parameters, (value, index) => {
1043
+ if (value === null || typeof value === "boolean") return value;
1044
+ if (typeof value === "number") {
1045
+ if (!Number.isFinite(value) || Number.isInteger(value) && !Number.isSafeInteger(value)) throw new RangeError(`SQL parameter ${index + 1} must be finite; use bigint for integers outside the safe range`);
1046
+ return value;
1047
+ }
1048
+ if (typeof value === "bigint") {
1049
+ if (value < SQL_INTEGER_MIN || value > SQL_INTEGER_MAX) throw new RangeError(`SQL parameter ${index + 1} exceeds the signed 64-bit integer range`);
1050
+ return value;
1051
+ }
1052
+ if (typeof value === "string") {
1053
+ if (!hasValidUnicode(value)) throw new TypeError(`SQL parameter ${index + 1} must contain valid Unicode scalar values`);
1054
+ return value;
1055
+ }
1056
+ if (value instanceof Uint8Array) return Uint8Array.from(value);
1057
+ throw new TypeError(`SQL parameter ${index + 1} must be null, boolean, number, bigint, string, or Uint8Array`);
1058
+ });
1059
+ }
1060
+ function resolveSqlValue(expression, parameters) {
1061
+ if (expression.kind === "literal") return expression.value;
1062
+ if (!Number.isSafeInteger(expression.index) || expression.index < 0 || expression.index >= parameters.length) throw new RangeError(`SQL parameter ${expression.index + 1} is missing`);
1063
+ const value = parameters.at(expression.index);
1064
+ if (value === void 0) throw new RangeError(`SQL parameter ${expression.index + 1} is missing`);
1065
+ return value;
1066
+ }
1067
+ var Parser = class {
1068
+ #tokens;
1069
+ #index = 0;
1070
+ #parameterCount = 0;
1071
+ #nesting = 0;
1072
+ #predicates = 0;
1073
+ constructor(tokens) {
1074
+ this.#tokens = tokens;
1075
+ }
1076
+ parse() {
1077
+ const statement = this.#parseStatement();
1078
+ this.#acceptSymbol(";");
1079
+ if (this.#current().kind !== "end") throw this.#error("Unexpected or unsupported SQL syntax");
1080
+ return {
1081
+ statement,
1082
+ parameterCount: this.#parameterCount
1083
+ };
1084
+ }
1085
+ #parseStatement() {
1086
+ if (this.#acceptKeyword("CREATE")) return this.#createTable();
1087
+ if (this.#acceptKeyword("DROP")) return this.#dropTable();
1088
+ if (this.#acceptKeyword("INSERT")) return this.#insert();
1089
+ if (this.#acceptKeyword("SELECT")) return this.#select();
1090
+ if (this.#acceptKeyword("UPDATE")) return this.#update();
1091
+ if (this.#acceptKeyword("DELETE")) return this.#delete();
1092
+ throw this.#error("Expected CREATE TABLE, DROP TABLE, INSERT, SELECT, UPDATE, or DELETE");
1093
+ }
1094
+ #createTable() {
1095
+ this.#expectKeyword("TABLE");
1096
+ const ifNotExists = this.#acceptKeyword("IF");
1097
+ if (ifNotExists) {
1098
+ this.#expectKeyword("NOT");
1099
+ this.#expectKeyword("EXISTS");
1100
+ }
1101
+ const table = this.#identifier();
1102
+ this.#expectSymbol("(");
1103
+ const columns = [];
1104
+ const names = /* @__PURE__ */ new Set();
1105
+ let primaryKeyCount = 0;
1106
+ do {
1107
+ const name = this.#identifier();
1108
+ this.#assertUnique(names, name, "column");
1109
+ const typeToken = this.#current();
1110
+ const type = typeToken.text.toUpperCase();
1111
+ if (typeToken.kind !== "word" || !COLUMN_TYPES$1.has(type)) throw this.#error("Expected INTEGER, REAL, TEXT, BOOLEAN, or BLOB column type");
1112
+ this.#index += 1;
1113
+ let primaryKey = false;
1114
+ let nullable = true;
1115
+ let nullabilitySpecified = false;
1116
+ for (;;) if (this.#acceptKeyword("PRIMARY")) {
1117
+ if (primaryKey) throw this.#error("Duplicate PRIMARY KEY constraint");
1118
+ this.#expectKeyword("KEY");
1119
+ primaryKey = true;
1120
+ primaryKeyCount += 1;
1121
+ if (primaryKeyCount > 1) throw this.#error("Only one inline PRIMARY KEY is supported");
1122
+ } else if (this.#acceptKeyword("NOT")) {
1123
+ this.#expectKeyword("NULL");
1124
+ if (nullabilitySpecified) throw this.#error("Duplicate nullability constraint");
1125
+ nullable = false;
1126
+ nullabilitySpecified = true;
1127
+ } else if (this.#acceptKeyword("NULL")) {
1128
+ if (nullabilitySpecified) throw this.#error("Duplicate nullability constraint");
1129
+ nullable = true;
1130
+ nullabilitySpecified = true;
1131
+ } else break;
1132
+ if (primaryKey && nullable && nullabilitySpecified) throw this.#error("PRIMARY KEY cannot be declared NULL");
1133
+ columns.push({
1134
+ name,
1135
+ type,
1136
+ primaryKey,
1137
+ nullable: primaryKey ? false : nullable
1138
+ });
1139
+ } while (this.#acceptSymbol(","));
1140
+ this.#expectSymbol(")");
1141
+ return {
1142
+ kind: "createTable",
1143
+ table,
1144
+ columns,
1145
+ ifNotExists
1146
+ };
1147
+ }
1148
+ #dropTable() {
1149
+ this.#expectKeyword("TABLE");
1150
+ const ifExists = this.#acceptKeyword("IF");
1151
+ if (ifExists) this.#expectKeyword("EXISTS");
1152
+ return {
1153
+ kind: "dropTable",
1154
+ table: this.#identifier(),
1155
+ ifExists
1156
+ };
1157
+ }
1158
+ #insert() {
1159
+ this.#expectKeyword("INTO");
1160
+ const table = this.#identifier();
1161
+ let columns = null;
1162
+ if (this.#acceptSymbol("(")) {
1163
+ columns = this.#identifierList();
1164
+ this.#expectSymbol(")");
1165
+ }
1166
+ this.#expectKeyword("VALUES");
1167
+ const rows = [];
1168
+ let width = columns?.length;
1169
+ do {
1170
+ this.#expectSymbol("(");
1171
+ const row = [this.#value()];
1172
+ while (this.#acceptSymbol(",")) row.push(this.#value());
1173
+ this.#expectSymbol(")");
1174
+ width ??= row.length;
1175
+ if (row.length !== width) throw this.#error("INSERT rows must match the column count");
1176
+ rows.push(row);
1177
+ } while (this.#acceptSymbol(","));
1178
+ return {
1179
+ kind: "insert",
1180
+ table,
1181
+ columns,
1182
+ rows
1183
+ };
1184
+ }
1185
+ #select() {
1186
+ const columns = this.#acceptSymbol("*") ? null : this.#identifierList();
1187
+ this.#expectKeyword("FROM");
1188
+ const table = this.#identifier();
1189
+ const where = this.#where();
1190
+ const orderBy = [];
1191
+ if (this.#acceptKeyword("ORDER")) {
1192
+ this.#expectKeyword("BY");
1193
+ const names = /* @__PURE__ */ new Set();
1194
+ do {
1195
+ const column = this.#identifier();
1196
+ this.#assertUnique(names, column, "ORDER BY column");
1197
+ const direction = this.#acceptKeyword("DESC") ? "desc" : "asc";
1198
+ if (direction === "asc") this.#acceptKeyword("ASC");
1199
+ orderBy.push({
1200
+ column,
1201
+ direction
1202
+ });
1203
+ } while (this.#acceptSymbol(","));
1204
+ }
1205
+ let limit = null;
1206
+ let offset = null;
1207
+ if (this.#acceptKeyword("LIMIT")) {
1208
+ limit = this.#rowCount("LIMIT");
1209
+ if (this.#acceptKeyword("OFFSET")) offset = this.#rowCount("OFFSET");
1210
+ }
1211
+ return {
1212
+ kind: "select",
1213
+ table,
1214
+ columns,
1215
+ where,
1216
+ orderBy,
1217
+ limit,
1218
+ offset
1219
+ };
1220
+ }
1221
+ #update() {
1222
+ const table = this.#identifier();
1223
+ this.#expectKeyword("SET");
1224
+ const assignments = [];
1225
+ const names = /* @__PURE__ */ new Set();
1226
+ do {
1227
+ const column = this.#identifier();
1228
+ this.#assertUnique(names, column, "assignment");
1229
+ this.#expectSymbol("=");
1230
+ assignments.push({
1231
+ column,
1232
+ value: this.#value()
1233
+ });
1234
+ } while (this.#acceptSymbol(","));
1235
+ return {
1236
+ kind: "update",
1237
+ table,
1238
+ assignments,
1239
+ where: this.#where()
1240
+ };
1241
+ }
1242
+ #delete() {
1243
+ this.#expectKeyword("FROM");
1244
+ return {
1245
+ kind: "delete",
1246
+ table: this.#identifier(),
1247
+ where: this.#where()
1248
+ };
1249
+ }
1250
+ #where() {
1251
+ return this.#acceptKeyword("WHERE") ? this.#or() : null;
1252
+ }
1253
+ #or() {
1254
+ let expression = this.#and();
1255
+ while (this.#acceptKeyword("OR")) expression = {
1256
+ kind: "or",
1257
+ left: expression,
1258
+ right: this.#and()
1259
+ };
1260
+ return expression;
1261
+ }
1262
+ #and() {
1263
+ let expression = this.#predicate();
1264
+ while (this.#acceptKeyword("AND")) expression = {
1265
+ kind: "and",
1266
+ left: expression,
1267
+ right: this.#predicate()
1268
+ };
1269
+ return expression;
1270
+ }
1271
+ #predicate() {
1272
+ if (this.#acceptSymbol("(")) {
1273
+ this.#nesting += 1;
1274
+ if (this.#nesting > MAX_NESTING) throw this.#error(`WHERE nesting exceeds ${MAX_NESTING}`);
1275
+ const expression = this.#or();
1276
+ this.#expectSymbol(")");
1277
+ this.#nesting -= 1;
1278
+ return expression;
1279
+ }
1280
+ this.#predicates += 1;
1281
+ if (this.#predicates > MAX_PREDICATES) throw this.#error(`WHERE predicates exceed ${MAX_PREDICATES}`);
1282
+ const column = this.#identifier();
1283
+ if (this.#acceptKeyword("IS")) {
1284
+ const negated = this.#acceptKeyword("NOT");
1285
+ this.#expectKeyword("NULL");
1286
+ return {
1287
+ kind: "isNull",
1288
+ column,
1289
+ negated
1290
+ };
1291
+ }
1292
+ const token = this.#current();
1293
+ if (token.kind !== "symbol" || ![
1294
+ "=",
1295
+ "!=",
1296
+ "<>",
1297
+ "<",
1298
+ "<=",
1299
+ ">",
1300
+ ">="
1301
+ ].includes(token.text)) throw this.#error("Expected a comparison operator or IS [NOT] NULL");
1302
+ this.#index += 1;
1303
+ return {
1304
+ kind: "comparison",
1305
+ column,
1306
+ operator: token.text === "<>" ? "!=" : token.text,
1307
+ value: this.#value()
1308
+ };
1309
+ }
1310
+ #rowCount(clause) {
1311
+ const value = this.#value();
1312
+ if (value.kind === "literal" && (typeof value.value !== "number" || !Number.isSafeInteger(value.value) || value.value < 0)) throw this.#error(`${clause} must be a non-negative safe integer or parameter`);
1313
+ return value;
1314
+ }
1315
+ #value() {
1316
+ const token = this.#current();
1317
+ if (token.kind === "parameter") {
1318
+ this.#index += 1;
1319
+ return {
1320
+ kind: "parameter",
1321
+ index: this.#parameterCount++
1322
+ };
1323
+ }
1324
+ if (token.kind === "string") {
1325
+ this.#index += 1;
1326
+ return {
1327
+ kind: "literal",
1328
+ value: token.text
1329
+ };
1330
+ }
1331
+ if (this.#acceptKeyword("NULL")) return {
1332
+ kind: "literal",
1333
+ value: null
1334
+ };
1335
+ if (this.#acceptKeyword("TRUE")) return {
1336
+ kind: "literal",
1337
+ value: true
1338
+ };
1339
+ if (this.#acceptKeyword("FALSE")) return {
1340
+ kind: "literal",
1341
+ value: false
1342
+ };
1343
+ const sign = this.#acceptSymbol("-") ? "-" : this.#acceptSymbol("+") ? "+" : "";
1344
+ const number = this.#current();
1345
+ if (number.kind === "number") {
1346
+ this.#index += 1;
1347
+ return {
1348
+ kind: "literal",
1349
+ value: parseNumber(sign + number.text, token.position)
1350
+ };
1351
+ }
1352
+ throw this.#error("Expected a scalar literal or positional ? parameter");
1353
+ }
1354
+ #identifierList() {
1355
+ const names = /* @__PURE__ */ new Set();
1356
+ const columns = [];
1357
+ do {
1358
+ const name = this.#identifier();
1359
+ this.#assertUnique(names, name, "column");
1360
+ columns.push(name);
1361
+ } while (this.#acceptSymbol(","));
1362
+ return columns;
1363
+ }
1364
+ #identifier() {
1365
+ const token = this.#current();
1366
+ if (token.kind !== "identifier" && (token.kind !== "word" || KEYWORDS.has(token.text.toUpperCase()))) throw this.#error("Expected an identifier; use double quotes for keywords");
1367
+ if (token.text.length === 0 || token.text.length > MAX_IDENTIFIER_LENGTH || hasControlCharacters(token.text) || FORBIDDEN_IDENTIFIERS.has(token.text.toLowerCase()) || !hasValidUnicode(token.text)) throw this.#error("Invalid or reserved identifier");
1368
+ this.#index += 1;
1369
+ return token.text;
1370
+ }
1371
+ #assertUnique(names, name, label) {
1372
+ if (names.has(name)) throw this.#error(`Duplicate ${label}: ${name}`);
1373
+ names.add(name);
1374
+ }
1375
+ #current() {
1376
+ return this.#tokens[this.#index];
1377
+ }
1378
+ #acceptKeyword(keyword) {
1379
+ const token = this.#current();
1380
+ if (token.kind !== "word" || token.text.toUpperCase() !== keyword) return false;
1381
+ this.#index += 1;
1382
+ return true;
1383
+ }
1384
+ #expectKeyword(keyword) {
1385
+ if (!this.#acceptKeyword(keyword)) throw this.#error(`Expected ${keyword}`);
1386
+ }
1387
+ #acceptSymbol(symbol) {
1388
+ const token = this.#current();
1389
+ if (token.kind !== "symbol" || token.text !== symbol) return false;
1390
+ this.#index += 1;
1391
+ return true;
1392
+ }
1393
+ #expectSymbol(symbol) {
1394
+ if (!this.#acceptSymbol(symbol)) throw this.#error(`Expected ${symbol}`);
1395
+ }
1396
+ #error(message) {
1397
+ return new SqlSyntaxError(message, this.#current().position);
1398
+ }
1399
+ };
1400
+ function tokenize(sql) {
1401
+ const tokens = [];
1402
+ let index = 0;
1403
+ while (index < sql.length) {
1404
+ const character = sql[index];
1405
+ if (/\s/u.test(character)) {
1406
+ index += 1;
1407
+ continue;
1408
+ }
1409
+ if (sql.startsWith("--", index)) {
1410
+ index += 2;
1411
+ while (index < sql.length && sql[index] !== "\n" && sql[index] !== "\r") index += 1;
1412
+ continue;
1413
+ }
1414
+ if (sql.startsWith("/*", index)) {
1415
+ const end = sql.indexOf("*/", index + 2);
1416
+ if (end < 0) throw new SqlSyntaxError("Unterminated block comment", index);
1417
+ index = end + 2;
1418
+ continue;
1419
+ }
1420
+ if (tokens.length >= MAX_TOKENS) throw new SqlSyntaxError(`SQL token count exceeds ${MAX_TOKENS}`, index);
1421
+ const position = index;
1422
+ if (character === "'" || character === "\"") {
1423
+ const quote = character;
1424
+ let text = "";
1425
+ let closed = false;
1426
+ index += 1;
1427
+ while (index < sql.length) if (sql[index] === quote) {
1428
+ if (sql[index + 1] === quote) {
1429
+ text += quote;
1430
+ index += 2;
1431
+ } else {
1432
+ index += 1;
1433
+ closed = true;
1434
+ break;
1435
+ }
1436
+ } else text += sql[index++];
1437
+ if (!closed) throw new SqlSyntaxError("Unterminated quoted token", position);
1438
+ if (!hasValidUnicode(text)) throw new SqlSyntaxError("Quoted token must contain valid Unicode scalar values", position);
1439
+ tokens.push({
1440
+ kind: quote === "'" ? "string" : "identifier",
1441
+ text,
1442
+ position
1443
+ });
1444
+ } else if (/[A-Za-z_]/u.test(character)) {
1445
+ index += 1;
1446
+ while (index < sql.length && /\w/u.test(sql[index])) index += 1;
1447
+ tokens.push({
1448
+ kind: "word",
1449
+ text: sql.slice(position, index),
1450
+ position
1451
+ });
1452
+ } else if (/\d/u.test(character) || character === "." && /\d/u.test(sql[index + 1] ?? "")) {
1453
+ index += 1;
1454
+ while (index < sql.length && /\d/u.test(sql[index])) index += 1;
1455
+ if (character !== "." && sql[index] === ".") {
1456
+ index += 1;
1457
+ while (index < sql.length && /\d/u.test(sql[index])) index += 1;
1458
+ }
1459
+ if (sql[index] === "e" || sql[index] === "E") {
1460
+ index += 1;
1461
+ if (sql[index] === "+" || sql[index] === "-") index += 1;
1462
+ const exponentStart = index;
1463
+ while (index < sql.length && /\d/u.test(sql[index])) index += 1;
1464
+ if (index === exponentStart) throw new SqlSyntaxError("Invalid numeric exponent", position);
1465
+ }
1466
+ const text = sql.slice(position, index);
1467
+ if (text.length > MAX_NUMBER_LENGTH) throw new SqlSyntaxError(`Numeric literal exceeds ${MAX_NUMBER_LENGTH} characters`, position);
1468
+ tokens.push({
1469
+ kind: "number",
1470
+ text,
1471
+ position
1472
+ });
1473
+ } else if (character === "?") {
1474
+ index += 1;
1475
+ tokens.push({
1476
+ kind: "parameter",
1477
+ text: character,
1478
+ position
1479
+ });
1480
+ } else {
1481
+ const pair = sql.slice(index, index + 2);
1482
+ if ([
1483
+ "<=",
1484
+ ">=",
1485
+ "<>",
1486
+ "!="
1487
+ ].includes(pair)) {
1488
+ index += 2;
1489
+ tokens.push({
1490
+ kind: "symbol",
1491
+ text: pair,
1492
+ position
1493
+ });
1494
+ } else if ("(),;*=<>+-".includes(character)) {
1495
+ index += 1;
1496
+ tokens.push({
1497
+ kind: "symbol",
1498
+ text: character,
1499
+ position
1500
+ });
1501
+ } else throw new SqlSyntaxError(`Unsupported SQL character: ${character}`, position);
1502
+ }
1503
+ }
1504
+ tokens.push({
1505
+ kind: "end",
1506
+ text: "",
1507
+ position: sql.length
1508
+ });
1509
+ return tokens;
1510
+ }
1511
+ function parseNumber(text, position) {
1512
+ if (/^[+-]?\d+$/u.test(text)) {
1513
+ const integer = BigInt(text);
1514
+ if (integer < SQL_INTEGER_MIN || integer > SQL_INTEGER_MAX) throw new SqlSyntaxError("Integer literal exceeds the signed 64-bit range", position);
1515
+ return integer >= BigInt(Number.MIN_SAFE_INTEGER) && integer <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(integer) : integer;
1516
+ }
1517
+ const value = Number(text);
1518
+ if (!Number.isFinite(value) || Number.isInteger(value) && !Number.isSafeInteger(value)) throw new SqlSyntaxError("REAL literal must be finite; use integer notation for large integers", position);
1519
+ const [mantissa, exponentText = "0"] = text.toLowerCase().split("e");
1520
+ const digits = mantissa.replace(/[+.-]/gu, "");
1521
+ const nonzero = /[1-9]/u.test(digits);
1522
+ if (value === 0 && nonzero) throw new SqlSyntaxError("REAL literal underflows to zero", position);
1523
+ if (Number.isInteger(value) && nonzero) {
1524
+ const fractionLength = mantissa.includes(".") ? mantissa.length - mantissa.indexOf(".") - 1 : 0;
1525
+ const trailingZeros = digits.length - digits.replace(/0+$/u, "").length;
1526
+ if (Number(exponentText) - fractionLength + trailingZeros < 0) throw new SqlSyntaxError("REAL literal loses its fractional part", position);
1527
+ }
1528
+ return value;
1529
+ }
1530
+ function hasValidUnicode(value) {
1531
+ for (const character of value) {
1532
+ const codePoint = character.codePointAt(0);
1533
+ if (codePoint >= 55296 && codePoint <= 57343) return false;
1534
+ }
1535
+ return true;
1536
+ }
1537
+ function hasControlCharacters(value) {
1538
+ for (const character of value) {
1539
+ const codePoint = character.codePointAt(0);
1540
+ if (codePoint < 32 || codePoint === 127) return true;
1541
+ }
1542
+ return false;
1543
+ }
1544
+ //#endregion
1545
+ //#region src/sql.ts
1546
+ var CATALOG = "_moyodb_sql_catalog";
1547
+ var OWNER_KEY = Uint8Array.of(0);
1548
+ var OWNER = {
1549
+ format: "moyodb.sql",
1550
+ version: 1
1551
+ };
1552
+ var TABLE_OWNER_KEY = Uint8Array.of(255);
1553
+ var TABLE_OWNER_FIELD = "\0moyodb.sql.table";
1554
+ var INTEGER_MIN = -(1n << 63n);
1555
+ var INTEGER_MAX = (1n << 63n) - 1n;
1556
+ var ROW_ID_MAX = (1n << 64n) - 1n;
1557
+ var COLUMN_TYPES = /* @__PURE__ */ new Set([
1558
+ "INTEGER",
1559
+ "REAL",
1560
+ "TEXT",
1561
+ "BOOLEAN",
1562
+ "BLOB"
1563
+ ]);
1564
+ var SqlSchemaError = class extends Error {
1565
+ constructor(message, options) {
1566
+ super(message, options);
1567
+ this.name = "SqlSchemaError";
1568
+ }
1569
+ };
1570
+ var SqlTypeError = class extends TypeError {
1571
+ constructor(message) {
1572
+ super(message);
1573
+ this.name = "SqlTypeError";
1574
+ }
1575
+ };
1576
+ function createSqlClient(db, options = {}) {
1577
+ const expectedIndexes = options.indexes === void 0 ? null : normalizeIndexDefinitions(options.indexes);
1578
+ const run = async (sql, parameters, explain) => {
1579
+ const parsed = parseSql(sql);
1580
+ const bound = bindSqlParameters(parsed, parameters);
1581
+ const statement = parsed.statement;
1582
+ if (explain && !isRowStatement(statement)) throw new SqlSchemaError("EXPLAIN requires SELECT, UPDATE, or DELETE");
1583
+ const tx = await db.begin(statement.kind === "select" || explain ? "readonly" : "readwrite");
1584
+ let finished = false;
1585
+ try {
1586
+ const indexes = normalizeIndexDefinitions(await tx.listIndexes?.() ?? []);
1587
+ if (expectedIndexes && serializeNormalizedIndexDefinitions(expectedIndexes) !== serializeNormalizedIndexDefinitions(indexes)) throw new SqlSchemaError("SQL index definitions do not match the transaction catalog");
1588
+ const result = await executeStatement(tx, statement, bound, indexes, explain);
1589
+ finished = true;
1590
+ if (tx.mode === "readwrite") await tx.commit();
1591
+ else await tx.rollback();
1592
+ return result;
1593
+ } catch (error) {
1594
+ if (!finished) try {
1595
+ await tx.rollback();
1596
+ } catch (rollbackError) {
1597
+ throw new AggregateError([error, rollbackError], "SQL statement and rollback failed", { cause: rollbackError });
1598
+ }
1599
+ throw error;
1600
+ }
1601
+ };
1602
+ return {
1603
+ execute: (sql, parameters = []) => run(sql, parameters, false),
1604
+ async query(sql, parameters = []) {
1605
+ if (parseSql(sql).statement.kind !== "select") throw new SqlSchemaError("query() requires SELECT");
1606
+ return (await run(sql, parameters, false)).rows;
1607
+ },
1608
+ async explain(sql, parameters = []) {
1609
+ const result = await run(sql, parameters, true);
1610
+ if (!result.plan) throw new SqlSchemaError("statement has no query plan");
1611
+ return result.plan;
1612
+ }
1613
+ };
1614
+ }
1615
+ async function executeStatement(tx, statement, parameters, indexes, explain) {
1616
+ assertTableName(statement.table);
1617
+ const catalogExists = await readCatalog(tx);
1618
+ const schema = catalogExists ? await readSchema(tx, statement.table) : null;
1619
+ if (schema) await assertTableOwnership(tx, schema);
1620
+ if (statement.kind === "createTable") {
1621
+ if (schema) {
1622
+ if (statement.ifNotExists) return {
1623
+ rows: [],
1624
+ rowsAffected: 0
1625
+ };
1626
+ throw new SqlSchemaError(`table already exists: ${statement.table}`);
1627
+ }
1628
+ const created = {
1629
+ version: 1,
1630
+ table: statement.table,
1631
+ columns: statement.columns,
1632
+ nextRowId: "1"
1633
+ };
1634
+ validateSchema(created, statement.table);
1635
+ if (!catalogExists) {
1636
+ await tx.createStore(CATALOG);
1637
+ await tx.put(CATALOG, OWNER_KEY, jsonEncode(OWNER));
1638
+ }
1639
+ await tx.createStore(statement.table);
1640
+ await tx.put(statement.table, TABLE_OWNER_KEY, tableOwnerValue(created));
1641
+ await tx.put(CATALOG, schemaKey(statement.table), jsonEncode(created));
1642
+ return {
1643
+ rows: [],
1644
+ rowsAffected: 0
1645
+ };
1646
+ }
1647
+ if (!schema) {
1648
+ if (statement.kind === "dropTable" && statement.ifExists) return {
1649
+ rows: [],
1650
+ rowsAffected: 0
1651
+ };
1652
+ throw new SqlSchemaError(`table does not exist: ${statement.table}`);
1653
+ }
1654
+ if (statement.kind === "dropTable") {
1655
+ await tx.dropStore(statement.table);
1656
+ await tx.delete(CATALOG, schemaKey(statement.table));
1657
+ return {
1658
+ rows: [],
1659
+ rowsAffected: 0
1660
+ };
1661
+ }
1662
+ if (statement.kind === "insert") return insertRows(tx, schema, statement, parameters);
1663
+ validateWhere(schema, statement.where, parameters);
1664
+ if (statement.kind === "select") {
1665
+ validateColumnList(schema, statement.columns);
1666
+ for (const order of statement.orderBy) getColumn(schema, order.column);
1667
+ }
1668
+ if (statement.kind === "update") {
1669
+ validateColumnList(schema, statement.assignments.map((assignment) => assignment.column));
1670
+ for (const assignment of statement.assignments) normalizeValue(getColumn(schema, assignment.column), resolveSqlValue(assignment.value, parameters));
1671
+ }
1672
+ const orderBy = statement.kind === "select" ? statement.orderBy : [];
1673
+ const plan = planAccess(schema, statement.where, orderBy, parameters, indexes);
1674
+ const limit = statement.kind === "select" ? resolveCount(statement.limit, parameters, "LIMIT") : null;
1675
+ const offset = statement.kind === "select" ? resolveCount(statement.offset, parameters, "OFFSET") ?? 0 : 0;
1676
+ if (explain) return {
1677
+ rows: [],
1678
+ rowsAffected: 0,
1679
+ plan: plan.public
1680
+ };
1681
+ const rows = (limit === 0 ? [] : await readRows(tx, schema, plan)).filter(({ row }) => !statement.where || evaluateWhere(row, statement.where, parameters) === true);
1682
+ if (statement.kind === "select") {
1683
+ if (orderBy.length) rows.sort((left, right) => compareRows(left, right, orderBy));
1684
+ return {
1685
+ rows: rows.slice(offset, limit === null ? void 0 : offset + limit).map(({ row }) => projectRow(row, statement.columns ?? schema.columns.map((column) => column.name))),
1686
+ rowsAffected: 0,
1687
+ plan: plan.public
1688
+ };
1689
+ }
1690
+ if (statement.kind === "delete") {
1691
+ await tx.deleteMany(schema.table, rows.map(({ key }) => key));
1692
+ return {
1693
+ rows: [],
1694
+ rowsAffected: rows.length,
1695
+ plan: plan.public
1696
+ };
1697
+ }
1698
+ const updates = rows.map(({ key, row }) => {
1699
+ const updated = { ...row };
1700
+ for (const assignment of statement.assignments) updated[assignment.column] = normalizeValue(getColumn(schema, assignment.column), resolveSqlValue(assignment.value, parameters));
1701
+ const primary = primaryColumn(schema);
1702
+ return {
1703
+ oldKey: key,
1704
+ key: primary ? primaryKey(primary, updated[primary.name]) : key,
1705
+ row: updated
1706
+ };
1707
+ });
1708
+ const destinations = /* @__PURE__ */ new Set();
1709
+ const oldKeys = new Set(rows.map(({ key }) => bytesIdentity(key)));
1710
+ for (const update of updates) {
1711
+ const identity = bytesIdentity(update.key);
1712
+ if (destinations.has(identity)) throw new ConstraintError(`duplicate primary key in ${schema.table}`);
1713
+ destinations.add(identity);
1714
+ if (!oldKeys.has(identity) && await tx.has(schema.table, update.key)) throw new ConstraintError(`duplicate primary key in ${schema.table}`);
1715
+ }
1716
+ await tx.deleteMany(schema.table, updates.map(({ oldKey }) => oldKey));
1717
+ for (const update of updates) await tx.put(schema.table, update.key, encodeRow(schema, update.row));
1718
+ return {
1719
+ rows: [],
1720
+ rowsAffected: updates.length,
1721
+ plan: plan.public
1722
+ };
1723
+ }
1724
+ async function readCatalog(tx) {
1725
+ let value;
1726
+ try {
1727
+ value = await tx.get(CATALOG, OWNER_KEY);
1728
+ } catch (error) {
1729
+ if (error instanceof StoreNotFoundError || error instanceof Error && error.name === "StoreNotFoundError") return false;
1730
+ throw error;
1731
+ }
1732
+ if (!value) throw new SqlSchemaError(`SQL catalog store is not owned by SQL: ${CATALOG}`);
1733
+ const owner = decodeJson(value);
1734
+ if (!isRecord(owner) || owner.format !== OWNER.format || owner.version !== OWNER.version) throw new SqlSchemaError("invalid SQL catalog metadata");
1735
+ return true;
1736
+ }
1737
+ async function readSchema(tx, table) {
1738
+ const value = await tx.get(CATALOG, schemaKey(table));
1739
+ if (!value) return null;
1740
+ const schema = decodeJson(value);
1741
+ validateSchema(schema, table);
1742
+ return schema;
1743
+ }
1744
+ async function assertTableOwnership(tx, schema) {
1745
+ let owner;
1746
+ try {
1747
+ owner = await tx.get(schema.table, TABLE_OWNER_KEY);
1748
+ } catch (error) {
1749
+ if (error instanceof StoreNotFoundError || error instanceof Error && error.name === "StoreNotFoundError") throw new SqlSchemaError(`SQL table store is missing: ${schema.table}`, { cause: error });
1750
+ throw error;
1751
+ }
1752
+ if (!owner || compareBytes(owner, tableOwnerValue(schema)) !== 0) throw new SqlSchemaError(`SQL table ownership does not match its catalog: ${schema.table}`);
1753
+ }
1754
+ function tableOwnerValue(schema) {
1755
+ return jsonEncode({ [TABLE_OWNER_FIELD]: JSON.stringify({
1756
+ format: "moyodb.sql.table",
1757
+ version: schema.version,
1758
+ table: schema.table,
1759
+ columns: schema.columns
1760
+ }) });
1761
+ }
1762
+ function validateSchema(value, table) {
1763
+ if (!isRecord(value) || value.version !== 1 || value.table !== table || !Array.isArray(value.columns) || !value.columns.length || typeof value.nextRowId !== "string" || value.nextRowId.length > 20 || !/^[1-9]\d*$/u.test(value.nextRowId) || BigInt(value.nextRowId) > ROW_ID_MAX + 1n) throw new SqlSchemaError(`invalid schema metadata for ${table}`);
1764
+ const names = /* @__PURE__ */ new Set();
1765
+ let primaries = 0;
1766
+ for (const candidate of value.columns) {
1767
+ if (!isRecord(candidate) || typeof candidate.name !== "string" || !candidate.name.length || typeof candidate.type !== "string" || !COLUMN_TYPES.has(candidate.type) || typeof candidate.primaryKey !== "boolean" || typeof candidate.nullable !== "boolean" || names.has(candidate.name) || candidate.primaryKey && candidate.nullable) throw new SqlSchemaError(`invalid column metadata for ${table}`);
1768
+ names.add(candidate.name);
1769
+ if (candidate.primaryKey) primaries += 1;
1770
+ }
1771
+ if (primaries > 1) throw new SqlSchemaError(`multiple primary keys in ${table}`);
1772
+ }
1773
+ async function insertRows(tx, schema, statement, parameters) {
1774
+ const columns = statement.columns ?? schema.columns.map((column) => column.name);
1775
+ validateColumnList(schema, columns);
1776
+ const primary = primaryColumn(schema);
1777
+ let nextRowId = BigInt(schema.nextRowId);
1778
+ const entries = [];
1779
+ const keys = /* @__PURE__ */ new Set();
1780
+ for (const values of statement.rows) {
1781
+ if (values.length !== columns.length) throw new SqlSchemaError("INSERT column count does not match VALUES");
1782
+ const row = Object.create(null);
1783
+ for (const column of schema.columns) row[column.name] = null;
1784
+ values.forEach((expression, index) => {
1785
+ row[columns[index]] = resolveSqlValue(expression, parameters);
1786
+ });
1787
+ for (const column of schema.columns) row[column.name] = normalizeValue(column, row[column.name]);
1788
+ if (!primary && nextRowId > ROW_ID_MAX) throw new ConstraintError(`row identifier space exhausted in ${schema.table}`);
1789
+ const key = primary ? primaryKey(primary, row[primary.name]) : u64Key(nextRowId++);
1790
+ const identity = bytesIdentity(key);
1791
+ if (keys.has(identity) || await tx.has(schema.table, key)) throw new ConstraintError(`duplicate primary key in ${schema.table}`);
1792
+ keys.add(identity);
1793
+ entries.push([key, encodeRow(schema, row)]);
1794
+ }
1795
+ await tx.putMany(schema.table, entries);
1796
+ if (!primary) {
1797
+ schema.nextRowId = nextRowId.toString();
1798
+ await tx.put(CATALOG, schemaKey(schema.table), jsonEncode(schema));
1799
+ }
1800
+ return {
1801
+ rows: [],
1802
+ rowsAffected: entries.length
1803
+ };
1804
+ }
1805
+ function normalizeValue(column, value) {
1806
+ if (value === null) {
1807
+ if (!column.nullable) throw new ConstraintError(`column ${column.name} cannot be NULL`);
1808
+ return null;
1809
+ }
1810
+ switch (column.type) {
1811
+ case "INTEGER": {
1812
+ if (typeof value !== "bigint" && (typeof value !== "number" || !Number.isSafeInteger(value))) break;
1813
+ const integer = typeof value === "bigint" ? value : BigInt(value);
1814
+ if (integer < INTEGER_MIN || integer > INTEGER_MAX) break;
1815
+ return integer >= BigInt(Number.MIN_SAFE_INTEGER) && integer <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(integer) : integer;
1816
+ }
1817
+ case "REAL":
1818
+ if (typeof value === "number" && Number.isFinite(value)) return Object.is(value, -0) ? 0 : value;
1819
+ if (typeof value === "bigint" && value >= BigInt(Number.MIN_SAFE_INTEGER) && value <= BigInt(Number.MAX_SAFE_INTEGER)) return Number(value);
1820
+ break;
1821
+ case "TEXT":
1822
+ if (typeof value === "string") {
1823
+ indexKey(value);
1824
+ return value;
1825
+ }
1826
+ break;
1827
+ case "BOOLEAN":
1828
+ if (typeof value === "boolean") return value;
1829
+ break;
1830
+ case "BLOB": if (value instanceof Uint8Array) return value.slice();
1831
+ }
1832
+ throw new SqlTypeError(`column ${column.name} requires ${column.type}`);
1833
+ }
1834
+ function primaryKey(column, value) {
1835
+ if (value === null) throw new ConstraintError(`primary key ${column.name} cannot be NULL`);
1836
+ if (column.type === "INTEGER") return u64Key(BigInt(value) - INTEGER_MIN);
1837
+ if (typeof value === "bigint") throw new SqlTypeError(`invalid primary key ${column.name}`);
1838
+ return indexKey(value);
1839
+ }
1840
+ function encodeRow(schema, row) {
1841
+ const stored = Object.create(null);
1842
+ for (const column of schema.columns) stored[column.name] = storedValue(row[column.name]);
1843
+ return jsonEncode(stored);
1844
+ }
1845
+ function storedValue(value) {
1846
+ if (typeof value === "bigint") return value.toString();
1847
+ if (value instanceof Uint8Array) return bytesIdentity(value);
1848
+ return value;
1849
+ }
1850
+ function decodeRow(schema, item) {
1851
+ const stored = decodeJson(item.value);
1852
+ if (!isRecord(stored) || Object.keys(stored).length !== schema.columns.length) throw new SerializationError(`invalid SQL row in ${schema.table}`);
1853
+ const row = Object.create(null);
1854
+ try {
1855
+ for (const column of schema.columns) {
1856
+ if (!Object.hasOwn(stored, column.name)) throw new Error("missing SQL column");
1857
+ let value = stored[column.name];
1858
+ if (column.type === "INTEGER" && typeof value === "string") {
1859
+ if (value.length > 20 || !/^-?(?:0|[1-9]\d*)$/u.test(value)) throw new Error("invalid stored integer");
1860
+ value = BigInt(value);
1861
+ } else if (column.type === "BLOB" && typeof value === "string") {
1862
+ if (value.length % 2 || !/^[0-9a-f]*$/u.test(value)) throw new Error("invalid stored blob");
1863
+ value = Uint8Array.from(value.match(/.{2}/gu) ?? [], (byte) => Number.parseInt(byte, 16));
1864
+ }
1865
+ if (value !== null && typeof value !== "boolean" && typeof value !== "number" && typeof value !== "string" && typeof value !== "bigint" && !(value instanceof Uint8Array)) throw new Error("invalid stored value");
1866
+ row[column.name] = normalizeValue(column, value);
1867
+ }
1868
+ const primary = primaryColumn(schema);
1869
+ if (primary && compareBytes(primaryKey(primary, row[primary.name]), item.key) !== 0) throw new Error("SQL row primary key differs from storage key");
1870
+ } catch (error) {
1871
+ throw new SerializationError(`invalid SQL row in ${schema.table}: ${error instanceof Error ? error.message : "invalid value"}`);
1872
+ }
1873
+ return {
1874
+ key: item.key,
1875
+ row
1876
+ };
1877
+ }
1878
+ function validateWhere(schema, expression, parameters) {
1879
+ if (!expression) return;
1880
+ if ("left" in expression) {
1881
+ validateWhere(schema, expression.left, parameters);
1882
+ validateWhere(schema, expression.right, parameters);
1883
+ return;
1884
+ }
1885
+ const column = getColumn(schema, expression.column);
1886
+ if (expression.kind === "comparison") {
1887
+ const value = resolveSqlValue(expression.value, parameters);
1888
+ if (value === null) return;
1889
+ if (column.type === "INTEGER" || column.type === "REAL") {
1890
+ if (typeof value === "bigint" || typeof value === "number" && Number.isFinite(value)) return;
1891
+ throw new SqlTypeError(`comparison for ${column.name} requires a numeric value`);
1892
+ }
1893
+ normalizeValue({
1894
+ ...column,
1895
+ nullable: true
1896
+ }, value);
1897
+ }
1898
+ }
1899
+ function evaluateWhere(row, expression, parameters) {
1900
+ if ("left" in expression) {
1901
+ const left = evaluateWhere(row, expression.left, parameters);
1902
+ const right = evaluateWhere(row, expression.right, parameters);
1903
+ if (expression.kind === "and") return left === false || right === false ? false : left === null || right === null ? null : true;
1904
+ return left === true || right === true ? true : left === null || right === null ? null : false;
1905
+ }
1906
+ const left = row[expression.column];
1907
+ if (expression.kind === "isNull") return expression.negated ? left !== null : left === null;
1908
+ const right = resolveSqlValue(expression.value, parameters);
1909
+ if (left === null || right === null) return null;
1910
+ const compared = compareValues(left, right);
1911
+ switch (expression.operator) {
1912
+ case "=": return compared === 0;
1913
+ case "!=": return compared !== 0;
1914
+ case "<": return compared < 0;
1915
+ case "<=": return compared <= 0;
1916
+ case ">": return compared > 0;
1917
+ case ">=": return compared >= 0;
1918
+ }
1919
+ }
1920
+ function planAccess(schema, where, orderBy, parameters, indexes) {
1921
+ const comparisons = conjunctiveComparisons(where);
1922
+ const primary = primaryColumn(schema);
1923
+ const candidates = [];
1924
+ if (primary) candidates.push({ column: primary });
1925
+ for (const index of indexes) if (index.store === schema.table && !index.compound && index.keyPath.length === 1 && !index.keyPath[0].includes(".")) {
1926
+ const column = schema.columns.find((entry) => entry.name === index.keyPath[0]);
1927
+ if (column) candidates.push({
1928
+ column,
1929
+ index
1930
+ });
1931
+ }
1932
+ for (const equalityOnly of [true, false]) for (const candidate of candidates) {
1933
+ const selected = comparisons.filter((entry) => entry.column === candidate.column.name && (equalityOnly ? entry.operator === "=" : [
1934
+ "<",
1935
+ "<=",
1936
+ ">",
1937
+ ">="
1938
+ ].includes(entry.operator)));
1939
+ let key;
1940
+ const range = {};
1941
+ for (const comparison of selected) {
1942
+ const value = resolveSqlValue(comparison.value, parameters);
1943
+ const encoded = planKey(candidate.column, value, Boolean(candidate.index), equalityOnly);
1944
+ if (!encoded) continue;
1945
+ if (equalityOnly) {
1946
+ key = encoded;
1947
+ break;
1948
+ }
1949
+ setRangeBound(range, comparison.operator, encoded);
1950
+ }
1951
+ if (!key && !Object.keys(range).length) continue;
1952
+ const access = candidate.index ? equalityOnly ? "index-lookup" : "index-range" : equalityOnly ? "primary-key-lookup" : "primary-key-range";
1953
+ return {
1954
+ public: {
1955
+ table: schema.table,
1956
+ access,
1957
+ column: candidate.column.name,
1958
+ ...candidate.index ? { index: candidate.index.name } : {},
1959
+ sort: orderBy.length ? "memory" : "none",
1960
+ reason: `use ${candidate.index ? `index ${candidate.index.name}` : "primary key"} for ${equalityOnly ? "equality" : "range"}; apply remaining predicates after lookup`
1961
+ },
1962
+ key,
1963
+ range,
1964
+ empty: !key && emptyRange(range)
1965
+ };
1966
+ }
1967
+ return { public: {
1968
+ table: schema.table,
1969
+ access: "table-scan",
1970
+ sort: orderBy.length ? "memory" : "none",
1971
+ reason: "no supported primary key or index bound; filter table rows"
1972
+ } };
1973
+ }
1974
+ function planKey(column, value, secondary, equality) {
1975
+ if (value === null) return null;
1976
+ if (column.type === "INTEGER") {
1977
+ if (typeof value !== "bigint" && (typeof value !== "number" || !Number.isSafeInteger(value))) return null;
1978
+ if (secondary && !equality) return null;
1979
+ const normalized = normalizeValue({
1980
+ ...column,
1981
+ nullable: true
1982
+ }, value);
1983
+ return secondary ? indexKey(storedValue(normalized)) : primaryKey(column, normalized);
1984
+ }
1985
+ if (column.type === "REAL" && typeof value === "bigint" && (value < BigInt(Number.MIN_SAFE_INTEGER) || value > BigInt(Number.MAX_SAFE_INTEGER))) return null;
1986
+ const normalized = normalizeValue({
1987
+ ...column,
1988
+ nullable: true
1989
+ }, value);
1990
+ return secondary ? indexKey(storedValue(normalized)) : primaryKey(column, normalized);
1991
+ }
1992
+ function conjunctiveComparisons(where) {
1993
+ if (!where) return [];
1994
+ if (where.kind === "comparison") return [where];
1995
+ if (where.kind === "and") return [...conjunctiveComparisons(where.left), ...conjunctiveComparisons(where.right)];
1996
+ return [];
1997
+ }
1998
+ function setRangeBound(range, operator, key) {
1999
+ if (operator === ">" || operator === ">=") {
2000
+ const current = range.gt ?? range.gte;
2001
+ const compared = current ? compareBytes(key, current) : 1;
2002
+ if (compared > 0 || compared === 0 && operator === ">") {
2003
+ delete range.gt;
2004
+ delete range.gte;
2005
+ if (operator === ">") range.gt = key;
2006
+ else range.gte = key;
2007
+ }
2008
+ } else if (operator === "<" || operator === "<=") {
2009
+ const current = range.lt ?? range.lte;
2010
+ const compared = current ? compareBytes(key, current) : -1;
2011
+ if (compared < 0 || compared === 0 && operator === "<") {
2012
+ delete range.lt;
2013
+ delete range.lte;
2014
+ if (operator === "<") range.lt = key;
2015
+ else range.lte = key;
2016
+ }
2017
+ }
2018
+ }
2019
+ function emptyRange(range) {
2020
+ const lower = range.gt ?? range.gte;
2021
+ const upper = range.lt ?? range.lte;
2022
+ if (!lower || !upper) return false;
2023
+ const compared = compareBytes(lower, upper);
2024
+ return compared > 0 || compared === 0 && (Boolean(range.gt) || Boolean(range.lt));
2025
+ }
2026
+ async function readRows(tx, schema, plan) {
2027
+ if (plan.empty) return [];
2028
+ let items;
2029
+ if (plan.public.index) {
2030
+ items = [];
2031
+ const range = plan.key ? {
2032
+ gte: plan.key,
2033
+ lte: plan.key
2034
+ } : plan.range;
2035
+ for await (const [key, value] of tx.scanByIndex(schema.table, plan.public.index, range)) items.push({
2036
+ key,
2037
+ value
2038
+ });
2039
+ } else if (plan.key) {
2040
+ const value = await tx.get(schema.table, plan.key);
2041
+ items = value ? [{
2042
+ key: plan.key,
2043
+ value
2044
+ }] : [];
2045
+ } else items = await tx.scan(schema.table, plan.range);
2046
+ return items.filter(({ key }) => key.length !== 1 || key[0] !== TABLE_OWNER_KEY[0]).map((item) => decodeRow(schema, item));
2047
+ }
2048
+ function compareRows(left, right, orders) {
2049
+ for (const order of orders) {
2050
+ const compared = compareValues(left.row[order.column], right.row[order.column]);
2051
+ if (compared) return order.direction === "desc" ? -compared : compared;
2052
+ }
2053
+ return compareBytes(left.key, right.key);
2054
+ }
2055
+ function compareValues(left, right) {
2056
+ if (left === null) return right === null ? 0 : -1;
2057
+ if (right === null) return 1;
2058
+ if ((typeof left === "number" || typeof left === "bigint") && (typeof right === "number" || typeof right === "bigint")) return left < right ? -1 : left > right ? 1 : 0;
2059
+ if (typeof left === "string" && typeof right === "string") return compareBytes(indexKey(left), indexKey(right));
2060
+ if (typeof left === "boolean" && typeof right === "boolean") return Number(left) - Number(right);
2061
+ if (left instanceof Uint8Array && right instanceof Uint8Array) return compareBytes(left, right);
2062
+ throw new SqlTypeError("comparison values have different SQL types");
2063
+ }
2064
+ function resolveCount(expression, parameters, clause) {
2065
+ if (!expression) return null;
2066
+ const value = resolveSqlValue(expression, parameters);
2067
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) throw new SqlTypeError(`${clause} requires a non-negative safe integer`);
2068
+ return value;
2069
+ }
2070
+ function projectRow(row, columns) {
2071
+ const result = {};
2072
+ for (const column of columns) Object.defineProperty(result, column, {
2073
+ value: row[column] instanceof Uint8Array ? row[column].slice() : row[column],
2074
+ enumerable: true,
2075
+ configurable: true,
2076
+ writable: true
2077
+ });
2078
+ return result;
2079
+ }
2080
+ function validateColumnList(schema, columns) {
2081
+ if (!columns) return;
2082
+ const seen = /* @__PURE__ */ new Set();
2083
+ for (const name of columns) {
2084
+ getColumn(schema, name);
2085
+ if (seen.has(name)) throw new SqlSchemaError(`duplicate column: ${name}`);
2086
+ seen.add(name);
2087
+ }
2088
+ }
2089
+ function getColumn(schema, name) {
2090
+ const column = schema.columns.find((entry) => entry.name === name);
2091
+ if (!column) throw new SqlSchemaError(`unknown column: ${name}`);
2092
+ return column;
2093
+ }
2094
+ function primaryColumn(schema) {
2095
+ return schema.columns.find((column) => column.primaryKey);
2096
+ }
2097
+ function schemaKey(table) {
2098
+ return utf8Encode(`table:${table}`);
2099
+ }
2100
+ function assertTableName(table) {
2101
+ if (table === CATALOG || table.startsWith("__browserdb:")) throw new SqlSchemaError(`reserved SQL table name: ${table}`);
2102
+ }
2103
+ function isRowStatement(statement) {
2104
+ return statement.kind === "select" || statement.kind === "update" || statement.kind === "delete";
2105
+ }
2106
+ function isRecord(value) {
2107
+ return value !== null && typeof value === "object" && !Array.isArray(value);
2108
+ }
2109
+ function decodeJson(value) {
2110
+ try {
2111
+ return jsonDecode(value);
2112
+ } catch {
2113
+ throw new SerializationError("invalid SQL JSON data");
2114
+ }
2115
+ }
2116
+ function bytesIdentity(bytes) {
2117
+ return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
2118
+ }
2119
+ function compareBytes(left, right) {
2120
+ for (let index = 0; index < Math.min(left.length, right.length); index += 1) if (left[index] !== right[index]) return left[index] - right[index];
2121
+ return left.length - right.length;
2122
+ }
2123
+ //#endregion
2124
+ //#region src/index.ts
2125
+ var INDEX_SCAN_PAGE_ROWS = 256;
2126
+ async function callProxy(fn) {
2127
+ try {
2128
+ return await fn();
2129
+ } catch (error) {
2130
+ throw normalizeError(error);
2131
+ }
2132
+ }
2133
+ var TransactionImpl = class {
2134
+ mode;
2135
+ #entry;
2136
+ #txId;
2137
+ #closed = false;
2138
+ #onClose;
2139
+ constructor(entry, txId, mode, onClose) {
2140
+ this.#entry = entry;
2141
+ this.#txId = txId;
2142
+ this.mode = mode;
2143
+ this.#onClose = onClose;
2144
+ }
2145
+ async listIndexes() {
2146
+ this.#ensureOpen();
2147
+ return callProxy(() => this.#entry.proxy.getIndexes(this.#txId));
2148
+ }
2149
+ async get(store, key) {
2150
+ this.#ensureOpen();
2151
+ assertPublicStoreName(store);
2152
+ return callProxy(() => this.#entry.proxy.get(this.#txId, store, key));
2153
+ }
2154
+ async getMany(store, keys) {
2155
+ this.#ensureOpen();
2156
+ assertPublicStoreName(store);
2157
+ return callProxy(() => this.#entry.proxy.getMany(this.#txId, store, keys));
2158
+ }
2159
+ async has(store, key) {
2160
+ this.#ensureOpen();
2161
+ assertPublicStoreName(store);
2162
+ return callProxy(() => this.#entry.proxy.has(this.#txId, store, key));
2163
+ }
2164
+ async put(store, key, value, options = {}) {
2165
+ this.#ensureOpen();
2166
+ assertPublicStoreName(store);
2167
+ return callProxy(() => this.#entry.proxy.put(this.#txId, store, key, value, normalizePutOptions(options)));
2168
+ }
2169
+ async putMany(store, entries, options = {}) {
2170
+ this.#ensureOpen();
2171
+ assertPublicStoreName(store);
2172
+ return callProxy(() => this.#entry.proxy.putMany(this.#txId, store, entries, normalizePutOptions(options)));
2173
+ }
2174
+ async delete(store, key) {
2175
+ this.#ensureOpen();
2176
+ assertPublicStoreName(store);
2177
+ return callProxy(() => this.#entry.proxy.delete(this.#txId, store, key));
2178
+ }
2179
+ async deleteMany(store, keys) {
2180
+ this.#ensureOpen();
2181
+ assertPublicStoreName(store);
2182
+ return callProxy(() => this.#entry.proxy.deleteMany(this.#txId, store, keys));
2183
+ }
2184
+ async applyBatch(store, ops) {
2185
+ this.#ensureOpen();
2186
+ assertPublicStoreName(store);
2187
+ return callProxy(() => this.#entry.proxy.applyBatch(this.#txId, store, ops));
2188
+ }
2189
+ async scan(store, range = {}) {
2190
+ this.#ensureOpen();
2191
+ assertPublicStoreName(store);
2192
+ return callProxy(() => this.#entry.proxy.scan(this.#txId, store, range));
2193
+ }
2194
+ async getByIndex(store, indexName, key) {
2195
+ this.#ensureOpen();
2196
+ assertPublicStoreName(store);
2197
+ return callProxy(() => this.#entry.proxy.getByIndex(this.#txId, store, indexName, key));
2198
+ }
2199
+ async *scanByIndex(store, indexName, range = {}) {
2200
+ this.#ensureOpen();
2201
+ assertPublicStoreName(store);
2202
+ let remaining = range.limit ?? Number.POSITIVE_INFINITY;
2203
+ let cursor = null;
2204
+ while (remaining > 0) {
2205
+ this.#ensureOpen();
2206
+ const pageLimit = Math.min(remaining, INDEX_SCAN_PAGE_ROWS);
2207
+ const resumeAfter = cursor;
2208
+ const page = await callProxy(() => this.#entry.proxy.scanByIndexPage(this.#txId, store, indexName, range, resumeAfter, pageLimit));
2209
+ for (const row of page.rows) yield [row.key, row.value];
2210
+ remaining -= page.rows.length;
2211
+ if (page.cursor === null) return;
2212
+ cursor = page.cursor;
2213
+ }
2214
+ }
2215
+ async createStore(name, options = {}) {
2216
+ this.#ensureOpen();
2217
+ assertPublicStoreName(name);
2218
+ return callProxy(() => this.#entry.proxy.createStore(this.#txId, name, normalizeCreateStoreOptions(options)));
2219
+ }
2220
+ async dropStore(name) {
2221
+ this.#ensureOpen();
2222
+ assertPublicStoreName(name);
2223
+ return callProxy(() => this.#entry.proxy.dropStore(this.#txId, name));
2224
+ }
2225
+ async clearStore(name) {
2226
+ this.#ensureOpen();
2227
+ assertPublicStoreName(name);
2228
+ return callProxy(() => this.#entry.proxy.clearStore(this.#txId, name));
2229
+ }
2230
+ async commit() {
2231
+ this.#ensureOpen();
2232
+ try {
2233
+ await callProxy(() => this.#entry.proxy.commit(this.#txId));
2234
+ } finally {
2235
+ this.#markClosed();
2236
+ }
2237
+ }
2238
+ async rollback() {
2239
+ this.#ensureOpen();
2240
+ try {
2241
+ await callProxy(() => this.#entry.proxy.rollback(this.#txId));
2242
+ } finally {
2243
+ this.#markClosed();
2244
+ }
2245
+ }
2246
+ async rollbackIfOpen() {
2247
+ if (this.#closed) return;
2248
+ if (this.#entry.invalidated) {
2249
+ this.#markClosed();
2250
+ return;
2251
+ }
2252
+ try {
2253
+ await this.#entry.proxy.rollback(this.#txId);
2254
+ } catch (error) {
2255
+ const normalized = normalizeError(error);
2256
+ if (!(normalized instanceof TransactionClosedError)) throw normalized;
2257
+ } finally {
2258
+ this.#markClosed();
2259
+ }
2260
+ }
2261
+ forceClose() {
2262
+ this.#markClosed();
2263
+ }
2264
+ internalId() {
2265
+ return this.#txId;
2266
+ }
2267
+ #ensureOpen() {
2268
+ if (this.#closed || this.#entry.invalidated) throw new TransactionClosedError();
2269
+ }
2270
+ #markClosed() {
2271
+ if (this.#closed) return;
2272
+ this.#closed = true;
2273
+ this.#onClose();
2274
+ }
2275
+ };
2276
+ var DBImpl = class {
2277
+ #entry;
2278
+ #closed = false;
2279
+ #closePromise = null;
2280
+ #transactions = /* @__PURE__ */ new Set();
2281
+ #pendingBegins = /* @__PURE__ */ new Set();
2282
+ #transactionGeneration = 0;
2283
+ #subscriptions;
2284
+ #unsubscribeTxInvalidated = null;
2285
+ #unsubscribeHandleInvalidated = null;
2286
+ constructor(entry) {
2287
+ this.#entry = entry;
2288
+ this.#subscriptions = new SubscriptionHub(entry.dbName, entry.channelName);
2289
+ this.#unsubscribeTxInvalidated = subscribeTransactionsInvalidated(entry, () => {
2290
+ this.#forceCloseTransactions();
2291
+ });
2292
+ this.#unsubscribeHandleInvalidated = subscribeHandleInvalidated(entry, () => {
2293
+ this.#markInvalidated();
2294
+ });
2295
+ }
2296
+ async begin(mode = "readonly") {
2297
+ return this.#beginImpl(mode);
2298
+ }
2299
+ async runSchemaMigration(oldVersion, newVersion, migrate, targetIndexes) {
2300
+ this.#ensureOpen();
2301
+ const tx = await this.#beginImpl("readwrite");
2302
+ const tracker = new MigrationCatalogTracker();
2303
+ const migrationTransaction = new MigrationTransactionImpl(tx, tracker);
2304
+ const migrationDb = new MigrationDbImpl(this, migrationTransaction, tracker);
2305
+ try {
2306
+ await this.#entry.proxy.reconcileIndexes(tx.internalId(), targetIndexes);
2307
+ await migrate({
2308
+ db: migrationDb,
2309
+ oldVersion,
2310
+ newVersion,
2311
+ transaction: migrationTransaction
2312
+ });
2313
+ await this.#entry.proxy.setSchemaVersion(tx.internalId(), newVersion);
2314
+ await tx.commit();
2315
+ } catch (error) {
2316
+ try {
2317
+ await tx.rollbackIfOpen();
2318
+ } catch {}
2319
+ throw error;
2320
+ }
2321
+ }
2322
+ async listStores() {
2323
+ this.#ensureOpen();
2324
+ return callProxy(() => this.#entry.proxy.listStores());
2325
+ }
2326
+ async listIndexes() {
2327
+ this.#ensureOpen();
2328
+ return callProxy(() => this.#entry.proxy.getIndexes());
2329
+ }
2330
+ async getVersion() {
2331
+ this.#ensureOpen();
2332
+ return callProxy(() => this.#entry.proxy.getVersion());
2333
+ }
2334
+ async changesSince(txid, options = {}) {
2335
+ this.#ensureOpen();
2336
+ return callProxy(() => this.#entry.proxy.changesSince(normalizeTxId(txid), normalizeChangeFeedOptions(options)));
2337
+ }
2338
+ async #beginImpl(mode) {
2339
+ this.#ensureOpen();
2340
+ const generation = this.#transactionGeneration;
2341
+ const pending = callProxy(async () => {
2342
+ const txId = await this.#entry.proxy.begin(mode);
2343
+ const tx = new TransactionImpl(this.#entry, txId, mode, () => {
2344
+ this.#transactions.delete(tx);
2345
+ });
2346
+ this.#transactions.add(tx);
2347
+ if (this.#closed || this.#entry.invalidated) {
2348
+ if (this.#entry.invalidated) tx.forceClose();
2349
+ throw new DatabaseClosedError();
2350
+ }
2351
+ if (generation !== this.#transactionGeneration) {
2352
+ await tx.rollbackIfOpen();
2353
+ throw new TransactionClosedError();
2354
+ }
2355
+ return tx;
2356
+ });
2357
+ this.#pendingBegins.add(pending);
2358
+ try {
2359
+ return await pending;
2360
+ } finally {
2361
+ this.#pendingBegins.delete(pending);
2362
+ }
2363
+ }
2364
+ async createStore(name, options = {}) {
2365
+ assertPublicStoreName(name);
2366
+ await this.#autocommit("readwrite", "createStore", [name, normalizeCreateStoreOptions(options)]);
2367
+ }
2368
+ async dropStore(name) {
2369
+ assertPublicStoreName(name);
2370
+ await this.#autocommit("readwrite", "dropStore", [name]);
2371
+ }
2372
+ async clearStore(name) {
2373
+ assertPublicStoreName(name);
2374
+ await this.#autocommit("readwrite", "clearStore", [name]);
2375
+ }
2376
+ async get(store, key) {
2377
+ assertPublicStoreName(store);
2378
+ return this.#autocommit("readonly", "get", [store, key]);
2379
+ }
2380
+ async getMany(store, keys) {
2381
+ assertPublicStoreName(store);
2382
+ return this.#autocommit("readonly", "getMany", [store, keys]);
2383
+ }
2384
+ async has(store, key) {
2385
+ assertPublicStoreName(store);
2386
+ return this.#autocommit("readonly", "has", [store, key]);
2387
+ }
2388
+ async put(store, key, value, options = {}) {
2389
+ assertPublicStoreName(store);
2390
+ await this.#autocommit("readwrite", "put", [
2391
+ store,
2392
+ key,
2393
+ value,
2394
+ normalizePutOptions(options)
2395
+ ]);
2396
+ }
2397
+ async delete(store, key) {
2398
+ assertPublicStoreName(store);
2399
+ return this.#autocommit("readwrite", "delete", [store, key]);
2400
+ }
2401
+ async scan(store, range = {}) {
2402
+ assertPublicStoreName(store);
2403
+ return this.#autocommit("readonly", "scan", [store, range]);
2404
+ }
2405
+ /** The transaction boundary stays in the worker, so one round trip is enough. */
2406
+ async #autocommit(mode, command, args) {
2407
+ this.#ensureOpen();
2408
+ return callProxy(() => this.#entry.proxy.autocommit(mode, command, args));
2409
+ }
2410
+ async exportSnapshot(options = {}) {
2411
+ this.#ensureOpen();
2412
+ return callProxy(async () => {
2413
+ const bytes = await this.#entry.proxy.exportSnapshot(normalizeExportSnapshotOptions(options));
2414
+ return bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
2415
+ });
2416
+ }
2417
+ async importSnapshot(data) {
2418
+ this.#ensureOpen();
2419
+ invalidateTransactions(this.#entry);
2420
+ return callProxy(() => {
2421
+ const bytes = data instanceof Uint8Array ? data : new Uint8Array(data);
2422
+ return this.#entry.proxy.importSnapshot(bytes);
2423
+ });
2424
+ }
2425
+ async reset() {
2426
+ this.#ensureOpen();
2427
+ invalidateTransactions(this.#entry);
2428
+ return callProxy(() => this.#entry.proxy.reset());
2429
+ }
2430
+ async compact() {
2431
+ this.#ensureOpen();
2432
+ invalidateTransactions(this.#entry);
2433
+ return callProxy(() => this.#entry.proxy.compact());
2434
+ }
2435
+ async rebuild() {
2436
+ this.#ensureOpen();
2437
+ invalidateTransactions(this.#entry);
2438
+ return callProxy(() => this.#entry.proxy.rebuild());
2439
+ }
2440
+ async stats() {
2441
+ this.#ensureOpen();
2442
+ return callProxy(() => this.#entry.proxy.stats());
2443
+ }
2444
+ async storageInfo() {
2445
+ this.#ensureOpen();
2446
+ return callProxy(() => this.#entry.proxy.storageInfo());
2447
+ }
2448
+ async requestPersistence() {
2449
+ this.#ensureOpen();
2450
+ return callProxy(() => this.#entry.proxy.requestPersistence());
2451
+ }
2452
+ async destroy() {
2453
+ this.#ensureOpen();
2454
+ return callProxy(() => this.#entry.destroy?.() ?? destroyDbWorker(this.#entry));
2455
+ }
2456
+ async setFailpoint(failpoint) {
2457
+ this.#ensureOpen();
2458
+ return callProxy(() => this.#entry.proxy.setFailpoint(failpoint));
2459
+ }
2460
+ subscribe(arg1, arg2, arg3) {
2461
+ this.#ensureOpen();
2462
+ if (typeof arg1 === "function") return this.#subscriptions.subscribe(arg1);
2463
+ if (typeof arg2 === "function") return this.#subscriptions.subscribe(arg1, arg2);
2464
+ if (arg2 instanceof Uint8Array && typeof arg3 === "function") return this.#subscriptions.subscribe(arg1, arg2, arg3);
2465
+ throw new TypeError("subscribe() expects one of: (callback), (storeName, callback), (storeName, keyPrefix, callback)");
2466
+ }
2467
+ watch(arg1, arg2, arg3) {
2468
+ if (typeof arg1 === "function") return this.subscribe(arg1);
2469
+ if (typeof arg2 === "function") return this.subscribe(arg1, arg2);
2470
+ if (arg2 instanceof Uint8Array && typeof arg3 === "function") return this.subscribe(arg1, arg2, arg3);
2471
+ throw new TypeError("watch() expects one of: (callback), (storeName, callback), (storeName, keyPrefix, callback)");
2472
+ }
2473
+ close() {
2474
+ if (this.#closePromise !== null) return this.#closePromise;
2475
+ if (this.#closed) return Promise.resolve();
2476
+ this.#closed = true;
2477
+ this.#disposeRegistrySubscriptions();
2478
+ this.#subscriptions.close();
2479
+ this.#closePromise = this.#finishClose();
2480
+ return this.#closePromise;
2481
+ }
2482
+ async #finishClose() {
2483
+ await Promise.allSettled(Array.from(this.#pendingBegins));
2484
+ let rollbackError = null;
2485
+ const txs = Array.from(this.#transactions);
2486
+ if (txs.length > 0) {
2487
+ const results = await Promise.allSettled(txs.map((tx) => tx.rollbackIfOpen()));
2488
+ for (const result of results) if (result.status === "rejected" && rollbackError === null) rollbackError = normalizeError(result.reason);
2489
+ }
2490
+ try {
2491
+ await (this.#entry.release?.() ?? releaseDbWorker(this.#entry));
2492
+ } catch (error) {
2493
+ throw normalizeError(error);
2494
+ }
2495
+ if (rollbackError) throw rollbackError;
2496
+ }
2497
+ #disposeRegistrySubscriptions() {
2498
+ this.#unsubscribeTxInvalidated?.();
2499
+ this.#unsubscribeTxInvalidated = null;
2500
+ this.#unsubscribeHandleInvalidated?.();
2501
+ this.#unsubscribeHandleInvalidated = null;
2502
+ }
2503
+ #forceCloseTransactions() {
2504
+ this.#transactionGeneration += 1;
2505
+ for (const tx of Array.from(this.#transactions)) tx.forceClose();
2506
+ }
2507
+ #markInvalidated() {
2508
+ if (this.#closed) return;
2509
+ this.#closed = true;
2510
+ this.#disposeRegistrySubscriptions();
2511
+ this.#forceCloseTransactions();
2512
+ this.#subscriptions.close();
2513
+ }
2514
+ #ensureOpen() {
2515
+ if (this.#closed || this.#entry.invalidated) throw new DatabaseClosedError();
2516
+ }
2517
+ };
2518
+ var MigrationCatalogTracker = class {
2519
+ #created = /* @__PURE__ */ new Set();
2520
+ #dropped = /* @__PURE__ */ new Set();
2521
+ recordCreate(name) {
2522
+ this.#dropped.delete(name);
2523
+ this.#created.add(name);
2524
+ }
2525
+ recordDrop(name) {
2526
+ if (this.#created.delete(name)) return;
2527
+ this.#dropped.add(name);
2528
+ }
2529
+ apply(stores) {
2530
+ const visible = new Set(stores);
2531
+ for (const name of this.#created) visible.add(name);
2532
+ for (const name of this.#dropped) visible.delete(name);
2533
+ return Array.from(visible).sort(compareStringsByCodeUnit);
2534
+ }
2535
+ };
2536
+ var MigrationTransactionImpl = class {
2537
+ #inner;
2538
+ #tracker;
2539
+ constructor(inner, tracker) {
2540
+ this.#inner = inner;
2541
+ this.#tracker = tracker;
2542
+ }
2543
+ get mode() {
2544
+ return this.#inner.mode;
2545
+ }
2546
+ async listIndexes() {
2547
+ return this.#inner.listIndexes();
2548
+ }
2549
+ async get(store, key) {
2550
+ return this.#inner.get(store, key);
2551
+ }
2552
+ async getMany(store, keys) {
2553
+ return this.#inner.getMany(store, keys);
2554
+ }
2555
+ async has(store, key) {
2556
+ return this.#inner.has(store, key);
2557
+ }
2558
+ async put(store, key, value, options = {}) {
2559
+ await this.#inner.put(store, key, value, options);
2560
+ }
2561
+ async putMany(store, entries, options = {}) {
2562
+ await this.#inner.putMany(store, entries, options);
2563
+ }
2564
+ async delete(store, key) {
2565
+ return this.#inner.delete(store, key);
2566
+ }
2567
+ async deleteMany(store, keys) {
2568
+ await this.#inner.deleteMany(store, keys);
2569
+ }
2570
+ async applyBatch(store, ops) {
2571
+ await this.#inner.applyBatch(store, ops);
2572
+ }
2573
+ async scan(store, range = {}) {
2574
+ return this.#inner.scan(store, range);
2575
+ }
2576
+ async getByIndex(store, indexName, key) {
2577
+ return this.#inner.getByIndex(store, indexName, key);
2578
+ }
2579
+ scanByIndex(store, indexName, range = {}) {
2580
+ return this.#inner.scanByIndex(store, indexName, range);
2581
+ }
2582
+ async createStore(name, options = {}) {
2583
+ await this.#inner.createStore(name, options);
2584
+ this.#tracker.recordCreate(name);
2585
+ }
2586
+ async dropStore(name) {
2587
+ await this.#inner.dropStore(name);
2588
+ this.#tracker.recordDrop(name);
2589
+ }
2590
+ async clearStore(name) {
2591
+ await this.#inner.clearStore(name);
2592
+ }
2593
+ commit() {
2594
+ return Promise.reject(migrationUnsupportedMethod("transaction.commit"));
2595
+ }
2596
+ rollback() {
2597
+ return Promise.reject(migrationUnsupportedMethod("transaction.rollback"));
2598
+ }
2599
+ };
2600
+ var MigrationDbImpl = class {
2601
+ #db;
2602
+ #transaction;
2603
+ #tracker;
2604
+ constructor(db, transaction, tracker) {
2605
+ this.#db = db;
2606
+ this.#transaction = transaction;
2607
+ this.#tracker = tracker;
2608
+ }
2609
+ begin(_mode = "readonly") {
2610
+ return Promise.reject(migrationUnsupportedMethod("db.begin"));
2611
+ }
2612
+ async createStore(name, options = {}) {
2613
+ await this.#transaction.createStore(name, options);
2614
+ }
2615
+ async dropStore(name) {
2616
+ await this.#transaction.dropStore(name);
2617
+ }
2618
+ async clearStore(name) {
2619
+ await this.#transaction.clearStore(name);
2620
+ }
2621
+ async listStores() {
2622
+ return this.#tracker.apply(await this.#db.listStores());
2623
+ }
2624
+ async listIndexes() {
2625
+ return this.#transaction.listIndexes();
2626
+ }
2627
+ async getVersion() {
2628
+ return this.#db.getVersion();
2629
+ }
2630
+ changesSince(_txid, _options = {}) {
2631
+ return Promise.reject(migrationUnsupportedMethod("db.changesSince"));
2632
+ }
2633
+ async get(store, key) {
2634
+ return this.#transaction.get(store, key);
2635
+ }
2636
+ async getMany(store, keys) {
2637
+ return this.#transaction.getMany(store, keys);
2638
+ }
2639
+ async has(store, key) {
2640
+ return this.#transaction.has(store, key);
2641
+ }
2642
+ async put(store, key, value, options = {}) {
2643
+ await this.#transaction.put(store, key, value, options);
2644
+ }
2645
+ async delete(store, key) {
2646
+ return this.#transaction.delete(store, key);
2647
+ }
2648
+ async scan(store, range = {}) {
2649
+ return this.#transaction.scan(store, range);
2650
+ }
2651
+ exportSnapshot(_options = {}) {
2652
+ return Promise.reject(migrationUnsupportedMethod("db.exportSnapshot"));
2653
+ }
2654
+ importSnapshot(_data) {
2655
+ return Promise.reject(migrationUnsupportedMethod("db.importSnapshot"));
2656
+ }
2657
+ reset() {
2658
+ return Promise.reject(migrationUnsupportedMethod("db.reset"));
2659
+ }
2660
+ compact() {
2661
+ return Promise.reject(migrationUnsupportedMethod("db.compact"));
2662
+ }
2663
+ rebuild() {
2664
+ return Promise.reject(migrationUnsupportedMethod("db.rebuild"));
2665
+ }
2666
+ async stats() {
2667
+ return this.#db.stats();
2668
+ }
2669
+ async storageInfo() {
2670
+ return this.#db.storageInfo();
2671
+ }
2672
+ async requestPersistence() {
2673
+ return this.#db.requestPersistence();
2674
+ }
2675
+ close() {
2676
+ return Promise.reject(migrationUnsupportedMethod("db.close"));
2677
+ }
2678
+ destroy() {
2679
+ return Promise.reject(migrationUnsupportedMethod("db.destroy"));
2680
+ }
2681
+ setFailpoint(_failpoint) {
2682
+ return Promise.reject(migrationUnsupportedMethod("db.setFailpoint"));
2683
+ }
2684
+ subscribe(_arg1, _arg2, _arg3) {
2685
+ throw migrationUnsupportedMethod("db.subscribe");
2686
+ }
2687
+ watch(_arg1, _arg2, _arg3) {
2688
+ throw migrationUnsupportedMethod("db.watch");
2689
+ }
2690
+ };
2691
+ function migrationUnsupportedMethod(method) {
2692
+ const error = /* @__PURE__ */ new Error(`${method}() is not available inside openDB() migrations; use the provided migration transaction and wait for openDB() to resolve`);
2693
+ error.name = "InvalidStateError";
2694
+ return error;
2695
+ }
2696
+ function normalizeDatabaseName(value, method) {
2697
+ if (typeof value !== "string" || value.length === 0) throw new TypeError(`${method}() database name must be a non-empty string`);
2698
+ return value;
2699
+ }
2700
+ var typeError = (message) => new TypeError(message);
2701
+ var invalidOpenOptionsError = (message) => new InvalidOpenOptionsError(message);
2702
+ function normalizeOptionsObject(options, message, createError = typeError) {
2703
+ if (options === void 0) return {};
2704
+ if (options === null || typeof options !== "object" || Array.isArray(options)) throw createError(message);
2705
+ return options;
2706
+ }
2707
+ function normalizeNonNegativeSafeInteger(value, message, createError = typeError) {
2708
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) throw createError(message);
2709
+ return value;
2710
+ }
2711
+ function normalizeOptionalNonNegativeSafeInteger(value, message, createError) {
2712
+ return value === void 0 ? void 0 : normalizeNonNegativeSafeInteger(value, message, createError);
2713
+ }
2714
+ function normalizeOpenOptionsInput(options) {
2715
+ return normalizeOptionsObject(options, "open options must be an object", invalidOpenOptionsError);
2716
+ }
2717
+ function normalizeSchemaVersion(value) {
2718
+ return normalizeOptionalNonNegativeSafeInteger(value, "version must be a non-negative safe integer", invalidOpenOptionsError);
2719
+ }
2720
+ function normalizePutOptions(options) {
2721
+ const { ttl } = normalizeOptionsObject(options, "put options must be an object");
2722
+ const normalizedTtl = normalizeOptionalNonNegativeSafeInteger(ttl, "ttl must be a non-negative safe integer");
2723
+ return normalizedTtl === void 0 ? {} : { ttl: normalizedTtl };
2724
+ }
2725
+ function normalizeCompressionOptions(options, message) {
2726
+ const { compression } = normalizeOptionsObject(options, message);
2727
+ if (compression === void 0) return {};
2728
+ if (compression !== false && compression !== "gzip" && compression !== "deflate") throw new TypeError("compression must be \"gzip\", \"deflate\", or false");
2729
+ return { compression };
2730
+ }
2731
+ function normalizeCreateStoreOptions(options) {
2732
+ return normalizeCompressionOptions(options, "createStore options must be an object");
2733
+ }
2734
+ function normalizeExportSnapshotOptions(options) {
2735
+ return normalizeCompressionOptions(options, "exportSnapshot options must be an object");
2736
+ }
2737
+ function normalizeTxId(value) {
2738
+ return normalizeNonNegativeSafeInteger(value, "txid must be a non-negative safe integer");
2739
+ }
2740
+ function normalizeChangeFeedOptions(options) {
2741
+ const { stores, limit } = normalizeOptionsObject(options, "change feed options must be an object");
2742
+ const normalized = {};
2743
+ if (stores !== void 0) {
2744
+ if (!Array.isArray(stores) || !stores.every((store) => typeof store === "string")) throw new TypeError("stores must be an array of strings");
2745
+ for (const store of stores) assertPublicStoreName(store);
2746
+ normalized.stores = [...stores];
2747
+ }
2748
+ const normalizedLimit = normalizeOptionalNonNegativeSafeInteger(limit, "limit must be a non-negative safe integer");
2749
+ if (normalizedLimit !== void 0) normalized.limit = normalizedLimit;
2750
+ return normalized;
2751
+ }
2752
+ function normalizeMigrateHook(value) {
2753
+ if (value === void 0) return;
2754
+ if (typeof value !== "function") throw new InvalidOpenOptionsError("migrate must be a function");
2755
+ return value;
2756
+ }
2757
+ function normalizeRequestedIndexes(value) {
2758
+ if (value === void 0) return;
2759
+ return normalizeIndexDefinitions(value);
2760
+ }
2761
+ function indexDefinitionsMatch(left, right) {
2762
+ return serializeNormalizedIndexDefinitions(left) === serializeNormalizedIndexDefinitions(right);
2763
+ }
2764
+ async function loadNormalizedIndexes(entry) {
2765
+ return normalizeIndexDefinitions(await entry.proxy.getIndexes());
2766
+ }
2767
+ async function openDBWithWorker(name, options, acquire) {
2768
+ const dbName = normalizeDatabaseName(name, "openDB");
2769
+ const normalizedOptions = normalizeOpenOptionsInput(options);
2770
+ const requestedVersion = normalizeSchemaVersion(normalizedOptions.version);
2771
+ const migrate = normalizeMigrateHook(normalizedOptions.migrate);
2772
+ const requestedIndexes = normalizeRequestedIndexes(normalizedOptions.indexes);
2773
+ if (requestedVersion === void 0 && migrate !== void 0) throw new InvalidOpenOptionsError("migrate requires version");
2774
+ if (requestedVersion === void 0 && requestedIndexes !== void 0) throw new InvalidOpenOptionsError("indexes requires version");
2775
+ const entry = await acquire(dbName, normalizedOptions);
2776
+ const db = new DBImpl(entry);
2777
+ try {
2778
+ await entry.proxy.acquireMigration?.();
2779
+ try {
2780
+ if (requestedVersion === void 0) return db;
2781
+ const [currentVersion, currentIndexes] = await Promise.all([db.getVersion(), loadNormalizedIndexes(entry)]);
2782
+ if (requestedVersion < currentVersion) throw new VersionError(`cannot open database ${dbName} at schema version ${requestedVersion}; current schema version is ${currentVersion}`);
2783
+ if (requestedVersion === currentVersion) {
2784
+ if (requestedIndexes !== void 0 && !indexDefinitionsMatch(requestedIndexes, currentIndexes)) throw new VersionError(`database ${dbName} is already at schema version ${currentVersion}, but the requested index catalog does not match the committed schema`);
2785
+ return db;
2786
+ }
2787
+ if (entry.refs > 1) throw new DatabaseBusyError(`database ${dbName} is already open in this tab; close existing handles before migrating from version ${currentVersion} to ${requestedVersion}`);
2788
+ if (!migrate) throw new VersionError(`database ${dbName} requires migration from version ${currentVersion} to ${requestedVersion}, but no migrate hook was provided`);
2789
+ const targetIndexes = requestedIndexes ?? currentIndexes;
2790
+ entry.schemaMigrationInProgress = true;
2791
+ try {
2792
+ await db.runSchemaMigration(currentVersion, requestedVersion, migrate, toPublicIndexDefinitions(targetIndexes));
2793
+ } finally {
2794
+ entry.schemaMigrationInProgress = false;
2795
+ }
2796
+ return db;
2797
+ } finally {
2798
+ await entry.proxy.releaseMigration?.();
2799
+ }
2800
+ } catch (error) {
2801
+ try {
2802
+ await db.close();
2803
+ } catch {}
2804
+ throw normalizeError(error);
2805
+ }
2806
+ }
2807
+ //#endregion
2808
+ //#region src/node-worker-client.ts
2809
+ var NodeWorkerTransport = class {
2810
+ proxy;
2811
+ #worker;
2812
+ #listeners = /* @__PURE__ */ new Map();
2813
+ #exited;
2814
+ #resolveExit;
2815
+ #rejectExit;
2816
+ #terminating = false;
2817
+ #failureEmitted = false;
2818
+ constructor(data, releaseLease) {
2819
+ this.#exited = new Promise((resolve, reject) => {
2820
+ this.#resolveExit = resolve;
2821
+ this.#rejectExit = reject;
2822
+ });
2823
+ this.#exited.catch(() => {});
2824
+ this.#worker = new Worker$1(new URL("./node-worker.mjs", import.meta.url), { workerData: data });
2825
+ this.#worker.on("message", (data) => {
2826
+ this.#emit("message", { data });
2827
+ });
2828
+ this.#worker.on("messageerror", () => {
2829
+ this.#emit("messageerror", {});
2830
+ });
2831
+ this.#worker.on("error", (error) => {
2832
+ this.#failureEmitted = true;
2833
+ this.#emit("error", {
2834
+ error,
2835
+ message: error.message
2836
+ });
2837
+ });
2838
+ this.#worker.once("exit", (code) => {
2839
+ if (!this.#terminating && !this.#failureEmitted) {
2840
+ const error = workerProtocolError("WorkerTerminatedError", `Node worker exited with code ${code}`);
2841
+ this.#emit("error", {
2842
+ error,
2843
+ message: error.message
2844
+ });
2845
+ }
2846
+ try {
2847
+ releaseLease();
2848
+ this.#resolveExit(code);
2849
+ } catch (error) {
2850
+ this.#rejectExit(error);
2851
+ }
2852
+ });
2853
+ this.proxy = new WorkerProtocolClient(this, { readyTimeoutMs: Math.min(2147483647, 3e4 + data.ownerWaitMs) });
2854
+ }
2855
+ postMessage(message, transfer = []) {
2856
+ this.#worker.postMessage(message, transfer);
2857
+ }
2858
+ addEventListener(type, listener) {
2859
+ let listeners = this.#listeners.get(type);
2860
+ if (!listeners) {
2861
+ listeners = /* @__PURE__ */ new Set();
2862
+ this.#listeners.set(type, listeners);
2863
+ }
2864
+ listeners.add(listener);
2865
+ }
2866
+ removeEventListener(type, listener) {
2867
+ this.#listeners.get(type)?.delete(listener);
2868
+ }
2869
+ terminate() {
2870
+ if (!this.#terminating) {
2871
+ this.#terminating = true;
2872
+ this.#worker.terminate().catch(this.#rejectExit);
2873
+ }
2874
+ return this.#exited;
2875
+ }
2876
+ #emit(type, event) {
2877
+ for (const listener of Array.from(this.#listeners.get(type) ?? [])) if (typeof listener === "function") listener(event);
2878
+ else listener.handleEvent(event);
2879
+ }
2880
+ };
2881
+ //#endregion
2882
+ //#region src/node-storage.mjs
2883
+ var LOCK_DIRECTORY = ".moyodb.lock";
2884
+ var PENDING_PREFIX = ".moyodb.pending-";
2885
+ var TOKEN_PATTERN = /^[a-f\d]{8}-[a-f\d]{4}-[a-f\d]{4}-[a-f\d]{4}-[a-f\d]{12}$/i;
2886
+ function namedError(name, message, cause) {
2887
+ const error = new Error(message, cause === void 0 ? void 0 : { cause });
2888
+ error.name = name;
2889
+ error.code = cause?.code ?? name;
2890
+ return error;
2891
+ }
2892
+ function storageError(error) {
2893
+ if ([
2894
+ "NotFoundError",
2895
+ "TypeMismatchError",
2896
+ "SecurityError",
2897
+ "DatabaseBusyError",
2898
+ "StorageError"
2899
+ ].includes(error?.name)) return error;
2900
+ if (error?.code === "ENOENT") return namedError("NotFoundError", error.message, error);
2901
+ return namedError("StorageError", error?.message ?? String(error), error);
2902
+ }
2903
+ function validateDirectory(directory) {
2904
+ if (typeof directory !== "string" || directory.length === 0 || directory.includes("\0")) throw new TypeError("directory must be a nonempty filesystem path");
2905
+ return resolve(directory);
2906
+ }
2907
+ function validateToken(token) {
2908
+ if (typeof token !== "string" || !TOKEN_PATTERN.test(token)) throw new TypeError("invalid storage lock token");
2909
+ return token;
2910
+ }
2911
+ function entryStat(path, kind) {
2912
+ const stat = fs.lstatSync(path);
2913
+ if (stat.isSymbolicLink() || stat.isFile() && stat.nlink !== 1) throw namedError("SecurityError", "storage entries must not be links");
2914
+ if (kind === "directory" && !stat.isDirectory() || kind === "file" && !stat.isFile()) throw namedError("TypeMismatchError", `storage entry is not a ${kind}`);
2915
+ if (!stat.isDirectory() && !stat.isFile()) throw namedError("SecurityError", "unsupported storage entry type");
2916
+ return stat;
2917
+ }
2918
+ function syncDirectory(path) {
2919
+ if (process.platform === "win32") return;
2920
+ const fd = fs.openSync(path, fs.constants.O_RDONLY | (fs.constants.O_DIRECTORY ?? 0));
2921
+ try {
2922
+ fs.fsyncSync(fd);
2923
+ } finally {
2924
+ fs.closeSync(fd);
2925
+ }
2926
+ }
2927
+ function readLease(directory, token) {
2928
+ const lockDirectory = join(directory, LOCK_DIRECTORY);
2929
+ entryStat(lockDirectory, "directory");
2930
+ const recordPath = join(lockDirectory, `${validateToken(token)}.json`);
2931
+ entryStat(recordPath, "file");
2932
+ const owner = JSON.parse(fs.readFileSync(recordPath, "utf8"));
2933
+ if (owner.token !== token || !Number.isSafeInteger(owner.pid) || owner.pid <= 0) throw namedError("DatabaseBusyError", "storage lock owner cannot be verified");
2934
+ return {
2935
+ lockDirectory,
2936
+ recordPath,
2937
+ owner
2938
+ };
2939
+ }
2940
+ function removeLease(lease) {
2941
+ fs.unlinkSync(lease.recordPath);
2942
+ try {
2943
+ fs.rmdirSync(lease.lockDirectory);
2944
+ } catch (error) {
2945
+ if (![
2946
+ "ENOENT",
2947
+ "ENOTEMPTY",
2948
+ "EEXIST"
2949
+ ].includes(error?.code)) throw error;
2950
+ }
2951
+ syncDirectory(dirname(lease.lockDirectory));
2952
+ }
2953
+ function removeVacantLease(directory) {
2954
+ const path = join(directory, LOCK_DIRECTORY);
2955
+ try {
2956
+ entryStat(path, "directory");
2957
+ if (fs.readdirSync(path).length !== 0) return false;
2958
+ fs.rmdirSync(path);
2959
+ syncDirectory(directory);
2960
+ return true;
2961
+ } catch (error) {
2962
+ if ([
2963
+ "ENOENT",
2964
+ "ENOTEMPTY",
2965
+ "EEXIST"
2966
+ ].includes(error?.code)) return false;
2967
+ throw error;
2968
+ }
2969
+ }
2970
+ function removePendingLease(directory, token) {
2971
+ const pending = join(directory, `${PENDING_PREFIX}${process.pid}-${token}`);
2972
+ try {
2973
+ entryStat(pending, "directory");
2974
+ } catch (error) {
2975
+ if (error?.code === "ENOENT") return false;
2976
+ throw error;
2977
+ }
2978
+ const entries = fs.readdirSync(pending);
2979
+ if (entries.length === 1 && entries[0] === `${token}.json`) {
2980
+ const recordPath = join(pending, entries[0]);
2981
+ entryStat(recordPath, "file");
2982
+ fs.unlinkSync(recordPath);
2983
+ } else if (entries.length !== 0) throw namedError("SecurityError", "pending storage lock contains unknown entries");
2984
+ fs.rmdirSync(pending);
2985
+ return true;
2986
+ }
2987
+ function releaseNodeStorageLease(directory, lockToken) {
2988
+ const path = validateDirectory(directory);
2989
+ validateToken(lockToken);
2990
+ let lease;
2991
+ let canonical;
2992
+ try {
2993
+ canonical = fs.realpathSync(path);
2994
+ lease = readLease(canonical, lockToken);
2995
+ } catch (error) {
2996
+ if (error?.code === "ENOTDIR") return false;
2997
+ if (error?.code === "ENOENT") {
2998
+ if (!canonical) return false;
2999
+ const removedPending = removePendingLease(canonical, lockToken);
3000
+ return removeVacantLease(canonical) || removedPending;
3001
+ }
3002
+ throw storageError(error);
3003
+ }
3004
+ if (lease.owner.pid !== process.pid) return false;
3005
+ try {
3006
+ removeLease(lease);
3007
+ removePendingLease(canonical, lockToken);
3008
+ return true;
3009
+ } catch (error) {
3010
+ if (error?.code === "ENOENT") return false;
3011
+ throw storageError(error);
3012
+ }
3013
+ }
3014
+ //#endregion
3015
+ //#region src/node.ts
3016
+ var registry = /* @__PURE__ */ new Map();
3017
+ var lifecycleOperations = /* @__PURE__ */ new Map();
3018
+ function queueLifecycleOperation(key, operation) {
3019
+ const result = (lifecycleOperations.get(key) ?? Promise.resolve()).then(operation);
3020
+ const settled = result.then(() => void 0, () => void 0);
3021
+ lifecycleOperations.set(key, settled);
3022
+ settled.then(() => {
3023
+ if (lifecycleOperations.get(key) === settled) lifecycleOperations.delete(key);
3024
+ });
3025
+ return result;
3026
+ }
3027
+ function validateName(name, method) {
3028
+ if (typeof name !== "string" || name.length === 0) throw new TypeError(`${method}() database name must be a non-empty string`);
3029
+ if (/[\uD800-\uDFFF]/u.test(name)) throw new TypeError(`${method}() database name must contain valid Unicode`);
3030
+ if (Buffer.byteLength(name, "utf8") > 127) throw new TypeError(`${method}() database name must contain at most 127 UTF-8 bytes`);
3031
+ }
3032
+ function validateDirectoryOptions(options) {
3033
+ if (options === null || typeof options !== "object" || Array.isArray(options)) throw new InvalidOpenOptionsError("Node options must contain directory");
3034
+ const directory = options.directory;
3035
+ if (typeof directory !== "string" || directory.length === 0 || directory.includes("\0")) throw new InvalidOpenOptionsError("directory must be a non-empty filesystem path");
3036
+ }
3037
+ async function databaseLocation(name, options) {
3038
+ await mkdir(options.directory, { recursive: true });
3039
+ const root = await realpath(options.directory);
3040
+ const directory = join(root, "stackdb", Buffer.from(name, "utf8").toString("hex"));
3041
+ return {
3042
+ directory,
3043
+ registryKey: process.platform === "win32" ? directory.toLowerCase() : directory
3044
+ };
3045
+ }
3046
+ function invalidateEntry(entry) {
3047
+ if (entry.invalidated) return;
3048
+ entry.invalidated = true;
3049
+ entry.refs = 0;
3050
+ invalidateTransactions(entry);
3051
+ for (const listener of Array.from(entry.handleInvalidationListeners)) listener();
3052
+ }
3053
+ function shutdownEntry(entry, reason) {
3054
+ if (entry.shutdown) return entry.shutdown;
3055
+ entry.proxy.dispose(reason);
3056
+ entry.shutdown = Promise.resolve(entry.worker.terminate()).then(() => void 0);
3057
+ return entry.shutdown;
3058
+ }
3059
+ function createEntry(dbName, directory, registryKey, options) {
3060
+ const normalized = normalizeOptions({
3061
+ ...options,
3062
+ workerMode: "dedicated"
3063
+ });
3064
+ const lockToken = randomUUID();
3065
+ const channelName = `node:${createHash("sha256").update(registryKey).digest("hex")}`;
3066
+ const worker = new NodeWorkerTransport({
3067
+ directory,
3068
+ dbName,
3069
+ channelName,
3070
+ lockToken,
3071
+ ownerWaitMs: normalized.ownerWaitMs,
3072
+ encodedDbName: Buffer.from(dbName, "utf8").toString("hex")
3073
+ }, () => {
3074
+ releaseNodeStorageLease(directory, lockToken);
3075
+ });
3076
+ const entry = {
3077
+ dbName,
3078
+ directory,
3079
+ registryKey,
3080
+ worker,
3081
+ proxy: worker.proxy,
3082
+ persistenceBridge: { close() {} },
3083
+ options: normalized,
3084
+ refs: 1,
3085
+ invalidated: false,
3086
+ schemaMigrationInProgress: false,
3087
+ txInvalidationListeners: /* @__PURE__ */ new Set(),
3088
+ handleInvalidationListeners: /* @__PURE__ */ new Set(),
3089
+ channelName,
3090
+ shutdown: null,
3091
+ release: () => releaseEntry(entry),
3092
+ destroy: () => queueLifecycleOperation(registryKey, () => destroyEntry(entry))
3093
+ };
3094
+ worker.proxy.setFatalHandler((error) => {
3095
+ if (registry.get(registryKey) === entry) registry.delete(registryKey);
3096
+ invalidateEntry(entry);
3097
+ queueLifecycleOperation(registryKey, () => shutdownEntry(entry, error)).catch(() => {});
3098
+ });
3099
+ return entry;
3100
+ }
3101
+ async function acquireEntry(dbName, directory, registryKey, options) {
3102
+ const normalized = normalizeOptions({
3103
+ ...options,
3104
+ workerMode: "dedicated"
3105
+ });
3106
+ return queueLifecycleOperation(registryKey, async () => {
3107
+ const existing = registry.get(registryKey);
3108
+ if (existing && !existing.invalidated) {
3109
+ if (existing.schemaMigrationInProgress) throw new DatabaseBusyError(`database ${dbName} is migrating; wait for openNodeDB() to resolve`);
3110
+ assertCompatibleOptions(dbName, existing.options, normalized);
3111
+ if (normalized.debugFailpoint !== null) await existing.proxy.setFailpoint(normalized.debugFailpoint);
3112
+ existing.refs += 1;
3113
+ return existing;
3114
+ }
3115
+ const entry = createEntry(dbName, directory, registryKey, options);
3116
+ try {
3117
+ await entry.proxy.open({
3118
+ dbName,
3119
+ options: normalized
3120
+ });
3121
+ if (entry.invalidated) throw new DatabaseClosedError();
3122
+ registry.set(registryKey, entry);
3123
+ return entry;
3124
+ } catch (error) {
3125
+ await shutdownEntry(entry, workerProtocolError("WorkerTerminatedError", "Node worker open failed"));
3126
+ throw normalizeError(error);
3127
+ }
3128
+ });
3129
+ }
3130
+ async function releaseEntry(entry) {
3131
+ return queueLifecycleOperation(entry.registryKey, async () => {
3132
+ if (entry.invalidated || registry.get(entry.registryKey) !== entry) {
3133
+ if (entry.shutdown) await entry.shutdown;
3134
+ return;
3135
+ }
3136
+ entry.refs -= 1;
3137
+ if (entry.refs > 0) return;
3138
+ registry.delete(entry.registryKey);
3139
+ try {
3140
+ await entry.proxy.close();
3141
+ } finally {
3142
+ invalidateEntry(entry);
3143
+ await shutdownEntry(entry, workerProtocolError("WorkerTerminatedError", "Node worker was released"));
3144
+ }
3145
+ });
3146
+ }
3147
+ async function destroyEntry(entry) {
3148
+ if (entry.invalidated || registry.get(entry.registryKey) !== entry) throw new DatabaseClosedError();
3149
+ registry.delete(entry.registryKey);
3150
+ invalidateEntry(entry);
3151
+ try {
3152
+ await entry.proxy.destroy();
3153
+ } finally {
3154
+ await shutdownEntry(entry, workerProtocolError("WorkerTerminatedError", "Node database was destroyed"));
3155
+ }
3156
+ }
3157
+ async function openNodeDB(name, options) {
3158
+ validateName(name, "openNodeDB");
3159
+ validateDirectoryOptions(options);
3160
+ const { directory, registryKey } = await databaseLocation(name, options);
3161
+ return openDBWithWorker(name, options, (dbName, openOptions) => acquireEntry(dbName, directory, registryKey, openOptions));
3162
+ }
3163
+ async function deleteNodeDB(name, options) {
3164
+ validateName(name, "deleteNodeDB");
3165
+ validateDirectoryOptions(options);
3166
+ const { directory, registryKey } = await databaseLocation(name, options);
3167
+ return queueLifecycleOperation(registryKey, async () => {
3168
+ const existing = registry.get(registryKey);
3169
+ if (existing) return destroyEntry(existing);
3170
+ const entry = createEntry(name, directory, registryKey, {});
3171
+ try {
3172
+ await entry.proxy.deleteDB(name);
3173
+ } finally {
3174
+ await shutdownEntry(entry, workerProtocolError("WorkerTerminatedError", "Node delete worker was released"));
3175
+ }
3176
+ });
3177
+ }
3178
+ async function unsafeDebugCrashNodeWorker(name, options) {
3179
+ validateName(name, "unsafeDebugCrashNodeWorker");
3180
+ validateDirectoryOptions(options);
3181
+ const { registryKey } = await databaseLocation(name, options);
3182
+ return queueLifecycleOperation(registryKey, async () => {
3183
+ const entry = registry.get(registryKey);
3184
+ if (!entry) return false;
3185
+ registry.delete(registryKey);
3186
+ invalidateEntry(entry);
3187
+ await shutdownEntry(entry, workerProtocolError("WorkerTerminatedError", "Node worker was terminated"));
3188
+ return true;
3189
+ });
3190
+ }
3191
+ //#endregion
3192
+ export { ChangeFeedCompactedError, ConstraintError, CorruptionError, DatabaseBusyError, DatabaseClosedError, INDEX_METADATA_STORE, IndexNotFoundError, InjectedFailureError, InternalError, InvalidOpenOptionsError, InvalidRangeError, KeyTooLargeError, MoyoDbError, ReadonlyTransactionError, RecordIndex, RecordStore, RecoveryRequiredError, ReservedStoreNameError, SerializationError, SqlSchemaError, SqlSyntaxError, SqlTypeError, StorageError, StoreExistsError, StoreNameTooLongError, StoreNotFoundError, TransactionClosedError, TransactionConflictError, UniqueIndexConstraintError, UnsupportedPlatformError, ValueTooLargeError, VersionError, WriteTransactionAlreadyOpenError, assertPublicStoreName, cloneNormalizedIndexDefinitions, compareNormalizedIndexDefinitions, compoundKey, compoundKeyCodec, compoundKeyRange, createIndexKeyExtractor, createRecordStore, createSqlClient, decodeIndexEntryKey, decodeIndexMetadataValue, decodeIndexedDocument, deleteNodeDB, encodeCompoundKeyParts, encodeIndexEntryKey, encodeIndexMetadataKey, encodeIndexMetadataValue, encodeIndexScalar, extractLogicalIndexKey, extractLogicalIndexKeyFromDocument, findIndexDefinition, indexDefinitionIdentity, indexKey, indexKeyExactRange, indexRangeToPhysicalRange, indexesForStore, isInternalStoreName, jsonDecode, jsonEncode, jsonRecordCodec, keyCodecs, normalizeError, normalizeIndexDefinitions, openNodeDB, openRecordStore, prefixRange, prefixSuccessor, scalarKeyCodec, serializeNormalizedIndexDefinitions, splitCompoundKey, toPublicIndexDefinitions, u64Key, unsafeDebugCrashNodeWorker, utf8Decode, utf8Encode, withRecordTransaction };
3193
+
3194
+ //# sourceMappingURL=node.js.map