/** * The typeguard module provides the building blocks for creating type-safe typeguards. * * The intent is to be able to create your types from the typeguards, instead of creating typeguards for a type. * * @module typeguard * @requires module:misc */ /** */ import {Callable, setAndReturn} from './misc.js'; /** This Type retrieves the guarded type from a TypeGuard. */ export type TypeGuardOf = T extends TypeGuard ? U : never; type OR = T extends readonly [first: infer U, ...rest: infer Rest] ? TypeGuardOf | OR : never; type AND = T extends readonly [first: infer U, ...rest: infer Rest] ? TypeGuardOf & AND : unknown; type ORVals = T extends readonly [first: infer U, ...rest: infer Rest] ? U | ORVals : never; type Template = T extends readonly [first: TypeGuard, second: string, ...rest: infer U] ? Template<`${S}${TypeGuardOf}${T[1]}`, U> : S; type AltTuple = T extends readonly [tg: infer G, s: infer S, ...rest: infer Rest] ? [G, S] extends [TypeGuard, string] ? readonly [G, S, ...AltTuple] : never : readonly []; type Deps = Record; type Aliases = { deps?: Deps; } type ObjectDefinition = readonly ["Object", Record]; type AndOrDefinition = readonly ["Or" | "And", readonly Definition[]]; type PrimitiveOrValueDefinition = readonly ["", string, string?]; type TemplateOr = ["Or", readonly PrimitiveOrValueDefinition[]]; type TemplateData = [string, ...(string | PrimitiveOrValueDefinition | TemplateOr)[]]; type TemplateDefinition = readonly ["Template", TemplateData] type Definition = PrimitiveOrValueDefinition | TemplateDefinition | readonly ["Array", Definition] | readonly ["Tuple", readonly Definition[], Definition?] | AndOrDefinition | ObjectDefinition | readonly ["Recur", string, Definition?] | readonly [Exclude, Definition, Definition?] // Normal, Array, Tuple, Or/And, Object, Special type DefinitionWithDeps = Definition & Aliases; type StoredDefinition = Definition | (() => Definition); let throwErrors = false, allowUndefined: boolean | null = null, take: (keyof any)[] | null = null, skip: (keyof any)[] | null = null, unknownTypes = 0; const throwUnknownError = (v: boolean) => { if (!v && throwErrors) { throw new TypeError("unknown type error"); } return v; }, throwOrReturn = (v: boolean, name: string, key?: any, err?: any) => { if (!v && throwErrors) { throw new TypeError(`invalid value: ${name}` + (key !== undefined && err ? `[${key}]: ${err instanceof Error ? err.message : err}` : "")); } return v; }, mods = () => { const mods = [allowUndefined, take, skip] as const; allowUndefined = null; take = null; skip = null; return mods; }, resetMods = ([au, tk, s]: readonly [typeof allowUndefined, typeof take, typeof skip]) => () => { allowUndefined = au; take = tk; skip = s; }, unknownStr = "unknown", [unknownDef, anyDef, numberDef, bigIntDef, stringDef, booleanDef, functionDef, symbolDef, neverDef, undefinedDef, nullDef, voidDef] = [unknownStr, "any", "number", "bigint", "string", "boolean", "Function", "symbol", "never", "undefined", "null", "void"].map(v => Object.freeze(["", v] as const)), definitions = new WeakMap, StoredDefinition>(), strings = new WeakMap(), spreads = new WeakMap, STypeGuard>(), identifer = /^[_$\p{ID_Start}][$\u200c\u200d\p{ID_Continue}]*$/v, matchTemplate = (v: string, p: readonly (string | TypeGuard)[]) => { const [tg, s, ...rest] = p as readonly [TypeGuard, string, ...(string | TypeGuard)[]]; if (rest.length === 0) { if (v.endsWith(s) && tg(v.slice(0, v.length - s.length))) { return true; } return false; } for (let pos = 0; pos < v.length - s.length; pos++) { pos = v.indexOf(s, pos); if (pos < 0) { break; } if (tg(v.slice(0, pos)) && matchTemplate(v.slice(pos + s.length), rest)) { return true; } } return false; }, filterObj = (def: Definition, fn: (key: string | number | symbol, val: Definition) => null | [typeof key, Definition]): Definition => def[0] === "Object" ? [def[0], (Object.entries(def[1]).map(([k, v]) => fn(k, v)).filter(v => v) as [string, Definition][]).reduce((o, [k, v]) => (o[k] = v, o), {} as Record)] as ObjectDefinition : def[0] === "Or" || def[0] === "And" ? [def[0], (def[1] as Definition[]).map(d => d[0] === "Object" || d[0] === "Or" || d[0] == "And" ? filterObj(d, fn) : d)] : def, reduceAndOr = (andOr: "And" | "Or", tgs: readonly TypeGuard[]): Definition => { const list: Definition[] = [], simple = new Set(); for (const tg of tgs) { const def = tg.def(); if (def[0] === andOr) { for (const d of def[1] as Definition[]) { if (d[0] === "") { if (simple.has(d[1])) { continue } simple.add(d[1]); } list.push(d); } } else if ((def[0] !== "" || def[1] !== "never")) { if (def[0] === "") { if (simple.has(def[1])) { continue } simple.add(def[1]); } list.push(def); } } return list.length === 1 ? list[0] : list.length ? [andOr, list] : neverDef; }, templateSafe = (s: string) => s.replaceAll("${", "\\${").replaceAll("`", "\\`"), toString = (def: Definition): string => strings.get(def) ?? setAndReturn(strings, def, defToString(def)), defToString = (def: Definition): string => { switch (def[0]) { case "": case "Recur": return typeof def[2] === "string" ? `${def[1]} /* ${def[2]} */` : def[1] as string; case "Template": return (def[1] as (string | PrimitiveOrValueDefinition)[]).reduce((s, d, n) => s + (n % 2 ? "${" + toString(d as PrimitiveOrValueDefinition) + "}" : templateSafe(d as string)), "`") + "`"; case "Array": return (def[1][0] === "And" || def[1][0] === "Or" ? `(${toString(def[1])})` : toString(def[1])) + "[]"; case "And": case "Or": return (def[1] as Definition[]).map(d => def[0] === "And" && d[0] === "Or" ? `(${toString(d)})` : toString(d)).join(def[0] === "And" ? " & " : " | "); case "Object": return (Object.entries(def[1]) as [string, Definition][]).filter(([k]) => typeof k === "string").map(([k, d]) => ` ${k.match(identifer) ? k : JSON.stringify(k)}${(d[0] === "" && d[1] === "undefined") || d[0] === "Or" && (d[1] as Definition[]).some(e => e[0] === "" && e[1] === "undefined") ? "?" : ""}: ${toString(d).replaceAll("\n", "\n ")};\n`).reduce((t, e, n) => t + (!n ? "\n" : "") + e, "{") + "}"; case "Tuple": return "[" + (def[1] as Definition[]).map(toString).concat(def[2] ? "..." + (["Or", "And"].includes(def[2][0]) ? `(${toString(def[2])})` : toString(def[2])) + "[]" : []).join(", ") + "]"; default: return `${def[0]}<${(def.slice(1) as Definition[]).map(toString).join(", ")}>`; } }; /** * This type represents a typeguard of the given type. * * In addition to being a callable typeguard function, has the following methods: * * @method def Returns a processable value that represents the type being guarded. * @method throw Runs the underlying typeguard and will throw errors on type mismatch. * @method throws Returns a TypeGuard that will throw errors on failures. * * Lastly, TypeGuards can be spread in Tuple calls. * */ export type TypeGuard = STypeGuard & ((v: unknown) => v is T); class STypeGuard extends Callable<(v: unknown) => v is T> { constructor(tg: (v: unknown) => v is T, def: StoredDefinition) { super(tg); definitions.set(this, def); } throw(v: unknown): v is T { const oldThrows = throwErrors; try { throwErrors = true; return this(v); } finally { throwErrors = oldThrows; } } throws() { return asTypeGuard((v: unknown): v is T => this.throw(v), () => this.def()); } *[Symbol.iterator]() { yield new SpreadTypeGuard(this) as TypeGuard; } def(): DefinitionWithDeps { const def = definitions.get(this) ?? unknownDef, processed = def instanceof Function ? def() : def, late = definitions.get(this); return late !== def && late instanceof Array && late[0] === "Recur" ? setAndReturn(definitions, this, Object.freeze(["Recur", late[1] as string, processed])) : processed !== def ? setAndReturn(definitions, this, Object.freeze(processed) as Definition) : processed; } toString(): string { return toString(this.def()); } } class SpreadTypeGuard extends Callable> { constructor(tg: STypeGuard) { const stg = ((v: unknown): v is T => tg(v)) as TypeGuard; super(stg); spreads.set(stg, tg); } def(): DefinitionWithDeps { return spreads.get(this)?.def() ?? unknownDef; } toString(): string { return spreads.get(this)?.toString() ?? unknownStr; } } export const /** * This function gives a custom typeguard additional functionality, such as being able to optionally throw errors, and be spreadable. * * NB: All TypeGuards created by this package already have this functionality. * * @typeParam T * * @param {(v: unknown) => v is T} tg The TypeGuard the functionality will be added to. * * @return {TypeGuard} The passed in typeguard, with additional functionality. */ asTypeGuard = (tg: (v: unknown) => v is T, def: StoredDefinition = unknownDef): TypeGuard => new STypeGuard(tg, def) as TypeGuard, /** * The Bool function returns a TypeGuard that checks for boolean values, and takes an optional, specific boolean value to check against. * * @param {boolean} [d] Exact boolean to match against. * * @return {TypeGuard} */ Bool = (d?: T) => asTypeGuard((v: unknown): v is T => throwOrReturn(typeof v === "boolean" && (d === undefined || v === d), "boolean"), d !== undefined ? ["", d.toString()] : booleanDef), /** * The Str function returns a TypeGuard that checks for string values, and takes an optional regex to confirm string format against. * * @param {regex} [r] Regexp to compare any strings against. * * @return {TypeGuard} */ Str = (r?: RegExp) => asTypeGuard((v: unknown): v is string => throwOrReturn(typeof v === "string" && (r === undefined || r.test(v)), "string"), stringDef), /** * The Tmpl function returns a TypeGuard that checks for template values. * * @param {string} first The Initial string part to match. * @param {(TypeGuard | string)[]} ...s Remaining parts to match, must be an alternating list of TypeGuard and string * * @return {TypeGuard} */ Tmpl = )[]>(first: S, ...s: T extends AltTuple ? T : never) => asTypeGuard((v: unknown): v is Template => throwOrReturn(typeof v === "string" && v.startsWith(first) && matchTemplate(v.slice(first.length), s), "template"), () => { let rest: (string | TypeGuard)[] = s.slice(), justString = first === ""; const vals: TemplateData = [first]; while (rest.length) { const [tg, s, ...r] = rest as [TypeGuard, string, ...(string | TypeGuard)[]], def = tg.def() as TemplateDefinition | PrimitiveOrValueDefinition | TemplateOr, [typ, val] = def; rest = r; switch (typ) { case "Template": vals[vals.length - 1] += val[0]; let dr = val.slice(1); while (dr.length) { const [d, ds, ...rest] = dr as [PrimitiveOrValueDefinition | TemplateOr, string, ...string[]]; dr = rest; if (d[0] === "" && d[1] === "string" && vals.length > 1 && !vals[vals.length - 1]) { vals[vals.length - 1] = ds; } else { vals.push(d, ds); justString &&= d[0] === "" && d[1] === "string" && ds === ""; } } vals[vals.length - 1] += s; break; case "Or": justString = false; vals.push(def, s); break; default: if (val.startsWith(`"`)) { vals[vals.length - 1] += JSON.parse(val) + s; justString = false; } else if (val === "string" && vals.length > 1 && !vals[vals.length - 1]) { vals[vals.length - 1] = s; } else { justString &&= val === "string"; vals.push(def, s); } } justString &&= s === ""; } return vals.length === 1 ? ["", JSON.stringify(vals[0])] : justString ? stringDef : ["Template", vals]; }), /** * The Undefined function returns a TypeGuard that checks for `undefined`. * * @return {TypeGuard} */ Undefined = () => asTypeGuard((v: unknown): v is undefined => throwOrReturn(v === undefined, "undefined"), undefinedDef), /** * The Opt function returns a TypeGuard that checks for both the passed TypeGuard while allowing for it to be undefined. * * @typedef T * * @return {TypeGuard} */ Opt = (v: TypeGuard) => Or(v, Undefined()), /** * The Null function returns a TypeGuard that checks for `null`. * * @return {TypeGuard} */ Null = () => asTypeGuard((v: unknown): v is null => throwOrReturn(v === null, "null"), nullDef), /** * The Num function returns a TypeGuard that checks for numbers, and takes optional min and max (inclusive) values to range check. * * @param {number} min Minimum value for the number. * @param {number} max Maximum value for the number. * * @return {TypeGuard} */ Num = (min = -Infinity, max = Infinity) => asTypeGuard((v: unknown): v is number => throwOrReturn(typeof v === "number" && v >= min && v <= max, "number"), min !== -Infinity ? ["", "number", `${min} <= n` + (max !== Infinity ? ` <= ${max}` : "")] : max !== Infinity ? ["", "number", `n <= ${max}`] : numberDef), /** * The Int function returns a TypeGuard that checks for integers, and takes optional min and max (inclusive) values to range check. * * @param {number} min Minimum value for the integer. * @param {number} max Maximum value for the integer. * * @return {TypeGuard} */ Int = (min = Number.MIN_SAFE_INTEGER, max = Number.MAX_SAFE_INTEGER) => asTypeGuard((v: unknown): v is number => throwOrReturn(typeof v === "number" && Number.isInteger(v) && v >= min && v <= max, "integer"), min > Number.MIN_SAFE_INTEGER ? ["", "number", `${min} <= i` + (max < Number.MAX_SAFE_INTEGER ? ` <= ${max}` : "")] : max < Number.MAX_SAFE_INTEGER ? ["", "number", `i <= ${max}`] : numberDef), /** * The BigInt function returns a TypeGuard that checks for bigints, and takes optional min and max (inclusive) values to range check. * * @param {number} min Minimum value for the bigint. * @param {number} max Maximum value for the bigint. * * @return {TypeGuard} */ BigInt = (min?: bigint, max?: bigint) => asTypeGuard((v: unknown): v is bigint => throwOrReturn(typeof v === "bigint" && (min === undefined || v >= min) && (max === undefined || v <= max), "bigint"), min !== undefined ? ["", "bigint", `${min}n <= b` + (max != undefined ? ` <= ${max}n` : "")] : max != undefined ? ["", "bigint", `b <= ${max}n`] : bigIntDef), /** * The Sym function returns a TypeGuard that checks for symbols. * * @return {TypeGuard} */ Sym = () => asTypeGuard((v: unknown): v is symbol => throwOrReturn(typeof v === "symbol", "symbol"), symbolDef), /** * The Val function returns a TypeGuard that checks for a specific, primitive value. * * @typedef {boolean | number | bigint | string | null | undefined} T * * @param {T} v The value to check against. * * @return {TypeGuard} */ Val = (val: T) => asTypeGuard((v: unknown): v is T => throwOrReturn(v === val, "value"), ["", typeof val === "bigint" ? val + "n" : val === undefined ? "undefined" : JSON.stringify(val)]), /** * The Any function returns a TypeGuard that allows any value. * * @return {TypeGuard} */ Any = () => asTypeGuard((_: unknown): _ is any => true, anyDef), /** * The Unknown function returns a TypeGuard that allows any value, but types to `unknown`. * * @return {TypeGuard} */ Unknown = () => asTypeGuard((_: unknown): _ is unknown => true, unknownDef), /** * The Void function returns a TypeGuard that performs no check as the value is not intended to be used. * * @return {TypeGuard} */ Void = () => asTypeGuard((_: unknown): _ is void => true, voidDef), /** * The Arr function returns a TypeGuard that checks for an Array, running the given TypeGuard on each element. * * @param {TypeGuard} t The TypeGuard to run on each element. * * @return {TypeGuard(t: TypeGuard) => asTypeGuard((v: unknown): v is Array => { if (!(v instanceof Array)) { return throwOrReturn(false, "array"); } mods(); let pos = 0; for (const e of v) { try { if (!throwUnknownError(t(e))) { return false; } } catch (err) { return throwOrReturn(false, "array", pos, err); } pos++; } return true; }, () => ["Array", t.def()]), /** * The Tuple function returns a TypeGuard that checks for the given types in an array. * * @param {...TypeGuard} The elements of the tuple. TypeGuards can be spread to allow for and unknown number of that type (follow the typescript rules for spreads). * * @return {TypeGuard<[]>} */ Tuple = } = {[K in keyof T]: TypeGuard}>(...t: U) => { const tgs: TypeGuard[] = []; for (const tg of t) { if (tg instanceof SpreadTypeGuard) { break; } tgs.push(tg); } const spread = tgs.length < t.length ? t.length - tgs.length === 1 ? t[t.length - 1] : Or(...t.slice(tgs.length)) : undefined; return asTypeGuard((v: unknown): v is {-readonly [K in keyof U]: TypeGuardOf;} => { if (!(v instanceof Array)) { return throwOrReturn(false, "tuple"); } mods(); let pos = 0; try { for (const tg of tgs) { if (!throwUnknownError(tg(v[pos]))) { return false; } pos++; } if (spread) { for (; pos < v.length; pos++) { if (!throwUnknownError(spread(v[pos]))) { return false; } } } } catch (err) { return throwOrReturn(false, "tuple", pos, err); } return throwOrReturn(pos === v.length, "tuple", "", "extra values"); }, () => spread ? ["Tuple", tgs.map(tg => tg.def()), spread.def()] : ["Tuple", tgs.map(tg => tg.def())]); }, /** * The Obj function returns a TypeGuard that checks for an object type defined by the passed object of TypeGuards. * * @param {Record} t The Object definition build from TypeGuards. * * @return {TypeGuard} */ Obj = } = {[K in keyof T]: TypeGuard}, R = {[K in keyof U as undefined extends TypeGuardOf ? never : K]: TypeGuardOf;} & {[K in keyof U as undefined extends TypeGuardOf ? K : never]?: TypeGuardOf;}>(t?: U) => asTypeGuard((v: unknown): v is {[K in keyof R]: R[K]} => { const [au, tk, s] = mods(); if (!(v instanceof Object)) { return throwOrReturn(false, "object"); } if (t) { for (const [k, tg] of Object.entries(t) as [keyof typeof v, TypeGuard][]) { if (tk && !tk.includes(k) || s?.includes(k)) { continue; } const e = v[k]; if (e === undefined) { if (au === true) { continue; } else if (au === false) { return throwOrReturn(false, "object", k, "required is undefined"); } } try { if (!throwUnknownError(tg(e))) { return false; } } catch (err) { return throwOrReturn(false, "object", k, err); } } } return true; }, () => ["Object", Object.fromEntries((Object.entries(t ?? {}) as [keyof U, TypeGuard][]).map(([k, v]) => [k, v.def()]))]), /** * The Part function takes an existing TypeGuard created by the Obj function and transforms it to allow any of the defined keys to not exist (or to be 'undefined'). * * @param {TypeGuard<{}>} tg The TypeGuard created by a call to Obj. * * @return {TypeGuard<{}>} */ Part = (tg: TypeGuard) => asTypeGuard((v: unknown): v is {[K in keyof T]?: T[K]} => { allowUndefined ??= true; try { return tg(v); } finally { allowUndefined = null; } }, () => filterObj(tg.def(), (k: string | number | symbol, v: Definition) => v[0] === "Or" ? [k, v] : [k, ["Or", [v, undefinedDef]]])), /** * The Req function takes an existing TypeGuard created by the Obj function and transforms it to require all of the defined keys to exist and to not be undefined. * * @param {TypeGuard<{}>} tg The TypeGuard created by a call to Obj. * * @return {TypeGuard<{}>} */ Req = (tg: TypeGuard) => asTypeGuard((v: unknown): v is {[K in keyof T]-?: Exclude} => { allowUndefined ??= false; try { return tg(v); } finally { allowUndefined = null; } }, () => filterObj(tg.def(), (k: string | number | symbol, v: Definition) => { if (v[0] === "Or") { const left = (v[1] as Definition[]).filter(d => d[0] !== "" || d[1] !== "undefined"); if (!left.length) { return null; } if (left.length === 1) { return [k, left[0]]; } return [k, ["Or", left]]; } else if (v[0] === "" && v[1] === "undefined") { return null; } return [k, v]; })), /** * The Take function takes an existing TypeGuard create by the Obj function and transforms it to only check the keys passed into this function. * * @param {TypeGuard<{}>} tg tg The TypeGuard created by a call to Obj. * @param {...(keyof any}[]} keys The list of keys to limit Object checking to. * * @return {TypeGuard<{}>} */ Take = (tg: TypeGuard, ...keys: Keys) => asTypeGuard((v: unknown): v is {[K in keyof T as K extends ORVals ? K : never]: T[K]} => { take = take ? take.filter(k => keys.includes(k as keyof T)) : keys; try{ return tg(v); } finally { take = null; } }, () => filterObj(tg.def(), (k: string | number | symbol, v: Definition) => keys.includes(k as keyof T) ? [k, v] : null)), /** * The Skip function takes an existing TypeGuard create by the Obj function and transforms it to not check the keys passed into this function. * * @param {TypeGuard<{}>} tg tg The TypeGuard created by a call to Obj. * @param {...(keyof any}[]} keys The list of keys to be skipped within Obj checking. * * @return {TypeGuard<{}>} */ Skip = (tg: TypeGuard, ...keys: Keys) => asTypeGuard((v: unknown): v is {[K in keyof T as K extends ORVals ? never : K]: T[K]} => { skip = skip ? [...skip, ...keys] : keys; try{ return tg(v); } finally { skip = null; } }, () => filterObj(tg.def(), (k: string | number | symbol, v: Definition) => keys.includes(k as keyof T) ? null : [k, v])), /** * The Recur function wraps an existing TypeGuard so it can be used recursively within within itself during TypeGuard creation. The base TypeGuard will need to have it's type specified manually when used this way. * * @typedef T * * @param {() => TypeGuard} tg A closure that returns the recurring TypeGuard. * @param {string} [str] Optional type name. If unspecified will be generated automatically from template: `type_${number}` * * @return {TypeGuard} */ Recur = (tg: () => TypeGuard, str?: string) => { let ttg: TypeGuard; const name = str ?? "type_"+unknownTypes++; // need to generate type name here return asTypeGuard((v: unknown): v is T => (ttg ??= tg())(v), () => setAndReturn(definitions, ttg ??= tg(), ["Recur", name])); }, /** * The NumStr function returns a TypeGuard that checks for a string value that represents an number. * * @return {TypeGuard<`${number}`>} */ NumStr = () => asTypeGuard((v: unknown): v is `${number}` => throwOrReturn(typeof v === "string" && parseFloat(v) + "" === v, "NumStr"), ["Template", ["", numberDef, ""]]), /** * The IntStr function returns a TypeGuard that checks for a string value that represents an integer. Intended to be used with Rec for integer key types. * * @return {TypeGuard<`${number}`>} */ IntStr = () => asTypeGuard((v: unknown): v is `${number}` => throwOrReturn(typeof v === "string" && parseInt(v) + "" === v, "IntStr"), ["Template", ["", numberDef, ""]]), /** * The BoolStr function returns a TypeGuard that checks for a string value that represents an boolean. * * @return {TypeGuard<`${boolean}`>} */ BoolStr = () => asTypeGuard((v: unknown): v is `${boolean}` => throwOrReturn(typeof v === "string" && (v === "true" || v === "false"), "BoolStr"), ["Template", ["", booleanDef, ""]]), /** * The Rec function returns a TypeGuard that checks for an Object type where the keys and values are of the types specified. * * @param {TypeGuard>} key The Key type. * @param {TypeGuard} value The Value type. * * @return {Record} */ Rec = >, V extends TypeGuard>(key: K, value: V) => asTypeGuard((v: unknown): v is Record, TypeGuardOf> => { if (!(v instanceof Object)) { return throwOrReturn(false, "record"); } mods(); for (const k of Reflect.ownKeys(v)) { try { if (!throwUnknownError(key(k))) { return false; } } catch (err) { return throwOrReturn(false, "record-key", k, err); } try { if (!throwUnknownError(value(v[k as keyof typeof v]))) { return false; } } catch (err) { return throwOrReturn(false, "record", k, err); } } return true; }, () => ["Record", key.def(), value.def()]), /** * The Or function returns a TypeGuard that checks a value matches against any of the given TypeGuards. * * @param {...TypeGuard} ths A list of TypeGuards to match against. * * @return {TypeGuard} */ Or = []>(...tgs: T) => asTypeGuard((v: unknown): v is OR => { const errs: string[] = [], rm = resetMods(mods()); for (const tg of tgs) { rm(); try { if (tg(v)) { return true; } throwUnknownError(false); } catch (err) { errs.push(err instanceof Error ? err.message : err + ""); } } return throwOrReturn(false, "OR", "", errs.join(" | ")); }, () => reduceAndOr("Or", tgs)), /** * The And function returns a TypeGuard that checks a value matches against all of the given TypeGuards. * * @param {...TypeGuard} ths A list of TypeGuards to match against. * * @return {TypeGuard} */ And = []>(...tgs: T) => asTypeGuard((v: unknown): v is {[K in keyof AND]: AND[K]} => { let pos = 0; const rm = resetMods(mods()); for (const tg of tgs) { rm(); try { if (!throwUnknownError(tg(v))) { return false; } } catch (err) { return throwOrReturn(false, "AND", pos, err); } pos++; } return true; }, () => reduceAndOr("And", tgs)), /** * The MapType function returns a TypeGuard that checks for an Map type where the keys and values are of the types specified. * * @param {TypeGuard} key The Key type. * @param {TypeGuard} value The Value type. * * @return {Map} */ MapType = , V extends TypeGuard>(key: K, value: V) => asTypeGuard((v: unknown): v is Map, TypeGuardOf> => { if (!(v instanceof Map)) { return throwOrReturn(false, "map"); } mods(); for (const [k, val] of v) { try { if (!throwUnknownError(key(k))) { return false; } } catch (err) { return throwOrReturn(false, "map-key", k, err); } try { if (!throwUnknownError(value(val))) { return false; } } catch (err) { return throwOrReturn(false, "map", k, err); } } return true; }, () => ["Map", key.def(), value.def()]), /** * The SetType function returns a TypeGuard that checks for an Set type where the values are of the type specified. * * @param {TypeGuard} value The Value type. * * @return {Set} */ SetType = (t: TypeGuard) => asTypeGuard((v: unknown): v is Set => { if (!(v instanceof Set)) { return throwOrReturn(false, "set"); } mods(); let pos = 0; for (const val of v) { try { if (!throwUnknownError(t(val))) { return false; } } catch (err) { return throwOrReturn(false, "set", pos, err); } pos++; } return true; }, () => ["Set", t.def()]), /** * The Class function returns a TypeGuard that checks a value is of the class specified. * * @param {Class} c The class to check against. * * @return {TypeGuard} */ Class = (t: T) => asTypeGuard((v: unknown): v is InstanceType => throwOrReturn(v instanceof t, "class"), ["", t.name || unknownStr]), /** * The Func function returns a TypeGuard that checks a value is a function. An optional number of arguments can be specified as an additional check. * * @param {number} [args] Number of arguments that the function must have. * * @returns {TypeGuard} */ Func = (args?: number) => asTypeGuard((v: unknown): v is T => throwOrReturn(v instanceof Function && (args === undefined || v.length === args), "Function"), args ? ["", "Function", args?.toString()] : functionDef), /** * The Forbid function returns a TypeGuard that disallows certain types from an existing type. * * @param {TypeGuard} t A TypeGuard to require. * @param {TypeGuard} u A TypeGuard to forbid. * * @returns {TypeGuard>} */ Forbid = (t: TypeGuard, u: TypeGuard) => asTypeGuard((v: unknown): v is Exclude => { let forbid = false; try { if (u(v)) { forbid = true; } } catch(e) {} if (forbid) { return throwOrReturn(false, "forbid") } return t(v); }, () => ["Exclude", t.def(), u.def()]);