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.
@@ -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
- const recoverableErrors = ['ETIMEDOUT', 'ECONNRESET', '57P02'];
261
- if (!recoverableErrors.includes(errorCode)) {
262
- throw err;
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 === 'ECONNREFUSED' || code === 'ENOTFOUND' || code === 'ETIMEDOUT') {
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
- const recoverableErrors = ['ETIMEDOUT', 'ECONNRESET', '57P02'];
224
- if (!recoverableErrors.includes(errorCode)) {
225
- throw err;
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 === 'ECONNREFUSED' || code === 'ENOTFOUND' || code === 'ETIMEDOUT') {
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>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "relq",
3
- "version": "1.0.112",
3
+ "version": "1.0.114",
4
4
  "description": "The Fully-Typed PostgreSQL ORM for TypeScript",
5
5
  "author": "Olajide Mathew O. <olajide.mathew@yuniq.solutions>",
6
6
  "license": "MIT",