relq 1.0.5 → 1.0.7

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 (86) hide show
  1. package/dist/cjs/cli/commands/add.cjs +257 -17
  2. package/dist/cjs/cli/commands/commit.cjs +13 -2
  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/config/config.cjs +29 -10
  32. package/dist/cjs/core/relq-client.cjs +22 -6
  33. package/dist/cjs/schema-definition/column-types.cjs +149 -13
  34. package/dist/cjs/schema-definition/defaults.cjs +72 -0
  35. package/dist/cjs/schema-definition/index.cjs +15 -1
  36. package/dist/cjs/schema-definition/introspection.cjs +7 -3
  37. package/dist/cjs/schema-definition/pg-relations.cjs +169 -0
  38. package/dist/cjs/schema-definition/pg-view.cjs +30 -0
  39. package/dist/cjs/schema-definition/table-definition.cjs +110 -4
  40. package/dist/cjs/types/config-types.cjs +13 -4
  41. package/dist/cjs/utils/aws-dsql.cjs +177 -0
  42. package/dist/config.d.ts +147 -2
  43. package/dist/esm/cli/commands/add.js +255 -18
  44. package/dist/esm/cli/commands/commit.js +13 -2
  45. package/dist/esm/cli/commands/export.js +25 -19
  46. package/dist/esm/cli/commands/import.js +221 -102
  47. package/dist/esm/cli/commands/init.js +86 -14
  48. package/dist/esm/cli/commands/pull.js +106 -25
  49. package/dist/esm/cli/commands/push.js +39 -4
  50. package/dist/esm/cli/index.js +9 -1
  51. package/dist/esm/cli/utils/ast/codegen/builder.js +291 -0
  52. package/dist/esm/cli/utils/ast/codegen/constraints.js +176 -0
  53. package/dist/esm/cli/utils/ast/codegen/defaults.js +305 -0
  54. package/dist/esm/cli/utils/ast/codegen/index.js +6 -0
  55. package/dist/esm/cli/utils/ast/codegen/type-map.js +111 -0
  56. package/dist/esm/cli/utils/ast/codegen/utils.js +60 -0
  57. package/dist/esm/cli/utils/ast/index.js +3 -0
  58. package/dist/esm/cli/utils/ast/transformer/helpers.js +141 -0
  59. package/dist/esm/cli/utils/ast/transformer/index.js +2 -0
  60. package/dist/esm/cli/utils/ast/types.js +1 -0
  61. package/dist/esm/cli/utils/ast-codegen.js +945 -0
  62. package/dist/esm/cli/utils/ast-transformer.js +907 -0
  63. package/dist/esm/cli/utils/change-tracker.js +50 -1
  64. package/dist/esm/cli/utils/cli-utils.js +147 -0
  65. package/dist/esm/cli/utils/fast-introspect.js +149 -23
  66. package/dist/esm/cli/utils/pg-parser.js +1 -0
  67. package/dist/esm/cli/utils/repo-manager.js +114 -4
  68. package/dist/esm/cli/utils/schema-comparator.js +98 -14
  69. package/dist/esm/cli/utils/schema-introspect.js +56 -19
  70. package/dist/esm/cli/utils/snapshot-manager.js +0 -1
  71. package/dist/esm/cli/utils/sql-generator.js +353 -64
  72. package/dist/esm/cli/utils/type-generator.js +114 -15
  73. package/dist/esm/config/config.js +29 -10
  74. package/dist/esm/core/relq-client.js +23 -7
  75. package/dist/esm/schema-definition/column-types.js +146 -12
  76. package/dist/esm/schema-definition/defaults.js +69 -0
  77. package/dist/esm/schema-definition/index.js +3 -0
  78. package/dist/esm/schema-definition/introspection.js +7 -3
  79. package/dist/esm/schema-definition/pg-relations.js +161 -0
  80. package/dist/esm/schema-definition/pg-view.js +24 -0
  81. package/dist/esm/schema-definition/table-definition.js +110 -4
  82. package/dist/esm/types/config-types.js +12 -4
  83. package/dist/esm/utils/aws-dsql.js +139 -0
  84. package/dist/index.d.ts +159 -1
  85. package/dist/schema-builder.d.ts +1314 -32
  86. package/package.json +1 -1
@@ -1,11 +1,12 @@
1
1
  import * as fs from 'fs';
2
2
  import * as path from 'path';
3
- import { parseSqlFile, parseFunctions, parseTriggers, parseComments } from "../utils/sql-parser.js";
4
- import { generateTypeScript } from "../utils/type-generator.js";
3
+ import { parseFunctions, parseTriggers, parseComments } from "../utils/sql-parser.js";
4
+ import { generateTypeScriptFromAST } from "../utils/ast-codegen.js";
5
+ import { parseSQL, normalizedToParsedSchema } from "../utils/ast-transformer.js";
5
6
  import { saveSnapshot, loadSnapshot, isInitialized, initRepository, stageChanges, addUnstagedChanges } from "../utils/repo-manager.js";
6
7
  import { compareSchemas } from "../utils/schema-comparator.js";
7
8
  import { getChangeDisplayName } from "../utils/change-tracker.js";
8
- import { loadRelqignore, validateIgnoreDependencies, isTableIgnored, isColumnIgnored, isIndexIgnored, isConstraintIgnored, isEnumIgnored, isDomainIgnored, isSequenceIgnored, isCompositeTypeIgnored, isFunctionIgnored, } from "../utils/relqignore.js";
9
+ import { loadRelqignore, validateIgnoreDependencies, isTableIgnored, isColumnIgnored, isIndexIgnored, isConstraintIgnored, isEnumIgnored, isDomainIgnored, isSequenceIgnored, isFunctionIgnored, } from "../utils/relqignore.js";
9
10
  import { colors, fatal, warning, hint, getWorkingTreeStatus, printDirtyWorkingTreeError, printMergeStrategyHelp, readSQLFile, createSpinner, formatBytes, } from "../utils/git-utils.js";
10
11
  export async function importCommand(sqlFilePath, options = {}, projectRoot = process.cwd()) {
11
12
  const { includeFunctions = false, includeTriggers = false, force = false, dryRun = false } = options;
@@ -66,7 +67,7 @@ export async function importCommand(sqlFilePath, options = {}, projectRoot = pro
66
67
  }
67
68
  }
68
69
  spinner.start('Parsing SQL schema');
69
- const parsedSchema = parseSqlFile(sqlContent);
70
+ const parsedSchema = await parseSQL(sqlContent);
70
71
  const functions = includeFunctions ? parseFunctions(sqlContent) : [];
71
72
  const triggers = includeTriggers ? parseTriggers(sqlContent) : [];
72
73
  const comments = parseComments(sqlContent);
@@ -154,13 +155,6 @@ export async function importCommand(sqlFilePath, options = {}, projectRoot = pro
154
155
  }
155
156
  return true;
156
157
  });
157
- const filteredCompositeTypes = parsedSchema.compositeTypes.filter(c => {
158
- if (isCompositeTypeIgnored(c.name, ignorePatterns).ignored) {
159
- ignoredCount++;
160
- return false;
161
- }
162
- return true;
163
- });
164
158
  const filteredSequences = parsedSchema.sequences.filter(s => {
165
159
  if (isSequenceIgnored(s.name, ignorePatterns).ignored) {
166
160
  ignoredCount++;
@@ -179,15 +173,11 @@ export async function importCommand(sqlFilePath, options = {}, projectRoot = pro
179
173
  tables: filteredTables,
180
174
  enums: filteredEnums,
181
175
  domains: filteredDomains,
182
- compositeTypes: filteredCompositeTypes,
183
176
  sequences: filteredSequences,
184
- collations: parsedSchema.collations,
185
- foreignTables: parsedSchema.foreignTables,
186
177
  views: parsedSchema.views,
187
- materializedViews: parsedSchema.materializedViews,
188
- foreignServers: parsedSchema.foreignServers,
178
+ functions: parsedSchema.functions,
179
+ triggers: parsedSchema.triggers,
189
180
  extensions: parsedSchema.extensions,
190
- partitions: parsedSchema.partitions,
191
181
  };
192
182
  if (ignoredCount > 0) {
193
183
  console.log(`${ignoredCount} object(s) ignored by .relqignore`);
@@ -197,14 +187,14 @@ export async function importCommand(sqlFilePath, options = {}, projectRoot = pro
197
187
  name: t.name,
198
188
  columns: t.columns.map(c => ({
199
189
  name: c.name,
200
- type: c.dataType,
190
+ type: c.type,
201
191
  default: c.defaultValue,
202
192
  })),
203
193
  })),
204
194
  enums: parsedSchema.enums,
205
195
  domains: parsedSchema.domains,
206
196
  sequences: parsedSchema.sequences,
207
- compositeTypes: parsedSchema.compositeTypes,
197
+ compositeTypes: [],
208
198
  }, ignorePatterns);
209
199
  if (dependencyErrors.length > 0) {
210
200
  spinner.stop();
@@ -213,12 +203,6 @@ export async function importCommand(sqlFilePath, options = {}, projectRoot = pro
213
203
  return;
214
204
  }
215
205
  spinner.start('Generating TypeScript schema');
216
- const dbSchema = convertToDbSchema(filteredSchema, filteredFunctions, triggers, comments);
217
- const typescriptContent = generateTypeScript(dbSchema, {
218
- camelCase: true,
219
- includeFunctions,
220
- includeTriggers,
221
- });
222
206
  spinner.succeed('Generated TypeScript schema');
223
207
  const incomingSchema = convertToNormalizedSchema(filteredSchema, filteredFunctions, triggers);
224
208
  const existingSnapshot = loadSnapshot(projectRoot);
@@ -282,8 +266,91 @@ export async function importCommand(sqlFilePath, options = {}, projectRoot = pro
282
266
  changes = [];
283
267
  console.log(`${colors.cyan('First import')} - creating initial snapshot`);
284
268
  }
285
- const mergedDbSchema = snapshotToDbSchemaForGeneration(mergedSchema);
286
- const finalTypescriptContent = generateTypeScript(mergedDbSchema, {
269
+ const astSchema = normalizedToParsedSchema({
270
+ tables: mergedSchema.tables.map(t => ({
271
+ name: t.name,
272
+ schema: t.schema,
273
+ columns: t.columns.map(c => ({
274
+ name: c.name,
275
+ type: c.type,
276
+ nullable: c.nullable,
277
+ default: c.default,
278
+ primaryKey: c.primaryKey,
279
+ unique: c.unique,
280
+ check: c.check,
281
+ checkName: c.checkName,
282
+ references: c.references,
283
+ comment: c.comment,
284
+ isGenerated: c.isGenerated,
285
+ generatedExpression: c.generationExpression,
286
+ generatedExpressionAst: c.generatedExpressionAst,
287
+ })),
288
+ indexes: t.indexes.map(i => ({
289
+ name: i.name,
290
+ columns: i.columns,
291
+ unique: i.unique,
292
+ type: i.type,
293
+ definition: i.definition,
294
+ whereClause: i.whereClause,
295
+ whereClauseAst: i.whereClauseAst,
296
+ })),
297
+ constraints: t.constraints?.map(c => ({
298
+ name: c.name,
299
+ type: c.type,
300
+ columns: c.columns,
301
+ definition: c.definition,
302
+ references: c.references,
303
+ })),
304
+ isPartitioned: t.isPartitioned,
305
+ partitionType: t.partitionType,
306
+ partitionKey: t.partitionKey,
307
+ comment: t.comment,
308
+ })),
309
+ enums: mergedSchema.enums.map(e => ({
310
+ name: e.name,
311
+ schema: e.schema,
312
+ values: e.values,
313
+ })),
314
+ domains: mergedSchema.domains?.map(d => ({
315
+ name: d.name,
316
+ baseType: d.baseType,
317
+ notNull: d.notNull,
318
+ default: d.default ?? undefined,
319
+ check: d.check ?? undefined,
320
+ checkName: d.checkName,
321
+ })),
322
+ sequences: mergedSchema.sequences?.map(s => ({
323
+ name: s.name,
324
+ start: s.start,
325
+ increment: s.increment,
326
+ minValue: s.minValue,
327
+ maxValue: s.maxValue,
328
+ cache: s.cache,
329
+ cycle: s.cycle,
330
+ })),
331
+ extensions: mergedSchema.extensions?.map(e => e.name),
332
+ functions: includeFunctions ? mergedSchema.functions?.map(f => ({
333
+ name: f.name,
334
+ schema: f.schema,
335
+ args: f.args || [],
336
+ returnType: f.returnType,
337
+ language: f.language,
338
+ body: f.body,
339
+ volatility: f.volatility,
340
+ isStrict: f.isStrict,
341
+ securityDefiner: f.securityDefiner,
342
+ })) : [],
343
+ triggers: includeTriggers ? mergedSchema.triggers?.map(t => ({
344
+ name: t.name,
345
+ table: t.table,
346
+ timing: t.timing,
347
+ events: t.events,
348
+ level: t.level,
349
+ functionName: t.functionName,
350
+ when: t.when,
351
+ })) : [],
352
+ });
353
+ const finalTypescriptContent = generateTypeScriptFromAST(astSchema, {
287
354
  camelCase: true,
288
355
  includeFunctions,
289
356
  includeTriggers,
@@ -311,6 +378,7 @@ export async function importCommand(sqlFilePath, options = {}, projectRoot = pro
311
378
  }
312
379
  fs.writeFileSync(absoluteOutputPath, finalTypescriptContent, 'utf-8');
313
380
  console.log(`Written ${colors.cyan(absoluteOutputPath)} ${colors.gray(`(${formatBytes(finalTypescriptContent.length)})`)}`);
381
+ applyTrackingIdsToSnapshot(finalTypescriptContent, mergedSchema);
314
382
  saveSnapshot(mergedSchema, projectRoot);
315
383
  if (changes.length > 0) {
316
384
  addUnstagedChanges(changes, projectRoot);
@@ -359,36 +427,50 @@ function convertToDbSchema(parsed, functions = [], triggers = [], comments = [])
359
427
  return {
360
428
  tables: parsed.tables.map(t => ({
361
429
  name: t.name,
362
- schema: t.schema,
430
+ schema: t.schema || 'public',
363
431
  comment: tableComments.get(t.name) || null,
364
- columns: t.columns.map(c => ({
432
+ columns: t.columns
433
+ .filter(c => {
434
+ if (!c.type) {
435
+ console.warn(`Warning: Skipping column ${t.name}.${c.name} (no type)`);
436
+ return false;
437
+ }
438
+ return true;
439
+ })
440
+ .map(c => ({
365
441
  name: c.name,
366
- dataType: c.dataType,
367
- udtName: c.dataType,
442
+ dataType: c.type,
443
+ udtName: c.type,
368
444
  isNullable: c.isNullable,
369
- defaultValue: c.defaultValue,
445
+ defaultValue: c.defaultValue ?? null,
370
446
  isPrimaryKey: c.isPrimaryKey,
371
447
  isUnique: c.isUnique,
372
- maxLength: c.maxLength,
373
- precision: c.precision,
374
- scale: c.scale,
448
+ maxLength: c.typeParams?.length ?? null,
449
+ precision: c.typeParams?.precision ?? null,
450
+ scale: c.typeParams?.scale ?? null,
375
451
  references: c.references ? `${c.references.table}.${c.references.column}` : null,
376
452
  comment: columnComments.get(`${t.name}.${c.name}`) || c.comment || null,
377
453
  isGenerated: c.isGenerated || false,
378
- generationExpression: c.generationExpression || null,
454
+ generationExpression: c.generatedExpression || null,
379
455
  })),
380
456
  indexes: t.indexes.map(idx => ({
381
457
  name: idx.name,
382
458
  columns: idx.columns,
383
459
  isUnique: idx.isUnique,
384
- isPrimary: idx.isPrimary,
385
- type: idx.type,
386
- definition: idx.definition,
460
+ isPrimary: false,
461
+ type: idx.method || 'btree',
462
+ definition: idx.expressions?.join(', ') || idx.columns.join(', '),
387
463
  whereClause: idx.whereClause,
388
- expression: idx.expression,
464
+ whereClauseAst: idx.whereClauseAst,
465
+ expression: idx.expressions?.[0],
389
466
  comment: indexComments.get(idx.name) || null,
390
467
  })),
391
- constraints: t.constraints,
468
+ constraints: t.constraints.map(c => ({
469
+ name: c.name,
470
+ type: c.type,
471
+ columns: c.columns,
472
+ definition: c.expression || c.columns.join(', '),
473
+ })),
392
474
  rowCount: 0,
393
475
  isPartitioned: t.isPartitioned,
394
476
  partitionType: t.partitionType,
@@ -398,34 +480,31 @@ function convertToDbSchema(parsed, functions = [], triggers = [], comments = [])
398
480
  name: e.name,
399
481
  values: e.values,
400
482
  })),
401
- domains: parsed.domains?.map((d) => ({
483
+ domains: parsed.domains.map(d => ({
402
484
  name: d.name,
403
485
  baseType: d.baseType,
404
486
  isNotNull: d.notNull || false,
405
- defaultValue: d.default || null,
406
- checkExpression: d.check || null,
407
- check: d.check,
408
- default: d.default,
487
+ defaultValue: d.defaultValue || null,
488
+ checkExpression: d.checkExpression || null,
489
+ check: d.checkExpression,
490
+ default: d.defaultValue,
409
491
  notNull: d.notNull,
410
- })) || [],
411
- compositeTypes: parsed.compositeTypes?.map((c) => ({
412
- name: c.name,
413
- attributes: c.attributes || [],
414
- })) || [],
415
- sequences: parsed.sequences?.map((s) => ({
492
+ })),
493
+ compositeTypes: [],
494
+ sequences: parsed.sequences.map(s => ({
416
495
  name: s.name,
417
- dataType: s.dataType,
418
- start: s.start,
496
+ dataType: 'bigint',
497
+ start: s.startValue,
419
498
  increment: s.increment,
420
499
  minValue: s.minValue,
421
500
  maxValue: s.maxValue,
422
501
  cache: s.cache,
423
502
  cycle: s.cycle,
424
- ownedBy: s.ownedBy,
425
- })) || [],
503
+ ownedBy: s.ownedBy ? `${s.ownedBy.table}.${s.ownedBy.column}` : undefined,
504
+ })),
426
505
  collations: [],
427
506
  extensions: parsed.extensions,
428
- partitions: parsed.partitions,
507
+ partitions: [],
429
508
  functions: functions.map(f => ({
430
509
  ...f,
431
510
  comment: functionComments.get(f.name) || null,
@@ -440,79 +519,95 @@ function convertToDbSchema(parsed, functions = [], triggers = [], comments = [])
440
519
  };
441
520
  }
442
521
  function convertToNormalizedSchema(parsed, functions = [], triggers = []) {
522
+ const regularViews = parsed.views.filter(v => !v.isMaterialized);
523
+ const materializedViews = parsed.views.filter(v => v.isMaterialized);
443
524
  return {
444
525
  extensions: parsed.extensions.map(ext => ({ name: ext })),
445
526
  enums: parsed.enums.map(e => ({
446
527
  name: e.name,
447
- schema: 'public',
528
+ schema: e.schema || 'public',
448
529
  values: e.values,
449
530
  })),
450
531
  domains: parsed.domains.map(d => ({
451
532
  name: d.name,
452
- schema: 'public',
533
+ schema: d.schema || 'public',
453
534
  baseType: d.baseType,
454
535
  notNull: d.notNull || false,
455
- default: d.default || null,
456
- check: d.check || null,
457
- })),
458
- compositeTypes: parsed.compositeTypes.map(c => ({
459
- name: c.name,
460
- schema: 'public',
461
- attributes: c.attributes,
536
+ default: d.defaultValue || null,
537
+ check: d.checkExpression || null,
462
538
  })),
539
+ compositeTypes: [],
463
540
  sequences: parsed.sequences.map(s => ({
464
541
  name: s.name,
465
- schema: 'public',
542
+ schema: s.schema || 'public',
466
543
  dataType: 'bigint',
467
- startValue: s.start || 1,
544
+ startValue: s.startValue || 1,
468
545
  increment: s.increment || 1,
469
546
  minValue: s.minValue || null,
470
547
  maxValue: s.maxValue || null,
471
- cache: 1,
472
- cycle: false,
473
- ownedBy: null,
548
+ cache: s.cache || 1,
549
+ cycle: s.cycle || false,
550
+ ownedBy: s.ownedBy ? `${s.ownedBy.table}.${s.ownedBy.column}` : null,
474
551
  })),
475
552
  collations: [],
476
553
  tables: parsed.tables.map(t => {
477
- const childPartitions = parsed.partitions.filter(p => p.parentTable === t.name);
478
554
  return {
479
555
  name: t.name,
480
- schema: t.schema,
481
- columns: t.columns.map(c => ({
482
- name: c.name,
483
- tsName: toCamelCase(c.name),
484
- type: c.dataType,
485
- nullable: c.isNullable,
486
- default: c.defaultValue,
487
- primaryKey: c.isPrimaryKey,
488
- unique: c.isUnique,
489
- identity: c.identityGeneration ? {
490
- type: c.identityGeneration,
491
- } : undefined,
492
- isGenerated: c.isGenerated,
493
- generationExpression: c.generationExpression,
494
- })),
556
+ schema: t.schema || 'public',
557
+ columns: t.columns.map(c => {
558
+ const col = {
559
+ name: c.name,
560
+ tsName: toCamelCase(c.name),
561
+ type: c.type,
562
+ nullable: c.isNullable,
563
+ default: c.defaultValue,
564
+ primaryKey: c.isPrimaryKey,
565
+ unique: c.isUnique,
566
+ isGenerated: c.isGenerated,
567
+ generationExpression: c.generatedExpression,
568
+ generatedExpressionAst: c.generatedExpressionAst,
569
+ };
570
+ return col;
571
+ }),
495
572
  indexes: t.indexes.map(idx => ({
496
573
  name: idx.name,
497
574
  columns: idx.columns,
498
575
  unique: idx.isUnique,
499
- type: idx.type || 'btree',
500
- definition: idx.definition,
576
+ type: idx.method || 'btree',
577
+ definition: idx.expressions?.join(', ') || idx.columns.join(', '),
501
578
  whereClause: idx.whereClause,
502
- expression: idx.expression,
503
- })),
504
- constraints: t.constraints.map(c => ({
505
- name: c.name,
506
- type: c.type,
507
- definition: c.definition,
579
+ whereClauseAst: idx.whereClauseAst,
580
+ expression: idx.expressions?.[0],
508
581
  })),
582
+ constraints: t.constraints.map(c => {
583
+ const base = {
584
+ name: c.name,
585
+ type: c.type,
586
+ definition: c.expression || c.columns.join(', '),
587
+ columns: c.columns,
588
+ };
589
+ if (c.type === 'FOREIGN KEY' && c.references) {
590
+ base.referencedTable = c.references.table;
591
+ base.referencedColumns = c.references.columns;
592
+ if (c.references.onDelete)
593
+ base.onDelete = c.references.onDelete;
594
+ if (c.references.onUpdate)
595
+ base.onUpdate = c.references.onUpdate;
596
+ if (c.references.match)
597
+ base.matchType = c.references.match;
598
+ if (c.references.deferrable)
599
+ base.deferrable = c.references.deferrable;
600
+ if (c.references.initiallyDeferred)
601
+ base.initiallyDeferred = c.references.initiallyDeferred;
602
+ }
603
+ if (c.type === 'CHECK' && c.expression) {
604
+ base.checkExpression = c.expression;
605
+ }
606
+ return base;
607
+ }),
509
608
  isPartitioned: t.isPartitioned,
510
609
  partitionType: t.partitionType,
511
610
  partitionKey: t.partitionKey,
512
- partitions: childPartitions.length > 0 ? childPartitions.map(p => ({
513
- name: p.name,
514
- bound: p.partitionBound || '',
515
- })) : undefined,
516
611
  };
517
612
  }),
518
613
  functions: functions.map(f => ({
@@ -529,16 +624,16 @@ function convertToNormalizedSchema(parsed, functions = [], triggers = []) {
529
624
  forEach: 'STATEMENT',
530
625
  functionName: t.functionName || '',
531
626
  })),
532
- views: parsed.views.map(v => ({
627
+ views: regularViews.map(v => ({
533
628
  name: v.name,
534
629
  schema: v.schema || 'public',
535
630
  definition: v.definition,
536
631
  })),
537
- materializedViews: parsed.materializedViews.map(mv => ({
632
+ materializedViews: materializedViews.map(mv => ({
538
633
  name: mv.name,
539
634
  schema: mv.schema || 'public',
540
635
  definition: mv.definition,
541
- withData: mv.withData ?? true,
636
+ withData: true,
542
637
  })),
543
638
  };
544
639
  }
@@ -650,6 +745,7 @@ function snapshotToDbSchemaForGeneration(snapshot) {
650
745
  type: i.type || 'btree',
651
746
  definition: i.definition,
652
747
  whereClause: i.whereClause,
748
+ whereClauseAst: i.whereClauseAst,
653
749
  comment: null,
654
750
  })),
655
751
  constraints: t.constraints?.map(c => ({
@@ -749,4 +845,27 @@ function mergeSchemas(existing, incoming, replaceAll = false) {
749
845
  foreignTables: mergeByName(existing.foreignTables || [], incoming.foreignTables || []),
750
846
  };
751
847
  }
848
+ function applyTrackingIdsToSnapshot(typescript, snapshot) {
849
+ for (const table of snapshot.tables) {
850
+ const tablePattern = new RegExp(`defineTable\\s*\\(\\s*['"]${table.name}['"]\\s*,\\s*\\{[^}]+\\}\\s*,\\s*\\{[^}]*\\$trackingId:\\s*['"]([^'"]+)['"]`, 's');
851
+ const tableMatch = typescript.match(tablePattern);
852
+ if (tableMatch) {
853
+ table.trackingId = tableMatch[1];
854
+ }
855
+ for (const col of table.columns) {
856
+ const colPattern = new RegExp(`(?:${col.tsName}|${col.name}):\\s*\\w+\\([^)]*\\)[^\\n]*\\.\\\$id\\(['"]([^'"]+)['"]\\)`);
857
+ const colMatch = typescript.match(colPattern);
858
+ if (colMatch) {
859
+ col.trackingId = colMatch[1];
860
+ }
861
+ }
862
+ for (const idx of table.indexes) {
863
+ const idxPattern = new RegExp(`index\\s*\\(\\s*['"]${idx.name}['"]\\s*\\)[^\\n]*\\.\\\$id\\(['"]([^'"]+)['"]\\)`);
864
+ const idxMatch = typescript.match(idxPattern);
865
+ if (idxMatch) {
866
+ idx.trackingId = idxMatch[1];
867
+ }
868
+ }
869
+ }
870
+ }
752
871
  export default importCommand;
@@ -48,21 +48,69 @@ function checkEnvVars() {
48
48
  DATABASE_NAME: process.env.DATABASE_NAME,
49
49
  DATABASE_USER: process.env.DATABASE_USER,
50
50
  DATABASE_PASSWORD: process.env.DATABASE_PASSWORD,
51
+ DATABASE_REGION: process.env.DATABASE_REGION,
52
+ AWS_DATABASE_HOST: process.env.AWS_DATABASE_HOST,
53
+ AWS_ACCESS_KEY_ID: process.env.AWS_ACCESS_KEY_ID,
54
+ AWS_DATABASE_USER: process.env.AWS_DATABASE_USER,
55
+ AWS_DATABASE_NAME: process.env.AWS_DATABASE_NAME,
56
+ AWS_DATABASE_PORT: process.env.AWS_DATABASE_PORT,
57
+ AWS_SECRET_ACCESS_KEY: process.env.AWS_SECRET_ACCESS_KEY,
51
58
  RELQ_PG_CONN_URL: process.env.RELQ_PG_CONN_URL,
52
59
  };
53
60
  const hasConnUrl = !!vars.RELQ_PG_CONN_URL;
54
- const hasIndividual = !!(vars.DATABASE_HOST && vars.DATABASE_NAME);
61
+ const hasIndividual = !!(vars.DATABASE_HOST && vars.DATABASE_NAME && vars.DATABASE_USER && vars.DATABASE_PASSWORD);
62
+ const hasAws = !!(vars.AWS_ACCESS_KEY_ID && vars.AWS_SECRET_ACCESS_KEY && vars.DATABASE_REGION && vars.AWS_DATABASE_NAME && vars.AWS_DATABASE_HOST);
55
63
  return {
56
- found: hasConnUrl || hasIndividual,
64
+ found: hasConnUrl || hasIndividual || hasAws,
65
+ hasConnUrl,
66
+ hasIndividual,
67
+ hasAws,
57
68
  vars,
58
69
  };
59
70
  }
71
+ async function askConnectionType(rl, options) {
72
+ const choices = [];
73
+ if (options.hasConnUrl) {
74
+ choices.push({ key: String(choices.length + 1), label: 'Connection URL (RELQ_PG_CONN_URL)', value: 'url' });
75
+ }
76
+ if (options.hasIndividual) {
77
+ choices.push({ key: String(choices.length + 1), label: 'Individual Config (DATABASE_HOST, DATABASE_NAME, ...)', value: 'individual' });
78
+ }
79
+ if (options.hasAws) {
80
+ choices.push({ key: String(choices.length + 1), label: 'AWS DSQL (AWS_ACCESS_KEY_ID, DATABASE_REGION, ...)', value: 'aws' });
81
+ }
82
+ if (choices.length === 1) {
83
+ return choices[0].value;
84
+ }
85
+ console.log('Multiple connection options found:');
86
+ console.log('');
87
+ for (const choice of choices) {
88
+ console.log(` ${colors.cyan(choice.key)}. ${choice.label}`);
89
+ }
90
+ console.log('');
91
+ const answer = await ask(rl, `Which connection type? [${choices.map(c => c.key).join('/')}]`, '1');
92
+ const selected = choices.find(c => c.key === answer);
93
+ return selected?.value ?? choices[0].value;
94
+ }
60
95
  function generateConfig(options) {
61
96
  let connectionBlock;
62
97
  if (options.useEnv) {
63
- if (process.env.RELQ_PG_CONN_URL) {
98
+ if (options.connectionType === 'url') {
64
99
  connectionBlock = ` connection: {
65
100
  url: process.env.RELQ_PG_CONN_URL,
101
+ },`;
102
+ }
103
+ else if (options.connectionType === 'aws') {
104
+ connectionBlock = ` connection: {
105
+ database: process.env.AWS_DATABASE_NAME,
106
+ aws: {
107
+ hostname: process.env.AWS_DATABASE_HOST!,
108
+ region: process.env.DATABASE_REGION!,
109
+ accessKeyId: process.env.AWS_ACCESS_KEY_ID,
110
+ secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
111
+ user: process.env.AWS_DATABASE_USER || 'admin',
112
+ port: parseInt(process.env.AWS_DATABASE_PORT || '5432'),
113
+ },
66
114
  },`;
67
115
  }
68
116
  else {
@@ -287,30 +335,53 @@ export async function initCommand(context) {
287
335
  console.log('');
288
336
  const envCheck = checkEnvVars();
289
337
  let useEnv = false;
338
+ let connectionType = 'individual';
290
339
  let host = 'localhost';
291
340
  let port = '5432';
292
341
  let database = '';
293
342
  let user = 'postgres';
294
343
  let password = '';
295
344
  if (envCheck.found) {
296
- console.log('Found database environment variables:');
297
- if (envCheck.vars.RELQ_PG_CONN_URL) {
298
- console.log(' RELQ_PG_CONN_URL');
345
+ console.log('Found database connection options:');
346
+ console.log('');
347
+ if (envCheck.hasConnUrl) {
348
+ console.log(` ${colors.cyan('Connection URL')}`);
349
+ console.log(` RELQ_PG_CONN_URL: ${envCheck.vars.RELQ_PG_CONN_URL?.substring(0, 40)}...`);
299
350
  }
300
- else {
351
+ if (envCheck.hasIndividual) {
352
+ console.log(` ${colors.cyan('Individual Config')}`);
301
353
  if (envCheck.vars.DATABASE_HOST)
302
- console.log(` DATABASE_HOST: ${envCheck.vars.DATABASE_HOST}`);
303
- if (envCheck.vars.DATABASE_PORT)
304
- console.log(` DATABASE_PORT: ${envCheck.vars.DATABASE_PORT}`);
354
+ console.log(` DATABASE_HOST: ${envCheck.vars.DATABASE_HOST}`);
305
355
  if (envCheck.vars.DATABASE_NAME)
306
- console.log(` DATABASE_NAME: ${envCheck.vars.DATABASE_NAME}`);
356
+ console.log(` DATABASE_NAME: ${envCheck.vars.DATABASE_NAME}`);
307
357
  if (envCheck.vars.DATABASE_USER)
308
- console.log(` DATABASE_USER: ${envCheck.vars.DATABASE_USER}`);
358
+ console.log(` DATABASE_USER: ${envCheck.vars.DATABASE_USER}`);
309
359
  if (envCheck.vars.DATABASE_PASSWORD)
310
- console.log(' DATABASE_PASSWORD: ***');
360
+ console.log(' DATABASE_PASSWORD: ***');
361
+ }
362
+ if (envCheck.hasAws) {
363
+ console.log(` ${colors.cyan('AWS DSQL')}`);
364
+ if (envCheck.vars.AWS_DATABASE_HOST)
365
+ console.log(` AWS_DATABASE_HOST: ${envCheck.vars.AWS_DATABASE_HOST}`);
366
+ if (envCheck.vars.DATABASE_REGION)
367
+ console.log(` DATABASE_REGION: ${envCheck.vars.DATABASE_REGION}`);
368
+ if (envCheck.vars.AWS_DATABASE_NAME)
369
+ console.log(` AWS_DATABASE_NAME: ${envCheck.vars.AWS_DATABASE_NAME}`);
370
+ if (envCheck.vars.AWS_ACCESS_KEY_ID)
371
+ console.log(` AWS_ACCESS_KEY_ID: ${envCheck.vars.AWS_ACCESS_KEY_ID?.substring(0, 8)}...`);
372
+ console.log(' AWS_SECRET_ACCESS_KEY: ***');
311
373
  }
312
374
  console.log('');
313
- useEnv = await askYesNo(rl, 'Use these environment variables?', true);
375
+ const optionCount = [envCheck.hasConnUrl, envCheck.hasIndividual, envCheck.hasAws].filter(Boolean).length;
376
+ if (optionCount > 1) {
377
+ connectionType = await askConnectionType(rl, envCheck);
378
+ console.log('');
379
+ useEnv = await askYesNo(rl, `Use ${connectionType === 'aws' ? 'AWS DSQL' : connectionType === 'url' ? 'Connection URL' : 'Individual Config'} from environment?`, true);
380
+ }
381
+ else {
382
+ connectionType = envCheck.hasConnUrl ? 'url' : envCheck.hasAws ? 'aws' : 'individual';
383
+ useEnv = await askYesNo(rl, 'Use these environment variables?', true);
384
+ }
314
385
  console.log('');
315
386
  }
316
387
  if (!useEnv) {
@@ -344,6 +415,7 @@ export async function initCommand(context) {
344
415
  if (!fs.existsSync(configPath)) {
345
416
  const configContent = generateConfig({
346
417
  useEnv,
418
+ connectionType,
347
419
  host,
348
420
  port,
349
421
  database,