Files
tech-log-frontend/src/adapters/storage/opfs/indexeddb-opfs-journal.ts
T
DongHyeonkaandClaude Opus 5 4bff9ca151 chore: sync the frontend template from 4dc033c to 8157ad4
The product was materialized from the template at `4dc033c` and has stayed
on it through 43 template commits, so it was missing all three rounds of
adapter remediation — including files it never had, such as the shared
`abortable-operation` primitive and the `exact-snapshot` decoder that
later fixes are written against. Taking only the newest round was not
possible for that reason: the delta is coherent only as a whole.

The product had not touched `src/adapters` at all since materialization,
so the 140-file delta applied with a three-way merge and no conflicts.
`package.json` was the single overlap and merged cleanly: the product owns
`name`, the template contributed `check:adapter-inventory`,
`check:remediation-ledger` and the image-resolve-signal type fixture.
All 24 product-owned files — README, index.html, CI workflow, i18n
catalog, home page, generated schemas, evidence scripts, component and
visual snapshots — are byte-identical to `main`.

`template.lock.json` now pins the synced revision and tree.

Verified in this repository, not inherited from the template: six type
projects, lint, nine gates (adapter inventory, remediation ledger,
registries, diagnostics, realtime boundaries, architecture, browser
file/storage boundaries, optional recipes, documentation), the production
build, and 2,054 of 2,073 tests. The 19 failures are all in
`tests/unit/ci-artifact-contract.test.ts` and are the same pre-existing
sandbox RLIMIT, EMFILE, umask and `/tmp` permission behaviour the template
records; four suites that failed once under parallel load pass in
isolation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 12:04:58 +09:00

1818 lines
56 KiB
TypeScript

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<typeof IDBKeyRange, "bound">;
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<Value> = Readonly<{
succeed(value: Value): void;
fail(result: BrowserDataResult<never>): 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<typeof globalThis.setTimeout>,
),
});
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<BrowserDataResult<IDBDatabase>> | 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<BrowserDataResult<OpfsJournalTransaction>> {
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<OpfsJournalTransaction>,
store: IDBObjectStore,
) => void,
): Promise<BrowserDataResult<OpfsJournalTransaction>> {
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<Value>(
operation: BrowserDataOperation,
task: (db: IDBDatabase) => Promise<BrowserDataResult<Value>>,
): Promise<BrowserDataResult<Value>> {
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<BrowserDataResult<IDBDatabase>> {
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<BrowserDataResult<IDBDatabase>>(
(resolve) => {
let settled = false;
let blockedTimer: unknown;
const settle = (
result: BrowserDataResult<IDBDatabase>,
): 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<unknown>,
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<Value>(
database: IDBDatabase,
storeNames: readonly string[],
mode: IDBTransactionMode,
operation: BrowserDataOperation,
run: (
transaction: IDBTransaction,
context: TransactionContext<Value>,
) => void,
): Promise<BrowserDataResult<Value>> {
return new Promise((resolve) => {
let value: Value | undefined;
let hasValue = false;
let explicitFailure: BrowserDataResult<never> | 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<Value>): void => {
if (settled) return;
settled = true;
resolve(result);
};
const context: TransactionContext<Value> = {
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<string, number>,
context: TransactionContext<OpfsJournalTransaction>,
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<string, number> {
const deltas = new Map<string, number>();
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,
);
}
/**
* STO-01 expand phase. v1 prepared objects stay readable through the rollback
* window; v2 additionally carries a transaction-unique physical fencing token.
*/
function isSupportedPhysicalSchema(value: object): boolean {
const record = value as Record<string, unknown>;
if (record.physicalSchemaVersion === 1) return true;
return (
record.physicalSchemaVersion === 2 &&
typeof record.physicalGenerationId === "string" &&
/^[0-9a-f]{32}$/u.test(record.physicalGenerationId)
);
}
function isPreparedObject(value: unknown): value is OpfsPreparedObject {
if (
!value ||
typeof value !== "object" ||
!("physicalSchemaVersion" in value) ||
!isSupportedPhysicalSchema(value) ||
!("descriptor" in value) ||
!value.descriptor ||
typeof value.descriptor !== "object" ||
!("chunks" in value) ||
!Array.isArray(value.chunks)
) {
return false;
}
const descriptor = value.descriptor as Record<string, unknown>;
const integrity =
descriptor.integrity && typeof descriptor.integrity === "object"
? (descriptor.integrity as Record<string, unknown>)
: 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<string, unknown>;
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<never> {
return browserDataFailure("CONFLICT", operation, {
recovery: "REOPEN",
});
}
function corrupt(
operation: BrowserDataOperation,
): BrowserDataResult<never> {
return browserDataFailure("CORRUPT_DATA", operation, {
recovery: "READ_ONLY",
});
}
function policyRejected(
operation: BrowserDataOperation,
): BrowserDataResult<never> {
return browserDataFailure("POLICY_REJECTED", operation, {
recovery: "READ_ONLY",
});
}
function limitExceeded(
operation: BrowserDataOperation,
): BrowserDataResult<never> {
return browserDataFailure("LIMIT_EXCEEDED", operation, {
recovery: "EXPORT_REQUIRED",
});
}