@minnowdb/core 0.10.0 → 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.
Files changed (37) hide show
  1. package/dist/engine/auto-store.d.ts +15 -3
  2. package/dist/engine/auto-store.js +48 -6
  3. package/dist/engine/client.js +23 -7
  4. package/dist/engine/database.js +347 -862
  5. package/dist/engine/index-terms.js +627 -0
  6. package/dist/engine/index.d.ts +1 -1
  7. package/dist/engine/index.js +2 -1
  8. package/dist/engine/live-maintenance.js +220 -0
  9. package/dist/engine/schema.js +2 -1
  10. package/dist/engine/sql-functions.js +3 -2
  11. package/dist/engine/sql-quote.js +6 -0
  12. package/dist/engine/vector.js +1 -3
  13. package/dist/engine/worker-host.js +2 -0
  14. package/dist/engine/worker-server.js +4 -5
  15. package/dist/engine/worker-store-auto.js +2 -2
  16. package/dist/engine/write-coordinator.js +21 -1
  17. package/dist/storage/indexeddb.d.ts +18 -0
  18. package/dist/storage/indexeddb.js +293 -211
  19. package/dist/storage/opfs/coordination-helpers.js +54 -0
  20. package/dist/storage/opfs/index.d.ts +1 -1
  21. package/dist/storage/opfs/index.js +3 -2
  22. package/dist/storage/opfs/leader.js +44 -5
  23. package/dist/storage/opfs/rpc.js +3 -1
  24. package/dist/storage/opfs/store.d.ts +12 -0
  25. package/dist/storage/opfs/store.js +59 -12
  26. package/dist/storage/toolkit/wal.js +16 -0
  27. package/dist/storage/toolkit/wire.js +3 -3
  28. package/dist/storage/types.d.ts +35 -4
  29. package/dist/storage/types.js +17 -4
  30. package/dist/testing/block-store-conformance.js +40 -1
  31. package/dist/testing/index.d.ts +1 -0
  32. package/dist/testing/index.js +9 -0
  33. package/dist/testing/interaction-simulator.d.ts +319 -0
  34. package/dist/testing/interaction-simulator.js +1631 -0
  35. package/dist/transactions/index.d.ts +7 -0
  36. package/dist/transactions/index.js +17 -3
  37. package/package.json +4 -1
@@ -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();
@@ -253,8 +264,7 @@ class IndexedDbBlockStore {
253
264
  return result;
254
265
  } catch (error) {
255
266
  abortIfActive(transaction);
256
- await ignoreAbort(transaction);
257
- throw error;
267
+ throw await refusalBehindAbort(transaction, error);
258
268
  }
259
269
  }
260
270
  async readManifestBlock(version, id) {
@@ -288,8 +298,7 @@ class IndexedDbBlockStore {
288
298
  return bytes;
289
299
  } catch (error) {
290
300
  abortIfActive(transaction);
291
- await ignoreAbort(transaction);
292
- throw error;
301
+ throw await refusalBehindAbort(transaction, error);
293
302
  }
294
303
  }
295
304
  async hasManifestBlocks(version, ids) {
@@ -314,8 +323,7 @@ class IndexedDbBlockStore {
314
323
  return result;
315
324
  } catch (error) {
316
325
  abortIfActive(transaction);
317
- await ignoreAbort(transaction);
318
- throw error;
326
+ throw await refusalBehindAbort(transaction, error);
319
327
  }
320
328
  }
321
329
  async listManifestBlockPage(input) {
@@ -346,8 +354,7 @@ class IndexedDbBlockStore {
346
354
  };
347
355
  } catch (error) {
348
356
  abortIfActive(transaction);
349
- await ignoreAbort(transaction);
350
- throw error;
357
+ throw await refusalBehindAbort(transaction, error);
351
358
  }
352
359
  }
353
360
  async listRetiredManifestBlockPage(input) {
@@ -372,8 +379,7 @@ class IndexedDbBlockStore {
372
379
  };
373
380
  } catch (error) {
374
381
  abortIfActive(transaction);
375
- await ignoreAbort(transaction);
376
- throw error;
382
+ throw await refusalBehindAbort(transaction, error);
377
383
  }
378
384
  }
379
385
  async putTempRunPage(page) {
@@ -392,8 +398,7 @@ class IndexedDbBlockStore {
392
398
  await transactionDone(transaction);
393
399
  } catch (error) {
394
400
  abortIfActive(transaction);
395
- await ignoreAbort(transaction);
396
- throw error;
401
+ throw await refusalBehindAbort(transaction, error);
397
402
  }
398
403
  }
399
404
  async getTempRunPage(ownerId, runId, pageIndex) {
@@ -412,8 +417,7 @@ class IndexedDbBlockStore {
412
417
  await transactionDone(transaction);
413
418
  } catch (error) {
414
419
  abortIfActive(transaction);
415
- await ignoreAbort(transaction);
416
- throw error;
420
+ throw await refusalBehindAbort(transaction, error);
417
421
  }
418
422
  }
419
423
  async removeTempOwner(ownerId) {
@@ -425,8 +429,7 @@ class IndexedDbBlockStore {
425
429
  await transactionDone(transaction);
426
430
  } catch (error) {
427
431
  abortIfActive(transaction);
428
- await ignoreAbort(transaction);
429
- throw error;
432
+ throw await refusalBehindAbort(transaction, error);
430
433
  }
431
434
  }
432
435
  async createTempOwner(record) {
@@ -448,8 +451,7 @@ class IndexedDbBlockStore {
448
451
  await transactionDone(transaction);
449
452
  } catch (error) {
450
453
  abortIfActive(transaction);
451
- await ignoreAbort(transaction);
452
- throw error;
454
+ throw await refusalBehindAbort(transaction, error);
453
455
  }
454
456
  }
455
457
  async getTempOwner(ownerId) {
@@ -589,8 +591,7 @@ class IndexedDbBlockStore {
589
591
  await transactionDone(transaction);
590
592
  } catch (error) {
591
593
  abortIfActive(transaction);
592
- await ignoreAbort(transaction);
593
- throw error;
594
+ throw await refusalBehindAbort(transaction, error);
594
595
  }
595
596
  }
596
597
  async getTable(id) {
@@ -780,8 +781,7 @@ class IndexedDbBlockStore {
780
781
  return updated;
781
782
  } catch (error) {
782
783
  abortIfActive(transaction);
783
- await ignoreAbort(transaction);
784
- throw error;
784
+ throw await refusalBehindAbort(transaction, error);
785
785
  }
786
786
  }
787
787
  async beginUniqueKeyBuild(input) {
@@ -836,8 +836,7 @@ class IndexedDbBlockStore {
836
836
  return structuredClone(record);
837
837
  } catch (error) {
838
838
  abortIfActive(transaction);
839
- await ignoreAbort(transaction);
840
- throw error;
839
+ throw await refusalBehindAbort(transaction, error);
841
840
  }
842
841
  }
843
842
  async getUniqueKeyBuild(buildId) {
@@ -872,8 +871,7 @@ class IndexedDbBlockStore {
872
871
  return structuredClone(record);
873
872
  } catch (error) {
874
873
  abortIfActive(transaction);
875
- await ignoreAbort(transaction);
876
- throw error;
874
+ throw await refusalBehindAbort(transaction, error);
877
875
  }
878
876
  }
879
877
  async appendUniqueKeyBuildChunk(input) {
@@ -946,8 +944,7 @@ class IndexedDbBlockStore {
946
944
  return structuredClone(record);
947
945
  } catch (error) {
948
946
  abortIfActive(transaction);
949
- await ignoreAbort(transaction);
950
- throw error;
947
+ throw await refusalBehindAbort(transaction, error);
951
948
  }
952
949
  }
953
950
  async finishUniqueKeyBuild(input) {
@@ -1039,8 +1036,7 @@ class IndexedDbBlockStore {
1039
1036
  return structuredClone(updated);
1040
1037
  } catch (error) {
1041
1038
  abortIfActive(transaction);
1042
- await ignoreAbort(transaction);
1043
- throw error;
1039
+ throw await refusalBehindAbort(transaction, error);
1044
1040
  }
1045
1041
  }
1046
1042
  async abortUniqueKeyBuild(input) {
@@ -1067,8 +1063,7 @@ class IndexedDbBlockStore {
1067
1063
  await transactionDone(transaction);
1068
1064
  } catch (error) {
1069
1065
  abortIfActive(transaction);
1070
- await ignoreAbort(transaction);
1071
- throw error;
1066
+ throw await refusalBehindAbort(transaction, error);
1072
1067
  }
1073
1068
  await this.#cleanupUniqueKeyBuildArtifacts(input.buildId);
1074
1069
  return true;
@@ -1093,8 +1088,7 @@ class IndexedDbBlockStore {
1093
1088
  }
1094
1089
  } catch (error) {
1095
1090
  abortIfActive(transaction);
1096
- await ignoreAbort(transaction);
1097
- throw error;
1091
+ throw await refusalBehindAbort(transaction, error);
1098
1092
  }
1099
1093
  }
1100
1094
  async dropTable(input) {
@@ -1181,14 +1175,13 @@ class IndexedDbBlockStore {
1181
1175
  this.#journalCache = void 0;
1182
1176
  if (this.#uniqueKeyCache?.tableId === input.tableId || this.#uniqueKeyCache?.tableId.startsWith(`${input.tableId}\0secondary-index\0`) === true) {
1183
1177
  this.#uniqueKeyCache = void 0;
1184
- } else if (this.#uniqueKeyCache !== void 0) {
1185
- this.#uniqueKeyCache.version = manifest.version;
1178
+ } else {
1179
+ this.#carryUniqueKeyCache(input.expectedManifestVersion, manifest.version);
1186
1180
  }
1187
1181
  return manifest;
1188
1182
  } catch (error) {
1189
1183
  abortIfActive(transaction);
1190
- await ignoreAbort(transaction);
1191
- throw error;
1184
+ throw await refusalBehindAbort(transaction, error);
1192
1185
  }
1193
1186
  }
1194
1187
  async dropTableColumn(input) {
@@ -1320,13 +1313,11 @@ class IndexedDbBlockStore {
1320
1313
  await transactionDone(transaction);
1321
1314
  this.#manifestCache = void 0;
1322
1315
  this.#journalCache = void 0;
1323
- if (this.#uniqueKeyCache !== void 0)
1324
- this.#uniqueKeyCache.version = manifest.version;
1316
+ this.#carryUniqueKeyCache(input.expectedManifestVersion, manifest.version);
1325
1317
  return manifest;
1326
1318
  } catch (error) {
1327
1319
  abortIfActive(transaction);
1328
- await ignoreAbort(transaction);
1329
- throw error;
1320
+ throw await refusalBehindAbort(transaction, error);
1330
1321
  }
1331
1322
  }
1332
1323
  async writeFtsBase(tableId, columnId, input) {
@@ -1462,8 +1453,7 @@ class IndexedDbBlockStore {
1462
1453
  await transactionDone(transaction);
1463
1454
  } catch (error) {
1464
1455
  abortIfActive(transaction);
1465
- await ignoreAbort(transaction);
1466
- throw error;
1456
+ throw await refusalBehindAbort(transaction, error);
1467
1457
  }
1468
1458
  }
1469
1459
  async writeFtsBaseBuildChunk(input) {
@@ -1881,8 +1871,7 @@ class IndexedDbBlockStore {
1881
1871
  };
1882
1872
  } catch (error) {
1883
1873
  abortIfActive(transaction);
1884
- await ignoreAbort(transaction);
1885
- throw error;
1874
+ throw await refusalBehindAbort(transaction, error);
1886
1875
  }
1887
1876
  }
1888
1877
  async removeAbortedSegment(segmentId, expectedTransactionId) {
@@ -1958,8 +1947,7 @@ class IndexedDbBlockStore {
1958
1947
  return true;
1959
1948
  } catch (error) {
1960
1949
  abortIfActive(transaction);
1961
- await ignoreAbort(transaction);
1962
- throw error;
1950
+ throw await refusalBehindAbort(transaction, error);
1963
1951
  }
1964
1952
  }
