introspection

package module
v0.1.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 15, 2026 License: GPL-3.0 Imports: 7 Imported by: 11

README

Introspection

Go Report Card Go Reference License Release

A domain-agnostic Go package for component state introspection, monitoring, and visualization.

Overview

Introspection solves the generic problem of "how any Go component publishes its internal state for visualization and monitoring". Originally extracted from the lifecycle project, this package provides:

  • Domain-Agnostic Design: No hardcoded terminology like "worker" or "signal" - works with any domain
  • Introspectable Interface: Components can expose their internal state
  • TypedWatcher[S]: Type-safe state watching with generics
  • State Aggregation: Combine state changes from multiple components
  • Customizable Mermaid Diagrams: Automatic visualization with full control over labels, styles, and structure

This package is useful for any project that wants to auto-document its topology at runtime and enable live monitoring and debugging without being tied to specific domain terminology.

Key Principle: Composability Over Context

The package emphasizes composability - you define your domain, we provide the observation layer. No assumptions about "workers", "signals", or any other specific domain concepts.

Features

1. Introspectable Components

Components implement the Introspectable interface to expose their state:

type Introspectable interface {
    State() any
}
2. Type-Safe State Watching

The TypedWatcher[S] interface provides type-safe state change notifications:

type TypedWatcher[S any] interface {
    State() S
    Watch(ctx context.Context) <-chan StateChange[S]
}
3. State Aggregation

Aggregate state changes from multiple components into a unified stream:

snapshots := introspection.AggregateWatchers(ctx, component1, component2, component3)
for snapshot := range snapshots {
    fmt.Printf("Component %s changed state\n", snapshot.ComponentID)
}
4. Generic Mermaid Diagram Generation (Domain-Agnostic)

Generate Mermaid diagrams with full customization - no hardcoded labels or terminology:

// Generic TreeDiagram - works with any hierarchical structure
config := &introspection.DiagramConfig{
    SecondaryID: "root",
}
diagram := introspection.TreeDiagram(hierarchyState, config)

// ComponentDiagram - fully customizable labels
config := &introspection.DiagramConfig{
    PrimaryID:        "controller",
    PrimaryLabel:     "Control Layer",
    PrimaryNodeLabel: "🎮 Controller",
    SecondaryID:      "workers",
    SecondaryLabel:   "Worker Pool",
    ConnectionLabel:  "manages",
}
diagram := introspection.ComponentDiagram(controllerState, workerState, config)

// StateMachineDiagram - custom state names and transitions
smConfig := &introspection.StateMachineConfig{
    InitialState:      "Active",
    GracefulState:     "Draining",
    ForcedState:       "Terminated",
    InitialToGraceful: "SHUTDOWN",
    GracefulToForced:  "KILL",
}
stateMachine := introspection.StateMachineDiagram(state, smConfig)

Backward Compatible: Legacy functions (WorkerTreeDiagram, SignalStateMachine, SystemDiagram) remain available but are deprecated in favor of the generic versions.

Installation

go get github.com/aretw0/introspection

Quick Start

package main

import (
    "context"
    "fmt"
    "github.com/aretw0/introspection"
)

// Define your component's state
type MyComponentState struct {
    Name   string
    Status string
}

// Implement TypedWatcher
type MyComponent struct {
    state MyComponentState
    changes chan introspection.StateChange[MyComponentState]
}

func (c *MyComponent) ComponentType() string {
    return "processor"  // Use your own domain terminology
}

func (c *MyComponent) State() MyComponentState {
    return c.state
}

func (c *MyComponent) Watch(ctx context.Context) <-chan introspection.StateChange[MyComponentState] {
    // Return a channel that sends state changes
    // (implementation details omitted for brevity)
}

func main() {
    component := &MyComponent{
        state: MyComponentState{Name: "processor-1", Status: "Running"},
    }
    
    // Watch state changes
    ctx := context.Background()
    changes := component.Watch(ctx)
    
    for change := range changes {
        fmt.Printf("State changed: %v -> %v\n", 
            change.OldState, change.NewState)
    }
}

Examples

