action

package
v0.3.2 Latest Latest
Warning

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

Go to latest
Published: Sep 3, 2026 License: Apache-2.0 Imports: 20 Imported by: 0

Documentation

Overview

Package action provides typed, composable application actions.

Build an action with New, configure it with fluent policies and middleware, then reuse the resulting BuiltAction concurrently. The typed Do method is the preferred execution path; decoded execution is intended for adapters.

Index

Constants

View Source
const (
	DefaultIdempotencyTTL      = 24 * time.Hour
	DefaultIdempotencyLeaseTTL = 2 * time.Minute
)

Variables

View Source
var ErrConcurrencyLimit = errors.New("concurrency limit exceeded")
View Source
var ErrLocked = xerr.Conflict("resource is currently locked by another instance")

ErrLocked is returned when an action is already locked by another instance.

View Source
var ErrTypeAssertion = errors.New("critical type assertion failure")

Functions

func AlwaysRetryPredicate added in v0.3.0

func AlwaysRetryPredicate(err error) bool

AlwaysRetryPredicate retries any non-nil error.

func Async

func Async[Req, Res any](ctx context.Context, act *BuiltAction[Req, Res], req Req) <-chan AsyncResult[Res]

Async executes the action in a goroutine and returns a result channel.

func CollectStream

func CollectStream[Req, T any](ctx context.Context, a *StreamAction[Req, T], req Req) ([]T, error)

CollectStream runs the entire stream and returns all items as a slice.

func ConstantBackoff

func ConstantBackoff(d time.Duration) func(attempt int) time.Duration

ConstantBackoff waits the same duration between every attempt.

func DefaultRetryPredicate added in v0.3.0

func DefaultRetryPredicate(err error) bool

DefaultRetryPredicate retries only errors flagged as transient by xerr.

func ExecutionIDFrom

func ExecutionIDFrom(ctx context.Context) string

ExecutionIDFrom returns the optional correlation ID attached to ctx.

func ExponentialBackoff

func ExponentialBackoff(base, maxDuration time.Duration) func(attempt int) time.Duration

ExponentialBackoff returns a backoff that doubles each attempt, capped at max. 100ms → 200ms → 400ms → 800ms … → max

Usage: .Retry(3, action.ExponentialBackoff(100*time.Millisecond, 5*time.Second))

func ExponentialJitter

func ExponentialJitter(base, maxDuration time.Duration) func(attempt int) time.Duration

ExponentialJitter adds ±30% random jitter to ExponentialBackoff. Prevents thundering-herd on simultaneous retries.

Usage: .Retry(3, action.ExponentialJitter(100*time.Millisecond, 5*time.Second))

func LinearBackoff

func LinearBackoff(step time.Duration) func(attempt int) time.Duration

LinearBackoff increments by step each attempt. 100ms → 200ms → 300ms …

func Race

func Race[Req, Res any](
	ctx context.Context,
	act *BuiltAction[Req, Res],
	reqs []Req,
) (Res, error)

Race executes the action concurrently for each request and returns the first success.

func SpanIDFrom

func SpanIDFrom(ctx context.Context) string

SpanIDFrom returns the optional current span ID from ctx.

func TraceIDFrom

func TraceIDFrom(ctx context.Context) string

TraceIDFrom returns the optional distributed trace ID from ctx.

func WithExecutionID

func WithExecutionID(ctx context.Context, id string) context.Context

WithExecutionID attaches an optional correlation ID to an action context. Callers should provide a request, trace, or action-execution ID at the transport or application boundary. Empty IDs are treated as absent.

func WithTraceContext

func WithTraceContext(ctx context.Context, traceID, spanID string) context.Context

WithTraceContext attaches optional distributed-trace identifiers to ctx. The transport or tracing adapter owns ID generation and propagation.

Types

type ActionProvider

type ActionProvider interface {
	Actions() []AnyAction
}

ActionProvider exposes the registered actions of an application.

type ActionScope

type ActionScope string

ActionScope describes the contract audience for an action. It is not an authorization rule: authentication and authorization remain enforced by the action's transport middleware and guards.

const (
	// ScopePublic is the browser/client business contract. It is the zero-value
	// behavior so normal client actions remain concise.
	ScopePublic ActionScope = "public"
	// ScopeInternal is a trusted service-to-service or runner contract. It is
	// included only in explicitly requested trusted SDK generation.
	ScopeInternal ActionScope = "internal"
	// ScopeSystem is a framework or operational contract. It is excluded from
	// public and trusted business contracts.
	ScopeSystem ActionScope = "system"
)

type AdaptiveConfig

type AdaptiveConfig struct {
	FailureThreshold int
	ResetTimeout     time.Duration
	InitialTimeout   time.Duration
}

func (*AdaptiveConfig) SetDefaults

func (c *AdaptiveConfig) SetDefaults()

type Admission

type Admission interface {
	Acquire(context.Context) error
	Release()
}

Admission controls whether an execution may enter a protected section. Implementations may be local or distributed. Acquire must return an error without retaining the request when admission is denied.

type AnyAction

type AnyAction interface {
	Executable
	Describable
	GetBindings() []Binding
	GetAnyHooks() []AnyHook
	AddAnyHook(h ...AnyHook)
}

AnyAction is the type-erased interface for the App and Transports to handle actions.

type AnyHook

type AnyHook struct {
	OnRegister     func(meta *Meta)
	Before         func(ctx context.Context, req any, meta *Meta) (context.Context, error)
	After          func(ctx context.Context, req any, res any, err error, meta *Meta)
	OnError        func(ctx context.Context, req any, err error, meta *Meta)
	OnRetry        func(ctx context.Context, req any, attempt int, err error, meta *Meta)
	OnCacheHit     func(ctx context.Context, req any, res any, meta *Meta)
	OnCacheMiss    func(ctx context.Context, req any, meta *Meta)
	OnCoalesced    func(ctx context.Context, req any, meta *Meta)
	OnDeduplicated func(ctx context.Context, req any, meta *Meta)
	OnCancel       func(ctx context.Context, req any, meta *Meta)
	OnExecuted     func(ctx context.Context, req any, res any, err error, meta *Meta)
	OnPanic        func(ctx context.Context, req any, recovered any, meta *Meta)
}

AnyHook is used for broad plugins (metrics, tracing, auth). Passing 'meta' dynamically guarantees thread-safety and zero allocations across shared instances.

func Adapt

func Adapt[Req, Res any](h Hook[Req, Res]) AnyHook

Adapt converts a type-safe Hook[Req, Res] into the standardized AnyHook container. It uses type assertions with safe zero-value fallbacks so hooks fire reliably even when res is nil.

type AsyncResult

type AsyncResult[Res any] struct {
	Value Res
	Err   error
}

AsyncResult carries the outcome of an asynchronous action execution.

func FanOut

func FanOut[Req, Res any](
	ctx context.Context,
	act *BuiltAction[Req, Res],
	reqs []Req,
	maxConcurrency int,
) []AsyncResult[Res]

