@pgpmjs/core 4.13.2 → 4.14.0

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.
package/README.md CHANGED
@@ -22,6 +22,37 @@ pgpm Core is the main package for pgpm, providing tools for database migrations,
22
22
  - Reading and writing SQL scripts
23
23
  - Resolving dependencies between migrations
24
24
 
25
+ ## Configuration
26
+
27
+ ### Error Output Configuration
28
+
29
+ When migrations fail, pgpm provides detailed error output including query history. For large deployments with many migrations, this output can be verbose. The following environment variables control error output formatting:
30
+
31
+ | Variable | Default | Description |
32
+ |----------|---------|-------------|
33
+ | `PGPM_ERROR_QUERY_HISTORY_LIMIT` | `30` | Maximum number of queries to show in error output. Earlier queries are omitted with a count. |
34
+ | `PGPM_ERROR_MAX_LENGTH` | `10000` | Maximum characters for error output. Output exceeding this is truncated. |
35
+ | `PGPM_ERROR_VERBOSE` | `false` | Set to `true` to disable all limiting and show full error output. |
36
+
37
+ #### Smart Query Collapsing
38
+
39
+ Error output automatically collapses consecutive identical queries (like repeated `pgpm_migrate.deploy` calls) into a summary showing:
40
+ - The range of query numbers (e.g., "2-57")
41
+ - The total count (e.g., "56 calls")
42
+ - For deploy calls: the first and last change names for context
43
+
44
+ Example collapsed output:
45
+ ```
46
+ Query history for this transaction:
47
+ 1. BEGIN
48
+ 2-57. CALL pgpm_migrate.deploy($1::TEXT, ...) (56 calls)
49
+ First: schemas/metaschema_public/tables/extension/table
50
+ Last: schemas/metaschema_modules_public/tables/permissions_module/table
51
+ 58. ROLLBACK
52
+ ```
53
+
54
+ To see full uncompressed output, set `PGPM_ERROR_VERBOSE=true`.
55
+
25
56
  ---
26
57
 
27
58
  ## Education and Tutorials
@@ -187,8 +187,8 @@ const config = {
187
187
  fields: {
188
188
  id: 'uuid',
189
189
  database_id: 'uuid',
190
- schema_id: 'uuid',
191
- name: 'text'
190
+ name: 'text',
191
+ code: 'text'
192
192
  }
193
193
  },
194
194
  rls_function: {
@@ -197,8 +197,13 @@ const config = {
197
197
  fields: {
198
198
  id: 'uuid',
199
199
  database_id: 'uuid',
200
- schema_id: 'uuid',
201
- name: 'text'
200
+ table_id: 'uuid',
201
+ name: 'text',
202
+ label: 'text',
203
+ description: 'text',
204
+ data: 'jsonb',
205
+ inline: 'boolean',
206
+ security: 'int'
202
207
  }
203
208
  },
204
209
  limit_function: {
@@ -207,8 +212,12 @@ const config = {
207
212
  fields: {
208
213
  id: 'uuid',
209
214
  database_id: 'uuid',
210
- schema_id: 'uuid',
211
- name: 'text'
215
+ table_id: 'uuid',
216
+ name: 'text',
217
+ label: 'text',
218
+ description: 'text',
219
+ data: 'jsonb',
220
+ security: 'int'
212
221
  }
213
222
  },
214
223
  procedure: {
@@ -217,8 +226,12 @@ const config = {
217
226
  fields: {
218
227
  id: 'uuid',
219
228
  database_id: 'uuid',
220
- schema_id: 'uuid',
221
- name: 'text'
229
+ name: 'text',
230
+ argnames: 'text[]',
231
+ argtypes: 'text[]',
232
+ argdefaults: 'text[]',
233
+ lang_name: 'text',
234
+ definition: 'text'
222
235
  }
223
236
  },
224
237
  foreign_key_constraint: {
@@ -229,11 +242,14 @@ const config = {
229
242
  database_id: 'uuid',
230
243
  table_id: 'uuid',
231
244
  name: 'text',
245
+ description: 'text',
246
+ smart_tags: 'jsonb',
247
+ type: 'text',
232
248
  field_ids: 'uuid[]',
233
249
  ref_table_id: 'uuid',
234
250
  ref_field_ids: 'uuid[]',
235
- on_delete: 'text',
236
- on_update: 'text'
251
+ delete_action: 'text',
252
+ update_action: 'text'
237
253
  }
238
254
  },
239
255
  primary_key_constraint: {
@@ -244,6 +260,7 @@ const config = {
244
260
  database_id: 'uuid',
245
261
  table_id: 'uuid',
246
262
  name: 'text',
263
+ type: 'text',
247
264
  field_ids: 'uuid[]'
248
265
  }
249
266
  },
@@ -255,6 +272,9 @@ const config = {
255
272
  database_id: 'uuid',
256
273
  table_id: 'uuid',
257
274
  name: 'text',
275
+ description: 'text',
276
+ smart_tags: 'jsonb',
277
+ type: 'text',
258
278
  field_ids: 'uuid[]'
259
279
  }
260
280
  },
@@ -266,7 +286,9 @@ const config = {
266
286
  database_id: 'uuid',
267
287
  table_id: 'uuid',
268
288
  name: 'text',
269
- expression: 'text'
289
+ type: 'text',
290
+ field_ids: 'uuid[]',
291
+ expr: 'jsonb'
270
292
  }
271
293
  },
272
294
  full_text_search: {
@@ -276,9 +298,10 @@ const config = {
276
298
  id: 'uuid',
277
299
  database_id: 'uuid',
278
300
  table_id: 'uuid',
279
- name: 'text',
301
+ field_id: 'uuid',
280
302
  field_ids: 'uuid[]',
281
- weights: 'text[]'
303
+ weights: 'text[]',
304
+ langs: 'text[]'
282
305
  }
283
306
  },
284
307
  schema_grant: {
@@ -288,8 +311,7 @@ const config = {
288
311
  id: 'uuid',
289
312
  database_id: 'uuid',
290
313
  schema_id: 'uuid',
291
- role_name: 'text',
292
- privilege: 'text'
314
+ grantee_name: 'text'
293
315
  }
294
316
  },
295
317
  table_grant: {
@@ -299,8 +321,9 @@ const config = {
299
321
  id: 'uuid',
300
322
  database_id: 'uuid',
301
323
  table_id: 'uuid',
324
+ privilege: 'text',
302
325
  role_name: 'text',
303
- privilege: 'text'
326
+ field_ids: 'uuid[]'
304
327
  }
305
328
  },
306
329
  // =============================================================================
@@ -0,0 +1,169 @@
1
+ import { getEnvVars } from '@pgpmjs/env';
2
+ import { pgpmDefaults } from '@pgpmjs/types';
3
+ /**
4
+ * Get error output configuration from environment variables with defaults.
5
+ * Uses centralized env var parsing from @pgpmjs/env and defaults from @pgpmjs/types.
6
+ */
7
+ export const getErrorOutputConfig = () => {
8
+ const envVars = getEnvVars();
9
+ const defaults = pgpmDefaults.errorOutput;
10
+ return {
11
+ queryHistoryLimit: envVars.errorOutput?.queryHistoryLimit ?? defaults.queryHistoryLimit,
12
+ maxLength: envVars.errorOutput?.maxLength ?? defaults.maxLength,
13
+ verbose: envVars.errorOutput?.verbose ?? defaults.verbose,
14
+ };
15
+ };
16
+ const errorConfig = getErrorOutputConfig();
17
+ /**
18
+ * Extract a friendly name from pgpm_migrate.deploy params for better error context
19
+ */
20
+ function extractDeployChangeName(params) {
21
+ if (!params || params.length < 2)
22
+ return null;
23
+ // params[1] is the change name (e.g., "schemas/metaschema_public/tables/extension/table")
24
+ return typeof params[1] === 'string' ? params[1] : null;
25
+ }
26
+ /**
27
+ * Group consecutive queries by their query template (ignoring params)
28
+ */
29
+ function groupConsecutiveQueries(history) {
30
+ if (history.length === 0)
31
+ return [];
32
+ const groups = [];
33
+ let currentGroup = {
34
+ query: history[0].query,
35
+ startIndex: 0,
36
+ endIndex: 0,
37
+ count: 1,
38
+ entries: [history[0]]
39
+ };
40
+ for (let i = 1; i < history.length; i++) {
41
+ const entry = history[i];
42
+ if (entry.query === currentGroup.query) {
43
+ // Same query template, extend the group
44
+ currentGroup.endIndex = i;
45
+ currentGroup.count++;
46
+ currentGroup.entries.push(entry);
47
+ }
48
+ else {
49
+ // Different query, start a new group
50
+ groups.push(currentGroup);
51
+ currentGroup = {
52
+ query: entry.query,
53
+ startIndex: i,
54
+ endIndex: i,
55
+ count: 1,
56
+ entries: [entry]
57
+ };
58
+ }
59
+ }
60
+ groups.push(currentGroup);
61
+ return groups;
62
+ }
63
+ /**
64
+ * Format a single query entry for display
65
+ */
66
+ function formatQueryEntry(entry, index) {
67
+ const duration = entry.duration ? ` (${entry.duration}ms)` : '';
68
+ const params = entry.params && entry.params.length > 0
69
+ ? ` with params: ${JSON.stringify(entry.params.slice(0, 2))}${entry.params.length > 2 ? '...' : ''}`
70
+ : '';
71
+ return ` ${index + 1}. ${entry.query.split('\n')[0].trim()}${params}${duration}`;
72
+ }
73
+ /**
74
+ * Format a group of queries for display, collapsing repetitive queries
75
+ */
76
+ function formatQueryGroup(group) {
77
+ const lines = [];
78
+ const queryPreview = group.query.split('\n')[0].trim();
79
+ if (group.count === 1) {
80
+ // Single query, format normally
81
+ lines.push(formatQueryEntry(group.entries[0], group.startIndex));
82
+ }
83
+ else {
84
+ // Multiple consecutive identical queries, collapse them
85
+ const isPgpmDeploy = queryPreview.includes('pgpm_migrate.deploy');
86
+ // Show range and count
87
+ lines.push(` ${group.startIndex + 1}-${group.endIndex + 1}. ${queryPreview} (${group.count} calls)`);
88
+ // For pgpm_migrate.deploy, show first and last change names for context
89
+ if (isPgpmDeploy) {
90
+ const firstChange = extractDeployChangeName(group.entries[0].params);
91
+ const lastChange = extractDeployChangeName(group.entries[group.entries.length - 1].params);
92
+ if (firstChange) {
93
+ lines.push(` First: ${firstChange}`);
94
+ }
95
+ if (lastChange && lastChange !== firstChange) {
96
+ lines.push(` Last: ${lastChange}`);
97
+ }
98
+ }
99
+ else {
100
+ // For other queries, show first and last params
101
+ const firstParams = group.entries[0].params;
102
+ const lastParams = group.entries[group.entries.length - 1].params;
103
+ if (firstParams && firstParams.length > 0) {
104
+ lines.push(` First params: ${JSON.stringify(firstParams.slice(0, 2))}${firstParams.length > 2 ? '...' : ''}`);
105
+ }
106
+ if (lastParams && lastParams.length > 0 && JSON.stringify(lastParams) !== JSON.stringify(firstParams)) {
107
+ lines.push(` Last params: ${JSON.stringify(lastParams.slice(0, 2))}${lastParams.length > 2 ? '...' : ''}`);
108
+ }
109
+ }
110
+ }
111
+ return lines;
112
+ }
113
+ /**
114
+ * Format query history with smart collapsing and limiting
115
+ */
116
+ export function formatQueryHistory(history) {
117
+ if (history.length === 0)
118
+ return [];
119
+ // In verbose mode, show everything without collapsing
120
+ if (errorConfig.verbose) {
121
+ return history.map((entry, index) => formatQueryEntry(entry, index));
122
+ }
123
+ // Group consecutive identical queries
124
+ const groups = groupConsecutiveQueries(history);
125
+ // Apply limit - keep last N queries worth of groups
126
+ let totalQueries = 0;
127
+ let startGroupIndex = groups.length;
128
+ for (let i = groups.length - 1; i >= 0; i--) {
129
+ totalQueries += groups[i].count;
130
+ if (totalQueries > errorConfig.queryHistoryLimit) {
131
+ startGroupIndex = i + 1;
132
+ break;
133
+ }
134
+ startGroupIndex = i;
135
+ }
136
+ const lines = [];
137
+ // If we're truncating, show how many were omitted
138
+ if (startGroupIndex > 0) {
139
+ let omittedCount = 0;
140
+ for (let i = 0; i < startGroupIndex; i++) {
141
+ omittedCount += groups[i].count;
142
+ }
143
+ lines.push(` ... (${omittedCount} earlier queries omitted, set PGPM_ERROR_VERBOSE=true to see all)`);
144
+ }
145
+ // Format the remaining groups
146
+ for (let i = startGroupIndex; i < groups.length; i++) {
147
+ lines.push(...formatQueryGroup(groups[i]));
148
+ }
149
+ return lines;
150
+ }
151
+ /**
152
+ * Truncate error output if it exceeds the max length
153
+ */
154
+ export function truncateErrorOutput(lines) {
155
+ if (errorConfig.verbose)
156
+ return lines;
157
+ const joined = lines.join('\n');
158
+ if (joined.length <= errorConfig.maxLength)
159
+ return lines;
160
+ // Truncate and add notice
161
+ const truncated = joined.slice(0, errorConfig.maxLength);
162
+ const lastNewline = truncated.lastIndexOf('\n');
163
+ const cleanTruncated = lastNewline > 0 ? truncated.slice(0, lastNewline) : truncated;
164
+ return [
165
+ ...cleanTruncated.split('\n'),
166
+ `... (output truncated at ${errorConfig.maxLength} chars, total was ${joined.length} chars)`,
167
+ ` Set PGPM_ERROR_VERBOSE=true to see full output`
168
+ ];
169
+ }
@@ -1,5 +1,8 @@
1
1
  import { Logger } from '@pgpmjs/logger';
2
2
  import { extractPgErrorFields, formatPgErrorFields } from '@pgpmjs/types';
3
+ import { formatQueryHistory, truncateErrorOutput } from './errors';
4
+ // Re-export error formatting functions for backward compatibility
5
+ export { formatQueryHistory, truncateErrorOutput } from './errors';
3
6
  const log = new Logger('migrate:transaction');
4
7
  /**
5
8
  * Execute a function within a transaction context
@@ -60,16 +63,11 @@ export async function withTransaction(pool, options, fn) {
60
63
  errorLines.push(...fieldLines);
61
64
  }
62
65
  }
63
- // Log query history for debugging
66
+ // Log query history for debugging (with smart collapsing and limiting)
64
67
  if (queryHistory.length > 0) {
65
68
  errorLines.push('Query history for this transaction:');
66
- queryHistory.forEach((entry, index) => {
67
- const duration = entry.duration ? ` (${entry.duration}ms)` : '';
68
- const params = entry.params && entry.params.length > 0
69
- ? ` with params: ${JSON.stringify(entry.params.slice(0, 2))}${entry.params.length > 2 ? '...' : ''}`
70
- : '';
71
- errorLines.push(` ${index + 1}. ${entry.query.split('\n')[0].trim()}${params}${duration}`);
72
- });
69
+ const historyLines = formatQueryHistory(queryHistory);
70
+ errorLines.push(...historyLines);
73
71
  }
74
72
  // For transaction aborted errors, provide additional context
75
73
  if (error.code === '25P02') {
@@ -77,8 +75,9 @@ export async function withTransaction(pool, options, fn) {
77
75
  errorLines.push(' This usually means a previous command in the transaction failed.');
78
76
  errorLines.push(' Check the query history above to identify the failing command.');
79
77
  }
80
- // Log the consolidated error message
81
- log.error(errorLines.join('\n'));
78
+ // Apply total output length limit and log the consolidated error message
79
+ const finalLines = truncateErrorOutput(errorLines);
80
+ log.error(finalLines.join('\n'));
82
81
  throw error;
83
82
  }
84
83
  finally {
@@ -190,8 +190,8 @@ const config = {
190
190
  fields: {
191
191
  id: 'uuid',
192
192
  database_id: 'uuid',
193
- schema_id: 'uuid',
194
- name: 'text'
193
+ name: 'text',
194
+ code: 'text'
195
195
  }
196
196
  },
197
197
  rls_function: {
@@ -200,8 +200,13 @@ const config = {
200
200
  fields: {
201
201
  id: 'uuid',
202
202
  database_id: 'uuid',
203
- schema_id: 'uuid',
204
- name: 'text'
203
+ table_id: 'uuid',
204
+ name: 'text',
205
+ label: 'text',
206
+ description: 'text',
207
+ data: 'jsonb',
208
+ inline: 'boolean',
209
+ security: 'int'
205
210
  }
206
211
  },
207
212
  limit_function: {
@@ -210,8 +215,12 @@ const config = {
210
215
  fields: {
211
216
  id: 'uuid',
212
217
  database_id: 'uuid',
213
- schema_id: 'uuid',
214
- name: 'text'
218
+ table_id: 'uuid',
219
+ name: 'text',
220
+ label: 'text',
221
+ description: 'text',
222
+ data: 'jsonb',
223
+ security: 'int'
215
224
  }
216
225
  },
217
226
  procedure: {
@@ -220,8 +229,12 @@ const config = {
220
229
  fields: {
221
230
  id: 'uuid',
222
231
  database_id: 'uuid',
223
- schema_id: 'uuid',
224
- name: 'text'
232
+ name: 'text',
233
+ argnames: 'text[]',
234
+ argtypes: 'text[]',
235
+ argdefaults: 'text[]',
236
+ lang_name: 'text',
237
+ definition: 'text'
225
238
  }
226
239
  },
227
240
  foreign_key_constraint: {
@@ -232,11 +245,14 @@ const config = {
232
245
  database_id: 'uuid',
233
246
  table_id: 'uuid',
234
247
  name: 'text',
248
+ description: 'text',
249
+ smart_tags: 'jsonb',
250
+ type: 'text',
235
251
  field_ids: 'uuid[]',
236
252
  ref_table_id: 'uuid',
237
253
  ref_field_ids: 'uuid[]',
238
- on_delete: 'text',
239
- on_update: 'text'
254
+ delete_action: 'text',
255
+ update_action: 'text'
240
256
  }
241
257
  },
242
258
  primary_key_constraint: {
@@ -247,6 +263,7 @@ const config = {
247
263
  database_id: 'uuid',
248
264
  table_id: 'uuid',
249
265
  name: 'text',
266
+ type: 'text',
250
267
  field_ids: 'uuid[]'
251
268
  }
252
269
  },
@@ -258,6 +275,9 @@ const config = {
258
275
  database_id: 'uuid',
259
276
  table_id: 'uuid',
260
277
  name: 'text',
278
+ description: 'text',
279
+ smart_tags: 'jsonb',
280
+ type: 'text',
261
281
  field_ids: 'uuid[]'
262
282
  }
263
283
  },
@@ -269,7 +289,9 @@ const config = {
269
289
  database_id: 'uuid',
270
290
  table_id: 'uuid',
271
291
  name: 'text',
272
- expression: 'text'
292
+ type: 'text',
293
+ field_ids: 'uuid[]',
294
+ expr: 'jsonb'
273
295
  }
274
296
  },
275
297
  full_text_search: {
@@ -279,9 +301,10 @@ const config = {
279
301
  id: 'uuid',
280
302
  database_id: 'uuid',
281
303
  table_id: 'uuid',
282
- name: 'text',
304
+ field_id: 'uuid',
283
305
  field_ids: 'uuid[]',
284
- weights: 'text[]'
306
+ weights: 'text[]',
307
+ langs: 'text[]'
285
308
  }
286
309
  },
287
310
  schema_grant: {
@@ -291,8 +314,7 @@ const config = {
291
314
  id: 'uuid',
292
315
  database_id: 'uuid',
293
316
  schema_id: 'uuid',
294
- role_name: 'text',
295
- privilege: 'text'
317
+ grantee_name: 'text'
296
318
  }
297
319
  },
298
320
  table_grant: {
@@ -302,8 +324,9 @@ const config = {
302
324
  id: 'uuid',
303
325
  database_id: 'uuid',
304
326
  table_id: 'uuid',
327
+ privilege: 'text',
305
328
  role_name: 'text',
306
- privilege: 'text'
329
+ field_ids: 'uuid[]'
307
330
  }
308
331
  },
309
332
  // =============================================================================
@@ -0,0 +1,18 @@
1
+ import { QueryHistoryEntry } from './transaction';
2
+ /**
3
+ * Get error output configuration from environment variables with defaults.
4
+ * Uses centralized env var parsing from @pgpmjs/env and defaults from @pgpmjs/types.
5
+ */
6
+ export declare const getErrorOutputConfig: () => {
7
+ queryHistoryLimit: number;
8
+ maxLength: number;
9
+ verbose: boolean;
10
+ };
11
+ /**
12
+ * Format query history with smart collapsing and limiting
13
+ */
14
+ export declare function formatQueryHistory(history: QueryHistoryEntry[]): string[];
15
+ /**
16
+ * Truncate error output if it exceeds the max length
17
+ */
18
+ export declare function truncateErrorOutput(lines: string[]): string[];
@@ -0,0 +1,175 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getErrorOutputConfig = void 0;
4
+ exports.formatQueryHistory = formatQueryHistory;
5
+ exports.truncateErrorOutput = truncateErrorOutput;
6
+ const env_1 = require("@pgpmjs/env");
7
+ const types_1 = require("@pgpmjs/types");
8
+ /**
9
+ * Get error output configuration from environment variables with defaults.
10
+ * Uses centralized env var parsing from @pgpmjs/env and defaults from @pgpmjs/types.
11
+ */
12
+ const getErrorOutputConfig = () => {
13
+ const envVars = (0, env_1.getEnvVars)();
14
+ const defaults = types_1.pgpmDefaults.errorOutput;
15
+ return {
16
+ queryHistoryLimit: envVars.errorOutput?.queryHistoryLimit ?? defaults.queryHistoryLimit,
17
+ maxLength: envVars.errorOutput?.maxLength ?? defaults.maxLength,
18
+ verbose: envVars.errorOutput?.verbose ?? defaults.verbose,
19
+ };
20
+ };
21
+ exports.getErrorOutputConfig = getErrorOutputConfig;
22
+ const errorConfig = (0, exports.getErrorOutputConfig)();
23
+ /**
24
+ * Extract a friendly name from pgpm_migrate.deploy params for better error context
25
+ */
26
+ function extractDeployChangeName(params) {
27
+ if (!params || params.length < 2)
28
+ return null;
29
+ // params[1] is the change name (e.g., "schemas/metaschema_public/tables/extension/table")
30
+ return typeof params[1] === 'string' ? params[1] : null;
31
+ }
32
+ /**
33
+ * Group consecutive queries by their query template (ignoring params)
34
+ */
35
+ function groupConsecutiveQueries(history) {
36
+ if (history.length === 0)
37
+ return [];
38
+ const groups = [];
39
+ let currentGroup = {
40
+ query: history[0].query,
41
+ startIndex: 0,
42
+ endIndex: 0,
43
+ count: 1,
44
+ entries: [history[0]]
45
+ };
46
+ for (let i = 1; i < history.length; i++) {
47
+ const entry = history[i];
48
+ if (entry.query === currentGroup.query) {
49
+ // Same query template, extend the group
50
+ currentGroup.endIndex = i;
51
+ currentGroup.count++;
52
+ currentGroup.entries.push(entry);
53
+ }
54
+ else {
55
+ // Different query, start a new group
56
+ groups.push(currentGroup);
57
+ currentGroup = {
58
+ query: entry.query,
59
+ startIndex: i,
60
+ endIndex: i,
61
+ count: 1,
62
+ entries: [entry]
63
+ };
64
+ }
65
+ }
66
+ groups.push(currentGroup);
67
+ return groups;
68
+ }
69
+ /**
70
+ * Format a single query entry for display
71
+ */
72
+ function formatQueryEntry(entry, index) {
73
+ const duration = entry.duration ? ` (${entry.duration}ms)` : '';
74
+ const params = entry.params && entry.params.length > 0
75
+ ? ` with params: ${JSON.stringify(entry.params.slice(0, 2))}${entry.params.length > 2 ? '...' : ''}`
76
+ : '';
77
+ return ` ${index + 1}. ${entry.query.split('\n')[0].trim()}${params}${duration}`;
78
+ }
79
+ /**
80
+ * Format a group of queries for display, collapsing repetitive queries
81
+ */
82
+ function formatQueryGroup(group) {
83
+ const lines = [];
84
+ const queryPreview = group.query.split('\n')[0].trim();
85
+ if (group.count === 1) {
86
+ // Single query, format normally
87
+ lines.push(formatQueryEntry(group.entries[0], group.startIndex));
88
+ }
89
+ else {
90
+ // Multiple consecutive identical queries, collapse them
91
+ const isPgpmDeploy = queryPreview.includes('pgpm_migrate.deploy');
92
+ // Show range and count
93
+ lines.push(` ${group.startIndex + 1}-${group.endIndex + 1}. ${queryPreview} (${group.count} calls)`);
94
+ // For pgpm_migrate.deploy, show first and last change names for context
95
+ if (isPgpmDeploy) {
96
+ const firstChange = extractDeployChangeName(group.entries[0].params);
97
+ const lastChange = extractDeployChangeName(group.entries[group.entries.length - 1].params);
98
+ if (firstChange) {
99
+ lines.push(` First: ${firstChange}`);
100
+ }
101
+ if (lastChange && lastChange !== firstChange) {
102
+ lines.push(` Last: ${lastChange}`);
103
+ }
104
+ }
105
+ else {
106
+ // For other queries, show first and last params
107
+ const firstParams = group.entries[0].params;
108
+ const lastParams = group.entries[group.entries.length - 1].params;
109
+ if (firstParams && firstParams.length > 0) {
110
+ lines.push(` First params: ${JSON.stringify(firstParams.slice(0, 2))}${firstParams.length > 2 ? '...' : ''}`);
111
+ }
112
+ if (lastParams && lastParams.length > 0 && JSON.stringify(lastParams) !== JSON.stringify(firstParams)) {
113
+ lines.push(` Last params: ${JSON.stringify(lastParams.slice(0, 2))}${lastParams.length > 2 ? '...' : ''}`);
114
+ }
115
+ }
116
+ }
117
+ return lines;
118
+ }
119
+ /**
120
+ * Format query history with smart collapsing and limiting
121
+ */
122
+ function formatQueryHistory(history) {
123
+ if (history.length === 0)
124
+ return [];
125
+ // In verbose mode, show everything without collapsing
126
+ if (errorConfig.verbose) {
127
+ return history.map((entry, index) => formatQueryEntry(entry, index));
128
+ }
129
+ // Group consecutive identical queries
130
+ const groups = groupConsecutiveQueries(history);
131
+ // Apply limit - keep last N queries worth of groups
132
+ let totalQueries = 0;
133
+ let startGroupIndex = groups.length;
134
+ for (let i = groups.length - 1; i >= 0; i--) {
135
+ totalQueries += groups[i].count;
136
+ if (totalQueries > errorConfig.queryHistoryLimit) {
137
+ startGroupIndex = i + 1;
138
+ break;
139
+ }
140
+ startGroupIndex = i;
141
+ }
142
+ const lines = [];
143
+ // If we're truncating, show how many were omitted
144
+ if (startGroupIndex > 0) {
145
+ let omittedCount = 0;
146
+ for (let i = 0; i < startGroupIndex; i++) {
147
+ omittedCount += groups[i].count;
148
+ }
149
+ lines.push(` ... (${omittedCount} earlier queries omitted, set PGPM_ERROR_VERBOSE=true to see all)`);
150
+ }
151
+ // Format the remaining groups
152
+ for (let i = startGroupIndex; i < groups.length; i++) {
153
+ lines.push(...formatQueryGroup(groups[i]));
154
+ }
155
+ return lines;
156
+ }
157
+ /**
158
+ * Truncate error output if it exceeds the max length
159
+ */
160
+ function truncateErrorOutput(lines) {
161
+ if (errorConfig.verbose)
162
+ return lines;
163
+ const joined = lines.join('\n');
164
+ if (joined.length <= errorConfig.maxLength)
165
+ return lines;
166
+ // Truncate and add notice
167
+ const truncated = joined.slice(0, errorConfig.maxLength);
168
+ const lastNewline = truncated.lastIndexOf('\n');
169
+ const cleanTruncated = lastNewline > 0 ? truncated.slice(0, lastNewline) : truncated;
170
+ return [
171
+ ...cleanTruncated.split('\n'),
172
+ `... (output truncated at ${errorConfig.maxLength} chars, total was ${joined.length} chars)`,
173
+ ` Set PGPM_ERROR_VERBOSE=true to see full output`
174
+ ];
175
+ }
@@ -1,4 +1,5 @@
1
1
  import { Pool, PoolClient } from 'pg';
2
+ export { formatQueryHistory, truncateErrorOutput } from './errors';
2
3
  export interface TransactionOptions {
3
4
  useTransaction: boolean;
4
5
  }
@@ -1,9 +1,15 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.truncateErrorOutput = exports.formatQueryHistory = void 0;
3
4
  exports.withTransaction = withTransaction;
4
5
  exports.executeQuery = executeQuery;
5
6
  const logger_1 = require("@pgpmjs/logger");
6
7
  const types_1 = require("@pgpmjs/types");
8
+ const errors_1 = require("./errors");
9
+ // Re-export error formatting functions for backward compatibility
10
+ var errors_2 = require("./errors");
11
+ Object.defineProperty(exports, "formatQueryHistory", { enumerable: true, get: function () { return errors_2.formatQueryHistory; } });
12
+ Object.defineProperty(exports, "truncateErrorOutput", { enumerable: true, get: function () { return errors_2.truncateErrorOutput; } });
7
13
  const log = new logger_1.Logger('migrate:transaction');
8
14
  /**
9
15
  * Execute a function within a transaction context
@@ -64,16 +70,11 @@ async function withTransaction(pool, options, fn) {
64
70
  errorLines.push(...fieldLines);
65
71
  }
66
72
  }
67
- // Log query history for debugging
73
+ // Log query history for debugging (with smart collapsing and limiting)
68
74
  if (queryHistory.length > 0) {
69
75
  errorLines.push('Query history for this transaction:');
70
- queryHistory.forEach((entry, index) => {
71
- const duration = entry.duration ? ` (${entry.duration}ms)` : '';
72
- const params = entry.params && entry.params.length > 0
73
- ? ` with params: ${JSON.stringify(entry.params.slice(0, 2))}${entry.params.length > 2 ? '...' : ''}`
74
- : '';
75
- errorLines.push(` ${index + 1}. ${entry.query.split('\n')[0].trim()}${params}${duration}`);
76
- });
76
+ const historyLines = (0, errors_1.formatQueryHistory)(queryHistory);
77
+ errorLines.push(...historyLines);
77
78
  }
78
79
  // For transaction aborted errors, provide additional context
79
80
  if (error.code === '25P02') {
@@ -81,8 +82,9 @@ async function withTransaction(pool, options, fn) {
81
82
  errorLines.push(' This usually means a previous command in the transaction failed.');
82
83
  errorLines.push(' Check the query history above to identify the failing command.');
83
84
  }
84
- // Log the consolidated error message
85
- log.error(errorLines.join('\n'));
85
+ // Apply total output length limit and log the consolidated error message
86
+ const finalLines = (0, errors_1.truncateErrorOutput)(errorLines);
87
+ log.error(finalLines.join('\n'));
86
88
  throw error;
87
89
  }
88
90
  finally {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pgpmjs/core",
3
- "version": "4.13.2",
3
+ "version": "4.14.0",
4
4
  "author": "Constructive <developers@constructive.io>",
5
5
  "description": "PGPM Package and Migration Tools",
6
6
  "main": "index.js",
@@ -48,21 +48,21 @@
48
48
  "makage": "^0.1.10"
49
49
  },
50
50
  "dependencies": {
51
- "@pgpmjs/env": "^2.9.3",
51
+ "@pgpmjs/env": "^2.9.4",
52
52
  "@pgpmjs/logger": "^1.3.7",
53
- "@pgpmjs/server-utils": "^2.8.14",
54
- "@pgpmjs/types": "^2.14.0",
53
+ "@pgpmjs/server-utils": "^2.8.15",
54
+ "@pgpmjs/types": "^2.14.1",
55
55
  "csv-to-pg": "^3.4.1",
56
56
  "genomic": "^5.2.3",
57
57
  "glob": "^13.0.0",
58
58
  "komoji": "^0.7.14",
59
59
  "parse-package-name": "^1.0.0",
60
60
  "pg": "^8.16.3",
61
- "pg-cache": "^1.6.14",
61
+ "pg-cache": "^1.6.15",
62
62
  "pg-env": "^1.2.5",
63
63
  "pgsql-deparser": "^17.17.2",
64
64
  "pgsql-parser": "^17.9.11",
65
65
  "yanse": "^0.1.11"
66
66
  },
67
- "gitHead": "6a66f8849db582924e8a24e938a6a38ae3122467"
67
+ "gitHead": "cb4af2cf6c23dad24cd951c232d3e2006b81aa3d"
68
68
  }