migraguard 0.0.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/COMMANDS.md CHANGED
@@ -70,6 +70,15 @@ Run Squawk lint on migration files to detect idempotency and safety rule violati
70
70
  migraguard lint
71
71
  ```
72
72
 
73
+ ### `migraguard verify`
74
+
75
+ Verify migration idempotency using a shadow DB. Dumps the current DB schema, restores it to a temporary shadow database, then applies each pending migration twice — checking for errors and schema drift.
76
+
77
+ ```bash
78
+ migraguard verify # incremental: restore current DB, verify pending only
79
+ migraguard verify --all # full: empty shadow, verify all migrations from scratch
80
+ ```
81
+
73
82
  ## Schema management
74
83
 
75
84
  ### `migraguard dump`
package/README.md CHANGED
@@ -4,12 +4,42 @@ PostgreSQL 向けの SQL マイグレーション管理ツールチェイン。
4
4
 
5
5
  DDL を直 SQL で記述し、`psql` で実行するシンプルな構成を前提に、冪等性の担保・改ざん検知・スキーマ drift チェックを提供する。
6
6
 
7
+ ## Guarantees
8
+
9
+ migraguard は以下を保証する。
10
+
11
+ - **editable ノード以外の変更を CI で検出して落とす** — 線形モデルでは末尾ファイル、DAG モデルでは葉ノードのみが編集可能。それ以外のファイルが変更されていれば `check` がエラーを返す
12
+ - **意図しない巻き戻し(regression)を検出してエラー** — hotfix 済みのファイルが古いチェックサムに戻っている場合、`apply` が即座にエラーを返す
13
+ - **`apply` は advisory lock で排他制御** — 同一環境への同時適用を防止し、競合状態を排除する
14
+ - **`apply --verify` は事前 drift 検知 → 適用 → dump 更新を一貫実行** — スキーマの期待状態と実 DB の乖離を検出してから適用し、適用後に dump を自動更新する
15
+ - **失敗状態は DB に記録され、未解決のまま先に進めない** — `failed` 状態のファイルが残っている限り後続の適用はブロックされる。`resolve` による明示的な人間の判断を要求する
16
+
17
+ ## Quick Start
18
+
19
+ ```bash
20
+ # インストール
21
+ npm install --save-dev migraguard
22
+
23
+ # 新規マイグレーション作成 → SQL 編集 → ローカル DB に適用
24
+ npx migraguard new create_users_table
25
+ vim db/migrations/20260301_120000__create_users_table.sql
26
+ npx migraguard apply
27
+
28
+ # リリース前: squash → lint + check → dump 更新
29
+ npx migraguard squash
30
+ npx migraguard lint && npx migraguard check
31
+ npx migraguard dump
32
+
33
+ # PR では CI が lint + check(+ 任意で verify)を実行
34
+ # リリース前に squash で 1 ファイルにまとめてからマージ
35
+ ```
36
+
7
37
  ## 設計思想
8
38
 
9
39
  - **直 SQL**: マイグレーションは `psql -f` で実行可能な SQL ファイルとして管理する。ORM やマイグレーションフレームワーク固有の DSL を排除し、トランザクション境界を SQL に明示する
10
40
  - **forward-only**: 適用済みマイグレーションの変更を原則禁止し、常に前方向へ積み上げる。最新のマイグレーションファイルのみ、冪等性が担保されている前提で上書き更新・再適用を許容する
11
- - **リリース単位は 1 ファイル**: 複数のマイグレーションファイルはリリース前に `squash` で 1 ファイルにまとめる。1 ファイル = 1 リリース単位とすることで、エラー時の修正・再適用を単純化する
12
- - **依存ツリーによる並行リリース**(拡張): DDL の依存関係を解析して DAG を構築し、独立した変更の並行作業・並行リリースを可能にする。線形モデルの制約を緩和し、大規模システムでの運用を改善する
41
+ - **リリース単位は 1 ファイル**: 依存関係のあるマイグレーションファイルはリリース前に `squash` で 1 ファイルにまとめる。1 ファイル = 1 リリース単位とすることで、エラー時の修正・再適用を単純化する。DAG モデルでは独立した DDL は個別にリリース可能で、`squash` は依存チェーンごとに自動グループ化して実行する
42
+ - **依存ツリーによる並行リリース**: DDL の依存関係を解析して DAG を構築し、独立した変更の並行作業・並行リリースを可能にする。線形モデルの制約を緩和し、大規模システムでの運用を改善する
13
43
  - **検証を左に寄せる**: Squawk による lint、チェックサムによる改ざん検知、スキーマ dump の diff を CI(PR 段階)で実行し、本番到達前にリスクを排除する
14
44
  - **最小構成**: `psql` + SQL ファイル + メタデータ JSON + DB 状態テーブルによる管理。ツール固有のロックイン・ブラックボックスを避ける
15
45
 
@@ -30,7 +60,7 @@ metadata.json は「どのファイルが存在すべきか」を、schema_migra
30
60
 
31
61
  | 機能 | 説明 |
32
62
  |------|------|
33
- | `migraguard new <name>` | UTC タイムスタンプ付きの新規マイグレーション SQL ファイルを生成 |
63
+ | `migraguard new <name>` | ローカルタイムゾーンのタイムスタンプ(またはシリアル番号)付きの新規マイグレーション SQL ファイルを生成 |
34
64
  | `migraguard squash` | 未適用の複数マイグレーションファイルを 1 ファイルにマージ。リリース前に実行する |
35
65
  | `migraguard apply` | 未適用マイグレーションを順番に `psql` で実行。対象 DB の `schema_migrations` テーブルで適用済みを判定 |
36
66
  | `migraguard resolve <file>` | 失敗したマイグレーションを明示的にスキップ済みとしてマーク。後続の forward migration で修正済みであることを人間が判断した上で実行する |
@@ -43,6 +73,8 @@ metadata.json は「どのファイルが存在すべきか」を、schema_migra
43
73
  |------|------|
44
74
  | `migraguard check` | metadata.json とファイル本体のチェックサム比較。最新ファイル以外の変更・追加を検出しエラーとする。DB 接続不要 |
45
75
  | `migraguard lint` | Squawk を使用した SQL lint。冪等性・安全性に関するルール違反を検出 |
76
+ | `migraguard verify` | shadow DB を使用して各マイグレーションの冪等性を動的に検証する。既存 DB をダンプして復元し、未適用分を2回適用してエラーなし・スキーマ不変を確認する |
77
+ | `migraguard verify --all` | 空の shadow DB で全マイグレーションを最初から冪等性検証する |
46
78
 
47
79
  ### スキーマ管理
48
80
 
@@ -51,12 +83,14 @@ metadata.json は「どのファイルが存在すべきか」を、schema_migra
51
83
  | `migraguard dump` | `pg_dump --schema-only` を実行し、正規化したスキーマを出力。diff が取れる形式で保存 |
52
84
  | `migraguard diff` | 現在の DB スキーマと保存済みスキーマ dump の差分を表示 |
53
85
 
54
- ### 依存関係解析(拡張)
86
+ ### 依存関係解析・DAG モデル
55
87
 
56
88
  | 機能 | 説明 |
57
89
  |------|------|
58
- | `migraguard deps` | マイグレーション間の依存関係グラフを解析・表示 |
59
- | `migraguard deps --dot` | DOT 形式で出力(Graphviz で可視化可能) |
90
+ | `migraguard deps` | マイグレーション間の依存関係をツリー形式で表示。◆=editable(葉ノード)、◇=locked(非葉ノード)のマーク付き |
91
+ | `migraguard deps --html <path>` | GitGraph.js による依存グラフの HTML を生成 |
92
+
93
+ ![Migration Dependency Graph](assets/deps-graph.png)
60
94
 
61
95
  ## ディレクトリ構成
62
96
 
@@ -64,32 +98,48 @@ metadata.json は「どのファイルが存在すべきか」を、schema_migra
64
98
  project-root/
65
99
  ├── migraguard.config.json # 設定ファイル
66
100
  ├── db/
67
- │ ├── migrations/ # マイグレーション SQL ファイル
101
+ │ ├── migrations/ # マイグレーション SQL ファイル(デフォルト)
68
102
  │ │ ├── 20260301_120000__create_users_table.sql
69
103
  │ │ ├── 20260302_093000__add_email_index.sql
70
104
  │ │ └── ...
71
105
  │ ├── schema.sql # 正規化されたスキーマ dump(生成物)
72
106
  │ └── .migraguard/
73
107
  │ └── metadata.json # ファイル一覧 + チェックサム(適用状態は含まない)
108
+ ├── services/ # モノレポ構成の場合
109
+ │ ├── auth/migrations/ # migrationsDirs で追加の検索パスを指定可能
110
+ │ │ └── ...
111
+ │ └── billing/migrations/
112
+ │ └── ...
74
113
  └── ...
75
114
  ```
