@nestarc/feature-flag 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +164 -13
  2. package/dist/admin/feature-flag-admin.controller.d.ts +3 -1
  3. package/dist/admin/feature-flag-admin.controller.js +12 -0
  4. package/dist/admin/feature-flag-admin.controller.js.map +1 -1
  5. package/dist/admin/feature-flag-admin.dto.d.ts +8 -0
  6. package/dist/admin/feature-flag-admin.dto.js +24 -1
  7. package/dist/admin/feature-flag-admin.dto.js.map +1 -1
  8. package/dist/events/feature-flag.events.d.ts +35 -2
  9. package/dist/events/feature-flag.events.js +1 -0
  10. package/dist/events/feature-flag.events.js.map +1 -1
  11. package/dist/flag-registry.d.ts +24 -0
  12. package/dist/flag-registry.js +62 -0
  13. package/dist/flag-registry.js.map +1 -0
  14. package/dist/guards/feature-flag.guard.js +5 -1
  15. package/dist/guards/feature-flag.guard.js.map +1 -1
  16. package/dist/index.d.ts +5 -2
  17. package/dist/index.js +7 -1
  18. package/dist/index.js.map +1 -1
  19. package/dist/interfaces/evaluation-context.interface.d.ts +2 -0
  20. package/dist/interfaces/evaluation-details.interface.d.ts +29 -0
  21. package/dist/interfaces/evaluation-details.interface.js +3 -0
  22. package/dist/interfaces/evaluation-details.interface.js.map +1 -0
  23. package/dist/interfaces/feature-flag-options.interface.d.ts +3 -0
  24. package/dist/interfaces/feature-flag.interface.d.ts +9 -0
  25. package/dist/interfaces/flag-registry.interface.d.ts +17 -0
  26. package/dist/interfaces/flag-registry.interface.js +3 -0
  27. package/dist/interfaces/flag-registry.interface.js.map +1 -0
  28. package/dist/openfeature.d.ts +26 -0
  29. package/dist/openfeature.js +96 -0
  30. package/dist/openfeature.js.map +1 -0
  31. package/dist/services/feature-flag.service.d.ts +14 -7
  32. package/dist/services/feature-flag.service.js +103 -24
  33. package/dist/services/feature-flag.service.js.map +1 -1
  34. package/dist/services/flag-evaluator.service.d.ts +7 -8
  35. package/dist/services/flag-evaluator.service.js +62 -10
  36. package/dist/services/flag-evaluator.service.js.map +1 -1
  37. package/dist/testing/index.d.ts +1 -1
  38. package/dist/testing/index.js +2 -1
  39. package/dist/testing/index.js.map +1 -1
  40. package/dist/testing/test-feature-flag.module.d.ts +21 -0
  41. package/dist/testing/test-feature-flag.module.js +78 -19
  42. package/dist/testing/test-feature-flag.module.js.map +1 -1
  43. package/package.json +25 -6
  44. package/prisma/schema.prisma +3 -2
@@ -6,6 +6,8 @@ export interface EvaluationContext {
6
6
  tenantId?: string | null;
7
7
  /** Environment - auto-injected from module options. Can be explicitly overridden */
8
8
  environment?: string;
9
+ /** Explicit stable key for percentage rollout bucketing */
10
+ targetingKey?: string | null;
9
11
  /** Additional exact-match targeting attributes */
10
12
  attributes?: TargetingAttributes;
11
13
  }
