command

package module
v0.24.3 Latest Latest
Warning

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

Go to latest
Published: Oct 15, 2026 License: MIT Imports: 16 Imported by: 25

README

Go Command

A Go package for implementing command and query patterns with support for multiple execution strategies including CLI, cron scheduling, RPC methods, message dispatching, and batch/parallel processing.

Overview

go-command provides a framework for building applications inspired by the Command Query Responsibility Segregation (CQRS) pattern. It offers:

  • Type safe message handling through Go generics
  • Multiple execution strategies (CLI, cron, RPC, dispatcher, batch, parallel)
  • Flexible error handling with retry support
  • Context aware operations with cancellation and timeouts
  • Registry system for automatic command/query discovery
  • Integration with popular frameworks (Kong for CLI, cron for scheduling)

Package and documentation map

Use Go 1.23.4 or newer for the root module; nested examples declare their own toolchain requirements. All packages below share the root Go module.

Package Responsibility Guide
Root command Typed handlers, metadata/catalog/input/result contracts, exposure, immutable registrations, placement and provenance ports Sections below; catalog
dispatcher Isolated/local and optional routed command/query execution, execution modes and run observers Routed dispatch
registry Global helpers and explicit runtime-container composition, subscription/lifecycle ownership Resolvers
router Message-type mux and optional rich route matching Matcher
runner, cron Handler retry/timeout controls and in-process cron scheduling Sections below
rpc Endpoint definitions, discovery, envelopes and invocation; no HTTP listener RPC implementation, TS scaffold CLI
flow Serial/parallel/batch/conditional/saga composition, FSM authoring/runtime and optional durable outbox orchestration Flow guide, FSM migration
data Embedded RPC/FSM core browser assets Client guide
data/builder, data/client-builder Optional UI-builder asset composition Builder contract

The TypeScript client workspace has four npm packages, separate from Go package imports. Examples with go.mod are separate modules and are not covered by root go test ./.... Hosts mount HTTP/WebSocket/static routes, enforce authorization and supply remote transport/job-scheduler adapters. The optional SQLite FSM store initializes its own configured state/outbox tables; there is no embedded application migration bundle.

Installation

go get github.com/goliatone/go-command

Embedded Client Assets

The TypeScript runtime is split into four client libraries:

  • RPC: @goliatone/go-command-rpc
  • FSM core: @goliatone/go-command-fsm
  • FSM RPC transport adapter: @goliatone/go-command-fsm-rpc
  • FSM UI Builder: @goliatone/go-command-fsm-ui-builder (optional authoring UI package)

Build/sync embedded artifacts:

./taskfile client:build

Embedded output paths:

  • data/client/rpc/index.js
  • data/client/fsm/index.js
  • data/client/fsm-rpc/index.js
  • data/client-builder/fsm-ui-builder/index.js (builder-only path; produced by client:build:builder / client:build:all)

Go accessors:

  • data.ClientFS() (rooted at data/client)
  • data.ClientRPCFS() (rooted at data/client/rpc)
  • data.ClientFSMFS() (rooted at data/client/fsm)
  • data.ClientFSMRPCFS() (rooted at data/client/fsm-rpc)
  • optional builder-only accessors live under data/builder so importing data remains core-only

Builder asset serving example (optional embed package):

import (
    "io/fs"
    "net/http"

    builderdata "github.com/goliatone/go-command/data/builder"
)

func mountBuilderAssets(mux *http.ServeMux) error {
    builderFS := builderdata.ClientFSMUIBuilderFS()
    staticFS, err := fs.Sub(builderFS, ".")
    if err != nil {
        return err
    }
    mux.Handle("/builder/", http.StripPrefix("/builder/", http.FileServer(http.FS(staticFS))))
    return nil
}

Builder demo host (mount + /rpc adapters) is available at:

  • client/packages/fsm-ui-builder/demo/index.html
  • client/packages/fsm-ui-builder/demo/main.ts

Core Concepts

Messages

Messages are data carriers that implement the Message interface:

type Message interface {
    Type() string
}

Example:

type CreateUserCommand struct {
    Email string
    Name  string
}

func (c CreateUserCommand) Type() string {
    return "user.create"
}
Commands

Commands handle operations with side effects:

type Commander[T any] interface {
    Execute(ctx context.Context, msg T) error
}

// Function adapter
type CommandFunc[T any] func(ctx context.Context, msg T) error
Queries

Queries retrieve data without side effects:

type Querier[T any, R any] interface {
    Query(ctx context.Context, msg T) (R, error)
}

// Function adapter
type QueryFunc[T any, R any] func(ctx context.Context, msg T) (R, error)

Execution Strategies

Instance-first local execution

Use an isolated runtime for explicit dependency injection and tests:

package main

import (
    "context"
    "fmt"

    command "github.com/goliatone/go-command"
    "github.com/goliatone/go-command/dispatcher"
)

type CreateUser struct { Email string }
func (CreateUser) Type() string { return "user.create" }

func main() {
    runtime := dispatcher.NewRuntime()
    subscription := dispatcher.SubscribeCommandTo(runtime,
        command.CommandFunc[CreateUser](func(ctx context.Context, msg CreateUser) error {
            fmt.Println(msg.Email)
            return nil
        }),
    )
    defer subscription.Unsubscribe()
    if err := dispatcher.DispatchTo(context.Background(), runtime, CreateUser{Email: "user@example.com"}); err != nil {
        panic(err)
    }
}

A bare command.Registry collects metadata and runs configured resolvers; registering there alone does not subscribe a handler to a dispatcher. Use registry.RuntimeContainer with explicit typed subscription hooks for combined assembly, or use the compatibility package-level registry helpers. Initialize registrations before attaching providers, installing placement policies and checking RoutedReady(). See routed assembly.

1. Dispatcher Pattern

The dispatcher provides a centralized message routing system:

import "github.com/goliatone/go-command/dispatcher"

// Subscribe a command handler
dispatcher.SubscribeCommand(&CreateUserHandler{db: db})

// Or use a function
dispatcher.SubscribeCommandFunc(func(ctx context.Context, cmd CreateUserCommand) error {
    // Handle command
    return nil
})

// Dispatch a command
err := dispatcher.Dispatch(context.Background(), CreateUserCommand{
    Email: "user@example.com",
    Name:  "John Doe",
})

// Subscribe a query handler
dispatcher.SubscribeQuery(&GetUserHandler{db: db})

// Execute a query
user, err := dispatcher.Query[GetUserMessage, *User](context.Background(), GetUserMessage{
    ID: "user-123",
})

Dispatcher routing defaults to exact message-type matching. Rich pattern matching is opt-in:

// default is exact matching:
// registered pattern must equal msg.Type()/message id.

// opt in to rich routing behavior when needed:
_ = dispatcher.SetCommandRoutingMode(
    dispatcher.RoutingModeRich,
    dispatcher.WithRoutingMatcher(router.MakeRouteMatcher(router.MakeRouteMatcherOptions{
        Separator: ".",
    })),
    dispatcher.WithRoutingMatchStrategy(router.MatchStrategySpecificity),
)

DispatchWith enforces canonical command ids (Type() string) for queued/policy paths. Inline compatibility remains unchanged for legacy reflection-derived message types.

Local, Hybrid, and Worker Dispatch

dispatcher.Runtime provides isolated local command/query muxes plus optional immutable registration, placement, and remote-dispatch generations. Existing package-level APIs remain local when unconfigured and can use a concurrency-safe installed default runtime for hybrid routing.

See Routed Message Dispatch for local-only assembly, hybrid policies, worker and authorized event ingress, command/query replies, readiness, forced-local loop protection, and the transport adapter contract.

Command Run Events and Progress

Register command run observers to receive lifecycle and cooperative progress events from dispatcher execution:

sub := dispatcher.AddCommandRunObserver(command.CommandRunObserverFunc(
    func(ctx context.Context, ev command.CommandRunEvent) error {
        log.Printf("command=%s run=%s phase=%s current=%d total=%d",
            ev.CommandID, ev.RunID, ev.Phase, ev.Current, ev.Total)
        return nil
    },
))
defer sub.Unsubscribe()

Inline dispatcher.Dispatch and inline dispatcher.DispatchWith automatically emit started and one terminal event per handler run. Terminal phases are succeeded, failed, or canceled; observer errors and panics are ignored so command results are unchanged. Runner policy skips, such as exhausted WithRunOnce(true) or WithMaxRuns(n) handlers, do not emit false successful lifecycle events.

Handlers can report checkpoints and progress through context helpers:

func (h *ImportUsers) Execute(ctx context.Context, msg ImportUsersCommand) error {
    command.Checkpoint(ctx, "loaded")
    for i, user := range msg.Users {
        if err := h.importOne(ctx, user); err != nil {
            return err
        }
        command.Progress(ctx, int64(i+1), int64(len(msg.Users)))
    }
    return nil
}

