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