Type-safe data access toolkit for TypeScript, built on Kysely
Repository pattern • Functional DAL • Plugin ecosystem • Not an ORM
Documentation • Getting Started • API Reference • Examples
Kysera extends Kysely with production-ready patterns — without hiding the query builder or inventing a new query language. You get repositories, plugins, security, and infrastructure while keeping full control over your SQL.
- Zero magic — explicit, traceable, debuggable
- Zero runtime deps in core packages
- Full type safety — strict TypeScript, no
any - Plugin architecture — compose behavior across Repository and DAL patterns
- Cross-runtime — Node.js, Bun, Deno
- 4,600+ tests at 95%+ coverage
npm install kysely @kysera/core @kysera/executor @kysera/repositoryimport { Kysely, PostgresDialect } from 'kysely'
import { Pool } from 'pg'
import { createExecutor } from '@kysera/executor'
import { createORM } from '@kysera/repository'
import { softDeletePlugin } from '@kysera/soft-delete'
import { timestampsPlugin } from '@kysera/timestamps'
// Define your schema
interface Database {
users: {
id: Generated<number>
email: string
name: string
created_at: Generated<Date>
updated_at: Generated<Date>
deleted_at: Date | null
}
}
// Connect
const db = new Kysely<Database>({
dialect: new PostgresDialect({ pool: new Pool({ connectionString: process.env.DATABASE_URL }) })
})
// Create executor with plugins — they apply automatically to all queries
const executor = await createExecutor(db, [
softDeletePlugin(),
timestampsPlugin()
])
// Create ORM and repository
const orm = await createORM(executor, [])
const userRepo = orm.createRepository({
tableName: 'users',
schemas: { entity: UserSchema, create: CreateUserSchema }
})
// Query — plugins apply transparently
const users = await userRepo.findAll() // filters deleted, includes timestamps
await userRepo.create({ email: 'a@b.com', name: 'Alice' }) // auto-sets created_at/updated_at
await userRepo.softDelete(1) // sets deleted_at instead of DELETE| Package | Description |
|---|---|
@kysera/core |
Errors, pagination, types, logger |
@kysera/executor |
Unified Execution Layer — plugin-aware Kysely wrapper (zero deps) |
@kysera/repository |
Repository pattern with Zod validation and MongoDB-style queries |
@kysera/dal |
Functional Data Access Layer — query functions, context, composition |
@kysera/dialects |
Dialect-specific utilities and error parsing |
| Package | Description |
|---|---|
@kysera/soft-delete |
Soft delete with automatic query filtering |
@kysera/timestamps |
Auto created_at / updated_at management |
@kysera/audit |
Audit logging with restore support |
@kysera/rls |
Row-Level Security — declarative policies, native PostgreSQL RLS |
| Package | Description |
|---|---|
@kysera/infra |
Health checks, retry, circuit breaker, graceful shutdown |
@kysera/debug |
Query logging, profiling, SQL formatting |
@kysera/testing |
Transaction isolation, factories, seeding |
@kysera/migrations |
Migration system with dry-run and rollback |
| Package | Description |
|---|---|
@kysera/cli |
Migrations, codegen, health monitoring, schema management |
The executor is the foundation — plugins defined once work across Repository and DAL patterns:
import { createExecutor } from '@kysera/executor'
import { softDeletePlugin } from '@kysera/soft-delete'
import { rlsPlugin } from '@kysera/rls'
const executor = await createExecutor(db, [
softDeletePlugin(),
rlsPlugin({ schema: rlsSchema })
])
// Direct query — plugins apply automatically
const users = await executor.selectFrom('users').selectAll().execute()Repository find() supports expressive filtering with type-safe operators:
const users = await userRepo.find({
where: {
age: { $gte: 18, $lte: 65 },
status: { $in: ['active', 'pending'] },
email: { $contains: '@company.com' },
name: { $startsWith: 'A' },
score: { $between: [80, 100] }
},
sort: [{ column: 'created_at', direction: 'desc' }],
limit: 20
})Operators: $eq $ne $gt $gte $lt $lte $in $nin $like $ilike $contains $startsWith $endsWith $isNull $isNotNull $between $or $and
For complex reads, use composable query functions:
import { createQuery, createContext, withTransaction, compose, parallel } from '@kysera/dal'
const getUserById = createQuery((ctx, id: number) =>
ctx.db.selectFrom('users').where('id', '=', id).executeTakeFirst()
)
const getUserPosts = createQuery((ctx, userId: number) =>
ctx.db.selectFrom('posts').where('user_id', '=', userId).execute()
)
// Compose and execute
const ctx = createContext(executor)
const user = await getUserById(ctx, 1)
// Parallel queries
const [user, posts] = await parallel(ctx, getUserById, getUserPosts)(ctx, 1, 1)
// Transactions preserve plugins
await withTransaction(ctx, async (txCtx) => {
const user = await getUserById(txCtx, 1) // plugins still active
})Declarative policies that transform queries at the executor level:
import { createRLSSchema, allow, filter } from '@kysera/rls'
const rlsSchema = createRLSSchema({
users: {
policies: [
allow('select').to('admin'),
filter('select').to('user').where((ctx) => ({
column: 'tenant_id',
op: '=',
value: ctx.tenantId
}))
]
}
})
const executor = await createExecutor(db, [
rlsPlugin({ schema: rlsSchema })
])Structured, dialect-aware error parsing:
import { parseDatabaseError, UniqueConstraintError, ForeignKeyError } from '@kysera/core'
try {
await userRepo.create(data)
} catch (error) {
const dbError = parseDatabaseError(error, 'postgres')
if (dbError instanceof UniqueConstraintError) {
console.log(dbError.constraint, dbError.columns)
}
}Offset and cursor-based pagination out of the box:
import { paginate, paginateCursor } from '@kysera/core'
// Offset
const page = await paginate(query, { page: 1, limit: 20 })
// → { data, total, page, limit, totalPages, hasNext, hasPrev }
// Cursor (for infinite scroll / real-time feeds)
const feed = await paginateCursor(query, {
orderBy: [{ column: 'created_at', direction: 'desc' }],
limit: 20,
after: lastCursor
})
// → { data, cursor, hasMore }Production-ready resilience and observability:
import { createHealthCheck } from '@kysera/infra/health'
import { withRetry, createCircuitBreaker } from '@kysera/infra/resilience'
import { gracefulShutdown } from '@kysera/infra/shutdown'
// Health checks
const health = createHealthCheck(db, { interval: 30_000 })
// Retry with exponential backoff
const result = await withRetry(() => fetchData(), {
maxRetries: 3,
backoff: 'exponential'
})
// Circuit breaker
const breaker = createCircuitBreaker({ threshold: 5, timeout: 30_000 })
// Graceful shutdown
gracefulShutdown({ db, onShutdown: () => console.log('bye') })npm install -g @kysera/cli
# Initialize project
kysera init
# Migrations
kysera migrate create add-users-table
kysera migrate up
kysera migrate status
kysera migrate rollback --steps 1
# Code generation
kysera generate model User
kysera generate repository User --with-tests
kysera generate crud User --with-api
# Database tools
kysera db seed
kysera health check
kysera debug explain "SELECT * FROM users"@kysera/executor (0 deps) ← Foundation
│
├── @kysera/dal ──────── Functional queries
│
└── @kysera/repository ─ Repository pattern
│
├── @kysera/soft-delete
├── @kysera/timestamps
├── @kysera/audit
└── @kysera/rls
@kysera/core (0 deps) ─── Shared errors, pagination, types
@kysera/dialects ───────── PostgreSQL, MySQL, SQLite, MSSQL
@kysera/infra ──────────── Health, retry, circuit breaker
@kysera/debug ──────────── Logging, profiling
@kysera/testing ────────── Factories, seeding, isolation
@kysera/migrations ─────── Schema versioning
Working examples in examples/:
- blog-app — Posts, comments, tags with soft-delete and audit
- e-commerce — Products, orders, payments with transactions
- multi-tenant-saas — Tenant isolation with RLS and schema separation
pnpm install # Install dependencies
pnpm build # Build all packages
pnpm test # Run tests (4,600+)
pnpm test:coverage # With coverage report
pnpm test:multi-db # PostgreSQL + MySQL + SQLite
pnpm dev # Watch mode
pnpm typecheck # Type checking
pnpm lint # ESLintRequirements: Node.js >=20, pnpm >=10, TypeScript ^5.9
Full docs at kysera.dev
