Package Exports
- isguard-ts
- isguard-ts/dist/index.js
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (isguard-ts) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
isguard-ts
A powerful typescript library that helps you build type guards quickly while maintaining type safety.
isguard-ts utilizes the typescript compiler to ensure that its type guards are aligned with the guarded type.
For example, when making a change to your type, isguard-ts will inform you to update your type guard as well.
Installation
npm install isguard-tsTable of Contents
- TypeGuard
- isType
- isOptional
- isMaybe
- isArray
- isLiteral
- isUnion
- isIntersection
- isRecord
- isPartialRecord
- isIndexRecord
- isLazy
- isTuple
- isEnum
- isSet
- isMap
- isInstanceof
- isRefine
- utility type guards
- generic types
- recursive types
- zod
Basic Usage
TypeGuard<T>
The most basic type - represents a type guard of T
type TypeGuard<T> = (value: unknown) => value is T;
isType
Helps you create type guards for types and interfaces
type Person = {
name: string;
age: number;
};
const isPerson = isType<Person>({
name: isString,
age: isNumber,
});
isPerson({ name: "Hello", age: 6 }); // trueBest Practice
Pass the generic type argument into
isType
Otherwise optional fields might have an unexpected behavior
isOptional
Helps you create type guards for optional (potentially undefined) types
isOptional(isNumber); // or isNumber.optional();
isMaybe
Helps you create type guards for nullable (potentially null) types
isMaybe(isNumber); // or isNumber.maybe();
isArray
Helps you create type guards for arrays
isArray(isBoolean); // or isBoolean.array();
isLiteral
Helps you create type guards for literals
const isHello = isLiteral("Hello");
const is12 = isLiteral(12);isLiteral can receive multiple values
const directions = ["up", "down", "left", "right"] as const;
type Direction = (typeof directions)[number];
const isDirection = isLiteral(...directions) satisfies TypeGuard<Direction>;Best Practice
Use the
satisfieskeyword on the result ofisLiteralwhen passing multiple values
This ensures the result is of the expected type
isUnion
Helps you create type guards for unions
type A = { a: number };
type B = { b: string };
type C = A | B;
const isA = isType<A>({ a: isNumber });
const isB = isType<B>({ b: isString });
isUnion(isA, isB) satisfies TypeGuard<C>; // or isA.or(isB);Best Practice
Use the
satisfieskeyword on the result ofisUnion
This ensures the result is of the expected type
isIntersection
Helps you create type guards for intersections
type A = { a: number };
type B = { b: string };
type C = A & B;
const isA = isType<A>({ a: isNumber });
const isB = isType<B>({ b: isString });
isIntersection(isA, isB) satisfies TypeGuard<C>; // or isA.and(isB);Best Practice
Use the
satisfieskeyword on the result ofisIntersection
This ensures the result is of the expected type
isRecord
Helps you create type guards for records
const timeUnits = ["second", "minute", "hour"] as const;
type TimeUnit = (typeof timeUnits)[number];
isRecord(timeUnits, isNumber);
// Record<TimeUnit, number>
isPartialRecord
Works just like isRecord but allows for undefined values
const timeUnits = ["second", "minute", "hour"] as const;
type TimeUnit = (typeof timeUnits)[number];
isPartialRecord(timeUnits, isNumber);
// Partial<Record<TimeUnit, number>>
isIndexRecord
Works just like isRecord but checks only the values and not the keys
isIndexRecord(isNumber); // or isNumber.indexRecord();
// Record<PropertyKey, number>
isLazy
Helps you lazy load a type guard. Useful for:
- Resolving undefined errors due to circular imports
- Creating type guards for recursive types
import { isPerson } from "./some-module";
const isPeople = isLazy(() => isPerson).array();In the example above isPerson, imported from ./some-module, might be undefined when isPeople is being created, due to circular imports. So isPerson.array() would throw an error. isLazy solves this issue by accessing isPerson only when needed.
isTuple
Helps you create type guards for tuples
type Row = [number, string?];
const isRow = isTuple<Row>([isNumber, isString.optional()]);
isRow([6, "Hello"]); // true
isRow([6]); // true
isRow(["Hello", "Bye"]); // falseBest Practice
Pass the generic type argument into
isTuple
Otherwise optional fields might have an unexpected behavior
isEnum
Helps you create type guards for enums
enum Direction {
up = 0,
down = 1,
left = 2,
right = 3,
}
const isDirection = isEnum(Direction);
isDirection(Direction.up); // true
isDirection(2); // true
isDirection("hello"); // false
isSet
Helps you create type guards for sets
isSet(isNumber); // or isNumber.set();
// Set<number>
isMap
Helps you create type guards for maps
isMap(isString, isBoolean);
// Map<string, boolean>
isInstanceof
Helps you create type guards for classes
abstract class Animal { }
class Dog extends Animal { }
const isAnimal = isInstanceof(Animal);
const isDog = isInstanceof(Dog);
isRefine
Helps you refine existing type guards. Can be used for:
- Branded types (like Email, PositiveNumber and more)
- Template literals (like `Bye ${string}`)
type Farewell = `Bye ${string}`;
const isFarewell = isRefine(isString, (value: string): value is Farewell => {
return value.startsWith("Bye ");
});Warning
using
isRefinecan be unsafe because it let's you implement potentially false logic
Use at your own risk.
Built-in Utility Type Guards
const isNumber: TypeGuard<number>;
const isBigint: TypeGuard<bigint>;
const isString: TypeGuard<string>;
const isBoolean: TypeGuard<boolean>;
const isSymbol: TypeGuard<symbol>;
const isFunction: TypeGuard<Function>;
const isPropertyKey: TypeGuard<PropertyKey>;
const isDate: TypeGuard<Date>;
const isRegExp: TypeGuard<RegExp>;
const isObject: TypeGuard<object>;
const isError: TypeGuard<Error>;
const isEvalError: TypeGuard<EvalError>;
const isRangeError: TypeGuard<RangeError>;
const isReferenceError: TypeGuard<ReferenceError>;
const isSyntaxError: TypeGuard<SyntaxError>;
const isTypeError: TypeGuard<TypeError>;
const isURIError: TypeGuard<URIError>;
const isNull: TypeGuard<null>;
const isUndefined: TypeGuard<undefined>;
const isNil: TypeGuard<null | undefined>;
const isTrue: TypeGuard<true>;
const isFalse: TypeGuard<false>;
const isUnknown: TypeGuard<unknown>;
const isNever: TypeGuard<never>;Advanced Usage
Generic Types
When creating type guards for generic types, you need to create your own TypeGuard generator
type ValueHolder<T> = {
value: T;
};
const isValueHolder = <T>(isValue: TypeGuard<T>) => {
return isType<ValueHolder<T>>({
value: isValue,
});
};
const isNumberHolder = isValueHolder(isNumber);
Recursive Types
One way to build recursive type guards is by using isLazy
type Json =
| number
| string
| boolean
| null
| Json[]
| { [key: string]: Json; };
const isJson: TypeGuard<Json> = isUnion(
isNumber,
isString,
isBoolean,
isNull,
isLazy(() => isJson).array(),
isLazy(() => isJson).indexRecord(),
);type Tree = {
value: number;
left?: Tree;
right?: Tree;
};
const isTree: TypeGuard<Tree> = isType<Tree>({
value: isNumber,
left: isLazy(() => isTree).optional(),
right: isLazy(() => isTree).optional(),
});There is a less recommended way using a getter
const isTree: TypeGuard<Tree> = isType<Tree>({
value: isNumber,
get left() {
return isTree.optional();
},
get right() {
return isTree.optional();
},
});Important
Annotate the recursive guard to avoid typescript errors
Plugins
Zod
Any TypeGuard has a .zod() method that returns a zod schema which represents the guarded type.
To use this feature you must have zod installed through npm. The supported versions of zod start with zod@3.20.0 and end with zod@5.0.0 (not included)
const ZodNumber = isNumber.zod(); // same as z.number()
type Person = {
name: string;
};
const isPerson = isType<Person>({
name: isString,
});
const ZodPerson = isPerson.zod(); // same as z.object({ name: z.string() })Important
The schema returned by
.zod()might not exactly represent the guarded type in certain edge cases.
For example:isNumber(NaN)returnstruewhilez.number()marksNaNas invalid.The differences vary between zod versions, but these are the most common
- Non finite numbers (
NaN, Infinity, -Infinity) are valid when usingisguard-tsbut invalid when usingzodzodignores symbol property keys whileisguard-tsdoesn't