@temporal-contract/boxed
Custom Future and Result implementation for Temporal workflows, providing type-safe error handling.
Installation
pnpm add @temporal-contract/boxedOverview
The @temporal-contract/boxed package provides Temporal-compatible implementations of the Result/Future patterns. These types are specifically designed to work with Temporal's deterministic execution model.
Why This Package?
The @swan-io/boxed library doesn't work properly with Temporal workflows due to Temporal's deterministic execution requirements. This package provides a Temporal-compatible implementation with an identical API surface.
Result<T, E>
The Result type represents the result of an operation that can succeed or fail.
Creating Results
import { Result } from '@temporal-contract/boxed';
// Create a successful result
const success = Result.Ok(42);
// Create an error result
const failure = Result.Error("Something went wrong");Pattern Matching
const value = result.match({
Ok: (value) => value * 2,
Error: (error) => 0,
});Transformations
// Transform success value
const doubled = success.map(x => x * 2);
// Transform error value
const recovered = failure.mapError(e => `Error: ${e}`);
// Chain operations
const chained = result.flatMap(value => {
if (value > 0) return Result.Ok(value * 2);
return Result.Error("Value must be positive");
});Checking State
if (result.isOk()) {
console.log('Success:', result.value);
}
if (result.isError()) {
console.error('Error:', result.error);
}Future<T>
The Future type wraps Promises with Result-based error handling.
Creating Futures
import { Future, Result } from '@temporal-contract/boxed';
// Create from value
const future = Future.value(42);
// Create from Promise
const fromPromise = Future.fromPromise(fetch('/api/data'));
// Create with executor
const custom = Future.make<number>(async (resolve) => {
const result = await someAsyncOperation();
resolve(Result.Ok(result));
});Transformations
// Transform value
const doubled = future.map(x => x * 2);
// Chain futures
const chained = future.flatMap(x => Future.value(x * 2));
// Transform Ok/Error separately (for Future<Result<T, E>>)
const handled = future
.mapOk(value => value * 2)
.mapError(error => new CustomError(error));Side Effects
// Execute side effect
await future.tap(value => console.log('Value:', value));
// Execute on Ok
await future.tapOk(value => console.log('Success:', value));
// Execute on Error
await future.tapError(error => console.error('Failed:', error));Combining Futures
// Wait for all
const results = await Future.all([future1, future2, future3]);
// Race
const winner = await Future.race([future1, future2]);Usage in Temporal
In Activities
Activities should return Future<Result<T, E>> for explicit error handling:
import { Future, Result } from '@temporal-contract/boxed';
import { declareActivitiesHandler, ActivityError } from '@temporal-contract/worker/activity';
export const activities = declareActivitiesHandler({
contract,
activities: {
processPayment: ({ amount }) => {
return Future.make(async (resolve) => {
try {
const txId = await paymentService.charge(amount);
resolve(Result.Ok({ transactionId: txId }));
} catch (error) {
resolve(Result.Error(
new ActivityError('PAYMENT_FAILED', 'Failed to process payment', error)
));
}
});
},
},
});In Workflows
Workflows work with Results returned by activities:
import { declareWorkflow } from '@temporal-contract/worker/workflow';
import { Result } from '@temporal-contract/boxed';
export const workflow = declareWorkflow({
workflowName: 'processOrder',
contract,
implementation: async (context, input) => {
const paymentResult = await context.activities.processPayment({
amount: input.amount
});
return paymentResult.match({
Ok: (payment) => Result.Ok({
success: true,
transactionId: payment.transactionId
}),
Error: (error) => Result.Error({
type: 'PAYMENT_FAILED',
message: error.message
}),
});
},
});API Reference
Result Methods
Result.Ok<T>(value: T)- Create a successful resultResult.Error<E>(error: E)- Create an error resultisOk(): boolean- Check if result is OkisError(): boolean- Check if result is Errormatch<R>(pattern: { Ok: (value: T) => R, Error: (error: E) => R }): R- Pattern matchmap<U>(fn: (value: T) => U): Result<U, E>- Transform Ok valuemapError<F>(fn: (error: E) => F): Result<T, F>- Transform Error valueflatMap<U>(fn: (value: T) => Result<U, E>): Result<U, E>- Chain resultsgetOr(defaultValue: T): T- Get value or defaulttoOption(): Option<T>- Convert to Option type
Future Methods
Future.value<T>(value: T): Future<T>- Create resolved futureFuture.fromPromise<T>(promise: Promise<T>): Future<Result<T, Error>>- Create from promiseFuture.make<T>(executor: (resolve: (value: T) => void) => Promise<void>): Future<T>- Create with executorFuture.reject<T>(error: Error): Future<Result<never, Error>>- Create rejected futureFuture.all<T>(futures: Future<T>[]): Future<T[]>- Combine futuresFuture.race<T>(futures: Future<T>[]): Future<T>- Race futuresmap<U>(fn: (value: T) => U): Future<U>- Transform valueflatMap<U>(fn: (value: T) => Future<U>): Future<U>- Chain futuresmapOk<U>(fn: (value: T) => U): Future<Result<U, E>>- Transform Ok valuemapError<F>(fn: (error: E) => F): Future<Result<T, F>>- Transform Error valuetap(fn: (value: T) => void): Future<T>- Execute side effecttapOk(fn: (value: T) => void): Future<Result<T, E>>- Execute side effect on OktapError(fn: (error: E) => void): Future<Result<T, E>>- Execute side effect on Error
Interoperability
This package provides interoperability with @swan-io/boxed through the /interop entry point:
import {
fromSwanResult,
toSwanResult,
fromSwanFuture,
toSwanFuture,
} from '@temporal-contract/boxed/interop';
// Convert from @swan-io/boxed
const temporalResult = fromSwanResult(swanResult);
const temporalFuture = fromSwanFuture(swanFuture);
// Convert to @swan-io/boxed compatible types
const swanCompatible = toSwanResult(temporalResult);
const swanFutureCompatible = toSwanFuture(temporalFuture);TypeScript Support
Full type safety with automatic type inference:
// Type inference works automatically
const result = Result.Ok(42); // Result<number, never>
const error = Result.Error("failed"); // Result<never, string>
// Generic types can be specified explicitly
const typed: Result<number, string> = Result.Ok(42);