Skip to content

Commit

Permalink
Add a(n optional) generic type map to PubSub. (#245)
Browse files Browse the repository at this point in the history
* Add a(n optional) generic type map to PubSub.

The class PubSub now has a generic type parameter so its methods `publish` and `subscribe` can be **optionally** type-checked by TypeScript.

* Added part for PubSub generic.

* Slight README tweaks to align formatting / adjust grammar a bit

* Changelog update

Co-authored-by: hwillson <hugh@octonary.com>
  • Loading branch information
mchccn and hwillson committed Nov 25, 2021
1 parent d370843 commit 8e8d511
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 8 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

### 3.0.0 (not yet released)

- TODO
- Add an optional generic type map to `PubSub`. <br/>
[@cursorsdottsx](https://github.com/cursorsdottsx) in [#245](https://github.com/apollographql/graphql-subscriptions/pull/245)

### 2.0.1 (not yet released)

Expand Down
28 changes: 24 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ To begin with GraphQL subscriptions, start by defining a GraphQL `Subscription`

```graphql
type Subscription {
somethingChanged: Result
somethingChanged: Result
}

type Result {
id: String
id: String
}
```

Expand All @@ -52,7 +52,27 @@ import { PubSub } from 'graphql-subscriptions';
export const pubsub = new PubSub();
```

Now, implement your Subscriptions type resolver, using the `pubsub.asyncIterator` to map the event you need:
If you're using TypeScript you can use the optional generic parameter for added type-safety:

```ts
import { PubSub } from "apollo-server-express";

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.asyncIterator` to map the event you need:

```js
const SOMETHING_CHANGED_TOPIC = 'something_changed';
Expand All @@ -68,7 +88,7 @@ export const resolvers = {

> Subscriptions resolvers are not a function, but an object with `subscribe` method, that returns `AsyncIterable`.
Now, the GraphQL engine knows that `somethingChanged` is a subscription, and every time we use `pubsub.publish` over this topic - it will publish it using the transport we use:
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:

```js
pubsub.publish(SOMETHING_CHANGED_TOPIC, { somethingChanged: { id: "123" }});
Expand Down
14 changes: 11 additions & 3 deletions src/pubsub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ export interface PubSubOptions {
eventEmitter?: EventEmitter;
}

export class PubSub extends PubSubEngine {
export class PubSub<
Events extends { [event: string]: unknown } = Record<string, never>
> extends PubSubEngine {
protected ee: EventEmitter;
private subscriptions: { [key: string]: [string, (...args: any[]) => void] };
private subIdCounter: number;
Expand All @@ -17,12 +19,18 @@ export class PubSub extends PubSubEngine {
this.subIdCounter = 0;
}

public publish(triggerName: string, payload: any): Promise<void> {
public publish<K extends keyof Events>(
triggerName: K & string,
payload: Events[K] extends never ? any : Events[K]
): Promise<void> {
this.ee.emit(triggerName, payload);
return Promise.resolve();
}

public subscribe(triggerName: string, onMessage: (...args: any[]) => void): Promise<number> {
public subscribe<K extends keyof Events>(
triggerName: K & string,
onMessage: (...args: any[]) => void
): Promise<number> {
this.ee.addListener(triggerName, onMessage);
this.subIdCounter = this.subIdCounter + 1;
this.subscriptions[this.subIdCounter] = [triggerName, onMessage];
Expand Down

0 comments on commit 8e8d511

Please sign in to comment.