relq 1.0.25 → 1.0.26

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.
Files changed (52) hide show
  1. package/dist/cjs/cli/commands/commit.cjs +80 -0
  2. package/dist/cjs/cli/commands/import.cjs +1 -0
  3. package/dist/cjs/cli/commands/pull.cjs +8 -25
  4. package/dist/cjs/cli/commands/push.cjs +48 -8
  5. package/dist/cjs/cli/commands/rollback.cjs +205 -84
  6. package/dist/cjs/cli/commands/schema-ast.cjs +219 -0
  7. package/dist/cjs/cli/index.cjs +6 -0
  8. package/dist/cjs/cli/utils/ast-codegen.cjs +95 -3
  9. package/dist/cjs/cli/utils/ast-transformer.cjs +12 -0
  10. package/dist/cjs/cli/utils/change-tracker.cjs +135 -0
  11. package/dist/cjs/cli/utils/commit-manager.cjs +54 -0
  12. package/dist/cjs/cli/utils/migration-generator.cjs +319 -0
  13. package/dist/cjs/cli/utils/repo-manager.cjs +99 -3
  14. package/dist/cjs/cli/utils/schema-diff.cjs +390 -0
  15. package/dist/cjs/cli/utils/schema-hash.cjs +4 -0
  16. package/dist/cjs/cli/utils/schema-to-ast.cjs +477 -0
  17. package/dist/cjs/schema-definition/column-types.cjs +50 -4
  18. package/dist/cjs/schema-definition/pg-enum.cjs +10 -0
  19. package/dist/cjs/schema-definition/pg-function.cjs +19 -0
  20. package/dist/cjs/schema-definition/pg-sequence.cjs +22 -1
  21. package/dist/cjs/schema-definition/pg-trigger.cjs +39 -0
  22. package/dist/cjs/schema-definition/pg-view.cjs +17 -0
  23. package/dist/cjs/schema-definition/sql-expressions.cjs +3 -0
  24. package/dist/cjs/schema-definition/table-definition.cjs +4 -0
  25. package/dist/config.d.ts +98 -0
  26. package/dist/esm/cli/commands/commit.js +83 -3
  27. package/dist/esm/cli/commands/import.js +1 -0
  28. package/dist/esm/cli/commands/pull.js +9 -26
  29. package/dist/esm/cli/commands/push.js +49 -9
  30. package/dist/esm/cli/commands/rollback.js +206 -85
  31. package/dist/esm/cli/commands/schema-ast.js +183 -0
  32. package/dist/esm/cli/index.js +6 -0
  33. package/dist/esm/cli/utils/ast-codegen.js +93 -3
  34. package/dist/esm/cli/utils/ast-transformer.js +12 -0
  35. package/dist/esm/cli/utils/change-tracker.js +134 -0
  36. package/dist/esm/cli/utils/commit-manager.js +51 -0
  37. package/dist/esm/cli/utils/migration-generator.js +318 -0
  38. package/dist/esm/cli/utils/repo-manager.js +96 -3
  39. package/dist/esm/cli/utils/schema-diff.js +389 -0
  40. package/dist/esm/cli/utils/schema-hash.js +4 -0
  41. package/dist/esm/cli/utils/schema-to-ast.js +447 -0
  42. package/dist/esm/schema-definition/column-types.js +50 -4
  43. package/dist/esm/schema-definition/pg-enum.js +10 -0
  44. package/dist/esm/schema-definition/pg-function.js +19 -0
  45. package/dist/esm/schema-definition/pg-sequence.js +22 -1
  46. package/dist/esm/schema-definition/pg-trigger.js +39 -0
  47. package/dist/esm/schema-definition/pg-view.js +17 -0
  48. package/dist/esm/schema-definition/sql-expressions.js +3 -0
  49. package/dist/esm/schema-definition/table-definition.js +4 -0
  50. package/dist/index.d.ts +98 -0
  51. package/dist/schema-builder.d.ts +223 -24
  52. package/package.json +1 -1
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.generateMigration = generateMigration;
4
+ exports.generateMigrationFromComparison = generateMigrationFromComparison;
4
5
  exports.generateMigrationFile = generateMigrationFile;
