Reputation: 5364
I'm working on a opensource library that makes Proxies of Rx Observables.
(I'll use Box<A>
instead of Observable<A>
for simplicity)
It takes in a Box<A>
and returns a type { [P in keyof A]: Box<A[P]> }
.
With one nuance: all methods on this new type should also return a Box
of that method result, while keeping it's params types. So:
Box<{ m(a:string):any }>
// becomes
{ m(a:string):Box<any> }
The issue I'm facing is that when I try to proxy A
that has overloaded methods on it — I'm only redefining one single method definition, not the whole overload group.
Simplified issue:
type Proxy<O> = { [P in keyof O]: Wrap<O[P]> }
type Wrap<O> = O extends (...a: infer P) => infer R
? (...args: P) => Proxy<R>
: Proxy<O>
class A {
m(a: number): any;
m(a: string): any;
m(...a:any[]) {}
}
let p = {} as Proxy<A>;
// p.m resolves to a single override
// m(a: string): void;
p.m('1') // > returns Proxy<any>
p.m(1) // > Error: 1 is not string
Try this snippet in TS playground
Is it even possible?
Please, help me with this typing. Thanks!
Notes:
Everything already works fine without typings!
You can check out the library here: rxjs-proxify (types defined in src/proxify.ts
)
Upvotes: 1
Views: 1173
Reputation: 249706
There is a problem with mapping overloaded functions. If you just use O extends (...a: infer P) => infer R
you will actually always get the last overload. There is no way to get the arguments for an arbitrary number of overloads, but you can create a conditional type to get the overloaded arguments for up to a certain number of overloads using the technique outlined here
type Proxy<O> = { [P in keyof O]: OverloadedWrap<O[P]> }
type OverloadedWrap<T> =
T extends
{ (...args: infer A1): infer R1; (...args: infer A2): infer R2; (...args: infer A3): infer R3; (...args: infer A4): infer R4 } ?
{ (...args: A1): Proxy<R1>; (...args: A2): Proxy<R2>; (...args: A3): Proxy<R3>; (...args: A4): Proxy<R4>; }:
T extends
{ (...args: infer A1): infer R1; (...args: infer A2): infer R2; (...args: infer A3): infer R3; } ?
{ (...args: A1): Proxy<R1>; (...args: A2): Proxy<R2>; (...args: A3): Proxy<R3>; }:
T extends
{ (...args: infer A1): infer R1; (...args: infer A2): infer R2; } ?
{ (...args: A1): Proxy<R1>; (...args: A2): Proxy<R2>; }:
T extends
{ (...args: infer A1): infer R1; } ?
{ (...args: A1): Proxy<R1>; }:
Proxy<T>
type Wrap<O> = O extends (...a: infer P) => infer R
? (...args: P) => Proxy<R>
: Proxy<O>
class A {
m(a: boolean): any;
m(a: number): any;
m(a: string): any;
m(...a: any[]) { }
}
let p = {} as Proxy<A>;
p.m('1') // > returns Proxy<any>
p.m(1) // > returns Proxy<any>
p.m
Upvotes: 2