export interface MagicController { get(key: string): Promise; set(key: string, val: any): Promise; } export interface MagicConfig { modules: { [key: string]: { data: { [key: string]: any }; controller: MagicController; }; }; spinnerTimeout?: number; pendingOnPush?: boolean; } export class MagicValue { public pending: boolean; public spinning: boolean; public error: any; private runningTimeout: number; constructor( private config: MagicConfig, private controller: MagicController, private keyName: string, public val?: any ) {} startTask() { this.pending = true; this.runningTimeout = setTimeout( () => (this.spinning = true), this.config.spinnerTimeout || 2000 ); } finishTask() { if (this.runningTimeout) clearTimeout(this.runningTimeout); this.pending = false; this.spinning = false; } pull() { this.startTask(); return this.controller .get(this.keyName) .then(fetchedVal => { this.val = fetchedVal; this.finishTask(); return fetchedVal; }) .catch(err => { console.error(err); this.error = err; this.finishTask(); }); } push() { if (this.config.pendingOnPush !== false) this.startTask(); return this.controller .set(this.keyName, this.val) .then(() => this.finishTask()) .catch(err => { console.error(err); this.error = err; this.finishTask; }); } } export interface MagicModule { [dataItemName: string]: MagicValue; } export class Magic { [moduleName: string]: MagicModule; constructor(config: MagicConfig) { let modulesConfig = config.modules || {}; for (let moduleName of Object.keys(modulesConfig)) { let module = modulesConfig[moduleName]; if (!module.data) { throw new Error(`Module "${moduleName}" has no configured data`); } if (!module.controller) { throw new Error(`Module "${moduleName}" has no configurred controller`); } this[moduleName] = {}; for (let key of Object.keys(module.data)) { this[moduleName][key] = new MagicValue( config, module.controller, key, module.data[key] ); this[moduleName][key].pull(); } } } }