Coder Social home page Coder Social logo

michalkvasnicak / aws-lambda-graphql Goto Github PK

View Code? Open in Web Editor NEW
460.0 18.0 93.0 6.63 MB

Use AWS Lambda + AWS API Gateway v2 for GraphQL subscriptions over WebSocket and AWS API Gateway v1 for HTTP

Home Page: https://chat-example-app.netlify.com

License: MIT License

JavaScript 0.88% TypeScript 98.24% HTML 0.76% Shell 0.12%
apollo graphql aws aws-lambda aws-dynamodb aws-apigateway websockets graphql-subscriptions subscriptions

aws-lambda-graphql's Introduction

Hi there 👋

I'm Michal Kvasničák a full stack engineer from 🇸🇰. I ❤️ React, GraphQL, Node.js, Typescript...

I know this README is boring, what'd you like to know? Ask me anything!

aws-lambda-graphql's People

Contributors

alpacagoescrazy avatar alvinypyim avatar clmntrss avatar dependabot[bot] avatar dorsev avatar fossamagna avatar geymed avatar guerrerocarlos avatar hally9k avatar islamwahid avatar j0k3r avatar jcane86 avatar kunhuangau avatar lepilepi avatar michalkvasnicak avatar n1te1337 avatar nenti avatar sammarks avatar seanchambo avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

aws-lambda-graphql's Issues

What is the reason behind dynamoDB streams for event store?

So right now EventStore is using dynamoDB table to store incoming events and ddb stream to relay that events one by one to event processor lambda. Basically something like that:
Event is posted by mutation -> Event is written to Events table to dynamoDB -> DynamoDB stream invokes event processor lambda -> Event processor lambda decides to which connections event should be posted and posts it.

My question is why do we need this complex process of relaying events thru dynamoDB table. I can guess that this is some sort of event bus which decouples logic which receives events and logic which publishes them.
However this approach has no architectural benefits as it seems to me.
We do not use dynamoDB stream event batching to process multiple events at the same time, as it would delay events coming thru.
And this system is not acting as a fan-out to subscribers, as the 'fan-out' of the event happens in the event processor lambda.

Wouldn't it be better to directly invoke event processor lambda on incoming event?

Error when running example app

I've been trying to get this library working on a project and am having no luck. So I tried just getting the examples in the project to work. I followed the directions in each of the repos however it doesn't work. I deployed the server to wss://f6aifsw9g7.execute-api.us-east-1.amazonaws.com/dev and I get an error in the client when subscribing: "Prohibited connection". I have a screenshot for when it works on https://chat-example-app.netlify.com/ and one of it not working when I use my own deployment.

Working
good

Not working
bad

Has anyone seen this before and know how to fix it or how I can troubleshoot it? I'm kind of stuck right now. I don't see any errors in the AWS logs. Thanks!

Adding custom context to resolvers

It would be nice to have a way to pass a custom context to the resolvers, something like:

const httpHandler = createHttpHandler({
  connectionManager,
  schema,
  context: async ({ event }) => ({
    hello: 'world'
  })
});

Can't use the first function inside `withSync`

I have a subscription has withFilter working as below:

subscribe: withFilter(
        pubSub.subscribe('TRANSACTION_REQUEST'),
        (payload: any, variables: any) => {
          return payload.transactionId === variables.transactionId;
        },
      ),

the above code works fine. But if I change the first parameter to a function like below:

subscribe: withFilter(
        (root: any, args: any) => {
          console.log('req txn with filter args', args);
          return pubSub.subscribe('TRANSACTION_REQUEST') as any;
        },
        (payload: any, variables: any) => {
          return payload.transactionId === variables.transactionId;
        },
      ),

I will get below error:

2020-06-01T12:39:20.897Z	c553cfff-e31f-4c99-b768-a2f7d3d599db	INFO	TypeError: asyncIterator.next is not a function
    at getNextPromise (/opt/nodejs/node_modules/aws-lambda-graphql/dist/withFilter.js:8:34)
    at Object.next (/opt/nodejs/node_modules/aws-lambda-graphql/dist/withFilter.js:23:24)
    at Object.next (/opt/nodejs/node_modules/graphql/subscription/mapAsyncIterator.js:55:23)
    at /opt/nodejs/node_modules/aws-lambda-graphql/dist/DynamoDBEventProcessor.js:79:59
    at processTicksAndRejections (internal/process/task_queues.js:97:5)
    at async Promise.all (index 47)
    at async /opt/nodejs/node_modules/aws-lambda-graphql/dist/DynamoDBEventProcessor.js:90:25

