GithubHelp home page GithubHelp logo

davidyaha / graphql-redis-subscriptions Goto Github PK

View Code? Open in Web Editor NEW
1.1K 12.0 124.0 1.31 MB

A graphql subscriptions implementation using redis and apollo's graphql-subscriptions

License: MIT License

TypeScript 99.35% JavaScript 0.65%
redis graphql-subscriptions redis-subscriptions graphql

graphql-redis-subscriptions's Introduction

graphql-redis-subscriptions

Build Status

This package implements the PubSubEngine Interface from the graphql-subscriptions package and also the new AsyncIterator interface. It allows you to connect your subscriptions manager to a Redis Pub Sub mechanism to support multiple subscription manager instances.

Installation

At first, install the graphql-redis-subscriptions package:

npm install graphql-redis-subscriptions

As the graphql-subscriptions package is declared as a peer dependency, you might receive warning about an unmet peer dependency if it's not installed already by one of your other packages. In that case you also need to install it too:

npm install graphql-subscriptions

Using as AsyncIterator

Define your GraphQL schema with a Subscription type:

schema {
  query: Query
  mutation: Mutation
  subscription: Subscription
}

type Subscription {
    somethingChanged: Result
}

type Result {
    id: String
}

Now, let's create a simple RedisPubSub instance:

import { RedisPubSub } from 'graphql-redis-subscriptions';
const pubsub = new RedisPubSub();

Now, implement your Subscriptions type resolver, using the pubsub.asyncIterator to map the event you need:

const SOMETHING_CHANGED_TOPIC = 'something_changed';

export const resolvers = {
  Subscription: {
    somethingChanged: {
      subscribe: () => pubsub.asyncIterator(SOMETHING_CHANGED_TOPIC),
    },
  },
}

Subscriptions resolvers are not a function, but an object with subscribe method, that returns AsyncIterable.

Calling the method asyncIterator of the RedisPubSub instance will send redis a SUBSCRIBE message to the topic provided and will return an AsyncIterator binded to the RedisPubSub instance and listens to any event published on that topic. Now, the GraphQL engine knows that somethingChanged is a subscription, and every time we will use pubsub.publish over this topic, the RedisPubSub will PUBLISH the event over redis to all other subscribed instances and those in their turn will emit the event to GraphQL using the next callback given by the GraphQL engine.

pubsub.publish(SOMETHING_CHANGED_TOPIC, { somethingChanged: { id: "123" }});

Dynamically create a topic based on subscription args passed on the query

export const resolvers = {
  Subscription: {
    somethingChanged: {
      subscribe: (_, args) => pubsub.asyncIterator(`${SOMETHING_CHANGED_TOPIC}.${args.relevantId}`),
    },
  },
}

Using a pattern on subscription

export const resolvers = {
  Subscription: {
    somethingChanged: {
      subscribe: (_, args) => pubsub.asyncIterator(`${SOMETHING_CHANGED_TOPIC}.${args.relevantId}.*`, { pattern: true })
    },
  },
}

Using both arguments and payload to filter events

import { withFilter } from 'graphql-subscriptions';

export const resolvers = {
  Subscription: {
    somethingChanged: {
      subscribe: withFilter(
        (_, args) => pubsub.asyncIterator(`${SOMETHING_CHANGED_TOPIC}.${args.relevantId}`),
        (payload, variables) => payload.somethingChanged.id === variables.relevantId,
      ),
    },
  },
}

Configuring RedisPubSub

RedisPubSub constructor can be passed a configuration object to enable some advanced features.

export interface PubSubRedisOptions {
  connection?: RedisOptions | string;
  triggerTransform?: TriggerTransform;
  connectionListener?: (err?: Error) => void;
  publisher?: RedisClient;
  subscriber?: RedisClient;
  reviver?: Reviver;
  serializer?: Serializer;
  deserializer?: Deserializer;
  messageEventName?: string;
  pmessageEventName?: string;
}
option type default description
connection object | string undefined the connection option is passed as is to the ioredis constructor to create redis subscriber and publisher instances. for greater controll, use publisher and subscriber options.
triggerTransform function (trigger) => trigger deprecated
connectionListener function undefined pass in connection listener to log errors or make sure connection to redis instance was created successfully.
publisher function undefined must be passed along side subscriber. see #creating-a-redis-client
subscriber function undefined must be passed along side publisher. see #creating-a-redis-client
reviver function undefined see #using-a-custom-reviver
serializer function undefined see #using-a-custom-serializerdeserializer
deserializer function undefined see #using-a-custom-serializerdeserializer
messageEventName string undefined see #receiving-messages-as-buffers
pmessageEventName string undefined see #receiving-messages-as-buffers

Creating a Redis Client

The basic usage is great for development and you will be able to connect to a Redis server running on your system seamlessly. For production usage, it is recommended to pass a redis client (like ioredis) to the RedisPubSub constructor. This way you can control all the options of your redis connection, for example the connection retry strategy.

import { RedisPubSub } from 'graphql-redis-subscriptions';
import * as Redis from 'ioredis';

const options = {
  host: REDIS_DOMAIN_NAME,
  port: PORT_NUMBER,
  retryStrategy: times => {
    // reconnect after
    return Math.min(times * 50, 2000);
  }
};

