store

package
v1.4.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 3, 2026 License: Apache-2.0 Imports: 9 Imported by: 0

Documentation

Overview

Package store defines the unified byte-level key-value interface shared by celeris middleware (session, csrf, ratelimit, jwt, cache, idempotency).

Interfaces

KV is the minimal required surface: Get, Set with TTL, Delete. All implementations must be safe for concurrent use and honor the contract that Get returns (nil, ErrNotFound) on a missing key.

Optional extension interfaces (GetAndDeleter, Scanner, PrefixDeleter, SetNXer, Scripter) surface backend capabilities. Middleware feature- detect these via type assertion and fall back to emulation or no-op semantics when a backend does not implement them.

Reference backends

  • MemoryKV — in-memory, sharded implementation. Implements every optional extension. Used as the default store and in tests.
  • middleware/session/redisstore — Redis-backed via driver/redis. Implements KV + Scanner; GetAndDeleter when Redis >= 6.2.
  • middleware/session/postgresstore — PostgreSQL-backed via driver/postgres. Implements KV only; Reset via TRUNCATE (exposed as io.Closer).
  • middleware/csrf/redisstore — Redis-backed CSRF storage. Implements KV + GetAndDeleter (GETDEL).
  • middleware/ratelimit/redisstore — Redis-backed ratelimit store. Implements ratelimit.Store via EVALSHA; not a KV adapter.

Response wire format

EncodedResponse + ResponseWireVersion define a byte-efficient snapshot format used by cache and idempotency middleware to persist captured HTTP responses. The format is versioned to allow forward compatibility; decoders reject unknown versions.

Contract summary

  • Get returns (nil, ErrNotFound) on miss. (nil, nil) is forbidden.
  • Set with ttl == 0 stores with no expiry.
  • Delete is idempotent: deleting a missing key returns nil.
  • Values returned by Get are caller-owned; backends must copy.
  • All methods are safe for concurrent use.

Package store defines the unified key-value interface that middleware stores (session, csrf, cache, idempotency, jwks cache) build on.

The core interface is KV — a small, byte-level, context-aware Get/Set/Delete surface. Backends implement KV plus any optional extensions they can support atomically (GETDEL, SCAN, SETNX, EVALSHA). Middleware adapters feature-detect extensions via type assertion.

All implementations must follow the [KV.Get] contract: return nil, ErrNotFound on a missing key. Returning nil, nil is forbidden.

Index

Examples

Constants

View Source
const ResponseWireVersion byte = 1

ResponseWireVersion is the current wire format version for encoded HTTP responses stored in KV backends by cache and idempotency middleware.

Variables

View Source
var ErrInvalidWireFormat = errors.New("store: invalid response wire format")

ErrInvalidWireFormat is returned by DecodeResponse when the buffer is truncated or carries an unknown version byte.

View Source
var ErrNotFound = errors.New("store: key not found")

ErrNotFound is returned by [KV.Get] and extension methods when the key does not exist or has expired. Adapters map their native "missing key" signal (redis.ErrNil, sql.ErrNoRows, 0 rows) onto this sentinel.

Functions

func DecodeJSON

func DecodeJSON(data []byte, v any) error

DecodeJSON unmarshals data into v. Convenience wrapper around json.Unmarshal for symmetry with EncodeJSON.

func EncodeJSON

func EncodeJSON(v any) ([]byte, error)

EncodeJSON returns the JSON encoding of v. Provided for adapters that need to persist structured payloads (session data maps) through the byte-level KV surface.

Types

type EncodedResponse

type EncodedResponse struct {
	Status  int
	Headers [][2]string
	Body    []byte
}

EncodedResponse is a byte-efficient snapshot of an HTTP response used by cache and idempotency middleware to persist captured responses.

Wire format (version 1):

[1 byte version=1]
[2 bytes status (big-endian)]
[2 bytes header_count (big-endian)]
for each header:
  [2 bytes key_len (big-endian)] [key_len bytes key]
  [2 bytes val_len (big-endian)] [val_len bytes value]
[remaining bytes: body]

func DecodeResponse

func DecodeResponse(buf []byte) (EncodedResponse, error)

