Skip to content

Flattened Traffic Light

A traffic light controller demonstrating flattened hierarchical machines. The controller has three modes (Broken, Working, Maintenance), and the Working mode contains a nested light cycle (Red → Green → Yellow → Red).

  • defineSubmachine - Define a nested machine inline within a parent state
  • flattenMachineDefinition - Convert hierarchical definition to flat states with dot-notation keys
  • createMachineFromFlat - Create a machine from the flattened definition

The flattened approach creates states like Working.Red, Working.Green, Working.Yellow instead of separate parent/child machine instances.

import {
defineStates,
defineMachine,
defineSubmachine,
flattenMachineDefinition,
createMachineFromFlat
} from "matchina";
/**
* Traffic Light Controller using flattened hierarchical machine
*
* Structure:
* - Broken: light is broken
* - Working: contains nested light cycle (Red -> Green -> Yellow -> Red)
* - Maintenance: under maintenance
*/
// Define the nested light cycle as a submachine
const lightCycle = defineSubmachine(
defineStates({
Red: undefined,
Green: undefined,
Yellow: undefined
}),
{
Red: { tick: "Green" },
Green: { tick: "Yellow" },
Yellow: { tick: "Red" },
},
"Red"
);
// Define the controller with Working state containing the light cycle
const controllerDef = defineMachine(
defineStates({
Broken: undefined,
Working: lightCycle,
Maintenance: undefined,
}),
{
Broken: { repair: "Working", maintenance: "Maintenance" },
Working: { break: "Broken", maintenance: "Maintenance" },
Maintenance: { complete: "Working" },
},
"Working"
);
// Flatten and create the machine
const flatDef = flattenMachineDefinition(controllerDef);
export function createTrafficLightMachine() {
return createMachineFromFlat(flatDef);
}
// Helper to parse hierarchical state key
export function parseStateKey(key: string) {
const parts = key.split(".");
return {
parent: parts[0],
child: parts[1] || null,
full: key,
};
}
  1. Define the child machine using defineSubmachine:

    const lightCycle = defineSubmachine(
    defineStates({ Red: undefined, Green: undefined, Yellow: undefined }),
    { Red: { tick: "Green" }, Green: { tick: "Yellow" }, Yellow: { tick: "Red" } },
    "Red"
    );
  2. Use it as a state factory in the parent:

    const controllerDef = defineMachine(
    defineStates({
    Broken: undefined,
    Working: lightCycle, // Nested machine here
    Maintenance: undefined,
    }),
    { /* transitions */ },
    "Working"
    );
  3. Flatten and create:

    const flatDef = flattenMachineDefinition(controllerDef);
    const machine = createMachineFromFlat(flatDef);

The resulting machine has states: Broken, Working.Red, Working.Green, Working.Yellow, Maintenance.

  • Single machine instance - No parent/child coordination needed
  • Simple state access - Just check state.key (e.g., "Working.Red")
  • All transitions visible - Both parent and child transitions work seamlessly
  • Easy serialization - State is just a string key