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

fix: support async generators as response resolvers #2108

Merged
merged 16 commits into from
Jul 23, 2024

Conversation

kettanaito
Copy link
Member

@kettanaito kettanaito commented Mar 25, 2024

@kettanaito kettanaito marked this pull request as draft March 25, 2024 11:58
@kettanaito kettanaito marked this pull request as ready for review March 25, 2024 12:54
@kettanaito kettanaito changed the title test: add generator resolver tests fix: support async generators as response resolvers Mar 25, 2024
@@ -256,6 +266,9 @@ export abstract class RequestHandler<
return null
}

// Preemptively mark the handler as used.
// Generators will undo this because only when the resolver reaches the
// "done" state of the generator that it considers the handler used.
this.isUsed = true
Copy link
Member Author

Choose a reason for hiding this comment

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

It's important to mark the handler as used immediately, even if using generators then opts-out from this behavior. This is what we promise right now, so let's keep that promise.

@@ -57,6 +56,11 @@ export type AsyncResponseResolverReturnType<
MaybeAsyncResponseResolverReturnType<ResponseBodyType>,
MaybeAsyncResponseResolverReturnType<ResponseBodyType>
>
| AsyncGenerator<
Copy link
Member Author

Choose a reason for hiding this comment

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

Older versions of TypeScript (4.7 - 5.2) have trouble digesting this type:

 FAIL  resolver-generator.test-d.ts > supports async generator function as response resolver
TypeCheckError: Argument of type '() => AsyncGenerator<StrictResponse<JsonBodyType>, StrictResponse<JsonBodyType>, any>' is not assignable to parameter of type 'HttpResponseResolver<never, never, { value string; }>'.
  Type 'AsyncGenerator<StrictResponse<JsonBodyType>, StrictResponse<JsonBodyType>, any>' is not assignable to type 'AsyncResponseResolverReturnType<{ value string; }>'.
    Type 'AsyncGenerator<StrictResponse<JsonBodyType>, StrictResponse<JsonBodyType>, any>' is not assignable to type 'AsyncGenerator<ResponseResolverReturnType<{ value string; }>, ResponseResolverReturnType<{ value string; }>, ResponseResolverReturnType<{ value string; }>>'.
      The types returned by 'next(...)' are incompatible between these types.
        Type 'Promise<IteratorResult<StrictResponse<JsonBodyType>, StrictResponse<JsonBodyType>>>' is not assignable to type 'Promise<IteratorResult<ResponseResolverReturnType<{ value string; }>, ResponseResolverReturnType<{ value string; }>>>'.
          Type 'IteratorResult<StrictResponse<JsonBodyType>, StrictResponse<JsonBodyType>>' is not assignable to type 'IteratorResult<ResponseResolverReturnType<{ value string; }>, ResponseResolverReturnType<{ value string; }>>'.
            Type 'IteratorYieldResult<StrictResponse<JsonBodyType>>' is not assignable to type 'IteratorResult<ResponseResolverReturnType<{ value string; }>, ResponseResolverReturnType<{ value string; }>>'.
              Type 'IteratorYieldResult<StrictResponse<JsonBodyType>>' is not assignable to type 'IteratorYieldResult<ResponseResolverReturnType<{ value string; }>>'.
                Type 'StrictResponse<JsonBodyType>' is not assignable to type 'ResponseResolverReturnType<{ value string; }>'.

Reproduction steps

  • TypeScript 4.7
  • pnpm test:ts

Copy link
Member Author

Choose a reason for hiding this comment

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

Hi, @Andarist! Sorry for troubling you once again. If you have a minute, could you please lend me a hand with this type issue?

Copy link
Contributor

Choose a reason for hiding this comment

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

I would need to see a slimmed-down repro. The one that I tried put together in a rush errors in the current TS version too: TS playground

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks for giving it a try, @Andarist. Here's a minimal repro:

pnpm install
pnpm build
pnpm test:ts test/typings/resolver-generator.test-d.ts

