Context Store
Per-request context using AsyncLocalStorage. Provides a type-safe API for storing and retrieving request-scoped data (such as request ID, logger, user, etc.) across async calls. Includes helpers for Express integration, context extension, and symbol-based keys. All methods are fully typed.
API Summary
ContextStore– The main class for managing context.getInstance(): AsyncLocalStorage<Store>– Returns the underlying AsyncLocalStorage instance.getAll(): Store | undefined– Retrieves the entire context store object for the current async context.run<T>(store: Store, callback: () => T): T– Initializes a new async context and executes a callback within it.set<T>(key: ContextKey<T>, value: T): void– Sets a value in the current context store by key.get<T>(key: ContextKey<T>): T | undefined– Retrieves a value from the async context store by key.has(key: ContextKey): boolean– Checks if a key exists in the current context store.delete(key: ContextKey): boolean– Removes a value from the current context store by key.patch(values: Partial<Store>): void– Updates multiple values in the current context store at once.withValue<T, V>(key: ContextKey<V>, value: V, callback: () => T): T– Executes a callback within a branched context with a temporary value.extend(newValues: Partial<Store>, callback: () => void): void– Creates a new context that inherits values and adds/overrides new ones.createExpressMiddleware(initialValuesFactory?: () => Partial<Store>): express.RequestHandler– Creates Express middleware that initializes a context for each request.
Other exports:
TypedStoreKeys– Pre-instantiated, type-safeTypedContextKeyobjects for core and messaging keys.StoreKeys– Predefined symbols for context keys.ContextKey<T>– Type alias forsymbol | TypedContextKey<T>.TypedContextKey<T>– Type-safe wrapper class for accessing and modifying context values.getRequestId(): string | undefined– Retrieves the current request ID from context.getFromContext<T>(key: ContextKey<T>): T | undefined– Type-safe getter for context values.
Interface & Types
ContextKey
Union type accepted by all ContextStore methods, allowing transparent use of raw symbols or TypedContextKey<T> instances:
export type ContextKey<T = unknown> = symbol | TypedContextKey<T>;
StoreKeys
Predefined symbols used as keys in AsyncLocalStorage:
export const StoreKeys = {
LOGGER: Symbol('LOGGER'),
REQUEST_ID: Symbol('REQUEST_ID'),
CORRELATION_ID: Symbol('CORRELATION_ID'),
USER_ID: Symbol('USER_ID'),
TRANSACTION_ID: Symbol('TRANSACTION_ID'),
TENANT_ID: Symbol('TENANT_ID'),
TRACE_ID: Symbol('TRACE_ID'),
SPAN_ID: Symbol('SPAN_ID'),
MESSAGE_ID: Symbol('MESSAGE_ID'),
MESSAGE_TYPE: Symbol('MESSAGE_TYPE'),
QUEUE_NAME: Symbol('QUEUE_NAME')
} as const;
TypedStoreKeys
Pre-configured, strongly typed TypedContextKey instances providing autocomplete and automatic type resolution without manual casting:
export const TypedStoreKeys = {
LOGGER: new TypedContextKey<Logger>(StoreKeys.LOGGER),
REQUEST_ID: new TypedContextKey<string>(StoreKeys.REQUEST_ID),
CORRELATION_ID: new TypedContextKey<string>(StoreKeys.CORRELATION_ID),
USER_ID: new TypedContextKey<string>(StoreKeys.USER_ID),
TRANSACTION_ID: new TypedContextKey<string>(StoreKeys.TRANSACTION_ID),
TENANT_ID: new TypedContextKey<string>(StoreKeys.TENANT_ID),
TRACE_ID: new TypedContextKey<string>(StoreKeys.TRACE_ID),
SPAN_ID: new TypedContextKey<string>(StoreKeys.SPAN_ID),
MESSAGE_ID: new TypedContextKey<string>(StoreKeys.MESSAGE_ID),
MESSAGE_TYPE: new TypedContextKey<string>(StoreKeys.MESSAGE_TYPE),
QUEUE_NAME: new TypedContextKey<string>(StoreKeys.QUEUE_NAME)
} as const;
Store
export interface Store {
[key: symbol]: unknown;
}
Example Usage
Express Middleware
import { ContextStore, StoreKeys, getLogger } from '@catbee/utils/context-store';
import express from 'express';
import crypto from 'crypto';
const app = express();
// Set up request context middleware
app.use((req, res, next) => {
// Generate request ID from header or create a new one
const requestId = req.headers['x-request-id']?.toString() || crypto.randomUUID();
// Run request in context with request ID
ContextStore.run({ [StoreKeys.REQUEST_ID]: requestId }, () => {
// Create a logger with request ID and store it in context
const logger = getLogger().child({ requestId });
ContextStore.set(StoreKeys.LOGGER, logger);
logger.info('Request started', {
method: req.method,
path: req.path
});
next();
});
});
// Access context in route handlers
app.get('/api/items', (req, res) => {
// Get request ID from anywhere in the request lifecycle
const requestId = getRequestId();
// Get logger from context
const logger = ContextStore.get<ReturnType<typeof getLogger>>(StoreKeys.LOGGER);
logger.info('Getting items', { count: 10 });
res.json({ items: [], requestId });
});
// Or use the built-in middleware
app.use(
ContextStore.createExpressMiddleware(() => ({
[StoreKeys.REQUEST_ID]: crypto.randomUUID()
}))
);
Function Documentation & Usage Examples
getInstance()
Returns the underlying AsyncLocalStorage instance for advanced access.
Method Signature:
getInstance(): AsyncLocalStorage<Store>
Returns:
- The AsyncLocalStorage instance.
Examples:
import { ContextStore } from '@catbee/utils/context-store';
const storage = ContextStore.getInstance();
const store = storage.getStore();
getAll()
Retrieves the entire context store object for the current async context.
Method Signature:
getAll(): Store | undefined
Returns:
- The current context store object or
undefinedif no context is active.
Examples:
import { ContextStore } from '@catbee/utils/context-store';
const allValues = ContextStore.getAll();
run()
Initializes a new async context and executes a callback within it.
Method Signature:
run(store: Store, callback: () => T): T
Parameters:
store: An object containing initial key-value pairs for the context.callback: A function to execute within the new context.
Returns:
- The return value of the callback.
Examples:
import { ContextStore } from '@catbee/utils/context-store';
ContextStore.run({ [StoreKeys.REQUEST_ID]: 'id' }, () => {
// Context is active here
const requestId = ContextStore.get<string>(StoreKeys.REQUEST_ID);
console.log(requestId); // "id"
});
set()
Sets a value in the current context store by key. Accepts a symbol or a TypedContextKey<T>.
Method Signature:
set<T>(key: ContextKey<T>, value: T): void
Parameters:
key: AsymbolorTypedContextKey<T>identifying the value.value: The value to store.
Throws:
Errorif called outside an active context (not within a.run()call).
Examples:
import { ContextStore, StoreKeys, TypedStoreKeys } from '@catbee/utils/context-store';
// Using TypedStoreKeys
ContextStore.set(TypedStoreKeys.REQUEST_ID, 'req_12345');
ContextStore.set(TypedStoreKeys.USER_ID, 'usr_98765');
ContextStore.set(TypedStoreKeys.QUEUE_NAME, 'orders.processing');
// Using raw Symbol
ContextStore.set(StoreKeys.TRANSACTION_ID, 'tx_9999');
get()
Retrieves a value from the async context store by key. Accepts a symbol or a TypedContextKey<T>.
Method Signature:
get<T>(key: ContextKey<T>): T | undefined
Parameters:
key: AsymbolorTypedContextKey<T>identifying the value.
Returns:
- The value associated with the key or
undefinedif not found.
Examples:
import { ContextStore, StoreKeys, TypedStoreKeys } from '@catbee/utils/context-store';
// Type is inferred automatically when using TypedStoreKeys: string | undefined
const userId = ContextStore.get(TypedStoreKeys.USER_ID);
const queueName = ContextStore.get(TypedStoreKeys.QUEUE_NAME);
// Using raw symbol with explicit type argument
const txId = ContextStore.get<string>(StoreKeys.TRANSACTION_ID);
has()
Checks if a key exists in the current context store. Accepts a symbol or a TypedContextKey.
Method Signature:
has(key: ContextKey): boolean
Parameters:
key: AsymbolorTypedContextKeyto check.
Returns:
trueif the key exists, otherwisefalse.
import { ContextStore, TypedStoreKeys } from '@catbee/utils/context-store';
if (ContextStore.has(TypedStoreKeys.USER_ID)) {
// user ID exists in context
}
delete()
Removes a value from the current context store by key. Accepts a symbol or a TypedContextKey.
Method Signature:
delete(key: ContextKey): boolean
Parameters:
key: AsymbolorTypedContextKeyto remove.
Returns:
trueif the key was found and deleted, otherwisefalse.
Throws:
Errorif called outside an active context.
Examples:
import { ContextStore, TypedStoreKeys } from '@catbee/utils/context-store';
ContextStore.delete(TypedStoreKeys.USER_ID);
patch()
Updates multiple values in the current context store at once.
Method Signature:
patch(values: Partial<Record<symbol, unknown>>): void
Parameters:
values: An object containing key-value pairs to update in the context.
Throws:
Errorif called outside an active context.
Examples:
import { ContextStore, StoreKeys } from '@catbee/utils/context-store';
ContextStore.patch({
[StoreKeys.USER_ID]: 'usr_123',
[StoreKeys.TENANT_ID]: 'tenant_abc',
[StoreKeys.MESSAGE_ID]: 'msg_456'
});
withValue()
Executes a callback within a branched async context containing a temporary value. Built with full async boundary safety via AsyncLocalStorage.run—the parent context remains untouched and cannot be corrupted by nested async work.
Method Signature:
withValue<T, V>(key: ContextKey<V>, value: V, callback: () => T): T
Parameters:
key: AsymbolorTypedContextKey<V>identifying the value.value: The temporary value to set for the duration of the callback.callback: A function to execute within the branched context.
Returns:
- The return value of the callback.
Throws:
Errorif called outside an active context.
Examples:
import { ContextStore, TypedStoreKeys } from '@catbee/utils/context-store';
ContextStore.withValue(TypedStoreKeys.USER_ID, 'temporary-system-user', () => {
// USER_ID is 'temporary-system-user' within this async branch
console.log(TypedStoreKeys.USER_ID.get()); // 'temporary-system-user'
});
// Original USER_ID is unaffected outside the callback
extend()
Creates a new context that inherits values from the current context and adds/overrides new ones.
Method Signature:
extend<T>(newValues: Partial<Record<symbol, unknown>>, callback: () => T): T
Parameters:
newValues: An object containing key-value pairs to add or override in the new context.callback: A function to execute within the new context.
Returns:
- The return value of the callback.
Examples:
import { ContextStore } from "@catbee/utils";
ContextStore.extend({ [StoreKeys.TENANT_ID]: "tenant-42" }, () => {
// context includes TENANT_ID here
});
createExpressMiddleware()
Creates Express middleware that initializes a context for each request.
Method Signature:
createExpressMiddleware(initialValuesFactory?: (req: any) => Partial<Record<symbol, unknown>>): express.RequestHandler
Parameters:
initialValuesFactory: An optional function that takes the request object and returns an object of initial key-value pairs for the context.
Returns:
- An Express middleware function.
import { ContextStore } from '@catbee/utils/context-store';
import crypto from 'crypto';
app.use(
ContextStore.createExpressMiddleware(req => ({
[StoreKeys.REQUEST_ID]: req.headers['x-request-id']?.toString() || crypto.randomUUID()
}))
);
getRequestId()
Retrieves the current request ID from the async context, if available.
Method Signature:
getRequestId(): string | undefined
Returns:
- The current request ID or
undefinedif not set.
Examples:
import { getRequestId } from '@catbee/utils/context-store';
const requestId = getRequestId();
getFromContext()
Type-safe getter for context values. Accepts either a raw symbol or a TypedContextKey<T>.
Method Signature:
getFromContext<T>(key: ContextKey<T>): T | undefined
Parameters:
key: TheContextKey(symbol orTypedContextKey<T>) to retrieve.
Returns:
- The typed value from the store or
undefinedif not found.
Examples:
import { getFromContext, StoreKeys, TypedStoreKeys } from '@catbee/utils/context-store';
// Automatically typed via TypedStoreKeys
const requestId = getFromContext(TypedStoreKeys.REQUEST_ID);
const queueName = getFromContext(TypedStoreKeys.QUEUE_NAME);
// Or with raw symbol and explicit type parameter
const correlationId = getFromContext<string>(StoreKeys.CORRELATION_ID);
TypedContextKey
Type-safe wrapper class for accessing and modifying context values with specific types.
Class Definition:
class TypedContextKey<T> {
constructor(symbol: symbol, defaultValue?: T);
get(): T | undefined;
set(value: T): void;
exists(): boolean;
delete(): boolean;
getSymbol(): symbol;
}
Constructor Parameters:
symbol: The unique symbol for this key.defaultValue: Optional default value returned byget()if key is not found in context.
Methods:
get(): Gets the current value for this key, or the default value if not found.set(value: T): Sets the value for this key in the current context.exists(): Checks if this key exists in the context.delete(): Deletes this key from the context.getSymbol(): Returns the underlyingsymbolfor this key.
Examples:
import { TypedContextKey, TypedStoreKeys, StoreKeys } from '@catbee/utils/context-store';
interface UserProfile {
id: string;
roles: string[];
}
// 1. Create a custom typed key
const USER_PROFILE_KEY = Symbol('USER_PROFILE');
const UserProfileKey = new TypedContextKey<UserProfile>(USER_PROFILE_KEY);
// 2. Type-safe operations
UserProfileKey.set({ id: 'u_123', roles: ['admin'] });
const profile = UserProfileKey.get(); // Type: UserProfile | undefined
if (UserProfileKey.exists()) {
console.log('User profile is in context');
}
// Retrieve underlying symbol
const rawSymbol = UserProfileKey.getSymbol();
// 3. Or use built-in pre-configured TypedStoreKeys directly
TypedStoreKeys.USER_ID.set('usr_9999');
const currentUserId = TypedStoreKeys.USER_ID.get();