11import { ISegmentsCacheBase , IStorageBase } from '../../../storages/types' ;
22import { ISplitChangesFetcher } from '../fetchers/types' ;
3- import { ISplit , ISplitChangesResponse , ISplitFiltersValidation } from '../../../dtos/types' ;
3+ import { IRBSegment , ISplit , ISplitChangesResponse , ISplitFiltersValidation , MaybeThenable } from '../../../dtos/types' ;
44import { ISplitsEventEmitter } from '../../../readiness/types' ;
55import { timeout } from '../../../utils/promise/timeout' ;
66import { SDK_SPLITS_ARRIVED , SDK_SPLITS_CACHE_LOADED } from '../../../readiness/constants' ;
77import { ILogger } from '../../../logger/types' ;
8- import { SYNC_SPLITS_FETCH , SYNC_SPLITS_UPDATE , SYNC_SPLITS_FETCH_FAILS , SYNC_SPLITS_FETCH_RETRY } from '../../../logger/constants' ;
8+ import { SYNC_SPLITS_FETCH , SYNC_SPLITS_UPDATE , SYNC_RBS_UPDATE , SYNC_SPLITS_FETCH_FAILS , SYNC_SPLITS_FETCH_RETRY } from '../../../logger/constants' ;
99import { startsWith } from '../../../utils/lang' ;
10- import { IN_SEGMENT } from '../../../utils/constants' ;
10+ import { IN_RULE_BASED_SEGMENT , IN_SEGMENT } from '../../../utils/constants' ;
1111import { setToArray } from '../../../utils/lang/sets' ;
1212
1313type ISplitChangesUpdater = ( noCache ?: boolean , till ?: number , splitUpdateNotification ?: { payload : ISplit , changeNumber : number } ) => Promise < boolean >
@@ -27,24 +27,23 @@ function checkAllSegmentsExist(segments: ISegmentsCacheBase): Promise<boolean> {
2727 * Collect segments from a raw split definition.
2828 * Exported for testing purposes.
2929 */
30- export function parseSegments ( { conditions } : ISplit ) : Set < string > {
30+ export function parseSegments ( { conditions } : ISplit | IRBSegment , matcherType : typeof IN_SEGMENT | typeof IN_RULE_BASED_SEGMENT = IN_SEGMENT ) : Set < string > {
3131 let segments = new Set < string > ( ) ;
3232
3333 for ( let i = 0 ; i < conditions . length ; i ++ ) {
3434 const matchers = conditions [ i ] . matcherGroup . matchers ;
3535
3636 matchers . forEach ( matcher => {
37- if ( matcher . matcherType === IN_SEGMENT ) segments . add ( matcher . userDefinedSegmentMatcherData . segmentName ) ;
37+ if ( matcher . matcherType === matcherType ) segments . add ( matcher . userDefinedSegmentMatcherData . segmentName ) ;
3838 } ) ;
3939 }
4040
4141 return segments ;
4242}
4343
44- interface ISplitMutations {
45- added : ISplit [ ] ,
46- removed : ISplit [ ] ,
47- segments : string [ ]
44+ interface ISplitMutations < T extends ISplit | IRBSegment > {
45+ added : T [ ] ,
46+ removed : T [ ]
4847}
4948
5049/**
@@ -68,30 +67,30 @@ function matchFilters(featureFlag: ISplit, filters: ISplitFiltersValidation) {
6867 return matchNames || matchPrefix ;
6968}
7069
70+ function isFF ( ruleBasedEntity : IRBSegment | ISplit ) : ruleBasedEntity is ISplit {
71+ return ( ruleBasedEntity as ISplit ) . defaultTreatment !== undefined ;
72+ }
73+
7174/**
7275 * Given the list of splits from /splitChanges endpoint, it returns the mutations,
7376 * i.e., an object with added splits, removed splits and used segments.
7477 * Exported for testing purposes.
7578 */
76- export function computeSplitsMutation ( entries : ISplit [ ] , filters : ISplitFiltersValidation ) : ISplitMutations {
77- const segments = new Set < string > ( ) ;
78- const computed = entries . reduce ( ( accum , split ) => {
79- if ( split . status === 'ACTIVE' && matchFilters ( split , filters ) ) {
80- accum . added . push ( split ) ;
79+ export function computeMutation < T extends ISplit | IRBSegment > ( rules : Array < T > , segments : Set < string > , filters ?: ISplitFiltersValidation ) : ISplitMutations < T > {
8180
82- parseSegments ( split ) . forEach ( ( segmentName : string ) => {
81+ return rules . reduce ( ( accum , ruleBasedEntity ) => {
82+ if ( ruleBasedEntity . status === 'ACTIVE' && ( ! filters || matchFilters ( ruleBasedEntity as ISplit , filters ) ) ) {
83+ accum . added . push ( ruleBasedEntity ) ;
84+
85+ parseSegments ( ruleBasedEntity ) . forEach ( ( segmentName : string ) => {
8386 segments . add ( segmentName ) ;
8487 } ) ;
8588 } else {
86- accum . removed . push ( split ) ;
89+ accum . removed . push ( ruleBasedEntity ) ;
8790 }
8891
8992 return accum ;
90- } , { added : [ ] , removed : [ ] , segments : [ ] } as ISplitMutations ) ;
91-
92- computed . segments = setToArray ( segments ) ;
93-
94- return computed ;
93+ } , { added : [ ] , removed : [ ] } as ISplitMutations < T > ) ;
9594}
9695
9796/**
@@ -111,14 +110,14 @@ export function computeSplitsMutation(entries: ISplit[], filters: ISplitFiltersV
111110export function splitChangesUpdaterFactory (
112111 log : ILogger ,
113112 splitChangesFetcher : ISplitChangesFetcher ,
114- storage : Pick < IStorageBase , 'splits' | 'segments' > ,
113+ storage : Pick < IStorageBase , 'splits' | 'rbSegments' | ' segments'> ,
115114 splitFiltersValidation : ISplitFiltersValidation ,
116115 splitsEventEmitter ?: ISplitsEventEmitter ,
117116 requestTimeoutBeforeReady : number = 0 ,
118117 retriesOnFailureBeforeReady : number = 0 ,
119118 isClientSide ?: boolean
120119) : ISplitChangesUpdater {
121- const { splits, segments } = storage ;
120+ const { splits, rbSegments , segments } = storage ;
122121
123122 let startingUp = true ;
124123
@@ -135,35 +134,60 @@ export function splitChangesUpdaterFactory(
135134 * @param noCache - true to revalidate data to fetch
136135 * @param till - query param to bypass CDN requests
137136 */
138- return function splitChangesUpdater ( noCache ?: boolean , till ?: number , splitUpdateNotification ?: { payload : ISplit , changeNumber : number } ) {
137+ return function splitChangesUpdater ( noCache ?: boolean , till ?: number , splitUpdateNotification ?: { payload : ISplit | IRBSegment , changeNumber : number } ) {
139138
140139 /**
141140 * @param since - current changeNumber at splitsCache
142141 * @param retry - current number of retry attempts
143142 */
144- function _splitChangesUpdater ( since : number , retry = 0 ) : Promise < boolean > {
145- log . debug ( SYNC_SPLITS_FETCH , [ since ] ) ;
146- const fetcherPromise = Promise . resolve ( splitUpdateNotification ?
147- { splits : [ splitUpdateNotification . payload ] , till : splitUpdateNotification . changeNumber } :
148- splitChangesFetcher ( since , noCache , till , _promiseDecorator )
143+ function _splitChangesUpdater ( sinces : [ number , number ] , retry = 0 ) : Promise < boolean > {
144+ const [ since , rbSince ] = sinces ;
145+ log . debug ( SYNC_SPLITS_FETCH , sinces ) ;
146+ const fetcherPromise = Promise . resolve (
147+ splitUpdateNotification ?
148+ isFF ( splitUpdateNotification . payload ) ?
149+ // IFFU edge case: a change to a flag that adds an IN_RULE_BASED_SEGMENT matcher that is not present yet
150+ Promise . resolve ( rbSegments . contains ( parseSegments ( splitUpdateNotification . payload , IN_RULE_BASED_SEGMENT ) ) ) . then ( ( contains ) => {
151+ return contains ?
152+ { ff : { d : [ splitUpdateNotification . payload as ISplit ] , t : splitUpdateNotification . changeNumber } } :
153+ splitChangesFetcher ( since , noCache , till , rbSince , _promiseDecorator ) ;
154+ } ) :
155+ { rbs : { d : [ splitUpdateNotification . payload as IRBSegment ] , t : splitUpdateNotification . changeNumber } } :
156+ splitChangesFetcher ( since , noCache , till , rbSince , _promiseDecorator )
149157 )
150158 . then ( ( splitChanges : ISplitChangesResponse ) => {
151159 startingUp = false ;
152160
153- const mutation = computeSplitsMutation ( splitChanges . splits , splitFiltersValidation ) ;
161+ const usedSegments = new Set < string > ( ) ;
162+
163+ let ffUpdate : MaybeThenable < boolean > = false ;
164+ if ( splitChanges . ff ) {
165+ const { added, removed } = computeMutation ( splitChanges . ff . d , usedSegments , splitFiltersValidation ) ;
166+ log . debug ( SYNC_SPLITS_UPDATE , [ added . length , removed . length ] ) ;
167+ ffUpdate = splits . update ( added , removed , splitChanges . ff . t ) ;
168+ }
154169
155- log . debug ( SYNC_SPLITS_UPDATE , [ mutation . added . length , mutation . removed . length , mutation . segments . length ] ) ;
170+ let rbsUpdate : MaybeThenable < boolean > = false ;
171+ if ( splitChanges . rbs ) {
172+ const { added, removed } = computeMutation ( splitChanges . rbs . d , usedSegments ) ;
173+ log . debug ( SYNC_RBS_UPDATE , [ added . length , removed . length ] ) ;
174+ rbsUpdate = rbSegments . update ( added , removed , splitChanges . rbs . t ) ;
175+ }
156176
157- // Write into storage
158- // @TODO call `setChangeNumber` only if the other storage operations have succeeded, in order to keep storage consistency
159- return Promise . all ( [
160- splits . update ( mutation . added , mutation . removed , splitChanges . till ) ,
161- segments . registerSegments ( mutation . segments )
162- ] ) . then ( ( [ isThereUpdate ] ) => {
177+ return Promise . all ( [ ffUpdate , rbsUpdate ,
178+ // @TODO if at least 1 segment fetch fails due to 404 and other segments are updated in the storage, SDK_UPDATE is not emitted
179+ segments . registerSegments ( setToArray ( usedSegments ) )
180+ ] ) . then ( ( [ ffChanged , rbsChanged ] ) => {
163181 if ( splitsEventEmitter ) {
164182 // To emit SDK_SPLITS_ARRIVED for server-side SDK, we must check that all registered segments have been fetched
165- return Promise . resolve ( ! splitsEventEmitter . splitsArrived || ( since !== splitChanges . till && isThereUpdate && ( isClientSide || checkAllSegmentsExist ( segments ) ) ) )
166- . catch ( ( ) => false /** noop. just to handle a possible `checkAllSegmentsExist` rejection, before emitting SDK event */ )
183+ return Promise . resolve ( ! splitsEventEmitter . splitsArrived ||
184+ (
185+ ( ! splitChanges . ff || since !== splitChanges . ff . t ) &&
186+ ( ! splitChanges . rbs || rbSince !== splitChanges . rbs . t ) &&
187+ ( ffChanged || rbsChanged ) &&
188+ ( isClientSide || checkAllSegmentsExist ( segments ) )
189+ )
190+ )
167191 . then ( emitSplitsArrivedEvent => {
168192 // emit SDK events
169193 if ( emitSplitsArrivedEvent ) splitsEventEmitter . emit ( SDK_SPLITS_ARRIVED ) ;
@@ -179,7 +203,7 @@ export function splitChangesUpdaterFactory(
179203 if ( startingUp && retriesOnFailureBeforeReady > retry ) {
180204 retry += 1 ;
181205 log . info ( SYNC_SPLITS_FETCH_RETRY , [ retry , error ] ) ;
182- return _splitChangesUpdater ( since , retry ) ;
206+ return _splitChangesUpdater ( sinces , retry ) ;
183207 } else {
184208 startingUp = false ;
185209 }
@@ -196,7 +220,7 @@ export function splitChangesUpdaterFactory(
196220 return fetcherPromise ;
197221 }
198222
199- let sincePromise = Promise . resolve ( splits . getChangeNumber ( ) ) ; // `getChangeNumber` never rejects or throws error
200- return sincePromise . then ( _splitChangesUpdater ) ;
223+ // `getChangeNumber` never rejects or throws error
224+ return Promise . all ( [ splits . getChangeNumber ( ) , rbSegments . getChangeNumber ( ) ] ) . then ( _splitChangesUpdater ) ;
201225 } ;
202226}
0 commit comments