const pubsub = new RedisPubSub({
  ...,
  publisher: new Redis(options),
  subscriber: new Redis(options)
});

Receiving messages as Buffers

Some Redis use cases require receiving binary-safe data back from redis (in a Buffer). To accomplish this, override the event names for receiving messages and pmessages. Different redis clients use different names, for example:

library message event message event (Buffer) pmessage event pmessage event (Buffer)
ioredis message messageBuffer pmessage pmessageBuffer
node-redis message message_buffer pmessage pmessage_buffer
import { RedisPubSub } from 'graphql-redis-subscriptions';
import * as Redis from 'ioredis';

const pubsub = new RedisPubSub({
  ...,
  // Tells RedisPubSub to register callbacks on the messageBuffer and pmessageBuffer EventEmitters
  messageEventName: 'messageBuffer',
  pmessageEventName: 'pmessageBuffer',
});

Also works with your Redis Cluster

import { RedisPubSub } from 'graphql-redis-subscriptions';
import { Cluster } from 'ioredis';

const cluster = new Cluster(REDIS_NODES); // like: [{host: 'ipOrHost', port: 1234}, ...]
const pubsub = new RedisPubSub({
  ...,
  publisher: cluster,
  subscriber: cluster
});

You can learn more on the ioredis package here.

Using a custom serializer/deserializer

By default, Javascript objects are (de)serialized using the JSON.stringify and JSON.parse methods. You may pass your own serializer and/or deserializer function(s) as part of the options.

The deserializer will be called with an extra context object containing pattern (if available) and channel properties, allowing you to access this information when subscribing to a pattern.

import { RedisPubSub } from 'graphql-redis-subscriptions';
import { someSerializer, someDeserializer } from 'some-serializer-library';

const serialize = (source) => {
  return someSerializer(source);
};

const deserialize = (sourceOrBuffer, { channel, pattern }) => {
  return someDeserializer(sourceOrBuffer, channel, pattern);
};

const pubSub = new RedisPubSub({ ..., serializer: serialize, deserializer: deserialize });

Using a custom reviver

By default, Javascript objects are serialized using the JSON.stringify and JSON.parse methods. This means that not all objects - such as Date or Regexp objects - will deserialize correctly without a custom reviver, that work out of the box with the default in-memory implementation. For handling such objects, you may pass your own reviver function to JSON.parse, for example to handle Date objects the following reviver can be used:

import { RedisPubSub } from 'graphql-redis-subscriptions';

const dateReviver = (key, value) => {
  const isISO8601Z = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/;
  if (typeof value === 'string' && isISO8601Z.test(value)) {
    const tempDateNumber = Date.parse(value);
    if (!isNaN(tempDateNumber)) {
      return new Date(tempDateNumber);
    }
  }
  return value;
};

const pubSub = new RedisPubSub({ ..., reviver: dateReviver });

pubSub.publish('Test', {
  validTime: new Date(),
  invalidTime: '2018-13-01T12:00:00Z'
});
pubSub.subscribe('Test', message => {
  message.validTime; // Javascript Date
  message.invalidTime; // string
});

Old Usage (Deprecated)

import { RedisPubSub } from 'graphql-redis-subscriptions';
const pubsub = new RedisPubSub();
const subscriptionManager = new SubscriptionManager({
  schema,
  pubsub,
  setupFunctions: {},
});

Using Trigger Transform (Deprecated)

Recently, graphql-subscriptions package added a way to pass in options to each call of subscribe. Those options are constructed via the setupFunctions object you provide the Subscription Manager constructor. The reason for graphql-subscriptions to add that feature is to allow pub sub engines a way to reduce their subscription set using the best method of said engine. For example, Meteor's live query could use Mongo selector with arguments passed from the subscription like the subscribed entity id. For Redis, this could be a bit more simplified, but much more generic. The standard for Redis subscriptions is to use dot notations to make the subscription more specific. This is only the standard but I would like to present an example of creating a specific subscription using the channel options feature.

First I create a simple and generic trigger transform

const triggerTransform = (trigger, {path}) => [trigger, ...path].join('.');

Then I pass it to the RedisPubSub constructor.

const pubsub = new RedisPubSub({
  triggerTransform,
});

Lastly, I provide a setupFunction for commentsAdded subscription field. It specifies one trigger called comments.added and it is called with the channelOptions object that holds repoName path fragment.

const subscriptionManager = new SubscriptionManager({
  schema,
  setupFunctions: {
    commentsAdded: (options, {repoName}) => ({
      'comments.added': {
        channelOptions: {path: [repoName]},
      },
    }),
  },
  pubsub,
});

When I call subscribe like this:

const query = `
  subscription X($repoName: String!) {
    commentsAdded(repoName: $repoName)
  }
`;
const variables = {repoName: 'graphql-redis-subscriptions'};
subscriptionManager.subscribe({query, operationName: 'X', variables, callback});

The subscription string that Redis will receive will be comments.added.graphql-redis-subscriptions. This subscription string is much more specific and means the the filtering required for this type of subscription is not needed anymore. This is one step towards lifting the load off of the GraphQL API server regarding subscriptions.

Tests

Spin a Redis in docker server and cluster

