@featurevisor/sdk 0.36.0 → 0.38.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/src/instance.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import {
2
- Attributes,
2
+ Context,
3
3
  AttributeValue,
4
4
  BucketKey,
5
5
  BucketValue,
@@ -7,10 +7,11 @@ import {
7
7
  Feature,
8
8
  FeatureKey,
9
9
  InitialFeatures,
10
+ OverrideFeature,
10
11
  StickyFeatures,
12
+ Traffic,
11
13
  VariableType,
12
14
  VariableValue,
13
- VariationType,
14
15
  VariationValue,
15
16
  Variation,
16
17
  RuleKey,
@@ -22,7 +23,7 @@ import { createLogger, Logger } from "./logger";
22
23
  import { DatafileReader } from "./datafileReader";
23
24
  import { Emitter } from "./emitter";
24
25
  import { getBucketedNumber } from "./bucket";
25
- import { findForceFromFeature, getMatchedTrafficAndAllocation } from "./feature";
26
+ import { findForceFromFeature, getMatchedTraffic, getMatchedTrafficAndAllocation } from "./feature";
26
27
  import { allConditionsAreMatched } from "./conditions";
27
28
  import { allGroupSegmentsAreMatched } from "./segments";
28
29
 
@@ -31,13 +32,13 @@ export type ReadyCallback = () => void;
31
32
  export type ActivationCallback = (
32
33
  featureName: string,
33
34
  variation: VariationValue,
34
- attributes: Attributes,
35
- captureAttributes: Attributes,
35
+ context: Context,
36
+ captureContext: Context,
36
37
  ) => void;
37
38
 
38
- export type ConfigureBucketKey = (feature, attributes, bucketKey: BucketKey) => BucketKey;
39
+ export type ConfigureBucketKey = (feature, context, bucketKey: BucketKey) => BucketKey;
39
40
 
40
- export type ConfigureBucketValue = (feature, attributes, bucketValue: BucketValue) => BucketValue;
41
+ export type ConfigureBucketValue = (feature, context, bucketValue: BucketValue) => BucketValue;
41
42
 
42
43
  export interface Statuses {
43
44
  ready: boolean;
@@ -46,6 +47,8 @@ export interface Statuses {
46
47
 
47
48
  const DEFAULT_BUCKET_KEY_SEPARATOR = ".";
48
49
 
50
+ export type InterceptContext = (context: Context) => Context;
51
+
49
52
  export interface InstanceOptions {
50
53
  bucketKeySeparator?: string;
51
54
  configureBucketKey?: ConfigureBucketKey;
@@ -54,7 +57,7 @@ export interface InstanceOptions {
54
57
  datafileUrl?: string;
55
58
  handleDatafileFetch?: (datafileUrl: string) => Promise<DatafileContent>;
56
59
  initialFeatures?: InitialFeatures;
57
- interceptAttributes?: (attributes: Attributes) => Attributes;
60
+ interceptContext?: InterceptContext;
58
61
  logger?: Logger;
59
62
  onActivation?: ActivationCallback;
60
63
  onReady?: ReadyCallback;
@@ -76,6 +79,9 @@ export type DatafileFetchHandler = (datafileUrl: string) => Promise<DatafileCont
76
79
 
77
80
  export enum EvaluationReason {
78
81
  NOT_FOUND = "not_found",
82
+ NO_VARIATIONS = "no_variations",
83
+ DISABLED = "disabled",
84
+ OUT_OF_RANGE = "out_of_range",
79
85
  FORCED = "forced",
80
86
  INITIAL = "initial",
81
87
  STICKY = "sticky",
@@ -95,6 +101,10 @@ export interface Evaluation {
95
101
  bucketValue?: BucketValue;
96
102
  ruleKey?: RuleKey;
97
103
  error?: Error;
104
+ enabled?: boolean;
105
+ traffic?: Traffic;
106
+ sticky?: OverrideFeature;
107
+ initial?: OverrideFeature;
98
108
 
99
109
  // variation
100
110
  variation?: Variation;
@@ -117,7 +127,7 @@ function fetchDatafileContent(
117
127
  return fetch(datafileUrl).then((res) => res.json());
118
128
  }
119
129
 
120
- type FieldType = VariationType | VariableType;
130
+ type FieldType = string | VariableType;
121
131
  type ValueType = VariableValue;
122
132
 
123
133
  export function getValueByType(value: ValueType, fieldType: FieldType): ValueType {
@@ -156,7 +166,7 @@ export class FeaturevisorInstance {
156
166
  private datafileUrl?: string;
157
167
  private handleDatafileFetch?: DatafileFetchHandler;
158
168
  private initialFeatures?: InitialFeatures;
159
- private interceptAttributes?: (attributes: Attributes) => Attributes;
169
+ private interceptContext?: InterceptContext;
160
170
  private logger: Logger;
161
171
  private refreshInterval?: number; // seconds
162
172
  private stickyFeatures?: StickyFeatures;
@@ -182,7 +192,7 @@ export class FeaturevisorInstance {
182
192
  this.datafileUrl = options.datafileUrl;
183
193
  this.handleDatafileFetch = options.handleDatafileFetch;
184
194
  this.initialFeatures = options.initialFeatures;
185
- this.interceptAttributes = options.interceptAttributes;
195
+ this.interceptContext = options.interceptContext;
186
196
  this.logger = options.logger || createLogger();
187
197
  this.refreshInterval = options.refreshInterval;
188
198
  this.stickyFeatures = options.stickyFeatures;
@@ -281,7 +291,7 @@ export class FeaturevisorInstance {
281
291
  /**
282
292
  * Bucketing
283
293
  */
284
- private getBucketKey(feature: Feature, attributes: Attributes): BucketKey {
294
+ private getBucketKey(feature: Feature, context: Context): BucketKey {
285
295
  const featureKey = feature.key;
286
296
 
287
297
  let type;
@@ -305,7 +315,7 @@ export class FeaturevisorInstance {
305
315
  const bucketKey: AttributeValue[] = [];
306
316
 
307
317
  attributeKeys.forEach((attributeKey) => {
308
- const attributeValue = attributes[attributeKey];
318
+ const attributeValue = context[attributeKey];
309
319
 
310
320
  if (typeof attributeValue === "undefined") {
311
321
  return;
@@ -326,19 +336,19 @@ export class FeaturevisorInstance {
326
336
  const result = bucketKey.join(this.bucketKeySeparator);
327
337
 
328
338
  if (this.configureBucketKey) {
329
- return this.configureBucketKey(feature, attributes, result);
339
+ return this.configureBucketKey(feature, context, result);
330
340
  }
331
341
 
332
342
  return result;
333
343
  }
334
344
 
335
- private getBucketValue(feature: Feature, attributes: Attributes): BucketValue {
336
- const bucketKey = this.getBucketKey(feature, attributes);
345
+ private getBucketValue(feature: Feature, context: Context): BucketValue {
346
+ const bucketKey = this.getBucketKey(feature, context);
337
347
 
338
348
  const value = getBucketedNumber(bucketKey);
339
349
 
340
350
  if (this.configureBucketValue) {
341
- return this.configureBucketValue(feature, attributes, value);
351
+ return this.configureBucketValue(feature, context, value);
342
352
  }
343
353
 
344
354
  return value;
@@ -416,15 +426,208 @@ export class FeaturevisorInstance {
416
426
  clearInterval(this.intervalId);
417
427
  }
418
428
 
429
+ /**
430
+ * Flag
431
+ */
432
+ evaluateFlag(featureKey: FeatureKey | Feature, context: Context = {}): Evaluation {
433
+ let evaluation: Evaluation;
434
+
435
+ try {
436
+ const key = typeof featureKey === "string" ? featureKey : featureKey.key;
437
+
438
+ // sticky
439
+ if (
440
+ this.stickyFeatures &&
441
+ this.stickyFeatures[key] &&
442
+ typeof this.stickyFeatures[key].enabled !== "undefined"
443
+ ) {
444
+ evaluation = {
445
+ featureKey: key,
446
+ reason: EvaluationReason.STICKY,
447
+ enabled: this.stickyFeatures[key].enabled,
448
+ sticky: this.stickyFeatures[key],
449
+ };
450
+
451
+ this.logger.debug("using sticky enabled", evaluation);
452
+
453
+ return evaluation;
454
+ }
455
+
456
+ // initial
457
+ if (
458
+ this.statuses &&
459
+ !this.statuses.ready &&
460
+ this.initialFeatures &&
461
+ this.initialFeatures[key] &&
462
+ typeof this.initialFeatures[key].enabled !== "undefined"
463
+ ) {
464
+ evaluation = {
465
+ featureKey: key,
466
+ reason: EvaluationReason.INITIAL,
467
+ enabled: this.initialFeatures[key].enabled,
468
+ initial: this.initialFeatures[key],
469
+ };
470
+
471
+ this.logger.debug("using initial enabled", evaluation);
472
+
473
+ return evaluation;
474
+ }
475
+
476
+ const feature = this.getFeature(featureKey);
477
+
478
+ // not found
479
+ if (!feature) {
480
+ evaluation = {
481
+ featureKey: key,
482
+ reason: EvaluationReason.NOT_FOUND,
483
+ };
484
+
485
+ this.logger.warn("feature not found", evaluation);
486
+
487
+ return evaluation;
488
+ }
489
+
490
+ const finalContext = this.interceptContext ? this.interceptContext(context) : context;
491
+
492
+ // forced
493
+ const force = findForceFromFeature(feature, context, this.datafileReader);
494
+
495
+ if (force && typeof force.enabled !== "undefined") {
496
+ evaluation = {
497
+ featureKey: feature.key,
498
+ reason: EvaluationReason.FORCED,
499
+ enabled: force.enabled,
500
+ };
501
+
502
+ this.logger.debug("forced enabled found", evaluation);
503
+
504
+ return evaluation;
505
+ }
506
+
507
+ // bucketing
508
+ const bucketValue = this.getBucketValue(feature, finalContext);
509
+
510
+ const matchedTraffic = getMatchedTraffic(feature.traffic, finalContext, this.datafileReader);
511
+
512
+ if (matchedTraffic) {
513
+ // check if mutually exclusive
514
+ if (feature.ranges && feature.ranges.length > 0) {
515
+ const matchedRange = feature.ranges.find((range) => {
516
+ return bucketValue >= range[0] && bucketValue < range[1];
517
+ });
518
+
519
+ // matched
520
+ if (matchedRange) {
521
+ evaluation = {
522
+ featureKey: feature.key,
523
+ reason: EvaluationReason.ALLOCATED,
524
+ enabled:
525
+ typeof matchedTraffic.enabled === "undefined" ? true : matchedTraffic.enabled,
526
+ bucketValue,
527
+ };
528
+
529
+ return evaluation;
530
+ }
531
+
532
+ // no match
533
+ evaluation = {
534
+ featureKey: feature.key,
535
+ reason: EvaluationReason.OUT_OF_RANGE,
536
+ enabled: false,
537
+ bucketValue,
538
+ };
539
+
540
+ this.logger.debug("not matched", evaluation);
541
+
542
+ return evaluation;
543
+ }
544
+
545
+ // override from rule
546
+ if (typeof matchedTraffic.enabled !== "undefined") {
547
+ evaluation = {
548
+ featureKey: feature.key,
549
+ reason: EvaluationReason.OVERRIDE,
550
+ enabled: matchedTraffic.enabled,
551
+ bucketValue,
552
+ ruleKey: matchedTraffic.key,
553
+ traffic: matchedTraffic,
554
+ };
555
+
556
+ this.logger.debug("override from rule", evaluation);
557
+
558
+ return evaluation;
559
+ }
560
+
561
+ // treated as enabled because of matched traffic
562
+ if (bucketValue < matchedTraffic.percentage) {
563
+ // @TODO: verify if range check should be inclusive or not
564
+ evaluation = {
565
+ featureKey: feature.key,
566
+ reason: EvaluationReason.RULE,
567
+ enabled: true,
568
+ bucketValue,
569
+ ruleKey: matchedTraffic.key,
570
+ traffic: matchedTraffic,
571
+ };
572
+
573
+ return evaluation;
574
+ }
575
+ }
576
+
577
+ // nothing matched
578
+ evaluation = {
579
+ featureKey: feature.key,
580
+ reason: EvaluationReason.ERROR,
581
+ enabled: false,
582
+ bucketValue,
583
+ };
584
+
585
+ return evaluation;
586
+ } catch (e) {
587
+ evaluation = {
588
+ featureKey: typeof featureKey === "string" ? featureKey : featureKey.key,
589
+ reason: EvaluationReason.ERROR,
590
+ error: e,
591
+ };
592
+
593
+ return evaluation;
594
+ }
595
+ }
596
+
597
+ isEnabled(featureKey: FeatureKey | Feature, context: Context = {}): boolean {
598
+ try {
599
+ const evaluation = this.evaluateFlag(featureKey, context);
600
+
601
+ return evaluation.enabled === true;
602
+ } catch (e) {
603
+ this.logger.error("isEnabled", { featureKey, error: e });
604
+
605
+ return false;
606
+ }
607
+ }
608
+
419
609
  /**
420
610
  * Variation
421
611
  */
422
- evaluateVariation(featureKey: FeatureKey | Feature, attributes: Attributes = {}): Evaluation {
612
+ evaluateVariation(featureKey: FeatureKey | Feature, context: Context = {}): Evaluation {
423
613
  let evaluation: Evaluation;
424
614
 
425
615
  try {
426
616
  const key = typeof featureKey === "string" ? featureKey : featureKey.key;
427
617
 
618
+ const flag = this.evaluateFlag(featureKey, context);
619
+
620
+ if (flag.enabled === false) {
621
+ evaluation = {
622
+ featureKey: key,
623
+ reason: EvaluationReason.DISABLED,
624
+ };
625
+
626
+ this.logger.debug("feature is disabled", evaluation);
627
+
628
+ return evaluation;
629
+ }
630
+
428
631
  // sticky
429
632
  if (this.stickyFeatures && this.stickyFeatures[key]) {
430
633
  const variationValue = this.stickyFeatures[key].variation;
@@ -472,17 +675,27 @@ export class FeaturevisorInstance {
472
675
  reason: EvaluationReason.NOT_FOUND,
473
676
  };
474
677
 
475
- this.logger.warn("feature not found in datafile", evaluation);
678
+ this.logger.warn("feature not found", evaluation);
679
+
680
+ return evaluation;
681
+ }
682
+
683
+ // no variations
684
+ if (!feature.variations || feature.variations.length === 0) {
685
+ evaluation = {
686
+ featureKey: key,
687
+ reason: EvaluationReason.NO_VARIATIONS,
688
+ };
689
+
690
+ this.logger.warn("no variations", evaluation);
476
691
 
477
692
  return evaluation;
478
693
  }
479
694
 
480
- const finalAttributes = this.interceptAttributes
481
- ? this.interceptAttributes(attributes)
482
- : attributes;
695
+ const finalContext = this.interceptContext ? this.interceptContext(context) : context;
483
696
 
484
697
  // forced
485
- const force = findForceFromFeature(feature, attributes, this.datafileReader);
698
+ const force = findForceFromFeature(feature, context, this.datafileReader);
486
699
 
487
700
  if (force && force.variation) {
488
701
  const variation = feature.variations.find((v) => v.value === force.variation);
@@ -501,11 +714,11 @@ export class FeaturevisorInstance {
501
714
  }
502
715
 
503
716
  // bucketing
504
- const bucketValue = this.getBucketValue(feature, finalAttributes);
717
+ const bucketValue = this.getBucketValue(feature, finalContext);
505
718
 
506
719
  const { matchedTraffic, matchedAllocation } = getMatchedTrafficAndAllocation(
507
720
  feature.traffic,
508
- finalAttributes,
721
+ finalContext,
509
722
  bucketValue,
510
723
  this.datafileReader,
511
724
  this.logger,
@@ -550,30 +763,14 @@ export class FeaturevisorInstance {
550
763
  }
551
764
  }
552
765
 
553
- // fall back to default
554
- const variation = feature.variations.find((v) => v.value === feature.defaultVariation);
555
-
556
- if (variation) {
557
- evaluation = {
558
- featureKey: feature.key,
559
- reason: EvaluationReason.DEFAULTED,
560
- bucketValue,
561
- variation,
562
- };
563
-
564
- this.logger.debug("using default variation", evaluation);
565
-
566
- return evaluation;
567
- }
568
-
569
- // nothing matched (this should never happen)
766
+ // nothing matched
570
767
  evaluation = {
571
768
  featureKey: feature.key,
572
769
  reason: EvaluationReason.ERROR,
573
770
  bucketValue,
574
771
  };
575
772
 
576
- this.logger.error("no matched variation", evaluation);
773
+ this.logger.debug("no matched variation", evaluation);
577
774
 
578
775
  return evaluation;
579
776
  } catch (e) {
@@ -589,10 +786,10 @@ export class FeaturevisorInstance {
589
786
 
590
787
  getVariation(
591
788
  featureKey: FeatureKey | Feature,
592
- attributes: Attributes = {},
789
+ context: Context = {},
593
790
  ): VariationValue | undefined {
594
791
  try {
595
- const evaluation = this.evaluateVariation(featureKey, attributes);
792
+ const evaluation = this.evaluateVariation(featureKey, context);
596
793
 
597
794
  if (typeof evaluation.variationValue !== "undefined") {
598
795
  return evaluation.variationValue;
@@ -610,48 +807,12 @@ export class FeaturevisorInstance {
610
807
  }
611
808
  }
612
809
 
613
- getVariationBoolean(
614
- featureKey: FeatureKey | Feature,
615
- attributes: Attributes = {},
616
- ): boolean | undefined {
617
- const variationValue = this.getVariation(featureKey, attributes);
618
-
619
- return getValueByType(variationValue, "boolean") as boolean | undefined;
620
- }
621
-
622
- getVariationString(
623
- featureKey: FeatureKey | Feature,
624
- attributes: Attributes = {},
625
- ): string | undefined {
626
- const variationValue = this.getVariation(featureKey, attributes);
627
-
628
- return getValueByType(variationValue, "string") as string | undefined;
629
- }
630
-
631
- getVariationInteger(
632
- featureKey: FeatureKey | Feature,
633
- attributes: Attributes = {},
634
- ): number | undefined {
635
- const variationValue = this.getVariation(featureKey, attributes);
636
-
637
- return getValueByType(variationValue, "integer") as number | undefined;
638
- }
639
-
640
- getVariationDouble(
641
- featureKey: FeatureKey | Feature,
642
- attributes: Attributes = {},
643
- ): number | undefined {
644
- const variationValue = this.getVariation(featureKey, attributes);
645
-
646
- return getValueByType(variationValue, "double") as number | undefined;
647
- }
648
-
649
810
  /**
650
811
  * Activate
651
812
  */
652
- activate(featureKey: FeatureKey, attributes: Attributes = {}): VariationValue | undefined {
813
+ activate(featureKey: FeatureKey, context: Context = {}): VariationValue | undefined {
653
814
  try {
654
- const evaluation = this.evaluateVariation(featureKey, attributes);
815
+ const evaluation = this.evaluateVariation(featureKey, context);
655
816
  const variationValue = evaluation.variation
656
817
  ? evaluation.variation.value
657
818
  : evaluation.variationValue;
@@ -660,19 +821,17 @@ export class FeaturevisorInstance {
660
821
  return undefined;
661
822
  }
662
823
 
663
- const finalAttributes = this.interceptAttributes
664
- ? this.interceptAttributes(attributes)
665
- : attributes;
824
+ const finalContext = this.interceptContext ? this.interceptContext(context) : context;
666
825
 
667
- const captureAttributes: Attributes = {};
826
+ const captureContext: Context = {};
668
827
 
669
828
  const attributesForCapturing = this.datafileReader
670
829
  .getAllAttributes()
671
830
  .filter((a) => a.capture === true);
672
831
 
673
832
  attributesForCapturing.forEach((a) => {
674
- if (typeof finalAttributes[a.key] !== "undefined") {
675
- captureAttributes[a.key] = attributes[a.key];
833
+ if (typeof finalContext[a.key] !== "undefined") {
834
+ captureContext[a.key] = context[a.key];
676
835
  }
677
836
  });
678
837
 
@@ -680,8 +839,8 @@ export class FeaturevisorInstance {
680
839
  "activation",
681
840
  featureKey,
682
841
  variationValue,
683
- finalAttributes,
684
- captureAttributes,
842
+ finalContext,
843
+ captureContext,
685
844
  evaluation,
686
845
  );
687
846
 
@@ -693,26 +852,26 @@ export class FeaturevisorInstance {
693
852
  }
694
853
  }
695
854
 
696
- activateBoolean(featureKey: FeatureKey, attributes: Attributes = {}): boolean | undefined {
697
- const variationValue = this.activate(featureKey, attributes);
855
+ activateBoolean(featureKey: FeatureKey, context: Context = {}): boolean | undefined {
856
+ const variationValue = this.activate(featureKey, context);
698
857
 
699
858
  return getValueByType(variationValue, "boolean") as boolean | undefined;
700
859
  }
701
860
 
702
- activateString(featureKey: FeatureKey, attributes: Attributes = {}): string | undefined {
703
- const variationValue = this.activate(featureKey, attributes);
861
+ activateString(featureKey: FeatureKey, context: Context = {}): string | undefined {
862
+ const variationValue = this.activate(featureKey, context);
704
863
 
705
864
  return getValueByType(variationValue, "string") as string | undefined;
706
865
  }
707
866
 
708
- activateInteger(featureKey: FeatureKey, attributes: Attributes = {}): number | undefined {
709
- const variationValue = this.activate(featureKey, attributes);
867
+ activateInteger(featureKey: FeatureKey, context: Context = {}): number | undefined {
868
+ const variationValue = this.activate(featureKey, context);
710
869
 
711
870
  return getValueByType(variationValue, "integer") as number | undefined;
712
871
  }
713
872
 
714
- activateDouble(featureKey: FeatureKey, attributes: Attributes = {}): number | undefined {
715
- const variationValue = this.activate(featureKey, attributes);
873
+ activateDouble(featureKey: FeatureKey, context: Context = {}): number | undefined {
874
+ const variationValue = this.activate(featureKey, context);
716
875
 
717
876
  return getValueByType(variationValue, "double") as number | undefined;
718
877
  }
@@ -723,28 +882,45 @@ export class FeaturevisorInstance {
723
882
  evaluateVariable(
724
883
  featureKey: FeatureKey | Feature,
725
884
  variableKey: VariableKey,
726
- attributes: Attributes = {},
885
+ context: Context = {},
727
886
  ): Evaluation {
728
887
  let evaluation: Evaluation;
729
888
 
730
889
  try {
731
890
  const key = typeof featureKey === "string" ? featureKey : featureKey.key;
732
891
 
892
+ const flag = this.evaluateFlag(featureKey, context);
893
+
894
+ if (flag.enabled === false) {
895
+ evaluation = {
896
+ featureKey: key,
897
+ reason: EvaluationReason.DISABLED,
898
+ };
899
+
900
+ this.logger.debug("feature is disabled", evaluation);
901
+
902
+ return evaluation;
903
+ }
904
+
733
905
  // sticky
734
- if (this.stickyFeatures && this.stickyFeatures[key] && this.stickyFeatures[key].variables) {
735
- const result = this.stickyFeatures[key].variables[variableKey];
906
+ if (this.stickyFeatures && this.stickyFeatures[key]) {
907
+ const variables = this.stickyFeatures[key].variables;
736
908
 
737
- if (typeof result !== "undefined") {
738
- evaluation = {
739
- featureKey: key,
740
- reason: EvaluationReason.STICKY,
741
- variableKey,
742
- variableValue: result,
743
- };
909
+ if (variables) {
910
+ const result = variables[variableKey];
744
911
 
745
- this.logger.debug("using sticky variable", evaluation);
912
+ if (typeof result !== "undefined") {
913
+ evaluation = {
914
+ featureKey: key,
915
+ reason: EvaluationReason.STICKY,
916
+ variableKey,
917
+ variableValue: result,
918
+ };
746
919
 
747
- return evaluation;
920
+ this.logger.debug("using sticky variable", evaluation);
921
+
922
+ return evaluation;
923
+ }
748
924
  }
749
925
  }
750
926
 
@@ -753,20 +929,24 @@ export class FeaturevisorInstance {
753
929
  this.statuses &&
754
930
  !this.statuses.ready &&
755
931
  this.initialFeatures &&
756
- this.initialFeatures[key] &&
757
- this.initialFeatures[key].variables &&
758
- typeof this.initialFeatures[key].variables[variableKey] !== "undefined"
932
+ this.initialFeatures[key]
759
933
  ) {
760
- evaluation = {
761
- featureKey: key,
762
- reason: EvaluationReason.INITIAL,
763
- variableKey,
764
- variableValue: this.initialFeatures[key].variables[variableKey],
765
- };
934
+ const variables = this.initialFeatures[key].variables;
766
935
 
767
- this.logger.debug("using initial variable", evaluation);
936
+ if (variables) {
937
+ if (typeof variables[variableKey] !== "undefined") {
938
+ evaluation = {
939
+ featureKey: key,
940
+ reason: EvaluationReason.INITIAL,
941
+ variableKey,
942
+ variableValue: variables[variableKey],
943
+ };
768
944
 
769
- return evaluation;
945
+ this.logger.debug("using initial variable", evaluation);
946
+
947
+ return evaluation;
948
+ }
949
+ }
770
950
  }
771
951
 
772
952
  const feature = this.getFeature(featureKey);
@@ -801,12 +981,10 @@ export class FeaturevisorInstance {
801
981
  return evaluation;
802
982
  }
803
983
 
804
- const finalAttributes = this.interceptAttributes
805
- ? this.interceptAttributes(attributes)
806
- : attributes;
984
+ const finalContext = this.interceptContext ? this.interceptContext(context) : context;
807
985
 
808
986
  // forced
809
- const force = findForceFromFeature(feature, attributes, this.datafileReader);
987
+ const force = findForceFromFeature(feature, context, this.datafileReader);
810
988
 
811
989
  if (force && force.variables && typeof force.variables[variableKey] !== "undefined") {
812
990
  evaluation = {
@@ -823,11 +1001,11 @@ export class FeaturevisorInstance {
823
1001
  }
824
1002
 
825
1003
  // bucketing
826
- const bucketValue = this.getBucketValue(feature, finalAttributes);
1004
+ const bucketValue = this.getBucketValue(feature, finalContext);
827
1005
 
828
1006
  const { matchedTraffic, matchedAllocation } = getMatchedTrafficAndAllocation(
829
1007
  feature.traffic,
830
- finalAttributes,
1008
+ finalContext,
831
1009
  bucketValue,
832
1010
  this.datafileReader,
833
1011
  this.logger,
@@ -855,7 +1033,7 @@ export class FeaturevisorInstance {
855
1033
  }
856
1034
 
857
1035
  // regular allocation
858
- if (matchedAllocation && matchedAllocation.variation) {
1036
+ if (matchedAllocation && matchedAllocation.variation && Array.isArray(feature.variations)) {
859
1037
  const variation = feature.variations.find((v) => v.value === matchedAllocation.variation);
860
1038
 
861
1039
  if (variation && variation.variables) {
@@ -867,7 +1045,7 @@ export class FeaturevisorInstance {
867
1045
  if (o.conditions) {
868
1046
  return allConditionsAreMatched(
869
1047
  typeof o.conditions === "string" ? JSON.parse(o.conditions) : o.conditions,
870
- finalAttributes,
1048
+ finalContext,
871
1049
  );
872
1050
  }
873
1051
 
@@ -876,7 +1054,7 @@ export class FeaturevisorInstance {
876
1054
  typeof o.segments === "string" && o.segments !== "*"
877
1055
  ? JSON.parse(o.segments)
878
1056
  : o.segments,
879
- finalAttributes,
1057
+ finalContext,
880
1058
  this.datafileReader,
881
1059
  );
882
1060
  }
@@ -949,10 +1127,10 @@ export class FeaturevisorInstance {
949
1127
  getVariable(
950
1128
  featureKey: FeatureKey | Feature,
951
1129
  variableKey: string,
952
- attributes: Attributes = {},
1130
+ context: Context = {},
953
1131
  ): VariableValue | undefined {
954
1132
  try {
955
- const evaluation = this.evaluateVariable(featureKey, variableKey, attributes);
1133
+ const evaluation = this.evaluateVariable(featureKey, variableKey, context);
956
1134
 
957
1135
  if (typeof evaluation.variableValue !== "undefined") {
958
1136
  if (
@@ -977,9 +1155,9 @@ export class FeaturevisorInstance {
977
1155
  getVariableBoolean(
978
1156
  featureKey: FeatureKey | Feature,
979
1157
  variableKey: string,
980
- attributes: Attributes = {},
1158
+ context: Context = {},
981
1159
  ): boolean | undefined {
982
- const variableValue = this.getVariable(featureKey, variableKey, attributes);
1160
+ const variableValue = this.getVariable(featureKey, variableKey, context);
983
1161
 
984
1162
  return getValueByType(variableValue, "boolean") as boolean | undefined;
985
1163
  }
@@ -987,9 +1165,9 @@ export class FeaturevisorInstance {
987
1165
  getVariableString(
988
1166
  featureKey: FeatureKey | Feature,
989
1167
  variableKey: string,
990
- attributes: Attributes = {},
1168
+ context: Context = {},
991
1169
  ): string | undefined {
992
- const variableValue = this.getVariable(featureKey, variableKey, attributes);
1170
+ const variableValue = this.getVariable(featureKey, variableKey, context);
993
1171
 
994
1172
  return getValueByType(variableValue, "string") as string | undefined;
995
1173
  }
@@ -997,9 +1175,9 @@ export class FeaturevisorInstance {
997
1175
  getVariableInteger(
998
1176
  featureKey: FeatureKey | Feature,
999
1177
  variableKey: string,
1000
- attributes: Attributes = {},
1178
+ context: Context = {},
1001
1179
  ): number | undefined {
1002
- const variableValue = this.getVariable(featureKey, variableKey, attributes);
1180
+ const variableValue = this.getVariable(featureKey, variableKey, context);
1003
1181
 
1004
1182
  return getValueByType(variableValue, "integer") as number | undefined;
1005
1183
  }
@@ -1007,9 +1185,9 @@ export class FeaturevisorInstance {
1007
1185
  getVariableDouble(
1008
1186
  featureKey: FeatureKey | Feature,
1009
1187
  variableKey: string,
1010
- attributes: Attributes = {},
1188
+ context: Context = {},
1011
1189
  ): number | undefined {
1012
- const variableValue = this.getVariable(featureKey, variableKey, attributes);
1190
+ const variableValue = this.getVariable(featureKey, variableKey, context);
1013
1191
 
1014
1192
  return getValueByType(variableValue, "double") as number | undefined;
1015
1193
  }
@@ -1017,9 +1195,9 @@ export class FeaturevisorInstance {
1017
1195
  getVariableArray(
1018
1196
  featureKey: FeatureKey | Feature,
1019
1197
  variableKey: string,
1020
- attributes: Attributes = {},
1198
+ context: Context = {},
1021
1199
  ): string[] | undefined {
1022
- const variableValue = this.getVariable(featureKey, variableKey, attributes);
1200
+ const variableValue = this.getVariable(featureKey, variableKey, context);
1023
1201
 
1024
1202
  return getValueByType(variableValue, "array") as string[] | undefined;
1025
1203
  }
@@ -1027,9 +1205,9 @@ export class FeaturevisorInstance {
1027
1205
  getVariableObject<T>(
1028
1206
  featureKey: FeatureKey | Feature,
1029
1207
  variableKey: string,
1030
- attributes: Attributes = {},
1208
+ context: Context = {},
1031
1209
  ): T | undefined {
1032
- const variableValue = this.getVariable(featureKey, variableKey, attributes);
1210
+ const variableValue = this.getVariable(featureKey, variableKey, context);
1033
1211
 
1034
1212
  return getValueByType(variableValue, "object") as T | undefined;
1035
1213
  }
@@ -1037,9 +1215,9 @@ export class FeaturevisorInstance {
1037
1215
  getVariableJSON<T>(
1038
1216
  featureKey: FeatureKey | Feature,
1039
1217
  variableKey: string,
1040
- attributes: Attributes = {},
1218
+ context: Context = {},
1041
1219
  ): T | undefined {
1042
- const variableValue = this.getVariable(featureKey, variableKey, attributes);
1220
+ const variableValue = this.getVariable(featureKey, variableKey, context);
1043
1221
 
1044
1222
  return getValueByType(variableValue, "json") as T | undefined;
1045
1223
  }