GraphQLSchemaBuilder
The GraphQLSchemaBuilder is an immutable, pipeable builder for constructing GraphQL schemas. It accumulates type definitions and resolver registrations, then builds a standard GraphQLSchema.
Import
Section titled “Import”import { GraphQLSchemaBuilder } from "@effect-gql/core"Creating a Builder
Section titled “Creating a Builder”GraphQLSchemaBuilder.empty
Section titled “GraphQLSchemaBuilder.empty”Creates a new empty schema builder.
const builder = GraphQLSchemaBuilder.empty// Type: GraphQLSchemaBuilder<never>The type parameter R tracks accumulated service requirements from resolvers.
Root Operation Methods
Section titled “Root Operation Methods”query(name, config)
Section titled “query(name, config)”Register a Query field.
builder.query("users", { type: S.Array(UserSchema), args?: { ... }, description?: "...", deprecationReason?: "...", resolve: (args) => Effect.succeed([...])})Parameters:
| Name | Type | Description |
|---|---|---|
name | string | Field name |
config.type | Schema<A> | Return type schema |
config.args | Record<string, Schema> | Optional arguments |
config.description | string | Optional field description |
config.deprecationReason | string | Mark field as deprecated |
config.resolve | (args) => Effect<A, E, R> | Resolver function |
Returns: GraphQLSchemaBuilder<R | R2> (accumulates service requirements)
mutation(name, config)
Section titled “mutation(name, config)”Register a Mutation field. Same signature as query().
builder.mutation("createUser", { type: UserSchema, args: { name: S.String, email: S.String }, resolve: ({ name, email }) => Effect.gen(function* () { const db = yield* Database return yield* db.createUser({ name, email }) })})subscription(name, config)
Section titled “subscription(name, config)”Register a Subscription field. The resolver must return a Stream.
builder.subscription("messageAdded", { type: MessageSchema, args: { channelId: S.String }, resolve: ({ channelId }) => Effect.gen(function* () { const pubsub = yield* PubSubService return pubsub.subscribe(`channel:${channelId}`) })})Returns: The resolver must return Effect<Stream<A>, E, R>.
Type Registration Methods
Section titled “Type Registration Methods”objectType(config)
Section titled “objectType(config)”Register a GraphQL Object type.
builder.objectType({ name: "User", // Required for plain structs schema: UserSchema, description?: "..."})
// Name is optional for TaggedStruct/TaggedClass/Classbuilder.objectType({ schema: S.TaggedStruct("User", { id: S.String, name: S.String })})Type Name Inference:
S.TaggedStruct("Name", {...})→ Name extracted from tagS.TaggedClass("Name")→ Name extracted from tagS.Class("Name")→ Name extracted from class- Plain
S.Struct({...})→ Requires explicitnameparameter
interfaceType(config)
Section titled “interfaceType(config)”Register a GraphQL Interface type.
builder.interfaceType({ name: "Node", schema: NodeSchema, description?: "..."})unionType(config)
Section titled “unionType(config)”Register a GraphQL Union type.
builder.unionType({ name: "SearchResult", types: ["User", "Post", "Comment"], description?: "...", resolveType?: (value) => { // Return the type name based on the value if ("email" in value) return "User" if ("title" in value) return "Post" return "Comment" }})enumType(config)
Section titled “enumType(config)”Register a GraphQL Enum type.
builder.enumType({ name: "Status", values: ["ACTIVE", "INACTIVE", "PENDING"], description?: "..."})
// Or with descriptions per valuebuilder.enumType({ name: "Status", values: { ACTIVE: { description: "Currently active" }, INACTIVE: { description: "Disabled" }, PENDING: { description: "Awaiting approval" } }})inputType(config)
Section titled “inputType(config)”Register a GraphQL Input type for complex arguments.
builder.inputType({ name: "CreateUserInput", schema: CreateUserInputSchema, description?: "..."})Field Methods
Section titled “Field Methods”field(typeName, fieldName, config)
Section titled “field(typeName, fieldName, config)”Add a computed or relational field to an object type.
builder.field("User", "posts", { type: S.Array(PostSchema), args?: { limit: S.optional(S.Number) }, description?: "...", resolve: (parent, args) => Effect.gen(function* () { const db = yield* Database return yield* db.getPostsByUserId(parent.id, args.limit) })})Parameters:
| Name | Type | Description |
|---|---|---|
typeName | string | Object type to add field to |
fieldName | string | Field name |
config.type | Schema<A> | Return type schema |
config.args | Record<string, Schema> | Optional arguments |
config.resolve | (parent, args) => Effect<A> | Resolver receiving parent value |
Directive Methods
Section titled “Directive Methods”directive(name, config)
Section titled “directive(name, config)”Register a custom directive with optional resolver transformation.
builder.directive("auth", { locations: ["FIELD_DEFINITION"], args: { role: S.optional(S.String) }, description: "Require authentication", transformer: (next, args, info) => Effect.gen(function* () { const ctx = yield* AuthContext if (args.role && ctx.user.role !== args.role) { return yield* Effect.fail(new AuthorizationError({ message: "Insufficient permissions" })) } return yield* next })})Parameters:
| Name | Type | Description |
|---|---|---|
name | string | Directive name (without @) |
config.locations | DirectiveLocation[] | Where directive can be used |
config.args | Record<string, Schema> | Directive arguments |
config.description | string | Directive description |
config.transformer | (next, args, info) => Effect | Resolver wrapper |
Building the Schema
Section titled “Building the Schema”buildSchema()
Section titled “buildSchema()”Build the final GraphQL schema.
const schema: GraphQLSchema = builder.buildSchema()Returns: Standard GraphQLSchema from the graphql package.
Throws: If the schema is invalid (e.g., missing required types).
Pipe API
Section titled “Pipe API”The builder implements Pipeable for fluent composition:
import { GraphQLSchemaBuilder, query, objectType, field } from "@effect-gql/core"
const builder = GraphQLSchemaBuilder.empty.pipe( query("users", { ... }), objectType({ name: "User", schema: UserSchema }), field("User", "posts", { ... }))See Pipe Functions for all available functions.
Type Parameter
Section titled “Type Parameter”The builder tracks service requirements via its type parameter:
const builder: GraphQLSchemaBuilder<Database | AuthService> = GraphQLSchemaBuilder.empty.pipe( query("users", { type: S.Array(UserSchema), resolve: () => Effect.gen(function* () { const db = yield* Database // Adds Database to R const auth = yield* AuthService // Adds AuthService to R // ... }) }) )This ensures you provide all required services when executing queries.
Example
Section titled “Example”import { GraphQLSchemaBuilder, query, mutation, objectType, field } from "@effect-gql/core"import { Effect, Context, Layer } from "effect"import * as S from "effect/Schema"
// Define schemasconst UserSchema = S.Struct({ id: S.String, name: S.String, email: S.String})
const PostSchema = S.Struct({ id: S.String, title: S.String, authorId: S.String})
// Build schemaconst builder = GraphQLSchemaBuilder.empty.pipe( // Register types objectType({ name: "User", schema: UserSchema }), objectType({ name: "Post", schema: PostSchema }),
// Root operations query("users", { type: S.Array(UserSchema), resolve: () => Effect.succeed([ { id: "1", name: "Alice", email: "alice@example.com" } ]) }),
query("user", { type: S.NullOr(UserSchema), args: { id: S.String }, resolve: ({ id }) => Effect.succeed( id === "1" ? { id: "1", name: "Alice", email: "alice@example.com" } : null ) }),
// Relational fields field("User", "posts", { type: S.Array(PostSchema), resolve: (user) => Effect.succeed([ { id: "1", title: "Hello World", authorId: user.id } ]) }),
field("Post", "author", { type: UserSchema, resolve: (post) => Effect.succeed({ id: post.authorId, name: "Alice", email: "alice@example.com" }) }))
// Build and useconst schema = builder.buildSchema()