L9 - Client
The orm.model.operation() interface providing type-safe queries with zero code generation
Location: src/client/
Why This Layer Exists
VibORM’s type safety comes from inferring types from schema definitions. The client layer makes this work:
import { createClient } from "viborm";
const orm = createClient({ schema, driver });
// Fully typed - no codegen!
const users = await orm.user.findMany({
where: { email: { contains: "@example.com" } },
select: { id: true, email: true, posts: { select: { title: true } } }
});
// Type: Array<{ id: string; email: string; posts: Array<{ title: string }> }>
How It Works
Immutable extensions
$extends() returns a new client view with one frozen ordered chain. Its six
capabilities are request, query, statement, observe, client, and model. No
extension code runs on a model-method property read. See
Client Extensions for lifecycle and authority.
The official defaultOmit(), cache(), and instrumentation() factories own
their private capabilities. These are not createClient() configuration keys.
The generic implementation has one home: src/extensions/definition.ts and
chain.ts own definition and composition, methods.ts owns added methods,
request.ts, query.ts, statement.ts, and observation.ts own their single
runners, and array-admission.ts owns only extension admission. Client code
keeps the model-operation trigger, transaction substrate, dispatch, result
order, and commit facts; it does not keep a second handler representation.
Recursive Proxy Pattern
The client uses JavaScript Proxy to handle dynamic model access:
orm.user // Proxy intercepts "user" property access
.findMany // Proxy intercepts "findMany" method access
(args) // Calls actual findMany implementation
This enables orm.<anyModel>.<anyOperation> without defining every combination.
Type Inference Chain
Types flow through a chain:
Schema Definition
↓
Model extracts fields (scalars and relations)
↓
Client infers model names from schema
↓
Operations infer args/return from model
↓
Select/Include modifies return type
No step uses code generation - TypeScript infers at each level.
Select/Include Aware Results
Return types change based on what you select:
// Full model (all fields)
orm.user.findFirst({ where: { id: "1" } });
// Type: { id, email, name, createdAt, ... } | null
// Selected fields only
orm.user.findFirst({
where: { id: "1" },
select: { id: true, email: true }
});
// Type: { id, email } | null
// With relations
orm.user.findFirst({
where: { id: "1" },
include: { posts: true }
});
// Type: { id, email, ..., posts: Post[] } | null
Available Operations
The full Operations union (see src/client/types.ts):
| Operation | Description |
|---|---|
findMany |
Get multiple records |
findFirst |
Get first matching record |
findFirstOrThrow |
Get first matching record or throw |
findUnique |
Get record by unique constraint |
findUniqueOrThrow |
Get record by unique constraint or throw |
exist |
Check whether any row matches |
create |
Create single record |
createMany |
Create multiple records; returns { count }, or the created rows when select is present |
update |
Update a record by unique constraint |
updateMany |
Update multiple records; returns { count }, or the updated rows when select is present |
upsert |
Create or update on conflict |
delete |
Delete a record by unique constraint |
deleteMany |
Delete multiple records; returns { count }, or the deleted rows when select is present |
count |
Count matching records |
aggregate |
Aggregate functions (SUM, AVG, etc.) |
groupBy |
Group rows and aggregate per group |
Why Zero Codegen?
Code generation ORMs (like Prisma) require a build step:
prisma generate # Must run after schema changes
VibORM infers types directly from your schema definition:
const schema = {
user: s.model({ ... }),
post: s.model({ ... }),
};
const orm = createClient({ schema, driver }); // Types inferred immediately
Benefits:
- Instant feedback: Types update on save
- No build step: Simpler CI/CD
- No generated files: Cleaner repository
Connection to Other Layers
- L2-L4 (Schema): Client infers types from schema definitions
- L3 (Query Schemas): Client validates args at runtime
- L6 (Query Engine): Client routes operations to query engine
- L8 (Drivers): Query results flow back through client