@middy/core
Advanced tools
Comparing version 5.1.0 to 5.2.0
381
index.js
@@ -1,165 +0,234 @@ | ||
import { Readable } from 'node:stream'; | ||
import { pipeline } from 'node:stream/promises'; | ||
import { setTimeout } from 'node:timers/promises'; | ||
const defaultLambdaHandler = ()=>{}; | ||
/* global awslambda */ | ||
import { Readable } from 'node:stream' | ||
import { pipeline } from 'node:stream/promises' | ||
import { setTimeout } from 'node:timers' | ||
const defaultLambdaHandler = () => {} | ||
const defaultPlugin = { | ||
timeoutEarlyInMillis: 5, | ||
timeoutEarlyResponse: ()=>{ | ||
const err = new Error('[AbortError]: The operation was aborted.', { | ||
cause: { | ||
package: '@middy/core' | ||
} | ||
}); | ||
err.name = 'TimeoutError'; | ||
throw err; | ||
}, | ||
streamifyResponse: false | ||
}; | ||
const middy = (lambdaHandler = defaultLambdaHandler, plugin = {})=>{ | ||
if (typeof lambdaHandler !== 'function') { | ||
plugin = lambdaHandler; | ||
lambdaHandler = defaultLambdaHandler; | ||
timeoutEarlyInMillis: 5, | ||
timeoutEarlyResponse: () => { | ||
const err = new Error('[AbortError]: The operation was aborted.', { | ||
cause: { package: '@middy/core' } | ||
}) | ||
err.name = 'TimeoutError' | ||
throw err | ||
}, | ||
streamifyResponse: false // Deprecate need for this when AWS provides a flag for when it's looking for it | ||
} | ||
const middy = (lambdaHandler = defaultLambdaHandler, plugin = {}) => { | ||
// Allow base handler to be set using .handler() | ||
if (typeof lambdaHandler !== 'function') { | ||
plugin = lambdaHandler | ||
lambdaHandler = defaultLambdaHandler | ||
} | ||
plugin = { ...defaultPlugin, ...plugin } | ||
plugin.timeoutEarly = plugin.timeoutEarlyInMillis > 0 | ||
plugin.beforePrefetch?.() | ||
const beforeMiddlewares = [] | ||
const afterMiddlewares = [] | ||
const onErrorMiddlewares = [] | ||
const middyHandler = (event = {}, context = {}) => { | ||
plugin.requestStart?.() | ||
const request = { | ||
event, | ||
context, | ||
response: undefined, | ||
error: undefined, | ||
internal: plugin.internal ?? {} | ||
} | ||
plugin = { | ||
...defaultPlugin, | ||
...plugin | ||
}; | ||
plugin.timeoutEarly = plugin.timeoutEarlyInMillis > 0; | ||
plugin.beforePrefetch?.(); | ||
const beforeMiddlewares = []; | ||
const afterMiddlewares = []; | ||
const onErrorMiddlewares = []; | ||
const middyHandler = (event = {}, context = {})=>{ | ||
plugin.requestStart?.(); | ||
const request = { | ||
event, | ||
context, | ||
response: undefined, | ||
error: undefined, | ||
internal: plugin.internal ?? {} | ||
}; | ||
return runRequest(request, [ | ||
...beforeMiddlewares | ||
], lambdaHandler, [ | ||
...afterMiddlewares | ||
], [ | ||
...onErrorMiddlewares | ||
], plugin); | ||
}; | ||
const middy = plugin.streamifyResponse ? awslambda.streamifyResponse(async (event, responseStream, context)=>{ | ||
const handlerResponse = await middyHandler(event, context); | ||
let handlerBody = handlerResponse; | ||
if (handlerResponse.statusCode) { | ||
handlerBody = handlerResponse.body ?? ''; | ||
delete handlerResponse.body; | ||
responseStream = awslambda.HttpResponseStream.from(responseStream, handlerResponse); | ||
return runRequest( | ||
request, | ||
beforeMiddlewares, | ||
lambdaHandler, | ||
afterMiddlewares, | ||
onErrorMiddlewares, | ||
plugin | ||
) | ||
} | ||
const middy = plugin.streamifyResponse | ||
? awslambda.streamifyResponse(async (event, responseStream, context) => { | ||
const handlerResponse = await middyHandler(event, context) | ||
let handlerBody = handlerResponse | ||
if (handlerResponse.statusCode) { | ||
handlerBody = handlerResponse.body ?? '' | ||
delete handlerResponse.body // #1137 | ||
responseStream = awslambda.HttpResponseStream.from( | ||
responseStream, | ||
handlerResponse | ||
) | ||
} | ||
// Source @datastream/core (MIT) | ||
let handlerStream | ||
if (handlerBody._readableState) { | ||
handlerStream = handlerBody | ||
} else if (typeof handlerBody === 'string') { | ||
function * iterator (input) { | ||
const size = 16384 // 16 * 1024 // Node.js default | ||
let position = 0 | ||
const length = input.length | ||
while (position < length) { | ||
yield input.substring(position, position + size) | ||
position += size | ||
} | ||
} | ||
let handlerStream; | ||
if (handlerBody._readableState) { | ||
handlerStream = handlerBody; | ||
} else if (typeof handlerBody === 'string') { | ||
function* iterator(input) { | ||
const size = 16384; | ||
let position = 0; | ||
const length = input.length; | ||
while(position < length){ | ||
yield input.substring(position, position + size); | ||
position += size; | ||
} | ||
handlerStream = Readable.from(iterator(handlerBody)) | ||
} | ||
if (!handlerStream) { | ||
throw new Error('handler response not a ReadableStream') | ||
} | ||
await pipeline(handlerStream, responseStream) | ||
}) | ||
: middyHandler | ||
middy.use = (middlewares) => { | ||
if (!Array.isArray(middlewares)) { | ||
middlewares = [middlewares] | ||
} | ||
for (const middleware of middlewares) { | ||
const { before, after, onError } = middleware | ||
if (before || after || onError) { | ||
if (before) middy.before(before) | ||
if (after) middy.after(after) | ||
if (onError) middy.onError(onError) | ||
} else { | ||
throw new Error( | ||
'Middleware must be an object containing at least one key among "before", "after", "onError"' | ||
) | ||
} | ||
} | ||
return middy | ||
} | ||
// Inline Middlewares | ||
middy.before = (beforeMiddleware) => { | ||
beforeMiddlewares.push(beforeMiddleware) | ||
return middy | ||
} | ||
middy.after = (afterMiddleware) => { | ||
afterMiddlewares.unshift(afterMiddleware) | ||
return middy | ||
} | ||
middy.onError = (onErrorMiddleware) => { | ||
onErrorMiddlewares.unshift(onErrorMiddleware) | ||
return middy | ||
} | ||
middy.handler = (replaceLambdaHandler) => { | ||
lambdaHandler = replaceLambdaHandler | ||
return middy | ||
} | ||
return middy | ||
} | ||
// shared AbortController, because it's slow | ||
let handlerAbort = new AbortController() | ||
const runRequest = async ( | ||
request, | ||
beforeMiddlewares, | ||
lambdaHandler, | ||
afterMiddlewares, | ||
onErrorMiddlewares, | ||
plugin | ||
) => { | ||
let timeoutID | ||
// context.getRemainingTimeInMillis checked for when AWS context missing (tests, containers) | ||
const timeoutEarly = | ||
plugin.timeoutEarly && request.context.getRemainingTimeInMillis | ||
try { | ||
await runMiddlewares(request, beforeMiddlewares, plugin) | ||
// Check if before stack hasn't exit early | ||
if (typeof request.response === 'undefined') { | ||
plugin.beforeHandler?.() | ||
// Can't manually abort and timeout with same AbortSignal | ||
// https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/timeout_static | ||
if (handlerAbort.signal.aborted) { | ||
handlerAbort = new AbortController() | ||
} | ||
const promises = [ | ||
lambdaHandler(request.event, request.context, { | ||
signal: handlerAbort.signal | ||
}) | ||
] | ||
// clearTimeout pattern is 10x faster than using AbortController | ||
// Note: signal.abort is slow ~6000ns | ||
if (timeoutEarly) { | ||
let timeoutResolve | ||
const timeoutPromise = new Promise((resolve, reject) => { | ||
timeoutResolve = () => { | ||
handlerAbort.abort() | ||
try { | ||
resolve(plugin.timeoutEarlyResponse()) | ||
} catch (e) { | ||
reject(e) | ||
} | ||
handlerStream = Readable.from(iterator(handlerBody)); | ||
} | ||
if (!handlerStream) { | ||
throw new Error('handler response not a ReadableStream'); | ||
} | ||
await pipeline(handlerStream, responseStream); | ||
}) : middyHandler; | ||
middy.use = (middlewares)=>{ | ||
if (!Array.isArray(middlewares)) { | ||
middlewares = [ | ||
middlewares | ||
]; | ||
} | ||
for (const middleware of middlewares){ | ||
const { before, after, onError } = middleware; | ||
if (!before && !after && !onError) { | ||
throw new Error('Middleware must be an object containing at least one key among "before", "after", "onError"'); | ||
} | ||
if (before) middy.before(before); | ||
if (after) middy.after(after); | ||
if (onError) middy.onError(onError); | ||
} | ||
return middy; | ||
}; | ||
middy.before = (beforeMiddleware)=>{ | ||
beforeMiddlewares.push(beforeMiddleware); | ||
return middy; | ||
}; | ||
middy.after = (afterMiddleware)=>{ | ||
afterMiddlewares.unshift(afterMiddleware); | ||
return middy; | ||
}; | ||
middy.onError = (onErrorMiddleware)=>{ | ||
onErrorMiddlewares.unshift(onErrorMiddleware); | ||
return middy; | ||
}; | ||
middy.handler = (replaceLambdaHandler)=>{ | ||
lambdaHandler = replaceLambdaHandler; | ||
return middy; | ||
}; | ||
return middy; | ||
}; | ||
const runRequest = async (request, beforeMiddlewares, lambdaHandler, afterMiddlewares, onErrorMiddlewares, plugin)=>{ | ||
let timeoutAbort; | ||
const timeoutEarly = plugin.timeoutEarly && request.context.getRemainingTimeInMillis; | ||
} | ||
}) | ||
timeoutID = setTimeout( | ||
timeoutResolve, | ||
request.context.getRemainingTimeInMillis() - | ||
plugin.timeoutEarlyInMillis | ||
) | ||
promises.push(timeoutPromise) | ||
} | ||
request.response = await Promise.race(promises) | ||
if (timeoutID) { | ||
clearTimeout(timeoutID) | ||
} | ||
plugin.afterHandler?.() | ||
await runMiddlewares(request, afterMiddlewares, plugin) | ||
} | ||
} catch (e) { | ||
// timeout should be aborted when errors happen in handler | ||
if (timeoutID) { | ||
clearTimeout(timeoutID) | ||
} | ||
// Reset response changes made by after stack before error thrown | ||
request.response = undefined | ||
request.error = e | ||
try { | ||
await runMiddlewares(request, beforeMiddlewares, plugin); | ||
if (typeof request.response === 'undefined') { | ||
plugin.beforeHandler?.(); | ||
const handlerAbort = new AbortController(); | ||
if (timeoutEarly) timeoutAbort = new AbortController(); | ||
request.response = await Promise.race([ | ||
lambdaHandler(request.event, request.context, { | ||
signal: handlerAbort.signal | ||
}), | ||
timeoutEarly ? setTimeout(request.context.getRemainingTimeInMillis() - plugin.timeoutEarlyInMillis, undefined, { | ||
signal: timeoutAbort.signal | ||
}).then(()=>{ | ||
handlerAbort.abort(); | ||
return plugin.timeoutEarlyResponse(); | ||
}) : Promise.race([]) | ||
]); | ||
timeoutAbort?.abort(); | ||
plugin.afterHandler?.(); | ||
await runMiddlewares(request, afterMiddlewares, plugin); | ||
} | ||
await runMiddlewares(request, onErrorMiddlewares, plugin) | ||
} catch (e) { | ||
timeoutAbort?.abort(); | ||
request.response = undefined; | ||
request.error = e; | ||
try { | ||
await runMiddlewares(request, onErrorMiddlewares, plugin); | ||
} catch (e) { | ||
e.originalError = request.error; | ||
request.error = e; | ||
throw request.error; | ||
} | ||
if (typeof request.response === 'undefined') throw request.error; | ||
} finally{ | ||
await plugin.requestEnd?.(request); | ||
// Save error that wasn't handled | ||
e.originalError = request.error | ||
request.error = e | ||
throw request.error | ||
} | ||
return request.response; | ||
}; | ||
const runMiddlewares = async (request, middlewares, plugin)=>{ | ||
for (const nextMiddleware of middlewares){ | ||
plugin.beforeMiddleware?.(nextMiddleware.name); | ||
const res = await nextMiddleware(request); | ||
plugin.afterMiddleware?.(nextMiddleware.name); | ||
if (typeof res !== 'undefined') { | ||
request.response = res; | ||
return; | ||
} | ||
// Catch if onError stack hasn't handled the error | ||
if (typeof request.response === 'undefined') throw request.error | ||
} finally { | ||
await plugin.requestEnd?.(request) | ||
} | ||
return request.response | ||
} | ||
const runMiddlewares = async (request, middlewares, plugin) => { | ||
for (const nextMiddleware of middlewares) { | ||
plugin.beforeMiddleware?.(nextMiddleware.name) | ||
const res = await nextMiddleware(request) | ||
plugin.afterMiddleware?.(nextMiddleware.name) | ||
// short circuit chaining and respond early | ||
if (typeof res !== 'undefined') { | ||
request.response = res | ||
return | ||
} | ||
}; | ||
export default middy; | ||
} | ||
} | ||
export default middy |
{ | ||
"name": "@middy/core", | ||
"version": "5.1.0", | ||
"version": "5.2.0", | ||
"description": "🛵 The stylish Node.js middleware engine for AWS Lambda (core package)", | ||
@@ -30,3 +30,4 @@ "type": "module", | ||
"test:unit": "ava", | ||
"test:benchmark": "node __benchmarks__/index.js" | ||
"test:benchmark": "node __benchmarks__/index.js", | ||
"test:profile": "node --prof __benchmarks__/index.js && node --prof-process --preprocess -j isolate*.log | speedscope -" | ||
}, | ||
@@ -63,3 +64,3 @@ "license": "MIT", | ||
}, | ||
"gitHead": "bbdaf5843914921804ba085dd58117273febe6b5", | ||
"gitHead": "2d9096a49cd8fb62359517be96d6c93609df41f0", | ||
"dependencies": { | ||
@@ -66,0 +67,0 @@ "@datastream/core": "0.0.35" |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
18145
377