@minnowdb/core 0.9.1 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/engine/auto-store.d.ts +52 -0
- package/dist/engine/auto-store.js +157 -0
- package/dist/engine/buffered-writer.d.ts +2 -0
- package/dist/engine/buffered-writer.js +15 -2
- package/dist/engine/client-audit-harness.js +123 -0
- package/dist/engine/client.d.ts +55 -6
- package/dist/engine/client.js +176 -46
- package/dist/engine/database.d.ts +15 -1
- package/dist/engine/database.js +1276 -251
- package/dist/engine/errors.d.ts +61 -2
- package/dist/engine/errors.js +116 -3
- package/dist/engine/index.d.ts +1 -0
- package/dist/engine/index.js +2 -0
- package/dist/engine/live.d.ts +24 -1
- package/dist/engine/live.js +33 -9
- package/dist/engine/scope-write-set.js +36 -0
- package/dist/engine/worker-auto.d.ts +1 -0
- package/dist/engine/worker-auto.js +3 -0
- package/dist/engine/worker-host.d.ts +2 -1
- package/dist/engine/worker-host.js +19 -1
- package/dist/engine/worker-server.d.ts +53 -1
- package/dist/engine/worker-server.js +122 -19
- package/dist/engine/worker-store-auto.js +36 -0
- package/dist/engine/worker-store-opfs.js +3 -2
- package/dist/engine/write-coordinator.js +44 -2
- package/dist/storage/indexeddb-audit-helpers.js +269 -0
- package/dist/storage/indexeddb.js +599 -374
- package/dist/storage/opfs/coordination-helpers.js +54 -0
- package/dist/storage/opfs/index.d.ts +1 -1
- package/dist/storage/opfs/index.js +3 -2
- package/dist/storage/opfs/leader.js +243 -17
- package/dist/storage/opfs/power-loss-model.js +62 -0
- package/dist/storage/opfs/rpc.js +24 -43
- package/dist/storage/opfs/store.d.ts +32 -0
- package/dist/storage/opfs/store.js +531 -65
- package/dist/storage/toolkit/record-core.js +67 -38
- package/dist/storage/toolkit/wal.js +16 -0
- package/dist/storage/toolkit/wire.d.ts +1 -1
- package/dist/storage/toolkit/wire.js +4 -4
- package/dist/storage/types.d.ts +31 -10
- package/dist/storage/types.js +27 -16
- package/dist/testing/opfs-shim.js +14 -6
- package/dist/transactions/index.d.ts +19 -0
- package/dist/transactions/index.js +99 -25
- package/dist/worker-protocol/index.d.ts +50 -2
- package/dist/worker-protocol/index.js +106 -4
- package/package.json +7 -2
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { CompactionJobConflictError, assertCompactionOutputProvenance, compactionOutputSegmentIds, CompactionBacklogError, createManifest, createGarbageCollectionJobRecord, storeNames, advanceGarbageCollectionJobRecord as advanceGarbageCollectionJobRecordUnchecked, BlockReadBatchTooLargeError, activePostingStorageColumnIds, assertTempRunPageBatchLimits, assertStorageBulkReadItems, assertTransactionArtifactBatchLimits,
|
|
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";
|
|
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";
|
|
5
|
-
const SCHEMA_VERSION =
|
|
5
|
+
const SCHEMA_VERSION = 2;
|
|
6
6
|
const FIRST_STABLE_SCHEMA_VERSION = 1;
|
|
7
7
|
const CURRENT_MANIFEST_KEY = "manifest/current";
|
|
8
8
|
const MANIFEST_PRUNE_CLEANUP_KEY = "manifest/prune-cleanup";
|
|
@@ -16,7 +16,13 @@ const SNAPSHOT_UNIQUE_OWNER_PREFIX = "snapshot/frame-import/unique-owner/";
|
|
|
16
16
|
const SNAPSHOT_POSTING_STAGING_PREFIX = "snapshot/frame-import/posting/";
|
|
17
17
|
const SNAPSHOT_POSTING_OWNER_PREFIX = "snapshot/frame-import/posting-owner/";
|
|
18
18
|
const SNAPSHOT_HEADER_STORE = "snapshotHeaders";
|
|
19
|
-
const
|
|
19
|
+
const TRANSACTION_JOURNAL_STORE = "transactionJournal";
|
|
20
|
+
const indexedDbStoreNames = [
|
|
21
|
+
...storeNames,
|
|
22
|
+
SNAPSHOT_HEADER_STORE,
|
|
23
|
+
TRANSACTION_JOURNAL_STORE
|
|
24
|
+
];
|
|
25
|
+
const TRANSACTION_JOURNAL_CHUNK_ENTRIES = 1024;
|
|
20
26
|
const SEGMENT_TABLE_INDEX = "byTable";
|
|
21
27
|
const LEASE_EXPIRY_INDEX = "byExpiry";
|
|
22
28
|
const TRANSACTION_STATUS_INDEX = "byStatus";
|
|
@@ -64,7 +70,50 @@ const ACTIVE_GARBAGE_COLLECTION_KEY = "maintenance/active-garbage-collection";
|
|
|
64
70
|
const MAINTENANCE_QUOTA_KEY = "maintenance/quota";
|
|
65
71
|
const storageTextEncoder = new TextEncoder();
|
|
66
72
|
const storageTextBuffer = new Uint8Array(1024);
|
|
67
|
-
const indexedDbSchemaMigrations = [
|
|
73
|
+
const indexedDbSchemaMigrations = [
|
|
74
|
+
{
|
|
75
|
+
targetVersion: 2,
|
|
76
|
+
migrate: migrateTransactionJournalsToChunks
|
|
77
|
+
}
|
|
78
|
+
];
|
|
79
|
+
function migrateTransactionJournalsToChunks(database, upgrade, fail) {
|
|
80
|
+
if (!database.objectStoreNames.contains("transactions") || database.objectStoreNames.contains(TRANSACTION_JOURNAL_STORE)) {
|
|
81
|
+
throw corruption("schema", "schema 1 layout cannot be migrated to schema 2");
|
|
82
|
+
}
|
|
83
|
+
database.createObjectStore(TRANSACTION_JOURNAL_STORE);
|
|
84
|
+
const transactionStore = upgrade.objectStore("transactions");
|
|
85
|
+
const journalStore = upgrade.objectStore(TRANSACTION_JOURNAL_STORE);
|
|
86
|
+
const request = transactionStore.openCursor();
|
|
87
|
+
request.onerror = () => fail(request.error ?? new Error("IndexedDB migration cursor failed"));
|
|
88
|
+
request.onsuccess = () => {
|
|
89
|
+
const cursor = request.result;
|
|
90
|
+
if (cursor === null)
|
|
91
|
+
return;
|
|
92
|
+
try {
|
|
93
|
+
const key = cursor.key;
|
|
94
|
+
if (typeof key !== "string")
|
|
95
|
+
throw corruption("transactions", "record key is invalid");
|
|
96
|
+
const value = cursor.value;
|
|
97
|
+
if (!isRecord(value))
|
|
98
|
+
throw corruption(`transactions/${key}`, "record is not an object");
|
|
99
|
+
const { pendingBlockIds, pendingSegmentIds, ...header } = value;
|
|
100
|
+
const blockIds = requiredUniqueStringArray(pendingBlockIds, `transactions/${key}/pendingBlockIds`);
|
|
101
|
+
const segmentIds = requiredUniqueStringArray(pendingSegmentIds, `transactions/${key}/pendingSegmentIds`);
|
|
102
|
+
packTransactionJournal(blockIds, segmentIds).forEach((chunk, index) => {
|
|
103
|
+
journalStore.add(chunk, transactionJournalChunkKey(key, index));
|
|
104
|
+
});
|
|
105
|
+
cursor.update({
|
|
106
|
+
...header,
|
|
107
|
+
journalChunkCount: transactionJournalChunkCount(blockIds.length, segmentIds.length),
|
|
108
|
+
pendingBlockCount: blockIds.length,
|
|
109
|
+
pendingSegmentCount: segmentIds.length
|
|
110
|
+
});
|
|
111
|
+
cursor.continue();
|
|
112
|
+
} catch (error) {
|
|
113
|
+
fail(error instanceof Error ? error : new Error(String(error)));
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
}
|
|
68
117
|
assertIndexedDbSchemaMigrationRegistry();
|
|
69
118
|
const DEFAULT_UNIQUE_KEY_CACHE_BYTES = 8 * 1024 * 1024;
|
|
70
119
|
const TABLE_NAME_CACHE_LIMIT = 256;
|
|
@@ -76,6 +125,7 @@ class IndexedDbBlockStore {
|
|
|
76
125
|
#tableIdsByName = /* @__PURE__ */ new Map();
|
|
77
126
|
#uniqueKeyCache;
|
|
78
127
|
#manifestCache;
|
|
128
|
+
#journalCache;
|
|
79
129
|
#snapshotPeakRetainedItems = 0;
|
|
80
130
|
#snapshotPeakRetainedBytes = 0;
|
|
81
131
|
#closed = false;
|
|
@@ -113,7 +163,10 @@ class IndexedDbBlockStore {
|
|
|
113
163
|
createCurrentIndexedDbSchema(request.result, upgrade);
|
|
114
164
|
return;
|
|
115
165
|
}
|
|
116
|
-
applyIndexedDbSchemaMigrations(request.result, upgrade, event.oldVersion)
|
|
166
|
+
applyIndexedDbSchemaMigrations(request.result, upgrade, event.oldVersion, (error) => {
|
|
167
|
+
upgradeError ??= error;
|
|
168
|
+
abortIfActive(upgrade);
|
|
169
|
+
});
|
|
117
170
|
} catch (error) {
|
|
118
171
|
upgradeError = error instanceof Error ? error : new Error(String(error));
|
|
119
172
|
request.transaction?.abort();
|
|
@@ -200,8 +253,7 @@ class IndexedDbBlockStore {
|
|
|
200
253
|
return result;
|
|
201
254
|
} catch (error) {
|
|
202
255
|
abortIfActive(transaction);
|
|
203
|
-
await
|
|
204
|
-
throw error;
|
|
256
|
+
throw await refusalBehindAbort(transaction, error);
|
|
205
257
|
}
|
|
206
258
|
}
|
|
207
259
|
async readManifestBlock(version, id) {
|
|
@@ -235,8 +287,7 @@ class IndexedDbBlockStore {
|
|
|
235
287
|
return bytes;
|
|
236
288
|
} catch (error) {
|
|
237
289
|
abortIfActive(transaction);
|
|
238
|
-
await
|
|
239
|
-
throw error;
|
|
290
|
+
throw await refusalBehindAbort(transaction, error);
|
|
240
291
|
}
|
|
241
292
|
}
|
|
242
293
|
async hasManifestBlocks(version, ids) {
|
|
@@ -261,8 +312,7 @@ class IndexedDbBlockStore {
|
|
|
261
312
|
return result;
|
|
262
313
|
} catch (error) {
|
|
263
314
|
abortIfActive(transaction);
|
|
264
|
-
await
|
|
265
|
-
throw error;
|
|
315
|
+
throw await refusalBehindAbort(transaction, error);
|
|
266
316
|
}
|
|
267
317
|
}
|
|
268
318
|
async listManifestBlockPage(input) {
|
|
@@ -293,8 +343,7 @@ class IndexedDbBlockStore {
|
|
|
293
343
|
};
|
|
294
344
|
} catch (error) {
|
|
295
345
|
abortIfActive(transaction);
|
|
296
|
-
await
|
|
297
|
-
throw error;
|
|
346
|
+
throw await refusalBehindAbort(transaction, error);
|
|
298
347
|
}
|
|
299
348
|
}
|
|
300
349
|
async listRetiredManifestBlockPage(input) {
|
|
@@ -319,8 +368,7 @@ class IndexedDbBlockStore {
|
|
|
319
368
|
};
|
|
320
369
|
} catch (error) {
|
|
321
370
|
abortIfActive(transaction);
|
|
322
|
-
await
|
|
323
|
-
throw error;
|
|
371
|
+
throw await refusalBehindAbort(transaction, error);
|
|
324
372
|
}
|
|
325
373
|
}
|
|
326
374
|
async putTempRunPage(page) {
|
|
@@ -339,8 +387,7 @@ class IndexedDbBlockStore {
|
|
|
339
387
|
await transactionDone(transaction);
|
|
340
388
|
} catch (error) {
|
|
341
389
|
abortIfActive(transaction);
|
|
342
|
-
await
|
|
343
|
-
throw error;
|
|
390
|
+
throw await refusalBehindAbort(transaction, error);
|
|
344
391
|
}
|
|
345
392
|
}
|
|
346
393
|
async getTempRunPage(ownerId, runId, pageIndex) {
|
|
@@ -359,8 +406,7 @@ class IndexedDbBlockStore {
|
|
|
359
406
|
await transactionDone(transaction);
|
|
360
407
|
} catch (error) {
|
|
361
408
|
abortIfActive(transaction);
|
|
362
|
-
await
|
|
363
|
-
throw error;
|
|
409
|
+
throw await refusalBehindAbort(transaction, error);
|
|
364
410
|
}
|
|
365
411
|
}
|
|
366
412
|
async removeTempOwner(ownerId) {
|
|
@@ -372,8 +418,7 @@ class IndexedDbBlockStore {
|
|
|
372
418
|
await transactionDone(transaction);
|
|
373
419
|
} catch (error) {
|
|
374
420
|
abortIfActive(transaction);
|
|
375
|
-
await
|
|
376
|
-
throw error;
|
|
421
|
+
throw await refusalBehindAbort(transaction, error);
|
|
377
422
|
}
|
|
378
423
|
}
|
|
379
424
|
async createTempOwner(record) {
|
|
@@ -395,8 +440,7 @@ class IndexedDbBlockStore {
|
|
|
395
440
|
await transactionDone(transaction);
|
|
396
441
|
} catch (error) {
|
|
397
442
|
abortIfActive(transaction);
|
|
398
|
-
await
|
|
399
|
-
throw error;
|
|
443
|
+
throw await refusalBehindAbort(transaction, error);
|
|
400
444
|
}
|
|
401
445
|
}
|
|
402
446
|
async getTempOwner(ownerId) {
|
|
@@ -536,8 +580,7 @@ class IndexedDbBlockStore {
|
|
|
536
580
|
await transactionDone(transaction);
|
|
537
581
|
} catch (error) {
|
|
538
582
|
abortIfActive(transaction);
|
|
539
|
-
await
|
|
540
|
-
throw error;
|
|
583
|
+
throw await refusalBehindAbort(transaction, error);
|
|
541
584
|
}
|
|
542
585
|
}
|
|
543
586
|
async getTable(id) {
|
|
@@ -727,8 +770,7 @@ class IndexedDbBlockStore {
|
|
|
727
770
|
return updated;
|
|
728
771
|
} catch (error) {
|
|
729
772
|
abortIfActive(transaction);
|
|
730
|
-
await
|
|
731
|
-
throw error;
|
|
773
|
+
throw await refusalBehindAbort(transaction, error);
|
|
732
774
|
}
|
|
733
775
|
}
|
|
734
776
|
async beginUniqueKeyBuild(input) {
|
|
@@ -783,8 +825,7 @@ class IndexedDbBlockStore {
|
|
|
783
825
|
return structuredClone(record);
|
|
784
826
|
} catch (error) {
|
|
785
827
|
abortIfActive(transaction);
|
|
786
|
-
await
|
|
787
|
-
throw error;
|
|
828
|
+
throw await refusalBehindAbort(transaction, error);
|
|
788
829
|
}
|
|
789
830
|
}
|
|
790
831
|
async getUniqueKeyBuild(buildId) {
|
|
@@ -819,8 +860,7 @@ class IndexedDbBlockStore {
|
|
|
819
860
|
return structuredClone(record);
|
|
820
861
|
} catch (error) {
|
|
821
862
|
abortIfActive(transaction);
|
|
822
|
-
await
|
|
823
|
-
throw error;
|
|
863
|
+
throw await refusalBehindAbort(transaction, error);
|
|
824
864
|
}
|
|
825
865
|
}
|
|
826
866
|
async appendUniqueKeyBuildChunk(input) {
|
|
@@ -893,8 +933,7 @@ class IndexedDbBlockStore {
|
|
|
893
933
|
return structuredClone(record);
|
|
894
934
|
} catch (error) {
|
|
895
935
|
abortIfActive(transaction);
|
|
896
|
-
await
|
|
897
|
-
throw error;
|
|
936
|
+
throw await refusalBehindAbort(transaction, error);
|
|
898
937
|
}
|
|
899
938
|
}
|
|
900
939
|
async finishUniqueKeyBuild(input) {
|
|
@@ -986,8 +1025,7 @@ class IndexedDbBlockStore {
|
|
|
986
1025
|
return structuredClone(updated);
|
|
987
1026
|
} catch (error) {
|
|
988
1027
|
abortIfActive(transaction);
|
|
989
|
-
await
|
|
990
|
-
throw error;
|
|
1028
|
+
throw await refusalBehindAbort(transaction, error);
|
|
991
1029
|
}
|
|
992
1030
|
}
|
|
993
1031
|
async abortUniqueKeyBuild(input) {
|
|
@@ -1014,8 +1052,7 @@ class IndexedDbBlockStore {
|
|
|
1014
1052
|
await transactionDone(transaction);
|
|
1015
1053
|
} catch (error) {
|
|
1016
1054
|
abortIfActive(transaction);
|
|
1017
|
-
await
|
|
1018
|
-
throw error;
|
|
1055
|
+
throw await refusalBehindAbort(transaction, error);
|
|
1019
1056
|
}
|
|
1020
1057
|
await this.#cleanupUniqueKeyBuildArtifacts(input.buildId);
|
|
1021
1058
|
return true;
|
|
@@ -1040,8 +1077,7 @@ class IndexedDbBlockStore {
|
|
|
1040
1077
|
}
|
|
1041
1078
|
} catch (error) {
|
|
1042
1079
|
abortIfActive(transaction);
|
|
1043
|
-
await
|
|
1044
|
-
throw error;
|
|
1080
|
+
throw await refusalBehindAbort(transaction, error);
|
|
1045
1081
|
}
|
|
1046
1082
|
}
|
|
1047
1083
|
async dropTable(input) {
|
|
@@ -1125,6 +1161,7 @@ class IndexedDbBlockStore {
|
|
|
1125
1161
|
await bumpSchemaEpoch(catalog);
|
|
1126
1162
|
await transactionDone(transaction);
|
|
1127
1163
|
this.#manifestCache = void 0;
|
|
1164
|
+
this.#journalCache = void 0;
|
|
1128
1165
|
if (this.#uniqueKeyCache?.tableId === input.tableId || this.#uniqueKeyCache?.tableId.startsWith(`${input.tableId}\0secondary-index\0`) === true) {
|
|
1129
1166
|
this.#uniqueKeyCache = void 0;
|
|
1130
1167
|
} else if (this.#uniqueKeyCache !== void 0) {
|
|
@@ -1133,8 +1170,7 @@ class IndexedDbBlockStore {
|
|
|
1133
1170
|
return manifest;
|
|
1134
1171
|
} catch (error) {
|
|
1135
1172
|
abortIfActive(transaction);
|
|
1136
|
-
await
|
|
1137
|
-
throw error;
|
|
1173
|
+
throw await refusalBehindAbort(transaction, error);
|
|
1138
1174
|
}
|
|
1139
1175
|
}
|
|
1140
1176
|
async dropTableColumn(input) {
|
|
@@ -1265,13 +1301,13 @@ class IndexedDbBlockStore {
|
|
|
1265
1301
|
await bumpSchemaEpoch(catalog);
|
|
1266
1302
|
await transactionDone(transaction);
|
|
1267
1303
|
this.#manifestCache = void 0;
|
|
1304
|
+
this.#journalCache = void 0;
|
|
1268
1305
|
if (this.#uniqueKeyCache !== void 0)
|
|
1269
1306
|
this.#uniqueKeyCache.version = manifest.version;
|
|
1270
1307
|
return manifest;
|
|
1271
1308
|
} catch (error) {
|
|
1272
1309
|
abortIfActive(transaction);
|
|
1273
|
-
await
|
|
1274
|
-
throw error;
|
|
1310
|
+
throw await refusalBehindAbort(transaction, error);
|
|
1275
1311
|
}
|
|
1276
1312
|
}
|
|
1277
1313
|
async writeFtsBase(tableId, columnId, input) {
|
|
@@ -1407,8 +1443,7 @@ class IndexedDbBlockStore {
|
|
|
1407
1443
|
await transactionDone(transaction);
|
|
1408
1444
|
} catch (error) {
|
|
1409
1445
|
abortIfActive(transaction);
|
|
1410
|
-
await
|
|
1411
|
-
throw error;
|
|
1446
|
+
throw await refusalBehindAbort(transaction, error);
|
|
1412
1447
|
}
|
|
1413
1448
|
}
|
|
1414
1449
|
async writeFtsBaseBuildChunk(input) {
|
|
@@ -1826,8 +1861,7 @@ class IndexedDbBlockStore {
|
|
|
1826
1861
|
};
|
|
1827
1862
|
} catch (error) {
|
|
1828
1863
|
abortIfActive(transaction);
|
|
1829
|
-
await
|
|
1830
|
-
throw error;
|
|
1864
|
+
throw await refusalBehindAbort(transaction, error);
|
|
1831
1865
|
}
|
|
1832
1866
|
}
|
|
1833
1867
|
async removeAbortedSegment(segmentId, expectedTransactionId) {
|
|
@@ -1845,12 +1879,10 @@ class IndexedDbBlockStore {
|
|
|
1845
1879
|
if (segment.id !== segmentId || segment.transactionId !== expectedTransactionId) {
|
|
1846
1880
|
throw new Error(`Segment ${segmentId} is not owned by transaction ${expectedTransactionId}`);
|
|
1847
1881
|
}
|
|
1848
|
-
const
|
|
1849
|
-
|
|
1850
|
-
if (ownerValue === void 0) {
|
|
1882
|
+
const owner = await readTransactionRecord(transaction, expectedTransactionId);
|
|
1883
|
+
if (owner === void 0) {
|
|
1851
1884
|
throw corruption(`segments/${segmentId}`, `owning transaction ${expectedTransactionId} is missing`);
|
|
1852
1885
|
}
|
|
1853
|
-
const owner = asTransactionRecord(ownerValue, expectedTransactionId);
|
|
1854
1886
|
if (owner.status !== "aborted") {
|
|
1855
1887
|
throw new Error(`Segment ${segmentId} owner must be aborted; found ${owner.status}`);
|
|
1856
1888
|
}
|
|
@@ -1861,10 +1893,7 @@ class IndexedDbBlockStore {
|
|
|
1861
1893
|
if (published !== void 0) {
|
|
1862
1894
|
throw new Error(`Segment ${segmentId} is published by manifest ${String(published.version)}`);
|
|
1863
1895
|
}
|
|
1864
|
-
await
|
|
1865
|
-
if (typeof key !== "string")
|
|
1866
|
-
throw corruption("transactions", "record key is invalid");
|
|
1867
|
-
const candidate = asTransactionRecord(candidateValue, key);
|
|
1896
|
+
await visitTransactionRecords(transaction, (candidate) => {
|
|
1868
1897
|
if (candidate.status === "active" && candidate.pendingSegmentIds.includes(segmentId)) {
|
|
1869
1898
|
throw new Error(`Segment ${segmentId} is referenced by active transaction ${candidate.id}`);
|
|
1870
1899
|
}
|
|
@@ -1903,13 +1932,12 @@ class IndexedDbBlockStore {
|
|
|
1903
1932
|
segmentStore.delete(segmentId);
|
|
1904
1933
|
for (const { next } of rebasedSegments)
|
|
1905
1934
|
segmentStore.put(next, next.id);
|
|
1906
|
-
|
|
1935
|
+
putTransactionRecord(transaction, updatedOwner, journalChunkCountOf(owner));
|
|
1907
1936
|
await transactionDone(transaction);
|
|
1908
1937
|
return true;
|
|
1909
1938
|
} catch (error) {
|
|
1910
1939
|
abortIfActive(transaction);
|
|
1911
|
-
await
|
|
1912
|
-
throw error;
|
|
1940
|
+
throw await refusalBehindAbort(transaction, error);
|
|
1913
1941
|
}
|
|
1914
1942
|
}
|
|
1915
1943
|
async adoptAbortedSegment(input) {
|
|
@@ -1925,11 +1953,10 @@ class IndexedDbBlockStore {
|
|
|
1925
1953
|
const transaction = this.#transaction(["blocks", "catalog", "gc", "manifests", "segments", "statistics", "transactions"], "readwrite");
|
|
1926
1954
|
try {
|
|
1927
1955
|
const segmentStore = transaction.objectStore("segments");
|
|
1928
|
-
const
|
|
1929
|
-
const [storedValue, abortedValue, replacementValue] = await Promise.all([
|
|
1956
|
+
const [storedValue, aborted, replacement] = await Promise.all([
|
|
1930
1957
|
requestResult(segmentStore.get(desired.id)),
|
|
1931
|
-
|
|
1932
|
-
|
|
1958
|
+
readTransactionRecord(transaction, input.expectedAbortedTransactionId),
|
|
1959
|
+
readTransactionRecord(transaction, input.replacementTransactionId)
|
|
1933
1960
|
]);
|
|
1934
1961
|
if (storedValue === void 0)
|
|
1935
1962
|
throw new Error(`Segment does not exist: ${desired.id}`);
|
|
@@ -1943,11 +1970,9 @@ class IndexedDbBlockStore {
|
|
|
1943
1970
|
if (stored.transactionId !== input.expectedAbortedTransactionId || !sameStructuredValue(storedContent, desiredContent)) {
|
|
1944
1971
|
throw new Error(`Segment ${desired.id} does not match the immutable adoption record`);
|
|
1945
1972
|
}
|
|
1946
|
-
const aborted = abortedValue === void 0 ? void 0 : asTransactionRecord(abortedValue, input.expectedAbortedTransactionId);
|
|
1947
1973
|
if (aborted?.revision !== input.expectedAbortedTransactionRevision) {
|
|
1948
1974
|
throw new TransactionRecordConflictError(input.expectedAbortedTransactionId, input.expectedAbortedTransactionRevision, aborted?.revision ?? null);
|
|
1949
1975
|
}
|
|
1950
|
-
const replacement = replacementValue === void 0 ? void 0 : asTransactionRecord(replacementValue, input.replacementTransactionId);
|
|
1951
1976
|
if (replacement?.revision !== input.expectedReplacementTransactionRevision) {
|
|
1952
1977
|
throw new TransactionRecordConflictError(input.replacementTransactionId, input.expectedReplacementTransactionRevision, replacement?.revision ?? null);
|
|
1953
1978
|
}
|
|
@@ -1985,10 +2010,7 @@ class IndexedDbBlockStore {
|
|
|
1985
2010
|
if (await findReadableManifestBlock(transaction, segmentBlockIds(stored)) !== void 0) {
|
|
1986
2011
|
throw new Error(`Segment ${desired.id} is still reachable from a readable manifest`);
|
|
1987
2012
|
}
|
|
1988
|
-
await
|
|
1989
|
-
if (typeof key !== "string")
|
|
1990
|
-
throw corruption("transactions", "record key is invalid");
|
|
1991
|
-
const candidate = asTransactionRecord(candidateValue, key);
|
|
2013
|
+
await visitTransactionRecords(transaction, (candidate) => {
|
|
1992
2014
|
if (candidate.id !== aborted.id && candidate.id !== replacement.id && candidate.status === "active" && candidate.pendingSegmentIds.includes(desired.id)) {
|
|
1993
2015
|
throw new Error(`Segment ${desired.id} is referenced by active transaction ${candidate.id}`);
|
|
1994
2016
|
}
|
|
@@ -2033,7 +2055,6 @@ class IndexedDbBlockStore {
|
|
|
2033
2055
|
revision: incrementSafeInteger(replacement.revision, "Transaction revision"),
|
|
2034
2056
|
updatedAt: input.updatedAt
|
|
2035
2057
|
};
|
|
2036
|
-
assertTransactionArtifactJournalLimits(updatedReplacement.pendingBlockIds, updatedReplacement.pendingSegmentIds);
|
|
2037
2058
|
const statistics = transaction.objectStore("statistics");
|
|
2038
2059
|
await updateTransactionResourceLedger(statistics, aborted.id, {
|
|
2039
2060
|
blockCount: 0,
|
|
@@ -2051,14 +2072,13 @@ class IndexedDbBlockStore {
|
|
|
2051
2072
|
segmentStore.put(desired, desired.id);
|
|
2052
2073
|
for (const { next } of rebasedAbortedSegments)
|
|
2053
2074
|
segmentStore.put(next, next.id);
|
|
2054
|
-
|
|
2055
|
-
|
|
2075
|
+
putTransactionRecord(transaction, updatedAborted, journalChunkCountOf(aborted));
|
|
2076
|
+
putTransactionRecord(transaction, updatedReplacement, journalChunkCountOf(replacement));
|
|
2056
2077
|
await transactionDone(transaction);
|
|
2057
2078
|
return structuredClone(updatedReplacement);
|
|
2058
2079
|
} catch (error) {
|
|
2059
2080
|
abortIfActive(transaction);
|
|
2060
|
-
await
|
|
2061
|
-
throw error;
|
|
2081
|
+
throw await refusalBehindAbort(transaction, error);
|
|
2062
2082
|
}
|
|
2063
2083
|
}
|
|
2064
2084
|
async reserveRowIds(tableId, count) {
|
|
@@ -2080,8 +2100,7 @@ class IndexedDbBlockStore {
|
|
|
2080
2100
|
return { start, endExclusive };
|
|
2081
2101
|
} catch (error) {
|
|
2082
2102
|
abortIfActive(transaction);
|
|
2083
|
-
await
|
|
2084
|
-
throw error;
|
|
2103
|
+
throw await refusalBehindAbort(transaction, error);
|
|
2085
2104
|
}
|
|
2086
2105
|
}
|
|
2087
2106
|
async reserveAutoIncrement(tableId, columnId, count, atLeast) {
|
|
@@ -2102,8 +2121,7 @@ class IndexedDbBlockStore {
|
|
|
2102
2121
|
return { start, endExclusive };
|
|
2103
2122
|
} catch (error) {
|
|
2104
2123
|
abortIfActive(transaction);
|
|
2105
|
-
await
|
|
2106
|
-
throw error;
|
|
2124
|
+
throw await refusalBehindAbort(transaction, error);
|
|
2107
2125
|
}
|
|
2108
2126
|
}
|
|
2109
2127
|
async getExistingUniqueKeys(tableId, keyTokens) {
|
|
@@ -2194,17 +2212,13 @@ class IndexedDbBlockStore {
|
|
|
2194
2212
|
}
|
|
2195
2213
|
segments.sort((left, right) => left.id.localeCompare(right.id));
|
|
2196
2214
|
const transactionIds = [...new Set(segments.map((segment) => segment.transactionId))];
|
|
2197
|
-
const transactionStore = transaction.objectStore("transactions");
|
|
2198
2215
|
const transactions = [];
|
|
2199
2216
|
for (let start = 0; start < transactionIds.length; start += 64) {
|
|
2200
2217
|
const window = transactionIds.slice(start, start + 64);
|
|
2201
|
-
const
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
transactions.push(asTransactionRecord(value, id));
|
|
2206
|
-
}
|
|
2207
|
-
});
|
|
2218
|
+
for (const record of await readTransactionRecords(transaction, window)) {
|
|
2219
|
+
if (record !== void 0)
|
|
2220
|
+
transactions.push(record);
|
|
2221
|
+
}
|
|
2208
2222
|
}
|
|
2209
2223
|
await transactionDone(transaction);
|
|
2210
2224
|
return {
|
|
@@ -2216,8 +2230,7 @@ class IndexedDbBlockStore {
|
|
|
2216
2230
|
};
|
|
2217
2231
|
} catch (error) {
|
|
2218
2232
|
abortIfActive(transaction);
|
|
2219
|
-
await
|
|
2220
|
-
throw error;
|
|
2233
|
+
throw await refusalBehindAbort(transaction, error);
|
|
2221
2234
|
}
|
|
2222
2235
|
}
|
|
2223
2236
|
async getCurrentManifest() {
|
|
@@ -2316,7 +2329,9 @@ class IndexedDbBlockStore {
|
|
|
2316
2329
|
currentVersion: current ?? null,
|
|
2317
2330
|
replacementTransaction: normalized
|
|
2318
2331
|
});
|
|
2319
|
-
transaction
|
|
2332
|
+
addTransactionRecord(transaction, normalized);
|
|
2333
|
+
if (this.#journalCache?.transactionId === normalized.id)
|
|
2334
|
+
this.#journalCache = void 0;
|
|
2320
2335
|
let rowIds;
|
|
2321
2336
|
if (input.reserveRowIds !== void 0) {
|
|
2322
2337
|
validateCount(input.reserveRowIds.count);
|
|
@@ -2352,8 +2367,7 @@ class IndexedDbBlockStore {
|
|
|
2352
2367
|
};
|
|
2353
2368
|
} catch (error) {
|
|
2354
2369
|
abortIfActive(transaction);
|
|
2355
|
-
await
|
|
2356
|
-
throw error;
|
|
2370
|
+
throw await refusalBehindAbort(transaction, error);
|
|
2357
2371
|
}
|
|
2358
2372
|
}
|
|
2359
2373
|
async createTransaction(record) {
|
|
@@ -2395,65 +2409,68 @@ class IndexedDbBlockStore {
|
|
|
2395
2409
|
retainedBytes
|
|
2396
2410
|
});
|
|
2397
2411
|
}
|
|
2398
|
-
transaction
|
|
2412
|
+
addTransactionRecord(transaction, normalized);
|
|
2399
2413
|
await transactionDone(transaction);
|
|
2400
2414
|
} catch (error) {
|
|
2401
2415
|
abortIfActive(transaction);
|
|
2402
|
-
await
|
|
2403
|
-
throw error;
|
|
2416
|
+
throw await refusalBehindAbort(transaction, error);
|
|
2404
2417
|
}
|
|
2405
2418
|
}
|
|
2406
2419
|
async getTransaction(id) {
|
|
2407
2420
|
validateId(id, "Transaction ID");
|
|
2408
2421
|
const transaction = this.#transaction("transactions", "readonly");
|
|
2409
|
-
const
|
|
2422
|
+
const record = await readTransactionRecord(transaction, id);
|
|
2410
2423
|
await transactionDone(transaction);
|
|
2411
|
-
return
|
|
2424
|
+
return record;
|
|
2412
2425
|
}
|
|
2413
2426
|
async getTransactions(ids) {
|
|
2414
2427
|
assertStorageBulkReadItems(ids, "Transaction read");
|
|
2415
2428
|
for (const id of ids)
|
|
2416
2429
|
validateId(id, "Transaction ID");
|
|
2417
2430
|
const transaction = this.#transaction("transactions", "readonly");
|
|
2418
|
-
const
|
|
2419
|
-
const values = await Promise.all(ids.map((id) => requestResult(store.get(id))));
|
|
2431
|
+
const records = await readTransactionRecords(transaction, ids);
|
|
2420
2432
|
await transactionDone(transaction);
|
|
2421
|
-
return
|
|
2433
|
+
return records;
|
|
2422
2434
|
}
|
|
2423
2435
|
async listTransactionPage(afterId, limit) {
|
|
2424
2436
|
validatePageLimit(limit);
|
|
2425
2437
|
const transaction = this.#transaction("transactions", "readonly");
|
|
2426
|
-
const
|
|
2438
|
+
const page = await readCursorPage(transaction.objectStore("transactions"), limit, (value, key) => {
|
|
2427
2439
|
if (typeof key !== "string")
|
|
2428
2440
|
throw corruption("transactions", "record key is invalid");
|
|
2429
|
-
return
|
|
2441
|
+
return asStoredTransactionRecord(value, key);
|
|
2430
2442
|
}, (key) => typeof key === "string" && (afterId === null || key > afterId), afterId ?? void 0);
|
|
2443
|
+
const records = await Promise.all(page.map((stored) => assembleTransactionRecord(transaction, stored)));
|
|
2431
2444
|
await transactionDone(transaction);
|
|
2432
2445
|
return { records, nextCursor: records.length === limit ? records.at(-1)?.id ?? null : null };
|
|
2433
2446
|
}
|
|
2434
2447
|
async updateTransaction(id, expectedRevision, update) {
|
|
2435
2448
|
validateId(id, "Transaction ID");
|
|
2436
2449
|
const transaction = this.#transaction(["transactions", "manifests", "blocks", "catalog", "segments", "statistics"], "readwrite");
|
|
2437
|
-
const
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
if (current?.revision !== expectedRevision) {
|
|
2441
|
-
transaction.abort();
|
|
2450
|
+
const stored = await readStoredTransactionRecord(transaction, id);
|
|
2451
|
+
if (stored?.revision !== expectedRevision) {
|
|
2452
|
+
abortIfActive(transaction);
|
|
2442
2453
|
await ignoreAbort(transaction);
|
|
2443
|
-
throw new TransactionRecordConflictError(id, expectedRevision,
|
|
2454
|
+
throw new TransactionRecordConflictError(id, expectedRevision, stored?.revision ?? null);
|
|
2444
2455
|
}
|
|
2445
2456
|
try {
|
|
2457
|
+
const current = await assembleTransactionRecord(transaction, stored);
|
|
2446
2458
|
assertGenericTransactionUpdateAllowed(current, update);
|
|
2447
2459
|
const updated = updateTransactionRecord(current, update);
|
|
2448
2460
|
if (update.snapshotVersion !== void 0) {
|
|
2449
2461
|
await assertSnapshotAvailableInTransaction(transaction, updated.snapshotVersion);
|
|
2450
2462
|
}
|
|
2451
2463
|
await assertPendingArtifactsAvailableInTransaction(transaction, updated, update.pendingBlockIds !== void 0, update.pendingSegmentIds !== void 0, update.pendingSegmentIds !== void 0);
|
|
2452
|
-
const
|
|
2453
|
-
const
|
|
2454
|
-
if (
|
|
2464
|
+
const updatedBlocks = new Set(updated.pendingBlockIds);
|
|
2465
|
+
const updatedSegments = new Set(updated.pendingSegmentIds);
|
|
2466
|
+
if (current.pendingBlockIds.some((blockId) => !updatedBlocks.has(blockId)) || current.pendingSegmentIds.some((segmentId) => !updatedSegments.has(segmentId))) {
|
|
2455
2467
|
throw new TypeError("Use rollbackTransactionArtifacts to remove journaled artifacts");
|
|
2456
2468
|
}
|
|
2469
|
+
if (!extendsJournalOrder(current.pendingBlockIds, updated.pendingBlockIds) || !extendsJournalOrder(current.pendingSegmentIds, updated.pendingSegmentIds)) {
|
|
2470
|
+
throw new TypeError("Transaction journal updates must append after the journaled ids");
|
|
2471
|
+
}
|
|
2472
|
+
const addedBlocks = updated.pendingBlockIds.slice(current.pendingBlockIds.length);
|
|
2473
|
+
const addedSegments = updated.pendingSegmentIds.slice(current.pendingSegmentIds.length);
|
|
2457
2474
|
if (addedBlocks.length > 0 || addedSegments.length > 0) {
|
|
2458
2475
|
let retainedBytes = 0;
|
|
2459
2476
|
for (const id2 of addedBlocks) {
|
|
@@ -2465,13 +2482,15 @@ class IndexedDbBlockStore {
|
|
|
2465
2482
|
retainedBytes
|
|
2466
2483
|
});
|
|
2467
2484
|
}
|
|
2468
|
-
|
|
2485
|
+
if (addedBlocks.length > 0 || addedSegments.length > 0) {
|
|
2486
|
+
appendTransactionJournalChunks(transaction, stored, await readTransactionJournalTail(transaction, stored), addedBlocks, addedSegments);
|
|
2487
|
+
}
|
|
2488
|
+
putStoredTransactionRecord(transaction, storedTransactionRecord(updated));
|
|
2469
2489
|
await transactionDone(transaction);
|
|
2470
|
-
return
|
|
2490
|
+
return updated;
|
|
2471
2491
|
} catch (error) {
|
|
2472
2492
|
abortIfActive(transaction);
|
|
2473
|
-
await
|
|
2474
|
-
throw error;
|
|
2493
|
+
throw await refusalBehindAbort(transaction, error);
|
|
2475
2494
|
}
|
|
2476
2495
|
}
|
|
2477
2496
|
async renewTransaction(input) {
|
|
@@ -2480,13 +2499,11 @@ class IndexedDbBlockStore {
|
|
|
2480
2499
|
const cutoff = validateBoundedLeaseExpiration(input.expiresAtCutoff, input.expiresAt, "Transaction");
|
|
2481
2500
|
const transaction = this.#transaction(["transactions", "catalog", "leases"], "readwrite");
|
|
2482
2501
|
try {
|
|
2483
|
-
const
|
|
2484
|
-
|
|
2485
|
-
if (value === void 0) {
|
|
2502
|
+
const record = await readStoredTransactionRecord(transaction, input.transactionId);
|
|
2503
|
+
if (record === void 0) {
|
|
2486
2504
|
await transactionDone(transaction);
|
|
2487
2505
|
return false;
|
|
2488
2506
|
}
|
|
2489
|
-
const record = asTransactionRecord(value, input.transactionId);
|
|
2490
2507
|
if (record.status !== "active" || record.ownerId !== input.ownerId || Date.parse(record.expiresAt) <= cutoff) {
|
|
2491
2508
|
await transactionDone(transaction);
|
|
2492
2509
|
return false;
|
|
@@ -2499,13 +2516,12 @@ class IndexedDbBlockStore {
|
|
|
2499
2516
|
replacementTransaction: renewed,
|
|
2500
2517
|
excludeTransactionId: record.id
|
|
2501
2518
|
});
|
|
2502
|
-
|
|
2519
|
+
putStoredTransactionRecord(transaction, renewed);
|
|
2503
2520
|
await transactionDone(transaction);
|
|
2504
2521
|
return true;
|
|
2505
2522
|
} catch (error) {
|
|
2506
2523
|
abortIfActive(transaction);
|
|
2507
|
-
await
|
|
2508
|
-
throw error;
|
|
2524
|
+
throw await refusalBehindAbort(transaction, error);
|
|
2509
2525
|
}
|
|
2510
2526
|
}
|
|
2511
2527
|
async abortTransactionIfExpired(input) {
|
|
@@ -2521,29 +2537,28 @@ class IndexedDbBlockStore {
|
|
|
2521
2537
|
const transaction = this.#transaction("transactions", "readwrite");
|
|
2522
2538
|
try {
|
|
2523
2539
|
const store = transaction.objectStore("transactions");
|
|
2524
|
-
const
|
|
2525
|
-
if (
|
|
2540
|
+
const stored = await readStoredTransactionRecord(transaction, input.transactionId);
|
|
2541
|
+
if (stored === void 0) {
|
|
2526
2542
|
await transactionDone(transaction);
|
|
2527
2543
|
return void 0;
|
|
2528
2544
|
}
|
|
2529
|
-
|
|
2530
|
-
if (current.status !== "active" || current.ownerId !== input.expectedOwnerId || Date.parse(current.expiresAt) > cutoff) {
|
|
2545
|
+
if (stored.status !== "active" || stored.ownerId !== input.expectedOwnerId || Date.parse(stored.expiresAt) > cutoff) {
|
|
2531
2546
|
await transactionDone(transaction);
|
|
2532
2547
|
return void 0;
|
|
2533
2548
|
}
|
|
2549
|
+
const current = await assembleTransactionRecord(transaction, stored);
|
|
2534
2550
|
const updated = updateTransactionRecord(current, {
|
|
2535
2551
|
status: "aborted",
|
|
2536
2552
|
updatedAt: input.updatedAt,
|
|
2537
2553
|
committedVersion: null
|
|
2538
2554
|
});
|
|
2539
2555
|
await assertTerminalTransactionAdmission(store);
|
|
2540
|
-
|
|
2556
|
+
putStoredTransactionRecord(transaction, storedTransactionRecord(updated));
|
|
2541
2557
|
await transactionDone(transaction);
|
|
2542
2558
|
return structuredClone(updated);
|
|
2543
2559
|
} catch (error) {
|
|
2544
2560
|
abortIfActive(transaction);
|
|
2545
|
-
await
|
|
2546
|
-
throw error;
|
|
2561
|
+
throw await refusalBehindAbort(transaction, error);
|
|
2547
2562
|
}
|
|
2548
2563
|
}
|
|
2549
2564
|
async stageTransactionArtifacts(input) {
|
|
@@ -2551,9 +2566,7 @@ class IndexedDbBlockStore {
|
|
|
2551
2566
|
validateId(input.transactionId, "Transaction ID");
|
|
2552
2567
|
const transaction = this.#transaction(["blocks", "catalog", "segments", "statistics", "transactions"], "readwrite");
|
|
2553
2568
|
try {
|
|
2554
|
-
const
|
|
2555
|
-
const value = await requestResult(transactionStore.get(input.transactionId));
|
|
2556
|
-
const current = value === void 0 ? void 0 : asTransactionRecord(value, input.transactionId);
|
|
2569
|
+
const current = await readStoredTransactionRecord(transaction, input.transactionId);
|
|
2557
2570
|
if (current?.revision !== input.expectedRevision || current.status !== "active") {
|
|
2558
2571
|
throw new TransactionRecordConflictError(input.transactionId, input.expectedRevision, current?.revision ?? null);
|
|
2559
2572
|
}
|
|
@@ -2577,7 +2590,7 @@ class IndexedDbBlockStore {
|
|
|
2577
2590
|
if (normalized.transactionId !== input.transactionId) {
|
|
2578
2591
|
throw new Error(`Segment ${normalized.id} belongs to another transaction`);
|
|
2579
2592
|
}
|
|
2580
|
-
const expectedCommitOrdinal = current.
|
|
2593
|
+
const expectedCommitOrdinal = current.pendingSegmentCount + normalizedSegments.length;
|
|
2581
2594
|
if (normalized.commitOrdinal !== expectedCommitOrdinal) {
|
|
2582
2595
|
throw new TypeError(`Segment ${normalized.id} commit ordinal must be ${String(expectedCommitOrdinal)}`);
|
|
2583
2596
|
}
|
|
@@ -2585,14 +2598,9 @@ class IndexedDbBlockStore {
|
|
|
2585
2598
|
segmentIds.add(normalized.id);
|
|
2586
2599
|
normalizedSegments.push(normalized);
|
|
2587
2600
|
}
|
|
2588
|
-
const
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
...current.pendingSegmentIds,
|
|
2592
|
-
...normalizedSegments.map((segment) => segment.id)
|
|
2593
|
-
],
|
|
2594
|
-
updatedAt: input.updatedAt
|
|
2595
|
-
};
|
|
2601
|
+
const addedBlockIds = input.blocks.map((block) => block.id);
|
|
2602
|
+
const addedSegmentIds = normalizedSegments.map((segment) => segment.id);
|
|
2603
|
+
const update = { updatedAt: input.updatedAt };
|
|
2596
2604
|
if (current.pendingTable !== void 0) {
|
|
2597
2605
|
let nextRowId = current.pendingTableNextRowId;
|
|
2598
2606
|
if (nextRowId === void 0)
|
|
@@ -2606,16 +2614,17 @@ class IndexedDbBlockStore {
|
|
|
2606
2614
|
update.pendingTableNextRowId = nextRowId;
|
|
2607
2615
|
}
|
|
2608
2616
|
assertGenericTransactionUpdateAllowed(current, update);
|
|
2609
|
-
const
|
|
2610
|
-
|
|
2611
|
-
const
|
|
2617
|
+
const tail = await readTransactionJournalTail(transaction, current);
|
|
2618
|
+
const journal = await this.#journaledIds(transaction, current, tail);
|
|
2619
|
+
const rejournaled = addedBlockIds.find((id) => journal.blockIdSet.has(id));
|
|
2620
|
+
if (rejournaled !== void 0)
|
|
2621
|
+
throw new Error(`Block already exists: ${rejournaled}`);
|
|
2612
2622
|
for (const segment of normalizedSegments) {
|
|
2613
|
-
const unjournaledBlock = segmentBlockIds(segment).find((id) => !
|
|
2623
|
+
const unjournaledBlock = segmentBlockIds(segment).find((id) => !ids.has(id) && !journal.blockIdSet.has(id));
|
|
2614
2624
|
if (unjournaledBlock !== void 0) {
|
|
2615
2625
|
throw new Error(`Segment ${segment.id} references block absent from the transaction journal: ${unjournaledBlock}`);
|
|
2616
2626
|
}
|
|
2617
2627
|
}
|
|
2618
|
-
await assertPendingArtifactsAvailableInTransaction(transaction, current);
|
|
2619
2628
|
await updateTransactionResourceLedger(transaction.objectStore("statistics"), input.transactionId, {
|
|
2620
2629
|
blockCount: input.blocks.length,
|
|
2621
2630
|
segmentCount: normalizedSegments.length,
|
|
@@ -2635,15 +2644,42 @@ class IndexedDbBlockStore {
|
|
|
2635
2644
|
for (const segment of normalizedSegments) {
|
|
2636
2645
|
segmentStore.add(segment, segment.id);
|
|
2637
2646
|
}
|
|
2638
|
-
|
|
2647
|
+
const updated = {
|
|
2648
|
+
...appendTransactionJournalChunks(transaction, current, tail, addedBlockIds, addedSegmentIds),
|
|
2649
|
+
...update.pendingTableNextRowId === void 0 ? {} : { pendingTableNextRowId: update.pendingTableNextRowId },
|
|
2650
|
+
updatedAt: input.updatedAt,
|
|
2651
|
+
revision: incrementSafeInteger(current.revision, "Transaction revision")
|
|
2652
|
+
};
|
|
2653
|
+
putStoredTransactionRecord(transaction, updated);
|
|
2639
2654
|
await transactionDone(transaction);
|
|
2640
|
-
|
|
2655
|
+
for (const id of addedBlockIds) {
|
|
2656
|
+
journal.blockIds.push(id);
|
|
2657
|
+
journal.blockIdSet.add(id);
|
|
2658
|
+
}
|
|
2659
|
+
for (const id of addedSegmentIds)
|
|
2660
|
+
journal.segmentIds.push(id);
|
|
2661
|
+
journal.revision = updated.revision;
|
|
2662
|
+
this.#journalCache = journal;
|
|
2663
|
+
return transactionRecordWithJournal(updated, journal.blockIds, journal.segmentIds);
|
|
2641
2664
|
} catch (error) {
|
|
2642
2665
|
abortIfActive(transaction);
|
|
2643
|
-
await
|
|
2644
|
-
throw error;
|
|
2666
|
+
throw await refusalBehindAbort(transaction, error);
|
|
2645
2667
|
}
|
|
2646
2668
|
}
|
|
2669
|
+
async #journaledIds(transaction, stored, tail) {
|
|
2670
|
+
const cached = this.#journalCache;
|
|
2671
|
+
if (cached?.transactionId === stored.id && cached.revision === stored.revision && cached.blockIds.length === stored.pendingBlockCount && cached.segmentIds.length === stored.pendingSegmentCount && journalTailMatches(cached, tail)) {
|
|
2672
|
+
return cached;
|
|
2673
|
+
}
|
|
2674
|
+
const record = await assembleTransactionRecord(transaction, stored);
|
|
2675
|
+
return {
|
|
2676
|
+
transactionId: record.id,
|
|
2677
|
+
revision: record.revision,
|
|
2678
|
+
blockIds: record.pendingBlockIds,
|
|
2679
|
+
segmentIds: record.pendingSegmentIds,
|
|
2680
|
+
blockIdSet: new Set(record.pendingBlockIds)
|
|
2681
|
+
};
|
|
2682
|
+
}
|
|
2647
2683
|
async rollbackTransactionArtifacts(input) {
|
|
2648
2684
|
validateId(input.transactionId, "Transaction ID");
|
|
2649
2685
|
for (const id of [...input.pendingBlockIds, ...input.removeBlockIds]) {
|
|
@@ -2654,9 +2690,7 @@ class IndexedDbBlockStore {
|
|
|
2654
2690
|
}
|
|
2655
2691
|
const transaction = this.#transaction(["blocks", "catalog", "gc", "manifests", "segments", "statistics", "transactions"], "readwrite");
|
|
2656
2692
|
try {
|
|
2657
|
-
const
|
|
2658
|
-
const value = await requestResult(transactionStore.get(input.transactionId));
|
|
2659
|
-
const current = value === void 0 ? void 0 : asTransactionRecord(value, input.transactionId);
|
|
2693
|
+
const current = await readTransactionRecord(transaction, input.transactionId);
|
|
2660
2694
|
if (current?.revision !== input.expectedRevision || current.status !== "active") {
|
|
2661
2695
|
throw new TransactionRecordConflictError(input.transactionId, input.expectedRevision, current?.revision ?? null);
|
|
2662
2696
|
}
|
|
@@ -2679,12 +2713,9 @@ class IndexedDbBlockStore {
|
|
|
2679
2713
|
if (publishedBlock !== void 0) {
|
|
2680
2714
|
throw corruption(`transactions/${input.transactionId}`, `rollback journal names block ${publishedBlock.blockId} reachable by manifest ${String(publishedBlock.version)}`);
|
|
2681
2715
|
}
|
|
2682
|
-
await
|
|
2683
|
-
if (typeof key !== "string")
|
|
2684
|
-
throw corruption("transactions", "record key is invalid");
|
|
2716
|
+
await visitTransactionRecords(transaction, (record, key) => {
|
|
2685
2717
|
if (key === input.transactionId)
|
|
2686
2718
|
return;
|
|
2687
|
-
const record = asTransactionRecord(recordValue, key);
|
|
2688
2719
|
const shared = record.pendingBlockIds.find((id) => removedBlocks.has(id));
|
|
2689
2720
|
const sharedSegment = record.pendingSegmentIds.find((id) => removedSegments.has(id));
|
|
2690
2721
|
if (shared !== void 0 || sharedSegment !== void 0) {
|
|
@@ -2744,7 +2775,7 @@ class IndexedDbBlockStore {
|
|
|
2744
2775
|
let pendingTableNextRowId;
|
|
2745
2776
|
if (current.pendingTable !== void 0) {
|
|
2746
2777
|
const retained = await Promise.all(input.pendingSegmentIds.map((id) => requestResult(segmentStore.get(id))));
|
|
2747
|
-
const retainedSegments = retained.map((
|
|
2778
|
+
const retainedSegments = retained.map((value) => value === void 0 ? void 0 : asSegmentRecord(value)).filter((segment) => segment?.tableId === current.pendingTable?.id);
|
|
2748
2779
|
pendingTableNextRowId = 1n;
|
|
2749
2780
|
for (const segment of retainedSegments) {
|
|
2750
2781
|
if (segment.kind !== "insert" || segment.rowIdStart !== pendingTableNextRowId) {
|
|
@@ -2774,13 +2805,19 @@ class IndexedDbBlockStore {
|
|
|
2774
2805
|
}
|
|
2775
2806
|
for (const id of input.removeSegmentIds)
|
|
2776
2807
|
segmentStore.delete(id);
|
|
2777
|
-
|
|
2808
|
+
putTransactionRecord(transaction, updated, journalChunkCountOf(current));
|
|
2778
2809
|
await transactionDone(transaction);
|
|
2810
|
+
this.#journalCache = {
|
|
2811
|
+
transactionId: updated.id,
|
|
2812
|
+
revision: updated.revision,
|
|
2813
|
+
blockIds: [...updated.pendingBlockIds],
|
|
2814
|
+
segmentIds: [...updated.pendingSegmentIds],
|
|
2815
|
+
blockIdSet: new Set(updated.pendingBlockIds)
|
|
2816
|
+
};
|
|
2779
2817
|
return structuredClone(updated);
|
|
2780
2818
|
} catch (error) {
|
|
2781
2819
|
abortIfActive(transaction);
|
|
2782
|
-
await
|
|
2783
|
-
throw error;
|
|
2820
|
+
throw await refusalBehindAbort(transaction, error);
|
|
2784
2821
|
}
|
|
2785
2822
|
}
|
|
2786
2823
|
async commitTransaction(input) {
|
|
@@ -2805,14 +2842,13 @@ class IndexedDbBlockStore {
|
|
|
2805
2842
|
const transaction = this.#transaction(storeNames2, "readwrite");
|
|
2806
2843
|
try {
|
|
2807
2844
|
const record = await readActiveTransactionRecord(transaction, input.transactionId, input.expectedTransactionRevision);
|
|
2808
|
-
const outcome = await this.#commitInTransaction(transaction, record, input);
|
|
2845
|
+
const outcome = await this.#commitInTransaction(transaction, record, "stored", input);
|
|
2809
2846
|
await transactionDone(transaction);
|
|
2810
2847
|
outcome.settle();
|
|
2811
2848
|
return outcome.manifest;
|
|
2812
2849
|
} catch (error) {
|
|
2813
2850
|
abortIfActive(transaction);
|
|
2814
|
-
await
|
|
2815
|
-
throw error;
|
|
2851
|
+
throw await refusalBehindAbort(transaction, error);
|
|
2816
2852
|
}
|
|
2817
2853
|
}
|
|
2818
2854
|
async writeTransaction(input) {
|
|
@@ -2863,7 +2899,6 @@ class IndexedDbBlockStore {
|
|
|
2863
2899
|
storeNames2.push("gc");
|
|
2864
2900
|
const transaction = this.#transaction(storeNames2, "readwrite");
|
|
2865
2901
|
try {
|
|
2866
|
-
const transactionStore = transaction.objectStore("transactions");
|
|
2867
2902
|
let base;
|
|
2868
2903
|
if ("record" in input.transaction) {
|
|
2869
2904
|
const id = input.transaction.record.id;
|
|
@@ -2871,7 +2906,7 @@ class IndexedDbBlockStore {
|
|
|
2871
2906
|
...input.transaction.record,
|
|
2872
2907
|
snapshotVersion: input.expectedManifestVersion
|
|
2873
2908
|
});
|
|
2874
|
-
if (await requestResult(
|
|
2909
|
+
if (await requestResult(transaction.objectStore("transactions").getKey(id)) !== void 0) {
|
|
2875
2910
|
throw new Error(`Transaction already exists: ${id}`);
|
|
2876
2911
|
}
|
|
2877
2912
|
const current = asOptionalManifestVersion(await requestResult(transaction.objectStore("catalog").get(CURRENT_MANIFEST_KEY)), CURRENT_MANIFEST_KEY);
|
|
@@ -2917,7 +2952,13 @@ class IndexedDbBlockStore {
|
|
|
2917
2952
|
stageUpdate.pendingTableNextRowId = nextRowId;
|
|
2918
2953
|
}
|
|
2919
2954
|
const staged = updateTransactionRecord(base, stageUpdate);
|
|
2920
|
-
|
|
2955
|
+
const stagedBlockIds = new Set(staged.pendingBlockIds);
|
|
2956
|
+
for (const segment of normalizedSegments) {
|
|
2957
|
+
const unjournaledBlock = segmentBlockIds(segment).find((id) => !stagedBlockIds.has(id));
|
|
2958
|
+
if (unjournaledBlock !== void 0) {
|
|
2959
|
+
throw new Error(`Segment ${segment.id} references block absent from the transaction journal: ${unjournaledBlock}`);
|
|
2960
|
+
}
|
|
2961
|
+
}
|
|
2921
2962
|
await assertPendingArtifactsAvailableInTransaction(transaction, base);
|
|
2922
2963
|
await updateRecordResourceLedger(transaction.objectStore("statistics"), {
|
|
2923
2964
|
segments: normalizedSegments.map((segment) => ({ next: segment }))
|
|
@@ -2933,17 +2974,16 @@ class IndexedDbBlockStore {
|
|
|
2933
2974
|
for (const segment of normalizedSegments) {
|
|
2934
2975
|
segmentStore.add(segment, segment.id);
|
|
2935
2976
|
}
|
|
2936
|
-
const outcome = await this.#commitInTransaction(transaction, staged, input);
|
|
2977
|
+
const outcome = await this.#commitInTransaction(transaction, staged, { replacedChunkCount: journalChunkCountOf(base) }, input);
|
|
2937
2978
|
await transactionDone(transaction);
|
|
2938
2979
|
outcome.settle();
|
|
2939
2980
|
return outcome.manifest;
|
|
2940
2981
|
} catch (error) {
|
|
2941
2982
|
abortIfActive(transaction);
|
|
2942
|
-
await
|
|
2943
|
-
throw error;
|
|
2983
|
+
throw await refusalBehindAbort(transaction, error);
|
|
2944
2984
|
}
|
|
2945
2985
|
}
|
|
2946
|
-
async #commitInTransaction(transaction, record, input) {
|
|
2986
|
+
async #commitInTransaction(transaction, record, journal, input) {
|
|
2947
2987
|
incrementSafeInteger(record.revision, "Transaction revision");
|
|
2948
2988
|
const transactionStore = transaction.objectStore("transactions");
|
|
2949
2989
|
const catalog = transaction.objectStore("catalog");
|
|
@@ -3001,15 +3041,11 @@ class IndexedDbBlockStore {
|
|
|
3001
3041
|
if (pendingRemovedBlock !== void 0) {
|
|
3002
3042
|
throw new Error(`Cannot supersede a pending block: ${pendingRemovedBlock}`);
|
|
3003
3043
|
}
|
|
3004
|
-
let baseBlockIdSet;
|
|
3005
3044
|
const removedMembership = await manifestBlockMembershipInTransaction(catalog, input.expectedManifestVersion, removedBlockIds);
|
|
3006
3045
|
const invalidRemovedIndex = removedMembership.findIndex((present) => !present);
|
|
3007
3046
|
if (invalidRemovedIndex >= 0) {
|
|
3008
3047
|
throw new Error(`Cannot supersede a block outside the transaction snapshot: ${removedBlockIds[invalidRemovedIndex] ?? ""}`);
|
|
3009
3048
|
}
|
|
3010
|
-
if ((input.levelZeroSegmentLimits?.length ?? 0) > 0) {
|
|
3011
|
-
baseBlockIdSet = await resolveManifestBlockSetInTransaction(catalog, input.expectedManifestVersion);
|
|
3012
|
-
}
|
|
3013
3049
|
if (removedBlockIds.length > 0) {
|
|
3014
3050
|
await assertCompactionRetirementInTransaction(transaction, record, input, removedBlockIds);
|
|
3015
3051
|
}
|
|
@@ -3031,7 +3067,7 @@ class IndexedDbBlockStore {
|
|
|
3031
3067
|
throw new Error(`Segment ${foreignSegment.id} belongs to another transaction`);
|
|
3032
3068
|
}
|
|
3033
3069
|
await assertCompactionOutputSegmentsInTransaction(transaction, record, input.compactionJobId, pendingSegments);
|
|
3034
|
-
await assertLevelZeroSegmentLimits(transaction, input.levelZeroSegmentLimits ?? [], pendingSegments,
|
|
3070
|
+
await assertLevelZeroSegmentLimits(transaction, input.levelZeroSegmentLimits ?? [], pendingSegments, input.expectedManifestVersion, pendingTable);
|
|
3035
3071
|
const uniqueKeyEntries = input.uniqueKeyChanges ?? [];
|
|
3036
3072
|
const coveredUniqueNamespaces = new Set(uniqueKeyEntries.map((entry) => entry.tableId));
|
|
3037
3073
|
const changedTableIds = /* @__PURE__ */ new Set([
|
|
@@ -3378,7 +3414,11 @@ class IndexedDbBlockStore {
|
|
|
3378
3414
|
if (ftsDeltaCounts.length > 0)
|
|
3379
3415
|
manifest.ftsDeltaCounts = ftsDeltaCounts;
|
|
3380
3416
|
await clearTransactionResourceLedger(transaction.objectStore("statistics"), committed.id);
|
|
3381
|
-
|
|
3417
|
+
if (journal === "stored") {
|
|
3418
|
+
putStoredTransactionRecord(transaction, storedTransactionRecord(committed));
|
|
3419
|
+
} else {
|
|
3420
|
+
putTransactionRecord(transaction, committed, journal.replacedChunkCount);
|
|
3421
|
+
}
|
|
3382
3422
|
const settle = () => {
|
|
3383
3423
|
if (pendingTable !== void 0) {
|
|
3384
3424
|
this.#tableIdsByName.set(pendingTable.name, pendingTable.id);
|
|
@@ -3439,6 +3479,7 @@ class IndexedDbBlockStore {
|
|
|
3439
3479
|
this.#uniqueKeyCache.version = manifest.version;
|
|
3440
3480
|
}
|
|
3441
3481
|
this.#manifestCache = void 0;
|
|
3482
|
+
this.#journalCache = void 0;
|
|
3442
3483
|
};
|
|
3443
3484
|
return { manifest, settle };
|
|
3444
3485
|
}
|
|
@@ -3467,8 +3508,7 @@ class IndexedDbBlockStore {
|
|
|
3467
3508
|
await transactionDone(transaction);
|
|
3468
3509
|
} catch (error) {
|
|
3469
3510
|
abortIfActive(transaction);
|
|
3470
|
-
await
|
|
3471
|
-
throw error;
|
|
3511
|
+
throw await refusalBehindAbort(transaction, error);
|
|
3472
3512
|
}
|
|
3473
3513
|
}
|
|
3474
3514
|
async getLease(id) {
|
|
@@ -3547,8 +3587,7 @@ class IndexedDbBlockStore {
|
|
|
3547
3587
|
return structuredClone(renewed);
|
|
3548
3588
|
} catch (error) {
|
|
3549
3589
|
abortIfActive(transaction);
|
|
3550
|
-
await
|
|
3551
|
-
throw error;
|
|
3590
|
+
throw await refusalBehindAbort(transaction, error);
|
|
3552
3591
|
}
|
|
3553
3592
|
}
|
|
3554
3593
|
async moveLease(input) {
|
|
@@ -3589,8 +3628,7 @@ class IndexedDbBlockStore {
|
|
|
3589
3628
|
return structuredClone(moved);
|
|
3590
3629
|
} catch (error) {
|
|
3591
3630
|
abortIfActive(transaction);
|
|
3592
|
-
await
|
|
3593
|
-
throw error;
|
|
3631
|
+
throw await refusalBehindAbort(transaction, error);
|
|
3594
3632
|
}
|
|
3595
3633
|
}
|
|
3596
3634
|
async removeLeaseIfExpired(id, expectedRevision, expiresAtCutoff) {
|
|
@@ -3776,8 +3814,7 @@ class IndexedDbBlockStore {
|
|
|
3776
3814
|
}
|
|
3777
3815
|
await assertActiveCompactionMarker(jobStore, current);
|
|
3778
3816
|
incrementSafeInteger(current.revision, "Compaction job revision");
|
|
3779
|
-
const
|
|
3780
|
-
const linkedTransaction = transactionValue === void 0 ? void 0 : asTransactionRecord(transactionValue);
|
|
3817
|
+
const linkedTransaction = current.transactionId === null ? void 0 : await readTransactionRecord(transaction, current.transactionId);
|
|
3781
3818
|
let updated;
|
|
3782
3819
|
let updatedLinkedTransaction;
|
|
3783
3820
|
try {
|
|
@@ -3815,7 +3852,7 @@ class IndexedDbBlockStore {
|
|
|
3815
3852
|
const terminalCompactionJobs = checkedQuotaIncrement(quota.terminalCompactionJobs, "Terminal compaction job count");
|
|
3816
3853
|
assertMaintenanceLimit("terminal compaction job", terminalCompactionJobs, MAX_TERMINAL_COMPACTION_JOB_RECORDS);
|
|
3817
3854
|
if (updatedLinkedTransaction !== void 0) {
|
|
3818
|
-
|
|
3855
|
+
putStoredTransactionRecord(transaction, storedTransactionRecord(updatedLinkedTransaction));
|
|
3819
3856
|
}
|
|
3820
3857
|
jobStore.put(compactionJobEnvelope(updated), key);
|
|
3821
3858
|
jobStore.put({
|
|
@@ -3858,8 +3895,7 @@ class IndexedDbBlockStore {
|
|
|
3858
3895
|
return true;
|
|
3859
3896
|
} catch (error) {
|
|
3860
3897
|
abortIfActive(transaction);
|
|
3861
|
-
await
|
|
3862
|
-
throw error;
|
|
3898
|
+
throw await refusalBehindAbort(transaction, error);
|
|
3863
3899
|
}
|
|
3864
3900
|
}
|
|
3865
3901
|
async createGarbageCollectionJob(input) {
|
|
@@ -3899,8 +3935,7 @@ class IndexedDbBlockStore {
|
|
|
3899
3935
|
return structuredClone(record);
|
|
3900
3936
|
} catch (error) {
|
|
3901
3937
|
abortIfActive(transaction);
|
|
3902
|
-
await
|
|
3903
|
-
throw error;
|
|
3938
|
+
throw await refusalBehindAbort(transaction, error);
|
|
3904
3939
|
}
|
|
3905
3940
|
}
|
|
3906
3941
|
async updateGarbageCollectionPlanning(input) {
|
|
@@ -3928,8 +3963,7 @@ class IndexedDbBlockStore {
|
|
|
3928
3963
|
return structuredClone(updated);
|
|
3929
3964
|
} catch (error) {
|
|
3930
3965
|
abortIfActive(transaction);
|
|
3931
|
-
await
|
|
3932
|
-
throw error;
|
|
3966
|
+
throw await refusalBehindAbort(transaction, error);
|
|
3933
3967
|
}
|
|
3934
3968
|
}
|
|
3935
3969
|
async getGarbageCollectionJob(id) {
|
|
@@ -3984,7 +4018,6 @@ class IndexedDbBlockStore {
|
|
|
3984
4018
|
const manifestStore = transaction.objectStore("manifests");
|
|
3985
4019
|
const segmentStore = transaction.objectStore("segments");
|
|
3986
4020
|
const blockStore = transaction.objectStore("blocks");
|
|
3987
|
-
const transactionStore = transaction.objectStore("transactions");
|
|
3988
4021
|
const currentVersionValue = await requestResult(catalog.get(CURRENT_MANIFEST_KEY));
|
|
3989
4022
|
const currentVersion = asOptionalManifestVersion(currentVersionValue, CURRENT_MANIFEST_KEY) ?? null;
|
|
3990
4023
|
const leaseCutoff = Date.parse(current.leaseCutoff);
|
|
@@ -4082,8 +4115,7 @@ class IndexedDbBlockStore {
|
|
|
4082
4115
|
if (id === void 0) {
|
|
4083
4116
|
throw new Error("Garbage collection transaction cursor is invalid");
|
|
4084
4117
|
}
|
|
4085
|
-
const
|
|
4086
|
-
const record = transactionValue === void 0 ? void 0 : asTransactionRecord(transactionValue, id);
|
|
4118
|
+
const record = await readTransactionRecord(transaction, id);
|
|
4087
4119
|
const manifestValue = record?.committedVersion === null || record?.committedVersion === void 0 ? void 0 : await requestResult(manifestStore.get(record.committedVersion));
|
|
4088
4120
|
const manifest = manifestValue === void 0 ? void 0 : asStoredManifestRecord(manifestValue, record?.committedVersion ?? void 0);
|
|
4089
4121
|
if (record === void 0)
|
|
@@ -4092,7 +4124,9 @@ class IndexedDbBlockStore {
|
|
|
4092
4124
|
retainedTransactionIds.push(id);
|
|
4093
4125
|
} else {
|
|
4094
4126
|
await clearTransactionResourceLedger(transaction.objectStore("statistics"), id);
|
|
4095
|
-
|
|
4127
|
+
deleteTransactionRecord(transaction, storedTransactionRecord(record));
|
|
4128
|
+
if (this.#journalCache?.transactionId === id)
|
|
4129
|
+
this.#journalCache = void 0;
|
|
4096
4130
|
reclaimedTransactionIds.push(id);
|
|
4097
4131
|
}
|
|
4098
4132
|
transactionIndex += 1;
|
|
@@ -4180,8 +4214,7 @@ class IndexedDbBlockStore {
|
|
|
4180
4214
|
};
|
|
4181
4215
|
} catch (error) {
|
|
4182
4216
|
abortIfActive(transaction);
|
|
4183
|
-
await
|
|
4184
|
-
throw error;
|
|
4217
|
+
throw await refusalBehindAbort(transaction, error);
|
|
4185
4218
|
}
|
|
4186
4219
|
}
|
|
4187
4220
|
async removePrunedManifestRecords(maxItems) {
|
|
@@ -4243,8 +4276,7 @@ class IndexedDbBlockStore {
|
|
|
4243
4276
|
return removed;
|
|
4244
4277
|
} catch (error) {
|
|
4245
4278
|
abortIfActive(transaction);
|
|
4246
|
-
await
|
|
4247
|
-
throw error;
|
|
4279
|
+
throw await refusalBehindAbort(transaction, error);
|
|
4248
4280
|
}
|
|
4249
4281
|
}
|
|
4250
4282
|
async removeGarbageCollectionJob(id) {
|
|
@@ -4339,6 +4371,7 @@ class IndexedDbBlockStore {
|
|
|
4339
4371
|
this.#tableIdsByName.clear();
|
|
4340
4372
|
this.#uniqueKeyCache = void 0;
|
|
4341
4373
|
this.#manifestCache = void 0;
|
|
4374
|
+
this.#journalCache = void 0;
|
|
4342
4375
|
return {
|
|
4343
4376
|
identity,
|
|
4344
4377
|
removedBlockCount: marker.kindItemCounts[SNAPSHOT_FRAME_KINDS.indexOf("block")] ?? 0,
|
|
@@ -4346,8 +4379,7 @@ class IndexedDbBlockStore {
|
|
|
4346
4379
|
};
|
|
4347
4380
|
} catch (error) {
|
|
4348
4381
|
abortIfActive(transaction);
|
|
4349
|
-
await
|
|
4350
|
-
throw error;
|
|
4382
|
+
throw await refusalBehindAbort(transaction, error);
|
|
4351
4383
|
}
|
|
4352
4384
|
}
|
|
4353
4385
|
async getStorageStats() {
|
|
@@ -4899,11 +4931,10 @@ class IndexedDbBlockStore {
|
|
|
4899
4931
|
observedSegmentBytes = safeByteSum(observedSegmentBytes, segmentRecordRetainedBytes(segment), "Integrity segment bytes");
|
|
4900
4932
|
if (segment.id !== key)
|
|
4901
4933
|
throw corruption(`segments/${key}`, "record id differs from key");
|
|
4902
|
-
const
|
|
4903
|
-
if (
|
|
4934
|
+
const owner = await readTransactionRecord(transaction, segment.transactionId);
|
|
4935
|
+
if (owner === void 0) {
|
|
4904
4936
|
throw corruption(`segments/${key}`, `owning transaction ${segment.transactionId} is missing`);
|
|
4905
4937
|
}
|
|
4906
|
-
const owner = asTransactionRecord(ownerValue, segment.transactionId);
|
|
4907
4938
|
const segmentTable = tablesById.get(segment.tableId) ?? (owner.status === "active" && owner.pendingTable?.id === segment.tableId ? owner.pendingTable : void 0);
|
|
4908
4939
|
if (segmentTable === void 0) {
|
|
4909
4940
|
throw corruption(`segments/${key}`, `table ${segment.tableId} is missing`);
|
|
@@ -4932,7 +4963,7 @@ class IndexedDbBlockStore {
|
|
|
4932
4963
|
try {
|
|
4933
4964
|
if (typeof key !== "string")
|
|
4934
4965
|
throw corruption("transactions", "record key is invalid");
|
|
4935
|
-
const record =
|
|
4966
|
+
const record = await assembleTransactionRecord(transaction, asStoredTransactionRecord(value, key));
|
|
4936
4967
|
for (const [commitOrdinal, segmentId] of record.pendingSegmentIds.entries()) {
|
|
4937
4968
|
const segmentValue = await requestResult(segmentStore.get(segmentId));
|
|
4938
4969
|
if (segmentValue === void 0) {
|
|
@@ -4961,13 +4992,7 @@ class IndexedDbBlockStore {
|
|
|
4961
4992
|
if (alreadyLive) {
|
|
4962
4993
|
throw corruption(`transactions/${key}`, `pending block ${blockId} is already live in the current manifest`);
|
|
4963
4994
|
}
|
|
4964
|
-
const journaledByAnotherActiveTransaction = await
|
|
4965
|
-
if (typeof candidateKey !== "string") {
|
|
4966
|
-
throw corruption("transactions", "record key is invalid");
|
|
4967
|
-
}
|
|
4968
|
-
const candidate = asTransactionRecord(candidateValue, candidateKey);
|
|
4969
|
-
return candidate.id !== record.id && candidate.status === "active" && candidate.pendingBlockIds.includes(blockId);
|
|
4970
|
-
});
|
|
4995
|
+
const journaledByAnotherActiveTransaction = await visitTransactionRecords(transaction, (candidate) => candidate.id !== record.id && candidate.status === "active" && candidate.pendingBlockIds.includes(blockId));
|
|
4971
4996
|
if (journaledByAnotherActiveTransaction) {
|
|
4972
4997
|
throw corruption(`transactions/${key}`, `pending block ${blockId} is journaled by another active transaction`);
|
|
4973
4998
|
}
|
|
@@ -5053,6 +5078,25 @@ class IndexedDbBlockStore {
|
|
|
5053
5078
|
} catch (error) {
|
|
5054
5079
|
issue("invalid-pending-catalog", "transactions", error);
|
|
5055
5080
|
}
|
|
5081
|
+
await visitObjectStoreSequentially(transaction.objectStore(TRANSACTION_JOURNAL_STORE), async (value, key) => {
|
|
5082
|
+
checkedRecords += 1;
|
|
5083
|
+
try {
|
|
5084
|
+
if (!Array.isArray(key) || key.length !== 2 || typeof key[0] !== "string" || typeof key[1] !== "number" || !Number.isSafeInteger(key[1]) || key[1] < 0) {
|
|
5085
|
+
throw corruption(TRANSACTION_JOURNAL_STORE, "journal chunk key is invalid");
|
|
5086
|
+
}
|
|
5087
|
+
const [transactionId, chunkIndex] = key;
|
|
5088
|
+
asTransactionJournalChunk(value, `transactions/${transactionId}/journal/${String(chunkIndex)}`);
|
|
5089
|
+
const owner = await readStoredTransactionRecord(transaction, transactionId);
|
|
5090
|
+
if (owner === void 0) {
|
|
5091
|
+
throw corruption(`transactions/${transactionId}`, `journal chunk ${String(chunkIndex)} has no transaction record`);
|
|
5092
|
+
}
|
|
5093
|
+
if (chunkIndex >= owner.journalChunkCount) {
|
|
5094
|
+
throw corruption(`transactions/${transactionId}`, `journal chunk ${String(chunkIndex)} lies beyond the declared ${String(owner.journalChunkCount)}`);
|
|
5095
|
+
}
|
|
5096
|
+
} catch (error) {
|
|
5097
|
+
issue("orphan-transaction-journal", storageKeyLocation(key), error);
|
|
5098
|
+
}
|
|
5099
|
+
});
|
|
5056
5100
|
const leaseIds = /* @__PURE__ */ new Set();
|
|
5057
5101
|
await visitObjectStoreSequentially(transaction.objectStore("leases"), async (value, key) => {
|
|
5058
5102
|
checkedRecords += 1;
|
|
@@ -5115,11 +5159,10 @@ class IndexedDbBlockStore {
|
|
|
5115
5159
|
}
|
|
5116
5160
|
}
|
|
5117
5161
|
if (job.transactionId !== null) {
|
|
5118
|
-
const
|
|
5119
|
-
if (
|
|
5162
|
+
const owner = await readTransactionRecord(transaction, job.transactionId);
|
|
5163
|
+
if (owner === void 0) {
|
|
5120
5164
|
throw corruption(`gc/${key}`, "linked transaction is missing");
|
|
5121
5165
|
}
|
|
5122
|
-
const owner = asTransactionRecord(transactionValue, job.transactionId);
|
|
5123
5166
|
if (job.state === "ready" || owner.pendingSegmentIds.length > 0) {
|
|
5124
5167
|
const table = tablesById.get(job.tableId);
|
|
5125
5168
|
if (table === void 0)
|
|
@@ -5164,11 +5207,8 @@ class IndexedDbBlockStore {
|
|
|
5164
5207
|
if (candidate !== void 0)
|
|
5165
5208
|
asBytes(candidate, `blocks/${id}`);
|
|
5166
5209
|
}
|
|
5167
|
-
for (const id of job.candidateTransactionIds)
|
|
5168
|
-
|
|
5169
|
-
if (candidate !== void 0)
|
|
5170
|
-
asTransactionRecord(candidate, id);
|
|
5171
|
-
}
|
|
5210
|
+
for (const id of job.candidateTransactionIds)
|
|
5211
|
+
await readTransactionRecord(transaction, id);
|
|
5172
5212
|
} else if (key.startsWith(ACTIVE_COMPACTION_KEY_PREFIX)) {
|
|
5173
5213
|
let tableId;
|
|
5174
5214
|
try {
|
|
@@ -5359,11 +5399,10 @@ class IndexedDbBlockStore {
|
|
|
5359
5399
|
const ledger = asOptionalTransactionResourceLedger(value, transactionId);
|
|
5360
5400
|
if (ledger === void 0)
|
|
5361
5401
|
throw corruption(`statistics/${key}`, "transaction ledger is missing");
|
|
5362
|
-
const
|
|
5363
|
-
if (
|
|
5402
|
+
const owner = await readTransactionRecord(transaction, transactionId);
|
|
5403
|
+
if (owner === void 0) {
|
|
5364
5404
|
throw corruption(`statistics/${key}`, "transaction resource ledger has no transaction");
|
|
5365
5405
|
}
|
|
5366
|
-
const owner = asTransactionRecord(transactionValue, transactionId);
|
|
5367
5406
|
if (owner.status === "committed" || ledger.blockCount !== owner.pendingBlockIds.length || ledger.segmentCount !== owner.pendingSegmentIds.length) {
|
|
5368
5407
|
throw corruption(`statistics/${key}`, "transaction resource ledger disagrees with its journal");
|
|
5369
5408
|
}
|
|
@@ -5532,8 +5571,7 @@ class IndexedDbBlockStore {
|
|
|
5532
5571
|
return { sessionId, ownerId: input.ownerId, expiresAt: input.expiresAt, header };
|
|
5533
5572
|
} catch (error) {
|
|
5534
5573
|
abortIfActive(transaction);
|
|
5535
|
-
await
|
|
5536
|
-
throw error;
|
|
5574
|
+
throw await refusalBehindAbort(transaction, error);
|
|
5537
5575
|
}
|
|
5538
5576
|
}
|
|
5539
5577
|
async readSnapshotExportFrame(input) {
|
|
@@ -5609,8 +5647,7 @@ class IndexedDbBlockStore {
|
|
|
5609
5647
|
return frame;
|
|
5610
5648
|
} catch (error) {
|
|
5611
5649
|
abortIfActive(transaction);
|
|
5612
|
-
await
|
|
5613
|
-
throw error;
|
|
5650
|
+
throw await refusalBehindAbort(transaction, error);
|
|
5614
5651
|
}
|
|
5615
5652
|
}
|
|
5616
5653
|
async closeSnapshotFrameExport(input) {
|
|
@@ -5638,8 +5675,7 @@ class IndexedDbBlockStore {
|
|
|
5638
5675
|
return true;
|
|
5639
5676
|
} catch (error) {
|
|
5640
5677
|
abortIfActive(transaction);
|
|
5641
|
-
await
|
|
5642
|
-
throw error;
|
|
5678
|
+
throw await refusalBehindAbort(transaction, error);
|
|
5643
5679
|
}
|
|
5644
5680
|
}
|
|
5645
5681
|
async beginSnapshotFrameImport(input) {
|
|
@@ -5739,6 +5775,7 @@ class IndexedDbBlockStore {
|
|
|
5739
5775
|
transaction.objectStore("manifests").clear();
|
|
5740
5776
|
transaction.objectStore("segments").clear();
|
|
5741
5777
|
transaction.objectStore("transactions").clear();
|
|
5778
|
+
transaction.objectStore(TRANSACTION_JOURNAL_STORE).clear();
|
|
5742
5779
|
transaction.objectStore(SNAPSHOT_HEADER_STORE).clear();
|
|
5743
5780
|
const statistics = transaction.objectStore("statistics");
|
|
5744
5781
|
statistics.clear();
|
|
@@ -5769,8 +5806,7 @@ class IndexedDbBlockStore {
|
|
|
5769
5806
|
return snapshotFrameImportSession(marker);
|
|
5770
5807
|
} catch (error) {
|
|
5771
5808
|
abortIfActive(transaction);
|
|
5772
|
-
await
|
|
5773
|
-
throw error;
|
|
5809
|
+
throw await refusalBehindAbort(transaction, error);
|
|
5774
5810
|
}
|
|
5775
5811
|
}
|
|
5776
5812
|
async renewSnapshotFrameImport(input) {
|
|
@@ -5788,8 +5824,7 @@ class IndexedDbBlockStore {
|
|
|
5788
5824
|
return snapshotFrameImportSession(renewed);
|
|
5789
5825
|
} catch (error) {
|
|
5790
5826
|
abortIfActive(transaction);
|
|
5791
|
-
await
|
|
5792
|
-
throw error;
|
|
5827
|
+
throw await refusalBehindAbort(transaction, error);
|
|
5793
5828
|
}
|
|
5794
5829
|
}
|
|
5795
5830
|
async appendSnapshotImportFrames(input) {
|
|
@@ -5910,8 +5945,7 @@ class IndexedDbBlockStore {
|
|
|
5910
5945
|
return snapshotFrameImportSession(marker);
|
|
5911
5946
|
} catch (error) {
|
|
5912
5947
|
abortIfActive(transaction);
|
|
5913
|
-
await
|
|
5914
|
-
throw error;
|
|
5948
|
+
throw await refusalBehindAbort(transaction, error);
|
|
5915
5949
|
}
|
|
5916
5950
|
}
|
|
5917
5951
|
async finishSnapshotFrameImport(input) {
|
|
@@ -5964,10 +5998,10 @@ class IndexedDbBlockStore {
|
|
|
5964
5998
|
this.#tableIdsByName.clear();
|
|
5965
5999
|
this.#uniqueKeyCache = void 0;
|
|
5966
6000
|
this.#manifestCache = void 0;
|
|
6001
|
+
this.#journalCache = void 0;
|
|
5967
6002
|
} catch (error) {
|
|
5968
6003
|
abortIfActive(transaction);
|
|
5969
|
-
await
|
|
5970
|
-
throw error;
|
|
6004
|
+
throw await refusalBehindAbort(transaction, error);
|
|
5971
6005
|
}
|
|
5972
6006
|
}
|
|
5973
6007
|
async cancelSnapshotFrameImport(input) {
|
|
@@ -6002,6 +6036,7 @@ class IndexedDbBlockStore {
|
|
|
6002
6036
|
this.#tableIdsByName.clear();
|
|
6003
6037
|
this.#uniqueKeyCache = void 0;
|
|
6004
6038
|
this.#manifestCache = void 0;
|
|
6039
|
+
this.#journalCache = void 0;
|
|
6005
6040
|
return {
|
|
6006
6041
|
identity: marker.identity,
|
|
6007
6042
|
removedBlockCount: marker.kindItemCounts[SNAPSHOT_FRAME_KINDS.indexOf("block")] ?? 0,
|
|
@@ -6009,8 +6044,7 @@ class IndexedDbBlockStore {
|
|
|
6009
6044
|
};
|
|
6010
6045
|
} catch (error) {
|
|
6011
6046
|
abortIfActive(transaction);
|
|
6012
|
-
await
|
|
6013
|
-
throw error;
|
|
6047
|
+
throw await refusalBehindAbort(transaction, error);
|
|
6014
6048
|
}
|
|
6015
6049
|
}
|
|
6016
6050
|
close() {
|
|
@@ -6020,6 +6054,7 @@ class IndexedDbBlockStore {
|
|
|
6020
6054
|
this.#tableIdsByName.clear();
|
|
6021
6055
|
this.#uniqueKeyCache = void 0;
|
|
6022
6056
|
this.#manifestCache = void 0;
|
|
6057
|
+
this.#journalCache = void 0;
|
|
6023
6058
|
this.#db.close();
|
|
6024
6059
|
}
|
|
6025
6060
|
_residentStateForTests() {
|
|
@@ -6066,8 +6101,7 @@ class IndexedDbBlockStore {
|
|
|
6066
6101
|
return true;
|
|
6067
6102
|
} catch (error) {
|
|
6068
6103
|
abortIfActive(transaction);
|
|
6069
|
-
await
|
|
6070
|
-
throw error;
|
|
6104
|
+
throw await refusalBehindAbort(transaction, error);
|
|
6071
6105
|
}
|
|
6072
6106
|
}
|
|
6073
6107
|
}
|
|
@@ -6083,8 +6117,7 @@ class IndexedDbBlockStore {
|
|
|
6083
6117
|
return;
|
|
6084
6118
|
} catch (error) {
|
|
6085
6119
|
abortIfActive(transaction);
|
|
6086
|
-
await
|
|
6087
|
-
throw error;
|
|
6120
|
+
throw await refusalBehindAbort(transaction, error);
|
|
6088
6121
|
}
|
|
6089
6122
|
}
|
|
6090
6123
|
}
|
|
@@ -6114,8 +6147,7 @@ class IndexedDbBlockStore {
|
|
|
6114
6147
|
await transactionDone(transaction);
|
|
6115
6148
|
} catch (error) {
|
|
6116
6149
|
abortIfActive(transaction);
|
|
6117
|
-
await
|
|
6118
|
-
throw error;
|
|
6150
|
+
throw await refusalBehindAbort(transaction, error);
|
|
6119
6151
|
}
|
|
6120
6152
|
if (cleanup)
|
|
6121
6153
|
await this.#cleanupUniqueKeyBuildArtifacts(buildId);
|
|
@@ -6146,8 +6178,7 @@ class IndexedDbBlockStore {
|
|
|
6146
6178
|
return;
|
|
6147
6179
|
} catch (error) {
|
|
6148
6180
|
abortIfActive(transaction);
|
|
6149
|
-
await
|
|
6150
|
-
throw error;
|
|
6181
|
+
throw await refusalBehindAbort(transaction, error);
|
|
6151
6182
|
}
|
|
6152
6183
|
}
|
|
6153
6184
|
}
|
|
@@ -6282,8 +6313,7 @@ class IndexedDbBlockStore {
|
|
|
6282
6313
|
await transactionDone(transaction);
|
|
6283
6314
|
} catch (error) {
|
|
6284
6315
|
abortIfActive(transaction);
|
|
6285
|
-
await
|
|
6286
|
-
throw error;
|
|
6316
|
+
throw await refusalBehindAbort(transaction, error);
|
|
6287
6317
|
}
|
|
6288
6318
|
}
|
|
6289
6319
|
async #cleanupFtsRetirementPage() {
|
|
@@ -6299,16 +6329,16 @@ class IndexedDbBlockStore {
|
|
|
6299
6329
|
await transactionDone(transaction);
|
|
6300
6330
|
} catch (error) {
|
|
6301
6331
|
abortIfActive(transaction);
|
|
6302
|
-
await
|
|
6303
|
-
throw error;
|
|
6332
|
+
throw await refusalBehindAbort(transaction, error);
|
|
6304
6333
|
}
|
|
6305
6334
|
}
|
|
6306
6335
|
#transaction(stores, mode, options = {}) {
|
|
6307
6336
|
if (this.#closed)
|
|
6308
6337
|
throw new Error("This IndexedDB store connection is closed");
|
|
6309
|
-
const
|
|
6338
|
+
const requested = typeof stores === "string" ? [stores] : stores;
|
|
6339
|
+
const storeList = requested.includes("transactions") && !requested.includes(TRANSACTION_JOURNAL_STORE) ? [...requested, TRANSACTION_JOURNAL_STORE] : requested;
|
|
6340
|
+
const transaction = mode === "readwrite" ? this.#db.transaction(storeList, mode, { durability: this.#durability }) : this.#db.transaction(storeList, mode);
|
|
6310
6341
|
void transactionDone(transaction).catch(() => void 0);
|
|
6311
|
-
const storeList = typeof stores === "string" ? [stores] : stores;
|
|
6312
6342
|
if (mode === "readwrite" && !options.allowSnapshotImport && storeList.includes("catalog")) {
|
|
6313
6343
|
const catalog = transaction.objectStore("catalog");
|
|
6314
6344
|
const marker = catalog.get(SNAPSHOT_FRAME_IMPORT_KEY);
|
|
@@ -6333,9 +6363,8 @@ function assertIndexedDbSchemaMigrationRegistry() {
|
|
|
6333
6363
|
}
|
|
6334
6364
|
}
|
|
6335
6365
|
function createCurrentIndexedDbSchema(database, upgrade) {
|
|
6336
|
-
for (const storeName of
|
|
6366
|
+
for (const storeName of indexedDbStoreNames)
|
|
6337
6367
|
database.createObjectStore(storeName);
|
|
6338
|
-
database.createObjectStore(SNAPSHOT_HEADER_STORE);
|
|
6339
6368
|
for (const [storeName, indexes] of Object.entries(indexedDbIndexSchema)) {
|
|
6340
6369
|
const store = upgrade.objectStore(storeName);
|
|
6341
6370
|
for (const index of indexes) {
|
|
@@ -6348,10 +6377,10 @@ function createCurrentIndexedDbSchema(database, upgrade) {
|
|
|
6348
6377
|
upgrade.objectStore("statistics").add(emptyCatalogResourceLedger(), CATALOG_RESOURCE_LEDGER_KEY);
|
|
6349
6378
|
upgrade.objectStore("statistics").add(emptyRecordResourceLedger(), RECORD_RESOURCE_LEDGER_KEY);
|
|
6350
6379
|
}
|
|
6351
|
-
function applyIndexedDbSchemaMigrations(database, upgrade, oldVersion) {
|
|
6380
|
+
function applyIndexedDbSchemaMigrations(database, upgrade, oldVersion, fail) {
|
|
6352
6381
|
for (const migration of indexedDbSchemaMigrations) {
|
|
6353
6382
|
if (migration.targetVersion > oldVersion)
|
|
6354
|
-
migration.migrate(database, upgrade);
|
|
6383
|
+
migration.migrate(database, upgrade, fail);
|
|
6355
6384
|
}
|
|
6356
6385
|
}
|
|
6357
6386
|
const indexedDbIndexSchema = {
|
|
@@ -6791,7 +6820,7 @@ async function assertTableRemovalAllowed(transaction, tableId, expectedRevision)
|
|
|
6791
6820
|
const segments = await readTableSegmentsForRemoval(transaction.objectStore("segments").index(SEGMENT_TABLE_INDEX), tableId);
|
|
6792
6821
|
const transactionStore = transaction.objectStore("transactions");
|
|
6793
6822
|
await visitObjectStoreSequentially(transactionStore, (candidateValue, key) => {
|
|
6794
|
-
const candidate =
|
|
6823
|
+
const candidate = asStoredTransactionRecord(candidateValue, storageKeyLocation(key));
|
|
6795
6824
|
const pendingTable = candidate.status === "active" ? candidate.pendingTable : void 0;
|
|
6796
6825
|
const foreignKey = (pendingTable?.foreignKeys ?? []).find((constraint) => constraint.parentTable === record.name);
|
|
6797
6826
|
if (foreignKey !== void 0) {
|
|
@@ -6803,11 +6832,10 @@ async function assertTableRemovalAllowed(transaction, tableId, expectedRevision)
|
|
|
6803
6832
|
for (const segment of segments) {
|
|
6804
6833
|
let owner = owners.get(segment.transactionId);
|
|
6805
6834
|
if (owner === void 0) {
|
|
6806
|
-
|
|
6807
|
-
if (
|
|
6835
|
+
owner = await readStoredTransactionRecord(transaction, segment.transactionId);
|
|
6836
|
+
if (owner === void 0) {
|
|
6808
6837
|
throw corruption(`segments/${segment.id}`, `references missing transaction ${segment.transactionId}`);
|
|
6809
6838
|
}
|
|
6810
|
-
owner = asTransactionRecord(ownerValue, segment.transactionId);
|
|
6811
6839
|
owners.set(segment.transactionId, owner);
|
|
6812
6840
|
}
|
|
6813
6841
|
if (owner.status === "active")
|
|
@@ -6960,6 +6988,14 @@ async function ignoreAbort(transaction) {
|
|
|
6960
6988
|
} catch {
|
|
6961
6989
|
}
|
|
6962
6990
|
}
|
|
6991
|
+
async function refusalBehindAbort(transaction, error) {
|
|
6992
|
+
await ignoreAbort(transaction);
|
|
6993
|
+
const refusal = transaction.error;
|
|
6994
|
+
if (refusal !== null && error instanceof DOMException && error.name === "AbortError") {
|
|
6995
|
+
return refusal;
|
|
6996
|
+
}
|
|
6997
|
+
return error;
|
|
6998
|
+
}
|
|
6963
6999
|
function corruption(location, message) {
|
|
6964
7000
|
return new StorageCorruptionError("indexeddb", location, message);
|
|
6965
7001
|
}
|
|
@@ -7099,6 +7135,9 @@ function requiredUniqueStringArray(value, location) {
|
|
|
7099
7135
|
}
|
|
7100
7136
|
return [...value];
|
|
7101
7137
|
}
|
|
7138
|
+
function extendsJournalOrder(current, updated) {
|
|
7139
|
+
return current.length <= updated.length && current.every((id, index) => updated[index] === id);
|
|
7140
|
+
}
|
|
7102
7141
|
function validateArtifactPartition(retained, removed, current) {
|
|
7103
7142
|
if (retained.length + removed.length !== current.length)
|
|
7104
7143
|
return false;
|
|
@@ -7813,7 +7852,7 @@ async function stageSnapshotMetadataFrame(transaction, frame) {
|
|
|
7813
7852
|
await assertCatalogReservationAdmission(transaction, record.pendingTable);
|
|
7814
7853
|
await assertTableForeignKeysInTransaction(catalog, record.pendingTable);
|
|
7815
7854
|
}
|
|
7816
|
-
transaction
|
|
7855
|
+
addTransactionRecord(transaction, record);
|
|
7817
7856
|
return;
|
|
7818
7857
|
}
|
|
7819
7858
|
if (item.kind === "unique-generation") {
|
|
@@ -7900,7 +7939,6 @@ async function readSnapshotGenerationId(catalog, key, label) {
|
|
|
7900
7939
|
async function validateAndPromoteStagedSnapshot(transaction, marker) {
|
|
7901
7940
|
const catalog = transaction.objectStore("catalog");
|
|
7902
7941
|
const segments = transaction.objectStore("segments");
|
|
7903
|
-
const transactions = transaction.objectStore("transactions");
|
|
7904
7942
|
const blocks = transaction.objectStore("blocks");
|
|
7905
7943
|
await visitObjectStoreSequentially(catalog, async (value, key) => {
|
|
7906
7944
|
if (typeof key !== "string" || !key.startsWith(TABLE_ID_PREFIX))
|
|
@@ -7942,10 +7980,7 @@ async function validateAndPromoteStagedSnapshot(transaction, marker) {
|
|
|
7942
7980
|
asSnapshotPostingStagingRecord(await requestResult(catalog.get(snapshotPostingStagingKey(generationId))), generationId);
|
|
7943
7981
|
}
|
|
7944
7982
|
});
|
|
7945
|
-
await
|
|
7946
|
-
if (typeof key !== "string")
|
|
7947
|
-
throw corruption("transactions", "snapshot transaction key is invalid");
|
|
7948
|
-
const record = asTransactionRecord(value, key);
|
|
7983
|
+
await visitTransactionRecords(transaction, (record, key) => {
|
|
7949
7984
|
if (record.status !== "committed" || record.committedVersion === null || record.committedVersion > marker.version || record.pendingBlockIds.length !== 0 || record.pendingSegmentIds.length !== 0) {
|
|
7950
7985
|
throw corruption(`transactions/${key}`, "snapshot transaction is not canonical committed history");
|
|
7951
7986
|
}
|
|
@@ -7959,10 +7994,9 @@ async function validateAndPromoteStagedSnapshot(transaction, marker) {
|
|
|
7959
7994
|
if (await requestResult(catalog.getKey(`${TABLE_ID_PREFIX}${segment.tableId}`)) === void 0) {
|
|
7960
7995
|
throw corruption(`segments/${key}`, "snapshot segment table is missing");
|
|
7961
7996
|
}
|
|
7962
|
-
|
|
7963
|
-
if (ownerValue === void 0)
|
|
7997
|
+
if (await readTransactionRecord(transaction, segment.transactionId) === void 0) {
|
|
7964
7998
|
throw corruption(`segments/${key}`, "snapshot segment owner is missing");
|
|
7965
|
-
|
|
7999
|
+
}
|
|
7966
8000
|
for (const blockId of segmentBlockIds(segment)) {
|
|
7967
8001
|
const provenance = asManifestBlockRecord(await requestResult(catalog.get(manifestBlockKey(blockId))), blockId);
|
|
7968
8002
|
if (!manifestBlockVisibleAt(provenance, marker.version)) {
|
|
@@ -8242,10 +8276,9 @@ async function writeSnapshotMetadataFramesInTransaction(input) {
|
|
|
8242
8276
|
});
|
|
8243
8277
|
await visitObjectStoreSequentially(input.transaction.objectStore("segments"), async (value) => {
|
|
8244
8278
|
const record = asSegmentRecord(value);
|
|
8245
|
-
const
|
|
8246
|
-
if (
|
|
8279
|
+
const owner = await readStoredTransactionRecord(input.transaction, record.transactionId);
|
|
8280
|
+
if (owner === void 0)
|
|
8247
8281
|
return;
|
|
8248
|
-
const owner = asTransactionRecord(ownerValue, record.transactionId);
|
|
8249
8282
|
if (owner.status !== "committed" || owner.committedVersion === null || owner.committedVersion > input.version) {
|
|
8250
8283
|
return;
|
|
8251
8284
|
}
|
|
@@ -8261,14 +8294,14 @@ async function writeSnapshotMetadataFramesInTransaction(input) {
|
|
|
8261
8294
|
await visitObjectStoreSequentially(input.transaction.objectStore("transactions"), (value, key) => {
|
|
8262
8295
|
if (typeof key !== "string")
|
|
8263
8296
|
throw corruption("transactions", "record key is invalid");
|
|
8264
|
-
const record =
|
|
8297
|
+
const record = asStoredTransactionRecord(value, key);
|
|
8265
8298
|
if (record.status !== "committed" || record.committedVersion === null || record.committedVersion > input.version) {
|
|
8266
8299
|
return;
|
|
8267
8300
|
}
|
|
8268
8301
|
input.observeRetainedItems(1);
|
|
8269
8302
|
writeItem("transaction-page", {
|
|
8270
8303
|
kind: "transaction",
|
|
8271
|
-
record:
|
|
8304
|
+
record: transactionRecordWithJournal(record, [], [])
|
|
8272
8305
|
});
|
|
8273
8306
|
});
|
|
8274
8307
|
await visitTables(async (table) => {
|
|
@@ -8510,34 +8543,74 @@ async function findReadableManifestBlock(transaction, ids, excludedVersions = /*
|
|
|
8510
8543
|
});
|
|
8511
8544
|
return found;
|
|
8512
8545
|
}
|
|
8513
|
-
function
|
|
8546
|
+
function journalTailMatches(cached, tail) {
|
|
8547
|
+
if (tail === void 0)
|
|
8548
|
+
return cached.blockIds.length === 0 && cached.segmentIds.length === 0;
|
|
8549
|
+
const endsWith = (ids, suffix) => suffix.every((id, index) => ids[ids.length - suffix.length + index] === id);
|
|
8550
|
+
return endsWith(cached.blockIds, tail.blockIds) && endsWith(cached.segmentIds, tail.segmentIds);
|
|
8551
|
+
}
|
|
8552
|
+
const TRANSACTION_RECORD_FIELDS = [
|
|
8553
|
+
"id",
|
|
8554
|
+
"ownerId",
|
|
8555
|
+
"expiresAt",
|
|
8556
|
+
"snapshotVersion",
|
|
8557
|
+
"status",
|
|
8558
|
+
"revision",
|
|
8559
|
+
"startedAt",
|
|
8560
|
+
"updatedAt",
|
|
8561
|
+
"committedVersion",
|
|
8562
|
+
"schemaEpochGuard",
|
|
8563
|
+
"pendingTable",
|
|
8564
|
+
"pendingTableNextRowId",
|
|
8565
|
+
"catalogEpochGuard"
|
|
8566
|
+
];
|
|
8567
|
+
function transactionJournalChunkKey(transactionId, chunkIndex) {
|
|
8568
|
+
return [transactionId, chunkIndex];
|
|
8569
|
+
}
|
|
8570
|
+
function transactionJournalEntryCount(blockCount, segmentCount) {
|
|
8571
|
+
const total = blockCount + segmentCount;
|
|
8572
|
+
if (!Number.isSafeInteger(total))
|
|
8573
|
+
throw new RangeError("Transaction journal is too large");
|
|
8574
|
+
return total;
|
|
8575
|
+
}
|
|
8576
|
+
function transactionJournalChunkCount(blockCount, segmentCount) {
|
|
8577
|
+
return Math.ceil(transactionJournalEntryCount(blockCount, segmentCount) / TRANSACTION_JOURNAL_CHUNK_ENTRIES);
|
|
8578
|
+
}
|
|
8579
|
+
function journalChunkCountOf(record) {
|
|
8580
|
+
return transactionJournalChunkCount(record.pendingBlockIds.length, record.pendingSegmentIds.length);
|
|
8581
|
+
}
|
|
8582
|
+
function packTransactionJournal(blockIds, segmentIds) {
|
|
8583
|
+
const chunks = [];
|
|
8584
|
+
let blockOffset = 0;
|
|
8585
|
+
let segmentOffset = 0;
|
|
8586
|
+
while (blockOffset < blockIds.length || segmentOffset < segmentIds.length) {
|
|
8587
|
+
const blocks = blockIds.slice(blockOffset, blockOffset + TRANSACTION_JOURNAL_CHUNK_ENTRIES);
|
|
8588
|
+
blockOffset += blocks.length;
|
|
8589
|
+
const segments = segmentIds.slice(segmentOffset, segmentOffset + TRANSACTION_JOURNAL_CHUNK_ENTRIES - blocks.length);
|
|
8590
|
+
segmentOffset += segments.length;
|
|
8591
|
+
chunks.push({ blockIds: blocks, segmentIds: segments });
|
|
8592
|
+
}
|
|
8593
|
+
return chunks;
|
|
8594
|
+
}
|
|
8595
|
+
function asTransactionJournalChunk(value, location) {
|
|
8514
8596
|
if (!isRecord(value))
|
|
8515
|
-
throw corruption(
|
|
8516
|
-
assertKnownFields(value, [
|
|
8517
|
-
|
|
8518
|
-
|
|
8519
|
-
|
|
8520
|
-
|
|
8521
|
-
|
|
8522
|
-
|
|
8523
|
-
|
|
8524
|
-
|
|
8525
|
-
|
|
8526
|
-
"updatedAt",
|
|
8527
|
-
"committedVersion",
|
|
8528
|
-
"schemaEpochGuard",
|
|
8529
|
-
"pendingTable",
|
|
8530
|
-
"pendingTableNextRowId",
|
|
8531
|
-
"catalogEpochGuard"
|
|
8532
|
-
], "transactions");
|
|
8597
|
+
throw corruption(location, "journal chunk is not an object");
|
|
8598
|
+
assertKnownFields(value, ["blockIds", "segmentIds"], location);
|
|
8599
|
+
const blockIds = requiredUniqueStringArray(value.blockIds, `${location}/blockIds`);
|
|
8600
|
+
const segmentIds = requiredUniqueStringArray(value.segmentIds, `${location}/segmentIds`);
|
|
8601
|
+
const entries = blockIds.length + segmentIds.length;
|
|
8602
|
+
if (entries === 0 || entries > TRANSACTION_JOURNAL_CHUNK_ENTRIES) {
|
|
8603
|
+
throw corruption(location, `journal chunk holds ${String(entries)} ids`);
|
|
8604
|
+
}
|
|
8605
|
+
return { blockIds, segmentIds };
|
|
8606
|
+
}
|
|
8607
|
+
function asTransactionRecordBase(value, expectedId) {
|
|
8533
8608
|
const id = nonEmptyStoredString(value.id, "transactions/id");
|
|
8534
8609
|
if (expectedId !== void 0 && id !== expectedId) {
|
|
8535
8610
|
throw corruption(`transactions/${expectedId}`, `record declares id ${id}`);
|
|
8536
8611
|
}
|
|
8537
8612
|
const snapshotVersion = asNullableStoredVersion(value.snapshotVersion, `transactions/${id}/snapshotVersion`);
|
|
8538
8613
|
const committedVersion = asNullableStoredVersion(value.committedVersion, `transactions/${id}/committedVersion`);
|
|
8539
|
-
const pendingBlockIds = requiredUniqueStringArray(value.pendingBlockIds, `transactions/${id}/pendingBlockIds`);
|
|
8540
|
-
const pendingSegmentIds = requiredUniqueStringArray(value.pendingSegmentIds, `transactions/${id}/pendingSegmentIds`);
|
|
8541
8614
|
if (!["active", "committed", "aborted"].includes(value.status)) {
|
|
8542
8615
|
throw corruption(`transactions/${id}/status`, "status is invalid");
|
|
8543
8616
|
}
|
|
@@ -8568,8 +8641,6 @@ function asTransactionRecord(value, expectedId) {
|
|
|
8568
8641
|
ownerId,
|
|
8569
8642
|
expiresAt,
|
|
8570
8643
|
snapshotVersion,
|
|
8571
|
-
pendingBlockIds,
|
|
8572
|
-
pendingSegmentIds,
|
|
8573
8644
|
status,
|
|
8574
8645
|
revision,
|
|
8575
8646
|
startedAt,
|
|
@@ -8584,6 +8655,185 @@ function asTransactionRecord(value, expectedId) {
|
|
|
8584
8655
|
}
|
|
8585
8656
|
return { ...base, pendingTable, pendingTableNextRowId, catalogEpochGuard };
|
|
8586
8657
|
}
|
|
8658
|
+
function asTransactionRecord(value, expectedId) {
|
|
8659
|
+
if (!isRecord(value))
|
|
8660
|
+
throw corruption("transactions", "record is not an object");
|
|
8661
|
+
assertKnownFields(value, [...TRANSACTION_RECORD_FIELDS, "pendingBlockIds", "pendingSegmentIds"], "transactions");
|
|
8662
|
+
const base = asTransactionRecordBase(value, expectedId);
|
|
8663
|
+
const pendingBlockIds = requiredUniqueStringArray(value.pendingBlockIds, `transactions/${base.id}/pendingBlockIds`);
|
|
8664
|
+
const pendingSegmentIds = requiredUniqueStringArray(value.pendingSegmentIds, `transactions/${base.id}/pendingSegmentIds`);
|
|
8665
|
+
transactionJournalEntryCount(pendingBlockIds.length, pendingSegmentIds.length);
|
|
8666
|
+
return { ...base, pendingBlockIds, pendingSegmentIds };
|
|
8667
|
+
}
|
|
8668
|
+
function asStoredTransactionRecord(value, expectedId) {
|
|
8669
|
+
if (!isRecord(value))
|
|
8670
|
+
throw corruption("transactions", "record is not an object");
|
|
8671
|
+
assertKnownFields(value, [...TRANSACTION_RECORD_FIELDS, "journalChunkCount", "pendingBlockCount", "pendingSegmentCount"], "transactions");
|
|
8672
|
+
const base = asTransactionRecordBase(value, expectedId);
|
|
8673
|
+
const pendingBlockCount = nonNegativeStoredInteger(value.pendingBlockCount, `transactions/${base.id}/pendingBlockCount`);
|
|
8674
|
+
const pendingSegmentCount = nonNegativeStoredInteger(value.pendingSegmentCount, `transactions/${base.id}/pendingSegmentCount`);
|
|
8675
|
+
const journalChunkCount = nonNegativeStoredInteger(value.journalChunkCount, `transactions/${base.id}/journalChunkCount`);
|
|
8676
|
+
let expectedChunkCount;
|
|
8677
|
+
try {
|
|
8678
|
+
expectedChunkCount = transactionJournalChunkCount(pendingBlockCount, pendingSegmentCount);
|
|
8679
|
+
} catch {
|
|
8680
|
+
throw corruption(`transactions/${base.id}`, "journal counts are unsafe");
|
|
8681
|
+
}
|
|
8682
|
+
if (journalChunkCount !== expectedChunkCount) {
|
|
8683
|
+
throw corruption(`transactions/${base.id}/journalChunkCount`, `chunk count ${String(journalChunkCount)} disagrees with ${String(pendingBlockCount)} blocks and ${String(pendingSegmentCount)} segments`);
|
|
8684
|
+
}
|
|
8685
|
+
return { ...base, journalChunkCount, pendingBlockCount, pendingSegmentCount };
|
|
8686
|
+
}
|
|
8687
|
+
function storedTransactionRecord(record) {
|
|
8688
|
+
const { pendingBlockIds, pendingSegmentIds, ...header } = record;
|
|
8689
|
+
return {
|
|
8690
|
+
...header,
|
|
8691
|
+
journalChunkCount: journalChunkCountOf(record),
|
|
8692
|
+
pendingBlockCount: pendingBlockIds.length,
|
|
8693
|
+
pendingSegmentCount: pendingSegmentIds.length
|
|
8694
|
+
};
|
|
8695
|
+
}
|
|
8696
|
+
function transactionRecordWithJournal(stored, pendingBlockIds, pendingSegmentIds) {
|
|
8697
|
+
const { journalChunkCount: _chunkCount, pendingBlockCount: _blockCount, pendingSegmentCount: _segmentCount, ...header } = stored;
|
|
8698
|
+
void _chunkCount;
|
|
8699
|
+
void _blockCount;
|
|
8700
|
+
void _segmentCount;
|
|
8701
|
+
return {
|
|
8702
|
+
...header,
|
|
8703
|
+
pendingBlockIds: [...pendingBlockIds],
|
|
8704
|
+
pendingSegmentIds: [...pendingSegmentIds]
|
|
8705
|
+
};
|
|
8706
|
+
}
|
|
8707
|
+
function transactionRecordFromJournal(stored, chunks) {
|
|
8708
|
+
const location = `transactions/${stored.id}`;
|
|
8709
|
+
if (chunks.length !== stored.journalChunkCount) {
|
|
8710
|
+
throw corruption(location, "journal chunk count disagrees with its record");
|
|
8711
|
+
}
|
|
8712
|
+
const pendingBlockIds = [];
|
|
8713
|
+
const pendingSegmentIds = [];
|
|
8714
|
+
for (const [index, chunk] of chunks.entries()) {
|
|
8715
|
+
const entries = chunk.blockIds.length + chunk.segmentIds.length;
|
|
8716
|
+
if (index < chunks.length - 1 && entries !== TRANSACTION_JOURNAL_CHUNK_ENTRIES) {
|
|
8717
|
+
throw corruption(`${location}/journal/${String(index)}`, "journal chunk is not packed");
|
|
8718
|
+
}
|
|
8719
|
+
for (const id of chunk.blockIds)
|
|
8720
|
+
pendingBlockIds.push(id);
|
|
8721
|
+
for (const id of chunk.segmentIds)
|
|
8722
|
+
pendingSegmentIds.push(id);
|
|
8723
|
+
}
|
|
8724
|
+
if (pendingBlockIds.length !== stored.pendingBlockCount || pendingSegmentIds.length !== stored.pendingSegmentCount) {
|
|
8725
|
+
throw corruption(location, "journal counts disagree with its chunks");
|
|
8726
|
+
}
|
|
8727
|
+
if (new Set(pendingBlockIds).size !== pendingBlockIds.length || new Set(pendingSegmentIds).size !== pendingSegmentIds.length) {
|
|
8728
|
+
throw corruption(location, "journal repeats an id");
|
|
8729
|
+
}
|
|
8730
|
+
return transactionRecordWithJournal(stored, pendingBlockIds, pendingSegmentIds);
|
|
8731
|
+
}
|
|
8732
|
+
async function readTransactionJournalChunk(journalStore, transactionId, chunkIndex) {
|
|
8733
|
+
const value = await requestResult(journalStore.get(transactionJournalChunkKey(transactionId, chunkIndex)));
|
|
8734
|
+
return value === void 0 ? void 0 : asTransactionJournalChunk(value, `transactions/${transactionId}/journal/${String(chunkIndex)}`);
|
|
8735
|
+
}
|
|
8736
|
+
async function readTransactionJournalChunks(transaction, stored) {
|
|
8737
|
+
const journalStore = transaction.objectStore(TRANSACTION_JOURNAL_STORE);
|
|
8738
|
+
const values = await Promise.all(Array.from({ length: stored.journalChunkCount + 1 }, (_, index) => readTransactionJournalChunk(journalStore, stored.id, index)));
|
|
8739
|
+
const extra = values.pop();
|
|
8740
|
+
if (extra !== void 0) {
|
|
8741
|
+
throw corruption(`transactions/${stored.id}`, "journal has more chunks than its record declares");
|
|
8742
|
+
}
|
|
8743
|
+
return values.map((chunk, index) => {
|
|
8744
|
+
if (chunk === void 0) {
|
|
8745
|
+
throw corruption(`transactions/${stored.id}/journal/${String(index)}`, "chunk is missing");
|
|
8746
|
+
}
|
|
8747
|
+
return chunk;
|
|
8748
|
+
});
|
|
8749
|
+
}
|
|
8750
|
+
async function assembleTransactionRecord(transaction, stored) {
|
|
8751
|
+
return transactionRecordFromJournal(stored, await readTransactionJournalChunks(transaction, stored));
|
|
8752
|
+
}
|
|
8753
|
+
async function readStoredTransactionRecord(transaction, id) {
|
|
8754
|
+
const value = await requestResult(transaction.objectStore("transactions").get(id));
|
|
8755
|
+
return value === void 0 ? void 0 : asStoredTransactionRecord(value, id);
|
|
8756
|
+
}
|
|
8757
|
+
async function readTransactionRecord(transaction, id) {
|
|
8758
|
+
const stored = await readStoredTransactionRecord(transaction, id);
|
|
8759
|
+
return stored === void 0 ? void 0 : assembleTransactionRecord(transaction, stored);
|
|
8760
|
+
}
|
|
8761
|
+
async function readTransactionRecords(transaction, ids) {
|
|
8762
|
+
const stored = await Promise.all(ids.map((id) => readStoredTransactionRecord(transaction, id)));
|
|
8763
|
+
return Promise.all(stored.map((record) => record === void 0 ? Promise.resolve(void 0) : assembleTransactionRecord(transaction, record)));
|
|
8764
|
+
}
|
|
8765
|
+
function visitTransactionRecords(transaction, visit) {
|
|
8766
|
+
return visitObjectStoreSequentially(transaction.objectStore("transactions"), async (value, key) => {
|
|
8767
|
+
if (typeof key !== "string")
|
|
8768
|
+
throw corruption("transactions", "record key is invalid");
|
|
8769
|
+
const record = await assembleTransactionRecord(transaction, asStoredTransactionRecord(value, key));
|
|
8770
|
+
return visit(record, key);
|
|
8771
|
+
});
|
|
8772
|
+
}
|
|
8773
|
+
function visitActiveTransactionRecords(transaction, visit) {
|
|
8774
|
+
return visitIndexPartitionSequentially(transaction.objectStore("transactions").index(TRANSACTION_STATUS_INDEX), "active", async (value, key) => {
|
|
8775
|
+
if (typeof key !== "string")
|
|
8776
|
+
throw corruption("transactions", "record key is invalid");
|
|
8777
|
+
const record = await assembleTransactionRecord(transaction, asStoredTransactionRecord(value, key));
|
|
8778
|
+
return visit(record, key);
|
|
8779
|
+
});
|
|
8780
|
+
}
|
|
8781
|
+
function putStoredTransactionRecord(transaction, stored) {
|
|
8782
|
+
transaction.objectStore("transactions").put(stored, stored.id);
|
|
8783
|
+
}
|
|
8784
|
+
function putTransactionRecord(transaction, record, replacedChunkCount) {
|
|
8785
|
+
const journalStore = transaction.objectStore(TRANSACTION_JOURNAL_STORE);
|
|
8786
|
+
const chunks = packTransactionJournal(record.pendingBlockIds, record.pendingSegmentIds);
|
|
8787
|
+
for (let index = chunks.length; index < replacedChunkCount; index += 1) {
|
|
8788
|
+
journalStore.delete(transactionJournalChunkKey(record.id, index));
|
|
8789
|
+
}
|
|
8790
|
+
chunks.forEach((chunk, index) => {
|
|
8791
|
+
journalStore.put(chunk, transactionJournalChunkKey(record.id, index));
|
|
8792
|
+
});
|
|
8793
|
+
putStoredTransactionRecord(transaction, storedTransactionRecord(record));
|
|
8794
|
+
}
|
|
8795
|
+
function addTransactionRecord(transaction, record) {
|
|
8796
|
+
transaction.objectStore("transactions").add(storedTransactionRecord(record), record.id);
|
|
8797
|
+
const journalStore = transaction.objectStore(TRANSACTION_JOURNAL_STORE);
|
|
8798
|
+
packTransactionJournal(record.pendingBlockIds, record.pendingSegmentIds).forEach((chunk, index) => {
|
|
8799
|
+
journalStore.put(chunk, transactionJournalChunkKey(record.id, index));
|
|
8800
|
+
});
|
|
8801
|
+
}
|
|
8802
|
+
function deleteTransactionRecord(transaction, stored) {
|
|
8803
|
+
const journalStore = transaction.objectStore(TRANSACTION_JOURNAL_STORE);
|
|
8804
|
+
for (let index = 0; index < stored.journalChunkCount; index += 1) {
|
|
8805
|
+
journalStore.delete(transactionJournalChunkKey(stored.id, index));
|
|
8806
|
+
}
|
|
8807
|
+
transaction.objectStore("transactions").delete(stored.id);
|
|
8808
|
+
}
|
|
8809
|
+
async function readTransactionJournalTail(transaction, stored) {
|
|
8810
|
+
const lastIndex = stored.journalChunkCount - 1;
|
|
8811
|
+
if (lastIndex < 0)
|
|
8812
|
+
return void 0;
|
|
8813
|
+
const tail = await readTransactionJournalChunk(transaction.objectStore(TRANSACTION_JOURNAL_STORE), stored.id, lastIndex);
|
|
8814
|
+
if (tail === void 0) {
|
|
8815
|
+
throw corruption(`transactions/${stored.id}/journal/${String(lastIndex)}`, "chunk is missing");
|
|
8816
|
+
}
|
|
8817
|
+
return tail;
|
|
8818
|
+
}
|
|
8819
|
+
function appendTransactionJournalChunks(transaction, stored, tail, addedBlockIds, addedSegmentIds) {
|
|
8820
|
+
const journalStore = transaction.objectStore(TRANSACTION_JOURNAL_STORE);
|
|
8821
|
+
const lastIndex = stored.journalChunkCount - 1;
|
|
8822
|
+
const pendingBlockCount = stored.pendingBlockCount + addedBlockIds.length;
|
|
8823
|
+
const pendingSegmentCount = stored.pendingSegmentCount + addedSegmentIds.length;
|
|
8824
|
+
const journalChunkCount = transactionJournalChunkCount(pendingBlockCount, pendingSegmentCount);
|
|
8825
|
+
const tailEntries = tail === void 0 ? 0 : tail.blockIds.length + tail.segmentIds.length;
|
|
8826
|
+
const topUp = tail !== void 0 && tailEntries < TRANSACTION_JOURNAL_CHUNK_ENTRIES;
|
|
8827
|
+
const firstIndex = topUp ? lastIndex : lastIndex + 1;
|
|
8828
|
+
const chunks = packTransactionJournal([...topUp ? tail.blockIds : [], ...addedBlockIds], [...topUp ? tail.segmentIds : [], ...addedSegmentIds]);
|
|
8829
|
+
if (firstIndex + chunks.length !== journalChunkCount) {
|
|
8830
|
+
throw corruption(`transactions/${stored.id}`, "journal append does not reach the chunk count");
|
|
8831
|
+
}
|
|
8832
|
+
chunks.forEach((chunk, offset) => {
|
|
8833
|
+
journalStore.put(chunk, transactionJournalChunkKey(stored.id, firstIndex + offset));
|
|
8834
|
+
});
|
|
8835
|
+
return { ...stored, journalChunkCount, pendingBlockCount, pendingSegmentCount };
|
|
8836
|
+
}
|
|
8587
8837
|
function asTableRecord(value, location = "catalog/table") {
|
|
8588
8838
|
if (!isRecord(value))
|
|
8589
8839
|
throw corruption(location, "table record is not an object");
|
|
@@ -8625,8 +8875,12 @@ function asTableRecord(value, location = "catalog/table") {
|
|
|
8625
8875
|
"defaultValue",
|
|
8626
8876
|
"backfill",
|
|
8627
8877
|
"enumValues",
|
|
8628
|
-
"hidden"
|
|
8878
|
+
"hidden",
|
|
8879
|
+
"generatedValue"
|
|
8629
8880
|
], `${location}/columns/${String(index)}`);
|
|
8881
|
+
if (isRecord(column.generatedValue)) {
|
|
8882
|
+
assertKnownFields(column.generatedValue, ["kind", "sql"], `${location}/columns/${String(index)}/generatedValue`);
|
|
8883
|
+
}
|
|
8630
8884
|
if (isRecord(column.sqlDomain)) {
|
|
8631
8885
|
const domainFields = column.sqlDomain.kind === "numeric" ? ["kind", "precision", "scale"] : column.sqlDomain.kind === "array" ? ["kind", "element"] : column.sqlDomain.kind === "enum" ? ["kind", "name", "values"] : ["kind"];
|
|
8632
8886
|
assertKnownFields(column.sqlDomain, domainFields, `${location}/columns/${String(index)}/sqlDomain`);
|
|
@@ -8951,8 +9205,7 @@ function asLeaseRecord(value, expectedId) {
|
|
|
8951
9205
|
};
|
|
8952
9206
|
}
|
|
8953
9207
|
async function readActiveTransactionRecord(transaction, id, expectedRevision) {
|
|
8954
|
-
const
|
|
8955
|
-
const record = value === void 0 ? void 0 : asTransactionRecord(value);
|
|
9208
|
+
const record = await readTransactionRecord(transaction, id);
|
|
8956
9209
|
if (record?.revision !== expectedRevision || record.status !== "active") {
|
|
8957
9210
|
throw new TransactionRecordConflictError(id, expectedRevision, record?.revision ?? null);
|
|
8958
9211
|
}
|
|
@@ -9012,11 +9265,9 @@ async function assertCompactionJobReferences(transaction, job) {
|
|
|
9012
9265
|
}
|
|
9013
9266
|
}
|
|
9014
9267
|
if (job.transactionId !== null) {
|
|
9015
|
-
|
|
9016
|
-
if (value === void 0) {
|
|
9268
|
+
if (await readTransactionRecord(transaction, job.transactionId) === void 0) {
|
|
9017
9269
|
throw new Error(`Compaction job ${job.id} references missing transaction: ${job.transactionId}`);
|
|
9018
9270
|
}
|
|
9019
|
-
asTransactionRecord(value, job.transactionId);
|
|
9020
9271
|
}
|
|
9021
9272
|
if (job.state === "ready" || job.state === "published") {
|
|
9022
9273
|
for (const outputId of compactionOutputSegmentIds(job)) {
|
|
@@ -9146,7 +9397,7 @@ async function assertCompactionOutputSegmentsInTransaction(transaction, committi
|
|
|
9146
9397
|
}));
|
|
9147
9398
|
assertCompactionOutputProvenance(job, table, committingTransaction, sourceSegments, pendingSegments);
|
|
9148
9399
|
}
|
|
9149
|
-
async function assertLevelZeroSegmentLimits(transaction, limits, pendingSegments,
|
|
9400
|
+
async function assertLevelZeroSegmentLimits(transaction, limits, pendingSegments, version, pendingTable) {
|
|
9150
9401
|
const pendingTables = new Set(pendingSegments.filter((segment) => segment.level === 0).map((segment) => segment.tableId));
|
|
9151
9402
|
if (limits.length !== pendingTables.size) {
|
|
9152
9403
|
throw new TypeError("Level-zero segment limits must exactly cover pending level-zero tables");
|
|
@@ -9171,7 +9422,7 @@ async function assertLevelZeroSegmentLimits(transaction, limits, pendingSegments
|
|
|
9171
9422
|
if (table === void 0) {
|
|
9172
9423
|
throw new Error(`Level-zero segment limit references missing table: ${entry.tableId}`);
|
|
9173
9424
|
}
|
|
9174
|
-
const existing = await countVisibleLevelZeroSegments(segmentIndex, transaction.objectStore("transactions"), entry.tableId
|
|
9425
|
+
const existing = await countVisibleLevelZeroSegments(segmentIndex, transaction.objectStore("transactions"), catalog, version, entry.tableId);
|
|
9175
9426
|
const added = pendingSegments.reduce((count2, segment) => count2 + (segment.tableId === entry.tableId && segment.level === 0 ? 1 : 0), 0);
|
|
9176
9427
|
const count = existing + added;
|
|
9177
9428
|
if (count > entry.limit) {
|
|
@@ -9179,66 +9430,54 @@ async function assertLevelZeroSegmentLimits(transaction, limits, pendingSegments
|
|
|
9179
9430
|
}
|
|
9180
9431
|
}
|
|
9181
9432
|
}
|
|
9182
|
-
function countVisibleLevelZeroSegments(index, transactions,
|
|
9183
|
-
|
|
9184
|
-
const
|
|
9185
|
-
let count = 0;
|
|
9433
|
+
async function countVisibleLevelZeroSegments(index, transactions, catalog, version, tableId) {
|
|
9434
|
+
const candidates = await new Promise((resolve, reject) => {
|
|
9435
|
+
const found = [];
|
|
9186
9436
|
const request = index.openCursor(tableId);
|
|
9187
9437
|
request.onerror = () => reject(request.error ?? new Error("IndexedDB segment cursor failed"));
|
|
9188
9438
|
request.onsuccess = () => {
|
|
9189
9439
|
const cursor = request.result;
|
|
9190
9440
|
if (cursor === null) {
|
|
9191
|
-
resolve(
|
|
9441
|
+
resolve(found);
|
|
9192
9442
|
return;
|
|
9193
9443
|
}
|
|
9194
|
-
let segment;
|
|
9195
9444
|
try {
|
|
9196
|
-
segment = asSegmentRecord(cursor.value);
|
|
9445
|
+
const segment = asSegmentRecord(cursor.value);
|
|
9197
9446
|
if (segment.id !== cursor.primaryKey || segment.tableId !== tableId) {
|
|
9198
9447
|
throw corruption(`segments/${segment.id}`, "table index does not match its record");
|
|
9199
9448
|
}
|
|
9200
|
-
if (segment.level
|
|
9201
|
-
|
|
9202
|
-
|
|
9203
|
-
}
|
|
9204
|
-
const blockIds = segmentBlockIds(segment);
|
|
9205
|
-
const visibleBlockCount = blockIds.filter((id) => currentBlockIds.has(id)).length;
|
|
9206
|
-
if (visibleBlockCount > 0 && visibleBlockCount < blockIds.length) {
|
|
9207
|
-
throw corruption(`segments/${segment.id}`, "only part of the segment is manifest-live");
|
|
9208
|
-
}
|
|
9209
|
-
if (blockIds.length > 0 && visibleBlockCount === 0) {
|
|
9210
|
-
cursor.continue();
|
|
9211
|
-
return;
|
|
9212
|
-
}
|
|
9449
|
+
if (segment.level === 0)
|
|
9450
|
+
found.push(segment);
|
|
9451
|
+
cursor.continue();
|
|
9213
9452
|
} catch (error) {
|
|
9214
9453
|
reject(error instanceof Error ? error : new Error(String(error)));
|
|
9215
|
-
return;
|
|
9216
|
-
}
|
|
9217
|
-
const knownStatus = statuses.get(segment.transactionId);
|
|
9218
|
-
if (knownStatus !== void 0) {
|
|
9219
|
-
if (knownStatus === "committed")
|
|
9220
|
-
count += 1;
|
|
9221
|
-
cursor.continue();
|
|
9222
|
-
return;
|
|
9223
9454
|
}
|
|
9224
|
-
const ownerRequest = transactions.get(segment.transactionId);
|
|
9225
|
-
ownerRequest.onerror = () => reject(ownerRequest.error ?? new Error("IndexedDB transaction lookup failed"));
|
|
9226
|
-
ownerRequest.onsuccess = () => {
|
|
9227
|
-
try {
|
|
9228
|
-
if (ownerRequest.result === void 0) {
|
|
9229
|
-
throw corruption(`segments/${segment.id}`, `owning transaction ${segment.transactionId} is missing`);
|
|
9230
|
-
}
|
|
9231
|
-
const owner = asTransactionRecord(ownerRequest.result, segment.transactionId);
|
|
9232
|
-
statuses.set(owner.id, owner.status);
|
|
9233
|
-
if (owner.status === "committed")
|
|
9234
|
-
count += 1;
|
|
9235
|
-
cursor.continue();
|
|
9236
|
-
} catch (error) {
|
|
9237
|
-
reject(error instanceof Error ? error : new Error(String(error)));
|
|
9238
|
-
}
|
|
9239
|
-
};
|
|
9240
9455
|
};
|
|
9241
9456
|
});
|
|
9457
|
+
const memberships = await Promise.all(candidates.map((segment) => manifestBlockMembershipInTransaction(catalog, version, segmentBlockIds(segment))));
|
|
9458
|
+
const statuses = /* @__PURE__ */ new Map();
|
|
9459
|
+
let count = 0;
|
|
9460
|
+
for (const [position, segment] of candidates.entries()) {
|
|
9461
|
+
const membership = memberships[position] ?? [];
|
|
9462
|
+
const visibleBlockCount = membership.filter((present) => present).length;
|
|
9463
|
+
if (visibleBlockCount > 0 && visibleBlockCount < membership.length) {
|
|
9464
|
+
throw corruption(`segments/${segment.id}`, "only part of the segment is manifest-live");
|
|
9465
|
+
}
|
|
9466
|
+
if (membership.length > 0 && visibleBlockCount === 0)
|
|
9467
|
+
continue;
|
|
9468
|
+
let status = statuses.get(segment.transactionId);
|
|
9469
|
+
if (status === void 0) {
|
|
9470
|
+
const owner = await requestResult(transactions.get(segment.transactionId));
|
|
9471
|
+
if (owner === void 0) {
|
|
9472
|
+
throw corruption(`segments/${segment.id}`, `owning transaction ${segment.transactionId} is missing`);
|
|
9473
|
+
}
|
|
9474
|
+
status = asStoredTransactionRecord(owner, segment.transactionId).status;
|
|
9475
|
+
statuses.set(segment.transactionId, status);
|
|
9476
|
+
}
|
|
9477
|
+
if (status === "committed")
|
|
9478
|
+
count += 1;
|
|
9479
|
+
}
|
|
9480
|
+
return count;
|
|
9242
9481
|
}
|
|
9243
9482
|
async function compactionJobRemovalPreservesProvenance(transaction, removedJob) {
|
|
9244
9483
|
const rootedBlocks = /* @__PURE__ */ new Set();
|
|
@@ -9267,10 +9506,7 @@ async function compactionJobRemovalPreservesProvenance(transaction, removedJob)
|
|
|
9267
9506
|
for (const blockId of segmentBlockIds(segment))
|
|
9268
9507
|
rootedBlocks.add(blockId);
|
|
9269
9508
|
});
|
|
9270
|
-
await
|
|
9271
|
-
if (typeof key !== "string")
|
|
9272
|
-
throw corruption("transactions", "record key is invalid");
|
|
9273
|
-
const record = asTransactionRecord(value, key);
|
|
9509
|
+
await visitTransactionRecords(transaction, (record) => {
|
|
9274
9510
|
for (const blockId of record.pendingBlockIds)
|
|
9275
9511
|
rootedBlocks.add(blockId);
|
|
9276
9512
|
for (const segmentId of record.pendingSegmentIds)
|
|
@@ -9320,7 +9556,7 @@ async function garbageCollectionPinsInTransaction(transaction, currentVersion, l
|
|
|
9320
9556
|
};
|
|
9321
9557
|
pin(currentVersion);
|
|
9322
9558
|
await visitObjectStoreReadBatches(transaction.objectStore("transactions"), (value) => {
|
|
9323
|
-
const record =
|
|
9559
|
+
const record = asStoredTransactionRecord(value);
|
|
9324
9560
|
if (record.status === "active") {
|
|
9325
9561
|
pin(record.snapshotVersion);
|
|
9326
9562
|
}
|
|
@@ -9341,10 +9577,9 @@ async function garbageCollectionPinsInTransaction(transaction, currentVersion, l
|
|
|
9341
9577
|
pin(job.sourceManifestVersion);
|
|
9342
9578
|
if (job.transactionId === null)
|
|
9343
9579
|
return;
|
|
9344
|
-
const
|
|
9345
|
-
if (
|
|
9580
|
+
const linkedTransaction = await readStoredTransactionRecord(transaction, job.transactionId);
|
|
9581
|
+
if (linkedTransaction === void 0)
|
|
9346
9582
|
return;
|
|
9347
|
-
const linkedTransaction = asTransactionRecord(transactionValue);
|
|
9348
9583
|
if (linkedTransaction.status !== "committed")
|
|
9349
9584
|
return;
|
|
9350
9585
|
if (linkedTransaction.committedVersion === null) {
|
|
@@ -9786,8 +10021,7 @@ async function assertGarbageCollectionCandidateProvenanceInTransaction(transacti
|
|
|
9786
10021
|
}
|
|
9787
10022
|
}
|
|
9788
10023
|
for (const id of candidates.candidateTransactionIds) {
|
|
9789
|
-
const
|
|
9790
|
-
const record = value === void 0 ? void 0 : asTransactionRecord(value);
|
|
10024
|
+
const record = await readStoredTransactionRecord(transaction, id);
|
|
9791
10025
|
if (record === void 0 || record.status !== "aborted" && (record.status !== "committed" || record.committedVersion === null)) {
|
|
9792
10026
|
throw new Error(`Garbage collection transaction candidate is not terminal: ${id}`);
|
|
9793
10027
|
}
|
|
@@ -9804,10 +10038,7 @@ async function assertGarbageCollectionCandidateProvenanceInTransaction(transacti
|
|
|
9804
10038
|
const blockHasProvenance = async (id) => {
|
|
9805
10039
|
if (manifestProvenBlockIds.has(id))
|
|
9806
10040
|
return true;
|
|
9807
|
-
const transactionProven = await
|
|
9808
|
-
const record = asTransactionRecord(value);
|
|
9809
|
-
return record.status === "aborted" && record.pendingBlockIds.includes(id);
|
|
9810
|
-
});
|
|
10041
|
+
const transactionProven = await visitTransactionRecords(transaction, (record) => record.status === "aborted" && record.pendingBlockIds.includes(id));
|
|
9811
10042
|
if (transactionProven)
|
|
9812
10043
|
return true;
|
|
9813
10044
|
return visitObjectStoreSequentially(transaction.objectStore("gc"), (value, key) => {
|
|
@@ -9843,8 +10074,7 @@ async function collectBoundedPhysicalRootsInTransaction(transaction, candidateSe
|
|
|
9843
10074
|
ownerBlockRootsComplete = false;
|
|
9844
10075
|
}
|
|
9845
10076
|
};
|
|
9846
|
-
await
|
|
9847
|
-
const record = asTransactionRecord(value);
|
|
10077
|
+
await visitActiveTransactionRecords(transaction, (record) => {
|
|
9848
10078
|
if (record.status !== "active")
|
|
9849
10079
|
return;
|
|
9850
10080
|
rememberOwnerBlocks(record.pendingBlockIds);
|
|
@@ -9875,10 +10105,7 @@ async function collectBoundedPhysicalRootsInTransaction(transaction, candidateSe
|
|
|
9875
10105
|
return cached;
|
|
9876
10106
|
let rooted = ownerBlockRoots.has(id);
|
|
9877
10107
|
if (!rooted && !ownerBlockRootsComplete) {
|
|
9878
|
-
rooted = await
|
|
9879
|
-
const record = asTransactionRecord(value);
|
|
9880
|
-
return record.status === "active" && record.pendingBlockIds.includes(id);
|
|
9881
|
-
});
|
|
10108
|
+
rooted = await visitActiveTransactionRecords(transaction, (record) => record.status === "active" && record.pendingBlockIds.includes(id));
|
|
9882
10109
|
if (!rooted) {
|
|
9883
10110
|
rooted = await visitObjectStoreSequentially(transaction.objectStore("gc"), (value, key) => {
|
|
9884
10111
|
const job = asCompactionJobAtMaintenanceKey(value, key);
|
|
@@ -9899,15 +10126,13 @@ async function collectBoundedPhysicalRootsInTransaction(transaction, candidateSe
|
|
|
9899
10126
|
const rootedBlockIds = /* @__PURE__ */ new Set();
|
|
9900
10127
|
const rootedSegmentIds = /* @__PURE__ */ new Set();
|
|
9901
10128
|
const segmentStore = transaction.objectStore("segments");
|
|
9902
|
-
const transactionStore = transaction.objectStore("transactions");
|
|
9903
10129
|
for (const id of candidateSegmentIds) {
|
|
9904
10130
|
const value = await requestResult(segmentStore.get(id));
|
|
9905
10131
|
if (value === void 0)
|
|
9906
10132
|
continue;
|
|
9907
10133
|
const segment = asSegmentRecord(value);
|
|
9908
10134
|
const ids = segmentBlockIds(segment);
|
|
9909
|
-
const
|
|
9910
|
-
const owner = ownerValue === void 0 ? void 0 : asTransactionRecord(ownerValue);
|
|
10135
|
+
const owner = await readStoredTransactionRecord(transaction, segment.transactionId);
|
|
9911
10136
|
let allBlocksRooted = ids.length > 0;
|
|
9912
10137
|
for (const blockId of ids) {
|
|
9913
10138
|
if (await isDirectBlockRoot(blockId))
|
|
@@ -10728,7 +10953,7 @@ async function pendingCatalogReservations(store, excludeTransactionId) {
|
|
|
10728
10953
|
await visitObjectStoreSequentially(store, (value, key) => {
|
|
10729
10954
|
if (typeof key !== "string")
|
|
10730
10955
|
throw corruption("transactions", "record key is invalid");
|
|
10731
|
-
const transaction =
|
|
10956
|
+
const transaction = asStoredTransactionRecord(value, key);
|
|
10732
10957
|
if (transaction.id === excludeTransactionId || transaction.status !== "active" || transaction.pendingTable === void 0) {
|
|
10733
10958
|
return;
|
|
10734
10959
|
}
|
|
@@ -10951,7 +11176,7 @@ async function assertPinnedHistoryAdmission(transaction, input) {
|
|
|
10951
11176
|
throw corruption("transactions", "record key is invalid");
|
|
10952
11177
|
if (key === input.excludeTransactionId)
|
|
10953
11178
|
return;
|
|
10954
|
-
const record =
|
|
11179
|
+
const record = asStoredTransactionRecord(value, key);
|
|
10955
11180
|
if (record.status !== "active") {
|
|
10956
11181
|
throw corruption(`transactions/${key}`, "status index does not match its record");
|
|
10957
11182
|
}
|