前に紹介した TypeScript でスキーマ定義できるマイグレーションツール kyrage の v1.0.0 をリリースした。
前回の時点ではテーブルのマイグレーションしか対応していなかったが、v1.0.0 ではもうちょい実用的なツールに仕上がったと思う。
以下、追加された主要な機能と変更点。
インデックスと制約のサポート
前回「今後やりたいこと」に挙げていた、index や外部キー制約がついに対応できた。
インデクスや制約を使うスキーマ定義は、defineConfig の第二引数で以下のように書ける:
import { column as c, defineTable as t } from "@izumisy/kyrage"; export const posts = t( "posts", { id: c("uuid"), author_id: c("uuid"), slug: c("text", { notNull: true }), title: c("text"), content: c("text", { notNull: true }), }, (t) => [ // 複合プライマリキー t.primaryKey(["id", "author_id"]), // ユニーク制約(カスタム名付き) t.unique(["author_id", "slug"], { name: "unique_author_slug", }), // 外部キー制約(CASCADE DELETE 付き) t.reference("author_id", members, "id", { onDelete: "cascade", name: "posts_author_fk" }), // インデックス t.index(["slug", "title"], { unique: true }) ] );
Dev Database サポート
Atlas の Dev Database という概念に近いものを実装した。
これは、本番データベースに影響を与えずにマイグレーションを生成するための機能。
# 開発用の一時的なデータベースコンテナを起動してマイグレーションを生成
$ kyrage generate --dev
🚀 Starting dev database for migration generation...
✔ Dev database started: postgres
-- create_table: users
-> column: id ({"type":"uuid","primaryKey":true,"notNull":true})
-> column: email ({"type":"text","notNull":true,"unique":true})
✔ Migration file generated: migrations/1755525514175.json
✔ Dev database stopped
内部的には以下のような処理が行われる:
- 新しい Docker コンテナでクリーンなデータベースを起動
- 既存のマイグレーションをすべて適用してベースラインを確立
- スキーマ定義とコンテナ上の状態を比較
- マイグレーションファイルを生成
- コンテナを自動的にクリーンアップ
設定ファイルでは以下のように定義できる:
export default defineConfig({ database: { dialect: "postgres", connectionString: "psql://prod:pass@prod-db.com/myapp", }, // 開発用データベース設定 dev: { // Docker コンテナを使う場合 container: { image: "postgres:17" }, // または既存のデータベースを使う場合 // connectionString: "psql://dev:pass@dev-db.com/myapp_dev" }, tables: [/* ... */], });
もともと、自前で Docker コンテナを立ち上げて connectionString を設定すれば同じようなことはできたのだが、コンテナの起動し忘れとか、既存マイグレーションのベースライン適用し忘れとか、そういう細かい手間がいちいち発生するのがダルい。作っている間も動作確認でよくあった。
Dev Database ならこれらが全部自動化される。複数の開発者が異なるスキーマ変更を並行して進めている場合でも、各自がクリーンな状態からマイグレーションを生成できる。本番DBの現在の状態に依存しないので、「誰かが先にマイグレーションを適用したから差分がおかしくなった」みたいな問題が起きないだろう。全員が同じ Docker イメージから始めるので、環境差異によるトラブルも起きにくい。
また、開発者に本番データベースへの接続情報を共有する必要がなくなる。これは地味だが重要で、本番環境へのアクセス権限を最小限に保てる。開発者は Dev Database だけで作業を完結できるので、誤って本番データを触ってしまうリスクもない。
ちなみに、内部的には Testcontainers を使って実装している。Testconatinersは超便利で、Github Actionsで動かす際も明示的にサービスの定義など不要で全部いい感じにしてくれるので素晴らしい。
環境別設定との組み合わせ
余談だが kyrage は設定の読み込みに unjs/c12 を採用しているので、NODE_ENV による設定の切り替えも可能。
例えば以下のような設定ができる:
export default defineConfig({ // 開発環境: 各自のローカル Docker を使用 $development: { database: { dialect: "postgres", connectionString: "postgres://dev:dev@localhost:5432/myapp_dev" }, dev: { container: { image: "postgres:17" } } }, // ステージング環境: 共有の Dev Database を使用 $staging: { database: { dialect: "postgres", connectionString: "postgres://staging:pass@staging-db.internal/myapp_staging" }, dev: { connectionString: "postgres://devdb:pass@shared-dev-db.internal/myapp_dev" } }, // 本番環境: Dev Database なしで直接本番に接続(CI/CD 用) $production: { database: { dialect: "postgres", connectionString: process.env.DATABASE_URL! } }, tables: [/* ... */] });
これにより、チーム内での役割や環境に応じた柔軟な運用が可能になるだろう。
また、まだ構想段階だが、Dev Database コンテナを永続化する Reuse API という機能の追加も検討している。
現在は --dev でマイグレーション生成のたびにコンテナが作り直されるが、Reuse API を使えばコンテナを使い回せるようになる。これにより、テストデータを保持したまま開発を進められたり、アプリケーションから同じ Dev Database に接続できたりと、より実践的な開発フローが実現できるはずだ。kyrage dev get-url でコネクション URL を取得して DATABASE_URL=$(kyrage dev get-url) npm run dev のように環境変数に設定する、といった使い方も想定している。
なお、作者である自分はKyselyと組み合わせたくてこのツールを作ったということもあり、スキーマ定義を更新するごとに kyrage generate --dev --apply && DATABASE_URL="$(kysely dev get-url)" kysely-codegen を実行する、みたいなプロセスを妄想している。
まとめ
v1.0.0 では、前回の記事で「今後やりたいこと」として挙げていた主要な機能のうち、特に重要な index と外部キー制約の対応が完了した。さらに Dev Database サポートによって、チーム開発やCI/CDでの利用がかなり実用的になったと思われる。
まだ PostgreSQL 互換以外の DB 対応や down マイグレーションなど、やりたいことは残っているが、ひとまず v1.0.0 として区切りをつけることにした。
興味がある方はぜひ試してみてください。フィードバックや PR も歓迎です。