RequestContext
RequestContext is an Effect service that carries per-request data through the pipeline without explicit argument threading. It is populated at the API entry point and discarded after the response is sent.
Type Definition
type t = {
correlationId: string,
identity: Identity.t,
claims: dict<string>,
}
| Field | Description | Persisted? |
|---|---|---|
correlationId | Links related messages across the system. | Yes — as meta.correlationId |
identity | The authenticated user for this request (see Identity). | Only identity.userId — as meta.user |
claims | Custom key-value pairs for per-request context (e.g., tenant ID, feature flags). | No |
Persisted vs. In-Memory
+---------------------------------------------------------------------+
| RequestContext (in-memory, per-request) |
| |
| correlationId: string --> meta.correlationId (persisted) |
| identity: Identity.t --> meta.user = identity.userId (persisted) |
| claims: dict<string> --> NOT persisted |
| |
| Available to: resolvers, application code |
| Lifetime: single request, discarded after response |
+---------------------------------------------------------------------+
The claims dict is purely transient. Use it for information that should influence request processing but should not be stored in the event stream (e.g., tenant context, feature flags, tracing metadata).
Usage
Providing RequestContext
At the Lambda handler or resolver entry point:
let ctx: RequestContext.t = {
correlationId: event.meta.correlationId,
identity,
claims: Dict.make(),
}
myEffect
->Effect.provideService(RequestContext.tag, ctx)
->Effect.runPromise
Reading RequestContext
Inside any Effect in the pipeline:
Effect.serviceWith(RequestContext.tag, ctx => {
let userId = ctx.identity.userId
let tenantId = ctx->RequestContext.getClaim("tenantId")
// ...
})
In Tests
The test() constructor provides sensible defaults:
// All defaults: anonymous identity, empty claims, "test-correlation-id"
let ctx = RequestContext.test()
// Custom identity
let ctx = RequestContext.test(
~identity={
userId: "user-1",
username: "alice",
groups: ["admin"],
provider: Cognito,
},
)
// Custom claims
let ctx = RequestContext.test(
~claims=Dict.fromArray([("tenantId", "acme")]),
)
Helpers
getClaim
Retrieve a claim value from the context:
let getClaim: (t, string) => option<string>
switch ctx->RequestContext.getClaim("tenantId") {
| Some(tenantId) => // use tenant
| None => // no tenant claim
}
withClaim
Return a new context with an added claim (immutable):
let withClaim: (t, string, string) => t
let enriched = ctx->RequestContext.withClaim("tenantId", "acme")
// original ctx is unchanged
How Identity Flows Through the System
- API boundary (GraphQL resolver, Lambda handler): Extract identity from the request (header, JWT, Cognito context).
- RequestContext: Populate
identityfield. Setpayload.meta.user = identity.userId. - CommandGenerator: Transforms
payload.metaintoMessage.meta. Theuserfield is propagated unchanged. - Event persistence:
meta.user(=identity.userId) andmeta.correlationIdare stored with every event. - Response: RequestContext is discarded. Full identity (groups, claims, provider) is not retained.