Forge Extension
Mount Chronicle into a Forge application as a first-class extension.
Chronicle ships as a forge.Extension that integrates with the Forge framework's lifecycle, DI container, and router. This is the recommended approach for Forge applications.
Register the extension
import (
"github.com/xraph/chronicle/extension"
"github.com/xraph/chronicle/store/postgres"
)
pgStore, err := postgres.New(pool)
if err != nil {
log.Fatal(err)
}
ext := extension.New(
extension.WithStore(pgStore),
extension.WithCryptoErasure(true),
extension.WithRetentionInterval(24 * time.Hour),
extension.WithArchiveSink(s3Sink),
extension.WithLogger(myLogger),
)
app.Register(ext)app.Register(ext) calls ext.Register(app) which:
- Runs
store.NewAdapterand buildschronicle.New. - Creates
compliance.Engine,retention.Enforcer, andhandler.API. - Registers all 21 HTTP routes in the Forge router (unless
WithDisableRoutes(true)). - Provides
chronicle.Emitterin the Vessel DI container.
app.Start(ctx) calls ext.Start(ctx) which:
- Runs
pgStore.Migrate(ctx)(unlessWithDisableMigrate(true)). - Launches the retention scheduler goroutine.
Receive the Emitter via DI
Other extensions receive the chronicle.Emitter interface from the DI container without importing Chronicle internals:
import (
"github.com/xraph/vessel"
"github.com/xraph/chronicle"
)
type MyExtension struct {
emitter chronicle.Emitter
}
func (e *MyExtension) Register(app forge.App) error {
return vessel.Invoke(app.Container(), func(emitter chronicle.Emitter) {
e.emitter = emitter
})
}
func (e *MyExtension) doSomething(ctx context.Context) {
e.emitter.Info(ctx, "action", "resource", "id").
Category("myservice").
Record()
}Access extension components
If you need direct access to Chronicle internals (e.g. to mount custom routes or run manual enforcement):
// The Chronicle engine for direct record/query
c := ext.Chronicle()
// The Emitter (same as what DI provides)
emitter := ext.Emitter()
// The compliance engine
engine := ext.ComplianceEngine()
// The retention enforcer
enforcer := ext.RetentionEnforcer()
// The handler.API (for manual route registration)
api := ext.API()
// An http.Handler for standalone use
h := ext.Handler()Disable automatic routes
If you want to mount routes on a sub-path or a different router:
ext := extension.New(
extension.WithStore(pgStore),
extension.WithDisableRoutes(true),
)
app.Register(ext)
// Mount manually in your own router
customRouter.Mount("/audit", ext.Handler())Disable automatic migrations
ext := extension.New(
extension.WithStore(pgStore),
extension.WithDisableMigrate(true),
)
// Run migrations yourself before starting
if err := pgStore.Migrate(ctx); err != nil {
log.Fatal(err)
}
app.Register(ext)Grove database integration
When your Forge app uses the Grove extension to manage database connections, Chronicle can automatically resolve a grove.DB from the DI container and construct the correct store backend based on the driver type.
Using the default grove database
If the Grove extension registers a single database (or a default in multi-DB mode), use WithGroveDatabase with an empty name:
ext := extension.New(
extension.WithGroveDatabase(""),
)Using a named grove database
In multi-database setups, reference a specific database by name:
ext := extension.New(
extension.WithGroveDatabase("audit"),
)This resolves the grove.DB named "audit" from the DI container and auto-constructs the matching store. The driver type is detected automatically -- you do not need to import individual store packages.
Using the default grove KV store
If the Grove extension registers a KV store, use WithGroveKV with an empty name:
ext := extension.New(
extension.WithGroveKV(""),
)Using a named grove KV store
In multi-KV setups, reference a specific KV store by name:
ext := extension.New(
extension.WithGroveKV("chronicle-kv"),
)Store resolution order
The extension resolves its store in this order:
- Explicit store -- if
WithStore(s)was called, it is used directly and grove is ignored. - Grove database -- if
WithGroveDatabase(name)was called (orgrove_databaseis set in YAML), the named or defaultgrove.DBis resolved from DI. - Grove KV -- if
WithGroveKV(name)was called (orgrove_kvis set in YAML), the named or default grove KV store is resolved from DI. - In-memory fallback -- if none of the above is configured, an in-memory store is used.
YAML configuration
The Chronicle extension automatically loads configuration from your Forge app's YAML config files. It looks for the key extensions.chronicle first, then falls back to chronicle:
# forge.yaml (or app.yaml, config.yaml, etc.)
extensions:
chronicle:
grove_database: audit
grove_kv: chronicle-kv
batch_size: 100
flush_interval: 1s
crypto_erasure: true
retention_interval: 24h
disable_routes: false
disable_migrate: falseOr at the top level:
chronicle:
grove_database: ""
grove_kv: ""
batch_size: 100
flush_interval: 1s
crypto_erasure: false
retention_interval: 24hConfiguration reference
| Field | YAML key | Type | Default | Description |
|---|---|---|---|---|
GroveDatabase | grove_database | string | "" | Name of the grove.DB to resolve from DI; empty uses the default DB |
GroveKV | grove_kv | string | "" | Name of the grove KV store to resolve from DI; empty uses the default KV |
BatchSize | batch_size | int | 100 | Event batch size |
FlushInterval | flush_interval | duration | 1s | Batch flush interval |
CryptoErasure | crypto_erasure | bool | false | Enable GDPR crypto-erasure |
RetentionInterval | retention_interval | duration | 24h | Retention enforcement interval (0 = off) |
DisableRoutes | disable_routes | bool | false | Skip auto route registration |
DisableMigrate | disable_migrate | bool | false | Skip Migrate on Start |
Full option reference
| Option | Default | Description |
|---|---|---|
WithStore(s) | — | Required. store.Store backend. |
WithBatchSize(n) | 100 | Event batch size. |
WithFlushInterval(d) | 1s | Batch flush interval. |
WithCryptoErasure(b) | false | Enable GDPR crypto-erasure. |
WithRetentionInterval(d) | 24h | Retention enforcement interval (0 = off). |
WithArchiveSink(s) | nil | Archive sink for retention. |
WithLogger(l) | slog.Default() | Logger. |
WithDisableRoutes(b) | false | Skip auto route registration. |
WithDisableMigrate(b) | false | Skip Migrate on Start. |
WithGroveDatabase(name) | "" | Resolve a grove.DB from DI by name; empty uses the default DB. |
WithGroveKV(name) | "" | Resolve a grove KV store from DI by name; empty uses the default KV. |