@middy/core 3.0.0-alpha.2 → 3.0.0-alpha.3

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.
Files changed (3) hide show
  1. package/index.d.ts +3 -1
  2. package/index.js +207 -125
  3. package/package.json +4 -6
package/index.d.ts CHANGED
@@ -42,8 +42,10 @@ export interface MiddlewareObj<TEvent = any, TResult = any, TErr = Error, TConte
42
42
  // The AWS provided Handler type uses void | Promise<TResult> so we have no choice but to follow and suppress the linter warning
43
43
  // eslint-disable-next-line @typescript-eslint/no-invalid-void-type
44
44
  type MiddyInputHandler<TEvent, TResult, TContext extends LambdaContext = LambdaContext> = (event: TEvent, context: TContext, callback: LambdaCallback<TResult>) => void | Promise<TResult>
45
+ type MiddyInputPromiseHandler<TEvent, TResult, TContext extends LambdaContext = LambdaContext> = ( event: TEvent, context: TContext, ) => Promise<TResult>;
45
46
 
46
- export interface MiddyfiedHandler<TEvent = any, TResult = any, TErr = Error, TContext extends LambdaContext = LambdaContext> extends MiddyInputHandler<TEvent, TResult, TContext> {
47
+ export interface MiddyfiedHandler<TEvent = any, TResult = any, TErr = Error, TContext extends LambdaContext = LambdaContext> extends MiddyInputHandler<TEvent, TResult, TContext>,
48
+ MiddyInputPromiseHandler<TEvent, TResult, TContext> {
47
49
  use: UseFn<TEvent, TResult, TErr, TContext>
48
50
  before: AttachMiddlewareFn<TEvent, TResult, TErr, TContext>
49
51
  after: AttachMiddlewareFn<TEvent, TResult, TErr, TContext>
package/index.js CHANGED
@@ -1,168 +1,250 @@
1
- import { AbortController } from 'node-abort-controller';
2
-
3
- const defaultBaseHandler = () => {};
4
-
1
+ const defaultLambdaHandler = () => {}
5
2
  const defaultPlugin = {
6
3
  timeoutEarlyInMillis: 5,
7
- timeoutEarlyResponse: () => {
8
- throw new Error('Timeout');
4
+ timeoutEarlyResponse: () => { throw new Error('Timeout') }
5
+ }
6
+
7
+ const middy = (lambdaHandler = defaultLambdaHandler, plugin = {}) => {
8
+ // Allow base handler to be set using .handler()
9
+ if (typeof lambdaHandler !== 'function') {
10
+ plugin = lambdaHandler
11
+ lambdaHandler = defaultLambdaHandler
9
12
  }
10
- };
13
+ plugin = { ...defaultPlugin, ...plugin }
14
+ plugin.timeoutEarly = plugin.timeoutEarlyInMillis > 0
11
15
 
12
- const middy = (baseHandler = defaultBaseHandler, plugin = {}) => {
13
- var _plugin$beforePrefetc, _plugin;
14
-
15
- if (typeof baseHandler !== 'function') {
16
- plugin = baseHandler;
17
- baseHandler = defaultBaseHandler;
18
- }
19
-
20
- plugin = { ...defaultPlugin,
21
- ...plugin
22
- };
23
- (_plugin$beforePrefetc = (_plugin = plugin).beforePrefetch) === null || _plugin$beforePrefetc === void 0 ? void 0 : _plugin$beforePrefetc.call(_plugin);
24
- const beforeMiddlewares = [];
25
- const afterMiddlewares = [];
26
- const onErrorMiddlewares = [];
16
+ plugin.beforePrefetch?.()
17
+ const beforeMiddlewares = []
18
+ const afterMiddlewares = []
19
+ const onErrorMiddlewares = []
27
20
 
28
21
  const middy = (event = {}, context = {}) => {
29
- var _plugin$requestStart, _plugin2;
30
-
31
- (_plugin$requestStart = (_plugin2 = plugin).requestStart) === null || _plugin$requestStart === void 0 ? void 0 : _plugin$requestStart.call(_plugin2);
22
+ plugin.requestStart?.()
32
23
  const request = {
33
24
  event,
34
25
  context,
35
26
  response: undefined,
36
27
  error: undefined,
37
28
  internal: plugin.internal ?? {}
38
- };
39
- return runRequest(request, [...beforeMiddlewares], baseHandler, [...afterMiddlewares], [...onErrorMiddlewares], plugin);
40
- };
29
+ }
41
30
 
42
- middy.use = middlewares => {
31
+ return runRequest(
32
+ request,
33
+ [...beforeMiddlewares],
34
+ lambdaHandler,
35
+ [...afterMiddlewares],
36
+ [...onErrorMiddlewares],
37
+ plugin
38
+ )
39
+ }
40
+
41
+ middy.use = (middlewares) => {
43
42
  if (!Array.isArray(middlewares)) {
44
- middlewares = [middlewares];
43
+ middlewares = [middlewares]
45
44
  }
46
-
47
45
  for (const middleware of middlewares) {
48
- const {
49
- before,
50
- after,
51
- onError
52
- } = middleware;
46
+ const { before, after, onError } = middleware
53
47
 
54
48
  if (!before && !after && !onError) {
55
- throw new Error('Middleware must be an object containing at least one key among "before", "after", "onError"');
49
+ throw new Error(
50
+ 'Middleware must be an object containing at least one key among "before", "after", "onError"'
51
+ )
56
52
  }
57
53
 
58
- if (before) middy.before(before);
59
- if (after) middy.after(after);
60
- if (onError) middy.onError(onError);
54
+ if (before) middy.before(before)
55
+ if (after) middy.after(after)
56
+ if (onError) middy.onError(onError)
61
57
  }
58
+ return middy
59
+ }
62
60
 
63
- return middy;
64
- };
65
-
66
- middy.before = beforeMiddleware => {
67
- beforeMiddlewares.push(beforeMiddleware);
68
- return middy;
69
- };
70
-
71
- middy.after = afterMiddleware => {
72
- afterMiddlewares.unshift(afterMiddleware);
73
- return middy;
74
- };
75
-
76
- middy.onError = onErrorMiddleware => {
77
- onErrorMiddlewares.unshift(onErrorMiddleware);
78
- return middy;
79
- };
80
-
81
- middy.handler = replaceBaseHandler => {
82
- baseHandler = replaceBaseHandler;
83
- };
84
-
85
- return middy;
86
- };
61
+ // Inline Middlewares
62
+ middy.before = (beforeMiddleware) => {
63
+ beforeMiddlewares.push(beforeMiddleware)
64
+ return middy
65
+ }
66
+ middy.after = (afterMiddleware) => {
67
+ afterMiddlewares.unshift(afterMiddleware)
68
+ return middy
69
+ }
70
+ middy.onError = (onErrorMiddleware) => {
71
+ onErrorMiddlewares.unshift(onErrorMiddleware)
72
+ return middy
73
+ }
74
+ middy.handler = (replaceLambdaHandler) => {
75
+ lambdaHandler = replaceLambdaHandler
76
+ }
87
77
 
88
- const runRequest = async (request, beforeMiddlewares, baseHandler, afterMiddlewares, onErrorMiddlewares, plugin) => {
78
+ return middy
79
+ }
80
+
81
+ const runRequest = async (
82
+ request,
83
+ beforeMiddlewares,
84
+ lambdaHandler,
85
+ afterMiddlewares,
86
+ onErrorMiddlewares,
87
+ plugin
88
+ ) => {
89
+ const { timeoutEarly } = plugin
89
90
  try {
90
- await runMiddlewares(request, beforeMiddlewares, plugin);
91
-
91
+ await runMiddlewares(request, beforeMiddlewares, plugin)
92
+ // Check if before stack hasn't exit early
92
93
  if (request.response === undefined) {
93
- var _plugin$beforeHandler, _plugin$afterHandler;
94
-
95
- (_plugin$beforeHandler = plugin.beforeHandler) === null || _plugin$beforeHandler === void 0 ? void 0 : _plugin$beforeHandler.call(plugin);
96
- const handlerAbort = new AbortController();
97
- const timeoutAbort = new AbortController();
98
- request.response = await Promise.race([baseHandler(request.event, request.context, {
99
- signal: handlerAbort.signal
100
- }), plugin.timeoutEarlyInMillis > 0 ? setTimeoutPromise(request.context.getRemainingTimeInMillis() - plugin.timeoutEarlyInMillis, {
101
- signal: timeoutAbort.signal
102
- }).then(() => {
103
- handlerAbort.abort();
104
- return plugin.timeoutEarlyResponse();
105
- }) : Promise.race([])]);
106
- timeoutAbort.abort();
107
- (_plugin$afterHandler = plugin.afterHandler) === null || _plugin$afterHandler === void 0 ? void 0 : _plugin$afterHandler.call(plugin);
108
- await runMiddlewares(request, afterMiddlewares, plugin);
94
+ plugin.beforeHandler?.()
95
+
96
+ const handlerAbort = new AbortController()
97
+ let timeoutAbort
98
+ if (timeoutEarly) timeoutAbort = new AbortController()
99
+ request.response = await Promise.race([
100
+ lambdaHandler(request.event, request.context, { signal: handlerAbort.signal }),
101
+ timeoutEarly
102
+ ? setTimeoutPromise(request.context.getRemainingTimeInMillis() - plugin.timeoutEarlyInMillis, { signal: timeoutAbort.signal })
103
+ .then(() => {
104
+ handlerAbort.abort()
105
+ return plugin.timeoutEarlyResponse()
106
+ })
107
+ : Promise.race([])
108
+ ])
109
+ if (timeoutEarly) timeoutAbort.abort() // lambdaHandler may not be a promise
110
+
111
+ plugin.afterHandler?.()
112
+ await runMiddlewares(request, afterMiddlewares, plugin)
109
113
  }
110
114
  } catch (e) {
111
- request.response = undefined;
112
- request.error = e;
113
-
115
+ // Reset response changes made by after stack before error thrown
116
+ request.response = undefined
117
+ request.error = e
114
118
  try {
115
- await runMiddlewares(request, onErrorMiddlewares, plugin);
119
+ await runMiddlewares(request, onErrorMiddlewares, plugin)
116
120
  } catch (e) {
117
- e.originalError = request.error;
118
- request.error = e;
119
- throw request.error;
120
- }
121
+ // Save error that wasn't handled
122
+ e.originalError = request.error
123
+ request.error = e
121
124
 
122
- if (request.response === undefined) throw request.error;
125
+ throw request.error
126
+ }
127
+ // Catch if onError stack hasn't handled the error
128
+ if (request.response === undefined) throw request.error
123
129
  } finally {
124
- var _plugin$requestEnd;
125
-
126
- await ((_plugin$requestEnd = plugin.requestEnd) === null || _plugin$requestEnd === void 0 ? void 0 : _plugin$requestEnd.call(plugin, request));
130
+ await plugin.requestEnd?.(request)
127
131
  }
128
132
 
129
- return request.response;
130
- };
133
+ return request.response
134
+ }
131
135
 
