Trove

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, xxhash

Programmatic 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: primary

Each 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:

FieldDescription
nameUnique identifier for the store (required)
storage_driverDSN for the storage backend (required)
grove_databaseNamed Grove DB for metadata
default_bucketDefault bucket name
enable_casEnable content-addressable storage
enable_encryptionAuto-configure encryption middleware
enable_compressionAuto-configure compression middleware

DI Integration (Multi-Store)

In multi-store mode, Trove registers:

  • Default *trove.Trove (unnamed) for backward-compatible injection
  • Named *trove.Trove for each store via vessel.ProvideNamed
  • *TroveManager for 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

MethodDescription
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:

  1. Load and merge configuration (YAML + programmatic + defaults)
  2. Resolve *grove.DB from DI (named or default)
  3. Run migrations (creates 5 tables + indexes)
  4. Open the storage driver from driver_dsn
  5. Create the *trove.Trove instance with middleware pipeline
  6. Register HTTP routes on fapp.Router()
  7. Provide *trove.Trove to DI via vessel.Provide()

Multi-store mode:

  1. Load and merge configuration, validate store entries
  2. For each store: resolve storage driver, resolve Grove DB, build metadata store, run migrations, create *trove.Trove
  3. Determine default store (explicit > config > first entry)
  4. Register HTTP routes
  5. 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_database is 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:

ServiceWhat 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.

On this page