pg-migration-engine 0.1.0
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/LICENSE +35 -0
- package/README.md +304 -0
- package/package.json +52 -0
- package/src/behavioral/behavioral-applier.js +374 -0
- package/src/behavioral/behavioral-extractor.js +266 -0
- package/src/behavioral/behavioral-puller.js +35 -0
- package/src/behavioral/index.js +4 -0
- package/src/behavioral/phase-sorter.js +24 -0
- package/src/constants.js +97 -0
- package/src/ddl-generator/alter-generator.js +1512 -0
- package/src/ddl-generator/comment-generator.js +95 -0
- package/src/ddl-generator/create-generator.js +1426 -0
- package/src/ddl-generator/drop-generator.js +194 -0
- package/src/ddl-generator/generator.js +78 -0
- package/src/ddl-generator/grant-generator.js +31 -0
- package/src/ddl-generator/index.js +3 -0
- package/src/ddl-generator/pg-version.js +15 -0
- package/src/ddl-generator/rename-generator.js +88 -0
- package/src/ddl-generator/safe-patterns.js +241 -0
- package/src/differ/change-classifier.js +192 -0
- package/src/differ/dependency-resolver.js +523 -0
- package/src/differ/index.js +12 -0
- package/src/differ/object-matcher.js +510 -0
- package/src/differ/property-differ.js +1134 -0
- package/src/differ/risk-tagger.js +561 -0
- package/src/differ/schema-differ.js +309 -0
- package/src/differ/utils/levenshtein.js +210 -0
- package/src/differ/utils/path-builder.js +198 -0
- package/src/differ/utils/type-compatibility.js +472 -0
- package/src/errors.js +147 -0
- package/src/executor/drift-detector.js +221 -0
- package/src/executor/index.js +7 -0
- package/src/executor/lock-manager.js +308 -0
- package/src/executor/migration-executor.js +1249 -0
- package/src/executor/progress-tracker.js +81 -0
- package/src/executor/recovery-manager.js +47 -0
- package/src/executor/snapshot-manager.js +22 -0
- package/src/executor/sql-splitter.js +120 -0
- package/src/executor/transaction-manager.js +288 -0
- package/src/index.js +483 -0
- package/src/introspection/index.js +4 -0
- package/src/introspection/introspector.js +250 -0
- package/src/introspection/queries/access-methods.js +29 -0
- package/src/introspection/queries/casts.js +38 -0
- package/src/introspection/queries/collations.js +51 -0
- package/src/introspection/queries/comments.js +66 -0
- package/src/introspection/queries/constraints.js +65 -0
- package/src/introspection/queries/conversions.js +39 -0
- package/src/introspection/queries/databases.js +43 -0
- package/src/introspection/queries/default-privileges.js +37 -0
- package/src/introspection/queries/event-triggers.js +43 -0
- package/src/introspection/queries/extensions.js +21 -0
- package/src/introspection/queries/foreign-data.js +136 -0
- package/src/introspection/queries/functions.js +75 -0
- package/src/introspection/queries/grants.js +38 -0
- package/src/introspection/queries/index.js +33 -0
- package/src/introspection/queries/indexes.js +99 -0
- package/src/introspection/queries/inheritance.js +24 -0
- package/src/introspection/queries/multiranges.js +37 -0
- package/src/introspection/queries/operators.js +180 -0
- package/src/introspection/queries/partitions.js +42 -0
- package/src/introspection/queries/pg18-19.js +43 -0
- package/src/introspection/queries/policies.js +35 -0
- package/src/introspection/queries/procedural-languages.js +36 -0
- package/src/introspection/queries/publications.js +114 -0
- package/src/introspection/queries/roles.js +66 -0
- package/src/introspection/queries/rules.js +49 -0
- package/src/introspection/queries/sequences.js +37 -0
- package/src/introspection/queries/statistics.js +74 -0
- package/src/introspection/queries/subscriptions.js +99 -0
- package/src/introspection/queries/tables.js +151 -0
- package/src/introspection/queries/tablespaces.js +36 -0
- package/src/introspection/queries/text-search.js +146 -0
- package/src/introspection/queries/triggers.js +61 -0
- package/src/introspection/queries/types.js +106 -0
- package/src/introspection/queries/views.js +146 -0
- package/src/introspection/translator.js +1283 -0
- package/src/introspection/version-detector.js +25 -0
- package/src/planner/backfill-planner.js +36 -0
- package/src/planner/dry-run.js +21 -0
- package/src/planner/index.js +6 -0
- package/src/planner/migration-planner.js +356 -0
- package/src/planner/smart-migrator.js +82 -0
- package/src/planner/step-sequencer.js +61 -0
- package/src/planner/type-registry.js +47 -0
- package/src/risk/compatibility-checker.js +29 -0
- package/src/risk/data-loss-checker.js +32 -0
- package/src/risk/destructive-checker.js +38 -0
- package/src/risk/index.js +6 -0
- package/src/risk/lock-analyzer.js +39 -0
- package/src/risk/recommendations.js +44 -0
- package/src/risk/risk-engine.js +25 -0
- package/src/storage/index.js +5 -0
- package/src/storage/memory-storage.js +87 -0
- package/src/storage/migration-table.js +423 -0
- package/src/storage/rollback-generator.js +344 -0
- package/src/types/changes.js +136 -0
- package/src/types/connection.js +17 -0
- package/src/types/execution.js +107 -0
- package/src/types/index.js +1 -0
- package/src/types/migration.js +67 -0
- package/src/types/risk.js +32 -0
- package/src/types/schema.d.ts +1004 -0
- package/src/types/schema.js +561 -0
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rollback Generator - Generate rollback SQL for migrations
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export class RollbackGenerator {
|
|
6
|
+
/**
|
|
7
|
+
* Generate rollback SQL from a completed migration's history
|
|
8
|
+
* @param {Object} migration - Migration history record
|
|
9
|
+
* @returns {Array} Rollback SQL statements in reverse order
|
|
10
|
+
*/
|
|
11
|
+
generateRollback(migration) {
|
|
12
|
+
const diff = migration.schema_diff || migration.diff;
|
|
13
|
+
const rollbackSteps = [];
|
|
14
|
+
|
|
15
|
+
if (!diff?.changes) {
|
|
16
|
+
return rollbackSteps;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const changes = [...diff.changes].reverse();
|
|
20
|
+
|
|
21
|
+
for (const change of changes) {
|
|
22
|
+
const undoSQL = this.generateUndoForChange(change);
|
|
23
|
+
if (undoSQL) {
|
|
24
|
+
rollbackSteps.push({
|
|
25
|
+
sql: undoSQL,
|
|
26
|
+
originalChangeId: change.id,
|
|
27
|
+
changeType: change.changeType,
|
|
28
|
+
objectKey: change.objectKey || change.path,
|
|
29
|
+
isTransactional: change.isTransactional !== false,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return rollbackSteps;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Generate undo SQL for a single change
|
|
39
|
+
* @param {Object} change
|
|
40
|
+
* @returns {string|null}
|
|
41
|
+
*/
|
|
42
|
+
generateUndoForChange(change) {
|
|
43
|
+
const changeType = change.changeType;
|
|
44
|
+
const objectType = change.objectType;
|
|
45
|
+
const path = change.objectKey || change.path;
|
|
46
|
+
const schema = change.schema;
|
|
47
|
+
const name = change.name;
|
|
48
|
+
|
|
49
|
+
switch (changeType) {
|
|
50
|
+
case 'CREATE':
|
|
51
|
+
case 'ADD':
|
|
52
|
+
return this.generateUndoForCreate(change, objectType, path);
|
|
53
|
+
|
|
54
|
+
case 'DROP':
|
|
55
|
+
case 'REMOVE':
|
|
56
|
+
return this.generateUndoForDrop(change, objectType, path);
|
|
57
|
+
|
|
58
|
+
case 'ALTER':
|
|
59
|
+
return this.generateUndoForAlter(change, objectType, path);
|
|
60
|
+
|
|
61
|
+
case 'RENAME':
|
|
62
|
+
return this.generateUndoForRename(change, path);
|
|
63
|
+
|
|
64
|
+
case 'RECREATE':
|
|
65
|
+
case 'REPLACE':
|
|
66
|
+
return this.generateUndoForReplace(change, objectType, path);
|
|
67
|
+
|
|
68
|
+
default:
|
|
69
|
+
if (changeType?.startsWith('CREATE')) {
|
|
70
|
+
return this.generateUndoForCreate(change, objectType, path);
|
|
71
|
+
}
|
|
72
|
+
if (changeType?.startsWith('DROP')) {
|
|
73
|
+
return this.generateUndoForDrop(change, objectType, path);
|
|
74
|
+
}
|
|
75
|
+
return `-- CANNOT AUTO-ROLLBACK: Unknown change type "${changeType}" for ${path}`;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Generate undo for CREATE operations
|
|
81
|
+
*/
|
|
82
|
+
generateUndoForCreate(change, objectType, path) {
|
|
83
|
+
switch (objectType) {
|
|
84
|
+
case 'table':
|
|
85
|
+
return `DROP TABLE IF EXISTS ${path} CASCADE;`;
|
|
86
|
+
|
|
87
|
+
case 'index':
|
|
88
|
+
if (change.isConcurrent) {
|
|
89
|
+
return `DROP INDEX CONCURRENTLY IF EXISTS ${path};`;
|
|
90
|
+
}
|
|
91
|
+
return `DROP INDEX IF EXISTS ${path};`;
|
|
92
|
+
|
|
93
|
+
case 'constraint':
|
|
94
|
+
const conTable = change.after?.tableKey || change.schema;
|
|
95
|
+
const conName = change.after?.name || change.name;
|
|
96
|
+
return `ALTER TABLE ${conTable} DROP CONSTRAINT IF EXISTS ${conName};`;
|
|
97
|
+
|
|
98
|
+
case 'view':
|
|
99
|
+
return `DROP VIEW IF EXISTS ${path} CASCADE;`;
|
|
100
|
+
|
|
101
|
+
case 'materializedView':
|
|
102
|
+
return `DROP MATERIALIZED VIEW IF EXISTS ${path} CASCADE;`;
|
|
103
|
+
|
|
104
|
+
case 'function':
|
|
105
|
+
case 'procedure':
|
|
106
|
+
const fnArgs = change.after?.argumentTypes
|
|
107
|
+
? `(${change.after.argumentTypes.join(', ')})`
|
|
108
|
+
: '';
|
|
109
|
+
return `DROP ${objectType === 'procedure' ? 'PROCEDURE' : 'FUNCTION'} IF EXISTS ${path}${fnArgs} CASCADE;`;
|
|
110
|
+
|
|
111
|
+
case 'trigger':
|
|
112
|
+
const trigTable = change.after?.tableName || change.after?.table;
|
|
113
|
+
const trigName = change.after?.name || change.name;
|
|
114
|
+
return `DROP TRIGGER IF EXISTS ${trigName} ON ${trigTable};`;
|
|
115
|
+
|
|
116
|
+
case 'policy':
|
|
117
|
+
const polTable = change.after?.table;
|
|
118
|
+
const polName = change.after?.name || change.name;
|
|
119
|
+
return `DROP POLICY IF EXISTS ${polName} ON ${polTable};`;
|
|
120
|
+
|
|
121
|
+
case 'type':
|
|
122
|
+
return `DROP TYPE IF EXISTS ${path} CASCADE;`;
|
|
123
|
+
|
|
124
|
+
case 'sequence':
|
|
125
|
+
return `DROP SEQUENCE IF EXISTS ${path} CASCADE;`;
|
|
126
|
+
|
|
127
|
+
case 'schema':
|
|
128
|
+
return `DROP SCHEMA IF EXISTS ${change.after?.name || change.name} CASCADE;`;
|
|
129
|
+
|
|
130
|
+
case 'extension':
|
|
131
|
+
return `DROP EXTENSION IF EXISTS ${change.after?.name || change.name} CASCADE;`;
|
|
132
|
+
|
|
133
|
+
case 'rule':
|
|
134
|
+
const ruleTable = change.after?.tableName || change.after?.table;
|
|
135
|
+
const ruleName = change.after?.name || change.name;
|
|
136
|
+
return `DROP RULE IF EXISTS ${ruleName} ON ${ruleTable};`;
|
|
137
|
+
|
|
138
|
+
default:
|
|
139
|
+
return `-- CANNOT AUTO-ROLLBACK: CREATE for object type "${objectType}" on ${path}`;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Generate undo for DROP operations
|
|
145
|
+
*/
|
|
146
|
+
generateUndoForDrop(change, objectType, path) {
|
|
147
|
+
switch (objectType) {
|
|
148
|
+
case 'table':
|
|
149
|
+
return `-- CANNOT ROLLBACK: Table ${path} was dropped. Data cannot be recovered.`;
|
|
150
|
+
|
|
151
|
+
case 'column':
|
|
152
|
+
return `-- CANNOT FULLY ROLLBACK: Column ${path} was dropped. Data cannot be recovered.`;
|
|
153
|
+
|
|
154
|
+
case 'constraint':
|
|
155
|
+
return `-- CANNOT ROLLBACK: Constraint ${path} was dropped. Recreate manually with original definition.`;
|
|
156
|
+
|
|
157
|
+
case 'index':
|
|
158
|
+
return `-- CANNOT ROLLBACK: Index ${path} was dropped. Recreate manually with original definition.`;
|
|
159
|
+
|
|
160
|
+
case 'view':
|
|
161
|
+
case 'materializedView':
|
|
162
|
+
return `-- CANNOT ROLLBACK: ${objectType} ${path} was dropped. Recreate manually.`;
|
|
163
|
+
|
|
164
|
+
case 'function':
|
|
165
|
+
case 'procedure':
|
|
166
|
+
return `-- CANNOT ROLLBACK: ${objectType} ${path} was dropped. Recreate manually.`;
|
|
167
|
+
|
|
168
|
+
case 'trigger':
|
|
169
|
+
case 'policy':
|
|
170
|
+
case 'rule':
|
|
171
|
+
return `-- CANNOT ROLLBACK: ${objectType} ${path} was dropped. Recreate manually.`;
|
|
172
|
+
|
|
173
|
+
default:
|
|
174
|
+
return `-- CANNOT AUTO-ROLLBACK: DROP for object type "${objectType}" on ${path}`;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Generate undo for ALTER operations
|
|
180
|
+
*/
|
|
181
|
+
generateUndoForAlter(change, objectType, path) {
|
|
182
|
+
const property = change.property;
|
|
183
|
+
const currentValue = change.currentValue;
|
|
184
|
+
const desiredValue = change.desiredValue;
|
|
185
|
+
const parts = path.split('.');
|
|
186
|
+
const col = parts.pop();
|
|
187
|
+
const table = parts.join('.');
|
|
188
|
+
|
|
189
|
+
switch (property) {
|
|
190
|
+
case 'dataType':
|
|
191
|
+
case 'type':
|
|
192
|
+
return `ALTER TABLE ${table} ALTER COLUMN "${col}" TYPE ${currentValue};`;
|
|
193
|
+
|
|
194
|
+
case 'isNullable':
|
|
195
|
+
case 'notNull':
|
|
196
|
+
if (desiredValue === true || desiredValue === false && currentValue === true) {
|
|
197
|
+
return `ALTER TABLE ${table} ALTER COLUMN "${col}" DROP NOT NULL;`;
|
|
198
|
+
}
|
|
199
|
+
return `ALTER TABLE ${table} ALTER COLUMN "${col}" SET NOT NULL;`;
|
|
200
|
+
|
|
201
|
+
case 'defaultValue':
|
|
202
|
+
case 'default':
|
|
203
|
+
if (desiredValue === null) {
|
|
204
|
+
return `ALTER TABLE ${table} ALTER COLUMN "${col}" SET DEFAULT ${currentValue};`;
|
|
205
|
+
}
|
|
206
|
+
if (currentValue === null) {
|
|
207
|
+
return `ALTER TABLE ${table} ALTER COLUMN "${col}" DROP DEFAULT;`;
|
|
208
|
+
}
|
|
209
|
+
return `ALTER TABLE ${table} ALTER COLUMN "${col}" SET DEFAULT ${currentValue};`;
|
|
210
|
+
|
|
211
|
+
case 'comment':
|
|
212
|
+
if (currentValue) {
|
|
213
|
+
return `COMMENT ON COLUMN ${path} IS '${currentValue.replace(/'/g, "''")}';`;
|
|
214
|
+
}
|
|
215
|
+
return `COMMENT ON COLUMN ${path} IS NULL;`;
|
|
216
|
+
|
|
217
|
+
default:
|
|
218
|
+
return `-- CANNOT AUTO-ROLLBACK: Property "${property}" change on ${path}`;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Generate undo for RENAME operations
|
|
224
|
+
*/
|
|
225
|
+
generateUndoForRename(change, path) {
|
|
226
|
+
const oldName = change.oldName || change.before?.name;
|
|
227
|
+
const newName = change.newName || change.after?.name;
|
|
228
|
+
const objectType = change.objectType;
|
|
229
|
+
const schema = change.schema;
|
|
230
|
+
|
|
231
|
+
switch (objectType) {
|
|
232
|
+
case 'table':
|
|
233
|
+
return `ALTER TABLE ${schema}.${newName} RENAME TO ${oldName};`;
|
|
234
|
+
|
|
235
|
+
case 'column':
|
|
236
|
+
const tableName = change.tableName || change.after?.tableName;
|
|
237
|
+
return `ALTER TABLE ${schema}.${tableName} RENAME COLUMN ${newName} TO ${oldName};`;
|
|
238
|
+
|
|
239
|
+
case 'index':
|
|
240
|
+
return `ALTER INDEX ${schema}.${newName} RENAME TO ${oldName};`;
|
|
241
|
+
|
|
242
|
+
case 'constraint':
|
|
243
|
+
const conTable = change.tableName || change.after?.tableName;
|
|
244
|
+
return `ALTER TABLE ${schema}.${conTable} RENAME CONSTRAINT ${newName} TO ${oldName};`;
|
|
245
|
+
|
|
246
|
+
default:
|
|
247
|
+
return `-- CANNOT AUTO-ROLLBACK: RENAME for object type "${objectType}" on ${path}`;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Generate undo for REPLACE/RECREATE operations
|
|
253
|
+
*/
|
|
254
|
+
generateUndoForReplace(change, objectType, path) {
|
|
255
|
+
const currentValue = change.currentValue || change.before;
|
|
256
|
+
const before = change.before;
|
|
257
|
+
|
|
258
|
+
switch (objectType) {
|
|
259
|
+
case 'view':
|
|
260
|
+
if (currentValue?.definition || before?.definition) {
|
|
261
|
+
return `CREATE OR REPLACE VIEW ${path} AS ${currentValue?.definition || before.definition};`;
|
|
262
|
+
}
|
|
263
|
+
return `-- CANNOT ROLLBACK: View ${path} was replaced. Original definition not available.`;
|
|
264
|
+
|
|
265
|
+
case 'materializedView':
|
|
266
|
+
return `-- CANNOT FULLY ROLLBACK: Materialized view ${path} was recreated. Data would need to be refreshed.`;
|
|
267
|
+
|
|
268
|
+
case 'function':
|
|
269
|
+
case 'procedure':
|
|
270
|
+
if (currentValue?.source || before?.source) {
|
|
271
|
+
return `CREATE OR REPLACE ${objectType === 'procedure' ? 'PROCEDURE' : 'FUNCTION'} ${path} AS $$${currentValue?.source || before.source}$$;`;
|
|
272
|
+
}
|
|
273
|
+
return `-- CANNOT ROLLBACK: ${objectType} ${path} was replaced. Original source not available.`;
|
|
274
|
+
|
|
275
|
+
case 'trigger':
|
|
276
|
+
const trigTable = change.tableName || before?.tableName;
|
|
277
|
+
const trigName = change.name || before?.name;
|
|
278
|
+
return `DROP TRIGGER IF EXISTS ${trigName} ON ${trigTable};`;
|
|
279
|
+
|
|
280
|
+
case 'policy':
|
|
281
|
+
return `-- CANNOT FULLY ROLLBACK: Policy ${path} was recreated. May need manual restoration.`;
|
|
282
|
+
|
|
283
|
+
default:
|
|
284
|
+
return `-- CANNOT AUTO-ROLLBACK: REPLACE for object type "${objectType}" on ${path}`;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Check if rollback is possible for a change
|
|
290
|
+
* @param {Object} change
|
|
291
|
+
* @returns {{possible: boolean, reason: string}}
|
|
292
|
+
*/
|
|
293
|
+
canRollback(change) {
|
|
294
|
+
const changeType = change.changeType;
|
|
295
|
+
const objectType = change.objectType;
|
|
296
|
+
|
|
297
|
+
if (changeType?.startsWith('DROP')) {
|
|
298
|
+
if (objectType === 'table' || objectType === 'column') {
|
|
299
|
+
return { possible: false, reason: 'Data loss - cannot recover dropped data' };
|
|
300
|
+
}
|
|
301
|
+
return { possible: false, reason: 'Definition lost - need original DDL to recreate' };
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (changeType === 'ADD_ENUM_VALUES') {
|
|
305
|
+
return { possible: false, reason: 'PostgreSQL cannot remove enum values without recreating type' };
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return { possible: true, reason: 'Rollback available' };
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Generate full rollback script with comments
|
|
313
|
+
* @param {Object} migration
|
|
314
|
+
* @returns {string}
|
|
315
|
+
*/
|
|
316
|
+
generateRollbackScript(migration) {
|
|
317
|
+
const steps = this.generateRollback(migration);
|
|
318
|
+
const lines = [];
|
|
319
|
+
|
|
320
|
+
lines.push(`-- Rollback script for migration: ${migration.migration_id || migration.id}`);
|
|
321
|
+
lines.push(`-- Generated at: ${new Date().toISOString()}`);
|
|
322
|
+
lines.push(`-- WARNING: This is a best-effort rollback. Some changes cannot be reversed.`);
|
|
323
|
+
lines.push('');
|
|
324
|
+
lines.push('BEGIN;');
|
|
325
|
+
lines.push('');
|
|
326
|
+
|
|
327
|
+
for (const step of steps) {
|
|
328
|
+
if (step.sql.startsWith('--')) {
|
|
329
|
+
lines.push(`-- Step ${step.originalChangeId}: (cannot rollback)`);
|
|
330
|
+
lines.push(`-- ${step.sql.replace(/^-- /, '')}`);
|
|
331
|
+
} else {
|
|
332
|
+
lines.push(`-- Rollback step ${step.originalChangeId}: ${step.changeType} ${step.objectKey}`);
|
|
333
|
+
lines.push(step.sql);
|
|
334
|
+
}
|
|
335
|
+
lines.push('');
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
lines.push('COMMIT;');
|
|
339
|
+
lines.push('');
|
|
340
|
+
lines.push('-- End of rollback script');
|
|
341
|
+
|
|
342
|
+
return lines.join('\n');
|
|
343
|
+
}
|
|
344
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {'CREATE'|'DROP'|'ALTER'|'RENAME'|'COMMENT'|'ADD_COLUMN'|'DROP_COLUMN'|'ALTER_COLUMN'|'ADD_CONSTRAINT'|'DROP_CONSTRAINT'|'ALTER_CONSTRAINT'|'ADD_INDEX'|'DROP_INDEX'|'ADD_TRIGGER'|'DROP_TRIGGER'|'ADD_POLICY'|'DROP_POLICY'|'ADD_EXTENSION'|'DROP_EXTENSION'|'ADD_FUNCTION'|'DROP_FUNCTION'|'ALTER_FUNCTION'|'ADD_VIEW'|'DROP_VIEW'|'ALTER_VIEW'|'ADD_TYPE'|'DROP_TYPE'|'ALTER_TYPE'|'ADD_SEQUENCE'|'DROP_SEQUENCE'|'ALTER_SEQUENCE'|'ADD_SCHEMA'|'DROP_SCHEMA'|'GRANT'|'REVOKE'|'IMPOSSIBLE_CAST'|'UNSAFE_CAST'|'NARROWING_CAST'|'SAFE_CAST'|'RECREATE_MATERIALIZED_VIEW'|'ADD_ENUM_VALUES'|'REMOVE_ENUM_VALUES'} ChangeType
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {'none'|'low'|'medium'|'high'|'critical'} RiskLevel
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {Object} RiskInfo
|
|
11
|
+
* @property {RiskLevel} level
|
|
12
|
+
* @property {string[]} categories - e.g., ['data_loss', 'lock_hazard']
|
|
13
|
+
* @property {string[]} warnings
|
|
14
|
+
* @property {boolean} safePatternAvailable
|
|
15
|
+
* @property {boolean} requiresDowntime
|
|
16
|
+
* @property {string|null} estimatedDuration
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @typedef {Object} ChangeWarning
|
|
21
|
+
* @property {string} code
|
|
22
|
+
* @property {string} message
|
|
23
|
+
* @property {string} [changeKey]
|
|
24
|
+
* @property {'info'|'warning'|'high'|'critical'} severity
|
|
25
|
+
* @property {string} [reason]
|
|
26
|
+
* @property {string[]} [suggestions]
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @typedef {Object} SchemaChange
|
|
31
|
+
* @property {string} id - Unique change ID
|
|
32
|
+
* @property {ChangeType} changeType - Type of change
|
|
33
|
+
* @property {string} objectType - Object type (table, column, index, etc.)
|
|
34
|
+
* @property {string} objectKey - Fully qualified key (e.g., "public.users.email")
|
|
35
|
+
* @property {string} [schema] - Schema name
|
|
36
|
+
* @property {string} [name] - Object name
|
|
37
|
+
* @property {string} [property] - Property being changed (for ALTERs)
|
|
38
|
+
* @property {*} [before] - Current value
|
|
39
|
+
* @property {*} [after] - Desired value
|
|
40
|
+
* @property {*} [currentValue] - Current property value (property-level diff)
|
|
41
|
+
* @property {*} [desiredValue] - Desired property value (property-level diff)
|
|
42
|
+
* @property {number} track - 1 = structural, 2 = behavioral
|
|
43
|
+
* @property {number} phase - Execution phase (1-25)
|
|
44
|
+
* @property {string} ddlStrategy - How to generate DDL (CREATE, ALTER, DROP, etc.)
|
|
45
|
+
* @property {string[]} dependencies - ObjectKeys this change depends on
|
|
46
|
+
* @property {string[]} dependents - ObjectKeys that depend on this change
|
|
47
|
+
* @property {RiskInfo} risk - Risk assessment
|
|
48
|
+
* @property {boolean} [isNonTransactional] - Cannot run in transaction
|
|
49
|
+
* @property {boolean} [requiresRecreation] - Object must be dropped and recreated
|
|
50
|
+
* @property {boolean} [safePatternAvailable] - Has a non-blocking alternative
|
|
51
|
+
* @property {number|null} [pgVersionMinimum] - Minimum PG version (e.g., 14 means PG 14+)
|
|
52
|
+
* @property {boolean} [dataLossRisk] - This change may lose data
|
|
53
|
+
* @property {string[]} [collateralDamage] - Other objects affected
|
|
54
|
+
* @property {boolean} [isRename] - Is a rename change
|
|
55
|
+
* @property {string} [renameFrom] - Original name if rename
|
|
56
|
+
* @property {string} [renameTo] - New name if rename
|
|
57
|
+
* @property {number} [confidence] - Rename detection confidence (0-1)
|
|
58
|
+
* @property {boolean} [confirmed] - Rename confirmed by user
|
|
59
|
+
* @property {string} [sql] - Generated SQL (filled by DDL generator)
|
|
60
|
+
* @property {Record<string,*>} [metadata] - Additional metadata
|
|
61
|
+
*/
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @typedef {Object} ObjectMatch
|
|
65
|
+
* @property {string} objectType
|
|
66
|
+
* @property {string} key
|
|
67
|
+
* @property {*} [object] - The object definition
|
|
68
|
+
* @property {*} [desired] - Desired snapshot object
|
|
69
|
+
* @property {*} [current] - Current snapshot object
|
|
70
|
+
* @property {number} [similarity] - Match similarity score
|
|
71
|
+
*/
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @typedef {Object} RenameMatch
|
|
75
|
+
* @property {string} objectType
|
|
76
|
+
* @property {string} key
|
|
77
|
+
* @property {string} oldName
|
|
78
|
+
* @property {string} newName
|
|
79
|
+
* @property {number} confidence - 0-1
|
|
80
|
+
* @property {boolean} confirmed - Was rename confirmed by user?
|
|
81
|
+
* @property {*} [object]
|
|
82
|
+
*/
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* @typedef {Object} MatchedObjects
|
|
86
|
+
* @property {ObjectMatch[]} creates - Objects only in desired
|
|
87
|
+
* @property {ObjectMatch[]} drops - Objects only in current
|
|
88
|
+
* @property {ObjectMatch[]} matches - Objects in both (property diff needed)
|
|
89
|
+
* @property {RenameMatch[]} renames - Detected renames (unconfirmed)
|
|
90
|
+
*/
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* @typedef {Object} DependencyNode
|
|
94
|
+
* @property {string} key
|
|
95
|
+
* @property {string} objectType
|
|
96
|
+
* @property {string[]} dependencies
|
|
97
|
+
* @property {string[]} dependents
|
|
98
|
+
*/
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* @typedef {Object} DependencyGraph
|
|
102
|
+
* @property {Map<string, DependencyNode>} nodes
|
|
103
|
+
* @property {string[][]} cycles - Detected cycles
|
|
104
|
+
*/
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* @typedef {Object} ChangeSummary
|
|
108
|
+
* @property {number} totalChanges
|
|
109
|
+
* @property {number} creates
|
|
110
|
+
* @property {number} drops
|
|
111
|
+
* @property {number} alters
|
|
112
|
+
* @property {number} renames
|
|
113
|
+
* @property {number} recreates
|
|
114
|
+
* @property {number} replaces
|
|
115
|
+
* @property {Object} byTrack
|
|
116
|
+
* @property {Object} byPhase
|
|
117
|
+
* @property {Object} byObjectType
|
|
118
|
+
* @property {Object} riskSummary
|
|
119
|
+
* @property {boolean} requiresDowntime
|
|
120
|
+
* @property {string} estimatedDuration
|
|
121
|
+
*/
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* @typedef {Object} SchemaDiff
|
|
125
|
+
* @property {ChangeSummary} summary
|
|
126
|
+
* @property {SchemaChange[]} changes
|
|
127
|
+
* @property {ChangeWarning[]} warnings
|
|
128
|
+
* @property {DependencyGraph} dependencyGraph
|
|
129
|
+
* @property {Object} metadata
|
|
130
|
+
* @property {number} metadata.diffDuration
|
|
131
|
+
* @property {number} metadata.pgVersion
|
|
132
|
+
* @property {string} [metadata.desiredChecksum]
|
|
133
|
+
* @property {string} [metadata.currentChecksum]
|
|
134
|
+
*/
|
|
135
|
+
|
|
136
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {Object} ConnectionConfig
|
|
3
|
+
* @property {string} host
|
|
4
|
+
* @property {number} port
|
|
5
|
+
* @property {string} database
|
|
6
|
+
* @property {string} user
|
|
7
|
+
* @property {string} password
|
|
8
|
+
* @property {boolean|{ca:string,cert:string,key:string}} [ssl]
|
|
9
|
+
* @property {string[]} [schema]
|
|
10
|
+
* @property {Record<string,string>} [options]
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @typedef {ConnectionConfig & {maxConnections?:number,idleTimeoutMs?:number,connectionTimeoutMs?:number}} PoolConfig
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {Object} ExecutionOptions
|
|
3
|
+
* @property {boolean} [dryRun]
|
|
4
|
+
* @property {boolean} [force]
|
|
5
|
+
* @property {number} [statementTimeoutMs]
|
|
6
|
+
* @property {number} [batchSize]
|
|
7
|
+
* @property {number} [sleepMs]
|
|
8
|
+
* @property {function(string, string):void} [onProgress]
|
|
9
|
+
* @property {string} [resumeFrom]
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @typedef {Object} PlanOptions
|
|
14
|
+
* @property {string} [name]
|
|
15
|
+
* @property {string} [description]
|
|
16
|
+
* @property {string} sourceChecksum
|
|
17
|
+
* @property {string} targetChecksum
|
|
18
|
+
* @property {boolean} [safeMode]
|
|
19
|
+
* @property {number} [batchSize]
|
|
20
|
+
* @property {number} [sleepMs]
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @typedef {Object} MigrationOptions
|
|
25
|
+
* @property {string} [name]
|
|
26
|
+
* @property {boolean} [dryRun]
|
|
27
|
+
* @property {boolean} [force]
|
|
28
|
+
* @property {boolean} [safeMode]
|
|
29
|
+
* @property {number} [statementTimeoutMs]
|
|
30
|
+
* @property {number} [batchSize]
|
|
31
|
+
* @property {number} [sleepMs]
|
|
32
|
+
* @property {function(string, string):void} [onProgress]
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @typedef {Object} DdlOptions
|
|
37
|
+
* @property {number} [pgVersion]
|
|
38
|
+
* @property {boolean} [safeMode]
|
|
39
|
+
* @property {boolean} [includeComments]
|
|
40
|
+
* @property {boolean} [includeGrants]
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @typedef {Object} DriftResult
|
|
45
|
+
* @property {boolean} hasDrift
|
|
46
|
+
* @property {import('./changes.js').SchemaChange[]} changes
|
|
47
|
+
* @property {string} [resolution]
|
|
48
|
+
*/
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @typedef {Object} RecoveryResult
|
|
52
|
+
* @property {string} action
|
|
53
|
+
* @property {string} message
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @typedef {Object} MigrationRecord
|
|
58
|
+
* @property {string} id
|
|
59
|
+
* @property {string} name
|
|
60
|
+
* @property {string} checksum
|
|
61
|
+
* @property {'PENDING'|'RUNNING'|'COMPLETED'|'FAILED'|'STALE'} status
|
|
62
|
+
* @property {MigrationPlan} plan
|
|
63
|
+
* @property {MigrationResult} [result]
|
|
64
|
+
* @property {string} [snapshotBefore]
|
|
65
|
+
* @property {string} [snapshotAfter]
|
|
66
|
+
* @property {string} [appliedAt]
|
|
67
|
+
* @property {string} [appliedBy]
|
|
68
|
+
* @property {number} [durationMs]
|
|
69
|
+
* @property {string} createdAt
|
|
70
|
+
*/
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* @typedef {Object} EngineConfig
|
|
74
|
+
* @property {import('./connection.js').PoolConfig} connection
|
|
75
|
+
* @property {StorageProvider} [storage]
|
|
76
|
+
* @property {boolean} [safeMode]
|
|
77
|
+
* @property {number} [defaultBatchSize]
|
|
78
|
+
* @property {number} [defaultSleepMs]
|
|
79
|
+
* @property {number} [statementTimeoutMs]
|
|
80
|
+
*/
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* @typedef {Object} BackfillOptions
|
|
84
|
+
* @property {string} table
|
|
85
|
+
* @property {string} fromColumn
|
|
86
|
+
* @property {string} toColumn
|
|
87
|
+
* @property {string} transform
|
|
88
|
+
* @property {number} batchSize
|
|
89
|
+
* @property {number} rateLimitMs
|
|
90
|
+
* @property {string} pkColumn
|
|
91
|
+
*/
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @typedef {Object} StorageProvider
|
|
95
|
+
* @property {function(string, import('./schema.js').SchemaModel):Promise<void>} saveSnapshot
|
|
96
|
+
* @property {function(string):Promise<import('./schema.js').SchemaModel>} loadSnapshot
|
|
97
|
+
* @property {function():Promise<Array<{checksum:string,timestamp:string}>>} listSnapshots
|
|
98
|
+
* @property {function(MigrationRecord):Promise<void>} saveMigration
|
|
99
|
+
* @property {function(string):Promise<MigrationRecord|null>} getMigration
|
|
100
|
+
* @property {function():Promise<MigrationRecord|null>} getLatestMigration
|
|
101
|
+
* @property {function():Promise<MigrationRecord[]>} getMigrationHistory
|
|
102
|
+
* @property {function(string,string):Promise<void>} saveMigrationFile
|
|
103
|
+
* @property {function(string):Promise<string>} loadMigrationFile
|
|
104
|
+
* @property {function():Promise<string[]>} listMigrationFiles
|
|
105
|
+
*/
|
|
106
|
+
|
|
107
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {Object} MigrationPlan
|
|
3
|
+
* @property {string} id
|
|
4
|
+
* @property {string} name
|
|
5
|
+
* @property {string} [description]
|
|
6
|
+
* @property {string} createdAt
|
|
7
|
+
* @property {string} sourceChecksum
|
|
8
|
+
* @property {string} targetChecksum
|
|
9
|
+
* @property {import('./changes.js').SchemaChange[]} changes
|
|
10
|
+
* @property {MigrationStep[]} steps
|
|
11
|
+
* @property {import('./risk.js').RiskAssessment} riskAssessment
|
|
12
|
+
* @property {number} [estimatedDurationMs]
|
|
13
|
+
* @property {boolean} isReversible
|
|
14
|
+
* @property {MigrationPlan} [reversePlan]
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @typedef {'pre_check'|'advisory_lock'|'pre_structural'|'structural'|'index'|'data_migration'|'constraint'|'post_structural'|'policy'|'grant'|'cleanup'|'snapshot'|'verify'} StepType
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @typedef {Object} MigrationStep
|
|
23
|
+
* @property {string} id
|
|
24
|
+
* @property {StepType} type
|
|
25
|
+
* @property {number} phase
|
|
26
|
+
* @property {string} description
|
|
27
|
+
* @property {string} sql
|
|
28
|
+
* @property {boolean} isTransactional
|
|
29
|
+
* @property {number} [timeoutMs]
|
|
30
|
+
* @property {number} [retryCount]
|
|
31
|
+
* @property {import('./risk.js').RiskLevel} riskLevel
|
|
32
|
+
* @property {string[]} dependencies
|
|
33
|
+
* @property {number} [estimatedRows]
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @typedef {Object} MigrationResult
|
|
38
|
+
* @property {boolean} success
|
|
39
|
+
* @property {string} migrationId
|
|
40
|
+
* @property {number} stepsCompleted
|
|
41
|
+
* @property {number} stepsTotal
|
|
42
|
+
* @property {number} durationMs
|
|
43
|
+
* @property {number} changesApplied
|
|
44
|
+
* @property {MigrationWarning[]} warnings
|
|
45
|
+
* @property {MigrationError[]} errors
|
|
46
|
+
* @property {string} [snapshotBefore]
|
|
47
|
+
* @property {string} [snapshotAfter]
|
|
48
|
+
*/
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @typedef {Object} MigrationWarning
|
|
52
|
+
* @property {string} step
|
|
53
|
+
* @property {string} message
|
|
54
|
+
* @property {import('./risk.js').RiskLevel} severity
|
|
55
|
+
*/
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @typedef {Object} MigrationError
|
|
59
|
+
* @property {string} step
|
|
60
|
+
* @property {string} message
|
|
61
|
+
* @property {string} sql
|
|
62
|
+
* @property {string} [code]
|
|
63
|
+
* @property {boolean} isRecoverable
|
|
64
|
+
* @property {string} [recoveryHint]
|
|
65
|
+
*/
|
|
66
|
+
|
|
67
|
+
export {};
|