How can I augment a property within a third-party TypeScript interface defined as "any"?
Asked Answered
F

1

15

Given a third-party TypeScript module defined like this:

// in /node_modules/third-party-module/index.d.ts
declare module 'ThirdPartyModule' {
    export interface ThirdPartyInterface {
        data: any;
    }
}

How can I augment this module to more strictly type the data property?

I've tried this:

// in /app/typings.d.ts
declare module 'ThirdPartyModule' {

    interface IMyCustomType {}

    interface ThirdPartyInterface {

        // causes a compiler error: Subsequent property declarations 
        // must have the same type.  Property 'data' must be of type 
        // 'any', but here has type 'IMyCustomType'.
        data: IMyCustomType;
    }
}

But this gives me a compiler error: "Subsequent property declarations must have the same type. Property 'data' must be of type 'any', but here has type 'IMyCustomType'."

If the third-party module instead defined the property as an actual type, like this:

// in /node_modules/third-party-module/index.d.ts
declare module 'ThirdPartyModule' {
    interface IAnotherThirdPartyInterface {
        something: string;
    }

    interface ThirdPartyInterface {
        data: IAnotherThirdPartyInterface;
    }
}

I could simply make my IMyCustomType interface extend this third party type:

// in /app/typings.d.ts
declare module 'ThirdPartyModule' {
    interface IMyCustomType extends IAnotherThirdPartyInterface {}

    interface ThirdPartyInterface {
        data: IMyCustomType;
    }
}

However, since the type is defined any, I can't extend it:

// causes a compiler error:  'any' only refers to a type, 
// but is being used as a value here.
interface IMyCustomType extends any {}
Feme answered 8/2, 2018 at 16:44 Comment(4)
What is your use case ? Do you want to have stricter types for calls in your library ? Or do you need this interface for your own functions which require similar data structures ?Fend
I'm attempting to override the type of Hapi's request.app property. The property is a generic place to store application data, so it is typed as any by the library's authors. However, since I know how this property will be used, I'd like to add typing information to this property to avoid casts.Feme
Hmm, I must admit that I've never found a better answer than "use a locally modified version of the library" for this (and possibly trying to get such a version pushed upstream if it's better for everyone). Declaration merging isn't granular enough for some purposes and this is one of the pain points. I'd be interested in seeing a better answer than that too.Seashore
@Seashore you might not be able to redefine it, but you could have a typed alternative to it. Which in this case might be just as useful.Fend
F
11

While you can't redefine the property, an alternative would be to define a new property, and augment the original object with this new property. While this solution is not generally applicable, if the property is on a class it can be done. In your case you mention in the comments hapi. Since the property is on the Server class we can define a new typed version of the property.

hapi.augment.ts

import *  as Hapi from 'hapi'

declare module 'hapi' {
    export function server(cfg: any): Hapi.Server;
    interface Server {
        typedApp:  {
            myData: string
        }
    }
}

Object.defineProperty(Hapi.Server.prototype, 'typedApp', {
    enumerable: false,
    get(this: Hapi.Server){
        return this.app;
    },
    set(this: Hapi.Server, value: any){
        this.app = value;
    }
});

usage.ts

import *  as Hapi from 'hapi'
import './hapi.augment'

const server = new Hapi.Server()
server.connection({ port: 3000, host: 'localhost' });
server.start();
server.typedApp.myData = "Here";

server.route({
    method: 'GET',
    path: '/',
    handler: function (request, reply) {
        reply(request.server.typedApp.myData);
    }
});
Fend answered 8/2, 2018 at 18:28 Comment(1)
Very interesting solution! This gets me closer to what I want. Thanks for the example!Feme

© 2022 - 2024 — McMap. All rights reserved.