What is Standard Schema?
Standard Schema v1 is a shared protocol that validation libraries implement so that frameworks can accept any compliant schema without coupling to a specific library. When a framework supports Standard Schema, it reads a ~standard property on your schema object to validate inputs in a uniform way.
ts-chas implements this protocol automatically. Every guard chain you build with is.* and every schema you define with defineSchemas exposes the ~standard property — you don’t need to adapt or wrap anything.
The ~standard property is attached automatically to every guard chain produced by is.* and to every schema returned by defineSchemas. You never need to configure it manually.
Integration with tRPC
tRPC accepts any Standard Schema-compliant object as an input validator. You can use a raw is.* guard chain or a defineSchemas schema directly in your procedure definition.
import { initTRPC } from '@trpc/server';
import { is, defineSchemas } from 'ts-chas/guard';
const t = initTRPC.create();
// Option 1: inline guard chain
const userRouter = t.router({
getUser: t.procedure
.input(is.object({ id: is.string.uuid('v4') }))
.query(({ input }) => {
// input is typed as { id: string }
return fetchUser(input.id);
}),
});
// Option 2: named schema from defineSchemas
const schemas = defineSchemas({
CreateUserInput: {
name: is.string.min(1),
email: is.string.email,
age: is.number.gte(18).error('Must be 18 or older'),
},
});
const userMutationRouter = t.router({
createUser: t.procedure
.input(schemas.CreateUserInput)
.mutation(({ input }) => {
// input is typed as { name: string; email: string; age: number }
return createUser(input);
}),
});
react-hook-form’s useForm accepts a resolver option. Libraries like @hookform/resolvers provide Standard Schema support, meaning you pass your guard or schema directly as the resolver value.
import { useForm } from 'react-hook-form';
import { standardSchemaResolver } from '@hookform/resolvers/standard-schema';
import { is, defineSchemas, type InferSchema } from 'ts-chas/guard';
const schemas = defineSchemas({
SignUpForm: {
email: is.string.email.error('Enter a valid email'),
password: is.string.min(8).error('Password must be at least 8 characters'),
age: is.number.gte(18).error('You must be 18 or older'),
},
});
type SignUpForm = InferSchema<typeof schemas.SignUpForm>;
function SignUpPage() {
const { register, handleSubmit, formState: { errors } } = useForm<SignUpForm>({
resolver: standardSchemaResolver(schemas.SignUpForm),
});
return (
<form onSubmit={handleSubmit((data) => submitSignUp(data))}>
<input {...register('email')} />
{errors.email && <p>{errors.email.message}</p>}
<input type="password" {...register('password')} />
{errors.password && <p>{errors.password.message}</p>}
<button type="submit">Sign up</button>
</form>
);
}
The resolver validates the form values against your schema on every submit, and maps GuardErr messages directly to react-hook-form’s errors object.
Integration with Drizzle
Drizzle’s queryClient and insert helpers accept Standard Schema validators for runtime validation of query inputs and results. Pass a guard or schema to Drizzle’s validation slot to enforce your types at the boundary between the database and your application code.
import { is, defineSchemas, type InferSchema } from 'ts-chas/guard';
const schemas = defineSchemas({
UserRow: {
id: is.string.uuid('v4'),
name: is.string.min(1),
email: is.string.email,
createdAt: is.date,
},
});
type UserRow = InferSchema<typeof schemas.UserRow>;
// Pass the schema to Drizzle's validation slot (exact API depends on your Drizzle version)
// Drizzle will call schemas.UserRow['~standard'].validate() on each row
const users = await db.select().from(usersTable).validate(schemas.UserRow);
// users is typed as UserRow[]
Even if a row from the database contains unexpected shapes — for example, after a migration that isn’t yet reflected in your types — the schema catches the mismatch before it silently propagates through your application.
Any Standard Schema-compatible library
Because ts-chas implements the Standard Schema v1 protocol, any library that supports it works with ts-chas guards out of the box. The pattern is always the same: find the library’s validation slot and pass a guard chain or a defineSchemas schema directly.
import { is, defineSchemas } from 'ts-chas/guard';
// A plain guard chain works anywhere a Standard Schema is accepted
const isPositiveNumber = is.number.positive;
// A defineSchemas schema also works
const schemas = defineSchemas({
Config: {
timeout: is.number.positive,
retries: is.number.gte(0).lte(10),
},
});
// Pass either one to the library's validation slot
someLibrary.setSchema(isPositiveNumber);
someLibrary.setSchema(schemas.Config);
Check the documentation for the library you’re integrating with to find its Standard Schema resolver or validator option.