Component Interface Reference
Complete reference for the module.Component interface that all TinySystems components must implement.
Interface Definition
go
type Component interface {
// GetInfo returns component metadata
GetInfo() Info
// Handle processes incoming messages
Handle(ctx context.Context, output Handler, port string, message any) error
// Ports returns the component's port definitions
Ports() []Port
// Instance creates a new component instance
Instance() Component
}Methods
GetInfo
Returns metadata about the component.
go
func (c *Component) GetInfo() module.InfoReturns: module.Info
go
type Info struct {
Name string // Unique component identifier
Description string // Human-readable description
Icon string // Icon name for UI
Tags []string // Categorization tags
}Example:
go
func (c *MyComponent) GetInfo() module.Info {
return module.Info{
Name: "my-transformer",
Description: "Transforms incoming data based on configuration",
Icon: "IconTransform",
Tags: []string{"transform", "data", "utility"},
}
}Notes:
Namemust be unique within the moduleNameshould use kebab-caseIconreferences Tabler Icons (e.g., "IconBox", "IconRouter")
Handle
Processes incoming messages on a specific port.
go
func (c *Component) Handle(
ctx context.Context,
output Handler,
port string,
message any,
) errorParameters:
| Parameter | Type | Description |
|---|---|---|
| ctx | context.Context | Request context with tracing, cancellation |
| output | Handler | Function to emit output messages |
| port | string | Name of the port receiving the message |
| message | any | The incoming message data |
Returns: error
nil- Successerror- Failure (will be retried)errors.PermanentError(err)- Permanent failure (no retry)
Example:
go
func (c *MyComponent) Handle(
ctx context.Context,
output module.Handler,
port string,
message any,
) error {
switch port {
case v1alpha1.SettingsPort:
c.settings = message.(Settings)
return nil
case "input":
input := message.(Input)
result, err := c.process(input)
if err != nil {
return err
}
return output(ctx, "output", result)
default:
return fmt.Errorf("unknown port: %s", port)
}
}Context Values:
go
// Check if this instance is the leader
if utils.IsLeader(ctx) {
// Leader-only logic
}
// Get tracing span
span := trace.SpanFromContext(ctx)
span.SetAttributes(attribute.String("key", "value"))Ports
Returns the component's port definitions.
go
func (c *Component) Ports() []module.PortReturns: []module.Port
go
type Port struct {
Name string // Unique port identifier
Label string // Display label
Position Position // UI position (Left, Right, Top, Bottom)
Source bool // true = input, false = output
Configuration interface{} // Schema definition struct
}Example:
go
func (c *MyComponent) Ports() []module.Port {
return []module.Port{
{
Name: v1alpha1.SettingsPort,
Label: "Settings",
Position: module.PositionTop,
Source: true,
Configuration: Settings{},
},
{
Name: "input",
Label: "Input",
Position: module.PositionLeft,
Source: true,
Configuration: Input{},
},
{
Name: "output",
Label: "Output",
Position: module.PositionRight,
Source: false,
Configuration: Output{},
},
{
Name: "error",
Label: "Error",
Position: module.PositionBottom,
Source: false,
Configuration: ErrorOutput{},
},
}
}Instance
Creates a new instance of the component.
go
func (c *Component) Instance() module.ComponentReturns: module.Component - A new, independent instance
Example:
go
func (c *MyComponent) Instance() module.Component {
return &MyComponent{}
}Notes:
- Must return a fresh instance
- Do not share state between instances
- Each TinyNode gets its own instance
Optional Interfaces
ControlHandler
For components with control ports (UI buttons).
go
type ControlHandler interface {
HandleControl(ctx context.Context, state any, port string, msg any) (any, error)
}Example:
go
func (c *MyComponent) HandleControl(
ctx context.Context,
state any,
port string,
msg any,
) (any, error) {
switch port {
case "start":
c.start()
return &ControlState{Running: true}, nil
case "stop":
c.stop()
return &ControlState{Running: false}, nil
}
return state, nil
}ReconcileHandler
For components that need reconciliation events.
go
type ReconcileHandler interface {
HandleReconcile(ctx context.Context, node *v1alpha1.TinyNode) error
}Example:
go
func (c *MyComponent) HandleReconcile(
ctx context.Context,
node *v1alpha1.TinyNode,
) error {
// React to TinyNode changes
if node.Status.Metadata != nil {
if port, ok := node.Status.Metadata["http-port"]; ok {
c.listenPort = port
}
}
return nil
}Handler Function
The output handler for emitting messages.
go
type Handler func(ctx context.Context, port string, message any) errorParameters:
| Parameter | Type | Description |
|---|---|---|
| ctx | context.Context | Must pass through context |
| port | string | Output port name |
| message | any | Data to emit |
Returns: error
nil- Message delivered successfullyerror- Delivery failed
Blocking Behavior:
go
// Default: blocks until downstream completes
err := output(ctx, "output", result)
// Execution continues after all downstream processing
// Non-blocking: use goroutine
go func() {
ctx := trace.ContextWithSpanContext(context.Background(),
trace.SpanContextFromContext(originalCtx))
output(ctx, "output", result)
}()Complete Example
go
package mycomponent
import (
"context"
"fmt"
"github.com/tiny-systems/module/api/v1alpha1"
"github.com/tiny-systems/module/module"
"github.com/tiny-systems/module/pkg/schema"
)
type Component struct {
settings Settings
}
type Settings struct {
Multiplier int `json:"multiplier" default:"1" title:"Multiplier"`
}
type Input struct {
Value int `json:"value" required:"true"`
}
type Output struct {
Result int `json:"result"`
}
func (c *Component) GetInfo() module.Info {
return module.Info{
Name: "multiplier",
Description: "Multiplies input value by configured multiplier",
Icon: "IconX",
Tags: []string{"math", "transform"},
}
}
func (c *Component) Ports() []module.Port {
return []module.Port{
{
Name: v1alpha1.SettingsPort,
Label: "Settings",
Position: module.PositionTop,
Source: true,
Configuration: Settings{Multiplier: 1},
},
{
Name: "input",
Label: "Input",
Position: module.PositionLeft,
Source: true,
Configuration: Input{},
},
{
Name: "output",
Label: "Output",
Position: module.PositionRight,
Source: false,
Configuration: Output{},
},
}
}
func (c *Component) Handle(
ctx context.Context,
output module.Handler,
port string,
message any,
) error {
switch port {
case v1alpha1.SettingsPort:
c.settings = message.(Settings)
return nil
case "input":
input := message.(Input)
result := input.Value * c.settings.Multiplier
return output(ctx, "output", Output{Result: result})
default:
return fmt.Errorf("unknown port: %s", port)
}
}
func (c *Component) Instance() module.Component {
return &Component{}
}