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.
- package/dist/cjs/cli/commands/commit.cjs +80 -0
- package/dist/cjs/cli/commands/import.cjs +1 -0
- package/dist/cjs/cli/commands/pull.cjs +8 -25
- package/dist/cjs/cli/commands/push.cjs +48 -8
- package/dist/cjs/cli/commands/rollback.cjs +205 -84
- package/dist/cjs/cli/commands/schema-ast.cjs +219 -0
- package/dist/cjs/cli/index.cjs +6 -0
- package/dist/cjs/cli/utils/ast-codegen.cjs +95 -3
- package/dist/cjs/cli/utils/ast-transformer.cjs +12 -0
- package/dist/cjs/cli/utils/change-tracker.cjs +135 -0
- package/dist/cjs/cli/utils/commit-manager.cjs +54 -0
- package/dist/cjs/cli/utils/migration-generator.cjs +319 -0
- package/dist/cjs/cli/utils/repo-manager.cjs +99 -3
- package/dist/cjs/cli/utils/schema-diff.cjs +390 -0
- package/dist/cjs/cli/utils/schema-hash.cjs +4 -0
- package/dist/cjs/cli/utils/schema-to-ast.cjs +477 -0
- package/dist/cjs/cli/utils/schema-validator.cjs +21 -1
- package/dist/cjs/schema-definition/column-types.cjs +63 -10
- package/dist/cjs/schema-definition/pg-enum.cjs +10 -0
- package/dist/cjs/schema-definition/pg-function.cjs +19 -0
- package/dist/cjs/schema-definition/pg-sequence.cjs +22 -1
- package/dist/cjs/schema-definition/pg-trigger.cjs +39 -0
- package/dist/cjs/schema-definition/pg-view.cjs +17 -0
- package/dist/cjs/schema-definition/sql-expressions.cjs +3 -0
- package/dist/cjs/schema-definition/table-definition.cjs +4 -0
- package/dist/config.d.ts +98 -0
- package/dist/esm/cli/commands/commit.js +83 -3
- package/dist/esm/cli/commands/import.js +1 -0
- package/dist/esm/cli/commands/pull.js +9 -26
- package/dist/esm/cli/commands/push.js +49 -9
- package/dist/esm/cli/commands/rollback.js +206 -85
- package/dist/esm/cli/commands/schema-ast.js +183 -0
- package/dist/esm/cli/index.js +6 -0
- package/dist/esm/cli/utils/ast-codegen.js +93 -3
- package/dist/esm/cli/utils/ast-transformer.js +12 -0
- package/dist/esm/cli/utils/change-tracker.js +134 -0
- package/dist/esm/cli/utils/commit-manager.js +51 -0
- package/dist/esm/cli/utils/migration-generator.js +318 -0
- package/dist/esm/cli/utils/repo-manager.js +96 -3
- package/dist/esm/cli/utils/schema-diff.js +389 -0
- package/dist/esm/cli/utils/schema-hash.js +4 -0
- package/dist/esm/cli/utils/schema-to-ast.js +447 -0
- package/dist/esm/cli/utils/schema-validator.js +21 -1
- package/dist/esm/schema-definition/column-types.js +63 -10
- package/dist/esm/schema-definition/pg-enum.js +10 -0
- package/dist/esm/schema-definition/pg-function.js +19 -0
- package/dist/esm/schema-definition/pg-sequence.js +22 -1
- package/dist/esm/schema-definition/pg-trigger.js +39 -0
- package/dist/esm/schema-definition/pg-view.js +17 -0
- package/dist/esm/schema-definition/sql-expressions.js +3 -0
- package/dist/esm/schema-definition/table-definition.js +4 -0
- package/dist/index.d.ts +98 -0
- package/dist/schema-builder.d.ts +223 -24
- 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;
|
package/dist/cjs/cli/index.cjs
CHANGED
|
@@ -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
|
+
}
|