@jordan-hall/typeguard
A TypeScript library providing type-safe handling of Result
types, pattern matching, and robust error handling utilities. Designed to enhance code reliability and readability by leveraging TypeScript's powerful type system.
Table of Contents
Features
- Result Types: Simplify error handling by encapsulating success and error states.
- Pattern Matching: Perform expressive and type-safe pattern matching on
Result
types. - Try/Catch Utilities: Advanced try/catch helpers for synchronous and asynchronous functions with type-safe error management.
- Type Guards and Predicates: Enhance pattern matching with custom type guards and predicate functions.
- Flexible Patterns: Supports literal, regex, predicate, object, array, and many patterns.
Installation (coming soon JSR)
You can install @jordan-hall/typeguard
via Pacakage Json:
bun install @jordan-hall/typeguard
npm install @jordan-hall/typeguard
yarn add @jordan-hall/typeguard
pnpm install @jordan-hall/typeguard
You can also use JSR (preferred). For import symbol view on JSR website
deno add jsr:@jordan-hall/@jordan-hall/typeguard
npx jsr add @jordanhall/@jordan-hall/typeguard
Usage
Result Types
Result<T, E>
represents either a successful computation Ok<T>
or a failed computation Err<E>
.
import { Ok, Err, Result } from '@jordan-hall/typeguard';
type User = { id: number; name: string };
type ValidationError = { field: string; message: string };
const success: Result<User, ValidationError> = Ok({ id: 1, name: "John Doe" });
const failure: Result<User, ValidationError> = Err({ field: "email", message: "Invalid email format" });
Pattern Matching
Perform pattern matching on Result
types for expressive and type-safe error handling.
import { match, when, _, Result } from '@jordan-hall/typeguard';
function getUser(id: number): Result<User, ValidationError> {
if (id === 0) return Err({ field: "id", message: "Invalid user ID" });
return Ok({ id, name: "Alice" });
}
const result = getUser(1);
const message = match(result,
{ type: 'Ok', pattern: when(user => user.id > 0), handler: user => `User ID: ${user.id}, Name: ${user.name}` },
{ type: 'Err', pattern: _, handler: error => `Error in field ${error.field}: ${error.message}` }
);
console.log(message);
Advanced Pattern Matching
Utilize type guards, predicates, and array patterns for complex matching scenarios.
import { match, when, guard, some, every, _ } from '@jordan-hall/typeguard';
interface Order {
id: string;
items: Array<{ name: string; price: number }>;
}
interface OrderError {
code: string;
message: string;
}
const processOrder = (order: Order): Result<Order, OrderError> => {
if (order.items.length === 0) return Err({ code: "NO_ITEMS", message: "Order must contain at least one item" });
return Ok(order);
};
const orderResult = processOrder({ id: "order123", items: [{ name: "Book", price: 10 }] });
const message = match(orderResult,
{ type: 'Ok', pattern: some(when(item => item.price > 5)), handler: order => `Order ${order.id} has expensive items` },
{ type: 'Err', pattern: { code: "NO_ITEMS" }, handler: error => `Order Error: ${error.message}` },
{ type: 'Ok', pattern: _, handler: order => `Order ${order.id} processed successfully` }
);
console.log(message);
Try/Catch Utilities
Simplify error handling with type-safe tryCatch
and tryCatchAsync
functions.
Synchronous tryCatch
import { tryCatch } from '@jordan-hall/typeguard/tryCatch';
const [error, result] = tryCatch(() => JSON.parse('{"key": "value"}'));
if (result)) {
console.log("Parsed value:", result);
} else {
console.error("Parsing error:", error);
}
Asynchronous tryCatchAsync
import { tryCatchAsyn } from '@jordan-hall/typeguard/tryCatch';
async function fetchData(): Promise<string> {
return "data";
}
const [error, data] = await tryCatchAsync(() => fetchData());
if (data) {
console.log("Fetched data:", data);
} else {
console.error("Fetching error:", error);
}
Utility Functions
Predicates and Guards
Create custom patterns using predicates and type guards.
import { when, guard, match, _, Result } from '@jordan-hall/typeguard';
interface Admin {
role: 'admin';
privileges: string[];
}
type User = { name: string; age: number } | Admin;
const isAdmin: guard<Admin> = (value): value is Admin => (value as Admin).role === 'admin';
const userResult: Result<User, string> = Ok({ name: "Bob", age: 25 });
const userMessage = match(userResult,
{ type: 'Ok', pattern: guard(isAdmin), handler: admin => `Admin with privileges: ${admin.privileges.join(", ")}` },
{ type: 'Ok', pattern: _, handler: user => `User: ${user.name}, Age: ${user.age}` },
{ type: 'Err', pattern: _, handler: error => `Error: ${error}` }
);
console.log(userMessage);
Array Patterns
Match arrays with some
or every
patterns.
import { some, every, when, match, _ } from '@jordan-hall/typeguard';
type Data = number[];
const data: Data = [1, 2, 3, 4];
const pattern = some(when(n => n > 3));
const matchResult = match(data,
{ type: 'Ok', pattern: pattern, handler: () => "At least one number is greater than 3" },
{ type: 'Ok', pattern: _, handler: () => "No number is greater than 3" }
);
console.log(matchResult);
API Reference
Types
-
Result<T, E>
Represents a computation that can either succeed with a value of type T
or fail with an error of type E
.
-
Ok<T, E>
Represents a successful computation containing a value of type T
.
-
Err<T, E>
Represents a failed computation containing an error of type E
.
Functions
Ok
Creates an Ok
result.
const Ok = <T, E>(value: T): Result<T, E> => ({ _tag: 'Ok', value });
Err
Creates an Err
result.
const Err = <T, E>(error: E): Result<T, E> => ({ _tag: 'Err', error });
match
Performs pattern matching on a Result
type.
function match<T, E, R>(
result: Result<T, E>,
...cases: Array<MatchCase<T, E, R>>
): R;
tryCatch
Executes a synchronous function with type-safe error handling.
function tryCatch<T, E = Error>(
fn: () => T,
acceptableErrors?: (new (...args: any[]) => AcceptableErrors<E>)[]
): [ExtractError<E>, null], [null, T];
tryCatchAsync
Executes an asynchronous function with type-safe error handling.
async function tryCatchAsync<T, E = Error>(
fn: () => Promise<T>,
acceptableErrors?: (new (...args: any[]) => AcceptableErrors<E>)[]
): Promise<[ExtractError<E>, null], [null, T]>
when
Creates a predicate pattern.
const when = <T>(predicate: (value: T) => boolean): Predicate<T> => predicate;
guard
Creates a type guard pattern.
const guard = <T>(predicate: (value: unknown) => value is T): Guard<T> => predicate;
some
Creates a pattern that matches if any element in an array matches the given pattern.
const some = <T>(pattern: Pattern<T>): Many<T> => ({
type: 'some',
pattern,
});
every
Creates a pattern that matches if all elements in an array match the given pattern.
const every = <T>(pattern: Pattern<T>): Many<T> => ({
type: 'every',
pattern,
});
Constants
Examples
1. Type-Based Pattern Matching with Result
import { Ok, Err, Result, match, when } from '@jordan-hall/typeguard';
function divide(a: number, b: number): Result<number, string> {
if (b === 0) return Err("Division by zero");
return Ok(a / b);
}
const result1 = divide(10, 2);
const result2 = divide(10, 0);
const message1 = match(result1,
{ type: 'Ok', pattern: when(n => n > 5), handler: (value) => `Success: ${value} is greater than 5` },
{ type: 'Ok', pattern: _, handler: (value) => `Success: ${value}` },
{ type: 'Err', pattern: _, handler: (error) => `Error: ${error}` }
);
console.log(message1);
const message2 = match(result2,
{ type: 'Ok', pattern: _, handler: (value) => `Success: ${value}` },
{ type: 'Err', pattern: "Division by zero", handler: (error) => `Caught error: ${error}` },
{ type: 'Err', pattern: _, handler: (error) => `Unhandled error: ${error}` }
);
console.log(message2);
import { Ok, Err, Result, match, when } from '@jordan-hall/typeguard';
interface Payment {
method: "credit_card" | "paypal" | "bank_transfer";
details: {
cardNumber?: string;
paypalId?: string;
bankAccount?: string;
};
amount: number;
}
interface PaymentError {
code: string;
message: string;
}
function processPayment(payment: Payment): Result<Payment, PaymentError> {
if (payment.amount <= 0) {
return Err({ code: "INVALID_AMOUNT", message: "Amount must be greater than zero" });
}
return Ok(payment);
}
const payment: Payment = {
method: "paypal",
details: {
paypalId: "user@paypal.com"
},
amount: 100
};
const paymentResult = processPayment(payment);
const paymentMessage = match(paymentResult,
{ type: 'Ok', pattern: when(p => p.amount >= 100), handler: (p) => `Large payment of \$${p.amount}` },
{ type: 'Ok', pattern: _, handler: (p) => `Payment of \$${p.amount} processed` },
{ type: 'Err', pattern: "INVALID_AMOUNT", handler: (e) => `Error: ${e.message}` },
);
console.log(paymentMessage);
5. Error Handling with match
import { Ok, Err, Result, match, when } from '@jordan-hall/typeguard';
interface FileData {
filename: string;
content: string;
}
interface FileError {
code: string;
message: string;
}
function readFile(filename: string): Result<FileData, FileError> {
if (filename === "") {
return Err({ code: "INVALID_FILENAME", message: "Filename cannot be empty" });
}
if (filename.endsWith(".txt")) {
return Ok({ filename, content: "Sample text content." });
}
return Err({ code: "UNSUPPORTED_FORMAT", message: "File format not supported" });
}
const fileResult = readFile("data.txt");
const fileMessage = match(fileResult,
{ type: 'Ok', pattern: _, handler: (file) => `File "${file.filename}" loaded with content: ${file.content}` },
{ type: 'Err', pattern: { code: "INVALID_FILENAME" }, handler: (error) => `Error: ${error.message}` },
{ type: 'Err', pattern: { code: "UNSUPPORTED_FORMAT" }, handler: (error) => `Error: ${error.message}` }
);
console.log(fileMessage);
6. Using Selector Functions in Path-Based Matching
import { Ok, Err, Result, match, when } from '@jordan-hall/typeguard';
interface Employee {
name: string;
department: {
name: string;
level: number;
};
projects: Array<{ title: string; hours: number }>;
}
interface EmployeeError {
code: string;
message: string;
}
function getEmployee(id: string): Result<Employee, EmployeeError> {
if (id === "unknown") {
return Err({ code: "NOT_FOUND", message: "Employee not found" });
}
return Ok({
name: "Carol",
department: {
name: "Engineering",
level: 3
},
projects: [
{ title: "Project A", hours: 120 },
{ title: "Project B", hours: 80 }
]
});
}
const employeeResult = getEmployee("emp456");
const employeeMessage = match(employeeResult,
{ type: 'Ok', pattern: when(employee => employee.department.level >= 3), handler: employee => `Senior ${employee.department.name} member: ${employee.name}` },
{ type: 'Ok', pattern: { projects: some(when(project => project.hours > 100)) }, handler: employee => `${employee.name} is working extensively on projects` },
{ type: 'Ok', pattern: when(employee => employee.projects.length > 5), handler: employee => `${employee.name} is handling multiple projects` },
{ type: 'Err', pattern: _, handler: error => `Error: ${error.message}` }
);
console.log(employeeMessage);