PackageTrack

npm · #4216

@xstate/fsm

2.1.0statelyai/xstate

XState for finite state machines

Release timeline

28 releases since 2019
2020202120222023202420252026

Releases

  1. 3.0.0-beta.311 Jul 2023pre-release

    Nothing published for this version

  2. 3.0.0-beta.29 Apr 2023pre-release

    Nothing published for this version

  3. 3.0.0-alpha.17 Apr 2023pre-release

    Nothing published for this version

  4. 3.0.0-alpha.07 Jan 2023pre-release
    Release notes

    Patch Changes

    Open source →
  5. 2.1.021 Jun 2023
    Release notes

    Minor Changes

    • #5020 e974797b0 Thanks @with-heart! - Added the EventFromStore utility type which extracts the type of events from a store:

      import { createStore, type EventFromStore } from '@xstate/store';
      
      const store = createStore(
        { count: 0 },
        {
          add: (context, event: { addend: number }) => ({
            count: context.count + event.addend
          }),
          multiply: (context, event: { multiplier: number }) => ({
            count: context.count * event.multiplier
          })
        }
      );
      
      type StoreEvent = EventFromStore<typeof store>;
      //   ^? { type: 'add'; addend: number } | { type: 'multiply'; multiplier: number }
      

      EventFromStore allows us to create our own utility types which operate on a store's event types.

      For example, we could create a type EventByType which extracts the specific type of store event where Type matches the event's type property:

      import { type EventFromStore, type Store } from '@xstate/store';
      
      /**
       * Extract the event where `Type` matches the event's `type` from the given
       * `Store`.
       */
      type EventByType<
        TStore extends Store<any, any>,
        // creates a type-safe relationship between `Type` and the `type` keys of the
        // store's events
        Type extends EventFromStore<TStore>['type']
      > = Extract<EventFromStore<TStore>, { type: Type }>;
      

      Here's how the type works with the store we defined in the first example:

      // we get autocomplete listing the store's event `type` values on the second
      // type parameter
      type AddEvent = EventByType<typeof store, 'add'>;
      //   ^? { type: 'add'; addend: number }
      
      type MultiplyEvent = EventByType<typeof store, 'multiply'>;
      //   ^? { type: 'multiply'; multiplier: number }
      
      // the second type parameter is type-safe, meaning we get a type error if the
      // value isn't a valid event `type`
      type DivideEvent = EventByType<typeof store, 'divide'>;
      // Type '"divide"' does not satisfy the constraint '"add" | "multiply"'.ts(2344)
      

      Building on that, we could create a type EventInputByType to extract a specific event's "input" type (the event type without the type property):

      import { type EventFromStore, type Store } from '@xstate/store';
      
      /**
       * Extract a specific store event's "input" type (the event type without the
       * `type` property).
       */
      type EventInputByType<
        TStore extends Store<any, any>,
        Type extends EventFromStore<TStore>['type']
      > = Omit<EventByType<TStore, Type>, 'type'>;
      

      And here's how EventInputByType works with our example store:

      type AddInput = EventInputByType<typeof store, 'add'>;
      //   ^? { addend: number }
      
      type MultiplyInput = EventInputByType<typeof store, 'multiply'>;
      //   ^? { multiplier: number }
      
      type DivideInput = EventInputByType<typeof store, 'divide'>;
      // Type '"divide"' does not satisfy the constraint '"add" | "multiply"'.ts(2344)
      

      Putting it all together, we can use EventInputByType to create a type-safe transition function for each of our store's defined events:

      import { createStore, type EventFromStore, type Store } from '@xstate/store';
      
      /**
       * Extract the event where `Type` matches the event's `type` from the given
       * `Store`.
       */
      type EventByType<
        TStore extends Store<any, any>,
        Type extends EventFromStore<TStore>['type']
      > = Extract<EventFromStore<TStore>, { type: Type }>;
      
      /**
       * Extract a specific store event's "input" type (the event type without the
       * `type` property).
       */
      type EventInputByType<
        TStore extends Store<any, any>,
        Type extends EventFromStore<TStore>['type']
      > = Omit<EventByType<TStore, Type>, 'type'>;
      
      const store = createStore(
        { count: 0 },
        {
          add: (context, event: { addend: number }) => ({
            count: context.count + event.addend
          }),
          multiply: (context, event: { multiplier: number }) => ({
            count: context.count * event.multiplier
          })
        }
      );
      
      const add = (input: EventInputByType<typeof store, 'add'>) =>
        store.send({ type: 'add', addend: input.addend });
      
      add({ addend: 1 }); // sends { type: 'add', addend: 1 }
      
      const multiply = (input: EventInputByType<typeof store, 'multiply'>) =>
        store.send({ type: 'multiply', multiplier: input.multiplier });
      
      multiply({ multiplier: 2 }); // sends { type: 'multiply', multiplier: 2 }
      

      Happy typing!

    Open source →
  6. 2.0.16 Jun 2023
    Release notes

    Patch Changes

    Open source →
  7. 2.0.08 Apr 2022
    Release notes4 sources agree

    Major Changes

    • #5512 063416d Thanks @davidkpiano! - Modernize Store v4 package entrypoints.

      Use framework-specific packages such as @xstate/store-react and @xstate/store-solid instead of @xstate/store/react or @xstate/store/solid. The Store packages now publish ESM package entrypoints.

    • #5512 063416d Thanks @davidkpiano! - Add createStoreLogic(...) for reusable store definitions, and support creating stores from logic in framework hooks.

      const counterLogic = createStoreLogic({
        context: (input: { initialCount: number }) => ({
          count: input.initialCount
        }),
        on: {
          inc: (context) => ({ count: context.count + 1 })
        }
      });
      
      const store = useStore(counterLogic, { initialCount: 0 });
      

      If a store logic requires input, the input argument is also required:

      useStore(counterLogic, { initialCount: 0 });
      

      Framework hooks also preserve schema-derived context, event, and emitted event types when creating stores from config objects.

    Minor Changes

    • #5512 063416d Thanks @davidkpiano! - Add reusable atom configs and framework atom-state helpers.

      createAtomConfig(...) creates an inert atom definition that can be instantiated with its createAtom(...) method or React/Preact/Vue/Solid's useAtomState(...). These helpers return the current framework-native value and live atom instance, and also work with existing atom instances.

      const countConfig = createAtomConfig((input: { initialCount: number }) => {
        return input.initialCount;
      });
      
      function Counter() {
        const [count, countAtom] = useAtomState(countConfig, { initialCount: 0 });
      
        return (
          <button onClick={() => countAtom.set((count) => count + 1)}>
            {count}
          </button>
        );
      }
      

    Patch Changes

    Open source →
    Additional notes2 sources agree

    Major Changes

    • #5512 063416d Thanks @davidkpiano! - Modernize Store v4 package entrypoints.

      Use framework-specific packages such as @xstate/store-react and @xstate/store-solid instead of @xstate/store/react or @xstate/store/solid. The Store packages now publish ESM package entrypoints.

    • #5512 063416d Thanks @davidkpiano! - Add createStoreLogic(...) for reusable store definitions, and support creating stores from logic in framework hooks.

      const counterLogic = createStoreLogic({
        context: (input: { initialCount: number }) => ({
          count: input.initialCount
        }),
        on: {
          inc: (context) => ({ count: context.count + 1 })
        }
      });
      
      const store = useStore(counterLogic, { initialCount: 0 });
      

      If a store logic requires input, the input argument is also required:

      useStore(counterLogic, { initialCount: 0 });
      

      Framework hooks also preserve schema-derived context, event, and emitted event types when creating stores from config objects.

    Patch Changes

    Open source →
    Additional notes

    Patch Changes

    Open source →
    Additional notes

    Major Changes

    • #5000 eeadb7121 Thanks @TkDodo! - - Replace use-sync-external-store/shim with useSyncExternalStore from React.
      • Do not memoize getSnapshot in useSyncExternalStore.
      • Implement getServerSnapshot in useSyncExternalStore.
      • Expect store to always be defined in useSelector
      • Update React types to v18 and testing library to v16.
    Open source →
    Additional notes

    Major Changes

    • #4896 7c6e2ea Thanks @davidkpiano! - Test model path generation now has the option to allow duplicate paths by setting allowDuplicatePaths: true:

      const paths = model.getSimplePaths({
        allowDuplicatePaths: true
      });
      // a
      // a -> b
      // a -> b -> c
      // a -> d
      // a -> d -> e
      

      By default, allowDuplicatePaths is set to false:

      const paths = model.getSimplePaths();
      // a -> b -> c
      // a -> d -> e
      
    • #4896 7c6e2ea Thanks @davidkpiano! - The adjacencyMapToArray(…) helper function has been introduced, which converts an adjacency map to an array of { state, event, nextState } objects.

      import { getAdjacencyMap, adjacencyMapToArray } from '@xstate/graph';
      
      const machine = createMachine({
        initial: 'green',
        states: {
          green: {
            on: {
              TIMER: 'yellow'
            }
          },
          yellow: {
            on: {
              TIMER: 'red'
            }
          },
          red: {
            on: {
              TIMER: 'green'
            }
          }
        }
      });
      
      const arr = adjacencyMapToArray(getAdjacencyMap(machine));
      // [
      //   {
      //     "state": {value: "green", ... },
      //     "event": { type: "TIMER" },
      //     "nextState": { value: "yellow", ... }
      //   },
      //   {
      //     "state": {value: "yellow", ... },
      //     "event": { type: "TIMER" },
      //     "nextState": { value: "red", ... }
      //   },
      //   {
      //     "state": {value: "red", ... },
      //     "event": { type: "TIMER" },
      //     "nextState": { value: "green", ... }
      //   },
      //   {
      //     "state": {value: "green", ... },
      //     "event": { type: "TIMER" },
      //     "nextState": { value: "yellow", ... }
      //   },
      // ]
      
    • #4896 7c6e2ea Thanks @davidkpiano! - The traversalLimit option has been renamed to limit:

      model.getShortestPaths({
      - traversalLimit: 100
      + limit: 100
      });
      
    • #4233 3d96d0f95 Thanks @davidkpiano! - Remove getMachineShortestPaths and getMachineSimplePaths

      import {
      - getMachineShortestPaths,
      + getShortestPaths,
      - getMachineSimplePaths,
      + getSimplePaths
      } from '@xstate/graph';
      
      -const paths = getMachineShortestPaths(machine);
      +const paths = getShortestPaths(machine);
      
      -const paths = getMachineSimplePaths(machine);
      +const paths = getSimplePaths(machine);
      
    • #4238 b4f12a517 Thanks @davidkpiano! - The steps in the paths returned from functions like getShortestPaths(...) and getSimplePaths(...) have the following changes:

      • The step.event property now represents the event object that resulted in the transition to the step.state, not the event that comes before the next step.
      • The path.steps array now includes the target path.state as the last step.
        • Note: this means that path.steps always has at least one step.
      • The first step now has the { type: 'xstate.init' } event
    • #4896 7c6e2ea Thanks @davidkpiano! - The createTestMachine(…) function has been removed. Use a normal createMachine(…) or setup(…).createMachine(…) function instead to create machines for path generation.

    • #4896 7c6e2ea Thanks @davidkpiano! - The filter and stopCondition option for path generation has been renamed to stopWhen, which is used to stop path generation when a condition is met. This is a breaking change, but it is a more accurate name for the option.

      const shortestPaths = getShortestPaths(machine, {
        events: [{ type: 'INC' }],
      - filter: (state) => state.context.count < 5
      - stopCondition: (state) => state.context.count < 5
      + stopWhen: (state) => state.context.count === 5
      });
      
    • #4896 7c6e2ea Thanks @davidkpiano! - Path generation now supports input for actor logic:

      const model = createTestModel(
        setup({
          types: {
            input: {} as {
              name: string;
            },
            context: {} as {
              name: string;
            }
          }
        }).createMachine({
          context: (x) => ({
            name: x.input.name
          }),
          initial: 'checking',
          states: {
            checking: {
              always: [
                { guard: (x) => x.context.name.length > 3, target: 'longName' },
                { target: 'shortName' }
              ]
            },
            longName: {},
            shortName: {}
          }
        })
      );
      
      const path1 = model.getShortestPaths({
        input: { name: 'ed' }
      });
      
      expect(path1[0].steps.map((s) => s.state.value)).toEqual(['shortName']);
      
      const path2 = model.getShortestPaths({
        input: { name: 'edward' }
      });
      
      expect(path2[0].steps.map((s) => s.state.value)).toEqual(['longName']);
      
    • #4896 7c6e2ea Thanks @davidkpiano! - The test model "sync" methods have been removed, including:

      • testModel.testPathSync(…)
      • testModel.testStateSync(…)
      • testPath.testSync(…)

      The async methods should always be used instead.

      model.getShortestPaths().forEach(async (path) => {
      - model.testPathSync(path, {
      + await model.testPath(path, {
          states: { /* ... */ },
          events: { /* ... */ },
        });
      })
      

    Minor Changes

    • #3727 5fb3c683d Thanks @Andarist! - exports field has been added to the package.json manifest. It limits what files can be imported from a package - it's no longer possible to import from files that are not considered to be a part of the public API.

    Patch Changes

    • #4896 7c6e2ea Thanks @davidkpiano! - The @xstate/graph package now includes everything from @xstate/test.

    • #4308 af032db12 Thanks @davidkpiano! - Traversing state machines that have delayed transitions will now work as expected:

      const machine = createMachine({
        initial: 'a',
        states: {
          a: {
            after: {
              1000: 'b'
            }
          },
          b: {}
        }
      });
      
      const paths = getShortestPaths(machine); // works
      
    Open source →
  8. 1.6.522 Feb 2022

    Nothing published for this version

  9. 1.6.427 Jan 2022

    Nothing published for this version

  10. 1.6.330 Dec 2021

    Nothing published for this version

  11. 1.6.23 Sept 2021

    Nothing published for this version

  12. 1.6.117 May 2021

    Nothing published for this version

  13. 1.6.05 Feb 2021

    Nothing published for this version

  14. 1.5.226 Nov 2020

    Nothing published for this version

  15. 1.5.127 Aug 2020

    Nothing published for this version

  16. 1.5.017 Aug 2020

    Nothing published for this version

  17. 1.4.015 Apr 2020

    Nothing published for this version

  18. 1.3.024 Jan 2020

    Nothing published for this version

  19. 1.2.07 Jan 2020

    Nothing published for this version

  20. 1.1.030 Oct 2019
    Release notes3 sources agree

    Minor Changes

    Open source →
  21. 1.0.329 Oct 2019
    Release notes

    Patch Changes

    Open source →
  22. 1.0.15 Oct 2019
    Release notes6 sources agree

    Patch Changes

    Open source →
    Additional notes

    Patch Changes

    Open source →
  23. 1.0.04 Oct 2019
    Release notes

    Patch Changes

    Open source →
    Additional notes

    Major Changes

    Patch Changes

    Open source →
    Additional notes

    Major Changes

    Patch Changes

    Open source →
    Additional notes

    Major Changes

    Patch Changes

    Open source →
    Additional notes

    Major Changes

    Patch Changes

    Open source →
    Additional notes

    Major Changes

    Patch Changes

    Open source →
    Additional notes

    Major Changes

    Patch Changes

    Open source →
    Additional notes

    Major Changes

    Open source →
  24. 0.3.021 Sept 2019

    Nothing published for this version

  25. 0.2.115 May 2019
    Release notes

    Patch Changes

    • #5055 ad38c35c37 Thanks @SandroMaglione! - Updated types of useActor, useMachine, and useActorRef to require input when defined inside types/input.

      Previously even when input was defined inside types, useActor, useMachine, and useActorRef would not make the input required:

      const machine = setup({
        types: {
          input: {} as { value: number }
        }
      }).createMachine({});
      
      function App() {
        // Event if `input` is not defined, `useMachine` works at compile time, but risks crashing at runtime
        const _ = useMachine(machine);
        return <></>;
      }
      

      With this change the above code will show a type error, since input is now required:

      const machine = setup({
        types: {
          input: {} as { value: number }
        }
      }).createMachine({});
      
      function App() {
        const _ = useMachine(machine, {
          input: { value: 1 } // Now input is required at compile time!
        });
        return <></>;
      }
      

      This avoids runtime errors when forgetting to pass input when defined inside types.

    Open source →
  26. 0.2.015 May 2019
    Release notes

    Minor Changes

    • #3727 5fb3c68 Thanks @Andarist! - exports field has been added to the package.json manifest. It limits what files can be imported from a package - it's no longer possible to import from files that are not considered to be a part of the public API.

    • #4265 1153b3f Thanks @davidkpiano! - FSM-related functions have been removed.

    • #4748 d73ac8e48 Thanks @Andarist! - The createService(machine) hook has been removed; use the useActorRef(logic) hook instead.

    • #4748 d73ac8e48 Thanks @Andarist! - The fromActorRef(actorRef) has been added. You can use it to get an accessor for reactive snapshot of any existing actorRef.

    • #4748 d73ac8e48 Thanks @Andarist! - The useActor hook accepts an actor logic now and not an existing actorRef. It's used to creating a new instance of an actor and it works just like useMachine used to work (useMachine is now just an alias of useActor).

    Open source →
  27. 0.1.112 May 2019

    Nothing published for this version

  28. 0.1.012 May 2019

    Nothing published for this version