Port Interface Reference

Complete reference for port definitions in TinySystems components.

Source of truth: module/node.go in the SDK (github.com/tiny-systems/module).

Port Structure

type Port struct {
    // true = this port is a SOURCE of data (output port)
    Source bool
    // Which side of the node displays this port
    Position Position
    // Lower-case programmatic name
    Name string
    // Human-readable name (capital cased)
    Label string
    // Request configuration/schema struct
    Configuration interface{}
    // Response configuration/schema struct (blocking request/response ports)
    ResponseConfiguration interface{}
    // Raw JSON schema override; published verbatim instead of reflecting Configuration
    Schema json.RawMessage
}

Fields

Source

Indicates port direction.

Type: bool

ValueDirectionDescription
trueOutputThe port is a source of data — the component emits messages from it
falseInputThe port receives messages (the zero value; usually omitted)

Examples:

// Output port
Source: true

// Input port (default — typically omitted)
Source: false

This matches TinyModuleComponentPort.Source in the CRD API: "Source is true for output ports, false for input ports."


Position

Visual position on the node in the flow editor.

Type: module.Position (an int)

Constants:

ConstantValueTypical Use
module.Top0Settings, Control (the zero value/default)
module.Right1Output ports
module.Bottom2Errors, Status
module.Left3Input ports
const (
    Top Position = iota // 0
    Right               // 1
    Bottom              // 2
    Left                // 3
)

Visual Layout:

            Top (Settings, Control)
            |
   Left  ---+---  Right
 (inputs)   |   (outputs)
            |
            Bottom (Errors, Status)

Name

Unique identifier for the port within the component.

Type: string

Requirements:

  • Must be unique within the component
  • Lower-case programmatic name
  • Names starting with _ are reserved for system ports

Reserved Names (constants in api/v1alpha1/consts.go):

NameConstantPurpose
_settingsv1alpha1.SettingsPortComponent settings
_controlv1alpha1.ControlPortDashboard/control messages
_reconcilev1alpha1.ReconcilePortNode reconciliation
_clientv1alpha1.ClientPortKubernetes client access
_identityv1alpha1.IdentityPortNode identity (name, namespace, flow, project)

Examples:

Name: "input"
Name: "output"
Name: "request"
Name: "result"
Name: "error"

Label

Human-readable display label for the UI.

Type: string

Guidelines:

  • Use title case
  • Keep concise (1-3 words)

Examples:

Label: "Input"
Label: "Output"
Label: "Settings"
Label: "HTTP Request"

Configuration

Struct instance that defines the port's schema and default values.

Type: interface{}

Purpose:

  • Defines the JSON Schema for the port (via reflection over struct tags)
  • Provides default/example values
  • Configures UI rendering

Example:

Configuration: Request{
    Array: []Item{"first", "second", "third"},
    Index: 1,
}

The live values are materialized into the published schema as defaults, so the simulator and editor mock real example data.


ResponseConfiguration

Struct instance describing the synchronous response of a blocking request/response port (the value that flows back through the Handler return chain).

Type: interface{}

Leave nil for fire-and-forget ports.


Schema

Raw JSON schema published verbatim instead of reflecting Configuration.

Type: json.RawMessage

Use it for forms whose shape is only known at runtime — a component that receives a schema as data and presents it to a human has no compile-time Go type to reflect. Components with a Go type for their port keep returning Configuration alone; reflection stays the default.

Note: do not rely on key order to order the rendered fields — set propertyOrder on each field in the schema JSON; that is what the editor sorts on.


Common Port Patterns

Real-world convention (see common-module components): inputs omit Source, outputs set Source: true.

Settings Port (input)

{
    Name:          v1alpha1.SettingsPort,
    Label:         "Settings",
    Configuration: Settings{},
}

Delivered via SettingsHandler.OnSettings — never via Handle.

Simple Input/Output

// Input
{
    Name:          "input",
    Label:         "Input",
    Position:      module.Left,
    Configuration: Input{},
}

// Output
{
    Name:          "output",
    Label:         "Output",
    Source:        true,
    Position:      module.Right,
    Configuration: Output{},
}

Result and Error Outputs

{
    Name:          "result",
    Label:         "Result",
    Source:        true,
    Position:      module.Right,
    Configuration: Result{},
},
{
    Name:          "error",
    Label:         "Error",
    Source:        true,
    Position:      module.Bottom,
    Configuration: module.ErrorMessage{},
}

Control Port

{
    Name:          v1alpha1.ControlPort,
    Label:         "Control",
    Source:        true, // publishes current state to the dashboard widget
    Configuration: c.getControl(),
}

Incoming control messages are delivered via ControlHandler.OnControl. Returning a different Go type per state (e.g. ControlRunning vs ControlStopped) is a supported pattern for state-dependent forms.

HTTP Server Ports

// Request output (emits incoming HTTP requests downstream)
{
    Name:                  "request",
    Label:                 "Request",
    Source:                true,
    Position:              module.Right,
    Configuration:         Request{},
    ResponseConfiguration: Response{}, // synchronous reply flowing back
}

Dynamic Ports

Ports() is called again after settings change, so ports can be generated from configuration.

func (c *Component) Ports() []module.Port {
    ports := []module.Port{
        {
            Name:          v1alpha1.SettingsPort,
            Label:         "Settings",
            Configuration: Settings{},
        },
        {
            Name:          "input",
            Label:         "Input",
            Position:      module.Left,
            Configuration: Input{},
        },
    }

    // Dynamic output ports based on configured routes
    for _, route := range c.settings.Routes {
        ports = append(ports, module.Port{
            Name:          "out_" + route.Name,
            Label:         route.Label,
            Source:        true,
            Position:      module.Right,
            Configuration: Output{},
        })
    }

    return ports
}

Schema Configuration

The Configuration field uses struct tags to define JSON Schema:

Configuration: struct {
    // Basic types
    StringField string  `json:"stringField" title:"String Field"`
    IntField    int     `json:"intField" title:"Integer Field"`
    BoolField   bool    `json:"boolField" title:"Boolean Field"`

    // Validation
    Required string `json:"required" required:"true"`
    MinMax   int    `json:"minMax" minimum:"0" maximum:"100"`
    Pattern  string `json:"pattern" pattern:"^[a-z]+$"`

    // UI hints (formats supported by the editor)
    Textarea string `json:"textarea" format:"textarea"`
    Config   string `json:"config" format:"code" language:"yaml"`

    // Data mapping
    Data any `json:"data" configurable:"true" title:"Data"`

    // Defaults
    WithDefault string `json:"withDefault" default:"hello"`
}{}

See the Struct Tags Reference for the full supported set.


Best Practices

1. Consistent Naming

// Good
"input", "output", "request", "result", "error"

// Avoid
"Input1", "OUTPUT", "my port"

2. Clear Labels

// Good
Label: "HTTP Request"
Label: "Result"

// Avoid
Label: "out"
Label: "data"

3. Logical Positioning

// Inputs on the left
Position: module.Left

// Outputs on the right
Position: module.Right

// Errors on the bottom
Position: module.Bottom

4. Meaningful Defaults

Configuration: Settings{
    Timeout:    "30s",
    MaxRetries: 3,
}

5. Canonical Error Payloads

// Error ports should use the SDK's shape
Configuration: module.ErrorMessage{}