@minnowdb/core 0.6.1 → 0.6.3

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,6 +1,37 @@
1
1
  import { CompactionBacklogError, CompactionJobConflictError, assertCompactionOutputProvenance, compactionOutputSegmentIds, createManifest, createGarbageCollectionJobRecord, advanceGarbageCollectionJobRecord, collectFtsCandidates, collectFtsPostingsBounded, activePostingStorageColumnIds, invalidateUncoveredFtsColumns, invalidateUncoveredSecondaryIndexes, GarbageCollectionJobConflictError, LeaseConflictError, LeaseExpiredError, LeaseOwnerConflictError, MAX_LEASE_TTL_MS, MAX_ACTIVE_LEASES, MAX_ACTIVE_TEMP_OWNERS, MAX_ACTIVE_COMPACTION_JOBS, MAX_ACTIVE_UNIQUE_KEY_BUILDS, MAX_ACTIVE_TRANSACTIONS, MAX_TERMINAL_TRANSACTION_RECORDS, MAX_TERMINAL_COMPACTION_JOB_RECORDS, MAX_COMPLETED_GARBAGE_COLLECTION_JOB_RECORDS, MAX_PINNED_MANIFEST_VERSION_LAG, MAX_PINNED_RETIRED_BLOCKS, MAX_PINNED_RETIRED_BYTES, MAX_RETIRED_HISTORY_BYTES, MAX_GLOBAL_STAGED_ARTIFACT_BYTES, MAX_GLOBAL_STAGED_BLOCKS, MAX_GLOBAL_STAGED_SEGMENTS, MAX_TEMP_OWNER_TTL_MS, MAX_ROW_ID, MAX_ROW_ID_EXCLUSIVE_END, MAX_AUTO_INCREMENT_EXCLUSIVE_END, MAX_LEVEL_ZERO_SEGMENTS, MAX_MANIFEST_BLOCK_PRESENCE_IDS, MAX_STORAGE_ID_CHARACTERS, MAX_CATALOG_RECORDS, MAX_CATALOG_RETAINED_BYTES, MAX_MANIFEST_RECORDS, MAX_MANIFEST_RETAINED_BYTES, MAX_SEGMENT_RECORDS, MAX_SEGMENT_RETAINED_BYTES, MAX_FTS_CANDIDATE_ROW_IDS, MAX_FTS_POSTINGS_PER_CHUNK, MAX_FTS_POSTING_ROW_IDS_PER_CHUNK, MAX_FTS_POSTING_TERM_CHARACTERS, MAX_FTS_TOKENS_PER_DOCUMENT, MAX_FTS_BASE_CHUNKS, MAX_FTS_DELTA_CHUNKS, SnapshotManifestMissingError, StorageResourceLimitError, TableInUseError, TableRecordConflictError, TempOwnerConflictError, TransactionRecordConflictError, UniqueKeyConflictError, UniqueKeyBuildConflictError, UniqueIndexCoverageError, assertTransactionArtifactBatchLimits, assertTransactionArtifactJournalLimits, assertTempRunPageBatchLimits, assertStorageBulkReadItems, boundedMaintenanceBatchItems, normalizeCompactionJobRecord, normalizeGarbageCollectionJobRecord, normalizeSegmentRecord, updateCompactionJobRecord, updateTransactionRecord, updateGarbageCollectionPlanningRecord, validateTableColumns, validateTableForeignKey, validateSecondaryIndexes, secondaryIndexColumnIds, secondaryIndexWriteContractChanged, secondaryUniqueKeyNamespace, transactionCommitDeltaRetainedBytes, uniqueKeyBuildChunkRetainedBytes, MAX_UNIQUE_KEY_BUILD_TTL_MS, MAX_UNIQUE_KEY_BUILD_STAGED_BYTES, MAX_UNIQUE_KEY_BUILD_STAGED_BYTES_TOTAL, MAX_UNIQUE_KEY_BUILD_TOKENS_PER_CHUNK, validateFtsPostingQueries, validateCanonicalManifestChangedTableIds, catalogRecordRetainedBytes, manifestRecordRetainedReservationBytes, segmentRecordRetainedBytes, snapshotAcceleratorItemRetainedUsage, assertSnapshotImportAcceleratorUsage, SchemaConflictError, WriteConflictError, } from "../types.js";
2
2
  import { dateIsoString } from "../../date-value.js";
3
3
  import { assertWellFormedString, crc32, MAX_STORED_BLOCK_BYTE_LENGTH, } from "../../block-format/index.js";
