Getting Started
This guide is for @rxova/journey-core only (headless, framework-agnostic).
Use it when you want to run journey logic in Node services, tests, CLIs, or your own UI layer.
1. Install
npm i @rxova/journey-core
2. Define Your Journey Types
type StepId = "start" | "details" | "review" | "confirmExit";
type Event = "next" | "back" | "close" | "submit";
type Context = {
name: string;
dirty: boolean;
includeDetails: boolean;
};
3. Create a Journey Definition
import { HISTORY_TARGET, JOURNEY_TERMINAL, type JourneyDefinition } from "@rxova/journey-core";
const journey: JourneyDefinition<Context, StepId, Event> = {
initial: "start",
context: {
name: "",
dirty: false,
includeDetails: true
},
steps: {
start: {},
details: {},
review: {},
confirmExit: {}
},
transitions: [
{ from: "start", event: "next", to: "details" },
{
from: "details",
event: "next",
to: "review",
when: ({ context }) => context.includeDetails
},
{ from: "*", event: "back", to: HISTORY_TARGET },
{
from: "*",
event: "close",
to: "confirmExit",
when: ({ context }) => context.dirty
},
{
from: "*",
event: "close",
to: JOURNEY_TERMINAL.CLOSE,
when: ({ context }) => !context.dirty
},
{ from: "review", event: "submit", to: JOURNEY_TERMINAL.COMPLETE }
]
};
4. Create the Machine
import { createJourneyMachine } from "@rxova/journey-core";
const machine = createJourneyMachine(journey);
5. Send Events and Read Snapshot
await machine.send({ type: "next" });
await machine.send({ type: "next" });
const snapshot = machine.getSnapshot();
console.log(snapshot.current); // "review"
console.log(snapshot.history); // ["start", "details"]
console.log(snapshot.visited); // ["start", "details", "review"]
console.log(snapshot.status); // "running"
6. Update Context
machine.updateContext((ctx) => ({ ...ctx, name: "Ada", dirty: true }));
7. Use Built-in goTo
await machine.send({ type: "goTo", to: "review" });
goTois built in.- It validates the target step exists.
- It appends the previous step to
history(unless target equals current).
8. Async Guards and Effects
const asyncJourney: JourneyDefinition<Context, StepId, Event> = {
...journey,
transitions: [
{
id: "save-and-continue",
from: "details",
event: "next",
to: "review",
when: async ({ context }) => context.name.trim().length > 0,
effect: async ({ context }) => {
const savedName = await Promise.resolve(context.name.trim());
return { ...context, name: savedName };
}
}
]
};
If when or effect throws:
send(...)rejects.- Current step async state goes to
error. - You can inspect and clear it:
const step = machine.getSnapshot().current;
const stepAsync = machine.getSnapshot().async.byStep[step];
if (stepAsync.phase === "error") {
console.error(stepAsync.error);
machine.clearStepError(step);
}
9. Persistence and History Limits
const machineWithOptions = createJourneyMachine(journey, {
persistence: {
key: "checkout-journey",
version: 2,
clearOnReset: false
},
history: {
maxHistory: 20,
onOverflow: ({ trimmed, reason }) => {
console.warn("history trimmed", trimmed, reason);
}
}
});
Notes:
- Default
maxHistoryis50. - Set
maxHistory: nullto disable trimming. - Persistence stores
current,context,history,visited, andstatus. - Runtime async state (
snapshot.async) is not persisted.
10. Reset and Terminal States
machine.reset();
reset()returns toinitial, initialcontext, emptyhistory,status: "running".- After terminal (
COMPLETEorCLOSE), furthersend(...)calls do not transition.
Next
- Architecture and model rationale:
/docs/core/architecture - API details:
/docs/core/api - Snapshot model:
/docs/core/snapshot - Lifecycle semantics:
/docs/core/lifecycle - Async transitions:
/docs/core/async - History behavior:
/docs/core/history - Persistence and migrations:
/docs/core/persistence - Common issues and decisions:
/docs/core/faq - Copyable patterns:
/docs/core/recipes - Full example catalog:
/docs/core/examples