@ayepi/aws 0.2.0 → 0.3.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/README.md CHANGED
@@ -49,6 +49,11 @@ const w = createWork({ queue, pubsub, store, work: [/* ... */] as const })
49
49
  and rethrown.
50
50
  - **You own the SDK clients.** The clients are optional peer deps — you construct and configure
51
51
  them (region, credentials, endpoint) and manage their lifecycle.
52
+ - **Connection pooling.** The SDK's default handler caps outbound connections at `maxSockets: 50`
53
+ per client — a common bottleneck for a busy S3+SQS work system. `@ayepi/aws/http` exports
54
+ `pooledRequestHandler({ maxSockets })` and `sharedHttpAgents(...)` — a keep-alive handler with a
55
+ higher cap to pass as `requestHandler`, or one shared pool across clients:
56
+ `new S3Client({ requestHandler: pooledRequestHandler({ maxSockets: 256 }) })`.
52
57
 
53
58
  ## Options
54
59
 
package/ayepi-aws.md CHANGED
@@ -205,6 +205,49 @@ deleted. Notes:
205
205
  - `store` is any [`@ayepi/files`](./ayepi-files.md) `FileStore` — typically an `s3Files({...})`,
206
206
  but a filesystem store or your own works too.
207
207
 
208
+ ### `@ayepi/aws/http` — a well-pooled request handler
209
+
210
+ The SDK's default `NodeHttpHandler` caps outbound connections at **`maxSockets: 50` per client**.
211
+ Under a busy work system (S3 + SQS + more, each its own client) that cap is often the first thing to
212
+ break — calls queue behind 50 sockets and latency balloons, with no error, just slowness. This
213
+ subpath builds a keep-alive handler (and shareable agents) with a higher cap.
214
+
215
+ ```ts
216
+ import { S3Client } from '@aws-sdk/client-s3';
217
+ import { SQSClient } from '@aws-sdk/client-sqs';
218
+ import { pooledRequestHandler, sharedHttpAgents } from '@ayepi/aws/http';
219
+
220
+ // One handler per client:
221
+ const s3 = new S3Client({ region, requestHandler: pooledRequestHandler({ maxSockets: 256 }) });
222
+
223
+ // ...or share ONE pool across many clients (bounds total sockets, better reuse):
224
+ const agents = sharedHttpAgents({ maxSockets: 512 });
225
+ const s3b = new S3Client({ requestHandler: pooledRequestHandler(agents) });
226
+ const sqs = new SQSClient({ requestHandler: pooledRequestHandler(agents) });
227
+ ```
228
+
229
+ ```ts
230
+ function pooledRequestHandler(opts?: PooledHandlerOptions): NodeHttpHandler
231
+ function sharedHttpAgents(opts?: PooledAgentsOptions): { httpAgent; httpsAgent }
232
+
233
+ interface PooledAgentsOptions {
234
+ readonly maxSockets?: number; // per host (default 128; SDK default 50)
235
+ readonly maxTotalSockets?: number; // across all hosts (default: unlimited)
236
+ readonly keepAlive?: boolean; // default true — avoids TCP churn / port exhaustion
237
+ readonly keepAliveMsecs?: number; // default 1000
238
+ }
239
+ interface PooledHandlerOptions extends PooledAgentsOptions {
240
+ readonly connectionTimeout?: number; // ms
241
+ readonly requestTimeout?: number; // ms
242
+ readonly httpAgent?: HttpAgent; // bring your own (overrides the pool knobs)
243
+ readonly httpsAgent?: HttpsAgent;
244
+ }
245
+ ```
246
+
247
+ Keep-alive is on by default so connections are reused instead of churned — which also sidesteps
248
+ ephemeral-port / `TIME_WAIT` exhaustion under sustained load. Requires `@smithy/node-http-handler`
249
+ (an optional peer, already a transitive dependency of the AWS SDK clients).
250
+
208
251
  ### `@ayepi/aws` (root) — the retry seam
209
252
 
210
253
  The root subpath exports the shared resilience machinery both backends build on. You rarely call
package/dist/http.cjs ADDED
@@ -0,0 +1,70 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let node_http = require("node:http");
3
+ let node_https = require("node:https");
4
+ let _smithy_node_http_handler = require("@smithy/node-http-handler");
5
+ //#region src/http.ts
6
+ /**
7
+ * # @ayepi/aws/http — a well-pooled request handler for the AWS SDK
8
+ *
9
+ * The AWS SDK v3's default `NodeHttpHandler` caps outbound connections at **`maxSockets: 50` per
10
+ * client**. Under a busy work system (S3 + SQS + more, each its own client) that cap is often the
11
+ * first thing to break: calls queue behind 50 sockets and latency balloons — with no error, just
12
+ * mysterious slowness. This module builds a handler (and shareable agents) with keep-alive on and a
13
+ * higher cap, to pass as `requestHandler` to your clients.
14
+ *
15
+ * ```ts
16
+ * import { S3Client } from '@aws-sdk/client-s3';
17
+ * import { SQSClient } from '@aws-sdk/client-sqs';
18
+ * import { pooledRequestHandler, sharedHttpAgents } from '@ayepi/aws/http';
19
+ *
20
+ * // One handler per client:
21
+ * const s3 = new S3Client({ region, requestHandler: pooledRequestHandler({ maxSockets: 256 }) });
22
+ *
23
+ * // ...or share ONE pool across many clients (fewer total sockets, better reuse):
24
+ * const agents = sharedHttpAgents({ maxSockets: 512 });
25
+ * const s3b = new S3Client({ requestHandler: pooledRequestHandler(agents) });
26
+ * const sqs = new SQSClient({ requestHandler: pooledRequestHandler(agents) });
27
+ * ```
28
+ *
29
+ * Keep-alive is on by default so connections are reused instead of churned — which also avoids
30
+ * ephemeral-port / `TIME_WAIT` exhaustion under sustained load. Requires `@smithy/node-http-handler`
31
+ * (a transitive dependency of the AWS SDK clients).
32
+ *
33
+ * @module
34
+ */
35
+ /** Agent constructor options derived from the pool knobs. */
36
+ function agentConf(opts) {
37
+ return {
38
+ keepAlive: opts.keepAlive ?? true,
39
+ keepAliveMsecs: opts.keepAliveMsecs ?? 1e3,
40
+ maxSockets: opts.maxSockets ?? 128,
41
+ maxTotalSockets: opts.maxTotalSockets
42
+ };
43
+ }
44
+ /**
45
+ * A pair of keep-alive `node:http` / `node:https` agents you can **share across many AWS clients**
46
+ * (pass to {@link pooledRequestHandler}). Sharing one pair bounds total sockets across S3/SQS/etc.
47
+ */
48
+ function sharedHttpAgents(opts = {}) {
49
+ const conf = agentConf(opts);
50
+ return {
51
+ httpAgent: new node_http.Agent(conf),
52
+ httpsAgent: new node_https.Agent(conf)
53
+ };
54
+ }
55
+ /**
56
+ * Build a `NodeHttpHandler` with keep-alive and a higher socket cap than the SDK default — pass it
57
+ * as `requestHandler` to an `S3Client`/`SQSClient`. Reuse one handler (or one {@link sharedHttpAgents}
58
+ * pair) across clients to bound total sockets.
59
+ */
60
+ function pooledRequestHandler(opts = {}) {
61
+ return new _smithy_node_http_handler.NodeHttpHandler({
62
+ httpAgent: opts.httpAgent ?? new node_http.Agent(agentConf(opts)),
63
+ httpsAgent: opts.httpsAgent ?? new node_https.Agent(agentConf(opts)),
64
+ connectionTimeout: opts.connectionTimeout,
65
+ requestTimeout: opts.requestTimeout
66
+ });
67
+ }
68
+ //#endregion
69
+ exports.pooledRequestHandler = pooledRequestHandler;
70
+ exports.sharedHttpAgents = sharedHttpAgents;
@@ -0,0 +1,44 @@
1
+ import { Agent } from "node:http";
2
+ import { Agent as Agent$1 } from "node:https";
3
+ import { NodeHttpHandler } from "@smithy/node-http-handler";
4
+
5
+ //#region src/http.d.ts
6
+
7
+ /** Connection-pool knobs shared by the http + https agents. */
8
+ interface PooledAgentsOptions {
9
+ /** Max sockets per host (default `128`; the SDK default is `50`). */
10
+ readonly maxSockets?: number;
11
+ /** Max sockets across all hosts (default: unlimited). */
12
+ readonly maxTotalSockets?: number;
13
+ /** Reuse connections (default `true`). Keep-alive avoids TCP churn and port exhaustion. */
14
+ readonly keepAlive?: boolean;
15
+ /** Idle time before a kept-alive socket may close (ms, default `1000`). */
16
+ readonly keepAliveMsecs?: number;
17
+ }
18
+ /** Options for {@link pooledRequestHandler}: the pool knobs plus timeouts and bring-your-own agents. */
19
+ interface PooledHandlerOptions extends PooledAgentsOptions {
20
+ /** Socket connect timeout (ms). */
21
+ readonly connectionTimeout?: number;
22
+ /** Time to wait for a response (ms). */
23
+ readonly requestTimeout?: number;
24
+ /** Bring your own http agent (overrides the pool knobs for http). */
25
+ readonly httpAgent?: Agent;
26
+ /** Bring your own https agent (overrides the pool knobs for https). */
27
+ readonly httpsAgent?: Agent$1;
28
+ }
29
+ /**
30
+ * A pair of keep-alive `node:http` / `node:https` agents you can **share across many AWS clients**
31
+ * (pass to {@link pooledRequestHandler}). Sharing one pair bounds total sockets across S3/SQS/etc.
32
+ */
33
+ declare function sharedHttpAgents(opts?: PooledAgentsOptions): {
34
+ httpAgent: Agent;
35
+ httpsAgent: Agent$1;
36
+ };
37
+ /**
38
+ * Build a `NodeHttpHandler` with keep-alive and a higher socket cap than the SDK default — pass it
39
+ * as `requestHandler` to an `S3Client`/`SQSClient`. Reuse one handler (or one {@link sharedHttpAgents}
40
+ * pair) across clients to bound total sockets.
41
+ */
42
+ declare function pooledRequestHandler(opts?: PooledHandlerOptions): NodeHttpHandler;
43
+ //#endregion
44
+ export { PooledAgentsOptions, PooledHandlerOptions, pooledRequestHandler, sharedHttpAgents };
package/dist/http.d.ts ADDED
@@ -0,0 +1,44 @@
1
+ import { Agent } from "node:http";
2
+ import { Agent as Agent$1 } from "node:https";
3
+ import { NodeHttpHandler } from "@smithy/node-http-handler";
4
+
5
+ //#region src/http.d.ts
6
+
7
+ /** Connection-pool knobs shared by the http + https agents. */
8
+ interface PooledAgentsOptions {
9
+ /** Max sockets per host (default `128`; the SDK default is `50`). */
10
+ readonly maxSockets?: number;
11
+ /** Max sockets across all hosts (default: unlimited). */
12
+ readonly maxTotalSockets?: number;
13
+ /** Reuse connections (default `true`). Keep-alive avoids TCP churn and port exhaustion. */
14
+ readonly keepAlive?: boolean;
15
+ /** Idle time before a kept-alive socket may close (ms, default `1000`). */
16
+ readonly keepAliveMsecs?: number;
17
+ }
18
+ /** Options for {@link pooledRequestHandler}: the pool knobs plus timeouts and bring-your-own agents. */
19
+ interface PooledHandlerOptions extends PooledAgentsOptions {
20
+ /** Socket connect timeout (ms). */
21
+ readonly connectionTimeout?: number;
22
+ /** Time to wait for a response (ms). */
23
+ readonly requestTimeout?: number;
24
+ /** Bring your own http agent (overrides the pool knobs for http). */
25
+ readonly httpAgent?: Agent;
26
+ /** Bring your own https agent (overrides the pool knobs for https). */
27
+ readonly httpsAgent?: Agent$1;
28
+ }
29
+ /**
30
+ * A pair of keep-alive `node:http` / `node:https` agents you can **share across many AWS clients**
31
+ * (pass to {@link pooledRequestHandler}). Sharing one pair bounds total sockets across S3/SQS/etc.
32
+ */
33
+ declare function sharedHttpAgents(opts?: PooledAgentsOptions): {
34
+ httpAgent: Agent;
35
+ httpsAgent: Agent$1;
36
+ };
37
+ /**
38
+ * Build a `NodeHttpHandler` with keep-alive and a higher socket cap than the SDK default — pass it
39
+ * as `requestHandler` to an `S3Client`/`SQSClient`. Reuse one handler (or one {@link sharedHttpAgents}
40
+ * pair) across clients to bound total sockets.
41
+ */
42
+ declare function pooledRequestHandler(opts?: PooledHandlerOptions): NodeHttpHandler;
43
+ //#endregion
44
+ export { PooledAgentsOptions, PooledHandlerOptions, pooledRequestHandler, sharedHttpAgents };
package/dist/http.js ADDED
@@ -0,0 +1,68 @@
1
+ import { Agent } from "node:http";
2
+ import { Agent as Agent$1 } from "node:https";
3
+ import { NodeHttpHandler } from "@smithy/node-http-handler";
4
+ //#region src/http.ts
5
+ /**
6
+ * # @ayepi/aws/http — a well-pooled request handler for the AWS SDK
7
+ *
8
+ * The AWS SDK v3's default `NodeHttpHandler` caps outbound connections at **`maxSockets: 50` per
9
+ * client**. Under a busy work system (S3 + SQS + more, each its own client) that cap is often the
10
+ * first thing to break: calls queue behind 50 sockets and latency balloons — with no error, just
11
+ * mysterious slowness. This module builds a handler (and shareable agents) with keep-alive on and a
12
+ * higher cap, to pass as `requestHandler` to your clients.
13
+ *
14
+ * ```ts
15
+ * import { S3Client } from '@aws-sdk/client-s3';
16
+ * import { SQSClient } from '@aws-sdk/client-sqs';
17
+ * import { pooledRequestHandler, sharedHttpAgents } from '@ayepi/aws/http';
18
+ *
19
+ * // One handler per client:
20
+ * const s3 = new S3Client({ region, requestHandler: pooledRequestHandler({ maxSockets: 256 }) });
21
+ *
22
+ * // ...or share ONE pool across many clients (fewer total sockets, better reuse):
23
+ * const agents = sharedHttpAgents({ maxSockets: 512 });
24
+ * const s3b = new S3Client({ requestHandler: pooledRequestHandler(agents) });
25
+ * const sqs = new SQSClient({ requestHandler: pooledRequestHandler(agents) });
26
+ * ```
27
+ *
28
+ * Keep-alive is on by default so connections are reused instead of churned — which also avoids
29
+ * ephemeral-port / `TIME_WAIT` exhaustion under sustained load. Requires `@smithy/node-http-handler`
30
+ * (a transitive dependency of the AWS SDK clients).
31
+ *
32
+ * @module
33
+ */
34
+ /** Agent constructor options derived from the pool knobs. */
35
+ function agentConf(opts) {
36
+ return {
37
+ keepAlive: opts.keepAlive ?? true,
38
+ keepAliveMsecs: opts.keepAliveMsecs ?? 1e3,
39
+ maxSockets: opts.maxSockets ?? 128,
40
+ maxTotalSockets: opts.maxTotalSockets
41
+ };
42
+ }
43
+ /**
44
+ * A pair of keep-alive `node:http` / `node:https` agents you can **share across many AWS clients**
45
+ * (pass to {@link pooledRequestHandler}). Sharing one pair bounds total sockets across S3/SQS/etc.
46
+ */
47
+ function sharedHttpAgents(opts = {}) {
48
+ const conf = agentConf(opts);
49
+ return {
50
+ httpAgent: new Agent(conf),
51
+ httpsAgent: new Agent$1(conf)
52
+ };
53
+ }
54
+ /**
55
+ * Build a `NodeHttpHandler` with keep-alive and a higher socket cap than the SDK default — pass it
56
+ * as `requestHandler` to an `S3Client`/`SQSClient`. Reuse one handler (or one {@link sharedHttpAgents}
57
+ * pair) across clients to bound total sockets.
58
+ */
59
+ function pooledRequestHandler(opts = {}) {
60
+ return new NodeHttpHandler({
61
+ httpAgent: opts.httpAgent ?? new Agent(agentConf(opts)),
62
+ httpsAgent: opts.httpsAgent ?? new Agent$1(agentConf(opts)),
63
+ connectionTimeout: opts.connectionTimeout,
64
+ requestTimeout: opts.requestTimeout
65
+ });
66
+ }
67
+ //#endregion
68
+ export { pooledRequestHandler, sharedHttpAgents };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ayepi/aws",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "AWS backends for ayepi — an SQS @ayepi/work queue (large payloads offloaded to S3) and an S3 @ayepi/files store, all retry-wrapped for throttle resilience",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
@@ -52,6 +52,16 @@
52
52
  "default": "./dist/sqs.cjs"