4
+ /**
5
+ * A deep copy for the plain record shapes this core stores — objects, arrays, and primitives,
6
+ * bigint included. Every read and every job advance copies a record across this boundary, and
7
+ * `structuredClone` was a sixth of a settle phase's CPU; a value outside the plain shape
8
+ * (a typed array, a Date, a Map) falls back to `structuredClone` for that value, so the copy
9
+ * stays exact whatever the record carries.
10
+ */
11
+ function cloneRecord(value) {
12
+ return cloneRecordValue(value);
13
+ }
14
+ function cloneRecordValue(value) {
15
+ if (typeof value !== "object" || value === null) {
16
+ // Preserve structuredClone's rejection of uncloneable leaves instead of silently sharing.
17
+ return typeof value === "function" ? structuredClone(value) : value;
18
+ }
19
+ if (Array.isArray(value)) {
20
+ const copy = new Array(value.length);
21
+ for (let index = 0; index < value.length; index += 1) {
22
+ copy[index] = cloneRecordValue(value[index]);
23
+ }
24
+ return copy;
25
+ }
26
+ const prototype = Object.getPrototypeOf(value);
27
+ if (prototype !== Object.prototype && prototype !== null)
28
+ return structuredClone(value);
29
+ const copy = {};
30
+ for (const key of Object.keys(value)) {
31
+ copy[key] = cloneRecordValue(value[key]);
32
+ }
33
+ return copy;
34
+ }
4
35
  const postingTextEncoder = new TextEncoder();
5
36
  const RESOURCE_EXPIRY_SWEEP_ITEMS = 64;
6
37
  /** Leaves ample canonical-wire overhead below the 4 MiB metadata frame ceiling. */
