@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
package/README.md CHANGED
@@ -12,15 +12,17 @@ DB-backed feature flags for NestJS + Prisma + PostgreSQL -- attribute-targeted o
12
12
 
13
13
  - **Database-backed** -- flags stored in PostgreSQL via Prisma, no external service required
14
14
  - **Attribute-targeted overrides** -- exact-match targeting for tenants, users, environments, plans, regions, or custom dimensions
15
- - **Percentage rollouts** -- deterministic hashing (murmurhash3) for consistent per-user bucketing
15
+ - **Percentage rollouts** -- deterministic hashing (murmurhash3) with explicit `targetingKey` / `bucketBy`
16
16
  - **Guard decorator** -- `@FeatureFlag()` automatically gates routes and controllers
17
17
  - **Bypass decorator** -- `@BypassFeatureFlag()` exempts health checks and public endpoints
18
- - **Programmatic evaluation** -- `isEnabled()` and `evaluateAll()` for service-layer logic
18
+ - **Programmatic evaluation** -- `isEnabled()`, `evaluateBoolean()`, and `evaluateAll()` for service-layer logic
19
+ - **Type-safe registry helpers** -- define flag keys, defaults, rollout bucket keys, exposure tracking, and lifecycle metadata in code
19
20
  - **Built-in caching** -- configurable TTL with manual invalidation; Redis Pub/Sub for multi-instance
20
21
  - **Pluggable persistence** -- `FeatureFlagRepository` interface for custom backends (Prisma default)
21
22
  - **Pluggable tenancy** -- `TenantContextProvider` interface for custom tenant resolution
22
23
  - **Admin REST API** -- opt-in `FeatureFlagAdminModule` with guard injection and proper error responses
23
24
  - **Event system** -- optional integration with `@nestjs/event-emitter` for audit and observability
25
+ - **OpenFeature adapter** -- optional boolean-only provider at `@nestarc/feature-flag/openfeature`
24
26
  - **Testing utilities** -- drop-in `TestFeatureFlagModule` for unit and integration tests
25
27
 
26
28
  ## Installation
@@ -32,9 +34,13 @@ npm install @nestarc/feature-flag
32
34
  ### Peer dependencies
33
35
 
34
36
  ```bash
35
- npm install @nestjs/common @nestjs/core @prisma/client class-transformer class-validator rxjs reflect-metadata
37
+ npm install @nestjs/common @nestjs/core @prisma/client @prisma/adapter-pg pg class-transformer class-validator rxjs reflect-metadata
38
+ npm install --save-dev prisma
36
39
  ```
37
40
 
41
+ Prisma 7 requires Node.js 20.19+, 22.12+, or 24+. This package follows the
42
+ same runtime requirement.
43
+
38
44
  ### Optional
39
45
 
40
46
  ```bash
@@ -43,6 +49,9 @@ npm install @nestjs/event-emitter
43
49
 
44
50
  # Required only if you use RedisCacheAdapter
45
51
  npm install ioredis
52
+
53
+ # Required only if you use the OpenFeature adapter with the SDK
54
+ npm install @openfeature/server-sdk
46
55
  ```
47
56
 
48
57
  ## Redis Cache (Multi-Instance)
@@ -71,6 +80,45 @@ When a flag is updated on any instance, all other instances are notified via Pub
71
80
 
72
81
  ## Prisma Schema
73
82
 
83
+ Prisma 7 keeps connection URLs in `prisma.config.ts` and requires a database
84
+ driver adapter at runtime. A minimal PostgreSQL setup is:
85
+
86
+ ```ts
87
+ // prisma.config.ts
88
+ import 'dotenv/config';
89
+ import { defineConfig, env } from 'prisma/config';
90
+
91
+ export default defineConfig({
92
+ schema: 'prisma/schema.prisma',
93
+ migrations: { path: 'prisma/migrations' },
94
+ datasource: { url: env('DATABASE_URL') },
95
+ });
96
+ ```
97
+
98
+ ```prisma
99
+ generator client {
100
+ provider = "prisma-client"
101
+ output = "../src/generated/prisma"
102
+ }
103
+
104
+ datasource db {
105
+ provider = "postgresql"
106
+ }
107
+ ```
108
+
109
+ Create the client with `@prisma/adapter-pg`, then pass that instance to
110
+ `FeatureFlagModule`:
111
+
112
+ ```ts
113
+ import { PrismaPg } from '@prisma/adapter-pg';
114
+ import { PrismaClient } from './generated/prisma/client';
115
+
116
+ const adapter = new PrismaPg({
117
+ connectionString: process.env.DATABASE_URL,
118
+ });
119
+ const prisma = new PrismaClient({ adapter });
120
+ ```
121
+
74
122
  Add the following models to your `schema.prisma`:
75
123
 
76
124
  ```prisma
@@ -279,6 +327,14 @@ getPremiumContent() { ... }
279
327
 
280
328
  When the flag is disabled, the guard responds with the given `statusCode` (default `403`) and optional `fallback` body.
281
329
 
330
+ Use `defaultValue` when a route should choose an invocation-specific fallback if a flag is missing or evaluation fails:
331
+
332
+ ```typescript
333
+ @FeatureFlag('OPTIONAL_PREVIEW', { defaultValue: true })
334
+ @Get('preview')
335
+ getPreview() { ... }
336
+ ```
337
+
282
338
  ### Bypassing the guard
283
339
 
284
340
  Use `@BypassFeatureFlag()` on methods that should always be accessible, even when a class-level flag is applied:
@@ -348,6 +404,78 @@ Passing `null` explicitly clears that dimension, suppressing any ambient value f
348
404
  const globalResult = await this.flags.isEnabled('MY_FLAG', { userId: null });
349
405
  ```
350
406
 
407
+ ### Detailed boolean evaluation
408
+
409
+ Use `evaluateBoolean()` when you need to explain why a flag resolved to a value:
410
+
411
+ ```typescript
412
+ const details = await this.flags.evaluateBoolean(
413
+ 'NEW_CHECKOUT',
414
+ { targetingKey: 'tenant-1', tenantId: 'tenant-1' },
415
+ { defaultValue: false, trackExposure: true },
416
+ );
417
+
418
+ console.log(details);
419
+ // {
420
+ // flagKey: 'NEW_CHECKOUT',
421
+ // value: true,
422
+ // result: true,
423
+ // source: 'percentage',
424
+ // reason: 'PERCENTAGE_MATCH',
425
+ // defaultUsed: false,
426
+ // bucket: 17,
427
+ // targetingKey: 'tenant-1',
428
+ // evaluationTimeMs: 1
429
+ // }
430
+ ```
431
+
432
+ Missing flags and evaluation errors return the selected default instead of throwing. Default priority is:
433
+
434
+ 1. Invocation `defaultValue`
435
+ 2. Registry `defaultValue`
436
+ 3. Module `defaultOnMissing`
437
+ 4. `false`
438
+
439
+ ### Type-safe flag registry
440
+
441
+ ```typescript
442
+ import { defineFlags, createFeatureFlagClient } from '@nestarc/feature-flag';
443
+
444
+ export const flags = defineFlags({
445
+ NEW_CHECKOUT: {
446
+ defaultValue: false,
447
+ bucketBy: 'tenantId',
448
+ trackExposure: true,
449
+ owner: 'payments',
450
+ tags: ['checkout'],
451
+ staleAt: '2026-09-01',
452
+ expiresAt: '2026-12-01',
453
+ },
454
+ });
455
+
456
+ const flagClient = createFeatureFlagClient(featureFlagService, flags);
457
+ const enabled = await flagClient.isEnabled('NEW_CHECKOUT', { tenantId: 'tenant-1' });
458
+ ```
459
+
460
+ You can also pass the registry to `FeatureFlagModule.forRoot({ flags })` so service-level fallback and `bucketBy` defaults apply to direct `FeatureFlagService` calls.
461
+
462
+ ### OpenFeature boolean adapter
463
+
464
+ The optional adapter lives on a separate subpath and delegates boolean resolution to `FeatureFlagService`:
465
+
466
+ ```typescript
467
+ import { createOpenFeatureBooleanProvider } from '@nestarc/feature-flag/openfeature';
468
+
469
+ const provider = createOpenFeatureBooleanProvider(featureFlagService);
470
+ const result = await provider.resolveBooleanEvaluation(
471
+ 'NEW_CHECKOUT',
472
+ false,
473
+ { targetingKey: 'tenant-1', tenantId: 'tenant-1', plan: 'pro' },
474
+ );
475
+ ```
476
+
477
+ Only boolean evaluation is supported in v0.4.0. Variant flags and string/number/json remote config remain out of scope.
478
+
351
479
  ## Attribute Targeting
