Not an ORM. A data-access toolkit.
SQL you can still see
Kysera is a thin layer over Kysely, not a replacement for it. Every query is still a Kysely query; every escape hatch stays open — drop to the raw instance or a sql template whenever you need to.
One plugin core, two patterns
Plugins intercept queries at the executor, so the Repository pattern and the functional DAL share the same soft-delete filters and RLS policies — in and out of transactions. Repositories add an atomic audit trail on top. Mix both styles in one codebase (CQRS-lite).
Hardened by default
Row-level security enforces SELECT, UPDATE, and DELETE in SQL. Soft delete narrows mutations to live rows. Opt-outs are scoped per statement — there is no global bypass switch to forget about.
Pick a pattern. Or both.
Structured CRUD where you want conventions, composable functions where you want reach — against the same executor, the same plugins, the same transaction.
Repository
import {
createORM, createRepositoryFactory, zodAdapter
} from '@kysera/repository'
const orm = await createORM(executor, [])
const users = orm.createRepository(exec =>
createRepositoryFactory(exec).create({
tableName: 'users',
mapRow: row => row,
schemas: { create: zodAdapter(CreateUser) },
})
)
const user = await users.create({
email: 'ada@example.com',
name: 'Ada',
})
await users.softDelete(user.id) // added by the plugin
await users.findAll() // deleted rows filtered
Functional DAL
import {
createQuery, createContext, withTransaction,
type DbContext
} from '@kysera/dal'
const userByEmail = createQuery(
(ctx: DbContext<DB>, email: string) =>
ctx.db
.selectFrom('users')
.selectAll()
.where('email', '=', email)
.executeTakeFirst()
)
const ctx = createContext(executor)
await userByEmail(ctx, 'ada@example.com')
// soft-delete filter applied automatically
await withTransaction(executor, async tx => {
await userByEmail(tx, 'ada@example.com')
}) // plugins survive; nested calls → savepoints
Four plugins. No magic.
Each plugin declares a priority tier and runs in a fixed, inspectable order — security → filters → transforms → audit — no matter how you register them.
Filters reads and narrows UPDATE/DELETE to live rows. Per-statement opt-out, no global bypass.
created_at / updated_at on repository writes — bulk methods included, explicit values win.
Row-level history with old/new values, committed atomically with the mutation. Restore included.
Declarative policies enforced in SQL for reads, mutations, and bulk operations — plus native PostgreSQL RLS generation.
Batteries included — separately.
Thirteen focused packages. Install what you use; tree-shake the rest.
Migrations
Advisory-locked runs on PostgreSQL, MySQL, and MSSQL; sha256 drift detection, dry-run plans, baselining.
CLI
kysera doctor, live-DB type codegen, migrate verify/baseline, RLS DDL generation, shell completions.
Production infra
Health checks, retry with backoff, whole-transaction retry on deadlocks, circuit breaker, graceful shutdown.
Debugging
Query logging, slow-query alerts, profiler with percentile metrics — wraps any Kysely instance.
Testing
Transaction-rollback isolation, data factories, plugin mocks and spies, live-database auto-detection.
Dialects
Adapters for PostgreSQL, MySQL, SQLite, and MSSQL: unified error matching, URL and schema utilities.
Tested in CI against live PostgreSQL and MySQL on every push; concurrency claims are proven by racing tests. A benchmark suite tracks overhead each release: the executor without plugins measures within noise-to-13% of raw Kysely on the execute path, and a full three-plugin stack costs ~15–20%.
Up and running in a minute
Add the packages to an existing project, or let the CLI scaffold one — config, migrations, and a health check included.
Install
npm install kysely zod
npm install @kysera/executor @kysera/repository @kysera/soft-delete
ESM-only, Node 22+. Zod is optional — bring Valibot or TypeBox instead, or skip validation entirely.
Or scaffold a project
npx @kysera/cli init my-app
npx @kysera/cli doctor
init sets up config and migrations; doctor verifies the whole environment in one shot.
Use
import { Kysely, PostgresDialect } from 'kysely'
import { createExecutor } from '@kysera/executor'
import {
createORM, createRepositoryFactory, zodAdapter
} from '@kysera/repository'
import { softDeletePlugin } from '@kysera/soft-delete'
import { z } from 'zod'
const db = new Kysely<Database>({
dialect: new PostgresDialect({ pool })
})
const executor = await createExecutor(db, [softDeletePlugin()])
const orm = await createORM(executor, [])
const users = orm.createRepository(exec =>
createRepositoryFactory(exec).create({
tableName: 'users',
mapRow: row => row,
schemas: {
create: zodAdapter(
z.object({ email: z.string().email(), name: z.string() })
)
},
})
)
const user = await users.create({ email: 'ada@example.com', name: 'Ada' })
await users.softDelete(user.id)
await users.findAll() // soft-deleted rows are filtered out