DecodeResponse parses buf into an EncodedResponse following the versioned wire format. Returns ErrInvalidWireFormat if the version is unknown or the buffer is truncated.

func (EncodedResponse) AppendTo

func (r EncodedResponse) AppendTo(dst []byte) []byte

AppendTo serializes r into dst and returns the extended slice. Lets callers reuse a pooled buffer across requests (cache / idempotency MISS paths transfer the encoded bytes into a Store.Set that copies internally, so the buffer is free to recycle immediately after).

func (EncodedResponse) Encode

func (r EncodedResponse) Encode() []byte

Encode serializes r into a new byte slice using the current wire format version.

type GetAndDeleter

type GetAndDeleter interface {
	GetAndDelete(ctx context.Context, key string) ([]byte, error)
}

GetAndDeleter is implemented by backends that support atomic GET+DEL (e.g., Redis GETDEL). Used by csrf for TOCTOU-safe single-use token validation. When a KV does not implement GetAndDeleter, callers fall back to a non-atomic Get+Delete pair.

type KV

type KV interface {
	Get(ctx context.Context, key string) ([]byte, error)
	Set(ctx context.Context, key string, value []byte, ttl time.Duration) error
	Delete(ctx context.Context, key string) error
}

KV is the unified byte-level key-value interface for middleware stores.

Contract:

  • Get returns (nil, ErrNotFound) on a missing key. A (nil, nil) return is forbidden; callers may rely on err != nil when the value is absent.
  • Set with ttl == 0 stores a value with no expiry. Positive ttl is honored by the backend; sub-second precision is best-effort.
  • Delete returning nil means "the key is not present" regardless of whether it was present before the call.
  • All methods are safe for concurrent use from multiple goroutines.
  • Values returned by Get are owned by the caller; backends must copy any internal buffer before returning.

func Prefixed

func Prefixed(inner KV, prefix string) KV

Prefixed returns a KV that transparently prepends prefix to every key passed through Get/Set/Delete. Useful when sharing one backend among multiple middleware (e.g., session on "sess:" and cache on "cache:").

Feature extensions (GetAndDeleter, Scanner, PrefixDeleter, SetNXer) on the inner KV are transparently surfaced: the returned value implements whichever of those the inner value implements, with keys/prefixes rewritten accordingly.

Example

ExamplePrefixed wraps any store.KV with a key namespace. Useful when several middlewares share one Redis instance — give each one its own prefix so keys never collide.

package main

import (
	"context"
	"fmt"
	"time"

	"github.com/goceleris/celeris/middleware/store"
)

func main() {
	kv := store.NewMemoryKV()
	defer kv.Close()

	sessions := store.Prefixed(kv, "sess:")
	_ = sessions.Set(context.Background(), "abc", []byte("hi"), time.Minute)

	// Underlying key is "sess:abc".
	raw, _ := kv.Get(context.Background(), "sess:abc")
	fmt.Println(string(raw))
}
Output:
hi

type MemoryKV

type MemoryKV struct {
	// contains filtered or unexported fields
}

MemoryKV is an in-memory, sharded implementation of KV. It also implements GetAndDeleter, Scanner, PrefixDeleter, and SetNXer.

MemoryKV is intended as the default/test backend and for single-process deployments. For multi-instance deployments, use a driver-backed adapter (middleware/session/redisstore, etc.).

func NewMemoryKV

func NewMemoryKV(config ...MemoryKVConfig) *MemoryKV

NewMemoryKV returns a new MemoryKV with a running cleanup goroutine. Call MemoryKV.Close when the store is no longer needed, or pass a cancellable [MemoryKVConfig.CleanupContext].

Example

ExampleNewMemoryKV — the in-memory sharded LRU is the default backing store for middleware/session, middleware/csrf, middleware/idempotency and middleware/cache. It implements every optional extension (Scanner, PrefixDeleter, GetAndDeleter, SetNXer) so any middleware that requires one will accept it.

package main

import (
	"context"
	"fmt"
	"time"

	"github.com/goceleris/celeris/middleware/store"
)