1965
1953
  async adoptAbortedSegment(input) {
@@ -2100,8 +2088,7 @@ class IndexedDbBlockStore {
2100
2088
  return structuredClone(updatedReplacement);
2101
2089
  } catch (error) {
2102
2090
  abortIfActive(transaction);
2103
- await ignoreAbort(transaction);
2104
- throw error;
2091
+ throw await refusalBehindAbort(transaction, error);
2105
2092
  }
2106
2093
  }
2107
2094
  async reserveRowIds(tableId, count) {
@@ -2123,8 +2110,7 @@ class IndexedDbBlockStore {
2123
2110
  return { start, endExclusive };
2124
2111
  } catch (error) {
2125
2112
  abortIfActive(transaction);
2126
- await ignoreAbort(transaction);
2127
- throw error;
2113
+ throw await refusalBehindAbort(transaction, error);
2128
2114
  }
2129
2115
  }
2130
2116
  async reserveAutoIncrement(tableId, columnId, count, atLeast) {
@@ -2145,8 +2131,17 @@ class IndexedDbBlockStore {
2145
2131
  return { start, endExclusive };
2146
2132
  } catch (error) {
2147
2133
  abortIfActive(transaction);
2148
- await ignoreAbort(transaction);
2149
- throw error;
2134
+ throw await refusalBehindAbort(transaction, error);
2135
+ }
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;
2150
2145
  }
2151
2146
  }
2152
2147
  async getExistingUniqueKeys(tableId, keyTokens) {
@@ -2255,8 +2250,7 @@ class IndexedDbBlockStore {
2255
2250
  };
2256
2251
  } catch (error) {
2257
2252
  abortIfActive(transaction);
2258
- await ignoreAbort(transaction);
2259
- throw error;
2253
+ throw await refusalBehindAbort(transaction, error);
2260
2254
  }
2261
2255
  }
2262
2256
  async getCurrentManifest() {
@@ -2393,8 +2387,7 @@ class IndexedDbBlockStore {
2393
2387
  };
2394
2388
  } catch (error) {
2395
2389
  abortIfActive(transaction);
2396
- await ignoreAbort(transaction);
2397
- throw error;
2390
+ throw await refusalBehindAbort(transaction, error);
2398
2391
  }
2399
2392
  }
2400
2393
  async createTransaction(record) {
@@ -2440,8 +2433,7 @@ class IndexedDbBlockStore {
2440
2433
  await transactionDone(transaction);
2441
2434
  } catch (error) {
2442
2435
  abortIfActive(transaction);
2443
- await ignoreAbort(transaction);
2444
- throw error;
2436
+ throw await refusalBehindAbort(transaction, error);
2445
2437
  }
2446
2438
  }
2447
2439
  async getTransaction(id) {
@@ -2511,15 +2503,14 @@ class IndexedDbBlockStore {
2511
2503
  });
2512
2504
  }
2513
2505
  if (addedBlocks.length > 0 || addedSegments.length > 0) {
2514
- appendTransactionJournalChunks(transaction, stored, packTransactionJournal(current.pendingBlockIds, current.pendingSegmentIds).at(-1), addedBlocks, addedSegments);
2506
+ appendTransactionJournalChunks(transaction, stored, await readTransactionJournalTail(transaction, stored), addedBlocks, addedSegments);
2515
2507
  }
2516
2508
  putStoredTransactionRecord(transaction, storedTransactionRecord(updated));
2517
2509
  await transactionDone(transaction);
2518
2510
  return updated;
2519
2511
  } catch (error) {
2520
2512
  abortIfActive(transaction);
2521
- await ignoreAbort(transaction);
2522
- throw error;
2513
+ throw await refusalBehindAbort(transaction, error);
2523
2514
  }
2524
2515
  }
2525
2516
  async renewTransaction(input) {
@@ -2550,8 +2541,7 @@ class IndexedDbBlockStore {
2550
2541
  return true;
2551
2542
  } catch (error) {
2552
2543
  abortIfActive(transaction);
2553
- await ignoreAbort(transaction);
2554
- throw error;
2544
+ throw await refusalBehindAbort(transaction, error);
2555
2545
  }
2556
2546
  }
2557
2547
  async abortTransactionIfExpired(input) {
@@ -2588,8 +2578,7 @@ class IndexedDbBlockStore {
2588
2578
  return structuredClone(updated);
2589
2579
  } catch (error) {
2590
2580
  abortIfActive(transaction);
2591
- await ignoreAbort(transaction);
2592
- throw error;
2581
+ throw await refusalBehindAbort(transaction, error);
2593
2582
  }
2594
2583
  }
2595
2584
  async stageTransactionArtifacts(input) {
@@ -2694,8 +2683,7 @@ class IndexedDbBlockStore {
2694
2683
  return transactionRecordWithJournal(updated, journal.blockIds, journal.segmentIds);
2695
2684
  } catch (error) {
2696
2685
  abortIfActive(transaction);
2697
- await ignoreAbort(transaction);
2698
- throw error;
2686
+ throw await refusalBehindAbort(transaction, error);
2699
2687
  }
2700
2688
  }
2701
2689
  async #journaledIds(transaction, stored, tail) {
@@ -2849,8 +2837,7 @@ class IndexedDbBlockStore {
2849
2837
  return structuredClone(updated);
2850
2838
  } catch (error) {
2851
2839
  abortIfActive(transaction);
2852
- await ignoreAbort(transaction);
2853
- throw error;
2840
+ throw await refusalBehindAbort(transaction, error);
2854
2841
  }
2855
2842
  }
2856
2843
  async commitTransaction(input) {
@@ -2881,8 +2868,7 @@ class IndexedDbBlockStore {
2881
2868
  return outcome.manifest;
2882
2869
  } catch (error) {
2883
2870
  abortIfActive(transaction);
2884
- await ignoreAbort(transaction);
2885
- throw error;
2871
+ throw await refusalBehindAbort(transaction, error);
2886
2872
  }
2887
2873
  }
