@osaedasia/sam
v0.4.1
Published
A lightweight utility to handle error in your applications.
Downloads
7
Maintainers
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 extendingSamError
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 returnSamExplicitFunc
, 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.