Please refer to https://github.com/Grokzen/docker-redis-cluster documentation to start a cluster

$ docker run --rm -p 6379:6379 redis:alpine
$ export REDIS_CLUSTER_IP=0.0.0.0; docker run -e "IP=0.0.0.0" --rm -p 7006:7000 -p 7001:7001 -p 7002:7002 -p 7003:7003 -p 7004:7004 -p 7005:7005 grokzen/redis-cluster

Test

npm run test

graphql-redis-subscriptions's People

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

graphql-redis-subscriptions's Issues

Incorrect example for setting the retry strategy on "Creating the Redis Client"

The current README.md uses the following code on the Creating the Redis Client section:

import { RedisPubSub } from 'graphql-redis-subscriptions';
import * as Redis from 'ioredis';

const options = {
  host: REDIS_DOMAIN_NAME,
  port: PORT_NUMBER,
  retry_strategy: options => {
    // reconnect after
    return Math.max(options.attempt * 100, 3000);
  }
};

const pubsub = new RedisPubSub({
  ...,
  publisher: new Redis(options),
  subscriber: new Redis(options)
});

It uses ioredis as the Redis client and specifies a retry_strategy option when creating the instance with an option object as argument with an attempt key. However, the ioredis actually expects a retryStrategy (camel-cased) with a times numeric argument inside with the current attempt, as described on the Auto-reconnect section of their documentation.

var redis = new Redis({
  // This is the default value of `retryStrategy`
  retryStrategy: function(times) {
    var delay = Math.min(times * 50, 2000);
    return delay;
  }
})

I think the current example from grapqhl-redis-subscriptions is probably using the retry_strategy signature from node-redis by mistake.

Documentation - redis pubsub

I am curious because I am getting a subscriptionsClient.subscribe is not a function error and I do not understand the source of it.

The code below in documentation,

import { RedisPubSub } from 'graphql-redis-subscriptions';

const pubsub = new RedisPubSub({
  connection: {
    host: REDIS_DOMAIN_NAME,
    port: PORT_NUMBER,
    retry_strategy: options => {
      // reconnect after
      return Math.max(options.attempt * 100, 3000);
    }
  }
});

It means replace the pubsub instance below with the pubsub instance created from new RedisPubSub right?

const SOMETHING_CHANGED_TOPIC = 'something_changed';

export const resolvers = {
  Subscription: {
    somethingChanged: {
      subscribe: () => pubsub.asyncIterator(SOMETHING_CHANGED_TOPIC),
    },
  },
}

An in-range update of ioredis is breaking the build 🚨

Version 3.2.2 of ioredis was just published.

Branch Build failing 🚨
Dependency ioredis
Current Version 3.2.1
Type devDependency

This version is covered by your current version range and after updating it in your project the build failed.

ioredis is a devDependency of this project. It might not break your production code or affect downstream projects, but probably breaks your build or test tools, which may prevent deploying or publishing.

Status Details
  • ❌ continuous-integration/travis-ci/push The Travis CI build failed Details

Commits

The new version differs by 4 commits.

See the full diff

FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

AsyncIterator causes memory leaks in production

Hi,

this is not an issue directly related to your repository, but as contributor of the project: https://github.com/axelspringer/graphql-google-pubsub I can say we use a very similar version of the AsyncIterator like you have. I wonder if you ran into similar issues?

We observe huge problems with memory leaks on production, where about 100 users are on the system in parallel. It takes about one hour until the memory reaches its limit and a restart of the container is triggered. We only saw this, because of our monitoring, otherwise nobody would have taken notice about it.

Heapdumps gave us the assumption, async iterator is the bad guy here:

screen shot 2018-08-22 at 11 33 41

Here my Questions:

  1. Is somebody using this project in production?
  2. Did anybody observe similar problems?
  3. What could be the right way in rewriting that AsyncIterator?

By the way, I wonder, why you are not interested in this pull-request, which seems to adress the problem, described: #114
--> We will try that out tomorrow and come back with results.

Feature request: custom parse message function to support non-JSON messages like Redis Keyspace Notifications

I started using Redis Keyspace Notifications today, and in one of my GraphQL publications, I need to watch for a Redis key expired notification, and if so, yield something in addition to the normal JSON messages I'm publishing.

I had to write some really confusing code to merge the stream of JSON events with the stream of expired events coming from direct redis client usage.
I wish I pass a top-level parseMessage function to RedisPubSub like:

parseMessage: (topic: string, message: string) => {
  if (topic.startsWith('__key')) return message
  return JSON.parse(message)
},

If only I could do this, then my code for handling the GraphQL subscription would be really simple:

const mainTopic = `Device/${deviceId}/status`
const keyspaceTopic = `__keyspace@0__:${mainTopic}`
for await (const event of pubsub.asyncIterator([mainTopic, keyspaceTopic])) {
  if (event === 'expired') yield {DeviceConnectionState: {status: 'NOT_CONNECTED'}}
  else yield event
}

What do you think?

redis password

Hi,

Is it possible to add a redis password when establishing a connection?

Thanks,

Support for Redis Sentinel and Cluster

Add support for Redis Sentinel and Cluster.
In production most places use Sentinel or Cluster for high availability and automatic failovers.
The library now used by 'graphql-redis-subscriptions' isn't suppotring it.

