Using Xstate, Cloudflare Workflows, Durable Objects, Websockets, Queues, Vue.js.
Multi-step approval workflow:
- User manually starts the process
- Sends job to queue for processing
- Waits for user approval/rejection
- If rejected, retries processing until approved
Initially based on Workflow-live of Jordan Coeyman at https://github.com/acoyfellow/workflow-live
Try it live at xstate-workflow.boxofapps.workers.dev
Each workflow instance is assigned a unique ID and managed through an XState machine that controls the approval flow. The system uses a Durable Object as an improvised KV database for data persistence and real-time broadcasting via WebSockets.
- Real-time Vue.js Frontend: Clean interface with WebSocket-powered live updates showing workflow status
- User-Controlled Workflow: Manual process initiation and approval/rejection controls
- Retry Logic: Rejected outputs trigger reprocessing until approval
- Error Testing: Random errors throughout to test resilience
- Data Persistence: Durable Object-based storage with broadcasting for real-time sync
- Dual State Management: Clean separation between Cloudflare Workflow infrastructure state and XState business logic
Separation of Concerns: The system maintains two distinct state machines that serve different purposes:
-
Cloudflare Workflow State: Generic infrastructure states that apply to any workflow type
Queued,Running,Waiting For Event,Processing Step,Completed,Errored- Handles workflow lifecycle, error handling, and infrastructure concerns
- Reusable across different workflow types
-
XState Machine State: Business-specific states that define the approval workflow logic
Unprocessed,Sending To Queue,Waiting For Approval,Finished,Error- Handles business rules, approval logic, and step-specific behavior
- Specific to each workflow type's requirements
This separation avoids mixing infrastructure concerns with business logic, prevents duplication of concerns, and makes the system clearer to reason about. The frontend displays both states independently, giving users visibility into both the infrastructure status and business process state.
- Unprocessed: Initial state, waiting for user to start processing
- Sending To Queue: Adds job to queue with random error simulation
- Waiting For Approval: User can approve or reject the output based on criteria (output > 0.25)
- Finished: Successfully completed workflow
- Error: Final error state
- Workflow Creation: User creates a new workflow instance via the Vue frontend
- Manual Start: User clicks "Process" to begin, transitioning machine to "Sending To Queue"
- Queue Processing: Job is sent to Cloudflare Queue with random error simulation (25% failure rate)
- Queue Consumer: Processes the job and returns a random output value
- Approval Check: If output > 0.25, moves to "Waiting For Approval", otherwise errors
- User Decision: User can approve (finish) or reject (retry processing)
- Real-time Updates: All state changes broadcast via WebSocket to connected clients
- Data Persistence: Workflow state and XState snapshots saved in Durable Object storage
The system includes intentional error scenarios for testing:
- 25% random failure rate when sending to queue
- Random API errors with configurable retry behavior
- Timeout configurations for various steps
- NonRetryableError vs retryable error handling
- Investigate creating a base class that delegates control to the xstate machine. For example, a class like
export class MyWorkflow extends XstateWorkflowEntrypoint<Env>could allow you to configure the machine and define steps for each state change. - Explore ways to structure the code to make it possible to cache the result of each step and use it as context for the subsequent step
- Does it even make any sense to use a Workflow for this? Wouldn't it be so much easier just using the durable object? Identify the value of using Workflows vs the alternative of not using it.
# Clone the repo
git clone https://github.com/franciscohermida/xstate-workflow.git
cd xstate-workflow
# Install dependencies
pnpm install
# Configure Cloudflare
wrangler login
# Deploy
pnpm run deploy
pnpm dev