132
136
  const runMiddlewares = async (request, middlewares, plugin) => {
133
137
  for (const nextMiddleware of middlewares) {
134
- var _plugin$beforeMiddlew, _plugin$afterMiddlewa;
138
+ plugin.beforeMiddleware?.(nextMiddleware.name)
139
+ const res = await nextMiddleware(request)
140
+ plugin.afterMiddleware?.(nextMiddleware.name)
141
+ // short circuit chaining and respond early
142
+ if (res !== undefined) {
143
+ request.response = res
144
+ return
145
+ }
146
+ }
147
+ }
148
+
149
+ // Start Polyfill (Nodejs v14)
150
+ /*
151
+ MIT License
152
+
153
+ Copyright (c) 2019 Steve Faulkner
154
+
155
+ node-abort-controller
156
+ */
157
+ const polyfillAbortController = async () => {
158
+ if (process.version < 'v15.0.0') {
159
+ const events = await import('events')
160
+ const { EventEmitter } = events
161
+
162
+ class AbortSignal {
163
+ constructor () {
164
+ this.eventEmitter = new EventEmitter()
165
+ this.onabort = null
166
+ this.aborted = false
167
+ }
135
168
 
136
- (_plugin$beforeMiddlew = plugin.beforeMiddleware) === null || _plugin$beforeMiddlew === void 0 ? void 0 : _plugin$beforeMiddlew.call(plugin, nextMiddleware.name);
137
- const res = await nextMiddleware(request);
138
- (_plugin$afterMiddlewa = plugin.afterMiddleware) === null || _plugin$afterMiddlewa === void 0 ? void 0 : _plugin$afterMiddlewa.call(plugin, nextMiddleware.name);
169
+ toString () {
170
+ return '[object AbortSignal]'
171
+ }
139
172
 
140
- if (res !== undefined) {
141
- request.response = res;
142
- return;
173
+ get [Symbol.toStringTag] () {
174
+ return 'AbortSignal'
175
+ }
176
+
177
+ removeEventListener (name, handler) {
178
+ this.eventEmitter.removeListener(name, handler)
179
+ }
180
+
181
+ addEventListener (name, handler) {
182
+ this.eventEmitter.on(name, handler)
183
+ }
184
+
185
+ dispatchEvent (type) {
186
+ const event = { type, target: this }
187
+ const handlerName = `on${type}`
188
+
189
+ if (typeof this[handlerName] === 'function') this[handlerName](event)
190
+
191
+ this.eventEmitter.emit(type, event)
192
+ }
143
193
  }
194
+
195
+ return class AbortController {
196
+ constructor () {
197
+ this.signal = new AbortSignal()
198
+ }
199
+
200
+ abort () {
201
+ if (this.signal.aborted) return
202
+
203
+ this.signal.aborted = true
204
+ this.signal.dispatchEvent('abort')
205
+ }
206
+
207
+ toString () {
208
+ return '[object AbortController]'
209
+ }
210
+
211
+ get [Symbol.toStringTag] () {
212
+ return 'AbortController'
213
+ }
214
+ }
215
+ } else {
216
+ return AbortController
144
217
  }
145
- };
218
+ }
219
+ global.AbortController = await polyfillAbortController()
146
220
 
