@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.
- package/LICENSE +21 -0
- package/README.md +509 -0
- package/dist/decorators/bypass-feature-flag.decorator.d.ts +1 -0
- package/dist/decorators/bypass-feature-flag.decorator.js +8 -0
- package/dist/decorators/bypass-feature-flag.decorator.js.map +1 -0
- package/dist/decorators/feature-flag.decorator.d.ts +2 -0
- package/dist/decorators/feature-flag.decorator.js +10 -0
- package/dist/decorators/feature-flag.decorator.js.map +1 -0
- package/dist/events/feature-flag.events.d.ts +29 -0
- package/dist/events/feature-flag.events.js +13 -0
- package/dist/events/feature-flag.events.js.map +1 -0
- package/dist/feature-flag.constants.d.ts +5 -0
- package/dist/feature-flag.constants.js +9 -0
- package/dist/feature-flag.constants.js.map +1 -0
- package/dist/feature-flag.module.d.ts +14 -0
- package/dist/feature-flag.module.js +132 -0
- package/dist/feature-flag.module.js.map +1 -0
- package/dist/guards/feature-flag.guard.d.ts +9 -0
- package/dist/guards/feature-flag.guard.js +53 -0
- package/dist/guards/feature-flag.guard.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -0
- package/dist/interfaces/evaluation-context.interface.d.ts +8 -0
- package/dist/interfaces/evaluation-context.interface.js +3 -0
- package/dist/interfaces/evaluation-context.interface.js.map +1 -0
- package/dist/interfaces/feature-flag-options.interface.d.ts +27 -0
- package/dist/interfaces/feature-flag-options.interface.js +3 -0
- package/dist/interfaces/feature-flag-options.interface.js.map +1 -0
- package/dist/interfaces/feature-flag.interface.d.ts +45 -0
- package/dist/interfaces/feature-flag.interface.js +3 -0
- package/dist/interfaces/feature-flag.interface.js.map +1 -0
- package/dist/middleware/flag-context.middleware.d.ts +10 -0
- package/dist/middleware/flag-context.middleware.js +35 -0
- package/dist/middleware/flag-context.middleware.js.map +1 -0
- package/dist/services/feature-flag.service.d.ts +29 -0
- package/dist/services/feature-flag.service.js +208 -0
- package/dist/services/feature-flag.service.js.map +1 -0
- package/dist/services/flag-cache.service.d.ts +13 -0
- package/dist/services/flag-cache.service.js +72 -0
- package/dist/services/flag-cache.service.js.map +1 -0
- package/dist/services/flag-context.d.ts +9 -0
- package/dist/services/flag-context.js +26 -0
- package/dist/services/flag-context.js.map +1 -0
- package/dist/services/flag-evaluator.service.d.ts +12 -0
- package/dist/services/flag-evaluator.service.js +63 -0
- package/dist/services/flag-evaluator.service.js.map +1 -0
- package/dist/testing/index.d.ts +1 -0
- package/dist/testing/index.js +6 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/test-feature-flag.module.d.ts +4 -0
- package/dist/testing/test-feature-flag.module.js +41 -0
- package/dist/testing/test-feature-flag.module.js.map +1 -0
- package/dist/utils/hash.d.ts +6 -0
- package/dist/utils/hash.js +28 -0
- package/dist/utils/hash.js.map +1 -0
- package/package.json +88 -0
- package/prisma/migrations/20260405000000_init/migration.sql +76 -0
- package/prisma/migrations/migration_lock.toml +3 -0
- 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,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,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,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,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
|
+
}
|