# FeatureFlagPlugin _(no submodules)_ ## Purpose `FeatureFlagPlugin` wraps a `@suaveplan/feature-flags` `FlagManager` and exposes its evaluation surface as a lifecycle-managed game-server plugin. It folds a per-player `userId` into the evaluation context automatically and records every evaluation to a rotating log so callers can replay or audit flag decisions without instrumenting every callsite by hand. The plugin is intentionally a thin adapter: it does not own the provider chain, override semantics, or persistence story — those remain in `@suaveplan/feature-flags`. Errors raised by the underlying manager (notably `FLAG_NOT_FOUND`) propagate unchanged so callers can catch the canonical `FeatureFlagsError` discriminator. ## Features - Lifecycle-managed via `BasePlugin` — clears the evaluation log on uninstall. - Per-player `EvaluationContext` building from `defaultContext + playerId`. - `isEnabled` / `evaluate` / `getString` / `getNumber` helpers wrapping the underlying manager API. - Rotating evaluation log (FIFO; default cap 1 000) for downstream analytics. - Zod-validated `maxLogEntries` option. ## Usage: Basic ```ts import { FlagManager, FlagRegistry, Evaluator, MemoryProvider, } from "@suaveplan/feature-flags"; import { FeatureFlagPlugin } from "@suaveplan/game-server/plugins/feature-flag-plugin"; const provider = new MemoryProvider({ flags: [ { key: "boss-rush", type: "boolean", defaultValue: false }, ], }); const registry = new FlagRegistry({ provider }); await registry.load(); const evaluator = new Evaluator(); const flagManager = new FlagManager({ registry, evaluator }); const plugin = new FeatureFlagPlugin({ flagManager }); plugin.isEnabled("boss-rush", "player-1"); ``` ## Usage: Advanced ```ts import { FeatureFlagPlugin } from "@suaveplan/game-server/plugins/feature-flag-plugin"; const plugin = new FeatureFlagPlugin({ flagManager, defaultContext: { attributes: { region: "us-west" } }, maxLogEntries: 50, clock: () => 1_700_000_000_000, }); await plugin.onInstall(ctx); plugin.evaluate("boss-rush", "p1"); plugin.getString("hero-skin", "p1"); plugin.getNumber("drop-rate-multiplier", "p1"); const log = plugin.getEvaluationLog(); ``` ## API Reference - `class FeatureFlagPlugin extends BasePlugin` - `constructor(options: FeatureFlagPluginOptions)` - `isEnabled(key, playerId?): boolean` - `evaluate(key, playerId?): FlagEvaluationResult` - `getString(key, playerId?): string` - `getNumber(key, playerId?): number` - `getEvaluationLog(): readonly EvaluationLogEntry[]` - `clearLog(): void` ## Implementation Notes - The evaluation log is bounded; once `evaluationLog.length` exceeds `maxLogEntries`, the oldest entries are pruned via `slice(-max)`. - `buildContext` merges `defaultContext` with the per-call player id. `userId` from the default context is preserved when the caller does not supply a `playerId`. - Errors thrown by `FlagManager` (e.g. `FLAG_NOT_FOUND`) bubble up as `FeatureFlagsError`. The plugin does not translate them so callers retain access to the underlying error code without an extra cause unwrap. - The plugin holds the `flagManager` by reference and never disposes it. Disposal is the caller's responsibility because the manager may be shared across plugins.