Generic Example (Custom Task Scheduler Domain)

See the examples/generic directory for a complete working example demonstrating domain-agnostic usage with a Task Scheduler domain (no worker/signal terminology):

  • Custom domain types (Scheduler, Tasks)
  • Generic TreeDiagram, ComponentDiagram, StateMachineDiagram
  • Custom node styling and labeling
  • Fully customized labels and terminology

Run the example:

cd examples/generic
go run main.go
Basic Example (Legacy Worker/Signal Domain)

See the examples/basic directory for a complete working example using the original worker/signal domain that demonstrates:

  • Implementing the Introspectable interface
  • Using TypedWatcher for type-safe state watching
  • Aggregating state changes from multiple components
  • Generating Mermaid diagrams (legacy functions)

Run the example:

cd examples/basic
go run main.go

Use Cases

  • Observability: Monitor the state of distributed system components in any domain
  • Debugging: Track state transitions in real-time
  • Documentation: Auto-generate system topology diagrams with your terminology
  • Testing: Verify component behavior through state inspection
  • Monitoring: Build dashboards that visualize component states
  • Domain Modeling: Express your system's architecture without framework constraints

Key Interfaces

Introspectable
type Introspectable interface {
    State() any
}
Component
type Component interface {
    ComponentType() string
}
TypedWatcher[S]
type TypedWatcher[S any] interface {
    State() S
    Watch(ctx context.Context) <-chan StateChange[S]
}
EventSource
type EventSource interface {
    Events(ctx context.Context) <-chan ComponentEvent
}

Core Types

StateChange[S]
type StateChange[S any] struct {
    ComponentID   string
    ComponentType string
    OldState      S
    NewState      S
    Timestamp     time.Time
}
StateSnapshot
type StateSnapshot struct {
    ComponentID   string
    ComponentType string
    Timestamp     time.Time
    Payload       any
}

Visualization

The package includes powerful Mermaid diagram generation capabilities:

Domain-Agnostic Configuration

Control every aspect of diagram generation through configuration:

// Custom node styling based on your domain
config := &introspection.DiagramConfig{
    NodeStyler: func(metadata map[string]string) (icon, shapeStart, shapeEnd, cssClass string) {
        switch metadata["type"] {
        case "database":
            return "🗄️", "[(", ")]", "container"
        case "api":
            return "🌐", "[/", "/]", "process"
        default:
            return "📋", "[", "]", "process"
        }
    },
    NodeLabeler: func(name, status string, pid int, metadata map[string]string, icon string) string {
        return fmt.Sprintf("%s %s [%s]", icon, name, status)
    },
}
Default Styles

The package comes with pre-defined Mermaid styles for common component states:

  • Running (blue)
  • Stopped (gray)
  • Failed (red)
  • Pending (purple)
  • And more...
Custom Styles

You can customize the appearance with your own Mermaid class definitions:

diagram := introspection.TreeDiagram(
    state,
    config,
    introspection.WithStyles("classDef custom fill:#fff;"),
)

Design Philosophy

Composability Over Context

The package is designed around the principle that generic observation mechanisms should not dictate domain terminology. Instead of hardcoding terms like "worker", "signal", or "supervisor":

  • You define your domain (tasks, services, processors, etc.)
  • You configure the visualization (labels, icons, connections)
  • You compose the observation layer (watchers, aggregators, diagrams)

This approach enables:

  • True reusability across different domains
  • No conceptual coupling to specific architectures
  • Full control over terminology and presentation
  • Backward compatibility with legacy code

Testing

Run tests:

go test -v

Run tests with coverage:

go test -v -cover

Documentation

For more detailed information:

Origin

This package was extracted from the lifecycle project and made domain-agnostic to serve as a standalone, reusable component for any Go project that needs state introspection and monitoring capabilities.

License

See LICENSE file for details.

Documentation

Overview

Package introspection provides a domain-agnostic observation layer for Go applications. It enables components to expose their internal state for visualization, monitoring, and debugging without coupling to specific domain concepts.

Core Concepts

The library is built around three pillars:

  1. State Exposure: Components implement simple interfaces to expose their state without domain-specific terminology.

    type Introspectable interface { State() any }

    type Component interface { ComponentType() string }

  2. Type-Safe Watching: Components can publish state changes through type-safe channels using Go generics.

    type TypedWatcher[S any] interface { State() S Watch(ctx context.Context) <-chan StateChange[S] }

  3. Automatic Visualization: Generate Mermaid diagrams from component state with full customization.

    config := &DiagramConfig{ SecondaryID: "components", } diagram := TreeDiagram(state, config)

Key Interfaces

The package defines minimal interfaces for maximum flexibility:

  • Introspectable: Exposes component state
  • Component: Identifies component type
  • TypedWatcher[S]: Type-safe state change notifications
  • EventSource: Event-based notifications

Visualization

The package includes powerful Mermaid diagram generation:

  • TreeDiagram: Hierarchical component structures
  • ComponentDiagram: Relationships between component types
  • StateMachineDiagram: Component lifecycle and transitions

All visualization is fully customizable through configuration:

config := &DiagramConfig{
	PrimaryID:        "scheduler",
	PrimaryLabel:     "Task Scheduler",
	PrimaryNodeLabel: "🗓️ Scheduler",
	SecondaryID:      "tasks",
	SecondaryLabel:   "Active Tasks",
	ConnectionLabel:  "schedules",
}

State Aggregation

Combine state changes from multiple components:

watchers := []TypedWatcher[MyState]{component1, component2, component3}
snapshots := AggregateWatchers(ctx, watchers...)
for snapshot := range snapshots {
	// Process state changes
}

Design Philosophy

The package emphasizes:

  • Domain Agnostic: No hardcoded terminology - you define your domain
  • Type Safety: Leverage Go generics for compile-time safety
  • Composability: Small, focused interfaces that compose well
  • Zero Dependencies: Standard library only
  • Backward Compatible: Legacy APIs remain available

Examples

See the examples directory for complete working examples:

  • examples/basic: Legacy worker/signal domain
  • examples/generic: Domain-agnostic task scheduler

Documentation

For detailed documentation:

  • docs/TECHNICAL.md: Architecture and design
  • docs/PRODUCT.md: Vision and use cases
  • docs/DECISIONS.md: Design rationale
  • docs/CONFIGURATION.md: Configuration philosophy
  • docs/RECIPES.md: Common usage patterns
Example

Example demonstrates basic usage of the introspection package for observing and visualizing component state.

// Define a simple component state
type ServiceState struct {
	Name   string
	Status string
}

// Create a component that implements TypedWatcher
service := &simpleWatcher[ServiceState]{
	state: ServiceState{Name: "API", Status: "Running"},
	ch:    make(chan introspection.StateChange[ServiceState], 1),
}

// Watch for state changes
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel()

changes := service.Watch(ctx)

// Trigger a state change
service.UpdateState(ServiceState{Name: "API", Status: "Stopped"})

// Observe the change
select {
case change := <-changes:
	fmt.Printf("State changed: %s -> %s\n", change.OldState.Status, change.NewState.Status)
case <-time.After(50 * time.Millisecond):
}
Output:

State changed: Running -> Stopped

Index

Examples

Constants

This section is empty.

Variables

View Source
var Version string

Functions

func AggregateEvents

func AggregateEvents(ctx context.Context, sources ...EventSource) <-chan ComponentEvent

AggregateEvents combines multiple event sources into a unified event stream.

Example

ExampleAggregateEvents demonstrates combining events from multiple event sources into a single stream.

package main

import (
	"context"
	"fmt"
	"time"

	introspection "github.com/aretw0/introspection"
)

func main() {
	// Create mock event sources
	source1 := &mockEventSource{
		id: "source-1",
		ch: make(chan introspection.ComponentEvent, 1),
	}
	source2 := &mockEventSource{
		id: "source-2",
		ch: make(chan introspection.ComponentEvent, 1),
	}

	ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
	defer cancel()

	events := introspection.AggregateEvents(ctx, source1, source2)

	// Send events from both sources
	source1.SendEvent(&mockEvent{id: "source-1", eventType: "started"})
	source2.SendEvent(&mockEvent{id: "source-2", eventType: "connected"})

	// Collect events
	count := 0
	for range events {
		count++
		if count >= 2 {
			cancel()
		}
	}

	fmt.Printf("Received %d events\n", count)

}

