Forge Extension
Integrating Trove into a Forge application with auto-discovery, DI, config, and lifecycle management.
The Trove Forge extension adapts the core object storage engine as a first-class Forge extension. It handles configuration loading, Grove database resolution, automatic migrations, route registration, and DI provision.
Quick Start
import (
"github.com/xraph/forge"
troveext "github.com/xraph/trove/extension"
)
app := forge.New(
forge.WithExtensions(
troveext.New(),
),
)With a YAML config:
extensions:
trove:
driver_dsn: "file:///data/storage"
grove_database: "default"
base_path: "/trove/v1"Configuration
The extension supports a three-phase config merge: YAML file, programmatic options, then defaults.
YAML Keys
The extension checks extensions.trove first, then falls back to trove:
extensions:
trove:
driver_dsn: "file:///data/storage" # Storage driver DSN
grove_database: "default" # Named Grove DB from DI
base_path: "/trove/v1" # HTTP route prefix
disable_routes: false # Skip HTTP route registration
disable_migrate: false # Skip auto-migration
default_bucket: "default" # Default bucket name
cas_algorithm: "sha256" # CAS hash: sha256, blake3, xxhashProgrammatic Options
troveext.New(
troveext.WithBasePath("/storage/v1"),
troveext.WithConfig(troveext.Config{
DriverDSN: "file:///data/storage",
DefaultBucket: "uploads",
}),
troveext.WithDisableRoutes(),
troveext.WithDisableMigrate(),
troveext.WithGroveDatabase("analytics-db"),
)Multi-Store Mode
Trove supports multiple named file stores, each with its own storage driver and metadata database. This mirrors how Grove supports multiple named databases.
YAML Configuration
extensions:
trove:
base_path: "/trove/v1"
stores:
- name: primary
storage_driver: "s3://us-east-1/my-bucket"
grove_database: "primary"
default_bucket: "uploads"
enable_compression: true
- name: archive
storage_driver: "file:///data/archive"
grove_database: "archive-db"
default_bucket: "archive"
default: primaryEach store entry creates an independent *trove.Trove instance with its own storage driver, metadata database, middleware, and default bucket. The default field sets which store is used for unnamed DI injection.
Programmatic Options
troveext.New(
troveext.WithFileStoreDSN("primary", "s3://us-east-1/my-bucket"),
troveext.WithFileStoreDSN("archive", "file:///data/archive"),
troveext.WithDefaultFileStore("primary"),
troveext.WithTroveOptionFor("primary", trove.WithDefaultBucket("uploads")),
)Or with pre-configured drivers:
troveext.New(
troveext.WithFileStore("primary", s3Driver),
troveext.WithFileStore("archive", localDriver),
troveext.WithDefaultFileStore("primary"),
)FileStoreConfig Fields
Each store entry accepts these fields:
| Field | Description |
|---|---|
name | Unique identifier for the store (required) |
storage_driver | DSN for the storage backend (required) |
grove_database | Named Grove DB for metadata |
default_bucket | Default bucket name |
enable_cas | Enable content-addressable storage |
enable_encryption | Auto-configure encryption middleware |
enable_compression | Auto-configure compression middleware |
DI Integration (Multi-Store)
In multi-store mode, Trove registers:
- Default
*trove.Trove(unnamed) for backward-compatible injection - Named
*trove.Trovefor each store viavessel.ProvideNamed *TroveManagerfor accessing all stores
// Inject the default store
t, err := vessel.Inject[*trove.Trove](fapp.Container())
// Inject a named store
archive, err := vessel.InjectNamed[*trove.Trove](fapp.Container(), "archive")
// Inject the manager for full access
mgr, err := vessel.Inject[*troveext.TroveManager](fapp.Container())
primary, _ := mgr.Get("primary")TroveManager API
| Method | Description |
|---|---|
Get(name) | Get a named *trove.Trove instance |
GetStore(name) | Get a named metadata store |
Default() | Get the default *trove.Trove |
DefaultStore() | Get the default metadata store |
DefaultName() | Name of the default store |
SetDefault(name) | Change the default |
All() | Map of all name-to-Trove instances |
Len() | Number of registered stores |
Accessor Methods
The extension provides backward-compatible accessors:
ext.Trove() // Default *trove.Trove (works in both single and multi-store)
ext.Store() // Default metadata store
ext.Manager() // *TroveManager (nil in single-store mode)Lifecycle
The extension follows Forge's lifecycle:
Register
Single-store mode:
- Load and merge configuration (YAML + programmatic + defaults)
- Resolve
*grove.DBfrom DI (named or default) - Run migrations (creates 5 tables + indexes)
- Open the storage driver from
driver_dsn - Create the
*trove.Troveinstance with middleware pipeline - Register HTTP routes on
fapp.Router() - Provide
*trove.Troveto DI viavessel.Provide()
Multi-store mode:
- Load and merge configuration, validate store entries
- For each store: resolve storage driver, resolve Grove DB, build metadata store, run migrations, create
*trove.Trove - Determine default store (explicit > config > first entry)
- Register HTTP routes
- Provide
*TroveManager, default*trove.Trove, and named instances to DI
Start / Stop
Start marks the extension as running. Stop closes all underlying storage drivers (all stores in multi-store mode).
Health
In single-store mode, pings the metadata database. In multi-store mode, health-checks all registered stores.
DI Integration
After registration, any component can inject Trove:
import (
"github.com/xraph/trove"
"github.com/xraph/vessel"
)
t, err := vessel.Inject[*trove.Trove](fapp.Container())Grove Database Resolution
The extension resolves a Grove database from DI for its metadata store:
- If
grove_databaseis set, looks up a named DB:vessel.InjectNamed[*grove.DB](container, name) - Otherwise, uses the default:
vessel.Inject[*grove.DB](container) - Supports PostgreSQL, SQLite, and MongoDB backends via Grove's driver detection
Auto-Discovery
The extension auto-detects ecosystem services from DI:
| Service | What Happens |
|---|---|
Vault (store.Store) | Encryption middleware auto-configured with Vault-managed keys |
Chronicle (chronicle.Emitter) | Audit events emitted for all storage operations |
Dispatch (*engine.Engine) | Background jobs registered (cleanup, GC) |
Warden (*warden.Engine) | Authorization middleware applied to routes |
All auto-discovery is optional. If a service isn't in DI, it's silently skipped.