@upyo/sendgrid 0.5.0-dev.87 → 0.5.0
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 +116 -17
- package/dist/index.d.cts +31 -5
- package/dist/index.d.ts +31 -5
- package/dist/index.js +94 -17
- package/package.json +3 -8
package/dist/index.cjs
CHANGED
|
@@ -1,3 +1,27 @@
|
|
|
1
|
+
//#region rolldown:runtime
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
10
|
+
key = keys[i];
|
|
11
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
12
|
+
get: ((k) => from[k]).bind(null, key),
|
|
13
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
19
|
+
value: mod,
|
|
20
|
+
enumerable: true
|
|
21
|
+
}) : target, mod));
|
|
22
|
+
|
|
23
|
+
//#endregion
|
|
24
|
+
const __upyo_core = __toESM(require("@upyo/core"));
|
|
1
25
|
|
|
2
26
|
//#region src/config.ts
|
|
3
27
|
/**
|
|
@@ -61,6 +85,9 @@ var SendGridHttpClient = class {
|
|
|
61
85
|
* @param url The URL to make the request to.
|
|
62
86
|
* @param options Fetch options.
|
|
63
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.
|
|
64
91
|
*/
|
|
65
92
|
async makeRequest(url, options) {
|
|
66
93
|
let lastError = null;
|
|
@@ -78,13 +105,17 @@ var SendGridHttpClient = class {
|
|
|
78
105
|
} catch {
|
|
79
106
|
errorData = { message: text || `HTTP ${response.status}` };
|
|
80
107
|
}
|
|
81
|
-
throw new SendGridApiError(errorData.message || `HTTP ${response.status}`, response.status, errorData.errors);
|
|
108
|
+
throw new SendGridApiError(errorData.message || `HTTP ${response.status}`, response.status, errorData.errors, (0, __upyo_core.parseRetryAfter)(response.headers.get("Retry-After")), attempt + 1);
|
|
82
109
|
} catch (error) {
|
|
83
110
|
lastError = error instanceof Error ? error : new Error(String(error));
|
|
111
|
+
if (isCallerAbort$1(error, options.signal)) throw error;
|
|
84
112
|
if (error instanceof SendGridApiError && error.statusCode && error.statusCode >= 400 && error.statusCode < 500) throw error;
|
|
85
|
-
if (attempt === this.config.retries)
|
|
113
|
+
if (attempt === this.config.retries) {
|
|
114
|
+
if (lastError instanceof SendGridApiError) throw lastError;
|
|
115
|
+
throw new SendGridApiError(lastError.message, void 0, void 0, void 0, attempt + 1);
|
|
116
|
+
}
|
|
86
117
|
const delay = Math.pow(2, attempt) * 1e3;
|
|
87
|
-
await
|
|
118
|
+
await sleep(delay, options.signal);
|
|
88
119
|
}
|
|
89
120
|
throw lastError || /* @__PURE__ */ new Error("Request failed after all retries");
|
|
90
121
|
}
|
|
@@ -94,6 +125,8 @@ var SendGridHttpClient = class {
|
|
|
94
125
|
* @param url The URL to make the request to.
|
|
95
126
|
* @param options Fetch options.
|
|
96
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.
|
|
97
130
|
*/
|
|
98
131
|
async fetchWithAuth(url, options) {
|
|
99
132
|
const headers = new Headers(options.headers);
|
|
@@ -101,19 +134,18 @@ var SendGridHttpClient = class {
|
|
|
101
134
|
for (const [key, value] of Object.entries(this.config.headers)) headers.set(key, value);
|
|
102
135
|
const controller = new AbortController();
|
|
103
136
|
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
|
|
104
|
-
|
|
105
|
-
if (options.signal) {
|
|
106
|
-
signal = options.signal;
|
|
107
|
-
if (options.signal.aborted) controller.abort();
|
|
108
|
-
else options.signal.addEventListener("abort", () => controller.abort());
|
|
109
|
-
}
|
|
137
|
+
const combinedSignal = (0, __upyo_core.combineSignals)(controller.signal, options.signal);
|
|
110
138
|
try {
|
|
111
139
|
return await globalThis.fetch(url, {
|
|
112
140
|
...options,
|
|
113
141
|
headers,
|
|
114
|
-
signal
|
|
142
|
+
signal: combinedSignal.signal
|
|
115
143
|
});
|
|
144
|
+
} catch (error) {
|
|
145
|
+
if (isAbortError$1(error) && controller.signal.aborted && !options.signal?.aborted) throw new Error(`SendGrid API request timed out after ${this.config.timeout} ms.`);
|
|
146
|
+
throw error;
|
|
116
147
|
} finally {
|
|
148
|
+
combinedSignal.cleanup();
|
|
117
149
|
clearTimeout(timeoutId);
|
|
118
150
|
}
|
|
119
151
|
}
|
|
@@ -130,18 +162,69 @@ var SendGridHttpClient = class {
|
|
|
130
162
|
}
|
|
131
163
|
};
|
|
132
164
|
/**
|
|
133
|
-
*
|
|
165
|
+
* Error thrown when a SendGrid API request fails.
|
|
166
|
+
*
|
|
167
|
+
* @since 0.5.0
|
|
134
168
|
*/
|
|
135
169
|
var SendGridApiError = class extends Error {
|
|
170
|
+
/**
|
|
171
|
+
* HTTP status code returned by SendGrid, if the request reached the API.
|
|
172
|
+
*/
|
|
136
173
|
statusCode;
|
|
174
|
+
/**
|
|
175
|
+
* Provider-supplied SendGrid error details.
|
|
176
|
+
*/
|
|
137
177
|
errors;
|
|
138
|
-
|
|
178
|
+
/**
|
|
179
|
+
* Retry delay from SendGrid's `Retry-After` response header.
|
|
180
|
+
*/
|
|
181
|
+
retryAfterMilliseconds;
|
|
182
|
+
/**
|
|
183
|
+
* Number of attempts made before this error was produced.
|
|
184
|
+
*/
|
|
185
|
+
attempts;
|
|
186
|
+
/**
|
|
187
|
+
* Creates a SendGrid API error.
|
|
188
|
+
*
|
|
189
|
+
* @param message Error message.
|
|
190
|
+
* @param statusCode HTTP status code returned by SendGrid.
|
|
191
|
+
* @param errors Provider-supplied SendGrid error details.
|
|
192
|
+
* @param retryAfterMilliseconds Retry delay from the response.
|
|
193
|
+
* @param attempts Number of attempts made before this error.
|
|
194
|
+
*/
|
|
195
|
+
constructor(message, statusCode, errors, retryAfterMilliseconds, attempts) {
|
|
139
196
|
super(message);
|
|
140
197
|
this.name = "SendGridApiError";
|
|
141
198
|
this.statusCode = statusCode;
|
|
142
199
|
this.errors = errors;
|
|
200
|
+
this.retryAfterMilliseconds = retryAfterMilliseconds;
|
|
201
|
+
this.attempts = attempts;
|
|
143
202
|
}
|
|
144
203
|
};
|
|
204
|
+
function isAbortError$1(error) {
|
|
205
|
+
return error instanceof Error && error.name === "AbortError";
|
|
206
|
+
}
|
|
207
|
+
function isCallerAbort$1(error, signal) {
|
|
208
|
+
return signal?.aborted === true && (isAbortError$1(error) || error === signal.reason);
|
|
209
|
+
}
|
|
210
|
+
function sleep(milliseconds, signal) {
|
|
211
|
+
if (signal?.aborted) return Promise.reject(createAbortError(signal));
|
|
212
|
+
return new Promise((resolve, reject) => {
|
|
213
|
+
function abort() {
|
|
214
|
+
clearTimeout(timeoutId);
|
|
215
|
+
signal?.removeEventListener("abort", abort);
|
|
216
|
+
reject(createAbortError(signal));
|
|
217
|
+
}
|
|
218
|
+
const timeoutId = setTimeout(() => {
|
|
219
|
+
signal?.removeEventListener("abort", abort);
|
|
220
|
+
resolve();
|
|
221
|
+
}, milliseconds);
|
|
222
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
function createAbortError(signal) {
|
|
226
|
+
return signal?.reason ?? new DOMException("The operation was aborted.", "AbortError");
|
|
227
|
+
}
|
|
145
228
|
|
|
146
229
|
//#endregion
|
|
147
230
|
//#region src/message-converter.ts
|
|
@@ -312,6 +395,7 @@ function isStandardHeader(headerName) {
|
|
|
312
395
|
* ```
|
|
313
396
|
*/
|
|
314
397
|
var SendGridTransport = class {
|
|
398
|
+
id = "sendgrid";
|
|
315
399
|
/**
|
|
316
400
|
* The resolved SendGrid configuration used by this transport.
|
|
317
401
|
*/
|
|
@@ -368,14 +452,13 @@ var SendGridTransport = class {
|
|
|
368
452
|
const messageId = this.extractMessageId(response);
|
|
369
453
|
return {
|
|
370
454
|
successful: true,
|
|
371
|
-
messageId
|
|
455
|
+
messageId,
|
|
456
|
+
provider: "sendgrid"
|
|
372
457
|
};
|
|
373
458
|
} catch (error) {
|
|
459
|
+
if (isCallerAbort(error, options?.signal)) throw error;
|
|
374
460
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
375
|
-
return
|
|
376
|
-
successful: false,
|
|
377
|
-
errorMessages: [errorMessage]
|
|
378
|
-
};
|
|
461
|
+
return createSendGridFailure(errorMessage, error);
|
|
379
462
|
}
|
|
380
463
|
}
|
|
381
464
|
/**
|
|
@@ -459,6 +542,22 @@ var SendGridTransport = class {
|
|
|
459
542
|
return `sendgrid-${timestamp}-${random}`;
|
|
460
543
|
}
|
|
461
544
|
};
|
|
545
|
+
function createSendGridFailure(message, error) {
|
|
546
|
+
if (error instanceof SendGridApiError) return (0, __upyo_core.createFailedReceipt)(message, {
|
|
547
|
+
provider: "sendgrid",
|
|
548
|
+
statusCode: error.statusCode,
|
|
549
|
+
retryAfterMilliseconds: error.retryAfterMilliseconds,
|
|
550
|
+
providerDetails: error.errors,
|
|
551
|
+
attempts: error.attempts
|
|
552
|
+
});
|
|
553
|
+
return (0, __upyo_core.createFailedReceipt)(message, { provider: "sendgrid" });
|
|
554
|
+
}
|
|
555
|
+
function isCallerAbort(error, signal) {
|
|
556
|
+
return signal?.aborted === true && (isAbortError(error) || error === signal.reason);
|
|
557
|
+
}
|
|
558
|
+
function isAbortError(error) {
|
|
559
|
+
return error instanceof Error && error.name === "AbortError";
|
|
560
|
+
}
|
|
462
561
|
|
|
463
562
|
//#endregion
|
|
464
563
|
exports.SendGridApiError = SendGridApiError;
|
package/dist/index.d.cts
CHANGED
|
@@ -123,7 +123,8 @@ type ResolvedSendGridConfig = Required<SendGridConfig>;
|
|
|
123
123
|
* }
|
|
124
124
|
* ```
|
|
125
125
|
*/
|
|
126
|
-
declare class SendGridTransport implements Transport {
|
|
126
|
+
declare class SendGridTransport implements Transport<"sendgrid"> {
|
|
127
|
+
readonly id = "sendgrid";
|
|
127
128
|
/**
|
|
128
129
|
* The resolved SendGrid configuration used by this transport.
|
|
129
130
|
*/
|
|
@@ -168,7 +169,7 @@ declare class SendGridTransport implements Transport {
|
|
|
168
169
|
* @returns A promise that resolves to a receipt indicating success or
|
|
169
170
|
* failure.
|
|
170
171
|
*/
|
|
171
|
-
send(message: Message, options?: TransportOptions): Promise<Receipt
|
|
172
|
+
send(message: Message, options?: TransportOptions): Promise<Receipt<"sendgrid">>;
|
|
172
173
|
/**
|
|
173
174
|
* Sends multiple email messages efficiently via SendGrid API.
|
|
174
175
|
*
|
|
@@ -221,7 +222,7 @@ declare class SendGridTransport implements Transport {
|
|
|
221
222
|
* cancellation.
|
|
222
223
|
* @returns An async iterable of receipts, one for each message.
|
|
223
224
|
*/
|
|
224
|
-
sendMany(messages: Iterable<Message> | AsyncIterable<Message>, options?: TransportOptions): AsyncIterable<Receipt
|
|
225
|
+
sendMany(messages: Iterable<Message> | AsyncIterable<Message>, options?: TransportOptions): AsyncIterable<Receipt<"sendgrid">>;
|
|
225
226
|
/**
|
|
226
227
|
* Extracts or generates a message ID from the SendGrid response.
|
|
227
228
|
*
|
|
@@ -237,20 +238,45 @@ declare class SendGridTransport implements Transport {
|
|
|
237
238
|
//#region src/http-client.d.ts
|
|
238
239
|
|
|
239
240
|
/**
|
|
240
|
-
*
|
|
241
|
+
* Error thrown when a SendGrid API request fails.
|
|
242
|
+
*
|
|
243
|
+
* @since 0.5.0
|
|
241
244
|
*/
|
|
242
245
|
declare class SendGridApiError extends Error {
|
|
246
|
+
/**
|
|
247
|
+
* HTTP status code returned by SendGrid, if the request reached the API.
|
|
248
|
+
*/
|
|
243
249
|
readonly statusCode?: number;
|
|
250
|
+
/**
|
|
251
|
+
* Provider-supplied SendGrid error details.
|
|
252
|
+
*/
|
|
244
253
|
readonly errors?: {
|
|
245
254
|
readonly message: string;
|
|
246
255
|
readonly field?: string;
|
|
247
256
|
readonly help?: string;
|
|
248
257
|
}[];
|
|
258
|
+
/**
|
|
259
|
+
* Retry delay from SendGrid's `Retry-After` response header.
|
|
260
|
+
*/
|
|
261
|
+
readonly retryAfterMilliseconds?: number;
|
|
262
|
+
/**
|
|
263
|
+
* Number of attempts made before this error was produced.
|
|
264
|
+
*/
|
|
265
|
+
readonly attempts?: number;
|
|
266
|
+
/**
|
|
267
|
+
* Creates a SendGrid API error.
|
|
268
|
+
*
|
|
269
|
+
* @param message Error message.
|
|
270
|
+
* @param statusCode HTTP status code returned by SendGrid.
|
|
271
|
+
* @param errors Provider-supplied SendGrid error details.
|
|
272
|
+
* @param retryAfterMilliseconds Retry delay from the response.
|
|
273
|
+
* @param attempts Number of attempts made before this error.
|
|
274
|
+
*/
|
|
249
275
|
constructor(message: string, statusCode?: number, errors?: Array<{
|
|
250
276
|
message: string;
|
|
251
277
|
field?: string;
|
|
252
278
|
help?: string;
|
|
253
|
-
}
|
|
279
|
+
}>, retryAfterMilliseconds?: number, attempts?: number);
|
|
254
280
|
}
|
|
255
281
|
//#endregion
|
|
256
282
|
export { SendGridApiError, SendGridConfig, SendGridTransport };
|
package/dist/index.d.ts
CHANGED
|
@@ -123,7 +123,8 @@ type ResolvedSendGridConfig = Required<SendGridConfig>;
|
|
|
123
123
|
* }
|
|
124
124
|
* ```
|
|
125
125
|
*/
|
|
126
|
-
declare class SendGridTransport implements Transport {
|
|
126
|
+
declare class SendGridTransport implements Transport<"sendgrid"> {
|
|
127
|
+
readonly id = "sendgrid";
|
|
127
128
|
/**
|
|
128
129
|
* The resolved SendGrid configuration used by this transport.
|
|
129
130
|
*/
|
|
@@ -168,7 +169,7 @@ declare class SendGridTransport implements Transport {
|
|
|
168
169
|
* @returns A promise that resolves to a receipt indicating success or
|
|
169
170
|
* failure.
|
|
170
171
|
*/
|
|
171
|
-
send(message: Message, options?: TransportOptions): Promise<Receipt
|
|
172
|
+
send(message: Message, options?: TransportOptions): Promise<Receipt<"sendgrid">>;
|
|
172
173
|
/**
|
|
173
174
|
* Sends multiple email messages efficiently via SendGrid API.
|
|
174
175
|
*
|
|
@@ -221,7 +222,7 @@ declare class SendGridTransport implements Transport {
|
|
|
221
222
|
* cancellation.
|
|
222
223
|
* @returns An async iterable of receipts, one for each message.
|
|
223
224
|
*/
|
|
224
|
-
sendMany(messages: Iterable<Message> | AsyncIterable<Message>, options?: TransportOptions): AsyncIterable<Receipt
|
|
225
|
+
sendMany(messages: Iterable<Message> | AsyncIterable<Message>, options?: TransportOptions): AsyncIterable<Receipt<"sendgrid">>;
|
|
225
226
|
/**
|
|
226
227
|
* Extracts or generates a message ID from the SendGrid response.
|
|
227
228
|
*
|
|
@@ -237,20 +238,45 @@ declare class SendGridTransport implements Transport {
|
|
|
237
238
|
//#region src/http-client.d.ts
|
|
238
239
|
|
|
239
240
|
/**
|
|
240
|
-
*
|
|
241
|
+
* Error thrown when a SendGrid API request fails.
|
|
242
|
+
*
|
|
243
|
+
* @since 0.5.0
|
|
241
244
|
*/
|
|
242
245
|
declare class SendGridApiError extends Error {
|
|
246
|
+
/**
|
|
247
|
+
* HTTP status code returned by SendGrid, if the request reached the API.
|
|
248
|
+
*/
|
|
243
249
|
readonly statusCode?: number;
|
|
250
|
+
/**
|
|
251
|
+
* Provider-supplied SendGrid error details.
|
|
252
|
+
*/
|
|
244
253
|
readonly errors?: {
|
|
245
254
|
readonly message: string;
|
|
246
255
|
readonly field?: string;
|
|
247
256
|
readonly help?: string;
|
|
248
257
|
}[];
|
|
258
|
+
/**
|
|
259
|
+
* Retry delay from SendGrid's `Retry-After` response header.
|
|
260
|
+
*/
|
|
261
|
+
readonly retryAfterMilliseconds?: number;
|
|
262
|
+
/**
|
|
263
|
+
* Number of attempts made before this error was produced.
|
|
264
|
+
*/
|
|
265
|
+
readonly attempts?: number;
|
|
266
|
+
/**
|
|
267
|
+
* Creates a SendGrid API error.
|
|
268
|
+
*
|
|
269
|
+
* @param message Error message.
|
|
270
|
+
* @param statusCode HTTP status code returned by SendGrid.
|
|
271
|
+
* @param errors Provider-supplied SendGrid error details.
|
|
272
|
+
* @param retryAfterMilliseconds Retry delay from the response.
|
|
273
|
+
* @param attempts Number of attempts made before this error.
|
|
274
|
+
*/
|
|
249
275
|
constructor(message: string, statusCode?: number, errors?: Array<{
|
|
250
276
|
message: string;
|
|
251
277
|
field?: string;
|
|
252
278
|
help?: string;
|
|
253
|
-
}
|
|
279
|
+
}>, retryAfterMilliseconds?: number, attempts?: number);
|
|
254
280
|
}
|
|
255
281
|
//#endregion
|
|
256
282
|
export { SendGridApiError, SendGridConfig, SendGridTransport };
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { combineSignals, createFailedReceipt, parseRetryAfter } from "@upyo/core";
|
|
2
|
+
|
|
1
3
|
//#region src/config.ts
|
|
2
4
|
/**
|
|
3
5
|
* Creates a resolved SendGrid configuration by applying default values to optional fields.
|
|
@@ -60,6 +62,9 @@ var SendGridHttpClient = class {
|
|
|
60
62
|
* @param url The URL to make the request to.
|
|
61
63
|
* @param options Fetch options.
|
|
62
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.
|
|
63
68
|
*/
|
|
64
69
|
async makeRequest(url, options) {
|
|
65
70
|
let lastError = null;
|
|
@@ -77,13 +82,17 @@ var SendGridHttpClient = class {
|
|
|
77
82
|
} catch {
|
|
78
83
|
errorData = { message: text || `HTTP ${response.status}` };
|
|
79
84
|
}
|
|
80
|
-
throw new SendGridApiError(errorData.message || `HTTP ${response.status}`, response.status, errorData.errors);
|
|
85
|
+
throw new SendGridApiError(errorData.message || `HTTP ${response.status}`, response.status, errorData.errors, parseRetryAfter(response.headers.get("Retry-After")), attempt + 1);
|
|
81
86
|
} catch (error) {
|
|
82
87
|
lastError = error instanceof Error ? error : new Error(String(error));
|
|
88
|
+
if (isCallerAbort$1(error, options.signal)) throw error;
|
|
83
89
|
if (error instanceof SendGridApiError && error.statusCode && error.statusCode >= 400 && error.statusCode < 500) throw error;
|
|
84
|
-
if (attempt === this.config.retries)
|
|
90
|
+
if (attempt === this.config.retries) {
|
|
91
|
+
if (lastError instanceof SendGridApiError) throw lastError;
|
|
92
|
+
throw new SendGridApiError(lastError.message, void 0, void 0, void 0, attempt + 1);
|
|
93
|
+
}
|
|
85
94
|
const delay = Math.pow(2, attempt) * 1e3;
|
|
86
|
-
await
|
|
95
|
+
await sleep(delay, options.signal);
|
|
87
96
|
}
|
|
88
97
|
throw lastError || /* @__PURE__ */ new Error("Request failed after all retries");
|
|
89
98
|
}
|
|
@@ -93,6 +102,8 @@ var SendGridHttpClient = class {
|
|
|
93
102
|
* @param url The URL to make the request to.
|
|
94
103
|
* @param options Fetch options.
|
|
95
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.
|
|
96
107
|
*/
|
|
97
108
|
async fetchWithAuth(url, options) {
|
|
98
109
|
const headers = new Headers(options.headers);
|
|
@@ -100,19 +111,18 @@ var SendGridHttpClient = class {
|
|
|
100
111
|
for (const [key, value] of Object.entries(this.config.headers)) headers.set(key, value);
|
|
101
112
|
const controller = new AbortController();
|
|
102
113
|
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
|
|
103
|
-
|
|
104
|
-
if (options.signal) {
|
|
105
|
-
signal = options.signal;
|
|
106
|
-
if (options.signal.aborted) controller.abort();
|
|
107
|
-
else options.signal.addEventListener("abort", () => controller.abort());
|
|
108
|
-
}
|
|
114
|
+
const combinedSignal = combineSignals(controller.signal, options.signal);
|
|
109
115
|
try {
|
|
110
116
|
return await globalThis.fetch(url, {
|
|
111
117
|
...options,
|
|
112
118
|
headers,
|
|
113
|
-
signal
|
|
119
|
+
signal: combinedSignal.signal
|
|
114
120
|
});
|
|
121
|
+
} catch (error) {
|
|
122
|
+
if (isAbortError$1(error) && controller.signal.aborted && !options.signal?.aborted) throw new Error(`SendGrid API request timed out after ${this.config.timeout} ms.`);
|
|
123
|
+
throw error;
|
|
115
124
|
} finally {
|
|
125
|
+
combinedSignal.cleanup();
|
|
116
126
|
clearTimeout(timeoutId);
|
|
117
127
|
}
|
|
118
128
|
}
|
|
@@ -129,18 +139,69 @@ var SendGridHttpClient = class {
|
|
|
129
139
|
}
|
|
130
140
|
};
|
|
131
141
|
/**
|
|
132
|
-
*
|
|
142
|
+
* Error thrown when a SendGrid API request fails.
|
|
143
|
+
*
|
|
144
|
+
* @since 0.5.0
|
|
133
145
|
*/
|
|
134
146
|
var SendGridApiError = class extends Error {
|
|
147
|
+
/**
|
|
148
|
+
* HTTP status code returned by SendGrid, if the request reached the API.
|
|
149
|
+
*/
|
|
135
150
|
statusCode;
|
|
151
|
+
/**
|
|
152
|
+
* Provider-supplied SendGrid error details.
|
|
153
|
+
*/
|
|
136
154
|
errors;
|
|
137
|
-
|
|
155
|
+
/**
|
|
156
|
+
* Retry delay from SendGrid's `Retry-After` response header.
|
|
157
|
+
*/
|
|
158
|
+
retryAfterMilliseconds;
|
|
159
|
+
/**
|
|
160
|
+
* Number of attempts made before this error was produced.
|
|
161
|
+
*/
|
|
162
|
+
attempts;
|
|
163
|
+
/**
|
|
164
|
+
* Creates a SendGrid API error.
|
|
165
|
+
*
|
|
166
|
+
* @param message Error message.
|
|
167
|
+
* @param statusCode HTTP status code returned by SendGrid.
|
|
168
|
+
* @param errors Provider-supplied SendGrid error details.
|
|
169
|
+
* @param retryAfterMilliseconds Retry delay from the response.
|
|
170
|
+
* @param attempts Number of attempts made before this error.
|
|
171
|
+
*/
|
|
172
|
+
constructor(message, statusCode, errors, retryAfterMilliseconds, attempts) {
|
|
138
173
|
super(message);
|
|
139
174
|
this.name = "SendGridApiError";
|
|
140
175
|
this.statusCode = statusCode;
|
|
141
176
|
this.errors = errors;
|
|
177
|
+
this.retryAfterMilliseconds = retryAfterMilliseconds;
|
|
178
|
+
this.attempts = attempts;
|
|
142
179
|
}
|
|
143
180
|
};
|
|
181
|
+
function isAbortError$1(error) {
|
|
182
|
+
return error instanceof Error && error.name === "AbortError";
|
|
183
|
+
}
|
|
184
|
+
function isCallerAbort$1(error, signal) {
|
|
185
|
+
return signal?.aborted === true && (isAbortError$1(error) || error === signal.reason);
|
|
186
|
+
}
|
|
187
|
+
function sleep(milliseconds, signal) {
|
|
188
|
+
if (signal?.aborted) return Promise.reject(createAbortError(signal));
|
|
189
|
+
return new Promise((resolve, reject) => {
|
|
190
|
+
function abort() {
|
|
191
|
+
clearTimeout(timeoutId);
|
|
192
|
+
signal?.removeEventListener("abort", abort);
|
|
193
|
+
reject(createAbortError(signal));
|
|
194
|
+
}
|
|
195
|
+
const timeoutId = setTimeout(() => {
|
|
196
|
+
signal?.removeEventListener("abort", abort);
|
|
197
|
+
resolve();
|
|
198
|
+
}, milliseconds);
|
|
199
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
function createAbortError(signal) {
|
|
203
|
+
return signal?.reason ?? new DOMException("The operation was aborted.", "AbortError");
|
|
204
|
+
}
|
|
144
205
|
|
|
145
206
|
//#endregion
|
|
146
207
|
//#region src/message-converter.ts
|
|
@@ -311,6 +372,7 @@ function isStandardHeader(headerName) {
|
|
|
311
372
|
* ```
|
|
312
373
|
*/
|
|
313
374
|
var SendGridTransport = class {
|
|
375
|
+
id = "sendgrid";
|
|
314
376
|
/**
|
|
315
377
|
* The resolved SendGrid configuration used by this transport.
|
|
316
378
|
*/
|
|
@@ -367,14 +429,13 @@ var SendGridTransport = class {
|
|
|
367
429
|
const messageId = this.extractMessageId(response);
|
|
368
430
|
return {
|
|
369
431
|
successful: true,
|
|
370
|
-
messageId
|
|
432
|
+
messageId,
|
|
433
|
+
provider: "sendgrid"
|
|
371
434
|
};
|
|
372
435
|
} catch (error) {
|
|
436
|
+
if (isCallerAbort(error, options?.signal)) throw error;
|
|
373
437
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
374
|
-
return
|
|
375
|
-
successful: false,
|
|
376
|
-
errorMessages: [errorMessage]
|
|
377
|
-
};
|
|
438
|
+
return createSendGridFailure(errorMessage, error);
|
|
378
439
|
}
|
|
379
440
|
}
|
|
380
441
|
/**
|
|
@@ -458,6 +519,22 @@ var SendGridTransport = class {
|
|
|
458
519
|
return `sendgrid-${timestamp}-${random}`;
|
|
459
520
|
}
|
|
460
521
|
};
|
|
522
|
+
function createSendGridFailure(message, error) {
|
|
523
|
+
if (error instanceof SendGridApiError) return createFailedReceipt(message, {
|
|
524
|
+
provider: "sendgrid",
|
|
525
|
+
statusCode: error.statusCode,
|
|
526
|
+
retryAfterMilliseconds: error.retryAfterMilliseconds,
|
|
527
|
+
providerDetails: error.errors,
|
|
528
|
+
attempts: error.attempts
|
|
529
|
+
});
|
|
530
|
+
return createFailedReceipt(message, { provider: "sendgrid" });
|
|
531
|
+
}
|
|
532
|
+
function isCallerAbort(error, signal) {
|
|
533
|
+
return signal?.aborted === true && (isAbortError(error) || error === signal.reason);
|
|
534
|
+
}
|
|
535
|
+
function isAbortError(error) {
|
|
536
|
+
return error instanceof Error && error.name === "AbortError";
|
|
537
|
+
}
|
|
461
538
|
|
|
462
539
|
//#endregion
|
|
463
540
|
export { SendGridApiError, SendGridTransport };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@upyo/sendgrid",
|
|
3
|
-
"version": "0.5.0
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "SendGrid transport for Upyo email library",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"email",
|
|
@@ -53,18 +53,13 @@
|
|
|
53
53
|
},
|
|
54
54
|
"sideEffects": false,
|
|
55
55
|
"peerDependencies": {
|
|
56
|
-
"@upyo/core": "0.5.0
|
|
56
|
+
"@upyo/core": "0.5.0"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
|
-
"@dotenvx/dotenvx": "^1.47.3",
|
|
60
59
|
"tsdown": "^0.12.7",
|
|
61
60
|
"typescript": "5.8.3"
|
|
62
61
|
},
|
|
63
62
|
"scripts": {
|
|
64
|
-
"
|
|
65
|
-
"prepublish": "tsdown",
|
|
66
|
-
"test": "tsdown && dotenvx run --ignore=MISSING_ENV_FILE -- node --experimental-transform-types --test",
|
|
67
|
-
"test:bun": "tsdown && bun test --timeout=30000 --env-file=.env",
|
|
68
|
-
"test:deno": "deno test --allow-env --allow-net --env-file=.env"
|
|
63
|
+
"prepublish": "mise run --no-deps :build"
|
|
69
64
|
}
|
|
70
65
|
}
|