11import fs from "fs" ;
22
3- import { evaluateFeatureRules , flattenJSON } from "@bucketco/flag-evaluation" ;
3+ import {
4+ EvaluationResult ,
5+ flattenJSON ,
6+ newEvaluator ,
7+ } from "@bucketco/flag-evaluation" ;
48
59import BatchBuffer from "./batch-buffer" ;
610import {
@@ -19,15 +23,14 @@ import inRequestCache from "./inRequestCache";
1923import periodicallyUpdatingCache from "./periodicallyUpdatingCache" ;
2024import { newRateLimiter } from "./rate-limiter" ;
2125import type {
26+ CachedFeatureDefinition ,
2227 CacheStrategy ,
2328 EvaluatedFeaturesAPIResponse ,
24- FeatureAPIResponse ,
2529 FeatureDefinition ,
2630 FeatureOverrides ,
2731 FeatureOverridesFn ,
2832 IdType ,
2933 RawFeature ,
30- RawFeatureRemoteConfig ,
3134} from "./types" ;
3235import {
3336 Attributes ,
@@ -127,7 +130,7 @@ export class BucketClient {
127130 } ;
128131 httpClient : HttpClient ;
129132
130- private featuresCache : Cache < FeaturesAPIResponse > ;
133+ private featuresCache : Cache < CachedFeatureDefinition [ ] > ;
131134 private batchBuffer : BatchBuffer < BulkEvent > ;
132135 private rateLimiter : ReturnType < typeof newRateLimiter > ;
133136
@@ -334,18 +337,40 @@ export class BucketClient {
334337 this . logger . warn ( "features cache: invalid response" , res ) ;
335338 return undefined ;
336339 }
337- return res ;
340+
341+ return res . features . map ( ( featureDef ) => {
342+ return {
343+ ...featureDef ,
344+ enabledEvaluator : newEvaluator (
345+ featureDef . targeting . rules . map ( ( rule ) => ( {
346+ filter : rule . filter ,
347+ value : true ,
348+ } ) ) ,
349+ ) ,
350+ configEvaluator : featureDef . config
351+ ? newEvaluator (
352+ featureDef . config ?. variants . map ( ( variant ) => ( {
353+ filter : variant . filter ,
354+ value : {
355+ key : variant . key ,
356+ payload : variant . payload ,
357+ } ,
358+ } ) ) ,
359+ )
360+ : undefined ,
361+ } satisfies CachedFeatureDefinition ;
362+ } ) ;
338363 } ;
339364
340365 if ( this . _config . cacheStrategy === "periodically-update" ) {
341- this . featuresCache = periodicallyUpdatingCache < FeaturesAPIResponse > (
366+ this . featuresCache = periodicallyUpdatingCache < CachedFeatureDefinition [ ] > (
342367 this . _config . refetchInterval ,
343368 this . _config . staleWarningInterval ,
344369 this . logger ,
345370 fetchFeatures ,
346371 ) ;
347372 } else {
348- this . featuresCache = inRequestCache < FeaturesAPIResponse > (
373+ this . featuresCache = inRequestCache < CachedFeatureDefinition [ ] > (
349374 this . _config . refetchInterval ,
350375 this . logger ,
351376 fetchFeatures ,
@@ -582,7 +607,7 @@ export class BucketClient {
582607 * @returns The features definitions.
583608 */
584609 public async getFeatureDefinitions ( ) : Promise < FeatureDefinition [ ] > {
585- const features = this . featuresCache . get ( ) ?. features || [ ] ;
610+ const features = this . featuresCache . get ( ) || [ ] ;
586611 return features . map ( ( f ) => ( {
587612 key : f . key ,
588613 description : f . description ,
@@ -1022,72 +1047,46 @@ export class BucketClient {
10221047 }
10231048
10241049 void this . syncContext ( options ) ;
1025- let featureDefinitions : FeaturesAPIResponse [ "features" ] ;
1050+ let featureDefinitions : CachedFeatureDefinition [ ] = [ ] ;
10261051
1027- if ( this . _config . offline ) {
1028- featureDefinitions = [ ] ;
1029- } else {
1030- const fetchedFeatures = this . featuresCache . get ( ) ;
1031- if ( ! fetchedFeatures ) {
1052+ if ( ! this . _config . offline ) {
1053+ const featureDefs = this . featuresCache . get ( ) ;
1054+ if ( ! featureDefs ) {
10321055 this . logger . warn (
10331056 "no feature definitions available, using fallback features." ,
10341057 ) ;
10351058 return this . _config . fallbackFeatures || { } ;
10361059 }
1037-
1038- featureDefinitions = fetchedFeatures . features ;
1060+ featureDefinitions = featureDefs ;
10391061 }
10401062
1041- const featureMap = featureDefinitions . reduce (
1042- ( acc , f ) => {
1043- acc [ f . key ] = f ;
1044- return acc ;
1045- } ,
1046- { } as Record < string , FeatureAPIResponse > ,
1047- ) ;
1048-
10491063 const { enableTracking = true , meta : _ , ...context } = options ;
10501064
1051- const evaluated = featureDefinitions . map ( ( feature ) =>
1052- evaluateFeatureRules ( {
1053- featureKey : feature . key ,
1054- rules : feature . targeting . rules . map ( ( r ) => ( { ...r , value : true } ) ) ,
1055- context,
1056- } ) ,
1057- ) ;
1058-
1059- const evaluatedConfigs = evaluated . reduce (
1060- ( acc , { featureKey } ) => {
1061- const feature = featureMap [ featureKey ] ;
1062- if ( feature . config ) {
1063- const variant = evaluateFeatureRules ( {
1064- featureKey,
1065- rules : feature . config . variants . map ( ( { filter, ...rest } ) => ( {
1066- filter,
1067- value : rest ,
1068- } ) ) ,
1069- context,
1070- } ) ;
1071-
1072- if ( variant . value ) {
1073- acc [ featureKey ] = {
1074- ...variant . value ,
1075- targetingVersion : feature . config . version ,
1076- ruleEvaluationResults : variant . ruleEvaluationResults ,
1077- missingContextFields : variant . missingContextFields ,
1078- } ;
1079- }
1080- }
1081- return acc ;
1082- } ,
1083- { } as Record < string , RawFeatureRemoteConfig > ,
1084- ) ;
1065+ const evaluated = featureDefinitions . map ( ( feature ) => ( {
1066+ featureKey : feature . key ,
1067+ targetingVersion : feature . targeting . version ,
1068+ configVersion : feature . config ?. version ,
1069+ enabledResult : feature . enabledEvaluator ( context , feature . key ) ,
1070+ configResult :
1071+ feature . configEvaluator ?.( context , feature . key ) ??
1072+ ( {
1073+ featureKey : feature . key ,
1074+ context,
1075+ value : undefined ,
1076+ ruleEvaluationResults : [ ] ,
1077+ missingContextFields : [ ] ,
1078+ } satisfies EvaluationResult < any > ) ,
1079+ } ) ) ;
10851080
10861081 this . warnMissingFeatureContextFields (
10871082 context ,
1088- evaluated . map ( ( { featureKey, missingContextFields } ) => ( {
1083+ evaluated . map ( ( { featureKey, enabledResult , configResult } ) => ( {
10891084 key : featureKey ,
1090- missingContextFields,
1085+ missingContextFields : enabledResult . missingContextFields ?? [ ] ,
1086+ config : {
1087+ key : configResult . value ,
1088+ missingContextFields : configResult . missingContextFields ?? [ ] ,
1089+ } ,
10911090 } ) ) ,
10921091 ) ;
10931092
@@ -1099,23 +1098,23 @@ export class BucketClient {
10991098 this . sendFeatureEvent ( {
11001099 action : "evaluate" ,
11011100 key : res . featureKey ,
1102- targetingVersion : featureMap [ res . featureKey ] . targeting . version ,
1103- evalResult : res . value ?? false ,
1104- evalContext : res . context ,
1105- evalRuleResults : res . ruleEvaluationResults ,
1106- evalMissingFields : res . missingContextFields ,
1101+ targetingVersion : res . targetingVersion ,
1102+ evalResult : res . enabledResult . value ?? false ,
1103+ evalContext : res . enabledResult . context ,
1104+ evalRuleResults : res . enabledResult . ruleEvaluationResults ,
1105+ evalMissingFields : res . enabledResult . missingContextFields ,
11071106 } ) ,
11081107 ) ;
11091108
1110- const config = evaluatedConfigs [ res . featureKey ] ;
1111- if ( config ) {
1109+ const config = res . configResult ;
1110+ if ( config . value ) {
11121111 outPromises . push (
11131112 this . sendFeatureEvent ( {
11141113 action : "evaluate-config" ,
11151114 key : res . featureKey ,
1116- targetingVersion : config . targetingVersion ,
1117- evalResult : { key : config . key , payload : config . payload } ,
1118- evalContext : res . context ,
1115+ targetingVersion : res . configVersion ,
1116+ evalResult : config . value ,
1117+ evalContext : config . context ,
11191118 evalRuleResults : config . ruleEvaluationResults ,
11201119 evalMissingFields : config . missingContextFields ,
11211120 } ) ,
@@ -1144,11 +1143,17 @@ export class BucketClient {
11441143 ( acc , res ) => {
11451144 acc [ res . featureKey as keyof TypedFeatures ] = {
11461145 key : res . featureKey ,
1147- isEnabled : res . value ?? false ,
1148- config : evaluatedConfigs [ res . featureKey ] ,
1149- ruleEvaluationResults : res . ruleEvaluationResults ,
1150- missingContextFields : res . missingContextFields ,
1151- targetingVersion : featureMap [ res . featureKey ] . targeting . version ,
1146+ isEnabled : res . enabledResult . value ?? false ,
1147+ ruleEvaluationResults : res . enabledResult . ruleEvaluationResults ,
1148+ missingContextFields : res . enabledResult . missingContextFields ,
1149+ targetingVersion : res . targetingVersion ,
1150+ config : {
1151+ key : res . configResult ?. value ?. key ,
1152+ payload : res . configResult ?. value ?. payload ,
1153+ targetingVersion : res . configVersion ,
1154+ ruleEvaluationResults : res . configResult ?. ruleEvaluationResults ,
1155+ missingContextFields : res . configResult ?. missingContextFields ,
1156+ } ,
11521157 } ;
11531158 return acc ;
11541159 } ,
0 commit comments