@middy/core 5.0.3 → 5.2.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/index.d.ts +1 -0
- package/index.js +225 -156
- package/package.json +4 -3
package/index.d.ts
CHANGED
|
@@ -56,6 +56,7 @@ export interface MiddlewareObj<
|
|
|
56
56
|
before?: MiddlewareFn<TEvent, TResult, TErr, TContext, TInternal>
|
|
57
57
|
after?: MiddlewareFn<TEvent, TResult, TErr, TContext, TInternal>
|
|
58
58
|
onError?: MiddlewareFn<TEvent, TResult, TErr, TContext, TInternal>
|
|
59
|
+
name?: string
|
|
59
60
|
}
|
|
60
61
|
|
|
61
62
|
// The AWS provided Handler type uses void | Promise<TResult> so we have no choice but to follow and suppress the linter warning
|
package/index.js
CHANGED
|
@@ -1,165 +1,234 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
1
|
+
/* global awslambda */
|
|
2
|
+
import { Readable } from 'node:stream'
|
|
3
|
+
import { pipeline } from 'node:stream/promises'
|
|
4
|
+
import { setTimeout } from 'node:timers'
|
|
5
|
+
|
|
6
|
+
const defaultLambdaHandler = () => {}
|
|
5
7
|
const defaultPlugin = {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
8
|
+
timeoutEarlyInMillis: 5,
|
|
9
|
+
timeoutEarlyResponse: () => {
|
|
10
|
+
const err = new Error('[AbortError]: The operation was aborted.', {
|
|
11
|
+
cause: { package: '@middy/core' }
|
|
12
|
+
})
|
|
13
|
+
err.name = 'TimeoutError'
|
|
14
|
+
throw err
|
|
15
|
+
},
|
|
16
|
+
streamifyResponse: false // Deprecate need for this when AWS provides a flag for when it's looking for it
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const middy = (lambdaHandler = defaultLambdaHandler, plugin = {}) => {
|
|
20
|
+
// Allow base handler to be set using .handler()
|
|
21
|
+
if (typeof lambdaHandler !== 'function') {
|
|
22
|
+
plugin = lambdaHandler
|
|
23
|
+
lambdaHandler = defaultLambdaHandler
|
|
24
|
+
}
|
|
25
|
+
plugin = { ...defaultPlugin, ...plugin }
|
|
26
|
+
plugin.timeoutEarly = plugin.timeoutEarlyInMillis > 0
|
|
27
|
+
|
|
28
|
+
plugin.beforePrefetch?.()
|
|
29
|
+
const beforeMiddlewares = []
|
|
30
|
+
const afterMiddlewares = []
|
|
31
|
+
const onErrorMiddlewares = []
|
|
32
|
+
|
|
33
|
+
const middyHandler = (event = {}, context = {}) => {
|
|
34
|
+
plugin.requestStart?.()
|
|
35
|
+
const request = {
|
|
36
|
+
event,
|
|
37
|
+
context,
|
|
38
|
+
response: undefined,
|
|
39
|
+
error: undefined,
|
|
40
|
+
internal: plugin.internal ?? {}
|
|
22
41
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
} else if (typeof handlerBody === 'string') {
|
|
61
|
-
function* iterator(input) {
|
|
62
|
-
const size = 16384;
|
|
63
|
-
let position = 0;
|
|
64
|
-
const length = input.length;
|
|
65
|
-
while(position < length){
|
|
66
|
-
yield input.substring(position, position + size);
|
|
67
|
-
position += size;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
handlerStream = Readable.from(iterator(handlerBody));
|
|
71
|
-
}
|
|
72
|
-
if (!handlerStream) {
|
|
73
|
-
throw new Error('handler response not a ReadableStream');
|
|
74
|
-
}
|
|
75
|
-
await pipeline(handlerStream, responseStream);
|
|
76
|
-
}) : middyHandler;
|
|
77
|
-
middy.use = (middlewares)=>{
|
|
78
|
-
if (!Array.isArray(middlewares)) {
|
|
79
|
-
middlewares = [
|
|
80
|
-
middlewares
|
|
81
|
-
];
|
|
42
|
+
|
|
43
|
+
return runRequest(
|
|
44
|
+
request,
|
|
45
|
+
beforeMiddlewares,
|
|
46
|
+
lambdaHandler,
|
|
47
|
+
afterMiddlewares,
|
|
48
|
+
onErrorMiddlewares,
|
|
49
|
+
plugin
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
const middy = plugin.streamifyResponse
|
|
53
|
+
? awslambda.streamifyResponse(async (event, responseStream, context) => {
|
|
54
|
+
const handlerResponse = await middyHandler(event, context)
|
|
55
|
+
|
|
56
|
+
let handlerBody = handlerResponse
|
|
57
|
+
if (handlerResponse.statusCode) {
|
|
58
|
+
handlerBody = handlerResponse.body ?? ''
|
|
59
|
+
delete handlerResponse.body // #1137
|
|
60
|
+
responseStream = awslambda.HttpResponseStream.from(
|
|
61
|
+
responseStream,
|
|
62
|
+
handlerResponse
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Source @datastream/core (MIT)
|
|
67
|
+
let handlerStream
|
|
68
|
+
if (handlerBody._readableState) {
|
|
69
|
+
handlerStream = handlerBody
|
|
70
|
+
} else if (typeof handlerBody === 'string') {
|
|
71
|
+
function * iterator (input) {
|
|
72
|
+
const size = 16384 // 16 * 1024 // Node.js default
|
|
73
|
+
let position = 0
|
|
74
|
+
const length = input.length
|
|
75
|
+
while (position < length) {
|
|
76
|
+
yield input.substring(position, position + size)
|
|
77
|
+
position += size
|
|
78
|
+
}
|
|
82
79
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
80
|
+
handlerStream = Readable.from(iterator(handlerBody))
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (!handlerStream) {
|
|
84
|
+
throw new Error('handler response not a ReadableStream')
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
await pipeline(handlerStream, responseStream)
|
|
88
|
+
})
|
|
89
|
+
: middyHandler
|
|
90
|
+
|
|
91
|
+
middy.use = (middlewares) => {
|
|
92
|
+
if (!Array.isArray(middlewares)) {
|
|
93
|
+
middlewares = [middlewares]
|
|
94
|
+
}
|
|
95
|
+
for (const middleware of middlewares) {
|
|
96
|
+
const { before, after, onError } = middleware
|
|
97
|
+
|
|
98
|
+
if (before || after || onError) {
|
|
99
|
+
if (before) middy.before(before)
|
|
100
|
+
if (after) middy.after(after)
|
|
101
|
+
if (onError) middy.onError(onError)
|
|
102
|
+
} else {
|
|
103
|
+
throw new Error(
|
|
104
|
+
'Middleware must be an object containing at least one key among "before", "after", "onError"'
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return middy
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Inline Middlewares
|
|
112
|
+
middy.before = (beforeMiddleware) => {
|
|
113
|
+
beforeMiddlewares.push(beforeMiddleware)
|
|
114
|
+
return middy
|
|
115
|
+
}
|
|
116
|
+
middy.after = (afterMiddleware) => {
|
|
117
|
+
afterMiddlewares.unshift(afterMiddleware)
|
|
118
|
+
return middy
|
|
119
|
+
}
|
|
120
|
+
middy.onError = (onErrorMiddleware) => {
|
|
121
|
+
onErrorMiddlewares.unshift(onErrorMiddleware)
|
|
122
|
+
return middy
|
|
123
|
+
}
|
|
124
|
+
middy.handler = (replaceLambdaHandler) => {
|
|
125
|
+
lambdaHandler = replaceLambdaHandler
|
|
126
|
+
return middy
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return middy
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// shared AbortController, because it's slow
|
|
133
|
+
let handlerAbort = new AbortController()
|
|
134
|
+
const runRequest = async (
|
|
135
|
+
request,
|
|
136
|
+
beforeMiddlewares,
|
|
137
|
+
lambdaHandler,
|
|
138
|
+
afterMiddlewares,
|
|
139
|
+
onErrorMiddlewares,
|
|
140
|
+
plugin
|
|
141
|
+
) => {
|
|
142
|
+
let timeoutID
|
|
143
|
+
// context.getRemainingTimeInMillis checked for when AWS context missing (tests, containers)
|
|
144
|
+
const timeoutEarly =
|
|
145
|
+
plugin.timeoutEarly && request.context.getRemainingTimeInMillis
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
await runMiddlewares(request, beforeMiddlewares, plugin)
|
|
149
|
+
|
|
150
|
+
// Check if before stack hasn't exit early
|
|
151
|
+
if (typeof request.response === 'undefined') {
|
|
152
|
+
plugin.beforeHandler?.()
|
|
153
|
+
|
|
154
|
+
// Can't manually abort and timeout with same AbortSignal
|
|
155
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/timeout_static
|
|
156
|
+
if (handlerAbort.signal.aborted) {
|
|
157
|
+
handlerAbort = new AbortController()
|
|
158
|
+
}
|
|
159
|
+
const promises = [
|
|
160
|
+
lambdaHandler(request.event, request.context, {
|
|
161
|
+
signal: handlerAbort.signal
|
|
162
|
+
})
|
|
163
|
+
]
|
|
164
|
+
|
|
165
|
+
// clearTimeout pattern is 10x faster than using AbortController
|
|
166
|
+
// Note: signal.abort is slow ~6000ns
|
|
167
|
+
if (timeoutEarly) {
|
|
168
|
+
let timeoutResolve
|
|
169
|
+
const timeoutPromise = new Promise((resolve, reject) => {
|
|
170
|
+
timeoutResolve = () => {
|
|
171
|
+
handlerAbort.abort()
|
|
172
|
+
try {
|
|
173
|
+
resolve(plugin.timeoutEarlyResponse())
|
|
174
|
+
} catch (e) {
|
|
175
|
+
reject(e)
|
|
87
176
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const timeoutEarly = plugin.timeoutEarly && request.context.getRemainingTimeInMillis;
|
|
177
|
+
}
|
|
178
|
+
})
|
|
179
|
+
timeoutID = setTimeout(
|
|
180
|
+
timeoutResolve,
|
|
181
|
+
request.context.getRemainingTimeInMillis() -
|
|
182
|
+
plugin.timeoutEarlyInMillis
|
|
183
|
+
)
|
|
184
|
+
promises.push(timeoutPromise)
|
|
185
|
+
}
|
|
186
|
+
request.response = await Promise.race(promises)
|
|
187
|
+
if (timeoutID) {
|
|
188
|
+
clearTimeout(timeoutID)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
plugin.afterHandler?.()
|
|
192
|
+
await runMiddlewares(request, afterMiddlewares, plugin)
|
|
193
|
+
}
|
|
194
|
+
} catch (e) {
|
|
195
|
+
// timeout should be aborted when errors happen in handler
|
|
196
|
+
if (timeoutID) {
|
|
197
|
+
clearTimeout(timeoutID)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Reset response changes made by after stack before error thrown
|
|
201
|
+
request.response = undefined
|
|
202
|
+
request.error = e
|
|
115
203
|
try {
|
|
116
|
-
|
|
117
|
-
if (typeof request.response === 'undefined') {
|
|
118
|
-
plugin.beforeHandler?.();
|
|
119
|
-
const handlerAbort = new AbortController();
|
|
120
|
-
if (timeoutEarly) timeoutAbort = new AbortController();
|
|
121
|
-
request.response = await Promise.race([
|
|
122
|
-
lambdaHandler(request.event, request.context, {
|
|
123
|
-
signal: handlerAbort.signal
|
|
124
|
-
}),
|
|
125
|
-
timeoutEarly ? setTimeout(request.context.getRemainingTimeInMillis() - plugin.timeoutEarlyInMillis, undefined, {
|
|
126
|
-
signal: timeoutAbort.signal
|
|
127
|
-
}).then(()=>{
|
|
128
|
-
handlerAbort.abort();
|
|
129
|
-
return plugin.timeoutEarlyResponse();
|
|
130
|
-
}) : Promise.race([])
|
|
131
|
-
]);
|
|
132
|
-
timeoutAbort?.abort();
|
|
133
|
-
plugin.afterHandler?.();
|
|
134
|
-
await runMiddlewares(request, afterMiddlewares, plugin);
|
|
135
|
-
}
|
|
204
|
+
await runMiddlewares(request, onErrorMiddlewares, plugin)
|
|
136
205
|
} catch (e) {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
} catch (e) {
|
|
143
|
-
e.originalError = request.error;
|
|
144
|
-
request.error = e;
|
|
145
|
-
throw request.error;
|
|
146
|
-
}
|
|
147
|
-
if (typeof request.response === 'undefined') throw request.error;
|
|
148
|
-
} finally{
|
|
149
|
-
await plugin.requestEnd?.(request);
|
|
206
|
+
// Save error that wasn't handled
|
|
207
|
+
e.originalError = request.error
|
|
208
|
+
request.error = e
|
|
209
|
+
|
|
210
|
+
throw request.error
|
|
150
211
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
212
|
+
// Catch if onError stack hasn't handled the error
|
|
213
|
+
if (typeof request.response === 'undefined') throw request.error
|
|
214
|
+
} finally {
|
|
215
|
+
await plugin.requestEnd?.(request)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return request.response
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const runMiddlewares = async (request, middlewares, plugin) => {
|
|
222
|
+
for (const nextMiddleware of middlewares) {
|
|
223
|
+
plugin.beforeMiddleware?.(nextMiddleware.name)
|
|
224
|
+
const res = await nextMiddleware(request)
|
|
225
|
+
plugin.afterMiddleware?.(nextMiddleware.name)
|
|
226
|
+
// short circuit chaining and respond early
|
|
227
|
+
if (typeof res !== 'undefined') {
|
|
228
|
+
request.response = res
|
|
229
|
+
return
|
|
162
230
|
}
|
|
163
|
-
}
|
|
164
|
-
|
|
231
|
+
}
|
|
232
|
+
}
|
|
165
233
|
|
|
234
|
+
export default middy
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@middy/core",
|
|
3
|
-
"version": "5.0
|
|
3
|
+
"version": "5.2.0",
|
|
4
4
|
"description": "🛵 The stylish Node.js middleware engine for AWS Lambda (core package)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
"scripts": {
|
|
28
28
|
"test": "npm run test:unit",
|
|
29
29
|
"test:unit": "ava",
|
|
30
|
-
"test:benchmark": "node __benchmarks__/index.js"
|
|
30
|
+
"test:benchmark": "node __benchmarks__/index.js",
|
|
31
|
+
"test:profile": "node --prof __benchmarks__/index.js && node --prof-process --preprocess -j isolate*.log | speedscope -"
|
|
31
32
|
},
|
|
32
33
|
"license": "MIT",
|
|
33
34
|
"keywords": [
|
|
@@ -59,7 +60,7 @@
|
|
|
59
60
|
"@types/aws-lambda": "^8.10.76",
|
|
60
61
|
"@types/node": "^20.0.0"
|
|
61
62
|
},
|
|
62
|
-
"gitHead": "
|
|
63
|
+
"gitHead": "2d9096a49cd8fb62359517be96d6c93609df41f0",
|
|
63
64
|
"dependencies": {
|
|
64
65
|
"@datastream/core": "0.0.35"
|
|
65
66
|
}
|