// mockEventSource is a simple EventSource implementation for examples.
type mockEventSource struct {
	id string
	ch chan introspection.ComponentEvent
}

func (m *mockEventSource) Events(ctx context.Context) <-chan introspection.ComponentEvent {
	out := make(chan introspection.ComponentEvent)
	go func() {
		defer close(out)
		for {
			select {
			case event := <-m.ch:
				select {
				case out <- event:
				case <-ctx.Done():
					return
				}
			case <-ctx.Done():
				return
			}
		}
	}()
	return out
}

func (m *mockEventSource) SendEvent(event introspection.ComponentEvent) {
	m.ch <- event
}

// mockEvent is a simple ComponentEvent implementation for examples.
type mockEvent struct {
	id        string
	eventType string
}

func (e *mockEvent) ComponentID() string {
	return e.id
}

func (e *mockEvent) ComponentType() string {
	return "service"
}

func (e *mockEvent) Timestamp() time.Time {
	return time.Now()
}

func (e *mockEvent) EventType() string {
	return e.eventType
}
Output:

Received 2 events

func AggregateWatchers

func AggregateWatchers(ctx context.Context, watchers ...interface{}) <-chan StateSnapshot

AggregateWatchers combines multiple typed watchers into a unified snapshot stream.

Example

ExampleAggregateWatchers demonstrates combining state changes from multiple components into a single stream.

type ServiceState struct {
	Name   string
	Status string
}

// Create multiple watchers
service1 := &simpleWatcher[ServiceState]{
	id:    "service-1",
	state: ServiceState{Name: "API", Status: "Running"},
	ch:    make(chan introspection.StateChange[ServiceState], 1),
}

service2 := &simpleWatcher[ServiceState]{
	id:    "service-2",
	state: ServiceState{Name: "Worker", Status: "Running"},
	ch:    make(chan introspection.StateChange[ServiceState], 1),
}

// Aggregate state changes from both services
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel()

snapshots := introspection.AggregateWatchers(ctx, service1, service2)

// Trigger state changes
service1.UpdateState(ServiceState{Name: "API", Status: "Stopped"})
service2.UpdateState(ServiceState{Name: "Worker", Status: "Idle"})

// Collect snapshots
count := 0
for range snapshots {
	count++
	if count >= 2 {
		cancel()
	}
}

fmt.Printf("Received %d snapshots\n", count)
Output:

Received 2 snapshots

func ComponentDiagram

func ComponentDiagram(primary, secondary any, config *DiagramConfig, opts ...MermaidOption) string

ComponentDiagram renders a customizable topology diagram with two components. This is a generic version that allows full customization of labels and styling.

Example

ExampleComponentDiagram demonstrates creating a diagram showing relationships between two components.

package main

import (
	"fmt"

	introspection "github.com/aretw0/introspection"
)

func main() {
	type Controller struct {
		Name string
	}

	type Worker struct {
		ID     int
		Status string
	}

	controller := Controller{Name: "MainController"}
	worker := Worker{ID: 1, Status: "Active"}

	config := introspection.DefaultDiagramConfig()
	config.PrimaryID = "controller"
	config.PrimaryLabel = "Controller"
	config.SecondaryID = "worker"
	config.SecondaryLabel = "Worker"
	config.ConnectionLabel = "manages"

	diagram := introspection.ComponentDiagram(controller, worker, config)

	fmt.Println(len(diagram) > 0)

}
Output:

true

func DefaultStyles

func DefaultStyles() string

DefaultStyles returns the standard Mermaid class definitions for lifecycle diagrams.

func SignalStateMachine

func SignalStateMachine(sig any, opts ...MermaidOption) string

SignalStateMachine renders a Mermaid state diagram for the signal context. Deprecated: Use StateMachineDiagram with custom StateMachineConfig for domain-agnostic diagrams.

Example

ExampleSignalStateMachine demonstrates the legacy SignalStateMachine function for backward compatibility with the signal domain.

package main

import (
	"fmt"

	introspection "github.com/aretw0/introspection"
)

func main() {
	type SignalState struct {
		Enabled            bool
		Stopping           bool
		ForceExitThreshold int
	}

	state := SignalState{
		Enabled:            true,
		ForceExitThreshold: 2,
	}

	diagram := introspection.SignalStateMachine(state)

	fmt.Println(len(diagram) > 0)

}
Output:

true

func StateMachineDiagram

func StateMachineDiagram(state any, config *StateMachineConfig, opts ...MermaidOption) string

StateMachineDiagram renders a customizable Mermaid state diagram. It introspects the state object via reflection to find relevant fields.

Example

ExampleStateMachineDiagram demonstrates generating a state machine visualization for a component.

package main

import (
	"fmt"

	introspection "github.com/aretw0/introspection"
)

func main() {
	type ProcessState struct {
		Running bool
		Stopped bool
	}

	state := ProcessState{Running: true}

	config := introspection.DefaultStateMachineConfig()
	config.InitialState = "Idle"
	config.GracefulState = "Stopping"
	config.ForcedState = "Killed"
	config.InitialToGraceful = "STOP"
	config.GracefulToForced = "KILL"
	config.GracefulToFinal = "Exit"

	diagram := introspection.StateMachineDiagram(state, config)

	fmt.Println(len(diagram) > 0)

}
Output:

true

func SystemDiagram

func SystemDiagram(sig, work any, opts ...MermaidOption) string

SystemDiagram renders a full system topology diagram combining signal context and worker tree. Deprecated: Use ComponentDiagram with custom DiagramConfig for domain-agnostic diagrams. Accepts signal.State and worker.State (or pointers to them) as any.

Example

ExampleSystemDiagram demonstrates the legacy SystemDiagram function that combines signal context and worker tree.

package main

import (
	"fmt"

	introspection "github.com/aretw0/introspection"
)

func main() {
	type SignalState struct {
		Enabled  bool
		Stopping bool
	}

	type WorkerState struct {
		Name     string
		Status   string
		Children []WorkerState
	}

	signal := SignalState{Enabled: true}
	worker := WorkerState{
		Name:   "root",
		Status: "Running",
		Children: []WorkerState{
			{Name: "child-1", Status: "Running"},
		},
	}

	diagram := introspection.SystemDiagram(signal, worker)

	fmt.Println(len(diagram) > 0)

}
Output:

true

func TreeDiagram

func TreeDiagram(root any, config *DiagramConfig, opts ...MermaidOption) string

TreeDiagram returns a generic Mermaid diagram representing a hierarchical tree structure. The structure is introspected via reflection using common field names (Name, Status, PID, Metadata, Children).

Example

ExampleTreeDiagram demonstrates generating a Mermaid diagram from a hierarchical data structure.

package main

import (
	"fmt"

	introspection "github.com/aretw0/introspection"
)

func main() {
	// Define a tree structure for tasks
	type Task struct {
		Name     string
		Status   string
		Children []Task
	}

	// Create a task hierarchy
	root := Task{
		Name:   "Project",
		Status: "Active",
		Children: []Task{
			{Name: "Backend", Status: "Running"},
			{Name: "Frontend", Status: "Running"},
		},
	}

	// Generate diagram with configuration
	config := introspection.DefaultDiagramConfig()
	config.SecondaryID = "tasks"

	diagram := introspection.TreeDiagram(root, config)

	// The diagram contains Mermaid markup
	fmt.Println(len(diagram) > 0)

}
Output:

true

func WorkerTreeDiagram

func WorkerTreeDiagram(s any, opts ...MermaidOption) string

WorkerTreeDiagram returns a Mermaid diagram string representing the worker hierarchy. Deprecated: Use TreeDiagram with custom DiagramConfig for domain-agnostic diagrams.

Example

ExampleWorkerTreeDiagram demonstrates the legacy WorkerTreeDiagram function for backward compatibility with the worker/signal domain.

