Last active
February 7, 2021 16:09
-
-
Save JSuder-xx/ccacaed3306d63f52a41722c24d584af to your computer and use it in GitHub Desktop.
Micro Api for building type predicates in TypeScript.
This file contains hidden or 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
/** Micro Api for building type predicates */ | |
module TypePredicateApi { | |
export type TypePredicate<refined> = (val: any) => val is refined; | |
export type TypeOfTypePredicate<refiner> = refiner extends TypePredicate<infer refined> ? refined : never; | |
export type TypePredicateMap = { [propertyName: string]: TypePredicate<any> } | |
export type ObjectFromTypePredicateMap<map extends { [propertyName: string]: TypePredicate<any> }> = { | |
[propertyName in keyof map]: TypeOfTypePredicate<map[propertyName]>; | |
} | |
/** Create a predicate which tests for an object. */ | |
export const createObjectPredicate = <typePredicateMap extends TypePredicateMap>(typePredicateMap: typePredicateMap) => | |
(obj: any): obj is ObjectFromTypePredicateMap<typePredicateMap> => | |
(obj === null) ? false | |
: (typeof obj !== "object") ? false | |
: Object.keys(obj).every(propertyName => typePredicateMap[propertyName](obj[propertyName])); | |
export const isString: TypePredicate<string> = (val: any): val is string => typeof val === "string"; | |
export const isNumber: TypePredicate<number> = (val: any): val is number => typeof val === "number"; | |
export const isBoolean: TypePredicate<boolean> = (val: any): val is boolean => typeof val === "boolean"; | |
export const isArray: TypePredicate<any[]> = (val: any): val is any[] => !!val && typeof val.push === "function"; | |
export const createIsArray = <Item extends unknown>(pred: TypePredicate<Item>): TypePredicate<Item[]> => | |
(val: any): val is Item[] => | |
isArray(val) && val.every(pred); | |
export const isValue = <value extends unknown>(value: value) => (val: any): val is value => (val === value); | |
export const isNull: TypePredicate<null> = (val: any): val is null => val === null; | |
export const isValue2 = <value1 extends unknown, value2 extends unknown>(value1: value1, value2: value2) => | |
(val: any): val is (value1 | value2) => | |
(val === value1 || val === value2); | |
export const isValue3 = <value1 extends unknown, value2 extends unknown, value3 extends unknown>(value1: value1, value2: value2, value3: value3) => | |
(val: any): val is (value1 | value2 | value3) => | |
(val === value1 || val === value2 || val === value3); | |
export const isValue4 = <value1 extends unknown, value2 extends unknown, value3 extends unknown, value4 extends unknown>(value1: value1, value2: value2, value3: value3, value4: value4) => | |
isValue2(isValue3(value1, value2, value3), value4); | |
export const or = <left, right>(left: TypePredicate<left>, right: TypePredicate<right>) => | |
(val: any): val is (left | right) => | |
left(val) || right(val); | |
export const or3 = <one, two, three>(one: TypePredicate<one>, two: TypePredicate<two>, three: TypePredicate<three>) => | |
(val: any): val is (one | two | three) => | |
one(val) || two(val) || three(val); | |
} | |
module Example { | |
const isGender = TypePredicateApi.isValue3("male" as const, "female" as const, "unknown" as const); | |
const isPerson = TypePredicateApi.createObjectPredicate({ | |
firstName: TypePredicateApi.isString, | |
gender: isGender, | |
lastName: TypePredicateApi.isString, | |
ageInYears: TypePredicateApi.isNumber, | |
isCool: TypePredicateApi.isBoolean | |
}) | |
// Observe the use of TypeOfTypePredicate to get ahold of the computed type. | |
type Person = TypePredicateApi.TypeOfTypePredicate<typeof isPerson>; | |
const bob: Person = { | |
firstName: "Bob", | |
lastName: "Smith", | |
ageInYears: 30, | |
gender: "male", | |
isCool: true | |
} | |
const isCompany = TypePredicateApi.createObjectPredicate({ | |
ceo: isPerson, | |
coo: isPerson, | |
cto: isPerson, | |
workers: TypePredicateApi.createIsArray(isPerson) | |
}) | |
export function doSomething(valueFromOutsideWord: any) { | |
if (isCompany(valueFromOutsideWord)) { | |
console.log(`CEO Age: ${valueFromOutsideWord.ceo.ageInYears}`); | |
console.log(`Company has ${valueFromOutsideWord.workers.length}`); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment