import * as _ from 'lodash' import {diff} from 'deep-diff' import StoreManager from './StoreManager' import StoreWithMetadata from './StoreWithMetadata' export abstract class StoreMemberGenerator { protected field: any constructor(protected source: any, protected fieldName: string) { this.field = source[fieldName] } get storeMetadata(): StoreWithMetadata { return StoreManager.getStoreAndMetadataFor(this.source.constructor) } abstract isMatched(): boolean abstract run(): void } export class StateGenerator extends StoreMemberGenerator { isMatched(): boolean { return Object.keys(this.source).includes(this.fieldName) } run() {} } export class SyncActionGenerator extends StoreMemberGenerator { isMatched(): boolean { return this.field.constructor.name === 'Function' } run() { const action = this.field.bind(this.source) const self = this this.source[this.fieldName] = function(...args: any[]) { let oldState: any if (0 >= self.storeMetadata.actionCallDepth) { oldState = _.cloneDeep(self.storeMetadata.state) } self.storeMetadata.actionCallDepth += 1 let returnedValue try { returnedValue = action(...args) } finally { self.storeMetadata.actionCallDepth -= 1 } if (returnedValue !== undefined) { // When the method is just calculator, not action return returnedValue } if (0 >= self.storeMetadata.actionCallDepth) { console.debug( 'clax:', 'SyncActionInvoked:', `${self.source.constructor.name}#${self.fieldName}`, args ) const changes = diff(oldState!, self.storeMetadata.state) console.debug('clax:', 'StateChanged:', self.source.constructor.name, changes, StoreManager.state) self.storeMetadata.notifier.notify() } }.bind(this.source) } } export class AsyncActionGenerator extends StoreMemberGenerator { isMatched(): boolean { return this.field.constructor.name === 'AsyncFunction' } run() { const action = this.field.bind(this.source) const self = this this.source[this.fieldName] = function(...args: any[]) { console.debug( 'clax:', 'AsyncActionInvoked:', `${self.source.constructor.name}#${self.fieldName}`, args ) action(...args) }.bind(this.source) } } export class GetterGenerator extends StoreMemberGenerator { private readonly propertyDescriptor = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(this.source), this.fieldName)! isMatched(): boolean { return this.propertyDescriptor.value === undefined && this.propertyDescriptor.get !== undefined && this.propertyDescriptor.set === undefined } run() {} } export const checkOrder = [ StateGenerator, GetterGenerator, SyncActionGenerator, AsyncActionGenerator ]