Files
2026-05-27 23:47:53 +01:00

3.1 KiB

conditional-step

(no submodules)

Purpose

ConditionalStep is the branching primitive in @suaveplan/workflow. It evaluates a GuardFn against the context and dispatches to one of two Step instances (onTrue or onFalse). When the false branch is omitted and the condition resolves falsy, the step is recorded as "skipped" — giving callers a way to express "do this only if the predicate holds" without filling in a no-op alternative.

This class is the workflow analogue of a ternary expression: it never declares its own retry policy or compensation directly; both branches are fully-fledged Step instances whose own configuration governs those concerns. compensate rolls back only the branch that actually ran.

Features

  • onTrue / optional onFalse branches.
  • lastBranch accessor reflecting which path executed.
  • Skipped branch records a "skipped" StepResult and emits an OTel "skipped" counter increment (the duration histogram is intentionally skipped — see otel/instruments).
  • compensate(ctx) rolls back only the branch that ran.
  • Injectable Clock.

Usage: Basic Example

import { createConditionalStep, createStep } from "@suaveplan/workflow";

const branch = createConditionalStep<{ premium: boolean }>({
	id: "discount",
	name: "Discount",
	condition: (ctx) => ctx.premium,
	onTrue: createStep({
		id: "premium",
		name: "Premium discount",
		execute: () => ({ discountPct: 30 }),
	}),
	onFalse: createStep({
		id: "standard",
		name: "Standard discount",
		execute: () => ({ discountPct: 5 }),
	}),
});
const result = await branch.run({ premium: true });

Usage: Advanced Example

import { createConditionalStep, createStep } from "@suaveplan/workflow";

const ship = createStep<{ shipped: boolean }>({
	id: "ship",
	name: "Ship",
	execute: async (ctx) => {
		await dispatch(ctx);
	},
	compensate: async (ctx) => {
		await recall(ctx);
	},
});

const guard = createConditionalStep<{ ready: boolean }>({
	id: "maybe-ship",
	name: "Maybe ship",
	condition: (ctx) => ctx.ready,
	onTrue: ship,
});

await guard.run({ ready: true });
// Later, on outer-step rollback:
await guard.compensate({ ready: true });

API Reference

  • ConditionalStepOptions<TCtx> — { id; name; condition; onTrue; onFalse?; clock?; }.
  • ConditionalStep<TCtx> —
    • trueBranch, falseBranch?, lastBranch?
    • run(context): Promise<StepResult>
    • compensate(context): Promise<void>
  • createConditionalStep<TCtx>(options): ConditionalStep<TCtx>.

Implementation Notes

lastBranch is set before the branch runs so callers that observe the step (e.g. via hooks) can attribute downstream side-effects to the chosen arm. compensate checks both the branch identity and the underlying step's hasCompensation flag, so a guard whose onTrue step has no compensation is a no-op rather than a thrown error.

recordStepExecuted(stepId, "skipped", 0) is emitted on the no-false-branch fallthrough so the metric for skipped steps lines up with what WorkflowEngine emits when a top-level Step.shouldExecute returns false.