Skip to content

Feature Flags Domain

The feature-flags domain provides controlled feature rollouts with key validation, toggle tracking, and optional contextual conditions for TypeScript backends. It is structured in strict Clean Architecture layers with zero npm dependencies in the domain and application layers.

Terminal window
npx @backcap/cli add feature-flags

The FeatureFlag entity is the aggregate root. It holds a validated key, an enabled/disabled state, and an optional conditions field for future rule engine integration.

import { FeatureFlag } from "./domains/feature-flags/domain/entities/feature-flag.entity";
const result = FeatureFlag.create({
id: crypto.randomUUID(),
key: "dark-mode",
isEnabled: false,
conditions: { percentage: 25, segment: "beta-users" },
});
if (result.isOk()) {
const flag = result.unwrap();
console.log(flag.key.value); // "dark-mode"
console.log(flag.isEnabled); // false
const { flag: enabled, event } = flag.enable();
console.log(enabled.isEnabled); // true
console.log(event.key); // "dark-mode"
}
FieldTypeDescription
idstringUnique identifier (UUID)
keyFlagKeyValidated lowercase key (2–64 chars)
isEnabledbooleanWhether the flag is currently on
conditionsRecord<string, unknown> | undefinedOptional contextual rules (opaque to domain)
createdAtDateTimestamp of creation

FeatureFlag.create() returns Result<FeatureFlag, InvalidFlagKey>. Invalid keys are rejected at creation time.

Validates that keys are lowercase, start with a letter, use only letters/digits/underscores/hyphens, and are 2–64 characters long.

import { FlagKey } from "./domains/feature-flags/domain/value-objects/flag-key.vo";
const key = FlagKey.create("dark-mode"); // Ok
const bad = FlagKey.create("INVALID!"); // Fail
EventFieldsEmitted when
FlagToggledflagId, key, isEnabled, occurredAtA flag is enabled or disabled
ErrorFactoryWhen
FlagNotFoundcreate(key)Flag lookup by key returns nothing
InvalidFlagKeycreate(reason)Key format validation fails
FlagAlreadyExistscreate(key)A flag with the given key already exists (thrown by CreateFlag)
FlagAlreadyInStatecreate(key, state)Toggle to current state (thrown by ToggleFlag use case, not domain methods)

Check whether a feature flag is enabled.

const evaluateFlag = new EvaluateFlag(flagStore);
const result = await evaluateFlag.execute({ key: "dark-mode" });
// Result<{ isEnabled: boolean; key: string }, FlagNotFound>

Register a new feature flag.

const createFlag = new CreateFlag(flagStore);
const result = await createFlag.execute({
key: "dark-mode",
isEnabled: false,
conditions: { percentage: 50 },
});
// Result<{ flagId: string; createdAt: Date }, Error>

Switch a flag on or off.

const toggleFlag = new ToggleFlag(flagStore);
const result = await toggleFlag.execute({ key: "dark-mode", enabled: true });
// Result<{ key: string; isEnabled: boolean; updatedAt: Date }, FlagNotFound | FlagAlreadyInState>
interface IFlagStore {
save(flag: FeatureFlag): Promise<void>;
findByKey(key: string): Promise<FeatureFlag | null>;
findAll(): Promise<FeatureFlag[]>;
}

The conditions field is Record<string, unknown> by design — opaque to the domain core. To add percentage rollout or user segmentation, create a condition evaluator outside the domain layer that receives conditions and context from the evaluate input.