lambda-deadline-middleware 0.0.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/LICENSE +21 -0
- package/README.md +278 -0
- package/SECURITY.md +95 -0
- package/dist/config.d.ts +4 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +15 -0
- package/dist/config.js.map +1 -0
- package/dist/context-store.d.ts +7 -0
- package/dist/context-store.d.ts.map +1 -0
- package/dist/context-store.js +24 -0
- package/dist/context-store.js.map +1 -0
- package/dist/error.d.ts +17 -0
- package/dist/error.d.ts.map +1 -0
- package/dist/error.js +21 -0
- package/dist/error.js.map +1 -0
- package/dist/handler-wrapper.d.ts +13 -0
- package/dist/handler-wrapper.d.ts.map +1 -0
- package/dist/handler-wrapper.js +8 -0
- package/dist/handler-wrapper.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware.d.ts +12 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/middleware.js +76 -0
- package/dist/middleware.js.map +1 -0
- package/dist/registration.d.ts +10 -0
- package/dist/registration.d.ts.map +1 -0
- package/dist/registration.js +23 -0
- package/dist/registration.js.map +1 -0
- package/dist/telemetry.d.ts +5 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +82 -0
- package/dist/telemetry.js.map +1 -0
- package/dist/types.d.ts +33 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +19 -0
- package/dist/types.js.map +1 -0
- package/package.json +73 -0
- package/src/config.ts +16 -0
- package/src/context-store.ts +36 -0
- package/src/error.ts +34 -0
- package/src/handler-wrapper.ts +19 -0
- package/src/index.ts +18 -0
- package/src/middleware.ts +109 -0
- package/src/registration.ts +36 -0
- package/src/telemetry.ts +129 -0
- package/src/types.ts +50 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 lambda-deadline-middleware contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
<!-- SPDX-FileCopyrightText: 2024 lambda-deadline-middleware contributors -->
|
|
2
|
+
<!-- SPDX-License-Identifier: MIT -->
|
|
3
|
+
|
|
4
|
+
# lambda-deadline-middleware
|
|
5
|
+
|
|
6
|
+
Zero-dependency AWS SDK v3 middleware that automatically propagates Lambda execution deadlines to outgoing SDK calls via `AbortController`-based timeouts.
|
|
7
|
+
|
|
8
|
+
When an AWS SDK call hangs inside a Lambda function, the runtime terminates the process at the configured timeout — destroying in-flight OpenTelemetry/X-Ray spans without export. This library prevents that by computing per-request deadlines from the Lambda's remaining execution time and aborting requests before the hard timeout fires.
|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
|
|
12
|
+
- **Automatic deadline propagation** — no manual timeout configuration per call
|
|
13
|
+
- **Fresh deadline per retry** — each SDK retry attempt gets a deadline based on _current_ remaining time
|
|
14
|
+
- **Signal composition** — preserves caller-provided `AbortSignal` via `AbortSignal.any()`
|
|
15
|
+
- **Zero runtime dependencies** — uses `@smithy/types` for compile-time types only
|
|
16
|
+
- **Safe outside Lambda** — complete no-op when no Lambda context is available
|
|
17
|
+
- **OpenTelemetry integration** — optional span events on deadline aborts (detected dynamically)
|
|
18
|
+
- **Type-safe** — branded types prevent millisecond/buffer interchange at compile time
|
|
19
|
+
|
|
20
|
+
## Requirements
|
|
21
|
+
|
|
22
|
+
- Node.js ≥ 24
|
|
23
|
+
- AWS SDK v3 (built against `@smithy/types` ≥ 3.0.0)
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pnpm add lambda-deadline-middleware
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Quick Start
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
import { withLambdaDeadline, withDeadline } from "lambda-deadline-middleware";
|
|
35
|
+
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
|
|
36
|
+
|
|
37
|
+
// 1. Wrap your Lambda handler
|
|
38
|
+
export const handler = withLambdaDeadline(async (event, context) => {
|
|
39
|
+
const result = await dynamodb.send(
|
|
40
|
+
new GetItemCommand({
|
|
41
|
+
/* ... */
|
|
42
|
+
}),
|
|
43
|
+
);
|
|
44
|
+
return { statusCode: 200, body: JSON.stringify(result) };
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// 2. Register middleware on SDK clients
|
|
48
|
+
const dynamodb = withDeadline(new DynamoDBClient({}));
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
That's it. Every SDK call through `dynamodb` will automatically receive a timeout derived from the Lambda's remaining execution time minus a 1000ms flush buffer.
|
|
52
|
+
|
|
53
|
+
## Architecture
|
|
54
|
+
|
|
55
|
+
```mermaid
|
|
56
|
+
graph TB
|
|
57
|
+
subgraph "Lambda Runtime"
|
|
58
|
+
LR[Lambda Runtime] -->|invokes| HW[Handler Wrapper<br/>withLambdaDeadline]
|
|
59
|
+
HW -->|stores context| ALS[AsyncLocalStorage<br/>Lambda Context Store]
|
|
60
|
+
HW -->|delegates to| UH[User Handler]
|
|
61
|
+
UH -->|sends command| SDK[AWS SDK v3 Client]
|
|
62
|
+
|
|
63
|
+
subgraph "SDK Middleware Stack"
|
|
64
|
+
direction TB
|
|
65
|
+
INIT[Initialize Step]
|
|
66
|
+
FIN[Finalize Step]
|
|
67
|
+
RETRY[Retry Middleware]
|
|
68
|
+
DM[Deadline Middleware<br/>← attempt level]
|
|
69
|
+
HTTP[HTTP Handler]
|
|
70
|
+
|
|
71
|
+
INIT --> FIN --> RETRY
|
|
72
|
+
RETRY -->|per attempt| DM --> HTTP
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
DM -->|reads remaining time| ALS
|
|
76
|
+
DM -->|creates| AC[AbortController<br/>+ setTimeout]
|
|
77
|
+
AC -->|signal attached to| HTTP
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
SDK -->|middleware stack| INIT
|
|
81
|
+
|
|
82
|
+
style DM fill:#f9a825,stroke:#f57f17
|
|
83
|
+
style ALS fill:#42a5f5,stroke:#1565c0
|
|
84
|
+
style HW fill:#66bb6a,stroke:#2e7d32
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The middleware registers at the **attempt level** of the SDK stack — it executes once per HTTP request dispatch including retries, computing a fresh deadline each time.
|
|
88
|
+
|
|
89
|
+
## Configuration
|
|
90
|
+
|
|
91
|
+
### Flush Buffer
|
|
92
|
+
|
|
93
|
+
The flush buffer is subtracted from the remaining Lambda time to leave room for telemetry export and error handling:
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
// Default: 1000ms flush buffer
|
|
97
|
+
const client = withDeadline(new DynamoDBClient({}));
|
|
98
|
+
|
|
99
|
+
// Custom: 500ms flush buffer
|
|
100
|
+
const client = withDeadline(new DynamoDBClient({}), { flushBufferMs: 500 });
|
|
101
|
+
|
|
102
|
+
// No buffer: use full remaining time
|
|
103
|
+
const client = withDeadline(new DynamoDBClient({}), { flushBufferMs: 0 });
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Handler-Level Defaults
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
export const handler = withLambdaDeadline(
|
|
110
|
+
async (event, context) => {
|
|
111
|
+
/* ... */
|
|
112
|
+
},
|
|
113
|
+
{ flushBufferMs: 2000, telemetryEnabled: false },
|
|
114
|
+
);
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Telemetry
|
|
118
|
+
|
|
119
|
+
OpenTelemetry integration is detected dynamically. If `@opentelemetry/api` is installed, span events are emitted on deadline aborts. Disable with:
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
const client = withDeadline(new DynamoDBClient({}), { telemetryEnabled: false });
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## API Reference
|
|
126
|
+
|
|
127
|
+
### `withLambdaDeadline(handler, options?)`
|
|
128
|
+
|
|
129
|
+
Wraps a Lambda handler to initialize the context store.
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
function withLambdaDeadline<TEvent, TResult>(
|
|
133
|
+
handler: (event: TEvent, context: LambdaContextLike) => Promise<TResult>,
|
|
134
|
+
options?: DeadlineOptions,
|
|
135
|
+
): (event: TEvent, context: LambdaContextLike) => Promise<TResult>;
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### `withDeadline(client, options?)`
|
|
139
|
+
|
|
140
|
+
Registers the deadline middleware on an SDK client. Returns the same client instance. Idempotent — subsequent calls on the same client are no-ops.
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
function withDeadline<T extends { middlewareStack: { use: Function } }>(
|
|
144
|
+
client: T,
|
|
145
|
+
options?: DeadlineOptions,
|
|
146
|
+
): T;
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### `deadlineMiddleware(options?)`
|
|
150
|
+
|
|
151
|
+
Factory returning a `Pluggable` object for `client.middlewareStack.use()`.
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
function deadlineMiddleware(options?: DeadlineOptions): Pluggable<object, object>;
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### `getRemainingTimeInMillis()`
|
|
158
|
+
|
|
159
|
+
Accessor for the current Lambda's remaining execution time. Returns `undefined` outside a Lambda context.
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
function getRemainingTimeInMillis(): number | undefined;
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### `DeadlineExceededError`
|
|
166
|
+
|
|
167
|
+
Error thrown when a request is aborted due to deadline expiration.
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
class DeadlineExceededError extends Error {
|
|
171
|
+
readonly name: "DeadlineExceededError";
|
|
172
|
+
readonly deadlineMs: Milliseconds;
|
|
173
|
+
readonly flushBufferMs: FlushBufferMs;
|
|
174
|
+
readonly remainingMs: Milliseconds;
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### `isDeadlineExceeded(error)`
|
|
179
|
+
|
|
180
|
+
Type guard for deadline-triggered abort errors.
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
function isDeadlineExceeded(error: unknown): error is DeadlineExceededError;
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### `DeadlineOptions`
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
interface DeadlineOptions {
|
|
190
|
+
readonly flushBufferMs?: number; // Default: 1000
|
|
191
|
+
readonly telemetryEnabled?: boolean; // Default: true
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Types
|
|
196
|
+
|
|
197
|
+
| Type | Description |
|
|
198
|
+
| -------------------------- | ---------------------------------------------------------------------------- |
|
|
199
|
+
| `Milliseconds` | Branded number representing a duration in ms |
|
|
200
|
+
| `FlushBufferMs` | Branded number for the flush buffer |
|
|
201
|
+
| `RequestDeadlineMs` | Branded number for a computed deadline |
|
|
202
|
+
| `DeadlineComputation` | Discriminated union: `"deadline"` \| `"insufficient-time"` \| `"no-context"` |
|
|
203
|
+
| `DeadlineMiddlewareConfig` | Validated configuration (internal) |
|
|
204
|
+
| `LambdaContextLike` | Minimal interface: `{ getRemainingTimeInMillis?(): number }` |
|
|
205
|
+
|
|
206
|
+
## Error Handling
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
import { isDeadlineExceeded } from "lambda-deadline-middleware";
|
|
210
|
+
|
|
211
|
+
try {
|
|
212
|
+
await client.send(
|
|
213
|
+
new GetItemCommand({
|
|
214
|
+
/* ... */
|
|
215
|
+
}),
|
|
216
|
+
);
|
|
217
|
+
} catch (error) {
|
|
218
|
+
if (isDeadlineExceeded(error)) {
|
|
219
|
+
console.log(`Deadline exceeded: ${error.deadlineMs}ms`);
|
|
220
|
+
console.log(`Remaining time was: ${error.remainingMs}ms`);
|
|
221
|
+
// Handle graceful degradation
|
|
222
|
+
}
|
|
223
|
+
throw error;
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
When remaining time is less than or equal to the flush buffer, the middleware throws `DeadlineExceededError` immediately without dispatching an HTTP request.
|
|
228
|
+
|
|
229
|
+
## Advanced Usage
|
|
230
|
+
|
|
231
|
+
### Direct Pluggable Registration
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
import { deadlineMiddleware } from "lambda-deadline-middleware";
|
|
235
|
+
|
|
236
|
+
const client = new S3Client({});
|
|
237
|
+
client.middlewareStack.use(deadlineMiddleware({ flushBufferMs: 750 }));
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### Combining with Existing Abort Signals
|
|
241
|
+
|
|
242
|
+
The middleware composes signals — your existing signal is preserved:
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
const controller = new AbortController();
|
|
246
|
+
setTimeout(() => controller.abort(), 5000); // your own timeout
|
|
247
|
+
|
|
248
|
+
await client.send(
|
|
249
|
+
new GetItemCommand({
|
|
250
|
+
/* ... */
|
|
251
|
+
}),
|
|
252
|
+
{
|
|
253
|
+
abortSignal: controller.signal, // both signals are respected
|
|
254
|
+
},
|
|
255
|
+
);
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Multiple Clients
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
const dynamodb = withDeadline(new DynamoDBClient({}));
|
|
262
|
+
const sqs = withDeadline(new SQSClient({}), { flushBufferMs: 2000 });
|
|
263
|
+
const s3 = withDeadline(new S3Client({}), { flushBufferMs: 500 });
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
## Reporting Bugs
|
|
267
|
+
|
|
268
|
+
Found a bug? Please open a [GitHub Issue](https://github.com/mikkopiu/lambda-deadline-middleware/issues/new) with:
|
|
269
|
+
|
|
270
|
+
- Your Node.js version and AWS SDK version
|
|
271
|
+
- A minimal code snippet reproducing the problem
|
|
272
|
+
- Expected vs actual behavior
|
|
273
|
+
|
|
274
|
+
For security vulnerabilities, see [SECURITY.md](SECURITY.md) instead.
|
|
275
|
+
|
|
276
|
+
## License
|
|
277
|
+
|
|
278
|
+
[MIT](LICENSE)
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
<!-- SPDX-FileCopyrightText: 2024 lambda-deadline-middleware contributors -->
|
|
2
|
+
<!-- SPDX-License-Identifier: MIT -->
|
|
3
|
+
|
|
4
|
+
# Security
|
|
5
|
+
|
|
6
|
+
This document describes the supply chain security practices for `lambda-deadline-middleware`.
|
|
7
|
+
|
|
8
|
+
## Provenance Verification
|
|
9
|
+
|
|
10
|
+
Every published release includes SLSA Level 3 provenance attestation, generated
|
|
11
|
+
automatically by GitHub Actions using the `--provenance` flag during `npm publish`.
|
|
12
|
+
|
|
13
|
+
### Verifying provenance
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm audit signatures
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
This command verifies that the package was:
|
|
20
|
+
|
|
21
|
+
- Built from the source repository (not a compromised local machine)
|
|
22
|
+
- Published by the expected GitHub Actions workflow
|
|
23
|
+
- Signed with a valid sigstore certificate tied to the workflow identity
|
|
24
|
+
|
|
25
|
+
You can also inspect the provenance bundle directly:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm provenance --package-name lambda-deadline-middleware
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## SBOM (Software Bill of Materials)
|
|
32
|
+
|
|
33
|
+
Each release includes a CycloneDX SBOM (`sbom.cdx.json`) attached as a GitHub
|
|
34
|
+
Release asset. The SBOM lists all runtime and development dependencies with their
|
|
35
|
+
versions, providing full transparency into the package's dependency tree.
|
|
36
|
+
|
|
37
|
+
### Generating the SBOM locally
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pnpm run sbom
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
This produces `sbom.cdx.json` in the project root using pnpm's native CycloneDX SBOM generation.
|
|
44
|
+
|
|
45
|
+
### SBOM format
|
|
46
|
+
|
|
47
|
+
- **Specification**: CycloneDX v1.5
|
|
48
|
+
- **Format**: JSON
|
|
49
|
+
- **Tool**: `pnpm sbom` (native, since pnpm 11)
|
|
50
|
+
- **Contents**: Component inventory including direct and transitive dependencies
|
|
51
|
+
|
|
52
|
+
## Sigstore Signing
|
|
53
|
+
|
|
54
|
+
All published artifacts are signed using **keyless sigstore signing** via OIDC
|
|
55
|
+
identity federation. This means:
|
|
56
|
+
|
|
57
|
+
- **No long-lived signing keys**: Signing happens via short-lived certificates
|
|
58
|
+
issued by Fulcio, tied to the GitHub Actions OIDC identity.
|
|
59
|
+
- **Transparency log**: All signatures are recorded in the Rekor transparency log,
|
|
60
|
+
providing a tamper-evident audit trail.
|
|
61
|
+
- **Verification without publisher keys**: Consumers verify signatures using the
|
|
62
|
+
sigstore root of trust, not a publisher-managed key.
|
|
63
|
+
|
|
64
|
+
### How it works in CI
|
|
65
|
+
|
|
66
|
+
1. The GitHub Actions release workflow requests an OIDC token from GitHub.
|
|
67
|
+
2. The OIDC token is exchanged with Fulcio for a short-lived signing certificate.
|
|
68
|
+
3. The npm package is signed with the certificate during `npm publish --provenance`.
|
|
69
|
+
4. The signature and certificate are recorded in the Rekor transparency log.
|
|
70
|
+
5. npm stores the provenance bundle alongside the package tarball.
|
|
71
|
+
|
|
72
|
+
### Verifying signatures
|
|
73
|
+
|
|
74
|
+
Signature verification is automatic when running `npm audit signatures`. The npm
|
|
75
|
+
CLI checks:
|
|
76
|
+
|
|
77
|
+
- The certificate was issued by Fulcio
|
|
78
|
+
- The OIDC identity matches the expected repository and workflow
|
|
79
|
+
- The signature is recorded in the Rekor transparency log
|
|
80
|
+
- The package content matches the signed digest
|
|
81
|
+
|
|
82
|
+
## Reporting Vulnerabilities
|
|
83
|
+
|
|
84
|
+
If you discover a security vulnerability in this package, please report it
|
|
85
|
+
responsibly by opening a [GitHub Security Advisory](https://github.com/mikkopiu/lambda-deadline-middleware/security/advisories/new) on this repository. Do not
|
|
86
|
+
file a public issue for security vulnerabilities.
|
|
87
|
+
|
|
88
|
+
### Response Timeline
|
|
89
|
+
|
|
90
|
+
We aim to acknowledge vulnerability reports within 72 hours. For critical
|
|
91
|
+
issues, we target a fix or mitigation plan within 14 days. Lower-severity
|
|
92
|
+
issues are addressed as part of normal maintenance cycles.
|
|
93
|
+
|
|
94
|
+
Resolved vulnerabilities are disclosed publicly via GitHub Security Advisories
|
|
95
|
+
once a fix is released.
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"mappings":"AAIA,cAAc,0BAA0B,uBAAuB;AAE/D,OAAO,iBAAS,YAAY,KAAK,8BAA8B","names":[],"sources":["src/config.ts"],"version":3,"sourcesContent":["// SPDX-FileCopyrightText: 2024 lambda-deadline-middleware contributors\n// SPDX-License-Identifier: MIT\n\nimport { flushBufferMs } from \"./types.js\";\nimport type { DeadlineMiddlewareConfig, DeadlineOptions } from \"./types.js\";\n\nexport function parseConfig(raw: DeadlineOptions | undefined): DeadlineMiddlewareConfig {\n const buffer = raw?.flushBufferMs ?? 1000;\n if (buffer < 0) {\n throw new TypeError(`flushBufferMs option must be non-negative, received: ${buffer}`);\n }\n return {\n flushBufferMs: flushBufferMs(buffer),\n telemetryEnabled: raw?.telemetryEnabled ?? true,\n };\n}\n"]}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2024 lambda-deadline-middleware contributors
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
import { flushBufferMs } from "./types.js";
|
|
4
|
+
export function parseConfig(raw) {
|
|
5
|
+
const buffer = raw?.flushBufferMs ?? 1e3;
|
|
6
|
+
if (buffer < 0) {
|
|
7
|
+
throw new TypeError(`flushBufferMs option must be non-negative, received: ${buffer}`);
|
|
8
|
+
}
|
|
9
|
+
return {
|
|
10
|
+
flushBufferMs: flushBufferMs(buffer),
|
|
11
|
+
telemetryEnabled: raw?.telemetryEnabled ?? true
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"mappings":";;AAGA,SAAS,qBAAqB;AAG9B,OAAO,SAAS,YAAY,KAA4D;CACtF,MAAM,SAAS,KAAK,iBAAiB;CACrC,IAAI,SAAS,GAAG;EACd,MAAM,IAAI,UAAU,wDAAwD,QAAQ;CACtF;CACA,OAAO;EACL,eAAe,cAAc,MAAM;EACnC,kBAAkB,KAAK,oBAAoB;CAC7C;AACF","names":[],"sources":["src/config.ts"],"version":3,"sourcesContent":["// SPDX-FileCopyrightText: 2024 lambda-deadline-middleware contributors\n// SPDX-License-Identifier: MIT\n\nimport { flushBufferMs } from \"./types.js\";\nimport type { DeadlineMiddlewareConfig, DeadlineOptions } from \"./types.js\";\n\nexport function parseConfig(raw: DeadlineOptions | undefined): DeadlineMiddlewareConfig {\n const buffer = raw?.flushBufferMs ?? 1000;\n if (buffer < 0) {\n throw new TypeError(`flushBufferMs option must be non-negative, received: ${buffer}`);\n }\n return {\n flushBufferMs: flushBufferMs(buffer),\n telemetryEnabled: raw?.telemetryEnabled ?? true,\n };\n}\n"]}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface LambdaContextLike {
|
|
2
|
+
getRemainingTimeInMillis?: () => number;
|
|
3
|
+
}
|
|
4
|
+
export declare function run<T>(context: LambdaContextLike | null | undefined, fn: () => T): T;
|
|
5
|
+
export declare function getRemainingTimeInMillis(): number | undefined;
|
|
6
|
+
|
|
7
|
+
//# sourceMappingURL=context-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"mappings":"AAKA,iBAAiB,kBAAkB;CACjC;AACF;AAWA,OAAO,iBAAS,IAAI,GAAG,SAAS,sCAAsC,UAAU,IAAI;AAKpF,OAAO,iBAAS","names":[],"sources":["src/context-store.ts"],"version":3,"sourcesContent":["// SPDX-FileCopyrightText: 2024 lambda-deadline-middleware contributors\n// SPDX-License-Identifier: MIT\n\nimport { AsyncLocalStorage } from \"node:async_hooks\";\n\nexport interface LambdaContextLike {\n getRemainingTimeInMillis?: () => number;\n}\n\n// Sentinel allows AsyncLocalStorage.run() to accept null/undefined context\n// without throwing, while the accessor can distinguish \"no context stored\"\n// from \"context present but missing the method\".\nconst NO_CONTEXT: unique symbol = Symbol(\"no-context\");\n\ntype StoreValue = LambdaContextLike | typeof NO_CONTEXT;\n\nconst contextStorage = new AsyncLocalStorage<StoreValue>();\n\nexport function run<T>(context: LambdaContextLike | null | undefined, fn: () => T): T {\n const value: StoreValue = context ?? NO_CONTEXT;\n return contextStorage.run(value, fn);\n}\n\nexport function getRemainingTimeInMillis(): number | undefined {\n const store = contextStorage.getStore();\n\n if (store === undefined || store === NO_CONTEXT) {\n return undefined;\n }\n\n if (typeof store.getRemainingTimeInMillis !== \"function\") {\n return undefined;\n }\n\n return store.getRemainingTimeInMillis();\n}\n"]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2024 lambda-deadline-middleware contributors
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
4
|
+
// Sentinel allows AsyncLocalStorage.run() to accept null/undefined context
|
|
5
|
+
// without throwing, while the accessor can distinguish "no context stored"
|
|
6
|
+
// from "context present but missing the method".
|
|
7
|
+
const NO_CONTEXT = Symbol("no-context");
|
|
8
|
+
const contextStorage = new AsyncLocalStorage();
|
|
9
|
+
export function run(context, fn) {
|
|
10
|
+
const value = context ?? NO_CONTEXT;
|
|
11
|
+
return contextStorage.run(value, fn);
|
|
12
|
+
}
|
|
13
|
+
export function getRemainingTimeInMillis() {
|
|
14
|
+
const store = contextStorage.getStore();
|
|
15
|
+
if (store === undefined || store === NO_CONTEXT) {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
if (typeof store.getRemainingTimeInMillis !== "function") {
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
return store.getRemainingTimeInMillis();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
//# sourceMappingURL=context-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"mappings":";;AAGA,SAAS,yBAAyB;;;;AASlC,MAAM,aAA4B,OAAO,YAAY;AAIrD,MAAM,iBAAiB,IAAI,kBAA8B;AAEzD,OAAO,SAAS,IAAO,SAA+C,IAAgB;CACpF,MAAM,QAAoB,WAAW;CACrC,OAAO,eAAe,IAAI,OAAO,EAAE;AACrC;AAEA,OAAO,SAAS,2BAA+C;CAC7D,MAAM,QAAQ,eAAe,SAAS;CAEtC,IAAI,UAAU,aAAa,UAAU,YAAY;EAC/C,OAAO;CACT;CAEA,IAAI,OAAO,MAAM,6BAA6B,YAAY;EACxD,OAAO;CACT;CAEA,OAAO,MAAM,yBAAyB;AACxC","names":[],"sources":["src/context-store.ts"],"version":3,"sourcesContent":["// SPDX-FileCopyrightText: 2024 lambda-deadline-middleware contributors\n// SPDX-License-Identifier: MIT\n\nimport { AsyncLocalStorage } from \"node:async_hooks\";\n\nexport interface LambdaContextLike {\n getRemainingTimeInMillis?: () => number;\n}\n\n// Sentinel allows AsyncLocalStorage.run() to accept null/undefined context\n// without throwing, while the accessor can distinguish \"no context stored\"\n// from \"context present but missing the method\".\nconst NO_CONTEXT: unique symbol = Symbol(\"no-context\");\n\ntype StoreValue = LambdaContextLike | typeof NO_CONTEXT;\n\nconst contextStorage = new AsyncLocalStorage<StoreValue>();\n\nexport function run<T>(context: LambdaContextLike | null | undefined, fn: () => T): T {\n const value: StoreValue = context ?? NO_CONTEXT;\n return contextStorage.run(value, fn);\n}\n\nexport function getRemainingTimeInMillis(): number | undefined {\n const store = contextStorage.getStore();\n\n if (store === undefined || store === NO_CONTEXT) {\n return undefined;\n }\n\n if (typeof store.getRemainingTimeInMillis !== \"function\") {\n return undefined;\n }\n\n return store.getRemainingTimeInMillis();\n}\n"]}
|
package/dist/error.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { FlushBufferMs, Milliseconds } from "./types.js";
|
|
2
|
+
interface DeadlineExceededInit {
|
|
3
|
+
readonly deadlineMs: Milliseconds;
|
|
4
|
+
readonly flushBufferMs: FlushBufferMs;
|
|
5
|
+
readonly remainingMs: Milliseconds;
|
|
6
|
+
}
|
|
7
|
+
export declare class DeadlineExceededError extends Error {
|
|
8
|
+
override readonly name = "DeadlineExceededError";
|
|
9
|
+
readonly deadlineMs: Milliseconds;
|
|
10
|
+
readonly flushBufferMs: FlushBufferMs;
|
|
11
|
+
readonly remainingMs: Milliseconds;
|
|
12
|
+
constructor(init: DeadlineExceededInit);
|
|
13
|
+
}
|
|
14
|
+
export declare function isDeadlineExceeded(error: unknown): error is DeadlineExceededError;
|
|
15
|
+
export {};
|
|
16
|
+
|
|
17
|
+
//# sourceMappingURL=error.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"mappings":"AAGA,cAAc,eAAe,oBAAoB;UAEvC,qBAAqB;UACpB,YAAY;UACZ,eAAe;UACf,aAAa;AACxB;AAEA,OAAO,cAAM,8BAA8B,MAAM;CAC/C,kBAAkB,OAAO;CACzB,SAAS,YAAY;CACrB,SAAS,eAAe;CACxB,SAAS,aAAa;CAEtB,YAAY,MAAM;AAQpB;AAIA,OAAO,iBAAS,mBAAmB,iBAAiB,SAAS","names":[],"sources":["src/error.ts"],"version":3,"sourcesContent":["// SPDX-FileCopyrightText: 2024 lambda-deadline-middleware contributors\n// SPDX-License-Identifier: MIT\n\nimport type { FlushBufferMs, Milliseconds } from \"./types.js\";\n\ninterface DeadlineExceededInit {\n readonly deadlineMs: Milliseconds;\n readonly flushBufferMs: FlushBufferMs;\n readonly remainingMs: Milliseconds;\n}\n\nexport class DeadlineExceededError extends Error {\n override readonly name = \"DeadlineExceededError\" as const;\n readonly deadlineMs: Milliseconds;\n readonly flushBufferMs: FlushBufferMs;\n readonly remainingMs: Milliseconds;\n\n constructor(init: DeadlineExceededInit) {\n super(\n `Request deadline exceeded: ${init.deadlineMs}ms deadline (${init.flushBufferMs}ms flush buffer)`,\n );\n this.deadlineMs = init.deadlineMs;\n this.flushBufferMs = init.flushBufferMs;\n this.remainingMs = init.remainingMs;\n }\n}\n\n// Structural check rather than instanceof — works across module boundaries\n// and serialization boundaries where prototype chain may be broken.\nexport function isDeadlineExceeded(error: unknown): error is DeadlineExceededError {\n if (error === null || error === undefined) return false;\n if (typeof error !== \"object\") return false;\n return (error as { name?: unknown }).name === \"DeadlineExceededError\";\n}\n"]}
|
package/dist/error.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export class DeadlineExceededError extends Error {
|
|
2
|
+
name = "DeadlineExceededError";
|
|
3
|
+
deadlineMs;
|
|
4
|
+
flushBufferMs;
|
|
5
|
+
remainingMs;
|
|
6
|
+
constructor(init) {
|
|
7
|
+
super(`Request deadline exceeded: ${init.deadlineMs}ms deadline (${init.flushBufferMs}ms flush buffer)`);
|
|
8
|
+
this.deadlineMs = init.deadlineMs;
|
|
9
|
+
this.flushBufferMs = init.flushBufferMs;
|
|
10
|
+
this.remainingMs = init.remainingMs;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
// Structural check rather than instanceof — works across module boundaries
|
|
14
|
+
// and serialization boundaries where prototype chain may be broken.
|
|
15
|
+
export function isDeadlineExceeded(error) {
|
|
16
|
+
if (error === null || error === undefined) return false;
|
|
17
|
+
if (typeof error !== "object") return false;
|
|
18
|
+
return error.name === "DeadlineExceededError";
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
//# sourceMappingURL=error.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"mappings":"AAWA,OAAO,MAAM,8BAA8B,MAAM;CAC/C,AAAkB,OAAO;CACzB,AAAS;CACT,AAAS;CACT,AAAS;CAET,YAAY,MAA4B;EACtC,MACE,8BAA8B,KAAK,WAAW,eAAe,KAAK,cAAc,iBAClF;EACA,KAAK,aAAa,KAAK;EACvB,KAAK,gBAAgB,KAAK;EAC1B,KAAK,cAAc,KAAK;CAC1B;AACF;;;AAIA,OAAO,SAAS,mBAAmB,OAAgD;CACjF,IAAI,UAAU,QAAQ,UAAU,WAAW,OAAO;CAClD,IAAI,OAAO,UAAU,UAAU,OAAO;CACtC,OAAQ,MAA6B,SAAS;AAChD","names":[],"sources":["src/error.ts"],"version":3,"sourcesContent":["// SPDX-FileCopyrightText: 2024 lambda-deadline-middleware contributors\n// SPDX-License-Identifier: MIT\n\nimport type { FlushBufferMs, Milliseconds } from \"./types.js\";\n\ninterface DeadlineExceededInit {\n readonly deadlineMs: Milliseconds;\n readonly flushBufferMs: FlushBufferMs;\n readonly remainingMs: Milliseconds;\n}\n\nexport class DeadlineExceededError extends Error {\n override readonly name = \"DeadlineExceededError\" as const;\n readonly deadlineMs: Milliseconds;\n readonly flushBufferMs: FlushBufferMs;\n readonly remainingMs: Milliseconds;\n\n constructor(init: DeadlineExceededInit) {\n super(\n `Request deadline exceeded: ${init.deadlineMs}ms deadline (${init.flushBufferMs}ms flush buffer)`,\n );\n this.deadlineMs = init.deadlineMs;\n this.flushBufferMs = init.flushBufferMs;\n this.remainingMs = init.remainingMs;\n }\n}\n\n// Structural check rather than instanceof — works across module boundaries\n// and serialization boundaries where prototype chain may be broken.\nexport function isDeadlineExceeded(error: unknown): error is DeadlineExceededError {\n if (error === null || error === undefined) return false;\n if (typeof error !== \"object\") return false;\n return (error as { name?: unknown }).name === \"DeadlineExceededError\";\n}\n"]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { LambdaContextLike } from "./context-store.js";
|
|
2
|
+
import type { DeadlineOptions } from "./types.js";
|
|
3
|
+
type AsyncHandler<
|
|
4
|
+
TEvent,
|
|
5
|
+
TResult
|
|
6
|
+
> = (event: TEvent, context: LambdaContextLike) => Promise<TResult>;
|
|
7
|
+
export declare function withLambdaDeadline<
|
|
8
|
+
TEvent,
|
|
9
|
+
TResult
|
|
10
|
+
>(handler: AsyncHandler<TEvent, TResult>, _options?: DeadlineOptions): AsyncHandler<TEvent, TResult>;
|
|
11
|
+
export {};
|
|
12
|
+
|
|
13
|
+
//# sourceMappingURL=handler-wrapper.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"mappings":"AAIA,cAAc,yBAAyB;AACvC,cAAc,uBAAuB;KAEhC;CAAa;CAAQ;KACxB,OAAO,QACP,SAAS,sBACN,QAAQ;AAEb,OAAO,iBAAS;CAAmB;CAAQ;EACzC,SAAS,aAAa,QAAQ,UAC9B,WAAW,kBACV,aAAa,QAAQ","names":[],"sources":["src/handler-wrapper.ts"],"version":3,"sourcesContent":["// SPDX-FileCopyrightText: 2024 lambda-deadline-middleware contributors\n// SPDX-License-Identifier: MIT\n\nimport { run } from \"./context-store.js\";\nimport type { LambdaContextLike } from \"./context-store.js\";\nimport type { DeadlineOptions } from \"./types.js\";\n\ntype AsyncHandler<TEvent, TResult> = (\n event: TEvent,\n context: LambdaContextLike,\n) => Promise<TResult>;\n\nexport function withLambdaDeadline<TEvent, TResult>(\n handler: AsyncHandler<TEvent, TResult>,\n _options?: DeadlineOptions,\n): AsyncHandler<TEvent, TResult> {\n return async (event: TEvent, context: LambdaContextLike): Promise<TResult> =>\n run(context, async () => handler(event, context));\n}\n"]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2024 lambda-deadline-middleware contributors
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
import { run } from "./context-store.js";
|
|
4
|
+
export function withLambdaDeadline(handler, _options) {
|
|
5
|
+
return async (event, context) => run(context, async () => handler(event, context));
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
//# sourceMappingURL=handler-wrapper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"mappings":";;AAGA,SAAS,WAAW;AASpB,OAAO,SAAS,mBACd,SACA,UAC+B;CAC/B,OAAO,OAAO,OAAe,YAC3B,IAAI,SAAS,YAAY,QAAQ,OAAO,OAAO,CAAC;AACpD","names":[],"sources":["src/handler-wrapper.ts"],"version":3,"sourcesContent":["// SPDX-FileCopyrightText: 2024 lambda-deadline-middleware contributors\n// SPDX-License-Identifier: MIT\n\nimport { run } from \"./context-store.js\";\nimport type { LambdaContextLike } from \"./context-store.js\";\nimport type { DeadlineOptions } from \"./types.js\";\n\ntype AsyncHandler<TEvent, TResult> = (\n event: TEvent,\n context: LambdaContextLike,\n) => Promise<TResult>;\n\nexport function withLambdaDeadline<TEvent, TResult>(\n handler: AsyncHandler<TEvent, TResult>,\n _options?: DeadlineOptions,\n): AsyncHandler<TEvent, TResult> {\n return async (event: TEvent, context: LambdaContextLike): Promise<TResult> =>\n run(context, async () => handler(event, context));\n}\n"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { withLambdaDeadline } from "./handler-wrapper.js";
|
|
2
|
+
export { deadlineMiddleware, withDeadline } from "./registration.js";
|
|
3
|
+
export { DeadlineExceededError, isDeadlineExceeded } from "./error.js";
|
|
4
|
+
export { getRemainingTimeInMillis } from "./context-store.js";
|
|
5
|
+
export type { Milliseconds, FlushBufferMs, RequestDeadlineMs, DeadlineComputation, DeadlineMiddlewareConfig, DeadlineOptions } from "./types.js";
|
|
6
|
+
export type { LambdaContextLike } from "./context-store.js";
|
|
7
|
+
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"mappings":"AAGA,SAAS,0BAA0B;AACnC,SAAS,oBAAoB,oBAAoB;AACjD,SAAS,uBAAuB,0BAA0B;AAC1D,SAAS,gCAAgC;AAEzC,cACE,cACA,eACA,mBACA,qBACA,0BACA,uBACK;AAEP,cAAc,yBAAyB","names":[],"sources":["src/index.ts"],"version":3,"sourcesContent":["// SPDX-FileCopyrightText: 2024 lambda-deadline-middleware contributors\n// SPDX-License-Identifier: MIT\n\nexport { withLambdaDeadline } from \"./handler-wrapper.js\";\nexport { deadlineMiddleware, withDeadline } from \"./registration.js\";\nexport { DeadlineExceededError, isDeadlineExceeded } from \"./error.js\";\nexport { getRemainingTimeInMillis } from \"./context-store.js\";\n\nexport type {\n Milliseconds,\n FlushBufferMs,\n RequestDeadlineMs,\n DeadlineComputation,\n DeadlineMiddlewareConfig,\n DeadlineOptions,\n} from \"./types.js\";\n\nexport type { LambdaContextLike } from \"./context-store.js\";\n"]}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2024 lambda-deadline-middleware contributors
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
export { withLambdaDeadline } from "./handler-wrapper.js";
|
|
4
|
+
export { deadlineMiddleware, withDeadline } from "./registration.js";
|
|
5
|
+
export { DeadlineExceededError, isDeadlineExceeded } from "./error.js";
|
|
6
|
+
export { getRemainingTimeInMillis } from "./context-store.js";
|
|
7
|
+
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"mappings":";;AAGA,SAAS,0BAA0B;AACnC,SAAS,oBAAoB,oBAAoB;AACjD,SAAS,uBAAuB,0BAA0B;AAC1D,SAAS,gCAAgC","names":[],"sources":["src/index.ts"],"version":3,"sourcesContent":["// SPDX-FileCopyrightText: 2024 lambda-deadline-middleware contributors\n// SPDX-License-Identifier: MIT\n\nexport { withLambdaDeadline } from \"./handler-wrapper.js\";\nexport { deadlineMiddleware, withDeadline } from \"./registration.js\";\nexport { DeadlineExceededError, isDeadlineExceeded } from \"./error.js\";\nexport { getRemainingTimeInMillis } from \"./context-store.js\";\n\nexport type {\n Milliseconds,\n FlushBufferMs,\n RequestDeadlineMs,\n DeadlineComputation,\n DeadlineMiddlewareConfig,\n DeadlineOptions,\n} from \"./types.js\";\n\nexport type { LambdaContextLike } from \"./context-store.js\";\n"]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { DeadlineComputation, DeadlineMiddlewareConfig, RequestDeadlineMs } from "./types.js";
|
|
2
|
+
import type { FinalizeRequestMiddleware } from "@smithy/types";
|
|
3
|
+
export declare function computeDeadline(config: DeadlineMiddlewareConfig): DeadlineComputation;
|
|
4
|
+
export interface DeadlineTimer {
|
|
5
|
+
readonly controller: AbortController;
|
|
6
|
+
[Symbol.dispose]: () => void;
|
|
7
|
+
}
|
|
8
|
+
export declare function createDeadlineTimer(deadlineMs: RequestDeadlineMs, config: DeadlineMiddlewareConfig): DeadlineTimer;
|
|
9
|
+
export declare function composeSignals(existing: AbortSignal | undefined, deadline: AbortSignal): AbortSignal;
|
|
10
|
+
export declare function deadlineMiddlewareHandler(config: DeadlineMiddlewareConfig): FinalizeRequestMiddleware<object, object>;
|
|
11
|
+
|
|
12
|
+
//# sourceMappingURL=middleware.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"mappings":"AAKA,cAAc,qBAAqB,0BAA0B,yBAAyB;AAGtF,cAIE,iCAEK;AAEP,OAAO,iBAAS,gBAAgB,QAAQ,2BAA2B;AAqBnE,iBAAiB,cAAc;UACpB,YAAY;EACpB,OAAO;AACV;AAEA,OAAO,iBAAS,oBACd,YAAY,mBACZ,QAAQ,2BACP;AAmBH,OAAO,iBAAS,eACd,UAAU,yBACV,UAAU,cACT;AAKH,OAAO,iBAAS,0BACd,QAAQ,2BACP","names":[],"sources":["src/middleware.ts"],"version":3,"sourcesContent":["// SPDX-FileCopyrightText: 2024 lambda-deadline-middleware contributors\n// SPDX-License-Identifier: MIT\n\nimport { getRemainingTimeInMillis } from \"./context-store.js\";\nimport { DeadlineExceededError } from \"./error.js\";\nimport type { DeadlineComputation, DeadlineMiddlewareConfig, RequestDeadlineMs } from \"./types.js\";\nimport { milliseconds } from \"./types.js\";\n\nimport type {\n FinalizeHandler,\n FinalizeHandlerArguments,\n FinalizeHandlerOutput,\n FinalizeRequestMiddleware,\n HandlerExecutionContext,\n} from \"@smithy/types\";\n\nexport function computeDeadline(config: DeadlineMiddlewareConfig): DeadlineComputation {\n const remaining = getRemainingTimeInMillis();\n\n if (remaining === undefined) {\n return { kind: \"no-context\" };\n }\n\n const deadline = remaining - config.flushBufferMs;\n\n if (deadline <= 0) {\n return {\n kind: \"insufficient-time\",\n remaining: milliseconds(remaining),\n buffer: config.flushBufferMs,\n };\n }\n\n // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- branded narrowing: deadline is validated > 0 above\n return { kind: \"deadline\", value: deadline as RequestDeadlineMs };\n}\n\nexport interface DeadlineTimer {\n readonly controller: AbortController;\n [Symbol.dispose]: () => void;\n}\n\nexport function createDeadlineTimer(\n deadlineMs: RequestDeadlineMs,\n config: DeadlineMiddlewareConfig,\n): DeadlineTimer {\n const controller = new AbortController();\n const remaining = milliseconds(deadlineMs + config.flushBufferMs);\n const error = new DeadlineExceededError({\n deadlineMs: milliseconds(deadlineMs),\n flushBufferMs: config.flushBufferMs,\n remainingMs: remaining,\n });\n const timeoutId = setTimeout(() => {\n controller.abort(error);\n }, deadlineMs);\n return {\n controller,\n [Symbol.dispose]() {\n clearTimeout(timeoutId);\n },\n };\n}\n\nexport function composeSignals(\n existing: AbortSignal | undefined,\n deadline: AbortSignal,\n): AbortSignal {\n if (existing === undefined) return deadline;\n return AbortSignal.any([existing, deadline]);\n}\n\nexport function deadlineMiddlewareHandler(\n config: DeadlineMiddlewareConfig,\n): FinalizeRequestMiddleware<object, object> {\n return (\n next: FinalizeHandler<object, object>,\n _context: HandlerExecutionContext,\n ): FinalizeHandler<object, object> =>\n // oxlint-disable-next-line typescript/consistent-return -- switch is exhaustive over DeadlineComputation discriminated union\n async (args: FinalizeHandlerArguments<object>): Promise<FinalizeHandlerOutput<object>> => {\n const computation = computeDeadline(config);\n\n switch (computation.kind) {\n case \"no-context\":\n return next(args);\n\n case \"insufficient-time\":\n throw new DeadlineExceededError({\n deadlineMs: milliseconds(0),\n flushBufferMs: computation.buffer,\n remainingMs: computation.remaining,\n });\n\n case \"deadline\": {\n using timer = createDeadlineTimer(computation.value, config);\n // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- Smithy request is an opaque object; we access optional signal property\n const request = args.request as { signal?: AbortSignal } | undefined;\n const signal = composeSignals(request?.signal, timer.controller.signal);\n const result = await next({\n ...args,\n // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- spreading opaque Smithy request to add signal\n request: { ...(args.request as object), signal },\n });\n return result;\n }\n }\n };\n}\n"]}
|