@mergedapp/feature-flags 0.1.3

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 (114) hide show
  1. package/README.md +651 -0
  2. package/dist/cjs/cli/audit.js +117 -0
  3. package/dist/cjs/cli/cleanup.js +105 -0
  4. package/dist/cjs/cli/config-loader.js +102 -0
  5. package/dist/cjs/cli/generate.js +194 -0
  6. package/dist/cjs/cli/parse-args.js +18 -0
  7. package/dist/cjs/cli.js +46 -0
  8. package/dist/cjs/client.js +505 -0
  9. package/dist/cjs/errors.js +24 -0
  10. package/dist/cjs/index.js +13 -0
  11. package/dist/cjs/jwt.js +85 -0
  12. package/dist/cjs/nestjs/bindings.js +36 -0
  13. package/dist/cjs/nestjs/constants.js +7 -0
  14. package/dist/cjs/nestjs/context.js +28 -0
  15. package/dist/cjs/nestjs/decorators.js +50 -0
  16. package/dist/cjs/nestjs/errors.js +25 -0
  17. package/dist/cjs/nestjs/evaluator.js +87 -0
  18. package/dist/cjs/nestjs/guard.js +67 -0
  19. package/dist/cjs/nestjs/interceptor.js +56 -0
  20. package/dist/cjs/nestjs/module.js +70 -0
  21. package/dist/cjs/nestjs/service.js +54 -0
  22. package/dist/cjs/nestjs/types.js +2 -0
  23. package/dist/cjs/nestjs.js +26 -0
  24. package/dist/cjs/openfeature/context.js +166 -0
  25. package/dist/cjs/openfeature/hooks.js +31 -0
  26. package/dist/cjs/openfeature/server-provider.js +107 -0
  27. package/dist/cjs/openfeature/server.js +13 -0
  28. package/dist/cjs/openfeature/shared.js +83 -0
  29. package/dist/cjs/openfeature/web-provider.js +156 -0
  30. package/dist/cjs/openfeature/web.js +13 -0
  31. package/dist/cjs/package.json +3 -0
  32. package/dist/cjs/persistence.js +249 -0
  33. package/dist/cjs/react/hooks.js +86 -0
  34. package/dist/cjs/react/provider.js +106 -0
  35. package/dist/cjs/react.js +7 -0
  36. package/dist/cjs/remote-evaluator.js +162 -0
  37. package/dist/cjs/types.js +2 -0
  38. package/dist/cli/audit.d.ts +3 -0
  39. package/dist/cli/audit.js +114 -0
  40. package/dist/cli/cleanup.d.ts +3 -0
  41. package/dist/cli/cleanup.js +102 -0
  42. package/dist/cli/config-loader.d.ts +26 -0
  43. package/dist/cli/config-loader.js +66 -0
  44. package/dist/cli/generate.d.ts +3 -0
  45. package/dist/cli/generate.js +191 -0
  46. package/dist/cli/parse-args.d.ts +1 -0
  47. package/dist/cli/parse-args.js +15 -0
  48. package/dist/cli.d.ts +1 -0
  49. package/dist/cli.js +45 -0
  50. package/dist/client.d.ts +67 -0
  51. package/dist/client.js +501 -0
  52. package/dist/errors.d.ts +15 -0
  53. package/dist/errors.js +18 -0
  54. package/dist/index.cjs +1 -0
  55. package/dist/index.d.ts +4 -0
  56. package/dist/index.js +3 -0
  57. package/dist/jwt.d.ts +20 -0
  58. package/dist/jwt.js +78 -0
  59. package/dist/nestjs/bindings.d.ts +5 -0
  60. package/dist/nestjs/bindings.js +33 -0
  61. package/dist/nestjs/constants.d.ts +4 -0
  62. package/dist/nestjs/constants.js +4 -0
  63. package/dist/nestjs/context.d.ts +12 -0
  64. package/dist/nestjs/context.js +24 -0
  65. package/dist/nestjs/decorators.d.ts +4 -0
  66. package/dist/nestjs/decorators.js +45 -0
  67. package/dist/nestjs/errors.d.ts +12 -0
  68. package/dist/nestjs/errors.js +20 -0
  69. package/dist/nestjs/evaluator.d.ts +17 -0
  70. package/dist/nestjs/evaluator.js +83 -0
  71. package/dist/nestjs/guard.d.ts +19 -0
  72. package/dist/nestjs/guard.js +63 -0
  73. package/dist/nestjs/interceptor.d.ts +10 -0
  74. package/dist/nestjs/interceptor.js +53 -0
  75. package/dist/nestjs/module.d.ts +6 -0
  76. package/dist/nestjs/module.js +67 -0
  77. package/dist/nestjs/service.d.ts +30 -0
  78. package/dist/nestjs/service.js +51 -0
  79. package/dist/nestjs/types.d.ts +100 -0
  80. package/dist/nestjs/types.js +1 -0
  81. package/dist/nestjs.cjs +1 -0
  82. package/dist/nestjs.d.ts +10 -0
  83. package/dist/nestjs.js +9 -0
  84. package/dist/openfeature/context.d.ts +10 -0
  85. package/dist/openfeature/context.js +160 -0
  86. package/dist/openfeature/hooks.d.ts +6 -0
  87. package/dist/openfeature/hooks.js +27 -0
  88. package/dist/openfeature/server-provider.d.ts +20 -0
  89. package/dist/openfeature/server-provider.js +102 -0
  90. package/dist/openfeature/server.cjs +1 -0
  91. package/dist/openfeature/server.d.ts +3 -0
  92. package/dist/openfeature/server.js +3 -0
  93. package/dist/openfeature/shared.d.ts +37 -0
  94. package/dist/openfeature/shared.js +74 -0
  95. package/dist/openfeature/web-provider.d.ts +27 -0
  96. package/dist/openfeature/web-provider.js +151 -0
  97. package/dist/openfeature/web.cjs +1 -0
  98. package/dist/openfeature/web.d.ts +3 -0
  99. package/dist/openfeature/web.js +3 -0
  100. package/dist/persistence.d.ts +39 -0
  101. package/dist/persistence.js +203 -0
  102. package/dist/react/hooks.d.ts +52 -0
  103. package/dist/react/hooks.js +78 -0
  104. package/dist/react/provider.d.ts +71 -0
  105. package/dist/react/provider.js +99 -0
  106. package/dist/react.cjs +1 -0
  107. package/dist/react.d.ts +2 -0
  108. package/dist/react.js +2 -0
  109. package/dist/remote-evaluator.d.ts +28 -0
  110. package/dist/remote-evaluator.js +158 -0
  111. package/dist/types.d.ts +56 -0
  112. package/dist/types.js +1 -0
  113. package/featureflags.config.schema.json +38 -0
  114. package/package.json +107 -0
