Skip to content

Instantly share code, notes, and snippets.

@serjKim
Last active May 1, 2021 20:00
Show Gist options
  • Save serjKim/1adbe508578dc7cf2d03c393c1d26f41 to your computer and use it in GitHub Desktop.
Save serjKim/1adbe508578dc7cf2d03c393c1d26f41 to your computer and use it in GitHub Desktop.
Typescript: nominal types
// Nominal types (branding)
module BrandedTypes {
const BRAND = Symbol();
type Branded<T, TBrand> = T & { readonly [BRAND]: TBrand };
export type UserId = Branded<number, 'userId'>;
export type RoleId = Branded<number, 'roleId'>;
export function toUserId(n: number): UserId {
// Validation... and other stuff.
return n as UserId;
}
export function toRoleId(n: number): RoleId {
return n as RoleId;
}
export type Cat = Branded<{ name: string }, 'cat'>;
export type Dog = Branded<{ name: string }, 'dog'>;
export function toCat(obj: { name: string }): Cat {
return obj as Cat;
}
export function toDog(obj: { name: string }): Dog {
return obj as Dog;
}
}
const userId = BrandedTypes.toUserId(1);
const roleId = BrandedTypes.toRoleId(2);
function getUser(userId: BrandedTypes.UserId) {
}
getUser(userId);
getUser(roleId); // Argument of type 'RoleId' is not assignable to parameter of type 'UserId'.
getUser(1); // Argument of type 'number' is not assignable to parameter of type 'UserId'
function f(cat: BrandedTypes.Cat) {
}
const dog = BrandedTypes.toDog({ name: 'cat' });
const cat = BrandedTypes.toCat({ name: 'dog' });
f(cat);
f(dog); // Argument of type 'Dog' is not assignable to parameter of type 'Cat'.
f({ name:'other' }) // Argument of type '{ name: string; }' is not assignable to parameter of type 'Cat'.
module ResultError {
const ERROR_KEY = Symbol();
export type Error<T> = {
readonly error: T;
readonly [ERROR_KEY]: true
};
export function error<T>(err: T): Error<T> {
return Object.freeze({ error: err, [ERROR_KEY]: true });
}
export function isError<TResult, TError>(obj: TResult | Error<TError> | null | undefined): obj is Error<TError> {
return !!(obj as Error<TError>)?.[ERROR_KEY];
}
}
module BrandedTypes {
const BRAND = Symbol();
type Branded<T, TBrand> = T & { readonly [BRAND]: TBrand };
export type UserId = Branded<number, 'userId'>;
export type RoleId = Branded<number, 'roleId'>;
export function toUserId(n: number | undefined | null): UserId | ResultError.Error<string> {
if (n == null || n <= 0) {
return ResultError.error(`Couldn't parse user id.`);
}
return n as UserId;
}
}
const userId = BrandedTypes.toUserId(null);
function getUser(userId: BrandedTypes.UserId) {
}
if (!ResultError.isError(userId)) {
getUser(userId);
} else {
console.log(userId.error);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment