// Props
import { DecisionTable } from "smithers-orchestrator";
type DecisionTableProps = {
id?: string;
rules: DecisionRule[];
default?: ReactElement; // rendered when no rule matches
strategy?: "first-match" | "all-match"; // default "first-match"
skipIf?: boolean;
};
type DecisionRule = {
when: boolean; // evaluated at render time
then: ReactElement;
label?: string;
};
<DecisionTable
rules={[
{
when: triage.severity === "critical",
then: (
<Task id="page-oncall" output={outputs.page} agent={pagerAgent}>
Page the on-call engineer immediately.
</Task>
),
},
{
when: triage.severity === "high",
then: <Task id="assign-senior" output={outputs.assign}>{{ assignee: "senior-pool" }}</Task>,
},
]}
default={<Task id="default-assign" output={outputs.assign}>{{ assignee: "general-pool" }}</Task>}
/>
Notes
first-matchbuilds nested Branches; order matters.all-matchwraps every matching rule in a Parallel; no ordering guarantee.
Source
The<DecisionTable> implementation and the files it imports, straight from the package source. This section is generated; edit the source, not this block.
// @smithers-type-exports-begin
/** @typedef {import("./DecisionRule.ts").DecisionRule} DecisionRule */
/** @typedef {import("./DecisionTableProps.ts").DecisionTableProps} DecisionTableProps */
// @smithers-type-exports-end
import React from "react";
import { Branch } from "./Branch.js";
import { Parallel } from "./Parallel.js";
/**
* Structured deterministic routing. Replaces deeply nested Branches with a
* flat, declarative rule table.
*
* - `"first-match"` builds nested Branch elements so the first matching rule wins.
* - `"all-match"` gathers all matching rules' `then` elements into a Parallel.
*
* Composes Branch and Parallel internally.
* @param {DecisionTableProps} props
*/
export function DecisionTable(props) {
if (props.skipIf)
return null;
const { rules, strategy = "first-match" } = props;
if (strategy === "all-match") {
const matching = rules.filter((r) => r.when).map((r) => r.then);
if (matching.length === 0) {
return props.default ?? null;
}
return React.createElement(Parallel, { id: props.id }, ...matching);
}
// "first-match": build nested Branches from the last rule backward.
// The innermost else is the default fallback.
let fallback = props.default ?? null;
for (let i = rules.length - 1; i >= 0; i--) {
const rule = rules[i];
fallback = React.createElement(Branch, {
if: rule.when,
then: rule.then,
else: fallback,
});
}
return fallback;
}
import React from "react";
import { SmithersError } from "@smithers-orchestrator/errors/SmithersError";
/** @typedef {import("./BranchProps.ts").BranchProps} BranchProps */
/**
* @param {BranchProps} props
*/
export function Branch(props) {
// <Branch> resolves its subtree from the `then`/`else` props; any JSX children
// would be silently dropped, removing those tasks from the graph with no
// feedback. Fail fast instead. (Checked before skipIf so a stray-children
// mistake still surfaces even on a skipped branch.)
if (props.children !== undefined && props.children !== null) {
throw new SmithersError("INVALID_INPUT", `<Branch> does not take children. Use the "then" and "else" props instead, e.g. ` +
`<Branch if={cond} then={<Task .../>} else={<Task .../>} />. ` +
`Children passed to <Branch> are silently ignored and would drop those tasks from the graph.`);
}
if (props.skipIf)
return null;
const chosen = props.if ? props.then : (props.else ?? null);
// The branch is resolved to `chosen` at render time, so the host element
// carries no props of its own (align with the sanitizing structural components).
return React.createElement("smithers:branch", {}, chosen);
}
import React from "react";
/** @typedef {import("./ParallelProps.ts").ParallelProps} ParallelProps */
/**
* @param {ParallelProps} props
*/
export function Parallel(props) {
if (props.skipIf)
return null;
// Align prop sanitization with other structural components. `label` names
// the phase group in run views (graph, the Claude /workflows mirror).
const next = {
maxConcurrency: props.maxConcurrency,
subtreeConcurrency: props.subtreeConcurrency,
id: props.id,
...(props.label === undefined ? {} : { label: props.label }),
};
return React.createElement("smithers:parallel", next, props.children);
}
import type React from "react";
export type DecisionRule = {
/** Condition evaluated at render time. */
when: boolean;
/** Element to render when this rule matches. */
then: React.ReactElement;
/** Optional display label for the rule. */
label?: string;
};
import type React from "react";
import type { DecisionRule } from "./DecisionRule.ts";
export type DecisionTableProps = {
/** ID prefix for generated wrapper nodes. */
id?: string;
/** Ordered list of rules. Each rule has a `when` condition and a `then` element. */
rules: DecisionRule[];
/** Fallback element rendered when no rules match. */
default?: React.ReactElement;
/** `"first-match"` (default): first matching rule wins. `"all-match"`: all matching rules run in parallel. */
strategy?: "first-match" | "all-match";
skipIf?: boolean;
};