It seems that the return value is not correct. I wonder what return value I should use. In ApolloServer, it uses the method pubsub.asyncIterator but I can't find this method in your PubSub class. How can I return the value in the first function?

[Discussion] Different event store implementations

Concerns about dynamoDB event store implementation, such as event publisher lambda trigger on each event occurring in events table, made me think about different approaches.

I came up with two options first one SNS+SQS and the second ElastiCache with Redis Pub/Sub.
For both of this options there is a problem on how to communicate published events back to publisher lambda.
For SQS there is an option to set a lambda trigger, but it seems that you can not set it dynamically for newly created queues.
For Redis Pub/Sub it seems there is no option but to have a continuously running server which listens to Redis publish events.

My question is if you know how to solve this problem or maybe there are more possible event store implementations to consider.

Cant connect to Websocket

I can connect to my web socket using wscat and get a connection but the graphql playground or the ApolloClient can't.

  • The Playground works and the subscription endpoint is correct
  • My Lambda function get called (getting entries in cloudwatch)
  • In Chrome Devtools i can see the following web socket message
  • No entries in any DynamoDB table

Error during WebSocket handshake: Sent non-empty 'Sec-WebSocket-Protocol' header but no response was received

Any Idea?

Update

I did some logging but it still doesnt work

  return async (e: any, c: any) => {
    const result = await server.createWebSocketHandler()(e, c);
    console.log(JSON.stringify(result, null, 2));
    return result;
  };

In Cloudwatch i can see the following (wich seems correct but it still doesnt work)

2020-01-10T13:10:40.893Z	a3f5d717-9b50-4ae1-9055-242946100bd6	INFO	
{
    "body": "",
    "statusCode": 200
}

Update

I cloned the chat-example-server example and changed the config so that the playground is activated and the endpoints are correct. This does not work too.


...
const server = new Server({
  connectionManager,
  eventProcessor: new DynamoDBEventProcessor(),
  resolvers,
  subscriptionManager,
  typeDefs,
 // changes
  playground: {
    endpoint: 'https://xxxxx.execute-api.eu-central-1.amazonaws.com/dev/',
    subscriptionEndpoint:
      'wss://xxxxx.execute-api.eu-central-1.amazonaws.com/dev',
  },
  introspection: true,
});

export const handleHttp = server.createHttpHandler();
export const handleWebSocket = server.createWebSocketHandler();
export const handleDynamoDBStream = server.createEventHandler();

Have a great day

Move JSON.stringify from PubSub to DynamoDBEventStore and make it optional

Hi @michalkvasnicak,

This is more a question at this stage, but after digging more into your implementation, I wonder what is the rationale for stringifying the message payload in the PubSub publish method ?

  publish = async (eventName: string, payload: any) => {
    if (typeof eventName !== 'string' || eventName === '') {
      throw new Error('Event name must be nonempty string');
    }

    await this.eventStore.publish({
      payload: JSON.stringify(payload),
      event: eventName,
    });
  };

On the contrary, the SubscriptionEvent type does not expect the String type:

export interface ISubscriptionEvent {
  event: string;
  payload: any;
}

Is this in an effort to ease EventStore backend implementations ?

Thank you

cc: @SuperHamouch11

[Question] Is it possible to use it with Netlify functions?

I have our Apollo server running on Netlify functions, after a disastrous implementation on AppSync and Amplify. Using the AWS Data API the server/Netlify function can interact with a DynamoDB and a Postgresql databases on AWS. Now I need to add subscriptions, and my search got me here. I followed the example and got a deployed stack in AWS, but only at the end I realized my existing Netlify Function was deployed as a new Lambda. Moving back to the AWS environment is something we would really like to avoid.

Since the demo https://chat-example-app.netlify.com/ is deployed in Netlify, could it be that it is running too on a Netlify function? If so, how the managers and store should be configured to get access to the other AWS resources?

Thank you,

