UNPKG

@aitianyu.cn/tianyu-store

Version:
432 lines 19.2 kB
"use strict"; /** @format */ Object.defineProperty(exports, "__esModule", { value: true }); exports.StoreImpl = void 0; const types_1 = require("@aitianyu.cn/types"); const Message_1 = require("../../infra/Message"); const Defs_1 = require("../../types/Defs"); const Model_1 = require("../../types/Model"); const Transaction_1 = require("../../types/Transaction"); const InterfaceUtils_1 = require("../../utils/InterfaceUtils"); const RedoUndoFactor_1 = require("../RedoUndoFactor"); const SystemActionFactor_1 = require("../SystemActionFactor"); const Transaction_2 = require("../modules/Transaction"); const Dispatching_1 = require("../processing/Dispatching"); const Selecting_1 = require("../processing/Selecting"); const StoreState_1 = require("../storage/interface/StoreState"); const InvalidExternalRegisterImpl_1 = require("./InvalidExternalRegisterImpl"); const StoreInstanceImpl_1 = require("./StoreInstanceImpl"); const DevToolsHelper_1 = require("../../develop/DevToolsHelper"); const ObjectUtils_1 = require("../../utils/ObjectUtils"); class StoreImpl { storeId; config; transaction; onSelector; onDispatch; onError; onChangeApplied; // private hierarchyChecker: StoreInstanceChecker; operationList; storyTypes; entityMap; instanceSubscribe; instanceListener; dispatchPromise; constructor(config) { this.storeId = (0, types_1.guid)(); this.config = config; this.transaction = new Transaction_2.TransactionImpl(this.storeId); this.onSelector = undefined; this.onDispatch = undefined; this.onError = undefined; this.onChangeApplied = undefined; // this.hierarchyChecker = new StoreInstanceChecker(); this.operationList = {}; this.storyTypes = []; this.entityMap = new Map(); this.instanceSubscribe = new Map(); this.instanceListener = new Map(); this.dispatchPromise = Promise.resolve(); // to register basic actions this.registerInterfaceInternal(Defs_1.TIANYU_STORE_INSTANCE_BASE_ENTITY_STORE_TYPE, RedoUndoFactor_1.TianyuStoreRedoUndoInterface); this.registerInterfaceInternal(Defs_1.TIANYU_STORE_INSTANCE_BASE_ENTITY_STORE_TYPE, SystemActionFactor_1.TianyuStoreEntityInterface); (0, DevToolsHelper_1.registerStore)(this); } get id() { return this.storeId; } get name() { return this.config.friendlyName || this.storeId; } // ============================================================================== // IStore Develop Tools Impl // ============================================================================== setOnSelector(callback) { this.onSelector = callback; } setOnDispatch(callback) { this.onDispatch = callback; } setOnError(callback) { this.onError = callback; } setOnChangeApplied(callback) { this.onChangeApplied = callback; } getState(instanceId) { if (!instanceId) { const states = {}; for (const entity of this.entityMap) { states[entity[0]] = entity[1].getRawState(); } return states; } return {}; } getAllDispatchs() { return this.transaction.getDispatched(); } getAllSelectors() { return this.transaction.getSelections(); } getAllErrors() { return this.transaction.getErrors(); } // ============================================================================== // IStore Manager Impl // ============================================================================== getAction(id, template, instanceId) { const action = this.getInterfaceInternal(id, template, instanceId); if (!action?.actionId) { throw new Error(Message_1.MessageBundle.getText("STORE_ACTION_NOT_FOUND", id)); } return action; } getSelector(id, template, instanceId) { const selector = this.getInterfaceInternal(id, template, instanceId); if (!selector?.selector) { throw new Error(Message_1.MessageBundle.getText("STORE_SELECTOR_NOT_FOUND", id)); } return selector; } createEntity(instanceId, state) { if (this.entityMap.has(instanceId.entity)) { throw new Error(Message_1.MessageBundle.getText("STORE_CREATE_ENTITY_DUP", instanceId.entity, instanceId.id)); } // before setting, to add all the story types for (const type of this.storyTypes) { state[StoreState_1.STORE_STATE_INSTANCE][type] = {}; } this.entityMap.set(instanceId.entity, new StoreInstanceImpl_1.StoreInstanceImpl(state, instanceId)); this.instanceListener.set(instanceId.entity, {}); this.instanceSubscribe.set(instanceId.entity, {}); } destroyEntity(instanceId) { this.entityMap.delete(instanceId.entity); this.instanceListener.delete(instanceId.entity); this.instanceSubscribe.delete(instanceId.entity); } getEntity(entity) { return this.entityMap.get(entity) || this; } error(msg, type) { const errorRec = this.transaction.error(msg, type); this.onError?.(errorRec); } select(selector) { const selectorRec = this.transaction.selected(selector); this.onSelector?.(selectorRec); } // ============================================================================== // IStore Public Impl // ============================================================================== applyHierarchyChecklist(checklist) { // this.hierarchyChecker.apply(checklist || {}); } registerInterface(interfaceMapOrStoreType, interfaceDefine) { const interfaceMap = { ...(typeof interfaceMapOrStoreType === "string" ? { [interfaceMapOrStoreType]: interfaceDefine } : interfaceMapOrStoreType), }; const storeTypes = Object.keys(interfaceMap); for (const types of storeTypes) { if (types === Defs_1.TIANYU_STORE_INSTANCE_BASE_ENTITY_STORE_TYPE) { // the registered operations could not be in tianyu-store-entity // due to this is the system using throw new Error(Message_1.MessageBundle.getText("STORE_SHOULD_NOT_REGISTER_SYSTEM_ENTITY")); } const interfaceOfStore = interfaceMap[types]; if (!interfaceOfStore) { continue; } this.registerInterfaceInternal(types, interfaceOfStore); if (!this.storyTypes.includes(types)) { // if the interfaces are not added before, this.storyTypes.push(types); this.entityMap.forEach((value) => { value.addStoreType(types); }); } } } startListen(listener) { const entityId = listener.selector.instanceId.entity; const entityListeners = this.instanceListener.get(entityId); if (!entityListeners) { throw new Error(Message_1.MessageBundle.getText("STORE_ENTITY_NOT_EXIST", entityId)); } const instanceId = listener.selector.instanceId.toString(); const listeners = entityListeners[instanceId] || []; if (!entityListeners[instanceId]) { entityListeners[instanceId] = listeners; } listeners.push(listener); } stopListen(listener) { const entityId = listener.selector.instanceId.entity; const entityListeners = this.instanceListener.get(entityId); if (!entityListeners) { return; } const instanceId = listener.selector.instanceId.toString(); const listeners = entityListeners[instanceId] || /* istanbul ignore next */ []; const listenerIndex = listeners.findIndex((value) => { return value.id === listener.id; }); if (-1 !== listenerIndex) { listeners.splice(listenerIndex, 1); entityListeners[instanceId] = listeners; } } subscribe(selector, eventTrigger) { const entityId = selector.instanceId.entity; const entityListeners = this.instanceSubscribe.get(entityId); if (!entityListeners) { throw new Error(Message_1.MessageBundle.getText("STORE_ENTITY_NOT_EXIST", entityId)); } const subscribeInstance = { id: (0, types_1.guid)(), selector: selector, trigger: eventTrigger, }; const instanceId2String = selector.instanceId.toString(); const subscribes = entityListeners[instanceId2String] || []; if (!entityListeners[instanceId2String]) { entityListeners[instanceId2String] = subscribes; } subscribes.push(subscribeInstance); const unSub = () => { const entityListeners = this.instanceSubscribe.get(entityId); // this 'if' should not accessed /* istanbul ignore if */ if (!entityListeners) { return; } const subscribes = entityListeners[instanceId2String] || /* istanbul ignore next */ []; const subscribeIndex = subscribes.findIndex((value) => { return value.id === subscribeInstance.id; }); if (-1 !== subscribeIndex) { subscribes.splice(subscribeIndex, 1); entityListeners[instanceId2String] = subscribes; } }; return unSub; } selecte(selector) { const entityId = selector.instanceId.entity; const entity = this.entityMap.get(entityId); if (!entity) { this.error(Message_1.MessageBundle.getText("STORE_ENTITY_NOT_EXIST", entityId), Transaction_1.TransactionType.Selector); return new Model_1.Missing(); } return (0, Selecting_1.doSelecting)(this, selector, true); } selecteWithThrow(selector) { const entityId = selector.instanceId.entity; const entity = this.entityMap.get(entityId); if (!entity) { const errorMsg = Message_1.MessageBundle.getText("STORE_ENTITY_NOT_EXIST", entityId); this.error(errorMsg, Transaction_1.TransactionType.Selector); throw new Error(errorMsg); } return (0, Selecting_1.doSelectingWithThrow)(this, selector, true); } dispatch(action) { const actions = Array.isArray(action.actions) ? action.actions : [action]; return this.dispatchInternal(actions, false); } dispatchForView(action) { const actions = Array.isArray(action.actions) ? action.actions : [action]; void this.dispatchInternal(actions, true); } destroy() { (0, DevToolsHelper_1.unregisterStore)(this); this.instanceListener.clear(); this.instanceSubscribe.clear(); this.entityMap.clear(); } // ============================================================================== // Store Internal Impl // ============================================================================== async dispatchInternal(action, notRedoUndo) { if (action.length === 0) { return; } const entity = action[0].instanceId.entity; return new Promise((resolve) => { this.dispatchPromise = this.dispatchPromise.finally(async () => { let resolved = false; const executor = this.getEntity(entity); try { const actions = await (0, Dispatching_1.dispatching)(executor, this, action, notRedoUndo); // transaction this.doneDispatch(actions); // apply changes const diff = executor.applyChanges(); if (!this.config.waitForAll) { resolved = true; resolve(); } // fire events await this.changeApply(entity, executor, diff); if (this.config.waitForAll && !resolved) { resolved = true; resolve(); } } catch (e) { // records error this.error(e, Transaction_1.TransactionType.Action); // if action run failed, to discard all changes of current action batch executor.discardChanges(); !resolved && resolve(); resolved = true; } }); }); } registerInterfaceInternal(storeType, interfaceDefine) { const interfaceOperationsList = (0, InterfaceUtils_1.registerInterface)(interfaceDefine, storeType); this.operationList = { ...this.operationList, ...interfaceOperationsList, }; } async fireListeners(entity, changes, executor) { const entityListeners = this.instanceListener.get(entity); if (!entityListeners) { return; } for (const storeType of Object.keys(changes)) { const instances = changes[storeType]; for (const instanceId of Object.keys(instances)) { const changeItem = instances[instanceId]; const listeners = entityListeners[instanceId] || []; listeners.forEach((listener) => { try { const oldState = changeItem.old && (0, Selecting_1.doSelectingWithState)(changeItem.old, executor, this, listener.selector); const newState = changeItem.new && (0, Selecting_1.doSelectingWithState)(changeItem.new, executor, this, listener.selector); const oldNoMissing = oldState instanceof Model_1.Missing ? undefined : oldState; const newNoMissing = newState instanceof Model_1.Missing ? undefined : newState; const isChanged = types_1.ObjectHelper.compareObjects(oldState, newState) === "different"; isChanged && listener.listener(oldNoMissing, newNoMissing); } catch (e) { this.error(Message_1.MessageBundle.getText("STORE_EVENT_LISTENER_TRIGGER_FAILED", typeof e === "string" ? e : e instanceof Error ? e.message : Message_1.MessageBundle.getText("TRANSACTION_ERROR_RECORDING_UNKNOWN_ERROR", (0, Transaction_2.formatTransactionType)(Transaction_1.TransactionType.Listener)), listener.id, listener.selector.selector), Transaction_1.TransactionType.Listener); } }); } } } async fireSubscribes(entity, changes, executor) { const entityListeners = this.instanceSubscribe.get(entity); if (!entityListeners) { return; } for (const storeType of Object.keys(changes)) { const instances = changes[storeType]; for (const instanceId of Object.keys(instances)) { const changeItem = instances[instanceId]; const listeners = entityListeners[instanceId] || []; listeners.forEach((listener) => { try { const oldState = (0, Selecting_1.doSelectingWithState)(changeItem.old, executor, this, listener.selector); const newState = (0, Selecting_1.doSelectingWithState)(changeItem.new, executor, this, listener.selector); const oldNoMissing = oldState instanceof Model_1.Missing ? undefined : oldState; const newNoMissing = newState instanceof Model_1.Missing ? undefined : newState; const isChanged = types_1.ObjectHelper.compareObjects(oldState, newState) === "different"; isChanged && listener.trigger(oldNoMissing, newNoMissing); } catch (e) { this.error(Message_1.MessageBundle.getText("STORE_EVENT_SUBSCRIBE_TRIGGER_FAILED", typeof e === "string" ? e : e instanceof Error ? e.message : Message_1.MessageBundle.getText("TRANSACTION_ERROR_RECORDING_UNKNOWN_ERROR", (0, Transaction_2.formatTransactionType)(Transaction_1.TransactionType.Subscribe)), listener.id, listener.selector.selector), Transaction_1.TransactionType.Subscribe); } }); } } } async changeApply(entity, executor, changes) { this.onChangeApplied?.(changes); if (!(0, ObjectUtils_1.isChangesEmpty)(changes)) { await this.fireListeners(entity, changes, executor); await this.fireSubscribes(entity, changes, executor); } } doneDispatch(actions) { const dispatchRec = this.transaction.dispatched(actions); this.onDispatch?.(dispatchRec); } getInterfaceInternal(id, template, instanceId) { if (!template) { return this.operationList[id]; } const instancePair = instanceId.structure(); for (let index = instancePair.length - 1; index >= 0; index--) { const storeType = instancePair[index].storeType; const operatorId = `${storeType}.${id}`; const operator = this.operationList[operatorId]; if (operator) { return operator; } } return undefined; } // ================================================================================================================ // unused methods - IStore Exectuor Impl // ================================================================================================================ getExternalRegister(_instanceId) { return InvalidExternalRegisterImpl_1.InvalidExternalRegister; } getOriginState(_instanceId) { return {}; } getRecentChanges() { return {}; } getHistories() { return { histroy: [], index: -1 }; } applyChanges() { return {}; } discardChanges() { } pushStateChange(_storeType, _instanceId, _actionType, _newState, _notRedoUndo) { } pushDiffChange(_diff) { } validateActionInstance(_action) { } } exports.StoreImpl = StoreImpl; //# sourceMappingURL=StoreImpl.js.map