@upyo/retry 0.5.0-dev.0 → 0.5.0-dev.193
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/index.cjs +95 -25
- package/dist/index.d.cts +19 -6
- package/dist/index.d.ts +19 -6
- package/dist/index.js +96 -26
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -74,7 +74,7 @@ async function defaultWait(context, signal) {
|
|
|
74
74
|
const abort = () => {
|
|
75
75
|
clearTimeout(timeout);
|
|
76
76
|
cleanup();
|
|
77
|
-
reject(createAbortError());
|
|
77
|
+
reject(signal?.reason ?? createAbortError());
|
|
78
78
|
};
|
|
79
79
|
if (signal?.aborted) {
|
|
80
80
|
abort();
|
|
@@ -153,7 +153,7 @@ var RetryTransport = class {
|
|
|
153
153
|
attempt,
|
|
154
154
|
nextAttempt: attempt + 1,
|
|
155
155
|
maxAttempts: this.config.maxAttempts,
|
|
156
|
-
delayMilliseconds: this.calculateDelay(attempt),
|
|
156
|
+
delayMilliseconds: this.calculateDelay(attempt, toReceiptError(error)),
|
|
157
157
|
error,
|
|
158
158
|
reason: "retry"
|
|
159
159
|
}, options?.signal);
|
|
@@ -178,17 +178,36 @@ var RetryTransport = class {
|
|
|
178
178
|
let inputDone = false;
|
|
179
179
|
let nextLaunchIndex = 0;
|
|
180
180
|
let nextYieldIndex = 0;
|
|
181
|
+
let launchPromise;
|
|
182
|
+
let launchError;
|
|
183
|
+
let hasLaunchError = false;
|
|
184
|
+
let pullingInput = false;
|
|
185
|
+
let closed = false;
|
|
186
|
+
const controller = new AbortController();
|
|
187
|
+
const combinedSignal = (0, __upyo_core.combineSignals)(controller.signal, options?.signal);
|
|
188
|
+
const sendOptions = {
|
|
189
|
+
...options,
|
|
190
|
+
signal: combinedSignal.signal
|
|
191
|
+
};
|
|
181
192
|
const launchNext = async () => {
|
|
182
193
|
if (inputDone) return;
|
|
183
|
-
|
|
184
|
-
|
|
194
|
+
combinedSignal.signal.throwIfAborted();
|
|
195
|
+
pullingInput = true;
|
|
196
|
+
let next;
|
|
197
|
+
try {
|
|
198
|
+
next = await iterator.next();
|
|
199
|
+
} finally {
|
|
200
|
+
pullingInput = false;
|
|
201
|
+
}
|
|
202
|
+
if (closed) return;
|
|
185
203
|
if (next.done) {
|
|
186
204
|
inputDone = true;
|
|
187
205
|
return;
|
|
188
206
|
}
|
|
189
207
|
const index = nextLaunchIndex++;
|
|
190
|
-
if (index > 0) await this.waitBetweenSendMany(
|
|
191
|
-
|
|
208
|
+
if (index > 0) await this.waitBetweenSendMany(combinedSignal.signal);
|
|
209
|
+
if (closed) return;
|
|
210
|
+
const promise = this.send(next.value, sendOptions).then((receipt) => ({
|
|
192
211
|
index,
|
|
193
212
|
successful: true,
|
|
194
213
|
receipt
|
|
@@ -199,12 +218,19 @@ var RetryTransport = class {
|
|
|
199
218
|
}));
|
|
200
219
|
inFlight.set(index, promise);
|
|
201
220
|
};
|
|
202
|
-
const
|
|
203
|
-
|
|
221
|
+
const startLaunch = () => {
|
|
222
|
+
if (launchPromise != null || inputDone || inFlight.size >= this.config.sendMany.maxConcurrent) return;
|
|
223
|
+
launchPromise = launchNext().catch((error) => {
|
|
224
|
+
launchError = error;
|
|
225
|
+
hasLaunchError = true;
|
|
226
|
+
}).finally(() => {
|
|
227
|
+
launchPromise = void 0;
|
|
228
|
+
});
|
|
204
229
|
};
|
|
205
230
|
try {
|
|
206
|
-
|
|
207
|
-
while (inFlight.size > 0 || completed.has(nextYieldIndex)) {
|
|
231
|
+
startLaunch();
|
|
232
|
+
while (inFlight.size > 0 || completed.has(nextYieldIndex) || launchPromise != null || !inputDone) {
|
|
233
|
+
if (hasLaunchError) throw launchError;
|
|
208
234
|
while (completed.has(nextYieldIndex)) {
|
|
209
235
|
const result$1 = completed.get(nextYieldIndex);
|
|
210
236
|
completed.delete(nextYieldIndex);
|
|
@@ -212,16 +238,33 @@ var RetryTransport = class {
|
|
|
212
238
|
if (!result$1.successful) throw result$1.error;
|
|
213
239
|
yield result$1.receipt;
|
|
214
240
|
nextYieldIndex++;
|
|
215
|
-
|
|
241
|
+
startLaunch();
|
|
216
242
|
}
|
|
217
|
-
|
|
218
|
-
|
|
243
|
+
startLaunch();
|
|
244
|
+
if (inFlight.size <= 0 && launchPromise == null) break;
|
|
245
|
+
const launch = launchPromise?.then(() => ({ launched: true }));
|
|
246
|
+
const result = await Promise.race([...inFlight.values(), ...launch == null ? [] : [launch]]);
|
|
247
|
+
if ("launched" in result) continue;
|
|
219
248
|
inFlight.delete(result.index);
|
|
220
249
|
completed.set(result.index, result);
|
|
221
|
-
|
|
250
|
+
startLaunch();
|
|
222
251
|
}
|
|
252
|
+
if (hasLaunchError) throw launchError;
|
|
223
253
|
} finally {
|
|
224
|
-
|
|
254
|
+
closed = true;
|
|
255
|
+
controller.abort(new DOMException("The operation was aborted.", "AbortError"));
|
|
256
|
+
launchPromise?.catch(() => {});
|
|
257
|
+
try {
|
|
258
|
+
const closeIterator = async () => {
|
|
259
|
+
if (!inputDone) await iterator.return?.();
|
|
260
|
+
};
|
|
261
|
+
if (pullingInput) launchPromise?.finally(() => {
|
|
262
|
+
closeIterator().catch(() => {});
|
|
263
|
+
});
|
|
264
|
+
else await closeIterator();
|
|
265
|
+
} finally {
|
|
266
|
+
combinedSignal.cleanup();
|
|
267
|
+
}
|
|
225
268
|
}
|
|
226
269
|
}
|
|
227
270
|
/**
|
|
@@ -256,32 +299,47 @@ var RetryTransport = class {
|
|
|
256
299
|
};
|
|
257
300
|
}
|
|
258
301
|
shouldRetryReceipt(receipt) {
|
|
259
|
-
|
|
302
|
+
const shouldRetry = this.config.shouldRetry;
|
|
303
|
+
if (shouldRetry != null) return shouldRetry({
|
|
304
|
+
kind: "receipt",
|
|
305
|
+
receipt
|
|
306
|
+
});
|
|
260
307
|
if (receipt.retryable != null) return receipt.retryable;
|
|
261
308
|
return this.hasRetryableError(receipt.errors);
|
|
262
309
|
}
|
|
263
310
|
shouldRetryError(error) {
|
|
264
|
-
|
|
311
|
+
const receiptError = toReceiptError(error);
|
|
312
|
+
const shouldRetry = this.config.shouldRetry;
|
|
313
|
+
if (shouldRetry != null) return shouldRetry({
|
|
314
|
+
kind: "error",
|
|
315
|
+
error,
|
|
316
|
+
receiptError
|
|
317
|
+
});
|
|
318
|
+
if (receiptError != null) return receiptError.retryable;
|
|
319
|
+
if (error instanceof Error && error.name === "AbortError") return true;
|
|
265
320
|
return (0, __upyo_core.classifyReceiptError)(error).retryable;
|
|
266
321
|
}
|
|
267
322
|
hasRetryableError(errors) {
|
|
268
323
|
return errors?.some((error) => error.retryable) ?? false;
|
|
269
324
|
}
|
|
270
|
-
calculateDelay(attempt,
|
|
271
|
-
const retryAfterMilliseconds = getRetryAfterMilliseconds(
|
|
325
|
+
calculateDelay(attempt, failure) {
|
|
326
|
+
const retryAfterMilliseconds = getRetryAfterMilliseconds(failure);
|
|
272
327
|
const cappedRetryAfter = retryAfterMilliseconds == null ? void 0 : Math.min(retryAfterMilliseconds, this.config.backoff.maxDelayMilliseconds);
|
|
273
328
|
if (cappedRetryAfter != null) return cappedRetryAfter;
|
|
274
329
|
const computedDelay = Math.min(this.config.backoff.baseDelayMilliseconds * Math.pow(this.config.backoff.factor, attempt - 1), this.config.backoff.maxDelayMilliseconds);
|
|
275
330
|
if (this.config.jitter === false || this.config.jitter === "none") return computedDelay;
|
|
276
|
-
|
|
331
|
+
const random = this.config.random;
|
|
332
|
+
return Math.floor(random() * computedDelay);
|
|
277
333
|
}
|
|
278
334
|
waitBeforeRetry(context, signal) {
|
|
279
|
-
|
|
335
|
+
const wait = this.config.wait;
|
|
336
|
+
return wait(context, signal);
|
|
280
337
|
}
|
|
281
338
|
waitBetweenSendMany(signal) {
|
|
282
339
|
const delayMilliseconds = this.config.sendMany.intervalMilliseconds;
|
|
283
340
|
if (delayMilliseconds <= 0) return Promise.resolve();
|
|
284
|
-
|
|
341
|
+
const wait = this.config.wait;
|
|
342
|
+
return wait({
|
|
285
343
|
attempt: 0,
|
|
286
344
|
nextAttempt: 0,
|
|
287
345
|
maxAttempts: this.config.maxAttempts,
|
|
@@ -290,6 +348,11 @@ var RetryTransport = class {
|
|
|
290
348
|
}, signal);
|
|
291
349
|
}
|
|
292
350
|
createThrownFailure(error, attempts) {
|
|
351
|
+
const receiptError = toReceiptError(error);
|
|
352
|
+
if (receiptError != null) return (0, __upyo_core.createFailedReceipt)(receiptError, {
|
|
353
|
+
provider: receiptError.provider ?? this.id,
|
|
354
|
+
attempts
|
|
355
|
+
});
|
|
293
356
|
const message = error instanceof Error ? error.message : String(error);
|
|
294
357
|
const classification = (0, __upyo_core.classifyReceiptError)(error);
|
|
295
358
|
return (0, __upyo_core.createFailedReceipt)((0, __upyo_core.createReceiptError)(message, {
|
|
@@ -315,13 +378,20 @@ var RetryTransport = class {
|
|
|
315
378
|
function createRetryTransport(baseTransport, config = {}) {
|
|
316
379
|
return new RetryTransport(baseTransport, config);
|
|
317
380
|
}
|
|
318
|
-
function getRetryAfterMilliseconds(
|
|
319
|
-
|
|
320
|
-
return
|
|
381
|
+
function getRetryAfterMilliseconds(failure) {
|
|
382
|
+
if (failure == null) return void 0;
|
|
383
|
+
if (isReceiptError(failure)) return failure.retryAfterMilliseconds;
|
|
384
|
+
return failure.errors?.find((error) => error.retryAfterMilliseconds != null)?.retryAfterMilliseconds;
|
|
321
385
|
}
|
|
322
386
|
async function* toAsyncIterator(values) {
|
|
323
387
|
for await (const value of values) yield value;
|
|
324
388
|
}
|
|
389
|
+
function toReceiptError(value) {
|
|
390
|
+
return isReceiptError(value) ? value : void 0;
|
|
391
|
+
}
|
|
392
|
+
function isReceiptError(value) {
|
|
393
|
+
return typeof value === "object" && value != null && typeof value.message === "string" && typeof value.code === "string" && typeof value.category === "string" && typeof value.retryable === "boolean";
|
|
394
|
+
}
|
|
325
395
|
|
|
326
396
|
//#endregion
|
|
327
397
|
exports.RetryTransport = RetryTransport;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Message, Receipt, Transport, TransportOptions } from "@upyo/core";
|
|
1
|
+
import { Message, Receipt, ReceiptError, Transport, TransportOptions } from "@upyo/core";
|
|
2
2
|
|
|
3
3
|
//#region src/config.d.ts
|
|
4
4
|
|
|
@@ -80,16 +80,29 @@ interface DelayContext<TProviderId extends string = string> {
|
|
|
80
80
|
* @since 0.5.0
|
|
81
81
|
*/
|
|
82
82
|
type WaitFunction<TProviderId extends string = string> = (context: DelayContext<TProviderId>, signal?: AbortSignal) => Promise<void>;
|
|
83
|
+
/**
|
|
84
|
+
* Failure passed to a custom retry classifier.
|
|
85
|
+
*
|
|
86
|
+
* @since 0.5.0
|
|
87
|
+
*/
|
|
88
|
+
type RetryFailure<TProviderId extends string = string> = {
|
|
89
|
+
readonly kind: "receipt";
|
|
90
|
+
readonly receipt: Receipt<TProviderId> & {
|
|
91
|
+
readonly successful: false;
|
|
92
|
+
};
|
|
93
|
+
} | {
|
|
94
|
+
readonly kind: "error";
|
|
95
|
+
readonly error: unknown;
|
|
96
|
+
readonly receiptError?: ReceiptError<TProviderId>;
|
|
97
|
+
};
|
|
83
98
|
/**
|
|
84
99
|
* Function that decides whether a failure should be retried.
|
|
85
100
|
*
|
|
86
|
-
* @param failure Failed receipt or thrown error.
|
|
101
|
+
* @param failure Failed receipt or thrown error metadata.
|
|
87
102
|
* @returns Whether another attempt should be made.
|
|
88
103
|
* @since 0.5.0
|
|
89
104
|
*/
|
|
90
|
-
type RetryClassifier<TProviderId extends string = string> = (failure:
|
|
91
|
-
readonly successful: false;
|
|
92
|
-
}) | unknown) => boolean;
|
|
105
|
+
type RetryClassifier<TProviderId extends string = string> = (failure: RetryFailure<TProviderId>) => boolean;
|
|
93
106
|
/**
|
|
94
107
|
* Retry behavior for `sendMany()`.
|
|
95
108
|
*
|
|
@@ -243,4 +256,4 @@ declare class RetryTransport<TProviderId extends string = string> implements Tra
|
|
|
243
256
|
*/
|
|
244
257
|
declare function createRetryTransport<TProviderId extends string = string>(baseTransport: Transport<TProviderId>, config?: RetryConfig<TProviderId>): RetryTransport<TProviderId>;
|
|
245
258
|
//#endregion
|
|
246
|
-
export { BackoffConfig, DelayContext, JitterConfig, RetryClassifier, RetryConfig, RetryTransport, SendManyRetryConfig, WaitFunction, createRetryTransport };
|
|
259
|
+
export { BackoffConfig, DelayContext, JitterConfig, RetryClassifier, RetryConfig, RetryFailure, RetryTransport, SendManyRetryConfig, WaitFunction, createRetryTransport };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Message, Receipt, Transport, TransportOptions } from "@upyo/core";
|
|
1
|
+
import { Message, Receipt, ReceiptError, Transport, TransportOptions } from "@upyo/core";
|
|
2
2
|
|
|
3
3
|
//#region src/config.d.ts
|
|
4
4
|
|
|
@@ -80,16 +80,29 @@ interface DelayContext<TProviderId extends string = string> {
|
|
|
80
80
|
* @since 0.5.0
|
|
81
81
|
*/
|
|
82
82
|
type WaitFunction<TProviderId extends string = string> = (context: DelayContext<TProviderId>, signal?: AbortSignal) => Promise<void>;
|
|
83
|
+
/**
|
|
84
|
+
* Failure passed to a custom retry classifier.
|
|
85
|
+
*
|
|
86
|
+
* @since 0.5.0
|
|
87
|
+
*/
|
|
88
|
+
type RetryFailure<TProviderId extends string = string> = {
|
|
89
|
+
readonly kind: "receipt";
|
|
90
|
+
readonly receipt: Receipt<TProviderId> & {
|
|
91
|
+
readonly successful: false;
|
|
92
|
+
};
|
|
93
|
+
} | {
|
|
94
|
+
readonly kind: "error";
|
|
95
|
+
readonly error: unknown;
|
|
96
|
+
readonly receiptError?: ReceiptError<TProviderId>;
|
|
97
|
+
};
|
|
83
98
|
/**
|
|
84
99
|
* Function that decides whether a failure should be retried.
|
|
85
100
|
*
|
|
86
|
-
* @param failure Failed receipt or thrown error.
|
|
101
|
+
* @param failure Failed receipt or thrown error metadata.
|
|
87
102
|
* @returns Whether another attempt should be made.
|
|
88
103
|
* @since 0.5.0
|
|
89
104
|
*/
|
|
90
|
-
type RetryClassifier<TProviderId extends string = string> = (failure:
|
|
91
|
-
readonly successful: false;
|
|
92
|
-
}) | unknown) => boolean;
|
|
105
|
+
type RetryClassifier<TProviderId extends string = string> = (failure: RetryFailure<TProviderId>) => boolean;
|
|
93
106
|
/**
|
|
94
107
|
* Retry behavior for `sendMany()`.
|
|
95
108
|
*
|
|
@@ -243,4 +256,4 @@ declare class RetryTransport<TProviderId extends string = string> implements Tra
|
|
|
243
256
|
*/
|
|
244
257
|
declare function createRetryTransport<TProviderId extends string = string>(baseTransport: Transport<TProviderId>, config?: RetryConfig<TProviderId>): RetryTransport<TProviderId>;
|
|
245
258
|
//#endregion
|
|
246
|
-
export { BackoffConfig, DelayContext, JitterConfig, RetryClassifier, RetryConfig, RetryTransport, SendManyRetryConfig, WaitFunction, createRetryTransport };
|
|
259
|
+
export { BackoffConfig, DelayContext, JitterConfig, RetryClassifier, RetryConfig, RetryFailure, RetryTransport, SendManyRetryConfig, WaitFunction, createRetryTransport };
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { classifyReceiptError, createFailedReceipt, createReceiptError } from "@upyo/core";
|
|
1
|
+
import { classifyReceiptError, combineSignals, createFailedReceipt, createReceiptError } from "@upyo/core";
|
|
2
2
|
|
|
3
3
|
//#region src/config.ts
|
|
4
4
|
/**
|
|
@@ -51,7 +51,7 @@ async function defaultWait(context, signal) {
|
|
|
51
51
|
const abort = () => {
|
|
52
52
|
clearTimeout(timeout);
|
|
53
53
|
cleanup();
|
|
54
|
-
reject(createAbortError());
|
|
54
|
+
reject(signal?.reason ?? createAbortError());
|
|
55
55
|
};
|
|
56
56
|
if (signal?.aborted) {
|
|
57
57
|
abort();
|
|
@@ -130,7 +130,7 @@ var RetryTransport = class {
|
|
|
130
130
|
attempt,
|
|
131
131
|
nextAttempt: attempt + 1,
|
|
132
132
|
maxAttempts: this.config.maxAttempts,
|
|
133
|
-
delayMilliseconds: this.calculateDelay(attempt),
|
|
133
|
+
delayMilliseconds: this.calculateDelay(attempt, toReceiptError(error)),
|
|
134
134
|
error,
|
|
135
135
|
reason: "retry"
|
|
136
136
|
}, options?.signal);
|
|
@@ -155,17 +155,36 @@ var RetryTransport = class {
|
|
|
155
155
|
let inputDone = false;
|
|
156
156
|
let nextLaunchIndex = 0;
|
|
157
157
|
let nextYieldIndex = 0;
|
|
158
|
+
let launchPromise;
|
|
159
|
+
let launchError;
|
|
160
|
+
let hasLaunchError = false;
|
|
161
|
+
let pullingInput = false;
|
|
162
|
+
let closed = false;
|
|
163
|
+
const controller = new AbortController();
|
|
164
|
+
const combinedSignal = combineSignals(controller.signal, options?.signal);
|
|
165
|
+
const sendOptions = {
|
|
166
|
+
...options,
|
|
167
|
+
signal: combinedSignal.signal
|
|
168
|
+
};
|
|
158
169
|
const launchNext = async () => {
|
|
159
170
|
if (inputDone) return;
|
|
160
|
-
|
|
161
|
-
|
|
171
|
+
combinedSignal.signal.throwIfAborted();
|
|
172
|
+
pullingInput = true;
|
|
173
|
+
let next;
|
|
174
|
+
try {
|
|
175
|
+
next = await iterator.next();
|
|
176
|
+
} finally {
|
|
177
|
+
pullingInput = false;
|
|
178
|
+
}
|
|
179
|
+
if (closed) return;
|
|
162
180
|
if (next.done) {
|
|
163
181
|
inputDone = true;
|
|
164
182
|
return;
|
|
165
183
|
}
|
|
166
184
|
const index = nextLaunchIndex++;
|
|
167
|
-
if (index > 0) await this.waitBetweenSendMany(
|
|
168
|
-
|
|
185
|
+
if (index > 0) await this.waitBetweenSendMany(combinedSignal.signal);
|
|
186
|
+
if (closed) return;
|
|
187
|
+
const promise = this.send(next.value, sendOptions).then((receipt) => ({
|
|
169
188
|
index,
|
|
170
189
|
successful: true,
|
|
171
190
|
receipt
|
|
@@ -176,12 +195,19 @@ var RetryTransport = class {
|
|
|
176
195
|
}));
|
|
177
196
|
inFlight.set(index, promise);
|
|
178
197
|
};
|
|
179
|
-
const
|
|
180
|
-
|
|
198
|
+
const startLaunch = () => {
|
|
199
|
+
if (launchPromise != null || inputDone || inFlight.size >= this.config.sendMany.maxConcurrent) return;
|
|
200
|
+
launchPromise = launchNext().catch((error) => {
|
|
201
|
+
launchError = error;
|
|
202
|
+
hasLaunchError = true;
|
|
203
|
+
}).finally(() => {
|
|
204
|
+
launchPromise = void 0;
|
|
205
|
+
});
|
|
181
206
|
};
|
|
182
207
|
try {
|
|
183
|
-
|
|
184
|
-
while (inFlight.size > 0 || completed.has(nextYieldIndex)) {
|
|
208
|
+
startLaunch();
|
|
209
|
+
while (inFlight.size > 0 || completed.has(nextYieldIndex) || launchPromise != null || !inputDone) {
|
|
210
|
+
if (hasLaunchError) throw launchError;
|
|
185
211
|
while (completed.has(nextYieldIndex)) {
|
|
186
212
|
const result$1 = completed.get(nextYieldIndex);
|
|
187
213
|
completed.delete(nextYieldIndex);
|
|
@@ -189,16 +215,33 @@ var RetryTransport = class {
|
|
|
189
215
|
if (!result$1.successful) throw result$1.error;
|
|
190
216
|
yield result$1.receipt;
|
|
191
217
|
nextYieldIndex++;
|
|
192
|
-
|
|
218
|
+
startLaunch();
|
|
193
219
|
}
|
|
194
|
-
|
|
195
|
-
|
|
220
|
+
startLaunch();
|
|
221
|
+
if (inFlight.size <= 0 && launchPromise == null) break;
|
|
222
|
+
const launch = launchPromise?.then(() => ({ launched: true }));
|
|
223
|
+
const result = await Promise.race([...inFlight.values(), ...launch == null ? [] : [launch]]);
|
|
224
|
+
if ("launched" in result) continue;
|
|
196
225
|
inFlight.delete(result.index);
|
|
197
226
|
completed.set(result.index, result);
|
|
198
|
-
|
|
227
|
+
startLaunch();
|
|
199
228
|
}
|
|
229
|
+
if (hasLaunchError) throw launchError;
|
|
200
230
|
} finally {
|
|
201
|
-
|
|
231
|
+
closed = true;
|
|
232
|
+
controller.abort(new DOMException("The operation was aborted.", "AbortError"));
|
|
233
|
+
launchPromise?.catch(() => {});
|
|
234
|
+
try {
|
|
235
|
+
const closeIterator = async () => {
|
|
236
|
+
if (!inputDone) await iterator.return?.();
|
|
237
|
+
};
|
|
238
|
+
if (pullingInput) launchPromise?.finally(() => {
|
|
239
|
+
closeIterator().catch(() => {});
|
|
240
|
+
});
|
|
241
|
+
else await closeIterator();
|
|
242
|
+
} finally {
|
|
243
|
+
combinedSignal.cleanup();
|
|
244
|
+
}
|
|
202
245
|
}
|
|
203
246
|
}
|
|
204
247
|
/**
|
|
@@ -233,32 +276,47 @@ var RetryTransport = class {
|
|
|
233
276
|
};
|
|
234
277
|
}
|
|
235
278
|
shouldRetryReceipt(receipt) {
|
|
236
|
-
|
|
279
|
+
const shouldRetry = this.config.shouldRetry;
|
|
280
|
+
if (shouldRetry != null) return shouldRetry({
|
|
281
|
+
kind: "receipt",
|
|
282
|
+
receipt
|
|
283
|
+
});
|
|
237
284
|
if (receipt.retryable != null) return receipt.retryable;
|
|
238
285
|
return this.hasRetryableError(receipt.errors);
|
|
239
286
|
}
|
|
240
287
|
shouldRetryError(error) {
|
|
241
|
-
|
|
288
|
+
const receiptError = toReceiptError(error);
|
|
289
|
+
const shouldRetry = this.config.shouldRetry;
|
|
290
|
+
if (shouldRetry != null) return shouldRetry({
|
|
291
|
+
kind: "error",
|
|
292
|
+
error,
|
|
293
|
+
receiptError
|
|
294
|
+
});
|
|
295
|
+
if (receiptError != null) return receiptError.retryable;
|
|
296
|
+
if (error instanceof Error && error.name === "AbortError") return true;
|
|
242
297
|
return classifyReceiptError(error).retryable;
|
|
243
298
|
}
|
|
244
299
|
hasRetryableError(errors) {
|
|
245
300
|
return errors?.some((error) => error.retryable) ?? false;
|
|
246
301
|
}
|
|
247
|
-
calculateDelay(attempt,
|
|
248
|
-
const retryAfterMilliseconds = getRetryAfterMilliseconds(
|
|
302
|
+
calculateDelay(attempt, failure) {
|
|
303
|
+
const retryAfterMilliseconds = getRetryAfterMilliseconds(failure);
|
|
249
304
|
const cappedRetryAfter = retryAfterMilliseconds == null ? void 0 : Math.min(retryAfterMilliseconds, this.config.backoff.maxDelayMilliseconds);
|
|
250
305
|
if (cappedRetryAfter != null) return cappedRetryAfter;
|
|
251
306
|
const computedDelay = Math.min(this.config.backoff.baseDelayMilliseconds * Math.pow(this.config.backoff.factor, attempt - 1), this.config.backoff.maxDelayMilliseconds);
|
|
252
307
|
if (this.config.jitter === false || this.config.jitter === "none") return computedDelay;
|
|
253
|
-
|
|
308
|
+
const random = this.config.random;
|
|
309
|
+
return Math.floor(random() * computedDelay);
|
|
254
310
|
}
|
|
255
311
|
waitBeforeRetry(context, signal) {
|
|
256
|
-
|
|
312
|
+
const wait = this.config.wait;
|
|
313
|
+
return wait(context, signal);
|
|
257
314
|
}
|
|
258
315
|
waitBetweenSendMany(signal) {
|
|
259
316
|
const delayMilliseconds = this.config.sendMany.intervalMilliseconds;
|
|
260
317
|
if (delayMilliseconds <= 0) return Promise.resolve();
|
|
261
|
-
|
|
318
|
+
const wait = this.config.wait;
|
|
319
|
+
return wait({
|
|
262
320
|
attempt: 0,
|
|
263
321
|
nextAttempt: 0,
|
|
264
322
|
maxAttempts: this.config.maxAttempts,
|
|
@@ -267,6 +325,11 @@ var RetryTransport = class {
|
|
|
267
325
|
}, signal);
|
|
268
326
|
}
|
|
269
327
|
createThrownFailure(error, attempts) {
|
|
328
|
+
const receiptError = toReceiptError(error);
|
|
329
|
+
if (receiptError != null) return createFailedReceipt(receiptError, {
|
|
330
|
+
provider: receiptError.provider ?? this.id,
|
|
331
|
+
attempts
|
|
332
|
+
});
|
|
270
333
|
const message = error instanceof Error ? error.message : String(error);
|
|
271
334
|
const classification = classifyReceiptError(error);
|
|
272
335
|
return createFailedReceipt(createReceiptError(message, {
|
|
@@ -292,13 +355,20 @@ var RetryTransport = class {
|
|
|
292
355
|
function createRetryTransport(baseTransport, config = {}) {
|
|
293
356
|
return new RetryTransport(baseTransport, config);
|
|
294
357
|
}
|
|
295
|
-
function getRetryAfterMilliseconds(
|
|
296
|
-
|
|
297
|
-
return
|
|
358
|
+
function getRetryAfterMilliseconds(failure) {
|
|
359
|
+
if (failure == null) return void 0;
|
|
360
|
+
if (isReceiptError(failure)) return failure.retryAfterMilliseconds;
|
|
361
|
+
return failure.errors?.find((error) => error.retryAfterMilliseconds != null)?.retryAfterMilliseconds;
|
|
298
362
|
}
|
|
299
363
|
async function* toAsyncIterator(values) {
|
|
300
364
|
for await (const value of values) yield value;
|
|
301
365
|
}
|
|
366
|
+
function toReceiptError(value) {
|
|
367
|
+
return isReceiptError(value) ? value : void 0;
|
|
368
|
+
}
|
|
369
|
+
function isReceiptError(value) {
|
|
370
|
+
return typeof value === "object" && value != null && typeof value.message === "string" && typeof value.code === "string" && typeof value.category === "string" && typeof value.retryable === "boolean";
|
|
371
|
+
}
|
|
302
372
|
|
|
303
373
|
//#endregion
|
|
304
374
|
export { RetryTransport, createRetryTransport };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@upyo/retry",
|
|
3
|
-
"version": "0.5.0-dev.
|
|
3
|
+
"version": "0.5.0-dev.193",
|
|
4
4
|
"description": "Retry and backoff decorator transport for Upyo email library",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"email",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
},
|
|
55
55
|
"sideEffects": false,
|
|
56
56
|
"peerDependencies": {
|
|
57
|
-
"@upyo/core": "0.5.0"
|
|
57
|
+
"@upyo/core": "0.5.0-dev.193+eb1e84b9"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
60
|
"tsdown": "^0.12.7",
|