-
-
Notifications
You must be signed in to change notification settings - Fork 165
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
Subscribe property of a graphql subscription has wrong return type (AsyncIterable vs AsyncIterator) #493
Comments
I think it is correct as is. The issue is likely that the object you are returning technically implements both the iterable and iterator interfaces, but only has type definitions for the iterator part. An async iterable is an object that has a property with the asyncIterable symbol returning the iterator (this is the case here: https://github.com/apollographql/graphql-subscriptions/blob/master/src/pubsub-async-iterator.ts#L69-L71) |
The explicit type definition here: https://github.com/apollographql/graphql-subscriptions/blob/master/src/pubsub-engine.ts#L7-L8 prevents inferring the more accurate return type of PubSubAsyncIterator which would likely satisfy the expected asyncIterable return type. |
Thank you @hayes for the deep dive! I'll try to remove the explicit return definition (from
It looks like in the link you have shared the What I found suspicious is when I tried to satisfy the type, it was not working at runtime. I'm an old Nexus GraphQL user happily moving to Pothos ❤️ and I remember the expected type was an AsyncIterator so I thought maybe there is something to dig |
Whoops, that was just a typo in my previous comment, was writing from a phone I am not 100% sure here, but I believe that nexus is also incorrect here. I think a lot of libraries dealing with subscriptions and iterators/iterables get this wrong. Part of the confusion is that all the built in iterables (arrays, maps, sets, etc) and async iterables (async generator functions) expose iterators/async iterators that are themselves iterables as well. Many custom iterators follow this same pattern. In general this means that the overwhelming number of iterators will work mostly as expected when passed somewhere that accepts in iterable (eg a for-of, or a graphql subscription). This is usually implemented in iterators by having something like In general the way to think about iterators vs iterables is: Iterables are things that can be iterated over (array, maps, sets, etc). iterators are the stateful iteration of that collection. eg an array is iterable, but each time you iterator over it you create an iterator which knows where in the array the current iteration is, and how to get the next value. Async iterators/iterables are very similar. There are some things like generators that return something that is both iterable (has the Symbol.iterator/Symbol.asyncIterator property, that return Things that iterate over something almost always are designed to accept an iterable rather than an iterator. This is a good practice for a number of reasons: One of the most important is that is trivial to detect iterables (look for the iterator/asyncIterator symbol prop, so you know what type if object you are dealing with. Another reason is that iterators are always stateful, and designed to be consumed once. Iterables on the other hand have a function to return an iterator, meaning that if you have a source that can be iterated over multiple times, it can return a new iterator for each consumer (array, sets, maps, etc). This isn't true for async generators or the pubsub iterator, but it's better to support it whenever possible. There are a lot of libraries out there that get this wrong, and I think both graphql-subscriptions and nexus are among those. It's possible that I am missing something, or some of my understanding here is not quite right, but I believe that subscriptions are a case where an async iterable (something that has a Here is a demo showing how an iterable works with graphql (without pothos) if you change it to return the iterator directly, you will see it no-longer works: https://stackblitz.com/edit/typescript-enxt6z?file=index.ts |
@hayes Thanks for detailing your rationale about that decision. I went and look at the And then translate this statement into code
which corresponds to the code you share through stackblitz. So if I understand it right, the right way to fulfill the types expected by Pothos and also have a right implementation at runtime (without casting stuff) would be to do that below ? subscribe: () => {
const iterator = pubSub.asyncIterator('some-channel')
return {[Symbol.asyncIterator]: () => iterator}
}, I tried it and it worked but I'd be happy to have a sanity check on that. Also if that's the case would it make sense to add that to the documentation ? A subsequent question I had to this issue was how I could make sure the type coming from the I actually have something like type Payload = GetSubscriptionPayloadType<ReturnType<typeof getSubscription>>
function getSubscription() {
return subscriptionService.createSubscription('onFileUrlUpdate')
}
builder.subscriptionFields((t) => ({
onFileUrlUpdated: t.prismaField({
type: 'FileUrl',
nullable: true,
subscribe: () => {
const iterator = subscriptionService.createSubscription('onFileUrlUpdate')
return {[Symbol.asyncIterator]: () => iterator}
},
resolve: async (query, payload) => {
const {id} = payload as Payload
},
}),
})) where
and that helper type extract the message type this way export type GetSubscriptionPayloadType<T> = T extends AsyncIterator<infer U> ? U : never I tried first to let it being inferred but it didn't work and it's fine as I had the same issue with Nexus (thus the reason why the helper function extracting the payload type exists). Then I dug in the generic of Thanks you a lot! I really appreciate the time you put into these thoughts |
This works, and will make typescript happy. The real fix is just to update the
This was a bug in t.prismaField, it did not correctly account for subscriptions. I pushed a fix that should make it work correctly now, the return type of |
opened an issue here to track this: apollographql/graphql-subscriptions#261 |
Oh wow that's awesome! Thank you for the quick fix! I fetched the most recent version of |
Thanks @hayes for all the explanations and all the work you've done so far by the way. I had a very bad time trying to make it work and this issue saved my day. Now I have a complete project migrated from Nexus to Pothos :) |
Hello!
I think the return type of the Subscriber has the wrong type. It says
AsyncIterable
while I think what is expected is anAsyncIterator
pothos/packages/deno/packages/core/types/builder-options.ts
Line 18 in 731a570
Below is a screenshot of the Typescript error.
pubSub
here is an instanceimport {PubSub} from 'graphql-subscriptions'
I tested it at runtime while casting to
any
and the subscription worked as expected. I also modified the type fromAsyncIterable
toAsyncIterator
in@pothos/core/dts/types/builder-option.d.ts
as a sanity check and it fixed the typing mismatchI'm happy to create a PR if you can confirm that's an actual bug :)
The text was updated successfully, but these errors were encountered: