@moyodb/sdk 1.1.0 → 1.4.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.
- package/CONTRIBUTING.md +46 -0
- package/README.md +144 -0
- package/dist/assets/browser-worker-Bj2ErRvC.js +4 -0
- package/dist/assets/browser-worker-Bj2ErRvC.js.map +1 -0
- package/dist/assets/shared-worker-CMeTcusa.js +2 -0
- package/dist/assets/shared-worker-CMeTcusa.js.map +1 -0
- package/dist/chunks/{indexing-BzmFhWXs.js → indexing-B0duqlUs.js} +131 -124
- package/dist/chunks/indexing-B0duqlUs.js.map +1 -0
- package/dist/compression.d.ts +16 -2
- package/dist/compression.d.ts.map +1 -1
- package/dist/demo.d.ts.map +1 -1
- package/dist/engine/README.md +32 -3
- package/dist/engine/moyodb_engine.d.ts +36 -10
- package/dist/engine/moyodb_engine.js +261 -51
- package/dist/engine/moyodb_engine_bg.wasm +0 -0
- package/dist/engine/moyodb_engine_bg.wasm.d.ts +18 -10
- package/dist/engine/package.json +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/indexing.d.ts.map +1 -1
- package/dist/internal.d.ts +6 -0
- package/dist/internal.d.ts.map +1 -1
- package/dist/moyodb-sdk.js +1069 -835
- package/dist/moyodb-sdk.js.map +1 -1
- package/dist/node-engine.js +731 -111
- package/dist/node-engine.js.map +1 -1
- package/dist/node-storage.d.mts +4 -0
- package/dist/node-storage.mjs +38 -3
- package/dist/node-worker-client.d.ts +1 -0
- package/dist/node-worker-client.d.ts.map +1 -1
- package/dist/node-worker.mjs +2 -1
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +302 -67
- package/dist/node.js.map +1 -1
- package/dist/registry.d.ts.map +1 -1
- package/dist/shared-worker-client.d.ts +4 -0
- package/dist/shared-worker-client.d.ts.map +1 -1
- package/dist/shared-worker.d.ts.map +1 -1
- package/dist/snappy.d.ts +3 -0
- package/dist/snappy.d.ts.map +1 -0
- package/dist/sql-parser.d.ts.map +1 -1
- package/dist/types.d.ts +33 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/worker-api.d.ts +19 -6
- package/dist/worker-api.d.ts.map +1 -1
- package/dist/worker-client.d.ts +7 -2
- package/dist/worker-client.d.ts.map +1 -1
- package/dist/worker-protocol.d.ts +18 -3
- package/dist/worker-protocol.d.ts.map +1 -1
- package/dist/worker-server.d.ts.map +1 -1
- package/dist/worker.d.ts +33 -4
- package/dist/worker.d.ts.map +1 -1
- package/package.json +6 -5
- package/dist/assets/browser-worker-DP6Mjz7c.js +0 -4
- package/dist/assets/browser-worker-DP6Mjz7c.js.map +0 -1
- package/dist/assets/shared-worker-DKzhauVf.js +0 -2
- package/dist/assets/shared-worker-DKzhauVf.js.map +0 -1
- package/dist/chunks/indexing-BzmFhWXs.js.map +0 -1
- package/dist/engine/snippets/moyodb-engine-cff0a0e2441a7a8f/js/opfs_shim.js +0 -513
- /package/dist/engine/snippets/{moyodb-engine-8ebe6af0cc2e81b7 → moyodb-engine-1d53039816cbc2f7}/js/opfs_shim.js +0 -0
package/dist/node.js
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
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
|
|
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, Gt as isRecord$1, H as InjectedFailureError, Ht as workerProtocolError, I as ChangeFeedCompactedError, J as ReadonlyTransactionError, K as KeyTooLargeError, L as ConstraintError, M as splitCompoundKey, N as u64Key, Nt as prepareWorkerCommandPayload, 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 clampTimerDelayMs, V as IndexNotFoundError, W as InvalidOpenOptionsError, Wt as compareStringsByCodeUnit, 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-B0duqlUs.js";
|
|
2
2
|
import { createHash, randomUUID } from "node:crypto";
|
|
3
|
-
import {
|
|
3
|
+
import { realpath } from "node:fs/promises";
|
|
4
4
|
import { dirname, join, resolve } from "node:path";
|
|
5
5
|
import { Worker as Worker$1 } from "node:worker_threads";
|
|
6
6
|
import fs from "node:fs";
|
|
7
7
|
//#region src/worker-client.ts
|
|
8
8
|
function clearPendingTimeout(pending) {
|
|
9
|
-
if (pending.timeout)
|
|
9
|
+
if (pending.timeout) {
|
|
10
|
+
clearTimeout(pending.timeout);
|
|
11
|
+
pending.timeout = null;
|
|
12
|
+
}
|
|
10
13
|
}
|
|
11
14
|
var WorkerProtocolClient = class {
|
|
12
15
|
worker;
|
|
@@ -26,16 +29,19 @@ var WorkerProtocolClient = class {
|
|
|
26
29
|
flushScheduled = false;
|
|
27
30
|
awaitingReady = 0;
|
|
28
31
|
readyTimeout = null;
|
|
32
|
+
forwardScanPackets;
|
|
29
33
|
constructor(worker, options = {}) {
|
|
30
34
|
this.worker = worker;
|
|
31
|
-
this.
|
|
35
|
+
this.forwardScanPackets = options.forwardScanPackets === true;
|
|
36
|
+
this.requestTimeoutMs = clampTimerDelayMs(options.requestTimeoutMs ?? 0);
|
|
32
37
|
this.ready = new Promise((resolve, reject) => {
|
|
33
38
|
this.readyResolve = resolve;
|
|
34
39
|
this.readyReject = reject;
|
|
35
40
|
});
|
|
36
|
-
|
|
41
|
+
const readyTimeoutMs = clampTimerDelayMs(options.readyTimeoutMs ?? 0);
|
|
42
|
+
if (readyTimeoutMs > 0) this.readyTimeout = setTimeout(() => {
|
|
37
43
|
this.fail(workerProtocolError("WorkerReadyTimeoutError", "worker did not become ready"));
|
|
38
|
-
},
|
|
44
|
+
}, readyTimeoutMs);
|
|
39
45
|
this.ready.catch(() => void 0);
|
|
40
46
|
this.worker.addEventListener("message", this.handleMessage);
|
|
41
47
|
this.worker.addEventListener("messageerror", this.handleMessageError);
|
|
@@ -160,6 +166,12 @@ var WorkerProtocolClient = class {
|
|
|
160
166
|
range
|
|
161
167
|
]);
|
|
162
168
|
}
|
|
169
|
+
scanPage(request) {
|
|
170
|
+
return this.request("scanPage", [request]);
|
|
171
|
+
}
|
|
172
|
+
closeCursor(cursorId) {
|
|
173
|
+
return this.request("closeCursor", [cursorId]);
|
|
174
|
+
}
|
|
163
175
|
getByIndex(txId, store, indexName, key) {
|
|
164
176
|
return this.request("getByIndex", [
|
|
165
177
|
txId,
|
|
@@ -176,14 +188,15 @@ var WorkerProtocolClient = class {
|
|
|
176
188
|
range
|
|
177
189
|
]);
|
|
178
190
|
}
|
|
179
|
-
scanByIndexPage(txId, store, indexName, range, cursor, limit) {
|
|
191
|
+
scanByIndexPage(txId, store, indexName, range, cursor, limit, maxBytes) {
|
|
180
192
|
return this.request("scanByIndexPage", [
|
|
181
193
|
txId,
|
|
182
194
|
store,
|
|
183
195
|
indexName,
|
|
184
196
|
range,
|
|
185
197
|
cursor,
|
|
186
|
-
limit
|
|
198
|
+
limit,
|
|
199
|
+
maxBytes
|
|
187
200
|
]);
|
|
188
201
|
}
|
|
189
202
|
getIndexes(txId) {
|
|
@@ -256,7 +269,10 @@ var WorkerProtocolClient = class {
|
|
|
256
269
|
};
|
|
257
270
|
return await new Promise((resolve, reject) => {
|
|
258
271
|
const timeout = this.requestTimeoutMs > 0 ? setTimeout(() => {
|
|
272
|
+
const current = this.pending.get(id);
|
|
273
|
+
if (!current || current.timeout === null) return;
|
|
259
274
|
this.pending.delete(id);
|
|
275
|
+
current.timeout = null;
|
|
260
276
|
reject(workerProtocolError("WorkerRequestTimeoutError", `worker command ${command} timed out after ${this.requestTimeoutMs}ms`));
|
|
261
277
|
}, this.requestTimeoutMs) : null;
|
|
262
278
|
this.pending.set(id, {
|
|
@@ -335,6 +351,10 @@ var WorkerProtocolClient = class {
|
|
|
335
351
|
this.fail(workerProtocolError("WorkerProtocolError", "incompatible worker protocol ready message"));
|
|
336
352
|
return;
|
|
337
353
|
}
|
|
354
|
+
if (isRecord$1(data) && (data.type === "moyodb:worker-protocol:response" || data.type === "moyodb:worker-protocol:response-batch") && data.version !== 1) {
|
|
355
|
+
this.fail(workerProtocolError("WorkerProtocolError", "incompatible worker protocol response"));
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
338
358
|
if (isWorkerProtocolResponseBatchMessage(data)) {
|
|
339
359
|
for (const response of data.responses) this.handleResponse(response);
|
|
340
360
|
return;
|
|
@@ -355,7 +375,7 @@ var WorkerProtocolClient = class {
|
|
|
355
375
|
this.pending.delete(data.id);
|
|
356
376
|
clearPendingTimeout(pending);
|
|
357
377
|
try {
|
|
358
|
-
if (data.ok) pending.resolve(decodeWorkerResponsePayload(pending.command, data.result));
|
|
378
|
+
if (data.ok) pending.resolve(this.forwardScanPackets && (pending.command === "scanPage" || pending.command === "scan" || pending.command === "scanByIndex" || pending.command === "scanByIndexPage") ? data.result : decodeWorkerResponsePayload(pending.command, data.result));
|
|
359
379
|
else pending.reject(deserializeWorkerError(data.error));
|
|
360
380
|
} catch (error) {
|
|
361
381
|
pending.reject(error instanceof Error && error.name === "WorkerProtocolError" ? error : workerProtocolError("WorkerProtocolError", "worker response could not be decoded"));
|
|
@@ -1042,7 +1062,7 @@ function bindSqlParameters(parsed, parameters = []) {
|
|
|
1042
1062
|
return Array.from(parameters, (value, index) => {
|
|
1043
1063
|
if (value === null || typeof value === "boolean") return value;
|
|
1044
1064
|
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`);
|
|
1065
|
+
if (!Number.isFinite(value) || Number.isInteger(value) && !Number.isSafeInteger(value) && fitsSigned64(value)) throw new RangeError(`SQL parameter ${index + 1} must be finite; use bigint for integers outside the safe range`);
|
|
1046
1066
|
return value;
|
|
1047
1067
|
}
|
|
1048
1068
|
if (typeof value === "bigint") {
|
|
@@ -1508,6 +1528,11 @@ function tokenize(sql) {
|
|
|
1508
1528
|
});
|
|
1509
1529
|
return tokens;
|
|
1510
1530
|
}
|
|
1531
|
+
function fitsSigned64(value) {
|
|
1532
|
+
if (!Number.isInteger(value)) return false;
|
|
1533
|
+
const integer = BigInt(value);
|
|
1534
|
+
return integer >= SQL_INTEGER_MIN && integer <= SQL_INTEGER_MAX;
|
|
1535
|
+
}
|
|
1511
1536
|
function parseNumber(text, position) {
|
|
1512
1537
|
if (/^[+-]?\d+$/u.test(text)) {
|
|
1513
1538
|
const integer = BigInt(text);
|
|
@@ -1515,7 +1540,7 @@ function parseNumber(text, position) {
|
|
|
1515
1540
|
return integer >= BigInt(Number.MIN_SAFE_INTEGER) && integer <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(integer) : integer;
|
|
1516
1541
|
}
|
|
1517
1542
|
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);
|
|
1543
|
+
if (!Number.isFinite(value) || Number.isInteger(value) && !Number.isSafeInteger(value) && fitsSigned64(value)) throw new SqlSyntaxError("REAL literal must be finite; use integer notation for large integers", position);
|
|
1519
1544
|
const [mantissa, exponentText = "0"] = text.toLowerCase().split("e");
|
|
1520
1545
|
const digits = mantissa.replace(/[+.-]/gu, "");
|
|
1521
1546
|
const nonzero = /[1-9]/u.test(digits);
|
|
@@ -1678,9 +1703,10 @@ async function executeStatement(tx, statement, parameters, indexes, explain) {
|
|
|
1678
1703
|
rowsAffected: 0,
|
|
1679
1704
|
plan: plan.public
|
|
1680
1705
|
};
|
|
1681
|
-
const
|
|
1706
|
+
const maxMatches = statement.kind === "select" && plan.public.sort === "none" && limit !== null ? Math.min(Number.MAX_SAFE_INTEGER, offset + limit) : void 0;
|
|
1707
|
+
const rows = limit === 0 ? [] : await readRows(tx, schema, plan, statement.where, parameters, maxMatches);
|
|
1682
1708
|
if (statement.kind === "select") {
|
|
1683
|
-
if (
|
|
1709
|
+
if (plan.public.sort === "memory") rows.sort((left, right) => compareRows(left, right, orderBy));
|
|
1684
1710
|
return {
|
|
1685
1711
|
rows: rows.slice(offset, limit === null ? void 0 : offset + limit).map(({ row }) => projectRow(row, statement.columns ?? schema.columns.map((column) => column.name))),
|
|
1686
1712
|
rowsAffected: 0,
|
|
@@ -1816,7 +1842,10 @@ function normalizeValue(column, value) {
|
|
|
1816
1842
|
}
|
|
1817
1843
|
case "REAL":
|
|
1818
1844
|
if (typeof value === "number" && Number.isFinite(value)) return Object.is(value, -0) ? 0 : value;
|
|
1819
|
-
if (typeof value === "bigint"
|
|
1845
|
+
if (typeof value === "bigint") {
|
|
1846
|
+
const asNumber = Number(value);
|
|
1847
|
+
if (Number.isFinite(asNumber) && BigInt(asNumber) === value) return asNumber;
|
|
1848
|
+
}
|
|
1820
1849
|
break;
|
|
1821
1850
|
case "TEXT":
|
|
1822
1851
|
if (typeof value === "string") {
|
|
@@ -1950,13 +1979,15 @@ function planAccess(schema, where, orderBy, parameters, indexes) {
|
|
|
1950
1979
|
}
|
|
1951
1980
|
if (!key && !Object.keys(range).length) continue;
|
|
1952
1981
|
const access = candidate.index ? equalityOnly ? "index-lookup" : "index-range" : equalityOnly ? "primary-key-lookup" : "primary-key-range";
|
|
1982
|
+
const ordered = traversalOrder(schema, candidate.column, candidate.index, orderBy, key);
|
|
1983
|
+
if (ordered && orderBy[0]?.direction === "desc") range.reverse = true;
|
|
1953
1984
|
return {
|
|
1954
1985
|
public: {
|
|
1955
1986
|
table: schema.table,
|
|
1956
1987
|
access,
|
|
1957
1988
|
column: candidate.column.name,
|
|
1958
1989
|
...candidate.index ? { index: candidate.index.name } : {},
|
|
1959
|
-
sort:
|
|
1990
|
+
sort: ordered ? "none" : "memory",
|
|
1960
1991
|
reason: `use ${candidate.index ? `index ${candidate.index.name}` : "primary key"} for ${equalityOnly ? "equality" : "range"}; apply remaining predicates after lookup`
|
|
1961
1992
|
},
|
|
1962
1993
|
key,
|
|
@@ -1964,6 +1995,20 @@ function planAccess(schema, where, orderBy, parameters, indexes) {
|
|
|
1964
1995
|
empty: !key && emptyRange(range)
|
|
1965
1996
|
};
|
|
1966
1997
|
}
|
|
1998
|
+
if (orderBy.length) for (const candidate of candidates) {
|
|
1999
|
+
if (!traversalOrder(schema, candidate.column, candidate.index, orderBy)) continue;
|
|
2000
|
+
return {
|
|
2001
|
+
public: {
|
|
2002
|
+
table: schema.table,
|
|
2003
|
+
access: candidate.index ? "index-range" : "table-scan",
|
|
2004
|
+
column: candidate.column.name,
|
|
2005
|
+
...candidate.index ? { index: candidate.index.name } : {},
|
|
2006
|
+
sort: "none",
|
|
2007
|
+
reason: `use ${candidate.index ? `index ${candidate.index.name}` : "primary key"} traversal order`
|
|
2008
|
+
},
|
|
2009
|
+
range: { reverse: orderBy[0].direction === "desc" }
|
|
2010
|
+
};
|
|
2011
|
+
}
|
|
1967
2012
|
return { public: {
|
|
1968
2013
|
table: schema.table,
|
|
1969
2014
|
access: "table-scan",
|
|
@@ -1971,6 +2016,15 @@ function planAccess(schema, where, orderBy, parameters, indexes) {
|
|
|
1971
2016
|
reason: "no supported primary key or index bound; filter table rows"
|
|
1972
2017
|
} };
|
|
1973
2018
|
}
|
|
2019
|
+
function traversalOrder(schema, column, index, orders, key) {
|
|
2020
|
+
if (!orders.length || key && !index) return true;
|
|
2021
|
+
if (orders[0].column !== column.name) return false;
|
|
2022
|
+
if (!index) return orders.length === 1;
|
|
2023
|
+
if (column.type === "INTEGER" && !key) return false;
|
|
2024
|
+
if (orders.length === 1) return orders[0].direction === "asc" || index.unique && !column.nullable;
|
|
2025
|
+
const primary = primaryColumn(schema);
|
|
2026
|
+
return orders.length === 2 && orders[1].column === primary?.name && orders[1].direction === orders[0].direction;
|
|
2027
|
+
}
|
|
1974
2028
|
function planKey(column, value, secondary, equality) {
|
|
1975
2029
|
if (value === null) return null;
|
|
1976
2030
|
if (column.type === "INTEGER") {
|
|
@@ -2023,27 +2077,48 @@ function emptyRange(range) {
|
|
|
2023
2077
|
const compared = compareBytes(lower, upper);
|
|
2024
2078
|
return compared > 0 || compared === 0 && (Boolean(range.gt) || Boolean(range.lt));
|
|
2025
2079
|
}
|
|
2026
|
-
async function readRows(tx, schema, plan) {
|
|
2080
|
+
async function readRows(tx, schema, plan, where, parameters, maxMatches) {
|
|
2027
2081
|
if (plan.empty) return [];
|
|
2028
|
-
|
|
2082
|
+
const rows = [];
|
|
2083
|
+
const accept = (item) => {
|
|
2084
|
+
if (item.key.length === 1 && item.key[0] === TABLE_OWNER_KEY[0]) return false;
|
|
2085
|
+
const row = decodeRow(schema, item);
|
|
2086
|
+
if (!where || evaluateWhere(row.row, where, parameters) === true) rows.push(row);
|
|
2087
|
+
return maxMatches !== void 0 && rows.length >= maxMatches;
|
|
2088
|
+
};
|
|
2029
2089
|
if (plan.public.index) {
|
|
2030
|
-
items = [];
|
|
2031
2090
|
const range = plan.key ? {
|
|
2091
|
+
...plan.range,
|
|
2032
2092
|
gte: plan.key,
|
|
2033
2093
|
lte: plan.key
|
|
2034
2094
|
} : plan.range;
|
|
2035
|
-
for await (const [key, value] of tx.scanByIndex(schema.table, plan.public.index, range))
|
|
2095
|
+
for await (const [key, value] of tx.scanByIndex(schema.table, plan.public.index, range, { maxRows: Math.min(256, maxMatches ?? 256) })) if (accept({
|
|
2036
2096
|
key,
|
|
2037
2097
|
value
|
|
2038
|
-
});
|
|
2098
|
+
})) break;
|
|
2039
2099
|
} else if (plan.key) {
|
|
2040
2100
|
const value = await tx.get(schema.table, plan.key);
|
|
2041
|
-
|
|
2101
|
+
if (value) accept({
|
|
2042
2102
|
key: plan.key,
|
|
2043
2103
|
value
|
|
2044
|
-
}
|
|
2045
|
-
} else
|
|
2046
|
-
|
|
2104
|
+
});
|
|
2105
|
+
} else if (typeof tx.scanPage === "function" && typeof tx.closeScanCursor === "function") {
|
|
2106
|
+
let cursor;
|
|
2107
|
+
try {
|
|
2108
|
+
for (;;) {
|
|
2109
|
+
const page = await tx.scanPage(schema.table, plan.range, {
|
|
2110
|
+
cursor,
|
|
2111
|
+
maxRows: Math.min(256, maxMatches === void 0 ? 256 : maxMatches - rows.length)
|
|
2112
|
+
});
|
|
2113
|
+
cursor = page.cursor;
|
|
2114
|
+
for (const item of page.rows) if (accept(item)) return rows;
|
|
2115
|
+
if (page.done) break;
|
|
2116
|
+
}
|
|
2117
|
+
} finally {
|
|
2118
|
+
if (cursor !== void 0) await tx.closeScanCursor(cursor);
|
|
2119
|
+
}
|
|
2120
|
+
} else for (const item of await tx.scan(schema.table, plan.range)) if (accept(item)) break;
|
|
2121
|
+
return rows;
|
|
2047
2122
|
}
|
|
2048
2123
|
function compareRows(left, right, orders) {
|
|
2049
2124
|
for (const order of orders) {
|
|
@@ -2123,6 +2198,8 @@ function compareBytes(left, right) {
|
|
|
2123
2198
|
//#endregion
|
|
2124
2199
|
//#region src/index.ts
|
|
2125
2200
|
var INDEX_SCAN_PAGE_ROWS = 256;
|
|
2201
|
+
var SCAN_PAGE_ROWS = 256;
|
|
2202
|
+
var SCAN_PAGE_BYTES = 8389662;
|
|
2126
2203
|
async function callProxy(fn) {
|
|
2127
2204
|
try {
|
|
2128
2205
|
return await fn();
|
|
@@ -2135,6 +2212,7 @@ var TransactionImpl = class {
|
|
|
2135
2212
|
#entry;
|
|
2136
2213
|
#txId;
|
|
2137
2214
|
#closed = false;
|
|
2215
|
+
#scanCursors = /* @__PURE__ */ new Set();
|
|
2138
2216
|
#onClose;
|
|
2139
2217
|
constructor(entry, txId, mode, onClose) {
|
|
2140
2218
|
this.#entry = entry;
|
|
@@ -2191,21 +2269,48 @@ var TransactionImpl = class {
|
|
|
2191
2269
|
assertPublicStoreName(store);
|
|
2192
2270
|
return callProxy(() => this.#entry.proxy.scan(this.#txId, store, range));
|
|
2193
2271
|
}
|
|
2272
|
+
async scanPage(store, range = {}, options = {}) {
|
|
2273
|
+
this.#ensureOpen();
|
|
2274
|
+
assertPublicStoreName(store);
|
|
2275
|
+
const normalized = normalizeScanPageOptions(options);
|
|
2276
|
+
if (normalized.cursor !== void 0 && !this.#scanCursors.has(normalized.cursor)) throw new TypeError("scan cursor does not belong to this transaction");
|
|
2277
|
+
const page = await callProxy(() => this.#entry.proxy.scanPage({
|
|
2278
|
+
txId: this.#txId,
|
|
2279
|
+
store,
|
|
2280
|
+
range,
|
|
2281
|
+
cursorId: normalized.cursor,
|
|
2282
|
+
maxRows: normalized.maxRows,
|
|
2283
|
+
maxBytes: normalized.maxBytes
|
|
2284
|
+
}));
|
|
2285
|
+
this.#ensureOpen();
|
|
2286
|
+
if (normalized.cursor !== void 0) this.#scanCursors.delete(normalized.cursor);
|
|
2287
|
+
if (page.cursorId !== void 0 && !this.#closed) this.#scanCursors.add(page.cursorId);
|
|
2288
|
+
return publicScanPage(page);
|
|
2289
|
+
}
|
|
2290
|
+
scanIter(store, range = {}, options = {}) {
|
|
2291
|
+
return iterateScanPages(this, store, range, options);
|
|
2292
|
+
}
|
|
2293
|
+
async closeScanCursor(cursor) {
|
|
2294
|
+
normalizeScanCursor(cursor);
|
|
2295
|
+
if (!this.#scanCursors.delete(cursor) || this.#entry.invalidated) return;
|
|
2296
|
+
await callProxy(() => this.#entry.proxy.closeCursor(cursor));
|
|
2297
|
+
}
|
|
2194
2298
|
async getByIndex(store, indexName, key) {
|
|
2195
2299
|
this.#ensureOpen();
|
|
2196
2300
|
assertPublicStoreName(store);
|
|
2197
2301
|
return callProxy(() => this.#entry.proxy.getByIndex(this.#txId, store, indexName, key));
|
|
2198
2302
|
}
|
|
2199
|
-
async *scanByIndex(store, indexName, range = {}) {
|
|
2303
|
+
async *scanByIndex(store, indexName, range = {}, options = {}) {
|
|
2200
2304
|
this.#ensureOpen();
|
|
2201
2305
|
assertPublicStoreName(store);
|
|
2306
|
+
const { maxRows: pageRows, maxBytes } = normalizeScanPageOptions(options);
|
|
2202
2307
|
let remaining = range.limit ?? Number.POSITIVE_INFINITY;
|
|
2203
2308
|
let cursor = null;
|
|
2204
2309
|
while (remaining > 0) {
|
|
2205
2310
|
this.#ensureOpen();
|
|
2206
|
-
const pageLimit = Math.min(remaining, INDEX_SCAN_PAGE_ROWS);
|
|
2311
|
+
const pageLimit = Math.min(remaining, pageRows, INDEX_SCAN_PAGE_ROWS);
|
|
2207
2312
|
const resumeAfter = cursor;
|
|
2208
|
-
const page = await callProxy(() => this.#entry.proxy.scanByIndexPage(this.#txId, store, indexName, range, resumeAfter, pageLimit));
|
|
2313
|
+
const page = await callProxy(() => this.#entry.proxy.scanByIndexPage(this.#txId, store, indexName, range, resumeAfter, pageLimit, maxBytes));
|
|
2209
2314
|
for (const row of page.rows) yield [row.key, row.value];
|
|
2210
2315
|
remaining -= page.rows.length;
|
|
2211
2316
|
if (page.cursor === null) return;
|
|
@@ -2270,6 +2375,7 @@ var TransactionImpl = class {
|
|
|
2270
2375
|
#markClosed() {
|
|
2271
2376
|
if (this.#closed) return;
|
|
2272
2377
|
this.#closed = true;
|
|
2378
|
+
this.#scanCursors.clear();
|
|
2273
2379
|
this.#onClose();
|
|
2274
2380
|
}
|
|
2275
2381
|
};
|
|
@@ -2279,6 +2385,8 @@ var DBImpl = class {
|
|
|
2279
2385
|
#closePromise = null;
|
|
2280
2386
|
#transactions = /* @__PURE__ */ new Set();
|
|
2281
2387
|
#pendingBegins = /* @__PURE__ */ new Set();
|
|
2388
|
+
#scanCursors = /* @__PURE__ */ new Set();
|
|
2389
|
+
#pendingScanPages = /* @__PURE__ */ new Set();
|
|
2282
2390
|
#transactionGeneration = 0;
|
|
2283
2391
|
#subscriptions;
|
|
2284
2392
|
#unsubscribeTxInvalidated = null;
|
|
@@ -2320,20 +2428,17 @@ var DBImpl = class {
|
|
|
2320
2428
|
}
|
|
2321
2429
|
}
|
|
2322
2430
|
async listStores() {
|
|
2323
|
-
this.#
|
|
2324
|
-
return callProxy(() => this.#entry.proxy.listStores());
|
|
2431
|
+
return this.#call(() => this.#entry.proxy.listStores());
|
|
2325
2432
|
}
|
|
2326
2433
|
async listIndexes() {
|
|
2327
2434
|
this.#ensureOpen();
|
|
2328
2435
|
return callProxy(() => this.#entry.proxy.getIndexes());
|
|
2329
2436
|
}
|
|
2330
2437
|
async getVersion() {
|
|
2331
|
-
this.#
|
|
2332
|
-
return callProxy(() => this.#entry.proxy.getVersion());
|
|
2438
|
+
return this.#call(() => this.#entry.proxy.getVersion());
|
|
2333
2439
|
}
|
|
2334
2440
|
async changesSince(txid, options = {}) {
|
|
2335
|
-
this.#
|
|
2336
|
-
return callProxy(() => this.#entry.proxy.changesSince(normalizeTxId(txid), normalizeChangeFeedOptions(options)));
|
|
2441
|
+
return this.#call(() => this.#entry.proxy.changesSince(normalizeTxId(txid), normalizeChangeFeedOptions(options)));
|
|
2337
2442
|
}
|
|
2338
2443
|
async #beginImpl(mode) {
|
|
2339
2444
|
this.#ensureOpen();
|
|
@@ -2402,60 +2507,82 @@ var DBImpl = class {
|
|
|
2402
2507
|
assertPublicStoreName(store);
|
|
2403
2508
|
return this.#autocommit("readonly", "scan", [store, range]);
|
|
2404
2509
|
}
|
|
2510
|
+
async scanPage(store, range = {}, options = {}) {
|
|
2511
|
+
this.#ensureOpen();
|
|
2512
|
+
assertPublicStoreName(store);
|
|
2513
|
+
const normalized = normalizeScanPageOptions(options);
|
|
2514
|
+
if (normalized.cursor !== void 0 && !this.#scanCursors.has(normalized.cursor)) throw new TypeError("scan cursor does not belong to this database handle");
|
|
2515
|
+
const generation = this.#transactionGeneration;
|
|
2516
|
+
const pending = callProxy(() => this.#entry.proxy.scanPage({
|
|
2517
|
+
store,
|
|
2518
|
+
range,
|
|
2519
|
+
cursorId: normalized.cursor,
|
|
2520
|
+
maxRows: normalized.maxRows,
|
|
2521
|
+
maxBytes: normalized.maxBytes
|
|
2522
|
+
}));
|
|
2523
|
+
this.#pendingScanPages.add(pending);
|
|
2524
|
+
try {
|
|
2525
|
+
const page = await pending;
|
|
2526
|
+
if (normalized.cursor !== void 0) this.#scanCursors.delete(normalized.cursor);
|
|
2527
|
+
if (page.cursorId !== void 0) this.#scanCursors.add(page.cursorId);
|
|
2528
|
+
this.#ensureOpen();
|
|
2529
|
+
if (generation !== this.#transactionGeneration) {
|
|
2530
|
+
if (page.cursorId !== void 0) await this.closeScanCursor(page.cursorId);
|
|
2531
|
+
throw new TransactionClosedError();
|
|
2532
|
+
}
|
|
2533
|
+
return publicScanPage(page);
|
|
2534
|
+
} finally {
|
|
2535
|
+
this.#pendingScanPages.delete(pending);
|
|
2536
|
+
}
|
|
2537
|
+
}
|
|
2538
|
+
scanIter(store, range = {}, options = {}) {
|
|
2539
|
+
return iterateScanPages(this, store, range, options);
|
|
2540
|
+
}
|
|
2541
|
+
async closeScanCursor(cursor) {
|
|
2542
|
+
normalizeScanCursor(cursor);
|
|
2543
|
+
if (!this.#scanCursors.delete(cursor) || this.#entry.invalidated) return;
|
|
2544
|
+
await callProxy(() => this.#entry.proxy.closeCursor(cursor));
|
|
2545
|
+
}
|
|
2405
2546
|
/** The transaction boundary stays in the worker, so one round trip is enough. */
|
|
2406
2547
|
async #autocommit(mode, command, args) {
|
|
2407
2548
|
this.#ensureOpen();
|
|
2408
2549
|
return callProxy(() => this.#entry.proxy.autocommit(mode, command, args));
|
|
2409
2550
|
}
|
|
2410
2551
|
async exportSnapshot(options = {}) {
|
|
2411
|
-
this.#
|
|
2412
|
-
return callProxy(async () => {
|
|
2552
|
+
return this.#call(async () => {
|
|
2413
2553
|
const bytes = await this.#entry.proxy.exportSnapshot(normalizeExportSnapshotOptions(options));
|
|
2414
2554
|
return bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
|
|
2415
2555
|
});
|
|
2416
2556
|
}
|
|
2417
2557
|
async importSnapshot(data) {
|
|
2418
|
-
this.#
|
|
2419
|
-
invalidateTransactions(this.#entry);
|
|
2420
|
-
return callProxy(() => {
|
|
2558
|
+
return this.#callAfterTxInvalidation(() => {
|
|
2421
2559
|
const bytes = data instanceof Uint8Array ? data : new Uint8Array(data);
|
|
2422
2560
|
return this.#entry.proxy.importSnapshot(bytes);
|
|
2423
2561
|
});
|
|
2424
2562
|
}
|
|
2425
2563
|
async reset() {
|
|
2426
|
-
this.#
|
|
2427
|
-
invalidateTransactions(this.#entry);
|
|
2428
|
-
return callProxy(() => this.#entry.proxy.reset());
|
|
2564
|
+
return this.#callAfterTxInvalidation(() => this.#entry.proxy.reset());
|
|
2429
2565
|
}
|
|
2430
2566
|
async compact() {
|
|
2431
|
-
this.#
|
|
2432
|
-
invalidateTransactions(this.#entry);
|
|
2433
|
-
return callProxy(() => this.#entry.proxy.compact());
|
|
2567
|
+
return this.#callAfterTxInvalidation(() => this.#entry.proxy.compact());
|
|
2434
2568
|
}
|
|
2435
2569
|
async rebuild() {
|
|
2436
|
-
this.#
|
|
2437
|
-
invalidateTransactions(this.#entry);
|
|
2438
|
-
return callProxy(() => this.#entry.proxy.rebuild());
|
|
2570
|
+
return this.#callAfterTxInvalidation(() => this.#entry.proxy.rebuild());
|
|
2439
2571
|
}
|
|
2440
2572
|
async stats() {
|
|
2441
|
-
this.#
|
|
2442
|
-
return callProxy(() => this.#entry.proxy.stats());
|
|
2573
|
+
return this.#call(() => this.#entry.proxy.stats());
|
|
2443
2574
|
}
|
|
2444
2575
|
async storageInfo() {
|
|
2445
|
-
this.#
|
|
2446
|
-
return callProxy(() => this.#entry.proxy.storageInfo());
|
|
2576
|
+
return this.#call(() => this.#entry.proxy.storageInfo());
|
|
2447
2577
|
}
|
|
2448
2578
|
async requestPersistence() {
|
|
2449
|
-
this.#
|
|
2450
|
-
return callProxy(() => this.#entry.proxy.requestPersistence());
|
|
2579
|
+
return this.#call(() => this.#entry.proxy.requestPersistence());
|
|
2451
2580
|
}
|
|
2452
2581
|
async destroy() {
|
|
2453
|
-
this.#
|
|
2454
|
-
return callProxy(() => this.#entry.destroy?.() ?? destroyDbWorker(this.#entry));
|
|
2582
|
+
return this.#call(() => this.#entry.destroy?.() ?? destroyDbWorker(this.#entry));
|
|
2455
2583
|
}
|
|
2456
2584
|
async setFailpoint(failpoint) {
|
|
2457
|
-
this.#
|
|
2458
|
-
return callProxy(() => this.#entry.proxy.setFailpoint(failpoint));
|
|
2585
|
+
return this.#call(() => this.#entry.proxy.setFailpoint(failpoint));
|
|
2459
2586
|
}
|
|
2460
2587
|
subscribe(arg1, arg2, arg3) {
|
|
2461
2588
|
this.#ensureOpen();
|
|
@@ -2481,7 +2608,10 @@ var DBImpl = class {
|
|
|
2481
2608
|
}
|
|
2482
2609
|
async #finishClose() {
|
|
2483
2610
|
await Promise.allSettled(Array.from(this.#pendingBegins));
|
|
2611
|
+
await Promise.allSettled(Array.from(this.#pendingScanPages));
|
|
2484
2612
|
let rollbackError = null;
|
|
2613
|
+
const cursorResults = await Promise.allSettled(Array.from(this.#scanCursors, (cursor) => this.closeScanCursor(cursor)));
|
|
2614
|
+
for (const result of cursorResults) if (result.status === "rejected" && rollbackError === null) rollbackError = normalizeError(result.reason);
|
|
2485
2615
|
const txs = Array.from(this.#transactions);
|
|
2486
2616
|
if (txs.length > 0) {
|
|
2487
2617
|
const results = await Promise.allSettled(txs.map((tx) => tx.rollbackIfOpen()));
|
|
@@ -2502,6 +2632,7 @@ var DBImpl = class {
|
|
|
2502
2632
|
}
|
|
2503
2633
|
#forceCloseTransactions() {
|
|
2504
2634
|
this.#transactionGeneration += 1;
|
|
2635
|
+
this.#scanCursors.clear();
|
|
2505
2636
|
for (const tx of Array.from(this.#transactions)) tx.forceClose();
|
|
2506
2637
|
}
|
|
2507
2638
|
#markInvalidated() {
|
|
@@ -2511,6 +2642,15 @@ var DBImpl = class {
|
|
|
2511
2642
|
this.#forceCloseTransactions();
|
|
2512
2643
|
this.#subscriptions.close();
|
|
2513
2644
|
}
|
|
2645
|
+
#call(run) {
|
|
2646
|
+
this.#ensureOpen();
|
|
2647
|
+
return callProxy(run);
|
|
2648
|
+
}
|
|
2649
|
+
#callAfterTxInvalidation(run) {
|
|
2650
|
+
this.#ensureOpen();
|
|
2651
|
+
invalidateTransactions(this.#entry);
|
|
2652
|
+
return callProxy(run);
|
|
2653
|
+
}
|
|
2514
2654
|
#ensureOpen() {
|
|
2515
2655
|
if (this.#closed || this.#entry.invalidated) throw new DatabaseClosedError();
|
|
2516
2656
|
}
|
|
@@ -2573,11 +2713,20 @@ var MigrationTransactionImpl = class {
|
|
|
2573
2713
|
async scan(store, range = {}) {
|
|
2574
2714
|
return this.#inner.scan(store, range);
|
|
2575
2715
|
}
|
|
2716
|
+
scanPage(store, range = {}, options = {}) {
|
|
2717
|
+
return this.#inner.scanPage(store, range, options);
|
|
2718
|
+
}
|
|
2719
|
+
scanIter(store, range = {}, options = {}) {
|
|
2720
|
+
return this.#inner.scanIter(store, range, options);
|
|
2721
|
+
}
|
|
2722
|
+
closeScanCursor(cursor) {
|
|
2723
|
+
return this.#inner.closeScanCursor(cursor);
|
|
2724
|
+
}
|
|
2576
2725
|
async getByIndex(store, indexName, key) {
|
|
2577
2726
|
return this.#inner.getByIndex(store, indexName, key);
|
|
2578
2727
|
}
|
|
2579
|
-
scanByIndex(store, indexName, range = {}) {
|
|
2580
|
-
return this.#inner.scanByIndex(store, indexName, range);
|
|
2728
|
+
scanByIndex(store, indexName, range = {}, options = {}) {
|
|
2729
|
+
return this.#inner.scanByIndex(store, indexName, range, options);
|
|
2581
2730
|
}
|
|
2582
2731
|
async createStore(name, options = {}) {
|
|
2583
2732
|
await this.#inner.createStore(name, options);
|
|
@@ -2648,6 +2797,15 @@ var MigrationDbImpl = class {
|
|
|
2648
2797
|
async scan(store, range = {}) {
|
|
2649
2798
|
return this.#transaction.scan(store, range);
|
|
2650
2799
|
}
|
|
2800
|
+
scanPage(store, range = {}, options = {}) {
|
|
2801
|
+
return this.#transaction.scanPage(store, range, options);
|
|
2802
|
+
}
|
|
2803
|
+
scanIter(store, range = {}, options = {}) {
|
|
2804
|
+
return this.#transaction.scanIter(store, range, options);
|
|
2805
|
+
}
|
|
2806
|
+
closeScanCursor(cursor) {
|
|
2807
|
+
return this.#transaction.closeScanCursor(cursor);
|
|
2808
|
+
}
|
|
2651
2809
|
exportSnapshot(_options = {}) {
|
|
2652
2810
|
return Promise.reject(migrationUnsupportedMethod("db.exportSnapshot"));
|
|
2653
2811
|
}
|
|
@@ -2693,8 +2851,61 @@ function migrationUnsupportedMethod(method) {
|
|
|
2693
2851
|
error.name = "InvalidStateError";
|
|
2694
2852
|
return error;
|
|
2695
2853
|
}
|
|
2854
|
+
function publicScanPage(page) {
|
|
2855
|
+
return {
|
|
2856
|
+
rows: page.rows,
|
|
2857
|
+
cursor: page.cursorId,
|
|
2858
|
+
done: page.done,
|
|
2859
|
+
bytes: page.bytes
|
|
2860
|
+
};
|
|
2861
|
+
}
|
|
2862
|
+
function normalizeScanCursor(value) {
|
|
2863
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0) throw new TypeError("scan cursor must be a positive safe integer");
|
|
2864
|
+
return value;
|
|
2865
|
+
}
|
|
2866
|
+
function normalizeScanPageOptions(options) {
|
|
2867
|
+
const input = normalizeOptionsObject(options, "scan page options must be an object");
|
|
2868
|
+
const maxRows = input.maxRows ?? SCAN_PAGE_ROWS;
|
|
2869
|
+
const maxBytes = input.maxBytes ?? SCAN_PAGE_BYTES;
|
|
2870
|
+
if (!Number.isSafeInteger(maxRows) || maxRows <= 0 || maxRows > 4294967295) throw new TypeError("scan page maxRows must be a positive 32-bit integer");
|
|
2871
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes < 4 || maxBytes > 4294967295) throw new TypeError("scan page maxBytes must be a 32-bit integer of at least 4");
|
|
2872
|
+
return {
|
|
2873
|
+
maxRows,
|
|
2874
|
+
maxBytes,
|
|
2875
|
+
cursor: input.cursor === void 0 ? void 0 : normalizeScanCursor(input.cursor)
|
|
2876
|
+
};
|
|
2877
|
+
}
|
|
2878
|
+
async function* iterateScanPages(source, store, range, options) {
|
|
2879
|
+
const bounds = { ...range };
|
|
2880
|
+
for (const field of [
|
|
2881
|
+
"gt",
|
|
2882
|
+
"gte",
|
|
2883
|
+
"lt",
|
|
2884
|
+
"lte"
|
|
2885
|
+
]) {
|
|
2886
|
+
const bound = bounds[field];
|
|
2887
|
+
if (bound instanceof Uint8Array) bounds[field] = bound.slice();
|
|
2888
|
+
}
|
|
2889
|
+
const pageOptions = normalizeScanPageOptions(options);
|
|
2890
|
+
let cursor;
|
|
2891
|
+
try {
|
|
2892
|
+
for (;;) {
|
|
2893
|
+
const page = await source.scanPage(store, bounds, {
|
|
2894
|
+
...pageOptions,
|
|
2895
|
+
cursor
|
|
2896
|
+
});
|
|
2897
|
+
cursor = page.cursor;
|
|
2898
|
+
for (const row of page.rows) yield row;
|
|
2899
|
+
if (page.done) return;
|
|
2900
|
+
}
|
|
2901
|
+
} finally {
|
|
2902
|
+
if (cursor !== void 0) await source.closeScanCursor(cursor);
|
|
2903
|
+
}
|
|
2904
|
+
}
|
|
2696
2905
|
function normalizeDatabaseName(value, method) {
|
|
2697
2906
|
if (typeof value !== "string" || value.length === 0) throw new TypeError(`${method}() database name must be a non-empty string`);
|
|
2907
|
+
if (/[\uD800-\uDFFF]/u.test(value)) throw new TypeError(`${method}() database name must contain valid Unicode`);
|
|
2908
|
+
if (new TextEncoder().encode(value).length > 127) throw new TypeError(`${method}() database name must contain at most 127 UTF-8 bytes`);
|
|
2698
2909
|
return value;
|
|
2699
2910
|
}
|
|
2700
2911
|
var typeError = (message) => new TypeError(message);
|
|
@@ -2722,17 +2933,17 @@ function normalizePutOptions(options) {
|
|
|
2722
2933
|
const normalizedTtl = normalizeOptionalNonNegativeSafeInteger(ttl, "ttl must be a non-negative safe integer");
|
|
2723
2934
|
return normalizedTtl === void 0 ? {} : { ttl: normalizedTtl };
|
|
2724
2935
|
}
|
|
2725
|
-
function normalizeCompressionOptions(options, message) {
|
|
2936
|
+
function normalizeCompressionOptions(options, message, allowSnappy) {
|
|
2726
2937
|
const { compression } = normalizeOptionsObject(options, message);
|
|
2727
2938
|
if (compression === void 0) return {};
|
|
2728
|
-
if (compression !== false && compression !== "gzip" && compression !== "deflate") throw new TypeError("compression must be \"gzip\", \"deflate\", or false");
|
|
2939
|
+
if (compression !== false && compression !== "gzip" && compression !== "deflate" && !(allowSnappy && compression === "snappy")) throw new TypeError(allowSnappy ? "compression must be \"gzip\", \"deflate\", \"snappy\", or false" : "compression must be \"gzip\", \"deflate\", or false");
|
|
2729
2940
|
return { compression };
|
|
2730
2941
|
}
|
|
2731
2942
|
function normalizeCreateStoreOptions(options) {
|
|
2732
|
-
return normalizeCompressionOptions(options, "createStore options must be an object");
|
|
2943
|
+
return normalizeCompressionOptions(options, "createStore options must be an object", true);
|
|
2733
2944
|
}
|
|
2734
2945
|
function normalizeExportSnapshotOptions(options) {
|
|
2735
|
-
return normalizeCompressionOptions(options, "exportSnapshot options must be an object");
|
|
2946
|
+
return normalizeCompressionOptions(options, "exportSnapshot options must be an object", false);
|
|
2736
2947
|
}
|
|
2737
2948
|
function normalizeTxId(value) {
|
|
2738
2949
|
return normalizeNonNegativeSafeInteger(value, "txid must be a non-negative safe integer");
|
|
@@ -2883,6 +3094,9 @@ var NodeWorkerTransport = class {
|
|
|
2883
3094
|
var LOCK_DIRECTORY = ".moyodb.lock";
|
|
2884
3095
|
var PENDING_PREFIX = ".moyodb.pending-";
|
|
2885
3096
|
var TOKEN_PATTERN = /^[a-f\d]{8}-[a-f\d]{4}-[a-f\d]{4}-[a-f\d]{4}-[a-f\d]{12}$/i;
|
|
3097
|
+
var MAX_LEASE_RECORD_BYTES = 1024;
|
|
3098
|
+
var MAX_LEASE_PID = 4294967295;
|
|
3099
|
+
var MISSING_DATABASE_MESSAGE = "database missing and create_if_missing=false";
|
|
2886
3100
|
function namedError(name, message, cause) {
|
|
2887
3101
|
const error = new Error(message, cause === void 0 ? void 0 : { cause });
|
|
2888
3102
|
error.name = name;
|
|
@@ -2928,9 +3142,9 @@ function readLease(directory, token) {
|
|
|
2928
3142
|
const lockDirectory = join(directory, LOCK_DIRECTORY);
|
|
2929
3143
|
entryStat(lockDirectory, "directory");
|
|
2930
3144
|
const recordPath = join(lockDirectory, `${validateToken(token)}.json`);
|
|
2931
|
-
entryStat(recordPath, "file");
|
|
3145
|
+
if (entryStat(recordPath, "file").size > MAX_LEASE_RECORD_BYTES) throw namedError("DatabaseBusyError", "storage lock owner cannot be verified");
|
|
2932
3146
|
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");
|
|
3147
|
+
if (owner.token !== token || !Number.isSafeInteger(owner.pid) || owner.pid <= 0 || owner.pid > MAX_LEASE_PID) throw namedError("DatabaseBusyError", "storage lock owner cannot be verified");
|
|
2934
3148
|
return {
|
|
2935
3149
|
lockDirectory,
|
|
2936
3150
|
recordPath,
|
|
@@ -3011,6 +3225,21 @@ function releaseNodeStorageLease(directory, lockToken) {
|
|
|
3011
3225
|
throw storageError(error);
|
|
3012
3226
|
}
|
|
3013
3227
|
}
|
|
3228
|
+
function ensureStorageRoot(directory, createIfMissing = true) {
|
|
3229
|
+
const path = validateDirectory(directory);
|
|
3230
|
+
if (!createIfMissing) {
|
|
3231
|
+
try {
|
|
3232
|
+
entryStat(path, "directory");
|
|
3233
|
+
} catch (error) {
|
|
3234
|
+
if (error?.code === "ENOENT") throw namedError("StorageError", MISSING_DATABASE_MESSAGE);
|
|
3235
|
+
if (error?.name === "TypeMismatchError" || error?.name === "SecurityError") throw error;
|
|
3236
|
+
throw storageError(error);
|
|
3237
|
+
}
|
|
3238
|
+
return path;
|
|
3239
|
+
}
|
|
3240
|
+
fs.mkdirSync(path, { recursive: true });
|
|
3241
|
+
return path;
|
|
3242
|
+
}
|
|
3014
3243
|
//#endregion
|
|
3015
3244
|
//#region src/node.ts
|
|
3016
3245
|
var registry = /* @__PURE__ */ new Map();
|
|
@@ -3035,7 +3264,12 @@ function validateDirectoryOptions(options) {
|
|
|
3035
3264
|
if (typeof directory !== "string" || directory.length === 0 || directory.includes("\0")) throw new InvalidOpenOptionsError("directory must be a non-empty filesystem path");
|
|
3036
3265
|
}
|
|
3037
3266
|
async function databaseLocation(name, options) {
|
|
3038
|
-
|
|
3267
|
+
try {
|
|
3268
|
+
ensureStorageRoot(options.directory, options.createIfMissing !== false);
|
|
3269
|
+
} catch (error) {
|
|
3270
|
+
if (error instanceof Error && error.name === "StorageError") throw new StorageError(error.message || "database missing and create_if_missing=false");
|
|
3271
|
+
throw error;
|
|
3272
|
+
}
|
|
3039
3273
|
const root = await realpath(options.directory);
|
|
3040
3274
|
const directory = join(root, "stackdb", Buffer.from(name, "utf8").toString("hex"));
|
|
3041
3275
|
return {
|
|
@@ -3069,7 +3303,8 @@ function createEntry(dbName, directory, registryKey, options) {
|
|
|
3069
3303
|
channelName,
|
|
3070
3304
|
lockToken,
|
|
3071
3305
|
ownerWaitMs: normalized.ownerWaitMs,
|
|
3072
|
-
encodedDbName: Buffer.from(dbName, "utf8").toString("hex")
|
|
3306
|
+
encodedDbName: Buffer.from(dbName, "utf8").toString("hex"),
|
|
3307
|
+
createIfMissing: normalized.createIfMissing
|
|
3073
3308
|
}, () => {
|
|
3074
3309
|
releaseNodeStorageLease(directory, lockToken);
|
|
3075
3310
|
});
|