2888
2874
  async writeTransaction(input) {
@@ -3014,8 +3000,7 @@ class IndexedDbBlockStore {
3014
3000
  return outcome.manifest;
3015
3001
  } catch (error) {
3016
3002
  abortIfActive(transaction);
3017
- await ignoreAbort(transaction);
3018
- throw error;
3003
+ throw await refusalBehindAbort(transaction, error);
3019
3004
  }
3020
3005
  }
3021
3006
  async #commitInTransaction(transaction, record, journal, input) {
@@ -3076,15 +3061,11 @@ class IndexedDbBlockStore {
3076
3061
  if (pendingRemovedBlock !== void 0) {
3077
3062
  throw new Error(`Cannot supersede a pending block: ${pendingRemovedBlock}`);
3078
3063
  }
3079
- let baseBlockIdSet;
3080
3064
  const removedMembership = await manifestBlockMembershipInTransaction(catalog, input.expectedManifestVersion, removedBlockIds);
3081
3065
  const invalidRemovedIndex = removedMembership.findIndex((present) => !present);
3082
3066
  if (invalidRemovedIndex >= 0) {
3083
3067
  throw new Error(`Cannot supersede a block outside the transaction snapshot: ${removedBlockIds[invalidRemovedIndex] ?? ""}`);
3084
3068
  }
3085
- if ((input.levelZeroSegmentLimits?.length ?? 0) > 0) {
3086
- baseBlockIdSet = await resolveManifestBlockSetInTransaction(catalog, input.expectedManifestVersion);
3087
- }
3088
3069
  if (removedBlockIds.length > 0) {
3089
3070
  await assertCompactionRetirementInTransaction(transaction, record, input, removedBlockIds);
3090
3071
  }
@@ -3106,7 +3087,7 @@ class IndexedDbBlockStore {
3106
3087
  throw new Error(`Segment ${foreignSegment.id} belongs to another transaction`);
3107
3088
  }
3108
3089
  await assertCompactionOutputSegmentsInTransaction(transaction, record, input.compactionJobId, pendingSegments);
3109
- await assertLevelZeroSegmentLimits(transaction, input.levelZeroSegmentLimits ?? [], pendingSegments, baseBlockIdSet ?? /* @__PURE__ */ new Set(), pendingTable);
3090
+ await assertLevelZeroSegmentLimits(transaction, input.levelZeroSegmentLimits ?? [], pendingSegments, input.expectedManifestVersion, pendingTable);
3110
3091
  const uniqueKeyEntries = input.uniqueKeyChanges ?? [];
3111
3092
  const coveredUniqueNamespaces = new Set(uniqueKeyEntries.map((entry) => entry.tableId));
3112
3093
  const changedTableIds = /* @__PURE__ */ new Set([
@@ -3509,13 +3490,11 @@ class IndexedDbBlockStore {
3509
3490
  } : void 0;
3510
3491
  } else if (this.#uniqueKeyCache?.tableId === uniqueKeyChanges.tableId) {
3511
3492
  this.#uniqueKeyCache = void 0;
3512
- } else if (this.#uniqueKeyCache !== void 0) {
3513
- this.#uniqueKeyCache.version = manifest.version;
3493
+ } else {
3494
+ this.#carryUniqueKeyCache(input.expectedManifestVersion, manifest.version);
3514
3495
  }
3515
- } else if (uniqueKeyChanges !== void 0 && this.#uniqueKeyCache?.tableId === uniqueKeyChanges.tableId) {
3516
- this.#uniqueKeyCache.version = manifest.version;
3517
- } else if (this.#uniqueKeyCache !== void 0) {
3518
- this.#uniqueKeyCache.version = manifest.version;
3496
+ } else {
3497
+ this.#carryUniqueKeyCache(input.expectedManifestVersion, manifest.version);
3519
3498
  }
3520
3499
  this.#manifestCache = void 0;
3521
3500
  this.#journalCache = void 0;
@@ -3547,8 +3526,7 @@ class IndexedDbBlockStore {
3547
3526
  await transactionDone(transaction);
3548
3527
  } catch (error) {
3549
3528
  abortIfActive(transaction);
3550
- await ignoreAbort(transaction);
3551
- throw error;
3529
+ throw await refusalBehindAbort(transaction, error);
3552
3530
  }
3553
3531
  }
3554
3532
  async getLease(id) {
@@ -3627,8 +3605,7 @@ class IndexedDbBlockStore {
3627
3605
  return structuredClone(renewed);
3628
3606
  } catch (error) {
3629
3607
  abortIfActive(transaction);
3630
- await ignoreAbort(transaction);
3631
- throw error;
3608
+ throw await refusalBehindAbort(transaction, error);
3632
3609
  }
3633
3610
  }
3634
3611
  async moveLease(input) {
@@ -3669,8 +3646,7 @@ class IndexedDbBlockStore {
3669
3646
  return structuredClone(moved);
3670
3647
  } catch (error) {
3671
3648
  abortIfActive(transaction);
3672
- await ignoreAbort(transaction);
3673
- throw error;
3649
+ throw await refusalBehindAbort(transaction, error);
3674
3650
  }
3675
3651
  }
3676
3652
  async removeLeaseIfExpired(id, expectedRevision, expiresAtCutoff) {
@@ -3937,8 +3913,7 @@ class IndexedDbBlockStore {
3937
3913
  return true;
3938
3914
  } catch (error) {
3939
3915
  abortIfActive(transaction);
3940
- await ignoreAbort(transaction);
3941
- throw error;
3916
+ throw await refusalBehindAbort(transaction, error);
3942
3917
  }
3943
3918
  }
