@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.
Files changed (75) hide show
  1. package/dist/engine/artifact-cache.js +3 -2
  2. package/dist/engine/buffered-writer.js +12 -2
  3. package/dist/engine/client.d.ts +9 -0
  4. package/dist/engine/client.js +112 -20
  5. package/dist/engine/database.d.ts +5 -3
  6. package/dist/engine/database.js +2199 -1675
  7. package/dist/engine/errors.d.ts +21 -2
  8. package/dist/engine/errors.js +30 -1
  9. package/dist/engine/index.d.ts +1 -0
  10. package/dist/engine/index.js +4 -0
  11. package/dist/engine/live-accept.js +13 -0
  12. package/dist/engine/live-aggregate.js +264 -0
  13. package/dist/engine/live-patch.d.ts +21 -0
  14. package/dist/engine/live-patch.js +31 -0
  15. package/dist/engine/live.d.ts +14 -0
  16. package/dist/engine/live.js +146 -142
  17. package/dist/engine/optimizer.js +11 -6
  18. package/dist/engine/query-cache.js +19 -15
  19. package/dist/engine/query-generations.js +61 -0
  20. package/dist/engine/query-identity.js +41 -0
  21. package/dist/engine/query.d.ts +7 -13
  22. package/dist/engine/query.js +298 -435
  23. package/dist/engine/result-state.d.ts +7 -0
  24. package/dist/engine/result-state.js +15 -0
  25. package/dist/engine/sql-domains.js +121 -0
  26. package/dist/engine/sql-semantics.js +7 -4
  27. package/dist/engine/typed-live.js +28 -20
  28. package/dist/engine/vector.d.ts +4 -0
  29. package/dist/engine/vector.js +14 -14
  30. package/dist/engine/windows.d.ts +12 -0
  31. package/dist/engine/windows.js +387 -0
  32. package/dist/engine/worker-server.d.ts +1 -1
  33. package/dist/engine/worker-server.js +28 -15
  34. package/dist/engine/write-coordinator.js +54 -0
  35. package/dist/plan/model.d.ts +1 -1
  36. package/dist/storage/indexeddb.js +134 -89
  37. package/dist/storage/opfs/index.d.ts +1 -1
  38. package/dist/storage/opfs/index.js +3 -1
  39. package/dist/storage/opfs/leader.js +20 -9
  40. package/dist/storage/opfs/rpc.js +3 -1
  41. package/dist/storage/opfs/store.d.ts +2 -0
  42. package/dist/storage/opfs/store.js +119 -43
  43. package/dist/storage/toolkit/record-core.js +2 -1
  44. package/dist/storage/types.d.ts +14 -0
  45. package/dist/storage/types.js +21 -0
  46. package/dist/transactions/index.d.ts +3 -0
  47. package/dist/transactions/index.js +4 -1
  48. package/dist/worker-protocol/index.d.ts +1 -1
  49. package/dist/worker-protocol/index.js +1 -1
  50. package/package.json +2 -2
  51. package/postgres-feature-profile.json +6 -1
  52. package/sql-feature-matrix.json +20 -27
  53. package/dist/date-value.d.ts +0 -20
  54. package/dist/engine/artifact-cache.d.ts +0 -29
  55. package/dist/engine/byte-estimates.d.ts +0 -11
  56. package/dist/engine/cancellation.d.ts +0 -2
  57. package/dist/engine/defaults.d.ts +0 -29
  58. package/dist/engine/group-index.d.ts +0 -33
  59. package/dist/engine/join-index.d.ts +0 -10
  60. package/dist/engine/live-equal.d.ts +0 -7
  61. package/dist/engine/point-read.d.ts +0 -59
  62. package/dist/engine/query-cache.d.ts +0 -21
  63. package/dist/engine/result-wire.d.ts +0 -70
  64. package/dist/engine/sort-keys.d.ts +0 -73
  65. package/dist/engine/sql-domains.d.ts +0 -93
  66. package/dist/engine/sql-functions.d.ts +0 -11
  67. package/dist/engine/sql-json.d.ts +0 -40
  68. package/dist/engine/sql-semantics.d.ts +0 -66
  69. package/dist/engine/worker-store-indexeddb.d.ts +0 -2
  70. package/dist/engine/worker-store-memory.d.ts +0 -2
  71. package/dist/engine/worker-store-opfs.d.ts +0 -2
  72. package/dist/engine/write-block-planner.d.ts +0 -19
  73. package/dist/storage/opfs/leader.d.ts +0 -460
  74. package/dist/storage/opfs/rpc.d.ts +0 -82
  75. 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 visitObjectStoreSequentially(store, (value, key) => {
1795
- if (typeof key === "string" && key.startsWith(TABLE_ID_PREFIX)) {
1796
- records.push(asTableRecord(value, key));
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 assertGarbageCollectionPinsAvailableInTransaction(transaction, currentVersion, leaseCutoff);
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 (await isManifestVersionPinnedInTransaction(transaction, version, currentVersion, leaseCutoff))
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, prunedManifestVersionSet);
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 visitObjectStoreSequentially(segmentStore, (value2) => {
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
- return new Promise((resolve, reject) => {
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 visitManifestBlockRecords(catalog, (record) => {
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 assertGarbageCollectionPinsAvailableInTransaction(transaction, currentVersion, leaseCutoff) {
9267
- await assertSnapshotAvailableInTransaction(transaction, currentVersion);
9268
- await visitObjectStoreSequentially(transaction.objectStore("transactions"), async (value) => {
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
- await assertSnapshotAvailableInTransaction(transaction, record.snapshotVersion);
9325
+ pin(record.snapshotVersion);
9272
9326
  }
9273
9327
  });
9274
- await visitObjectStoreSequentially(transaction.objectStore("leases"), async (value) => {
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
- await assertSnapshotAvailableInTransaction(transaction, lease.manifestVersion);
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
- await assertSnapshotAvailableInTransaction(transaction, job.sourceManifestVersion);
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
- await assertSnapshotAvailableInTransaction(transaction, linkedTransaction.committedVersion);
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 visitObjectStoreSequentially(transaction.objectStore("manifests"), (value, key) => {
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 !== void 0 || newlyPrunedVersions.has(record.version))
9357
- return;
9358
- readableVersions.push(record.version);
9413
+ if (record.prunedAt === void 0 && !newlyPrunedVersions.has(record.version)) {
9414
+ readableVersions.push(record.version);
9415
+ }
9359
9416
  });
9360
- const referenced = /* @__PURE__ */ new Map();
9361
- await visitManifestBlockRecords(transaction.objectStore("catalog"), (record) => {
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 isManifestVersionPinnedInTransaction(transaction, version, currentVersion, leaseCutoff) {
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
- await visitObjectStoreSequentially(transaction.objectStore("transactions"), (value) => {
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 = await visitObjectStoreSequentially(transaction.objectStore("transactions"), (value) => {
9847
- const record = asTransactionRecord(value);
9848
- return record.status === "active" && record.pendingBlockIds.includes(id);
9849
- });
9850
- if (!rooted) {
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
- rooted = await findReadableManifestBlock(transaction, [id], newlyPrunedVersions) !== void 0;
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 visitManifestBlockRecords(transaction.objectStore("catalog"), (record) => {
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
- await this.#healthy();
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
- await this.#healthy();
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
- await this.#healthy();
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
- await this.#healthy();
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
- await this.#healthy();
1818
+ while (this.#poisoned)
1819
+ await this.#healthy();
1815
1820
  return this.#core.listManifestBlockPage(input);
1816
1821
  }
1817
1822
  async listRetiredManifestBlockPage(input) {
1818
- await this.#healthy();
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
- await this.#healthy();
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
- await this.#healthy();
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
- await this.#healthy();
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 = {}) {
@@ -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. */