# @wistia/type-guards — full reference Runtime type guards, utility types, and utility functions for TypeScript. Every guard is a `value is T` predicate that narrows `unknown` values; every type is a small composable building block; utilities are general-purpose helper functions. Side-effect free, tree-shakeable, ESM and CJS builds. ## Install ```sh npm install @wistia/type-guards yarn add @wistia/type-guards pnpm add @wistia/type-guards ``` ## Import ```ts import { isString, isNotNil, isNonEmptyArray, type Nilable, type NonEmptyArray, } from '@wistia/type-guards'; ``` ## Guards ### `hasKey` ```ts hasKey(value: unknown, key: Key): value is Record ``` ```ts hasKey({ a: 1 }, 'a'); // true hasKey({ a: 1 }, 'b'); // false ``` ### `isArray` ```ts isArray(value: unknown): value is unknown[] ``` ```ts isArray([1, 2]); // true isArray('abc'); // false ``` ### `isAsyncFunction` ```ts isAsyncFunction(value: unknown): value is (...args: unknown[]) => Promise ``` Detection relies on `constructor.name === 'AsyncFunction'` and is a heuristic: async functions transpiled to regular functions return false, async generator functions return false, and a forged `constructor` property can fool the check. Bound async functions return true, since `bind` preserves the prototype chain. Synchronous functions that return promises also return false. ```ts isAsyncFunction(async () => {}); // true isAsyncFunction(() => {}); // false ``` ### `isBigInt` ```ts isBigInt(value: unknown): value is bigint ``` ```ts isBigInt(10n); // true isBigInt(10); // false ``` ### `isBoolean` ```ts isBoolean(value: unknown): value is boolean ``` ```ts isBoolean(true); // true isBoolean(1); // false ``` ### `isDate` ```ts isDate(value: unknown): value is Date ``` Matches `Date` instances only. Does not parse date strings or accept numeric timestamps. ```ts isDate(new Date()); // true isDate('2024-01-01'); // false ``` ### `isEmptyArray` ```ts isEmptyArray(value: unknown): value is never[] ``` ```ts isEmptyArray([]); // true isEmptyArray([1]); // false ``` ### `isEmptyRecord` ```ts isEmptyRecord(value: unknown): value is EmptyObject ``` ```ts isEmptyRecord({}); // true isEmptyRecord({ a: 1 }); // false ``` ### `isEmptyString` ```ts isEmptyString(value: unknown): value is '' ``` ```ts isEmptyString(''); // true isEmptyString('a'); // false ``` ### `isError` ```ts isError(value: unknown): value is Error ``` Matches subclasses of `Error` too (e.g. `TypeError`, custom errors). ```ts isError(new Error('boom')); // true isError('boom'); // false ``` ### `isFalsy` ```ts isFalsy(value: unknown): value is Falsy ``` Returns true for `false`, `0`, `-0`, `0n`, `''`, `null`, `undefined`, and `NaN`. ```ts isFalsy(0); // true isFalsy(1); // false ``` ### `isFiniteNumber` ```ts isFiniteNumber(value: unknown): value is number ``` ```ts isFiniteNumber(42); // true isFiniteNumber(Infinity); // false ``` ### `isFunction` ```ts isFunction(value: unknown): value is (...args: unknown[]) => unknown ``` ```ts isFunction(() => {}); // true isFunction({}); // false ``` ### `isHtmlButtonElement` ```ts isHtmlButtonElement(value: unknown): value is HTMLButtonElement ``` ```ts isHtmlButtonElement(document.createElement('button')); // true isHtmlButtonElement(document.createElement('div')); // false ``` ### `isHtmlElement` ```ts isHtmlElement(value: unknown): value is HTMLElement ``` ```ts isHtmlElement(document.body); // true isHtmlElement('div'); // false ``` ### `isHtmlInputElement` ```ts isHtmlInputElement(value: unknown): value is HTMLInputElement ``` ```ts isHtmlInputElement(document.createElement('input')); // true isHtmlInputElement(document.createElement('div')); // false ``` ### `isHtmlVideoElement` ```ts isHtmlVideoElement(value: unknown): value is HTMLVideoElement ``` ```ts isHtmlVideoElement(document.createElement('video')); // true isHtmlVideoElement(document.createElement('div')); // false ``` ### `isInteger` ```ts isInteger(value: unknown): value is number ``` Uses `Number.isInteger`, so non-finite values (`NaN`, `Infinity`) return false. ```ts isInteger(5); // true isInteger(5.5); // false ``` ### `isIterable` ```ts isIterable(value: unknown): value is Iterable ``` Strings are iterable and return true. Plain objects without `Symbol.iterator` return false. ```ts isIterable([1, 2, 3]); // true isIterable({}); // false ``` ### `isMap` ```ts isMap(value: unknown): value is Map ``` WeakMap instances return false. ```ts isMap(new Map()); // true isMap({}); // false ``` ### `isMouseEvent` ```ts isMouseEvent(value: unknown): value is MouseEvent ``` ```ts isMouseEvent(new MouseEvent('click')); // true isMouseEvent(new Event('change')); // false ``` ### `isNaN` ```ts isNaN(value: unknown): value is number ``` Uses `Number.isNaN` (not the coercive global `isNaN`), so only the literal `NaN` value returns true. ```ts isNaN(Number.NaN); // true isNaN(0); // false ``` ### `isNil` ```ts isNil(value: unknown): value is Nil ``` ```ts isNil(null); // true isNil(0); // false ``` ### `isNonBlankString` ```ts isNonBlankString(value: unknown): value is string ``` ```ts isNonBlankString('a'); // true isNonBlankString(' '); // false ``` ### `isNonEmptyArray` ```ts isNonEmptyArray(value: Nilable): value is NonEmptyArray ``` ```ts isNonEmptyArray([1]); // true isNonEmptyArray([]); // false ``` ### `isNonEmptyRecord` ```ts isNonEmptyRecord(value: unknown): value is Record ``` ```ts isNonEmptyRecord({ a: 1 }); // true isNonEmptyRecord({}); // false ``` ### `isNonEmptyString` ```ts isNonEmptyString(value: unknown): value is string ``` ```ts isNonEmptyString('a'); // true isNonEmptyString(''); // false ``` ### `isNonNaNNumber` ```ts isNonNaNNumber(value: unknown): value is number ``` ```ts isNonNaNNumber(42); // true isNonNaNNumber(Number.NaN); // false ``` ### `isNotArray` ```ts isNotArray(value: unknown): value is Exclude ``` ```ts isNotArray('x'); // true isNotArray([]); // false ``` ### `isNotBoolean` ```ts isNotBoolean(value: unknown): value is Exclude ``` ```ts isNotBoolean(1); // true isNotBoolean(true); // false ``` ### `isNotFunction` ```ts isNotFunction(value: unknown): value is Exclude unknown> ``` ```ts isNotFunction(1); // true isNotFunction(() => {}); // false ``` ### `isNotNil` ```ts isNotNil(value: Nil | T): value is T ``` Common in `.filter(isNotNil)` to drop `null`/`undefined` from arrays with narrowing. ```ts isNotNil(0); // true isNotNil(null); // false const items: (string | null)[] = ['a', null, 'b']; const clean: string[] = items.filter(isNotNil); // ['a', 'b'] ``` ### `isNotNull` ```ts isNotNull(value: unknown): value is Exclude ``` ```ts isNotNull(undefined); // true isNotNull(null); // false ``` ### `isNotNumber` ```ts isNotNumber(value: unknown): value is Exclude ``` ```ts isNotNumber('1'); // true isNotNumber(1); // false ``` ### `isNotRecord` ```ts isNotRecord(value: unknown): value is Exclude> ``` ```ts isNotRecord([]); // true isNotRecord({}); // false ``` ### `isNotString` ```ts isNotString(value: unknown): value is Exclude ``` ```ts isNotString(1); // true isNotString('a'); // false ``` ### `isNotUndefined` ```ts isNotUndefined(value: Undefinable): value is Exclude ``` ```ts isNotUndefined(null); // true isNotUndefined(undefined); // false ``` ### `isNotVoid` ```ts isNotVoid(value: unknown): value is Exclude ``` ```ts isNotVoid(0); // true isNotVoid(undefined); // false ``` ### `isNull` ```ts isNull(value: unknown): value is null ``` ```ts isNull(null); // true isNull(undefined); // false ``` ### `isNumber` ```ts isNumber(value: unknown): value is number ``` `NaN` is a `number`, so `isNumber(NaN)` is true. Use `isInteger` or combine with `!isNaN(value)` if you need a finite number. ```ts isNumber(1); // true isNumber('1'); // false ``` ### `isPlainObject` ```ts isPlainObject(value: unknown): value is Record ``` ```ts isPlainObject({}); // true isPlainObject(new Date()); // false ``` ### `isPromise` ```ts isPromise(value: unknown): value is Promise ``` The full `Promise` method surface is required so the narrowed type is accurate: `then`, `catch`, and `finally` must all be functions. Thenables that only implement a subset are rejected. ```ts isPromise(Promise.resolve()); // true isPromise({ then: () => 1 }); // false ``` ### `isRecord` ```ts isRecord(value: unknown): value is Record ``` ```ts isRecord({}); // true isRecord([]); // false ``` ### `isRegExp` ```ts isRegExp(value: unknown): value is RegExp ``` ```ts isRegExp(/abc/); // true isRegExp('/abc/'); // false ``` ### `isSet` ```ts isSet(value: unknown): value is Set ``` WeakSet instances return false. ```ts isSet(new Set()); // true isSet([]); // false ``` ### `isString` ```ts isString(value: unknown): value is string ``` ```ts isString('hi'); // true isString(42); // false ``` ### `isSvgElement` ```ts isSvgElement(value: unknown): value is SVGElement ``` ```ts isSvgElement(document.createElementNS('http://www.w3.org/2000/svg', 'svg')); // true isSvgElement(document.createElement('div')); // false ``` ### `isSymbol` ```ts isSymbol(value: unknown): value is symbol ``` ```ts isSymbol(Symbol('a')); // true isSymbol('symbol'); // false ``` ### `isTextNode` ```ts isTextNode(value: unknown): value is Text ``` ```ts isTextNode(document.createTextNode('hi')); // true isTextNode(document.createElement('div')); // false ``` ### `isTruthy` ```ts isTruthy(value: unknown): boolean ``` The complement of `isFalsy`. Does not provide narrowing on its own; use `isNotNil` or a specific positive guard when narrowing matters. ```ts isTruthy(1); // true isTruthy(0); // false ``` ### `isUndefined` ```ts isUndefined(value: unknown): value is undefined ``` ```ts isUndefined(undefined); // true isUndefined(null); // false ``` ### `isVoid` ```ts isVoid(value: unknown): value is void ``` ```ts isVoid(undefined); // true isVoid(0); // false ``` ### `isWeakMap` ```ts isWeakMap(value: unknown): value is WeakMap ``` Map instances return false. Narrows to `WeakMap`, since weak collections accept non-registered symbols as keys (ES2023). ```ts isWeakMap(new WeakMap()); // true isWeakMap(new Map()); // false ``` ### `isWeakSet` ```ts isWeakSet(value: unknown): value is WeakSet ``` Set instances return false. Narrows to `WeakSet`, since weak collections accept non-registered symbols as members (ES2023). ```ts isWeakSet(new WeakSet()); // true isWeakSet(new Set()); // false ``` ### `readonlyArrayIncludes` ```ts readonlyArrayIncludes(arr: readonly T[], elem: unknown): elem is T ``` ```ts const arr = ['a', 'b'] as const; readonlyArrayIncludes(arr, 'a'); // true readonlyArrayIncludes(arr, 'z'); // false ``` ## Types ### `Arrayable` ```ts type Arrayable = T | T[]; type Tags = Arrayable; // string | string[] ``` ### `Falsy` ```ts type Falsy = typeof Number.NaN | '' | 0n | false | null | undefined; type X = Falsy; // number | '' | 0n | false | null | undefined ``` ### `NestedNonNullable & string>` ```ts type NestedNonNullable & string> = NonNullable>; type User = { profile?: { name?: string } }; type Name = NestedNonNullable; // string ``` This type recursively traverses the object along the given path and applies `NonNullable` to the resulting type. It may fail for excessively deep property paths or in the presence of circular references. In such cases, consider using nested `NonNullable` definitions to avoid errors such as: - TS2589: Type instantiation is excessively deep and possibly infinite. - TS2615: Type of property 'XYZ' circularly references itself in mapped type. ### `Nil` ```ts type Nil = null | undefined; type X = Nil; // null | undefined ``` ### `Nilable` ```ts type Nilable = A | Nil; type Maybe = Nilable; // string | null | undefined ``` ### `NilableArray` ```ts type NilableArray = Nilable[]>; type Items = NilableArray; // (string | null | undefined)[] | null | undefined ``` ### `NonEmptyArray` ```ts type NonEmptyArray = [T, ...T[]]; type Ids = NonEmptyArray; // [number, ...number[]] ``` ### `NotNilable` ```ts type NotNilable = Exclude; type X = NotNilable; // string ``` ### `Nullable` ```ts type Nullable = A | null; type X = Nullable; // string | null ``` ### `NullableProperties` ```ts type NullableProperties = { [Key in keyof T]: Nullable }; type User = { name: string; age: number }; type X = NullableProperties; // { name: string | null; age: number | null } ``` ### `Truthy` ```ts type Truthy = Exclude; type X = Truthy; // string ``` ### `Undefinable` ```ts type Undefinable = A | undefined; type X = Undefinable; // string | undefined ``` ## Utilities ### `buildTimeDuration` ```ts buildTimeDuration(numberOfMilliseconds: number): TimeDuration ``` ```ts buildTimeDuration(3723000); // { hours: 1, minutes: 2, seconds: 3 } ``` ### `coerceToBoolean` ```ts coerceToBoolean(value: unknown): boolean ``` ```ts coerceToBoolean('true'); // true coerceToBoolean('false'); // false coerceToBoolean(0); // false ``` ### `dateOnlyISOString` ```ts dateOnlyISOString(date: unknown, { timeZone = defaultTimeZone }: DateOnlyStringOptions = {}): string ``` ```ts dateOnlyISOString(new Date(2011, 11, 15)); // '2011-12-15' ``` ### `dateOnlyString` ```ts dateOnlyString( date: unknown, { timeZone = defaultTimeZone, omitYear = false }: DateOnlyStringOptions = {}, ): string ``` ```ts dateOnlyString(new Date(2011, 11, 15)); // 'Dec 15, 2011' ``` ### `dateOnlyStringForSentence` ```ts dateOnlyStringForSentence( date: unknown, { timeZone = defaultTimeZone }: DateOnlyStringOptions = {}, ): string ``` ```ts dateOnlyStringForSentence(new Date(2011, 11, 15)); // 'December 15, 2011' ``` ### `dateOnlyStringNumeric` ```ts dateOnlyStringNumeric( date: unknown, { timeZone = defaultTimeZone }: DateOnlyStringOptions = {}, ): string ``` ```ts dateOnlyStringNumeric(new Date(2011, 11, 15)); // '12/15/2011' ``` ### `dateTimeRounded` ```ts dateTimeRounded(dateTime: Date, toISOString = true): string | Date | null ``` ```ts dateTimeRounded(new Date(2015, 10, 7, 0, 10)); // '2015-11-07T00:30:00.000Z' (in UTC) dateTimeRounded(new Date(2015, 10, 7, 0, 40), false); // new Date(2015, 10, 7, 1, 0) ``` ### `dateTimeString` ```ts dateTimeString( date: Date | number | string | null | undefined, { timeZone = defaultTimeZone, omitYear = false }: DateOnlyStringOptions = {}, ): string ``` ```ts dateTimeString(new Date(2011, 11, 15, 7, 30)); // 'Dec 15, 2011, 7:30 AM' ``` ### `dateTimeStringForSentence` ```ts dateTimeStringForSentence( date: unknown, { timeZone = defaultTimeZone }: DateOnlyStringOptions = {}, ): string ``` ```ts dateTimeStringForSentence(new Date(2011, 11, 15, 7, 30)); // 'December 15, 2011, 7:30 AM' ``` ### `dateTimeToDate` ```ts dateTimeToDate(dateTime: Partial | null | undefined): Date | null ``` ```ts dateTimeToDate({ year: 2015, month: 10, dayOfMonth: 7, hours: 4, minutes: 15 }); // new Date(2015, 10, 7, 4, 15) ``` ### `dateTimeToISO` ```ts dateTimeToISO(dateTime: Partial): string | null ``` ```ts dateTimeToISO({ year: 2015, month: 10, dayOfMonth: 7, hours: 4, minutes: 15 }); // '2015-11-07T04:15:00.000Z' (in UTC) ``` ### `dateToDateTime` ```ts dateToDateTime(date: Date | null | undefined): WistiaDateTimeObject | null ``` ```ts dateToDateTime(new Date(2011, 11, 15, 3, 10)); // { year: 2011, month: 11, dayOfMonth: 15, hours: 3, minutes: 10 } ``` ### `dateUTCOffset` ```ts dateUTCOffset(date: Date): string ``` ```ts dateUTCOffset(new Date(2011, 11, 15)); // '+00:00' (when run in UTC) ``` ### `dayOfWeekString` ```ts dayOfWeekString( date: Date | null, { timeZone = defaultTimeZone }: DateOnlyStringOptions = {}, ): string ``` ```ts dayOfWeekString(new Date(2011, 11, 15)); // 'Thursday' ``` ### `deepMerge` ```ts deepMerge[]>(...objects: T): DeepMergeResult ``` ```ts deepMerge({ a: { b: 1 }, c: [1] }, { a: { d: 2 }, c: [2] }); // { a: { b: 1, d: 2 }, c: [1, 2] } ``` ### `getObjectEntries` ```ts getObjectEntries>(obj: T): [keyof T, T[keyof T]][] ``` ```ts getObjectEntries({ a: 1, b: 2 }); // [['a', 1], ['b', 2]] ``` ### `getObjectKeys` ```ts getObjectKeys>(obj: T): (keyof T)[] ``` ```ts getObjectKeys({ a: 1, b: 2 }); // ['a', 'b'] ``` ### `getObjectValues` ```ts getObjectValues>(obj: T): T[keyof T][] ``` ```ts getObjectValues({ a: 1, b: 2 }); // [1, 2] ``` ### `isUrl` ```ts isUrl(value: unknown): boolean ``` ```ts isUrl('https://wistia.com'); // true isUrl('not a url'); // false ``` ### `mediaDurationString` ```ts mediaDurationString(numberOfMilliseconds: number): string ``` ```ts mediaDurationString(234230); // '3:54' mediaDurationString(23423000); // '6:30:23' ``` ### `millisecondsToDurationISOString` ```ts millisecondsToDurationISOString(numberOfMilliseconds: number): string ``` ```ts millisecondsToDurationISOString(33000); // 'PT33S' millisecondsToDurationISOString(3605000); // 'PT1H5S' ``` ### `monthDayStringNumeric` ```ts monthDayStringNumeric( date: unknown, { timeZone = defaultTimeZone }: DateOnlyStringOptions = {}, ): string ``` ```ts monthDayStringNumeric(new Date(2011, 11, 15)); // '12/15' ``` ### `parseDateString` ```ts parseDateString(str: string): Date | null ``` ```ts parseDateString('Jan 5, 2025'); // Date for 2025-01-05 at local midnight parseDateString('garbage'); // null ``` ### `sessionDurationString` ```ts sessionDurationString(numberOfMilliseconds: number): string ``` ```ts sessionDurationString(2342300); // '0:39:02' ``` ### `stripExtension` ```ts stripExtension(str: string): string ``` ```ts stripExtension('config.yml'); // 'config' stripExtension('example.test.ts'); // 'example.test' ``` ### `timeAgoString` ```ts timeAgoString( date: Date, { nowAnchor = new Date(), includeTime = true }: TimeAgoOptions = {}, ): string ``` ```ts timeAgoString(new Date(Date.now() - 30_000)); // '< 1 minute ago' ``` ### `timeOnlyString` ```ts timeOnlyString(date: unknown, { timeZone = defaultTimeZone }: DateOnlyStringOptions = {}): string ``` ```ts timeOnlyString(new Date(2011, 11, 15, 7, 30)); // '7:30 AM' ``` ### `tupleIncludes` ```ts tupleIncludes( tuple: TTuple, item: TItem, ): item is TTuple[number] ``` ```ts const tuple = ['x', 'y'] as const; tupleIncludes(tuple, 'x'); // true tupleIncludes(tuple, 'z'); // false ``` ## Patterns Filter out nullish values from a list, with narrowing: ```ts import { isNotNil } from '@wistia/type-guards'; const raw: (string | null | undefined)[] = ['a', null, 'b', undefined]; const clean: string[] = raw.filter(isNotNil); // ['a', 'b'] ``` Narrow a parameter before using it: ```ts import { isString, isNonEmptyArray } from '@wistia/type-guards'; function describe(value: unknown): string { if (isString(value)) return value.toUpperCase(); if (isNonEmptyArray(value)) return `${value.length} items`; return 'other'; } ``` Safely access an unknown object key: ```ts import { hasKey } from '@wistia/type-guards'; function readName(value: unknown): string | undefined { if (hasKey<'name', string>(value, 'name') && typeof value.name === 'string') { return value.name; } return undefined; } ```