Suggested solutions:

  1. Allow user to send it's own client.
  2. Replace 'redis' library with 'ioredis'.

I personally think the second one is better, because it's not a breaking change and there's not much use in letting the developers send a redis instance.
Maybe I will do 2 and open a PR in the near future.

Subscriptions stacking with each deploy

The subscriptions stack in Redis every time we deploy our server, meaning by now every message is sent five times to every user that's subscribed.

Any ideas why that's happening? What are we doing wrong?

An in-range update of graphql is breaking the build 🚨

The devDependency graphql was updated from 14.4.2 to 14.5.0.

🚨 View failing branch.

This version is covered by your current version range and after updating it in your project the build failed.

graphql is a devDependency of this project. It might not break your production code or affect downstream projects, but probably breaks your build or test tools, which may prevent deploying or publishing.

Status Details
  • ❌ continuous-integration/travis-ci/push: The Travis CI build failed (Details).

Release Notes for v14.5.0

v14.5.0 (2019-08-22)

New Feature πŸš€

Bug Fix 🐞

Docs πŸ“

Polish πŸ’…

42 PRs were merged

Internal 🏠

14 PRs were merged

Dependency πŸ“¦

7 PRs were merged

Committers: 4

FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

An in-range update of graphql is breaking the build 🚨

The devDependency graphql was updated from 14.0.2 to 14.1.0.

🚨 View failing branch.

This version is covered by your current version range and after updating it in your project the build failed.

graphql is a devDependency of this project. It might not break your production code or affect downstream projects, but probably breaks your build or test tools, which may prevent deploying or publishing.

Status Details
  • ❌ continuous-integration/travis-ci/push: The Travis CI build failed (Details).

Release Notes for v14.1.0

