Advanced Topics

Architecture Guide

Advanced patterns for large-scale StateFlow applications, drawn from production systems like media players and real-time applications.

Multi-State Applications

Coordinated State Systems

Production applications often require 7+ interconnected states working together:

// Define related states that need to coordinate
const connectionState = defineState<{
  url: string;
  retryCount: number;
  lastError?: Error;
}>()
  .name("connection")
  .variant("disconnected", true)
  .variant("connecting")
  .variant("connected")
  .variant("failed")
  .build();

const mediaState = defineState<{
  duration: number;
  position: number;
  buffered: TimeRanges | null;
}>()
  .name("media")
  .variant("idle", true)
  .variant("loading")
  .variant("ready")
  .variant("error")
  .build();

const playbackState = defineState<{
  volume: number;
  playbackRate: number;
  seeking: boolean;
}>()
  .name("playback")
  .variant("stopped", true)
  .variant("playing")
  .variant("paused")
  .variant("buffering")
  .build();

State Dependencies and Cross-State Logic

Implement state dependencies using the context parameter in flows:

// Application type with all states
interface MediaApplication {
  connection: Infer<typeof connectionState>;
  media: Infer<typeof mediaState>;
  playback: Infer<typeof playbackState>;
}

// Flow that depends on other states
defineFlow(playbackState.stopped, {
  play: (state, signal, context: MediaApplication) => {
    // Check if we can play based on other states
    if (stateVar(context.connection) !== 'connected') {
      return Result.reject('Cannot play while disconnected');
    }
    
    if (stateVar(context.media) !== 'ready') {
      return Result.reject('Media not ready for playback');
    }
    
    return playbackState.playing({
      ...state,
      seeking: false
    });
  }
});

// Cross-state reactions
defineFlow(connectionState.connecting, {
  connectionFailed: (state, signal, context: MediaApplication) => {
    // When connection fails, reset dependent states
    return connectionState.failed({
      ...state,
      lastError: signal.error,
      retryCount: state.retryCount + 1
    });
  }
});

Signal Organization at Scale

Hierarchical Signal Structure

For applications with 50+ signals, organize them by domain and purpose:

// Production example from video player
const signals = {
  // Connection management
  connection: {
    connect: defineSignal<{ url: string; options?: ConnectionOptions }>("connect"),
    disconnect: defineSignal("disconnect"),
    retry: defineSignal("retry"),
    timeout: defineSignal<{ after: number }>("timeout"),
  },

  // Media loading and preparation
  media: {
    load: defineSignal<{ source: MediaSource }>("loadMedia"),
    metadata: defineSignal<{ duration: number; tracks: Track[] }>("mediaMetadata"),
    error: defineSignal<{ code: number; message: string }>("mediaError"),
    buffering: defineSignal<{ buffered: TimeRanges }>("buffering"),
  },

  // Playback control
  playback: {
    play: defineSignal("play"),
    pause: defineSignal("pause"),
    stop: defineSignal("stop"),
    seek: defineSignal<{ position: number; precise?: boolean }>("seek"),
    setVolume: defineSignal<{ level: number }>("setVolume"),
    setRate: defineSignal<{ rate: number }>("setRate"),
  },

  // User interface
  ui: {
    showControls: defineSignal("showControls"),
    hideControls: defineSignal("hideControls"),
    toggleFullscreen: defineSignal("toggleFullscreen"),
    showError: defineSignal<{ message: string; recoverable: boolean }>("showError"),
  },

  // Internal system signals
  internal: {
    tick: defineSignal<{ currentTime: number }>("tick"),
    qualityChange: defineSignal<{ level: QualityLevel }>("qualityChange"),
    cleanup: defineSignal("cleanup"),
  }
};

Signal Naming Conventions

Establish consistent naming patterns:

// Pattern: domain.verb[Object] for user actions
signals.playback.seekToPosition({ position: 30 })
signals.connection.connectToUrl({ url: "..." })
signals.ui.showErrorMessage({ message: "..." })

// Pattern: domain.on[Event] for external events
signals.media.onLoadComplete({ duration: 180 })
signals.connection.onDisconnected()
signals.playback.onTimeUpdate({ position: 45 })

// Pattern: internal.[system] for internal operations
signals.internal.cleanup()
signals.internal.syncStates()
signals.internal.validateConfiguration()

Advanced State Patterns

State Machines with Complex Transitions

Use nested state logic for complex state machines:

const downloadState = defineState<{
  url: string;
  progress: number;
  speed: number; // bytes/second
  resumeData?: ResumeData;
}>()
  .name("download")
  .variant("idle", true)
  .variant("preparing")
  .variant("downloading") 
  .variant("paused")
  .variant("completed")
  .variant("failed")
  .variant("cancelled")
  .build();

// Complex state machine logic
defineFlow(downloadState.downloading, {
  pause: (state) => {
    // Can pause from downloading
    return downloadState.paused({
      ...state,
      resumeData: captureResumeData(state)
    });
  },

  progress: (state, signal) => {
    // Update progress while downloading
    if (signal.progress >= 100) {
      return downloadState.completed(state);
    }
    return {
      ...state,
      progress: signal.progress,
      speed: calculateSpeed(state, signal)
    };
  },

  networkError: (state, signal) => {
    // Handle network interruptions
    if (signal.error.recoverable && state.resumeData) {
      return downloadState.paused({
        ...state,
        resumeData: signal.error.resumeData
      });
    }
    return downloadState.failed({
      ...state,
      error: signal.error
    });
  }
});

State Composition Patterns

Break complex states into composable pieces:

// Instead of one large state
interface MonolithicPlayerState {
  // 50+ properties covering all aspects
  connectionUrl: string;
  connectionStatus: string;
  mediaUrl: string;
  mediaDuration: number;
  playbackVolume: number;
  playbackPosition: number;
  uiControlsVisible: boolean;
  uiFullscreen: boolean;
  // ... many more
}

// Prefer composition of focused states
interface ComposedPlayerApplication {
  connection: ConnectionState;
  media: MediaState; 
  playback: PlaybackState;
  ui: UIState;
  quality: QualityState;
  audio: AudioState;
  video: VideoState;
}

// Each state handles its own domain
const audioState = defineState<{
  volume: number;
  muted: boolean;
  tracks: AudioTrack[];
  selectedTrack?: string;
}>()
  .name("audio")
  .variant("unavailable", true)
  .variant("available")
  .variant("processing")
  .build();

Resource Management

Lifecycle Management with State Handlers

Use StateFlow's handler system for resource management:

interface VideoPlayerApp {
  connection: Infer<typeof connectionState>;
  media: Infer<typeof mediaState>;
  playback: Infer<typeof playbackState>;
  resources: {
    webSocket?: WebSocket;
    mediaElement?: HTMLVideoElement;
    bufferController?: BufferController;
    timers: Map<string, NodeJS.Timer>;
  };
}

applyFlow(app, [connectionState, mediaState, playbackState], (sm) => {
  // Resource acquisition
  sm.addEnterHandler(connectionState.connecting, async (state, context) => {
    return Result.transition(async () => {
      try {
        const ws = new WebSocket(state.url);
        
        // Set up event handlers
        ws.onopen = () => dispatch(context, signals.connection.connected()).done();
        ws.onerror = (error) => dispatch(context, signals.connection.failed({ error })).done();
        
        context.resources.webSocket = ws;
        
        // Wait for connection with timeout
        await waitForConnection(ws, 5000);
        return Result.ok();
      } catch (error) {
        return Result.error(error);
      }
    }, 5000);
  });

  // Resource cleanup
  sm.addExitHandler(connectionState.connected, (state, context) => {
    if (context.resources.webSocket) {
      context.resources.webSocket.close();
      context.resources.webSocket = undefined;
    }
    return Result.ok();
  });

  // Automatic cleanup on state changes
  sm.addExitHandler(playbackState.playing, (state, context) => {
    // Clear any playback timers
    context.resources.timers.forEach((timer) => clearInterval(timer));
    context.resources.timers.clear();
    return Result.ok();
  });

  // Error recovery
  sm.addRollbackHandler(mediaState.loading, (state, context) => {
    // Clean up partially loaded resources
    if (context.resources.bufferController) {
      context.resources.bufferController.abort();
      context.resources.bufferController = undefined;
    }
    return Result.ok();
  });
});

