Conditional Execution
Execute steps only when certain conditions are met, with automatic event emission for skipped steps.
Basic usage
Section titled “Basic usage”import { ok, err, type AsyncResult } from 'awaitly';import { when, unless, createWorkflow } from 'awaitly/workflow';
const fetchUser = async (id: string): AsyncResult<User, 'NOT_FOUND'> => { // ...};
const sendEmail = async (to: string): AsyncResult<void, 'SEND_FAILED'> => { // ...};
const workflow = createWorkflow('workflow', { fetchUser, sendEmail });
const result = await workflow.run(async ({ step, deps }) => { const user = await step('fetchUser', () => fetchUser('123'));
// Only send email if user is not verified await when( !user.isVerified, () => step('sendEmail', () => sendEmail(user.email)), { name: 'send-verification', reason: 'User is already verified' } );
return user;});when - Execute if condition is true
Section titled “when - Execute if condition is true”Run a step only when a condition is true. Returns undefined if skipped.
const result = await workflow.run(async ({ step, deps }) => { const user = await step('fetchUser', () => fetchUser('123'));
// Only fetch premium data if user is premium const premium = await when( user.isPremium, () => step('fetchPremiumData', () => fetchPremiumData(user.id)), { name: 'premium-data', reason: 'User is not premium' } );
return { user, premium }; // premium is User | undefined});unless - Execute if condition is false
Section titled “unless - Execute if condition is false”Run a step only when a condition is false. Returns undefined if skipped.
const result = await workflow.run(async ({ step, deps }) => { const user = await step('fetchUser', () => fetchUser('123'));
// Only send verification email if user is NOT verified const email = await unless( user.isVerified, () => step('sendVerificationEmail', () => sendVerificationEmail(user.email)), { name: 'send-verification', reason: 'User is already verified' } );
return { user, email }; // email is void | undefined});whenOr - Execute if true, else return default
Section titled “whenOr - Execute if true, else return default”Run a step if condition is true, otherwise return a default value.
const result = await workflow.run(async ({ step, deps }) => { const user = await step('fetchUser', () => fetchUser('123'));
// Get premium limits or use default for non-premium users const limits = await whenOr( user.isPremium, () => step('fetchPremiumLimits', () => fetchPremiumLimits(user.id)), { maxRequests: 100, maxStorage: 1000 }, // default { name: 'premium-limits', reason: 'Using default limits' } );
return { user, limits }; // limits is PremiumLimits | DefaultLimits});unlessOr - Execute if false, else return default
Section titled “unlessOr - Execute if false, else return default”Run a step if condition is false, otherwise return a default value.
const result = await workflow.run(async ({ step, deps }) => { const user = await step('fetchUser', () => fetchUser('123'));
// Generate new token if NOT authenticated, else use existing const token = await unlessOr( user.isAuthenticated, () => step('generateNewToken', () => generateNewToken(user.id)), user.existingToken, // default { name: 'token-generation', reason: 'Using existing token' } );
return { user, token };});Event emission
Section titled “Event emission”Conditional helpers automatically emit step_skipped events when steps are skipped:
const workflow = createWorkflow('workflow', { fetchUser }, { onEvent: (event) => { if (event.type === 'step_skipped') { console.log(`Step ${event.name} skipped: ${event.reason}`); // event.decisionId - unique ID for this decision } }});With workflow context
Section titled “With workflow context”Use createConditionalHelpers to bind helpers to workflow context for automatic event emission:
import { createConditionalHelpers, createWorkflow } from 'awaitly/workflow';
const workflow = createWorkflow('workflow', { fetchUser }, { onEvent: (event, ctx) => { // ctx is available here }});
const result = await workflow.run(async ({ step, deps, args, ctx }) => { // Create bound helpers const { when, whenOr } = createConditionalHelpers({ workflowId: ctx.workflowId, onEvent: (e) => { // Events automatically include context }, context: ctx });
const user = await step('fetchUser', () => fetchUser('123'));
// Helpers automatically emit events with context const premium = await when( user.isPremium, () => step('fetchPremiumData', () => fetchPremiumData(user.id)), { name: 'premium-data' } );
return { user, premium };});With run()
Section titled “With run()”When using run(), pass context manually:
import { run } from 'awaitly/run';import { createConditionalHelpers } from 'awaitly/workflow';
const result = await run(async ({ step }) => { const ctx = { workflowId: 'workflow-123', onEvent: (event) => { // Handle events }, context: { requestId: 'req-456' } };
const { when } = createConditionalHelpers(ctx);
const user = await step('fetchUser', () => fetchUser('123'));
const premium = await when( user.isPremium, () => step('fetchPremiumData', () => fetchPremiumData(user.id)), { name: 'premium-data' } );
return { user, premium };}, { onEvent: ctx.onEvent, workflowId: ctx.workflowId, context: ctx.context});Options
Section titled “Options”All conditional helpers accept an optional ConditionalOptions object:
{ name?: string; // Human-readable name for the step key?: string; // Stable identity key for caching/tracking reason?: string; // Explanation for why step was skipped}Visualization
Section titled “Visualization”Skipped steps appear in workflow visualizations:
import { createVisualizer } from 'awaitly-visualizer';
const viz = createVisualizer();const workflow = createWorkflow('workflow', deps, { onEvent: viz.handleEvent });
await workflow.run(async ({ step, deps }) => { const user = await step('fetchUser', () => fetchUser('123'));
await when( user.isPremium, () => step('fetchPremiumData', () => fetchPremiumData(user.id)), { name: 'premium-data', reason: 'Not premium' } );
return user;});
console.log(viz.render());// Shows skipped step with reasonReal-world example
Section titled “Real-world example”const processOrder = createWorkflow('workflow', { fetchOrder, chargeCard, sendEmail, applyDiscount });
const result = await processOrder.run(async ({ step, deps }) => { const order = await step('fetchOrder', () => deps.fetchOrder(orderId));
// Apply discount only if order is large enough const discount = await whenOr( order.total > 100, () => step('applyDiscount', () => deps.applyDiscount(order.id, 'BULK_10')), 0, // no discount { name: 'apply-discount', reason: 'Order too small for discount' } );
// Charge card const payment = await step('chargeCard', () => deps.chargeCard(order.total - discount));
// Send confirmation email only if payment succeeded await when( payment.status === 'succeeded', () => step('sendEmail', () => deps.sendEmail(order.email, 'Order confirmed')), { name: 'send-confirmation', reason: 'Payment failed' } );
return { order, payment, discount };});