I have a class like the following:
const p = Symbol();
class Cat {
// private data store
private [p]:{[key:string]: unknown} = {};
constructor() {}
jump(height:number):void {
// ...
}
}
Elsewhere I have a class that has a reference to an instance of a Cat and exposes it as a property. However, for reasons, this property is a function:
class Microchip {
constructor(cat:Cat) {
this._cat = () => cat;
}
get cat():() => Cat {
return this._cat;
}
}
However, it's actually a little more complicated than that. The cat
property isn't just a function, it's a Proxy to a function that exposes members of the underlying Cat instance:
interface CatQuickAccess {
jump(height:number):void;
():Cat;
}
class Microchip {
constructor(cat:Cat) {
this._cat = new Proxy(() => cat, {
get(_, propName) {
return cat[propName];
}
}) as CatQuickAccess;
}
get cat():CatQuickAccess {
return this._cat;
}
}
The CatQuickAccess
interface is burdensome as the Cat class has many, many complex members, all of which must be carefully copied and maintained to ensure consistency with the underlying class.
Generic functions allow the following syntax which is very close to what I'd like to accomplish:
function getCatProp<T extends keyof Cat>(key:T):Cat[T] {
return this._cat[key];
}
I was hoping I could use a similar technique to avoid listing out all of the Cat properties and instead have TypeScript infer the properties directly from the Cat class, but either this is not possible or I haven't yet sussed out the right syntax.
The following generates two very reasonable errors:
An index signature parameter type cannot be a literal type or generic type. Consider using a mapped object type instead.
'key' refers to a value, but is being used as a type here. Did you mean 'typeof key'?
// WRONG
interface CatQuickAccess {
[key:keyof Cat]:Cat[key];
():Cat;
}
I thought about using a merge type as below:
// WRONG
type CatQuickAccess = Cat & {
():Cat;
}
However, this is misleading, as it makes it look like you can pass around the quick access value as an instance of a Cat, when actually it's just a function with some properties on it. It has a very similar shape, but would fail simple type checks like instanceof Cat
at runtime.
I've been experimenting with the spread operator to see if I could get TypeScript to understand what I'm trying to describe, but so far this has not worked at all:
// VERY WRONG
type CatQuickAccess = {
...Cat,
():Cat;
}
Is there a solution here, or does maintaining this very odd construct require the signature duplication?
(I believe I have provided a sufficient MCVE example, but please keep in mind that the actual code is more complex in its implementation and history. If it's possible, I'd like my question answered as I've stated it. Adjusting the structure of the code is not possible for reasons™)