Reputation: 16368
Consider the following class which is declared in a 3rd party library. I cannot modify this file.
declare class Foo<T> {
methodA(foo: string): this
methodB(): this;
methodD(): this;
methodD(type: string): this;
}
The above class is a fluent api which can calls itself. The requirement is that methodB
should never be callable. Therefore I created the following type which will be used over Foo<T>
type FooWithoutB<T> = Omit<Foo<T>, 'methodB'>
But this still allows me to do the following:
let typeWithoutB!: WithoutB<any>;
typeWithoutB.methodA('hello').methodB() // should not be possible
So I thought, ok, let's override the return type, thus I made this type:
type Override<T> = {
[key in keyof T]: T[key] extends (...param: any[]) => T ? (...params: Parameters<T[key]>) => WithoutB<T> : T[key]
}
The above almost works but breaks Parameter types:
foo.methodD() // expected one argument but got 0
foo.methodD('ok') // this works, but above should also work
Edit
Found out that type F = Parameters<Foo<any>['methodD']>
returns [type: string]
. Is there an option to resolve both parameter types?
Upvotes: 0
Views: 132
Reputation: 9475
I don't think there's an easy answer here. One possibility is to fall back on classic OOP and introduce a facade. So we'd wrap an instance of the existing class in a new class, and forward calls where appropriate. Something like the code below. Note that this assumes that our MethodA and MethodDs return 'this'.
class FooFacade<T>
{
private _foo: Foo<T> = new Foo<T>();
methodA(foo: string): this { this._foo.methodA(foo); return this; }
methodD(): this;
methodD(type: string): this;
methodD(type?: string): this {
if (type !== undefined) {
this._foo.methodD(type);
} else {
this._foo.methodD();
}
return this;
}
}
The obvious drawback of this is it's a bit clunky and depending on how complex Foo actually is it could be a lot of work. An advantage is it gives you complete control over how the third-party software is called.
Upvotes: 2