package main

import (
	"fmt"

	introspection "github.com/aretw0/introspection"
)

func main() {
	// Define worker state (legacy domain)
	type WorkerState struct {
		Name     string
		Status   string
		PID      int
		Metadata map[string]string
		Children []WorkerState
	}

	root := WorkerState{
		Name:   "supervisor",
		Status: "Running",
		Children: []WorkerState{
			{Name: "worker-1", Status: "Running", PID: 1001},
			{Name: "worker-2", Status: "Idle", PID: 1002},
		},
	}

	diagram := introspection.WorkerTreeDiagram(root)

	fmt.Println(len(diagram) > 0)

}
Output:

true

Types

type Component

type Component interface {
	// ComponentType returns the type of the component.
	ComponentType() string
}

Component identifies the type of a system component. Implementing this interface allows the introspection system to correctly classify the component (e.g., "processor", "controller", "manager") without relying on package paths.

type ComponentEvent

type ComponentEvent interface {
	ComponentID() string
	ComponentType() string
	Timestamp() time.Time
	EventType() string
}

ComponentEvent is the interface for event sourcing. Every event must provide identification and timing metadata.

type DiagramConfig

type DiagramConfig struct {
	// Primary component configuration
	PrimaryID        string // Node ID for primary component (default: "primary")
	PrimaryLabel     string // Subgraph label for primary component (default: "Primary Component")
	PrimaryNodeLabel string // Label prefix for primary node (default: "⚡ Component")

	// Secondary component configuration
	SecondaryID    string // Root node ID for secondary component (default: "secondary")
	SecondaryLabel string // Subgraph label for secondary component (default: "Secondary Component")

	// Connection configuration
	ConnectionLabel string // Label for edge between components (default: "manages")

	// Node style customization (for secondary/tree nodes)
	NodeStyler  NodeStyleFunc // Custom function to style nodes based on metadata
	NodeLabeler NodeLabelFunc // Custom function to build node labels

	// Primary node customization
	PrimaryNodeStyler  PrimaryNodeStyleFunc // Custom function to determine CSS class for primary component
	PrimaryNodeLabeler PrimaryNodeLabelFunc // Custom function to build HTML label for primary component
}

DiagramConfig holds configuration for customizing diagram rendering.

func DefaultDiagramConfig

func DefaultDiagramConfig() *DiagramConfig

DefaultDiagramConfig returns a generic configuration with no domain-specific terms.

type EventSource

type EventSource interface {
	// Events returns a channel of component events.
	// The channel is closed when the provided context is cancelled.
	Events(ctx context.Context) <-chan ComponentEvent
}

EventSource provides an event stream for observability.

type Introspectable

type Introspectable interface {
	// State returns a serializable DTO (Data Transfer Object) representing the component's state.
	State() any
}

Introspectable is an interface for components that can report their internal state. This is used for generating visualization and status reports.

Note: For type-safe state watching, use TypedWatcher[S] instead.

type MermaidOption

type MermaidOption func(*MermaidOptions)

MermaidOption configures the rendering behavior.

func WithStyles

func WithStyles(styles string) MermaidOption

WithStyles allows custom Mermaid class definitions.

Example

ExampleWithStyles demonstrates customizing Mermaid diagram styles.

package main

import (
	"fmt"

	introspection "github.com/aretw0/introspection"
)

func main() {
	type Task struct {
		Name     string
		Status   string
		Children []Task
	}

	root := Task{
		Name:   "Main",
		Status: "Running",
	}

	customStyles := `
    classDef running fill:#90EE90
    classDef failed fill:#FFB6C1
`

	config := introspection.DefaultDiagramConfig()
	diagram := introspection.TreeDiagram(root, config, introspection.WithStyles(customStyles))

	fmt.Println(len(diagram) > 0)

}
Output:

true

type MermaidOptions

type MermaidOptions struct {
	Styles string // Custom Mermaid class definitions
}

MermaidOptions holds Mermaid rendering options.

type NodeLabelFunc

type NodeLabelFunc func(name, status string, pid int, metadata map[string]string, icon string) string