5
6
  exports.getNextMigrationNumber = getNextMigrationNumber;
6
7
  exports.generateTimestampedName = generateTimestampedName;
@@ -29,6 +30,324 @@ function generateMigration(diff, options = {}) {
29
30
  }
30
31
  return { up, down };
31
32
  }
33
+ function generateMigrationFromComparison(comparison, options = {}) {
34
+ const { includeDown = true } = options;
35
+ const up = [];
36
+ const down = [];
37
+ for (const rename of comparison.renamed.enums) {
38
+ up.push(`ALTER TYPE "${rename.from}" RENAME TO "${rename.to}";`);
39
+ if (includeDown) {
40
+ down.unshift(`ALTER TYPE "${rename.to}" RENAME TO "${rename.from}";`);
41
+ }
42
+ }
43
+ for (const rename of comparison.renamed.sequences) {
44
+ up.push(`ALTER SEQUENCE "${rename.from}" RENAME TO "${rename.to}";`);
45
+ if (includeDown) {
46
+ down.unshift(`ALTER SEQUENCE "${rename.to}" RENAME TO "${rename.from}";`);
47
+ }
48
+ }
49
+ for (const rename of comparison.renamed.tables) {
50
+ up.push(`ALTER TABLE "${rename.from}" RENAME TO "${rename.to}";`);
51
+ if (includeDown) {
52
+ down.unshift(`ALTER TABLE "${rename.to}" RENAME TO "${rename.from}";`);
53
+ }
54
+ }
55
+ for (const rename of comparison.renamed.columns) {
56
+ up.push(`ALTER TABLE "${rename.table}" RENAME COLUMN "${rename.from}" TO "${rename.to}";`);
57
+ if (includeDown) {
58
+ down.unshift(`ALTER TABLE "${rename.table}" RENAME COLUMN "${rename.to}" TO "${rename.from}";`);
59
+ }
60
+ }
61
+ for (const rename of comparison.renamed.indexes) {
62
+ up.push(`ALTER INDEX "${rename.from}" RENAME TO "${rename.to}";`);
63
+ if (includeDown) {
64
+ down.unshift(`ALTER INDEX "${rename.to}" RENAME TO "${rename.from}";`);
65
+ }
66
+ }
67
+ for (const rename of comparison.renamed.functions) {
68
+ up.push(`ALTER FUNCTION "${rename.from}" RENAME TO "${rename.to}";`);
69
+ if (includeDown) {
70
+ down.unshift(`ALTER FUNCTION "${rename.to}" RENAME TO "${rename.from}";`);
71
+ }
72
+ }
73
+ for (const enumMod of comparison.modified.enums) {
74
+ for (const value of enumMod.changes.added) {
75
+ up.push(`ALTER TYPE "${enumMod.name}" ADD VALUE IF NOT EXISTS '${value}';`);
76
+ }
77
+ if (enumMod.changes.removed.length > 0 && includeDown) {
78
+ down.unshift(`-- Warning: Cannot remove enum values in PostgreSQL: ${enumMod.changes.removed.join(', ')}`);
79
+ }
80
+ }
81
+ for (const colMod of comparison.modified.columns) {
82
+ const { upSQL, downSQL } = generateColumnModification(colMod.table, colMod.column, colMod.changes);
83
+ up.push(...upSQL);
84
+ if (includeDown)
85
+ down.unshift(...downSQL);
86
+ }
87
+ for (const ext of comparison.added.extensions) {
88
+ up.push(`CREATE EXTENSION IF NOT EXISTS "${ext}";`);
89
+ if (includeDown) {
90
+ down.unshift(`DROP EXTENSION IF EXISTS "${ext}";`);
91
+ }
92
+ }
93
+ for (const enumDef of comparison.added.enums) {
94
+ const values = enumDef.values.map(v => `'${v}'`).join(', ');
95
+ up.push(`CREATE TYPE "${enumDef.name}" AS ENUM (${values});`);
96
+ if (includeDown) {
97
+ down.unshift(`DROP TYPE IF EXISTS "${enumDef.name}";`);
98
+ }
99
+ }
100
+ for (const domain of comparison.added.domains) {
101
+ let domainSQL = `CREATE DOMAIN "${domain.name}" AS ${domain.baseType}`;
102
+ if (domain.notNull)
103
+ domainSQL += ' NOT NULL';
104
+ if (domain.defaultValue)
105
+ domainSQL += ` DEFAULT ${domain.defaultValue}`;
106
+ if (domain.checkExpression) {
107
+ const checkName = domain.checkName ? `CONSTRAINT "${domain.checkName}" ` : '';
108
+ domainSQL += ` ${checkName}CHECK (${domain.checkExpression})`;
109
+ }
110
+ up.push(domainSQL + ';');
111
+ if (includeDown) {
112
+ down.unshift(`DROP DOMAIN IF EXISTS "${domain.name}";`);
113
+ }
114
+ }
115
+ for (const seq of comparison.added.sequences) {
116
+ const parts = [`CREATE SEQUENCE "${seq.name}"`];
117
+ if (seq.startValue !== undefined)
118
+ parts.push(`START WITH ${seq.startValue}`);
119
+ if (seq.increment !== undefined)
120
+ parts.push(`INCREMENT BY ${seq.increment}`);
121
+ if (seq.minValue !== undefined)
122
+ parts.push(`MINVALUE ${seq.minValue}`);
123
+ if (seq.maxValue !== undefined)
124
+ parts.push(`MAXVALUE ${seq.maxValue}`);
125
+ if (seq.cache !== undefined)
126
+ parts.push(`CACHE ${seq.cache}`);
127
+ if (seq.cycle)
128
+ parts.push('CYCLE');
129
+ up.push(parts.join(' ') + ';');
130
+ if (includeDown) {
131
+ down.unshift(`DROP SEQUENCE IF EXISTS "${seq.name}";`);
132
+ }
133
+ }
134
+ for (const table of comparison.added.tables) {
135
+ up.push(...generateCreateTableFromParsed(table));
136
+ if (includeDown) {
137
+ down.unshift(`DROP TABLE IF EXISTS "${table.name}" CASCADE;`);
138
+ }
139
+ }
140
+ for (const { table, column } of comparison.added.columns) {
141
+ up.push(`ALTER TABLE "${table}" ADD COLUMN ${generateParsedColumnDef(column)};`);
142
+ if (includeDown) {
143
+ down.unshift(`ALTER TABLE "${table}" DROP COLUMN IF EXISTS "${column.name}";`);
144
+ }
145
+ }
146
+ for (const { table, index } of comparison.added.indexes) {
147
+ up.push(generateCreateIndexFromParsed(table, index));
148
+ if (includeDown) {
149
+ down.unshift(`DROP INDEX IF EXISTS "${index.name}";`);
150
+ }
151
+ }
152
+ for (const view of comparison.added.views) {
153
+ const materialized = view.isMaterialized ? 'MATERIALIZED ' : '';
154
+ up.push(`CREATE ${materialized}VIEW "${view.name}" AS ${view.definition};`);
155
+ if (includeDown) {
156
+ down.unshift(`DROP ${materialized}VIEW IF EXISTS "${view.name}";`);
157
+ }
158
+ }
159
+ for (const func of comparison.added.functions) {
160
+ const argsStr = func.args.map(a => `${a.name || ''} ${a.type}`.trim()).join(', ');
161
+ const volatility = func.volatility || 'VOLATILE';
162
+ up.push(`CREATE OR REPLACE FUNCTION "${func.name}"(${argsStr}) RETURNS ${func.returnType} LANGUAGE ${func.language} ${volatility} AS $$ ${func.body} $$;`);
163
+ if (includeDown) {
164
+ down.unshift(`DROP FUNCTION IF EXISTS "${func.name}"(${func.args.map(a => a.type).join(', ')});`);
165
+ }
166
+ }
167
+ for (const trigger of comparison.added.triggers) {
168
+ const events = trigger.events.join(' OR ');
169
+ const forEach = trigger.forEach || 'ROW';
170
+ up.push(`CREATE TRIGGER "${trigger.name}" ${trigger.timing} ${events} ON "${trigger.table}" FOR EACH ${forEach} EXECUTE FUNCTION ${trigger.functionName}();`);
171
+ if (includeDown) {
172
+ down.unshift(`DROP TRIGGER IF EXISTS "${trigger.name}" ON "${trigger.table}";`);
173
+ }
174
+ }
175
+ for (const trigger of comparison.removed.triggers) {
176
+ up.push(`DROP TRIGGER IF EXISTS "${trigger.name}" ON "${trigger.table}";`);
177
+ if (includeDown) {
178
+ const events = trigger.events.join(' OR ');
179
+ const forEach = trigger.forEach || 'ROW';
180
+ down.unshift(`CREATE TRIGGER "${trigger.name}" ${trigger.timing} ${events} ON "${trigger.table}" FOR EACH ${forEach} EXECUTE FUNCTION ${trigger.functionName}();`);
181
+ }
182
+ }
183
+ for (const func of comparison.removed.functions) {
184
+ const argTypes = func.args.map(a => a.type).join(', ');
185
+ up.push(`DROP FUNCTION IF EXISTS "${func.name}"(${argTypes});`);
186
+ if (includeDown) {
187
+ const argsStr = func.args.map(a => `${a.name || ''} ${a.type}`.trim()).join(', ');
188
+ const volatility = func.volatility || 'VOLATILE';
189
+ down.unshift(`CREATE OR REPLACE FUNCTION "${func.name}"(${argsStr}) RETURNS ${func.returnType} LANGUAGE ${func.language} ${volatility} AS $$ ${func.body} $$;`);
190
+ }
191
+ }
192
+ for (const view of comparison.removed.views) {
193
+ const materialized = view.isMaterialized ? 'MATERIALIZED ' : '';
194
+ up.push(`DROP ${materialized}VIEW IF EXISTS "${view.name}";`);
195
+ if (includeDown) {
196
+ down.unshift(`CREATE ${materialized}VIEW "${view.name}" AS ${view.definition};`);
197
+ }
198
+ }
199
+ for (const { index } of comparison.removed.indexes) {
200
+ up.push(`DROP INDEX IF EXISTS "${index.name}";`);
201
+ }
202
+ for (const { table, column } of comparison.removed.columns) {
203
+ up.push(`ALTER TABLE "${table}" DROP COLUMN IF EXISTS "${column.name}";`);
204
+ if (includeDown) {
205
+ down.unshift(`ALTER TABLE "${table}" ADD COLUMN ${generateParsedColumnDef(column)};`);
206
+ }
207
+ }
208
+ for (const table of comparison.removed.tables) {
209
+ up.push(`DROP TABLE IF EXISTS "${table.name}" CASCADE;`);
210
+ if (includeDown) {
211
+ down.unshift(...generateCreateTableFromParsed(table));
212
+ }
213
+ }
214
+ for (const seq of comparison.removed.sequences) {
215
+ up.push(`DROP SEQUENCE IF EXISTS "${seq.name}";`);
216
+ if (includeDown) {
217
+ const parts = [`CREATE SEQUENCE "${seq.name}"`];
218
+ if (seq.startValue !== undefined)
219
+ parts.push(`START WITH ${seq.startValue}`);
220
+ if (seq.increment !== undefined)
221
+ parts.push(`INCREMENT BY ${seq.increment}`);
222
+ if (seq.minValue !== undefined)
223
+ parts.push(`MINVALUE ${seq.minValue}`);
224
+ if (seq.maxValue !== undefined)
225
+ parts.push(`MAXVALUE ${seq.maxValue}`);
226
+ if (seq.cache !== undefined)
227
+ parts.push(`CACHE ${seq.cache}`);
228
+ if (seq.cycle)
229
+ parts.push('CYCLE');
230
+ down.unshift(parts.join(' ') + ';');
231
+ }
232
+ }
233
+ for (const domain of comparison.removed.domains) {
234
+ up.push(`DROP DOMAIN IF EXISTS "${domain.name}";`);
235
+ if (includeDown) {
236
+ let domainSQL = `CREATE DOMAIN "${domain.name}" AS ${domain.baseType}`;
237
+ if (domain.notNull)
238
+ domainSQL += ' NOT NULL';
239
+ if (domain.defaultValue)
240
+ domainSQL += ` DEFAULT ${domain.defaultValue}`;
241
+ if (domain.checkExpression) {
242
+ const checkName = domain.checkName ? `CONSTRAINT "${domain.checkName}" ` : '';
243
+ domainSQL += ` ${checkName}CHECK (${domain.checkExpression})`;
244
+ }
245
+ down.unshift(domainSQL + ';');
246
+ }
247
+ }
248
+ for (const enumDef of comparison.removed.enums) {
249
+ up.push(`DROP TYPE IF EXISTS "${enumDef.name}";`);
250
+ if (includeDown) {
251
+ const values = enumDef.values.map(v => `'${v}'`).join(', ');
252
+ down.unshift(`CREATE TYPE "${enumDef.name}" AS ENUM (${values});`);
253
+ }
254
+ }
255
+ for (const ext of comparison.removed.extensions) {
256
+ up.push(`DROP EXTENSION IF EXISTS "${ext}";`);
257
+ if (includeDown) {
258
+ down.unshift(`CREATE EXTENSION IF NOT EXISTS "${ext}";`);
259
+ }
260
+ }
261
+ return { up, down };
262
+ }
263
+ function generateColumnModification(tableName, columnName, changes) {
264
+ const upSQL = [];
265
+ const downSQL = [];
266
+ for (const change of changes) {
267
+ switch (change.field) {
268
+ case 'type':
269
+ upSQL.push(`ALTER TABLE "${tableName}" ALTER COLUMN "${columnName}" TYPE ${change.to} USING "${columnName}"::${change.to};`);
270
+ downSQL.push(`ALTER TABLE "${tableName}" ALTER COLUMN "${columnName}" TYPE ${change.from} USING "${columnName}"::${change.from};`);
271
+ break;
272
+ case 'nullable':
273
+ if (change.to) {
274
+ upSQL.push(`ALTER TABLE "${tableName}" ALTER COLUMN "${columnName}" DROP NOT NULL;`);
275
+ downSQL.push(`ALTER TABLE "${tableName}" ALTER COLUMN "${columnName}" SET NOT NULL;`);
276
+ }
277
+ else {
278
+ upSQL.push(`ALTER TABLE "${tableName}" ALTER COLUMN "${columnName}" SET NOT NULL;`);
279
+ downSQL.push(`ALTER TABLE "${tableName}" ALTER COLUMN "${columnName}" DROP NOT NULL;`);
280
+ }
281
+ break;
282
+ case 'default':
283
+ if (change.to !== undefined && change.to !== null) {
284
+ upSQL.push(`ALTER TABLE "${tableName}" ALTER COLUMN "${columnName}" SET DEFAULT ${change.to};`);
285
+ }
286
+ else {
287
+ upSQL.push(`ALTER TABLE "${tableName}" ALTER COLUMN "${columnName}" DROP DEFAULT;`);
288
+ }
289
+ if (change.from !== undefined && change.from !== null) {
290
+ downSQL.push(`ALTER TABLE "${tableName}" ALTER COLUMN "${columnName}" SET DEFAULT ${change.from};`);
291
+ }
292
+ else {
293
+ downSQL.push(`ALTER TABLE "${tableName}" ALTER COLUMN "${columnName}" DROP DEFAULT;`);
294
+ }
295
+ break;
296
+ }
297
+ }
298
+ return { upSQL, downSQL };
299
+ }
300
+ function generateCreateTableFromParsed(table) {
301
+ const sql = [];
302
+ const columnDefs = [];
303
+ for (const col of table.columns) {
304
+ columnDefs.push(` ${generateParsedColumnDef(col)}`);
305
+ }
306
+ let createSQL = `CREATE TABLE "${table.name}" (\n${columnDefs.join(',\n')}\n)`;
307
+ if (table.isPartitioned && table.partitionType && table.partitionKey?.length) {
308
+ createSQL += ` PARTITION BY ${table.partitionType} (${table.partitionKey.join(', ')})`;
309
+ }
310
+ sql.push(createSQL + ';');
311
+ for (const idx of table.indexes) {
312
+ const isPKIndex = table.columns.some(c => c.isPrimaryKey && idx.columns.includes(c.name) && idx.columns.length === 1);
313
+ if (!isPKIndex) {
314
+ sql.push(generateCreateIndexFromParsed(table.name, idx));
315
+ }
316
+ }
317
+ return sql;
318
+ }
319
+ function generateParsedColumnDef(col) {
320
+ const parts = [`"${col.name}"`, col.type];
321
+ if (col.isPrimaryKey) {
322
+ parts.push('PRIMARY KEY');
323
+ }
324
+ if (!col.isNullable && !col.isPrimaryKey) {
325
+ parts.push('NOT NULL');
326
+ }
327
+ if (col.isUnique && !col.isPrimaryKey) {
328
+ parts.push('UNIQUE');
329
+ }
330
+ if (col.defaultValue !== undefined) {
331
+ parts.push(`DEFAULT ${col.defaultValue}`);
332
+ }
333
+ if (col.references) {
334
+ parts.push(`REFERENCES "${col.references.table}"("${col.references.column}")`);
335
+ if (col.references.onDelete) {
336
+ parts.push(`ON DELETE ${col.references.onDelete}`);
337
+ }
338
+ if (col.references.onUpdate) {
339
+ parts.push(`ON UPDATE ${col.references.onUpdate}`);
340
+ }
341
+ }
342
+ return parts.join(' ');
343
+ }
344
+ function generateCreateIndexFromParsed(tableName, idx) {
345
+ const unique = idx.isUnique ? 'UNIQUE ' : '';
346
+ const colList = idx.columns.map(c => `"${c}"`).join(', ');
347
+ const using = idx.method && idx.method !== 'btree' ? ` USING ${idx.method}` : '';
348
+ const where = idx.whereClause ? ` WHERE ${idx.whereClause}` : '';
349
+ return `CREATE ${unique}INDEX IF NOT EXISTS "${idx.name}" ON "${tableName}"${using} (${colList})${where};`;
350
+ }
32
351
  function generateMigrationFile(diff, name, options = {}) {
33
352
  const { message, includeComments = true } = options;
34
353
  const { up, down } = generateMigration(diff, options);
@@ -51,6 +51,7 @@ exports.saveCommit = saveCommit;
51
51
  exports.loadCommit = loadCommit;
52
52
  exports.getAllCommits = getAllCommits;
53
53
  exports.getCommitHistory = getCommitHistory;
54
+ exports.buildTrackingMap = buildTrackingMap;
54
55
  exports.createCommit = createCommit;
55
56
  exports.getStaged = getStaged;
56
57
  exports.setStaged = setStaged;
@@ -75,6 +76,8 @@ exports.cleanupStagedChanges = cleanupStagedChanges;
75
76
  exports.getUnstagedChanges = getUnstagedChanges;
76
77
  exports.hasUncommittedChanges = hasUncommittedChanges;
77
78
  exports.ensureRemoteTable = ensureRemoteTable;
79
+ exports.markCommitAsApplied = markCommitAsApplied;
80
+ exports.markCommitAsRolledBack = markCommitAsRolledBack;
78
81
  exports.fetchRemoteCommits = fetchRemoteCommits;
79
82
  exports.getRemoteHead = getRemoteHead;
80
83
  exports.pushCommit = pushCommit;
@@ -270,7 +273,42 @@ function getCommitHistory(limit = 20, projectRoot = process.cwd()) {
270
273
  }
271
274
  return history;
272
275
  }