New:

  • Added assertSchema and assertDirective (#1580)
  • Adds additional type extension validation (#1643)

Fixes:

  • Improves flow type definitions
  • Adds back in support for node 9, and tests for node 11. node >= 6 is currently supported.
  • Allows custom node inspect (#1605)
  • Correctly handles newlines in block strings (#1637)
Commits

The new version differs by 109 commits.

  • da61380 14.1.0
  • 4de99f7 Merge branch 'OneCyrus-feat_listTypeForError'
  • 0a731a5 Prettier and fix tests
  • a6676dc Merge branch 'feat_listTypeForError' of https://github.com/OneCyrus/graphql-js into OneCyrus-feat_listTypeForError
  • 37c022d buildClientSchema: add missing tests for introspection validations (#1666)
  • 3fef0d4 Enable Flow on buildClientSchema tests (#1665)
  • 6876587 Move $DisableFlowOnNegativeTest to more precise locations (#1664)
  • b747bb4 changed type error message
  • 8d7a5fd Merge schema validation tests with other schema tests (#1663)
  • 6741ac2 Add tests for GraphQLDirective (#1662)
  • d48e481 Move isInputType & isOutputType test to rest of predicates test (#1661)
  • 4cdc8e2 Update Flow to 0.90.0 (#1660)
  • 9f85d51 Remove excessive invariants (#1658)
  • 0818c18 ignore .nyc_output dir (#1657)
  • 9e40465 Move polyfills into separate dir and exclude them from coverage (#1656)

There are 109 commits in total.

See the full diff

FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

An in-range update of @types/mocha is breaking the build 🚨

Version 2.2.42 of @types/mocha just got published.

Branch Build failing 🚨
Dependency @types/mocha
Current Version 2.2.41
Type devDependency

This version is covered by your current version range and after updating it in your project the build failed.

As @types/mocha is β€œonly” a devDependency of this project it might not break production or downstream projects, but β€œonly” your build or test tools – preventing new deploys or publishes.

I recommend you give this issue a high priority. I’m sure you can resolve this πŸ’ͺ

Status Details
  • ❌ continuous-integration/travis-ci/push The Travis CI build could not complete due to an error Details

Not sure how things should work exactly?

There is a collection of frequently asked questions and of course you may always ask my humans.


Your Greenkeeper Bot 🌴

Use `redis.createClient` rather than the RedisClient constructor

It's a bit confusing that we are using the RedisClient constructor directly, as all of the docs (including options) for node_redis are around redis.createClient. Because of this, there is at least one discrepancy with passing connection options to redis. According to current node_redis docs here, the client should be able to specify a url option rather than passing host and port options through the connection hash that you pass through to node_redis. However, the RedisClient constructor does not support a url property. The url property is normalized and split into host and port properties through a call here, which dives into lib/createClient.js and parsed into host and port here. Passing the url option through options.connection in RedisPubSub constructor causes an attempted connection to redis://127.0.0.1:6379, regardless of what is passed via url. Because of this discrepancy, and for the sake of developer sanity, I think it makes more sense to interact with the publicly documented API of node_redis (aka, redis.createClient(options: Object) rather than interface with the RedisClient constructor directly as it fits more inline with the node_redis docs and this lib provides a passthrough via options.connection which will be assumed to follow the node_redis publicly doc'd API. I am willing to put in a PR to make this switch later this week.

After updating typedscript compileroptions to ES6 fails

Seems like ES6 destruction params not working after changing compilerOptions for target to es6. Wondering whether anyone has same issue?

                resolve: function (root, { filterBoolean }) {
                                         ^

SyntaxError: Unexpected token {
    at exports.runInThisContext (vm.js:53:16)

An in-range update of typescript is breaking the build 🚨

Version 2.4.1 of typescript just got published.

Branch Build failing 🚨
Dependency typescript
Current Version 2.4.0
Type devDependency

This version is covered by your current version range and after updating it in your project the build failed.

As typescript is β€œonly” a devDependency of this project it might not break production or downstream projects, but β€œonly” your build or test tools – preventing new deploys or publishes.

I recommend you give this issue a high priority. I’m sure you can resolve this πŸ’ͺ

Status Details
  • ❌ continuous-integration/travis-ci/push The Travis CI build could not complete due to an error Details

Commits

The new version differs by 141 commits.

  • 8b2fe13 Update LKG.
  • 14d95ed Test:Block-scoped definition of Promise works
  • a8846bf Skip block scope check with no error location
  • 44f2336 Merge pull request #16633 from Microsoft/release-2.4_fixIncrementalParsing
  • 4875a27 Add tests
  • 15ef20d Set the structureReused to be safemoudles when dynamic import change
  • 6d33083 Add tests
  • 11b9f6e Wip-fix incremental parsing
  • 2721fd4 In TypeScript code, never bind JSDoc normally, just set parent pointers (#16555) (#16561)
  • 0968ed9 Revert string enum changes (#16569)
  • 096f8cc Update LKG
  • 9241175 Allow running in strict mode (#16557)
  • f49b007 Update LKG
  • f1b0f59 Update version to 2.4.1
  • ed9cde9 Update LKG

There are 141 commits in total.

See the full diff

Not sure how things should work exactly?

There is a collection of frequently asked questions and of course you may always ask my humans.


Your Greenkeeper Bot 🌴

[RFC] mutation-based trigger API

Hi there. I'm looking at subscriptions in Node coming from some experience with graphQL in Elixir. They offer an option in Absinthe where instead of manually publishing arbitrarily named events, you can subscribe to mutations themselves:
kinda like:

  Subscription: {
    userUpdated: {
      subscribe: () => pubsub.triggeredBy(['updateUserName', 'updateUserAddress'])
    },

We would need to publish all mutations with something like a pubsub middleware that would publish the mutation and the payload. That way we wouldn't need to create subscription payloads as the payload would be the mutation payload, in this case the User payload. This is almost always desirable anyway: having the mutation payload be the subscription payload.

Is there any way to define some PREFIX or SCOPE ?

I'm working on a react application which depends highly on real time data.
Using redis pubsub and it works fine.

My problem is I have multiple deployments of this application
1 - abc.somedomain.com
2 - xyz.somedomain.com

I want to share 1 redis cluster between these 2 deployments. Now the issue is, if some data is published on some channel "channelABC" from 1st deployment, it visible for 2nd deployment as well.

Is there any way to use some kind of prefix or scope just like
https://github.com/louischatriot/node-redis-pubsub

Clarification on readme

The readme instructions has two sets of conflicting code, and both say to use them in production. Which one do I use:

Option 1:

import { RedisPubSub } from 'graphql-redis-subscriptions';
import * as Redis from 'ioredis';

const options = {
  host: REDIS_DOMAIN_NAME,
  port: PORT_NUMBER,
  retry_strategy: options => {
    // reconnect after
    return Math.max(options.attempt * 100, 3000);
  }
};

const pubsub = new RedisPubSub({
  ...,
  publisher: new Redis(options),
  subscriber: new Redis(options)
});

Option 2:

import { RedisPubSub } from 'graphql-redis-subscriptions';

const pubsub = new RedisPubSub({
  connection: {
    host: REDIS_DOMAIN_NAME,
    port: PORT_NUMBER,
    retry_strategy: options => {
      // reconnect after
      return Math.max(options.attempt * 100, 3000);
    }
  }
});

Guaranteed delivery?

In this article it mentions delivery guarantees:

We worked with David Yahalomi to create the graphql-redis-subscriptions and graphql-mqtt-subscriptions packages, which can be easily dropped in as a replacement for the default PubSub object without making any changes to the rest of your code.

Using an external pub/sub system gives you more publication featuresβ€Šβ€”β€Šsuch as persistence, caching, delivery guarantees and other optimizations.

Does graphql-redis-subscriptions support guaranteed delivery, and how would one go about implementing it?

Incorrect peerDependency (update to work with latest graphql package)

I'm getting the following error when attempting to install graphql-redis-subscriptions

warning "graphql-redis-subscriptions > [email protected]" has incorrect peer dependency "graphql@^0.7.0 || ^0.8.0 || ^0.9.0 || ^0.10.1"

The latest version of graphql as of submitting this issue is 0.11.7. My cursory test seems to show that it works with the new version of graphql, not sure of the work involved in just getting that updated.

Group graphql executions when possible

Hello!

I'm building a real time application with graphql subscriptions and I am facing performance issues at the moment.

What I do is I use the graphql subscriptions more like a contract than for it's resolvers per say.

The data I want to pass through to the connected clients is precomputed and I know my audience from the start, therefore I don't need to run the graphql execution for as many sockets as I have connected.

Once per payload is enough, and I could just use the result of that execution to all the users (the resolvers don't do anything related to the user so it doesn't matter really).

I have been trying to implement this but I think I'm missing something and don't understand exactly how/where I could do it.

Would you mind giving me some pointers so I can implement it?
I think this is really critical for subscriptions performances, and could spend some time on it given some help.

An in-range update of tslint is breaking the build 🚨

Version 5.6.0 of tslint just got published.

Branch Build failing 🚨
Dependency tslint
Current Version 5.5.0
Type devDependency

This version is covered by your current version range and after updating it in your project the build failed.

As tslint is β€œonly” a devDependency of this project it might not break production or downstream projects, but β€œonly” your build or test tools – preventing new deploys or publishes.

I recommend you give this issue a high priority. I’m sure you can resolve this πŸ’ͺ

Status Details
  • ❌ continuous-integration/travis-ci/push The Travis CI build could not complete due to an error Details

Release Notes v5.6.0

πŸŽ‰ New rules, options, and fixers

πŸ›  Bugfixes & enhancements

Thanks to our contributors!

  • Klaus Meinhardt
  • Julian Verdurmen
  • Alexandre Alonso
  • Josh Goldberg
  • ksvitkovsky
  • Daisuke Yokomoto
  • Andrii Dieiev
  • Florent Suc
  • Jason Killian
  • Amin Pakseresht
  • reduckted
  • vilicvane
  • Russell Briggs
  • Andy Hanson
  • Leo Liang
  • Dan Homola
  • BehindTheMath
  • David Golightly
  • aervin
  • Daniel Kucal
  • Ika
  • Chris Barr
Not sure how things should work exactly?

There is a collection of frequently asked questions and of course you may always ask my humans.


Your Greenkeeper Bot 🌴

Add option to use existing redis connection for publish events

Hello, i'm still halfway implementing my subscription one thing i noticed is that there was no example ( that i could find ) for peeps using an already existing Redis connection.

For instance in my case before creating the RedisPubSub i already have a connection to redis happening on my application:

  client = redis.createClient({
    url: url,
    db : db
  })

So i'm wondering if using this object as "connection" parameter for the PubSub will be enough for it to "reuse" my existing connection, as in:

var pubsub = new RedisPubSub({
  connection      : client,
  triggerTransform: transform
})

Is that supposed to work, if not how would i go about this ?

Issue with importing ioredis

The example code used in section Creating the Redis Client of the readme has the following import:

import * as Redis from 'ioredis';

This results in an error message:

publisher: new Redis(options),
           ^

TypeError: Redis is not a constructor

To fix this, you simply need to change the import to:

import Redis from 'ioredis';

Add support for custom event serialization (Was: Serialization is breaking Dates)

I'm using the Date type as in this documentation page https://www.apollographql.com/docs/graphql-tools/scalars.html

  Date: new GraphQLScalarType({
    name: 'Date',
    description: 'Date custom scalar type',
    parseValue(value) {
      return new Date(value); // value from the client
    },
    serialize(value) {
      return value.getTime(); // value sent to the client
    },
    parseLiteral(ast) {
      if (ast.kind === Kind.INT) {
        return parseInt(ast.value, 10); // ast value is always in string format
      }
      return null;
    },
  }),

I'm doing a findOne with Mongoose, and pass the returned document to the pubsub like this:

pubsub.publish('bookUpdated', { bookUpdated: book });

In my network tab, in the websocket, I'm getting the following error:

value.getTime is not a function in ["bookUpdated", "events", 1, "timestamp"]

The same code works without problem with the in-memory PubSub. This bug only affects RedisPubSub. Certainly because of the use of JSON.stringify and JSON.parse. See https://stackoverflow.com/questions/4511705/how-to-parse-json-to-receive-a-date-object-in-javascript

So I'm not sure if this can be considered a graphql-redis-subscriptions bug, but both PubSub are supposed to give the same result, I think.

triggerTransform example results in very hard-to-find error

After spending about 4 hours debugging a weird error where I was getting the message Cannot convert undefined or null to object as a message back from the WS subscribe query, I discovered that it was caused by the transpiled version of the triggerTransform example in the README:

function triggerTransform(trigger, _ref) {
  var path = _ref.path;
  return [trigger].concat(_toConsumableArray(path)).join('.');
}

After doing a bit of manual debugging, I discovered that the error was being thrown along this path:

Point #1
Point #2

The triggerTransform function is, of course, the transpiled function pasted above which is the triggerTransform example in the README. It looks like the problem is that _ref.path is actually undefined. The trigger parameter is equal to a string (the channel name), and _ref (which is the value from {path} as the second param in the example) is undefined because the options object in the function header is an empty object for whatever reason (presumably a default option from here).

I was able to pinpoint the source by wrapping the transpiled code of both of the functions listed above in try/catch blocks and tracing out the errors.

Wondering if this is an error with the docs or a regression somewhere else, like in the coupling between this library and graphql-subscriptions?

Example project

Could someone provide some code using graphql-redis-subscriptions?
I am facing some difficulties getting the data sent to the client although the subscription is called on the server.

[Feature] Support messages different than strings (messageBuffer)

Hello! First of all thanks for your time and your work on making and maintaining this lib! πŸ‘

We're trying to migrate from the basic 'graphql-subscriptions' package to this one.

We have a use case in which we don't just publish a single change, e.g. a plain object { ... }, but a bunch of them; so we publish a Set([ ... ]) of changes in one shot. The problem is that the code is doing this:

public async publish<T>(trigger: string, payload: T): Promise<void> {
    await this.redisPublisher.publish(trigger, JSON.stringify(payload));
  }

Here the Set is lost because of the assumption that everything would be a String. πŸ˜„

However, ioredis supports publishing Buffers as well:

redis.on("messageBuffer", function(channel, message) {
  // Both `channel` and `message` are buffers.
});

Would you be open to add this feature?

Many thanks! πŸ™

Event loop blocked by too many executions

Hello πŸ‘‹

I have noticed something weird I would like to discuss with you for I'm not certain of its origin and am not comfortable enough with asyncIterators to diagnose this properly.

My apologies if this doesn't come from the redis-subscriptions package.

Using this reproduction repo you can:

  • set up a server accepting websocket connections
  • set up 300 websocket connections
  • publish 500 messages into redis to be computed

Doing this, you should obtain the following behavior

I am a bit confused as to why the event loop would be blocked. Is there an operation that should be async that isn't?

You can see this happen as well if the filterFn in server/index.js:49 returns false so that nobody is interested in spinning the graphql execution for this message. (You might have to send more messages to witness it, node publisher/index.js 1000)

Is it possible that this comes from this package, or should I rather be looking into https://github.com/apollographql/subscriptions-transport-ws ?

While I realize this makes for a lot of messages to be handled by the process

Each message triggers 300 potential executions, this makes for 150 000 graphql executions to be performed
I would expect the whole process to be slow, but not for the event loop to be totally blocked.

This is especially true when we return false in the filterFn function (which means the issue doesn't come from the graphql layer)

An in-range update of @types/chai is breaking the build 🚨

Version 4.0.2 of @types/chai just got published.

Branch Build failing 🚨
Dependency @types/chai
Current Version 4.0.1
Type devDependency

This version is covered by your current version range and after updating it in your project the build failed.

As @types/chai is β€œonly” a devDependency of this project it might not break production or downstream projects, but β€œonly” your build or test tools – preventing new deploys or publishes.

I recommend you give this issue a high priority. I’m sure you can resolve this πŸ’ͺ

Status Details
  • ❌ continuous-integration/travis-ci/push The Travis CI build could not complete due to an error Details

Not sure how things should work exactly?

There is a collection of frequently asked questions and of course you may always ask my humans.


Your Greenkeeper Bot 🌴

An in-range update of mocha is breaking the build 🚨

Version 3.4.0 of mocha just got published.

Branch Build failing 🚨
Dependency mocha
Current Version 3.3.0
Type devDependency

This version is covered by your current version range and after updating it in your project the build failed.

As mocha is β€œonly” a devDependency of this project it might not break production or downstream projects, but β€œonly” your build or test tools – preventing new deploys or publishes.

I recommend you give this issue a high priority. I’m sure you can resolve this πŸ’ͺ

Status Details
  • ❌ continuous-integration/travis-ci/push The Travis CI build could not complete due to an error Details

Release Notes v3.4.0

Mocha is now moving to a quicker release schedule: when non-breaking changes are merged, a release should happen that week.

This week's highlights:

  • allowUncaught added to commandline as --allow-uncaught (and bugfixed)
  • warning-related Node flags

πŸŽ‰ Enhancements

πŸ› Fixes

πŸ”© Other

Commits

The new version differs by 9 commits0.

  • 7554b31 Add Changelog for v3.4.0
  • 9f7f7ed Add --trace-warnings flag
  • 92561c8 Add --no-warnings flag
  • ceee976 lint test/integration/fixtures/simple-reporter.js
  • dcfc094 Revert "use semistandard directly"
  • 93392dd no special case for macOS running Karma locally
  • 4d1d91d --allow-uncaught cli option
  • fb1e083 fix allowUncaught in browser
  • 4ed3fc5 Add license report and scan status

false

See the full diff

Not sure how things should work exactly?

There is a collection of frequently asked questions and of course you may always ask my humans.


Your Greenkeeper Bot 🌴

Trouble setting up pubsub without subscription manager

So my express server calls MY subscriptionServer function

//subscriptions.js
const { execute, subscribe } = require('graphql');
const { SubscriptionServer } = require('subscriptions-transport-ws');
const { RedisPubSub } = require('graphql-redis-subscriptions');
const schema = require('./src/schema');

const redisOptions = {
    host: 'localhost',
    port: 6379,
    connect_timeout: 15000,
    enable_offline_queue: true,
    retry_unfulfilled_commands: true,
};


const pubsub = new RedisPubSub({
    connection: redisOptions,
    connectionListener: (err) => {
        if (err) {
            console.error(err); // eslint-disable-line no-console
        }
        console.info('Succefuly connected to redis'); // eslint-disable-line no-console
    }
});

function subscriptionServer(server) {
    new SubscriptionServer({
        execute,
        subscribe,
        schema
    }, {
        server,
        path: '/subscriptions',
    });
}

module.exports = {
    pubsub,
    subscriptionServer
};

heres a simple subscription query

const RoomType = require('../type');
const { GraphQLNonNull, GraphQLString } = require('graphql');
const { pubsub } = require('../../../subscriptions.js');

module.exports = {
    type: RoomType,
    name: 'roomSubscription',
    args: {
        placeId: {
            description: 'ID of the place',
            type: new GraphQLNonNull(GraphQLString)
        }
    },
    subscribe: () => pubsub.asyncIterator('checkedin')
};

so anytime i run this query in graphiQL i get this error

[{"message":"Schema must be an instance of GraphQLSchema. Also ensure that there are not multiple versions of GraphQL installed in your node_modules directory."}]

if i replace the pubsub with the one from graphql-subscriptions, the subscriptions work fine.
edit: publish seems to work fine, i can see logs on redis

Geoadd, set and georadius question

Hi all, great work. Somewhat newbie disclosure, Im new to graphql and new to redis subscriptions but have fair amount of experience with redis in general. I'm trying to determine/conceptualize how and if I can utilize graphql-redis-subscriptions to

A. perform geo-add
B. change(re"set") the value of hash
C. perform a georadius query

Currently doing above this via a simple service but would like to experiment the same implementation via graphql-redis-subscriptions, is it feasible? Would graphql add complexity or any lag to response if feasible?

thanks! Great work!

subscriptionMap seems to be leaking memory

Hello,

Since we implemented subscriptions in our app (including this package) we see sometimes elevated memory usage over time. I'm not quite sure what's happening in there but I see a lot of the RedisPubSub's subscriptionMap retaining some memory:

developer_tools_-_https___trace_risingstack_com_app_

This may be due to many different things: first of all, we have a kue job queue hooked in the same pubsub. That would explain a good chunk of the retained memory ("Matrix" part in the retained objects).

However, from what I gather in your source, there is no cleaning in the subscriptionMap of the Array mapped by your subscriptionId: https://github.com/davidyaha/graphql-redis-subscriptions/blob/master/src/redis-pubsub.ts#L63:L83

I'll try to things to mitigate the issue:

  • separate redis pubsubs: this may already reduce the retained memory but may not stop the leak
  • a PR here to clearly delete in the subscriptionMap on unsubscription

Maybe we should consider mapping the subscriptions using a WeakMap, avoiding maintaining the cleaning of the retained objects in the subscription map...

An in-range update of redis is breaking the build 🚨

Version 2.8.0 of redis just got published.

Branch Build failing 🚨
Dependency redis
Current Version 2.7.1
Type dependency

This version is covered by your current version range and after updating it in your project the build failed.

redis is a direct dependency of this project this is very likely breaking your project right now. If other packages depend on you it’s very likely also breaking them.
I recommend you give this issue a very high priority. I’m sure you can resolve this πŸ’ͺ

Status Details
  • ❌ continuous-integration/travis-ci/push The Travis CI build could not complete due to an error Details

Commits

The new version differs by 35 commits.

  • 1380ad6 fix: test on old node versions
  • 6694c91 v.2.8.0
  • ad8355a fix: add command tests
  • 79558c5 doc: improve README readability by limiting chars to 80
  • 51fdbb7 chore: improve new add_command and add documentation
  • 0437aa4 enabled adding abritary commands
  • 4f7f1ad fix: silence auth errors on reconnect
  • 42e8bd6 chore: stop testing Node.js 7
  • 937081b fix: dependency
  • e5c8f81 fix: revert some dev dependency updates to run tests
  • 7e9dda1 chore: test node 8
  • 16632f4 fix emitting internal auth error on reconnect
  • 50774ae fix: accept UPPER_CASE commands in send_command
  • 789471b chore: update peer dependency version
  • 6934270 fix: always copy subscribe unsubscribe arguments

There are 35 commits in total.

See the full diff

Not sure how things should work exactly?

There is a collection of frequently asked questions and of course you may always ask my humans.


Your Greenkeeper Bot 🌴

Incompatibility with mqemitter (?)

Hi,
I'm trying to use your module in bundle with an MQTT broker which also relies on Redis, as described in StackOverflow.
I point out that if I try to use import * as Redis from 'ioredis'; as described here, I get TypeError: Redis is not a constructor.
When I try to publish something, I can see commands forwarded to redis through redis-cli monitor command, but I get that error inside mqemitter package which make crash the server.
I know that mqemitter it's not part of your module, the fact is I'm not properly sure where is the problem, since when I use the default PubSub implementation of graphql-subscription module I don't get this type of errors.
Let me know if you can spot something particular,
best regards

Using pattern in subscription and knowing destination topic

Hi, first thank you for your work in this project!

I was trying to use it subscribing to a redis topic using a pattern and doesn't seem to be working:

mySubscription: {
  subscribe: (_, args) => pubsub.asyncIterator(`project.${args.id}.*`),
  resolve: (payload: any, args, context, info) => {
    // I received this message from which topic? Question even using [topics] in asyncIterator
    return payload;
  }
}
  • Can I use a patter in the asyncIterator?
  • Can I get the topic in the resolver? I can add the topic into the payload but I was wondering if there's another cleaner approach.

Thanks!

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.