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
JavaScript
;
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;