Skip to content

Nodes

Placement types: the appearances of nodes inside a DAG. Each placement is a discriminated union member keyed by @type, ships with a JSON Schema in @noocodex/dagonizer/entities, and resolves to a typed TS shape via json-schema-to-ts.

A registered NodeInterface (the consumer-implemented unit of work) is referenced from a placement by name. A "node" is the unit of work. A "placement" is its appearance inside a DAG. TerminalNode is placement-only and references no registered node.

Placement @typeSchemaTS typePurpose
SingleNodeSingleNodeSchemaSingleNode, SingleNodePlacementInterface<TOutput>Run one registered node; route per output
ParallelNodeParallelNodeSchemaParallelNodeRun a group of single-node placements concurrently; combine outputs
ScatterNodeScatterNodeSchemaScatterNodeIsolate one clone per source-array item, run a node body, gather produced state, route on aggregate outcome
EmbeddedDAGNodeEmbeddedDAGNodeSchemaEmbeddedDAGNodeInvoke a registered sub-DAG exactly once (cardinality 1); route on the child's terminal outcome
TerminalNodeTerminalNodeSchemaTerminalNode, TerminalNodePlacementInterfaceEnd the flow with an explicit outcome
PhaseNodePhaseNodeSchemaPhaseNode, PhaseNodePlacementInterfacePre/post lifecycle hook running outside the main loop

Every schema's $id is https://noocodex.dev/schemas/dagonizer/<TypeName>.


SingleNode

ts
import { SingleNodeSchema } from '@noocodex/dagonizer/entities';
import type { SingleNode, SingleNodePlacementInterface } from '@noocodex/dagonizer/entities';
json
{
  "@id":     "urn:noocodex:dag:my-dag/node/greet",
  "@type":   "SingleNode",
  "name":    "greet",
  "node":    "greet",
  "outputs": { "success": "next-node", "error": null }
}
FieldTypeRequiredDescription
@idstringyesPlacement URN
@type'SingleNode'yesDiscriminator
namestringyesPlacement name (unique within the DAG)
nodestringyesRegistered NodeInterface.name to invoke
outputsRecord<string, string | null>yesOutput port to next-placement name (or null to terminate the path)

SingleNodePlacementInterface<TOutput extends string> narrows outputs to Record<TOutput, null | string> for compile-time exhaustiveness when TOutput is a literal union.


ParallelNode

ts
import { ParallelNodeSchema } from '@noocodex/dagonizer/entities';
import type { ParallelNode } from '@noocodex/dagonizer/entities';
json
{
  "@id":     "urn:noocodex:dag:my-dag/node/probe-group",
  "@type":   "ParallelNode",
  "name":    "probe-group",
  "nodes":   ["probe-a", "probe-b"],
  "combine": "all-success",
  "outputs": { "success": "merge", "error": null }
}
FieldTypeRequiredDescription
@idstringyesPlacement URN
@type'ParallelNode'yesDiscriminator
namestringyesPlacement name
nodesreadonly string[]yesNon-empty list of SingleNode placement names within the same DAG
combine'all-success' | 'any-success' | 'collect'yesBuilt-in combiner, or any name registered via ParallelCombiners.register
outputsRecord<string, string | null>yesRoutes for the combined output

The group runs every member via Promise.all. The dispatcher resolves the combiner by ParallelCombiners.resolve(combine) and yields a group result after the intermediates.


ScatterNode

ts
import { ScatterNodeSchema } from '@noocodex/dagonizer/entities';
import type { ScatterNode } from '@noocodex/dagonizer/entities';

Generate-collect pattern (one clone per source-array item):

json
{
  "@id":         "urn:noocodex:dag:my-dag/node/generate",
  "@type":       "ScatterNode",
  "name":        "generate",
  "body":        { "node": "generate-worker" },
  "source":      "providers",
  "itemKey":     "currentItem",
  "concurrency": 4,
  "gather":      { "strategy": "map", "mapping": { "candidate": "candidates" } },
  "outputs":     { "all-success": "select", "partial": "select", "all-error": null, "empty": null }
}
FieldTypeRequiredDescription
@idstringyesPlacement URN
@type'ScatterNode'yesDiscriminator
namestringyesPlacement name
body{ node: string }yesBody: registered node name
outputsRecord<string, string | null>yesRoutes for the reduced outcome
sourcestringyesDotted state-array path. One clone runs per item.
itemKeystringnoMetadata key bound to the current item per clone (default 'currentItem').
concurrencynumbernoBatch size for Promise.all (default: source length).
stateMapping{ input?: Record<childKey, parentPath> }noSeeds each clone: input copies parent fields into the clone before the body runs. Authored via the inputs builder option.
gatherGatherConfignoHow produced clone state merges back into the parent.
reducerstringnoOutcome reducer name. Defaults to 'aggregate'.

GatherConfig is documented under Gather configuration below.

Per-item resume bookkeeping is persisted under the reserved metadata key SCATTER_PROGRESS_KEY so a checkpoint-resume cycle skips clones completed in the prior run.


EmbeddedDAGNode

ts
import { EmbeddedDAGNodeSchema } from '@noocodex/dagonizer/entities';
import type { EmbeddedDAGNode } from '@noocodex/dagonizer/entities';
json
{
  "@id":     "urn:noocodex:dag:parent/node/run-child",
  "@type":   "EmbeddedDAGNode",
  "name":    "run-child",
  "dag":     "child-pipeline",
  "outputs": { "success": "next-step", "error": null },
  "stateMapping": {
    "input":  { "payload": "user.name" },
    "output": { "user.result": "result" }
  }
}
FieldTypeRequiredDescription
@idstringyesPlacement URN
@type'EmbeddedDAGNode'yesDiscriminator
namestringyesPlacement name
dagstringyesRegistered sub-DAG name to invoke (cardinality 1)
outputsRecord<'success' | 'error', string | null>yesRoutes for the child's terminal outcome
stateMapping{ input?: Record<string, string>; output?: Record<string, string> }noinput copies parent fields into the child before it runs (child-key ← parent-path); output copies child fields back into the parent after it completes (parent-path ← child-key).

EmbeddedDAGNode invokes a registered sub-DAG exactly once (cardinality 1). It is the embedding primitive: the parent flow suspends, the child DAG runs to completion in an isolated state, and the parent routes on the child's terminal outcome (success when the child lifecycle is completed; error when failed). Authored via .embeddedDAG(name, dagName, routes, { inputs, outputs }) on DAGBuilder, or via the embeddedDAGs annotation on DAGDeriver.derive.


TerminalNode

ts
import { TerminalNodeSchema } from '@noocodex/dagonizer/entities';
import type { TerminalNode, TerminalNodePlacementInterface } from '@noocodex/dagonizer/entities';
json
{
  "@id":     "urn:noocodex:dag:my-dag/node/done-ok",
  "@type":   "TerminalNode",
  "name":    "done-ok",
  "outcome": "completed"
}
FieldTypeRequiredDescription
@idstringyesPlacement URN
@type'TerminalNode'yesDiscriminator
namestringyesPlacement name
outcome'completed' | 'failed'yesLifecycle outcome to mark on exit

No outputs map. Placement-only (no backing NodeInterface). On reach, the engine fires state.markCompleted() or state.markFailed(...) and ends the loop. The placement-level outcome surfaces on ExecutionResultInterface.terminalOutcome.


PhaseNode

ts
import { PhaseNodeSchema } from '@noocodex/dagonizer/entities';
import type { PhaseNode, PhaseNodePlacementInterface } from '@noocodex/dagonizer/entities';
json
{
  "@id":   "urn:noocodex:dag:my-dag/node/seed-cache",
  "@type": "PhaseNode",
  "name":  "seed-cache",
  "node":  "seed-cache",
  "phase": "pre"
}
FieldTypeRequiredDescription
@idstringyesPlacement URN
@type'PhaseNode'yesDiscriminator
namestringyesPlacement name
nodestringyesRegistered NodeInterface.name invoked at the phase boundary
phase'pre' | 'post'yesRun before the entrypoint or after the main loop drains

No outputs map. Pre-phase placements run in DAG declaration order before the entrypoint; a thrown error aborts the run (lifecycle becomes failed, the main loop never executes). Post-phase placements run in declaration order on every exit path (completion, abort, timeout, terminal-failed, node throw); a thrown error is collected as a warning (code POST_PHASE_FAILED) and does not change the already-set lifecycle. Phase placements surface via Instrumentation.phaseEnter / phaseExit.


Gather configuration

GatherConfig is referenced from ScatterNode.gather and is also exported as a standalone schema and type.

ts
import { GatherConfigSchema } from '@noocodex/dagonizer/entities';
import type { GatherConfig } from '@noocodex/dagonizer/entities';
ts
interface GatherConfig {
  strategy: 'map' | 'append' | 'partition' | 'custom';
  mapping?:    Record<string, string>;   // map: clone path → parent path
  field?:      string;                   // append/partition: clone field to read (omit ⇒ source item)
  target?:     string;                   // append: parent array path
  partitions?: Record<string, string>;   // partition: output token → parent array path
  customNode?: string;                   // custom: registered node name
}
StrategyKey fieldsBehaviour
mapmapping (clone path → parent path)One clone ⇒ scalar set; N clones ⇒ index-ordered array append. This is the generate-collect pattern.
appendtarget (dotted path), optional fieldFlatten the clone field (or source item) across all clones into target.
partitionpartitions: Record<output, path>, optional fieldBucket clones by their output token; write each group to its dedicated path.
customcustomNode (registered name)Stage per-clone records under state.metadata.gatherResults and dispatch the named node.

Strategies are pluggable: register a new one with GatherStrategies.register(strategy). See Reference: Core.


Watched over by the Order of Dagon.