import type { DurableObjectDescriptor, DurableObjectMaintenancePort, DurableObjectStorePort, OpfsJournalPort, OpfsJournalTransaction, OpfsPolicyMaintenanceReport, OpfsPreparedObject, OpfsSensitiveMaintenanceReason, OpfsReconciliationReport, OpfsStorageScope, PutDurableObjectRequest, OpfsPhysicalGenerationId, } from "../../../application/ports/browser-file-storage/opfs-ports.ts"; import { type BrowserDataFailure, type BrowserDataOperation, type BrowserDataResult, type BrowserStoragePolicy, } from "../../../application/ports/browser-file-storage/shared.ts"; import { abortedResult, browserDataFailure, browserDataSuccess, } from "../../browser-file-storage/result.ts"; import { byteBucket, observeOpfsSafely, resolveOpfsRuntimePolicy, transactionBucket, snapshotOpfsStoragePolicy, snapshotOpfsStorageScope, validateObjectWriteInput, type OpfsRuntimePolicy, type OpfsSafeObserver, } from "./opfs-policy.ts"; import type { OpfsWorkerGateway } from "./opfs-worker-protocol.ts"; type BrowserFailureResult = Readonly<{ ok: false; error: BrowserDataFailure; }>; export type OpfsByteStore = Readonly<{ objects: DurableObjectStorePort; maintenance: DurableObjectMaintenancePort; }>; export type OpfsMaintenanceAuthorityRequest = Readonly<{ reason: OpfsSensitiveMaintenanceReason; scope: OpfsStorageScope; storagePolicy: BrowserStoragePolicy; signal?: AbortSignal; }>; export type OpfsMaintenanceAuthorityDecision = | Readonly<{ authorized: false }> | Readonly<{ authorized: true; /** * Opaque, short-lived proof issued for this exact action and scope. * It is passed directly to the composition-owned consumer and is never * persisted or returned through the application port. */ proofToken: string; expiresAtEpochMs: number; }>; export type OpfsMaintenanceAuthorityProvider = ( request: OpfsMaintenanceAuthorityRequest, ) => | OpfsMaintenanceAuthorityDecision | Promise; export type OpfsMaintenanceAuthorityConsumer = ( request: Readonly<{ reason: OpfsSensitiveMaintenanceReason; proofToken: string; expiresAtEpochMs: number; scope: OpfsStorageScope; storagePolicy: BrowserStoragePolicy; signal?: AbortSignal; }>, ) => boolean | Promise; export type OpfsByteStoreDependencies = Readonly<{ journal: OpfsJournalPort; worker: OpfsWorkerGateway; scope: OpfsStorageScope; storagePolicy: BrowserStoragePolicy; policy?: Partial; createTransactionId?: () => string; createPhysicalGenerationId?: () => OpfsPhysicalGenerationId; /** * Composition-owned bounded signal for compensating cleanup. It is * deliberately separate from any caller signal. */ compensationSignal?: AbortSignal; now?: () => number; observer?: OpfsSafeObserver; /** * Obtains a new proof for every sensitive maintenance invocation. The * provider must bind it to the exact reason, scope and policy. */ requestMaintenanceAuthority?: OpfsMaintenanceAuthorityProvider; /** * Atomically validates and consumes the issued proof. It must reject replay, * scope/action/policy mismatch and expiry. The runtime also performs shape * and short-expiry checks before invoking it. */ consumeMaintenanceAuthority?: OpfsMaintenanceAuthorityConsumer; }>; export function createOpfsByteStoreAdapter( inputDependencies: OpfsByteStoreDependencies, ): OpfsByteStore { const policy = resolveOpfsRuntimePolicy(inputDependencies.policy); const scope = snapshotOpfsStorageScope(inputDependencies.scope); const storagePolicy = snapshotOpfsStoragePolicy( inputDependencies.storagePolicy, ); if ( storagePolicy.namespace !== scope.namespace ) { throw new TypeError( "OPFS storage policy must match the bound scope.", ); } const dependencies: OpfsByteStoreDependencies = Object.freeze({ ...inputDependencies, journal: snapshotOpfsJournal(inputDependencies.journal), worker: snapshotOpfsWorker(inputDependencies.worker), scope, storagePolicy, policy, }); const createTransactionId = dependencies.createTransactionId ?? (() => globalThis.crypto.randomUUID()); const createPhysicalGenerationId = dependencies.createPhysicalGenerationId ?? (() => { const bytes = new Uint8Array(16); globalThis.crypto.getRandomValues(bytes); let hex = ""; for (const byte of bytes) hex += byte.toString(16).padStart(2, "0"); return hex as OpfsPhysicalGenerationId; }); /** * STO-01. Compensation must not inherit the caller's already aborted signal; * a cleanup that never starts cannot justify releasing the journal row. */ const compensationSignal = dependencies.compensationSignal; const now = dependencies.now ?? Date.now; const objects: DurableObjectStorePort = Object.freeze({ async capabilities() { return await dependencies.worker.capabilities(); }, async put(sourceRequest: PutDurableObjectRequest) { const requestSnapshot = snapshotPutRequest(sourceRequest); if (!requestSnapshot.ok) return requestSnapshot; const request = requestSnapshot.value; const earlyFailure = validatePut( request, policy, dependencies.scope, dependencies.storagePolicy, ); if (earlyFailure) return earlyFailure; observeOpfsSafely(dependencies.observer, { operation: "OBJECT_WRITE", outcome: "STARTED", byteBucket: byteBucket(request.source.byteLength!), }); notifyProgress(request, "VALIDATING", 0); const currentResult = await dependencies.journal.getCommittedObject( dependencies.scope, request.objectId, ); if (!currentResult.ok) { return observeFailure( rebaseFailure(currentResult.error, "OBJECT_WRITE"), dependencies.observer, request.source.byteLength!, ); } const current = currentResult.value; if ( (current === null && request.expectedGeneration !== null) || (current !== null && request.expectedGeneration !== current.descriptor.generation) ) { return observeFailure( browserDataFailure("CONFLICT", "OBJECT_WRITE", { recovery: "REOPEN", }), dependencies.observer, request.source.byteLength!, ); } const targetGeneration = (current?.descriptor.generation ?? 0) + 1; const transactionId = createTransactionId(); // STO-01. The logical generation is reused across transactions; this // token makes the physical target unique so a late compensation can never // delete a newer transaction's directory. const physicalGenerationId = createPhysicalGenerationId(); notifyProgress(request, "PREPARING", 0); const begun = await dependencies.journal.begin({ transactionId, mutation: "PUT", scope: dependencies.scope, objectId: request.objectId, expectedGeneration: request.expectedGeneration, targetGeneration, targetByteLength: request.source.byteLength!, targetStoragePolicy: dependencies.storagePolicy, startedAtEpochMs: now(), }); if (!begun.ok) { return observeFailure( rebaseFailure(begun.error, "OBJECT_WRITE"), dependencies.observer, request.source.byteLength!, ); } const descriptor: Omit = Object.freeze({ objectId: request.objectId, scope: dependencies.scope, generation: targetGeneration, byteLength: request.source.byteLength!, mediaType: request.mediaType, createdAtEpochMs: now(), storagePolicy: dependencies.storagePolicy, }); const prepared = await dependencies.worker.preparePut({ transactionId, physicalGenerationId, descriptor, source: request.source, signal: request.signal, onProgress: request.onProgress, }); if (!prepared.ok) { const compensated = await compensatePreparedPut( begun.value, physicalGenerationId, ); if (!compensated.ok) { return observeFailure( compensated, dependencies.observer, request.source.byteLength!, ); } return observeFailure( prepared, dependencies.observer, request.source.byteLength!, ); } const filesReady = await dependencies.journal.markFilesReady( transactionId, begun.value.fencingToken, prepared.value, ); if (!filesReady.ok) { const compensated = await compensatePreparedPut( begun.value, physicalGenerationId, ); if (!compensated.ok) { return observeFailure( compensated, dependencies.observer, request.source.byteLength!, ); } return observeFailure( rebaseFailure(filesReady.error, "OBJECT_WRITE"), dependencies.observer, request.source.byteLength!, ); } const committed = await dependencies.journal.commitPut( transactionId, begun.value.fencingToken, ); if (!committed.ok) { // The commit response can be lost after IndexedDB commits. Reconciliation // decides from the durable journal; rolling back here would be unsafe. return observeFailure( rebaseFailure(committed.error, "OBJECT_WRITE"), dependencies.observer, request.source.byteLength!, ); } notifyProgress( request, "FINALIZING", prepared.value.descriptor.byteLength, ); const finalized = await dependencies.worker.finalizePut( transactionId, prepared.value, request.signal, ); if (!finalized.ok) { // STO-RR-01. The commit fence already passed, so the payload is durable // and the journal keeps its COMMITTED record for reconciliation to // settle. What did not happen is finalization: the previous generation // and the staging directory are still present. Reporting a plain // success here would claim a settled state nobody observed, so the // worker's own failure is surfaced and the record is left recoverable. return observeFailure( rebaseFailure(finalized.error, "OBJECT_WRITE"), dependencies.observer, request.source.byteLength!, ); } await dependencies.journal.complete( transactionId, begun.value.fencingToken, ); observeOpfsSafely(dependencies.observer, { operation: "OBJECT_WRITE", outcome: "SUCCEEDED", byteBucket: byteBucket(prepared.value.descriptor.byteLength), }); return browserDataSuccess(prepared.value.descriptor); }, async open( sourceRequest: Parameters[0], ) { const requestSnapshot = snapshotOpenRequest(sourceRequest); if (!requestSnapshot.ok) return requestSnapshot; const request = requestSnapshot.value; const aborted = abortedResult(request.signal, "OBJECT_READ"); if (aborted) return aborted; if ( !policy.isObjectIdAllowed(request.objectId) || (request.generation !== undefined && (!Number.isSafeInteger(request.generation) || request.generation < 1)) ) { return browserDataFailure("INVALID_INPUT", "OBJECT_READ"); } observeOpfsSafely(dependencies.observer, { operation: "OBJECT_READ", outcome: "STARTED", }); const committed = await dependencies.journal.getCommittedObject( dependencies.scope, request.objectId, ); if (!committed.ok) { return observeFailure( rebaseFailure(committed.error, "OBJECT_READ"), dependencies.observer, ); } if ( !committed.value || (request.generation !== undefined && request.generation !== committed.value.descriptor.generation) ) { return observeFailure( browserDataFailure("NOT_FOUND", "OBJECT_READ", { recovery: "REHYDRATE", }), dependencies.observer, ); } if (isExpired(committed.value.descriptor, now())) { await objects.remove({ objectId: request.objectId, expectedGeneration: committed.value.descriptor.generation, signal: request.signal, }); return observeFailure( browserDataFailure("EXPIRED_RESOURCE", "OBJECT_READ", { recovery: "REHYDRATE", }), dependencies.observer, committed.value.descriptor.byteLength, ); } const opened = await dependencies.worker.openObject( committed.value, request.signal, ); if (!opened.ok) { return observeFailure( opened, dependencies.observer, committed.value.descriptor.byteLength, ); } observeOpfsSafely(dependencies.observer, { operation: "OBJECT_READ", outcome: "SUCCEEDED", byteBucket: byteBucket(committed.value.descriptor.byteLength), }); return browserDataSuccess({ descriptor: committed.value.descriptor, source: opened.value, }); }, async remove( sourceRequest: Parameters[0], ) { const requestSnapshot = snapshotRemoveRequest(sourceRequest); if (!requestSnapshot.ok) return requestSnapshot; const request = requestSnapshot.value; const aborted = abortedResult(request.signal, "OBJECT_DELETE"); if (aborted) return aborted; if ( !policy.isObjectIdAllowed(request.objectId) || !Number.isSafeInteger(request.expectedGeneration) || request.expectedGeneration < 1 ) { return browserDataFailure("INVALID_INPUT", "OBJECT_DELETE"); } observeOpfsSafely(dependencies.observer, { operation: "OBJECT_DELETE", outcome: "STARTED", }); const current = await dependencies.journal.getCommittedObject( dependencies.scope, request.objectId, ); if (!current.ok) { return observeFailure( rebaseFailure(current.error, "OBJECT_DELETE"), dependencies.observer, ); } if (!current.value) { return observeFailure( browserDataFailure("NOT_FOUND", "OBJECT_DELETE"), dependencies.observer, ); } if ( current.value.descriptor.generation !== request.expectedGeneration ) { return observeFailure( browserDataFailure("CONFLICT", "OBJECT_DELETE", { recovery: "REOPEN", }), dependencies.observer, ); } const transactionId = createTransactionId(); const begun = await dependencies.journal.begin({ transactionId, mutation: "DELETE", scope: dependencies.scope, objectId: request.objectId, expectedGeneration: request.expectedGeneration, targetGeneration: request.expectedGeneration + 1, targetByteLength: 0, targetStoragePolicy: current.value.descriptor.storagePolicy, startedAtEpochMs: now(), }); if (!begun.ok) { return observeFailure( rebaseFailure(begun.error, "OBJECT_DELETE"), dependencies.observer, ); } const committed = await dependencies.journal.commitDelete( transactionId, begun.value.fencingToken, ); if (!committed.ok) { return observeFailure( rebaseFailure(committed.error, "OBJECT_DELETE"), dependencies.observer, ); } const removed = await dependencies.worker.removeObject( dependencies.scope, request.objectId, request.expectedGeneration, request.signal, ); if (removed.ok) { await dependencies.journal.complete( transactionId, begun.value.fencingToken, ); } // Logical deletion is already committed. Physical cleanup is retryable // maintenance and must not make the caller repeat a non-idempotent delete. observeOpfsSafely(dependencies.observer, { operation: "OBJECT_DELETE", outcome: "SUCCEEDED", }); return browserDataSuccess(undefined); }, }); const maintenance: DurableObjectMaintenancePort = Object.freeze({ async reconcile( sourceRequest: NonNullable< Parameters[0] > = {}, ) { const requestSnapshot = snapshotReconciliationRequest(sourceRequest); if (!requestSnapshot.ok) return requestSnapshot; const request = requestSnapshot.value; const aborted = abortedResult(request.signal, "OBJECT_RECONCILE"); if (aborted) return aborted; if ( !isValidOptionalPositiveInteger(request.budgetMs) || !isValidOptionalPositiveInteger(request.maxTransactions) ) { return browserDataFailure("INVALID_INPUT", "OBJECT_RECONCILE"); } if ( exceedsConfiguredCeiling( request.budgetMs, policy.reconciliationBudgetMs, ) || exceedsConfiguredCeiling( request.maxTransactions, policy.reconciliationBatchSize, ) ) { return browserDataFailure("LIMIT_EXCEEDED", "OBJECT_RECONCILE"); } const budgetMs = request.budgetMs ?? policy.reconciliationBudgetMs; const maxTransactions = request.maxTransactions ?? policy.reconciliationBatchSize; const deadline = now() + budgetMs; const page = await dependencies.journal.listIncomplete(maxTransactions); if (!page.ok) { return rebaseFailure(page.error, "OBJECT_RECONCILE"); } observeOpfsSafely(dependencies.observer, { operation: "OBJECT_RECONCILE", outcome: "STARTED", transactionBucket: transactionBucket(page.value.transactions.length), }); const report: { inspectedTransactions: number; committedTransactions: number; rolledBackTransactions: number; cleanedTransactions: number; inspectedOrphanChunks: number; deletedOrphanChunks: number; orphanGcStatus: | "COMPLETED" | "DEADLINE_REACHED" | "STAGING_STATE_UNREADABLE"; moreTransactionsAvailable: boolean; deadlineReached: boolean; } = { inspectedTransactions: 0, committedTransactions: 0, rolledBackTransactions: 0, cleanedTransactions: 0, inspectedOrphanChunks: 0, deletedOrphanChunks: 0, orphanGcStatus: "COMPLETED", moreTransactionsAvailable: page.value.moreAvailable, deadlineReached: false, }; for (const transaction of page.value.transactions) { if (request.signal?.aborted) { return browserDataFailure("ABORTED", "OBJECT_RECONCILE"); } if (now() >= deadline) { report.deadlineReached = true; report.moreTransactionsAvailable = true; break; } report.inspectedTransactions += 1; const reconciled = await reconcileTransaction(transaction, request.signal); if (!reconciled.ok) { return observeFailure(reconciled, dependencies.observer); } report.committedTransactions += reconciled.value.committed; report.rolledBackTransactions += reconciled.value.rolledBack; report.cleanedTransactions += reconciled.value.cleaned; } if (now() >= deadline) { report.deadlineReached = true; report.orphanGcStatus = "DEADLINE_REACHED"; } else { const gc = await reconcileOrphanChunks( deadline, request.signal, ); if (!gc.ok) return observeFailure(gc, dependencies.observer); report.inspectedOrphanChunks = gc.value.inspected; report.deletedOrphanChunks = gc.value.deleted; report.orphanGcStatus = gc.value.status; if (gc.value.status === "DEADLINE_REACHED") { report.deadlineReached = true; } } observeOpfsSafely(dependencies.observer, { operation: "OBJECT_RECONCILE", outcome: "SUCCEEDED", transactionBucket: transactionBucket(report.inspectedTransactions), }); return browserDataSuccess( Object.freeze({ ...report }), ); }, async enforcePolicies( sourceRequest: Parameters< DurableObjectMaintenancePort["enforcePolicies"] >[0], ) { const requestSnapshot = snapshotPolicyMaintenanceRequest( sourceRequest, ); if (!requestSnapshot.ok) return requestSnapshot; const request = requestSnapshot.value; const aborted = abortedResult(request.signal, "OBJECT_RECONCILE"); if (aborted) return aborted; if ( !isValidOptionalPositiveInteger(request.budgetMs) || !isValidOptionalPositiveInteger(request.maxObjects) || (request.reason === "PRESSURE" && (!Number.isSafeInteger(request.targetBytesToRelease) || request.targetBytesToRelease < 1)) ) { return browserDataFailure("INVALID_INPUT", "OBJECT_RECONCILE"); } if ( exceedsConfiguredCeiling( request.budgetMs, policy.reconciliationBudgetMs, ) || exceedsConfiguredCeiling( request.maxObjects, policy.reconciliationBatchSize, ) ) { return browserDataFailure("LIMIT_EXCEEDED", "OBJECT_RECONCILE"); } if ( !maintenanceReasonAllowed( request.reason, dependencies.storagePolicy, ) ) { return browserDataFailure( "POLICY_REJECTED", "OBJECT_RECONCILE", { recovery: dependencies.storagePolicy.unavailableFallback }, ); } const budgetMs = request.budgetMs ?? policy.reconciliationBudgetMs; const maxObjects = request.maxObjects ?? policy.reconciliationBatchSize; const deadline = now() + budgetMs; const authorized = await authorizePolicyMaintenance(request); if (!authorized.ok) return authorized; if (request.signal?.aborted) { return browserDataFailure("ABORTED", "OBJECT_RECONCILE"); } if (now() >= deadline) { // Authority acquisition is part of the maintenance budget. No journal // read is allowed after the deadline, so remaining work is reported // conservatively instead of implying that the partition was scanned. return browserDataSuccess( Object.freeze({ inspectedObjects: 0, removedObjects: 0, releasedBytes: 0, moreObjectsAvailable: true, deadlineReached: true, }), ); } let afterObjectId: string | undefined; let inspectedObjects = 0; let removedObjects = 0; let releasedBytes = 0; let moreObjectsAvailable = false; let deadlineReached = false; while ( inspectedObjects < maxObjects && now() < deadline ) { const page = await dependencies.journal.listCommittedObjects({ scope: dependencies.scope, afterObjectId, limit: Math.min( policy.reconciliationBatchSize, maxObjects - inspectedObjects, ), }); if (!page.ok) { return rebaseFailure(page.error, "OBJECT_RECONCILE"); } const candidates = [...page.value.objects].sort( policyMaintenanceOrder, ); for (const candidate of candidates) { if ( request.signal?.aborted || now() >= deadline || inspectedObjects >= maxObjects ) { deadlineReached = now() >= deadline; moreObjectsAvailable = true; break; } inspectedObjects += 1; if (!shouldRemoveForPolicy(candidate, request, now())) continue; const removed = await objects.remove({ objectId: candidate.descriptor.objectId, expectedGeneration: candidate.descriptor.generation, signal: request.signal, }); if (!removed.ok) { if (removed.error.code === "CONFLICT" || removed.error.code === "NOT_FOUND") { continue; } return rebaseFailure(removed.error, "OBJECT_RECONCILE"); } removedObjects += 1; releasedBytes += candidate.descriptor.byteLength; if ( request.reason === "PRESSURE" && releasedBytes >= request.targetBytesToRelease ) { moreObjectsAvailable = page.value.moreAvailable; break; } } if ( deadlineReached || (request.reason === "PRESSURE" && releasedBytes >= request.targetBytesToRelease) || !page.value.moreAvailable || !page.value.nextObjectId ) { moreObjectsAvailable ||= page.value.moreAvailable; break; } afterObjectId = page.value.nextObjectId; moreObjectsAvailable = page.value.moreAvailable; } const report: OpfsPolicyMaintenanceReport = Object.freeze({ inspectedObjects, removedObjects, releasedBytes, moreObjectsAvailable, deadlineReached, }); return browserDataSuccess(report); }, }); async function authorizePolicyMaintenance( request: Parameters< DurableObjectMaintenancePort["enforcePolicies"] >[0], ): Promise> { if (!requiresMaintenanceAuthority(request)) { return browserDataSuccess(undefined); } const requestAuthority = dependencies.requestMaintenanceAuthority; const consumeAuthority = dependencies.consumeMaintenanceAuthority; if (!requestAuthority || !consumeAuthority) { return browserDataFailure( "POLICY_REJECTED", "OBJECT_RECONCILE", { recovery: "READ_ONLY" }, ); } let decision: OpfsMaintenanceAuthorityDecision; try { decision = await requestAuthority( Object.freeze({ reason: request.reason, scope: dependencies.scope, storagePolicy: dependencies.storagePolicy, ...(request.signal ? { signal: request.signal } : {}), }), ); } catch { return browserDataFailure( "POLICY_REJECTED", "OBJECT_RECONCILE", { recovery: "READ_ONLY" }, ); } if (request.signal?.aborted) { return browserDataFailure("ABORTED", "OBJECT_RECONCILE"); } if ( !decision || decision.authorized !== true || typeof decision.proofToken !== "string" || !OPAQUE_AUTHORITY_PROOF.test(decision.proofToken) || !Number.isSafeInteger(decision.expiresAtEpochMs) ) { return browserDataFailure( "POLICY_REJECTED", "OBJECT_RECONCILE", { recovery: "READ_ONLY" }, ); } let authorizationEpochMs: number; try { authorizationEpochMs = now(); } catch { return browserDataFailure( "UNAVAILABLE", "OBJECT_RECONCILE", { retryable: true, recovery: "RETRY" }, ); } if ( !Number.isSafeInteger(authorizationEpochMs) || authorizationEpochMs < 0 || decision.expiresAtEpochMs <= authorizationEpochMs || decision.expiresAtEpochMs - authorizationEpochMs > MAX_AUTHORITY_PROOF_LIFETIME_MS ) { return browserDataFailure( "POLICY_REJECTED", "OBJECT_RECONCILE", { recovery: "READ_ONLY" }, ); } const proofToken = decision.proofToken; const expiresAtEpochMs = decision.expiresAtEpochMs; let consumed: boolean; try { consumed = await consumeAuthority( Object.freeze({ reason: request.reason, proofToken, expiresAtEpochMs, scope: dependencies.scope, storagePolicy: dependencies.storagePolicy, ...(request.signal ? { signal: request.signal } : {}), }), ); } catch { return browserDataFailure( "POLICY_REJECTED", "OBJECT_RECONCILE", { recovery: "READ_ONLY" }, ); } // The proof is intentionally not retained after this invocation. The // composition consumer owns atomic replay prevention across runtimes. if (request.signal?.aborted) { return browserDataFailure("ABORTED", "OBJECT_RECONCILE"); } return consumed ? browserDataSuccess(undefined) : browserDataFailure( "POLICY_REJECTED", "OBJECT_RECONCILE", { recovery: "READ_ONLY" }, ); } return Object.freeze({ objects, maintenance }); /** * STO-01. The compensating half of the put saga. * * The journal row and its budget reservation are the only durable evidence * that a physical staging generation may still exist, so they are released * exactly when the physical effect is confirmed `CLEANED` or * `ALREADY_CLEAN`. A timeout, crash, malformed response or `EFFECT_UNKNOWN` * keeps `PREPARING`/`FILES_READY` in place and asks for reconciliation. */ async function compensatePreparedPut( transaction: OpfsJournalTransaction, physicalGenerationId: OpfsPhysicalGenerationId, ): Promise> { const cleanup = await dependencies.worker.abortPreparedPut({ scope: transaction.scope, transactionId: transaction.transactionId, physicalGenerationId, ...(compensationSignal ? { signal: compensationSignal } : {}), }); if (!cleanup.ok || cleanup.value.kind === "EFFECT_UNKNOWN") { return browserDataFailure("CONFLICT", "OBJECT_RECONCILE", { recovery: "RETRY", }); } const rolledBack = await dependencies.journal.rollback( transaction.transactionId, transaction.fencingToken, ); if (!rolledBack.ok) { return rebaseFailure(rolledBack.error, "OBJECT_RECONCILE"); } return browserDataSuccess(undefined); } async function reconcileTransaction( transaction: OpfsJournalTransaction, signal: AbortSignal | undefined, ): Promise< BrowserDataResult< Readonly<{ committed: number; rolledBack: number; cleaned: number }> > > { if (transaction.phase === "PREPARING") { const cleaned = await dependencies.worker.cleanupTransaction( transaction.scope, transaction.transactionId, signal, ); if (!cleaned.ok) return cleaned; if (cleaned.value.kind === "EFFECT_UNKNOWN") { return browserDataFailure("CONFLICT", "OBJECT_RECONCILE", { recovery: "RETRY", }); } const rolledBack = await dependencies.journal.rollback( transaction.transactionId, transaction.fencingToken, ); if (!rolledBack.ok) { return rebaseFailure(rolledBack.error, "OBJECT_RECONCILE"); } return browserDataSuccess({ committed: 0, rolledBack: 1, cleaned: 1 }); } if (transaction.phase === "FILES_READY") { if ( transaction.mutation !== "PUT" || !transaction.preparedObject ) { return browserDataFailure("CORRUPT_DATA", "OBJECT_RECONCILE", { recovery: "READ_ONLY", }); } const verified = await dependencies.worker.verifyObject( transaction.preparedObject, signal, ); if (!verified.ok) return verified; if (!verified.value) { const cleaned = await dependencies.worker.cleanupTransaction( transaction.scope, transaction.transactionId, signal, ); if (!cleaned.ok) return cleaned; const rolledBack = await dependencies.journal.rollback( transaction.transactionId, transaction.fencingToken, ); if (!rolledBack.ok) { return rebaseFailure(rolledBack.error, "OBJECT_RECONCILE"); } return browserDataSuccess({ committed: 0, rolledBack: 1, cleaned: 1, }); } const committed = await dependencies.journal.commitPut( transaction.transactionId, transaction.fencingToken, ); if (!committed.ok) { return rebaseFailure(committed.error, "OBJECT_RECONCILE"); } const finalized = await dependencies.worker.finalizePut( transaction.transactionId, transaction.preparedObject, signal, ); if (!finalized.ok) return finalized; const completed = await dependencies.journal.complete( transaction.transactionId, transaction.fencingToken, ); if (!completed.ok) { return rebaseFailure(completed.error, "OBJECT_RECONCILE"); } return browserDataSuccess({ committed: 1, rolledBack: 0, cleaned: 1 }); } if (transaction.phase === "COMMITTED") { if (transaction.mutation === "PUT") { if (!transaction.preparedObject) { return browserDataFailure("CORRUPT_DATA", "OBJECT_RECONCILE", { recovery: "READ_ONLY", }); } const finalized = await dependencies.worker.finalizePut( transaction.transactionId, transaction.preparedObject, signal, ); if (!finalized.ok) return finalized; } else { const removed = await dependencies.worker.removeObject( transaction.scope, transaction.objectId, Math.max(1, transaction.targetGeneration - 1), signal, ); if (!removed.ok) return removed; } const completed = await dependencies.journal.complete( transaction.transactionId, transaction.fencingToken, ); if (!completed.ok) { return rebaseFailure(completed.error, "OBJECT_RECONCILE"); } return browserDataSuccess({ committed: 0, rolledBack: 0, cleaned: 1 }); } return browserDataFailure("CORRUPT_DATA", "OBJECT_RECONCILE", { recovery: "READ_ONLY", }); } async function reconcileOrphanChunks( deadline: number, signal: AbortSignal | undefined, ): Promise< BrowserDataResult< Readonly<{ inspected: number; deleted: number; status: | "COMPLETED" | "DEADLINE_REACHED" | "STAGING_STATE_UNREADABLE"; }> > > { const olderThanEpochMs = Math.max( 0, now() - policy.orphanGracePeriodMs, ); const candidates = await dependencies.worker.listOrphanCandidates( dependencies.scope, olderThanEpochMs, policy.orphanGcBatchSize, signal, ); if (!candidates.ok) return candidates; if (!candidates.value.safeToSweep) { return browserDataSuccess({ inspected: 0, deleted: 0, status: "STAGING_STATE_UNREADABLE", }); } let inspected = 0; let deleted = 0; for (const digestHex of candidates.value.digests) { if (signal?.aborted) { return browserDataFailure("ABORTED", "OBJECT_RECONCILE"); } if (now() >= deadline) { return browserDataSuccess({ inspected, deleted, status: "DEADLINE_REACHED", }); } inspected += 1; const referenced = await dependencies.journal.isChunkReferenced( dependencies.scope, digestHex, ); if (!referenced.ok) { return rebaseFailure(referenced.error, "OBJECT_RECONCILE"); } if (referenced.value) continue; const removal = await dependencies.worker.deleteOrphanChunk( dependencies.scope, digestHex, olderThanEpochMs, signal, ); if (!removal.ok) return removal; if (removal.value.skippedUnsafe) { return browserDataSuccess({ inspected, deleted, status: "STAGING_STATE_UNREADABLE", }); } if (removal.value.deleted) deleted += 1; } return browserDataSuccess({ inspected, deleted, status: candidates.value.moreAvailable ? "DEADLINE_REACHED" : "COMPLETED", }); } } function snapshotPutRequest( input: PutDurableObjectRequest, ): BrowserDataResult { try { if (!input || typeof input !== "object") { return browserDataFailure("INVALID_INPUT", "OBJECT_WRITE"); } const sourceInput = input.source; if (!sourceInput || typeof sourceInput !== "object") { return browserDataFailure("INVALID_INPUT", "OBJECT_WRITE"); } const objectId = input.objectId; const expectedGeneration = input.expectedGeneration; const mediaType = input.mediaType; const signal = input.signal; const onProgress = input.onProgress; const byteLength = sourceInput.byteLength; const stream = sourceInput.stream; if ( typeof stream !== "function" || (onProgress !== undefined && typeof onProgress !== "function") ) { return browserDataFailure("INVALID_INPUT", "OBJECT_WRITE"); } const boundStream = stream.bind(sourceInput); const source = Object.freeze({ byteLength, stream(signal: AbortSignal) { return boundStream(signal); }, }); return browserDataSuccess( Object.freeze({ objectId, expectedGeneration, mediaType, source, ...(signal === undefined ? {} : { signal }), ...(onProgress === undefined ? {} : { onProgress }), }), ); } catch { return browserDataFailure("INVALID_INPUT", "OBJECT_WRITE"); } } function snapshotOpenRequest( input: Parameters[0], ): BrowserDataResult< Parameters[0] > { try { if (!input || typeof input !== "object") { return browserDataFailure("INVALID_INPUT", "OBJECT_READ"); } const objectId = input.objectId; const generation = input.generation; const signal = input.signal; return browserDataSuccess( Object.freeze({ objectId, ...(generation === undefined ? {} : { generation }), ...(signal === undefined ? {} : { signal }), }), ); } catch { return browserDataFailure("INVALID_INPUT", "OBJECT_READ"); } } function snapshotRemoveRequest( input: Parameters[0], ): BrowserDataResult< Parameters[0] > { try { if (!input || typeof input !== "object") { return browserDataFailure("INVALID_INPUT", "OBJECT_DELETE"); } const objectId = input.objectId; const expectedGeneration = input.expectedGeneration; const signal = input.signal; return browserDataSuccess( Object.freeze({ objectId, expectedGeneration, ...(signal === undefined ? {} : { signal }), }), ); } catch { return browserDataFailure("INVALID_INPUT", "OBJECT_DELETE"); } } function snapshotReconciliationRequest( input: NonNullable< Parameters[0] >, ): BrowserDataResult< NonNullable< Parameters[0] > > { try { if (!input || typeof input !== "object") { return browserDataFailure("INVALID_INPUT", "OBJECT_RECONCILE"); } const budgetMs = input.budgetMs; const maxTransactions = input.maxTransactions; const signal = input.signal; return browserDataSuccess( Object.freeze({ ...(budgetMs === undefined ? {} : { budgetMs }), ...(maxTransactions === undefined ? {} : { maxTransactions }), ...(signal === undefined ? {} : { signal }), }), ); } catch { return browserDataFailure("INVALID_INPUT", "OBJECT_RECONCILE"); } } function snapshotPolicyMaintenanceRequest( input: Parameters< DurableObjectMaintenancePort["enforcePolicies"] >[0], ): BrowserDataResult< Parameters[0] > { try { if (!input || typeof input !== "object") { return browserDataFailure("INVALID_INPUT", "OBJECT_RECONCILE"); } const reason = input.reason; const budgetMs = input.budgetMs; const maxObjects = input.maxObjects; const signal = input.signal; const common = { ...(budgetMs === undefined ? {} : { budgetMs }), ...(maxObjects === undefined ? {} : { maxObjects }), ...(signal === undefined ? {} : { signal }), }; switch (reason) { case "TTL": case "LOGOUT": case "SESSION_END": case "UNTIL_SYNCED": case "ACCOUNT_DELETION": return browserDataSuccess( Object.freeze({ reason, ...common }), ); case "PRESSURE": { const targetBytesToRelease = input.targetBytesToRelease; return browserDataSuccess( Object.freeze({ reason: "PRESSURE", targetBytesToRelease, ...common, }), ); } default: return browserDataFailure( "INVALID_INPUT", "OBJECT_RECONCILE", ); } } catch { return browserDataFailure("INVALID_INPUT", "OBJECT_RECONCILE"); } } function snapshotOpfsJournal( source: OpfsJournalPort, ): OpfsJournalPort { if (!source || typeof source !== "object") { throw new TypeError("OPFS journal dependency is invalid."); } const { getCommittedObject, begin, markFilesReady, commitPut, commitDelete, complete, rollback, listIncomplete, listCommittedObjects, isChunkReferenced, } = source; if ( [ getCommittedObject, begin, markFilesReady, commitPut, commitDelete, complete, rollback, listIncomplete, listCommittedObjects, isChunkReferenced, ].some((method) => typeof method !== "function") ) { throw new TypeError("OPFS journal dependency is invalid."); } return Object.freeze({ getCommittedObject: getCommittedObject.bind(source), begin: begin.bind(source), markFilesReady: markFilesReady.bind(source), commitPut: commitPut.bind(source), commitDelete: commitDelete.bind(source), complete: complete.bind(source), rollback: rollback.bind(source), listIncomplete: listIncomplete.bind(source), listCommittedObjects: listCommittedObjects.bind(source), isChunkReferenced: isChunkReferenced.bind(source), }); } function snapshotOpfsWorker( source: OpfsWorkerGateway, ): OpfsWorkerGateway { if (!source || typeof source !== "object") { throw new TypeError("OPFS worker dependency is invalid."); } const { capabilities, preparePut, verifyObject, openObject, removeObject, cleanupTransaction, abortPreparedPut, finalizePut, listOrphanCandidates, deleteOrphanChunk, close, } = source; if ( [ capabilities, preparePut, verifyObject, openObject, removeObject, cleanupTransaction, abortPreparedPut, finalizePut, listOrphanCandidates, deleteOrphanChunk, close, ].some((method) => typeof method !== "function") ) { throw new TypeError("OPFS worker dependency is invalid."); } return Object.freeze({ capabilities: capabilities.bind(source), preparePut: preparePut.bind(source), verifyObject: verifyObject.bind(source), openObject: openObject.bind(source), removeObject: removeObject.bind(source), cleanupTransaction: cleanupTransaction.bind(source), abortPreparedPut: abortPreparedPut.bind(source), finalizePut: finalizePut.bind(source), listOrphanCandidates: listOrphanCandidates.bind(source), deleteOrphanChunk: deleteOrphanChunk.bind(source), close: close.bind(source), }); } function validatePut( request: PutDurableObjectRequest, policy: OpfsRuntimePolicy, scope: OpfsStorageScope, storagePolicy: BrowserStoragePolicy, ): BrowserDataResult | null { const aborted = abortedResult(request.signal, "OBJECT_WRITE"); if (aborted) return aborted; if ( !validateObjectWriteInput( { objectId: request.objectId, scope, expectedGeneration: request.expectedGeneration, mediaType: request.mediaType, byteLength: request.source.byteLength, storagePolicy, }, policy, ) ) { return browserDataFailure( request.source.byteLength !== null && request.source.byteLength > policy.maxObjectBytes ? "LIMIT_EXCEEDED" : "INVALID_INPUT", "OBJECT_WRITE", ); } return null; } function rebaseFailure( failure: BrowserDataFailure, operation: BrowserDataOperation, ): BrowserFailureResult { return { ok: false, error: Object.freeze({ ...failure, operation }), }; } function observeFailure( failure: BrowserDataResult, observer: OpfsSafeObserver | undefined, byteLength?: number, ): BrowserFailureResult { if (failure.ok) { throw new TypeError("Expected an OPFS failure result."); } observeOpfsSafely(observer, { operation: failure.error.operation, outcome: "FAILED", failureCode: failure.error.code, byteBucket: byteLength === undefined ? undefined : byteBucket(byteLength), }); return failure; } function notifyProgress( request: PutDurableObjectRequest, phase: "VALIDATING" | "PREPARING" | "FINALIZING", transferredBytes: number, ): void { try { request.onProgress?.({ phase, transferredBytes, totalBytes: request.source.byteLength, }); } catch { // A UI callback cannot affect persistence. } } function exceedsConfiguredCeiling( requested: number | undefined, configured: number, ): boolean { return requested !== undefined && requested > configured; } function isValidOptionalPositiveInteger( value: number | undefined, ): boolean { return ( value === undefined || (Number.isSafeInteger(value) && value > 0) ); } function isExpired( descriptor: DurableObjectDescriptor, nowEpochMs: number, ): boolean { const retention = descriptor.storagePolicy.retention; return ( retention.kind === "TTL" && descriptor.createdAtEpochMs + retention.maxAgeMs <= nowEpochMs ); } function policyMaintenanceOrder( left: OpfsPreparedObject, right: OpfsPreparedObject, ): number { const priorities = { RECONSTRUCTABLE: 0, SYNCED_COPY: 1, USER_AUTHORED: 2, } as const; const priority = priorities[left.descriptor.storagePolicy.evictionPriority] - priorities[right.descriptor.storagePolicy.evictionPriority]; return priority !== 0 ? priority : left.descriptor.createdAtEpochMs - right.descriptor.createdAtEpochMs; } function shouldRemoveForPolicy( object: OpfsPreparedObject, request: Parameters< DurableObjectMaintenancePort["enforcePolicies"] >[0], nowEpochMs: number, ): boolean { if (request.reason === "TTL") { return isExpired(object.descriptor, nowEpochMs); } if (request.reason === "LOGOUT") { return ( object.descriptor.storagePolicy.logoutAction === "PURGE_PARTITION" || object.descriptor.storagePolicy.logoutAction === "EXPORT_THEN_PURGE" ); } if (request.reason === "SESSION_END") { return object.descriptor.storagePolicy.retention.kind === "SESSION"; } if (request.reason === "UNTIL_SYNCED") { return object.descriptor.storagePolicy.retention.kind === "UNTIL_SYNCED"; } if (request.reason === "ACCOUNT_DELETION") { return ( object.descriptor.storagePolicy.accountDeletionAction === "PURGE_PARTITION" ); } return ( object.descriptor.storagePolicy.retention.kind !== "EXPLICIT_DELETE" && object.descriptor.storagePolicy.pressureAction === "EVICT_RECONSTRUCTABLE" ); } function maintenanceReasonAllowed( reason: Parameters< DurableObjectMaintenancePort["enforcePolicies"] >[0]["reason"], policy: BrowserStoragePolicy, ): boolean { switch (reason) { case "TTL": return policy.retention.kind === "TTL"; case "SESSION_END": return policy.retention.kind === "SESSION"; case "UNTIL_SYNCED": return policy.retention.kind === "UNTIL_SYNCED"; case "LOGOUT": return ( policy.accountScope === "OPAQUE_PARTITION" && (policy.logoutAction === "PURGE_PARTITION" || policy.logoutAction === "EXPORT_THEN_PURGE") ); case "ACCOUNT_DELETION": return ( policy.accountScope === "OPAQUE_PARTITION" && policy.accountDeletionAction === "PURGE_PARTITION" ); case "PRESSURE": return policy.pressureAction === "EVICT_RECONSTRUCTABLE"; } } function requiresMaintenanceAuthority( request: Parameters< DurableObjectMaintenancePort["enforcePolicies"] >[0], ): request is Extract< Parameters[0], { reason: "LOGOUT" | "UNTIL_SYNCED" | "ACCOUNT_DELETION" } > { return ( request.reason === "LOGOUT" || request.reason === "UNTIL_SYNCED" || request.reason === "ACCOUNT_DELETION" ); } const OPAQUE_AUTHORITY_PROOF = /^[A-Za-z0-9_-]{16,512}$/u; const MAX_AUTHORITY_PROOF_LIFETIME_MS = 5 * 60 * 1_000;