import { combineReducers, AnyAction } from "redux"; import { concat, defaults } from "lodash"; import { Entities, EntitiesDefinition, EntityDefinition, BakedEntityHandler, AggregationCondition, ConditionFunc, Options, Required, ParentId, ParentIds, CreatedAt, UpdatedAt, Count, Sum, DuplicateChildren } from "interfaces"; import EntityManager from "./EntityManager"; import TransactionManager from "./TransactionManager"; import Operation from "./Operation"; /** * 예약어들 * - 다음 예약어들은 EntityType이 될 수 없다 : AFTER_SELF, BEFORE_SELF, SELF */ const defaultOptions: Options = { fetchItemTimeout: 5000 }; export class EntitiesManager { _firebase: any; _firestore: any; _getEntities: (state: Object) => Entities; _errorHandler: (error: Error) => any; _entityManagers: { [entityType: string]: EntityManager }; _entityReducers: { [entityType: string]: (state: any, action: any) => any }; _reservedTransactionManagerRuns: Array<() => void>; _options: Options = defaultOptions; constructor( getEntities: (state: any) => Entities, entitiesDefinition: EntitiesDefinition, errorHandler: (error: Error) => any, firestore: any, firebase: any, options: Options = defaultOptions ) { this._options = defaults(options, defaultOptions); this._firestore = firestore; this._firebase = firebase; this._getEntities = getEntities; this._errorHandler = errorHandler; this._entityManagers = {}; this._entityReducers = {}; this._reservedTransactionManagerRuns = []; this._initializeEntityManagers(entitiesDefinition); this._setChildEntityHandlers(); this._generateEntityReducers(); this.entity = this.entity.bind(this); } _inspect() { for (const entityType in this._entityManagers) { if (this._entityManagers[entityType]) { const entityManager = this._entityManagers[entityType]; entityManager._inspect(); } } } // generate this.entityManagers _initializeEntityManagers(entitiesDefinition: EntitiesDefinition) { // initialize this._entityManagers for (const entityType in entitiesDefinition) { if (entitiesDefinition[entityType]) { const entityDefinition: EntityDefinition = entitiesDefinition[entityType]; this._entityManagers[entityType] = new EntityManager( entityType, entityDefinition, this._getEntities, this._errorHandler, this._runTransactionManager.bind(this), this._firestore, this._options ); } } } _setChildEntityHandlers() { for (const entityType in this._entityManagers) { if (this._entityManagers[entityType]) { const entityManager = this._entityManagers[entityType]; const childHandlerSetters = entityManager._getChildHandlerSetters(); childHandlerSetters.forEach(handlerSetter => { this._entityManagers[handlerSetter.handlerHolder]._setChildHandler( handlerSetter ); }); } } } _generateEntityReducers() { for (const entityType in this._entityManagers) { if (this._entityManagers[entityType]) { this._entityReducers[entityType] = this._entityManagers[ entityType ]._generateReducer(); } } } _runTransactionManager( dispatch: any, getState: any, initialOperation: Operation, conditionFunc?: ConditionFunc ): Promise<"DONE"> { return new Promise((resolve, reject) => { const transactionManager = new TransactionManager( this._firestore, this._firebase, this._errorHandler, getState, dispatch, initialOperation, operationsNotResolved => { let bakedEntityHandlers: Array = []; operationsNotResolved.forEach(operation => { const { itemSelector } = operation; const { entityType } = itemSelector; bakedEntityHandlers = concat( bakedEntityHandlers, this._entityManagers[entityType]._getBakedEntityHandlers( operation ) ); }); return bakedEntityHandlers; } ); // run next reservedTransactionManagerRun if exists const runNextReservedTransactionManagerRun = () => { const nextReservedTransactionManagerRun = this ._reservedTransactionManagerRuns[0]; if (nextReservedTransactionManagerRun) nextReservedTransactionManagerRun(); }; const reservedTransactionManagerRun = () => { transactionManager .run(conditionFunc) .then(() => { this._reservedTransactionManagerRuns.shift(); runNextReservedTransactionManagerRun(); resolve("DONE"); }) .catch(error => reject(error)); }; const reservedLength = this._reservedTransactionManagerRuns.push( reservedTransactionManagerRun ); if (reservedLength === 1) runNextReservedTransactionManagerRun(); }); } // methods for consumer entity(entityType: string): EntityManager { return this._entityManagers[entityType]; } getEntitiesReducer() { return combineReducers(this._entityReducers); } // define props with static methods below static required(): Required { return { type: "REQUIRED", constraints: { requiredOnAdd: true, notNull: true } }; } static parentId(parentEntityType: string): ParentId { return { type: "PARENT_ID", parentEntityType, constraints: { unSettableOnUpdate: false, notNull: false } }; } static parentIds(parentEntityType: string): ParentIds { return { type: "PARENT_IDS", parentEntityType, constraints: { notNull: true } }; } static createdAt(): CreatedAt { return { type: "CREATED_AT", constraints: { unSettableOnAdd: true, unSettableOnUpdate: true, notNull: true } }; } static updatedAt(): UpdatedAt { return { type: "UPDATED_AT", constraints: { unSettableOnAdd: true, unSettableOnUpdate: true, notNull: true } }; } static count( childEntityType: string, conditions: Array = [] ): Count { return { type: "COUNT", childEntityType, conditions, constraints: { unSettableOnAdd: true, unSettableOnUpdate: true, notNull: true } }; } static sum( childEntityType: string, childPropKey: string, conditions: Array = [] ): Sum { return { type: "SUM", childEntityType, childPropKey, conditions, constraints: { unSettableOnAdd: true, unSettableOnUpdate: true, notNull: true } }; } static duplicateChildren( childEntityType: string, mapFunc: (entityItem: Object) => Object, limit: number, conditions: Array = [] ): DuplicateChildren { return { type: "DUPLICATE_CHILDREN", childEntityType, mapFunc, limit, // limit <= 0 : all of the children, limit > 0 : last (limit) items would be cached conditions, constraints: { unSettableOnAdd: true, unSettableOnUpdate: true, notNull: true } }; } }