The merging problem
There is no way to declare a member of a class with a different type, declaration merging can only add members not change the types of existing members.
Something we can do is use an intersection type. If two members of an intersection type have the same name and different types the member on the intersection will be an intersection of the two original types.
class _MyClass {
public constructor (t: string) {
}
public myMethod(param1:string) {
}
static staticF(){}
}
type ReplaceInstanceType<T extends new(...a: any[])=> any, TNew> = {
new (... a: (T extends new (...p: infer P) => unknown ? P: [])) : TNew
} & Pick<T, keyof T>
export type MyClass = _MyClass & {
myMethod: {
myMember(): void
}
}
export const MyClass: ReplaceInstanceType<typeof _MyClass, MyClass> = _MyClass as any;
let a = new MyClass("") // can new up
a.myMethod("") // can call myMethod
a.myMethod.myMember(); // can call myMember
MyClass.staticF
The decorator problem
The second part of your problem is that a decorator can't mutate the type of the member it is applied to (this is by design). You could use a HOC to create the class instead, and pass in a list of member names (compile checked using keyof
) to have the extra members added :
type ReplaceInstanceType<T extends new(...a: any[])=> any, TNew> = {
new (... a: (T extends new (...p: infer P) => unknown ? P: [])) : TNew
} & Pick<T, keyof T>
function addMemebers<T extends new(...a: any[])=> any, K extends keyof InstanceType<T>>(members: K[], cls: T) : ReplaceInstanceType<T, InstanceType<T> & Record<K, {myMember(): void}>>{
return null as any; // replace with implementation
}
export const MyClass = addMemebers(["myMethod"], class {
public constructor (t: string) {
}
public myMethod(param1:string) {
}
static staticF(){}
})
let a = new MyClass("") // can new up
a.myMethod("") // can call myMethod
a.myMethod.myMember(); // can call myMember
MyClass.staticF
myMethod
should have both callable signature andmyMember
method:myMethod: { (param1: string): void, myMember: () => void }
. There still will be an error (Duplicate identifier 'myMethod'.) which you can ignore with// @ts-ignore
– Dude