# Runtime
URL: /docs/runtime
LLM index: /llms.txt
Description: What runtime means in Farming Labs ORM and how it differs from generation.

# Runtime

In Farming Labs ORM, **runtime** is the layer that executes the typed query API.

It is separate from:

- schema authoring
- code generation

## The shape

```ts title="create-orm.ts"
import { createMemoryDriver, createOrm } from "@farming-labs/orm";
import { authSchema } from "./schema";

const orm = createOrm({
  schema: authSchema,
  driver: createMemoryDriver(),
});
```

That gives you typed model clients such as `orm.user`, `orm.session`, and
`orm.account`.

It also exposes the attached runtime handle on `orm.$driver`. That gives app
code access to the underlying instance that was passed into the driver:

```ts title="driver-handle.ts"
const orm = createOrm({
  schema: authSchema,
  driver: createPrismaDriver({ client: prisma }),
});

orm.$driver.kind; // "prisma"
orm.$driver.client; // the same PrismaClient instance
orm.$driver.capabilities.upsert; // "native" | "emulated" | "none"
orm.$driver.capabilities.returning.update; // whether update returns records in this ORM layer
```

The handle also exposes read-only capability metadata:

```ts title="driver-capabilities.ts"
const orm = createOrm({
  schema: authSchema,
  driver: createPgPoolDriver(pool),
});

orm.$driver.capabilities.supportsJSON; // true
orm.$driver.capabilities.supportsTransactions; // true
orm.$driver.capabilities.nativeRelationLoading; // "partial"
orm.$driver.capabilities.numericIds; // "none" | "manual" | "generated"
orm.$driver.capabilities.textMatching.equality; // "database-default" | "case-sensitive" | "case-insensitive"
orm.$driver.capabilities.nativeRelations.hasMany; // true | false
```

That metadata is derived from the driver, not passed in manually through
`createOrm(...)`. It is frozen read-only runtime metadata, so higher layers can
inspect it safely without treating it as mutable app state.

The repo also verifies this against real local database-backed runtimes. The
local SQL and Prisma integration suites assert the same capability metadata on
`orm.$driver` and again inside real transaction scopes.

If you need to inspect a raw client before building a driver, the core package
also exposes `detectDatabaseRuntime(...)`:

```ts title="detect-runtime.ts"
import { detectDatabaseRuntime } from "@farming-labs/orm";

const detected = detectDatabaseRuntime(prisma);

detected?.kind; // "prisma"
detected?.dialect; // "postgres" | "mysql" | "sqlite" when detectable
detected?.source; // "client" | "db" | "connection" | "pool" | "database"
detected?.client; // the same instance you passed in
```

If detection fails, use the diagnostic report instead of guessing:

```ts title="explain-runtime-detection.ts"
import { inspectDatabaseRuntime } from "@farming-labs/orm";

const report = inspectDatabaseRuntime(client);

report.runtime; // detected runtime or null
report.summary; // human-readable summary
report.hint; // recommended next step or override
report.candidates; // heuristics that almost matched
```

If you want the same fallback behavior in one step, use
`@farming-labs/orm-runtime`:

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

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

