tryError Documentation

TypeScript Troubleshooting

Common TypeScript issues and their solutions when using tryError.

Critical
Union Type Inference Issues
Property 'message' does not exist on type 'never' errors

❌ Common Problem

Problem: Direct property accesstypescript
const result = await tryAsync(() => fetchData());
if (!isOk(result)) {
  // ❌ TypeScript Error: Property 'message' does not exist on type 'never'
  console.log(result.message);
}

✅ Solution 1: Use Type Guards

Solution: Proper type guardstypescript
const result = await tryAsync(() => fetchData());
if (isTryError(result)) {
  // ✅ Type-safe access to error properties
  console.log(result.message);
  console.log(result.type);
  console.log(result.source);
}

✅ Solution 2: Destructured Pattern

Solution: Destructure with fallbacktypescript
const result = await tryAsync(() => fetchData());
if (!isOk(result)) {
  const errorMsg = isTryError(result) ? result.message : String(result);
  const errorType = isTryError(result) ? result.type : 'UnknownError';
  console.log(`Error (${errorType}): ${errorMsg}`);
}
Critical
Generic Type Parameter Issues
When TypeScript can't infer the correct error type

❌ Problem: Ambiguous return types

Problem: Generic inference failuretypescript
// TypeScript can't determine if this is T or TryError
function processData<T>(data: T): TryResult<ProcessedData, ValidationError> {
  const result = trySync(() => validate(data));
  // ❌ Type issues here
  return result;
}

✅ Solution: Explicit Type Annotations

Solution: Explicit error typestypescript
interface ValidationError extends TryError<'ValidationError'> {
  field: string;
  rule: string;
}

function processData<T>(data: T): TryResult<ProcessedData, ValidationError> {
  const result = trySync<ProcessedData, ValidationError>(() => {
    const validation = validate(data);
    if (!validation.valid) {
      throw createError({
        type: 'ValidationError' as const,
        message: validation.message,
        context: { field: validation.field, rule: validation.rule }
      });
    }
    return process(validation.data);
  });
  
  return result;
}
Critical
Error Context Access Issues
Property does not exist on type 'unknown' when accessing error context

❌ Common Problem

Problem: Direct context property accesstypescript
const result = await tryAsync(() => validateForm(data));
if (isTryError(result)) {
  // ❌ TypeScript Error: Property 'field' does not exist on type 'unknown'
  console.log(result.context.field);
  console.log(result.context.rule);
}

✅ Solution 1: Type Guards

Solution: Create type guards for contexttypescript
interface ValidationContext {
  field: string;
  rule: string;
  value: unknown;
}

function isValidationContext(context: unknown): context is ValidationContext {
  return typeof context === 'object' && 
         context !== null && 
         'field' in context &&
         'rule' in context;
}

const result = await tryAsync(() => validateForm(data));
if (isTryError(result) && isValidationContext(result.context)) {
  // ✅ Type-safe access to context properties
  console.log(`Field: ${result.context.field}`);
  console.log(`Rule: ${result.context.rule}`);
}

✅ Solution 2: Safe Type Assertion

Solution: Type assertion with validationtypescript
const result = await tryAsync(() => validateForm(data));
if (isTryError(result)) {
  // ✅ Safe assertion with optional properties
  const context = result.context as { field?: string; rule?: string } | undefined;
  
  if (context?.field && context?.rule) {
    console.log(`Validation failed: ${context.field} (${context.rule})`);
  } else {
    console.log(`Validation failed: ${result.message}`);
  }
}

✅ Solution 3: Context Helper Utility

Solution: Reusable context extractiontypescript
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;
}

const result = await tryAsync(() => validateForm(data));
if (isTryError(result)) {
  const field = getContextValue(result, 'field', 'unknown');
  const rule = getContextValue(result, 'rule', 'general');
  
  console.log(`Validation failed for ${field} (rule: ${rule})`);
}

Why this happens: Error context is typed as unknown by design to enforce type safety. Since errors can originate from anywhere with different context structures, TypeScript requires you to validate the shape before accessing properties.

Common
React Component Type Issues
TypeScript issues when using tryError in React components

❌ Problem: State type inference

Problem: useState with TryResulttypescript
function UserProfile({ userId }: { userId: string }) {
  // ❌ TypeScript can't infer the correct type
  const [userData, setUserData] = useState(null);
  
  useEffect(() => {
    const fetchUser = async () => {
      const result = await tryAsync(() => api.getUser(userId));
      setUserData(result); // ❌ Type error
    };
    fetchUser();
  }, [userId]);
}

✅ Solution: Explicit state typing

Solution: Proper state typestypescript
interface User {
  id: string;
  name: string;
  email: string;
}

type UserState = {
  data: User | null;
  error: TryError | null;
  loading: boolean;
};

function UserProfile({ userId }: { userId: string }) {
  const [state, setState] = useState<UserState>({
    data: null,
    error: null,
    loading: true
  });
  
  useEffect(() => {
    const fetchUser = async () => {
      setState(prev => ({ ...prev, loading: true, error: null }));
      
      const result = await tryAsync<User>(() => api.getUser(userId));
      
      if (isOk(result)) {
        setState({ data: result, error: null, loading: false });
      } else {
        setState({ data: null, error: result, loading: false });
      }
    };
    fetchUser();
  }, [userId]);
  
  if (state.loading) return <LoadingSpinner />;
  if (state.error) return <ErrorDisplay error={state.error} />;
  if (!state.data) return <NotFound />;
  
  return <UserData user={state.data} />;
}
TypeScript Configuration
Recommended TypeScript settings for optimal tryError experience
tsconfig.json recommendationsjson
{
  "compilerOptions": {
    "strict": true,                    // Enable all strict checks
    "strictNullChecks": true,          // Critical for proper type inference
    "noImplicitReturns": true,         // Catch missing return statements
    "noImplicitAny": false,            // Allow some flexibility
    "exactOptionalPropertyTypes": true // Better optional property handling
  }
}
Quick Reference: Type Guards
Essential type guards for working with tryError results
Essential type guardstypescript
// ✅ Check if operation succeeded
if (isOk(result)) {
  // result is T (success value)
  console.log(result); // Type-safe access
}

// ✅ Check if operation failed
if (isErr(result)) {
  // result is TryError
  console.log(result.message);
}

// ✅ Check if it's a TryError specifically
if (isTryError(result)) {
  // result is TryError with all properties
  console.log(result.type, result.message, result.source);
}

// ✅ Type-safe unwrapping with fallback
const value = unwrapOr(result, 'default value');

// ✅ Pattern matching style
const message = isOk(result) 
  ? `Success: ${result}`
  : `Error: ${result.message}`;

✅ Fixed: TypeScript Union Type Issues

We've added improved type guards and utilities to solve the most common TypeScript inference problems:

1. Use isTryFailure() for Error Narrowing

// ✅ RECOMMENDED: Use isTryFailure for perfect error narrowing
const result = await tryAsync(() => fetchData());

if (isTryFailure(result)) {
  // TypeScript knows this is TryError - perfect inference!
  console.log(result.message);
  console.log(result.type);
  console.log(result.source);
}

// ✅ Or use isTrySuccess for success narrowing
if (isTrySuccess(result)) {
  // TypeScript knows this is your success type
  console.log(result.data);
}

2. Use matchTryResult() for Perfect Type Safety

// ✅ BEST: Use matchTryResult for 100% type-safe handling
const message = matchTryResult(result, {
  success: (data) => `Success: ${data.name}`,  // data is properly typed
  error: (error) => `Error: ${error.message}`   // error is TryError
});

// No type inference issues, no manual checking needed!

3. Use unwrapTryResult() for Structured Access

// ✅ GOOD: Use unwrapTryResult for clear success/error structure
const unwrapped = unwrapTryResult(result);

if (unwrapped.success) {
  console.log('Data:', unwrapped.data.name);
} else {
  console.log('Error:', unwrapped.error.message);
}

// Perfect TypeScript inference with clear intent