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 🙏

© 2024 – Pkg Stats / Ryan Hefner

@osaedasia/sam

v0.4.1

Published

A lightweight utility to handle error in your applications.

Downloads

585

Readme

SAM

A lightweight utility to handle error in your applications.

The library is available through the following package managers:

Table of Contents

Features

  • Simplifies error handling in server actions: Streamline the process of managing errors within your Next.js server actions.
  • Specify Watched Errors: Define specific errors to monitor and handle gracefully.
  • Handles Unexpected Errors: Provide mechanisms to manage errors that are not explicitly watched.
  • Designed for Next.js: Tailored to integrate seamlessly with Next.js applications using server actions.
  • Supports Chaining Multiple Error Handlers: Combine multiple error handlers for complex workflows, ensuring comprehensive error management.

Usage

SAM Action

SAM Action provides a straightforward way to handle errors within your application, whether on the server or client side. Below are examples demonstrating its usage.

Synchronous or Asynchronous

SAM Action supports both synchronous and asynchronous functions.

  • Synchronous action:
import {createSamAction} from "@osaedasia/sam";


const helloWorldSyncAction = createSamAction()
    .handler(() => {
        return "Sync Hello world!";
    });

const result = helloWorldSyncAction(); // Type: SamOption<string>
if(result.type === "success") {
    console.log(result.data); // Output: Sync Hello world!
}
  • Asynchronous action:
import { createSamAction } from "@osaedasia/sam";

const helloWorldAsyncAction = createSamAction()
    .handler(async () => { // async annotation
        return "Async Hello world!";
    });

const promiseResult = helloWorldAsyncAction(); // Type: Promise<SamOption<string>>
const result = await promiseResult; // Type: SamOption<string> 
if(result.type === "success") {
    console.log(result.data); // Output: Async Hello world!
}

Watching Specific Errors

Specify which errors to watch and return their messages to the client.

// action.ts
"use server";

import { createSamAction, SamError } from "@osaedasia/sam";

class InputParseError extends SamError {}

const validateInputAction = createSamAction()
    .watchErrors([InputParseError])
    .handler(async (input: string) => {
        if (input.length < 5) {
            throw new InputParseError('Input is too short');
        }
        return `Processed ${input}`;
    });

// page.tsx
const result = await validateInputAction("Hi"); // Type: SamOption<string>
if(result.type === "error") {
    console.log(result.reason); // Output: Input is too short
}

Handling Unwatched Errors

Define a callback to handle errors that are not watched.

// action.ts
"use server";

import { createSamAction, SamError } from "@osaedasia/sam";

class InputParseError extends SamError {}
class UnhandledError extends SamError {}

const unexpectedAction = createSamAction()
    .watchErrors([InputParseError])
    .unhandledErrors(error => {
        // Log the error or perform any action
        console.error('Unhandled error:', error);
    })
    .handler(async () => {
        // Both works
        throw new Error('Unexpected error');
        throw new UnhandledError('Unexpected error');
    });

// page.tsx
await unexpectedAction();

SAM Error

SAM provides a custom error class to standardize error handling within your application.

Creating an Error

To create a custom error, extend the SamError class:

import {SamError} from "@osaedasia/sam";

// Default SamError Constructor
class CustomError extends SamError {}

Throwing an Error

Use the custom error within your SAM actions or anywhere else:

import { createSamAction, SamError } from "@osaedasia/sam";

class CustomError extends SamError {}

const action = createSamAction()
    .watchErrors([CustomError])
    .handler(() => {
        throw new CustomError('Something went wrong');
    });

Best Practices

  • Extend SamError: Always create custom errors by extending SamError to ensure compatibility with SAM's error handling mechanisms.
  • Provide Clear Messages: Ensure error messages are descriptive to facilitate easier debugging.
  • Use Specific Errors: Define and watch specific error types to handle different error scenarios appropriately.

SAM Result

SAM introduces a SamResult class to represent operation outcomes without relying on exceptions. It also introduces a SamOption that represents either a successful result or an error as an object.

Use Cases

  • Handling Operation Results: Use SamResult to manage the success or failure of operations in a predictable manner.
  • Avoiding Exceptions: Reduce reliance on try-catch blocks by using SamResult to encapsulate results.
  • Type-Safe Error Handling: Ensure that all possible outcomes are handled, enhancing type safety.

Examples

Using SamResult

import { SamResult } from "@osaedasia/sam";