3944
3919
  async createGarbageCollectionJob(input) {
@@ -3953,13 +3928,13 @@ class IndexedDbBlockStore {
3953
3928
  const quota = await readMaintenanceQuota(gcStore);
3954
3929
  let activeGarbageCollectionJobs;
3955
3930
  if (record.state !== "completed") {
3956
- activeGarbageCollectionJobs = checkedQuotaIncrement(quota.activeGarbageCollectionJobs, "Active garbage collection job count");
3957
- assertMaintenanceLimit("garbage collection job", activeGarbageCollectionJobs, MAX_ACTIVE_GARBAGE_COLLECTION_JOBS);
3958
3931
  const markerValue = await requestResult(gcStore.get(ACTIVE_GARBAGE_COLLECTION_KEY));
3959
3932
  if (markerValue !== void 0) {
3960
3933
  const marker = asActiveGarbageCollectionMarker(markerValue);
3961
- throw new Error(`Garbage collection job ${marker.jobId} is already active`);
3934
+ throw new GarbageCollectionJobConflictError(marker.jobId, record.revision, null);
3962
3935
  }
3936
+ activeGarbageCollectionJobs = checkedQuotaIncrement(quota.activeGarbageCollectionJobs, "Active garbage collection job count");
3937
+ assertMaintenanceLimit("garbage collection job", activeGarbageCollectionJobs, MAX_ACTIVE_GARBAGE_COLLECTION_JOBS);
3963
3938
  }
3964
3939
  await assertGarbageCollectionCandidateProvenanceInTransaction(transaction, record);
3965
3940
  if (record.state !== "completed") {
@@ -3978,8 +3953,7 @@ class IndexedDbBlockStore {
3978
3953
  return structuredClone(record);
3979
3954
  } catch (error) {
3980
3955
  abortIfActive(transaction);
3981
- await ignoreAbort(transaction);
3982
- throw error;
3956
+ throw await refusalBehindAbort(transaction, error);
3983
3957
  }
3984
3958
  }
3985
3959
  async updateGarbageCollectionPlanning(input) {
@@ -4003,12 +3977,14 @@ class IndexedDbBlockStore {
4003
3977
  candidateTransactionIds: input.candidateTransactionIds ?? []
4004
3978
  });
4005
3979
  gcStore.put(garbageCollectionJobEnvelope(updated), key);
3980
+ if (updated.state === "completed" && current.state !== "completed") {
3981
+ await releaseGarbageCollectionAdmission(gcStore);
3982
+ }
4006
3983
  await transactionDone(transaction);
4007
3984
  return structuredClone(updated);
4008
3985
  } catch (error) {
4009
3986
  abortIfActive(transaction);
4010
- await ignoreAbort(transaction);
4011
- throw error;
3987
+ throw await refusalBehindAbort(transaction, error);
4012
3988
  }
4013
3989
  }
4014
3990
  async getGarbageCollectionJob(id) {
@@ -4228,17 +4204,8 @@ class IndexedDbBlockStore {
4228
4204
  updatedAt: input.updatedAt
4229
4205
  });
4230
4206
  gcStore.put(garbageCollectionJobEnvelope(updated), key);
4231
- if (updated.state === "completed") {
4232
- const quota = await readMaintenanceQuota(gcStore);
4233
- const completedGarbageCollectionJobs = checkedQuotaIncrement(quota.completedGarbageCollectionJobs, "Completed garbage collection job count");
4234
- assertMaintenanceLimit("completed garbage collection job", completedGarbageCollectionJobs, MAX_COMPLETED_GARBAGE_COLLECTION_JOB_RECORDS);
4235
- gcStore.put({
4236
- ...quota,
4237
- activeGarbageCollectionJobs: checkedQuotaDecrement(quota.activeGarbageCollectionJobs, "active garbage collection job count"),
4238
- completedGarbageCollectionJobs
4239
- }, MAINTENANCE_QUOTA_KEY);
4240
- await requestResult(gcStore.delete(ACTIVE_GARBAGE_COLLECTION_KEY));
4241
- }
4207
+ if (updated.state === "completed")
4208
+ await releaseGarbageCollectionAdmission(gcStore);
4242
4209
  await transactionDone(transaction);
4243
4210
  return {
4244
4211
  job: structuredClone(updated),
@@ -4259,8 +4226,7 @@ class IndexedDbBlockStore {
4259
4226
  };
4260
4227
  } catch (error) {
4261
4228
  abortIfActive(transaction);
4262
- await ignoreAbort(transaction);
4263
- throw error;
4229
+ throw await refusalBehindAbort(transaction, error);
4264
4230
  }
4265
4231
  }
4266
4232
  async removePrunedManifestRecords(maxItems) {
@@ -4322,8 +4288,7 @@ class IndexedDbBlockStore {
4322
4288
  return removed;
4323
4289
  } catch (error) {
4324
4290
  abortIfActive(transaction);
4325
- await ignoreAbort(transaction);
4326
- throw error;
4291
+ throw await refusalBehindAbort(transaction, error);
4327
4292
  }
4328
4293
  }
4329
4294
  async removeGarbageCollectionJob(id) {
@@ -4426,8 +4391,7 @@ class IndexedDbBlockStore {
4426
4391
  };
4427
4392
  } catch (error) {
4428
4393
  abortIfActive(transaction);
4429
- await ignoreAbort(transaction);
4430
- throw error;
4394
+ throw await refusalBehindAbort(transaction, error);
4431
4395
  }
4432
4396
  }
4433
4397
  async getStorageStats() {
@@ -5619,8 +5583,7 @@ class IndexedDbBlockStore {
5619
5583
  return { sessionId, ownerId: input.ownerId, expiresAt: input.expiresAt, header };
5620
5584
  } catch (error) {
5621
5585
  abortIfActive(transaction);
5622
- await ignoreAbort(transaction);
5623
- throw error;
5586
+ throw await refusalBehindAbort(transaction, error);
5624
5587
  }
5625
5588
  }
5626
5589
  async readSnapshotExportFrame(input) {
@@ -5696,8 +5659,7 @@ class IndexedDbBlockStore {
5696
5659
  return frame;
5697
5660
  } catch (error) {
5698
5661
  abortIfActive(transaction);
5699
- await ignoreAbort(transaction);
5700
- throw error;
5662
+ throw await refusalBehindAbort(transaction, error);
5701
5663
  }
5702
5664
  }
5703
5665
  async closeSnapshotFrameExport(input) {
@@ -5725,8 +5687,7 @@ class IndexedDbBlockStore {
5725
5687
  return true;
5726
5688
  } catch (error) {
5727
5689
  abortIfActive(transaction);
5728
- await ignoreAbort(transaction);
5729
- throw error;
5690
+ throw await refusalBehindAbort(transaction, error);
5730
5691
  }
5731
5692
  }
