All files / libs/observability/src request-context.ts

100% Statements 12/12
100% Branches 2/2
100% Functions 4/4
100% Lines 11/11

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 4219x                             19x   19x       12x     19x 38x     19x 42x               19x 3x 3x    
import { AsyncLocalStorage } from 'node:async_hooks';
 
export interface RequestContext {
  requestId: string;
  /** Authenticated user (JWT subject), set at the gateway after the token is
   * verified and propagated to services via the x-user-id header. */
  userId?: string;
}
 
/**
 * AsyncLocalStorage keeps the request context reachable from anywhere on the
 * async call path — service methods, event publishers — without threading a
 * parameter through every signature or resorting to request-scoped providers
 * (which would re-instantiate the whole injection subtree per request).
 */
const storage = new AsyncLocalStorage<RequestContext>();
 
export function runWithRequestContext<T>(
  context: RequestContext,
  fn: () => T,
): T {
  return storage.run(context, fn);
}
 
export function currentRequestId(): string | undefined {
  return storage.getStore()?.requestId;
}
 
export function currentUserId(): string | undefined {
  return storage.getStore()?.userId;
}
 
/**
 * Authentication happens after the context middleware opened the store (the
 * guard runs later in the request lifecycle), so the user id is attached by
 * mutating the already-open context rather than opening a new one.
 */
export function setCurrentUserId(userId: string): void {
  const store = storage.getStore();
  if (store) store.userId = userId;
}