Cookbook
Need a quick pattern without reading the whole tutorial? Grab a ready-made recipe below 👇
All snippets assume
dbis an activeSurrealconnection and models are already defined.
Pagination with start, limit, and orderBy
Section titled “Pagination with start, limit, and orderBy”// Fetch the next page of posts ordered by newest firstconst pageSize = 10;const posts = await Post.select(db, { orderBy: [{ field: 'createdAt', order: 'desc' }], start: 20, // skip first 2 pages limit: pageSize,});Soft Delete (isDeleted flag)
Section titled “Soft Delete (isDeleted flag)”class Post extends Table.normal({ name: 'post', fields: { title: Field.string(), content: Field.string(), isDeleted: Field.bool({ default: surql`false` }), },}) { // Convenience helpers async softDelete(db: Surreal) { return this.update(db, { data: { isDeleted: true }, mode: 'merge', }); }
static async allActive(db: Surreal) { return this.select(db, { where: surql`isDeleted = false` }); }}Enum Helper
Section titled “Enum Helper”// Authorised values enforced in SurrealDBexport const Roles = ['admin', 'editor', 'viewer'] as const;export type Role = (typeof Roles)[number];
class User extends Table.normal({ name: 'user', fields: { role: Field.custom<Role>('string', { assert: surql`$value INSIDE ["admin", "editor", "viewer"]`, }), },}) {}Patch Update with merge
Section titled “Patch Update with merge”// Partially update fields without replacing the whole recordawait Post.update(db, 'post:123', { data: { content: 'Updated body' }, mode: 'merge',});Parameterised Full-Text Search
Section titled “Parameterised Full-Text Search”await Post.select(db, { where: surql`title ~~ $q OR content ~~ $q`, vars: { q: '*orm*' },});Type-Safe Select (Field Projection)
Section titled “Type-Safe Select (Field Projection)”Select specific fields with full type inference:
import { typed } from 'unreal-orm';
// Select specific fields - return type is inferredconst posts = await Post.select(db, { select: { title: true, author: { name: true, email: true } },});// Type: { title: string; author: { name: string; email: string } }[]
// Computed fields with typed() helperconst posts = await Post.select(db, { select: { title: true, commentCount: typed<number>(surql`count(<-comment)`) },});// Type: { title: string; commentCount: number }[]
// Nested relation fetch by overriding the wildcard defaultconst latest = await Post.select(db, { select: { '*': true, author: { name: true, email: true } }});// Type: { title: string; content: string; views: number; metadata: ...; author: { name: string; email: string } }[]
// Native ID string parsingconst onlyIDs = await Post.select(db, { select: { id: true, title: true }});// Type: { id: RecordId<"post">; title: string }[]
// Type-safe omit - exclude fields from resultconst users = await User.select(db, { omit: { password: true },});// Type: Omit<User, 'password'>[]DRY & Reusable Definitions
Section titled “DRY & Reusable Definitions”Keep your models clean by abstracting common field patterns and logic.
🧱 Field Helper Functions
Section titled “🧱 Field Helper Functions”Instead of repeating complex validation, wrap field definitions in functions.
// Reusable email field with consistent validationexport const EmailField = (options = {}) => Field.string({ assert: surql`string::is::email($value)`, comment: 'User email address', ...options });
// Usage in modelsclass User extends Table.normal({ name: 'user', fields: { email: EmailField({ unique: true }), }}) {}🏗️ Base Models & Shared Methods
Section titled “🏗️ Base Models & Shared Methods”You can use TypeScript inheritance to share logic across models.
class BaseModel extends Table.normal({ ... }) { // Shared logic for all models extending this class async archive(db: Surreal) { return this.update(db, { data: { archived: true }, mode: 'merge' }); }}
class Post extends BaseModel { // Post inherits .archive()}📋 Shared Fields (Spread Pattern)
Section titled “📋 Shared Fields (Spread Pattern)”Need the same timestamps on every table? Use the spread operator.
const TIMESTAMPS = { createdAt: Field.datetime({ default: surql`time::now()`, readonly: true }), updatedAt: Field.datetime({ value: surql`time::now()` }),};
class Post extends Table.normal({ name: 'post', fields: { title: Field.string(), ...TIMESTAMPS }}) {}🚀 Deep Dives
Section titled “🚀 Deep Dives”Looking for more than just a snippet? Check out our in-depth guides:
- Mastering Graph Relations — Many-to-many and edge properties.
- Security & Permissions — Row-level security patterns.
- Testing Strategies — Fast in-memory unit testing.
- Common Patterns — Architectural patterns like singletons and hierarchies.
- Migrations & Schema Sync — CLI usage and CI/CD integration.
📖 Continue exploring the Tutorial or deep-dive into the API Reference.