Skip to main content
Version: Next (2.x)

Typed PubSub

TypedPubSub is a thin wrapper around graphql-subscriptions that types channel names and payloads. The publish and subscribe methods accept only channels declared in your event map; typos and payload mismatches fail at compile time.

tip

GraphQL Yoga already ships a type-safe PubSub. If you're on Yoga, you don't need this package.

note

Yoga's PubSub uses tuple payloads ('channel': [Payload]); TypedPubSub (over graphql-subscriptions) uses the bare type. Don't copy a PubSubMap between the two without flipping the shape.

Installation

yarn add @baeta/subscriptions-pubsub

Basic setup

1. Declare your channels

Map each channel name to its payload type:

import { TypedPubSub } from "@baeta/subscriptions-pubsub";
import { PubSub } from "graphql-subscriptions";
import type { User } from "../__generated__/types.ts";

export type PubSubMap = {
"user-updated": User;
[c: `user-updated-${string}`]: User;
// Add more channels as needed:
// 'post-created': Post;
// 'comment-deleted': { postId: string; commentId: string };
};

2. Create the instance

// In-memory transport — fine for development and single-instance deployments.
// For multi-instance setups, see "Using with Redis" below.
export const pubsub = new TypedPubSub<PubSub, PubSubMap>(new PubSub());

3. Add it to your context

export interface Context {
pubsub: typeof pubsub;
// ... other context properties
}

export function createContext(): Context {
return {
pubsub,
// ... other context properties
};
}

Usage

Publishing events

Publish accepts only declared channels and the right payload type:

const updateUserMutation = Mutation.updateUser.resolve(({ args, ctx }) => {
const updatedUser = {
id: args.where.id,
givenName: args.data.givenName ?? "Jon",
lastName: args.data.lastName ?? "Doe",
};

// Type-safe publish - TypeScript will error if channel or payload type is incorrect
ctx.pubsub.publish("user-updated", updatedUser);

return updatedUser;
});

Subscribing to events

Get a typed async iterable for a channel:

const userUpdatedSubscription = Subscription.userUpdated
.subscribe(({ ctx }) => {
// Type-safe subscription - channel name is autocompleted and type-checked
return ctx.pubsub.asyncIterableIterator("user-updated");
})
.resolve(({ source }) => {
// source is properly inferred as User
return source;
});

Using with Redis

For multi-instance deployments, swap the in-memory transport for graphql-redis-subscriptions:

import { RedisPubSub } from "graphql-redis-subscriptions";
import Redis from "ioredis";

const options = {
host: "redis-server",
port: 6379,
retryStrategy: (times) => Math.min(times * 50, 2000),
};

const pubsub = new TypedPubSub<RedisPubSub, PubSubMap>(
new RedisPubSub({
publisher: new Redis(options),
subscriber: new Redis(options),
}),
{
prefix: "feature-1:", // optional channel prefix
},
);