Controllers

Business Logic Coordination

Controllers coordinate business logic and data flow in StateWalker applications, serving as the intermediary layer between user interactions and application state. They operate within the framework’s unidirectional data flow architecture, observing input model changes, executing business operations, and updating output models with results.

Controllers access models, APIs, and services through the process context using the adapter pattern. This approach maintains loose coupling while enabling resource sharing across components. The process context serves as a dependency injection mechanism without requiring complex configuration.

Controllers establish reactive subscriptions to models through onChange listeners, enabling them to respond automatically to data changes. When input models change, controllers execute business logic such as API calls, data validation, or calculations, then update output models with the results.

Controllers return cleanup functions to ensure proper resource management during state transitions. These cleanup functions release allocated resources, unsubscribe from reactive subscriptions, and cancel pending operations, preventing memory leaks and maintaining system stability.

Minimal Controller: Simple State Update

export default function MyStateController(context: ProcessContext) {
  // Context sharing: Access models through context
  const simpleModel = getSimpleModel(context);

  // Business logic: Update model state
  simpleModel.update({ key: "value" });
}

Basic Controller: Resource Access and Cleanup

export default async function UserController(context: ProcessContext) {
  // Context sharing: Access models and APIs through context
  const userModel = getUserModel(context);
  const userAPI = getUserAPI(context);
  
  // Business logic: Load initial data
  const userData = await userAPI.getCurrentUser();
  userModel.setUser(userData);
  
  // Cleanup function: Release resources when state exits
  return () => {
    // Cancel pending requests, clear timers, etc.
  };
}

Reactive Controller: Model Observation and State Management

async function SearchController(context: ProcessContext) {
  // Registry pattern: Manage multiple cleanup functions
  const [register, cleanup] = newRegistry();
  
  // Context sharing: Access input/output models and services
  const searchForm = getSearchFormModel(context);     // Input model
  const searchResults = getSearchResultsModel(context); // Output model
  const searchApi = getSearchApi(context);            // External service
  
  // Initialize models to clean state
  searchForm.reset();
  searchResults.reset();
  
  // Reactive pattern: Respond to model changes
  const runSearch = async () => {
    const query = searchForm.query;
    
    if (searchForm.isSubmitted()) {
      // Update output model with loading state
      searchResults.loading = true;
      searchResults.error = null;
      
      try {
        // Business logic: Perform API call
        const results = await searchApi.searchResults({ query });
        searchResults.items = results;
      } catch (error) {
        // Error handling: Update model with error state
        searchResults.error = error;
      } finally {
        // State cleanup: Reset loading and form
        searchResults.loading = false;
        searchForm.reset();
      }
    }
  };
  
  // Reactive subscription: Listen to input model changes
  register(searchForm.onChange(runSearch));
  
  // Initial search execution on controller activation
  await runSearch();
  
  // Cleanup function: Registry automatically releases all resources
  return cleanup;
}

Advanced Controller: Multiple Resources and Complex Cleanup

async function DatabaseController(context: ProcessContext) {
  const [register, cleanup] = newRegistry();
  
  // Context sharing: Access database service and models
  const database = getDatabaseApi(context);
  const dataModel = getDataModel(context);
  const statusModel = getStatusModel(context);
  
  try {
    // Resource acquisition: Establish database connection
    const connection = await database.connect();
    
    // Resource cleanup: Register connection cleanup
    register(() => connection.close());
    
    // Reactive pattern: Listen to data model changes
    const handleDataChanges = async (data) => {
      statusModel.syncing = true;
      try {
        await connection.save(data);
        statusModel.lastSaved = Date.now();
      } catch (error) {
        statusModel.error = error;
      } finally {
        statusModel.syncing = false;
      }
    };
    
    // Multiple subscriptions: Register cleanup for each listener
    register(dataModel.onChange(handleDataChanges));
    
    // Periodic tasks: Setup interval and register cleanup
    const syncInterval = setInterval(() => {
      connection.ping();
    }, 30000);
    register(() => clearInterval(syncInterval));
    
  } catch (initError) {
    // Error handling: Log initialization failure
    console.error("Database controller initialization failed:", initError);
  }
  
  // Cleanup function: All registered cleanups called automatically
  return cleanup;
}

Summary

Reactive Pattern: Controllers observe model changes through onChange subscriptions and respond with business operations within the unidirectional data flow architecture. This eliminates manual coordination between components.

Context Access: The process context provides access to models, APIs, and services using the adapter pattern. This maintains loose coupling while enabling resource sharing across the application.

Resource Management: Controllers return cleanup functions that release resources when states transition. The registry pattern manages multiple cleanup operations automatically, ensuring proper resource lifecycle management.

These patterns combine to create controllers that respond to model changes, access shared resources through context, and maintain clean resource management throughout the application lifecycle.