chore: initial commit
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
import {setImmediate} from 'node:timers/promises';
|
||||
import {performance} from 'node:perf_hooks';
|
||||
import type {BranchState, NodeReturn, WorkflowDependencies} from '../../../types/bot/workflow/runtime.ts';
|
||||
import type {WorkflowNode} from '../../../types/bot/workflow/workflow.ts';
|
||||
import {node_registry} from '../registry.ts';
|
||||
import {assert_json} from './value.ts';
|
||||
|
||||
|
||||
export async function execute_node(node: WorkflowNode, state: BranchState, signal: AbortSignal,
|
||||
deps: WorkflowDependencies): Promise<NodeReturn> {
|
||||
const controller = new AbortController();
|
||||
const combined = AbortSignal.any([signal, controller.signal]);
|
||||
const timeout_ms = node.timeout_ms ?? 30_000;
|
||||
const timeout_error = Object.assign(new Error('Node execution timed out'), {code: 'NODE_TIMEOUT'});
|
||||
const started_at = performance.now();
|
||||
const valid_timeout = Number.isFinite(timeout_ms) && timeout_ms > 0 && timeout_ms <= 2_147_483_647;
|
||||
let timer: ReturnType<typeof setTimeout> | undefined;
|
||||
try {
|
||||
if (valid_timeout) timer = setTimeout(() => controller.abort(timeout_error), timeout_ms);
|
||||
await setImmediate();
|
||||
signal.throwIfAborted();
|
||||
if (!valid_timeout) {
|
||||
throw Object.assign(new Error('Invalid node timeout'), {code: 'INVALID_NODE_TIMEOUT'});
|
||||
}
|
||||
combined.throwIfAborted();
|
||||
if (!Object.hasOwn(node_registry, node.type)) {
|
||||
throw Object.assign(new Error('Unknown workflow node type'), {code: 'UNKNOWN_NODE_TYPE'});
|
||||
}
|
||||
// Await the handler itself so cancellation never leaves an operation running in the background.
|
||||
const returned = await node_registry[node.type].execute(node, state, combined, deps);
|
||||
signal.throwIfAborted();
|
||||
if (performance.now() - started_at >= timeout_ms) controller.abort(timeout_error);
|
||||
combined.throwIfAborted();
|
||||
assert_json(returned);
|
||||
if (typeof returned.port !== 'string' || returned.port.length === 0 || !returned.result
|
||||
|| !['success', 'failure'].includes(returned.result.status)
|
||||
|| !Object.hasOwn(returned.result, 'output')
|
||||
|| (returned.result.error !== undefined && (typeof returned.result.error.code !== 'string'
|
||||
|| typeof returned.result.error.message !== 'string' || typeof returned.result.error.node_id !== 'string'))
|
||||
|| (returned.vars !== undefined && (returned.vars === null || Array.isArray(returned.vars)
|
||||
|| typeof returned.vars !== 'object'))
|
||||
|| (returned.retry_counts !== undefined && (returned.retry_counts === null
|
||||
|| typeof returned.retry_counts !== 'object' || Array.isArray(returned.retry_counts)
|
||||
|| Object.values(returned.retry_counts).some(value => !Number.isSafeInteger(value) || value < 0)))) {
|
||||
throw Object.assign(new Error('Invalid node return value'), {code: 'INVALID_NODE_RETURN'});
|
||||
}
|
||||
if (!node_registry[node.type].config_template.ports.some(port => port.name === returned.port)) {
|
||||
throw Object.assign(new Error('Node returned an undeclared port'), {code: 'INVALID_NODE_PORT'});
|
||||
}
|
||||
signal.throwIfAborted();
|
||||
if (performance.now() - started_at >= timeout_ms) throw timeout_error;
|
||||
return returned;
|
||||
} catch (error) {
|
||||
signal.throwIfAborted();
|
||||
const cause = controller.signal.aborted || (valid_timeout && performance.now() - started_at >= timeout_ms)
|
||||
? timeout_error : error;
|
||||
const coded = cause as {code?: unknown; message?: unknown} | null;
|
||||
const code = typeof coded?.code === 'string' ? coded.code : 'NODE_EXECUTION_FAILED';
|
||||
const message = typeof coded?.message === 'string' ? coded.message : String(cause);
|
||||
return {port: 'failure', result: {status: 'failure', output: null,
|
||||
error: {code: code.slice(0, 256), message: message.slice(0, 4096), node_id: node.id}}};
|
||||
} finally {
|
||||
if (timer !== undefined) clearTimeout(timer);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user