import type { BeginOpfsJournalTransaction, OpfsCommittedObjectPage, OpfsJournalPage, OpfsJournalPort, OpfsJournalTransaction, OpfsPreparedObject, OpfsStorageScope, } from "../../../application/ports/browser-file-storage/opfs-ports.ts"; import { assertValidStoragePolicy, type BrowserDataFailure, type BrowserDataOperation, type BrowserDataResult, type BrowserStoragePolicy, } from "../../../application/ports/browser-file-storage/shared.ts"; import { browserDataFailure, browserDataSuccess, } from "../../browser-file-storage/result.ts"; import { mapIndexedDbException } from "../indexeddb/indexeddb-failure.ts"; import { isValidOpfsStorageScope } from "./opfs-policy.ts"; export type IndexedDbOpfsJournalDependencies = Readonly<{ authorityToken: string; databaseName?: string; factory?: IDBFactory; keyRange?: Pick; createFencingToken?: () => string; blockedTimeoutMs?: number; scheduler?: Readonly<{ setTimeout(callback: () => void, milliseconds: number): unknown; clearTimeout(handle: unknown): void; }>; observe?: ( event: Readonly<{ operation: BrowserDataOperation; outcome: "SUCCEEDED" | "FAILED"; failureCode?: BrowserDataFailure["code"]; }>, ) => void; }>; export interface IndexedDbOpfsJournal extends OpfsJournalPort { close(): void; } type TransactionContext = Readonly<{ succeed(value: Value): void; fail(result: BrowserDataResult): void; }>; type StoredJournalRow = OpfsJournalTransaction & Readonly<{ logicalKey: string }>; type StoredObjectRow = Readonly<{ logicalKey: string; scopeObjectKey: string; scopeKey: string; objectId: string; preparedObject: OpfsPreparedObject; }>; type StoredBudgetRow = Readonly<{ budgetKey: string; namespace: string; authorityToken: string; namespaceToken: string; partitionToken: string; hardBudgetBytes: number; committedBytes: number; reservedBytes: number; }>; type StoredScopeBinding = Readonly<{ scopeKey: string; namespace: string; authorityToken: string; namespaceToken: string; partitionToken: string; policyFingerprint: string; }>; type StoredLogicalScopeBinding = Readonly<{ logicalScopeKey: string; physicalScopeKey: string; authorityToken: string; namespace: string; namespaceToken: string; partitionToken: string; }>; type StoredChunkReference = Readonly<{ referenceKey: string; scopeKey: string; digestHex: string; referenceCount: number; }>; const DATABASE_VERSION = 1; const JOURNAL_STORE = "opfs-journal"; const OBJECT_STORE = "opfs-objects"; const BUDGET_STORE = "opfs-budgets"; const SCOPE_BINDING_STORE = "opfs-scope-bindings"; const LOGICAL_SCOPE_BINDING_STORE = "opfs-logical-scope-bindings"; const CHUNK_REFERENCE_STORE = "opfs-chunk-references"; const LOGICAL_KEY_INDEX = "by-logical-key"; const STARTED_AT_INDEX = "by-started-at"; const SCOPE_OBJECT_INDEX = "by-scope-object"; const SAFE_DATABASE_NAME = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/u; const SAFE_BOUNDARY_ID = /^[A-Za-z0-9_-]{8,128}$/u; const SHA256_HEX = /^[a-f0-9]{64}$/u; export function opfsJournalDatabaseName( authorityToken: string, ): string { if (!SAFE_BOUNDARY_ID.test(authorityToken)) { throw new TypeError("OPFS authority token is invalid."); } return `ca-frontend-opfs-metadata-v1:${authorityToken}`; } export function createIndexedDbOpfsJournal( dependencies: IndexedDbOpfsJournalDependencies, ): IndexedDbOpfsJournal { const expectedDatabaseName = opfsJournalDatabaseName( dependencies.authorityToken, ); const databaseName = dependencies.databaseName ?? expectedDatabaseName; const factory = dependencies.factory ?? (typeof globalThis.indexedDB === "undefined" ? undefined : globalThis.indexedDB); const keyRange = dependencies.keyRange ?? (typeof globalThis.IDBKeyRange === "undefined" ? undefined : globalThis.IDBKeyRange); const createFencingToken = dependencies.createFencingToken ?? (() => globalThis.crypto.randomUUID()); const blockedTimeoutMs = dependencies.blockedTimeoutMs ?? 10_000; const scheduler = dependencies.scheduler ?? Object.freeze({ setTimeout: (callback: () => void, milliseconds: number) => globalThis.setTimeout(callback, milliseconds), clearTimeout: (handle: unknown) => globalThis.clearTimeout( handle as ReturnType, ), }); if ( !SAFE_BOUNDARY_ID.test(dependencies.authorityToken) || !SAFE_DATABASE_NAME.test(databaseName) || databaseName !== expectedDatabaseName || !Number.isSafeInteger(blockedTimeoutMs) || blockedTimeoutMs < 0 ) { throw new TypeError("IndexedDB OPFS journal configuration is invalid."); } let database: IDBDatabase | null = null; let opening: Promise> | null = null; let closed = false; const journal: IndexedDbOpfsJournal = { async getCommittedObject(scope, objectId) { if ( !isBoundScope(scope) || !validScopedObject(scope, objectId) ) { return browserDataFailure("INVALID_INPUT", "INDEXEDDB_READ"); } return await withDatabase("INDEXEDDB_READ", (db) => runTransaction( db, [OBJECT_STORE], "readonly", "INDEXEDDB_READ", (transaction, context) => { const request = transaction .objectStore(OBJECT_STORE) .get(logicalObjectKey(scope, objectId)); request.onsuccess = () => { if (request.result === undefined) { context.succeed(null); return; } if ( !isStoredObjectRow(request.result) || !sameScope(request.result.preparedObject.descriptor.scope, scope) ) { context.fail(corrupt("INDEXEDDB_READ")); return; } context.succeed(request.result.preparedObject); }; }, ), ); }, async begin(input) { if ( !isBoundScope(input.scope) || !isBeginTransaction(input) ) { return browserDataFailure("INVALID_INPUT", "INDEXEDDB_WRITE"); } const fencingToken = createFencingToken(); if (!SAFE_BOUNDARY_ID.test(fencingToken)) { return browserDataFailure("UNAVAILABLE", "INDEXEDDB_WRITE", { recovery: "REOPEN", }); } return await withDatabase("INDEXEDDB_WRITE", (db) => runTransaction( db, [ JOURNAL_STORE, OBJECT_STORE, BUDGET_STORE, SCOPE_BINDING_STORE, LOGICAL_SCOPE_BINDING_STORE, ], "readwrite", "INDEXEDDB_WRITE", (nativeTransaction, context) => { const bindingStore = nativeTransaction.objectStore(SCOPE_BINDING_STORE); const scopeKey = storageScopeKey(input.scope); const bindingRequest = bindingStore.get(scopeKey); bindingRequest.onsuccess = () => { const storedBinding = bindingRequest.result; if ( storedBinding !== undefined && !isScopeBinding(storedBinding) ) { context.fail(corrupt("INDEXEDDB_WRITE")); return; } const expectedBinding = scopeBinding( input.scope, input.targetStoragePolicy, ); const logicalBindingStore = nativeTransaction.objectStore( LOGICAL_SCOPE_BINDING_STORE, ); const expectedLogicalBinding = logicalScopeBinding( input.scope, ); const logicalRequest = logicalBindingStore.get( expectedLogicalBinding.logicalScopeKey, ); logicalRequest.onsuccess = () => { const storedLogicalBinding = logicalRequest.result; if ( storedLogicalBinding !== undefined && !isLogicalScopeBinding(storedLogicalBinding) ) { context.fail(corrupt("INDEXEDDB_WRITE")); return; } if ( (storedBinding && stableJson(storedBinding) !== stableJson(expectedBinding)) || (storedLogicalBinding && stableJson(storedLogicalBinding) !== stableJson(expectedLogicalBinding)) ) { context.fail(policyRejected("INDEXEDDB_WRITE")); return; } if ( (storedBinding === undefined) !== (storedLogicalBinding === undefined) ) { context.fail(corrupt("INDEXEDDB_WRITE")); return; } if (!storedBinding) { bindingStore.add(expectedBinding); logicalBindingStore.add(expectedLogicalBinding); } }; }; const logicalKey = logicalObjectKey( input.scope, input.objectId, ); const objectRequest = nativeTransaction .objectStore(OBJECT_STORE) .get(logicalKey); objectRequest.onsuccess = () => { const storedCurrent = objectRequest.result; if ( storedCurrent !== undefined && !isStoredObjectRow(storedCurrent) ) { context.fail(corrupt("INDEXEDDB_WRITE")); return; } const current: OpfsPreparedObject | undefined = storedCurrent?.preparedObject; if ( !generationMatches( current, input.expectedGeneration, ) || input.targetGeneration !== (current?.descriptor.generation ?? 0) + 1 ) { context.fail(conflict("INDEXEDDB_WRITE")); return; } const currentBytes = current?.descriptor.byteLength ?? 0; const reservedBytes = input.mutation === "PUT" ? Math.max(0, input.targetByteLength - currentBytes) : 0; const budgetKey = storageBudgetKey(input.scope); const budgetRequest = nativeTransaction .objectStore(BUDGET_STORE) .get(budgetKey); budgetRequest.onsuccess = () => { const budgetResult = budgetRequest.result; if ( budgetResult !== undefined && !isBudgetRow(budgetResult) ) { context.fail(corrupt("INDEXEDDB_WRITE")); return; } if ( budgetResult && (budgetResult.hardBudgetBytes !== input.targetStoragePolicy.hardBudgetBytes || budgetResult.namespace !== input.scope.namespace || budgetResult.authorityToken !== input.scope.authorityToken || budgetResult.namespaceToken !== input.scope.namespaceToken || budgetResult.partitionToken !== input.scope.partitionToken) ) { context.fail(policyRejected("INDEXEDDB_WRITE")); return; } if (!budgetResult && current) { context.fail(corrupt("INDEXEDDB_WRITE")); return; } const budget: StoredBudgetRow = budgetResult ?? { budgetKey, namespace: input.scope.namespace, authorityToken: input.scope.authorityToken, namespaceToken: input.scope.namespaceToken, partitionToken: input.scope.partitionToken, hardBudgetBytes: input.targetStoragePolicy.hardBudgetBytes, committedBytes: 0, reservedBytes: 0, }; if ( budget.committedBytes + budget.reservedBytes + reservedBytes > budget.hardBudgetBytes ) { context.fail(limitExceeded("INDEXEDDB_WRITE")); return; } const nextBudget: StoredBudgetRow = Object.freeze({ ...budget, reservedBytes: budget.reservedBytes + reservedBytes, }); nativeTransaction .objectStore(BUDGET_STORE) .put(nextBudget); const row: StoredJournalRow = Object.freeze({ ...input, logicalKey, fencingToken, phase: "PREPARING", budgetReservation: Object.freeze({ namespace: input.scope.namespace, reservedBytes, hardBudgetBytes: input.targetStoragePolicy.hardBudgetBytes, }), }); nativeTransaction .objectStore(JOURNAL_STORE) .add(row); context.succeed(row); }; }; }, ), ); }, async markFilesReady( transactionId, fencingToken, preparedObject, ) { if ( !SAFE_BOUNDARY_ID.test(transactionId) || !SAFE_BOUNDARY_ID.test(fencingToken) || !isPreparedObject(preparedObject) || !isBoundScope(preparedObject.descriptor.scope) ) { return browserDataFailure("INVALID_INPUT", "INDEXEDDB_WRITE"); } return await updateJournalRow( transactionId, fencingToken, (row, context, store) => { if ( row.mutation !== "PUT" || row.objectId !== preparedObject.descriptor.objectId || !sameScope(row.scope, preparedObject.descriptor.scope) || row.targetGeneration !== preparedObject.descriptor.generation || row.targetByteLength !== preparedObject.descriptor.byteLength || stableJson(row.targetStoragePolicy) !== stableJson(preparedObject.descriptor.storagePolicy) ) { context.fail(corrupt("INDEXEDDB_WRITE")); return; } if (row.phase === "FILES_READY") { if ( !row.preparedObject || stableJson(row.preparedObject) !== stableJson(preparedObject) ) { context.fail(conflict("INDEXEDDB_WRITE")); return; } context.succeed(row); return; } if (row.phase !== "PREPARING") { context.fail(conflict("INDEXEDDB_WRITE")); return; } const updated: StoredJournalRow = Object.freeze({ ...row, phase: "FILES_READY", preparedObject, }); store.put(updated); context.succeed(updated); }, ); }, async commitPut(transactionId, fencingToken) { return await commitMutation( transactionId, fencingToken, "PUT", ); }, async commitDelete(transactionId, fencingToken) { return await commitMutation( transactionId, fencingToken, "DELETE", ); }, async complete(transactionId, fencingToken) { if (!validTransactionIdentity(transactionId, fencingToken)) { return browserDataFailure("INVALID_INPUT", "INDEXEDDB_WRITE"); } return await withDatabase("INDEXEDDB_WRITE", (db) => runTransaction( db, [JOURNAL_STORE], "readwrite", "INDEXEDDB_WRITE", (transaction, context) => { const store = transaction.objectStore(JOURNAL_STORE); const request = store.get(transactionId); request.onsuccess = () => { if (request.result === undefined) { context.succeed(undefined); return; } if ( !isStoredJournalRow(request.result) || !isBoundScope(request.result.scope) || request.result.fencingToken !== fencingToken || request.result.phase !== "COMMITTED" ) { context.fail(conflict("INDEXEDDB_WRITE")); return; } store.delete(transactionId); context.succeed(undefined); }; }, ), ); }, async rollback(transactionId, fencingToken) { if (!validTransactionIdentity(transactionId, fencingToken)) { return browserDataFailure("INVALID_INPUT", "INDEXEDDB_WRITE"); } return await withDatabase("INDEXEDDB_WRITE", (db) => runTransaction( db, [JOURNAL_STORE, BUDGET_STORE], "readwrite", "INDEXEDDB_WRITE", (transaction, context) => { const journalStore = transaction.objectStore(JOURNAL_STORE); const request = journalStore.get(transactionId); request.onsuccess = () => { if (request.result === undefined) { context.succeed(undefined); return; } const row = request.result; if ( !isStoredJournalRow(row) || !isBoundScope(row.scope) || row.fencingToken !== fencingToken || row.phase === "COMMITTED" ) { context.fail(conflict("INDEXEDDB_WRITE")); return; } const budgetStore = transaction.objectStore(BUDGET_STORE); const budgetRequest = budgetStore.get( storageBudgetKey(row.scope), ); budgetRequest.onsuccess = () => { if (!isBudgetRow(budgetRequest.result)) { context.fail(corrupt("INDEXEDDB_WRITE")); return; } const budget = budgetRequest.result; if ( budget.reservedBytes < row.budgetReservation.reservedBytes ) { context.fail(corrupt("INDEXEDDB_WRITE")); return; } putOrDeleteBudget( budgetStore, Object.freeze({ ...budget, reservedBytes: budget.reservedBytes - row.budgetReservation.reservedBytes, }), ); journalStore.delete(transactionId); context.succeed(undefined); }; }; }, ), ); }, async listIncomplete(limit) { if ( !Number.isSafeInteger(limit) || limit < 1 || limit > 1_000 ) { return browserDataFailure("INVALID_INPUT", "INDEXEDDB_READ"); } return await withDatabase("INDEXEDDB_READ", (db) => runTransaction( db, [JOURNAL_STORE], "readonly", "INDEXEDDB_READ", (transaction, context) => { const rows: OpfsJournalTransaction[] = []; const request = transaction .objectStore(JOURNAL_STORE) .index(STARTED_AT_INDEX) .openCursor(); request.onsuccess = () => { const cursor = request.result; if (!cursor) { context.succeed( Object.freeze({ transactions: Object.freeze(rows), moreAvailable: false, }), ); return; } if ( !isStoredJournalRow(cursor.value) || !isBoundScope(cursor.value.scope) ) { context.fail(corrupt("INDEXEDDB_READ")); return; } if (rows.length === limit) { context.succeed( Object.freeze({ transactions: Object.freeze(rows), moreAvailable: true, }), ); return; } rows.push(cursor.value); cursor.continue(); }; }, ), ); }, async listCommittedObjects(request) { if ( !keyRange || !isBoundScope(request.scope) || !isValidOpfsStorageScope(request.scope) || !Number.isSafeInteger(request.limit) || request.limit < 1 || request.limit > 1_000 || (request.afterObjectId !== undefined && !SAFE_BOUNDARY_ID.test(request.afterObjectId)) ) { return browserDataFailure( keyRange ? "INVALID_INPUT" : "UNSUPPORTED", "INDEXEDDB_READ", ); } return await withDatabase("INDEXEDDB_READ", (db) => runTransaction( db, [OBJECT_STORE], "readonly", "INDEXEDDB_READ", (transaction, context) => { const prefix = `${storageScopeKey(request.scope)}|`; const lower = request.afterObjectId ? `${prefix}${request.afterObjectId}` : prefix; const range = keyRange.bound( lower, `${prefix}\uffff`, request.afterObjectId !== undefined, false, ); const objects: OpfsPreparedObject[] = []; const cursorRequest = transaction .objectStore(OBJECT_STORE) .index(SCOPE_OBJECT_INDEX) .openCursor(range); cursorRequest.onsuccess = () => { const cursor = cursorRequest.result; if (!cursor) { context.succeed( Object.freeze({ objects: Object.freeze(objects), nextObjectId: null, moreAvailable: false, }), ); return; } if ( !isStoredObjectRow(cursor.value) || !sameScope( cursor.value.preparedObject.descriptor.scope, request.scope, ) ) { context.fail(corrupt("INDEXEDDB_READ")); return; } if (objects.length === request.limit) { context.succeed( Object.freeze({ objects: Object.freeze(objects), nextObjectId: objects.at(-1)?.descriptor.objectId ?? null, moreAvailable: true, }), ); return; } objects.push(cursor.value.preparedObject); cursor.continue(); }; }, ), ); }, async isChunkReferenced(scope, digestHex) { if ( !isBoundScope(scope) || !isValidOpfsStorageScope(scope) || !SHA256_HEX.test(digestHex) ) { return browserDataFailure("INVALID_INPUT", "INDEXEDDB_READ"); } const referenceKey = chunkReferenceKey(scope, digestHex); return await withDatabase("INDEXEDDB_READ", (db) => runTransaction( db, [CHUNK_REFERENCE_STORE], "readonly", "INDEXEDDB_READ", (transaction, context) => { const request = transaction .objectStore(CHUNK_REFERENCE_STORE) .get(referenceKey); request.onsuccess = () => { if (request.result === undefined) { context.succeed(false); return; } if ( !isChunkReference(request.result) || request.result.referenceKey !== referenceKey || request.result.scopeKey !== storageScopeKey(scope) ) { context.fail(corrupt("INDEXEDDB_READ")); return; } context.succeed(request.result.referenceCount > 0); }; }, ), ); }, close() { closed = true; database?.close(); database = null; opening = null; }, }; return Object.freeze(journal); async function commitMutation( transactionId: string, fencingToken: string, mutation: "PUT" | "DELETE", ): Promise> { if (!validTransactionIdentity(transactionId, fencingToken)) { return browserDataFailure("INVALID_INPUT", "INDEXEDDB_WRITE"); } return await withDatabase("INDEXEDDB_WRITE", (db) => runTransaction( db, [ JOURNAL_STORE, OBJECT_STORE, BUDGET_STORE, CHUNK_REFERENCE_STORE, ], "readwrite", "INDEXEDDB_WRITE", (nativeTransaction, context) => { const journalStore = nativeTransaction.objectStore(JOURNAL_STORE); const journalRequest = journalStore.get(transactionId); journalRequest.onsuccess = () => { const row = journalRequest.result; if ( !isStoredJournalRow(row) || !isBoundScope(row.scope) || row.fencingToken !== fencingToken || row.mutation !== mutation ) { context.fail(conflict("INDEXEDDB_WRITE")); return; } if (row.phase === "COMMITTED") { context.succeed(row); return; } if ( mutation === "PUT" && (row.phase !== "FILES_READY" || !row.preparedObject) ) { context.fail(conflict("INDEXEDDB_WRITE")); return; } if (mutation === "DELETE" && row.phase !== "PREPARING") { context.fail(conflict("INDEXEDDB_WRITE")); return; } const objectStore = nativeTransaction.objectStore(OBJECT_STORE); const objectRequest = objectStore.get(row.logicalKey); objectRequest.onsuccess = () => { const storedCurrent = objectRequest.result; if ( storedCurrent !== undefined && !isStoredObjectRow(storedCurrent) ) { context.fail(corrupt("INDEXEDDB_WRITE")); return; } const current: OpfsPreparedObject | undefined = storedCurrent?.preparedObject; if ( !generationMatches(current, row.expectedGeneration) ) { context.fail(conflict("INDEXEDDB_WRITE")); return; } const budgetStore = nativeTransaction.objectStore(BUDGET_STORE); const budgetRequest = budgetStore.get( storageBudgetKey(row.scope), ); budgetRequest.onsuccess = () => { if (!isBudgetRow(budgetRequest.result)) { context.fail(corrupt("INDEXEDDB_WRITE")); return; } const budget = budgetRequest.result; const currentBytes = current?.descriptor.byteLength ?? 0; const nextBytes = mutation === "PUT" ? row.targetByteLength : 0; const nextCommitted = budget.committedBytes - currentBytes + nextBytes; const nextReserved = budget.reservedBytes - row.budgetReservation.reservedBytes; if ( nextCommitted < 0 || nextReserved < 0 || nextCommitted + nextReserved > budget.hardBudgetBytes ) { context.fail(corrupt("INDEXEDDB_WRITE")); return; } const referenceDeltas = chunkReferenceDeltas( current, mutation === "PUT" ? row.preparedObject : undefined, ); applyChunkReferenceDeltas( nativeTransaction.objectStore( CHUNK_REFERENCE_STORE, ), row.scope, referenceDeltas, context, () => { putOrDeleteBudget( budgetStore, Object.freeze({ ...budget, committedBytes: nextCommitted, reservedBytes: nextReserved, }), ); if (mutation === "PUT") { objectStore.put( storedObjectRow(row.preparedObject!), ); } else { objectStore.delete(row.logicalKey); } const committed: StoredJournalRow = Object.freeze({ ...row, phase: "COMMITTED", }); journalStore.put(committed); context.succeed(committed); }, ); }; }; }; }, ), ); } async function updateJournalRow( transactionId: string, fencingToken: string, update: ( row: StoredJournalRow, context: TransactionContext, store: IDBObjectStore, ) => void, ): Promise> { return await withDatabase("INDEXEDDB_WRITE", (db) => runTransaction( db, [JOURNAL_STORE], "readwrite", "INDEXEDDB_WRITE", (transaction, context) => { const store = transaction.objectStore(JOURNAL_STORE); const request = store.get(transactionId); request.onsuccess = () => { if ( !isStoredJournalRow(request.result) || !isBoundScope(request.result.scope) || request.result.fencingToken !== fencingToken ) { context.fail(conflict("INDEXEDDB_WRITE")); return; } update(request.result, context, store); }; }, ), ); } async function withDatabase( operation: BrowserDataOperation, task: (db: IDBDatabase) => Promise>, ): Promise> { const opened = await openDatabase(); if (!opened.ok) return opened; try { const result = await task(opened.value); observe(result, operation); return result; } catch (error) { const result = mapIndexedDbException(error, operation); observe(result, operation); return result; } } async function openDatabase(): Promise> { if (closed || !factory) { return browserDataFailure("UNSUPPORTED", "INDEXEDDB_OPEN", { recovery: "ONLINE_ONLY", }); } if (database) return browserDataSuccess(database); if (opening) return await opening; const currentOpening = new Promise>( (resolve) => { let settled = false; let blockedTimer: unknown; const settle = ( result: BrowserDataResult, ): void => { if (settled) return; settled = true; if (blockedTimer !== undefined) { scheduler.clearTimeout(blockedTimer); } resolve(result); }; let request: IDBOpenDBRequest; try { request = factory.open(databaseName, DATABASE_VERSION); } catch (error) { settle(mapIndexedDbException(error, "INDEXEDDB_OPEN")); return; } request.onupgradeneeded = (event) => { const db = request.result; if (event.oldVersion !== 0) { request.transaction?.abort(); return; } const journalStore = db.createObjectStore(JOURNAL_STORE, { keyPath: "transactionId", }); journalStore.createIndex( LOGICAL_KEY_INDEX, "logicalKey", { unique: true }, ); journalStore.createIndex( STARTED_AT_INDEX, "startedAtEpochMs", { unique: false }, ); const objectStore = db.createObjectStore(OBJECT_STORE, { keyPath: "logicalKey", }); objectStore.createIndex( SCOPE_OBJECT_INDEX, "scopeObjectKey", { unique: true }, ); db.createObjectStore(BUDGET_STORE, { keyPath: "budgetKey", }); db.createObjectStore(SCOPE_BINDING_STORE, { keyPath: "scopeKey", }); db.createObjectStore(LOGICAL_SCOPE_BINDING_STORE, { keyPath: "logicalScopeKey", }); db.createObjectStore(CHUNK_REFERENCE_STORE, { keyPath: "referenceKey", }); }; request.onblocked = () => { if (blockedTimer !== undefined) return; blockedTimer = scheduler.setTimeout(() => { settle( browserDataFailure("BLOCKED", "INDEXEDDB_OPEN", { retryable: true, recovery: "RELOAD_OTHER_CONTEXTS", }), ); }, blockedTimeoutMs); }; request.onerror = () => settle( mapIndexedDbException( request.error, "INDEXEDDB_OPEN", ), ); request.onsuccess = () => { if (settled || closed) { request.result.close(); return; } database = request.result; database.onversionchange = () => { database?.close(); database = null; opening = null; }; database.onclose = () => { database = null; opening = null; }; settle(browserDataSuccess(request.result)); }; }, ).finally(() => { if (opening === currentOpening) opening = null; }); opening = currentOpening; return await currentOpening; } function observe( result: BrowserDataResult, operation: BrowserDataOperation, ): void { try { dependencies.observe?.( result.ok ? { operation, outcome: "SUCCEEDED" } : { operation, outcome: "FAILED", failureCode: result.error.code, }, ); } catch { // Journal behavior never depends on telemetry. } } function isBoundScope(scope: OpfsStorageScope): boolean { return scope.authorityToken === dependencies.authorityToken; } } function runTransaction( database: IDBDatabase, storeNames: readonly string[], mode: IDBTransactionMode, operation: BrowserDataOperation, run: ( transaction: IDBTransaction, context: TransactionContext, ) => void, ): Promise> { return new Promise((resolve) => { let value: Value | undefined; let hasValue = false; let explicitFailure: BrowserDataResult | null = null; let settled = false; let transaction: IDBTransaction; try { transaction = mode === "readwrite" ? strictReadwriteTransaction(database, storeNames) : database.transaction([...storeNames], mode); } catch (error) { resolve(mapIndexedDbException(error, operation)); return; } const settle = (result: BrowserDataResult): void => { if (settled) return; settled = true; resolve(result); }; const context: TransactionContext = { succeed(nextValue) { value = nextValue; hasValue = true; }, fail(result) { if (explicitFailure) return; explicitFailure = result; try { transaction.abort(); } catch { settle(result); } }, }; transaction.oncomplete = () => { if (!hasValue) { settle( browserDataFailure("UNAVAILABLE", operation, { retryable: true, recovery: "REOPEN", }), ); return; } settle(browserDataSuccess(value as Value)); }; transaction.onabort = () => settle( explicitFailure ?? mapIndexedDbException(transaction.error, operation), ); transaction.onerror = () => { // onabort owns the single failure result. }; try { run(transaction, context); } catch (error) { explicitFailure = mapIndexedDbException(error, operation); try { transaction.abort(); } catch { settle(explicitFailure); } } }); } function strictReadwriteTransaction( database: IDBDatabase, storeNames: readonly string[], ): IDBTransaction { try { return database.transaction([...storeNames], "readwrite", { durability: "strict", }); } catch (error) { if (error instanceof TypeError) { return database.transaction([...storeNames], "readwrite"); } throw error; } } function applyChunkReferenceDeltas( store: IDBObjectStore, scope: OpfsStorageScope, deltas: ReadonlyMap, context: TransactionContext, completed: () => void, ): void { const entries = [...deltas].filter(([, delta]) => delta !== 0); if (entries.length === 0) { completed(); return; } let remaining = entries.length; for (const [digestHex, delta] of entries) { const referenceKey = chunkReferenceKey(scope, digestHex); const request = store.get(referenceKey); request.onsuccess = () => { if ( request.result !== undefined && (!isChunkReference(request.result) || request.result.referenceKey !== referenceKey || request.result.scopeKey !== storageScopeKey(scope)) ) { context.fail(corrupt("INDEXEDDB_WRITE")); return; } const current = (request.result as StoredChunkReference | undefined) ?.referenceCount ?? 0; const next = current + delta; if (!Number.isSafeInteger(next) || next < 0) { context.fail(corrupt("INDEXEDDB_WRITE")); return; } if (next === 0) { store.delete(referenceKey); } else { store.put( Object.freeze({ referenceKey, scopeKey: storageScopeKey(scope), digestHex, referenceCount: next, }), ); } remaining -= 1; if (remaining === 0) completed(); }; } } function chunkReferenceDeltas( previous: OpfsPreparedObject | undefined, next: OpfsPreparedObject | undefined, ): ReadonlyMap { const deltas = new Map(); for (const chunk of previous?.chunks ?? []) { deltas.set(chunk.digestHex, (deltas.get(chunk.digestHex) ?? 0) - 1); } for (const chunk of next?.chunks ?? []) { deltas.set(chunk.digestHex, (deltas.get(chunk.digestHex) ?? 0) + 1); } return deltas; } function putOrDeleteBudget( store: IDBObjectStore, budget: StoredBudgetRow, ): void { if (budget.committedBytes === 0 && budget.reservedBytes === 0) { store.delete(budget.budgetKey); } else { store.put(budget); } } function storedObjectRow( preparedObject: OpfsPreparedObject, ): StoredObjectRow { const { scope, objectId } = preparedObject.descriptor; return Object.freeze({ logicalKey: logicalObjectKey(scope, objectId), scopeObjectKey: `${storageScopeKey(scope)}|${objectId}`, scopeKey: storageScopeKey(scope), objectId, preparedObject, }); } function generationMatches( current: OpfsPreparedObject | undefined, expectedGeneration: number | null, ): boolean { return expectedGeneration === null ? current === undefined : current?.descriptor.generation === expectedGeneration; } function logicalObjectKey( scope: OpfsStorageScope, objectId: string, ): string { return `${storageScopeKey(scope)}|${objectId}`; } function storageScopeKey(scope: OpfsStorageScope): string { return [ scope.authorityToken, scope.namespaceToken, scope.partitionToken, ].join("|"); } function storageBudgetKey(scope: OpfsStorageScope): string { return storageScopeKey(scope); } function scopeBinding( scope: OpfsStorageScope, storagePolicy: BrowserStoragePolicy, ): StoredScopeBinding { return Object.freeze({ scopeKey: storageScopeKey(scope), namespace: scope.namespace, authorityToken: scope.authorityToken, namespaceToken: scope.namespaceToken, partitionToken: scope.partitionToken, policyFingerprint: stableJson(storagePolicy), }); } function logicalScopeBinding( scope: OpfsStorageScope, ): StoredLogicalScopeBinding { return Object.freeze({ logicalScopeKey: [ scope.authorityToken, scope.namespace, scope.partitionToken, ].join("|"), physicalScopeKey: storageScopeKey(scope), authorityToken: scope.authorityToken, namespace: scope.namespace, namespaceToken: scope.namespaceToken, partitionToken: scope.partitionToken, }); } function chunkReferenceKey( scope: OpfsStorageScope, digestHex: string, ): string { return `${storageScopeKey(scope)}|${digestHex}`; } function sameScope( left: OpfsStorageScope, right: OpfsStorageScope, ): boolean { return ( left.namespace === right.namespace && left.authorityToken === right.authorityToken && left.namespaceToken === right.namespaceToken && left.partitionToken === right.partitionToken ); } function validScopedObject( scope: OpfsStorageScope, objectId: string, ): boolean { return ( isValidOpfsStorageScope(scope) && SAFE_BOUNDARY_ID.test(objectId) ); } function validTransactionIdentity( transactionId: string, fencingToken: string, ): boolean { return ( SAFE_BOUNDARY_ID.test(transactionId) && SAFE_BOUNDARY_ID.test(fencingToken) ); } function isBeginTransaction( value: BeginOpfsJournalTransaction, ): boolean { try { assertValidStoragePolicy(value.targetStoragePolicy); } catch { return false; } return Boolean( validScopedObject(value.scope, value.objectId) && SAFE_BOUNDARY_ID.test(value.transactionId) && value.targetStoragePolicy.namespace === value.scope.namespace && (value.mutation === "PUT" || value.mutation === "DELETE") && (value.expectedGeneration === null || (Number.isSafeInteger(value.expectedGeneration) && value.expectedGeneration > 0)) && Number.isSafeInteger(value.targetGeneration) && value.targetGeneration > 0 && Number.isSafeInteger(value.targetByteLength) && value.targetByteLength >= 0 && (value.mutation === "PUT" || value.targetByteLength === 0) && Number.isSafeInteger(value.startedAtEpochMs) && value.startedAtEpochMs >= 0, ); } function isStoredJournalRow( value: unknown, ): value is StoredJournalRow { if ( !value || typeof value !== "object" || !("logicalKey" in value) || typeof value.logicalKey !== "string" || !("transactionId" in value) || typeof value.transactionId !== "string" || !SAFE_BOUNDARY_ID.test(value.transactionId) || !("fencingToken" in value) || typeof value.fencingToken !== "string" || !SAFE_BOUNDARY_ID.test(value.fencingToken) || !("scope" in value) || !value.scope || typeof value.scope !== "object" || !isValidOpfsStorageScope(value.scope as OpfsStorageScope) || !("mutation" in value) || (value.mutation !== "PUT" && value.mutation !== "DELETE") || !("phase" in value) || !["PREPARING", "FILES_READY", "COMMITTED"].includes( String(value.phase), ) || !("objectId" in value) || typeof value.objectId !== "string" || !SAFE_BOUNDARY_ID.test(value.objectId) || value.logicalKey !== logicalObjectKey(value.scope as OpfsStorageScope, value.objectId) || !("expectedGeneration" in value) || (value.expectedGeneration !== null && (typeof value.expectedGeneration !== "number" || !Number.isSafeInteger(value.expectedGeneration) || value.expectedGeneration < 1)) || !("targetGeneration" in value) || typeof value.targetGeneration !== "number" || !Number.isSafeInteger(value.targetGeneration) || value.targetGeneration < 1 || !("targetByteLength" in value) || typeof value.targetByteLength !== "number" || !Number.isSafeInteger(value.targetByteLength) || value.targetByteLength < 0 || !("targetStoragePolicy" in value) || !value.targetStoragePolicy || typeof value.targetStoragePolicy !== "object" || !("budgetReservation" in value) || !isBudgetReservation(value.budgetReservation) || !("startedAtEpochMs" in value) || typeof value.startedAtEpochMs !== "number" || !Number.isSafeInteger(value.startedAtEpochMs) || value.startedAtEpochMs < 0 ) { return false; } try { assertValidStoragePolicy( value.targetStoragePolicy as OpfsJournalTransaction["targetStoragePolicy"], ); } catch { return false; } const scope = value.scope as OpfsStorageScope; const storagePolicy = value.targetStoragePolicy as OpfsJournalTransaction["targetStoragePolicy"]; const budgetReservation = value.budgetReservation as OpfsJournalTransaction["budgetReservation"]; if ( storagePolicy.namespace !== scope.namespace || budgetReservation.namespace !== scope.namespace || budgetReservation.hardBudgetBytes !== storagePolicy.hardBudgetBytes ) { return false; } if ("preparedObject" in value && value.preparedObject !== undefined) { return ( value.mutation === "PUT" && value.phase !== "PREPARING" && isPreparedObject(value.preparedObject) && value.preparedObject.descriptor.objectId === value.objectId && sameScope( value.preparedObject.descriptor.scope, value.scope as OpfsStorageScope, ) ); } return ( value.phase === "PREPARING" || (value.phase === "COMMITTED" && value.mutation === "DELETE") ); } function isBudgetReservation(value: unknown): boolean { return Boolean( value && typeof value === "object" && "namespace" in value && typeof value.namespace === "string" && "reservedBytes" in value && typeof value.reservedBytes === "number" && Number.isSafeInteger(value.reservedBytes) && value.reservedBytes >= 0 && "hardBudgetBytes" in value && typeof value.hardBudgetBytes === "number" && Number.isSafeInteger(value.hardBudgetBytes) && value.hardBudgetBytes >= 0, ); } function isStoredObjectRow(value: unknown): value is StoredObjectRow { if ( !value || typeof value !== "object" || !("logicalKey" in value) || typeof value.logicalKey !== "string" || !("scopeObjectKey" in value) || typeof value.scopeObjectKey !== "string" || !("scopeKey" in value) || typeof value.scopeKey !== "string" || !("objectId" in value) || typeof value.objectId !== "string" || !("preparedObject" in value) || !isPreparedObject(value.preparedObject) ) { return false; } const scope = value.preparedObject.descriptor.scope; return ( value.objectId === value.preparedObject.descriptor.objectId && value.logicalKey === logicalObjectKey(scope, value.objectId) && value.scopeKey === storageScopeKey(scope) && value.scopeObjectKey === `${value.scopeKey}|${value.objectId}` ); } function isBudgetRow(value: unknown): value is StoredBudgetRow { return Boolean( value && typeof value === "object" && "budgetKey" in value && typeof value.budgetKey === "string" && "namespace" in value && typeof value.namespace === "string" && "authorityToken" in value && typeof value.authorityToken === "string" && SAFE_BOUNDARY_ID.test(value.authorityToken) && "namespaceToken" in value && typeof value.namespaceToken === "string" && SAFE_BOUNDARY_ID.test(value.namespaceToken) && "partitionToken" in value && typeof value.partitionToken === "string" && SAFE_BOUNDARY_ID.test(value.partitionToken) && value.budgetKey === `${value.authorityToken}|${value.namespaceToken}|${value.partitionToken}` && "hardBudgetBytes" in value && typeof value.hardBudgetBytes === "number" && Number.isSafeInteger(value.hardBudgetBytes) && value.hardBudgetBytes >= 0 && "committedBytes" in value && typeof value.committedBytes === "number" && Number.isSafeInteger(value.committedBytes) && value.committedBytes >= 0 && "reservedBytes" in value && typeof value.reservedBytes === "number" && Number.isSafeInteger(value.reservedBytes) && value.reservedBytes >= 0 && value.committedBytes + value.reservedBytes <= value.hardBudgetBytes, ); } function isScopeBinding(value: unknown): value is StoredScopeBinding { return Boolean( value && typeof value === "object" && "scopeKey" in value && typeof value.scopeKey === "string" && "namespace" in value && typeof value.namespace === "string" && "authorityToken" in value && typeof value.authorityToken === "string" && SAFE_BOUNDARY_ID.test(value.authorityToken) && "namespaceToken" in value && typeof value.namespaceToken === "string" && SAFE_BOUNDARY_ID.test(value.namespaceToken) && "partitionToken" in value && typeof value.partitionToken === "string" && SAFE_BOUNDARY_ID.test(value.partitionToken) && value.scopeKey === `${value.authorityToken}|${value.namespaceToken}|${value.partitionToken}` && "policyFingerprint" in value && typeof value.policyFingerprint === "string" && value.policyFingerprint.length > 0 && value.policyFingerprint.length <= 4_096, ); } function isLogicalScopeBinding( value: unknown, ): value is StoredLogicalScopeBinding { return Boolean( value && typeof value === "object" && "logicalScopeKey" in value && typeof value.logicalScopeKey === "string" && "physicalScopeKey" in value && typeof value.physicalScopeKey === "string" && "authorityToken" in value && typeof value.authorityToken === "string" && SAFE_BOUNDARY_ID.test(value.authorityToken) && "namespace" in value && typeof value.namespace === "string" && "namespaceToken" in value && typeof value.namespaceToken === "string" && SAFE_BOUNDARY_ID.test(value.namespaceToken) && "partitionToken" in value && typeof value.partitionToken === "string" && SAFE_BOUNDARY_ID.test(value.partitionToken) && value.logicalScopeKey === `${value.authorityToken}|${value.namespace}|${value.partitionToken}` && value.physicalScopeKey === `${value.authorityToken}|${value.namespaceToken}|${value.partitionToken}`, ); } function isChunkReference( value: unknown, ): value is StoredChunkReference { return Boolean( value && typeof value === "object" && "referenceKey" in value && typeof value.referenceKey === "string" && "scopeKey" in value && typeof value.scopeKey === "string" && "digestHex" in value && typeof value.digestHex === "string" && SHA256_HEX.test(value.digestHex) && value.referenceKey === `${value.scopeKey}|${value.digestHex}` && "referenceCount" in value && typeof value.referenceCount === "number" && Number.isSafeInteger(value.referenceCount) && value.referenceCount > 0, ); } function isPreparedObject(value: unknown): value is OpfsPreparedObject { if ( !value || typeof value !== "object" || !("physicalSchemaVersion" in value) || value.physicalSchemaVersion !== 1 || !("descriptor" in value) || !value.descriptor || typeof value.descriptor !== "object" || !("chunks" in value) || !Array.isArray(value.chunks) ) { return false; } const descriptor = value.descriptor as Record; const integrity = descriptor.integrity && typeof descriptor.integrity === "object" ? (descriptor.integrity as Record) : null; if ( typeof descriptor.objectId !== "string" || !SAFE_BOUNDARY_ID.test(descriptor.objectId) || !descriptor.scope || typeof descriptor.scope !== "object" || !isValidOpfsStorageScope(descriptor.scope as OpfsStorageScope) || typeof descriptor.generation !== "number" || !Number.isSafeInteger(descriptor.generation) || descriptor.generation < 1 || typeof descriptor.byteLength !== "number" || !Number.isSafeInteger(descriptor.byteLength) || descriptor.byteLength < 0 || typeof descriptor.mediaType !== "string" || descriptor.mediaType.length < 1 || typeof descriptor.createdAtEpochMs !== "number" || !Number.isSafeInteger(descriptor.createdAtEpochMs) || descriptor.createdAtEpochMs < 0 || !integrity || integrity.algorithm !== "SHA-256-TREE-V1" || typeof integrity.rootDigestHex !== "string" || !SHA256_HEX.test(integrity.rootDigestHex) || typeof integrity.chunkSizeBytes !== "number" || !Number.isSafeInteger(integrity.chunkSizeBytes) || integrity.chunkSizeBytes < 1 || !descriptor.storagePolicy || typeof descriptor.storagePolicy !== "object" ) { return false; } try { assertValidStoragePolicy( descriptor.storagePolicy as OpfsPreparedObject["descriptor"]["storagePolicy"], ); } catch { return false; } if ( (descriptor.scope as OpfsStorageScope).namespace !== ( descriptor.storagePolicy as OpfsPreparedObject["descriptor"]["storagePolicy"] ).namespace ) { return false; } let totalBytes = 0; for (let index = 0; index < value.chunks.length; index += 1) { const chunk = value.chunks[index] as unknown; if ( !chunk || typeof chunk !== "object" || !("sequence" in chunk) || chunk.sequence !== index || !("byteLength" in chunk) || typeof chunk.byteLength !== "number" || !Number.isSafeInteger(chunk.byteLength) || chunk.byteLength < 1 || chunk.byteLength > integrity.chunkSizeBytes || !("digestHex" in chunk) || typeof chunk.digestHex !== "string" || !SHA256_HEX.test(chunk.digestHex) ) { return false; } if ( index < value.chunks.length - 1 && chunk.byteLength !== integrity.chunkSizeBytes ) { return false; } totalBytes += chunk.byteLength; } return ( totalBytes === descriptor.byteLength && value.chunks.length === Math.ceil(descriptor.byteLength / integrity.chunkSizeBytes) ); } function stableJson(value: unknown): string { if ( value === null || typeof value === "string" || typeof value === "boolean" || typeof value === "number" ) { return JSON.stringify(value); } if (Array.isArray(value)) { return `[${value.map(stableJson).join(",")}]`; } if (typeof value === "object") { const record = value as Record; return `{${Object.keys(record) .sort() .map((key) => `${JSON.stringify(key)}:${stableJson(record[key])}`) .join(",")}}`; } throw new TypeError("Journal value is not JSON-safe."); } function conflict( operation: BrowserDataOperation, ): BrowserDataResult { return browserDataFailure("CONFLICT", operation, { recovery: "REOPEN", }); } function corrupt( operation: BrowserDataOperation, ): BrowserDataResult { return browserDataFailure("CORRUPT_DATA", operation, { recovery: "READ_ONLY", }); } function policyRejected( operation: BrowserDataOperation, ): BrowserDataResult { return browserDataFailure("POLICY_REJECTED", operation, { recovery: "READ_ONLY", }); } function limitExceeded( operation: BrowserDataOperation, ): BrowserDataResult { return browserDataFailure("LIMIT_EXCEEDED", operation, { recovery: "EXPORT_REQUIRED", }); }