@beignet/core
Version:
Core framework primitives for Beignet
107 lines • 4.2 kB
TypeScript
/**
* @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