@upyo/retry 0.5.0-dev.197 → 0.5.0-dev.199

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
@@ -113,6 +113,7 @@ var RetryTransport = class {
113
113
  * @param transport The transport to wrap.
114
114
  * @param config Retry configuration.
115
115
  * @throws {RangeError} If retry configuration is invalid.
116
+ * @since 0.5.0
116
117
  */
117
118
  constructor(transport, config = {}) {
118
119
  this.wrappedTransport = transport;
@@ -126,25 +127,16 @@ var RetryTransport = class {
126
127
  * @param options Optional transport options.
127
128
  * @returns The final delivery receipt.
128
129
  * @throws {DOMException} If the operation is aborted.
130
+ * @since 0.5.0
129
131
  */
130
132
  async send(message, options) {
131
133
  options?.signal?.throwIfAborted();
132
134
  let lastThrownError;
133
135
  for (let attempt = 1; attempt <= this.config.maxAttempts; attempt++) {
134
136
  options?.signal?.throwIfAborted();
137
+ let receipt;
135
138
  try {
136
- const receipt = await this.wrappedTransport.send(message, options);
137
- if (receipt.successful) return this.withSuccessMetadata(receipt, attempt);
138
- const failedReceipt = this.withFailureMetadata(receipt, attempt);
139
- if (attempt >= this.config.maxAttempts || !this.shouldRetryReceipt(failedReceipt)) return failedReceipt;
140
- await this.waitBeforeRetry({
141
- attempt,
142
- nextAttempt: attempt + 1,
143
- maxAttempts: this.config.maxAttempts,
144
- delayMilliseconds: this.calculateDelay(attempt, failedReceipt),
145
- receipt: failedReceipt,
146
- reason: "retry"
147
- }, options?.signal);
139
+ receipt = await this.wrappedTransport.send(message, options);
148
140
  } catch (error) {
149
141
  options?.signal?.throwIfAborted();
150
142
  lastThrownError = error;
@@ -157,7 +149,20 @@ var RetryTransport = class {
157
149
  error,
158
150
  reason: "retry"
159
151
  }, options?.signal);
152
+ continue;
160
153
  }
154
+ options?.signal?.throwIfAborted();
155
+ if (receipt.successful) return this.withSuccessMetadata(receipt, attempt);
156
+ const failedReceipt = this.withFailureMetadata(receipt, attempt);
157
+ if (attempt >= this.config.maxAttempts || !this.shouldRetryReceipt(failedReceipt)) return failedReceipt;
158
+ await this.waitBeforeRetry({
159
+ attempt,
160
+ nextAttempt: attempt + 1,
161
+ maxAttempts: this.config.maxAttempts,
162
+ delayMilliseconds: this.calculateDelay(attempt, failedReceipt),
163
+ receipt: failedReceipt,
164
+ reason: "retry"
165
+ }, options?.signal);
161
166
  }
162
167
  return this.createThrownFailure(lastThrownError, this.config.maxAttempts);
163
168
  }
@@ -170,6 +175,7 @@ var RetryTransport = class {
170
175
  * @param options Optional transport options.
171
176
  * @returns An async iterable of receipts.
172
177
  * @throws {DOMException} If the operation is aborted.
178
+ * @since 0.5.0
173
179
  */
174
180
  async *sendMany(messages, options) {
175
181
  const iterator = toAsyncIterator(messages);
@@ -193,12 +199,16 @@ var RetryTransport = class {
193
199
  if (inputDone) return;
194
200
  combinedSignal.signal.throwIfAborted();
195
201
  pullingInput = true;
196
- let next;
202
+ let inputPull;
197
203
  try {
198
- next = await iterator.next();
199
- } finally {
200
- pullingInput = false;
204
+ inputPull = Promise.resolve(iterator.next());
205
+ } catch (error) {
206
+ inputPull = Promise.reject(error);
201
207
  }
208
+ inputPull = inputPull.finally(() => {
209
+ pullingInput = false;
210
+ });
211
+ const next = await raceWithAbort(inputPull, combinedSignal.signal);
202
212
  if (closed) return;
203
213
  if (next.done) {
204
214
  inputDone = true;
@@ -323,7 +333,7 @@ var RetryTransport = class {
323
333
  }
324
334
  calculateDelay(attempt, failure) {
325
335
  const retryAfterMilliseconds = getRetryAfterMilliseconds(failure);
326
- const cappedRetryAfter = retryAfterMilliseconds == null ? void 0 : Math.min(retryAfterMilliseconds, this.config.backoff.maxDelayMilliseconds);
336
+ const cappedRetryAfter = retryAfterMilliseconds == null || !Number.isFinite(retryAfterMilliseconds) || retryAfterMilliseconds <= 0 ? void 0 : Math.min(retryAfterMilliseconds, this.config.backoff.maxDelayMilliseconds);
327
337
  if (cappedRetryAfter != null) return cappedRetryAfter;
328
338
  const computedDelay = Math.min(this.config.backoff.baseDelayMilliseconds * Math.pow(this.config.backoff.factor, attempt - 1), this.config.backoff.maxDelayMilliseconds);
329
339
  if (this.config.jitter === false || this.config.jitter === "none") return computedDelay;
@@ -390,17 +400,37 @@ function toAsyncIterator(values) {
390
400
  if (Symbol.asyncIterator in values) return values[Symbol.asyncIterator]();
391
401
  const iterator = values[Symbol.iterator]();
392
402
  return {
393
- next() {
394
- return Promise.resolve(iterator.next());
403
+ async next() {
404
+ return await Promise.resolve(iterator.next());
395
405
  },
396
- return(value) {
397
- return Promise.resolve(iterator.return?.(value) ?? {
406
+ async return(value) {
407
+ return await Promise.resolve(iterator.return?.(value) ?? {
398
408
  done: true,
399
409
  value
400
410
  });
401
411
  }
402
412
  };
403
413
  }
414
+ function raceWithAbort(promise, signal) {
415
+ if (signal.aborted) return Promise.reject(signal.reason);
416
+ return new Promise((resolve, reject) => {
417
+ const abort = () => {
418
+ cleanup();
419
+ reject(signal.reason);
420
+ };
421
+ const cleanup = () => {
422
+ signal.removeEventListener("abort", abort);
423
+ };
424
+ signal.addEventListener("abort", abort, { once: true });
425
+ promise.then((value) => {
426
+ cleanup();
427
+ resolve(value);
428
+ }, (error) => {
429
+ cleanup();
430
+ reject(error);
431
+ });
432
+ });
433
+ }
404
434
  function toReceiptError(value) {
405
435
  return isReceiptError(value) ? value : void 0;
406
436
  }
package/dist/index.d.cts CHANGED
@@ -207,6 +207,7 @@ declare class RetryTransport<TProviderId extends string = string> implements Tra
207
207
  * @param transport The transport to wrap.
208
208
  * @param config Retry configuration.
209
209
  * @throws {RangeError} If retry configuration is invalid.
210
+ * @since 0.5.0
210
211
  */
211
212
  constructor(transport: Transport<TProviderId>, config?: RetryConfig<TProviderId>);
212
213
  /**
@@ -216,6 +217,7 @@ declare class RetryTransport<TProviderId extends string = string> implements Tra
216
217
  * @param options Optional transport options.
217
218
  * @returns The final delivery receipt.
218
219
  * @throws {DOMException} If the operation is aborted.
220
+ * @since 0.5.0
219
221
  */
220
222
  send(message: Message, options?: TransportOptions): Promise<Receipt<TProviderId>>;
221
223
  /**
@@ -227,6 +229,7 @@ declare class RetryTransport<TProviderId extends string = string> implements Tra
227
229
  * @param options Optional transport options.
228
230
  * @returns An async iterable of receipts.
229
231
  * @throws {DOMException} If the operation is aborted.
232
+ * @since 0.5.0
230
233
  */
231
234
  sendMany(messages: Iterable<Message> | AsyncIterable<Message>, options?: TransportOptions): AsyncIterable<Receipt<TProviderId>>;
232
235
  /**
package/dist/index.d.ts CHANGED
@@ -207,6 +207,7 @@ declare class RetryTransport<TProviderId extends string = string> implements Tra
207
207
  * @param transport The transport to wrap.
208
208
  * @param config Retry configuration.
209
209
  * @throws {RangeError} If retry configuration is invalid.
210
+ * @since 0.5.0
210
211
  */
211
212
  constructor(transport: Transport<TProviderId>, config?: RetryConfig<TProviderId>);
212
213
  /**
@@ -216,6 +217,7 @@ declare class RetryTransport<TProviderId extends string = string> implements Tra
216
217
  * @param options Optional transport options.
217
218
  * @returns The final delivery receipt.
218
219
  * @throws {DOMException} If the operation is aborted.
220
+ * @since 0.5.0
219
221
  */
220
222
  send(message: Message, options?: TransportOptions): Promise<Receipt<TProviderId>>;
221
223
  /**
@@ -227,6 +229,7 @@ declare class RetryTransport<TProviderId extends string = string> implements Tra
227
229
  * @param options Optional transport options.
228
230
  * @returns An async iterable of receipts.
229
231
  * @throws {DOMException} If the operation is aborted.
232
+ * @since 0.5.0
230
233
  */
231
234
  sendMany(messages: Iterable<Message> | AsyncIterable<Message>, options?: TransportOptions): AsyncIterable<Receipt<TProviderId>>;
232
235
  /**
package/dist/index.js CHANGED
@@ -90,6 +90,7 @@ var RetryTransport = class {
90
90
  * @param transport The transport to wrap.
91
91
  * @param config Retry configuration.
92
92
  * @throws {RangeError} If retry configuration is invalid.
93
+ * @since 0.5.0
93
94
  */
94
95
  constructor(transport, config = {}) {
95
96
  this.wrappedTransport = transport;
@@ -103,25 +104,16 @@ var RetryTransport = class {
103
104
  * @param options Optional transport options.
104
105
  * @returns The final delivery receipt.
105
106
  * @throws {DOMException} If the operation is aborted.
107
+ * @since 0.5.0
106
108
  */
107
109
  async send(message, options) {
108
110
  options?.signal?.throwIfAborted();
109
111
  let lastThrownError;
110
112
  for (let attempt = 1; attempt <= this.config.maxAttempts; attempt++) {
111
113
  options?.signal?.throwIfAborted();
114
+ let receipt;
112
115
  try {
113
- const receipt = await this.wrappedTransport.send(message, options);
114
- if (receipt.successful) return this.withSuccessMetadata(receipt, attempt);
115
- const failedReceipt = this.withFailureMetadata(receipt, attempt);
116
- if (attempt >= this.config.maxAttempts || !this.shouldRetryReceipt(failedReceipt)) return failedReceipt;
117
- await this.waitBeforeRetry({
118
- attempt,
119
- nextAttempt: attempt + 1,
120
- maxAttempts: this.config.maxAttempts,
121
- delayMilliseconds: this.calculateDelay(attempt, failedReceipt),
122
- receipt: failedReceipt,
123
- reason: "retry"
124
- }, options?.signal);
116
+ receipt = await this.wrappedTransport.send(message, options);
125
117
  } catch (error) {
126
118
  options?.signal?.throwIfAborted();
127
119
  lastThrownError = error;
@@ -134,7 +126,20 @@ var RetryTransport = class {
134
126
  error,
135
127
  reason: "retry"
136
128
  }, options?.signal);
129
+ continue;
137
130
  }
131
+ options?.signal?.throwIfAborted();
132
+ if (receipt.successful) return this.withSuccessMetadata(receipt, attempt);
133
+ const failedReceipt = this.withFailureMetadata(receipt, attempt);
134
+ if (attempt >= this.config.maxAttempts || !this.shouldRetryReceipt(failedReceipt)) return failedReceipt;
135
+ await this.waitBeforeRetry({
136
+ attempt,
137
+ nextAttempt: attempt + 1,
138
+ maxAttempts: this.config.maxAttempts,
139
+ delayMilliseconds: this.calculateDelay(attempt, failedReceipt),
140
+ receipt: failedReceipt,
141
+ reason: "retry"
142
+ }, options?.signal);
138
143
  }
139
144
  return this.createThrownFailure(lastThrownError, this.config.maxAttempts);
140
145
  }
@@ -147,6 +152,7 @@ var RetryTransport = class {
147
152
  * @param options Optional transport options.
148
153
  * @returns An async iterable of receipts.
149
154
  * @throws {DOMException} If the operation is aborted.
155
+ * @since 0.5.0
150
156
  */
151
157
  async *sendMany(messages, options) {
152
158
  const iterator = toAsyncIterator(messages);
@@ -170,12 +176,16 @@ var RetryTransport = class {
170
176
  if (inputDone) return;
171
177
  combinedSignal.signal.throwIfAborted();
172
178
  pullingInput = true;
173
- let next;
179
+ let inputPull;
174
180
  try {
175
- next = await iterator.next();
176
- } finally {
177
- pullingInput = false;
181
+ inputPull = Promise.resolve(iterator.next());
182
+ } catch (error) {
183
+ inputPull = Promise.reject(error);
178
184
  }
185
+ inputPull = inputPull.finally(() => {
186
+ pullingInput = false;
187
+ });
188
+ const next = await raceWithAbort(inputPull, combinedSignal.signal);
179
189
  if (closed) return;
180
190
  if (next.done) {
181
191
  inputDone = true;
@@ -300,7 +310,7 @@ var RetryTransport = class {
300
310
  }
301
311
  calculateDelay(attempt, failure) {
302
312
  const retryAfterMilliseconds = getRetryAfterMilliseconds(failure);
303
- const cappedRetryAfter = retryAfterMilliseconds == null ? void 0 : Math.min(retryAfterMilliseconds, this.config.backoff.maxDelayMilliseconds);
313
+ const cappedRetryAfter = retryAfterMilliseconds == null || !Number.isFinite(retryAfterMilliseconds) || retryAfterMilliseconds <= 0 ? void 0 : Math.min(retryAfterMilliseconds, this.config.backoff.maxDelayMilliseconds);
304
314
  if (cappedRetryAfter != null) return cappedRetryAfter;
305
315
  const computedDelay = Math.min(this.config.backoff.baseDelayMilliseconds * Math.pow(this.config.backoff.factor, attempt - 1), this.config.backoff.maxDelayMilliseconds);
306
316
  if (this.config.jitter === false || this.config.jitter === "none") return computedDelay;
@@ -367,17 +377,37 @@ function toAsyncIterator(values) {
367
377
  if (Symbol.asyncIterator in values) return values[Symbol.asyncIterator]();
368
378
  const iterator = values[Symbol.iterator]();
369
379
  return {
370
- next() {
371
- return Promise.resolve(iterator.next());
380
+ async next() {
381
+ return await Promise.resolve(iterator.next());
372
382
  },
373
- return(value) {
374
- return Promise.resolve(iterator.return?.(value) ?? {
383
+ async return(value) {
384
+ return await Promise.resolve(iterator.return?.(value) ?? {
375
385
  done: true,
376
386
  value
377
387
  });
378
388
  }
379
389
  };
380
390
  }
391
+ function raceWithAbort(promise, signal) {
392
+ if (signal.aborted) return Promise.reject(signal.reason);
393
+ return new Promise((resolve, reject) => {
394
+ const abort = () => {
395
+ cleanup();
396
+ reject(signal.reason);
397
+ };
398
+ const cleanup = () => {
399
+ signal.removeEventListener("abort", abort);
400
+ };
401
+ signal.addEventListener("abort", abort, { once: true });
402
+ promise.then((value) => {
403
+ cleanup();
404
+ resolve(value);
405
+ }, (error) => {
406
+ cleanup();
407
+ reject(error);
408
+ });
409
+ });
410
+ }
381
411
  function toReceiptError(value) {
382
412
  return isReceiptError(value) ? value : void 0;
383
413
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@upyo/retry",
3
- "version": "0.5.0-dev.197",
3
+ "version": "0.5.0-dev.199",
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-dev.197+0d26a10d"
57
+ "@upyo/core": "0.5.0-dev.199+f10145fc"
58
58
  },
59
59
  "devDependencies": {
60
60
  "tsdown": "^0.12.7",