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

Add types to async fastify hooks #2615

Merged
merged 15 commits into from Oct 30, 2020
Merged

Add types to async fastify hooks #2615

merged 15 commits into from Oct 30, 2020

Conversation

RafaelGSS
Copy link
Member

@RafaelGSS RafaelGSS commented Oct 14, 2020

I did not add types for non-documented async hooks like: onRegister hook. I would like to adjust it in another PR.

This PR solve this kind of usage:

const preValidationExample: preValidationAsyncHookHandler = async (_request: FastifyRequest, _reply: FastifyReply) => {  };
preValidationExample({} as FastifyRequest, {} as FastifyReply); // don't need to pass something as third param

Checklist

@RafaelGSS RafaelGSS added the typescript TypeScript related label Oct 14, 2020
@RafaelGSS RafaelGSS requested a review from a team October 14, 2020 00:40
@L2jLiga
Copy link
Member

L2jLiga commented Oct 14, 2020

Can't we use overloads?

example:

/**
 * `onRequest` is the first hook to be executed in the request lifecycle. There was no previous hook, the next hook will be `preParsing`.
 *  Notice: in the `onRequest` hook, request.body will always be null, because the body parsing happens before the `preHandler` hook.
 */
export interface onRequestHookHandler<
  RawServer extends RawServerBase = RawServerDefault,
  RawRequest extends RawRequestDefaultExpression<RawServer> = RawRequestDefaultExpression<RawServer>,
  RawReply extends RawReplyDefaultExpression<RawServer> = RawReplyDefaultExpression<RawServer>,
  RouteGeneric extends RouteGenericInterface = RouteGenericInterface,
  ContextConfig = ContextConfigDefault
> {
  (
    request: FastifyRequest<RouteGeneric, RawServer, RawRequest>,
    reply: FastifyReply<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig>,
    done: HookHandlerDoneFunction
  ): void;

  (
    request: FastifyRequest<RouteGeneric, RawServer, RawRequest>,
    reply: FastifyReply<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig>,
  ): Promise<unknown>;
}

@zekth
Copy link
Member

zekth commented Oct 14, 2020

@L2jLiga unfortunately it doesn't seem to work :(

@fox1t
Copy link
Member

fox1t commented Oct 14, 2020

@L2jLiga unfortunately it doesn't seem to work :(

What's the error?

@zekth
Copy link
Member

zekth commented Oct 14, 2020

@L2jLiga unfortunately it doesn't seem to work :(

What's the error?

Error was on the addHook method not taking the proper overload.

Copy link
Member

@L2jLiga L2jLiga left a comment

Choose a reason for hiding this comment

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

Let's add tests first and then check what is better approach

@RafaelGSS
Copy link
Member Author

I tried the approach of overload but seems not works. However, I'll update the PR with this approach to show.

@RafaelGSS
Copy link
Member Author

@L2jLiga @fox1t Attempt added 1562086

@L2jLiga
Copy link
Member

L2jLiga commented Oct 15, 2020

I just checked both approaches with TS playground, neither separated declaration nor inside one interface seems to work

update: seems like I found possible solution, @fox1t wdyt?

@zekth
Copy link
Member

zekth commented Oct 15, 2020

I just checked both approaches with TS playground, neither separated declaration nor inside one interface seems to work

update: seems like I found possible solution, @fox1t wdyt?

If you try this in the project the issue comes at the end of the different hooks. Like the preValidation hook has an error related to onClose which is totally out of scope. I'll reproduce and share the code.

Also in your example:

'req' is declared but its value is never read.
'res' is declared but its value is never read.
'req' is declared but its value is never read.
'res' is declared but its value is never read.
Argument of type '(req: Object, res: Object, done: DoneFn) => Promise<void>' is not assignable to parameter of type '(req: Object, res: Object) => Promise<void>'

Means like the type mapping is not working properly

@L2jLiga
Copy link
Member

L2jLiga commented Oct 15, 2020

'req' is declared but its value is never read.
'res' is declared but its value is never read.
'req' is declared but its value is never read.
'res' is declared but its value is never read.

Yeah, it's just a warning that they're not used hence can be deleted

Argument of type '(req: Object, res: Object, done: DoneFn) => Promise<void>' is not assignable to parameter of type '(req: Object, res: Object) => Promise<void>'

Means like the type mapping is not working properly

This is expected due to documentation

Notice: the done callback is not available when using async/await or returning a Promise. If you do invoke a done callback in this situation unexpected behaviour may occur, e.g. duplicate invocation of handlers.

@L2jLiga
Copy link
Member

L2jLiga commented Oct 15, 2020

I updated example in playground: link

update: link was updated

@zekth
Copy link
Member

zekth commented Oct 15, 2020

I updated example in playground: link

My bad you're right. This looks like a good alternative

@mcollina mcollina requested a review from a team October 15, 2020 07:34
@L2jLiga
Copy link
Member

L2jLiga commented Oct 15, 2020

@RafaelGSS, @fox1t what is your opinion on it?

@fox1t
Copy link
Member

fox1t commented Oct 15, 2020

@L2jLiga, this is a brilliant solution! Conditional types allow inferring the proper types instead of relying upon intersection/overloading.

As we (re)discovered here, we can't use overloading (type intersection in the types world) for fastify methods. We already encountered this same issue when we decided to deprecate the FastifyPlugin interface in favor of FastifyPluginCallback and FastifyPluginAsync.
@RafaelGSS, can you try to implement what @L2jLiga made in the last playground? I am curious if it works in our codebase. If so, we should start thinking about using it in other places.

@L2jLiga
Copy link
Member

L2jLiga commented Oct 15, 2020

@fox1t I played a bit more with types and think you will like this: link

It's universal solution and you have to pass arguments as second generic to DonableFn<T, Args, DoneFn>

@fox1t
Copy link
Member

fox1t commented Oct 15, 2020

@fox1t I played a bit more with types and think you will like this: link

It's universal solution and you have to pass arguments as second generic to DonableFn<T, Args, DoneFn>

I definitely like this. @Ethan-Arrowood what do you think?
We have to remember that this works only on TS >= 4.0.0, though.
Can you please exlpain line by line DonableFn type? I want to add this as reference somewhere. I am adding some comments by my own.

  • Why T extends (...args: any[]) => void is passed insread of T extends (...args: any[]) => any?
  • This should not be valid but it is valid impl2.addHook('onRequest', () => {}); No call to done() nor returning promise.

@Ethan-Arrowood
Copy link
Member

Ooh very clever. Inferring the function type (callback v async) based on the arguments list. I like it!

@L2jLiga
Copy link
Member

L2jLiga commented Oct 15, 2020

  • Why T extends (...args: any[]) => void is passed insread of T extends (...args: any[]) => any?

this does not make much sense for example, so no answer 😅

  • This should not be valid but it is valid impl2.addHook('onRequest', () => {}); No call to done() nor returning promise.

Unfortunately I have no idea how to force ts to check arguments length 😕

Can you please exlpain line by line DonableFn type? I want to add this as reference somewhere. I am adding some comments by my own.

I think would be better to describe it block by block, I will try provide description asap 🐱

@Ethan-Arrowood
Copy link
Member

Unfortunately I have no idea how to force ts to check arguments length 😕

I don't think you can. I believe TS specifically doesn't do this for some other reasons. Honestly, I can't remember exactly but I do think I explored that rabbit hole in the past and came up empty.

@RafaelGSS
Copy link
Member Author

Hey! I'll update the example tonight with the example provided by @L2jLiga! Seems very interesting!

@fox1t
Copy link
Member

fox1t commented Oct 15, 2020

@L2jLiga what is addErrorHook suppose to be? Shouldn't it be https://www.fastify.io/docs/v3.2.x/Hooks/#onerror?

@L2jLiga
Copy link
Member

L2jLiga commented Oct 15, 2020

I don't think you can. I believe TS specifically doesn't do this for some other reasons. Honestly, I can't remember exactly but I do think I explored that rabbit hole in the past and came up empty.

Then I suggest to don't worry about :)

Hey! I'll update the example tonight with the example provided by @L2jLiga! Seems very interesting!

Cool! Thanks dude!

@L2jLiga what is addErrorHook suppose to be? Shouldn't it be https://www.fastify.io/docs/v3.2.x/Hooks/#onerror?

Just an example how it can be used for hooks with different arguments length, does not related to fastify itself

@RafaelGSS
Copy link
Member Author

@L2jLiga I have failed to update the onRequestHookHandler with these solutions. Can you update one type on this branch? Not sure about the solution

@fox1t
Copy link
Member

fox1t commented Oct 16, 2020

@mcollina we are waiting for the description of the code provided by @L2jLiga. After that, we can try to integrate his solution in fastify types. If it works we finally have a way of expressing polymorphic methods and it would be an awesome achievement.
Again, this solution would be only compatibile with TS >= 4.0, though.

@RafaelGSS
Copy link
Member Author

Fixed (Sorry for the late - busy week). If the implementation is accepted I'll change every hook.

@RafaelGSS
Copy link
Member Author

@L2jLiga Unfortunately the implementation seems not to work on tsd checks. Check the tsd errors on callback-or-promise.d.ts and hooks.test-d.ts.

@L2jLiga
Copy link
Member

L2jLiga commented Oct 24, 2020

@L2jLiga Unfortunately the implementation seems not to work on tsd checks. Check the tsd errors on callback-or-promise.d.ts and hooks.test-d.ts.

@RafaelGSS I mentioned this here and already thinking how we can fix it 🤔

@L2jLiga
Copy link
Member

L2jLiga commented Oct 24, 2020

Here's fix:

tsdjs/tsd#83

@fox1t
Copy link
Member

fox1t commented Oct 26, 2020

I think we should just wait for the TS 4.1 if we want to implement this. It is a breaking change for TS users.

@RafaelGSS
Copy link
Member Author

@fox1t This does not change only on fastify side? IMHO we should land this fix without the overload/conditional type and after the tsd merge, we add support for it.

@fox1t
Copy link
Member

fox1t commented Oct 26, 2020

Yes. Sorry for the confusion: I meant that we should wait to land the "overload/conditional type" part and go on with the current fix for the moment.

Copy link
Member

@L2jLiga L2jLiga left a comment

Choose a reason for hiding this comment

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

Ok, works for me :)

Copy link
Member

@fox1t fox1t left a comment

Choose a reason for hiding this comment

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

LGTM!

Copy link
Member

@zekth zekth left a comment

Choose a reason for hiding this comment

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

lgtm

@RafaelGSS
Copy link
Member Author

@Ethan-Arrowood Have any concerns about? I will land tomorrow.

Copy link
Member

@Ethan-Arrowood Ethan-Arrowood left a comment

Choose a reason for hiding this comment

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

lgtm :) great work everyone!

@RafaelGSS RafaelGSS merged commit de94d31 into master Oct 30, 2020
@RafaelGSS RafaelGSS deleted the fix-types-hooks branch October 30, 2020 12:07
@github-actions
Copy link

github-actions bot commented Feb 6, 2022

This pull request has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Feb 6, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
typescript TypeScript related
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants