Typescript Compiler Does Not Know About ES6 Proxy Trap on Class

threeve picture threeve · Aug 15, 2018 · Viewed 7.9k times · Source

I have an abstract class:

abstract class Foo {
    abstract bar(): string;
}

I have some classes that extend Foo:

class Foo1 extends Foo {
    bar(): string { return 'foo1'; }
}

class Foo2 extends Foo {
    bar(): string { return 'foo2'; }
}

I have another class that I want to proxy all methods of Foo to a Foo. This actually works fine, if I define all methods of Foo on this class. But I would rather not do that. I would prefer to have the methods of Foo defined on Foo and the compiler to know that FooProxy also implements those methods without actually having to implement them. Is this possible? The Proxy class looks something like this:

class FooProxy {
    public foo: Foo;

    constructor(foo: Foo) {
        this.foo = foo;
        let handler = {
            get: function(target: FooProxy, prop: string, receiver: any) {
                if(Foo.prototype[prop] !== null) {
                    return target.foo[prop];
                }

                return Reflect.get(target, prop, receiver);
            }
        }
        return new Proxy(this, handler);
    }
}

Example:

let f = new Foo1();
let fp = new FooProxy(f);
fp.bar();

Output:

error TS2339: Property 'bar' does not exist on type 'FooProxy'.

This program actually runs in the playground, but tsc does not emit anything. I just need to trick the compiler some how...

Answer

Titian Cernicova-Dragomir picture Titian Cernicova-Dragomir · Aug 15, 2018

I don't think in this case a class is the best approach you can just use a function to create the proxy and all will work as expected:

function createFooProxy(foo:Foo) : Foo { // Proxy<Foo> is compatible with Foo
    let handler = {
        get: function(target: Foo, prop: keyof Foo, receiver: any) {
            if(Foo.prototype[prop] !== null) {
                return foo[prop];
            }

            return Reflect.get(target, prop, receiver);
        }
    }
    return new Proxy(foo, handler);
}

If you are set on using the class approach, you can fake a base class:

function fakeBaseClass<T>() : new() => Pick<T, keyof T>{ // we use a pick to remove the abstract modifier
    return class {} as any
}

class FooProxy extends fakeBaseClass<Foo>(){
    private foo: Foo; // I would make this private as it is not really accessible on what the constructor of FooProxy returns (maybe remove it as I see no use for it)

    constructor(foo: Foo) {
        super();
        this.foo = foo;
        let handler = {
            get: function(target: FooProxy, prop: keyof Foo, receiver: any) {
                if(Foo.prototype[prop] !== null) {
                    return target.foo[prop];
                }

                return Reflect.get(target, prop, receiver);
            }
        }
        return new Proxy(this, handler);
    }
}