Memory Management and Cleanup

// Automatic disposal pattern
class VideoPlayerApplication implements Disposable {
  private disposables: Array<{ [Symbol.dispose](): void }> = [];
  
  constructor() {
    // Set up observers that auto-cleanup
    this.disposables.push(
      observe(this, [connectionState.failed], (state) => {
        this.handleConnectionFailure(state);
      }),
      
      observe(this, [mediaState.error], (state) => {
        this.handleMediaError(state);
      })
    );
  }

  [Symbol.dispose]() {
    // Clean up all observers
    this.disposables.forEach(disposable => disposable[Symbol.dispose]());
    
    // Clean up resources
    this.resources.webSocket?.close();
    this.resources.timers.forEach(timer => clearInterval(timer));
  }
}

// Usage with automatic cleanup
function createPlayer(): VideoPlayerApplication {
  using player = new VideoPlayerApplication();
  
  // Player automatically disposed when scope exits
  return player;
}

Integration Patterns

External System Integration

Connect StateFlow to external APIs and services:

// Service integration through handlers
interface ExternalServices {
  analyticsService: AnalyticsService;
  authService: AuthService;
  mediaService: MediaService;
}

applyFlow(app, states, (sm) => {
  // Analytics integration
  sm.addEnterHandler(playbackState.playing, (state, context) => {
    context.services.analyticsService.trackEvent('playback_started', {
      mediaId: context.media.id,
      position: state.position,
      timestamp: Date.now()
    });
    return Result.ok();
  });

  // Authentication integration
  sm.addEnterHandler(connectionState.connecting, async (state, context) => {
    return Result.transition(async () => {
      try {
        const token = await context.services.authService.getValidToken();
        
        // Update connection with auth token
        const authenticatedUrl = addAuthToken(state.url, token);
        return connectionState.connecting({
          ...state,
          url: authenticatedUrl
        });
      } catch (error) {
        return Result.error(error);
      }
    }, 3000);
  });
});

Event System Integration

Bridge StateFlow with external event systems:

// Two-way integration with DOM events
class DOMIntegration {
  constructor(private app: MediaApplication, private element: HTMLVideoElement) {
    this.setupDOMListeners();
    this.setupStateFlowObservers();
  }

  private setupDOMListeners() {
    // DOM events -> StateFlow signals
    this.element.addEventListener('play', () => {
      dispatch(this.app, signals.playback.play()).done();
    });

    this.element.addEventListener('pause', () => {
      dispatch(this.app, signals.playback.pause()).done();
    });

    this.element.addEventListener('timeupdate', () => {
      dispatch(this.app, signals.internal.tick({ 
        currentTime: this.element.currentTime 
      })).done();
    });

    this.element.addEventListener('error', (event) => {
      dispatch(this.app, signals.media.error({
        code: this.element.error?.code || 0,
        message: this.element.error?.message || 'Unknown error'
      })).done();
    });
  }

  private setupStateFlowObservers() {
    // StateFlow states -> DOM updates
    observe(this.app, [playbackState.playing], (state) => {
      if (this.element.paused) {
        this.element.play().catch(error => {
          dispatch(this.app, signals.playback.error({ error })).done();
        });
      }
    });

    observe(this.app, [playbackState.paused], (state) => {
      if (!this.element.paused) {
        this.element.pause();
      }
    });

    observe(this.app, [playbackState.seeking], (state) => {
      if (Math.abs(this.element.currentTime - state.position) > 0.5) {
        this.element.currentTime = state.position;
      }
    });
  }
}

