Process Context
type ProcessContext = {
parent?: ProcessContext;
[key: string]: unknown;
}
Process context is a container for shared resources in StateWalker FSM applications. It passes through all state handlers and enables modules to access common entities without creating tight coupling between them.
The context acts as a communication bridge between orchestrator components, allowing handlers to share data models, APIs, services, and configuration while maintaining separation of concerns.
Process contexts support hierarchical structures where child contexts inherit resources from parent contexts. This enables resource scoping and configuration overrides without creating direct dependencies between modules.
The adapter pattern provides type-safe access to context resources through getter and setter functions. This eliminates manual type casting and provides compile-time validation of resource access.
Using Adapters for Resource Access
The adapter pattern provides a clean interface for storing and retrieving typed resources from the process context. By creating dedicated accessor functions, you establish clear contracts for resource management while maintaining type safety throughout your application.
import { newContextAdapter } from "@/utils/adapters.js";
// Create type-safe adapters for a FileSystem API
const [getFileSystem, setFileSystem] = newContextAdapter<FileSystem>("fileSystem");
// Create adapters for other common resources
const [getConfiguration, setConfiguration] = newContextAdapter<AppConfig>("config");
const [getLogger, setLogger] = newContextAdapter<Logger>("logger");
const [getUserModel, setUserModel] = newContextAdapter<UserModel>("user:model");
Resource initialization typically occurs in top-level application handlers during the bootstrap phase, ensuring that shared resources are available before other modules attempt to access them.
async function ApplicationBootstrap(context: ProcessContext) {
// Initialize core APIs and services
const filesApi = new InBrowserFilesApi({
baseDirectory: '/app-data',
maxFileSize: 10 * 1024 * 1024
});
const config = await loadConfiguration();
const logger = new ConsoleLogger(config.logLevel);
// Bind resources to context for application-wide access
setFileSystem(context, filesApi);
setConfiguration(context, config);
setLogger(context, logger);
// Initialize reactive data models
const userModel = new UserModel();
setUserModel(context, userModel);
return () => {
// Cleanup resources when application shuts down
filesApi.close();
logger.flush();
};
}
Once resources are bound to the context, any handler in the application can access them through the corresponding getter functions, maintaining type safety and clear dependency relationships.
async function SaveUserData(context: ProcessContext) {
const filesApi = getFileSystem(context);
const userModel = getUserModel(context);
const logger = getLogger(context);
try {
const userData = userModel.serialize();
await filesApi.writeFile('user-profile.json', userData);
logger.info('User data saved successfully');
} catch (error) {
logger.error('Failed to save user data', error);
throw error;
}
}
async function LoadConfiguration(context: ProcessContext) {
const config = getConfiguration(context);
const logger = getLogger(context);
logger.debug(`Loading configuration: ${config.environment}`);
// Use configuration to customize behavior
if (config.features.enableAnalytics) {
// Initialize analytics tracking
}
}
Hierarchical Context Management
The hierarchical nature of process contexts enables sophisticated resource scoping and inheritance patterns. Child contexts automatically inherit resources from their parents while allowing local overrides for specific use cases.
async function CreateUserSession(context: ProcessContext) {
// Retrieve a process runner from the parent context
const startProcess = getProcessRunner(context);
// Create child context for user session
const sessionContext: ProcessContext = { parent: context };
// Session-specific resources that override global ones
const sessionLogger = new SessionLogger(getUserId(context));
const sessionConfig = { ...getConfiguration(context), sessionTimeout: 3600 };
setLogger(sessionContext, sessionLogger);
setConfiguration(sessionContext, sessionConfig);
// Session context inherits FileSystem from parent but uses custom logger
const cleanupChildProcess = await startProces("child-process-name", sessionContext)
// Return the function terminating the child process
return cleanupChildProcess;
}
async function SessionHandler(context: ProcessContext) {
// Accesses session-specific logger but inherits FileSystem from parent
const logger = getLogger(context); // Session-specific logger
const filesApi = getFileSystem(context); // Inherited from parent context
logger.info('Session started');
await filesApi.createDirectory('session-data');
}
This hierarchical approach enables powerful patterns like feature flags, environment-specific configuration, and user-specific customization without polluting the global context or creating complex dependency chains. Each level of the hierarchy can provide its own resources while falling back to parent contexts for missing resources, creating a flexible and maintainable resource management system.