UNPKG

@statezero/core

Version:

The type-safe frontend client for StateZero - connect directly to your backend models with zero boilerplate

208 lines (207 loc) 8.09 kB
import { operationEvents, Status, Type } from './operation.js'; import { modelStoreRegistry } from '../registries/modelStoreRegistry.js'; import { querysetStoreRegistry } from '../registries/querysetStoreRegistry.js'; import { metricRegistry } from '../registries/metricRegistry.js'; import { getFingerprint } from './utils.js'; import { QuerySet } from '../../flavours/django/querySet.js'; import { isEqual, isNil } from 'lodash-es'; import hash from 'object-hash'; /** * Returns querysets that have the same nodes as any ancestor of the specific queryset * @param {QuerySet} queryset * @returns {Map<QuerySet, Store>} */ function relatedQuerysets(queryset) { // Collect ancestor nodes for comparison let ancestorNodes = []; let current = queryset; while (current) { ancestorNodes.push(current.nodes); current = current.__parent; } const modelClass = queryset.ModelClass; const result = new Map(); Array.from(querysetStoreRegistry._stores.entries()).forEach(([queryset, store]) => { if (store.modelClass !== modelClass) return; try { if (ancestorNodes.some(nodes => isEqual(nodes, store.queryset.nodes))) { result.set(store.queryset, store); } } catch (e) { console.warn('Error comparing nodes for related querysets', e); } }); return result; } /** * Process an operation in the model store * * @param {Operation} operation - The operation to process * @param {string} actionType - The action to perform ('add', 'update', 'confirm', 'reject') */ function processModelStore(operation, actionType) { const ModelClass = operation.queryset.ModelClass; const modelStore = modelStoreRegistry.getStore(ModelClass); if (!modelStore) return; switch (actionType) { case 'add': modelStore.addOperation(operation); break; case 'update': modelStore.updateOperation(operation); break; case 'confirm': modelStore.confirm(operation); break; case 'reject': modelStore.reject(operation); break; } } /** * Process an operation in the queryset stores based on operation type * Uses different routing strategies based on the operation type * * @param {Operation} operation - The operation to process * @param {string} actionType - The action to perform ('add', 'update', 'confirm', 'reject') */ function processQuerysetStores(operation, actionType) { const ModelClass = operation.queryset.ModelClass; const queryset = operation.queryset; // Apply the appropriate action to a single queryset store const applyAction = (store) => { switch (actionType) { case 'add': store.addOperation(operation); break; case 'update': store.updateOperation(operation); break; case 'confirm': store.confirm(operation); break; case 'reject': store.reject(operation); break; } }; let querysetStoreMap; // Different routing strategies based on operation type switch (operation.type) { case Type.CREATE: case Type.GET_OR_CREATE: case Type.UPDATE_OR_CREATE: // For creates, route to related querysets (they might want to include the new item) querysetStoreMap = relatedQuerysets(queryset); break; case Type.UPDATE: case Type.UPDATE_INSTANCE: case Type.DELETE: case Type.DELETE_INSTANCE: // No need to do anything, the model will change the queryset local filtering will handle it querysetStoreMap = new Map(); break; case Type.CHECKPOINT: // No need to do anything, the model will change the queryset local filtering will handle it querysetStoreMap = new Map(); break; default: // For other operation types, use the existing related querysets logic querysetStoreMap = relatedQuerysets(queryset); break; } Array.from(querysetStoreMap.values()).forEach(applyAction); } /** * Process an operation in the metric stores based on operation type * * @param {Operation} operation - The operation to process * @param {string} actionType - The action to perform ('add', 'update', 'confirm', 'reject') */ function processMetricStores(operation, actionType) { const queryset = operation.queryset; const ModelClass = queryset.ModelClass; // For metrics, we can use a similar strategy but might be more conservative // and always use related querysets since metrics are aggregations const allQuerysets = Array.from(relatedQuerysets(queryset).keys()); let allMetricStores = new Set(); allQuerysets.forEach(qs => { const stores = metricRegistry.getAllStoresForQueryset(qs); if (stores && stores.length > 0) { stores.forEach(store => allMetricStores.add(store)); } }); if (!allMetricStores || allMetricStores.size === 0) { return; } // Apply the action to each matching metric store allMetricStores.forEach(store => { switch (actionType) { case 'add': store.addOperation(operation); break; case 'update': store.updateOperation(operation); break; case 'confirm': store.confirm(operation); break; case 'reject': store.reject(operation); break; } }); } /** * Common processing logic for operations, handling validation and routing * to the appropriate store processors * * @param {Operation} operation - The operation to process * @param {string} actionType - The action to perform ('add', 'update', 'confirm', 'reject') */ function processOperation(operation, actionType) { if (!operation || !operation.queryset || !operation.queryset.ModelClass) { console.warn(`Received invalid operation in processOperation (${actionType})`, operation); return; } if (operation.doNotPropagate) { return; } // Process model store first processModelStore(operation, actionType); // Then process queryset stores with improved routing processQuerysetStores(operation, actionType); // Finally process metric stores processMetricStores(operation, actionType); } // Define handlers as named arrow functions at the top level const handleOperationCreated = operation => processOperation(operation, 'add'); const handleOperationUpdated = operation => processOperation(operation, 'update'); const handleOperationMutated = operation => processOperation(operation, 'update'); const handleOperationConfirmed = operation => processOperation(operation, 'confirm'); const handleOperationRejected = operation => processOperation(operation, 'reject'); /** * Initialize the operation event handler system by setting up event listeners */ export function initEventHandler() { operationEvents.on(Status.CREATED, handleOperationCreated); operationEvents.on(Status.UPDATED, handleOperationUpdated); operationEvents.on(Status.CONFIRMED, handleOperationConfirmed); operationEvents.on(Status.REJECTED, handleOperationRejected); operationEvents.on(Status.MUTATED, handleOperationMutated); console.log('Operation event handler initialized'); } /** * Clean up by removing all event listeners */ export function cleanupEventHandler() { operationEvents.off(Status.CREATED, handleOperationCreated); operationEvents.off(Status.UPDATED, handleOperationUpdated); operationEvents.off(Status.CONFIRMED, handleOperationConfirmed); operationEvents.off(Status.REJECTED, handleOperationRejected); operationEvents.off(Status.MUTATED, handleOperationMutated); console.log('Operation event handler cleaned up'); }