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.
- 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.
You can install @jordan-hall/typeguard
via Pacakage Json:
### Using Bun
bun install @jordan-hall/typeguard
### Using npm
npm install @jordan-hall/typeguard
### Using yarn
yarn add @jordan-hall/typeguard
### Using pnpm
pnpm install @jordan-hall/typeguard
You can also use JSR (preferred). For import symbol view on JSR website
### Using Deno
deno add jsr:@jordan-hall/@jordan-hall/typeguard
### Using NPM
npx jsr add @jordanhall/@jordan-hall/typeguard
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" });
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); // Output: User ID: 1, Name: Alice
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); // Output: Order order123 has expensive items
Simplify error handling with type-safe tryCatch
and tryCatchAsync
functions.
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);
}
import { tryCatchAsyn } from '@jordan-hall/typeguard/tryCatch';
async function fetchData(): Promise<string> {
// Simulate fetching data
return "data";
}
const [error, data] = await tryCatchAsync(() => fetchData());
if (data) {
console.log("Fetched data:", data);
} else {
console.error("Fetching error:", error);
}
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); // Output: User: Bob, Age: 25
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); // Output: At least one number is greater than 3
-
Result<T, E>
Represents a computation that can either succeed with a value of typeT
or fail with an error of typeE
. -
Ok<T, E>
Represents a successful computation containing a value of typeT
. -
Err<T, E>
Represents a failed computation containing an error of typeE
.
Creates an Ok
result.
const Ok = <T, E>(value: T): Result<T, E> => ({ _tag: 'Ok', value });
Creates an Err
result.
const Err = <T, E>(error: E): Result<T, E> => ({ _tag: 'Err', error });
Performs pattern matching on a Result
type.
function match<T, E, R>(
result: Result<T, E>,
...cases: Array<MatchCase<T, E, R>>
): R;
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];
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]>
Creates a predicate pattern.
const when = <T>(predicate: (value: T) => boolean): Predicate<T> => predicate;
Creates a type guard pattern.
const guard = <T>(predicate: (value: unknown) => value is T): Guard<T> => predicate;
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,
});
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,
});
-
_
Wildcard pattern that matches any value.const _ = Symbol('_');
import { Ok, Err, Result, match, when } from '@jordan-hall/typeguard';
/**
* Performs division and returns a Result type.
* @param a The dividend.
* @param b The divisor.
* @returns Ok with the result if successful, Err with an error message if division by zero.
*/
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);
// Handling result1
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); // Output: Success: 5 is greater than 5
// Handling result2
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); // Output: Caught error: Division by zero
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;
}
/**
* Processes a payment and returns a Result type.
* @param payment The payment to process.
* @returns Ok with the payment if successful, Err with an error otherwise.
*/
function processPayment(payment: Payment): Result<Payment, PaymentError> {
if (payment.amount <= 0) {
return Err({ code: "INVALID_AMOUNT", message: "Amount must be greater than zero" });
}
// Assume processing logic here...
return Ok(payment);
}
const payment: Payment = {
method: "paypal",
details: {
paypalId: "user@paypal.com"
},
amount: 100
};
const paymentResult = processPayment(payment);
const paymentMessage = match(paymentResult,
// Type-based cases
{ 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); // Output: Large payment of $100
import { Ok, Err, Result, match, when } from '@jordan-hall/typeguard';
interface FileData {
filename: string;
content: string;
}
interface FileError {
code: string;
message: string;
}
/**
* Reads a file and returns a Result type.
* @param filename The name of the file to read.
* @returns Ok with file data if successful, Err with a FileError otherwise.
*/
function readFile(filename: string): Result<FileData, FileError> {
if (filename === "") {
return Err({ code: "INVALID_FILENAME", message: "Filename cannot be empty" });
}
// Simulate file reading
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); // Output: File "data.txt" loaded with content: Sample text content.
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;
}
/**
* Retrieves employee data and returns a Result type.
* @param id The ID of the employee.
* @returns Ok with employee data if found, Err with an EmployeeError otherwise.
*/
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); // Output: Senior Engineering member: Carol