npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

result-guard

v1.2.0

Published

Type-safe error handling with discriminated unions and type guards for TypeScript

Downloads

218

Readme

result-guard

A TypeScript utility for elegant, type-safe error handling. It wraps your code in a Result type that makes error handling explicit and type-safe, eliminating the need for try-catch blocks while maintaining full type information.

Table of Contents

Features

  • 🎯 Type-Safe: Full TypeScript support with discriminated unions and type guards
  • 🔄 Universal: Works with both sync and async code
  • 🛡️ Robust: Automatically converts thrown values to proper Error objects
  • 🧬 Preserves: Keeps error stack traces and inheritance chains intact
  • 🎨 Flexible: Supports custom error types
  • Performant: Zero dependencies, lightweight implementation
  • 🔍 Developer Friendly: Great TypeScript inference and detailed error info
  • 📦 Module Support: Works with both ESM and CommonJS

Installation

npm install result-guard

Module Support

result-guard supports both ESM (ECMAScript Modules) and CommonJS:

// ESM
import { tryCatch, isSuccess } from 'result-guard';

// CommonJS
const { tryCatch, isSuccess } = require('result-guard');

The package automatically uses the correct format based on your project's configuration:

  • If your package.json has "type": "module", it uses ESM
  • If not specified, it uses CommonJS
  • You can also explicitly import the ESM version using the .mjs extension or import field

Quick Start

import { tryCatch, isSuccess } from 'result-guard';

// Sync example
const result = tryCatch(() => "hello world");
if (isSuccess(result)) {
  console.log(result.data); // TypeScript knows this is string
}

