Show HN: Kysera – a data layer for Kysely with RLS compiled into the SQL

old8man1 pts0 comments

The type-safe data layer for Kysely | Kysera

Skip to main content<br>Not an ORM. A data-access toolkit.<br>SQL you can still see<br>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.<br>One plugin core, two patterns<br>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).<br>Hardened by default<br>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.<br>Structured CRUD where you want conventions, composable functions where you want reach — against the same executor, the same plugins, the same transaction.Repository<br>users.repository.ts<br>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<br>users.queries.ts<br>import {

createQuery, createContext, withTransaction,

type DbContext

} from '@kysera/dal'

const userByEmail = createQuery(

(ctx: DbContextDB>, 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

Writes through repositories, complex reads through the DAL — the CQRS-lite guide shows when to reach for which.

Four plugins. No magic.<br>Each plugin declares a priority tier and runs in a fixed, inspectable order — security → filters → transforms → audit — no matter how you register them.Soft Delete~4 KB<br>Filters reads and narrows UPDATE/DELETE to live rows. Per-statement opt-out, no global bypass.<br>Timestamps~5 KB<br>created_at / updated_at on repository writes — bulk methods included, explicit values win.<br>Audit~12 KB<br>Row-level history with old/new values, committed atomically with the mutation. Restore included.<br>Row-Level Security~57 KB<br>Declarative policies enforced in SQL for reads, mutations, and bulk operations — plus native PostgreSQL RLS generation.

Batteries included — separately.<br>Thirteen focused packages. Install what you use; tree-shake the rest.Migrations<br>Advisory-locked runs on PostgreSQL, MySQL, and MSSQL; sha256 drift detection, dry-run plans, baselining.<br>CLI<br>kysera doctor, live-DB type codegen, migrate verify/baseline, RLS DDL generation, shell completions.<br>Production infra<br>Health checks, retry with backoff, whole-transaction retry on deadlocks, circuit breaker, graceful shutdown.<br>Debugging<br>Query logging, slow-query alerts, profiler with percentile metrics — wraps any Kysely instance.<br>Testing<br>Transaction-rollback isolation, data factories, plugin mocks and spies, live-database auto-detection.<br>Dialects<br>Adapters for PostgreSQL, MySQL, SQLite, and MSSQL: unified error matching, URL and schema utilities.

13<br>Focused packages

Plugins

Third-party runtime deps

Runtimes: Node, Bun, Deno

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<br>Add the packages to an existing project, or let the CLI scaffold one — config, migrations, and a health check included.Install<br>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.<br>Or scaffold a project<br>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<br>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 KyselyDatabase>({

dialect: new PostgresDialect({ pool })

})

const executor = await createExecutor(db, [softDeletePlugin()])

const orm = await createORM(executor, [])

const users =...

kysera executor kysely users const await

Related Articles