relq 1.0.4 → 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 +150 -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 +147 -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
@@ -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,
@@ -2,11 +2,12 @@ import * as fs from 'fs';
2
2
  import * as path from 'path';
3
3
  import { requireValidConfig } from "../utils/config-loader.js";
4
4
  import { fastIntrospectDatabase } from "../utils/fast-introspect.js";
5
- import { generateTypeScript } from "../utils/type-generator.js";
5
+ import { introspectedToParsedSchema } from "../utils/ast-transformer.js";
6
+ import { generateTypeScriptFromAST } from "../utils/ast-codegen.js";
6
7
  import { getConnectionDescription } from "../utils/env-loader.js";
7
- import { createSpinner, colors, formatBytes, formatDuration, fatal, confirm, warning } from "../utils/cli-utils.js";
8
+ import { createSpinner, colors, formatBytes, formatDuration, fatal, confirm, warning, createMultiProgress } from "../utils/cli-utils.js";
8
9
  import { loadRelqignore, isTableIgnored, isColumnIgnored, isIndexIgnored, isConstraintIgnored, isEnumIgnored, isDomainIgnored, isCompositeTypeIgnored, isFunctionIgnored, } from "../utils/relqignore.js";
9
- import { isInitialized, initRepository, getHead, saveSnapshot, loadSnapshot, createCommit, shortHash, fetchRemoteCommits, ensureRemoteTable, setFetchHead, addUnstagedChanges, getStagedChanges, getUnstagedChanges, clearWorkingState, hashFileContent, saveFileHash, } from "../utils/repo-manager.js";
10
+ import { isInitialized, initRepository, getHead, saveSnapshot, loadSnapshot, createCommit, shortHash, fetchRemoteCommits, ensureRemoteTable, setFetchHead, addUnstagedChanges, getStagedChanges, getUnstagedChanges, clearWorkingState, hashFileContent, saveFileHash, markCommitAsPulled, } from "../utils/repo-manager.js";
10
11
  import { compareSchemas } from "../utils/schema-comparator.js";
11
12
  function toCamelCase(str) {
12
13
  return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
@@ -145,8 +146,8 @@ export async function pullCommand(context) {
145
146
  const author = config.author || 'Relq CLI';
146
147
  const schemaPathRaw = typeof config.schema === 'string' ? config.schema : './db/schema.ts';
147
148
  const schemaPath = path.resolve(projectRoot, schemaPathRaw);
148
- const includeFunctions = config.generate?.includeFunctions ?? false;
149
- const includeTriggers = config.generate?.includeTriggers ?? false;
149
+ const includeFunctions = config.includeFunctions ?? true;
150
+ const includeTriggers = config.includeTriggers ?? true;
150
151
  const spinner = createSpinner();
151
152
  const startTime = Date.now();
152
153
  console.log('');
@@ -202,12 +203,36 @@ export async function pullCommand(context) {
202
203
  setFetchHead(remoteHead, projectRoot);
203
204
  }
204
205
  spinner.succeed(`Fetched ${remoteCommits.length} remote commits`);
205
- spinner.start('Introspecting database...');
206
+ console.log('');
207
+ console.log(colors.bold('Introspecting database...'));
208
+ console.log('');
209
+ const progress = createMultiProgress();
210
+ const progressItems = [
211
+ { id: 'tables', label: 'tables', status: 'pending', count: 0 },
212
+ { id: 'columns', label: 'columns', status: 'pending', count: 0 },
213
+ { id: 'constraints', label: 'constraints', status: 'pending', count: 0 },
214
+ { id: 'indexes', label: 'indexes', status: 'pending', count: 0 },
215
+ { id: 'checks', label: 'checks', status: 'pending', count: 0 },
216
+ { id: 'enums', label: 'enums', status: 'pending', count: 0 },
217
+ { id: 'partitions', label: 'partitions', status: 'pending', count: 0 },
218
+ { id: 'extensions', label: 'extensions', status: 'pending', count: 0 },
219
+ ...(includeFunctions ? [{ id: 'functions', label: 'functions', status: 'pending', count: 0 }] : []),
220
+ ...(includeTriggers ? [{ id: 'triggers', label: 'triggers', status: 'pending', count: 0 }] : []),
221
+ { id: 'collations', label: 'collations', status: 'pending', count: 0 },
222
+ { id: 'foreign_servers', label: 'foreign servers', status: 'pending', count: 0 },
223
+ { id: 'foreign_tables', label: 'foreign tables', status: 'pending', count: 0 },
224
+ ];
225
+ progress.setItems(progressItems);
226
+ progress.start();
206
227
  const dbSchema = await fastIntrospectDatabase(connection, undefined, {
207
228
  includeFunctions,
208
229
  includeTriggers,
230
+ onDetailedProgress: (update) => {
231
+ progress.updateItem(update.step, { status: update.status, count: update.count });
232
+ },
209
233
  });
210
- spinner.succeed(`Found ${dbSchema.tables.length} tables`);
234
+ progress.complete();
235
+ console.log('');
211
236
  const ignorePatterns = loadRelqignore(projectRoot);
212
237
  const filteredTables = dbSchema.tables
213
238
  .filter(t => !isTableIgnored(t.name, ignorePatterns).ignored)
@@ -241,12 +266,21 @@ export async function pullCommand(context) {
241
266
  default: c.defaultValue,
242
267
  primaryKey: c.isPrimaryKey,
243
268
  unique: c.isUnique,
269
+ maxLength: c.maxLength ?? null,
270
+ precision: c.precision ?? null,
271
+ scale: c.scale ?? null,
272
+ comment: c.comment || undefined,
273
+ isGenerated: c.isGenerated || false,
274
+ generationExpression: c.generationExpression ?? null,
244
275
  })),
