kysforge
v0.3.5
Published
Codegen for Kysely + PostgreSQL/SQLite projects.
Maintainers
Readme
kysforge
Database introspection + Kysely type generation for PostgreSQL and SQLite.
A small, programmatic codegen library. No CLI, no env-var magic, you import a function, pass a config object, and get a fully-typed Database interface written to disk.
Features
- PostgreSQL codegen: tables, views, enums, generated columns, configurable overrides
- SQLite codegen: tables, views, generated (STORED/VIRTUAL) columns, configurable overrides
- Programmatic API: no CLI, no env vars; call from your own script or build step
- Kysely-native output: emits
ColumnType,Generated,Selectable,Insertable,Updateable
Requirements
- Node
>=22, SQLite codegen uses the built-innode:sqlitemodule:- On Node 22–23, run your script with
--experimental-sqlite - On Node 24+,
node:sqliteis stable and no flag is needed
- On Node 22–23, run your script with
kysely >= 0.27as a peer dep- For PostgreSQL: one of the following drivers as a peer dep (install only the one you use):
pg >=8.00.0, orpostgres >=3.4.9(postgres.js) together withkysely-postgres-js >=3.0.0
Installation
npm install kysforge
# peer deps
npm install kysely
# PostgreSQL — pick ONE driver:
npm install pg # node-postgres, or
npm install postgres kysely-postgres-js # postgres.jsPostgreSQL
import { pgCodegen } from "kysforge/pg";
const result = await pgCodegen({
connection: {
host: "localhost",
port: 5432, // optional, defaults to 5432
database: "my_db",
user: "dev",
password: "password",
},
// Optional: which driver to use ("pg" or "postgres"). When omitted, the
// driver is auto-detected at runtime ("pg" preferred, then "postgres").
driver: "pg",
// Optional: schemas to introspect. Defaults to ["public"].
schemas: ["public", "auth"],
output: {
database: "./src/database.generated.ts",
// Optional: write enums to a separate file.
enums: "./src/enums.generated.ts",
// Optional: module specifier used to import enums inside the database file.
// If omitted, a relative path is derived automatically.
enumsImportPath: "@my-app/shared",
},
// Optional: include views as read-only types in the Database interface.
views: true,
// Optional: override the inferred TS type for specific columns.
overrides: {
columns: {
"books.type": '"book"',
"movies.type": '"movie"',
},
},
});
// `result` is a PgCodegenResult, log it, print warnings, exit non-zero
// on unknown types, etc. kysforge itself does not log or call process.exit.
console.info(
`Generated ${result.tables} table(s), ${result.enums} enum(s), ${result.views} view(s)`,
);
for (const w of result.warnings) console.warn(w);The function returns a PgCodegenResult with the counts, the resolved databasePath / enumsPath, the schemas that were introspected, and a warnings array (e.g. unknown data types). On failure it throws, wrap the call in try/catch if you need custom error handling.
How you load the connection settings is up to you, read a .env file in your own script, pull from a secret manager, hard-code in dev, etc. kysforge itself does not touch process.env.
Choosing a driver
PostgreSQL codegen works with either pg (node-postgres) or postgres (postgres.js). These are optional peer dependencies — install only the one you use, and kysforge imports it lazily at runtime.
- Leave
driverunset to auto-detect the installed package (pgis preferred, thenpostgres). - Set
driver: "pg"ordriver: "postgres"explicitly to disambiguate when both packages are installed.
Using postgres.js also requires the kysely-postgres-js dialect package. If the configured (or detected) driver isn't installed, pgCodegen throws with an install hint.
Generated output (example)
import type {
ColumnType,
Generated,
Insertable,
Selectable,
Updateable,
} from "kysely";
export interface UsersTable {
id: Generated<string>;
email: string;
created_at: ColumnType<Date, string | Date | undefined, never>;
updated_at: ColumnType<Date, string | Date | undefined, string | Date>;
}
export type User = Selectable<UsersTable>;
export type NewUser = Insertable<UsersTable>;
export type UserUpdate = Updateable<UsersTable>;
// Views are read-only: Selectable only, no Insertable / Updateable.
export interface ActiveUsersView {
id: string | null;
email: string | null;
}
export type ActiveUser = Selectable<ActiveUsersView>;
export interface Database {
users: UsersTable;
active_users: ActiveUsersView;
}SQLite
Uses Node's built-in node:sqlite, no native dependency required.
Node 22–23: run your script with
node --experimental-sqlite .... Node 24+: no flag needed.
import { sqliteCodegen } from "kysforge/sqlite";
const result = await sqliteCodegen({
databasePath: "./data/app.db",
output: {
database: "./src/database.generated.ts",
},
// Optional: include views as read-only types in the Database interface.
views: true,
// Optional: override the inferred TS type for specific columns.
overrides: {
columns: {
"users.metadata": "Record<string, unknown>",
},
},
});
// `result` is a SqliteCodegenResult with `tables`, `views`, `databasePath`,
// and a `warnings` array. Errors are thrown rather than logged.
console.info(
`Generated ${result.tables} table(s) and ${result.views} view(s)`,
);
for (const w of result.warnings) console.warn(w);Type mapping notes
SQLite stores values under five storage classes (INTEGER, REAL, TEXT, BLOB, NULL) but accepts arbitrary declared type names. kysforge maps only declared types that correspond to a real storage class:
INTEGER,INT,BIGINT,INT2, … →numberREAL,DOUBLE,FLOAT,NUMERIC,DECIMAL→numberTEXT,VARCHAR(n),CHAR,CLOB→stringBLOB→Buffer
Anything else (e.g. BOOLEAN, DATETIME, JSONB, custom names) is recorded in the result's warnings array and resolves to unknown. Use the overrides map to plug in the type you actually want for those columns.
INTEGER PRIMARY KEY columns are detected as auto-assigned and emitted as Generated<number>. STORED and VIRTUAL generated columns are emitted as ColumnType<T, never, never>.
