@devilsdev/rag-pipeline-utils 2.1.12 → 2.2.1

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.
Files changed (113) hide show
  1. package/.ragrc.schema.json +218 -0
  2. package/README.md +178 -550
  3. package/bin/cli.js +8 -14
  4. package/contracts/reranker-contract.json +69 -0
  5. package/dist/index.d.ts +237 -0
  6. package/dist/index.mjs +22 -7
  7. package/package.json +18 -13
  8. package/src/ai/adaptive-retrieval.js +191 -0
  9. package/src/ai/federated-learning.js +214 -0
  10. package/src/ai/federation/federated-learning-coordinator.js +345 -0
  11. package/src/ai/federation.js +76 -0
  12. package/src/ai/index-fixed.js +509 -0
  13. package/src/ai/index.js +33 -0
  14. package/src/ai/model-trainer.js +217 -0
  15. package/src/ai/model-training.js +205 -0
  16. package/src/ai/module-template.js +37 -0
  17. package/src/ai/multimodal/multi-modal-processor.js +345 -0
  18. package/src/ai/multimodal-processing.js +268 -0
  19. package/src/ai/multimodal.js +71 -0
  20. package/src/ai/orchestrator.js +70 -0
  21. package/src/ai/retrieval/adaptive-retrieval-engine.js +398 -0
  22. package/src/ai/retrieval-engine.js +69 -0
  23. package/src/ai/training/model-training-orchestrator.js +228 -0
  24. package/src/ci/workflow-analyzer.js +71 -0
  25. package/src/cli/commands/ai-ml.js +967 -0
  26. package/src/cli/commands/docs.js +228 -0
  27. package/src/cli/commands/dx.js +538 -0
  28. package/src/cli/commands/plugin-hub.js +875 -0
  29. package/src/cli/doctor-command.js +639 -0
  30. package/src/cli/enhanced-cli-commands.js +945 -0
  31. package/src/cli/interactive-wizard.js +997 -0
  32. package/src/cli/plugin-marketplace-commands.js +869 -0
  33. package/src/cli.js +17 -0
  34. package/src/config/enhanced-ragrc-schema.js +632 -0
  35. package/src/config/load-config.js +54 -0
  36. package/src/config/load-plugin-config.js +69 -0
  37. package/src/config/normalize-config.js +105 -0
  38. package/src/config/schema-validator.js +315 -0
  39. package/src/config/validate-plugin-schema.js +35 -0
  40. package/src/config/validate-schema.js +113 -0
  41. package/src/contracts/plugin-contracts.js +33 -0
  42. package/src/core/create-pipeline.js +99 -0
  43. package/src/core/dependency-injection.js +242 -0
  44. package/src/core/observability/event-logger.js +241 -0
  45. package/src/core/observability/instrumented-pipeline.js +609 -0
  46. package/src/core/observability/metrics.js +497 -0
  47. package/src/core/observability/tracing.js +781 -0
  48. package/src/core/performance/benchmark.js +516 -0
  49. package/src/core/performance/parallel-processor.js +240 -0
  50. package/src/core/performance/streaming-safeguards.js +620 -0
  51. package/src/core/pipeline-factory.js +60 -0
  52. package/src/core/plugin-contracts.js +89 -0
  53. package/src/core/plugin-marketplace/plugin-metadata.js +485 -0
  54. package/src/core/plugin-marketplace/plugin-publisher.js +574 -0
  55. package/src/core/plugin-marketplace/plugin-registry-format.js +419 -0
  56. package/src/core/plugin-marketplace/version-resolver.js +61 -0
  57. package/src/core/plugin-registry.js +294 -0
  58. package/src/dag/dag-engine.js +954 -0
  59. package/src/dag/dag-node.js +76 -0
  60. package/src/dag/enhanced-dag-engine.js +822 -0
  61. package/src/dx/index.js +40 -0
  62. package/src/dx/integration-templates.js +665 -0
  63. package/src/dx/performance-profiler.js +968 -0
  64. package/src/dx/realtime-debugger.js +755 -0
  65. package/src/dx/visual-pipeline-builder.js +578 -0
  66. package/src/ecosystem/plugin-analytics-dashboard.js +778 -0
  67. package/src/ecosystem/plugin-certification.js +580 -0
  68. package/src/ecosystem/plugin-hub.js +821 -0
  69. package/src/enterprise/audit-logging.js +967 -0
  70. package/src/enterprise/data-governance.js +408 -0
  71. package/src/enterprise/multi-tenancy.js +496 -0
  72. package/src/enterprise/sso-integration.js +1030 -0
  73. package/src/evaluate/evaluator.js +52 -0
  74. package/src/evaluate/scoring.js +91 -0
  75. package/src/index.js +67 -0
  76. package/src/ingest.js +59 -0
  77. package/src/loader/csv-loader.js +38 -0
  78. package/src/loader/directory-loader.js +48 -0
  79. package/src/loader/html-loader.js +62 -0
  80. package/src/loader/markdown-loader.js +67 -0
  81. package/src/mocks/openai-embedder.js +119 -0
  82. package/src/mocks/openai-llm.js +77 -0
  83. package/src/mocks/pdf-loader.js +21 -0
  84. package/src/mocks/pinecone-retriever.js +106 -0
  85. package/src/observability/audit-logger.js +758 -0
  86. package/src/observability/dashboard-config.json +675 -0
  87. package/src/observability/event-logger.js +310 -0
  88. package/src/observability/metrics.js +691 -0
  89. package/src/observability/security-monitor.js +830 -0
  90. package/src/observability/slo-monitor.js +319 -0
  91. package/src/observability/telemetry.js +686 -0
  92. package/src/pipeline/pipeline.js +18 -0
  93. package/src/plugins/sample-embedder.js +53 -0
  94. package/src/plugins/sample-llm.js +30 -0
  95. package/src/plugins/sample-loader.js +24 -0
  96. package/src/plugins/sample-retriever.js +84 -0
  97. package/src/query.js +117 -0
  98. package/src/reranker/llm-reranker.js +111 -0
  99. package/src/scripts/script-utilities.js +36 -0
  100. package/src/security/plugin-sandbox.js +586 -0
  101. package/src/security/plugin-signature-verifier.js +414 -0
  102. package/src/security/validator.js +36 -0
  103. package/src/utils/ci/diagnostic-reporter.js +57 -0
  104. package/src/utils/logger.js +211 -0
  105. package/src/utils/performance-monitor.js +561 -0
  106. package/src/utils/plugin-scaffolder.js +447 -0
  107. package/src/utils/rate-limiter.js +244 -0
  108. package/src/utils/retry-policy.js +443 -0
  109. package/src/utils/retry.js +107 -0
  110. package/src/utils/safe-expression-evaluator.js +579 -0
  111. package/src/utils/secure-logging.js +338 -0
  112. package/src/utils/structured-logger.js +388 -0
  113. package/src/utils/validate-plugin-contract.js +42 -0
