UNPKG

narrow-minded

Version:

Easy typeof validations with sophisticated TypeScript inference.

118 lines (108 loc) 3.25 kB
import { Narrower, UnNarrow } from './schema' import { narrow } from './narrow' /** * Creates a function from a narrower schema that can be reused to narrow objects. * This simple closure can be used when a whole Guard instance would be too much. * * @example * import { satisfier } from 'narrow-minded' * const satisfies = satisfier(['string', 'number']) * satisfies(['horse', 42]) // => true */ export const satisfier = <N extends Narrower>(n: N) => (u: unknown): u is UnNarrow<N> => narrow(n, u) export type NarrowingFunction<P> = (u: unknown) => u is P export type Payload<G> = G extends Guard<infer P> ? P : unknown export class Guard<P> { /** * Creates a new guard that uses a `narrow` function. * A little shortcut for `new Guard(narrow(...))`. * @example * * import { Guard } from 'narrow-minded' * const myGuard = Guard.narrow(['string', 'number']) * myGuard.satisfied(['horse', 42]) // => true * * @param n Narrower * @returns Guard */ static narrow<N extends Narrower>(n: N) { return new Guard((u: unknown): u is UnNarrow<N> => narrow(n, u)) } readonly NF: NarrowingFunction<P> constructor(NF: NarrowingFunction<P>) { this.NF = NF } /** * Runs the guard's narrowing function to validate the unknown value's type. * Operates as a type predicate so conditional blocks infer this structure. * @example * * const myGuard = Guard.narrow({ * name: 'string', * values: ['number'], * }) * * const good: unknown = { name: 'Horse', values: [1, 2] } * if (myGuard.satisfied(good)) { * console.log('Good ' + good.name) * // => 'Good Horse' * } * * const bad: unknown = { name: 42, values: 'Nope' } * if (!myGuard.satisfied(bad)) { * console.log('Bad ') * // => 'Bad' * } * * @param u The unknown value. * @returns A type predicate that `u` satisfies this guard. */ satisfied(u: unknown): u is P { return this.NF(u) } /** * An identity function that returns the value passed to it. Useful for * defining objects that satisfy this guard using type inference. * @param p * @returns p */ build(p: P): P { return p } /** * Creates a new guard that will satisfy the constraints of `this` AND `other`. * Useful for combining primitive narrows with more complex type checking. * @example * const myGuard = Guard.narrow({ type: 'string' }).and( * (u: unknown): u is { type: 'this' | 'that' } => * ['this', 'that'].includes((u as any).type), * ) * * @param other - Another Guard or a Narrower/NarrowerFunction which will * be wrapped into a Guard automatically. * @return Guard */ and<N extends Narrower>(other: N): Guard<P & UnNarrow<N>> and<P2>(other: Guard<P2> | NarrowingFunction<P2>): Guard<P & P2> and<P2>(other: any) { const left = this.NF const right = other instanceof Guard ? other.NF : other instanceof Function ? other : (u: unknown): u is P2 => narrow(other, u) return new Guard((u: unknown): u is P & P2 => left(u) && right(u)) } } /** * A singleton that can be used to build `and` chains. * @example * if (unknown.and('string').satisfied('Great')) { * console.log('Great') * } */ export const unknown = new Guard<unknown>((_): _ is unknown => true)