A Drizzle-style ORM for SurrealDB built with TypeScript. Type-safe, chainable queries with zero runtime overhead.
- Type-safe - Full TypeScript support with compile-time type inference
- Zero runtime reflection - No decorators, no proxies hiding behavior
- SurrealQL-first - Direct SQL compilation with parameter binding
- Lightweight - Minimal dependencies, fast compilation
bun add surorm
npm install surormimport { table, id, string, datetime, select, eq, compileSelect } from 'surorm';
// Define schema
const users = table("user", {
id: id("id"),
email: string("email"),
createdAt: datetime("created_at"),
});
// Build and compile query
const query = select()
.from(users)
.where(eq(users.email, "test@example.com"))
.limit(1);
const { sql, params } = compileSelect(query);
// sql: "SELECT * FROM user WHERE email = $1 LIMIT 1"
// params: ["test@example.com"]Define your database schema with type-safe column definitions.
import { table, id, string, datetime } from 'surorm';
const users = table("user", {
// ID field - maps to SurrealDB record ID
id: id("id"),
// String field
name: string("name"),
// Datetime field
createdAt: datetime("created_at"),
});const users = table("user", {
id: id("id"),
// Unique constraint
email: string("email").unique(),
// Optional field
bio: string("bio").optional(),
// Default value
createdAt: datetime("created_at").default("time::now()"),
});SurORM automatically infers types for select and insert operations:
const users = table("user", {
id: id("id"),
email: string("email"),
createdAt: datetime("created_at"),
});
// Type for SELECT queries - includes all fields
type User = typeof users.$inferSelect;
// { id: string, email: string, createdAt: string }
// Type for INSERT - excludes fields with defaults
type InsertUser = typeof users.$inferInsert;
// { id: string, email: string }import { select, from, eq, and, or, gt, lt } from 'surorm';
// Basic select
select().from(users)
// With where clause
select().from(users).where(eq(users.email, "test@example.com"))
// With multiple conditions
select().from(users).where(and(
eq(users.status, "active"),
gt(users.createdAt, "2024-01-01")
))
// With OR
select().from(users).where(or(
eq(users.role, "admin"),
eq(users.role, "moderator")
))
// With limit and offset
select().from(users).limit(10).start(20)
// Order by
select().from(users).orderBy("createdAt", "DESC")import { insert, compileInsert } from 'surorm';
// Single record
insert(users).values({
id: "user:1",
email: "test@example.com"
})
// Multiple records
insert(users).values([
{ id: "user:1", email: "a@test.com" },
{ id: "user:2", email: "b@test.com" }
])
const { sql, params } = compileInsert(query);
// sql: "INSERT INTO user [{ id: $1, email: $2 }, { id: $3, email: $4 }]"import { update, compileUpdate } from 'surorm';
// Update all records
update(users).set({ status: "inactive" })
// Update with where clause
update(users)
.set({ email: "new@example.com" })
.where(eq(users.id, "user:1"))
const { sql, params } = compileUpdate(query);
// sql: "UPDATE user SET email = $1 WHERE id = $2"import { remove, compileDelete } from 'surorm';
// Delete all
remove(users)
// Delete with condition
remove(users).where(eq(users.status, "deleted"))
const { sql, params } = compileDelete(query);
// sql: "DELETE FROM user WHERE status = $1"import { eq, ne, gt, gte, lt, lte, like } from 'surorm';
eq(users.id, "user:1") // =
ne(users.status, "active") // !=
gt(users.age, 18) // >
gte(users.age, 18) // >=
lt(users.age, 65) // <
lte(users.age, 65) // <=
like(users.name, "%john%") // LIKEimport { and, or } from 'surorm';
// AND - all conditions must match
and(
eq(users.status, "active"),
gt(users.age, 18)
)
// OR - any condition can match
or(
eq(users.role, "admin"),
eq(users.role, "moderator")
)Create relationships between records using SurrealDB's RELATE statements.
import { edge, relate, compileRelate } from 'surorm';
// Define tables
const users = table("user", { id: id("id") });
const posts = table("post", { id: id("id"), title: string("title") });
// Define edge relationship
const authored = edge("authored", users, posts);
// Create relationship
relate(users.id("user:1"), authored, posts.id("post:42"))
// With data
relate(users.id("user:1"), authored, posts.id("post:42"))
.set({ createdAt: "time::now()" })
const { sql, params } = compileRelate(query);
// sql: "RELATE user:1->authored->post:42 SET createdAt = $1"Execute multiple operations atomically with transaction support.
import { SurrealORMClient } from 'surorm';
import Surreal from 'surrealdb';
const db = new Surreal();
await db.connect("ws://localhost:8000");
await db.use({ namespace: "test", database: "test" });
const client = new SurrealORMClient(db);
await client.transaction(async (tx) => {
// All operations in transaction
await tx.query("INSERT INTO user { id: 'user:1', email: 'test@example.com' }");
await tx.query("INSERT INTO post { title: 'Hello World' }");
// If no error, transaction commits automatically
});Capture current schema state:
import { createSnapshot } from 'surorm';
const users = table("user", {
id: id("id"),
email: string("email").unique(),
});
const posts = table("post", {
id: id("id"),
title: string("title"),
});
const snapshot = createSnapshot([users, posts]);
// snapshot.tables: [{ name: "post", columns: [...] }, { name: "user", columns: [...] }]Generate migration SQL from schema changes:
import { generateDiff, createSnapshot } from 'surorm';
const oldSnapshot = createSnapshot([users]); // Before
const newSnapshot = createSnapshot([users, posts]); // After
const diff = generateDiff(oldSnapshot, newSnapshot);
// diff.up: ["DEFINE TABLE post SCHEMAFULL;", "DEFINE FIELD id ON post TYPE string;", ...]
// diff.down: ["REMOVE TABLE post;"]Apply migrations automatically:
import { MigrationRunner } from 'surorm';
const runner = new MigrationRunner(db);
// Run migrations
const result = await runner.runMigrations(
[users, posts],
[{ name: "initial", sql: "..." }]
);
// result.applied: ["initial"]
// result.skipped: []| Function | Description |
|---|---|
table(name, columns) |
Define a table |
id(name) |
ID column |
string(name) |
String column |
datetime(name) |
Datetime column |
| Function | Description |
|---|---|
select() |
Create select query |
from(table) |
Specify source table |
insert(table) |
Create insert query |
update(table) |
Create update query |
remove(table) |
Create delete query |
| Function | Description |
|---|---|
eq(a, b) |
Equal |
ne(a, b) |
Not equal |
gt(a, b) |
Greater than |
gte(a, b) |
Greater or equal |
lt(a, b) |
Less than |
lte(a, b) |
Less or equal |
like(a, b) |
Like pattern |
and(...c) |
AND conditions |
or(...c) |
OR conditions |
| Function | Description |
|---|---|
compileSelect(query) |
Compile select to SQL |
compileInsert(query) |
Compile insert to SQL |
compileUpdate(query) |
Compile update to SQL |
compileDelete(query) |
Compile delete to SQL |
compileRelate(query) |
Compile relate to SQL |
import {
table, id, string, datetime,
select, insert, update, remove,
eq, compileSelect, compileInsert, compileUpdate, compileDelete
} from 'surorm';
// Schema
const users = table("user", {
id: id("id"),
email: string("email").unique(),
name: string("name"),
createdAt: datetime("created_at").default("time::now()"),
});
// CREATE
const insertResult = compileInsert(
insert(users).values({ id: "user:1", email: "test@example.com", name: "John" })
);
console.log(insertResult);
// { sql: "INSERT INTO user { id: $1, email: $2, name: $3 }", params: ["user:1", "test@example.com", "John"] }
// READ
const selectResult = compileSelect(
select().from(users).where(eq(users.email, "test@example.com"))
);
console.log(selectResult);
// { sql: "SELECT * FROM user WHERE email = $1", params: ["test@example.com"] }
// UPDATE
const updateResult = compileUpdate(
update(users).set({ name: "Jane" }).where(eq(users.id, "user:1"))
);
console.log(updateResult);
// { sql: "UPDATE user SET name = $1 WHERE id = $2", params: ["Jane", "user:1"] }
// DELETE
const deleteResult = compileDelete(
remove(users).where(eq(users.id, "user:1"))
);
console.log(deleteResult);
// { sql: "DELETE FROM user WHERE id = $1", params: ["user:1"] }bun testAll 41 tests passing.
MIT
OwO