relq 1.0.24 → 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 (54) 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/cli/utils/schema-validator.cjs +21 -1
  18. package/dist/cjs/schema-definition/column-types.cjs +63 -10
  19. package/dist/cjs/schema-definition/pg-enum.cjs +10 -0
  20. package/dist/cjs/schema-definition/pg-function.cjs +19 -0
  21. package/dist/cjs/schema-definition/pg-sequence.cjs +22 -1
  22. package/dist/cjs/schema-definition/pg-trigger.cjs +39 -0
  23. package/dist/cjs/schema-definition/pg-view.cjs +17 -0
  24. package/dist/cjs/schema-definition/sql-expressions.cjs +3 -0
  25. package/dist/cjs/schema-definition/table-definition.cjs +4 -0
  26. package/dist/config.d.ts +98 -0
  27. package/dist/esm/cli/commands/commit.js +83 -3
  28. package/dist/esm/cli/commands/import.js +1 -0
  29. package/dist/esm/cli/commands/pull.js +9 -26
  30. package/dist/esm/cli/commands/push.js +49 -9
  31. package/dist/esm/cli/commands/rollback.js +206 -85
  32. package/dist/esm/cli/commands/schema-ast.js +183 -0
  33. package/dist/esm/cli/index.js +6 -0
  34. package/dist/esm/cli/utils/ast-codegen.js +93 -3
  35. package/dist/esm/cli/utils/ast-transformer.js +12 -0
  36. package/dist/esm/cli/utils/change-tracker.js +134 -0
  37. package/dist/esm/cli/utils/commit-manager.js +51 -0
  38. package/dist/esm/cli/utils/migration-generator.js +318 -0
  39. package/dist/esm/cli/utils/repo-manager.js +96 -3
  40. package/dist/esm/cli/utils/schema-diff.js +389 -0
  41. package/dist/esm/cli/utils/schema-hash.js +4 -0
  42. package/dist/esm/cli/utils/schema-to-ast.js +447 -0
  43. package/dist/esm/cli/utils/schema-validator.js +21 -1
  44. package/dist/esm/schema-definition/column-types.js +63 -10
  45. package/dist/esm/schema-definition/pg-enum.js +10 -0
  46. package/dist/esm/schema-definition/pg-function.js +19 -0
  47. package/dist/esm/schema-definition/pg-sequence.js +22 -1
  48. package/dist/esm/schema-definition/pg-trigger.js +39 -0
  49. package/dist/esm/schema-definition/pg-view.js +17 -0
  50. package/dist/esm/schema-definition/sql-expressions.js +3 -0
  51. package/dist/esm/schema-definition/table-definition.js +4 -0
  52. package/dist/index.d.ts +98 -0
  53. package/dist/schema-builder.d.ts +223 -24
  54. package/package.json +1 -1
