Skip to content
Ovsep Avakian edited this page Jun 1, 2026 · 2 revisions

Overview

Ports are the connection points of components in F-Mesh. Unlike systems that follow the actor model (e.g., Akka), where components communicate directly, F-Mesh components are isolated and communicate only through signals passed via ports.

This design promotes modularity and separation of concerns: a component only interacts with its own ports and does not need to know where its signals are routed. The abstraction ensures that the logic and behavior of each component remain self-contained and decoupled from the overall mesh structure.

You can explore the full API for ports here.

Types of Ports

There are two types of ports in F-Mesh:

  1. Input Ports: Receive signals for the component to process.
  2. Output Ports: Send signals to connected components.

Both input and output ports are instances of the same underlying type, Port.
However, F-Mesh enforces type-safe connections:

  • Pipes cannot be created between two input ports or two output ports.
  • Connections are strictly from an output port to an input port.

Creating Ports

There are two approaches to creating ports:

Approach 1: Using Component Methods (Simple)

The most common way is to use WithInputs() / WithOutputs() options or AddInputs() / AddOutputs() on components:

c, err := component.New("processor",
    component.WithInputs("data", "config", "control"),
    component.WithOutputs("result", "logs"),
    component.WithActivationFunc(myFunc),
)

Or after construction:

c, _ := component.New("processor", component.WithActivationFunc(myFunc))
_ = c.AddInputs("data", "config", "control")
_ = c.AddOutputs("result", "logs")

This is simple and works well for most cases.

Approach 2: Creating Ports Directly (Advanced)

For more control, you can create ports directly with descriptions and labels:

// Create ports with configuration
dataPort, _ := port.NewInput("data",
    port.WithDescription("Primary data input for processing"),
    port.WithLabel("type", "primary"),
    port.WithLabel("required", "true"),
)

configPort, _ := port.NewInput("config",
    port.WithDescription("Configuration parameters"),
)
configPort.SetLabels(map[string]string{
    "type":     "configuration",
    "optional": "true",
})

resultPort, _ := port.NewOutput("result",
    port.WithDescription("Processing results"),
)
resultPort.AddLabel("type", "data")

logsPort, _ := port.NewOutput("logs",
    port.WithDescription("Diagnostic logs"),
)
logsPort.AddLabel("type", "diagnostic")

// Add pre-configured ports to component
c, _ := component.New("processor", component.WithActivationFunc(myFunc))
_ = c.AttachInputPorts(dataPort, configPort)
_ = c.AttachOutputPorts(resultPort, logsPort)

This approach is useful when you need to:

  • Add descriptions for documentation or export
  • Configure labels during port creation
  • Set up complex port configurations
  • Reuse port configurations

Internal Structure

Each port has five core elements:

  1. Name: Unique name used to access port.
  2. Signal Buffer: Holds an unlimited number of signals.
  3. Pipes List: Tracks outbound connections to other ports.
  4. Labels: String key-value metadata for categorizing and filtering ports. See the Labels documentation for details.
  5. Scalars: Numeric metadata (stringfloat64).

Important

The port buffer does not guarantee any specific order for the signals it holds.
Your program must not rely on the observed order of signals.

Adding Signals

Ports accept signals via two methods:

// From existing signal objects
err := port.PutSignals(signal.New("data"), signal.New(42))

// From raw payloads (convenience)
err := port.PutPayloads("data", 42)

Both methods return an error and trigger port hooks if configured.

Port direction

Every port has a Direction: DirectionIn, DirectionOut, or DirectionUndefined (misconfigured). Use IsInput() / IsOutput() in logic. Piping validates direction at call time — connecting two outputs or two inputs returns port.ErrInvalidPipeDirection.

Signal forwarding helpers

Package-level functions copy signals between ports without clearing the source:

// Copy all signals
err := port.ForwardSignals(source, dest)

// Copy only matching signals
err := port.ForwardWithFilter(source, dest, signal.LabelEquals("type", "data"))

// Transform then copy
err := port.ForwardWithMap(source, dest, func(s *signal.Signal) *signal.Signal {
    return s.MapPayload(func(p any) any { return strings.ToUpper(p.(string)) })
})

PutSignalGroups adds all signals from one or more signal.Group values:

_ = port.PutSignalGroups(group1, group2)

Port metadata and description

Ports support SetDescription (method) and WithDescription (constructor option). Labels and scalars use the mutating API (AddLabel, SetLabels, AddScalar, etc.) — see Labels page.

ParentComponent() returns the owning component (as a ParentComponent interface with Name()).

Port errors

Error When
ErrInvalidPipeDirection PipeTo between same-direction ports
ErrWrongPortDirection Attaching output port as input (or vice versa)
ErrNilPort Nil port in pipe validation
ErrInvalidRangeForIndexedGroup Invalid index range for indexed port creation

Port hooks

Ports support hooks that allow you to intercept signal operations. Hooks are set up on individual ports:

c, _ := component.New("logger",
    component.WithInputs("data"),
    component.WithActivationFunc(myFunc),
)

// Setup hook on individual port
c.InputByName("data").SetupHooks(func(h *port.Hooks) {
    h.OnSignalsAdded(func(ctx *port.SignalsAddedContext) error {
        fmt.Printf("Port '%s' received %d signals\n", 
            ctx.Port.Name(), 
            len(ctx.SignalsAdded))
        return nil
    })
})

Port hooks are useful for:

  • Tracking signals flowing through specific ports
  • Validating signal payloads
  • Debugging signal flow
  • Collecting metrics

For complete documentation on hooks at all levels (F-Mesh, Component, and Port), see the Hooks page.


Working with Groups and Collections

When working with multiple ports, F-Mesh provides two utility types: Group and Collection:

  • Collection - Indexed structure where ports are accessed by name
  • Group - Ordered structure for iterating over ports without names

These are part of F-Mesh's consistent API for managing multiple entities. For complete documentation on how Collections and Groups work across F-Mesh, see the Collections and Groups page.

port.Group and indexed ports

port.Group is an ordered list of ports — used for outbound pipes on a port and for batch operations. Create groups with:

group := port.NewGroup("p1", "p2", "p3")
indexed, err := port.NewIndexedGroup("worker", 0, 3) // worker0 .. worker3

port.Group supports Without, ForEachIf, MapIf, and scalar aggregation (SumScalar, MinScalar, MaxScalar, AvgScalar) across contained ports.

Clone this wiki locally