352
480
 
353
481
  Overrides match exact attributes. Every key/value in an override's `attributes` object must exist in the evaluation context attributes for the override to apply.
@@ -432,6 +560,7 @@ export class AppModule {}
432
560
  | Event constant | Event string | Payload type |
433
561
  | ---------------------------------------- | ---------------------------------- | -------------------- |
434
562
  | `FeatureFlagEvents.EVALUATED` | `feature-flag.evaluated` | `FlagEvaluatedEvent` |
563
+ | `FeatureFlagEvents.EXPOSED` | `feature-flag.exposed` | `FlagExposedEvent` |
435
564
  | `FeatureFlagEvents.CREATED` | `feature-flag.created` | `FlagMutationEvent` |
436
565
  | `FeatureFlagEvents.UPDATED` | `feature-flag.updated` | `FlagMutationEvent` |
437
566
  | `FeatureFlagEvents.ARCHIVED` | `feature-flag.archived` | `FlagMutationEvent` |
@@ -449,11 +578,13 @@ import { FeatureFlagEvents, FlagEvaluatedEvent } from '@nestarc/feature-flag';
449
578
  export class FlagAuditListener {
450
579
  @OnEvent(FeatureFlagEvents.EVALUATED)
451
580
  handleEvaluation(event: FlagEvaluatedEvent) {
452
- console.log(`Flag ${event.flagKey} = ${event.result} (source: ${event.source})`);
581
+ console.log(`Flag ${event.flagKey} = ${event.result} (${event.reason})`);
453
582
  }
454
583
  }
