@minnowdb/core 0.7.10 → 0.9.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/dist/engine/artifact-cache.js +3 -2
- package/dist/engine/buffered-writer.js +12 -2
- package/dist/engine/client.d.ts +9 -0
- package/dist/engine/client.js +112 -20
- package/dist/engine/database.d.ts +5 -3
- package/dist/engine/database.js +2199 -1675
- package/dist/engine/errors.d.ts +21 -2
- package/dist/engine/errors.js +30 -1
- 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 +264 -0
- package/dist/engine/live-patch.d.ts +21 -0
- package/dist/engine/live-patch.js +31 -0
- package/dist/engine/live.d.ts +14 -0
- package/dist/engine/live.js +146 -142
- package/dist/engine/optimizer.js +11 -6
- package/dist/engine/query-cache.js +19 -15
- package/dist/engine/query-generations.js +61 -0
- package/dist/engine/query-identity.js +41 -0
- package/dist/engine/query.d.ts +7 -13
- package/dist/engine/query.js +298 -435
- package/dist/engine/result-state.d.ts +7 -0
- package/dist/engine/result-state.js +15 -0
- package/dist/engine/sql-domains.js +121 -0
- package/dist/engine/sql-semantics.js +7 -4
- package/dist/engine/typed-live.js +28 -20
- package/dist/engine/vector.d.ts +4 -0
- package/dist/engine/vector.js +14 -14
- package/dist/engine/windows.d.ts +12 -0
- package/dist/engine/windows.js +387 -0
- 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/plan/model.d.ts +1 -1
- 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 +119 -43
- 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/postgres-feature-profile.json +6 -1
- package/sql-feature-matrix.json +20 -27
- 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-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/result-wire.d.ts +0 -70
- package/dist/engine/sort-keys.d.ts +0 -73
- package/dist/engine/sql-domains.d.ts +0 -93
- 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
|
@@ -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
|
|
@@ -1774,7 +1774,8 @@ class OpfsLeader {
|
|
|
1774
1774
|
}
|
|
1775
1775
|
async getBlock(id) {
|
|
1776
1776
|
validateId(id);
|
|
1777
|
-
|
|
1777
|
+
while (this.#poisoned)
|
|
1778
|
+
await this.#healthy();
|
|
1778
1779
|
const placement = this.#blockIndex.get(id);
|
|
1779
1780
|
if (placement === void 0)
|
|
1780
1781
|
return void 0;
|
|
@@ -1784,13 +1785,15 @@ class OpfsLeader {
|
|
|
1784
1785
|
assertStorageBulkReadItems(ids, "Block read");
|
|
1785
1786
|
for (const id of ids)
|
|
1786
1787
|
validateId(id);
|
|
1787
|
-
|
|
1788
|
+
while (this.#poisoned)
|
|
1789
|
+
await this.#healthy();
|
|
1788
1790
|
const placements = ids.map((id) => this.#blockIndex.get(id));
|
|
1789
1791
|
assertBlockReadBatchByteLimit(placements);
|
|
1790
1792
|
return Promise.all(placements.map(async (placement) => placement === void 0 ? void 0 : await this.#pool.read(placement)));
|
|
1791
1793
|
}
|
|
1792
1794
|
async readManifestBlock(version, id) {
|
|
1793
|
-
|
|
1795
|
+
while (this.#poisoned)
|
|
1796
|
+
await this.#healthy();
|
|
1794
1797
|
if (this.#core.hasManifestBlocks(version, [id])[0] !== true)
|
|
1795
1798
|
return void 0;
|
|
1796
1799
|
const placement = this.#blockIndex.get(id);
|
|
@@ -1804,18 +1807,21 @@ class OpfsLeader {
|
|
|
1804
1807
|
}
|
|
1805
1808
|
}
|
|
1806
1809
|
async hasManifestBlocks(version, ids) {
|
|
1807
|
-
|
|
1810
|
+
while (this.#poisoned)
|
|
1811
|
+
await this.#healthy();
|
|
1808
1812
|
if (ids.length > MAX_MANIFEST_BLOCK_PRESENCE_IDS) {
|
|
1809
1813
|
throw new RangeError(`Manifest block presence accepts at most ${String(MAX_MANIFEST_BLOCK_PRESENCE_IDS)} ids`);
|
|
1810
1814
|
}
|
|
1811
1815
|
return this.#core.hasManifestBlocks(version, ids);
|
|
1812
1816
|
}
|
|
1813
1817
|
async listManifestBlockPage(input) {
|
|
1814
|
-
|
|
1818
|
+
while (this.#poisoned)
|
|
1819
|
+
await this.#healthy();
|
|
1815
1820
|
return this.#core.listManifestBlockPage(input);
|
|
1816
1821
|
}
|
|
1817
1822
|
async listRetiredManifestBlockPage(input) {
|
|
1818
|
-
|
|
1823
|
+
while (this.#poisoned)
|
|
1824
|
+
await this.#healthy();
|
|
1819
1825
|
return this.#core.listRetiredManifestBlockPage(input);
|
|
1820
1826
|
}
|
|
1821
1827
|
async putTempRunPage(page) {
|
|
@@ -1894,7 +1900,8 @@ class OpfsLeader {
|
|
|
1894
1900
|
});
|
|
1895
1901
|
}
|
|
1896
1902
|
async _readCoreGenerated(method, args) {
|
|
1897
|
-
|
|
1903
|
+
while (this.#poisoned)
|
|
1904
|
+
await this.#healthy();
|
|
1898
1905
|
const read = Reflect.get(this.#core, method);
|
|
1899
1906
|
return Reflect.apply(read, this.#core, args);
|
|
1900
1907
|
}
|
|
@@ -1909,7 +1916,8 @@ class OpfsLeader {
|
|
|
1909
1916
|
return removed;
|
|
1910
1917
|
}
|
|
1911
1918
|
async listTempOwnerIdsPage(afterOwnerId, limit) {
|
|
1912
|
-
|
|
1919
|
+
while (this.#poisoned)
|
|
1920
|
+
await this.#healthy();
|
|
1913
1921
|
this.#core.listTempOwnerIdsPage(afterOwnerId, limit, []);
|
|
1914
1922
|
const physicalOwnerIds = [];
|
|
1915
1923
|
for await (const name of this.#tree.iterateNames(["temp"])) {
|
|
@@ -1923,10 +1931,13 @@ class OpfsLeader {
|
|
|
1923
1931
|
if (physicalOwnerIds.length > limit)
|
|
1924
1932
|
physicalOwnerIds.pop();
|
|
1925
1933
|
}
|
|
1934
|
+
while (this.#poisoned)
|
|
1935
|
+
await this.#healthy();
|
|
1926
1936
|
return this.#core.listTempOwnerIdsPage(afterOwnerId, limit, physicalOwnerIds);
|
|
1927
1937
|
}
|
|
1928
1938
|
async listExpiredTempOwnerPage(expiresAtCutoff, afterCursor, limit) {
|
|
1929
|
-
|
|
1939
|
+
while (this.#poisoned)
|
|
1940
|
+
await this.#healthy();
|
|
1930
1941
|
return this.#core.listExpiredTempOwnerPage(expiresAtCutoff, afterCursor, limit);
|
|
1931
1942
|
}
|
|
1932
1943
|
async addTable(record, options = {}) {
|
package/dist/storage/opfs/rpc.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BlockReadBatchTooLargeError, CompactionBacklogError, CompactionJobConflictError, GarbageCollectionJobConflictError, IndexedDbSchemaUpgradeBlockedError, LeaseConflictError, LeaseExpiredError, LeaseOwnerConflictError, OpfsUncertainOutcomeError, PostingBuildConflictError, SnapshotImportConflictError, SnapshotManifestMissingError, StorageCorruptionError, StorageFormatVersionError, StorageResourceLimitError, TableInUseError, TableRecordConflictError, TempOwnerConflictError, TransactionRecordConflictError, UniqueKeyBuildConflictError, UniqueKeyConflictError, UniqueIndexCoverageError, WriteConflictError, SchemaConflictError } from "../types.js";
|
|
1
|
+
import { BlockReadBatchTooLargeError, CompactionBacklogError, CompactionJobConflictError, GarbageCollectionJobConflictError, IndexedDbSchemaUpgradeBlockedError, LeaseConflictError, LeaseExpiredError, LeaseOwnerConflictError, OpfsCoordinationError, OpfsDatabaseInUseError, OpfsUncertainOutcomeError, PostingBuildConflictError, SnapshotImportConflictError, SnapshotManifestMissingError, StorageCorruptionError, StorageFormatVersionError, StorageResourceLimitError, TableInUseError, TableRecordConflictError, TempOwnerConflictError, TransactionRecordConflictError, UniqueKeyBuildConflictError, UniqueKeyConflictError, UniqueIndexCoverageError, WriteConflictError, SchemaConflictError } from "../types.js";
|
|
2
2
|
import { dateIsoString } from "../../date-value.js";
|
|
3
3
|
const MAX_OPFS_RPC_MESSAGE_BYTES = 66 * 1024 * 1024;
|
|
4
4
|
const MAX_OPFS_RPC_IDENTIFIER_CHARACTERS = 1024;
|
|
@@ -190,6 +190,8 @@ const errorRegistry = new Map([
|
|
|
190
190
|
PostingBuildConflictError,
|
|
191
191
|
StorageCorruptionError,
|
|
192
192
|
StorageFormatVersionError,
|
|
193
|
+
OpfsCoordinationError,
|
|
194
|
+
OpfsDatabaseInUseError,
|
|
193
195
|
OpfsUncertainOutcomeError
|
|
194
196
|
].map((constructor) => [constructor.name, constructor]));
|
|
195
197
|
function serializeStoreError(error) {
|
|
@@ -61,6 +61,8 @@ export declare class OpfsBlockStore {
|
|
|
61
61
|
close(): void;
|
|
62
62
|
/** Test-only: whether this connection currently holds the database's handles. */
|
|
63
63
|
_isLeaderForTests(): boolean;
|
|
64
|
+
/** @internal Simulates a suspended leader without releasing its file handles. */
|
|
65
|
+
_pauseCoordinationForTests(): () => void;
|
|
64
66
|
/** Test-only: the id that names this connection's inbox channel. */
|
|
65
67
|
_instanceIdForTests(): string;
|
|
66
68
|
/** Test-only: simulates an acknowledgement lost after the served operation settles. */
|