177 lines
4.1 KiB
TypeScript
177 lines
4.1 KiB
TypeScript
import { evaluators } from '@nocobase/evaluators';
|
|
import { Registry } from '@nocobase/utils';
|
|
import { Instruction } from '.';
|
|
import { Processor } from '..';
|
|
import { JOB_STATUS } from '../constants';
|
|
import type { FlowNodeModel, JobModel } from '../types';
|
|
|
|
type Comparer = (a: any, b: any) => boolean;
|
|
|
|
export const calculators = new Registry<Comparer>();
|
|
|
|
// built-in functions
|
|
function equal(a, b) {
|
|
return a == b;
|
|
}
|
|
|
|
function notEqual(a, b) {
|
|
return a != b;
|
|
}
|
|
|
|
function gt(a, b) {
|
|
return a > b;
|
|
}
|
|
|
|
function gte(a, b) {
|
|
return a >= b;
|
|
}
|
|
|
|
function lt(a, b) {
|
|
return a < b;
|
|
}
|
|
|
|
function lte(a, b) {
|
|
return a <= b;
|
|
}
|
|
|
|
calculators.register('equal', equal);
|
|
calculators.register('notEqual', notEqual);
|
|
calculators.register('gt', gt);
|
|
calculators.register('gte', gte);
|
|
calculators.register('lt', lt);
|
|
calculators.register('lte', lte);
|
|
|
|
calculators.register('==', equal);
|
|
calculators.register('!=', notEqual);
|
|
calculators.register('>', gt);
|
|
calculators.register('>=', gte);
|
|
calculators.register('<', lt);
|
|
calculators.register('<=', lte);
|
|
|
|
function includes(a, b) {
|
|
return a.includes(b);
|
|
}
|
|
|
|
function notIncludes(a, b) {
|
|
return !a.includes(b);
|
|
}
|
|
|
|
function startsWith(a: string, b: string) {
|
|
return a.startsWith(b);
|
|
}
|
|
|
|
function notStartsWith(a: string, b: string) {
|
|
return !a.startsWith(b);
|
|
}
|
|
|
|
function endsWith(a: string, b: string) {
|
|
return a.endsWith(b);
|
|
}
|
|
|
|
function notEndsWith(a: string, b: string) {
|
|
return !a.endsWith(b);
|
|
}
|
|
|
|
calculators.register('includes', includes);
|
|
calculators.register('notIncludes', notIncludes);
|
|
calculators.register('startsWith', startsWith);
|
|
calculators.register('notStartsWith', notStartsWith);
|
|
calculators.register('endsWith', endsWith);
|
|
calculators.register('notEndsWith', notEndsWith);
|
|
|
|
type CalculationItem = {
|
|
calculator?: string;
|
|
operands?: [any?, any?];
|
|
};
|
|
|
|
type CalculationGroup = {
|
|
group: {
|
|
type: 'and' | 'or';
|
|
calculations?: Calculation[];
|
|
};
|
|
};
|
|
|
|
type Calculation = CalculationItem | CalculationGroup;
|
|
|
|
function calculate(calculation: CalculationItem = {}) {
|
|
type NewType = Comparer;
|
|
|
|
let fn: NewType;
|
|
if (!(calculation.calculator && (fn = calculators.get(calculation.calculator)))) {
|
|
throw new Error(`no calculator function registered for "${calculation.calculator}"`);
|
|
}
|
|
return Boolean(fn(...(calculation.operands ?? [])));
|
|
}
|
|
|
|
function logicCalculate(calculation?: Calculation) {
|
|
if (!calculation) {
|
|
return true;
|
|
}
|
|
|
|
if (typeof calculation['group'] === 'object') {
|
|
const method = calculation['group'].type === 'and' ? 'every' : 'some';
|
|
return (calculation['group'].calculations ?? [])[method]((item: Calculation) => logicCalculate(item));
|
|
}
|
|
|
|
return calculate(calculation as CalculationItem);
|
|
}
|
|
|
|
export default {
|
|
async run(node: FlowNodeModel, prevJob, processor: Processor) {
|
|
const { engine, calculation, expression, rejectOnFalse } = node.config || {};
|
|
const evaluator = evaluators.get(engine);
|
|
|
|
let result = true;
|
|
|
|
try {
|
|
result = evaluator
|
|
? evaluator(expression, processor.getScope(node.id))
|
|
: logicCalculate(processor.getParsedValue(calculation, node.id));
|
|
} catch (e) {
|
|
return {
|
|
result: e.toString(),
|
|
status: JOB_STATUS.ERROR,
|
|
};
|
|
}
|
|
|
|
if (!result && rejectOnFalse) {
|
|
return {
|
|
status: JOB_STATUS.FAILED,
|
|
result,
|
|
};
|
|
}
|
|
|
|
const job = {
|
|
status: JOB_STATUS.RESOLVED,
|
|
result,
|
|
// TODO(optimize): try unify the building of job
|
|
nodeId: node.id,
|
|
upstreamId: (prevJob && prevJob.id) || null,
|
|
};
|
|
|
|
const branchNode = processor.nodes.find((item) => item.upstream === node && Boolean(item.branchIndex) === result);
|
|
|
|
if (!branchNode) {
|
|
return job;
|
|
}
|
|
|
|
const savedJob = await processor.saveJob(job);
|
|
|
|
await processor.run(branchNode, savedJob);
|
|
|
|
return null;
|
|
},
|
|
|
|
async resume(node: FlowNodeModel, branchJob: JobModel, processor: Processor) {
|
|
const job = processor.findBranchParentJob(branchJob, node) as JobModel;
|
|
|
|
if (branchJob.status === JOB_STATUS.RESOLVED) {
|
|
// return to continue node.downstream
|
|
return job;
|
|
}
|
|
|
|
// pass control to upper scope by ending current scope
|
|
return processor.exit(branchJob.status);
|
|
},
|
|
} as Instruction;
|