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

230 lines (229 loc) 9.29 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.StateManager = void 0; const eventemitter3_1 = __importDefault(require("eventemitter3")); const entities_1 = require("./entities"); const actions_1 = require("./actions"); const deep_object_diff_1 = require("deep-object-diff"); const lodash_1 = require("lodash"); const utils_1 = require("../utils"); const mnemonist_1 = require("mnemonist"); const EntityProxyManager_1 = require("./EntityProxyManager"); const SchemaManager_1 = require("./SchemaManager"); const typescript_algos_1 = require("typescript-algos"); class StateManager extends eventemitter3_1.default { _instanceId; get instanceId() { return this._instanceId; } constructor(_instanceId) { super(); this._instanceId = _instanceId; this._instanceId = _instanceId; this._state = {}; if (!EntityProxyManager_1.EntityProxyManager['proxyStateTree']) { EntityProxyManager_1.EntityProxyManager.initialize(); } } static getInstance(instanceId) { if (!instanceId) { throw new Error(`You must provide a instanceId ${instanceId}!`); } if (!EntityProxyManager_1.EntityProxyManager['proxyStateTree']) { EntityProxyManager_1.EntityProxyManager.initialize(); } if (!this.instances[instanceId]) { this.instances[instanceId] = new StateManager(instanceId); } return this.instances[instanceId]; } static instances = {}; _processing = false; get processing() { return this._processing; } _state; get state() { return this._state ?? {}; } set state(state) { this._state = state; } async handleStateChange(newState, previousState, origins) { const differences = (0, deep_object_diff_1.detailedDiff)(previousState, newState); if (!(0, lodash_1.isEmpty)(differences.added) || !(0, lodash_1.isEmpty)(differences.updated) || !(0, lodash_1.isEmpty)(differences.deleted)) { this._state = newState; this.cache.clear(); EntityProxyManager_1.EntityProxyManager.clearCache(); await this.emitStateChangeEvents(differences, previousState, newState, Array.from(origins)); } } async setState(newState) { if (this._state === newState) return; await this.dispatch((0, actions_1.setState)(newState), true, this.instanceId); } cache = new mnemonist_1.LRUCacheWithDelete(1000); _queue = new typescript_algos_1.Queue(); get queue() { return this._queue; } async dispatch(actions, sync = true, origin) { const actionArray = Array.isArray(actions) ? actions : [actions]; const queueItems = actionArray.map((action) => ({ action, origin })); if (sync && !this._processing) { await this.processChanges(queueItems); } else { queueItems.forEach((item) => this._queue.enqueue(item)); } } async processChanges(items) { const origins = new Set(); const pendingChanges = items ?? this._queue; const previousState = this._state; this._processing = true; let newState; let itemsProcessed = 0; const startTime = Date.now(); const MAX_PROCESSING_TIME = 30000; while (pendingChanges.length > 0 && itemsProcessed < 100 && Date.now() - startTime < MAX_PROCESSING_TIME) { const item = Array.isArray(pendingChanges) ? pendingChanges.shift() : pendingChanges.dequeue(); if (!item) break; const { action, origin } = item; newState = (0, entities_1.reducer)(newState || this._state, action); if (origin) { origins.add(origin); } itemsProcessed++; } if (newState && newState !== this._state) { await this.handleStateChange(newState, previousState, origins); } this._processing = false; } async emitStateChangeEvents(differences, previousState, newState, origins) { const changedPaths = ['added', 'updated', 'deleted']; const emitMatchingEvents = async (event, details) => { if (this.listenerCount(event) > 0) { await this.emitAsync(event, details); } const [entityName, eventIdAndType] = event.split('.'); const [eventId, eventType] = eventIdAndType.split(':'); const wildcardPatterns = [ `${entityName}.*:${eventType}`, `*.*:${eventType}`, `${entityName}.${eventId}:*`, `${entityName}.*:*`, '*.*:*' ]; for (const pattern of wildcardPatterns) { if (this.listenerCount(pattern) > 0) { await this.emitAsync(pattern, details); } } }; for (const changeType of changedPaths) { const entities = differences[changeType]; if (!entities || typeof entities !== 'object') continue; for (const [entityName, entityChanges] of Object.entries(entities)) { if (!entityChanges || typeof entityChanges !== 'object') continue; for (const entityId of Object.keys(entityChanges)) { const eventName = `${entityName}.${entityId}:${changeType}`; if (origins.includes(this.instanceId)) { continue; } const eventDetails = { newState, previousState, changeType, changes: entityChanges[entityId], origins }; await emitMatchingEvents(eventName, eventDetails); } } } await this.emitAsync('stateChange', { newState, previousState, differences, changeOrigins: origins }); } query(entityName, id, denormalizeData = true) { if (!entityName || !id) { return null; } const entity = this._state[entityName]?.[id]; if (!entity) { return null; } if (!denormalizeData) { return entity; } const denormalized = (0, utils_1.limitRecursion)(id, entityName, this._state, this); if (denormalized === id) { return entity; } return EntityProxyManager_1.EntityProxyManager.createEntityProxy(entityName, id, denormalized, this); } clear() { this.dispatch((0, actions_1.clearEntities)(), true, this.instanceId); } async emitAsync(event, ...args) { const listeners = this.listeners(event); for (const listener of listeners) { try { await Promise.resolve().then(() => listener(...args)); } catch (error) { console.error(`Error in listener for event '${event}':`, error); } } return listeners.length > 0; } isEntityReferenced(entityName, entityId, ignoreReference, checkedEntities = new Set()) { const entityKey = `${entityName}:${entityId}`; if (checkedEntities.has(entityKey)) { return false; } checkedEntities.add(entityKey); const referenceMap = SchemaManager_1.SchemaManager.relationshipMap[entityName]?._referencedBy; if (!referenceMap) return false; return Object.entries(referenceMap).some(([referencingEntityName, referencingEntity]) => { if (ignoreReference && ignoreReference.entityName === referencingEntityName && ignoreReference.fieldName === referencingEntity.fieldName) { return false; } const entities = this.state[referencingEntityName]; if (!entities) return false; return Object.keys(entities).some((id) => { const entity = entities[id]; if (!entity || entity[referencingEntity.fieldName] === undefined) return false; const hasReference = referencingEntity.isMany ? Array.isArray(entity[referencingEntity.fieldName]) && entity[referencingEntity.fieldName].includes(entityId) : entity[referencingEntity.fieldName] === entityId; if (hasReference) { return this.isEntityReferenced(referencingEntityName, id, ignoreReference, checkedEntities); } return false; }); }); } cleanup() { this.removeAllListeners(); this._state = {}; this._processing = false; while (this._queue.length > 0) { this._queue.dequeue(); } this.cache.clear(); EntityProxyManager_1.EntityProxyManager.clearCache(); delete StateManager.instances[this.instanceId]; } static clearInstances() { this.instances = {}; } } exports.StateManager = StateManager; exports.default = StateManager;