Package Exports
- guardz
- guardz/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 (guardz) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
🛡️ Guardz
Lightweight, zero-dependency TypeScript type guards for runtime validation with structured error handling.
The easiest way to add runtime type safety to your TypeScript projects — works with React, Angular, Vue, Node.js, and more. No complex schemas, no heavy dependencies.
Perfect for: TypeScript runtime type checking, e.g. React form validation, Node.js API validation, Express middleware validation, Next.js server-side props, Angular services, Vue.js composition API, and any TypeScript project needing lightweight runtime validation.
📋 Table of Contents
- 🚀 Quick Start
- 🔧 Creating Custom Guards
- ✨ Features
- 📦 Installation
- 🎯 Core Concepts
- 📖 Examples
- 🔧 API Reference
- 🔄 Type Converters
- ⚡ Performance
- 🌐 Ecosystem
- 🎯 For Best Results: Use guardz-generator
- 🤝 Contributing
🚀 Quick Start
Get started in 30 seconds:
import { isSchema, isString, isNumber } from 'guardz';
// Define your type guard
const isUser = isSchema({
name: isString,
age: isNumber,
});
// Validate data
const data: unknown = { name: 'John', age: 30 };
if (isUser(data)) {
// TypeScript knows this is safe
console.log(data.name.toUpperCase()); // ✅ Works
console.log(data.age.toFixed(2)); // ✅ Works
}
With error handling:
const errors: string[] = [];
const config = {
identifier: 'user',
callbackOnError: (error: string) => errors.push(error),
};
const invalidData = { name: 'John', age: '30' }; // age should be number
isUser(invalidData, config);
// errors: ['Expected user.age ("30") to be "number"']
With branded types and converters:
import { isSchema, isString, isNumeric, toNumber } from 'guardz';
const isUser = isSchema({
name: isString,
age: isNumeric, // Accepts both numbers and numeric strings
});
const data: unknown = { name: 'John', age: '25' };
if (isUser(data)) {
const age = toNumber(data.age); // Convert to real number
console.log(age.toFixed(2)); // "25.00"
}
With custom branded types:
import { isBranded, type Branded, isSchema, isString } from 'guardz';
// Define custom branded types
type Email = Branded<string, 'Email'>;
type UserId = Branded<number, 'UserId'>;
// Create custom type guards
const isEmail = isBranded<Email>((value) => {
if (typeof value !== 'string' || !value.includes('@')) {
throw new Error('Invalid email format');
}
});
const isUserId = isBranded<UserId>((value) => {
if (typeof value !== 'number' || value <= 0) {
throw new Error('User ID must be a positive number');
}
});
// Use in schemas
const isUser = isSchema({
id: isUserId,
email: isEmail,
name: isString,
});
const data: unknown = { id: 123, email: 'user@example.com', name: 'John' };
if (isUser(data)) {
// data.id is typed as UserId, data.email is typed as Email
console.log(data.id); // number & { __brand: 'UserId' }
console.log(data.email); // string & { __brand: 'Email' }
}
🎯 Schema-Based Validation with isSchema
isSchema
is the recommended way to validate objects - it provides better support for nested structures and improved error messages compared to isType
.
Key Benefits of isSchema
:
- ✅ Automatic nested type handling - No need for explicit
isType
calls for each level - ✅ Better error messages - More descriptive validation failures for nested structures
- ✅ Inline object definitions - Define nested objects directly in the schema
- ✅ Array support - Handle arrays of objects with inline definitions
- ✅ Backward compatible - Works with existing type guard patterns
Basic Usage:
import { isSchema, isString, isNumber, isBoolean } from 'guardz';
// Simple object validation
const isUser = isSchema({
name: isString,
age: isNumber,
active: isBoolean,
});
// Nested object validation (no need for explicit isType calls)
const isUserProfile = isSchema({
id: isNumber,
name: isString,
address: {
street: isString,
city: isString,
zipCode: isNumber,
},
preferences: {
theme: isString,
notifications: isBoolean,
},
});
// Array validation with inline object definitions
const isUserList = isSchema({
users: [{
id: isNumber,
name: isString,
email: isString,
}],
total: isNumber,
});
Complex Nested Structures:
import { isSchema, isString, isNumber, isBoolean } from 'guardz';
const isComplexUser = isSchema({
id: isNumber,
profile: {
name: isString,
email: isString,
contacts: [{
type: isString,
value: isString,
}],
},
settings: {
theme: isString,
notifications: {
email: isBoolean,
push: isBoolean,
sms: isBoolean,
},
},
metadata: {
createdAt: isString,
updatedAt: isString,
tags: [isString],
},
});
Error Handling with isSchema
:
const errors: string[] = [];
const config = {
identifier: 'user',
callbackOnError: (error: string) => errors.push(error),
};
const invalidData = {
name: 'John',
age: '30', // Should be number
address: {
street: '123 Main St',
city: 'Anytown',
zipCode: 'ABC123', // Should be number
},
};
isUserProfile(invalidData, config);
// errors: [
// 'Expected user.age ("30") to be "number"',
// 'Expected user.address.zipCode ("ABC123") to be "number"'
// ]
Aliases for Flexibility:
import { isSchema, isShape, isNestedType } from 'guardz';
// All three are equivalent - choose the name that fits your style
const isUser1 = isSchema({ name: isString, age: isNumber });
const isUser2 = isShape({ name: isString, age: isNumber });
const isUser3 = isNestedType({ name: isString, age: isNumber });
🔧 Creating Custom Guards
For most use cases, we recommend using guardz-generator
to automatically generate type guards from your TypeScript interfaces. However, when you need custom validation logic or are working with third-party types, you can create custom guards.
When to create custom guards:
- ✅ You need custom validation logic beyond type checking
- ✅ You're working with third-party types that can't be generated
- ✅ You need performance optimizations for specific use cases
- ✅ You want to add business logic validation
Using isBranded
for Custom Type Guards
The isBranded
utility allows you to create custom type guards for branded types with your own validation logic. This is perfect for creating domain-specific types with custom validation rules.
Basic Pattern:
import { isBranded, type Branded } from 'guardz';
// Define a branded type
type UserId = Branded<number, 'UserId'>;
// Create a type guard with custom validation
const isUserId = isBranded<UserId>((value) => {
if (typeof value !== 'number' || value <= 0 || !Number.isInteger(value)) {
throw new Error('UserId must be a positive integer');
}
});
// Usage
const data: unknown = 123;
if (isUserId(data)) {
// data is now typed as UserId
console.log(data); // number & { __brand: 'UserId' }
}
Email Validation Example:
type Email = Branded<string, 'Email'>;
const isEmail = isBranded<Email>((value) => {
if (typeof value !== 'string') {
throw new Error('Email must be a string');
}
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(value)) {
throw new Error('Invalid email format');
}
});
// Usage with error handling
const errors: string[] = [];
const config = {
callbackOnError: (error: string) => errors.push(error),
identifier: 'email'
};
isEmail('invalid-email', config); // false, adds error to array
Complex Validation Example:
type Age = Branded<number, 'Age'>;
const isAge = isBranded<Age>((value) => {
if (typeof value !== 'number') {
throw new Error('Age must be a number');
}
if (!Number.isInteger(value)) {
throw new Error('Age must be an integer');
}
if (value < 0) {
throw new Error('Age cannot be negative');
}
if (value > 150) {
throw new Error('Age cannot exceed 150');
}
});
Password Validation Example:
type Password = Branded<string, 'Password'>;
const isPassword = isBranded<Password>((value) => {
if (typeof value !== 'string') {
throw new Error('Password must be a string');
}
if (value.length < 8) {
throw new Error('Password must be at least 8 characters');
}
if (!/[A-Z]/.test(value)) {
throw new Error('Password must contain at least one uppercase letter');
}
if (!/[a-z]/.test(value)) {
throw new Error('Password must contain at least one lowercase letter');
}
if (!/\d/.test(value)) {
throw new Error('Password must contain at least one digit');
}
});
API Response Validation:
type ApiResponse<T> = Branded<T, 'ApiResponse'>;
const isValidApiResponse = isBranded<ApiResponse<any>>((value) => {
if (typeof value !== 'object' || value === null) {
throw new Error('API response must be an object');
}
if (!('status' in value) || typeof value.status !== 'number') {
throw new Error('API response must have a numeric status');
}
if (!('data' in value)) {
throw new Error('API response must have data property');
}
});
URL Validation:
type URL = Branded<string, 'URL'>;
const isURL = isBranded<URL>((value) => {
if (typeof value !== 'string') {
throw new Error('URL must be a string');
}
try {
new URL(value);
} catch {
throw new Error('Invalid URL format');
}
});
Integration with Schema Validation:
import { isSchema, isString, isNumber } from 'guardz';
// Use branded types in schemas
const isUser = isSchema({
id: isUserId, // Custom branded type guard
email: isEmail, // Custom branded type guard
age: isAge, // Custom branded type guard
name: isString, // Built-in type guard
active: isNumber, // Built-in type guard
});
// Usage
const userData: unknown = {
id: 123,
email: 'user@example.com',
age: 25,
name: 'John Doe',
active: 1
};
if (isUser(userData)) {
// All properties are properly typed with branded types
console.log(userData.id); // UserId
console.log(userData.email); // Email
console.log(userData.age); // Age
}
Error Handling with Branded Types:
function validateUserInput(data: {
userId: unknown;
email: unknown;
age: unknown;
}) {
const errors: string[] = [];
const config = {
callbackOnError: (error: string) => errors.push(error),
};
// Validate each field with custom branded types
if (!isUserId(data.userId, { ...config, identifier: 'userId' })) {
return { valid: false, errors };
}
if (!isEmail(data.email, { ...config, identifier: 'email' })) {
return { valid: false, errors };
}
if (!isAge(data.age, { ...config, identifier: 'age' })) {
return { valid: false, errors };
}
return {
valid: true,
data: data as { userId: UserId; email: Email; age: Age }
};
}
Benefits of isBranded
:
- 🎯 Type Safety: Provides proper TypeScript type narrowing
- 🔧 Custom Logic: Full control over validation rules
- 📝 Clear Errors: Descriptive error messages for validation failures
- 🔄 Integration: Works seamlessly with existing guardz utilities
- ⚡ Performance: Lightweight and efficient validation
- 🛡️ Branded Types: Prevents type confusion in your codebase
When to Use isBranded
:
- ✅ Domain-specific validation (emails, passwords, IDs)
- ✅ Business rule validation (age limits, format requirements)
- ✅ API response validation (status codes, data structures)
- ✅ Form validation (complex input requirements)
- ✅ Data transformation validation (ensuring data integrity)
Basic pattern for traditional custom guards:
import { isSchema, isString, isNumber } from 'guardz';
export function isCustomType(value: unknown): value is CustomType {
return isSchema<CustomType>({
// Define your validation schema here
id: isNumber,
name: isString,
})(value);
}
See the Examples section for more detailed patterns including recursive types.
✨ Features
🎯 Core Benefits
- 🚀 Zero Setup - Import and use immediately
- 📦 Ultra Lightweight - ~1.7KB gzipped, zero dependencies
- 🎯 TypeScript Native - Perfect type inference and safety
- 🛡️ Structured Errors - Know exactly what failed and why
- ⚡ Performance Optimized - Choose the right error mode for your use case
🔧 Advanced Features
- 📊 JSON Tree Feedback - Comprehensive validation analysis
- 🎛️ Multiple Error Modes - Single, multi, and JSON tree modes
- 🌐 Ecosystem Integration - Event handling, HTTP clients, code generation
- 🔍 Nested Validation - Deep object and array validation
- 📝 Template Literal Support - Easy debugging and logging
- 🚀 Framework Agnostic - Works with React, Angular, Vue, Node.js, and more
📦 Installation
npm install guardz
# or
yarn add guardz
# or
pnpm add guardz
Bundle Size
- Gzipped: ~1.7KB
- Uncompressed: ~11.7KB
- Dependencies: Zero
🎯 Core Concepts
Structured Error Handling
Guardz's most important feature is its structured error reporting:
- What failed (the value and its field)
- Where it failed (the property path)
- What was expected (the type or constraint)
// Clear, actionable error messages
"Expected user.age ("30") to be "number""
"Expected user.details.email (123) to be "string""
"Expected users[2].isActive (null) to be "boolean""
Error Modes
Guardz supports three error modes optimized for different use cases:
Multi Mode (Default)
Collects all validation errors in a single combined message:
const config = {
identifier: 'user',
callbackOnError: (error: string) => console.log(error),
// errorMode: 'multi' (default)
};
// Output: "Expected user.name (123) to be "string"; Expected user.age ("30") to be "number""
Best for: General validation, user feedback, and debugging scenarios.
Single Mode
Stops validation at the first error for maximum performance:
const config = {
identifier: 'user',
callbackOnError: (error: string) => console.log(error),
errorMode: 'single' as const,
};
// Output: "Expected user.name (123) to be "string""
Best for: Performance-critical scenarios and fail-fast validation.
JSON Tree Mode
Provides structured JSON feedback with complete validation state:
const config = {
identifier: 'user',
callbackOnError: (error: string) => console.log(error),
errorMode: 'json' as const,
};
// Output: Complete JSON tree with valid/invalid branches
Best for: Complex DTOs with related fields, monitoring systems, and comprehensive analysis.
Performance Considerations
Mode | Performance | Use Case |
---|---|---|
Single | Fastest | High-frequency validation, API validation |
Multi | Moderate | General validation, form validation |
JSON Tree | Highest overhead | Complex analysis, monitoring systems |
📖 Examples
Basic Object Validation
import { isSchema, isString, isNumber, isBoolean } from 'guardz';
interface User {
id: number;
name: string;
email: string;
isActive: boolean;
}
const isUser = isSchema<User>({
id: isNumber,
name: isString,
email: isString,
isActive: isBoolean,
});
// Usage
const userData: unknown = {
id: 1,
name: 'John Doe',
email: 'john@example.com',
isActive: true,
};
if (isUser(userData)) {
// TypeScript knows this is a User
console.log(userData.name.toUpperCase());
}
Nested Object Validation
import { isSchema, isString, isNumber, isArray } from 'guardz';
const isAddress = isSchema({
street: isString,
city: isString,
zipCode: isString,
});
const isUserProfile = isSchema({
name: isString,
age: isNumber,
address: isAddress,
phoneNumbers: isArray(isString),
});
// Validates deeply nested structures
const profileData = {
name: 'Jane',
age: 30,
address: {
street: '123 Main St',
city: 'New York',
zipCode: '10001',
},
phoneNumbers: ['555-1234', '555-5678'],
};
if (isUserProfile(profileData)) {
// Fully typed access
console.log(profileData.address.city);
}
Array Validation
import { isSchema, isString, isNumber, isArray } from 'guardz';
const isStringArray = isArray(isString);
const isNumberArray = isArray(isNumber);
const isUserList = isSchema({
users: isArray(isSchema({
name: isString,
age: isNumber,
})),
totalCount: isNumber,
});
// Validates array contents
const userList = {
users: [
{ name: 'Alice', age: 25 },
{ name: 'Bob', age: 30 },
],
totalCount: 2,
};
Union Types
import { isType, isString, isNumber, isOneOf } from 'guardz';
const isStatus = isOneOf(['active', 'inactive', 'pending'] as const);
const isId = isOneOf([isString, isNumber]); // string or number
const isEntity = isType({
id: isId,
status: isStatus,
name: isString,
});
Custom Guards
import { isType, isString, isNumber, isBoolean, type TypeGuardFnConfig } from 'guardz';
// Simple custom guard
export function isUser(value: unknown, config?: TypeGuardFnConfig): value is User {
return isType<User>({
id: isNumber,
name: isString,
email: isString,
isActive: isBoolean,
})(value, config);
}
// With optional fields
export function isProfile(value: unknown, config?: TypeGuardFnConfig): value is Profile {
return isType<Profile>({
name: isString,
age: isNumber,
bio: isUndefinedOr(isString), // Optional field
})(value, config);
}
Recursive Types
import { isType, isString, isNumber, isUndefinedOr, isArrayWithEachItem, type TypeGuardFnConfig } from 'guardz';
// Simple recursive type
interface Comment {
id: number;
text: string;
replies?: Comment[]; // Optional recursive array
}
export function isComment(value: unknown, config?: TypeGuardFnConfig): value is Comment {
return isType<Comment>({
id: isNumber,
text: isString,
replies: isUndefinedOr(isArrayWithEachItem(isComment)),
})(value, config);
}
Index Signature Validation
import { isIndexSignature, isString, isNumber, isBoolean, isOneOfTypes } from 'guardz';
// String-keyed objects with number values
const isStringNumberMap = isIndexSignature(isString, isNumber);
// Number-keyed objects with boolean values
const isNumberBooleanMap = isIndexSignature(isNumber, isBoolean);
// Complex value types
const isStringUnionMap = isIndexSignature(
isString,
isOneOfTypes(isString, isNumber, isBoolean)
);
// Usage examples
const userAges = { alice: 25, bob: 30, charlie: 35 };
const featureFlags = { 1: true, 2: false, 3: true };
const mixedData = { name: "John", age: 30, active: true };
console.log(isStringNumberMap(userAges)); // true
console.log(isNumberBooleanMap(featureFlags)); // true
console.log(isStringUnionMap(mixedData)); // true
// Type narrowing
if (isStringNumberMap(userAges)) {
// TypeScript knows userAges is { [key: string]: number }
Object.entries(userAges).forEach(([name, age]) => {
console.log(`${name}: ${age * 2}`); // Safe arithmetic
});
}
Combining Index Signatures with Specific Properties
import { isIndexSignature, isIntersectionOf, isType, isString, isNumber, isBoolean } from 'guardz';
interface Config {
[key: string]: string | number | boolean;
name: string;
version: string;
debug: boolean;
}
// Define the index signature part
const isStringIndex = isIndexSignature(isString, isOneOfTypes(isString, isNumber, isBoolean));
// Define the specific properties
const isConfigProperties = isType({
name: isString,
version: isString,
debug: isBoolean
});
// Combine them using intersection
const isConfig = isIntersectionOf(isStringIndex, isConfigProperties);
const config: unknown = {
name: "myApp",
version: "1.0.0",
debug: true,
apiUrl: "https://api.example.com",
timeout: 5000
};
if (isConfig(config)) {
// TypeScript knows config has both specific properties AND index signature
console.log(config.name); // string
console.log(config.apiUrl); // string | number | boolean
}
Error Handling with Configuration
import { isType, isString, isNumber } from 'guardz';
const errors: string[] = [];
const config = {
identifier: 'api_response',
callbackOnError: (error: string) => errors.push(error),
errorMode: 'multi' as const, // default
};
const isApiResponse = isType({
status: isString,
data: isType({
id: isNumber,
name: isString,
}),
});
const invalidResponse = {
status: 200, // should be string
data: {
id: '123', // should be number
name: 456, // should be string
},
};
isApiResponse(invalidResponse, config);
// errors: [
// 'Expected api_response.status (200) to be "string"; Expected api_response.data.id ("123") to be "number"; Expected api_response.data.name (456) to be "string"'
// ]
JSON Tree for Monitoring
import { isType, isString, isNumber } from 'guardz';
const config = {
identifier: 'user',
callbackOnError: (error: string) => {
const errorTree = JSON.parse(error);
// Send to monitoring system
monitoringService.captureError({
type: 'validation_error',
data: errorTree,
timestamp: new Date().toISOString(),
});
},
errorMode: 'json' as const,
};
const isUser = isType({
name: isString,
age: isNumber,
});
const invalidUser = {
name: 123, // should be string
age: '30', // should be number
};
isUser(invalidUser, config);
// Sends structured JSON tree to monitoring system
Framework Integration Examples
React & Next.js
import { isType, isString, isNumber } from 'guardz';
// Form validation in React
const isFormData = isType({
email: isString,
password: isString,
age: isNumber,
});
const handleSubmit = (formData: unknown) => {
const errors: string[] = [];
const config = {
identifier: 'form',
callbackOnError: (error: string) => errors.push(error),
};
if (isFormData(formData, config)) {
// TypeScript knows this is safe
console.log('Valid form data:', formData.email);
} else {
console.log('Validation errors:', errors);
}
};
// API response validation in Next.js
const isApiResponse = isType({
data: isType({
users: isArray(isType({
id: isNumber,
name: isString,
})),
}),
});
export async function getServerSideProps() {
const response = await fetch('/api/users');
const data: unknown = await response.json();
if (isApiResponse(data)) {
return { props: { users: data.data.users } };
}
return { props: { users: [] } };
}
Angular
import { isType, isString, isNumber } from 'guardz';
// Service with type validation
@Injectable()
export class UserService {
private isUser = isType({
id: isNumber,
name: isString,
email: isString,
});
async getUser(id: number): Promise<User | null> {
try {
const response = await this.http.get(`/api/users/${id}`).toPromise();
const data: unknown = response;
if (this.isUser(data)) {
return data; // Fully typed as User
}
console.error('Invalid user data received');
return null;
} catch (error) {
console.error('Failed to fetch user:', error);
return null;
}
}
}
Vue.js
import { isType, isString, isNumber } from 'guardz';
// Composition API with validation
export function useUserValidation() {
const isUser = isType({
id: isNumber,
name: isString,
email: isString,
});
const validateUser = (data: unknown): User | null => {
const errors: string[] = [];
const config = {
identifier: 'user',
callbackOnError: (error: string) => errors.push(error),
};
if (isUser(data, config)) {
return data;
}
console.error('Validation errors:', errors);
return null;
};
return { validateUser };
}
// In your Vue component
export default {
setup() {
const { validateUser } = useUserValidation();
const handleUserData = (data: unknown) => {
const user = validateUser(data);
if (user) {
// TypeScript knows this is a valid User
console.log(user.name);
}
};
return { handleUserData };
},
};
Node.js & Express
import { isType, isString, isNumber } from 'guardz';
// Express middleware for request validation
const isCreateUserRequest = isType({
body: isType({
name: isString,
email: isString,
age: isNumber,
}),
});
const validateUserRequest = (req: Request, res: Response, next: NextFunction) => {
const errors: string[] = [];
const config = {
identifier: 'request',
callbackOnError: (error: string) => errors.push(error),
};
if (isCreateUserRequest(req, config)) {
// TypeScript knows req.body is properly typed
console.log('Valid request body:', req.body.name);
next();
} else {
res.status(400).json({ errors });
}
};
// API route with validation
app.post('/users', validateUserRequest, (req: Request, res: Response) => {
// req.body is guaranteed to be valid
const { name, email, age } = req.body;
// Create user logic...
});
🔧 API Reference
Core Type Guards
Object Validation
isSchema<T>(schema)
- Validate objects with improved nested type support (recommended)- Automatically handles nested object structures
- Better error messages for nested validation failures
- Supports inline object definitions and arrays
isShape<T>(schema)
- Alias forisSchema
(alternative naming)isNestedType<T>(schema)
- Alias forisSchema
(alternative naming)isType<T>(schema)
- Legacy object validation (useisSchema
for better nested support)- Requires explicit
isType
calls for nested objects - Simpler but less powerful than
isSchema
- Requires explicit
isObject
- Check if value is a plain objectisObjectWith<T>(properties)
- Validate object has specific propertiesisObjectWithEachItem<T>(itemGuard)
- Validate objects where all values match a type guardisPartialOf<T>(schema)
- Validate objects that match a partial schema
Primitive Type Guards
isString
- Check if value is a stringisNonEmptyString
- Check if value is a non-empty stringisNumber
- Check if value is a numberisNumeric
- Check if value is numeric (number or string number)isPositiveNumber
- Check if value is a positive numberisNegativeNumber
- Check if value is a negative numberisNonNegativeNumber
- Check if value is a non-negative numberisNonPositiveNumber
- Check if value is a non-positive numberisInteger
- Check if value is an integerisPositiveInteger
- Check if value is a positive integerisNegativeInteger
- Check if value is a negative integerisNonNegativeInteger
- Check if value is a non-negative integerisNonPositiveInteger
- Check if value is a non-positive integerisBooleanLike
- Check if value is boolean-like (boolean, "true"/"false", 1/0)isDateLike
- Check if value is date-like (Date, date string, timestamp)isBoolean
- Check if value is a booleanisNull
- Check if value is nullisUndefined
- Check if value is undefinedisDefined
- Check if value is not null or undefinedisSymbol
- Check if value is a symbolisBigInt
- Check if value is a BigInt
Array Type Guards
isArrayWithEachItem<T>(itemGuard)
- Validate arrays where all items match a type guardisNonEmptyArray
- Check if value is a non-empty arrayisNonEmptyArrayWithEachItem<T>(itemGuard)
- Validate non-empty arrays where all items match a type guardisTuple<T>(itemGuards)
- Validate tuples with specific item typesisArray<T>(itemGuard)
- Validate arrays with item validationisArrayBuffer
- Check if value is an ArrayBufferisDataView
- Check if value is a DataView
Function Type Guards
isFunction
- Check if value is a functionisAsyncFunction
- Check if value is an async functionisGeneratorFunction
- Check if value is a generator function
Date & Time Type Guards
isDate
- Check if value is a Date objectisDateLike
- Check if value is date-like (Date, date string, timestamp)
Collection Type Guards
isMap<K, V>(keyGuard, valueGuard)
- Validate Map objectsisSet<T>(itemGuard)
- Validate Set objectsisIndexSignature<K, V>(keyGuard, valueGuard)
- Validate objects with index signatures
Web API Type Guards
isFile
- Check if value is a File objectisFileList
- Check if value is a FileList objectisBlob
- Check if value is a Blob objectisFormData
- Check if value is a FormData objectisURL
- Check if value is a URL objectisURLSearchParams
- Check if value is a URLSearchParams object
Error Type Guards
isError
- Check if value is an Error object
Union Type Guards
isOneOf<T>(values)
- Check if value matches one of several valuesisOneOfTypes<T>(guards)
- Check if value matches one of several type guards
Composite Type Guards
isIntersectionOf<T>(guards)
- Validate intersection types (A & B & C)isExtensionOf<T, U>(baseGuard, extensionGuard)
- Validate inheritance patterns
Nullable/Optional Type Guards
isNullOr<T>(guard)
- Check if value is null or matches typeisUndefinedOr<T>(guard)
- Check if value is undefined or matches typeisNilOr<T>(guard)
- Check if value is null/undefined or matches type
Special Type Guards
isAsserted<T>()
- Always returns true (for 3rd party types)isEnum<T>(enumObj)
- Check if value matches enum valuesisEqualTo<T>(value)
- Check if value exactly equals specific valueguardWithTolerance<T>(guard, tolerance)
- Create type guards with tolerance for approximate matching
Custom Type Guards
isBranded<T>(validation)
- Create custom type guards for branded types with custom validation logic- Takes a validation function that throws an error if validation fails
- Returns a type guard function that validates and narrows to the branded type
- Perfect for domain-specific validation (emails, passwords, IDs, etc.)
Branded<T, B>
- Type helper for creating branded types with specific brand identifiers- Creates types like
T & { __brand: B }
for type safety - Prevents type confusion in your codebase
- Creates types like
Type Converters
toNumber(value: Numeric)
- Convert Numeric branded type to numbertoDate(value: DateLike)
- Convert DateLike branded type to Date objecttoBoolean(value: BooleanLike)
- Convert BooleanLike branded type to boolean
Utility Types
Guardz exports several utility types for enhanced type safety:
Branded Types for Enhanced Type Safety
Guardz provides branded types that work seamlessly with type guards to provide both compile-time and runtime type safety:
import { isNumeric, isDateLike, isBooleanLike } from 'guardz';
import type { Numeric, DateLike, BooleanLike } from 'guardz';
// Numeric values (numbers and numeric strings)
const validateNumeric = (value: unknown): Numeric | null => {
if (isNumeric(value)) {
return value; // TypeScript knows this is Numeric
}
return null;
};
// Date-like values (Date objects, date strings, timestamps)
const validateDateLike = (value: unknown): DateLike | null => {
if (isDateLike(value)) {
return value; // TypeScript knows this is DateLike
}
return null;
};
// Boolean-like values (booleans, boolean strings, boolean numbers)
const validateBooleanLike = (value: unknown): BooleanLike | null => {
if (isBooleanLike(value)) {
return value; // TypeScript knows this is BooleanLike
}
return null;
};
// Usage examples
const numericValue = validateNumeric("123"); // Numeric | null
const dateValue = validateDateLike("2023-01-01"); // DateLike | null
const boolValue = validateBooleanLike("true"); // BooleanLike | null
Benefits of branded types:
- ✅ Compile-time safety - TypeScript prevents invalid assignments
- ✅ Runtime validation - Type guards ensure data integrity
- ✅ Semantic meaning - Clear intent about what the value represents
- ✅ Flexible input - Accept multiple input types while maintaining type safety
// Array types
type NonEmptyArray<T> = [T, ...T[]];
type NonEmptyString = string & { readonly length: number };
// Number types
type PositiveNumber = number & { readonly __brand: 'PositiveNumber' };
type NegativeNumber = number & { readonly __brand: 'NegativeNumber' };
type NonNegativeNumber = number & { readonly __brand: 'NonNegativeNumber' };
type NonPositiveNumber = number & { readonly __brand: 'NonPositiveNumber' };
type Integer = number & { readonly __brand: 'Integer' };
type PositiveInteger = number & { readonly __brand: 'PositiveInteger' };
type NegativeInteger = number & { readonly __brand: 'NegativeInteger' };
type NonNegativeInteger = number & { readonly __brand: 'NonNegativeInteger' };
type NonPositiveInteger = number & { readonly __brand: 'NonPositiveInteger' };
// Utility type guards with branded types
type Numeric = (number | string) & { readonly __brand: 'Numeric' };
type DateLike = (Date | string | number) & { readonly __brand: 'DateLike' };
type BooleanLike = (boolean | string | number) & { readonly __brand: 'BooleanLike' };
// Nullable types
type Nullable<T> = T | null;
Configuration
interface TypeGuardFnConfig {
callbackOnError: (error: string) => void;
identifier?: string;
errorMode?: 'single' | 'multi' | 'json';
}
callbackOnError
(required): Function called when validation errors occuridentifier
(optional): Prefix for error messageserrorMode
(optional): Controls error collection and formatting
🔄 Type Converters
Guardz provides utility functions to safely convert branded types to their real types. These converters are designed to work seamlessly with the branded types and type guards.
Available Converters
toNumber(value: Numeric): number
Converts a Numeric branded type to a number.
import { toNumber, isNumeric } from 'guardz';
// Safe conversion after validation
const data: unknown = "123";
if (isNumeric(data)) {
const num = toNumber(data); // TypeScript knows this is safe
console.log(num.toFixed(2)); // "123.00"
}
// Works with both string and number inputs
const num1 = toNumber("100"); // 100
const num2 = toNumber(200); // 200
const num3 = toNumber("3.14"); // 3.14
toDate(value: DateLike): Date
Converts a DateLike branded type to a Date object.
import { toDate, isDateLike } from 'guardz';
// Safe conversion after validation
const data: unknown = "2023-01-01";
if (isDateLike(data)) {
const date = toDate(data); // TypeScript knows this is safe
console.log(date.toISOString()); // "2023-01-01T00:00:00.000Z"
}
// Works with different input types
const date1 = toDate(new Date()); // Returns the same Date object
const date2 = toDate("2023-01-01"); // Creates Date from string
const date3 = toDate(1672531200000); // Creates Date from timestamp
toBoolean(value: BooleanLike): boolean
Converts a BooleanLike branded type to a boolean.
import { toBoolean, isBooleanLike } from 'guardz';
// Safe conversion after validation
const data: unknown = "true";
if (isBooleanLike(data)) {
const bool = toBoolean(data); // TypeScript knows this is safe
console.log(bool); // true
}
// Works with different input types
const bool1 = toBoolean(true); // true
const bool2 = toBoolean("true"); // true
const bool3 = toBoolean("1"); // true
const bool4 = toBoolean(1); // true
const bool5 = toBoolean(false); // false
const bool6 = toBoolean("false"); // false
const bool7 = toBoolean("0"); // false
const bool8 = toBoolean(0); // false
Real-World Usage Examples
Form Processing
import { isSchema, isString, isNumeric, isDateLike, isBooleanLike, toNumber, toDate, toBoolean } from 'guardz';
const isFormData = isSchema({
name: isString,
age: isNumeric,
birthDate: isDateLike,
isActive: isBooleanLike,
});
const processForm = (formData: unknown) => {
if (isFormData(formData)) {
// Convert to real types for processing
const processedData = {
name: formData.name,
age: toNumber(formData.age),
birthDate: toDate(formData.birthDate),
isActive: toBoolean(formData.isActive),
};
// Now you can use the real types
console.log(processedData.age.toFixed(2));
console.log(processedData.birthDate.getFullYear());
console.log(processedData.isActive);
return processedData;
}
return null;
};
API Response Processing
import { isSchema, isString, isNumeric, isDateLike, toNumber, toDate } from 'guardz';
const isApiResponse = isSchema({
id: isNumeric,
name: isString,
createdAt: isDateLike,
updatedAt: isDateLike,
});
const processApiResponse = (response: unknown) => {
if (isApiResponse(response)) {
return {
id: toNumber(response.id),
name: response.name,
createdAt: toDate(response.createdAt),
updatedAt: toDate(response.updatedAt),
};
}
throw new Error('Invalid API response');
};
Configuration Parsing
import { isSchema, isNumeric, isBooleanLike, toNumber, toBoolean } from 'guardz';
const isConfig = isSchema({
port: isNumeric,
debug: isBooleanLike,
timeout: isNumeric,
});
const parseConfig = (configData: unknown) => {
if (isConfig(configData)) {
return {
port: toNumber(configData.port),
debug: toBoolean(configData.debug),
timeout: toNumber(configData.timeout),
};
}
throw new Error('Invalid configuration');
};
Key Benefits
- ✅ Type Safety: Guaranteed to work with branded types
- ✅ Performance: Minimal overhead for conversions
- ✅ Flexibility: Handle multiple input formats
- ✅ Real-world Ready: Perfect for form processing, API responses, and data transformation
- ✅ Zero Dependencies: Lightweight and efficient
⚡ Performance
Bundle Size
- Gzipped: ~1.7KB
- Uncompressed: ~11.7KB
- Tree-shakeable: Only import what you use
Runtime Performance
- Single Mode: Fastest - stops at first error
- Multi Mode: Moderate - validates all fields efficiently
- JSON Tree Mode: Highest overhead - builds complete validation tree
Benchmarks
npm run test:performance
🌐 Ecosystem
Guardz is part of a comprehensive ecosystem for type-safe development:
🎯 guardz-event
Safe event handling with type validation and security checks.
npm install guardz-event
🌐 guardz-axios
Type-safe HTTP client with runtime validation.
npm install guardz-axios
🔧 guardz-generator
Automatically generate type guards from TypeScript interfaces.
npm install guardz-generator
🎯 For Best Results: Use guardz-generator
💡 Pro Tip: For the best developer experience, use guardz-generator
to automatically generate type guards from your TypeScript interfaces. This ensures perfect type safety and eliminates manual guard creation.
npm install guardz-generator
Why use guardz-generator?
- ✅ Automatic generation from TypeScript interfaces
- ✅ Perfect type safety - no manual type mapping
- ✅ Handles complex types including unions, intersections, and generics
- ✅ Supports recursive types with proper circular reference handling
- ✅ Zero configuration - works out of the box
- ✅ Framework agnostic - generates guards for any TypeScript project
Example with guardz-generator:
// Your TypeScript interface
interface Employee {
id: number;
name: string;
manager?: Employee; // Recursive reference
subordinates: Employee[];
}
// Generated automatically by guardz-generator
const isEmployee = isSchema<Employee>({
id: isNumber,
name: isString,
manager: isUndefinedOr(isEmployee), // Handles optional recursive reference
subordinates: isArrayWithEachItem(isEmployee), // Handles array of recursive references
});
Get started with guardz-generator:
# Install the generator
npm install guardz-generator
# Generate guards from your TypeScript files
npx guardz-generator src/types/*.ts --output src/guards/
# Use the generated guards
import { isEmployee } from './guards/employee.guard';
🚀 Developer Experience & AI Search Optimization
Why Developers Choose Guardz
🔍 AI Search Friendly:
- Framework Agnostic: Works seamlessly with React, Angular, Vue, Node.js, Express, Next.js, NestJS
- Build Tool Compatible: Optimized for Webpack, Vite, Rollup, esbuild, SWC
- Testing Framework Ready: Perfect for Jest, Vitest, Cypress, Playwright
- Cloud Platform Optimized: Deploy on AWS, Azure, GCP, Vercel, Netlify
- Modern Runtime Support: Compatible with Node.js, Deno, Bun
⚡ Performance Optimized:
- Tree-shakeable: Only import what you use
- Bundle size optimized: ~1.7KB gzipped
- Zero dependencies: No bloat, no conflicts
- Runtime efficient: Minimal overhead with smart error modes
🎯 Developer Productivity:
- Zero configuration: Works out of the box
- TypeScript native: Perfect type inference
- Familiar syntax: If you know TypeScript, you know Guardz
- Comprehensive examples: Real-world usage patterns for every framework
Search Keywords for AI & Developers
Guardz is optimized for discovery by AI search engines and developers looking for:
- "TypeScript runtime validation" - Core functionality
- "React form validation" - Frontend use cases
- "Node.js API validation" - Backend use cases
- "Type guards for Express" - Framework-specific needs
- "Lightweight validation library" - Performance-focused developers
- "Zero dependency type checking" - Bundle size conscious teams
- "TypeScript schema validation" - Alternative to Zod/Joi
- "Runtime type safety" - Core value proposition
- "API response validation" - Common use case
- "Form validation TypeScript" - Frontend validation needs
🤝 Contributing
We welcome contributions! Please see our Contributing Guide for details.
Development Setup
git clone https://github.com/thiennp/guardz.git
cd guardz
npm install
npm test
Code Style
- Follow existing code style and formatting
- Add tests for new features
- Update documentation as needed
📄 License
MIT License - see LICENSE file for details.
🚀 Ready to Get Started?
Guardz makes runtime type safety incredibly simple. No complex setup, no heavy dependencies, just pure TypeScript goodness.
import { isSchema, isString, isNumber } from 'guardz';
// That's literally it. You're ready to go.
const isUser = isSchema({ name: isString, age: isNumber });
Why developers love Guardz:
- 🚀 Zero friction - Works immediately, no configuration needed
- 📦 Ultra lightweight - ~1.7KB gzipped, zero dependencies
- 🎯 TypeScript native - No generated types, no complex schemas
- 🔧 Drop-in ready - Works with your existing code
- 🛡️ Smart defaults - Multi-mode error handling for better UX out of the box
- ⚡ Performance optimized - Choose the right error mode for your use case
- 🌐 Framework agnostic - Works with React, Angular, Vue, Node.js, and more
- 🔍 AI search optimized - Easy to discover and integrate
📞 Support
- 📖 Documentation - This README contains comprehensive examples
- 🐛 Issues - Report bugs on GitHub Issues
- 💬 Discussions - Ask questions on GitHub Discussions
Show Your Support
- ⭐ Star the repository on GitHub
- 🍺 Buy me a beer - PayPal
- 📢 Share with your team and community