Enable Discovery debug logging for production troubleshooting
- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
This commit is contained in:
286
resources/js/core/StateManager.js
Normal file
286
resources/js/core/StateManager.js
Normal file
@@ -0,0 +1,286 @@
|
||||
/**
|
||||
* Reactive State Management System für Module-Kommunikation
|
||||
* Ermöglicht type-safe state sharing zwischen Modulen
|
||||
*/
|
||||
import { Logger } from './logger.js';
|
||||
|
||||
/**
|
||||
* @typedef {Object} StateChangeEvent
|
||||
* @property {string} key - State key that changed
|
||||
* @property {*} value - New value
|
||||
* @property {*} oldValue - Previous value
|
||||
* @property {string} source - Module that triggered the change
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} StateSubscription
|
||||
* @property {string} id - Unique subscription ID
|
||||
* @property {Function} callback - Callback function
|
||||
* @property {string} subscriber - Module name that subscribed
|
||||
*/
|
||||
|
||||
export class StateManager {
|
||||
constructor() {
|
||||
/** @type {Map<string, any>} */
|
||||
this.state = new Map();
|
||||
|
||||
/** @type {Map<string, StateSubscription[]>} */
|
||||
this.subscribers = new Map();
|
||||
|
||||
/** @type {Map<string, string>} */
|
||||
this.stateOwners = new Map();
|
||||
|
||||
/** @type {Map<string, any>} */
|
||||
this.defaultValues = new Map();
|
||||
|
||||
/** @type {string} */
|
||||
this.currentModule = 'unknown';
|
||||
|
||||
this.subscriptionCounter = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current module context for state operations
|
||||
* @param {string} moduleName - Name of the module
|
||||
*/
|
||||
setContext(moduleName) {
|
||||
this.currentModule = moduleName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a state key with default value and owner
|
||||
* @param {string} key - State key
|
||||
* @param {*} defaultValue - Default value
|
||||
* @param {string} [owner] - Module that owns this state
|
||||
*/
|
||||
register(key, defaultValue, owner = this.currentModule) {
|
||||
if (this.state.has(key)) {
|
||||
Logger.warn(`[StateManager] State key '${key}' already registered by ${this.stateOwners.get(key)}`);
|
||||
return;
|
||||
}
|
||||
|
||||
this.state.set(key, defaultValue);
|
||||
this.defaultValues.set(key, defaultValue);
|
||||
this.stateOwners.set(key, owner);
|
||||
this.subscribers.set(key, []);
|
||||
|
||||
Logger.info(`[StateManager] Registered '${key}' (owner: ${owner})`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current value of state key
|
||||
* @param {string} key - State key
|
||||
* @returns {*} Current value or undefined
|
||||
*/
|
||||
get(key) {
|
||||
if (!this.state.has(key)) {
|
||||
Logger.warn(`[StateManager] Unknown state key: '${key}'`);
|
||||
return undefined;
|
||||
}
|
||||
return this.state.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set value for state key (with ownership check)
|
||||
* @param {string} key - State key
|
||||
* @param {*} value - New value
|
||||
* @param {boolean} [force=false] - Force set even if not owner
|
||||
* @returns {boolean} Success status
|
||||
*/
|
||||
set(key, value, force = false) {
|
||||
if (!this.state.has(key)) {
|
||||
Logger.warn(`[StateManager] Cannot set unknown state key: '${key}'`);
|
||||
return false;
|
||||
}
|
||||
|
||||
const owner = this.stateOwners.get(key);
|
||||
if (!force && owner !== this.currentModule) {
|
||||
Logger.warn(`[StateManager] Module '${this.currentModule}' cannot modify '${key}' (owned by ${owner})`);
|
||||
return false;
|
||||
}
|
||||
|
||||
const oldValue = this.state.get(key);
|
||||
if (oldValue === value) {
|
||||
return true; // No change
|
||||
}
|
||||
|
||||
this.state.set(key, value);
|
||||
this.notifySubscribers(key, value, oldValue);
|
||||
|
||||
Logger.info(`[StateManager] Updated '${key}' by ${this.currentModule}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to state changes
|
||||
* @param {string} key - State key to watch
|
||||
* @param {Function} callback - Callback function (value, oldValue, key) => void
|
||||
* @param {string} [subscriber] - Subscriber module name
|
||||
* @returns {string} Subscription ID for unsubscribing
|
||||
*/
|
||||
subscribe(key, callback, subscriber = this.currentModule) {
|
||||
if (!this.state.has(key)) {
|
||||
Logger.warn(`[StateManager] Cannot subscribe to unknown state key: '${key}'`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const subscriptionId = `${subscriber}_${++this.subscriptionCounter}`;
|
||||
const subscription = {
|
||||
id: subscriptionId,
|
||||
callback,
|
||||
subscriber
|
||||
};
|
||||
|
||||
if (!this.subscribers.has(key)) {
|
||||
this.subscribers.set(key, []);
|
||||
}
|
||||
|
||||
this.subscribers.get(key).push(subscription);
|
||||
Logger.info(`[StateManager] Subscribed '${subscriber}' to '${key}'`);
|
||||
|
||||
return subscriptionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribe from state changes
|
||||
* @param {string} subscriptionId - Subscription ID from subscribe()
|
||||
*/
|
||||
unsubscribe(subscriptionId) {
|
||||
for (const [key, subscriptions] of this.subscribers.entries()) {
|
||||
const index = subscriptions.findIndex(sub => sub.id === subscriptionId);
|
||||
if (index !== -1) {
|
||||
const subscription = subscriptions[index];
|
||||
subscriptions.splice(index, 1);
|
||||
Logger.info(`[StateManager] Unsubscribed '${subscription.subscriber}' from '${key}'`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
Logger.warn(`[StateManager] Subscription ID not found: ${subscriptionId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify all subscribers of a state change
|
||||
* @private
|
||||
* @param {string} key - State key
|
||||
* @param {*} value - New value
|
||||
* @param {*} oldValue - Previous value
|
||||
*/
|
||||
notifySubscribers(key, value, oldValue) {
|
||||
const subscriptions = this.subscribers.get(key) || [];
|
||||
|
||||
subscriptions.forEach(subscription => {
|
||||
try {
|
||||
subscription.callback(value, oldValue, key);
|
||||
} catch (error) {
|
||||
Logger.error(`[StateManager] Error in subscriber '${subscription.subscriber}' for '${key}':`, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset state key to default value
|
||||
* @param {string} key - State key to reset
|
||||
* @returns {boolean} Success status
|
||||
*/
|
||||
reset(key) {
|
||||
if (!this.state.has(key)) {
|
||||
Logger.warn(`[StateManager] Cannot reset unknown state key: '${key}'`);
|
||||
return false;
|
||||
}
|
||||
|
||||
const defaultValue = this.defaultValues.get(key);
|
||||
return this.set(key, defaultValue, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all subscriptions for a module (useful for cleanup)
|
||||
* @param {string} moduleName - Module name
|
||||
*/
|
||||
clearModuleSubscriptions(moduleName) {
|
||||
let cleared = 0;
|
||||
|
||||
for (const [key, subscriptions] of this.subscribers.entries()) {
|
||||
const filtered = subscriptions.filter(sub => sub.subscriber !== moduleName);
|
||||
cleared += subscriptions.length - filtered.length;
|
||||
this.subscribers.set(key, filtered);
|
||||
}
|
||||
|
||||
if (cleared > 0) {
|
||||
Logger.info(`[StateManager] Cleared ${cleared} subscriptions for module '${moduleName}'`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current state snapshot (for debugging)
|
||||
* @returns {Object} Current state and metadata
|
||||
*/
|
||||
getSnapshot() {
|
||||
const snapshot = {
|
||||
state: Object.fromEntries(this.state),
|
||||
owners: Object.fromEntries(this.stateOwners),
|
||||
subscriptions: {}
|
||||
};
|
||||
|
||||
for (const [key, subs] of this.subscribers.entries()) {
|
||||
snapshot.subscriptions[key] = subs.map(sub => ({
|
||||
id: sub.id,
|
||||
subscriber: sub.subscriber
|
||||
}));
|
||||
}
|
||||
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset entire state manager (useful for testing)
|
||||
*/
|
||||
resetAll() {
|
||||
this.state.clear();
|
||||
this.subscribers.clear();
|
||||
this.stateOwners.clear();
|
||||
this.defaultValues.clear();
|
||||
this.subscriptionCounter = 0;
|
||||
Logger.info('[StateManager] Reset complete');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a scoped state manager for a specific module
|
||||
* @param {string} moduleName - Module name
|
||||
* @returns {Object} Scoped state interface
|
||||
*/
|
||||
createScope(moduleName) {
|
||||
return {
|
||||
register: (key, defaultValue) => {
|
||||
this.setContext(moduleName);
|
||||
return this.register(key, defaultValue, moduleName);
|
||||
},
|
||||
|
||||
get: (key) => this.get(key),
|
||||
|
||||
set: (key, value) => {
|
||||
this.setContext(moduleName);
|
||||
return this.set(key, value);
|
||||
},
|
||||
|
||||
subscribe: (key, callback) => {
|
||||
this.setContext(moduleName);
|
||||
return this.subscribe(key, callback, moduleName);
|
||||
},
|
||||
|
||||
unsubscribe: (subscriptionId) => this.unsubscribe(subscriptionId),
|
||||
|
||||
reset: (key) => this.reset(key),
|
||||
|
||||
cleanup: () => this.clearModuleSubscriptions(moduleName)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Global instance
|
||||
export const stateManager = new StateManager();
|
||||
|
||||
// Debug access
|
||||
if (typeof window !== 'undefined') {
|
||||
window.stateManager = stateManager;
|
||||
window.stateSnapshot = () => stateManager.getSnapshot();
|
||||
}
|
||||
Reference in New Issue
Block a user