FanOut executes the action concurrently for each request. maxConcurrency limits how many run simultaneously; 0 = unbounded.

type AuditLogger

type AuditLogger interface {
	Log(ctx context.Context, category string, actionName string, details string)
}

AuditLogger defines the minimal contract required by the builder to log audit events. This prevents circular import dependencies between action and audit adapters.

type Binding

type Binding any

type Bootstrapper

type Bootstrapper interface {
	OnBoot(app ActionProvider) error
}

Bootstrapper allows plugins/actions to hook into the boot lifecycle.

type Builder

type Builder[Req, Res any] struct {
	// contains filtered or unexported fields
}

Builder[Req, Res] is the fluent construction API for an action. All methods return the same builder for chaining. Call Build() once — the result is immutable and safe for concurrent use.

func Branch

func Branch[Req, Res any](
	name string,
	routes map[string]*Builder[Req, Res],
	router func(context.Context, Req) (string, error),
) *Builder[Req, Res]

Branch routes a request to one of several named actions based on a router function.

func Chain

func Chain[T any](
	name string,
	builders ...*Builder[T, T],
) *Builder[T, T]

Chain runs same-typed actions sequentially; each receives the previous output.

func FirstSuccess

func FirstSuccess[Req, Res any](
	name string,
	builders ...*Builder[Req, Res],
) *Builder[Req, Res]

FirstSuccess executes actions in order, returning the first non-error result.

func New

func New[Req, Res any](name string, exec Fn[Req, Res]) *Builder[Req, Res]

New creates an action builder.

func Parallel

func Parallel[Req, Res any](
	name string,
	builders ...*Builder[Req, Res],
) *Builder[Req, []Res]

Parallel executes all actions concurrently with the same request. Concurrency pattern: Scatter-gather using sync.WaitGroup with independent result slots.

func Pipe

func Pipe[Req, Mid, Res any](
	name string,
	first *BuiltAction[Req, Mid],
	second *BuiltAction[Mid, Res],
) *Builder[Req, Res]

Pipe connects two actions: output of first feeds input of second. Usage:

pipe := action.Pipe[Req, Middle, Res]("order.pipe",
    buildCheck, buildProcess)
result := pipe.Do(ctx, req)

func (*Builder[Req, Res]) Add

func (b *Builder[Req, Res]) Add(others ...AnyAction) *Builder[Req, Res]

Add composes AnyHooks and Bindings from other AnyActions (plugins) into this builder. Typed hooks are NOT composed — use Hook() directly for those. Safe to call multiple times; idempotent per unique plugin instance.

func (*Builder[Req, Res]) AnyHook

func (b *Builder[Req, Res]) AnyHook(h ...AnyHook) *Builder[Req, Res]

AnyHook registers a type-erased hook (used by plugins: monitor, telemetry, tracing). Prefer typed Hook[Req,Res] when the action types are known.

func (*Builder[Req, Res]) Audited

func (b *Builder[Req, Res]) Audited(logger AuditLogger, category string, detailsFn func(req Req, res Res) string) *Builder[Req, Res]

Audited attaches a generic audit hook invoked after successful action execution.

func (*Builder[Req, Res]) Build

func (b *Builder[Req, Res]) Build() *BuiltAction[Req, Res]

func (*Builder[Req, Res]) Cache

func (b *Builder[Req, Res]) Cache(ttl time.Duration, keyFn func(Req) string, layers ...CacheLayer[Res]) *Builder[Req, Res]

Cache adds a multi-layer cache to the action. Layers are checked in order: L1 (fast, local) → L2 (shared, e.g. Redis). On a miss, the handler runs and the result is written to all layers. On an L2 hit, L1 is back-filled automatically.

The keyFn derives a stable string cache key from the request. Keep keys short and deterministic (UUID, int, composite "tenant:id", etc.).

Example — single in-memory layer:

action.New("catalog.list", fetchCatalog).
    Cache(30*time.Minute,
        func(_ CatalogReq) string { return "catalog:all" },
        cache.NewInMemory[[]Product](30*time.Minute),
    ).Build()

Example — L1 memory + L2 Redis:

action.New("product.get", fetchProduct).
    Cache(10*time.Minute,
        func(r ProductReq) string { return r.ProductID },
        cache.NewInMemory[Product](5*time.Minute),
        cache.NewRedis[Product](redisClient),
    ).Build()

func (*Builder[Req, Res]) Coalesce

func (b *Builder[Req, Res]) Coalesce(c *Coalescer, keyFn func(Req) string) *Builder[Req, Res]

Coalesce deduplicates concurrent requests using request coalescing. Unlike Dedup, Coalesce allows a caller whose context is canceled to bail out early without killing the underlying in-flight request — the in-flight request continues for other waiters.

A single *Coalescer can be shared across multiple actions:

c := action.NewCoalescer()

productAct := action.New("product.get", fetchProduct).
    Coalesce(c, func(r ProductReq) string { return r.ProductID }).
    Build()

inventoryAct := action.New("inventory.check", checkStock).
    Coalesce(c, func(r InventoryReq) string { return r.SKU }).
    Build()

func (*Builder[Req, Res]) Compose

func (b *Builder[Req, Res]) Compose(other *BuiltAction[Req, Res]) *Builder[Req, Res]

Compose copies typed hooks, AnyHooks, and Bindings from another BuiltAction with the same Req/Res types into this builder.

func (*Builder[Req, Res]) ConcurrencyLimit

func (b *Builder[Req, Res]) ConcurrencyLimit(limit int32) *Builder[Req, Res]

func (*Builder[Req, Res]) Dedup

func (b *Builder[Req, Res]) Dedup(keyFn func(Req) string) *Builder[Req, Res]

Dedup prevents concurrent identical requests from executing multiple times. All callers with the same key block until the first one completes, then share its result — exactly one handler invocation per unique key at any point in time.

keyFn returns the dedup key. An empty string disables dedup for that request.

Use case — prevent N concurrent callers from all hitting the DB for the same product:

productAct := action.New("product.price", fetchPrice).
    Dedup(func(r PriceReq) string { return r.ProductID }).
    Build()

func (*Builder[Req, Res]) Describe

func (b *Builder[Req, Res]) Describe() *Meta

func (*Builder[Req, Res]) Description

func (b *Builder[Req, Res]) Description(d string) *Builder[Req, Res]

func (*Builder[Req, Res]) Emits

func (b *Builder[Req, Res]) Emits(subject string, mapper func(res Res) any) *Builder[Req, Res]

Emits registers automatic event emission upon successful action execution. Payload mapping is evaluated lazily only when an active EventPublisher is in context.

func (*Builder[Req, Res]) Exclusive

func (b *Builder[Req, Res]) Exclusive(m Mutex, ttl time.Duration, keyFn func(Req) string) *Builder[Req, Res]

