Skip to content

Percentage Rollouts

Percentage rollout uses murmurhash3 for deterministic bucketing: the same user always gets the same result for a given flag, ensuring a consistent experience across requests.

Evaluation Priority

When isEnabled() is called, flags are evaluated through a 6-layer cascade. The first matching layer wins:

PriorityLayerDescription
1ArchivedIf the flag has archivedAt set, evaluation always returns false
2User overrideOverride matching the current userId (most specific)
3Tenant overrideOverride matching the current tenantId
4Environment overrideOverride matching the current environment
5Percentage rolloutDeterministic hash of flagKey + userId (or tenantId) mod 100
6Global defaultThe flag's enabled field

CRUD Operations

FeatureFlagService also exposes methods for managing flags programmatically:

typescript
// Create a flag
const flag = await this.flags.create({
  key: 'NEW_FEATURE',
  description: 'Enables the new feature',
  enabled: false,
  percentage: 0,
});

// Update a flag
await this.flags.update('NEW_FEATURE', {
  enabled: true,
  percentage: 50,
});

// Archive a flag (soft delete -- evaluations return false)
await this.flags.archive('OLD_FEATURE');

// List all active (non-archived) flags
const allFlags = await this.flags.findAll();

// Manually invalidate the cache
this.flags.invalidateCache();

Caching

Built-in caching is controlled by the cacheTtlMs option (default 30000 ms). Set to 0 to disable caching. You can manually invalidate the cache at any time:

typescript
this.flags.invalidateCache();

Events

Enable event emission to observe flag lifecycle changes. Requires @nestjs/event-emitter as an optional peer dependency.

Important: You must import EventEmitterModule.forRoot() in your app module. The feature-flag module reuses the same EventEmitter2 singleton that NestJS manages, so @OnEvent() listeners work out of the box.

Setup

typescript
import { EventEmitterModule } from '@nestjs/event-emitter';

@Module({
  imports: [
    EventEmitterModule.forRoot(),   // must be imported
    FeatureFlagModule.forRoot({
      environment: 'production',
      prisma: prismaService,
      emitEvents: true,
    }),
  ],
})
export class AppModule {}

Event types

Event constantEvent stringPayload type
FeatureFlagEvents.EVALUATEDfeature-flag.evaluatedFlagEvaluatedEvent
FeatureFlagEvents.CREATEDfeature-flag.createdFlagMutationEvent
FeatureFlagEvents.UPDATEDfeature-flag.updatedFlagMutationEvent
FeatureFlagEvents.ARCHIVEDfeature-flag.archivedFlagMutationEvent
FeatureFlagEvents.OVERRIDE_SETfeature-flag.override.setFlagOverrideEvent
FeatureFlagEvents.OVERRIDE_REMOVEDfeature-flag.override.removedFlagOverrideEvent
FeatureFlagEvents.CACHE_INVALIDATEDfeature-flag.cache.invalidated{}

Listening to events

typescript
import { OnEvent } from '@nestjs/event-emitter';
import { FeatureFlagEvents, FlagEvaluatedEvent } from '@nestarc/feature-flag';

@Injectable()
export class FlagAuditListener {
  @OnEvent(FeatureFlagEvents.EVALUATED)
  handleEvaluation(event: FlagEvaluatedEvent) {
    console.log(`Flag ${event.flagKey} = ${event.result} (source: ${event.source})`);
  }
}

Released under the MIT License.