Services Pattern
The Services pattern creates a dynamic publish-subscribe system that enables loose coupling between data producers and consumers in StateWalker applications. This lightweight implementation supports reactive programming where components can dynamically register to provide or consume data without direct dependencies.
Key Features:
- Multiple Providers: Several components can contribute values to the same service
- Multiple Consumers: Many components can listen to service changes simultaneously
- Reactive Updates: Consumers receive automatic notifications when providers update values
- Memory Management: Built-in cleanup prevents memory leaks
- Error Isolation: Safe error handling prevents component failures from affecting others
- Service Registry: Optional named service management for organization
References:
newService
Type Definition
/**
* Creates a new service that allows consumers to subscribe to updates
* and providers to supply values. This service supports multiple consumers
* and providers, ensuring that all consumers are notified whenever a provider
* updates the values.
*
* @typeParam T - The type of the values managed by the service.
* @returns A tuple containing:
* - `consumeService`: A function that allows consumers to subscribe to the service.
* - @param callback - A function that will be called with the current values whenever they are updated.
* - @returns A function to unsubscribe the consumer from the service.
* - `newServiceProvider`: A function that creates a new provider for the service.
* - @param initialValue - An optional initial value to be provided by the new provider.
* - @returns A tuple containing:
* - `provideService`: A function to update the value provided by this provider.
* - @param service - The new value to provide.
* - `removeService`: A function to remove this provider from the service.
*/
function newService<T>(): [
consumeService: (callback: (values: T[]) => void) => () => void,
newServiceProvider: (
initialValue?: T,
) => [provideService: (service: T) => void, removeService: () => void],
]
The services pattern provides reactive data flow with multiple providers contributing values and multiple consumers receiving automatic updates, all with proper error isolation and memory management.
newContextService
Type Definition
/**
* Creates a new context-aware service that allows consumers to subscribe to updates
* and providers to supply values within a hierarchical context. This service ensures
* that all consumers in the same root context are notified whenever a provider updates
* the values.
*
* @typeParam T - The type of the values managed by the service.
* @typeParam C - The type of the context used to determine the hierarchy.
* @param key - A unique key used to store the service in the root context.
* @param getParentContext - A function that retrieves the parent context of a given context.
* @returns A tuple containing:
* - `consumeService`: A function that allows consumers to subscribe to the service within a context.
* - @param context - The context in which the consumer is subscribing.
* - @param callback - A function that will be called with the current values whenever they are updated.
* - @returns A function to unsubscribe the consumer from the service.
* - `newServiceProvider`: A function that creates a new provider for the service within a context.
* - @param context - The context in which the provider is being created.
* - @param initialValue - An optional initial value to be provided by the new provider.
* - @returns A tuple containing:
* - `provideService`: A function to update the value provided by this provider.
* - @param service - The new value to provide.
* - `removeService`: A function to remove this provider from the service.
*/
function newContextService<T, C>(
key: string,
getParentContext: (context: C) => C | undefined,
): [
// Registers a new service consumer callback in the context and returns a cleanup function
consumeService: (context: C, callback: (values: T[]) => void) => () => void,
// Registers a new service provider in the context and returns two functions:
// - provideService: updates the service value
// - removeProvider: unregisters the provider
newServiceProvider: (
context: C,
initialValue?: T,
) => [provideService: (service: T) => void, removeService: () => void],
]
Context-aware services extend the basic service pattern to work within hierarchical structures, enabling service inheritance and scoped data sharing across parent-child relationships. This approach is particularly valuable in StateWalker applications where process contexts form natural hierarchies, allowing services to be shared across different levels of the application while maintaining proper scoping.
Unlike basic services that operate in isolation, contextual services automatically discover and utilize the root context of a hierarchy, ensuring that all related contexts share the same service instance. This creates a unified service space where providers and consumers across different context levels can communicate seamlessly.
Key Benefits:
- Hierarchical Awareness: Services automatically traverse context hierarchies to find the appropriate root
- Scoped Sharing: Services are shared within context trees but isolated between different hierarchies
- Automatic Discovery: No need to manually pass service instances between related contexts
- Inheritance Pattern: Child contexts naturally inherit services from their parent contexts
Common Use Cases:
- Cross-state communication in FSM processes
- Feature-specific service scoping
- Plugin systems with context isolation
- Hierarchical configuration management
newService
Usage Examples
Basic service usage:
const [newConsumer, newProvider] = newService<string>();
// Register a consumer
const unsubscribe = newConsumer((values) => {
console.log("Current values:", values);
});
// Register a provider
const [provideService, removeProvider] = newProvider();
provideService("Hello, world!");
// Output: Current values: ["Hello, world!"]
// Cleanup
unsubscribe();
removeProvider();
Use TypeScript generics for type-safe implementations:
interface UserProfile {
id: string;
name: string;
email: string;
preferences: Record<string, unknown>;
}
const [newUserConsumer, newUserProvider] = newService<UserProfile>();
// TypeScript ensures type safety
const [provideProfile, removeProfile] = newUserProvider();
provideProfile({
id: "user-123",
name: "John Doe",
email: "john@example.com",
preferences: { theme: "dark", language: "en" }
});
Multi-provider scenario:
// TypeScript ensures type safety
interface UserProfile {
id: string;
name: string;
}
const [newUserConsumer, newUserProvider] = newService<UserProfile>();
// Create multiple providers
const [provideUser1, removeProvider1] = newUserProvider();
const [provideUser2, removeProvider2] = newUserProvider();
// Register consumer
const unsubscribe = newUserConsumer((users) => {
console.log(`Received ${users.length} users:`, users);
});
// Providers add users
provideUser1({ id: 1, name: "Alice" });
provideUser2({ id: 2, name: "Bob" });
// Output: Received 2 users: [{ id: 1, name: "Alice" }, { id: 2, name: "Bob" }]
// Update user
provideUser1({ id: 1, name: "Alice Smith" });
// Output: Received 2 users: [{ id: 1, name: "Alice Smith" }, { id: 2, name: "Bob" }]
// Cleanup consumer
unsubscribe();
// Cleanup providers
removeProvider1();
removeProvider2();
newContextService
Usage Example
interface User {
id : number;
name : string;
}
const [onUsersUpdates, newUserProvider] = newContextService<User>("users");
const context = {}
const [provideUser, removeUserProvider] = newUserProvider(context);
const unsubscribeUsers = onUsersUpdates(context, (users) => {
console.log("Users updated:", users);
});
provideUser({ id: 1, name: "John" });