Exclusive wraps an action with a standard distributed lock. If the lock cannot be acquired, ErrLocked is returned immediately.

func (*Builder[Req, Res]) ExclusiveFenced

func (b *Builder[Req, Res]) ExclusiveFenced(m FencedMutex, ttl time.Duration, keyFn func(Req) string) *Builder[Req, Res]

ExclusiveFenced runs an action under an ownership-checked lease. The lease is renewed while the action is running; loss of the lease cancels the action context and returns an unavailable error rather than claiming success.

func (*Builder[Req, Res]) Hook

func (b *Builder[Req, Res]) Hook(h ...Hook[Req, Res]) *Builder[Req, Res]

Hook registers a full typed hook struct. Use when you need more than one hook event in a single declaration.

func (*Builder[Req, Res]) HookAfter

func (b *Builder[Req, Res]) HookAfter(fn func(ctx context.Context, req Req, res Res, err error, meta *Meta)) *Builder[Req, Res]

HookAfter runs fn after execution regardless of success or failure. Use for audit logging, always-on cleanup, unconditional metrics.

func (*Builder[Req, Res]) HookBefore

func (b *Builder[Req, Res]) HookBefore(fn func(ctx context.Context, req Req, meta *Meta) (context.Context, error)) *Builder[Req, Res]

HookBefore runs fn before the handler. Can enrich ctx or abort with an error. Aborting returns the error immediately — the handler never runs.

func (*Builder[Req, Res]) HookCacheHit

func (b *Builder[Req, Res]) HookCacheHit(fn func(ctx context.Context, req Req, res Res, meta *Meta)) *Builder[Req, Res]

HookCacheHit runs fn when the CacheMiddleware serves a response from cache. The handler is NOT called in this case. Use for cache-hit metrics, hit-rate logging.

func (*Builder[Req, Res]) HookCacheMiss

func (b *Builder[Req, Res]) HookCacheMiss(fn func(ctx context.Context, req Req, meta *Meta)) *Builder[Req, Res]

HookCacheMiss runs fn when the CacheMiddleware finds no cached result. The handler will be called immediately after. Use for cache-miss metrics, warming triggers.

func (*Builder[Req, Res]) HookCancel

func (b *Builder[Req, Res]) HookCancel(fn func(ctx context.Context, req Req, meta *Meta)) *Builder[Req, Res]

HookCancel runs fn when the request context is canceled (client disconnect, upstream timeout, explicit cancel). Use for cleanup, releasing reserved resources, cancellation metrics.

func (*Builder[Req, Res]) HookError

func (b *Builder[Req, Res]) HookError(fn func(ctx context.Context, req Req, err error, meta *Meta)) *Builder[Req, Res]

HookError runs fn only when the handler returns a non-nil error. Use for alerting, dead-letter queues, structured error logging.

func (*Builder[Req, Res]) HookExecuted

func (b *Builder[Req, Res]) HookExecuted(fn func(ctx context.Context, req Req, res Res, meta *Meta)) *Builder[Req, Res]

HookExecuted runs fn only on successful execution (err == nil). The fn signature omits err for convenience — it is always nil here. Use for domain-event publishing, analytics, cache warming.

func (*Builder[Req, Res]) HookRegister

func (b *Builder[Req, Res]) HookRegister(fn func(meta *Meta)) *Builder[Req, Res]

HookRegister runs fn once at Build() time — never on the hot path. Use to pre-allocate metric labels, register with service discovery, validate configuration, or build static routing tables.

Example — pre-allocate Prometheus labels:

.HookRegister(func(meta *action.Meta) {
    requestCounter.WithLabelValues(meta.Name) // pre-allocate label set
})

func (*Builder[Req, Res]) HookRetry

func (b *Builder[Req, Res]) HookRetry(fn func(ctx context.Context, req Req, attempt int, err error, meta *Meta)) *Builder[Req, Res]

HookRetry runs fn before each retry attempt made by the Retry middleware. attempt starts at 1 for the first retry. Use for retry-specific logging, jitter metrics, backoff tracing.

func (*Builder[Req, Res]) Idempotent

func (b *Builder[Req, Res]) Idempotent() *Builder[Req, Res]

func (*Builder[Req, Res]) IdempotentWithConfig

func (b *Builder[Req, Res]) IdempotentWithConfig(cfg IdempotencyConfig) *Builder[Req, Res]

func (*Builder[Req, Res]) ImmutableWhen

func (b *Builder[Req, Res]) ImmutableWhen(guard func(ctx context.Context, req Req) (bool, error), reason string) *Builder[Req, Res]

ImmutableWhen aborts execution with 403 Forbidden if the guard condition returns true. Useful for locking entities in terminal or processed states (e.g. settled invoices).

func (*Builder[Req, Res]) InferredResilient

func (b *Builder[Req, Res]) InferredResilient() *Builder[Req, Res]

InferredResilient applies the smart defaults to a Builder

func (*Builder[Req, Res]) Instrument

func (b *Builder[Req, Res]) Instrument(
	incCall func(actionName string),
	incError func(actionName string),
	recordLatency func(actionName string, ms float64),
) *Builder[Req, Res]

Instrument wires a minimal set of Prometheus-style counters via AnyHook. Counts are tracked by calling the provided inc functions — no Prometheus import required, works with any counter abstraction.

Example with Prometheus:

calls   := prometheus.NewCounterVec(...)
errors  := prometheus.NewCounterVec(...)
latency := prometheus.NewHistogramVec(...)

act := action.New("order.create", handler).
    Instrument(
        func(name string) { calls.WithLabelValues(name).Inc() },
        func(name string) { errors.WithLabelValues(name).Inc() },
        func(name string, ms float64) { latency.WithLabelValues(name).Observe(ms / 1000) },
    ).Build()

func (*Builder[Req, Res]) Internal

func (b *Builder[Req, Res]) Internal() *Builder[Req, Res]

Internal exposes this action only through an explicitly requested trusted service-to-service or runner contract.

func (*Builder[Req, Res]) LeaderOnly

func (b *Builder[Req, Res]) LeaderOnly(m Mutex, ttl time.Duration) *Builder[Req, Res]

LeaderOnly restricts action execution to a single instance using a global leader key.

func (*Builder[Req, Res]) LeaderOnlyFenced

func (b *Builder[Req, Res]) LeaderOnlyFenced(m FencedMutex, ttl time.Duration) *Builder[Req, Res]

LeaderOnlyFenced is the safe singleton-action form. It provides a renewable ownership lease, not merely a best-effort process-local convention.

func (*Builder[Req, Res]) LogCalls

func (b *Builder[Req, Res]) LogCalls(log *slog.Logger) *Builder[Req, Res]

LogCalls injects a structured log entry for every call using the provided logger. Logs before the call (level=Debug) and after (level=Info on success, Error on failure).

This is a lightweight alternative to telemetry.New() for simple deployments.

debugAct := action.New("order.create", handler).
    LogCalls(slog.Default()).
    Build()

