Guardis is an unopinionated validation and type guard system that prioritizes using TypeScript types to define your validation logic. Let your types dictate validation rather than having your validation library dictate your types.
Use the included type guards to perform validation or quickly generate your own anywhere in your code, using your existing type definitions.
import { createTypeGuard, isString } from "jsr:@spudlabs/guardis";
// Use built-in guards
if (isString(userInput)) {
console.log(userInput.toUpperCase()); // TypeScript knows this is a string
}
// Create custom guards from your types
type User = { id: number; name: string };
const isUser = createTypeGuard<User>((val, { has }) =>
has(val, "id", Is.Number) && has(val, "name", Is.String) ? val : null
);- Type-First: Define TypeScript types first, validation follows
- Zero Dependencies: No runtime dependencies
- Multiple Modes: Basic, strict (throws), assert, optional, and notEmpty variants
- Helper Functions: Built-in utilities for object, array and tuple validation
- Extensible: Create custom guards, extend existing guards, and extend the core library
- Modular: Import only what you need
# Deno
deno install jsr:@spudlabs/guardis
# Node.js/npm
npx jsr add @spudlabs/guardis- Features
- Installation
- Quick Start
- Type Guard Modes
- Creating Custom Type Guards
- Extending Type Guards
- Specialized Modules
- Batch Creation
- Extending the Is Object
- Real-World Examples
- TypeScript Integration
Guardis provides type guards for all common JavaScript types:
import { Is } from "jsr:@spudlabs/guardis";
// Primitives
Is.String("hello"); // true
Is.Number(42); // true
Is.Boolean(true); // true
Is.Null(null); // true
Is.Undefined(undefined); // true
// Collections
Is.Array([1, 2, 3]); // true
Is.Object({ key: "value" }); // true
// Special types
Is.Date(new Date()); // true
Is.Function(() => {}); // true
Is.Iterable([1, 2, 3]); // true (arrays, sets, maps, etc.)
Is.Tuple([1, 2], 2); // true (array with exact length)
// JSON-safe types
Is.JsonValue({ a: 1, b: "text" }); // true
Is.JsonObject({ key: "value" }); // true
Is.JsonArray([1, "two", true]); // trueImport specific guards to keep bundles small:
import { isArray, isNumber, isString } from "jsr:@spudlabs/guardis";
if (isString(userInput)) {
console.log(userInput.trim());
}
if (isNumber(userInput)) {
return userInput * 10;
}
if (isArray(userValues)) {
return userValues.at(-1);
}Every type guard comes with multiple modes for different use cases:
if (Is.String(value)) {
// TypeScript knows value is a string here
console.log(value.toUpperCase());
}// Allows undefined values
Is.String.optional(value); // true for strings OR undefined
Is.Number.optional(undefined); // true
Is.Number.optional(42); // true
Is.Number.optional("hello"); // false// Rejects "empty" values (null, undefined, "", [], {})
Is.String.notEmpty("hello"); // true
Is.String.notEmpty(""); // false
Is.Array.notEmpty([1, 2, 3]); // true
Is.Array.notEmpty([]); // falseThrows an error if the predicate fails.
// Throws TypeError if validation fails
Is.String.strict(value);
// With custom error message
Is.Number.strict(value, "Expected a number for calculation");It's a requirement of the TypeScript language that all assertion functions have an explicit type annotation. For that reason, in order for TypeScript to recognize that any use of the variable after assertIsString can safely consider the value to be a string, you have to explicitly set the type to itself.
See microsoft/TypeScript#47945 for more information.
// For TypeScript assertion functions
const assertIsString: typeof Is.String.assert = Is.String.assert;
assertIsString(value);
console.log(value.toUpperCase()); // TypeScript now knows value is a stringUse createTypeGuard to build validators for your own types:
import { createTypeGuard } from "jsr:@spudlabs/guardis";
type Status = "pending" | "complete" | "failed";
const isStatus = createTypeGuard((val, { includes }) => {
const validStatuses: Status[] = ["pending", "complete", "failed"];
return isString(val) && includes(validStatuses, val) ? val : null;
});
// All modes available automatically
isStatus("pending"); // true
isStatus.strict("invalid"); // throws TypeError
isStatus.optional(undefined); // trueUse helper functions for object validation:
type User = {
id: number;
name: string;
email?: string; // optional property
};
const isUser = createTypeGuard<User>((val, { has, hasOptional }) => {
if (!Is.Object(val)) return null;
if (
has(val, "id", Is.Number) &&
has(val, "name", Is.String) &&
hasOptional(val, "email", Is.String)
) {
return val;
}
return null;
});const isExample = createTypeGuard((val, helpers) => {
const { has, hasOptional, tupleHas, includes } = helpers;
// Check required object property
has(obj, "key", Is.String);
// Check optional object property
hasOptional(obj, "optional", Is.Number); // { optional?: number | undefined }
// Check tuple element at specific index
tupleHas(tuple, 0, Is.String); // [string, ...unknown[]]
// Check if value is in array (for union types)
const colors = ["red", "blue", "green"] as const;
includes(colors, val); // "red" | "blue" | "green"
return val;
});The extend method allows you to build upon existing type guards by adding additional validation rules. This is particularly useful when you need to add constraints or refinements to a base type.
// Extend isString to validate email format
const isEmail = isString.extend((val) => {
return val.includes("@") && val.includes(".") ? val : null;
});
isEmail("user@example.com"); // true
isEmail("invalid-email"); // false
isEmail(123); // false (fails base validation)// Extend isNumber to create a percentage validator (0-100)
const isPercentage = isNumber.extend((val) => {
return val >= 0 && val <= 100 ? val : null;
});
isPercentage(50); // true
isPercentage(150); // false
isPercentage("50"); // false// Extend isString to validate phone numbers
const isPhoneNumber = isString.extend((val) => {
return /^\d{3}-\d{3}-\d{4}$/.test(val) ? val : null;
});
isPhoneNumber("555-123-4567"); // true
isPhoneNumber("invalid"); // falsetype User = { name: string; age: number };
const isUser = createTypeGuard<User>((val, { has }) => {
if (Is.Object(val) && has(val, "name", Is.String) && has(val, "age", Is.Number)) {
return val;
}
return null;
});
// Extend to only accept adults
const isAdult = isUser.extend((val) => {
return val.age >= 18 ? val : null;
});
isAdult({ name: "Alice", age: 25 }); // true
isAdult({ name: "Bob", age: 16 }); // falseExtensions can be chained to create increasingly specific validators:
const isPositiveNumber = isNumber.extend((val) =>
val > 0 ? val : null
);
const isPositiveInteger = isPositiveNumber.extend((val) =>
Number.isInteger(val) ? val : null
);
const isEvenPositiveInteger = isPositiveInteger.extend((val) =>
val % 2 === 0 ? val : null
);
isEvenPositiveInteger(10); // true
isEvenPositiveInteger(9); // false (not even)
isEvenPositiveInteger(3.5); // false (not integer)
isEvenPositiveInteger(-2); // false (not positive)Extended validators have access to the same helper functions as the base validators:
// Create a status type with specific allowed values
const validStatuses = ["active", "inactive", "pending"] as const;
const isStatus = isString.extend((val, { includes }) => {
if (includes(validStatuses, val)) {
return val as typeof validStatuses[number];
}
return null;
});
isStatus("active"); // true
isStatus("completed"); // falseExtended type guards support all the same modes as base type guards:
const isPositiveNumber = isNumber.extend((val) =>
val > 0 ? val : null
);
// Basic mode
isPositiveNumber(10); // true
// Strict mode (throws on failure)
isPositiveNumber.strict(10); // passes
isPositiveNumber.strict(-5); // throws TypeError
// Assert mode
const assertIsPositive: typeof isPositiveNumber.assert = isPositiveNumber.assert;
assertIsPositive(10); // passes
// assertIsPositive(-5); // throws
// Optional mode
isPositiveNumber.optional(10); // true
isPositiveNumber.optional(undefined); // true
isPositiveNumber.optional(-5); // falseGuardis includes specialized modules for domain-specific types:
import { isNativeURL, isRequest, isResponse } from "jsr:@spudlabs/guardis/http";
// Web API types
isNativeURL(new URL("https://example.com")); // true
isRequest(new Request("https://api.com")); // true
isResponse(new Response("data")); // true
// All modes available
isRequest.strict(value); // throws if not Request
isResponse.optional(value); // allows undefinedGenerate multiple type guards at once:
import { batch } from "jsr:@spudlabs/guardis";
const { isRed, isBlue, isGreen } = batch({
Red: (val) => val === "red" ? val : null,
Blue: (val) => val === "blue" ? val : null,
Green: (val) => val === "green" ? val : null,
});
// Automatic camelCase conversion
const { isUserRole, isAdminRole } = batch({
"user-role": (val) => val === "user" ? val : null,
AdminRole: (val) => val === "admin" ? val : null,
});
// All guards get full mode support
isRed.strict("blue"); // throws
isBlue.optional(undefined); // trueAdd your own type guards to the Is namespace:
import { extend, Is as BaseIs } from "jsr:@spudlabs/guardis";
// Create new Is object with custom guards
const Is = extend(BaseIs, {
Email: (val) => typeof val === "string" && val.includes("@") ? val : null,
PositiveNumber: (val) => typeof val === "number" && val > 0 ? val : null,
});
// Use built-in and custom guards together
Is.String("hello"); // built-in
Is.Email("user@domain.com"); // custom
Is.PositiveNumber(42); // custom
// All modes work with custom guards
Is.Email.strict(invalidEmail); // throws
Is.PositiveNumber.optional(undefined); // truetype ApiResponse<T> = {
success: boolean;
data?: T;
error?: string;
};
const isApiResponse = <T>(dataGuard: (val: unknown) => val is T) =>
createTypeGuard<ApiResponse<T>>((val, { has, hasOptional }) => {
if (!Is.Object(val)) return null;
if (
has(val, "success", Is.Boolean) &&
hasOptional(val, "data", dataGuard) &&
hasOptional(val, "error", Is.String)
) {
return val;
}
return null;
});
// Usage
const isUserResponse = isApiResponse(isUser);
if (isUserResponse(response)) {
console.log(response.data?.name); // TypeScript knows the shape
}type ContactForm = {
name: string;
email: string;
age: number;
newsletter: boolean;
};
const isContactForm = createTypeGuard<ContactForm>((val, { has }) => {
if (!Is.Object(val)) return null;
if (
has(val, "name", Is.String) &&
has(val, "email", (v) => Is.String(v) && v.includes("@") ? v : null) &&
has(val, "age", (v) => Is.Number(v) && v >= 0 ? v : null) &&
has(val, "newsletter", Is.Boolean)
) {
return val;
}
return null;
});
// Use in form handler
function handleSubmit(formData: unknown) {
try {
isContactForm.strict(formData, "Invalid form data");
// formData is now typed as ContactForm
saveContact(formData);
} catch (error) {
showError(error.message);
}
}Guardis is designed to work seamlessly with TypeScript:
- Type Narrowing: Guards narrow types in
ifstatements - Assertion Functions:
.assert()methods work as TypeScript assertions - Generic Support: Create guards for generic types
- Strict Typing: All guards are fully typed with proper inference
- Type Inference: Extract types from guards using the
_TYPEproperty
function processData(input: unknown) {
if (Is.Array(input)) {
// TypeScript knows input is unknown[]
input.forEach((item) => {/* ... */});
}
// Assertion style
const assertIsString: typeof Is.String.assert = Is.String.assert;
assertIsString(input);
// TypeScript knows input is string after this line
}Every type guard includes a _TYPE property that allows you to extract the guarded type for use in other parts of your code:
// Extract the type from a built-in guard
type StringType = typeof Is.String._TYPE; // string
type NumberType = typeof Is.Number._TYPE; // number
// Extract the type from a custom guard
type User = { id: number; name: string };
const isUser = createTypeGuard<User>((val, { has }) =>
has(val, "id", Is.Number) && has(val, "name", Is.String) ? val : null
);
// Use _TYPE to infer the type elsewhere
type UserType = typeof isUser._TYPE; // { id: number; name: string }
// Useful for creating related types
type UserArray = Array<typeof isUser._TYPE>;
type UserResponse = {
success: boolean;
user: typeof isUser._TYPE;
};
// Works with extended guards too
const isAdult = isUser.extend((val) => val.age >= 18 ? val : null);
type AdultType = typeof isAdult._TYPE; // inferred type from extension