Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typescript: Wrap function return type of generic class

I am not being able to wrap my head around this issue in a generic way with Typescript, I would appreciate any help!

  • A Factory should deploy a Contract
  • A CustomFactory is a Factory and should deploy a CustomContract (that is a Contract)
  • A MockFactory should be a wrapper of all this logic

Something like this would be the goal (semi pseudocode)

interface MockFactory<F extends Factory> extends F {
    deploy: (...args: Parameters<F.prototype.deploy>) => MockContract<F.prototype.deploy.returnValue>
}

In order to illustrate the issue even better I created a Playground where you can see the errors

like image 524
0xGorilla Avatar asked Apr 06 '26 20:04

0xGorilla


2 Answers

Just solved by using ReturnType and Parameters, as well needed to transform the interface into a type:

interface Contract {}

interface Factory {
    deploy: (...args: any[]) => Contract;
}

class CustomContract implements Contract {}

class CustomFactory implements Factory {
    deploy(x: number, y: number): CustomContract {
        return {};
    }
}

type MockContract<C extends Contract> = Contract & C & {
    mockProperty: number;
}

type MockFactory<F extends Factory> = F & {
    // deploy should have the parameters of the function deploy inside F (in this case CustomFactory)
    // deploy should return a MockContract of the return type of the function deploy inside F (MockContract<CustomContract> in a generic way)
    deploy: (...args: Parameters<F['deploy']>) => MockContract<ReturnType<F['deploy']>>
}

const example: MockFactory<CustomFactory> = {} as any;
example.deploy(1, 2);

Updated Playground

like image 109
0xGorilla Avatar answered Apr 09 '26 10:04

0xGorilla


You could go with (abstract) classes, but interfaces are possible too. In that case, making the factory itself generic seems like the wrong move. Make the contract type generic instead. After a bit of playing around, I was able to puzzle this together:

interface Contract { }

interface Factory<A extends any[], C extends Contract> {
    deploy: (...args: A) => C;
}

// Some helper types
type FactoryArgs<F extends Factory<any, any>> = F extends Factory<infer A, any> ? A : never;
type FactoryContractType<F extends Factory<any, any>> = F extends Factory<any, infer C> ? C : never;

interface FactoryForClass<C extends new (...args: any) => Contract> {
    //deploy: C extends new (...args: infer A) => infer T ? Factory<A, T>['deploy'] : never;
    deploy: Factory<ConstructorParameters<C>, InstanceType<C>>['deploy'];
}

class CustomContract implements Contract {
    constructor(a: number, b: number) { }
}

class CustomFactory implements FactoryForClass<typeof CustomContract> {
    deploy(x: number, y: number): CustomContract {
        return new CustomContract(x, y);
    }
}

type MockContract<C extends Contract> = Contract & C & {
    mockProperty: number;
}

type MockFactory<F extends Factory<any, any>> = F & {
    deploy: (...args: FactoryArgs<F>) => MockContract<FactoryContractType<F>>;
}

const mockFactory: MockFactory<CustomFactory> = {
    deploy(a: number, b: number) {
        const customContract = new CustomContract(a, b);
        const result: CustomContract & MockContract<CustomContract> = customContract as any;
        result.mockProperty = 123;
        return result;
    }
};
like image 31
Kelvin Schoofs Avatar answered Apr 09 '26 09:04

Kelvin Schoofs



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!