Rene Nochebuena df7aa63e5c feat(cache-valkey): initial implementation — Provider, adapters (v1.0.0)
Introduces code.nochebuena.dev/einherjar/cache-valkey — the Valkey cache starter
for the Einherjar framework. Absorbs the valkey package from micro-lib and adds
three duck-typed adapters that wire directly into auth, web, and auth-jwt.

Core:
- Provider interface — Get, Set, Del, Exists, Expire, IncrWithTTL, Native()
- Component interface — lifecycle.Component + observability.Checkable + Provider
- Config struct (EINHERJAR_VALKEY_* env vars)
- New(logger, cfg) Component — creates valkey-go client in OnInit;
  PING in OnStart; logs "valkey: connected"
- IncrWithTTL implemented with Lua script (atomic INCR + conditional EXPIRE);
  race-free fixed-window semantics with no MULTI/EXEC overhead
- Priority: LevelDegraded — Valkey outage degrades, does not halt the service

Adapters (duck-typed — no import of auth, web, or auth-jwt):
- PermissionCache — Get/Set int64 bitmasks as string; satisfies auth/rbac.Cache
- RateLimiterStore — fixed-window via IncrWithTTL; satisfies web/mw.RateLimiterStore
- Blacklist — Exists/Set "1" with TTL; satisfies auth-jwt.Blacklist

Compliance test verifies CT-6, duck-type shape assignments, and full adapter
behavioural coverage with a mockProvider (no live server required).

- Component interface embeds observability.Identifiable; identifiable.go implements
  ModulePath and ModuleVersion via runtime/debug.ReadBuildInfo() — prints in launcher banner
2026-05-29 15:58:56 +00:00

einherjar/cache-valkey

version license go health

Speed is not a virtue. It is the difference between arriving and never arriving.

code.nochebuena.dev/einherjar/cache-valkey is the Valkey cache component of the Einherjar framework. It wraps valkey-go behind a lifecycle-aware Component and ships three adapters — permission cache, rate limiter, and JWT blacklist — that satisfy interfaces in auth, web, and auth-jwt via Go's structural typing, with no cross-module import required.

Quick start

import cachevalkey "code.nochebuena.dev/einherjar/cache-valkey"

vk := cachevalkey.New(logger, cachevalkey.Config{
    Addrs: []string{"localhost:6379"},
})
launcher.Register(vk) // OnInit → OnStart → OnStop
health.Register(vk)   // PING-based health check, LevelDegraded

Connecting to other modules

Go's structural typing handles interface assignment across module boundaries — no cast, no glue interface required. Create adapters from the component and pass them directly to the consuming modules:

// --- 1. Create the component (lifecycle + health + Provider) ---
vk := cachevalkey.New(logger, cfg)
launcher.Register(vk)
health.Register(vk)

// --- 2. Create adapters (one per consumer interface) ---
// Each adapter wraps vk (a Provider) and satisfies one interface in another module.
// No import of auth, web, or auth-jwt is needed here.
permCache   := cachevalkey.NewPermissionCache(vk)
rateLimiter := cachevalkey.NewRateLimiterStore(vk, time.Second, 100)
blacklist   := cachevalkey.NewBlacklist(vk)

// --- 3. Wire adapters into consuming modules ---
// Go's structural typing handles the interface assignment — no cast required.

// auth: permission cache (satisfies rbac.Cache)
permProvider := rbac.NewCachedPermissionProvider(permCache, baseProvider, rbac.CacheConfig{TTL: 5 * time.Minute})

// web: rate limiter (satisfies mw.RateLimiterStore)
router.Use(mw.IPRateLimit(rateLimiter))

// auth-jwt: refresh token blacklist (satisfies authjwt.Blacklist)
pair, err := authjwt.RefreshTokenPair(ctx, signer, oldRefreshToken, blacklist, tokenCfg, newClaims)

The compile-time duck-type checks in compliance_test.go verify that each adapter satisfies its target interface. If those checks compile, the wiring above is guaranteed to work.

Configuration

Environment variable Required Default Description
EINHERJAR_VALKEY_ADDRS Yes Comma-separated host:port addresses
EINHERJAR_VALKEY_PASSWORD No "" Auth password
EINHERJAR_VALKEY_DB No 0 Database index
EINHERJAR_VALKEY_CLIENT_CACHE_MB No 0 Client-side cache per connection in MB (0 = disabled)

API

Provider interface

Component satisfies Provider. Pass the result of New() wherever a Provider is expected.

Method Description
Get(ctx, key) (string, bool, error) GET key; returns ("", false, nil) on miss
Set(ctx, key, value, ttl) error SET key value [EX ttl]; ttl=0 = no expiry
Del(ctx, keys...) error DEL one or more keys
Exists(ctx, key) (bool, error) EXISTS key
Expire(ctx, key, ttl) error EXPIRE key ttl
IncrWithTTL(ctx, key, ttl) (int64, error) Atomic INCR + EXPIRE on first increment (Lua)
Native() vk.Client Raw valkey-go client for operations not in Provider

Adapters

Constructor Satisfies Description
NewPermissionCache(Provider) auth/rbac.Cache Stores int64 permission bitmasks as decimal strings
NewRateLimiterStore(Provider, window, limit) web/mw.RateLimiterStore Fixed-window counter; Lua atomic increment
NewBlacklist(Provider) auth-jwt.Blacklist JWT JTI revocation via SET/EXISTS

Dependency graph

cache-valkey
├── contracts v1.0.0   (lifecycle.Component, observability.Checkable, logging.Logger)
├── core v1.0.0        (xerrors)
└── valkey-go v1.0.54  (native client)

No dependency on web, auth, or auth-jwt. The three adapters satisfy interfaces in those modules via duck typing.

Description
Valkey cache component with rate limiter, permission cache, and JWT blacklist adapters
Readme 63 KiB
2026-05-29 09:59:33 -06:00
Languages
Go 100%