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.
Files changed (84) hide show
  1. package/dist/cjs/cli/commands/add.cjs +252 -12
  2. package/dist/cjs/cli/commands/commit.cjs +12 -1
  3. package/dist/cjs/cli/commands/export.cjs +25 -19
  4. package/dist/cjs/cli/commands/import.cjs +219 -100
  5. package/dist/cjs/cli/commands/init.cjs +86 -14
  6. package/dist/cjs/cli/commands/pull.cjs +104 -23
  7. package/dist/cjs/cli/commands/push.cjs +38 -3
  8. package/dist/cjs/cli/index.cjs +9 -1
  9. package/dist/cjs/cli/utils/ast/codegen/builder.cjs +297 -0
  10. package/dist/cjs/cli/utils/ast/codegen/constraints.cjs +185 -0
  11. package/dist/cjs/cli/utils/ast/codegen/defaults.cjs +311 -0
  12. package/dist/cjs/cli/utils/ast/codegen/index.cjs +24 -0
  13. package/dist/cjs/cli/utils/ast/codegen/type-map.cjs +116 -0
  14. package/dist/cjs/cli/utils/ast/codegen/utils.cjs +69 -0
  15. package/dist/cjs/cli/utils/ast/index.cjs +19 -0
  16. package/dist/cjs/cli/utils/ast/transformer/helpers.cjs +154 -0
  17. package/dist/cjs/cli/utils/ast/transformer/index.cjs +25 -0
  18. package/dist/cjs/cli/utils/ast/types.cjs +2 -0
  19. package/dist/cjs/cli/utils/ast-codegen.cjs +949 -0
  20. package/dist/cjs/cli/utils/ast-transformer.cjs +916 -0
  21. package/dist/cjs/cli/utils/change-tracker.cjs +50 -1
  22. package/dist/cjs/cli/utils/cli-utils.cjs +151 -0
  23. package/dist/cjs/cli/utils/fast-introspect.cjs +149 -23
  24. package/dist/cjs/cli/utils/pg-parser.cjs +1 -0
  25. package/dist/cjs/cli/utils/repo-manager.cjs +121 -4
  26. package/dist/cjs/cli/utils/schema-comparator.cjs +98 -14
  27. package/dist/cjs/cli/utils/schema-introspect.cjs +56 -19
  28. package/dist/cjs/cli/utils/snapshot-manager.cjs +0 -1
  29. package/dist/cjs/cli/utils/sql-generator.cjs +353 -64
  30. package/dist/cjs/cli/utils/type-generator.cjs +114 -15
  31. package/dist/cjs/core/relq-client.cjs +22 -6
  32. package/dist/cjs/schema-definition/column-types.cjs +149 -13
  33. package/dist/cjs/schema-definition/defaults.cjs +72 -0
  34. package/dist/cjs/schema-definition/index.cjs +15 -1
  35. package/dist/cjs/schema-definition/introspection.cjs +7 -3
  36. package/dist/cjs/schema-definition/pg-relations.cjs +169 -0
  37. package/dist/cjs/schema-definition/pg-view.cjs +30 -0
  38. package/dist/cjs/schema-definition/table-definition.cjs +110 -4
  39. package/dist/cjs/types/config-types.cjs +13 -4
  40. package/dist/cjs/utils/aws-dsql.cjs +177 -0
  41. package/dist/config.d.ts +146 -1
  42. package/dist/esm/cli/commands/add.js +250 -13
  43. package/dist/esm/cli/commands/commit.js +12 -1
  44. package/dist/esm/cli/commands/export.js +25 -19
  45. package/dist/esm/cli/commands/import.js +221 -102
  46. package/dist/esm/cli/commands/init.js +86 -14
  47. package/dist/esm/cli/commands/pull.js +106 -25
  48. package/dist/esm/cli/commands/push.js +39 -4
  49. package/dist/esm/cli/index.js +9 -1
  50. package/dist/esm/cli/utils/ast/codegen/builder.js +291 -0
  51. package/dist/esm/cli/utils/ast/codegen/constraints.js +176 -0
  52. package/dist/esm/cli/utils/ast/codegen/defaults.js +305 -0
  53. package/dist/esm/cli/utils/ast/codegen/index.js +6 -0
  54. package/dist/esm/cli/utils/ast/codegen/type-map.js +111 -0
  55. package/dist/esm/cli/utils/ast/codegen/utils.js +60 -0
  56. package/dist/esm/cli/utils/ast/index.js +3 -0
  57. package/dist/esm/cli/utils/ast/transformer/helpers.js +141 -0
  58. package/dist/esm/cli/utils/ast/transformer/index.js +2 -0
  59. package/dist/esm/cli/utils/ast/types.js +1 -0
  60. package/dist/esm/cli/utils/ast-codegen.js +945 -0
  61. package/dist/esm/cli/utils/ast-transformer.js +907 -0
  62. package/dist/esm/cli/utils/change-tracker.js +50 -1
  63. package/dist/esm/cli/utils/cli-utils.js +147 -0
  64. package/dist/esm/cli/utils/fast-introspect.js +149 -23
  65. package/dist/esm/cli/utils/pg-parser.js +1 -0
  66. package/dist/esm/cli/utils/repo-manager.js +114 -4
  67. package/dist/esm/cli/utils/schema-comparator.js +98 -14
  68. package/dist/esm/cli/utils/schema-introspect.js +56 -19
  69. package/dist/esm/cli/utils/snapshot-manager.js +0 -1
  70. package/dist/esm/cli/utils/sql-generator.js +353 -64
  71. package/dist/esm/cli/utils/type-generator.js +114 -15
  72. package/dist/esm/core/relq-client.js +23 -7
  73. package/dist/esm/schema-definition/column-types.js +146 -12
  74. package/dist/esm/schema-definition/defaults.js +69 -0
  75. package/dist/esm/schema-definition/index.js +3 -0
  76. package/dist/esm/schema-definition/introspection.js +7 -3
  77. package/dist/esm/schema-definition/pg-relations.js +161 -0
  78. package/dist/esm/schema-definition/pg-view.js +24 -0
  79. package/dist/esm/schema-definition/table-definition.js +110 -4
  80. package/dist/esm/types/config-types.js +12 -4
  81. package/dist/esm/utils/aws-dsql.js +139 -0
  82. package/dist/index.d.ts +159 -1
  83. package/dist/schema-builder.d.ts +1314 -32
  84. 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 content = fs.readFileSync(schemaPath, 'utf-8');
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 funcDefaultMatch = !defaultValue && colDef.match(/\.default\(\s*(genRandomUuid|now|currentDate|currentTimestamp|emptyArray|emptyObject)\s*\(\s*\)\s*\)/);
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
- const isArray = colDef.includes('.array()');
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 isUnique = optionsBlock.includes(`index('${indexName}')`) &&
214
- optionsBlock.substring(optionsBlock.indexOf(`index('${indexName}')`)).split('\n')[0].includes('.unique()');
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
- const existingUnstaged = (0, repo_manager_1.getUnstagedChanges)(projectRoot);
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
- console.log(` ${creates} create(s), ${alters} alter(s), ${drops} drop(s)`);
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 config = await (0, config_1.loadConfig)();
85
+ const cfg = config || {};
87
86
  const ignorePatterns = (0, relqignore_1.loadRelqignore)(projectRoot);
88
87
  const filteredSnapshot = filterNormalizedSchema(snapshot, ignorePatterns, {
89
- includeFunctions: config.includeFunctions ?? options.includeFunctions ?? false,
90
- includeTriggers: config.includeTriggers ?? options.includeTriggers ?? false,
91
- includeViews: config.includeViews ?? false,
92
- includeFDW: config.includeFDW ?? false,
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 sqlContent = generateFullSQL(schema, options);
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: [],