6

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?

1

1 Answer 1

3

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
    }
}
Sign up to request clarification or add additional context in comments.

2 Comments

Was just coming to this conclusion, but this is definitely it. Working around the nasty declare module 'ko-component-router/dist/context'by moving any interfaces that are expected to be augmented into the index file itself.
Important note: declare module thing is NOT the same as declare module "thing". Had me puzzled for some time. (with the quotes, it tries to resolve the module to existing module matches; without, it assumes you're declaring a whole new module/namespace)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.