5732
5693
  async beginSnapshotFrameImport(input) {
@@ -5857,8 +5818,7 @@ class IndexedDbBlockStore {
5857
5818
  return snapshotFrameImportSession(marker);
5858
5819
  } catch (error) {
5859
5820
  abortIfActive(transaction);
5860
- await ignoreAbort(transaction);
5861
- throw error;
5821
+ throw await refusalBehindAbort(transaction, error);
5862
5822
  }
5863
5823
  }
5864
5824
  async renewSnapshotFrameImport(input) {
@@ -5876,8 +5836,7 @@ class IndexedDbBlockStore {
5876
5836
  return snapshotFrameImportSession(renewed);
5877
5837
  } catch (error) {
5878
5838
  abortIfActive(transaction);
5879
- await ignoreAbort(transaction);
5880
- throw error;
5839
+ throw await refusalBehindAbort(transaction, error);
5881
5840
  }
5882
5841
  }
5883
5842
  async appendSnapshotImportFrames(input) {
@@ -5998,8 +5957,7 @@ class IndexedDbBlockStore {
5998
5957
  return snapshotFrameImportSession(marker);
5999
5958
  } catch (error) {
6000
5959
  abortIfActive(transaction);
6001
- await ignoreAbort(transaction);
6002
- throw error;
5960
+ throw await refusalBehindAbort(transaction, error);
6003
5961
  }
6004
5962
  }
6005
5963
  async finishSnapshotFrameImport(input) {
@@ -6055,8 +6013,7 @@ class IndexedDbBlockStore {
6055
6013
  this.#journalCache = void 0;
6056
6014
  } catch (error) {
6057
6015
  abortIfActive(transaction);
6058
- await ignoreAbort(transaction);
6059
- throw error;
6016
+ throw await refusalBehindAbort(transaction, error);
6060
6017
  }
6061
6018
  }
6062
6019
  async cancelSnapshotFrameImport(input) {
@@ -6099,8 +6056,7 @@ class IndexedDbBlockStore {
6099
6056
  };
6100
6057
  } catch (error) {
6101
6058
  abortIfActive(transaction);
6102
- await ignoreAbort(transaction);
6103
- throw error;
6059
+ throw await refusalBehindAbort(transaction, error);
6104
6060
  }
6105
6061
  }
6106
6062
  close() {
@@ -6111,6 +6067,7 @@ class IndexedDbBlockStore {
6111
6067
  this.#uniqueKeyCache = void 0;
6112
6068
  this.#manifestCache = void 0;
6113
6069
  this.#journalCache = void 0;
6070
+ this.#stallGuard.dispose();
6114
6071
  this.#db.close();
6115
6072
  }
6116
6073
  _residentStateForTests() {
@@ -6157,8 +6114,7 @@ class IndexedDbBlockStore {
6157
6114
  return true;
6158
6115
  } catch (error) {
6159
6116
  abortIfActive(transaction);
6160
- await ignoreAbort(transaction);
6161
- throw error;
6117
+ throw await refusalBehindAbort(transaction, error);
6162
6118
  }
6163
6119
  }
6164
6120
  }
@@ -6174,8 +6130,7 @@ class IndexedDbBlockStore {
6174
6130
  return;
6175
6131
  } catch (error) {
6176
6132
  abortIfActive(transaction);
6177
- await ignoreAbort(transaction);
6178
- throw error;
6133
+ throw await refusalBehindAbort(transaction, error);
6179
6134
  }
6180
6135
  }
6181
6136
  }
@@ -6205,8 +6160,7 @@ class IndexedDbBlockStore {
6205
6160
  await transactionDone(transaction);
6206
6161
  } catch (error) {
6207
6162
  abortIfActive(transaction);
6208
- await ignoreAbort(transaction);
6209
- throw error;
6163
+ throw await refusalBehindAbort(transaction, error);
6210
6164
  }
6211
6165
  if (cleanup)
6212
6166
  await this.#cleanupUniqueKeyBuildArtifacts(buildId);
@@ -6237,8 +6191,7 @@ class IndexedDbBlockStore {
6237
6191
  return;
6238
6192
  } catch (error) {
6239
6193
  abortIfActive(transaction);
6240
- await ignoreAbort(transaction);
6241
- throw error;
6194
+ throw await refusalBehindAbort(transaction, error);
6242
6195
  }
6243
6196
  }
6244
6197
  }
@@ -6373,8 +6326,7 @@ class IndexedDbBlockStore {
6373
6326
  await transactionDone(transaction);
6374
6327
  } catch (error) {
6375
6328
  abortIfActive(transaction);
6376
- await ignoreAbort(transaction);
6377
- throw error;
6329
+ throw await refusalBehindAbort(transaction, error);
6378
6330
  }
6379
6331
  }
6380
6332
  async #cleanupFtsRetirementPage() {
@@ -6390,11 +6342,12 @@ class IndexedDbBlockStore {
6390
6342
  await transactionDone(transaction);
6391
6343
  } catch (error) {
6392
6344
  abortIfActive(transaction);
6393
- await ignoreAbort(transaction);
6394
- throw error;
6345
+ throw await refusalBehindAbort(transaction, error);
6395
6346
  }
6396
6347
  }
6397
6348
  #transaction(stores, mode, options = {}) {
