feat(firebase): initial stable release v0.9.0

Firebase App component with launcher lifecycle and health check integration.

What's included:
- Config with ProjectID (FIREBASE_PROJECT_ID env var); credentials via ADC
- Provider interface exposing native *firebase.App directly
- Component interface: launcher.Component + health.Checkable + Provider
- New(logger, cfg) constructor for lifecycle registration via lc.Append
- Health check via GetUser("health-probe-non-existent") + auth.IsUserNotFound at LevelCritical
- No-op OnStop (Firebase Admin SDK has no Close method)

Tested-via: todo-api POC integration
Reviewed-against: docs/adr/
This commit is contained in:
2026-03-19 13:35:40 +00:00
commit 3ef30c2354
13 changed files with 741 additions and 0 deletions

91
firebase.go Normal file
View File

@@ -0,0 +1,91 @@
package firebase
import (
"context"
"fmt"
fb "firebase.google.com/go/v4"
"firebase.google.com/go/v4/auth"
"code.nochebuena.dev/go/health"
"code.nochebuena.dev/go/launcher"
"code.nochebuena.dev/go/logz"
)
// Provider is the minimal interface for consumers that only need the firebase App.
type Provider interface {
App() *fb.App
}
// Component adds lifecycle management and health check to Provider.
type Component interface {
launcher.Component
health.Checkable
Provider
}
// Config holds Firebase configuration.
type Config struct {
// ProjectID is the Google Cloud Project ID.
ProjectID string `env:"FIREBASE_PROJECT_ID,required"`
}
type firebaseComponent struct {
cfg Config
logger logz.Logger
app *fb.App
}
// New returns a firebase Component. Call lc.Append(fb) to manage its lifecycle.
func New(logger logz.Logger, cfg Config) Component {
return &firebaseComponent{cfg: cfg, logger: logger}
}
func (f *firebaseComponent) OnInit() error {
if f.cfg.ProjectID == "" {
return fmt.Errorf("firebase: ProjectID is required")
}
f.logger.Info("firebase: initializing app", "project_id", f.cfg.ProjectID)
app, err := fb.NewApp(context.Background(), &fb.Config{
ProjectID: f.cfg.ProjectID,
})
if err != nil {
f.logger.Error("firebase: failed to create app", err)
return fmt.Errorf("firebase: create app: %w", err)
}
f.app = app
return nil
}
func (f *firebaseComponent) OnStart() error {
f.logger.Info("firebase: app ready")
return nil
}
func (f *firebaseComponent) OnStop() error {
f.logger.Info("firebase: shutting down")
return nil
}
func (f *firebaseComponent) App() *fb.App { return f.app }
func (f *firebaseComponent) HealthCheck(ctx context.Context) error {
if f.app == nil {
return fmt.Errorf("firebase: app not initialized")
}
authClient, err := f.app.Auth(ctx)
if err != nil {
return fmt.Errorf("firebase: get auth client: %w", err)
}
_, err = authClient.GetUser(ctx, "health-probe-non-existent")
if err != nil {
if auth.IsUserNotFound(err) {
return nil // expected: probe of non-existent UID succeeded
}
return err
}
return nil
}
func (f *firebaseComponent) Name() string { return "firebase" }
func (f *firebaseComponent) Priority() health.Level { return health.LevelCritical }