@microsoft/feature-management 2.0.0-preview.3 → 2.0.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/dist/umd/index.js CHANGED
@@ -141,7 +141,7 @@
141
141
  name = "Microsoft.Targeting";
142
142
  async evaluate(context, appContext) {
143
143
  const { featureName, parameters } = context;
144
- TargetingFilter.#validateParameters(parameters);
144
+ TargetingFilter.#validateParameters(featureName, parameters);
145
145
  if (appContext === undefined) {
146
146
  throw new Error("The app context is required for targeting filter.");
147
147
  }
@@ -184,15 +184,15 @@
184
184
  const hint = featureName;
185
185
  return isTargetedPercentile(appContext?.userId, hint, 0, parameters.Audience.DefaultRolloutPercentage);
186
186
  }
187
- static #validateParameters(parameters) {
187
+ static #validateParameters(featureName, parameters) {
188
188
  if (parameters.Audience.DefaultRolloutPercentage < 0 || parameters.Audience.DefaultRolloutPercentage > 100) {
189
- throw new Error("Audience.DefaultRolloutPercentage must be a number between 0 and 100.");
189
+ throw new Error(`Invalid feature flag: ${featureName}. Audience.DefaultRolloutPercentage must be a number between 0 and 100.`);
190
190
  }
191
191
  // validate RolloutPercentage for each group
192
192
  if (parameters.Audience.Groups !== undefined) {
193
193
  for (const group of parameters.Audience.Groups) {
194
194
  if (group.RolloutPercentage < 0 || group.RolloutPercentage > 100) {
195
- throw new Error(`RolloutPercentage of group ${group.Name} must be a number between 0 and 100.`);
195
+ throw new Error(`Invalid feature flag: ${featureName}. RolloutPercentage of group ${group.Name} must be a number between 0 and 100.`);
196
196
  }
197
197
  }
198
198
  }
@@ -471,157 +471,157 @@
471
471
  throw new TypeError("Feature flag 'id' must be a string.");
472
472
  }
473
473
  if (featureFlag.enabled !== undefined && typeof featureFlag.enabled !== "boolean") {
474
- throw new TypeError("Feature flag 'enabled' must be a boolean.");
474
+ throw new TypeError(`Invalid feature flag: ${featureFlag.id}. Feature flag 'enabled' must be a boolean.`);
475
475
  }
476
476
  if (featureFlag.conditions !== undefined) {
477
- validateFeatureEnablementConditions(featureFlag.conditions);
477
+ validateFeatureEnablementConditions(featureFlag.id, featureFlag.conditions);
478
478
  }
479
479
  if (featureFlag.variants !== undefined) {
480
- validateVariants(featureFlag.variants);
480
+ validateVariants(featureFlag.id, featureFlag.variants);
481
481
  }
482
482
  if (featureFlag.allocation !== undefined) {
483
- validateVariantAllocation(featureFlag.allocation);
483
+ validateVariantAllocation(featureFlag.id, featureFlag.allocation);
484
484
  }
485
485
  if (featureFlag.telemetry !== undefined) {
486
- validateTelemetryOptions(featureFlag.telemetry);
486
+ validateTelemetryOptions(featureFlag.id, featureFlag.telemetry);
487
487
  }
488
488
  }
