Let’s say I want to make a set of logic rules stored in an object :
const rule1 : Rule = {
">" : [3,2]
}; // Which represents : 3>2
const rule2 : Rule = {
"and" : [
{ ">" : [3,1] },
{ "<" : [1,3] }
]
}; // Which represents : (3>1) && (1<3)
I have written my types as such :
type Operand = number | string | Rule
type Operator =
"var" |
"===" |
"!==" |
"<=" |
"<" |
">=" |
">" |
"or" |
"and" ;
interface Rule extends Record<Operator, [Operand, Operand]> { }
But I get the following error Type '{ ">": [number, number]; }' is missing the following properties from type 'Record<Operator, [Operand, Operand]>': var, "===", "!==", "<=", and 4 more.
What do I do wrong ?
>Solution :
interface Rule extends Record<Operator, [Operand, Operand]> { } will name all the operators required on any given rule.
You can extend Partial<Record<Operator, [Operand, Operand]>> and make them all optional. This would mean that a rule can specify multiple operators, which might not be what you want:
interface Rule extends Partial<Record<Operator, [Operand, Operand]>> { }
const rule1 : Rule = {
">" : [3, 2],
"<" : [3, 2]
};
You could also not use an interface, but instead generate a union where each object type has one operator required:
type Rule<T extends Operator = Operator> = T extends T ? Record<T, [Operand, Operand]> & Partial<Record<Exclude<Operator, T>, undefined>>: never;
With this second solution Rule basically generates a union of types using the distributive property of conditional types. Basically we get:
Rule = Record<"var", [Operand, Operand]> | Record<"===", [Operand, Operand]> | Record<"!==", [Operand, Operand]> | ... 5 more ... | Record<...>
This means that each constituent of the union has exactly one required property. To also ensure other don’t have other properties (unions can allow members from other constituents), we also add to each object type all the other properties but optional and with type undefined (the & Partial<Record<Exclude<Operator, T>, undefined>> part)