Skip to content

Context API

Effect GraphQL provides two context systems: ResolverContext for dynamic request-scoped values and GraphQLRequestContext for HTTP request information.

Type-safe context for passing values through the resolver hierarchy.

import { ResolverContext } from "@effect-gql/core"

Create a new typed context slot.

function make<A>(name: string): ResolverContextSlot<A>

Parameters:

NameTypeDescription
namestringIdentifier for error messages

Returns: ResolverContextSlot<A> - A typed slot for storing values

Example:

const CurrentUser = ResolverContext.make<User>("CurrentUser")
const TenantId = ResolverContext.make<string>("TenantId")

Get a value from the context. Fails if not set.

function get<A>(
slot: ResolverContextSlot<A>
): Effect.Effect<A, MissingResolverContextError, ResolverContextStore>

Example:

const user = yield* ResolverContext.get(CurrentUser)
// Type: User
// Fails with MissingResolverContextError if not set

Get a value as an Option. Returns None if not set.

function getOption<A>(
slot: ResolverContextSlot<A>
): Effect.Effect<Option.Option<A>, never, ResolverContextStore>

Example:

const maybeUser = yield* ResolverContext.getOption(CurrentUser)
// Type: Option<User>
if (Option.isSome(maybeUser)) {
console.log(maybeUser.value.name)
}

Get a value or return a default if not set.

function getOrElse<A>(
slot: ResolverContextSlot<A>,
orElse: () => A
): Effect.Effect<A, never, ResolverContextStore>

Example:

const requestId = yield* ResolverContext.getOrElse(
RequestId,
() => "unknown"
)
// Type: string (never fails)

Set a value in the context.

function set<A>(
slot: ResolverContextSlot<A>,
value: A
): Effect.Effect<void, never, ResolverContextStore>

Example:

yield* ResolverContext.set(CurrentUser, user)

Set multiple values at once.

function setMany(
values: ReadonlyArray<readonly [ResolverContextSlot<any>, any]>
): Effect.Effect<void, never, ResolverContextStore>

Example:

yield* ResolverContext.setMany([
[CurrentUser, user],
[TenantId, user.tenantId],
[RequestId, crypto.randomUUID()]
])

Check if a slot has a value set.

function has<A>(
slot: ResolverContextSlot<A>
): Effect.Effect<boolean, never, ResolverContextStore>

Example:

const isAuthenticated = yield* ResolverContext.has(CurrentUser)

Run an effect with a temporary context value. The value is set before the effect runs and restored after.

function scoped<A>(
slot: ResolverContextSlot<A>,
value: A
): <B, E, R>(
effect: Effect.Effect<B, E, R>
) => Effect.Effect<B, E, R | ResolverContextStore>

Example:

const result = yield* ResolverContext.scoped(LogPrefix, "[Resolver]")(
Effect.gen(function* () {
const prefix = yield* ResolverContext.get(LogPrefix)
console.log(prefix, "Running...")
return yield* someEffect
})
)
// LogPrefix is restored to previous value after

Layer that provides the ResolverContextStore. Include this in your service layer.

const storeLayer: Layer.Layer<ResolverContextStore>

Example:

const serviceLayer = Layer.mergeAll(
DatabaseLive,
ResolverContext.storeLayer
)

Create a new store layer programmatically.

function makeStoreLayer(): Effect.Effect<Layer.Layer<ResolverContextStore>>

The Effect Context tag for the store. Useful for advanced use cases.

const Store: Context.Tag<ResolverContextStore, ResolverContextStore>

A typed slot for storing values in the resolver context.

interface ResolverContextSlot<A> {
readonly _tag: "ResolverContextSlot"
readonly name: string
readonly _A: A // Phantom type
}

Internal storage for resolver context values.

interface ResolverContextStore {
readonly ref: Ref.Ref<HashMap.HashMap<string, unknown>>
}

Error thrown when accessing a context slot that hasn’t been set.

class MissingResolverContextError extends Error {
readonly _tag = "MissingResolverContextError"
readonly contextName: string
constructor(contextName: string)
}

Properties:

NameTypeDescription
contextNamestringThe name of the missing context slot
messagestringHuman-readable error message

HTTP request information available in resolvers.

import { GraphQLRequestContext, makeRequestContextLayer } from "@effect-gql/core"

Effect Context tag for accessing request information.

interface GraphQLRequestContext {
readonly request: {
readonly headers: Record<string, string>
readonly query: string
readonly variables?: Record<string, unknown>
readonly operationName?: string
}
}
const GraphQLRequestContext: Context.Tag<GraphQLRequestContext, GraphQLRequestContext>

Example:

const resolve = () => Effect.gen(function* () {
const ctx = yield* GraphQLRequestContext
const authHeader = ctx.request.headers.authorization
const operationName = ctx.request.operationName
// ...
})

Create a layer from request context data.

function makeRequestContextLayer(
context: GraphQLRequestContext
): Layer.Layer<GraphQLRequestContext>

Example:

const requestLayer = makeRequestContextLayer({
request: {
headers: { authorization: "Bearer token" },
query: "query { users { id } }",
variables: {},
operationName: "GetUsers"
}
})
import {
GraphQLSchemaBuilder,
query,
directive,
ResolverContext,
GraphQLRequestContext,
AuthorizationError
} from "@effect-gql/core"
import { Effect, Layer } from "effect"
import * as S from "effect/Schema"
// Define context slots
const CurrentUser = ResolverContext.make<User>("CurrentUser")
const Permissions = ResolverContext.make<Set<string>>("Permissions")
// Auth directive that populates context
const builder = GraphQLSchemaBuilder.empty.pipe(
directive("auth", {
locations: ["FIELD_DEFINITION"],
transformer: (next) => Effect.gen(function* () {
// Get auth header from request
const ctx = yield* GraphQLRequestContext
const token = ctx.request.headers.authorization?.replace("Bearer ", "")
if (!token) {
return yield* Effect.fail(new AuthorizationError({
message: "Authentication required"
}))
}
// Validate and set context
const user = yield* AuthService.validateToken(token)
const perms = yield* PermissionService.getForUser(user.id)
yield* ResolverContext.setMany([
[CurrentUser, user],
[Permissions, new Set(perms)]
])
return yield* next
})
}),
query("me", {
type: UserSchema,
directives: ["@auth"],
resolve: () => ResolverContext.get(CurrentUser)
}),
query("adminDashboard", {
type: DashboardSchema,
directives: ["@auth"],
resolve: () => Effect.gen(function* () {
const perms = yield* ResolverContext.get(Permissions)
if (!perms.has("admin:read")) {
return yield* Effect.fail(new AuthorizationError({
message: "Admin access required"
}))
}
return yield* DashboardService.getData()
})
})
)
// Service layer must include ResolverContext.storeLayer
const serviceLayer = Layer.mergeAll(
AuthServiceLive,
PermissionServiceLive,
DashboardServiceLive,
ResolverContext.storeLayer
)