76
115
 
116
+ `migrationsDirs` で複数の検索パスを指定すると、全ディレクトリからマイグレーションファイルをスキャンし、タイムスタンプ(またはシリアル番号)順にソートして一元管理する。`new` / `squash` は配列の先頭ディレクトリに書き込む。
117
+
77
118
  ### schema_migrations テーブル(各環境の DB に作成)
78
119
 
79
120
  ```sql
80
121
  CREATE TABLE IF NOT EXISTS schema_migrations (
122
+ id BIGSERIAL PRIMARY KEY,
81
123
  file_name VARCHAR(256) NOT NULL,
82
124
  checksum VARCHAR(64) NOT NULL,
83
125
  status VARCHAR(16) NOT NULL DEFAULT 'applied', -- applied / failed / skipped
84
126
  applied_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
85
- resolved_at TIMESTAMPTZ, -- skipped 時の解決日時
86
- PRIMARY KEY (file_name, checksum)
127
+ resolved_at TIMESTAMPTZ -- skipped 時の解決日時
87
128
  );
88
129
  ```
89
130
 
90
131
  `migraguard apply` の初回実行時に自動作成される。
91
132
 
92
- 同一ファイルの再適用(チェックサム変更後の hotfix 再適用など)は別レコードとして記録される。これにより、あるファイルがいつ・どのバージョンで適用されたかの履歴が全て残る。
133
+ **履歴方針: 完全 INSERT-only**
134
+
135
+ PK は `BIGSERIAL`(自動採番)であり、同一ファイル・同一チェックサムであっても毎回新しいレコードが INSERT される。UPDATE は行わない。
136
+
137
+ - 同一ファイルの再適用(hotfix でチェックサム変更後の再適用など)→ 新しいチェックサムで別レコードを INSERT
138
+ - 同一チェックサムでの再実行(冪等性による再適用)→ 同じチェックサムで別レコードを INSERT(`applied_at` が異なる)
139
+ - `failed` → 修正後の再実行 → `failed` レコードは残したまま、新しい `applied` レコードを INSERT
140
+ - `resolve` → `failed` レコードは残したまま、同じチェックサムで `skipped` レコードを INSERT
141
+
142
+ あるファイルの「最新状態」は、そのファイル名の最新レコード(`applied_at` が最大)の `status` で判定する。過去のレコードは監査ログとして残り続ける。
93
143
 