@@ -1153,12 +1184,12 @@ export class RecordCore {
1153
1184
  if (this.#tempOwners.size >= MAX_ACTIVE_TEMP_OWNERS) {
1154
1185
  throw new StorageResourceLimitError("temp owner", this.#tempOwners.size + 1, MAX_ACTIVE_TEMP_OWNERS);
1155
1186
  }
1156
- this.#tempOwners.set(record.ownerId, structuredClone(record));
1187
+ this.#tempOwners.set(record.ownerId, cloneRecord(record));
1157
1188
  }
1158
1189
  getTempOwner(ownerId) {
1159
1190
  validateId(ownerId);
1160
1191
  const record = this.#tempOwners.get(ownerId);
1161
- return record === undefined ? undefined : structuredClone(record);
1192
+ return record === undefined ? undefined : cloneRecord(record);
1162
1193
  }
1163
1194
  renewTempOwner(input) {
1164
1195
  validateId(input.ownerId);
@@ -1176,7 +1207,7 @@ export class RecordCore {
1176
1207
  revision: safeWholeIncrement(record.revision, "Temp owner revision"),
1177
1208
  };
1178
1209
  this.#tempOwners.set(input.ownerId, renewed);
1179
- return structuredClone(renewed);
1210
+ return cloneRecord(renewed);
1180
1211
  }
1181
1212
  listExpiredTempOwnerPage(expiresAtCutoff, afterCursor, limit) {
1182
1213
  const page = boundedExpiryPage(this.#tempOwners, expiresAtCutoff, afterCursor, limit, (record) => record.ownerId, "Temp owner page");
@@ -1278,7 +1309,7 @@ export class RecordCore {
1278
1309
  }
1279
1310
  /** Applies a table whose complete admission checks already passed. */
1280
1311
  #installTableRecord(record) {
1281
- this.#setTable(structuredClone(record));
1312
+ this.#setTable(cloneRecord(record));
1282
1313
  this.#tableIdsByName.set(record.name, record.id);
1283
1314
  if (record.uniqueKeyColumnId !== undefined)
1284
1315
  this.#uniqueKeys.set(record.id, new OrderedStringSet());
@@ -1305,7 +1336,7 @@ export class RecordCore {
1305
1336
  }
1306
1337
  getTable(id) {
1307
1338
  const record = this.#tables.get(id);
1308
- return record === undefined ? undefined : structuredClone(record);
1339
+ return record === undefined ? undefined : cloneRecord(record);
1309
1340
  }
1310
1341
  updateTable(id, expectedRevision, update) {
1311
1342
  const record = this.#tables.get(id);
@@ -1368,18 +1399,16 @@ export class RecordCore {
1368
1399
  validateTableView(nextView ?? undefined);
1369
1400
  const updated = {
1370
1401
  ...base,
1371
- columns: update.columns === undefined ? record.columns : structuredClone(update.columns),
1372
- ...(nextFts === null || nextFts === undefined
1373
- ? {}
1374
- : { ftsColumns: structuredClone(nextFts) }),
1402
+ columns: update.columns === undefined ? record.columns : cloneRecord(update.columns),
1403
+ ...(nextFts === null || nextFts === undefined ? {} : { ftsColumns: cloneRecord(nextFts) }),
1375
1404
  ...(nextSecondary === null || nextSecondary === undefined
1376
1405
  ? {}
1377
- : { secondaryIndexes: structuredClone(nextSecondary) }),
1406
+ : { secondaryIndexes: cloneRecord(nextSecondary) }),
1378
1407
  ...(nextTriggers === null || nextTriggers === undefined
1379
1408
  ? {}
1380
- : { triggers: structuredClone(nextTriggers) }),
1381
- ...(nextForeignKeys === undefined ? {} : { foreignKeys: structuredClone(nextForeignKeys) }),
1382
- ...(nextView === null || nextView === undefined ? {} : { view: structuredClone(nextView) }),
1409
+ : { triggers: cloneRecord(nextTriggers) }),
1410
+ ...(nextForeignKeys === undefined ? {} : { foreignKeys: cloneRecord(nextForeignKeys) }),
1411
+ ...(nextView === null || nextView === undefined ? {} : { view: cloneRecord(nextView) }),
1383
1412
  revision: safeWholeIncrement(expectedRevision, "Table revision"),
1384
1413
  };
1385
1414
  validateSecondaryIndexes(updated);
@@ -1440,7 +1469,7 @@ export class RecordCore {
1440
1469
  this.#setTable(updated);
1441
1470
  this.#catalogEpoch = nextCatalogEpoch;
1442
1471
  this.#schemaEpoch = nextSchemaEpoch;
1443
- return structuredClone(updated);
1472
+ return cloneRecord(updated);
1444
1473
  }
1445
1474
  removeTable(id, expectedRevision, options = {}) {
1446
1475
  const record = this.#tables.get(id);
@@ -1526,7 +1555,7 @@ export class RecordCore {
1526
1555
  this.#deleteTableRecords(table);
1527
1556
  this.#catalogEpoch = nextCatalogEpoch;
1528
1557
  this.#schemaEpoch = nextSchemaEpoch;
1529
- return structuredClone(manifest);
1558
+ return cloneRecord(manifest);
1530
1559
  }
1531
1560
  dropTableColumn(input) {
1532
1561
  validateId(input.tableId);
@@ -1603,7 +1632,7 @@ export class RecordCore {
1603
1632
  this.#deleteFtsColumn(input.tableId, input.columnId);
1604
1633
  this.#catalogEpoch = nextCatalogEpoch;
1605
1634
  this.#schemaEpoch = nextSchemaEpoch;
1606
- return structuredClone(manifest);
1635
+ return cloneRecord(manifest);
1607
1636
  }
1608
1637
  #assertTableNotInUse(id) {
1609
1638
  const target = this.#tables.get(id);
@@ -1675,7 +1704,7 @@ export class RecordCore {
1675
1704
  }
1676
1705
  validateFtsBaseInput(input, "Full-text base");
1677
1706
  const key = `${tableId}/${columnId}`;
1678
- this.#ftsBases.set(key, structuredClone(input));
1707
+ this.#ftsBases.set(key, cloneRecord(input));
1679
1708
  const deltas = this.#ftsDeltas.get(key);
1680
1709
  if (deltas !== undefined) {
1681
1710
  for (const version of [...deltas.keys()]) {
@@ -1777,7 +1806,7 @@ export class RecordCore {
1777
1806
  continue;
1778
1807
  }
1779
1808
  deltas.set(version, {
1780
- postings: structuredClone(column.postings),
1809
+ postings: cloneRecord(column.postings),
1781
1810
  totalTokens: column.totalTokens,
1782
1811
  });
1783
1812
  this.#ftsDeltas.set(key, deltas);
@@ -1798,7 +1827,7 @@ export class RecordCore {
1798
1827
  continue;
1799
1828
  deltaChunkCount += 1;
1800
1829
  deltaTokens += delta.totalTokens;
1801
- chunkLists.push(structuredClone(delta.postings));
1830
+ chunkLists.push(cloneRecord(delta.postings));
1802
1831
  }
1803
1832
  return { chunkLists, deltaChunkCount, deltaTokens };
1804
1833
  }
@@ -1940,7 +1969,7 @@ export class RecordCore {
1940
1969
  this.#manifestBlocks.delete(id);
1941
1970
  for (const id of reclaimedTransactionIds)
1942
1971
  this.#transactions.delete(id);
1943
- this.#garbageCollectionJobs.set(updated.id, structuredClone(updated));
1972
+ this.#garbageCollectionJobs.set(updated.id, cloneRecord(updated));
1944
1973
  }
1945
1974
  getTableByName(name) {
1946
1975
  const id = this.#tableIdsByName.get(name);
@@ -1953,7 +1982,7 @@ export class RecordCore {
1953
1982
  listTables() {
1954
1983
  return [...this.#tables.values()]
1955
1984
  .sort((left, right) => left.name.localeCompare(right.name))
1956
- .map((record) => structuredClone(record));
1985
+ .map((record) => cloneRecord(record));
1957
1986
  }
1958
1987
  getSegment(id) {
1959
1988
  const record = this.#segments.get(id);
@@ -2138,7 +2167,7 @@ export class RecordCore {
2138
2167
  this.#replaceSegments([desired, ...rebasedOldOwnerSegments]);
2139
2168
  this.#setTransaction(updatedOldOwner);
2140
2169
  this.#setTransaction(updatedReplacement);
2141
- return structuredClone(updatedReplacement);
2170
+ return cloneRecord(updatedReplacement);
2142
2171
  }
2143
2172
  reserveRowIds(tableId, count) {
2144
2173
  validateId(tableId);
@@ -2253,7 +2282,7 @@ export class RecordCore {
2253
2282
  updatedAt: input.createdAt,
2254
2283
  };
2255
2284
  if (deepRecordEqual(current.record, expected))
2256
- return structuredClone(current.record);
2285
+ return cloneRecord(current.record);
2257
2286
  throw new UniqueKeyBuildConflictError(input.buildId, "another live owner exists");
2258
2287
  }
2259
2288
  if (current?.record.state === "completed") {
@@ -2282,12 +2311,12 @@ export class RecordCore {
2282
2311
  updatedAt: input.createdAt,
2283
2312
  };
2284
2313
  this.#setUniqueKeyBuild({ record, chunks: [], tokens: new Set(), completedInput: null }, input.buildId);
2285
- return structuredClone(record);
2314
+ return cloneRecord(record);
2286
2315
  }
2287
2316
  getUniqueKeyBuild(buildId) {
2288
2317
  validateId(buildId);
2289
2318
  const state = this.#uniqueKeyBuilds.get(buildId);
2290
- return state === undefined ? undefined : structuredClone(state.record);
2319
+ return state === undefined ? undefined : cloneRecord(state.record);
2291
2320
  }
2292
2321
  renewUniqueKeyBuild(input) {
2293
2322
  validateId(input.buildId);
@@ -2301,13 +2330,13 @@ export class RecordCore {
2301
2330
  throw new UniqueKeyBuildConflictError(input.buildId, "ownership expired or changed");
2302
2331
  }
2303
2332
  if (input.expiresAt <= state.record.expiresAt)
2304
- return structuredClone(state.record);
2333
+ return cloneRecord(state.record);
2305
2334
  const next = {
2306
2335
  ...state,
2307
2336
  record: { ...state.record, expiresAt: input.expiresAt, updatedAt: input.updatedAt },
2308
2337
  };
2309
2338
  this.#setUniqueKeyBuild(next, input.buildId);
2310
- return structuredClone(next.record);
2339
+ return cloneRecord(next.record);
2311
2340
  }
2312
2341
  appendUniqueKeyBuildChunk(input) {
2313
2342
  validateId(input.buildId);
@@ -2326,7 +2355,7 @@ export class RecordCore {
2326
2355
  }
2327
2356
  if (input.ordinal < state.record.nextOrdinal) {
2328
2357
  if (deepRecordEqual(state.chunks[input.ordinal], input.keyTokens)) {
2329
- return structuredClone(state.record);
2358
+ return cloneRecord(state.record);
2330
2359
  }
2331
2360
  throw new UniqueKeyBuildConflictError(input.buildId, "chunk replay changed");
2332
2361
  }
@@ -2357,7 +2386,7 @@ export class RecordCore {
2357
2386
  completedInput: null,
2358
2387
  };
2359
2388
  this.#setUniqueKeyBuild(next, input.buildId);
2360
- return structuredClone(next.record);
2389
+ return cloneRecord(next.record);
2361
2390
  }
2362
2391
  finishUniqueKeyBuild(input) {
2363
2392
  validateId(input.buildId);
@@ -2372,7 +2401,7 @@ export class RecordCore {
2372
2401
  const table = this.#tables.get(state.record.tableId);
2373
2402
  if (table === undefined)
2374
2403
  throw new UniqueKeyBuildConflictError(input.buildId, "table was removed");
2375
- return structuredClone(table);
2404
+ return cloneRecord(table);
2376
2405
  }
2377
2406
  if (state?.record.state !== "active" ||
2378
2407
  state.record.ownerId !== input.ownerId ||
@@ -2421,11 +2450,11 @@ export class RecordCore {
2421
2450
  record: completedRecord,
2422
2451
  chunks: [],
2423
2452
  tokens: state.tokens,
2424
- completedInput: structuredClone(input),
2453
+ completedInput: cloneRecord(input),
2425
2454
  }, input.buildId);
2426
2455
  this.#catalogEpoch = nextCatalogEpoch;
2427
2456
  this.#schemaEpoch = nextSchemaEpoch;
2428
- return structuredClone(updated);
2457
+ return cloneRecord(updated);
2429
2458
  }
2430
2459
  abortUniqueKeyBuild(input) {
2431
2460
  validateId(input.buildId);
@@ -2550,15 +2579,15 @@ export class RecordCore {
2550
2579
  }
2551
2580
  getCurrentManifest() {
2552
2581
  const manifest = this.#currentVersion === null ? undefined : this.#manifests.get(this.#currentVersion);
2553
- return manifest === undefined ? undefined : structuredClone(manifest);
2582
+ return manifest === undefined ? undefined : cloneRecord(manifest);
2554
2583
  }
2555
2584
  getManifest(version) {
2556
2585
  const manifest = this.#manifests.get(version);
2557
- return manifest === undefined ? undefined : structuredClone(manifest);
2586
+ return manifest === undefined ? undefined : cloneRecord(manifest);
2558
2587
  }
2559
2588
  listManifestPage(afterVersion, limit) {
2560
2589
  validatePageLimit(limit);
2561
- const records = boundedRecordPage(this.#manifests, afterVersion, limit).map((manifest) => structuredClone(manifest));
2590
+ const records = boundedRecordPage(this.#manifests, afterVersion, limit).map((manifest) => cloneRecord(manifest));
2562
2591
  return {
2563
2592
  records,
2564
2593
  nextCursor: records.length === limit ? (records.at(-1)?.version ?? null) : null,
@@ -2621,13 +2650,13 @@ export class RecordCore {
2621
2650
  this.#assertTableForeignKeys(pending.record);
2622
2651
  }
2623
2652
  const record = {
2624
- ...structuredClone(input.record),
2653
+ ...cloneRecord(input.record),
2625
2654
  snapshotVersion: this.#currentVersion,
2626
2655
  schemaEpochGuard: this.#schemaEpoch,
2627
2656
  ...(pending === undefined
2628
2657
  ? {}
2629
2658
  : {
2630
- pendingTable: structuredClone(pending.record),
2659
+ pendingTable: cloneRecord(pending.record),
2631
2660
  pendingTableNextRowId: pending.nextRowId,
2632
2661
  catalogEpochGuard: pending.expectedCatalogEpoch,
2633
2662
  }),
@@ -2680,14 +2709,14 @@ export class RecordCore {
2680
2709
  this.#nextAutoIncrement.set(autoCounterKey, autoIncrementValues.endExclusive);
2681
2710
  }
2682
2711
  return {
2683
- record: structuredClone(record),
2712
+ record: cloneRecord(record),
2684
2713
  ...(rowIds === undefined ? {} : { rowIds }),
2685
2714
  ...(autoIncrementValues === undefined ? {} : { autoIncrementValues }),
2686
2715
  };
2687
2716
  }
2688
2717
  createTransaction(record) {
2689
2718
  const normalized = {
2690
- ...structuredClone(record),
2719
+ ...cloneRecord(record),
2691
2720
  ...(record.status === "active" && record.schemaEpochGuard === undefined
2692
2721
  ? { schemaEpochGuard: this.#schemaEpoch }
2693
2722
  : {}),
@@ -2728,7 +2757,7 @@ export class RecordCore {
2728
2757
  }
2729
2758
  getTransaction(id) {
2730
2759
  const record = this.#transactions.get(id);
2731
- return record === undefined ? undefined : structuredClone(record);
2760
+ return record === undefined ? undefined : cloneRecord(record);
2732
2761
  }
2733
2762
  getTransactions(ids) {
2734
2763
  assertStorageBulkReadItems(ids, "Transaction read");
@@ -2736,12 +2765,12 @@ export class RecordCore {
2736
2765
  validateId(id);
2737
2766
  return ids.map((id) => {
2738
2767
  const record = this.#transactions.get(id);
2739
- return record === undefined ? undefined : structuredClone(record);
2768
+ return record === undefined ? undefined : cloneRecord(record);
2740
2769
  });
2741
2770
  }
2742
2771
  listTransactionPage(afterId, limit) {
2743
2772
  validatePageLimit(limit);
2744
- const records = boundedRecordPage(this.#transactions, afterId, limit).map((record) => structuredClone(record));
2773
+ const records = boundedRecordPage(this.#transactions, afterId, limit).map((record) => cloneRecord(record));
2745
2774
  return { records, nextCursor: records.length === limit ? (records.at(-1)?.id ?? null) : null };
2746
2775
  }
2747
2776
  updateTransaction(id, expectedRevision, update) {
@@ -2756,7 +2785,7 @@ export class RecordCore {
2756
2785
  }
2757
2786
  this.#assertPendingArtifactsAvailable(updated, update.pendingBlockIds !== undefined, update.pendingSegmentIds !== undefined);
2758
2787
  this.#setTransaction(updated);
2759
- return structuredClone(updated);
2788
+ return cloneRecord(updated);
2760
2789
  }
2761
2790
  renewTransaction(input) {
2762
2791
  validateId(input.transactionId);
@@ -2790,7 +2819,7 @@ export class RecordCore {
2790
2819
  updatedAt: input.updatedAt,
2791
2820
  });
2792
2821
  this.#setTransaction(updated);
2793
- return structuredClone(updated);
2822
+ return cloneRecord(updated);
2794
2823
  }
2795
2824
  /**
2796
2825
  * Validates and applies the record half of staging: the journal update and the segment
@@ -2806,7 +2835,7 @@ export class RecordCore {
2806
2835
  this.#assertTransactionResourceTransition(plan.updated, blockByteLengths);
2807
2836
  this.#replaceSegments(plan.segments);
2808
2837
  this.#setTransaction(plan.updated, blockByteLengths);
2809
- return structuredClone(plan.updated);
2838
+ return cloneRecord(plan.updated);
2810
2839
  }
2811
2840
  /**
2812
2841
  * Runs the complete staging preflight without changing record state. Persistent adapters use
@@ -2991,7 +3020,7 @@ export class RecordCore {
2991
3020
  const updated = updateTransactionRecord(current, update);
2992
3021
  this.#replaceSegments([], input.removeSegmentIds);
2993
3022
  this.#setTransaction(updated);
2994
- return structuredClone(updated);
3023
+ return cloneRecord(updated);
2995
3024
  }
2996
3025
  #validateStagedSegment(segment, transactionId, journalBlockIds) {
2997
3026
  if (segment.transactionId !== transactionId) {
@@ -3085,7 +3114,7 @@ export class RecordCore {
3085
3114
  throw new WriteConflictError(input.expectedManifestVersion, this.#currentVersion);
3086
3115
  }
3087
3116
  base = {
3088
- ...structuredClone(input.transaction.record),
3117
+ ...cloneRecord(input.transaction.record),
3089
3118
  snapshotVersion: input.expectedManifestVersion,
3090
3119
  };
3091
3120
  validateTransactionRuntimeRecord(base, "Transaction");
@@ -3532,7 +3561,7 @@ export class RecordCore {
3532
3561
  columnId: column.columnId,
3533
3562
  count: this.#ftsDeltas.get(`${changes.tableId}/${column.columnId}`)?.size ?? 0,
3534
3563
  })));
3535
- return structuredClone({
3564
+ return cloneRecord({
3536
3565
  ...manifest,
3537
3566
  ...(ftsDeltaCounts.length === 0 ? {} : { ftsDeltaCounts }),
3538
3567
  });
@@ -3549,21 +3578,21 @@ export class RecordCore {
3549
3578
  if (this.#leases.size >= MAX_ACTIVE_LEASES) {
3550
3579
  throw new StorageResourceLimitError("lease", this.#leases.size + 1, MAX_ACTIVE_LEASES);
3551
3580
  }
3552
- this.#leases.set(record.id, structuredClone(record));
3581
+ this.#leases.set(record.id, cloneRecord(record));
3553
3582
  }
3554
3583
  getLease(id) {
3555
3584
  const record = this.#leases.get(id);
3556
- return record === undefined ? undefined : structuredClone(record);
3585
+ return record === undefined ? undefined : cloneRecord(record);
3557
3586
  }
3558
3587
  listLeases() {
3559
3588
  return [...this.#leases.values()]
3560
3589
  .sort((left, right) => left.id.localeCompare(right.id))
3561
- .map((record) => structuredClone(record));
3590
+ .map((record) => cloneRecord(record));
3562
3591
  }
3563
3592
  listExpiredLeasePage(expiresAtCutoff, afterCursor, limit) {
3564
3593
  const page = boundedExpiryPage(this.#leases, expiresAtCutoff, afterCursor, limit, (record) => record.id, "Lease page");
3565
3594
  return {
3566
- records: page.records.map((record) => structuredClone(record)),
3595
+ records: page.records.map((record) => cloneRecord(record)),
3567
3596
  nextCursor: page.nextCursor,
3568
3597
  };
3569
3598
  }
@@ -3583,7 +3612,7 @@ export class RecordCore {
3583
3612
  revision: safeWholeIncrement(record.revision, "Lease revision"),
3584
3613
  };
3585
3614
  this.#leases.set(input.id, renewed);
3586
- return structuredClone(renewed);
3615
+ return cloneRecord(renewed);
3587
3616
  }
3588
3617
  moveLease(input) {
3589
3618
  const cutoff = validateBoundedExpiration(input.expiresAtCutoff, input.expiresAt, "Lease", MAX_LEASE_TTL_MS);
@@ -3603,7 +3632,7 @@ export class RecordCore {
3603
3632
  revision: safeWholeIncrement(record.revision, "Lease revision"),
3604
3633
  };
3605
3634
  this.#leases.set(input.id, moved);
3606
- return structuredClone(moved);
3635
+ return cloneRecord(moved);
3607
3636
  }
3608
3637
  removeLeaseIfExpired(id, expectedRevision, expiresAtCutoff) {
3609
3638
  const cutoff = Date.parse(expiresAtCutoff);
@@ -3654,17 +3683,17 @@ export class RecordCore {
3654
3683
  }
3655
3684
  getCompactionJob(id) {
3656
3685
  const record = this.#compactionJobs.get(id);
3657
- return record === undefined ? undefined : structuredClone(record);
3686
+ return record === undefined ? undefined : cloneRecord(record);
3658
3687
  }
3659
3688
  listCompactionJobs(tableId) {
3660
3689
  return [...this.#compactionJobs.values()]
3661
3690
  .filter((record) => tableId === undefined || record.tableId === tableId)
3662
3691
  .sort((left, right) => left.createdAt.localeCompare(right.createdAt) || left.id.localeCompare(right.id))
3663
- .map((record) => structuredClone(record));
3692
+ .map((record) => cloneRecord(record));
3664
3693
  }
3665
3694
  listCompactionJobPage(afterId, limit) {
3666
3695
  validatePageLimit(limit);
3667
- const records = boundedRecordPage(this.#compactionJobs, afterId, limit).map((record) => structuredClone(record));
3696
+ const records = boundedRecordPage(this.#compactionJobs, afterId, limit).map((record) => cloneRecord(record));
3668
3697
  return { records, nextCursor: records.length === limit ? (records.at(-1)?.id ?? null) : null };
3669
3698
  }
3670
3699
  updateCompactionJob(id, expectedRevision, update) {
@@ -3679,7 +3708,7 @@ export class RecordCore {
3679
3708
  this.#assertCompactionJobReferences(updated);
3680
3709
  this.#assertTerminalCompactionJobTransition(current, updated);
3681
3710
  this.#compactionJobs.set(id, updated);
3682
- return structuredClone(updated);
3711
+ return cloneRecord(updated);
3683
3712
  }
3684
3713
  #assertCompactionJobReferences(job) {
3685
3714
  // Cancelled/aborted jobs are retained diagnostics rather than live physical roots.
@@ -3746,7 +3775,7 @@ export class RecordCore {
3746
3775
  throw new CompactionJobConflictError(id, expectedRevision, current?.revision ?? null);
3747
3776
  }
3748
3777
  if (isTerminalCompactionJob(current)) {
3749
- return structuredClone(current);
3778
+ return cloneRecord(current);
3750
3779
  }
3751
3780
  const transaction = current.transactionId === null ? undefined : this.#transactions.get(current.transactionId);
3752
3781
  if (transaction?.status === "committed") {
@@ -3761,7 +3790,7 @@ export class RecordCore {
3761
3790
  });
3762
3791
  this.#assertTerminalCompactionJobTransition(current, published);
3763
3792
  this.#compactionJobs.set(id, published);
3764
- return structuredClone(published);
3793
+ return cloneRecord(published);
3765
3794
  }
3766
3795
  const cancelled = updateCompactionJobRecord(current, {
3767
3796
  state: "cancelled",
@@ -3780,7 +3809,7 @@ export class RecordCore {
3780
3809
  this.#setTransaction(abortedTransaction);
3781
3810
  }
3782
3811
  this.#compactionJobs.set(id, cancelled);
3783
- return structuredClone(cancelled);
3812
+ return cloneRecord(cancelled);
3784
3813
  }
3785
3814
  removeCompactionJob(id) {
3786
3815
  if (!this.canRemoveCompactionJob(id))
@@ -3850,7 +3879,7 @@ export class RecordCore {
3850
3879
  assertGarbageCollectionCandidateProvenance(record, this.#manifests, this.#manifestBlocks, this.#segments, this.#transactions, this.#roots);
3851
3880
  this.#pruneOneCompletedGarbageCollectionJob();
3852
3881
  this.#garbageCollectionJobs.set(record.id, record);
3853
- return structuredClone(record);
3882
+ return cloneRecord(record);
3854
3883
  }
3855
3884
  updateGarbageCollectionPlanning(input) {
3856
3885
  const current = this.#garbageCollectionJobs.get(input.jobId);
@@ -3860,20 +3889,20 @@ export class RecordCore {
3860
3889
  const updated = updateGarbageCollectionPlanningRecord(current, input);
3861
3890
  assertGarbageCollectionCandidateProvenance(updated, this.#manifests, this.#manifestBlocks, this.#segments, this.#transactions, this.#roots);
3862
3891
  this.#garbageCollectionJobs.set(updated.id, updated);
3863
- return structuredClone(updated);
3892
+ return cloneRecord(updated);
3864
3893
  }
3865
3894
  getGarbageCollectionJob(id) {
3866
3895
  const record = this.#garbageCollectionJobs.get(id);
3867
- return record === undefined ? undefined : structuredClone(record);
3896
+ return record === undefined ? undefined : cloneRecord(record);
3868
3897
  }
3869
3898
  listGarbageCollectionJobs() {
3870
3899
  return [...this.#garbageCollectionJobs.values()]
3871
3900
  .sort((left, right) => left.createdAt.localeCompare(right.createdAt) || left.id.localeCompare(right.id))
3872
- .map((record) => structuredClone(record));
3901
+ .map((record) => cloneRecord(record));
3873
3902
  }
3874
3903
  listGarbageCollectionJobPage(afterId, limit) {
3875
3904
  validatePageLimit(limit);
3876
- const records = boundedRecordPage(this.#garbageCollectionJobs, afterId, limit).map((record) => structuredClone(record));
3905
+ const records = boundedRecordPage(this.#garbageCollectionJobs, afterId, limit).map((record) => cloneRecord(record));
3877
3906
  return { records, nextCursor: records.length === limit ? (records.at(-1)?.id ?? null) : null };
3878
3907
  }
3879
3908
  /**
@@ -4048,7 +4077,7 @@ export class RecordCore {
4048
4077
  reclaimedTransactionIds.forEach((id) => this.#transactions.delete(id));
4049
4078
  this.#garbageCollectionJobs.set(updated.id, updated);
4050
4079
  return {
4051
- job: structuredClone(updated),
4080
+ job: cloneRecord(updated),
4052
4081
  prunedManifestVersions,
4053
4082
  alreadyPrunedManifestVersions,
4054
4083
  retainedManifestVersions,
@@ -4095,7 +4124,7 @@ export class RecordCore {
4095
4124
  });
4096
4125
  yield {
4097
4126
  kind: "table",
4098
- record: structuredClone(table),
4127
+ record: cloneRecord(table),
4099
4128
  nextRowId: this.#nextRowIds.get(table.id) ?? 1n,
4100
4129
  autoIncrement,
4101
4130
  };
@@ -4133,7 +4162,7 @@ export class RecordCore {
4133
4162
  yield {
4134
4163
  kind: "transaction",
4135
4164
  record: {
4136
- ...structuredClone(record),
4165
+ ...cloneRecord(record),
4137
4166
  pendingBlockIds: [],
4138
4167
  pendingSegmentIds: [],
4139
4168
  },
@@ -4305,7 +4334,7 @@ export class RecordCore {
4305
4334
  const manifest = this.#manifests.get(version);
4306
4335
  if (manifest === undefined)
4307
4336
  throw new SnapshotManifestMissingError(version);
4308
- return structuredClone(manifest);
4337
+ return cloneRecord(manifest);
4309
4338
  }
4310
4339
  /** O(1) counters for diagnostics; callers never need to clone the full durable state. */
4311
4340
  storageRecordStats() {
@@ -4404,7 +4433,7 @@ export class RecordCore {
4404
4433
  throw new Error("Snapshot tables are not in canonical ID order");
4405
4434
  }
4406
4435
  previousTableId = item.record.id;
4407
- const record = structuredClone(item.record);
4436
+ const record = cloneRecord(item.record);
4408
4437
  validateTableRuntimeRecord(record, `Snapshot table ${item.record.id}`);
4409
4438
  if (trial.#tables.has(record.id) || trial.#tableIdsByName.has(record.name)) {
4410
4439
  throw new Error(`Snapshot repeats a table identity: ${record.id}`);
@@ -4448,7 +4477,7 @@ export class RecordCore {
4448
4477
  if (record.status !== "committed" || record.committedVersion === null) {
4449
4478
  throw new Error(`Snapshot transaction is not committed: ${record.id}`);
4450
4479
  }
4451
- trial.#setTransaction(structuredClone(record));
4480
+ trial.#setTransaction(cloneRecord(record));
4452
4481
  break;
4453
4482
  }
4454
4483
  case "unique-generation": {
@@ -4510,7 +4539,7 @@ export class RecordCore {
4510
4539
  throw new Error(`Snapshot posting chunk is out of order: ${item.storageColumnId}`);
4511
4540
  }
4512
4541
  retainAcceleratorChunk(item);
4513
- const chunk = structuredClone(item.postings);
4542
+ const chunk = cloneRecord(item.postings);
4514
4543
  if (posting.previousTerm !== undefined &&
4515
4544
  chunk[0] !== undefined &&
4516
4545
  chunk[0].term <= posting.previousTerm) {
@@ -4671,7 +4700,7 @@ export class RecordCore {
4671
4700
  }
4672
4701
  /** Replaces the whole record state with a dump's content. */
4673
4702
  load(state) {
4674
- const cloned = structuredClone(state);
4703
+ const cloned = cloneRecord(state);
4675
4704
  validateRecordCoreState(cloned, this.#physical);
4676
4705
  // A recovery candidate may intentionally discard an unpublished WAL suffix whose physical
4677
4706
  // blocks were already reclaimed. Clearing the old candidate state must therefore not need
@@ -6414,7 +6443,7 @@ export function validateTempRunPageIdentity(ownerId, runId, pageIndex) {
6414
6443
  }
6415
6444
  function emptyGarbageCollectionStep(job) {
6416
6445
  return {
6417
- job: structuredClone(job),
6446
+ job: cloneRecord(job),
6418
6447
  prunedManifestVersions: [],
6419
6448
  alreadyPrunedManifestVersions: [],
6420
6449
  retainedManifestVersions: [],