Skip to main content

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

CommandDescription
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 TypeTypeScript Type
serial, int, bigintnumber
varchar, textstring
boolean, boolboolean
timestamp, datetimeDate
json, jsonbunknown
uuidstring

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