@featurevisor/sdk 1.29.2 → 1.30.1

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/src/instance.ts CHANGED
@@ -1,38 +1,21 @@
1
1
  import {
2
2
  Context,
3
- AttributeValue,
4
- BucketKey,
5
- BucketValue,
6
3
  DatafileContent,
7
4
  Feature,
8
5
  FeatureKey,
9
6
  InitialFeatures,
10
- OverrideFeature,
11
7
  StickyFeatures,
12
- Traffic,
13
8
  VariableType,
14
9
  VariableValue,
15
10
  VariationValue,
16
- Variation,
17
- RuleKey,
18
11
  VariableKey,
19
- VariableSchema,
20
- Force,
21
- Required,
22
12
  } from "@featurevisor/types";
23
13
 
24
14
  import { createLogger, Logger, LogLevel } from "./logger";
25
15
  import { DatafileReader } from "./datafileReader";
26
16
  import { Emitter } from "./emitter";
27
- import { getBucketedNumber } from "./bucket";
28
- import {
29
- findForceFromFeature,
30
- getMatchedTraffic,
31
- getMatchedTrafficAndAllocation,
32
- parseFromStringifiedSegments,
33
- } from "./feature";
34
- import { allConditionsAreMatched } from "./conditions";
35
- import { allGroupSegmentsAreMatched } from "./segments";
17
+ import { ConfigureBucketKey, ConfigureBucketValue } from "./bucket";
18
+ import { Evaluation, evaluate } from "./evaluate";
36
19
 
37
20
  export type ReadyCallback = () => void;
38
21
 
@@ -43,10 +26,6 @@ export type ActivationCallback = (
43
26
  captureContext: Context,
44
27
  ) => void;
45
28
 
