319 lines
11 KiB
TypeScript
319 lines
11 KiB
TypeScript
// Workers do not have access to log.error
|
|
/* eslint-disable no-console */
|
|
import { DataTree } from "entities/DataTree/dataTreeFactory";
|
|
import {
|
|
DependencyMap,
|
|
EVAL_WORKER_ACTIONS,
|
|
EvalError,
|
|
EvalErrorTypes,
|
|
} from "utils/DynamicBindingUtils";
|
|
import {
|
|
CrashingError,
|
|
DataTreeDiff,
|
|
getSafeToRenderDataTree,
|
|
removeFunctions,
|
|
validateWidgetProperty,
|
|
} from "./evaluationUtils";
|
|
import DataTreeEvaluator from "workers/DataTreeEvaluator";
|
|
import ReplayEntity from "entities/Replay";
|
|
import evaluate, {
|
|
evaluateAsync,
|
|
setupEvaluationEnvironment,
|
|
} from "workers/evaluate";
|
|
import ReplayCanvas from "entities/Replay/ReplayEntity/ReplayCanvas";
|
|
import ReplayEditor from "entities/Replay/ReplayEntity/ReplayEditor";
|
|
import { setFormEvaluationSaga } from "./formEval";
|
|
import { isEmpty } from "lodash";
|
|
|
|
const CANVAS = "canvas";
|
|
|
|
const ctx: Worker = self as any;
|
|
|
|
export let dataTreeEvaluator: DataTreeEvaluator | undefined;
|
|
|
|
let replayMap: Record<string, ReplayEntity<any>>;
|
|
|
|
//TODO: Create a more complete RPC setup in the subtree-eval branch.
|
|
function messageEventListener(
|
|
fn: (
|
|
message: EVAL_WORKER_ACTIONS,
|
|
requestData: any,
|
|
requestId: string,
|
|
) => any,
|
|
) {
|
|
return (e: MessageEvent) => {
|
|
const startTime = performance.now();
|
|
const { method, requestData, requestId } = e.data;
|
|
if (method) {
|
|
const responseData = fn(method, requestData, requestId);
|
|
if (responseData) {
|
|
const endTime = performance.now();
|
|
try {
|
|
ctx.postMessage({
|
|
requestId,
|
|
responseData,
|
|
timeTaken: (endTime - startTime).toFixed(2),
|
|
});
|
|
} catch (e) {
|
|
console.error(e);
|
|
// we dont want to log dataTree because it is huge.
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
const { dataTree, ...rest } = requestData;
|
|
ctx.postMessage({
|
|
requestId,
|
|
responseData: {
|
|
errors: [
|
|
{
|
|
type: EvalErrorTypes.CLONE_ERROR,
|
|
message: e,
|
|
context: JSON.stringify(rest),
|
|
},
|
|
],
|
|
},
|
|
timeTaken: (endTime - startTime).toFixed(2),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
ctx.addEventListener(
|
|
"message",
|
|
messageEventListener((method, requestData: any, requestId) => {
|
|
switch (method) {
|
|
case EVAL_WORKER_ACTIONS.SETUP: {
|
|
setupEvaluationEnvironment();
|
|
return true;
|
|
}
|
|
case EVAL_WORKER_ACTIONS.EVAL_TREE: {
|
|
const {
|
|
allActionValidationConfig,
|
|
shouldReplay = true,
|
|
unevalTree,
|
|
widgets,
|
|
widgetTypeConfigMap,
|
|
} = requestData;
|
|
|
|
let dataTree: DataTree = unevalTree;
|
|
let errors: EvalError[] = [];
|
|
let logs: any[] = [];
|
|
let dependencies: DependencyMap = {};
|
|
let evaluationOrder: string[] = [];
|
|
let unEvalUpdates: DataTreeDiff[] = [];
|
|
let jsUpdates: Record<string, any> = {};
|
|
try {
|
|
if (!dataTreeEvaluator) {
|
|
replayMap = replayMap || {};
|
|
replayMap[CANVAS] = new ReplayCanvas(widgets);
|
|
//allActionValidationConfigs maybe empty
|
|
dataTreeEvaluator = new DataTreeEvaluator(
|
|
widgetTypeConfigMap,
|
|
allActionValidationConfig,
|
|
);
|
|
const dataTreeResponse = dataTreeEvaluator.createFirstTree(
|
|
unevalTree,
|
|
);
|
|
evaluationOrder = dataTreeEvaluator.sortedDependencies;
|
|
dataTree = dataTreeResponse.evalTree;
|
|
jsUpdates = dataTreeResponse.jsUpdates;
|
|
// 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 if (dataTreeEvaluator.hasCyclicalDependency) {
|
|
if (dataTreeEvaluator && !isEmpty(allActionValidationConfig)) {
|
|
//allActionValidationConfigs may not be set in dataTreeEvaluatior. Therefore, set it explicitly via setter method
|
|
dataTreeEvaluator.setAllActionValidationConfig(
|
|
allActionValidationConfig,
|
|
);
|
|
}
|
|
if (shouldReplay) {
|
|
replayMap[CANVAS]?.update(widgets);
|
|
}
|
|
dataTreeEvaluator = new DataTreeEvaluator(
|
|
widgetTypeConfigMap,
|
|
allActionValidationConfig,
|
|
);
|
|
if (dataTreeEvaluator && !isEmpty(allActionValidationConfig)) {
|
|
dataTreeEvaluator.setAllActionValidationConfig(
|
|
allActionValidationConfig,
|
|
);
|
|
}
|
|
const dataTreeResponse = dataTreeEvaluator.createFirstTree(
|
|
unevalTree,
|
|
);
|
|
evaluationOrder = dataTreeEvaluator.sortedDependencies;
|
|
dataTree = dataTreeResponse.evalTree;
|
|
jsUpdates = dataTreeResponse.jsUpdates;
|
|
dataTree = dataTree && JSON.parse(JSON.stringify(dataTree));
|
|
} else {
|
|
if (dataTreeEvaluator && !isEmpty(allActionValidationConfig)) {
|
|
dataTreeEvaluator.setAllActionValidationConfig(
|
|
allActionValidationConfig,
|
|
);
|
|
}
|
|
dataTree = {};
|
|
if (shouldReplay) {
|
|
replayMap[CANVAS]?.update(widgets);
|
|
}
|
|
const updateResponse = dataTreeEvaluator.updateDataTree(unevalTree);
|
|
evaluationOrder = updateResponse.evaluationOrder;
|
|
unEvalUpdates = updateResponse.unEvalUpdates;
|
|
dataTree = JSON.parse(JSON.stringify(dataTreeEvaluator.evalTree));
|
|
jsUpdates = updateResponse.jsUpdates;
|
|
}
|
|
dependencies = dataTreeEvaluator.inverseDependencyMap;
|
|
errors = dataTreeEvaluator.errors;
|
|
dataTreeEvaluator.clearErrors();
|
|
logs = dataTreeEvaluator.logs;
|
|
if (replayMap[CANVAS]?.logs)
|
|
logs = logs.concat(replayMap[CANVAS]?.logs);
|
|
replayMap[CANVAS]?.clearLogs();
|
|
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);
|
|
}
|
|
return {
|
|
dataTree,
|
|
dependencies,
|
|
errors,
|
|
evaluationOrder,
|
|
logs,
|
|
unEvalUpdates,
|
|
jsUpdates,
|
|
};
|
|
}
|
|
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,
|
|
globalContext,
|
|
} = requestData;
|
|
if (!dataTreeEvaluator) {
|
|
return { triggers: [], errors: [] };
|
|
}
|
|
dataTreeEvaluator.updateDataTree(dataTree);
|
|
const evalTree = dataTreeEvaluator.evalTree;
|
|
const resolvedFunctions = dataTreeEvaluator.resolvedFunctions;
|
|
|
|
dataTreeEvaluator.evaluateTriggers(
|
|
dynamicTrigger,
|
|
evalTree,
|
|
requestId,
|
|
resolvedFunctions,
|
|
callbackData,
|
|
{
|
|
globalContext,
|
|
},
|
|
);
|
|
|
|
break;
|
|
}
|
|
case EVAL_WORKER_ACTIONS.PROCESS_TRIGGER:
|
|
/**
|
|
* This action will not be processed here. This is handled in the eval trigger sub steps
|
|
* @link promisifyAction
|
|
**/
|
|
break;
|
|
case EVAL_WORKER_ACTIONS.CLEAR_CACHE: {
|
|
dataTreeEvaluator = undefined;
|
|
return true;
|
|
}
|
|
case EVAL_WORKER_ACTIONS.VALIDATE_PROPERTY: {
|
|
const { props, validation, value } = requestData;
|
|
return removeFunctions(
|
|
validateWidgetProperty(validation, value, props),
|
|
);
|
|
}
|
|
case EVAL_WORKER_ACTIONS.UNDO: {
|
|
const { entityId } = requestData;
|
|
if (!replayMap[entityId || CANVAS]) return;
|
|
const replayResult = replayMap[entityId || CANVAS].replay("UNDO");
|
|
replayMap[entityId || CANVAS].clearLogs();
|
|
return replayResult;
|
|
}
|
|
case EVAL_WORKER_ACTIONS.REDO: {
|
|
const { entityId } = requestData;
|
|
if (!replayMap[entityId ?? CANVAS]) return;
|
|
const replayResult = replayMap[entityId ?? CANVAS].replay("REDO");
|
|
replayMap[entityId ?? CANVAS].clearLogs();
|
|
return replayResult;
|
|
}
|
|
case EVAL_WORKER_ACTIONS.EXECUTE_SYNC_JS: {
|
|
const { functionCall } = requestData;
|
|
|
|
if (!dataTreeEvaluator) {
|
|
return true;
|
|
}
|
|
const evalTree = dataTreeEvaluator.evalTree;
|
|
const resolvedFunctions = dataTreeEvaluator.resolvedFunctions;
|
|
const { errors, result } = evaluate(
|
|
functionCall,
|
|
evalTree,
|
|
resolvedFunctions,
|
|
false,
|
|
undefined,
|
|
);
|
|
return { errors, result };
|
|
}
|
|
case EVAL_WORKER_ACTIONS.EVAL_EXPRESSION:
|
|
const { expression, isTrigger } = requestData;
|
|
const evalTree = dataTreeEvaluator?.evalTree;
|
|
if (!evalTree) return {};
|
|
// TODO find a way to do this for snippets
|
|
return isTrigger
|
|
? evaluateAsync(expression, evalTree, "SNIPPET", {})
|
|
: evaluate(expression, evalTree, {}, false);
|
|
case EVAL_WORKER_ACTIONS.UPDATE_REPLAY_OBJECT:
|
|
const { entity, entityId, entityType } = requestData;
|
|
const replayObject = replayMap[entityId];
|
|
if (replayObject) {
|
|
replayObject.update(entity);
|
|
} else {
|
|
replayMap[entityId] = new ReplayEditor(entity, entityType);
|
|
}
|
|
break;
|
|
case EVAL_WORKER_ACTIONS.SET_EVALUATION_VERSION:
|
|
const { version } = requestData;
|
|
self.evaluationVersion = version || 1;
|
|
break;
|
|
case EVAL_WORKER_ACTIONS.INIT_FORM_EVAL:
|
|
const { currentEvalState, payload, type } = requestData;
|
|
const response = setFormEvaluationSaga(type, payload, currentEvalState);
|
|
return response;
|
|
default: {
|
|
console.error("Action not registered on worker", method);
|
|
}
|
|
}
|
|
}),
|
|
);
|