PURPOSE
Feature flags client. Bulk-hydrates the feature_flags table at boot and exposes a synchronous lookup for callers. Default-on policy: missing flags (whether un-hydrated or never seeded) return true. Kill switches must be explicitly flipped off in the Supabase dashboard to take effect. This biases toward “feature works” over “feature silently disabled because someone forgot to seed a row.”
OWNS
- Module-private
_cache: Map<string, boolean>of flag name to enabled boolean. - Module-private
_hydrated: booleanindicating whetherhydrateFeatureFlagshas completed (success or failure). - Default-on lookup semantics for any unknown or missing flag.
READS FROM
- Supabase
feature_flagstable, columnsflag_nameandenabled, via the sharedsupabaseclient from./supabase.
PUSHES TO
- The internal
_cachemap (writes only happen duringhydrateFeatureFlags). console.warnon hydrate failure.
DOES NOT
- Does not write to the
feature_flagstable. - Does not subscribe to realtime updates — flags are snapshotted at boot.
- Does not re-hydrate on demand; one hydration per session.
- Does not throw on lookup or hydration; hydration failures are swallowed and marked hydrated to avoid a permanent fallback path.
- Does not provide async lookups;
isFlagEnabledis synchronous. - Does not differentiate between “missing row” and “row with
enabled = true” — both returntrue.
Signals
- Hydration failure logs
[featureFlags] hydrate failedto the console with the underlying error.
Entry points
hydrateFeatureFlags(): Promise<void>— fetches all rows fromfeature_flagsand populates_cache. Sets_hydrated = trueeven when hydration throws.isFlagEnabled(flagName: string): boolean— synchronous lookup. Returnstrueif not yet hydrated, returns_cache.get(flagName) ?? trueotherwise.
Pattern notes
- Default-on / fail-open: every branch returns
truewhen the answer is unknown — pre-hydration, hydration error, or missing key in cache. Kill switches are opt-in disablement, not opt-in enablement. - Failure path still sets
_hydrated = trueso the pre-hydration branch (which always returnstrue) doesn’t become a permanent fallback masking a broken Supabase connection. - Module-level singleton cache (no class, no exported reset); state lives for the lifetime of the JS module.
- Synchronous lookup at call sites; the only async surface is the one-shot
hydrateFeatureFlagsat boot.