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

made mercurius's implicitly injected pubsub subscribe/publish methods type safe #1115

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

adrtivv
Copy link
Contributor

@adrtivv adrtivv commented Sep 30, 2024

The rationale is that mercurius implicitly injects the pubsub field into the context received by the graphql resolvers. For making the context type-safe we need to define a custom context type to annotate its typescript type correctly for usage in the graphql resolvers and its in that context type where we can annotate the typescript type for pubsub object correctly for usage in the graphql resolvers by having the option of providing strict event_name-payload types to the pubsub.subscribe and pubsub.publish methods.

Usage is shown in the example.ts file present in the root directory of this stackblitz example with proper comments.

This implementation is inspired by this implementation of pubsub by graphql-yoga but its made to work with mercurius's implementation of pubsub.

Ideally mercurius should provide an explicit way of either correctly passing the types for these event_name-payload that are to be used in pubsub.subscribe and pubsub.publish functions or it should provide an explicit way of initializing the pubsub object instead of implicitly injecting it into the graphql context.

Signed-off-by: Matteo Collina <hello@matteocollina.com>
@adrtivv
Copy link
Contributor Author

adrtivv commented Sep 30, 2024

ahh i was fixing some formatting, anyway you can fix the minor details

Copy link
Collaborator

@mcollina mcollina left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add tests for the types? We use tsd.

package.json Show resolved Hide resolved
@adrtivv
Copy link
Contributor Author

adrtivv commented Oct 13, 2024

Can you add tests for the types? We use tsd.

Not free right now, will try later.

Copy link
Contributor

@voxpelli voxpelli left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some comments. I'm not a maintainer of this module though, just an active user.

Comment on lines +30 to +32
export interface PubSub<
TPubSubPublishArgsByKey extends PubSubPublishArgsByKey,
> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless you do this, this is a breaking change, as PubSub will require TPubSubPublishArgsByKey to be specified:

Suggested change
export interface PubSub<
TPubSubPublishArgsByKey extends PubSubPublishArgsByKey,
> {
export interface PubSub<
TPubSubPublishArgsByKey extends PubSubPublishArgsByKey = PubSubPublishArgsByKey,
> {

Comment on lines +33 to +39
subscribe<TKey extends Extract<keyof TPubSubPublishArgsByKey, string>>(
topics: TKey | TKey[],
): Promise<Readable & AsyncIterableIterator<TPubSubPublishArgsByKey[TKey]>>;
publish<TKey extends Extract<keyof TPubSubPublishArgsByKey, string>>(
event: { topic: TKey; payload: TPubSubPublishArgsByKey[TKey] },
callback?: () => void,
): void;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The subscribe is a wrapper for the emitter / mqemitter method on and publish is a wrapper for the emitter / mqemitter method emit:

export type Message = Record<string, any> & { topic: string }
on(topic: string, listener: (message: Message, done: () => void) => void, callback?: () => void): this
emit(message: Message, callback?: (error?: Error) => void): void

There is some mismatch here, both in the old and in the new types. Eg: the publish callback should be: callback?: (error?: Error) => void

I think the emitter option of the constructor should also be specified to ensure a match between it and what's sent here:

emitter?: object;

(Ideally the publish() would be reimplemented as public(topic, payload, [callback]) and have some way to resolve to a promise rather than the callback, so that an await pubsub.publish('topic1', 'value1)` would be possible, but that's out of scope of this PR)

Comment on lines +34 to +35
topics: TKey | TKey[],
): Promise<Readable & AsyncIterableIterator<TPubSubPublishArgsByKey[TKey]>>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this work properly when TKey is something like 'key1' | 'key2'?

Comment on lines +26 to +28
export type PubSubPublishArgsByKey = {
[key: string]: any;
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably mimic eg MercuriusContext and be one can extend, rather than (or in addition) to being needed to specify like PubSub<MyTopics> – that way it will work automatically in all the places that use the PubSub type.

The name should also be in the style of the other interfaces.

Suggested change
export type PubSubPublishArgsByKey = {
[key: string]: any;
};
export interface PubSubTopics {
[topic: string]: any;
}

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