個人開発で CockroachDB を使いたかったのだが、有名どころの drizzle や Prisma でマイグレーションしようとすると、うまくいかなかった。
drizzle-orm はそもそも CockroachDB サポートが計画段階なのだが、PostgreSQL 互換だし動くだろうと思って pg ドライバで動かすとマイグレーションでデータ型関連のエラーが発生してしまう。Prisma も原因は別だが CockroachDB 特有の機能でエラーが起きる。
では、なにかNode.js系のマイグレーションツールを適当に採用しようか... という所だが、よくあるやつはマイグレーションのSQLやコードそのものを基本的に手書きする。これがやりたくない。理想的には Ruby 界隈の ridgepole のように宣言的なスタイルでマイグレーションができ、かつ Node.js エコシステムと親和性の高いミニマルなツールが欲しくなる。
Node.js とは関係ないが、似たようなもので Atlas という宣言的なアプローチを採用したデータベースマイグレーション管理ツールがあるのだが、これは HCL でスキーマを書く必要がある。道具としては良いが、個人的な好みとして別に HCL を書きたくないし、補完のために VSCode でも専用の拡張を入れる必要があり微妙だった。あとなんかクラウド版がどうだとか、ツール単体として使うには不要な概念が多くて、ドキュメントがわかりづらかった気がする。
結局、自分の場合は基本的に Next.js + TypeScript という環境でアプリを作ることが多いのでそれに合わせて全部 TS のコードベースに統一したく、そういうやつをバイブコーディングするかーと思って作ることにした。
作ったもの
kyrage (kirāju) という名前のマイグレーションツールを作った。今はテーブルのマイグレーションのみ対応。
やっていることは以下の通り:
この手法は前述する Atlas が Versioned Migration Authoring と呼んでいるスタイルとのこと。
なお、マイグレーションにおけるSQLの発行だとかバージョン管理などの仕組みは自前では作っていなくて、kyselyのマイグレーション機能をライブラリとして使っている。もちろんCockroachDBのdialectも用意した。
基本的な使い方
グローバルインストールしてもいいが、ここから先はnpxで使う例とする。
1. 設定
kyrage.config.ts を作る
import { defineConfig } from "@izumisy/kyrage"; export default defineConfig({ database: { dialect: "postgres", connectionString: "postgres://postgres:password@localhost:5432/mydb", }, });
ここで書いたDBに対してイントロスペクションを実行し、スキーマを取得してくる。
2. スキーマ定義
ここでは schema.ts に以下を書く
import { column as c, defineTable as t } from "@izumisy/kyrage"; export const members = t("members", { id: c("uuid", { primaryKey: true }), email: c("text", { unique: true }), name: c("text", { unique: true }), age: c("integer", { nullable: true }), createdAt: c("timestamptz"), }); export const posts = t("posts", { id: c("uuid", { primaryKey: true }), author_id: c("uuid"), title: c("text"), content: c("text"), published: c("boolean", { default: false }), published_at: c("timestamptz", { nullable: true }), });
データ型は kysely から拝借しているので、スキーマ定義ではいい感じに補完が効く。
書けたら設定に追加していく
import { defineConfig } from "@izumisy/kyrage";
+import { members, posts } from "./schema";
export default defineConfig({
database: {
dialect: "postgres",
connectionString: "postgres://postgres:password@localhost:5432/mydb",
},
+ tables: [members, posts],
});
3. マイグレーション生成
generate コマンドで、テーブルやカラムの作成、削除、変更などの差分を記録した JSON ファイルが作られる
$ npx @izumisy/kyrage generate -- create_table: members (id, email, name, age, createdAt) -- create_table: posts (id, author_id, title, content, published, published_at) ✔ Migration file generated: migrations/1754372124127.json
4. 適用
--plan オプションがあるので、事前にどういうSQLが発行されるのか確認しておく。
$ npx @izumisy/kyrage apply --plan
create table "members" ("id" uuid not null primary key, "email" text not null unique, "name" text not null unique, "age" integer, "createdAt" timestamptz not null)
create table "posts" ("id" uuid not null primary key, "author_id" uuid not null, "title" text not null, "content" text not null, "published" boolean not null, "published_at" timestamptz)
今回は空のデータベースにapplyするので、テーブル作成のクエリしかでていない。
問題なさそうであれば、マイグレーションを実行する。
$ npx @izumisy/kyrage apply ✔ Migration applied: 1754372124127
手でマイグレーションファイルを書く必要がないので便利。
今後やりたいこと
- もうちょっとまともな diff の生成。
- 現状は up/down のうち up しか生成できていないので、reversible な変更であれば down も対応したい
- indexや外部キー制約などの対応
- かなり重要だけど、まだできてないのでやる
- PostgreSQL互換以外のDB対応
- どっかで気合いいれてやる
その他やりたいことはGithub issuesに色々あります