xState as the canonical authoring surface
Statecharts replace flat DAGs. Parallel regions, hierarchical states, history, formal transition semantics, SCXML for cross-engine portability, and a free visualizer (Stately) — all without building any of it ourselves.
Why
WDK's grammar is a flat DAG of awaits.
Each step is an await; control flow is JS itself. That works for linear pipelines and breaks down everywhere else — parallel regions, hierarchical states, history, formal verification. xFlow adopts xState as the canonical inner shape so we get those for free.
What xState gets you
- · Parallel regions (multiple states active simultaneously)
- · Hierarchical states (compound/atomic/final/history)
- · Formal transition semantics (SCXML-aligned)
- · Stately Studio for visual authoring + simulation
- · SCXML serialization for cross-engine handoff
- · An ecosystem of test utilities and inspectors
What xFlow adds
- · Statecharts become content-addressed registry entries
- · Round-trip codec to/from WorkflowDefinition
- · Per-step claim/placement/retry config via
meta.xflow - · Substrate-pluggable execution; no xstate runtime dep
Codec
fromXState — author once, run anywhere.
Take any xstate v5 machine config and produce a WorkflowDefinition the runtime can drive.
import { fromXState } from "@decoperations/xflow-xstate"
const machine = {
id: "shop.checkout",
initial: "load",
states: {
load: { invoke: { src: "cart.load", onDone: "price" } },
price: { invoke: { src: "cart.price", onDone: "charge" } },
charge: { invoke: { src: "payments.charge", onDone: "done", onError: "review" } },
review: { invoke: { src: "human.review", onDone: "done" } },
done: { type: "final" },
},
}
const workflow = fromXState(machine, { id: "shop.checkout", version: "1.0.0" })
// → WorkflowDefinition with steps + links derived from invoke / onDone / onErrorCodec (inverse)
toXState — round-trip preserves the topology.
The WorkflowDefinition coming out of fromXState round-trips back to a machine config that you can hand to Stately's visualizer or to another engine. SCXML serialization is on the deliverable list (#2).
import { toXState } from "@decoperations/xflow-xstate"
const machine = toXState(workflow)
// Round-trip preserves: step ids, types, link topology,
// and per-step config (claim, placement, retry, sideEffects, timeoutMs)
// via meta.xflow on each state.Per-step config
meta.xflow rides alongside each state.
Claim mode, placement policy, retry policy, side-effect kind, timeout — anything xFlow needs that xstate doesn't model. Standard xstate consumers ignore it; xFlow's codec extracts it.
// Per-step xFlow config rides as meta.xflow on each xstate state.
const machine = {
id: "deploy",
initial: "build",
states: {
build: {
invoke: { src: "ci.build", onDone: "deploy" },
meta: {
xflow: {
claim: { mode: "lease", ttlMs: 30_000 },
retry: { maxAttempts: 3, backoff: { kind: "exponential", initialMs: 200, maxMs: 5000 } },
timeoutMs: 120_000,
},
},
},
deploy: { invoke: { src: "ci.deploy", onDone: "done" } },
done: { type: "final" },
},
}Next