Skip to content
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

(lambdanodejs): using top level await fails at runtime #21329

Open
ionscorobogaci opened this issue Jul 26, 2022 · 3 comments
Open

(lambdanodejs): using top level await fails at runtime #21329

ionscorobogaci opened this issue Jul 26, 2022 · 3 comments
Labels
@aws-cdk/aws-lambda-nodejs bug This issue is a bug. effort/medium Medium work item – several days of effort p2

Comments

@ionscorobogaci
Copy link

ionscorobogaci commented Jul 26, 2022

Describe the bug

Top level awaits cannot be used or fails at runtime always
Top level awaits are supported now in NodeJS lambda, however trying to deploy such a function with cdk contruct NodejsFunction always fails

here is the sample lambda function :

import {APIGatewayEvent, Context} from "aws-lambda";

import {SSMClient, GetParameterCommand} from "@aws-sdk/client-ssm";

const readParameterStore = async () => {
    console.log("reading parameter store ")
    const ssmClient = new SSMClient({});
    const input = {"Name": "test-ion"}
    const command = new GetParameterCommand(input);
    return await ssmClient.send(command);
}

const parameterValue = await readParameterStore();

export const handler = async (event: APIGatewayEvent, context: Context) => {

    console.log("parameterValue : ", parameterValue);
    return {
        statusCode: 200,
        body: JSON.stringify({
            message: 'hello world from typescript',
        }),
    };
};

here is the definition with cdk

    new NodejsFunction(scope, 'CheckTypescript', {
      functionName: props.stage.concat(HYPHEN).concat('typescript-check'),
      description: 'Sample function written in NodeJs with typescript and deployed using CDK NodejsFunction.',
      handler: 'handler',
      depsLockFilePath: join(__dirname, '../../../services/package-lock.json'),
      entry: join(__dirname, '../../../services/lambda-sample-typescript/index.ts'),
      runtime: Runtime.NODEJS_16_X,
      logRetention: RetentionDays.TWO_WEEKS,
      bundling:{
        format: OutputFormat.ESM,
        target:'es2022',
        tsconfig: join(__dirname, '../../../services/tsconfig.json')
      }
    });

it fails with the following error always when trying to use top level awaits

{
    "errorType": "Error",
    "errorMessage": "Dynamic require of \"crypto\" is not supported",
    "stack": [
        "Error: Dynamic require of \"crypto\" is not supported",
        "    at file:///var/task/index.mjs:7:9",
        "    at asset-input/node_modules/uuid/dist/rng.js (file:///var/task/index.mjs:2082:42)",
        "    at __require2 (file:///var/task/index.mjs:10:50)",
        "    at asset-input/node_modules/uuid/dist/v1.js (file:///var/task/index.mjs:2167:39)",
        "    at __require2 (file:///var/task/index.mjs:10:50)",
        "    at asset-input/node_modules/uuid/dist/index.js (file:///var/task/index.mjs:2550:37)",
        "    at __require2 (file:///var/task/index.mjs:10:50)",
        "    at asset-input/node_modules/@aws-sdk/client-secrets-manager/dist-cjs/protocols/Aws_json1_1.js (file:///var/task/index.mjs:2573:18)",
        "    at __require2 (file:///var/task/index.mjs:10:50)",
        "    at asset-input/node_modules/@aws-sdk/client-secrets-manager/dist-cjs/commands/CancelRotateSecretCommand.js (file:///var/task/index.mjs:4640:25)"
    ]
}

if the bundling is not specified , the function cannot be even deployed

      bundling:{
        format: OutputFormat.ESM,
        target:'es2022',
        tsconfig: join(__dirname, '../../../services/tsconfig.json')
      },

Expected Behavior

The function should execute normally and the output should be .js and not .mjs

Current Behavior

an .mjs file is generated and the execution always fails with

{
    "errorType": "Error",
    "errorMessage": "Dynamic require of \"crypto\" is not supported",
    "stack": [
        "Error: Dynamic require of \"crypto\" is not supported",
        "    at file:///var/task/index.mjs:7:9",
        "    at asset-input/node_modules/uuid/dist/rng.js (file:///var/task/index.mjs:2082:42)",
        "    at __require2 (file:///var/task/index.mjs:10:50)",
        "    at asset-input/node_modules/uuid/dist/v1.js (file:///var/task/index.mjs:2167:39)",
        "    at __require2 (file:///var/task/index.mjs:10:50)",
        "    at asset-input/node_modules/uuid/dist/index.js (file:///var/task/index.mjs:2550:37)",
        "    at __require2 (file:///var/task/index.mjs:10:50)",
        "    at asset-input/node_modules/@aws-sdk/client-secrets-manager/dist-cjs/protocols/Aws_json1_1.js (file:///var/task/index.mjs:2573:18)",
        "    at __require2 (file:///var/task/index.mjs:10:50)",
        "    at asset-input/node_modules/@aws-sdk/client-secrets-manager/dist-cjs/commands/CancelRotateSecretCommand.js (file:///var/task/index.mjs:4640:25)"
    ]
}

