State machines and Petri nets with Symfony's exact semantics. Markings, transitions, guards, and events — all in TypeScript.
Events fire in Symfony order: guard, leave, transition, enter, entered, completed, announce. Subscribe with typed listeners.
Attach guard expressions to transitions and plug in your own evaluator. Wire up role checks, feature flags, or any custom authorization logic.
Petri-net AND-split (parallel fork), AND-join (synchronization), OR-split (exclusive choice), and XOR patterns for state machines.
Mirrors Symfony's Workflow service. Pass your entity to can() and apply() — marking stores read and write the marking for you.
Round-trip import and export for every format. Read existing Symfony configs (including !php/const and !php/enum) or generate typed TypeScript modules.
Symfony YAML using PHP constants and backed enums is resolved automatically. App\Enum\Status::Active becomes "Active".
Catch unreachable places, dead transitions, and orphan nodes before runtime. Analyze structural patterns at a glance.
Create an engine, validate, apply transitions, listen to events.
import { WorkflowEngine, validateDefinition } from "symflow/engine";
const definition = {
name: "order",
type: "state_machine",
places: [
{ name: "draft" },
{ name: "submitted" },
{ name: "approved" },
{ name: "fulfilled" },
],
transitions: [
{ name: "submit", froms: ["draft"], tos: ["submitted"] },
{ name: "approve", froms: ["submitted"], tos: ["approved"] },
{ name: "fulfill", froms: ["approved"], tos: ["fulfilled"] },
],
initialMarking: ["draft"],
};
// Validate
const { valid, errors } = validateDefinition(definition);
// Run
const engine = new WorkflowEngine(definition);
engine.apply("submit");
engine.getActivePlaces(); // ["submitted"]
// Listen to events
engine.on("entered", (event) => {
console.log(`Entered via ${event.transition.name}`);
});Like Symfony — pass your entity, marking stores handle the rest.
import { createWorkflow, propertyMarkingStore } from "symflow/subject";
interface Order {
id: string;
total: number;
status: string;
}
const workflow = createWorkflow(definition, {
markingStore: propertyMarkingStore("status"),
guardEvaluator: (expr, { subject }) => {
if (expr === "subject.total < 10000") {
return subject.total < 10000;
}
return true;
},
});
const order = { id: "ord_1", total: 500, status: "draft" };
workflow.apply(order, "submit");
console.log(order.status); // "submitted"
workflow.on("entered", (event) => {
console.log(event.subject.id, event.transition.name);
});| Import | Contents | Extra deps |
|---|---|---|
| symflow/engine | WorkflowEngine, validateDefinition, analyzeWorkflow | none |
| symflow/subject | Workflow<T>, createWorkflow, marking stores | none |
| symflow/yaml | Symfony YAML import/export, !php/const, !php/enum | js-yaml |
| symflow/json | JSON import/export | none |
| symflow/typescript | TypeScript codegen | none |
| symflow/react-flow | React Flow graph utilities | @xyflow/react |