@aitianyu.cn/tianyu-store
Version:
tianyu storage for nodejs.
432 lines • 19.2 kB
JavaScript
"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