@rotomeca/rop
v5.0.2
Published
A robust, lightweight, and type-safe implementation of Railway Oriented Programming (ROP) for TypeScript.
Downloads
895
Readme
🚂 TS Railway Result
A robust, lightweight, and type-safe implementation of Railway Oriented Programming (ROP) for TypeScript.
Eliminate try/catch spaghetti code and handle errors elegantly using Results and Decorators.
📦 Installation
npm install @rotomeca/rop
# or
yarn add @rotomeca/rop🚀 Quick Start
Transform your risky operations into safe "Rails" using decorators.
import { Risky, HappyPath, ErrorPath, Result } from '@rotomeca/rop';
class UserService {
@ErrorPath((err) => console.error(`[Admin Alert] Failed to create user: ${err.message}`))
@HappyPath((user) => console.log(`[Analytics] User created: ${user.name}`))
@Risky() // Automatically catches exceptions and converts return value to Result
async createUser(name: string, age: number): Promise<Result<{ name: string }>> {
if (name.length < 3) {
throw new Error("Name too short"); // Will be converted to Fail(Error)
}
// Simulate DB call
return { name }; // Will be converted to Success({ name })
}
}
// Usage
async function main() {
const service = new UserService();
const result = await service.createUser("Alice", 25);
result.match({
Ok: (user) => console.log(`Welcome ${user.name}!`),
Err: (error) => console.log(`Oops: ${error.message}`)
});
}✨ Features
- 🛡️ Type-Safe Error Handling: No more guessing if a function throws. Returns
Result<T, E>. - ✨ Powerful Decorators:
@Risky: Wraps methods to catch exceptions and returnSuccessorFail.@HappyPath: Execute side effects (logging, events) only on success.@ErrorPath: Execute side effects (monitoring, alerts) only on failure.
- ⛓️ Fluent API: Chain operations with
map,andThen(flatMap), andunwrapOr. - 🔗 Sync & Async Support: Works seamlessly with both synchronous functions and Promises.
📖 Core Concepts
1. The Result Object (Result)
Instead of throwing exceptions, your functions return an Result. It has two states:
Success<T>: The operation succeeded and contains a value of typeT.Fail<E>: The operation failed and contains an error of typeE(default isError).
import { Success, Fail, Result } from '@rotomeca/rop';
function divide(a: number, b: number): Result<number> {
if (b === 0) {
return Fail.Create(new Error("Cannot divide by zero"));
}
return Success.Create(a / b);
}2. Matching (Pattern Matching)
Force yourself to handle both success and failure cases.
const result = divide(10, 2);
const message = result.match({
Ok: (val) => `Result is ${val}`,
Err: (err) => `Calculation failed: ${err.message}`
});3. Decorators
@Risky()
Automatically wraps the method execution in a try/catch block.
- If the method returns a value, it becomes
Success(value). - If the method throws, it becomes
Fail(error). - Works with
async/awaitand synchronous code.
@RiskyPath()
Same as Risky, but use it with ErrorPath and HappyPath.
@HappyPath(fn) & @ErrorPath(fn)
Perfect for Side Effects (logging, notifications) without polluting your business logic. These decorators act as "Taps": they inspect the result but do not modify the return value.
@ErrorPath((e) => Sentry.captureException(e)) // Only if fails
@HappyPath((data) => Analytics.track('success', data)) // Only if success
@RiskyPath()
saveData(data: any) { ... }Note: To use the decorators, ensure you have disabled experimentalDecorators in your tsconfig.json:
{
"compilerOptions": {
"experimentalDecorators": false
}
}📚 API Reference
Result<T, E> Methods
| Method | Description |
| :--- | :--- |
| match({ Ok, Err }) | Executes Ok fn if success, Err fn if failure. Returns the result of the function. |
| map(fn) | Transforms the inner value T to U only if Success. |
| mapError(fn) | Transforms the inner error E to NewError only if Fail. |
| andThen(fn) | Chains operations. fn must return a new Result. (Often called flatMap). |
| unwrapOr(default) | Returns the value if Success, otherwise returns default. |
| throwIfError() | Unsafe. Returns value if Success, throws the error if Fail. |
| isSuccess() / isError() | Boolean checks for the state. |
🧩 Advanced Usage: Chaining
You can build complex pipelines that stop at the first error:
const result = validateInput(input) // returns Result<Input>
.andThen(parsed => processData(parsed)) // returns Result<Data>
.andThen(data => saveToDb(data)) // returns Result<Id>
.map(id => `Created object with ID: ${id}`); // returns Result<string>
// If any step failed, 'result' is a Fail with the error of that step.
// If all succeeded, 'result' is a Success with the final string.📄 License
MIT
