Files

2122 lines
61 KiB
TypeScript

import type {
ByteCount,
BrowserFilePolicyProfile,
BrowserManagedDownloadCapabilityResolver,
BrowserManagedDownloadCapabilityReceipt,
DownloadDeliveryPort,
DownloadOutcome,
DurableObjectDescriptor,
DurableObjectId,
DurableObjectMaintenancePort,
DurableObjectRead,
DurableObjectStorePort,
ExampleQuarantinedUpload,
ExampleQuarantinedUploadPort,
ExampleUploadPartReceipt,
ExampleUploadSession,
FileBytePattern,
FileCandidate,
FileByteSource,
FileContentPort,
FileInspection,
FileInspectionPolicy,
FilePolicyReference,
FilePickerPort,
FileSelectionOutcome,
FileSelectionLimitReduction,
FileSelectionPolicy,
FileVerificationReceipt,
LocalFileRef,
MigrationProgress,
ObjectGeneration,
ObjectStoreRecoverySummary,
OfflineCommitReceipt,
OfflinePage,
OfflineStoreLifecycleEvent,
OfflineStoreMaintenancePort,
OfflineStoreStatus,
PublicAssetEntry,
PublicCacheInspection,
PublicCacheLookup,
PublicResponseCacheAdmin,
StorageDurabilityPort,
StorageEstimate,
StoredRecord,
StructuredOfflineMutation,
StructuredOfflineStore,
TransientPreviewPort,
RegisteredDownloadPolicy,
RegisteredPreviewPolicy,
} from "./browser-file-storage-contracts.ts";
import type { CapabilityResult } from "./contracts.ts";
import { failure, success } from "./fake-adapters.ts";
export function asByteCount(value: number): ByteCount {
if (!Number.isSafeInteger(value) || value < 0) {
throw new TypeError("Byte counts must be non-negative safe integers.");
}
return value as ByteCount;
}
export function asLocalFileRef(value: string): LocalFileRef {
if (value.length === 0) {
throw new TypeError("Local file references must not be empty.");
}
return value as LocalFileRef;
}
export function asFileVerificationReceipt(
value: string,
): FileVerificationReceipt {
if (!/^[a-z0-9][a-z0-9:_-]{0,127}$/i.test(value)) {
throw new TypeError("File verification receipts must be opaque tokens.");
}
return value as FileVerificationReceipt;
}
const issuedFilePolicyReferences = new WeakSet<object>();
export function browserFilePolicyReference(
policyKey: string,
intention: string,
): FilePolicyReference {
const token = /^[a-z0-9][a-z0-9._:-]{0,127}$/i;
if (!token.test(policyKey) || !token.test(intention)) {
throw new TypeError("File policy references must be opaque tokens.");
}
const reference = Object.freeze({
policyKey:
policyKey as FilePolicyReference["policyKey"],
intention:
intention as FilePolicyReference["intention"],
});
issuedFilePolicyReferences.add(reference);
return reference;
}
export function asBrowserManagedDownloadCapabilityReceipt(
value: string,
): BrowserManagedDownloadCapabilityReceipt {
if (!/^[a-z0-9][a-z0-9:_-]{0,127}$/i.test(value)) {
throw new TypeError(
"Browser-managed download receipts must be opaque tokens.",
);
}
return value as BrowserManagedDownloadCapabilityReceipt;
}
type MemoryPolicyProfile = Readonly<{
bindingId: string;
selection?: FileSelectionPolicy;
inspection?: FileInspectionPolicy;
preview?: RegisteredPreviewPolicy;
download?: RegisteredDownloadPolicy;
}>;
/**
* Composition-time dataset policy registry used by the deterministic fakes.
* It deep-snapshots profile data and resolves only the exact registered
* reference object. Equal key/intention strings are not authority.
*/
export class MemoryBrowserFilePolicyRegistry {
readonly #profiles =
new Map<FilePolicyReference, MemoryPolicyProfile>();
constructor(profiles: ReadonlyArray<BrowserFilePolicyProfile>) {
if (profiles.length === 0) {
throw new TypeError("At least one file policy profile is required.");
}
const semanticKeys = new Set<string>();
for (const profile of profiles) {
if (
!issuedFilePolicyReferences.has(profile.reference) ||
(!profile.selection &&
!profile.inspection &&
!profile.preview &&
!profile.download) ||
(profile.preview && !profile.inspection)
) {
throw new TypeError("The file policy profile is invalid.");
}
const bindingId = `${profile.reference.policyKey}\u0000${profile.reference.intention}`;
if (semanticKeys.has(bindingId)) {
throw new TypeError("The file policy profile is duplicated.");
}
semanticKeys.add(bindingId);
this.#profiles.set(
profile.reference,
Object.freeze({
bindingId,
...(profile.selection
? { selection: snapshotSelectionPolicy(profile.selection) }
: {}),
...(profile.inspection
? {
inspection: snapshotInspectionPolicy(
profile.inspection,
),
}
: {}),
...(profile.preview
? { preview: snapshotPreviewPolicy(profile.preview) }
: {}),
...(profile.download
? {
download: Object.freeze({
...profile.download,
}),
}
: {}),
}),
);
}
}
selection(
reference: FilePolicyReference,
reductions?: FileSelectionLimitReduction,
): CapabilityResult<FileSelectionPolicy> {
const profile = this.#profiles.get(reference);
if (!profile?.selection) return rejectedPolicy();
const maxCount = reduceLimit(
reductions?.maxCount,
profile.selection.maxCount,
);
const maxFileBytes = reduceLimit(
reductions?.maxFileBytes,
profile.selection.maxFileBytes,
);
const maxTotalBytes = reduceLimit(
reductions?.maxTotalBytes,
profile.selection.maxTotalBytes,
);
if (
maxCount === null ||
maxFileBytes === null ||
maxTotalBytes === null
) {
return failure(
"LIMIT_EXCEEDED",
false,
"A caller may only reduce registered file limits.",
);
}
return success(
Object.freeze({
...profile.selection,
maxCount,
maxFileBytes: asByteCount(maxFileBytes),
maxTotalBytes: asByteCount(maxTotalBytes),
}),
);
}
inspection(
reference: FilePolicyReference,
reduction?: ByteCount,
): CapabilityResult<
Readonly<{
policy: FileInspectionPolicy;
bindingId: string;
}>
> {
const profile = this.#profiles.get(reference);
if (!profile?.inspection) return rejectedPolicy();
const maxInspectionBytes = reduceLimit(
reduction,
profile.inspection.maxInspectionBytes,
);
if (maxInspectionBytes === null) {
return failure(
"LIMIT_EXCEEDED",
false,
"A caller may only reduce registered inspection limits.",
);
}
return success(
Object.freeze({
bindingId: profile.bindingId,
policy: Object.freeze({
...profile.inspection,
maxInspectionBytes: asByteCount(maxInspectionBytes),
}),
}),
);
}
preview(
reference: FilePolicyReference,
reduction?: ByteCount,
): CapabilityResult<
Readonly<{
bindingId: string;
allowedMediaTypes: ReadonlySet<string>;
maxPreviewBytes: ByteCount;
}>
> {
const profile = this.#profiles.get(reference);
if (!profile?.preview || !profile.inspection) {
return rejectedPolicy();
}
const maxPreviewBytes = reduceLimit(
reduction,
profile.preview.maxPreviewBytes,
);
if (maxPreviewBytes === null) {
return failure(
"LIMIT_EXCEEDED",
false,
"A caller may only reduce registered preview limits.",
);
}
return success(
Object.freeze({
bindingId: profile.bindingId,
allowedMediaTypes: new Set(
profile.preview.allowedMediaTypes,
),
maxPreviewBytes: asByteCount(maxPreviewBytes),
}),
);
}
download(
reference: FilePolicyReference,
reductions: Readonly<{
maxTransferBytes?: ByteCount;
maxBufferedBytes?: ByteCount;
}>,
): CapabilityResult<RegisteredDownloadPolicy> {
const profile = this.#profiles.get(reference);
if (!profile?.download) return rejectedPolicy();
const maxTransferBytes = reduceLimit(
reductions.maxTransferBytes,
profile.download.maxTransferBytes,
);
const maxBufferedBytes = reduceLimit(
reductions.maxBufferedBytes,
Math.min(
profile.download.maxBufferedBytes,
maxTransferBytes ?? -1,
),
);
if (
maxTransferBytes === null ||
maxBufferedBytes === null
) {
return failure(
"LIMIT_EXCEEDED",
false,
"A caller may only reduce registered download limits.",
);
}
return success(
Object.freeze({
...profile.download,
maxTransferBytes: asByteCount(maxTransferBytes),
maxBufferedBytes: asByteCount(maxBufferedBytes),
}),
);
}
}
function rejectedPolicy(): CapabilityResult<never> {
return failure(
"POLICY_REJECTED",
false,
"The composition-issued file policy is not available.",
);
}
function reduceLimit(
reduction: number | undefined,
ceiling: number,
): number | null {
const value = reduction ?? ceiling;
return Number.isSafeInteger(value) &&
value > 0 &&
value <= ceiling
? value
: null;
}
function snapshotSelectionPolicy(
policy: FileSelectionPolicy,
): FileSelectionPolicy {
return Object.freeze({
...policy,
accept: Object.freeze(
policy.accept.map((rule) =>
Object.freeze({
mediaType: rule.mediaType,
extensions: Object.freeze([...rule.extensions]),
}),
),
),
});
}
function snapshotInspectionPolicy(
policy: FileInspectionPolicy,
): FileInspectionPolicy {
return Object.freeze({
...policy,
acceptedSignatures: Object.freeze(
policy.acceptedSignatures.map((rule) =>
Object.freeze({
mediaType: rule.mediaType,
extensions: Object.freeze([...rule.extensions]),
patterns: Object.freeze(
rule.patterns.map((pattern) =>
Object.freeze({
offset: pattern.offset,
bytes: Object.freeze([...pattern.bytes]),
...(pattern.mask
? { mask: Object.freeze([...pattern.mask]) }
: {}),
}),
),
),
}),
),
),
});
}
function snapshotPreviewPolicy(
policy: RegisteredPreviewPolicy,
): RegisteredPreviewPolicy {
return Object.freeze({
allowedMediaTypes: Object.freeze([
...policy.allowedMediaTypes,
]),
maxPreviewBytes: policy.maxPreviewBytes,
});
}
export function asDurableObjectId(value: string): DurableObjectId {
if (value.length === 0) {
throw new TypeError("Durable object IDs must not be empty.");
}
return value as DurableObjectId;
}
export function asObjectGeneration(value: number): ObjectGeneration {
if (!Number.isSafeInteger(value) || value < 1) {
throw new TypeError("Object generations must be positive safe integers.");
}
return value as ObjectGeneration;
}
function cancelled(signal?: AbortSignal): CapabilityResult<never> | null {
return signal?.aborted
? failure("ABORTED", false, "The operation was cancelled.")
: null;
}
function isReadableStatus(status: OfflineStoreStatus): boolean {
return status.kind === "READY" || status.kind === "READ_ONLY";
}
function isWritableStatus(status: OfflineStoreStatus): boolean {
return status.kind === "READY";
}
function cloneBytes(bytes: Uint8Array): Uint8Array {
return bytes.slice();
}
function extensionOf(fileName: string): string | null {
const normalized = fileName.normalize("NFC");
const index = normalized.lastIndexOf(".");
return index > 0 && index < normalized.length - 1
? normalized.slice(index).toLowerCase()
: null;
}
function acceptsCandidate(
policy: FileSelectionPolicy,
candidate: FileCandidate,
): boolean {
if (policy.accept.length === 0) return true;
const extension = extensionOf(candidate.displayName);
return policy.accept.some(
(rule) =>
(candidate.reportedMediaType !== null &&
candidate.reportedMediaType.toLowerCase() ===
rule.mediaType.toLowerCase()) ||
(extension !== null &&
rule.extensions.some(
(accepted) => accepted.toLowerCase() === extension,
)),
);
}
function matchesFileBytePattern(
bytes: Uint8Array,
pattern: FileBytePattern,
): boolean {
if (pattern.offset + pattern.bytes.length > bytes.byteLength) {
return false;
}
for (const [index, expected] of pattern.bytes.entries()) {
const actual = bytes[pattern.offset + index];
const mask = pattern.mask?.[index] ?? 0xff;
if (
actual === undefined ||
!Number.isInteger(expected) ||
expected < 0 ||
expected > 0xff ||
(actual & mask) !== (expected & mask)
) {
return false;
}
}
return true;
}
export type MemoryFileFixture = Readonly<{
ref: LocalFileRef;
displayName: string;
bytes: Uint8Array;
reportedMediaType: string | null;
detectedMediaType: string | null;
lastModifiedEpochMs?: number | null;
signature?: FileInspection["signature"];
source?: FileCandidate["source"];
}>;
/**
* Deterministic picker and transient file vault. Metadata matching is only a
* preflight policy simulation. Any separately selected backend transfer
* workflow must independently revalidate content.
*/
export class MemoryFileSelectionAdapter
implements FilePickerPort, FileContentPort
{
readonly support;
readonly #policies: MemoryBrowserFilePolicyRegistry;
readonly #files = new Map<LocalFileRef, MemoryFileFixture>();
readonly #verifications = new Map<
FileVerificationReceipt,
Readonly<{
ref: LocalFileRef;
policyBindingId: string;
mediaType: string;
}>
>();
#verificationSequence = 0;
#nextOutcome: "SELECTED" | "DISMISSED" | "PERMISSION_DENIED" = "SELECTED";
constructor(
files: ReadonlyArray<MemoryFileFixture>,
policies: MemoryBrowserFilePolicyRegistry,
support: FilePickerPort["support"] = {
nativeInput: true,
systemOpenPicker: false,
systemSavePicker: false,
},
) {
this.#policies = policies;
this.support = Object.freeze({ ...support });
for (const file of files) {
this.#files.set(file.ref, {
...file,
bytes: cloneBytes(file.bytes),
});
}
}
setNextOutcome(
outcome: "SELECTED" | "DISMISSED" | "PERMISSION_DENIED",
): void {
this.#nextOutcome = outcome;
}
async select(input: {
policy: FilePolicyReference;
limits?: FileSelectionLimitReduction;
signal?: AbortSignal;
}): Promise<CapabilityResult<FileSelectionOutcome>> {
const aborted = cancelled(input.signal);
if (aborted) return aborted;
const outcome = this.#nextOutcome;
this.#nextOutcome = "SELECTED";
if (outcome === "DISMISSED") {
return success({ kind: "DISMISSED" });
}
if (outcome === "PERMISSION_DENIED") {
return failure(
"PERMISSION_DENIED",
false,
"File access permission was denied.",
);
}
const resolvedPolicy = this.#policies.selection(
input.policy,
input.limits,
);
if (!resolvedPolicy.ok) return resolvedPolicy;
const policy = resolvedPolicy.value;
const candidates = Array.from(this.#files.values(), (file) =>
Object.freeze({
ref: file.ref,
displayName: file.displayName,
sizeBytes: asByteCount(file.bytes.byteLength),
reportedMediaType: file.reportedMediaType,
lastModifiedEpochMs: file.lastModifiedEpochMs ?? null,
source: file.source ?? "NATIVE_INPUT",
}),
);
const total = candidates.reduce(
(sum, candidate) => sum + candidate.sizeBytes,
0,
);
if (
candidates.length > policy.maxCount ||
(!policy.multiple && candidates.length > 1) ||
candidates.some(
(candidate) =>
candidate.sizeBytes > policy.maxFileBytes ||
(!policy.allowEmpty && candidate.sizeBytes === 0),
) ||
total > policy.maxTotalBytes
) {
return failure(
"LIMIT_EXCEEDED",
false,
"The selected files exceed the approved count or byte budget.",
);
}
if (
candidates.some(
(candidate) => !acceptsCandidate(policy, candidate),
)
) {
return failure(
"POLICY_REJECTED",
false,
"A selected file does not match the intake policy.",
);
}
return success({ kind: "SELECTED", files: candidates });
}
async inspect(input: {
ref: LocalFileRef;
policy: FilePolicyReference;
maxInspectionBytes?: ByteCount;
signal: AbortSignal;
}): Promise<CapabilityResult<FileInspection>> {
const aborted = cancelled(input.signal);
if (aborted) return aborted;
const resolvedPolicy = this.#policies.inspection(
input.policy,
input.maxInspectionBytes,
);
if (!resolvedPolicy.ok) return resolvedPolicy;
const { policy, bindingId } = resolvedPolicy.value;
const file = this.#files.get(input.ref);
if (!file) {
return failure("NOT_FOUND", false, "The selected file is no longer available.");
}
this.#invalidateVerifications(input.ref);
const inspectedBytes = file.bytes.slice(
0,
policy.maxInspectionBytes,
);
const matchedRule = policy.acceptedSignatures.find(
(rule) =>
rule.mediaType.toLowerCase() ===
file.detectedMediaType?.toLowerCase() &&
rule.patterns.some((pattern) =>
matchesFileBytePattern(inspectedBytes, pattern),
),
);
const detectedMediaType = matchedRule?.mediaType ?? null;
const signature =
file.signature === "MISMATCHED"
? "MISMATCHED"
: matchedRule
? "MATCHED"
: file.signature === "MATCHED"
? "MISMATCHED"
: "UNKNOWN";
const verificationReceipt =
signature === "MATCHED" && detectedMediaType !== null
? asFileVerificationReceipt(
`verification:${++this.#verificationSequence}`,
)
: null;
if (verificationReceipt) {
this.#verifications.set(
verificationReceipt,
Object.freeze({
ref: input.ref,
policyBindingId: bindingId,
mediaType: detectedMediaType!,
}),
);
}
return success({
byteLength: asByteCount(file.bytes.byteLength),
reportedMediaType: file.reportedMediaType,
detectedMediaType,
normalizedExtension: extensionOf(file.displayName),
signature,
verificationReceipt,
});
}
async readRange(input: {
ref: LocalFileRef;
offset: ByteCount;
length: ByteCount;
signal: AbortSignal;
}): Promise<CapabilityResult<Uint8Array>> {
const aborted = cancelled(input.signal);
if (aborted) return aborted;
const file = this.#files.get(input.ref);
if (!file) {
return failure("NOT_FOUND", false, "The selected file is no longer available.");
}
const end = input.offset + input.length;
if (
!Number.isSafeInteger(end) ||
input.offset > file.bytes.byteLength ||
end > file.bytes.byteLength
) {
return failure("INVALID_INPUT", false, "The requested byte range is invalid.");
}
return success(file.bytes.slice(input.offset, end));
}
async openSource(input: {
ref: LocalFileRef;
signal: AbortSignal;
}): Promise<CapabilityResult<FileByteSource>> {
const aborted = cancelled(input.signal);
if (aborted) return aborted;
const file = this.#files.get(input.ref);
if (!file) {
return failure(
"NOT_FOUND",
false,
"The selected file is no longer available.",
);
}
const bytes = cloneBytes(file.bytes);
return success(
Object.freeze({
byteLength: asByteCount(bytes.byteLength),
async *stream(signal: AbortSignal) {
const streamAbort = cancelled(signal);
if (streamAbort) {
yield streamAbort;
return;
}
yield success(cloneBytes(bytes));
},
}),
);
}
release(ref: LocalFileRef): void {
this.#invalidateVerifications(ref);
this.#files.delete(ref);
}
verification(
receipt: FileVerificationReceipt,
): Readonly<{
ref: LocalFileRef;
policyBindingId: string;
mediaType: string;
byteLength: ByteCount;
}> | null {
const verification = this.#verifications.get(receipt);
if (!verification) return null;
const file = this.#files.get(verification.ref);
return file
? Object.freeze({
...verification,
byteLength: asByteCount(file.bytes.byteLength),
})
: null;
}
#invalidateVerifications(ref: LocalFileRef): void {
for (const [receipt, verification] of this.#verifications) {
if (verification.ref === ref) this.#verifications.delete(receipt);
}
}
}
export class MemoryTransientPreviewAdapter implements TransientPreviewPort {
static readonly #ACTIVE_CONTENT_DENYLIST = new Set([
"application/pdf",
"application/xhtml+xml",
"application/xml",
"image/svg+xml",
"text/html",
"text/xml",
]);
readonly #files: MemoryFileSelectionAdapter;
readonly #policies: MemoryBrowserFilePolicyRegistry;
readonly #active = new Set<string>();
#sequence = 0;
#disposed = false;
constructor(
files: MemoryFileSelectionAdapter,
policies: MemoryBrowserFilePolicyRegistry,
) {
this.#files = files;
this.#policies = policies;
}
async create(
input: Parameters<TransientPreviewPort["create"]>[0],
): ReturnType<TransientPreviewPort["create"]> {
if (this.#disposed) {
return failure(
"PROVIDER_UNAVAILABLE",
false,
"The preview adapter has been disposed.",
);
}
const aborted = cancelled(input.signal);
if (aborted) return aborted;
const verification = this.#files.verification(
input.verificationReceipt,
);
const resolvedPolicy = this.#policies.preview(
input.policy,
input.maxPreviewBytes,
);
if (!resolvedPolicy.ok) return resolvedPolicy;
const policy = resolvedPolicy.value;
if (
!verification ||
verification.ref !== input.ref ||
verification.policyBindingId !== policy.bindingId
) {
return failure(
"POLICY_REJECTED",
false,
"The preview verification receipt is invalid.",
);
}
if (
verification.byteLength > policy.maxPreviewBytes
) {
return failure(
"LIMIT_EXCEEDED",
false,
"The preview exceeds its byte budget.",
);
}
if (
!policy.allowedMediaTypes.has(verification.mediaType) ||
MemoryTransientPreviewAdapter.#ACTIVE_CONTENT_DENYLIST.has(
verification.mediaType.toLowerCase(),
)
) {
return failure(
"POLICY_REJECTED",
false,
"The file type is not approved for inline preview.",
);
}
const url = `blob:memory-preview-${++this.#sequence}`;
this.#active.add(url);
let released = false;
return success(
Object.freeze({
url,
mediaType: verification.mediaType,
release: () => {
if (released) return;
released = true;
this.#active.delete(url);
},
}),
);
}
get activeLeaseCount(): number {
return this.#active.size;
}
dispose(): void {
if (this.#disposed) return;
this.#disposed = true;
this.#active.clear();
}
}
type MemoryUploadState = {
expectedBytes: number;
parts: Map<
number,
Readonly<{ offset: number; receipt: ExampleUploadPartReceipt }>
>;
idempotency: Map<
string,
Readonly<{
fingerprint: string;
receipt: ExampleUploadPartReceipt;
}>
>;
};
/**
* Deterministic example for a separately selected, backend-authorized
* resumable upload workflow. It is not part of browser-file composition.
*/
export class MemoryExampleQuarantinedUploadAdapter
implements ExampleQuarantinedUploadPort
{
readonly #sessions = new Map<string, MemoryUploadState>();
#sequence = 0;
constructor(
private readonly maxUploadBytes: ByteCount,
private readonly partSizeBytes: ByteCount,
private readonly now: () => number = Date.now,
) {}
async create(input: {
purpose: string;
byteLength: ByteCount;
detectedMediaType: string;
signal: AbortSignal;
}): Promise<CapabilityResult<ExampleUploadSession>> {
const aborted = cancelled(input.signal);
if (aborted) return aborted;
if (
input.purpose.length === 0 ||
input.detectedMediaType.length === 0 ||
input.byteLength > this.maxUploadBytes
) {
return failure("LIMIT_EXCEEDED", false, "The upload policy rejected the file.");
}
const sessionId = `upload-${++this.#sequence}`;
this.#sessions.set(sessionId, {
expectedBytes: input.byteLength,
parts: new Map(),
idempotency: new Map(),
});
return success({
sessionId,
partSizeBytes: this.partSizeBytes,
maxConcurrency: 2,
expiresAt: new Date(this.now() + 15 * 60_000).toISOString(),
checksumAlgorithm: "SHA-256",
});
}
async uploadPart(input: {
sessionId: string;
partNumber: number;
offset: ByteCount;
bytes: Uint8Array;
checksumSha256: string;
idempotencyKey: string;
signal: AbortSignal;
}): Promise<CapabilityResult<ExampleUploadPartReceipt>> {
const aborted = cancelled(input.signal);
if (aborted) return aborted;
const session = this.#sessions.get(input.sessionId);
if (!session) {
return failure(
"EXPIRED_RESOURCE",
false,
"The upload session is no longer available.",
);
}
const fingerprint = [
input.partNumber,
input.offset,
input.bytes.byteLength,
input.checksumSha256,
].join(":");
const replay = session.idempotency.get(input.idempotencyKey);
if (replay) {
return replay.fingerprint === fingerprint
? success(replay.receipt)
: failure(
"CONFLICT",
false,
"The idempotency key was reused for a different upload part.",
);
}
if (
input.partNumber < 1 ||
input.bytes.byteLength === 0 ||
input.bytes.byteLength > this.partSizeBytes ||
input.offset + input.bytes.byteLength > session.expectedBytes ||
input.checksumSha256.length === 0
) {
return failure("INVALID_INPUT", false, "The upload part is invalid.");
}
if (session.parts.has(input.partNumber)) {
return failure("CONFLICT", false, "The upload part already exists.");
}
const receipt = Object.freeze({
partNumber: input.partNumber,
acceptedBytes: asByteCount(input.bytes.byteLength),
checksumSha256: input.checksumSha256,
});
session.parts.set(input.partNumber, {
offset: input.offset,
receipt,
});
session.idempotency.set(input.idempotencyKey, {
fingerprint,
receipt,
});
return success(receipt);
}
async complete(input: {
sessionId: string;
parts: ReadonlyArray<ExampleUploadPartReceipt>;
signal: AbortSignal;
}): Promise<CapabilityResult<ExampleQuarantinedUpload>> {
const aborted = cancelled(input.signal);
if (aborted) return aborted;
const session = this.#sessions.get(input.sessionId);
if (!session) {
return failure(
"EXPIRED_RESOURCE",
false,
"The upload session is no longer available.",
);
}
const partNumbers = new Set(input.parts.map((part) => part.partNumber));
const acceptedBytes = input.parts.reduce(
(sum, part) => sum + part.acceptedBytes,
0,
);
const matches = input.parts.every(
(part) =>
session.parts.get(part.partNumber)?.receipt.checksumSha256 ===
part.checksumSha256,
);
const storedParts = Array.from(session.parts.values()).sort(
(left, right) => left.offset - right.offset,
);
let nextOffset = 0;
const contiguous = storedParts.every((part) => {
if (part.offset !== nextOffset) return false;
nextOffset += part.receipt.acceptedBytes;
return true;
});
if (
!matches ||
partNumbers.size !== input.parts.length ||
input.parts.length !== session.parts.size ||
!contiguous ||
nextOffset !== session.expectedBytes ||
acceptedBytes !== session.expectedBytes
) {
return failure(
"INTEGRITY_FAILED",
false,
"The uploaded parts did not pass final verification.",
);
}
this.#sessions.delete(input.sessionId);
return success({
resourceId: `resource-${input.sessionId}`,
state: "QUARANTINED",
});
}
async abort(
sessionId: string,
signal?: AbortSignal,
): Promise<CapabilityResult<void>> {
const aborted = cancelled(signal);
if (aborted) return aborted;
this.#sessions.delete(sessionId);
return success(undefined);
}
}
const windowsDeviceNames = new Set([
"CON",
"PRN",
"AUX",
"NUL",
"COM1",
"COM2",
"COM3",
"COM4",
"COM5",
"COM6",
"COM7",
"COM8",
"COM9",
"LPT1",
"LPT2",
"LPT3",
"LPT4",
"LPT5",
"LPT6",
"LPT7",
"LPT8",
"LPT9",
]);
function sanitizeFileNameCharacter(character: string): string {
const codePoint = character.codePointAt(0) ?? 0;
const control = codePoint <= 31 || codePoint === 127;
const bidiControl =
(codePoint >= 0x202a && codePoint <= 0x202e) ||
(codePoint >= 0x2066 && codePoint <= 0x2069);
if (control || bidiControl) return "";
return character === ":" ? "_" : character;
}
export function sanitizeDownloadFileName(
candidate: string,
fallback = "download.bin",
): string {
const normalized = candidate.normalize("NFC");
const pathSegments = normalized.split(/[/\\]/);
const leafName = pathSegments.at(-1) ?? normalized;
const cleaned = Array.from(leafName)
.map(sanitizeFileNameCharacter)
.join("")
.trim()
.replace(/^[. ]+|[. ]+$/g, "")
.replace(/_+/g, "_");
const bounded = Array.from(cleaned).slice(0, 180).join("");
const stem = bounded.split(".")[0]?.toUpperCase() ?? "";
if (
bounded.length === 0 ||
bounded === "." ||
bounded === ".." ||
windowsDeviceNames.has(stem)
) {
return fallback;
}
return bounded;
}
export class RecordingDownloadDeliveryAdapter
implements DownloadDeliveryPort
{
readonly receipts: Array<
Readonly<{ fileName: string; strategy: string; bytesWritten: number }>
> = [];
readonly #policies: MemoryBrowserFilePolicyRegistry;
readonly #resolveBrowserManaged:
BrowserManagedDownloadCapabilityResolver["resolve"];
readonly #now: () => number;
#sequence = 0;
constructor(
policies: MemoryBrowserFilePolicyRegistry,
browserManagedCapabilities: BrowserManagedDownloadCapabilityResolver,
now: () => number = Date.now,
) {
this.#policies = policies;
this.#resolveBrowserManaged =
browserManagedCapabilities.resolve.bind(
browserManagedCapabilities,
);
this.#now = now;
}
async deliver(
input: Parameters<DownloadDeliveryPort["deliver"]>[0],
): Promise<CapabilityResult<DownloadOutcome>> {
const aborted = cancelled(input.signal);
if (aborted) return aborted;
const resolvedPolicy = this.#policies.download(input.policy, {
maxTransferBytes: input.maxTransferBytes,
maxBufferedBytes: input.maxBufferedBytes,
});
if (!resolvedPolicy.ok) return resolvedPolicy;
const policy = resolvedPolicy.value;
const sanitized = sanitizeDownloadFileName(input.suggestedFileName);
const safeExtension = policy.safeExtension.toLowerCase();
const fileName = sanitized.toLowerCase().endsWith(safeExtension)
? sanitized
: `${sanitized.replace(/\.[^.]+$/, "")}${safeExtension}`;
const transferId = `download-${++this.#sequence}`;
if (
(policy.strategy === "BROWSER_MANAGED" &&
input.source.kind !== "BROWSER_MANAGED_RESOURCE") ||
(policy.strategy !== "BROWSER_MANAGED" &&
input.source.kind === "BROWSER_MANAGED_RESOURCE")
) {
return failure(
"POLICY_REJECTED",
false,
"The source is incompatible with the registered strategy.",
);
}
if (
policy.strategy === "BROWSER_MANAGED" &&
input.source.kind === "BROWSER_MANAGED_RESOURCE"
) {
const capability = this.#resolveBrowserManaged({
resourceId: input.source.resourceId,
capabilityReceipt: input.source.capabilityReceipt,
});
if (!capability.ok) return capability;
if (
capability.value.capabilityReceipt !==
input.source.capabilityReceipt ||
capability.value.resourceId !== input.source.resourceId ||
capability.value.mediaType !== policy.mediaType ||
capability.value.safeExtension !== policy.safeExtension ||
capability.value.maxBytes > policy.maxTransferBytes ||
capability.value.expiresAtEpochMs <= this.#now() ||
(policy.integrity === "REQUIRED" &&
!capability.value.expectedSha256)
) {
return failure(
capability.value.expiresAtEpochMs <= this.#now()
? "EXPIRED_RESOURCE"
: "POLICY_REJECTED",
false,
"The server-bound download capability is invalid.",
);
}
this.receipts.push({
fileName,
strategy: policy.strategy,
bytesWritten: 0,
});
return success({ kind: "BROWSER_HANDOFF", transferId });
}
if (input.source.kind === "AUTHORIZED_STREAM_RESOURCE") {
return failure(
"UNSUPPORTED",
false,
"This fake cannot stream an authorized server resource.",
);
}
if (input.source.kind !== "GENERATED") {
return failure(
"POLICY_REJECTED",
false,
"The download source is incompatible with this fake.",
);
}
const expectedSha256 = input.source.expectedSha256;
if (policy.integrity === "REQUIRED" && !expectedSha256) {
return failure(
"POLICY_REJECTED",
false,
"The registered download policy requires integrity.",
);
}
if (
policy.strategy === "BOUNDED_OBJECT_URL" &&
input.source.bytes.byteLength !== null &&
input.source.bytes.byteLength > policy.maxBufferedBytes
) {
return failure(
"LIMIT_EXCEEDED",
false,
"The generated download exceeds the buffering budget.",
);
}
let bytesWritten = 0;
const integrityChunks: Uint8Array[] = [];
for await (const chunkResult of input.source.bytes.stream(
input.signal,
)) {
if (!chunkResult.ok) return chunkResult;
const chunk = chunkResult.value;
const duringTransfer = cancelled(input.signal);
if (duringTransfer) return duringTransfer;
bytesWritten += chunk.byteLength;
if (bytesWritten > policy.maxTransferBytes) {
return failure(
"LIMIT_EXCEEDED",
false,
"The generated download exceeds the transfer budget.",
);
}
if (
input.source.bytes.byteLength !== null &&
bytesWritten > input.source.bytes.byteLength
) {
return failure(
"INTEGRITY_FAILED",
false,
"The generated download exceeded its declared size.",
);
}
if (expectedSha256) integrityChunks.push(cloneBytes(chunk));
input.onProgress({
phase: "TRANSFERRING",
transferredBytes: bytesWritten,
totalBytes: input.source.bytes.byteLength,
});
}
if (
input.source.bytes.byteLength !== null &&
bytesWritten !== input.source.bytes.byteLength
) {
return failure(
"INTEGRITY_FAILED",
false,
"The generated download did not match its declared size.",
);
}
if (expectedSha256) {
const beforeVerification = cancelled(input.signal);
if (beforeVerification) return beforeVerification;
input.onProgress({
phase: "VERIFYING",
transferredBytes: bytesWritten,
totalBytes: input.source.bytes.byteLength,
});
const actualSha256 = await sha256Hex(concatenateBytes(integrityChunks));
if (actualSha256 !== expectedSha256.toLowerCase()) {
return failure(
"INTEGRITY_FAILED",
false,
"The generated download failed integrity verification.",
);
}
}
this.receipts.push({
fileName,
strategy: policy.strategy,
bytesWritten,
});
return success({
kind: "SAVED",
transferId,
bytesWritten: asByteCount(bytesWritten),
integrity: expectedSha256 ? "VERIFIED" : "NOT_PROVIDED",
});
}
}
function concatenateBytes(chunks: ReadonlyArray<Uint8Array>): Uint8Array {
const totalBytes = chunks.reduce((total, chunk) => total + chunk.byteLength, 0);
const combined = new Uint8Array(totalBytes);
let offset = 0;
for (const chunk of chunks) {
combined.set(chunk, offset);
offset += chunk.byteLength;
}
return combined;
}
type MemoryStructuredOfflineStoreOptions = Readonly<{
maxBytes: number;
now?: () => number;
initialStatus?: OfflineStoreStatus;
}>;
export class MemoryStructuredOfflineStore<T>
implements StructuredOfflineStore<T>, OfflineStoreMaintenancePort
{
readonly #records = new Map<string, StoredRecord<T>>();
readonly #receipts = new Map<
string,
Readonly<{ fingerprint: string; receipt: OfflineCommitReceipt }>
>();
readonly #now: () => number;
readonly #maxBytes: number;
#status: OfflineStoreStatus;
#onLifecycle: ((event: OfflineStoreLifecycleEvent) => void) | null = null;
#commitSequence = 0;
constructor(options: MemoryStructuredOfflineStoreOptions) {
this.#maxBytes = options.maxBytes;
this.#now = options.now ?? Date.now;
this.#status =
options.initialStatus ??
Object.freeze({
kind: "CLOSED",
reason: "DISPOSED",
});
}
async open(input: {
partitionKey: string;
signal?: AbortSignal;
onLifecycle(event: OfflineStoreLifecycleEvent): void;
}): Promise<CapabilityResult<OfflineStoreStatus>> {
const aborted = cancelled(input.signal);
if (aborted) return aborted;
if (input.partitionKey.length === 0) {
return failure("INVALID_INPUT", false, "The storage partition is invalid.");
}
this.#onLifecycle = input.onLifecycle;
if (this.#status.kind === "UPGRADE_BLOCKED") {
input.onLifecycle({ kind: "STATUS_CHANGED", status: this.#status });
return success(this.#status);
}
this.#status = Object.freeze({
kind: "READY",
persistence: "BEST_EFFORT",
});
input.onLifecycle({ kind: "STATUS_CHANGED", status: this.#status });
return success(this.#status);
}
async read(
id: string,
signal?: AbortSignal,
): Promise<CapabilityResult<StoredRecord<T> | null>> {
const aborted = cancelled(signal);
if (aborted) return aborted;
if (!isReadableStatus(this.#status)) {
return failure("PROVIDER_UNAVAILABLE", false, "The offline store is not open.");
}
const record = this.#records.get(id);
if (
record?.expiresAtEpochMs !== null &&
record?.expiresAtEpochMs !== undefined &&
record.expiresAtEpochMs <= this.#now()
) {
return success(null);
}
return success(record ? structuredClone(record) : null);
}
async page(input: {
cursor?: string;
limit: number;
includeExpired?: boolean;
signal?: AbortSignal;
}): Promise<CapabilityResult<OfflinePage<T>>> {
const aborted = cancelled(input.signal);
if (aborted) return aborted;
if (!isReadableStatus(this.#status)) {
return failure("PROVIDER_UNAVAILABLE", false, "The offline store is not open.");
}
if (!Number.isInteger(input.limit) || input.limit < 1 || input.limit > 500) {
return failure("LIMIT_EXCEEDED", false, "The offline page limit is invalid.");
}
const offset = input.cursor === undefined ? 0 : Number(input.cursor);
if (!Number.isSafeInteger(offset) || offset < 0) {
return failure("INVALID_INPUT", false, "The offline cursor is invalid.");
}
const now = this.#now();
const records = Array.from(this.#records.values())
.filter(
(record) =>
input.includeExpired === true ||
record.expiresAtEpochMs === null ||
record.expiresAtEpochMs > now,
)
.sort((left, right) => left.id.localeCompare(right.id));
const selected = records
.slice(offset, offset + input.limit)
.map((record) => structuredClone(record));
const nextOffset = offset + selected.length;
return success({
records: selected,
nextCursor: nextOffset < records.length ? String(nextOffset) : null,
});
}
async commit(input: {
idempotencyKey: string;
mutations: ReadonlyArray<StructuredOfflineMutation<T>>;
signal?: AbortSignal;
}): Promise<CapabilityResult<OfflineCommitReceipt>> {
const aborted = cancelled(input.signal);
if (aborted) return aborted;
if (!isWritableStatus(this.#status)) {
return failure("PROVIDER_UNAVAILABLE", false, "The offline store is read-only.");
}
if (input.idempotencyKey.length === 0 || input.mutations.length === 0) {
return failure("INVALID_INPUT", false, "The offline commit is empty.");
}
let fingerprint: string;
try {
fingerprint = JSON.stringify(input.mutations);
} catch {
return failure(
"CORRUPT_DATA",
false,
"The offline mutation cannot be encoded safely.",
);
}
const replay = this.#receipts.get(input.idempotencyKey);
if (replay) {
return replay.fingerprint === fingerprint
? success({ ...replay.receipt, replayed: true })
: failure(
"CONFLICT",
false,
"The idempotency key was reused for a different offline commit.",
);
}
const draft = new Map(
Array.from(this.#records, ([id, record]) => [
id,
structuredClone(record),
]),
);
const revisions: Record<string, number | null> = {};
const now = this.#now();
try {
for (const mutation of input.mutations) {
const current = draft.get(mutation.id);
if (!revisionAllows(mutation.revision, current?.revision)) {
return failure(
"CONFLICT",
false,
"The offline record changed before the commit.",
);
}
if (mutation.kind === "DELETE") {
draft.delete(mutation.id);
revisions[mutation.id] = null;
continue;
}
const revision = (current?.revision ?? 0) + 1;
draft.set(
mutation.id,
structuredClone({
id: mutation.id,
revision,
payloadVersion: mutation.payloadVersion,
createdAtEpochMs: current?.createdAtEpochMs ?? now,
updatedAtEpochMs: now,
expiresAtEpochMs: mutation.expiresAtEpochMs,
value: mutation.value,
}),
);
revisions[mutation.id] = revision;
}
} catch {
return failure(
"CORRUPT_DATA",
false,
"The offline value cannot be persisted safely.",
);
}
if (estimateRecordBytes(draft) > this.#maxBytes) {
return failure(
"QUOTA_EXCEEDED",
true,
"The offline storage budget was exceeded.",
);
}
const lastAbortCheck = cancelled(input.signal);
if (lastAbortCheck) return lastAbortCheck;
this.#records.clear();
for (const [id, record] of draft) this.#records.set(id, record);
const receipt = Object.freeze({
commitId: `commit-${++this.#commitSequence}`,
revisions: Object.freeze(revisions),
replayed: false,
});
this.#receipts.set(input.idempotencyKey, { fingerprint, receipt });
return success(receipt);
}
async clearPartition(input: {
reason: "LOGOUT" | "ACCOUNT_DELETION" | "USER_REQUEST";
signal?: AbortSignal;
}): Promise<CapabilityResult<void>> {
const aborted = cancelled(input.signal);
if (aborted) return aborted;
this.#records.clear();
this.#receipts.clear();
return success(undefined);
}
status(): OfflineStoreStatus {
return this.#status;
}
close(): void {
this.#onLifecycle = null;
this.#status = Object.freeze({ kind: "CLOSED", reason: "DISPOSED" });
}
async resumeDataMigration(input: {
migrationId: string;
maxRecords: number;
timeBudgetMs: number;
signal?: AbortSignal;
}): Promise<CapabilityResult<MigrationProgress>> {
const aborted = cancelled(input.signal);
if (aborted) return aborted;
if (
input.migrationId.length === 0 ||
input.maxRecords < 1 ||
input.timeBudgetMs < 1
) {
return failure("INVALID_INPUT", false, "The migration budget is invalid.");
}
const progress = Object.freeze({
migrationId: input.migrationId,
state: "COMPLETED" as const,
processedCount: Math.min(this.#records.size, input.maxRecords),
remainingEstimate: 0,
});
this.#onLifecycle?.({ kind: "MIGRATION_PROGRESS", ...progress });
return success(progress);
}
async purgeExpired(input: {
maxRecords: number;
nowEpochMs: number;
signal?: AbortSignal;
}): Promise<CapabilityResult<Readonly<{ purgedCount: number }>>> {
const aborted = cancelled(input.signal);
if (aborted) return aborted;
if (input.maxRecords < 1) {
return failure("INVALID_INPUT", false, "The purge budget is invalid.");
}
let purgedCount = 0;
for (const [id, record] of this.#records) {
if (purgedCount >= input.maxRecords) break;
if (
record.expiresAtEpochMs !== null &&
record.expiresAtEpochMs <= input.nowEpochMs
) {
this.#records.delete(id);
purgedCount += 1;
}
}
return success({ purgedCount });
}
}
function revisionAllows(
guard: StructuredOfflineMutation<unknown>["revision"],
currentRevision: number | undefined,
): boolean {
if (guard.kind === "ANY") return true;
if (guard.kind === "MUST_NOT_EXIST") return currentRevision === undefined;
return currentRevision === guard.revision;
}
function estimateRecordBytes<T>(
records: ReadonlyMap<string, StoredRecord<T>>,
): number {
try {
return new TextEncoder().encode(JSON.stringify(Array.from(records.values())))
.byteLength;
} catch {
return Number.POSITIVE_INFINITY;
}
}
export class MemoryStorageDurabilityAdapter implements StorageDurabilityPort {
#persisted: boolean;
constructor(
private readonly usageBytes: number | null,
private readonly quotaBytes: number | null,
private readonly persistenceDecision: "GRANTED" | "DENIED",
persisted = false,
) {
this.#persisted = persisted;
}
async inspect(
signal?: AbortSignal,
): Promise<CapabilityResult<StorageEstimate>> {
return (
cancelled(signal) ??
success({
usageBytes: this.usageBytes,
quotaBytes: this.quotaBytes,
persisted: this.#persisted,
pressure: storagePressure(
this.usageBytes,
this.quotaBytes,
),
})
);
}
async requestPersistence(input: {
reason: "PROTECT_UNSYNCED_USER_DATA";
userInitiated: true;
signal?: AbortSignal;
}): Promise<CapabilityResult<"GRANTED" | "DENIED">> {
const aborted = cancelled(input.signal);
if (aborted) return aborted;
this.#persisted = this.persistenceDecision === "GRANTED";
return success(this.persistenceDecision);
}
}
function storagePressure(
usageBytes: number | null,
quotaBytes: number | null,
): StorageEstimate["pressure"] {
if (
usageBytes === null ||
quotaBytes === null ||
!Number.isFinite(usageBytes) ||
!Number.isFinite(quotaBytes) ||
usageBytes < 0 ||
quotaBytes <= 0
) {
return "UNKNOWN";
}
const ratio = usageBytes / quotaBytes;
if (ratio >= 0.85) return "CRITICAL";
if (ratio >= 0.7) return "PRESSURE";
return "NORMAL";
}
type MemoryObject = Readonly<{
descriptor: DurableObjectDescriptor;
chunks: ReadonlyArray<Uint8Array>;
}>;
type PendingObject = Readonly<{
id: DurableObjectId;
phase: "PREPARING" | "FILES_READY";
}>;
export class MemoryDurableObjectStore
implements DurableObjectStorePort, DurableObjectMaintenancePort
{
readonly #objects = new Map<DurableObjectId, MemoryObject>();
readonly #pending = new Map<DurableObjectId, PendingObject>();
#failurePhase: PendingObject["phase"] | null = null;
constructor(
private readonly maxObjectBytes: ByteCount,
private readonly backend: "OPFS" | "INDEXEDDB_BLOB" | "NONE" = "OPFS",
) {}
failNextPutAfter(phase: PendingObject["phase"]): void {
this.#failurePhase = phase;
}
async capabilities(signal?: AbortSignal) {
return (
cancelled(signal) ??
success({
backend: this.backend,
persistence: "BEST_EFFORT" as const,
maxObjectBytes: this.maxObjectBytes,
})
);
}
async put(
input: Parameters<DurableObjectStorePort["put"]>[0],
): Promise<CapabilityResult<DurableObjectDescriptor>> {
const aborted = cancelled(input.signal);
if (aborted) return aborted;
if (this.backend === "NONE") {
return failure("UNSUPPORTED", false, "Durable object storage is unsupported.");
}
if (input.declaredByteLength > this.maxObjectBytes) {
return failure("LIMIT_EXCEEDED", false, "The object exceeds its byte budget.");
}
const current = this.#objects.get(input.id);
if (
(input.expectedGeneration === null && current !== undefined) ||
(input.expectedGeneration !== null &&
current?.descriptor.generation !== input.expectedGeneration)
) {
return failure("CONFLICT", false, "The object generation changed.");
}
this.#pending.set(input.id, { id: input.id, phase: "PREPARING" });
if (this.#failurePhase === "PREPARING") {
this.#failurePhase = null;
return failure(
"PROVIDER_UNAVAILABLE",
true,
"The object writer stopped after journal preparation.",
);
}
const chunks: Uint8Array[] = [];
let totalBytes = 0;
try {
for await (const chunkResult of input.source) {
if (!chunkResult.ok) return chunkResult;
const chunk = chunkResult.value;
const duringWrite = cancelled(input.signal);
if (duringWrite) return duringWrite;
if (!(chunk instanceof Uint8Array)) {
return failure(
"CORRUPT_DATA",
false,
"The object source emitted an invalid byte chunk.",
);
}
totalBytes += chunk.byteLength;
if (
totalBytes > input.declaredByteLength ||
totalBytes > this.maxObjectBytes
) {
return failure(
"INTEGRITY_FAILED",
false,
"The object exceeded its declared size.",
);
}
chunks.push(cloneBytes(chunk));
input.onProgress({
phase: "TRANSFERRING",
transferredBytes: totalBytes,
totalBytes: input.declaredByteLength,
});
}
} catch {
return failure(
"NOT_READABLE",
true,
"The object byte source could not be read.",
);
}
if (totalBytes !== input.declaredByteLength) {
return failure(
"INTEGRITY_FAILED",
false,
"The object did not match its declared size.",
);
}
this.#pending.set(input.id, { id: input.id, phase: "FILES_READY" });
if (this.#failurePhase === "FILES_READY") {
this.#failurePhase = null;
return failure(
"PROVIDER_UNAVAILABLE",
true,
"The object writer stopped before logical commit.",
);
}
const generation = asObjectGeneration(
(current?.descriptor.generation ?? 0) + 1,
);
const chunkDigests = await Promise.all(chunks.map(sha256Hex));
const chunkSizeBytes = Math.max(
0,
...chunks.map((chunk) => chunk.byteLength),
);
const canonicalTree = new TextEncoder().encode(
[
totalBytes,
chunkSizeBytes,
...chunks.map(
(chunk, index) =>
`${chunk.byteLength}:${chunkDigests[index] ?? "missing"}`,
),
].join(":"),
);
const descriptor: DurableObjectDescriptor = Object.freeze({
id: input.id,
generation,
byteLength: asByteCount(totalBytes),
mediaType: input.mediaType,
integrity: Object.freeze({
algorithm: "SHA-256-TREE-V1",
rootDigest: await sha256Hex(canonicalTree),
chunkSizeBytes: asByteCount(chunkSizeBytes),
}),
dataClass: input.dataClass,
retention: input.retention,
});
this.#objects.set(input.id, { descriptor, chunks });
this.#pending.delete(input.id);
return success(descriptor);
}
async open(
id: DurableObjectId,
signal?: AbortSignal,
): Promise<CapabilityResult<DurableObjectRead>> {
const aborted = cancelled(signal);
if (aborted) return aborted;
const object = this.#objects.get(id);
if (!object) {
return failure("NOT_FOUND", false, "The durable object does not exist.");
}
const chunks = object.chunks.map(cloneBytes);
return success({
descriptor: object.descriptor,
chunks: chunksFrom(chunks),
});
}
async remove(
input: Parameters<DurableObjectStorePort["remove"]>[0],
): Promise<CapabilityResult<void>> {
const aborted = cancelled(input.signal);
if (aborted) return aborted;
const current = this.#objects.get(input.id);
if (!current) {
return failure("NOT_FOUND", false, "The durable object does not exist.");
}
if (current.descriptor.generation !== input.expectedGeneration) {
return failure("CONFLICT", false, "The object generation changed.");
}
this.#objects.delete(input.id);
return success(undefined);
}
async reconcile(
input: Parameters<DurableObjectMaintenancePort["reconcile"]>[0],
): Promise<CapabilityResult<ObjectStoreRecoverySummary>> {
const aborted = cancelled(input.signal);
if (aborted) return aborted;
if (input.maxEntries < 1 || input.timeBudgetMs < 1) {
return failure("INVALID_INPUT", false, "The recovery budget is invalid.");
}
const pending = Array.from(this.#pending.keys()).slice(0, input.maxEntries);
for (const id of pending) this.#pending.delete(id);
return success({
resumedCount: 0,
purgedCount: pending.length,
quarantinedCount: 0,
nextCursor: this.#pending.size > 0 ? String(pending.length) : null,
});
}
}
async function sha256Hex(bytes: Uint8Array): Promise<string> {
const buffer = new ArrayBuffer(bytes.byteLength);
new Uint8Array(buffer).set(bytes);
const digest = await crypto.subtle.digest("SHA-256", buffer);
return Array.from(new Uint8Array(digest), (byte) =>
byte.toString(16).padStart(2, "0"),
).join("");
}
async function* chunksFrom(
chunks: ReadonlyArray<Uint8Array>,
): AsyncIterable<CapabilityResult<Uint8Array>> {
for (const chunk of chunks) yield success(cloneBytes(chunk));
}
type MemoryCacheRelease = Readonly<{
manifestDigest: string;
entries: ReadonlyMap<string, PublicAssetEntry>;
}>;
type MemoryPublicResponseCacheOptions = Readonly<{
origin: string;
maxEntryBytes: number;
allowedMediaTypes: ReadonlySet<string>;
forbiddenPaths?: ReadonlyArray<string>;
}>;
export class MemoryPublicResponseCache
implements PublicResponseCacheAdmin
{
readonly #releases = new Map<string, MemoryCacheRelease>();
readonly #origin: string;
readonly #maxEntryBytes: number;
readonly #allowedMediaTypes: ReadonlySet<string>;
readonly #forbiddenPaths: ReadonlyArray<string>;
#activeReleaseId: string | null = null;
#previousReleaseId: string | null = null;
constructor(options: MemoryPublicResponseCacheOptions) {
this.#origin = new URL(options.origin).origin;
this.#maxEntryBytes = options.maxEntryBytes;
this.#allowedMediaTypes = options.allowedMediaTypes;
this.#forbiddenPaths = options.forbiddenPaths ?? [
"/config.json",
"/release-manifest.json",
"/api/",
"/auth/",
"/user/",
"/tenant/",
];
}
async stageRelease(
input: Parameters<PublicResponseCacheAdmin["stageRelease"]>[0],
): Promise<CapabilityResult<void>> {
const aborted = cancelled(input.signal);
if (aborted) return aborted;
if (
input.releaseId.length === 0 ||
input.manifestDigest.length === 0 ||
input.entries.length === 0
) {
return failure("INVALID_INPUT", false, "The cache release is invalid.");
}
const staged = new Map<string, PublicAssetEntry>();
for (const entry of input.entries) {
const key = this.#cacheKey(entry.url);
if (
key === null ||
entry.byteLength > this.#maxEntryBytes ||
entry.integritySha256.length === 0 ||
entry.requestCredentials !== "OMIT" ||
entry.dataClass !== "PUBLIC" ||
!this.#allowedMediaTypes.has(entry.mediaType) ||
staged.has(key)
) {
return failure(
"POLICY_REJECTED",
false,
"A public cache entry violates the cache policy.",
);
}
staged.set(key, Object.freeze({ ...entry, url: key }));
}
this.#releases.set(input.releaseId, {
manifestDigest: input.manifestDigest,
entries: staged,
});
return success(undefined);
}
async activateRelease(
input: Parameters<PublicResponseCacheAdmin["activateRelease"]>[0],
): Promise<CapabilityResult<void>> {
const aborted = cancelled(input.signal);
if (aborted) return aborted;
const candidate = this.#releases.get(input.releaseId);
if (
!candidate ||
candidate.manifestDigest !== input.manifestDigest ||
this.#activeReleaseId !== input.expectedPreviousReleaseId
) {
return failure(
"CONFLICT",
false,
"The cache candidate is incomplete or stale.",
);
}
this.#previousReleaseId = this.#activeReleaseId;
this.#activeReleaseId = input.releaseId;
return success(undefined);
}
async lookup(
input: Parameters<PublicResponseCacheAdmin["lookup"]>[0],
): Promise<CapabilityResult<PublicCacheLookup>> {
const aborted = cancelled(input.signal);
if (aborted) return aborted;
if (
input.requestCredentials !== "OMIT" ||
input.hasAuthorization !== false
) {
return success({ kind: "MISS", reason: "POLICY_REJECTED" });
}
if (!this.#activeReleaseId) {
return success({ kind: "MISS", reason: "NO_ACTIVE_RELEASE" });
}
const key = this.#cacheKey(input.url);
if (!key) return success({ kind: "MISS", reason: "POLICY_REJECTED" });
const entry = this.#releases
.get(this.#activeReleaseId)
?.entries.get(key);
return entry
? success({
kind: "HIT",
releaseId: this.#activeReleaseId,
entry,
})
: success({ kind: "MISS", reason: "NOT_FOUND" });
}
async inspect(
signal?: AbortSignal,
): Promise<CapabilityResult<PublicCacheInspection>> {
const aborted = cancelled(signal);
if (aborted) return aborted;
const candidateReleaseIds = Array.from(this.#releases.keys()).filter(
(releaseId) =>
releaseId !== this.#activeReleaseId &&
releaseId !== this.#previousReleaseId,
);
const ownedBytes = Array.from(this.#releases.values()).reduce(
(total, release) =>
total +
Array.from(release.entries.values()).reduce(
(releaseTotal, entry) => releaseTotal + entry.byteLength,
0,
),
0,
);
return success({
activeReleaseId: this.#activeReleaseId,
previousReleaseId: this.#previousReleaseId,
candidateReleaseIds,
ownedBytes,
});
}
async rollback(
signal?: AbortSignal,
): Promise<CapabilityResult<void>> {
const aborted = cancelled(signal);
if (aborted) return aborted;
if (!this.#previousReleaseId) {
return failure("NOT_FOUND", false, "No previous cache release is available.");
}
const active = this.#activeReleaseId;
this.#activeReleaseId = this.#previousReleaseId;
this.#previousReleaseId = active;
return success(undefined);
}
async deleteOwned(
input: Parameters<PublicResponseCacheAdmin["deleteOwned"]>[0],
): Promise<CapabilityResult<Readonly<{ deletedCount: number }>>> {
const aborted = cancelled(input.signal);
if (aborted) return aborted;
let deletedCount = 0;
if (input.roles.includes("CANDIDATE")) {
for (const releaseId of Array.from(this.#releases.keys())) {
if (
releaseId !== this.#activeReleaseId &&
releaseId !== this.#previousReleaseId
) {
this.#releases.delete(releaseId);
deletedCount += 1;
}
}
}
if (
input.roles.includes("PREVIOUS") &&
this.#previousReleaseId !== null
) {
this.#releases.delete(this.#previousReleaseId);
this.#previousReleaseId = null;
deletedCount += 1;
}
return success({ deletedCount });
}
#cacheKey(value: string): string | null {
try {
const url = new URL(value, this.#origin);
if (
url.origin !== this.#origin ||
this.#forbiddenPaths.some(
(path) =>
url.pathname === path ||
(path.endsWith("/") && url.pathname.startsWith(path)),
)
) {
return null;
}
url.hash = "";
return url.href;
} catch {
return null;
}
}
}