@farming-labs/orm

Runtime Helpers

@farming-labs/orm-runtime is the light, lazy convenience layer on top of createOrm(...).

It accepts a raw runtime instance, detects what it is, creates the matching driver, and then builds the ORM for you.

Why it exists

The core createOrm(...) API stays explicit:

const orm = createOrm({
  schema,
  driver: createPrismaDriver({ client: prisma }),
});

The runtime helper is for higher-level integrations that want to accept the raw client directly:

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

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

That is especially useful when another library wants to accept:

createOrmFromRuntime(...)

This is the highest-level helper.

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

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

It does four things:

  1. detects the runtime with the same logic as detectDatabaseRuntime(...)
  2. creates the right Farming ORM driver
  3. calls createOrm(...)
  4. returns the normal typed ORM client

The returned client still exposes the same generic capability and normalized error surface as any other Farming ORM client through orm.$driver.capabilities and OrmError.

Capabilities and normalized errors

The runtime helper does not return a special client shape. Once the ORM is created, it behaves like any other Farming ORM client.

runtime-capabilities-and-errors.ts
import { isOrmError } from "@farming-labs/orm";
import { createOrmFromRuntime } from "@farming-labs/orm-runtime";

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

orm.$driver.capabilities.upsert; // "native" | "emulated" | "none"
orm.$driver.capabilities.returning.update; // true | false
orm.$driver.capabilities.supportsSchemaNamespaces; // true | false
orm.$driver.capabilities.numericIds; // "none" | "manual" | "generated"
orm.$driver.capabilities.textMatching.contains; // "database-default" | "case-sensitive" | "case-insensitive"
orm.$driver.capabilities.nativeRelations.manyToMany; // true | false

const error = await orm.user
  .create({
    data: {
      email: "duplicate@farminglabs.dev",
      name: "Ada",
    },
  })
  .catch((reason) => reason);

if (isOrmError(error)) {
  error.code; // "UNIQUE_CONSTRAINT_VIOLATION"
  error.backendKind; // "sql" | "prisma" | "mongo" | "dynamodb" | ...
}

Generated integer IDs are now first-class in the supporting runtimes too:

generated-numeric-id.ts
const schema = defineSchema({
  auditEvent: model({
    table: tableName("audit_events", { schema: "auth" }),
    fields: {
      id: id({ type: "integer", generated: "increment" }),
      email: string().unique(),
    },
  }),
});

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

const created = await orm.auditEvent.create({
  data: {
    email: "ada@farminglabs.dev",
  },
});

created.id; // 1, 2, 3, ...

If runtime detection fails, ask for the structured report:

runtime-detection-report.ts
import { inspectDatabaseRuntime } from "@farming-labs/orm";

const report = inspectDatabaseRuntime(client);

report.runtime; // null when detection failed
report.summary; // high-level reason
report.hint; // what to pass explicitly
report.candidates; // partial heuristic matches

pushSchema(...), applySchema(...), and bootstrapDatabase(...)

The runtime-aware setup flow lives on the Node-only setup subpath.

That matters for Cloudflare D1 in particular: the live runtime path works in a Worker, while the setup helpers fit best in local, CI, or other Node-managed bootstrap flows.

bootstrap-database.ts
import { bootstrapDatabase } from "@farming-labs/orm-runtime/setup";

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

Use them like this:

When setup fails, the setup subpath throws RuntimeSetupError with the stage, detected runtime kind, dialect, and original cause preserved:

runtime-setup-error.ts
import { RuntimeSetupError, pushSchema } from "@farming-labs/orm-runtime/setup";

try {
  await pushSchema({
    schema,
    client: prisma,
  });
} catch (error) {
  if (error instanceof RuntimeSetupError) {
    error.stage; // "push"
    error.runtimeKind; // "prisma"
    error.dialect; // "sqlite" | "postgres" | "mysql" | undefined
  }
}

That means one package now covers:

  1. detect the runtime
  2. create the driver
  3. create the ORM
  4. prepare the live database or collections

SQL-family runtimes

For direct SQL, Drizzle, and Kysely, the helper renders safe SQL DDL and applies it to the detected dialect.

push-sql-schema.ts
import { pushSchema } from "@farming-labs/orm-runtime/setup";

await pushSchema({
  schema,
  client: pool,
});

Cloudflare D1

For Cloudflare D1, pass the Worker binding or a local Miniflare D1 binding directly:

const orm = await createOrmFromRuntime({
  schema,
  client: env.DB,
});

The D1 setup path uses the same schema and the same helper surface, but it is best used in local or CI flows because @farming-labs/orm-runtime/setup stays Node-only.

EdgeDB / Gel

For EdgeDB, pass the official Gel SQL client directly:

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

This runtime is intentionally query-first:

Neo4j

For Neo4j, pass the official driver or session directly:

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

This runtime is intentionally graph-aware but conservative:

Cloudflare KV

For Cloudflare KV, pass the namespace directly:

const orm = await createOrmFromRuntime({
  schema,
  client: env.KV,
});

The Cloudflare KV runtime keeps setup intentionally light:

Redis / Upstash Redis

For Redis and Upstash Redis, pass the client directly:

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

The Redis runtime keeps setup intentionally light:

Supabase

For the direct Supabase JS path, pass the client created with createClient(...) directly:

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

This path is intentionally query-first:

If the app already owns a raw PostgreSQL client connected to Supabase, use the normal SQL runtime path instead.

Xata

For Xata, pass the official client directly:

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

This runtime stays SQL-backed but conservative:

Prisma

For Prisma, the helper renders a temporary Prisma schema and runs prisma db push --skip-generate under the hood.

push-prisma-schema.ts
import { pushSchema } from "@farming-labs/orm-runtime/setup";

