Kysely
Kysely integration in this repo is runtime-first.
Use @farming-labs/orm-kysely when:
- the app already owns a real
Kyselyinstance - a shared package wants to keep writing the unified Farming ORM API
- you want SQLite, MySQL, or PostgreSQL without rewriting the package storage layer
Supported Kysely dialects
postgresmysqlsqlite
Runtime setup
import { createOrm } from "@farming-labs/orm";
import { createKyselyDriver } from "@farming-labs/orm-kysely";
import { Kysely, PostgresDialect } from "kysely";
import { Pool } from "pg";
import { authSchema } from "./schema";
const kysely = new Kysely({
dialect: new PostgresDialect({
pool: new Pool({
connectionString: process.env.DATABASE_URL,
}),
}),
});
const orm = createOrm({
schema: authSchema,
driver: createKyselyDriver({
db: kysely,
dialect: "postgres",
}),
});From there, library code keeps using the unified API:
const user = await orm.user.findUnique({
where: {
email: "ada@farminglabs.dev",
},
select: {
id: true,
email: true,
profile: {
select: {
bio: true,
},
},
sessions: {
orderBy: {
token: "asc",
},
select: {
token: true,
},
},
},
});What the Kysely driver is doing
The Kysely runtime package does not invent a second ORM API.
Instead it:
- receives the unified Farming ORM query
- executes through the app’s real
Kyselydatabase - reuses the shared SQL runtime behavior for relation loading, filtering, sorting, paging, and mutation semantics
That shared SQL runtime now includes a native relation-loading path, so queries
like session.user, session.user.profile, user.sessions, and simple
explicit join-table manyToMany(...) traversal can load through one SQL
statement. Relation branches with relation-level filters, paging, or ordering
still fall back to the shared resolver.
That does not mean Kysely or the underlying database is missing join support. It only means the shared SQL runtime currently translates the direct singular subset natively and leaves the more complex branches on the fallback path for now.
That keeps the runtime behavior aligned with:
@farming-labs/orm-sql@farming-labs/orm-drizzle@farming-labs/orm-prisma
Relation support
The Kysely runtime covers:
belongsTo(...)hasOne(...)hasMany(...)- explicit join-table
manyToMany(...)
That means auth-style lookups such as:
await orm.user.findUnique({
where: { email: "ada@farminglabs.dev" },
select: {
id: true,
profile: {
select: { bio: true },
},
sessions: {
select: { token: true },
},
organizations: {
select: { name: true },
},
},
});work the same way they do in the other live runtimes.
Mutations and transactions
The Kysely runtime also supports:
createcreateManyupdateupdateManyupsertdeletedeleteManytransaction
Example:
await orm.transaction(async (tx) => {
const user = await tx.user.create({
data: {
email: "ada@farminglabs.dev",
name: "Ada",
},
select: {
id: true,
},
});
await tx.session.upsert({
where: {
token: "session-token",
},
create: {
userId: user.id,
token: "session-token",
expiresAt: new Date("2027-01-01T00:00:00.000Z"),
},
update: {
expiresAt: new Date("2027-01-01T00:00:00.000Z"),
},
});
});The same live Kysely matrix also verifies:
integer()fields with numeric comparison filtersjson()fields with raw JSON equality filters- compound-unique lookups and upserts
- JSON mutation round-trips
Local verification
The repo verifies Kysely locally against SQLite, PostgreSQL, and MySQL.
Run it with:
pnpm test:local:kyselyHelpful references
How it fits into a Kysely app
Typical flow:
- the library defines a reusable schema in
@farming-labs/orm - the app creates its own Kysely instance normally
- the app passes that instance to
createKyselyDriver(...) - the shared package keeps one storage/query layer for every supported stack
That is the main value: Kysely apps can participate in the same package-level storage contract as Prisma, Drizzle, SQL, and Mongo-backed apps.
How is this guide?