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.
@@ -1 +1 @@
1
- {".":"8.0.1"}
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.1",
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 { FeatureModel, FeatureStateModel } from '../../../flagsmith-engine/features/models.js';
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
+ });