Initial Commit
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
# FeatureFlagPlugin
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## 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<T>(key, playerId?): FlagEvaluationResult<T>`
|
||||
- `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.
|
||||
Reference in New Issue
Block a user