dispatch
Function, in @jr2/orchestrator/src/dispatch-effect.ts. It is an export of @jr2/orchestrator, on the Authoring surface page, in Effects.
function dispatch<
S extends z.ZodObject | undefined = undefined,
const T extends DispatchDefinition<S>["target"] = undefined,
>(
workflow: string,
def?: DispatchDefinition<S> & { target?: T },
): EffectLogic<DispatchInput<S>, DispatchOutput> & OpenOf<DispatchOpenParts<T>>Make a dispatch Effect: it starts a run of a Workflow. Put it in actors, and invoke it from a state: actors: { triage: dispatch("triage", { input }) }. The input of the invoke is the run input of the new run, as jr2 run --input gives it.
The new run is a run of its own, with its own id, its own Workspaces and its own CANCEL. Its record names this run as the run that dispatched it. A CANCEL of this run does not end it.
With no target, the run starts in this Orchestrator, under the current Assembly. With target: { url, token }, it starts on the Orchestrator at url, and the Effect runs in the Instance Effector. Every outcome is output (DispatchOutput): started, settled, refused, unreachable or unknown. Each start carries a key, so a start that jr2 sends again after a restart starts no second run.
It refuses:
- a name that is not a Workflow name:
dispatch("<name>"): the Workflow is named as `jr2 run` names it — a Machine's filename stem ("review"), or "<wrapper>/<name>" for a named Chart: lowercase letters, digits and "-" (ADR-0071) - a
waitthat is not a boolean:dispatch("<name>", { wait }): wait is true or false — a slot that needs both declares two - an
inputthat is not az.object():dispatch("<name>", { input }): the input is a z.object(...) — the run's body is a JSON object (ADR-0071) - a
timeoutthat is not a duration:dispatch("<name>"): timeout "<timeout>" is not a positive duration (like "30s", "10m", "1h") - a
targeturl that is not an origin:dispatch("<name>", { target }): "<url>" is not an origin — write the scheme and the host, with a port if it is not the scheme's, and no path, query or userinfo, then two examples secret(open)in a target:dispatch("<name>", { target }): secret(open) in a target — the target is the Open part, not its secret, then the fix
jr2 up refuses a dispatch with no target whose Workflow the Assembly does not have, and an input schema that the input of that Workflow does not take.
Parameters
workflow: The Workflow to start, asjr2 runnames it:"triage", or"<wrapper>/<name>"for a named Chart.def: Optional. Each field tells what it does.input(optional):SA zod schema of the run input of the Workflow. It types the
inputof the invoke andfromPick, andjr2 upcompares it with the input that the Workflow declares. Without it, only the Workflow checks the input.wait(optional):booleantrue: end when the new run ends (settled).false, the default: end when the Orchestrator accepts the run (started).timeout(optional):DurationHow long the Effect waits: by default 30s for the start, and 10m with
wait.target(optional):{ url: string; token: NamedSecret; } | typeof openA different Orchestrator:
urlis its origin, andtokenis the secret that holds its Instance token. Oropen, which the composer binds withcustomize(<import>, { effects: { <slot>: { target: { url, token } } } }). Without it, the run starts in this Orchestrator.
Returns: An Effect, for actors.
Example
import { assign } from "xstate";
import { z } from "zod";
import { defineEvent, dispatch, jr2Setup } from "@jr2/orchestrator";
const triageInput = z.object({ request: z.string() });
const report = defineEvent({
name: "report",
description: "Report a request. A run of triage starts for it.",
input: triageInput,
});
export const machine = jr2Setup({
types: {} as { context: { request: string } },
events: [report],
// `wait: true`: the Effect ends when the run of triage ends.
actors: { triage: dispatch("triage", { input: triageInput, wait: true }) },
}).createMachine({
id: "intake",
context: { request: "" },
initial: "listening",
states: {
listening: {
invoke: { src: "gate" },
on: { report: { target: "starting", actions: assign({ request: ({ event }) => event.request }) } },
},
starting: {
invoke: {
src: "triage",
input: ({ context }) => ({ request: context.request }),
onDone: [
{ guard: ({ event }) => event.output.outcome === "settled", target: "done" },
{ target: "listening" },
],
},
},
done: { type: "final" },
},
});See also