import { DataTree, EvaluationSubstitutionType, } from "entities/DataTree/dataTreeFactory"; import { DependencyMap, EVAL_WORKER_ACTIONS, EvalError, EvalErrorTypes, EvaluationError, PropertyEvaluationErrorType, } from "utils/DynamicBindingUtils"; import { CrashingError, getSafeToRenderDataTree, removeFunctions, validateWidgetProperty, } from "./evaluationUtils"; import DataTreeEvaluator from "workers/DataTreeEvaluator"; import { Diff } from "deep-diff"; const ctx: Worker = self as any; let dataTreeEvaluator: DataTreeEvaluator | undefined; //TODO: Create a more complete RPC setup in the subtree-eval branch. function messageEventListener( fn: (message: EVAL_WORKER_ACTIONS, requestData: any) => void, ) { return (e: MessageEvent) => { const startTime = performance.now(); const { method, requestData, requestId } = e.data; const responseData = fn(method, requestData); const endTime = performance.now(); ctx.postMessage({ requestId, responseData, timeTaken: (endTime - startTime).toFixed(2), }); }; } ctx.addEventListener( "message", messageEventListener((method, requestData: any) => { switch (method) { case EVAL_WORKER_ACTIONS.EVAL_TREE: { const { unevalTree, widgetTypeConfigMap } = requestData; let dataTree: DataTree = unevalTree; let errors: EvalError[] = []; let logs: any[] = []; let dependencies: DependencyMap = {}; let updates: Diff[] = []; let evaluationOrder: string[] = []; try { if (!dataTreeEvaluator) { dataTreeEvaluator = new DataTreeEvaluator(widgetTypeConfigMap); dataTree = dataTreeEvaluator.createFirstTree(unevalTree); evaluationOrder = dataTreeEvaluator.sortedDependencies; // We need to clean it to remove any possible functions inside the tree. // If functions exist, it will crash the web worker dataTree = dataTree && JSON.parse(JSON.stringify(dataTree)); } else { dataTree = {}; const updateResponse = dataTreeEvaluator.updateDataTree(unevalTree); updates = JSON.parse(JSON.stringify(updateResponse.updates)); evaluationOrder = updateResponse.evaluationOrder; } dependencies = dataTreeEvaluator.inverseDependencyMap; errors = dataTreeEvaluator.errors; dataTreeEvaluator.clearErrors(); logs = dataTreeEvaluator.logs; dataTreeEvaluator.clearLogs(); } catch (e) { if (dataTreeEvaluator !== undefined) { errors = dataTreeEvaluator.errors; logs = dataTreeEvaluator.logs; } if (!(e instanceof CrashingError)) { errors.push({ type: EvalErrorTypes.UNKNOWN_ERROR, message: e.message, }); console.error(e); } dataTree = getSafeToRenderDataTree(unevalTree, widgetTypeConfigMap); dataTreeEvaluator = undefined; } return { dataTree, dependencies, errors, evaluationOrder, logs, updates, }; } case EVAL_WORKER_ACTIONS.EVAL_ACTION_BINDINGS: { const { bindings, executionParams } = requestData; if (!dataTreeEvaluator) { return { values: undefined, errors: [] }; } const values = dataTreeEvaluator.evaluateActionBindings( bindings, executionParams, ); const cleanValues = removeFunctions(values); const errors = dataTreeEvaluator.errors; dataTreeEvaluator.clearErrors(); return { values: cleanValues, errors }; } case EVAL_WORKER_ACTIONS.EVAL_TRIGGER: { const { callbackData, dataTree, dynamicTrigger } = requestData; if (!dataTreeEvaluator) { return { triggers: [], errors: [] }; } dataTreeEvaluator.updateDataTree(dataTree); const evalTree = dataTreeEvaluator.evalTree; const { errors: evalErrors, triggers, }: { errors: EvaluationError[]; triggers: Array; } = dataTreeEvaluator.getDynamicValue( dynamicTrigger, evalTree, EvaluationSubstitutionType.TEMPLATE, true, callbackData, ); const cleanTriggers = removeFunctions(triggers); // Transforming eval errors into eval trigger errors. Since trigger // errors occur less, we want to treat it separately const errors = evalErrors .filter( (error) => error.errorType === PropertyEvaluationErrorType.PARSE, ) .map((error) => ({ ...error, message: error.errorMessage, type: EvalErrorTypes.EVAL_TRIGGER_ERROR, })); return { triggers: cleanTriggers, errors }; } case EVAL_WORKER_ACTIONS.CLEAR_CACHE: { dataTreeEvaluator = undefined; return true; } case EVAL_WORKER_ACTIONS.CLEAR_PROPERTY_CACHE: { const { propertyPath } = requestData; if (!dataTreeEvaluator) { return true; } dataTreeEvaluator.clearPropertyCache(propertyPath); return true; } case EVAL_WORKER_ACTIONS.CLEAR_PROPERTY_CACHE_OF_WIDGET: { const { widgetName } = requestData; if (!dataTreeEvaluator) { return true; } dataTreeEvaluator.clearPropertyCacheOfWidget(widgetName); return true; } case EVAL_WORKER_ACTIONS.VALIDATE_PROPERTY: { const { props, validation, value } = requestData; return removeFunctions( validateWidgetProperty(validation, value, props), ); } default: { console.error("Action not registered on worker", method); } } }), );