-
-
Save renoirb/d30e7edbe32d9c6b9c2006b13db7a016 to your computer and use it in GitHub Desktop.
TypeScript type Discriminator factory
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export enum EmployeeRole { | |
DEVELOPER = 100, | |
PRODUCT_MANAGER = 200, | |
PRODUCT_DESIGNER = 300, | |
} | |
/** | |
* This is equivalent to: | |
* | |
* type EmployeeRoleType = 'SOFTWARE_DEVELOPER' | 'SOFTWARE_DEVELOPER_COOP'; | |
*/ | |
export type EmployeeRoleKeys = keyof typeof EmployeeRole; | |
/** | |
* Should we want to make sure we have a label for EVERY EmployeeRole | |
* | |
* Bookmarks: | |
* - https://stackoverflow.com/a/51832623 | |
*/ | |
export type EmployeeRoleMustHave<T> = { | |
[K in EmployeeRoleKeys]: Extract<K, EmployeeRoleKeys> extends never ? never : T; | |
}; | |
/** | |
* Translation label hash-map for Employee Roles. | |
* So we can display text instead of a number. | |
*/ | |
const ROLES: EmployeeRoleMustHave<string> = { | |
DEVELOPER: 'Software Developer', | |
PRODUCT_MANAGER: 'Product Manager', | |
PRODUCT_DESIGNER: 'Product Designer', | |
}; | |
/** | |
* Build up all possible role labels. | |
* This is here to help be re-used elsewhere, from both Express and Angular. | |
* That's also why we're enforcing above the ROLES map to make sure we don't miss. | |
*/ | |
const ownSubset = Object.entries(EmployeeRole).filter(i => 1 in i && typeof i[1] === 'number') as [string, number][]; | |
const rolesLabels = ownSubset.map(i => ([i[1], ROLES[i[0]]])) as [number, string][]; | |
// Return only a copy when called. Just to be sure we do not mutate. | |
export const EmployeeRolesLabelMap = (() => new Map<number, string>(rolesLabels))(); | |
export interface Employee { | |
role: EmployeeRole; | |
} | |
/** | |
* Runtime Typing utility. | |
* | |
* Take an HashMap object, extract keys, | |
* and possibly make it a type | |
*/ | |
export const convertRecordToDiscriminator = <T>( | |
hashMap: T, | |
): ReadonlyArray<keyof T> => { | |
const k = Object.keys(hashMap) as (keyof T)[]; | |
return Object.freeze(k); | |
}; |
There has been other attempts but were getting bigger and bigger
export enum UserType {
fooName = 'fooName',
}
export const USER_TYPE_KEYS = Object.freeze<keyof typeof UserType>(
Object.keys(UserType)
.map<keyof typeof UserType>(key => UserType[key])
.filter(value => typeof value === 'string'),
)
export const USER_TYPE_ITEM_KEYS = Object.freeze<keyof UserCredential>(
Object.keys(UserType[USER_TYPE_KEYS[0]])
.map<keyof UserCredential>(key => UserType[USER_TYPE_KEYS[0]][key])
.filter(value => typeof value === 'string'),
)
export const assertsIsUserCredientialMap: (maybe: unknown) => asserts maybe is UserCredientialMap = maybe => {
try {
const maybeUserTypeKeys = Object.keys(maybe)
assert.equal(maybeUserTypeKeys.length > 0, true)
// Has the same keys
assert.equal(maybeUserTypeKeys, USER_TYPE_KEYS)
// Exactly the same count of keys
assert.equal(maybeUserTypeKeys.length, USER_TYPE_KEYS.length)
for (const [, userTypeItem] of Object.entries(maybe)) {
const userTypeItemKeys = Object.keys(userTypeItem)
assert.equal(userTypeItemKeys, USER_TYPE_ITEM_KEYS)
assert.equal(userTypeItemKeys.length, USER_TYPE_ITEM_KEYS.length)
}
} catch (e) {
throw new TypeError(`Is not an UserCredentialMap ${e}`)
}
}
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This Gist is one more use-case expanded in Renoir's references to books and chapters