@temporal-contract/boxed
v0.2.0
Published
Custom Future and Result implementation for Temporal workflows
Maintainers
Readme
@temporal-contract/boxed
Custom Future and Result implementation for Temporal workflows, providing type-safe error handling and async operations compatible 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 of the Result/Future patterns with an identical API surface.
Installation
pnpm add @temporal-contract/boxedBasic Usage
Result Pattern
The Result type provides explicit error handling without exceptions:
import { Result } from "@temporal-contract/boxed";
// Create results
const success = Result.Ok(42);
const failure = Result.Error("Something went wrong");
// Pattern matching
const value = success.match({
Ok: (value) => value * 2,
Error: (error) => 0,
});
// Transformations
const doubled = success.map((x) => x * 2);
const recovered = failure.mapError((e) => `Error: ${e}`);Future Pattern
The Future type wraps Promises with Result-based error handling:
import { Future, Result } from "@temporal-contract/boxed";
// Create futures
const future = Future.value(42);
const fromPromise = Future.fromPromise(fetch("/api/data"));
// Transform values
const doubled = future.map((x) => x * 2);
const chained = future.flatMap((x) => Future.value(x * 2));
// Work with Results in Futures
const result = await Future.fromPromise(asyncOperation());
result.match({
Ok: (value) => console.log("Success:", value),
Error: (error) => console.error("Failed:", error),
});Usage in Temporal Workflows
Activities
Activities return Future<Result<T, ActivityError>> for explicit error handling:
import { Future, Result } from "@swan-io/boxed";
import { declareActivitiesHandler, ActivityError } from "@temporal-contract/worker/activity";
export const activities = declareActivitiesHandler({
contract,
activities: {
processOrder: {
processPayment: (args) => {
return Future.fromPromise(paymentService.charge(args))
.mapError(
(error) =>
new ActivityError(
"PAYMENT_FAILED",
error instanceof Error ? error.message : String(error),
error,
),
)
.mapOk((result) => ({ transactionId: result.id }));
},
},
},
});Workflows
import { declareWorkflow } from "@temporal-contract/worker/workflow";
export const processOrder = declareWorkflow({
workflowName: "processOrder",
contract,
activityOptions: { startToCloseTimeout: "1 minute" },
implementation: async ({ activities }, input) => {
// Activities return plain values (Result is unwrapped by framework)
const payment = await activities.processPayment(input);
// Workflow returns plain object (serializable for Temporal)
return { success: true, transactionId: payment.transactionId };
},
});Interoperability with @swan-io/boxed
This package provides bi-directional interoperability with @swan-io/boxed for smooth migration and compatibility.
Default Compatibility (Recommended)
Our Result and Future types implement the same interface as @swan-io/boxed, making them compatible by default:
import { Result, Future } from "@temporal-contract/boxed";
// Your types are already compatible with @swan-io/boxed consumers
const result = Result.Ok(42);
const future = Future.value(42);
// These work with any library expecting @swan-io/boxed types
function processSwanResult(r: SwanResult<number, string>) {
return r.match({
Ok: (v) => v * 2,
Error: () => 0,
});
}
processSwanResult(result); // ✅ Works directlyExplicit Converters
For cases where you need explicit conversion, use the interop module:
import { Result, Future } from "@temporal-contract/boxed";
import {
fromSwanResult,
toSwanResult,
fromSwanFuture,
toSwanFuture,
fromSwanFutureResult,
toSwanFutureResult,
} from "@temporal-contract/boxed/interop";
// Convert from @swan-io/boxed to @temporal-contract/boxed
const swanResult = externalLibrary.getSomething();
const temporalResult = fromSwanResult(swanResult);
// Convert from @temporal-contract/boxed to @swan-io/boxed
const temporalResult = Result.Ok(42);
const swanCompatible = toSwanResult(temporalResult);
externalLibrary.processSomething(swanCompatible);Interop API Reference
Result Converters:
fromSwanResult<T, E>(swanResult)- Convert @swan-io/boxed Result to @temporal-contract/boxed ResulttoSwanResult<T, E>(temporalResult)- Convert @temporal-contract/boxed Result to @swan-io/boxed compatible Result
Future Converters:
fromSwanFuture<T>(swanFuture)- Convert @swan-io/boxed Future to @temporal-contract/boxed FuturetoSwanFuture<T>(temporalFuture)- Convert @temporal-contract/boxed Future to @swan-io/boxed compatible Future
Future Converters:
fromSwanFutureResult<T, E>(swanFutureResult)- Convert @swan-io/boxed Future to @temporal-contract/boxed FuturetoSwanFutureResult<T, E>(temporalFutureResult)- Convert @temporal-contract/boxed Future to @swan-io/boxed compatible Future
Note:
@swan-io/boxedis an optional peer dependency and only needed if you're explicitly converting between implementations.
API Reference
Result<T, E>
Result.Ok<T>(value: T)- Create a successful resultResult.Error<E>(error: E)- Create an error resultisOk()- Check if result is OkisError()- Check if result is Errormatch<R>(pattern)- Pattern match on resultmap<U>(fn)- Transform Ok valuemapError<F>(fn)- Transform Error valueflatMap<U>(fn)- Chain resultsflatMapOk<U>(fn)- Alias for flatMapgetOr(defaultValue)- Get value or defaultgetWithDefault(defaultValue)- Alias for getOrtoOption()- Convert to Option typeResult.fromExecution(fn)- Create result from synchronous function that may throwResult.fromAsyncExecution(fn)- Create result from async function that may throwResult.all(results)- Combine array of results into result of array
Future<T>
Future.value<T>(value)- Create resolved futureFuture.fromPromise<T>(promise)- Create future from promise (returnsFuture<Result<T, unknown>>)Future.make<T>(executor)- Create future from executor functionFuture.reject<T>(error)- Create rejected futureFuture.fromAsync<T>(fn)- Create future from async functionFuture.all(futures)- Combine multiple futuresFuture.race(futures)- Race multiple futuresmap<U>(fn)- Transform future valueflatMap<U>(fn)- Chain futuresmapOk<U>(fn)- Transform Ok value in FuturemapError<F>(fn)- Transform Error value in FutureflatMapOk<U>(fn)- FlatMap over Ok value in Futuretap(fn)- Execute side effecttapOk(fn)- Execute side effect on OktapError(fn)- Execute side effect on ErrortoPromise()- Convert to Promise
TypeScript Support
This package is written in TypeScript and provides full type safety:
// 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);Testing
cd packages/boxed
pnpm testAll tests verify:
- Result operations and transformations
- Future operations and async behavior
- Interoperability with @swan-io/boxed
- Type safety and compatibility
Documentation
📖 Read the full documentation →
License
MIT
