I need help with these type definitions. TS is not showing me a warning but I believe I'm violating the type.
type ElementType = "ShortText" | "Status" | "Number";
type BoardType = {
id: string;
name: string;
columns: Array<ColumnType>;
};
type Column = {
id: string;
name: string;
element: ElementType;
};
type StatusType = Column & {
options: { option: string; color: string }[];
};
type ColumnType = Column | StatusType;
type ColumnValue<T extends ElementType> = T extends "ShortText"
? string
: T extends "Status"
? string
: T extends "Number"
? number
: never;
type PulseType<Columns extends ColumnType[]> = {
id: string;
board: string;
data: {
[K in Columns[number]["id"]]?: K extends Columns[number]["id"]
? ColumnValue<Extract<Columns[number], { id: K }>["element"]>
: never;
};
};
const board: BoardType = {
id: "board-1",
name: "Sample Board",
columns: [
{ id: "Name", name: "Full Name", element: "ShortText" },
{
id: "Status",
name: "To DOs",
element: "Status",
options: [
{ option: "To Do", color: "grey" },
{ option: "In Progress", color: "blue" },
{ option: "Done", color: "green" },
],
},
{ id: "Age", name: "Age", element: "Number" },
],
};
const pulses: PulseType<typeof board.columns> = {
id: "1",
board: "board-1",
data: {
Name: "John Doe",
Status: "Done",
Age: 25,
// Uncommented 'ehh', which is not a valid column ID
ehh: "error", // Error: 'ehh' is not assignable to 'never'
},
};
The goal is to have a board. In that board there's an array called columns. Based on the columns it should determine the type of a pulse. It's kind like a list and items.
In BoardType it takes an array of ColumnType, which is the union Column | Status | Relationship. The reason for this is basically, instead of keeping all the settings in the pulse, any common info is kept in the board. For example the status has options; those options are the same for each pulse in the board, so the options are kept in the column which is extended by Status.
The ElementType is just all the possible values of board.columns[].element.
ColumnValue is a conditional type that returns the type of each element
The issue I'm running into is, when I pass PulseType the argument, it doesn't enforce that the data object in it only has the keys of the ids in the board's column array.
As you see, the ehh property should cause TypeScript to give a warning, but it's not.
First of all, it won't be possible with the current board, since the compiler needs to know the exact shape of the
board, which is impossible just by typing it asBoardType. Instead, we will need to use const assertion that will prevent the compiler from widening the types to their primitives. With const assertion there will no longer be type checking, which can be fixed by using the satisfies operator. Since const assertion will turn every array to their read-only versions, we will need to update all array/tuple properties to become read-only:In the mapped type instead of mapping through
Columns[number]["id"]]just map through the whole elements (Columns[number]) and you can change the key to the value ofidproperty by using the key remapping. There is also no point in checking ifK extends XwhereXis what you are mapping. Since not all columns haveelementproperty, we will check whether the current item has this property and if it does then we will pass it toColumnValue, otherwise the value will benever:Testing:
playground