Unfortunately the way that TypeScript loads modules at compile time is significantly different to the way that Javascript loads modules at runtime (especially when using frameworks such as React or Jest that have their own Javascript module loading system). This difference in module loading system means that even though your code compiles, it may fail during runtime (and whether it fails heavily depends on the particular framework you are using and module import/exports, leading to very fragile code). As such, it is very hard to reliably use extension methods in TypeScript, so would recommend alternatives (e.g. helper method, custom class, etc).
However, if you do really want to get Extension methods working, read on.
Fundamentally this error means that TypeScript has correctly located the interface extension during compile time:
declare interface Array<T> {
flatten<T>(): T;
}
but Javascript has not executed the call to extend the prototype at runtime:
Array.prototype.flatten = function<T> () : T {
return this.reduce(function (flat: Array<T>, toFlatten: Array<T>) {
return flat.concat(Array.isArray(toFlatten) ? toFlatten.flatten() : toFlatten )
}, []);
}
Unfortunately the currently accepted answer and other related answers on StackOverflow only mention about putting the definitions in a "top-level file", which doesn't give clear enough instructions to handle all scenarios (see How to create an extension method in TypeScript for 'Date' data type,
Typescript extension method compiling but not working at run time and Typescript Extend String interface Runtime Error).
To debug inconsistent behaviour, you can put a breakpoint on the prototype extension call to see when/if it is invoked. You can also examine the proto
field of the extended class to check if the extension method has been created at runtime yet. Remember that due to Tree Shaking, even adding redundant import lines will not be sufficient to force JavaScript to evaluate this line.
To ensure that the prototype extension call occurs, find all your application entry points (e.g. Jest tests, web framework global, etc.) and ensure that the prototype extension call is called from each entry point (a crude solution is to have a "setup" method" that is called at the start of each entry point). Note: remember that Jest and other test libraries can mock modules, so you want to ensure that you never mock the module that calls the prototype extension. Practically this means that you should have a separate file for extensions and disable Jest automocking (or equivalent).