245
276
  indexes: t.indexes.map(i => ({
246
277
  name: i.name,
247
278
  columns: i.columns,
248
279
  unique: i.isUnique,
249
280
  type: i.type,
281
+ definition: i.definition || undefined,
282
+ whereClause: i.whereClause || undefined,
283
+ comment: i.comment || undefined,
250
284
  })),
251
285
  constraints: t.constraints.map(c => ({
252
286
  name: c.name,
@@ -256,6 +290,12 @@ export async function pullCommand(context) {
256
290
  isPartitioned: t.isPartitioned,
257
291
  partitionType: t.partitionType,
258
292
  partitionKey: normalizePartitionKey(t.partitionKey),
293
+ comment: t.comment || undefined,
294
+ partitions: t.childPartitions?.map((p) => ({
295
+ name: p.name,
296
+ bound: p.partitionBound || '',
297
+ boundType: p.partitionType,
298
+ })) || undefined,
259
299
  })),
260
300
  enums: filteredEnums.map(e => ({
261
301
  name: e.name,
@@ -359,24 +399,43 @@ export async function pullCommand(context) {
359
399
  }
360
400
  }
361
401
  else if (!schemaExists) {
362
- console.log('First pull - creating schema');
402
+ console.log(colors.bold('Creating schema') + colors.dim(' (first pull)'));
363
403
  console.log('');
404
+ const tableCount = filteredTables.length;
405
+ const columnCount = filteredTables.reduce((sum, t) => sum + t.columns.length, 0);
364
406
  const indexCount = filteredTables.reduce((sum, t) => sum + (t.indexes?.filter(i => !i.isPrimary).length || 0), 0);
365
- const partitionCount = filteredTables.filter(t => t.isPartitioned).length;
407
+ const partitionedCount = filteredTables.filter(t => t.isPartitioned).length;
408
+ const childPartitionCount = filteredTables.reduce((sum, t) => sum + (t.childPartitions?.length || 0), 0);
366
409
  const tableCommentCount = filteredTables.filter(t => t.comment).length;
367
410
  const columnCommentCount = filteredTables.reduce((sum, t) => sum + t.columns.filter(c => c.comment).length, 0);
368
- console.log('Schema Summary:');
369
- console.log(` ${colors.green(String(filteredTables.length))} tables`);
411
+ const indexCommentCount = filteredTables.reduce((sum, t) => sum + t.indexes.filter(i => i.comment).length, 0);
412
+ const summaryItems = [];
413
+ summaryItems.push({ count: tableCount, label: 'tables' });
414
+ summaryItems.push({ count: columnCount, label: 'columns' });
370
415
  if (indexCount > 0)
371
- console.log(` ${colors.green(String(indexCount))} indexes`);
372
- if (partitionCount > 0)
373
- console.log(` ${colors.green(String(partitionCount))} partitioned tables`);
374
- if (tableCommentCount > 0)
375
- console.log(` ${colors.green(String(tableCommentCount))} table comments`);
376
- if (columnCommentCount > 0)
377
- console.log(` ${colors.green(String(columnCommentCount))} column comments`);
416
+ summaryItems.push({ count: indexCount, label: 'indexes' });
417
+ if (partitionedCount > 0) {
418
+ const partLabel = childPartitionCount > 0
419
+ ? `partitioned (${childPartitionCount} child partitions)`
420
+ : 'partitioned';
421
+ summaryItems.push({ count: partitionedCount, label: partLabel });
422
+ }
423
+ if (tableCommentCount > 0 || columnCommentCount > 0 || indexCommentCount > 0) {
424
+ const commentParts = [];
425
+ if (tableCommentCount > 0)
426
+ commentParts.push(`${tableCommentCount} table`);
427
+ if (columnCommentCount > 0)
428
+ commentParts.push(`${columnCommentCount} column`);
429
+ if (indexCommentCount > 0)
430
+ commentParts.push(`${indexCommentCount} index`);
431
+ summaryItems.push({ count: tableCommentCount + columnCommentCount + indexCommentCount, label: `comments (${commentParts.join(', ')})` });
432
+ }
378
433
  if (dbSchema.extensions.length > 0)
379
- console.log(` ${colors.green(String(dbSchema.extensions.length))} extensions`);
434
+ summaryItems.push({ count: dbSchema.extensions.length, label: 'extensions' });
435
+ for (const item of summaryItems) {
436
+ const countStr = String(item.count).padStart(4);
437
+ console.log(`${colors.green('[+]')} ${colors.green(countStr)} ${item.label}`);
438
+ }
380
439
  console.log('');
381
440
  }
382
441
  if (dryRun) {
@@ -385,13 +444,10 @@ export async function pullCommand(context) {
385
444
  return;
386
445
  }
387
446
  spinner.start('Generating TypeScript schema...');
388
- const typescript = generateTypeScript(dbSchema, {
389
- includeDefineTables: true,
390
- includeSchema: true,
391
- includeIndexes: true,
392
- includeFunctions,
393
- includeTriggers,
447
+ const parsedSchema = await introspectedToParsedSchema(dbSchema);
448
+ const typescript = generateTypeScriptFromAST(parsedSchema, {
394
449
  camelCase: config.generate?.camelCase ?? true,
450
+ importPath: 'relq/schema-builder',
395
451
  });
396
452
  spinner.succeed('Generated TypeScript schema');
397
453
  const schemaDir = path.dirname(schemaPath);
@@ -494,6 +550,7 @@ export async function pullCommand(context) {
494
550
  triggers: filteredTriggers || [],
495
551
  };
496
552
  const schemaChanges = compareSchemas(beforeSchema, afterSchema);
553
+ applyTrackingIdsToSnapshot(typescript, currentSchema);
497
554
  saveSnapshot(currentSchema, projectRoot);
498
555
  const duration = Date.now() - startTime;
499
556
  if (noCommit) {
@@ -516,6 +573,7 @@ export async function pullCommand(context) {
516
573
  const connectionDesc = getConnectionDescription(connection);
517
574
  const commitMessage = `pull: sync from ${connectionDesc}`;
518
575
  const commit = createCommit(currentSchema, author, commitMessage, projectRoot);
576
+ markCommitAsPulled(commit.hash, connection, projectRoot);
519
577
  clearWorkingState(projectRoot);
520
578
  console.log('');
521
579
  console.log(`Pull completed in ${formatDuration(duration)}`);
@@ -598,3 +656,26 @@ function detectObjectConflicts(local, remote) {
598
656
  }
599
657
  return conflicts;
600
658
  }
659
+ function applyTrackingIdsToSnapshot(typescript, snapshot) {
660
+ for (const table of snapshot.tables) {
661
+ const tablePattern = new RegExp(`defineTable\\s*\\(\\s*['"]${table.name}['"]\\s*,\\s*\\{[^}]+\\}\\s*,\\s*\\{[^}]*\\$trackingId:\\s*['"]([^'"]+)['"]`, 's');
662
+ const tableMatch = typescript.match(tablePattern);
663
+ if (tableMatch) {
664
+ table.trackingId = tableMatch[1];
665
+ }
666
+ for (const col of table.columns) {
667
+ const colPattern = new RegExp(`(?:${col.tsName}|${col.name}):\\s*\\w+\\([^)]*\\)[^\\n]*\\.\\\$id\\(['"]([^'"]+)['"]\\)`);
668
+ const colMatch = typescript.match(colPattern);
669
+ if (colMatch) {
670
+ col.trackingId = colMatch[1];
671
+ }
672
+ }
673
+ for (const idx of table.indexes) {
674
+ const idxPattern = new RegExp(`index\\s*\\(\\s*['"]${idx.name}['"]\\s*\\)[^\\n]*\\.\\\$id\\(['"]([^'"]+)['"]\\)`);
675
+ const idxMatch = typescript.match(idxPattern);
676
+ if (idxMatch) {
677
+ idx.trackingId = idxMatch[1];
678
+ }
679
+ }
680
+ }
681
+ }
@@ -5,7 +5,7 @@ import { getConnectionDescription } from "../utils/env-loader.js";
5
5
  import { colors, createSpinner, fatal, confirm, warning } from "../utils/cli-utils.js";
6
6
  import { fastIntrospectDatabase } from "../utils/fast-introspect.js";
7
7
  import { loadRelqignore, isTableIgnored, isColumnIgnored, isEnumIgnored, isDomainIgnored, isFunctionIgnored, } from "../utils/relqignore.js";
8
- import { isInitialized, getHead, shortHash, fetchRemoteCommits, pushCommit, ensureRemoteTable, getAllCommits, loadSnapshot, } from "../utils/repo-manager.js";
8
+ import { isInitialized, getHead, shortHash, fetchRemoteCommits, pushCommit, ensureRemoteTable, getAllCommits, loadSnapshot, isCommitSyncedWith, markCommitAsPushed, getConnectionLabel, } from "../utils/repo-manager.js";
9
9
  export async function pushCommand(context) {
10
10
  const { config, flags } = context;
11
11
  if (!config) {
@@ -41,7 +41,15 @@ export async function pushCommand(context) {
41
41
  const localCommits = getAllCommits(projectRoot);
42
42
  const remoteHashes = new Set(remoteCommits.map(c => c.hash));
43
43
  const localHashes = new Set(localCommits.map(c => c.hash));
44
- const toPush = localCommits.filter(c => !remoteHashes.has(c.hash));
44
+ const toPush = localCommits.filter(c => {
45
+ if (remoteHashes.has(c.hash))
46
+ return false;
47
+ const syncStatus = isCommitSyncedWith(c, connection);
48
+ if (syncStatus.synced) {
49
+ return false;
50
+ }
51
+ return true;
52
+ });
45
53
  const remoteMissing = remoteCommits.filter(c => !localHashes.has(c.hash));
46
54
  spinner.succeed('Checked remote commits');
47
55
  spinner.start('Introspecting remote database...');
@@ -131,9 +139,10 @@ export async function pushCommand(context) {
131
139
  const commitsToProcess = [...toPush].reverse();
132
140
  spinner.start(`Pushing ${toPush.length} commit(s)...`);
133
141
  for (const commit of commitsToProcess) {
134
- await pushCommit(connection, commit);
142
+ await pushCommit(connection, commit, projectRoot);
143
+ markCommitAsPushed(commit.hash, connection, projectRoot);
135
144
  }
136
- spinner.succeed(`Pushed ${toPush.length} commit(s)`);
145
+ spinner.succeed(`Pushed ${toPush.length} commit(s) to ${getConnectionLabel(connection)}`);
137
146
  }
138
147
  if (applySQL) {
139
148
  spinner.start('Applying SQL changes...');
@@ -173,6 +182,32 @@ export async function pushCommand(context) {
173
182
  }
174
183
  await client.query('COMMIT');
175
184
  spinner.succeed(`Applied ${statementsRun} statement(s) from ${sqlExecuted} commit(s)`);
185
+ let hasRenameOperations = false;
186
+ for (const commit of commitsToProcess) {
187
+ const commitPath = path.join(projectRoot, '.relq', 'commits', `${commit.hash}.json`);
188
+ if (fs.existsSync(commitPath)) {
189
+ const enhancedCommit = JSON.parse(fs.readFileSync(commitPath, 'utf-8'));
190
+ if (enhancedCommit.changes?.some((c) => c.type === 'RENAME')) {
191
+ hasRenameOperations = true;
192
+ break;
193
+ }
194
+ }
195
+ }
196
+ if (hasRenameOperations) {
197
+ spinner.start('Updating snapshot after RENAME...');
198
+ try {
199
+ const updatedSchema = await fastIntrospectDatabase(connection, undefined, {
200
+ includeFunctions,
201
+ includeTriggers,
202
+ });
203
+ const snapshotPath = path.join(projectRoot, '.relq', 'snapshot.json');
204
+ fs.writeFileSync(snapshotPath, JSON.stringify(updatedSchema, null, 2));
205
+ spinner.succeed('Snapshot updated with renamed objects');
206
+ }
207
+ catch (e) {
208
+ spinner.warn('Could not update snapshot after RENAME - run "relq pull" manually');
209
+ }
210
+ }
176
211
  }
177
212
  catch (error) {
178
213
  try {
@@ -62,7 +62,14 @@ function parseArgs(argv) {
62
62
  flags[arg.slice(2, eqIndex)] = arg.slice(eqIndex + 1);
63
63
  }
64
64
  else {
65
- flags[arg.slice(2)] = true;
65
+ const nextArg = argv[i + 1];
66
+ if (nextArg && !nextArg.startsWith('-')) {
67
+ flags[arg.slice(2)] = nextArg;
68
+ i++;
69
+ }
70
+ else {
71
+ flags[arg.slice(2)] = true;
72
+ }
66
73
  }
67
74
  }
68
75
  else if (arg.startsWith('-') && arg.length === 2) {
@@ -175,6 +182,7 @@ async function main() {
175
182
  printHelp();
176
183
  process.exit(1);
177
184
  }
185
+ console.log('Command:', command);
178
186
  let config = null;
179
187
  let resolvedProjectRoot = process.cwd();
180
188
  if (requiresConfig(command)) {