@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 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
- options?.signal?.throwIfAborted();
184
- const next = await iterator.next();
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(options?.signal);
191
- const promise = this.send(next.value, options).then((receipt) => ({
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 launchAvailable = async () => {
203
- while (!inputDone && inFlight.size < this.config.sendMany.maxConcurrent) await launchNext();
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
- await launchAvailable();
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
- await launchAvailable();
240
+ startLaunch();
216
241
  }
217
- if (inFlight.size <= 0) break;
218
- const result = await Promise.race(inFlight.values());
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
- if (result.index !== nextYieldIndex) await launchAvailable();
250
+ startLaunch();
222
251
  }
252
+ if (hasLaunchError) throw launchError;
223
253
  } finally {
224
- if (!inputDone) await iterator.return?.();
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
- if (this.config.shouldRetry != null) return this.config.shouldRetry(receipt);
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
- if (this.config.shouldRetry != null) return this.config.shouldRetry(error);
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, receipt) {
271
- const retryAfterMilliseconds = getRetryAfterMilliseconds(receipt);
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
- return Math.floor(this.config.random() * computedDelay);
331
+ const random = this.config.random;
332
+ return Math.floor(random() * computedDelay);
277
333
  }
278
334
  waitBeforeRetry(context, signal) {
279
- return this.config.wait(context, signal);
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
- return this.config.wait({
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 = (0, __upyo_core.classifyReceiptError)(error);
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(receipt) {
319
- const receiptDelay = receipt?.errors?.map((error) => error.retryAfterMilliseconds).find((delay) => delay != null);
320
- return receiptDelay;
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: (Receipt<TProviderId> & {
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: (Receipt<TProviderId> & {
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
- options?.signal?.throwIfAborted();
161
- const next = await iterator.next();
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(options?.signal);
168
- const promise = this.send(next.value, options).then((receipt) => ({
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 launchAvailable = async () => {
180
- while (!inputDone && inFlight.size < this.config.sendMany.maxConcurrent) await launchNext();
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
- await launchAvailable();
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
- await launchAvailable();
217
+ startLaunch();
193
218
  }
194
- if (inFlight.size <= 0) break;
195
- const result = await Promise.race(inFlight.values());
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
- if (result.index !== nextYieldIndex) await launchAvailable();
227
+ startLaunch();
199
228
  }
229
+ if (hasLaunchError) throw launchError;
200
230
  } finally {
201
- if (!inputDone) await iterator.return?.();
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
- if (this.config.shouldRetry != null) return this.config.shouldRetry(receipt);
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
- if (this.config.shouldRetry != null) return this.config.shouldRetry(error);
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, receipt) {
248
- const retryAfterMilliseconds = getRetryAfterMilliseconds(receipt);
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
- return Math.floor(this.config.random() * computedDelay);
308
+ const random = this.config.random;
309
+ return Math.floor(random() * computedDelay);
254
310
  }
255
311
  waitBeforeRetry(context, signal) {
256
- return this.config.wait(context, signal);
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
- return this.config.wait({
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 = classifyReceiptError(error);
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(receipt) {
296
- const receiptDelay = receipt?.errors?.map((error) => error.retryAfterMilliseconds).find((delay) => delay != null);
297
- return receiptDelay;
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.0",
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",