@@ -0,0 +1,4 @@
1
+ export const FEATURE_FLAGS_MODULE_OPTIONS = Symbol("FEATURE_FLAGS_MODULE_OPTIONS");
2
+ export const FEATURE_FLAGS_EVALUATOR = Symbol("FEATURE_FLAGS_EVALUATOR");
3
+ export const FEATURE_FLAGS_REQUEST_CONTEXT = Symbol("FEATURE_FLAGS_REQUEST_CONTEXT");
4
+ export const REQUIRE_FEATURE_FLAG_KEY = "REQUIRE_FEATURE_FLAG";
@@ -0,0 +1,12 @@
1
+ import { type ExecutionContext } from "@nestjs/common";
2
+ import type { FeatureFlagEvaluationContext } from "@repo/types-v2";
3
+ import type { FeatureFlagContextFactory } from "./types.js";
4
+ export declare class FeatureFlagsRequestContextStore {
5
+ private readonly storage;
6
+ getContext(): FeatureFlagEvaluationContext | null;
7
+ run<T>(context: FeatureFlagEvaluationContext | null | undefined, callback: () => T): T;
8
+ }
9
+ export declare function resolveFeatureFlagContext(params: {
10
+ context: ExecutionContext;
11
+ contextFactory?: FeatureFlagContextFactory;
12
+ }): Promise<FeatureFlagEvaluationContext | undefined>;
@@ -0,0 +1,24 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { AsyncLocalStorage } from "node:async_hooks";
8
+ import { Injectable } from "@nestjs/common";
9
+ let FeatureFlagsRequestContextStore = class FeatureFlagsRequestContextStore {
10
+ storage = new AsyncLocalStorage();
11
+ getContext() {
12
+ return this.storage.getStore() ?? null;
13
+ }
14
+ run(context, callback) {
15
+ return this.storage.run(context ?? null, callback);
16
+ }
17
+ };
18
+ FeatureFlagsRequestContextStore = __decorate([
19
+ Injectable()
20
+ ], FeatureFlagsRequestContextStore);
21
+ export { FeatureFlagsRequestContextStore };
22
+ export async function resolveFeatureFlagContext(params) {
23
+ return params.contextFactory ? params.contextFactory(params.context) : undefined;
24
+ }
@@ -0,0 +1,4 @@
1
+ import type { RawFeatureFlagGateOptions, RawRequireFeatureFlagOptions } from "./types.js";
2
+ export declare function RequireFeatureFlag(options: RawRequireFeatureFlagOptions): <TFunction extends Function, Y>(target: TFunction | object, propertyKey?: string | symbol, descriptor?: TypedPropertyDescriptor<Y>) => void;
3
+ export declare function FeatureFlagGate(options: RawFeatureFlagGateOptions): MethodDecorator;
4
+ export declare const FeatureFlagContext: (...dataOrPipes: unknown[]) => ParameterDecorator;
@@ -0,0 +1,45 @@
1
+ import { SetMetadata, UseGuards, applyDecorators, createParamDecorator } from "@nestjs/common";
2
+ import { FEATURE_FLAGS_REQUEST_CONTEXT, REQUIRE_FEATURE_FLAG_KEY } from "./constants.js";
3
+ import { FeatureFlagGateError } from "./errors.js";
4
+ import { FeatureFlagGuard, evaluateGate } from "./guard.js";
5
+ export function RequireFeatureFlag(options) {
6
+ return applyDecorators(SetMetadata(REQUIRE_FEATURE_FLAG_KEY, options), UseGuards(FeatureFlagGuard));
7
+ }
8
+ export function FeatureFlagGate(options) {
9
+ return (_target, _propertyKey, descriptor) => {
10
+ const original = descriptor.value;
11
+ if (typeof original !== "function") {
12
+ return descriptor;
13
+ }
14
+ descriptor.value = async function wrappedFeatureFlagGate(...args) {
15
+ const serviceProperty = options.serviceProperty ?? "featureFlags";
16
+ const featureFlagsService = this[serviceProperty];
17
+ if (!isFeatureFlagsService(featureFlagsService)) {
18
+ throw new TypeError(`FeatureFlagGate expected a FeatureFlagsService instance on this.${serviceProperty}.`);
19
+ }
20
+ const context = options.resolveContext ? await options.resolveContext(this, args) : options.context;
21
+ const allowed = await evaluateGate({
22
+ context,
23
+ defaultValue: options.defaultValue ?? false,
24
+ expectedValue: options.expectedValue,
25
+ featureFlagsService,
26
+ flagKey: options.flagKey,
27
+ });
28
+ if (!allowed) {
29
+ throw new FeatureFlagGateError({ flagKey: options.flagKey });
30
+ }
31
+ return original.apply(this, args);
32
+ };
33
+ return descriptor;
34
+ };
35
+ }
36
+ export const FeatureFlagContext = createParamDecorator((_data, ctx) => {
37
+ const request = ctx.switchToHttp().getRequest();
38
+ return request?.[FEATURE_FLAGS_REQUEST_CONTEXT] ?? null;
39
+ });
40
+ function isFeatureFlagsService(value) {
41
+ return (typeof value === "object" &&
42
+ value !== null &&
43
+ "getDetails" in value &&
44
+ typeof value.getDetails === "function");
45
+ }
@@ -0,0 +1,12 @@
1
+ import { HttpException } from "@nestjs/common";
2
+ export declare class FeatureFlagDisabledHttpException extends HttpException {
3
+ constructor({ flagKey, message }: {
4
+ flagKey: string;
5
+ message?: string;
6
+ });
7
+ }
8
+ export declare class FeatureFlagGateError extends FeatureFlagDisabledHttpException {
9
+ constructor({ flagKey }: {
10
+ flagKey: string;
11
+ });
12
+ }
@@ -0,0 +1,20 @@
1
+ import { HttpException, HttpStatus } from "@nestjs/common";
2
+ export class FeatureFlagDisabledHttpException extends HttpException {
3
+ constructor({ flagKey, message }) {
4
+ super({
5
+ code: "FEATURE_FLAG_DISABLED",
6
+ error: "FEATURE_FLAG_DISABLED",
7
+ message: message ?? `Feature flag (${flagKey}) is disabled for this request.`,
8
+ statusCode: HttpStatus.FORBIDDEN,
9
+ }, HttpStatus.FORBIDDEN);
10
+ }
11
+ }
12
+ export class FeatureFlagGateError extends FeatureFlagDisabledHttpException {
13
+ constructor({ flagKey }) {
14
+ super({
15
+ flagKey,
16
+ message: `Feature flag (${flagKey}) blocked this service method.`,
17
+ });
18
+ this.name = "FeatureFlagGateError";
19
+ }
20
+ }
@@ -0,0 +1,17 @@
1
+ import type { FeatureFlagEvaluationContext } from "@repo/types-v2";
2
+ import type { FlagRegistry } from "../types.js";
3
+ import type { FeatureFlagEvaluationDetails, NativeFeatureFlagsModuleOptions } from "./types.js";
4
+ export type FeatureFlagsEvaluationRequest<TValue> = {
5
+ context?: FeatureFlagEvaluationContext;
6
+ defaultValue: TValue;
7
+ flagKey: string;
8
+ };
9
+ export interface NestFeatureFlagsEvaluator {
10
+ getDetails<TValue>(request: FeatureFlagsEvaluationRequest<TValue>): Promise<FeatureFlagEvaluationDetails<TValue>>;
11
+ }
12
+ export declare class NativeNestFeatureFlagsEvaluator<TFlags extends FlagRegistry = Record<string, unknown>> implements NestFeatureFlagsEvaluator {
13
+ private readonly options;
14
+ private readonly evaluator;
15
+ constructor(options: NativeFeatureFlagsModuleOptions<TFlags>);
16
+ getDetails<TValue>(request: FeatureFlagsEvaluationRequest<TValue>): Promise<FeatureFlagEvaluationDetails<TValue>>;
17
+ }
@@ -0,0 +1,83 @@
1
+ import { RemoteFeatureFlagEvaluator } from "../remote-evaluator.js";
2
+ export class NativeNestFeatureFlagsEvaluator {
3
+ options;
4
+ evaluator;
5
+ constructor(options) {
6
+ this.options = options;
7
+ this.evaluator = new RemoteFeatureFlagEvaluator(options);
8
+ }
9
+ async getDetails(request) {
10
+ const evaluation = await this.evaluator.evaluate(request.context ?? null);
11
+ const flag = findEvaluatedFlag({
12
+ flags: evaluation.flags,
13
+ flagIds: this.options.flagIds,
14
+ flagKey: request.flagKey,
15
+ });
16
+ if (!flag) {
17
+ return {
18
+ errorCode: "FLAG_NOT_FOUND",
19
+ errorMessage: `Feature flag (${request.flagKey}) was not found.`,
20
+ flagKey: request.flagKey,
21
+ flagMetadata: {},
22
+ reason: "ERROR",
23
+ value: request.defaultValue,
24
+ };
25
+ }
26
+ if (!matchesExpectedType({
27
+ defaultValue: request.defaultValue,
28
+ flag,
29
+ })) {
30
+ return {
31
+ errorCode: "TYPE_MISMATCH",
32
+ errorMessage: `Feature flag (${flag.name}) resolved as ${flag.type}, which does not match the requested default value type.`,
33
+ flagKey: request.flagKey,
34
+ flagMetadata: createEvaluationMetadata(flag),
35
+ reason: "ERROR",
36
+ value: request.defaultValue,
37
+ };
38
+ }
39
+ return {
40
+ flagKey: request.flagKey,
41
+ flagMetadata: createEvaluationMetadata(flag),
42
+ reason: flag.enabled ? "TARGETING_MATCH" : "DISABLED",
43
+ value: flag.value,
44
+ };
45
+ }
46
+ }
47
+ function createEvaluationMetadata(flag) {
48
+ return {
49
+ enabled: flag.enabled,
50
+ id: flag.id,
51
+ name: flag.name,
52
+ teamId: flag.teamId ?? undefined,
53
+ type: flag.type,
54
+ };
55
+ }
56
+ function findEvaluatedFlag(params) {
57
+ const resolvedId = params.flagIds?.[params.flagKey];
58
+ if (resolvedId) {
59
+ const matchedByResolvedId = params.flags.find((flag) => flag.id === resolvedId);
60
+ if (matchedByResolvedId) {
61
+ return matchedByResolvedId;
62
+ }
63
+ }
64
+ return (params.flags.find((flag) => flag.id === params.flagKey) ?? params.flags.find((flag) => flag.name === params.flagKey));
65
+ }
66
+ function matchesExpectedType(params) {
67
+ if (typeof params.defaultValue === "boolean") {
68
+ return params.flag.type === "BOOLEAN";
69
+ }
70
+ if (typeof params.defaultValue === "string") {
71
+ return params.flag.type === "STRING";
72
+ }
73
+ if (typeof params.defaultValue === "number") {
74
+ return params.flag.type === "NUMBER";
75
+ }
76
+ if (Array.isArray(params.defaultValue)) {
77
+ return params.flag.type === "ARRAY";
78
+ }
79
+ return params.flag.type === "JSON" && isObjectLikeValue(params.defaultValue);
80
+ }
81
+ function isObjectLikeValue(value) {
82
+ return value === null || typeof value === "object";
83
+ }
@@ -0,0 +1,19 @@
1
+ import { CanActivate, ExecutionContext } from "@nestjs/common";
2
+ import { Reflector } from "@nestjs/core";
3
+ import type { FeatureFlagEvaluationContext } from "@repo/types-v2";
4
+ import { FeatureFlagsService } from "./service.js";
5
+ import type { RawRequireFeatureFlagOptions } from "./types.js";
6
+ export type RequireFeatureFlagMetadata = RawRequireFeatureFlagOptions;
7
+ export declare class FeatureFlagGuard implements CanActivate {
8
+ private readonly reflector;
9
+ private readonly featureFlagsService;
10
+ constructor(reflector: Reflector, featureFlagsService: FeatureFlagsService);
11
+ canActivate(context: ExecutionContext): Promise<boolean>;
12
+ }
13
+ export declare function evaluateGate(params: {
14
+ context?: FeatureFlagEvaluationContext;
15
+ defaultValue: boolean | number | string;
16
+ expectedValue?: boolean | number | string;
17
+ featureFlagsService: FeatureFlagsService;
18
+ flagKey: string;
19
+ }): Promise<boolean>;
@@ -0,0 +1,63 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ import { Injectable } from "@nestjs/common";
11
+ import { Reflector } from "@nestjs/core";
12
+ import { REQUIRE_FEATURE_FLAG_KEY } from "./constants.js";
13
+ import { resolveFeatureFlagContext } from "./context.js";
14
+ import { FeatureFlagDisabledHttpException } from "./errors.js";
15
+ import { FeatureFlagsService } from "./service.js";
16
+ let FeatureFlagGuard = class FeatureFlagGuard {
17
+ reflector;
18
+ featureFlagsService;
19
+ constructor(reflector, featureFlagsService) {
20
+ this.reflector = reflector;
21
+ this.featureFlagsService = featureFlagsService;
22
+ }
23
+ async canActivate(context) {
24
+ const metadata = this.reflector.getAllAndOverride(REQUIRE_FEATURE_FLAG_KEY, [context.getHandler(), context.getClass()]);
25
+ if (!metadata) {
26
+ return true;
27
+ }
28
+ const evaluationContext = metadata.contextFactory
29
+ ? await resolveFeatureFlagContext({
30
+ context,
31
+ contextFactory: metadata.contextFactory,
32
+ })
33
+ : metadata.context;
34
+ const allowed = await evaluateGate({
35
+ context: evaluationContext,
36
+ defaultValue: metadata.defaultValue ?? false,
37
+ expectedValue: metadata.expectedValue,
38
+ featureFlagsService: this.featureFlagsService,
39
+ flagKey: metadata.flagKey,
40
+ });
41
+ if (!allowed) {
42
+ throw new FeatureFlagDisabledHttpException({ flagKey: metadata.flagKey });
43
+ }
44
+ return true;
45
+ }
46
+ };
47
+ FeatureFlagGuard = __decorate([
48
+ Injectable(),
49
+ __metadata("design:paramtypes", [Reflector,
50
+ FeatureFlagsService])
51
+ ], FeatureFlagGuard);
52
+ export { FeatureFlagGuard };
53
+ export async function evaluateGate(params) {
54
+ const details = await params.featureFlagsService.getDetails({
55
+ context: params.context,
56
+ defaultValue: params.defaultValue,
57
+ flagKey: params.flagKey,
58
+ });
59
+ if (typeof params.expectedValue === "undefined") {
60
+ return Boolean(details.value);
61
+ }
62
+ return Object.is(details.value, params.expectedValue);
63
+ }
@@ -0,0 +1,10 @@
1
+ import { Observable } from "rxjs";
2
+ import { CallHandler, ExecutionContext, NestInterceptor } from "@nestjs/common";
3
+ import { FeatureFlagsRequestContextStore } from "./context.js";
4
+ import type { NativeFeatureFlagsModuleOptions } from "./types.js";
5
+ export declare class FeatureFlagContextInterceptor implements NestInterceptor {
6
+ private readonly options;
7
+ private readonly requestContextStore;
8
+ constructor(options: NativeFeatureFlagsModuleOptions, requestContextStore: FeatureFlagsRequestContextStore);
9
+ intercept(context: ExecutionContext, next: CallHandler): Promise<Observable<unknown>>;
10
+ }
@@ -0,0 +1,53 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
11
+ return function (target, key) { decorator(target, key, paramIndex); }
12
+ };
13
+ import { Observable } from "rxjs";
14
+ import { Inject, Injectable } from "@nestjs/common";
15
+ import { FEATURE_FLAGS_MODULE_OPTIONS, FEATURE_FLAGS_REQUEST_CONTEXT } from "./constants.js";
16
+ import { FeatureFlagsRequestContextStore, resolveFeatureFlagContext } from "./context.js";
17
+ let FeatureFlagContextInterceptor = class FeatureFlagContextInterceptor {
18
+ options;
19
+ requestContextStore;
20
+ constructor(options, requestContextStore) {
21
+ this.options = options;
22
+ this.requestContextStore = requestContextStore;
23
+ }
24
+ async intercept(context, next) {
25
+ const evaluationContext = await resolveFeatureFlagContext({
26
+ context,
27
+ contextFactory: this.options.contextFactory,
28
+ });
29
+ const request = context.switchToHttp().getRequest();
30
+ if (request) {
31
+ request[FEATURE_FLAGS_REQUEST_CONTEXT] = evaluationContext ?? null;
32
+ }
33
+ return new Observable((subscriber) => {
34
+ let innerSubscription;
35
+ this.requestContextStore.run(evaluationContext ?? null, () => {
36
+ innerSubscription = next.handle().subscribe({
37
+ complete: () => subscriber.complete(),
38
+ error: (error) => subscriber.error(error),
39
+ next: (value) => subscriber.next(value),
40
+ });
41
+ });
42
+ return () => {
43
+ innerSubscription?.unsubscribe();
44
+ };
45
+ });
46
+ }
47
+ };
48
+ FeatureFlagContextInterceptor = __decorate([
49
+ Injectable(),
50
+ __param(0, Inject(FEATURE_FLAGS_MODULE_OPTIONS)),
51
+ __metadata("design:paramtypes", [Object, FeatureFlagsRequestContextStore])
52
+ ], FeatureFlagContextInterceptor);
53
+ export { FeatureFlagContextInterceptor };
@@ -0,0 +1,6 @@
1
+ import { DynamicModule } from "@nestjs/common";
2
+ import type { NativeFeatureFlagsModuleAsyncOptions, NativeFeatureFlagsModuleOptions } from "./types.js";
3
+ export declare class FeatureFlagsModule {
4
+ static forRoot<TFlags extends Record<string, unknown> = Record<string, unknown>>(options: NativeFeatureFlagsModuleOptions<TFlags>): DynamicModule;
5
+ static forRootAsync<TFlags extends Record<string, unknown> = Record<string, unknown>>(options: NativeFeatureFlagsModuleAsyncOptions<TFlags>): DynamicModule;
6
+ }
@@ -0,0 +1,67 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { Module } from "@nestjs/common";
8
+ import { APP_INTERCEPTOR } from "@nestjs/core";
9
+ import { FEATURE_FLAGS_EVALUATOR, FEATURE_FLAGS_MODULE_OPTIONS } from "./constants.js";
10
+ import { FeatureFlagsRequestContextStore } from "./context.js";
11
+ import { NativeNestFeatureFlagsEvaluator } from "./evaluator.js";
12
+ import { FeatureFlagGuard } from "./guard.js";
13
+ import { FeatureFlagContextInterceptor } from "./interceptor.js";
14
+ import { FeatureFlagsService } from "./service.js";
15
+ let FeatureFlagsModule = class FeatureFlagsModule {
16
+ static forRoot(options) {
17
+ return createFeatureFlagsModule({
18
+ provideOptions: {
19
+ provide: FEATURE_FLAGS_MODULE_OPTIONS,
20
+ useValue: options,
21
+ },
22
+ });
23
+ }
24
+ static forRootAsync(options) {
25
+ return createFeatureFlagsModule({
26
+ imports: options.imports ?? [],
27
+ provideOptions: {
28
+ provide: FEATURE_FLAGS_MODULE_OPTIONS,
29
+ inject: options.inject ?? [],
30
+ useFactory: options.useFactory,
31
+ },
32
+ });
33
+ }
34
+ };
35
+ FeatureFlagsModule = __decorate([
36
+ Module({})
37
+ ], FeatureFlagsModule);
38
+ export { FeatureFlagsModule };
39
+ function createFeatureFlagsModule(params) {
40
+ return {
41
+ module: FeatureFlagsModule,
42
+ imports: params.imports ?? [],
43
+ providers: [
44
+ params.provideOptions,
45
+ FeatureFlagsRequestContextStore,
46
+ FeatureFlagsService,
47
+ FeatureFlagGuard,
48
+ FeatureFlagContextInterceptor,
49
+ {
50
+ provide: FEATURE_FLAGS_EVALUATOR,
51
+ inject: [FEATURE_FLAGS_MODULE_OPTIONS],
52
+ useFactory: (options) => new NativeNestFeatureFlagsEvaluator(options),
53
+ },
54
+ {
55
+ provide: APP_INTERCEPTOR,
56
+ inject: [FEATURE_FLAGS_MODULE_OPTIONS, FeatureFlagContextInterceptor],
57
+ useFactory: (options, interceptor) => options.useGlobalInterceptor === false ? new PassthroughInterceptor() : interceptor,
58
+ },
59
+ ],
60
+ exports: [FeatureFlagsRequestContextStore, FeatureFlagsService, FeatureFlagGuard, FeatureFlagContextInterceptor],
61
+ };
62
+ }
63
+ class PassthroughInterceptor {
64
+ intercept(_context, next) {
65
+ return next.handle();
66
+ }
67
+ }
@@ -0,0 +1,30 @@
1
+ import type { FlagRegistry } from "../types.js";
2
+ import { FeatureFlagsRequestContextStore } from "./context.js";
3
+ import type { BooleanFlagKey, FeatureFlagEvaluationDetails, FlagKey } from "./types.js";
4
+ import type { NestFeatureFlagsEvaluator } from "./evaluator.js";
5
+ import type { FeatureFlagEvaluationContext } from "@repo/types-v2";
6
+ export declare class FeatureFlagsService<TFlags extends FlagRegistry = Record<string, unknown>> {
7
+ private readonly evaluator;
8
+ private readonly requestContextStore;
9
+ constructor(evaluator: NestFeatureFlagsEvaluator, requestContextStore: FeatureFlagsRequestContextStore);
10
+ getDetails<K extends FlagKey<TFlags>>(params: {
11
+ context?: FeatureFlagEvaluationContext;
12
+ defaultValue: TFlags[K];
13
+ flagKey: K;
14
+ }): Promise<FeatureFlagEvaluationDetails<TFlags[K]>>;
15
+ getValue<K extends FlagKey<TFlags>>(params: {
16
+ context?: FeatureFlagEvaluationContext;
17
+ defaultValue: TFlags[K];
18
+ flagKey: K;
19
+ }): Promise<TFlags[K]>;
20
+ getBooleanDetails<K extends BooleanFlagKey<TFlags>>(params: {
21
+ context?: FeatureFlagEvaluationContext;
22
+ defaultValue?: boolean;
23
+ flagKey: K;
24
+ }): Promise<FeatureFlagEvaluationDetails<boolean>>;
25
+ isEnabled<K extends BooleanFlagKey<TFlags>>(params: {
26
+ context?: FeatureFlagEvaluationContext;
27
+ defaultValue?: boolean;
28
+ flagKey: K;
29
+ }): Promise<boolean>;
30
+ }
@@ -0,0 +1,51 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
11
+ return function (target, key) { decorator(target, key, paramIndex); }
12
+ };
13
+ import { Inject, Injectable } from "@nestjs/common";
14
+ import { FEATURE_FLAGS_EVALUATOR } from "./constants.js";
15
+ import { FeatureFlagsRequestContextStore } from "./context.js";
16
+ let FeatureFlagsService = class FeatureFlagsService {
17
+ evaluator;
18
+ requestContextStore;
19
+ constructor(evaluator, requestContextStore) {
20
+ this.evaluator = evaluator;
21
+ this.requestContextStore = requestContextStore;
22
+ }
23
+ getDetails(params) {
24
+ return this.evaluator.getDetails({
25
+ context: params.context ?? this.requestContextStore.getContext() ?? undefined,
26
+ defaultValue: params.defaultValue,
27
+ flagKey: params.flagKey,
28
+ });
29
+ }
30
+ async getValue(params) {
31
+ const details = await this.getDetails(params);
32
+ return details.value;
33
+ }
34
+ getBooleanDetails(params) {
35
+ return this.getDetails({
36
+ context: params.context,
37
+ defaultValue: (params.defaultValue ?? false),
38
+ flagKey: params.flagKey,
39
+ });
40
+ }
41
+ async isEnabled(params) {
42
+ const details = await this.getBooleanDetails(params);
43
+ return details.value;
44
+ }
45
+ };
46
+ FeatureFlagsService = __decorate([
47
+ Injectable(),
48
+ __param(0, Inject(FEATURE_FLAGS_EVALUATOR)),
49
+ __metadata("design:paramtypes", [Object, FeatureFlagsRequestContextStore])
50
+ ], FeatureFlagsService);
51
+ export { FeatureFlagsService };
@@ -0,0 +1,100 @@
1
+ import type { DynamicModule, ExecutionContext } from "@nestjs/common";
2
+ import type { EvaluatedFlag, FeatureFlagEvaluationContext } from "@repo/types-v2";
3
+ import type { FeatureFlagClientConfig, FlagRegistry } from "../types.js";
4
+ type AnyFlagRegistry = Record<string, unknown>;
5
+ export type FlagKey<TFlags extends FlagRegistry> = Extract<keyof TFlags, string>;
6
+ type KeysMatching<TFlags extends FlagRegistry, TValue> = Extract<{
7
+ [K in keyof TFlags]-?: TFlags[K] extends TValue ? K : never;
8
+ }[keyof TFlags], string>;
9
+ export type BooleanFlagKey<TFlags extends FlagRegistry> = KeysMatching<TFlags, boolean>;
10
+ export type StringFlagKey<TFlags extends FlagRegistry> = KeysMatching<TFlags, string>;
11
+ export type NumberFlagKey<TFlags extends FlagRegistry> = KeysMatching<TFlags, number>;
12
+ export type ScalarFlagKey<TFlags extends FlagRegistry> = BooleanFlagKey<TFlags> | StringFlagKey<TFlags> | NumberFlagKey<TFlags>;
13
+ export type ComparableFlagKey<TFlags extends FlagRegistry> = ScalarFlagKey<TFlags>;
14
+ export type NonBooleanComparableFlagKey<TFlags extends FlagRegistry> = Exclude<ComparableFlagKey<TFlags>, BooleanFlagKey<TFlags>>;
15
+ export type ObjectFlagKey<TFlags extends FlagRegistry> = Exclude<FlagKey<TFlags>, ComparableFlagKey<TFlags>>;
16
+ export type FeatureFlagContextFactory = (context: ExecutionContext) => Promise<FeatureFlagEvaluationContext | undefined> | FeatureFlagEvaluationContext | undefined;
17
+ export type FeatureFlagEvaluationReason = "DISABLED" | "ERROR" | "TARGETING_MATCH";
18
+ export type FeatureFlagEvaluationMetadata = {
19
+ enabled?: boolean;
20
+ id?: string;
21
+ name?: string;
22
+ teamId?: string;
23
+ type?: EvaluatedFlag["type"];
24
+ };
25
+ export type FeatureFlagEvaluationDetails<TValue> = {
26
+ errorCode?: "FLAG_NOT_FOUND" | "TYPE_MISMATCH";
27
+ errorMessage?: string;
28
+ flagKey: string;
29
+ flagMetadata: FeatureFlagEvaluationMetadata;
30
+ reason: FeatureFlagEvaluationReason;
31
+ value: TValue;
32
+ };
33
+ export type NativeFeatureFlagsModuleOptions<TFlags extends FlagRegistry = AnyFlagRegistry> = Pick<FeatureFlagClientConfig<TFlags>, "apiUrl" | "clientKey" | "environmentId" | "flagIds" | "organizationId" | "publicKey" | "teamId"> & {
34
+ contextFactory?: FeatureFlagContextFactory;
35
+ useGlobalInterceptor?: boolean;
36
+ };
37
+ export type NativeFeatureFlagsModuleAsyncOptions<TFlags extends FlagRegistry = AnyFlagRegistry> = {
38
+ imports?: DynamicModule["imports"];
39
+ inject?: Array<string | symbol | Function>;
40
+ useFactory: (...args: unknown[]) => Promise<NativeFeatureFlagsModuleOptions<TFlags>> | NativeFeatureFlagsModuleOptions<TFlags>;
41
+ };
42
+ export type RawRequireFeatureFlagOptions = {
43
+ context?: FeatureFlagEvaluationContext;
44
+ contextFactory?: FeatureFlagContextFactory;
45
+ defaultValue?: boolean | number | string;
46
+ expectedValue?: boolean | number | string;
47
+ flagKey: string;
48
+ };
49
+ export type RawFeatureFlagGateOptions = RawRequireFeatureFlagOptions & {
50
+ resolveContext?: (self: object, args: unknown[]) => Promise<FeatureFlagEvaluationContext | undefined> | FeatureFlagEvaluationContext | undefined;
51
+ serviceProperty?: string;
52
+ };
53
+ type TypedBooleanRequireFeatureFlagOptions<TFlags extends FlagRegistry> = {
54
+ context?: FeatureFlagEvaluationContext;
55
+ contextFactory?: FeatureFlagContextFactory;
56
+ defaultValue?: boolean;
57
+ flagKey: BooleanFlagKey<TFlags>;
58
+ };
59
+ type TypedMatchedRequireFeatureFlagOptions<TFlags extends FlagRegistry> = {
60
+ [K in NonBooleanComparableFlagKey<TFlags>]: {
61
+ context?: FeatureFlagEvaluationContext;
62
+ contextFactory?: FeatureFlagContextFactory;
63
+ defaultValue: TFlags[K];
64
+ expectedValue: TFlags[K];
65
+ flagKey: K;
66
+ };
67
+ }[NonBooleanComparableFlagKey<TFlags>];
68
+ export type TypedRequireFeatureFlagOptions<TFlags extends FlagRegistry> = TypedBooleanRequireFeatureFlagOptions<TFlags> | TypedMatchedRequireFeatureFlagOptions<TFlags>;
69
+ type TypedBooleanFeatureFlagGateOptions<TFlags extends FlagRegistry> = {
70
+ defaultValue?: boolean;
71
+ flagKey: BooleanFlagKey<TFlags>;
72
+ resolveContext?: (self: object, args: unknown[]) => Promise<FeatureFlagEvaluationContext | undefined> | FeatureFlagEvaluationContext | undefined;
73
+ serviceProperty?: string;
74
+ };
75
+ type TypedMatchedFeatureFlagGateOptions<TFlags extends FlagRegistry> = {
76
+ [K in NonBooleanComparableFlagKey<TFlags>]: {
77
+ defaultValue: TFlags[K];
78
+ expectedValue: TFlags[K];
79
+ flagKey: K;
80
+ resolveContext?: (self: object, args: unknown[]) => Promise<FeatureFlagEvaluationContext | undefined> | FeatureFlagEvaluationContext | undefined;
81
+ serviceProperty?: string;
82
+ };
83
+ }[NonBooleanComparableFlagKey<TFlags>];
84
+ export type TypedFeatureFlagGateOptions<TFlags extends FlagRegistry> = TypedBooleanFeatureFlagGateOptions<TFlags> | TypedMatchedFeatureFlagGateOptions<TFlags>;
85
+ export type TypedFeatureFlagsModuleOptions<TFlags extends FlagRegistry> = Omit<NativeFeatureFlagsModuleOptions<TFlags>, "flagIds">;
86
+ export type TypedFeatureFlagsModuleAsyncOptions<TFlags extends FlagRegistry> = {
87
+ imports?: DynamicModule["imports"];
88
+ inject?: Array<string | symbol | Function>;
89
+ useFactory: (...args: unknown[]) => Promise<TypedFeatureFlagsModuleOptions<TFlags>> | TypedFeatureFlagsModuleOptions<TFlags>;
90
+ };
91
+ export type TypedFeatureFlagsModuleFacade<TFlags extends FlagRegistry> = {
92
+ forRoot(options: TypedFeatureFlagsModuleOptions<TFlags>): DynamicModule;
93
+ forRootAsync(options: TypedFeatureFlagsModuleAsyncOptions<TFlags>): DynamicModule;
94
+ };
95
+ export type TypedNestjsBindings<TFlags extends FlagRegistry> = {
96
+ FeatureFlagGate(options: TypedFeatureFlagGateOptions<TFlags>): MethodDecorator;
97
+ FeatureFlagsModule: TypedFeatureFlagsModuleFacade<TFlags>;
98
+ RequireFeatureFlag(options: TypedRequireFeatureFlagOptions<TFlags>): ClassDecorator & MethodDecorator;
99
+ };
100
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ module.exports = require("./cjs/nestjs.js")