@upyo/ses 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 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
  /**
@@ -72,11 +96,15 @@ var SesHttpClient = class {
72
96
  } catch {
73
97
  errorData = { message: text || `HTTP ${response.status}` };
74
98
  }
75
- throw new SesApiError(errorData.message || `HTTP ${response.status}`, response.status, errorData.errors);
99
+ throw new SesApiError(errorData.message || `HTTP ${response.status}`, response.status, errorData.errors, (0, __upyo_core.parseRetryAfter)(response.headers.get("Retry-After")), attempt + 1);
76
100
  } catch (error) {
77
101
  lastError = error instanceof Error ? error : new Error(String(error));
102
+ if (isAbortError$1(error)) throw error;
78
103
  if (error instanceof SesApiError && error.statusCode && error.statusCode >= 400 && error.statusCode < 500) throw error;
79
- if (attempt === this.config.retries) throw error;
104
+ if (attempt === this.config.retries) {
105
+ if (lastError instanceof SesApiError) throw lastError;
106
+ throw new SesApiError(lastError.message, void 0, void 0, void 0, attempt + 1);
107
+ }
80
108
  const delay = Math.pow(2, attempt) * 1e3;
81
109
  await new Promise((resolve) => setTimeout(resolve, delay));
82
110
  }
@@ -92,18 +120,16 @@ var SesHttpClient = class {
92
120
  for (const [key, value] of Object.entries(this.config.headers)) signedHeaders.set(key, value);
93
121
  const controller = new AbortController();
94
122
  const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
95
- let signal = controller.signal;
96
- if (options.signal) {
97
- signal = options.signal;
98
- if (options.signal.aborted) controller.abort();
99
- else options.signal.addEventListener("abort", () => controller.abort());
100
- }
123
+ const signal = options.signal == null ? controller.signal : AbortSignal.any([controller.signal, options.signal]);
101
124
  try {
102
125
  return await globalThis.fetch(url, {
103
126
  ...options,
104
127
  headers: this.headersToRecord(signedHeaders),
105
128
  signal
106
129
  });
130
+ } catch (error) {
131
+ if (isAbortError$1(error) && controller.signal.aborted && !options.signal?.aborted) throw new Error(`SES API request timed out after ${this.config.timeout} ms.`);
132
+ throw error;
107
133
  } finally {
108
134
  clearTimeout(timeoutId);
109
135
  }
@@ -190,14 +216,21 @@ var SesHttpClient = class {
190
216
  return record;
191
217
  }
192
218
  };
219
+ function isAbortError$1(error) {
220
+ return error instanceof Error && error.name === "AbortError";
221
+ }
193
222
  var SesApiError = class extends Error {
194
223
  statusCode;
195
224
  errors;
196
- constructor(message, statusCode, errors) {
225
+ retryAfterMilliseconds;
226
+ attempts;
227
+ constructor(message, statusCode, errors, retryAfterMilliseconds, attempts) {
197
228
  super(message);
198
229
  this.name = "SesApiError";
199
230
  this.statusCode = statusCode;
200
231
  this.errors = errors;
232
+ this.retryAfterMilliseconds = retryAfterMilliseconds;
233
+ this.attempts = attempts;
201
234
  }
202
235
  };
203
236
 
@@ -311,6 +344,7 @@ function formatAddress(address) {
311
344
  * @since 0.2.0
312
345
  */
313
346
  var SesTransport = class {
347
+ id = "ses";
314
348
  /** Resolved configuration with defaults applied */
315
349
  config;
316
350
  /** HTTP client for SES API requests */
@@ -360,14 +394,13 @@ var SesTransport = class {
360
394
  const messageId = this.extractMessageId(response);
361
395
  return {
362
396
  successful: true,
363
- messageId
397
+ messageId,
398
+ provider: "ses"
364
399
  };
365
400
  } catch (error) {
401
+ if (isAbortError(error) && options?.signal?.aborted) throw error;
366
402
  const errorMessage = error instanceof Error ? error.message : String(error);
367
- return {
368
- successful: false,
369
- errorMessages: [errorMessage]
370
- };
403
+ return createSesFailure(errorMessage, error);
371
404
  }
372
405
  }
373
406
  /**
@@ -430,6 +463,19 @@ var SesTransport = class {
430
463
  return `ses-${timestamp}-${random}`;
431
464
  }
432
465
  };
466
+ function createSesFailure(message, error) {
467
+ if (error instanceof SesApiError) return (0, __upyo_core.createFailedReceipt)(message, {
468
+ provider: "ses",
469
+ statusCode: error.statusCode,
470
+ retryAfterMilliseconds: error.retryAfterMilliseconds,
471
+ providerDetails: error.errors,
472
+ attempts: error.attempts
473
+ });
474
+ return (0, __upyo_core.createFailedReceipt)(message, { provider: "ses" });
475
+ }
476
+ function isAbortError(error) {
477
+ return error instanceof Error && error.name === "AbortError";
478
+ }
433
479
 
434
480
  //#endregion
435
481
  exports.SesTransport = SesTransport;
package/dist/index.d.cts CHANGED
@@ -188,7 +188,8 @@ type ResolvedSesConfig = Required<Omit<SesConfig, "configurationSetName" | "defa
188
188
  *
189
189
  * @since 0.2.0
190
190
  */
