User Onboarding
This example shows a user onboarding flow that validates email, creates an account, optionally sends an SMS, and publishes domain events.
The flow
Section titled “The flow”import { createFlow, ValidationError } from '@celom/prose';
interface OnboardInput { email: string; name: string; phone?: string;}
const onboardUser = createFlow<OnboardInput>('onboard-user')
// 1. Validate email format .validate('checkEmail', (ctx) => { if (!ctx.input.email.includes('@')) throw ValidationError.single('email', 'Invalid email address'); })
// 2. Check if user already exists — break early if so .step('checkExisting', async (ctx) => { const existing = await db.findByEmail(ctx.input.email); return { existing }; }) .breakIf( (ctx) => ctx.state.existing != null, (ctx) => ({ user: ctx.state.existing, created: false }) )
// 3. Create the account (with retry for transient DB errors) .step('createAccount', async (ctx) => { const user = await db.createUser({ email: ctx.input.email, name: ctx.input.name, }); return { user }; }) .withRetry({ maxAttempts: 3, delayMs: 200, backoffMultiplier: 2 })
// 4. Send welcome email .step('sendWelcome', async (ctx) => { await mailer.send(ctx.state.user.email, { template: 'welcome', name: ctx.state.user.name, }); })
// 5. Optionally send SMS if phone number provided .stepIf( 'sendSms', (ctx) => ctx.input.phone != null, async (ctx) => { await sms.send(ctx.input.phone!, `Welcome ${ctx.state.user.name}!`); return { smsSent: true }; } )
// 6. Publish domain event .event('users', (ctx) => ({ eventType: 'user.onboarded', userId: ctx.state.user.id, email: ctx.input.email, }))
// 7. Shape output .map((input, state) => ({ user: state.user, created: true })) .build();Running the flow
Section titled “Running the flow”const result = await onboardUser.execute( { email: 'alice@example.com', name: 'Alice', phone: '+1234567890' }, { db, eventPublisher }, { observer: { onStepSkipped: (name) => console.log(`Skipped: ${name}`), onFlowBreak: (name, step) => console.log(`Early exit at: ${step}`), }, });
// result: { user: User; created: true } | { user: User; created: false }What this demonstrates
Section titled “What this demonstrates”- Validation — email check runs first and is never retried
- Early exit —
breakIfreturns the existing user without running creation steps - Conditional steps — SMS is only sent when a phone number is provided
- Retries — account creation retries transient database errors
- Events — domain event enriched with
correlationId - Type-safe output — return type is a union reflecting both paths (new user vs. existing)