147
- const setTimeoutPromise = (ms, {
148
- signal
149
- }) => {
150
- if (signal.aborted) {
151
- return Promise.reject(new Error('Aborted', 'AbortError'));
221
+ const polyfillSetTimeoutPromise = async () => {
222
+
223
+ if (process.version < 'v15.0.0') {
224
+ return (ms, { signal }) => {
225
+ if (signal.aborted) {
226
+ return Promise.reject(new Error('Aborted', 'AbortError'))
227
+ }
228
+ return new Promise((resolve, reject) => {
229
+ const abortHandler = () => {
230
+ clearTimeout(timeout)
231
+ reject(new Error('Aborted', 'AbortError'))
232
+ }
233
+ // start async operation
234
+ const timeout = setTimeout(() => {
235
+ resolve()
236
+ signal.removeEventListener('abort', abortHandler)
237
+ }, ms)
238
+ signal.addEventListener('abort', abortHandler)
239
+ })
240
+ }
241
+ } else {
242
+ const timers = await import('timers/promises')
243
+ return timers.setTimeout
152
244
  }
245
+ }
246
+ global.setTimeoutPromise = await polyfillSetTimeoutPromise()
247
+ // import { setTimeout as setTimeoutPromise } from 'timers/promises'
248
+ // End Polyfill
153
249
 
154
- return new Promise((resolve, reject) => {
155
- const abortHandler = () => {
156
- clearTimeout(timeout);
157
- reject(new Error('Aborted', 'AbortError'));
158
- };
159
-
160
- const timeout = setTimeout(() => {
161
- resolve();
162
- signal.removeEventListener('abort', abortHandler);
163
- }, ms);
164
- signal.addEventListener('abort', abortHandler);
165
- });
166
- };
167
-
168
- export default middy;
250
+ export default middy
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@middy/core",
3
- "version": "3.0.0-alpha.2",
3
+ "version": "3.0.0-alpha.3",
4
4
  "description": "🛵 The stylish Node.js middleware engine for AWS Lambda (core package)",
5
5
  "type": "module",
6
6
  "engines": {
@@ -18,7 +18,8 @@
18
18
  ],
19
19
  "scripts": {
20
20
  "test": "npm run test:unit",
21
- "test:unit": "ava"
21
+ "test:unit": "ava",
22
+ "test:benchmark": "node __benchmarks__/index.js"
22
23
  },
23
24
  "license": "MIT",
24
25
  "keywords": [
@@ -46,8 +47,5 @@
46
47
  "@types/aws-lambda": "^8.10.76",
47
48
  "@types/node": "^17.0.0"
48
49
  },
49
- "gitHead": "de30419273ecbff08f367f47c7e320ec981cf145",
50
- "dependencies": {
51
- "node-abort-controller": "3.0.1"
52
- }
50
+ "gitHead": "1441158711580313765e6d156046ef0fade0d156"
53
51
  }