@hadyfayed/filament-react-wrapper
Version:
Enterprise React integration for Laravel/Filament - Smart asset loading, 90%+ React-PHP function mapping, no-plugin Filament integration
269 lines (224 loc) • 7.96 kB
text/typescript
/**
* State Manager Service - implements IStateManager interface
* Following Liskov Substitution Principle
*/
import {
IStateManager,
IStateManagerState,
IStateValidator,
IStatePersistence,
} from '../interfaces/IStateManager';
export abstract class BaseStateManager implements IStateManager {
protected state: IStateManagerState = {};
protected subscribers: Map<string, Set<(value: unknown) => void>> = new Map();
abstract setState(path: string, value: unknown): void;
abstract updateState(path: string, updater: (current: unknown) => unknown): void;
abstract getState(path: string): unknown;
abstract resetState(newState?: IStateManagerState): void;
abstract batchUpdate(updates: Array<{ path: string; value: unknown }>): void;
subscribe(path: string, callback: (value: unknown) => void): () => void {
if (!this.subscribers.has(path)) {
this.subscribers.set(path, new Set());
}
const subscribers = this.subscribers.get(path)!;
subscribers.add(callback);
// Immediately notify with current value
try {
const currentValue = this.getNestedValue(this.state, path);
callback(currentValue);
} catch (error) {
console.error(`Error in immediate callback for path ${path}:`, error);
}
// Return unsubscribe function
return () => {
const pathSubscribers = this.subscribers.get(path);
if (pathSubscribers) {
pathSubscribers.delete(callback);
if (pathSubscribers.size === 0) {
this.subscribers.delete(path);
}
}
};
}
protected getNestedValue<T = unknown>(obj: Record<string, unknown>, path: string): T | undefined {
if (!path) return obj as T;
if (!obj || typeof obj !== 'object') return undefined;
return path
.split('.')
.reduce(
(current: unknown, key: string) =>
current && typeof current === 'object'
? (current as Record<string, unknown>)[key]
: undefined,
obj as unknown
) as T;
}
protected setNestedValue(
obj: Record<string, unknown>,
path: string,
value: unknown
): Record<string, unknown> {
if (!path) return value as Record<string, unknown>;
const keys = path.split('.');
if (keys.length === 0) return obj;
const result = { ...obj } as Record<string, unknown>;
let current = result;
for (let i = 0; i < keys.length - 1; i += 1) {
const key = keys[i];
if (key === undefined || key === '') continue;
const shouldInitialize =
!(key in current) || current[key] === null || typeof current[key] !== 'object';
current[key] = shouldInitialize ? {} : { ...(current[key] as Record<string, unknown>) };
current = current[key] as Record<string, unknown>;
}
const lastKey = keys[keys.length - 1];
if (lastKey !== undefined && lastKey !== '') {
current[lastKey] = value;
}
return result;
}
protected notifySubscribers(path: string, value: any): void {
// Notify exact path subscribers
const exactSubscribers = this.subscribers.get(path);
if (exactSubscribers) {
exactSubscribers.forEach(callback => {
try {
callback(value);
} catch (error) {
console.error(`Error in subscriber callback for path ${path}:`, error);
}
});
}
// Notify parent path subscribers
const pathParts = path.split('.');
for (let i = pathParts.length - 1; i > 0; i -= 1) {
const parentPath = pathParts.slice(0, i).join('.');
const parentSubscribers = this.subscribers.get(parentPath);
if (parentSubscribers) {
const parentValue = this.getNestedValue(this.state, parentPath);
parentSubscribers.forEach(callback => {
try {
callback(parentValue);
} catch (error) {
console.error(`Error in subscriber callback for parent path ${parentPath}:`, error);
}
});
}
}
}
}
export class StandardStateManager extends BaseStateManager {
setState(path: string, value: any): void {
if (!path) return;
this.state = this.setNestedValue(this.state, path, value);
this.notifySubscribers(path, value);
}
updateState(path: string, updater: (current: any) => any): void {
if (!path || typeof updater !== 'function') return;
const currentValue = this.getNestedValue(this.state, path);
const newValue = updater(currentValue);
this.setState(path, newValue);
}
getState(path: string): any {
return this.getNestedValue(this.state, path);
}
resetState(newState: IStateManagerState = {}): void {
this.state = { ...newState };
// Notify all subscribers of reset
this.subscribers.forEach((callbacks, path) => {
const value = this.getNestedValue(this.state, path);
callbacks.forEach(callback => {
try {
callback(value);
} catch (error) {
console.error(`Error in reset callback for path ${path}:`, error);
}
});
});
}
batchUpdate(updates: Array<{ path: string; value: any }>): void {
if (!Array.isArray(updates) || updates.length === 0) return;
let newState = { ...this.state };
const notificationPaths = new Set<string>();
updates.forEach(({ path, value }) => {
if (path) {
newState = this.setNestedValue(newState, path, value);
notificationPaths.add(path);
}
});
this.state = newState;
// Notify all affected paths
notificationPaths.forEach(path => {
const value = this.getNestedValue(this.state, path);
this.notifySubscribers(path, value);
});
}
}
export class ValidatedStateManager extends StandardStateManager {
constructor(private validator?: IStateValidator) {
super();
}
setState(path: string, value: any): void {
if (this.validator && !this.validator.validate(path, value)) {
const errors = this.validator.getValidationErrors(path, value);
console.error(`Validation failed for path ${path}:`, errors);
return;
}
super.setState(path, value);
}
updateState(path: string, updater: (current: any) => any): void {
const currentValue = this.getNestedValue(this.state, path);
const newValue = updater(currentValue);
if (this.validator && !this.validator.validate(path, newValue)) {
const errors = this.validator.getValidationErrors(path, newValue);
console.error(`Validation failed for path ${path}:`, errors);
return;
}
super.setState(path, newValue);
}
}
export class PersistentStateManager extends StandardStateManager {
constructor(
private persistence: IStatePersistence,
private persistenceKey: string = 'app-state'
) {
super();
this.loadFromPersistence();
}
async setState(path: string, value: any): Promise<void> {
super.setState(path, value);
await this.saveToPersistence();
}
async resetState(newState: IStateManagerState = {}): Promise<void> {
super.resetState(newState);
await this.saveToPersistence();
}
async batchUpdate(updates: Array<{ path: string; value: any }>): Promise<void> {
super.batchUpdate(updates);
await this.saveToPersistence();
}
private async loadFromPersistence(): Promise<void> {
try {
const persistedState = await this.persistence.load(this.persistenceKey);
if (persistedState) {
this.state = persistedState;
}
} catch (error) {
console.error('Error loading persisted state:', error);
}
}
private async saveToPersistence(): Promise<void> {
try {
await this.persistence.save(this.persistenceKey, this.state);
} catch (error) {
console.error('Error saving state to persistence:', error);
}
}
async clearPersistence(): Promise<void> {
try {
await this.persistence.remove(this.persistenceKey);
} catch (error) {
console.error('Error clearing persisted state:', error);
}
}
}