@upyo/sendgrid 0.5.0-dev.158 → 0.5.0-dev.168

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
@@ -85,6 +85,9 @@ var SendGridHttpClient = class {
85
85
  * @param url The URL to make the request to.
86
86
  * @param options Fetch options.
87
87
  * @returns Promise that resolves to the parsed response.
88
+ * @throws {DOMException} If the caller aborts the request.
89
+ * @throws {SendGridApiError} If SendGrid returns a client error or all retry
90
+ * attempts are exhausted.
88
91
  */
89
92
  async makeRequest(url, options) {
90
93
  let lastError = null;
@@ -112,7 +115,7 @@ var SendGridHttpClient = class {
112
115
  throw new SendGridApiError(lastError.message, void 0, void 0, void 0, attempt + 1);
113
116
  }
114
117
  const delay = Math.pow(2, attempt) * 1e3;
115
- await new Promise((resolve) => setTimeout(resolve, delay));
118
+ await sleep(delay, options.signal);
116
119
  }
117
120
  throw lastError || /* @__PURE__ */ new Error("Request failed after all retries");
118
121
  }
@@ -122,6 +125,8 @@ var SendGridHttpClient = class {
122
125
  * @param url The URL to make the request to.
123
126
  * @param options Fetch options.
124
127
  * @returns Promise that resolves to the fetch response.
128
+ * @throws {Error} If the configured request timeout is reached.
129
+ * @throws {DOMException} If the caller aborts the request.
125
130
  */
126
131
  async fetchWithAuth(url, options) {
127
132
  const headers = new Headers(options.headers);
@@ -129,17 +134,18 @@ var SendGridHttpClient = class {
129
134
  for (const [key, value] of Object.entries(this.config.headers)) headers.set(key, value);
130
135
  const controller = new AbortController();
131
136
  const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
132
- const signal = options.signal == null ? controller.signal : AbortSignal.any([controller.signal, options.signal]);
137
+ const combinedSignal = combineSignals(controller.signal, options.signal);
133
138
  try {
134
139
  return await globalThis.fetch(url, {
135
140
  ...options,
136
141
  headers,
137
- signal
142
+ signal: combinedSignal.signal
138
143
  });
139
144
  } catch (error) {
140
145
  if (isAbortError$1(error) && controller.signal.aborted && !options.signal?.aborted) throw new Error(`SendGrid API request timed out after ${this.config.timeout} ms.`);
141
146
  throw error;
142
147
  } finally {
148
+ combinedSignal.cleanup();
143
149
  clearTimeout(timeoutId);
144
150
  }
145
151
  }
@@ -198,6 +204,57 @@ var SendGridApiError = class extends Error {
198
204
  function isAbortError$1(error) {
199
205
  return error instanceof Error && error.name === "AbortError";
200
206
  }
207
+ function sleep(milliseconds, signal) {
208
+ if (signal?.aborted) return Promise.reject(createAbortError(signal));
209
+ return new Promise((resolve, reject) => {
210
+ function abort() {
211
+ clearTimeout(timeoutId);
212
+ signal?.removeEventListener("abort", abort);
213
+ reject(createAbortError(signal));
214
+ }
215
+ const timeoutId = setTimeout(() => {
216
+ signal?.removeEventListener("abort", abort);
217
+ resolve();
218
+ }, milliseconds);
219
+ signal?.addEventListener("abort", abort, { once: true });
220
+ });
221
+ }
222
+ function createAbortError(signal) {
223
+ return signal?.reason ?? new DOMException("The operation was aborted.", "AbortError");
224
+ }
225
+ function combineSignals(timeoutSignal, externalSignal) {
226
+ if (externalSignal == null) return {
227
+ signal: timeoutSignal,
228
+ cleanup: () => {}
229
+ };
230
+ if (typeof AbortSignal.any === "function") return {
231
+ signal: AbortSignal.any([timeoutSignal, externalSignal]),
232
+ cleanup: () => {}
233
+ };
234
+ const controller = new AbortController();
235
+ const cleanup = () => {
236
+ timeoutSignal.removeEventListener("abort", abortFromTimeout);
237
+ externalSignal.removeEventListener("abort", abortFromExternal);
238
+ };
239
+ const abortFromTimeout = () => {
240
+ cleanup();
241
+ controller.abort(timeoutSignal.reason);
242
+ };
243
+ const abortFromExternal = () => {
244
+ cleanup();
245
+ controller.abort(externalSignal.reason);
246
+ };
247
+ if (timeoutSignal.aborted) controller.abort(timeoutSignal.reason);
248
+ else if (externalSignal.aborted) controller.abort(externalSignal.reason);
249
+ else {
250
+ timeoutSignal.addEventListener("abort", abortFromTimeout, { once: true });
251
+ externalSignal.addEventListener("abort", abortFromExternal, { once: true });
252
+ }
253
+ return {
254
+ signal: controller.signal,
255
+ cleanup
256
+ };
257
+ }
201
258
 
202
259
  //#endregion
203
260
  //#region src/message-converter.ts
@@ -429,7 +486,7 @@ var SendGridTransport = class {
429
486
  provider: "sendgrid"
430
487
  };
431
488
  } catch (error) {
432
- if (isAbortError(error) && options?.signal?.aborted) throw error;
489
+ if (isCallerAbort(error, options?.signal)) throw error;
433
490
  const errorMessage = error instanceof Error ? error.message : String(error);
434
491
  return createSendGridFailure(errorMessage, error);
435
492
  }
@@ -525,6 +582,9 @@ function createSendGridFailure(message, error) {
525
582
  });
526
583
  return (0, __upyo_core.createFailedReceipt)(message, { provider: "sendgrid" });
527
584
  }
