fix: Gitea Traefik routing and connection pool optimization
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
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
This commit is contained in:
397
docs/modules/state-manager.md
Normal file
397
docs/modules/state-manager.md
Normal file
@@ -0,0 +1,397 @@
|
||||
# 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) →
|
||||
|
||||
Reference in New Issue
Block a user