Copy link
Contributor

Choose a reason for hiding this comment

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

That is not exactly minimal ;p it still depends on everything in the MSW's codebase. I could really use a self-contained TS playground here.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, I can do that.

Copy link
Contributor

Choose a reason for hiding this comment

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

FWIW I did compile and test locally, and it all seemed to work, though I did not acutally test the full range of TS versions (but I can't imagine it wouldn't work, maybe I'm jinxing myself)

Copy link
Member Author

Choose a reason for hiding this comment

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

Happy to give this a try once it's here 👀 Installing different typescript dependency versions triggers the test:ts into using that version for testing. The CI will also cover all the ranges for us. So excited we may actually ship this!

Copy link
Contributor

Choose a reason for hiding this comment

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

Sent #2213.

FWIW if you're looking for multi-version testing and aren't too tied to vitest, https://github.com/tstyche/tstyche is one option I've heard of which automatically runs multiple versions of TS without having to install them explicitly.

Copy link
Member Author

Choose a reason for hiding this comment

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

That's nice! Something to consider in the setup.

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
@kettanaito
Copy link
Member Author

The fix from @jakebailey (#2213) seems to fix the compatibility with older TypeScript versions! 🎉 Super job here. Any objections to merge this?

@jakebailey
Copy link
Contributor

This technically now enables passing in arrays, maps, etc; is that desirable? Should it be tested?

@kettanaito
Copy link
Member Author

@jakebailey as the return value of the resolver? It would be nice to guard against that on runtime at least.

@jakebailey
Copy link
Contributor

Actually, the old code may have already allowed that, at least for like .values()...

@jakebailey
Copy link
Contributor

It's probably fine as is, I'm just paranoid given I know nothing about this codebase!

@kettanaito
Copy link
Member Author

@jakebailey, well, now you know a little bit :) I'm infinitely grateful for your work on this. Looks like I know another TypeScript wizard now.

I will give that false matching types scenario a try before releasing this.

@kettanaito
Copy link
Member Author

Yeah, @jakebailey, you're right. As long as the return type satisfies the Iterator type, there's no type error:

http.get('/user/:a/:b/:b', ({ params }) => {
  return []
  // ^ This compiles OK while it must error. 
})

@jakebailey
Copy link
Contributor

I guess the question is whether or not that's bad or not; I can just as easily pass in a generator which never yields today, right?

@kettanaito
Copy link
Member Author

That's certainly agains the established contract. Response resolvers either return/yield a Response instance or nothing. Nothing is an accepted value. But anything save for a Response instance is not. We don't have runtime guards against that now because we rely on type-level validation to prevent developers from returning non-Response stuff from their resolvers.

@kettanaito
Copy link
Member Author

This only seems to be a problem when returning an empty array () => []. Arrays are iterable, and they will even respect the resolver return type (response type) so that () => [123] errors, but an empty array is okay. Exploring if I can tweak the types so that an empty iterator is not allowed but that's equivalent of having a generator that doesn't yield value, which is still okay in MSW world.

Huh.

@kettanaito
Copy link
Member Author

Oddly enough, I'm beginning to like the array stuff. Since arrays are iterable, one can return an array of mocked responses at once (no need for a generator). If that happens, MSW will exhaust that array as it does a generator function, producing mocked responses in order, and stopping on the last response to then use it forever. I wouldn't document this behavior but I like it!

@kettanaito kettanaito merged commit d38fc3d into main Jul 23, 2024
12 checks passed
@kettanaito kettanaito deleted the fix/generator-resolver branch July 23, 2024 13:31
@kettanaito
Copy link
Member Author

Released: v2.3.3 🎉

This has been released in v2.3.3!

Make sure to always update to the latest version (npm i msw@latest) to get the newest features and bug fixes.


Predictable release automation by @ossjs/release.

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

Successfully merging this pull request may close these issues.

"Cannot read properties of undefined (reading 'get')" when using generators/async generators as resolvers
4 participants