@nestarc/feature-flag 0.1.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 (60) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +509 -0
  3. package/dist/decorators/bypass-feature-flag.decorator.d.ts +1 -0
  4. package/dist/decorators/bypass-feature-flag.decorator.js +8 -0
  5. package/dist/decorators/bypass-feature-flag.decorator.js.map +1 -0
  6. package/dist/decorators/feature-flag.decorator.d.ts +2 -0
  7. package/dist/decorators/feature-flag.decorator.js +10 -0
  8. package/dist/decorators/feature-flag.decorator.js.map +1 -0
  9. package/dist/events/feature-flag.events.d.ts +29 -0
  10. package/dist/events/feature-flag.events.js +13 -0
  11. package/dist/events/feature-flag.events.js.map +1 -0
  12. package/dist/feature-flag.constants.d.ts +5 -0
  13. package/dist/feature-flag.constants.js +9 -0
  14. package/dist/feature-flag.constants.js.map +1 -0
  15. package/dist/feature-flag.module.d.ts +14 -0
  16. package/dist/feature-flag.module.js +132 -0
  17. package/dist/feature-flag.module.js.map +1 -0
  18. package/dist/guards/feature-flag.guard.d.ts +9 -0
  19. package/dist/guards/feature-flag.guard.js +53 -0
  20. package/dist/guards/feature-flag.guard.js.map +1 -0
  21. package/dist/index.d.ts +11 -0
  22. package/dist/index.js +26 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/interfaces/evaluation-context.interface.d.ts +8 -0
  25. package/dist/interfaces/evaluation-context.interface.js +3 -0
  26. package/dist/interfaces/evaluation-context.interface.js.map +1 -0
  27. package/dist/interfaces/feature-flag-options.interface.d.ts +27 -0
  28. package/dist/interfaces/feature-flag-options.interface.js +3 -0
  29. package/dist/interfaces/feature-flag-options.interface.js.map +1 -0
  30. package/dist/interfaces/feature-flag.interface.d.ts +45 -0
  31. package/dist/interfaces/feature-flag.interface.js +3 -0
  32. package/dist/interfaces/feature-flag.interface.js.map +1 -0
  33. package/dist/middleware/flag-context.middleware.d.ts +10 -0
  34. package/dist/middleware/flag-context.middleware.js +35 -0
  35. package/dist/middleware/flag-context.middleware.js.map +1 -0
  36. package/dist/services/feature-flag.service.d.ts +29 -0
  37. package/dist/services/feature-flag.service.js +208 -0
  38. package/dist/services/feature-flag.service.js.map +1 -0
  39. package/dist/services/flag-cache.service.d.ts +13 -0
  40. package/dist/services/flag-cache.service.js +72 -0
  41. package/dist/services/flag-cache.service.js.map +1 -0
  42. package/dist/services/flag-context.d.ts +9 -0
  43. package/dist/services/flag-context.js +26 -0
  44. package/dist/services/flag-context.js.map +1 -0
  45. package/dist/services/flag-evaluator.service.d.ts +12 -0
  46. package/dist/services/flag-evaluator.service.js +63 -0
  47. package/dist/services/flag-evaluator.service.js.map +1 -0
  48. package/dist/testing/index.d.ts +1 -0
  49. package/dist/testing/index.js +6 -0
  50. package/dist/testing/index.js.map +1 -0
  51. package/dist/testing/test-feature-flag.module.d.ts +4 -0
  52. package/dist/testing/test-feature-flag.module.js +41 -0
  53. package/dist/testing/test-feature-flag.module.js.map +1 -0
  54. package/dist/utils/hash.d.ts +6 -0
  55. package/dist/utils/hash.js +28 -0
  56. package/dist/utils/hash.js.map +1 -0
  57. package/package.json +88 -0
  58. package/prisma/migrations/20260405000000_init/migration.sql +76 -0
  59. package/prisma/migrations/migration_lock.toml +3 -0
  60. package/prisma/schema.prisma +41 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"feature-flag.service.js","sourceRoot":"","sources":["../../src/services/feature-flag.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import { FeatureFlagModuleOptions } from '../interfaces/feature-flag-options.interface';
2
+ import { FeatureFlagWithOverrides } from '../interfaces/feature-flag.interface';
3
+ export declare class FlagCacheService {
4
+ private readonly options;
5
+ private cache;
6
+ private allFlagsCache;
7
+ constructor(options: FeatureFlagModuleOptions);
8
+ get(key: string): FeatureFlagWithOverrides | null;
9
+ set(key: string, data: FeatureFlagWithOverrides): void;
10
+ getAll(): FeatureFlagWithOverrides[] | null;
11
+ setAll(data: FeatureFlagWithOverrides[]): void;
12
+ invalidate(key?: string): void;
13
+ }
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ 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;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.FlagCacheService = void 0;
16
+ const common_1 = require("@nestjs/common");
17
+ const feature_flag_constants_1 = require("../feature-flag.constants");
18
+ let FlagCacheService = class FlagCacheService {
19
+ constructor(options) {
20
+ this.options = options;
21
+ this.cache = new Map();
22
+ this.allFlagsCache = null;
23
+ }
24
+ get(key) {
25
+ const entry = this.cache.get(key);
26
+ if (!entry)
27
+ return null;
28
+ if (Date.now() > entry.expiresAt) {
29
+ this.cache.delete(key);
30
+ return null;
31
+ }
32
+ return entry.data;
33
+ }
34
+ set(key, data) {
35
+ const ttl = this.options.cacheTtlMs ?? 30_000;
36
+ if (ttl === 0)
37
+ return;
38
+ this.cache.set(key, { data, expiresAt: Date.now() + ttl });
39
+ }
40
+ getAll() {
41
+ if (!this.allFlagsCache)
42
+ return null;
43
+ if (Date.now() > this.allFlagsCache.expiresAt) {
44
+ this.allFlagsCache = null;
45
+ return null;
46
+ }
47
+ return this.allFlagsCache.data;
48
+ }
49
+ setAll(data) {
50
+ const ttl = this.options.cacheTtlMs ?? 30_000;
51
+ if (ttl === 0)
52
+ return;
53
+ this.allFlagsCache = { data, expiresAt: Date.now() + ttl };
54
+ }
55
+ invalidate(key) {
56
+ if (key) {
57
+ this.cache.delete(key);
58
+ this.allFlagsCache = null;
59
+ }
60
+ else {
61
+ this.cache.clear();
62
+ this.allFlagsCache = null;
63
+ }
64
+ }
65
+ };
66
+ exports.FlagCacheService = FlagCacheService;
67
+ exports.FlagCacheService = FlagCacheService = __decorate([
68
+ (0, common_1.Injectable)(),
69
+ __param(0, (0, common_1.Inject)(feature_flag_constants_1.FEATURE_FLAG_MODULE_OPTIONS)),
70
+ __metadata("design:paramtypes", [Object])
71
+ ], FlagCacheService);
72
+ //# sourceMappingURL=flag-cache.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flag-cache.service.js","sourceRoot":"","sources":["../../src/services/flag-cache.service.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAAoD;AACpD,sEAAwE;AAUjE,IAAM,gBAAgB,GAAtB,MAAM,gBAAgB;IAI3B,YACuC,OAAkD;QAAjC,YAAO,GAAP,OAAO,CAA0B;QAJjF,UAAK,GAAG,IAAI,GAAG,EAAgD,CAAC;QAChE,kBAAa,GAAkD,IAAI,CAAC;IAIzE,CAAC;IAEJ,GAAG,CAAC,GAAW;QACb,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAClC,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QACxB,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,SAAS,EAAE,CAAC;YACjC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACvB,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,KAAK,CAAC,IAAI,CAAC;IACpB,CAAC;IAED,GAAG,CAAC,GAAW,EAAE,IAA8B;QAC7C,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,IAAI,MAAM,CAAC;QAC9C,IAAI,GAAG,KAAK,CAAC;YAAE,OAAO;QACtB,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,GAAG,EAAE,CAAC,CAAC;IAC7D,CAAC;IAED,MAAM;QACJ,IAAI,CAAC,IAAI,CAAC,aAAa;YAAE,OAAO,IAAI,CAAC;QACrC,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,aAAa,CAAC,SAAS,EAAE,CAAC;YAC9C,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;YAC1B,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC;IACjC,CAAC;IAED,MAAM,CAAC,IAAgC;QACrC,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,IAAI,MAAM,CAAC;QAC9C,IAAI,GAAG,KAAK,CAAC;YAAE,OAAO;QACtB,IAAI,CAAC,aAAa,GAAG,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,GAAG,EAAE,CAAC;IAC7D,CAAC;IAED,UAAU,CAAC,GAAY;QACrB,IAAI,GAAG,EAAE,CAAC;YACR,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACvB,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;QAC5B,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;YACnB,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;QAC5B,CAAC;IACH,CAAC;CACF,CAAA;AAhDY,4CAAgB;2BAAhB,gBAAgB;IAD5B,IAAA,mBAAU,GAAE;IAMR,WAAA,IAAA,eAAM,EAAC,oDAA2B,CAAC,CAAA;;GAL3B,gBAAgB,CAgD5B"}
@@ -0,0 +1,9 @@
1
+ interface FlagStore {
2
+ userId: string | null;
3
+ }
4
+ export declare class FlagContext {
5
+ private static readonly storage;
6
+ run<T>(store: FlagStore, callback: () => T): T;
7
+ getUserId(): string | null;
8
+ }
9
+ export {};
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ 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;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var FlagContext_1;
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.FlagContext = void 0;
11
+ const common_1 = require("@nestjs/common");
12
+ const async_hooks_1 = require("async_hooks");
13
+ let FlagContext = FlagContext_1 = class FlagContext {
14
+ run(store, callback) {
15
+ return FlagContext_1.storage.run(store, callback);
16
+ }
17
+ getUserId() {
18
+ return FlagContext_1.storage.getStore()?.userId ?? null;
19
+ }
20
+ };
21
+ exports.FlagContext = FlagContext;
22
+ FlagContext.storage = new async_hooks_1.AsyncLocalStorage();
23
+ exports.FlagContext = FlagContext = FlagContext_1 = __decorate([
24
+ (0, common_1.Injectable)()
25
+ ], FlagContext);
26
+ //# sourceMappingURL=flag-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flag-context.js","sourceRoot":"","sources":["../../src/services/flag-context.ts"],"names":[],"mappings":";;;;;;;;;;AAAA,2CAA4C;AAC5C,6CAAgD;AAOzC,IAAM,WAAW,mBAAjB,MAAM,WAAW;IAGtB,GAAG,CAAI,KAAgB,EAAE,QAAiB;QACxC,OAAO,aAAW,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IAClD,CAAC;IAED,SAAS;QACP,OAAO,aAAW,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,MAAM,IAAI,IAAI,CAAC;IACxD,CAAC;;AATU,kCAAW;AACE,mBAAO,GAAG,IAAI,+BAAiB,EAAa,AAArC,CAAsC;sBAD1D,WAAW;IADvB,IAAA,mBAAU,GAAE;GACA,WAAW,CAUvB"}
@@ -0,0 +1,12 @@
1
+ import { FeatureFlagWithOverrides } from '../interfaces/feature-flag.interface';
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
+ }
9
+ export declare class FlagEvaluatorService {
10
+ evaluate(flag: FeatureFlagWithOverrides, context: EvaluationContext): EvaluationResult;
11
+ }
12
+ export {};
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ 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;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.FlagEvaluatorService = void 0;
10
+ const common_1 = require("@nestjs/common");
11
+ const hash_1 = require("../utils/hash");
12
+ let FlagEvaluatorService = class FlagEvaluatorService {
13
+ evaluate(flag, context) {
14
+ // 1. Archived → always false
15
+ if (flag.archivedAt) {
16
+ return { result: false, source: 'global' };
17
+ }
18
+ // 2. User override (most specific)
19
+ if (context.userId) {
20
+ const userOverride = flag.overrides.find((o) => o.userId === context.userId &&
21
+ (o.tenantId === null || o.tenantId === context.tenantId) &&
22
+ (o.environment === null || o.environment === context.environment));
23
+ if (userOverride) {
24
+ return { result: userOverride.enabled, source: 'user_override' };
25
+ }
26
+ }
27
+ // 3. Tenant override
28
+ if (context.tenantId) {
29
+ const tenantOverride = flag.overrides.find((o) => o.tenantId === context.tenantId &&
30
+ o.userId === null &&
31
+ (o.environment === null || o.environment === context.environment));
32
+ if (tenantOverride) {
33
+ return { result: tenantOverride.enabled, source: 'tenant_override' };
34
+ }
35
+ }
36
+ // 4. Environment override
37
+ if (context.environment) {
38
+ const envOverride = flag.overrides.find((o) => o.environment === context.environment && o.tenantId === null && o.userId === null);
39
+ if (envOverride) {
40
+ return { result: envOverride.enabled, source: 'env_override' };
41
+ }
42
+ }
43
+ // 5. Percentage rollout
44
+ if (flag.percentage > 0) {
45
+ if (flag.percentage === 100) {
46
+ return { result: true, source: 'percentage' };
47
+ }
48
+ const hashKey = context.userId ?? context.tenantId ?? '';
49
+ if (!hashKey) {
50
+ return { result: flag.enabled, source: 'global' };
51
+ }
52
+ const bucket = (0, hash_1.murmurhash3)(flag.key + hashKey) % 100;
53
+ return { result: bucket < flag.percentage, source: 'percentage' };
54
+ }
55
+ // 6. Global default
56
+ return { result: flag.enabled, source: 'global' };
57
+ }
58
+ };
59
+ exports.FlagEvaluatorService = FlagEvaluatorService;
60
+ exports.FlagEvaluatorService = FlagEvaluatorService = __decorate([
61
+ (0, common_1.Injectable)()
62
+ ], FlagEvaluatorService);
63
+ //# sourceMappingURL=flag-evaluator.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flag-evaluator.service.js","sourceRoot":"","sources":["../../src/services/flag-evaluator.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export { TestFeatureFlagModule } from './test-feature-flag.module';
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TestFeatureFlagModule = void 0;
4
+ var test_feature_flag_module_1 = require("./test-feature-flag.module");
5
+ Object.defineProperty(exports, "TestFeatureFlagModule", { enumerable: true, get: function () { return test_feature_flag_module_1.TestFeatureFlagModule; } });
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":";;;AAAA,uEAAmE;AAA1D,iIAAA,qBAAqB,OAAA"}
@@ -0,0 +1,4 @@
1
+ import { DynamicModule } from '@nestjs/common';
2
+ export declare class TestFeatureFlagModule {
3
+ static register(flags?: Record<string, boolean>): DynamicModule;
4
+ }
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ 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;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var TestFeatureFlagModule_1;
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.TestFeatureFlagModule = void 0;
11
+ const common_1 = require("@nestjs/common");
12
+ const feature_flag_service_1 = require("../services/feature-flag.service");
13
+ let TestFeatureFlagModule = TestFeatureFlagModule_1 = class TestFeatureFlagModule {
14
+ static register(flags) {
15
+ return {
16
+ module: TestFeatureFlagModule_1,
17
+ global: true,
18
+ providers: [
19
+ {
20
+ provide: feature_flag_service_1.FeatureFlagService,
21
+ useValue: {
22
+ isEnabled: async (key) => flags?.[key] ?? false,
23
+ evaluateAll: async () => flags ?? {},
24
+ create: async () => ({}),
25
+ update: async () => ({}),
26
+ archive: async () => ({}),
27
+ setOverride: async () => { },
28
+ findAll: async () => [],
29
+ invalidateCache: () => { },
30
+ },
31
+ },
32
+ ],
33
+ exports: [feature_flag_service_1.FeatureFlagService],
34
+ };
35
+ }
36
+ };
37
+ exports.TestFeatureFlagModule = TestFeatureFlagModule;
38
+ exports.TestFeatureFlagModule = TestFeatureFlagModule = TestFeatureFlagModule_1 = __decorate([
39
+ (0, common_1.Module)({})
40
+ ], TestFeatureFlagModule);
41
+ //# sourceMappingURL=test-feature-flag.module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-feature-flag.module.js","sourceRoot":"","sources":["../../src/testing/test-feature-flag.module.ts"],"names":[],"mappings":";;;;;;;;;;AAAA,2CAAuD;AACvD,2EAAsE;AAG/D,IAAM,qBAAqB,6BAA3B,MAAM,qBAAqB;IAChC,MAAM,CAAC,QAAQ,CAAC,KAA+B;QAC7C,OAAO;YACL,MAAM,EAAE,uBAAqB;YAC7B,MAAM,EAAE,IAAI;YACZ,SAAS,EAAE;gBACT;oBACE,OAAO,EAAE,yCAAkB;oBAC3B,QAAQ,EAAE;wBACR,SAAS,EAAE,KAAK,EAAE,GAAW,EAAE,EAAE,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,IAAI,KAAK;wBACvD,WAAW,EAAE,KAAK,IAAI,EAAE,CAAC,KAAK,IAAI,EAAE;wBACpC,MAAM,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;wBACxB,MAAM,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;wBACxB,OAAO,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;wBACzB,WAAW,EAAE,KAAK,IAAI,EAAE,GAAE,CAAC;wBAC3B,OAAO,EAAE,KAAK,IAAI,EAAE,CAAC,EAAE;wBACvB,eAAe,EAAE,GAAG,EAAE,GAAE,CAAC;qBAC1B;iBACF;aACF;YACD,OAAO,EAAE,CAAC,yCAAkB,CAAC;SAC9B,CAAC;IACJ,CAAC;CACF,CAAA;AAvBY,sDAAqB;gCAArB,qBAAqB;IADjC,IAAA,eAAM,EAAC,EAAE,CAAC;GACE,qBAAqB,CAuBjC"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * MurmurHash3 32-bit implementation.
3
+ * Deterministic hash for percentage rollout bucket assignment.
4
+ * Same (flagKey + userId) always maps to the same bucket.
5
+ */
6
+ export declare function murmurhash3(key: string, seed?: number): number;
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.murmurhash3 = murmurhash3;
4
+ /**
5
+ * MurmurHash3 32-bit implementation.
6
+ * Deterministic hash for percentage rollout bucket assignment.
7
+ * Same (flagKey + userId) always maps to the same bucket.
8
+ */
9
+ function murmurhash3(key, seed = 0) {
10
+ let h = seed;
11
+ for (let i = 0; i < key.length; i++) {
12
+ let k = key.charCodeAt(i);
13
+ k = Math.imul(k, 0xcc9e2d51);
14
+ k = (k << 15) | (k >>> 17);
15
+ k = Math.imul(k, 0x1b873593);
16
+ h ^= k;
17
+ h = (h << 13) | (h >>> 19);
18
+ h = Math.imul(h, 5) + 0xe6546b64;
19
+ }
20
+ h ^= key.length;
21
+ h ^= h >>> 16;
22
+ h = Math.imul(h, 0x85ebca6b);
23
+ h ^= h >>> 13;
24
+ h = Math.imul(h, 0xc2b2ae35);
25
+ h ^= h >>> 16;
26
+ return h >>> 0;
27
+ }
28
+ //# sourceMappingURL=hash.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hash.js","sourceRoot":"","sources":["../../src/utils/hash.ts"],"names":[],"mappings":";;AAKA,kCAqBC;AA1BD;;;;GAIG;AACH,SAAgB,WAAW,CAAC,GAAW,EAAE,OAAe,CAAC;IACvD,IAAI,CAAC,GAAG,IAAI,CAAC;IAEb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACpC,IAAI,CAAC,GAAG,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;QAC1B,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC;QAC7B,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;QAC3B,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC;QAC7B,CAAC,IAAI,CAAC,CAAC;QACP,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;QAC3B,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,UAAU,CAAC;IACnC,CAAC;IAED,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC;IAChB,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;IACd,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC;IAC7B,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;IACd,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC;IAC7B,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;IAEd,OAAO,CAAC,KAAK,CAAC,CAAC;AACjB,CAAC"}
package/package.json ADDED
@@ -0,0 +1,88 @@
1
+ {
2
+ "name": "@nestarc/feature-flag",
3
+ "version": "0.1.0",
4
+ "description": "DB-backed feature flags for NestJS + Prisma + PostgreSQL with tenant-aware overrides",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "default": "./dist/index.js"
11
+ },
12
+ "./testing": {
13
+ "types": "./dist/testing/index.d.ts",
14
+ "default": "./dist/testing/index.js"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "prisma"
20
+ ],
21
+ "scripts": {
22
+ "build": "tsc -p tsconfig.build.json",
23
+ "test": "jest",
24
+ "test:cov": "jest --coverage",
25
+ "lint": "eslint 'src/**/*.ts' 'test/**/*.ts'",
26
+ "format": "prettier --write 'src/**/*.ts' 'test/**/*.ts'",
27
+ "prisma:generate": "prisma generate",
28
+ "docker:up": "docker compose up -d --wait",
29
+ "docker:down": "docker compose down",
30
+ "db:migrate": "dotenv -e .env.test -- npx prisma migrate deploy",
31
+ "db:reset": "dotenv -e .env.test -- npx prisma migrate reset --force",
32
+ "pretest:e2e": "npm run docker:up && npm run db:migrate",
33
+ "test:e2e": "dotenv -e .env.test -- jest --config jest.e2e.config.ts --runInBand"
34
+ },
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "https://github.com/nestarc/nestjs-feature-flag"
38
+ },
39
+ "keywords": [
40
+ "nestjs",
41
+ "feature-flag",
42
+ "feature-toggle",
43
+ "prisma",
44
+ "postgresql",
45
+ "multi-tenant"
46
+ ],
47
+ "author": "nestarc",
48
+ "license": "MIT",
49
+ "peerDependencies": {
50
+ "@nestjs/common": "^10.0.0 || ^11.0.0",
51
+ "@nestjs/core": "^10.0.0 || ^11.0.0",
52
+ "@prisma/client": "^5.0.0 || ^6.0.0",
53
+ "reflect-metadata": "^0.1.13 || ^0.2.0",
54
+ "rxjs": "^7.0.0"
55
+ },
56
+ "peerDependenciesMeta": {
57
+ "@nestarc/tenancy": {
58
+ "optional": true
59
+ },
60
+ "@nestjs/event-emitter": {
61
+ "optional": true
62
+ }
63
+ },
64
+ "devDependencies": {
65
+ "@eslint/js": "^9.0.0",
66
+ "@nestjs/common": "^11.0.0",
67
+ "@nestjs/core": "^11.0.0",
68
+ "@nestjs/event-emitter": "^3.0.0",
69
+ "@nestjs/platform-express": "^11.0.0",
70
+ "@nestjs/testing": "^11.0.0",
71
+ "@prisma/client": "^6.0.0",
72
+ "@types/express": "^5.0.0",
73
+ "@types/jest": "^29.5.0",
74
+ "@types/supertest": "^7.2.0",
75
+ "dotenv-cli": "^11.0.0",
76
+ "eslint": "^9.0.0",
77
+ "jest": "^29.7.0",
78
+ "prettier": "^3.0.0",
79
+ "prisma": "^6.0.0",
80
+ "reflect-metadata": "^0.2.0",
81
+ "rxjs": "^7.8.0",
82
+ "supertest": "^7.2.2",
83
+ "ts-jest": "^29.2.0",
84
+ "ts-node": "^10.9.2",
85
+ "typescript": "^5.5.0",
86
+ "typescript-eslint": "^8.0.0"
87
+ }
88
+ }
@@ -0,0 +1,76 @@
1
+ -- CreateTable
2
+ CREATE TABLE "feature_flags" (
3
+ "id" UUID NOT NULL DEFAULT gen_random_uuid(),
4
+ "key" TEXT NOT NULL,
5
+ "description" TEXT,
6
+ "enabled" BOOLEAN NOT NULL DEFAULT false,
7
+ "percentage" INTEGER NOT NULL DEFAULT 0,
8
+ "metadata" JSONB NOT NULL DEFAULT '{}',
9
+ "archived_at" TIMESTAMPTZ,
10
+ "created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
11
+ "updated_at" TIMESTAMPTZ NOT NULL,
12
+
13
+ CONSTRAINT "feature_flags_pkey" PRIMARY KEY ("id")
14
+ );
15
+
16
+ -- CreateTable
17
+ CREATE TABLE "feature_flag_overrides" (
18
+ "id" UUID NOT NULL DEFAULT gen_random_uuid(),
19
+ "flag_id" UUID NOT NULL,
20
+ "tenant_id" TEXT,
21
+ "user_id" TEXT,
22
+ "environment" TEXT,
23
+ "enabled" BOOLEAN NOT NULL,
24
+ "created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
25
+ "updated_at" TIMESTAMPTZ NOT NULL,
26
+
27
+ CONSTRAINT "feature_flag_overrides_pkey" PRIMARY KEY ("id")
28
+ );
29
+
30
+ -- CreateIndex
31
+ CREATE UNIQUE INDEX "feature_flags_key_key" ON "feature_flags"("key");
32
+
33
+ -- CreateIndex
34
+ CREATE INDEX "idx_override_flag_id" ON "feature_flag_overrides"("flag_id");
35
+
36
+ -- AddForeignKey
37
+ ALTER TABLE "feature_flag_overrides"
38
+ ADD CONSTRAINT "feature_flag_overrides_flag_id_fkey"
39
+ FOREIGN KEY ("flag_id") REFERENCES "feature_flags"("id")
40
+ ON DELETE CASCADE ON UPDATE CASCADE;
41
+
42
+ -- Partial unique indexes for NULL-safe override uniqueness.
43
+ -- PostgreSQL treats NULL != NULL in standard unique constraints,
44
+ -- so we need one partial index per NULL/NOT-NULL combination.
45
+
46
+ CREATE UNIQUE INDEX uq_override_000
47
+ ON feature_flag_overrides (flag_id)
48
+ WHERE tenant_id IS NULL AND user_id IS NULL AND environment IS NULL;
49
+
50
+ CREATE UNIQUE INDEX uq_override_001
51
+ ON feature_flag_overrides (flag_id, environment)
52
+ WHERE tenant_id IS NULL AND user_id IS NULL AND environment IS NOT NULL;
53
+
54
+ CREATE UNIQUE INDEX uq_override_010
55
+ ON feature_flag_overrides (flag_id, user_id)
56
+ WHERE tenant_id IS NULL AND user_id IS NOT NULL AND environment IS NULL;
57
+
58
+ CREATE UNIQUE INDEX uq_override_011
59
+ ON feature_flag_overrides (flag_id, user_id, environment)
60
+ WHERE tenant_id IS NULL AND user_id IS NOT NULL AND environment IS NOT NULL;
61
+
62
+ CREATE UNIQUE INDEX uq_override_100
63
+ ON feature_flag_overrides (flag_id, tenant_id)
64
+ WHERE tenant_id IS NOT NULL AND user_id IS NULL AND environment IS NULL;
65
+
66
+ CREATE UNIQUE INDEX uq_override_101
67
+ ON feature_flag_overrides (flag_id, tenant_id, environment)
68
+ WHERE tenant_id IS NOT NULL AND user_id IS NULL AND environment IS NOT NULL;
69
+
70
+ CREATE UNIQUE INDEX uq_override_110
71
+ ON feature_flag_overrides (flag_id, tenant_id, user_id)
72
+ WHERE tenant_id IS NOT NULL AND user_id IS NOT NULL AND environment IS NULL;
73
+
74
+ CREATE UNIQUE INDEX uq_override_111
75
+ ON feature_flag_overrides (flag_id, tenant_id, user_id, environment)
76
+ WHERE tenant_id IS NOT NULL AND user_id IS NOT NULL AND environment IS NOT NULL;
@@ -0,0 +1,3 @@
1
+ # Please do not edit this file manually
2
+ # It should be added in your version-control system (i.e. Git)
3
+ provider = "postgresql"
@@ -0,0 +1,41 @@
1
+ generator client {
2
+ provider = "prisma-client-js"
3
+ }
4
+
5
+ datasource db {
6
+ provider = "postgresql"
7
+ url = env("DATABASE_URL")
8
+ }
9
+
10
+ model FeatureFlag {
11
+ id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
12
+ key String @unique
13
+ description String?
14
+ enabled Boolean @default(false)
15
+ percentage Int @default(0)
16
+ metadata Json @default("{}")
17
+ archivedAt DateTime? @map("archived_at") @db.Timestamptz()
18
+ createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz()
19
+ updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz()
20
+
21
+ overrides FeatureFlagOverride[]
22
+
23
+ @@map("feature_flags")
24
+ }
25
+
26
+ model FeatureFlagOverride {
27
+ id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
28
+ flagId String @map("flag_id") @db.Uuid
29
+ tenantId String? @map("tenant_id")
30
+ userId String? @map("user_id")
31
+ environment String?
32
+ enabled Boolean
33
+ createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz()
34
+ updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz()
35
+
36
+ flag FeatureFlag @relation(fields: [flagId], references: [id], onDelete: Cascade)
37
+
38
+ // Unique indexes defined in raw SQL migration (0001) to handle NULL equality correctly
39
+ @@index([flagId], map: "idx_override_flag_id")
40
+ @@map("feature_flag_overrides")
41
+ }