Get `Server sent no subprotocol` error when connect through websocket

I have setup API Gateway websocket on AWS, the integration is LAMBDA_PROXY. and I got this error for $connect request.

target: WebSocket, type: "error", message: "Server sent no subprotocol", error: Error: Server sent no subprotocol
    at ClientReq

The client code is:

const getWSClient = (uri: string) => {
  const client = new SubscriptionClient(
    uri,
    {
      reconnect: true,
    },
    ws,
  );
  client.onConnected((data: any): any => console.log('connected ', data));
  client.onError((data: any): any => console.log('connect error', data));
  client.onDisconnected((data: any): any => console.log('disconnect ', data));
  client.onReconnecting((data: any): any => console.log('reconnect', data));
  client.onReconnecting((data: any): any => console.log('reconnected', data));

  const link = new WebSocketLink(client);
  return new ApolloClient({
    link: link,
    cache: new InMemoryCache(),
  });
};

const client = getWSClient(WS_URI);

  client
    .query({
      query: gql`
        query {
          transaction(transactionId: "123") {
            stan
          }
        }
      `,
    })

The error happens when client sends connect request to the server.

Exceeding the maximum request items sent to DynamoDB when cleaning up stale connections

As in the title.

After processing an event, the Lambda function will publish the result to the connections. For the connections that are stale (e.g. disconnected from the client silently), the Lambda function will remove the connection and unsubscribe all the subscriptions by removing the records from the DynamoDB tables. When there are 13 or more subscriptions, there will more than 25 request items sent which exceeds the maximum items defined by AWS, and that causes an error.

In addition, the default number of retry attempt is 10,000 for this type of Lambda invocation. That means whenever this error occur, it would be retrying for 10,000 times and that would probably result a hefty AWS bill.

$connect and GQL_CONNECT in quick succession fail with setup

The current setup of creating the connection entry into the dynamodb table on $connect and then relying on it to be there on the GQL_CONNECT results in non deterministic connection failures and disconnects because of a ConnectionNotFoundError on the GQL_CONNECT call.

[offline] connect:ck68ih4ee000065p6323weeu2 handleSubSockets CONNECT $connect no body 2020-02-04T23:25:37.291Z [offline] action:$default on connection=ck68ih4ee000065p6323weeu2 handleSubSockets MESSAGE $default connection_init 2020-02-04T23:25:37.300Z { ConnectionNotFoundError: Connection ck68ih4ee000065p6323weeu2 not found at DynamoDBConnectionManager.hydrateConnection (/home/circleci/repo/node_modules/aws-lambda-graphql/src/DynamoDBConnectionManager.ts:69:13) at process._tickCallback (internal/process/next_tick.js:68:7) name: 'ConnectionNotFoundError' } [offline] action:$default on connection=ck68ih4ee000065p6323weeu2 handleSubSockets MESSAGE $default start 2020-02-04T23:25:37.400Z [offline] got POST to http://localhost:3001/@connections/ck68ih4ee000065p6323weeu2 [offline] sent data to connection:ck68ih4ee000065p6323weeu2 Serverless: [AWS apigatewaymanagementapi 200 0.006s 0 retries] postToConnection({ ConnectionId: 'ck68ih4ee000065p6323weeu2', Data: '{"type":"error","payload":{"message":"Prohibited connection!"}}' }) [offline] got DELETE to http://localhost:3001/@connections/ck68ih4ee000065p6323weeu2 [offline] closed connection:ck68ih4ee000065p6323weeu2 Serverless: [AWS apigatewaymanagementapi 200 0.003s 0 retries] deleteConnection({ ConnectionId: 'ck68ih4ee000065p6323weeu2' }) [offline] disconnect:ck68ih4ee000065p6323weeu2 handleSubSockets DISCONNECT $disconnect no body 2020-02-04T23:25:37.435Z

[LocalStack] - GraphQL Playground is not able to run subscription

I am trying this library and so far its all good for Queries and Mutation. However the real reason I am trying this library is to work with GraphQL subscription in Serverless.

So after running this server, If i try to connect via google console a raw ws connection var socket = new WebSocket("ws://localhost:3001");, It saves a record in DynamoDB Connections table.

However, from GraphQL playground, running the subscription, it goes to listening mode forever, but there is no effect on the server.

