Search code examples
typescripthapi

How to augment Hapi.js server methods when using TypeScript?


To augment hapi module to add type checking for server methods, following is the code:

import { Server } from 'hapi';

declare module 'hapi' {

    export interface Server {
        methods: {
            getUsername: () => Promise<string>;
        };
    }
}

However, this doesn't work because @types/hapi package already defines methods as:

readonly methods: Util.Dictionary<ServerMethod>;

From this StackOverflow question, As far as I know, it is not possible to redefine the existing property. But I am not sure. I could not find any documentation about this. If this is not the case, how can I augment/patch/fix the type info?

As a workaround, I have created a new Hapi.js Server interface that extends the original interface. So instead of using hapi Server interface, I am using the one I created:

// src/augment.d.ts
import { Server as HapiServer } from 'hapi';

export interface Server extends HapiServer {

    methods: {
        getUsername: () => Promise<string>;
    };
}

// In my file src/server.ts file
import { Server } from 'src/augment';

// NOW IT TYPECHECKS CORRECTLY
const username = server.methods.getUsername();

Is this the right approach to take?


Solution

  • While we can't modify the type of an existing field in an interface, we can modify the type of the field in this case. The type of the field is Util.Dictionary<ServerMethod>. While Util.Dictionary is used in several places, the only place it is used with the type argument ServerMethod is for this member.

    We can extend the Dictionary interface with the extra methods and have the methods only show up if the type parameter is ServerMethod (also we do a bit of magic insipider from here to not show the methods if T is any)

    declare module 'hapi' {
        export namespace Util {
            type IfAny<T, Y, N> = 0 extends (1 & T) ? Y : N; 
            export interface Dictionary<T> {
                getUsername<T extends ServerMethod>(this: Dictionary<T> & IfAny<T, never, {}>) : Promise<string>;
            }
        }
    }
    
    let d = new Server({})
    let f!: Util.Dictionary<any>;
    
    d.methods.getUsername() // type checked
    

    Note Ideally there would be a dedicated type for methods that would allow us to add methods to it without extending the very generic Dictionary interface, but the typings are what they are right now. Someone could do a pull request to create a new interface for methods....