@farming-labs/orm

SurrealDB

SurrealDB integration is runtime-first.

Use @farming-labs/orm-surrealdb when:

Supported SurrealDB runtime inputs

The current runtime uses the official client directly. It does not invent another hidden SQL or graph client layer.

Runtime setup

surrealdb-runtime.ts
import { createNodeEngines } from "@surrealdb/node";
import { createRemoteEngines, Surreal } from "surrealdb";
import { createOrm } from "@farming-labs/orm";
import { createSurrealDbDriver } from "@farming-labs/orm-surrealdb";
import { appSchema } from "./schema";

const db = new Surreal({
  engines: {
    ...createRemoteEngines(),
    ...createNodeEngines(),
  },
});

await db.connect(process.env.SURREAL_URL ?? "ws://127.0.0.1:8000/rpc");
await db.use({
  namespace: "app",
  database: "main",
});

const orm = createOrm({
  schema: appSchema,
  driver: createSurrealDbDriver({
    client: db,
  }),
});

From there, shared code keeps using the same unified API:

const user = await orm.user.findUnique({
  where: {
    email: "ada@farminglabs.dev",
  },
  select: {
    id: true,
    email: true,
    sessions: {
      select: {
        token: true,
      },
    },
  },
});

What the SurrealDB driver is doing

The SurrealDB driver keeps the shared ORM surface and stores records through the official client using ORM-managed record and unique-lookup entries.

It:

  1. accepts the app's real SurrealDB client
  2. executes reads and writes through the official client and transaction API
  3. keeps the same normalized error and capability surface as the rest of the repo
  4. resolves relations conservatively through follow-up lookups

That means a package can write its storage layer once while each app decides whether the actual execution stack is SurrealDB, Prisma, Drizzle, Kysely, MikroORM, TypeORM, Sequelize, direct SQL, Neo4j, Firestore, DynamoDB, Redis, Unstorage, MongoDB, or Mongoose.

Runtime helper path

If a framework or shared package wants to accept the raw SurrealDB client directly, use the runtime helpers:

import { createOrmFromRuntime } from "@farming-labs/orm-runtime";

const orm = await createOrmFromRuntime({
  schema: appSchema,
  client: db,
});

That is the cleanest path for higher-level integrations that do not want to branch on SurrealDB specifically.

Setup helpers

The setup helpers still work with SurrealDB:

import { bootstrapDatabase, pushSchema } from "@farming-labs/orm-runtime/setup";

await pushSchema({
  schema: appSchema,
  client: db,
});

const orm = await bootstrapDatabase({
  schema: appSchema,
  client: db,
});

For SurrealDB runtimes, pushSchema(...) and applySchema(...) are intentional no-ops.

That is useful when a package or framework wants:

Transactions and limits

The current SurrealDB runtime supports normal read and write transactions through the official client's transaction API.

That means:

Two boundaries still matter:

Relation support

SurrealDB is a multi-model database, but the current ORM layer stays conservative about relation loading.

That means:

work through the shared relation resolver and follow-up lookups instead of claiming native graph or SQL join planning.

That makes the current runtime a strong fit for:

Local verification

Run the local SurrealDB suite with:

terminal
pnpm test:local:surrealdb

That package-local suite uses a real embedded mem:// SurrealDB client through the official SDK, so the runtime, helper, relation, transaction, and duplicate-key paths are exercised against a live local backend instead of a fake harness.