tryError Documentation

Error Factory API Reference

Complete reference for all error factory functions and their usage patterns.

Error Factory Philosophy
Understanding the design principles behind tryError factories

Why Error Factories?

Error factories solve three core problems in JavaScript error handling:

Consistency

Standardized error structure across your entire application

Type Safety

Full TypeScript support with discriminated unions

Rich Context

Capture debugging information at the point of failure

Design Principles

  • Progressive Enhancement: Start simple, add complexity as needed
  • Ergonomic API: Natural parameter order, intuitive naming
  • Context-Rich: Capture relevant debugging information automatically
  • Type-First: Leverage TypeScript's discriminated unions
Available Factory Methods
Quick reference for all error factory functions

Core Factories

createError()Primary factory function
wrapError()Wrap existing errors
fromThrown()Convert thrown exceptions

Domain-Specific Factories

validationError()Field validation failures
amountError()Financial/quantity errors
externalError()External service failures
entityError()Entity not found/conflicts
Choosing the Right Factory

Start with domain-specific factories for common use cases

Use createError() for custom error types and complex scenarios

Use wrapError() when you need to add context to existing errors

Use fromThrown() in try/catch blocks to convert exceptions

Quick Start Examples
Common patterns to get you started immediately

Simple Validation

Quick field validationtypescript
// Quick field validation
const error = validationError('email', 'required', 'Email is required');

// With additional context
const error = validationError('age', 'range', 'Age must be 18-65', {
  min: 18,
  max: 65,
  provided: 16
});

API Error Handling

External service errorstypescript
// External service failure
const error = externalError('PaymentAPI', 'timeout', 'Payment service unavailable');

// With request context
const error = externalError('UserAPI', 'not_found', 'User not found', {
  userId: '123',
  endpoint: '/api/users/123'
});

Business Logic Errors

Amount and entity errorstypescript
// Insufficient funds
const error = amountError(150, 100, 'insufficient', 'Insufficient balance');

// Entity conflicts
const error = entityError('user', 'john@example.com', 'User already exists');

✅ New: Improved Ergonomic Error Factories

We've added more intuitive error factory functions with better parameter orders:

Validation Errors (Simplified)

Simplified validation errorstypescript
// ✅ NEW: Simple field validation
const error = validationError('email', 'invalid', 'Must be a valid email address', { 
  value: 'invalid-email' 
});

// ✅ NEW: Multi-field validation
const formError = fieldValidationError({
  email: ['Must be a valid email address'],
  password: ['Must be at least 8 characters', 'Must contain a number']
}, 'FORM_VALIDATION_ERROR');

Amount Errors (Simplified)

Simplified amount errorstypescript
// ✅ NEW: Intuitive amount error with context
const error = amountError(
  150,        // requested amount
  100,        // available amount  
  'insufficient', 
  'Insufficient funds available'
);

console.log(error.context.requestedAmount); // 150
console.log(error.context.availableAmount); // 100

External Service Errors (Simplified)

Simplified external service errorstypescript
// ✅ NEW: Clear service error creation
const error = externalError('API', 'failed', 'Service unavailable', { 
  transactionId: 'tx_123',
  statusCode: 503 
});

// ✅ NEW: Quick entity errors
const userError = entityError('user', 'user_123', 'User not found');

Note: The original factory functions (createValidationError, createAmountError, etc.) are still available for backward compatibility. Use whichever style you prefer!

createError
Core Error Factory
The primary function for creating custom tryError instances
createError APItypescript
function createError<T extends string = string>(options: {
  type: T;                              // Error category (required)
  message: string;                      // Human-readable description (required)
  cause?: unknown;                      // Underlying error (optional)
  context?: Record<string, unknown>;    // Additional debug data (optional)
  timestamp?: number;                   // When error occurred (optional)
  source?: string;                      // Source location (optional)
}): TryError<T>

// Examples
const validationError = createError({
  type: 'ValidationError',
  message: 'Email address is required'
});

const detailedError = createError({
  type: 'DatabaseError',
  message: 'Connection failed',
  context: { host: 'localhost', port: 5432, retryAttempt: 3 },
  cause: originalError
});
wrapError
Error Wrapper Factory
wrapError APItypescript
function wrapError<T extends string>(
  type: T,
  cause: unknown,
  message?: string,
  context?: Record<string, unknown>
): TryError<T>