191
- declare class SesTransport implements Transport {
191
+ declare class SesTransport implements Transport<"ses"> {
192
+ readonly id = "ses";
192
193
  /** Resolved configuration with defaults applied */
193
194
  config: ResolvedSesConfig;
194
195
  /** HTTP client for SES API requests */
@@ -226,7 +227,7 @@ declare class SesTransport implements Transport {
226
227
  * @param options Optional transport options (e.g., abort signal).
227
228
  * @returns A promise that resolves to a receipt with the result.
228
229
  */
229
- send(message: Message, options?: TransportOptions): Promise<Receipt>;
230
+ send(message: Message, options?: TransportOptions): Promise<Receipt<"ses">>;
230
231
  /**
231
232
  * Sends multiple email messages concurrently through Amazon SES.
232
233
  *
@@ -256,7 +257,7 @@ declare class SesTransport implements Transport {
256
257
  * @param options Optional transport options (e.g., abort signal).
257
258
  * @returns Individual receipts for each message as they complete.
258
259
  */
259
- sendMany(messages: Iterable<Message> | AsyncIterable<Message>, options?: TransportOptions): AsyncIterable<Receipt>;
260
+ sendMany(messages: Iterable<Message> | AsyncIterable<Message>, options?: TransportOptions): AsyncIterable<Receipt<"ses">>;
260
261
  private sendConcurrent;
261
262
  private extractMessageId;
262
263
  }
package/dist/index.d.ts CHANGED
@@ -188,7 +188,8 @@ type ResolvedSesConfig = Required<Omit<SesConfig, "configurationSetName" | "defa
188
188
  *
189
189
  * @since 0.2.0
190
190
  */
191
- declare class SesTransport implements Transport {
191
+ declare class SesTransport implements Transport<"ses"> {
192
+ readonly id = "ses";
192
193
  /** Resolved configuration with defaults applied */
193
194
  config: ResolvedSesConfig;
194
195
  /** HTTP client for SES API requests */
@@ -226,7 +227,7 @@ declare class SesTransport implements Transport {
226
227
  * @param options Optional transport options (e.g., abort signal).
227
228
  * @returns A promise that resolves to a receipt with the result.
228
229
  */
229
- send(message: Message, options?: TransportOptions): Promise<Receipt>;
230
+ send(message: Message, options?: TransportOptions): Promise<Receipt<"ses">>;
230
231
  /**
231
232
  * Sends multiple email messages concurrently through Amazon SES.
232
233
  *
@@ -256,7 +257,7 @@ declare class SesTransport implements Transport {
256
257
  * @param options Optional transport options (e.g., abort signal).
257
258
  * @returns Individual receipts for each message as they complete.
258
259
  */
259
- sendMany(messages: Iterable<Message> | AsyncIterable<Message>, options?: TransportOptions): AsyncIterable<Receipt>;
260
+ sendMany(messages: Iterable<Message> | AsyncIterable<Message>, options?: TransportOptions): AsyncIterable<Receipt<"ses">>;
260
261
  private sendConcurrent;
261
262
  private extractMessageId;
262
263
  }
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 SES configuration with default values applied.
@@ -71,11 +73,15 @@ var SesHttpClient = class {
71
73
  } catch {
72
74
  errorData = { message: text || `HTTP ${response.status}` };
73
75
  }
74
- throw new SesApiError(errorData.message || `HTTP ${response.status}`, response.status, errorData.errors);
76
+ throw new SesApiError(errorData.message || `HTTP ${response.status}`, response.status, errorData.errors, parseRetryAfter(response.headers.get("Retry-After")), attempt + 1);
75
77
  } catch (error) {
76
78
  lastError = error instanceof Error ? error : new Error(String(error));
79
+ if (isAbortError$1(error)) throw error;
77
80
  if (error instanceof SesApiError && error.statusCode && error.statusCode >= 400 && error.statusCode < 500) throw error;
78
- if (attempt === this.config.retries) throw error;
81
+ if (attempt === this.config.retries) {
82
+ if (lastError instanceof SesApiError) throw lastError;
83
+ throw new SesApiError(lastError.message, void 0, void 0, void 0, attempt + 1);
84
+ }
79
85
  const delay = Math.pow(2, attempt) * 1e3;
80
86
  await new Promise((resolve) => setTimeout(resolve, delay));
81
87
  }
@@ -91,18 +97,16 @@ var SesHttpClient = class {
91
97
  for (const [key, value] of Object.entries(this.config.headers)) signedHeaders.set(key, value);
92
98
  const controller = new AbortController();
93
99
  const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
94
- let signal = controller.signal;
95
- if (options.signal) {
96
- signal = options.signal;
97
- if (options.signal.aborted) controller.abort();
98
- else options.signal.addEventListener("abort", () => controller.abort());
99
- }
100
+ const signal = options.signal == null ? controller.signal : AbortSignal.any([controller.signal, options.signal]);
100
101
  try {
101
102
  return await globalThis.fetch(url, {
102
103
  ...options,
103
104
  headers: this.headersToRecord(signedHeaders),
104
105
  signal
105
106
  });
107
+ } catch (error) {
108
+ if (isAbortError$1(error) && controller.signal.aborted && !options.signal?.aborted) throw new Error(`SES API request timed out after ${this.config.timeout} ms.`);
109
+ throw error;
106
110
  } finally {
107
111
  clearTimeout(timeoutId);
108
112
  }
@@ -189,14 +193,21 @@ var SesHttpClient = class {
189
193
  return record;
190
194
  }
191
195
  };
196
+ function isAbortError$1(error) {
197
+ return error instanceof Error && error.name === "AbortError";
198
+ }
192
199
  var SesApiError = class extends Error {
193
200
  statusCode;
194
201
  errors;
195
- constructor(message, statusCode, errors) {
202
+ retryAfterMilliseconds;
203
+ attempts;
204
+ constructor(message, statusCode, errors, retryAfterMilliseconds, attempts) {
196
205
  super(message);
197
206
  this.name = "SesApiError";
198
207
  this.statusCode = statusCode;
199
208
  this.errors = errors;
209
+ this.retryAfterMilliseconds = retryAfterMilliseconds;
210
+ this.attempts = attempts;
200
211
  }
201
212
  };
202
213
 
@@ -310,6 +321,7 @@ function formatAddress(address) {
310
321
  * @since 0.2.0
311
322
  */
312
323
  var SesTransport = class {
324
+ id = "ses";
313
325
  /** Resolved configuration with defaults applied */
314
326
  config;
315
327
  /** HTTP client for SES API requests */
@@ -359,14 +371,13 @@ var SesTransport = class {
359
371
  const messageId = this.extractMessageId(response);
360
372
  return {
361
373
  successful: true,
362
- messageId
374
+ messageId,
375
+ provider: "ses"
363
376
  };
364
377
  } catch (error) {
378
+ if (isAbortError(error) && options?.signal?.aborted) throw error;
365
379
  const errorMessage = error instanceof Error ? error.message : String(error);
366
- return {
367
- successful: false,
368
- errorMessages: [errorMessage]
369
- };
380
+ return createSesFailure(errorMessage, error);
370
381
  }
371
382
  }
372
383
  /**
@@ -429,6 +440,19 @@ var SesTransport = class {
429
440
  return `ses-${timestamp}-${random}`;
430
441
  }
431
442
  };
443
+ function createSesFailure(message, error) {
444
+ if (error instanceof SesApiError) return createFailedReceipt(message, {
445
+ provider: "ses",
446
+ statusCode: error.statusCode,
447
+ retryAfterMilliseconds: error.retryAfterMilliseconds,
448
+ providerDetails: error.errors,
449
+ attempts: error.attempts
450
+ });
451
+ return createFailedReceipt(message, { provider: "ses" });
452
+ }
453
+ function isAbortError(error) {
454
+ return error instanceof Error && error.name === "AbortError";
455
+ }
432
456
 
433
457
  //#endregion
434
458
  export { SesTransport };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@upyo/ses",
3
- "version": "0.5.0-dev.136",
3
+ "version": "0.5.0-dev.156",
4
4
  "description": "Amazon SES transport for Upyo email library",
5
5
  "keywords": [
6
6
  "email",
@@ -55,18 +55,13 @@
55
55
  },
56
56
  "sideEffects": false,
57
57
  "peerDependencies": {
58
- "@upyo/core": "0.5.0-dev.136+adacf579"
58
+ "@upyo/core": "0.5.0-dev.156+edad9790"
59
59
  },
60
60
  "devDependencies": {
61
- "@dotenvx/dotenvx": "^1.47.3",
62
61
  "tsdown": "^0.12.7",
63
62
  "typescript": "5.8.3"
64
63
  },
65
64
  "scripts": {
66
- "build": "tsdown",
67
- "prepublish": "tsdown",
68
- "test": "tsdown && dotenvx run --ignore=MISSING_ENV_FILE -- node --experimental-transform-types --test",
69
- "test:bun": "tsdown && bun test --timeout=30000 --env-file=.env",
70
- "test:deno": "deno test --allow-env --allow-net --env-file=.env"
65
+ "prepublish": "mise run --no-deps :build"
71
66
  }
72
67
  }