orm.$driver.kind; // "prisma"
orm.$driver.client; // the same PrismaClient instance
```

That light root entrypoint keeps `createOrm(...)` itself explicit while giving
higher-level integrations a clean “accept the raw client and normalize it”
entrypoint.

The setup helpers now live on the Node-only subpath:

- `pushSchema(...)`
- `applySchema(...)`
- `bootstrapDatabase(...)`

Import them from `@farming-labs/orm-runtime/setup`. They use the same runtime
detection layer to prepare direct SQL, Prisma, Drizzle, Kysely, MikroORM,
TypeORM, Sequelize, EdgeDB / Gel, Neo4j, Cloudflare D1, Cloudflare KV, Redis,
Supabase, Xata, MongoDB, Mongoose, Firestore, DynamoDB, and Unstorage runtimes
without inventing a separate config surface.

There is also a dedicated guide for this flow:

- [Runtime Helpers](/docs/runtime/runtime-helpers)
- [Framework Authors](/docs/use-cases/framework-authors)

For EdgeDB / Gel specifically, the runtime path is supported directly through
the Gel SQL client, while schema management stays in the app's own Gel
migration or SQL workflow.

The runtime also understands declared model-level compound unique keys. If a
schema says `account(provider, accountId)` is unique, live drivers can use that
same shape in `findUnique(...)` and `upsert(...)`.

Numeric IDs are first-class now too. `id({ type: "integer" })` gives you manual
numeric IDs, while `id({ type: "integer", generated: "increment" })` enables
auto-generated numeric IDs on the supporting SQL-family, Prisma, and memory
runtimes.

## Query surface

The runtime client supports:

- `findUnique`
- `findOne`
- `findFirst`
- `findMany`
- `count`
- `create`
- `createMany`
- `update`
- `updateMany`
- `upsert`
- `delete`
- `deleteMany`
- `transaction`
- `batch`

## Relations

All live runtime drivers support:

- `belongsTo`
- `hasOne`
- `hasMany`
- explicit join-table `manyToMany`

That means nested queries like `user.profile`, `user.sessions`, and
`session.user` work across the supported runtimes.

For loading strategy, the current runtime behaves like this:

- direct SQL, Drizzle, and Kysely use a native single-query path for:
  - singular relation chains built from `belongsTo(...)` and `hasOne(...)`
  - simple `hasMany(...)` and explicit join-table `manyToMany(...)` branches
    when the relation branch does not add its own `where`, `orderBy`, `take`, or `skip`
- Prisma translates supported nested relation branches directly into Prisma
  delegate `select` trees, including simple explicit join-table
  `manyToMany(...)` convenience traversal through the generated join model
- relation branches that add their own filtering, ordering, paging, or other
  unsupported planner requirements still fall back to the shared relation resolver
- MongoDB and Mongoose keep using the relation resolver today

That fallback behavior is mostly a **current implementation boundary**, not a
statement that the backend cannot do more. PostgreSQL, MySQL, SQLite, Prisma,
and MongoDB all have richer native relation-loading options available. This repo
currently uses the native path for the safest direct branches first and keeps
the shared resolver for the more complex shapes until those planners are added.

It also means unique lookups can be:

- a generated `id`
- a single `.unique()` field such as `email` or `token`
- a declared compound unique constraint such as `provider + accountId`

## Capabilities and Errors

Every live ORM client exposes read-only capability metadata on `orm.$driver.capabilities`.

That includes:

- basic value support like JSON, dates, booleans, and transactions
- numeric ID support
- namespace and transactional DDL support
- mutation-returning support
- upsert behavior
- text comparison behavior
- relation-loading capability

Runtime failures are normalized too. Duplicate-key, foreign-key, missing-table,
deadlock, and transaction-conflict failures are exposed as `OrmError` values
through the unified ORM client, so higher-level integrations do not need to
parse Prisma, SQL, EdgeDB, Xata, MongoDB, or Mongoose error formats separately.

Runtime-aware schema setup failures from `@farming-labs/orm-runtime/setup` are
exposed separately as `RuntimeSetupError`, so bootstrap and push failures keep
the stage, runtime kind, dialect, and underlying cause available.

## Available drivers

- `createMemoryDriver(...)` from `@farming-labs/orm`
- `createPrismaDriver(...)` from `@farming-labs/orm-prisma`
- `createDrizzleDriver(...)` from `@farming-labs/orm-drizzle`
- `createKyselyDriver(...)` from `@farming-labs/orm-kysely`
- `createMikroormDriver(...)` from `@farming-labs/orm-mikroorm`
- `createTypeormDriver(...)` from `@farming-labs/orm-typeorm`
- `createSequelizeDriver(...)` from `@farming-labs/orm-sequelize`
- `createEdgeDbDriver(...)` from `@farming-labs/orm-edgedb`
- `createD1Driver(...)` from `@farming-labs/orm-d1`
- `createKvDriver(...)` from `@farming-labs/orm-kv`
- `createRedisDriver(...)` from `@farming-labs/orm-redis`
- `createSupabaseDriver(...)` from `@farming-labs/orm-supabase`
- `createSqliteDriver(...)` from `@farming-labs/orm-sql`
- `createMysqlDriver(...)` from `@farming-labs/orm-sql`
- `createPgPoolDriver(...)` from `@farming-labs/orm-sql`
- `createPgClientDriver(...)` from `@farming-labs/orm-sql`
- `createSupabasePoolDriver(...)` from `@farming-labs/orm-sql`
- `createSupabaseClientDriver(...)` from `@farming-labs/orm-sql`
- `createFirestoreDriver(...)` from `@farming-labs/orm-firestore`
- `createMongoDriver(...)` from `@farming-labs/orm-mongo`
- `createMongooseDriver(...)` from `@farming-labs/orm-mongoose`
- `createDynamodbDriver(...)` from `@farming-labs/orm-dynamodb`
- `createUnstorageDriver(...)` from `@farming-labs/orm-unstorage`
- `createOrmFromRuntime(...)` from `@farming-labs/orm-runtime`
- `createDriverFromRuntime(...)` from `@farming-labs/orm-runtime`
- `pushSchema(...)` from `@farming-labs/orm-runtime/setup`
- `applySchema(...)` from `@farming-labs/orm-runtime/setup`
- `bootstrapDatabase(...)` from `@farming-labs/orm-runtime/setup`

## Why this matters

Libraries can write one storage layer against `createOrm(...)` and let each app
bring its own runtime.

```ts title="find-user-by-email.ts"
async function findUserByEmail(orm: typeof authOrm, email: string) {
  return orm.user.findUnique({
    where: { email },
    select: {
      id: true,
      email: true,
      sessions: {
        select: { token: true },
      },
    },
  });
}
```

That helper stays the same whether the app uses Prisma, Drizzle, TypeORM,
Sequelize, EdgeDB / Gel, Cloudflare D1, Cloudflare KV, Redis, Supabase, direct
SQL, Firestore, DynamoDB, Unstorage, MongoDB, or Mongoose.

The same is true for compound auth lookups:

```ts title="find-account.ts"
async function findAccount(orm: typeof authOrm, provider: string, accountId: string) {
  return orm.account.findUnique({
    where: {
      provider,
      accountId,
    },
  });
}
```

## Local verification

`pnpm test` already includes these real integration suites. Use the commands
below when you want to rerun the database-backed paths directly.

```bash title="terminal"
pnpm test:local
pnpm test:local:prisma
pnpm test:local:drizzle
pnpm test:local:sqlite
pnpm test:local:postgres
pnpm test:local:mysql
pnpm test:local:supabase
pnpm test:local:dynamodb
pnpm test:local:unstorage
pnpm test:local:mongodb
```

## Continue

- [Runtime Helpers](/docs/runtime/runtime-helpers)
- [Query API](/docs/runtime/query-api)
- [Memory driver](/docs/runtime/memory-driver)
- [Schema relations](/docs/schema/relations)