command.Checkpoint and command.Progress are noops when no progress reporter is present, so commands do not need host-specific branching.

Queued dispatch can opt into matching acceptance events:

_ = dispatcher.RegisterObservedExecutor(command.ExecutionModeQueued, queueExecutor)

Observed executors emit submitted after an accepted queued receipt and rejected for acceptance failures. The executor can read command.DispatchRunFromContext(ctx) during Execute and persist the run ID with its queued job. Later, the worker can reuse that run context:

run := storedRunContext
run.DispatchID = storedDispatchID

err := dispatcher.RunObservedCommand(ctx, run, func(runCtx context.Context) error {
    command.Checkpoint(runCtx, "dequeued")
    return runner.RunCommand(runCtx, runner.NewHandler(), cmd, msg)
})

Observer adapters are available for common host integrations:

statsObserver := command.CommandRunStatsObserver(statsRecorder)
storeObserver := command.CommandRunStoreObserver(runStore)
safeObserver := command.SanitizingCommandRunObserver(storeObserver, func(metadata map[string]any) map[string]any {
    delete(metadata, "token")
    return metadata
})

Event metadata is defensively copied at context, reporting, and fan-out boundaries, but core does not inspect payloads or enforce a global redaction policy. Hosts should only attach safe metadata or use a sanitizing observer before storing, streaming, or rendering events.

Command catalog descriptors can optionally expose progress metadata by implementing command.CommandProgressDescriber. Commands that do not implement it omit the progress field.

2. Registry System with CLI, Cron, and RPC

The registry allows commands to be registered once and executed through multiple interfaces:

import (
    "github.com/goliatone/go-command/registry"
    "github.com/goliatone/go-command/cron"
)

// Command that supports multiple execution modes
type SyncDataCommand struct {
    service SyncService
    logger  Logger
}

// Core business logic
func (c *SyncDataCommand) Execute(ctx context.Context, evt *SyncDataEvent) error {
    return c.service.Sync(ctx, evt.Source, evt.Target, evt.BatchSize)
}

// Enable CLI execution
func (c *SyncDataCommand) CLIHandler() any {
    return &SyncDataCLICommand{cmd: c}
}

func (c *SyncDataCommand) CLIOptions() command.CLIConfig {
    return command.CLIConfig{
        Path:        []string{"sync"},
        Description: "Synchronize data between directories",
        Group:       "data",
    }
}

// Namespaced commands (e.g., ctx prompt create)
func (c *CreatePromptCommand) CLIOptions() command.CLIConfig {
    return command.CLIConfig{
        Path:        []string{"prompt", "create"},
        Description: "Create a prompt",
        Aliases:     []string{"add"},
        Groups: []command.CLIGroup{
            {Name: "prompt", Description: "Prompt management"},
        },
    }
}

// Enable cron scheduling
func (c *SyncDataCommand) CronHandler() func() error {
    return func() error {
        return c.Execute(context.Background(), &SyncDataEvent{
            Source:    os.Getenv("SYNC_SOURCE"),
            Target:    os.Getenv("SYNC_TARGET"),
            BatchSize: 500,
        })
    }
}

func (c *SyncDataCommand) CronOptions() command.HandlerConfig {
    return command.HandlerConfig{
        Expression: "0 2 * * *", // Daily at 2 AM
        MaxRetries: 3,
        Timeout:    time.Hour,
    }
}

// Setup
scheduler := cron.NewScheduler()
scheduler.Start(context.Background())

// Call before Start/Initialize.
registry.SetCronRegister(func(opts command.HandlerConfig, handler any) error {
    _, err := scheduler.AddHandler(opts, handler)
    return err
})

registry.RegisterCommand(syncCmd)
registry.Start(context.Background())

// Get CLI options for Kong integration
cliOptions, _ := registry.GetCLIOptions()
Registry Resolvers

Resolvers run during registry initialization for each registered command. CLI, cron, and RPC are built-in resolvers (keys "cli", "cron", and "rpc"), and you can add more with AddResolver:

cmdRegistry := command.NewRegistry()
queueRegistry := queuecmd.NewRegistry()

if err := cmdRegistry.AddResolver("queue", queuecmd.QueueResolver(queueRegistry)); err != nil {
    return err
}

If your command uses an interface message parameter, implement command.MessageFactory to provide a concrete value for metadata:

type Event interface{ Type() string }

type EventCommand struct{}

func (c *EventCommand) Execute(ctx context.Context, msg Event) error { return nil }
func (c *EventCommand) MessageValue() any { return &UserCreated{} }

MessageValue() must return a value assignable to the interface parameter type; otherwise metadata is treated as empty and resolvers that rely on MessageType will skip it.

For the global registry helpers, use registry.AddResolver and registry.HasResolver.

Migration notes:

  • If you switch to resolver based queue registration, you must attach the queue resolver or queue registration will not happen.
  • Choose one queue registration path per command (resolver-driven or direct), not both.
  • Queued routing paths should treat duplicate command id registrations as explicit conflicts (no silent no-op shadowing).

See docs/GUIDE_RESOLVERS.md for a deeper guide.

3. Batch Executor

Process commands in batches with concurrency control:

import "github.com/goliatone/go-command/flow"

// Create batch executor
executor := flow.NewBatchExecutor(
    &ItemProcessor{},
    flow.WithBatchSize[ProcessItemCommand](100),
    flow.WithConcurrency[ProcessItemCommand](5),
)

// Process messages in batches
messages := []ProcessItemCommand{
    {ItemID: "1"}, {ItemID: "2"}, // ... more items
}
err := executor.Execute(context.Background(), messages)

// Or use the functional approach
err = flow.ExecuteBatch(
    context.Background(),
    messages,
    processFunc,
    100, // batch size
    5,   // concurrency
)

The batch executor:

  • Splits messages into batches of specified size
  • Processes batches concurrently with configurable parallelism
  • Supports error handling with optional "stop on error" behavior
  • Provides detailed error metadata for debugging
4. Parallel Executor

Execute multiple handlers concurrently for the same message:

import "github.com/goliatone/go-command/flow"

// Create parallel executor with multiple handlers
handlers := []command.Commander[NotificationEvent]{
    &EmailNotifier{},
    &SMSNotifier{},
    &PushNotifier{},
}

executor := flow.NewParallelExecutor(handlers)

// Execute all handlers in parallel
err := executor.Execute(context.Background(), NotificationEvent{
    UserID:  "user-123",
    Message: "Your order has been shipped",
})

// Or use the functional approach
err = flow.ParallelExecute(
    context.Background(),
    event,
    []command.CommandFunc[NotificationEvent]{
        sendEmail,
        sendSMS,
        sendPush,
    },
)

The parallel executor:

  • Runs all handlers concurrently
  • Supports context cancellation
  • Can stop all handlers on first error (configurable)
  • Collects and returns all errors
5. Runner with Retry Logic

The runner provides execution control with retries and timeouts:

import "github.com/goliatone/go-command/runner"

handler := runner.NewHandler(
    runner.WithMaxRetries(3),
    runner.WithTimeout(30 * time.Second),
    runner.WithRetryDelay(time.Second),
    runner.WithStopOnError(true),
)

err := runner.RunCommand(ctx, handler, cmd, msg)

The runner supports custom retry logic through error interfaces:

  • IsRetryable() bool: Control whether an error should trigger a retry
  • RetryDelay(attempt int) time.Duration: Custom retry delay calculation
6. Cron Scheduler

Schedule commands to run periodically:

import "github.com/goliatone/go-command/cron"

scheduler := cron.NewScheduler(
    cron.WithLocation(time.UTC),
    cron.WithLogLevel(cron.LogLevelInfo),
)

// Add a command to run every 5 minutes
id, err := scheduler.AddHandler(
    command.HandlerConfig{
        Expression: "*/5 * * * *",
        MaxRetries: 3,
        Timeout:    time.Minute,
    },
    func() error {
        return processBatch(context.Background())
    },
)

scheduler.Start(context.Background())
7. RPC Method Adapter

Expose methods as explicit typed RPC endpoints by implementing rpc.EndpointsProvider:

type ApplyEventData struct {
    EntityID string `json:"entityId"`
    Event    string `json:"event"`
}

type ApplyEventEndpoint struct{}

func (c *ApplyEventEndpoint) RPCEndpoints() []rpc.EndpointDefinition {
    return []rpc.EndpointDefinition{
        rpc.NewEndpoint[ApplyEventData, map[string]any](
            rpc.EndpointSpec{
                Method:      "fsm.apply_event",
                Kind:        rpc.MethodKindCommand,
                Timeout:     5 * time.Second,
                Permissions: []string{"fsm:write"},
                Roles:       []string{"admin"},
            },
            func(ctx context.Context, req rpc.RequestEnvelope[ApplyEventData]) (rpc.ResponseEnvelope[map[string]any], error) {
                return rpc.ResponseEnvelope[map[string]any]{
                    Data: map[string]any{
                        "entityId": req.Data.EntityID,
                        "event":    req.Data.Event,
                        "actorId":  req.Meta.ActorID,
                    },
                }, nil
            },
        ),
    }
}

