/lib/graphql-rx

Contents

This optional module provides the publisher and subscriber objects used by GraphQL subscriptions.

Subscription events need a transport that stays open, and the library does not provide one. A universal API can serve either a WebSocket channel or an SSE stream, and each event is an ExecutionResult either way.

The examples below use SSE, the shorter of the two: it needs no handshake, and a browser consumes it with EventSource.

Choose a WebSocket instead when the consumer is a standard GraphQL client. GraphiQL, Apollo Client and similar tools speak the graphql-transport-ws protocol, which expects the server to answer connection_init with connection_ack and to wrap each event in a next message carrying the operation’s id. An API serves that from a webSocketEvent handler, the counterpart to the sseEvent handler used below. The publisher and subscriber on this page behave identically under either transport.

Functions

createPublishProcessor

Creates a PublishProcessor that can be returned by a subscription field resolver.

Returns

PublishProcessor — a reactive event source.

Example

Create the processor outside the resolver, so the same instance survives across requests and can be published to from anywhere in the application.

Returns a processor from a subscription field:
const graphQlRxLib = require('/lib/graphql-rx');

const personProcessor = graphQlRxLib.createPublishProcessor();

const rootSubscriptionType = schemaGenerator.createObjectType({
    name: 'Subscription',
    fields: {
        personAdded: {
            type: personType,
            resolve: () => personProcessor
        }
    }
});

createSubscriber

Creates a subscriber for the publisher returned as data by a subscription execution.

Parameters

createSubscriber() takes a single params object with these properties:

Name Type Description

onNext

function

Optional. Called with each mapped ExecutionResult produced by a subscription.

Returns

Example

Executing a subscription returns a publisher as data rather than a result object. Subscribing to it starts delivery, and each event arriving in onNext is a full ExecutionResult.

Send to a named group rather than to a single clientId. Connection ids must not be collected in module-scope state, a group name is a constant, and group membership is cleaned up automatically when a client disconnects.

An API that streams to everyone in a group:
const sseLib = require('/lib/xp/sse');

exports.GET = () => ({
    sse: {
        retry: 5000
    }
});

exports.sseEvent = (event) => {
    if (event.type === 'open') {
        sseLib.addToGroup({group: 'people', clientId: event.clientId});
    }
};
Forwards each subscription event to that group:
const graphQlLib = require('/lib/graphql');
const graphQlRxLib = require('/lib/graphql-rx');
const sseLib = require('/lib/xp/sse');

const result = graphQlLib.execute(
    schema,
    'subscription { personAdded { name } }'
);

const subscriber = graphQlRxLib.createSubscriber({
    onNext: (event) => sseLib.sendToGroup({
        group: 'people',
        message: {
            event: 'personAdded',
            data: JSON.stringify(event)
        }
    })
});

result.data.subscribe(subscriber);

Classes

PublishProcessor

A reactive event source returned by createPublishProcessor(). It has no constructor.

All the examples below use a processor created with createPublishProcessor():

Creating a publish processor:
const graphQlRxLib = require('/lib/graphql-rx');

const personProcessor = graphQlRxLib.createPublishProcessor();

onNext

Publishes a value to active subscribers.

Parameters

Name Type Description

value

any

Value delivered to every active subscriber. Becomes the source of the subscription field’s resolver.

Example

The published value becomes the source of the subscription field’s resolver, so it is the application’s own data, not an execution result.

Publishes a person when one is added:
const peopleLib = require('/lib/people');

personProcessor.onNext(peopleLib.getPerson(name));

onError

Terminates the stream with an error.

Parameters

Name Type Description

error

Java Throwable

Error that terminates the stream. Construct it with Java.type(); a string or a JavaScript Error is not accepted.

Example

The argument is a Java Throwable, so construct one with Java.type() rather than passing a string or a JavaScript Error.

Ends the stream when the event source fails:
const IllegalStateException = Java.type('java.lang.IllegalStateException');

personProcessor.onError(new IllegalStateException('Person event source became unavailable'));

onComplete

Completes the stream.

Example

Subscribers receive no further events after this call.

Ends the stream normally:
personProcessor.onComplete();

filter

Forwards only the values a predicate accepts.

Parameters

Name Type Description

predicate

function

Called with each published value. The value is forwarded when the return value is truthy.

Returns

A publisher carrying only the values the predicate accepted. It exposes subscribe() and filter(), so filters can be chained.

Example

Filtering returns a new publisher and leaves the processor untouched, so one processor can back several subscription fields with different predicates.

Publishes only adults to this subscription field:
const rootSubscriptionType = schemaGenerator.createObjectType({
    name: 'Subscription',
    fields: {
        personAdded: {
            type: personType,
            resolve: () => personProcessor.filter(
                (person) => person.age >= 18
            )
        }
    }
});

subscribe

Subscribes a compatible reactive-streams subscriber.

Parameters

Name Type Description

subscriber

SubscriptionSubscriber

Subscriber that receives the published values, created with createSubscriber().

Example

Application code normally reaches the publisher as result.data after executing a subscription, and calls subscribe() on that. Calling it on the processor directly is equivalent when the resolver returns the processor unfiltered.

Subscribes a subscriber to the processor:
const subscriber = graphQlRxLib.createSubscriber({
    onNext: (event) => sseLib.sendToGroup({
        group: 'people',
        message: {data: JSON.stringify(event)}
    })
});

personProcessor.subscribe(subscriber);

SubscriptionSubscriber

A subscriber returned by createSubscriber(). It has no constructor.

cancelSubscription

Cancels its active subscription. Calling it before subscription or more than once has no effect.

Example

Cancel from wherever the subscriber is held, so the processor stops delivering to a client that has gone away. For a subscription serving one client, the SSE close event is the reliable trigger: it is terminal, firing after timeout and after error as well. A subscription feeding a broadcast group outlives any single client and is not cancelled here.

Cancels the subscription when the client’s stream closes:
exports.sseEvent = (event) => {
    if (event.type === 'close') {
        subscriber.cancelSubscription();
    }
};

Contents

Contents