Skip to content

Quick Reference

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' | UnexpectedError
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));

Inside a workflow callback use ({ step, deps }) => { ... } when the workflow has deps:

// Unwrap an AsyncResult (cache by passing { key })
const user = await step('fetchUser', () => fetchUser('1'), { key: 'user:1' });
// Chain — just call step again with the success value
const enriched = await step('enrich', () => enrichUser(user));
// Pattern match — branch on the unwrapped value
const msg = user.name ? `Hello ${user.name}` : 'Failed';
import { zip, zipAsync, andThen } from 'awaitly';
// Sync: combine two Results
const combined = zip(userResult, postsResult);
// combined: Result<[User, Post[]], UserError | PostsError>
// Async: run two fetches in parallel
const data = await zipAsync(fetchUser('1'), fetchPosts('1'));
if (data.ok) {
const [user, posts] = data.value;
}
// Chain with andThen
const dashboard = andThen(
zip(userResult, postsResult),
([user, posts]) => createDashboard(user, posts)
);
import { createSagaWorkflow } from 'awaitly/saga';
const saga = createSagaWorkflow('checkout', { charge, refund, reserve, release });
const result = await saga.run(async ({ step, deps }) => {
const payment = await step('charge', () => deps.charge({ amount: 100 }), {
compensate: (p) => deps.refund({ id: p.id }),
});
// If a later step fails, charge is automatically refunded (LIFO order)
const reservation = await step('reserve', () => deps.reserve({ items }), {
compensate: (r) => deps.release({ id: r.id }),
});
return { payment, reservation };
});
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 step
const 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);
}
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 });
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', initialDelay: 100 }
);
return data;
});
const result = await workflow.run(async ({ step, deps }) => {
// Timeout after 5 seconds
const data = await step.withTimeout(
'slowOp',
() => slowOperation(),
{ ms: 5000 }
);
return data;
});
// 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 }) }
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);
}
import { singleflight } from 'awaitly/singleflight';
const fetchUserOnce = singleflight(fetchUser, {
key: (id) => `user:${id}`,
});
// 3 concurrent calls → 1 network request
const [a, b, c] = await Promise.all([
fetchUserOnce('1'),
fetchUserOnce('1'), // Shares request
fetchUserOnce('1'), // Shares request
]);
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}%`) }
);
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
}
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']);

Need Import from
Result types only (minimal bundle) awaitly/result
Standalone retry for async/Result (no workflow) awaitly/result/retry (tryAsyncRetry, tryAsyncBoundary)
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

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, tryAsyncBoundary (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

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.

// Define error types per domain
type UserError = 'NOT_FOUND' | 'SUSPENDED';
type PaymentError = 'DECLINED' | 'EXPIRED' | 'LIMIT_EXCEEDED';
// Workflows automatically union all possible errors
const workflow = createWorkflow('workflow', { fetchUser, chargeCard });
// result.error is: UserError | PaymentError | UnexpectedError
import type { ErrorOf, Errors } from 'awaitly';
type FetchUserError = ErrorOf<typeof fetchUser>; // 'NOT_FOUND' | 'SUSPENDED'
type AllErrors = Errors<[typeof fetchUser, typeof chargeCard]>; // Union of all
import { unwrap, unwrapOr, unwrapOrElse, UnwrapError } from 'awaitly';
// Throws UnwrapError if err
const user = unwrap(result);
// Returns default if err
const user = unwrapOr(result, defaultUser);
// Compute default from error
const user = unwrapOrElse(result, (error) => createGuestUser(error));
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 match
const message = match(result, {
ok: (user) => `Hello, ${user.name}!`,
err: (error) => `Failed: ${error}`,
});
import { TaggedError } from 'awaitly';
import { TimeoutError, RetryExhaustedError, ValidationError, isAwaitlyError } from 'awaitly/errors';
// Create typed errors
const timeout = new TimeoutError({ operation: 'fetchUser', ms: 5000 });
const validation = new ValidationError({ field: 'email', reason: 'Invalid format' });
// Pattern match on errors
const 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 guard
if (isAwaitlyError(error)) {
console.log('Awaitly error:', error._tag);
}

Topic Guide
Common issues Troubleshooting
Framework setup Framework Integrations
Production best practices Production Deployment
Complete API API Reference