Let's say I'm using external package "foo". Its main export is an object which has an .extend() method that can be used to add functionality by creating a derived object (the original is left unchanged). It looks something like this:
import foo from 'foo';
const extendedFoo = foo.extend(
// Some complex definition
);
const result1a = extendedFoo.bar.existingMethod();
const result2a = extendedFoo.bar.addedMethod();
const result1b = foo.bar.existingMethod();
const result2b = foo.bar.addedMethod(); // error!
Unfortunately the provided types for the package aren't great and .extend() is declared to return any, which is unhelpful. I can see a couple of ways to fix the issue:
Option 1, module augmentation/interface merging:
import foo from 'foo';
declare module 'foo' {
interface Bar {
addedMethod(): string;
}
}
const extendedFoo = foo.extend(
// ...
) as foo.Foo;
Option 2, interface extension:
import foo from 'foo';
interface ExtendedBar extends foo.Bar {
addedMethod(): string;
}
interface ExtendedFoo extends foo.Foo {
bar: ExtendedBar;
}
const extendedFoo = foo.extend(
// ...
) as ExtendedFoo;
The issue with option 1 is that if the original foo module is used anywhere, TypeScript will think it has this added method when it doesn't.
The issue with option 2 is that it's oversimplified and incomplete: in reality the Bar interface is used in numerous places in the type definitions, e.g. as the type of a parameter in some functions, as part of big union types or other more complex types, etc. Accounting for all these would make my extended types both lengthy and brittle, whereas with option 1 all I had to do was change the relevant part at its source.
Of course the real solution would be for the provided types to be rewritten - for example a bunch of them could be made generic, including the base Foo type, and the type params propagated through. But assuming that isn't an option, is there any other way I can get the desired result while avoiding the issues I mentioned?