relq 1.0.1 → 1.0.3

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 (194) hide show
  1. package/dist/cjs/addon/buffer/index.cjs +1881 -0
  2. package/dist/cjs/addon/pg/index.cjs +4812 -0
  3. package/dist/cjs/addon/pg-cursor/index.cjs +1451 -0
  4. package/dist/cjs/addon/pg-format/index.cjs +2270 -0
  5. package/dist/cjs/cli/commands/add.cjs +430 -25
  6. package/dist/cjs/cli/commands/branch.cjs +131 -0
  7. package/dist/cjs/cli/commands/checkout.cjs +121 -0
  8. package/dist/cjs/cli/commands/cherry-pick.cjs +282 -0
  9. package/dist/cjs/cli/commands/commit.cjs +21 -29
  10. package/dist/cjs/cli/commands/diff.cjs +144 -69
  11. package/dist/cjs/cli/commands/export.cjs +70 -11
  12. package/dist/cjs/cli/commands/fetch.cjs +42 -18
  13. package/dist/cjs/cli/commands/generate.cjs +28 -54
  14. package/dist/cjs/cli/commands/history.cjs +19 -40
  15. package/dist/cjs/cli/commands/import.cjs +305 -41
  16. package/dist/cjs/cli/commands/init.cjs +69 -59
  17. package/dist/cjs/cli/commands/introspect.cjs +4 -8
  18. package/dist/cjs/cli/commands/log.cjs +84 -15
  19. package/dist/cjs/cli/commands/merge.cjs +207 -0
  20. package/dist/cjs/cli/commands/migrate.cjs +13 -26
  21. package/dist/cjs/cli/commands/pull.cjs +321 -95
  22. package/dist/cjs/cli/commands/push.cjs +228 -52
  23. package/dist/cjs/cli/commands/remote.cjs +17 -0
  24. package/dist/cjs/cli/commands/reset.cjs +148 -0
  25. package/dist/cjs/cli/commands/resolve.cjs +191 -0
  26. package/dist/cjs/cli/commands/rollback.cjs +17 -39
  27. package/dist/cjs/cli/commands/stash.cjs +152 -0
  28. package/dist/cjs/cli/commands/status.cjs +52 -9
  29. package/dist/cjs/cli/commands/sync.cjs +30 -50
  30. package/dist/cjs/cli/commands/tag.cjs +146 -0
  31. package/dist/cjs/cli/index.cjs +117 -10
  32. package/dist/cjs/cli/utils/change-tracker.cjs +107 -3
  33. package/dist/cjs/cli/utils/cli-utils.cjs +217 -0
  34. package/dist/cjs/cli/utils/commit-manager.cjs +3 -3
  35. package/dist/cjs/cli/utils/config-loader.cjs +34 -8
  36. package/dist/cjs/cli/utils/env-loader.cjs +3 -2
  37. package/dist/cjs/cli/utils/fast-introspect.cjs +110 -4
  38. package/dist/cjs/cli/utils/git-utils.cjs +42 -161
  39. package/dist/cjs/cli/utils/pool-manager.cjs +156 -0
  40. package/dist/cjs/cli/utils/project-root.cjs +107 -0
  41. package/dist/cjs/cli/utils/relqignore.cjs +297 -38
  42. package/dist/cjs/cli/utils/repo-manager.cjs +92 -3
  43. package/dist/cjs/cli/utils/schema-comparator.cjs +301 -11
  44. package/dist/cjs/cli/utils/schema-diff.cjs +202 -1
  45. package/dist/cjs/cli/utils/schema-hash.cjs +2 -1
  46. package/dist/cjs/cli/utils/schema-introspect.cjs +9 -5
  47. package/dist/cjs/cli/utils/snapshot-manager.cjs +1 -0
  48. package/dist/cjs/cli/utils/spinner.cjs +14 -106
  49. package/dist/cjs/cli/utils/sql-generator.cjs +2 -2
  50. package/dist/cjs/cli/utils/sql-parser.cjs +94 -7
  51. package/dist/cjs/cli/utils/type-generator.cjs +28 -16
  52. package/dist/cjs/condition/array-condition-builder.cjs +1 -1
  53. package/dist/cjs/condition/condition-collector.cjs +1 -1
  54. package/dist/cjs/condition/fulltext-condition-builder.cjs +1 -1
  55. package/dist/cjs/condition/geometric-condition-builder.cjs +1 -1
  56. package/dist/cjs/condition/jsonb-condition-builder.cjs +1 -1
  57. package/dist/cjs/condition/network-condition-builder.cjs +1 -1
  58. package/dist/cjs/condition/range-condition-builder.cjs +1 -1
  59. package/dist/cjs/copy/copy-builder.cjs +1 -1
  60. package/dist/cjs/core/query-builder.cjs +1 -1
  61. package/dist/cjs/core/relq-client.cjs +2 -2
  62. package/dist/cjs/count/count-builder.cjs +1 -1
  63. package/dist/cjs/cte/cte-builder.cjs +1 -1
  64. package/dist/cjs/delete/delete-builder.cjs +1 -1
  65. package/dist/cjs/function/create-function-builder.cjs +1 -1
  66. package/dist/cjs/functions/advanced-functions.cjs +1 -1
  67. package/dist/cjs/functions/case-builder.cjs +1 -1
  68. package/dist/cjs/functions/geometric-functions.cjs +1 -1
  69. package/dist/cjs/functions/network-functions.cjs +1 -1
  70. package/dist/cjs/functions/sql-functions.cjs +1 -1
  71. package/dist/cjs/indexing/create-index-builder.cjs +1 -1
  72. package/dist/cjs/indexing/drop-index-builder.cjs +1 -1
  73. package/dist/cjs/insert/conflict-builder.cjs +1 -1
  74. package/dist/cjs/insert/insert-builder.cjs +1 -1
  75. package/dist/cjs/maintenance/vacuum-builder.cjs +1 -1
  76. package/dist/cjs/pubsub/listen-notify-builder.cjs +1 -1
  77. package/dist/cjs/pubsub/listener-connection.cjs +2 -2
  78. package/dist/cjs/raw/raw-query-builder.cjs +1 -1
  79. package/dist/cjs/schema/schema-builder.cjs +1 -1
  80. package/dist/cjs/schema-definition/table-definition.cjs +1 -1
  81. package/dist/cjs/select/aggregate-builder.cjs +1 -1
  82. package/dist/cjs/select/select-builder.cjs +1 -1
  83. package/dist/cjs/sequence/sequence-builder.cjs +1 -1
  84. package/dist/cjs/table/alter-table-builder.cjs +1 -1
  85. package/dist/cjs/table/constraint-builder.cjs +1 -1
  86. package/dist/cjs/table/create-table-builder.cjs +1 -1
  87. package/dist/cjs/table/partition-builder.cjs +1 -1
  88. package/dist/cjs/table/truncate-builder.cjs +1 -1
  89. package/dist/cjs/transaction/transaction-builder.cjs +1 -1
  90. package/dist/cjs/trigger/create-trigger-builder.cjs +1 -1
  91. package/dist/cjs/update/array-update-builder.cjs +1 -1
  92. package/dist/cjs/update/update-builder.cjs +1 -1
  93. package/dist/cjs/utils/index.cjs +1 -1
  94. package/dist/cjs/view/create-view-builder.cjs +1 -1
  95. package/dist/cjs/window/window-builder.cjs +1 -1
  96. package/dist/config.d.ts +16 -25
  97. package/dist/esm/cli/commands/add.js +399 -27
  98. package/dist/esm/cli/commands/branch.js +95 -0
  99. package/dist/esm/cli/commands/checkout.js +85 -0
  100. package/dist/esm/cli/commands/cherry-pick.js +246 -0
  101. package/dist/esm/cli/commands/commit.js +22 -30
  102. package/dist/esm/cli/commands/diff.js +144 -69
  103. package/dist/esm/cli/commands/export.js +71 -12
  104. package/dist/esm/cli/commands/fetch.js +42 -18
  105. package/dist/esm/cli/commands/generate.js +28 -54
  106. package/dist/esm/cli/commands/history.js +11 -32
  107. package/dist/esm/cli/commands/import.js +306 -42
  108. package/dist/esm/cli/commands/init.js +65 -55
  109. package/dist/esm/cli/commands/introspect.js +4 -8
  110. package/dist/esm/cli/commands/log.js +78 -10
  111. package/dist/esm/cli/commands/merge.js +171 -0
  112. package/dist/esm/cli/commands/migrate.js +13 -26
  113. package/dist/esm/cli/commands/pull.js +313 -87
  114. package/dist/esm/cli/commands/push.js +223 -47
  115. package/dist/esm/cli/commands/remote.js +14 -0
  116. package/dist/esm/cli/commands/reset.js +112 -0
  117. package/dist/esm/cli/commands/resolve.js +155 -0
  118. package/dist/esm/cli/commands/rollback.js +17 -39
  119. package/dist/esm/cli/commands/stash.js +116 -0
  120. package/dist/esm/cli/commands/status.js +20 -10
  121. package/dist/esm/cli/commands/sync.js +30 -50
  122. package/dist/esm/cli/commands/tag.js +110 -0
  123. package/dist/esm/cli/index.js +118 -11
  124. package/dist/esm/cli/utils/change-tracker.js +107 -3
  125. package/dist/esm/cli/utils/cli-utils.js +169 -0
  126. package/dist/esm/cli/utils/commit-manager.js +3 -3
  127. package/dist/esm/cli/utils/config-loader.js +34 -8
  128. package/dist/esm/cli/utils/env-loader.js +3 -2
  129. package/dist/esm/cli/utils/fast-introspect.js +110 -4
  130. package/dist/esm/cli/utils/git-utils.js +2 -124
  131. package/dist/esm/cli/utils/pool-manager.js +114 -0
  132. package/dist/esm/cli/utils/project-root.js +69 -0
  133. package/dist/esm/cli/utils/relqignore.js +278 -37
  134. package/dist/esm/cli/utils/repo-manager.js +83 -3
  135. package/dist/esm/cli/utils/schema-comparator.js +301 -11
  136. package/dist/esm/cli/utils/schema-diff.js +202 -1
  137. package/dist/esm/cli/utils/schema-hash.js +2 -1
  138. package/dist/esm/cli/utils/schema-introspect.js +9 -5
  139. package/dist/esm/cli/utils/snapshot-manager.js +1 -0
  140. package/dist/esm/cli/utils/spinner.js +1 -101
  141. package/dist/esm/cli/utils/sql-generator.js +2 -2
  142. package/dist/esm/cli/utils/sql-parser.js +94 -7
  143. package/dist/esm/cli/utils/type-generator.js +28 -16
  144. package/dist/esm/condition/array-condition-builder.js +1 -1
  145. package/dist/esm/condition/condition-collector.js +1 -1
  146. package/dist/esm/condition/fulltext-condition-builder.js +1 -1
  147. package/dist/esm/condition/geometric-condition-builder.js +1 -1
  148. package/dist/esm/condition/jsonb-condition-builder.js +1 -1
  149. package/dist/esm/condition/network-condition-builder.js +1 -1
  150. package/dist/esm/condition/range-condition-builder.js +1 -1
  151. package/dist/esm/copy/copy-builder.js +1 -1
  152. package/dist/esm/core/query-builder.js +1 -1
  153. package/dist/esm/core/relq-client.js +2 -2
  154. package/dist/esm/count/count-builder.js +1 -1
  155. package/dist/esm/cte/cte-builder.js +1 -1
  156. package/dist/esm/delete/delete-builder.js +1 -1
  157. package/dist/esm/function/create-function-builder.js +1 -1
  158. package/dist/esm/functions/advanced-functions.js +1 -1
  159. package/dist/esm/functions/case-builder.js +1 -1
  160. package/dist/esm/functions/geometric-functions.js +1 -1
  161. package/dist/esm/functions/network-functions.js +1 -1
  162. package/dist/esm/functions/sql-functions.js +1 -1
  163. package/dist/esm/indexing/create-index-builder.js +1 -1
  164. package/dist/esm/indexing/drop-index-builder.js +1 -1
  165. package/dist/esm/insert/conflict-builder.js +1 -1
  166. package/dist/esm/insert/insert-builder.js +1 -1
  167. package/dist/esm/maintenance/vacuum-builder.js +1 -1
  168. package/dist/esm/pubsub/listen-notify-builder.js +1 -1
  169. package/dist/esm/pubsub/listener-connection.js +2 -2
  170. package/dist/esm/raw/raw-query-builder.js +1 -1
  171. package/dist/esm/schema/schema-builder.js +1 -1
  172. package/dist/esm/schema-definition/table-definition.js +1 -1
  173. package/dist/esm/select/aggregate-builder.js +1 -1
  174. package/dist/esm/select/select-builder.js +1 -1
  175. package/dist/esm/sequence/sequence-builder.js +1 -1
  176. package/dist/esm/table/alter-table-builder.js +1 -1
  177. package/dist/esm/table/constraint-builder.js +1 -1
  178. package/dist/esm/table/create-table-builder.js +1 -1
  179. package/dist/esm/table/partition-builder.js +1 -1
  180. package/dist/esm/table/truncate-builder.js +1 -1
  181. package/dist/esm/transaction/transaction-builder.js +1 -1
  182. package/dist/esm/trigger/create-trigger-builder.js +1 -1
  183. package/dist/esm/update/array-update-builder.js +1 -1
  184. package/dist/esm/update/update-builder.js +1 -1
  185. package/dist/esm/utils/index.js +1 -1
  186. package/dist/esm/view/create-view-builder.js +1 -1
  187. package/dist/esm/window/window-builder.js +1 -1
  188. package/dist/index.d.ts +25 -8
  189. package/dist/schema-builder.d.ts +16 -6
  190. package/package.json +1 -1
  191. /package/dist/{addons/buffer.js → esm/addon/buffer/index.js} +0 -0
  192. /package/dist/{addons/pg.js → esm/addon/pg/index.js} +0 -0
  193. /package/dist/{addons/pg-cursor.js → esm/addon/pg-cursor/index.js} +0 -0
  194. /package/dist/{addons/pg-format.js → esm/addon/pg-format/index.js} +0 -0
