-
Notifications
You must be signed in to change notification settings - Fork 83
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: type the return of createEventHandler
#422
Conversation
Some questions coming up to me @G-Rath
|
|
||
export function createEventHandler(options: Options<any>) { | ||
interface EventHandler<TTransformed = unknown> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❓ Why TTransformed
for naming?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be
interface EventHandler<TTransformed = unknown> { | |
interface EventHandler<TTransformed = {}> { |
Considering U = {}
here?
Line 24 in d3df9f3
class Webhooks<T extends EmitterWebhookEvent = EmitterWebhookEvent, U = {}> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the name I used for the generic in HandlerFunction
- ideally I'd rename U
to match, but held off doing that for now to reduce the scope of the changes in both this PR & in #406 .
The reasoning for the name is that it gives you more information about what the generic actually represents, which is good as generics tend to exist in a vacuum so it's not as easy to know what values should be being passed in when using the type (i.e "should this be the event payload, or the result of what my custom transformer function is doing?").
I'm not against single letter generics, but try to only use them when they're obvious i.e
removeListener<E extends EmitterEventName>(
event: E | E[],
callback: HandlerFunction<E, TTransformed>
): void;
Because E
has a constraint, it's far more obvious that it's the name of an event, but if TTransformed
was just U
you'd not have a lot of information on what it's actually representing (as the extends
doesn't clear things up either).
Considering U = {} here?
{}
is actually means "any non-nullish value", so it's not safe to use. unknown
is the better type as we've got no constraints on what type can be passed, meaning a user can transform the value into anything (including nullish values).
I believe that U = {}
should be changed to use unknown
as well, but I've not done it because of scoping + it's one of those things that while I know of the gotcha I can't remember all the rules around it well enough to do the replacement without playing around with it first (which I need the time to do) :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a big fan of your explanations. Thanks for sharing your knowledge and taking your time on giving explanations in detail.
event: E | E[], | ||
callback: HandlerFunction<E, TTransformed> | ||
): void; | ||
receive(event: EmitterWebhookEvent): Promise<void>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be equivalent to this?
Lines 37 to 41 in d3df9f3
public receive: (options: { | |
id: string; | |
name: string; | |
payload: any; | |
}) => Promise<void>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually it should be the other way around - I've deliberately not modified that type because I don't want to blow out the scope of the PR more than needed, and the types are compatible so it's not a required change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually it should be the other way around - I've deliberately not modified that type because I don't want to blow out the scope of the PR more than needed, and the types are compatible so it's not a required change.
👍🏽
I will create an issue + PR for this if you are ok with it :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will create an issue + PR for this if you are ok with it :)
Go for gold! There's a number of potential improvements to the types and structure that I think can be made but have held off doing until #406 was landed as they're about smoothing out the internal types rather than external, and so would have been moot to land before a big refactor to the types.
That's also why I aimed for functionality first in that PR, rather than ensuring the types were completely perfect (i.e making sure there were no type
aliases for the same type)
This is preventing us from building the project, so we can't do any releases - so you could say the impact for the end user is nothing (since they're not pulling any new code), or everything (since we can't release any new code for fixs, features etc) :)
No, this is the fix that'll allow us to properly ship #406 - while the smallest version of this fix is to just export
The types for this PR are correct it's our internal types that could use improvement, and definitely worth investing improving. However the gains vs amount of work that'd be involved would be pretty low so I'd not say it's a super high priority (in particular because of the
Totally - so a quick solution would be to just do That's something I think should be addressed in another PR. |
👍🏽
👍🏽 |
We can create an issue of it ( |
I can create an issue for this too, and we discuss if |
🎉 This PR is included in version 7.22.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
receive()
signature in src/index.ts
should be the same one as the one in event-handler/index.ts
#423
This fixes the error that occurs when building the project currently, caused by merging #406
This occurs because
createEventHandler
doesn't have an explicit return type but is exported meaning TypeScript has to infer the return for the declaration file. For efficiently when doing this TypeScript inlines types as much as possible leading to the above error because it infers the type of theonAny
property of the return to behandler: (event: import("../types").BaseWebhookEvent<"*">) => any) => void
, which errors sinceBaseWebhookEvent
is not exported.This actually revealed it's also doing this for
EmitterEventName
, meaning the declaration file forcreateEventHandler
has an inline string union of ~200 strings four times (because we're doingEmitterEventName | EmitterEventName[]
so it's doing"push" | "error" | "public" | "label" | "meta" | ... | ( "push" | "error" | "public" | "label" | "meta" | ...)[]
).So I've explicitly defined the return type for
createEventEmitter
- this type ideally should be propertaged to the rest of the project (i.eState#eventEmitter
should be using this type), but it's not an easy thing to apply for a few reasons, so will leave that for a follow-up PR down the line (tbh would be good if we could remove/reduceState
as much as possible, as it's a complex object)