@nocobase/flow-engine
Version:
A standalone flow engine for NocoBase, managing workflows, models, and actions.
85 lines (75 loc) • 2.26 kB
text/typescript
/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import 'ses';
export interface JSRunnerOptions {
timeoutMs?: number;
globals?: Record<string, any>;
version?: string;
}
export class JSRunner {
private globals: Record<string, any>;
private timeoutMs: number;
constructor(options: JSRunnerOptions = {}) {
const bindWindowFn = (key: 'setTimeout' | 'clearTimeout' | 'setInterval' | 'clearInterval') => {
if (typeof window !== 'undefined' && typeof window[key] === 'function') {
return window[key].bind(window);
}
const fn = globalThis[key];
return typeof fn === 'function' ? fn.bind(globalThis) : fn;
};
this.globals = {
console,
setTimeout: bindWindowFn('setTimeout'),
clearTimeout: bindWindowFn('clearTimeout'),
setInterval: bindWindowFn('setInterval'),
clearInterval: bindWindowFn('clearInterval'),
...(options.globals || {}),
};
this.timeoutMs = options.timeoutMs ?? 5000; // 默认 5 秒超时
}
/**
* 注册单个变量或函数
*/
register(name: string, value: any): void {
this.globals[name] = value;
}
/**
* 异步运行代码,带错误处理和超时机制
*/
async run(code: string): Promise<{
success: boolean;
value?: any;
error?: any;
timeout?: boolean;
}> {
const wrapped = `(async () => {
try {
${code};
} catch (e) {
throw e;
}
})()`;
const compartment = new Compartment(this.globals);
try {
const task = compartment.evaluate(wrapped);
const timeoutPromise = new Promise((_resolve, reject) =>
setTimeout(() => reject(new Error('Execution timed out')), this.timeoutMs),
);
const result = await Promise.race([task, timeoutPromise]);
return { success: true, value: result };
} catch (err) {
console.error(err);
return {
success: false,
error: err,
timeout: err.message === 'Execution timed out',
};
}
}
}