// Examples
const wrappedError = wrapError('NetworkError', originalError, 'API call failed');

const detailedWrapper = wrapError(
  'ServiceError',
  error,
  'User service unavailable',
  { service: 'user-api', endpoint: '/users/123' }
);
fromThrown
Exception Converter
fromThrown APItypescript
function fromThrown(
  error: unknown,
  context?: Record<string, unknown>
): TryError

// Automatically detects error types:
fromThrown(new TypeError('...'))      // -> TryError<'TypeError'>
fromThrown(new SyntaxError('...'))    // -> TryError<'SyntaxError'>
fromThrown(new Error('...'))          // -> TryError<'Error'>
fromThrown('string error')            // -> TryError<'StringError'>
fromThrown({ weird: 'object' })       // -> TryError<'UnknownError'>

// With context
const convertedError = fromThrown(caughtError, { 
  operation: 'parseJson',
  input: jsonString 
});
Domain-Specific Factories
Pre-built factories for common scenarios
Common error factory patternstypescript
// Validation errors
function createValidationError(field: string, rule: string, value?: unknown) {
  return createError({
    type: 'ValidationError' as const,
    message: `Validation failed for field '${field}': ${rule}`,
    context: { field, rule, value }
  });
}

// Network errors
function createNetworkError(endpoint: string, status?: number) {
  return createError({
    type: 'NetworkError' as const,
    message: status ? `${endpoint} failed with ${status}` : `${endpoint} failed`,
    context: { endpoint, status }
  });
}

// Entity errors
function createNotFoundError(entityType: string, id: string | number) {
  return createError({
    type: 'NotFoundError' as const,
    message: `${entityType} with id '${id}' not found`,
    context: { entityType, id }
  });
}

// Business logic errors
function createBusinessRuleError(rule: string, details?: string) {
  return createError({
    type: 'BusinessRuleError' as const,
    message: `Business rule violated: ${rule}`,
    context: { rule, details }
  });
}
Error Factory Best Practices
Guidelines for creating effective error factories

1. Use Descriptive Type Names

Good vs bad error typestypescript
// ✅ Good: Descriptive and specific
createError({ type: 'EmailValidationError', message: '...' });
createError({ type: 'DatabaseConnectionTimeout', message: '...' });
createError({ type: 'InsufficientInventoryError', message: '...' });

// ❌ Bad: Generic and unclear
createError({ type: 'Error', message: '...' });
createError({ type: 'ValidationError', message: '...' }); // Too generic
createError({ type: 'Err', message: '...' }); // Abbreviated

2. Include Actionable Context

Rich error contexttypescript
// ✅ Good: Includes actionable debugging information
createError({
  type: 'ApiValidationError',
  message: 'Request validation failed',
  context: {
    endpoint: '/api/users',
    method: 'POST',
    validationErrors: {
      email: 'Invalid format',
      age: 'Must be between 18 and 120'
    },
    requestId: 'req_123456'
  }
});

// ❌ Bad: No context for debugging
createError({
  type: 'ValidationError',
  message: 'Validation failed'
});

3. Create Domain-Specific Factories

Custom factory patternstypescript
// ✅ Create reusable factories for your domain
class UserErrorFactory {
  static notFound(userId: string) {
    return createError({
      type: 'UserNotFoundError' as const,
      message: `User with ID ${userId} not found`,
      context: { userId, entity: 'User' }
    });
  }

  static alreadyExists(email: string) {
    return createError({
      type: 'UserAlreadyExistsError' as const,
      message: `User with email ${email} already exists`,
      context: { email, entity: 'User' }
    });
  }

  static invalidCredentials() {
    return createError({
      type: 'InvalidCredentialsError' as const,
      message: 'Invalid email or password',
      context: { entity: 'User', action: 'authenticate' }
    });
  }
}

// Usage
const user = await tryAsync(() => findUser(userId));
if (!user) {
  return UserErrorFactory.notFound(userId);
}

4. Type-Safe Factory Functions

Type-safe error creationtypescript
// ✅ Use const assertions for literal types
function createApiError(status: number, message: string) {
  const type = status >= 500 
    ? 'ServerError' as const
    : status >= 400 
    ? 'ClientError' as const
    : 'ApiError' as const;
    
  return createError({
    type,
    message,
    context: { status, category: status >= 500 ? 'server' : 'client' }
  });
}

