zoobzio January 27, 2026 Edit this page

API Reference

Complete documentation for grub's public API.

Package grub

import "github.com/zoobz-io/grub"

Errors

Semantic errors returned by all providers.

ErrorDescription
ErrNotFoundRecord does not exist
ErrDuplicateRecord with same key already exists
ErrConflictConcurrent modification conflict
ErrConstraintConstraint violation (FK, check, etc.)
ErrInvalidKeyKey is malformed or empty
ErrReadOnlyWrite attempted on read-only connection
ErrTableExistsTable name already registered
ErrTableNotFoundTable not registered
ErrTTLNotSupportedProvider doesn't support TTL
ErrDimensionMismatchVector dimension doesn't match index
ErrInvalidVectorVector is malformed (nil, empty, NaN)
ErrIndexNotReadyIndex not loaded or initialized
ErrInvalidQueryFilter contains validation errors
ErrOperatorNotSupportedProvider doesn't support filter operator
ErrNoPrimaryKeyNo field has constraints:"primarykey" tag
ErrMultiplePrimaryKeysMultiple fields have primarykey constraint
if errors.Is(err, grub.ErrNotFound) {
    // Handle missing record
}

StoreT

Type-safe key-value store wrapper.

NewStore

func NewStore[T any](provider StoreProvider) *Store[T]

Creates a new Store with JSON codec.

store := grub.NewStore[Session](redis.New(client))

NewStoreWithCodec

func NewStoreWithCodec[T any](provider StoreProvider, codec Codec) *Store[T]

Creates a new Store with custom codec.

store := grub.NewStoreWithCodec[Config](provider, grub.GobCodec{})

Methods

Get

func (s *Store[T]) Get(ctx context.Context, key string) (*T, error)

Retrieves value by key. Returns ErrNotFound if key doesn't exist.

session, err := store.Get(ctx, "session:abc123")

Set

func (s *Store[T]) Set(ctx context.Context, key string, value *T, ttl time.Duration) error

Stores value with optional TTL. TTL=0 means no expiration.

store.Set(ctx, "session:abc123", &session, 24*time.Hour)
store.Set(ctx, "config:app", &config, 0) // No expiration

Delete

func (s *Store[T]) Delete(ctx context.Context, key string) error

Removes key. Returns ErrNotFound if key doesn't exist.

err := store.Delete(ctx, "session:abc123")

Exists

func (s *Store[T]) Exists(ctx context.Context, key string) (bool, error)

Checks if key exists without loading value.

exists, err := store.Exists(ctx, "session:abc123")

List

func (s *Store[T]) List(ctx context.Context, prefix string, limit int) ([]string, error)

Lists keys matching prefix. Limit=0 means no limit.

keys, err := store.List(ctx, "session:", 100)
keys, err := store.List(ctx, "", 0) // All keys

GetBatch

func (s *Store[T]) GetBatch(ctx context.Context, keys []string) (map[string]*T, error)

Retrieves multiple keys. Missing keys are omitted from result (no error).

users, err := store.GetBatch(ctx, []string{"user:1", "user:2"})

SetBatch

func (s *Store[T]) SetBatch(ctx context.Context, items map[string]*T, ttl time.Duration) error

Stores multiple values with same TTL.

items := map[string]*User{"user:1": &alice, "user:2": &bob}
err := store.SetBatch(ctx, items, time.Hour)

Atomic

func (s *Store[T]) Atomic() *atomic.Store[T]

Returns atomic view for field-level access. Lazily initialized, cached. Panics if T is not atomizable.

atomicStore := store.Atomic()
atom, err := atomicStore.Get(ctx, "key")

BucketT

Type-safe blob storage wrapper.

NewBucket

func NewBucket[T any](provider BucketProvider) *Bucket[T]

Creates a new Bucket with JSON codec.

bucket := grub.NewBucket[Document](s3.New(client, "my-bucket"))

NewBucketWithCodec

func NewBucketWithCodec[T any](provider BucketProvider, codec Codec) *Bucket[T]

Creates a new Bucket with custom codec.

Methods

Get

func (b *Bucket[T]) Get(ctx context.Context, key string) (*Object[T], error)

Retrieves object with metadata and payload. Returns ErrNotFound if missing.

obj, err := bucket.Get(ctx, "docs/report.json")
fmt.Println(obj.Data.Title)
fmt.Println(obj.ContentType)

Put

func (b *Bucket[T]) Put(ctx context.Context, obj *Object[T]) error

