compose
compose() returns a Composer. .step() adds Tasks and Wires; .run(), .graph(), and .guard() consume the chain.
Returns the chain as a tree without executing it.
.graph()— Returns a JSON-serializable tree- Each node has a
type:"seq","con", or"run" seq— Sequential group; children run one after another. The tree’s root is always aseqcon— Concurrent group; children run in parallel- Both
seqandconhavemeta.name(from.meta({ name }), orundefined) andchildren childrenmay containrun,seq, orconnodes; a nestedcompose()appears as a nestedseqrun— Leaf for a Task or Wire withmeta.name(Task name, or destination Tag names joined with+for a Wire),meta.kind("task"or"wire"),id, anddependenciesid— Increments in tree-traversal order from0dependencies—{ required: number[], optional: number[] }. IDs ofrunnodes this leaf reads from. Inrequired, an entry of-1means no earlierrunproduces that value;optionalomits missing producers
compose() .meta({ name: "app" }) .step(createWire({ from: literal(1), to: userId })) .step([fetchUser, fetchPermissions]) .step(compose().meta({ name: "cleanup" }).step(close)) .graph(){ "type": "seq", "meta": { "name": "app" }, "children": [ { "type": "run", "meta": { "name": "userId", "kind": "wire" }, "id": 0, "dependencies": { "required": [], "optional": [] } }, { "type": "con", "meta": {}, "children": [ { "type": "run", "meta": { "name": "fetchUser", "kind": "task" }, "id": 1, "dependencies": { "required": [0], "optional": [] } }, { "type": "run", "meta": { "name": "fetchPermissions", "kind": "task" }, "id": 2, "dependencies": { "required": [0], "optional": [] } } ] }, { "type": "seq", "meta": { "name": "cleanup" }, "children": [ { "type": "run", "meta": { "name": "close", "kind": "task" }, "id": 3, "dependencies": { "required": [], "optional": [] } } ] } ]}Checks the chain configuration without executing it.
.guard()— Returns nothing; throws on the first problem found- Duplicate Task or Wire — same instance added twice
- Missing context — a Spot has no producer in the chain
- Unused Wire — its value is never read
expect(() => app.guard()).not.toThrow()Same checks run inside .run(), where unused Wires log a warning via console.warn instead of throwing.
Attaches metadata to the chain: a name and lifecycle hooks.
.meta({ name?, hooks? })— Updatesnameand/orhookson the chainname— String identifier. Appears in.graph()output, in messages from the guard, and inonStart/onCompletepayloadshooks.onStart({ meta })— Fires when the chain starts; receives the chain’s metahooks.onComplete({ meta })— Fires when the chain completes; receives the chain’s metahooks.onTaskFail({ task, error })— Fires when a Task inside the chain fails; receives the failing Task and its error- Returns a Composer
compose() .meta({ name: "app", hooks: { onStart: ({ meta }) => console.log(`${meta?.name}: start`), onComplete: ({ meta }) => console.log(`${meta?.name}: complete`), onTaskFail: ({ task, error }) => console.error(`${task.name}:`, error), }, }) .step(loadUser)Executes the chain.
.run()— ReturnsPromise<Scope>scope.get(spot)— Returns the resolved value ofspot, orundefined
const scope = await compose() .step(loadUser) .step(createWire({ from: loadUser.result.id, to: userId })) .run()
scope.get(loadUser.result) // { id, name }scope.get(loadUser.status) // "done"scope.get(userId.value) // the idBefore execution, .run() runs the guard. Duplicate Tasks or Wires and missing context throw; unused Wires log a warning via console.warn. Use .guard() for a strict variant where unused Wires also throw.
.get returns undefined when the Spot has no value — a Task’s result after it failed or was skipped, or a Tag that no Wire has supplied.
Appends a Task, Wire, or nested Composer to the chain.
.step(child)— Appendschildas the next sequential step.step([...children])— Appends an array as a concurrent group. Members start togetherchild, array member — A Task, a Wire, or another Composer- Returns a Composer
- Throws when the argument is anything else
compose() .step(loadConfig) .step([fetchUser, fetchPermissions]) .step(createWire({ from: literal("/api"), to: apiUrl })) .step(compose().step(setup).step(start)) .step([compose().step(dashboard), compose().step(analytics)])Each .step() runs after the previous one completes; later steps can read values produced by earlier steps. Members of a concurrent group cannot read each other’s values.