Skip to content

zoobz-io/grub

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

33 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

grub

CI codecov Go Report Card CodeQL Go Reference License Go Version Release

Provider-agnostic storage for Go.

Type-safe CRUD across key-value stores, blob storage, SQL databases, and vector similarity search with a unified interface.

One Interface, Any Backend

// Same code, different providers
sessions := grub.NewStore[Session](redis.New(client))
sessions := grub.NewStore[Session](badger.New(db))
sessions := grub.NewStore[Session](bolt.New(db, "sessions"))

// Type-safe operations
session, _ := sessions.Get(ctx, "session:abc")
sessions.Set(ctx, "session:xyz", &Session{UserID: "123"}, time.Hour)

Swap Redis for BadgerDB. Move from S3 to Azure. Switch databases from SQLite to PostgreSQL. Your business logic stays the same.

// Key-value, blob, SQL, or vector — same patterns
store := grub.NewStore[Config](provider)           // key-value
bucket := grub.NewBucket[Document](provider)       // blob storage
db := grub.NewDatabase[User](conn, "users", renderer)            // SQL
index := grub.NewIndex[Embedding](provider)        // vector search

Four storage modes, consistent API, semantic errors across all providers.

Install

go get github.com/zoobz-io/grub

Requires Go 1.24+.

Quick Start

package main

import (
    "context"
    "fmt"
    "time"

    "github.com/zoobz-io/grub"
    "github.com/zoobz-io/grub/redis"
    goredis "github.com/redis/go-redis/v9"
)

type Session struct {
    UserID    string `json:"user_id"`
    Token     string `json:"token"`
    ExpiresAt int64  `json:"expires_at"`
}

func main() {
    ctx := context.Background()

    // Connect to Redis
    client := goredis.NewClient(&goredis.Options{Addr: "localhost:6379"})
    defer client.Close()

    // Create type-safe store
    sessions := grub.NewStore[Session](redis.New(client))

    // Store with TTL
    session := &Session{
        UserID:    "user:123",
        Token:     "abc123",
        ExpiresAt: time.Now().Add(24 * time.Hour).Unix(),
    }
    _ = sessions.Set(ctx, "session:abc123", session, 24*time.Hour)

    // Retrieve
    s, _ := sessions.Get(ctx, "session:abc123")
    fmt.Println(s.UserID) // user:123
}

Capabilities

Feature Description Docs
Key-Value Store Sessions, cache, config with optional TTL Providers
Blob Storage Files and documents with metadata Lifecycle
SQL Database Structured records with query capabilities Concepts
Vector Search Similarity search with metadata filtering Providers
Atomic Views Field-level access for encryption pipelines Architecture
Semantic Errors ErrNotFound, ErrDuplicate across all providers API Reference
Custom Codecs JSON default, Gob available, or bring your own Concepts

Why grub?

  • Type-safe — Generics eliminate runtime type assertions
  • Swap backends — Change providers without touching business logic
  • Consistent errors — Same error types whether you're using Redis or S3
  • Atomic views — Field-level access for framework internals (encryption, pipelines)
  • Isolated dependencies — Each provider is a separate module; only pull what you use

Storage Without Coupling

Grub enables a pattern: define storage once, swap implementations freely.

Your domain code works with typed stores. Infrastructure decisions — Redis vs embedded, S3 vs local filesystem, PostgreSQL vs SQLite, Pinecone vs Qdrant — become configuration, not architecture.

// Domain code doesn't know or care about the backend
type SessionStore struct {
    store *grub.Store[Session]
}

func (s *SessionStore) Save(ctx context.Context, session *Session) error {
    return s.store.Set(ctx, "session:"+session.Token, session, 24*time.Hour)
}

// Production: Redis
store := grub.NewStore[Session](redis.New(redisClient))

// Development: embedded BadgerDB
store := grub.NewStore[Session](badger.New(localDB))

// Testing: in-memory
store := grub.NewStore[Session](badger.New(memDB))

One interface. Any backend. Zero vendor lock-in.

Documentation

  • Overview — Design philosophy and architecture

Learn

Guides

  • Providers — Setup and configuration for all backends
  • Lifecycle — CRUD operations and batch processing
  • Pagination — Listing and iterating large datasets
  • Testing — Mocks, embedded DBs, testcontainers
  • Best Practices — Key design, error handling, performance

Cookbook

  • Caching — Cache-aside, read-through, TTL strategies
  • Migrations — Switching providers without downtime
  • Multi-Tenant — Tenant isolation patterns

Reference

  • API — Complete API documentation
  • Providers — Provider-specific behaviors and limitations

Contributing

See CONTRIBUTING.md for guidelines.

License

MIT License — see LICENSE for details.