Documentation
¶
Overview ¶
Package graph builds and runs directed acyclic graphs.
A graph connects typed node ports. Independent nodes can run at the same time. Definitions can come from Go values, JSON, or YAML.
Compile resolves reusable node types and validates the complete graph. Executor runs the immutable graph with bounded parallelism and fail-fast cancellation.
Values move through named ports. Each port has a portable type ID and a declared Go type. Optional ports represent values that can be absent.
Example ¶
package main
import (
"context"
"fmt"
"reflect"
graph "github.com/bpradana/graph"
)
type arithmeticNode struct{}
func (*arithmeticNode) Inputs() []graph.InputPort {
integer := reflect.TypeFor[int]()
return []graph.InputPort{
{Name: "left", TypeID: "integer/v1", Type: integer},
{Name: "right", TypeID: "integer/v1", Type: integer},
}
}
func (*arithmeticNode) Outputs() []graph.OutputPort {
integer := reflect.TypeFor[int]()
return []graph.OutputPort{
{Name: "sum", TypeID: "integer/v1", Type: integer},
{Name: "product", TypeID: "integer/v1", Type: integer},
}
}
func (*arithmeticNode) Run(_ context.Context, inputs graph.Inputs) (graph.Outputs, error) {
left, err := graph.ValueAs[int](inputs, "left")
if err != nil {
return graph.Outputs{}, err
}
right, err := graph.ValueAs[int](inputs, "right")
if err != nil {
return graph.Outputs{}, err
}
return graph.NewValues(map[string]any{
"sum": left + right,
"product": left * right,
}), nil
}
func main() {
registry := graph.NewRegistry()
_ = registry.Register("math/arithmetic", func(graph.Config) (graph.Node, error) {
return &arithmeticNode{}, nil
})
integerPorts := []graph.PortDefinition{
{Name: "left", TypeID: "integer/v1"},
{Name: "right", TypeID: "integer/v1"},
}
definition := graph.Definition{
APIVersion: graph.APIVersion,
Name: "arithmetic",
Inputs: []graph.GraphInputDefinition{
{PortDefinition: integerPorts[0], Targets: []graph.Endpoint{{Node: "math", Port: "left"}}},
{PortDefinition: integerPorts[1], Targets: []graph.Endpoint{{Node: "math", Port: "right"}}},
},
Outputs: []graph.GraphOutputDefinition{
{PortDefinition: graph.PortDefinition{Name: "sum", TypeID: "integer/v1"}, Source: graph.Endpoint{Node: "math", Port: "sum"}},
{PortDefinition: graph.PortDefinition{Name: "product", TypeID: "integer/v1"}, Source: graph.Endpoint{Node: "math", Port: "product"}},
},
Nodes: []graph.NodeDefinition{{
ID: "math", Type: "math/arithmetic",
Inputs: integerPorts,
Outputs: []graph.PortDefinition{
{Name: "sum", TypeID: "integer/v1"},
{Name: "product", TypeID: "integer/v1"},
},
}},
}
compiled, _ := graph.Compile(definition, registry)
executor, _ := graph.NewExecutor(graph.WithMaxParallel(2))
result, _ := executor.Run(context.Background(), compiled, graph.NewValues(map[string]any{
"left": 6,
"right": 7,
}))
sum, _ := graph.ValueAs[int](result.Outputs, "sum")
product, _ := graph.ValueAs[int](result.Outputs, "product")
fmt.Println(sum, product)
}
Output: 13 42
Index ¶
- Constants
- func EncodeJSON(definition Definition) ([]byte, error)
- func EncodeYAML(definition Definition) ([]byte, error)
- func LookupAs[T any](values Values, name string) (T, bool, error)
- func ValueAs[T any](values Values, name string) (T, error)
- type Builder
- type Config
- type Definition
- type EdgeDefinition
- type Endpoint
- type Event
- type EventKind
- type Executor
- type ExecutorOption
- type Graph
- type GraphInputDefinition
- type GraphOutputDefinition
- type InputPort
- type InputRef
- type Inputs
- type Issue
- type Node
- type NodeDefinition
- type NodeFactory
- type NodeRef
- type Observer
- type ObserverError
- type ObserverFunc
- type OutputPort
- type OutputRef
- type Outputs
- type PanicError
- type PortDefinition
- type Registry
- type Resolver
- type RunError
- type RunResult
- type Source
- type ValidationError
- type Values
Examples ¶
Constants ¶
const APIVersion = "graph/v1"
APIVersion identifies the supported serialized definition format.
Variables ¶
This section is empty.
Functions ¶
func EncodeJSON ¶
func EncodeJSON(definition Definition) ([]byte, error)
EncodeJSON encodes a graph definition as indented JSON.
Encoding does not validate graph semantics.
func EncodeYAML ¶
func EncodeYAML(definition Definition) ([]byte, error)
EncodeYAML encodes a graph definition as YAML.
Encoding does not preserve source comments or formatting.
Types ¶
type Builder ¶
type Builder struct {
// contains filtered or unexported fields
}
Builder creates a graph definition through fluent node bindings.
Builder resolves node contracts during Build. Builder collects errors and returns them from Build. Builder does not support concurrent use.
func NewBuilder ¶
NewBuilder creates a fluent builder for a named graph.
func (*Builder) Build ¶
func (b *Builder) Build() (Definition, error)
Build resolves node contracts and returns the generated graph definition.
Build returns ValidationError when a builder operation or connection is invalid.
func (*Builder) Input ¶ added in v1.1.0
Input declares one graph input and returns a reusable source reference.
type Config ¶
Config contains portable node configuration values.
JSON and YAML definitions can contain maps, lists, numbers, strings, Boolean values, and null values.
func (Config) Decode ¶
Decode converts the configuration to a typed value.
Target must be a non-nil pointer. Decode rejects fields that do not exist in the target type.
func (*Config) UnmarshalJSON ¶
UnmarshalJSON decodes configuration and preserves JSON number values.
Applications normally use DecodeJSON instead of calling this method.
type Definition ¶
type Definition struct {
// APIVersion identifies the serialized schema version.
APIVersion string `json:"apiVersion" yaml:"apiVersion"`
// Name identifies the graph in diagnostics and events.
Name string `json:"name" yaml:"name"`
// Inputs declares values that a caller can supply.
Inputs []GraphInputDefinition `json:"inputs,omitempty" yaml:"inputs,omitempty"`
// Outputs declares values that a caller can read.
Outputs []GraphOutputDefinition `json:"outputs,omitempty" yaml:"outputs,omitempty"`
// Nodes declares all node instances.
Nodes []NodeDefinition `json:"nodes" yaml:"nodes"`
// Edges connects node output ports to node input ports.
Edges []EdgeDefinition `json:"edges,omitempty" yaml:"edges,omitempty"`
}
Definition describes a serializable graph.
func DecodeJSON ¶
func DecodeJSON(data []byte) (Definition, error)
DecodeJSON decodes one strict JSON graph definition.
It rejects unknown fields, trailing values, malformed data, and unsupported API versions.
func DecodeYAML ¶
func DecodeYAML(data []byte) (Definition, error)
DecodeYAML decodes one strict YAML graph definition.
It rejects unknown fields, duplicate keys, multiple documents, malformed data, and unsupported API versions.
Example ¶
package main
import (
"fmt"
graph "github.com/bpradana/graph"
)
func main() {
data := []byte(`
apiVersion: graph/v1
name: empty
nodes: []
`)
definition, err := graph.DecodeYAML(data)
fmt.Println(definition.Name, err)
}
Output: empty <nil>
type EdgeDefinition ¶
type EdgeDefinition struct {
// From identifies the source output port.
From Endpoint `json:"from" yaml:"from"`
// To identifies the target input port.
To Endpoint `json:"to" yaml:"to"`
}
EdgeDefinition connects one node output to one node input.
type Endpoint ¶
type Endpoint struct {
// Node identifies one node instance.
Node string `json:"node" yaml:"node"`
// Port identifies one port on the node instance.
Port string `json:"port" yaml:"port"`
}
Endpoint identifies one port on one node instance.
type Event ¶
type Event struct {
// Kind identifies the lifecycle transition.
Kind EventKind
// GraphName identifies the running graph.
GraphName string
// NodeID identifies the applicable node instance.
NodeID string
// Err contains the applicable failure.
Err error
// Time records when the executor emitted the event.
Time time.Time
}
Event describes one graph lifecycle transition.
type EventKind ¶
type EventKind string
EventKind identifies one graph lifecycle transition.
const ( // EventGraphStarted reports the start of one graph run. EventGraphStarted EventKind = "graph_started" // EventNodeStarted reports the start of one node run. EventNodeStarted EventKind = "node_started" // EventNodeSucceeded reports successful node completion. EventNodeSucceeded EventKind = "node_succeeded" // EventNodeFailed reports failed node completion. EventNodeFailed EventKind = "node_failed" // EventGraphSucceeded reports successful graph completion. EventGraphSucceeded EventKind = "graph_succeeded" // EventGraphFailed reports failed graph completion. EventGraphFailed EventKind = "graph_failed" )
type Executor ¶
type Executor struct {
// contains filtered or unexported fields
}
Executor runs compiled graphs.
One executor supports concurrent Run calls.
func NewExecutor ¶
func NewExecutor(options ...ExecutorOption) (*Executor, error)
NewExecutor creates an executor.
The default parallel limit is runtime.GOMAXPROCS(0).
type ExecutorOption ¶
type ExecutorOption interface {
// contains filtered or unexported methods
}
ExecutorOption configures an executor.
func WithMaxParallel ¶
func WithMaxParallel(limit int) ExecutorOption
WithMaxParallel sets the maximum number of active nodes in each run.
NewExecutor returns an error when limit is not positive.
func WithObserver ¶
func WithObserver(observer Observer) ExecutorOption
WithObserver adds one lifecycle observer.
The executor calls observers in option order. NewExecutor rejects a nil observer.
type Graph ¶
type Graph struct {
// contains filtered or unexported fields
}
Graph is an immutable compiled graph.
Graph supports concurrent runs. Each runtime node must support concurrent Run calls.
type GraphInputDefinition ¶
type GraphInputDefinition struct {
// PortDefinition declares the graph input contract.
PortDefinition `json:",inline" yaml:",inline"`
// Targets identifies every node input that receives the value.
Targets []Endpoint `json:"targets" yaml:"targets"`
}
GraphInputDefinition exposes one graph input.
type GraphOutputDefinition ¶
type GraphOutputDefinition struct {
// PortDefinition declares the graph output contract.
PortDefinition `json:",inline" yaml:",inline"`
// Source identifies the node output that supplies the value.
Source Endpoint `json:"source" yaml:"source"`
}
GraphOutputDefinition exposes one graph output.
type InputPort ¶
type InputPort struct {
// Name identifies the port inside one node.
Name string
// TypeID identifies the portable value contract.
TypeID string
// Type identifies the required Go value type.
Type reflect.Type
// Optional permits the input value to be absent.
Optional bool
}
InputPort describes one input that a node accepts.
type InputRef ¶ added in v1.1.0
type InputRef struct {
// contains filtered or unexported fields
}
InputRef identifies one graph input created by Builder.Input. Its fields are intentionally hidden.
type Issue ¶
type Issue struct {
// Path identifies the invalid definition location.
Path string
// Message describes the validation failure.
Message string
}
Issue describes one invalid part of a graph definition.
type Node ¶
type Node interface {
// Inputs returns all input ports that the node accepts.
Inputs() []InputPort
// Outputs returns all output ports that the node can produce.
Outputs() []OutputPort
// Run executes the node with one input collection.
Run(context.Context, Inputs) (Outputs, error)
}
Node runs one operation in a graph.
Inputs and Outputs declare the complete runtime port contract. Compile compares this contract with the node definition.
Run receives values only for present input ports. It must return all required outputs. Run can be called concurrently when a compiled graph has concurrent runs.
type NodeDefinition ¶
type NodeDefinition struct {
// ID uniquely identifies this node instance inside the graph.
ID string `json:"id" yaml:"id"`
// Type selects a reusable node type through the resolver.
Type string `json:"type" yaml:"type"`
// Inputs repeats the complete runtime input contract.
Inputs []PortDefinition `json:"inputs,omitempty" yaml:"inputs,omitempty"`
// Outputs repeats the complete runtime output contract.
Outputs []PortDefinition `json:"outputs,omitempty" yaml:"outputs,omitempty"`
// Config contains configuration for this node instance.
Config Config `json:"config,omitempty" yaml:"config,omitempty"`
}
NodeDefinition describes one node instance.
type NodeFactory ¶
NodeFactory creates one runtime node from instance configuration.
type NodeRef ¶ added in v1.1.0
type NodeRef struct {
// contains filtered or unexported fields
}
NodeRef identifies one node instance created by Builder.Node.
type Observer ¶
Observer receives ordered graph lifecycle events.
The executor calls an observer synchronously. It never calls one observer concurrently. An observer must not call Run on the same executor.
type ObserverError ¶
type ObserverError struct {
// Value contains the recovered panic value.
Value any
// Stack contains the recovered goroutine stack.
Stack []byte
// Event contains the event that caused the observer panic.
Event Event
}
ObserverError reports a recovered observer panic.
func (*ObserverError) Error ¶
func (e *ObserverError) Error() string
Error returns the recovered observer panic value.
type ObserverFunc ¶
ObserverFunc adapts a function to the Observer interface.
type OutputPort ¶
type OutputPort struct {
// Name identifies the port inside one node.
Name string
// TypeID identifies the portable value contract.
TypeID string
// Type identifies the required Go value type.
Type reflect.Type
// Optional permits the output value to be absent.
Optional bool
}
OutputPort describes one output that a node produces.
type OutputRef ¶ added in v1.1.0
type OutputRef struct {
// contains filtered or unexported fields
}
OutputRef identifies one node output created by NodeRef.Out. Its fields are intentionally hidden.
type PanicError ¶
type PanicError struct {
// Value contains the recovered panic value.
Value any
// Stack contains the recovered goroutine stack.
Stack []byte
}
PanicError reports a recovered node panic.
func (*PanicError) Error ¶
func (e *PanicError) Error() string
Error returns the recovered panic value.
type PortDefinition ¶
type PortDefinition struct {
// Name identifies the port inside one node or graph boundary.
Name string `json:"name" yaml:"name"`
// TypeID identifies the portable value contract.
TypeID string `json:"type" yaml:"type"`
// Optional permits the value to be absent.
Optional bool `json:"optional,omitempty" yaml:"optional,omitempty"`
}
PortDefinition describes a portable port contract.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry resolves node types through registered factories.
Registry supports concurrent registration and resolution.
type Resolver ¶
type Resolver interface {
Resolve(NodeDefinition) (Node, error)
}
Resolver creates a runtime node for one node definition.
Compile calls Resolve once for each valid node instance.
type RunError ¶
type RunError struct {
// NodeID identifies the failed node instance.
NodeID string
// Err contains the original node error.
Err error
}
RunError reports a failed node.
type RunResult ¶
type RunResult struct {
// Outputs contains present graph boundary outputs.
Outputs Values
// NodeResults contains outputs from every successful completed node.
NodeResults map[string]Outputs
}
RunResult contains graph outputs and successful node results.
type Source ¶ added in v1.1.0
type Source interface {
// contains filtered or unexported methods
}
Source identifies a value that can supply one node input.
InputRef and OutputRef are the only implementations.
type ValidationError ¶
type ValidationError struct {
// Issues contains every detected validation issue.
Issues []Issue
}
ValidationError contains all detected definition issues.
func (*ValidationError) Error ¶
func (e *ValidationError) Error() string
Error returns a summary of the validation issues.
type Values ¶
type Values struct {
// contains filtered or unexported fields
}
Values contains named graph values.
The zero value is an empty collection. NewValues copies the source map. Values stored inside the map are not copied.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
docs
|
|
|
examples/concurrency
command
|
|
|
examples/json
command
|
|
|
examples/observer
command
|
|
|
examples/optional
command
|
|
|
examples/programmatic
command
|
|
|
examples/yaml
command
|