I\'m experimenting with TypeScript, and in the process of creating a class with an ID field that should be an integer, I have gotten a
In TypeScript you can approximate what is sometimes called an opaque type using a marker.
// Helper for generating Opaque types.
type Opaque = T & { __opaque__: K };
// 2 opaque types created with the helper
type Int = Opaque;
type ID = Opaque;
// using our types to differentiate our properties even at runtime
// they are still just numbers
class Foo {
someId: ID;
someInt: Int;
}
let foo = new Foo();
// compiler won't let you do this due to or markers
foo.someId = 2;
foo.someInt = 1;
// when assigning, you have to cast to the specific type
// NOTE: This is not completely type safe as you can trick the compiler
// with something like foo.someId = 1.45 as ID and it won't complain.
foo.someId = 2 as ID;
foo.someInt = 1 as Int;
// you can still consume as numbers
let sum: number = foo.someId + foo.someInt;
Doing this allow you to be more explicit in your code as to what types your properties expect, and the compiler won't allow you to assign a primitive value without a cast. This doesn't produce any additional .js output, and you can still consume and use the values as whatever types they are based on. In this example I'm using numbers, but you can use on strings and other types as well.
You can still trick the compiler into accepting something that isn't an Int or an Id in this example, but it should jump out if you were trying to assign 1.45 as Int or something like that. You also have the option of creating helper functions that you use to create your values to provide runtime validation.
There's a number of different ways you can create "marked" types. Here's a good article: https://michalzalecki.com/nominal-typing-in-typescript/