57 lines
1.6 KiB
TypeScript
57 lines
1.6 KiB
TypeScript
import type { Context } from "./context.ts";
|
|
|
|
export interface CachePolicy {
|
|
ttlMs?: number;
|
|
staleWhileRevalidateMs?: number;
|
|
tags?: string[] | ((ctx: Context) => string[]);
|
|
}
|
|
|
|
export interface LoaderDefinition<T> {
|
|
cache?: CachePolicy;
|
|
load(ctx: Context): T | Promise<T>;
|
|
}
|
|
|
|
export interface ActionDefinition<I, O> {
|
|
csrf?: boolean;
|
|
run(input: I, ctx: Context): O | Promise<O>;
|
|
invalidate?: string[] | ((output: O, ctx: Context) => string[]);
|
|
}
|
|
|
|
export interface DefinedLoader<T> {
|
|
readonly definition: LoaderDefinition<T>;
|
|
(ctx: Context): Promise<T>;
|
|
}
|
|
|
|
export interface DefinedAction<I, O> {
|
|
readonly definition: ActionDefinition<I, O>;
|
|
(input: I, ctx: Context): Promise<O>;
|
|
}
|
|
|
|
export function defineLoader<T>(definition: LoaderDefinition<T>): DefinedLoader<T> {
|
|
return Object.assign(async (ctx: Context) => definition.load(ctx), { definition });
|
|
}
|
|
|
|
export function defineAction<I, O>(definition: ActionDefinition<I, O>): DefinedAction<I, O> {
|
|
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<T>(ctx: Context, key: string, load: () => T | Promise<T>): Promise<T> {
|
|
const bucket = (ctx.locals.__wrnexusData ??= new Map<string, Promise<unknown>>()) as Map<
|
|
string,
|
|
Promise<unknown>
|
|
>;
|
|
const existing = bucket.get(key);
|
|
if (existing) return existing as Promise<T>;
|
|
const pending = Promise.resolve().then(load);
|
|
bucket.set(key, pending);
|
|
try {
|
|
return await pending;
|
|
} catch (error) {
|
|
bucket.delete(key);
|
|
throw error;
|
|
}
|
|
}
|