@@ -0,0 +1,443 @@
1
+ /**
2
+ * @fileoverview Enterprise Retry Policy System
3
+ * Provides exponential backoff, circuit breaker patterns, and retry budgets
4
+ *
5
+ * @author DevilsDev Team
6
+ * @since 2.1.8
7
+ */
8
+
9
+ const { logger } = require('./structured-logger.js');
10
+
11
+ /**
12
+ * Retry policy with exponential backoff and circuit breaker
13
+ */
14
+ class RetryPolicy {
15
+ constructor(options = {}) {
16
+ this.maxRetries = options.maxRetries || 3;
17
+ this.baseDelay = options.baseDelay || 1000;
18
+ this.maxDelay = options.maxDelay || 30000;
19
+ this.multiplier = options.multiplier || 2;
20
+ this.jitter = options.jitter || 0.1;
21
+ this.timeout = options.timeout || 60000;
22
+
23
+ // Circuit breaker configuration
24
+ this.circuitBreaker = {
25
+ enabled: options.circuitBreaker?.enabled !== false,
26
+ failureThreshold: options.circuitBreaker?.failureThreshold || 5,
27
+ resetTimeout: options.circuitBreaker?.resetTimeout || 60000,
28
+ state: 'CLOSED', // CLOSED, OPEN, HALF_OPEN
29
+ failures: 0,
30
+ lastFailureTime: null,
31
+ };
32
+
33
+ // Retry budget configuration
34
+ this.retryBudget = {
35
+ enabled: options.retryBudget?.enabled !== false,
36
+ maxRetriesPerMinute: options.retryBudget?.maxRetriesPerMinute || 60,
37
+ windowSize: options.retryBudget?.windowSize || 60000,
38
+ retries: [],
39
+ };
40
+
41
+ // Strategy for determining retryable errors
42
+ this.retryCondition =
43
+ options.retryCondition || this.defaultRetryCondition.bind(this);
44
+ this.onRetry = options.onRetry || this.defaultOnRetry.bind(this);
45
+ this.onFailure = options.onFailure || this.defaultOnFailure.bind(this);
46
+
47
+ // Sleep function (injectable for testing)
48
+ this.sleep =
49
+ options.sleep ||
50
+ ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
51
+ }
52
+
53
+ /**
54
+ * Default retry condition - retry on network/timeout errors
55
+ * @param {Error} error - Error to evaluate
56
+ * @returns {boolean} Whether error is retryable
57
+ */
58
+ defaultRetryCondition(error) {
59
+ // Retry on common transient errors
60
+ const retryableCodes = [
61
+ 'ECONNRESET',
62
+ 'ENOTFOUND',
63
+ 'ECONNREFUSED',
64
+ 'ETIMEDOUT',
65
+ ];
66
+ const retryableMessages = ['timeout', 'network', 'connection', 'temporary'];
67
+
68
+ if (error.code && retryableCodes.includes(error.code)) {
69
+ return true;
70
+ }
71
+
72
+ const message = error.message.toLowerCase();
73
+ return retryableMessages.some((keyword) => message.includes(keyword));
74
+ }
75
+
76
+ /**
77
+ * Default retry callback
78
+ * @param {Error} error - Error that caused retry
79
+ * @param {number} attempt - Current attempt number
80
+ * @param {number} delay - Delay before next attempt
81
+ */
82
+ defaultOnRetry(error, attempt, delay) {
83
+ logger.warn('Retrying operation', {
84
+ component: 'retry-policy',
85
+ attempt,
86
+ delay,
87
+ error: error.message,
88
+ errorCode: error.code,
89
+ });
90
+ }
91
+
92
+ /**
93
+ * Default failure callback
94
+ * @param {Error} error - Final error after all retries
95
+ * @param {number} attempts - Total attempts made
96
+ */
97
+ defaultOnFailure(error, attempts) {
98
+ logger.error('Operation failed after all retries', {
99
+ component: 'retry-policy',
100
+ attempts,
101
+ error: error.message,
102
+ errorCode: error.code,
103
+ });
104
+ }
105
+
106
+ /**
107
+ * Check circuit breaker state
108
+ * @returns {boolean} Whether circuit is open (requests should fail fast)
109
+ */
110
+ isCircuitOpen() {
111
+ if (!this.circuitBreaker.enabled) {
112
+ return false;
113
+ }
114
+
115
+ const { state, failures, failureThreshold, resetTimeout, lastFailureTime } =
116
+ this.circuitBreaker;
117
+
118
+ if (state === 'OPEN') {
119
+ // Check if we should transition to HALF_OPEN
120
+ if (Date.now() - lastFailureTime >= resetTimeout) {
121
+ this.circuitBreaker.state = 'HALF_OPEN';
122
+ logger.info('Circuit breaker transitioning to HALF_OPEN', {
123
+ component: 'circuit-breaker',
124
+ previousFailures: failures,
125
+ });
126
+ return false;
127
+ }
128
+ return true;
129
+ }
130
+
131
+ return failures >= failureThreshold;
132
+ }
133
+
134
+ /**
135
+ * Record circuit breaker success
136
+ */
137
+ recordCircuitSuccess() {
138
+ if (!this.circuitBreaker.enabled) return;
139
+
140
+ if (this.circuitBreaker.state === 'HALF_OPEN') {
141
+ this.circuitBreaker.state = 'CLOSED';
142
+ this.circuitBreaker.failures = 0;
143
+ logger.info('Circuit breaker closed after successful operation', {
144
+ component: 'circuit-breaker',
145
+ });
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Record circuit breaker failure
151
+ */
152
+ recordCircuitFailure() {
153
+ if (!this.circuitBreaker.enabled) return;
154
+
155
+ this.circuitBreaker.failures++;
156
+ this.circuitBreaker.lastFailureTime = Date.now();
157
+
158
+ if (this.circuitBreaker.failures >= this.circuitBreaker.failureThreshold) {
159
+ this.circuitBreaker.state = 'OPEN';
160
+ logger.warn('Circuit breaker opened due to excessive failures', {
161
+ component: 'circuit-breaker',
162
+ failures: this.circuitBreaker.failures,
163
+ threshold: this.circuitBreaker.failureThreshold,
164
+ });
165
+ }
166
+ }
167
+
168
+ /**
169
+ * Check retry budget availability
170
+ * @returns {boolean} Whether retry budget allows retry
171
+ */
172
+ checkRetryBudget() {
173
+ if (!this.retryBudget.enabled) {
174
+ return true;
175
+ }
176
+
177
+ const now = Date.now();
178
+ const windowStart = now - this.retryBudget.windowSize;
179
+
180
+ // Clean old retries outside window
181
+ this.retryBudget.retries = this.retryBudget.retries.filter(
182
+ (time) => time > windowStart,
183
+ );
184
+
185
+ // Check if we're under budget
186
+ const retriesInWindow = this.retryBudget.retries.length;
187
+ const budgetAvailable =
188
+ retriesInWindow < this.retryBudget.maxRetriesPerMinute;
189
+
190
+ if (!budgetAvailable) {
191
+ logger.warn('Retry budget exhausted', {
192
+ component: 'retry-budget',
193
+ retriesInWindow,
194
+ maxRetries: this.retryBudget.maxRetriesPerMinute,
195
+ windowSize: this.retryBudget.windowSize,
196
+ });
197
+ }
198
+
199
+ return budgetAvailable;
200
+ }
201
+
202
+ /**
203
+ * Record retry attempt in budget
204
+ */
205
+ recordRetryAttempt() {
206
+ if (this.retryBudget.enabled) {
207
+ this.retryBudget.retries.push(Date.now());
208
+ }
209
+ }
210
+
211
+ /**
212
+ * Calculate delay for retry attempt with jitter
213
+ * @param {number} attempt - Current attempt number (0-based)
214
+ * @returns {number} Delay in milliseconds
215
+ */
216
+ calculateDelay(attempt) {
217
+ const exponentialDelay =
218
+ this.baseDelay * Math.pow(this.multiplier, attempt);
219
+ const maxDelay = Math.min(exponentialDelay, this.maxDelay);
220
+
221
+ // Add jitter to prevent thundering herd
222
+ const jitterRange = maxDelay * this.jitter;
223
+ const jitter = (Math.random() * 2 - 1) * jitterRange;
224
+
225
+ return Math.max(0, Math.floor(maxDelay + jitter));
226
+ }
227
+
228
+ /**
229
+ * Execute operation with retry logic
230
+ * @param {Function} operation - Operation to execute
231
+ * @param {object} context - Additional context for logging
232
+ * @returns {Promise} Operation result
233
+ */
234
+ async execute(operation, context = {}) {
235
+ // Check circuit breaker
236
+ if (this.isCircuitOpen()) {
237
+ const error = new Error('Circuit breaker is open - failing fast');
238
+ error.code = 'CIRCUIT_OPEN';
239
+ throw error;
240
+ }
241
+
242
+ let lastError;
243
+ let attempt = 0;
244
+
245
+ const startTime = Date.now();
246
+ const operationTimeout = setTimeout(() => {
247
+ const timeoutError = new Error(
248
+ `Operation timeout after ${this.timeout}ms`,
249
+ );
250
+ timeoutError.code = 'OPERATION_TIMEOUT';
251
+ throw timeoutError;
252
+ }, this.timeout);
253
+
254
+ try {
255
+ while (attempt <= this.maxRetries) {
256
+ try {
257
+ const result = await operation();
258
+
259
+ // Record success for circuit breaker
260
+ this.recordCircuitSuccess();
261
+
262
+ logger.debug('Operation succeeded', {
263
+ component: 'retry-policy',
264
+ attempt,
265
+ duration: Date.now() - startTime,
266
+ ...context,
267
+ });
268
+
269
+ return result;
270
+ } catch (error) {
271
+ lastError = error;
272
+ attempt++;
273
+
274
+ // Check if error is retryable
275
+ if (!this.retryCondition(error)) {
276
+ logger.debug('Error is not retryable, failing immediately', {
277
+ component: 'retry-policy',
278
+ error: error.message,
279
+ errorCode: error.code,
280
+ ...context,
281
+ });
282
+ break;
283
+ }
284
+
285
+ // Check if we have retries left
286
+ if (attempt > this.maxRetries) {
287
+ break;
288
+ }
289
+
290
+ // Check retry budget
291
+ if (!this.checkRetryBudget()) {
292
+ const budgetError = new Error('Retry budget exhausted');
293
+ budgetError.code = 'RETRY_BUDGET_EXHAUSTED';
294
+ throw budgetError;
295
+ }
296
+
297
+ // Record retry attempt
298
+ this.recordRetryAttempt();
299
+
300
+ // Calculate delay and wait
301
+ const delay = this.calculateDelay(attempt - 1);
302
+ this.onRetry(error, attempt, delay);
303
+
304
+ await this.sleep(delay);
305
+ }
306
+ }
307
+
308
+ // Record failure for circuit breaker
309
+ this.recordCircuitFailure();
310
+
311
+ // Call failure callback
312
+ this.onFailure(lastError, attempt);
313
+
314
+ // Enhance error with retry information
315
+ const enhancedError = new Error(
316
+ `Operation failed after ${attempt} attempts: ${lastError.message}`,
317
+ );
318
+ enhancedError.originalError = lastError;
319
+ enhancedError.attempts = attempt;
320
+ enhancedError.code = lastError.code || 'RETRY_EXHAUSTED';
321
+
322
+ throw enhancedError;
323
+ } finally {
324
+ clearTimeout(operationTimeout);
325
+ }
326
+ }
327
+
328
+ /**
329
+ * Get retry policy metrics
330
+ * @returns {object} Policy metrics
331
+ */
332
+ getMetrics() {
333
+ return {
334
+ circuitBreaker: {
335
+ state: this.circuitBreaker.state,
336
+ failures: this.circuitBreaker.failures,
337
+ lastFailureTime: this.circuitBreaker.lastFailureTime,
338
+ },
339
+ retryBudget: {
340
+ currentRetries: this.retryBudget.retries.length,
341
+ maxRetries: this.retryBudget.maxRetriesPerMinute,
342
+ windowSize: this.retryBudget.windowSize,
343
+ },
344
+ policy: {
345
+ maxRetries: this.maxRetries,
346
+ baseDelay: this.baseDelay,
347
+ maxDelay: this.maxDelay,
348
+ multiplier: this.multiplier,
349
+ jitter: this.jitter,
350
+ },
351
+ };
352
+ }
353
+
354
+ /**
355
+ * Reset retry policy state
356
+ */
357
+ reset() {
358
+ this.circuitBreaker.state = 'CLOSED';
359
+ this.circuitBreaker.failures = 0;
360
+ this.circuitBreaker.lastFailureTime = null;
361
+ this.retryBudget.retries = [];
362
+ }
363
+ }
364
+
365
+ /**
366
+ * Create retry policy instance
367
+ * @param {object} options - Policy configuration
368
+ * @returns {RetryPolicy} Retry policy instance
369
+ */
370
+ function createRetryPolicy(options = {}) {
371
+ return new RetryPolicy(options);
372
+ }
373
+
374
+ /**
375
+ * Default retry policy for most operations
376
+ */
377
+ const defaultPolicy = createRetryPolicy({
378
+ maxRetries: 3,
379
+ baseDelay: 1000,
380
+ maxDelay: 10000,
381
+ multiplier: 2,
382
+ jitter: 0.1,
383
+ circuitBreaker: {
384
+ enabled: true,
385
+ failureThreshold: 5,
386
+ resetTimeout: 30000,
387
+ },
388
+ retryBudget: {
389
+ enabled: true,
390
+ maxRetriesPerMinute: 30,
391
+ },
392
+ });
393
+
394
+ /**
395
+ * Aggressive retry policy for critical operations
396
+ */
397
+ const aggressivePolicy = createRetryPolicy({
398
+ maxRetries: 5,
399
+ baseDelay: 500,
400
+ maxDelay: 30000,
401
+ multiplier: 2.5,
402
+ jitter: 0.2,
403
+ circuitBreaker: {
404
+ enabled: true,
405
+ failureThreshold: 10,
406
+ resetTimeout: 60000,
407
+ },
408
+ retryBudget: {
409
+ enabled: true,
410
+ maxRetriesPerMinute: 60,
411
+ },
412
+ });
413
+
414
+ /**
415
+ * Conservative retry policy for non-critical operations
416
+ */
417
+ const conservativePolicy = createRetryPolicy({
418
+ maxRetries: 2,
419
+ baseDelay: 2000,
420
+ maxDelay: 15000,
421
+ multiplier: 1.5,
422
+ jitter: 0.05,
423
+ circuitBreaker: {
424
+ enabled: true,
425
+ failureThreshold: 3,
426
+ resetTimeout: 45000,
427
+ },
428
+ retryBudget: {
429
+ enabled: true,
430
+ maxRetriesPerMinute: 15,
431
+ },
432
+ });
433
+
434
+ module.exports = {
435
+ RetryPolicy,
436
+ createRetryPolicy,
437
+ defaultPolicy,
438
+ aggressivePolicy,
439
+ conservativePolicy,
440
+ };
441
+
442
+ // CommonJS/ESM interop
443
+ module.exports.default = module.exports;
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Version: 0.2.0
3
+ * Path: /src/utils/retry.js
4
+ * Description: Generic retry wrapper with exponential backoff and injectable sleep for testing
5
+ * Author: Ali Kahwaji
6
+ */
7
+
8
+ const { logger } = require('./logger.js');
9
+
10
+ // Keep a default sleeper that tests can override via options
11
+ const defaultSleep = (ms) =>
12
+ new Promise((res) => {
13
+ const t = setTimeout(res, ms);
14
+ if (typeof t.unref === 'function') t.unref();
15
+ });
16
+
17
+ /**
18
+ * Retry a function with exponential backoff.
19
+ *
20
+ * @param {Function} fn - async or sync function to call; receives (attempt)
21
+ * @param {Object} [opts]
22
+ * @param {number} [opts.retries=3] - number of *retries* (total attempts = retries + 1)
23
+ * @param {number} [opts.baseDelay=100] - initial delay in ms
24
+ * @param {number} [opts.multiplier=2] - backoff multiplier
25
+ * @param {boolean} [opts.jitter=false] - add +/-50% jitter if true
26
+ * @param {Function} [opts.sleep=defaultSleep] - injected sleeper for tests
27
+ * @param {Function} [opts.onDelay] - callback (delayMs, attempt, error) before each sleep
28
+ * @returns {Promise<*>}
29
+ */
30
+ async function retry(fn, opts = {}) {
31
+ const {
32
+ retries = 3,
33
+ baseDelay = 100,
34
+ multiplier = 2,
35
+ jitter = false,
36
+ sleep: injectedSleep,
37
+ onDelay,
38
+ } = opts;
39
+
40
+ const testNoopSleep = () => Promise.resolve();
41
+ const sleep =
42
+ injectedSleep ??
43
+ (process.env.NODE_ENV === 'test'
44
+ ? testNoopSleep
45
+ : (ms) =>
46
+ new Promise((r) => {
47
+ const t = setTimeout(r, ms);
48
+ if (typeof t.unref === 'function') t.unref();
49
+ }));
50
+
51
+ let attempt = 0; // 0-based attempt index
52
+
53
+ // eslint-disable-next-line no-constant-condition
54
+ while (true) {
55
+ try {
56
+ return await fn(attempt);
57
+ } catch (err) {
58
+ if (attempt >= retries) throw err;
59
+
60
+ // exponential backoff: base * multiplier^attempt
61
+ const backoff = baseDelay * Math.pow(multiplier, attempt);
62
+ const delay = jitter
63
+ ? Math.round(backoff * (0.5 + Math.random())) // 50%..150%
64
+ : backoff;
65
+
66
+ if (typeof onDelay === 'function') {
67
+ onDelay(delay, attempt, err);
68
+ }
69
+
70
+ // IMPORTANT: call the *injected* sleeper so tests can spy on it
71
+ await sleep(delay);
72
+
73
+ attempt += 1;
74
+ }
75
+ }
76
+ }
77
+
78
+ // Legacy wrapper for backward compatibility
79
+ async function withRetry(
80
+ _fn,
81
+ { retries = 3, initialDelay = 300, label = 'operation' } = {},
82
+ ) {
83
+ return retry(_fn, {
84
+ retries,
85
+ baseDelay: initialDelay,
86
+ onDelay: (delay, attempt, err) => {
87
+ if (attempt > 0) {
88
+ logger.warn(
89
+ { attempt: attempt + 1, label },
90
+ `Retrying ${label} (attempt ${attempt + 1})`,
91
+ );
92
+ }
93
+ if (attempt >= retries - 1) {
94
+ logger.error(
95
+ { label, error: err.message },
96
+ `Failed ${label} after ${retries} retries`,
97
+ );
98
+ }
99
+ },
100
+ });
101
+ }
102
+
103
+ module.exports = {
104
+ retry,
105
+ withRetry,
106
+ defaultSleep,
107
+ };