import { mkdir, readFile, rename, writeFile } from "node:fs/promises"; import { existsSync } from "node:fs"; import { join } from "node:path"; export interface Cache { get(key: string): Promise; put( key: string, value: Uint8Array, options?: { ttl?: number }, ): Promise; delete(key: string): Promise; } export interface CloudflareCacheLike { match(request: Request): Promise; put(request: Request, response: Response): Promise; delete(request: Request): Promise; } function cacheKey(key: string): string { return encodeURIComponent(key); } export function filesystemCache(dir: string): Cache { return { async get(key) { const path = join(dir, cacheKey(key)); if (!existsSync(path)) return undefined; return new Uint8Array(await readFile(path)); }, async put(key, value) { await mkdir(dir, { recursive: true }); const path = join(dir, cacheKey(key)); const staged = `${path}.next`; await writeFile(staged, value); await rename(staged, path); }, async delete(key) { const path = join(dir, cacheKey(key)); if (existsSync(path)) await rename(path, `${path}.deleted`); }, }; } export function cloudflareCache( cache: CloudflareCacheLike, origin = "https://muddle.cache", ): Cache { const request = (key: string) => new Request(new URL(`/${encodeURIComponent(key)}`, origin).toString()); return { async get(key) { const response = await cache.match(request(key)); return response ? new Uint8Array(await response.arrayBuffer()) : undefined; }, async put(key, value, options) { const headers = options?.ttl ? { "cache-control": `max-age=${options.ttl}` } : undefined; await cache.put(request(key), new Response(value, { headers })); }, async delete(key) { await cache.delete(request(key)); }, }; }