import { unionBy, filter, isNumber, set, cloneDeep} from 'lodash'; import { ItemSelector, EntityItemCommon } from './interfaces'; // IDEA@Taemin: involvedItem에 대한 operation method는 한 번 결정되면 바뀌지 않으므로 // involveditem과 operation을 한데 묶었다. export default class Operation { method: 'ADD' | 'UPDATE' | 'DELETE'; itemSelector: ItemSelector; requireRead: boolean; before: any; partialAfter: any; after: any; resolved: boolean; onResolved: (operation: Operation) => void; onSetBefore: (operation: Operation) => void; constructor(method: 'ADD'|'UPDATE'|'DELETE', itemSelector: ItemSelector, partialItem: Object = {}) { this.method = method; this.itemSelector = itemSelector; this.resolved = false; // ---------- ADD ---------- if (method === 'ADD') { this.requireRead = false; this.before = {}; this.partialAfter = partialItem; this.after = partialItem; // ---------- UPDATE ---------- } else if (method === 'UPDATE') { this.requireRead = true; this.before = {}; this.partialAfter = partialItem; this.after = partialItem; // ---------- DELETE ---------- } else if (method === 'DELETE') { this.requireRead = true; this.before = {}; this.partialAfter = {}; this.after = {}; } else { throw Error(`Can't handle method, ${method}`); } } connectToOperationManager( onResolved: (operation: Operation) => void, onSetBefore: (operation: Operation) => void ) { this.onResolved = onResolved; this.onSetBefore = onSetBefore; } // set this.before after read setBefore(beforeItem: Object) { this.before = beforeItem; this.updateAfter(); this.requireRead = false; if (this.onSetBefore) this.onSetBefore(this); } markAsResolved() { this.resolved = true; if (this.onResolved) this.onResolved(this); } updateAfter() { if (this.method !== 'DELETE') { const newAfter = cloneDeep(this.before); Object.keys(this.partialAfter).forEach(key => { set(newAfter, key, this.partialAfter[key]) }); this.after = newAfter; } } throwErrorIfRequireRead(errorMessage: string) { if (this.requireRead) { throw Error(errorMessage); } } modify(partialItem: Object) { // this.throwErrorIfRequireRead("It's danger to modify operation before READ."); this.partialAfter = { ...this.partialAfter, ...partialItem }; this.updateAfter(); } addNumToProp(propKey: string, delta: number) { this.throwErrorIfRequireRead("It's danger to modify operation before READ."); const deltaNum = +delta; // type-casting for safety let beforeNum = +this.after[propKey]; if (!isNumber(beforeNum) || isNaN(beforeNum)) beforeNum = 0; this.partialAfter = { ...this.partialAfter, [propKey]: beforeNum + deltaNum }; this.updateAfter(); } // newItem에 id가 없을 수도 있으므로 강제로 id를 넣어주기 위해 itemSelector를 받아낸다. setItemToProp(propKey: string, newItem: Object, newItemSelector:ItemSelector, limit: number) { this.throwErrorIfRequireRead("It's danger to modify operation before READ."); const newItemWithId = { ...newItem, id: newItemSelector.id }; const itemList: Array = this.partialAfter[propKey] || this.before[propKey] || []; const newItemList = unionBy([newItemWithId], itemList, 'id'); if (limit > 0 && newItemList.length > limit) { newItemList.pop(); } this.partialAfter = { ...this.partialAfter, [propKey]: newItemList }; this.updateAfter(); } delItemFromProp(propKey: string, targetItemSelector:ItemSelector) { this.throwErrorIfRequireRead("It's danger to modify operation before READ."); const itemList: Array = this.partialAfter[propKey] || this.before[propKey] || []; const newItemList = filter(itemList, item => item.id !== targetItemSelector.id); this.partialAfter = { ...this.partialAfter, [propKey]: newItemList }; this.updateAfter(); } }