UNPKG

alepha

Version:

Easy-to-use modern TypeScript framework for building many kind of applications.

80 lines (70 loc) 2.25 kB
import { createMiddleware, type Middleware } from "alepha"; /** * Lightweight in-process memoization middleware. * * Caches handler results in a plain `Map` — no external store, no serialization, * no provider dependency. Process-local only. Entries live until eviction by capacity. * * ```typescript * class Api { * getStats = $action({ * use: [$memoize({ max: 100 })], * handler: async () => this.repo.aggregate(), * }); * } * ``` * * > For more advanced caching, use `$cache` from "alepha/cache" instead — it supports TTL, invalidation, external stores (Redis). */ export const $memoize = (options?: MemoizeOptions): Middleware => { return createMiddleware({ name: "$memoize", options: options as unknown as Record<string, unknown>, handler: ({ next }) => { const store = new Map<string, any>(); const maxSize = options?.max ?? 1000; const keyFn = options?.key ?? ((...args: any[]) => JSON.stringify(args)); return async (...args) => { const key = keyFn(...args); if (store.has(key)) { return store.get(key); } // Store the promise immediately to deduplicate concurrent calls // for the same key (thundering herd prevention). const promise = next(...args); store.set(key, promise); try { const result = await promise; // Replace the promise with the resolved value store.set(key, result); // Evict oldest if at capacity if (store.size > maxSize) { const firstKey = store.keys().next().value; if (firstKey !== undefined) { store.delete(firstKey); } } return result; } catch (error) { // Don't cache failed results store.delete(key); throw error; } }; }, }); }; export interface MemoizeOptions { /** * Maximum number of entries to keep in the cache. * When exceeded, the oldest entry is evicted (FIFO). * * @default 1000 */ max?: number; /** * Custom key function. Receives the handler's arguments. * By default, `JSON.stringify(args)` is used. */ key?: (...args: any[]) => string; }