Protocol defining how Runic components compose together and connect within workflows.
The Component protocol supports extension of modeling new component types that can be
added and connected with other components in Runic workflows. It provides introspection
capabilities for components (sub-components, inputs, outputs) and connection semantics
for workflow composition.
Protocol Functions
| Function | Purpose |
|---|---|
connectable?/2 | Check if a component can be connected to another |
connect/3 | Connect this component to a parent in a workflow |
source/1 | Returns the source AST for building/serializing the component |
hash/1 | Returns the content-addressable hash of the component |
inputs/1 | Returns the nimble_options schema for component inputs |
outputs/1 | Returns the nimble_options schema for component outputs |
Built-in Implementations
| Component Type | Description |
|---|---|
Runic.Workflow.Step | Single transformation function |
Runic.Workflow.Rule | Conditional logic with condition and reaction |
Runic.Workflow.Map | Fan-out transformation over enumerables |
Runic.Workflow.Reduce | Fan-in aggregation |
Runic.Workflow.Accumulator | Stateful reducer across invocations |
Runic.Workflow.StateMachine | Stateful reducer with reactive conditions |
Runic.Workflow | Workflows themselves are components |
Tuple | Pipeline syntax {parent, [children]} |
Type Compatibility
The Component protocol includes type compatibility checking via an internal
TypeCompatibility helper module. This enables schema-based
validation when connecting components:
# Type compatibility checks
TypeCompatibility.types_compatible?(:any, :integer) # => true
TypeCompatibility.types_compatible?(:string, :integer) # => false
# Port compatibility for connecting components
producer_outputs = [out: [type: {:list, :integer}]]
consumer_inputs = [in: [type: {:list, :any}]]
TypeCompatibility.ports_compatible?(producer_outputs, consumer_inputs) # => {:ok, :inferred}Usage
require Runic
step = Runic.step(fn x -> x * 2 end, name: :double)
rule = Runic.rule(fn x when x > 10 -> :large end, name: :classify)
# Introspection
Runic.Component.hash(step) # => content-addressable hash
Runic.Component.source(step) # => AST representation
# Compatibility checking
Runic.Component.connectable?(step, rule) # => true
# Connection (typically done via Workflow.add/3)
workflow = Runic.Workflow.new()
|> Runic.Workflow.add(step)
|> Runic.Workflow.add(rule, to: :double)Implementing Custom Component
defmodule MyApp.CustomComponent do
defstruct [:hash, :name, :config]
end
defimpl Runic.Component, for: MyApp.CustomComponent do
alias Runic.Workflow
def connectable?(_component, _other), do: true
def connect(component, to, workflow) do
workflow
|> Workflow.add_step(to, some_internal_step(component))
|> Workflow.register_component(component)
end
def source(component) do
quote do
MyApp.CustomComponent.new(name: unquote(component.name))
end
end
def hash(component), do: component.hash
def inputs(_component), do: [in: [type: :any, doc: "Input value"]]
def outputs(_component), do: [out: [type: :any, doc: "Output value"]]
endSee the Protocols Guide for more details and examples.
Summary
Functions
Check if a component can be connected to another component.
Returns port contract for component inputs. Each entry is a named port with options like :type, :doc, :cardinality, :required.
Returns port contract for component outputs. Each entry is a named port with options like :type, :doc, :cardinality.
Returns the source AST for building a component.
Types
@type t() :: term()
All the types that implement this protocol.
Functions
Check if a component can be connected to another component.
Returns port contract for component inputs. Each entry is a named port with options like :type, :doc, :cardinality, :required.
Returns port contract for component outputs. Each entry is a named port with options like :type, :doc, :cardinality.
Returns the source AST for building a component.