xstate-builders-gk
v1.0.3
Published
Fluent builder pattern library for XState v5+ machines with full TypeScript support
Maintainers
Readme
🎯 XState Builders
A complete fluent builder pattern library for creating XState v5+ state machines with full TypeScript support. Build complex state machines using an intuitive, type-safe API that makes XState more approachable and maintainable.
✨ Features
- 🏗️ Fluent Builder Pattern: Intuitive, chainable API for constructing state machines
- 🎯 Full TypeScript Support: Complete type safety with intelligent autocompletion
- 🏷️ Advanced State Features: Tags, meta, descriptions, outputs, and more
- ⏰ Delayed Transitions: Built-in support for time-based transitions with actions and guards
- 🔄 Always Transitions: Automatic state transitions with conditional logic
- 📦 Multi-Format Builds: CommonJS, ES Modules, and UMD distributions
- 🧪 Comprehensive Tests: 425+ tests with 79% branch coverage
- 📚 Live Examples: Interactive React examples included
- 🎭 Actor Support: Full support for XState v5 actors and invocations
- 🛡️ Guards & Actions: Rich support for guards, actions, and context manipulation
🚀 Quick Start
Installation
npm install xstate-builders-gk xstateEnums and Types
// Define your enums for type safety and better code organization
enum MachineStates {
IDLE = 'idle',
LOADING = 'loading',
SUCCESS = 'success',
ERROR = 'error',
TIMEOUT = 'timeout'
}
enum MachineEvents {
START = 'START',
RETRY = 'RETRY',
CANCEL = 'CANCEL',
COMPLETE = 'COMPLETE'
}
enum ActionNames {
START_LOADING = 'startLoading',
UPDATE_PROGRESS = 'updateProgress',
COMPLETE_LOADING = 'completeLoading',
NOTIFY_PARENT = 'notifyParent',
CLEAR_ERROR = 'clearError',
CLEANUP = 'cleanup'
}
enum GuardNames {
HAS_DATA = 'hasData',
HAS_ERROR = 'hasError',
IS_VALID = 'isValid'
}
enum ServiceNames {
FETCH_DATA = 'fetchDataService',
VALIDATION = 'validationService',
PAYMENT = 'paymentService'
}
enum StateTags {
READY = 'ready',
LOADING = 'loading',
VISIBLE = 'visible',
ACTIVE = 'active',
SUCCESS = 'success',
ERROR = 'error',
RECOVERABLE = 'recoverable',
TIMEOUT = 'timeout'
}Basic Usage
import {
GenericMachineBuilder,
GenericStateBuilder,
GenericStatesBuilder,
GenericActionsBuilder
} from 'xstate-builders-gk';
// Create actions with full type safety
const actions = GenericActionsBuilder.create()
.withAssignAction(ActionNames.START_LOADING, { isLoading: true, progress: 0 })
.withAssignAction(ActionNames.UPDATE_PROGRESS, ({ progress }) => ({ progress: progress + 10 }))
.withAssignAction(ActionNames.COMPLETE_LOADING, { isLoading: false, result: 'success' })
.withSendAction(ActionNames.NOTIFY_PARENT, { type: MachineEvents.COMPLETE })
.build();
// Create sophisticated states
const idleState = GenericStateBuilder.create()
.withTag(StateTags.READY)
.withDescription('Waiting for user action')
.withMeta({ color: 'blue', priority: 'low' })
.withTransition(MachineEvents.START, MachineStates.LOADING, [ActionNames.START_LOADING])
.build();
const loadingState = GenericStateBuilder.create()
.withTags(StateTags.LOADING, StateTags.VISIBLE, StateTags.ACTIVE)
.withEntry([ActionNames.START_LOADING])
.withExit([ActionNames.CLEANUP])
.withAfter(
GenericDelayedTransitionsBuilder.create()
.afterWithActions(1000, [ActionNames.UPDATE_PROGRESS])
.afterWithActionsAndGuard(5000, [ActionNames.COMPLETE_LOADING], GuardNames.HAS_DATA, MachineStates.SUCCESS)
.after(10000, MachineStates.TIMEOUT) // Fallback timeout
.build()
)
.withAlways(MachineStates.ERROR, GuardNames.HAS_ERROR) // Auto-transition on error
.build();
const successState = GenericStateBuilder.create()
.asFinalStateWithOutput({
status: 'completed',
timestamp: Date.now(),
result: 'Processing finished successfully'
})
.withTag(StateTags.SUCCESS)
.withDescription('Task completed successfully')
.build();
const errorState = GenericStateBuilder.create()
.withTags(StateTags.ERROR, StateTags.RECOVERABLE)
.withDescription('An error occurred during processing')
.withTransition(MachineEvents.RETRY, MachineStates.IDLE, [ActionNames.CLEAR_ERROR])
.build();
const timeoutState = GenericStateBuilder.create()
.withTags(StateTags.ERROR, StateTags.TIMEOUT)
.withDescription('Operation timed out')
.withTransition(MachineEvents.RETRY, MachineStates.IDLE, [ActionNames.CLEAR_ERROR])
.build();
// Build the complete machine
const machine = GenericMachineBuilder.create()
.withId('advancedProcessor')
.withInitial(MachineStates.IDLE)
.withContext({
isLoading: false,
progress: 0,
result: null,
error: null
})
.withStates(
GenericStatesBuilder.create()
.withState(MachineStates.IDLE, idleState)
.withState(MachineStates.LOADING, loadingState)
.withState(MachineStates.SUCCESS, successState)
.withState(MachineStates.ERROR, errorState)
.withState(MachineStates.TIMEOUT, timeoutState)
.build()
)
.withActions(actions)
.withGuards(
GenericGuardsBuilder.create()
.withGuard(GuardNames.HAS_DATA, ({ context }) => context.result !== null)
.withGuard(GuardNames.HAS_ERROR, ({ context }) => context.error !== null)
.build()
)
.build();📚 Complete API Reference
🏗️ MachineBuilder
const machine = GenericMachineBuilder.create()
.withId('myMachine')
.withInitial('idle')
.withContext({ count: 0 })
.withStates(statesBuilder.build())
.withActions(actionsBuilder.build())
.withGuards(guardsBuilder.build())
.withActors(actorsBuilder.build())
.withDelays({ SHORT: 1000, LONG: 5000 })
.build();🎭 StateBuilder - Complete Features
const advancedState = GenericStateBuilder.create()
// Basic state configuration
.withType('compound') // 'atomic', 'compound', 'parallel', 'final', 'history'
.withDescription('A sophisticated state with all features')
// Tags for state identification
.withTag(StateTags.VISIBLE)
.withTags(StateTags.LOADING, StateTags.VISIBLE, StateTags.ERROR)
// Metadata for UI and other purposes
.withMeta({
component: 'LoadingSpinner',
color: 'blue',
timeout: 5000,
level: 'high'
})
// Output for final states
.withOutput({
status: 'completed',
data: [1, 2, 3],
timestamp: Date.now()
})
// Or mark as final with output
.asFinalStateWithOutput({ result: 'success' })
// Entry and exit actions
.withEntry(['enterAction', 'logEntry'])
.withExit(['exitAction', 'cleanup'])
// Transitions
.withTransition(MachineEvents.START, MachineStates.LOADING)
.withTransition(MachineEvents.COMPLETE, MachineStates.SUCCESS, [ActionNames.UPDATE_PROGRESS, ActionNames.COMPLETE_LOADING])
.withTransition(MachineEvents.RETRY, MachineStates.IDLE, [], GuardNames.IS_VALID)
.withTransition(MachineEvents.CANCEL, MachineStates.ERROR, [ActionNames.CLEAR_ERROR], GuardNames.HAS_ERROR)
// Delayed transitions (after)
.withAfter(
GenericDelayedTransitionsBuilder.create()
.after(1000, MachineStates.TIMEOUT) // Simple delay
.afterWithActions(2000, [ActionNames.CLEAR_ERROR], MachineStates.TIMEOUT)
.afterWithGuard(3000, GuardNames.IS_VALID, MachineStates.SUCCESS)
.afterWithActionsAndGuard(5000, [ActionNames.UPDATE_PROGRESS], GuardNames.HAS_DATA, MachineStates.SUCCESS)
// Using delay references
.afterWithActions('SHORT_DELAY', [ActionNames.NOTIFY_PARENT], MachineStates.ERROR)
.build()
)
// Always transitions (automatic)
.withAlways(MachineStates.SUCCESS) // Unconditional
.withAlways(MachineStates.ERROR, GuardNames.HAS_ERROR) // With guard
// Actor invocations
.withInvoke({
id: 'dataService',
src: Services.DataFetcher,
onDone: { target: 'success', actions: ['handleData'] },
onError: { target: 'error', actions: ['handleError'] }
})
.build();⏰ DelayedTransitionsBuilder
const delayedTransitions = GenericDelayedTransitionsBuilder.create()
// Basic delays
.after(1000, 'timeout')
.after('CUSTOM_DELAY', 'namedTimeout')
// With actions
.afterWithActions(2000, ['logTimeout', 'clearData'])
.afterWithActions(1500, ['saveState'], 'timeout')
.afterWithActions('SHORT_DELAY', ['showWarning'], 'warning')
// With guards
.afterWithGuard(3000, 'isValid', 'success')
.afterWithGuard('LONG_DELAY', 'stillWaiting', 'giveUp')
// With both actions and guards
.afterWithActionsAndGuard(2000, ['validate'], 'isReady', 'proceed')
.afterWithActionsAndGuard('MEDIUM_DELAY', ['log'], 'canLog', 'logged')
// Without target (just actions)
.afterWithActions(1000, ['logEvent'])
.afterWithActionsAndGuard(500, ['quickAction'], 'shouldAct')
.build();🎬 ActionsBuilder - Rich Actions Support
// Define machine types for spawn actions
enum MachineTypes {
Worker = 'workerMachine',
Task = 'taskMachine'
}
// Define event types for better type safety
enum EventTypes {
ChildUpdated = 'CHILD_UPDATED',
Delayed = 'DELAYED',
Message = 'MESSAGE'
}
// Define actor targets
enum ActorTargets {
ChildActor = 'childActor'
}
const actions = GenericActionsBuilder.create()
// Assign actions
.withAssignAction('setLoading', { isLoading: true })
.withAssignAction('updateUser', ({ user }) => ({
user: { ...user, lastLogin: Date.now() }
}))
// Send actions
.withSendAction('notifyParent', { type: EventTypes.ChildUpdated })
.withSendAction('delayedEvent', { type: EventTypes.Delayed }, { delay: 1000 })
.withSendAction('targetedEvent', { type: EventTypes.Message }, { to: ActorTargets.ChildActor })
// Spawn child actors
.withSpawnChildAction('spawnWorker', MachineTypes.Worker)
.withSpawnChildAction('spawnWithId', MachineTypes.Task, { id: 'task-1' })
// Custom actions
.withAction('customAction', ({ context, event }) => {
console.log('Custom action executed', { context, event });
})
.build();🛡️ GuardsBuilder
const guards = GenericGuardsBuilder.create()
.withGuard('isAuthorized', ({ context }) => context.user?.isAdmin === true)
.withGuard('hasValidData', ({ context, event }) => {
return context.data && event.type === 'VALIDATE';
})
.withGuard('isNotEmpty', ({ context }) => Object.keys(context).length > 0)
.build();🎭 ActorsBuilder
// Define service enums for better type safety
enum Services {
DataFetcher = 'fetchDataService',
WebSocketService = 'webSocketActor',
Timer = 'timerService',
Validator = 'validationService'
}
enum ActorIds {
DataFetcher = 'dataFetcher',
WebSocketService = 'webSocketService',
Timer = 'timer',
Validator = 'validator'
}
const actors = GenericActorsBuilder.create()
.withActor(ActorIds.DataFetcher, Services.DataFetcher)
.withActor(ActorIds.WebSocketService, Services.WebSocketService)
.withActors({
[ActorIds.Timer]: Services.Timer,
[ActorIds.Validator]: Services.Validator
})
.build();⏱️ DelaysBuilder
// Define delay constants for consistency
enum DelayTimes {
Short = 1000,
Medium = 5000,
Long = 10000,
CustomShort = 500,
CustomLong = 15000
}
enum DelayNames {
Short = 'SHORT',
Medium = 'MEDIUM',
Long = 'LONG',
CustomShort = 'CUSTOM_SHORT',
CustomLong = 'CUSTOM_LONG',
Dynamic = 'DYNAMIC'
}
const delays = GenericDelaysBuilder.create()
.withDelay(DelayNames.Short, DelayTimes.Short)
.withDelay(DelayNames.Medium, DelayTimes.Medium)
.withDelay(DelayNames.Long, DelayTimes.Long)
.withDelays({
[DelayNames.CustomShort]: DelayTimes.CustomShort,
[DelayNames.CustomLong]: DelayTimes.CustomLong,
[DelayNames.Dynamic]: ({ context }) => context.timeout || 2000
})
.build();🏗️ StatesBuilder
// Define state names for consistency
enum StateNames {
Idle = 'idle',
Loading = 'loading',
Success = 'success',
Error = 'error'
}
const states = GenericStatesBuilder.create()
.withState(StateNames.Idle, idleState)
.withState(StateNames.Loading, loadingState)
.withState(StateNames.Success, successState)
.withState(StateNames.Error, errorState)
.build();🎯 Advanced Examples
Complex State Machine with All Features
import {
GenericMachineBuilder,
GenericStateBuilder,
GenericStatesBuilder,
GenericActionsBuilder,
GenericGuardsBuilder,
GenericActorsBuilder,
GenericDelaysBuilder,
GenericDelayedTransitionsBuilder
} from 'xstate-builders-gk';
// Define all enums for better type safety and maintainability
enum CheckoutStates {
Idle = 'idle',
ValidatingCart = 'validatingCart',
CollectingShipping = 'collectingShipping',
ValidatingShipping = 'validatingShipping',
CollectingPayment = 'collectingPayment',
ProcessingPayment = 'processingPayment',
OrderComplete = 'orderComplete',
CartError = 'cartError',
ShippingError = 'shippingError',
PaymentError = 'paymentError',
Timeout = 'timeout',
PaymentTimeout = 'paymentTimeout',
Cancelled = 'cancelled'
}
enum CheckoutEvents {
StartCheckout = 'START_CHECKOUT',
SubmitShipping = 'SUBMIT_SHIPPING',
SubmitPayment = 'SUBMIT_PAYMENT',
Back = 'BACK',
Retry = 'RETRY',
Cancel = 'CANCEL',
CreateOrder = 'CREATE_ORDER'
}
enum CheckoutServices {
CartValidator = 'cartValidationService',
AddressValidator = 'addressValidationService',
PaymentProcessor = 'paymentService'
}
enum CheckoutActors {
CartValidator = 'cartValidator',
ShippingValidator = 'shippingValidator',
PaymentProcessor = 'paymentProcessor'
}
enum CheckoutTags {
Ready = 'ready',
Loading = 'loading',
Validation = 'validation',
Form = 'form',
Shipping = 'shipping',
Payment = 'payment',
Critical = 'critical',
Success = 'success',
Final = 'final',
Error = 'error',
Recoverable = 'recoverable'
}
enum CheckoutGuards {
HasValidShipping = 'hasValidShipping',
HasAttempts = 'hasAttempts'
}
enum CheckoutActions {
ShowLoader = 'showLoader',
HideLoader = 'hideLoader',
ShowPaymentLoader = 'showPaymentLoader',
DisableForm = 'disableForm',
CreateOrder = 'createOrder',
LogTimeout = 'logTimeout',
LogPaymentTimeout = 'logPaymentTimeout',
ValidateCart = 'validateCart',
SetShipping = 'setShipping',
SetPayment = 'setPayment',
SetValidatedCart = 'setValidatedCart',
SetCartError = 'setCartError',
SetShippingError = 'setShippingError',
SetPaymentError = 'setPaymentError',
SetPaymentResult = 'setPaymentResult',
ClearError = 'clearError'
}
enum CheckoutDelays {
Short = 'SHORT',
Medium = 'MEDIUM',
Long = 'LONG'
}
// Full-featured e-commerce checkout machine
const checkoutMachine = GenericMachineBuilder.create()
.withId('ecommerceCheckout')
.withInitial(CheckoutStates.Idle)
.withContext({
cart: [],
user: null,
paymentMethod: null,
shippingAddress: null,
orderTotal: 0,
error: null
})
.withStates(
GenericStatesBuilder.create()
.withState(CheckoutStates.Idle,
GenericStateBuilder.create()
.withTag(CheckoutTags.Ready)
.withDescription('Ready to start checkout process')
.withMeta({ step: 1, title: 'Cart Review' })
.withTransition(CheckoutEvents.StartCheckout, CheckoutStates.ValidatingCart, ['validateCart'])
.build()
)
.withState(CheckoutStates.ValidatingCart,
GenericStateBuilder.create()
.withTags(CheckoutTags.Loading, CheckoutTags.Validation)
.withDescription('Validating cart contents and availability')
.withEntry(['showLoader'])
.withExit(['hideLoader'])
.withInvoke({
id: CheckoutActors.CartValidator,
src: CheckoutServices.CartValidator,
onDone: {
target: CheckoutStates.CollectingShipping,
actions: ['setValidatedCart']
},
onError: {
target: CheckoutStates.CartError,
actions: ['setCartError']
}
})
.withAfter(
GenericDelayedTransitionsBuilder.create()
.afterWithActions(30000, ['logTimeout'], CheckoutStates.Timeout)
.build()
)
.build()
)
.withState(CheckoutStates.CollectingShipping,
GenericStateBuilder.create()
.withTags(CheckoutTags.Form, CheckoutTags.Shipping)
.withMeta({ step: 2, title: 'Shipping Information' })
.withTransition(CheckoutEvents.SubmitShipping, CheckoutStates.ValidatingShipping, ['setShipping'])
.withTransition(CheckoutEvents.Back, CheckoutStates.Idle)
.withAlways(CheckoutStates.CollectingPayment, CheckoutGuards.HasValidShipping)
.build()
)
.withState(CheckoutStates.ValidatingShipping,
GenericStateBuilder.create()
.withTags(CheckoutTags.Loading, CheckoutTags.Validation, CheckoutTags.Shipping)
.withInvoke({
id: CheckoutActors.ShippingValidator,
src: CheckoutServices.AddressValidator,
onDone: { target: CheckoutStates.CollectingPayment },
onError: { target: CheckoutStates.ShippingError, actions: ['setShippingError'] }
})
.build()
)
.withState(CheckoutStates.CollectingPayment,
GenericStateBuilder.create()
.withTags(CheckoutTags.Form, CheckoutTags.Payment)
.withMeta({ step: 3, title: 'Payment Information' })
.withTransition(CheckoutEvents.SubmitPayment, CheckoutStates.ProcessingPayment, ['setPayment'])
.withTransition(CheckoutEvents.Back, CheckoutStates.CollectingShipping)
.build()
)
.withState(CheckoutStates.ProcessingPayment,
GenericStateBuilder.create()
.withTags(CheckoutTags.Loading, CheckoutTags.Payment, CheckoutTags.Critical)
.withDescription('Processing payment with payment gateway')
.withEntry(['showPaymentLoader', 'disableForm'])
.withInvoke({
id: CheckoutActors.PaymentProcessor,
src: CheckoutServices.PaymentProcessor,
onDone: {
target: CheckoutStates.OrderComplete,
actions: ['setPaymentResult', 'createOrder']
},
onError: {
target: CheckoutStates.PaymentError,
actions: ['setPaymentError']
}
})
.withAfter(
GenericDelayedTransitionsBuilder.create()
.afterWithActionsAndGuard(60000, ['logPaymentTimeout'], CheckoutGuards.HasAttempts, 'retry')
.after(120000, CheckoutStates.PaymentTimeout)
.build()
)
.build()
)
.withState(CheckoutStates.OrderComplete,
GenericStateBuilder.create()
.asFinalStateWithOutput({
orderId: 'generated-order-id',
status: 'completed',
timestamp: Date.now(),
total: 'calculated-total'
})
.withTags(CheckoutTags.Success, CheckoutTags.Final)
.withDescription('Order completed successfully')
.withMeta({ step: 4, title: 'Order Confirmation' })
.build()
)
// Error states
.withState(CheckoutStates.CartError, createErrorState('Cart validation failed'))
.withState(CheckoutStates.ShippingError, createErrorState('Shipping validation failed'))
.withState(CheckoutStates.PaymentError, createErrorState('Payment processing failed'))
.withState(CheckoutStates.Timeout, createErrorState('Request timeout'))
.withState(CheckoutStates.PaymentTimeout, createErrorState('Payment timeout'))
.build()
)
.withActions(
GenericActionsBuilder.create()
// Cart actions
.withAssignAction('validateCart', { isValidatingCart: true })
.withAssignAction('setValidatedCart', ({ cart }) => ({
cart: cart.map(item => ({ ...item, validated: true }))
}))
.withAssignAction('setCartError', ({ error }) => ({ error }))
// Shipping actions
.withAssignAction('setShipping', ({ shippingAddress }) => ({ shippingAddress }))
.withAssignAction('setShippingError', ({ error }) => ({ error }))
// Payment actions
.withAssignAction('setPayment', ({ paymentMethod }) => ({ paymentMethod }))
.withAssignAction('setPaymentResult', ({ result }) => ({ paymentResult: result }))
.withAssignAction('setPaymentError', ({ error }) => ({ error }))
// UI actions
.withAction(CheckoutActions.ShowLoader, () => console.log('Showing loader'))
.withAction(CheckoutActions.HideLoader, () => console.log('Hiding loader'))
.withAction(CheckoutActions.ShowPaymentLoader, () => console.log('Showing payment loader'))
.withAction(CheckoutActions.DisableForm, () => console.log('Disabling form'))
// Business logic actions
.withSendAction(CheckoutActions.CreateOrder, { type: CheckoutEvents.CreateOrder })
.withAction(CheckoutActions.LogTimeout, () => console.log('Operation timed out'))
.withAction(CheckoutActions.LogPaymentTimeout, () => console.log('Payment timed out'))
.build()
)
.withGuards(
GenericGuardsBuilder.create()
.withGuard(CheckoutGuards.HasValidShipping, ({ context }) =>
context.shippingAddress && context.shippingAddress.isValid
)
.withGuard(CheckoutGuards.HasAttempts, ({ context }) =>
(context.paymentAttempts || 0) < 3
)
.build()
)
.withActors(
GenericActorsBuilder.create()
.withActor(CheckoutActors.CartValidator, CheckoutServices.CartValidator)
.withActor(CheckoutActors.ShippingValidator, CheckoutServices.AddressValidator)
.withActor(CheckoutActors.PaymentProcessor, CheckoutServices.PaymentProcessor)
.build()
)
.withDelays(
GenericDelaysBuilder.create()
.withDelay('SHORT', 1000)
.withDelay('MEDIUM', 5000)
.withDelay('LONG', 30000)
.build()
)
.build();
// Helper function for error states
function createErrorState(message: string) {
return GenericStateBuilder.create()
.withTags(CheckoutTags.Error, CheckoutTags.Recoverable)
.withDescription(message)
.withMeta({ errorType: 'recoverable' })
.withTransition(CheckoutEvents.Retry, CheckoutStates.Idle, ['clearError'])
.withTransition(CheckoutEvents.Cancel, CheckoutStates.Cancelled)
.build();
}React Integration with TypeScript
import { useMachine } from '@xstate/react';
import { checkoutMachine } from './machines/checkout';
interface CartItem {
id: string;
name: string;
price: number;
quantity: number;
}
interface CheckoutComponentProps {
initialCart: CartItem[];
}
function CheckoutComponent({ initialCart }: CheckoutComponentProps) {
const [state, send] = useMachine(checkoutMachine, {
input: { cart: initialCart }
});
const isLoading = state.hasTag('loading');
const isError = state.hasTag('error');
const currentStep = state.meta?.step || 1;
return (
<div className="checkout-container">
{/* Progress indicator */}
<div className="progress-bar">
<div className="progress" style={{ width: `${(currentStep / 4) * 100}%` }} />
</div>
{/* Step title */}
<h2>{state.meta?.title || 'Checkout'}</h2>
{/* Loading indicator */}
{isLoading && (
<div className="loading-overlay">
<div className="spinner" />
<p>{state.description}</p>
</div>
)}
{/* Error handling */}
{isError && (
<div className="error-container">
<p className="error-message">{state.context.error}</p>
{state.can({ type: 'RETRY' }) && (
<button onClick={() => send({ type: 'RETRY' })}>
Try Again
</button>
)}
<button onClick={() => send({ type: 'CANCEL' })}>
Cancel Order
</button>
</div>
)}
{/* State-specific content */}
{state.matches('idle') && (
<CartReview
cart={state.context.cart}
onStartCheckout={() => send({ type: 'START_CHECKOUT' })}
/>
)}
{state.matches('collectingShipping') && (
<ShippingForm
onSubmit={(address) => send({
type: 'SUBMIT_SHIPPING',
shippingAddress: address
})}
onBack={() => send({ type: 'BACK' })}
/>
)}
{state.matches('collectingPayment') && (
<PaymentForm
total={state.context.orderTotal}
onSubmit={(payment) => send({
type: 'SUBMIT_PAYMENT',
paymentMethod: payment
})}
onBack={() => send({ type: 'BACK' })}
/>
)}
{state.matches('orderComplete') && (
<OrderConfirmation
orderId={state.output?.orderId}
total={state.output?.total}
timestamp={state.output?.timestamp}
/>
)}
{/* Debug info in development */}
{process.env.NODE_ENV === 'development' && (
<div className="debug-panel">
<h4>Debug Info</h4>
<p>State: {JSON.stringify(state.value)}</p>
<p>Tags: {Array.from(state.tags).join(', ')}</p>
<p>Can events: {
['START_CHECKOUT', 'SUBMIT_SHIPPING', 'SUBMIT_PAYMENT', 'RETRY', 'CANCEL']
.filter(event => state.can({ type: event }))
.join(', ')
}</p>
</div>
)}
</div>
);
}🛠️ Development
Development Scripts
# Development with hot reload
npm run dev # Build library in watch mode
npm run example:dev # Start example app (port 3001)
# Building
npm run build # Build library (CJS, ESM, UMD)
npm run example:build # Build example app
# Testing
npm test # Run tests in watch mode
npm run test:ci # Run tests with coverage
npm run type-check # TypeScript type checking
npm run lint # ESLint checkingProject Structure
xstate-builders-gk/
├── src/
│ ├── lib/ # Library entry point
│ │ └── index.ts
│ ├── xstate-builders/ # Builder implementations
│ │ ├── MachineBuilder.ts
│ │ ├── StateBuilder.ts
│ │ ├── ActionsBuilder.ts
│ │ ├── GuardsBuilder.ts
│ │ ├── ActorsBuilder.ts
│ │ ├── DelaysBuilder.ts
│ │ ├── DelayedTransitionsBuilder.ts
│ │ ├── InvokeBuilder.ts
│ │ ├── StatesBuilder.ts
│ │ └── ...
│ └── __tests__/ # Test files (425+ tests)
├── examples/ # Example React app
│ ├── src/
│ │ └── App.tsx # Live examples
│ └── package.json # References parent via file:..
├── dist/ # Built library files
│ ├── index.js # CommonJS
│ ├── index.esm.js # ES Modules
│ ├── index.umd.js # UMD
│ └── index.d.ts # TypeScript definitions
└── package.json # Main package configurationBuilder Classes Reference
| Builder | Purpose | Key Methods |
|---------|---------|-------------|
| GenericMachineBuilder | Complete state machines | withId(), withStates(), withActions(), withGuards(), withActors() |
| GenericStateBuilder | Individual states | withTag(), withTags(), withMeta(), withAfter(), withAlways(), withInvoke() |
| GenericStatesBuilder | State collections | withState(), build() |
| GenericActionsBuilder | Actions | withAssignAction(), withSendAction(), withSpawnChildAction(), withAction() |
| GenericGuardsBuilder | Guards | withGuard(), withGuards() |
| GenericActorsBuilder | Actors | withActor(), withActors() |
| GenericDelaysBuilder | Delays | withDelay(), withDelays() |
| GenericDelayedTransitionsBuilder | Time-based transitions | after(), afterWithActions(), afterWithGuard(), afterWithActionsAndGuard() |
| GenericInvokeBuilder | Actor invocations | withSrc(), withOnDone(), withOnError(), withInput() |
📦 Multi-Format Distribution
The library is distributed in multiple formats for maximum compatibility:
- CommonJS (
dist/index.js): For Node.js and older bundlers - ES Modules (
dist/index.esm.js): For modern bundlers and browsers - UMD (
dist/index.umd.js): For direct browser usage - TypeScript (
dist/index.d.ts): Complete type definitions
🎯 Why Use XState Builders?
Before (Raw XState):
const machine = createMachine({
id: 'complex',
initial: 'idle',
context: { count: 0, error: null },
states: {
idle: {
tags: ['ready'],
meta: { color: 'blue' },
on: {
START: {
target: 'loading',
actions: ['setLoading']
}
}
},
loading: {
tags: ['busy', 'visible'],
entry: ['startLoader'],
exit: ['stopLoader'],
invoke: {
id: 'dataFetcher',
src: 'fetchData',
onDone: {
target: 'success',
actions: ['setData']
},
onError: {
target: 'error',
actions: ['setError']
}
},
after: {
5000: {
target: 'timeout',
actions: ['logTimeout']
}
},
always: [
{
target: 'error',
guard: 'hasError'
}
]
},
success: {
type: 'final',
output: { status: 'completed' }
},
error: {
tags: ['error'],
on: {
RETRY: 'idle'
}
},
timeout: {
tags: ['error', 'timeout'],
on: {
RETRY: 'idle'
}
}
}
}, {
actions: {
setLoading: assign({ loading: true }),
setData: assign({ data: ({ event }) => event.output }),
setError: assign({ error: ({ event }) => event.error }),
startLoader: () => console.log('Loading...'),
stopLoader: () => console.log('Stopped loading'),
logTimeout: () => console.log('Timeout occurred')
},
guards: {
hasError: ({ context }) => context.error !== null
},
actors: {
fetchData: 'fetchDataService'
}
});After (XState Builders):
// Define enums for the complex example
enum ComplexStates {
IDLE = 'idle',
LOADING = 'loading',
SUCCESS = 'success',
ERROR = 'error',
TIMEOUT = 'timeout'
}
enum ComplexEvents {
START = 'START',
RETRY = 'RETRY'
}
enum ComplexActions {
SET_LOADING = 'setLoading',
START_LOADER = 'startLoader',
STOP_LOADER = 'stopLoader',
SET_DATA = 'setData',
SET_ERROR = 'setError',
LOG_TIMEOUT = 'logTimeout'
}
enum ComplexGuards {
HAS_ERROR = 'hasError'
}
enum ComplexTags {
READY = 'ready',
BUSY = 'busy',
VISIBLE = 'visible',
SUCCESS = 'success',
ERROR = 'error'
}
const machine = GenericMachineBuilder.create()
.withId('complex')
.withInitial(ComplexStates.IDLE)
.withContext({ count: 0, error: null })
.withStates(
GenericStatesBuilder.create()
.withState(ComplexStates.IDLE,
GenericStateBuilder.create()
.withTag(ComplexTags.READY)
.withMeta({ color: 'blue' })
.withTransition(ComplexEvents.START, ComplexStates.LOADING, [ComplexActions.SET_LOADING])
.build()
)
.withState(ComplexStates.LOADING,
GenericStateBuilder.create()
.withTags(ComplexTags.BUSY, ComplexTags.VISIBLE)
.withEntry([ComplexActions.START_LOADER])
.withExit([ComplexActions.STOP_LOADER])
.withInvoke({
id: 'dataFetcher',
src: ServiceNames.FETCH_DATA,
onDone: { target: ComplexStates.SUCCESS, actions: [ComplexActions.SET_DATA] },
onError: { target: ComplexStates.ERROR, actions: [ComplexActions.SET_ERROR] }
})
.withAfter(
GenericDelayedTransitionsBuilder.create()
.afterWithActions(5000, [ComplexActions.LOG_TIMEOUT], ComplexStates.TIMEOUT)
.build()
)
.withAlways(ComplexStates.ERROR, ComplexGuards.HAS_ERROR)
.build()
)
.withState(ComplexStates.SUCCESS,
GenericStateBuilder.create()
.asFinalStateWithOutput({ status: 'completed' })
.build()
)
.withState(ComplexStates.ERROR,
GenericStateBuilder.create()
.withTag(ComplexTags.ERROR)
.withTransition(ComplexEvents.RETRY, ComplexStates.IDLE)
.build()
)
.withState(ComplexStates.TIMEOUT,
GenericStateBuilder.create()
.withTag(ComplexTags.ERROR)
.withTransition(ComplexEvents.RETRY, ComplexStates.IDLE)
.build()
)
.build()
)
.withActions(
GenericActionsBuilder.create()
.withAssignAction('setLoading', { loading: true })
.withAssignAction('setData', ({ event }) => ({ data: event.output }))
.withAssignAction('setError', ({ event }) => ({ error: event.error }))
.withAction('startLoader', () => console.log('Loading...'))
.withAction('stopLoader', () => console.log('Stopped loading'))
.withAction('logTimeout', () => console.log('Timeout occurred'))
.build()
)
.withGuards(
GenericGuardsBuilder.create()
.withGuard('hasError', ({ context }) => context.error !== null)
.build()
)
.withActors(
GenericActorsBuilder.create()
.withActor(ActorIds.DataFetcher, ServiceNames.FETCH_DATA)
.build()
)
.build();Benefits:
- 🔧 More Readable: Self-documenting, fluent API
- 🎯 Type Safe: Catch errors at compile time
- 📦 Reusable: Share builder configurations
- 🧩 Composable: Mix and match builders
- 🛡️ Reliable: Comprehensive test coverage (425+ tests)
- 🚀 Modern: Built for XState v5 with all latest features
- 🎭 Actor Ready: Full support for XState v5 actor model
📊 Test Coverage
The library maintains high test coverage across all builders:
Test Suites: 16 passed
Tests: 425 passed
Coverage: 79% branches, 55% statements
Time: ~1.2s🤝 Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/AmazingFeature) - Add tests for your changes
- Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
📄 License
MIT License - see the LICENSE file for details.
🔗 Links
Built with ❤️ for the XState community by German Kuber
