Is there a way to maintain a constant literal expression (with const assertion) while still enforcing type checking against a specific type to prevent missing or excess properties?
In simpler terms, how can the type annotation be prevented from overriding the as const
assertion and widening the type?
I have a basic understanding of what's happening and I've asked around on chat, but it seems like there may not be a straightforward solution in the realm of types. Perhaps there's a clever workaround that I'm not aware of.
Here's the scenario:
I need to create a config object that allows me to infer types conditionally based on its values, all while ensuring that the config precisely matches the keys of a specified type.
Consider this minimal example. The State
type defines the key structure, and the config
object must align with those keys for type validation. However, I also need the config
to remain constant so I can access a union type at a unit level, instead of a widened string
type.
type State = Readonly<{
a: number;
b: string;
}>;
const config: Record<keyof State, { value: string }> = {
a: { value: "aaa" },
b: { value: "bbb" }
} as const;
// Desired output should be "aaa" | "bbb"
type ConfigValues = (typeof config)[keyof typeof config]["value"];
One potential solution is as follows:
const config = {
a: { value: "aaa" },
b: { value: "bbb" }
} as const;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const config_check: Record<keyof State, any> = config;
However, this approach only checks for missing properties, not excessive ones :/.
Keep in mind that this illustration is quite simplistic. In reality, the config is more intricate, and the desired inference type relies on conditional logic tied to the values.
Furthermore, encountering this issue repeatedly suggests it's not just an isolated incident.