Skip to content

Pipe Functions

Effect GraphQL provides standalone functions that can be used with .pipe() for a more functional composition style.

import {
query,
mutation,
subscription,
objectType,
interfaceType,
unionType,
enumType,
inputType,
field,
directive
} from "@effect-gql/core"

All functions are designed to work with GraphQLSchemaBuilder.pipe():

import { GraphQLSchemaBuilder, query, objectType, field } from "@effect-gql/core"
const builder = GraphQLSchemaBuilder.empty.pipe(
objectType({ name: "User", schema: UserSchema }),
query("users", {
type: S.Array(UserSchema),
resolve: () => Effect.succeed([...])
}),
field("User", "posts", {
type: S.Array(PostSchema),
resolve: (user) => Effect.succeed([...])
})
)

Add a Query field to the builder.

query("users", {
type: S.Array(UserSchema),
args: { limit: S.optional(S.Number) },
description: "Get all users",
resolve: ({ limit }) => Effect.succeed([...])
})

Signature:

function query<A, E, R>(
name: string,
config: {
type: Schema<A>
args?: Record<string, Schema<any>>
description?: string
deprecationReason?: string
resolve: (args: Args) => Effect<A, E, R>
}
): (builder: GraphQLSchemaBuilder<R0>) => GraphQLSchemaBuilder<R0 | R>

Add a Mutation field to the 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 })
})
})

Add a Subscription field to the builder. The resolver must return a Stream.

subscription("messageAdded", {
type: MessageSchema,
args: { channelId: S.String },
resolve: ({ channelId }) => Effect.gen(function* () {
const pubsub = yield* PubSubService
return pubsub.subscribe(`channel:${channelId}`)
})
})

Register a GraphQL Object type.

objectType({
name: "User",
schema: S.Struct({
id: S.String,
name: S.String,
email: S.String
}),
description: "A user in the system"
})
// With TaggedStruct (name is inferred)
objectType({
schema: S.TaggedStruct("User", {
id: S.String,
name: S.String
})
})

Register a GraphQL Interface type.

interfaceType({
name: "Node",
schema: S.Struct({
id: S.String
}),
description: "An object with a global ID"
})

Register a GraphQL Union type.

unionType({
name: "SearchResult",
types: ["User", "Post", "Comment"],
resolveType: (value) => {
if ("email" in value) return "User"
if ("title" in value) return "Post"
return "Comment"
}
})

Register a GraphQL Enum type.

// Simple enum
enumType({
name: "Status",
values: ["ACTIVE", "INACTIVE", "PENDING"]
})
// With value descriptions
enumType({
name: "Priority",
values: {
LOW: { description: "Low priority" },
MEDIUM: { description: "Medium priority" },
HIGH: { description: "High priority" }
}
})

Register a GraphQL Input type.

inputType({
name: "CreateUserInput",
schema: S.Struct({
name: S.String,
email: S.String,
role: S.optional(S.String)
})
})

Add a field to an existing object type.

field("User", "posts", {
type: S.Array(PostSchema),
args: {
limit: S.optional(S.Number),
offset: S.optional(S.Number)
},
description: "Posts authored by this user",
resolve: (user, { limit, offset }) => Effect.gen(function* () {
const db = yield* Database
return yield* db.getPostsByUserId(user.id, { limit, offset })
})
})

Signature:

function field<Parent, A, E, R>(
typeName: string,
fieldName: string,
config: {
type: Schema<A>
args?: Record<string, Schema<any>>
description?: string
deprecationReason?: string
resolve: (parent: Parent, args: Args) => Effect<A, E, R>
}
): (builder: GraphQLSchemaBuilder<R0>) => GraphQLSchemaBuilder<R0 | R>

Register a custom directive.

directive("auth", {
locations: ["FIELD_DEFINITION"],
args: {
roles: S.optional(S.Array(S.String))
},
description: "Require authentication with optional role check",
transformer: (next, { roles }, info) => Effect.gen(function* () {
const auth = yield* AuthContext
if (!auth.user) {
return yield* Effect.fail(new AuthorizationError({
message: "Authentication required"
}))
}
if (roles && !roles.includes(auth.user.role)) {
return yield* Effect.fail(new AuthorizationError({
message: `Required role: ${roles.join(" or ")}`
}))
}
return yield* next
})
})

Directive Locations:

  • QUERY, MUTATION, SUBSCRIPTION
  • FIELD, FIELD_DEFINITION
  • OBJECT, INTERFACE, UNION, ENUM, INPUT_OBJECT
  • SCALAR, ARGUMENT_DEFINITION, ENUM_VALUE, INPUT_FIELD_DEFINITION
const builder = GraphQLSchemaBuilder.empty.pipe(
// Types first
objectType({ name: "User", schema: UserSchema }),
objectType({ name: "Post", schema: PostSchema }),
// Then queries
query("users", { ... }),
query("posts", { ... }),
// Then relational fields
field("User", "posts", { ... }),
field("Post", "author", { ... })
)
users.ts
export const userModule = (builder: GraphQLSchemaBuilder<any>) =>
builder.pipe(
objectType({ name: "User", schema: UserSchema }),
query("users", { ... }),
query("user", { ... }),
mutation("createUser", { ... })
)
// posts.ts
export const postModule = (builder: GraphQLSchemaBuilder<any>) =>
builder.pipe(
objectType({ name: "Post", schema: PostSchema }),
query("posts", { ... }),
field("User", "posts", { ... })
)
// schema.ts
const builder = GraphQLSchemaBuilder.empty.pipe(
userModule,
postModule
)
const withAdminFeatures = process.env.ENABLE_ADMIN === "true"
const builder = GraphQLSchemaBuilder.empty.pipe(
query("users", { ... }),
// Conditionally add admin features
...(withAdminFeatures ? [
query("adminStats", { ... }),
mutation("deleteUser", { ... })
] : [])
)

All pipe functions correctly infer and accumulate service requirements:

const builder = GraphQLSchemaBuilder.empty.pipe(
query("users", {
type: S.Array(UserSchema),
resolve: () => Effect.gen(function* () {
const db = yield* Database // Adds Database to R
return yield* db.getUsers()
})
}),
query("posts", {
type: S.Array(PostSchema),
resolve: () => Effect.gen(function* () {
const cache = yield* CacheService // Adds CacheService to R
return yield* cache.getPosts()
})
})
)
// Type: GraphQLSchemaBuilder<Database | CacheService>