I have a function convert, its parameter is an object, the value can be function or an object contains three properties, the type looks like this.
type Input = {
[K: string]: () => any | { pending: () => any, fulfilled: () => any, rejected: {} => any }
}
I hope can return an object, that the keys will generate by the values, for example:
const result = convert({
sync() {},
async: {
pending() {},
fulfilled() {},
rejected() {}
}
});
// the keys of result
result.sync();
result.asyncPending();
result.asyncFulfilled();
result.asyncRejected();
currently, I know how to generate result by the keys and the string literals
type Result<Input> = {
[key in keyof Input as `${string & key}Fulfilled`]: any
}
but can't meet my requirement, I google for a long time, but can't found the solution.
My proposed solution would look like this:
type Input = {
[key: string]: { pending: () => any, fulfilled: () => any, rejected: () => any } | (() => any)
}
type UnionToIntersection<U> =
(U extends any ? (k: U)=>void : never) extends ((k: infer I)=>void) ? I : never
type Result<Input> = {
[K in keyof Input as Input[K] extends () => any ? K : never]: Input[K]
} & UnionToIntersection<{
[K in keyof Input]: {
[K2 in keyof Input[K] as `${K & string}${Capitalize<K2 & string>}`]: Input[K][K2]
}
}[keyof Input]> extends infer O ? { [K in keyof O]: O[K] } : never
function convert<T extends Input>(arg: T): Result<T> {
return {} as any
}
For each property K of Input where the type is an object we map over the keys in Input[K]. For each key K2 inside Input[K], we rename the key to ${K & string}${Capitalize<K2 & string>} and return the type Input[K][K2]. The resulting mapped object is converted to a union by indexing it with keyof Input.
This union can be converted to an intersection with UnionToIntersection to get all nested keys a level higher.
Now result has the correct type:
const result = convert({
sync() {},
async: {
pending() {},
fulfilled() {},
rejected() {}
}
});
// const result: {
// sync: () => void;
// asyncPending: () => void;
// asyncFulfilled: () => void;
// asyncRejected: () => void;
// }
result.sync();
result.asyncPending();
result.asyncFulfilled();
result.asyncRejected();