Settling
State Backed supports long-running workflows composed of intermediate-length steps. That means that your machine instances can process a huge amount of data or events in a completely durable and reliable way by running many short (<90 seconds) actions.
When a machine instance receives an event, the machine continues processing until it "settles" or until that event's 90 second timeout elapses.
A machine instance is considered "settled" when it has no ephemeral child services running.
An ephemeral child service is any service spawned or invoked by the machine with the default spawn or invoke (i.e. any child service that is not persistent).
Persistent child services may be spawned with the
spawnPersistentInstance
method from @statebacked/machine
or by
specifying a persistent source in an invoke
block by using the
persistentInvocableSource
method from @statebacked/machine
.
Ephemeral service invocation example
Consider this machine:
import { createMachine, assign } from "xstate";
export default createMachine({
initial: "idle",
states: {
idle: {
on: {
run: "run",
}
},
run: {
invoke: {
id: "request-data",
src: async (ctx) => {
const res = await fetch("https://example.com/");
if (!res.ok) {
throw new Error("oops");
}
const { data } = await res.json();
return data;
},
onDone: {
target: "complete",
actions: assign({
data: (_, evt) => evt.data
}),
},
onError: "failed",
},
},
complete: {},
failed: {},
}
});
The request to create a machine instance from this machine will complete right away.
A subsequent request to send the run
event to the machine instance will wait until
the request-data
request completes or until 90 seconds have elapsed. If the request
completes prior to the 90 second timeout, the send event request will return the
new state of the machine instance complete
or failed
. Otherwise, because the event
produced one successful transition (from idle
to run
), it will return run
as
the state of the machine instance and stop the ongoing fetch after 90 seconds.
Then, when the next event is sent to the machine instance, it will first receive an
error event for the request-data
service and transition to the failed
state
and only then will it process the new event. This ensures a completely consistent
sequence of states using only the natural error handling primitives provided by
state machines.
Persistent service invocation example
Consider this machine:
import { createMachine, assign } from "xstate";
import { persistentInvocableSource } from "@statebacked/machine";
export default createMachine({
initial: "idle",
states: {
idle: {
on: {
run: "run",
}
},
run: {
on: {
// receive the proceed event from our child-machine instance
proceed: "complete"
},
invoke: {
id: "request-data",
src: persistentInvocableSource({
machineName: "child-machine",
}),
},
},
complete: {},
}
});
The request to create a machine instance from this machine will complete right away.
A subsequent request to send the run
event to the machine instance will also complete
right away but it will create a new, persistent State Backed machine instance of the
"child-machine" machine (it will create a random machine instance name because one wasn't
provided).
Whenever the "child-machine" instance executes a sendParent
action to send its parent
a "proceed" event, our instance will receive it and execute the appropriate transition.
Time spent to create the new persistent machine instance does not count against the 90 second timeout for event processing.