@@ -0,0 +1,29 @@
1
+ export type EvaluationSource = 'override' | 'percentage' | 'global' | 'default';
2
+ export type EvaluationReason = 'ARCHIVED' | 'OVERRIDE_MATCH' | 'PERCENTAGE_MATCH' | 'PERCENTAGE_MISS' | 'PERCENTAGE_NO_TARGETING_KEY' | 'GLOBAL' | 'FLAG_NOT_FOUND' | 'ERROR';
3
+ export type BucketBy = 'userId' | 'tenantId' | 'environment' | 'targetingKey' | (string & {});
4
+ export interface EvaluateBooleanOptions {
5
+ /** Invocation-level default used when the flag is missing or evaluation fails. */
6
+ defaultValue?: boolean;
7
+ /** Emit an exposure event for this evaluation. */
8
+ trackExposure?: boolean;
9
+ /** Include the full resolved context in evaluation/exposure events. */
10
+ includeContextInEvent?: boolean;
11
+ }
12
+ export interface BooleanEvaluationDetails {
13
+ flagKey: string;
14
+ value: boolean;
15
+ /** Backward-compatible alias for value. */
16
+ result: boolean;
17
+ source: EvaluationSource;
18
+ reason: EvaluationReason;
19
+ defaultUsed: boolean;
20
+ errorCode?: string;
21
+ errorMessage?: string;
22
+ matchedOverrideId?: string;
23
+ bucket?: number;
24
+ targetingKey?: string;
25
+ evaluationTimeMs?: number;
26
+ }
27
+ export interface FlagEvaluatorOptions {
28
+ bucketBy?: BucketBy;
29
+ }
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=evaluation-details.interface.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"evaluation-details.interface.js","sourceRoot":"","sources":["../../src/interfaces/evaluation-details.interface.ts"],"names":[],"mappings":""}
@@ -1,6 +1,7 @@
1
1
  import { ModuleMetadata, Type } from '@nestjs/common';
2
2
  import { Request } from 'express';
3
3
  import { CacheAdapter } from './cache-adapter.interface';