Wire the RPC server before registry initialization. Endpoint-only providers use the instance registry (RegisterCommand(any)); the global typed helper requires a Commander[T]. Hosts enforce endpoint permissions in invoke middleware:

import (
    command "github.com/goliatone/go-command"
    "github.com/goliatone/go-command/rpc"
)

rpcServer := rpc.NewServer(
    rpc.WithFailureMode(rpc.FailureModeRecover),
    rpc.WithFailureLogger(func(ev rpc.FailureEvent) {
        log.Printf("rpc failure stage=%s method=%s err=%v", ev.Stage, ev.Method, ev.Err)
    }),
)

commands := command.NewRegistry()
if err := commands.AddResolver("rpc-explicit", rpc.Resolver(rpcServer)); err != nil { return err }
if err := commands.RegisterCommand(&ApplyEventEndpoint{}); err != nil { return err }
if err := commands.Initialize(); err != nil { return err }

Canonical RPC method payloads should use:

  • Request: rpc.RequestEnvelope[T] (data + meta)
  • Response: rpc.ResponseEnvelope[T]

Failure handling modes:

  • FailureModeReject (default): registration errors return, invoke panics.
  • FailureModeRecover: registration errors return, invoke panics become errors.
  • FailureModeLogAndContinue: registration failures are skipped after logging, invoke panics return errors after logging.

Endpoint metadata returned by Endpoint() and Endpoints() is defensively copied, so caller mutations do not affect server state.

Invoke middleware can be registered for auth/logging/metrics concerns:

rpcServer := rpc.NewServer(
    rpc.WithMiddleware(
        func(next rpc.InvokeHandler) rpc.InvokeHandler {
            return func(ctx context.Context, req rpc.InvokeRequest) (any, error) {
                if req.Method == "fsm.apply_event" && req.Endpoint.HandlerKind == rpc.HandlerKindExecute {
                    // add auth checks or request-scoped instrumentation here
                }
                return next(ctx, req)
            }
        },
    ),
)

Composing a Workflow

Keep domain behavior behind a service and expose the composed handler through the same typed dispatcher. The host supplies the service and notification handlers:

import (
    "context"

    command "github.com/goliatone/go-command"
    "github.com/goliatone/go-command/dispatcher"
    "github.com/goliatone/go-command/flow"
)

type ProcessOrder struct { OrderID, UserID string }
func (ProcessOrder) Type() string { return "order.process" }

type OrderService interface {
    Validate(context.Context, ProcessOrder) error
    Process(context.Context, ProcessOrder) error
}

func processOrder(ctx context.Context, service OrderService, msg ProcessOrder,
    notifications []command.Commander[ProcessOrder],
) error {
    workflow := flow.NewSerialExecutor([]command.Commander[ProcessOrder]{
        command.CommandFunc[ProcessOrder](service.Validate),
        command.CommandFunc[ProcessOrder](service.Process),
        flow.NewParallelExecutor(notifications),
    })
    runtime := dispatcher.NewRuntime()
    sub := dispatcher.SubscribeCommandTo(runtime, workflow)
    defer sub.Unsubscribe()
    return dispatcher.DispatchTo(ctx, runtime, msg)
}

Workflow sequencing alone does not provide a database transaction or durable notification delivery. Put transactional behavior in the domain service and choose a durable outbox/job adapter when delivery must survive process failure.

Error Handling

The package provides consistent error handling across all execution strategies:

// Configure error handler
handler := runner.NewHandler(
    runner.WithErrorHandler(func(err error) {
        log.Printf("Command execution failed: %v", err)
    }),
)

// Custom retryable errors
type RetryableError struct {
    err   error
    delay time.Duration
}

func (e RetryableError) Error() string { return e.err.Error() }
func (e RetryableError) IsRetryable() bool { return true }
func (e RetryableError) RetryDelay(attempt int) time.Duration {
    return e.delay * time.Duration(attempt)
}

Catalog and exposure contracts

CommandDescriptor, input/result schemas and option-source contracts let hosts build discovery/UI adapters. ExposableCommand.Exposure() opts into admin/REPL visibility; its zero value is hidden. Metadata does not execute commands or authorize callers. Consumers must enforce permissions and preserve the trusted actor/scope boundary. MessageFactory.MessageValue() supplies a concrete, assignable message for interface-typed handlers and metadata extraction.

Run events are observation hooks. Stats/store observer adapters are best effort and do not turn an observer failure into command failure. Use SanitizingCommandRunObserver with a host disclosure policy before forwarding metadata. Observer implementation.

FSM

flow exposes a canonical envelope contract:

  • ApplyEvent(ctx, ApplyEventRequest[T]) -> *ApplyEventResponse[T]
    • apply request supports optional MachineID, IdempotencyKey, Metadata, DryRun
    • DryRun is evaluation-only: no state/outbox/orchestrator/lifecycle/idempotency-store writes
    • apply response includes EventID, Version, IdempotencyHit
  • Snapshot(ctx, SnapshotRequest[T]) -> *Snapshot
    • snapshot request supports optional MachineID, EvaluateGuards, IncludeBlocked
    • TransitionInfo includes Allowed and structured Rejections
  • Execute(ctx, msg) remains only as a compatibility wrapper.

Additional surfaces include:

  • Authoring: CompileDSL, MachineDefinition.ToDSL()
  • UI schema: GenerateMachineSchema, GenerateMachineUISchema
  • Orchestration: mandatory execution policy (lightweight or orchestrated)
  • RPC command methods: fsm.apply_event, fsm.execution.pause, fsm.execution.resume, fsm.execution.stop
  • RPC query methods: fsm.snapshot, fsm.execution.status, fsm.execution.list, fsm.execution.history
    • query telemetry is additive under query_* keys and does not overwrite transition metadata keys
  • Transport/runtime error mapping: MapRuntimeError, RPCErrorForError

See:

  • flow/README.md for API examples.

Testing

The package provides utilities for testing:

import "github.com/goliatone/go-command/registry"

func TestCommand(t *testing.T) {
    registry.WithTestRegistry(func() {
        // Register test command
        registry.RegisterCommand(myCommand)

        // Start registry
        err := registry.Start(context.Background())
        require.NoError(t, err)

        // Test execution
        err = dispatcher.Dispatch(context.Background(), MyCommand{})
        assert.NoError(t, err)
    })
}

Advanced Features

Message Type Resolution

Messages can implement custom type resolution:

func (m MyMessage) Type() string {
    return "custom.message.type"
}

Or rely on automatic type detection based on struct name.

Context Propagation

All operations support context for:

  • Cancellation
  • Deadlines
  • Value propagation
  • Tracing integration
Thread Safety

Runtime dispatch and its installed-default generation support concurrent use. Assemble registries, resolvers, options and flow definitions before execution; thread safety does not imply arbitrary mutation is safe. Application handlers, shared message pointers, callbacks, stores and transport adapters own their concurrency guarantees. Prefer isolated runtimes when tests run in parallel.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

This project is licensed under the MIT License: see the LICENSE file for details.

Documentation

Index

Constants

View Source
const (
	TextCodeInvalidExecutionMode         = "INVALID_EXECUTION_MODE"
	TextCodeInvalidDedupPolicy           = "INVALID_DEDUP_POLICY"
	TextCodeDispatchOptionsInvalid       = "DISPATCH_OPTIONS_INVALID"
	TextCodeDispatchReceiptInvalid       = "DISPATCH_RECEIPT_INVALID"
	TextCodeUnknownCommandPolicyKey      = "UNKNOWN_COMMAND_POLICY_KEY"
	TextCodeInvalidCommandID             = "INVALID_COMMAND_ID"
	TextCodeQueueMultiHandlerUnsupported = "QUEUE_MULTI_HANDLER_UNSUPPORTED"
	TextCodeDispatchRequestInvalid       = "DISPATCH_REQUEST_INVALID"
	TextCodeDispatchResponseInvalid      = "DISPATCH_RESPONSE_INVALID"
)
View Source
const (
	TextCodeRegistrationNotFound              = "REGISTRATION_NOT_FOUND"
	TextCodeRegistrationConflict              = "REGISTRATION_CONFLICT"
	TextCodeRegistrationInvalid               = "REGISTRATION_INVALID"
	TextCodeRegistrationProviderNotConfigured = "REGISTRATION_PROVIDER_NOT_CONFIGURED"
	TextCodeDynamicMessageTypeMismatch        = "DYNAMIC_MESSAGE_TYPE_MISMATCH"
	TextCodeDynamicResultTypeMismatch         = "DYNAMIC_RESULT_TYPE_MISMATCH"
	TextCodeDynamicResultMissing              = "DYNAMIC_RESULT_MISSING"
	TextCodePlacementResolutionFailed         = "PLACEMENT_RESOLUTION_FAILED"
	TextCodeInvalidDispatchRoute              = "INVALID_DISPATCH_ROUTE"
	TextCodeRemoteDispatcherNotConfigured     = "REMOTE_DISPATCHER_NOT_CONFIGURED"
	TextCodeLocalInvocationRequired           = "LOCAL_INVOCATION_REQUIRED"
	TextCodeDispatchRejected                  = "DISPATCH_REJECTED"
)

