---
title: "Forge Extension"
description: "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

```go
import (
    "github.com/xraph/forge"
    troveext "github.com/xraph/trove/extension"
)

app := forge.New(
    forge.WithExtensions(
        troveext.New(),
    ),
)
```

With a YAML config:

```yaml
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`:

```yaml
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

```go
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

```yaml
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

```go
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:

```go
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.Trove`** for each store via `vessel.ProvideNamed`
- **`*TroveManager`** for accessing all stores

```go
// 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:

```go
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:

```go
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:

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