relq 1.0.5 → 1.0.6
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/add.cjs +252 -12
- package/dist/cjs/cli/commands/commit.cjs +12 -1
- package/dist/cjs/cli/commands/export.cjs +25 -19
- package/dist/cjs/cli/commands/import.cjs +219 -100
- package/dist/cjs/cli/commands/init.cjs +86 -14
- package/dist/cjs/cli/commands/pull.cjs +104 -23
- package/dist/cjs/cli/commands/push.cjs +38 -3
- package/dist/cjs/cli/index.cjs +9 -1
- package/dist/cjs/cli/utils/ast/codegen/builder.cjs +297 -0
- package/dist/cjs/cli/utils/ast/codegen/constraints.cjs +185 -0
- package/dist/cjs/cli/utils/ast/codegen/defaults.cjs +311 -0
- package/dist/cjs/cli/utils/ast/codegen/index.cjs +24 -0
- package/dist/cjs/cli/utils/ast/codegen/type-map.cjs +116 -0
- package/dist/cjs/cli/utils/ast/codegen/utils.cjs +69 -0
- package/dist/cjs/cli/utils/ast/index.cjs +19 -0
- package/dist/cjs/cli/utils/ast/transformer/helpers.cjs +154 -0
- package/dist/cjs/cli/utils/ast/transformer/index.cjs +25 -0
- package/dist/cjs/cli/utils/ast/types.cjs +2 -0
- package/dist/cjs/cli/utils/ast-codegen.cjs +949 -0
- package/dist/cjs/cli/utils/ast-transformer.cjs +916 -0
- package/dist/cjs/cli/utils/change-tracker.cjs +50 -1
- package/dist/cjs/cli/utils/cli-utils.cjs +151 -0
- package/dist/cjs/cli/utils/fast-introspect.cjs +149 -23
- package/dist/cjs/cli/utils/pg-parser.cjs +1 -0
- package/dist/cjs/cli/utils/repo-manager.cjs +121 -4
- package/dist/cjs/cli/utils/schema-comparator.cjs +98 -14
- package/dist/cjs/cli/utils/schema-introspect.cjs +56 -19
- package/dist/cjs/cli/utils/snapshot-manager.cjs +0 -1
- package/dist/cjs/cli/utils/sql-generator.cjs +353 -64
- package/dist/cjs/cli/utils/type-generator.cjs +114 -15
- package/dist/cjs/core/relq-client.cjs +22 -6
- package/dist/cjs/schema-definition/column-types.cjs +149 -13
- package/dist/cjs/schema-definition/defaults.cjs +72 -0
- package/dist/cjs/schema-definition/index.cjs +15 -1
- package/dist/cjs/schema-definition/introspection.cjs +7 -3
- package/dist/cjs/schema-definition/pg-relations.cjs +169 -0
- package/dist/cjs/schema-definition/pg-view.cjs +30 -0
- package/dist/cjs/schema-definition/table-definition.cjs +110 -4
- package/dist/cjs/types/config-types.cjs +13 -4
- package/dist/cjs/utils/aws-dsql.cjs +177 -0
- package/dist/config.d.ts +146 -1
- package/dist/esm/cli/commands/add.js +250 -13
- package/dist/esm/cli/commands/commit.js +12 -1
- package/dist/esm/cli/commands/export.js +25 -19
- package/dist/esm/cli/commands/import.js +221 -102
- package/dist/esm/cli/commands/init.js +86 -14
- package/dist/esm/cli/commands/pull.js +106 -25
- package/dist/esm/cli/commands/push.js +39 -4
- package/dist/esm/cli/index.js +9 -1
- package/dist/esm/cli/utils/ast/codegen/builder.js +291 -0
- package/dist/esm/cli/utils/ast/codegen/constraints.js +176 -0
- package/dist/esm/cli/utils/ast/codegen/defaults.js +305 -0
- package/dist/esm/cli/utils/ast/codegen/index.js +6 -0
- package/dist/esm/cli/utils/ast/codegen/type-map.js +111 -0
- package/dist/esm/cli/utils/ast/codegen/utils.js +60 -0
- package/dist/esm/cli/utils/ast/index.js +3 -0
- package/dist/esm/cli/utils/ast/transformer/helpers.js +141 -0
- package/dist/esm/cli/utils/ast/transformer/index.js +2 -0
- package/dist/esm/cli/utils/ast/types.js +1 -0
- package/dist/esm/cli/utils/ast-codegen.js +945 -0
- package/dist/esm/cli/utils/ast-transformer.js +907 -0
- package/dist/esm/cli/utils/change-tracker.js +50 -1
- package/dist/esm/cli/utils/cli-utils.js +147 -0
- package/dist/esm/cli/utils/fast-introspect.js +149 -23
- package/dist/esm/cli/utils/pg-parser.js +1 -0
- package/dist/esm/cli/utils/repo-manager.js +114 -4
- package/dist/esm/cli/utils/schema-comparator.js +98 -14
- package/dist/esm/cli/utils/schema-introspect.js +56 -19
- package/dist/esm/cli/utils/snapshot-manager.js +0 -1
- package/dist/esm/cli/utils/sql-generator.js +353 -64
- package/dist/esm/cli/utils/type-generator.js +114 -15
- package/dist/esm/core/relq-client.js +23 -7
- package/dist/esm/schema-definition/column-types.js +146 -12
- package/dist/esm/schema-definition/defaults.js +69 -0
- package/dist/esm/schema-definition/index.js +3 -0
- package/dist/esm/schema-definition/introspection.js +7 -3
- package/dist/esm/schema-definition/pg-relations.js +161 -0
- package/dist/esm/schema-definition/pg-view.js +24 -0
- package/dist/esm/schema-definition/table-definition.js +110 -4
- package/dist/esm/types/config-types.js +12 -4
- package/dist/esm/utils/aws-dsql.js +139 -0
- package/dist/index.d.ts +159 -1
- package/dist/schema-builder.d.ts +1314 -32
- package/package.json +1 -1
|
@@ -32,10 +32,14 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
32
32
|
return result;
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
35
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
39
|
exports.addCommand = addCommand;
|
|
37
40
|
exports.getRelatedChanges = getRelatedChanges;
|
|
38
41
|
const fs = __importStar(require("fs"));
|
|
42
|
+
const strip_comments_1 = __importDefault(require("strip-comments"));
|
|
39
43
|
const spinner_1 = require("../utils/spinner.cjs");
|
|
40
44
|
const relqignore_1 = require("../utils/relqignore.cjs");
|
|
41
45
|
const config_1 = require("../../config/config.cjs");
|
|
@@ -47,7 +51,8 @@ function parseSchemaFileForComparison(schemaPath) {
|
|
|
47
51
|
if (!fs.existsSync(schemaPath)) {
|
|
48
52
|
return null;
|
|
49
53
|
}
|
|
50
|
-
const
|
|
54
|
+
const rawContent = fs.readFileSync(schemaPath, 'utf-8');
|
|
55
|
+
const content = (0, strip_comments_1.default)(rawContent);
|
|
51
56
|
const tables = [];
|
|
52
57
|
const tableStartRegex = /defineTable\s*\(\s*['"]([^'"]+)['"],\s*\{/g;
|
|
53
58
|
let tableStartMatch;
|
|
@@ -87,15 +92,21 @@ function parseSchemaFileForComparison(schemaPath) {
|
|
|
87
92
|
const tsToDbNameMap = new Map();
|
|
88
93
|
const lines = columnsBlock.split('\n');
|
|
89
94
|
let currentColDef = '';
|
|
95
|
+
let pendingJsDocComment = null;
|
|
90
96
|
for (const line of lines) {
|
|
91
97
|
const trimmed = line.trim();
|
|
98
|
+
const jsDocMatch = trimmed.match(/^\/\*\*\s*(.*?)\s*\*\/$/);
|
|
99
|
+
if (jsDocMatch) {
|
|
100
|
+
pendingJsDocComment = jsDocMatch[1];
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
92
103
|
if (trimmed.startsWith('//') || trimmed.startsWith('/*') || trimmed.startsWith('*'))
|
|
93
104
|
continue;
|
|
94
105
|
currentColDef += ' ' + trimmed;
|
|
95
106
|
if (trimmed.endsWith(',') || trimmed.endsWith(')')) {
|
|
96
107
|
const colDef = currentColDef.trim();
|
|
97
108
|
currentColDef = '';
|
|
98
|
-
const typePattern = 'varchar|text|uuid|integer|bigint|boolean|timestamp|date|jsonb|json|numeric|serial|bigserial|smallserial|tsvector|smallint|real|doublePrecision|char|inet|cidr|macaddr|macaddr8|interval|time|point|line|lseg|box|path|polygon|circle|bytea|bit|varbit|money|xml|oid';
|
|
109
|
+
const typePattern = 'varchar|text|uuid|integer|bigint|boolean|timestamptz|timestamp|date|jsonb|json|numeric|serial|bigserial|smallserial|tsvector|smallint|real|doublePrecision|char|inet|cidr|macaddr|macaddr8|interval|timetz|time|point|line|lseg|box|path|polygon|circle|bytea|bit|varbit|money|xml|oid|enumType|domainType';
|
|
99
110
|
const colMatch = colDef.match(new RegExp(`^(\\w+):\\s*(${typePattern})`));
|
|
100
111
|
if (!colMatch)
|
|
101
112
|
continue;
|
|
@@ -111,13 +122,17 @@ function parseSchemaFileForComparison(schemaPath) {
|
|
|
111
122
|
if (bigintMatch) {
|
|
112
123
|
defaultValue = bigintMatch[1];
|
|
113
124
|
}
|
|
114
|
-
const
|
|
125
|
+
const bigintLiteralMatch = !defaultValue && colDef.match(/\.default\(\s*(-?\d+)n\s*\)/);
|
|
126
|
+
if (bigintLiteralMatch) {
|
|
127
|
+
defaultValue = bigintLiteralMatch[1];
|
|
128
|
+
}
|
|
129
|
+
const funcDefaultMatch = !defaultValue && colDef.match(/\.default\(\s*(?:DEFAULT\.)?(genRandomUuid|now|currentDate|currentTimestamp|emptyArray|emptyObject|emptyJsonb)\s*\(\s*\)\s*\)/);
|
|
115
130
|
if (funcDefaultMatch) {
|
|
116
131
|
const funcName = funcDefaultMatch[1];
|
|
117
132
|
if (funcName === 'emptyArray') {
|
|
118
133
|
defaultValue = isJsonbColumn ? "'[]'::jsonb" : "'{}'::text[]";
|
|
119
134
|
}
|
|
120
|
-
else if (funcName === 'emptyObject') {
|
|
135
|
+
else if (funcName === 'emptyObject' || funcName === 'emptyJsonb') {
|
|
121
136
|
defaultValue = "'{}'::jsonb";
|
|
122
137
|
}
|
|
123
138
|
else {
|
|
@@ -182,10 +197,22 @@ function parseSchemaFileForComparison(schemaPath) {
|
|
|
182
197
|
if (commentMatch) {
|
|
183
198
|
comment = commentMatch[2];
|
|
184
199
|
}
|
|
200
|
+
else if (pendingJsDocComment) {
|
|
201
|
+
comment = pendingJsDocComment;
|
|
202
|
+
}
|
|
203
|
+
pendingJsDocComment = null;
|
|
185
204
|
if (colDef.includes('.identity()')) {
|
|
186
205
|
defaultValue = defaultValue || 'GENERATED BY DEFAULT AS IDENTITY';
|
|
187
206
|
}
|
|
188
|
-
|
|
207
|
+
let trackingId = undefined;
|
|
208
|
+
const trackingIdMatch = colDef.match(/\.\$id\(\s*['"]([^'"]+)['"]\s*\)/);
|
|
209
|
+
if (trackingIdMatch) {
|
|
210
|
+
trackingId = trackingIdMatch[1];
|
|
211
|
+
}
|
|
212
|
+
const hasArrayModifier = colDef.includes('.array()');
|
|
213
|
+
const hasEmptyArrayDefault = colDef.includes('emptyArray()');
|
|
214
|
+
const isJsonbType = type === 'jsonb' || type === 'json';
|
|
215
|
+
const isArray = hasArrayModifier || (hasEmptyArrayDefault && !isJsonbType);
|
|
189
216
|
columns.push({
|
|
190
217
|
name: dbColName,
|
|
191
218
|
dataType: isArray ? `${type}[]` : type,
|
|
@@ -198,6 +225,7 @@ function parseSchemaFileForComparison(schemaPath) {
|
|
|
198
225
|
scale: null,
|
|
199
226
|
references: null,
|
|
200
227
|
comment,
|
|
228
|
+
trackingId,
|
|
201
229
|
});
|
|
202
230
|
}
|
|
203
231
|
}
|
|
@@ -210,8 +238,13 @@ function parseSchemaFileForComparison(schemaPath) {
|
|
|
210
238
|
const tsColName = c.trim().replace(/table\.\s*/, '');
|
|
211
239
|
return tsToDbNameMap.get(tsColName) || tsColName;
|
|
212
240
|
});
|
|
213
|
-
const
|
|
214
|
-
|
|
241
|
+
const indexStart = optionsBlock.indexOf(`index('${indexName}')`);
|
|
242
|
+
const indexLine = indexStart >= 0 ? optionsBlock.substring(indexStart).split('\n')[0] : '';
|
|
243
|
+
const isUnique = indexLine.includes('.unique()');
|
|
244
|
+
const commentMatch = indexLine.match(/\.comment\(\s*(['"])([^'"]*)\1\s*\)/);
|
|
245
|
+
const comment = commentMatch ? commentMatch[2] : null;
|
|
246
|
+
const trackingIdMatch = indexLine.match(/\.\$id\(\s*(['"])([^'"]+)\1\s*\)/);
|
|
247
|
+
const trackingId = trackingIdMatch ? trackingIdMatch[2] : undefined;
|
|
215
248
|
indexes.push({
|
|
216
249
|
name: indexName,
|
|
217
250
|
columns: indexCols,
|
|
@@ -221,6 +254,8 @@ function parseSchemaFileForComparison(schemaPath) {
|
|
|
221
254
|
definition: '',
|
|
222
255
|
whereClause: null,
|
|
223
256
|
expression: null,
|
|
257
|
+
comment,
|
|
258
|
+
trackingId,
|
|
224
259
|
});
|
|
225
260
|
}
|
|
226
261
|
const checkRegexLegacy = /check\s*\(\s*['"]([^'"]+)['"]\s*,\s*sql`([^`]+)`\s*\)/g;
|
|
@@ -271,6 +306,83 @@ function parseSchemaFileForComparison(schemaPath) {
|
|
|
271
306
|
const dbPartitionKey = tsToDbNameMap.get(tsPartitionKey) || tsPartitionKey;
|
|
272
307
|
partitionKey = [dbPartitionKey];
|
|
273
308
|
}
|
|
309
|
+
const pkColumns = columns.filter(c => c.isPrimaryKey).map(c => c.name);
|
|
310
|
+
if (pkColumns.length > 0) {
|
|
311
|
+
const pkName = `${tableName}_pkey`;
|
|
312
|
+
if (!constraints.some(c => c.type === 'PRIMARY KEY')) {
|
|
313
|
+
constraints.push({
|
|
314
|
+
name: pkName,
|
|
315
|
+
type: 'PRIMARY KEY',
|
|
316
|
+
columns: pkColumns,
|
|
317
|
+
definition: '',
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
for (const col of columns) {
|
|
322
|
+
if (col.isUnique && !col.isPrimaryKey) {
|
|
323
|
+
const uniqueName = `${tableName}_${col.name}_key`;
|
|
324
|
+
col.isUnique = false;
|
|
325
|
+
if (!constraints.some(c => c.name === uniqueName)) {
|
|
326
|
+
constraints.push({
|
|
327
|
+
name: uniqueName,
|
|
328
|
+
type: 'UNIQUE',
|
|
329
|
+
columns: [col.name],
|
|
330
|
+
definition: '',
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
const uniqueConstraintRegex = /constraint\.unique\s*\(\s*\{\s*name:\s*['"]([^'"]+)['"]\s*,\s*columns:\s*\[([^\]]+)\]/g;
|
|
336
|
+
let uniqueMatch;
|
|
337
|
+
while ((uniqueMatch = uniqueConstraintRegex.exec(optionsBlock)) !== null) {
|
|
338
|
+
const constraintName = uniqueMatch[1];
|
|
339
|
+
const colsStr = uniqueMatch[2];
|
|
340
|
+
const uniqueCols = colsStr.split(',').map(c => {
|
|
341
|
+
const tsColName = c.trim().replace(/table\.\s*/, '');
|
|
342
|
+
return tsToDbNameMap.get(tsColName) || tsColName;
|
|
343
|
+
});
|
|
344
|
+
if (!constraints.some(c => c.name === constraintName)) {
|
|
345
|
+
constraints.push({
|
|
346
|
+
name: constraintName,
|
|
347
|
+
type: 'UNIQUE',
|
|
348
|
+
columns: uniqueCols,
|
|
349
|
+
definition: '',
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
const pkConstraintRegex = /constraint\.primaryKey\s*\(\s*\{\s*name:\s*['"]([^'"]+)['"]\s*,\s*columns:\s*\[([^\]]+)\]/g;
|
|
354
|
+
let pkMatch;
|
|
355
|
+
while ((pkMatch = pkConstraintRegex.exec(optionsBlock)) !== null) {
|
|
356
|
+
const constraintName = pkMatch[1];
|
|
357
|
+
const colsStr = pkMatch[2];
|
|
358
|
+
const pkCols = colsStr.split(',').map(c => {
|
|
359
|
+
const tsColName = c.trim().replace(/table\.\s*/, '');
|
|
360
|
+
return tsToDbNameMap.get(tsColName) || tsColName;
|
|
361
|
+
});
|
|
362
|
+
const existingPkIdx = constraints.findIndex(c => c.type === 'PRIMARY KEY');
|
|
363
|
+
if (existingPkIdx >= 0) {
|
|
364
|
+
constraints[existingPkIdx] = {
|
|
365
|
+
name: constraintName,
|
|
366
|
+
type: 'PRIMARY KEY',
|
|
367
|
+
columns: pkCols,
|
|
368
|
+
definition: '',
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
else {
|
|
372
|
+
constraints.push({
|
|
373
|
+
name: constraintName,
|
|
374
|
+
type: 'PRIMARY KEY',
|
|
375
|
+
columns: pkCols,
|
|
376
|
+
definition: '',
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
for (const dbColName of pkCols) {
|
|
380
|
+
const col = columns.find(c => c.name === dbColName);
|
|
381
|
+
if (col) {
|
|
382
|
+
col.isPrimaryKey = true;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
274
386
|
tables.push({
|
|
275
387
|
name: tableName,
|
|
276
388
|
schema: 'public',
|
|
@@ -306,6 +418,80 @@ function parseSchemaFileForComparison(schemaPath) {
|
|
|
306
418
|
}
|
|
307
419
|
}
|
|
308
420
|
}
|
|
421
|
+
const toSnakeCase = (str) => {
|
|
422
|
+
return str.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`);
|
|
423
|
+
};
|
|
424
|
+
const pgRelationsStart = content.indexOf('pgRelations(');
|
|
425
|
+
let relationsBlock = '';
|
|
426
|
+
if (pgRelationsStart !== -1) {
|
|
427
|
+
const arrowStart = content.indexOf('=> ({', pgRelationsStart);
|
|
428
|
+
if (arrowStart !== -1) {
|
|
429
|
+
const blockStart = arrowStart + 5;
|
|
430
|
+
let depth = 1;
|
|
431
|
+
let i = blockStart;
|
|
432
|
+
while (i < content.length && depth > 0) {
|
|
433
|
+
if (content[i] === '{')
|
|
434
|
+
depth++;
|
|
435
|
+
else if (content[i] === '}')
|
|
436
|
+
depth--;
|
|
437
|
+
i++;
|
|
438
|
+
}
|
|
439
|
+
relationsBlock = content.slice(blockStart, i - 1);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
if (relationsBlock) {
|
|
443
|
+
const tableEntryRegex = /(\w+):\s*tables\.\1\s*\(\s*\([^)]*\)\s*=>\s*\(\{/g;
|
|
444
|
+
let tableMatch;
|
|
445
|
+
while ((tableMatch = tableEntryRegex.exec(relationsBlock)) !== null) {
|
|
446
|
+
const tableTsName = tableMatch[1];
|
|
447
|
+
const tableDbName = toSnakeCase(tableTsName);
|
|
448
|
+
const entryStart = tableMatch.index + tableMatch[0].length;
|
|
449
|
+
let depth = 1;
|
|
450
|
+
let j = entryStart;
|
|
451
|
+
while (j < relationsBlock.length && depth > 0) {
|
|
452
|
+
if (relationsBlock[j] === '{')
|
|
453
|
+
depth++;
|
|
454
|
+
else if (relationsBlock[j] === '}')
|
|
455
|
+
depth--;
|
|
456
|
+
j++;
|
|
457
|
+
}
|
|
458
|
+
const tableRelationsContent = relationsBlock.slice(entryStart, j - 1);
|
|
459
|
+
const table = tables.find(t => t.name === tableDbName);
|
|
460
|
+
if (!table)
|
|
461
|
+
continue;
|
|
462
|
+
const fkRegex = /(\w+):\s*r\.referenceTo\.(\w+)\s*\(\s*\w*\s*=>\s*\(\{([^}]*)\}\)/g;
|
|
463
|
+
let fkMatch;
|
|
464
|
+
while ((fkMatch = fkRegex.exec(tableRelationsContent)) !== null) {
|
|
465
|
+
const colTsName = fkMatch[1];
|
|
466
|
+
const refTableTsName = fkMatch[2];
|
|
467
|
+
const fkOptionsStr = fkMatch[3];
|
|
468
|
+
const colDbName = toSnakeCase(colTsName);
|
|
469
|
+
const refTableDbName = toSnakeCase(refTableTsName);
|
|
470
|
+
let onDelete;
|
|
471
|
+
let onUpdate;
|
|
472
|
+
const nameMatch = fkOptionsStr.match(/name:\s*['"]([^'"]+)['"]/);
|
|
473
|
+
const onDeleteMatch = fkOptionsStr.match(/onDelete:\s*['"]([^'"]+)['"]/);
|
|
474
|
+
const onUpdateMatch = fkOptionsStr.match(/onUpdate:\s*['"]([^'"]+)['"]/);
|
|
475
|
+
if (onDeleteMatch)
|
|
476
|
+
onDelete = onDeleteMatch[1];
|
|
477
|
+
if (onUpdateMatch)
|
|
478
|
+
onUpdate = onUpdateMatch[1];
|
|
479
|
+
const fkName = nameMatch ? nameMatch[1] : `${tableDbName}_${colDbName}_fkey`;
|
|
480
|
+
if (!table.constraints.some(c => c.name === fkName || (c.type === 'FOREIGN KEY' && c.columns?.includes(colDbName)))) {
|
|
481
|
+
table.constraints.push({
|
|
482
|
+
name: fkName,
|
|
483
|
+
type: 'FOREIGN KEY',
|
|
484
|
+
columns: [colDbName],
|
|
485
|
+
definition: '',
|
|
486
|
+
referencedTable: refTableDbName,
|
|
487
|
+
referencedColumns: ['id'],
|
|
488
|
+
onDelete,
|
|
489
|
+
onUpdate,
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
309
495
|
return {
|
|
310
496
|
tables,
|
|
311
497
|
enums,
|
|
@@ -338,6 +524,7 @@ function snapshotToDatabaseSchema(snapshot) {
|
|
|
338
524
|
scale: null,
|
|
339
525
|
references: null,
|
|
340
526
|
comment: c.comment || null,
|
|
527
|
+
trackingId: c.trackingId,
|
|
341
528
|
})),
|
|
342
529
|
indexes: (t.indexes || []).map(i => ({
|
|
343
530
|
name: i.name,
|
|
@@ -348,6 +535,7 @@ function snapshotToDatabaseSchema(snapshot) {
|
|
|
348
535
|
definition: i.definition || '',
|
|
349
536
|
whereClause: i.whereClause || null,
|
|
350
537
|
expression: null,
|
|
538
|
+
trackingId: i.trackingId,
|
|
351
539
|
})),
|
|
352
540
|
constraints: (t.constraints || []).map(c => ({
|
|
353
541
|
name: c.name,
|
|
@@ -376,6 +564,57 @@ function snapshotToDatabaseSchema(snapshot) {
|
|
|
376
564
|
extensions: (snapshot.extensions || []).map(e => typeof e === 'string' ? e : e.name),
|
|
377
565
|
};
|
|
378
566
|
}
|
|
567
|
+
let trackingIdCounter = 0;
|
|
568
|
+
function generateTrackingId(prefix) {
|
|
569
|
+
trackingIdCounter++;
|
|
570
|
+
const base = (Date.now().toString(36) + trackingIdCounter.toString(36)).slice(-5);
|
|
571
|
+
return prefix + base.padStart(5, '0');
|
|
572
|
+
}
|
|
573
|
+
function injectTrackingIds(schemaPath) {
|
|
574
|
+
if (!fs.existsSync(schemaPath)) {
|
|
575
|
+
return 0;
|
|
576
|
+
}
|
|
577
|
+
let content = fs.readFileSync(schemaPath, 'utf-8');
|
|
578
|
+
let injectedCount = 0;
|
|
579
|
+
const columnLineRegex = /^(\s*)(\w+):\s*(varchar|text|uuid|integer|bigint|boolean|timestamptz|timestamp|date|jsonb|json|numeric|serial|bigserial|smallserial|tsvector|smallint|real|doublePrecision|char|inet|cidr|macaddr|macaddr8|interval|timetz|time|point|line|lseg|box|path|polygon|circle|bytea|bit|varbit|money|xml|oid|enumType|domainType)\s*\([^)]*\)([^,\n]*)(,?)$/gm;
|
|
580
|
+
content = content.replace(columnLineRegex, (match, indent, colName, type, modifiers, comma) => {
|
|
581
|
+
if (modifiers.includes('.$id(')) {
|
|
582
|
+
return match;
|
|
583
|
+
}
|
|
584
|
+
const commentMatch = modifiers.match(/^(.*)(\.\s*comment\s*\([^)]+\))$/);
|
|
585
|
+
if (commentMatch) {
|
|
586
|
+
const beforeComment = commentMatch[1];
|
|
587
|
+
const comment = commentMatch[2];
|
|
588
|
+
const trackingId = generateTrackingId('c');
|
|
589
|
+
injectedCount++;
|
|
590
|
+
return `${indent}${colName}: ${type}(${match.split('(')[1].split(')')[0]})${beforeComment}.$id('${trackingId}')${comment}${comma}`;
|
|
591
|
+
}
|
|
592
|
+
const trackingId = generateTrackingId('c');
|
|
593
|
+
injectedCount++;
|
|
594
|
+
return `${indent}${colName}: ${type}(${match.split('(')[1].split(')')[0]})${modifiers}.$id('${trackingId}')${comma}`;
|
|
595
|
+
});
|
|
596
|
+
const indexLineRegex = /^(\s*)(index\s*\(\s*['"][^'"]+['"]\s*\)\s*\.on\s*\([^)]+\)[^,\n]*)(,?)$/gm;
|
|
597
|
+
content = content.replace(indexLineRegex, (match, indent, indexDef, comma) => {
|
|
598
|
+
if (indexDef.includes('.$id(')) {
|
|
599
|
+
return match;
|
|
600
|
+
}
|
|
601
|
+
const commentMatch = indexDef.match(/^(.*)(\.\s*comment\s*\([^)]+\))$/);
|
|
602
|
+
if (commentMatch) {
|
|
603
|
+
const beforeComment = commentMatch[1];
|
|
604
|
+
const comment = commentMatch[2];
|
|
605
|
+
const trackingId = generateTrackingId('i');
|
|
606
|
+
injectedCount++;
|
|
607
|
+
return `${indent}${beforeComment}.$id('${trackingId}')${comment}${comma}`;
|
|
608
|
+
}
|
|
609
|
+
const trackingId = generateTrackingId('i');
|
|
610
|
+
injectedCount++;
|
|
611
|
+
return `${indent}${indexDef}.$id('${trackingId}')${comma}`;
|
|
612
|
+
});
|
|
613
|
+
if (injectedCount > 0) {
|
|
614
|
+
fs.writeFileSync(schemaPath, content, 'utf-8');
|
|
615
|
+
}
|
|
616
|
+
return injectedCount;
|
|
617
|
+
}
|
|
379
618
|
async function addCommand(context) {
|
|
380
619
|
const { args, projectRoot } = context;
|
|
381
620
|
console.log('');
|
|
@@ -386,6 +625,10 @@ async function addCommand(context) {
|
|
|
386
625
|
const config = await (0, config_1.loadConfig)();
|
|
387
626
|
const schemaPathRaw = typeof config.schema === 'string' ? config.schema : './db/schema.ts';
|
|
388
627
|
const schemaPath = path.resolve(projectRoot, schemaPathRaw);
|
|
628
|
+
const injectedCount = injectTrackingIds(schemaPath);
|
|
629
|
+
if (injectedCount > 0) {
|
|
630
|
+
console.log(spinner_1.colors.muted(`Injected ${injectedCount} tracking ID(s) into schema.ts`));
|
|
631
|
+
}
|
|
389
632
|
const fileChange = (0, repo_manager_1.detectFileChanges)(schemaPath, projectRoot);
|
|
390
633
|
if (fileChange) {
|
|
391
634
|
const currentSchema = parseSchemaFileForComparison(schemaPath);
|
|
@@ -393,16 +636,13 @@ async function addCommand(context) {
|
|
|
393
636
|
if (currentSchema && snapshot) {
|
|
394
637
|
const snapshotAsDbSchema = snapshotToDatabaseSchema(snapshot);
|
|
395
638
|
const schemaChanges = (0, schema_comparator_1.compareSchemas)(snapshotAsDbSchema, currentSchema);
|
|
639
|
+
(0, repo_manager_1.cleanupStagedChanges)(schemaChanges, projectRoot);
|
|
396
640
|
if (schemaChanges.length > 0) {
|
|
397
641
|
(0, repo_manager_1.clearUnstagedChanges)(projectRoot);
|
|
398
642
|
(0, repo_manager_1.addUnstagedChanges)(schemaChanges, projectRoot);
|
|
399
643
|
}
|
|
400
644
|
else {
|
|
401
|
-
|
|
402
|
-
const hasFileChange = existingUnstaged.some(c => c.objectType === 'SCHEMA_FILE');
|
|
403
|
-
if (!hasFileChange) {
|
|
404
|
-
(0, repo_manager_1.addUnstagedChanges)([fileChange], projectRoot);
|
|
405
|
-
}
|
|
645
|
+
(0, repo_manager_1.clearUnstagedChanges)(projectRoot);
|
|
406
646
|
}
|
|
407
647
|
}
|
|
408
648
|
}
|
|
@@ -75,6 +75,7 @@ async function commitCommand(context) {
|
|
|
75
75
|
const creates = staged.filter(c => c.type === 'CREATE').length;
|
|
76
76
|
const alters = staged.filter(c => c.type === 'ALTER').length;
|
|
77
77
|
const drops = staged.filter(c => c.type === 'DROP').length;
|
|
78
|
+
const renames = staged.filter(c => c.type === 'RENAME').length;
|
|
78
79
|
const parentHash = (0, repo_manager_1.getHead)(projectRoot);
|
|
79
80
|
const hashInput = JSON.stringify({
|
|
80
81
|
changes: staged.map(c => c.id),
|
|
@@ -96,6 +97,7 @@ async function commitCommand(context) {
|
|
|
96
97
|
creates,
|
|
97
98
|
alters,
|
|
98
99
|
drops,
|
|
100
|
+
renames,
|
|
99
101
|
total: staged.length,
|
|
100
102
|
},
|
|
101
103
|
};
|
|
@@ -128,7 +130,16 @@ async function commitCommand(context) {
|
|
|
128
130
|
(0, repo_manager_1.saveFileHash)(currentHash, projectRoot);
|
|
129
131
|
}
|
|
130
132
|
console.log(`[${(0, repo_manager_1.shortHash)(hash)}] ${message}`);
|
|
131
|
-
|
|
133
|
+
const statsParts = [];
|
|
134
|
+
if (creates > 0)
|
|
135
|
+
statsParts.push(`${creates} create(s)`);
|
|
136
|
+
if (alters > 0)
|
|
137
|
+
statsParts.push(`${alters} alter(s)`);
|
|
138
|
+
if (drops > 0)
|
|
139
|
+
statsParts.push(`${drops} drop(s)`);
|
|
140
|
+
if (renames > 0)
|
|
141
|
+
statsParts.push(`${renames} rename(s)`);
|
|
142
|
+
console.log(` ${statsParts.length > 0 ? statsParts.join(', ') : 'no changes'}`);
|
|
132
143
|
console.log('');
|
|
133
144
|
(0, cli_utils_1.hint)("run 'relq push' to apply changes to database");
|
|
134
145
|
(0, cli_utils_1.hint)("run 'relq export' to export as SQL file");
|
|
@@ -42,7 +42,6 @@ const repo_manager_1 = require("../utils/repo-manager.cjs");
|
|
|
42
42
|
const change_tracker_1 = require("../utils/change-tracker.cjs");
|
|
43
43
|
const sql_generator_1 = require("../utils/sql-generator.cjs");
|
|
44
44
|
const relqignore_1 = require("../utils/relqignore.cjs");
|
|
45
|
-
const config_1 = require("../../config/config.cjs");
|
|
46
45
|
async function exportCommand(context) {
|
|
47
46
|
const spinner = (0, spinner_1.createSpinner)();
|
|
48
47
|
const { args, flags, projectRoot } = context;
|
|
@@ -64,9 +63,9 @@ async function exportCommand(context) {
|
|
|
64
63
|
if (fromDb) {
|
|
65
64
|
return exportFromDatabase(context, absoluteOutputPath, options);
|
|
66
65
|
}
|
|
67
|
-
return exportFromSnapshot(projectRoot, absoluteOutputPath, options);
|
|
66
|
+
return exportFromSnapshot(projectRoot, absoluteOutputPath, options, context.config ?? undefined);
|
|
68
67
|
}
|
|
69
|
-
async function exportFromSnapshot(projectRoot, absoluteOutputPath, options) {
|
|
68
|
+
async function exportFromSnapshot(projectRoot, absoluteOutputPath, options, config) {
|
|
70
69
|
const spinner = (0, spinner_1.createSpinner)();
|
|
71
70
|
if (!(0, repo_manager_1.isInitialized)(projectRoot)) {
|
|
72
71
|
console.log(`${spinner_1.colors.red('error:')} relq not initialized`);
|
|
@@ -83,13 +82,13 @@ async function exportFromSnapshot(projectRoot, absoluteOutputPath, options) {
|
|
|
83
82
|
return;
|
|
84
83
|
}
|
|
85
84
|
spinner.succeed('Loaded snapshot');
|
|
86
|
-
const
|
|
85
|
+
const cfg = config || {};
|
|
87
86
|
const ignorePatterns = (0, relqignore_1.loadRelqignore)(projectRoot);
|
|
88
87
|
const filteredSnapshot = filterNormalizedSchema(snapshot, ignorePatterns, {
|
|
89
|
-
includeFunctions:
|
|
90
|
-
includeTriggers:
|
|
91
|
-
includeViews:
|
|
92
|
-
includeFDW:
|
|
88
|
+
includeFunctions: cfg.includeFunctions ?? options.includeFunctions ?? true,
|
|
89
|
+
includeTriggers: cfg.includeTriggers ?? options.includeTriggers ?? true,
|
|
90
|
+
includeViews: cfg.includeViews ?? false,
|
|
91
|
+
includeFDW: cfg.includeFDW ?? false,
|
|
93
92
|
});
|
|
94
93
|
const schema = normalizedToDbSchema(filteredSnapshot);
|
|
95
94
|
const ignoredCount = (snapshot.tables.length - filteredSnapshot.tables.length) +
|
|
@@ -113,7 +112,12 @@ async function exportFromSnapshot(projectRoot, absoluteOutputPath, options) {
|
|
|
113
112
|
console.log(` ${spinner_1.colors.muted(`${ignoredCount} object(s) filtered by .relqignore`)}`);
|
|
114
113
|
}
|
|
115
114
|
spinner.start('Generating SQL statements');
|
|
116
|
-
const
|
|
115
|
+
const exportOptions = {
|
|
116
|
+
...options,
|
|
117
|
+
includeFunctions: cfg.includeFunctions ?? options.includeFunctions ?? true,
|
|
118
|
+
includeTriggers: cfg.includeTriggers ?? options.includeTriggers ?? true,
|
|
119
|
+
};
|
|
120
|
+
const sqlContent = generateFullSQL(schema, exportOptions);
|
|
117
121
|
spinner.succeed('Generated SQL statements');
|
|
118
122
|
const outputDir = path.dirname(absoluteOutputPath);
|
|
119
123
|
if (!fs.existsSync(outputDir)) {
|
|
@@ -183,15 +187,9 @@ function normalizedToDbSchema(normalized) {
|
|
|
183
187
|
defaultValue: c.default,
|
|
184
188
|
isPrimaryKey: c.primaryKey,
|
|
185
189
|
isUnique: c.unique,
|
|
186
|
-
maxLength: null,
|
|
187
|
-
precision: null,
|
|
188
|
-
scale: null,
|
|
189
|
-
references: c.references ? {
|
|
190
|
-
table: c.references.table,
|
|
191
|
-
column: c.references.column,
|
|
192
|
-
onDelete: c.references.onDelete,
|
|
193
|
-
onUpdate: c.references.onUpdate,
|
|
194
|
-
} : null,
|
|
190
|
+
maxLength: c.maxLength ?? null,
|
|
191
|
+
precision: c.precision ?? null,
|
|
192
|
+
scale: c.scale ?? null,
|
|
195
193
|
check: c.check ?? undefined,
|
|
196
194
|
comment: c.comment ?? undefined,
|
|
197
195
|
isGenerated: c.isGenerated || false,
|
|
@@ -207,6 +205,7 @@ function normalizedToDbSchema(normalized) {
|
|
|
207
205
|
definition: i.definition,
|
|
208
206
|
whereClause: i.whereClause ?? null,
|
|
209
207
|
expression: i.columnDefs?.find(cd => cd.expression)?.expression ?? null,
|
|
208
|
+
comment: i.comment ?? null,
|
|
210
209
|
})),
|
|
211
210
|
constraints: t.constraints.map(c => ({
|
|
212
211
|
name: c.name,
|
|
@@ -221,6 +220,7 @@ function normalizedToDbSchema(normalized) {
|
|
|
221
220
|
isPartitioned: t.isPartitioned || t.partitionType !== undefined,
|
|
222
221
|
partitionType: t.partitionType,
|
|
223
222
|
partitionKey: t.partitionKey,
|
|
223
|
+
comment: t.comment ?? null,
|
|
224
224
|
})),
|
|
225
225
|
enums: normalized.enums.map(e => ({
|
|
226
226
|
name: e.name,
|
|
@@ -269,7 +269,13 @@ function normalizedToDbSchema(normalized) {
|
|
|
269
269
|
definition: '',
|
|
270
270
|
isEnabled: t.isEnabled !== false,
|
|
271
271
|
})),
|
|
272
|
-
partitions: []
|
|
272
|
+
partitions: normalized.tables.flatMap(t => (t.partitions || []).map(p => ({
|
|
273
|
+
name: p.name,
|
|
274
|
+
parentTable: t.name,
|
|
275
|
+
partitionBound: p.bound || '',
|
|
276
|
+
partitionType: (p.boundType || t.partitionType || 'LIST'),
|
|
277
|
+
partitionKey: t.partitionKey || [],
|
|
278
|
+
}))),
|
|
273
279
|
policies: [],
|
|
274
280
|
foreignServers: [],
|
|
275
281
|
foreignTables: [],
|