Cerial

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:

TypeDescription
UserBase output interface. All fields use output types (CerialId<T> for records, Date for datetimes).
UserInputBase input interface. Record fields use RecordIdInput<T> instead of CerialId<T>.
UserCreateData type for create(). Fields with @default, @createdAt, or @updatedAt are optional. @now fields are excluded (computed).
UserNestedCreateData type for nested creates inside relation operations. Omits the id field since SurrealDB auto-generates it.
UserUpdateData type for updateUnique() / updateMany(). All fields optional. Supports array push/unset and object merge/set operations.
UserWhereWhere clause type. Includes comparison operators, logical operators (AND, OR, NOT), nested relation filtering, and object field filtering.
UserSelectField selection type. Each field is boolean. Object fields accept boolean | ObjectSelect for sub-field narrowing.
UserIncludeRelation include type. Each relation accepts boolean or an object with nested where, select, include, orderBy, limit, offset.
UserOrderByOrdering type. Each field accepts 'asc' | 'desc'. Supports nested object field ordering.
UserUnsetUnset type for clearing optional fields (set to NONE). Supports nested object fields and tuple elements.
UserFindUniqueWhereWhere clause for unique lookups. Requires exactly one unique field (typically id).
User$RelationsRelation 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.

TypeDescription
AddressBase interface with all fields typed.
AddressInputInput interface (identical to base for objects without Record fields).
AddressCreateInputCreate input where @default/@createdAt/@updatedAt fields are optional and @now fields are excluded. Only generated when needed.
AddressWhereWhere clause type for filtering by nested object fields.
AddressSelectSub-field selection type.
AddressOrderByOrdering 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.

TypeDescription
CoordinateOutput type, a TypeScript tuple (e.g., [number, number]).
CoordinateInputInput type. Accepts array form, object form (with named/index keys), or mixed.
CoordinateWhereWhere clause type with named keys, index keys, and comparison operators for each element.
CoordinateUpdatePer-element update type. Allows updating individual elements without replacing the entire tuple.
CoordinateSelectSub-field select type. Only generated when the tuple contains object elements at any depth.
CoordinateUnsetPer-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:

TypeDescription
{Name}Output type, a union of variant values/types.
{Name}InputInput type. Only generated when the literal has object/tuple variants.
{Name}WhereWhere 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:

TypeDescription
{Name}Enumas const object with all enum values (e.g., { Admin: 'Admin', Editor: 'Editor' } as const).
{Name}EnumTypeUnion type of all enum values (e.g., 'Admin' | 'Editor' | 'Viewer').
{Name}EnumWhereWhere 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:

  • id is CerialId, not string
  • age is number with ? (optional). It can be a number or NONE (absent). Add @nullable to allow null.
  • isActive is non-optional in output even though it has @default. The default ensures it always has a value.
  • shipping is Address with ? (optional). Object fields use undefined, not | null.
  • profileId is CerialId with ?. Optional record ref can be a value or NONE.
  • tagIds and nicknames are 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 @updatedAt become optional (the database provides a default if omitted)
  • Fields with @now are excluded (they are computed by the database and cannot be set)
  • Array fields become optional (default to [])
  • CerialId becomes RecordIdInput (accepts strings, CerialId, or RecordId)
  • Relation fields are replaced with nested create / connect operations

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 NONE sentinel to remove the field
  • @nullable fields also accept null to 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, and disconnect operations

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
  • true means "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)
  • @readonly fields, relations, arrays, and id are excluded
  • TypeScript prevents conflicts between data and unset using the SafeUnset<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 Where type for filtering by sub-fields
  • Relation fields support is/isNot for singular and some/every/none for arrays
  • AND, OR, NOT for 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:

  • select fields are boolean. true includes the field, false excludes it.
  • Object fields in select accept boolean | ObjectSelect for sub-field narrowing
  • include relation entries accept boolean (simple include) or an object with nested query options
  • Array relations in include support where, orderBy, limit, and offset for 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

On this page