L3 - Operation Schemas
Validation schemas generated for where, create, update, and args operations
Location: src/validation/model/, src/validation/relations/
Why This Layer Exists
VibORM validates query inputs at runtime:
orm.user.findMany({
where: { email: { contains: "@example.com" } }, // Validated!
include: { posts: true }, // Validated!
});
Operation schemas define what’s valid for each operation. They’re built dynamically by SchemaRegistry from model definitions.
Schema Categories
Core Schemas (Per Scalar)
Each scalar type generates its own schemas:
| Schema | Purpose | Example |
|---|---|---|
| Base | Raw input validation | string or null |
| Filter | Where clause operators | { contains: string } |
| Create | Create operation input | Required unless defaulted |
| Update | Update operation input | Optional, can be null |
Args Schemas (Per Model)
The validation registry composes scalar, relation, and model state into operation schemas:
| Schema | Purpose |
|---|---|
| WhereSchema | Full where clause with all fields |
| CreateDataSchema | Full data for create operations |
| UpdateDataSchema | Full data for update operations |
| FindArgs | where + select + include + orderBy |
| CreateArgs | data with nested relation creates |
| UpdateArgs | where + data with nested writes |
Dynamic Schema Generation
Schemas are generated based on scalar state:
// If scalar is nullable + has default:
// - Filter: allows null comparisons
// - Create: optional (has default)
// - Update: accepts null or value
The logic considers:
- Is the scalar nullable?
- Does it have a default value?
- Is it an auto-generated scalar (UUID, timestamps)?
- Is it optional vs required?
Handling Relations
Relations add complexity because queries can be nested:
orm.user.findMany({
where: {
posts: { some: { published: true } } // Nested where
},
include: {
posts: {
where: { published: true }, // Nested filter
select: { title: true } // Nested select
}
}
});
Operation schemas handle this with recursive definitions using thunks to avoid infinite loops.
Why Registry Building?
Schemas are expensive to construct and nested relation inputs need full model graph context. VibORM builds them through SchemaRegistry:
const registry = createSchemaRegistry({ user });
registry.proxy.user.core.where; // Built through registry access, then cached
This keeps schema definitions focused on structure while operation validation stays centralized.
Connection to Other Layers
- L1 (Validation): Operation schemas use
v.*primitives andSchemaRegistry - L2 (Scalars): Scalar base schemas and state feed registry scalar schemas
- L4 (Relations): Relation state feeds nested operation schemas
- L6 (Query Engine): Query engine validates against these schemas