UNPKG

chrono-forge

Version:

A comprehensive framework for building resilient Temporal workflows, advanced state management, and real-time streaming activities in TypeScript. Designed for a seamless developer experience with powerful abstractions, dynamic orchestration, and full cont

368 lines (367 loc) 17.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.EntityProxyManager = void 0; const proxy_state_tree_1 = require("proxy-state-tree"); const SchemaManager_1 = require("./SchemaManager"); const utils_1 = require("../utils"); const actions_1 = require("./actions"); class EntityProxyManager { static proxyStateTree; static entityCache = new Map(); static entityStateManagers = new Map(); static initialize() { if (!this.proxyStateTree) { this.proxyStateTree = new proxy_state_tree_1.ProxyStateTree({}); } } static createEntityProxy(entityName, entityId, data, stateManager) { const cacheKey = `${entityName}::${entityId}`; if (this.entityCache.has(cacheKey)) { return this.entityCache.get(cacheKey); } this.entityStateManagers.set(cacheKey, stateManager); const mutationTree = this.proxyStateTree.getMutationTree(); if (!mutationTree.state[entityName]) { mutationTree.state[entityName] = {}; } mutationTree.state[entityName][entityId] = data; mutationTree.onMutation((mutation) => { if (mutation.path.startsWith(`${entityName}.${entityId}`)) { this.handleEntityMutation(entityName, entityId, mutation); } }); const entityProxy = mutationTree.state[entityName][entityId]; this.entityCache.set(cacheKey, entityProxy); return entityProxy; } static handleEntityMutation(entityName, entityId, mutation) { const cacheKey = `${entityName}::${entityId}`; const stateManager = this.entityStateManagers.get(cacheKey); if (!stateManager) { return; } const pathParts = mutation.path.split('.'); if (pathParts.length < 3) { return; } const mutationTree = this.proxyStateTree.getMutationTree(); const topLevelField = pathParts[2]; const currentValue = mutationTree.state[entityName][entityId][topLevelField]; const relationships = SchemaManager_1.SchemaManager.relationshipMap[entityName]; const relation = relationships?.[topLevelField]; if (pathParts.length === 3) { this.handleRelationshipMutation(entityName, entityId, topLevelField, currentValue, stateManager); } else if (relation) { this.processNestedMutation(entityName, entityId, topLevelField, pathParts.slice(3), currentValue, stateManager, relation, mutation); } else { this.updateEntityField(entityName, entityId, topLevelField, currentValue, stateManager); } this.proxyStateTree.flush([mutationTree]); } static processNestedMutation(entityName, entityId, fieldName, nestedPath, currentValue, stateManager, relation, mutation) { const relatedEntityName = (0, utils_1.getEntityName)(relation); const idAttribute = SchemaManager_1.SchemaManager.schemas[relatedEntityName].idAttribute; if (this.isArrayMethodMutation(mutation) && nestedPath.length === 0) { this.updateEntityField(entityName, entityId, fieldName, currentValue, stateManager); return; } if (relation.isMany && Array.isArray(currentValue)) { const arrayIndex = Number(nestedPath[0]); if (!isNaN(arrayIndex) && arrayIndex < currentValue.length) { const item = currentValue[arrayIndex]; let relatedEntityId; if (typeof item === 'string' || typeof item === 'number') { relatedEntityId = item.toString(); } else if (typeof item === 'object' && item !== null) { relatedEntityId = typeof idAttribute === 'function' ? idAttribute(item) : item[idAttribute]; } if (relatedEntityId) { if (stateManager.state[relatedEntityName]?.[relatedEntityId]) { this.updateNestedEntity(relatedEntityName, relatedEntityId, nestedPath.slice(1), stateManager, mutation); return; } } this.updateEntityField(entityName, entityId, fieldName, currentValue, stateManager); return; } } else if (!relation.isMany && currentValue && typeof currentValue === 'object') { const relatedEntityId = typeof idAttribute === 'function' ? idAttribute(currentValue) : currentValue[idAttribute]; if (relatedEntityId && stateManager.state[relatedEntityName]?.[relatedEntityId]) { this.updateNestedEntity(relatedEntityName, relatedEntityId, nestedPath, stateManager, mutation); return; } } this.updateEntityField(entityName, entityId, fieldName, currentValue, stateManager); } static updateNestedEntity(entityName, entityId, nestedPath, stateManager, mutation) { const entity = stateManager.state[entityName]?.[entityId]; if (!entity) { return; } const updatedEntity = { ...entity }; const idAttribute = SchemaManager_1.SchemaManager.schemas[entityName].idAttribute; if (nestedPath.length === 0) { stateManager.dispatch([(0, actions_1.updateEntity)(updatedEntity, entityName)], false, stateManager.instanceId); return; } const fieldName = nestedPath[0]; if (!(fieldName in entity)) { return; } if (nestedPath.length === 1) { let newValue; let strategy = '$merge'; if (this.isArrayMethodMutation(mutation)) { const mutationTree = this.proxyStateTree.getMutationTree(); if (mutationTree.state[entityName]?.[entityId]?.[fieldName]) { const currentArray = mutationTree.state[entityName][entityId][fieldName]; newValue = JSON.parse(JSON.stringify(currentArray)); strategy = '$set'; } else if (mutation.method === 'push' && Array.isArray(entity[fieldName])) { newValue = [...entity[fieldName], ...mutation.args]; strategy = '$set'; } else { newValue = mutation.args[0]; strategy = '$set'; } } else { newValue = mutation.args[0]; if (Array.isArray(newValue) || newValue === null || newValue === undefined) { strategy = '$set'; } } stateManager.dispatch([ (0, actions_1.updateEntityPartial)({ [typeof idAttribute === 'function' ? idAttribute(updatedEntity) : idAttribute]: entityId, [fieldName]: newValue }, entityName, strategy) ], false, stateManager.instanceId); return; } const relationships = SchemaManager_1.SchemaManager.relationshipMap[entityName]; const relation = relationships?.[fieldName]; if (relation) { const fieldValue = entity[fieldName]; this.processNestedMutation(entityName, entityId, fieldName, nestedPath.slice(1), fieldValue, stateManager, relation, mutation); } else { let currentValue = entity[fieldName]; if (currentValue === undefined || currentValue === null) { currentValue = nestedPath[1] === '0' || !isNaN(Number(nestedPath[1])) ? [] : {}; } const updatedValue = JSON.parse(JSON.stringify(currentValue)); let target = updatedValue; let validPath = true; for (let i = 1; i < nestedPath.length - 1; i++) { const part = nestedPath[i]; if (!isNaN(Number(part))) { if (!Array.isArray(target)) { target = []; } if (Number(part) >= target.length) { validPath = false; break; } } else if (target[part] === undefined || target[part] === null) { target[part] = nestedPath[i + 1] === '0' || !isNaN(Number(nestedPath[i + 1])) ? [] : {}; } target = target[part]; if (target === undefined || target === null) { validPath = false; break; } } if (!validPath) { return; } const lastPart = nestedPath[nestedPath.length - 1]; if (this.isArrayMethodMutation(mutation)) { if (mutation.method === 'push' && Array.isArray(target[lastPart])) { target[lastPart] = [...target[lastPart], ...mutation.args]; } else if (mutation.method === 'pop' && Array.isArray(target[lastPart])) { target[lastPart] = target[lastPart].slice(0, -1); } else if (mutation.method === 'shift' && Array.isArray(target[lastPart])) { target[lastPart] = target[lastPart].slice(1); } else if (mutation.method === 'unshift' && Array.isArray(target[lastPart])) { target[lastPart] = [...mutation.args, ...target[lastPart]]; } else if (mutation.method === 'splice' && Array.isArray(target[lastPart])) { const newArray = [...target[lastPart]]; newArray.splice(Number(mutation.args[0]), mutation.args.length > 1 ? Number(mutation.args[1]) : 0, ...mutation.args.slice(2)); target[lastPart] = newArray; } else { target[lastPart] = mutation.args[0]; } } else { target[lastPart] = mutation.args[0]; } const strategy = this.isArrayMethodMutation(mutation) || Array.isArray(target[lastPart]) ? '$set' : '$merge'; stateManager.dispatch([ (0, actions_1.updateEntityPartial)({ [typeof idAttribute === 'function' ? idAttribute({}) : idAttribute]: entityId, [fieldName]: updatedValue }, entityName, strategy) ], false, stateManager.instanceId); } } static handleRelationshipMutation(entityName, entityId, fieldName, newValue, stateManager) { const relationships = SchemaManager_1.SchemaManager.relationshipMap[entityName]; const relation = relationships?.[fieldName]; if (!relation) { this.updateEntityField(entityName, entityId, fieldName, newValue, stateManager); return; } const relatedEntityName = (0, utils_1.getEntityName)(relation); const idAttribute = SchemaManager_1.SchemaManager.schemas[relatedEntityName].idAttribute; let processedValue = newValue; let removedEntityIds = []; let addedEntities = []; const oldValue = stateManager.state[entityName]?.[entityId]?.[fieldName]; if (Array.isArray(newValue)) { if (Array.isArray(oldValue)) { const oldIds = oldValue.map((item) => { if (typeof item === 'string' || typeof item === 'number') { return item.toString(); } return typeof idAttribute === 'function' ? idAttribute(item) : item[idAttribute]; }); const newIds = []; addedEntities = newValue.filter((item) => { if (typeof item === 'string' || typeof item === 'number') { const id = item.toString(); newIds.push(id); return !oldIds.includes(id); } const id = typeof idAttribute === 'function' ? idAttribute(item) : item[idAttribute]; newIds.push(id); return !oldIds.includes(id) && typeof item === 'object'; }); removedEntityIds = oldIds.filter((id) => !newIds.includes(id)); } else { addedEntities = newValue.filter((item) => typeof item === 'object' && item !== null); } processedValue = newValue.map((item) => { if (typeof item === 'string' || typeof item === 'number') { return item.toString(); } return typeof idAttribute === 'function' ? idAttribute(item) : item[idAttribute]; }); } else if (newValue && typeof newValue === 'object') { if (oldValue && typeof oldValue !== 'undefined') { let oldId; if (typeof oldValue === 'string' || typeof oldValue === 'number') { oldId = oldValue.toString(); } else { oldId = typeof idAttribute === 'function' ? idAttribute(oldValue) : oldValue[idAttribute]; } const newId = typeof idAttribute === 'function' ? idAttribute(newValue) : newValue[idAttribute]; if (oldId && oldId !== newId) { removedEntityIds.push(oldId); addedEntities.push(newValue); } } else { addedEntities.push(newValue); } processedValue = typeof idAttribute === 'function' ? idAttribute(newValue) : newValue[idAttribute]; } else if (newValue === null || newValue === undefined) { if (oldValue && typeof oldValue !== 'undefined') { let oldId; if (typeof oldValue === 'string' || typeof oldValue === 'number') { oldId = oldValue.toString(); } else { oldId = typeof idAttribute === 'function' ? idAttribute(oldValue) : oldValue[idAttribute]; } if (oldId) { removedEntityIds.push(oldId); } } processedValue = null; } let strategy = '$merge'; if (Array.isArray(oldValue)) { strategy = Array.isArray(newValue) ? '$set' : '$push'; } else if (newValue === null || newValue === undefined) { strategy = '$set'; } const actions = []; if (addedEntities.length > 0 && relation) { const relatedEntityName = (0, utils_1.getEntityName)(relation); addedEntities.forEach((entity) => { if (entity && typeof entity === 'object') { const idAttribute = SchemaManager_1.SchemaManager.schemas[relatedEntityName].idAttribute; const entityId = typeof idAttribute === 'function' ? idAttribute(entity) : entity[idAttribute]; if (entityId) { actions.push((0, actions_1.updateEntity)(entity, relatedEntityName)); } } }); } actions.push((0, actions_1.updateEntityPartial)({ [typeof SchemaManager_1.SchemaManager.schemas[entityName].idAttribute === 'function' ? SchemaManager_1.SchemaManager.schemas[entityName].idAttribute({}) : SchemaManager_1.SchemaManager.schemas[entityName].idAttribute]: entityId, [fieldName]: relation ? processedValue : JSON.parse(JSON.stringify(processedValue)) }, entityName, strategy)); if (removedEntityIds.length > 0 && relation) { const removedEntityName = (0, utils_1.getEntityName)(relation); removedEntityIds.forEach((removedEntityId) => { if (!stateManager.isEntityReferenced(removedEntityName, removedEntityId, { entityName: entityName, fieldName: fieldName })) { actions.push((0, actions_1.deleteEntity)((0, utils_1.limitRecursion)(removedEntityId, removedEntityName, stateManager.state, stateManager, new Map([[`${entityName}::${entityId}`, 0]]), 1), removedEntityName)); } }); } stateManager.dispatch(actions, false, stateManager.instanceId); } static updateEntityField(entityName, entityId, fieldName, newValue, stateManager) { const idAttribute = SchemaManager_1.SchemaManager.schemas[entityName].idAttribute; let strategy = '$merge'; if (Array.isArray(newValue)) { strategy = '$set'; } else if (newValue === undefined) { strategy = '$unset'; } const action = (0, actions_1.updateEntityPartial)({ [typeof idAttribute === 'function' ? idAttribute({}) : idAttribute]: entityId, [fieldName]: JSON.parse(JSON.stringify(newValue)) }, entityName, strategy); stateManager.dispatch([action], false, stateManager.instanceId); } static isArrayMethodMutation(mutation) { return (!!mutation.method && ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'].includes(mutation.method)); } static clearCache() { this.entityCache.clear(); this.entityStateManagers.clear(); this.proxyStateTree = new proxy_state_tree_1.ProxyStateTree({}); } static removeFromCache(entityName, entityId) { const cacheKey = `${entityName}::${entityId}`; this.entityCache.delete(cacheKey); this.entityStateManagers.delete(cacheKey); } } exports.EntityProxyManager = EntityProxyManager;