Skip to content

Instantly share code, notes, and snippets.

@paduc
Forked from safareli/type-guard-composition.ts
Last active January 14, 2022 14:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save paduc/5d1322e3a40ce5415b9477004716df9b to your computer and use it in GitHub Desktop.
Save paduc/5d1322e3a40ce5415b9477004716df9b to your computer and use it in GitHub Desktop.
Composing TypeScript type guards
/**
* Type representing a guard function accepting Input and some other arguments
* while refining type of input as `Output`
*/
export type TypeGuard<Input, Output extends Input> = (value: Input) => value is Output
/**
* Combines multiple TypeGuards using `&&` operator
*/
export function and<I, O extends I, O2 extends O>(
f: TypeGuard<I, O>,
g: TypeGuard<O, O2>
): TypeGuard<I, O2> {
return (value: I): value is O2 => f(value) && g(value)
}
/**
* Combines multiple TypeGuards using `||` operator
*/
export function or<T extends readonly TypeGuard<unknown, unknown>[]>(...typeguards: T) {
return (value: InputType<TypeGuardOfUnion<T>>): value is GuardedType<TypeGuardOfUnion<T>> =>
typeguards.some((typeguard) => typeguard(value))
}
// Take an array of TypeGuards and return a union of output types
// Ex: ExtractOutputTypesOfUnion<[TypeGuard<I, O1>, TypeGuard<I, 02>] = 01 | O2
type ExtractOutputTypesOfUnion<T extends readonly TypeGuard<unknown, unknown>[]> = {
[idx in keyof T]: T[idx] extends TypeGuard<unknown, infer V> ? V : never
}[number]
// Take an array of TypeGuards and return the input type
// it takes the input type of the first (since they are all the same)
type ExtractInputTypeOfUnion<
T extends readonly TypeGuard<unknown, unknown>[]
> = T[0] extends TypeGuard<infer U, any> ? U : never
// Build a full TypeGuard type with the two previous
// enforces the constraint that the output extends the input
type TypeGuardOfUnion<
T extends readonly TypeGuard<unknown, unknown>[],
Input = ExtractInputTypeOfUnion<T>,
Output = ExtractOutputTypesOfUnion<T>
> = Output extends Input ? TypeGuard<Input, Output> : never
// Returns the return type of a TypeGuard<I, O> ("value is O")
type GuardedType<T> = T extends (x: any) => x is infer T ? T : never
// Returns the input type of a TypeGuard (type of first parameter)
type InputType<T extends (...args: any) => any> = Parameters<T>[0]
@paduc
Copy link
Author

paduc commented Jan 14, 2022

I simplified the type guards to only accept a single value argument and made it possible to have an indefinite amount of typeguards passed to or

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment