PraxisJS

@praxisjs/fsm

Changelog for @praxisjs/fsm — finite state machines with @StateMachine and @Transition.

@praxisjs/fsm

2.1.0

Guards and per-transition actions on @StateMachine.

Transitions can now declare a guard and/or action instead of a plain target string:

states: {
  editing: {
    on: {
      SUBMIT: { target: 'submitting', guard: () => formIsValid, action: () => clearErrors() },
    },
  },
}

If the guard returns false, send() returns false and nothing runs — no onExit, no state change, no history entry. can(event) also evaluates the guard.

Instance access in guards. Pass the component class as the third generic @StateMachine<S, E, T> — guards and actions then receive the instance as their first argument:

@StateMachine<State, Event, MyForm>({
  initial: 'editing',
  states: {
    editing: {
      on: {
        SUBMIT: {
          target: 'submitting',
          guard:  (self) => self.name.trim().length > 0,
          action: (self) => self.clearErrors(),
        },
      },
    },
  },
})
machine!: Machine<State, Event>

Guards read live field values — they are re-evaluated on every send() call, not at definition time.

onEnter / onExit context. Both hooks now receive an optional { event, from } / { event, to } argument. Zero-argument callbacks remain valid — no migration needed.

New export: TransitionTarget<S, T> type for the object form of a transition config.

2.0.0 — Breaking

@StateMachine converted from a class decorator to a field decorator, matching the PraxisJS decorator-first convention (@State, @Compose, @Collection).

Before:

@StateMachine<State, Event>({ initial: 'idle', states: { ... } }, 'machine')
@Component()
class MyComp extends StatefulComponent {
  declare machine: Machine<State, Event>
}

After:

@Component()
class MyComp extends StatefulComponent {
  @StateMachine<State, Event>({ initial: 'idle', states: { ... } })
  machine!: Machine<State, Event>
}

The field name is now the machine property reference — no propertyKey second argument needed. The TypeScript type is inferred from the field declaration. @Transition(fieldName, event) works unchanged.

Multiple machines on one class are now declared as multiple fields.

1.0.12

Updated dependencies — @praxisjs/[email protected], @praxisjs/[email protected].

1.0.11

Updated dependencies — @praxisjs/[email protected].

1.0.10

Updated dependencies — @praxisjs/[email protected].

1.0.9

Updated dependencies — @praxisjs/[email protected], @praxisjs/[email protected].

1.0.8

Updated dependencies — @praxisjs/[email protected], @praxisjs/[email protected].

1.0.7

Updated dependencies — @praxisjs/[email protected], @praxisjs/[email protected].

1.0.6

Updated dependencies — @praxisjs/[email protected], @praxisjs/[email protected].

1.0.5

Updated dependencies — @praxisjs/[email protected].

1.0.4

Updated dependencies — @praxisjs/[email protected], @praxisjs/[email protected].

1.0.3

Updated dependencies — @praxisjs/[email protected].

1.0.2

Updated dependencies — @praxisjs/[email protected].

1.0.1

Updated dependencies — @praxisjs/[email protected], @praxisjs/[email protected], @praxisjs/[email protected].

1.0.0 — Breaking

createMachine() removed. Use @StateMachine and @Transition decorators directly:

// before: const machine = createMachine({ initial, states })
// after:
@StateMachine({ initial: 'idle', states: { ... } })
@Component()
class MyComponent extends StatefulComponent {
  @Transition('machine', 'START')
  start() { ... }
}

@StateMachine creates a per-instance machine on this.machine (configurable). @Transition wraps a method so it only runs when the named event causes a valid transition.

0.2.0

Migrated to TC39 decorator context API.

0.1.0

Initial beta release.

On this page