46
- export type ConfigureBucketKey = (feature, context, bucketKey: BucketKey) => BucketKey;
47
-
48
- export type ConfigureBucketValue = (feature, context, bucketValue: BucketValue) => BucketValue;
49
-
50
29
  export interface Statuses {
51
30
  ready: boolean;
52
31
  refreshInProgress: boolean;
@@ -84,51 +63,6 @@ const emptyDatafile: DatafileContent = {
84
63
 
85
64
  export type DatafileFetchHandler = (datafileUrl: string) => Promise<DatafileContent>;
86
65
 
87
- export enum EvaluationReason {
88
- NOT_FOUND = "not_found",
89
- NO_VARIATIONS = "no_variations",
90
- NO_MATCH = "no_match",
91
- DISABLED = "disabled",
92
- REQUIRED = "required",
93
- OUT_OF_RANGE = "out_of_range",
94
- FORCED = "forced",
95
- INITIAL = "initial",
96
- STICKY = "sticky",
97
- RULE = "rule",
98
- ALLOCATED = "allocated",
99
- DEFAULTED = "defaulted",
100
- OVERRIDE = "override",
101
- ERROR = "error",
102
- }
103
-
104
- export interface Evaluation {
105
- // required
106
- featureKey: FeatureKey;
107
- reason: EvaluationReason;
108
-
109
- // common
110
- bucketKey?: BucketKey;
111
- bucketValue?: BucketValue;
112
- ruleKey?: RuleKey;
113
- error?: Error;
114
- enabled?: boolean;
115
- traffic?: Traffic;
116
- forceIndex?: number;
117
- force?: Force;
118
- required?: Required[];
119
- sticky?: OverrideFeature;
120
- initial?: OverrideFeature;
121
-
122
- // variation
123
- variation?: Variation;
124
- variationValue?: VariationValue;
125
-
126
- // variable
127
- variableKey?: VariableKey;
128
- variableValue?: VariableValue;
129
- variableSchema?: VariableSchema;
130
- }
131
-
132
66
  function fetchDatafileContent(
133
67
  datafileUrl,
134
68
  handleDatafileFetch?: DatafileFetchHandler,
@@ -321,83 +255,6 @@ export class FeaturevisorInstance {
321
255
  : featureKey; // full feature provided
322
256
  }
323
257
 
324
- /**
325
- * Bucketing
326
- */
327
- private getBucketKey(feature: Feature, context: Context): BucketKey {
328
- const featureKey = feature.key;
329
-
330
- let type;
331
- let attributeKeys;
332
-
333
- if (typeof feature.bucketBy === "string") {
334
- type = "plain";
335
- attributeKeys = [feature.bucketBy];
336
- } else if (Array.isArray(feature.bucketBy)) {
337
- type = "and";
338
- attributeKeys = feature.bucketBy;
339
- } else if (typeof feature.bucketBy === "object" && Array.isArray(feature.bucketBy.or)) {
340
- type = "or";
341
- attributeKeys = feature.bucketBy.or;
342
- } else {
343
- this.logger.error("invalid bucketBy", { featureKey, bucketBy: feature.bucketBy });
344
-
345
- throw new Error("invalid bucketBy");
346
- }
347
-
348
- const bucketKey: AttributeValue[] = [];
349
-
350
- attributeKeys.forEach((attributeKey) => {
351
- const attributeValue = context[attributeKey];
352
-
353
- if (typeof attributeValue === "undefined") {
354
- return;
355
- }
356
-
357
- if (type === "plain" || type === "and") {
358
- bucketKey.push(attributeValue);
359
- } else {
360
- // or
361
- if (bucketKey.length === 0) {
362
- bucketKey.push(attributeValue);
363
- }
364
- }
365
- });
366
-
367
- bucketKey.push(featureKey);
368
-
369
- const result = bucketKey.join(this.bucketKeySeparator);
370
-
371
- if (this.configureBucketKey) {
372
- return this.configureBucketKey(feature, context, result);
373
- }
374
-
375
- return result;
376
- }
377
-
378
- private getBucketValue(
379
- feature: Feature,
380
- context: Context,
381
- ): { bucketKey: BucketKey; bucketValue: BucketValue } {
382
- const bucketKey = this.getBucketKey(feature, context);
383
-
384
- const value = getBucketedNumber(bucketKey);
385
-
386
- if (this.configureBucketValue) {
387
- const configuredValue = this.configureBucketValue(feature, context, value);
388
-
389
- return {
390
- bucketKey,
391
- bucketValue: configuredValue,
392
- };
393
- }
394
-
395
- return {
396
- bucketKey,
397
- bucketValue: value,
398
- };
399
- }
400
-
401
258
  /**
402
259
  * Statuses
403
260
  */
@@ -474,241 +331,24 @@ export class FeaturevisorInstance {
474
331
  * Flag
475
332
  */
476
333
  evaluateFlag(featureKey: FeatureKey | Feature, context: Context = {}): Evaluation {
477
- let evaluation: Evaluation;
478
-
479
- try {
480
- const key = typeof featureKey === "string" ? featureKey : featureKey.key;
481
-
482
- // sticky
483
- if (
484
- this.stickyFeatures &&
485
- this.stickyFeatures[key] &&
486
- typeof this.stickyFeatures[key].enabled !== "undefined"
487
- ) {
488
- evaluation = {
489
- featureKey: key,
490
- reason: EvaluationReason.STICKY,
491
- sticky: this.stickyFeatures[key],
492
- enabled: this.stickyFeatures[key].enabled,
493
- };
494
-
495
- this.logger.debug("using sticky enabled", evaluation);
496
-
497
- return evaluation;
498
- }
499
-
500
- // initial
501
- if (
502
- this.statuses &&
503
- !this.statuses.ready &&
504
- this.initialFeatures &&
505
- this.initialFeatures[key] &&
506
- typeof this.initialFeatures[key].enabled !== "undefined"
507
- ) {
508
- evaluation = {
509
- featureKey: key,
510
- reason: EvaluationReason.INITIAL,
511
- initial: this.initialFeatures[key],
512
- enabled: this.initialFeatures[key].enabled,
513
- };
514
-
515
- this.logger.debug("using initial enabled", evaluation);
516
-
517
- return evaluation;
518
- }
519
-
520
- const feature = this.getFeature(featureKey);
521
-
522
- // not found
523
- if (!feature) {
524
- evaluation = {
525
- featureKey: key,
526
- reason: EvaluationReason.NOT_FOUND,
527
- };
528
-
529
- this.logger.warn("feature not found", evaluation);
530
-
531
- return evaluation;
532
- }
533
-
534
- // deprecated
535
- if (feature.deprecated) {
536
- this.logger.warn("feature is deprecated", { featureKey: feature.key });
537
- }
538
-
539
- const finalContext = this.interceptContext ? this.interceptContext(context) : context;
540
-
541
- // forced
542
- const { force, forceIndex } = findForceFromFeature(
543
- feature,
544
- context,
545
- this.datafileReader,
546
- this.logger,
547
- );
548
-
549
- if (force && typeof force.enabled !== "undefined") {
550
- evaluation = {
551
- featureKey: feature.key,
552
- reason: EvaluationReason.FORCED,
553
- forceIndex,
554
- force,
555
- enabled: force.enabled,
556
- };
557
-
558
- this.logger.debug("forced enabled found", evaluation);
559
-
560
- return evaluation;
561
- }
562
-
563
- // required
564
- if (feature.required && feature.required.length > 0) {
565
- const requiredFeaturesAreEnabled = feature.required.every((required) => {
566
- let requiredKey;
567
- let requiredVariation;
568
-
569
- if (typeof required === "string") {
570
- requiredKey = required;
571
- } else {
572
- requiredKey = required.key;
573
- requiredVariation = required.variation;
574
- }
575
-
576
- const requiredIsEnabled = this.isEnabled(requiredKey, finalContext);
577
-
578
- if (!requiredIsEnabled) {
579
- return false;
580
- }
581
-
582
- if (typeof requiredVariation !== "undefined") {
583
- const requiredVariationValue = this.getVariation(requiredKey, finalContext);
584
-
585
- return requiredVariationValue === requiredVariation;
586
- }
334
+ return evaluate({
335
+ type: "flag",
587
336
 
588
- return true;
589
- });
590
-
591
- if (!requiredFeaturesAreEnabled) {
592
- evaluation = {
593
- featureKey: feature.key,
594
- reason: EvaluationReason.REQUIRED,
595
- required: feature.required,
596
- enabled: requiredFeaturesAreEnabled,
597
- };
337
+ featureKey,
338
+ context,
598
339
 
599
- this.logger.debug("required features not enabled", evaluation);
340
+ logger: this.logger,
341
+ datafileReader: this.datafileReader,
342
+ statuses: this.statuses,
343
+ interceptContext: this.interceptContext,
600
344
 
601
- return evaluation;
602
- }
603
- }
345
+ stickyFeatures: this.stickyFeatures,
346
+ initialFeatures: this.initialFeatures,
604
347
 
605
- // bucketing
606
- const { bucketKey, bucketValue } = this.getBucketValue(feature, finalContext);
607
-
608
- const matchedTraffic = getMatchedTraffic(
609
- feature.traffic,
610
- finalContext,
611
- this.datafileReader,
612
- this.logger,
613
- );
614
-
615
- if (matchedTraffic) {
616
- // check if mutually exclusive
617
- if (feature.ranges && feature.ranges.length > 0) {
618
- const matchedRange = feature.ranges.find((range) => {
619
- return bucketValue >= range[0] && bucketValue < range[1];
620
- });
621
-
622
- // matched
623
- if (matchedRange) {
624
- evaluation = {
625
- featureKey: feature.key,
626
- reason: EvaluationReason.ALLOCATED,
627
- bucketKey,
628
- bucketValue,
629
- ruleKey: matchedTraffic.key,
630
- traffic: matchedTraffic,
631
- enabled:
632
- typeof matchedTraffic.enabled === "undefined" ? true : matchedTraffic.enabled,
633
- };
634
-
635
- this.logger.debug("matched", evaluation);
636
-
637
- return evaluation;
638
- }
639
-
640
- // no match
641
- evaluation = {
642
- featureKey: feature.key,
643
- reason: EvaluationReason.OUT_OF_RANGE,
644
- bucketKey,
645
- bucketValue,
646
- enabled: false,
647
- };
648
-
649
- this.logger.debug("not matched", evaluation);
650
-
651
- return evaluation;
652
- }
653
-
654
- // override from rule
655
- if (typeof matchedTraffic.enabled !== "undefined") {
656
- evaluation = {
657
- featureKey: feature.key,
658
- reason: EvaluationReason.OVERRIDE,
659
- bucketKey,
660
- bucketValue,
661
- ruleKey: matchedTraffic.key,
662
- traffic: matchedTraffic,
663
- enabled: matchedTraffic.enabled,
664
- };
665
-
666
- this.logger.debug("override from rule", evaluation);
667
-
668
- return evaluation;
669
- }
670
-
671
- // treated as enabled because of matched traffic
672
- if (bucketValue <= matchedTraffic.percentage) {
673
- evaluation = {
674
- featureKey: feature.key,
675
- reason: EvaluationReason.RULE,
676
- bucketKey,
677
- bucketValue,
678
- ruleKey: matchedTraffic.key,
679
- traffic: matchedTraffic,
680
- enabled: true,
681
- };
682
-
683
- this.logger.debug("matched traffic", evaluation);
684
-
685
- return evaluation;
686
- }
687
- }
688
-
689
- // nothing matched
690
- evaluation = {
691
- featureKey: feature.key,
692
- reason: EvaluationReason.NO_MATCH,
693
- bucketKey,
694
- bucketValue,
695
- enabled: false,
696
- };
697
-
698
- this.logger.debug("nothing matched", evaluation);
699
-
700
- return evaluation;
701
- } catch (e) {
702
- evaluation = {
703
- featureKey: typeof featureKey === "string" ? featureKey : featureKey.key,
704
- reason: EvaluationReason.ERROR,
705
- error: e,
706
- };
707
-
708
- this.logger.error("error", evaluation);
709
-
710
- return evaluation;
711
- }
348
+ bucketKeySeparator: this.bucketKeySeparator,
349
+ configureBucketKey: this.configureBucketKey,
350
+ configureBucketValue: this.configureBucketValue,
351
+ });
712
352
  }
713
353
 
714
354
  isEnabled(featureKey: FeatureKey | Feature, context: Context = {}): boolean {
@@ -727,193 +367,24 @@ export class FeaturevisorInstance {
727
367
  * Variation
728
368
  */
729
369
  evaluateVariation(featureKey: FeatureKey | Feature, context: Context = {}): Evaluation {
730
- let evaluation: Evaluation;
731
-
732
- try {
733
- const key = typeof featureKey === "string" ? featureKey : featureKey.key;
734
-
735
- const flag = this.evaluateFlag(featureKey, context);
736
-
737
- if (flag.enabled === false) {
738
- evaluation = {
739
- featureKey: key,
740
- reason: EvaluationReason.DISABLED,
741
- };
742
-
743
- this.logger.debug("feature is disabled", evaluation);
744
-
745
- return evaluation;
746
- }
747
-
748
- // sticky
749
- if (this.stickyFeatures && this.stickyFeatures[key]) {
750
- const variationValue = this.stickyFeatures[key].variation;
751
-
752
- if (typeof variationValue !== "undefined") {
753
- evaluation = {
754
- featureKey: key,
755
- reason: EvaluationReason.STICKY,
756
- variationValue,
757
- };
758
-
759
- this.logger.debug("using sticky variation", evaluation);
370
+ return evaluate({
371
+ type: "variation",
760
372
 
761
- return evaluation;
762
- }
763
- }
764
-
765
- // initial
766
- if (
767
- this.statuses &&
768
- !this.statuses.ready &&
769
- this.initialFeatures &&
770
- this.initialFeatures[key] &&
771
- typeof this.initialFeatures[key].variation !== "undefined"
772
- ) {
773
- const variationValue = this.initialFeatures[key].variation;
774
-
775
- evaluation = {
776
- featureKey: key,
777
- reason: EvaluationReason.INITIAL,
778
- variationValue,
779
- };
780
-
781
- this.logger.debug("using initial variation", evaluation);
782
-
783
- return evaluation;
784
- }
785
-
786
- const feature = this.getFeature(featureKey);
787
-
788
- // not found
789
- if (!feature) {
790
- evaluation = {
791
- featureKey: key,
792
- reason: EvaluationReason.NOT_FOUND,
793
- };
794
-
795
- this.logger.warn("feature not found", evaluation);
796
-
797
- return evaluation;
798
- }
799
-
800
- // no variations
801
- if (!feature.variations || feature.variations.length === 0) {
802
- evaluation = {
803
- featureKey: key,
804
- reason: EvaluationReason.NO_VARIATIONS,
805
- };
806
-
807
- this.logger.warn("no variations", evaluation);
808
-
809
- return evaluation;
810
- }
373
+ featureKey,
374
+ context,
811
375
 
812
- const finalContext = this.interceptContext ? this.interceptContext(context) : context;
813
-
814
- // forced
815
- const { force, forceIndex } = findForceFromFeature(
816
- feature,
817
- context,
818
- this.datafileReader,
819
- this.logger,
820
- );
821
-
822
- if (force && force.variation) {
823
- const variation = feature.variations.find((v) => v.value === force.variation);
376
+ logger: this.logger,
377
+ datafileReader: this.datafileReader,
378
+ statuses: this.statuses,
379
+ interceptContext: this.interceptContext,
824
380
 
825
- if (variation) {
826
- evaluation = {
827
- featureKey: feature.key,
828
- reason: EvaluationReason.FORCED,
829
- forceIndex,
830
- force,
831
- variation,
832
- };
381
+ stickyFeatures: this.stickyFeatures,
382
+ initialFeatures: this.initialFeatures,
833
383
 
834
- this.logger.debug("forced variation found", evaluation);
835
-
836
- return evaluation;
837
- }
838
- }
839
-
840
- // bucketing
841
- const { bucketKey, bucketValue } = this.getBucketValue(feature, finalContext);
842
-
843
- const { matchedTraffic, matchedAllocation } = getMatchedTrafficAndAllocation(
844
- feature.traffic,
845
- finalContext,
846
- bucketValue,
847
- this.datafileReader,
848
- this.logger,
849
- );
850
-
851
- if (matchedTraffic) {
852
- // override from rule
853
- if (matchedTraffic.variation) {
854
- const variation = feature.variations.find((v) => v.value === matchedTraffic.variation);
855
-
856
- if (variation) {
857
- evaluation = {
858
- featureKey: feature.key,
859
- reason: EvaluationReason.RULE,
860
- bucketKey,
861
- bucketValue,
862
- ruleKey: matchedTraffic.key,
863
- traffic: matchedTraffic,
864
- variation,
865
- };
866
-
867
- this.logger.debug("override from rule", evaluation);
868
-
869
- return evaluation;
870
- }
871
- }
872
-
873
- // regular allocation
874
- if (matchedAllocation && matchedAllocation.variation) {
875
- const variation = feature.variations.find((v) => v.value === matchedAllocation.variation);
876
-
877
- if (variation) {
878
- evaluation = {
879
- featureKey: feature.key,
880
- reason: EvaluationReason.ALLOCATED,
881
- bucketKey,
882
- bucketValue,
883
- ruleKey: matchedTraffic.key,
884
- traffic: matchedTraffic,
885
- variation,
886
- };
887
-
888
- this.logger.debug("allocated variation", evaluation);
889
-
890
- return evaluation;
891
- }
892
- }
893
- }
894
-
895
- // nothing matched
896
- evaluation = {
897
- featureKey: feature.key,
898
- reason: EvaluationReason.NO_MATCH,
899
- bucketKey,
900
- bucketValue,
901
- };
902
-
903
- this.logger.debug("no matched variation", evaluation);
904
-
905
- return evaluation;
906
- } catch (e) {
907
- evaluation = {
908
- featureKey: typeof featureKey === "string" ? featureKey : featureKey.key,
909
- reason: EvaluationReason.ERROR,
910
- error: e,
911
- };
912
-
913
- this.logger.error("error", evaluation);
914
-
915
- return evaluation;
916
- }
384
+ bucketKeySeparator: this.bucketKeySeparator,
385
+ configureBucketKey: this.configureBucketKey,
386
+ configureBucketValue: this.configureBucketValue,
387
+ });
917
388
  }
918
389
 
919
390
  getVariation(
@@ -992,268 +463,25 @@ export class FeaturevisorInstance {
992
463
  variableKey: VariableKey,
993
464
  context: Context = {},
994
465
  ): Evaluation {
995
- let evaluation: Evaluation;
996
-
997
- try {
998
- const key = typeof featureKey === "string" ? featureKey : featureKey.key;
999
-
1000
- const flag = this.evaluateFlag(featureKey, context);
1001
-
1002
- if (flag.enabled === false) {
1003
- evaluation = {
1004
- featureKey: key,
1005
- reason: EvaluationReason.DISABLED,
1006
- };
1007
-
1008
- this.logger.debug("feature is disabled", evaluation);
1009
-
1010
- return evaluation;
1011
- }
466
+ return evaluate({
467
+ type: "variable",
1012
468
 
1013
- // sticky
1014
- if (this.stickyFeatures && this.stickyFeatures[key]) {
1015
- const variables = this.stickyFeatures[key].variables;
469
+ featureKey,
470
+ variableKey,
471
+ context,
1016
472
 
1017
- if (variables) {
1018
- const result = variables[variableKey];
473
+ logger: this.logger,
474
+ datafileReader: this.datafileReader,
475
+ statuses: this.statuses,
476
+ interceptContext: this.interceptContext,
1019
477
 
1020
- if (typeof result !== "undefined") {
1021
- evaluation = {
1022
- featureKey: key,
1023
- reason: EvaluationReason.STICKY,
1024
- variableKey,
1025
- variableValue: result,
1026
- };
478
+ stickyFeatures: this.stickyFeatures,
479
+ initialFeatures: this.initialFeatures,
1027
480
 
1028
- this.logger.debug("using sticky variable", evaluation);
1029
-
1030
- return evaluation;
1031
- }
1032
- }
1033
- }
1034
-
1035
- // initial
1036
- if (
1037
- this.statuses &&
1038
- !this.statuses.ready &&
1039
- this.initialFeatures &&
1040
- this.initialFeatures[key]
1041
- ) {
1042
- const variables = this.initialFeatures[key].variables;
1043
-
1044
- if (variables) {
1045
- if (typeof variables[variableKey] !== "undefined") {
1046
- evaluation = {
1047
- featureKey: key,
1048
- reason: EvaluationReason.INITIAL,
1049
- variableKey,
1050
- variableValue: variables[variableKey],
1051
- };
1052
-
1053
- this.logger.debug("using initial variable", evaluation);
1054
-
1055
- return evaluation;
1056
- }
1057
- }
1058
- }
1059
-
1060
- const feature = this.getFeature(featureKey);
1061
-
1062
- // not found
1063
- if (!feature) {
1064
- evaluation = {
1065
- featureKey: key,
1066
- reason: EvaluationReason.NOT_FOUND,
1067
- variableKey,
1068
- };
1069
-
1070
- this.logger.warn("feature not found in datafile", evaluation);
1071
-
1072
- return evaluation;
1073
- }
1074
-
1075
- const variableSchema = Array.isArray(feature.variablesSchema)
1076
- ? feature.variablesSchema.find((v) => v.key === variableKey)
1077
- : undefined;
1078
-
1079
- // variable schema not found
1080
- if (!variableSchema) {
1081
- evaluation = {
1082
- featureKey: key,
1083
- reason: EvaluationReason.NOT_FOUND,
1084
- variableKey,
1085
- };
1086
-
1087
- this.logger.warn("variable schema not found", evaluation);
1088
-
1089
- return evaluation;
1090
- }
1091
-
1092
- const finalContext = this.interceptContext ? this.interceptContext(context) : context;
1093
-
1094
- // forced
1095
- const { force, forceIndex } = findForceFromFeature(
1096
- feature,
1097
- context,
1098
- this.datafileReader,
1099
- this.logger,
1100
- );
1101
-
1102
- if (force && force.variables && typeof force.variables[variableKey] !== "undefined") {
1103
- evaluation = {
1104
- featureKey: feature.key,
1105
- reason: EvaluationReason.FORCED,
1106
- forceIndex,
1107
- force,
1108
- variableKey,
1109
- variableSchema,
1110
- variableValue: force.variables[variableKey],
1111
- };
1112
-
1113
- this.logger.debug("forced variable", evaluation);
1114
-
1115
- return evaluation;
1116
- }
1117
-
1118
- // bucketing
1119
- const { bucketKey, bucketValue } = this.getBucketValue(feature, finalContext);
1120
-
1121
- const { matchedTraffic, matchedAllocation } = getMatchedTrafficAndAllocation(
1122
- feature.traffic,
1123
- finalContext,
1124
- bucketValue,
1125
- this.datafileReader,
1126
- this.logger,
1127
- );
1128
-
1129
- if (matchedTraffic) {
1130
- // override from rule
1131
- if (
1132
- matchedTraffic.variables &&
1133
- typeof matchedTraffic.variables[variableKey] !== "undefined"
1134
- ) {
1135
- evaluation = {
1136
- featureKey: feature.key,
1137
- reason: EvaluationReason.RULE,
1138
- bucketKey,
1139
- bucketValue,
1140
- ruleKey: matchedTraffic.key,
1141
- traffic: matchedTraffic,
1142
- variableKey,
1143
- variableSchema,
1144
- variableValue: matchedTraffic.variables[variableKey],
1145
- };
1146
-
1147
- this.logger.debug("override from rule", evaluation);
1148
-
1149
- return evaluation;
1150
- }
1151
-
1152
- // regular allocation
1153
- let variationValue;
1154
-
1155
- if (force && force.variation) {
1156
- variationValue = force.variation;
1157
- } else if (matchedAllocation && matchedAllocation.variation) {
1158
- variationValue = matchedAllocation.variation;
1159
- }
1160
-
1161
- if (variationValue && Array.isArray(feature.variations)) {
1162
- const variation = feature.variations.find((v) => v.value === variationValue);
1163
-
1164
- if (variation && variation.variables) {
1165
- const variableFromVariation = variation.variables.find((v) => v.key === variableKey);
1166
-
1167
- if (variableFromVariation) {
1168
- if (variableFromVariation.overrides) {
1169
- const override = variableFromVariation.overrides.find((o) => {
1170
- if (o.conditions) {
1171
- return allConditionsAreMatched(
1172
- typeof o.conditions === "string" ? JSON.parse(o.conditions) : o.conditions,
1173
- finalContext,
1174
- this.logger,
1175
- );
1176
- }
1177
-
1178
- if (o.segments) {
1179
- return allGroupSegmentsAreMatched(
1180
- parseFromStringifiedSegments(o.segments),
1181
- finalContext,
1182
- this.datafileReader,
1183
- this.logger,
1184
- );
1185
- }
1186
-
1187
- return false;
1188
- });
1189
-
1190
- if (override) {
1191
- evaluation = {
1192
- featureKey: feature.key,
1193
- reason: EvaluationReason.OVERRIDE,
1194
- bucketKey,
1195
- bucketValue,
1196
- ruleKey: matchedTraffic.key,
1197
- traffic: matchedTraffic,
1198
- variableKey,
1199
- variableSchema,
1200
- variableValue: override.value,
1201
- };
1202
-
1203
- this.logger.debug("variable override", evaluation);
1204
-
1205
- return evaluation;
1206
- }
1207
- }
1208
-
1209
- if (typeof variableFromVariation.value !== "undefined") {
1210
- evaluation = {
1211
- featureKey: feature.key,
1212
- reason: EvaluationReason.ALLOCATED,
1213
- bucketKey,
1214
- bucketValue,
1215
- ruleKey: matchedTraffic.key,
1216
- traffic: matchedTraffic,
1217
- variableKey,
1218
- variableSchema,
1219
- variableValue: variableFromVariation.value,
1220
- };
1221
-
1222
- this.logger.debug("allocated variable", evaluation);
1223
-
1224
- return evaluation;
1225
- }
1226
- }
1227
- }
1228
- }
1229
- }
1230
-
1231
- // fall back to default
1232
- evaluation = {
1233
- featureKey: feature.key,
1234
- reason: EvaluationReason.DEFAULTED,
1235
- bucketKey,
1236
- bucketValue,
1237
- variableKey,
1238
- variableSchema,
1239
- variableValue: variableSchema.defaultValue,
1240
- };
1241
-
1242
- this.logger.debug("using default value", evaluation);
1243
-
1244
- return evaluation;
1245
- } catch (e) {
1246
- evaluation = {
1247
- featureKey: typeof featureKey === "string" ? featureKey : featureKey.key,
1248
- reason: EvaluationReason.ERROR,
1249
- variableKey,
1250
- error: e,
1251
- };
1252
-
1253
- this.logger.error("error", evaluation);
1254
-
1255
- return evaluation;
1256
- }
481
+ bucketKeySeparator: this.bucketKeySeparator,
482
+ configureBucketKey: this.configureBucketKey,
483
+ configureBucketValue: this.configureBucketValue,
484
+ });
1257
485
  }
1258
486
 
1259
487
  getVariable(