@types/hapi package and had problems.Definitions by: in index.d.ts) so they can respond.I have a problem with definition of ApplicationState, and Request.payload (same problem).
I tried to add some custom parameters to server.app, same as I saw in documentation, and I got compiler issues. First problem is on line where value is set.
server.app.key = 'value'; // Property 'key' does not exist on type 'ApplicationState'.
And second one is when reading that value (same issue).
Both can be hacked with casting, but from my perspective this is unnecessary (and ugly), as you can see in example bellow.
(<any> server.app).key = 'value'; // to fix first issue
// and when reading
console.log((<{ key: string }> server.app).key); // pretty ugly
I checked source, and saw that ApplicationState is defined as an empty interface, I'm not sure why it is done like that, could you maybe update it to at least support adding new keys, or in better case to be parametrized (so that one could add specific type for it, through generators). Same thing goes for Request.payload, from which is also impossible to read values (which are validated through Joi validators) without casting it. My suggestion would be to maybe add generators to Server and Request (maybe some others as well).
Example bellows solves only problems I've come up on, it may need some more stuff, it's just to demonstrate the logic.
class Server<TApplicationState = {}> {
/* rest of the code */
app: TApplicationState,
}
export interface Request<TPayload = {}, TApplicationState = {}> extends Podium {
app: TApplicationState,
payload: TPayload,
// rest of the code
}
Any opinions on subject?
ApplicationState is an empty interface to allow extension through module augmentation.
This is how you would do it:
declare module 'hapi' {
interface ApplicationState {
key: string,
}
}
About the request: Could be looked into, but considering the amount of customizable parameters possible per-route, there's a lot to be looked into besides just the payload type.
ApplicationState being a static interface isn't really ideal when you are working with multiple server instances, I would agree to make Server generic to allow this, but adding the Payload property to request is a nightmare-ish rabbit hole I don't think we should dive into for now as it would require passing many many generics around.
@saboya I agree, it's possible to re-declare interface, but it is kind of strange, since it would probably be defined somewhere outside server definition file.
@SimonSchick as for Payload what do you think for maybe creating it as any or { [name: string]: any } instead of object (at the end), so that we could avoid casting it before any usage?
Also, while not exactly pretty, you can do custom payload typings in a not completely terrible way:
interface CustomRouteRequest extends Request {
payload: {
email: string,
password: string,
}
}
type CustomRouteHandler = (request: GetTokenRequest, h: ResponseToolkit, err: Error) => Promise<{ token: string }>
server.route({
method: 'POST',
path: '/auth',
handler: (async (request, h) => {
console.log(request.payload.email); // type-checked
console.log(request.payload.password); // type-checked
return { token: 'hash' }; // type-checked
} as CustomRouteHandler)
});
Could be prettier, but it works.
@saboya that is significantly more verbose than just casting to your desired payload type without really improving type safety.
I created a PR that addresses this (https://github.com/DefinitelyTyped/DefinitelyTyped/pull/36037). It allows you to pass interfaces/classes for both request.app and server.app as generic parameters into the hapi server constructor. This is my first PR to DefinitelyTyped (to any open source project, really), so I would greatly appreciate feedback.
ApplicationStateis an empty interface to allow extension through module augmentation.This is how you would do it:
declare module 'hapi' { interface ApplicationState { key: string, } }@saboya Thank you for this idea. I am learning TS at the moment. I tried this to solve the same issue as OP, but encountered a different problem. When declaring the module this way, it appears to affect the behavior of the rest of the hapi types. For example, when trying to type an instance of a hapi Server, I get the error that "Module '"hapi"' has no exported member 'Server'." Can you help me to understand how to best accomplish this? Thanks
Any news on this one?
@SimonSchick as for
Payloadwhat do you think for maybe creating it asanyor{ [name: string]: any }instead ofobject(at the end), so that we could avoid casting it before any usage?
I second this.
ApplicationStateis an empty interface to allow extension through module augmentation.This is how you would do it:
declare module 'hapi' { interface ApplicationState { key: string, } }
@saboya Thanks for sharing this module augmentation solution.
Given a Hapi project that defines two plugins, each of which augments the ApplicationState with a different key, where should ApplicationState be best augmented?
Most helpful comment
@saboya I agree, it's possible to re-declare interface, but it is kind of strange, since it would probably be defined somewhere outside server definition file.
@SimonSchick as for
Payloadwhat do you think for maybe creating it asanyor{ [name: string]: any }instead ofobject(at the end), so that we could avoid casting it before any usage?