Stores object with metadata.

err := bucket.Put(ctx, &grub.Object[Document]{
    Key:         "docs/report.json",
    ContentType: "application/json",
    Metadata:    map[string]string{"author": "alice"},
    Data:        Document{Title: "Report"},
})

Delete

func (b *Bucket[T]) Delete(ctx context.Context, key string) error

Removes object. Returns ErrNotFound if missing.

Exists

func (b *Bucket[T]) Exists(ctx context.Context, key string) (bool, error)

Checks if object exists.

List

func (b *Bucket[T]) List(ctx context.Context, prefix string, limit int) ([]ObjectInfo, error)

Lists objects matching prefix. Returns metadata only (no payload).

infos, err := bucket.List(ctx, "docs/", 100)
for _, info := range infos {
    fmt.Printf("%s (%d bytes)\n", info.Key, info.Size)
}

Atomic

func (b *Bucket[T]) Atomic() *atomic.Bucket[T]

Returns atomic view. Panics if T is not atomizable.


DatabaseT

Type-safe SQL database wrapper.

NewDatabase

func NewDatabase[T any](
    db *sqlx.DB,
    table string,
    renderer astql.Renderer,
) (*Database[T], error)

Creates a new Database wrapper.

  • db: Database connection
  • table: Table name
  • renderer: SQL dialect renderer (postgres.New(), mariadb.New(), etc.)

The primary key column is automatically derived from struct tags. Mark the primary key field with constraints:"primarykey":

type User struct {
    ID    int    `db:"id" constraints:"primarykey"`
    Email string `db:"email" constraints:"notnull,unique"`
    Name  string `db:"name"`
}

db := grub.NewDatabase[User](sqlxDB, "users", sqlite.New())

Panics if no field has the primarykey constraint (ErrNoPrimaryKey) or if multiple fields have the constraint (ErrMultiplePrimaryKeys). These are programmer errors — same category as the existing panic in Atomic().

Use the *Tx method variants (GetTx, SetTx, etc.) for transaction support.

Methods

Get

func (d *Database[T]) Get(ctx context.Context, key string) (*T, error)

Retrieves record by primary key. Returns ErrNotFound if missing.

user, err := db.Get(ctx, "123")

Set

func (d *Database[T]) Set(ctx context.Context, key string, value *T) error

Upserts record (insert or update on conflict).

err := db.Set(ctx, "123", &User{ID: "123", Name: "Alice"})

Delete

func (d *Database[T]) Delete(ctx context.Context, key string) error

Removes record. Returns ErrNotFound if missing.

Exists

func (d *Database[T]) Exists(ctx context.Context, key string) (bool, error)

Checks if record exists.

Query Builders

Direct access to soy query builders for ad-hoc queries.

Query

func (d *Database[T]) Query() *soy.Query[T]

Returns a query builder for fetching multiple records.

users, err := db.Query().
    Where("age", ">=", "min_age").
    OrderBy("name", "ASC").
    Limit(10).
    Exec(ctx, map[string]any{"min_age": 18})

Select

func (d *Database[T]) Select() *soy.Select[T]

Returns a select builder for fetching a single record.

user, err := db.Select().
    Where("email", "=", "email").
    Exec(ctx, map[string]any{"email": "alice@example.com"})

Insert

func (d *Database[T]) Insert() *soy.Create[T]

Returns an insert builder (auto-generates PK).

user, err := db.Insert().Exec(ctx, &User{Name: "Alice", Email: "alice@example.com"})

InsertFull

func (d *Database[T]) InsertFull() *soy.Create[T]

Returns an insert builder that includes the PK field.

user, err := db.InsertFull().Exec(ctx, &User{ID: 123, Name: "Alice"})

Modify

func (d *Database[T]) Modify() *soy.Update[T]

Returns an update builder.

updated, err := db.Modify().
    Set("name", "new_name").
    Where("id", "=", "user_id").
    Exec(ctx, map[string]any{"new_name": "Bob", "user_id": 123})

Remove

func (d *Database[T]) Remove() *soy.Delete[T]

Returns a delete builder.

affected, err := db.Remove().
    Where("status", "=", "inactive").
    Exec(ctx, map[string]any{"inactive": "deleted"})

Count

func (d *Database[T]) Count() *soy.Aggregate[T]

Returns an aggregate builder for counting records.

// Count all records
count, err := db.Count().Exec(ctx, nil)

// Count with condition
count, err := db.Count().
    Where("status", "=", "active").
    Exec(ctx, map[string]any{"active": "enabled"})

// Count in transaction
count, err := db.Count().ExecTx(ctx, tx, nil)

Statement Execution

Execute pre-defined edamame statements.

ExecQuery

func (d *Database[T]) ExecQuery(ctx context.Context, stmt edamame.QueryStatement, params map[string]any) ([]*T, error)

Executes a query statement returning multiple records.

users, err := db.ExecQuery(ctx, grub.QueryAll, nil)
users, err := db.ExecQuery(ctx, byRoleStmt, map[string]any{"role": "admin"})

ExecSelect

func (d *Database[T]) ExecSelect(ctx context.Context, stmt edamame.SelectStatement, params map[string]any) (*T, error)

Executes a select statement returning a single record.

user, err := db.ExecSelect(ctx, byEmailStmt, map[string]any{"email": "alice@example.com"})

ExecUpdate

func (d *Database[T]) ExecUpdate(ctx context.Context, stmt edamame.UpdateStatement, params map[string]any) (*T, error)

Executes an update statement returning the modified record.

ExecAggregate

func (d *Database[T]) ExecAggregate(ctx context.Context, stmt edamame.AggregateStatement, params map[string]any) (float64, error)

Executes an aggregate statement.

count, err := db.ExecAggregate(ctx, grub.CountAll, nil)

Transaction Methods

All operations have *Tx variants that accept a transaction as the second parameter.

GetTx

func (d *Database[T]) GetTx(ctx context.Context, tx *sqlx.Tx, key string) (*T, error)

SetTx

func (d *Database[T]) SetTx(ctx context.Context, tx *sqlx.Tx, key string, value *T) error

DeleteTx

func (d *Database[T]) DeleteTx(ctx context.Context, tx *sqlx.Tx, key string) error

ExistsTx

func (d *Database[T]) ExistsTx(ctx context.Context, tx *sqlx.Tx, key string) (bool, error)

ExecQueryTx

func (d *Database[T]) ExecQueryTx(ctx context.Context, tx *sqlx.Tx, stmt edamame.QueryStatement, params map[string]any) ([]*T, error)

ExecSelectTx

func (d *Database[T]) ExecSelectTx(ctx context.Context, tx *sqlx.Tx, stmt edamame.SelectStatement, params map[string]any) (*T, error)

ExecUpdateTx

func (d *Database[T]) ExecUpdateTx(ctx context.Context, tx *sqlx.Tx, stmt edamame.UpdateStatement, params map[string]any) (*T, error)

ExecAggregateTx

func (d *Database[T]) ExecAggregateTx(ctx context.Context, tx *sqlx.Tx, stmt edamame.AggregateStatement, params map[string]any) (float64, error)

Usage Example

tx, err := sqlxDB.BeginTxx(ctx, nil)
if err != nil {
    return err
}
defer tx.Rollback()

user, err := db.GetTx(ctx, tx, "123")
if err != nil {
    return err
}

user.Name = "Updated"
err = db.SetTx(ctx, tx, "123", user)
if err != nil {
    return err
}

return tx.Commit()

Executor

func (d *Database[T]) Executor() *edamame.Executor[T]

Returns the underlying edamame Executor for advanced query operations.

Atomic

func (d *Database[T]) Atomic() AtomicDatabase

Returns atomic view. Panics if T is not atomizable.


IndexT

Type-safe vector storage wrapper.

NewIndex

func NewIndex[T any](provider VectorProvider) *Index[T]

Creates a new Index with JSON codec.

index := grub.NewIndex[Embedding](qdrant.New(client, qdrant.Config{
    Collection: "documents",
}))

NewIndexWithCodec

func NewIndexWithCodec[T any](provider VectorProvider, codec Codec) *Index[T]

Creates a new Index with custom codec.

Methods

Upsert

func (i *Index[T]) Upsert(ctx context.Context, id string, vector []float32, metadata *T) error

Stores or updates a vector with associated metadata. If the ID exists, the vector and metadata are replaced.

index.Upsert(ctx, "doc:1", embedding, &Embedding{Category: "tech"})

UpsertBatch

func (i *Index[T]) UpsertBatch(ctx context.Context, vectors []Vector[T]) error

Stores or updates multiple vectors.

vectors := []grub.Vector[Embedding]{
    {ID: "doc:1", Vector: vec1, Metadata: Embedding{Category: "tech"}},
    {ID: "doc:2", Vector: vec2, Metadata: Embedding{Category: "science"}},
}
err := index.UpsertBatch(ctx, vectors)

Get

func (i *Index[T]) Get(ctx context.Context, id string) (*Vector[T], error)

Retrieves a vector by ID. Returns ErrNotFound if the ID does not exist.

result, err := index.Get(ctx, "doc:1")

Delete

func (i *Index[T]) Delete(ctx context.Context, id string) error

Removes a vector by ID. Returns ErrNotFound if the ID does not exist.

DeleteBatch

func (i *Index[T]) DeleteBatch(ctx context.Context, ids []string) error

Removes multiple vectors by ID. Non-existent IDs are silently ignored.

func (i *Index[T]) Search(ctx context.Context, vector []float32, k int, filter *T) ([]*Vector[T], error)

Performs similarity search and returns the k nearest neighbors. Filter is optional metadata filtering (nil means no filter).

results, err := index.Search(ctx, queryVector, 10, nil)
results, err := index.Search(ctx, queryVector, 10, &Embedding{Category: "tech"})

Query

func (i *Index[T]) Query(ctx context.Context, vector []float32, k int, filter *vecna.Filter) ([]*Vector[T], error)

Performs similarity search with vecna filter support. Returns ErrInvalidQuery if the filter contains validation errors. Returns ErrOperatorNotSupported if the provider doesn't support an operator.

filter := vecna.And(
    vecna.Eq("category", "tech"),
    vecna.Gte("score", 0.8),
)
results, err := index.Query(ctx, queryVector, 10, filter)

Filter

func (i *Index[T]) Filter(ctx context.Context, filter *vecna.Filter, limit int) ([]*Vector[T], error)

Returns vectors matching the metadata filter without similarity search. Result ordering is provider-dependent and not guaranteed by the interface. Limit of 0 returns all matching vectors. Returns ErrFilterNotSupported if the provider cannot perform metadata-only filtering (e.g., Pinecone).

filter := vecna.Eq("category", "tech")
results, err := index.Filter(ctx, filter, 100)

// Nil filter returns all vectors
all, err := index.Filter(ctx, nil, 0)

List

func (i *Index[T]) List(ctx context.Context, prefix string, limit int) ([]string, error)

Returns vector IDs matching the optional prefix. Limit of 0 means no limit.

Exists

func (i *Index[T]) Exists(ctx context.Context, id string) (bool, error)

Checks whether a vector ID exists.

Atomic

func (i *Index[T]) Atomic() *atomic.Index[T]

Returns atomic view for field-level access. Lazily initialized, cached. Panics if T is not atomizable.


Types

ObjectT

Blob object with metadata and typed payload.

type Object[T any] struct {
    Key         string            `json:"key"`
    ContentType string            `json:"content_type"`
    Size        int64             `json:"size"`
    ETag        string            `json:"etag,omitempty"`
    Metadata    map[string]string `json:"metadata,omitempty"`
    Data        T                 `json:"data"`
}

ObjectInfo

Blob metadata without payload (returned by List).

type ObjectInfo struct {
    Key         string
    ContentType string
    Size        int64
    ETag        string
    Metadata    map[string]string
}

VectorT

Vector with typed metadata payload.

type Vector[T any] struct {
    ID       string    `json:"id"`
    Vector   []float32 `json:"vector"`
    Score    float32   `json:"score,omitempty"`
    Metadata T         `json:"metadata"`
}

VectorInfo

Vector metadata returned by providers.

type VectorInfo struct {
    ID        string
    Dimension int
    Score     float32
    Metadata  map[string]any
}

VectorRecord

Batch operation format for vectors.

type VectorRecord struct {
    ID       string
    Vector   []float32
    Metadata map[string]any
}

VectorResult

Search result returned by providers.

type VectorResult struct {
    ID       string
    Vector   []float32
    Metadata map[string]any
    Score    float32
}

AtomicVector

Vector with atomized metadata payload.

type AtomicVector struct {
    ID       string
    Vector   []float32
    Score    float32
    Metadata *atom.Atom
}

Interfaces

StoreProvider

Raw key-value storage interface.

type StoreProvider 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
    Exists(ctx context.Context, key string) (bool, error)
    List(ctx context.Context, prefix string, limit int) ([]string, error)
    GetBatch(ctx context.Context, keys []string) (map[string][]byte, error)
    SetBatch(ctx context.Context, items map[string][]byte, ttl time.Duration) error
}

BucketProvider

Raw blob storage interface.

type BucketProvider interface {
    Get(ctx context.Context, key string) ([]byte, *ObjectInfo, error)
    Put(ctx context.Context, key string, data []byte, info *ObjectInfo) error
    Delete(ctx context.Context, key string) error
    Exists(ctx context.Context, key string) (bool, error)
    List(ctx context.Context, prefix string, limit int) ([]ObjectInfo, error)
}

VectorProvider

Raw vector storage interface.

type VectorProvider interface {
    Upsert(ctx context.Context, id string, vector []float32, metadata map[string]any) error
    UpsertBatch(ctx context.Context, vectors []VectorRecord) error
    Get(ctx context.Context, id string) ([]float32, *VectorInfo, error)
    Delete(ctx context.Context, id string) error
    DeleteBatch(ctx context.Context, ids []string) error
    Search(ctx context.Context, vector []float32, k int, filter map[string]any) ([]VectorResult, error)
    Query(ctx context.Context, vector []float32, k int, filter *vecna.Filter) ([]VectorResult, error)
    Filter(ctx context.Context, filter *vecna.Filter, limit int) ([]VectorResult, error)
    List(ctx context.Context, prefix string, limit int) ([]string, error)
    Exists(ctx context.Context, id string) (bool, error)
}

BeforeSave

Called before persisting T. Return an error to abort the write.

type BeforeSave interface {
    BeforeSave(ctx context.Context) error
}

AfterSave

Called after T has been successfully persisted.

type AfterSave interface {
    AfterSave(ctx context.Context) error
}

AfterLoad

Called after T has been loaded and decoded.

type AfterLoad interface {
    AfterLoad(ctx context.Context) error
}

BeforeDelete

Called before deleting a record. Invoked on a zero-value T (no loaded state).

type BeforeDelete interface {
    BeforeDelete(ctx context.Context) error
}

AfterDelete

Called after a record has been successfully deleted. Invoked on a zero-value T (no loaded state).

type AfterDelete interface {
    AfterDelete(ctx context.Context) error
}

Codec

Serialization interface.

type Codec interface {
    Encode(v any) ([]byte, error)
    Decode(data []byte, v any) error
}

AtomicStore

Type-agnostic key-value access.

type AtomicStore interface {
    Spec() atom.Spec
    Get(ctx context.Context, key string) (*atom.Atom, error)
    Set(ctx context.Context, key string, a *atom.Atom, ttl time.Duration) error
    Delete(ctx context.Context, key string) error
    Exists(ctx context.Context, key string) (bool, error)
}

AtomicBucket

Type-agnostic blob access.

type AtomicBucket interface {
    Spec() atom.Spec
    Get(ctx context.Context, key string) (*AtomicObject, error)
    Put(ctx context.Context, key string, obj *AtomicObject) error
    Delete(ctx context.Context, key string) error
    Exists(ctx context.Context, key string) (bool, error)
}

AtomicDatabase

Type-agnostic SQL access.

type AtomicDatabase interface {
    Table() string
    Spec() atom.Spec
    Get(ctx context.Context, key string) (*atom.Atom, error)
    Set(ctx context.Context, key string, a *atom.Atom) error
    Delete(ctx context.Context, key string) error
    Exists(ctx context.Context, key string) (bool, error)
    ExecQuery(ctx context.Context, stmt edamame.QueryStatement, params map[string]any) ([]*atom.Atom, error)
    ExecSelect(ctx context.Context, stmt edamame.SelectStatement, params map[string]any) (*atom.Atom, error)
}

AtomicIndex

Type-agnostic vector access.

type AtomicIndex interface {
    Spec() atom.Spec
    Get(ctx context.Context, id string) (*AtomicVector, error)
    Upsert(ctx context.Context, id string, vector []float32, metadata *atom.Atom) error
    Delete(ctx context.Context, id string) error
    Exists(ctx context.Context, id string) (bool, error)
    Search(ctx context.Context, vector []float32, k int, filter *atom.Atom) ([]AtomicVector, error)
    Query(ctx context.Context, vector []float32, k int, filter *vecna.Filter) ([]AtomicVector, error)
    Filter(ctx context.Context, filter *vecna.Filter, limit int) ([]AtomicVector, error)
}

Codecs

JSONCodec

Default codec using encoding/json.

type JSONCodec struct{}

GobCodec

Binary codec using encoding/gob.

type GobCodec struct{}

Usage

// Default (JSON)
store := grub.NewStore[Config](provider)

// Custom codec
store := grub.NewStoreWithCodec[Config](provider, grub.GobCodec{})