// Async example
const fetchUser = async (id: string) => {
  const result = await tryCatch(async () => {
    const response = await fetch(`/api/users/${id}`);
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}`);
    }
    return response.json();
  });

  if (isSuccess(result)) {
    return result.data; // Success case
  }
  // Error case - result.error is typed as Error
  console.error('Failed to fetch user:', result.error.message);
  return null;
};

Core Concepts

The Result Type

The Result type is a discriminated union that represents either success or failure:

type Result<T, E = Error> = 
  | { data: T; error: null; isError: false }  // Success case
  | { data: null; error: E; isError: true }   // Failure case

// Example usage:
const divide = (a: number, b: number): Result<number> => {
  if (b === 0) {
    return { data: null, error: new Error("Division by zero"), isError: true };
  }
  return { data: a / b, error: null, isError: false };
};

const result = divide(10, 2);
if (!result.isError) {
  console.log(result.data); // TypeScript knows this is number
}

Type Guards

Type guards help TypeScript narrow down the type:

import { isSuccess, isFailure } from 'result-guard';

const result = tryCatch(() => "hello");

// TypeScript knows result.data is string here
if (isSuccess(result)) {
  console.log(result.data.toUpperCase());
}

// TypeScript knows result.error is Error here
if (isFailure(result)) {
  console.log(result.error.message);
}

Custom Error Types

You can use your own error types for better error handling:

class ApiError extends Error {
  constructor(
    public statusCode: number,
    message: string
  ) {
    super(message);
  }
}

// Specify the error type as ApiError
const result = await tryCatch<Response, ApiError>(async () => {
  const response = await fetch('/api/data');
  if (!response.ok) {
    throw new ApiError(response.status, response.statusText);
  }
  return response;
});

if (isFailure(result)) {
  // TypeScript knows result.error is ApiError
  console.log(`API Error ${result.error.statusCode}: ${result.error.message}`);
}

Common Patterns

Early Return Pattern

Best for functions that should stop on error:

async function processUserData(userId: string) {
  // Get user
  const userResult = await tryCatch(async () => {
    const response = await fetch(`/api/users/${userId}`);
    return response.json();
  });

  if (isFailure(userResult)) {
    return { error: `Failed to fetch user: ${userResult.error.message}` };
  }

  // Get user's posts
  const postsResult = await tryCatch(async () => {
    const response = await fetch(`/api/users/${userId}/posts`);
    return response.json();
  });

  if (isFailure(postsResult)) {
    return { error: `Failed to fetch posts: ${postsResult.error.message}` };
  }

  // Success case - both operations succeeded
  return {
    user: userResult.data,
    posts: postsResult.data
  };
}

Destructuring Pattern

Good for simple cases where you want to handle both success and error inline:

async function getLatestPost() {
  const { data: post, error } = await tryCatch(async () => {
    const response = await fetch('/api/posts/latest');
    return response.json();
  });

  if (error) {
    console.error('Failed to fetch post:', error);
    return null;
  }

  return post;
}

Parallel Operations Pattern

Handle multiple operations that can succeed or fail independently:

async function getDashboardData() {
  const [usersResult, postsResult, statsResult] = await Promise.all([
    tryCatch(() => fetch('/api/users').then(r => r.json())),
    tryCatch(() => fetch('/api/posts').then(r => r.json())),
    tryCatch(() => fetch('/api/stats').then(r => r.json()))
  ]);

  return {
    users: isSuccess(usersResult) ? usersResult.data : [],
    posts: isSuccess(postsResult) ? postsResult.data : [],
    stats: isSuccess(statsResult) ? statsResult.data : null,
    errors: [
      isFailure(usersResult) && 'Failed to load users',
      isFailure(postsResult) && 'Failed to load posts',
      isFailure(statsResult) && 'Failed to load stats'
    ].filter(Boolean)
  };
}

Utility Functions

Working with Events (withEvents)

Safely handle event emitters and streams:

import { withEvents } from 'result-guard';
import { createReadStream } from 'fs';

async function readFileContents(filePath: string) {
  const stream = createReadStream(filePath);
  
  const result = await withEvents(
    stream,
    async () => {
      const chunks: Buffer[] = [];
      for await (const chunk of stream) {
        chunks.push(chunk);
      }
      return Buffer.concat(chunks).toString('utf8');
    },
    {
      timeout: 5000, // 5 second timeout
      cleanup: () => stream.destroy(), // Clean up the stream
      errorEvent: 'error' // Listen for 'error' events
    }
  );

  if (isSuccess(result)) {
    return result.data;
  }
  throw new Error(`Failed to read file: ${result.error.message}`);
}

Processing Iterators (withIterator)

Safely process async iterators with timeout and early termination:

import { withIterator } from 'result-guard';

async function processLargeDataSet() {
  async function* dataGenerator() {
    let page = 1;
    while (true) {
      const response = await fetch(`/api/data?page=${page}`);
      const data = await response.json();
      if (data.length === 0) break;
      yield* data;
      page++;
    }
  }

  const result = await withIterator(dataGenerator(), {
    timeout: 30000, // 30 second timeout
    maxItems: 1000, // Stop after 1000 items
    onItem: (item) => {
      // Stop if we find an invalid item
      if (!item.isValid) return false;
      // Continue processing
      return true;
    }
  });

  if (isSuccess(result)) {
    return result.data;
  }
  console.error('Failed to process data:', result.error);
  return [];
}

Handling Callbacks (withCallbacks)

Convert callback-style APIs to promises:

import { withCallbacks } from 'result-guard';
import { Database } from 'some-db-library';

function queryDatabase(sql: string, params: any[]) {
  return withCallbacks<any[]>(({ resolve, reject }) => {
    const db = new Database();
    
    db.query(sql, params, (err, results) => {
      if (err) reject(err);
      else resolve(results);
    });

    // Return cleanup function
    return () => db.close();
  }, {
    timeout: 5000 // 5 second timeout
  });
}

// Usage
const result = await queryDatabase('SELECT * FROM users WHERE id = ?', [123]);
if (isSuccess(result)) {
  console.log('Query results:', result.data);
}

Running Concurrent Operations (concurrent)

Execute multiple operations with controlled concurrency and precise type inference:

// Example with typed functions
interface User { name: string; id: number }
interface Post { title: string; content: string }

const getUser = async (): Promise<User> => ({ name: 'bob', id: 1 });
const getPost = async (): Promise<Post> => ({ 
  title: 'Hello',
  content: 'World'
});

// TypeScript infers exact return types
const results = await concurrent([
  getUser,
  getPost
] as const);

const [userResult, postResult] = results;

if (!userResult.isError) {
  const user = userResult.data; // TypeScript knows this is User
  console.log(user.name, user.id);
}

if (!postResult.isError) {
  const post = postResult.data; // TypeScript knows this is Post
  console.log(post.title, post.content);
}

// Example with literal types
const literalResults = await concurrent([
  async () => 42 as const,
  async () => 'hello' as const,
  async () => ({ status: 'ok' as const })
] as const);

const [numResult, strResult, objResult] = literalResults;

if (!numResult.isError) {
  const num = numResult.data; // Type is exactly 42
  console.log(num); // TypeScript knows this is exactly 42
}

if (!strResult.isError) {
  const str = strResult.data; // Type is exactly 'hello'
  console.log(str); // TypeScript knows this is exactly 'hello'
}

if (!objResult.isError) {
  const obj = objResult.data; // Type is exactly { status: 'ok' }
  console.log(obj.status); // TypeScript knows this is exactly 'ok'
}

// With concurrency control
const results = await concurrent(
  [getUser, getPost],
  {
    timeout: 5000, // 5 second timeout
    maxConcurrent: 2, // Run at most 2 operations at once
    stopOnError: false // Continue on error
  }
);

The concurrent function provides:

  • Precise type inference for each operation's return type
  • Support for both typed functions and literal types
  • Controlled concurrency with maxConcurrent
  • Timeout handling for long-running operations
  • Error handling with stopOnError option
  • Type-safe access to results through destructuring

Configuration Types

Common Options

All utility functions accept a timeout option:

type TimeoutOptions = {
  timeout?: number; // Milliseconds before operation times out
};

Event Handler Options

Options for withEvents:

type EventOptions = TimeoutOptions & {
  errorEvent?: string; // Event name to listen for errors (default: 'error')
  cleanup?: () => void | Promise<void>; // Cleanup function
};

Iterator Options

Options for withIterator:

type IteratorOptions<T> = TimeoutOptions & {
  maxItems?: number; // Maximum number of items to process
  onItem?: (item: T) => boolean | Promise<boolean>; // Return false to stop
};

Concurrent Operation Options

Options for concurrent:

type ConcurrentOptions = TimeoutOptions & {
  maxConcurrent?: number; // Maximum parallel operations
  stopOnError?: boolean; // Stop all operations on first error
};

License

MIT

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.