Reproduction Steps

create a function which uses top level await and deploy it using NodejsFunction

Possible Solution

No response

Additional Information/Context

No response

CDK CLI Version

2.30.0

Framework Version

No response

Node.js Version

16

OS

Linux

Language

Typescript

Language Version

No response

Other information

No response

@ionscorobogaci ionscorobogaci added bug This issue is a bug. needs-triage This issue or PR still needs to be triaged. labels Jul 26, 2022
@github-actions github-actions bot added the @aws-cdk/aws-lambda Related to AWS Lambda label Jul 26, 2022
@ionscorobogaci
Copy link
Author

if works perfectly when defined as simple Lambda function and not using crappy NodejsFunction

this sample works perfectly

private static sampleTopLevelAwait(scope: Construct, props: ServiceProps): void {
    const sampleFunction = new LambdaFunction(scope, 'SampleTopLevelAwait', {
      functionName: props.stage.concat(HYPHEN).concat('top-level-await'),
      description: 'Check top level await',
      timeout: Duration.seconds(3),
      code: Code.fromAsset(join(__dirname, '../../../services')),
      runtime: Runtime.NODEJS_16_X,
      handler: 'lambda-sample-typescript/index.handler',
      logRetention: RetentionDays.TWO_MONTHS,
    })

    const functionPolicy = new PolicyStatement({
      actions: ['secretsmanager:*','ssm:*'],
      resources: ['*'],
      effect: Effect.ALLOW,
    });

    sampleFunction.addToRolePolicy(functionPolicy);
  }

@peterwoodworth peterwoodworth added p2 effort/medium Medium work item – several days of effort and removed needs-triage This issue or PR still needs to be triaged. labels Jul 26, 2022
@peterwoodworth peterwoodworth changed the title (module name): (short issue description) (lambdanodejs): using top level await fails at runtime Jul 26, 2022
@koshic
Copy link

koshic commented Aug 11, 2022

Workaround ('mainFields' is required to bundle ESM version of AWS SDK):

bundling: {
  format: OutputFormat.ESM,
  mainFields: ["module", "main"],
  esbuildArgs: {
    "--conditions": "module",
  }
}
Why? Looks like it's not CDK / SDK, it somewhere between esbuild & uuid: uuid already has esm version in 'dist/node_esm', but per my (and issue author) stack trace we see that 'dist/rng.js' used instead. It's a bit complicated 'exports' field in uuid's package.json:
  "exports": {
    ".": {
      "node": {
        "module": "./dist/esm-node/index.js",
        "require": "./dist/index.js",
        "import": "**./wrapper.mjs**"
      },
      "default": "./dist/esm-browser/index.js"
    },
    "./package.json": "./package.json"
  },

ESBuild shall choose exports->node->module instead of exports->node->import (which is proper way from my understanding).

Yep, simple lambda with format: ESM

import { v4 } from "uuid";

export const handler = async () => {
  console.log(v4());
};

produces output with CJS version of uuid

// node_modules/uuid/dist/rng.js
var require_rng = __commonJS({
  "node_modules/uuid/dist/rng.js"(exports) {

From esbuild logs

    Looking for "." in "exports" map in "/Users/vgrenaderov/CCW/nexus/node_modules/uuid/package.json"
      Using the entry for "."
      Checking condition map for one of ["default", "import", "node"]
        The key "node" applies
        Checking condition map for one of ["default", "import", "node"]
          **The key "module" does not apply**
          The key "require" does not apply
          The key "import" applies
          Checking path "" against target "./wrapper.mjs"
            Joined "" to "./wrapper.mjs" to get "./wrapper.mjs"
      The resolved path "/Users/vgrenaderov/CCW/nexus/node_modules/uuid/wrapper.mjs" is exact

With '--conditions=module' it works fine

    Looking for "." in "exports" map in "/Users/vgrenaderov/CCW/nexus/node_modules/uuid/package.json"
      Using the entry for "."
      Checking condition map for one of ["default", "import", "module", "node"]
        The key "node" applies
        Checking condition map for one of ["default", "import", "module", "node"]
          The key "module" applies
          Checking path "" against target "./dist/esm-node/index.js"
            Joined "" to "./dist/esm-node/index.js" to get "./dist/esm-node/index.js"
      The resolved path "/Users/vgrenaderov/CCW/nexus/node_modules/uuid/dist/esm-node/index.js" is exact

Final thoughts: root cause is uuid package with strange exports section (see https://nodejs.org/api/packages.html#conditional-exports and try to find 'module' condition...) and mixed CJS / ESM ecosystem complexity.

@Dror-Bar
Copy link

Dror-Bar commented Jan 6, 2025

@koshic Your solution actually worked, thanks a lot!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
@aws-cdk/aws-lambda-nodejs bug This issue is a bug. effort/medium Medium work item – several days of effort p2
Projects
None yet
Development

No branches or pull requests

5 participants