Variables

View Source
var ErrValidation = errors.New("validation error", errors.CategoryValidation).
	WithTextCode("VALIDATION_FAILED")

ErrValidation is a sentinel error used to mark validation failures. Wrappers can compare errors with errors.Is(err, ErrValidation) to propagate validation intent through additional layers.

Functions

func CanonicalCommandID added in v0.17.0

func CanonicalCommandID(msg any) (string, error)

CanonicalCommandID resolves a stable command id for policy/dedup paths. It requires an explicit Type() implementation and does not allow reflection-only fallback ids.

func Checkpoint added in v0.22.0

func Checkpoint(ctx context.Context, checkpoint string, opts ...ProgressOption)

Checkpoint reports a named checkpoint when the context has a reporter.

func CloneCommandRunMetadata added in v0.22.0

func CloneCommandRunMetadata(metadata map[string]any) map[string]any

CloneCommandRunMetadata shallow-clones event, run, and progress metadata.

func CommandRunRevisionFromContext added in v0.24.0

func CommandRunRevisionFromContext(ctx context.Context) (uint64, bool)

CommandRunRevisionFromContext returns the latest reserved revision.

func ContextWithCommandProgressReporter added in v0.22.0

func ContextWithCommandProgressReporter(ctx context.Context, reporter CommandProgressReporter) context.Context

ContextWithCommandProgressReporter stores a progress reporter in context.

func ContextWithCommandRunAttempt added in v0.22.0

func ContextWithCommandRunAttempt(ctx context.Context, attempt CommandRunAttempt) context.Context

ContextWithCommandRunAttempt stores the current attempt for command code.

func ContextWithCommandRunAttemptTracker added in v0.22.0

func ContextWithCommandRunAttemptTracker(ctx context.Context) context.Context

ContextWithCommandRunAttemptTracker lets dispatch code read the latest attempt summary after runner execution.

func ContextWithCommandRunRevision added in v0.24.0

func ContextWithCommandRunRevision(ctx context.Context, last uint64) context.Context

ContextWithCommandRunRevision starts a process-local sequencer from the last revision persisted with a dispatch run. The state is shared by derived contexts so progress reporters and lifecycle emitters allocate one sequence.

func ContextWithDispatchOptions added in v0.17.0

func ContextWithDispatchOptions(ctx context.Context, opts DispatchOptions) context.Context

func ContextWithDispatchProvenance added in v0.23.0

func ContextWithDispatchProvenance(ctx context.Context, provenance DispatchProvenance) context.Context

func ContextWithDispatchRun added in v0.22.0

func ContextWithDispatchRun(ctx context.Context, run DispatchRunContext) context.Context

ContextWithDispatchRun stores command run identity in context.

func ContextWithForcedLocalDispatch added in v0.23.0

func ContextWithForcedLocalDispatch(ctx context.Context) context.Context

ContextWithForcedLocalDispatch marks a trusted ingress call so nested compatibility APIs execute locally instead of resolving placement again.

func ContextWithResult added in v0.3.0

func ContextWithResult[T any](ctx context.Context, result *Result[T]) context.Context

Context helpers

func DefaultPanicLogger added in v0.2.0

func DefaultPanicLogger(funcName string, err any, stack []byte, fields ...map[string]any)

func ForcedLocalDispatchFromContext added in v0.23.0

func ForcedLocalDispatchFromContext(ctx context.Context) bool

ForcedLocalDispatchFromContext reports the transport-neutral ingress guard. Payload fields and dispatch metadata cannot create this marker.

func GetGoroutineID added in v0.2.0

func GetGoroutineID() uint64

func GetMessageType added in v0.3.0

func GetMessageType(msg any) string

func IsNilMessage

func IsNilMessage(msg any) bool

func MakePanicHandler added in v0.2.0

func MakePanicHandler(logger PanicLogger) func(funcName string, fields ...map[string]any)

func NewDispatchRejectedError added in v0.23.3

func NewDispatchRejectedError(commandID string, receipt DispatchReceipt) *errors.Error

NewDispatchRejectedError reports a structurally valid receipt that did not accept the command. APIs that cannot return receipts still surface rejection as a classified error rather than silently reporting success.

func NewDynamicMessageTypeMismatchError added in v0.23.0

func NewDynamicMessageTypeMismatchError(reg MessageRegistration, actual any) *errors.Error

func NewDynamicResultMissingError added in v0.23.0

func NewDynamicResultMissingError(reg MessageRegistration) *errors.Error

func NewDynamicResultTypeMismatchError added in v0.23.0

func NewDynamicResultTypeMismatchError(reg MessageRegistration, actual any) *errors.Error

func NewInvalidDispatchRouteError added in v0.23.0

func NewInvalidDispatchRouteError(route DispatchRoute) *errors.Error

func NewLocalInvocationRequiredError added in v0.23.0

func NewLocalInvocationRequiredError(reg MessageRegistration) *errors.Error

func NewPlacementResolutionError added in v0.23.0

func NewPlacementResolutionError(reg MessageRegistration, source error) *errors.Error

NewPlacementResolutionError classifies resolver failures while retaining structured source metadata when the resolver already returned go-errors.

func NewQueueMultiHandlerUnsupportedError added in v0.17.0

func NewQueueMultiHandlerUnsupportedError(commandID string, handlerCount int) *errors.Error

TODO(BKG_CMD phase 2): enforce this via DispatchWith in queued routing paths.

func NewRegistrationConflictError added in v0.23.0

func NewRegistrationConflictError(kind HandlerKind, key string) *errors.Error

func NewRegistrationInvalidError added in v0.23.0

func NewRegistrationInvalidError(message string, metadata map[string]any) *errors.Error

func NewRegistrationNotFoundError added in v0.23.0

func NewRegistrationNotFoundError(kind HandlerKind, key string) *errors.Error

func NewRegistrationProviderNotConfiguredError added in v0.23.0

func NewRegistrationProviderNotConfiguredError() *errors.Error

func NewRemoteDispatcherNotConfiguredError added in v0.23.0

func NewRemoteDispatcherNotConfiguredError(reg MessageRegistration, route DispatchRoute) *errors.Error

func NextCommandRunRevision added in v0.24.0

func NextCommandRunRevision(ctx context.Context) uint64

NextCommandRunRevision atomically reserves the next lifecycle revision.

func NilCronRegister added in v0.3.0

func NilCronRegister(opts HandlerConfig, handler any) error

func NilRPCRegister added in v0.15.0

func NilRPCRegister(opts RPCConfig, handler any, meta CommandMeta) error

NilRPCRegister is a noop RPC registration function.

func Progress added in v0.22.0

func Progress(ctx context.Context, current, total int64, opts ...ProgressOption)

Progress reports current and total counts when the context has a reporter.

func RecordCommandRunAttempt added in v0.22.0

func RecordCommandRunAttempt(ctx context.Context, attempt CommandRunAttempt)

RecordCommandRunAttempt records the latest runner attempt when a tracker is present in context.

func ValidateCommandDispatchRequest added in v0.21.0

func ValidateCommandDispatchRequest(req CommandDispatchRequest) error

ValidateCommandDispatchRequest checks the reusable dispatch payload shape.

func ValidateCommandDispatchResponse added in v0.21.0

func ValidateCommandDispatchResponse(resp CommandDispatchResponse) error

ValidateCommandDispatchResponse checks reusable response and field error shapes without assuming a specific renderer.

func ValidateDedupPolicy added in v0.17.0

func ValidateDedupPolicy(policy DedupPolicy) error

func ValidateDispatchOptions added in v0.17.0

func ValidateDispatchOptions(effectiveMode ExecutionMode, opts DispatchOptions) error

func ValidateDispatchReceipt added in v0.17.0

func ValidateDispatchReceipt(receipt DispatchReceipt) error

func ValidateDispatchRoute added in v0.23.0

func ValidateDispatchRoute(route DispatchRoute) error

func ValidateExecutionMode added in v0.17.0

func ValidateExecutionMode(mode ExecutionMode) error

