Relay

Grove Integration

How Relay stores use Grove ORM and Grove KV for database access.

All Relay persistence backends are built on Grove -- the ORM and KV abstraction layer used across the Forge ecosystem. This means every store follows the same patterns for connection management, migrations, and DI integration.

Architecture

Relay's stores fall into two categories based on the Grove layer they use:

LayerStoresDescription
Grove ORM (grove.DB)PostgreSQL, SQLite, MongoDBStruct-based models with query builders and the Grove migrator
Grove KV (kv.Store)RedisKey-value storage with JSON-serialized entities and sorted set indexes

Constructor pattern

Every store accepts a Grove connection and returns a store.Store:

// SQL stores accept *grove.DB
pgStore    := pgstore.New(db)
sqlStore   := sqlitestore.New(db)
mongoStore := mongostore.New(db)

// KV stores accept *kv.Store
redisStore := redisstore.New(kvStore)

Auto-detection with Forge

When using the Forge extension, you don't need to import individual store packages. The extension resolves a grove.DB or kv.Store from the DI container and auto-constructs the correct store based on the driver type:

// Detects pg/sqlite/mongo driver and builds the right store
app.Use(extension.New(
    extension.WithGroveDatabase(""),
))

// Or for Redis via KV
app.Use(extension.New(
    extension.WithGroveKV(""),
))

The driver detection maps:

Grove driverStore package
pgstore/postgres
sqlitestore/sqlite
mongostore/mongo
Redis (via KV)store/redis

Named databases

In multi-database Forge apps, reference a specific Grove database by name:

extension.WithGroveDatabase("webhooks")   // named DB
extension.WithGroveKV("relay-cache")      // named KV

Or via YAML:

extensions:
  relay:
    grove_database: webhooks
    grove_kv: relay-cache

Store resolution order

The extension resolves its store in this order:

  1. Explicit store -- if relay.WithStore(s) was passed as an option, it is used directly.
  2. Grove database -- if WithGroveDatabase was called (or grove_database is set in YAML), the named or default grove.DB is resolved from DI and the driver type determines the store.
  3. Grove KV -- if WithGroveKV was called (or grove_kv is set in YAML), the named or default kv.Store is resolved from DI.
  4. Fallback -- if none of the above is configured, no store is set and relay.New() returns ErrNoStore unless a store was provided via other means.

Migrations

All Grove ORM stores use the Grove migrator (grove/migrate). Each store registers a migration group named "relay":

var Migrations = migrate.NewGroup("relay")

The Grove migrator supports:

  • Version tracking in a _migrations table
  • Up/down migration functions
  • Orchestrated execution with locking

The Redis store does not require migrations (keys are created on demand).

On this page