2026-05-29 15:58:56 +00:00
# einherjar/cache-valkey
2026-08-07 19:09:10 -06:00
[](https://code.nochebuena.dev/einherjar/cache-valkey)
2026-05-29 15:58:56 +00:00
[](LICENSE)
[](https://go.dev)
[]()
> 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" },
})
2026-08-07 22:14:06 -06:00
lc . Append ( vk ) // OnInit → OnStart → OnStop
// vk is observability.Checkable (PING-based, LevelDegraded):
srv . Get ( "/health" , health . NewHandler ( logger , vk ). ServeHTTP )
2026-05-29 15:58:56 +00:00
```
## 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 )
2026-08-07 22:14:06 -06:00
lc . Append ( vk )
srv . Get ( "/health" , health . NewHandler ( logger , vk ). ServeHTTP )
2026-05-29 15:58:56 +00:00
// --- 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.