npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

xstate-builders-gk

v1.0.3

Published

Fluent builder pattern library for XState v5+ machines with full TypeScript support

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.

Build Status TypeScript XState License Coverage

✨ 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 xstate

Enums 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 checking

Project 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 configuration

Builder 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.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/AmazingFeature)
  3. Add tests for your changes
  4. Commit your changes (git commit -m 'Add some AmazingFeature')
  5. Push to the branch (git push origin feature/AmazingFeature)
  6. Open a Pull Request

📄 License

MIT License - see the LICENSE file for details.

🔗 Links


Built with ❤️ for the XState community by German Kuber