feat(valkey): initial stable release v0.9.0

Valkey (Redis-compatible) client component with launcher lifecycle and health check integration.

What's included:
- Config with Addrs, Password, SelectDB, CacheSizeEachConn (env-driven)
- Provider interface exposing native valkey-go Client() directly (no wrapper)
- Component interface: launcher.Component + health.Checkable + Provider
- New(logger, cfg) constructor for lifecycle registration via lc.Append
- Health check via PING at LevelDegraded priority
- Graceful shutdown calling client.Close() in OnStop

Tested-via: todo-api POC integration
Reviewed-against: docs/adr/
This commit is contained in:
2026-03-19 13:29:28 +00:00
commit eda54153d6
13 changed files with 490 additions and 0 deletions

92
valkey.go Normal file
View File

@@ -0,0 +1,92 @@
package valkey
import (
"context"
"fmt"
vk "github.com/valkey-io/valkey-go"
"code.nochebuena.dev/go/health"
"code.nochebuena.dev/go/launcher"
"code.nochebuena.dev/go/logz"
)
// Provider is the minimal interface for consumers that only need the valkey client.
type Provider interface {
Client() vk.Client
}
// Component adds lifecycle management and health check to Provider.
type Component interface {
launcher.Component
health.Checkable
Provider
}
// Config holds Valkey connection settings.
type Config struct {
Addrs []string `env:"VK_ADDRS,required" envSeparator:","`
Password string `env:"VK_PASSWORD"`
SelectDB int `env:"VK_DB" envDefault:"0"`
CacheSizeEachConn int `env:"VK_CLIENT_CACHE_MB" envDefault:"0"` // MB; 0 = disable
}
type vkComponent struct {
cfg Config
logger logz.Logger
client vk.Client
}
// New returns a valkey Component. Call lc.Append(vk) to manage its lifecycle.
func New(logger logz.Logger, cfg Config) Component {
return &vkComponent{cfg: cfg, logger: logger}
}
func (v *vkComponent) OnInit() error {
opts := vk.ClientOption{
InitAddress: v.cfg.Addrs,
Password: v.cfg.Password,
SelectDB: v.cfg.SelectDB,
}
if v.cfg.CacheSizeEachConn > 0 {
opts.CacheSizeEachConn = v.cfg.CacheSizeEachConn * 1024 * 1024
}
client, err := vk.NewClient(opts)
if err != nil {
return fmt.Errorf("valkey: failed to create client: %w", err)
}
v.client = client
return nil
}
func (v *vkComponent) OnStart() error {
v.logger.Info("valkey: verifying connection")
if v.client == nil {
return fmt.Errorf("valkey: client not initialized")
}
if err := v.client.Do(context.Background(), v.client.B().Ping().Build()).Error(); err != nil {
return fmt.Errorf("valkey: ping failed: %w", err)
}
v.logger.Info("valkey: connected")
return nil
}
func (v *vkComponent) OnStop() error {
v.logger.Info("valkey: closing client")
if v.client != nil {
v.client.Close()
}
return nil
}
func (v *vkComponent) Client() vk.Client { return v.client }
func (v *vkComponent) HealthCheck(ctx context.Context) error {
if v.client == nil {
return fmt.Errorf("valkey: client not initialized")
}
return v.client.Do(ctx, v.client.B().Ping().Build()).Error()
}
func (v *vkComponent) Name() string { return "valkey" }
func (v *vkComponent) Priority() health.Level { return health.LevelDegraded }