Branded Types
This principal works perfectly together with a parser / validator. A perfect
example would be zod which uses Branded
Types to make objects as validated.
Lets assume we want to create a Password type:
type Password = string;
And we have a function which creates a user:
function createUser(username: string, password: Password): void {
// creation of a user
// ...
}
TypeScript would allow us to call the function as follows:
createUser('username', 'some-password');
This is because Password is just a type alias for string. However, we rather
would like to pass Passwords to the createUser function which have been
validated and meet our password requirements. To achieve this, we can use
Branded Types.
To brand our Password type, we simply intersect it with an object:
// '__brand' can be named to your liking.
// Some also name it '__type'.
type Password = string & {__brand: 'Password'};
Now we have a type which we normally wouldn't be able to create since we cant
have a string which at the same time is an object.
Note that this type doesn't resolve to never which is important. We, without a
doubt, can't create never types. However we can tell TypeScript that we know
more about a specific variable type than itself.
Let's create another function which takes a password and returns whether its valid or not:
function validatePassword(password: string): password is Password {
if (password.length < 8) return false;
// ... some other checks
return true;
}
We now can use this function to tell TypeScript: "Hey TypeScript. If this
function returns true treat the provided parameter password as a Password
rather than a string".
These type of functions are also called
Type Guards
in TypeScript.
Now that we have your validate function in place we can call createUser:
const password = '12345678';
if (validatePassword(password)) {
createUser('username', password);
}
If you prefer not to nest your code or rather use guards we can create a
function called assertValidPassword:
function assertValidPassword(password: string): asserts password is Password {
if (password.length < 8) {
throw new Error('Password needs to be at least 8 characters long.');
}
}
This function must throw an Error and doesn't return a boolean like the
validatePassword function. The assertValidPassword function can now be used
once and afterwards TypeScript treats the provided parameter as a Password:
const password = '1234';
assertValidPassword(password);
createUser('username', password);