585
+ function isCallerAbort(error, signal) {
586
+ return signal?.aborted === true && (isAbortError(error) || error === signal.reason);
587
+ }
528
588
  function isAbortError(error) {
529
589
  return error instanceof Error && error.name === "AbortError";
530
590
  }
package/dist/index.js CHANGED
@@ -62,6 +62,9 @@ var SendGridHttpClient = class {
62
62
  * @param url The URL to make the request to.
63
63
  * @param options Fetch options.
64
64
  * @returns Promise that resolves to the parsed response.
65
+ * @throws {DOMException} If the caller aborts the request.
66
+ * @throws {SendGridApiError} If SendGrid returns a client error or all retry
67
+ * attempts are exhausted.
65
68
  */
66
69
  async makeRequest(url, options) {
67
70
  let lastError = null;
@@ -89,7 +92,7 @@ var SendGridHttpClient = class {
89
92
  throw new SendGridApiError(lastError.message, void 0, void 0, void 0, attempt + 1);
90
93
  }
91
94
  const delay = Math.pow(2, attempt) * 1e3;
92
- await new Promise((resolve) => setTimeout(resolve, delay));
95
+ await sleep(delay, options.signal);
93
96
  }
94
97
  throw lastError || /* @__PURE__ */ new Error("Request failed after all retries");
95
98
  }
