little-bear-labs / aws-utils

Apache License 2.0
56 stars 35 forks source link

Debugging appsync-emulator-serverless #103

Open NateWritesCode opened 5 years ago

NateWritesCode commented 5 years ago

Wasn't sure if you anyone would be able to help me but worth a shot. I'm trying to run this the appsync-emulator in VSCode by debugging. I can confirm that my application works correctly when running from the command line and serverless offline start but when I run the appsync-server via the node --inspect-brk command with serverless offline start, the query just hangs when it gets to this bit of code in the executeGQL function of serverCore.js

await execute(
      schema,
      documentAST,
      null, // root value
      context,
      variables,
      operationName
    );

Any idea why that might be? I'm not all that familiar with Node.JS so sorry if my question isn't all that clear.

cbaron commented 5 years ago

If you could provide the query you are trying to run, and the relevant serverless.yml, then it would be easier to help.

Also, running the emulator with NODE_DEBUG=* will provide a verbose console.log output. Currently, I am not very familiar with VSCode or inspect-brk.

Furthermore, it may be helpful to wrap the problem in a try/catch so that you can inspect the error that's being thrown ( or ignored )

NateWritesCode commented 5 years ago

I should mention I have forked and made some changes to fit my use case. Got rid of DynamoDBEmulator and subscriptions and added an AWSTimestamp Scalar type.

serverless.yml

service: fake-serverless

provider:
  name: aws
  runtime: nodejs8.10
  stage: ${opt:stage,'dev'}

plugins:
  - serverless-webpack #order matters
  - serverless-appsync-offline
  - serverless-offline

functions:
  getChannel:
    handler: functions/getChannel.handler
  # startChannel:
  #   handler: functions/startChannel.handler

custom:
  appsync-emulator:
    buildPrefix: .webpack/service
  webpack:
    includeModules: true
  appSync:
    name: project-name
    authenticationType: AWS_IAM
    mappingTemplates:
      - dataSource: getChannel
        type: Query
        field: getChannel
        request: requests/complete-request.vtl
        response: responses/common-response.vtl
    dataSources:
      - type: AWS_LAMBDA
        name: getChannel
        config:
          functionName: getChannel
          serviceRoleArn: { Fn::GetAtt: [backendAdmin, Arn] }

resources:
  Resources:
    backendAdmin:
      Type: AWS::IAM::Role
      Properties:
        Path: /
        RoleName: backendAdmin-${self:provider.stage} # required if you want to use 'serverless deploy --function' later on
        AssumeRolePolicyDocument:
          Version: "2012-10-17"
          Statement:
            - Effect: Allow
              Principal:
                Service:
                  - lambda.amazonaws.com
                  - appsync.amazonaws.com
                  - sns.amazonaws.com
                  - ses.amazonaws.com
                  - s3.amazonaws.com
                  - sqs.amazonaws.com
              Action: sts:AssumeRole

I do have the GraphQL execute request wrapped in a try catch but no exception ever gets caught. I have looked over the schema, documentAST, variables, and operationName variables and they seem to be the exact same when calling from the debugger or as regular.

One thing is the request that gets made as part of context has some additional fields in the object when not run as normal from the command line. Not sure if that matters?

The request in the context object has the following fields when called with the debugger.

{
...
host: localhost,
hostname: localhost,
xhr: false,
protocol: http
...
}

I can confirm that my web client is connecting to the GraphQL server. The only error I get is in my web client after about 2 minutes...

POST http://localhost:40000/graphql net::ERR_EMPTY_RESPONSE

So for some reason my request is not making it to the resolver when called from the debugger...

My VSCode launch.json

{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "Launch Dev Offline",
      "program": "/home/nathanh81/.nvm/versions/node/v8.12.0/lib/node_modules/serverless/bin/serverless",
      "args": ["offline", "start"],
      "protocol": "inspector",
      "runtimeExecutable": "node",
      "runtimeArgs": ["--lazy"],
      "outFiles": ["${workspaceRoot}/.webpack/service/*"],
      "sourceMaps": true,
      "cwd": "/home/nathanh81/Projects/appsync-offline/fake-serverless",
      "env": {
        "NODE_ENV": "development",
        "NODE_DEBUG": "*"
        // Here we set some environment vars that should be set locally.
        // They can and will overwrite the ones coming from your serverless.yml
      }
    }
  ]
}

The command line after debugger called

/home/nathanh81/.nvm/versions/node/v8.12.0/bin/node --lazy --inspect-brk=26732 ../../../.nvm/versions/node/v8.12.0/lib/node_modules/serverless/bin/serverless offline start 
Debugger listening on ws://127.0.0.1:26732/bb23cc68-6e5c-4616-b3d6-36f4e821b5c6
Debugger attached.
Serverless: Bundling with Webpack...
CLI.js:337
Time: 1228ms
CLI.js:337
Built at: 2019-03-25 14:02:22
                  Asset      Size                Chunks             Chunk Names
functions/getChannel.js  4.14 KiB  functions/getChannel  [emitted]  functions/getChannel
Entrypoint functions/getChannel = functions/getChannel.js
[./functions/getChannel.js] 214 bytes {functions/getChannel} [built]
Serverless: Watching for changes...
CLI.js:337
this.serverless.schema undefined
index.js:82
Serverless: AppSync started: http://localhost:40000/graphql
CLI.js:337
Serverless: Starting Offline: dev/us-east-1.
CLI.js:337
Serverless: Routes for getChannel:
CLI.js:337
Serverless: (none)
Serverless: Offline listening on http://localhost:3000
NateWritesCode commented 5 years ago

schema.graphql

schema {
  query: Query
}

type Channel {
  serverTime: AWSTimestamp
}

type Query {
  getChannel(channelName: String, fingerprint: ID): Channel
}

getChannel.js

exports.handler = async (event, context) => {
  try {
    return {
      serverTime: Date.now()
    };
  } catch (error) {
    throw new Error(error);
  }
};