I am facing a scenario where I am working towards achieving a specific "state":
type State = { foo: number, bar: number, baz?: string };
Initially, I may not have reached the complete State yet but rather align with the structure of Partial<State>
. My objective is to determine when I have successfully attained the desired State
.
To assist me in this process, I developed a utility type named Resolve:
type Resolve<T extends Partial<State>> =
T extends infer U
? U extends State ? State : Partial<State>
: never;
This utility functions well when handling inferred types like:
const implicit1 = { foo: 5 };
const implicit2 = { foo: 5, bar: 10 };
// Resolves correctly to Partial<State>
const implicitResolve1: Resolve<typeof implicit1> = implicit1;
// Resolves correctly to State
const implicitResolve2: Resolve<typeof implicit2> = implicit2;
However, once a type has been defined as a Partial<State>
, it fails to recognize that it could be of type State
:
const explicit1: Partial<State> = { foo: 5 };
const explicit2: Partial<State> = { foo: 5, bar: 10 };
// Resolves correctly to Partial<State>
const explicitResolve1: Resolve<typeof explicit1> = explicit1;
// Unfortunately resolves to Partial<State> despite the intended State type
const explicitResolve2: Resolve<typeof explicit2> = explicit2;
In my overall solution, I already had a type guard ready and I believed it would provide me with the necessary capabilities:
type TypeGuard<T> = (thing: unknown) => thing is T;
const tg: TypeGuard<State> = (thing: unknown): thing is State => {
return typeof thing === "object"
&& typeof (thing as any)?.foo === "number"
&& typeof (thing as any)?.bar === "number";
};
function SuperResolve(thing: unknown, tg: TypeGuard<State>) {
return tg(thing) ? thing as State : thing as Partial<State>
}
// Surprisingly, this also resolves to Partial<State>!
const fnResolve = SuperResolve(explicit2, tg);
At this point, I feel like I've exhausted all my options... there must be a way to identify when a Partial<T>
has transitioned into <T>
.