d1-kyt
Opinionated Cloudflare D1 + Kysely toolkit.
ky(sely) + t(oolkit) = kyt
Not an ORM. Thin wrapper with helpers that relies on Kysely's type inference. No magic, no runtime overhead.
Migration DSL
// d1-kyt/migrations/0001_create_user_table.ts import { defineTable, createIndex } from 'd1-kyt/migrate'; const User = defineTable('User', (col) => ({ externalId: col.text().notNull(), email: col.text().notNull(), name: col.text(), })); export const migration = () => [ ...User.sql, createIndex(User, ['externalId'], { unique: true }), createIndex(User, ['email'], { unique: true }), ];
Query Builder
// src/queries.ts import { createQueryBuilder } from 'd1-kyt'; import type { DB } from './db/generated'; const db = createQueryBuilder<DB>(); export const getUsers = () => db.selectFrom('User').selectAll().compile(); export const getUser = (id: number) => db.selectFrom('User').selectAll().where('id', '=', id).compile(); export const insertUser = (email: string, name: string) => db.insertInto('User').values({ email, name }).returning(['id']).compile();
Execute Queries
// src/app.ts import Hono from 'hono'; import { queryAll, queryFirst, queryRun } from 'd1-kyt'; import * as q from './queries'; const app = new Hono(); app.get('/users', async (c) => { const users = await queryAll(c.env.DB, q.getUsers()); return c.json(users); }); app.get('/users/:id', async (c) => { const user = await queryFirst(c.env.DB, q.getUser(c.req.param('id'))); return user ? c.json(user) : c.notFound(); }); app.post('/users', async (c) => { const { email, name } = await c.req.json(); const [user] = await queryAll(c.env.DB, q.insertUser(email, name)); return c.json(user, 201); });
Customizing Auto Columns
// Disable all auto columns const Event = defineTable('Event', (col) => ({ uuid: col.text().notNull(), name: col.text().notNull(), }), { primaryKey: false, createdAt: false, updatedAt: false }); // Custom column names (snake_case) const User = defineTable('user', (col) => ({ email: col.text().notNull(), }), { primaryKeyColumn: 'user_id', createdAtColumn: 'created_at', updatedAtColumn: 'updated_at', });
Later Migrations
Use createUseTable for type-safe references to existing tables:
import type { DB } from '../../db/generated'; import { createUseTable, addColumn, createIndex } from 'd1-kyt/migrate'; const useTable = createUseTable<DB>(); const User = useTable('User'); export const migration = () => [ addColumn(User, 'age', (col) => col.integer()), createIndex(User, ['age']), ];
Install
npm install d1-kyt kysely npm install -D kysely-codegen
CLI
d1-kyt init # creates d1-kyt/ folder with config d1-kyt migrate:create <name> # creates d1-kyt/migrations/0001_<name>.ts d1-kyt migrate:build # compiles *.ts → db/migrations/*.sql d1-kyt typegen # runs kysely-codegen
Reads wrangler.jsonc to detect migrations_dir automatically.
Configuration
// d1-kyt/config.ts import { defineConfig } from 'd1-kyt/config'; export default defineConfig({ migrationsDir: 'db/migrations', dbDir: 'db', namingStrategy: 'sequential', // or 'timestamp' });
Conventions
- Auto
id,createdAt,updatedAton every table (configurable) - Auto trigger for
updatedAt - Index naming:
{table}_{cols}_idx,{table}_{cols}_uq - Trigger naming:
{table}_{col}_trg
API
| Function | Description |
|---|---|
defineTable(name, fn, opts?) |
Define new table |
createUseTable<DB>() |
Factory for typed table refs |
useTable<T>(name) |
Reference table (manual typing) |
createIndex(table, cols, opts?) |
Create index |
addColumn(table, col, fn) |
Add column |
dropTable(table, updatedAtCol?) |
Drop table + trigger |
dropIndex(name) |
Drop index |
queryAll(db, query) |
Execute, return all rows |
queryFirst(db, query) |
Execute, return first row or null |
queryRun(db, query) |
Execute mutation |
queryBatch(db, queries) |
Execute batch |
License
MIT