func ValidateMessage added in v0.3.0

func ValidateMessage(msg any) error

func ValidatePolicyCommandIDs added in v0.17.0

func ValidatePolicyCommandIDs(knownIDs []string, configuredIDs []string) error

Types

type CLICommand added in v0.3.0

type CLICommand interface {
	CLIHandler() any
	CLIOptions() CLIConfig
}

type CLIConfig added in v0.3.0

type CLIConfig struct {
	Path        []string
	Description string
	// Group is kept for backward compatibility; prefer Groups for nested paths.
	Group   string
	Groups  []CLIGroup
	Aliases []string
	Hidden  bool
}

func (CLIConfig) BuildTags added in v0.3.0

func (opts CLIConfig) BuildTags() []string

type CLIGroup added in v0.7.0

type CLIGroup struct {
	Name        string
	Description string
}

type CatalogDescriber added in v0.21.0

type CatalogDescriber interface {
	CommandDescriptor() CommandDescriptor
}

CatalogDescriber lets a command provide its own descriptor.

type CatalogProvider added in v0.21.0

type CatalogProvider interface {
	CommandDescriptors() []CommandDescriptor
}

CatalogProvider lets hosts provide descriptors externally.

type CommandDescriptor added in v0.21.0

type CommandDescriptor struct {
	ID             string                     `json:"id"`
	MessageType    string                     `json:"message_type,omitempty"`
	Label          string                     `json:"label,omitempty"`
	Summary        string                     `json:"summary,omitempty"`
	Description    string                     `json:"description,omitempty"`
	Domain         string                     `json:"domain,omitempty"`
	Group          string                     `json:"group,omitempty"`
	Tags           []string                   `json:"tags,omitempty"`
	ExposeInAdmin  bool                       `json:"expose_in_admin,omitempty"`
	Mutating       bool                       `json:"mutating,omitempty"`
	ExecutionMode  ExecutionMode              `json:"execution_mode,omitempty"`
	Permissions    []string                   `json:"permissions,omitempty"`
	Roles          []string                   `json:"roles,omitempty"`
	Exposure       CommandExposure            `json:"exposure,omitempty"`
	RPC            RPCConfig                  `json:"rpc,omitempty"`
	Input          CommandInputSchema         `json:"input,omitempty"`
	Result         CommandResultDescriptor    `json:"result,omitempty"`
	Progress       *CommandProgressDescriptor `json:"progress,omitempty"`
	DisplayHints   map[string]any             `json:"display_hints,omitempty"`
	RedactionHints []string                   `json:"redaction_hints,omitempty"`
}

CommandDescriptor is the reusable command catalog contract. It describes an intentionally exposed command without importing an admin UI or transport.

func DescriptorForCommand added in v0.21.0

func DescriptorForCommand(cmd any, meta CommandMeta) (CommandDescriptor, bool)

DescriptorForCommand returns the merged descriptor for an exposed command.

func MergeCommandDescriptor added in v0.21.0

func MergeCommandDescriptor(meta CommandMeta, exposure CommandExposure, rpcConfig RPCConfig, explicit CommandDescriptor) CommandDescriptor

MergeCommandDescriptor composes message, exposure, RPC, and explicit descriptor metadata. Explicit descriptor fields take precedence.

type CommandDispatchRequest added in v0.21.0

type CommandDispatchRequest struct {
	CommandID string          `json:"command_id,omitempty"`
	Name      string          `json:"name,omitempty"`
	Payload   map[string]any  `json:"payload,omitempty"`
	IDs       []string        `json:"ids,omitempty"`
	Options   DispatchOptions `json:"options,omitempty"`
}

CommandDispatchRequest is the canonical generic command dispatch payload. Transports should wrap this in rpc.RequestEnvelope.

func (CommandDispatchRequest) ResolvedCommandID added in v0.21.0

func (r CommandDispatchRequest) ResolvedCommandID() string

ResolvedCommandID returns the stable command id, accepting Name only as a compatibility alias for older admin dispatch callers.

type CommandDispatchResponse added in v0.21.0

type CommandDispatchResponse struct {
	Receipt          DispatchReceipt                  `json:"receipt"`
	Result           any                              `json:"result,omitempty"`
	StatusReference  string                           `json:"status_reference,omitempty"`
	ValidationErrors []CommandDispatchValidationError `json:"validation_errors,omitempty"`
}

CommandDispatchResponse is the canonical generic command dispatch response. Transports should wrap this in rpc.ResponseEnvelope.

type CommandDispatchValidationError added in v0.21.0

type CommandDispatchValidationError struct {
	Path    string         `json:"path,omitempty"`
	Code    string         `json:"code,omitempty"`
	Message string         `json:"message"`
	Details map[string]any `json:"details,omitempty"`
}

CommandDispatchValidationError is safe for browser display and preserves a payload path so renderers can map validation failures to fields.

type CommandExposure added in v0.13.0

type CommandExposure struct {
	// ExposeInAdmin signals this command can be listed in go-admin UI/REPL.
	ExposeInAdmin bool
	// Tags for grouping/filtering in UI (e.g. "debug", "ops").
	Tags []string
	// Permissions required to execute (admin will enforce).
	// If empty, consumers should derive defaults from Mutates.
	Permissions []string
	// Roles allowed to execute (optional).
	Roles []string
	// Mutates signals side effects; false implies read-only.
	Mutates bool
}

CommandExposure declares optional UI/REPL exposure metadata. The zero value is safe: ExposeInAdmin is false and Mutates is read-only.

func ExposureOf added in v0.13.0

func ExposureOf(cmd any) (CommandExposure, bool)

ExposureOf returns exposure metadata if cmd implements ExposableCommand.

type CommandFunc

type CommandFunc[T any] func(ctx context.Context, msg T) error

CommandFunc is an adapter that lets you use a function as a CommandHandler[T]

func (CommandFunc[T]) Execute

func (f CommandFunc[T]) Execute(ctx context.Context, msg T) error

Execute calls the underlying function

type CommandInputField added in v0.21.0

type CommandInputField struct {
	ID            string                  `json:"id,omitempty"`
	Name          string                  `json:"name,omitempty"`
	Path          string                  `json:"path"`
	Label         string                  `json:"label,omitempty"`
	Kind          string                  `json:"kind,omitempty"`
	Type          string                  `json:"type,omitempty"`
	Format        string                  `json:"format,omitempty"`
	Required      bool                    `json:"required,omitempty"`
	Default       any                     `json:"default,omitempty"`
	Placeholder   string                  `json:"placeholder,omitempty"`
	Description   string                  `json:"description,omitempty"`
	Help          string                  `json:"help,omitempty"`
	Sensitive     bool                    `json:"sensitive,omitempty"`
	StaticOptions []CommandOption         `json:"static_options,omitempty"`
	OptionSource  *CommandOptionSourceRef `json:"option_source,omitempty"`
	Validation    map[string]any          `json:"validation,omitempty"`
	DisplayHints  map[string]any          `json:"display_hints,omitempty"`
}

CommandInputField describes one stable payload target.

type CommandInputSchema added in v0.21.0

type CommandInputSchema struct {
	Type       string              `json:"type,omitempty"`
	Fields     []CommandInputField `json:"fields,omitempty"`
	Required   []string            `json:"required,omitempty"`
	JSONSchema map[string]any      `json:"json_schema,omitempty"`
	Extensions map[string]any      `json:"extensions,omitempty"`
	NoInput    bool                `json:"no_input,omitempty"`
}

CommandInputSchema is a renderer-neutral, JSON Schema/OpenAPI-compatible payload description. Consumers can translate it to go-formgen or another UI.

func CommandInputSchemaFromMessage added in v0.21.0

func CommandInputSchemaFromMessage(msg any) CommandInputSchema

CommandInputSchemaFromMessage derives a minimal payload schema from exported struct fields. Sensitive fields marked with catalog:"sensitive" or command:"sensitive" are intentionally skipped.

type CommandInputSchemaProvider added in v0.21.0

type CommandInputSchemaProvider interface {
	CommandInputSchema() CommandInputSchema
}

CommandInputSchemaProvider lets a command provide late-bound schema metadata without coupling to a renderer.

type CommandMeta added in v0.11.0

type CommandMeta struct {
	MessageType      string
	MessageValue     any
	MessageTypeValue reflect.Type
}

func MessageTypeForCommand added in v0.11.0

func MessageTypeForCommand(cmd any) CommandMeta

type CommandOption added in v0.21.0

type CommandOption struct {
	Value       string         `json:"value"`
	Label       string         `json:"label,omitempty"`
	Description string         `json:"description,omitempty"`
	Disabled    bool           `json:"disabled,omitempty"`
	Metadata    map[string]any `json:"metadata,omitempty"`
}

CommandOption is a safe static option value.

