SDK Installation
The TinySystems SDK provides everything you need to build custom modules and components.
Installation
Start from the Template (Recommended)
The official example-module repository is a GitHub template with the correct layout, entrypoint, Dockerfile, and a working echo component. Click Use this template, clone your copy, and rename the Go module path.
Or Create a Module from Scratch
# Create project directory
mkdir my-module && cd my-module
# Initialize Go module (requires Go 1.25+)
go mod init github.com/myorg/my-module
# Install the SDK
go get github.com/tiny-systems/module@latest
Verify Installation
go list -m github.com/tiny-systems/module
# github.com/tiny-systems/module v0.13.x
SDK Package Overview
The SDK is organized into several packages:
| Package | Import Path | Purpose |
|---|---|---|
| module | github.com/tiny-systems/module/module | Core interfaces (Component, Handler, Result, Port, lifecycle capabilities) |
| registry | github.com/tiny-systems/module/registry | Component registration |
| cli | github.com/tiny-systems/module/cli | CLI commands (run, tools, pre-install, pre-delete) |
| v1alpha1 | github.com/tiny-systems/module/api/v1alpha1 | CRD types and system port names (TinyNode, SettingsPort, ...) |
| evaluator | github.com/tiny-systems/module/pkg/evaluator | Edge expression evaluation |
| utils | github.com/tiny-systems/module/pkg/utils | Helpers (leader checks, port names) |
| errors | github.com/tiny-systems/module/pkg/errors | Low-level error utilities (prefer module.Retryable/module.Permanent) |
Core Interfaces
Component Interface
The main interface you'll implement (from github.com/tiny-systems/module/module):
import "github.com/tiny-systems/module/module"
type Component interface {
// Returns component metadata (name, description, info, tags)
GetInfo() ComponentInfo
// Processes a message arriving on `port`. Emit downstream by calling
// `output`; return its Result (or module.Fail/module.Ok) so responses
// can flow back to blocking callers.
Handle(ctx context.Context, output Handler, port string, message any) Result
// Defines input and output ports
Ports() []Port
// Factory method to create new instances
Instance() Component
}
Handler Function
The callback for emitting to your own ports. It returns a Result — never discard it:
type Handler func(ctx context.Context, port string, data any) Result
Result carries either a payload or an error. Construct it with module.Ok(v) / module.Fail(err); inspect it with .Err(), .Value(), .IsErr(). Handler returns must propagate up the call chain — that is how blocking components (like http_server) receive the response that flows back from downstream nodes. Never call a handler with go output(...): that drops the Result.
Port Definition
Define component ports:
type Port struct {
Name string // Unique lowercase identifier
Label string // Display name in UI
Position Position // module.Top, module.Right, module.Bottom, module.Left
Source bool // true = OUTPUT port (a source of data)
Configuration interface{} // Typed struct the schema is reflected from, e.g. InMessage{}
}
Inputs leave Source false. Configuration carries a typed struct instance (InMessage{} or new(OutMessage)); the framework reflects its JSON schema. The optional Schema field is only for runtime-authored schemas and stays nil in normal components.
Minimal Example
A module is a small Go program: components live in their own packages and self-register in init(); cmd/main.go blank-imports them and wires the SDK CLI into a cobra root command.
// components/echo/echo.go
package echo
import (
"context"
"fmt"
"github.com/tiny-systems/module/module"
"github.com/tiny-systems/module/registry"
)
const (
ComponentName = "echo"
InPort string = "in"
OutPort string = "out"
)
type Context any
type InMessage struct {
Context Context `json:"context" configurable:"true" required:"true" title:"Context" description:"Arbitrary message to be echoed"`
}
type OutMessage struct {
Context Context `json:"context" configurable:"true" title:"Context" description:"Passthrough — echoed unchanged"`
}
type Component struct{}
func (t *Component) Instance() module.Component {
return &Component{}
}
func (t *Component) GetInfo() module.ComponentInfo {
return module.ComponentInfo{
Name: ComponentName,
Description: "Echo",
Info: "Sends the same message as it receives",
Tags: []string{"Echo", "Demo"},
}
}
func (t *Component) Handle(ctx context.Context, handler module.Handler, port string, msg any) module.Result {
if in, ok := msg.(InMessage); ok {
return handler(ctx, OutPort, OutMessage{Context: in.Context})
}
return module.Fail(fmt.Errorf("invalid message"))
}
func (t *Component) Ports() []module.Port {
return []module.Port{
{Name: InPort, Label: "In", Configuration: InMessage{}, Position: module.Left},
{Name: OutPort, Label: "Out", Source: true, Configuration: new(OutMessage), Position: module.Right},
}
}
var _ module.Component = (*Component)(nil)
func init() {
registry.Register(&Component{})
}
// cmd/main.go
package main
import (
"context"
"fmt"
"os"
"os/signal"
"syscall"
"github.com/rs/zerolog"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/tiny-systems/module/cli"
// Import components to register them via init()
_ "github.com/myorg/my-module/components/echo"
)
var rootCmd = &cobra.Command{
Use: "server",
Short: "my-module",
Run: func(cmd *cobra.Command, args []string) {
cmd.Help()
},
}
func main() {
zerolog.SetGlobalLevel(zerolog.InfoLevel)
viper.AutomaticEnv()
if viper.GetBool("debug") {
zerolog.SetGlobalLevel(zerolog.DebugLevel)
}
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
cli.RegisterCommands(rootCmd)
if err := rootCmd.ExecuteContext(ctx); err != nil {
fmt.Printf("command execute error: %v\n", err)
}
}
Running Your Module
Local Development
# Build the module
go build -o my-module ./cmd
# Run locally (connects to current kubectl context)
./my-module run \
--name=my-module \
--version=1.0.0 \
--namespace=tinysystems
--name and --version are required. The gRPC server binds to :0 (random port) by default; the Helm chart sets it to :8483 in-cluster via --grpc-server-bind-address.
With Environment Variables
# Enable OpenTelemetry tracing
OTLP_DSN=http://localhost:4317 ./my-module run \
--name=my-module \
--version=1.0.0 \
--namespace=tinysystems
Available CLI Commands
cli.RegisterCommands adds these commands to your binary:
# Run the module operator
./my-module run --name=my-module --version=1.0.0
# Print info about registered components (--json emits the
# discovery shape for a repo index's components.yaml)
./my-module tools components-info
./my-module tools components-info --json
# Emit the RBAC values block to commit with the module's index entry
./my-module tools rbac-values
# Report Kubernetes calls the declared RBAC does not cover
./my-module tools rbac-check [--strict]
# Helm lifecycle hooks (run by the chart, not by hand)
./my-module pre-install --name=my-module
./my-module pre-delete --name=my-module
There are no build, init, or generate commands — building is plain go build, and images are built with Docker.
See CLI Reference for details.
Dependencies
The SDK brings in these key dependencies:
require (
// Kubernetes
sigs.k8s.io/controller-runtime
k8s.io/client-go
k8s.io/apimachinery
// gRPC (cross-module communication)
google.golang.org/grpc
// NATS JetStream (durable execution, optional)
github.com/nats-io/nats.go
// OpenTelemetry (observability)
go.opentelemetry.io/otel
// Expression evaluation
github.com/tiny-systems/ajson
)
Next Steps
- Hello World Component - Build a complete component
- Project Structure - Organize your module
- Component Interface - Deep dive into the API