1.0.12 • Published 4 years ago

graphql-field-subscriptions v1.0.12

Weekly downloads
1
License
ISC
Repository
-
Last release
4 years ago

graphql-field-subscriptions

This library allows GraphQL.js servers to treat subscriptions much like queries, in that one can make successive subscriptions through the schema's graph, much like multiple query resolves can be made in succession.

Installation

For npm users,

$ npm install graphql graphql-field-subscriptions

For yarn users,

$ yarn add graphql graphql-field-subscriptions

This library supports TypeScript by default, as God intended

The problem (in detail)

Given a resolver map like so,

const schema = gql`
    type Query {
        hello: Hello!
    }

    type Hello {
        world: String!
    }
`

const resolverMap = {
    Query: {
        hello: () => ({}),
    },
    Hello: {
        world: () => "Hello World!",
    },
}

The nature of GraphQL allows us to make a query like so,

{
    hello {
        world
    }
}

And recieve the result,

{
    "data": {
        "hello": {
            "world": "Hello World!"
        }
    }
}

However, the same is not true for subscriptions. If we now have a schema and resolver map like so,

const schema = gql`
    type Subscription {
        hello: Hello!
    }

    type Hello {
        world: String!
    }
`

const resolverMap = {
    Subscription: {
        hello: {
            subscribe: () => toAsyncIterator({ hello: {} }),
        },
    },
    Hello: {
        world: {
            subscribe: () => toAsyncIterator({ world: "Hello World!" }),
        },
    },
}

const toAsyncIterator = x =>
    async function* () {
        yield x
    }

And query the server accordingly,

subscription {
    hello {
        world
    }
}

Then we recieve an error, like so,

{
    "error": {
        "name": "FormatedError",
        "message": "Unknown error",
        "originalError": "Cannot return null value for non-null type."
    }
}

Which is odd, as we haven't returned null anywhere. On further inspection, if we make a small change:

const resolverMap = {
    ...
    Hello: {
        world: {
            resolve: () => 'Hello World!', // new line here
            subscribe: () => toAsyncIterator({ world: 'Hello World!' })
        }
    }
}

Then we get the return value as desired. However, what if we wanted the value of hello.world to change over time? GraphQL.js currently does not allow for this, only allowing subscriptions at the top level of the Subscription query. This certainly isn't in the spirit of GraphQL.

The solution

This library supplies a single function, patchFieldSubscriptions, which allows for this functionality. If we do the following,

import { patchFieldSubscriptions } from 'graphql-field-subscriptions'

...

const resolverMap = patchFieldSubscriptions({
    Subscription: {
        hello: {
            subscribe: () => toAsyncIterator({ hello: {} })
        }
    },
    Hello: {
        world: {
            resolve: () => 'A different string, to show that this works',
            subscribe: () => toAsyncIterator({ world: 'Hello World!' })
        }
    }
})

...

We now get the following result,

{
    "data": {
        "hello": {
            "world": "Hello World!"
        }
    }
}

Not only this, but the value of world, as well as any values at a further depth, can mutate over time.

...

const resolverMap = patchFieldSubscriptions({
    ...
    Hello: {
        world: {
            subscribe: () => (async function* () {
                while (true) {
                    yield `Here's a cool number: ${Math.random()}`
                    await wait(5000) // this function hangs the thread for 5 seconds
                }
            })
        }
    }
})

...
{
    "data": {
        "hello": {
            "world": "Here's a cool number: 0.8345525102611744"
        }
    }
}
...

{
    "data": {
        "hello": {
            "world": "Here's a cool number: 0.6994837333822601"
        }
    }
}
...

{
    "data": {
        "hello": {
            "world": "Here's a cool number: 0.22817198786140014"
        }
    }
}
1.0.12

4 years ago

1.0.9

4 years ago

1.0.11

4 years ago

1.0.10

4 years ago

1.0.8

4 years ago

1.0.7

4 years ago

1.0.6

4 years ago

1.0.5

4 years ago

1.0.4

4 years ago

1.0.2

4 years ago

1.0.3

4 years ago

1.0.1

4 years ago

1.0.0

4 years ago