@farming-labs/orm

SQL Databases

Farming Labs ORM supports SQL-backed teams in 2 different ways:

That means teams can either generate SQL artifacts, run the unified runtime against a real SQL client, or do both.

This is the integration path where method translation is already live today. The same typed calls such as findUnique, findMany, create, update, upsert, and deleteMany are compiled by the SQL driver into the target dialect at runtime.

Supported SQL dialects

Basic config

import { defineConfig } from "@farming-labs/orm-cli";
import { authSchema } from "./src/schema";

export default defineConfig({
  schemas: [authSchema],
  targets: {
    sql: {
      out: "./generated/sql/0001_init.sql",
      dialect: "postgres",
    },
  },
});

Runtime package

import { createOrm } from "@farming-labs/orm";
import { createMysqlDriver, createPgPoolDriver, createSqliteDriver } from "@farming-labs/orm-sql";
import { Pool } from "pg";
import { authSchema } from "./src/schema";

PostgreSQL with pg.Pool

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

const orm = createOrm({
  schema: authSchema,
  driver: createPgPoolDriver(pool),
});

MySQL

const orm = createOrm({
  schema: authSchema,
  driver: createMysqlDriver(mysqlPool),
});

SQLite

const orm = createOrm({
  schema: authSchema,
  driver: createSqliteDriver(sqliteDatabase),
});

All 3 drivers expose the same unified query and mutation API:

await orm.user.findUnique({ where: { email: "ada@farminglabs.dev" } });
await orm.session.upsert({
  where: { token: "session-token" },
  create: { userId: "user_1", token: "session-token", expiresAt: new Date() },
  update: { expiresAt: new Date() },
});

The direct SQL runtime also verifies:

For relation loading, the SQL runtime now has a native single-query path for joinable singular branches such as:

It also covers simple collection branches when the relation branch does not add its own where, orderBy, take, or skip, including:

That same native path is what the Drizzle and Kysely runtimes inherit, because both packages route through @farming-labs/orm-sql under the hood.

Plural branches still work too, but they currently fall back to the shared relation resolver when they need relation-level filters, paging, or ordering.

That fallback list should be read as the current implementation scope, not as a limitation of SQL itself. PostgreSQL, MySQL, and SQLite can all support richer native relation plans than the first singular path added here.

The native loader also keeps its internal presence markers on collision-safe aliases. That means a real model field such as present can still be selected normally without conflicting with native row-mapping metadata.

Example output

For a simple user model, the SQL generator emits output like:

create table if not exists "users" (
  "id" text primary key not null,
  "email_address" text not null unique,
  "createdAt" timestamp not null
);

If you want that SQL output as a string instead of writing a file, use renderSafeSql(...) from @farming-labs/orm. The CLI page includes the string render example: CLI generation.

How this fits into PostgreSQL, MySQL, and SQLite workflows

PostgreSQL

Use:

sql: {
  out: "./generated/sql/0001_init.sql",
  dialect: "postgres",
}

This is a good fit when the team wants schema-first SQL generation but does not need Prisma or Drizzle to be the consumer of that schema, or wants to pair the generated SQL with createPgPoolDriver(pool) at runtime.

MySQL

Use:

sql: {
  out: "./generated/sql/0001_init.sql",
  dialect: "mysql",
}

This works well when the database is MySQL but the team prefers a more direct SQL workflow. It also pairs cleanly with createMysqlDriver(mysqlPool) if the team wants the unified runtime API instead of handwritten query code.

SQLite

Use:

sql: {
  out: "./generated/sql/0001_init.sql",
  dialect: "sqlite",
}

This is especially practical for lightweight apps, prototypes, demos, and local development workflows, and it can run directly through createSqliteDriver(sqliteDatabase).

When the SQL path is a good fit

Important limit

The SQL generator currently emits safe SQL output. It is not trying to replace a full migration system with every advanced schema transform built in.

That is why the docs call it safe SQL generation rather than a complete migration framework.

Local verification

The repo verifies the SQL runtime locally against SQLite, PostgreSQL, and MySQL.

Run it with:

terminal
pnpm test:local:sql

Helpful references