Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add example to server.Context.md documentation for pubsub withFilter function #272

Closed
wants to merge 1 commit into from

Conversation

Gembot-ai-admin
Copy link

Added some documentation to explain how this can be used with the withFilter function of pubSub

Took me ages to figure this out so thought I'd leave that knowledge here in case it helped someone else in the future

Added some documentation to explain how this can be used with the withFilter function of pubSub

Took me ages to figure this out so thought I'd leave that knowledge here in case it helped someone else in the future
@enisdenjo
Copy link
Owner

Sorry to disappoint but I wont be merging this. A few reasons:

  • The whole docs/ dir is auto-generated using typedoc, your changes would just get overwritten the next time yarn gendocs runs
  • The code example is straight up invalid syntax, pasting the code in JavaScript and trying to run it wont work. Specifically because of context: (ctx) => ctx.extra.context; and the subscription: { ....
  • Your addition will raise more questions than answers. Like, where does withFilter come from? What is it even? Where does pubSub come from? What does pubSub.asyncIterator do? Maybe people with Apollo background could connect the dots; but, not everyone uses Apollo, including myself.
  • Example is simply unrelated to graphql-ws, but completely related to Apollo and graphql-subscriptions. graphql-ws is a transport library, all examples in this repo are about that, not about managing your GraphQL schema.
  • Quite literally the same example with proper explanation can be found at its proper place: in Apollo's documentation on filtering events.

@enisdenjo enisdenjo closed this Nov 19, 2021
@Gembot-ai-admin
Copy link
Author

Gembot-ai-admin commented Nov 30, 2021

All good @enisdenjo, just trying to help.

Got your point about auto generated docs, makes sense.

Apollo docs is where I started, the reason I was unable to get their version working was due to the fact the context was not being passed through, hence my example. It took me a long time to figure out why that was the case.

But yeah fair enough, you want to keep this focused on a single task so makes sense not to scope-creep. It's interesting though that Apollo no longer supports web socket transport, and the library they recommended actually recommends this library which is how I found it.

I'm curious to know if there is an alternative way to manage subscriptions back to the front-end that is not "the apollo way". I wasn't really aware of there being much gql outside of that ecosystem. Without filtering you just end up broadcasting all events to every subscriber on the front-end, which is obviously not desirable due to security etc.

So if you happen to have any non-apollo libraries or resources on that I'd be super interested in those!

@n1ru4l
Copy link
Contributor

n1ru4l commented Nov 30, 2021

@Gembot-ai-admin You can simply create your own typed EventEmitter:

import { EventEmitter, on } from 'events'

type PubSubPublishArgsByKey = {
  [key: string]: [any] | [number | string, any]
}

export const createChannelPubSub = <
  TPubSubPublishArgsByKey extends PubSubPublishArgsByKey,
>() => {
  const emitter = new EventEmitter()

  return {
    publish: <TKey extends Extract<keyof TPubSubPublishArgsByKey, string>>(
      routingKey: TKey,
      ...args: TPubSubPublishArgsByKey[TKey]
    ) => {
      if (args[1] !== undefined) {
        emitter.emit(`${routingKey}:${args[0] as number}`, args[1])
      }

      emitter.emit(routingKey, args[0])
    },
    subscribe: async function* <
      TKey extends Extract<keyof TPubSubPublishArgsByKey, string>,
    >(
      ...[routingKey, id]: TPubSubPublishArgsByKey[TKey][1] extends undefined
        ? [TKey]
        : [TKey, TPubSubPublishArgsByKey[TKey][0]]
    ): AsyncGenerator<
      TPubSubPublishArgsByKey[TKey][1] extends undefined
        ? TPubSubPublishArgsByKey[TKey][0]
        : TPubSubPublishArgsByKey[TKey][1]
    > {
      const asyncIterator = on(
        emitter,
        id === undefined ? routingKey : `${routingKey}:${id as number}`,
      )
      for await (const [value] of asyncIterator) {
        yield value as any
      }
    },
  }
}

In addition you can filter with these functions:

export const map = <T, O>(map: (input: T) => Promise<O> | O) =>
  async function* mapGenerator(asyncIterable: AsyncIterableIterator<T>) {
    for await (const value of asyncIterable) {
      yield map(value);
    }
  };

export const filter = <T, U extends T = T>(filter: (input: T) => input is U) =>
  async function* filterGenerator(asyncIterable: AsyncIterableIterator<T>) {
    for await (const value of asyncIterable) {
      if (filter(value)) {
        yield value;
      }
    }
  };

// you probably wanna use a pipe with type definitions from lodash or similar :)
const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x);

Putting all the pieces together:

type PubSubChannels = {
  someEvent: [string]
}

const pubSub = createChannelPubSub<PubSubChannels>()

const Subscription = new GraphQLObjectType({
  name: "Subscription",
  fields: {
    test: {
      type: GraphQLString,
      subscribe: () =>
        pipe(
          pubSub.subscribe("someEvent"),
          map(value => value.repeat(3)),
          filter(value => value.length < 10)
        )
    }
  }
});

Also see apollographql/graphql-subscriptions#240

@Gembot-ai-admin
Copy link
Author

Wow, thanks for the input, that's super helpful.

I'm pretty sure I can get it to do what I need now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants