Typescript Compiler Does Not Know About ES6 Proxy Trap on Class

As an addition to the accepted answer I want to share a solution that can be used for more simple cases where you simply want to be able to proxy to any method or property (either a trap in the handler or something that actually exists on the actual target). The solution is based on using an "index signature", read more on that topic here: https://www.typescriptlang.org/docs/handbook/interfaces.html

I hope it will be useful for others ending up at this great question:

const handler = {
  get: (target: object, property: string, receiver: any) => {
    // define custom traps here:
    if (property === "trap") {
      // ... do something ...
    }

    // optional test whether property exists using the Reflect class:
    if (!Reflect.has(target, property)) {
      throw Error(`Property ${property} does not exist`);
    }
    // proxy to the target using the Reflect class:
    return Reflect.get(target, property);
  },
};

I will show two solutions:

1. create a factory and an interface:

// Any property is allowed in this interface using the following index signature:
interface FooInterface {
  [key: string]: any;
}

// From the factory we return the FooInterface 
const proxyFactory = (target: Foo): FooInterface => {
  return new Proxy(target, handler);
};

2. create a class with an index signature and return the proxy directly from the class constructor

class FooProxy {
  [key: string]: any;
  constructor(target: Foo) {
    return new Proxy(target, handler);
  }
}

Can be used like this where all possible methods and properties you want to build a trap for inside the handler will be recognized:

const fooProxy = proxyFactory(foo);

or

const fooProxy = new FooProxy(foo);

No more messages complaining:

Property '...property name...' does not exist on type 'Proxy'.

So anything can be called and trapped now:

const returnedFromMethod = fooProxy.anyMethod();
const property = fooProxy.anyProperty;

You can of course refine your interface, the example is simply to demonstrate a solution for all methods and properties.


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);
    }
}