Issue
A strongly-typed id would make it more difficult to pass the wrong id as a parameter to a function. E.g. getThingById(notAThing.id)
is an easy mistake to make if both Thing
and NotAThing
have the same type of id
.
Here’s a first crack it but in the end Id<TModel, TId>
are both number
, so it does not accomplish the goal. How can this be altered so that the final two function calls fail?
type Thing = { id: Id<Thing> };
type NotAThing = { id: Id<NotAThing> };
type Id<TModel, TId = number> = TId;
const thing: Thing = {
id: 123,
};
const notAThing: NotAThing = {
id: 456,
};
function getThing(id: Id<File>) {
return "here's the thing";
}
function getNotAThing(id: Id<NotAThing>) {
return "here's the not a thing";
}
// This works.
getThing(thing.id);
getNotAThing(notAThing.id);
// How to make it not work (fail compilation) if you pass the "wrong type of id"?
getThing(notAThing.id);
getNotAThing(thing.id);
Ready to test in the Playground
Solution
The concept is called "opaque types". It is a type similar to an existing one (like a string or a number) but not interchangeable with it.
There is a great implementation of this described by Drew Colthorp in the article Flavoring: Flexible Nominal Typing for TypeScript which uses TypeScript branding but allows for implicit conversions. It looks like this:
interface Flavoring<FlavorT> {
_type?: FlavorT;
}
export type Flavor<T, FlavorT> = T & Flavoring<FlavorT>;
Which you can then use as (example from the article
type PersonId = Flavor<number, "Person">
type BlogPostId = Flavor<number, "BlogPost">
And now you can assign numbers to both of these but not cross assign values defined as PersonId
and BlogPostId
let p: PersonId = 1;
let b: BlogPostId= 2;
p = b; //error
b = p; //error
For your code, with minimal changes, you can define your Id
type as
type Id<T> = Flavor<number, T>;
And then use it by passing a string:
type Thing = { id: Id<"Thing"> };
type NotAThing = { id: Id<"NotAThing"> };
Which now makes your example behave as you expect: Playground Link
Answered By – VLAZ
Answer Checked By – Katrina (BugsFixing Volunteer)