Rxova
Skip to content

1.0.0-rc.2
Your wizard became a graph

Journey is a flow runtime for product steps that branch, skip, retry, recover and resume. Start with an array of step names, graduate to a transition graph when the flow earns it, and keep the navigation rules in one place instead of scattered across click handlers.

One import to a running machine

No provider to mount, no store to configure, no scheduler to reason about. Define the steps, define how they connect, and start it.

Install

npm i @rxova/journey-core

Use

import { createLinearJourney } from "@rxova/journey-core";
const machine = createLinearJourney({
context: { name: "" },
steps: ["account", "review"]
});
await machine.navigate.goToNextStep();

See for yourself

This is not a diagram. Drive it.

A real machine from @rxova/journey-core, running in this page with no framework around it. Change the plan, walk the flow, then step back through the timeline and watch the guard re-decide.

  1. plan
  2. account
  3. payment
  4. review

Snapshot

currentStepId
plan
status
running
context.plan
free

Timelinehistory.timeline

  1. plan

Pick Pro and the flow routes through the payment step. Pick Free and it skips straight to review. Step back, change your mind, and the guard is re-evaluated on the way forward.

Three modes, one field

Start as a list. Become a graph.

The transitions field is the only thing that changes as a flow grows. There is no rewrite between these three, and no new runtime concepts to learn on the way.

  1. Linear

    A fixed sequence. Use the array shorthand when every step just goes to the next one.

    steps: ["account", "details", "payment", "review"];
  2. Graph

    Branching, retries and conditional routing. Keyed by step, then by event, matched in order.

    steps: {
    login: {
    on: {
    submit: [
    { to: "admin", when: ({ context }) => context.role === "admin" },
    { to: "dashboard" }
    ];
    }
    }
    }

Why this one

Explicit enough to model real flows, small enough to stay practical

Journey sits between step arrays that collapse the moment a flow branches and general statechart engines whose modelling surface is wider than product journeys need.

  • Typed step IDs

    Invalid step names, events and transition targets are compile errors, not runtime bugs. The types are checked where the flow is authored.

  • Timeline history

    The runtime keeps the realized path, so goToPreviousStep() and goToLastVisitedStep() are deterministic instead of guesswork.

  • Async guards

    Loading, failure, timeout and retry are part of flow behaviour. Check with async when, and read the phase off the snapshot.

  • Plugins, not baggage

    Persistence, analytics, replay and execution paths extend the machine without adding to the base cost.

  • Zero dependencies

    Nothing to inherit and nothing to audit. Tree-shakeable, so you ship the parts of it you actually reach for.

  • Framework-agnostic core

    The runtime is vanilla TypeScript. React bindings are a thin separate package; Vue, Svelte and plain TS need no shim at all.

Proof

The numbers, and where to check them

Every figure below is read out of the thing that enforces it, at the moment this page is built. The bundle sizes are measured by running size-limit against each package's built output — brotli-compressed, dependencies included — not restated from a changelog.

  • 0

    Runtime dependencies

    Across core, the React bindings and the DevTools bridge

  • ≤ ?

    Core, brotlied

    The whole framework-agnostic runtime, measured by size-limit

  • ≤ ?

    React bindings

    Provider, step renderer and typed hooks

  • 3.02 kB

    DevTools bridge

    Opt-in — nothing reaches your bundle unless you attach it

  • 95%

    Coverage floor, per file

    Statements, branches, functions and lines — enforced by vitest in CI

  • 3

    Published packages

    Take only what you need — each one is independently installable

  • MIT

    Licensed, every package

    Open source, developed in the open at github.com/rxova/journey

DevTools

Watch the runtime tell the truth

A native Chrome extension that reads the machine while your app runs: the realized timeline, the context behind the current step, the transition each event matched, and a command panel to send events back in.

  • Rxova Journey DevTools: Timeline and state

    Timeline and state

    The realized timeline, the current step, and the context behind it.

  • Rxova Journey DevTools: Transition log

    Transition log

    Every event, the transition it matched, and the diff it committed.

  • Rxova Journey DevTools: Live commands

    Live commands

    Send events into the running machine and watch the snapshot answer.

Get it from the Chrome Web Store

React

Thin bindings, not a second state system

The React package exposes the runtime and gets out of the way. Flow rules stay in the definition; components render the active step.

import { createLinearJourney } from "@rxova/journey-react";
const signup = createLinearJourney({
name: "signup",
context: { email: "" },
steps: ["start", "review"]
});
const App = () => (
<signup.Provider views={{ start: <Start />, review: <Review /> }}>
<signup.StepRenderer />
</signup.Provider>
);

Need one runtime per request, route boundary or owned component boundary? Use useJourney instead of creating the machine at module scope.