I have a function 'fetchData' that takes a key value object where the key represents a function name and the value is the parameter to pass into that function. The 'fetchData' function returns an object with the returned value from each of those specified functions.
The 'fetchData' function is working well, I'm just struggling with the types within it. I'm trying to make it so that any number of keys can be passed in, but they're all essentially optional, and in doing so the types returned should only be provided for those keys. I'm also trying to provide type safety on the function parameters, to ensure that the types match the requirements of each specified function.
The TypeScript is actually working in that all the validation is applied etc, but there's a few problems with type indexes within the function, so I'm looking for a bit of help on how to deal with those. I hope everything makes sense, I've tried to comment and simplify it as much as possible. Can explain more if it helps..
const getA = async (query: { foop: string }) => ({
shapeOfA: query
});
const getB = async (query: { barp: string }) => ("returnB");
const getC = async (query: { boop: string }) => ("returnC");
const modelFunctions = {
getA,
getB,
getC
};
// the keys for all of our model functions, i.e. "getA", "getB", etc (strings)
type ModelFunctionKeys = keyof typeof modelFunctions;
// type of query props coming into function (e.g. { foop: "hello", ... } on getA)
type Queries = {
[modelFunctionKey in ModelFunctionKeys]?: Parameters<
typeof modelFunctions[modelFunctionKey]
>[0];
};
// the actual functions of our modelFunctions, i.e. getA(), getB()
type ModelFunctions = typeof modelFunctions;
export const fetchData = async <T extends Queries>(
queries: { [K in keyof T]: K extends keyof Queries ? T[K] : never } // Ensure that only valid model function keys are passed in
) => {
// pull the keys from our queries param as an array
const keys = Object.keys(queries) as Array<
keyof ModelFunctions
>;
// pull the promises from our queries param
const promises = keys.map((key) => modelFunctions[key](queries[key]));
// resolve all the promises (make the API calls)
const resolved = await Promise.allSettled(promises);
// build an object for the return data with our keys as labels
const toReturn: { [key: string]: unknown } = {};
// iterate over the returned data and assign it to the correct key
keys.forEach((key, index) => {
toReturn[key] = resolved[index];
});
// return the data in the correct type
return toReturn as {
[QueryKey in keyof T]: Awaited<ReturnType<ModelFunctions[QueryKey]>>;
};
};
const data = await fetchData({
getA: { foop: "hello" },
getC: { boop: "bar" },
});
/** Working example. Should return the following type (only return getA and getC, not getB)
const data: {
getA: {
shapeOfA: {
foop: string;
};
};
getC: string;
}
.. and the following object:
const data: {
getA: {
shapeOfA: {
foop: "hello";
}
},
getC: "returnC";
}
*/
const data2 = await fetchData({
getD: { msg: "hello" }, // should error because getD doesn't exist
getC: { boop: "bar" },
});
const data3 = await fetchData({
getA: "wrongtype", // should error because value is in the wrong format
getC: { boop: "bar" },
});
A working example is at Playground Link which shows the errors I'm experiencing
Solution at Typescript Playground
CodePudding user response:
By being a bit more strict with types, I have only one error left:
type Queries<QueryKey extends ModelFunctionKeys> = {
[modelFunctionKey in QueryKey]: Parameters<
typeof modelFunctions[modelFunctionKey]
>[0];
};
// the actual functions of our modelFunctions, i.e. getA(), getB()
type ModelFunctions = typeof modelFunctions;
export const fetchData = async <QueryKey extends ModelFunctionKeys>(
queries: Queries<QueryKey> // Ensure that only valid model function keys are passed in
) => {
// pull the keys from our queries param as an array
const keys = Object.keys(queries) as QueryKey[];
// pull the promises from our queries param
const promises = keys.map((key) => modelFunctions[key](queries[key] as any));
// resolve all the promises (make the API calls)
const resolved = await Promise.allSettled(promises);
// build an object for the return data with our keys as labels
const toReturn: { [key: string]: unknown } = {};
// iterate over the returned data and assign it to the correct key
keys.forEach((key, index) => {
toReturn[key] = resolved[index];
});
// return the data in the correct type
return toReturn as {
[k in QueryKey]: Awaited<ReturnType<ModelFunctions[k]>>;
};
};