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.

Introduction

Unreal ORM Logo

GitHub Stars npm version License: ISC npm downloads TypeScript


A modern, type-safe ORM for SurrealDB. Native SurrealDB power, full TypeScript safety, zero abstraction—no decorators, no magic, just classes and functions.

UnrealORM builds on top of the official surrealdb package, providing a TypeScript ORM experience while preserving full access to SurrealDB’s native features. Define your schema once in code, and the ORM handles type inference, DDL generation, and schema synchronization.

Note: UnrealORM 1.0.0-alpha.x requires SurrealDB’s 2.0 (alpha) JS SDK. If you’re using 1.x of their SDK, install unreal-orm@0.6.0 instead. To upgrade, see the Migration Guide.

Terminal window
bunx @unreal-orm/cli init
# Or with other package managers
npx @unreal-orm/cli init
pnpm dlx @unreal-orm/cli init
yarn dlx @unreal-orm/cli init

This will:

  • Set up your project structure (unreal/ folder)
  • Configure database connection (surreal.ts)
  • Install dependencies (unreal-orm, surrealdb, @unreal-orm/cli)
  • Optionally generate sample tables or import from existing database
Manual installation
Terminal window
# Using bun
bun add unreal-orm@latest surrealdb@latest
bun add -D @unreal-orm/cli@latest
# Using pnpm
pnpm add unreal-orm@latest surrealdb@latest
pnpm add -D @unreal-orm/cli@latest
# Using npm
npm install unreal-orm@latest surrealdb@latest
npm install -D @unreal-orm/cli@latest
# Using yarn
yarn add unreal-orm@latest surrealdb@latest
yarn add -D @unreal-orm/cli@latest
  • Type-safe models — Define tables as classes with full TypeScript inference for fields, queries, and results
  • Schema sync — Generate DDL from code with applySchema(), or generate code from database with unreal pull
  • Relations — Typed record links with automatic hydration via fetch
  • Native SurrealQL & query builder — Use surql templates and functional expressions directly, or filter with typed field proxies in select, count, updateMany, and deleteMany
  • Indexes — Define unique, composite, and search indexes with full type safety
  • Custom methods — Add instance and static methods to your models
  • CLI toolsinit, pull, push, diff, mermaid for schema management
import { Surreal, surql } from "surrealdb";
import { Table, Field, Index, Unreal } from "unreal-orm";
// Define a User model with validation and custom methods
class User extends Table.normal({
name: "user",
fields: {
name: Field.string(),
email: Field.string({ assert: surql`$value CONTAINS "@"` }),
createdAt: Field.datetime({ default: surql`time::now()` }),
},
}) {
getDisplayName() {
return `${this.name} <${this.email}>`;
}
}
// Define a unique index
const idx_user_email = Index.define(() => User, {
name: "idx_user_email",
fields: ["email"],
unique: true,
});
// Define a Post with a relation to User
class Post extends Table.normal({
name: "post",
fields: {
title: Field.string(),
content: Field.string(),
author: Field.record(() => User),
},
}) {}
async function main() {
const db = new Surreal();
await db.connect("ws://localhost:8000", {
namespace: "test",
database: "test",
authentication: { username: "root", password: "root" },
});
// Apply schema to database
await Unreal.applySchema(db, [User, idx_user_email, Post]);
// Create records
const user = await User.create(db, {
name: "Alice",
email: "alice@example.com",
});
const post = await Post.create(db, {
title: "Hello",
content: "World",
author: user.id,
});
// Query with hydrated relations
const result = await Post.select(db, {
from: post.id,
only: true,
fetch: ["author"],
});
console.log(result.author.getDisplayName()); // "Alice <alice@example.com>"
// Update with explicit mode
await user.update(db, { data: { name: "Alice Smith" }, mode: "merge" });
await db.close();
}

See the Hands-on Tutorial for a complete walkthrough building a blog API with users, posts, comments, and relations.

Select specific fields with full type inference:

import { typed } from "unreal-orm";
import { surql } from "surrealdb";
// Nested object fields - types inferred from objectSchema
const posts = await Post.select(db, {
select: { title: true, metadata: { category: true } },
});
// Type: { title: string; metadata: { category: string } }[]
// Nested record fields - types inferred from linked table
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 }[]
// Type-safe omit - exclude fields from result
const users = await User.select(db, {
omit: { password: true, secret: true },
});
// Type: Omit<User, 'password' | 'secret'>[]

Use the callback-based where API for fully typed filters with IntelliSense:

import { and, or, eq, gt } from "unreal-orm";
// SELECT with typed filters
const posts = await Post.select(db, {
where: (f) => f.views.gt(100),
});
// Use SurrealDB string/array/date functions on columns
const recent = await Post.select(db, {
where: (f) => f.title.toLowerCase().contains("surreal"),
});
// Native function namespaces cover the entire SurrealDB function surface
const filtered = await Post.select(db, {
where: (f) => and(
f.title.string.starts_with("hello"),
f.views.math.round().eq(100),
),
});
// Compose logical expressions
const featuredTech = await Post.select(db, {
where: (f) =>
and(
eq(f.metadata.category, "tech"),
or(f.metadata.featured.eq(true), gt(f.views, 1000)),
),
});
// COUNT, UPDATE, and DELETE also accept callbacks
const popular = await Post.count(db, {
where: (f) => f.views.gt(100),
});
await Post.updateMany(db, {
where: (f) => f.metadata.category.eq("tech"),
data: { views: 0 },
mode: "merge",
});
await Post.deleteMany(db, {
where: (f) => f.tags.contains("deprecated"),
});

Available operators include eq, ne, gt, gte, lt, lte, exact (==), isNone, isNull, isTrue, isFalse, inside, outside, intersects, matches (full-text @@), isIn, isNotIn, containsAny, containsAll, containsNone, plus logical helpers and, or, not (all available as both column methods and standalone helpers).

Arithmetic operators (add, subtract, multiply, divide, modulo) return ColumnRef for chaining:

// WHERE views + 10 > 100
const trending = await Post.select(db, {
where: (f) => f.views.add(10).gt(100),
});

Graph traversal (out, in, both) for ->, <-, <-> operators:

// WHERE author->follow->user.name = "Alice"
const posts = await Post.select(db, {
where: (f) => f.author.out("follow").out("user").name.eq("Alice"),
});

Columns expose all native SurrealDB function namespaces: string, math, array, time, is, meta, parse, bytes, crypto, duration, encoding, geo, http, object, rand, record, search, session, type, vector. Any built-in function can be called, e.g. f.title.string.lowercase(), f.views.math.round(2), f.password.crypto.sha256(), f.location.geo.distance(other). Nested functions with :: use bracket notation: f.title.string['similarity::jaro']().

Runtime availability depends on the connected SurrealDB version; full-text matches() requires a SEARCH index, search::* and vector::* require SurrealDB 2.x+, and regex operators such as ~ and !~ were removed in SurrealDB 3.x.

The CLI helps manage schema synchronization between your code and database:

Terminal window
unreal init # Initialize project with connection and sample tables
unreal pull # Generate TypeScript models from database schema
unreal push # Apply TypeScript schema to database
unreal diff # Compare code vs database schema
unreal mermaid # Generate ERD diagram
unreal view # Interactive TUI for browsing/editing records
unreal docs # Open the UnrealORM documentation
unreal github # Open the UnrealORM GitHub repository

After init, the CLI is installed as a dev dependency and can be run via bunx unreal or npx unreal.

UnrealORM is created and maintained by Jimpex.

ISC License