@minnowdb/core 0.10.1 → 0.10.2

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.
@@ -1,4 +1,4 @@
1
- import { CompactionJobConflictError, assertCompactionOutputProvenance, compactionOutputSegmentIds, CompactionBacklogError, createManifest, createGarbageCollectionJobRecord, storeNames, advanceGarbageCollectionJobRecord as advanceGarbageCollectionJobRecordUnchecked, BlockReadBatchTooLargeError, activePostingStorageColumnIds, assertTempRunPageBatchLimits, assertStorageBulkReadItems, assertTransactionArtifactBatchLimits, boundedMaintenanceBatchItems, canonicalManifestChangedTableIds, catalogRecordRetainedBytes, manifestRecordRetainedReservationBytes, segmentRecordRetainedBytes, collectFtsPostings, ftsPostingQueryMatches, invalidateUncoveredFtsColumns, invalidateUncoveredSecondaryIndexes, GarbageCollectionJobConflictError, LeaseConflictError, LeaseExpiredError, LeaseOwnerConflictError, MAX_FTS_CANDIDATE_ROW_IDS, MAX_ACTIVE_UNIQUE_KEY_BUILDS, MAX_UNIQUE_KEY_BUILD_STAGED_BYTES, MAX_UNIQUE_KEY_BUILD_STAGED_BYTES_TOTAL, MAX_UNIQUE_KEY_BUILD_CHUNK_BYTES, MAX_UNIQUE_KEY_BUILD_TOKENS_PER_CHUNK, MAX_UNIQUE_KEY_BUILD_TTL_MS, MAX_FTS_BASE_CHUNKS, MAX_FTS_DELTA_CHUNKS, MAX_FTS_POSTINGS_PER_CHUNK, MAX_FTS_POSTING_ROW_IDS_PER_CHUNK, MAX_FTS_POSTING_TERM_CHARACTERS, MAX_FTS_TOKENS_PER_DOCUMENT, MAX_FTS_ORDERED_READ_BYTES, MAX_MANIFEST_BLOCK_PRESENCE_IDS, MAX_MANIFEST_RECORDS, MAX_MANIFEST_RETAINED_BYTES, MAX_BLOCK_READ_BATCH_BYTES, MAX_STORAGE_ID_CHARACTERS, MAX_LEASE_TTL_MS, MAX_ACTIVE_LEASES, MAX_ACTIVE_TRANSACTIONS, MAX_GLOBAL_STAGED_ARTIFACT_BYTES, MAX_GLOBAL_STAGED_BLOCKS, MAX_GLOBAL_STAGED_SEGMENTS, MAX_RETIRED_HISTORY_BYTES, MAX_PINNED_MANIFEST_VERSION_LAG, MAX_PINNED_RETIRED_BLOCKS, MAX_PINNED_RETIRED_BYTES, MAX_TERMINAL_TRANSACTION_RECORDS, MAX_ACTIVE_COMPACTION_JOBS, MAX_ACTIVE_GARBAGE_COLLECTION_JOBS, MAX_TERMINAL_COMPACTION_JOB_RECORDS, MAX_COMPLETED_GARBAGE_COLLECTION_JOB_RECORDS, MAX_CATALOG_RECORDS, MAX_CATALOG_RETAINED_BYTES, MAX_SEGMENT_RECORDS, MAX_SEGMENT_RETAINED_BYTES, MAX_ACTIVE_FTS_BASE_BUILDS, MAX_ACTIVE_SECONDARY_INDEX_BUILDS, MAX_ACCELERATOR_BUILD_STAGED_BYTES_TOTAL, MAX_ACCELERATOR_BUILD_STAGED_ENTRIES_TOTAL, MAX_POSTING_BUILD_TTL_MS, MAX_SNAPSHOT_SESSION_TTL_MS, MAX_SNAPSHOT_FRAME_BATCH_BYTES, MAX_SNAPSHOT_FRAME_BATCH_ITEMS, MAX_SNAPSHOT_METADATA_BATCH_BYTES, MAX_SNAPSHOT_METADATA_FRAME_BYTES, SNAPSHOT_FRAME_KINDS, MAX_LEVEL_ZERO_SEGMENTS, MAX_AUTO_INCREMENT_EXCLUSIVE_END, MAX_ROW_ID, MAX_ROW_ID_EXCLUSIVE_END, StorageCorruptionError, StorageFormatVersionError, IndexedDbSchemaUpgradeBlockedError, StorageResourceLimitError, SnapshotManifestMissingError, SnapshotImportConflictError, PostingBuildConflictError, validateTableForeignKey, TableInUseError, TableRecordConflictError, TempOwnerConflictError, MAX_TEMP_OWNER_TTL_MS, MAX_ACTIVE_TEMP_OWNERS, MAX_TEMP_RUNS_PER_OWNER, MAX_TEMP_PAGES_PER_OWNER, MAX_TEMP_RUNS_TOTAL, MAX_TEMP_PAGES_TOTAL, MAX_TEMP_BYTES_PER_OWNER, MAX_TEMP_BYTES_TOTAL, TransactionRecordConflictError, UniqueKeyConflictError, UniqueKeyBuildConflictError, UniqueIndexCoverageError, SchemaConflictError, normalizeCompactionJobRecord, normalizeGarbageCollectionJobRecord, normalizeSegmentRecord, updateCompactionJobRecord as updateCompactionJobRecordUnchecked, updateGarbageCollectionPlanningRecord, updateTransactionRecord as updateTransactionRecordUnchecked, validateColumnDefault, validateCatalogName, validateCanonicalManifestChangedTableIds, validateEnumValues, validateFtsCandidateLimit, validateFtsOrderedReadLimits, validateFtsReadVersion, validateFtsPostingQueries, validateStorageId, validateStorageDatabaseName, validateTableColumns, validateSecondaryIndexes, validateTableRecordBounds, secondaryIndexColumnIds, secondaryIndexWriteContractChanged, secondaryUniqueKeyNamespace, transactionCommitDeltaRetainedBytes, uniqueKeyBuildChunkRetainedBytes, WriteConflictError } from "./types.js";
1
+ import { CompactionJobConflictError, assertCompactionOutputProvenance, compactionOutputSegmentIds, CompactionBacklogError, createManifest, createGarbageCollectionJobRecord, storeNames, advanceGarbageCollectionJobRecord as advanceGarbageCollectionJobRecordUnchecked, BlockReadBatchTooLargeError, activePostingStorageColumnIds, assertTempRunPageBatchLimits, assertStorageBulkReadItems, assertTransactionArtifactBatchLimits, boundedMaintenanceBatchItems, canonicalManifestChangedTableIds, catalogRecordRetainedBytes, manifestRecordRetainedReservationBytes, segmentRecordRetainedBytes, collectFtsPostings, ftsPostingQueryMatches, invalidateUncoveredFtsColumns, invalidateUncoveredSecondaryIndexes, GarbageCollectionJobConflictError, LeaseConflictError, LeaseExpiredError, LeaseOwnerConflictError, MAX_FTS_CANDIDATE_ROW_IDS, MAX_ACTIVE_UNIQUE_KEY_BUILDS, MAX_UNIQUE_KEY_BUILD_STAGED_BYTES, MAX_UNIQUE_KEY_BUILD_STAGED_BYTES_TOTAL, MAX_UNIQUE_KEY_BUILD_CHUNK_BYTES, MAX_UNIQUE_KEY_BUILD_TOKENS_PER_CHUNK, MAX_UNIQUE_KEY_BUILD_TTL_MS, MAX_FTS_BASE_CHUNKS, MAX_FTS_DELTA_CHUNKS, MAX_FTS_POSTINGS_PER_CHUNK, MAX_FTS_POSTING_ROW_IDS_PER_CHUNK, MAX_FTS_POSTING_TERM_CHARACTERS, MAX_FTS_TOKENS_PER_DOCUMENT, MAX_FTS_ORDERED_READ_BYTES, MAX_MANIFEST_BLOCK_PRESENCE_IDS, MAX_MANIFEST_RECORDS, MAX_MANIFEST_RETAINED_BYTES, MAX_BLOCK_READ_BATCH_BYTES, MAX_STORAGE_ID_CHARACTERS, MAX_LEASE_TTL_MS, MAX_ACTIVE_LEASES, MAX_ACTIVE_TRANSACTIONS, MAX_GLOBAL_STAGED_ARTIFACT_BYTES, MAX_GLOBAL_STAGED_BLOCKS, MAX_GLOBAL_STAGED_SEGMENTS, MAX_RETIRED_HISTORY_BYTES, MAX_PINNED_MANIFEST_VERSION_LAG, MAX_PINNED_RETIRED_BLOCKS, MAX_PINNED_RETIRED_BYTES, MAX_TERMINAL_TRANSACTION_RECORDS, MAX_ACTIVE_COMPACTION_JOBS, MAX_ACTIVE_GARBAGE_COLLECTION_JOBS, MAX_TERMINAL_COMPACTION_JOB_RECORDS, MAX_COMPLETED_GARBAGE_COLLECTION_JOB_RECORDS, MAX_CATALOG_RECORDS, MAX_CATALOG_RETAINED_BYTES, MAX_SEGMENT_RECORDS, MAX_SEGMENT_RETAINED_BYTES, MAX_ACTIVE_FTS_BASE_BUILDS, MAX_ACTIVE_SECONDARY_INDEX_BUILDS, MAX_ACCELERATOR_BUILD_STAGED_BYTES_TOTAL, MAX_ACCELERATOR_BUILD_STAGED_ENTRIES_TOTAL, MAX_POSTING_BUILD_TTL_MS, MAX_SNAPSHOT_SESSION_TTL_MS, MAX_SNAPSHOT_FRAME_BATCH_BYTES, MAX_SNAPSHOT_FRAME_BATCH_ITEMS, MAX_SNAPSHOT_METADATA_BATCH_BYTES, MAX_SNAPSHOT_METADATA_FRAME_BYTES, SNAPSHOT_FRAME_KINDS, MAX_LEVEL_ZERO_SEGMENTS, MAX_AUTO_INCREMENT_EXCLUSIVE_END, MAX_ROW_ID, MAX_ROW_ID_EXCLUSIVE_END, StorageCorruptionError, StorageFormatVersionError, StorageUnresponsiveError, IndexedDbSchemaUpgradeBlockedError, StorageResourceLimitError, SnapshotManifestMissingError, SnapshotImportConflictError, PostingBuildConflictError, validateTableForeignKey, TableInUseError, TableRecordConflictError, TempOwnerConflictError, MAX_TEMP_OWNER_TTL_MS, MAX_ACTIVE_TEMP_OWNERS, MAX_TEMP_RUNS_PER_OWNER, MAX_TEMP_PAGES_PER_OWNER, MAX_TEMP_RUNS_TOTAL, MAX_TEMP_PAGES_TOTAL, MAX_TEMP_BYTES_PER_OWNER, MAX_TEMP_BYTES_TOTAL, TransactionRecordConflictError, UniqueKeyConflictError, UniqueKeyBuildConflictError, UniqueIndexCoverageError, SchemaConflictError, normalizeCompactionJobRecord, normalizeGarbageCollectionJobRecord, normalizeSegmentRecord, updateCompactionJobRecord as updateCompactionJobRecordUnchecked, updateGarbageCollectionPlanningRecord, updateTransactionRecord as updateTransactionRecordUnchecked, validateColumnDefault, validateCatalogName, validateCanonicalManifestChangedTableIds, validateEnumValues, validateFtsCandidateLimit, validateFtsOrderedReadLimits, validateFtsReadVersion, validateFtsPostingQueries, validateStorageId, validateStorageDatabaseName, validateTableColumns, validateSecondaryIndexes, validateTableRecordBounds, secondaryIndexColumnIds, secondaryIndexWriteContractChanged, secondaryUniqueKeyNamespace, transactionCommitDeltaRetainedBytes, uniqueKeyBuildChunkRetainedBytes, WriteConflictError } from "./types.js";
2
2
  import { crc32, verifyStoredBlock } from "../block-format/index.js";