53
53
  }
54
54
  },
55
+ "./http": {
56
+ "import": {
57
+ "types": "./dist/http.d.ts",
58
+ "default": "./dist/http.js"
59
+ },
60
+ "require": {
61
+ "types": "./dist/http.d.cts",
62
+ "default": "./dist/http.cjs"
63
+ }
64
+ },
55
65
  "./package.json": "./package.json"
56
66
  },
57
67
  "engines": {
@@ -62,9 +72,10 @@
62
72
  "@aws-sdk/client-sqs": "^3",
63
73
  "@aws-sdk/lib-storage": "^3",
64
74
  "@aws-sdk/s3-request-presigner": "^3",
65
- "@ayepi/core": "^0.2.0",
66
- "@ayepi/work": "^0.2.0",
67
- "@ayepi/files": "^0.2.0"
75
+ "@smithy/node-http-handler": "^4",
76
+ "@ayepi/core": "^0.3.0",
77
+ "@ayepi/work": "^0.3.0",
78
+ "@ayepi/files": "^0.3.0"
68
79
  },
69
80
  "peerDependenciesMeta": {
70
81
  "@ayepi/work": {
@@ -84,6 +95,9 @@
84
95
  },
85
96
  "@aws-sdk/s3-request-presigner": {
86
97
  "optional": true
98
+ },
99
+ "@smithy/node-http-handler": {
100
+ "optional": true
87
101
  }
88
102
  },
89
103
  "devDependencies": {
@@ -91,15 +105,16 @@
91
105
  "@aws-sdk/client-sqs": "^3.700.0",
92
106
  "@aws-sdk/lib-storage": "^3.700.0",
93
107
  "@aws-sdk/s3-request-presigner": "^3.700.0",
108
+ "@smithy/node-http-handler": "^4.0.0",
94
109
  "@vitest/coverage-v8": "^2.1.8",
95
110
  "publint": "^0.3.0",
96
111
  "testcontainers": "^10.13.0",
97
112
  "tsdown": "^0.12.0",
98
113
  "vitest": "^2.1.8",
99
114
  "zod": "^4.4.3",
100
- "@ayepi/core": "0.2.0",
101
- "@ayepi/work": "0.2.0",
102
- "@ayepi/files": "0.2.0"
115
+ "@ayepi/core": "0.3.0",
116
+ "@ayepi/files": "0.3.0",
117
+ "@ayepi/work": "0.3.0"
103
118
  },
104
119
  "keywords": [
105
120
  "ayepi",