migraguard 0.6.0 → 0.6.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/README.md +4 -3
- package/docs/dag-internals.md +165 -0
- package/docs/expand-contract.md +607 -0
- package/docs/safe-ddl.md +350 -0
- package/docs/state-model.md +238 -0
- package/docs/typescript-api.md +338 -0
- package/package.json +2 -2
- /package/{COMMANDS.md → docs/commands.md} +0 -0
package/README.md
CHANGED
|
@@ -204,7 +204,7 @@ See [docs/expand-contract.md](docs/expand-contract.md) for the complete guide: f
|
|
|
204
204
|
| `gate` | Evaluate deployment gate conditions |
|
|
205
205
|
| `baseline` | Squash applied migrations into `schema.sql` |
|
|
206
206
|
|
|
207
|
-
See [
|
|
207
|
+
See [docs/commands.md](docs/commands.md) for detailed usage, options, and examples.
|
|
208
208
|
|
|
209
209
|
## CI Integration
|
|
210
210
|
|
|
@@ -542,8 +542,9 @@ No. `verify` creates a temporary shadow DB, applies migrations twice, then drops
|
|
|
542
542
|
|
|
543
543
|
## Detailed Documentation
|
|
544
544
|
|
|
545
|
-
- [
|
|
545
|
+
- [docs/commands.md](docs/commands.md) — Full command reference with options and examples
|
|
546
546
|
- [docs/state-model.md](docs/state-model.md) — Apply/check/resolve/squash flows, INSERT-only design, regression detection
|
|
547
547
|
- [docs/dag-internals.md](docs/dag-internals.md) — Dependency analysis, explicit declarations, DAG migration compatibility
|
|
548
548
|
- [docs/safe-ddl.md](docs/safe-ddl.md) — Safe DDL patterns for PostgreSQL (lock timeout, CONCURRENTLY, batch backfills)
|
|
549
|
-
- [docs/expand-contract.md](docs/expand-contract.md) — Expand/contract pattern: phased migrations, state machine, CI/CD deployment gate
|
|
549
|
+
- [docs/expand-contract.md](docs/expand-contract.md) — Expand/contract pattern: phased migrations, state machine, CI/CD deployment gate
|
|
550
|
+
- [docs/typescript-api.md](docs/typescript-api.md) — TypeScript programmatic API: all commands as typed async functions
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# DAG Model: Dependency Analysis Internals
|
|
2
|
+
|
|
3
|
+
## Dependency Analysis Method
|
|
4
|
+
|
|
5
|
+
Each migration SQL is parsed into an AST using a PostgreSQL parser (`libpg_query`) to extract object creation/reference relationships and build a DAG (directed acyclic graph).
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
Information extracted from SQL statements:
|
|
9
|
+
|
|
10
|
+
CREATE TABLE users (...)
|
|
11
|
+
→ creates: users
|
|
12
|
+
|
|
13
|
+
ALTER TABLE users ADD COLUMN email VARCHAR(256)
|
|
14
|
+
→ depends: users → creates: users.email
|
|
15
|
+
|
|
16
|
+
CREATE INDEX CONCURRENTLY ON users (email)
|
|
17
|
+
→ depends: users, users.email
|
|
18
|
+
|
|
19
|
+
CREATE TABLE orders (user_id INT REFERENCES users(id))
|
|
20
|
+
→ depends: users → creates: orders
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Extractable DDL and Dependency Types
|
|
24
|
+
|
|
25
|
+
| DDL | Creates | Depends On |
|
|
26
|
+
|-----|---------|------------|
|
|
27
|
+
| `CREATE TABLE` | table | none (referenced tables if REFERENCES present) |
|
|
28
|
+
| `ALTER TABLE ADD COLUMN` | column | table |
|
|
29
|
+
| `ALTER TABLE ADD CONSTRAINT` | constraint | table, columns, referenced tables |
|
|
30
|
+
| `CREATE INDEX` | index | table, columns |
|
|
31
|
+
| `CREATE VIEW` | view | referenced tables |
|
|
32
|
+
| `CREATE FUNCTION` | function | referenced tables (requires body analysis) |
|
|
33
|
+
| `DROP *` | none | target object |
|
|
34
|
+
|
|
35
|
+
### Limitations of Auto-Extraction
|
|
36
|
+
|
|
37
|
+
| Case | Auto-extraction | Workaround |
|
|
38
|
+
|------|----------------|------------|
|
|
39
|
+
| `CREATE TABLE` / `ALTER TABLE` / `CREATE INDEX` / `CREATE VIEW` | ✅ extractable | — |
|
|
40
|
+
| Table references inside `CREATE FUNCTION` body | ⚠️ partial | Static analysis of function body SQL, but dynamic SQL (`EXECUTE format(...)` etc.) is undetectable. Use explicit declarations |
|
|
41
|
+
| DDL inside `DO $$ ... $$` blocks | ❌ undetectable | Explicit declaration required |
|
|
42
|
+
| Dynamic SQL (`EXECUTE`, variable expansion) | ❌ undetectable | Explicit declaration required |
|
|
43
|
+
| Implicit schema references via `search_path` | ❌ undetectable | Explicit declaration required |
|
|
44
|
+
| Business-logic ordering dependencies (data dependencies) | ❌ out of scope | Explicit declaration required |
|
|
45
|
+
|
|
46
|
+
When auto-extraction fails to detect dependencies, `check` will pass without warning. Add explicit declarations when in doubt.
|
|
47
|
+
|
|
48
|
+
## Explicit Dependency Declaration
|
|
49
|
+
|
|
50
|
+
Dependencies that cannot be auto-extracted are explicitly declared via SQL file comments:
|
|
51
|
+
|
|
52
|
+
```sql
|
|
53
|
+
-- migraguard:depends-on 20260228_120000__create_users_table.sql
|
|
54
|
+
|
|
55
|
+
SET lock_timeout = '5s';
|
|
56
|
+
...
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Or declared in `migraguard.config.json`:
|
|
60
|
+
|
|
61
|
+
```json
|
|
62
|
+
{
|
|
63
|
+
"dependencies": {
|
|
64
|
+
"20260301_093000__backfill_user_status.sql": [
|
|
65
|
+
"20260228_120000__add_user_status_column.sql"
|
|
66
|
+
]
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Auto-extracted and explicit declarations are merged to build the final DAG. Explicit declarations are **composed as additional dependencies** (they cannot reduce dependencies). The final DAG is the union of both.
|
|
72
|
+
|
|
73
|
+
## Impact on check and apply
|
|
74
|
+
|
|
75
|
+
In the dependency tree model, "latest file (tail)" is replaced by "leaf node (a file that no other file depends on)."
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
Linear model: A → B → C → [D]
|
|
79
|
+
↑ editable (tail only)
|
|
80
|
+
|
|
81
|
+
DAG model:
|
|
82
|
+
A (locked — B, C depend on it)
|
|
83
|
+
/ \
|
|
84
|
+
B C (locked — D, E depend on them)
|
|
85
|
+
| \
|
|
86
|
+
[D] [E] ← editable (leaf nodes)
|
|
87
|
+
|
|
88
|
+
CI (check):
|
|
89
|
+
- Non-leaf node modified → error
|
|
90
|
+
- Leaf node modified → allowed
|
|
91
|
+
- New file depends on existing leaf → that leaf transitions to locked
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**apply in DAG mode**:
|
|
95
|
+
- Files are applied in topological sort order (dependencies first)
|
|
96
|
+
- Independent files have no ordering constraint
|
|
97
|
+
- On failure: only files depending on the failed file are blocked; independent files are unaffected
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
Example: D fails
|
|
101
|
+
|
|
102
|
+
A
|
|
103
|
+
/ \
|
|
104
|
+
B C
|
|
105
|
+
| \
|
|
106
|
+
[D] E ← independent of D, so apply proceeds
|
|
107
|
+
|
|
108
|
+
D's failure does not block E's release
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Squash in DAG Mode
|
|
112
|
+
|
|
113
|
+
New files are automatically split into connected components (groups) based on dependencies. Squash is performed per group; independent DDL remains as individual files.
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
Before squash:
|
|
117
|
+
20260308_100000__create_follows.sql (new — depends on users)
|
|
118
|
+
20260308_110000__add_follow_index.sql (new — depends on follows)
|
|
119
|
+
20260309_100000__create_notifications.sql (new — depends on users, independent of follows)
|
|
120
|
+
|
|
121
|
+
After squash:
|
|
122
|
+
20260308_110000__create_follows_and_add_follow_index.sql (dependency chain merged)
|
|
123
|
+
20260309_100000__create_notifications.sql (independent — unchanged)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Within each group, files are concatenated in ascending timestamp order, guaranteeing that dependencies precede dependents.
|
|
127
|
+
|
|
128
|
+
## Benefits for Large-Scale Systems
|
|
129
|
+
|
|
130
|
+
| Constraint | Linear Model | Dependency Tree Model |
|
|
131
|
+
|------------|-------------|----------------------|
|
|
132
|
+
| Concurrently modifiable files | 1 (tail only) | Number of leaf nodes |
|
|
133
|
+
| Parallel releases | Not possible | Independent branches can be released in parallel |
|
|
134
|
+
| Error blast radius | All subsequent files blocked | Only dependent files blocked |
|
|
135
|
+
| Multi-team work | Serialized | Parallel work possible for independent tables |
|
|
136
|
+
|
|
137
|
+
## DAG Migration Compatibility Policy
|
|
138
|
+
|
|
139
|
+
When migrating from the linear model to the dependency tree model, compatibility with existing schema_migrations is maintained.
|
|
140
|
+
|
|
141
|
+
### Migration Steps
|
|
142
|
+
|
|
143
|
+
1. **Retain existing schema_migrations as-is**: Records from the linear model are treated as files with "implicitly fully-serial dependencies"
|
|
144
|
+
2. **Migration point marker**: Add `"model": "dag"` flag to metadata.json. Files before this flag use linear ordering; files after use DAG analysis
|
|
145
|
+
3. **Backward compatibility**: DAG-aware migraguard can read linear model metadata.json. The reverse (downgrade from DAG to linear) is not supported
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
metadata.json example:
|
|
149
|
+
|
|
150
|
+
{
|
|
151
|
+
"model": "dag",
|
|
152
|
+
"modelSince": "20260401_000000__first_dag_migration.sql",
|
|
153
|
+
"migrations": [
|
|
154
|
+
{"file": "20260301_...", "checksum": "aaa"}, ← linear model era (fully serial)
|
|
155
|
+
{"file": "20260302_...", "checksum": "bbb"}, ← linear model era
|
|
156
|
+
{"file": "20260401_...", "checksum": "ccc"} ← DAG model (dependency analysis)
|
|
157
|
+
]
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### check / apply Behavior at Boundary
|
|
162
|
+
|
|
163
|
+
- Files before `modelSince`: Checked linearly by timestamp as before
|
|
164
|
+
- Files after `modelSince`: Leaf node determination and topological sort via DAG analysis
|
|
165
|
+
- Boundary: The `modelSince` file implicitly depends on all prior files (inherits the linear model's final state)
|