Quick Reference
I want to…
Section titled “I want to…”Handle errors without exceptions
Section titled “Handle errors without exceptions”For minimal bundle use awaitly/result; for full API (Awaitly namespace, pipe, etc.) use awaitly.
// Minimal: from 'awaitly/result'. Or from 'awaitly' for full API (Awaitly, pipe, etc.)import { ok, err, type AsyncResult } from 'awaitly';
async function fetchUser(id: string): AsyncResult<User, 'NOT_FOUND'> { const user = await db.find(id); return user ? ok(user) : err('NOT_FOUND');}Compose multiple Result-returning functions
Section titled “Compose multiple Result-returning functions”import { createWorkflow } from 'awaitly/workflow';
const workflow = createWorkflow('workflow', { fetchUser, chargeCard });const result = await workflow.run(async ({ step, deps }) => { const user = await step('fetchUser', () => deps.fetchUser('1')); const charge = await step('chargeCard', () => deps.chargeCard(user.id, 100)); return { user, charge };});// First arg = label (literal); optional key = instance (cache/identity)// result.error is: 'NOT_FOUND' | 'CARD_DECLINED' | UnexpectedErrorRun multiple operations in parallel
Section titled “Run multiple operations in parallel”import { allAsync, anyAsync, allSettledAsync } from 'awaitly';import { createWorkflow } from 'awaitly/workflow';
// Inside a workflow: step.all (named results, step tracking)await workflow.run(async ({ step, deps }) => { const { user, posts } = await step.all('fetchAll', { user: () => deps.fetchUser('1'), posts: () => deps.fetchPosts('1'), }); const users = await step.map('fetchUsers', ['1', '2', '3'], (id) => deps.fetchUser(id)); return { user, posts, users };});
// Standalone: allAsync — all must succeed (fail-fast)const [user, posts] = await allAsync([fetchUser('1'), fetchPosts('1')]);
// First success wins (failover pattern)const data = await anyAsync([fetchFromPrimary(), fetchFromBackup()]);
// Collect ALL errors (if any fail)const result = await allSettledAsync([op1(), op2(), op3()]);if (!result.ok) console.log('Errors:', result.error.map(e => e.error));Use Effect-style step helpers (run, andThen, match)
Section titled “Use Effect-style step helpers (run, andThen, match)”Inside a workflow callback use ({ step, deps }) => { ... } when the workflow has deps, e.g. run(async ({ step, deps }) => { ... }):
// Unwrap AsyncResult (use getter when caching: () => fetchUser('1'))const user = await step.run('fetchUser', () => fetchUser('1'), { key: 'user:1' });
// Chain from success valueconst enriched = await step.andThen('enrich', user, (u) => enrichUser(u));
// Pattern match with step trackingconst msg = await step.match('handleUser', userResult, { ok: (user) => `Hello ${user.name}`, err: () => 'Failed',});Combine two Results into a tuple
Section titled “Combine two Results into a tuple”import { zip, zipAsync, andThen } from 'awaitly';
// Sync: combine two Resultsconst combined = zip(userResult, postsResult);// combined: Result<[User, Post[]], UserError | PostsError>
// Async: run two fetches in parallelconst data = await zipAsync(fetchUser('1'), fetchPosts('1'));if (data.ok) { const [user, posts] = data.value;}
// Chain with andThenconst dashboard = andThen( zip(userResult, postsResult), ([user, posts]) => createDashboard(user, posts));Undo completed steps when one fails
Section titled “Undo completed steps when one fails”import { createSagaWorkflow } from 'awaitly/workflow';
const saga = createSagaWorkflow('saga', { charge, refund, reserve, release });const result = await saga(async ({ saga }) => { const payment = await saga.step( () => charge({ amount: 100 }), { name: 'charge', compensate: (p) => refund({ id: p.id }) } ); // If next step fails, charge is automatically refunded (LIFO order) const reservation = await saga.step( () => reserve({ items }), { name: 'reserve', compensate: (r) => release({ id: r.id }) } ); return { payment, reservation };});Wait for human approval
Section titled “Wait for human approval”import { createApprovalStep, isPendingApproval } from 'awaitly/hitl';import { createResumeStateCollector } from 'awaitly/workflow';
const approvalStep = createApprovalStep({ key: 'manager-approval', checkApproval: async () => { const record = await db.approvals.find('workflow-123'); if (!record) return { status: 'pending' }; return record.approved ? { status: 'approved', value: record } : { status: 'rejected' }; },});
const collector = createResumeStateCollector();const workflow = createWorkflow('workflow', deps, { onEvent: collector.handleEvent });
// Workflow pauses at approval stepconst result = await workflow.run(async ({ step, deps }) => { const data = await step('fetchData', () => deps.fetchData()); const approval = await step('approval', approvalStep); return finalize(data);});
if (!result.ok && isPendingApproval(result.error)) { const state = collector.getResumeState(); await store.save(workflowId, state);}Persist and resume workflow state
Section titled “Persist and resume workflow state”import { createWorkflow } from 'awaitly/workflow';import { postgres } from 'awaitly-postgres';
const store = postgres(process.env.DATABASE_URL!);const workflow = createWorkflow('workflow', deps);
const { result, resumeState } = await workflow.runWithState(fn);await store.save(workflowId, resumeState);
// Resume later (use loadResumeState for type-safe restore)const loaded = await store.loadResumeState(workflowId);if (loaded) await workflow.run(fn, { resumeState: loaded });Retry failed operations
Section titled “Retry failed operations”import { createWorkflow } from 'awaitly/workflow';
const workflow = createWorkflow('workflow', deps);const result = await workflow.run(async ({ step, deps }) => { // Retry up to 3 times with exponential backoff const data = await step.retry( 'fetchApi', () => fetchUnreliableAPI(), { attempts: 3, backoff: 'exponential', delayMs: 100 } ); return data;});Add timeouts to operations
Section titled “Add timeouts to operations”const result = await workflow.run(async ({ step, deps }) => { // Timeout after 5 seconds const data = await step.withTimeout( 'slowOp', () => slowOperation(), { ms: 5000 } ); return data;});Timeout behavior variants
Section titled “Timeout behavior variants”// Default: return error on timeout{ ms: 5000, onTimeout: 'error' }
// Return undefined instead of error (optional operation){ ms: 1000, onTimeout: 'option' }
// Return error but let operation finish in background{ ms: 2000, onTimeout: 'disconnect' }
// Custom error handler{ ms: 5000, onTimeout: ({ name, ms }) => ({ _tag: 'Timeout', name, ms }) }Cancel workflow from outside
Section titled “Cancel workflow from outside”import { createWorkflow, isWorkflowCancelled } from 'awaitly/workflow';
const controller = new AbortController();const workflow = createWorkflow('workflow', deps, { signal: controller.signal });
const resultPromise = workflow.run(async ({ step, deps }) => { const user = await step('fetchUser', () => fetchUser('1'), { key: 'user' }); await step('sendEmail', () => sendEmail(user.email), { key: 'email' }); return user;});
// Cancel from outside (e.g., timeout, user action)setTimeout(() => controller.abort('timeout'), 5000);
const result = await resultPromise;if (!result.ok && isWorkflowCancelled(result.cause)) { console.log('Cancelled:', result.cause.reason);}Dedupe concurrent requests
Section titled “Dedupe concurrent requests”import { singleflight } from 'awaitly/singleflight';
const fetchUserOnce = singleflight(fetchUser, { key: (id) => `user:${id}`,});
// 3 concurrent calls → 1 network requestconst [a, b, c] = await Promise.all([ fetchUserOnce('1'), fetchUserOnce('1'), // Shares request fetchUserOnce('1'), // Shares request]);Process large datasets in batches
Section titled “Process large datasets in batches”import { processInBatches, batchPresets } from 'awaitly/batch';
const result = await processInBatches( users, async (user) => migrateUser(user), { batchSize: 50, concurrency: 5 }, { onProgress: (p) => console.log(`${p.percent}%`) });Prevent cascading failures
Section titled “Prevent cascading failures”import { createCircuitBreaker, isCircuitOpenError } from 'awaitly/circuit-breaker';
const breaker = createCircuitBreaker('payment-api', { failureThreshold: 5, resetTimeMs: 30000,});
const result = await breaker.call(() => paymentAPI.charge());if (!result.ok && isCircuitOpenError(result.error)) { // Circuit is open - fail fast without calling the API}Test workflows deterministically
Section titled “Test workflows deterministically”import { createWorkflowHarness, okOutcome, errOutcome } from 'awaitly/testing';
const harness = createWorkflowHarness(deps);harness.script([ okOutcome({ id: '1', name: 'Alice' }), errOutcome('PAYMENT_DECLINED'),]);
const result = await harness.run(async ({ step, deps }) => { const user = await step('fetchUser', () => fetchUser('1')); const charge = await step('chargeCard', () => chargeCard(100)); return { user, charge };});
expect(result.ok).toBe(false);harness.assertSteps(['fetch-user', 'charge-card']);Import Cheatsheet
Section titled “Import Cheatsheet”| Need | Import from |
|---|---|
| Result types only (minimal bundle) | awaitly/result |
| Standalone retry for async/Result (no workflow) | awaitly/result/retry (tryAsyncRetry, RetryConfig) |
Result types + composition (ok, err, isOk, isErr, map, mapError, andThen, tap, from, fromPromise, all, allAsync, partition, match, TaggedError) | awaitly |
| run() for step composition | awaitly/run |
Workflow engine (createWorkflow, Duration, isStepComplete, createResumeStateCollector, isWorkflowCancelled, step types, ResumeState) | awaitly/workflow |
Workflow instance (.run(name?, fn, config?)) | Returned by createWorkflow |
Saga pattern (createSagaWorkflow) | awaitly/workflow |
Parallel ops (allAsync, allSettledAsync, zip, zipAsync) | awaitly |
HITL (pendingApproval, createApprovalStep, gatedStep, injectApproval, isPendingApproval) | awaitly/hitl |
Snapshot store types and validation (SnapshotStore, WorkflowSnapshot, validateSnapshot) | awaitly/persistence |
Batch processing (processInBatches) | awaitly/batch |
| Circuit breaker | awaitly/circuit-breaker |
| Rate limiting | awaitly/ratelimit |
Singleflight (singleflight, createSingleflightGroup) | awaitly/singleflight |
| Testing utilities | awaitly/testing |
| Visualization | awaitly-visualizer (createVisualizer, Mermaid/ASCII/JSON); awaitly-visualizer (optional React UI) |
| Duration helpers | awaitly/workflow |
| Tagged errors | awaitly |
| Pattern matching | awaitly |
Functional utilities (pipe, flow, compose, R namespace) | awaitly/functional |
Pre-built errors (TimeoutError, RetryExhaustedError, RateLimitError, etc.) | awaitly/errors |
Module Sizes
Section titled “Module Sizes”For optimal bundle size, import from specific entry points:
| Entry Point | Use Case |
|---|---|
awaitly/result | Result types only (smallest bundle; sizes in docs are gzipped when given) |
awaitly/result/retry | Result retry: tryAsyncRetry, RetryConfig (no workflow engine) |
awaitly | Result types, transforms for composition |
awaitly/run | run() for step composition |
awaitly/workflow | Workflow engine (createWorkflow, Duration, etc.) |
awaitly/functional | Functional utilities (pipe, flow, compose, R namespace) |
awaitly/hitl | Human-in-the-loop (createApprovalStep, isPendingApproval, etc.) |
awaitly/persistence | Snapshot types, validation, createMemoryCache |
awaitly/batch | Batch processing only |
Decision Matrix
Section titled “Decision Matrix”| Scenario | Pattern | Key APIs |
|---|---|---|
| Linear multi-step operations | Workflow | createWorkflow, step() |
| Steps that may need rollback | Saga | createSagaWorkflow, compensate |
| Independent parallel calls | Parallel | allAsync(), allSettledAsync() |
| First success wins (failover) | Race | anyAsync() |
| Human approval gates | HITL | createApprovalStep(), injectApproval() |
| Cancel from outside | Cancellation | signal, isWorkflowCancelled() |
| Dedupe concurrent requests | Singleflight | singleflight() |
| High-volume processing | Batch | processInBatches() |
| Flaky external APIs | Circuit Breaker | createCircuitBreaker() |
| Rate-limited APIs | Rate Limiter | createRateLimiter() |
| Rich typed errors | Tagged Errors | TaggedError(), TimeoutError, etc. |
| Functional composition | Pipe/Flow | pipe(), flow(), R.map(), etc. |
Common Patterns
Section titled “Common Patterns”Typed error domains
Section titled “Typed error domains”// Define error types per domaintype UserError = 'NOT_FOUND' | 'SUSPENDED';type PaymentError = 'DECLINED' | 'EXPIRED' | 'LIMIT_EXCEEDED';
// Workflows automatically union all possible errorsconst workflow = createWorkflow('workflow', { fetchUser, chargeCard });// result.error is: UserError | PaymentError | UnexpectedErrorExtracting error types from functions
Section titled “Extracting error types from functions”import type { ErrorOf, Errors } from 'awaitly';
type FetchUserError = ErrorOf<typeof fetchUser>; // 'NOT_FOUND' | 'SUSPENDED'type AllErrors = Errors<[typeof fetchUser, typeof chargeCard]>; // Union of allUnwrapping results
Section titled “Unwrapping results”import { unwrap, unwrapOr, unwrapOrElse, UnwrapError } from 'awaitly';
// Throws UnwrapError if errconst user = unwrap(result);
// Returns default if errconst user = unwrapOr(result, defaultUser);
// Compute default from errorconst user = unwrapOrElse(result, (error) => createGuestUser(error));Transforming results
Section titled “Transforming results”import { map, mapError, andThen, match } from 'awaitly';
// Transform value (if ok)const name = map(userResult, user => user.name);
// Transform error (if err)const apiError = mapError(result, error => ({ code: 'API_ERROR', cause: error }));
// Chain operations (flatMap)const posts = andThen(userResult, user => fetchPosts(user.id));
// Pattern matchconst message = match(result, { ok: (user) => `Hello, ${user.name}!`, err: (error) => `Failed: ${error}`,});Using tagged errors
Section titled “Using tagged errors”import { TaggedError } from 'awaitly';import { TimeoutError, RetryExhaustedError, ValidationError, isAwaitlyError } from 'awaitly/errors';
// Create typed errorsconst timeout = new TimeoutError({ operation: 'fetchUser', ms: 5000 });const validation = new ValidationError({ field: 'email', reason: 'Invalid format' });
// Pattern match on errorsconst message = TaggedError.match(error, { TimeoutError: (e) => `Timed out after ${e.ms}ms`, RetryExhaustedError: (e) => `Failed after ${e.attempts} attempts`, ValidationError: (e) => `Invalid ${e.field}: ${e.reason}`,});
// Type guardif (isAwaitlyError(error)) { console.log('Awaitly error:', error._tag);}See Also
Section titled “See Also”| Topic | Guide |
|---|---|
| Common issues | Troubleshooting |
| Framework setup | Framework Integrations |
| Production best practices | Production Deployment |
| Complete API | API Reference |