94
144
  status の意味:
95
145
 
@@ -99,9 +149,11 @@ status の意味:
99
149
  | `failed` | 適用を試みたがエラーで失敗。未解決 |
100
150
  | `skipped` | `migraguard resolve` により明示的にスキップ。後続の forward migration で修正済みという人間の判断 |
101
151
 
102
- ### 先祖返り検知
152
+ ### Hotfix 済みマイグレーションの意図しない巻き戻し検知(regression detection)
153
+
154
+ **防いでいる事故**: hotfix で修正済みのマイグレーションファイルが、git revert・ブランチ切り替え・マージミスなどにより古いバージョンに戻ってしまい、修正前の DDL が本番に再適用されること。
103
155
 
104
- apply 時、ファイルの現在のチェックサムが **過去のレコード(最新以外)のチェックサムと一致** した場合、先祖返り(意図しない巻き戻し)としてエラーにする。
156
+ apply 時、ファイルの現在のチェックサムが **過去のレコード(最新以外)のチェックサムと一致** した場合、意図しない巻き戻しとしてエラーにする。
105
157
 
106
158
  ```
107
159
  例:
@@ -111,16 +163,14 @@ apply 時、ファイルの現在のチェックサムが **過去のレコー
111
163
 
112
164
  ファイルのチェックサムが checksum_v1 に戻っている
113
165
  → 最新レコードは checksum_v2 → 不一致 → さらに過去の checksum_v1 と一致
114
- 先祖返りとしてエラー
166
+ 巻き戻しとしてエラー
115
167
  ```
116
168
 
117
- これにより、ファイルを誤って古いバージョンに戻してしまった場合を検知できる。
118
-
119
169
  ## 設定ファイル(migraguard.config.json)
120
170
 
