Integration with Kingly

For anything serious, you do want to use yEd to create Kingly state machines. As simple and minimal as Kingly API strives to be, crafting and maintaining a large state machine by hand can involve repetitive tasks, tasks that a textual encoding supports poorly (e.g., refactoring transitions). Modeling is an iterative process and you want fast iterations. You want tooling that supports that. So what tools do you have at your disposal?

You have a professional, intuitive graph editor. You can model state machines by drawing them in that editor. When done, you save the graph in the native yEd .graphml format. To turn the drawing into an actual machine that you can run in a program, you then have two options:

Why two separate CLIs? How do they differ? yed2kingly fits well with development workflows; produces files that are easier to debug; and allows using the Kingly’s devtool extension. On the other hand, the slim command has optimizations that makes it more fit to production; it produces files with a smaller size (most machines you will write will fit between 0.5 and 2.5KB). Both CLIs will produce JavaScript files that can be imported/required by other modules of the application. Those files export factories that create Kingly machines.

This section will focus on the yed2kingly command. We start by reviewing in detail the end-to-end implementation workflow that the command supports. We then explain how to install and use the CLI.

From a drawing to a JavaScript function

A Kingly state machine computes outputs as a result of being passed an input — we will often say that the machine processes or receives an event. The high-level specifications of the machine computation can be represented with a graph. Nodes in the graph correspond to control states of the machine. Edges connecting two nodes represent transitions between control states of the machine.

We said specifications because the drawing let us know how the machine will compute in response to an input depending on the state it is in. We said high-level specifications because the drawing does not allow the actual computing of a machine response — it is a drawing. Let’s look at a simple such drawing from the tutorials:

simple machine

This machine drawing tells us that when processing a click input, the modeled JavaScript machine should trigger the incrementing of a counter, and some rendering. It does not tell us how exactly that works. To turn the drawing into an actual JavaScript function, you need to provide the missing JavaScript objects:

The .graphml file contains the following pieces of information:

The yed2kingly CLI extracts the pieces of information contained in the .graphml file that it receives as a parameter and produces two almost identical JavaScript files. One is destined to be used in a browser context (.js file), the other in a Node.js context (.cjs file that can be required but not imported).

The produced JavaScript files import/require the Kingly library, and export a machine factory that can be used by other modules to construct the actual JavaScript machine we seek. The machine factory must be passed the following pieces of information that are missing from the graph:

You are invited to review the Password meter tutorial for a guided example of turning a machine drawing into a JavaScript machine with yed2kingly.

Get started

If you haven’t yet installed the yEd editor, please do so by following the instructions here.

To use yed2kingly in the shell terminal, you will need to install the package globally:

npm install -g yed2kingly

Usage

yed2kingly filename.graphml

Running the converter produces two files, targeted at consumption in a browser and Node.js environment. Assuming the file src/graphs/file.graphml is passed to yed2kingly, the following two files are created: src/graphs/file.graphml.fsm.js, and src/graphs/file.graphml.fsm.cjs.

Examples

There are plenty of graph examples in the test directory. One such example involving compound states and history pseudo-states is as follows:

example of yed graph with history pseudo-state and compound state

Running the yed2kingly script produces a JavaScript file that exports a createStateMachineFromGraph machine factory:

import { createStateMachine } from "kingly";

// Copy-paste help
// For debugging purposes, guards and actions functions should all have a name
// Using natural language sentences for labels in the graph is valid
// guard and action functions name still follow JavaScript rules though
// -----Guards------
/**
 * @param {E} extendedState
 * @param {D} eventData
 * @param {X} settings
 * @returns Boolean
 */
// const guards = {
//   "can move && won": function (){},
//   "can move && !won": function (){},
//   "white piece": function (){},
//   "black piece": function (){},
//   ">0 moves": function (){},
// };
// -----Actions------
/**
 * @param {E} extendedState
 * @param {D} eventData
 * @param {X} settings
 * @returns {{updates: U[], outputs: O[]}}
 * (such that updateState:: E -> U[] -> E)
 */
// const actions = {
//   "update clock": function (){},
//   "cancel timer": function (){},
//   "Move": function (){},
//   "restart timer": function (){},
//   "Undo": function (){},
//   "reset and start clock": function (){},
// };
// ----------------

...

var events = ["clock ticked", "paused", "click", "resume", "Undo"];

...

function createStateMachineFromGraph(fsmDefForCompile, settings) {
  var updateState = fsmDefForCompile.updateState;
  var initialExtendedState = fsmDefForCompile.initialExtendedState;

  var transitions = getKinglyTransitions({
    actionFactories: fsmDefForCompile.actionFactories,
    guards: fsmDefForCompile.guards,
  });

  var fsm = createStateMachine(
    {
      updateState,
      initialExtendedState,
      states,
      events,
      transitions,
    },
    settings
  );

  return fsm;
}

export { createStateMachineFromGraph, ... };

The exported createStateMachineFromGraph factory can then be used in a regular JavaScript file as follows:

    import {createStateMachineFromGraph} from 'tests/graphs/hierarchy-history-H.graphml.fsm.js';
(...)

    const guards = ...;
    const actionFactories = {
     "update clock": ...,
     "cancel timer": ...,
     "Move": ...,
     "restart timer": ...,
     "Undo": ...,
     "reset and start clock": ...,
    };
    const fsmDef = {
      // NOTE: updateState and initialExtendedState are provided by the user
      updateState,
      initialExtendedState,
      guards,
      actionFactories
    };
    const fsm = createStateMachineFromGraph(fsmDef, settings);

Tips

Troubleshooting

The script will exit with an error code whenever:

Feedback welcome!

Kingly tooling exist to address pain points from real use cases, and drive your productivity up. Your feedback is welcome and may result in the improvement or extension of the existing set of tools. If there is anything you feel should be addressed and is not, or should be addressed differently, get in touch by opening an issue on Github.