@upyo/resend 0.5.0-dev.136 → 0.5.0-dev.156
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 +113 -24
- package/dist/index.d.cts +28 -6
- package/dist/index.d.ts +28 -6
- package/dist/index.js +91 -24
- 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
|
/**
|
|
@@ -25,14 +49,37 @@ function createResendConfig(config) {
|
|
|
25
49
|
//#endregion
|
|
26
50
|
//#region src/http-client.ts
|
|
27
51
|
/**
|
|
28
|
-
*
|
|
52
|
+
* Error thrown when a Resend API request fails.
|
|
53
|
+
*
|
|
54
|
+
* @since 0.5.0
|
|
29
55
|
*/
|
|
30
56
|
var ResendApiError = class extends Error {
|
|
57
|
+
/**
|
|
58
|
+
* HTTP status code returned by Resend, if the request reached the API.
|
|
59
|
+
*/
|
|
31
60
|
statusCode;
|
|
32
|
-
|
|
61
|
+
/**
|
|
62
|
+
* Retry delay from Resend's `Retry-After` response header.
|
|
63
|
+
*/
|
|
64
|
+
retryAfterMilliseconds;
|
|
65
|
+
/**
|
|
66
|
+
* Number of attempts made before this error was produced.
|
|
67
|
+
*/
|
|
68
|
+
attempts;
|
|
69
|
+
/**
|
|
70
|
+
* Creates a Resend API error.
|
|
71
|
+
*
|
|
72
|
+
* @param message Error message.
|
|
73
|
+
* @param statusCode HTTP status code returned by Resend.
|
|
74
|
+
* @param retryAfterMilliseconds Retry delay from the response.
|
|
75
|
+
* @param attempts Number of attempts made before this error.
|
|
76
|
+
*/
|
|
77
|
+
constructor(message, statusCode, retryAfterMilliseconds, attempts) {
|
|
33
78
|
super(message);
|
|
34
79
|
this.name = "ResendApiError";
|
|
35
80
|
this.statusCode = statusCode;
|
|
81
|
+
this.retryAfterMilliseconds = retryAfterMilliseconds;
|
|
82
|
+
this.attempts = attempts;
|
|
36
83
|
}
|
|
37
84
|
};
|
|
38
85
|
/**
|
|
@@ -102,7 +149,10 @@ var ResendHttpClient = class {
|
|
|
102
149
|
const errorBody = JSON.parse(text);
|
|
103
150
|
errorMessage = errorBody.message;
|
|
104
151
|
} catch {}
|
|
105
|
-
|
|
152
|
+
const parsedErrorMessage = errorMessage === "" ? void 0 : errorMessage;
|
|
153
|
+
const responseMessage = truncateErrorBody(text);
|
|
154
|
+
const fallbackMessage = responseMessage === "" ? void 0 : responseMessage;
|
|
155
|
+
throw new ResendApiError(parsedErrorMessage ?? fallbackMessage ?? `HTTP ${response.status}`, response.status, (0, __upyo_core.parseRetryAfter)(response.headers.get("Retry-After")), attempt + 1);
|
|
106
156
|
}
|
|
107
157
|
try {
|
|
108
158
|
return JSON.parse(text);
|
|
@@ -111,9 +161,12 @@ var ResendHttpClient = class {
|
|
|
111
161
|
}
|
|
112
162
|
} catch (error) {
|
|
113
163
|
lastError = error instanceof Error ? error : new Error(String(error));
|
|
114
|
-
if (error instanceof ResendApiError && error.statusCode >= 400 && error.statusCode < 500) throw error;
|
|
164
|
+
if (error instanceof ResendApiError && error.statusCode !== void 0 && error.statusCode >= 400 && error.statusCode < 500) throw error;
|
|
115
165
|
if (error instanceof Error && error.name === "AbortError") throw error;
|
|
116
|
-
if (attempt === this.config.retries)
|
|
166
|
+
if (attempt === this.config.retries) {
|
|
167
|
+
if (lastError instanceof ResendApiError) throw lastError;
|
|
168
|
+
throw new ResendApiError(lastError.message, void 0, void 0, attempt + 1);
|
|
169
|
+
}
|
|
117
170
|
const backoffMs = Math.min(1e3 * Math.pow(2, attempt), 1e4);
|
|
118
171
|
await new Promise((resolve) => setTimeout(resolve, backoffMs));
|
|
119
172
|
}
|
|
@@ -132,26 +185,51 @@ var ResendHttpClient = class {
|
|
|
132
185
|
for (const [key, value] of Object.entries(this.config.headers)) headers.set(key, value);
|
|
133
186
|
const controller = new AbortController();
|
|
134
187
|
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
|
|
135
|
-
|
|
136
|
-
if (options.signal) {
|
|
137
|
-
const combinedController = new AbortController();
|
|
138
|
-
const onAbort = () => combinedController.abort();
|
|
139
|
-
options.signal.addEventListener("abort", onAbort, { once: true });
|
|
140
|
-
controller.signal.addEventListener("abort", onAbort, { once: true });
|
|
141
|
-
signal = combinedController.signal;
|
|
142
|
-
} else signal = controller.signal;
|
|
188
|
+
const combinedSignal = combineSignals(controller.signal, options.signal);
|
|
143
189
|
try {
|
|
144
190
|
const response = await fetch(url, {
|
|
145
191
|
...options,
|
|
146
192
|
headers,
|
|
147
|
-
signal
|
|
193
|
+
signal: combinedSignal.signal
|
|
148
194
|
});
|
|
149
195
|
return response;
|
|
196
|
+
} catch (error) {
|
|
197
|
+
if (isAbortError$1(error) && controller.signal.aborted && !options.signal?.aborted) throw new Error(`Resend API request timed out after ${this.config.timeout} ms.`);
|
|
198
|
+
throw error;
|
|
150
199
|
} finally {
|
|
200
|
+
combinedSignal.cleanup();
|
|
151
201
|
clearTimeout(timeoutId);
|
|
152
202
|
}
|
|
153
203
|
}
|
|
154
204
|
};
|
|
205
|
+
function combineSignals(timeoutSignal, externalSignal) {
|
|
206
|
+
if (externalSignal == null) return {
|
|
207
|
+
signal: timeoutSignal,
|
|
208
|
+
cleanup: () => {}
|
|
209
|
+
};
|
|
210
|
+
if (typeof AbortSignal.any === "function") return {
|
|
211
|
+
signal: AbortSignal.any([timeoutSignal, externalSignal]),
|
|
212
|
+
cleanup: () => {}
|
|
213
|
+
};
|
|
214
|
+
const controller = new AbortController();
|
|
215
|
+
const abort = () => controller.abort();
|
|
216
|
+
timeoutSignal.addEventListener("abort", abort, { once: true });
|
|
217
|
+
externalSignal.addEventListener("abort", abort, { once: true });
|
|
218
|
+
if (timeoutSignal.aborted || externalSignal.aborted) controller.abort();
|
|
219
|
+
return {
|
|
220
|
+
signal: controller.signal,
|
|
221
|
+
cleanup: () => {
|
|
222
|
+
timeoutSignal.removeEventListener("abort", abort);
|
|
223
|
+
externalSignal.removeEventListener("abort", abort);
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
function isAbortError$1(error) {
|
|
228
|
+
return error instanceof Error && error.name === "AbortError";
|
|
229
|
+
}
|
|
230
|
+
function truncateErrorBody(text) {
|
|
231
|
+
return text.length > 500 ? `${text.slice(0, 500)}...` : text;
|
|
232
|
+
}
|
|
155
233
|
|
|
156
234
|
//#endregion
|
|
157
235
|
//#region src/message-converter.ts
|
|
@@ -339,6 +417,7 @@ function generateIdempotencyKey() {
|
|
|
339
417
|
* ```
|
|
340
418
|
*/
|
|
341
419
|
var ResendTransport = class {
|
|
420
|
+
id = "resend";
|
|
342
421
|
/**
|
|
343
422
|
* The resolved Resend configuration used by this transport.
|
|
344
423
|
*/
|
|
@@ -396,14 +475,13 @@ var ResendTransport = class {
|
|
|
396
475
|
const response = await this.httpClient.sendMessage(emailData, options?.signal, idempotencyKey);
|
|
397
476
|
return {
|
|
398
477
|
successful: true,
|
|
399
|
-
messageId: response.id
|
|
478
|
+
messageId: response.id,
|
|
479
|
+
provider: "resend"
|
|
400
480
|
};
|
|
401
481
|
} catch (error) {
|
|
482
|
+
if (isAbortError(error) && options?.signal?.aborted) throw error;
|
|
402
483
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
403
|
-
return
|
|
404
|
-
successful: false,
|
|
405
|
-
errorMessages: [errorMessage]
|
|
406
|
-
};
|
|
484
|
+
return createResendFailure(errorMessage, error);
|
|
407
485
|
}
|
|
408
486
|
}
|
|
409
487
|
/**
|
|
@@ -512,14 +590,13 @@ var ResendTransport = class {
|
|
|
512
590
|
const response = await this.httpClient.sendBatch(batchData, options?.signal, idempotencyKey);
|
|
513
591
|
for (const result of response.data) yield {
|
|
514
592
|
successful: true,
|
|
515
|
-
messageId: result.id
|
|
593
|
+
messageId: result.id,
|
|
594
|
+
provider: "resend"
|
|
516
595
|
};
|
|
517
596
|
} catch (error) {
|
|
597
|
+
if (isAbortError(error) && options?.signal?.aborted) throw error;
|
|
518
598
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
519
|
-
for (let i = 0; i < messages.length; i++) yield
|
|
520
|
-
successful: false,
|
|
521
|
-
errorMessages: [errorMessage]
|
|
522
|
-
};
|
|
599
|
+
for (let i = 0; i < messages.length; i++) yield createResendFailure(errorMessage, error);
|
|
523
600
|
}
|
|
524
601
|
}
|
|
525
602
|
/**
|
|
@@ -549,6 +626,18 @@ var ResendTransport = class {
|
|
|
549
626
|
return chunks;
|
|
550
627
|
}
|
|
551
628
|
};
|
|
629
|
+
function createResendFailure(message, error) {
|
|
630
|
+
if (error instanceof ResendApiError) return (0, __upyo_core.createFailedReceipt)(message, {
|
|
631
|
+
provider: "resend",
|
|
632
|
+
statusCode: error.statusCode,
|
|
633
|
+
retryAfterMilliseconds: error.retryAfterMilliseconds,
|
|
634
|
+
attempts: error.attempts
|
|
635
|
+
});
|
|
636
|
+
return (0, __upyo_core.createFailedReceipt)(message, { provider: "resend" });
|
|
637
|
+
}
|
|
638
|
+
function isAbortError(error) {
|
|
639
|
+
return error instanceof Error && error.name === "AbortError";
|
|
640
|
+
}
|
|
552
641
|
|
|
553
642
|
//#endregion
|
|
554
643
|
exports.ResendTransport = ResendTransport;
|
package/dist/index.d.cts
CHANGED
|
@@ -99,7 +99,8 @@ type ResolvedResendConfig = Required<ResendConfig>;
|
|
|
99
99
|
* }
|
|
100
100
|
* ```
|
|
101
101
|
*/
|
|
102
|
-
declare class ResendTransport implements Transport {
|
|
102
|
+
declare class ResendTransport implements Transport<"resend"> {
|
|
103
|
+
readonly id = "resend";
|
|
103
104
|
/**
|
|
104
105
|
* The resolved Resend configuration used by this transport.
|
|
105
106
|
*/
|
|
@@ -145,7 +146,7 @@ declare class ResendTransport implements Transport {
|
|
|
145
146
|
* @returns A promise that resolves to a receipt indicating success or
|
|
146
147
|
* failure.
|
|
147
148
|
*/
|
|
148
|
-
send(message: Message, options?: TransportOptions): Promise<Receipt
|
|
149
|
+
send(message: Message, options?: TransportOptions): Promise<Receipt<"resend">>;
|
|
149
150
|
/**
|
|
150
151
|
* Sends multiple email messages efficiently via Resend API.
|
|
151
152
|
*
|
|
@@ -200,7 +201,7 @@ declare class ResendTransport implements Transport {
|
|
|
200
201
|
* cancellation.
|
|
201
202
|
* @returns An async iterable of receipts, one for each message.
|
|
202
203
|
*/
|
|
203
|
-
sendMany(messages: Iterable<Message> | AsyncIterable<Message>, options?: TransportOptions): AsyncIterable<Receipt
|
|
204
|
+
sendMany(messages: Iterable<Message> | AsyncIterable<Message>, options?: TransportOptions): AsyncIterable<Receipt<"resend">>;
|
|
204
205
|
/**
|
|
205
206
|
* Optimized batch sending that chooses the best strategy based on message features.
|
|
206
207
|
*
|
|
@@ -274,11 +275,32 @@ interface ResendError {
|
|
|
274
275
|
name?: string;
|
|
275
276
|
}
|
|
276
277
|
/**
|
|
277
|
-
*
|
|
278
|
+
* Error thrown when a Resend API request fails.
|
|
279
|
+
*
|
|
280
|
+
* @since 0.5.0
|
|
278
281
|
*/
|
|
279
282
|
declare class ResendApiError extends Error {
|
|
280
|
-
|
|
281
|
-
|
|
283
|
+
/**
|
|
284
|
+
* HTTP status code returned by Resend, if the request reached the API.
|
|
285
|
+
*/
|
|
286
|
+
readonly statusCode?: number;
|
|
287
|
+
/**
|
|
288
|
+
* Retry delay from Resend's `Retry-After` response header.
|
|
289
|
+
*/
|
|
290
|
+
readonly retryAfterMilliseconds?: number;
|
|
291
|
+
/**
|
|
292
|
+
* Number of attempts made before this error was produced.
|
|
293
|
+
*/
|
|
294
|
+
readonly attempts?: number;
|
|
295
|
+
/**
|
|
296
|
+
* Creates a Resend API error.
|
|
297
|
+
*
|
|
298
|
+
* @param message Error message.
|
|
299
|
+
* @param statusCode HTTP status code returned by Resend.
|
|
300
|
+
* @param retryAfterMilliseconds Retry delay from the response.
|
|
301
|
+
* @param attempts Number of attempts made before this error.
|
|
302
|
+
*/
|
|
303
|
+
constructor(message: string, statusCode?: number, retryAfterMilliseconds?: number, attempts?: number);
|
|
282
304
|
}
|
|
283
305
|
/**
|
|
284
306
|
* HTTP client wrapper for Resend API requests.
|
package/dist/index.d.ts
CHANGED
|
@@ -99,7 +99,8 @@ type ResolvedResendConfig = Required<ResendConfig>;
|
|
|
99
99
|
* }
|
|
100
100
|
* ```
|
|
101
101
|
*/
|
|
102
|
-
declare class ResendTransport implements Transport {
|
|
102
|
+
declare class ResendTransport implements Transport<"resend"> {
|
|
103
|
+
readonly id = "resend";
|
|
103
104
|
/**
|
|
104
105
|
* The resolved Resend configuration used by this transport.
|
|
105
106
|
*/
|
|
@@ -145,7 +146,7 @@ declare class ResendTransport implements Transport {
|
|
|
145
146
|
* @returns A promise that resolves to a receipt indicating success or
|
|
146
147
|
* failure.
|
|
147
148
|
*/
|
|
148
|
-
send(message: Message, options?: TransportOptions): Promise<Receipt
|
|
149
|
+
send(message: Message, options?: TransportOptions): Promise<Receipt<"resend">>;
|
|
149
150
|
/**
|
|
150
151
|
* Sends multiple email messages efficiently via Resend API.
|
|
151
152
|
*
|
|
@@ -200,7 +201,7 @@ declare class ResendTransport implements Transport {
|
|
|
200
201
|
* cancellation.
|
|
201
202
|
* @returns An async iterable of receipts, one for each message.
|
|
202
203
|
*/
|
|
203
|
-
sendMany(messages: Iterable<Message> | AsyncIterable<Message>, options?: TransportOptions): AsyncIterable<Receipt
|
|
204
|
+
sendMany(messages: Iterable<Message> | AsyncIterable<Message>, options?: TransportOptions): AsyncIterable<Receipt<"resend">>;
|
|
204
205
|
/**
|
|
205
206
|
* Optimized batch sending that chooses the best strategy based on message features.
|
|
206
207
|
*
|
|
@@ -274,11 +275,32 @@ interface ResendError {
|
|
|
274
275
|
name?: string;
|
|
275
276
|
}
|
|
276
277
|
/**
|
|
277
|
-
*
|
|
278
|
+
* Error thrown when a Resend API request fails.
|
|
279
|
+
*
|
|
280
|
+
* @since 0.5.0
|
|
278
281
|
*/
|
|
279
282
|
declare class ResendApiError extends Error {
|
|
280
|
-
|
|
281
|
-
|
|
283
|
+
/**
|
|
284
|
+
* HTTP status code returned by Resend, if the request reached the API.
|
|
285
|
+
*/
|
|
286
|
+
readonly statusCode?: number;
|
|
287
|
+
/**
|
|
288
|
+
* Retry delay from Resend's `Retry-After` response header.
|
|
289
|
+
*/
|
|
290
|
+
readonly retryAfterMilliseconds?: number;
|
|
291
|
+
/**
|
|
292
|
+
* Number of attempts made before this error was produced.
|
|
293
|
+
*/
|
|
294
|
+
readonly attempts?: number;
|
|
295
|
+
/**
|
|
296
|
+
* Creates a Resend API error.
|
|
297
|
+
*
|
|
298
|
+
* @param message Error message.
|
|
299
|
+
* @param statusCode HTTP status code returned by Resend.
|
|
300
|
+
* @param retryAfterMilliseconds Retry delay from the response.
|
|
301
|
+
* @param attempts Number of attempts made before this error.
|
|
302
|
+
*/
|
|
303
|
+
constructor(message: string, statusCode?: number, retryAfterMilliseconds?: number, attempts?: number);
|
|
282
304
|
}
|
|
283
305
|
/**
|
|
284
306
|
* HTTP client wrapper for Resend API requests.
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { createFailedReceipt, parseRetryAfter } from "@upyo/core";
|
|
2
|
+
|
|
1
3
|
//#region src/config.ts
|
|
2
4
|
/**
|
|
3
5
|
* Creates a resolved Resend configuration by applying default values to optional fields.
|
|
@@ -24,14 +26,37 @@ function createResendConfig(config) {
|
|
|
24
26
|
//#endregion
|
|
25
27
|
//#region src/http-client.ts
|
|
26
28
|
/**
|
|
27
|
-
*
|
|
29
|
+
* Error thrown when a Resend API request fails.
|
|
30
|
+
*
|
|
31
|
+
* @since 0.5.0
|
|
28
32
|
*/
|
|
29
33
|
var ResendApiError = class extends Error {
|
|
34
|
+
/**
|
|
35
|
+
* HTTP status code returned by Resend, if the request reached the API.
|
|
36
|
+
*/
|
|
30
37
|
statusCode;
|
|
31
|
-
|
|
38
|
+
/**
|
|
39
|
+
* Retry delay from Resend's `Retry-After` response header.
|
|
40
|
+
*/
|
|
41
|
+
retryAfterMilliseconds;
|
|
42
|
+
/**
|
|
43
|
+
* Number of attempts made before this error was produced.
|
|
44
|
+
*/
|
|
45
|
+
attempts;
|
|
46
|
+
/**
|
|
47
|
+
* Creates a Resend API error.
|
|
48
|
+
*
|
|
49
|
+
* @param message Error message.
|
|
50
|
+
* @param statusCode HTTP status code returned by Resend.
|
|
51
|
+
* @param retryAfterMilliseconds Retry delay from the response.
|
|
52
|
+
* @param attempts Number of attempts made before this error.
|
|
53
|
+
*/
|
|
54
|
+
constructor(message, statusCode, retryAfterMilliseconds, attempts) {
|
|
32
55
|
super(message);
|
|
33
56
|
this.name = "ResendApiError";
|
|
34
57
|
this.statusCode = statusCode;
|
|
58
|
+
this.retryAfterMilliseconds = retryAfterMilliseconds;
|
|
59
|
+
this.attempts = attempts;
|
|
35
60
|
}
|
|
36
61
|
};
|
|
37
62
|
/**
|
|
@@ -101,7 +126,10 @@ var ResendHttpClient = class {
|
|
|
101
126
|
const errorBody = JSON.parse(text);
|
|
102
127
|
errorMessage = errorBody.message;
|
|
103
128
|
} catch {}
|
|
104
|
-
|
|
129
|
+
const parsedErrorMessage = errorMessage === "" ? void 0 : errorMessage;
|
|
130
|
+
const responseMessage = truncateErrorBody(text);
|
|
131
|
+
const fallbackMessage = responseMessage === "" ? void 0 : responseMessage;
|
|
132
|
+
throw new ResendApiError(parsedErrorMessage ?? fallbackMessage ?? `HTTP ${response.status}`, response.status, parseRetryAfter(response.headers.get("Retry-After")), attempt + 1);
|
|
105
133
|
}
|
|
106
134
|
try {
|
|
107
135
|
return JSON.parse(text);
|
|
@@ -110,9 +138,12 @@ var ResendHttpClient = class {
|
|
|
110
138
|
}
|
|
111
139
|
} catch (error) {
|
|
112
140
|
lastError = error instanceof Error ? error : new Error(String(error));
|
|
113
|
-
if (error instanceof ResendApiError && error.statusCode >= 400 && error.statusCode < 500) throw error;
|
|
141
|
+
if (error instanceof ResendApiError && error.statusCode !== void 0 && error.statusCode >= 400 && error.statusCode < 500) throw error;
|
|
114
142
|
if (error instanceof Error && error.name === "AbortError") throw error;
|
|
115
|
-
if (attempt === this.config.retries)
|
|
143
|
+
if (attempt === this.config.retries) {
|
|
144
|
+
if (lastError instanceof ResendApiError) throw lastError;
|
|
145
|
+
throw new ResendApiError(lastError.message, void 0, void 0, attempt + 1);
|
|
146
|
+
}
|
|
116
147
|
const backoffMs = Math.min(1e3 * Math.pow(2, attempt), 1e4);
|
|
117
148
|
await new Promise((resolve) => setTimeout(resolve, backoffMs));
|
|
118
149
|
}
|
|
@@ -131,26 +162,51 @@ var ResendHttpClient = class {
|
|
|
131
162
|
for (const [key, value] of Object.entries(this.config.headers)) headers.set(key, value);
|
|
132
163
|
const controller = new AbortController();
|
|
133
164
|
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
|
|
134
|
-
|
|
135
|
-
if (options.signal) {
|
|
136
|
-
const combinedController = new AbortController();
|
|
137
|
-
const onAbort = () => combinedController.abort();
|
|
138
|
-
options.signal.addEventListener("abort", onAbort, { once: true });
|
|
139
|
-
controller.signal.addEventListener("abort", onAbort, { once: true });
|
|
140
|
-
signal = combinedController.signal;
|
|
141
|
-
} else signal = controller.signal;
|
|
165
|
+
const combinedSignal = combineSignals(controller.signal, options.signal);
|
|
142
166
|
try {
|
|
143
167
|
const response = await fetch(url, {
|
|
144
168
|
...options,
|
|
145
169
|
headers,
|
|
146
|
-
signal
|
|
170
|
+
signal: combinedSignal.signal
|
|
147
171
|
});
|
|
148
172
|
return response;
|
|
173
|
+
} catch (error) {
|
|
174
|
+
if (isAbortError$1(error) && controller.signal.aborted && !options.signal?.aborted) throw new Error(`Resend API request timed out after ${this.config.timeout} ms.`);
|
|
175
|
+
throw error;
|
|
149
176
|
} finally {
|
|
177
|
+
combinedSignal.cleanup();
|
|
150
178
|
clearTimeout(timeoutId);
|
|
151
179
|
}
|
|
152
180
|
}
|
|
153
181
|
};
|
|
182
|
+
function combineSignals(timeoutSignal, externalSignal) {
|
|
183
|
+
if (externalSignal == null) return {
|
|
184
|
+
signal: timeoutSignal,
|
|
185
|
+
cleanup: () => {}
|
|
186
|
+
};
|
|
187
|
+
if (typeof AbortSignal.any === "function") return {
|
|
188
|
+
signal: AbortSignal.any([timeoutSignal, externalSignal]),
|
|
189
|
+
cleanup: () => {}
|
|
190
|
+
};
|
|
191
|
+
const controller = new AbortController();
|
|
192
|
+
const abort = () => controller.abort();
|
|
193
|
+
timeoutSignal.addEventListener("abort", abort, { once: true });
|
|
194
|
+
externalSignal.addEventListener("abort", abort, { once: true });
|
|
195
|
+
if (timeoutSignal.aborted || externalSignal.aborted) controller.abort();
|
|
196
|
+
return {
|
|
197
|
+
signal: controller.signal,
|
|
198
|
+
cleanup: () => {
|
|
199
|
+
timeoutSignal.removeEventListener("abort", abort);
|
|
200
|
+
externalSignal.removeEventListener("abort", abort);
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
function isAbortError$1(error) {
|
|
205
|
+
return error instanceof Error && error.name === "AbortError";
|
|
206
|
+
}
|
|
207
|
+
function truncateErrorBody(text) {
|
|
208
|
+
return text.length > 500 ? `${text.slice(0, 500)}...` : text;
|
|
209
|
+
}
|
|
154
210
|
|
|
155
211
|
//#endregion
|
|
156
212
|
//#region src/message-converter.ts
|
|
@@ -338,6 +394,7 @@ function generateIdempotencyKey() {
|
|
|
338
394
|
* ```
|
|
339
395
|
*/
|
|
340
396
|
var ResendTransport = class {
|
|
397
|
+
id = "resend";
|
|
341
398
|
/**
|
|
342
399
|
* The resolved Resend configuration used by this transport.
|
|
343
400
|
*/
|
|
@@ -395,14 +452,13 @@ var ResendTransport = class {
|
|
|
395
452
|
const response = await this.httpClient.sendMessage(emailData, options?.signal, idempotencyKey);
|
|
396
453
|
return {
|
|
397
454
|
successful: true,
|
|
398
|
-
messageId: response.id
|
|
455
|
+
messageId: response.id,
|
|
456
|
+
provider: "resend"
|
|
399
457
|
};
|
|
400
458
|
} catch (error) {
|
|
459
|
+
if (isAbortError(error) && options?.signal?.aborted) throw error;
|
|
401
460
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
402
|
-
return
|
|
403
|
-
successful: false,
|
|
404
|
-
errorMessages: [errorMessage]
|
|
405
|
-
};
|
|
461
|
+
return createResendFailure(errorMessage, error);
|
|
406
462
|
}
|
|
407
463
|
}
|
|
408
464
|
/**
|
|
@@ -511,14 +567,13 @@ var ResendTransport = class {
|
|
|
511
567
|
const response = await this.httpClient.sendBatch(batchData, options?.signal, idempotencyKey);
|
|
512
568
|
for (const result of response.data) yield {
|
|
513
569
|
successful: true,
|
|
514
|
-
messageId: result.id
|
|
570
|
+
messageId: result.id,
|
|
571
|
+
provider: "resend"
|
|
515
572
|
};
|
|
516
573
|
} catch (error) {
|
|
574
|
+
if (isAbortError(error) && options?.signal?.aborted) throw error;
|
|
517
575
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
518
|
-
for (let i = 0; i < messages.length; i++) yield
|
|
519
|
-
successful: false,
|
|
520
|
-
errorMessages: [errorMessage]
|
|
521
|
-
};
|
|
576
|
+
for (let i = 0; i < messages.length; i++) yield createResendFailure(errorMessage, error);
|
|
522
577
|
}
|
|
523
578
|
}
|
|
524
579
|
/**
|
|
@@ -548,6 +603,18 @@ var ResendTransport = class {
|
|
|
548
603
|
return chunks;
|
|
549
604
|
}
|
|
550
605
|
};
|
|
606
|
+
function createResendFailure(message, error) {
|
|
607
|
+
if (error instanceof ResendApiError) return createFailedReceipt(message, {
|
|
608
|
+
provider: "resend",
|
|
609
|
+
statusCode: error.statusCode,
|
|
610
|
+
retryAfterMilliseconds: error.retryAfterMilliseconds,
|
|
611
|
+
attempts: error.attempts
|
|
612
|
+
});
|
|
613
|
+
return createFailedReceipt(message, { provider: "resend" });
|
|
614
|
+
}
|
|
615
|
+
function isAbortError(error) {
|
|
616
|
+
return error instanceof Error && error.name === "AbortError";
|
|
617
|
+
}
|
|
551
618
|
|
|
552
619
|
//#endregion
|
|
553
620
|
export { ResendTransport };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@upyo/resend",
|
|
3
|
-
"version": "0.5.0-dev.
|
|
3
|
+
"version": "0.5.0-dev.156",
|
|
4
4
|
"description": "Resend 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-dev.
|
|
56
|
+
"@upyo/core": "0.5.0-dev.156+edad9790"
|
|
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
|
}
|