6349
+ if (this.#unresponsive !== void 0)
6350
+ throw this.#unresponsive;
6398
6351
  if (this.#closed)
6399
6352
  throw new Error("This IndexedDB store connection is closed");
6400
6353
  const requested = typeof stores === "string" ? [stores] : stores;
@@ -6516,49 +6469,156 @@ async function readIndexedDbVersion(factory, name) {
6516
6469
  return null;
6517
6470
  }
6518
6471
  }
6519
- 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) {
6520
6548
  return new Promise((resolve, reject) => {
6521
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
+ };
6522
6564
  request.addEventListener("success", () => {
6523
- if (settled) {
6565
+ if (!settle()) {
6524
6566
  request.result.close();
6525
6567
  return;
6526
6568
  }
6527
- settled = true;
6528
6569
  resolve(request.result);
6529
6570
  }, { once: true });
6530
6571
  request.addEventListener("error", () => {
6531
- if (settled)
6572
+ if (!settle())
6532
6573
  return;
6533
- settled = true;
6534
6574
  reject(request.error ?? new Error("IndexedDB open failed"));
6535
6575
  }, { once: true });
6536
6576
  request.addEventListener("blocked", (event) => {
6537
- if (settled)
6577
+ if (!settle())
6538
6578
  return;
6539
- settled = true;
6540
6579
  reject(blockedError(event));
6541
6580
  }, { once: true });
6542
6581
  });
6543
6582
  }
6544
6583
  function requestResult(request) {
6584
+ const guard = stallGuardFor(request.transaction?.db);
6545
6585
  return new Promise((resolve, reject) => {
6546
- request.addEventListener("success", () => resolve(request.result), { once: true });
6547
- 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
+ }, {
6548
6595
  once: true
6549
6596
  });
6550
6597
  });
6551
6598
  }
6552
6599
  function readCursorPage(store, limit, decode, acceptKey, seekKey) {
6600
+ const guard = stallGuardFor(store.transaction.db);
6553
6601
  return new Promise((resolve, reject) => {
6554
6602
  const records = [];
6555
6603
  const request = store.openCursor();
6556
6604
  let seekPending = seekKey !== void 0;
6557
- 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
+ };
6558
6616
  request.onsuccess = () => {
6617
+ release?.();
6618
+ release = guard?.wait(reject);
6559
6619
  const cursor = request.result;
6560
6620
  if (cursor === null || records.length === limit) {
6561
- resolve(records);
6621
+ settle(records);
6562
6622
  return;
6563
6623
  }
6564
6624
  try {
@@ -6572,10 +6632,12 @@ function readCursorPage(store, limit, decode, acceptKey, seekKey) {
6572
6632
  if (acceptKey(cursor.key))
6573
6633
  records.push(decode(cursor.value, cursor.key));
6574
6634
  if (records.length === limit)
6575
- resolve(records);
6635
+ settle(records);
6576
6636
  else
6577
6637
  cursor.continue();
6578
6638
  } catch (error) {
6639
+ release?.();
6640
+ release = void 0;
6579
6641
  reject(error instanceof Error ? error : new Error(String(error)));
6580
6642
  }
6581
6643
  };
@@ -7035,9 +7097,17 @@ function transactionDone(transaction) {
7035
7097
  const existing = transactionCompletions.get(transaction);
7036
7098
  if (existing !== void 0)
7037
7099
  return existing;
7100
+ const guard = stallGuardFor(transaction.db);
7038
7101
  const done = new Promise((resolve, reject) => {
7039
- transaction.addEventListener("complete", () => resolve(), { once: true });
7040
- 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
+ }, {
7041
7111
  once: true
7042
7112
  });
7043
7113
  });
@@ -7050,6 +7120,14 @@ async function ignoreAbort(transaction) {
7050
7120
  } catch {
7051
7121
  }
7052
7122
  }
7123
+ async function refusalBehindAbort(transaction, error) {
7124
+ await ignoreAbort(transaction);
7125
+ const refusal = transaction.error;
7126
+ if (refusal !== null && error instanceof DOMException && error.name === "AbortError") {
7127
+ return refusal;
7128
+ }
7129
+ return error;
7130
+ }
7053
7131
  function corruption(location, message) {
7054
7132
  return new StorageCorruptionError("indexeddb", location, message);
7055
7133
  }
@@ -8929,8 +9007,12 @@ function asTableRecord(value, location = "catalog/table") {
8929
9007
  "defaultValue",
8930
9008
  "backfill",
8931
9009
  "enumValues",
8932
- "hidden"
9010
+ "hidden",
9011
+ "generatedValue"
8933
9012
  ], `${location}/columns/${String(index)}`);
9013
+ if (isRecord(column.generatedValue)) {
9014
+ assertKnownFields(column.generatedValue, ["kind", "sql"], `${location}/columns/${String(index)}/generatedValue`);
9015
+ }
8934
9016
  if (isRecord(column.sqlDomain)) {
8935
9017
  const domainFields = column.sqlDomain.kind === "numeric" ? ["kind", "precision", "scale"] : column.sqlDomain.kind === "array" ? ["kind", "element"] : column.sqlDomain.kind === "enum" ? ["kind", "name", "values"] : ["kind"];
8936
9018
  assertKnownFields(column.sqlDomain, domainFields, `${location}/columns/${String(index)}/sqlDomain`);
@@ -9447,7 +9529,7 @@ async function assertCompactionOutputSegmentsInTransaction(transaction, committi
9447
9529
  }));
9448
9530
  assertCompactionOutputProvenance(job, table, committingTransaction, sourceSegments, pendingSegments);
9449
9531
  }
