flagsmith-nodejs 8.0.1 → 8.0.2
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/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +12 -0
- package/build/cjs/flagsmith-engine/evaluation/evaluationContext/mappers.js +12 -8
- package/build/esm/flagsmith-engine/evaluation/evaluationContext/mappers.js +12 -8
- package/flagsmith-engine/evaluation/evaluationContext/mappers.ts +14 -10
- package/package.json +1 -1
- package/tests/engine/unit/engine.test.ts +92 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{".":"8.0.
|
|
1
|
+
{".":"8.0.2"}
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [8.0.2](https://github.com/Flagsmith/flagsmith-nodejs-client/compare/v8.0.1...v8.0.2) (2026-03-19)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* Missing variants in feature engine context ([#257](https://github.com/Flagsmith/flagsmith-nodejs-client/issues/257)) ([7e191b8](https://github.com/Flagsmith/flagsmith-nodejs-client/commit/7e191b8fff2634e58ff4ade92d4729b0b51ed6dc))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Other
|
|
12
|
+
|
|
13
|
+
* **deps:** bump minimatch and npm ([#255](https://github.com/Flagsmith/flagsmith-nodejs-client/issues/255)) ([c18c3b3](https://github.com/Flagsmith/flagsmith-nodejs-client/commit/c18c3b357243df72708fdc3e0184589b25efa8a1))
|
|
14
|
+
|
|
3
15
|
## [8.0.1](https://github.com/Flagsmith/flagsmith-nodejs-client/compare/v8.0.0...v8.0.1) (2026-03-16)
|
|
4
16
|
|
|
5
17
|
|
|
@@ -27,19 +27,12 @@ function mapEnvironmentModelToEvaluationContext(environment) {
|
|
|
27
27
|
};
|
|
28
28
|
const features = {};
|
|
29
29
|
for (const fs of environment.featureStates) {
|
|
30
|
-
const variants = fs.multivariateFeatureStateValues?.length > 0
|
|
31
|
-
? fs.multivariateFeatureStateValues.map(mv => ({
|
|
32
|
-
value: mv.multivariateFeatureOption.value,
|
|
33
|
-
weight: mv.percentageAllocation,
|
|
34
|
-
priority: mv.id ?? (0, util_js_1.uuidToBigInt)(mv.mvFsValueUuid)
|
|
35
|
-
}))
|
|
36
|
-
: undefined;
|
|
37
30
|
features[fs.feature.name] = {
|
|
38
31
|
key: fs.djangoID?.toString() || fs.featurestateUUID,
|
|
39
32
|
name: fs.feature.name,
|
|
40
33
|
enabled: fs.enabled,
|
|
41
34
|
value: fs.getValue(),
|
|
42
|
-
variants,
|
|
35
|
+
variants: mapFeatureStateVariants(fs),
|
|
43
36
|
priority: fs.featureSegment?.priority,
|
|
44
37
|
metadata: {
|
|
45
38
|
id: fs.feature.id
|
|
@@ -58,6 +51,7 @@ function mapEnvironmentModelToEvaluationContext(environment) {
|
|
|
58
51
|
name: fs.feature.name,
|
|
59
52
|
enabled: fs.enabled,
|
|
60
53
|
value: fs.getValue(),
|
|
54
|
+
variants: mapFeatureStateVariants(fs),
|
|
61
55
|
priority: fs.featureSegment?.priority,
|
|
62
56
|
metadata: {
|
|
63
57
|
id: fs.feature.id
|
|
@@ -95,6 +89,15 @@ function mapIdentityModelToIdentityContext(identity, overrideTraits) {
|
|
|
95
89
|
};
|
|
96
90
|
return identityContext;
|
|
97
91
|
}
|
|
92
|
+
function mapFeatureStateVariants(fs) {
|
|
93
|
+
return fs.multivariateFeatureStateValues?.length > 0
|
|
94
|
+
? fs.multivariateFeatureStateValues.map(mv => ({
|
|
95
|
+
value: mv.multivariateFeatureOption.value,
|
|
96
|
+
weight: mv.percentageAllocation,
|
|
97
|
+
priority: mv.id ?? (0, util_js_1.uuidToBigInt)(mv.mvFsValueUuid)
|
|
98
|
+
}))
|
|
99
|
+
: undefined;
|
|
100
|
+
}
|
|
98
101
|
function mapSegmentRuleModelToRule(rule) {
|
|
99
102
|
return {
|
|
100
103
|
type: rule.type,
|
|
@@ -118,6 +121,7 @@ function mapIdentityOverridesToSegments(identityOverrides) {
|
|
|
118
121
|
name: fs.feature.name,
|
|
119
122
|
enabled: fs.enabled,
|
|
120
123
|
value: fs.getValue(),
|
|
124
|
+
variants: mapFeatureStateVariants(fs),
|
|
121
125
|
priority: -Infinity,
|
|
122
126
|
metadata: {
|
|
123
127
|
id: fs.feature.id
|
|
@@ -23,19 +23,12 @@ function mapEnvironmentModelToEvaluationContext(environment) {
|
|
|
23
23
|
};
|
|
24
24
|
const features = {};
|
|
25
25
|
for (const fs of environment.featureStates) {
|
|
26
|
-
const variants = fs.multivariateFeatureStateValues?.length > 0
|
|
27
|
-
? fs.multivariateFeatureStateValues.map(mv => ({
|
|
28
|
-
value: mv.multivariateFeatureOption.value,
|
|
29
|
-
weight: mv.percentageAllocation,
|
|
30
|
-
priority: mv.id ?? uuidToBigInt(mv.mvFsValueUuid)
|
|
31
|
-
}))
|
|
32
|
-
: undefined;
|
|
33
26
|
features[fs.feature.name] = {
|
|
34
27
|
key: fs.djangoID?.toString() || fs.featurestateUUID,
|
|
35
28
|
name: fs.feature.name,
|
|
36
29
|
enabled: fs.enabled,
|
|
37
30
|
value: fs.getValue(),
|
|
38
|
-
variants,
|
|
31
|
+
variants: mapFeatureStateVariants(fs),
|
|
39
32
|
priority: fs.featureSegment?.priority,
|
|
40
33
|
metadata: {
|
|
41
34
|
id: fs.feature.id
|
|
@@ -54,6 +47,7 @@ function mapEnvironmentModelToEvaluationContext(environment) {
|
|
|
54
47
|
name: fs.feature.name,
|
|
55
48
|
enabled: fs.enabled,
|
|
56
49
|
value: fs.getValue(),
|
|
50
|
+
variants: mapFeatureStateVariants(fs),
|
|
57
51
|
priority: fs.featureSegment?.priority,
|
|
58
52
|
metadata: {
|
|
59
53
|
id: fs.feature.id
|
|
@@ -91,6 +85,15 @@ function mapIdentityModelToIdentityContext(identity, overrideTraits) {
|
|
|
91
85
|
};
|
|
92
86
|
return identityContext;
|
|
93
87
|
}
|
|
88
|
+
function mapFeatureStateVariants(fs) {
|
|
89
|
+
return fs.multivariateFeatureStateValues?.length > 0
|
|
90
|
+
? fs.multivariateFeatureStateValues.map(mv => ({
|
|
91
|
+
value: mv.multivariateFeatureOption.value,
|
|
92
|
+
weight: mv.percentageAllocation,
|
|
93
|
+
priority: mv.id ?? uuidToBigInt(mv.mvFsValueUuid)
|
|
94
|
+
}))
|
|
95
|
+
: undefined;
|
|
96
|
+
}
|
|
94
97
|
function mapSegmentRuleModelToRule(rule) {
|
|
95
98
|
return {
|
|
96
99
|
type: rule.type,
|
|
@@ -114,6 +117,7 @@ function mapIdentityOverridesToSegments(identityOverrides) {
|
|
|
114
117
|
name: fs.feature.name,
|
|
115
118
|
enabled: fs.enabled,
|
|
116
119
|
value: fs.getValue(),
|
|
120
|
+
variants: mapFeatureStateVariants(fs),
|
|
117
121
|
priority: -Infinity,
|
|
118
122
|
metadata: {
|
|
119
123
|
id: fs.feature.id
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
import { EnvironmentModel } from '../../environments/models.js';
|
|
13
13
|
import { IdentityModel } from '../../identities/models.js';
|
|
14
14
|
import { TraitModel } from '../../identities/traits/models.js';
|
|
15
|
+
import { FeatureStateModel } from '../../features/models.js';
|
|
15
16
|
import { IDENTITY_OVERRIDE_SEGMENT_NAME } from '../../segments/constants.js';
|
|
16
17
|
import { createHash } from 'node:crypto';
|
|
17
18
|
import { uuidToBigInt } from '../../features/util.js';
|
|
@@ -48,21 +49,12 @@ function mapEnvironmentModelToEvaluationContext(
|
|
|
48
49
|
|
|
49
50
|
const features: FeaturesWithMetadata<SDKFeatureMetadata> = {};
|
|
50
51
|
for (const fs of environment.featureStates) {
|
|
51
|
-
const variants =
|
|
52
|
-
fs.multivariateFeatureStateValues?.length > 0
|
|
53
|
-
? fs.multivariateFeatureStateValues.map(mv => ({
|
|
54
|
-
value: mv.multivariateFeatureOption.value,
|
|
55
|
-
weight: mv.percentageAllocation,
|
|
56
|
-
priority: mv.id ?? uuidToBigInt(mv.mvFsValueUuid)
|
|
57
|
-
}))
|
|
58
|
-
: undefined;
|
|
59
|
-
|
|
60
52
|
features[fs.feature.name] = {
|
|
61
53
|
key: fs.djangoID?.toString() || fs.featurestateUUID,
|
|
62
54
|
name: fs.feature.name,
|
|
63
55
|
enabled: fs.enabled,
|
|
64
56
|
value: fs.getValue(),
|
|
65
|
-
variants,
|
|
57
|
+
variants: mapFeatureStateVariants(fs),
|
|
66
58
|
priority: fs.featureSegment?.priority,
|
|
67
59
|
metadata: {
|
|
68
60
|
id: fs.feature.id
|
|
@@ -83,6 +75,7 @@ function mapEnvironmentModelToEvaluationContext(
|
|
|
83
75
|
name: fs.feature.name,
|
|
84
76
|
enabled: fs.enabled,
|
|
85
77
|
value: fs.getValue(),
|
|
78
|
+
variants: mapFeatureStateVariants(fs),
|
|
86
79
|
priority: fs.featureSegment?.priority,
|
|
87
80
|
metadata: {
|
|
88
81
|
id: fs.feature.id
|
|
@@ -130,6 +123,16 @@ function mapIdentityModelToIdentityContext(
|
|
|
130
123
|
return identityContext;
|
|
131
124
|
}
|
|
132
125
|
|
|
126
|
+
function mapFeatureStateVariants(fs: FeatureStateModel) {
|
|
127
|
+
return fs.multivariateFeatureStateValues?.length > 0
|
|
128
|
+
? fs.multivariateFeatureStateValues.map(mv => ({
|
|
129
|
+
value: mv.multivariateFeatureOption.value,
|
|
130
|
+
weight: mv.percentageAllocation,
|
|
131
|
+
priority: mv.id ?? uuidToBigInt(mv.mvFsValueUuid)
|
|
132
|
+
}))
|
|
133
|
+
: undefined;
|
|
134
|
+
}
|
|
135
|
+
|
|
133
136
|
function mapSegmentRuleModelToRule(rule: any): any {
|
|
134
137
|
return {
|
|
135
138
|
type: rule.type,
|
|
@@ -160,6 +163,7 @@ function mapIdentityOverridesToSegments(
|
|
|
160
163
|
name: fs.feature.name,
|
|
161
164
|
enabled: fs.enabled,
|
|
162
165
|
value: fs.getValue(),
|
|
166
|
+
variants: mapFeatureStateVariants(fs),
|
|
163
167
|
priority: -Infinity,
|
|
164
168
|
metadata: {
|
|
165
169
|
id: fs.feature.id
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flagsmith-nodejs",
|
|
3
|
-
"version": "8.0.
|
|
3
|
+
"version": "8.0.2",
|
|
4
4
|
"description": "Flagsmith lets you manage features flags and remote config across web, mobile and server side applications. Deliver true Continuous Integration. Get builds out faster. Control who has access to new features.",
|
|
5
5
|
"main": "./build/cjs/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -7,7 +7,13 @@ import {
|
|
|
7
7
|
shouldApplyOverride
|
|
8
8
|
} from '../../../flagsmith-engine/index.js';
|
|
9
9
|
import { CONSTANTS } from '../../../flagsmith-engine/features/constants.js';
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
FeatureModel,
|
|
12
|
+
FeatureSegment,
|
|
13
|
+
FeatureStateModel,
|
|
14
|
+
MultivariateFeatureOptionModel,
|
|
15
|
+
MultivariateFeatureStateValueModel
|
|
16
|
+
} from '../../../flagsmith-engine/features/models.js';
|
|
11
17
|
import { TraitModel } from '../../../flagsmith-engine/identities/traits/models.js';
|
|
12
18
|
import {
|
|
13
19
|
environment,
|
|
@@ -15,12 +21,15 @@ import {
|
|
|
15
21
|
feature1,
|
|
16
22
|
identity,
|
|
17
23
|
identityInSegment,
|
|
24
|
+
segment,
|
|
18
25
|
segmentConditionProperty,
|
|
19
|
-
segmentConditionStringValue
|
|
26
|
+
segmentConditionStringValue,
|
|
27
|
+
traitMatchingSegment
|
|
20
28
|
} from './utils.js';
|
|
21
29
|
import { getEvaluationContext } from '../../../flagsmith-engine/evaluation/evaluationContext/mappers.js';
|
|
22
30
|
import { TARGETING_REASONS } from '../../../flagsmith-engine/features/types.js';
|
|
23
31
|
import { EvaluationContext } from '../../../flagsmith-engine/evaluation/evaluationContext/evaluationContext.types.js';
|
|
32
|
+
import { EvaluationContextWithMetadata } from '../../../flagsmith-engine/evaluation/models.js';
|
|
24
33
|
import { IDENTITY_OVERRIDE_SEGMENT_NAME } from '../../../flagsmith-engine/segments/constants.js';
|
|
25
34
|
|
|
26
35
|
test('test_get_evaluation_result_without_any_override', () => {
|
|
@@ -356,3 +365,84 @@ test('evaluateFeatures with multivariate evaluation', () => {
|
|
|
356
365
|
const flags = evaluateFeatures(context as any, {});
|
|
357
366
|
expect(flags['Multivariate Feature'].value).toBe('variant_b');
|
|
358
367
|
});
|
|
368
|
+
|
|
369
|
+
test('local evaluation returns correct multivariate value for segment override with 100% weight', () => {
|
|
370
|
+
// Given
|
|
371
|
+
// a feature with two multivariate variants where the segment override
|
|
372
|
+
// assigns 100% weight to the second variant
|
|
373
|
+
const env = environment();
|
|
374
|
+
const seg = segment();
|
|
375
|
+
|
|
376
|
+
const mvFeature = new FeatureModel(10, 'mv_feature', CONSTANTS.STANDARD);
|
|
377
|
+
|
|
378
|
+
const controlOption = new MultivariateFeatureOptionModel('control', 1);
|
|
379
|
+
const variantOption = new MultivariateFeatureOptionModel('variant_b', 2);
|
|
380
|
+
|
|
381
|
+
const envFs = new FeatureStateModel(mvFeature, true, 10, 'default');
|
|
382
|
+
envFs.multivariateFeatureStateValues = [
|
|
383
|
+
new MultivariateFeatureStateValueModel(controlOption, 0, 1),
|
|
384
|
+
new MultivariateFeatureStateValueModel(variantOption, 100, 2)
|
|
385
|
+
];
|
|
386
|
+
env.featureStates.push(envFs);
|
|
387
|
+
|
|
388
|
+
const overrideFs = new FeatureStateModel(mvFeature, true, 11, 'default');
|
|
389
|
+
overrideFs.featureSegment = new FeatureSegment(0);
|
|
390
|
+
overrideFs.multivariateFeatureStateValues = [
|
|
391
|
+
new MultivariateFeatureStateValueModel(controlOption, 0, 1),
|
|
392
|
+
new MultivariateFeatureStateValueModel(variantOption, 100, 2)
|
|
393
|
+
];
|
|
394
|
+
seg.featureStates.push(overrideFs);
|
|
395
|
+
env.project.segments = [seg];
|
|
396
|
+
|
|
397
|
+
// When
|
|
398
|
+
// evaluating flags for an identity that matches the segment
|
|
399
|
+
const context = getEvaluationContext(env, identityInSegment(), [traitMatchingSegment()]);
|
|
400
|
+
const result = getEvaluationResult(context as EvaluationContextWithMetadata);
|
|
401
|
+
const flag = result.flags['mv_feature'];
|
|
402
|
+
|
|
403
|
+
// Then
|
|
404
|
+
// the flag value should be the 100%-weighted variant, not the base default
|
|
405
|
+
expect(flag).toBeDefined();
|
|
406
|
+
expect(flag.value).toBe('variant_b');
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
test('getEvaluationContext maps multivariate variants onto segment override feature states', () => {
|
|
410
|
+
// Given
|
|
411
|
+
// a segment override feature state with multivariate values
|
|
412
|
+
const env = environment();
|
|
413
|
+
const seg = segment();
|
|
414
|
+
|
|
415
|
+
const mvFeature = new FeatureModel(10, 'mv_feature', CONSTANTS.STANDARD);
|
|
416
|
+
env.featureStates.push(new FeatureStateModel(mvFeature, true, 10, 'default'));
|
|
417
|
+
|
|
418
|
+
const overrideFs = new FeatureStateModel(mvFeature, true, 11, 'default');
|
|
419
|
+
overrideFs.featureSegment = new FeatureSegment(0);
|
|
420
|
+
overrideFs.multivariateFeatureStateValues = [
|
|
421
|
+
new MultivariateFeatureStateValueModel(
|
|
422
|
+
new MultivariateFeatureOptionModel('variant_value', 1),
|
|
423
|
+
100,
|
|
424
|
+
1
|
|
425
|
+
)
|
|
426
|
+
];
|
|
427
|
+
seg.featureStates.push(overrideFs);
|
|
428
|
+
env.project.segments = [seg];
|
|
429
|
+
|
|
430
|
+
// When
|
|
431
|
+
// mapping the environment model to an evaluation context
|
|
432
|
+
const context = getEvaluationContext(env, identityInSegment(), [traitMatchingSegment()]);
|
|
433
|
+
|
|
434
|
+
// Then
|
|
435
|
+
// the segment override should include the variants array
|
|
436
|
+
const segmentOverrides = Object.values(context.segments || {});
|
|
437
|
+
const segWithOverrides = segmentOverrides.find(
|
|
438
|
+
s => s.overrides && s.overrides.some((o: any) => o.name === 'mv_feature')
|
|
439
|
+
);
|
|
440
|
+
expect(segWithOverrides).toBeDefined();
|
|
441
|
+
|
|
442
|
+
const mvOverride = segWithOverrides!.overrides!.find((o: any) => o.name === 'mv_feature');
|
|
443
|
+
expect(mvOverride).toBeDefined();
|
|
444
|
+
expect((mvOverride as any).variants).toBeDefined();
|
|
445
|
+
expect((mvOverride as any).variants).toHaveLength(1);
|
|
446
|
+
expect((mvOverride as any).variants[0].value).toBe('variant_value');
|
|
447
|
+
expect((mvOverride as any).variants[0].weight).toBe(100);
|
|
448
|
+
});
|