Please advise.

Thanks

Documentation has invalid code

In the Usage (Apollo client + aws-lambda-ws-link) section it uses a constant called "client" before it's declared.

const link = new WebSocketLink(client);
const client = new ApolloClient({
  cache: new InMemoryCache(),
  link,
});

I'm pretty sure the fix is that it should be wsClient instead but I'm not sure since I can't get this library working at all.

Subscription field must return Async Iterable. Received: [function]

I'm getting the below error when trying to subscribe.

Error: Subscription field must return Async Iterable. Received: [function]
at /Users/paul/development/aleign/graphql-backend/node_modules/graphql/subscription/subscribe.js:169:13

I'm not sure it's necessarily an issue with this library directly, but what I think is going on is that the pubsub.subscribe function from this library is returning a promise which resolves to the asyncIterator. So I've tried to wrap my subscribe function in resolver with async and await pubsub.subscribe('event'), then return the asynIterator

Subscription: {
trackerAdded: {
// Additional event labels can be passed to asyncIterator creation
subscribe: async (root, args, { pubsub }, session) => {
const asyncIterator = await pubsub.subscribe([TRACKER_ADDED]);
return asyncIterator;
}
}
}

but I still get the error, so I'm guessing graphql-subscriptions isn't liking being handed a promise and the [function] it's referring to in the error is in fact a promise.

I'm also using graphql-modules so at this stage not sure if that's getting in the way.

Has anyone had any issues passing the promise back in the subscribe function?

Cheers
Paul

Passing more info with requests to resolver

Thanks for this awesome module! Works great for me, however, I wasn't able to implement one thing.

Let's assume that header X-User-ID comes with a request. How can I pass it down to resolver?

JSON.Stringify event payload

DynamoDB throws error when you try to store empty strings:
One or more parameter values were invalid: An AttributeValue may not contain an empty string
Event data should be serializable anyway, so maybe it will be better to pass it as string to event store so the event store implementation won't interfere with our event?

cleanup of subscriptions and connections doesn't seem to be working

Not entirely sure if this is an issue or just working as expected. I'm just trying to get my head around how the cleanup should work as I seem to have lots of old subscriptions. Looking through the cloudwatch logs it looks like all the old subscriptions for a particular event are getting pulled down each time, then discarded. But essentially it's making the lambda function run longer and longer each time.

My understanding was that subscriptions and subscription operations are removed when they are stale like connections. That doesn't seem to be the case, well at least in our implementation.

looking at the code base I can see that unsubscribeAllByConnectionId is called in unregisterConnection, which is called

  1. when sending the message to the client fails
  2. when the $disconnect is received
  3. when stop message is received

I think there might be a couple of gaps there. Maybe it should be called when the Events processor is iterating through the events and executing the event for all the subscribers. If there is an error in that execution for a subscriber, I think maybe that should cause the subscription to be removed and the client informed so they can resubscribe.

Possibly in closeConnection in the connection manager, although my assumption here is that triggering the apigateway to close the connection is triggering the $disconnect, which in turn should be cleaning up.

So anyway, I'm not sure if there are maybe circumstances where relying on the 2 hour $disconnect from API gateway is failing through the cracks sometimes and subscriptions are left and never cleaned up. Possibly a check to see if the connection in the subscription is valid before executing it in the event processor? Or maybe that should be the role of the subscription manager, it shouldn't return any subscriptions to the event processor if they don't have valid connections?

Add multipart/form-data support to upload files

I've adapted jaydenseric/graphql-upload (https://github.com/jaydenseric/graphql-upload) to work with a Lambda event. Below is the implementation:
https://gist.github.com/samin/976271f97b03bba14017a760570df88a

This implementation is based on https://github.com/jaydenseric/graphql-upload/blob/master/src/processRequest.mjs

It would be possible to add the following method when a request is a multipart/form-data in createHttpHandler.ts like this:

      switch (parsedType.type) {
        case 'application/json': {
          return JSON.parse(event.body!);
        }
        case 'application/x-www-form-urlencoded': {
          return querystring.parse(event.body!) as any;
        }
        case 'multipart/form-data': {
          return processRequest(event);
        }
        default: {
          throw new HTTPError(400, 'Invalid request content type');
        }
      }

