zoobzio December 10, 2025 Edit this page

Quickstart

Requirements

Go 1.24 or later.

Installation

go get github.com/zoobz-io/flux
go get github.com/zoobz-io/flux/file  # For file watching

Basic Example

Watch a JSON config file and reload on changes:

package main

import (
    "context"
    "errors"
    "log"
    "os"
    "os/signal"

    "github.com/zoobz-io/flux"
    "github.com/zoobz-io/flux/file"
)

type Config struct {
    Port     int    `json:"port"`
    Host     string `json:"host"`
    LogLevel string `json:"log_level"`
}

func (c Config) Validate() error {
    if c.Port < 1 || c.Port > 65535 {
        return errors.New("port must be between 1 and 65535")
    }
    if c.Host == "" {
        return errors.New("host is required")
    }
    switch c.LogLevel {
    case "debug", "info", "warn", "error":
        // valid
    default:
        return errors.New("log_level must be one of: debug, info, warn, error")
    }
    return nil
}

func main() {
    ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
    defer cancel()

    capacitor := flux.New[Config](
        file.New("/etc/myapp/config.json"),
        func(ctx context.Context, prev, curr Config) error {
            log.Printf("config updated: port=%d, host=%s", curr.Port, curr.Host)
            return nil
        },
    )

    if err := capacitor.Start(ctx); err != nil {
        log.Printf("initial config failed: %v", err)
    }

    log.Printf("state: %s", capacitor.State())

    <-ctx.Done()
}

Config File

{
    "port": 8080,
    "host": "localhost",
    "log_level": "info"
}

Run It

go run main.go
# Output: config updated: port=8080, host=localhost
# Output: state: healthy

# Edit config.json in another terminal
# Output: config updated: port=9090, host=localhost

Validation

Configuration types must implement the Validator interface:

type Validator interface {
    Validate() error
}

Your Validate() method has full control over validation logic:

type Config struct {
    Port     int    `json:"port"`
    Host     string `json:"host"`
    LogLevel string `json:"log_level"`
}

func (c Config) Validate() error {
    if c.Port < 1 || c.Port > 65535 {
        return fmt.Errorf("port must be between 1 and 65535, got %d", c.Port)
    }
    if c.Host == "" {
        return errors.New("host is required")
    }
    return nil
}

For complex validation, you can use libraries like go-playground/validator inside your Validate() method.

Invalid configs are rejected. The previous valid config is retained.

Accessing Current Config

cfg, ok := capacitor.Current()
if !ok {
    // No valid config yet (Loading or Empty state)
    return
}
fmt.Printf("using port %d\n", cfg.Port)

Error Handling

// Check last error
if err := capacitor.LastError(); err != nil {
    log.Printf("config error: %v", err)
}

// Check state
switch capacitor.State() {
case flux.StateHealthy:
    // All good
case flux.StateDegraded:
    // Update failed, using previous config
case flux.StateEmpty:
    // No valid config ever loaded
}

YAML Support

capacitor := flux.New[Config](
    file.New("/etc/myapp/config.yaml"),
    callback,
).Codec(flux.YAMLCodec{})

Debouncing

Rapid changes are coalesced:

capacitor := flux.New[Config](
    watcher,
    callback,
).Debounce(200*time.Millisecond) // Default: 100ms

Next Steps