diff --git a/.gitea/CODEOWNERS b/.gitea/CODEOWNERS new file mode 100644 index 0000000..ae1f67b --- /dev/null +++ b/.gitea/CODEOWNERS @@ -0,0 +1 @@ +* @go/CoreDevelopers @go/Agents diff --git a/CHANGELOG.md b/CHANGELOG.md index d0e5861..e677520 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,29 @@ All notable changes to this module will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this module adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.0.0] - 2026-05-12 + +### Added + +- `authClient *auth.Client` cached in `OnInit` — the Auth client is now initialised once + during startup and reused in `HealthCheck`, eliminating a per-probe `app.Auth(ctx)` call. + +### Changed + +- Go directive bumped from 1.25 to 1.26 +- `health`, `launcher`, and `logz` dependencies bumped to v1.0.1 +- `xerrors v1.0.1` added — all error returns now use structured `xerrors.Err` instead of + `fmt.Errorf`; `OnInit` returns `ErrInvalidInput` for missing `ProjectID` and + `ErrInternal` for SDK initialisation failures; `HealthCheck` returns `ErrInternal` when + called before `OnInit` + +### Stabilization + +- API committed as stable. `Config`, `Provider`, `Component`, and `New` are unchanged + from v0.9.0. + +[1.0.0]: https://code.nochebuena.dev/go/firebase/compare/v0.9.0...v1.0.0 + ## [0.9.0] - 2026-03-18 ### Added diff --git a/COMMIT.md b/COMMIT.md new file mode 100644 index 0000000..d446a3a --- /dev/null +++ b/COMMIT.md @@ -0,0 +1,6 @@ +feat(firebase)!: promote to v1.0.0 — cache auth.Client, adopt xerrors, bump deps to v1 + +Cache auth.Client in OnInit (eliminates per-probe app.Auth call in HealthCheck). +Replace all fmt.Errorf with xerrors structured errors (ErrInvalidInput / ErrInternal). +Bump health/launcher/logz to v1.0.1, add xerrors v1.0.1, Go directive to 1.26. +API committed as stable. diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 0000000..10bb755 --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,55 @@ +# v1.0.0 + +> `code.nochebuena.dev/go/firebase` + +## Overview + +`firebase` manages the lifecycle of a `firebase.google.com/go/v4` App: validates +configuration at init time, initialises the SDK App, exposes the native `*firebase.App` +to consumers, and performs health checks by probing the Firebase Auth service. + +v1.0.0 caches the `auth.Client` inside the component (eliminating per-probe allocation), +adopts `xerrors` for all structured error returns, bumps all micro-lib dependencies to v1, +and commits the API as stable. + +## What Changed Since v0.9.0 + +### Auth client cached in `OnInit` + +`OnInit` now calls `app.Auth(context.Background())` once and stores the result on the +component. `HealthCheck` uses the cached client directly — no per-probe `app.Auth(ctx)` +call. + +### Structured errors via `xerrors` + +All `fmt.Errorf` calls replaced with `xerrors`: + +| Situation | Code | +|-----------|------| +| `ProjectID` empty | `ErrInvalidInput` | +| `fb.NewApp` fails | `ErrInternal` (wraps SDK error) | +| `app.Auth` fails | `ErrInternal` (wraps SDK error) | +| `HealthCheck` before `OnInit` | `ErrInternal` | + +### Dependency updates + +- `health`, `launcher`, `logz` bumped from `v0.9.x` to `v1.0.1` +- `xerrors v1.0.1` added as direct dependency +- Go directive bumped from `1.25` to `1.26` + +## Full API (stable) + +- `Config` — `ProjectID string` (`FIREBASE_PROJECT_ID`, required) +- `Provider` — `App() *fb.App` +- `Component` — embeds `launcher.Component` + `health.Checkable` + `Provider` +- `New(logger, cfg) Component` + +## Installation + +``` +go get code.nochebuena.dev/go/firebase@v1.0.0 +``` + +## Changelog + +See [CHANGELOG.md](CHANGELOG.md#100---2026-05-12). diff --git a/firebase.go b/firebase.go index d4699ee..441e9d3 100644 --- a/firebase.go +++ b/firebase.go @@ -2,7 +2,6 @@ package firebase import ( "context" - "fmt" fb "firebase.google.com/go/v4" "firebase.google.com/go/v4/auth" @@ -10,6 +9,7 @@ import ( "code.nochebuena.dev/go/health" "code.nochebuena.dev/go/launcher" "code.nochebuena.dev/go/logz" + "code.nochebuena.dev/go/xerrors" ) // Provider is the minimal interface for consumers that only need the firebase App. @@ -31,9 +31,10 @@ type Config struct { } type firebaseComponent struct { - cfg Config - logger logz.Logger - app *fb.App + cfg Config + logger logz.Logger + app *fb.App + authClient *auth.Client } // New returns a firebase Component. Call lc.Append(fb) to manage its lifecycle. @@ -43,7 +44,7 @@ func New(logger logz.Logger, cfg Config) Component { func (f *firebaseComponent) OnInit() error { if f.cfg.ProjectID == "" { - return fmt.Errorf("firebase: ProjectID is required") + return xerrors.New(xerrors.ErrInvalidInput, "firebase: ProjectID is required") } f.logger.Info("firebase: initializing app", "project_id", f.cfg.ProjectID) app, err := fb.NewApp(context.Background(), &fb.Config{ @@ -51,9 +52,14 @@ func (f *firebaseComponent) OnInit() error { }) if err != nil { f.logger.Error("firebase: failed to create app", err) - return fmt.Errorf("firebase: create app: %w", err) + return xerrors.Wrap(xerrors.ErrInternal, "firebase: create app", err) } f.app = app + authClient, err := app.Auth(context.Background()) + if err != nil { + return xerrors.Wrap(xerrors.ErrInternal, "firebase: get auth client", err) + } + f.authClient = authClient return nil } @@ -70,14 +76,10 @@ func (f *firebaseComponent) OnStop() error { 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") + if f.authClient == nil { + return xerrors.New(xerrors.ErrInternal, "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") + _, err := f.authClient.GetUser(ctx, "health-probe-non-existent") if err != nil { if auth.IsUserNotFound(err) { return nil // expected: probe of non-existent UID succeeded diff --git a/go.mod b/go.mod index d2dfb61..1a6932d 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,11 @@ module code.nochebuena.dev/go/firebase -go 1.25 +go 1.26 require ( - code.nochebuena.dev/go/health v0.9.0 - code.nochebuena.dev/go/launcher v0.9.0 - code.nochebuena.dev/go/logz v0.9.0 + code.nochebuena.dev/go/health v1.0.1 + code.nochebuena.dev/go/launcher v1.0.1 + code.nochebuena.dev/go/logz v1.0.1 firebase.google.com/go/v4 v4.15.0 ) @@ -17,6 +17,7 @@ require ( cloud.google.com/go/iam v1.1.7 // indirect cloud.google.com/go/longrunning v0.5.5 // indirect cloud.google.com/go/storage v1.40.0 // indirect + code.nochebuena.dev/go/xerrors v1.0.1 // indirect github.com/MicahParks/keyfunc v1.9.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.1 // indirect diff --git a/go.sum b/go.sum index 25f218d..40d63b7 100644 --- a/go.sum +++ b/go.sum @@ -13,12 +13,14 @@ cloud.google.com/go/longrunning v0.5.5 h1:GOE6pZFdSrTb4KAiKnXsJBtlE6mEyaW44oKyMI cloud.google.com/go/longrunning v0.5.5/go.mod h1:WV2LAxD8/rg5Z1cNW6FJ/ZpX4E4VnDnoTk0yawPBB7s= cloud.google.com/go/storage v1.40.0 h1:VEpDQV5CJxFmJ6ueWNsKxcr1QAYOXEgxDa+sBbJahPw= cloud.google.com/go/storage v1.40.0/go.mod h1:Rrj7/hKlG87BLqDJYtwR0fbPld8uJPbQ2ucUMY7Ir0g= -code.nochebuena.dev/go/health v0.9.0 h1:x0UKjC7CHAE3AgwyFzCyjmGJIjoLBBxeOHxXuqpbKwI= -code.nochebuena.dev/go/health v0.9.0/go.mod h1:f3IsNtU60JSn5yXmBBh9XOvr5pRyEah5+wS4tjDQZso= -code.nochebuena.dev/go/launcher v0.9.0 h1:dJHonA9Xm03AQKK0919FJaQn9ZKHZ+RZfB9yxjnx3TA= -code.nochebuena.dev/go/launcher v0.9.0/go.mod h1:IBtntmbnyddukjEhxlc7Ysdzz9nZsnd9+8FzAIHt77g= -code.nochebuena.dev/go/logz v0.9.0 h1:wfV7vtI4V/8ED7Hm31Fbql7Y5iOGrlHN4X8Z5ajTZZE= -code.nochebuena.dev/go/logz v0.9.0/go.mod h1:qODhSbKb+tWE7rdhHLcKweiP5CgwIaWoZxadCT3bQV8= +code.nochebuena.dev/go/health v1.0.1 h1:FwB1sDa9oZBPgIi3kNMUjTVRKQ/Yn77WvlrwNtgFlws= +code.nochebuena.dev/go/health v1.0.1/go.mod h1:sy/HVT+E+k2rEuqmW9Q8QX0QnotAQxPHXSexYs7GNMg= +code.nochebuena.dev/go/launcher v1.0.1 h1:hbPV8jNtyxfchrT7igzz3M2tKGI3bm8uWkHBXRvSPgg= +code.nochebuena.dev/go/launcher v1.0.1/go.mod h1:1KwndVuqm31JN9Dpl9YvOmlogPlKKzoDMo9aRFkYwmM= +code.nochebuena.dev/go/logz v1.0.1 h1:kK9aZo19L208CwCr2D/dbSOMaOv62cXsigMSsdFu+8Y= +code.nochebuena.dev/go/logz v1.0.1/go.mod h1:YNpNm03fURm2v0ySh/477z9AJhtfRcd9rFOW6fFqgNM= +code.nochebuena.dev/go/xerrors v1.0.1 h1:fgXoabY/ZwxAzaM1sKFf3sbL7ZHWyDxItB/rdbnl0mo= +code.nochebuena.dev/go/xerrors v1.0.1/go.mod h1:03MMVfrhaf4XmTMgMrEUFCmuZPGHUCKDitiQvwCuwvY= firebase.google.com/go/v4 v4.15.0 h1:k27M+cHbyN1YpBI2Cf4NSjeHnnYRB9ldXwpqA5KikN0= firebase.google.com/go/v4 v4.15.0/go.mod h1:S/4MJqVZn1robtXkHhpRUbwOC4gdYtgsiMMJQ4x+xmQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=