npm packageMIT License

symflow

A Symfony-compatible workflow engine for TypeScript and Node.js. Run state machines and Petri nets anywhere JavaScript runs — no PHP required.

npm install symflow

Everything from Symfony's Workflow, in TypeScript

Symfony-Compatible Runtime

State machines and Petri net workflows with the same semantics as Symfony's Workflow component. Markings, transitions, guards, and events — all in TypeScript.

Event System

Events fire in Symfony's exact order: guard, leave, transition, enter, entered, completed, announce. Subscribe to any event with typed listeners.

Pluggable Guards

Attach guard expressions to transitions and provide your own evaluator. Integrate role checks, feature flags, or any custom authorization logic.

AND / OR Patterns

Petri net support with AND-split (parallel fork), AND-join (synchronization), OR-split (exclusive choice), and XOR patterns for state machines.

Subject-Driven API

Mirrors Symfony's Workflow service. Pass your entity to can() and apply() — the marking is read from and written back automatically via marking stores.

YAML / JSON / TypeScript

Round-trip import and export for all formats. Import existing Symfony configs including !php/const and !php/enum tags. Generate typed TypeScript modules.

!php/const & !php/enum

Symfony YAML configs using PHP constants and backed enums are resolved automatically. App\Enum\Status::Active becomes "Active".

Validation & Analysis

Catch unreachable places, dead transitions, and orphan nodes before runtime. Analyze patterns to understand your workflow's structure.

Standalone Engine

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}`);
});

Subject-Driven API

Like Symfony — pass your entity, the marking is managed for you.

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 only what you need

ImportContentsExtra deps
symflow/engineWorkflowEngine, validateDefinition, analyzeWorkflownone
symflow/subjectWorkflow<T>, createWorkflow, marking storesnone
symflow/yamlSymfony YAML import/export, !php/const, !php/enumjs-yaml
symflow/jsonJSON import/exportnone
symflow/typescriptTypeScript codegennone
symflow/react-flowReact Flow graph utilities@xyflow/react

Symfony Parity

WorkflowDefinition
Marking
Transition
can()
apply()
getEnabledTransitions()
guard events
leave events
transition events
enter / entered
completed / announce
state_machine
workflow (Petri net)
property marking store
method marking store
!php/const
!php/enum
YAML import/export

Design visually, run anywhere

Build your workflow in SymFlowBuilder, export it, and run it with symflow in Node.js, serverless functions, or the browser.