@upyo/retry 0.5.0-dev.0 → 0.5.0-dev.195
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 +100 -26
- package/dist/index.d.cts +19 -6
- package/dist/index.d.ts +19 -6
- package/dist/index.js +101 -27
- 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,18 @@ 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) {
|
|
208
233
|
while (completed.has(nextYieldIndex)) {
|
|
209
234
|
const result$1 = completed.get(nextYieldIndex);
|
|
210
235
|
completed.delete(nextYieldIndex);
|
|
@@ -212,16 +237,34 @@ var RetryTransport = class {
|
|
|
212
237
|
if (!result$1.successful) throw result$1.error;
|
|
213
238
|
yield result$1.receipt;
|
|
214
239
|
nextYieldIndex++;
|
|
215
|
-
|
|
240
|
+
startLaunch();
|
|
216
241
|
}
|
|
217
|
-
if (
|
|
218
|
-
|
|
242
|
+
if (hasLaunchError) throw launchError;
|
|
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,8 +348,17 @@ 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
|
-
const classification =
|
|
357
|
+
const classification = error instanceof Error && error.name === "AbortError" ? {
|
|
358
|
+
category: "timeout",
|
|
359
|
+
code: "timeout",
|
|
360
|
+
retryable: true
|
|
361
|
+
} : (0, __upyo_core.classifyReceiptError)(error);
|
|
295
362
|
return (0, __upyo_core.createFailedReceipt)((0, __upyo_core.createReceiptError)(message, {
|
|
296
363
|
provider: this.id,
|
|
297
364
|
category: classification.category,
|
|
@@ -315,13 +382,20 @@ var RetryTransport = class {
|
|
|
315
382
|
function createRetryTransport(baseTransport, config = {}) {
|
|
316
383
|
return new RetryTransport(baseTransport, config);
|
|
317
384
|
}
|
|
318
|
-
function getRetryAfterMilliseconds(
|
|
319
|
-
|
|
320
|
-
return
|
|
385
|
+
function getRetryAfterMilliseconds(failure) {
|
|
386
|
+
if (failure == null) return void 0;
|
|
387
|
+
if (isReceiptError(failure)) return failure.retryAfterMilliseconds;
|
|
388
|
+
return failure.errors?.find((error) => error.retryAfterMilliseconds != null)?.retryAfterMilliseconds;
|
|
321
389
|
}
|
|
322
390
|
async function* toAsyncIterator(values) {
|
|
323
391
|
for await (const value of values) yield value;
|
|
324
392
|
}
|
|
393
|
+
function toReceiptError(value) {
|
|
394
|
+
return isReceiptError(value) ? value : void 0;
|
|
395
|
+
}
|
|
396
|
+
function isReceiptError(value) {
|
|
397
|
+
return typeof value === "object" && value != null && typeof value.message === "string" && typeof value.code === "string" && typeof value.category === "string" && typeof value.retryable === "boolean";
|
|
398
|
+
}
|
|
325
399
|
|
|
326
400
|
//#endregion
|
|
327
401
|
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,18 @@ 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) {
|
|
185
210
|
while (completed.has(nextYieldIndex)) {
|
|
186
211
|
const result$1 = completed.get(nextYieldIndex);
|
|
187
212
|
completed.delete(nextYieldIndex);
|
|
@@ -189,16 +214,34 @@ var RetryTransport = class {
|
|
|
189
214
|
if (!result$1.successful) throw result$1.error;
|
|
190
215
|
yield result$1.receipt;
|
|
191
216
|
nextYieldIndex++;
|
|
192
|
-
|
|
217
|
+
startLaunch();
|
|
193
218
|
}
|
|
194
|
-
if (
|
|
195
|
-
|
|
219
|
+
if (hasLaunchError) throw launchError;
|
|
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,8 +325,17 @@ 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
|
-
const classification =
|
|
334
|
+
const classification = error instanceof Error && error.name === "AbortError" ? {
|
|
335
|
+
category: "timeout",
|
|
336
|
+
code: "timeout",
|
|
337
|
+
retryable: true
|
|
338
|
+
} : classifyReceiptError(error);
|
|
272
339
|
return createFailedReceipt(createReceiptError(message, {
|
|
273
340
|
provider: this.id,
|
|
274
341
|
category: classification.category,
|
|
@@ -292,13 +359,20 @@ var RetryTransport = class {
|
|
|
292
359
|
function createRetryTransport(baseTransport, config = {}) {
|
|
293
360
|
return new RetryTransport(baseTransport, config);
|
|
294
361
|
}
|
|
295
|
-
function getRetryAfterMilliseconds(
|
|
296
|
-
|
|
297
|
-
return
|
|
362
|
+
function getRetryAfterMilliseconds(failure) {
|
|
363
|
+
if (failure == null) return void 0;
|
|
364
|
+
if (isReceiptError(failure)) return failure.retryAfterMilliseconds;
|
|
365
|
+
return failure.errors?.find((error) => error.retryAfterMilliseconds != null)?.retryAfterMilliseconds;
|
|
298
366
|
}
|
|
299
367
|
async function* toAsyncIterator(values) {
|
|
300
368
|
for await (const value of values) yield value;
|
|
301
369
|
}
|
|
370
|
+
function toReceiptError(value) {
|
|
371
|
+
return isReceiptError(value) ? value : void 0;
|
|
372
|
+
}
|
|
373
|
+
function isReceiptError(value) {
|
|
374
|
+
return typeof value === "object" && value != null && typeof value.message === "string" && typeof value.code === "string" && typeof value.category === "string" && typeof value.retryable === "boolean";
|
|
375
|
+
}
|
|
302
376
|
|
|
303
377
|
//#endregion
|
|
304
378
|
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.195",
|
|
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.195+5d1ba2de"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
60
|
"tsdown": "^0.12.7",
|