import type { PublicCacheAsset, PublicCacheReleaseManifest, } from "../../src/application/ports/browser-file-storage/cache-storage-ports.ts"; import { createDefaultPublicCachePolicy, type PublicCacheRuntimePolicy, } from "../../src/adapters/cache-storage/public-cache-policy.ts"; import { computePublicCacheManifestDigestHex, type PublicCacheMutationLock, } from "../../src/adapters/cache-storage/public-response-cache-adapter.ts"; export class MemoryCache { readonly responses: Array> = []; async match(request: RequestInfo | URL): Promise { const url = request instanceof Request ? request.url : new URL(String(request)).href; const nativeRequest = request instanceof Request ? request : new Request(url); return this.responses .find( (entry) => entry.request.url === url && varyMatches(entry.request, nativeRequest, entry.response), ) ?.response.clone(); } async put(request: RequestInfo | URL, response: Response): Promise { const url = request instanceof Request ? request.url : new URL(String(request)).href; const nativeRequest = request instanceof Request ? request.clone() : new Request(url); const existing = this.responses.findIndex( (entry) => entry.request.url === url && varyMatches(entry.request, nativeRequest, response), ); const entry = { request: nativeRequest, response: response.clone(), }; if (existing >= 0) this.responses.splice(existing, 1, entry); else this.responses.push(entry); } } function varyMatches( storedRequest: Request, incomingRequest: Request, response: Response, ): boolean { const vary = response.headers.get("vary"); if (!vary) return true; return vary .split(",") .map((name) => name.trim().toLowerCase()) .every( (name) => storedRequest.headers.get(name) === incomingRequest.headers.get(name), ); } export class MemoryCacheStorage { readonly caches = new Map(); async open(name: string): Promise { let cache = this.caches.get(name); if (!cache) { cache = new MemoryCache(); this.caches.set(name, cache); } return cache as unknown as Cache; } async keys(): Promise { return [...this.caches.keys()]; } async delete(name: string): Promise { return this.caches.delete(name); } } export const immediateLock: PublicCacheMutationLock = { async run(_signal, task) { return await task(); }, }; export function deferred() { let settle: ((value: Value) => void) | undefined; const promise = new Promise((resolve) => { settle = resolve; }); return Object.freeze({ promise, resolve(value: Value): void { settle?.(value); }, }); } export async function digestHex(bytes: Uint8Array): Promise { const digest = await globalThis.crypto.subtle.digest( "SHA-256", Uint8Array.from(bytes), ); return [...new Uint8Array(digest)] .map((byte) => byte.toString(16).padStart(2, "0")) .join(""); } export async function manifestFor( releaseRegistryId: string, assets: readonly PublicCacheAsset[], policy: PublicCacheRuntimePolicy = createDefaultPublicCachePolicy( "https://assets.example.test", ), ): Promise { return { releaseRegistryId, assets, manifestDigestHex: await computePublicCacheManifestDigestHex( globalThis.crypto, releaseRegistryId, assets, policy, ), }; }