import type { Context } from "./context.ts"; export interface CachePolicy { ttlMs?: number; staleWhileRevalidateMs?: number; tags?: string[] | ((ctx: Context) => string[]); } export interface LoaderDefinition { cache?: CachePolicy; load(ctx: Context): T | Promise; } export interface ActionDefinition { csrf?: boolean; run(input: I, ctx: Context): O | Promise; invalidate?: string[] | ((output: O, ctx: Context) => string[]); } export interface DefinedLoader { readonly definition: LoaderDefinition; (ctx: Context): Promise; } export interface DefinedAction { readonly definition: ActionDefinition; (input: I, ctx: Context): Promise; } export function defineLoader(definition: LoaderDefinition): DefinedLoader { return Object.assign(async (ctx: Context) => definition.load(ctx), { definition }); } export function defineAction(definition: ActionDefinition): DefinedAction { return Object.assign(async (input: I, ctx: Context) => definition.run(input, ctx), { definition, }); } /** Request-local fetch deduplication keyed by a stable string. */ export async function dedupe(ctx: Context, key: string, load: () => T | Promise): Promise { const bucket = (ctx.locals.__wrnexusData ??= new Map>()) as Map< string, Promise >; const existing = bucket.get(key); if (existing) return existing as Promise; const pending = Promise.resolve().then(load); bucket.set(key, pending); try { return await pending; } catch (error) { bucket.delete(key); throw error; } }