/* * ------------------------------------------- * Guard Combinators * ------------------------------------------- */ import { memoize } from "../_utils"; import { pipe } from "../Function"; import type { ReadonlyRecord } from "../Record"; import type { Guard } from "./Guard"; import { UnknownArray, UnknownRecord } from "./primitives"; export const refine = (refinement: (a: A) => a is B) => ( from: Guard ): Guard => ({ is: (u): u is B => from.is(u) && refinement(u) }); export const nullable = (or: Guard): Guard => ({ is: (u): u is null | A => u === null || or.is(u) }); export const type = ( properties: { [K in keyof A]: Guard } ): Guard => pipe( UnknownRecord, refine((r): r is { [K in keyof A]: A[K] } => { for (const k in properties) { if (!(k in r) || !properties[k].is(r[k])) { return false; } } return true; }) ); export const partial = ( properties: { [K in keyof A]: Guard } ): Guard> => pipe( UnknownRecord, refine((r): r is Partial => { for (const k in properties) { const v = r[k]; if (v !== undefined && !properties[k].is(r[k])) { return false; } } return true; }) ); export const array = (item: Guard): Guard> => pipe( UnknownArray, refine((u): u is ReadonlyArray => u.every(item.is)) ); export const record = (codomain: Guard): Guard> => pipe( UnknownRecord, refine((r): r is ReadonlyRecord => { for (const k in r) { if (!codomain.is(r[k])) { return false; } } return true; }) ); export const tuple = >( ...components: { [K in keyof A]: Guard } ): Guard => ({ is: (u): u is A => Array.isArray(u) && u.length === components.length && components.every((g, index) => g.is(u[index])) }); export const intersect = (right: Guard) => (left: Guard): Guard => ({ is: (u): u is A & B => left.is(u) && right.is(u) }); export const union = ]>( ...members: { [K in keyof A]: Guard } ): Guard => ({ is: (u): u is A | A[number] => members.some((m) => m.is(u)) }); export const sum = (tag: T) => ( members: { [K in keyof A]: Guard } ): Guard => pipe( UnknownRecord, refine((r): r is any => { const v = r[tag] as keyof A; if (v in members) { return members[v].is(r); } return false; }) ); export const lazy = (f: () => Guard): Guard => { const get = memoize>(f); return { is: (u): u is A => get().is(u) }; };