@minnowdb/core 0.8.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/engine/artifact-cache.js +3 -2
- package/dist/engine/buffered-writer.js +12 -2
- package/dist/engine/client.d.ts +8 -2
- package/dist/engine/client.js +106 -22
- package/dist/engine/database.d.ts +5 -3
- package/dist/engine/database.js +2044 -1674
- package/dist/engine/errors.d.ts +19 -0
- package/dist/engine/errors.js +29 -0
- package/dist/engine/index.d.ts +1 -0
- package/dist/engine/index.js +4 -0
- package/dist/engine/live-accept.js +13 -0
- package/dist/engine/live-aggregate.js +54 -16
- package/dist/engine/live.d.ts +2 -0
- package/dist/engine/live.js +109 -108
- package/dist/engine/query-cache.js +17 -2
- package/dist/engine/query.d.ts +1 -2
- package/dist/engine/query.js +4 -7
- package/dist/engine/result-state.d.ts +7 -0
- package/dist/engine/result-state.js +15 -0
- package/dist/engine/vector.d.ts +4 -0
- package/dist/engine/vector.js +14 -14
- package/dist/engine/windows.js +15 -14
- package/dist/engine/worker-server.d.ts +1 -1
- package/dist/engine/worker-server.js +28 -15
- package/dist/engine/write-coordinator.js +54 -0
- package/dist/storage/indexeddb.js +134 -89
- package/dist/storage/opfs/index.d.ts +1 -1
- package/dist/storage/opfs/index.js +3 -1
- package/dist/storage/opfs/leader.js +20 -9
- package/dist/storage/opfs/rpc.js +3 -1
- package/dist/storage/opfs/store.d.ts +2 -0
- package/dist/storage/opfs/store.js +128 -40
- package/dist/storage/toolkit/record-core.js +2 -1
- package/dist/storage/types.d.ts +14 -0
- package/dist/storage/types.js +21 -0
- package/dist/transactions/index.d.ts +3 -0
- package/dist/transactions/index.js +4 -1
- package/dist/worker-protocol/index.d.ts +1 -1
- package/dist/worker-protocol/index.js +1 -1
- package/package.json +2 -2
- package/dist/date-value.d.ts +0 -20
- package/dist/engine/artifact-cache.d.ts +0 -29
- package/dist/engine/byte-estimates.d.ts +0 -11
- package/dist/engine/cancellation.d.ts +0 -2
- package/dist/engine/defaults.d.ts +0 -29
- package/dist/engine/group-index.d.ts +0 -33
- package/dist/engine/join-index.d.ts +0 -10
- package/dist/engine/live-aggregate.d.ts +0 -12
- package/dist/engine/live-equal.d.ts +0 -7
- package/dist/engine/point-read.d.ts +0 -59
- package/dist/engine/query-cache.d.ts +0 -21
- package/dist/engine/query-generations.d.ts +0 -8
- package/dist/engine/query-identity.d.ts +0 -3
- package/dist/engine/result-wire.d.ts +0 -70
- package/dist/engine/sort-keys.d.ts +0 -73
- package/dist/engine/sql-domains.d.ts +0 -97
- package/dist/engine/sql-functions.d.ts +0 -11
- package/dist/engine/sql-json.d.ts +0 -40
- package/dist/engine/sql-semantics.d.ts +0 -66
- package/dist/engine/worker-store-indexeddb.d.ts +0 -2
- package/dist/engine/worker-store-memory.d.ts +0 -2
- package/dist/engine/worker-store-opfs.d.ts +0 -2
- package/dist/engine/write-block-planner.d.ts +0 -19
- package/dist/storage/opfs/leader.d.ts +0 -460
- package/dist/storage/opfs/rpc.d.ts +0 -82
- package/dist/storage/opfs/snapshot-ledger.d.ts +0 -41
package/dist/engine/windows.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { QueryMemoryContext } from "./memory.js";
|
|
2
2
|
import { buildSortKeyColumn, sortKeyIndexes } from "./sort-keys.js";
|
|
3
|
-
import { compareSqlValues } from "./sql-semantics.js";
|
|
3
|
+
import { compareSqlValues, defineSqlResultProperty } from "./sql-semantics.js";
|
|
4
4
|
import { exactNumericBinary, exactNumericValue, isExactNumeric } from "./sql-domains.js";
|
|
5
5
|
import { throwIfAborted } from "./cancellation.js";
|
|
6
6
|
class FrameAggregate {
|
|
@@ -246,13 +246,12 @@ function* applyPartition(rows, partition, window, result, memory) {
|
|
|
246
246
|
}
|
|
247
247
|
}
|
|
248
248
|
const row = rows[indexes[position] ?? -1];
|
|
249
|
-
if (row !== void 0)
|
|
250
|
-
|
|
251
|
-
value
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
});
|
|
249
|
+
if (row !== void 0) {
|
|
250
|
+
if (window.alias in row)
|
|
251
|
+
defineSqlResultProperty(row, window.alias, value);
|
|
252
|
+
else
|
|
253
|
+
row[window.alias] = value;
|
|
254
|
+
}
|
|
256
255
|
}
|
|
257
256
|
}
|
|
258
257
|
function* windowSteps(result, windows, options) {
|
|
@@ -286,7 +285,9 @@ function* windowSteps(result, windows, options) {
|
|
|
286
285
|
...window.partitionAliases,
|
|
287
286
|
...window.orderAliases.map(({ alias }) => alias)
|
|
288
287
|
];
|
|
289
|
-
|
|
288
|
+
const peers = group.some((member) => !["ROW_NUMBER", "NTILE", "LAG", "LEAD"].includes(member.name) && (["RANK", "DENSE_RANK", "PERCENT_RANK", "CUME_DIST"].includes(member.name) || member.frame?.unit !== "rows" || member.frame.exclude === "group" || member.frame.exclude === "ties"));
|
|
289
|
+
const range = group.some((member) => member.frame?.unit === "range");
|
|
290
|
+
work.tally(rows.length * (20 + (peers ? 20 : 0) + (range ? 8 : 0) + aliases.length * 40), "Window sort and partition buffers");
|
|
290
291
|
const columns = aliases.map((alias) => buildSortKeyColumn(rows.length, (index) => rows[index]?.[alias] ?? null));
|
|
291
292
|
yield;
|
|
292
293
|
const indexes = sortKeyIndexes(rows.length, columns.map((column, index) => {
|
|
@@ -306,14 +307,14 @@ function* windowSteps(result, windows, options) {
|
|
|
306
307
|
const size = end - start;
|
|
307
308
|
const partition = {
|
|
308
309
|
indexes: indexes.subarray(start, end),
|
|
309
|
-
peerStart: new Int32Array(size),
|
|
310
|
-
peerEnd: new Int32Array(size),
|
|
311
|
-
groupOrdinal: new Int32Array(size),
|
|
310
|
+
peerStart: new Int32Array(peers ? size : 0),
|
|
311
|
+
peerEnd: new Int32Array(peers ? size : 0),
|
|
312
|
+
groupOrdinal: new Int32Array(peers ? size : 0),
|
|
312
313
|
groupStarts: [],
|
|
313
|
-
orderValues: Array.from(indexes.subarray(start, end), (index) => rows[index]?.[window.orderAliases[0]?.alias ?? ""] ?? null)
|
|
314
|
+
orderValues: range ? Array.from(indexes.subarray(start, end), (index) => rows[index]?.[window.orderAliases[0]?.alias ?? ""] ?? null) : []
|
|
314
315
|
};
|
|
315
316
|
let begin = 0;
|
|
316
|
-
for (let position = 1; position <= size; position += 1) {
|
|
317
|
+
for (let position = 1; peers && position <= size; position += 1) {
|
|
317
318
|
if (position % 2048 === 0)
|
|
318
319
|
yield;
|
|
319
320
|
if (position === size || orders.some((column) => column.compare(indexes[start + begin] ?? 0, indexes[start + position] ?? 0) !== 0)) {
|
|
@@ -26,7 +26,7 @@ export type StoreDescriptor = {
|
|
|
26
26
|
durability?: "relaxed" | "strict";
|
|
27
27
|
};
|
|
28
28
|
/** The cloneable subset of MinnowDatabaseOptions; function-valued seams stay worker-side. */
|
|
29
|
-
export type WireDatabaseOptions = Pick<MinnowDatabaseOptions, "compression" | "targetBlockBytes" | "rowsPerBlock" | "maxCommitRetries" | "spillOwnerLeaseMs" | "transactionOwnerLeaseMs" | "transactionIdleTimeoutMs" | "bufferPoolBytes" | "executionMemoryBudgetBytes" | "autoCollectDebtLimitCommits" | "autoCollect" | "autoCompact">;
|
|
29
|
+
export type WireDatabaseOptions = Pick<MinnowDatabaseOptions, "compression" | "targetBlockBytes" | "rowsPerBlock" | "maxCommitRetries" | "coordinateWrites" | "spillOwnerLeaseMs" | "transactionOwnerLeaseMs" | "transactionIdleTimeoutMs" | "bufferPoolBytes" | "executionMemoryBudgetBytes" | "autoCollectDebtLimitCommits" | "autoCollect" | "autoCompact">;
|
|
30
30
|
export interface DatabaseInitPayload {
|
|
31
31
|
store: StoreDescriptor;
|
|
32
32
|
options?: WireDatabaseOptions;
|
|
@@ -455,7 +455,9 @@ class DatabaseRpcServer {
|
|
|
455
455
|
resolveVersion = resolve;
|
|
456
456
|
rejectVersion = reject;
|
|
457
457
|
});
|
|
458
|
+
let snapshotSession;
|
|
458
459
|
const done = this.database.snapshot(async (session) => {
|
|
460
|
+
snapshotSession = session;
|
|
459
461
|
resolveVersion(session.version);
|
|
460
462
|
await new Promise((resolveRelease) => {
|
|
461
463
|
release = resolveRelease;
|
|
@@ -466,7 +468,7 @@ class DatabaseRpcServer {
|
|
|
466
468
|
const version = await versionReady;
|
|
467
469
|
if (release === void 0)
|
|
468
470
|
throw new Error("Snapshot scope did not install its release");
|
|
469
|
-
this.#publishHandle(handleId, { type: "snapshot", release, done });
|
|
471
|
+
this.#publishHandle(handleId, { type: "snapshot", session: snapshotSession, release, done });
|
|
470
472
|
return { handleId, version };
|
|
471
473
|
} catch (error) {
|
|
472
474
|
release?.();
|
|
@@ -494,6 +496,9 @@ class DatabaseRpcServer {
|
|
|
494
496
|
}
|
|
495
497
|
switch (handle.type) {
|
|
496
498
|
case "snapshot": {
|
|
499
|
+
if (method === "query") {
|
|
500
|
+
return this.#querySession(handle.session, args, context);
|
|
501
|
+
}
|
|
497
502
|
if (method !== "close")
|
|
498
503
|
throw new Error(`Unsupported snapshot method: ${method}`);
|
|
499
504
|
handle.release();
|
|
@@ -537,19 +542,21 @@ class DatabaseRpcServer {
|
|
|
537
542
|
}
|
|
538
543
|
}
|
|
539
544
|
}
|
|
545
|
+
async #querySession(session, args, context) {
|
|
546
|
+
const [sql, options, reportStats = false] = args;
|
|
547
|
+
return new ColumnarResult(encodeQueryResult(await session.query(sql, {
|
|
548
|
+
...options,
|
|
549
|
+
signal: context.signal,
|
|
550
|
+
...reportStats ? {
|
|
551
|
+
onStats: (stats) => {
|
|
552
|
+
this.scope.postMessage(rpcEvent(context.requestId, "stats", stats));
|
|
553
|
+
}
|
|
554
|
+
} : {}
|
|
555
|
+
})));
|
|
556
|
+
}
|
|
540
557
|
async #callWriteHandle(handleId, handle, method, args, context) {
|
|
541
|
-
if (method === "query")
|
|
542
|
-
|
|
543
|
-
return new ColumnarResult(encodeQueryResult(await handle.session.query(sql, {
|
|
544
|
-
...options,
|
|
545
|
-
signal: context.signal,
|
|
546
|
-
...reportStats ? {
|
|
547
|
-
onStats: (stats) => {
|
|
548
|
-
this.scope.postMessage(rpcEvent(context.requestId, "stats", stats));
|
|
549
|
-
}
|
|
550
|
-
} : {}
|
|
551
|
-
})));
|
|
552
|
-
}
|
|
558
|
+
if (method === "query")
|
|
559
|
+
return this.#querySession(handle.session, args, context);
|
|
553
560
|
if (method === "execute") {
|
|
554
561
|
const [sql, params] = args;
|
|
555
562
|
return handle.session.execute(sql, params);
|
|
@@ -665,15 +672,21 @@ class DatabaseRpcServer {
|
|
|
665
672
|
case "subscribe": {
|
|
666
673
|
const subscriptionId = this.#claimHandleId(args[0]);
|
|
667
674
|
const query = args[1];
|
|
675
|
+
const patches = args[2]?.patches === true;
|
|
668
676
|
let subscription;
|
|
669
677
|
try {
|
|
670
678
|
subscription = await handle.set.subscribe(query, {
|
|
671
679
|
onChange: (result, delivery) => {
|
|
672
|
-
const
|
|
680
|
+
const patch = patches && !delivery.initial && delivery.retained !== void 0;
|
|
681
|
+
const encoded = encodeQueryResult(patch ? {
|
|
682
|
+
columns: result.columns,
|
|
683
|
+
columnDomains: result.columnDomains,
|
|
684
|
+
rows: result.rows.filter((_, index) => (delivery.retained?.[index] ?? -1) < 0)
|
|
685
|
+
} : result);
|
|
673
686
|
const retained = delivery.retained?.slice();
|
|
674
687
|
if (retained !== void 0)
|
|
675
688
|
encoded.transfer.push(retained.buffer);
|
|
676
|
-
this.scope.postMessage(rpcEvent(subscriptionId, "change", {
|
|
689
|
+
this.scope.postMessage(rpcEvent(subscriptionId, patch ? "patch" : "change", {
|
|
677
690
|
result: encoded.payload,
|
|
678
691
|
delivery: retained === void 0 ? delivery : { ...delivery, retained }
|
|
679
692
|
}), { transfer: encoded.transfer });
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const anonymous = /* @__PURE__ */ new WeakMap();
|
|
2
|
+
const named = /* @__PURE__ */ new Map();
|
|
3
|
+
async function coordinateWrite(store, run, signal) {
|
|
4
|
+
signal.throwIfAborted();
|
|
5
|
+
const name = store.liveQueryChannelName;
|
|
6
|
+
let queue = name === void 0 ? anonymous.get(store) : named.get(name);
|
|
7
|
+
if (queue === void 0) {
|
|
8
|
+
queue = { tail: Promise.resolve() };
|
|
9
|
+
if (name === void 0)
|
|
10
|
+
anonymous.set(store, queue);
|
|
11
|
+
else
|
|
12
|
+
named.set(name, queue);
|
|
13
|
+
}
|
|
14
|
+
const locks = typeof navigator === "undefined" ? void 0 : navigator.locks;
|
|
15
|
+
const lockController = new AbortController();
|
|
16
|
+
let admitted = false;
|
|
17
|
+
const enter = () => {
|
|
18
|
+
signal.throwIfAborted();
|
|
19
|
+
admitted = true;
|
|
20
|
+
return run();
|
|
21
|
+
};
|
|
22
|
+
const operation = queue.tail.then(async () => {
|
|
23
|
+
signal.throwIfAborted();
|
|
24
|
+
return name !== void 0 && locks !== void 0 ? await locks.request(`minnowdb-write:${name}`, { signal: lockController.signal }, enter) : await enter();
|
|
25
|
+
});
|
|
26
|
+
const settled = operation.then(() => void 0, () => void 0);
|
|
27
|
+
queue.tail = settled;
|
|
28
|
+
void settled.then(() => {
|
|
29
|
+
if (queue.tail !== settled)
|
|
30
|
+
return;
|
|
31
|
+
if (name === void 0)
|
|
32
|
+
anonymous.delete(store);
|
|
33
|
+
else
|
|
34
|
+
named.delete(name);
|
|
35
|
+
});
|
|
36
|
+
let abort = () => void 0;
|
|
37
|
+
const cancelled = new Promise((_resolve, reject) => {
|
|
38
|
+
abort = () => {
|
|
39
|
+
if (!admitted) {
|
|
40
|
+
lockController.abort(signal.reason);
|
|
41
|
+
reject(signal.reason instanceof Error ? signal.reason : new Error("Write admission cancelled", { cause: signal.reason }));
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
45
|
+
});
|
|
46
|
+
try {
|
|
47
|
+
return await Promise.race([operation, cancelled]);
|
|
48
|
+
} finally {
|
|
49
|
+
signal.removeEventListener("abort", abort);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export {
|
|
53
|
+
coordinateWrite
|
|
54
|
+
};
|
|
@@ -1791,11 +1791,9 @@ class IndexedDbBlockStore {
|
|
|
1791
1791
|
const transaction = this.#transaction("catalog", "readonly");
|
|
1792
1792
|
const store = transaction.objectStore("catalog");
|
|
1793
1793
|
const records = [];
|
|
1794
|
-
await
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
}
|
|
1798
|
-
});
|
|
1794
|
+
await visitObjectStoreReadBatches(store, (value, key) => {
|
|
1795
|
+
records.push(asTableRecord(value, storageKeyLocation(key)));
|
|
1796
|
+
}, { prefix: TABLE_ID_PREFIX });
|
|
1799
1797
|
await transactionDone(transaction);
|
|
1800
1798
|
return records.sort((left, right) => left.name.localeCompare(right.name));
|
|
1801
1799
|
}
|
|
@@ -3990,7 +3988,7 @@ class IndexedDbBlockStore {
|
|
|
3990
3988
|
const currentVersionValue = await requestResult(catalog.get(CURRENT_MANIFEST_KEY));
|
|
3991
3989
|
const currentVersion = asOptionalManifestVersion(currentVersionValue, CURRENT_MANIFEST_KEY) ?? null;
|
|
3992
3990
|
const leaseCutoff = Date.parse(current.leaseCutoff);
|
|
3993
|
-
await
|
|
3991
|
+
const pinnedVersions = await garbageCollectionPinsInTransaction(transaction, currentVersion, leaseCutoff);
|
|
3994
3992
|
const prunedManifestVersions = [];
|
|
3995
3993
|
const alreadyPrunedManifestVersions = [];
|
|
3996
3994
|
const retainedManifestVersions = [];
|
|
@@ -4018,7 +4016,7 @@ class IndexedDbBlockStore {
|
|
|
4018
4016
|
missingManifestVersions.push(version);
|
|
4019
4017
|
else if (asStoredManifestRecord(manifestValue, version).prunedAt !== void 0) {
|
|
4020
4018
|
alreadyPrunedManifestVersions.push(version);
|
|
4021
|
-
} else if (
|
|
4019
|
+
} else if (pinnedVersions.has(version))
|
|
4022
4020
|
retainedManifestVersions.push(version);
|
|
4023
4021
|
else {
|
|
4024
4022
|
const previous = asStoredManifestRecord(manifestValue, version);
|
|
@@ -4031,12 +4029,12 @@ class IndexedDbBlockStore {
|
|
|
4031
4029
|
remaining -= 1;
|
|
4032
4030
|
}
|
|
4033
4031
|
const prunedManifestVersionSet = new Set(prunedManifestVersions);
|
|
4034
|
-
await assertRemainingManifestRecordsAvailable(transaction, prunedManifestVersionSet);
|
|
4032
|
+
const readableVersions = await assertRemainingManifestRecordsAvailable(transaction, prunedManifestVersionSet);
|
|
4035
4033
|
let segmentIndex = current.cursor.segmentIndex;
|
|
4036
4034
|
const segmentIdsToExamine = manifestIndex === current.candidateManifestVersions.length ? current.candidateSegmentIds.slice(segmentIndex, segmentIndex + remaining) : [];
|
|
4037
4035
|
const blockCapacity = Math.max(0, remaining - segmentIdsToExamine.length);
|
|
4038
4036
|
const blockIdsToExamine = manifestIndex === current.candidateManifestVersions.length && segmentIndex + segmentIdsToExamine.length === current.candidateSegmentIds.length ? current.candidateBlockIds.slice(current.cursor.blockIndex, current.cursor.blockIndex + blockCapacity) : [];
|
|
4039
|
-
const roots = await collectBoundedPhysicalRootsInTransaction(transaction, segmentIdsToExamine, blockIdsToExamine,
|
|
4037
|
+
const roots = await collectBoundedPhysicalRootsInTransaction(transaction, segmentIdsToExamine, blockIdsToExamine, readableVersions);
|
|
4040
4038
|
while (remaining > 0 && manifestIndex === current.candidateManifestVersions.length && segmentIndex < current.candidateSegmentIds.length) {
|
|
4041
4039
|
const id = current.candidateSegmentIds[segmentIndex];
|
|
4042
4040
|
if (id === void 0)
|
|
@@ -4065,7 +4063,7 @@ class IndexedDbBlockStore {
|
|
|
4065
4063
|
if (remaining > 0 && manifestIndex === current.candidateManifestVersions.length && segmentIndex === current.candidateSegmentIds.length && blockIndex === current.candidateBlockIds.length) {
|
|
4066
4064
|
const candidates = new Set(current.candidateTransactionIds.slice(transactionIndex));
|
|
4067
4065
|
const segmentOwners = /* @__PURE__ */ new Set();
|
|
4068
|
-
await
|
|
4066
|
+
await visitObjectStoreReadBatches(segmentStore, (value2) => {
|
|
4069
4067
|
const owner = asSegmentRecord(value2).transactionId;
|
|
4070
4068
|
if (candidates.has(owner))
|
|
4071
4069
|
segmentOwners.add(owner);
|
|
@@ -6309,6 +6307,7 @@ class IndexedDbBlockStore {
|
|
|
6309
6307
|
if (this.#closed)
|
|
6310
6308
|
throw new Error("This IndexedDB store connection is closed");
|
|
6311
6309
|
const transaction = mode === "readwrite" ? this.#db.transaction(stores, mode, { durability: this.#durability }) : this.#db.transaction(stores, mode);
|
|
6310
|
+
void transactionDone(transaction).catch(() => void 0);
|
|
6312
6311
|
const storeList = typeof stores === "string" ? [stores] : stores;
|
|
6313
6312
|
if (mode === "readwrite" && !options.allowSnapshotImport && storeList.includes("catalog")) {
|
|
6314
6313
|
const catalog = transaction.objectStore("catalog");
|
|
@@ -6378,6 +6377,7 @@ async function validateCurrentIndexedDbSchema(database) {
|
|
|
6378
6377
|
let transaction;
|
|
6379
6378
|
try {
|
|
6380
6379
|
transaction = database.transaction([...indexedDbStoreNames], "readonly");
|
|
6380
|
+
void transactionDone(transaction).catch(() => void 0);
|
|
6381
6381
|
for (const storeName of indexedDbStoreNames) {
|
|
6382
6382
|
const store = transaction.objectStore(storeName);
|
|
6383
6383
|
if (store.keyPath !== null || store.autoIncrement) {
|
|
@@ -6674,6 +6674,44 @@ function visitObjectStoreSequentially(store, visit, direction = "next") {
|
|
|
6674
6674
|
};
|
|
6675
6675
|
});
|
|
6676
6676
|
}
|
|
6677
|
+
async function visitObjectStoreReadBatches(store, visit, partition) {
|
|
6678
|
+
const kind = partition !== void 0 && "kind" in partition ? partition.kind : void 0;
|
|
6679
|
+
const prefix = partition !== void 0 && "prefix" in partition ? partition.prefix : void 0;
|
|
6680
|
+
if (typeof IDBKeyRange === "undefined") {
|
|
6681
|
+
if (kind === MANIFEST_BLOCK) {
|
|
6682
|
+
await visitManifestBlockRecords(store, (record) => Promise.resolve(visit(record, [kind, record.blockId])));
|
|
6683
|
+
return;
|
|
6684
|
+
}
|
|
6685
|
+
await visitObjectStoreSequentially(store, (value, key) => {
|
|
6686
|
+
if (prefix !== void 0) {
|
|
6687
|
+
if (typeof key === "string" && key.startsWith(prefix))
|
|
6688
|
+
return visit(value, key);
|
|
6689
|
+
} else if (kind === void 0 || compareStructuredKind(key, kind) === 0)
|
|
6690
|
+
return visit(value, key);
|
|
6691
|
+
});
|
|
6692
|
+
return;
|
|
6693
|
+
}
|
|
6694
|
+
const limit = 128;
|
|
6695
|
+
const upper = prefix !== void 0 ? prefix.slice(0, -1) + String.fromCharCode(prefix.charCodeAt(prefix.length - 1) + 1) : kind === void 0 ? void 0 : [kind + "\0"];
|
|
6696
|
+
let lower = prefix ?? (kind === void 0 ? void 0 : [kind]);
|
|
6697
|
+
let exclusive = false;
|
|
6698
|
+
for (; ; ) {
|
|
6699
|
+
const range = lower === void 0 ? void 0 : upper === void 0 ? IDBKeyRange.lowerBound(lower, exclusive) : IDBKeyRange.bound(lower, upper, exclusive, true);
|
|
6700
|
+
const [keys, values] = await Promise.all([
|
|
6701
|
+
requestResult(store.getAllKeys(range, limit)),
|
|
6702
|
+
requestResult(store.getAll(range, limit))
|
|
6703
|
+
]);
|
|
6704
|
+
if (keys.length !== values.length)
|
|
6705
|
+
throw corruption(store.name, "batch keys do not match values");
|
|
6706
|
+
await Promise.all(keys.map(async (key, index) => {
|
|
6707
|
+
await visit(values[index], key);
|
|
6708
|
+
}));
|
|
6709
|
+
if (keys.length < limit)
|
|
6710
|
+
return;
|
|
6711
|
+
lower = keys.at(-1);
|
|
6712
|
+
exclusive = true;
|
|
6713
|
+
}
|
|
6714
|
+
}
|
|
6677
6715
|
function visitIndexPartitionSequentially(index, key, visit) {
|
|
6678
6716
|
return new Promise((resolve, reject) => {
|
|
6679
6717
|
const request = index.openCursor(key);
|
|
@@ -6902,16 +6940,19 @@ function transactionFailure(transaction) {
|
|
|
6902
6940
|
transaction.addEventListener("error", () => reject(transaction.error ?? new Error("Transaction failed")), { once: true });
|
|
6903
6941
|
});
|
|
6904
6942
|
}
|
|
6943
|
+
const transactionCompletions = /* @__PURE__ */ new WeakMap();
|
|
6905
6944
|
function transactionDone(transaction) {
|
|
6906
|
-
|
|
6945
|
+
const existing = transactionCompletions.get(transaction);
|
|
6946
|
+
if (existing !== void 0)
|
|
6947
|
+
return existing;
|
|
6948
|
+
const done = new Promise((resolve, reject) => {
|
|
6907
6949
|
transaction.addEventListener("complete", () => resolve(), { once: true });
|
|
6908
6950
|
transaction.addEventListener("abort", () => reject(transaction.error ?? new Error("Transaction aborted")), {
|
|
6909
6951
|
once: true
|
|
6910
6952
|
});
|
|
6911
|
-
transaction.addEventListener("error", () => reject(transaction.error ?? new Error("Transaction failed")), {
|
|
6912
|
-
once: true
|
|
6913
|
-
});
|
|
6914
6953
|
});
|
|
6954
|
+
transactionCompletions.set(transaction, done);
|
|
6955
|
+
return done;
|
|
6915
6956
|
}
|
|
6916
6957
|
async function ignoreAbort(transaction) {
|
|
6917
6958
|
try {
|
|
@@ -8367,13 +8408,21 @@ async function resolveManifestBlockSetInTransaction(catalog, version) {
|
|
|
8367
8408
|
const blockIds = /* @__PURE__ */ new Set();
|
|
8368
8409
|
if (version === null)
|
|
8369
8410
|
return blockIds;
|
|
8370
|
-
await
|
|
8411
|
+
await visitManifestBlockReadBatches(catalog, (record) => {
|
|
8371
8412
|
if (manifestBlockVisibleAt(record, version))
|
|
8372
8413
|
blockIds.add(record.blockId);
|
|
8373
8414
|
return void 0;
|
|
8374
8415
|
});
|
|
8375
8416
|
return blockIds;
|
|
8376
8417
|
}
|
|
8418
|
+
async function visitManifestBlockReadBatches(catalog, visit) {
|
|
8419
|
+
await visitObjectStoreReadBatches(catalog, (value, key) => {
|
|
8420
|
+
if (!Array.isArray(key) || key.length !== 2 || typeof key[1] !== "string") {
|
|
8421
|
+
throw corruption(MANIFEST_BLOCK, "record key is invalid");
|
|
8422
|
+
}
|
|
8423
|
+
visit(asManifestBlockRecord(value, key[1]));
|
|
8424
|
+
}, { kind: MANIFEST_BLOCK });
|
|
8425
|
+
}
|
|
8377
8426
|
function visitManifestBlockRecords(catalog, visit) {
|
|
8378
8427
|
return new Promise((resolve, reject) => {
|
|
8379
8428
|
const request = catalog.openCursor();
|
|
@@ -9263,19 +9312,24 @@ async function compactionJobRemovalPreservesProvenance(transaction, removedJob)
|
|
|
9263
9312
|
}
|
|
9264
9313
|
return true;
|
|
9265
9314
|
}
|
|
9266
|
-
async function
|
|
9267
|
-
|
|
9268
|
-
|
|
9315
|
+
async function garbageCollectionPinsInTransaction(transaction, currentVersion, leaseCutoff) {
|
|
9316
|
+
const versions = /* @__PURE__ */ new Set();
|
|
9317
|
+
const pin = (version) => {
|
|
9318
|
+
if (version !== null)
|
|
9319
|
+
versions.add(version);
|
|
9320
|
+
};
|
|
9321
|
+
pin(currentVersion);
|
|
9322
|
+
await visitObjectStoreReadBatches(transaction.objectStore("transactions"), (value) => {
|
|
9269
9323
|
const record = asTransactionRecord(value);
|
|
9270
9324
|
if (record.status === "active") {
|
|
9271
|
-
|
|
9325
|
+
pin(record.snapshotVersion);
|
|
9272
9326
|
}
|
|
9273
9327
|
});
|
|
9274
|
-
await visitObjectStoreSequentially(transaction.objectStore("leases"),
|
|
9328
|
+
await visitObjectStoreSequentially(transaction.objectStore("leases"), (value) => {
|
|
9275
9329
|
const lease = asLeaseRecord(value);
|
|
9276
9330
|
const expiresAt = Date.parse(lease.expiresAt);
|
|
9277
9331
|
if (!Number.isFinite(expiresAt) || expiresAt > leaseCutoff) {
|
|
9278
|
-
|
|
9332
|
+
pin(lease.manifestVersion);
|
|
9279
9333
|
}
|
|
9280
9334
|
});
|
|
9281
9335
|
await visitObjectStoreSequentially(transaction.objectStore("gc"), async (value, key) => {
|
|
@@ -9284,7 +9338,7 @@ async function assertGarbageCollectionPinsAvailableInTransaction(transaction, cu
|
|
|
9284
9338
|
return;
|
|
9285
9339
|
if (isTerminalCompactionJob(job))
|
|
9286
9340
|
return;
|
|
9287
|
-
|
|
9341
|
+
pin(job.sourceManifestVersion);
|
|
9288
9342
|
if (job.transactionId === null)
|
|
9289
9343
|
return;
|
|
9290
9344
|
const transactionValue = await requestResult(transaction.objectStore("transactions").get(job.transactionId));
|
|
@@ -9296,8 +9350,11 @@ async function assertGarbageCollectionPinsAvailableInTransaction(transaction, cu
|
|
|
9296
9350
|
if (linkedTransaction.committedVersion === null) {
|
|
9297
9351
|
throw new Error(`Committed transaction has no manifest version: ${linkedTransaction.id}`);
|
|
9298
9352
|
}
|
|
9299
|
-
|
|
9353
|
+
pin(linkedTransaction.committedVersion);
|
|
9300
9354
|
});
|
|
9355
|
+
for (const version of versions)
|
|
9356
|
+
await assertSnapshotAvailableInTransaction(transaction, version);
|
|
9357
|
+
return versions;
|
|
9301
9358
|
}
|
|
9302
9359
|
async function assertPendingArtifactsAvailableInTransaction(transaction, record, validateBlocks = true, validateSegments = true, validateCurrentTable = false) {
|
|
9303
9360
|
const [blockKeys, segmentValues] = await Promise.all([
|
|
@@ -9349,27 +9406,25 @@ async function assertPendingArtifactsAvailableInTransaction(transaction, record,
|
|
|
9349
9406
|
}
|
|
9350
9407
|
async function assertRemainingManifestRecordsAvailable(transaction, newlyPrunedVersions) {
|
|
9351
9408
|
const readableVersions = [];
|
|
9352
|
-
await
|
|
9409
|
+
await visitObjectStoreReadBatches(transaction.objectStore("manifests"), (value, key) => {
|
|
9353
9410
|
if (typeof key !== "number")
|
|
9354
9411
|
throw corruption("manifests", "record key is invalid");
|
|
9355
9412
|
const record = asStoredManifestRecord(value, key);
|
|
9356
|
-
if (record.prunedAt
|
|
9357
|
-
|
|
9358
|
-
|
|
9413
|
+
if (record.prunedAt === void 0 && !newlyPrunedVersions.has(record.version)) {
|
|
9414
|
+
readableVersions.push(record.version);
|
|
9415
|
+
}
|
|
9359
9416
|
});
|
|
9360
|
-
|
|
9361
|
-
|
|
9417
|
+
await visitObjectStoreReadBatches(transaction.objectStore("catalog"), async (value, key) => {
|
|
9418
|
+
if (!Array.isArray(key) || key.length !== 2 || typeof key[1] !== "string") {
|
|
9419
|
+
throw corruption(MANIFEST_BLOCK, "record key is invalid");
|
|
9420
|
+
}
|
|
9421
|
+
const record = asManifestBlockRecord(value, key[1]);
|
|
9362
9422
|
const version = firstOverlappingManifestVersion(record, readableVersions);
|
|
9363
|
-
if (version !== void 0)
|
|
9364
|
-
referenced.set(record.blockId, version);
|
|
9365
|
-
return void 0;
|
|
9366
|
-
});
|
|
9367
|
-
const blockStore = transaction.objectStore("blocks");
|
|
9368
|
-
for (const [id, version] of referenced) {
|
|
9369
|
-
if (await requestResult(blockStore.getKey(id)) === void 0) {
|
|
9423
|
+
if (version !== void 0 && await requestResult(transaction.objectStore("blocks").getKey(record.blockId)) === void 0) {
|
|
9370
9424
|
throw new SnapshotManifestMissingError(version);
|
|
9371
9425
|
}
|
|
9372
|
-
}
|
|
9426
|
+
}, { kind: MANIFEST_BLOCK });
|
|
9427
|
+
return readableVersions;
|
|
9373
9428
|
}
|
|
9374
9429
|
function compactionJobKey(id) {
|
|
9375
9430
|
return `${COMPACTION_JOB_KEY_PREFIX}${id}`;
|
|
@@ -9774,52 +9829,25 @@ async function assertGarbageCollectionCandidateProvenanceInTransaction(transacti
|
|
|
9774
9829
|
throw new Error(`Garbage collection segment candidate has no persisted provenance: ${id}`);
|
|
9775
9830
|
}
|
|
9776
9831
|
}
|
|
9777
|
-
async function
|
|
9778
|
-
if (currentVersion === version)
|
|
9779
|
-
return true;
|
|
9780
|
-
const transactionPinned = await visitObjectStoreSequentially(transaction.objectStore("transactions"), (value) => {
|
|
9781
|
-
const record = asTransactionRecord(value);
|
|
9782
|
-
return record.status === "active" && record.snapshotVersion === version;
|
|
9783
|
-
});
|
|
9784
|
-
if (transactionPinned)
|
|
9785
|
-
return true;
|
|
9786
|
-
const leasePinned = await visitObjectStoreSequentially(transaction.objectStore("leases"), (value) => {
|
|
9787
|
-
const lease = asLeaseRecord(value);
|
|
9788
|
-
const expiresAt = Date.parse(lease.expiresAt);
|
|
9789
|
-
return lease.manifestVersion === version && (!Number.isFinite(expiresAt) || expiresAt > leaseCutoff);
|
|
9790
|
-
});
|
|
9791
|
-
if (leasePinned)
|
|
9792
|
-
return true;
|
|
9793
|
-
return visitObjectStoreSequentially(transaction.objectStore("gc"), async (value, key) => {
|
|
9794
|
-
const job = asCompactionJobAtMaintenanceKey(value, key);
|
|
9795
|
-
if (job === void 0)
|
|
9796
|
-
return false;
|
|
9797
|
-
if (isTerminalCompactionJob(job))
|
|
9798
|
-
return false;
|
|
9799
|
-
if (job.sourceManifestVersion === version)
|
|
9800
|
-
return true;
|
|
9801
|
-
if (job.transactionId === null)
|
|
9802
|
-
return false;
|
|
9803
|
-
const transactionValue = await requestResult(transaction.objectStore("transactions").get(job.transactionId));
|
|
9804
|
-
if (transactionValue === void 0)
|
|
9805
|
-
return false;
|
|
9806
|
-
const linkedTransaction = asTransactionRecord(transactionValue);
|
|
9807
|
-
if (linkedTransaction.status !== "committed")
|
|
9808
|
-
return false;
|
|
9809
|
-
if (linkedTransaction.committedVersion === null) {
|
|
9810
|
-
throw new Error(`Committed transaction has no manifest version: ${linkedTransaction.id}`);
|
|
9811
|
-
}
|
|
9812
|
-
return linkedTransaction.committedVersion === version;
|
|
9813
|
-
});
|
|
9814
|
-
}
|
|
9815
|
-
async function collectBoundedPhysicalRootsInTransaction(transaction, candidateSegmentIds, candidateBlockIds, newlyPrunedVersions) {
|
|
9832
|
+
async function collectBoundedPhysicalRootsInTransaction(transaction, candidateSegmentIds, candidateBlockIds, readableVersions) {
|
|
9816
9833
|
const candidateSegments = new Set(candidateSegmentIds);
|
|
9817
9834
|
const candidateBlocks = new Set(candidateBlockIds);
|
|
9818
9835
|
const directSegmentRoots = /* @__PURE__ */ new Set();
|
|
9819
|
-
|
|
9836
|
+
const ownerBlockRoots = /* @__PURE__ */ new Set();
|
|
9837
|
+
let ownerBlockRootsComplete = true;
|
|
9838
|
+
const rememberOwnerBlocks = (ids) => {
|
|
9839
|
+
for (const id of ids) {
|
|
9840
|
+
if (ownerBlockRoots.size < 4096)
|
|
9841
|
+
ownerBlockRoots.add(id);
|
|
9842
|
+
else if (!ownerBlockRoots.has(id))
|
|
9843
|
+
ownerBlockRootsComplete = false;
|
|
9844
|
+
}
|
|
9845
|
+
};
|
|
9846
|
+
await visitIndexPartitionSequentially(transaction.objectStore("transactions").index(TRANSACTION_STATUS_INDEX), "active", (value) => {
|
|
9820
9847
|
const record = asTransactionRecord(value);
|
|
9821
9848
|
if (record.status !== "active")
|
|
9822
9849
|
return;
|
|
9850
|
+
rememberOwnerBlocks(record.pendingBlockIds);
|
|
9823
9851
|
for (const id of record.pendingSegmentIds)
|
|
9824
9852
|
if (candidateSegments.has(id))
|
|
9825
9853
|
directSegmentRoots.add(id);
|
|
@@ -9830,6 +9858,8 @@ async function collectBoundedPhysicalRootsInTransaction(transaction, candidateSe
|
|
|
9830
9858
|
return;
|
|
9831
9859
|
if (isTerminalCompactionJob(job))
|
|
9832
9860
|
return;
|
|
9861
|
+
rememberOwnerBlocks(job.sourceBlockIds);
|
|
9862
|
+
rememberOwnerBlocks(job.outputBlockIds);
|
|
9833
9863
|
for (const id of job.sourceSegmentIds)
|
|
9834
9864
|
if (candidateSegments.has(id))
|
|
9835
9865
|
directSegmentRoots.add(id);
|
|
@@ -9843,20 +9873,24 @@ async function collectBoundedPhysicalRootsInTransaction(transaction, candidateSe
|
|
|
9843
9873
|
const cached = directBlockRootCache.get(id);
|
|
9844
9874
|
if (cached !== void 0)
|
|
9845
9875
|
return cached;
|
|
9846
|
-
let rooted =
|
|
9847
|
-
|
|
9848
|
-
|
|
9849
|
-
|
|
9850
|
-
|
|
9851
|
-
rooted = await visitObjectStoreSequentially(transaction.objectStore("gc"), (value, key) => {
|
|
9852
|
-
const job = asCompactionJobAtMaintenanceKey(value, key);
|
|
9853
|
-
if (job === void 0)
|
|
9854
|
-
return false;
|
|
9855
|
-
return !isTerminalCompactionJob(job) && (job.sourceBlockIds.includes(id) || job.outputBlockIds.includes(id));
|
|
9876
|
+
let rooted = ownerBlockRoots.has(id);
|
|
9877
|
+
if (!rooted && !ownerBlockRootsComplete) {
|
|
9878
|
+
rooted = await visitIndexPartitionSequentially(transaction.objectStore("transactions").index(TRANSACTION_STATUS_INDEX), "active", (value) => {
|
|
9879
|
+
const record = asTransactionRecord(value);
|
|
9880
|
+
return record.status === "active" && record.pendingBlockIds.includes(id);
|
|
9856
9881
|
});
|
|
9882
|
+
if (!rooted) {
|
|
9883
|
+
rooted = await visitObjectStoreSequentially(transaction.objectStore("gc"), (value, key) => {
|
|
9884
|
+
const job = asCompactionJobAtMaintenanceKey(value, key);
|
|
9885
|
+
if (job === void 0)
|
|
9886
|
+
return false;
|
|
9887
|
+
return !isTerminalCompactionJob(job) && (job.sourceBlockIds.includes(id) || job.outputBlockIds.includes(id));
|
|
9888
|
+
});
|
|
9889
|
+
}
|
|
9857
9890
|
}
|
|
9858
9891
|
if (!rooted) {
|
|
9859
|
-
|
|
9892
|
+
const value = await requestResult(transaction.objectStore("catalog").get(manifestBlockKey(id)));
|
|
9893
|
+
rooted = value !== void 0 && firstOverlappingManifestVersion(asManifestBlockRecord(value, id), readableVersions) !== void 0;
|
|
9860
9894
|
}
|
|
9861
9895
|
if (directBlockRootCache.size < 4096)
|
|
9862
9896
|
directBlockRootCache.set(id, rooted);
|
|
@@ -9893,6 +9927,17 @@ async function collectBoundedPhysicalRootsInTransaction(transaction, candidateSe
|
|
|
9893
9927
|
rootedBlockIds.add(id);
|
|
9894
9928
|
for (const id of directSegmentRoots)
|
|
9895
9929
|
rootedSegmentIds.add(id);
|
|
9930
|
+
if (candidateBlocks.size > 0) {
|
|
9931
|
+
await visitObjectStoreReadBatches(segmentStore, (value) => {
|
|
9932
|
+
const segment = asSegmentRecord(value);
|
|
9933
|
+
if (candidateSegments.has(segment.id) && !rootedSegmentIds.has(segment.id))
|
|
9934
|
+
return;
|
|
9935
|
+
for (const id of segmentBlockIds(segment)) {
|
|
9936
|
+
if (candidateBlocks.has(id))
|
|
9937
|
+
rootedBlockIds.add(id);
|
|
9938
|
+
}
|
|
9939
|
+
});
|
|
9940
|
+
}
|
|
9896
9941
|
return { blockIds: rootedBlockIds, segmentIds: rootedSegmentIds };
|
|
9897
9942
|
}
|
|
9898
9943
|
function segmentBlockIds(segment) {
|
|
@@ -10929,7 +10974,7 @@ async function assertPinnedHistoryAdmission(transaction, input) {
|
|
|
10929
10974
|
return;
|
|
10930
10975
|
let pinnedBlockCount = 0;
|
|
10931
10976
|
let pinnedBytes = 0;
|
|
10932
|
-
await
|
|
10977
|
+
await visitManifestBlockReadBatches(transaction.objectStore("catalog"), (record) => {
|
|
10933
10978
|
const removedVersion = input.prospectiveRemovedBlockIds?.has(record.blockId) ? input.currentVersion : record.removedVersion;
|
|
10934
10979
|
if (removedVersion === null)
|
|
10935
10980
|
return void 0;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { OpfsBlockStore, deleteOpfsDatabase, type OpfsBlockStoreOptions } from "./store.js";
|
|
2
|
-
export { OpfsUncertainOutcomeError } from "../types.js";
|
|
2
|
+
export { OpfsCoordinationError, OpfsDatabaseInUseError, OpfsUncertainOutcomeError, } from "../types.js";
|
|
3
3
|
export { OpfsTree, type ReadFileOptions, type WriteFileOptions } from "./files.js";
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { OpfsBlockStore, deleteOpfsDatabase } from "./store.js";
|
|
2
|
-
import { OpfsUncertainOutcomeError } from "../types.js";
|
|
2
|
+
import { OpfsCoordinationError, OpfsDatabaseInUseError, OpfsUncertainOutcomeError } from "../types.js";
|
|
3
3
|
import { OpfsTree } from "./files.js";
|
|
4
4
|
export {
|
|
5
5
|
OpfsBlockStore,
|
|
6
|
+
OpfsCoordinationError,
|
|
7
|
+
OpfsDatabaseInUseError,
|
|
6
8
|
OpfsTree,
|
|
7
9
|
OpfsUncertainOutcomeError,
|
|
8
10
|
deleteOpfsDatabase
|