Simply Stated Docs

Take a look under the hood

So what does
Simply Stated
actually do?

Statecharts are a great way to represent the logic for your app (and lots of other things too). But there just isn't enough tooling available to really achieve the full promise of this structure. Simply Stated is here to help change that.

One of the amazing things about statecharts is that they're declarative. It's really hard to derive anything meaningful from standard javascript syntax (compiler writing is hard!) but we compute things from json data all the time.

Simply Stated takes advantage of the fact that our statecharts are data to generate all kinds of things based on the structure of your logic.

Starting with simple-as-can-be analytics.

You:

  1. Register your machines with one command.
  2. Add one wrapper function to your existing XState state machines.
  3. Get back to solving your business problems.

Then, we provide full, transition-by-transition analytics and debugging for every step every user took throughout your app.

What did we miss? Ask our developers and we'll get back to you right away!

Scroll down to learn more about statecharts or check out this great intro from our friends at XState.

Start building software that works

Simple and easy, Simply Stated means you do less work and get better software.

You're on the list!
Oops! Something went wrong.

What the heck is a
statechart?

Think about your most recent software project...

We'll venture a guess that, if we asked you to explain the logic to us, you'd start drawing some boxes with arrows between them. "First, we ask the user for their name, then..." The cool thing about this flowchart-style of communication is that it's not just us programmers who can understand and convey logic this way--show the same diagram to your product manager, your designer, your marketing person, your CEO, they'll all have a pretty good idea of what you're talking about.

That is the foundation on which statecharts are built. They are a visual formalism for complex systems (David Harel's original paper).

Visual - Statecharts, in essence, consist of boxes--representing states--connected by arrows--representing transitions between states. One beautiful aspect of that representation is that boxes and arrows are a fantastic visual aid, allowing most people to quickly and directly grasp the logic of the flow in question. At the same time, boxes and arrows are just graphs, which are easily represented, processed, and traversed by machines. This dual suitability--for humans and machines--is critical for building understandable, correct systems.

Formalism - Arbitrary boxes and arrows lack sufficient structure to ensure that the resulting system has basic well-formedness guarantees or that the model will be consistently interpreted by different people and machines. Additionally, without allowing for hierarchy within states, any such machine would suffer from an exponential explosion in the number of boxes (states) it would need to account for. So statecharts add a bit of structure on top of the base boxes and arrows. We'll get into what that structure is in a minute.

Complex systems - We talk a lot about scalability in our industry but we would do well to focus more on how well our systems scale as requirements increase in complexity. A one-file monolith of imperative code does an ok job up until some time around the 4th time requirements change slightly. Conversely, it is straightforward to create a simple statechart and continues to be straightforward as you change its logic and expand its capabilities. Statecharts were designed to work great for simple systems, support you as you evolve your simple system into a complex system, and work great for really complex systems (like spaceships and guidance control modules).

So how is a statechart structured?

Statecharts consist of (possibly hierarchical) states, context, transitions, guards, actions, and services.

States

  • States are finite and named. The blog editor app might have "compose", "preview", and "browse" states, corresponding to views for creating or editing a blog post, previewing a blog post, and browsing your blog posts.
  • States may be nested. If a state has sub-states, it will specify which sub-state is entered when the machine transitions to the parent state. The "preview" state might have substates "laptop-size", "tablet-size", and "phone-size" for the different screen sizes you might want to preview your blog post in.
  • Nested states may be exclusive or parallel. If exclusive, a machine may only be "in" one child state at any time. If parallel, the machine will be in all of the child states simultaneously. The "compose" state might have substates for "bold-text" and "underline-text", where each has a substate "on" and "off." The machine can then be in any of these substates of compose: bold-text off, underline-text off; bold-text off, underline-text on; bold-text on, underline-text off; bold-text on, underline-text on.
  • States can specify actions to run on entry and exit (see actions, below).
  • States can specify a service to run only while the machine is in that state.
  • One state is annotated as the initial state and states may be annotated as a "final" state. When a machine transitions to a final state, it stops executing and may produce a result.

Context

  • States are finite but some data that our machine needs to deal with is infinite (or at least unknown when we're building the machine). Context stores arbitrary data within an instance of a machine. We only allow updates to context through declarative effects, which ensures that our statecharts are statically analyzable. In our blog editor app, we would add a "content" property of type string on our context and, from the "compose" state, we would declare an effect to update context.content with the content property of a "content-updated" event (see the description of events in the transitions section, below).

Transitions

  • Transitions allow a machine to move from one state to another in response to events.
  • Events are named and may contain additional data beyond just the name. Each named event may have one schema for the additional data it carries.
  • Multiple transitions may be defined for the same state in response to the same event if some of those transitions have a guard (see guards, below). The first passing transition will be taken.
  • Transitions may define actions that should be executed whenever the transition is taken (see actions, below).
  • Transitions may occur automatically, not in response to any event. This can be useful in combination with guards (see guards, below) or if the transition is defined with a delay. A transition from some state, S, defined with a delay will occur the specified amount of time after the state, S, is entered. This can be useful in combination with services, for timeouts, for example (see services, below).
  • In our blog app, We would have an event for "compose-new-post", "preview-post", and "browse-posts" and transitions defined on our top-level machine to move to the "compose", "preview", or "browse" states respectively whenever the corresponding event is received.

Guards

  • Guards are predicates, functions that take the current context and event and return true or false.
  • Transitions can be "guarded" to only allow the transition to occur if the guard passes.
  • In our blog app, the transition from "compose" to "browse" may be guarded to ensure that context.saved || context.discard so that we don't let the user lose the blog post they were in the middle of writing.

Actions

  • Actions define effects that may be run in response to entering or exiting a state or whenever a transition is taken.
  • Actions can modify context, log a message, spawn a child machine, or initiate a fire-and-forget http call. Unlike services, actions cannot access the response of any call they make (see services, below).
  • In our blog app, we could add a transition from the "compose" state back to itself in response to the "content-updated" event with an action that set the context.content property to event.content.

Services

  • Services are effects that initiate some external operation and can respond to events that that operation generates (most typically, a response or an error).
  • Because services initiate an operation and then respond to events, they take time and, therefore, must be associated with a state (state transitions are "instantaneous" in the model and the machine must be in a particular state at any point in time). Therefore, states can be annotated to invoke services whenever the state is entered and, if the machine transitions out of that state before the service completes, the service will be stopped.
  • Transitions can be defined in response to events corresponding to the service completing, returning an error, or generating an event.
  • In our blog app, the "compose" state may have a "save-status" parallel sub-state. The "save-status" state will have "dirty" (initial), "saved", and "failed" sub-states. The "dirty" state will invoke a service that persists the context.content to some backend datastore and, on a success response, will transition to the "saved" substate or, on an error response, will transition to a "failed" substate.

Can statecharts communicate?

Funny you should ask ;) Yes, they can. Machine instances can spawn other machine instances either in an action or as a service. When spawned in an action, the reference to the machine can be stored in context and events can be sent to it using that reference. When spawned as a service, events can be sent to it and, if the child machine reaches a final state, it triggers the response event in the parent. Every machine has 0 or 1 parent machines and can send messages to its parent.

Try State Backed's forever free plan!
You're <5 mins away from deploying your backend.

You're on the list!
Oops! Something went wrong.
Try for free
Build invincible workflows and live backends in minutes with simple state machines