Async Store and Node Context
For async storage (local or distributed) and context propagation in Node.js, the library offers two opt-in subpaths that are not imported automatically from the root entrypoint.
AsyncMemoryStore
Section titled “AsyncMemoryStore”A local in-memory store with TTL (time-to-live) support and atomic operations:
import { AsyncMemoryStore } from 'annotations-design-patterns/store';
const store = new AsyncMemoryStore<string, number>();
// Store a value with TTLawait store.update('attempts', (value) => (value ?? 0) + 1, { ttlMs: 60_000 });
// Read a valueconst attempts = await store.get('attempts');
// Verify the value has TTLawait store.requireAtomicStore(store, 'circuitBreaker');Methods
Section titled “Methods”get(key: K): Promise<V | undefined>— Reads a valueset(key: K, value: V, options?: { ttlMs?: number }): Promise<void>— Sets a valueupdate(key: K, fn: (value: V | undefined) => V | Promise<V>, options?: { ttlMs?: number }): Promise<V>— Updates by applying a function and returns the committed valuedelete(key: K): Promise<boolean>— Deletes one entryclear(): Promise<void>— Clears the entire store
Atomicity and TTL
Section titled “Atomicity and TTL”requireAtomicStore(store, operationName) validates that the store supports:
- Atomic operations —
get/set/updatewithout race conditions - TTL — automatic expiration after
ttlMs
Throws if the store doesn’t guarantee both.
Node Context with AsyncLocalStorage
Section titled “Node Context with AsyncLocalStorage”AsyncLocalStorage (native in Node.js) lets you associate data with an async context without parameters:
import { getRequestContext, runWithRequestContext } from 'annotations-design-patterns/node';
// At the start of an HTTP requestawait runWithRequestContext({ requestId: 'req-42', userId: '123' }, async () => { // Inside, retrieve the context const ctx = getRequestContext(); console.log(ctx?.requestId); // 'req-42'
// Flows through async operations await someAsyncFunction();});Methods
Section titled “Methods”runWithRequestContext<T>(context: RequestContext, fn: () => T): Promise<T | Awaited<T>>— Executes a function with contextgetRequestContext(): RequestContext | undefined— Reads the current context (orundefinedif none)
Use in middleware
Section titled “Use in middleware”// Example with Expressapp.use((req, res, next) => { runWithRequestContext({ requestId: req.id, userId: req.user?.id }, () => next());});
// In your handlersapp.get('/users/:id', async (req, res) => { const ctx = getRequestContext(); const user = await fetchUser(req.params.id); console.log(`[${ctx?.requestId}] Loaded user ${ctx?.userId}`); res.json(user);});Combination: store + context
Section titled “Combination: store + context”You can combine them to store data per-request or per-session:
import { AsyncMemoryStore } from 'annotations-design-patterns/store';import { getRequestContext, runWithRequestContext } from 'annotations-design-patterns/node';
const requestStore = new AsyncMemoryStore<string, number>();
await runWithRequestContext({ requestId: 'req-42' }, async () => { const ctx = getRequestContext();
// Store counters per request await requestStore.update(`${ctx?.requestId}:calls`, (v) => (v ?? 0) + 1);});Limitations
Section titled “Limitations”- AsyncMemoryStore is local to the process — not distributed across instances. For clustered/distributed scenarios, use a remote store that implements the interface.
- Node 20+ required —
AsyncLocalStorageis native since Node.js 12, but the/nodesubpath is only published for Node 20+. - Don’t import in the browser —
AsyncLocalStoragedoesn’t exist in web clients.
For types and advanced methods
Section titled “For types and advanced methods”See the Store Reference and Node Reference.