func main() {
	kv := store.NewMemoryKV()
	defer kv.Close()

	ctx := context.Background()
	_ = kv.Set(ctx, "session:abc", []byte("payload"), 30*time.Minute)
	v, _ := kv.Get(ctx, "session:abc")
	fmt.Println(string(v))
}
Output:
payload

func (*MemoryKV) Close

func (m *MemoryKV) Close()

Close stops the cleanup goroutine. Safe to call multiple times. After Close, Get/Set/Delete still work but expired entries are not reaped.

func (*MemoryKV) Delete

func (m *MemoryKV) Delete(_ context.Context, key string) error

Delete implements KV.

func (*MemoryKV) DeletePrefix

func (m *MemoryKV) DeletePrefix(_ context.Context, prefix string) error

DeletePrefix implements PrefixDeleter.

func (*MemoryKV) Get

func (m *MemoryKV) Get(_ context.Context, key string) ([]byte, error)

Get implements KV.

func (*MemoryKV) GetAndDelete

func (m *MemoryKV) GetAndDelete(_ context.Context, key string) ([]byte, error)

GetAndDelete implements GetAndDeleter atomically under the shard lock. Returns nil, ErrNotFound on a missing or expired key.

func (*MemoryKV) Scan

func (m *MemoryKV) Scan(_ context.Context, prefix string) ([]string, error)

Scan implements Scanner. Returns all non-expired keys with the given prefix. Scan is a point-in-time snapshot; concurrent Set/Delete may or may not be reflected.

func (*MemoryKV) Set

func (m *MemoryKV) Set(_ context.Context, key string, value []byte, ttl time.Duration) error

Set implements KV. A ttl <= 0 stores the value with no expiry.

func (*MemoryKV) SetNX

func (m *MemoryKV) SetNX(_ context.Context, key string, value []byte, ttl time.Duration) (bool, error)

SetNX implements SetNXer. Acquires the key only if it does not already exist (or has expired). Returns (true, nil) on acquisition, (false, nil) on contention.

type MemoryKVConfig

type MemoryKVConfig struct {
	// Shards is the number of lock shards. Default: runtime.NumCPU(),
	// rounded up to the next power of two.
	Shards int

	// CleanupInterval is how often expired entries are evicted.
	// Default: 1 minute. Zero is replaced with the default.
	CleanupInterval time.Duration

	// CleanupContext, if set, controls the cleanup goroutine lifetime.
	// When the context is cancelled, the goroutine stops. If nil,
	// [MemoryKV.Close] is the only way to stop the goroutine.
	CleanupContext context.Context
}

MemoryKVConfig configures the in-memory MemoryKV store.

type PrefixDeleter

type PrefixDeleter interface {
	DeletePrefix(ctx context.Context, prefix string) error
}

PrefixDeleter is implemented by backends that can atomically (or efficiently) delete all keys matching a prefix. Used by Cache for InvalidatePrefix operations. Backends that do not implement this may emulate it via Scan + Delete, but with weaker atomicity guarantees.

type Scanner

type Scanner interface {
	Scan(ctx context.Context, prefix string) ([]string, error)
}

Scanner is implemented by backends that support key enumeration by prefix (e.g., Redis SCAN). Used by session Reset to delete all keys matching a prefix. When a KV does not implement Scanner, Reset is a documented no-op.

type Scripter

type Scripter interface {
	EvalSHA(ctx context.Context, sha string, keys []string, args ...any) (any, error)
	ScriptLoad(ctx context.Context, script string) (string, error)
}

Scripter is implemented by backends that support server-side atomic scripts (e.g., Redis EVALSHA + ScriptLoad). Used by the ratelimit Redis adapter for atomic token-bucket updates via a Lua script. Most KV backends do not implement Scripter; it is strictly optional.

type SetNXer

type SetNXer interface {
	SetNX(ctx context.Context, key string, value []byte, ttl time.Duration) (acquired bool, err error)
}

SetNXer is implemented by backends that support atomic "set if not exists" semantics (e.g., Redis SET NX, Postgres INSERT ON CONFLICT DO NOTHING). Used by idempotency for lock acquisition. A backend without SetNX cannot serve as an idempotency store.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL