relq 1.0.110 → 1.0.112

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.
@@ -93,9 +93,32 @@ class RelqBase {
93
93
  }
94
94
  await this.ensureInitialized();
95
95
  const startTime = performance.now();
96
- const result = await this._query(sql);
97
- const duration = performance.now() - startTime;
98
- return { result, duration };
96
+ const retryConfig = this.config.retry;
97
+ if (!retryConfig) {
98
+ const result = await this._query(sql);
99
+ return { result, duration: performance.now() - startTime };
100
+ }
101
+ const opts = typeof retryConfig === 'object'
102
+ ? { maxRetries: retryConfig.maxRetries ?? 3, initialDelayMs: retryConfig.initialDelayMs ?? 250, maxDelayMs: retryConfig.maxDelayMs ?? 5000 }
103
+ : { maxRetries: 3, initialDelayMs: 250, maxDelayMs: 5000 };
104
+ let lastError;
105
+ for (let attempt = 0; attempt <= opts.maxRetries; attempt++) {
106
+ try {
107
+ const result = await this._query(sql);
108
+ return { result, duration: performance.now() - startTime };
109
+ }
110
+ catch (error) {
111
+ lastError = error;
112
+ if (attempt < opts.maxRetries && isTransientError(error)) {
113
+ const delay = Math.min(opts.initialDelayMs * 2 ** attempt, opts.maxDelayMs);
114
+ (0, helpers_1.debugLog)(this.config, `Transient error (attempt ${attempt + 1}/${opts.maxRetries}), retrying in ${delay}ms: ${error.code || error.message}`);
115
+ await sleep(delay);
116
+ continue;
117
+ }
118
+ throw error;
119
+ }
120
+ }
121
+ throw lastError;
99
122
  }
100
123
  buildMetadata(result, duration) {
101
124
  return {
@@ -220,3 +243,29 @@ class RelqBase {
220
243
  }
221
244
  }
222
245
  exports.RelqBase = RelqBase;
246
+ const TRANSIENT_NETWORK_CODES = new Set([
247
+ 'ECONNRESET', 'ECONNREFUSED', 'ENOTFOUND', 'ESERVFAIL',
248
+ 'ETIMEDOUT', 'EPIPE', 'EAI_AGAIN', 'EHOSTUNREACH',
249
+ 'CONNECTION_LOST', 'PROTOCOL_CONNECTION_LOST',
250
+ ]);
251
+ const TRANSIENT_PG_CODES = new Set([
252
+ '40P01',
253
+ '40001',
254
+ '08006',
255
+ '08001',
256
+ '08004',
257
+ '57P01',
258
+ '57P03',
259
+ ]);
260
+ function isTransientError(error) {
261
+ if (TRANSIENT_NETWORK_CODES.has(error.code))
262
+ return true;
263
+ if (error.cause && TRANSIENT_NETWORK_CODES.has(error.cause.code))
264
+ return true;
265
+ if (TRANSIENT_PG_CODES.has(error.code))
266
+ return true;
267
+ return false;
268
+ }
269
+ function sleep(ms) {
270
+ return new Promise(resolve => setTimeout(resolve, ms));
271
+ }
@@ -90,9 +90,32 @@ export class RelqBase {
90
90
  }
91
91
  await this.ensureInitialized();
92
92
  const startTime = performance.now();
93
- const result = await this._query(sql);
94
- const duration = performance.now() - startTime;
95
- return { result, duration };
93
+ const retryConfig = this.config.retry;
94
+ if (!retryConfig) {
95
+ const result = await this._query(sql);
96
+ return { result, duration: performance.now() - startTime };
97
+ }
98
+ const opts = typeof retryConfig === 'object'
99
+ ? { maxRetries: retryConfig.maxRetries ?? 3, initialDelayMs: retryConfig.initialDelayMs ?? 250, maxDelayMs: retryConfig.maxDelayMs ?? 5000 }
100
+ : { maxRetries: 3, initialDelayMs: 250, maxDelayMs: 5000 };
101
+ let lastError;
102
+ for (let attempt = 0; attempt <= opts.maxRetries; attempt++) {
103
+ try {
104
+ const result = await this._query(sql);
105
+ return { result, duration: performance.now() - startTime };
106
+ }
107
+ catch (error) {
108
+ lastError = error;
109
+ if (attempt < opts.maxRetries && isTransientError(error)) {
110
+ const delay = Math.min(opts.initialDelayMs * 2 ** attempt, opts.maxDelayMs);
111
+ debugLog(this.config, `Transient error (attempt ${attempt + 1}/${opts.maxRetries}), retrying in ${delay}ms: ${error.code || error.message}`);
112
+ await sleep(delay);
113
+ continue;
114
+ }
115
+ throw error;
116
+ }
117
+ }
118
+ throw lastError;
96
119
  }
97
120
  buildMetadata(result, duration) {
98
121
  return {
@@ -216,3 +239,29 @@ export class RelqBase {
216
239
  return this._isClosed;
217
240
  }
218
241
  }
242
+ const TRANSIENT_NETWORK_CODES = new Set([
243
+ 'ECONNRESET', 'ECONNREFUSED', 'ENOTFOUND', 'ESERVFAIL',
244
+ 'ETIMEDOUT', 'EPIPE', 'EAI_AGAIN', 'EHOSTUNREACH',
245
+ 'CONNECTION_LOST', 'PROTOCOL_CONNECTION_LOST',
246
+ ]);
247
+ const TRANSIENT_PG_CODES = new Set([
248
+ '40P01',
249
+ '40001',
250
+ '08006',
251
+ '08001',
252
+ '08004',
253
+ '57P01',
254
+ '57P03',
255
+ ]);
256
+ function isTransientError(error) {
257
+ if (TRANSIENT_NETWORK_CODES.has(error.code))
258
+ return true;
259
+ if (error.cause && TRANSIENT_NETWORK_CODES.has(error.cause.code))
260
+ return true;
261
+ if (TRANSIENT_PG_CODES.has(error.code))
262
+ return true;
263
+ return false;
264
+ }
265
+ function sleep(ms) {
266
+ return new Promise(resolve => setTimeout(resolve, ms));
267
+ }
package/dist/index.d.ts CHANGED
@@ -6760,6 +6760,22 @@ export interface RelqBaseConfig {
6760
6760
  commitLimit?: number;
6761
6761
  /** Schema relations for type-safe joins */
6762
6762
  relations?: SchemaRelations | SchemaRelationsArray;
6763
+ /**
6764
+ * Retry transient query failures (DNS, connection reset, timeout).
6765
+ * Disabled by default. Set `true` for 3 retries, or pass options.
6766
+ */
6767
+ retry?: boolean | RetryOptions;
6768
+ }
6769
+ /**
6770
+ * Configuration for automatic query retry on transient errors.
6771
+ */
6772
+ export interface RetryOptions {
6773
+ /** Maximum number of retry attempts @default 3 */
6774
+ maxRetries?: number;
6775
+ /** Initial delay in ms before first retry (doubles each attempt) @default 250 */
6776
+ initialDelayMs?: number;
6777
+ /** Maximum delay in ms between retries @default 5000 */
6778
+ maxDelayMs?: number;
6763
6779
  }
6764
6780
  export interface PgConnectionFields {
6765
6781
  /** Database host @default 'localhost' */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "relq",
3
- "version": "1.0.110",
3
+ "version": "1.0.112",
4
4
  "description": "The Fully-Typed PostgreSQL ORM for TypeScript",
5
5
  "author": "Olajide Mathew O. <olajide.mathew@yuniq.solutions>",
6
6
  "license": "MIT",