1Getting started

1.1Choosing the database

XJog ships with two persistence adapters, and an additional mock adapter for testing purposes. Writing new persistence adapters is not difficult either, should it be necessary. You will find a comparison of the adapters in table 1. PostgreSQL is recommended for production use.

Note that in addition to the regular persistence adapters, there are delta persistence adapters. Recording deltas is optional, and a separate adapter makes it possible to store delta data separately.

Adapter Connection pooling Notifications Use cases
PostgreSQL Yes Listeners Server-side, production
SQLite Yes Polling Local applications, experimenting
Mock No Callbacks Unit testing, development


XJog is available as an npm package xjog at the npm registry. In addition to the XJog itself, you will need to install some additional packages depending on which database you are planning to use.

Database Required packages Required for deltas
  • pg@^8.7.3
  • node-pg-migrate@^6.2.1
  • pg-listen@^1.7.0
  • rfc6902@5.0.1
  • sqlite@^4.0.23
  • sqlite3@^5.0.3
  • rfc6902@5.0.1
Mock (None)
  • rfc6902@5.0.1

Listing 1 shows how to install XJog to your Node project translated to shell commands.

Listing  1Installing required packages
# PostgreSQL yarn add xjog \ pg@^8.7.3 node-pg-migrate@^6.2.1 \ pg-listen@^1.7.0 rfc6902@5.0.1 npm install yarn add xjog \ pg@^8.7.3 node-pg-migrate@^6.2.1 \ pg-listen@^1.7.0 rfc6902@5.0.1 # SQLite yarn add xjog \ sqlite@^4.0.23 sqlite3@^5.0.3 \ rfc6902@5.0.1 npm install xjog \ sqlite@^4.0.23 sqlite3@^5.0.3 \ rfc6902@5.0.1

1.3Hello, world

Let's create a simple machine and register it with XJog. Then we will run until we reach a final state and then clean up.

Let's start with creating the instance (listing 2). We'll be using in-memory SQLite database. This way the example is self-sufficient and ready to run.

Listing  2Instantiating XJog
const persistence = await PostgreSQLPersistenceAdapter.connect(); const xJog = new XJog({ persistence }); await xJog.start();

The next step is to configure a machine using the createMachine function, and to register it with XJog. This machine will determine how the chart will behave. In our example we only have two states, shy and confident. When our chart collects enough courage, it yelps "Hello, world!" and then retires as a confident chart.

Listing  3Registering a machine
const machine = await xJog.registerMachine( createMachine({ id: 'hello world', initial: 'shy', states: { 'shy': { on: { 'get courage': { actions: () => console.log("Hello, world!"), target: 'confident' }, }, }, 'confident': { type: 'final' }, }, }), );

After registering the machine, we have to instantiate a chart (listing 4).

The nomenclature here can be a bit confusing. In XJog, machine defines the behavior (and any bindings), and chart is an instance of that machine. Charts have their own state and lifecycle. At the same time, chart can also refer to a statechart, which is the actual definition of the behavior. What XJog calls charts, correspond to interpreters in XState.

While we're at it, let's send the chart an event and check what the state is after that.

Listing  4Instantiating a chart
const chart = await machine.createChart(); const state = await chart.send('get courage'); assert(state.matches('confident')).toEqual(true);

As a result, when running, we should also see "Hello, world!" printed in the console.

One more thing to add is the cleanup. We'll wrap the whole thing in try-finally to make sure it's called in both successful and failing paths.

Listing  5Cleanup
const persistence = await PostgreSQLPersistenceAdapter.connect(); const xJog = new XJog({ persistence }); await xJog.start(); try { const machine = await xJog.registerMachine( … ); … await xJog.start(); … } finally { await xJog?.shutdown(); }

And there we are!