type CommandOptionProvider added in v0.21.0

type CommandOptionProvider interface {
	CommandOptions(context.Context, CommandOptionRequest) ([]CommandOption, error)
}

CommandOptionProvider resolves request-scoped option values without coupling go-command descriptors to an admin UI or transport.

type CommandOptionRequest added in v0.21.0

type CommandOptionRequest struct {
	CommandID string                 `json:"command_id"`
	FieldID   string                 `json:"field_id,omitempty"`
	FieldName string                 `json:"field_name,omitempty"`
	FieldPath string                 `json:"field_path,omitempty"`
	Source    CommandOptionSourceRef `json:"source"`
	Payload   map[string]any         `json:"payload,omitempty"`
}

CommandOptionRequest identifies one request-scoped option lookup. Consumers authorize discovery before invoking a provider.

type CommandOptionSourceRef added in v0.21.0

type CommandOptionSourceRef struct {
	ID            string         `json:"id"`
	Label         string         `json:"label,omitempty"`
	Dynamic       bool           `json:"dynamic,omitempty"`
	CacheScope    string         `json:"cache_scope,omitempty"`
	RedactionHint string         `json:"redaction_hint,omitempty"`
	Params        map[string]any `json:"params,omitempty"`
}

CommandOptionSourceRef references request-scoped dynamic options without serializing the option values in the descriptor.

type CommandProgressCheckpoint added in v0.22.0

type CommandProgressCheckpoint struct {
	ID          string         `json:"id"`
	Label       string         `json:"label,omitempty"`
	Description string         `json:"description,omitempty"`
	Order       int            `json:"order,omitempty"`
	Metadata    map[string]any `json:"metadata,omitempty"`
}

CommandProgressCheckpoint describes a named checkpoint that a command may report.

type CommandProgressDescriber added in v0.22.0

type CommandProgressDescriber interface {
	CommandProgressDescriptor() CommandProgressDescriptor
}

CommandProgressDescriber lets a command provide optional progress metadata.

type CommandProgressDescriptor added in v0.22.0

type CommandProgressDescriptor struct {
	Units       string                      `json:"units,omitempty"`
	Total       int64                       `json:"total,omitempty"`
	Checkpoints []CommandProgressCheckpoint `json:"checkpoints,omitempty"`
	Metadata    map[string]any              `json:"metadata,omitempty"`
}

CommandProgressDescriptor describes optional, safe progress metadata for a command catalog entry.

type CommandProgressReporter added in v0.22.0

type CommandProgressReporter interface {
	ReportProgress(context.Context, ProgressUpdate)
}

CommandProgressReporter receives cooperative progress updates from commands.

func CommandProgressReporterFromContext added in v0.22.0

func CommandProgressReporterFromContext(ctx context.Context) (CommandProgressReporter, bool)

CommandProgressReporterFromContext returns the progress reporter, if present.

type CommandProgressReporterFunc added in v0.22.0

type CommandProgressReporterFunc func(context.Context, ProgressUpdate)

CommandProgressReporterFunc adapts a function into a CommandProgressReporter.

func (CommandProgressReporterFunc) ReportProgress added in v0.22.0

func (f CommandProgressReporterFunc) ReportProgress(ctx context.Context, update ProgressUpdate)

type CommandResultDescriptor added in v0.21.0

type CommandResultDescriptor struct {
	Inline          bool           `json:"inline,omitempty"`
	Queued          bool           `json:"queued,omitempty"`
	StatusReference string         `json:"status_reference,omitempty"`
	ReceiptExpected bool           `json:"receipt_expected,omitempty"`
	ResultSchema    map[string]any `json:"result_schema,omitempty"`
	RedactionHints  []string       `json:"redaction_hints,omitempty"`
	DisplayHints    map[string]any `json:"display_hints,omitempty"`
}

CommandResultDescriptor declares the safe result/receipt expectations.

type CommandRunAttempt added in v0.22.0

type CommandRunAttempt struct {
	Attempt     int
	MaxAttempts int
}

CommandRunAttempt describes the current or final runner attempt.

func CommandRunAttemptFromContext added in v0.22.0

func CommandRunAttemptFromContext(ctx context.Context) (CommandRunAttempt, bool)

CommandRunAttemptFromContext returns current attempt metadata, if present.

type CommandRunEvent added in v0.22.0

type CommandRunEvent struct {
	RunID           string
	Revision        uint64
	DispatchID      string
	CommandID       string
	Handler         string
	HandlerKind     HandlerKind
	DispatchTarget  DispatchTarget
	Route           string
	ExecutionMode   ExecutionMode
	Phase           CommandRunPhase
	Checkpoint      string
	Message         string
	Current         int64
	Total           int64
	CorrelationID   string
	IdempotencyKey  string
	Attempt         int
	MaxAttempts     int
	OccurredAt      time.Time
	StartedAt       time.Time
	Duration        time.Duration
	Receipt         DispatchReceipt
	Provenance      DispatchProvenance
	FailureCategory string
	Metadata        map[string]any
	Error           error
}

CommandRunEvent is the transport-neutral event emitted for command run lifecycle and cooperative progress updates.

func (CommandRunEvent) Clone added in v0.22.0

func (e CommandRunEvent) Clone() CommandRunEvent

Clone returns an event copy with isolated metadata.

type CommandRunMetadataSanitizer added in v0.22.0

type CommandRunMetadataSanitizer func(map[string]any) map[string]any

CommandRunMetadataSanitizer transforms safe metadata before forwarding.

type CommandRunObserver added in v0.22.0

type CommandRunObserver interface {
	OnCommandRunEvent(context.Context, CommandRunEvent) error
}

CommandRunObserver receives command run events. Implementations should return errors for host diagnostics only; dispatchers treat observers as fail-open.

func CommandRunStatsObserver added in v0.22.0

func CommandRunStatsObserver(recorder CommandRunStatsRecorder) CommandRunObserver

CommandRunStatsObserver adapts a stats recorder into a fail-open observer.

func CommandRunStoreObserver added in v0.22.0

func CommandRunStoreObserver(store CommandRunStore) CommandRunObserver

CommandRunStoreObserver adapts a run store into a fail-open observer.

func SanitizingCommandRunObserver added in v0.22.0

func SanitizingCommandRunObserver(inner CommandRunObserver, sanitizer CommandRunMetadataSanitizer) CommandRunObserver

SanitizingCommandRunObserver clones and sanitizes metadata before forwarding an event to another observer.

type CommandRunObserverFunc added in v0.22.0

type CommandRunObserverFunc func(context.Context, CommandRunEvent) error

CommandRunObserverFunc adapts a function into a CommandRunObserver.

func (CommandRunObserverFunc) OnCommandRunEvent added in v0.22.0

func (f CommandRunObserverFunc) OnCommandRunEvent(ctx context.Context, event CommandRunEvent) error

type CommandRunPhase added in v0.22.0

type CommandRunPhase string

CommandRunPhase identifies a lifecycle or cooperative progress event.

const (
	CommandRunPhaseSubmitted  CommandRunPhase = "submitted"
	CommandRunPhaseStarted    CommandRunPhase = "started"
	CommandRunPhaseCheckpoint CommandRunPhase = "checkpoint"
	CommandRunPhaseProgress   CommandRunPhase = "progress"
	CommandRunPhaseSucceeded  CommandRunPhase = "succeeded"
	CommandRunPhaseFailed     CommandRunPhase = "failed"
	CommandRunPhaseCanceled   CommandRunPhase = "canceled"
	CommandRunPhaseRejected   CommandRunPhase = "rejected"
)

type CommandRunStatsRecorder added in v0.22.0

type CommandRunStatsRecorder interface {
	RecordCommandRunEvent(context.Context, CommandRunEvent) error
}

CommandRunStatsRecorder is a small adapter target for counters and metrics.

type CommandRunStatsRecorderFunc added in v0.22.0

type CommandRunStatsRecorderFunc func(context.Context, CommandRunEvent) error

CommandRunStatsRecorderFunc adapts a function into a stats recorder.

func (CommandRunStatsRecorderFunc) RecordCommandRunEvent added in v0.22.0

func (f CommandRunStatsRecorderFunc) RecordCommandRunEvent(ctx context.Context, event CommandRunEvent) error

type CommandRunStore added in v0.22.0

type CommandRunStore interface {
	StoreCommandRunEvent(context.Context, CommandRunEvent) error
}

CommandRunStore is a small adapter target for durable or in-memory run stores.

type CommandRunStoreFunc added in v0.22.0

type CommandRunStoreFunc func(context.Context, CommandRunEvent) error

CommandRunStoreFunc adapts a function into a run store.

func (CommandRunStoreFunc) StoreCommandRunEvent added in v0.22.0

func (f CommandRunStoreFunc) StoreCommandRunEvent(ctx context.Context, event CommandRunEvent) error

