@upyo/resend 0.5.0-dev.128 → 0.5.0-dev.154

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
@@ -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
- * Resend API error class for handling API-specific errors.
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
- constructor(message, statusCode) {
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
- throw new ResendApiError(errorMessage || text || `HTTP ${response.status}`, response.status);
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) throw lastError;
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
  }
@@ -133,11 +186,19 @@ var ResendHttpClient = class {
133
186
  const controller = new AbortController();
134
187
  const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
135
188
  let signal;
189
+ let cleanup = () => {};
136
190
  if (options.signal) {
137
191
  const combinedController = new AbortController();
138
192
  const onAbort = () => combinedController.abort();
139
- options.signal.addEventListener("abort", onAbort, { once: true });
140
- controller.signal.addEventListener("abort", onAbort, { once: true });
193
+ if (options.signal.aborted || controller.signal.aborted) combinedController.abort();
194
+ else {
195
+ options.signal.addEventListener("abort", onAbort, { once: true });
196
+ controller.signal.addEventListener("abort", onAbort, { once: true });
197
+ }
198
+ cleanup = () => {
199
+ options.signal?.removeEventListener("abort", onAbort);
200
+ controller.signal.removeEventListener("abort", onAbort);
201
+ };
141
202
  signal = combinedController.signal;
142
203
  } else signal = controller.signal;
143
204
  try {
@@ -147,11 +208,21 @@ var ResendHttpClient = class {
147
208
  signal
148
209
  });
149
210
  return response;
211
+ } catch (error) {
212
+ if (isAbortError$1(error) && controller.signal.aborted && !options.signal?.aborted) throw new Error(`Resend API request timed out after ${this.config.timeout} ms.`);
213
+ throw error;
150
214
  } finally {
215
+ cleanup();
151
216
  clearTimeout(timeoutId);
152
217
  }
153
218
  }
154
219
  };
220
+ function isAbortError$1(error) {
221
+ return error instanceof Error && error.name === "AbortError";
222
+ }
223
+ function truncateErrorBody(text) {
224
+ return text.length > 500 ? `${text.slice(0, 500)}...` : text;
225
+ }
155
226
 
156
227
  //#endregion
157
228
  //#region src/message-converter.ts
