I’m currently refactoring a codebase to support TypeScript’s strict mode. However, I’ve ran into an issue with functions that leverage generics.
In essence, I have a function that takes an object with a blocks
property, this property uses a type that is generic, but has default types.
This is the definition of the object:
export type Context<
S extends VariableSchema = VariableSchema,
R extends VariableSchema = VariableSchema,
> = {
blocks: { [key: string]: Block };
schema: S;
result: R;
};
The Block
type itself is generic as well and looks like this:
export type Block<
ParamMap extends BlockParamSchema = BlockParamSchema,
HandleMap extends { [key: string]: string } = { [key: string]: string },
> = {
label: string;
parameters: ParamMap;
validateParameters: (args: {
parameters: ConvertVariableSchemaToObjectType<ParamMap>;
getVariableDefinition: (name: string) => Variable;
}) => z.AnyZodObject;
handles: HandleMap;
execute: (args: BlockExecutionApi<ParamMap, HandleMap>) => Promisable<void>;
};
I then use it in the following manner:
export const deductionCalculationContext = createContext({
blocks: defaultBlocks,
schema: {
deductionYtd: v.number('Deduction YTD'),
eligibleWages: v.number('Eligible Wages'),
},
result: {
employeeAmount: v.number('Employee Amount'),
companyAmount: v.number('Company Amount'),
},
});
All Blocks are declared using a createBlock
function which infers the generic types of Block
for type safety. Specifically, it ensures that when a developer passes a set of parameters, the validator arguments are typed based on that:
export const conditionBlock = createBlock({
label: 'If',
parameters: {
when: {
label: 'Condition',
type: 'unknown',
required: true,
},
equalTo: {
label: 'Equal to',
type: 'unknown',
required: true,
},
},
handles: {
true: 'True',
false: 'False',
},
execute: async ({ parameters, fireHandle }) => {
parameters.when === parameters.equalTo ? fireHandle('true') : fireHandle('false');
},
});
However even though it’s the same type, less the generic args, TS won’t compile, displaying this error:
I’ve tried the following things:
-
Omitting the
blocks
and redeclaring it without generics works, but I’m afraid of the type safety issues it’ll bring. -
Disabling
strictFunctionTypes
also works, but breaks other type safety features related to functions. -
Making the
blocks
property generic. -
Making the
blocks
property generic, but without a generic type constraint, and then using a mapped type, conditional types, and inference to get the values of the generics. This worked in concept but I can’t apply it since it’d reference the type recursively.
I’m trying to avoid having to explicitly pass the generic type, and I know there are libraries out there that have accomplished this.