@osaedasia/sam
TypeScript icon, indicating that this package has built-in type declarations

0.4.1 • Public • Published

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.

Package Sidebar

Install

npm i @osaedasia/sam

Weekly Downloads

0

Version

0.4.1

License

MIT

Unpacked Size

30 kB

Total Files

13

Last publish

Collaborators

  • osaedasia