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

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 value
const enriched = await step.andThen('enrich', user, (u) => enrichUser(u));
// Pattern match with step tracking
const msg = await step.match('handleUser', userResult, {
ok: (user) => `Hello ${user.name}`,
err: () => '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/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 };
});
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', delayMs: 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']);

NeedImport 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 compositionawaitly/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 breakerawaitly/circuit-breaker
Rate limitingawaitly/ratelimit
Singleflight (singleflight, createSingleflightGroup)awaitly/singleflight
Testing utilitiesawaitly/testing
Visualizationawaitly-visualizer (createVisualizer, Mermaid/ASCII/JSON); awaitly-visualizer (optional React UI)
Duration helpersawaitly/workflow
Tagged errorsawaitly
Pattern matchingawaitly
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 PointUse Case
awaitly/resultResult types only (smallest bundle; sizes in docs are gzipped when given)
awaitly/result/retryResult retry: tryAsyncRetry, RetryConfig (no workflow engine)
awaitlyResult types, transforms for composition
awaitly/runrun() for step composition
awaitly/workflowWorkflow engine (createWorkflow, Duration, etc.)
awaitly/functionalFunctional utilities (pipe, flow, compose, R namespace)
awaitly/hitlHuman-in-the-loop (createApprovalStep, isPendingApproval, etc.)
awaitly/persistenceSnapshot types, validation, createMemoryCache
awaitly/batchBatch processing only

ScenarioPatternKey APIs
Linear multi-step operationsWorkflowcreateWorkflow, step()
Steps that may need rollbackSagacreateSagaWorkflow, compensate
Independent parallel callsParallelallAsync(), allSettledAsync()
First success wins (failover)RaceanyAsync()
Human approval gatesHITLcreateApprovalStep(), injectApproval()
Cancel from outsideCancellationsignal, isWorkflowCancelled()
Dedupe concurrent requestsSingleflightsingleflight()
High-volume processingBatchprocessInBatches()
Flaky external APIsCircuit BreakercreateCircuitBreaker()
Rate-limited APIsRate LimitercreateRateLimiter()
Rich typed errorsTagged ErrorsTaggedError(), TimeoutError, etc.
Functional compositionPipe/Flowpipe(), 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);
}

TopicGuide
Common issuesTroubleshooting
Framework setupFramework Integrations
Production best practicesProduction Deployment
Complete APIAPI Reference