I have an object composed of Records mapping string keys to known types.
type Material = string;
type Mesh = { material: string };
interface Library {
materials: Record<string, Material>;
meshes: Record<string, Mesh>;
}
Some of those known types may need to reference a value from another record: for example a Mesh
has a material
and the value for that property must be a key of the materials
record.
This is how I turned that constraint into types:
type Material = string;
type Mesh<MaterialId> = {
material: MaterialId;
}
interface Library<
MaterialId extends string
> {
materials: Record<MaterialId, Material>;
meshes: Record<string, Mesh<MaterialId>>;
}
That works, and trying to set a material
to a string that doesn't exist on the materials
record will cause an error:
function loadLibrary<T extends string>(lib: Library<T>) {}
// Not valid
loadLibrary({
materials: {
plastic: "./plastic.png",
metal: "./metal.png"
},
meshes: {
ball: { material: "plastic" },
pipe: { material: "metal" },
chair: { material: "wood" }
}
});
However, the error I get here is that the property "wood"
is missing on the materials
record. The error I want is that "wood"
is not a valid value for material
because it doesn't exist on the record.
The reason I want this is that if I try to reference a value that doesn't exist, I want that reference to be marked as an error so I know to either add that type to the relevant record or use an existing one. It's not useful to have material
accept any string and trigger an error on the record, instead of the other way around.
loadLibrary({
materials: { // << There's nothing wrong here, `materials` should be what defines the valid values for this type.
plastic: "./plastic.png",
metal: "./metal.png"
},
meshes: {
ball: { material: "plastic" },
pipe: { material: "metal" },
chair: { material: "wood" } // << The error should be "wood", it's not a key of `materials`.
}
});
Is this possible? Or can I not control which of the references to that generic type control the valid values for it?
Here's a playground.