489
- function validateFeatureEnablementConditions(conditions) {
489
+ function validateFeatureEnablementConditions(id, conditions) {
490
490
  if (typeof conditions !== "object") {
491
- throw new TypeError("Feature flag 'conditions' must be an object.");
491
+ throw new TypeError(`Invalid feature flag: ${id}. Feature flag 'conditions' must be an object.`);
492
492
  }
493
493
  if (conditions.requirement_type !== undefined && conditions.requirement_type !== "Any" && conditions.requirement_type !== "All") {
494
- throw new TypeError("'requirement_type' must be 'Any' or 'All'.");
494
+ throw new TypeError(`Invalid feature flag: ${id}. 'requirement_type' must be 'Any' or 'All'.`);
495
495
  }
496
496
  if (conditions.client_filters !== undefined) {
497
- validateClientFilters(conditions.client_filters);
497
+ validateClientFilters(id, conditions.client_filters);
498
498
  }
499
499
  }
500
- function validateClientFilters(client_filters) {
500
+ function validateClientFilters(id, client_filters) {
501
501
  if (!Array.isArray(client_filters)) {
502
- throw new TypeError("Feature flag conditions 'client_filters' must be an array.");
502
+ throw new TypeError(`Invalid feature flag: ${id}. Feature flag conditions 'client_filters' must be an array.`);
503
503
  }
504
504
  for (const filter of client_filters) {
505
505
  if (typeof filter.name !== "string") {
506
- throw new TypeError("Client filter 'name' must be a string.");
506
+ throw new TypeError(`Invalid feature flag: ${id}. Client filter 'name' must be a string.`);
507
507
  }
508
508
  if (filter.parameters !== undefined && typeof filter.parameters !== "object") {
509
- throw new TypeError("Client filter 'parameters' must be an object.");
509
+ throw new TypeError(`Invalid feature flag: ${id}. Client filter 'parameters' must be an object.`);
510
510
  }
511
511
  }
512
512
  }
513
- function validateVariants(variants) {
513
+ function validateVariants(id, variants) {
514
514
  if (!Array.isArray(variants)) {
515
- throw new TypeError("Feature flag 'variants' must be an array.");
515
+ throw new TypeError(`Invalid feature flag: ${id}. Feature flag 'variants' must be an array.`);
516
516
  }
517
517
  for (const variant of variants) {
518
518
  if (typeof variant.name !== "string") {
519
- throw new TypeError("Variant 'name' must be a string.");
519
+ throw new TypeError(`Invalid feature flag: ${id}. Variant 'name' must be a string.`);
520
520
  }
521
521
  // skip configuration_value validation as it accepts any type
522
522
  if (variant.status_override !== undefined && typeof variant.status_override !== "string") {
523
- throw new TypeError("Variant 'status_override' must be a string.");
523
+ throw new TypeError(`Invalid feature flag: ${id}. Variant 'status_override' must be a string.`);
524
524
  }
525
525
  if (variant.status_override !== undefined && variant.status_override !== "None" && variant.status_override !== "Enabled" && variant.status_override !== "Disabled") {
526
- throw new TypeError("Variant 'status_override' must be 'None', 'Enabled', or 'Disabled'.");
526
+ throw new TypeError(`Invalid feature flag: ${id}. Variant 'status_override' must be 'None', 'Enabled', or 'Disabled'.`);
527
527
  }
528
528
  }
529
529
  }
530
- function validateVariantAllocation(allocation) {
530
+ function validateVariantAllocation(id, allocation) {
531
531
  if (typeof allocation !== "object") {
532
- throw new TypeError("Variant 'allocation' must be an object.");
532
+ throw new TypeError(`Invalid feature flag: ${id}. Variant 'allocation' must be an object.`);
533
533
  }
534
534
  if (allocation.default_when_disabled !== undefined && typeof allocation.default_when_disabled !== "string") {
535
- throw new TypeError("Variant allocation 'default_when_disabled' must be a string.");
535
+ throw new TypeError(`Invalid feature flag: ${id}. Variant allocation 'default_when_disabled' must be a string.`);
536
536
  }
537
537
  if (allocation.default_when_enabled !== undefined && typeof allocation.default_when_enabled !== "string") {
538
- throw new TypeError("Variant allocation 'default_when_enabled' must be a string.");
538
+ throw new TypeError(`Invalid feature flag: ${id}. Variant allocation 'default_when_enabled' must be a string.`);
539
539
  }
540
540
  if (allocation.user !== undefined) {
541
- validateUserVariantAllocation(allocation.user);
541
+ validateUserVariantAllocation(id, allocation.user);
542
542
  }
543
543
  if (allocation.group !== undefined) {
544
- validateGroupVariantAllocation(allocation.group);
544
+ validateGroupVariantAllocation(id, allocation.group);
545
545
  }
546
546
  if (allocation.percentile !== undefined) {
547
- validatePercentileVariantAllocation(allocation.percentile);
547
+ validatePercentileVariantAllocation(id, allocation.percentile);
548
548
  }
549
549
  if (allocation.seed !== undefined && typeof allocation.seed !== "string") {
550
- throw new TypeError("Variant allocation 'seed' must be a string.");
550
+ throw new TypeError(`Invalid feature flag: ${id}. Variant allocation 'seed' must be a string.`);
551
551
  }
552
552
  }
553
- function validateUserVariantAllocation(UserAllocations) {
553
+ function validateUserVariantAllocation(id, UserAllocations) {
554
554
  if (!Array.isArray(UserAllocations)) {
555
- throw new TypeError("Variant 'user' allocation must be an array.");
555
+ throw new TypeError(`Invalid feature flag: ${id}. Variant 'user' allocation must be an array.`);
556
556
  }
557
557
  for (const allocation of UserAllocations) {
558
558
  if (typeof allocation !== "object") {
559
- throw new TypeError("Elements in variant 'user' allocation must be an object.");
559
+ throw new TypeError(`Invalid feature flag: ${id}. Elements in variant 'user' allocation must be an object.`);
560
560
  }
561
561
  if (typeof allocation.variant !== "string") {
562
- throw new TypeError("User allocation 'variant' must be a string.");
562
+ throw new TypeError(`Invalid feature flag: ${id}. User allocation 'variant' must be a string.`);
563
563
  }
564
564
  if (!Array.isArray(allocation.users)) {
565
- throw new TypeError("User allocation 'users' must be an array.");
565
+ throw new TypeError(`Invalid feature flag: ${id}. User allocation 'users' must be an array.`);
566
566
  }
567
567
  for (const user of allocation.users) {
568
568
  if (typeof user !== "string") {
569
- throw new TypeError("Elements in user allocation 'users' must be strings.");
569
+ throw new TypeError(`Invalid feature flag: ${id}. Elements in user allocation 'users' must be strings.`);
570
570
  }
571
571
  }
572
572
  }
573
573
  }
574
- function validateGroupVariantAllocation(groupAllocations) {
574
+ function validateGroupVariantAllocation(id, groupAllocations) {
575
575
  if (!Array.isArray(groupAllocations)) {
576
- throw new TypeError("Variant 'group' allocation must be an array.");
576
+ throw new TypeError(`Invalid feature flag: ${id}. Variant 'group' allocation must be an array.`);
577
577
  }
578
578
  for (const allocation of groupAllocations) {
579
579
  if (typeof allocation !== "object") {
580
- throw new TypeError("Elements in variant 'group' allocation must be an object.");
580
+ throw new TypeError(`Invalid feature flag: ${id}. Elements in variant 'group' allocation must be an object.`);
581
581
  }
582
582
  if (typeof allocation.variant !== "string") {
583
- throw new TypeError("Group allocation 'variant' must be a string.");
583
+ throw new TypeError(`Invalid feature flag: ${id}. Group allocation 'variant' must be a string.`);
584
584
  }
585
585
  if (!Array.isArray(allocation.groups)) {
586
- throw new TypeError("Group allocation 'groups' must be an array.");
586
+ throw new TypeError(`Invalid feature flag: ${id}. Group allocation 'groups' must be an array.`);
587
587
  }
588
588
  for (const group of allocation.groups) {
589
589
  if (typeof group !== "string") {
590
- throw new TypeError("Elements in group allocation 'groups' must be strings.");
590
+ throw new TypeError(`Invalid feature flag: ${id}. Elements in group allocation 'groups' must be strings.`);
591
591
  }
592
592
  }
593
593
  }
594
594
  }
595
- function validatePercentileVariantAllocation(percentileAllocations) {
595
+ function validatePercentileVariantAllocation(id, percentileAllocations) {
596
596
  if (!Array.isArray(percentileAllocations)) {
597
- throw new TypeError("Variant 'percentile' allocation must be an array.");
597
+ throw new TypeError(`Invalid feature flag: ${id}. Variant 'percentile' allocation must be an array.`);
598
598
  }
599
599
  for (const allocation of percentileAllocations) {
600
600
  if (typeof allocation !== "object") {
601
- throw new TypeError("Elements in variant 'percentile' allocation must be an object.");
601
+ throw new TypeError(`Invalid feature flag: ${id}. Elements in variant 'percentile' allocation must be an object.`);
602
602
  }
603
603
  if (typeof allocation.variant !== "string") {
604
- throw new TypeError("Percentile allocation 'variant' must be a string.");
604
+ throw new TypeError(`Invalid feature flag: ${id}. Percentile allocation 'variant' must be a string.`);
605
605
  }
606
606
  if (typeof allocation.from !== "number" || allocation.from < 0 || allocation.from > 100) {
607
- throw new TypeError("Percentile allocation 'from' must be a number between 0 and 100.");
607
+ throw new TypeError(`Invalid feature flag: ${id}. Percentile allocation 'from' must be a number between 0 and 100.`);
608
608
  }
609
609
  if (typeof allocation.to !== "number" || allocation.to < 0 || allocation.to > 100) {
610
- throw new TypeError("Percentile allocation 'to' must be a number between 0 and 100.");
610
+ throw new TypeError(`Invalid feature flag: ${id}. Percentile allocation 'to' must be a number between 0 and 100.`);
611
611
  }
612
612
  }
613
613
  }
614
614
  // #endregion
615
615
  // #region Telemetry
616
- function validateTelemetryOptions(telemetry) {
616
+ function validateTelemetryOptions(id, telemetry) {
617
617
  if (typeof telemetry !== "object") {
618
- throw new TypeError("Feature flag 'telemetry' must be an object.");
618
+ throw new TypeError(`Invalid feature flag: ${id}. Feature flag 'telemetry' must be an object.`);
619
619
  }
620
620
  if (telemetry.enabled !== undefined && typeof telemetry.enabled !== "boolean") {
621
- throw new TypeError("Telemetry 'enabled' must be a boolean.");
621
+ throw new TypeError(`Invalid feature flag: ${id}. Telemetry 'enabled' must be a boolean.`);
622
622
  }
623
623
  if (telemetry.metadata !== undefined && typeof telemetry.metadata !== "object") {
624
- throw new TypeError("Telemetry 'metadata' must be an object.");
624
+ throw new TypeError(`Invalid feature flag: ${id}. Telemetry 'metadata' must be an object.`);
625
625
  }
626
626
  }
627
627
  // #endregion
@@ -644,9 +644,11 @@
644
644
  }
645
645
  async getFeatureFlags() {
646
646
  const featureConfig = this.#configuration.get(FEATURE_MANAGEMENT_KEY);
647
- const featureFlag = featureConfig?.[FEATURE_FLAGS_KEY] ?? [];
648
- validateFeatureFlag(featureFlag);
649
- return featureFlag;
647
+ const featureFlags = featureConfig?.[FEATURE_FLAGS_KEY] ?? [];
648
+ featureFlags.forEach(featureFlag => {
649
+ validateFeatureFlag(featureFlag);
650
+ });
651
+ return featureFlags;
650
652
  }
651
653
  }
652
654
  /**
@@ -664,15 +666,17 @@
664
666
  return featureFlag;
665
667
  }
666
668
  async getFeatureFlags() {
667
- const featureFlag = this.#configuration[FEATURE_MANAGEMENT_KEY]?.[FEATURE_FLAGS_KEY] ?? [];
668
- validateFeatureFlag(featureFlag);
669
- return featureFlag;
669
+ const featureFlags = this.#configuration[FEATURE_MANAGEMENT_KEY]?.[FEATURE_FLAGS_KEY] ?? [];
670
+ featureFlags.forEach(featureFlag => {
671
+ validateFeatureFlag(featureFlag);
672
+ });
673
+ return featureFlags;
670
674
  }
671
675
  }
672
676
 
673
677
  // Copyright (c) Microsoft Corporation.
674
678
  // Licensed under the MIT license.
675
- const VERSION$1 = "2.0.0-preview.3";
679
+ const VERSION$1 = "2.0.0";
676
680
  const EVALUATION_EVENT_VERSION = "1.0.0";
677
681
 
678
682
  // Copyright (c) Microsoft Corporation.
@@ -683,8 +687,6 @@
683
687
  const TARGETING_ID = "TargetingId";
684
688
  const VARIANT = "Variant";
685
689
  const VARIANT_ASSIGNMENT_REASON = "VariantAssignmentReason";
686
- const DEFAULT_WHEN_ENABLED = "DefaultWhenEnabled";
687
- const VARIANT_ASSIGNMENT_PERCENTAGE = "VariantAssignmentPercentage";
688
690
  function createFeatureEvaluationEventProperties(result) {
689
691
  if (result.feature === undefined) {
690
692
  return undefined;
@@ -698,29 +700,6 @@
698
700
  [VARIANT]: result.variant ? result.variant.name : "",
699
701
  [VARIANT_ASSIGNMENT_REASON]: result.variantAssignmentReason,
700
702
  };
701
- if (result.feature.allocation?.default_when_enabled) {
702
- eventProperties[DEFAULT_WHEN_ENABLED] = result.feature.allocation.default_when_enabled;
703
- }
704
- if (result.variantAssignmentReason === exports.VariantAssignmentReason.DefaultWhenEnabled) {
705
- let percentileAllocationPercentage = 0;
706
- if (result.variant !== undefined && result.feature.allocation !== undefined && result.feature.allocation.percentile !== undefined) {
707
- for (const percentile of result.feature.allocation.percentile) {
708
- percentileAllocationPercentage += percentile.to - percentile.from;
709
- }
710
- }
711
- eventProperties[VARIANT_ASSIGNMENT_PERCENTAGE] = (100 - percentileAllocationPercentage).toString();
712
- }
713
- else if (result.variantAssignmentReason === exports.VariantAssignmentReason.Percentile) {
714
- let percentileAllocationPercentage = 0;
715
- if (result.variant !== undefined && result.feature.allocation !== undefined && result.feature.allocation.percentile !== undefined) {
716
- for (const percentile of result.feature.allocation.percentile) {
717
- if (percentile.variant === result.variant.name) {
718
- percentileAllocationPercentage += percentile.to - percentile.from;
719
- }
720
- }
721
- }
722
- eventProperties[VARIANT_ASSIGNMENT_PERCENTAGE] = percentileAllocationPercentage.toString();
723
- }
724
703
  const metadata = result.feature.telemetry?.metadata;
725
704
  if (metadata) {
726
705
  for (const key in metadata) {