brick.ts

Context API Reference

Detailed specification of BaseContext, ActionContext, and context lifecycle guarantees.

llms.txt

This reference documents the TypeScript interfaces, fields, and lifecycle guarantees of the Brick-TS context system.

BaseContext Interface

BaseContext is the foundational object passed to all service context resolvers and action executions:

export interface BaseContext<TDb = any> {
  request: Request;
  db: TDb;
  logger: Logger;
  traceId: string;
  requestId?: string;
  caller?: string;
  tenantId?: string;
  services?: Record<string, any>;
  metadata?: Record<string, unknown>;
}

Properties Breakdown

PropertyTypeDescription
requestRequestStandard web Request for the active HTTP call. In-process calls without a request receive a placeholder Request("http://localhost").
dbTDbThe database client from the service's database handle (service.getDb()). Undefined when the service has no database.
loggerLoggerLogger with .info(), .warn(), .error(), and .debug() methods. The default logger prefixes output with the level (e.g. [INFO]).
traceIdstringCorrelation id. Over HTTP it is taken from the x-trace-id request header or minted (e.g. tr_1), and echoed back as an x-trace-id response header.
requestIdstring | undefinedRequest id. Over HTTP it is taken from the x-request-id header or minted (e.g. req_1), and echoed back as an x-request-id response header. The HTTP runtime always sets it.
servicesRecord<string, any>Reserved. The HTTP runtime and default context set it to an empty object.
metadataRecord<string, unknown>Free-form metadata. The HTTP action runtime sets it to { headers } (the request headers).

Logger Interface

The logger property on BaseContext adheres to:

export interface Logger {
  info: (...args: any[]) => void;
  warn: (...args: any[]) => void;
  error: (...args: any[]) => void;
  debug: (...args: any[]) => void;
}

Arguments are formatted as console.log would format them. While a dashboard is on (brick dev, or brick start with the dashboard option), a line logged while a traced request is running goes to the dashboard (the Logs tab, and the request's own details) instead of the terminal, and so does a plain console.log, info, warn, error, or debug; see Logs in the dashboard. With no dashboard, nothing is captured: both print to the console as before, as do lines logged outside a request.

ActionContext

ActionContext is the intersection of BaseContext and your service's custom context resolver return type:

export type ActionContext<
  TDb = any,
  TServiceContext = DefaultServiceContext,
> = BaseContext<TDb> & TServiceContext;

Inside an action's execute({ ctx }) or CRUD hook, ctx includes all fields from BaseContext plus whatever your service context resolver returned.

NarrowAuthorizedContext<TCtx>

When an action declares an authorize guard, Brick wraps the context with NarrowAuthorizedContext:

export type NarrowAuthorizedContext<TCtx> = TCtx extends { user: infer U }
  ? [U] extends [null | undefined]
    ? TCtx
    : Omit<TCtx, "user"> & { user: NonNullable<U> }
  : TCtx;

This utility narrows ctx.user from User | null to NonNullable<User> at the type level. Inside execute({ ctx }) of an action with authorize, you do not need optional chaining or null assertions on ctx.user. The narrowing is a type assertion only: it is your authorize function that must actually reject calls without a user.

Context Isolation Across Service Proxies

When calling actions with createServiceProxy("targetService") (call signature: proxy.actionName(input, ctx?)), Brick isolates execution contexts:

  1. Envelope allowlist: Only user, traceId, requestId, and tenantId from the proxy's options.context or per-call context are forwarded, plus caller as described below. Per-call values take precedence over proxy defaults. Custom fields such as session and isOwner are dropped.
  2. Independent Context Resolution: The target service receives the envelope in its resolver input and uses its own database. Resolver return values take precedence over envelope values, including explicit null or undefined. Direct action calls enforce the same envelope allowlist for caller custom fields while accepting runtime fields such as request.
  3. caller Field: The proxy sets ctx.caller to options.from — the name of the service that created the proxy, passed to createServiceProxy(target, { from: "..." }) — never the target service's name. When from is provided, any caller in options.context or the per-call context is ignored, so it cannot be spoofed. Without from, an explicitly supplied caller still passes through; otherwise caller is left unset.

Only in-process dispatch is implemented; creating a proxy with { isMonolith: false } throws when an action is called.

The resolver input also includes user?: unknown, an identity claim. Validate it before use. tenantId is a claim to validate against the user's access, not proof of tenant membership. Internal proxy calls have a placeholder request without browser cookies; resolve their identity from the envelope according to the receiving service's trust policy.