relq 1.0.109 → 1.0.111

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.
@@ -282,6 +282,9 @@ async function runPush(config, projectRoot, opts = {}) {
282
282
  const removed = e.changes.removed.length > 0 ? `-${e.changes.removed.join(',')}` : '';
283
283
  console.log(` ${colors_1.colors.yellow('~')} ${colors_1.colors.bold(e.name)} ${colors_1.colors.muted(`(enum: ${[added, removed].filter(Boolean).join(' ')})`)}`);
284
284
  }
285
+ for (const idx of comparison.modified.indexes) {
286
+ console.log(` ${colors_1.colors.yellow('~')} index ${colors_1.colors.bold(idx.newIndex.name)} ${colors_1.colors.muted(`(${idx.changes.join(', ')})`)}`);
287
+ }
285
288
  for (const d of comparison.added.domains) {
286
289
  console.log(` ${colors_1.colors.green('+')} ${colors_1.colors.bold(d.name)} ${colors_1.colors.muted('(new domain)')}`);
287
290
  }
@@ -238,6 +238,14 @@ function generateMigrationFromComparison(comparison, options = {}) {
238
238
  down.unshift(`DROP INDEX IF EXISTS "${index.name}";`);
239
239
  }
240
240
  }
241
+ for (const { table, oldIndex, newIndex } of comparison.modified.indexes) {
242
+ up.push(`DROP INDEX IF EXISTS "${oldIndex.name}";`);
243
+ up.push(generateCreateIndexFromParsed(table, newIndex));
244
+ if (includeDown) {
245
+ down.unshift(`DROP INDEX IF EXISTS "${newIndex.name}";`);
246
+ down.unshift(generateCreateIndexFromParsed(table, oldIndex));
247
+ }
248
+ }
241
249
  for (const view of comparison.added.views) {
242
250
  const materialized = view.isMaterialized ? 'MATERIALIZED ' : '';
243
251
  up.push(`CREATE ${materialized}VIEW "${view.name}" AS ${view.definition};`);
@@ -883,6 +883,7 @@ function compareSchemas(oldSchema, newSchema) {
883
883
  modified: {
884
884
  tables: [],
885
885
  columns: [],
886
+ indexes: [],
886
887
  enums: [],
887
888
  },
888
889
  hasChanges: false,
@@ -938,6 +939,7 @@ function compareSchemas(oldSchema, newSchema) {
938
939
  result.renamed.functions.length > 0 ||
939
940
  result.modified.tables.length > 0 ||
940
941
  result.modified.columns.length > 0 ||
942
+ result.modified.indexes.length > 0 ||
941
943
  result.modified.enums.length > 0;
942
944
  return result;
943
945
  }
@@ -1124,21 +1126,35 @@ function compareTableIndexes(oldTable, newTable, tableName, result) {
1124
1126
  const processedNew = new Set();
1125
1127
  for (const [trackingId, oldIdx] of oldByTrackingId) {
1126
1128
  const newIdx = newByTrackingId.get(trackingId);
1127
- if (newIdx && newIdx.name !== oldIdx.name) {
1129
+ if (!newIdx)
1130
+ continue;
1131
+ if (newIdx.name !== oldIdx.name) {
1128
1132
  result.renamed.indexes.push({
1129
1133
  table: tableName,
1130
1134
  from: oldIdx.name,
1131
1135
  to: newIdx.name,
1132
1136
  trackingId,
1133
1137
  });
1134
- processedOld.add(oldIdx.name);
1135
- processedNew.add(newIdx.name);
1136
1138
  }
1139
+ const changes = compareIndexProperties(oldIdx, newIdx);
1140
+ if (changes.length > 0) {
1141
+ result.modified.indexes.push({ table: tableName, oldIndex: oldIdx, newIndex: newIdx, changes });
1142
+ }
1143
+ processedOld.add(oldIdx.name);
1144
+ processedNew.add(newIdx.name);
1137
1145
  }
1138
1146
  for (const [name, newIdx] of newByName) {
1139
1147
  if (processedNew.has(name))
1140
1148
  continue;
1141
- if (!oldByName.has(name)) {
1149
+ const oldIdx = oldByName.get(name);
1150
+ if (oldIdx) {
1151
+ const changes = compareIndexProperties(oldIdx, newIdx);
1152
+ if (changes.length > 0) {
1153
+ result.modified.indexes.push({ table: tableName, oldIndex: oldIdx, newIndex: newIdx, changes });
1154
+ }
1155
+ processedOld.add(name);
1156
+ }
1157
+ else {
1142
1158
  result.added.indexes.push({ table: tableName, index: newIdx });
1143
1159
  }
1144
1160
  }
@@ -1150,6 +1166,25 @@ function compareTableIndexes(oldTable, newTable, tableName, result) {
1150
1166
  }
1151
1167
  }
1152
1168
  }
1169
+ function compareIndexProperties(oldIdx, newIdx) {
1170
+ const changes = [];
1171
+ if (oldIdx.isUnique !== newIdx.isUnique) {
1172
+ changes.push(`unique: ${oldIdx.isUnique} → ${newIdx.isUnique}`);
1173
+ }
1174
+ if ((oldIdx.method || 'btree') !== (newIdx.method || 'btree')) {
1175
+ changes.push(`method: ${oldIdx.method || 'btree'} → ${newIdx.method || 'btree'}`);
1176
+ }
1177
+ if (oldIdx.columns.join(',') !== newIdx.columns.join(',')) {
1178
+ changes.push(`columns: ${oldIdx.columns.join(',')} → ${newIdx.columns.join(',')}`);
1179
+ }
1180
+ if ((oldIdx.whereClause || '') !== (newIdx.whereClause || '')) {
1181
+ changes.push(`where: changed`);
1182
+ }
1183
+ if ((oldIdx.includeColumns || []).join(',') !== (newIdx.includeColumns || []).join(',')) {
1184
+ changes.push(`include: changed`);
1185
+ }
1186
+ return changes;
1187
+ }
1153
1188
  function compareEnums(oldEnums, newEnums, result) {
1154
1189
  const oldByName = new Map(oldEnums.map(e => [e.name, e]));
1155
1190
  const newByName = new Map(newEnums.map(e => [e.name, e]));
@@ -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,18 @@ class RelqBase {
220
243
  }
221
244
  }
222
245
  exports.RelqBase = RelqBase;
246
+ const TRANSIENT_CODES = new Set([
247
+ 'ECONNRESET', 'ECONNREFUSED', 'ENOTFOUND', 'ESERVFAIL',
248
+ 'ETIMEDOUT', 'EPIPE', 'EAI_AGAIN', 'EHOSTUNREACH',
249
+ 'CONNECTION_LOST', 'PROTOCOL_CONNECTION_LOST',
250
+ ]);
251
+ function isTransientError(error) {
252
+ if (TRANSIENT_CODES.has(error.code))
253
+ return true;
254
+ if (error.cause && TRANSIENT_CODES.has(error.cause.code))
255
+ return true;
256
+ return false;
257
+ }
258
+ function sleep(ms) {
259
+ return new Promise(resolve => setTimeout(resolve, ms));
260
+ }
@@ -246,6 +246,9 @@ export async function runPush(config, projectRoot, opts = {}) {
246
246
  const removed = e.changes.removed.length > 0 ? `-${e.changes.removed.join(',')}` : '';
247
247
  console.log(` ${colors.yellow('~')} ${colors.bold(e.name)} ${colors.muted(`(enum: ${[added, removed].filter(Boolean).join(' ')})`)}`);
248
248
  }
249
+ for (const idx of comparison.modified.indexes) {
250
+ console.log(` ${colors.yellow('~')} index ${colors.bold(idx.newIndex.name)} ${colors.muted(`(${idx.changes.join(', ')})`)}`);
251
+ }
249
252
  for (const d of comparison.added.domains) {
250
253
  console.log(` ${colors.green('+')} ${colors.bold(d.name)} ${colors.muted('(new domain)')}`);
251
254
  }
@@ -196,6 +196,14 @@ export function generateMigrationFromComparison(comparison, options = {}) {
196
196
  down.unshift(`DROP INDEX IF EXISTS "${index.name}";`);
197
197
  }
198
198
  }
199
+ for (const { table, oldIndex, newIndex } of comparison.modified.indexes) {
200
+ up.push(`DROP INDEX IF EXISTS "${oldIndex.name}";`);
201
+ up.push(generateCreateIndexFromParsed(table, newIndex));
202
+ if (includeDown) {
203
+ down.unshift(`DROP INDEX IF EXISTS "${newIndex.name}";`);
204
+ down.unshift(generateCreateIndexFromParsed(table, oldIndex));
205
+ }
206
+ }
199
207
  for (const view of comparison.added.views) {
200
208
  const materialized = view.isMaterialized ? 'MATERIALIZED ' : '';
201
209
  up.push(`CREATE ${materialized}VIEW "${view.name}" AS ${view.definition};`);
@@ -872,6 +872,7 @@ export function compareSchemas(oldSchema, newSchema) {
872
872
  modified: {
873
873
  tables: [],
874
874
  columns: [],
875
+ indexes: [],
875
876
  enums: [],
876
877
  },
877
878
  hasChanges: false,
@@ -927,6 +928,7 @@ export function compareSchemas(oldSchema, newSchema) {
927
928
  result.renamed.functions.length > 0 ||
928
929
  result.modified.tables.length > 0 ||
929
930
  result.modified.columns.length > 0 ||
931
+ result.modified.indexes.length > 0 ||
930
932
  result.modified.enums.length > 0;
931
933
  return result;
932
934
  }
@@ -1113,21 +1115,35 @@ function compareTableIndexes(oldTable, newTable, tableName, result) {
1113
1115
  const processedNew = new Set();
1114
1116
  for (const [trackingId, oldIdx] of oldByTrackingId) {
1115
1117
  const newIdx = newByTrackingId.get(trackingId);
1116
- if (newIdx && newIdx.name !== oldIdx.name) {
1118
+ if (!newIdx)
1119
+ continue;
1120
+ if (newIdx.name !== oldIdx.name) {
1117
1121
  result.renamed.indexes.push({
1118
1122
  table: tableName,
1119
1123
  from: oldIdx.name,
1120
1124
  to: newIdx.name,
1121
1125
  trackingId,
1122
1126
  });
1123
- processedOld.add(oldIdx.name);
1124
- processedNew.add(newIdx.name);
1125
1127
  }
1128
+ const changes = compareIndexProperties(oldIdx, newIdx);
1129
+ if (changes.length > 0) {
1130
+ result.modified.indexes.push({ table: tableName, oldIndex: oldIdx, newIndex: newIdx, changes });
1131
+ }
1132
+ processedOld.add(oldIdx.name);
1133
+ processedNew.add(newIdx.name);
1126
1134
  }
1127
1135
  for (const [name, newIdx] of newByName) {
1128
1136
  if (processedNew.has(name))
1129
1137
  continue;
1130
- if (!oldByName.has(name)) {
1138
+ const oldIdx = oldByName.get(name);
1139
+ if (oldIdx) {
1140
+ const changes = compareIndexProperties(oldIdx, newIdx);
1141
+ if (changes.length > 0) {
1142
+ result.modified.indexes.push({ table: tableName, oldIndex: oldIdx, newIndex: newIdx, changes });
1143
+ }
1144
+ processedOld.add(name);
1145
+ }
1146
+ else {
1131
1147
  result.added.indexes.push({ table: tableName, index: newIdx });
1132
1148
  }
1133
1149
  }
@@ -1139,6 +1155,25 @@ function compareTableIndexes(oldTable, newTable, tableName, result) {
1139
1155
  }
1140
1156
  }
1141
1157
  }
1158
+ function compareIndexProperties(oldIdx, newIdx) {
1159
+ const changes = [];
1160
+ if (oldIdx.isUnique !== newIdx.isUnique) {
1161
+ changes.push(`unique: ${oldIdx.isUnique} → ${newIdx.isUnique}`);
1162
+ }
1163
+ if ((oldIdx.method || 'btree') !== (newIdx.method || 'btree')) {
1164
+ changes.push(`method: ${oldIdx.method || 'btree'} → ${newIdx.method || 'btree'}`);
1165
+ }
1166
+ if (oldIdx.columns.join(',') !== newIdx.columns.join(',')) {
1167
+ changes.push(`columns: ${oldIdx.columns.join(',')} → ${newIdx.columns.join(',')}`);
1168
+ }
1169
+ if ((oldIdx.whereClause || '') !== (newIdx.whereClause || '')) {
1170
+ changes.push(`where: changed`);
1171
+ }
1172
+ if ((oldIdx.includeColumns || []).join(',') !== (newIdx.includeColumns || []).join(',')) {
1173
+ changes.push(`include: changed`);
1174
+ }
1175
+ return changes;
1176
+ }
1142
1177
  function compareEnums(oldEnums, newEnums, result) {
1143
1178
  const oldByName = new Map(oldEnums.map(e => [e.name, e]));
1144
1179
  const newByName = new Map(newEnums.map(e => [e.name, e]));
@@ -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,18 @@ export class RelqBase {
216
239
  return this._isClosed;
217
240
  }
218
241
  }
242
+ const TRANSIENT_CODES = new Set([
243
+ 'ECONNRESET', 'ECONNREFUSED', 'ENOTFOUND', 'ESERVFAIL',
244
+ 'ETIMEDOUT', 'EPIPE', 'EAI_AGAIN', 'EHOSTUNREACH',
245
+ 'CONNECTION_LOST', 'PROTOCOL_CONNECTION_LOST',
246
+ ]);
247
+ function isTransientError(error) {
248
+ if (TRANSIENT_CODES.has(error.code))
249
+ return true;
250
+ if (error.cause && TRANSIENT_CODES.has(error.cause.code))
251
+ return true;
252
+ return false;
253
+ }
254
+ function sleep(ms) {
255
+ return new Promise(resolve => setTimeout(resolve, ms));
256
+ }
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.109",
3
+ "version": "1.0.111",
4
4
  "description": "The Fully-Typed PostgreSQL ORM for TypeScript",
5
5
  "author": "Olajide Mathew O. <olajide.mathew@yuniq.solutions>",
6
6
  "license": "MIT",