@nestarc/feature-flag 0.1.0 → 0.3.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/README.md +252 -87
- package/dist/admin/admin-options.interface.d.ts +5 -0
- package/dist/admin/admin-options.interface.js +3 -0
- package/dist/admin/admin-options.interface.js.map +1 -0
- package/dist/admin/feature-flag-admin.controller.d.ts +14 -0
- package/dist/admin/feature-flag-admin.controller.js +106 -0
- package/dist/admin/feature-flag-admin.controller.js.map +1 -0
- package/dist/admin/feature-flag-admin.dto.d.ts +22 -0
- package/dist/admin/feature-flag-admin.dto.js +93 -0
- package/dist/admin/feature-flag-admin.dto.js.map +1 -0
- package/dist/admin/feature-flag-admin.module.d.ts +5 -0
- package/dist/admin/feature-flag-admin.module.js +33 -0
- package/dist/admin/feature-flag-admin.module.js.map +1 -0
- package/dist/admin/targeting-attributes.validator.d.ts +6 -0
- package/dist/admin/targeting-attributes.validator.js +36 -0
- package/dist/admin/targeting-attributes.validator.js.map +1 -0
- package/dist/cache/memory-cache.adapter.d.ts +11 -0
- package/dist/{services/flag-cache.service.js → cache/memory-cache.adapter.js} +18 -31
- package/dist/cache/memory-cache.adapter.js.map +1 -0
- package/dist/cache/redis-cache.adapter.d.ts +26 -0
- package/dist/cache/redis-cache.adapter.js +102 -0
- package/dist/cache/redis-cache.adapter.js.map +1 -0
- package/dist/events/feature-flag.events.d.ts +4 -5
- package/dist/feature-flag.constants.d.ts +3 -0
- package/dist/feature-flag.constants.js +4 -1
- package/dist/feature-flag.constants.js.map +1 -1
- package/dist/feature-flag.module.js +49 -10
- package/dist/feature-flag.module.js.map +1 -1
- package/dist/index.d.ts +10 -2
- package/dist/index.js +13 -1
- package/dist/index.js.map +1 -1
- package/dist/interfaces/cache-adapter.interface.d.ts +9 -0
- package/dist/interfaces/cache-adapter.interface.js +3 -0
- package/dist/interfaces/cache-adapter.interface.js.map +1 -0
- package/dist/interfaces/evaluation-context.interface.d.ts +6 -3
- package/dist/interfaces/feature-flag-options.interface.d.ts +3 -0
- package/dist/interfaces/feature-flag-repository.interface.d.ts +22 -0
- package/dist/interfaces/feature-flag-repository.interface.js +3 -0
- package/dist/interfaces/feature-flag-repository.interface.js.map +1 -0
- package/dist/interfaces/feature-flag.interface.d.ts +11 -6
- package/dist/interfaces/tenant-context-provider.interface.d.ts +3 -0
- package/dist/interfaces/tenant-context-provider.interface.js +3 -0
- package/dist/interfaces/tenant-context-provider.interface.js.map +1 -0
- package/dist/repositories/prisma-feature-flag.repository.d.ts +18 -0
- package/dist/repositories/prisma-feature-flag.repository.js +161 -0
- package/dist/repositories/prisma-feature-flag.repository.js.map +1 -0
- package/dist/services/default-tenant-context-provider.d.ts +7 -0
- package/dist/services/default-tenant-context-provider.js +35 -0
- package/dist/services/default-tenant-context-provider.js.map +1 -0
- package/dist/services/feature-flag.service.d.ts +21 -13
- package/dist/services/feature-flag.service.js +111 -126
- package/dist/services/feature-flag.service.js.map +1 -1
- package/dist/services/flag-context-resolver.d.ts +11 -0
- package/dist/services/flag-context-resolver.js +51 -0
- package/dist/services/flag-context-resolver.js.map +1 -0
- package/dist/services/flag-evaluator.service.d.ts +1 -0
- package/dist/services/flag-evaluator.service.js +29 -27
- package/dist/services/flag-evaluator.service.js.map +1 -1
- package/dist/services/flag-event-publisher.d.ts +7 -0
- package/dist/services/flag-event-publisher.js +37 -0
- package/dist/services/flag-event-publisher.js.map +1 -0
- package/dist/testing/test-feature-flag.module.js +26 -5
- package/dist/testing/test-feature-flag.module.js.map +1 -1
- package/dist/utils/targeting-attributes.d.ts +9 -0
- package/dist/utils/targeting-attributes.js +44 -0
- package/dist/utils/targeting-attributes.js.map +1 -0
- package/package.json +20 -3
- package/prisma/migrations/20260512000000_attribute_targeting/migration.sql +59 -0
- package/prisma/schema.prisma +7 -9
- package/dist/services/flag-cache.service.d.ts +0 -13
- package/dist/services/flag-cache.service.js.map +0 -1
|
@@ -14,46 +14,46 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
15
|
exports.FeatureFlagService = void 0;
|
|
16
16
|
const common_1 = require("@nestjs/common");
|
|
17
|
-
const core_1 = require("@nestjs/core");
|
|
18
17
|
const feature_flag_constants_1 = require("../feature-flag.constants");
|
|
19
|
-
const flag_cache_service_1 = require("./flag-cache.service");
|
|
20
18
|
const flag_evaluator_service_1 = require("./flag-evaluator.service");
|
|
21
|
-
const
|
|
19
|
+
const flag_context_resolver_1 = require("./flag-context-resolver");
|
|
20
|
+
const flag_event_publisher_1 = require("./flag-event-publisher");
|
|
22
21
|
const feature_flag_events_1 = require("../events/feature-flag.events");
|
|
22
|
+
const targeting_attributes_1 = require("../utils/targeting-attributes");
|
|
23
|
+
const CACHE_INVALIDATION_FAILED = 'feature-flag.cache.invalidation-failed';
|
|
23
24
|
let FeatureFlagService = class FeatureFlagService {
|
|
24
|
-
constructor(options,
|
|
25
|
+
constructor(options, repository, cacheAdapter, evaluator, contextResolver, eventPublisher) {
|
|
25
26
|
this.options = options;
|
|
26
|
-
this.
|
|
27
|
-
this.
|
|
27
|
+
this.repository = repository;
|
|
28
|
+
this.cacheAdapter = cacheAdapter;
|
|
28
29
|
this.evaluator = evaluator;
|
|
29
|
-
this.
|
|
30
|
-
this.
|
|
31
|
-
|
|
30
|
+
this.contextResolver = contextResolver;
|
|
31
|
+
this.eventPublisher = eventPublisher;
|
|
32
|
+
}
|
|
33
|
+
get cacheTtlMs() {
|
|
34
|
+
return this.options.cacheTtlMs ?? 30_000;
|
|
32
35
|
}
|
|
33
36
|
async isEnabled(flagKey, explicitContext) {
|
|
34
37
|
const flag = await this.resolveFlag(flagKey);
|
|
35
38
|
if (!flag) {
|
|
36
39
|
return this.options.defaultOnMissing ?? false;
|
|
37
40
|
}
|
|
38
|
-
const context = this.
|
|
41
|
+
const context = this.contextResolver.resolve(explicitContext);
|
|
39
42
|
const startTime = Date.now();
|
|
40
43
|
const { result, source } = this.evaluator.evaluate(flag, context);
|
|
41
44
|
const evaluationTimeMs = Date.now() - startTime;
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
};
|
|
50
|
-
this.eventEmitter.emit(feature_flag_events_1.FeatureFlagEvents.EVALUATED, event);
|
|
51
|
-
}
|
|
45
|
+
this.eventPublisher.emit(feature_flag_events_1.FeatureFlagEvents.EVALUATED, {
|
|
46
|
+
flagKey,
|
|
47
|
+
result,
|
|
48
|
+
context,
|
|
49
|
+
source,
|
|
50
|
+
evaluationTimeMs,
|
|
51
|
+
});
|
|
52
52
|
return result;
|
|
53
53
|
}
|
|
54
54
|
async evaluateAll(explicitContext) {
|
|
55
55
|
const flags = await this.resolveAllFlags();
|
|
56
|
-
const context = this.
|
|
56
|
+
const context = this.contextResolver.resolve(explicitContext);
|
|
57
57
|
const result = {};
|
|
58
58
|
for (const flag of flags) {
|
|
59
59
|
result[flag.key] = this.evaluator.evaluate(flag, context).result;
|
|
@@ -61,148 +61,133 @@ let FeatureFlagService = class FeatureFlagService {
|
|
|
61
61
|
return result;
|
|
62
62
|
}
|
|
63
63
|
async create(input) {
|
|
64
|
-
const flag = await this.
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
description: input.description,
|
|
68
|
-
enabled: input.enabled ?? false,
|
|
69
|
-
percentage: input.percentage ?? 0,
|
|
70
|
-
metadata: input.metadata ?? {},
|
|
71
|
-
},
|
|
72
|
-
include: { overrides: true },
|
|
73
|
-
});
|
|
74
|
-
this.cache.invalidate();
|
|
75
|
-
if (this.options.emitEvents && this.eventEmitter) {
|
|
76
|
-
this.eventEmitter.emit(feature_flag_events_1.FeatureFlagEvents.CREATED, { flagKey: input.key, action: 'created' });
|
|
77
|
-
}
|
|
64
|
+
const flag = await this.repository.createFlag(input);
|
|
65
|
+
await this.safeInvalidateCache();
|
|
66
|
+
this.eventPublisher.emit(feature_flag_events_1.FeatureFlagEvents.CREATED, { flagKey: input.key, action: 'created' });
|
|
78
67
|
return flag;
|
|
79
68
|
}
|
|
80
69
|
async update(key, input) {
|
|
81
|
-
const flag = await this.
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
...(input.description !== undefined && { description: input.description }),
|
|
85
|
-
...(input.enabled !== undefined && { enabled: input.enabled }),
|
|
86
|
-
...(input.percentage !== undefined && { percentage: input.percentage }),
|
|
87
|
-
...(input.metadata !== undefined && { metadata: input.metadata }),
|
|
88
|
-
},
|
|
89
|
-
include: { overrides: true },
|
|
90
|
-
});
|
|
91
|
-
this.cache.invalidate(key);
|
|
92
|
-
if (this.options.emitEvents && this.eventEmitter) {
|
|
93
|
-
this.eventEmitter.emit(feature_flag_events_1.FeatureFlagEvents.UPDATED, { flagKey: key, action: 'updated' });
|
|
94
|
-
}
|
|
70
|
+
const flag = await this.repository.updateFlag(key, input);
|
|
71
|
+
await this.safeInvalidateCache(key);
|
|
72
|
+
this.eventPublisher.emit(feature_flag_events_1.FeatureFlagEvents.UPDATED, { flagKey: key, action: 'updated' });
|
|
95
73
|
return flag;
|
|
96
74
|
}
|
|
97
75
|
async archive(key) {
|
|
98
|
-
const flag = await this.
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
include: { overrides: true },
|
|
102
|
-
});
|
|
103
|
-
this.cache.invalidate(key);
|
|
104
|
-
if (this.options.emitEvents && this.eventEmitter) {
|
|
105
|
-
this.eventEmitter.emit(feature_flag_events_1.FeatureFlagEvents.ARCHIVED, { flagKey: key, action: 'archived' });
|
|
106
|
-
}
|
|
76
|
+
const flag = await this.repository.archiveFlag(key);
|
|
77
|
+
await this.safeInvalidateCache(key);
|
|
78
|
+
this.eventPublisher.emit(feature_flag_events_1.FeatureFlagEvents.ARCHIVED, { flagKey: key, action: 'archived' });
|
|
107
79
|
return flag;
|
|
108
80
|
}
|
|
109
81
|
async setOverride(key, input) {
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
82
|
+
const attributes = this.normalizeOverrideAttributes(input.attributes);
|
|
83
|
+
const priority = input.priority ?? 0;
|
|
84
|
+
const flagId = await this.repository.findFlagIdByKey(key);
|
|
85
|
+
if (!flagId) {
|
|
86
|
+
throw new common_1.NotFoundException(`Feature flag "${key}" not found`);
|
|
113
87
|
}
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
tenantId: input.tenantId ?? null,
|
|
117
|
-
userId: input.userId ?? null,
|
|
118
|
-
environment: input.environment ?? null,
|
|
119
|
-
};
|
|
120
|
-
const existing = await this.prisma.featureFlagOverride.findFirst({ where });
|
|
88
|
+
const criteria = { attributes };
|
|
89
|
+
const existing = await this.repository.findOverride(flagId, criteria);
|
|
121
90
|
if (existing) {
|
|
122
|
-
await this.
|
|
123
|
-
|
|
124
|
-
|
|
91
|
+
await this.repository.updateOverride(existing.id, {
|
|
92
|
+
enabled: input.enabled,
|
|
93
|
+
priority,
|
|
125
94
|
});
|
|
126
95
|
}
|
|
127
96
|
else {
|
|
128
|
-
await this.
|
|
129
|
-
data: { ...where, enabled: input.enabled },
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
this.cache.invalidate(key);
|
|
133
|
-
if (this.options.emitEvents && this.eventEmitter) {
|
|
134
|
-
this.eventEmitter.emit(feature_flag_events_1.FeatureFlagEvents.OVERRIDE_SET, {
|
|
135
|
-
flagKey: key,
|
|
136
|
-
...input,
|
|
137
|
-
action: 'set',
|
|
138
|
-
});
|
|
97
|
+
await this.repository.createOverride(flagId, criteria, input.enabled, priority);
|
|
139
98
|
}
|
|
99
|
+
await this.safeInvalidateCache(key);
|
|
100
|
+
this.eventPublisher.emit(feature_flag_events_1.FeatureFlagEvents.OVERRIDE_SET, {
|
|
101
|
+
flagKey: key,
|
|
102
|
+
attributes,
|
|
103
|
+
enabled: input.enabled,
|
|
104
|
+
priority,
|
|
105
|
+
action: 'set',
|
|
106
|
+
});
|
|
140
107
|
}
|
|
141
108
|
async findAll() {
|
|
142
|
-
return this.
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
109
|
+
return this.repository.findAllActiveFlags();
|
|
110
|
+
}
|
|
111
|
+
async invalidateCache() {
|
|
112
|
+
await this.cacheAdapter.invalidate();
|
|
113
|
+
this.eventPublisher.emit(feature_flag_events_1.FeatureFlagEvents.CACHE_INVALIDATED, {});
|
|
114
|
+
}
|
|
115
|
+
async findByKey(key) {
|
|
116
|
+
const flag = await this.repository.findFlagByKey(key);
|
|
117
|
+
if (!flag) {
|
|
118
|
+
throw new common_1.NotFoundException(`Feature flag "${key}" not found`);
|
|
119
|
+
}
|
|
120
|
+
return flag;
|
|
121
|
+
}
|
|
122
|
+
async removeOverride(key, input) {
|
|
123
|
+
const attributes = this.normalizeOverrideAttributes(input.attributes);
|
|
124
|
+
const flagId = await this.repository.findFlagIdByKey(key);
|
|
125
|
+
if (!flagId) {
|
|
126
|
+
throw new common_1.NotFoundException(`Feature flag "${key}" not found`);
|
|
127
|
+
}
|
|
128
|
+
const criteria = { attributes };
|
|
129
|
+
const existing = await this.repository.findOverride(flagId, criteria);
|
|
130
|
+
if (existing) {
|
|
131
|
+
await this.repository.deleteOverride(existing.id);
|
|
132
|
+
}
|
|
133
|
+
await this.safeInvalidateCache(key);
|
|
134
|
+
this.eventPublisher.emit(feature_flag_events_1.FeatureFlagEvents.OVERRIDE_REMOVED, {
|
|
135
|
+
flagKey: key,
|
|
136
|
+
attributes,
|
|
137
|
+
action: 'removed',
|
|
146
138
|
});
|
|
147
139
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
140
|
+
normalizeOverrideAttributes(input) {
|
|
141
|
+
try {
|
|
142
|
+
return (0, targeting_attributes_1.normalizeTargetingAttributes)(input, { allowEmpty: false });
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
throw new common_1.BadRequestException(String(error instanceof Error ? error.message : error));
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Best-effort cache invalidation for mutation paths.
|
|
150
|
+
* DB write already succeeded — cache failure should not fail the caller.
|
|
151
|
+
* Stale entries self-heal via TTL (default 30s).
|
|
152
|
+
*/
|
|
153
|
+
async safeInvalidateCache(key) {
|
|
154
|
+
try {
|
|
155
|
+
await this.cacheAdapter.invalidate(key);
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
this.eventPublisher.emit(CACHE_INVALIDATION_FAILED, {
|
|
159
|
+
key: key ?? '__all__',
|
|
160
|
+
error: String(error),
|
|
161
|
+
});
|
|
152
162
|
}
|
|
153
163
|
}
|
|
154
164
|
async resolveFlag(key) {
|
|
155
|
-
const cached = this.
|
|
165
|
+
const cached = await this.cacheAdapter.get(key);
|
|
156
166
|
if (cached)
|
|
157
167
|
return cached;
|
|
158
|
-
const flag = await this.
|
|
159
|
-
where: { key },
|
|
160
|
-
include: { overrides: true },
|
|
161
|
-
});
|
|
168
|
+
const flag = await this.repository.findFlagByKey(key);
|
|
162
169
|
if (flag) {
|
|
163
|
-
this.
|
|
170
|
+
await this.cacheAdapter.set(key, flag, this.cacheTtlMs);
|
|
164
171
|
}
|
|
165
172
|
return flag;
|
|
166
173
|
}
|
|
167
174
|
async resolveAllFlags() {
|
|
168
|
-
const cached = this.
|
|
175
|
+
const cached = await this.cacheAdapter.getAll();
|
|
169
176
|
if (cached)
|
|
170
177
|
return cached;
|
|
171
|
-
const flags = await this.
|
|
172
|
-
|
|
173
|
-
include: { overrides: true },
|
|
174
|
-
});
|
|
175
|
-
this.cache.setAll(flags);
|
|
178
|
+
const flags = await this.repository.findAllActiveFlags();
|
|
179
|
+
await this.cacheAdapter.setAll(flags, this.cacheTtlMs);
|
|
176
180
|
return flags;
|
|
177
181
|
}
|
|
178
|
-
buildContext(explicit) {
|
|
179
|
-
return {
|
|
180
|
-
userId: explicit?.userId !== undefined ? explicit.userId : this.flagContext.getUserId(),
|
|
181
|
-
tenantId: explicit?.tenantId !== undefined ? explicit.tenantId : this.getTenantId(),
|
|
182
|
-
environment: explicit?.environment !== undefined ? explicit.environment : this.options.environment,
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
getTenantId() {
|
|
186
|
-
try {
|
|
187
|
-
const { TenancyService } = require('@nestarc/tenancy');
|
|
188
|
-
const tenancyService = this.moduleRef.get(TenancyService, { strict: false });
|
|
189
|
-
return tenancyService?.getCurrentTenant() ?? null;
|
|
190
|
-
}
|
|
191
|
-
catch {
|
|
192
|
-
return null;
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
182
|
};
|
|
196
183
|
exports.FeatureFlagService = FeatureFlagService;
|
|
197
184
|
exports.FeatureFlagService = FeatureFlagService = __decorate([
|
|
198
185
|
(0, common_1.Injectable)(),
|
|
199
186
|
__param(0, (0, common_1.Inject)(feature_flag_constants_1.FEATURE_FLAG_MODULE_OPTIONS)),
|
|
200
|
-
__param(1, (0, common_1.Inject)(
|
|
201
|
-
__param(
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
flag_context_1.FlagContext,
|
|
206
|
-
core_1.ModuleRef, Object])
|
|
187
|
+
__param(1, (0, common_1.Inject)(feature_flag_constants_1.FEATURE_FLAG_REPOSITORY)),
|
|
188
|
+
__param(2, (0, common_1.Inject)(feature_flag_constants_1.CACHE_ADAPTER)),
|
|
189
|
+
__metadata("design:paramtypes", [Object, Object, Object, flag_evaluator_service_1.FlagEvaluatorService,
|
|
190
|
+
flag_context_resolver_1.FlagContextResolver,
|
|
191
|
+
flag_event_publisher_1.FlagEventPublisher])
|
|
207
192
|
], FeatureFlagService);
|
|
208
193
|
//# sourceMappingURL=feature-flag.service.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"feature-flag.service.js","sourceRoot":"","sources":["../../src/services/feature-flag.service.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,
|
|
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,11 @@
|
|
|
1
|
+
import { FeatureFlagModuleOptions } from '../interfaces/feature-flag-options.interface';
|
|
2
|
+
import { EvaluationContext } from '../interfaces/evaluation-context.interface';
|
|
3
|
+
import { TenantContextProvider } from '../interfaces/tenant-context-provider.interface';
|
|
4
|
+
import { FlagContext } from './flag-context';
|
|
5
|
+
export declare class FlagContextResolver {
|
|
6
|
+
private readonly options;
|
|
7
|
+
private readonly flagContext;
|
|
8
|
+
private readonly tenantProvider;
|
|
9
|
+
constructor(options: FeatureFlagModuleOptions, flagContext: FlagContext, tenantProvider: TenantContextProvider);
|
|
10
|
+
resolve(explicit?: EvaluationContext): EvaluationContext;
|
|
11
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
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.FlagContextResolver = void 0;
|
|
16
|
+
const common_1 = require("@nestjs/common");
|
|
17
|
+
const feature_flag_constants_1 = require("../feature-flag.constants");
|
|
18
|
+
const flag_context_1 = require("./flag-context");
|
|
19
|
+
let FlagContextResolver = class FlagContextResolver {
|
|
20
|
+
constructor(options, flagContext, tenantProvider) {
|
|
21
|
+
this.options = options;
|
|
22
|
+
this.flagContext = flagContext;
|
|
23
|
+
this.tenantProvider = tenantProvider;
|
|
24
|
+
}
|
|
25
|
+
resolve(explicit) {
|
|
26
|
+
const userId = explicit?.userId !== undefined ? explicit.userId : this.flagContext.getUserId();
|
|
27
|
+
const tenantId = explicit?.tenantId !== undefined
|
|
28
|
+
? explicit.tenantId
|
|
29
|
+
: this.tenantProvider.getCurrentTenantId();
|
|
30
|
+
const environment = explicit?.environment !== undefined ? explicit.environment : this.options.environment;
|
|
31
|
+
return {
|
|
32
|
+
userId,
|
|
33
|
+
tenantId,
|
|
34
|
+
environment,
|
|
35
|
+
attributes: {
|
|
36
|
+
...(explicit?.attributes ?? {}),
|
|
37
|
+
...(userId !== undefined && { userId }),
|
|
38
|
+
...(tenantId !== undefined && { tenantId }),
|
|
39
|
+
...(environment !== undefined && { environment }),
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
exports.FlagContextResolver = FlagContextResolver;
|
|
45
|
+
exports.FlagContextResolver = FlagContextResolver = __decorate([
|
|
46
|
+
(0, common_1.Injectable)(),
|
|
47
|
+
__param(0, (0, common_1.Inject)(feature_flag_constants_1.FEATURE_FLAG_MODULE_OPTIONS)),
|
|
48
|
+
__param(2, (0, common_1.Inject)(feature_flag_constants_1.TENANT_CONTEXT_PROVIDER)),
|
|
49
|
+
__metadata("design:paramtypes", [Object, flag_context_1.FlagContext, Object])
|
|
50
|
+
], FlagContextResolver);
|
|
51
|
+
//# sourceMappingURL=flag-context-resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flag-context-resolver.js","sourceRoot":"","sources":["../../src/services/flag-context-resolver.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAAoD;AACpD,sEAAiG;AAIjG,iDAA6C;AAGtC,IAAM,mBAAmB,GAAzB,MAAM,mBAAmB;IAC9B,YACwD,OAAiC,EACtE,WAAwB,EACS,cAAqC;QAFjC,YAAO,GAAP,OAAO,CAA0B;QACtE,gBAAW,GAAX,WAAW,CAAa;QACS,mBAAc,GAAd,cAAc,CAAuB;IACtF,CAAC;IAEJ,OAAO,CAAC,QAA4B;QAClC,MAAM,MAAM,GACV,QAAQ,EAAE,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,SAAS,EAAE,CAAC;QAClF,MAAM,QAAQ,GACZ,QAAQ,EAAE,QAAQ,KAAK,SAAS;YAC9B,CAAC,CAAC,QAAQ,CAAC,QAAQ;YACnB,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,kBAAkB,EAAE,CAAC;QAC/C,MAAM,WAAW,GACf,QAAQ,EAAE,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC;QAExF,OAAO;YACL,MAAM;YACN,QAAQ;YACR,WAAW;YACX,UAAU,EAAE;gBACV,GAAG,CAAC,QAAQ,EAAE,UAAU,IAAI,EAAE,CAAC;gBAC/B,GAAG,CAAC,MAAM,KAAK,SAAS,IAAI,EAAE,MAAM,EAAE,CAAC;gBACvC,GAAG,CAAC,QAAQ,KAAK,SAAS,IAAI,EAAE,QAAQ,EAAE,CAAC;gBAC3C,GAAG,CAAC,WAAW,KAAK,SAAS,IAAI,EAAE,WAAW,EAAE,CAAC;aAClD;SACF,CAAC;IACJ,CAAC;CACF,CAAA;AA7BY,kDAAmB;8BAAnB,mBAAmB;IAD/B,IAAA,mBAAU,GAAE;IAGR,WAAA,IAAA,eAAM,EAAC,oDAA2B,CAAC,CAAA;IAEnC,WAAA,IAAA,eAAM,EAAC,gDAAuB,CAAC,CAAA;6CADF,0BAAW;GAHhC,mBAAmB,CA6B/B"}
|
|
@@ -9,38 +9,16 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
9
9
|
exports.FlagEvaluatorService = void 0;
|
|
10
10
|
const common_1 = require("@nestjs/common");
|
|
11
11
|
const hash_1 = require("../utils/hash");
|
|
12
|
+
const targeting_attributes_1 = require("../utils/targeting-attributes");
|
|
12
13
|
let FlagEvaluatorService = class FlagEvaluatorService {
|
|
13
14
|
evaluate(flag, context) {
|
|
14
|
-
// 1. Archived → always false
|
|
15
15
|
if (flag.archivedAt) {
|
|
16
16
|
return { result: false, source: 'global' };
|
|
17
17
|
}
|
|
18
|
-
|
|
19
|
-
if (
|
|
20
|
-
|
|
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
|
-
}
|
|
18
|
+
const override = this.findMatchingOverride(flag.overrides, context);
|
|
19
|
+
if (override) {
|
|
20
|
+
return { result: override.enabled, source: 'override' };
|
|
42
21
|
}
|
|
43
|
-
// 5. Percentage rollout
|
|
44
22
|
if (flag.percentage > 0) {
|
|
45
23
|
if (flag.percentage === 100) {
|
|
46
24
|
return { result: true, source: 'percentage' };
|
|
@@ -52,12 +30,36 @@ let FlagEvaluatorService = class FlagEvaluatorService {
|
|
|
52
30
|
const bucket = (0, hash_1.murmurhash3)(flag.key + hashKey) % 100;
|
|
53
31
|
return { result: bucket < flag.percentage, source: 'percentage' };
|
|
54
32
|
}
|
|
55
|
-
// 6. Global default
|
|
56
33
|
return { result: flag.enabled, source: 'global' };
|
|
57
34
|
}
|
|
35
|
+
findMatchingOverride(overrides, context) {
|
|
36
|
+
const contextAttributes = context.attributes ?? {};
|
|
37
|
+
return (overrides
|
|
38
|
+
.filter((override) => (0, targeting_attributes_1.matchesTargetingAttributes)(override.attributes, contextAttributes))
|
|
39
|
+
.sort(compareOverrides)[0] ?? null);
|
|
40
|
+
}
|
|
58
41
|
};
|
|
59
42
|
exports.FlagEvaluatorService = FlagEvaluatorService;
|
|
60
43
|
exports.FlagEvaluatorService = FlagEvaluatorService = __decorate([
|
|
61
44
|
(0, common_1.Injectable)()
|
|
62
45
|
], FlagEvaluatorService);
|
|
46
|
+
function compareOverrides(a, b) {
|
|
47
|
+
const specificityDelta = Object.keys(b.attributes).length - Object.keys(a.attributes).length;
|
|
48
|
+
if (specificityDelta !== 0) {
|
|
49
|
+
return specificityDelta;
|
|
50
|
+
}
|
|
51
|
+
const priorityDelta = b.priority - a.priority;
|
|
52
|
+
if (priorityDelta !== 0) {
|
|
53
|
+
return priorityDelta;
|
|
54
|
+
}
|
|
55
|
+
const createdAtDelta = getOverrideCreatedAtTime(a) - getOverrideCreatedAtTime(b);
|
|
56
|
+
if (createdAtDelta !== 0) {
|
|
57
|
+
return createdAtDelta;
|
|
58
|
+
}
|
|
59
|
+
return a.id.localeCompare(b.id);
|
|
60
|
+
}
|
|
61
|
+
function getOverrideCreatedAtTime(override) {
|
|
62
|
+
const createdAt = override.createdAt;
|
|
63
|
+
return createdAt instanceof Date ? createdAt.getTime() : new Date(createdAt).getTime();
|
|
64
|
+
}
|
|
63
65
|
//# sourceMappingURL=flag-evaluator.service.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flag-evaluator.service.js","sourceRoot":"","sources":["../../src/services/flag-evaluator.service.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAA4C;AAG5C,wCAA4C;
|
|
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,7 @@
|
|
|
1
|
+
import { FeatureFlagModuleOptions } from '../interfaces/feature-flag-options.interface';
|
|
2
|
+
export declare class FlagEventPublisher {
|
|
3
|
+
private readonly options;
|
|
4
|
+
private readonly eventEmitter?;
|
|
5
|
+
constructor(options: FeatureFlagModuleOptions, eventEmitter?: any | undefined);
|
|
6
|
+
emit(event: string, payload: Record<string, unknown>): void;
|
|
7
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
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.FlagEventPublisher = void 0;
|
|
16
|
+
const common_1 = require("@nestjs/common");
|
|
17
|
+
const feature_flag_constants_1 = require("../feature-flag.constants");
|
|
18
|
+
let FlagEventPublisher = class FlagEventPublisher {
|
|
19
|
+
constructor(options, eventEmitter) {
|
|
20
|
+
this.options = options;
|
|
21
|
+
this.eventEmitter = eventEmitter;
|
|
22
|
+
}
|
|
23
|
+
emit(event, payload) {
|
|
24
|
+
if (this.options.emitEvents && this.eventEmitter) {
|
|
25
|
+
this.eventEmitter.emit(event, payload);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
exports.FlagEventPublisher = FlagEventPublisher;
|
|
30
|
+
exports.FlagEventPublisher = FlagEventPublisher = __decorate([
|
|
31
|
+
(0, common_1.Injectable)(),
|
|
32
|
+
__param(0, (0, common_1.Inject)(feature_flag_constants_1.FEATURE_FLAG_MODULE_OPTIONS)),
|
|
33
|
+
__param(1, (0, common_1.Optional)()),
|
|
34
|
+
__param(1, (0, common_1.Inject)('EVENT_EMITTER')),
|
|
35
|
+
__metadata("design:paramtypes", [Object, Object])
|
|
36
|
+
], FlagEventPublisher);
|
|
37
|
+
//# sourceMappingURL=flag-event-publisher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flag-event-publisher.js","sourceRoot":"","sources":["../../src/services/flag-event-publisher.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAA8D;AAC9D,sEAAwE;AAIjE,IAAM,kBAAkB,GAAxB,MAAM,kBAAkB;IAC7B,YACwD,OAAiC,EACjC,YAAkB;QADlB,YAAO,GAAP,OAAO,CAA0B;QACjC,iBAAY,GAAZ,YAAY,CAAM;IACvE,CAAC;IAEJ,IAAI,CAAC,KAAa,EAAE,OAAgC;QAClD,IAAI,IAAI,CAAC,OAAO,CAAC,UAAU,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YACjD,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QACzC,CAAC;IACH,CAAC;CACF,CAAA;AAXY,gDAAkB;6BAAlB,kBAAkB;IAD9B,IAAA,mBAAU,GAAE;IAGR,WAAA,IAAA,eAAM,EAAC,oDAA2B,CAAC,CAAA;IACnC,WAAA,IAAA,iBAAQ,GAAE,CAAA;IAAE,WAAA,IAAA,eAAM,EAAC,eAAe,CAAC,CAAA;;GAH3B,kBAAkB,CAW9B"}
|
|
@@ -10,6 +10,20 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
10
10
|
exports.TestFeatureFlagModule = void 0;
|
|
11
11
|
const common_1 = require("@nestjs/common");
|
|
12
12
|
const feature_flag_service_1 = require("../services/feature-flag.service");
|
|
13
|
+
function makeStubFlag(partial = {}) {
|
|
14
|
+
return {
|
|
15
|
+
id: partial.id ?? 'stub-id',
|
|
16
|
+
key: partial.key ?? 'STUB',
|
|
17
|
+
description: partial.description ?? null,
|
|
18
|
+
enabled: partial.enabled ?? false,
|
|
19
|
+
percentage: partial.percentage ?? 0,
|
|
20
|
+
metadata: partial.metadata ?? {},
|
|
21
|
+
archivedAt: partial.archivedAt ?? null,
|
|
22
|
+
createdAt: partial.createdAt ?? new Date('2026-01-01'),
|
|
23
|
+
updatedAt: partial.updatedAt ?? new Date('2026-01-01'),
|
|
24
|
+
overrides: partial.overrides ?? [],
|
|
25
|
+
};
|
|
26
|
+
}
|
|
13
27
|
let TestFeatureFlagModule = TestFeatureFlagModule_1 = class TestFeatureFlagModule {
|
|
14
28
|
static register(flags) {
|
|
15
29
|
return {
|
|
@@ -21,12 +35,19 @@ let TestFeatureFlagModule = TestFeatureFlagModule_1 = class TestFeatureFlagModul
|
|
|
21
35
|
useValue: {
|
|
22
36
|
isEnabled: async (key) => flags?.[key] ?? false,
|
|
23
37
|
evaluateAll: async () => flags ?? {},
|
|
24
|
-
create: async () => ({}),
|
|
25
|
-
update: async () => ({}),
|
|
26
|
-
archive: async () => ({}),
|
|
38
|
+
create: async (input) => makeStubFlag({ key: input.key, enabled: input.enabled ?? false }),
|
|
39
|
+
update: async (key, input) => makeStubFlag({ key, ...input }),
|
|
40
|
+
archive: async (key) => makeStubFlag({ key, archivedAt: new Date() }),
|
|
27
41
|
setOverride: async () => { },
|
|
28
|
-
|
|
29
|
-
|
|
42
|
+
removeOverride: async () => { },
|
|
43
|
+
findAll: async () => Object.entries(flags ?? {}).map(([key, enabled]) => makeStubFlag({ key, enabled })),
|
|
44
|
+
findByKey: async (key) => {
|
|
45
|
+
if (flags && key in flags) {
|
|
46
|
+
return makeStubFlag({ key, enabled: flags[key] });
|
|
47
|
+
}
|
|
48
|
+
throw new common_1.NotFoundException(`Feature flag "${key}" not found`);
|
|
49
|
+
},
|
|
50
|
+
invalidateCache: async () => { },
|
|
30
51
|
},
|
|
31
52
|
},
|
|
32
53
|
],
|