@upyo/ses 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 +78 -4
- package/dist/index.js +78 -4
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -80,6 +80,16 @@ var SesHttpClient = class {
|
|
|
80
80
|
signal
|
|
81
81
|
});
|
|
82
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* Makes an HTTP request to the SES API with retry logic.
|
|
85
|
+
*
|
|
86
|
+
* @param url The URL to make the request to.
|
|
87
|
+
* @param options Fetch options.
|
|
88
|
+
* @returns Promise that resolves to the parsed response.
|
|
89
|
+
* @throws {DOMException} If the caller aborts the request.
|
|
90
|
+
* @throws {SesApiError} If SES returns a client error or all retry attempts
|
|
91
|
+
* are exhausted.
|
|
92
|
+
*/
|
|
83
93
|
async makeRequest(url, options) {
|
|
84
94
|
let lastError = null;
|
|
85
95
|
for (let attempt = 0; attempt <= this.config.retries; attempt++) try {
|
|
@@ -106,10 +116,19 @@ var SesHttpClient = class {
|
|
|
106
116
|
throw new SesApiError(lastError.message, void 0, void 0, void 0, attempt + 1);
|
|
107
117
|
}
|
|
108
118
|
const delay = Math.pow(2, attempt) * 1e3;
|
|
109
|
-
await
|
|
119
|
+
await sleep(delay, options.signal);
|
|
110
120
|
}
|
|
111
121
|
throw lastError || /* @__PURE__ */ new Error("Request failed after all retries");
|
|
112
122
|
}
|
|
123
|
+
/**
|
|
124
|
+
* Makes a signed fetch request to the SES API.
|
|
125
|
+
*
|
|
126
|
+
* @param url The URL to make the request to.
|
|
127
|
+
* @param options Fetch options.
|
|
128
|
+
* @returns Promise that resolves to the fetch response.
|
|
129
|
+
* @throws {Error} If the configured request timeout is reached.
|
|
130
|
+
* @throws {DOMException} If the caller aborts the request.
|
|
131
|
+
*/
|
|
113
132
|
async fetchWithAuth(url, options) {
|
|
114
133
|
const credentials = await this.getCredentials();
|
|
115
134
|
const headers = new Headers(options.headers);
|
|
@@ -120,17 +139,18 @@ var SesHttpClient = class {
|
|
|
120
139
|
for (const [key, value] of Object.entries(this.config.headers)) signedHeaders.set(key, value);
|
|
121
140
|
const controller = new AbortController();
|
|
122
141
|
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
|
|
123
|
-
const
|
|
142
|
+
const combinedSignal = combineSignals(controller.signal, options.signal);
|
|
124
143
|
try {
|
|
125
144
|
return await globalThis.fetch(url, {
|
|
126
145
|
...options,
|
|
127
146
|
headers: this.headersToRecord(signedHeaders),
|
|
128
|
-
signal
|
|
147
|
+
signal: combinedSignal.signal
|
|
129
148
|
});
|
|
130
149
|
} catch (error) {
|
|
131
150
|
if (isAbortError$1(error) && controller.signal.aborted && !options.signal?.aborted) throw new Error(`SES API request timed out after ${this.config.timeout} ms.`);
|
|
132
151
|
throw error;
|
|
133
152
|
} finally {
|
|
153
|
+
combinedSignal.cleanup();
|
|
134
154
|
clearTimeout(timeoutId);
|
|
135
155
|
}
|
|
136
156
|
}
|
|
@@ -219,6 +239,57 @@ var SesHttpClient = class {
|
|
|
219
239
|
function isAbortError$1(error) {
|
|
220
240
|
return error instanceof Error && error.name === "AbortError";
|
|
221
241
|
}
|
|
242
|
+
function sleep(milliseconds, signal) {
|
|
243
|
+
if (signal?.aborted) return Promise.reject(createAbortError(signal));
|
|
244
|
+
return new Promise((resolve, reject) => {
|
|
245
|
+
function abort() {
|
|
246
|
+
clearTimeout(timeoutId);
|
|
247
|
+
signal?.removeEventListener("abort", abort);
|
|
248
|
+
reject(createAbortError(signal));
|
|
249
|
+
}
|
|
250
|
+
const timeoutId = setTimeout(() => {
|
|
251
|
+
signal?.removeEventListener("abort", abort);
|
|
252
|
+
resolve();
|
|
253
|
+
}, milliseconds);
|
|
254
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
function createAbortError(signal) {
|
|
258
|
+
return signal?.reason ?? new DOMException("The operation was aborted.", "AbortError");
|
|
259
|
+
}
|
|
260
|
+
function combineSignals(timeoutSignal, externalSignal) {
|
|
261
|
+
if (externalSignal == null) return {
|
|
262
|
+
signal: timeoutSignal,
|
|
263
|
+
cleanup: () => {}
|
|
264
|
+
};
|
|
265
|
+
if (typeof AbortSignal.any === "function") return {
|
|
266
|
+
signal: AbortSignal.any([timeoutSignal, externalSignal]),
|
|
267
|
+
cleanup: () => {}
|
|
268
|
+
};
|
|
269
|
+
const controller = new AbortController();
|
|
270
|
+
const cleanup = () => {
|
|
271
|
+
timeoutSignal.removeEventListener("abort", abortFromTimeout);
|
|
272
|
+
externalSignal.removeEventListener("abort", abortFromExternal);
|
|
273
|
+
};
|
|
274
|
+
const abortFromTimeout = () => {
|
|
275
|
+
cleanup();
|
|
276
|
+
controller.abort(timeoutSignal.reason);
|
|
277
|
+
};
|
|
278
|
+
const abortFromExternal = () => {
|
|
279
|
+
cleanup();
|
|
280
|
+
controller.abort(externalSignal.reason);
|
|
281
|
+
};
|
|
282
|
+
if (timeoutSignal.aborted) controller.abort(timeoutSignal.reason);
|
|
283
|
+
else if (externalSignal.aborted) controller.abort(externalSignal.reason);
|
|
284
|
+
else {
|
|
285
|
+
timeoutSignal.addEventListener("abort", abortFromTimeout, { once: true });
|
|
286
|
+
externalSignal.addEventListener("abort", abortFromExternal, { once: true });
|
|
287
|
+
}
|
|
288
|
+
return {
|
|
289
|
+
signal: controller.signal,
|
|
290
|
+
cleanup
|
|
291
|
+
};
|
|
292
|
+
}
|
|
222
293
|
var SesApiError = class extends Error {
|
|
223
294
|
statusCode;
|
|
224
295
|
errors;
|
|
@@ -398,7 +469,7 @@ var SesTransport = class {
|
|
|
398
469
|
provider: "ses"
|
|
399
470
|
};
|
|
400
471
|
} catch (error) {
|
|
401
|
-
if (
|
|
472
|
+
if (isCallerAbort(error, options?.signal)) throw error;
|
|
402
473
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
403
474
|
return createSesFailure(errorMessage, error);
|
|
404
475
|
}
|
|
@@ -473,6 +544,9 @@ function createSesFailure(message, error) {
|
|
|
473
544
|
});
|
|
474
545
|
return (0, __upyo_core.createFailedReceipt)(message, { provider: "ses" });
|
|
475
546
|
}
|
|
547
|
+
function isCallerAbort(error, signal) {
|
|
548
|
+
return signal?.aborted === true && (isAbortError(error) || error === signal.reason);
|
|
549
|
+
}
|
|
476
550
|
function isAbortError(error) {
|
|
477
551
|
return error instanceof Error && error.name === "AbortError";
|
|
478
552
|
}
|
package/dist/index.js
CHANGED
|
@@ -57,6 +57,16 @@ var SesHttpClient = class {
|
|
|
57
57
|
signal
|
|
58
58
|
});
|
|
59
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* Makes an HTTP request to the SES API with retry logic.
|
|
62
|
+
*
|
|
63
|
+
* @param url The URL to make the request to.
|
|
64
|
+
* @param options Fetch options.
|
|
65
|
+
* @returns Promise that resolves to the parsed response.
|
|
66
|
+
* @throws {DOMException} If the caller aborts the request.
|
|
67
|
+
* @throws {SesApiError} If SES returns a client error or all retry attempts
|
|
68
|
+
* are exhausted.
|
|
69
|
+
*/
|
|
60
70
|
async makeRequest(url, options) {
|
|
61
71
|
let lastError = null;
|
|
62
72
|
for (let attempt = 0; attempt <= this.config.retries; attempt++) try {
|
|
@@ -83,10 +93,19 @@ var SesHttpClient = class {
|
|
|
83
93
|
throw new SesApiError(lastError.message, void 0, void 0, void 0, attempt + 1);
|
|
84
94
|
}
|
|
85
95
|
const delay = Math.pow(2, attempt) * 1e3;
|
|
86
|
-
await
|
|
96
|
+
await sleep(delay, options.signal);
|
|
87
97
|
}
|
|
88
98
|
throw lastError || /* @__PURE__ */ new Error("Request failed after all retries");
|
|
89
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* Makes a signed fetch request to the SES API.
|
|
102
|
+
*
|
|
103
|
+
* @param url The URL to make the request to.
|
|
104
|
+
* @param options Fetch options.
|
|
105
|
+
* @returns Promise that resolves to the fetch response.
|
|
106
|
+
* @throws {Error} If the configured request timeout is reached.
|
|
107
|
+
* @throws {DOMException} If the caller aborts the request.
|
|
108
|
+
*/
|
|
90
109
|
async fetchWithAuth(url, options) {
|
|
91
110
|
const credentials = await this.getCredentials();
|
|
92
111
|
const headers = new Headers(options.headers);
|
|
@@ -97,17 +116,18 @@ var SesHttpClient = class {
|
|
|
97
116
|
for (const [key, value] of Object.entries(this.config.headers)) signedHeaders.set(key, value);
|
|
98
117
|
const controller = new AbortController();
|
|
99
118
|
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
|
|
100
|
-
const
|
|
119
|
+
const combinedSignal = combineSignals(controller.signal, options.signal);
|
|
101
120
|
try {
|
|
102
121
|
return await globalThis.fetch(url, {
|
|
103
122
|
...options,
|
|
104
123
|
headers: this.headersToRecord(signedHeaders),
|
|
105
|
-
signal
|
|
124
|
+
signal: combinedSignal.signal
|
|
106
125
|
});
|
|
107
126
|
} catch (error) {
|
|
108
127
|
if (isAbortError$1(error) && controller.signal.aborted && !options.signal?.aborted) throw new Error(`SES API request timed out after ${this.config.timeout} ms.`);
|
|
109
128
|
throw error;
|
|
110
129
|
} finally {
|
|
130
|
+
combinedSignal.cleanup();
|
|
111
131
|
clearTimeout(timeoutId);
|
|
112
132
|
}
|
|
113
133
|
}
|
|
@@ -196,6 +216,57 @@ var SesHttpClient = class {
|
|
|
196
216
|
function isAbortError$1(error) {
|
|
197
217
|
return error instanceof Error && error.name === "AbortError";
|
|
198
218
|
}
|
|
219
|
+
function sleep(milliseconds, signal) {
|
|
220
|
+
if (signal?.aborted) return Promise.reject(createAbortError(signal));
|
|
221
|
+
return new Promise((resolve, reject) => {
|
|
222
|
+
function abort() {
|
|
223
|
+
clearTimeout(timeoutId);
|
|
224
|
+
signal?.removeEventListener("abort", abort);
|
|
225
|
+
reject(createAbortError(signal));
|
|
226
|
+
}
|
|
227
|
+
const timeoutId = setTimeout(() => {
|
|
228
|
+
signal?.removeEventListener("abort", abort);
|
|
229
|
+
resolve();
|
|
230
|
+
}, milliseconds);
|
|
231
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
function createAbortError(signal) {
|
|
235
|
+
return signal?.reason ?? new DOMException("The operation was aborted.", "AbortError");
|
|
236
|
+
}
|
|
237
|
+
function combineSignals(timeoutSignal, externalSignal) {
|
|
238
|
+
if (externalSignal == null) return {
|
|
239
|
+
signal: timeoutSignal,
|
|
240
|
+
cleanup: () => {}
|
|
241
|
+
};
|
|
242
|
+
if (typeof AbortSignal.any === "function") return {
|
|
243
|
+
signal: AbortSignal.any([timeoutSignal, externalSignal]),
|
|
244
|
+
cleanup: () => {}
|
|
245
|
+
};
|
|
246
|
+
const controller = new AbortController();
|
|
247
|
+
const cleanup = () => {
|
|
248
|
+
timeoutSignal.removeEventListener("abort", abortFromTimeout);
|
|
249
|
+
externalSignal.removeEventListener("abort", abortFromExternal);
|
|
250
|
+
};
|
|
251
|
+
const abortFromTimeout = () => {
|
|
252
|
+
cleanup();
|
|
253
|
+
controller.abort(timeoutSignal.reason);
|
|
254
|
+
};
|
|
255
|
+
const abortFromExternal = () => {
|
|
256
|
+
cleanup();
|
|
257
|
+
controller.abort(externalSignal.reason);
|
|
258
|
+
};
|
|
259
|
+
if (timeoutSignal.aborted) controller.abort(timeoutSignal.reason);
|
|
260
|
+
else if (externalSignal.aborted) controller.abort(externalSignal.reason);
|
|
261
|
+
else {
|
|
262
|
+
timeoutSignal.addEventListener("abort", abortFromTimeout, { once: true });
|
|
263
|
+
externalSignal.addEventListener("abort", abortFromExternal, { once: true });
|
|
264
|
+
}
|
|
265
|
+
return {
|
|
266
|
+
signal: controller.signal,
|
|
267
|
+
cleanup
|
|
268
|
+
};
|
|
269
|
+
}
|
|
199
270
|
var SesApiError = class extends Error {
|
|
200
271
|
statusCode;
|
|
201
272
|
errors;
|
|
@@ -375,7 +446,7 @@ var SesTransport = class {
|
|
|
375
446
|
provider: "ses"
|
|
376
447
|
};
|
|
377
448
|
} catch (error) {
|
|
378
|
-
if (
|
|
449
|
+
if (isCallerAbort(error, options?.signal)) throw error;
|
|
379
450
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
380
451
|
return createSesFailure(errorMessage, error);
|
|
381
452
|
}
|
|
@@ -450,6 +521,9 @@ function createSesFailure(message, error) {
|
|
|
450
521
|
});
|
|
451
522
|
return createFailedReceipt(message, { provider: "ses" });
|
|
452
523
|
}
|
|
524
|
+
function isCallerAbort(error, signal) {
|
|
525
|
+
return signal?.aborted === true && (isAbortError(error) || error === signal.reason);
|
|
526
|
+
}
|
|
453
527
|
function isAbortError(error) {
|
|
454
528
|
return error instanceof Error && error.name === "AbortError";
|
|
455
529
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@upyo/ses",
|
|
3
|
-
"version": "0.5.0-dev.
|
|
3
|
+
"version": "0.5.0-dev.168",
|
|
4
4
|
"description": "Amazon SES transport for Upyo email library",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"email",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
},
|
|
56
56
|
"sideEffects": false,
|
|
57
57
|
"peerDependencies": {
|
|
58
|
-
"@upyo/core": "0.5.0-dev.
|
|
58
|
+
"@upyo/core": "0.5.0-dev.168+1e808a3a"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
61
|
"tsdown": "^0.12.7",
|