Yes - this can be done. You need to use recursive array iteration within your generics to iterate through the array of ReturnValues to build up the parameters for the callback.
Let me know if there's something you want further explanation on as there's a lot in here!
function returnValue<K extends string, T>(key: K, type: T): [K, T] {
return [key, type];
}
type ReturnValue = ReturnType<typeof returnValue>;
type GenerateCallbackParams<I extends readonly ReturnValue[], P = {}> =
// Base case where we have one item left in I
I extends readonly [infer R]
? R extends [infer K, infer T]
? K extends string
// Merge params with K: T
? P & { [key in K]: T }
: P
: never
// When multiple array elements in I
: I extends readonly [infer R, ...(infer Tail)]
? R extends [infer K, infer T]
? K extends string
? Tail extends ReturnValue[]
? GenerateCallbackParams<Tail, P & { [key in K]: T }>
: never
: never
: never
: never;
function arbitraryFunction<I extends readonly ReturnValue[]>(inputParams: I, callback: (params: GenerateCallbackParams<I>) => GenerateCallbackParams<I>) {
}
// Has to be declared as const here otherwise TS widens the type inside the array
const params = [
returnValue("key1", "a"),
returnValue("key2", 1),
returnValue("key3", ()=>{})
] as const;
arbitraryFunction(
// Dynamically generated input
params,
// Callback, parameters are typed against input
({ key1, key2, key3}) => {
typeof key1; // string
typeof key2; // number
typeof key3; // () => void
return { key1, key2, key3 };
}
)
Link to playground