await pushSchema({
  schema,
  client: prisma,
});

If your Prisma client does not expose a usable datasource URL, pass one explicitly:

push-prisma-schema-explicit-url.ts
await pushSchema({
  schema,
  client: prisma,
  prisma: {
    databaseUrl: process.env.DATABASE_URL!,
  },
});

MongoDB and Mongoose

For MongoDB and Mongoose, the helper creates the declared collections and ensures unique/index metadata from the schema manifest.

push-mongo-schema.ts
await pushSchema({
  schema,
  client,
  databaseName: "app",
});

DynamoDB

For DynamoDB, the helper creates one table per model through the raw AWS client.

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

await pushSchema({
  schema,
  client: dynamo,
});

The DynamoDB runtime then stores:

inside that model table so exact unique and compound-unique lookups do not force higher-level packages to reimplement locking logic themselves.

Unstorage

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

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

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

That is the honest behavior for this runtime:

createDriverFromRuntime(...)

If you want the driver but still want to call createOrm(...) yourself, use the lower-level helper:

create-driver-from-runtime.ts
import { createOrm } from "@farming-labs/orm";
import { createDriverFromRuntime } from "@farming-labs/orm-runtime";

const driver = await createDriverFromRuntime({
  schema,
  client: prisma,
});

const orm = createOrm({
  schema,
  driver,
});

Supported runtime inputs

@farming-labs/orm-runtime currently supports:

Examples

Prisma

prisma.ts
import { PrismaClient } from "@prisma/client";
import { createOrmFromRuntime } from "@farming-labs/orm-runtime";

const prisma = new PrismaClient();

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

Direct SQL

pg.ts
import { Pool } from "pg";
import { createOrmFromRuntime } from "@farming-labs/orm-runtime";

const orm = await createOrmFromRuntime({
  schema,
  client: new Pool({
    connectionString: process.env.DATABASE_URL,
  }),
});

Drizzle

For PostgreSQL and MySQL, Drizzle usually carries enough runtime information on its own.

drizzle.ts
import { drizzle } from "drizzle-orm/node-postgres";
import { Pool } from "pg";
import { createOrmFromRuntime } from "@farming-labs/orm-runtime";

const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
});
const db = drizzle(pool);

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

For Drizzle SQLite, pass the raw client explicitly too:

drizzle-sqlite.ts
import { DatabaseSync } from "node:sqlite";
import { drizzle } from "drizzle-orm/sqlite-proxy";
import { createOrmFromRuntime } from "@farming-labs/orm-runtime";

const sqlite = new DatabaseSync("app.db");
const db = drizzle(async (sql, params, method) => {
  const statement = sqlite.prepare(sql);
  if (method === "run") {
    statement.run(...params);
    return { rows: [] };
  }
  return { rows: statement.all(...params) as Record<string, unknown>[] };
});

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

Kysely

kysely.ts
import { Kysely, PostgresDialect } from "kysely";
import { Pool } from "pg";
import { createOrmFromRuntime } from "@farming-labs/orm-runtime";

const db = new Kysely({
  dialect: new PostgresDialect({
    pool: new Pool({
      connectionString: process.env.DATABASE_URL,
    }),
  }),
});

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

TypeORM

If you already have a TypeORM DataSource, pass it directly:

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

const dataSource = new DataSource({
  type: "postgres",
  url: process.env.DATABASE_URL,
  entities: [],
});

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

Sequelize

If you already have a Sequelize instance, pass it directly:

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

const sequelize = new Sequelize(process.env.DATABASE_URL!, {
  dialect: "postgres",
  logging: false,
});

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

MongoDB

If you pass a Db, the helper can use it directly:

mongo-db.ts
const db = client.db("app");

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

If you pass a MongoClient, also provide databaseName:

mongo-client.ts
import { MongoClient } from "mongodb";
import { createOrmFromRuntime } from "@farming-labs/orm-runtime";

const client = new MongoClient(process.env.MONGODB_URL!);
await client.connect();

const orm = await createOrmFromRuntime({
  schema,
  client,
  databaseName: "app",
});

Mongoose

If you pass a Mongoose connection, the helper will infer models by collection name from the schema manifest.

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

const connection = await mongoose.createConnection(process.env.MONGODB_URL!).asPromise();

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

If you want to control the exact models or transforms, pass overrides:

mongoose-overrides.ts
const orm = await createOrmFromRuntime({
  schema,
  client: connection,
  mongoose: {
    models: {
      user: UserModel,
      profile: ProfileModel,
      session: SessionModel,
    },
  },
});

Unstorage

If you already have an Unstorage instance, pass it directly:

import { createStorage } from "unstorage";
import memoryDriver from "unstorage/drivers/memory";
import { createOrmFromRuntime } from "@farming-labs/orm-runtime";

const storage = createStorage({
  driver: memoryDriver(),
});

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

Use this when you want one storage layer over a lightweight key-value/document backend. Prefer the SQL-family runtimes when the workload is deeply relational or join-heavy.

Relationship with detectDatabaseRuntime(...)

These helpers build directly on top of runtime detection.

runtime-detection.ts
import { detectDatabaseRuntime } from "@farming-labs/orm";

const detected = detectDatabaseRuntime(prisma);

detected?.kind; // "prisma"
detected?.client; // the same instance

Use detectDatabaseRuntime(...) when you only want inspection.

Use createDriverFromRuntime(...) or createOrmFromRuntime(...) when you want to execute queries.

Use pushSchema(...), applySchema(...), or bootstrapDatabase(...) from @farming-labs/orm-runtime/setup when you also want the runtime helper package to prepare the live database.

Real local verification

The repo verifies this helper against live local runtimes, not a fake adapter layer.

That matrix includes:

Continue