cf_valkey_state

package module
v0.0.11 Latest Latest
Warning

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

Go to latest
Published: Aug 19, 2026 License: Apache-2.0 Imports: 17 Imported by: 0

README

caerus-framework-valkey-state

CI codecov License

Caerus Framework Valkey State Component. Keyed state services — sessions (by id plus an optional per-user SET index), cache, and rate-limit counters — on top of caerus-framework-valkey. Apps do not import valkey-go or invent KEYS scans. Framework-owned lifecycle, configuration (file + env + flags), live tunable reload with last-good, logging through logs, observability health/metrics.

This is not valkey-queues (VPQ / jobs). Queues and state are siblings on the same fridge; neither owns the other. HTTP 429 / KeyFunc / fail-open stay on caerus-framework-http-ratelimiter. This module stores the count.

The component is a stateless consumer of a CFValkey peer: it holds the peer pointer (never a client snapshot) and uses live Client() and prefix-aware Key(). Reconnects stay on the valkey component.

Wiring

Two wiring shapes are supported. Prefer the app-owned shape (demoapp golden path): main declares only the chassis (valkey-state alongside postgres / valkey) and the app class; product machinery that uses sessions, cache, or rate limits lives under the app and resolves the component as a peer at Init. Use the simple main-level shape for one-off binaries.

App-owned consumer (golden — demoapp pattern)

main declares valkey-state as chassis and runs the app class; it never touches the component itself:

fw := cf.New(&cf.FrameworkOptions{
	Logs: &cf.LogsSettings{Format: "json", Level: "info", ConfigSource: "logs"},
	Observability: &cf.ObservabilitySettings{Bind: ":9090", ConfigSource: "observability"},
	Components: []cf.CaerusComponent{
		cf_postgres.New(cf_postgres.WithConfigSource("postgresql", "config/postgresql.json")),
		cf_valkey.New(cf_valkey.WithConfigSource("valkey", "config/valkey.json")),
		cf_valkey_state.New(cf_valkey_state.WithConfigSource("valkey-state", "config/valkey-state.json")),
		app.New(app.Options{}),
	},
})
if err := fw.RunWithSignals(context.Background()); err != nil {
	log.Fatal(err)
}

The app resolves the valkey-state component pointer once at Init (never a client snapshot), declares it in GetDependencies, and calls the accessors per use:

type App struct {
	state *cf_valkey_state.CFState
}

func (a *App) GetDependencies() []string {
	return []string{cf_valkey_state.ComponentName} // + logs, chassis peers
}

func (a *App) Init(ctx context.Context, fw *cf.CaerusFramework) error {
	st, ok := cf.Get[*cf_valkey_state.CFState](fw)
	if !ok {
		return errors.New("app: valkey-state component missing")
	}
	a.state = st
	return nil
}

