kysera generate
Generate type-safe code from database schema (alias: kysera g). Two workflows are covered: generate database keeps a single Kysely schema file in sync with the live database (the codegen most projects want first), while model/repository/schema/crud scaffold per-table application code you then own and edit.
Commands
| Command | Description |
|---|---|
database (alias db-types) | Generate one Kysely schema file (table interfaces + Database) |
model [table] | Generate TypeScript model from a table |
repository [table] | Generate repository class from a table |
schema [table] | Generate Zod schemas from a table |
crud <table> | Generate the complete stack (model + schema + repository) |
database
Generate a single Kysely schema file — one interface per table plus the aggregated Database interface — by introspecting the connected database. Columns with defaults or auto-increment are wrapped in Generated<>, so inserts and selects type correctly.
kysera generate database
kysera g db-types # same command via aliases
Options:
-o, --output <path> Output file (default: ./src/db/schema.ts)
-c, --config <path> Path to configuration file
-s, --schema <name> PostgreSQL schema name (default: public)
--include <patterns> Comma-separated table globs to include
(also re-includes internal tables)
--exclude <patterns> Comma-separated table globs to exclude (wins over include)
--with-helpers Emit Selectable/Insertable/Updateable aliases per table
--watch Keep running: poll the schema and regenerate on change
--poll-interval <seconds> Schema poll interval used by --watch (default: 2)
--json Output a {file, tables, written} summary as JSON
Generated file:
// src/db/schema.ts
/**
* Kysely database schema types.
*
* Generated by `kysera generate database -o ./src/db/schema.ts`.
* Do not edit this file manually — rerun the command to refresh it.
*
* Dialect: postgres
*/
import type { Generated } from 'kysely'
export interface PostsTable {
id: Generated<number>
user_id: number
title: string
created_at: Generated<Date>
}
export interface UsersTable {
id: Generated<number>
email: string
name: string
}
export interface Database {
posts: PostsTable
users: UsersTable
}
With --with-helpers, each table also gets Selectable/Insertable/Updateable aliases (export type Posts = Selectable<PostsTable>, NewPosts, PostsUpdate).
Table selection: internal bookkeeping tables (the migrations tracking table, SQLite internals) are excluded by default. --include narrows generation to matching tables (and can deliberately re-include internal ones); --exclude always wins. Globs support * and ?:
kysera generate database --exclude "audit_*,sessions"
kysera generate database --include "tenant_*"
Deterministic and idempotent: tables are sorted by name and no timestamps are embedded, so regenerated files diff cleanly — and the file is only rewritten when content actually changed ("written": false in the JSON summary otherwise). That makes it safe in a lint/CI step:
# Fail CI when the checked-in schema file is stale
kysera generate database --json | jq -e '.written == false'
Watch mode polls the schema and regenerates whenever it changes — pair it with kysera migrate up during development:
kysera generate database --watch --poll-interval 5
# Watching schema (poll every 5s) — press Ctrl+C to stop
In watch mode with --json, each regeneration emits one compact JSON line so the stream stays machine-parseable. Transient introspection failures (connection loss, mid-migration states) are reported and retried on the next poll.
model
Generate model interfaces.
kysera generate model [table]
kysera g model [table]
The [table] argument is optional — omit it to pick the table interactively.
Options:
-o, --output <path> Output directory (default: ./src/models)
--overwrite Overwrite existing files
--timestamps Include timestamp fields (default: true)
--no-timestamps Exclude timestamp fields
--soft-delete Include soft delete fields
--json Output results as JSON
-c, --config <path> Path to configuration file
-s, --schema <name> PostgreSQL schema name (default: public)
Generated:
// src/models/user.ts
import { Generated } from 'kysely'
export interface User {
id: number
email: string
name: string
createdAt: Date
}
export interface UserTable {
id: Generated<number>
email: string
name: string
created_at: Generated<Date>
}
export type NewUser = Omit<User, 'id' | 'createdAt'>
export type UserUpdate = Partial<NewUser>
repository
Generate repository class.
kysera generate repository [table]
The [table] argument is optional — omit it to pick the table interactively.
Options:
-o, --output <path> Output directory (default: ./src/repositories)
--overwrite Overwrite existing files
--with-validation Include Zod validation (default: true)
--with-pagination Include pagination methods (default: true)
--with-soft-delete Include soft delete support
--with-timestamps Include timestamp support (default: true)
--json Output results as JSON
-c, --config <path> Path to configuration file
-s, --schema <name> PostgreSQL schema name (default: public)
Generated:
The generator emits a class with the standard CRUD surface (findById, findAll, create, update, delete, count, plus pagination when enabled). With --with-soft-delete, reads filter on deleted_at and delete becomes a soft delete:
// src/repositories/user.repository.ts
import { Kysely } from 'kysely'
import type { User, NewUser, UserUpdate, UserTable } from '../models/user.js'
import type { Database } from '../database.js'
import { NewUserSchema, UpdateUserSchema } from '../schemas/user.schema.js'
export class UserRepository {
constructor(private db: Kysely<Database>) {}
async findById(id: number): Promise<User | undefined> {
const result = await this.db
.selectFrom('users')
.selectAll()
.where('id', '=', id)
.executeTakeFirst()
return result as User | undefined
}
async create(data: NewUser): Promise<User> {
const validated = NewUserSchema.parse(data)
const result = await this.db
.insertInto('users')
.values(validated as any)
.returningAll()
.executeTakeFirstOrThrow()
return result as User
}
// findAll, update, delete, count, ...
}
schema
Generate Zod validation schemas.
kysera generate schema [table]
The [table] argument is optional — omit it to pick the table interactively.
Options:
-o, --output <path> Output directory (default: ./src/schemas)
--overwrite Overwrite existing files
--strict Strict validation, no unknown keys (default: true)
--no-strict Allow unknown keys in validation
--json Output results as JSON
-c, --config <path> Path to configuration file
-s, --schema <name> PostgreSQL schema name (default: public)
Generated:
Four schemas per table — entity, New* (insert), Update* (independently defined, not derived from the insert schema), and *FilterSchema — plus validate* and safeParse* helpers:
// src/schemas/user.schema.ts
import { z } from 'zod'
export const UserSchema = z.object({
id: z.number(),
email: z.string().email(),
name: z.string(),
created_at: z.date()
})
export type User = z.infer<typeof UserSchema>
// Schema for creating new records
export const NewUserSchema = z.object({
email: z.string().email(),
name: z.string()
})
export type NewUser = z.infer<typeof NewUserSchema>
// Schema for updating records
export const UpdateUserSchema = z.object({
email: z.string().email().optional(),
name: z.string().optional()
})
export type UpdateUser = z.infer<typeof UpdateUserSchema>
// Schema for filtering/querying records
export const UserFilterSchema = UserSchema.partial()
export type UserFilter = z.infer<typeof UserFilterSchema>
// Validation helpers (throwing and safe variants)
export const validateUser = (data: unknown) => UserSchema.parse(data)
export const validateNewUser = (data: unknown) => NewUserSchema.parse(data)
export const validateUpdateUser = (data: unknown) => UpdateUserSchema.parse(data)
export const safeParseUser = (data: unknown) => UserSchema.safeParse(data)
export const safeParseNewUser = (data: unknown) => NewUserSchema.safeParse(data)
export const safeParseUpdateUser = (data: unknown) => UpdateUserSchema.safeParse(data)
crud
Generate complete CRUD stack.
kysera generate crud <table>
Options:
-o, --output-dir <path> Base output directory (default: ./src)
--overwrite Overwrite existing files
--with-validation Include Zod validation (default: true)
--with-pagination Include pagination (default: true)
--with-soft-delete Include soft delete support
--with-timestamps Include timestamp support (default: true)
--format Format with Prettier (default: true)
--json Output results as JSON
-c, --config <path> Path to configuration file
-s, --schema <name> PostgreSQL schema name (default: public)
Generated Files:
src/
├── models/user.ts
├── schemas/user.schema.ts
├── repositories/user.repository.ts
└── index.ts (exports)
Examples
# Generate model for users table
kysera generate model User
# Generate full CRUD with soft delete
kysera generate crud Post --with-soft-delete
# Generate to custom directory
kysera generate crud Order --output-dir ./src/domain
# Regenerate existing files
kysera generate crud User --overwrite
Type Mapping
| Database Type | TypeScript Type |
|---|---|
| serial, int, bigint | number |
| varchar, text | string |
| boolean, bool | boolean |
| timestamp, datetime | Date |
| json, jsonb | unknown |
| uuid | string |
Best Practices
1. Generate After Schema Changes
kysera migrate up
kysera generate crud User --overwrite
2. Customize Generated Code
Generated code is a starting point. Customize:
- Validation rules
- Row mapping logic
- Additional methods
3. Use Consistent Naming
# Singular table names generate better code
kysera generate crud User # → user.ts
kysera generate crud Post # → post.ts