Files
WRNexusJS/packages/core/src/data.ts
T
2026-07-22 17:29:08 +05:30

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;
}
}