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

@livesubscriptions/node-server

v0.6.16

Published

Subscription middleware and utilities for GQL Live Subscriptions

Downloads

8

Readme

Live Subscriptions

pipeline status coverage report licence npm version

Live Subscriptions for GraphQL. Client state easily managed by the server, only send patches on updates.

Installation

To integrate Live Subscriptions in your system you need to insert middleware in both the client and the server.

TLDR

  • Obtain the packages via npm.
  • Install middleware on client and server, both a single line change.
  • Introduce two small keywords in your schema
    • Prefix subscription with live; livePosts
    • Add liveId: String! to the toplevel type.

Apollo Client

yarn add @livesubscriptions/apollo-client

Assuming you already know how to set up Subscriptions for Apollo Client, adding support for Live Subscriptions is straightforward.

import { ApolloClient, ApolloLink, InMemoryCache } from '@apollo/client';
import { liveRequestHandlerBuilder } from '@livesubscriptions/apollo-client';

const client = new ApolloClient({
  cache: new InMemoryCache(),
  link: ApolloLink.from([liveRequestHandlerBuilder(), splitLink]), // It's just this line (and the import)
});

Server

yarn add @livesubscriptions/node-server

When running an Express GraphQL Server installing Live Subscriptions is can be done as follows:

import { SubscriptionServer } from 'subscriptions-transport-ws';
import { execute, subscribe } from 'graphql';
import { liveSubscribeBuilder } from '@livesubscriptions/node-server';

server.listen(PORT, () => {
  new SubscriptionServer(
    {
      execute,
      subscribe: liveSubscribeBuilder(subscribe), // It's just this line (and the import)
      schema: myGraphQLSchema,
    },
    {
      server: server,
      path: '/subscriptions',
    },
  );
});

Usage

Client side there are no additional requirements to start using Live Subscriptions. Server side some minor changes are needed.

Schema

Without Live Subscriptions a regular schema looks like the schema below. A list with posts, and each Post has an Author. Suppose our backend has all capabilities to notify all clients whenever new data is present. Whenever something changes all clients receives all Posts and all Authors. So when the name of an Author changes, all Posts and all nested Authors are send to all client.

type Subscription {
  posts: [Post!]!
}

type Post {
  author: Author!
  content: string!
}

type Author {
  name: string!
}

Therefore typically this is not done with a Subscription, but with a Query and several Subscription. Merging data is left to the client.

Now see the Live Subscriptions implementation. The root of the subscription has been changed, and prefixed with live . Also a new object is introduced, with the field liveId. Everything else is the same. With just these two minor changes to the schema Live Subscriptions can start working.

type Subscription {
  livePosts: LivePosts!
}

type LivePosts {
  liveId: string!
  posts: [Post!]!
}

type Post {
  author: Author!
  content: string!
}

type Author {
  name: string!
}

When ever a part of the data changes, only the changed data is send to all clients. For this the middleware keeps track of state for each client, and uses JSON-Patch (RFC6902) standard format to create diffs. State is automatically cleaned when the websocket closes. This keeps working, even when using multiple instance of GraphQL, since websockets are tied to a single instance.

The schema requirements for Live Subscriptions can be summarized in 2 bullets:

  • The subscription must be prefixed with live; livePosts.
  • The root object must have a liveId field op type string.

Implementation

There are no additional requirements for the client, besides installing middleware.

const { data, error, loading } = useSubscription(gql`
  subscription livePosts {
    livePosts {
      liveId
      posts {
        content
        author {
          name
        }
      }
    }
  }
`);

Server side mostly relies on the middleware as well, and regular GraphQL resolvers. However, a utility class LiveManager is provided to simply managing the AsyncIterator.

export const liveManager = new LiveManager(pubSub);
liveManager.addTopic('livePosts');
pubSub.subscribe('PostUpdateEvent', () => liveManager.publish('livePosts'), {});
pubSub.subscribe('AuthorUpdateEvent', () => liveManager.publish('livePosts'), {});

Subscription: {
  livePosts: {
    // The name of the topic in the LiveManager must be the name of this field; livePosts.
    subscribe: async (parent: unknown, args: unknown, context: any) => {
      return liveManager.addSubscription({ topic: 'livePosts' }, context.user.id);
    };
  }
}
LivePosts: {
  posts: async (root: GqlLivePosts, args: unknown, context: any) => {
    /* Resolver implementations */
  };
}

Now whenever the backend inform GraphQL on new data, the resolvers will reconstruct the entire data structure per client that might be interested. The middleware will create patch files with only the diffs per client, and the middleware client side will reconstruct the data structure.

[
  {
    "OP": {
      "path": "xxx",
      "value": "yyy"
    }
  }
]

Caveats

There are some caveats to Live Subscriptions, where the main one is that your server becomes statefull. This however is without risk, since the middleware is responsible for cleaning this state once it becomes obsolete, and websockets guarentee connection to a single server. Next to that there are some more:

  • Subscribe not accessible in all version Apollo GQL, some copy code is needed
  • useSubscription can create issues with strict mode, during development.
  • useSubscription can remain open on live reload, during development
    • https://github.com/apollographql/apollo-client/issues/6405
  • Sockets need custom dataloader init script and auth, see upcoming blogposts
    • https://github.com/apollographql/apollo-link/issues/197
    • https://www.apollographql.com/docs/react/data/subscriptions/#authentication-over-websocket
    • https://github.com/apollographql/apollo-server/issues/1526
  • useSubscription can stays active after unmount
    • Read more

Misc

  • For testability in - for example - e2e tests, liveSubscribe exposes minifyLiveData.

Keywords

GraphQL, Apollo, Live Queries, Live Subscriptions

License

By David Hardy and codecentric:

The MIT License

Copyright (c) 2022 David Hardy
Copyright (c) 2022 codecentric nl

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.