function divide(a: number, b: number): SamResult<number> {
    if (b === 0) {
        return SamResult.err("Division by zero is not allowed.");
    }
    return SamResult.ok(a / b);
}

// Usage
const result = divide(10, 2);
if (result.isOk()) {
    console.log(`Result: ${result.unwrap()}`); // Output: Result: 5
} else {
    console.error(`Error: ${result.unwrapErr()}`);
}

const errorResult = divide(10, 0);
if (errorResult.isErr()) {
    console.error(`Error: ${errorResult.unwrapErr()}`); // Output: Error: Division by zero is not allowed.
}

Using SamOption

import { SamResult, SamOption } from "@osaedasia/sam";

function divide(a: number, b: number): SamOption<number> {
    if (b === 0) {
        // Using SamResult class to generate Option
        return SamResult.err("Division by zero is not allowed.").toOption();
    }
    return SamResult.ok(a / b).toOption();
}

// Usage
const result = divide(10, 2);
if (result.type === "success") {
    console.log(`Result: ${result.data}`); // Output: Result: 5
} else {
    console.error(`Error: ${result.reason}`);
}

const errorResult = divide(10, 0);
if (errorResult.type === "error") {
    console.error(`Error: ${errorResult.reason}`); // Output: Error: Division by zero is not allowed.
}

SAM Func

SAM Func provides type definitions for handling both synchronous and asynchronous functions within SAM Actions.

SamExplicitFunc

SamExplicitFunc is a type that represents an explicit function structure, typically used to wrap functions or operations with additional metadata such as thrown errors.

Use Case in OOP and Testing

SamExplicitFunc is particularly useful in object-oriented programming (OOP) where interfaces define the expected behavior of classes, including the exceptions they might throw. By using SamExplicitFunc, you can explicitly specify the possible errors that a function can throw when implementing an interface. This enhances type safety and clarity, especially when creating mock classes for testing.

Example:

import { SamExplicitFunc, SamError, SamOption } from "@osaedasia/sam";

// Define custom errors
class NotFoundError extends SamError {}
class ValidationError extends SamError {}

// Define an interface with methods that specify possible errors
interface IUserService {
    getUser(id: number): SamExplicitFunc<User, [NotFoundError]>;
    createUser(userData: CreateUserDTO): SamExplicitFunc<User, [ValidationError]>;
}

// Implement the interface
class UserService implements IUserService {
    getUser(id: number): SamExplicitFunc<User, [NotFoundError]> {
        if (id <= 0) {
            throw new NotFoundError("User not found.");
        }
        // Assume we fetch the user successfully
        return SamResult.ok({ id, name: "John Doe" }).toOption();
    }

    createUser(userData: CreateUserDTO): SamExplicitFunc<User, [ValidationError]> {
        if (!userData.name) {
            throw new ValidationError("Name is required.");
        }
        // Assume user is created successfully
        return SamResult.ok({ id: 1, name: userData.name }).toOption();
    }
}

// Mocking the service for testing
class MockUserService implements IUserService {
    getUser(id: number): SamExplicitFunc<User, [NotFoundError]> {
        if (id <= 0) {
            throw new NotFoundError("User not found.");
        }
        return SamResult.ok({ id: 1, name: "Mock User" }).toOption();
    }

    createUser(userData: CreateUserDTO): SamExplicitFunc<User, [ValidationError]> {
        if (!userData.name) {
            throw new ValidationError("Name is required.");
        }
        return SamResult.ok({ id: 2, name: userData.name }).toOption();
    }
}

In this example:

  • Interface Definition: The IUserService interface defines methods that return SamExplicitFunc, explicitly stating the possible errors each method can throw.
  • Implementation: The UserService class implements the interface, ensuring that it throws the specified errors.
  • Mocking for Tests: The MockUserService class can be used in tests to simulate different scenarios without relying on real implementations knowing what errors to throws, enhancing test reliability and isolation.

Reporting Issues

If you encounter any issues while using the library, feel free to report them by creating an issue on the GitLab repository. Since I’m the sole maintainer, please provide as much detail as possible to help me resolve the issue efficiently. Make sure to include:

  • Clear description of the problem.
  • Steps to reproduce the issue.
  • library version.
  • Relevant information such as:
    • Stack trace.
    • Environment details (e.g., operating system, Node.js version, Deno version, etc.).
    • Code snippets where the issue occurs.

Your detailed feedback is crucial in improving the library for everyone.

LICENCE

MIT License

Copyright (c) 2024 Osaedasia

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.