Skip to content

Your First Workflow

This guide walks through building a workflow that fetches a user and their posts, with typed error handling.

Operations return AsyncResult<T, E> - either ok(value) or err(error):

import { ok, err, type AsyncResult } from 'awaitly';
type User = { id: string; name: string };
type Post = { id: number; title: string };
const fetchUser = async (id: string): AsyncResult<User, 'NOT_FOUND'> => {
if (id === '1') {
return ok({ id: '1', name: 'Alice' });
}
return err('NOT_FOUND');
};
const fetchPosts = async (userId: string): AsyncResult<Post[], 'FETCH_ERROR'> => {
return ok([
{ id: 1, title: 'Hello World' },
{ id: 2, title: 'Second Post' },
]);
};

Pass your operations to createWorkflow. Error types are inferred automatically:

import { createWorkflow } from 'awaitly/workflow';
const loadUserData = createWorkflow('workflow', { fetchUser, fetchPosts });

Options like caching, events, and resume state are passed to createWorkflow, or per-run via workflow.run(fn, config):

// Correct: Options at creation
const workflow = createWorkflow('workflow', { fetchUser }, {
cache: new Map(),
onEvent: (e) => console.log(e)
});
await workflow.run(async ({ step }) => { ... });
// Per-run options: pass config as second argument to .run()
await workflow.run(async ({ step }) => { ... }, { signal: controller.signal });

Use step() to execute operations. Prefer step('id', fn) so step names appear in statically generated docs and diagrams. If any step fails, the workflow exits early:

const result = await loadUserData.run(async ({ step, deps }) => {
const user = await step('fetchUser', () => deps.fetchUser('1'));
const posts = await step('fetchPosts', () => deps.fetchPosts(user.id));
return { user, posts };
});

Check result.ok to determine success or failure:

if (result.ok) {
console.log(result.value.user.name);
console.log(result.value.posts.length, 'posts');
} else {
// TypeScript knows: result.error is 'NOT_FOUND' | 'FETCH_ERROR' | UnexpectedError
switch (result.error) {
case 'NOT_FOUND':
console.log('User not found');
break;
case 'FETCH_ERROR':
console.log('Failed to fetch posts');
break;
default:
// UnexpectedError - something threw an exception
console.log('Unexpected error:', result.error);
}
}
import { ok, err, type AsyncResult } from 'awaitly';
import { createWorkflow } from 'awaitly/workflow';
type User = { id: string; name: string };
type Post = { id: number; title: string };
const fetchUser = async (id: string): AsyncResult<User, 'NOT_FOUND'> =>
id === '1' ? ok({ id: '1', name: 'Alice' }) : err('NOT_FOUND');
const fetchPosts = async (userId: string): AsyncResult<Post[], 'FETCH_ERROR'> =>
ok([{ id: 1, title: 'Hello World' }]);
const loadUserData = createWorkflow('workflow', { fetchUser, fetchPosts });
const result = await loadUserData.run(async ({ step, deps }) => {
const user = await step('fetchUser', () => deps.fetchUser('1'));
const posts = await step('fetchPosts', () => deps.fetchPosts(user.id));
return { user, posts };
});
if (result.ok) {
console.log(`${result.value.user.name} has ${result.value.posts.length} posts`);
}

Change fetchUser('1') to fetchUser('999'):

const result = await loadUserData.run(async ({ step, deps }) => {
const user = await step('fetchUser', () => deps.fetchUser('999')); // Returns err('NOT_FOUND')
// This line never runs
const posts = await step('fetchPosts', () => deps.fetchPosts(user.id));
return { user, posts };
});
console.log(result.ok); // false
console.log(result.error); // 'NOT_FOUND'

The workflow exits at the first error. No need for try/catch or manual error checking.

Learn about error handling →