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
| Type | Builder | TS Type | Notes |
|---|---|---|---|
| ID | t.id() | string | Auto-generated UUID v4 |
| String | t.string() | string | |
| Number | t.number() | number | Rejects NaN |
| Boolean | t.boolean() | boolean | |
| Date | t.date() | Date | Serialized as ISO string |
| Array | t.array(t.string()) | T[] | Element type validated |
| Object | t.object({...}) | { ... } | Nested validation |
| Enum | t.enum(['a', 'b'] as const) | 'a' | 'b' | Const array required |
| Ref | t.ref('collection') | string | Foreign 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"
}