Generated Types
Complete reference of all TypeScript types Cerial generates per model, object, tuple, literal, and enum.
Cerial generates a comprehensive set of TypeScript types for every model, object, tuple, literal, and enum in your schema. These types power autocompletion, compile-time validation, and runtime type safety across all query operations.
Types Per Model
For each model in your schema, Cerial generates the following types:
| Type | Description |
|---|---|
User | Base output interface. All fields use output types (CerialId<T> for records, Date for datetimes). |
UserInput | Base input interface. Record fields use RecordIdInput<T> instead of CerialId<T>. |
UserCreate | Data type for create(). Fields with @default, @createdAt, or @updatedAt are optional. @now fields are excluded (computed). |
UserNestedCreate | Data type for nested creates inside relation operations. Omits the id field since SurrealDB auto-generates it. |
UserUpdate | Data type for updateUnique() / updateMany(). All fields optional. Supports array push/unset and object merge/set operations. |
UserWhere | Where clause type. Includes comparison operators, logical operators (AND, OR, NOT), nested relation filtering, and object field filtering. |
UserSelect | Field selection type. Each field is boolean. Object fields accept boolean | ObjectSelect for sub-field narrowing. |
UserInclude | Relation include type. Each relation accepts boolean or an object with nested where, select, include, orderBy, limit, offset. |
UserOrderBy | Ordering type. Each field accepts 'asc' | 'desc'. Supports nested object field ordering. |
UserUnset | Unset type for clearing optional fields (set to NONE). Supports nested object fields and tuple elements. |
UserFindUniqueWhere | Where clause for unique lookups. Requires exactly one unique field (typically id). |
User$Relations | Relation metadata mapping. Maps relation names to their target model and cardinality. |
GetUserPayload<S, I> | Dynamic return type. Computes the result type based on select (S) and include (I) options. See Dynamic Return Types. |
GetUserIncludePayload<I> | Helper type for resolving included relation types. |
Types Per Object
For each object in your schema, Cerial generates a smaller set of types. Objects are embedded inline within models; they don't have their own tables, IDs, or relations.
| Type | Description |
|---|---|
Address | Base interface with all fields typed. |
AddressInput | Input interface (identical to base for objects without Record fields). |
AddressCreateInput | Create input where @default/@createdAt/@updatedAt fields are optional and @now fields are excluded. Only generated when needed. |
AddressWhere | Where clause type for filtering by nested object fields. |
AddressSelect | Sub-field selection type. |
AddressOrderBy | Ordering type for nested object fields. |
ObjectNameCreateInput is only generated when the object has fields with @default, @now, @createdAt, or @updatedAt decorators. If none of the object's fields have auto-set decorators, the base ObjectName type is used directly for input.
Objects do not generate GetPayload, Include, Create, Update, or Model-level types. Since objects are embedded, they are always operated on through their parent model.
Types Per Tuple
For each tuple in your schema, Cerial generates a focused set of types. Tuples are fixed-length typed arrays.
| Type | Description |
|---|---|
Coordinate | Output type, a TypeScript tuple (e.g., [number, number]). |
CoordinateInput | Input type. Accepts array form, object form (with named/index keys), or mixed. |
CoordinateWhere | Where clause type with named keys, index keys, and comparison operators for each element. |
CoordinateUpdate | Per-element update type. Allows updating individual elements without replacing the entire tuple. |
CoordinateSelect | Sub-field select type. Only generated when the tuple contains object elements at any depth. |
CoordinateUnset | Per-element unset type. Only generated when the tuple has optional elements. |
CoordinateSelect is conditionally generated: it only exists when the tuple has object elements (directly or via nested tuples). Primitive-only tuples use simple boolean selection on the parent model.
CoordinateUnset is conditionally generated: it only exists when the tuple has at least one optional element.
Tuples do not generate OrderBy, Create, Include, or GetPayload types.
Types Per Literal
For each literal in your schema, Cerial generates:
| Type | Description |
|---|---|
{Name} | Output type, a union of variant values/types. |
{Name}Input | Input type. Only generated when the literal has object/tuple variants. |
{Name}Where | Where filter type with eq/neq/in/notIn (plus conditional operators). |
Numeric-only literals get additional comparison operators (gt, gte, lt, lte, between). Literals with broad String type get string operators (contains, startsWith, endsWith).
Literal fields are excluded from OrderBy types and use boolean-only select (no sub-field selection).
Types Per Enum
For each enum in your schema, Cerial generates:
| Type | Description |
|---|---|
{Name}Enum | as const object with all enum values (e.g., { Admin: 'Admin', Editor: 'Editor' } as const). |
{Name}EnumType | Union type of all enum values (e.g., 'Admin' | 'Editor' | 'Viewer'). |
{Name}EnumWhere | Where filter type with string-compatible operators. |
Enum fields support OrderBy (string ordering), unlike non-enum literal fields.
Example: Generated Output Interface
Given this schema:
object Address {
street String
city String
state String
zipCode String?
}
model User {
id Record @id
email String @unique
name String
age Int?
isActive Bool @default(true)
createdAt Date @createdAt
address Address
shipping Address?
profileId Record?
tagIds Record[]
nicknames String[]
}Cerial generates:
export interface User {
id: CerialId;
email: string;
name: string;
age?: number;
isActive: boolean;
createdAt: Date;
address: Address;
shipping?: Address;
profileId?: CerialId;
tagIds: CerialId[];
nicknames: string[];
}Key points:
idisCerialId, notstringageisnumberwith?(optional). It can be a number or NONE (absent). Add@nullableto allow null.isActiveis non-optional in output even though it has@default. The default ensures it always has a value.shippingisAddresswith?(optional). Object fields useundefined, not| null.profileIdisCerialIdwith?. Optional record ref can be a value or NONE.tagIdsandnicknamesare non-optional arrays. Array fields always have a value (default[]).
Example: Generated Create Type
export interface UserCreate {
email: string;
name: string;
age?: number;
isActive?: boolean; // Optional — has @default(true)
createdAt?: Date; // Optional — has @createdAt
address: AddressInput;
shipping?: AddressInput;
profileId?: RecordIdInput;
tagIds?: RecordIdInput[]; // Optional — defaults to []
nicknames?: string[]; // Optional — defaults to []
// Relation fields use nested operations
profile?: { create: ProfileNestedCreate } | { connect: RecordIdInput };
posts?: { create: PostNestedCreate[] } | { connect: RecordIdInput[] };
tags?: { connect: RecordIdInput[] };
}Key differences from the output interface:
- Fields with
@default,@createdAt, or@updatedAtbecome optional (the database provides a default if omitted) - Fields with
@noware excluded (they are computed by the database and cannot be set) - Array fields become optional (default to
[]) CerialIdbecomesRecordIdInput(accepts strings, CerialId, or RecordId)- Relation fields are replaced with nested
create/connectoperations
Example: Generated Update Type
export interface UserUpdate {
email?: string;
name?: string;
age?: number | typeof NONE;
isActive?: boolean;
address?: AddressInput | { set: AddressInput };
shipping?: AddressInput | { set: AddressInput };
profileId?: RecordIdInput | typeof NONE;
tagIds?: RecordIdInput[];
nicknames?: string[] | { push: string | string[] } | { unset: string | string[] };
// Relation fields use update operations
profile?: { create: ProfileNestedCreate } | { connect: RecordIdInput } | { disconnect: true };
posts?:
| { create: PostNestedCreate | PostNestedCreate[] }
| { connect: RecordIdInput | RecordIdInput[] }
| { disconnect: RecordIdInput | RecordIdInput[] };
tags?:
| { connect: RecordIdInput | RecordIdInput[] }
| { disconnect: RecordIdInput | RecordIdInput[] };
}Key features:
- All fields are optional (only update what you need)
- Optional fields accept
NONEsentinel to remove the field @nullablefields also acceptnullto set the field to null- Object fields accept partial data (merge) or
{ set: ... }(full replacement) - Array primitive fields support
{ push: ... }and{ unset: ... }(value-based removal) - Relation fields support
create,connect, anddisconnectoperations
Example: Generated Unset Type
export interface UserUnset {
age?: true; // optional primitive — can be unset
shipping?: true | { zipCode?: true }; // optional object with optional children
address?: { zipCode?: true }; // required object with optional children — nested only
// Required primitives, arrays, relations, @readonly, and id are NOT included
}Key features:
- Only optional fields or fields with optional children appear in
Unset truemeans "unset this entire field" (set to NONE)- Object fields with optional children allow nested unset:
{ subField: true } - Required objects with optional children only allow nested form (can't unset a required field)
@readonlyfields, relations, arrays, andidare excluded- TypeScript prevents conflicts between
dataandunsetusing theSafeUnset<Unset, Data>utility type
Example: Generated Where Type
export interface UserWhere {
id?: RecordIdInput | RecordIdInput[];
email?:
| string
| {
eq?: string;
neq?: string;
contains?: string;
startsWith?: string;
endsWith?: string;
in?: string[];
notIn?: string[];
};
age?:
| number
| {
eq?: number;
neq?: number;
gt?: number;
gte?: number;
lt?: number;
lte?: number;
in?: number[];
notIn?: number[];
isNone?: boolean; // available because age is optional (?)
isDefined?: boolean; // alias for !isNone
};
isActive?: boolean | { eq?: boolean; neq?: boolean };
address?: AddressWhere;
shipping?: AddressWhere | { isNone?: boolean };
nicknames?: { has?: string; hasAll?: string[]; hasAny?: string[]; isEmpty?: boolean };
// Relation filtering
profile?: ProfileWhere | { is?: ProfileWhere; isNot?: ProfileWhere };
posts?: { some?: PostWhere; every?: PostWhere; none?: PostWhere };
tags?: { some?: TagWhere; every?: TagWhere; none?: TagWhere };
// Logical operators
AND?: UserWhere[];
OR?: UserWhere[];
NOT?: UserWhere;
}Key features:
- Scalar fields accept a direct value (shorthand for
{ eq: value }) or an operator object - Object fields accept their nested
Wheretype for filtering by sub-fields - Relation fields support
is/isNotfor singular andsome/every/nonefor arrays AND,OR,NOTfor composing complex conditions
Example: Generated Select and Include Types
export interface UserSelect {
id?: boolean;
email?: boolean;
name?: boolean;
age?: boolean;
isActive?: boolean;
createdAt?: boolean;
address?: boolean | AddressSelect; // true = full, object = sub-fields
shipping?: boolean | AddressSelect;
profileId?: boolean;
tagIds?: boolean;
nicknames?: boolean;
}
export interface UserInclude {
profile?:
| boolean
| {
select?: ProfileSelect;
include?: ProfileInclude;
where?: ProfileWhere;
};
posts?:
| boolean
| {
select?: PostSelect;
include?: PostInclude;
where?: PostWhere;
orderBy?: PostOrderBy;
limit?: number;
offset?: number;
};
}Key features:
selectfields areboolean.trueincludes the field,falseexcludes it.- Object fields in
selectacceptboolean | ObjectSelectfor sub-field narrowing includerelation entries acceptboolean(simple include) or an object with nested query options- Array relations in
includesupportwhere,orderBy,limit, andoffsetfor filtering and pagination
CerialQueryPromise
All model methods return CerialQueryPromise<T> instead of Promise<T>. It works exactly like a regular Promise: you can await it, call .then(), .catch(), and .finally() as usual. It can also be passed to $transaction for atomic batched execution.
// Works like a normal Promise
const user = await client.db.User.findOne({ where: { id: '123' } });
// Or collect multiple queries for atomic execution
const [user, posts] = await client.$transaction([
client.db.User.findOne({ where: { id: '123' } }),
client.db.Post.findMany({ where: { published: true } }),
]);It's re-exported from the generated client for type annotations:
import { CerialQueryPromise } from './db-client';Generated File Structure
All types are generated into the output directory (typically db-client/ or a configured path). Models, objects, and tuples each get their own directory:
db-client/
├── client.ts # CerialClient class with Model proxies
├── models/ # Model types (User, UserCreate, UserWhere, ...)
│ ├── user.ts
│ ├── post.ts
│ ├── profile.ts
│ └── index.ts
├── objects/ # Object types (Address, AddressInput, AddressWhere, ...)
│ ├── address.ts
│ └── index.ts
├── tuples/ # Tuple types (Coordinate, CoordinateInput, CoordinateWhere, ...)
│ ├── coordinate.ts
│ └── index.ts
├── internal/
│ ├── model-registry.ts # Runtime model metadata (fields, relations, types)
│ └── migrations.ts # DEFINE TABLE / DEFINE FIELD statements
└── index.ts # Re-exports all public types and client