Skip to content
@tde.io/plexis  ·  npm install @tde.io/plexis  ·  npm  ·  GitHub

Forks in Depth

Forks are how a pipeline decides where to go next. They live on a node’s forks array and are evaluated in order after the node’s action runs. The first fork whose condition returns true wins; the rest are skipped.

import { definePipeline, node, action, fork, target, terminal } from '@tde.io/plexis';
type Ctx = { score: number };
const classify = definePipeline<Ctx>('classify', () => {
node('evaluate', () => {
action(async (ctx) => ctx);
fork('grade-a', target('grade-a'), (ctx) => ctx.score >= 90);
fork('grade-b', target('grade-b'), (ctx) => ctx.score >= 80);
fork('grade-c', target('grade-c'), (ctx) => ctx.score >= 70);
fork('grade-d', target('grade-d'), (ctx) => ctx.score >= 60);
fork('grade-f', target('grade-f'));
});
node('grade-a', terminal());
node('grade-b', terminal());
node('grade-c', terminal());
node('grade-d', terminal());
node('grade-f', terminal());
return { initial: 'evaluate' };
});
const result = await classify.run({ score: 85 });
// score >= 90 → false (skip)
// score >= 80 → true (wins)
// result.finalNode === 'grade-b'

A score of 85 satisfies score >= 80 and score >= 70, but only the first match runs — grade-b is selected and grade-c is never evaluated.

A fork with no condition always matches. Place it last to handle any input not caught by earlier conditions.

import { definePipeline, node, action, fork, target, terminal } from '@tde.io/plexis';
type Ctx = { status: string };
const route = definePipeline<Ctx>('route', () => {
node('dispatch', () => {
fork('approved', target('process'), (ctx) => ctx.status === 'approved');
fork('review', target('hold'), (ctx) => ctx.status === 'review');
fork('default', target('reject'));
});
node('process', terminal());
node('hold', terminal());
node('reject', terminal());
return { initial: 'dispatch' };
});

If the forks array is empty, or every condition returns false, the pipeline stops at the current node with status: 'stopped' in the result. This is not an error — it is the intended behavior for optional continuations.

import { definePipeline, node, action, fork, target, terminal } from '@tde.io/plexis';
type Ctx = { sendNotification: boolean };
const maybe = definePipeline<Ctx>('maybe', () => {
node('check', () => {
fork('notify', target('notify'), (ctx) => ctx.sendNotification);
});
node('notify', terminal());
return { initial: 'check' };
});
const result = await maybe.run({ sendNotification: false });
// result.status === 'stopped'
// result.finalNode === 'check'

Distinguish this from a terminal node: status: 'completed' means a terminal node (registered with terminal()) was reached; status: 'stopped' means execution ended because no fork matched.

Conditions receive the context after the node’s action has run and its patch has been merged. Write conditions against the updated context, not the input context.

import { definePipeline, node, action, fork, target, terminal } from '@tde.io/plexis';
type Ctx = { raw: number; score: number };
const score = definePipeline<Ctx>('score', () => {
node('compute', () => {
action(async (ctx) => ({ score: ctx.raw * 10 }));
fork('pass', target('pass'), (ctx) => ctx.score >= 100);
fork('fail', target('fail'));
});
node('pass', terminal());
node('fail', terminal());
return { initial: 'compute' };
});
const result = await score.run({ raw: 10, score: 0 });
// action sets score: 100 → condition ctx.score >= 100 is true
// result.finalNode === 'pass'

For the full fork() signature, see the Registration Helpers reference.