Performance Optimization

Efficient Observer Patterns

Optimize observer performance for large applications:

// Batch observer updates
class BatchedObserver {
  private pendingUpdates = new Map<string, StateInstance>();
  private updateScheduled = false;

  observe<T>(app: Application, states: StateVariant<T>[], handler: (state: StateInstance<T>) => void) {
    return observe(app, states, (state) => {
      // Batch updates instead of immediate handling
      const stateKey = `${getName(state)}.${stateVar(state)}`;
      this.pendingUpdates.set(stateKey, state);
      
      if (!this.updateScheduled) {
        this.updateScheduled = true;
        queueMicrotask(() => this.flushUpdates(handler));
      }
    });
  }

  private flushUpdates<T>(handler: (state: StateInstance<T>) => void) {
    for (const [key, state] of this.pendingUpdates) {
      handler(state as StateInstance<T>);
    }
    this.pendingUpdates.clear();
    this.updateScheduled = false;
  }
}

// Selective observation with custom comparisons
observe(
  app,
  [mediaState.ready],
  (state) => expensiveUIUpdate(state),
  (prev, curr) => {
    // Only update UI if meaningful properties changed
    return prev.duration !== curr.duration || 
           prev.tracks.length !== curr.tracks.length ||
           prev.quality !== curr.quality;
  }
);

State Validation and Runtime Checks

Add runtime validation for production applications:

// Schema validation with Zod
import { z } from 'zod';

const MediaPropsSchema = z.object({
  url: z.string().url(),
  duration: z.number().min(0),
  position: z.number().min(0),
  bitrate: z.number().positive().optional(),
  tracks: z.array(z.object({
    id: z.string(),
    type: z.enum(['audio', 'video', 'subtitle']),
    language: z.string().optional()
  }))
});

const mediaState = defineState<z.infer<typeof MediaPropsSchema>>()
  .name("media")
  .variant("loading", true)
  .variant("ready")
  .variant("error")
  .parser(obj => {
    // Runtime validation
    const result = MediaPropsSchema.safeParse(obj);
    if (!result.success) {
      throw new StateFlowError(`Invalid media state: ${result.error.message}`);
    }
    return result.data;
  })
  .stringRepr(s => `${s.url} (${Math.round(s.position)}/${Math.round(s.duration)}s)`)
  .build();

Error Handling and Recovery

Graceful Degradation Patterns

Implement fallback states for robust applications:

const qualityState = defineState<{
  current: QualityLevel;
  available: QualityLevel[];
  auto: boolean;
  fallbackReason?: string;
}>()
  .name("quality")
  .variant("auto", true) // Automatic quality selection
  .variant("manual") // User selected quality
  .variant("degraded") // Fallback due to network/performance
  .variant("unavailable") // No quality options available
  .build();

defineFlow(qualityState.auto, {
  networkDegraded: (state, signal) => {
    // Automatic fallback to lower quality
    const fallbackQuality = findBestQuality(
      state.available, 
      signal.bandwidth,
      signal.dropRate
    );
    
    if (!fallbackQuality) {
      return qualityState.unavailable({
        ...state,
        fallbackReason: 'No suitable quality for current network conditions'
      });
    }
    
    return qualityState.degraded({
      ...state,
      current: fallbackQuality,
      fallbackReason: `Downgraded due to ${signal.reason}`
    });
  },

  networkImproved: (state, signal) => {
    // Try to upgrade quality when conditions improve
    const betterQuality = findBetterQuality(
      state.available,
      state.current,
      signal.bandwidth
    );
    
    if (betterQuality && betterQuality.bitrate > state.current.bitrate) {
      return {
        ...state,
        current: betterQuality,
        fallbackReason: undefined
      };
    }
    
    return Result.ignore('No better quality available');
  }
});

These architectural patterns enable building complex, maintainable StateFlow applications that can scale with your needs while maintaining the core benefits of type safety, immutability, and explicit error handling.

Previous
Testing