Some checks failed
🚀 Build & Deploy Image / Determine Build Necessity (push) Failing after 10m14s
🚀 Build & Deploy Image / Build Runtime Base Image (push) Has been skipped
🚀 Build & Deploy Image / Build Docker Image (push) Has been skipped
🚀 Build & Deploy Image / Run Tests & Quality Checks (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Staging (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Production (push) Has been skipped
Security Vulnerability Scan / Check for Dependency Changes (push) Failing after 11m25s
Security Vulnerability Scan / Composer Security Audit (push) Has been cancelled
- Remove middleware reference from Gitea Traefik labels (caused routing issues) - Optimize Gitea connection pool settings (MAX_IDLE_CONNS=30, authentication_timeout=180s) - Add explicit service reference in Traefik labels - Fix intermittent 504 timeouts by improving PostgreSQL connection handling Fixes Gitea unreachability via git.michaelschiemer.de
398 lines
8.9 KiB
Markdown
398 lines
8.9 KiB
Markdown
# State Manager Module
|
|
|
|
**Centralized, Reactive State Management for Client-Side State**
|
|
|
|
The State Manager Module provides a centralized state management system similar to Redux or Vuex, with support for state persistence, cross-tab synchronization, and integration with LiveComponents.
|
|
|
|
---
|
|
|
|
## Features
|
|
|
|
- **Reactive State Store** - Subscribe to state changes and react automatically
|
|
- **State Persistence** - Automatically save and restore state from localStorage or sessionStorage
|
|
- **Cross-Tab Synchronization** - Keep state synchronized across browser tabs
|
|
- **Integration with LiveComponents** - Seamless integration with LiveComponent state
|
|
- **Time-Travel Debugging** - Debug state changes with history and time-travel
|
|
- **Middleware Support** - Extend functionality with custom middleware
|
|
|
|
---
|
|
|
|
## Quick Start
|
|
|
|
### Basic Usage
|
|
|
|
```javascript
|
|
import { StateManager } from './modules/state-manager/index.js';
|
|
|
|
// Create a state manager
|
|
const store = StateManager.create({
|
|
initialState: {
|
|
user: { name: '', email: '' },
|
|
cart: { items: [], total: 0 },
|
|
ui: { sidebarOpen: false }
|
|
}
|
|
});
|
|
|
|
// Set state
|
|
store.set('user.name', 'John Doe');
|
|
store.set('cart.items', [{ id: 1, name: 'Product' }]);
|
|
|
|
// Get state
|
|
const userName = store.get('user.name');
|
|
const cartItems = store.get('cart.items', []);
|
|
|
|
// Subscribe to changes
|
|
const unsubscribe = store.subscribe('cart.items', (items) => {
|
|
console.log('Cart items changed:', items);
|
|
updateCartUI(items);
|
|
});
|
|
|
|
// Unsubscribe
|
|
unsubscribe();
|
|
```
|
|
|
|
### Module System Integration
|
|
|
|
```html
|
|
<!-- Enable global state manager -->
|
|
<script type="module">
|
|
import { init } from './modules/state-manager/index.js';
|
|
|
|
init({
|
|
initialState: {
|
|
user: {},
|
|
cart: {}
|
|
},
|
|
persistence: {
|
|
enabled: true,
|
|
storage: 'localStorage',
|
|
key: 'app-state'
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<!-- Access globally -->
|
|
<script>
|
|
window.StateManager.set('user.name', 'John');
|
|
const name = window.StateManager.get('user.name');
|
|
</script>
|
|
```
|
|
|
|
---
|
|
|
|
## API Reference
|
|
|
|
### StateManager.create(config)
|
|
|
|
Create a new StateManager instance.
|
|
|
|
**Parameters**:
|
|
- `config.initialState` - Initial state object
|
|
- `config.maxHistorySize` - Maximum history size (default: 50)
|
|
- `config.enableHistory` - Enable history for time-travel (default: false)
|
|
- `config.persistence.enabled` - Enable state persistence (default: false)
|
|
- `config.persistence.storage` - Storage type: 'localStorage' or 'sessionStorage' (default: 'localStorage')
|
|
- `config.persistence.key` - Storage key (default: 'app-state')
|
|
- `config.persistence.paths` - Array of paths to persist (empty = all)
|
|
- `config.sync.enabled` - Enable cross-tab synchronization (default: false)
|
|
|
|
**Example**:
|
|
```javascript
|
|
const store = StateManager.create({
|
|
initialState: { user: {}, cart: {} },
|
|
persistence: {
|
|
enabled: true,
|
|
storage: 'localStorage',
|
|
paths: ['user', 'cart'] // Only persist these paths
|
|
},
|
|
sync: {
|
|
enabled: true // Sync across tabs
|
|
}
|
|
});
|
|
```
|
|
|
|
### store.getState()
|
|
|
|
Get the entire state object.
|
|
|
|
**Returns**: `Record<string, any>`
|
|
|
|
### store.get(path, defaultValue)
|
|
|
|
Get state at a specific path.
|
|
|
|
**Parameters**:
|
|
- `path` - Dot-separated path (e.g., 'user.name')
|
|
- `defaultValue` - Default value if path doesn't exist
|
|
|
|
**Returns**: `any`
|
|
|
|
**Example**:
|
|
```javascript
|
|
const userName = store.get('user.name', 'Guest');
|
|
const cartTotal = store.get('cart.total', 0);
|
|
```
|
|
|
|
### store.set(path, value)
|
|
|
|
Set state at a specific path.
|
|
|
|
**Parameters**:
|
|
- `path` - Dot-separated path
|
|
- `value` - Value to set
|
|
|
|
**Example**:
|
|
```javascript
|
|
store.set('user.name', 'John Doe');
|
|
store.set('cart.items', [{ id: 1, name: 'Product' }]);
|
|
```
|
|
|
|
### store.dispatch(action)
|
|
|
|
Dispatch an action (Redux-style).
|
|
|
|
**Parameters**:
|
|
- `action` - Action object with `type` property, or a thunk function
|
|
|
|
**Example**:
|
|
```javascript
|
|
// Simple action
|
|
store.dispatch({
|
|
type: 'ADD_TO_CART',
|
|
productId: 123,
|
|
quantity: 1
|
|
});
|
|
|
|
// Thunk (async action)
|
|
store.dispatch(async (dispatch, getState) => {
|
|
const response = await fetch('/api/products');
|
|
const products = await response.json();
|
|
dispatch({ type: 'SET_PRODUCTS', products });
|
|
});
|
|
```
|
|
|
|
### store.subscribe(path, callback)
|
|
|
|
Subscribe to state changes at a specific path.
|
|
|
|
**Parameters**:
|
|
- `path` - Dot-separated path, or '*' for all changes
|
|
- `callback` - Callback function: `(newValue, oldValue, path) => void`
|
|
|
|
**Returns**: Unsubscribe function
|
|
|
|
**Example**:
|
|
```javascript
|
|
const unsubscribe = store.subscribe('cart.items', (items, oldItems, path) => {
|
|
console.log(`Cart items changed at ${path}:`, items);
|
|
updateCartUI(items);
|
|
});
|
|
|
|
// Later...
|
|
unsubscribe();
|
|
```
|
|
|
|
### store.subscribeAll(callback)
|
|
|
|
Subscribe to all state changes.
|
|
|
|
**Parameters**:
|
|
- `callback` - Callback function: `(state) => void`
|
|
|
|
**Returns**: Unsubscribe function
|
|
|
|
### store.use(middleware)
|
|
|
|
Add middleware to the state manager.
|
|
|
|
**Parameters**:
|
|
- `middleware` - Middleware function: `(action, getState) => action | null`
|
|
|
|
**Example**:
|
|
```javascript
|
|
// Logging middleware
|
|
store.use((action, getState) => {
|
|
console.log('Action:', action);
|
|
console.log('Current state:', getState());
|
|
return action; // Return action to continue, or null to block
|
|
});
|
|
|
|
// Validation middleware
|
|
store.use((action, getState) => {
|
|
if (action.type === 'SET' && action.path === 'user.email') {
|
|
if (!isValidEmail(action.value)) {
|
|
console.error('Invalid email');
|
|
return null; // Block the action
|
|
}
|
|
}
|
|
return action;
|
|
});
|
|
```
|
|
|
|
### store.getHistory()
|
|
|
|
Get action history for time-travel debugging.
|
|
|
|
**Returns**: `Array<HistoryPoint>`
|
|
|
|
### store.timeTravel(index)
|
|
|
|
Time-travel to a specific history point.
|
|
|
|
**Parameters**:
|
|
- `index` - History index
|
|
|
|
### store.reset()
|
|
|
|
Reset state to initial state.
|
|
|
|
### store.destroy()
|
|
|
|
Destroy the state manager and clean up resources.
|
|
|
|
---
|
|
|
|
## Integration with LiveComponents
|
|
|
|
```javascript
|
|
import { StateManager } from './modules/state-manager/index.js';
|
|
import { LiveComponentManager } from './modules/livecomponent/index.js';
|
|
|
|
const store = StateManager.create();
|
|
const lcManager = LiveComponentManager.getInstance();
|
|
|
|
// Sync LiveComponent state with StateManager
|
|
lcManager.on('component:state-updated', (componentId, state) => {
|
|
store.set(`livecomponents.${componentId}`, state);
|
|
});
|
|
|
|
// Update LiveComponent from StateManager
|
|
store.subscribe('livecomponents', (state) => {
|
|
Object.entries(state).forEach(([componentId, componentState]) => {
|
|
lcManager.updateComponentState(componentId, componentState);
|
|
});
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## Use Cases
|
|
|
|
### User Preferences
|
|
|
|
```javascript
|
|
const store = StateManager.create({
|
|
initialState: {
|
|
preferences: {
|
|
theme: 'light',
|
|
language: 'en',
|
|
notifications: true
|
|
}
|
|
},
|
|
persistence: {
|
|
enabled: true,
|
|
storage: 'localStorage',
|
|
paths: ['preferences']
|
|
}
|
|
});
|
|
|
|
// Save preference
|
|
store.set('preferences.theme', 'dark');
|
|
|
|
// Load preference
|
|
const theme = store.get('preferences.theme', 'light');
|
|
```
|
|
|
|
### Shopping Cart
|
|
|
|
```javascript
|
|
const store = StateManager.create({
|
|
initialState: {
|
|
cart: {
|
|
items: [],
|
|
total: 0
|
|
}
|
|
},
|
|
persistence: {
|
|
enabled: true,
|
|
storage: 'sessionStorage',
|
|
paths: ['cart']
|
|
},
|
|
sync: {
|
|
enabled: true // Sync cart across tabs
|
|
}
|
|
});
|
|
|
|
// Add item
|
|
store.set('cart.items', [
|
|
...store.get('cart.items', []),
|
|
{ id: 1, name: 'Product', price: 99.99 }
|
|
]);
|
|
|
|
// Calculate total
|
|
store.subscribe('cart.items', (items) => {
|
|
const total = items.reduce((sum, item) => sum + item.price, 0);
|
|
store.set('cart.total', total);
|
|
});
|
|
```
|
|
|
|
### UI State
|
|
|
|
```javascript
|
|
const store = StateManager.create({
|
|
initialState: {
|
|
ui: {
|
|
sidebarOpen: false,
|
|
modalOpen: false,
|
|
activeTab: 'home'
|
|
}
|
|
}
|
|
});
|
|
|
|
// Toggle sidebar
|
|
store.set('ui.sidebarOpen', !store.get('ui.sidebarOpen'));
|
|
|
|
// Subscribe to UI changes
|
|
store.subscribe('ui', (uiState) => {
|
|
updateUI(uiState);
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## Best Practices
|
|
|
|
1. **Use Scoped State Managers** - Create separate state managers for different features
|
|
2. **Persist Only Necessary Data** - Use `paths` to limit what gets persisted
|
|
3. **Use Middleware for Cross-Cutting Concerns** - Logging, validation, etc.
|
|
4. **Subscribe Selectively** - Only subscribe to paths you need
|
|
5. **Clean Up Subscriptions** - Always call unsubscribe when done
|
|
|
|
---
|
|
|
|
## Performance Considerations
|
|
|
|
- State updates are synchronous and immediate
|
|
- Subscriptions are called synchronously (be careful with expensive operations)
|
|
- Persistence is debounced internally
|
|
- Cross-tab sync uses BroadcastChannel (efficient)
|
|
|
|
---
|
|
|
|
## Browser Support
|
|
|
|
- **Chrome/Edge**: 38+
|
|
- **Firefox**: 38+
|
|
- **Safari**: 15.4+
|
|
- **Mobile**: iOS 15.4+, Android Chrome 38+
|
|
|
|
**Required Features**:
|
|
- ES2020 JavaScript
|
|
- BroadcastChannel (for cross-tab sync)
|
|
- localStorage/sessionStorage (for persistence)
|
|
|
|
---
|
|
|
|
**Next**: [Validation Module](validation.md) →
|
|
|