9450
- async function assertLevelZeroSegmentLimits(transaction, limits, pendingSegments, currentBlockIds, pendingTable) {
9532
+ async function assertLevelZeroSegmentLimits(transaction, limits, pendingSegments, version, pendingTable) {
9451
9533
  const pendingTables = new Set(pendingSegments.filter((segment) => segment.level === 0).map((segment) => segment.tableId));
9452
9534
  if (limits.length !== pendingTables.size) {
9453
9535
  throw new TypeError("Level-zero segment limits must exactly cover pending level-zero tables");
@@ -9472,7 +9554,7 @@ async function assertLevelZeroSegmentLimits(transaction, limits, pendingSegments
9472
9554
  if (table === void 0) {
9473
9555
  throw new Error(`Level-zero segment limit references missing table: ${entry.tableId}`);
9474
9556
  }
9475
- const existing = await countVisibleLevelZeroSegments(segmentIndex, transaction.objectStore("transactions"), entry.tableId, currentBlockIds);
9557
+ const existing = await countVisibleLevelZeroSegments(segmentIndex, transaction.objectStore("transactions"), catalog, version, entry.tableId);
9476
9558
  const added = pendingSegments.reduce((count2, segment) => count2 + (segment.tableId === entry.tableId && segment.level === 0 ? 1 : 0), 0);
9477
9559
  const count = existing + added;
9478
9560
  if (count > entry.limit) {
@@ -9480,66 +9562,54 @@ async function assertLevelZeroSegmentLimits(transaction, limits, pendingSegments
9480
9562
  }
9481
9563
  }
9482
9564
  }
9483
- function countVisibleLevelZeroSegments(index, transactions, tableId, currentBlockIds) {
9484
- return new Promise((resolve, reject) => {
9485
- const statuses = /* @__PURE__ */ new Map();
9486
- let count = 0;
9565
+ async function countVisibleLevelZeroSegments(index, transactions, catalog, version, tableId) {
9566
+ const candidates = await new Promise((resolve, reject) => {
9567
+ const found = [];
9487
9568
  const request = index.openCursor(tableId);
9488
9569
  request.onerror = () => reject(request.error ?? new Error("IndexedDB segment cursor failed"));
9489
9570
  request.onsuccess = () => {
9490
9571
  const cursor = request.result;
9491
9572
  if (cursor === null) {
9492
- resolve(count);
9573
+ resolve(found);
9493
9574
  return;
9494
9575
  }
9495
- let segment;
9496
9576
  try {
9497
- segment = asSegmentRecord(cursor.value);
9577
+ const segment = asSegmentRecord(cursor.value);
9498
9578
  if (segment.id !== cursor.primaryKey || segment.tableId !== tableId) {
9499
9579
  throw corruption(`segments/${segment.id}`, "table index does not match its record");
9500
9580
  }
9501
- if (segment.level !== 0) {
9502
- cursor.continue();
9503
- return;
9504
- }
9505
- const blockIds = segmentBlockIds(segment);
9506
- const visibleBlockCount = blockIds.filter((id) => currentBlockIds.has(id)).length;
9507
- if (visibleBlockCount > 0 && visibleBlockCount < blockIds.length) {
9508
- throw corruption(`segments/${segment.id}`, "only part of the segment is manifest-live");
9509
- }
9510
- if (blockIds.length > 0 && visibleBlockCount === 0) {
9511
- cursor.continue();
9512
- return;
9513
- }
9581
+ if (segment.level === 0)
9582
+ found.push(segment);
9583
+ cursor.continue();
9514
9584
  } catch (error) {
9515
9585
  reject(error instanceof Error ? error : new Error(String(error)));
9516
- return;
9517
- }
9518
- const knownStatus = statuses.get(segment.transactionId);
9519
- if (knownStatus !== void 0) {
9520
- if (knownStatus === "committed")
9521
- count += 1;
9522
- cursor.continue();
9523
- return;
9524
9586
  }
9525
- const ownerRequest = transactions.get(segment.transactionId);
9526
- ownerRequest.onerror = () => reject(ownerRequest.error ?? new Error("IndexedDB transaction lookup failed"));
9527
- ownerRequest.onsuccess = () => {
9528
- try {
9529
- if (ownerRequest.result === void 0) {
9530
- throw corruption(`segments/${segment.id}`, `owning transaction ${segment.transactionId} is missing`);
9531
- }
9532
- const owner = asStoredTransactionRecord(ownerRequest.result, segment.transactionId);
9533
- statuses.set(owner.id, owner.status);
9534
- if (owner.status === "committed")
9535
- count += 1;
9536
- cursor.continue();
9537
- } catch (error) {
9538
- reject(error instanceof Error ? error : new Error(String(error)));
9539
- }
9540
- };
9541
9587
  };
9542
9588
  });
9589
+ const memberships = await Promise.all(candidates.map((segment) => manifestBlockMembershipInTransaction(catalog, version, segmentBlockIds(segment))));
9590
+ const statuses = /* @__PURE__ */ new Map();
9591
+ let count = 0;
9592
+ for (const [position, segment] of candidates.entries()) {
9593
+ const membership = memberships[position] ?? [];
9594
+ const visibleBlockCount = membership.filter((present) => present).length;
9595
+ if (visibleBlockCount > 0 && visibleBlockCount < membership.length) {
9596
+ throw corruption(`segments/${segment.id}`, "only part of the segment is manifest-live");
9597
+ }
9598
+ if (membership.length > 0 && visibleBlockCount === 0)
9599
+ continue;
9600
+ let status = statuses.get(segment.transactionId);
9601
+ if (status === void 0) {
9602
+ const owner = await requestResult(transactions.get(segment.transactionId));
9603
+ if (owner === void 0) {
9604
+ throw corruption(`segments/${segment.id}`, `owning transaction ${segment.transactionId} is missing`);
9605
+ }
9606
+ status = asStoredTransactionRecord(owner, segment.transactionId).status;
9607
+ statuses.set(segment.transactionId, status);
9608
+ }
9609
+ if (status === "committed")
9610
+ count += 1;
9611
+ }
9612
+ return count;
9543
9613
  }
9544
9614
  async function compactionJobRemovalPreservesProvenance(transaction, removedJob) {
9545
9615
  const rootedBlocks = /* @__PURE__ */ new Set();
@@ -9799,6 +9869,17 @@ async function assertActiveCompactionMarker(store, record) {
9799
9869
  throw corruption(`gc/${activeCompactionKey(record.tableId)}`, `marker names job ${marker.jobId} instead of ${record.id}`);
9800
9870
  }
9801
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
+ }
9802
9883
  async function assertActiveGarbageCollectionMarker(store, record) {
9803
9884
  const marker = asActiveGarbageCollectionMarker(await requestResult(store.get(ACTIVE_GARBAGE_COLLECTION_KEY)));
9804
9885
  if (marker.jobId !== record.id) {
@@ -12959,5 +13040,6 @@ function utf8Bytes(value) {
12959
13040
  return storageTextEncoder.encode(value).byteLength;
12960
13041
  }
12961
13042
  export {
13043
+ INDEXEDDB_UNRESPONSIVE_AFTER_MS,
12962
13044
  IndexedDbBlockStore
12963
13045
  };