@@ -99,6 +102,8 @@ var SendGridHttpClient = class {
99
102
  * @param url The URL to make the request to.
100
103
  * @param options Fetch options.
101
104
  * @returns Promise that resolves to the fetch response.
105
+ * @throws {Error} If the configured request timeout is reached.
106
+ * @throws {DOMException} If the caller aborts the request.
102
107
  */
103
108
  async fetchWithAuth(url, options) {
104
109
  const headers = new Headers(options.headers);
@@ -106,17 +111,18 @@ var SendGridHttpClient = class {
106
111
  for (const [key, value] of Object.entries(this.config.headers)) headers.set(key, value);
107
112
  const controller = new AbortController();
108
113
  const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
109
- const signal = options.signal == null ? controller.signal : AbortSignal.any([controller.signal, options.signal]);
114
+ const combinedSignal = combineSignals(controller.signal, options.signal);
110
115
  try {
111
116
  return await globalThis.fetch(url, {
112
117
  ...options,
113
118
  headers,
114
- signal
119
+ signal: combinedSignal.signal
115
120
  });
116
121
  } catch (error) {
117
122
  if (isAbortError$1(error) && controller.signal.aborted && !options.signal?.aborted) throw new Error(`SendGrid API request timed out after ${this.config.timeout} ms.`);
118
123
  throw error;
119
124
  } finally {
125
+ combinedSignal.cleanup();
120
126
  clearTimeout(timeoutId);
121
127
  }
122
128
  }
@@ -175,6 +181,57 @@ var SendGridApiError = class extends Error {
175
181
  function isAbortError$1(error) {
176
182
  return error instanceof Error && error.name === "AbortError";
177
183
  }
184
+ function sleep(milliseconds, signal) {
185
+ if (signal?.aborted) return Promise.reject(createAbortError(signal));
186
+ return new Promise((resolve, reject) => {
187
+ function abort() {
188
+ clearTimeout(timeoutId);
189
+ signal?.removeEventListener("abort", abort);
190
+ reject(createAbortError(signal));
191
+ }
192
+ const timeoutId = setTimeout(() => {
193
+ signal?.removeEventListener("abort", abort);
194
+ resolve();
195
+ }, milliseconds);
196
+ signal?.addEventListener("abort", abort, { once: true });
197
+ });
198
+ }
199
+ function createAbortError(signal) {
200
+ return signal?.reason ?? new DOMException("The operation was aborted.", "AbortError");
201
+ }
202
+ function combineSignals(timeoutSignal, externalSignal) {
203
+ if (externalSignal == null) return {
204
+ signal: timeoutSignal,
205
+ cleanup: () => {}
206
+ };
207
+ if (typeof AbortSignal.any === "function") return {
208
+ signal: AbortSignal.any([timeoutSignal, externalSignal]),
209
+ cleanup: () => {}
210
+ };
211
+ const controller = new AbortController();
212
+ const cleanup = () => {
213
+ timeoutSignal.removeEventListener("abort", abortFromTimeout);
214
+ externalSignal.removeEventListener("abort", abortFromExternal);
215
+ };
216
+ const abortFromTimeout = () => {
217
+ cleanup();
218
+ controller.abort(timeoutSignal.reason);
219
+ };
220
+ const abortFromExternal = () => {
221
+ cleanup();
222
+ controller.abort(externalSignal.reason);
223
+ };
224
+ if (timeoutSignal.aborted) controller.abort(timeoutSignal.reason);
225
+ else if (externalSignal.aborted) controller.abort(externalSignal.reason);
226
+ else {
227
+ timeoutSignal.addEventListener("abort", abortFromTimeout, { once: true });
228
+ externalSignal.addEventListener("abort", abortFromExternal, { once: true });
229
+ }
230
+ return {
231
+ signal: controller.signal,
232
+ cleanup
233
+ };
234
+ }
178
235
 
179
236
  //#endregion
180
237
  //#region src/message-converter.ts
@@ -406,7 +463,7 @@ var SendGridTransport = class {
406
463
  provider: "sendgrid"
407
464
  };
408
465
  } catch (error) {
409
- if (isAbortError(error) && options?.signal?.aborted) throw error;
466
+ if (isCallerAbort(error, options?.signal)) throw error;
410
467
  const errorMessage = error instanceof Error ? error.message : String(error);
411
468
  return createSendGridFailure(errorMessage, error);
412
469
  }
@@ -502,6 +559,9 @@ function createSendGridFailure(message, error) {
502
559
  });
503
560
  return createFailedReceipt(message, { provider: "sendgrid" });
504
561
  }
562
+ function isCallerAbort(error, signal) {
563
+ return signal?.aborted === true && (isAbortError(error) || error === signal.reason);
564
+ }
505
565
  function isAbortError(error) {
506
566
  return error instanceof Error && error.name === "AbortError";
507
567
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@upyo/sendgrid",
3
- "version": "0.5.0-dev.158",
3
+ "version": "0.5.0-dev.168",
4
4
  "description": "SendGrid transport for Upyo email library",
5
5
  "keywords": [
6
6
  "email",
@@ -53,7 +53,7 @@
53
53
  },
54
54
  "sideEffects": false,
55
55
  "peerDependencies": {
56
- "@upyo/core": "0.5.0-dev.158+468b767c"
56
+ "@upyo/core": "0.5.0-dev.168+1e808a3a"
57
57
  },
58
58
  "devDependencies": {
59
59
  "tsdown": "^0.12.7",