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.