@blueforge-studio/flags

Feature-flag SDK for the BlueForge federation: typed schema with compiled fail-closed defaults, ETag-revalidating poll client, and a thin React hook entry.

Información del paquete

Latest Version0.1.0
Licensenone
Keywords-
Repository-
Author-
Homepage-
Bugs-

Versiones

VersiónPublicada
0.1.02026-10-11

Cadena de suministro por versión

VersiónFirmaSBOMAtt
0.1.0

Haz clic en una versión para ver sus detalles.

Cadena de suministro

Procedencia

Publicado por
web:5
Cuándo
2026-10-11T06:21:25Z
sha256 (calculado por el registro)
de8bd705a9c807f1100d3b50ccc491da8e26ab94eb17ba48f4e6b4e3fba6eeb6

Observado por el registro en el momento de publicar.

Declarado por la compilación

Quien publicó no declaró nada sobre su compilación.

Adjuntos

Firma
ninguno
SBOM
ninguno

Dependencias

Cargando el grafo de dependencias...

README

@blueforge-studio/flags

Feature-flag SDK for the BlueForge federation (spec: docs/superpowers/specs/2026-09-30-feature-flags-service-design.md §5). Server-side: it holds a flags:read key. The only browser-safe entry is @blueforge-studio/flags/client, which never sees a key.

Usage

import { defineFlags, createFlagClient } from '@blueforge-studio/flags'

export const flags = defineFlags({
  PAYMENT_PLANS: { type: 'enum', values: ['off', 'trial_only', 'all'], default: 'off', maxStalenessMs: 60_000 },
  TRIAL_DAYS:    { type: 'number', default: 14, min: 0, max: 60 },
  BANNER:        { type: 'bool', default: false },
})

const client = createFlagClient({
  schema: flags,
  url: process.env.FORGE_API_URL!,       // hosting-api base URL
  key: process.env.FLAGS_READ_KEY!,      // flags:read key, server env only
  app: 'lq-core',
  env: 'prod',
})
await client.ready()                     // resolves, never rejects (default timeout 2 s)

client.enum('PAYMENT_PLANS')             // typed getters: bool / enum / number / string
client.get('TRIAL_DAYS')                 // { value, source, stale, ageMs }
await client.refresh()                   // force revalidation, e.g. at checkout

Code against FlagSource<D> (implemented by FlagClient and by test fakes), not the concrete client.

Fail-closed semantics

  • Never throws at runtime (network error, 4xx/5xx, non-JSON, bad values, throwing store/telemetry). The only throws are startup misconfiguration: maxStalenessMs < 2 x pollIntervalMs, and a compiled default that is invalid for its own definition.
  • get() is synchronous and cache-backed; refresh is background polling (30 s +/-20% jitter, ETag If-None-Match, 304 renews the timestamp).
  • On failure the last-known-good snapshot is served as stale: true, source: 'stale-cache' until the staleness ceiling, then the compiled default (source: 'compiled-default').
  • Staleness ceiling per flag = the tightest of the flag's maxStalenessMs, the server's per-flag maxStalenessSeconds, else cache.maxStalenessMs (15 min). Money flags: maxStalenessMs: 60_000.
  • Every served value is re-validated against the compiled definition (type, enum membership, number min/max). Failing values fall back to the default and emit schema_mismatch. Unknown keys from the server are ignored; missing keys use defaults.
  • 401/403 counts as an outage (error: 'auth', backoff 5 min). Other failures back off 1 s to 5 min with jitter; one request in flight.
  • Rollout is not in v1: subject is passed through to the server untouched.
  • Cache is in-memory by default; pass cache.store ({ load(), save() }, synchronous) for cold-start survival.

Browser: @blueforge-studio/flags/client

import { useFlags } from '@blueforge-studio/flags/client'
const { status, get } = useFlags(flags)   // 'loading' | 'ready' | 'fallback'
get('BANNER').value

Reads the app's own GET /api/flags (default endpoint, configurable), never hosting-api. Serve it server-side with flagsHandler(client, { expose: ['BANNER'] }) (explicit allowlist; never expose protected keys). React is an optional peer dependency. UI hiding is cosmetic; enforce server-side.

Wire types

ResolveResponse, ResolveResponseOk, ResolvedFlag are exported for the server side (GET /api/flags/resolve).