The package-doc examples showed a fictional launcher.Register / health.Register API (and launcher.Append as a package func). Corrected to the real wiring: lc.Append + web/health.NewHandler(...).ServeHTTP. (component.go doc comment corrected too.) Documentation-only. No code, API, or dependency changes; version stays v1.1.0 (the v1.1.0 tag is moved to include this fix). Verified by compiling the corrected pattern against the real API.
106 lines
4.5 KiB
Markdown
106 lines
4.5 KiB
Markdown
# einherjar/cache-valkey
|
|
|
|
[](https://code.nochebuena.dev/einherjar/cache-valkey)
|
|
[](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"},
|
|
})
|
|
lc.Append(vk) // OnInit → OnStart → OnStop
|
|
// vk is observability.Checkable (PING-based, LevelDegraded):
|
|
srv.Get("/health", health.NewHandler(logger, vk).ServeHTTP)
|
|
```
|
|
|
|
## 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)
|
|
lc.Append(vk)
|
|
srv.Get("/health", health.NewHandler(logger, vk).ServeHTTP)
|
|
|
|
// --- 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.
|