Skip to content

Maintenance: evaluate migration to Middy 3.x #875

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
dreamorosi opened this issue May 13, 2022 · 5 comments
Closed

Maintenance: evaluate migration to Middy 3.x #875

dreamorosi opened this issue May 13, 2022 · 5 comments
Assignees
Labels
completed This item is complete and has been merged/shipped dependencies Changes that touch dependencies, e.g. Dependabot, etc.

Comments

@dreamorosi
Copy link
Contributor

Description of the feature request

Problem statement
Middy, the dependency that we use to vend middleware has recently released a new major version (v 3.x). This version drops support for Node JS version 12.x and introduces some breaking changes.

Given that Middy, as a whole, has a large surface and we are only using @middy/core we need to investigate on whether there's a path to upgrade to the newer version while still continuing to support Node JS 12.x in Powertools.

Additional context
Running the e2e tests on a branch that upgraded @middy/core to 3.0.1 seems to show that the newer release is compatible with Powertools on both versions 12 and 14 of Node (see run results).

At the same time, the unit tests for that same branch are failing for all utilities and specifically in the section that relates the middleware implementations. The error message (see detail below) seems to hint at incompatibilities between the new bundling of middy that now supports both CJS and ESM bundling, and our project's configuration since the errors are related to the imports and the tests don't run at all.

 FAIL   AWS Lambda Powertools utility: TRACER  tests/unit/middy.test.ts
  ● Test suite failed to run

    Jest encountered an unexpected token

    Jest failed to parse a file. This happens e.g. when your code or its dependencies use non-standard JavaScript syntax, or when Jest is not configured to support such syntax.

    Out of the box Jest supports Babel, which will be used to transform your files into valid JS based on your Babel configuration.

    By default "node_modules" folder is ignored by transformers.

    Here's what you can do:
     • If you are trying to use ECMAScript Modules, see https://jestjs.io/docs/ecmascript-modules for how to enable it.
     • If you are trying to use TypeScript, see https://jestjs.io/docs/getting-started#using-typescript
     • To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config.
     • If you need a custom transformation specify a "transform" option in your config.
     • If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the "moduleNameMapper" config option.

    You'll find more details and examples of these config options in the docs:
    https://jestjs.io/docs/configuration
    For information about custom transformations, see:
    https://jestjs.io/docs/code-transformation

    Details:

    /home/ec2-user/aws-lambda-powertools-typescript/node_modules/@middy/core/index.js:1
    ({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,jest){import{EventEmitter}from"events";const defaultLambdaHandler=()=>{};const defaultPlugin={timeoutEarlyInMillis:5,timeoutEarlyResponse:()=>{throw new Error("Timeout")}};const middy=(lambdaHandler=defaultLambdaHandler,plugin={})=>{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 middy1=(event={},context={})=>{plugin.requestStart?.();const request={event,context,response:undefined,error:undefined,internal:plugin.internal??{}};return runRequest(request,[...beforeMiddlewares],lambdaHandler,[...afterMiddlewares],[...onErrorMiddlewares],plugin)};middy1.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)middy1.before(before);if(after)middy1.after(after);if(onError)middy1.onError(onError)}return middy1};middy1.before=beforeMiddleware=>{beforeMiddlewares.push(beforeMiddleware);return middy1};middy1.after=afterMiddleware=>{afterMiddlewares.unshift(afterMiddleware);return middy1};middy1.onError=onErrorMiddleware=>{onErrorMiddlewares.unshift(onErrorMiddleware);return middy1};middy1.handler=replaceLambdaHandler=>{lambdaHandler=replaceLambdaHandler;return middy1};return middy1};const runRequest=async(request,beforeMiddlewares,lambdaHandler,afterMiddlewares,onErrorMiddlewares,plugin)=>{const timeoutEarly=plugin.timeoutEarly&&request.context.getRemainingTimeInMillis;try{await runMiddlewares(request,beforeMiddlewares,plugin);if(request.response===undefined){plugin.beforeHandler?.();const handlerAbort=new AbortController;let timeoutAbort;if(timeoutEarly)timeoutAbort=new AbortController;request.response=await Promise.race([lambdaHandler(request.event,request.context,{signal:handlerAbort.signal}),timeoutEarly?setTimeoutPromise(request.context.getRemainingTimeInMillis()-plugin.timeoutEarlyInMillis,{signal:timeoutAbort.signal}).then(()=>{handlerAbort.abort();return plugin.timeoutEarlyResponse()}):Promise.race([])]);if(timeoutEarly)timeoutAbort.abort();plugin.afterHandler?.();await runMiddlewares(request,afterMiddlewares,plugin)}}catch(e){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(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);if(res!==undefined){request.response=res;return}}};const polyfillAbortController=()=>{if(process.version<"v15.0.0"){let _toStringTag;let _toStringTag1=Symbol.toStringTag;class AbortSignal{toString(){return"[object AbortSignal]"}get[_toStringTag1](){return"AbortSignal"}removeEventListener(name,handler){this.eventEmitter.removeListener(name,handler)}addEventListener(name,handler){this.eventEmitter.on(name,handler)}dispatchEvent(type){const event={type,target:this};const handlerName=`on${type}`;if(typeof this[handlerName]==="function")this[handlerName](event);this.eventEmitter.emit(type,event)}constructor(){this.eventEmitter=new EventEmitter;this.onabort=null;this.aborted=false}}return _toStringTag=Symbol.toStringTag,class AbortController{abort(){if(this.signal.aborted)return;this.signal.aborted=true;this.signal.dispatchEvent("abort")}toString(){return"[object AbortController]"}get[_toStringTag](){return"AbortController"}constructor(){this.signal=new AbortSignal}}}else{return AbortController}};global.AbortController=polyfillAbortController();const polyfillSetTimeoutPromise=()=>{return(ms,{signal})=>{if(signal.aborted){return Promise.reject(new Error("Aborted","AbortError"))}return new Promise((resolve,reject)=>{const abortHandler=()=>{clearTimeout(timeout);reject(new Error("Aborted","AbortError"))};const timeout=setTimeout(()=>{resolve();signal.removeEventListener("abort",abortHandler)},ms);signal.addEventListener("abort",abortHandler)})}};const setTimeoutPromise=polyfillSetTimeoutPromise();export default middy
                                                                                      ^^^^^^

    SyntaxError: Cannot use import statement outside a module

       6 |
       7 | import { captureLambdaHandler } from '../../src/middleware/middy';
    >  8 | import middy from '@middy/core';
         | ^
       9 | import { Tracer } from './../../src';
      10 | import type { Context, Handler } from 'aws-lambda/handler';
      11 | import { Segment, setContextMissingStrategy, Subsegment } from 'aws-xray-sdk-core';

      at Runtime.createScriptFromCode (../../node_modules/jest-runtime/build/index.js:1728:14)
      at Object.<anonymous> (tests/unit/middy.test.ts:8:1)

Code examples
N/A

Benefits for you and the wider AWS community

Describe alternatives you've considered
Not upgrading to 3.x and staying on version 2.5.x

Additional context

Related issues, RFCs

#861

@dreamorosi dreamorosi added enhancement dependencies Changes that touch dependencies, e.g. Dependabot, etc. triage This item has not been triaged by a maintainer, please wait labels May 13, 2022
@willfarrell
Copy link

Middy does not support nodejs12.x out of the box, but @middy/core​ with an additional build step to transpile newer syntax down to es2019 would support running on nodejs12.x. I just tested this using es2019​ in our build config with nodejs12.x locally and it tested without issue for @middy/core.

The breaking changes around onError does not affect any middleware within powertools from what I can see.

v2.5.x is very stable and is fine to stay with till AWS itself deprecates nodejs12.x. Because we're community maintained, I have no plans to apply any future updates to that branch. However, I would make an exception for security patches. I don't expect any though because @middy/core​ doesn't have any runtime dependencies by design and we're passing CodeQL with flying colours currently.

@shooit
Copy link

shooit commented Jun 2, 2022

Anecdotally, we are using @middy/core at v3.0.3 with the Powertools middleware with no issues. However we run all our lambdas on Node 16.x so we are not affected by Middy's deprecation of Node 12.x.

@dreamorosi dreamorosi added on-hold This item is on-hold and will be revisited in the future and removed triage This item has not been triaged by a maintainer, please wait priority:low labels Nov 13, 2022
@dreamorosi
Copy link
Contributor Author

I can confirm that at least on my experience @middy/core v3.x doesn't cause issues at runtime.

However I have tried upgrading the dependency in the repo and I have found that it makes the unit tests ran on Node.js 12 fail. At the time of writing we run unit and integration tests on Node.js 12, 14, and 16.

We'll revisit the issue we decide to drop Node.js 12, to track the progress of this decision follow #1061

@dreamorosi dreamorosi moved this from On hold to Working on it in AWS Lambda Powertools for TypeScript Nov 13, 2022
@dreamorosi dreamorosi moved this from Working on it to On hold in AWS Lambda Powertools for TypeScript Nov 13, 2022
@dreamorosi dreamorosi changed the title Feature (all): evaluate migration to Middy 3.x Maintenance: evaluate migration to Middy 3.x Nov 14, 2022
@dreamorosi dreamorosi moved this from On hold to Shipped in AWS Lambda Powertools for TypeScript Jan 17, 2023
@dreamorosi dreamorosi self-assigned this Jan 17, 2023
@dreamorosi dreamorosi added completed This item is complete and has been merged/shipped and removed on-hold This item is on-hold and will be revisited in the future labels Jan 17, 2023
@dreamorosi
Copy link
Contributor Author

While migrating the codebase to Node.js 18.x, we have updated the devDependencies and as a part of that effort we have updated @middy/core to version 3.x, which si now also part of our CI tests.

We can consider this issue as complete, we will revisit the Middy version once we drop support for Node.js 14.

@github-project-automation github-project-automation bot moved this from Shipped to Coming soon in AWS Lambda Powertools for TypeScript Jan 17, 2023
@dreamorosi dreamorosi moved this from Coming soon to Shipped in AWS Lambda Powertools for TypeScript Jan 17, 2023
@github-actions
Copy link
Contributor

⚠️ COMMENT VISIBILITY WARNING ⚠️

Comments on closed issues are hard for our team to see.
If you need more assistance, please either tag a team member or open a new issue that references this one.
If you wish to keep having a conversation with other community members under this issue feel free to do so.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
completed This item is complete and has been merged/shipped dependencies Changes that touch dependencies, e.g. Dependabot, etc.
Projects
None yet
Development

No branches or pull requests

3 participants