121
171
  ```json
122
172
  {
123
- "migrationsDir": "db/migrations",
173
+ "migrationsDirs": ["db/migrations"],
124
174
  "schemaFile": "db/schema.sql",
125
175
  "metadataFile": "db/.migraguard/metadata.json",
126
176
  "naming": {
@@ -151,7 +201,7 @@ apply 時、ファイルの現在のチェックサムが **過去のレコー
151
201
  | キー | デフォルト | 説明 |
152
202
  |------|-----------|------|
153
203
  | `pattern` | `{timestamp}__{description}.sql` | ファイル名のテンプレート。`{timestamp}`, `{prefix}`, `{description}` を使用可能 |
154
- | `timestamp` | `YYYYMMDD_HHMMSS` | タイムスタンプのフォーマット。ソート順の基準になる |
204
+ | `timestamp` | `YYYYMMDD_HHMMSS` | タイムスタンプのフォーマット(ローカルタイムゾーン)。`NNNN` 等の `N` のみの形式でシリアル番号モード(既存ファイルの最大番号 + 1 で自動採番) |
155
205
  | `prefix` | `""` | 全ファイルに付与する固定プレフィックス。カテゴリやサービス名の識別に使用 |
156
206
  | `sortKey` | `timestamp` | ファイルのソート順を決定するキー。`timestamp`(タイムスタンプ部分で昇順)が標準 |
157
207
 
@@ -189,6 +239,19 @@ apply 時、ファイルの現在のチェックサムが **過去のレコー
189
239
 
190
240
  `migraguard new` はこの設定に従ってファイル名を生成する。`migraguard check` / `apply` はパターンに基づいてタイムスタンプ部分を抽出し、ソート順を決定する。
191
241
 
242
+ `migrationsDirs` には複数の検索パスを指定可能。モノレポでマイクロサービスごとにマイグレーションディレクトリを分けている場合に使用する。後方互換のため `migrationsDir`(単数)も受け付ける。`new` / `squash` は配列の先頭ディレクトリに書き込む。
243
+
244
+ ```json
245
+ // モノレポでの複数ディレクトリ構成例
246
+ {
247
+ "migrationsDirs": [
248
+ "db/migrations",
249
+ "services/auth/migrations",
250
+ "services/billing/migrations"
251
+ ]
252
+ }
253
+ ```
254
+
192
255
  `connection` は環境変数(`PGHOST`, `PGPORT`, `PGDATABASE`, `PGUSER`, `PGPASSWORD`)でオーバーライド可能。
193
256
 
194
257
  ## マイグレーションファイルの規約
@@ -207,7 +270,7 @@ YYYYMMDD_HHMMSS__<description>.sql
207
270
  <prefix>_YYYYMMDD_HHMMSS__<description>.sql
208
271
  ```
209
272
 
210
- - タイムスタンプは UTC 固定(`naming.timestamp` で形式を変更可能)
273
+ - タイムスタンプはローカルタイムゾーン(`naming.timestamp` で形式を変更可能。`NNNN` でシリアル番号モード)
211
274
  - description は英数字とアンダースコアのみ
212
275
  - 操作種別を先頭に付与: `create_`, `add_`, `alter_`, `drop_`, `backfill_`, `create_index_`
213
276
  - prefix はカテゴリ・マイクロサービス名等の識別子(`naming.prefix` で設定)
@@ -233,6 +296,8 @@ UPDATE users SET status = 'active' WHERE status IS NULL;
233
296
  npx migraguard squash
234
297
  ```
235
298
 
299
+ ### 線形モデルでの動作
300
+
236
301
  1. metadata.json を読み込む
237
302
  2. `migrationsDir` のファイルのうち、metadata.json に記録されていない新規ファイルを特定
238
303
  3. 新規ファイルが 2 つ以上ある場合、タイムスタンプ順に連結して 1 ファイルにまとめる
@@ -248,7 +313,26 @@ squash 後:
248
313
  20260301_093000__add_user_email_and_email_index.sql (1ファイルに統合)
249
314
  ```
250
315
 
251
- ### なぜ 1 リリース = 1 ファイルか
316
+ `migraguard check` は新規ファイル(metadata.json に未記録)が 2 つ以上ある場合にエラーとする。squash を実行してから push する運用を強制する。
317
+
318
+ ### DAG モデルでの動作
319
+
320
+ DAG モードでは、新規ファイルを依存関係に基づいて連結成分(グループ)に自動分割し、グループごとに squash する。独立した DDL(互いに依存関係がない)は個別のファイルとして残す。
321
+
322
+ ```
323
+ squash 前:
324
+ 20260308_100000__create_follows.sql (新規 — users に依存)
325
+ 20260308_110000__add_follow_index.sql (新規 — follows に依存)
326
+ 20260309_100000__create_notifications.sql (新規 — users に依存、follows とは独立)
327
+
328
+ squash 後:
329
+ 20260308_110000__create_follows_and_add_follow_index.sql (依存チェーンを統合)
330
+ 20260309_100000__create_notifications.sql (独立 — そのまま)
331
+ ```
332
+
333
+ 各グループ内ではタイムスタンプの古い順に連結し、依存先が先・依存元が後の順序を保証する。
334
+
335
+ ### なぜ依存チェーンを 1 ファイルにまとめるか
252
336
 
253
337
  複数ファイルが未適用の状態でリリースすると、以下の問題が起きる。
254
338
 
@@ -260,9 +344,9 @@ squash 後:
260
344
 
261
345
  **1 ファイルなら単純**:
262
346
  - squash 後の 1 ファイルがエラー → そのファイルを修正 → 再適用
263
- - 最新ファイルルールに適合し、冪等性により安全に再実行できる
347
+ - 葉ノードルールに適合し、冪等性により安全に再実行できる
264
348
 
265
- `migraguard check` は新規ファイル(metadata.json に未記録)が 2 つ以上ある場合にエラーとする。squash を実行してから push する運用を強制する。
349
+ 独立した DDL はこの問題が発生しないため、無理にまとめる必要はない。
266
350
 
267
351
  ## apply のフロー
268
352
 
@@ -670,24 +754,15 @@ SET lock_timeout = '5s';
670
754
  | エラーの影響範囲 | 全後続ファイルがブロック | 依存するファイルのみブロック |
671
755
  | 複数チームの作業 | 直列化(1 チームずつ) | 独立したテーブルなら並行作業可能 |
672
756
 
673
- ### 実装フェーズ
674
-
675
- 依存ツリーモデルは線形モデルの上位互換として段階的に導入する。
676
-
677
- | フェーズ | 内容 |
678
- |---------|------|
679
- | Phase 1 | 線形モデルで基本機能(new / apply / check / squash / lint / dump)を実装 |
680
- | Phase 2 | `libpg_query` による DDL 依存抽出と `migraguard deps` コマンドを実装。情報表示のみで動作は変更しない |
681
- | Phase 3 | check と apply を依存ツリー対応に拡張。葉ノード判定、トポロジカルソート適用 |
682
-
683
- ### 依存解析の候補ライブラリ
757
+ ### 実装状況
684
758
 
685
- | ライブラリ | 概要 |
686
- |-----------|------|
687
- | [libpg_query](https://www.npmjs.com/package/libpg_query) | PostgreSQL 実パーサの Node.js バインディング。SQL → AST のパースを提供 |
688
- | [@pg-nano/pg-parser](https://www.npmjs.com/package/@pg-nano/pg-parser) | TypeScript ファーストの PostgreSQL パーサ。型定義と AST ユーティリティ(`walk()`, `select()`)を提供 |
759
+ 依存ツリーモデルは線形モデルの上位互換として段階的に導入された。
689
760
 
690
- Squawk は単一ファイル内の lint に特化しており、ファイル間の依存抽出機能は持たない。依存解析は上記ライブラリを使って自前で構築する。
761
+ | フェーズ | 内容 | 状態 |
762
+ |---------|------|------|
763
+ | Phase 1 | 線形モデルで基本機能(new / apply / check / squash / lint / dump / verify)を実装 | ✅ 完了 |
764
+ | Phase 2 | `@pg-nano/pg-parser` による DDL 依存抽出と `migraguard deps` コマンドを実装 | ✅ 完了 |
765
+ | Phase 3 | check / apply / editable / squash を依存ツリー対応に拡張。葉ノード判定、トポロジカルソート適用、部分失敗時の独立ブランチ続行 | ✅ 完了 |
691
766
 
692
767
  ## 既存ツールとの比較
693
768
 
@@ -815,5 +890,5 @@ metadata.json の移行例:
815
890
  | DB 接続 | `psql` CLI(DDL ファイルを直接渡す) |
816
891
  | スキーマ dump | `pg_dump --schema-only` |
817
892
  | SQL lint | [Squawk](https://squawkhq.com/) |
818
- | SQL パーサ | [libpg_query](https://www.npmjs.com/package/libpg_query) / [@pg-nano/pg-parser](https://www.npmjs.com/package/@pg-nano/pg-parser)(依存解析用) |
893
+ | SQL パーサ | [@pg-nano/pg-parser](https://www.npmjs.com/package/@pg-nano/pg-parser)(DDL 依存解析用。PostgreSQL 実パーサの TypeScript バインディング) |
819
894
  | パッケージ管理 | npm |
package/bin/migraguard.js CHANGED
File without changes