Skip to content
🚀 This documentation is for unreal-orm 1.0.0 alpha which requires SurrealDB 2.0 SDK. For use with version 1.x, see here.

Cookbook

Need a quick pattern without reading the whole tutorial? Grab a ready-made recipe below 👇

All snippets assume db is an active Surreal connection and models are already defined.

// Fetch the next page of posts ordered by newest first
const pageSize = 10;
const posts = await Post.select(db, {
orderBy: [{ field: 'createdAt', order: 'desc' }],
start: 20, // skip first 2 pages
limit: pageSize,
});
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` });
}
}
// Authorised values enforced in SurrealDB
export 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"]`,
}),
},
}) {}
// Partially update fields without replacing the whole record
await Post.update(db, 'post:123', {
data: { content: 'Updated body' },
mode: 'merge',
});
await Post.select(db, {
where: surql`title ~~ $q OR content ~~ $q`,
vars: { q: '*orm*' },
});

Select specific fields with full type inference:

import { typed } from 'unreal-orm';
// Select specific fields - return type is inferred
const 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() helper
const 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 default
const 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 parsing
const onlyIDs = await Post.select(db, {
select: { id: true, title: true }
});
// Type: { id: RecordId<"post">; title: string }[]
// Type-safe omit - exclude fields from result
const users = await User.select(db, {
omit: { password: true },
});
// Type: Omit<User, 'password'>[]

Keep your models clean by abstracting common field patterns and logic.

Instead of repeating complex validation, wrap field definitions in functions.

// Reusable email field with consistent validation
export const EmailField = (options = {}) =>
Field.string({
assert: surql`string::is::email($value)`,
comment: 'User email address',
...options
});
// Usage in models
class User extends Table.normal({
name: 'user',
fields: {
email: EmailField({ unique: true }),
}
}) {}

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()
}

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
}
}) {}

Looking for more than just a snippet? Check out our in-depth guides:

📖 Continue exploring the Tutorial or deep-dive into the API Reference.