4
+ import { FlagRegistry } from './flag-registry.interface';
4
5
  export interface FeatureFlagModuleOptions {
5
6
  /** Current environment (e.g., 'development', 'staging', 'production') */
6
7
  environment: string;
@@ -14,6 +15,8 @@ export interface FeatureFlagModuleOptions {
14
15
  emitEvents?: boolean;
15
16
  /** Custom cache adapter implementation. If not provided, an in-memory cache is used. */
16
17
  cacheAdapter?: CacheAdapter;
18
+ /** Optional type-safe flag registry used for defaults and evaluation metadata. */
19
+ flags?: FlagRegistry;
17
20
  }
18
21
  export interface FeatureFlagModuleOptionsFactory {
19
22
  createFeatureFlagOptions(): Promise<FeatureFlagModuleOptions & {
@@ -23,6 +23,8 @@ export interface FeatureFlagGuardOptions {
23
23
  statusCode?: number;
24
24
  /** Response body when flag is OFF */
25
25
  fallback?: Record<string, unknown>;
26
+ /** Invocation-level default when the guard cannot find or evaluate the flag. Default: false */
27
+ defaultValue?: boolean;
26
28
  }
27
29
  export interface FeatureFlagWithOverrides {
28
30
  id: string;
@@ -48,3 +50,10 @@ export interface FlagOverride {
48
50
  export interface RemoveOverrideInput {
49
51
  attributes: TargetingAttributes;
50
52
  }
53
+ export interface FlagMutationMetadata {
54
+ actorId?: string;
55
+ actorType?: string;
56
+ reason?: string;
57
+ requestId?: string;
58
+ correlationId?: string;
59
+ }
@@ -0,0 +1,17 @@
1
+ import { BucketBy } from './evaluation-details.interface';
2
+ export type FeatureFlagType = 'release' | 'experiment' | 'ops' | 'permission';
3
+ export interface FeatureFlagLifecycleMetadata {
4
+ type?: FeatureFlagType;
5
+ owner?: string;
6
+ tags?: readonly string[];
7
+ expiresAt?: string | Date;
8
+ staleAt?: string | Date;
9
+ }
10
+ export interface FlagDefinition extends FeatureFlagLifecycleMetadata {
11
+ defaultValue: boolean;
12
+ description?: string;
13
+ bucketBy?: BucketBy;
14
+ trackExposure?: boolean;
15
+ }
16
+ export type FlagRegistry = Record<string, FlagDefinition>;
17
+ export type FlagKey<TFlags extends FlagRegistry> = Extract<keyof TFlags, string>;
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=flag-registry.interface.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flag-registry.interface.js","sourceRoot":"","sources":["../../src/interfaces/flag-registry.interface.ts"],"names":[],"mappings":""}
@@ -0,0 +1,26 @@
1
+ import { FeatureFlagService } from './services/feature-flag.service';
2
+ export interface OpenFeatureBooleanProviderOptions {
3
+ name?: string;
4
+ }
5
+ export interface OpenFeatureEvaluationContext {
6
+ targetingKey?: unknown;
7
+ userId?: unknown;
8
+ tenantId?: unknown;
9
+ environment?: unknown;
10
+ [key: string]: unknown;
11
+ }
12
+ export interface OpenFeatureBooleanResolutionDetails {
13
+ value: boolean;
14
+ reason: string;
15
+ variant?: string;
16
+ errorCode?: string;
17
+ errorMessage?: string;
18
+ flagMetadata: Record<string, unknown>;
19
+ }
20
+ export interface OpenFeatureBooleanProvider {
21
+ metadata: {
22
+ name: string;
23
+ };
24
+ resolveBooleanEvaluation(flagKey: string, defaultValue: boolean, context: OpenFeatureEvaluationContext): Promise<OpenFeatureBooleanResolutionDetails>;
25
+ }
26
+ export declare function createOpenFeatureBooleanProvider(service: FeatureFlagService, options?: OpenFeatureBooleanProviderOptions): OpenFeatureBooleanProvider;
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createOpenFeatureBooleanProvider = createOpenFeatureBooleanProvider;
4
+ const targeting_attributes_1 = require("./utils/targeting-attributes");
5
+ function createOpenFeatureBooleanProvider(service, options = {}) {
6
+ return {
7
+ metadata: {
8
+ name: options.name ?? '@nestarc/feature-flag',
9
+ },
10
+ resolveBooleanEvaluation: async (flagKey, defaultValue, context) => {
11
+ try {
12
+ const details = await service.evaluateBoolean(flagKey, toEvaluationContext(context), { defaultValue });
13
+ return toOpenFeatureResolution(details);
14
+ }
15
+ catch (error) {
16
+ return {
17
+ value: defaultValue,
18
+ reason: 'ERROR',
19
+ errorCode: error instanceof Error ? error.constructor.name : 'Error',
20
+ errorMessage: error instanceof Error ? error.message : String(error),
21
+ flagMetadata: {
22
+ source: 'default',
23
+ localReason: 'ERROR',
24
+ defaultUsed: true,
25
+ },
26
+ };
27
+ }
28
+ },
29
+ };
30
+ }
31
+ function toEvaluationContext(context) {
32
+ const attributes = {};
33
+ for (const [key, value] of Object.entries(context)) {
34
+ if (isKnownContextKey(key)) {
35
+ continue;
36
+ }
37
+ if ((0, targeting_attributes_1.isTargetingAttributeValue)(value)) {
38
+ attributes[key] = value;
39
+ }
40
+ }
41
+ return {
42
+ targetingKey: readString(context.targetingKey),
43
+ userId: readString(context.userId),
44
+ tenantId: readString(context.tenantId),
45
+ environment: readString(context.environment) ?? undefined,
46
+ attributes,
47
+ };
48
+ }
49
+ function toOpenFeatureResolution(details) {
50
+ return {
51
+ value: details.value,
52
+ reason: mapOpenFeatureReason(details.reason),
53
+ errorCode: details.errorCode,
54
+ errorMessage: details.errorMessage,
55
+ flagMetadata: {
56
+ source: details.source,
57
+ localReason: details.reason,
58
+ defaultUsed: details.defaultUsed,
59
+ matchedOverrideId: details.matchedOverrideId,
60
+ bucket: details.bucket,
61
+ targetingKey: details.targetingKey,
62
+ evaluationTimeMs: details.evaluationTimeMs,
63
+ },
64
+ };
65
+ }
66
+ function mapOpenFeatureReason(reason) {
67
+ switch (reason) {
68
+ case 'OVERRIDE_MATCH':
69
+ return 'TARGETING_MATCH';
70
+ case 'PERCENTAGE_MATCH':
71
+ case 'PERCENTAGE_MISS':
72
+ return 'SPLIT';
73
+ case 'FLAG_NOT_FOUND':
74
+ case 'PERCENTAGE_NO_TARGETING_KEY':
75
+ return 'DEFAULT';
76
+ case 'ERROR':
77
+ return 'ERROR';
78
+ case 'ARCHIVED':
79
+ return 'DISABLED';
80
+ case 'GLOBAL':
81
+ return 'STATIC';
82
+ }
83
+ }
84
+ function isKnownContextKey(key) {
85
+ return (key === 'targetingKey' ||
86
+ key === 'userId' ||
87
+ key === 'tenantId' ||
88
+ key === 'environment');
89
+ }
90
+ function readString(value) {
91
+ if (value === null) {
92
+ return null;
93
+ }
94
+ return typeof value === 'string' && value.length > 0 ? value : undefined;
95
+ }
96
+ //# sourceMappingURL=openfeature.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openfeature.js","sourceRoot":"","sources":["../src/openfeature.ts"],"names":[],"mappings":"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"}
@@ -1,11 +1,12 @@
1
1
  import { FeatureFlagModuleOptions } from '../interfaces/feature-flag-options.interface';
2
- import { CreateFeatureFlagInput, UpdateFeatureFlagInput, SetOverrideInput, RemoveOverrideInput, FeatureFlagWithOverrides } from '../interfaces/feature-flag.interface';
2
+ import { CreateFeatureFlagInput, UpdateFeatureFlagInput, SetOverrideInput, RemoveOverrideInput, FeatureFlagWithOverrides, FlagMutationMetadata } from '../interfaces/feature-flag.interface';
3
3
  import { EvaluationContext } from '../interfaces/evaluation-context.interface';
4
4
  import { CacheAdapter } from '../interfaces/cache-adapter.interface';
5
5
  import { FeatureFlagRepository } from '../interfaces/feature-flag-repository.interface';
6
6
  import { FlagEvaluatorService } from './flag-evaluator.service';
7
7
  import { FlagContextResolver } from './flag-context-resolver';
8
8
  import { FlagEventPublisher } from './flag-event-publisher';
9
+ import { BooleanEvaluationDetails, EvaluateBooleanOptions } from '../interfaces/evaluation-details.interface';
9
10
  export declare class FeatureFlagService {
10
11
  private readonly options;
11
12
  private readonly repository;
@@ -15,16 +16,17 @@ export declare class FeatureFlagService {
15
16
  private readonly eventPublisher;
16
17
  constructor(options: FeatureFlagModuleOptions, repository: FeatureFlagRepository, cacheAdapter: CacheAdapter, evaluator: FlagEvaluatorService, contextResolver: FlagContextResolver, eventPublisher: FlagEventPublisher);
17
18
  private get cacheTtlMs();
18
- isEnabled(flagKey: string, explicitContext?: EvaluationContext): Promise<boolean>;
19
+ isEnabled(flagKey: string, explicitContext?: EvaluationContext, evaluationOptions?: EvaluateBooleanOptions): Promise<boolean>;
20
+ evaluateBoolean(flagKey: string, explicitContext?: EvaluationContext, evaluationOptions?: EvaluateBooleanOptions): Promise<BooleanEvaluationDetails>;
19
21
  evaluateAll(explicitContext?: EvaluationContext): Promise<Record<string, boolean>>;
20
- create(input: CreateFeatureFlagInput): Promise<FeatureFlagWithOverrides>;
21
- update(key: string, input: UpdateFeatureFlagInput): Promise<FeatureFlagWithOverrides>;
22
- archive(key: string): Promise<FeatureFlagWithOverrides>;
23
- setOverride(key: string, input: SetOverrideInput): Promise<void>;
22
+ create(input: CreateFeatureFlagInput, metadata?: FlagMutationMetadata): Promise<FeatureFlagWithOverrides>;
23
+ update(key: string, input: UpdateFeatureFlagInput, metadata?: FlagMutationMetadata): Promise<FeatureFlagWithOverrides>;
24
+ archive(key: string, metadata?: FlagMutationMetadata): Promise<FeatureFlagWithOverrides>;
25
+ setOverride(key: string, input: SetOverrideInput, metadata?: FlagMutationMetadata): Promise<void>;
24
26
  findAll(): Promise<FeatureFlagWithOverrides[]>;
25
27
  invalidateCache(): Promise<void>;
26
28
  findByKey(key: string): Promise<FeatureFlagWithOverrides>;
27
- removeOverride(key: string, input: RemoveOverrideInput): Promise<void>;
29
+ removeOverride(key: string, input: RemoveOverrideInput, metadata?: FlagMutationMetadata): Promise<void>;
28
30
  private normalizeOverrideAttributes;
29
31
  /**
30
32
  * Best-effort cache invalidation for mutation paths.
@@ -34,4 +36,9 @@ export declare class FeatureFlagService {
34
36
  private safeInvalidateCache;
35
37
  private resolveFlag;
36
38
  private resolveAllFlags;
39
+ private defaultDetails;
40
+ private resolveDefaultValue;
41
+ private shouldTrackExposure;
42
+ private emitEvaluation;
43
+ private emitExposure;
37
44
  }
@@ -33,23 +33,38 @@ let FeatureFlagService = class FeatureFlagService {
33
33
  get cacheTtlMs() {
34
34
  return this.options.cacheTtlMs ?? 30_000;
35
35
  }
36
- async isEnabled(flagKey, explicitContext) {
37
- const flag = await this.resolveFlag(flagKey);
38
- if (!flag) {
39
- return this.options.defaultOnMissing ?? false;
40
- }
41
- const context = this.contextResolver.resolve(explicitContext);
36
+ async isEnabled(flagKey, explicitContext, evaluationOptions = {}) {
37
+ return (await this.evaluateBoolean(flagKey, explicitContext, evaluationOptions)).value;
38
+ }
39
+ async evaluateBoolean(flagKey, explicitContext, evaluationOptions = {}) {
42
40
  const startTime = Date.now();
43
- const { result, source } = this.evaluator.evaluate(flag, context);
44
- const evaluationTimeMs = Date.now() - startTime;
45
- this.eventPublisher.emit(feature_flag_events_1.FeatureFlagEvents.EVALUATED, {
46
- flagKey,
47
- result,
48
- context,
49
- source,
50
- evaluationTimeMs,
51
- });
52
- return result;
41
+ let context = {};
42
+ try {
43
+ context = this.contextResolver.resolve(explicitContext);
44
+ const flag = await this.resolveFlag(flagKey);
45
+ const registryDefinition = this.options.flags?.[flagKey];
46
+ const details = flag
47
+ ? {
48
+ ...this.evaluator.evaluate(flag, context, {
49
+ bucketBy: registryDefinition?.bucketBy,
50
+ }),
51
+ evaluationTimeMs: Date.now() - startTime,
52
+ }
53
+ : this.defaultDetails(flagKey, 'FLAG_NOT_FOUND', startTime, evaluationOptions);
54
+ this.emitEvaluation(details, context, evaluationOptions);
55
+ if (this.shouldTrackExposure(flagKey, flag, evaluationOptions)) {
56
+ this.emitExposure(details, context, evaluationOptions);
57
+ }
58
+ return details;
59
+ }
60
+ catch (error) {
61
+ const details = this.defaultDetails(flagKey, 'ERROR', startTime, evaluationOptions, error);
62
+ this.emitEvaluation(details, context, evaluationOptions);
63
+ if (this.shouldTrackExposure(flagKey, null, evaluationOptions)) {
64
+ this.emitExposure(details, context, evaluationOptions);
65
+ }
66
+ return details;
67
+ }
53
68
  }
54
69
  async evaluateAll(explicitContext) {
55
70
  const flags = await this.resolveAllFlags();
@@ -60,25 +75,37 @@ let FeatureFlagService = class FeatureFlagService {
60
75
  }
61
76
  return result;
62
77
  }
63
- async create(input) {
78
+ async create(input, metadata = {}) {
64
79
  const flag = await this.repository.createFlag(input);
65
80
  await this.safeInvalidateCache();
66
- this.eventPublisher.emit(feature_flag_events_1.FeatureFlagEvents.CREATED, { flagKey: input.key, action: 'created' });
81
+ this.eventPublisher.emit(feature_flag_events_1.FeatureFlagEvents.CREATED, {
82
+ flagKey: input.key,
83
+ action: 'created',
84
+ ...metadata,
85
+ });
67
86
  return flag;
68
87
  }
69
- async update(key, input) {
88
+ async update(key, input, metadata = {}) {
70
89
  const flag = await this.repository.updateFlag(key, input);
71
90
  await this.safeInvalidateCache(key);
72
- this.eventPublisher.emit(feature_flag_events_1.FeatureFlagEvents.UPDATED, { flagKey: key, action: 'updated' });
91
+ this.eventPublisher.emit(feature_flag_events_1.FeatureFlagEvents.UPDATED, {
92
+ flagKey: key,
93
+ action: 'updated',
94
+ ...metadata,
95
+ });
73
96
  return flag;
74
97
  }
75
- async archive(key) {
98
+ async archive(key, metadata = {}) {
76
99
  const flag = await this.repository.archiveFlag(key);
77
100
  await this.safeInvalidateCache(key);
78
- this.eventPublisher.emit(feature_flag_events_1.FeatureFlagEvents.ARCHIVED, { flagKey: key, action: 'archived' });
101
+ this.eventPublisher.emit(feature_flag_events_1.FeatureFlagEvents.ARCHIVED, {
102
+ flagKey: key,
103
+ action: 'archived',
104
+ ...metadata,
105
+ });
79
106
  return flag;
80
107
  }
81
- async setOverride(key, input) {
108
+ async setOverride(key, input, metadata = {}) {
82
109
  const attributes = this.normalizeOverrideAttributes(input.attributes);
83
110
  const priority = input.priority ?? 0;
84
111
  const flagId = await this.repository.findFlagIdByKey(key);
@@ -103,6 +130,7 @@ let FeatureFlagService = class FeatureFlagService {
103
130
  enabled: input.enabled,
104
131
  priority,
105
132
  action: 'set',
133
+ ...metadata,
106
134
  });
107
135
  }
108
136
  async findAll() {
@@ -119,7 +147,7 @@ let FeatureFlagService = class FeatureFlagService {
119
147
  }
120
148
  return flag;
121
149
  }
122
- async removeOverride(key, input) {
150
+ async removeOverride(key, input, metadata = {}) {
123
151
  const attributes = this.normalizeOverrideAttributes(input.attributes);
124
152
  const flagId = await this.repository.findFlagIdByKey(key);
125
153
  if (!flagId) {
@@ -135,6 +163,7 @@ let FeatureFlagService = class FeatureFlagService {
135
163
  flagKey: key,
136
164
  attributes,
137
165
  action: 'removed',
166
+ ...metadata,
138
167
  });
139
168
  }
140
169
  normalizeOverrideAttributes(input) {
@@ -179,6 +208,52 @@ let FeatureFlagService = class FeatureFlagService {
179
208
  await this.cacheAdapter.setAll(flags, this.cacheTtlMs);
180
209
  return flags;
181
210
  }
211
+ defaultDetails(flagKey, reason, startTime, evaluationOptions, error) {
212
+ const defaultValue = this.resolveDefaultValue(flagKey, evaluationOptions);
213
+ const details = {
214
+ flagKey,
215
+ value: defaultValue,
216
+ result: defaultValue,
217
+ source: 'default',
218
+ reason,
219
+ defaultUsed: true,
220
+ evaluationTimeMs: Date.now() - startTime,
221
+ };
222
+ if (error) {
223
+ details.errorCode = error instanceof Error ? error.constructor.name : 'Error';
224
+ details.errorMessage = error instanceof Error ? error.message : String(error);
225
+ }
226
+ return details;
227
+ }
228
+ resolveDefaultValue(flagKey, evaluationOptions) {
229
+ return (evaluationOptions.defaultValue ??
230
+ this.options.flags?.[flagKey]?.defaultValue ??
231
+ this.options.defaultOnMissing ??
232
+ false);
233
+ }
234
+ shouldTrackExposure(flagKey, flag, evaluationOptions) {
235
+ return (evaluationOptions.trackExposure ??
236
+ this.options.flags?.[flagKey]?.trackExposure ??
237
+ readBooleanMetadata(flag?.metadata, 'trackExposure') ??
238
+ false);
239
+ }
240
+ emitEvaluation(details, context, evaluationOptions) {
241
+ const event = {
242
+ ...details,
243
+ evaluationTimeMs: details.evaluationTimeMs ?? 0,
244
+ };
245
+ if (evaluationOptions.includeContextInEvent ?? true) {
246
+ event.context = context;
247
+ }
248
+ this.eventPublisher.emit(feature_flag_events_1.FeatureFlagEvents.EVALUATED, event);
249
+ }
250
+ emitExposure(details, context, evaluationOptions) {
251
+ const event = { ...details };
252
+ if (evaluationOptions.includeContextInEvent === true) {
253
+ event.context = context;
254
+ }
255
+ this.eventPublisher.emit(feature_flag_events_1.FeatureFlagEvents.EXPOSED, event);
256
+ }
182
257
  };
183
258
  exports.FeatureFlagService = FeatureFlagService;
184
259
  exports.FeatureFlagService = FeatureFlagService = __decorate([
@@ -190,4 +265,8 @@ exports.FeatureFlagService = FeatureFlagService = __decorate([
190
265
  flag_context_resolver_1.FlagContextResolver,
191
266
  flag_event_publisher_1.FlagEventPublisher])
192
267
  ], FeatureFlagService);
268
+ function readBooleanMetadata(metadata, key) {
269
+ const value = metadata?.[key];
270
+ return typeof value === 'boolean' ? value : undefined;
271
+ }
193
272
  //# sourceMappingURL=feature-flag.service.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"feature-flag.service.js","sourceRoot":"","sources":["../../src/services/feature-flag.service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"feature-flag.service.js","sourceRoot":"","sources":["../../src/services/feature-flag.service.ts"],"names":[],"mappings":"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"}
@@ -1,13 +1,12 @@
1
1
  import { FeatureFlagWithOverrides } from '../interfaces/feature-flag.interface';
2
2
  import { EvaluationContext } from '../interfaces/evaluation-context.interface';
3
- import { FlagEvaluatedEvent } from '../events/feature-flag.events';
4
- type EvaluationSource = FlagEvaluatedEvent['source'];
5
- export interface EvaluationResult {
6
- result: boolean;
7
- source: EvaluationSource;
8
- }
3
+ import { BooleanEvaluationDetails, FlagEvaluatorOptions } from '../interfaces/evaluation-details.interface';
4
+ export type EvaluationResult = BooleanEvaluationDetails;
9
5
  export declare class FlagEvaluatorService {
10
- evaluate(flag: FeatureFlagWithOverrides, context: EvaluationContext): EvaluationResult;
6
+ evaluate(flag: FeatureFlagWithOverrides, context: EvaluationContext, options?: FlagEvaluatorOptions): EvaluationResult;
11
7
  private findMatchingOverride;
8
+ private resolveTargetingKey;
9
+ private getMetadataBucketBy;
10
+ private readBucketValue;
11
+ private result;
12
12
  }
13
- export {};
@@ -11,26 +11,29 @@ const common_1 = require("@nestjs/common");
11
11
  const hash_1 = require("../utils/hash");
12
12
  const targeting_attributes_1 = require("../utils/targeting-attributes");
13
13
  let FlagEvaluatorService = class FlagEvaluatorService {
14
- evaluate(flag, context) {
14
+ evaluate(flag, context, options = {}) {
15
15
  if (flag.archivedAt) {
16
- return { result: false, source: 'global' };
16
+ return this.result(flag.key, false, 'global', 'ARCHIVED');
17
17
  }
18
18
  const override = this.findMatchingOverride(flag.overrides, context);
19
19
  if (override) {
20
- return { result: override.enabled, source: 'override' };
20
+ return this.result(flag.key, override.enabled, 'override', 'OVERRIDE_MATCH', {
21
+ matchedOverrideId: override.id,
22
+ });
21
23
  }
22
24
  if (flag.percentage > 0) {
23
25
  if (flag.percentage === 100) {
24
- return { result: true, source: 'percentage' };
26
+ return this.result(flag.key, true, 'percentage', 'PERCENTAGE_MATCH');
25
27
  }
26
- const hashKey = context.userId ?? context.tenantId ?? '';
27
- if (!hashKey) {
28
- return { result: flag.enabled, source: 'global' };
28
+ const targetingKey = this.resolveTargetingKey(flag, context, options.bucketBy);
29
+ if (!targetingKey) {
30
+ return this.result(flag.key, flag.enabled, 'global', 'PERCENTAGE_NO_TARGETING_KEY');
29
31
  }
30
- const bucket = (0, hash_1.murmurhash3)(flag.key + hashKey) % 100;
31
- return { result: bucket < flag.percentage, source: 'percentage' };
32
+ const bucket = (0, hash_1.murmurhash3)(flag.key + targetingKey) % 100;
33
+ const enabled = bucket < flag.percentage;
34
+ return this.result(flag.key, enabled, 'percentage', enabled ? 'PERCENTAGE_MATCH' : 'PERCENTAGE_MISS', { bucket, targetingKey });
32
35
  }
33
- return { result: flag.enabled, source: 'global' };
36
+ return this.result(flag.key, flag.enabled, 'global', 'GLOBAL');
34
37
  }
35
38
  findMatchingOverride(overrides, context) {
36
39
  const contextAttributes = context.attributes ?? {};
@@ -38,11 +41,60 @@ let FlagEvaluatorService = class FlagEvaluatorService {
38
41
  .filter((override) => (0, targeting_attributes_1.matchesTargetingAttributes)(override.attributes, contextAttributes))
39
42
  .sort(compareOverrides)[0] ?? null);
40
43
  }
44
+ resolveTargetingKey(flag, context, bucketBy) {
45
+ if (context.targetingKey) {
46
+ return context.targetingKey;
47
+ }
48
+ const configuredBucketBy = bucketBy ?? this.getMetadataBucketBy(flag.metadata);
49
+ if (configuredBucketBy) {
50
+ const configuredValue = this.readBucketValue(context, configuredBucketBy);
51
+ if (configuredValue) {
52
+ return configuredValue;
53
+ }
54
+ }
55
+ return context.userId ?? context.tenantId ?? '';
56
+ }
57
+ getMetadataBucketBy(metadata) {
58
+ const bucketBy = metadata.bucketBy;
59
+ return typeof bucketBy === 'string' && bucketBy.length > 0 ? bucketBy : undefined;
60
+ }
61
+ readBucketValue(context, bucketBy) {
62
+ if (bucketBy === 'userId') {
63
+ return context.userId ?? '';
64
+ }
65
+ if (bucketBy === 'tenantId') {
66
+ return context.tenantId ?? '';
67
+ }
68
+ if (bucketBy === 'environment') {
69
+ return context.environment ?? '';
70
+ }
71
+ if (bucketBy === 'targetingKey') {
72
+ return context.targetingKey ?? '';
73
+ }
74
+ return stringifyBucketValue(context.attributes?.[bucketBy]);
75
+ }
76
+ result(flagKey, value, source, reason, details = {}) {
77
+ return {
78
+ flagKey,
79
+ value,
80
+ result: value,
81
+ source,
82
+ reason,
83
+ defaultUsed: false,
84
+ ...details,
85
+ };
86
+ }
41
87
  };
42
88
  exports.FlagEvaluatorService = FlagEvaluatorService;
43
89
  exports.FlagEvaluatorService = FlagEvaluatorService = __decorate([
44
90
  (0, common_1.Injectable)()
45
91
  ], FlagEvaluatorService);
92
+ function stringifyBucketValue(value) {
93
+ if (value === undefined || value === null) {
94
+ return '';
95
+ }
96
+ return String(value);
97
+ }
46
98
  function compareOverrides(a, b) {
47
99
  const specificityDelta = Object.keys(b.attributes).length - Object.keys(a.attributes).length;
48
100
  if (specificityDelta !== 0) {