Standard Schema V1
Standard Schema V1 compliance for interoperability with the validation ecosystem
What is Standard Schema?
Standard Schema is a universal interface for TypeScript validation libraries. Instead of each library having its own API, Standard Schema provides a common interface:
interface StandardSchemaV1<TInput, TOutput> {
readonly "~standard": {
readonly version: 1;
readonly vendor: string;
validate(value: unknown): { value: TOutput } | { issues: Issue[] };
};
}
Why We Comply
Library Integration
Many tools support Standard Schema. VibORM schemas work with:
- Form validation (React Hook Form, Formik)
- API validation (tRPC, Hono)
- AI tool calling (Vercel AI SDK)
- OpenAPI generation
Interoperability
You can use VibORM schemas alongside Zod or Valibot if needed:
// Both implement the same interface
zodSchema["~standard"].validate(data);
vibSchema["~standard"].validate(data);
JSON Schema Export
VibORM also complies with the JSON Schema specification, allowing you to export schemas for documentation, API specs, or external validation:
const jsonSchema = schema["~standard"].jsonSchema.output({
target: "draft-07", // or "openapi-3.0"
});
Circular references are automatically converted to $ref entries.
Accessing Model Schemas
VibORM provides a getSchemas utility to easily access all validation schemas for your models:
import { getSchemas } from "viborm";
const schemas = getSchemas(mySchema);
// Access any model's schemas
schemas.user.core.where // WHERE clause validation
schemas.user.core.create // CREATE data validation
schemas.user.args.findMany // Full findMany args validation
Available Schemas
Each model exposes a comprehensive set of schemas:
Core Schemas
| Schema | Purpose |
|---|---|
core.where |
Full WHERE clause with all operators |
core.whereUnique |
WHERE clause for unique constraints |
core.create |
Data for create operations |
core.update |
Data for update operations |
core.select |
Field selection |
core.include |
Relation inclusion |
core.orderBy |
Sorting configuration |
Operation Args
Complete argument schemas for each operation:
| Schema | Operation |
|---|---|
args.findUnique |
findUnique() arguments |
args.findFirst |
findFirst() arguments |
args.findMany |
findMany() arguments |
args.create |
create() arguments |
args.createMany |
createMany() arguments (including the optional select that makes it return rows) |
args.update |
update() arguments |
args.updateMany |
updateMany() arguments (including the optional select that makes it return rows) |
args.delete |
delete() arguments |
args.deleteMany |
deleteMany() arguments (including the optional select that makes it return rows) |
args.upsert |
upsert() arguments |
args.count |
count() arguments |
args.aggregate |
aggregate() arguments |
args.groupBy |
groupBy() arguments |
Building Blocks (Advanced)
Lower-level schemas for custom composition:
schemas.user.core.scalarFilter // Scalar filters
schemas.user.core.uniqueFilter // Unique constraint filters
schemas.user.core.relationFilter // Relation filters
schemas.user.core.compoundConstraintFilter // Compound constraint filters
schemas.user.core.compoundIdFilter // Compound ID filters
schemas.user.core.scalarCreate // Scalar create
schemas.user.core.relationCreate // Relation nested creates
schemas.user.core.scalarUpdate // Scalar update
schemas.user.core.relationUpdate // Relation nested updates
Recursive Schema Handling
The key challenge for Standard Schema compliance is recursive schemas. ORM models reference each other:
// User → Posts → User (circular)
const userSchema = v.object({
posts: () => v.array(postSchema), // Thunk
});
Thunks + Lazy Evaluation
VibORM uses thunks (functions that return schemas) combined with lazy evaluation:
- During schema construction, thunks are stored but NOT called
- During validation, thunks are called to resolve the actual schema
- TypeScript infers the return type of thunks at compile time
This approach was inspired by ArkType’s handling of recursive types.
Why Other Libraries Struggled
| Library | Issue with Recursion |
|---|---|
| Zod | z.lazy() loses type information, returns any |
| Valibot | lazy() wrapper evaluated eagerly, causing infinite loops |
| ArkType | Good inference, but thunks evaluated during construction |
VibORM’s approach: thunks are truly lazy - only called at validation time.