/** * Comprehensive examples demonstrating the fully typed error handling system * Similar to neverthrow and Rust's Result type */ import type { Result } from './src/types' import { combine, combineWithAllErrors, err, fromNullable, ok, traverse, tryCatch, tryCatchAsync, } from './src/result' // ============================================================================ // Example 1: Basic usage with type inference // ============================================================================ function divideBasic(a: number, b: number): Result { if (b === 0) { return err('Division by zero') } return ok(a / b) } // Type is inferred as Result const result1 = divideBasic(10, 2) // Type narrowing works perfectly if (result1.isOk) { // eslint-disable-next-line no-console console.log(result1.value) // Type: number } else { // eslint-disable-next-line no-console console.log(result1.error) // Type: string } // ============================================================================ // Example 2: Chaining operations with map and andThen // ============================================================================ function parseNumber(input: string): Result { const num = Number.parseFloat(input) return Number.isNaN(num) ? err('Invalid number') : ok(num) } function validatePositive(num: number): Result { return num > 0 ? ok(num) : err('Number must be positive') } function sqrt(num: number): Result { return num >= 0 ? ok(Math.sqrt(num)) : err('Cannot take square root of negative number') } // Chain operations with full type safety // Example showing the power of chaining parseNumber('16') .andThen(validatePositive) .andThen(sqrt) .map(n => n * 2) // ============================================================================ // Example 3: Pattern matching // ============================================================================ interface User { id: number name: string email: string } function findUser(id: number): Result { // Simulated database lookup if (id === 1) { return ok({ id: 1, name: 'Alice', email: 'alice@example.com' }) } return err(`User ${id} not found`) } // Example of pattern matching findUser(1).match({ ok: user => `Found user: ${user.name}`, err: error => `Error: ${error}`, }) // ============================================================================ // Example 4: Handling nullable values // ============================================================================ interface Config { apiKey?: string timeout?: number } function getApiKey(config: Config): Result { return fromNullable(config.apiKey, 'API key is required') } const config: Config = { timeout: 5000 } // Example of handling nullable values getApiKey(config) // Result // ============================================================================ // Example 5: Converting exceptions to Results // ============================================================================ function parseJSON(json: string): Result { return tryCatch( () => JSON.parse(json) as T, (error) => { if (error instanceof Error) { return error.message } return 'Unknown error' }, ) } // Example of parsing JSON safely parseJSON<{ name: string }>('{"name": "Alice"}') // ============================================================================ // Example 6: Async operations // ============================================================================ async function fetchUser(id: number): Promise> { return tryCatchAsync( async () => { const response = await fetch(`https://api.example.com/users/${id}`) if (!response.ok) { throw new Error(`HTTP ${response.status}`) } return response.json() as Promise }, error => error instanceof Error ? error.message : 'Unknown error', ) } // ============================================================================ // Example 7: Combining multiple Results // ============================================================================ interface UserProfile { user: User age: number country: string } function getUser(): Result { return ok({ id: 1, name: 'Alice', email: 'alice@example.com' }) } function getAge(): Result { return ok(30) } function getCountry(): Result { return ok('USA') } // Combine all results - stops at first error // @ts-expect-error - Complex type inference with combine function const profileResult = combine([getUser(), getAge(), getCountry()]) if (profileResult.isOk) { const values = profileResult.value const profile: UserProfile = { user: values[0] as User, age: values[1] as number, country: values[2] as string, } // eslint-disable-next-line no-console console.log(profile) } // ============================================================================ // Example 8: Form validation with combineWithAllErrors // ============================================================================ interface FormData { email: string password: string age: string } function validateEmail(email: string): Result { const emailRegex = /^[^\s@]+@[^\s@][^\s.@]*\.[^\s@]+$/ return emailRegex.test(email) ? ok(email) : err('Invalid email format') } function validatePassword(password: string): Result { return password.length >= 8 ? ok(password) : err('Password must be at least 8 characters') } function validateAge(age: string): Result { const num = Number.parseInt(age) if (Number.isNaN(num)) return err('Age must be a number') if (num < 18) return err('Must be 18 or older') return ok(num) } function validateForm(data: FormData): Result { // Collect all errors instead of stopping at first one return combineWithAllErrors( // @ts-expect-error - Complex type inference with combineWithAllErrors [validateEmail(data.email), validatePassword(data.password), validateAge(data.age)], ) } const formData: FormData = { email: 'invalid-email', password: 'short', age: '15', } const validationResult = validateForm(formData) if (validationResult.isErr) { // eslint-disable-next-line no-console console.log('Validation errors:', validationResult.error) // Type: string[] } // ============================================================================ // Example 9: Railway-oriented programming // ============================================================================ interface OrderData { productId: string quantity: number userId: string } interface Product { id: string name: string price: number stock: number } interface Order { id: string product: Product quantity: number total: number } function validateQuantity(quantity: number): Result { return quantity > 0 ? ok(quantity) : err('Quantity must be positive') } function findProduct(productId: string): Result { // Simulated product lookup return ok({ id: productId, name: 'Widget', price: 9.99, stock: 100, }) } function checkStock(product: Product, quantity: number): Result { return product.stock >= quantity ? ok(product) : err(`Insufficient stock. Available: ${product.stock}`) } function calculateTotal(product: Product, quantity: number): number { return product.price * quantity } function createOrder(orderData: OrderData): Result { return validateQuantity(orderData.quantity) // eslint-disable-next-line no-unused-vars .andThen(quantity => findProduct(orderData.productId) .andThen(product => checkStock(product, quantity)) .map((product) => { const order: Order = { id: crypto.randomUUID(), product, quantity, total: calculateTotal(product, quantity), } return order }), ) } // ============================================================================ // Example 10: Working with arrays using traverse // ============================================================================ function processNumbers(inputs: string[]): Result { return traverse(inputs, (input) => { const num = Number.parseFloat(input) return Number.isNaN(num) ? err(`Invalid number: ${input}`) : ok(num) }) } // Examples of processing arrays with traverse processNumbers(['1', '2', '3']) // Result = Ok([1, 2, 3]) processNumbers(['1', 'invalid', '3']) // Result = Err("Invalid number: invalid") // ============================================================================ // Example 11: Error recovery with orElse // ============================================================================ function fetchFromPrimaryCache(_key: string): Result { return err('Cache miss') } function fetchFromSecondaryCache(_key: string): Result { return err('Cache miss') } function fetchFromDatabase(_key: string): Result { return ok('data from database') } function getData(key: string): Result { return fetchFromPrimaryCache(key) .orElse(() => fetchFromSecondaryCache(key)) .orElse(() => fetchFromDatabase(key)) } // ============================================================================ // Example 12: Type-safe error transformation with mapErr // ============================================================================ enum ErrorCode { NotFound = 'NOT_FOUND', Unauthorized = 'UNAUTHORIZED', ValidationError = 'VALIDATION_ERROR', } interface ApiError { code: ErrorCode message: string timestamp: Date } function validateInput(input: string): Result { return input.length > 0 ? ok(input) : err('Input cannot be empty') } function processWithApiError(input: string): Result { return validateInput(input).mapErr((errorMessage) => { const error: ApiError = { code: ErrorCode.ValidationError, message: errorMessage, timestamp: new Date(), } return error }) } // ============================================================================ // Example 13: Practical API client example // ============================================================================ class HttpError { constructor( public status: number, public message: string, ) {} } async function makeRequest(url: string) { return tryCatchAsync( async () => { const response = await fetch(url) if (!response.ok) { throw new HttpError(response.status, response.statusText) } return response.json() as Promise }, (error) => { if (error instanceof HttpError) { return error } return new HttpError(500, 'Internal error') }, ) } // Usage with full type safety // eslint-disable-next-line no-unused-vars async function getUserProfile(userId: string): Promise< { success: true, user: User } | { success: false, error: string } > { const result = await makeRequest(`/api/users/${userId}`) if (result.isOk) { return { success: true as const, user: result.value } } else { return { success: false as const, error: `Failed to fetch user: ${result.error.message} (${result.error.status})`, } } } // ============================================================================ // Export examples for documentation // ============================================================================ export { createOrder, divideBasic, fetchUser, getData, getUserProfile, parseJSON, parseNumber, processNumbers, processWithApiError, sqrt, validateForm, validatePositive, }