@ooneex/feature-flag component is a lightweight, type-safe contract for gating functionality behind toggles. Each flag is a small, injectable class implementing the IFeatureFlag interface — getKey, getDescription, and isEnabled — registered with a decorator and resolved from the container. The enablement check can be synchronous or asynchronous, so a flag can return a constant, read an environment variable, or query a remote service.
Why this component
- One focused contract. Every flag implements the same
IFeatureFlaginterface, so call sites evaluate them the same way regardless of how each flag decides. - Sync or async.
isEnabled()may return abooleanor aPromise<boolean>— back a flag with a constant, an env var, config, or a remote check without changing callers. - Self-describing. Each flag carries a stable
getKey()and a human-readablegetDescription(), so flags are discoverable and auditable. - Container-managed. Register a flag class with a decorator and resolve it from the DI container with any scope.
- Type-safe. Full TypeScript support with
IFeatureFlagandFeatureFlagClassType.
How it works
You implementIFeatureFlag as a class, register it with the featureFlag decorator, and resolve it from the container. Callers invoke isEnabled() and branch on the result.
| Method | Purpose |
|---|---|
getKey() | Return a unique, stable identifier for the flag (kebab-case by convention). |
getDescription() | Return a human-readable explanation of what the flag controls. |
isEnabled() | Decide whether the flag is on; returns boolean or Promise<boolean>. |
isEnabled() can be async, the same flag shape covers constant toggles, environment-driven rollouts, and remote configuration — callers always await the result and never need to know the source.
Decorator and usage
@decorator.featureFlag(scope?)
Registers a feature-flag class with the DI container. It accepts an optional EContainerScope (defaults to EContainerScope.Singleton).
Implement IFeatureFlag and register it:
isEnabled() can resolve a value from a remote service, database, or environment:
Best practices
- Keep keys stable and unique. Use kebab-case keys like
dark-modeandbeta-checkout; never reuse or rename a key once it ships, since logs and config may reference it. - Write a real description.
getDescription()is what makes flags auditable — explain what the flag controls and who it targets, not just its name. - Always
awaitthe result. TreatisEnabled()as potentially async even for constant flags, so a flag can later move to a remote check without touching callers. - Keep the decision in one place. Put all gating logic inside
isEnabled(); callers should only branch on the boolean, never re-derive the condition. - Match the scope to the source. Use the default singleton for constant or env-driven flags; use
TransientorRequestwhen the flag must be re-evaluated per request. - Inject dependencies. When a flag reads config or calls a service, inject those dependencies through the constructor rather than reaching for globals.
CLI command
Scaffold a feature flag class and its test file with the generator. It writes the class undermodules/<module>/src/flags/<Name>FeatureFlag.ts and installs @ooneex/feature-flag if it is missing.
| Option | Description | Default |
|---|---|---|
--name | Feature flag class name. The FeatureFlag suffix is appended automatically. | Prompted if omitted |
--module | Target module the class is generated into. | shared |
--override | Overwrite an existing class without prompting. | false |
Use with Claude and Codex
The generator ships a matchingflag:create skill. It runs the scaffold and then guides your AI agent through completing the flag — setting a stable key, writing the description, and implementing isEnabled() with the real gating logic. Initialize the skills once for your agent:
- Claude
- Codex
Prompt
flag:create --name=NewCheckout, then implements getKey, getDescription, and isEnabled for the new checkout flow.