It follows all the specs specified by the graphql-upload library, so it works with it's clients as well, and you can follow the documentation implementation.

Change ulid library to support React Native

Is it possible to change the ulid dependency to something like uuid? ulid uses crypto, something not built in to React Native and can cause problems when using the Client implementation.

How can I call the lambda from another lambda?

I have tried your library and it works great. One question I have is how I can call the graphql lambda function from another lambda? I can only send query mutation through API gateway but I don't know how to pass the event from lambda function.

The reason I want to call the graphql lambda from another lambda is to publish event for subscribers. One of my lambda is listening on an event from an external service and it needs to publish the event to all subscribers in the graphql.

Does this library support this?

Refactor to one server implementation

Motivation

Right now is initial code really hard to grasp without good documentation. It's just too much code user needs to write in order to provide just simple endpoint.

const {
  createDynamoDBEventProcessor,
  createWsHandler,
  DynamoDBConnectionManager,
  DynamoDBEventStore,
  DynamoDBSubscriptionManager,
  PubSub,
} = require('aws-lambda-graphql');
import { makeExecutableSchema } from 'graphql-tools';

// instantiate event store
const eventStore = new DynamoDBEventStore();
const pubSub = new PubSub({ eventStore });
const subscriptionManager = new DynamoDBSubscriptionManager();
const connectionManager = new DynamoDBConnectionManager({
  subscriptions: subscriptionManager,
});

// const schema ...

const eventProcessor = createDynamoDBEventProcessor({
  connectionManager,
  schema,
  subscriptionManager,
});
const wsHandler = createWsHandler({
  connectionManager,
  schema,
  subscriptionManager,
});

module.exports.handler = wsHandler;

Solution

The solution should be some sort of a pluggable server that has sane defaults and if the user needs to use something different it's possible to override defaults.

const server = new GraphQLServer({
    // connection manager keeps information about connections and their respective subscriptions
    connectionManager?: IConnectionManager;
    // event store should provide pubSub by context to subscription handlers
    // event store is responsible to determine if an event received by handler
    // is from event source
	eventStore?: IEventStore;
   	// ... maybe same options as ApolloServer? But they have tracing, etc
   	// so we'd need to maybe use their apollo-server-lambda implementation
   	// and just implement few things to support subscriptions?
});

// handler should handle HTTP and WebSocket ApiGateway events
// and maybe somehow handle pub sub events too?
// this could be handled by IEventStore.isEventSourceEvent()
// if it returns true, then it's event that needs to be processed by event handler
module.exports.handler = server.handler;

Thoughts

  • get rid of custom protocol and use just Apollo's one
  • use apollo-server-lambda as the core for HTTP processing
  • merge ConnectionManager and SubscriptionManager resposibilities?
  • provide pubSub in GraphQL resolver context
  • provide internals in GraphQL resolver info instead of GraphQL resolver context
  • separate protocol to separate package and support both apollo and custom protocols?
  • make ws link independent of aws-lambda-graphql package
  • rename whole project?

Subscriptions based on the userId from authorizer

Hello,
thanks for this library! I've been trying to setup simple subscriptions based on your example server app.

I managed to setup the authorizer on websocket, and retrieve user in the context function. With something like this:

context: async (context) => {
   // When called from DynamoDB event stream
    if (context.event?.requestContext == undefined) {
      return {
        pubSub
      }
    }

    let principalId = context.event.requestContext.authorizer!.principalId
    
    const user = await User.findByPk(principalId)

    if (!user) {
      throw new AuthenticationError('User not authorized');
      return
    }

    return {
      me: user,
      pubSub
    };
  }

And the subscription would be something like this:

hasUnseenViewNotifications: {
            resolve: (rootValue: ViewNotificationSeenChanged) => {
              return rootValue.unseen;
            },
            subscribe: (rootValue, args, context, info) => {
                const result = (context.pubSub as PubSub).subscribe(Subscriptions.viewNotificationSeenChanged.toString())
                
                return withFilter(result, (rootValue: ViewNotificationSeenChanged, args) => {
                    return rootValue.userId == context.me.id
                })(rootValue, args, context, info)
            }
        },

I assume the subscribe function is called from the dynamodb event stream so I am not getting requestContext and the userId with it. How to get it inside of the subscribe function?

