Settings and Configuration
Settings allow users to configure component behavior through the visual editor. The SDK provides the _settings system port for this purpose.
Settings Port Overview
┌─────────────────────────────────────────────────────────────────────────────┐
│ SETTINGS FLOW │
└─────────────────────────────────────────────────────────────────────────────┘
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ Visual UI │ │ TinyNode │ │ Component │
│ │───────▶│ │───────▶│ │
│ User edits │ Save │ spec.edges │ Deliver │ Handle() │
│ settings form │ │ [_settings] │ │ _settings │
└───────────────┘ └───────────────┘ └───────────────┘Defining Settings
Settings Struct
go
type Settings struct {
Timeout int `json:"timeout" title:"Timeout (ms)" default:"5000" minimum:"100"`
RetryCount int `json:"retryCount" title:"Retry Count" default:"3" minimum:"0" maximum:"10"`
Endpoint string `json:"endpoint" title:"API Endpoint" format:"uri" required:"true"`
Debug bool `json:"debug" title:"Debug Mode" default:"false"`
}Component with Settings
go
type HTTPClient struct {
settings Settings
}
func (c *HTTPClient) Ports() []module.Port {
return []module.Port{
// Settings port (system port)
{
Name: v1alpha1.SettingsPort, // "_settings"
Label: "Settings",
Source: true,
Position: module.PositionTop,
Schema: schema.FromGo(Settings{}),
},
// Other ports...
{
Name: "request",
Label: "Request",
Source: true,
Position: module.PositionLeft,
Schema: schema.FromGo(Request{}),
},
}
}
func (c *HTTPClient) Handle(ctx context.Context, output module.Handler, port string, msg any) error {
switch port {
case v1alpha1.SettingsPort:
c.settings = msg.(Settings)
return nil
case "request":
return c.handleRequest(ctx, output, msg)
}
return nil
}
func (c *HTTPClient) handleRequest(ctx context.Context, output module.Handler, msg any) error {
// Use settings
client := &http.Client{
Timeout: time.Duration(c.settings.Timeout) * time.Millisecond,
}
req := msg.(Request)
req.URL = c.settings.Endpoint + req.Path
// Make request with configured client
// ...
}Settings Schema Tags
Basic Tags
| Tag | Description | Example |
|---|---|---|
json | Field name in JSON | json:"fieldName" |
title | Display label | title:"Field Name" |
description | Help text | description:"Enter value" |
default | Default value | default:"100" |
required | Mark required | required:"true" |
Validation Tags
| Tag | Description | Example |
|---|---|---|
minimum | Min number value | minimum:"0" |
maximum | Max number value | maximum:"100" |
minLength | Min string length | minLength:"1" |
maxLength | Max string length | maxLength:"255" |
pattern | Regex pattern | pattern:"^[a-z]+$" |
enum | Allowed values | enum:"GET,POST,PUT" |
format | Format validation | format:"email" |
UI Tags
| Tag | Description | Example |
|---|---|---|
propertyOrder | Field order | propertyOrder:"1" |
enumTitles | Enum display names | enumTitles:"Get,Post,Put" |
widget | Custom widget | widget:"textarea" |
Settings Examples
Enum Field
go
type Settings struct {
Method string `json:"method" title:"HTTP Method" enum:"GET,POST,PUT,DELETE" default:"GET"`
}Nested Settings
go
type Settings struct {
Server ServerSettings `json:"server" title:"Server Configuration"`
Logging LoggingSettings `json:"logging" title:"Logging Options"`
}
type ServerSettings struct {
Host string `json:"host" title:"Host" default:"localhost"`
Port int `json:"port" title:"Port" default:"8080" minimum:"1" maximum:"65535"`
}
type LoggingSettings struct {
Level string `json:"level" title:"Log Level" enum:"debug,info,warn,error" default:"info"`
Format string `json:"format" title:"Format" enum:"json,text" default:"json"`
}Array Settings
go
type Settings struct {
Endpoints []EndpointConfig `json:"endpoints" title:"Endpoints" minItems:"1"`
}
type EndpointConfig struct {
Name string `json:"name" title:"Name" required:"true"`
URL string `json:"url" title:"URL" format:"uri" required:"true"`
Timeout int `json:"timeout" title:"Timeout (ms)" default:"5000"`
}Optional Fields
go
type Settings struct {
// Required
APIKey string `json:"apiKey" title:"API Key" required:"true"`
// Optional (omitempty)
CustomHeader string `json:"customHeader,omitempty" title:"Custom Header"`
}Secret Fields
go
type Settings struct {
// Marked as password in UI
Password string `json:"password" title:"Password" format:"password"`
// Or using configRef for Kubernetes secrets
APIKeyRef ConfigRef `json:"apiKeyRef" title:"API Key (from Secret)"`
}
type ConfigRef struct {
SecretName string `json:"secretName" title:"Secret Name"`
Key string `json:"key" title:"Key"`
}Settings in Handle()
Basic Pattern
go
func (c *Component) Handle(ctx context.Context, output module.Handler, port string, msg any) error {
switch port {
case v1alpha1.SettingsPort:
c.settings = msg.(Settings)
// Optionally reinitialize resources
c.reinitialize()
return nil
case "input":
// Settings guaranteed to be set before input arrives
return c.processWithSettings(ctx, output, msg)
}
return nil
}Settings with Validation
go
func (c *Component) Handle(ctx context.Context, output module.Handler, port string, msg any) error {
if port == v1alpha1.SettingsPort {
settings := msg.(Settings)
// Validate settings
if err := c.validateSettings(settings); err != nil {
// Log warning but accept settings
log.Warn("invalid settings", "error", err)
}
c.settings = settings
return nil
}
return nil
}
func (c *Component) validateSettings(s Settings) error {
if s.Timeout < 100 {
return fmt.Errorf("timeout must be at least 100ms")
}
if s.Endpoint == "" {
return fmt.Errorf("endpoint is required")
}
return nil
}Settings-Triggered Initialization
go
type Server struct {
settings Settings
listener net.Listener
mu sync.Mutex
}
func (s *Server) Handle(ctx context.Context, output module.Handler, port string, msg any) error {
if port == v1alpha1.SettingsPort {
settings := msg.(Settings)
s.mu.Lock()
defer s.mu.Unlock()
// Port changed? Restart listener
if s.settings.Port != settings.Port && s.listener != nil {
s.listener.Close()
s.listener = nil
}
s.settings = settings
return nil
}
return nil
}Settings Delivery Order
Settings are delivered before other messages:
┌─────────────────────────────────────────────────────────────────────────────┐
│ MESSAGE DELIVERY ORDER │
└─────────────────────────────────────────────────────────────────────────────┘
1. Node created in Kubernetes
│
▼
2. _settings delivered first
│ Handle(ctx, output, "_settings", Settings{...})
│
▼
3. _reconcile delivered
│ Handle(ctx, output, "_reconcile", TinyNode{...})
│
▼
4. Regular messages delivered
Handle(ctx, output, "input", Message{...})Dynamic Settings
Settings Affect Ports
go
type DynamicRouter struct {
settings Settings
}
type Settings struct {
Routes []string `json:"routes" title:"Route Names"`
}
func (r *DynamicRouter) Ports() []module.Port {
ports := []module.Port{
{
Name: v1alpha1.SettingsPort,
Label: "Settings",
Source: true,
Position: module.PositionTop,
Schema: schema.FromGo(Settings{}),
},
{
Name: "input",
Label: "Input",
Source: true,
Position: module.PositionLeft,
Schema: schema.FromGo(Message{}),
},
}
// Generate ports from settings
for _, route := range r.settings.Routes {
ports = append(ports, module.Port{
Name: route,
Label: route,
Source: false,
Position: module.PositionRight,
Schema: schema.FromGo(Message{}),
})
}
return ports
}Settings with Expressions
Settings can use expressions for dynamic values:
yaml
# In TinyNode edge
- from: _settings
data:
endpoint: "{{$.env.API_URL}}"
timeout: "{{$.config.defaultTimeout}}"Best Practices
1. Sensible Defaults
go
type Settings struct {
// Good: Reasonable defaults
Timeout int `json:"timeout" default:"5000"`
Retries int `json:"retries" default:"3"`
// Bad: No defaults, requires user action
Timeout int `json:"timeout"`
}2. Clear Descriptions
go
type Settings struct {
// Good: Clear what to enter
APIKey string `json:"apiKey" title:"API Key" description:"Your API key from the dashboard settings page"`
// Bad: No guidance
Key string `json:"key"`
}3. Appropriate Validation
go
type Settings struct {
// Good: Constrained to valid values
Port int `json:"port" minimum:"1" maximum:"65535" default:"8080"`
// Good: Format validation
Email string `json:"email" format:"email"`
// Good: Enum for known values
LogLevel string `json:"logLevel" enum:"debug,info,warn,error"`
}4. Thread Safety
go
type Component struct {
settings Settings
mu sync.RWMutex
}
func (c *Component) Handle(ctx context.Context, output module.Handler, port string, msg any) error {
if port == v1alpha1.SettingsPort {
c.mu.Lock()
c.settings = msg.(Settings)
c.mu.Unlock()
return nil
}
// Read with lock
c.mu.RLock()
settings := c.settings
c.mu.RUnlock()
return c.processWithSettings(ctx, output, msg, settings)
}Next Steps
- Control Ports - UI buttons and actions
- System Ports - All system ports
- Schema Definition - Advanced schemas