@@ -36,17 +36,32 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.pullCommand = pullCommand;
37
37
  const fs = __importStar(require("fs"));
38
38
  const path = __importStar(require("path"));
39
- const readline = __importStar(require("readline"));
40
39
  const config_loader_1 = require("../utils/config-loader.cjs");
41
40
  const fast_introspect_1 = require("../utils/fast-introspect.cjs");
42
41
  const type_generator_1 = require("../utils/type-generator.cjs");
43
42
  const env_loader_1 = require("../utils/env-loader.cjs");
44
- const spinner_1 = require("../utils/spinner.cjs");
43
+ const cli_utils_1 = require("../utils/cli-utils.cjs");
45
44
  const relqignore_1 = require("../utils/relqignore.cjs");
46
45
  const repo_manager_1 = require("../utils/repo-manager.cjs");
46
+ const schema_comparator_1 = require("../utils/schema-comparator.cjs");
47
47
  function toCamelCase(str) {
48
48
  return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
49
49
  }
50
+ function normalizePartitionKey(partitionKey) {
51
+ if (!partitionKey)
52
+ return undefined;
53
+ if (Array.isArray(partitionKey)) {
54
+ return partitionKey;
55
+ }
56
+ if (typeof partitionKey === 'string') {
57
+ return partitionKey
58
+ .replace(/^\{|\}$/g, '')
59
+ .split(',')
60
+ .map(k => k.trim())
61
+ .filter(Boolean);
62
+ }
63
+ return undefined;
64
+ }
50
65
  function parseSchemaFileForSnapshot(schemaPath) {
51
66
  if (!fs.existsSync(schemaPath)) {
52
67
  return null;
@@ -152,86 +167,70 @@ function parseSchemaFileForSnapshot(schemaPath) {
152
167
  extensions: [],
153
168
  };
154
169
  }
155
- function askConfirm(question, defaultYes = true) {
156
- const suffix = defaultYes ? spinner_1.colors.muted('[Y/n]') : spinner_1.colors.muted('[y/N]');
157
- const rl = readline.createInterface({
158
- input: process.stdin,
159
- output: process.stdout,
160
- });
161
- return new Promise((resolve) => {
162
- rl.question(`${question} ${suffix}: `, (answer) => {
163
- rl.close();
164
- const a = answer.trim().toLowerCase();
165
- if (!a)
166
- resolve(defaultYes);
167
- else
168
- resolve(a === 'y' || a === 'yes');
169
- });
170
- });
171
- }
172
170
  async function pullCommand(context) {
173
- const { config, flags } = context;
171
+ const { config, flags, projectRoot } = context;
174
172
  if (!config) {
175
- console.error('Error: No configuration found.');
176
- process.exit(1);
173
+ (0, cli_utils_1.fatal)('No configuration found', `Run ${cli_utils_1.colors.cyan('relq init')} to create one.`);
177
174
  }
178
- (0, config_loader_1.requireValidConfig)(config);
175
+ await (0, config_loader_1.requireValidConfig)(config, { calledFrom: 'pull' });
179
176
  const connection = config.connection;
180
- const projectRoot = process.cwd();
181
177
  const force = flags['force'] === true;
178
+ const merge = flags['merge'] === true;
179
+ const noCommit = flags['no-commit'] === true;
182
180
  const dryRun = flags['dry-run'] === true;
183
181
  const author = config.author || 'Relq CLI';
184
- const schemaPath = typeof config.schema === 'string' ? config.schema : './db/schema.ts';
182
+ const schemaPathRaw = typeof config.schema === 'string' ? config.schema : './db/schema.ts';
183
+ const schemaPath = path.resolve(projectRoot, schemaPathRaw);
185
184
  const includeFunctions = config.generate?.includeFunctions ?? false;
186
185
  const includeTriggers = config.generate?.includeTriggers ?? false;
187
- const spinner = (0, spinner_1.createSpinner)();
186
+ const spinner = (0, cli_utils_1.createSpinner)();
188
187
  const startTime = Date.now();
189
188
  console.log('');
190
189
  if (!(0, repo_manager_1.isInitialized)(projectRoot)) {
191
190
  (0, repo_manager_1.initRepository)(projectRoot);
192
- console.log(`${spinner_1.colors.cyan('ℹ')} Initialized .relq folder`);
191
+ console.log('Initialized .relq folder');
193
192
  }
194
- if (!force) {
195
- const stagedChanges = (0, repo_manager_1.getStagedChanges)(projectRoot);
196
- const unstagedChanges = (0, repo_manager_1.getUnstagedChanges)(projectRoot);
197
- if (stagedChanges.length > 0 || unstagedChanges.length > 0) {
193
+ const stagedChanges = (0, repo_manager_1.getStagedChanges)(projectRoot);
194
+ const unstagedChanges = (0, repo_manager_1.getUnstagedChanges)(projectRoot);
195
+ const hasLocalChanges = stagedChanges.length > 0 || unstagedChanges.length > 0;
196
+ let stashedSchemaContent = null;
197
+ const schemaFileExists = fs.existsSync(schemaPath);
198
+ if (hasLocalChanges) {
199
+ if (force) {
200
+ (0, repo_manager_1.clearWorkingState)(projectRoot);
201
+ }
202
+ else if (merge) {
203
+ if (schemaFileExists) {
204
+ stashedSchemaContent = fs.readFileSync(schemaPath, 'utf-8');
205
+ }
206
+ (0, repo_manager_1.clearWorkingState)(projectRoot);
207
+ console.log(`Stashed local file content for restore after pull`);
208
+ }
209
+ else {
198
210
  const hasUnstaged = unstagedChanges.length > 0;
199
211
  const hasStaged = stagedChanges.length > 0;
200
- if (hasUnstaged && hasStaged) {
201
- console.error(spinner_1.colors.red('Error: You have uncommitted and unstaged changes.'));
202
- }
203
- else if (hasStaged) {
204
- console.error(spinner_1.colors.red('Error: You have uncommitted changes.'));
205
- }
206
- else {
207
- console.error(spinner_1.colors.red('Error: You have unstaged changes.'));
208
- }
209
212
  console.log('');
210
213
  if (hasStaged) {
211
- console.log(` ${spinner_1.colors.green('Staged (uncommitted):')} ${stagedChanges.length} change(s)`);
214
+ console.log(` ${cli_utils_1.colors.green('Staged (uncommitted):')} ${stagedChanges.length} change(s)`);
212
215
  }
213
216
  if (hasUnstaged) {
214
- console.log(` ${spinner_1.colors.red('Unstaged:')} ${unstagedChanges.length} change(s)`);
217
+ console.log(` ${cli_utils_1.colors.red('Unstaged:')} ${unstagedChanges.length} change(s)`);
215
218
  }
216
- console.log('');
217
- if (hasUnstaged && !hasStaged) {
218
- console.log('Please stage and commit, or reset your changes:');
219
- console.log(` ${spinner_1.colors.cyan('relq add .')} - stage all changes`);
220
- console.log(` ${spinner_1.colors.cyan('relq commit -m "message"')} - then commit`);
219
+ let errorMsg = 'You have uncommitted changes';
220
+ if (hasUnstaged && hasStaged) {
221
+ errorMsg = 'You have uncommitted and unstaged changes';
221
222
  }
222
- else {
223
- console.log('Please commit or reset your changes:');
224
- console.log(` ${spinner_1.colors.cyan('relq commit -m "message"')} - commit staged changes`);
223
+ else if (hasUnstaged) {
224
+ errorMsg = 'You have unstaged changes';
225
225
  }
226
- console.log(` ${spinner_1.colors.cyan('relq reset')} - discard all changes`);
227
- console.log(` ${spinner_1.colors.cyan('relq pull --force')} - force pull (overwrites local changes)`);
228
- return;
226
+ const hint = `Commit first: ${cli_utils_1.colors.cyan('relq add . && relq commit -m "message"')}\nOr merge: ${cli_utils_1.colors.cyan('relq pull --merge')} (preserve local edits)\nOr force: ${cli_utils_1.colors.cyan('relq pull --force')} (discard & pull)`;
227
+ (0, cli_utils_1.fatal)(errorMsg, hint);
229
228
  }
230
229
  }
231
230
  try {
232
231
  spinner.start('Connecting to database...');
233
232
  await (0, repo_manager_1.ensureRemoteTable)(connection);
234
- spinner.succeed(`Connected to ${spinner_1.colors.cyan((0, env_loader_1.getConnectionDescription)(connection))}`);
233
+ spinner.succeed(`Connected to ${cli_utils_1.colors.cyan((0, env_loader_1.getConnectionDescription)(connection))}`);
235
234
  spinner.start('Fetching remote commits...');
236
235
  const remoteCommits = await (0, repo_manager_1.fetchRemoteCommits)(connection, 100);
237
236
  const remoteHead = remoteCommits.length > 0 ? remoteCommits[0].hash : null;
@@ -245,8 +244,24 @@ async function pullCommand(context) {
245
244
  includeTriggers,
246
245
  });
247
246
  spinner.succeed(`Found ${dbSchema.tables.length} tables`);
248
- const ignorePatterns = (0, relqignore_1.getIgnorePatterns)(projectRoot);
249
- const filteredTables = (0, relqignore_1.filterIgnored)(dbSchema.tables, ignorePatterns);
247
+ const ignorePatterns = (0, relqignore_1.loadRelqignore)(projectRoot);
248
+ const filteredTables = dbSchema.tables
249
+ .filter(t => !(0, relqignore_1.isTableIgnored)(t.name, ignorePatterns).ignored)
250
+ .map(t => ({
251
+ ...t,
252
+ columns: t.columns.filter(c => !(0, relqignore_1.isColumnIgnored)(t.name, c.name, ignorePatterns).ignored),
253
+ indexes: t.indexes.filter(i => !(0, relqignore_1.isIndexIgnored)(t.name, i.name, ignorePatterns).ignored),
254
+ constraints: t.constraints.filter(c => !(0, relqignore_1.isConstraintIgnored)(t.name, c.name, ignorePatterns).ignored),
255
+ }));
256
+ const filteredEnums = dbSchema.enums.filter(e => !(0, relqignore_1.isEnumIgnored)(e.name, ignorePatterns).ignored);
257
+ const filteredDomains = dbSchema.domains.filter(d => !(0, relqignore_1.isDomainIgnored)(d.name, ignorePatterns).ignored);
258
+ const filteredCompositeTypes = dbSchema.compositeTypes.filter(c => !(0, relqignore_1.isCompositeTypeIgnored)(c.name, ignorePatterns).ignored);
259
+ const filteredFunctions = includeFunctions
260
+ ? dbSchema.functions.filter(f => !(0, relqignore_1.isFunctionIgnored)(f.name, ignorePatterns).ignored)
261
+ : [];
262
+ const filteredTriggers = includeTriggers
263
+ ? dbSchema.triggers
264
+ : [];
250
265
  const localHead = (0, repo_manager_1.getHead)(projectRoot);
251
266
  const localSnapshot = (0, repo_manager_1.loadSnapshot)(projectRoot);
252
267
  const schemaExists = fs.existsSync(schemaPath);
@@ -274,13 +289,16 @@ async function pullCommand(context) {
274
289
  type: c.type,
275
290
  definition: c.definition,
276
291
  })),
292
+ isPartitioned: t.isPartitioned,
293
+ partitionType: t.partitionType,
294
+ partitionKey: normalizePartitionKey(t.partitionKey),
277
295
  })),
278
- enums: dbSchema.enums.map(e => ({
296
+ enums: filteredEnums.map(e => ({
279
297
  name: e.name,
280
298
  schema: 'public',
281
299
  values: e.values,
282
300
  })),
283
- domains: dbSchema.domains.map(d => ({
301
+ domains: filteredDomains.map(d => ({
284
302
  name: d.name,
285
303
  schema: 'public',
286
304
  baseType: d.baseType,
@@ -288,20 +306,20 @@ async function pullCommand(context) {
288
306
  default: d.defaultValue || null,
289
307
  check: d.checkExpression || null,
290
308
  })),
291
- compositeTypes: dbSchema.compositeTypes.map(c => ({
309
+ compositeTypes: filteredCompositeTypes.map(c => ({
292
310
  name: c.name,
293
311
  schema: 'public',
294
312
  attributes: c.attributes,
295
313
  })),
296
314
  sequences: [],
297
315
  collations: [],
298
- functions: dbSchema.functions.map(f => ({
316
+ functions: filteredFunctions.map(f => ({
299
317
  name: f.name,
300
318
  returnType: f.returnType,
301
319
  argTypes: f.argTypes,
302
320
  language: f.language,
303
321
  })),
304
- triggers: dbSchema.triggers.map(t => ({
322
+ triggers: filteredTriggers.map(t => ({
305
323
  name: t.name,
306
324
  table: t.tableName,
307
325
  events: [t.event],
@@ -312,59 +330,93 @@ async function pullCommand(context) {
312
330
  extensions: dbSchema.extensions.map(ext => ({ name: ext })),
313
331
  };
314
332
  console.log('');
333
+ const mergeStatePath = path.join(projectRoot, '.relq', 'MERGE_STATE');
334
+ if (fs.existsSync(mergeStatePath) && !force) {
335
+ (0, cli_utils_1.fatal)('You have unresolved merge conflicts', `Use ${cli_utils_1.colors.cyan('relq resolve')} to see and resolve conflicts\nOr use ${cli_utils_1.colors.cyan('relq pull --force')} to overwrite local`);
336
+ }
315
337
  if (schemaExists && localSnapshot && !force) {
316
338
  const localTables = new Set(localSnapshot.tables.map(t => t.name));
317
339
  const remoteTables = new Set(currentSchema.tables.map(t => t.name));
318
340
  const added = [...remoteTables].filter(t => !localTables.has(t));
319
341
  const removed = [...localTables].filter(t => !remoteTables.has(t));
342
+ const conflicts = detectObjectConflicts(localSnapshot, currentSchema);
343
+ if (conflicts.length > 0 && !force) {
344
+ const mergeState = {
345
+ conflicts,
346
+ remoteSnapshot: currentSchema,
347
+ createdAt: new Date().toISOString(),
348
+ };
349
+ fs.writeFileSync(mergeStatePath, JSON.stringify(mergeState, null, 2));
350
+ console.log('');
351
+ console.log(`Both local and remote have modified the same objects:`);
352
+ console.log('');
353
+ for (const c of conflicts.slice(0, 10)) {
354
+ const name = c.parentName ? `${c.parentName}.${c.objectName}` : c.objectName;
355
+ console.log(` ${cli_utils_1.colors.red('conflict:')} ${c.objectType.toLowerCase()} ${cli_utils_1.colors.bold(name)}`);
356
+ console.log(` ${cli_utils_1.colors.muted(c.description)}`);
357
+ }
358
+ if (conflicts.length > 10) {
359
+ console.log(` ${cli_utils_1.colors.muted(`... and ${conflicts.length - 10} more`)}`);
360
+ }
361
+ (0, cli_utils_1.fatal)('Automatic merge failed; fix conflicts and then commit', `${cli_utils_1.colors.cyan('relq resolve --theirs <name>')} Take remote version\n${cli_utils_1.colors.cyan('relq resolve --all-theirs')} Take all remote\n${cli_utils_1.colors.cyan('relq pull --force')} Force overwrite local`);
362
+ }
320
363
  if (added.length === 0 && removed.length === 0) {
321
- console.log(`${spinner_1.colors.green('✓')} Already up to date with remote`);
364
+ console.log('Already up to date with remote');
322
365
  console.log('');
323
366
  return;
324
367
  }
325
- console.log(`${spinner_1.colors.yellow('Remote has changes:')}`);
368
+ console.log(`${cli_utils_1.colors.yellow('Remote has changes:')}`);
326
369
  if (added.length > 0) {
327
- console.log(` ${spinner_1.colors.green(`+${added.length}`)} tables added`);
370
+ console.log(` ${cli_utils_1.colors.green(`+${added.length}`)} tables added`);
328
371
  }
329
372
  if (removed.length > 0) {
330
- console.log(` ${spinner_1.colors.red(`-${removed.length}`)} tables removed`);
373
+ console.log(` ${cli_utils_1.colors.red(`-${removed.length}`)} tables removed`);
331
374
  }
332
375
  console.log('');
333
376
  if (!dryRun) {
334
- const proceed = await askConfirm(`${spinner_1.colors.bold('Pull these changes?')}`, true);
377
+ const proceed = await (0, cli_utils_1.confirm)(`${cli_utils_1.colors.bold('Pull these changes?')}`, true);
335
378
  if (!proceed) {
336
- console.log('');
337
- console.log(`${spinner_1.colors.muted('Cancelled.')}`);
338
- return;
379
+ (0, cli_utils_1.fatal)('Operation cancelled by user');
339
380
  }
340
381
  console.log('');
341
382
  }
342
383
  }
343
384
  else if (schemaExists && !force) {
344
- console.log(`${spinner_1.colors.yellow('⚠')} ${spinner_1.colors.bold('Local schema exists but not tracked')}`);
385
+ (0, cli_utils_1.warning)('Local schema exists but not tracked');
345
386
  console.log('');
346
- console.log(` ${spinner_1.colors.cyan(schemaPath)}`);
387
+ console.log(` ${cli_utils_1.colors.cyan(schemaPath)}`);
347
388
  console.log('');
348
389
  if (!dryRun) {
349
- const proceed = await askConfirm(`${spinner_1.colors.bold('Overwrite local schema?')}`, false);
390
+ const proceed = await (0, cli_utils_1.confirm)(`${cli_utils_1.colors.bold('Overwrite local schema?')}`, false);
350
391
  if (!proceed) {
351
- console.log('');
352
- console.log(`${spinner_1.colors.muted('Cancelled. Run')} ${spinner_1.colors.cyan('relq status')} ${spinner_1.colors.muted('to see current state.')}`);
353
- return;
392
+ (0, cli_utils_1.fatal)('Operation cancelled by user', `Run ${cli_utils_1.colors.cyan('relq status')} to see current state.`);
354
393
  }
355
394
  console.log('');
356
395
  }
357
396
  }
358
397
  else if (!schemaExists) {
359
- console.log(`${spinner_1.colors.cyan('ℹ')} ${spinner_1.colors.bold('First pull - creating schema')}`);
398
+ console.log('First pull - creating schema');
360
399
  console.log('');
361
- console.log(`📊 ${spinner_1.colors.bold('Schema Summary')}`);
362
- console.log(` ${spinner_1.colors.green(String(filteredTables.length))} tables`);
363
- console.log(` ${spinner_1.colors.green(String(dbSchema.extensions.length))} extensions`);
400
+ const indexCount = filteredTables.reduce((sum, t) => sum + (t.indexes?.filter(i => !i.isPrimary).length || 0), 0);
401
+ const partitionCount = filteredTables.filter(t => t.isPartitioned).length;
402
+ const tableCommentCount = filteredTables.filter(t => t.comment).length;
403
+ const columnCommentCount = filteredTables.reduce((sum, t) => sum + t.columns.filter(c => c.comment).length, 0);
404
+ console.log('Schema Summary:');
405
+ console.log(` ${cli_utils_1.colors.green(String(filteredTables.length))} tables`);
406
+ if (indexCount > 0)
407
+ console.log(` ${cli_utils_1.colors.green(String(indexCount))} indexes`);
408
+ if (partitionCount > 0)
409
+ console.log(` ${cli_utils_1.colors.green(String(partitionCount))} partitioned tables`);
410
+ if (tableCommentCount > 0)
411
+ console.log(` ${cli_utils_1.colors.green(String(tableCommentCount))} table comments`);
412
+ if (columnCommentCount > 0)
413
+ console.log(` ${cli_utils_1.colors.green(String(columnCommentCount))} column comments`);
414
+ if (dbSchema.extensions.length > 0)
415
+ console.log(` ${cli_utils_1.colors.green(String(dbSchema.extensions.length))} extensions`);
364
416
  console.log('');
365
417
  }
366
418
  if (dryRun) {
367
- console.log(`${spinner_1.colors.yellow('⚠')} Dry run - no files written`);
419
+ console.log('Dry run - no files written');
368
420
  console.log('');
369
421
  return;
370
422
  }
@@ -385,26 +437,200 @@ async function pullCommand(context) {
385
437
  spinner.start('Writing schema file...');
386
438
  fs.writeFileSync(schemaPath, typescript, 'utf-8');
387
439
  const fileSize = Buffer.byteLength(typescript, 'utf8');
388
- spinner.succeed(`Written ${spinner_1.colors.cyan(schemaPath)} ${spinner_1.colors.muted(`(${(0, spinner_1.formatBytes)(fileSize)})`)}`);
389
- const snapshotFromFile = parseSchemaFileForSnapshot(schemaPath);
390
- if (snapshotFromFile) {
391
- (0, repo_manager_1.saveSnapshot)(snapshotFromFile, projectRoot);
440
+ spinner.succeed(`Written ${cli_utils_1.colors.cyan(schemaPath)} ${cli_utils_1.colors.muted(`(${(0, cli_utils_1.formatBytes)(fileSize)})`)}`);
441
+ const fileHash = (0, repo_manager_1.hashFileContent)(typescript);
442
+ (0, repo_manager_1.saveFileHash)(fileHash, projectRoot);
443
+ const oldSnapshot = (0, repo_manager_1.loadSnapshot)(projectRoot);
444
+ const beforeSchema = oldSnapshot ? {
445
+ extensions: oldSnapshot.extensions?.map(e => e.name) || [],
446
+ enums: oldSnapshot.enums || [],
447
+ domains: oldSnapshot.domains?.map(d => ({
448
+ name: d.name,
449
+ baseType: d.baseType,
450
+ isNotNull: d.notNull,
451
+ defaultValue: d.default,
452
+ checkExpression: d.check,
453
+ })) || [],
454
+ compositeTypes: oldSnapshot.compositeTypes || [],
455
+ sequences: oldSnapshot.sequences || [],
456
+ tables: oldSnapshot.tables.map(t => ({
457
+ name: t.name,
458
+ schema: t.schema,
459
+ columns: t.columns.map(c => ({
460
+ name: c.name,
461
+ dataType: c.type,
462
+ isNullable: c.nullable,
463
+ defaultValue: c.default,
464
+ isPrimaryKey: c.primaryKey,
465
+ isUnique: c.unique,
466
+ comment: c.comment,
467
+ })),
468
+ indexes: t.indexes.map(i => ({
469
+ name: i.name,
470
+ columns: i.columns,
471
+ isUnique: i.unique,
472
+ type: i.type,
473
+ })),
474
+ constraints: t.constraints || [],
475
+ isPartitioned: t.isPartitioned,
476
+ partitionType: t.partitionType,
477
+ partitionKey: t.partitionKey,
478
+ comment: t.comment,
479
+ })),
480
+ functions: oldSnapshot.functions || [],
481
+ triggers: oldSnapshot.triggers || [],
482
+ } : {
483
+ extensions: [],
484
+ enums: [],
485
+ domains: [],
486
+ compositeTypes: [],
487
+ sequences: [],
488
+ tables: [],
489
+ functions: [],
490
+ triggers: [],
491
+ };
492
+ const afterSchema = {
493
+ extensions: dbSchema.extensions || [],
494
+ enums: filteredEnums || [],
495
+ domains: filteredDomains?.map(d => ({
496
+ name: d.name,
497
+ baseType: d.baseType,
498
+ isNotNull: d.isNotNull,
499
+ defaultValue: d.defaultValue,
500
+ checkExpression: d.checkExpression,
501
+ })) || [],
502
+ compositeTypes: filteredCompositeTypes || [],
503
+ sequences: [],
504
+ tables: filteredTables.map(t => ({
505
+ name: t.name,
506
+ schema: t.schema,
507
+ columns: t.columns.map(c => ({
508
+ name: c.name,
509
+ dataType: c.dataType,
510
+ isNullable: c.isNullable,
511
+ defaultValue: c.defaultValue,
512
+ isPrimaryKey: c.isPrimaryKey,
513
+ isUnique: c.isUnique,
514
+ comment: c.comment,
515
+ })),
516
+ indexes: t.indexes.map(i => ({
517
+ name: i.name,
518
+ columns: i.columns,
519
+ isUnique: i.isUnique,
520
+ type: i.type,
521
+ })),
522
+ constraints: t.constraints || [],
523
+ isPartitioned: t.isPartitioned,
524
+ partitionType: t.partitionType,
525
+ partitionKey: t.partitionKey,
526
+ childPartitions: t.childPartitions,
527
+ comment: t.comment,
528
+ })),
529
+ functions: filteredFunctions || [],
530
+ triggers: filteredTriggers || [],
531
+ };
532
+ const schemaChanges = (0, schema_comparator_1.compareSchemas)(beforeSchema, afterSchema);
533
+ (0, repo_manager_1.saveSnapshot)(currentSchema, projectRoot);
534
+ const duration = Date.now() - startTime;
535
+ if (noCommit) {
536
+ if (schemaChanges.length > 0) {
537
+ (0, repo_manager_1.addUnstagedChanges)(schemaChanges, projectRoot);
538
+ spinner.succeed(`Detected ${schemaChanges.length} schema change(s)`);
539
+ }
540
+ console.log('');
541
+ console.log(`Pull completed in ${(0, cli_utils_1.formatDuration)(duration)}`);
542
+ if (schemaChanges.length > 0) {
543
+ console.log('');
544
+ console.log(`${cli_utils_1.colors.green(String(schemaChanges.length))} change(s) ready to stage`);
545
+ console.log(`hint: run ${cli_utils_1.colors.cyan("'relq add .'")} to stage all changes`);
546
+ }
547
+ else {
548
+ console.log('Already up to date');
549
+ }
392
550
  }
393
551
  else {
394
- (0, repo_manager_1.saveSnapshot)(currentSchema, projectRoot);
552
+ const connectionDesc = (0, env_loader_1.getConnectionDescription)(connection);
553
+ const commitMessage = `pull: sync from ${connectionDesc}`;
554
+ const commit = (0, repo_manager_1.createCommit)(currentSchema, author, commitMessage, projectRoot);
555
+ (0, repo_manager_1.clearWorkingState)(projectRoot);
556
+ console.log('');
557
+ console.log(`Pull completed in ${(0, cli_utils_1.formatDuration)(duration)}`);
558
+ console.log('');
559
+ console.log(`${cli_utils_1.colors.yellow('→')} ${(0, repo_manager_1.shortHash)(commit.hash)} ${commitMessage}`);
560
+ console.log(` ${cli_utils_1.colors.green(String(commit.stats.tables))} tables, ${cli_utils_1.colors.green(String(commit.stats.columns))} columns`);
561
+ if (stashedSchemaContent) {
562
+ fs.writeFileSync(schemaPath, stashedSchemaContent, 'utf-8');
563
+ console.log('');
564
+ console.log(`${cli_utils_1.colors.yellow('Restored')} local schema file content`);
565
+ console.log(`hint: run ${cli_utils_1.colors.cyan("'relq add .'")} to detect changes against the new snapshot`);
566
+ }
395
567
  }
396
- spinner.start('Creating commit...');
397
- const message = localHead ? 'Pulled schema from database' : 'Initial schema pull';
398
- const commit = (0, repo_manager_1.createCommit)(currentSchema, author, message, projectRoot);
399
- spinner.succeed(`Created commit ${spinner_1.colors.yellow((0, repo_manager_1.shortHash)(commit.hash))}`);
400
- const duration = Date.now() - startTime;
401
- console.log('');
402
- console.log(`${spinner_1.colors.green('✓')} Pull completed in ${spinner_1.colors.cyan(`${duration}ms`)}`);
403
568
  console.log('');
404
569
  }
405
- catch (error) {
570
+ catch (err) {
406
571
  spinner.fail('Pull failed');
407
- console.error(spinner_1.colors.red(`Error: ${error instanceof Error ? error.message : error}`));
408
- process.exit(1);
572
+ (0, cli_utils_1.fatal)(err instanceof Error ? err.message : String(err));
573
+ }
574
+ }
575
+ function detectObjectConflicts(local, remote) {
576
+ const conflicts = [];
577
+ for (const localTable of local.tables) {
578
+ const remoteTable = remote.tables.find(t => t.name === localTable.name);
579
+ if (!remoteTable)
580
+ continue;
581
+ for (const localCol of localTable.columns) {
582
+ const remoteCol = remoteTable.columns.find(c => c.name === localCol.name);
583
+ if (!remoteCol)
584
+ continue;
585
+ if (localCol.type !== remoteCol.type) {
586
+ conflicts.push({
587
+ objectType: 'COLUMN',
588
+ objectName: localCol.name,
589
+ parentName: localTable.name,
590
+ localValue: localCol.type,
591
+ remoteValue: remoteCol.type,
592
+ description: `Type changed: ${localCol.type} → ${remoteCol.type}`,
593
+ });
594
+ }
595
+ else if (localCol.nullable !== remoteCol.nullable) {
596
+ conflicts.push({
597
+ objectType: 'COLUMN',
598
+ objectName: localCol.name,
599
+ parentName: localTable.name,
600
+ localValue: localCol.nullable,
601
+ remoteValue: remoteCol.nullable,
602
+ description: `Nullable changed: ${localCol.nullable} → ${remoteCol.nullable}`,
603
+ });
604
+ }
605
+ else if (String(localCol.default || '') !== String(remoteCol.default || '')) {
606
+ if (localCol.default && remoteCol.default && localCol.default !== remoteCol.default) {
607
+ conflicts.push({
608
+ objectType: 'COLUMN',
609
+ objectName: localCol.name,
610
+ parentName: localTable.name,
611
+ localValue: localCol.default,
612
+ remoteValue: remoteCol.default,
613
+ description: `Default changed`,
614
+ });
615
+ }
616
+ }
617
+ }
618
+ }
619
+ for (const localEnum of local.enums) {
620
+ const remoteEnum = remote.enums.find(e => e.name === localEnum.name);
621
+ if (!remoteEnum)
622
+ continue;
623
+ const localVals = JSON.stringify(localEnum.values.sort());
624
+ const remoteVals = JSON.stringify(remoteEnum.values.sort());
625
+ if (localVals !== remoteVals) {
626
+ conflicts.push({
627
+ objectType: 'ENUM',
628
+ objectName: localEnum.name,
629
+ localValue: localEnum.values,
630
+ remoteValue: remoteEnum.values,
631
+ description: `Values differ`,
632
+ });
633
+ }
409
634
  }
635
+ return conflicts;
410
636
  }