@middy/core 2.5.6 → 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.
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2017-2021 Luciano Mammino, will Farrell and the [Middy team](https://github.com/middyjs/middy/graphs/contributors)
3
+ Copyright (c) 2017-2022 Luciano Mammino, will Farrell and the [Middy team](https://github.com/middyjs/middy/graphs/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/README.md CHANGED
@@ -47,7 +47,7 @@ Everyone is very welcome to contribute to this repository. Feel free to [raise i
47
47
 
48
48
  ## License
49
49
 
50
- Licensed under [MIT License](LICENSE). Copyright (c) 2017-2021 Luciano Mammino, will Farrell, and the [Middy team](https://github.com/middyjs/middy/graphs/contributors).
50
+ Licensed under [MIT License](LICENSE). Copyright (c) 2017-2022 Luciano Mammino, will Farrell, and the [Middy team](https://github.com/middyjs/middy/graphs/contributors).
51
51
 
52
52
  <a href="https://app.fossa.io/projects/git%2Bgithub.com%2Fmiddyjs%2Fmiddy?ref=badge_large">
53
53
  <img src="https://app.fossa.io/api/projects/git%2Bgithub.com%2Fmiddyjs%2Fmiddy.svg?type=large" alt="FOSSA Status" style="max-width:100%;">
package/index.d.ts CHANGED
@@ -9,11 +9,14 @@ declare type PluginHookWithMiddlewareName = (middlewareName: string) => void
9
9
  declare type PluginHookPromise = (request: Request) => Promise<unknown> | unknown
10
10
 
11
11
  interface PluginObject {
12
+ internal?: any
12
13
  beforePrefetch?: PluginHook
13
14
  requestStart?: PluginHook
14
15
  beforeMiddleware?: PluginHookWithMiddlewareName
15
16
  afterMiddleware?: PluginHookWithMiddlewareName
16
17
  beforeHandler?: PluginHook
18
+ timeoutEarlyInMillis?: number
19
+ timeoutEarlyResponse?: PluginHook
17
20
  afterHandler?: PluginHook
18
21
  requestEnd?: PluginHookPromise
19
22
  }
@@ -39,20 +42,14 @@ export interface MiddlewareObj<TEvent = any, TResult = any, TErr = Error, TConte
39
42
  // The AWS provided Handler type uses void | Promise<TResult> so we have no choice but to follow and suppress the linter warning
40
43
  // eslint-disable-next-line @typescript-eslint/no-invalid-void-type
41
44
  type MiddyInputHandler<TEvent, TResult, TContext extends LambdaContext = LambdaContext> = (event: TEvent, context: TContext, callback: LambdaCallback<TResult>) => void | Promise<TResult>
42
- type MiddyInputPromiseHandler<TEvent, TResult, TContext extends LambdaContext = LambdaContext> = (event: TEvent, context: TContext,) => Promise<TResult>
45
+ type MiddyInputPromiseHandler<TEvent, TResult, TContext extends LambdaContext = LambdaContext> = ( event: TEvent, context: TContext, ) => Promise<TResult>;
43
46
 
44
47
  export interface MiddyfiedHandler<TEvent = any, TResult = any, TErr = Error, TContext extends LambdaContext = LambdaContext> extends MiddyInputHandler<TEvent, TResult, TContext>,
45
48
  MiddyInputPromiseHandler<TEvent, TResult, TContext> {
46
49
  use: UseFn<TEvent, TResult, TErr, TContext>
47
- applyMiddleware: AttachMiddlewareObj<TEvent, TResult, TErr, TContext>
48
50
  before: AttachMiddlewareFn<TEvent, TResult, TErr, TContext>
49
51
  after: AttachMiddlewareFn<TEvent, TResult, TErr, TContext>
50
52
  onError: AttachMiddlewareFn<TEvent, TResult, TErr, TContext>
51
- __middlewares: {
52
- before: Array<MiddlewareFn<TEvent, TResult, TErr, TContext>>
53
- after: Array<MiddlewareFn<TEvent, TResult, TErr, TContext>>
54
- onError: Array<MiddlewareFn<TEvent, TResult, TErr, TContext>>
55
- }
56
53
  }
57
54
 
58
55
  declare type AttachMiddlewareFn<TEvent = any, TResult = any, TErr = Error, TContext extends LambdaContext = LambdaContext> = (middleware: MiddlewareFn) => MiddyfiedHandler<TEvent, TResult, TErr, TContext>
package/index.js CHANGED
@@ -1,93 +1,114 @@
1
- const middy = (baseHandler = () => {}, plugin) => {
2
- plugin?.beforePrefetch?.()
1
+ const defaultLambdaHandler = () => {}
2
+ const defaultPlugin = {
3
+ timeoutEarlyInMillis: 5,
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
12
+ }
13
+ plugin = { ...defaultPlugin, ...plugin }
14
+ plugin.timeoutEarly = plugin.timeoutEarlyInMillis > 0
15
+
16
+ plugin.beforePrefetch?.()
3
17
  const beforeMiddlewares = []
4
18
  const afterMiddlewares = []
5
19
  const onErrorMiddlewares = []
6
20
 
7
- const instance = (event = {}, context = {}) => {
8
- plugin?.requestStart?.()
21
+ const middy = (event = {}, context = {}) => {
22
+ plugin.requestStart?.()
9
23
  const request = {
10
24
  event,
11
25
  context,
12
26
  response: undefined,
13
27
  error: undefined,
14
- internal: {}
28
+ internal: plugin.internal ?? {}
15
29
  }
16
30
 
17
31
  return runRequest(
18
32
  request,
19
33
  [...beforeMiddlewares],
20
- baseHandler,
34
+ lambdaHandler,
21
35
  [...afterMiddlewares],
22
36
  [...onErrorMiddlewares],
23
37
  plugin
24
38
  )
25
39
  }
26
40
 
27
- instance.use = (middlewares) => {
28
- if (Array.isArray(middlewares)) {
29
- for (const middleware of middlewares) {
30
- instance.applyMiddleware(middleware)
31
- }
32
- return instance
41
+ middy.use = (middlewares) => {
42
+ if (!Array.isArray(middlewares)) {
43
+ middlewares = [middlewares]
33
44
  }
34
- return instance.applyMiddleware(middlewares)
35
- }
45
+ for (const middleware of middlewares) {
46
+ const { before, after, onError } = middleware
36
47
 
37
- instance.applyMiddleware = (middleware) => {
38
- const { before, after, onError } = middleware
48
+ if (!before && !after && !onError) {
49
+ throw new Error(
50
+ 'Middleware must be an object containing at least one key among "before", "after", "onError"'
51
+ )
52
+ }
39
53
 
40
- if (!before && !after && !onError) {
41
- throw new Error(
42
- 'Middleware must be an object containing at least one key among "before", "after", "onError"'
43
- )
54
+ if (before) middy.before(before)
55
+ if (after) middy.after(after)
56
+ if (onError) middy.onError(onError)
44
57
  }
45
-
46
- if (before) instance.before(before)
47
- if (after) instance.after(after)
48
- if (onError) instance.onError(onError)
49
-
50
- return instance
58
+ return middy
51
59
  }
52
60
 
53
61
  // Inline Middlewares
54
- instance.before = (beforeMiddleware) => {
62
+ middy.before = (beforeMiddleware) => {
55
63
  beforeMiddlewares.push(beforeMiddleware)
56
- return instance
64
+ return middy
57
65
  }
58
- instance.after = (afterMiddleware) => {
66
+ middy.after = (afterMiddleware) => {
59
67
  afterMiddlewares.unshift(afterMiddleware)
60
- return instance
68
+ return middy
61
69
  }
62
- instance.onError = (onErrorMiddleware) => {
63
- onErrorMiddlewares.push(onErrorMiddleware)
64
- return instance
70
+ middy.onError = (onErrorMiddleware) => {
71
+ onErrorMiddlewares.unshift(onErrorMiddleware)
72
+ return middy
65
73
  }
66
-
67
- instance.__middlewares = {
68
- before: beforeMiddlewares,
69
- after: afterMiddlewares,
70
- onError: onErrorMiddlewares
74
+ middy.handler = (replaceLambdaHandler) => {
75
+ lambdaHandler = replaceLambdaHandler
71
76
  }
72
77
 
73
- return instance
78
+ return middy
74
79
  }
75
80
 
76
81
  const runRequest = async (
77
82
  request,
78
83
  beforeMiddlewares,
79
- baseHandler,
84
+ lambdaHandler,
80
85
  afterMiddlewares,
81
86
  onErrorMiddlewares,
82
87
  plugin
83
88
  ) => {
89
+ const { timeoutEarly } = plugin
84
90
  try {
85
91
  await runMiddlewares(request, beforeMiddlewares, plugin)
86
92
  // Check if before stack hasn't exit early
87
93
  if (request.response === undefined) {
88
- plugin?.beforeHandler?.()
89
- request.response = await baseHandler(request.event, request.context)
90
- plugin?.afterHandler?.()
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?.()
91
112
  await runMiddlewares(request, afterMiddlewares, plugin)
92
113
  }
93
114
  } catch (e) {
@@ -106,7 +127,7 @@ const runRequest = async (
106
127
  // Catch if onError stack hasn't handled the error
107
128
  if (request.response === undefined) throw request.error
108
129
  } finally {
109
- await plugin?.requestEnd?.(request)
130
+ await plugin.requestEnd?.(request)
110
131
  }
111
132
 
112
133
  return request.response
@@ -114,9 +135,9 @@ const runRequest = async (
114
135
 
115
136
  const runMiddlewares = async (request, middlewares, plugin) => {
116
137
  for (const nextMiddleware of middlewares) {
117
- plugin?.beforeMiddleware?.(nextMiddleware?.name)
118
- const res = await nextMiddleware?.(request)
119
- plugin?.afterMiddleware?.(nextMiddleware?.name)
138
+ plugin.beforeMiddleware?.(nextMiddleware.name)
139
+ const res = await nextMiddleware(request)
140
+ plugin.afterMiddleware?.(nextMiddleware.name)
120
141
  // short circuit chaining and respond early
121
142
  if (res !== undefined) {
122
143
  request.response = res
@@ -125,4 +146,105 @@ const runMiddlewares = async (request, middlewares, plugin) => {
125
146
  }
126
147
  }
127
148
 
128
- module.exports = middy
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
+ }
168
+
169
+ toString () {
170
+ return '[object AbortSignal]'
171
+ }
172
+
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
+ }
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
217
+ }
218
+ }
219
+ global.AbortController = await polyfillAbortController()
220
+
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
244
+ }
245
+ }
246
+ global.setTimeoutPromise = await polyfillSetTimeoutPromise()
247
+ // import { setTimeout as setTimeoutPromise } from 'timers/promises'
248
+ // End Polyfill
249
+
250
+ export default middy
package/package.json CHANGED
@@ -1,23 +1,25 @@
1
1
  {
2
2
  "name": "@middy/core",
3
- "version": "2.5.6",
3
+ "version": "3.0.0-alpha.3",
4
4
  "description": "🛵 The stylish Node.js middleware engine for AWS Lambda (core package)",
5
- "type": "commonjs",
5
+ "type": "module",
6
6
  "engines": {
7
- "node": ">=12"
7
+ "node": ">=14"
8
8
  },
9
9
  "engineStrict": true,
10
10
  "publishConfig": {
11
11
  "access": "public"
12
12
  },
13
- "main": "index.js",
13
+ "exports": "./index.js",
14
14
  "types": "index.d.ts",
15
15
  "files": [
16
+ "index.js",
16
17
  "index.d.ts"
17
18
  ],
18
19
  "scripts": {
19
20
  "test": "npm run test:unit",
20
- "test:unit": "ava"
21
+ "test:unit": "ava",
22
+ "test:benchmark": "node __benchmarks__/index.js"
21
23
  },
22
24
  "license": "MIT",
23
25
  "keywords": [
@@ -43,7 +45,7 @@
43
45
  "homepage": "https://github.com/middyjs/middy#readme",
44
46
  "devDependencies": {
45
47
  "@types/aws-lambda": "^8.10.76",
46
- "@types/node": "^16.0.0"
48
+ "@types/node": "^17.0.0"
47
49
  },
48
- "gitHead": "0c789f55b4adf691f977b0d9904d1a805bb3bb2b"
50
+ "gitHead": "1441158711580313765e6d156046ef0fade0d156"
49
51
  }