A robust, lightweight, and type-safe implementation of Railway Oriented Programming (ROP) for TypeScript.
npm install @rotomeca/ropA 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.
``bash`
npm install @rotomeca/ropor
yarn add @rotomeca/rop
Transform your risky operations into safe "Rails" using decorators.
`typescript
import { Risky, HappyPath, ErrorPath, Result } from '@rotomeca/rop';
class UserService {
@ErrorPath((err) => console.error([Admin Alert] Failed to create user: ${err.message}))[Analytics] User created: ${user.name}
@HappyPath((user) => console.log())
@Risky() // Automatically catches exceptions and converts return value to Result
async createUser(name: string, age: number): Promise
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}!),Oops: ${error.message}
Err: (error) => console.log()`
});
}
* 🛡️ Type-Safe Error Handling: No more guessing if a function throws. Returns Result.@Risky
* ✨ Powerful Decorators:
* : Wraps methods to catch exceptions and return Success or Fail.@HappyPath
* : Execute side effects (logging, events) only on success.@ErrorPath
* : Execute side effects (monitoring, alerts) only on failure.map
* ⛓️ Fluent API: Chain operations with , andThen (flatMap), and unwrapOr.
* 🔗 Sync & Async Support: Works seamlessly with both synchronous functions and Promises.
. It has two states:
* Success: The operation succeeded and contains a value of type T.
* Fail: The operation failed and contains an error of type E (default is Error).`typescript
import { Success, Fail, Result } from '@rotomeca/rop';function divide(a: number, b: number): Result {
if (b === 0) {
return Fail.Create(new Error("Cannot divide by zero"));
}
return Success.Create(a / b);
}
`$3
Force yourself to handle both success and failure cases.`typescript
const result = divide(10, 2);const message = result.match({
Ok: (val) =>
Result is ${val},
Err: (err) => Calculation failed: ${err.message}
});
`$3
####
@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/await and 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.`typescript
@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:
`json
{
"compilerOptions": {
"experimentalDecorators": false
}
}
`📚 API Reference
$3
| 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:
`typescript
const result = validateInput(input) // returns Result
.andThen(parsed => processData(parsed)) // returns Result
.andThen(data => saveToDb(data)) // returns Result
.map(id => Created object with ID: ${id}); // returns Result// 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.
``MIT