3
3
  import { dateIsoString } from "../date-value.js";
4
4
  import { decodeSnapshotMetadataItems, encodeSnapshotMetadataPage, extendSnapshotFrameStreamChecksum, prepareSnapshotFrameStreamHeader, snapshotFrameEnvelopeParts, snapshotFrameStreamHeaderIdentity } from "./snapshot-stream.js";
@@ -129,11 +129,18 @@ class IndexedDbBlockStore {
129
129
  #snapshotPeakRetainedItems = 0;
130
130
  #snapshotPeakRetainedBytes = 0;
131
131
  #closed = false;
132
- constructor(db, durability, uniqueKeyCacheBytes) {
132
+ #unresponsive;
133
+ #stallGuard;
134
+ constructor(db, durability, uniqueKeyCacheBytes, unresponsiveAfterMs) {
133
135
  this.#db = db;
134
136
  this.liveQueryChannelName = `minnowdb-live:indexeddb:${db.name}`;
135
137
  this.#durability = durability;
136
138
  this.#uniqueKeyCacheBytes = uniqueKeyCacheBytes;
139
+ this.#stallGuard = new ConnectionStallGuard(db.name, unresponsiveAfterMs, (error) => {
140
+ this.#unresponsive = error;
141
+ this.close();
142
+ });
143
+ stallGuards.set(db, this.#stallGuard);
137
144
  db.addEventListener("versionchange", () => {
138
145
  this.close();
139
146
  });
@@ -144,6 +151,10 @@ class IndexedDbBlockStore {
144
151
  if (!Number.isSafeInteger(uniqueKeyCacheBytes) || uniqueKeyCacheBytes < 0) {
145
152
  throw new RangeError("Unique-key cache bytes must be a non-negative whole number");
146
153
  }
154
+ const unresponsiveAfterMs = options.unresponsiveAfterMs ?? INDEXEDDB_UNRESPONSIVE_AFTER_MS;
155
+ if (!Number.isSafeInteger(unresponsiveAfterMs) || unresponsiveAfterMs <= 0) {
156
+ throw new RangeError("The unresponsive deadline must be a positive whole number of ms");
157
+ }
147
158
  const factory = options.indexedDB ?? getGlobalIndexedDb();
148
159
  if (factory === void 0)
149
160
  throw new Error("IndexedDB is unavailable");
@@ -177,7 +188,7 @@ class IndexedDbBlockStore {
177
188
  db = await openDatabaseRequest(request, (event) => {
178
189
  abandoned = true;
179
190
  return new IndexedDbSchemaUpgradeBlockedError(options.name, event.oldVersion, event.newVersion ?? SCHEMA_VERSION);
180
- });
191
+ }, options.name, unresponsiveAfterMs);
181
192
  } catch (error) {
182
193
  if (upgradeError !== void 0)
183
194
  throw upgradeError;
@@ -187,7 +198,7 @@ class IndexedDbBlockStore {
187
198
  }
188
199
  throw error;
189
200
  }
190
- const store = new IndexedDbBlockStore(db, options.durability ?? "strict", uniqueKeyCacheBytes);
201
+ const store = new IndexedDbBlockStore(db, options.durability ?? "strict", uniqueKeyCacheBytes, unresponsiveAfterMs);
191
202
  try {
192
203
  await validateCurrentIndexedDbSchema(db);
193
204
  await store.#validateCatalogResourceLedger();
@@ -1164,8 +1175,8 @@ class IndexedDbBlockStore {
1164
1175
  this.#journalCache = void 0;
1165
1176
  if (this.#uniqueKeyCache?.tableId === input.tableId || this.#uniqueKeyCache?.tableId.startsWith(`${input.tableId}\0secondary-index\0`) === true) {
1166
1177
  this.#uniqueKeyCache = void 0;
1167
- } else if (this.#uniqueKeyCache !== void 0) {
1168
- this.#uniqueKeyCache.version = manifest.version;
1178
+ } else {
1179
+ this.#carryUniqueKeyCache(input.expectedManifestVersion, manifest.version);
1169
1180
  }
1170
1181
  return manifest;
1171
1182
  } catch (error) {
@@ -1302,8 +1313,7 @@ class IndexedDbBlockStore {
1302
1313
  await transactionDone(transaction);
1303
1314
  this.#manifestCache = void 0;
1304
1315
  this.#journalCache = void 0;
1305
- if (this.#uniqueKeyCache !== void 0)
1306
- this.#uniqueKeyCache.version = manifest.version;
1316
+ this.#carryUniqueKeyCache(input.expectedManifestVersion, manifest.version);
1307
1317
  return manifest;
1308
1318
  } catch (error) {
1309
1319
  abortIfActive(transaction);
@@ -2124,6 +2134,16 @@ class IndexedDbBlockStore {
2124
2134
  throw await refusalBehindAbort(transaction, error);
2125
2135
  }
2126
2136
  }
2137
+ #carryUniqueKeyCache(basedOn, version) {
2138
+ const cache = this.#uniqueKeyCache;
2139
+ if (cache === void 0 || cache.version === version)
2140
+ return;
2141
+ if (basedOn !== null && basedOn !== void 0 && cache.version === basedOn) {
2142
+ cache.version = version;
2143
+ } else {
2144
+ this.#uniqueKeyCache = void 0;
2145
+ }
2146
+ }
2127
2147
  async getExistingUniqueKeys(tableId, keyTokens) {
2128
2148
  validateId(tableId, "Table ID");
2129
2149
  assertStorageBulkReadItems(keyTokens, "Unique-key read");
@@ -3470,13 +3490,11 @@ class IndexedDbBlockStore {
3470
3490
  } : void 0;
3471
3491
  } else if (this.#uniqueKeyCache?.tableId === uniqueKeyChanges.tableId) {
3472
3492
  this.#uniqueKeyCache = void 0;
3473
- } else if (this.#uniqueKeyCache !== void 0) {
3474
- this.#uniqueKeyCache.version = manifest.version;
3493
+ } else {
3494
+ this.#carryUniqueKeyCache(input.expectedManifestVersion, manifest.version);
3475
3495
  }
3476
- } else if (uniqueKeyChanges !== void 0 && this.#uniqueKeyCache?.tableId === uniqueKeyChanges.tableId) {
3477
- this.#uniqueKeyCache.version = manifest.version;
3478
- } else if (this.#uniqueKeyCache !== void 0) {
3479
- this.#uniqueKeyCache.version = manifest.version;
3496
+ } else {
3497
+ this.#carryUniqueKeyCache(input.expectedManifestVersion, manifest.version);
3480
3498
  }
3481
3499
  this.#manifestCache = void 0;
3482
3500
  this.#journalCache = void 0;
@@ -3910,13 +3928,13 @@ class IndexedDbBlockStore {
3910
3928
  const quota = await readMaintenanceQuota(gcStore);
3911
3929
  let activeGarbageCollectionJobs;
3912
3930
  if (record.state !== "completed") {
3913
- activeGarbageCollectionJobs = checkedQuotaIncrement(quota.activeGarbageCollectionJobs, "Active garbage collection job count");
3914
- assertMaintenanceLimit("garbage collection job", activeGarbageCollectionJobs, MAX_ACTIVE_GARBAGE_COLLECTION_JOBS);
3915
3931
  const markerValue = await requestResult(gcStore.get(ACTIVE_GARBAGE_COLLECTION_KEY));
3916
3932
  if (markerValue !== void 0) {
3917
3933
  const marker = asActiveGarbageCollectionMarker(markerValue);
3918
- throw new Error(`Garbage collection job ${marker.jobId} is already active`);
3934
+ throw new GarbageCollectionJobConflictError(marker.jobId, record.revision, null);
3919
3935
  }
3936
+ activeGarbageCollectionJobs = checkedQuotaIncrement(quota.activeGarbageCollectionJobs, "Active garbage collection job count");
3937
+ assertMaintenanceLimit("garbage collection job", activeGarbageCollectionJobs, MAX_ACTIVE_GARBAGE_COLLECTION_JOBS);
3920
3938
  }
3921
3939
  await assertGarbageCollectionCandidateProvenanceInTransaction(transaction, record);
3922
3940
  if (record.state !== "completed") {
@@ -3959,6 +3977,9 @@ class IndexedDbBlockStore {
3959
3977
  candidateTransactionIds: input.candidateTransactionIds ?? []
3960
3978
  });
3961
3979
  gcStore.put(garbageCollectionJobEnvelope(updated), key);
3980
+ if (updated.state === "completed" && current.state !== "completed") {
3981
+ await releaseGarbageCollectionAdmission(gcStore);
3982
+ }
3962
3983
  await transactionDone(transaction);
3963
3984
  return structuredClone(updated);
3964
3985
  } catch (error) {
@@ -4183,17 +4204,8 @@ class IndexedDbBlockStore {
4183
4204
  updatedAt: input.updatedAt
4184
4205
  });
4185
4206
  gcStore.put(garbageCollectionJobEnvelope(updated), key);
4186
- if (updated.state === "completed") {
4187
- const quota = await readMaintenanceQuota(gcStore);
4188
- const completedGarbageCollectionJobs = checkedQuotaIncrement(quota.completedGarbageCollectionJobs, "Completed garbage collection job count");
4189
- assertMaintenanceLimit("completed garbage collection job", completedGarbageCollectionJobs, MAX_COMPLETED_GARBAGE_COLLECTION_JOB_RECORDS);
4190
- gcStore.put({
4191
- ...quota,
4192
- activeGarbageCollectionJobs: checkedQuotaDecrement(quota.activeGarbageCollectionJobs, "active garbage collection job count"),
4193
- completedGarbageCollectionJobs
4194
- }, MAINTENANCE_QUOTA_KEY);
4195
- await requestResult(gcStore.delete(ACTIVE_GARBAGE_COLLECTION_KEY));
4196
- }
4207
+ if (updated.state === "completed")
4208
+ await releaseGarbageCollectionAdmission(gcStore);
4197
4209
  await transactionDone(transaction);
4198
4210
  return {
4199
4211
  job: structuredClone(updated),
@@ -6055,6 +6067,7 @@ class IndexedDbBlockStore {
6055
6067
  this.#uniqueKeyCache = void 0;
6056
6068
  this.#manifestCache = void 0;
6057
6069
  this.#journalCache = void 0;
6070
+ this.#stallGuard.dispose();
6058
6071
  this.#db.close();
6059
6072
  }
6060
6073
  _residentStateForTests() {
@@ -6333,6 +6346,8 @@ class IndexedDbBlockStore {
6333
6346
  }
6334
6347
  }
6335
6348
  #transaction(stores, mode, options = {}) {
6349
+ if (this.#unresponsive !== void 0)
6350
+ throw this.#unresponsive;
6336
6351
  if (this.#closed)
6337
6352
  throw new Error("This IndexedDB store connection is closed");
6338
6353
  const requested = typeof stores === "string" ? [stores] : stores;
@@ -6454,49 +6469,156 @@ async function readIndexedDbVersion(factory, name) {
6454
6469
  return null;
6455
6470
  }
6456
6471
  }
6457
- function openDatabaseRequest(request, blockedError) {
6472
+ const INDEXEDDB_UNRESPONSIVE_AFTER_MS = 3e4;
6473
+ class ConnectionStallGuard {
6474
+ #databaseName;
6475
+ #afterMs;
6476
+ #onTrip;
6477
+ #waiting = /* @__PURE__ */ new Set();
6478
+ #timer;
6479
+ #tripped;
6480
+ #disposed = false;
6481
+ constructor(databaseName, afterMs, onTrip) {
6482
+ this.#databaseName = databaseName;
6483
+ this.#afterMs = afterMs;
6484
+ this.#onTrip = onTrip;
6485
+ }
6486
+ wait(reject) {
6487
+ if (this.#tripped !== void 0) {
6488
+ const tripped = this.#tripped;
6489
+ queueMicrotask(() => reject(tripped));
6490
+ return () => void 0;
6491
+ }
6492
+ if (this.#disposed)
6493
+ return () => void 0;
6494
+ this.#waiting.add(reject);
6495
+ this.#arm();
6496
+ let released = false;
6497
+ return () => {
6498
+ if (released)
6499
+ return;
6500
+ released = true;
6501
+ this.#waiting.delete(reject);
6502
+ this.#progress();
6503
+ };
6504
+ }
6505
+ dispose() {
6506
+ this.#disposed = true;
6507
+ this.#waiting.clear();
6508
+ this.#disarm();
6509
+ }
6510
+ #arm() {
6511
+ if (this.#timer !== void 0 || this.#waiting.size === 0)
6512
+ return;
6513
+ this.#timer = setTimeout(() => {
6514
+ this.#timer = void 0;
6515
+ this.#trip();
6516
+ }, this.#afterMs);
6517
+ this.#timer.unref?.();
6518
+ }
6519
+ #disarm() {
6520
+ if (this.#timer === void 0)
6521
+ return;
6522
+ clearTimeout(this.#timer);
6523
+ this.#timer = void 0;
6524
+ }
6525
+ #progress() {
6526
+ if (this.#tripped !== void 0)
6527
+ return;
6528
+ this.#disarm();
6529
+ this.#arm();
6530
+ }
6531
+ #trip() {
6532
+ if (this.#tripped !== void 0)
6533
+ return;
6534
+ const error = new StorageUnresponsiveError("indexeddb", this.#databaseName, this.#afterMs);
6535
+ this.#tripped = error;
6536
+ const waiting = [...this.#waiting];
6537
+ this.#waiting.clear();
6538
+ this.#onTrip(error);
6539
+ for (const reject of waiting)
6540
+ reject(error);
6541
+ }
6542
+ }
6543
+ const stallGuards = /* @__PURE__ */ new WeakMap();
6544
+ function stallGuardFor(db) {
6545
+ return db === null || db === void 0 ? void 0 : stallGuards.get(db);
6546
+ }
6547
+ function openDatabaseRequest(request, blockedError, databaseName, unresponsiveAfterMs) {
6458
6548
  return new Promise((resolve, reject) => {
6459
6549
  let settled = false;
6550
+ const timer = setTimeout(() => {
6551
+ if (settled)
6552
+ return;
6553
+ settled = true;
6554
+ reject(new StorageUnresponsiveError("indexeddb", databaseName, unresponsiveAfterMs));
6555
+ }, unresponsiveAfterMs);
6556
+ timer.unref?.();
6557
+ const settle = () => {
6558
+ if (settled)
6559
+ return false;
6560
+ settled = true;
6561
+ clearTimeout(timer);
6562
+ return true;
6563
+ };
6460
6564
  request.addEventListener("success", () => {
6461
- if (settled) {
6565
+ if (!settle()) {
6462
6566
  request.result.close();
6463
6567
  return;
6464
6568
  }
6465
- settled = true;
6466
6569
  resolve(request.result);
6467
6570
  }, { once: true });
6468
6571
  request.addEventListener("error", () => {
6469
- if (settled)
6572
+ if (!settle())
6470
6573
  return;
6471
- settled = true;
6472
6574
  reject(request.error ?? new Error("IndexedDB open failed"));
6473
6575
  }, { once: true });
6474
6576
  request.addEventListener("blocked", (event) => {
6475
- if (settled)
6577
+ if (!settle())
6476
6578
  return;
6477
- settled = true;
6478
6579
  reject(blockedError(event));
6479
6580
  }, { once: true });
6480
6581
  });
6481
6582
  }
6482
6583
  function requestResult(request) {
6584
+ const guard = stallGuardFor(request.transaction?.db);
6483
6585
  return new Promise((resolve, reject) => {
6484
- request.addEventListener("success", () => resolve(request.result), { once: true });
6485
- request.addEventListener("error", () => reject(request.error ?? new Error("IndexedDB request failed")), {
6586
+ const release = guard?.wait(reject);
6587
+ request.addEventListener("success", () => {
6588
+ release?.();
6589
+ resolve(request.result);
6590
+ }, { once: true });
6591
+ request.addEventListener("error", () => {
6592
+ release?.();
6593
+ reject(request.error ?? new Error("IndexedDB request failed"));
6594
+ }, {
6486
6595
  once: true
6487
6596
  });
6488
6597
  });
6489
6598
  }
6490
6599
  function readCursorPage(store, limit, decode, acceptKey, seekKey) {
6600
+ const guard = stallGuardFor(store.transaction.db);
6491
6601
  return new Promise((resolve, reject) => {
6492
6602
  const records = [];
6493
6603
  const request = store.openCursor();
6494
6604
  let seekPending = seekKey !== void 0;
6495
- request.onerror = () => reject(request.error ?? new Error("IndexedDB cursor failed"));
6605
+ let release = guard?.wait(reject);
6606
+ const settle = (value) => {
6607
+ release?.();
6608
+ release = void 0;
6609
+ resolve(value);
6610
+ };
6611
+ request.onerror = () => {
6612
+ release?.();
6613
+ release = void 0;
6614
+ reject(request.error ?? new Error("IndexedDB cursor failed"));
6615
+ };
6496
6616
  request.onsuccess = () => {
6617
+ release?.();
6618
+ release = guard?.wait(reject);
6497
6619
  const cursor = request.result;
6498
6620
  if (cursor === null || records.length === limit) {
6499
- resolve(records);
6621
+ settle(records);
6500
6622
  return;
6501
6623
  }
6502
6624
  try {
@@ -6510,10 +6632,12 @@ function readCursorPage(store, limit, decode, acceptKey, seekKey) {
6510
6632
  if (acceptKey(cursor.key))
6511
6633
  records.push(decode(cursor.value, cursor.key));
6512
6634
  if (records.length === limit)
6513
- resolve(records);
6635
+ settle(records);
6514
6636
  else
6515
6637
  cursor.continue();
6516
6638
  } catch (error) {
6639
+ release?.();
6640
+ release = void 0;
6517
6641
  reject(error instanceof Error ? error : new Error(String(error)));
6518
6642
  }
6519
6643
  };
@@ -6973,9 +7097,17 @@ function transactionDone(transaction) {
6973
7097
  const existing = transactionCompletions.get(transaction);
6974
7098
  if (existing !== void 0)
6975
7099
  return existing;
7100
+ const guard = stallGuardFor(transaction.db);
6976
7101
  const done = new Promise((resolve, reject) => {
6977
- transaction.addEventListener("complete", () => resolve(), { once: true });
6978
- transaction.addEventListener("abort", () => reject(transaction.error ?? new Error("Transaction aborted")), {
7102
+ const release = guard?.wait(reject);
7103
+ transaction.addEventListener("complete", () => {
7104
+ release?.();
7105
+ resolve();
7106
+ }, { once: true });
7107
+ transaction.addEventListener("abort", () => {
7108
+ release?.();
7109
+ reject(transaction.error ?? new Error("Transaction aborted"));
7110
+ }, {
6979
7111
  once: true
6980
7112
  });
6981
7113
  });
@@ -9737,6 +9869,17 @@ async function assertActiveCompactionMarker(store, record) {
9737
9869
  throw corruption(`gc/${activeCompactionKey(record.tableId)}`, `marker names job ${marker.jobId} instead of ${record.id}`);
9738
9870
  }
9739
9871
  }
9872
+ async function releaseGarbageCollectionAdmission(gcStore) {
9873
+ const quota = await readMaintenanceQuota(gcStore);
9874
+ const completedGarbageCollectionJobs = checkedQuotaIncrement(quota.completedGarbageCollectionJobs, "Completed garbage collection job count");
9875
+ assertMaintenanceLimit("completed garbage collection job", completedGarbageCollectionJobs, MAX_COMPLETED_GARBAGE_COLLECTION_JOB_RECORDS);
9876
+ gcStore.put({
9877
+ ...quota,
9878
+ activeGarbageCollectionJobs: checkedQuotaDecrement(quota.activeGarbageCollectionJobs, "active garbage collection job count"),
9879
+ completedGarbageCollectionJobs
9880
+ }, MAINTENANCE_QUOTA_KEY);
9881
+ await requestResult(gcStore.delete(ACTIVE_GARBAGE_COLLECTION_KEY));
9882
+ }
9740
9883
  async function assertActiveGarbageCollectionMarker(store, record) {
9741
9884
  const marker = asActiveGarbageCollectionMarker(await requestResult(store.get(ACTIVE_GARBAGE_COLLECTION_KEY)));
9742
9885
  if (marker.jobId !== record.id) {
@@ -12897,5 +13040,6 @@ function utf8Bytes(value) {
12897
13040
  return storageTextEncoder.encode(value).byteLength;
12898
13041
  }
12899
13042
  export {
13043
+ INDEXEDDB_UNRESPONSIVE_AFTER_MS,
12900
13044
  IndexedDbBlockStore
12901
13045
  };
@@ -1,4 +1,4 @@
1
- import { BlockReadBatchTooLargeError, CompactionBacklogError, CompactionJobConflictError, ConnectionLostError, GarbageCollectionJobConflictError, IndexedDbSchemaUpgradeBlockedError, LeaseConflictError, LeaseExpiredError, LeaseOwnerConflictError, OpfsCoordinationError, OpfsDatabaseInUseError, OpfsUncertainOutcomeError, PostingBuildConflictError, SchemaConflictError, SnapshotImportConflictError, SnapshotManifestMissingError, StorageCorruptionError, StorageFormatVersionError, StorageResourceLimitError, TableInUseError, TableRecordConflictError, TempOwnerConflictError, TransactionRecordConflictError, UniqueIndexCoverageError, UniqueKeyBuildConflictError, UniqueKeyConflictError, UnknownOutcomeError, WriteConflictError } from "../types.js";
1
+ import { BlockReadBatchTooLargeError, CompactionBacklogError, CompactionJobConflictError, ConnectionLostError, GarbageCollectionJobConflictError, IndexedDbSchemaUpgradeBlockedError, LeaseConflictError, LeaseExpiredError, LeaseOwnerConflictError, OpfsCoordinationError, OpfsDatabaseInUseError, OpfsUncertainOutcomeError, PostingBuildConflictError, SchemaConflictError, SnapshotImportConflictError, SnapshotManifestMissingError, StorageCorruptionError, StorageUnresponsiveError, StorageFormatVersionError, StorageResourceLimitError, TableInUseError, TableRecordConflictError, TempOwnerConflictError, TransactionRecordConflictError, UniqueIndexCoverageError, UniqueKeyBuildConflictError, UniqueKeyConflictError, UnknownOutcomeError, WriteConflictError } from "../types.js";
2
2
  import { dateIsoString } from "../../date-value.js";
3
3
  import { MAX_SERIALIZED_CAUSE_DEPTH, rehydrateError, serializeError } from "../../worker-protocol/index.js";
4
4
  const MAX_OPFS_RPC_STACK_CHARACTERS = 64 * 1024;
@@ -203,6 +203,7 @@ const errorRegistry = new Map([
203
203
  PostingBuildConflictError,
204
204
  StorageCorruptionError,
205
205
  StorageFormatVersionError,
206
+ StorageUnresponsiveError,
206
207
  OpfsCoordinationError,
207
208
  OpfsDatabaseInUseError,
208
209
  OpfsUncertainOutcomeError,
@@ -254,8 +254,16 @@ export interface SecondaryIndexRecord {
254
254
  unique?: true;
255
255
  /** The membership set has been seeded and must be enforced, independent of postings state. */
256
256
  uniqueEnforced?: true;
257
- /** Prefix-free composite encoding used by every v1 secondary index. */
258
- termEncoding: "tuple-v1";
257
+ /**
258
+ * Prefix-free composite term encoding.
259
+ *
260
+ * `tuple-v1` encodes non-null components only: a row with a NULL in any indexed column has no
261
+ * posting at all, so a prefix lookup through the index cannot see it. `tuple-v2` keeps every
262
+ * non-null component byte-identical and adds a one-character marker for a NULL component, so a
263
+ * row with a NULL trailing component is named under its non-null prefix. Both stay readable;
264
+ * only `tuple-v2` may serve a prefix lookup whose unconstrained trailing columns are nullable.
265
+ */
266
+ termEncoding: "tuple-v1" | "tuple-v2";
259
267
  storage: "postings-v1";
260
268
  storageColumnId: string;
261
269
  locator: "row-id" | "key-hash-v1";
@@ -1361,6 +1369,29 @@ export declare class UnknownOutcomeError extends Error {
1361
1369
  export declare class ConnectionLostError extends Error {
1362
1370
  readonly name: string;
1363
1371
  }
1372
+ /**
1373
+ * The store stopped answering: the whole connection went the adapter's unresponsive deadline
1374
+ * without a single storage event while it had work outstanding. The remedy is to reload the
1375
+ * page, not to retry — the connection cannot recover, and a fresh one in the same page may be
1376
+ * wedged by the same cause.
1377
+ *
1378
+ * The known cause is a worker terminated with a write in flight. WebKit keeps the dead worker's
1379
+ * IndexedDB connection, and its unfinished transaction, registered until the owning document
1380
+ * goes away, and while it is registered every connection to that database blocks — including
1381
+ * connections opened afterwards in other tabs. A store cannot unwedge the browser, so it bounds
1382
+ * the wait and says so instead of hanging.
1383
+ *
1384
+ * It extends `UnknownOutcomeError` because a write that stalled may still be committed by the
1385
+ * browser once the wedge clears, and carries a `ConnectionLostError` cause so `classifyError`
1386
+ * reports the connection as unusable.
1387
+ */
1388
+ export declare class StorageUnresponsiveError extends UnknownOutcomeError {
1389
+ readonly backend: string;
1390
+ readonly databaseName: string;
1391
+ readonly waitedMs: number;
1392
+ readonly name = "StorageUnresponsiveError";
1393
+ constructor(backend: string, databaseName: string, waitedMs: number);
1394
+ }
1364
1395
  /**
1365
1396
  * A remote OPFS leader may have committed a mutation whose acknowledgement was lost.
1366
1397
  * Reconcile stable identities or revisions before retrying the named operation.
@@ -226,7 +226,7 @@ function secondaryIndexWriteContractChanged(previous, next) {
226
226
  if (rightEntry?.[0] !== id)
227
227
  return true;
228
228
  const right = rightEntry[1];
229
- return left.name !== right.name || left.columnId !== right.columnId || left.columnIds.length !== right.columnIds.length || left.columnIds.some((columnId, index) => columnId !== right.columnIds[index]) || left.directions.length !== right.directions.length || left.directions.some((direction, index) => direction !== right.directions[index]) || left.unique !== right.unique || left.uniqueEnforced !== right.uniqueEnforced || left.storageColumnId !== right.storageColumnId || left.locator !== right.locator;
229
+ return left.name !== right.name || left.columnId !== right.columnId || left.columnIds.length !== right.columnIds.length || left.columnIds.some((columnId, index) => columnId !== right.columnIds[index]) || left.directions.length !== right.directions.length || left.directions.some((direction, index) => direction !== right.directions[index]) || left.termEncoding !== right.termEncoding || left.unique !== right.unique || left.uniqueEnforced !== right.uniqueEnforced || left.storageColumnId !== right.storageColumnId || left.locator !== right.locator;
230
230
  });
231
231
  }
232
232
  function validateTableForeignKey(childTable, key, parentTable) {
@@ -461,7 +461,7 @@ function validateSecondaryIndexes(record) {
461
461
  }
462
462
  const directions = index.directions;
463
463
  const termEncoding = index.termEncoding;
464
- if (directions.length !== indexedColumnIds.length || directions.some((direction) => direction !== "asc" && direction !== "desc") || termEncoding !== "tuple-v1") {
464
+ if (directions.length !== indexedColumnIds.length || directions.some((direction) => direction !== "asc" && direction !== "desc") || termEncoding !== "tuple-v1" && termEncoding !== "tuple-v2") {
465
465
  throw new TypeError(`Secondary index ${index.name} has invalid key metadata`);
466
466
  }
467
467
  if (index.uniqueEnforced === true && index.unique !== true) {
@@ -993,6 +993,18 @@ class UnknownOutcomeError extends Error {
993
993
  class ConnectionLostError extends Error {
994
994
  name = "ConnectionLostError";
995
995
  }
996
+ class StorageUnresponsiveError extends UnknownOutcomeError {
997
+ backend;
998
+ databaseName;
999
+ waitedMs;
1000
+ name = "StorageUnresponsiveError";
1001
+ constructor(backend, databaseName, waitedMs) {
1002
+ super(`The ${backend} database ${databaseName} answered nothing for ${String(waitedMs)}ms and is treated as unresponsive; reload the page to open it again`, { cause: new ConnectionLostError(`The ${backend} connection stopped answering`) });
1003
+ this.backend = backend;
1004
+ this.databaseName = databaseName;
1005
+ this.waitedMs = waitedMs;
1006
+ }
1007
+ }
996
1008
  class OpfsUncertainOutcomeError extends UnknownOutcomeError {
997
1009
  method;
998
1010
  name = "OpfsUncertainOutcomeError";
@@ -2428,7 +2440,7 @@ function normalizeMergeCompactionRewritePlan(value) {
2428
2440
  for (let index = 1; index < sourceSegments.length; index += 1) {
2429
2441
  const previous = sourceSegments[index - 1];
2430
2442
  const current = sourceSegments[index];
2431
- if (previous !== void 0 && current !== void 0 && compareMergeSourceSegments(previous, current) >= 0) {
2443
+ if (previous !== void 0 && current !== void 0 && compareMergeSourceSegments(previous, current) > 0) {
2432
2444
  throw new TypeError("Merge source segments must use canonical logical order");
2433
2445
  }
2434
2446
  }
@@ -2739,7 +2751,7 @@ function normalizeRowIdSpans(value, totalRows, rowIdStart, rowIdEndExclusive, la
2739
2751
  return spans;
2740
2752
  }
2741
2753
  function compareMergeSourceSegments(left, right) {
2742
- return left.logicalOrder - right.logicalOrder || left.committedVersion - right.committedVersion || left.segmentId.localeCompare(right.segmentId);
2754
+ return left.logicalOrder - right.logicalOrder || left.committedVersion - right.committedVersion;
2743
2755
  }
2744
2756
  function normalizeCompactionOutputCursor(value, plan) {
2745
2757
  if (plan.kind === "copy-v1") {
@@ -3065,6 +3077,7 @@ export {
3065
3077
  StorageCorruptionError,
3066
3078
  StorageFormatVersionError,
3067
3079
  StorageResourceLimitError,
3080
+ StorageUnresponsiveError,
3068
3081
  TableInUseError,
3069
3082
  TableRecordConflictError,
3070
3083
  TempOwnerConflictError,
@@ -1,4 +1,4 @@
1
- import { CompactionJobConflictError, LeaseConflictError, LeaseOwnerConflictError, MAX_LEVEL_ZERO_SEGMENTS, MAX_MANIFEST_BLOCK_PRESENCE_IDS, MAX_STORAGE_BULK_READ_ITEMS, PostingBuildConflictError, SnapshotManifestMissingError, TableInUseError, TableRecordConflictError, TempOwnerConflictError, TransactionRecordConflictError, UniqueKeyConflictError, secondaryUniqueKeyNamespace, WriteConflictError } from "../storage/types.js";
1
+ import { CompactionJobConflictError, GarbageCollectionJobConflictError, LeaseConflictError, LeaseOwnerConflictError, MAX_LEVEL_ZERO_SEGMENTS, MAX_MANIFEST_BLOCK_PRESENCE_IDS, MAX_STORAGE_BULK_READ_ITEMS, PostingBuildConflictError, SnapshotManifestMissingError, TableInUseError, TableRecordConflictError, TempOwnerConflictError, TransactionRecordConflictError, UniqueKeyConflictError, secondaryUniqueKeyNamespace, WriteConflictError } from "../storage/types.js";
2
2
  async function runBlockStoreConformance(target) {
3
3
  for (const conformanceCase of blockStoreConformanceCases()) {
4
4
  try {
@@ -768,6 +768,36 @@ function blockStoreConformanceCases() {
768
768
  limit: 1
769
769
  });
770
770
  checkEqual(retiredBeforeCollection.records.map((record) => record.blockId), [first.blockId], "retired provenance must remain independently pageable before collection");
771
+ const discovering = await store.createGarbageCollectionJob({
772
+ id: "gc-discovering",
773
+ candidateManifestVersions: [],
774
+ candidateSegmentIds: [],
775
+ candidateBlockIds: [],
776
+ leaseCutoff: LATER,
777
+ createdAt: LATER,
778
+ discovery: {
779
+ phase: "manifests",
780
+ currentManifestVersion: null,
781
+ retainAboveVersion: 0,
782
+ retainAfter: 0,
783
+ maxPlanningItems: 16,
784
+ manifestCursor: null,
785
+ segmentCursor: null,
786
+ transactionCursor: null,
787
+ compactionCursor: null,
788
+ visitedRecords: 0,
789
+ resumePhase: null,
790
+ postManifestPhase: null,
791
+ artifactCursor: null
792
+ }
793
+ });
794
+ const discovered = await store.updateGarbageCollectionPlanning({
795
+ jobId: discovering.id,
796
+ expectedRevision: discovering.revision,
797
+ discovery: { ...discovering.discovery ?? {}, phase: "complete" },
798
+ updatedAt: LATER
799
+ });
800
+ check(discovered.state === "completed", "a discovery that found nothing must complete its job");
771
801
  const job = await store.createGarbageCollectionJob({
772
802
  id: "gc-1",
773
803
  candidateManifestVersions: [first.version],
@@ -778,6 +808,15 @@ function blockStoreConformanceCases() {
778
808
  });
779
809
  await checkThrows(() => store.removeGarbageCollectionJob(job.id), Error, "removing a non-completed garbage-collection job");
780
810
  check((await store.getGarbageCollectionJob(job.id))?.state === "planned", "a refused garbage-collection job removal mutated the record");
811
+ await checkThrows(() => store.createGarbageCollectionJob({
812
+ id: "gc-2",
813
+ candidateManifestVersions: [first.version],
814
+ candidateSegmentIds: [],
815
+ candidateBlockIds: [first.blockId],
816
+ leaseCutoff: LATER,
817
+ createdAt: LATER
818
+ }), GarbageCollectionJobConflictError, "a second active garbage-collection job");
819
+ check(await store.getGarbageCollectionJob("gc-2") === void 0, "a refused second garbage-collection job left a record behind");
781
820
  const step = await store.runGarbageCollectionStep({
782
821
  jobId: job.id,
783
822
  expectedRevision: job.revision,
@@ -96,4 +96,5 @@ export declare class FaultInjectingBlockStore implements BlockStore {
96
96
  export { blockStoreConformanceCases, runBlockStoreConformance, type BlockStoreConformanceCase, type BlockStoreConformanceTarget, } from "./block-store-conformance.js";
97
97
  export { MemoryOpfs, type TransferLimit, type WriteFault } from "./opfs-shim.js";
98
98
  export { DeterministicScheduler, generateSimulationPlan, parseSimulationPlan, runSimulation, scheduledBlockStore, type CollectionPassSummary, type SimulationDelete, type SimulationMutation, type SimulationOptions, type SimulationPlan, type SimulationPut, type SimulationResult, type SimulationStep, type SimulationTraceEvent, } from "./simulator.js";
99
+ export { InteractionFailure, createDatabaseDriver, evaluate as evaluatePlanPredicate, generateInteractionPlan, parseInteractionPlan, renderLiteral as renderPlanLiteral, renderPredicate as renderPlanPredicate, runInteractionPlan, type Assignment, type ColumnType, type CompareOperator, type DatabaseDriverOptions, type DriverStoreSource, type FaultPointName, type Interaction, type InteractionPlan, type InteractionPlanOptions, type InteractionRunOptions, type InteractionRunResult, type KeyedMutation, type PlanColumn, type PlanRow, type PlanTable, type PlanValue, type Predicate, type SimulatedConnection, type SimulatedExecuteResult, type SimulatedFaults, type SimulatedQueryResult, type SimulationDriver, type TransactionStatement, } from "./interaction-simulator.js";
99
100
  export { SqlLogicFailure, SqlLogicParseError, md5Hex, parseSqlLogicTest, parseSqlLogicTestLines, renderSqlLogicValue, runSqlLogicTest, type SqlLogicCondition, type SqlLogicDatabase, type SqlLogicExpectedHash, type SqlLogicExpectedValues, type SqlLogicHalt, type SqlLogicHashThreshold, type SqlLogicLocation, type SqlLogicQuery, type SqlLogicRecord, type SqlLogicRunStatistics, type SqlLogicRunOptions, type SqlLogicSortMode, type SqlLogicStatement, type SqlLogicType, } from "./sqllogictest.js";
@@ -290,22 +290,31 @@ class FaultInjectingBlockStore {
290
290
  import { blockStoreConformanceCases, runBlockStoreConformance } from "./block-store-conformance.js";
291
291
  import { MemoryOpfs } from "./opfs-shim.js";
292
292
  import { DeterministicScheduler, generateSimulationPlan, parseSimulationPlan, runSimulation, scheduledBlockStore } from "./simulator.js";
293
+ import { InteractionFailure, createDatabaseDriver, evaluate, generateInteractionPlan, parseInteractionPlan, renderLiteral, renderPredicate, runInteractionPlan } from "./interaction-simulator.js";
293
294
  import { SqlLogicFailure, SqlLogicParseError, md5Hex, parseSqlLogicTest, parseSqlLogicTestLines, renderSqlLogicValue, runSqlLogicTest } from "./sqllogictest.js";
294
295
  export {
295
296
  DeterministicScheduler,
296
297
  FaultInjectingBlockStore,
298
+ InteractionFailure,
297
299
  MemoryOpfs,
298
300
  SqlLogicFailure,
299
301
  SqlLogicParseError,
300
302
  blockStoreConformanceCases,
303
+ createDatabaseDriver,
304
+ evaluate as evaluatePlanPredicate,
301
305
  faultPoints,
306
+ generateInteractionPlan,
302
307
  generateSimulationPlan,
303
308
  md5Hex,
309
+ parseInteractionPlan,
304
310
  parseSimulationPlan,
305
311
  parseSqlLogicTest,
306
312
  parseSqlLogicTestLines,
313
+ renderLiteral as renderPlanLiteral,
314
+ renderPredicate as renderPlanPredicate,
307
315
  renderSqlLogicValue,
308
316
  runBlockStoreConformance,
317
+ runInteractionPlan,
309
318
  runSimulation,
310
319
  runSqlLogicTest,
311
320
  scheduledBlockStore