type Commander

type Commander[T any] interface {
	Execute(ctx context.Context, msg T) error
}

Commander is responsible for executing side effects

type CronCommand added in v0.3.0

type CronCommand interface {
	CronHandler() func() error
	CronOptions() HandlerConfig
}

type DedupPolicy added in v0.17.0

type DedupPolicy string
const (
	DedupPolicyIgnore  DedupPolicy = "ignore"
	DedupPolicyDrop    DedupPolicy = "drop"
	DedupPolicyMerge   DedupPolicy = "merge"
	DedupPolicyReplace DedupPolicy = "replace"
)

func NormalizeDedupPolicy added in v0.17.0

func NormalizeDedupPolicy(policy DedupPolicy) DedupPolicy

func ParseDedupPolicy added in v0.17.0

func ParseDedupPolicy(raw string) (DedupPolicy, error)

type DispatchOptions added in v0.17.0

type DispatchOptions struct {
	Mode           ExecutionMode
	IdempotencyKey string
	DedupPolicy    DedupPolicy
	Delay          time.Duration
	RunAt          *time.Time
	CorrelationID  string
	Metadata       map[string]any
}

func DispatchOptionsFromContext added in v0.17.0

func DispatchOptionsFromContext(ctx context.Context) (DispatchOptions, bool)

type DispatchOutcome added in v0.23.0

type DispatchOutcome struct {
	Receipt         DispatchReceipt
	Target          DispatchTarget
	Route           string
	Result          any
	ResultPresent   bool
	StatusReference string
}

DispatchOutcome is the transport-neutral result of local or remote execution. ResultPresent distinguishes a valid zero/nil result from no result.

type DispatchProvenance added in v0.23.0

type DispatchProvenance struct {
	IngressKind   string
	Route         string
	DeliveryID    string
	Attempt       int
	CorrelationID string
	CausationID   string
}

DispatchProvenance carries transport-neutral ingress identity without exposing broker envelopes or payloads.

func DispatchProvenanceFromContext added in v0.23.0

func DispatchProvenanceFromContext(ctx context.Context) (DispatchProvenance, bool)

type DispatchReceipt added in v0.17.0

type DispatchReceipt struct {
	Accepted      bool
	Mode          ExecutionMode
	CommandID     string
	DispatchID    string
	EnqueuedAt    *time.Time
	CorrelationID string
}

type DispatchRoute added in v0.23.0

type DispatchRoute struct {
	Target DispatchTarget
	Name   string
}

DispatchRoute is a logical placement decision. Name is adapter-owned and is intentionally not a broker destination or transport URL.

func NormalizeDispatchRoute added in v0.23.3

func NormalizeDispatchRoute(route DispatchRoute) DispatchRoute

NormalizeDispatchRoute returns the canonical logical route representation used for validation, adapter invocation, outcomes, and lifecycle events.

type DispatchRunContext added in v0.22.0

type DispatchRunContext struct {
	RunID string
	// Revision is the last lifecycle revision reserved by the process handing
	// this run to another executor. Persist it with queued work so a worker can
	// continue the same monotonic sequence in another process.
	Revision       uint64
	DispatchID     string
	CommandID      string
	Handler        string
	HandlerKind    HandlerKind
	DispatchTarget DispatchTarget
	Route          string
	ExecutionMode  ExecutionMode
	CorrelationID  string
	IdempotencyKey string
	Attempt        int
	MaxAttempts    int
	StartedAt      time.Time
	Receipt        DispatchReceipt
	Provenance     DispatchProvenance
	Metadata       map[string]any
}

DispatchRunContext carries safe run identity and dispatch metadata through a command execution context.

func DispatchRunFromContext added in v0.22.0

func DispatchRunFromContext(ctx context.Context) (DispatchRunContext, bool)

DispatchRunFromContext returns command run identity, if present.

type DispatchTarget added in v0.23.0

type DispatchTarget string

DispatchTarget identifies whether a registration executes in this process or through an installed remote dispatcher.

const (
	DispatchTargetLocal  DispatchTarget = "local"
	DispatchTargetRemote DispatchTarget = "remote"
)

type DynamicResultSink added in v0.23.0

type DynamicResultSink interface {
	ResultType() reflect.Type
	StoreDynamic(any) error
}

DynamicResultSink is the type-reporting bridge used when a runtime receives a result without compile-time access to T.

func DynamicResultSinkFromContext added in v0.23.0

func DynamicResultSinkFromContext(ctx context.Context) (DynamicResultSink, bool)

DynamicResultSinkFromContext returns the untyped view installed by ContextWithResult. Typed callers should continue using ResultFromContext.

type ExecutionMode added in v0.17.0

type ExecutionMode string
const (
	ExecutionModeInline ExecutionMode = "inline"
	ExecutionModeQueued ExecutionMode = "queued"
)

func NormalizeExecutionMode added in v0.17.0

func NormalizeExecutionMode(mode ExecutionMode) ExecutionMode

func ParseExecutionMode added in v0.17.0

func ParseExecutionMode(raw string) (ExecutionMode, error)

type ExposableCommand added in v0.13.0

type ExposableCommand interface {
	Exposure() CommandExposure
}

ExposableCommand can be implemented by CLICommand (or any Command/Query). Opt-in is explicit: consumers should ignore commands that do not implement it or return ExposeInAdmin=false.

type HTTPCommand added in v0.3.0

type HTTPCommand interface {
	HTTPHandler()
}

type HandlerConfig added in v0.3.0

type HandlerConfig struct {
	Timeout    time.Duration `json:"timeout"`
	Deadline   time.Time     `json:"deadline"`
	MaxRetries int           `json:"max_retries"`
	MaxRuns    int           `json:"max_runs"`
	RunOnce    bool          `json:"run_once"`
	Expression string        `json:"expression"`
	NoTimeout  bool          `json:"no_timeout"`
}

type HandlerKind added in v0.23.0

type HandlerKind string

HandlerKind identifies which handler capability a registration represents. A concrete handler may expose both command and query registrations.

const (
	HandlerKindCommand HandlerKind = "command"
	HandlerKindQuery   HandlerKind = "query"
)

type LocalInvoker added in v0.23.0

type LocalInvoker interface {
	InvokeLocal(context.Context, MessageRegistration, any, DispatchOptions) (DispatchOutcome, error)
}

LocalInvoker executes an already-resolved registration in this process.

type Message

type Message interface {
	Type() string
}

Message is the interface command and queries messages must implement

type MessageFactory added in v0.11.0

type MessageFactory interface {
	MessageValue() any
}

MessageFactory provides a concrete message value for interface-based commands.

type MessageRegistration added in v0.23.0

type MessageRegistration interface {
	ID() string
	MessageType() string
	Kind() HandlerKind
	NewMessage() any
	RequestType() reflect.Type
	ResultType() reflect.Type
}

MessageRegistration is immutable metadata for one kind-qualified message. Implementations must return a fresh message from NewMessage.

func MessageRegistrationsForCommand added in v0.23.0

func MessageRegistrationsForCommand(cmd any) ([]MessageRegistration, error)

MessageRegistrationsForCommand discovers command and query capabilities independently. The legacy MessageTypeForCommand helper intentionally remains query-first for source compatibility.

type MessageRegistrationIndex added in v0.23.0

type MessageRegistrationIndex struct {
	// contains filtered or unexported fields
}

MessageRegistrationIndex atomically publishes complete immutable registration generations.

func NewMessageRegistrationIndex added in v0.23.0

func NewMessageRegistrationIndex(registrations ...MessageRegistration) (*MessageRegistrationIndex, error)

func (*MessageRegistrationIndex) RegistrationByID added in v0.23.0

func (i *MessageRegistrationIndex) RegistrationByID(kind HandlerKind, stableID string) (MessageRegistration, bool)

func (*MessageRegistrationIndex) RegistrationByMessageType added in v0.23.0

func (i *MessageRegistrationIndex) RegistrationByMessageType(kind HandlerKind, messageType string) (MessageRegistration, bool)

func (*MessageRegistrationIndex) Registrations added in v0.23.0

func (i *MessageRegistrationIndex) Registrations() []MessageRegistration

func (*MessageRegistrationIndex) Replace added in v0.23.0

func (i *MessageRegistrationIndex) Replace(registrations []MessageRegistration) error

Replace validates a complete generation before publishing it. A failed replacement leaves the previous snapshot active.

type PanicLogger added in v0.2.0

type PanicLogger func(funcName string, err any, stack []byte, fields ...map[string]any)

type PlacementPoliciesProvider added in v0.23.0

type PlacementPoliciesProvider interface {
	PlacementResolver
	PlacementPolicies() []PlacementPolicy
}

PlacementPoliciesProvider exposes copied policies so a runtime can validate them against a replacement registration generation.

