If you want to enforce a specific type in TypeScript, you can utilize a type constructor along with what is known as a "Phantom Type" (learn more about this concept in this interesting article). This technique ensures that a type cannot be directly assigned to a value.
For instance, consider the StringOfLength<Min,Max>
type created using these methods:
type StringOfLength<Min, Max> = string & {
min: Min;
max: Max;
StringOfLength: unique symbol // acting as the phantom type
};
// The following function acts as a type guard and confirms that a given string meets the criteria of being of type StringOfLength<Min,Max>
const isStringOfLength = <Min extends number, Max extends number>(
str: string,
min: Min,
max: Max
): str is StringOfLength<Min, Max> => str.length >= min && str.length <= max;
// Define the type constructor function
export const stringOfLength = <Min extends number, Max extends number>(
input: unknown,
min: Min,
max: Max
): StringOfLength<Min, Max> => {
if (typeof input !== "string") {
throw new Error("invalid input");
}
if (!isStringOfLength(input, min, max)) {
throw new Error("input is not between specified min and max");
}
return input; // the type of input now becomes StringOfLength<Min,Max>
};
// Utilize the type constructor function
const myString = stringOfLength('hello', 1, 10) // myString will have type StringOfLength<1,10>
// The type constructor function fails when the input is invalid
stringOfLength('a', 5, 10) // Error: input is not between specified min and max
// Manually assigning StringOfLength is prevented by the use of the phantom type:
const a: StringOfLength<0, 10> = 'hello' // Type '"hello"' is not assignable to type { StringOfLength: unique symbol }
It's important to note that there are limitations to this approach - for example, it does not prevent the creation of an invalid type like StringOfLength<-1, -300>
. However, runtime checks can be added within the stringOfLength
constructor function to ensure that the min
and max
values passed are valid.
Note: In Typescript, this technique has become more commonly referred to as "branded types."