Runtime safety
Ensure row shape is exactly what your service layer expects.
Validation
Attach a Standard Schema validator (for example, Zod) to validate execution result after SQL runs.
Validation adds a runtime contract for query results. SQL can be syntactically correct but still return unexpected shapes. Validation catches that mismatch before data flows deeper into your app.
Runtime safety
Ensure row shape is exactly what your service layer expects.
Better debugging
Validation failures make data contract breaks obvious and fast to diagnose.
Shared contract
Same schema can represent both expected runtime data and docs for your team.
Pick any validator compatible with Standard Schema.
npm install zod
# optional alternatives (Standard Schema compatible)
npm install valibot arktype @effect/schemanpm install zod
# optional alternatives (Standard Schema compatible)
npm install valibot arktype @effect/schemaThis project accepts Standard Schema implementations (see standardschema.dev). In practice, any library exposing the Standard Schema contract can be used.
| Library | Status | Notes |
|---|---|---|
| Zod | Recommended | Used in repository tests and examples. |
| Valibot | Supported via Standard Schema | Good option for lightweight, composable validation. |
| ArkType | Supported via Standard Schema | Great for expressive type-first schema declarations. |
| Effect Schema | Supported via Standard Schema | Strong option if you already use Effect in your app architecture. |
Call setValidation on a query. Runtime metapassed to execute(meta) is forwarded to the execution handler.
import { z } from "zod"
const q = sqlBuilder()
.setFormatParamHandler("pg")
.setExecutionHandler(async () => [{ name: "John", age: 20 }])
const result = await q
.select("*")
.from(q.t("users"))
.setValidation(
z.array(
z.object({
name: z.string(),
age: z.number(),
}),
),
)
.execute({ requestId: "docs-1" })import { z } from "zod"
const q = sqlBuilder()
.setFormatParamHandler("pg")
.setExecutionHandler(async () => [{ name: "John", age: 20 }])
const result = await q
.select("*")
.from(q.t("users"))
.setValidation(
z.array(
z.object({
name: z.string(),
age: z.number(),
}),
),
)
.execute({ requestId: "docs-1" })Alternative example: Valibot
import * as v from "valibot"
const usersSchema = v.array(
v.object({
name: v.string(),
age: v.number(),
}),
)
const result = await q
.select("*")
.from(q.t("users"))
.setValidation(usersSchema)
.execute()import * as v from "valibot"
const usersSchema = v.array(
v.object({
name: v.string(),
age: v.number(),
}),
)
const result = await q
.select("*")
.from(q.t("users"))
.setValidation(usersSchema)
.execute()Alternative example: ArkType
import { type } from "arktype"
const usersSchema = type("{ name: string, age: number }[]")
const result = await q
.select("*")
.from(q.t("users"))
.setValidation(usersSchema)
.execute()import { type } from "arktype"
const usersSchema = type("{ name: string, age: number }[]")
const result = await q
.select("*")
.from(q.t("users"))
.setValidation(usersSchema)
.execute()You can also define validator once at schema query registration withvalidation(...).
import { z } from "zod"
const sch = sqlSchema().setQuery(
"getMagma",
sqlSchema()
.set
.query(q.select("*").from(q.t("users")))
.validation(
z.array(
z.object({
name: z.string(),
age: z.number(),
}),
),
),
)
const result = await sch.query("getMagma").execute({ requestId: "schema-1" })import { z } from "zod"
const sch = sqlSchema().setQuery(
"getMagma",
sqlSchema()
.set
.query(q.select("*").from(q.t("users")))
.validation(
z.array(
z.object({
name: z.string(),
age: z.number(),
}),
),
),
)
const result = await sch.query("getMagma").execute({ requestId: "schema-1" })If execution result does not satisfy the validation schema, execution throws an error.
const qValidate = sqlBuilder()
.setFormatParamHandler("pg")
.setExecutionHandler(async () => [{ id: 10 }])
const builder = qValidate
.select("*")
.from(qValidate.t("users"))
.setValidation(z.array(z.object({ id: z.string() })))
await builder.execute()
// throws when validation failsconst qValidate = sqlBuilder()
.setFormatParamHandler("pg")
.setExecutionHandler(async () => [{ id: 10 }])
const builder = qValidate
.select("*")
.from(qValidate.t("users"))
.setValidation(z.array(z.object({ id: z.string() })))
await builder.execute()
// throws when validation fails