npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

graphql-subscriptions

v3.0.0

Published

GraphQL subscriptions for node.js

Downloads

3,904,644

Readme

npm version GitHub license

graphql-subscriptions

GraphQL subscriptions is a simple npm package that lets you wire up GraphQL with a pubsub system (like Redis) to implement subscriptions in GraphQL.

You can use it with any GraphQL client and server (not only Apollo).

Installation

npm install graphql-subscriptions graphql or yarn add graphql-subscriptions graphql

This package should be used with a network transport, for example subscriptions-transport-ws.

TypeScript

If you are developing a project that uses this module with TypeScript:

  • ensure that your tsconfig.json lib definition includes "es2018.asynciterable"
  • npm install @types/graphql or yarn add @types/graphql

Getting started with your first subscription

To begin with GraphQL subscriptions, start by defining a GraphQL Subscription type in your schema:

type Subscription {
  somethingChanged: Result
}

type Result {
  id: String
}

Next, add the Subscription type to your schema definition:

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

Now, let's create a simple PubSub instance - it is a simple pubsub implementation, based on EventEmitter. Alternative EventEmitter implementations can be passed by an options object to the PubSub constructor.

import { PubSub } from "graphql-subscriptions";

export const pubsub = new PubSub();

If you're using TypeScript you can use the optional generic parameter for added type-safety:

import { PubSub } from "graphql-subscriptions";

const pubsub = new PubSub<{
  EVENT_ONE: { data: number };
  EVENT_TWO: { data: string };
}>();

pubsub.publish("EVENT_ONE", { data: 42 });
pubsub.publish("EVENTONE", { data: 42 }); // ! ERROR
pubsub.publish("EVENT_ONE", { data: "42" }); // ! ERROR
pubsub.publish("EVENT_TWO", { data: "hello" });

pubsub.subscribe("EVENT_ONE", () => {});
pubsub.subscribe("EVENTONE", () => {}); // ! ERROR
pubsub.subscribe("EVENT_TWO", () => {});

Next implement your Subscriptions type resolver using the pubsub.asyncIterableIterator to map the event you need:

const SOMETHING_CHANGED_TOPIC = "something_changed";

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

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

The GraphQL engine now knows that somethingChanged is a subscription, and every time we use pubsub.publish it will publish content using our chosen transport layer:

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

Note that the default PubSub implementation is intended for demo purposes. It only works if you have a single instance of your server and doesn't scale beyond a couple of connections. For production usage you'll want to use one of the PubSub implementations backed by an external store. (e.g. Redis)

Filters

When publishing data to subscribers, we need to make sure that each subscriber gets only the data it needs.

To do so, we can use withFilter helper from this package, which wraps AsyncIterator with a filter function, and lets you control each publication for each user.

withFilter API:

  • asyncIteratorFn: (rootValue, args, context, info) => AsyncIterator<any> : A function that returns AsyncIterator you got from your pubsub.asyncIterableIterator.
  • filterFn: (payload, variables, context, info) => boolean | Promise<boolean> - A filter function, executed with the payload (the published value), variables, context and operation info, must return boolean or Promise<boolean> indicating if the payload should pass to the subscriber.

For example, if somethingChanged would also accept a variable with the ID that is relevant, we can use the following code to filter according to it:

import { withFilter } from "graphql-subscriptions";

const SOMETHING_CHANGED_TOPIC = "something_changed";

export const resolvers = {
  Subscription: {
    somethingChanged: {
      subscribe: withFilter(
        () => pubsub.asyncIterableIterator(SOMETHING_CHANGED_TOPIC),
        (payload, variables) => {
          return payload.somethingChanged.id === variables.relevantId;
        }
      ),
    },
  },
};

Note that when using withFilter, you don't need to wrap your return value with a function.

Channels Mapping

You can map multiple channels into the same subscription, for example when there are multiple events that trigger the same subscription in the GraphQL engine.

const SOMETHING_UPDATED = "something_updated";
const SOMETHING_CREATED = "something_created";
const SOMETHING_REMOVED = "something_removed";

export const resolvers = {
  Subscription: {
    somethingChanged: {
      subscribe: () =>
        pubsub.asyncIterableIterator([
          SOMETHING_UPDATED,
          SOMETHING_CREATED,
          SOMETHING_REMOVED,
        ]),
    },
  },
};

Payload Manipulation

You can also manipulate the published payload, by adding resolve methods to your subscription:

const SOMETHING_UPDATED = "something_updated";

export const resolvers = {
  Subscription: {
    somethingChanged: {
      resolve: (payload, args, context, info) => {
        // Manipulate and return the new value
        return payload.somethingChanged;
      },
      subscribe: () => pubsub.asyncIterableIterator(SOMETHING_UPDATED),
    },
  },
};

Note that resolve methods execute after subscribe, so if the code in subscribe depends on a manipulated payload field, you will need to factor out the manipulation and call it from both subscribe and resolve.

Usage with callback listeners

Your database might have callback-based listeners for changes, for example something like this:

const listenToNewMessages = (callback) => {
  return db.table("messages").listen((newMessage) => callback(newMessage));
};

// Kick off the listener
listenToNewMessages((message) => {
  console.log(message);
});

The callback function would be called every time a new message is saved in the database. Unfortunately, that doesn't play very well with async iterators out of the box because callbacks are push-based, where async iterators are pull-based.

We recommend using the callback-to-async-iterator module to convert your callback-based listener into an async iterator:

import asyncify from "callback-to-async-iterator";

export const resolvers = {
  Subscription: {
    somethingChanged: {
      subscribe: () => asyncify(listenToNewMessages),
    },
  },
};

Custom AsyncIterator Wrappers

The value you should return from your subscribe resolver must be an AsyncIterable.

You can wrap an AsyncIterator with custom logic for your subscriptions. For compatibility with APIs that require AsyncIterator or AsyncIterable, your wrapper can return an AsyncIterableIterator to comply with both.

For example, the following implementation manipulates the payload by adding some static fields:

export const withStaticFields = (
  asyncIterator: AsyncIterator<any>,
  staticFields: Object
): Function => {
  return (
    rootValue: any,
    args: any,
    context: any,
    info: any
  ): AsyncIterableIterator<any> => {
    return {
      next() {
        return asyncIterator.next().then(({ value, done }) => {
          return {
            value: {
              ...value,
              ...staticFields,
            },
            done,
          };
        });
      },
      return() {
        return Promise.resolve({ value: undefined, done: true });
      },
      throw(error) {
        return Promise.reject(error);
      },
      [Symbol.asyncIterator]() {
        return this;
      },
    };
  };
};

You can also take a look at withFilter for inspiration.

PubSub Implementations

It can be easily replaced with some other implementations of PubSubEngine abstract class. Here are a few of them:

  • Use Redis with https://github.com/davidyaha/graphql-redis-subscriptions
  • Use Google PubSub with https://github.com/axelspringer/graphql-google-pubsub
  • Use MQTT enabled broker with https://github.com/aerogear/graphql-mqtt-subscriptions
  • Use RabbitMQ with https://github.com/cdmbase/graphql-rabbitmq-subscriptions
  • Use AMQP (RabbitMQ) with https://github.com/Surnet/graphql-amqp-subscriptions
  • Use Kafka with https://github.com/ancashoria/graphql-kafka-subscriptions
  • Use Kafka (using Kafkajs) with https://github.com/tomasAlabes/graphql-kafkajs-subscriptions
  • Use Postgres with https://github.com/GraphQLCollege/graphql-postgres-subscriptions
  • Use NATS with https://github.com/moonwalker/graphql-nats-subscriptions
  • Use Mongoose (MongoDB) with https://github.com/Nickolasmv/graphql-mongoose-subscriptions
  • Use multiple backends with https://github.com/jcoreio/graphql-multiplex-subscriptions
  • Use Ably for multi-protocol support with https://github.com/ably-labs/graphql-ably-pubsub
  • Use Google Firestore with https://github.com/m19c/graphql-firestore-subscriptions
  • Use Amazon's Simple Notification Service (SNS) and Simple Queue Service (SQS) with https://github.com/sagahead-io/graphql-snssqs-subscriptions
  • Add your implementation...

You can also implement a PubSub of your own, by using the exported abstract class PubSubEngine from this package. By using extends PubSubEngine you use the default asyncIterator method implementation; by using implements PubSubEngine you must implement your own AsyncIterator.

SubscriptionManager @deprecated

SubscriptionManager is the previous alternative for using graphql-js subscriptions directly, and it's now deprecated.

If you are looking for its API docs, refer to a previous commit of the repository