// ✅ Use union types for predefined error categories
type ValidationErrorType = 
  | 'RequiredFieldError'
  | 'FormatValidationError' 
  | 'RangeValidationError'
  | 'CustomValidationError';

function createValidationError(
  type: ValidationErrorType,
  field: string,
  message: string
) {
  return createError({
    type,
    message: `${type}: ${message}`,
    context: { field, type, message }
  });
}

// Usage
const apiError = createApiError(404, 'Resource not found');
const validationError = createValidationError('email', 'Invalid format', 'The email is not valid');
Important
Working with Error Context
Understanding and safely accessing error context data

Why Context is Typed as `unknown`

Error context is intentionally typed as `unknown` for type safety. Since errors can originate from anywhere in your application with different context structures, TypeScript can't guarantee the shape of the context data.

Design Decision: This forces you to validate context before accessing properties, preventing runtime errors from unexpected context structures.

Type-Safe Context Access Patterns

1. Type guards for known contexttypescript
// Define your expected context structure
interface ValidationContext {
  field: string;
  value: unknown;
  rule: string;
}

// Create a type guard
function isValidationContext(context: unknown): context is ValidationContext {
  return typeof context === 'object' && 
         context !== null && 
         'field' in context &&
         'rule' in context &&
         typeof (context as any).field === 'string';
}

// Safe usage
const result = trySync(() => validateEmail(email));
if (isTryError(result) && isValidationContext(result.context)) {
  // ✅ Now context is properly typed
  console.log(`Field ${result.context.field} failed rule: ${result.context.rule}`);
}
2. Type assertion with validationtypescript
// Quick check with optional properties
function handleValidationError(error: TryError) {
  const context = error.context as { field?: string; rule?: string } | undefined;
  
  if (context?.field && context?.rule) {
    return `Validation failed for ${context.field}: ${context.rule}`;
  }
  
  return `Validation failed: ${error.message}`;
}
3. Generic context utilitytypescript
// Reusable helper for safe context access
function getContextValue<T>(
  error: TryError, 
  key: string, 
  defaultValue: T
): T {
  if (typeof error.context === 'object' && 
      error.context !== null && 
      key in error.context) {
    return (error.context as any)[key] ?? defaultValue;
  }
  return defaultValue;
}

// Usage
const field = getContextValue(error, 'field', 'unknown');
const status = getContextValue(error, 'status', 500);

Best Practices for Context Design

✅ Do
  • • Use consistent context structure within domains
  • • Document your context interfaces
  • • Include debug-relevant information
  • • Create type guards for validation
  • • Provide fallback values
❌ Don't
  • • Access context properties directly
  • • Assume context structure without validation
  • • Include sensitive data in context
  • • Create overly complex context structures
  • • Ignore context validation errors

Domain-Specific Context Patterns

Consistent context interfacestypescript
// Define context interfaces for your domain
export interface ApiErrorContext {
  endpoint: string;
  method: 'GET' | 'POST' | 'PUT' | 'DELETE';
  status?: number;
  requestId?: string;
}

export interface ValidationErrorContext {
  field: string;
  value: unknown;
  rule: string;
  constraints?: Record<string, unknown>;
}

export interface BusinessErrorContext {
  entity: string;
  operation: string;
  businessRule: string;
  metadata?: Record<string, unknown>;
}

// Create factory functions with typed context
export function createApiError(
  message: string, 
  context: ApiErrorContext
): TryError<'ApiError'> {
  return createError({
    type: 'ApiError' as const,
    message,
    context
  });
}

// Type guards for each context type
export function isApiErrorContext(context: unknown): context is ApiErrorContext {
  return typeof context === 'object' && 
         context !== null && 
         'endpoint' in context &&
         'method' in context;
}

// Usage
const error = createApiError('Failed to fetch user', {
  endpoint: '/api/users/123',
  method: 'GET',
  status: 404,
  requestId: 'req_abc123'
});

if (isTryError(error) && isApiErrorContext(error.context)) {
  // ✅ Fully typed access to context
  console.log(`${error.context.method} ${error.context.endpoint} failed`);
}