|
| 1 | +# LaunchQL Migrate Documentation |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +LaunchQL Migrate (`@launchql/migrate`) is a low-level migration engine that serves as a pure TypeScript replacement for Sqitch. It provides database migration management with support for deployment, reversion, and verification of database changes. |
| 6 | + |
| 7 | +## Architecture |
| 8 | + |
| 9 | +```mermaid |
| 10 | +graph TB |
| 11 | + subgraph "@launchql/core" |
| 12 | + Core[Core Orchestration] |
| 13 | + ProjectMgmt[Project Management] |
| 14 | + HighLevel[High-level APIs] |
| 15 | + end |
| 16 | + |
| 17 | + subgraph "@launchql/migrate" |
| 18 | + Parser[Plan Parser] |
| 19 | + Validator[Reference Validator] |
| 20 | + Deploy[Deploy Engine] |
| 21 | + Revert[Revert Engine] |
| 22 | + Verify[Verify Engine] |
| 23 | + Registry[Change Registry] |
| 24 | + Transaction[Transaction Manager] |
| 25 | + end |
| 26 | + |
| 27 | + subgraph "Database" |
| 28 | + PG[(PostgreSQL)] |
| 29 | + Schema[sqitch schema] |
| 30 | + Changes[changes table] |
| 31 | + Dependencies[dependencies table] |
| 32 | + Tags[tags table] |
| 33 | + end |
| 34 | + |
| 35 | + Core --> Parser |
| 36 | + Core --> Deploy |
| 37 | + Core --> Revert |
| 38 | + Core --> Verify |
| 39 | + |
| 40 | + Parser --> Validator |
| 41 | + Deploy --> Registry |
| 42 | + Deploy --> Transaction |
| 43 | + Revert --> Registry |
| 44 | + Revert --> Transaction |
| 45 | + Verify --> Registry |
| 46 | + |
| 47 | + Registry --> Schema |
| 48 | + Transaction --> PG |
| 49 | +``` |
| 50 | + |
| 51 | +## Core Components |
| 52 | + |
| 53 | +### 1. Plan Parser (`src/parser/`) |
| 54 | + |
| 55 | +The plan parser reads and validates Sqitch plan files, supporting all reference formats defined in the Sqitch specification. |
| 56 | + |
| 57 | +#### Key Files: |
| 58 | +- `plan-parser.ts` - Main parser with validation |
| 59 | +- `validators.ts` - Reference validation logic |
| 60 | +- `plan.ts` - Legacy parser (backwards compatibility) |
| 61 | + |
| 62 | +#### Supported Reference Formats: |
| 63 | + |
| 64 | +```typescript |
| 65 | +// Plain change name |
| 66 | +"users_table" |
| 67 | + |
| 68 | +// Tag reference |
| 69 | +"@v1.0" |
| 70 | + |
| 71 | +// Change at specific tag |
| 72 | + |
| 73 | + |
| 74 | +// SHA1 reference (40 hex chars) |
| 75 | +"abc1234567890123456789012345678901234567" |
| 76 | + |
| 77 | +// Project-qualified forms |
| 78 | +"other_project:users_table" |
| 79 | +"other_project:@v1.0" |
| 80 | +"other_project:abc1234567890123456789012345678901234567" |
| 81 | + |
| 82 | +// Symbolic references |
| 83 | +"HEAD" // Last change |
| 84 | +"ROOT" // First change |
| 85 | +"@HEAD" // Alternative form |
| 86 | +"@ROOT" // Alternative form |
| 87 | + |
| 88 | +// Relative references |
| 89 | +"HEAD^" // One change before HEAD |
| 90 | +"HEAD^^" // Two changes before HEAD |
| 91 | +"HEAD^3" // Three changes before HEAD |
| 92 | +"@v1.0~" // One change after tag v1.0 |
| 93 | +"@v1.0~2" // Two changes after tag v1.0 |
| 94 | +``` |
| 95 | + |
| 96 | +### 2. Deploy Engine (`src/commands/deploy.ts`) |
| 97 | + |
| 98 | +Deploys database changes in dependency order. |
| 99 | + |
| 100 | +#### Deploy Flow: |
| 101 | + |
| 102 | +```mermaid |
| 103 | +sequenceDiagram |
| 104 | + participant CLI |
| 105 | + participant Deploy |
| 106 | + participant Registry |
| 107 | + participant Transaction |
| 108 | + participant DB |
| 109 | + |
| 110 | + CLI->>Deploy: deploy(options) |
| 111 | + Deploy->>Registry: getDeployedChanges() |
| 112 | + Registry->>DB: SELECT FROM changes |
| 113 | + DB-->>Registry: deployed changes |
| 114 | + Registry-->>Deploy: Set<deployed> |
| 115 | + |
| 116 | + Deploy->>Deploy: calculateDeployOrder() |
| 117 | + |
| 118 | + loop For each change |
| 119 | + Deploy->>Registry: isDeployed(change) |
| 120 | + alt Not deployed |
| 121 | + Deploy->>Transaction: begin() |
| 122 | + Deploy->>DB: Execute deploy.sql |
| 123 | + Deploy->>Registry: recordChange() |
| 124 | + Deploy->>Transaction: commit() |
| 125 | + else Already deployed |
| 126 | + Deploy->>Deploy: skip |
| 127 | + end |
| 128 | + end |
| 129 | +``` |
| 130 | + |
| 131 | +### 3. Revert Engine (`src/commands/revert.ts`) |
| 132 | + |
| 133 | +Reverts deployed changes in reverse dependency order. |
| 134 | + |
| 135 | +#### Revert Flow: |
| 136 | + |
| 137 | +```mermaid |
| 138 | +sequenceDiagram |
| 139 | + participant CLI |
| 140 | + participant Revert |
| 141 | + participant Registry |
| 142 | + participant Transaction |
| 143 | + participant DB |
| 144 | + |
| 145 | + CLI->>Revert: revert(target, options) |
| 146 | + Revert->>Registry: getDeployedChanges() |
| 147 | + Revert->>Revert: calculateRevertOrder() |
| 148 | + |
| 149 | + loop For each change |
| 150 | + Revert->>Registry: hasDependents(change) |
| 151 | + alt No dependents |
| 152 | + Revert->>Transaction: begin() |
| 153 | + Revert->>DB: Execute revert.sql |
| 154 | + Revert->>Registry: removeChange() |
| 155 | + Revert->>Transaction: commit() |
| 156 | + else Has dependents |
| 157 | + Revert->>Revert: error |
| 158 | + end |
| 159 | + end |
| 160 | +``` |
| 161 | + |
| 162 | +### 4. Verify Engine (`src/commands/verify.ts`) |
| 163 | + |
| 164 | +Verifies that deployed changes match their expected state. |
| 165 | + |
| 166 | +### 5. Change Registry (`src/registry/`) |
| 167 | + |
| 168 | +Manages the sqitch schema and tracks deployed changes, dependencies, and tags. |
| 169 | + |
| 170 | +## API Reference |
| 171 | + |
| 172 | +### Plan Parser API |
| 173 | + |
| 174 | +```typescript |
| 175 | +import { parsePlanFileWithValidation } from '@launchql/migrate'; |
| 176 | + |
| 177 | +// Parse a plan file with full validation |
| 178 | +const result = parsePlanFileWithValidation('/path/to/sqitch.plan'); |
| 179 | + |
| 180 | +if (result.errors.length > 0) { |
| 181 | + // Handle validation errors |
| 182 | + result.errors.forEach(error => { |
| 183 | + console.error(`Line ${error.line}: ${error.message}`); |
| 184 | + }); |
| 185 | +} else { |
| 186 | + // Use the parsed plan |
| 187 | + const plan = result.plan; |
| 188 | + console.log(`Project: ${plan.project}`); |
| 189 | + console.log(`Changes: ${plan.changes.length}`); |
| 190 | + console.log(`Tags: ${plan.tags.length}`); |
| 191 | +} |
| 192 | +``` |
| 193 | + |
| 194 | +### Reference Validation API |
| 195 | + |
| 196 | +```typescript |
| 197 | +import { isValidChangeName, isValidTagName, parseReference } from '@launchql/migrate'; |
| 198 | + |
| 199 | +// Validate change names |
| 200 | +isValidChangeName('users_table'); // true |
| 201 | +isValidChangeName('@invalid'); // false |
| 202 | + |
| 203 | +// Validate tag names |
| 204 | +isValidTagName('v1.0'); // true |
| 205 | +isValidTagName('tag/with/slash'); // false |
| 206 | + |
| 207 | +// Parse references |
| 208 | +const ref = parseReference( 'other_project:[email protected]'); |
| 209 | +// Returns: { |
| 210 | +// project: 'other_project', |
| 211 | +// change: 'users_table', |
| 212 | +// tag: 'v1.0' |
| 213 | +// } |
| 214 | +``` |
| 215 | + |
| 216 | +### Deploy API |
| 217 | + |
| 218 | +```typescript |
| 219 | +import { deploy } from '@launchql/migrate'; |
| 220 | + |
| 221 | +// Deploy all changes |
| 222 | +await deploy({ |
| 223 | + planFile: '/path/to/sqitch.plan', |
| 224 | + connection: pgConnection, |
| 225 | + transaction: true, // Use transaction mode |
| 226 | + verify: true // Verify after deploy |
| 227 | +}); |
| 228 | + |
| 229 | +// Deploy to a specific target |
| 230 | +await deploy({ |
| 231 | + planFile: '/path/to/sqitch.plan', |
| 232 | + connection: pgConnection, |
| 233 | + target: '@v1.0' // Deploy up to tag v1.0 |
| 234 | +}); |
| 235 | +``` |
| 236 | + |
| 237 | +### Revert API |
| 238 | + |
| 239 | +```typescript |
| 240 | +import { revert } from '@launchql/migrate'; |
| 241 | + |
| 242 | +// Revert to a specific change |
| 243 | +await revert({ |
| 244 | + planFile: '/path/to/sqitch.plan', |
| 245 | + connection: pgConnection, |
| 246 | + target: 'users_table', |
| 247 | + transaction: true |
| 248 | +}); |
| 249 | + |
| 250 | +// Revert last 3 changes |
| 251 | +await revert({ |
| 252 | + planFile: '/path/to/sqitch.plan', |
| 253 | + connection: pgConnection, |
| 254 | + target: 'HEAD^3' |
| 255 | +}); |
| 256 | +``` |
| 257 | + |
| 258 | +### Registry API |
| 259 | + |
| 260 | +```typescript |
| 261 | +import { Registry } from '@launchql/migrate'; |
| 262 | + |
| 263 | +const registry = new Registry(pgConnection); |
| 264 | + |
| 265 | +// Check if a change is deployed |
| 266 | +const isDeployed = await registry.isChangeDeployed('project', 'change_name'); |
| 267 | + |
| 268 | +// Get all deployed changes |
| 269 | +const deployed = await registry.getDeployedChanges('project'); |
| 270 | + |
| 271 | +// Record a new deployment |
| 272 | +await registry.recordChange({ |
| 273 | + project: 'my_project', |
| 274 | + change: 'users_table', |
| 275 | + dependencies: ['initial_schema'], |
| 276 | + deployedBy: 'developer', |
| 277 | + deployedAt: new Date() |
| 278 | +}); |
| 279 | +``` |
| 280 | + |
| 281 | +## Transaction Management |
| 282 | + |
| 283 | +All operations support both transactional and non-transactional modes: |
| 284 | + |
| 285 | +```typescript |
| 286 | +// With transaction (default) - rolls back on error |
| 287 | +await deploy({ |
| 288 | + transaction: true, |
| 289 | + // ... other options |
| 290 | +}); |
| 291 | + |
| 292 | +// Without transaction - continues on error |
| 293 | +await deploy({ |
| 294 | + transaction: false, |
| 295 | + // ... other options |
| 296 | +}); |
| 297 | +``` |
| 298 | + |
| 299 | +## Cross-Project Dependencies |
| 300 | + |
| 301 | +LaunchQL Migrate supports dependencies across different projects: |
| 302 | + |
| 303 | +```typescript |
| 304 | +// In project A's plan file |
| 305 | +users_table 2024-01-01T00:00:00Z dev <dev@example.com> |
| 306 | + |
| 307 | +// In project B's plan file |
| 308 | +user_profiles [project_a:users_table] 2024-01-02T00:00:00Z dev <dev@example.com> |
| 309 | +``` |
| 310 | + |
| 311 | +The system ensures that cross-project dependencies are satisfied before deployment and prevents reversion of changes that have cross-project dependents. |
| 312 | + |
| 313 | +## Error Handling |
| 314 | + |
| 315 | +All operations return detailed error information: |
| 316 | + |
| 317 | +```typescript |
| 318 | +try { |
| 319 | + await deploy(options); |
| 320 | +} catch (error) { |
| 321 | + if (error.code === 'DEPENDENCY_NOT_MET') { |
| 322 | + console.error(`Missing dependency: ${error.dependency}`); |
| 323 | + } else if (error.code === 'VERIFICATION_FAILED') { |
| 324 | + console.error(`Verification failed for: ${error.change}`); |
| 325 | + } |
| 326 | +} |
| 327 | +``` |
| 328 | + |
| 329 | +## Best Practices |
| 330 | + |
| 331 | +1. **Always use transactions in production** - This ensures database consistency |
| 332 | +2. **Verify after deployment** - Use the `verify` option to ensure changes were applied correctly |
| 333 | +3. **Use meaningful change names** - Follow the naming conventions (alphanumeric, underscore, hyphen) |
| 334 | +4. **Tag releases** - Use tags to mark stable points in your migration history |
| 335 | +5. **Test migrations** - Always test in a development environment first |
| 336 | + |
| 337 | +## Migration from Sqitch |
| 338 | + |
| 339 | +LaunchQL Migrate is designed to be compatible with existing Sqitch plan files and database schemas. To migrate: |
| 340 | + |
| 341 | +1. Install `@launchql/migrate` |
| 342 | +2. Point it to your existing `sqitch.plan` file |
| 343 | +3. Use the same database connection |
| 344 | +4. All existing deployments will be recognized |
| 345 | + |
| 346 | +The main differences from Sqitch: |
| 347 | +- Pure TypeScript implementation (no Perl required) |
| 348 | +- Better error messages and validation |
| 349 | +- Programmatic API for integration |
| 350 | +- Support for modern JavaScript/TypeScript tooling |
0 commit comments