Documentation
¶
Overview ¶
Package flags provides feature flags stored in the app's own database: typed flags declared in code, per-user evaluation with an allow-list, roles and sticky percentage rollout, and an in-memory cache kept fresh by database change listeners. See docs/proposed-features/feature-flags.md.
Index ¶
Constants ¶
const Channel = "hamr_feature_flags"
Channel is the Postgres NOTIFY channel the trigger publishes on.
const PostgresTriggerSQL = `CREATE OR REPLACE FUNCTION hamr_feature_flags_notify() RETURNS trigger AS $$ BEGIN PERFORM pg_notify('` + Channel + `', ''); RETURN NULL; END; $$ LANGUAGE plpgsql; DROP TRIGGER IF EXISTS hamr_feature_flags_notify ON ` + Table + `; CREATE TRIGGER hamr_feature_flags_notify AFTER INSERT OR UPDATE OR DELETE OR TRUNCATE ON ` + Table + ` FOR EACH STATEMENT EXECUTE FUNCTION hamr_feature_flags_notify();`
PostgresTriggerSQL creates the trigger that NOTIFYs Channel on every change to Table. It is idempotent and multi-statement. The scaffold's migration inlines the same SQL.
const Table = "feature_flags"
Table is the flags table the scaffold creates.
Variables ¶
var ErrInvalid = errors.New("invalid flag rule")
ErrInvalid wraps every validation error from Set. Its messages are safe to show to the person editing the rule; store errors are not.
Functions ¶
func DevAction ¶
func DevAction(c *Client) devactions.Action
DevAction returns a "set flag" action for the hamr dev widget. The flag list is read here, so declare flags before calling it.
Types ¶
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client holds the in-memory rules and keeps them fresh.
func (*Client) Middleware ¶
func (c *Client) Middleware(who WhoFunc) echo.MiddlewareFunc
Middleware puts c and the request's Who on the request context, and adds every flag's value to the request logger as a "flags" group.
type Definition ¶
type Definition struct {
Key string
Type Type
Default json.RawMessage
}
Definition describes a declared flag, for admin pages and dev actions.
func Definitions ¶
func Definitions() []Definition
Definitions returns every declared flag, sorted by key.
type Flag ¶
type Flag[T any] struct { // contains filtered or unexported fields }
Flag is a typed flag declaration. It holds only the key and the code default; live values come from the Client carried on the context.
func Bool ¶
Bool declares a bool flag. Like every declaration, it panics if key is already declared or is not lowercase letters, digits, '_', '.' or '-'.
func JSON ¶
JSON declares a flag whose value is any JSON-decodable T. It panics if key is already declared. Each Get decodes a fresh T, so callers may mutate it.
func (*Flag[T]) Default ¶
func (f *Flag[T]) Default() T
Default returns the code default (a fresh copy for JSON flags).
type Option ¶
type Option func(*Client)
Option configures a Client.
func WithAnonymousCookie ¶
WithAnonymousCookie makes the middleware set a random first-party ID cookie with this name for logged-out visitors, so percentage rollout applies to them. The cookie is likely not "strictly necessary" under ePrivacy: the app may need consent before enabling it.
func WithDirectURL ¶
WithDirectURL makes the Postgres watcher open its own connection to url instead of using the pool. Use it behind PgBouncer in transaction mode, where LISTEN does not work.
func WithLogger ¶
WithLogger sets the logger for warnings. Default slog.Default().
func WithPollInterval ¶
WithPollInterval sets the safety-net full reload interval. Default 5m.
func WithPostgres ¶
WithPostgres watches db (a pgx-backed *sql.DB) with LISTEN and reloads on every change. The listener pins one pooled connection, so the pool needs at least two (or use WithDirectURL); on a one-connection pool it logs an error and does not listen, leaving only the poll.
func WithSQLite ¶
WithSQLite watches db by polling PRAGMA data_version on one pinned connection and reloads when another connection has committed. ponytail: data_version ignores this connection's own commits (Set updates the cache itself) and a 1s poll means up to 1s staleness. With a single-connection pool (hamr's default) the connection is borrowed per tick instead of pinned, since pinning would starve the app.
type Rule ¶
type Rule struct {
Key string
Enabled bool
Value json.RawMessage
Subjects []string
Roles []string
Percent int // 0–100
}
Rule is the stored state of one flag. When Enabled and the user matches (in Subjects, has one of Roles, or hashes under Percent), Get returns Value; otherwise the code default.
type Store ¶
type Store interface {
List(ctx context.Context) ([]Rule, error)
Save(ctx context.Context, r Rule) error
}
Store persists rules. The app implements it over its own table.
func NewMemStore ¶
NewMemStore returns an in-memory Store seeded with rules. For tests.