dherault / serverless-offline

Emulate AWS λ and API Gateway locally when developing your Serverless project
MIT License
5.2k stars 794 forks source link

The "message" argument must be specified (TypeGraphQL + Prisma 2) #1194

Closed omar-dulaimi closed 3 years ago

omar-dulaimi commented 3 years ago

Bug Report

Current Behavior

Invoking a playground route produces the error:

🚀 ~ file: childProcessHelper.js ~ line 45 ~ result undefined
(node:5185) UnhandledPromiseRejectionWarning: TypeError [ERR_INVALID_ARG_TYPE]: The "message" argument must be one of type string, object, number, or boolean. Received type function ([Function (anonymous)])
    at process.target._send (internal/child_process.js:728:13)
    at process.target.send (internal/child_process.js:703:19)
    at process.<anonymous> (/home/omar/Desktop/Graphql-API-Prisma2-TypeGraphQL/node_modules/serverless-offline/dist/lambda/handler-runner/child-process-runner/childProcessHelper.js:53:11)
    at processTicksAndRejections (internal/process/task_queues.js:93:5)
(Use `node --trace-warnings ...` to show where the warning was created)

Sample Code

service: graphql-api-prisma2
frameworkVersion: "2"

provider:
  name: aws
  runtime: nodejs12.x
  lambdaHashingVersion: 20201221
  stage: staging #default stage
  region: us-east-1 #default region
  stackName: cfn-stack-${env:APP_ENV, opt:stage, self:provider.stage}-graphql-api-prisma2
  apiName: ${env:APP_ENV, opt:stage, self:provider.stage}-graphql-api-prisma2
  memorySize: 512
  vpc:
    securityGroupIds:
      - sg-id
    subnetIds:
      - subnet-id
      - subnet-id
      - subnet-id

plugins:
  - serverless-dotenv-plugin
  - serverless-offline

functions:
  graphql:
    handler: dist/src/index.graphql
    timeout: 30
    events:
      - http:
          path: graphql
          method: post

      - http:
          path: graphql
          method: get

custom:
  dotenv:
    path: .env.${opt:stage, self:provider.stage}
  serverless-offline:
    useChildProcesses: true

package:
  exclude:
    - "**"
    - "!dist"
    - "!node_modules"
    - "node_modules/@prisma/engines/**"
    - "node_modules/@prisma/cli/**"
import 'reflect-metadata';
import { ApolloServer } from 'apollo-server-lambda';
import { applyMiddleware } from 'graphql-middleware';
import validationRules from './validation-rules';
import formatError from './errors/formatter';
import permissions from './middlewares/shield/permissions';
import { createContext } from './context';
import './resolvers/Enhancers';
import buildSchema from './buildSchema';

export const graphql = async () => {
    const schema = await buildSchema();

    const context = createContext();
    const server = new ApolloServer({
        schema: applyMiddleware(schema, permissions),
        context: req => ({
            ...context,
            ...req,
        }),
        validationRules,
        formatError,
        introspection: true,
        playground: {
            endpoint: process.env.PLAYGROUND_ENDPOINT,
        },
    });
    return server.createHandler();
};

Expected behavior/code

Environment

Possible Solution

The problem seems to be happening in the childProcessHelper.js file, on this line:

  process.send(result);

The result variable gets undefined here:

    result = await inProcessRunner.run(event, context);

although messageData is already populated with data.

Additional context/Screenshots

omar-dulaimi commented 3 years ago

Here's how we got it to work:

(global as any).schema = (global as any).schema || buildSchema();
const schema = (global as any).schema;

const prismaContext = createContext();
const server = new ApolloServer({
    schema: applyMiddleware(schema, permissions),
    context: ({ event }) => ({
        ...prismaContext,
        headers: event.headers,
    }),
    validationRules,
    formatError,
    introspection: true,
    playground: {
        endpoint: process.env.PLAYGROUND_ENDPOINT,
    },
});

export const graphql = server.createHandler();

And make sure to use the synchronous version of buildSchema:

export default function buildSchema(): GraphQLSchema {
    return tq.buildSchemaSync({
        resolvers
    });
}

A working repo made by Ryan from the Prisma team: https://github.com/ryands17/sls-typegraphql

chaddjohnson commented 3 years ago

I have been receiving this error with my webhooks app:

(node:92749) UnhandledPromiseRejectionWarning: TypeError [ERR_MISSING_ARGS]: The "message" argument must be specified
    at process.target._send (internal/child_process.js:691:13)
    at process.target.send (internal/child_process.js:676:19)
    at process.<anonymous> (/Users/chad/development/appname/node_modules/serverless-offline/dist/lambda/handler-runner/child-process-runner/childProcessHelper.js:48:11)
    at processTicksAndRejections (internal/process/task_queues.js:94:5)
(node:92749) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)

I have no idea why this happens. Here is my serverless.yml:

service: webhooks-service
useDotenv: true

provider:
  name: aws
  runtime: nodejs12.x
  memorySize: 256
  timeout: 10
  endpointType: REGIONAL
  stage: ${env:STAGE, 'dev'}
  region: ${env:AWS_REGION, 'us-east-1'}
  versionFunctions: false
  logRetentionInDays: 14
  apiGateway:
    shouldStartNameWithService: true
  environment:
    LOG_QUEUE_URL: ${env:LOG_QUEUE_URL, ssm:/${self:provider.stage}/queues/log/url}
    LOG_SOURCE: 'webhooks-service'
    SHOPIFY_ADMIN_APP_API_SECRET_KEY: ${env:SHOPIFY_ADMIN_APP_API_SECRET_KEY, ssm:/${self:provider.stage}/shopify-admin/api-secret-key~true}
    SHOPS_API_URL: ${env:SHOPS_API_URL, ssm:/${self:provider.stage}/shops-api/url}

functions:
  health:
    handler: src/routes/health.handler
    events:
      - http:
          path: /health
          method: get
          cors: true
  appUninstall:
    reservedConcurrency: 1
    role: ${env:SERVICES_CONSUMER_ROLE_ARN, ssm:/${self:provider.stage}/roles/services-consumer-arn}
    handler: src/workers/appUninstall.handler
    events:
      - sqs:
          arn: ${env:APP_UNINSTALL_QUEUE_ARN, ssm:/${self:provider.stage}/queues/app-uninstall/arn}
      - http:
          path: /shopify/app-uninstall
          method: post
  product:
    reservedConcurrency: 2
    role: ${env:SERVICES_CONSUMER_ROLE_ARN, ssm:/${self:provider.stage}/roles/services-consumer-arn}
    handler: src/workers/product.handler
    events:
      - sqs:
          arn: ${env:PRODUCT_QUEUE_ARN, ssm:/${self:provider.stage}/queues/product/arn}
          batchSize: 10
          maximumBatchingWindow: 10
      - http:
          path: /shopify/product
          method: post
  productDelete:
    reservedConcurrency: 1
    role: ${env:SERVICES_CONSUMER_ROLE_ARN, ssm:/${self:provider.stage}/roles/services-consumer-arn}
    handler: src/workers/productDelete.handler
    events:
      - sqs:
          arn: ${env:PRODUCT_DELETE_QUEUE_ARN, ssm:/${self:provider.stage}/queues/product-delete/arn}
      - http:
          path: /shopify/product-delete
          method: post
  shop:
    reservedConcurrency: 1
    role: ${env:SERVICES_CONSUMER_ROLE_ARN, ssm:/${self:provider.stage}/roles/services-consumer-arn}
    handler: src/workers/shop.handler
    events:
      - sqs:
          arn: ${env:SHOP_QUEUE_ARN, ssm:/${self:provider.stage}/queues/shop-update/arn}
      - http:
          path: /shopify/shop
          method: post

plugins:
  - serverless-offline
  - serverless-plugin-monorepo

custom:
  serverless-offline:
    httpPort: ${env:WEBHOOK_PORT}
    lambdaPort: 5004

package:
  exclude:
    - test/**
    - node_modules/aws-sdk
    - node_modules/**/aws-sdk
    - node_modules/**/*.map
nicwise commented 2 years ago

looks like whatever this is, its back in serverless-offline v8. I just upgraded, and now a LOAD of my functions fail with this same error. No Prisma involved.

I am, however, NOT returning anything from the function. So I suspect that might be the issue?

nicwise commented 2 years ago

Tho I worked it out - but it's not a great fix. My function returns nothing. Just ends. as in

const theHandler = async ({appointmentId}: {appointmentId: string}) => {
  await doAThing();
};

However, this appears to be not valid. I also use Middy, so I can change the response on the way thru :) There is no response in these cases.

[service] {
[service]   "event": {
[service]     "appointmentId": "a0913b9cb",
[service]     "context": {
[service
....
[service]     },
[service]     "honeycombContext": ""
[service]   },
[service]   "context": {
[service]     "awsRequestId": "cl1cyutg500042vn7gnwwf1nw",
[service]     "callbackWaitsForEmptyEventLoop": true,
[service]     "functionName": "messaging-service-dev-create-booking-messages",
[service]     "functionVersion": "$LATEST",
[service]     "invokedFunctionArn": "offline_invokedFunctionArn_for_messaging-service-dev-create-booking-messages",
[service]     "logGroupName": "offline_logGroupName_for_messaging-service-dev-create-booking-messages",
[service]     "logStreamName": "offline_logStreamName_for_messaging-service-dev-create-booking-messages",
[service]     "memoryLimitInMB": "1024"
[service]   },
[service]   "internal": {}
[service] }

if I add a response in there, it works fine. No exceptions

in my middy middlware:
const after = async (request: middy.Request) => {
    if (!request.response) {
      request.response = {};
    }

and the json:
[service] {
[service]   "event": {
[service]     "appointmentId": "a09138f9cb",
[service]     "context": {
[service
....
[service]     },
[service]     "honeycombContext": ""
[service]   },
[service]   "context": {
[service]     "awsRequestId": "cl1cyutg500042vn7gnwwf1nw",
[service]     "callbackWaitsForEmptyEventLoop": true,
[service]     "functionName": "messaging-service-dev-create-booking-messages",
[service]     "functionVersion": "$LATEST",
[service]     "invokedFunctionArn": "offline_invokedFunctionArn_for_messaging-service-dev-create-booking-messages",
[service]     "logGroupName": "offline_logGroupName_for_messaging-service-dev-create-booking-messages",
[service]     "logStreamName": "offline_logStreamName_for_messaging-service-dev-create-booking-messages",
[service]     "memoryLimitInMB": "1024"
[service]   },
[service]   "internal": {},
[service]   "response": {}
[service] }

and its all good.

I'd rather not have to return true for all the cases where I don't need the result....