Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Augment imported typescript interface

Tags:

typescript

I am using a package — ko-component-router — with the following (condensed) type definitions...

index.d.ts

export { IContext, Context } from './context';
export { Router } from './router';

context.d.ts

export interface IContext {
    $root: IContext;
    $child: IContext;
    $children: IContext[];
    $parent: IContext;
    $parents: IContext[];
    router: Router;
    route: Route;
    params: {
        [k: string]: any;
    };
    path: string;
    pathname: string;
    base: string;
    canonicalPath: string;
}
export declare class Context implements IContext {
  // ...
}

router.d.ts

import { IContext } from './context';
export declare type Middleware = (ctx: IContext, done?: () => any) => {
    beforeRender?: (done?: () => void) => Promise<any> | void;
    afterRender?: (done?: () => void) => Promise<any> | void;
    beforeDispose?: (done?: () => void) => Promise<any> | void;
    afterDispose?: (done?: () => void) => Promise<any> | void;
};
export declare class Router {
    static use(...fns: Middleware[]): void;
}

On the consumer side, middleware is registered that can add properties to the context that is passed into the view component, as such...

import { Router } from 'ko-component-router'


Router.use((ctx) => ({
    beforeRender() {
        ctx.someProperty = 'foo'
    }
}))

As expected, a Property 'someProperty' does not exist on type 'IContext' error is thrown by the compiler.

Based on the documentation on declaration merging, I attempted adding the following in order to make the compiler aware of this new property...

import { Router } from 'ko-component-router'

declare module 'ko-component-router' {
    interface IContext {
        someProperty: string
    }
}

Router.use((ctx) => ({
    beforeRender() {
        ctx.someProperty = 'foo'
    }
}))

But the same error is thrown. I've tried just about everything I can think of, but I'm unable to make the compiler aware of this new property without completely re-implementing the type definitions in my project, which is obviously far from ideal.

Is this possible, and if so, where am I going astray?

like image 541
caseyWebb Avatar asked Oct 29 '22 00:10

caseyWebb


1 Answers

As mentioned in cartant's comment, module augmentation does not work for classes and interfaces that are exported "indirectly" - this is a known issue.

It works if you augment the inner module where IContext is defined:

declare module 'ko-component-router/context' {
    export interface IContext {
        someProperty: string
    }
}

Unfortunately, anyone who adds augmentations in this way introduces a dependency on internal structure of your library.

Also, with this augmentation class Context does not compile anymore:

error TS2420: Class 'Context' incorrectly implements interface 'IContext'. Property 'someProperty' is missing in type 'Context'.

So if you add properties to interfaces which are implemented by some classes, these properties must be optional:

declare module 'ko-component-router/context' {
    export interface IContext {
        someProperty?: string
    }
}
like image 58
artem Avatar answered Dec 29 '22 14:12

artem