relq 1.0.112 → 1.0.114
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/dist/cjs/cli/commands/push.cjs +26 -2
- package/dist/cjs/cli/utils/schema-loader.cjs +18 -0
- package/dist/cjs/cli/utils/snapshot-manager.cjs +34 -2
- package/dist/cjs/core/pg-family/shared/pg-base.cjs +36 -8
- package/dist/cjs/core/relq-base.cjs +2 -0
- package/dist/cjs/errors/relq-errors.cjs +17 -1
- package/dist/esm/cli/commands/push.js +26 -2
- package/dist/esm/cli/utils/schema-loader.js +18 -0
- package/dist/esm/cli/utils/snapshot-manager.js +34 -2
- package/dist/esm/core/pg-family/shared/pg-base.js +36 -8
- package/dist/esm/core/relq-base.js +2 -0
- package/dist/esm/errors/relq-errors.js +17 -1
- package/dist/index.d.ts +1 -0
- package/package.json +1 -1
|
@@ -57,6 +57,7 @@ const schema_hash_1 = require("../utils/schema-hash.cjs");
|
|
|
57
57
|
const migration_generator_1 = require("../utils/migration-generator.cjs");
|
|
58
58
|
const migration_helpers_1 = require("../utils/migration-helpers.cjs");
|
|
59
59
|
const ast_transformer_1 = require("../utils/ast-transformer.cjs");
|
|
60
|
+
const types_manager_1 = require("../utils/types-manager.cjs");
|
|
60
61
|
function formatColumnProps(col) {
|
|
61
62
|
if (!col)
|
|
62
63
|
return '';
|
|
@@ -110,6 +111,20 @@ function mergeTrackingIds(introspected, desired) {
|
|
|
110
111
|
}
|
|
111
112
|
}
|
|
112
113
|
}
|
|
114
|
+
const desiredFuncMap = new Map((desired.functions || []).map(f => [f.name, f]));
|
|
115
|
+
for (const func of introspected.functions || []) {
|
|
116
|
+
const desiredFunc = desiredFuncMap.get(func.name);
|
|
117
|
+
if (desiredFunc?.trackingId) {
|
|
118
|
+
func.trackingId = desiredFunc.trackingId;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const desiredTrigMap = new Map((desired.triggers || []).map(t => [t.name, t]));
|
|
122
|
+
for (const trig of introspected.triggers || []) {
|
|
123
|
+
const desiredTrig = desiredTrigMap.get(trig.name);
|
|
124
|
+
if (desiredTrig?.trackingId) {
|
|
125
|
+
trig.trackingId = desiredTrig.trackingId;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
113
128
|
return introspected;
|
|
114
129
|
}
|
|
115
130
|
async function runPush(config, projectRoot, opts = {}) {
|
|
@@ -187,14 +202,17 @@ async function runPush(config, projectRoot, opts = {}) {
|
|
|
187
202
|
}
|
|
188
203
|
}
|
|
189
204
|
const desiredASTCopy = { ...desiredAST };
|
|
190
|
-
if (!includeFunctions) {
|
|
205
|
+
if (!includeFunctions || desiredASTCopy.functions.length === 0) {
|
|
191
206
|
dbParsedSchema.functions = [];
|
|
192
207
|
desiredASTCopy.functions = [];
|
|
193
208
|
}
|
|
194
|
-
if (!includeTriggers) {
|
|
209
|
+
if (!includeTriggers || desiredASTCopy.triggers.length === 0) {
|
|
195
210
|
dbParsedSchema.triggers = [];
|
|
196
211
|
desiredASTCopy.triggers = [];
|
|
197
212
|
}
|
|
213
|
+
if (desiredASTCopy.sequences.length === 0) {
|
|
214
|
+
dbParsedSchema.sequences = [];
|
|
215
|
+
}
|
|
198
216
|
const comparison = (0, schema_diff_1.compareSchemas)(dbParsedSchema, desiredASTCopy);
|
|
199
217
|
spin.stop('Diff computed');
|
|
200
218
|
if (!comparison.hasChanges && !filteredDiff.hasChanges) {
|
|
@@ -587,6 +605,12 @@ async function runPush(config, projectRoot, opts = {}) {
|
|
|
587
605
|
mergeTrackingIds(updatedSchema, desiredSchema);
|
|
588
606
|
(0, snapshot_manager_1.saveSnapshot)(updatedSchema, snapshotPath, connection.database);
|
|
589
607
|
spin.stop('Snapshot updated');
|
|
608
|
+
const typesFilePath = (0, types_manager_1.getTypesFilePath)(schemaPath);
|
|
609
|
+
try {
|
|
610
|
+
await (0, types_manager_1.syncTypesToDb)(connection, typesFilePath, schemaPath);
|
|
611
|
+
}
|
|
612
|
+
catch {
|
|
613
|
+
}
|
|
590
614
|
const duration = Date.now() - startTime;
|
|
591
615
|
console.log('');
|
|
592
616
|
console.log(`Push completed in ${(0, format_1.formatDuration)(duration)}`);
|
|
@@ -111,6 +111,24 @@ async function loadSchemaFile(schemaPath, projectRoot) {
|
|
|
111
111
|
});
|
|
112
112
|
const schemaModule = await jiti.import(absolutePath);
|
|
113
113
|
const ast = (0, schema_to_ast_1.schemaToAST)(schemaModule);
|
|
114
|
+
const ext = path.extname(absolutePath);
|
|
115
|
+
const baseName = absolutePath.slice(0, -ext.length);
|
|
116
|
+
for (const suffix of ['functions', 'triggers', 'views']) {
|
|
117
|
+
const companionPath = `${baseName}.${suffix}${ext}`;
|
|
118
|
+
if (!fs.existsSync(companionPath))
|
|
119
|
+
continue;
|
|
120
|
+
const companionModule = await jiti.import(companionPath);
|
|
121
|
+
const companionAST = (0, schema_to_ast_1.schemaToAST)(companionModule);
|
|
122
|
+
ast.tables.push(...companionAST.tables);
|
|
123
|
+
ast.enums.push(...companionAST.enums);
|
|
124
|
+
ast.domains.push(...companionAST.domains);
|
|
125
|
+
ast.compositeTypes.push(...companionAST.compositeTypes);
|
|
126
|
+
ast.sequences.push(...companionAST.sequences);
|
|
127
|
+
ast.views.push(...companionAST.views);
|
|
128
|
+
ast.functions.push(...companionAST.functions);
|
|
129
|
+
ast.triggers.push(...companionAST.triggers);
|
|
130
|
+
ast.extensions.push(...companionAST.extensions);
|
|
131
|
+
}
|
|
114
132
|
const tables = ast.tables.map(schema_to_ast_1.parsedTableToTableInfo);
|
|
115
133
|
const schema = {
|
|
116
134
|
tables,
|
|
@@ -78,11 +78,32 @@ function databaseSchemaToSnapshot(schema, database) {
|
|
|
78
78
|
for (const table of schema.tables) {
|
|
79
79
|
tables[table.name] = tableToSnapshot(table);
|
|
80
80
|
}
|
|
81
|
+
const functions = (schema.functions || []).map(f => ({
|
|
82
|
+
name: f.name,
|
|
83
|
+
schema: f.schema || 'public',
|
|
84
|
+
returnType: f.returnType,
|
|
85
|
+
argTypes: f.argTypes || [],
|
|
86
|
+
language: f.language,
|
|
87
|
+
definition: f.definition || '',
|
|
88
|
+
volatility: f.volatility || 'VOLATILE',
|
|
89
|
+
...(f.trackingId ? { trackingId: f.trackingId } : {}),
|
|
90
|
+
}));
|
|
91
|
+
const triggers = (schema.triggers || []).map(t => ({
|
|
92
|
+
name: t.name,
|
|
93
|
+
tableName: t.tableName,
|
|
94
|
+
event: t.event,
|
|
95
|
+
timing: t.timing,
|
|
96
|
+
forEach: t.forEach,
|
|
97
|
+
functionName: t.functionName,
|
|
98
|
+
...(t.trackingId ? { trackingId: t.trackingId } : {}),
|
|
99
|
+
}));
|
|
81
100
|
return {
|
|
82
101
|
version: SNAPSHOT_VERSION,
|
|
83
102
|
generatedAt: new Date().toISOString(),
|
|
84
103
|
database,
|
|
85
104
|
tables,
|
|
105
|
+
functions,
|
|
106
|
+
triggers,
|
|
86
107
|
appliedMigrations: [],
|
|
87
108
|
extensions: schema.extensions || [],
|
|
88
109
|
};
|
|
@@ -151,6 +172,17 @@ function snapshotToDatabaseSchema(snapshot) {
|
|
|
151
172
|
for (const [, tableSnapshot] of Object.entries(snapshot.tables)) {
|
|
152
173
|
tables.push(snapshotToTable(tableSnapshot));
|
|
153
174
|
}
|
|
175
|
+
const functions = (snapshot.functions || []).map(f => ({
|
|
176
|
+
...f,
|
|
177
|
+
argTypes: f.argTypes || [],
|
|
178
|
+
isAggregate: false,
|
|
179
|
+
}));
|
|
180
|
+
const triggers = (snapshot.triggers || []).map(t => ({
|
|
181
|
+
...t,
|
|
182
|
+
forEach: (t.forEach || 'ROW'),
|
|
183
|
+
definition: '',
|
|
184
|
+
isEnabled: true,
|
|
185
|
+
}));
|
|
154
186
|
return {
|
|
155
187
|
tables,
|
|
156
188
|
enums: [],
|
|
@@ -158,8 +190,8 @@ function snapshotToDatabaseSchema(snapshot) {
|
|
|
158
190
|
compositeTypes: [],
|
|
159
191
|
sequences: [],
|
|
160
192
|
collations: [],
|
|
161
|
-
functions
|
|
162
|
-
triggers
|
|
193
|
+
functions,
|
|
194
|
+
triggers,
|
|
163
195
|
policies: [],
|
|
164
196
|
partitions: [],
|
|
165
197
|
foreignServers: [],
|
|
@@ -257,15 +257,24 @@ class PgBase extends relq_base_1.RelqBase {
|
|
|
257
257
|
return;
|
|
258
258
|
this.poolErrorHandler = (err) => {
|
|
259
259
|
const errorCode = err.code;
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
260
|
+
if (isPoolRecoverableError(errorCode)) {
|
|
261
|
+
const logLevel = this.config.logLevel ?? 'info';
|
|
262
|
+
if (logLevel !== 'silent') {
|
|
263
|
+
console.warn('[Relq Pool] Recoverable connection error (pool will auto-recover):', {
|
|
264
|
+
code: errorCode,
|
|
265
|
+
message: err.message,
|
|
266
|
+
action: 'Connection removed from pool, will be replaced on next query'
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
const logLevel = this.config.logLevel ?? 'info';
|
|
272
|
+
if (logLevel !== 'silent') {
|
|
273
|
+
console.error('[Relq Pool] Pool error:', {
|
|
274
|
+
code: errorCode,
|
|
275
|
+
message: err.message,
|
|
276
|
+
});
|
|
263
277
|
}
|
|
264
|
-
console.warn('[Relq Pool] Recoverable connection error (pool will auto-recover):', {
|
|
265
|
-
code: errorCode,
|
|
266
|
-
message: err.message,
|
|
267
|
-
action: 'Connection removed from pool, will be replaced on next query'
|
|
268
|
-
});
|
|
269
278
|
};
|
|
270
279
|
this.poolConnectHandler = () => { };
|
|
271
280
|
this.poolRemoveHandler = () => { };
|
|
@@ -349,3 +358,22 @@ class PgBase extends relq_base_1.RelqBase {
|
|
|
349
358
|
}
|
|
350
359
|
}
|
|
351
360
|
exports.PgBase = PgBase;
|
|
361
|
+
const POOL_RECOVERABLE_NETWORK_CODES = new Set([
|
|
362
|
+
'ECONNRESET', 'ECONNREFUSED', 'ENOTFOUND', 'ESERVFAIL',
|
|
363
|
+
'ETIMEDOUT', 'EPIPE', 'EAI_AGAIN', 'EHOSTUNREACH',
|
|
364
|
+
'ECONNABORTED', 'ENETUNREACH', 'ENETRESET',
|
|
365
|
+
'CONNECTION_LOST', 'PROTOCOL_CONNECTION_LOST',
|
|
366
|
+
]);
|
|
367
|
+
const POOL_RECOVERABLE_PG_CODES = new Set([
|
|
368
|
+
'57P01',
|
|
369
|
+
'57P02',
|
|
370
|
+
'57P03',
|
|
371
|
+
'08006',
|
|
372
|
+
'08001',
|
|
373
|
+
'08004',
|
|
374
|
+
]);
|
|
375
|
+
function isPoolRecoverableError(code) {
|
|
376
|
+
if (!code)
|
|
377
|
+
return true;
|
|
378
|
+
return POOL_RECOVERABLE_NETWORK_CODES.has(code) || POOL_RECOVERABLE_PG_CODES.has(code);
|
|
379
|
+
}
|
|
@@ -14,6 +14,7 @@ class RelqBase {
|
|
|
14
14
|
config;
|
|
15
15
|
schema;
|
|
16
16
|
emitter = new node_events_1.EventEmitter();
|
|
17
|
+
_defaultErrorHandler = () => { };
|
|
17
18
|
columnMappings = new Map();
|
|
18
19
|
initialized = false;
|
|
19
20
|
initPromise;
|
|
@@ -21,6 +22,7 @@ class RelqBase {
|
|
|
21
22
|
constructor(schema, config) {
|
|
22
23
|
this.config = config;
|
|
23
24
|
this.schema = schema;
|
|
25
|
+
this.emitter.on('error', this._defaultErrorHandler);
|
|
24
26
|
if (schema) {
|
|
25
27
|
const log = config.logLevel === 'debug'
|
|
26
28
|
? (...args) => (0, helpers_1.debugLog)(config, ...args)
|
|
@@ -312,7 +312,15 @@ function wrapError(error, context) {
|
|
|
312
312
|
function parsePostgresError(error, sql) {
|
|
313
313
|
const message = error.message || 'Database error';
|
|
314
314
|
const code = error.code;
|
|
315
|
-
if (code
|
|
315
|
+
if (isNetworkErrorCode(code)) {
|
|
316
|
+
return new RelqConnectionError(message, {
|
|
317
|
+
cause: error,
|
|
318
|
+
code,
|
|
319
|
+
host: error.hostname || error.address,
|
|
320
|
+
port: error.port
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
if (code === '57P01' || code === '57P03' || code === '08006' || code === '08001' || code === '08004') {
|
|
316
324
|
return new RelqConnectionError(message, {
|
|
317
325
|
cause: error,
|
|
318
326
|
code,
|
|
@@ -331,3 +339,11 @@ function parsePostgresError(error, sql) {
|
|
|
331
339
|
hint: error.hint
|
|
332
340
|
});
|
|
333
341
|
}
|
|
342
|
+
const NETWORK_ERROR_CODES = new Set([
|
|
343
|
+
'ECONNREFUSED', 'ECONNRESET', 'ENOTFOUND', 'ESERVFAIL',
|
|
344
|
+
'ETIMEDOUT', 'EPIPE', 'EAI_AGAIN', 'EHOSTUNREACH',
|
|
345
|
+
'CONNECTION_LOST', 'PROTOCOL_CONNECTION_LOST',
|
|
346
|
+
]);
|
|
347
|
+
function isNetworkErrorCode(code) {
|
|
348
|
+
return !!code && NETWORK_ERROR_CODES.has(code);
|
|
349
|
+
}
|
|
@@ -21,6 +21,7 @@ import { normalizeSchema } from "../utils/schema-hash.js";
|
|
|
21
21
|
import { generateTimestampedName, generateMigrationFromComparison, generateMigrationNameFromComparison } from "../utils/migration-generator.js";
|
|
22
22
|
import { getMigrationTableDDL } from "../utils/migration-helpers.js";
|
|
23
23
|
import { introspectedToParsedSchema } from "../utils/ast-transformer.js";
|
|
24
|
+
import { syncTypesToDb, getTypesFilePath } from "../utils/types-manager.js";
|
|
24
25
|
function formatColumnProps(col) {
|
|
25
26
|
if (!col)
|
|
26
27
|
return '';
|
|
@@ -74,6 +75,20 @@ function mergeTrackingIds(introspected, desired) {
|
|
|
74
75
|
}
|
|
75
76
|
}
|
|
76
77
|
}
|
|
78
|
+
const desiredFuncMap = new Map((desired.functions || []).map(f => [f.name, f]));
|
|
79
|
+
for (const func of introspected.functions || []) {
|
|
80
|
+
const desiredFunc = desiredFuncMap.get(func.name);
|
|
81
|
+
if (desiredFunc?.trackingId) {
|
|
82
|
+
func.trackingId = desiredFunc.trackingId;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const desiredTrigMap = new Map((desired.triggers || []).map(t => [t.name, t]));
|
|
86
|
+
for (const trig of introspected.triggers || []) {
|
|
87
|
+
const desiredTrig = desiredTrigMap.get(trig.name);
|
|
88
|
+
if (desiredTrig?.trackingId) {
|
|
89
|
+
trig.trackingId = desiredTrig.trackingId;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
77
92
|
return introspected;
|
|
78
93
|
}
|
|
79
94
|
export async function runPush(config, projectRoot, opts = {}) {
|
|
@@ -151,14 +166,17 @@ export async function runPush(config, projectRoot, opts = {}) {
|
|
|
151
166
|
}
|
|
152
167
|
}
|
|
153
168
|
const desiredASTCopy = { ...desiredAST };
|
|
154
|
-
if (!includeFunctions) {
|
|
169
|
+
if (!includeFunctions || desiredASTCopy.functions.length === 0) {
|
|
155
170
|
dbParsedSchema.functions = [];
|
|
156
171
|
desiredASTCopy.functions = [];
|
|
157
172
|
}
|
|
158
|
-
if (!includeTriggers) {
|
|
173
|
+
if (!includeTriggers || desiredASTCopy.triggers.length === 0) {
|
|
159
174
|
dbParsedSchema.triggers = [];
|
|
160
175
|
desiredASTCopy.triggers = [];
|
|
161
176
|
}
|
|
177
|
+
if (desiredASTCopy.sequences.length === 0) {
|
|
178
|
+
dbParsedSchema.sequences = [];
|
|
179
|
+
}
|
|
162
180
|
const comparison = compareSchemas(dbParsedSchema, desiredASTCopy);
|
|
163
181
|
spin.stop('Diff computed');
|
|
164
182
|
if (!comparison.hasChanges && !filteredDiff.hasChanges) {
|
|
@@ -551,6 +569,12 @@ export async function runPush(config, projectRoot, opts = {}) {
|
|
|
551
569
|
mergeTrackingIds(updatedSchema, desiredSchema);
|
|
552
570
|
saveSnapshot(updatedSchema, snapshotPath, connection.database);
|
|
553
571
|
spin.stop('Snapshot updated');
|
|
572
|
+
const typesFilePath = getTypesFilePath(schemaPath);
|
|
573
|
+
try {
|
|
574
|
+
await syncTypesToDb(connection, typesFilePath, schemaPath);
|
|
575
|
+
}
|
|
576
|
+
catch {
|
|
577
|
+
}
|
|
554
578
|
const duration = Date.now() - startTime;
|
|
555
579
|
console.log('');
|
|
556
580
|
console.log(`Push completed in ${formatDuration(duration)}`);
|
|
@@ -75,6 +75,24 @@ export async function loadSchemaFile(schemaPath, projectRoot) {
|
|
|
75
75
|
});
|
|
76
76
|
const schemaModule = await jiti.import(absolutePath);
|
|
77
77
|
const ast = schemaToAST(schemaModule);
|
|
78
|
+
const ext = path.extname(absolutePath);
|
|
79
|
+
const baseName = absolutePath.slice(0, -ext.length);
|
|
80
|
+
for (const suffix of ['functions', 'triggers', 'views']) {
|
|
81
|
+
const companionPath = `${baseName}.${suffix}${ext}`;
|
|
82
|
+
if (!fs.existsSync(companionPath))
|
|
83
|
+
continue;
|
|
84
|
+
const companionModule = await jiti.import(companionPath);
|
|
85
|
+
const companionAST = schemaToAST(companionModule);
|
|
86
|
+
ast.tables.push(...companionAST.tables);
|
|
87
|
+
ast.enums.push(...companionAST.enums);
|
|
88
|
+
ast.domains.push(...companionAST.domains);
|
|
89
|
+
ast.compositeTypes.push(...companionAST.compositeTypes);
|
|
90
|
+
ast.sequences.push(...companionAST.sequences);
|
|
91
|
+
ast.views.push(...companionAST.views);
|
|
92
|
+
ast.functions.push(...companionAST.functions);
|
|
93
|
+
ast.triggers.push(...companionAST.triggers);
|
|
94
|
+
ast.extensions.push(...companionAST.extensions);
|
|
95
|
+
}
|
|
78
96
|
const tables = ast.tables.map(parsedTableToTableInfo);
|
|
79
97
|
const schema = {
|
|
80
98
|
tables,
|
|
@@ -34,11 +34,32 @@ export function databaseSchemaToSnapshot(schema, database) {
|
|
|
34
34
|
for (const table of schema.tables) {
|
|
35
35
|
tables[table.name] = tableToSnapshot(table);
|
|
36
36
|
}
|
|
37
|
+
const functions = (schema.functions || []).map(f => ({
|
|
38
|
+
name: f.name,
|
|
39
|
+
schema: f.schema || 'public',
|
|
40
|
+
returnType: f.returnType,
|
|
41
|
+
argTypes: f.argTypes || [],
|
|
42
|
+
language: f.language,
|
|
43
|
+
definition: f.definition || '',
|
|
44
|
+
volatility: f.volatility || 'VOLATILE',
|
|
45
|
+
...(f.trackingId ? { trackingId: f.trackingId } : {}),
|
|
46
|
+
}));
|
|
47
|
+
const triggers = (schema.triggers || []).map(t => ({
|
|
48
|
+
name: t.name,
|
|
49
|
+
tableName: t.tableName,
|
|
50
|
+
event: t.event,
|
|
51
|
+
timing: t.timing,
|
|
52
|
+
forEach: t.forEach,
|
|
53
|
+
functionName: t.functionName,
|
|
54
|
+
...(t.trackingId ? { trackingId: t.trackingId } : {}),
|
|
55
|
+
}));
|
|
37
56
|
return {
|
|
38
57
|
version: SNAPSHOT_VERSION,
|
|
39
58
|
generatedAt: new Date().toISOString(),
|
|
40
59
|
database,
|
|
41
60
|
tables,
|
|
61
|
+
functions,
|
|
62
|
+
triggers,
|
|
42
63
|
appliedMigrations: [],
|
|
43
64
|
extensions: schema.extensions || [],
|
|
44
65
|
};
|
|
@@ -107,6 +128,17 @@ export function snapshotToDatabaseSchema(snapshot) {
|
|
|
107
128
|
for (const [, tableSnapshot] of Object.entries(snapshot.tables)) {
|
|
108
129
|
tables.push(snapshotToTable(tableSnapshot));
|
|
109
130
|
}
|
|
131
|
+
const functions = (snapshot.functions || []).map(f => ({
|
|
132
|
+
...f,
|
|
133
|
+
argTypes: f.argTypes || [],
|
|
134
|
+
isAggregate: false,
|
|
135
|
+
}));
|
|
136
|
+
const triggers = (snapshot.triggers || []).map(t => ({
|
|
137
|
+
...t,
|
|
138
|
+
forEach: (t.forEach || 'ROW'),
|
|
139
|
+
definition: '',
|
|
140
|
+
isEnabled: true,
|
|
141
|
+
}));
|
|
110
142
|
return {
|
|
111
143
|
tables,
|
|
112
144
|
enums: [],
|
|
@@ -114,8 +146,8 @@ export function snapshotToDatabaseSchema(snapshot) {
|
|
|
114
146
|
compositeTypes: [],
|
|
115
147
|
sequences: [],
|
|
116
148
|
collations: [],
|
|
117
|
-
functions
|
|
118
|
-
triggers
|
|
149
|
+
functions,
|
|
150
|
+
triggers,
|
|
119
151
|
policies: [],
|
|
120
152
|
partitions: [],
|
|
121
153
|
foreignServers: [],
|
|
@@ -220,15 +220,24 @@ export class PgBase extends RelqBase {
|
|
|
220
220
|
return;
|
|
221
221
|
this.poolErrorHandler = (err) => {
|
|
222
222
|
const errorCode = err.code;
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
223
|
+
if (isPoolRecoverableError(errorCode)) {
|
|
224
|
+
const logLevel = this.config.logLevel ?? 'info';
|
|
225
|
+
if (logLevel !== 'silent') {
|
|
226
|
+
console.warn('[Relq Pool] Recoverable connection error (pool will auto-recover):', {
|
|
227
|
+
code: errorCode,
|
|
228
|
+
message: err.message,
|
|
229
|
+
action: 'Connection removed from pool, will be replaced on next query'
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
const logLevel = this.config.logLevel ?? 'info';
|
|
235
|
+
if (logLevel !== 'silent') {
|
|
236
|
+
console.error('[Relq Pool] Pool error:', {
|
|
237
|
+
code: errorCode,
|
|
238
|
+
message: err.message,
|
|
239
|
+
});
|
|
226
240
|
}
|
|
227
|
-
console.warn('[Relq Pool] Recoverable connection error (pool will auto-recover):', {
|
|
228
|
-
code: errorCode,
|
|
229
|
-
message: err.message,
|
|
230
|
-
action: 'Connection removed from pool, will be replaced on next query'
|
|
231
|
-
});
|
|
232
241
|
};
|
|
233
242
|
this.poolConnectHandler = () => { };
|
|
234
243
|
this.poolRemoveHandler = () => { };
|
|
@@ -311,3 +320,22 @@ export class PgBase extends RelqBase {
|
|
|
311
320
|
this.emitter.emit('connect', this.client);
|
|
312
321
|
}
|
|
313
322
|
}
|
|
323
|
+
const POOL_RECOVERABLE_NETWORK_CODES = new Set([
|
|
324
|
+
'ECONNRESET', 'ECONNREFUSED', 'ENOTFOUND', 'ESERVFAIL',
|
|
325
|
+
'ETIMEDOUT', 'EPIPE', 'EAI_AGAIN', 'EHOSTUNREACH',
|
|
326
|
+
'ECONNABORTED', 'ENETUNREACH', 'ENETRESET',
|
|
327
|
+
'CONNECTION_LOST', 'PROTOCOL_CONNECTION_LOST',
|
|
328
|
+
]);
|
|
329
|
+
const POOL_RECOVERABLE_PG_CODES = new Set([
|
|
330
|
+
'57P01',
|
|
331
|
+
'57P02',
|
|
332
|
+
'57P03',
|
|
333
|
+
'08006',
|
|
334
|
+
'08001',
|
|
335
|
+
'08004',
|
|
336
|
+
]);
|
|
337
|
+
function isPoolRecoverableError(code) {
|
|
338
|
+
if (!code)
|
|
339
|
+
return true;
|
|
340
|
+
return POOL_RECOVERABLE_NETWORK_CODES.has(code) || POOL_RECOVERABLE_PG_CODES.has(code);
|
|
341
|
+
}
|
|
@@ -11,6 +11,7 @@ export class RelqBase {
|
|
|
11
11
|
config;
|
|
12
12
|
schema;
|
|
13
13
|
emitter = new EventEmitter();
|
|
14
|
+
_defaultErrorHandler = () => { };
|
|
14
15
|
columnMappings = new Map();
|
|
15
16
|
initialized = false;
|
|
16
17
|
initPromise;
|
|
@@ -18,6 +19,7 @@ export class RelqBase {
|
|
|
18
19
|
constructor(schema, config) {
|
|
19
20
|
this.config = config;
|
|
20
21
|
this.schema = schema;
|
|
22
|
+
this.emitter.on('error', this._defaultErrorHandler);
|
|
21
23
|
if (schema) {
|
|
22
24
|
const log = config.logLevel === 'debug'
|
|
23
25
|
? (...args) => debugLog(config, ...args)
|
|
@@ -290,7 +290,15 @@ export function wrapError(error, context) {
|
|
|
290
290
|
export function parsePostgresError(error, sql) {
|
|
291
291
|
const message = error.message || 'Database error';
|
|
292
292
|
const code = error.code;
|
|
293
|
-
if (code
|
|
293
|
+
if (isNetworkErrorCode(code)) {
|
|
294
|
+
return new RelqConnectionError(message, {
|
|
295
|
+
cause: error,
|
|
296
|
+
code,
|
|
297
|
+
host: error.hostname || error.address,
|
|
298
|
+
port: error.port
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
if (code === '57P01' || code === '57P03' || code === '08006' || code === '08001' || code === '08004') {
|
|
294
302
|
return new RelqConnectionError(message, {
|
|
295
303
|
cause: error,
|
|
296
304
|
code,
|
|
@@ -309,3 +317,11 @@ export function parsePostgresError(error, sql) {
|
|
|
309
317
|
hint: error.hint
|
|
310
318
|
});
|
|
311
319
|
}
|
|
320
|
+
const NETWORK_ERROR_CODES = new Set([
|
|
321
|
+
'ECONNREFUSED', 'ECONNRESET', 'ENOTFOUND', 'ESERVFAIL',
|
|
322
|
+
'ETIMEDOUT', 'EPIPE', 'EAI_AGAIN', 'EHOSTUNREACH',
|
|
323
|
+
'CONNECTION_LOST', 'PROTOCOL_CONNECTION_LOST',
|
|
324
|
+
]);
|
|
325
|
+
function isNetworkErrorCode(code) {
|
|
326
|
+
return !!code && NETWORK_ERROR_CODES.has(code);
|
|
327
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -10023,6 +10023,7 @@ export declare abstract class RelqBase<TSchema = any> {
|
|
|
10023
10023
|
protected config: RelqConfig;
|
|
10024
10024
|
protected schema?: TSchema;
|
|
10025
10025
|
protected readonly emitter: EventEmitter<any>;
|
|
10026
|
+
private readonly _defaultErrorHandler;
|
|
10026
10027
|
protected columnMappings: ColumnMappings;
|
|
10027
10028
|
protected initialized: boolean;
|
|
10028
10029
|
protected initPromise?: Promise<void>;
|