455
584
  ```
456
585
 
586
+ Exposure events are opt-in per call, registry entry, or flag metadata via `trackExposure`. They do not persist analytics; attach your own listener if you need sampling, batching, or storage.
587
+
457
588
  ## Testing
458
589
 
459
590
  Import `TestFeatureFlagModule` from the `/testing` subpath to stub flag values in tests without a database connection:
@@ -489,11 +620,29 @@ describe('DashboardController', () => {
489
620
 
490
621
  `TestFeatureFlagModule.register()` provides a global mock of `FeatureFlagService`:
491
622
  - `isEnabled(key)` returns the boolean you specified (defaulting to `false` for unregistered keys)
623
+ - `evaluateBoolean(key)` returns `BooleanEvaluationDetails`
492
624
  - `evaluateAll()` returns the full flag map
493
625
  - `create()`, `update()`, `archive()`, `findByKey()`, `findAll()` return full `FeatureFlagWithOverrides` stub objects
494
626
  - `findByKey()` throws `NotFoundException` for unknown keys
495
627
 
496
- This is a **stateless boolean stub** -- write operations do not persist state across calls. For stateful test doubles, use your own mock implementation.
628
+ For registry-based tests, use `registerRegistry()` and the injected controller:
629
+
630
+ ```typescript
631
+ import {
632
+ TestFeatureFlagController,
633
+ TestFeatureFlagModule,
634
+ } from '@nestarc/feature-flag/testing';
635
+
636
+ const module = await Test.createTestingModule({
637
+ imports: [TestFeatureFlagModule.registerRegistry(flags)],
638
+ }).compile();
639
+
640
+ const testFlags = module.get(TestFeatureFlagController);
641
+ testFlags.set('NEW_CHECKOUT', true);
642
+ testFlags.reset();
643
+ ```
644
+
645
+ The testing controller keeps state inside the compiled testing module. CRUD-style write methods on the mocked service still return stub objects and do not persist database rows.
497
646
 
498
647
  ## Evaluation Priority
499
648
 
@@ -503,10 +652,10 @@ When `isEnabled()` is called, flags are evaluated through the current cascade. T
503
652
  | -------- | ---------------------- | ------------------------------------------------------------------ |
504
653
  | 1 | **Archived** | If the flag has `archivedAt` set, evaluation always returns `false` |
505
654
  | 2 | **Attribute override** | Best override whose attributes are all present in the evaluation context |
506
- | 3 | **Percentage rollout** | Deterministic hash of `flagKey + userId` (or `tenantId`) mod 100 |
655
+ | 3 | **Percentage rollout** | Deterministic hash of `flagKey + targetingKey` mod 100 |
507
656
  | 4 | **Global default** | The flag's `enabled` field |
508
657
 
509
- 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.
658
+ Percentage rollout uses murmurhash3 for deterministic bucketing. The targeting key is resolved in this order: explicit `context.targetingKey`, registry or metadata `bucketBy`, then the legacy `userId ?? tenantId` fallback.
510
659
 
511
660
  ## Configuration Reference
512
661
 
@@ -520,6 +669,7 @@ Percentage rollout uses murmurhash3 for deterministic bucketing: the same user a
520
669
  | `defaultOnMissing` | `boolean` | `false` | Value returned when a flag key does not exist in the database |
521
670
  | `emitEvents` | `boolean` | `false` | Emit lifecycle events via `@nestjs/event-emitter` |
522
671
  | `cacheAdapter` | `CacheAdapter` | `MemoryCacheAdapter` | Pluggable cache backend (e.g. `RedisCacheAdapter`) |
672
+ | `flags` | `FlagRegistry` | `undefined` | Optional typed registry for defaults, `bucketBy`, and exposure settings |
523
673
 
524
674
  ### FeatureFlagModuleRootOptions
525
675
 
@@ -587,6 +737,7 @@ export class AppModule {}
587
737
  | GET | `/feature-flags/:key` | Get a single flag | 404 not found |
588
738
  | PATCH | `/feature-flags/:key` | Update a flag | 404 not found, 400 invalid percentage |
589
739
  | DELETE | `/feature-flags/:key` | Archive a flag | 404 not found |
740
+ | POST | `/feature-flags/:key/evaluate` | Evaluate a flag without mutating it | |
590
741
  | POST | `/feature-flags/:key/overrides` | Set an override | 404 flag not found |
591
742
  | DELETE | `/feature-flags/:key/overrides` | Remove an override | 404 flag not found |
592
743
 
@@ -656,16 +807,16 @@ export class AppModule {}
656
807
 
657
808
  ## Performance
658
809
 
659
- Measured with PostgreSQL 16, Prisma 6, 500 iterations on Apple Silicon:
810
+ Measured with PostgreSQL 16, Prisma 7.9.1, 500 iterations on Apple Silicon:
660
811
 
661
812
  | Scenario | Avg | P50 | P95 | P99 |
662
813
  |----------|-----|-----|-----|-----|
663
- | **isEnabled() — cache hit** | **0.04ms** | **0.03ms** | **0.05ms** | **0.07ms** |
664
- | isEnabled() — cache miss (DB lookup) | 1.30ms | 1.14ms | 2.54ms | 3.69ms |
665
- | isEnabled() — override cascade (cold) | 1.07ms | 1.02ms | 1.43ms | 2.11ms |
666
- | **evaluateAll() — 50 flags (mixed)** | **0.19ms** | **0.04ms** | **1.55ms** | **1.71ms** |
814
+ | **isEnabled() — cache hit** | **0.04ms** | **0.04ms** | **0.05ms** | **0.12ms** |
815
+ | isEnabled() — cache miss (DB lookup) | 1.17ms | 1.10ms | 1.62ms | 2.04ms |
816
+ | isEnabled() — override cascade (cold) | 0.95ms | 0.89ms | 1.36ms | 1.87ms |
817
+ | **evaluateAll() — 50 flags (mixed)** | **0.19ms** | **0.04ms** | **1.47ms** | **1.78ms** |
667
818
 
668
- Cache speedup: **32.5x** (hit vs miss). Keep the default 30s cache TTL for optimal performance.
819
+ Cache speedup: **29.2x** (hit vs miss). Keep the default 30s cache TTL for optimal performance.
669
820
 
670
821
  > Reproduce: `docker compose up -d && dotenv -e .env.test -- npx ts-node benchmarks/evaluation-overhead.ts`
671
822
 
@@ -1,6 +1,7 @@
1
1
  import { FeatureFlagService } from '../services/feature-flag.service';
2
2
  import { FeatureFlagWithOverrides } from '../interfaces/feature-flag.interface';
3
- import { CreateFeatureFlagDto, RemoveOverrideDto, SetOverrideDto, UpdateFeatureFlagDto } from './feature-flag-admin.dto';
3
+ import { BooleanEvaluationDetails } from '../interfaces/evaluation-details.interface';
4
+ import { CreateFeatureFlagDto, EvaluateFeatureFlagDto, RemoveOverrideDto, SetOverrideDto, UpdateFeatureFlagDto } from './feature-flag-admin.dto';
4
5
  export declare class FeatureFlagAdminController {
5
6
  private readonly service;
6
7
  constructor(service: FeatureFlagService);
@@ -9,6 +10,7 @@ export declare class FeatureFlagAdminController {
9
10
  findByKey(key: string): Promise<FeatureFlagWithOverrides>;
10
11
  update(key: string, input: UpdateFeatureFlagDto): Promise<FeatureFlagWithOverrides>;
11
12
  archive(key: string): Promise<FeatureFlagWithOverrides>;
13
+ evaluate(key: string, input: EvaluateFeatureFlagDto): Promise<BooleanEvaluationDetails>;
12
14
  setOverride(key: string, input: SetOverrideDto): Promise<void>;
13
15
  removeOverride(key: string, input: RemoveOverrideDto): Promise<void>;
14
16
  }
@@ -35,6 +35,10 @@ let FeatureFlagAdminController = class FeatureFlagAdminController {
35
35
  archive(key) {
36
36
  return this.service.archive(key);
37
37
  }
38
+ evaluate(key, input) {
39
+ const { context, ...options } = input;
40
+ return this.service.evaluateBoolean(key, context, options);
41
+ }
38
42
  setOverride(key, input) {
39
43
  return this.service.setOverride(key, input);
40
44
  }
@@ -78,6 +82,14 @@ __decorate([
78
82
  __metadata("design:paramtypes", [String]),
79
83
  __metadata("design:returntype", Promise)
80
84
  ], FeatureFlagAdminController.prototype, "archive", null);
85
+ __decorate([
86
+ (0, common_1.Post)(':key/evaluate'),
87
+ __param(0, (0, common_1.Param)('key')),
88
+ __param(1, (0, common_1.Body)()),
89
+ __metadata("design:type", Function),
90
+ __metadata("design:paramtypes", [String, feature_flag_admin_dto_1.EvaluateFeatureFlagDto]),
91
+ __metadata("design:returntype", Promise)
92
+ ], FeatureFlagAdminController.prototype, "evaluate", null);
81
93
  __decorate([
82
94
  (0, common_1.Post)(':key/overrides'),
83
95
  __param(0, (0, common_1.Param)('key')),
@@ -1 +1 @@
1
- {"version":3,"file":"feature-flag-admin.controller.js","sourceRoot":"","sources":["../../src/admin/feature-flag-admin.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAUwB;AACxB,2EAAsE;AAEtE,qEAKkC;AAU3B,IAAM,0BAA0B,GAAhC,MAAM,0BAA0B;IACrC,YAA6B,OAA2B;QAA3B,YAAO,GAAP,OAAO,CAAoB;IAAG,CAAC;IAG5D,MAAM,CAAS,KAA2B;QACxC,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACpC,CAAC;IAGD,OAAO;QACL,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;IAChC,CAAC;IAGD,SAAS,CAAe,GAAW;QACjC,OAAO,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;IACrC,CAAC;IAGD,MAAM,CACU,GAAW,EACjB,KAA2B;QAEnC,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IACzC,CAAC;IAGD,OAAO,CAAe,GAAW;QAC/B,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACnC,CAAC;IAGD,WAAW,CAAe,GAAW,EAAU,KAAqB;QAClE,OAAO,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IAC9C,CAAC;IAGD,cAAc,CAAe,GAAW,EAAU,KAAwB;QACxE,OAAO,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IACjD,CAAC;CACF,CAAA;AAxCY,gEAA0B;AAIrC;IADC,IAAA,aAAI,GAAE;IACC,WAAA,IAAA,aAAI,GAAE,CAAA;;qCAAQ,6CAAoB;;wDAEzC;AAGD;IADC,IAAA,YAAG,GAAE;;;;yDAGL;AAGD;IADC,IAAA,YAAG,EAAC,MAAM,CAAC;IACD,WAAA,IAAA,cAAK,EAAC,KAAK,CAAC,CAAA;;;;2DAEtB;AAGD;IADC,IAAA,cAAK,EAAC,MAAM,CAAC;IAEX,WAAA,IAAA,cAAK,EAAC,KAAK,CAAC,CAAA;IACZ,WAAA,IAAA,aAAI,GAAE,CAAA;;6CAAQ,6CAAoB;;wDAGpC;AAGD;IADC,IAAA,eAAM,EAAC,MAAM,CAAC;IACN,WAAA,IAAA,cAAK,EAAC,KAAK,CAAC,CAAA;;;;yDAEpB;AAGD;IADC,IAAA,aAAI,EAAC,gBAAgB,CAAC;IACV,WAAA,IAAA,cAAK,EAAC,KAAK,CAAC,CAAA;IAAe,WAAA,IAAA,aAAI,GAAE,CAAA;;6CAAQ,uCAAc;;6DAEnE;AAGD;IADC,IAAA,eAAM,EAAC,gBAAgB,CAAC;IACT,WAAA,IAAA,cAAK,EAAC,KAAK,CAAC,CAAA;IAAe,WAAA,IAAA,aAAI,GAAE,CAAA;;6CAAQ,0CAAiB;;gEAEzE;qCAvCU,0BAA0B;IARtC,IAAA,mBAAU,GAAE;IACZ,IAAA,iBAAQ,EACP,IAAI,uBAAc,CAAC;QACjB,SAAS,EAAE,IAAI;QACf,oBAAoB,EAAE,IAAI;QAC1B,SAAS,EAAE,IAAI;KAChB,CAAC,CACH;qCAEuC,yCAAkB;GAD7C,0BAA0B,CAwCtC"}
1
+ {"version":3,"file":"feature-flag-admin.controller.js","sourceRoot":"","sources":["../../src/admin/feature-flag-admin.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAUwB;AACxB,2EAAsE;AAGtE,qEAMkC;AAU3B,IAAM,0BAA0B,GAAhC,MAAM,0BAA0B;IACrC,YAA6B,OAA2B;QAA3B,YAAO,GAAP,OAAO,CAAoB;IAAG,CAAC;IAG5D,MAAM,CAAS,KAA2B;QACxC,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACpC,CAAC;IAGD,OAAO;QACL,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;IAChC,CAAC;IAGD,SAAS,CAAe,GAAW;QACjC,OAAO,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;IACrC,CAAC;IAGD,MAAM,CACU,GAAW,EACjB,KAA2B;QAEnC,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IACzC,CAAC;IAGD,OAAO,CAAe,GAAW;QAC/B,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACnC,CAAC;IAGD,QAAQ,CACQ,GAAW,EACjB,KAA6B;QAErC,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,EAAE,GAAG,KAAK,CAAC;QACtC,OAAO,IAAI,CAAC,OAAO,CAAC,eAAe,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IAC7D,CAAC;IAGD,WAAW,CAAe,GAAW,EAAU,KAAqB;QAClE,OAAO,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IAC9C,CAAC;IAGD,cAAc,CAAe,GAAW,EAAU,KAAwB;QACxE,OAAO,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IACjD,CAAC;CACF,CAAA;AAjDY,gEAA0B;AAIrC;IADC,IAAA,aAAI,GAAE;IACC,WAAA,IAAA,aAAI,GAAE,CAAA;;qCAAQ,6CAAoB;;wDAEzC;AAGD;IADC,IAAA,YAAG,GAAE;;;;yDAGL;AAGD;IADC,IAAA,YAAG,EAAC,MAAM,CAAC;IACD,WAAA,IAAA,cAAK,EAAC,KAAK,CAAC,CAAA;;;;2DAEtB;AAGD;IADC,IAAA,cAAK,EAAC,MAAM,CAAC;IAEX,WAAA,IAAA,cAAK,EAAC,KAAK,CAAC,CAAA;IACZ,WAAA,IAAA,aAAI,GAAE,CAAA;;6CAAQ,6CAAoB;;wDAGpC;AAGD;IADC,IAAA,eAAM,EAAC,MAAM,CAAC;IACN,WAAA,IAAA,cAAK,EAAC,KAAK,CAAC,CAAA;;;;yDAEpB;AAGD;IADC,IAAA,aAAI,EAAC,eAAe,CAAC;IAEnB,WAAA,IAAA,cAAK,EAAC,KAAK,CAAC,CAAA;IACZ,WAAA,IAAA,aAAI,GAAE,CAAA;;6CAAQ,+CAAsB;;0DAItC;AAGD;IADC,IAAA,aAAI,EAAC,gBAAgB,CAAC;IACV,WAAA,IAAA,cAAK,EAAC,KAAK,CAAC,CAAA;IAAe,WAAA,IAAA,aAAI,GAAE,CAAA;;6CAAQ,uCAAc;;6DAEnE;AAGD;IADC,IAAA,eAAM,EAAC,gBAAgB,CAAC;IACT,WAAA,IAAA,cAAK,EAAC,KAAK,CAAC,CAAA;IAAe,WAAA,IAAA,aAAI,GAAE,CAAA;;6CAAQ,0CAAiB;;gEAEzE;qCAhDU,0BAA0B;IARtC,IAAA,mBAAU,GAAE;IACZ,IAAA,iBAAQ,EACP,IAAI,uBAAc,CAAC;QACjB,SAAS,EAAE,IAAI;QACf,oBAAoB,EAAE,IAAI;QAC1B,SAAS,EAAE,IAAI;KAChB,CAAC,CACH;qCAEuC,yCAAkB;GAD7C,0BAA0B,CAiDtC"}
@@ -1,3 +1,5 @@
1
+ import { EvaluationContext } from '../interfaces/evaluation-context.interface';
2
+ import { EvaluateBooleanOptions } from '../interfaces/evaluation-details.interface';
1
3
  import { TargetingAttributes } from '../interfaces/feature-flag.interface';
2
4
  export declare class CreateFeatureFlagDto {
3
5
  key: string;
@@ -20,3 +22,9 @@ export declare class SetOverrideDto {
20
22
  export declare class RemoveOverrideDto {
21
23
  attributes: TargetingAttributes;
22
24
  }
25
+ export declare class EvaluateFeatureFlagDto implements EvaluateBooleanOptions {
26
+ context?: EvaluationContext;
27
+ defaultValue?: boolean;
28
+ trackExposure?: boolean;
29
+ includeContextInEvent?: boolean;
30
+ }
@@ -9,7 +9,7 @@ var __metadata = (this && this.__metadata) || function (k, v) {
9
9
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.RemoveOverrideDto = exports.SetOverrideDto = exports.UpdateFeatureFlagDto = exports.CreateFeatureFlagDto = void 0;
12
+ exports.EvaluateFeatureFlagDto = exports.RemoveOverrideDto = exports.SetOverrideDto = exports.UpdateFeatureFlagDto = exports.CreateFeatureFlagDto = void 0;
13
13
  const class_validator_1 = require("class-validator");
14
14
  const targeting_attributes_validator_1 = require("./targeting-attributes.validator");
15
15
  class CreateFeatureFlagDto {
@@ -90,4 +90,27 @@ __decorate([
90
90
  (0, targeting_attributes_validator_1.IsTargetingAttributes)(),
91
91
  __metadata("design:type", Object)
92
92
  ], RemoveOverrideDto.prototype, "attributes", void 0);
93
+ class EvaluateFeatureFlagDto {
94
+ }
95
+ exports.EvaluateFeatureFlagDto = EvaluateFeatureFlagDto;
96
+ __decorate([
97
+ (0, class_validator_1.IsOptional)(),
98
+ (0, class_validator_1.IsObject)(),
99
+ __metadata("design:type", Object)
100
+ ], EvaluateFeatureFlagDto.prototype, "context", void 0);
101
+ __decorate([
102
+ (0, class_validator_1.IsOptional)(),
103
+ (0, class_validator_1.IsBoolean)(),
104
+ __metadata("design:type", Boolean)
105
+ ], EvaluateFeatureFlagDto.prototype, "defaultValue", void 0);
106
+ __decorate([
107
+ (0, class_validator_1.IsOptional)(),
108
+ (0, class_validator_1.IsBoolean)(),
109
+ __metadata("design:type", Boolean)
110
+ ], EvaluateFeatureFlagDto.prototype, "trackExposure", void 0);
111
+ __decorate([
112
+ (0, class_validator_1.IsOptional)(),
113
+ (0, class_validator_1.IsBoolean)(),
114
+ __metadata("design:type", Boolean)
115
+ ], EvaluateFeatureFlagDto.prototype, "includeContextInEvent", void 0);
93
116
  //# sourceMappingURL=feature-flag-admin.dto.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"feature-flag-admin.dto.js","sourceRoot":"","sources":["../../src/admin/feature-flag-admin.dto.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qDASyB;AAEzB,qFAAyE;AAEzE,MAAa,oBAAoB;CAsBhC;AAtBD,oDAsBC;AAnBC;IAFC,IAAA,0BAAQ,GAAE;IACV,IAAA,4BAAU,GAAE;;iDACA;AAIb;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;yDACU;AAIrB;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,2BAAS,GAAE;;qDACM;AAMlB;IAJC,IAAA,4BAAU,GAAE;IACZ,IAAA,uBAAK,GAAE;IACP,IAAA,qBAAG,EAAC,CAAC,CAAC;IACN,IAAA,qBAAG,EAAC,GAAG,CAAC;;wDACW;AAIpB;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;sDACwB;AAGrC,MAAa,oBAAoB;CAkBhC;AAlBD,oDAkBC;AAfC;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;yDACU;AAIrB;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,2BAAS,GAAE;;qDACM;AAMlB;IAJC,IAAA,4BAAU,GAAE;IACZ,IAAA,uBAAK,GAAE;IACP,IAAA,qBAAG,EAAC,CAAC,CAAC;IACN,IAAA,qBAAG,EAAC,GAAG,CAAC;;wDACW;AAIpB;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;sDACwB;AAGrC,MAAa,cAAc;CAU1B;AAVD,wCAUC;AARC;IADC,IAAA,sDAAqB,GAAE;;kDACS;AAGjC;IADC,IAAA,2BAAS,GAAE;;+CACM;AAIlB;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,uBAAK,GAAE;;gDACU;AAGpB,MAAa,iBAAiB;CAG7B;AAHD,8CAGC;AADC;IADC,IAAA,sDAAqB,GAAE;;qDACS"}
1
+ {"version":3,"file":"feature-flag-admin.dto.js","sourceRoot":"","sources":["../../src/admin/feature-flag-admin.dto.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qDASyB;AAIzB,qFAAyE;AAEzE,MAAa,oBAAoB;CAsBhC;AAtBD,oDAsBC;AAnBC;IAFC,IAAA,0BAAQ,GAAE;IACV,IAAA,4BAAU,GAAE;;iDACA;AAIb;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;yDACU;AAIrB;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,2BAAS,GAAE;;qDACM;AAMlB;IAJC,IAAA,4BAAU,GAAE;IACZ,IAAA,uBAAK,GAAE;IACP,IAAA,qBAAG,EAAC,CAAC,CAAC;IACN,IAAA,qBAAG,EAAC,GAAG,CAAC;;wDACW;AAIpB;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;sDACwB;AAGrC,MAAa,oBAAoB;CAkBhC;AAlBD,oDAkBC;AAfC;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;yDACU;AAIrB;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,2BAAS,GAAE;;qDACM;AAMlB;IAJC,IAAA,4BAAU,GAAE;IACZ,IAAA,uBAAK,GAAE;IACP,IAAA,qBAAG,EAAC,CAAC,CAAC;IACN,IAAA,qBAAG,EAAC,GAAG,CAAC;;wDACW;AAIpB;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;sDACwB;AAGrC,MAAa,cAAc;CAU1B;AAVD,wCAUC;AARC;IADC,IAAA,sDAAqB,GAAE;;kDACS;AAGjC;IADC,IAAA,2BAAS,GAAE;;+CACM;AAIlB;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,uBAAK,GAAE;;gDACU;AAGpB,MAAa,iBAAiB;CAG7B;AAHD,8CAGC;AADC;IADC,IAAA,sDAAqB,GAAE;;qDACS;AAGnC,MAAa,sBAAsB;CAgBlC;AAhBD,wDAgBC;AAbC;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;uDACiB;AAI5B;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,2BAAS,GAAE;;4DACW;AAIvB;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,2BAAS,GAAE;;6DACY;AAIxB;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,2BAAS,GAAE;;qEACoB"}
@@ -1,6 +1,8 @@
1
1
  import { EvaluationContext } from '../interfaces/evaluation-context.interface';
2
+ import { EvaluationReason, EvaluationSource } from '../interfaces/evaluation-details.interface';
2
3
  export declare const FeatureFlagEvents: {
3
4
  readonly EVALUATED: "feature-flag.evaluated";
5
+ readonly EXPOSED: "feature-flag.exposed";
4
6
  readonly CREATED: "feature-flag.created";
5
7
  readonly UPDATED: "feature-flag.updated";
6
8
  readonly ARCHIVED: "feature-flag.archived";
@@ -11,13 +13,39 @@ export declare const FeatureFlagEvents: {
11
13
  export interface FlagEvaluatedEvent {
12
14
  flagKey: string;
13
15
  result: boolean;
14
- context: EvaluationContext;
15
- source: 'override' | 'percentage' | 'global';
16
+ value?: boolean;
17
+ context?: EvaluationContext;
18
+ source: EvaluationSource;
19
+ reason?: EvaluationReason;
20
+ defaultUsed?: boolean;
21
+ errorCode?: string;
22
+ errorMessage?: string;
23
+ matchedOverrideId?: string;
24
+ bucket?: number;
25
+ targetingKey?: string;
16
26
  evaluationTimeMs: number;
17
27
  }
28
+ export interface FlagExposedEvent {
29
+ flagKey: string;
30
+ value: boolean;
31
+ result: boolean;
32
+ source: EvaluationSource;
33
+ reason: EvaluationReason;
34
+ defaultUsed: boolean;
35
+ context?: EvaluationContext;
36
+ matchedOverrideId?: string;
37
+ bucket?: number;
38
+ targetingKey?: string;
39
+ evaluationTimeMs?: number;
40
+ }
18
41
  export interface FlagMutationEvent {
19
42
  flagKey: string;
20
43
  action: 'created' | 'updated' | 'archived';
44
+ actorId?: string;
45
+ actorType?: string;
46
+ reason?: string;
47
+ requestId?: string;
48
+ correlationId?: string;
21
49
  }
22
50
  export interface FlagOverrideEvent {
23
51
  flagKey: string;
@@ -25,4 +53,9 @@ export interface FlagOverrideEvent {
25
53
  enabled?: boolean;
26
54
  priority?: number;
27
55
  action: 'set' | 'removed';
56
+ actorId?: string;
57
+ actorType?: string;
58
+ reason?: string;
59
+ requestId?: string;
60
+ correlationId?: string;
28
61
  }
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.FeatureFlagEvents = void 0;
4
4
  exports.FeatureFlagEvents = {
5
5
  EVALUATED: 'feature-flag.evaluated',
6
+ EXPOSED: 'feature-flag.exposed',
6
7
  CREATED: 'feature-flag.created',
7
8
  UPDATED: 'feature-flag.updated',
8
9
  ARCHIVED: 'feature-flag.archived',
@@ -1 +1 @@
1
- {"version":3,"file":"feature-flag.events.js","sourceRoot":"","sources":["../../src/events/feature-flag.events.ts"],"names":[],"mappings":";;;AAEa,QAAA,iBAAiB,GAAG;IAC/B,SAAS,EAAE,wBAAwB;IACnC,OAAO,EAAE,sBAAsB;IAC/B,OAAO,EAAE,sBAAsB;IAC/B,QAAQ,EAAE,uBAAuB;IACjC,YAAY,EAAE,2BAA2B;IACzC,gBAAgB,EAAE,+BAA+B;IACjD,iBAAiB,EAAE,gCAAgC;CAC3C,CAAC"}
1
+ {"version":3,"file":"feature-flag.events.js","sourceRoot":"","sources":["../../src/events/feature-flag.events.ts"],"names":[],"mappings":";;;AAMa,QAAA,iBAAiB,GAAG;IAC/B,SAAS,EAAE,wBAAwB;IACnC,OAAO,EAAE,sBAAsB;IAC/B,OAAO,EAAE,sBAAsB;IAC/B,OAAO,EAAE,sBAAsB;IAC/B,QAAQ,EAAE,uBAAuB;IACjC,YAAY,EAAE,2BAA2B;IACzC,gBAAgB,EAAE,+BAA+B;IACjD,iBAAiB,EAAE,gCAAgC;CAC3C,CAAC"}
@@ -0,0 +1,24 @@
1
+ import { EvaluationContext } from './interfaces/evaluation-context.interface';
2
+ import { BooleanEvaluationDetails, EvaluateBooleanOptions } from './interfaces/evaluation-details.interface';
3
+ import { FeatureFlagGuardOptions } from './interfaces/feature-flag.interface';
4
+ import { FeatureFlagLifecycleMetadata, FlagKey, FlagRegistry } from './interfaces/flag-registry.interface';
5
+ import { FeatureFlagService } from './services/feature-flag.service';
6
+ export interface TypedFeatureFlagClient<TFlags extends FlagRegistry> {
7
+ isEnabled<K extends FlagKey<TFlags>>(flagKey: K, context?: EvaluationContext, options?: EvaluateBooleanOptions): Promise<boolean>;
8
+ evaluateBoolean<K extends FlagKey<TFlags>>(flagKey: K, context?: EvaluationContext, options?: EvaluateBooleanOptions): Promise<BooleanEvaluationDetails>;
9
+ registry: TFlags;
10
+ }
11
+ export interface TypedFeatureFlagDecorators<TFlags extends FlagRegistry> {
12
+ FeatureFlag<K extends FlagKey<TFlags>>(flagKey: K, options?: FeatureFlagGuardOptions): ClassDecorator & MethodDecorator;
13
+ }
14
+ export type FlagLifecycleStatusName = 'active' | 'stale' | 'expired';
15
+ export interface FlagLifecycleStatus extends FeatureFlagLifecycleMetadata {
16
+ status: FlagLifecycleStatusName;
17
+ tags: string[];
18
+ staleAt?: Date;
19
+ expiresAt?: Date;
20
+ }
21
+ export declare function defineFlags<const TFlags extends FlagRegistry>(flags: TFlags): TFlags;
22
+ export declare function createFeatureFlagClient<TFlags extends FlagRegistry>(service: FeatureFlagService, registry: TFlags): TypedFeatureFlagClient<TFlags>;
23
+ export declare function createFeatureFlagDecorators<TFlags extends FlagRegistry>(registry: TFlags): TypedFeatureFlagDecorators<TFlags>;
24
+ export declare function getFlagLifecycleStatus(metadata: FeatureFlagLifecycleMetadata, now?: Date): FlagLifecycleStatus;
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.defineFlags = defineFlags;
4
+ exports.createFeatureFlagClient = createFeatureFlagClient;
5
+ exports.createFeatureFlagDecorators = createFeatureFlagDecorators;
6
+ exports.getFlagLifecycleStatus = getFlagLifecycleStatus;
7
+ const feature_flag_decorator_1 = require("./decorators/feature-flag.decorator");
8
+ function defineFlags(flags) {
9
+ return flags;
10
+ }
11
+ function createFeatureFlagClient(service, registry) {
12
+ return {
13
+ registry,
14
+ isEnabled: (flagKey, context, options) => service.isEnabled(flagKey, context, mergeRegistryOptions(registry[flagKey], options)),
15
+ evaluateBoolean: (flagKey, context, options) => service.evaluateBoolean(flagKey, context, mergeRegistryOptions(registry[flagKey], options)),
16
+ };
17
+ }
18
+ function createFeatureFlagDecorators(registry) {
19
+ return {
20
+ FeatureFlag: (flagKey, options = {}) => (0, feature_flag_decorator_1.FeatureFlag)(flagKey, {
21
+ defaultValue: registry[flagKey].defaultValue,
22
+ ...options,
23
+ }),
24
+ };
25
+ }
26
+ function getFlagLifecycleStatus(metadata, now = new Date()) {
27
+ const staleAt = parseLifecycleDate(metadata.staleAt);
28
+ const expiresAt = parseLifecycleDate(metadata.expiresAt);
29
+ const status = getLifecycleStatusName(now, staleAt, expiresAt);
30
+ return {
31
+ ...metadata,
32
+ tags: [...(metadata.tags ?? [])],
33
+ staleAt,
34
+ expiresAt,
35
+ status,
36
+ };
37
+ }
38
+ function mergeRegistryOptions(definition, options = {}) {
39
+ const merged = {
40
+ defaultValue: definition.defaultValue,
41
+ };
42
+ if (definition.trackExposure !== undefined) {
43
+ merged.trackExposure = definition.trackExposure;
44
+ }
45
+ return { ...merged, ...options };
46
+ }
47
+ function getLifecycleStatusName(now, staleAt, expiresAt) {
48
+ if (expiresAt && now >= expiresAt) {
49
+ return 'expired';
50
+ }
51
+ if (staleAt && now >= staleAt) {
52
+ return 'stale';
53
+ }
54
+ return 'active';
55
+ }
56
+ function parseLifecycleDate(value) {
57
+ if (!value) {
58
+ return undefined;
59
+ }
60
+ return value instanceof Date ? value : new Date(value);
61
+ }
62
+ //# sourceMappingURL=flag-registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flag-registry.js","sourceRoot":"","sources":["../src/flag-registry.ts"],"names":[],"mappings":";;AA8CA,kCAEC;AAED,0DAeC;AAED,kEAUC;AAED,wDAeC;AA9FD,gFAAkE;AA8ClE,SAAgB,WAAW,CAAoC,KAAa;IAC1E,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAgB,uBAAuB,CACrC,OAA2B,EAC3B,QAAgB;IAEhB,OAAO;QACL,QAAQ;QACR,SAAS,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,CACvC,OAAO,CAAC,SAAS,CAAC,OAAO,EAAE,OAAO,EAAE,oBAAoB,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,CAAC;QACvF,eAAe,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,CAC7C,OAAO,CAAC,eAAe,CACrB,OAAO,EACP,OAAO,EACP,oBAAoB,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,CACjD;KACJ,CAAC;AACJ,CAAC;AAED,SAAgB,2BAA2B,CACzC,QAAgB;IAEhB,OAAO;QACL,WAAW,EAAE,CAAC,OAAO,EAAE,OAAO,GAAG,EAAE,EAAE,EAAE,CACrC,IAAA,oCAAW,EAAC,OAAO,EAAE;YACnB,YAAY,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC,YAAY;YAC5C,GAAG,OAAO;SACX,CAAC;KACL,CAAC;AACJ,CAAC;AAED,SAAgB,sBAAsB,CACpC,QAAsC,EACtC,MAAY,IAAI,IAAI,EAAE;IAEtB,MAAM,OAAO,GAAG,kBAAkB,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IACrD,MAAM,SAAS,GAAG,kBAAkB,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;IACzD,MAAM,MAAM,GAAG,sBAAsB,CAAC,GAAG,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC;IAE/D,OAAO;QACL,GAAG,QAAQ;QACX,IAAI,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;QAChC,OAAO;QACP,SAAS;QACT,MAAM;KACP,CAAC;AACJ,CAAC;AAED,SAAS,oBAAoB,CAC3B,UAAgC,EAChC,UAAkC,EAAE;IAEpC,MAAM,MAAM,GAA2B;QACrC,YAAY,EAAE,UAAU,CAAC,YAAY;KACtC,CAAC;IAEF,IAAI,UAAU,CAAC,aAAa,KAAK,SAAS,EAAE,CAAC;QAC3C,MAAM,CAAC,aAAa,GAAG,UAAU,CAAC,aAAa,CAAC;IAClD,CAAC;IAED,OAAO,EAAE,GAAG,MAAM,EAAE,GAAG,OAAO,EAAE,CAAC;AACnC,CAAC;AAED,SAAS,sBAAsB,CAC7B,GAAS,EACT,OAAc,EACd,SAAgB;IAEhB,IAAI,SAAS,IAAI,GAAG,IAAI,SAAS,EAAE,CAAC;QAClC,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,IAAI,OAAO,IAAI,GAAG,IAAI,OAAO,EAAE,CAAC;QAC9B,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,SAAS,kBAAkB,CAAC,KAAgC;IAC1D,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,OAAO,KAAK,YAAY,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC;AACzD,CAAC"}
@@ -35,7 +35,11 @@ let FeatureFlagGuard = class FeatureFlagGuard {
35
35
  const options = this.reflector.get(feature_flag_constants_1.FEATURE_FLAG_OPTIONS_KEY, handler) ??
36
36
  this.reflector.get(feature_flag_constants_1.FEATURE_FLAG_OPTIONS_KEY, classRef) ??
37
37
  {};
38
- const enabled = await this.featureFlagService.isEnabled(flagKey);
38
+ const enabled = options.defaultValue === undefined
39
+ ? await this.featureFlagService.isEnabled(flagKey)
40
+ : await this.featureFlagService.isEnabled(flagKey, undefined, {
41
+ defaultValue: options.defaultValue,
42
+ });
39
43
  if (!enabled) {
40
44
  const statusCode = options.statusCode ?? 403;
41
45
  const body = options.fallback ?? { message: 'Feature not available' };
@@ -1 +1 @@
1
- {"version":3,"file":"feature-flag.guard.js","sourceRoot":"","sources":["../../src/guards/feature-flag.guard.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAA0F;AAC1F,uCAAyC;AACzC,2EAAsE;AACtE,sEAImC;AAI5B,IAAM,gBAAgB,GAAtB,MAAM,gBAAgB;IAC3B,YACmB,SAAoB,EACpB,kBAAsC;QADtC,cAAS,GAAT,SAAS,CAAW;QACpB,uBAAkB,GAAlB,kBAAkB,CAAoB;IACtD,CAAC;IAEJ,KAAK,CAAC,WAAW,CAAC,OAAyB;QACzC,MAAM,OAAO,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;QACrC,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;QAEpC,eAAe;QACf,MAAM,MAAM,GACV,IAAI,CAAC,SAAS,CAAC,GAAG,CAAU,gDAAuB,EAAE,OAAO,CAAC;YAC7D,IAAI,CAAC,SAAS,CAAC,GAAG,CAAU,gDAAuB,EAAE,QAAQ,CAAC,CAAC;QACjE,IAAI,MAAM;YAAE,OAAO,IAAI,CAAC;QAExB,yDAAyD;QACzD,MAAM,OAAO,GACX,IAAI,CAAC,SAAS,CAAC,GAAG,CAAS,yCAAgB,EAAE,OAAO,CAAC;YACrD,IAAI,CAAC,SAAS,CAAC,GAAG,CAAS,yCAAgB,EAAE,QAAQ,CAAC,CAAC;QACzD,IAAI,CAAC,OAAO;YAAE,OAAO,IAAI,CAAC;QAE1B,MAAM,OAAO,GACX,IAAI,CAAC,SAAS,CAAC,GAAG,CAA0B,iDAAwB,EAAE,OAAO,CAAC;YAC9E,IAAI,CAAC,SAAS,CAAC,GAAG,CAA0B,iDAAwB,EAAE,QAAQ,CAAC;YAC/E,EAAE,CAAC;QAEL,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,kBAAkB,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QAEjE,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,GAAG,CAAC;YAC7C,MAAM,IAAI,GAAG,OAAO,CAAC,QAAQ,IAAI,EAAE,OAAO,EAAE,uBAAuB,EAAE,CAAC;YACtE,MAAM,IAAI,sBAAa,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;QAC5C,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;CACF,CAAA;AArCY,4CAAgB;2BAAhB,gBAAgB;IAD5B,IAAA,mBAAU,GAAE;qCAGmB,gBAAS;QACA,yCAAkB;GAH9C,gBAAgB,CAqC5B"}
1
+ {"version":3,"file":"feature-flag.guard.js","sourceRoot":"","sources":["../../src/guards/feature-flag.guard.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAA0F;AAC1F,uCAAyC;AACzC,2EAAsE;AACtE,sEAImC;AAI5B,IAAM,gBAAgB,GAAtB,MAAM,gBAAgB;IAC3B,YACmB,SAAoB,EACpB,kBAAsC;QADtC,cAAS,GAAT,SAAS,CAAW;QACpB,uBAAkB,GAAlB,kBAAkB,CAAoB;IACtD,CAAC;IAEJ,KAAK,CAAC,WAAW,CAAC,OAAyB;QACzC,MAAM,OAAO,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;QACrC,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;QAEpC,eAAe;QACf,MAAM,MAAM,GACV,IAAI,CAAC,SAAS,CAAC,GAAG,CAAU,gDAAuB,EAAE,OAAO,CAAC;YAC7D,IAAI,CAAC,SAAS,CAAC,GAAG,CAAU,gDAAuB,EAAE,QAAQ,CAAC,CAAC;QACjE,IAAI,MAAM;YAAE,OAAO,IAAI,CAAC;QAExB,yDAAyD;QACzD,MAAM,OAAO,GACX,IAAI,CAAC,SAAS,CAAC,GAAG,CAAS,yCAAgB,EAAE,OAAO,CAAC;YACrD,IAAI,CAAC,SAAS,CAAC,GAAG,CAAS,yCAAgB,EAAE,QAAQ,CAAC,CAAC;QACzD,IAAI,CAAC,OAAO;YAAE,OAAO,IAAI,CAAC;QAE1B,MAAM,OAAO,GACX,IAAI,CAAC,SAAS,CAAC,GAAG,CAA0B,iDAAwB,EAAE,OAAO,CAAC;YAC9E,IAAI,CAAC,SAAS,CAAC,GAAG,CAA0B,iDAAwB,EAAE,QAAQ,CAAC;YAC/E,EAAE,CAAC;QAEL,MAAM,OAAO,GACX,OAAO,CAAC,YAAY,KAAK,SAAS;YAChC,CAAC,CAAC,MAAM,IAAI,CAAC,kBAAkB,CAAC,SAAS,CAAC,OAAO,CAAC;YAClD,CAAC,CAAC,MAAM,IAAI,CAAC,kBAAkB,CAAC,SAAS,CAAC,OAAO,EAAE,SAAS,EAAE;gBAC1D,YAAY,EAAE,OAAO,CAAC,YAAY;aACnC,CAAC,CAAC;QAET,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,GAAG,CAAC;YAC7C,MAAM,IAAI,GAAG,OAAO,CAAC,QAAQ,IAAI,EAAE,OAAO,EAAE,uBAAuB,EAAE,CAAC;YACtE,MAAM,IAAI,sBAAa,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;QAC5C,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;CACF,CAAA;AA1CY,4CAAgB;2BAAhB,gBAAgB;IAD5B,IAAA,mBAAU,GAAE;qCAGmB,gBAAS;QACA,yCAAkB;GAH9C,gBAAgB,CA0C5B"}
package/dist/index.d.ts CHANGED
@@ -6,8 +6,11 @@ export { FeatureFlag } from './decorators/feature-flag.decorator';
6
6
  export { BypassFeatureFlag } from './decorators/bypass-feature-flag.decorator';
7
7
  export { FeatureFlagModuleOptions, FeatureFlagModuleAsyncOptions, FeatureFlagModuleOptionsFactory, } from './interfaces/feature-flag-options.interface';
8
8
  export { EvaluationContext } from './interfaces/evaluation-context.interface';
9
- export { CreateFeatureFlagInput, UpdateFeatureFlagInput, SetOverrideInput, RemoveOverrideInput, TargetingAttributeValue, TargetingAttributes, FeatureFlagGuardOptions, FeatureFlagWithOverrides, FlagOverride, } from './interfaces/feature-flag.interface';
10
- export { FeatureFlagEvents, FlagEvaluatedEvent, FlagMutationEvent, FlagOverrideEvent, } from './events/feature-flag.events';
9
+ export { EvaluationSource, EvaluationReason, EvaluateBooleanOptions, BooleanEvaluationDetails, BucketBy, FlagEvaluatorOptions, } from './interfaces/evaluation-details.interface';
10
+ export { FeatureFlagLifecycleMetadata, FeatureFlagType, FlagDefinition, FlagRegistry, FlagKey, } from './interfaces/flag-registry.interface';
11
+ export { CreateFeatureFlagInput, UpdateFeatureFlagInput, SetOverrideInput, RemoveOverrideInput, TargetingAttributeValue, TargetingAttributes, FeatureFlagGuardOptions, FeatureFlagWithOverrides, FlagOverride, FlagMutationMetadata, } from './interfaces/feature-flag.interface';
12
+ export { defineFlags, createFeatureFlagClient, createFeatureFlagDecorators, getFlagLifecycleStatus, TypedFeatureFlagClient, TypedFeatureFlagDecorators, FlagLifecycleStatus, FlagLifecycleStatusName, } from './flag-registry';
13
+ export { FeatureFlagEvents, FlagEvaluatedEvent, FlagExposedEvent, FlagMutationEvent, FlagOverrideEvent, } from './events/feature-flag.events';
11
14
  export { FEATURE_FLAG_MODULE_OPTIONS, CACHE_ADAPTER, FEATURE_FLAG_REPOSITORY, TENANT_CONTEXT_PROVIDER, } from './feature-flag.constants';
12
15
  export type { CacheAdapter } from './interfaces/cache-adapter.interface';
13
16
  export { MemoryCacheAdapter } from './cache/memory-cache.adapter';
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.FeatureFlagAdminModule = exports.PrismaFeatureFlagRepository = exports.RedisCacheAdapter = exports.MemoryCacheAdapter = exports.TENANT_CONTEXT_PROVIDER = exports.FEATURE_FLAG_REPOSITORY = exports.CACHE_ADAPTER = exports.FEATURE_FLAG_MODULE_OPTIONS = exports.FeatureFlagEvents = exports.BypassFeatureFlag = exports.FeatureFlag = exports.FeatureFlagGuard = exports.FlagContext = exports.FeatureFlagService = exports.FeatureFlagModule = void 0;
3
+ exports.FeatureFlagAdminModule = exports.PrismaFeatureFlagRepository = exports.RedisCacheAdapter = exports.MemoryCacheAdapter = exports.TENANT_CONTEXT_PROVIDER = exports.FEATURE_FLAG_REPOSITORY = exports.CACHE_ADAPTER = exports.FEATURE_FLAG_MODULE_OPTIONS = exports.FeatureFlagEvents = exports.getFlagLifecycleStatus = exports.createFeatureFlagDecorators = exports.createFeatureFlagClient = exports.defineFlags = exports.BypassFeatureFlag = exports.FeatureFlag = exports.FeatureFlagGuard = exports.FlagContext = exports.FeatureFlagService = exports.FeatureFlagModule = void 0;
4
4
  // Module
5
5
  var feature_flag_module_1 = require("./feature-flag.module");
6
6
  Object.defineProperty(exports, "FeatureFlagModule", { enumerable: true, get: function () { return feature_flag_module_1.FeatureFlagModule; } });
@@ -17,6 +17,12 @@ var feature_flag_decorator_1 = require("./decorators/feature-flag.decorator");
17
17
  Object.defineProperty(exports, "FeatureFlag", { enumerable: true, get: function () { return feature_flag_decorator_1.FeatureFlag; } });
18
18
  var bypass_feature_flag_decorator_1 = require("./decorators/bypass-feature-flag.decorator");
19
19
  Object.defineProperty(exports, "BypassFeatureFlag", { enumerable: true, get: function () { return bypass_feature_flag_decorator_1.BypassFeatureFlag; } });
20
+ // Registry helpers
21
+ var flag_registry_1 = require("./flag-registry");
22
+ Object.defineProperty(exports, "defineFlags", { enumerable: true, get: function () { return flag_registry_1.defineFlags; } });
23
+ Object.defineProperty(exports, "createFeatureFlagClient", { enumerable: true, get: function () { return flag_registry_1.createFeatureFlagClient; } });
24
+ Object.defineProperty(exports, "createFeatureFlagDecorators", { enumerable: true, get: function () { return flag_registry_1.createFeatureFlagDecorators; } });
25
+ Object.defineProperty(exports, "getFlagLifecycleStatus", { enumerable: true, get: function () { return flag_registry_1.getFlagLifecycleStatus; } });
20
26
  // Events
21
27
  var feature_flag_events_1 = require("./events/feature-flag.events");
22
28
  Object.defineProperty(exports, "FeatureFlagEvents", { enumerable: true, get: function () { return feature_flag_events_1.FeatureFlagEvents; } });
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,SAAS;AACT,6DAA2H;AAAlH,wHAAA,iBAAiB,OAAA;AAE1B,WAAW;AACX,wEAAqE;AAA5D,0HAAA,kBAAkB,OAAA;AAC3B,wDAAsD;AAA7C,2GAAA,WAAW,OAAA;AAEpB,QAAQ;AACR,kEAA+D;AAAtD,sHAAA,gBAAgB,OAAA;AAEzB,aAAa;AACb,8EAAkE;AAAzD,qHAAA,WAAW,OAAA;AACpB,4FAA+E;AAAtE,kIAAA,iBAAiB,OAAA;AAqB1B,SAAS;AACT,oEAKsC;AAJpC,wHAAA,iBAAiB,OAAA;AAMnB,YAAY;AACZ,mEAKkC;AAJhC,qIAAA,2BAA2B,OAAA;AAC3B,uHAAA,aAAa,OAAA;AACb,iIAAA,uBAAuB,OAAA;AACvB,iIAAA,uBAAuB,OAAA;AAKzB,qEAAkE;AAAzD,0HAAA,kBAAkB,OAAA;AAC3B,mEAA+F;AAAtF,wHAAA,iBAAiB,OAAA;AAQ1B,gGAA4F;AAAnF,6IAAA,2BAA2B,OAAA;AAKpC,eAAe;AACf,+EAA2E;AAAlE,mIAAA,sBAAsB,OAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,SAAS;AACT,6DAA2H;AAAlH,wHAAA,iBAAiB,OAAA;AAE1B,WAAW;AACX,wEAAqE;AAA5D,0HAAA,kBAAkB,OAAA;AAC3B,wDAAsD;AAA7C,2GAAA,WAAW,OAAA;AAEpB,QAAQ;AACR,kEAA+D;AAAtD,sHAAA,gBAAgB,OAAA;AAEzB,aAAa;AACb,8EAAkE;AAAzD,qHAAA,WAAW,OAAA;AACpB,4FAA+E;AAAtE,kIAAA,iBAAiB,OAAA;AAqC1B,mBAAmB;AACnB,iDASyB;AARvB,4GAAA,WAAW,OAAA;AACX,wHAAA,uBAAuB,OAAA;AACvB,4HAAA,2BAA2B,OAAA;AAC3B,uHAAA,sBAAsB,OAAA;AAOxB,SAAS;AACT,oEAMsC;AALpC,wHAAA,iBAAiB,OAAA;AAOnB,YAAY;AACZ,mEAKkC;AAJhC,qIAAA,2BAA2B,OAAA;AAC3B,uHAAA,aAAa,OAAA;AACb,iIAAA,uBAAuB,OAAA;AACvB,iIAAA,uBAAuB,OAAA;AAKzB,qEAAkE;AAAzD,0HAAA,kBAAkB,OAAA;AAC3B,mEAA+F;AAAtF,wHAAA,iBAAiB,OAAA;AAQ1B,gGAA4F;AAAnF,6IAAA,2BAA2B,OAAA;AAKpC,eAAe;AACf,+EAA2E;AAAlE,mIAAA,sBAAsB,OAAA"}