@@ -0,0 +1,219 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.schemaAstCommand = schemaAstCommand;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const jiti_1 = require("jiti");
40
+ const config_loader_1 = require("../utils/config-loader.cjs");
41
+ const schema_to_ast_1 = require("../utils/schema-to-ast.cjs");
42
+ const spinner_1 = require("../utils/spinner.cjs");
43
+ async function schemaAstCommand(context) {
44
+ const { config, args, flags, projectRoot } = context;
45
+ const spinner = (0, spinner_1.createSpinner)();
46
+ console.log('');
47
+ const options = {
48
+ json: Boolean(flags.json),
49
+ output: flags.output,
50
+ pretty: flags.pretty !== false,
51
+ };
52
+ let schemaPath;
53
+ if (args.length > 0) {
54
+ schemaPath = path.resolve(projectRoot, args[0]);
55
+ }
56
+ else {
57
+ schemaPath = path.resolve(projectRoot, (0, config_loader_1.getSchemaPath)(config ?? undefined));
58
+ }
59
+ const relativePath = path.relative(process.cwd(), schemaPath);
60
+ spinner.start(`Loading schema from ${spinner_1.colors.cyan(relativePath)}`);
61
+ let schemaModule;
62
+ try {
63
+ const jiti = (0, jiti_1.createJiti)(path.dirname(schemaPath), { interopDefault: true });
64
+ const module = await jiti.import(schemaPath);
65
+ if (module && module.default && typeof module.default === 'object') {
66
+ schemaModule = module.default;
67
+ }
68
+ else if (module && typeof module === 'object') {
69
+ schemaModule = module;
70
+ }
71
+ else {
72
+ throw new Error('Schema file must export an object with table/enum definitions');
73
+ }
74
+ spinner.succeed(`Loaded schema from ${spinner_1.colors.cyan(relativePath)}`);
75
+ }
76
+ catch (error) {
77
+ spinner.fail(`Failed to load schema`);
78
+ console.log('');
79
+ console.log(spinner_1.colors.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
80
+ console.log('');
81
+ console.log(spinner_1.colors.yellow('hint:') + ` Make sure ${relativePath} is a valid TypeScript schema file.`);
82
+ console.log('');
83
+ process.exit(1);
84
+ }
85
+ spinner.start('Converting schema to AST');
86
+ let ast;
87
+ try {
88
+ ast = (0, schema_to_ast_1.schemaToAST)(schemaModule);
89
+ spinner.succeed('Converted schema to AST');
90
+ }
91
+ catch (error) {
92
+ spinner.fail('Failed to convert schema');
93
+ console.log('');
94
+ console.log(spinner_1.colors.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
95
+ console.log('');
96
+ process.exit(1);
97
+ }
98
+ const indent = options.pretty ? 2 : 0;
99
+ const output = JSON.stringify(ast, null, indent);
100
+ if (options.output) {
101
+ const outputPath = path.resolve(projectRoot, options.output);
102
+ const outputDir = path.dirname(outputPath);
103
+ if (!fs.existsSync(outputDir)) {
104
+ fs.mkdirSync(outputDir, { recursive: true });
105
+ }
106
+ fs.writeFileSync(outputPath, output, 'utf-8');
107
+ console.log('');
108
+ console.log(`Written AST to ${spinner_1.colors.cyan(options.output)}`);
109
+ }
110
+ else if (options.json) {
111
+ console.log(output);
112
+ }
113
+ else {
114
+ console.log('');
115
+ printAstSummary(ast);
116
+ console.log('');
117
+ console.log(spinner_1.colors.muted(`Use ${spinner_1.colors.cyan('--json')} for full AST output or ${spinner_1.colors.cyan('--output <file>')} to write to file.`));
118
+ }
119
+ console.log('');
120
+ }
121
+ function printAstSummary(ast) {
122
+ console.log(spinner_1.colors.bold('Schema AST Summary'));
123
+ console.log('');
124
+ if (ast.extensions.length > 0) {
125
+ console.log(spinner_1.colors.cyan('Extensions:') + ` ${ast.extensions.length}`);
126
+ for (const ext of ast.extensions) {
127
+ console.log(` ${spinner_1.colors.green('•')} ${ext}`);
128
+ }
129
+ console.log('');
130
+ }
131
+ if (ast.enums.length > 0) {
132
+ console.log(spinner_1.colors.cyan('Enums:') + ` ${ast.enums.length}`);
133
+ for (const e of ast.enums) {
134
+ const tid = e.trackingId ? spinner_1.colors.muted(` [${e.trackingId}]`) : '';
135
+ console.log(` ${spinner_1.colors.green('•')} ${e.name}${tid}: (${e.values.join(', ')})`);
136
+ }
137
+ console.log('');
138
+ }
139
+ if (ast.domains.length > 0) {
140
+ console.log(spinner_1.colors.cyan('Domains:') + ` ${ast.domains.length}`);
141
+ for (const d of ast.domains) {
142
+ const tid = d.trackingId ? spinner_1.colors.muted(` [${d.trackingId}]`) : '';
143
+ console.log(` ${spinner_1.colors.green('•')} ${d.name}${tid}: ${d.baseType}`);
144
+ }
145
+ console.log('');
146
+ }
147
+ if (ast.compositeTypes.length > 0) {
148
+ console.log(spinner_1.colors.cyan('Composite Types:') + ` ${ast.compositeTypes.length}`);
149
+ for (const c of ast.compositeTypes) {
150
+ const tid = c.trackingId ? spinner_1.colors.muted(` [${c.trackingId}]`) : '';
151
+ const attrs = c.attributes.map(a => a.name).join(', ');
152
+ console.log(` ${spinner_1.colors.green('•')} ${c.name}${tid}: (${attrs})`);
153
+ }
154
+ console.log('');
155
+ }
156
+ if (ast.sequences.length > 0) {
157
+ console.log(spinner_1.colors.cyan('Sequences:') + ` ${ast.sequences.length}`);
158
+ for (const s of ast.sequences) {
159
+ const tid = s.trackingId ? spinner_1.colors.muted(` [${s.trackingId}]`) : '';
160
+ console.log(` ${spinner_1.colors.green('•')} ${s.name}${tid}`);
161
+ }
162
+ console.log('');
163
+ }
164
+ if (ast.tables.length > 0) {
165
+ console.log(spinner_1.colors.cyan('Tables:') + ` ${ast.tables.length}`);
166
+ for (const t of ast.tables) {
167
+ const tid = t.trackingId ? spinner_1.colors.muted(` [${t.trackingId}]`) : '';
168
+ console.log(` ${spinner_1.colors.green('•')} ${t.name}${tid}`);
169
+ console.log(` Columns: ${t.columns.length}`);
170
+ for (const col of t.columns.slice(0, 5)) {
171
+ const colTid = col.trackingId ? spinner_1.colors.muted(` [${col.trackingId}]`) : '';
172
+ const pk = col.isPrimaryKey ? spinner_1.colors.yellow(' PK') : '';
173
+ const nullable = col.isNullable ? '' : spinner_1.colors.red(' NOT NULL');
174
+ console.log(` - ${col.name}${colTid}: ${col.type}${pk}${nullable}`);
175
+ }
176
+ if (t.columns.length > 5) {
177
+ console.log(` ${spinner_1.colors.muted(`... and ${t.columns.length - 5} more`)}`);
178
+ }
179
+ if (t.indexes.length > 0) {
180
+ console.log(` Indexes: ${t.indexes.length}`);
181
+ }
182
+ if (t.constraints.length > 0) {
183
+ console.log(` Constraints: ${t.constraints.length}`);
184
+ }
185
+ }
186
+ console.log('');
187
+ }
188
+ if (ast.views.length > 0) {
189
+ console.log(spinner_1.colors.cyan('Views:') + ` ${ast.views.length}`);
190
+ for (const v of ast.views) {
191
+ const tid = v.trackingId ? spinner_1.colors.muted(` [${v.trackingId}]`) : '';
192
+ const mat = v.isMaterialized ? spinner_1.colors.yellow(' (materialized)') : '';
193
+ console.log(` ${spinner_1.colors.green('•')} ${v.name}${tid}${mat}`);
194
+ }
195
+ console.log('');
196
+ }
197
+ if (ast.functions.length > 0) {
198
+ console.log(spinner_1.colors.cyan('Functions:') + ` ${ast.functions.length}`);
199
+ for (const f of ast.functions) {
200
+ const tid = f.trackingId ? spinner_1.colors.muted(` [${f.trackingId}]`) : '';
201
+ const args = f.args.map(a => a.type).join(', ');
202
+ console.log(` ${spinner_1.colors.green('•')} ${f.name}${tid}(${args}) -> ${f.returnType}`);
203
+ }
204
+ console.log('');
205
+ }
206
+ if (ast.triggers.length > 0) {
207
+ console.log(spinner_1.colors.cyan('Triggers:') + ` ${ast.triggers.length}`);
208
+ for (const tr of ast.triggers) {
209
+ const tid = tr.trackingId ? spinner_1.colors.muted(` [${tr.trackingId}]`) : '';
210
+ console.log(` ${spinner_1.colors.green('•')} ${tr.name}${tid} on ${tr.table}`);
211
+ }
212
+ console.log('');
213
+ }
214
+ const total = ast.tables.length + ast.enums.length + ast.views.length +
215
+ ast.functions.length + ast.triggers.length + ast.sequences.length +
216
+ ast.domains.length + ast.compositeTypes.length;
217
+ console.log(spinner_1.colors.bold(`Total: ${total} schema objects`));
218
+ }
219
+ exports.default = schemaAstCommand;
@@ -58,6 +58,7 @@ const tag_1 = require("./commands/tag.cjs");
58
58
  const cherry_pick_1 = require("./commands/cherry-pick.cjs");
59
59
  const remote_1 = require("./commands/remote.cjs");
60
60
  const validate_1 = require("./commands/validate.cjs");
61
+ const schema_ast_1 = require("./commands/schema-ast.cjs");
61
62
  const fs = __importStar(require("fs"));
62
63
  const path = __importStar(require("path"));
63
64
  function loadEnvFile() {
@@ -168,6 +169,7 @@ Other Commands:
168
169
  introspect Parse database schema
169
170
  import <sql-file> Import SQL file to schema
170
171
  export [file] Export schema to SQL file
172
+ schema:ast [file] Convert schema to AST (JSON)
171
173
 
172
174
  Options:
173
175
  --help, -h Show this help
@@ -359,6 +361,10 @@ async function main() {
359
361
  case 'validate':
360
362
  await (0, validate_1.validateCommand)(context);
361
363
  break;
364
+ case 'schema:ast':
365
+ case 'ast':
366
+ await (0, schema_ast_1.schemaAstCommand)(context);
367
+ break;
362
368
  case 'sync':
363
369
  await (0, sync_1.syncCommand)(context);
364
370
  break;
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.resetTrackingIdCounter = resetTrackingIdCounter;
4
+ exports.assignTrackingIds = assignTrackingIds;
5
+ exports.copyTrackingIdsToNormalized = copyTrackingIdsToNormalized;
4
6
  exports.generateTypeScriptFromAST = generateTypeScriptFromAST;
5
7
  const utils_1 = require("./ast/codegen/utils.cjs");
6
8
  const builder_1 = require("./ast/codegen/builder.cjs");
@@ -18,6 +20,96 @@ function generateTrackingId(prefix) {
18
20
  function resetTrackingIdCounter() {
19
21
  trackingIdCounter = 0;
20
22
  }
23
+ function assignTrackingIds(schema) {
24
+ for (const table of schema.tables) {
25
+ if (!table.trackingId) {
26
+ table.trackingId = generateTrackingId('t');
27
+ }
28
+ for (const col of table.columns) {
29
+ if (!col.trackingId) {
30
+ col.trackingId = generateTrackingId('c');
31
+ }
32
+ }
33
+ for (const idx of table.indexes) {
34
+ if (!idx.trackingId) {
35
+ idx.trackingId = generateTrackingId('i');
36
+ }
37
+ }
38
+ }
39
+ for (const e of schema.enums) {
40
+ if (!e.trackingId) {
41
+ e.trackingId = generateTrackingId('t');
42
+ }
43
+ }
44
+ for (const f of schema.functions) {
45
+ if (!f.trackingId) {
46
+ f.trackingId = generateTrackingId('f');
47
+ }
48
+ }
49
+ for (const s of schema.sequences) {
50
+ if (!s.trackingId) {
51
+ s.trackingId = generateTrackingId('t');
52
+ }
53
+ }
54
+ for (const v of schema.views) {
55
+ if (!v.trackingId) {
56
+ v.trackingId = generateTrackingId('t');
57
+ }
58
+ }
59
+ for (const d of schema.domains) {
60
+ if (!d.trackingId) {
61
+ d.trackingId = generateTrackingId('t');
62
+ }
63
+ }
64
+ for (const tr of schema.triggers) {
65
+ if (!tr.trackingId) {
66
+ tr.trackingId = generateTrackingId('t');
67
+ }
68
+ }
69
+ return schema;
70
+ }
71
+ function copyTrackingIdsToNormalized(parsedSchema, normalizedSchema) {
72
+ const tableMap = new Map();
73
+ for (const table of parsedSchema.tables) {
74
+ const columnMap = new Map();
75
+ for (const col of table.columns) {
76
+ if (col.trackingId) {
77
+ columnMap.set(col.name, col.trackingId);
78
+ }
79
+ }
80
+ const indexMap = new Map();
81
+ for (const idx of table.indexes) {
82
+ if (idx.trackingId) {
83
+ indexMap.set(idx.name, idx.trackingId);
84
+ }
85
+ }
86
+ tableMap.set(table.name, {
87
+ trackingId: table.trackingId,
88
+ columns: columnMap,
89
+ indexes: indexMap,
90
+ });
91
+ }
92
+ for (const table of normalizedSchema.tables) {
93
+ const parsed = tableMap.get(table.name);
94
+ if (!parsed)
95
+ continue;
96
+ if (parsed.trackingId) {
97
+ table.trackingId = parsed.trackingId;
98
+ }
99
+ for (const col of table.columns) {
100
+ const colTrackingId = parsed.columns.get(col.name);
101
+ if (colTrackingId) {
102
+ col.trackingId = colTrackingId;
103
+ }
104
+ }
105
+ for (const idx of table.indexes) {
106
+ const idxTrackingId = parsed.indexes.get(idx.name);
107
+ if (idxTrackingId) {
108
+ idx.trackingId = idxTrackingId;
109
+ }
110
+ }
111
+ }
112
+ }
21
113
  function getExplicitFKName(constraintName, tableName, columnName) {
22
114
  if (!constraintName)
23
115
  return undefined;
@@ -85,7 +177,7 @@ function generateColumnCode(col, useCamelCase, enumNames, domainNames, checkOver
85
177
  if (!col.isNullable && !col.isPrimaryKey) {
86
178
  line += '.notNull()';
87
179
  }
88
- const trackingId = generateTrackingId('c');
180
+ const trackingId = col.trackingId || generateTrackingId('c');
89
181
  line += `.$id('${trackingId}')`;
90
182
  return line + commentSuffix;
91
183
  }
@@ -131,7 +223,7 @@ function generateIndexCode(index, useCamelCase) {
131
223
  const includeCols = index.includeColumns.map(c => `table.${useCamelCase ? (0, utils_1.toCamelCase)(c) : c}`).join(', ');
132
224
  line += `.include(${includeCols})`;
133
225
  }
134
- const trackingId = generateTrackingId('i');
226
+ const trackingId = index.trackingId || generateTrackingId('i');
135
227
  line += `.$id('${trackingId}')`;
136
228
  if (index.comment) {
137
229
  line += `.comment('${(0, utils_1.escapeString)(index.comment)}')`;
@@ -400,7 +492,7 @@ function generateTableCode(table, useCamelCase, enumNames, domainNames) {
400
492
  if (tableComment) {
401
493
  optionParts.push(tableComment);
402
494
  }
403
- const tableTrackingId = generateTrackingId('t');
495
+ const tableTrackingId = table.trackingId || generateTrackingId('t');
404
496
  optionParts.push(` $trackingId: '${tableTrackingId}'`);
405
497
  let tableCode;
406
498
  if (optionParts.length > 0) {
@@ -486,6 +486,7 @@ async function parseSQL(sql) {
486
486
  const schema = {
487
487
  enums: [],
488
488
  domains: [],
489
+ compositeTypes: [],
489
490
  sequences: [],
490
491
  tables: [],
491
492
  views: [],
@@ -560,6 +561,7 @@ async function introspectedToParsedSchema(schema) {
560
561
  tables: [],
561
562
  enums: [],
562
563
  domains: [],
564
+ compositeTypes: [],
563
565
  sequences: [],
564
566
  views: [],
565
567
  functions: [],
@@ -581,6 +583,15 @@ async function introspectedToParsedSchema(schema) {
581
583
  checkExpression: d.checkExpression,
582
584
  });
583
585
  }
586
+ for (const ct of schema.compositeTypes || []) {
587
+ parsed.compositeTypes.push({
588
+ name: ct.name,
589
+ attributes: ct.attributes.map(attr => ({
590
+ name: attr.name,
591
+ type: attr.type,
592
+ })),
593
+ });
594
+ }
584
595
  for (const t of schema.tables || []) {
585
596
  const columns = [];
586
597
  for (const c of t.columns) {
@@ -814,6 +825,7 @@ function normalizedToParsedSchema(schema) {
814
825
  checkExpression: d.check,
815
826
  checkName: d.checkName,
816
827
  })),
828
+ compositeTypes: [],
817
829
  sequences: (schema.sequences || []).map(s => ({
818
830
  name: s.name,
819
831
  startValue: s.start,
@@ -39,6 +39,7 @@ exports.createChange = createChange;
39
39
  exports.getChangeDisplayName = getChangeDisplayName;
40
40
  exports.sortChangesByDependency = sortChangesByDependency;
41
41
  exports.generateCombinedSQL = generateCombinedSQL;
42
+ exports.generateDownSQL = generateDownSQL;
42
43
  const crypto = __importStar(require("crypto"));
43
44
  function generateChangeId(type, objectType, objectName, parentName) {
44
45
  const input = `${type}:${objectType}:${parentName || ''}:${objectName}:${Date.now()}`;
@@ -597,3 +598,137 @@ function generateCombinedSQL(changes) {
597
598
  }
598
599
  return lines.join('\n');
599
600
  }
601
+ function generateDownSQL(changes) {
602
+ const reversed = [...changes].reverse();
603
+ const lines = [
604
+ '--',
605
+ '-- DOWN Migration (Rollback)',
606
+ `-- Generated at: ${new Date().toISOString()}`,
607
+ '--',
608
+ '',
609
+ ];
610
+ for (const change of reversed) {
611
+ const downSQL = generateDownSQLForChange(change);
612
+ if (downSQL) {
613
+ lines.push(downSQL);
614
+ lines.push('');
615
+ }
616
+ }
617
+ return lines.join('\n');
618
+ }
619
+ function generateDownSQLForChange(change) {
620
+ const { type, objectType, objectName, parentName, before, after } = change;
621
+ switch (type) {
622
+ case 'CREATE':
623
+ return generateDropSQL(objectType, objectName, parentName);
624
+ case 'DROP':
625
+ if (before) {
626
+ return generateCreateSQL(objectType, objectName, before, parentName);
627
+ }
628
+ return `-- Cannot reverse DROP ${objectType} ${objectName} (no 'before' state saved)`;
629
+ case 'ALTER':
630
+ if (before && after) {
631
+ return generateAlterReverseSQL(objectType, objectName, after, before, parentName);
632
+ }
633
+ return `-- Cannot reverse ALTER ${objectType} ${objectName} (no state saved)`;
634
+ case 'RENAME':
635
+ if (before && after) {
636
+ const oldName = before.name || objectName;
637
+ const newName = after.name || objectName;
638
+ return generateRenameSQL(objectType, newName, oldName, parentName);
639
+ }
640
+ return `-- Cannot reverse RENAME ${objectType} ${objectName} (no state saved)`;
641
+ default:
642
+ return null;
643
+ }
644
+ }
645
+ function generateDropSQL(objectType, objectName, parentName) {
646
+ switch (objectType) {
647
+ case 'TABLE':
648
+ return `DROP TABLE IF EXISTS "${objectName}" CASCADE;`;
649
+ case 'COLUMN':
650
+ return `ALTER TABLE "${parentName}" DROP COLUMN IF EXISTS "${objectName}";`;
651
+ case 'INDEX':
652
+ return `DROP INDEX IF EXISTS "${objectName}";`;
653
+ case 'ENUM':
654
+ return `DROP TYPE IF EXISTS "${objectName}";`;
655
+ case 'SEQUENCE':
656
+ return `DROP SEQUENCE IF EXISTS "${objectName}";`;
657
+ case 'FUNCTION':
658
+ return `DROP FUNCTION IF EXISTS "${objectName}" CASCADE;`;
659
+ case 'TRIGGER':
660
+ return `DROP TRIGGER IF EXISTS "${objectName}" ON "${parentName}";`;
661
+ case 'VIEW':
662
+ return `DROP VIEW IF EXISTS "${objectName}";`;
663
+ case 'CONSTRAINT':
664
+ case 'CHECK':
665
+ case 'FOREIGN_KEY':
666
+ case 'PRIMARY_KEY':
667
+ return `ALTER TABLE "${parentName}" DROP CONSTRAINT IF EXISTS "${objectName}";`;
668
+ default:
669
+ return `-- Cannot generate DROP for ${objectType} ${objectName}`;
670
+ }
671
+ }
672
+ function generateCreateSQL(objectType, objectName, state, parentName) {
673
+ switch (objectType) {
674
+ case 'COLUMN':
675
+ if (state.dataType) {
676
+ const nullable = state.isNullable === false ? ' NOT NULL' : '';
677
+ const defaultVal = state.defaultValue ? ` DEFAULT ${state.defaultValue}` : '';
678
+ return `ALTER TABLE "${parentName}" ADD COLUMN "${objectName}" ${state.dataType}${nullable}${defaultVal};`;
679
+ }
680
+ break;
681
+ case 'INDEX':
682
+ if (state.columns) {
683
+ const unique = state.isUnique ? 'UNIQUE ' : '';
684
+ const cols = Array.isArray(state.columns) ? state.columns.join(', ') : state.columns;
685
+ return `CREATE ${unique}INDEX "${objectName}" ON "${state.tableName || parentName}" (${cols});`;
686
+ }
687
+ break;
688
+ }
689
+ return `-- Cannot reconstruct CREATE ${objectType} ${objectName} from saved state`;
690
+ }
691
+ function generateAlterReverseSQL(objectType, objectName, from, to, parentName) {
692
+ switch (objectType) {
693
+ case 'COLUMN':
694
+ const stmts = [];
695
+ if (from.dataType !== to.dataType && to.dataType) {
696
+ stmts.push(`ALTER TABLE "${parentName}" ALTER COLUMN "${objectName}" TYPE ${to.dataType};`);
697
+ }
698
+ if (from.isNullable !== to.isNullable) {
699
+ if (to.isNullable === false) {
700
+ stmts.push(`ALTER TABLE "${parentName}" ALTER COLUMN "${objectName}" SET NOT NULL;`);
701
+ }
702
+ else {
703
+ stmts.push(`ALTER TABLE "${parentName}" ALTER COLUMN "${objectName}" DROP NOT NULL;`);
704
+ }
705
+ }
706
+ if (from.defaultValue !== to.defaultValue) {
707
+ if (to.defaultValue) {
708
+ stmts.push(`ALTER TABLE "${parentName}" ALTER COLUMN "${objectName}" SET DEFAULT ${to.defaultValue};`);
709
+ }
710
+ else {
711
+ stmts.push(`ALTER TABLE "${parentName}" ALTER COLUMN "${objectName}" DROP DEFAULT;`);
712
+ }
713
+ }
714
+ return stmts.length > 0 ? stmts.join('\n') : `-- No reverse ALTER needed for ${objectName}`;
715
+ default:
716
+ return `-- Cannot reverse ALTER ${objectType} ${objectName}`;
717
+ }
718
+ }
719
+ function generateRenameSQL(objectType, fromName, toName, parentName) {
720
+ switch (objectType) {
721
+ case 'TABLE':
722
+ return `ALTER TABLE "${fromName}" RENAME TO "${toName}";`;
723
+ case 'COLUMN':
724
+ return `ALTER TABLE "${parentName}" RENAME COLUMN "${fromName}" TO "${toName}";`;
725
+ case 'INDEX':
726
+ return `ALTER INDEX "${fromName}" RENAME TO "${toName}";`;
727
+ case 'ENUM':
728
+ return `ALTER TYPE "${fromName}" RENAME TO "${toName}";`;
729
+ case 'SEQUENCE':
730
+ return `ALTER SEQUENCE "${fromName}" RENAME TO "${toName}";`;
731
+ default:
732
+ return `-- Cannot rename ${objectType} ${fromName} to ${toName}`;
733
+ }
734
+ }
@@ -46,9 +46,14 @@ exports.getLatestRemoteCommit = getLatestRemoteCommit;
46
46
  exports.addRemoteCommit = addRemoteCommit;
47
47
  exports.createCommit = createCommit;
48
48
  exports.checkSyncStatus = checkSyncStatus;
49
+ exports.generateASTHash = generateASTHash;
50
+ exports.createCommitFromSchema = createCommitFromSchema;
51
+ exports.createCommitFromSchemaWithRemote = createCommitFromSchemaWithRemote;
49
52
  const fs = __importStar(require("fs"));
50
53
  const path = __importStar(require("path"));
54
+ const crypto = __importStar(require("crypto"));
51
55
  const schema_hash_1 = require("./schema-hash.cjs");
56
+ const schema_to_ast_1 = require("./schema-to-ast.cjs");
52
57
  const RELQ_DIR = '.relq';
53
58
  const COMMITS_FILE = 'commits.json';
54
59
  const HEAD_FILE = 'HEAD';
@@ -237,3 +242,52 @@ async function checkSyncStatus(connection, baseDir = process.cwd()) {
237
242
  const remoteAhead = [...remoteHashes].filter(h => !localHashes.has(h)).length;
238
243
  return { inSync, localHead, remoteHead, localAhead, remoteAhead };
239
244
  }
245
+ function generateASTHash(ast) {
246
+ const normalized = {
247
+ enums: [...ast.enums].sort((a, b) => a.name.localeCompare(b.name)),
248
+ domains: [...ast.domains].sort((a, b) => a.name.localeCompare(b.name)),
249
+ compositeTypes: [...ast.compositeTypes].sort((a, b) => a.name.localeCompare(b.name)),
250
+ sequences: [...ast.sequences].sort((a, b) => a.name.localeCompare(b.name)),
251
+ tables: [...ast.tables].sort((a, b) => a.name.localeCompare(b.name)),
252
+ views: [...ast.views].sort((a, b) => a.name.localeCompare(b.name)),
253
+ functions: [...ast.functions].sort((a, b) => a.name.localeCompare(b.name)),
254
+ triggers: [...ast.triggers].sort((a, b) => a.name.localeCompare(b.name)),
255
+ extensions: [...ast.extensions].sort(),
256
+ };
257
+ const json = JSON.stringify(normalized, null, 0);
258
+ return crypto
259
+ .createHash('sha1')
260
+ .update(json, 'utf8')
261
+ .digest('hex');
262
+ }
263
+ function createCommitFromSchema(schema, author, message, commitLimit = 1000, baseDir = process.cwd()) {
264
+ const parsedSchema = (0, schema_to_ast_1.schemaToAST)(schema);
265
+ const hash = generateASTHash(parsedSchema);
266
+ const parentHash = getLocalHead(baseDir);
267
+ const commit = {
268
+ hash,
269
+ parentHash,
270
+ schemaSnapshot: parsedSchema,
271
+ author,
272
+ message,
273
+ createdAt: new Date(),
274
+ };
275
+ addLocalCommit(commit, commitLimit, baseDir);
276
+ return commit;
277
+ }
278
+ async function createCommitFromSchemaWithRemote(schema, connection, author, message, commitLimit = 1000, baseDir = process.cwd()) {
279
+ const parsedSchema = (0, schema_to_ast_1.schemaToAST)(schema);
280
+ const hash = generateASTHash(parsedSchema);
281
+ const parentHash = getLocalHead(baseDir);
282
+ const commit = {
283
+ hash,
284
+ parentHash,
285
+ schemaSnapshot: parsedSchema,
286
+ author,
287
+ message,
288
+ createdAt: new Date(),
289
+ };
290
+ addLocalCommit(commit, commitLimit, baseDir);
291
+ await addRemoteCommit(connection, commit, commitLimit);
292
+ return commit;
293
+ }