UNPKG

@beignet/core

Version:

Core framework primitives for Beignet

107 lines 4.2 kB
/** * @beignet/core/memo * * Request-scoped memoization for port lookups and other async functions. * * `createMemo(fn)` deduplicates calls with the same arguments for the * lifetime of one request: the server enters a memo scope around every HTTP * request and `runServiceContext(...)` execution, and the scope's cache dies * with it. There is no TTL and no cross-request state, so memoized reads can * never serve data staler than the request that fetched them. * * Outside any scope — plain scripts, `createServiceContext(...)` callers — * memoized functions call straight through without caching. */ /** * Error thrown when a default cache key cannot be derived from arguments. */ export declare class MemoKeyError extends Error { constructor(message: string); } /** * Instrumentation event emitted by memoized functions inside a scope that * carries a recorder. */ export interface MemoInstrumentationEvent { /** Whether the call was served from the scope cache or filled it. */ kind: "hit" | "miss"; /** Memo name from `createMemo` options, defaulting to the function name. */ memo: string; /** Encoded argument key for the call. */ key: string; /** Fill duration for misses, rounded to two decimals. */ durationMs?: number; /** Set when a miss's underlying call rejected (the entry is evicted). */ failed?: boolean; } /** * Run a function inside a memo scope with an explicit recorder. * * Internal to the server runtime — apps should use `runMemoScope(...)`. * Always uses `AsyncLocalStorage.run` (never `enterWith`), so callers' * continuations never resume through a dangling frame. */ export declare function runWithMemoScope<T>(options: { record?: (event: MemoInstrumentationEvent) => void; }, fn: () => T): T; /** * Run a function inside a fresh memo scope. * * Use this in scripts and unit tests to give memoized functions a cache * lifetime. Nested scopes start empty; an active recorder is inherited so * instrumentation keeps flowing. */ export declare function runMemoScope<T>(fn: () => T): T; /** * Memoized function returned by `createMemo(...)`. */ export type Memoized<F extends (...args: never[]) => unknown> = F & { /** * Drop the current scope's entry for these arguments, so the next call * re-executes. Call this from mutation methods that make a memoized read * stale within the same request. * * @returns Whether an entry existed. */ invalidate(...args: Parameters<F>): boolean; /** * Drop all of this function's entries in the current scope. */ clear(): void; }; /** * Options for `createMemo(...)`. */ export interface CreateMemoOptions<F extends (...args: never[]) => unknown> { /** * Name used in instrumentation events and key errors. Defaults to the * wrapped function's name. */ name?: string; /** * Build the cache key from the call arguments. Defaults to a structural, * type-tagged encoding of the arguments that throws `MemoKeyError` for * values it cannot encode deterministically (functions, symbols, class * instances, circular references). */ key?: (...args: Parameters<F>) => string; } /** * Wrap an async lookup so calls with the same arguments run once per request. * * Within a memo scope the first call executes and every later call with the * same key returns the same value — including the same in-flight promise, so * concurrent calls share one execution. Rejected promises are evicted, so a * failed lookup is retried by the next call rather than memoized. Outside a * scope the function calls through uncached. * * Memoize reads, not mutations. When a mutation in the same infra module * makes a memoized read stale, call `memoized.invalidate(...)` with the * read's arguments. * * @param fn - Function to memoize. `this` is not forwarded. * @param options - Optional name and key builder. * @returns The function with `invalidate(...)` and `clear()` attached. */ export declare function createMemo<F extends (...args: never[]) => unknown>(fn: F, options?: CreateMemoOptions<F>): Memoized<F>; //# sourceMappingURL=index.d.ts.map