A lightweight utility to handle error in your applications.
The library is available through the following package managers:
- 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.
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.
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!
}
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
}
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 provides a custom error class to standardize error handling within your application.
To create a custom error, extend the SamError
class:
import {SamError} from "@osaedasia/sam";
// Default SamError Constructor
class CustomError extends SamError {}
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');
});
-
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 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
.
-
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.
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.
}
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 provides type definitions for handling both synchronous and asynchronous functions within SAM Actions.
SamExplicitFunc
is a type that represents an explicit function structure, typically used to wrap functions or operations with additional metadata such as thrown errors.
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.
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.
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.