lambda-deadline-middleware 0.0.0 → 1.1.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 +1 -1
- package/LICENSES/MIT.txt +18 -0
- package/README.md +99 -159
- package/REUSE.toml +9 -0
- package/SECURITY.md +34 -38
- package/dist/context-store.d.ts +2 -2
- package/dist/context-store.d.ts.map +1 -1
- package/dist/context-store.js +9 -17
- package/dist/context-store.js.map +1 -1
- package/dist/error.d.ts +4 -4
- package/dist/error.d.ts.map +1 -1
- package/dist/error.js +2 -2
- package/dist/error.js.map +1 -1
- package/dist/handler-wrapper.d.ts +5 -4
- package/dist/handler-wrapper.d.ts.map +1 -1
- package/dist/handler-wrapper.js +2 -4
- package/dist/handler-wrapper.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/middleware.d.ts +7 -10
- package/dist/middleware.d.ts.map +1 -1
- package/dist/middleware.js +53 -67
- package/dist/middleware.js.map +1 -1
- package/dist/types.d.ts +1 -19
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +2 -12
- package/dist/types.js.map +1 -1
- package/package.json +39 -33
- package/src/context-store.ts +12 -24
- package/src/error.ts +6 -6
- package/src/handler-wrapper.ts +9 -10
- package/src/index.ts +3 -10
- package/src/middleware.ts +76 -89
- package/src/types.ts +5 -33
- package/dist/config.d.ts +0 -4
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js +0 -15
- package/dist/config.js.map +0 -1
- package/dist/registration.d.ts +0 -10
- package/dist/registration.d.ts.map +0 -1
- package/dist/registration.js +0 -23
- package/dist/registration.js.map +0 -1
- package/dist/telemetry.d.ts +0 -5
- package/dist/telemetry.d.ts.map +0 -1
- package/dist/telemetry.js +0 -82
- package/dist/telemetry.js.map +0 -1
- package/src/config.ts +0 -16
- package/src/registration.ts +0 -36
- package/src/telemetry.ts +0 -129
package/LICENSE
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c)
|
|
3
|
+
Copyright (c) 2026 lambda-deadline-middleware contributors
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
package/LICENSES/MIT.txt
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) <year> <copyright holders>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
|
6
|
+
associated documentation files (the "Software"), to deal in the Software without restriction, including
|
|
7
|
+
without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
|
|
9
|
+
following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial
|
|
12
|
+
portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
|
|
15
|
+
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
|
|
16
|
+
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
17
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
|
18
|
+
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,21 +1,23 @@
|
|
|
1
|
-
<!-- SPDX-FileCopyrightText:
|
|
1
|
+
<!-- SPDX-FileCopyrightText: 2026 lambda-deadline-middleware contributors -->
|
|
2
2
|
<!-- SPDX-License-Identifier: MIT -->
|
|
3
3
|
|
|
4
4
|
# lambda-deadline-middleware
|
|
5
5
|
|
|
6
|
-
Zero-dependency AWS SDK v3 middleware that automatically propagates Lambda execution deadlines to outgoing SDK calls via
|
|
6
|
+
Zero-dependency AWS SDK v3 middleware that automatically propagates Lambda execution deadlines to outgoing SDK calls via
|
|
7
|
+
`AbortController`-based timeouts.
|
|
7
8
|
|
|
8
|
-
When an AWS SDK call hangs inside a Lambda function, the runtime terminates the process at the configured timeout
|
|
9
|
+
When an AWS SDK call hangs inside a Lambda function, the runtime terminates the process at the configured timeout
|
|
10
|
+
without throwing an error or giving your code a chance to react. This library prevents that by computing per-request
|
|
11
|
+
deadlines from the Lambda's remaining execution time and aborting requests before the hard timeout fires.
|
|
9
12
|
|
|
10
13
|
## Features
|
|
11
14
|
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
- **Type-safe** — branded types prevent millisecond/buffer interchange at compile time
|
|
15
|
+
- Automatic deadline propagation, no manual timeout configuration per call
|
|
16
|
+
- Fresh deadline per retry: each SDK retry attempt uses _current_ remaining time
|
|
17
|
+
- Signal composition: preserves caller-provided `AbortSignal` via `AbortSignal.any()`
|
|
18
|
+
- Zero runtime dependencies (`@smithy/types` is compile-time only)
|
|
19
|
+
- Complete no-op when no Lambda context is available
|
|
20
|
+
- Branded types prevent millisecond/buffer interchange at compile time
|
|
19
21
|
|
|
20
22
|
## Requirements
|
|
21
23
|
|
|
@@ -28,13 +30,23 @@ When an AWS SDK call hangs inside a Lambda function, the runtime terminates the
|
|
|
28
30
|
pnpm add lambda-deadline-middleware
|
|
29
31
|
```
|
|
30
32
|
|
|
31
|
-
##
|
|
33
|
+
## Usage
|
|
34
|
+
|
|
35
|
+
Setup requires two pieces:
|
|
36
|
+
|
|
37
|
+
1. **Wrap your handler** with `withLambdaDeadline`. This stores the Lambda `context` (specifically
|
|
38
|
+
`getRemainingTimeInMillis()`) in `AsyncLocalStorage` so the SDK middleware can read it. The SDK middleware stack has
|
|
39
|
+
no access to the Lambda context on its own.
|
|
40
|
+
|
|
41
|
+
2. **Register the middleware** on each SDK client via the standard `middlewareStack.use()` pattern.
|
|
32
42
|
|
|
33
43
|
```typescript
|
|
34
|
-
import { withLambdaDeadline,
|
|
35
|
-
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
|
|
44
|
+
import { withLambdaDeadline, deadlineMiddleware } from "lambda-deadline-middleware";
|
|
45
|
+
import { DynamoDBClient, GetItemCommand } from "@aws-sdk/client-dynamodb";
|
|
46
|
+
|
|
47
|
+
const dynamodb = new DynamoDBClient({});
|
|
48
|
+
dynamodb.middlewareStack.use(deadlineMiddleware());
|
|
36
49
|
|
|
37
|
-
// 1. Wrap your Lambda handler
|
|
38
50
|
export const handler = withLambdaDeadline(async (event, context) => {
|
|
39
51
|
const result = await dynamodb.send(
|
|
40
52
|
new GetItemCommand({
|
|
@@ -43,112 +55,107 @@ export const handler = withLambdaDeadline(async (event, context) => {
|
|
|
43
55
|
);
|
|
44
56
|
return { statusCode: 200, body: JSON.stringify(result) };
|
|
45
57
|
});
|
|
46
|
-
|
|
47
|
-
// 2. Register middleware on SDK clients
|
|
48
|
-
const dynamodb = withDeadline(new DynamoDBClient({}));
|
|
49
58
|
```
|
|
50
59
|
|
|
51
|
-
|
|
60
|
+
Every SDK call through `dynamodb` now receives a timeout derived from the Lambda's remaining execution time minus a
|
|
61
|
+
configurable flush buffer (default: 1000ms).
|
|
52
62
|
|
|
53
|
-
##
|
|
63
|
+
## How It Works
|
|
54
64
|
|
|
55
65
|
```mermaid
|
|
56
|
-
|
|
57
|
-
subgraph
|
|
58
|
-
LR
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
|
66
|
+
flowchart LR
|
|
67
|
+
subgraph Lambda Invocation
|
|
68
|
+
direction LR
|
|
69
|
+
A[Lambda Runtime] --> B[withLambdaDeadline]
|
|
70
|
+
B --> C[Your Handler]
|
|
71
|
+
C --> D[SDK .send]
|
|
78
72
|
end
|
|
79
73
|
|
|
80
|
-
|
|
74
|
+
subgraph Per Attempt
|
|
75
|
+
direction LR
|
|
76
|
+
D --> E[Deadline Middleware]
|
|
77
|
+
E -->|getRemainingTimeInMillis\nminus flush buffer| F[AbortController\n+ setTimeout]
|
|
78
|
+
F --> G[HTTP Request]
|
|
79
|
+
end
|
|
81
80
|
|
|
82
|
-
style
|
|
83
|
-
style
|
|
84
|
-
style HW fill:#66bb6a,stroke:#2e7d32
|
|
81
|
+
style E fill:#f9a825,stroke:#f57f17
|
|
82
|
+
style B fill:#66bb6a,stroke:#2e7d32
|
|
85
83
|
```
|
|
86
84
|
|
|
87
|
-
|
|
85
|
+
`withLambdaDeadline` stores the Lambda context in `AsyncLocalStorage`. The deadline middleware reads it on every attempt
|
|
86
|
+
(including retries), computes a fresh timeout, and attaches an `AbortSignal` to the outgoing HTTP request.
|
|
88
87
|
|
|
89
88
|
## Configuration
|
|
90
89
|
|
|
91
90
|
### Flush Buffer
|
|
92
91
|
|
|
93
|
-
The flush buffer is subtracted from the remaining Lambda time to leave room for
|
|
92
|
+
The flush buffer is subtracted from the remaining Lambda time to leave room for graceful shutdown and error handling:
|
|
94
93
|
|
|
95
94
|
```typescript
|
|
96
|
-
// Default: 1000ms
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
// Custom: 500ms flush buffer
|
|
100
|
-
const client = withDeadline(new DynamoDBClient({}), { flushBufferMs: 500 });
|
|
95
|
+
// Default: 1000ms
|
|
96
|
+
dynamodb.middlewareStack.use(deadlineMiddleware());
|
|
101
97
|
|
|
102
|
-
//
|
|
103
|
-
|
|
98
|
+
// Custom: 500ms
|
|
99
|
+
dynamodb.middlewareStack.use(deadlineMiddleware({ flushBufferMs: 500 }));
|
|
104
100
|
```
|
|
105
101
|
|
|
106
|
-
|
|
102
|
+
## Error Handling
|
|
103
|
+
|
|
104
|
+
When remaining time is less than or equal to the flush buffer, the middleware throws `DeadlineExceededError` immediately
|
|
105
|
+
without dispatching an HTTP request.
|
|
107
106
|
|
|
108
107
|
```typescript
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
108
|
+
import { isDeadlineExceeded } from "lambda-deadline-middleware";
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
await dynamodb.send(
|
|
112
|
+
new GetItemCommand({
|
|
113
|
+
/* ... */
|
|
114
|
+
}),
|
|
115
|
+
);
|
|
116
|
+
} catch (error) {
|
|
117
|
+
if (isDeadlineExceeded(error)) {
|
|
118
|
+
console.log(`Deadline exceeded: ${error.deadlineMs}ms`);
|
|
119
|
+
console.log(`Remaining time was: ${error.remainingMs}ms`);
|
|
120
|
+
}
|
|
121
|
+
throw error;
|
|
122
|
+
}
|
|
115
123
|
```
|
|
116
124
|
|
|
117
|
-
|
|
125
|
+
## Signal Composition
|
|
118
126
|
|
|
119
|
-
|
|
127
|
+
If you pass an `AbortSignal` to a request, the middleware composes both signals:
|
|
120
128
|
|
|
121
129
|
```typescript
|
|
122
|
-
const
|
|
130
|
+
const controller = new AbortController();
|
|
131
|
+
setTimeout(() => controller.abort(), 5000);
|
|
132
|
+
|
|
133
|
+
await dynamodb.send(
|
|
134
|
+
new GetItemCommand({
|
|
135
|
+
/* ... */
|
|
136
|
+
}),
|
|
137
|
+
{
|
|
138
|
+
abortSignal: controller.signal,
|
|
139
|
+
},
|
|
140
|
+
);
|
|
123
141
|
```
|
|
124
142
|
|
|
125
143
|
## API Reference
|
|
126
144
|
|
|
127
|
-
### `withLambdaDeadline(handler
|
|
145
|
+
### `withLambdaDeadline(handler)`
|
|
128
146
|
|
|
129
|
-
Wraps a Lambda handler to
|
|
147
|
+
Wraps a Lambda handler to store the Lambda context in `AsyncLocalStorage`. Required for the middleware to access
|
|
148
|
+
`getRemainingTimeInMillis()`.
|
|
130
149
|
|
|
131
150
|
```typescript
|
|
132
151
|
function withLambdaDeadline<TEvent, TResult>(
|
|
133
152
|
handler: (event: TEvent, context: LambdaContextLike) => Promise<TResult>,
|
|
134
|
-
options?: DeadlineOptions,
|
|
135
153
|
): (event: TEvent, context: LambdaContextLike) => Promise<TResult>;
|
|
136
154
|
```
|
|
137
155
|
|
|
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
156
|
### `deadlineMiddleware(options?)`
|
|
150
157
|
|
|
151
|
-
|
|
158
|
+
Returns a `Pluggable` for `client.middlewareStack.use()`.
|
|
152
159
|
|
|
153
160
|
```typescript
|
|
154
161
|
function deadlineMiddleware(options?: DeadlineOptions): Pluggable<object, object>;
|
|
@@ -162,106 +169,39 @@ Accessor for the current Lambda's remaining execution time. Returns `undefined`
|
|
|
162
169
|
function getRemainingTimeInMillis(): number | undefined;
|
|
163
170
|
```
|
|
164
171
|
|
|
165
|
-
### `
|
|
172
|
+
### `isDeadlineExceeded(error)`
|
|
173
|
+
|
|
174
|
+
Type guard for deadline-triggered abort errors.
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
function isDeadlineExceeded(error: unknown): error is DeadlineExceededError;
|
|
178
|
+
```
|
|
166
179
|
|
|
167
|
-
|
|
180
|
+
### `DeadlineExceededError`
|
|
168
181
|
|
|
169
182
|
```typescript
|
|
170
183
|
class DeadlineExceededError extends Error {
|
|
171
184
|
readonly name: "DeadlineExceededError";
|
|
172
185
|
readonly deadlineMs: Milliseconds;
|
|
173
|
-
readonly flushBufferMs:
|
|
186
|
+
readonly flushBufferMs: Milliseconds;
|
|
174
187
|
readonly remainingMs: Milliseconds;
|
|
175
188
|
}
|
|
176
189
|
```
|
|
177
190
|
|
|
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
191
|
### `DeadlineOptions`
|
|
187
192
|
|
|
188
193
|
```typescript
|
|
189
194
|
interface DeadlineOptions {
|
|
190
195
|
readonly flushBufferMs?: number; // Default: 1000
|
|
191
|
-
readonly telemetryEnabled?: boolean; // Default: true
|
|
192
196
|
}
|
|
193
197
|
```
|
|
194
198
|
|
|
195
199
|
### Types
|
|
196
200
|
|
|
197
|
-
| Type
|
|
198
|
-
|
|
|
199
|
-
| `Milliseconds`
|
|
200
|
-
| `
|
|
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
|
-
```
|
|
201
|
+
| Type | Description |
|
|
202
|
+
| ------------------- | ------------------------------------------------------------ |
|
|
203
|
+
| `Milliseconds` | Branded number representing a duration in ms |
|
|
204
|
+
| `LambdaContextLike` | Minimal interface: `{ getRemainingTimeInMillis?(): number }` |
|
|
265
205
|
|
|
266
206
|
## Reporting Bugs
|
|
267
207
|
|
package/REUSE.toml
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
version = 1
|
|
2
|
+
SPDX-PackageName = "lambda-deadline-middleware"
|
|
3
|
+
SPDX-PackageSupplier = "https://github.com/mikkopiu/lambda-deadline-middleware/issues"
|
|
4
|
+
|
|
5
|
+
[[annotations]]
|
|
6
|
+
path = ["**/*"]
|
|
7
|
+
precedence = "aggregate"
|
|
8
|
+
SPDX-FileCopyrightText = "2026 lambda-deadline-middleware contributors"
|
|
9
|
+
SPDX-License-Identifier = "MIT"
|
package/SECURITY.md
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
<!-- SPDX-FileCopyrightText:
|
|
1
|
+
<!-- SPDX-FileCopyrightText: 2026 lambda-deadline-middleware contributors -->
|
|
2
2
|
<!-- SPDX-License-Identifier: MIT -->
|
|
3
3
|
|
|
4
4
|
# Security
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
Supply chain security practices for `lambda-deadline-middleware`.
|
|
7
7
|
|
|
8
8
|
## Provenance Verification
|
|
9
9
|
|
|
10
|
-
Every published release includes SLSA Level 3 provenance attestation, generated
|
|
11
|
-
|
|
10
|
+
Every published release includes SLSA Level 3 provenance attestation, generated by GitHub Actions via npm trusted
|
|
11
|
+
publishing (OIDC). No long-lived tokens are used. Authentication and provenance signing use short-lived OIDC
|
|
12
|
+
credentials.
|
|
12
13
|
|
|
13
14
|
### Verifying provenance
|
|
14
15
|
|
|
@@ -16,10 +17,10 @@ automatically by GitHub Actions using the `--provenance` flag during `npm publis
|
|
|
16
17
|
npm audit signatures
|
|
17
18
|
```
|
|
18
19
|
|
|
19
|
-
This
|
|
20
|
+
This verifies that the package was:
|
|
20
21
|
|
|
21
22
|
- Built from the source repository (not a compromised local machine)
|
|
22
|
-
- Published by the expected GitHub Actions workflow
|
|
23
|
+
- Published by the expected GitHub Actions workflow via OIDC
|
|
23
24
|
- Signed with a valid sigstore certificate tied to the workflow identity
|
|
24
25
|
|
|
25
26
|
You can also inspect the provenance bundle directly:
|
|
@@ -30,9 +31,8 @@ npm provenance --package-name lambda-deadline-middleware
|
|
|
30
31
|
|
|
31
32
|
## SBOM (Software Bill of Materials)
|
|
32
33
|
|
|
33
|
-
Each release includes a CycloneDX SBOM (`sbom.cdx.json`) attached as a GitHub
|
|
34
|
-
|
|
35
|
-
versions, providing full transparency into the package's dependency tree.
|
|
34
|
+
Each release includes a CycloneDX SBOM (`sbom.cdx.json`) attached as a GitHub Release asset. The SBOM lists all runtime
|
|
35
|
+
and development dependencies with their versions.
|
|
36
36
|
|
|
37
37
|
### Generating the SBOM locally
|
|
38
38
|
|
|
@@ -40,7 +40,7 @@ versions, providing full transparency into the package's dependency tree.
|
|
|
40
40
|
pnpm run sbom
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
This produces `sbom.cdx.json` in the project root using pnpm's native CycloneDX
|
|
43
|
+
This produces `sbom.cdx.json` in the project root using pnpm's native CycloneDX generation.
|
|
44
44
|
|
|
45
45
|
### SBOM format
|
|
46
46
|
|
|
@@ -49,30 +49,32 @@ This produces `sbom.cdx.json` in the project root using pnpm's native CycloneDX
|
|
|
49
49
|
- **Tool**: `pnpm sbom` (native, since pnpm 11)
|
|
50
50
|
- **Contents**: Component inventory including direct and transitive dependencies
|
|
51
51
|
|
|
52
|
-
##
|
|
52
|
+
## Trusted Publishing (OIDC)
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
This project uses npm trusted publishing via OpenID Connect (OIDC) identity federation. No long-lived npm tokens
|
|
55
|
+
(`NPM_TOKEN`) are stored as secrets. Authentication to the npm registry uses short-lived OIDC tokens issued by GitHub
|
|
56
|
+
Actions.
|
|
56
57
|
|
|
57
|
-
|
|
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.
|
|
58
|
+
### How it works
|
|
63
59
|
|
|
64
|
-
|
|
60
|
+
1. The release workflow requests an OIDC token from GitHub (via `id-token: write` permission).
|
|
61
|
+
2. semantic-release exchanges the OIDC token with the npm registry for a short-lived publish credential.
|
|
62
|
+
3. The package is published with provenance (`publishConfig.provenance: true`).
|
|
63
|
+
4. Fulcio issues a signing certificate tied to the workflow identity.
|
|
64
|
+
5. The signature is recorded in the Rekor transparency log.
|
|
65
|
+
6. npm stores the provenance bundle alongside the package tarball.
|
|
65
66
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
67
|
+
### Security benefits
|
|
68
|
+
|
|
69
|
+
- **No long-lived secrets**: No `NPM_TOKEN` to leak, rotate, or compromise.
|
|
70
|
+
- **Scoped trust**: Only the specific workflow in this repository can publish.
|
|
71
|
+
- **Automatic provenance**: SLSA Level 3 provenance without extra configuration.
|
|
72
|
+
- **Transparency log**: All signatures are recorded in Rekor for tamper-evident auditing.
|
|
73
|
+
- **No publisher keys needed**: Consumers verify using the sigstore root of trust.
|
|
71
74
|
|
|
72
75
|
### Verifying signatures
|
|
73
76
|
|
|
74
|
-
Signature verification is automatic when running `npm audit signatures`. The npm
|
|
75
|
-
CLI checks:
|
|
77
|
+
Signature verification is automatic when running `npm audit signatures`. The npm CLI checks:
|
|
76
78
|
|
|
77
79
|
- The certificate was issued by Fulcio
|
|
78
80
|
- The OIDC identity matches the expected repository and workflow
|
|
@@ -81,15 +83,9 @@ CLI checks:
|
|
|
81
83
|
|
|
82
84
|
## Reporting Vulnerabilities
|
|
83
85
|
|
|
84
|
-
If you discover a security vulnerability
|
|
85
|
-
|
|
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.
|
|
86
|
+
If you discover a security vulnerability, report it by opening a
|
|
87
|
+
[GitHub Security Advisory](https://github.com/mikkopiu/lambda-deadline-middleware/security/advisories/new) on this
|
|
88
|
+
repository. Do not file a public issue for security vulnerabilities.
|
|
93
89
|
|
|
94
|
-
Resolved vulnerabilities are disclosed
|
|
95
|
-
|
|
90
|
+
Reports are handled on a best-effort basis. Resolved vulnerabilities are disclosed via GitHub Security Advisories once a
|
|
91
|
+
fix is released.
|
package/dist/context-store.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export interface LambdaContextLike {
|
|
2
2
|
getRemainingTimeInMillis?: () => number;
|
|
3
3
|
}
|
|
4
|
-
export declare
|
|
5
|
-
export declare
|
|
4
|
+
export declare const run: <T>(context: LambdaContextLike | null | undefined, fn: () => T) => T;
|
|
5
|
+
export declare const getRemainingTimeInMillis: () => number | undefined;
|
|
6
6
|
|
|
7
7
|
//# sourceMappingURL=context-store.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"mappings":"
|
|
1
|
+
{"mappings":"AAOA,iBAAiB,kBAAkB;CACjC;AACF;AAIA,OAAO,cAAM,MAAO,GAAG,SAAS,sCAAsC,UAAU,MAAI;AAKpF,OAAO,cAAM","names":[],"sources":["src/context-store.ts"],"version":3,"sourcesContent":["// SPDX-FileCopyrightText: 2026 lambda-deadline-middleware contributors\n// SPDX-License-Identifier: MIT\n\nimport { AsyncLocalStorage } from \"node:async_hooks\";\n\n// AsyncLocalStorage propagates the Lambda context through the entire async call chain without parameter threading.\n// SDK middleware executes deep in the Smithy stack where we can't pass the context through function signatures.\nexport interface LambdaContextLike {\n getRemainingTimeInMillis?: () => number;\n}\n\nconst contextStorage = new AsyncLocalStorage<LambdaContextLike>();\n\nexport const run = <T>(context: LambdaContextLike | null | undefined, fn: () => T): T => {\n if (context === null || context === undefined) return fn();\n return contextStorage.run(context, fn);\n};\n\nexport const getRemainingTimeInMillis = (): number | undefined => {\n const store = contextStorage.getStore();\n if (store === undefined) return undefined;\n if (typeof store.getRemainingTimeInMillis !== \"function\") return undefined;\n return store.getRemainingTimeInMillis();\n};\n"]}
|
package/dist/context-store.js
CHANGED
|
@@ -1,24 +1,16 @@
|
|
|
1
|
-
// SPDX-FileCopyrightText:
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 lambda-deadline-middleware contributors
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
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
4
|
const contextStorage = new AsyncLocalStorage();
|
|
9
|
-
export
|
|
10
|
-
|
|
11
|
-
return contextStorage.run(
|
|
12
|
-
}
|
|
13
|
-
export
|
|
5
|
+
export const run = (context, fn) => {
|
|
6
|
+
if (context === null || context === undefined) return fn();
|
|
7
|
+
return contextStorage.run(context, fn);
|
|
8
|
+
};
|
|
9
|
+
export const getRemainingTimeInMillis = () => {
|
|
14
10
|
const store = contextStorage.getStore();
|
|
15
|
-
if (store === undefined
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
if (typeof store.getRemainingTimeInMillis !== "function") {
|
|
19
|
-
return undefined;
|
|
20
|
-
}
|
|
11
|
+
if (store === undefined) return undefined;
|
|
12
|
+
if (typeof store.getRemainingTimeInMillis !== "function") return undefined;
|
|
21
13
|
return store.getRemainingTimeInMillis();
|
|
22
|
-
}
|
|
14
|
+
};
|
|
23
15
|
|
|
24
16
|
//# sourceMappingURL=context-store.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"mappings":";;AAGA,SAAS,yBAAyB
|
|
1
|
+
{"mappings":";;AAGA,SAAS,yBAAyB;AAQlC,MAAM,iBAAiB,IAAI,kBAAqC;AAEhE,OAAO,MAAM,OAAU,SAA+C,OAAmB;CACvF,IAAI,YAAY,QAAQ,YAAY,WAAW,OAAO,GAAG;CACzD,OAAO,eAAe,IAAI,SAAS,EAAE;AACvC;AAEA,OAAO,MAAM,iCAAqD;CAChE,MAAM,QAAQ,eAAe,SAAS;CACtC,IAAI,UAAU,WAAW,OAAO;CAChC,IAAI,OAAO,MAAM,6BAA6B,YAAY,OAAO;CACjE,OAAO,MAAM,yBAAyB;AACxC","names":[],"sources":["src/context-store.ts"],"version":3,"sourcesContent":["// SPDX-FileCopyrightText: 2026 lambda-deadline-middleware contributors\n// SPDX-License-Identifier: MIT\n\nimport { AsyncLocalStorage } from \"node:async_hooks\";\n\n// AsyncLocalStorage propagates the Lambda context through the entire async call chain without parameter threading.\n// SDK middleware executes deep in the Smithy stack where we can't pass the context through function signatures.\nexport interface LambdaContextLike {\n getRemainingTimeInMillis?: () => number;\n}\n\nconst contextStorage = new AsyncLocalStorage<LambdaContextLike>();\n\nexport const run = <T>(context: LambdaContextLike | null | undefined, fn: () => T): T => {\n if (context === null || context === undefined) return fn();\n return contextStorage.run(context, fn);\n};\n\nexport const getRemainingTimeInMillis = (): number | undefined => {\n const store = contextStorage.getStore();\n if (store === undefined) return undefined;\n if (typeof store.getRemainingTimeInMillis !== \"function\") return undefined;\n return store.getRemainingTimeInMillis();\n};\n"]}
|
package/dist/error.d.ts
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Milliseconds } from "./types.js";
|
|
2
2
|
interface DeadlineExceededInit {
|
|
3
3
|
readonly deadlineMs: Milliseconds;
|
|
4
|
-
readonly flushBufferMs:
|
|
4
|
+
readonly flushBufferMs: Milliseconds;
|
|
5
5
|
readonly remainingMs: Milliseconds;
|
|
6
6
|
}
|
|
7
7
|
export declare class DeadlineExceededError extends Error {
|
|
8
8
|
override readonly name = "DeadlineExceededError";
|
|
9
9
|
readonly deadlineMs: Milliseconds;
|
|
10
|
-
readonly flushBufferMs:
|
|
10
|
+
readonly flushBufferMs: Milliseconds;
|
|
11
11
|
readonly remainingMs: Milliseconds;
|
|
12
12
|
constructor(init: DeadlineExceededInit);
|
|
13
13
|
}
|
|
14
|
-
export declare
|
|
14
|
+
export declare const isDeadlineExceeded: (error: unknown) => error is DeadlineExceededError;
|
|
15
15
|
export {};
|
|
16
16
|
|
|
17
17
|
//# sourceMappingURL=error.d.ts.map
|
package/dist/error.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"mappings":"AAGA,cAAc,
|
|
1
|
+
{"mappings":"AAGA,cAAc,oBAAoB;UAExB,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,cAAM,qBAAsB,mBAAiB,SAAS","names":[],"sources":["src/error.ts"],"version":3,"sourcesContent":["// SPDX-FileCopyrightText: 2026 lambda-deadline-middleware contributors\n// SPDX-License-Identifier: MIT\n\nimport type { Milliseconds } from \"./types.js\";\n\ninterface DeadlineExceededInit {\n readonly deadlineMs: Milliseconds;\n readonly flushBufferMs: Milliseconds;\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: Milliseconds;\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 const 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
CHANGED
|
@@ -12,10 +12,10 @@ export class DeadlineExceededError extends Error {
|
|
|
12
12
|
}
|
|
13
13
|
// Structural check rather than instanceof — works across module boundaries
|
|
14
14
|
// and serialization boundaries where prototype chain may be broken.
|
|
15
|
-
export
|
|
15
|
+
export const isDeadlineExceeded = (error) => {
|
|
16
16
|
if (error === null || error === undefined) return false;
|
|
17
17
|
if (typeof error !== "object") return false;
|
|
18
18
|
return error.name === "DeadlineExceededError";
|
|
19
|
-
}
|
|
19
|
+
};
|
|
20
20
|
|
|
21
21
|
//# sourceMappingURL=error.js.map
|
package/dist/error.js.map
CHANGED
|
@@ -1 +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,
|
|
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,MAAM,sBAAsB,UAAmD;CACpF,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: 2026 lambda-deadline-middleware contributors\n// SPDX-License-Identifier: MIT\n\nimport type { Milliseconds } from \"./types.js\";\n\ninterface DeadlineExceededInit {\n readonly deadlineMs: Milliseconds;\n readonly flushBufferMs: Milliseconds;\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: Milliseconds;\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 const 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"]}
|