Skip to main content

Schema Definition

Schemas define the shape and validation rules for your documents. They're pure TypeScript objects — no codegen, no external DSL.

Basic Syntax

import { schema, t } from '@sajjadbzn/jcell'

const userSchema = schema({
id: t.id(),
name: t.string(),
age: t.number().optional(),
role: t.enum(['admin', 'user', 'guest'] as const),
createdAt: t.date().default(() => new Date()),
})

Field Types

TypeBuilderTS TypeNotes
IDt.id()stringAuto-generated UUID v4
Stringt.string()string
Numbert.number()numberRejects NaN
Booleant.boolean()boolean
Datet.date()DateSerialized as ISO string
Arrayt.array(t.string())T[]Element type validated
Objectt.object({...}){ ... }Nested validation
Enumt.enum(['a', 'b'] as const)'a' | 'b'Const array required
Reft.ref('collection')stringForeign key reference

Array Examples

const postSchema = schema({
tags: t.array(t.string()),
scores: t.array(t.number()),
comments: t.array(t.object({
author: t.string(),
body: t.string(),
})),
})

Object (Nested) Examples

const productSchema = schema({
metadata: t.object({
views: t.number().default(0),
rating: t.number().optional(),
dimensions: t.object({
width: t.number(),
height: t.number(),
}),
}),
})

Enum with Default

const orderSchema = schema({
status: t.enum(['pending', 'shipped', 'delivered'] as const)
.default('pending'),
})

Reference Fields

const commentSchema = schema({
id: t.id(),
postId: t.ref('posts'), // stores the id of the related post
authorId: t.ref('users'), // stores the id of the related user
body: t.string(),
})

Field Modifiers

.optional()

Makes a field nullable/omittable:

const userSchema = schema({
nickname: t.string().optional(), // string | undefined
bio: t.string().optional(), // string | undefined
})

.default(value)

Provides a default value when the field is not specified:

const taskSchema = schema({
completed: t.boolean().default(false), // static default
createdAt: t.date().default(() => new Date()), // factory default
metadata: t.object({ views: t.number() })
.default(() => ({ views: 0 })), // factory for objects
})

.default(fn)

For values that need to be computed at insert time (like dates or random values):

const sessionSchema = schema({
token: t.string().default(() => crypto.randomUUID()),
expiresAt: t.date().default(() => {
const d = new Date()
d.setDate(d.getDate() + 7)
return d
}),
})

.index(options?)

Marks a field as indexed for faster lookups:

const userSchema = schema({
email: t.string().index({ unique: true }), // unique index
name: t.string().index(), // simple index
})

Type Inference

Schemas automatically produce TypeScript types:

const userSchema = schema({
id: t.id(),
name: t.string(),
age: t.number().optional(),
role: t.enum(['admin', 'user'] as const),
})

// Infer the document type
type User = typeof userSchema.infer
// { id: string; name: string; age?: number; role: 'admin' | 'user' }

Use inferred types for type-safe operations:

function greet(user: User) {
return `Hello, ${user.name}!` // autocompleted ✨
}

Validation Rules

Documents are validated automatically on insert() and update():

  • Required fields — must be present unless .optional() or .default()
  • Type checking — values must match the declared type
  • Enum values — must be one of the allowed values
  • Nested validation — object and array fields are recursively validated
  • Unknown fields — extra fields are rejected
try {
await tasks.insert({ title: 42 } as any)
} catch (err) {
// ValidationError: "Invalid value for field 'title': expected string, got number"
}