SQL Resolvers and Batching
Batched database access using SqlResolver with Effect's request batching
resolverbatchingSqlResolverrequest
SQL Resolvers and Batching
Overview
@effect/sql provides SqlResolver for defining batched database access patterns. When used with { batching: true } in Effect.all or Effect.forEach, multiple individual requests are automatically combined into single SQL queries. The resolver example demonstrates this with the Pg client.
Key Concepts
Resolver Types
SqlResolver.ordered - For insert/update operations where result order matches request order:
const Insert = yield* SqlResolver.ordered("InsertPerson", {
Request: InsertPersonSchema,
Result: Person,
execute: (requests) =>
sql`INSERT INTO people ${sql.insert(requests)} RETURNING people.*`
})
SqlResolver.findById - For lookup by unique ID:
const GetById = yield* SqlResolver.findById("GetPersonById", {
Id: Schema.Number,
Result: Person,
ResultId: (result) => result.id,
execute: (ids) =>
sql`SELECT * FROM people WHERE id IN ${sql.in(ids)}`
})
SqlResolver.grouped - For lookup where multiple results can match each request:
const GetByName = yield* SqlResolver.grouped("GetPersonByName", {
Request: Schema.String,
RequestGroupKey: (_) => _,
Result: Person,
ResultGroupKey: (_) => _.name,
execute: (ids) =>
sql`SELECT * FROM people WHERE name IN ${sql.in(ids)}`
})
Batching in Action
Individual calls are batched into single queries:
// These two execute as ONE SQL statement
const [person1, person2] = yield* Effect.all(
[Insert.execute({ name: "John" }), Insert.execute({ name: "Jane" })],
{ batching: true }
)
// These three lookups execute as ONE SQL statement
yield* Effect.forEach(
["John", "Jane", "John"],
(name) => GetByName.execute(name),
{ batching: true }
)
Schema Integration
Resolvers use effect/Schema for request/result validation:
class Person extends Schema.Class<Person>("Person")({
id: Schema.Number,
name: Schema.String,
createdAt: Schema.DateFromSelf
}) {}
const InsertPersonSchema = Schema.Struct(Person.fields).pipe(
Schema.omit("id", "createdAt")
)
Transaction Support
Resolvers work within transactions:
yield* sql.withTransaction(
Effect.all(
[Insert.execute({ name: "John" }), Insert.execute({ name: "Jane" })],
{ batching: true }
)
)
Related Files
examples/resolver.ts- Complete resolver example with ordered, findById, and grouped patternssrc/PgClient.ts- SQL helpers (insert,in) used in resolver execute functions