func (*Builder[Req, Res]) LogSlowWhen

func (b *Builder[Req, Res]) LogSlowWhen(d time.Duration) *Builder[Req, Res]

func (*Builder[Req, Res]) Name

func (b *Builder[Req, Res]) Name(name string) *Builder[Req, Res]

func (*Builder[Req, Res]) Node

func (b *Builder[Req, Res]) Node(nodeName string) *Builder[Req, Res]

func (*Builder[Req, Res]) Once

func (b *Builder[Req, Res]) Once() *Builder[Req, Res]

Once caches the result of the first successful execution and returns it for all subsequent calls. Errors are also cached – the action will not retry. Use only for idempotent, read‑only actions (e.g., static config generation).

The first caller determines the cached result; later callers receive the same value or error without re‑executing the handler. Context cancellation is ignored after the first execution.

Example:

action.New("config.generate", generator).
    Once().
    Route(thttp.GET("/config.json")).
    Build()

func (*Builder[Req, Res]) Public

func (b *Builder[Req, Res]) Public() *Builder[Req, Res]

Public exposes this action through the browser/client business contract. It does not weaken authentication or authorization requirements.

func (*Builder[Req, Res]) RateLimit

func (b *Builder[Req, Res]) RateLimit(requestsPerSecond float64, burst int) *Builder[Req, Res]

func (*Builder[Req, Res]) RateLimitDistributed

func (b *Builder[Req, Res]) RateLimitDistributed(limiter RateLimiter, keyFn func(context.Context) string) *Builder[Req, Res]

func (*Builder[Req, Res]) RateLimitWithKey

func (b *Builder[Req, Res]) RateLimitWithKey(rps float64, burst int, keyFn func(context.Context) string) *Builder[Req, Res]

func (*Builder[Req, Res]) RecordHistory

func (b *Builder[Req, Res]) RecordHistory(capacity int) *Builder[Req, Res]

WithHistory attaches a ring-buffer execution history to the action AND returns the *History[Req, Res] handle for inspection. Replaces the two-return-value .WithHistory pattern with a single method that stores the handle on the builder for later retrieval.

The built action keeps the last cap records (newest-first via Snapshot).

Usage:

paymentAct := action.New("payment.charge", chargeCard).
    RecordHistory(200).
    Build()

hist := paymentAct.History() // nil if RecordHistory was not called

func (*Builder[Req, Res]) RequireAnyFeature

func (b *Builder[Req, Res]) RequireAnyFeature(keys ...string) *Builder[Req, Res]

RequireAnyFeature aborts with 403 Forbidden if NONE of the listed feature flags are enabled in ctx (logical OR — at least one must be on).

betaOrPremiumAct := action.New("widget.beta", handler).
    RequireAnyFeature("beta-access", "premium-plan").
    Build()

func (*Builder[Req, Res]) RequireAnyRole

func (b *Builder[Req, Res]) RequireAnyRole(roles ...string) *Builder[Req, Res]

RequireAnyRole aborts with 403 Forbidden if ctx contains none of the given roles. First matching role passes — order does not matter.

approveRefundAct := action.New("refund.approve", handler).
    RequireAnyRole("admin", "finance", "support").
    Build()

func (*Builder[Req, Res]) RequireAuth

func (b *Builder[Req, Res]) RequireAuth() *Builder[Req, Res]

RequireAuth aborts with 401 Unauthorized if ctx has no authenticated user (UserID is empty). Use when an endpoint requires login but any role is fine.

myProfileAct := action.New("user.me", handler).
    RequireAuth().
    Build()

func (*Builder[Req, Res]) RequireCreationLimit

func (b *Builder[Req, Res]) RequireCreationLimit(resourceName string, checkFn QuotaCheckFunc) *Builder[Req, Res]

RequireCreationLimit enforces plan quotas ONLY during new entity creation (ID == 0). Updates to existing resources bypass this check.

func (*Builder[Req, Res]) RequireFeature

func (b *Builder[Req, Res]) RequireFeature(keys ...string) *Builder[Req, Res]

RequireFeature aborts with 403 Forbidden if ALL listed feature flags are not enabled in ctx (logical AND — every flag must be on). Feature flags are injected from the "features" JWT claim.

Single flag:

aiCheckoutAct := action.New("checkout.ai", handler).
    RequireFeature("ai-checkout").
    Build()

Multiple flags (all must be enabled):

premiumExportAct := action.New("export.premium", handler).
    RequireFeature("premium-plan", "data-export").
    Build()

func (*Builder[Req, Res]) RequirePermission

func (b *Builder[Req, Res]) RequirePermission(perm string) *Builder[Req, Res]

RequirePermission aborts with 403 Forbidden if ctx does not contain the given fine-grained permission string. Permissions are injected from the "perms" JWT claim.

exportAct := action.New("data.export", handler).
    RequirePermission("data:export").
    Build()

func (*Builder[Req, Res]) RequireRole

func (b *Builder[Req, Res]) RequireRole(role string) *Builder[Req, Res]

RequireRole aborts with 403 Forbidden if ctx does not contain the given role. Roles are injected by the JWT middleware from the "roles" claim.

deleteOrderAct := action.New("order.delete", handler).
    RequireRole("admin").
    Route(thttp.DELETE("/api/v1/orders/{id}")).
    Build()

func (*Builder[Req, Res]) RequireTenant

func (b *Builder[Req, Res]) RequireTenant() *Builder[Req, Res]

RequireTenant aborts with 401 Unauthorized if ctx has no tenant ID. Use to guard multi-tenant endpoints from unauthenticated callers.

tenantOrderAct := action.New("order.list", handler).
    RequireTenant().
    Build()

func (*Builder[Req, Res]) Resilient

func (b *Builder[Req, Res]) Resilient(cfg ResilienceConfig) *Builder[Req, Res]

func (*Builder[Req, Res]) Retry

func (b *Builder[Req, Res]) Retry(
	maxRetry int,
	backoff func(attempt int) time.Duration,
) *Builder[Req, Res]

Retry retries only transient errors detected by xerr.IsTransient.

func (*Builder[Req, Res]) RetryAll added in v0.3.0

func (b *Builder[Req, Res]) RetryAll(
	maxRetry int,
	backoff func(attempt int) time.Duration,
) *Builder[Req, Res]

RetryAll retries on ANY non-nil error. Use only for idempotent jobs, scripts, and safe batch operations.

func (*Builder[Req, Res]) RetryIf added in v0.3.0

func (b *Builder[Req, Res]) RetryIf(
	maxRetry int,
	backoff func(attempt int) time.Duration,
	predicate RetryPredicate,
) *Builder[Req, Res]

RetryIf retries when the supplied predicate returns true.

func (*Builder[Req, Res]) Route

func (b *Builder[Req, Res]) Route(bs ...Binding) *Builder[Req, Res]

func (*Builder[Req, Res]) SuccessStatus

func (b *Builder[Req, Res]) SuccessStatus(code int) *Builder[Req, Res]

func (*Builder[Req, Res]) System

func (b *Builder[Req, Res]) System() *Builder[Req, Res]

System marks a framework or operations-plane action. It is excluded from public and trusted business contracts.

func (*Builder[Req, Res]) Tag

func (b *Builder[Req, Res]) Tag(tags ...string) *Builder[Req, Res]

func (*Builder[Req, Res]) Timeout

func (b *Builder[Req, Res]) Timeout(d time.Duration) *Builder[Req, Res]

func (*Builder[Req, Res]) TrackPIIAccess

func (b *Builder[Req, Res]) TrackPIIAccess(tracker PIITracker, purpose string) *Builder[Req, Res]

TrackPIIAccess attaches a non-blocking hook recording the purpose of PII data access.

func (*Builder[Req, Res]) Transactional

func (b *Builder[Req, Res]) Transactional(runner TxRunner) *Builder[Req, Res]

func (*Builder[Req, Res]) Use

func (b *Builder[Req, Res]) Use(m Middleware[Req, Res]) *Builder[Req, Res]

func (*Builder[Req, Res]) UseWithDispatcher

func (b *Builder[Req, Res]) UseWithDispatcher(m DispatcherMiddleware[Req, Res]) *Builder[Req, Res]

func (*Builder[Req, Res]) Validate

func (b *Builder[Req, Res]) Validate(fn func(ctx context.Context, req Req) error) *Builder[Req, Res]

Validate adds a request validation middleware to the action construction pipeline.

func (*Builder[Req, Res]) WithHistory

func (b *Builder[Req, Res]) WithHistory(capacity int) (*Builder[Req, Res], *History[Req, Res])

func (*Builder[Req, Res]) WithProfile

func (b *Builder[Req, Res]) WithProfile(profile Profile) *Builder[Req, Res]

WithProfile applies a policy bundle before optional action-specific overrides. The returned builder remains fully fluent, so exceptional actions can explicitly refine timeout, route, or idempotency configuration.

type BuiltAction

type BuiltAction[Req, Res any] struct {
	// contains filtered or unexported fields
}

func (*BuiltAction[Req, Res]) AddAnyHook

func (a *BuiltAction[Req, Res]) AddAnyHook(h ...AnyHook)

func (*BuiltAction[Req, Res]) Describe

func (a *BuiltAction[Req, Res]) Describe() *Meta

func (*BuiltAction[Req, Res]) Do

func (a *BuiltAction[Req, Res]) Do(ctx context.Context, req Req) (res Res, err error)

func (*BuiltAction[Req, Res]) ExecuteDecoded

func (a *BuiltAction[Req, Res]) ExecuteDecoded(ctx context.Context, decode DecodeFunc) (any, error)

func (*BuiltAction[Req, Res]) GetAnyHooks

func (a *BuiltAction[Req, Res]) GetAnyHooks() []AnyHook

func (*BuiltAction[Req, Res]) GetBindings

func (a *BuiltAction[Req, Res]) GetBindings() []Binding

func (*BuiltAction[Req, Res]) GetMeta

func (a *BuiltAction[Req, Res]) GetMeta() *Meta

func (*BuiltAction[Req, Res]) History

func (a *BuiltAction[Req, Res]) History() *History[Req, Res]

func (*BuiltAction[Req, Res]) OnCacheHit

func (a *BuiltAction[Req, Res]) OnCacheHit(ctx context.Context, req Req, res Res)

func (*BuiltAction[Req, Res]) OnCacheMiss

func (a *BuiltAction[Req, Res]) OnCacheMiss(ctx context.Context, req Req)

func (*BuiltAction[Req, Res]) OnCoalesced

func (a *BuiltAction[Req, Res]) OnCoalesced(ctx context.Context, req Req)

func (*BuiltAction[Req, Res]) OnDeduplicated

func (a *BuiltAction[Req, Res]) OnDeduplicated(ctx context.Context, req Req)

func (*BuiltAction[Req, Res]) OnRetry

func (a *BuiltAction[Req, Res]) OnRetry(ctx context.Context, req Req, attempt int, err error)

func (*BuiltAction[Req, Res]) ReqPayload

func (a *BuiltAction[Req, Res]) ReqPayload() any

func (*BuiltAction[Req, Res]) ResPayload

func (a *BuiltAction[Req, Res]) ResPayload() any

func (*BuiltAction[Req, Res]) String

func (a *BuiltAction[Req, Res]) String() string

type BuiltSaga

type BuiltSaga[Req, Res any] struct {
	// contains filtered or unexported fields
}

BuiltSaga is the immutable, executable Saga.

func (*BuiltSaga[Req, Res]) Do

func (s *BuiltSaga[Req, Res]) Do(ctx context.Context, req Req) (SagaResult[Res], error)

Do executes the Saga. If a mandatory step fails, it automatically runs Undo functions in reverse order.

type CacheConfig

type CacheConfig[Req, Res any] struct {
	KeyFunc func(Req) string
	Layers  []CacheLayer[Res]
	TTL     time.Duration
	Timeout time.Duration
}

type CacheLayer

type CacheLayer[V any] interface {
	Get(ctx context.Context, key string) (val V, hit bool, err error)
	Set(ctx context.Context, key string, val V, ttl time.Duration) error
}

type Coalescer

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

func NewCoalescer

func NewCoalescer() *Coalescer

func (*Coalescer) Do

func (c *Coalescer) Do(ctx context.Context, key string, fn func(context.Context) (any, error)) (val any, shared bool, err error)

type DecodeFunc

type DecodeFunc func(v any) error

DecodeFunc allows transports to inject data directly into the concrete type.

type Describable

type Describable interface {
	Describe() *Meta
}

Describable handles the COLD path — boot, discovery, routing, and CLI help.

type DispatcherMiddleware

type DispatcherMiddleware[Req, Res any] func(next Fn[Req, Res], hooks HookDispatcher[Req, Res]) Fn[Req, Res]

func AdmissionMiddleware

func AdmissionMiddleware[Req, Res any](admission Admission) DispatcherMiddleware[Req, Res]

AdmissionMiddleware applies an external admission policy around execution. It keeps the policy outside Builder while allowing typed middleware composition and automatic request/response inference.

func CacheMiddleware

func CacheMiddleware[Req, Res any](cfg CacheConfig[Req, Res]) DispatcherMiddleware[Req, Res]

CacheMiddleware implements the Read-Through / Write-Behind pattern with isolated singleflight execution.

func CoalesceMiddleware

func CoalesceMiddleware[Req, Res any](coalescer *Coalescer, actionName string, keyFn func(Req) string) DispatcherMiddleware[Req, Res]

func Deduplicate

func Deduplicate[Req, Res any](keyFn func(Req) string) DispatcherMiddleware[Req, Res]

func RetryMiddleware

func RetryMiddleware[Req, Res any](
	maxRetry int,
	backoff func(attempt int) time.Duration,
) DispatcherMiddleware[Req, Res]

RetryMiddleware is the backward-compatible, safe default retry middleware. It retries only transient errors detected by xerr.IsTransient.

func RetryWithPredicateMiddleware added in v0.3.0

func RetryWithPredicateMiddleware[Req, Res any](
	maxRetry int,
	backoff func(attempt int) time.Duration,
	predicate RetryPredicate,
) DispatcherMiddleware[Req, Res]

RetryWithPredicateMiddleware retries errors when predicate returns true. If predicate is nil, it falls back to DefaultRetryPredicate. If backoff is nil, it defaults to ConstantBackoff(0). If maxRetry is negative, it is clamped to 0.

type Executable

type Executable interface {
	ExecuteDecoded(ctx context.Context, decode DecodeFunc) (any, error)
}

Executable handles the HOT path — execution only.

type FencedMutex

type FencedMutex interface {
	Acquire(ctx context.Context, key string, ttl time.Duration) (lease LockLease, acquired bool, err error)
	Renew(ctx context.Context, lease LockLease, ttl time.Duration) (renewed bool, err error)
	Release(ctx context.Context, lease LockLease) (released bool, err error)
}

FencedMutex is the production-safe distributed coordination contract. A lease belongs to exactly one owner, can be renewed only by that owner, and can be released only by that owner.

type Fn

type Fn[Req, Res any] func(context.Context, Req) (Res, error)

type History

type History[Req, Res any] struct {
	// contains filtered or unexported fields
}

func NewHistory

func NewHistory[Req, Res any](capacity int) *History[Req, Res]

func (*History[Req, Res]) Push

func (h *History[Req, Res]) Push(rec Record[Req, Res])

func (*History[Req, Res]) Snapshot

func (h *History[Req, Res]) Snapshot() []Record[Req, Res]

type Hook

type Hook[Req, Res any] struct {
	OnRegister     func(meta *Meta)
	Before         func(ctx context.Context, req Req, meta *Meta) (context.Context, error)
	After          func(ctx context.Context, req Req, res Res, err error, meta *Meta)
	OnError        func(ctx context.Context, req Req, err error, meta *Meta)
	OnRetry        func(ctx context.Context, req Req, attempt int, err error, meta *Meta)
	OnCacheHit     func(ctx context.Context, req Req, res Res, meta *Meta)
	OnCacheMiss    func(ctx context.Context, req Req, meta *Meta)
	OnCoalesced    func(ctx context.Context, req Req, meta *Meta)
	OnDeduplicated func(ctx context.Context, req Req, meta *Meta)
	OnCancel       func(ctx context.Context, req Req, meta *Meta)
	OnExecuted     func(ctx context.Context, req Req, res Res, err error, meta *Meta)
}

type HookDispatcher

type HookDispatcher[Req, Res any] interface {
	OnCacheHit(ctx context.Context, req Req, res Res)
	OnCacheMiss(ctx context.Context, req Req)
	OnRetry(ctx context.Context, req Req, attempt int, err error)
	OnCoalesced(ctx context.Context, req Req)
	OnDeduplicated(ctx context.Context, req Req)
}

type HookProvider

type HookProvider interface {
	GetAnyHooks() []AnyHook
}

type IdempotencyClaim

type IdempotencyClaim struct {
	State IdempotencyClaimState
	Token string
	Entry IdempotencyEntry
}

IdempotencyClaim is returned by an atomic coordinator. Token is populated only for an acquired claim and must be presented to Complete or Release.

type IdempotencyClaimState

type IdempotencyClaimState uint8

IdempotencyClaimState describes one atomic attempt to own an idempotency key. It is deliberately business-neutral: transports decide whether an in-progress request should be retried, polled, or reported to a caller.

const (
	IdempotencyClaimAcquired IdempotencyClaimState = iota + 1
	IdempotencyClaimCompleted
	IdempotencyClaimInProgress
	IdempotencyClaimConflict
)

type IdempotencyConfig

type IdempotencyConfig struct {
	// Enabled activates idempotency for this action.
	Enabled bool

	// TTL overrides the store's default TTL for entries of this action.
	// 0 = use store default (24 h).
	TTL time.Duration

	// KeyHeader is the HTTP header to read the key from.
	// Defaults to "Idempotency-Key".
	KeyHeader string

	// KeyFunc derives the idempotency key from the raw request body bytes.
	// Useful when the key lives inside JSON rather than a header.
	// When nil, only KeyHeader is used.
	KeyFunc func(body []byte) string

	// LeaseTTL bounds one in-progress owner when a store provides atomic
	// coordination. Zero uses DefaultIdempotencyLeaseTTL. It must exceed the
	// action's worst-case execution time; it is not the completed replay TTL.
	LeaseTTL time.Duration
}

IdempotencyConfig controls per-action idempotency behavior. Zero value = disabled. Attach via .Idempotent() or .IdempotentWithConfig().

func (IdempotencyConfig) EffectiveLeaseTTL

func (c IdempotencyConfig) EffectiveLeaseTTL() time.Duration

EffectiveLeaseTTL returns the configured lease or the documented default.

func (IdempotencyConfig) Header

func (c IdempotencyConfig) Header() string

Header returns the effective header name (never empty).

type IdempotencyCoordinator

type IdempotencyCoordinator interface {
	IdempotencyStore
	Claim(ctx context.Context, key, requestHash string, leaseTTL time.Duration) (IdempotencyClaim, error)
	Complete(ctx context.Context, key, token string, entry IdempotencyEntry, ttl time.Duration) error
	Release(ctx context.Context, key, token string) error
}

IdempotencyCoordinator is an optional stronger capability implemented by a durable store. Claim must atomically create an in-progress owner or return the already stored state. Complete and Release must affect only a claim held by the supplied opaque token.

This protects duplicate execution while a valid lease is held. It does not make an arbitrary external side effect globally exactly-once; use a transactional business write/outbox where that guarantee is required.

type IdempotencyEntry

type IdempotencyEntry struct {
	Status      int
	Body        []byte
	Headers     map[string]string // safe headers only: Content-Type, X-Request-ID
	StoredAt    time.Time
	RequestHash string
}

IdempotencyEntry is the captured response for a completed idempotent request.

type IdempotencyStore

type IdempotencyStore interface {
	Get(ctx context.Context, key string) (IdempotencyEntry, bool)
	Set(ctx context.Context, key string, entry IdempotencyEntry, ttl time.Duration)
}

IdempotencyStore persists and retrieves idempotency entries. Implement this interface backed by Redis for multi-node deployments. The default MemoryIdempotencyStore is suitable for single-node / dev.

type LoadShedConfig

type LoadShedConfig struct {
	MaxCPU        float64
	MaxGoroutines int
}

type LockLease

type LockLease struct {
	Key   string
	Owner string
	Fence int64
}

LockLease proves ownership of a distributed lock. Fence is monotonically increasing for a key and must be carried to any downstream system that can reject stale writers.

func LeaseFromContext added in v0.2.0

func LeaseFromContext(ctx context.Context) (LockLease, bool)

LeaseFromContext retrieves the active LockLease from the execution context.

type MemoryIdempotencyStore

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

MemoryIdempotencyStore provides an in-memory implementation of IdempotencyCoordinator with background TTL eviction and atomic lease claims.

func NewMemoryIdempotencyStore

func NewMemoryIdempotencyStore(defTTL time.Duration) *MemoryIdempotencyStore

NewMemoryIdempotencyStore creates a store with background TTL eviction. defTTL 0 → 24 h.

func (*MemoryIdempotencyStore) Claim added in v0.2.0

func (s *MemoryIdempotencyStore) Claim(ctx context.Context, key, requestHash string, leaseTTL time.Duration) (IdempotencyClaim, error)

func (*MemoryIdempotencyStore) Close

func (s *MemoryIdempotencyStore) Close()

Close stops the eviction goroutine. Safe to call multiple times.

func (*MemoryIdempotencyStore) Complete added in v0.2.0

func (s *MemoryIdempotencyStore) Complete(_ context.Context, key, token string, entry IdempotencyEntry, ttl time.Duration) error

func (*MemoryIdempotencyStore) Get

func (*MemoryIdempotencyStore) Release added in v0.2.0

func (s *MemoryIdempotencyStore) Release(_ context.Context, key, token string) error

func (*MemoryIdempotencyStore) Set

type MessageRes

type MessageRes struct {
	Message string `json:"message"`
}

MessageRes is a standard DTO for actions that only need to return a text message. Using a strongly-typed struct instead of map[string]string ensures precise SDK generation and clean OpenAPI documentation.

type Meta

type Meta struct {
	Name             string            `json:"name"`
	Description      string            `json:"description,omitempty"`
	Node             string            `json:"node,omitempty"`
	Tags             []string          `json:"tags,omitempty"`
	Scope            ActionScope       `json:"scope,omitempty"`
	Idempotency      IdempotencyConfig `json:"idempotency"`
	SuccessStatus    int               `json:"success_status,omitempty"`
	LogSlowThreshold time.Duration     `json:"log_slow_threshold,omitempty"`

	RequiredRoles       []string `json:"required_roles,omitempty"`
	RequiredPermissions []string `json:"required_permissions,omitempty"`
	RequiredFeatures    []string `json:"required_features,omitempty"`
	RequiresAuth        bool     `json:"requires_auth,omitempty"`

	RetryMax         int           `json:"retry_max,omitempty"`
	Timeout          time.Duration `json:"timeout,omitempty"`
	ConcurrencyLimit int32         `json:"concurrency_limit,omitempty"`
	RateLimit        string        `json:"rate_limit,omitempty"`
	CacheTTL         time.Duration `json:"cache_ttl,omitempty"`
	Deduplicated     bool          `json:"deduplicated,omitempty"`
	Coalesced        bool          `json:"coalesced,omitempty"`
}

func (*Meta) IsInternal

func (m *Meta) IsInternal() bool

IsInternal reports whether the action belongs only to trusted callers.

func (*Meta) IsPublic

func (m *Meta) IsPublic() bool

IsPublic reports whether the action is part of the public business contract. The zero value is public for concise ordinary client actions.

func (*Meta) IsSystem

func (m *Meta) IsSystem() bool

IsSystem reports whether the action belongs to the framework/operations plane.

func (Meta) String

func (m Meta) String() string

type Middleware

type Middleware[Req, Res any] func(Fn[Req, Res]) Fn[Req, Res]

func Adaptive

func Adaptive[Req, Res any](name string, cfg AdaptiveConfig) Middleware[Req, Res]

func AdaptiveLoadShedding

func AdaptiveLoadShedding[Req, Res any](stats SystemStats, cfg LoadShedConfig, p Priority) Middleware[Req, Res]

AdaptiveLoadShedding drops traffic instantly if the server is physically choking.

func ConcurrencyLimitMiddleware

func ConcurrencyLimitMiddleware[Req, Res any](limit int32) Middleware[Req, Res]

func HistoryMiddleware

func HistoryMiddleware[Req, Res any](hist *History[Req, Res]) Middleware[Req, Res]

func SlowLogMiddleware added in v0.2.0

func SlowLogMiddleware[Req, Res any](threshold time.Duration, actionName string) Middleware[Req, Res]

func SmartResilience

func SmartResilience[Req, Res any](name string) Middleware[Req, Res]

SmartResilience automatically applies intelligent backoff and circuit breaking based entirely on the xerr.Kind of the returned error. It embodies the "Inferred Resilience" pattern.

func TimeoutMiddleware

func TimeoutMiddleware[Req, Res any](d time.Duration) Middleware[Req, Res]

TimeoutMiddleware uses standard Middleware.

type Mutex

type Mutex interface {
	TryLock(ctx context.Context, key string, ttl time.Duration) (bool, error)
	Unlock(ctx context.Context, key string) error
}

Mutex defines a standard distributed lock contract for critical sections. For systems requiring protection against stale writers during process pauses, prefer FencedMutex via ExclusiveFenced.

type PIITracker

type PIITracker interface {
	TrackPIIAccess(ctx context.Context, purpose string, actionName string)
}

PIITracker defines the minimal contract for personal data access tracking.

type Plugin

type Plugin interface {
	HookProvider
	ActionProvider
}

type Priority

type Priority uint8
const (
	PriorityCritical Priority = 0 // Payments, Logins
	PriorityNormal   Priority = 1 // Standard CRUD
	PriorityLow      Priority = 2 // Background syncs, Exports
)

type Profile

type Profile struct {
	Tags                []string
	Scope               ActionScope
	Timeout             time.Duration
	ConcurrencyLimit    int32
	SuccessStatus       int
	RequireAuth         bool
	RequiredPermissions []string
	Idempotency         *IdempotencyConfig
}

Profile is a named bundle of action metadata and middleware defaults. It deliberately excludes a route and description: those are action-specific contract details and should remain visible at registration sites.

func AuthenticatedReadProfile

func AuthenticatedReadProfile(permission string, tags ...string) Profile

AuthenticatedReadProfile is the conservative default for an authenticated, read-only query. It intentionally does not enable idempotency because no side effect exists to replay.

func IdempotentCommandProfile

func IdempotentCommandProfile(permission string, tags ...string) Profile

IdempotentCommandProfile is the default for a mutation that can safely replay the same request key. Business code remains responsible for durable transactions and external-side-effect coordination.

func InternalEventProfile

func InternalEventProfile(tags ...string) Profile

InternalEventProfile is for service-to-service ingestion routes. It requires an authenticated transport identity and idempotency, but makes no claim about the application-specific identity verifier installed by the service.

type QuotaCheckFunc

type QuotaCheckFunc func(ctx context.Context, tenantID string) (current int64, limit int64, err error)

type RateLimiter

type RateLimiter interface {
	Allow(ctx context.Context, key string) (bool, error)
}

type Record

type Record[Req, Res any] struct {
	Time     time.Time
	Duration time.Duration
	Req      Req
	Res      Res
	Err      error
}

type ResilienceConfig

type ResilienceConfig struct {
	MaxRetries    int
	Backoff       func(attempt int) time.Duration
	Predicate     RetryPredicate
	Timeout       time.Duration
	MaxConcurrent int32
	Adaptive      *AdaptiveConfig
}

type RetryPredicate added in v0.3.0

type RetryPredicate func(err error) bool

RetryPredicate determines whether a given error warrants an execution retry.

type SagaBuilder

type SagaBuilder[Req, Res any] struct {
	// contains filtered or unexported fields
}

SagaBuilder constructs a distributed transaction pipeline.

func NewSaga

func NewSaga[Req, Res any](name string) *SagaBuilder[Req, Res]

NewSaga initializes an in-memory Saga pipeline.

func (*SagaBuilder[Req, Res]) AddOptionalStep

func (s *SagaBuilder[Req, Res]) AddOptionalStep(
	name string,
	do func(context.Context, Req) (Res, error),
	undo func(context.Context, Req) error,
) *SagaBuilder[Req, Res]

AddOptionalStep appends a step that skips on failure without triggering a Saga rollback.

func (*SagaBuilder[Req, Res]) AddStep

func (s *SagaBuilder[Req, Res]) AddStep(
	name string,
	do func(context.Context, Req) (Res, error),
	undo func(context.Context, Req) error,
) *SagaBuilder[Req, Res]

AddStep appends a mandatory (Do, Undo) pair.

func (*SagaBuilder[Req, Res]) Build

func (s *SagaBuilder[Req, Res]) Build() *BuiltSaga[Req, Res]

Build compiles the Saga.

type SagaResult

type SagaResult[Res any] struct {
	Saga       string       `json:"saga"`
	Success    bool         `json:"success"`
	Output     Res          `json:"output,omitempty"`
	Steps      []StepResult `json:"steps"`
	Error      string       `json:"error,omitempty"`
	RolledBack bool         `json:"rolled_back,omitempty"`
	DurationMs int64        `json:"duration_ms"`
}

SagaResult captures the full execution audit and output of the Saga.

type SagaStep

type SagaStep[Req, Res any] struct {
	Name     string
	Do       func(context.Context, Req) (Res, error)
	Undo     func(context.Context, Req) error
	Optional bool
}

SagaStep represents a single operation and its compensating rollback.

type StateEntity

type StateEntity interface {
	GetState() string
	SetState(string)
}

StateEntity defines an interface for structs that possess a lifecycle state.

type StateMachineBuilder

type StateMachineBuilder[Req StateEntity, Res any] struct {
	// contains filtered or unexported fields
}

StateMachineBuilder provides a fluent DSL for building state-guarded actions.

func NewStateMachine

func NewStateMachine[Req StateEntity, Res any](name string, exec Fn[Req, Res]) *StateMachineBuilder[Req, Res]

NewStateMachine creates an action that strictly guards state transitions.

func (*StateMachineBuilder[Req, Res]) Allow

func (sm *StateMachineBuilder[Req, Res]) Allow(from string, to ...string) *StateMachineBuilder[Req, Res]

Allow maps a valid state transition. Example: .Allow("pending", "paid", "canceled")

func (*StateMachineBuilder[Req, Res]) Build

func (sm *StateMachineBuilder[Req, Res]) Build() *BuiltAction[Req, Res]

Build compiles the State Machine into a standard Nexss Builder, injecting the validation middleware automatically.

type StepResult

type StepResult struct {
	Step       string `json:"step"`
	Success    bool   `json:"success"`
	Skipped    bool   `json:"skipped,omitempty"`
	Error      string `json:"error,omitempty"`
	DurationMs int64  `json:"duration_ms"`
}

StepResult captures execution metadata for a single Saga step.

type StreamAction

type StreamAction[Req, T any] struct {
	// contains filtered or unexported fields
}

StreamAction wraps a streaming handler with the standard middleware chain.

func NewStream

func NewStream[Req, T any](name string, h StreamHandler[Req, T]) *StreamAction[Req, T]

NewStream creates a StreamAction.

func (*StreamAction[Req, T]) Do

func (a *StreamAction[Req, T]) Do(ctx context.Context, req Req) (iter.Seq2[T, error], error)

Do returns the iterator.

func (*StreamAction[Req, T]) Use

func (a *StreamAction[Req, T]) Use(h ...Hook[Req, iter.Seq2[T, error]]) *StreamAction[Req, T]

Use appends middleware to the setup phase.

type StreamHandler

type StreamHandler[Req, T any] func(context.Context, Req) (iter.Seq2[T, error], error)

StreamHandler returns an iter.Seq2 that yields (item, error) pairs.

type SystemStats

type SystemStats interface {
	CPUPercent() float64
	Goroutines() int
}

SystemStats is implemented lock-free by nexss/monitor.

type Testable

type Testable[Req, Res any] struct {
	// contains filtered or unexported fields
}

Testable wraps a BuiltAction and provides helpers for unit testing.

func TestFrom

func TestFrom[Req, Res any](b *Builder[Req, Res]) *Testable[Req, Res]

func TestFromAction

func TestFromAction[Req, Res any](act *BuiltAction[Req, Res]) *Testable[Req, Res]

FromAction returns a Testable wrapper for a BuiltAction.

func (*Testable[Req, Res]) CaptureReq

func (t *Testable[Req, Res]) CaptureReq(ctx context.Context, input Req) (captured Req, res Res, err error)

CaptureReq executes the action and captures the request as seen by the base handler. The captured value is the one after all middleware have run, right before calling exec.

func (*Testable[Req, Res]) Do

func (t *Testable[Req, Res]) Do(ctx context.Context, req Req) (Res, error)

Do executes the full action (with all middleware/hooks).

func (*Testable[Req, Res]) DoRaw

func (t *Testable[Req, Res]) DoRaw(ctx context.Context, req Req) (Res, error)

DoRaw executes only the base handler — no validation, no cache, no middleware.

func (*Testable[Req, Res]) ExpectErr

func (t *Testable[Req, Res]) ExpectErr(ctx context.Context, req Req, expected string) error

ExpectErr checks if action returns expected error kind.

type TxRunner

type TxRunner interface {
	RunInTx(ctx context.Context, fn func(txCtx context.Context) error) error
}

type TypedPayload

type TypedPayload interface {
	ReqPayload() any
	ResPayload() any
}

TypedPayload allows plugins (like OpenAPI) to discover the underlying Request and Response types at boot time without storing them in metadata or using reflection during execution.

Jump to

Keyboard shortcuts

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