A powerful and fluent Result type implementation for TypeScript, providing a clean and expressive way to handle success and failure states in your code.
- π― Type-safe Result handling
- π Chainable fluent API
- β‘ Async support with ResultAsync
- π‘οΈ Comprehensive error handling
- π¦ Zero dependencies
- π¨ Clean and expressive syntax
- π Generic context type support
- π Rich metadata handling
# Using npm
npm install tsfluent
# Using yarn
yarn add tsfluent
# Using pnpm
pnpm add tsfluent
# Using bun
bun add tsfluent
import { Result } from "tsfluent";
// Creating a success result
const success = Result.ok("Hello, World!");
// Creating a failure result
const failure = Result.fail("Something went wrong");
// Checking result state using properties
if (success.isSuccess) {
console.log(success.value); // "Hello, World!"
}
if (failure.isFailure) {
console.log(failure.errors[0].message); // "Something went wrong"
}
// Define your custom context type
interface UserContext {
userId: string;
timestamp: Date;
environment: "dev" | "prod";
}
// Create a result with typed context
const result = Result.ok<number, UserContext>(42).withMetadata({
context: {
userId: "123",
timestamp: new Date(),
environment: "prod",
},
});
// Type-safe access to context
const context = result.metadata?.context; // Type is UserContext | undefined
const result = Result.ok(5)
.withValue(10)
.withSuccess("Value updated")
.withMetadata({
timestamp: new Date(),
context: { source: "user-input" },
});
// Access the final value using property
if (result.isSuccess) {
console.log(result.value); // 10
console.log(result.getSuccesses()); // [{message: "Value updated", timestamp: Date}]
console.log(result.metadata); // {timestamp: 2025-02-13T10:34:13.928Z, context: { source: "user-input", },}
}
import { ResultAsync } from "tsfluent";
interface IUser {
id: number;
name: string;
username: string;
email: string;
address: {
street: string;
suite: string;
city: string;
zipcode: string;
geo: {
lat: string;
lng: string;
};
};
phone: string;
website: string;
company: {
name: string;
catchPhrase: string;
bs: string;
};
}
interface IApiContext {
requestId: string;
endpoint: string;
}
// Create an async result from a Promise
const asyncResult = await ResultAsync.from<IUser[]>(
fetch("https://jsonplaceholder.typicode.com/users").then((res) => res.json())
);
asyncResult.withMetadata({
context: {
requestId: "uniquerequestid",
endpoint: "/users",
},
});
// Handle the result using properties
if (asyncResult.isSuccess) {
const data = await asyncResult.value;
console.log("Response:", data);
} else {
console.error(asyncResult.errors);
}
// Alternative: Use okAsync directly with context
const resultWithContext = await ResultAsync.okAsync<IUser[], IApiContext>(
fetch("https://jsonplaceholder.typicode.com/users").then((res) => res.json()),
{
metadata: {
context: {
requestId: "youruniqueRequestId",
endpoint: "/users",
},
},
}
);
if (resultWithContext.isSuccess) {
const users = await resultWithContext.value;
console.log(users.map((user) => user.name));
}
import { AsyncUtils } from "tsfluent";
// Using tryAsync with retry configuration
const result = await AsyncUtils.tryAsync<string>(
async () => {
// Some async operation that might fail
throw new Error("Oops!");
},
3, // maxAttempts
1000, // delayMs
2000 // timeoutMs (optional)
);
// Handle errors with retry information
if (result.isFailure) {
console.error(result.errors); // Array of errors with timestamps
const context = result.errors[0].context; // Access retry context
if (context) {
console.log(
`Failed after ${context.attempts} of ${context.maxAttempts} attempts`
);
console.log(`Delay between attempts: ${context.delayMs}ms`);
if (context.timeoutMs) {
console.log(`Operation timed out after ${context.timeoutMs}ms`);
}
}
}
import { ResultAsync } from "tsfluent";
interface ApiContext {
requestId: string;
endpoint: string;
timestamp: Date;
}
// Create a successful async result with context
const result = await ResultAsync.okAsync<Response, ApiContext>(
fetch("https://api.example.com/data"),
{
metadata: {
context: {
requestId: "req-123",
endpoint: "/data",
timestamp: new Date(),
},
},
}
);
// Chain operations with type safety
const processedResult = await result
.map((response) => response.json()) // Transform the value
.onSuccess(async (data) => {
console.log("Data processed:", data);
console.log("Request ID:", result.metadata?.context?.requestId);
})
.onFailure((errors) => {
console.error("Failed:", errors);
// Context is preserved in errors
console.log("Failed endpoint:", errors[0].context?.endpoint);
});
// Convert to synchronous Result
const syncResult = await result.toResult();
import { ResultAsync, AsyncUtils } from "tsfluent";
// Combine multiple async results
const results = [
ResultAsync.okAsync(fetchUser(1)),
ResultAsync.okAsync(fetchUser(2)),
ResultAsync.okAsync(fetchUser(3)),
];
const combined = await AsyncUtils.combine(results);
if (combined.isSuccess) {
const users = await combined.value; // Array of users
console.log("All users fetched:", users);
} else {
console.error("Some fetches failed:", combined.errors);
}
// Using merge for sync results
const syncResults = [Result.ok(1), Result.ok(2), Result.ok(3)];
const merged = Result.merge(syncResults);
console.log(merged.value); // [1, 2, 3]
The main Result type that wraps a success value of type T with an optional context type TContext.
isSuccess: boolean
- Whether the result is successfulisFailure: boolean
- Whether the result is a failurevalue: T
- Gets the success value (throws if accessing on failure)errors: IError[]
- Gets the array of errorsmetadata?: IResultMetadata<TContext>
- Gets the metadata with typed context
ok<T, TContext>(value: T, options?: IResultOptions<TContext>): Result<T, TContext>
- Creates a success resultfail<T, TContext>(error: string | IError | IError[]): Result<T, TContext>
- Creates a failure resultmerge<T, TContext>(results: Result<T, TContext>[]): Result<T[], TContext>
- Merges multiple results
withError(error: string | IError): Result<T, TContext>
- Adds an errorwithSuccess(success: string | ISuccess): Result<T, TContext>
- Adds a success messagewithValue(value: T): Result<T, TContext>
- Sets the valuewithMetadata(metadata: IResultMetadata<TContext>): Result<T, TContext>
- Adds metadatawithContext(context: Record<string, unknown>): Result<T, TContext>
- Adds context to the last errortoPromise(): Promise<T>
- Converts to a Promise
Asynchronous version of Result with Promise support and context type.
isSuccess: boolean
- Whether the result is successfulisFailure: boolean
- Whether the result is a failurevalue: Promise<T>
- Gets the success value (throws if accessing on failure)errors: IError[]
- Gets the array of errorsmetadata?: IResultMetadata<TContext>
- Gets the metadata with typed context
okAsync<T, TContext>(value: T | Promise<T>, options?: IResultOptions<TContext>): Promise<ResultAsync<T, TContext>>
- Creates a success resultfailAsync<T, TContext>(error: string | IError | IError[]): Promise<ResultAsync<T, TContext>>
- Creates a failure resultfrom<T>(promise: Promise<Result<T> | T>): Promise<ResultAsync<T>>
- Creates from a PromisefromResult<T, TContext>(result: Result<T, TContext>): ResultAsync<T, TContext>
- Creates from a Resultmerge<T, TContext>(results: ResultAsync<T, TContext>[]): Promise<ResultAsync<T[], TContext>>
- Merges multiple results
withError(error: string | IError): ResultAsync<T, TContext>
- Adds an errorwithSuccess(success: string | ISuccess): ResultAsync<T, TContext>
- Adds a success messagewithValue(value: T | Promise<T>): Promise<ResultAsync<T, TContext>>
- Sets the valuewithMetadata(metadata: IResultMetadata<TContext>): ResultAsync<T, TContext>
- Adds metadatawithContext(context: Record<string, unknown>): ResultAsync<T, TContext>
- Adds context to the last errortoResult(): Promise<Result<T, TContext>>
- Converts to a synchronous Resultmap<U>(func: (value: T) => Promise<U> | U): Promise<ResultAsync<U, TContext>>
- Maps the valuebind<U>(func: (value: T) => Promise<ResultAsync<U, TContext>>): Promise<ResultAsync<U, TContext>>
- Chains resultstap(action: (value: T) => void | Promise<void>): Promise<ResultAsync<T, TContext>>
- Executes side effectsonSuccess(callback: (value: T) => void | Promise<void>): Promise<ResultAsync<T, TContext>>
- Success callbackonFailure(callback: (errors: IError[]) => void | Promise<void>): Promise<ResultAsync<T, TContext>>
- Failure callback
Utility class for working with asynchronous operations.
try<T>(action: () => Promise<T>, options?: IResultOptions): Promise<Result<T>>
- Wraps an async operationtryAsync<T>(action: () => Promise<T>, maxAttempts?: number, delayMs?: number, timeoutMs?: number): Promise<Result<T>>
- Retries an async operation with timeoutcombine<T>(asyncResults: ResultAsync<T>[], options?: IResultOptions): Promise<ResultAsync<T[]>>
- Combines multiple resultstimeout<T>(action: () => Promise<T>, timeoutMs: number): Promise<Result<T>>
- Executes with timeoutretry<T>(action: () => Promise<T>, retries?: number, delay?: number): Promise<Result<T>>
- Retries with delay
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
- Fork the repository
- Create your feature branch (`git checkout -b feature/AmazingFeature`)
- Commit your changes (`git commit -m 'Add some AmazingFeature'`)
- Push to the branch (`git push origin feature/AmazingFeature`)
- Open a Pull Request
# Clone the repository
git clone https://github.com/Adedoyin-Emmanuel/tsresult.git
# Install dependencies
bun install
# Run tests
bun test
# Build the project
bun run build
This project is licensed under the MIT License - see the LICENSE file for details.
Tsfluent takes heavy inspiration from the following projects:
If you find this package helpful, please give it a βοΈ on GitHub!