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:
| Layer | Stores | Description |
|---|---|---|
Grove ORM (grove.DB) | PostgreSQL, SQLite, MongoDB | Struct-based models with query builders and the Grove migrator |
Grove KV (kv.Store) | Redis | Key-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 driver | Store package |
|---|---|
pg | store/postgres |
sqlite | store/sqlite |
mongo | store/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 KVOr via YAML:
extensions:
relay:
grove_database: webhooks
grove_kv: relay-cacheStore resolution order
The extension resolves its store in this order:
- Explicit store -- if
relay.WithStore(s)was passed as an option, it is used directly. - Grove database -- if
WithGroveDatabasewas called (orgrove_databaseis set in YAML), the named or defaultgrove.DBis resolved from DI and the driver type determines the store. - Grove KV -- if
WithGroveKVwas called (orgrove_kvis set in YAML), the named or defaultkv.Storeis resolved from DI. - Fallback -- if none of the above is configured, no store is set and
relay.New()returnsErrNoStoreunless 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
_migrationstable - Up/down migration functions
- Orchestrated execution with locking
The Redis store does not require migrations (keys are created on demand).