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:
- a
PrismaClient - a Drizzle database
- a Kysely instance
- an EdgeDB / Gel SQL client
- a Neo4j driver or session
- a MikroORM instance or
EntityManager - a TypeORM
DataSource - a Sequelize instance
- a Cloudflare D1 binding
- a Cloudflare KV namespace
- a Redis or Upstash Redis client
- a Supabase client created with
createClient(...) - a Xata client with
client.sql(...) - a
pgpool/client - a
mysql2pool/connection - a Firestore client
- a
DynamoDBClientorDynamoDBDocumentClient - an Unstorage
createStorage(...)instance - a Mongo
DborMongoClient - a Mongoose connection
createOrmFromRuntime(...)
This is the highest-level helper.
import { createOrmFromRuntime } from "@farming-labs/orm-runtime";
const orm = await createOrmFromRuntime({
schema,
client: prisma,
});It does four things:
- detects the runtime with the same logic as
detectDatabaseRuntime(...) - creates the right Farming ORM driver
- calls
createOrm(...) - 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.
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:
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:
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 matchespushSchema(...), 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.
import { bootstrapDatabase } from "@farming-labs/orm-runtime/setup";
const orm = await bootstrapDatabase({
schema,
client: prisma,
});Use them like this:
pushSchema(...)Ensures the current schema is pushed into the live runtime.applySchema(...)Applies the current schema through the same runtime-aware setup path.bootstrapDatabase(...)Pushes the schema, then returnscreateOrmFromRuntime(...).
When setup fails, the setup subpath throws RuntimeSetupError with the stage,
detected runtime kind, dialect, and original cause preserved:
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:
- detect the runtime
- create the driver
- create the ORM
- 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.
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:
createOrmFromRuntime(...)detects the Gel SQL client directlypushSchema(...)andapplySchema(...)are no-opsbootstrapDatabase(...)still returns the ORM, but assumes the Gel schema already exists
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:
createOrmFromRuntime(...)detects the Neo4j driver or session directlypushSchema(...)andapplySchema(...)create ORM-owned constraints and indexes- relation loading stays on the shared resolver path instead of claiming Cypher-native graph traversal planning
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:
pushSchema(...)andapplySchema(...)are no-opsbootstrapDatabase(...)still works as the single convenience entrypoint- relation loading uses follow-up reads instead of a native join planner
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:
pushSchema(...)andapplySchema(...)are no-opsbootstrapDatabase(...)still works as the single convenience entrypoint- relation loading uses follow-up reads instead of a native join planner
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:
pushSchema(...)andapplySchema(...)are no-opsbootstrapDatabase(...)still works as the single convenience entrypoint- the runtime uses the Supabase table API instead of a hidden raw
pgbridge
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:
createOrmFromRuntime(...)detects the Xata client through its SQL surfacepushSchema(...)andapplySchema(...)render PostgreSQL-safe SQL and apply it through the Xata clientorm.transaction(...)does not overclaim interactive rollback semantics
Prisma
For Prisma, the helper renders a temporary Prisma schema and runs prisma db push --skip-generate under the hood.
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:
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.
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:
- record items
- internal unique-lock items
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:
- it works well for lightweight key-value/document storage
- it keeps the same storage-layer API surface as the other runtimes
- it is not meant for highly relational or join-heavy database workloads
createDriverFromRuntime(...)
If you want the driver but still want to call createOrm(...) yourself, use
the lower-level helper:
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:
- Prisma client
- Drizzle database
- Kysely instance
- EdgeDB / Gel SQL client
- MikroORM instance or
EntityManager - TypeORM
DataSource - Sequelize instance
- Cloudflare D1 binding
- Cloudflare KV namespace
- Redis client
- Upstash Redis client
- Supabase client
- SQLite database
pgPoolpgClientmysql2Poolmysql2Connection- Firestore client
DynamoDBClientDynamoDBDocumentClient- Unstorage storage
- Mongo
Db - Mongo
MongoClient - Mongoose connection
Examples
Prisma
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
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.
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:
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
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:
const db = client.db("app");
const orm = await createOrmFromRuntime({
schema,
client: db,
});If you pass a MongoClient, also provide databaseName:
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.
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:
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.
import { detectDatabaseRuntime } from "@farming-labs/orm";
const detected = detectDatabaseRuntime(prisma);
detected?.kind; // "prisma"
detected?.client; // the same instanceUse 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:
- direct SQL on SQLite, PostgreSQL, and MySQL
- Drizzle on SQLite, PostgreSQL, and MySQL
- Kysely on SQLite, PostgreSQL, and MySQL
- TypeORM on SQLite-family, PostgreSQL, and MySQL
- Sequelize on PostgreSQL and MySQL
- Prisma on SQLite, PostgreSQL, and MySQL
- Firestore
- DynamoDB through a local Dynalite-backed suite
- Unstorage through the in-memory and
fs-litedrivers - MongoDB
- Mongoose
Continue
How is this guide?