Store Interface
Complete API reference for the Grove KV Store: core commands, raw access, multi-key operations, TTL, scanning, options, hooks, and the batch builder.
The Store is the top-level KV handle. It manages the driver connection, hook engine, default codec, and provides entry points for all KV operations.
Creating a Store
Use kv.Open to create a Store with a connected driver:
import (
"github.com/xraph/grove/kv"
"github.com/xraph/grove/kv/codec"
"github.com/xraph/grove/kv/drivers/redisdriver"
)
rdb := redisdriver.New()
rdb.Open(ctx, "redis://localhost:6379/0")
store, err := kv.Open(rdb,
kv.WithCodec(codec.MsgPack()), // optional: override default JSON codec
kv.WithHook(&myLoggingHook{}), // optional: register hooks at creation
)The driver must be connected before passing it to kv.Open. The Store takes ownership of the driver and will close it when store.Close() is called.
Store Options
| Option | Description |
|---|---|
kv.WithCodec(c) | Set the default codec for the store (default: JSON) |
kv.WithHook(h, scope...) | Register a hook with optional scope at store creation |
Core Methods
Get
Retrieves the value for key and decodes it into dest using the store's codec.
func (s *Store) Get(ctx context.Context, key string, dest any) errorvar user User
err := store.Get(ctx, "user:1", &user)
if errors.Is(err, kv.ErrNotFound) {
// key does not exist
}Set
Encodes value using the store's codec and stores it under key. Accepts optional SetOption values for TTL and conditional writes.
func (s *Store) Set(ctx context.Context, key string, value any, opts ...SetOption) errorerr := store.Set(ctx, "user:1", &user, kv.WithTTL(time.Hour))Delete
Removes one or more keys.
func (s *Store) Delete(ctx context.Context, keys ...string) errorerr := store.Delete(ctx, "user:1", "user:2", "user:3")Exists
Returns the count of keys that exist.
func (s *Store) Exists(ctx context.Context, keys ...string) (int64, error)count, err := store.Exists(ctx, "user:1", "user:2")
fmt.Printf("%d keys exist\n", count)Ping
Verifies the store is reachable.
func (s *Store) Ping(ctx context.Context) errorClose
Closes the store and releases all resources, including the underlying driver connection.
func (s *Store) Close() errorRaw Methods
Raw methods bypass the store-level codec. They are used internally by Keyspace[T] to apply its own codec, and can be used directly when you manage serialization yourself.
GetRaw
Retrieves the raw bytes for a key without codec decoding.
func (s *Store) GetRaw(ctx context.Context, key string) ([]byte, error)SetRaw
Stores raw bytes under a key without codec encoding.
func (s *Store) SetRaw(ctx context.Context, key string, value []byte, opts ...SetOption) errorMulti-Key Operations
Multi-key methods require the driver to implement driver.BatchDriver. All built-in drivers except BoltDB support batch operations.
MGet
Retrieves multiple keys. Values are decoded through the store's codec into a map[string]any.
func (s *Store) MGet(ctx context.Context, keys []string, dest map[string]any) errordest := make(map[string]any)
err := store.MGet(ctx, []string{"user:1", "user:2", "user:3"}, dest)
for key, val := range dest {
fmt.Printf("%s => %v\n", key, val)
}MSet
Sets multiple key-value pairs. Values are encoded through the store's codec.
func (s *Store) MSet(ctx context.Context, pairs map[string]any, opts ...SetOption) errorpairs := map[string]any{
"config:theme": "dark",
"config:language": "en",
"config:timezone": "UTC",
}
err := store.MSet(ctx, pairs, kv.WithTTL(24*time.Hour))TTL Operations
TTL methods require the driver to implement driver.TTLDriver. Redis, Memcached, DynamoDB, and Badger all support TTL.
TTL
Returns the remaining time-to-live for a key.
func (s *Store) TTL(ctx context.Context, key string) (time.Duration, error)ttl, err := store.TTL(ctx, "session:abc")
if ttl == -1 {
fmt.Println("key has no expiry")
}Expire
Sets or updates the TTL on an existing key.
func (s *Store) Expire(ctx context.Context, key string, ttl time.Duration) errorerr := store.Expire(ctx, "session:abc", 30*time.Minute)Scan
Iterates over keys matching a glob pattern. Requires the driver to implement driver.ScanDriver. The callback fn is called for each matching key.
func (s *Store) Scan(ctx context.Context, pattern string, fn func(key string) error) errorerr := store.Scan(ctx, "user:*", func(key string) error {
fmt.Println("found key:", key)
return nil
})Return a non-nil error from the callback to stop iteration early.
Set Options
SetOption values configure individual Set and SetRaw calls:
| Option | Description |
|---|---|
kv.WithTTL(d) | Set the TTL for this key. Zero means no expiry. |
kv.WithNX() | Only set the key if it does not already exist. Returns kv.ErrConflict if the key exists. |
kv.WithXX() | Only set the key if it already exists. Returns kv.ErrNotFound if the key is missing. |
kv.WithCAS(version) | Compare-and-swap: only set if the stored version matches. |
WithNX and WithXX require the driver to implement driver.CASDriver.
// Set only if key does not exist (distributed lock pattern).
err := store.Set(ctx, "lock:resource", &lockData, kv.WithNX(), kv.WithTTL(30*time.Second))
// Update only if key already exists.
err = store.Set(ctx, "user:1", &updatedUser, kv.WithXX())Accessor Methods
Driver
Returns the underlying KV driver. Use this with typed Unwrap functions to access driver-specific features.
func (s *Store) Driver() driver.Driver// Access Redis-specific features.
rdb := redisdriver.Unwrap(store)
rdb.Client().HSet(ctx, "hash:1", "field", "value")Hooks
Returns the hook engine for registering additional hooks after store creation.
func (s *Store) Hooks() *hook.Enginestore.Hooks().AddHook(&AuditLogger{}, hook.Scope{
Priority: 1,
})Codec
Returns the store's default codec.
func (s *Store) Codec() codec.CodecBatch Builder
The Batch provides a fluent builder API for composing multi-key operations (gets, sets, and deletes) into a single execution. The driver must implement driver.BatchDriver.
Creating a Batch
batch := kv.NewBatch(store)Building Operations
result, err := kv.NewBatch(store).
Set("user:1", &alice).
Set("user:2", &bob).
Get("config:theme", "config:language").
Delete("temp:session").
WithOptions(kv.WithTTL(time.Hour)).
Exec(ctx)BatchResult
Exec returns a BatchResult containing retrieved values and operation counts:
type BatchResult struct {
Values map[string][]byte // Raw bytes from Get operations
Written int64 // Number of keys written
Deleted int64 // Number of keys deleted
}Decode individual values from the result:
var theme string
err := result.Decode("config:theme", &theme, store.Codec())Sentinel Errors
| Error | Description |
|---|---|
kv.ErrNotFound | Key does not exist |
kv.ErrConflict | CAS version mismatch or NX conflict |
kv.ErrStoreClosed | Operation on a closed store |
kv.ErrNotSupported | Driver does not support the operation |
kv.ErrHookDenied | A hook denied the operation |
kv.ErrCodecEncode | Value encoding failed |
kv.ErrCodecDecode | Value decoding failed |
kv.ErrKeyEmpty | Empty key provided |
kv.ErrNilValue | Nil value provided to Set |
KV Operations
Every command passes through the hook engine as an Operation. These are the KV-specific operations:
| Operation | Command | Description |
|---|---|---|
kv.OpGet | GET | Retrieve a single key |
kv.OpSet | SET | Store a value under a key |
kv.OpDelete | DEL | Remove one or more keys |
kv.OpExists | EXISTS | Check key existence |
kv.OpMGet | MGET | Retrieve multiple keys |
kv.OpMSet | MSET | Store multiple key-value pairs |
kv.OpTTL | TTL | Get remaining TTL |
kv.OpExpire | EXPIRE | Set TTL on existing key |
kv.OpScan | SCAN | Iterate over matching keys |