273
- function createCommit(schema, author, message, projectRoot = process.cwd()) {
276
+ function buildTrackingMap(ast) {
277
+ const map = {
278
+ tables: {},
279
+ columns: {},
280
+ indexes: {},
281
+ enums: {},
282
+ functions: {},
283
+ };
284
+ for (const table of ast.tables) {
285
+ if (table.trackingId) {
286
+ map.tables[table.trackingId] = table.name;
287
+ }
288
+ for (const col of table.columns) {
289
+ if (col.trackingId) {
290
+ map.columns[col.trackingId] = `${table.name}.${col.name}`;
291
+ }
292
+ }
293
+ for (const idx of table.indexes) {
294
+ if (idx.trackingId) {
295
+ map.indexes[idx.trackingId] = idx.name;
296
+ }
297
+ }
298
+ }
299
+ for (const e of ast.enums) {
300
+ if (e.trackingId) {
301
+ map.enums[e.trackingId] = e.name;
302
+ }
303
+ }
304
+ for (const f of ast.functions) {
305
+ if (f.trackingId) {
306
+ map.functions[f.trackingId] = f.name;
307
+ }
308
+ }
309
+ return map;
310
+ }
311
+ function createCommit(schema, author, message, projectRoot = process.cwd(), options) {
274
312
  const hash = generateHash(schema);
275
313
  const parentHash = getHead(projectRoot);
276
314
  const commit = {
@@ -292,9 +330,18 @@ function createCommit(schema, author, message, projectRoot = process.cwd()) {
292
330
  triggers: schema.triggers?.length || 0,
293
331
  },
294
332
  };
333
+ if (options?.schemaAST) {
334
+ commit.schemaAST = options.schemaAST;
335
+ commit.trackingMap = buildTrackingMap(options.schemaAST);
336
+ }
337
+ if (options?.changes) {
338
+ commit.changes = options.changes;
339
+ }
295
340
  saveCommit(commit, projectRoot);
296
341
  setHead(hash, projectRoot);
297
- clearStaged(projectRoot);
342
+ if (!options?.skipClearStaged) {
343
+ clearStaged(projectRoot);
344
+ }
298
345
  return commit;
299
346
  }
300
347
  function getStaged(projectRoot = process.cwd()) {
@@ -517,12 +564,61 @@ async function ensureRemoteTable(connection) {
517
564
  message TEXT,
518
565
  schema_snapshot JSONB NOT NULL,
519
566
  stats JSONB,
520
- created_at TIMESTAMPTZ DEFAULT NOW()
567
+ created_at TIMESTAMPTZ DEFAULT NOW(),
568
+ applied_at TIMESTAMPTZ,
569
+ rolled_back_at TIMESTAMPTZ
521
570
  );
522
571
 
523
572
  CREATE INDEX IF NOT EXISTS idx_relq_commits_hash ON _relq_commits(hash);
524
573
  CREATE INDEX IF NOT EXISTS idx_relq_commits_created ON _relq_commits(created_at DESC);
525
574
  `);
575
+ await pool.query(`
576
+ DO $$
577
+ BEGIN
578
+ IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = '_relq_commits' AND column_name = 'applied_at') THEN
579
+ ALTER TABLE _relq_commits ADD COLUMN applied_at TIMESTAMPTZ;
580
+ END IF;
581
+ IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = '_relq_commits' AND column_name = 'rolled_back_at') THEN
582
+ ALTER TABLE _relq_commits ADD COLUMN rolled_back_at TIMESTAMPTZ;
583
+ END IF;
584
+ END $$;
585
+ `);
586
+ }
587
+ finally {
588
+ await pool.end();
589
+ }
590
+ }
591
+ async function markCommitAsApplied(connection, commitHash) {
592
+ const { Pool } = await Promise.resolve().then(() => __importStar(require("../../addon/pg/index.cjs")));
593
+ const pool = new Pool({
594
+ host: connection.host,
595
+ port: connection.port || 5432,
596
+ database: connection.database,
597
+ user: connection.user,
598
+ password: connection.password,
599
+ connectionString: connection.url,
600
+ ssl: connection.ssl,
601
+ });
602
+ try {
603
+ await pool.query(`UPDATE _relq_commits SET applied_at = NOW(), rolled_back_at = NULL WHERE hash = $1`, [commitHash]);
604
+ }
605
+ finally {
606
+ await pool.end();
607
+ }
608
+ }
609
+ async function markCommitAsRolledBack(connection, commitHash) {
610
+ const { Pool } = await Promise.resolve().then(() => __importStar(require("../../addon/pg/index.cjs")));
611
+ const pool = new Pool({
612
+ host: connection.host,
613
+ port: connection.port || 5432,
614
+ database: connection.database,
615
+ user: connection.user,
616
+ password: connection.password,
617
+ connectionString: connection.url,
618
+ ssl: connection.ssl,
619
+ });
620
+ try {
621
+ await pool.query(`UPDATE _relq_commits SET rolled_back_at = NOW() WHERE hash = $1`, [commitHash]);
526
622
  }
527
623
  finally {
528
624
  await pool.end();