@@ -339,6 +410,7 @@ function generateIdempotencyKey() {
339
410
  * ```
340
411
  */
341
412
  var ResendTransport = class {
413
+ id = "resend";
342
414
  /**
343
415
  * The resolved Resend configuration used by this transport.
344
416
  */
@@ -396,14 +468,13 @@ var ResendTransport = class {
396
468
  const response = await this.httpClient.sendMessage(emailData, options?.signal, idempotencyKey);
397
469
  return {
398
470
  successful: true,
399
- messageId: response.id
471
+ messageId: response.id,
472
+ provider: "resend"
400
473
  };
401
474
  } catch (error) {
475
+ if (isAbortError(error) && options?.signal?.aborted) throw error;
402
476
  const errorMessage = error instanceof Error ? error.message : String(error);
403
- return {
404
- successful: false,
405
- errorMessages: [errorMessage]
406
- };
477
+ return createResendFailure(errorMessage, error);
407
478
  }
408
479
  }
409
480
  /**
@@ -512,14 +583,13 @@ var ResendTransport = class {
512
583
  const response = await this.httpClient.sendBatch(batchData, options?.signal, idempotencyKey);
513
584
  for (const result of response.data) yield {
514
585
  successful: true,
515
- messageId: result.id
586
+ messageId: result.id,
587
+ provider: "resend"
516
588
  };
517
589
  } catch (error) {
590
+ if (isAbortError(error) && options?.signal?.aborted) throw error;
518
591
  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
- };
592
+ for (let i = 0; i < messages.length; i++) yield createResendFailure(errorMessage, error);
523
593
  }
524
594
  }
525
595
  /**
@@ -549,6 +619,18 @@ var ResendTransport = class {
549
619
  return chunks;
550
620
  }
551
621
  };
622
+ function createResendFailure(message, error) {
623
+ if (error instanceof ResendApiError) return (0, __upyo_core.createFailedReceipt)(message, {
624
+ provider: "resend",
625
+ statusCode: error.statusCode,
626
+ retryAfterMilliseconds: error.retryAfterMilliseconds,
627
+ attempts: error.attempts
628
+ });
629
+ return (0, __upyo_core.createFailedReceipt)(message, { provider: "resend" });
630
+ }
631
+ function isAbortError(error) {
632
+ return error instanceof Error && error.name === "AbortError";
633
+ }
552
634
 
553
635
  //#endregion
554
636
  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
- * Resend API error class for handling API-specific errors.
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
- readonly statusCode: number;
281
- constructor(message: string, statusCode: number);
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
- * Resend API error class for handling API-specific errors.
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
- readonly statusCode: number;
281
- constructor(message: string, statusCode: number);
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
- * Resend API error class for handling API-specific errors.
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
- constructor(message, statusCode) {
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
- throw new ResendApiError(errorMessage || text || `HTTP ${response.status}`, response.status);
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) throw lastError;
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
  }
@@ -132,11 +163,19 @@ var ResendHttpClient = class {
132
163
  const controller = new AbortController();
133
164
  const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
134
165
  let signal;
166
+ let cleanup = () => {};
135
167
  if (options.signal) {
136
168
  const combinedController = new AbortController();
137
169
  const onAbort = () => combinedController.abort();
138
- options.signal.addEventListener("abort", onAbort, { once: true });
139
- controller.signal.addEventListener("abort", onAbort, { once: true });
170
+ if (options.signal.aborted || controller.signal.aborted) combinedController.abort();
171
+ else {
172
+ options.signal.addEventListener("abort", onAbort, { once: true });
173
+ controller.signal.addEventListener("abort", onAbort, { once: true });
174
+ }
175
+ cleanup = () => {
176
+ options.signal?.removeEventListener("abort", onAbort);
177
+ controller.signal.removeEventListener("abort", onAbort);
178
+ };
140
179
  signal = combinedController.signal;
141
180
  } else signal = controller.signal;
142
181
  try {
@@ -146,11 +185,21 @@ var ResendHttpClient = class {
146
185
  signal
147
186
  });
148
187
  return response;
188
+ } catch (error) {
189
+ if (isAbortError$1(error) && controller.signal.aborted && !options.signal?.aborted) throw new Error(`Resend API request timed out after ${this.config.timeout} ms.`);
190
+ throw error;
149
191
  } finally {
192
+ cleanup();
150
193
  clearTimeout(timeoutId);
151
194
  }
152
195
  }
153
196
  };
197
+ function isAbortError$1(error) {
198
+ return error instanceof Error && error.name === "AbortError";
199
+ }
200
+ function truncateErrorBody(text) {
201
+ return text.length > 500 ? `${text.slice(0, 500)}...` : text;
202
+ }
154
203
 
155
204
  //#endregion
156
205
  //#region src/message-converter.ts
@@ -338,6 +387,7 @@ function generateIdempotencyKey() {
338
387
  * ```
339
388
  */
340
389
  var ResendTransport = class {
390
+ id = "resend";
341
391
  /**
342
392
  * The resolved Resend configuration used by this transport.
343
393
  */
@@ -395,14 +445,13 @@ var ResendTransport = class {
395
445
  const response = await this.httpClient.sendMessage(emailData, options?.signal, idempotencyKey);
396
446
  return {
397
447
  successful: true,
398
- messageId: response.id
448
+ messageId: response.id,
449
+ provider: "resend"
399
450
  };
400
451
  } catch (error) {
452
+ if (isAbortError(error) && options?.signal?.aborted) throw error;
401
453
  const errorMessage = error instanceof Error ? error.message : String(error);
402
- return {
403
- successful: false,
404
- errorMessages: [errorMessage]
405
- };
454
+ return createResendFailure(errorMessage, error);
406
455
  }
407
456
  }
408
457
  /**
@@ -511,14 +560,13 @@ var ResendTransport = class {
511
560
  const response = await this.httpClient.sendBatch(batchData, options?.signal, idempotencyKey);
512
561
  for (const result of response.data) yield {
513
562
  successful: true,
514
- messageId: result.id
563
+ messageId: result.id,
564
+ provider: "resend"
515
565
  };
516
566
  } catch (error) {
567
+ if (isAbortError(error) && options?.signal?.aborted) throw error;
517
568
  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
- };
569
+ for (let i = 0; i < messages.length; i++) yield createResendFailure(errorMessage, error);
522
570
  }
523
571
  }
524
572
  /**
@@ -548,6 +596,18 @@ var ResendTransport = class {
548
596
  return chunks;
549
597
  }
550
598
  };
599
+ function createResendFailure(message, error) {
600
+ if (error instanceof ResendApiError) return createFailedReceipt(message, {
601
+ provider: "resend",
602
+ statusCode: error.statusCode,
603
+ retryAfterMilliseconds: error.retryAfterMilliseconds,
604
+ attempts: error.attempts
605
+ });
606
+ return createFailedReceipt(message, { provider: "resend" });
607
+ }
608
+ function isAbortError(error) {
609
+ return error instanceof Error && error.name === "AbortError";
610
+ }
551
611
 
552
612
  //#endregion
553
613
  export { ResendTransport };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@upyo/resend",
3
- "version": "0.5.0-dev.128",
3
+ "version": "0.5.0-dev.154",
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.128+6e126027"
56
+ "@upyo/core": "0.5.0-dev.154+2f72d353"
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
- "build": "tsdown",
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
  }