relq 1.0.1 → 1.0.2

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 (159) 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 +30 -1
  6. package/dist/cjs/cli/commands/branch.cjs +141 -0
  7. package/dist/cjs/cli/commands/checkout.cjs +134 -0
  8. package/dist/cjs/cli/commands/cherry-pick.cjs +283 -0
  9. package/dist/cjs/cli/commands/diff.cjs +148 -69
  10. package/dist/cjs/cli/commands/export.cjs +64 -5
  11. package/dist/cjs/cli/commands/fetch.cjs +34 -4
  12. package/dist/cjs/cli/commands/history.cjs +1 -1
  13. package/dist/cjs/cli/commands/import.cjs +283 -12
  14. package/dist/cjs/cli/commands/log.cjs +75 -0
  15. package/dist/cjs/cli/commands/merge.cjs +224 -0
  16. package/dist/cjs/cli/commands/migrate.cjs +1 -1
  17. package/dist/cjs/cli/commands/pull.cjs +123 -7
  18. package/dist/cjs/cli/commands/push.cjs +245 -29
  19. package/dist/cjs/cli/commands/remote.cjs +16 -0
  20. package/dist/cjs/cli/commands/reset.cjs +169 -0
  21. package/dist/cjs/cli/commands/resolve.cjs +193 -0
  22. package/dist/cjs/cli/commands/rollback.cjs +1 -1
  23. package/dist/cjs/cli/commands/stash.cjs +154 -0
  24. package/dist/cjs/cli/commands/status.cjs +48 -0
  25. package/dist/cjs/cli/commands/tag.cjs +147 -0
  26. package/dist/cjs/cli/index.cjs +46 -2
  27. package/dist/cjs/cli/utils/commit-manager.cjs +3 -3
  28. package/dist/cjs/cli/utils/env-loader.cjs +3 -2
  29. package/dist/cjs/cli/utils/fast-introspect.cjs +1 -1
  30. package/dist/cjs/cli/utils/project-root.cjs +56 -0
  31. package/dist/cjs/cli/utils/relqignore.cjs +296 -38
  32. package/dist/cjs/cli/utils/repo-manager.cjs +45 -3
  33. package/dist/cjs/cli/utils/schema-introspect.cjs +2 -2
  34. package/dist/cjs/cli/utils/sql-generator.cjs +1 -1
  35. package/dist/cjs/cli/utils/sql-parser.cjs +94 -7
  36. package/dist/cjs/condition/array-condition-builder.cjs +1 -1
  37. package/dist/cjs/condition/condition-collector.cjs +1 -1
  38. package/dist/cjs/condition/fulltext-condition-builder.cjs +1 -1
  39. package/dist/cjs/condition/geometric-condition-builder.cjs +1 -1
  40. package/dist/cjs/condition/jsonb-condition-builder.cjs +1 -1
  41. package/dist/cjs/condition/network-condition-builder.cjs +1 -1
  42. package/dist/cjs/condition/range-condition-builder.cjs +1 -1
  43. package/dist/cjs/copy/copy-builder.cjs +1 -1
  44. package/dist/cjs/core/query-builder.cjs +1 -1
  45. package/dist/cjs/core/relq-client.cjs +2 -2
  46. package/dist/cjs/count/count-builder.cjs +1 -1
  47. package/dist/cjs/cte/cte-builder.cjs +1 -1
  48. package/dist/cjs/delete/delete-builder.cjs +1 -1
  49. package/dist/cjs/function/create-function-builder.cjs +1 -1
  50. package/dist/cjs/functions/advanced-functions.cjs +1 -1
  51. package/dist/cjs/functions/case-builder.cjs +1 -1
  52. package/dist/cjs/functions/geometric-functions.cjs +1 -1
  53. package/dist/cjs/functions/network-functions.cjs +1 -1
  54. package/dist/cjs/functions/sql-functions.cjs +1 -1
  55. package/dist/cjs/indexing/create-index-builder.cjs +1 -1
  56. package/dist/cjs/indexing/drop-index-builder.cjs +1 -1
  57. package/dist/cjs/insert/conflict-builder.cjs +1 -1
  58. package/dist/cjs/insert/insert-builder.cjs +1 -1
  59. package/dist/cjs/maintenance/vacuum-builder.cjs +1 -1
  60. package/dist/cjs/pubsub/listen-notify-builder.cjs +1 -1
  61. package/dist/cjs/pubsub/listener-connection.cjs +2 -2
  62. package/dist/cjs/raw/raw-query-builder.cjs +1 -1
  63. package/dist/cjs/schema/schema-builder.cjs +1 -1
  64. package/dist/cjs/schema-definition/table-definition.cjs +1 -1
  65. package/dist/cjs/select/aggregate-builder.cjs +1 -1
  66. package/dist/cjs/select/select-builder.cjs +1 -1
  67. package/dist/cjs/sequence/sequence-builder.cjs +1 -1
  68. package/dist/cjs/table/alter-table-builder.cjs +1 -1
  69. package/dist/cjs/table/constraint-builder.cjs +1 -1
  70. package/dist/cjs/table/create-table-builder.cjs +1 -1
  71. package/dist/cjs/table/partition-builder.cjs +1 -1
  72. package/dist/cjs/table/truncate-builder.cjs +1 -1
  73. package/dist/cjs/transaction/transaction-builder.cjs +1 -1
  74. package/dist/cjs/trigger/create-trigger-builder.cjs +1 -1
  75. package/dist/cjs/update/array-update-builder.cjs +1 -1
  76. package/dist/cjs/update/update-builder.cjs +1 -1
  77. package/dist/cjs/utils/index.cjs +1 -1
  78. package/dist/cjs/view/create-view-builder.cjs +1 -1
  79. package/dist/cjs/window/window-builder.cjs +1 -1
  80. package/dist/esm/cli/commands/add.js +30 -1
  81. package/dist/esm/cli/commands/branch.js +105 -0
  82. package/dist/esm/cli/commands/checkout.js +98 -0
  83. package/dist/esm/cli/commands/cherry-pick.js +247 -0
  84. package/dist/esm/cli/commands/diff.js +148 -69
  85. package/dist/esm/cli/commands/export.js +64 -5
  86. package/dist/esm/cli/commands/fetch.js +35 -5
  87. package/dist/esm/cli/commands/history.js +1 -1
  88. package/dist/esm/cli/commands/import.js +283 -12
  89. package/dist/esm/cli/commands/log.js +74 -0
  90. package/dist/esm/cli/commands/merge.js +188 -0
  91. package/dist/esm/cli/commands/migrate.js +1 -1
  92. package/dist/esm/cli/commands/pull.js +124 -8
  93. package/dist/esm/cli/commands/push.js +246 -30
  94. package/dist/esm/cli/commands/remote.js +13 -0
  95. package/dist/esm/cli/commands/reset.js +133 -0
  96. package/dist/esm/cli/commands/resolve.js +157 -0
  97. package/dist/esm/cli/commands/rollback.js +1 -1
  98. package/dist/esm/cli/commands/stash.js +118 -0
  99. package/dist/esm/cli/commands/status.js +15 -0
  100. package/dist/esm/cli/commands/tag.js +111 -0
  101. package/dist/esm/cli/index.js +47 -3
  102. package/dist/esm/cli/utils/commit-manager.js +3 -3
  103. package/dist/esm/cli/utils/env-loader.js +3 -2
  104. package/dist/esm/cli/utils/fast-introspect.js +1 -1
  105. package/dist/esm/cli/utils/project-root.js +19 -0
  106. package/dist/esm/cli/utils/relqignore.js +277 -37
  107. package/dist/esm/cli/utils/repo-manager.js +41 -3
  108. package/dist/esm/cli/utils/schema-introspect.js +2 -2
  109. package/dist/esm/cli/utils/sql-generator.js +1 -1
  110. package/dist/esm/cli/utils/sql-parser.js +94 -7
  111. package/dist/esm/condition/array-condition-builder.js +1 -1
  112. package/dist/esm/condition/condition-collector.js +1 -1
  113. package/dist/esm/condition/fulltext-condition-builder.js +1 -1
  114. package/dist/esm/condition/geometric-condition-builder.js +1 -1
  115. package/dist/esm/condition/jsonb-condition-builder.js +1 -1
  116. package/dist/esm/condition/network-condition-builder.js +1 -1
  117. package/dist/esm/condition/range-condition-builder.js +1 -1
  118. package/dist/esm/copy/copy-builder.js +1 -1
  119. package/dist/esm/core/query-builder.js +1 -1
  120. package/dist/esm/core/relq-client.js +2 -2
  121. package/dist/esm/count/count-builder.js +1 -1
  122. package/dist/esm/cte/cte-builder.js +1 -1
  123. package/dist/esm/delete/delete-builder.js +1 -1
  124. package/dist/esm/function/create-function-builder.js +1 -1
  125. package/dist/esm/functions/advanced-functions.js +1 -1
  126. package/dist/esm/functions/case-builder.js +1 -1
  127. package/dist/esm/functions/geometric-functions.js +1 -1
  128. package/dist/esm/functions/network-functions.js +1 -1
  129. package/dist/esm/functions/sql-functions.js +1 -1
  130. package/dist/esm/indexing/create-index-builder.js +1 -1
  131. package/dist/esm/indexing/drop-index-builder.js +1 -1
  132. package/dist/esm/insert/conflict-builder.js +1 -1
  133. package/dist/esm/insert/insert-builder.js +1 -1
  134. package/dist/esm/maintenance/vacuum-builder.js +1 -1
  135. package/dist/esm/pubsub/listen-notify-builder.js +1 -1
  136. package/dist/esm/pubsub/listener-connection.js +2 -2
  137. package/dist/esm/raw/raw-query-builder.js +1 -1
  138. package/dist/esm/schema/schema-builder.js +1 -1
  139. package/dist/esm/schema-definition/table-definition.js +1 -1
  140. package/dist/esm/select/aggregate-builder.js +1 -1
  141. package/dist/esm/select/select-builder.js +1 -1
  142. package/dist/esm/sequence/sequence-builder.js +1 -1
  143. package/dist/esm/table/alter-table-builder.js +1 -1
  144. package/dist/esm/table/constraint-builder.js +1 -1
  145. package/dist/esm/table/create-table-builder.js +1 -1
  146. package/dist/esm/table/partition-builder.js +1 -1
  147. package/dist/esm/table/truncate-builder.js +1 -1
  148. package/dist/esm/transaction/transaction-builder.js +1 -1
  149. package/dist/esm/trigger/create-trigger-builder.js +1 -1
  150. package/dist/esm/update/array-update-builder.js +1 -1
  151. package/dist/esm/update/update-builder.js +1 -1
  152. package/dist/esm/utils/index.js +1 -1
  153. package/dist/esm/view/create-view-builder.js +1 -1
  154. package/dist/esm/window/window-builder.js +1 -1
  155. package/package.json +1 -1
  156. /package/dist/{addons/buffer.js → esm/addon/buffer/index.js} +0 -0
  157. /package/dist/{addons/pg.js → esm/addon/pg/index.js} +0 -0
  158. /package/dist/{addons/pg-cursor.js → esm/addon/pg-cursor/index.js} +0 -0
  159. /package/dist/{addons/pg-format.js → esm/addon/pg-format/index.js} +0 -0
@@ -0,0 +1,188 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { colors, createSpinner } from "../utils/spinner.js";
4
+ import { isInitialized, getHead, loadCommit, loadSnapshot, saveSnapshot, createCommit, shortHash, } from "../utils/repo-manager.js";
5
+ function loadBranchState(projectRoot) {
6
+ const branchPath = path.join(projectRoot, '.relq', 'branches.json');
7
+ if (fs.existsSync(branchPath)) {
8
+ return JSON.parse(fs.readFileSync(branchPath, 'utf-8'));
9
+ }
10
+ const head = require('../utils/repo-manager').getHead(projectRoot);
11
+ return { current: 'main', branches: { main: head || '' } };
12
+ }
13
+ function saveBranchState(state, projectRoot) {
14
+ const branchPath = path.join(projectRoot, '.relq', 'branches.json');
15
+ fs.writeFileSync(branchPath, JSON.stringify(state, null, 2));
16
+ }
17
+ export async function mergeCommand(context) {
18
+ const { config, args, flags } = context;
19
+ const projectRoot = process.cwd();
20
+ console.log('');
21
+ if (!isInitialized(projectRoot)) {
22
+ console.log(`${colors.red('fatal:')} not a relq repository`);
23
+ return;
24
+ }
25
+ const branchName = args[0];
26
+ const abort = flags['abort'] === true;
27
+ const mergeStatePath = path.join(projectRoot, '.relq', 'MERGE_STATE');
28
+ if (abort) {
29
+ if (fs.existsSync(mergeStatePath)) {
30
+ fs.unlinkSync(mergeStatePath);
31
+ console.log(`${colors.green('✓')} Merge aborted`);
32
+ }
33
+ else {
34
+ console.log(`${colors.muted('No merge in progress.')}`);
35
+ }
36
+ console.log('');
37
+ return;
38
+ }
39
+ if (fs.existsSync(mergeStatePath)) {
40
+ const mergeState = JSON.parse(fs.readFileSync(mergeStatePath, 'utf-8'));
41
+ console.log(`${colors.red('error:')} Merge in progress from '${mergeState.fromBranch}'`);
42
+ console.log('');
43
+ console.log(`${colors.muted('Use')} ${colors.cyan('relq resolve')} ${colors.muted('to resolve conflicts')}`);
44
+ console.log(`${colors.muted('Or')} ${colors.cyan('relq merge --abort')} ${colors.muted('to cancel')}`);
45
+ console.log('');
46
+ return;
47
+ }
48
+ if (!branchName) {
49
+ console.log(`${colors.red('error:')} Please specify a branch to merge`);
50
+ console.log('');
51
+ console.log(`Usage: ${colors.cyan('relq merge <branch>')}`);
52
+ console.log('');
53
+ return;
54
+ }
55
+ const state = loadBranchState(projectRoot);
56
+ if (!state.branches[branchName]) {
57
+ console.log(`${colors.red('error:')} Branch not found: ${branchName}`);
58
+ return;
59
+ }
60
+ if (branchName === state.current) {
61
+ console.log(`${colors.red('error:')} Cannot merge branch into itself`);
62
+ return;
63
+ }
64
+ const spinner = createSpinner();
65
+ spinner.start(`Merging '${branchName}' into '${state.current}'...`);
66
+ try {
67
+ const currentHash = getHead(projectRoot);
68
+ const incomingHash = state.branches[branchName];
69
+ if (!currentHash || !incomingHash) {
70
+ spinner.fail('No commits to merge');
71
+ return;
72
+ }
73
+ if (currentHash === incomingHash) {
74
+ spinner.succeed('Already up to date');
75
+ console.log('');
76
+ return;
77
+ }
78
+ const currentCommit = loadCommit(currentHash, projectRoot);
79
+ const incomingCommit = loadCommit(incomingHash, projectRoot);
80
+ if (!currentCommit || !incomingCommit) {
81
+ spinner.fail('Cannot load commit data');
82
+ return;
83
+ }
84
+ const currentSnapshot = loadSnapshot(projectRoot) || currentCommit.schema;
85
+ const incomingSnapshot = incomingCommit.schema;
86
+ if (!currentSnapshot || !incomingSnapshot) {
87
+ spinner.fail('No snapshot data');
88
+ return;
89
+ }
90
+ const conflicts = detectMergeConflicts(currentSnapshot, incomingSnapshot);
91
+ if (conflicts.length > 0) {
92
+ const mergeState = {
93
+ fromBranch: branchName,
94
+ toBranch: state.current,
95
+ conflicts,
96
+ incomingSnapshot,
97
+ createdAt: new Date().toISOString(),
98
+ };
99
+ fs.writeFileSync(mergeStatePath, JSON.stringify(mergeState, null, 2));
100
+ spinner.fail(`${conflicts.length} conflict(s) detected`);
101
+ console.log('');
102
+ for (const c of conflicts.slice(0, 5)) {
103
+ const name = c.parentName ? `${c.parentName}.${c.objectName}` : c.objectName;
104
+ console.log(` ${colors.red('conflict:')} ${c.objectType.toLowerCase()} ${name}`);
105
+ }
106
+ if (conflicts.length > 5) {
107
+ console.log(` ${colors.muted(`... and ${conflicts.length - 5} more`)}`);
108
+ }
109
+ console.log('');
110
+ console.log(`${colors.muted('Use')} ${colors.cyan('relq resolve --all-theirs')} ${colors.muted('to accept incoming')}`);
111
+ console.log(`${colors.muted('Or')} ${colors.cyan('relq merge --abort')} ${colors.muted('to cancel')}`);
112
+ console.log('');
113
+ return;
114
+ }
115
+ const mergedSnapshot = mergeSnapshots(currentSnapshot, incomingSnapshot);
116
+ saveSnapshot(mergedSnapshot, projectRoot);
117
+ const author = config?.author || 'Relq CLI';
118
+ const message = `Merge branch '${branchName}' into ${state.current}`;
119
+ const commit = createCommit(mergedSnapshot, author, message, projectRoot);
120
+ spinner.succeed(`Merged '${branchName}' into '${state.current}'`);
121
+ console.log(` ${colors.yellow(shortHash(commit.hash))} ${message}`);
122
+ console.log('');
123
+ }
124
+ catch (error) {
125
+ spinner.fail('Merge failed');
126
+ console.error(colors.red(`Error: ${error instanceof Error ? error.message : error}`));
127
+ }
128
+ }
129
+ function detectMergeConflicts(current, incoming) {
130
+ const conflicts = [];
131
+ for (const currentTable of current.tables) {
132
+ const incomingTable = incoming.tables.find(t => t.name === currentTable.name);
133
+ if (!incomingTable)
134
+ continue;
135
+ for (const currentCol of currentTable.columns) {
136
+ const incomingCol = incomingTable.columns.find(c => c.name === currentCol.name);
137
+ if (!incomingCol)
138
+ continue;
139
+ if (currentCol.type !== incomingCol.type) {
140
+ conflicts.push({
141
+ objectType: 'COLUMN',
142
+ objectName: currentCol.name,
143
+ parentName: currentTable.name,
144
+ currentValue: currentCol.type,
145
+ incomingValue: incomingCol.type,
146
+ description: `Type differs: ${currentCol.type} vs ${incomingCol.type}`,
147
+ });
148
+ }
149
+ }
150
+ }
151
+ for (const currentEnum of current.enums) {
152
+ const incomingEnum = incoming.enums.find(e => e.name === currentEnum.name);
153
+ if (!incomingEnum)
154
+ continue;
155
+ const currentVals = JSON.stringify(currentEnum.values.sort());
156
+ const incomingVals = JSON.stringify(incomingEnum.values.sort());
157
+ if (currentVals !== incomingVals) {
158
+ conflicts.push({
159
+ objectType: 'ENUM',
160
+ objectName: currentEnum.name,
161
+ currentValue: currentEnum.values,
162
+ incomingValue: incomingEnum.values,
163
+ description: 'Values differ',
164
+ });
165
+ }
166
+ }
167
+ return conflicts;
168
+ }
169
+ function mergeSnapshots(current, incoming) {
170
+ const result = JSON.parse(JSON.stringify(current));
171
+ for (const table of incoming.tables) {
172
+ if (!result.tables.find(t => t.name === table.name)) {
173
+ result.tables.push(table);
174
+ }
175
+ }
176
+ for (const e of incoming.enums) {
177
+ if (!result.enums.find(x => x.name === e.name)) {
178
+ result.enums.push(e);
179
+ }
180
+ }
181
+ for (const d of incoming.domains) {
182
+ if (!result.domains.find(x => x.name === d.name)) {
183
+ result.domains.push(d);
184
+ }
185
+ }
186
+ return result;
187
+ }
188
+ export default mergeCommand;
@@ -39,7 +39,7 @@ export async function migrateCommand(context) {
39
39
  console.log(` Connection: ${getConnectionDescription(connection)}`);
40
40
  console.log(` Migrations: ${migrationsDir}\n`);
41
41
  try {
42
- const { Pool } = await import("../../addon/pg.js");
42
+ const { Pool } = await import("../../addon/pg/index.js");
43
43
  const pool = new Pool({
44
44
  host: connection.host,
45
45
  port: connection.port || 5432,
@@ -6,7 +6,7 @@ import { fastIntrospectDatabase } from "../utils/fast-introspect.js";
6
6
  import { generateTypeScript } from "../utils/type-generator.js";
7
7
  import { getConnectionDescription } from "../utils/env-loader.js";
8
8
  import { createSpinner, colors, formatBytes } from "../utils/spinner.js";
9
- import { getIgnorePatterns, filterIgnored } from "../utils/relqignore.js";
9
+ import { loadRelqignore, isTableIgnored, isColumnIgnored, isIndexIgnored, isConstraintIgnored, isEnumIgnored, isDomainIgnored, isCompositeTypeIgnored, isFunctionIgnored, } from "../utils/relqignore.js";
10
10
  import { isInitialized, initRepository, getHead, saveSnapshot, loadSnapshot, createCommit, shortHash, fetchRemoteCommits, ensureRemoteTable, setFetchHead, getStagedChanges, getUnstagedChanges, } from "../utils/repo-manager.js";
11
11
  function toCamelCase(str) {
12
12
  return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
@@ -209,8 +209,24 @@ export async function pullCommand(context) {
209
209
  includeTriggers,
210
210
  });
211
211
  spinner.succeed(`Found ${dbSchema.tables.length} tables`);
212
- const ignorePatterns = getIgnorePatterns(projectRoot);
213
- const filteredTables = filterIgnored(dbSchema.tables, ignorePatterns);
212
+ const ignorePatterns = loadRelqignore(projectRoot);
213
+ const filteredTables = dbSchema.tables
214
+ .filter(t => !isTableIgnored(t.name, ignorePatterns).ignored)
215
+ .map(t => ({
216
+ ...t,
217
+ columns: t.columns.filter(c => !isColumnIgnored(t.name, c.name, ignorePatterns).ignored),
218
+ indexes: t.indexes.filter(i => !isIndexIgnored(t.name, i.name, ignorePatterns).ignored),
219
+ constraints: t.constraints.filter(c => !isConstraintIgnored(t.name, c.name, ignorePatterns).ignored),
220
+ }));
221
+ const filteredEnums = dbSchema.enums.filter(e => !isEnumIgnored(e.name, ignorePatterns).ignored);
222
+ const filteredDomains = dbSchema.domains.filter(d => !isDomainIgnored(d.name, ignorePatterns).ignored);
223
+ const filteredCompositeTypes = dbSchema.compositeTypes.filter(c => !isCompositeTypeIgnored(c.name, ignorePatterns).ignored);
224
+ const filteredFunctions = includeFunctions
225
+ ? dbSchema.functions.filter(f => !isFunctionIgnored(f.name, ignorePatterns).ignored)
226
+ : [];
227
+ const filteredTriggers = includeTriggers
228
+ ? dbSchema.triggers
229
+ : [];
214
230
  const localHead = getHead(projectRoot);
215
231
  const localSnapshot = loadSnapshot(projectRoot);
216
232
  const schemaExists = fs.existsSync(schemaPath);
@@ -239,12 +255,12 @@ export async function pullCommand(context) {
239
255
  definition: c.definition,
240
256
  })),
241
257
  })),
242
- enums: dbSchema.enums.map(e => ({
258
+ enums: filteredEnums.map(e => ({
243
259
  name: e.name,
244
260
  schema: 'public',
245
261
  values: e.values,
246
262
  })),
247
- domains: dbSchema.domains.map(d => ({
263
+ domains: filteredDomains.map(d => ({
248
264
  name: d.name,
249
265
  schema: 'public',
250
266
  baseType: d.baseType,
@@ -252,20 +268,20 @@ export async function pullCommand(context) {
252
268
  default: d.defaultValue || null,
253
269
  check: d.checkExpression || null,
254
270
  })),
255
- compositeTypes: dbSchema.compositeTypes.map(c => ({
271
+ compositeTypes: filteredCompositeTypes.map(c => ({
256
272
  name: c.name,
257
273
  schema: 'public',
258
274
  attributes: c.attributes,
259
275
  })),
260
276
  sequences: [],
261
277
  collations: [],
262
- functions: dbSchema.functions.map(f => ({
278
+ functions: filteredFunctions.map(f => ({
263
279
  name: f.name,
264
280
  returnType: f.returnType,
265
281
  argTypes: f.argTypes,
266
282
  language: f.language,
267
283
  })),
268
- triggers: dbSchema.triggers.map(t => ({
284
+ triggers: filteredTriggers.map(t => ({
269
285
  name: t.name,
270
286
  table: t.tableName,
271
287
  events: [t.event],
@@ -276,11 +292,49 @@ export async function pullCommand(context) {
276
292
  extensions: dbSchema.extensions.map(ext => ({ name: ext })),
277
293
  };
278
294
  console.log('');
295
+ const mergeStatePath = path.join(projectRoot, '.relq', 'MERGE_STATE');
296
+ if (fs.existsSync(mergeStatePath) && !force) {
297
+ console.log(`${colors.red('error:')} You have unresolved merge conflicts`);
298
+ console.log('');
299
+ console.log(`${colors.muted('Use')} ${colors.cyan('relq resolve')} ${colors.muted('to see and resolve conflicts')}`);
300
+ console.log(`${colors.muted('Or use')} ${colors.cyan('relq pull --force')} ${colors.muted('to overwrite local')}`);
301
+ console.log('');
302
+ return;
303
+ }
279
304
  if (schemaExists && localSnapshot && !force) {
280
305
  const localTables = new Set(localSnapshot.tables.map(t => t.name));
281
306
  const remoteTables = new Set(currentSchema.tables.map(t => t.name));
282
307
  const added = [...remoteTables].filter(t => !localTables.has(t));
283
308
  const removed = [...localTables].filter(t => !remoteTables.has(t));
309
+ const conflicts = detectObjectConflicts(localSnapshot, currentSchema);
310
+ if (conflicts.length > 0 && !force) {
311
+ const mergeState = {
312
+ conflicts,
313
+ remoteSnapshot: currentSchema,
314
+ createdAt: new Date().toISOString(),
315
+ };
316
+ fs.writeFileSync(mergeStatePath, JSON.stringify(mergeState, null, 2));
317
+ console.log(`${colors.red('error:')} Merge conflict detected`);
318
+ console.log('');
319
+ console.log(`Both local and remote have modified the same objects:`);
320
+ console.log('');
321
+ for (const c of conflicts.slice(0, 10)) {
322
+ const name = c.parentName ? `${c.parentName}.${c.objectName}` : c.objectName;
323
+ console.log(` ${colors.red('conflict:')} ${c.objectType.toLowerCase()} ${colors.bold(name)}`);
324
+ console.log(` ${colors.muted(c.description)}`);
325
+ }
326
+ if (conflicts.length > 10) {
327
+ console.log(` ${colors.muted(`... and ${conflicts.length - 10} more`)}`);
328
+ }
329
+ console.log('');
330
+ console.log('To resolve:');
331
+ console.log(` ${colors.cyan('relq resolve --theirs <name>')} Take remote version`);
332
+ console.log(` ${colors.cyan('relq resolve --ours <name>')} Keep local version`);
333
+ console.log(` ${colors.cyan('relq resolve --all-theirs')} Take all remote`);
334
+ console.log(` ${colors.cyan('relq pull --force')} Force overwrite local`);
335
+ console.log('');
336
+ return;
337
+ }
284
338
  if (added.length === 0 && removed.length === 0) {
285
339
  console.log(`${colors.green('✓')} Already up to date with remote`);
286
340
  console.log('');
@@ -372,3 +426,65 @@ export async function pullCommand(context) {
372
426
  process.exit(1);
373
427
  }
374
428
  }
429
+ function detectObjectConflicts(local, remote) {
430
+ const conflicts = [];
431
+ for (const localTable of local.tables) {
432
+ const remoteTable = remote.tables.find(t => t.name === localTable.name);
433
+ if (!remoteTable)
434
+ continue;
435
+ for (const localCol of localTable.columns) {
436
+ const remoteCol = remoteTable.columns.find(c => c.name === localCol.name);
437
+ if (!remoteCol)
438
+ continue;
439
+ if (localCol.type !== remoteCol.type) {
440
+ conflicts.push({
441
+ objectType: 'COLUMN',
442
+ objectName: localCol.name,
443
+ parentName: localTable.name,
444
+ localValue: localCol.type,
445
+ remoteValue: remoteCol.type,
446
+ description: `Type changed: ${localCol.type} → ${remoteCol.type}`,
447
+ });
448
+ }
449
+ else if (localCol.nullable !== remoteCol.nullable) {
450
+ conflicts.push({
451
+ objectType: 'COLUMN',
452
+ objectName: localCol.name,
453
+ parentName: localTable.name,
454
+ localValue: localCol.nullable,
455
+ remoteValue: remoteCol.nullable,
456
+ description: `Nullable changed: ${localCol.nullable} → ${remoteCol.nullable}`,
457
+ });
458
+ }
459
+ else if (String(localCol.default || '') !== String(remoteCol.default || '')) {
460
+ if (localCol.default && remoteCol.default && localCol.default !== remoteCol.default) {
461
+ conflicts.push({
462
+ objectType: 'COLUMN',
463
+ objectName: localCol.name,
464
+ parentName: localTable.name,
465
+ localValue: localCol.default,
466
+ remoteValue: remoteCol.default,
467
+ description: `Default changed`,
468
+ });
469
+ }
470
+ }
471
+ }
472
+ }
473
+ for (const localEnum of local.enums) {
474
+ const remoteEnum = remote.enums.find(e => e.name === localEnum.name);
475
+ if (!remoteEnum)
476
+ continue;
477
+ const localVals = JSON.stringify(localEnum.values.sort());
478
+ const remoteVals = JSON.stringify(remoteEnum.values.sort());
479
+ if (localVals !== remoteVals) {
480
+ conflicts.push({
481
+ objectType: 'ENUM',
482
+ objectName: localEnum.name,
483
+ localValue: localEnum.values,
484
+ remoteValue: remoteEnum.values,
485
+ description: `Values differ`,
486
+ });
487
+ }
488
+ }
489
+ return conflicts;
490
+ }