Multiple valkey-state instances in one process use WithName + GetByName[*cf_valkey_state.CFState](fw, "sessions"); a state component bound to a named valkey peer uses WithValkeyName (its GetDependencies reports the peer's actual name, so framework Validate passes).

Simple main-level wiring

For a one-off binary, register the components directly and use cf.MustGet to reach the component:

fw := cf.New()

logs := cf_logs.New(cf_logs.WithWriter(os.Stdout))
valkey := cf_valkey.New(cf_valkey.WithConfigSource("valkey", "config/valkey.json"))
state := cf_valkey_state.New(cf_valkey_state.WithConfigSource("valkey-state", "config/valkey-state.json"))
fw.AddComponent(logs)
fw.AddComponent(valkey) // GetDependencies() -> [logs configuration]
fw.AddComponent(state)  // GetDependencies() -> [valkey logs configuration]

In both shapes the component is cf.ConfigSourceRegistrar-self-sufficient: WithConfigSource registers the Source[StateConfig] with the configuration component during argv absorption, so main never touches os.Getenv/ParseFlags. Prefer the same string for the config source name and the component Name() ("valkey-state") so GetDependencies and --valkey-state / VALKEY_STATE_ line up. You may still choose a shorter source name (e.g. "state") — then remember GetDependencies must use ComponentName ("valkey-state"), not the source nickname.

Usage

The app resolves the component once at Init and calls accessors per use. Keys go through the peer’s Key() (session:*, session-bind:*, session-user:*, cache:*, rl:*) plus valkey WithKeyPrefix.

CFState.Key(parts…) is the same prefix helper for other patterns (e.g. patterns.NewMutex). Rate-limit storage keys stay unexported (rlKey). Pass the logical key into Allow / Peek / Reset (login:+email), not prod:rl:login:….

Sessions

Opaque JSON at session:<id> with a TTL. The app chooses the id (opaque token). Optional user index (no Redis KEYS):

Call What it does
CreateSession(ctx, id, v, ttl) By id only. ListSessionsForUser will not see it.
CreateSession(..., WithSessionUser(userID)) Also SADD session-user:<user> and session-bind:<id> (user id for SREM; the JSON is not parsed).
ListSessionsForUser(ctx, userID) SMEMBERS + EXISTS; expired payloads are SREM’d (ghosts). O(that user’s sessions).
RevokeAllForUser(ctx, userID, keep…) Delete every indexed session except keep (usually the current id).
RevokeSession DEL payload + bind and SREM when indexed.
TouchSession Sliding TTL on payload; bind and user SET PTTL bumped when indexed.

Empty session/user id is an error. Valkey/JSON errors do not include the session or user id — log those in the app if you need them.

Do not use Allow / counters to list sessions. Do not KEYS. This module does not set cookies or CSRF tokens.

sess := map[string]any{"user_id": "u1", "roles": []string{"admin"}}
_ = st.CreateSession(ctx, "tok-abc", sess, 24*time.Hour, cf_valkey_state.WithSessionUser("u1"))
var got map[string]any
found, _ := st.GetSession(ctx, "tok-abc", &got) // found=false on miss/expiry
st.SessionExists(ctx, "tok-abc")
st.TouchSession(ctx, "tok-abc", 15*time.Minute)
ids, _ := st.ListSessionsForUser(ctx, "u1")
_ = st.RevokeAllForUser(ctx, "u1", "tok-abc") // revoke others, keep current
st.RevokeSession(ctx, "tok-abc")
Cache

JSON cache-aside. CacheGetOrLoad runs load at most once per key per in-flight wave in this process (singleflight). SET after load re-resolves Client(). Other pods still stampede on a cold key.

_ = st.CacheSet(ctx, someStruct, time.Minute, "catalog", "sku-1")
var cached someStruct
_ = st.CacheGet(ctx, &cached, "catalog", "sku-1") // miss: no-op, dst untouched

val, shared, _ := st.CacheGetOrLoad(ctx, time.Minute, func(ctx context.Context) ([]byte, error) {
	return json.Marshal(fetchFromDB(ctx))
}, "catalog", "sku-2")
Rate-limit counters (the store, not HTTP)

Fixed-window Lua (INCR + PEXPIRE on first count) or the nested rate_limit memory map. HTTP 429 / KeyFunc / fail-open live on http-ratelimiter, which calls this machine. Login lockout from an auth handler should go through that HTTP limiter, not a raw Allow here.

res, err := st.Allow(ctx, "login:"+email, 5, time.Minute)
if err != nil {
	// store failed; FailOpen / FailClosed is the HTTP module
}

Peek / Reset use the same logical key. There is no public RateLimitKey. Sliding window / token bucket are not shipped; this is fixed-window until a named product needs more Lua here.

Counter store settings (rate_limit)

State chooses Lua vs the sticky-note map from nested file settings. The app does not pass “use memory on this call.” Only counters get a map — sessions and cache always need a live Valkey client.

Setting Meaning
force_memory Always use the map (GH App / laptop).
use_memory_fallback Lua first; on nil client or Eval error, use the map.
memory_map_full_policy allow or deny when the map hits its cap (required when memory is on).
Health (/readyz)

State does not PING. Valkey’s Health already PINGs. Two shapes:

Wiring After Init, Health
Path AWithoutValkeyPeer + memory on (counters only) Ready. No fridge. Sessions/cache still error.
Path B — valkey declared (normal app) Ready only if Client() is non-nil. Soft-init / DegradedMode can finish Init with a nil client; /readyz stays red until the peer connects.

DegradedMode answers “may Initialize finish?” /readyz answers “send LB traffic?” Do not mix them.

GH App / laptop counters without Valkey

No session store in-process. Construct state without a valkey component; turn memory on. main does not add cf_valkey.

cf_valkey_state.New(
	cf_valkey_state.WithoutValkeyPeer(),
	cf_valkey_state.WithForceMemory(true),
	cf_valkey_state.WithMemoryMapFullPolicy("deny"),
	cf_valkey_state.WithConfigSource("valkey-state", "config/valkey-state.json"),
)

Init fails if memory is off. Mixed app (sessions + counters, one valkey) uses Path B Health, not this recipe.

Options

Option Description
WithConfig(StateConfig) static config snapshot; non-zero fields override option-set defaults
WithConfigSource(name, path, …) bind a configuration source for Init + OnConfigReload; the module registers the Source[StateConfig] itself (declares configuration dep)
WithSessionTTL(d) default session TTL when a call leaves ttl at zero (default 24h)
WithSessionUser on CreateSession index that session under a user SET (list / revoke-all)
WithCacheTTL(d) default cache TTL when a call leaves ttl at zero (default 5m)
WithValkeyName(name) bind to a named valkey peer (default "valkey")
WithoutValkeyPeer() omit valkey (counter memory only; sessions/cache error)
WithUseMemoryFallback(bool) / WithForceMemory(bool) counter map (see rate_limit)
WithMemoryMapFullPolicy("allow"|"deny") required when counter memory is on
WithName(name) custom component name for multiple instances (default "valkey-state")
WithLogger(*slog.Logger) explicit logger override; defaults to the framework logs component's logger (re-delivered on logs Reconfigure), falling back to slog.Default()

Configuration

Load StateConfig through the configuration component. With the recommended source name "valkey-state", the default EnvPrefix is VALKEY_STATE_; env tags map VALKEY_STATE_SESSION_TTL_SEC, VALKEY_STATE_CACHE_TTL_SEC (override with WithSourceEnvPrefix if you want a shorter prefix).

{
  "session_ttl_sec": 86400,
  "cache_ttl_sec": 300,
  "rate_limit": {
    "use_memory_fallback": true,
    "force_memory": false,
    "memory_map_full_policy": "deny"
  }
}

File/YAML may nest rate_limit. Env overlay does not recurse nested structs; use VALKEY_STATE_RATE_LIMIT_USE_MEMORY_FALLBACK, _FORCE_MEMORY, _MEMORY_MAX_ENTRIES, _MEMORY_MAP_FULL_POLICY. Do not flatten those switches onto the top of the JSON next to session_ttl_sec — the nested object is the counter store, not HTTP policy and not session TTLs.

The tunables apply live: on reload OnConfigReload re-reads the source and replaces the TTL defaults (last-good on failure). The valkey peer owns connection rotation, so a reload never rebuilds anything here.

Metrics emits the following while initialized, nil before Init/after Shutdown:

Metric Type Labels
valkey_state_info gauge 1 component
valkey_state_config_reloads_total counter component
valkey_state_sessions_created_total counter component
valkey_state_sessions_revoked_total counter component
valkey_state_sessions_touched_total counter component
valkey_state_cache_hits_total counter component
valkey_state_cache_misses_total counter component
valkey_state_rate_allowed_total counter component
valkey_state_rate_rejected_total counter component

Counters appear at zero until first fired, so the series are always present while initialized. Cache hit ratio is valkey_state_cache_hits_total / (valkey_state_cache_hits_total + valkey_state_cache_misses_total); rate-limit rejection rate is rate(valkey_state_rate_rejected_total[5m]).

Tests

Unit tests cover the component contract, options/config layering, dependency declaration, and Init failure modes with no external service. Integration tests (real session/cache/rate-limit behavior, TTL expiry, sliding-window touch, user session index, singleflight coalescing, Lua atomicity) run only when the gate env var is set:

docker run -d --rm -p 6379:6379 --name v valkey/valkey:8
VALKEY_ADDR=127.0.0.1:6379 go test -race ./...

License

Apache License 2.0 — see LICENSE.

Documentation

Index

Constants

View Source
const (
	// ComponentName is the framework component name for the valkey-state
	// component. It is the identifier other components use in GetDependencies
	// to require it.
	ComponentName = "valkey-state"

	// ComponentStage is the stage data-layer components initialize in. It is
	// not a built-in bootstrap stage; AddComponent registers it automatically
	// the first time a component declares it.
	ComponentStage = cf.Stage("data")
)

Variables

View Source
var ErrInvalidLimit = errors.New("cf_valkey_state: limit must be > 0")

ErrInvalidLimit is returned when limit is not positive (the HTTP limiter may disable limiting before calling the machine; the store never treats limit 0 as “always allow”).

View Source
var ErrInvalidWindow = errors.New("cf_valkey_state: window must be > 0")

ErrInvalidWindow is returned when window is not positive.

View Source
var ErrMissingTTL = errors.New("cf_valkey_state: counter missing TTL")

ErrMissingTTL is returned when a Valkey counter exists with count > 0 but no usable TTL, after the key was scrubbed, and the missing-TTL policy is error.

Functions

This section is empty.

Types

type CFState

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

CFState is the caerus-framework-valkey-state component. It is a stateless consumer of a cf_valkey.CFValkey peer: it holds the peer component (never a client snapshot) and builds every command through the peer's live Client() and prefix-aware Key(), so reconnects and key prefixes stay consistent. It provides three keyed state services over that client:

  • Sessions: opaque JSON values with a TTL and sliding-window touch.
  • Cache: JSON cache-aside plus a singleflight get-or-load.
  • Rate limit: atomic fixed-window counters.

func New

func New(opts ...Option) *CFState

New creates a state component. The valkey peer is resolved at Init, not here.

func (*CFState) Allow added in v0.0.7

func (c *CFState) Allow(ctx context.Context, key string, limit int64, window time.Duration) (RateLimitResult, error)

Allow increments the fixed-window counter for the logical key.

func (*CFState) AllowOpts added in v0.0.7

func (c *CFState) AllowOpts(ctx context.Context, key string, limit int64, window time.Duration, opts CounterOpts) (RateLimitResult, error)

AllowOpts is Allow with a missing-TTL override.

func (*CFState) CacheGet

func (c *CFState) CacheGet(ctx context.Context, dst any, parts ...string) error

CacheGet reads a JSON value at cache:<parts...> into dst. A miss returns nil and leaves dst untouched. Cache hit/miss counters are updated.

func (*CFState) CacheGetOrLoad

func (c *CFState) CacheGetOrLoad(ctx context.Context, ttl time.Duration, load LoadFunc, parts ...string) (val []byte, shared bool, err error)

CacheGetOrLoad returns the bytes cached at cache:<parts...>, or runs load once for concurrent callers in this process (singleflight), then stores the result with ttl. A ttl <= 0 uses the configured default.

WARNING: singleflight is process-local only; other pods still stampede on a cold key. shared=true when this caller waited on another goroutine's load. Failure modes: a load error is returned to waiters and nothing is cached; a SET failure after a successful load returns the loaded bytes along with the error so callers can log without losing the value.

func (*CFState) CacheSet

func (c *CFState) CacheSet(ctx context.Context, v any, ttl time.Duration, parts ...string) error

CacheSet marshals v as JSON and stores it at cache:<parts...> with the given TTL. A ttl <= 0 uses the configured default.

func (*CFState) Client

func (c *CFState) Client() valkey.Client

Client returns the peer's live valkey client (nil before Init or after Shutdown). It follows the peer-pointer convention: the peer is re-read per use, so a client swap on the valkey side is picked up immediately.

func (*CFState) CreateSession

func (c *CFState) CreateSession(ctx context.Context, id string, v any, ttl time.Duration, opts ...SessionOption) error

CreateSession stores a JSON session value at session:<id> with the given TTL. A ttl <= 0 uses the configured default. The session id is chosen by the caller (typically an opaque token); it is never derived from the value. Pass WithSessionUser to maintain the per-user SET (Choice A). Without it, the session exists by id only — ListSessionsForUser will not see it.

func (*CFState) GetDependencies

func (c *CFState) GetDependencies() []string

GetDependencies implements cf.Dependencies. The component depends on the valkey component it consumes (the actual peer name when WithValkeyName is set, the default ComponentName otherwise), logs through the framework logs component, and depends on configuration when WithConfigSource is set. Peer names are fixed at construction, so the graph is stable before Init.

func (*CFState) GetInitOrderStage

func (c *CFState) GetInitOrderStage() cf.Stage

GetInitOrderStage implements cf.CaerusComponent.

func (*CFState) GetSession

func (c *CFState) GetSession(ctx context.Context, id string, dst any) (found bool, err error)

GetSession reads the session at session:<id> and unmarshals it into dst. found is false when the session does not exist or has expired. The session TTL is not extended here; call TouchSession for a sliding window.

func (*CFState) Health

func (c *CFState) Health(ctx context.Context) error

Health implements cf.HealthProvider. It reports healthy while the peer's valkey client is initialized; real connectivity is owned by the valkey component's own Health (aggregated by observability's /readyz).

func (*CFState) Init

func (c *CFState) Init(ctx context.Context, fw *cf.CaerusFramework) error

Init implements cf.CaerusComponent. It resolves the valkey peer component (by name or the default "valkey"), failing fast when it is missing or not yet initialized. No connection is opened here; the peer owns its client.

func (*CFState) Key

func (c *CFState) Key(parts ...string) string

Key builds a prefix-aware key through the valkey peer. Useful for composing with caerus-framework-valkey/patterns helpers (e.g. patterns.NewMutex) against the same key space.

func (*CFState) ListSessionsForUser added in v0.0.8

func (c *CFState) ListSessionsForUser(ctx context.Context, userID string) ([]string, error)

ListSessionsForUser returns live session ids for userID (SMEMBERS on the per-user SET, then EXISTS on each payload). Ghost members (expired payload) are SREM'd. This is O(sessions of that user), not KEYS. Sessions created without WithSessionUser are not listed. Empty userID is an error. Errors do not include the user id.

func (*CFState) Metrics

func (c *CFState) Metrics() []cf_observability.Metric

Metrics implements cf_observability.MetricsProvider. It reports operation counters while the peer's client is initialized; before Init or after Shutdown it returns nil, so the observability component skips it (lazy pickup). Counters are cumulative for the process lifetime and are emitted (zero until first fired) so the series are always present.

func (*CFState) Name

func (c *CFState) Name() string

Name implements cf.CaerusComponent. Returns the custom name set via WithName, or the default ComponentName ("valkey-state") if no custom name was set.

func (*CFState) OnConfigReload

func (c *CFState) OnConfigReload(source string, cfg any)

OnConfigReload implements cf.ConfigReloader. It re-reads the behavior tunables (session/cache TTLs) from the bound configuration source. No connection is rebuilt: the valkey peer owns client rotation, and this component is stateless over it.

func (*CFState) Peek added in v0.0.7

func (c *CFState) Peek(ctx context.Context, key string) (RateLimitResult, error)

Peek reads the counter without incrementing. Allowed is always false.

func (*CFState) PeekOpts added in v0.0.7

func (c *CFState) PeekOpts(ctx context.Context, key string, opts CounterOpts) (RateLimitResult, error)

PeekOpts is Peek with a missing-TTL override.

func (*CFState) RateLimit

func (c *CFState) RateLimit(ctx context.Context, key string, limit int64, window time.Duration) (RateLimitResult, error)

RateLimit is sugar for Allow with default CounterOpts (fixed window).

func (*CFState) RegisterConfigSources

func (c *CFState) RegisterConfigSources(conf any) error

RegisterConfigSources implements cf.ConfigSourceRegistrar. The framework calls it during argv absorption; it registers this component's configuration source (name, path, env prefix, format, Owner) with the configuration component. No-op when no source is bound.

func (*CFState) Reset added in v0.0.7

func (c *CFState) Reset(ctx context.Context, key string) error

Reset deletes the counter for the logical key (idempotent).

func (*CFState) RevokeAllForUser added in v0.0.8

func (c *CFState) RevokeAllForUser(ctx context.Context, userID string, keep ...string) error

RevokeAllForUser deletes every indexed session for userID except keep. keep is typically the current session id ("revoke others"). Not KEYS. Empty userID is an error. Errors do not include the user id.

func (*CFState) RevokeSession

func (c *CFState) RevokeSession(ctx context.Context, id string) error

RevokeSession deletes session:<id> and SREM from the user SET when the session was created with WithSessionUser. Deleting a missing session is a no-op success.

func (*CFState) SessionExists

func (c *CFState) SessionExists(ctx context.Context, id string) (bool, error)

SessionExists reports whether session:<id> exists and is unexpired.

func (*CFState) Shutdown

func (c *CFState) Shutdown(ctx context.Context) error

Shutdown implements cf.CaerusComponent. It unsubscribes the logs subscription and drops the valkey peer. Further use returns an error.

func (*CFState) TouchSession

func (c *CFState) TouchSession(ctx context.Context, id string, ttl time.Duration) error

TouchSession extends session:<id> with a fresh TTL (sliding window). A missing or expired session is a no-op success. A ttl <= 0 uses the configured default.

type CounterOpts added in v0.0.7

type CounterOpts struct {
	// MissingTTL overrides the default proceed policy for this call.
	MissingTTL MissingTTLPolicy
}

CounterOpts is optional per-call store hygiene. The app does not pick Valkey vs memory here — that is the nested rate_limit settings.

type LoadFunc

type LoadFunc func(ctx context.Context) ([]byte, error)

LoadFunc fetches the canonical value on a cache miss. Called at most once per key per in-flight wave inside this process.

type MapFullPolicy added in v0.0.7

type MapFullPolicy int

MapFullPolicy is what happens when the counter sticky-note map hits MaxEntries for a new logical key.

const (
	// MapFullAllow allows a new key without counting when the map is full.
	MapFullAllow MapFullPolicy = iota
	// MapFullDeny rejects Allow for a new key when the cap is reached.
	MapFullDeny
)

type MissingTTLPolicy added in v0.0.7

type MissingTTLPolicy int

MissingTTLPolicy selects what happens after scrubbing a counter that has count > 0 but no usable TTL. Zero value means proceed.

const (
	// MissingTTLProceed continues after delete: Allow retries Lua once; Peek
	// returns empty. This is the default when CounterOpts leaves the field unset.
	MissingTTLProceed MissingTTLPolicy = iota
	// MissingTTLError returns ErrMissingTTL after delete.
	MissingTTLError
)

type Option

type Option func(*options)

Option configures the state component at construction time.

func WithCacheTTL

func WithCacheTTL(d time.Duration) Option

WithCacheTTL sets the default cache TTL used when a cache call leaves ttl at zero (default 5m).

func WithConfig

func WithConfig(cfg StateConfig) Option

WithConfig sets a static configuration snapshot. Non-zero fields of cfg override the values set by the convenience options. Prefer WithConfigSource when using caerus-framework-configuration with hot-reload.

func WithConfigSource

func WithConfigSource(name, path string, opts ...SourceOption) Option

WithConfigSource binds this component to a named configuration source and registers that source with the configuration component (via the framework's ConfigSourceRegistrar pass during argv absorption). The module owns the Source: the config type, the default EnvPrefix and its Owner (Name(), so named instances reload correctly). main only points the instance at where the config lives.

cf_valkey_state.New(cf_valkey_state.WithConfigSource("valkey-state", "config/valkey-state.json"))
cf_valkey_state.New(cf_valkey_state.WithConfigSource("sess", "/etc/app/sess.yaml",
    cf_valkey_state.WithSourceFormat(cf_configuration.FormatYAML)))

A path of "" registers an env-only (fileless) source when the EnvPrefix is non-empty. The path CLI override stays --<source-name> (ParseFlags). Declares a dependency on "configuration".

func WithForceMemory added in v0.0.7

func WithForceMemory(enabled bool) Option

WithForceMemory prefers the counter map even when a live Valkey client exists.

func WithLogger

func WithLogger(logger *slog.Logger) Option

WithLogger overrides the logger used for component diagnostics. By default the component logs through the framework logs component (declared in GetDependencies); WithLogger is an explicit override for tests and embedded use and wins over the framework logger. slog.Default() remains the fallback only when neither is available.

func WithMemoryMapFullPolicy added in v0.0.7

func WithMemoryMapFullPolicy(policy string) Option

WithMemoryMapFullPolicy sets "allow" or "deny" when the counter map is full.

func WithName

func WithName(name string) Option

WithName sets a custom component name, allowing multiple state instances in the same process. The default name is "valkey-state" (ComponentName). Retrieve named instances with GetByName[*CFState](fw, "sessions").

func WithSessionTTL

func WithSessionTTL(d time.Duration) Option

WithSessionTTL sets the default session TTL used when a session call leaves ttl at zero (default 24h).

func WithUseMemoryFallback added in v0.0.7

func WithUseMemoryFallback(enabled bool) Option

WithUseMemoryFallback enables the counter sticky-note map when Valkey Eval fails or Client() is nil.

func WithValkeyName

func WithValkeyName(name string) Option

WithValkeyName binds the state component to a valkey component with the given name (WithName on the valkey side). The default is the valkey ComponentName ("valkey").

func WithoutValkeyPeer added in v0.0.7

func WithoutValkeyPeer() Option

WithoutValkeyPeer omits valkey from GetDependencies (GH App / counters-only sticky notes). Init then requires rate_limit memory (use_memory_fallback or force_memory) plus memory_map_full_policy. Sessions and cache error: there is no fridge.

type RateLimitConfig added in v0.0.7

type RateLimitConfig struct {
	UseMemoryFallback   *bool  `json:"use_memory_fallback,omitempty" yaml:"use_memory_fallback,omitempty"`
	ForceMemory         *bool  `json:"force_memory,omitempty" yaml:"force_memory,omitempty"`
	MemoryMaxEntries    int    `json:"memory_max_entries,omitempty" yaml:"memory_max_entries,omitempty"`
	MemoryMapFullPolicy string `json:"memory_map_full_policy,omitempty" yaml:"memory_map_full_policy,omitempty"`
}

RateLimitConfig is the counter store (not HTTP policy). Only this nested block may use an in-process map — sessions and cache stay Valkey-only.

type RateLimitResult

type RateLimitResult struct {
	// Allowed is true when Count does not exceed the limit for the window.
	// Peek always leaves this false (Peek has no limit).
	Allowed bool
	// Count is the (incremented, for Allow) request count within the window.
	Count int64
	// ResetIn is the time remaining until the window resets (for Retry-After).
	ResetIn time.Duration
}

RateLimitResult reports the outcome of Allow or Peek.

type SessionOption added in v0.0.8

type SessionOption func(*sessionOpts)

SessionOption configures CreateSession (user index binding).

func WithSessionUser added in v0.0.8

func WithSessionUser(userID string) SessionOption

WithSessionUser indexes this session under userID so ListSessionsForUser and RevokeAllForUser can find it. Empty userID is ignored (no index, same as omitting the option). This is not inferred from the JSON value.

type SourceOption

type SourceOption func(*sourceOptions)

SourceOption configures the self-registered configuration source created by WithConfigSource.

func WithSourceEnvPrefix

func WithSourceEnvPrefix(prefix string) SourceOption

WithSourceEnvPrefix sets the environment overlay prefix for the source (default: the uppercase source name with "-" replaced by "_", plus "_"). An empty prefix disables env overlay.

func WithSourceFormat

func WithSourceFormat(f cf_configuration.Format) SourceOption

WithSourceFormat forces the file format instead of inferring it from the path extension (".yaml"/".yml" → YAML; anything else JSON).

type StateConfig

type StateConfig struct {
	// SessionTTLSec is the default session TTL in seconds when a call leaves
	// ttl at zero (default 24h).
	SessionTTLSec int64 `json:"session_ttl_sec,omitempty" yaml:"session_ttl_sec,omitempty" env:"SESSION_TTL_SEC"`
	// CacheTTLSec is the default cache TTL in seconds when a call leaves ttl
	// at zero (default 5m).
	CacheTTLSec int64 `json:"cache_ttl_sec,omitempty" yaml:"cache_ttl_sec,omitempty" env:"CACHE_TTL_SEC"`
	// RateLimit is the nested counter-store section (Lua vs sticky-note map).
	// HTTP limit/window numbers do not live here.
	RateLimit RateLimitConfig `json:"rate_limit,omitempty" yaml:"rate_limit,omitempty" env:"-"`

	// Env overlay cannot recurse into nested structs; these aliases merge
	// into RateLimit in applyConfig (VALKEY_STATE_RATE_LIMIT_*).
	RateLimitUseMemoryFallback *bool  `json:"-" yaml:"-" env:"RATE_LIMIT_USE_MEMORY_FALLBACK"`
	RateLimitForceMemory       *bool  `json:"-" yaml:"-" env:"RATE_LIMIT_FORCE_MEMORY"`
	RateLimitMemoryMaxEntries  int    `json:"-" yaml:"-" env:"RATE_LIMIT_MEMORY_MAX_ENTRIES"`
	RateLimitMapFullPolicy     string `json:"-" yaml:"-" env:"RATE_LIMIT_MEMORY_MAP_FULL_POLICY"`
}

StateConfig is the file/env-drivable behavior configuration. Load it through the configuration component (caerus-framework-configuration) and pass it via WithConfigSource; both JSON and YAML tags are provided.

Jump to

Keyboard shortcuts

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