NodeLabelFunc is a function that builds the label for a node.

type NodeStyleFunc

type NodeStyleFunc func(metadata map[string]string) (icon, shapeStart, shapeEnd, cssClass string)

NodeStyleFunc is a function that returns icon, shape start, shape end, and CSS class for a node.

type PrimaryNodeLabelFunc added in v0.1.3

type PrimaryNodeLabelFunc func(state any) string

PrimaryNodeLabelFunc builds the HTML label for the primary component.

type PrimaryNodeStyleFunc added in v0.1.3

type PrimaryNodeStyleFunc func(state any) (cssClass string)

PrimaryNodeStyleFunc determines the CSS class for the primary component based on its state.

type StateChange

type StateChange[S any] struct {
	ComponentID   string
	ComponentType string // Component type identifier (e.g., "processor", "controller", "manager")
	OldState      S
	NewState      S
	Timestamp     time.Time
}

StateChange represents a typed state transition. The generic parameter S allows type-safe access to state without assertions.

type StateMachineConfig

type StateMachineConfig struct {
	// State names
	InitialState  string // Default: "Running"
	GracefulState string // Default: "Graceful"
	ForcedState   string // Default: "ForceExit"

	// Transition labels
	InitialToGraceful string // Default: "Interrupt"
	GracefulToForced  string // Default: "Force"
	GracefulToFinal   string // Default: "Complete"

	// Note content generator
	NoteGenerator func(state any) string
}

StateMachineConfig configures generic Mermaid state diagram rendering.

func DefaultStateMachineConfig

func DefaultStateMachineConfig() *StateMachineConfig

DefaultStateMachineConfig returns a generic state machine configuration.

type StateSnapshot

type StateSnapshot struct {
	ComponentID   string
	ComponentType string // Component type identifier (e.g., "processor", "controller", "manager")
	Timestamp     time.Time
	Payload       any // Component state as any type
}

StateSnapshot is the envelope for cross-domain aggregation. It unifies different state types via a common wrapper.

type TypedWatcher

type TypedWatcher[S any] interface {
	// State returns the current state snapshot
	State() S

	// Watch returns a channel of type-safe state changes.
	// The channel is closed when the provided context is cancelled.
	Watch(ctx context.Context) <-chan StateChange[S]
}

TypedWatcher provides type-safe state watching for a specific state type S. Implementations can return their domain-specific state without any type assertions.

type WatcherAdapter

type WatcherAdapter[S any] struct {
	// contains filtered or unexported fields
}

WatcherAdapter converts typed state changes to snapshots for aggregation. This allows TypedWatcher[S] instances to participate in cross-domain aggregation.

func NewWatcherAdapter

func NewWatcherAdapter[S any](componentType string, w TypedWatcher[S]) *WatcherAdapter[S]

NewWatcherAdapter creates an adapter for the given typed watcher.

Example

ExampleNewWatcherAdapter demonstrates wrapping a TypedWatcher to convert it to StateSnapshot stream for aggregation.

type ServiceState struct {
	Name   string
	Status string
}

service := &simpleWatcher[ServiceState]{
	id:    "api",
	state: ServiceState{Name: "API", Status: "Running"},
	ch:    make(chan introspection.StateChange[ServiceState], 1),
}

// Create an adapter
adapter := introspection.NewWatcherAdapter("service", service)

ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel()

snapshots := adapter.Snapshots(ctx)

// Trigger a state change
service.UpdateState(ServiceState{Name: "API", Status: "Stopped"})

// Observe the snapshot
select {
case snapshot := <-snapshots:
	fmt.Printf("Snapshot from: %s, Type: %s\n", snapshot.ComponentID, snapshot.ComponentType)
case <-time.After(50 * time.Millisecond):
}
Output:

Snapshot from: api, Type: service

func (*WatcherAdapter[S]) Snapshots

func (a *WatcherAdapter[S]) Snapshots(ctx context.Context) <-chan StateSnapshot

Snapshots converts the typed state change stream into snapshot envelopes.

Directories

Path Synopsis
examples
basic command
generic command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL