# einherjar/cache-valkey [![version](https://img.shields.io/badge/version-v1.0.0-5C4EE5?style=flat-square)](https://code.nochebuena.dev/einherjar/cache-valkey) [![license](https://img.shields.io/badge/license-AGPL--3.0-22863A?style=flat-square)](LICENSE) [![go](https://img.shields.io/badge/Go-1.26+-00ADD8?style=flat-square&logo=go&logoColor=white)](https://go.dev) [![health](https://img.shields.io/badge/health-degraded-E36209?style=flat-square)]() > 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 ```go 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: ```go // --- 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.