func NewPlacementPolicySnapshot added in v0.23.0

func NewPlacementPolicySnapshot(provider RegistrationProvider, policies ...PlacementPolicy) (PlacementPoliciesProvider, error)

type PlacementPolicy added in v0.23.0

type PlacementPolicy struct {
	Kind           HandlerKind
	RegistrationID string
	Route          DispatchRoute
}

PlacementPolicy binds one kind-qualified registration ID to a logical route.

type PlacementResolver added in v0.23.0

type PlacementResolver interface {
	ResolvePlacement(context.Context, MessageRegistration, DispatchOptions) (route DispatchRoute, found bool, err error)
}

PlacementResolver selects a logical route for a registration. found=false selects the local default.

type ProgressAwareCommander added in v0.22.0

type ProgressAwareCommander[T any] interface {
	ExecuteWithProgress(context.Context, T, CommandProgressReporter) error
}

ProgressAwareCommander can receive an explicit reporter while still supporting the ordinary Commander Execute method for compatibility.

type ProgressOption added in v0.22.0

type ProgressOption func(*ProgressUpdate)

ProgressOption customizes a checkpoint or progress update.

func WithProgressMessage added in v0.22.0

func WithProgressMessage(message string) ProgressOption

WithProgressMessage attaches a safe operator-facing message to an update.

func WithProgressMetadata added in v0.22.0

func WithProgressMetadata(metadata map[string]any) ProgressOption

WithProgressMetadata attaches safe metadata to an update. The map is cloned.

type ProgressUpdate added in v0.22.0

type ProgressUpdate struct {
	Checkpoint string
	Message    string
	Current    int64
	Total      int64
	Metadata   map[string]any
}

ProgressUpdate describes a cooperative progress or checkpoint update from command code.

type Querier

type Querier[T any, R any] interface {
	Query(ctx context.Context, msg T) (R, error)
}

Querier is responsible for returning data, with no side effects

type QueryFunc

type QueryFunc[T any, R any] func(ctx context.Context, msg T) (R, error)

QueryFunc is an adapter that lets you use a function as a QueryHandler[T, R]

func (QueryFunc[T, R]) Query

func (f QueryFunc[T, R]) Query(ctx context.Context, msg T) (R, error)

Query calls the underlying function

type RPCCommand added in v0.15.0

type RPCCommand interface {
	RPCHandler() any
	RPCOptions() RPCConfig
}

RPCCommand allows commands/queries to opt into RPC registration.

type RPCConfig added in v0.15.0

type RPCConfig struct {
	// Method is the RPC method name, e.g. "fsm.apply_event".
	Method string
	// Timeout is an optional transport timeout hint.
	Timeout time.Duration
	// Streaming declares whether this endpoint is stream oriented.
	Streaming bool
	// Idempotent declares whether retries are generally safe.
	Idempotent bool
	// Permissions required to invoke this method.
	Permissions []string
	// Roles allowed to invoke this method.
	Roles []string
	// Summary is a short endpoint description for docs/discovery.
	Summary string
	// Description is a longer endpoint description for docs/discovery.
	Description string
	// Tags groups endpoint in generated clients/docs.
	Tags []string
	// Deprecated marks endpoint for phased removal.
	Deprecated bool
	// Since captures the version this endpoint was introduced.
	Since string
}

RPCConfig declares optional transport level metadata for RPC exposure. The zero value is safe and keeps behavior minimally configured.

type RPCDescriber added in v0.15.0

type RPCDescriber interface {
	RPCDescription() RPCDescription
}

RPCDescriber allows commands to expose richer endpoint metadata for docs/codegen. This is optional and only used by RPC resolver paths.

type RPCDescription added in v0.15.0

type RPCDescription struct {
	Summary     string
	Description string
	Tags        []string
	Deprecated  bool
	Since       string
}

RPCDescription carries optional endpoint documentation metadata.

type RegistrationProvider added in v0.23.0

type RegistrationProvider interface {
	RegistrationByID(kind HandlerKind, stableID string) (MessageRegistration, bool)
	RegistrationByMessageType(kind HandlerKind, messageType string) (MessageRegistration, bool)
	Registrations() []MessageRegistration
}

RegistrationProvider exposes an immutable registration snapshot.

type Registry added in v0.3.0

type Registry struct {
	// contains filtered or unexported fields
}

func NewRegistry added in v0.3.0

func NewRegistry() *Registry

func (*Registry) AddResolver added in v0.11.0

func (r *Registry) AddResolver(key string, res Resolver) error

func (*Registry) CatalogDescriptor added in v0.21.0

func (r *Registry) CatalogDescriptor(commandID string) (CommandDescriptor, bool)

CatalogDescriptor returns one descriptor by stable command ID.

func (*Registry) CatalogDescriptors added in v0.21.0

func (r *Registry) CatalogDescriptors() []CommandDescriptor

CatalogDescriptors returns descriptors in deterministic registration order.

func (*Registry) CatalogDescriptorsByExposure added in v0.21.0

func (r *Registry) CatalogDescriptorsByExposure(exposed bool) []CommandDescriptor

CatalogDescriptorsByExposure returns descriptors with the requested admin exposure state. It does not perform actor authorization.

func (*Registry) CatalogDescriptorsByTag added in v0.21.0

func (r *Registry) CatalogDescriptorsByTag(tags ...string) []CommandDescriptor

CatalogDescriptorsByTag returns descriptors matching at least one tag.

func (*Registry) GetCLIOptions added in v0.3.0

func (r *Registry) GetCLIOptions() ([]kong.Option, error)

func (*Registry) HasResolver added in v0.11.0

func (r *Registry) HasResolver(key string) bool

func (*Registry) Initialize added in v0.3.0

func (r *Registry) Initialize() error

func (*Registry) RegisterCatalogDescriptor added in v0.21.0

func (r *Registry) RegisterCatalogDescriptor(descriptor CommandDescriptor) error

RegisterCatalogDescriptor registers or replaces an external catalog descriptor. Duplicate IDs replace in place and preserve original ordering.

func (*Registry) RegisterCommand added in v0.3.0

func (r *Registry) RegisterCommand(cmd any) error

func (*Registry) RegistrationByID added in v0.23.0

func (r *Registry) RegistrationByID(kind HandlerKind, stableID string) (MessageRegistration, bool)

func (*Registry) RegistrationByMessageType added in v0.23.0

func (r *Registry) RegistrationByMessageType(kind HandlerKind, messageType string) (MessageRegistration, bool)

func (*Registry) Registrations added in v0.23.0

func (r *Registry) Registrations() []MessageRegistration

func (*Registry) SetCronRegister added in v0.3.0

func (r *Registry) SetCronRegister(fn func(opts HandlerConfig, handler any) error) *Registry

func (*Registry) SetRPCRegister added in v0.15.0

func (r *Registry) SetRPCRegister(fn func(opts RPCConfig, handler any, meta CommandMeta) error) *Registry

type RemoteDispatcher added in v0.23.0

type RemoteDispatcher interface {
	DispatchRemote(context.Context, DispatchRoute, MessageRegistration, any, DispatchOptions) (DispatchOutcome, error)
}

RemoteDispatcher is implemented by an optional transport adapter. The port deliberately excludes envelopes, acknowledgements, and broker concepts.

type Resolver added in v0.11.0

type Resolver func(cmd any, meta CommandMeta, r *Registry) error

type Result added in v0.3.0

type Result[T any] struct {
	// contains filtered or unexported fields
}

Result collector implementation from before

func NewResult added in v0.3.0

func NewResult[T any]() *Result[T]

func ResultFromContext added in v0.3.0

func ResultFromContext[T any](ctx context.Context) *Result[T]

func (*Result[T]) Error added in v0.3.0

func (r *Result[T]) Error() error

func (*Result[T]) GetMetadata added in v0.3.0

func (r *Result[T]) GetMetadata(key string) (any, bool)

func (*Result[T]) Load added in v0.3.0

func (r *Result[T]) Load() (T, bool)

func (*Result[T]) ResultType added in v0.23.0

func (r *Result[T]) ResultType() reflect.Type

ResultType reports the exact collector type, including pointer and interface result types.

func (*Result[T]) Store added in v0.3.0

func (r *Result[T]) Store(value T)

func (*Result[T]) StoreDynamic added in v0.23.0

func (r *Result[T]) StoreDynamic(value any) error

StoreDynamic validates and stores an untyped value. An untyped nil is a present result only when T can represent nil.

func (*Result[T]) StoreError added in v0.3.0

func (r *Result[T]) StoreError(err error)

func (*Result[T]) StoreWithMeta added in v0.3.0

func (r *Result[T]) StoreWithMeta(value T, meta map[string]any)

type ResultKey added in v0.3.0

type ResultKey[T any] struct{}

Jump to

Keyboard shortcuts

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