I came upon this issue: #70 which might be what I am looking for, but I am confused how this would go all together.

Could you point me in the right direction?

Thanks
Tomas

Add boost package or packages

Basically we don't want to force users to use our verbose instantiation of the server for their use case. We should provide them with boost packages for different storage options.

For example we could have something like aws-lambda-graphql-dynamodb-boost or something like that.

I really don't like the name of the package so maybe this whole project could be moved to its own organisation.

But what I want to achieve is that the user would just do something like this

import { Server } from 'aws-lambda-graphql-dynamodb`;

const server = new Server({
	typeDefs: `...`,
	resolvers: {
		Subscription: {
			feed: (root, args, { pubSub }) => {
				// pub sub could be the part of context or GraphQL info
				// I don't know yet because I don't like to pass internal information using GraphQL context as it it at the moment but on the other hand people are use to access services in GraphQL context. So maybe pubSub could be a part of context but internal information like lambda event, etc should be a part of graphql info.
				return pubSub.subscribe('EVENT');
			},
		}
	},
});

module.exports.wsHandler = server.wsHandler;
module.exports.httpHandler = server.httpHandler;
module.exports.eventStreamHandler = server.streamHandler;

Serverless offline does not track hot reload changes

Steps to reproduce:

  1. Open and start up server and app example.
  2. Change something in server while sls offline is running. For example:
const server = new Server({
  connectionManager,
  eventProcessor: new DynamoDBEventProcessor(),
  resolvers,
  subscriptionManager,
  typeDefs,
  context: () => {
    console.log('test')
    return {}
  }
});
  1. Serverless offline webpack will rebuild the server application.
  2. Reload client application.

Expected behavior:
'test' will be output in sls console when client reconnects
Actual behavior:
nothing is output to console and no changes to the code affect running application.

If you stop serverless offline and restart changes to the code will take effect.

version: 1.0.0-alpha.1

Update Docs to reflect the code example

I see the main README is not updated as the current code example and it's quite confusing that you don't know which one is the correct one. e.g. the dynamodb-local part in the code example it's initialized for local development and that's not mentioned in the README

Update

Awesome work on exploring how to integrate with API Gateway.

Do you have a roadmap or see any problems with the current implementation?

Question on scalability and data retrieval limits/cost of Dynamo

Hi,

Thanks for putting this library together it looks really great. I haven't had a chance to fully test it out yet, but currently implementing it. This isn't an issue, more a question or request for advice. In pretty much every apollo subscription tutorial or example, they are always so rudimentary it's hard to think about how they might apply to a real application and also how they might perform at scale.

Going through the source to see how things hang together and thinking about how subscriptions should be implemented in particular the amount of data having to be read from Dynamo on every event. If event names are so simplistic as per all the examples around the web, if we just had a 'newMessage' event, is my understanding correct that if we had 20,000 users subscribed to this event, that this library would essentially need to fetch 20,000 records from Dynamo every time, even if the there was a withFilter of eg payload.topic === variables.topic.

Am I correct in thinking it would have to fetch all of the subscribers before processing the filter as the variables requested by the client are stored in the subscription record?

So what happens if that is thousands of records. I noticed the recent added limit to number of records retrieved from dynamo per request. Do you think this could start to cause issues at scale? Also processing large number of subscriptions to an event in the same lambda instance, could that have an effect on a realtime application? Maybe batching and invoking lambdas in parallel to process the responses.

So my question is, in this sort of scenario (ie serverless, external pubsub) in your experience and given the current functionality should we be taking a different approach to the withFilter pattern and looking to bake some of the filtering elements into the event name, 'newMessage::team:some_team_topic:cars'. How have you gone about it in any more production like systems?

I noticed the event name is stored as the hashkey, with Dynamos ability to filter on the range key with operators like beginsWith and lte, gte, between maybe some future support for specifying a range key in the event name passed from graphql could give an almost redis like feature set to this library ('newMessage::everything_passed_the_colons_is_range_key').

Being able to the subscribe just to eg all messages for all topics in a team by using

beginsWith('newMessage::team:some_team')

Any advice much appreciated.

Cheers
Paul

Publishing to undefined topic breaks dynamoDB trigger

When you try to publish subscription message like this
await pubSub.publish(undefined, payload)
It will allow to publish such message however lambda trigger will throw this error:

ExpressionAttributeValues must not be empty

Moreover because trigger lambda failed it will try to invoke it again with same parameter creating a loop which is possible to break only when you delete your dynamoDB trigger

I think event property should be part of composite key on dynamoDB Events table, and we should throw an error in PubSub when trying to publish event without a topic

Provide/Document API Gateway v2 configuration

Hi, many thanks for your implementation

Unless i missed it, i could not find details on the AWS API Gateway v2 configuration in the chat example (doc, severless config, cf config)

Could you describe the configuration you used in the example ?

Question: behavior of the socket after running serverless deploy

I have a question I managed to set up everything on the server-side as the example tested that locally and also after deployment to AWS using serverless with apollo playground and everything was fine.

but I was trying to deploy while there's a subscription on the apollo playground to see how it would react in this case and found that the playground isn't getting any disconnect event to close the socket.

the connection is still open but not getting any new events. I checked the dynamoDb tables the subscriptions and subscriptionsOperations are getting empty after a few mins of the deployment and the rest still not affected.

Don't know if there's something I should implement to handle this or this is not handled by the apollo playground.

here are more details on my code:

serverless.yml

functions:
  webSocketHandler:
    handler: dist/src/index.webSocketHandler
    events:
      - websocket:
          route: $connect
      - websocket:
          route: $disconnect
      - websocket:
          route: $default

  dynamoDBStreamHandler:
    handler: dist/src/index.dynamoDBStreamHandler
    events:
      - stream:
          enabled: true
          type: dynamodb
          arn:
            Fn::GetAtt: [EventsDynamoDBTable, StreamArn]

  graphqlHttpHandler:
    handler: dist/src/index.graphqlHandler
    events:
      - http:
          path: graphql
          method: any
          cors: true

package.json

"dependencies": {
    "apollo-server-lambda": "^2.9.3",
    "aws-lambda-graphql": "^1.0.0-alpha.4",
    "aws-sdk": "^2.589.0",
    "graphql": "^14.0.2",
    "graphql-subscriptions": "^1.1.0",
  }

subscription.ts

import {
  DynamoDBConnectionManager,
  DynamoDBEventProcessor,
  DynamoDBEventStore,
  DynamoDBSubscriptionManager,
  PubSub,
} from 'aws-lambda-graphql';
import { ApiGatewayManagementApi, DynamoDB } from 'aws-sdk';
import config from '@config';

const env = config.get('env');
const postfix = `metatrust-service-${env}`;

const eventsTable = `Events-${postfix}`;
const subscriptionsTable = `Subscriptions-${postfix}`;
const subscriptionOperationsTable = `SubscriptionOperations-${postfix}`;
const connectionsTable = `Connections-${postfix}`;

const dynamoDbClient = new DynamoDB.DocumentClient({
  ...(process.env.IS_OFFLINE
    ? {
        endpoint: 'http://localhost:8000',
      }
    : {}),
});
const eventStore = new DynamoDBEventStore({
  dynamoDbClient,
  eventsTable,
});

export const pubSub = new PubSub({ eventStore });
export const subscriptionManager = new DynamoDBSubscriptionManager({
  dynamoDbClient,
  subscriptionsTableName: subscriptionsTable,
  subscriptionOperationsTableName: subscriptionOperationsTable,
});
export const connectionManager = new DynamoDBConnectionManager({
  apiGatewayManager: process.env.IS_OFFLINE
    ? new ApiGatewayManagementApi({
        endpoint: 'http://localhost:3002',
      })
    : undefined,
  dynamoDbClient,
  subscriptions: subscriptionManager,
  connectionsTable,
});
export const eventProcessor = new DynamoDBEventProcessor();

Update:

  • after testing this with client implementation on staging environment I tried to deploy it when there're some open connections and it was handled perfectly with the AWS gateway.

Socket disconnect recovery

For your example chat app, I've set Client options to include:
{ reconnect: true, reconnectAttempts: Infinity, }

However, when a websocket disconnects (simulated using sleep on mac), all subscriptions are lost and no messages are passed even after reconnecting. Is there a way to remedy this?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.