@splitsoftware/splitio-commons 2.5.0-rc.0 → 2.5.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.
Files changed (41) hide show
  1. package/CHANGES.txt +3 -3
  2. package/cjs/evaluator/convertions/index.js +9 -1
  3. package/cjs/evaluator/matchersTransform/index.js +2 -3
  4. package/cjs/sdkClient/sdkClientMethodCS.js +5 -1
  5. package/cjs/sdkFactory/index.js +8 -2
  6. package/cjs/storages/getRolloutPlan.js +69 -0
  7. package/cjs/storages/inLocalStorage/validateCache.js +2 -2
  8. package/cjs/storages/inMemory/InMemoryStorageCS.js +14 -32
  9. package/cjs/storages/setRolloutPlan.js +66 -0
  10. package/cjs/utils/inputValidation/index.js +1 -3
  11. package/cjs/utils/settingsValidation/index.js +4 -0
  12. package/esm/evaluator/convertions/index.js +7 -0
  13. package/esm/evaluator/matchersTransform/index.js +3 -4
  14. package/esm/sdkClient/sdkClientMethodCS.js +5 -1
  15. package/esm/sdkFactory/index.js +8 -2
  16. package/esm/storages/getRolloutPlan.js +65 -0
  17. package/esm/storages/inLocalStorage/validateCache.js +2 -2
  18. package/esm/storages/inMemory/InMemoryStorageCS.js +14 -32
  19. package/esm/storages/setRolloutPlan.js +61 -0
  20. package/esm/utils/inputValidation/index.js +0 -1
  21. package/esm/utils/settingsValidation/index.js +4 -0
  22. package/package.json +1 -1
  23. package/src/evaluator/convertions/index.ts +10 -0
  24. package/src/evaluator/matchersTransform/index.ts +3 -4
  25. package/src/sdkClient/sdkClientMethodCS.ts +7 -1
  26. package/src/sdkFactory/index.ts +9 -2
  27. package/src/storages/getRolloutPlan.ts +72 -0
  28. package/src/storages/inLocalStorage/validateCache.ts +2 -2
  29. package/src/storages/inMemory/InMemoryStorageCS.ts +14 -37
  30. package/src/storages/setRolloutPlan.ts +71 -0
  31. package/src/storages/types.ts +20 -2
  32. package/src/types.ts +2 -0
  33. package/src/utils/inputValidation/index.ts +0 -1
  34. package/src/utils/settingsValidation/index.ts +4 -0
  35. package/types/splitio.d.ts +30 -35
  36. package/cjs/storages/dataLoader.js +0 -109
  37. package/cjs/utils/inputValidation/preloadedData.js +0 -59
  38. package/esm/storages/dataLoader.js +0 -104
  39. package/esm/utils/inputValidation/preloadedData.js +0 -55
  40. package/src/storages/dataLoader.ts +0 -113
  41. package/src/utils/inputValidation/preloadedData.ts +0 -57
@@ -0,0 +1,61 @@
1
+ import { isObject } from '../utils/lang';
2
+ import { isConsumerMode } from '../utils/settingsValidation/mode';
3
+ /**
4
+ * Validates if the given rollout plan is valid.
5
+ */
6
+ export function validateRolloutPlan(log, settings) {
7
+ var mode = settings.mode, initialRolloutPlan = settings.initialRolloutPlan;
8
+ if (isConsumerMode(mode)) {
9
+ log.warn('storage: initial rollout plan is ignored in consumer mode');
10
+ return;
11
+ }
12
+ if (isObject(initialRolloutPlan) && isObject(initialRolloutPlan.splitChanges))
13
+ return initialRolloutPlan;
14
+ log.error('storage: invalid rollout plan provided');
15
+ return;
16
+ }
17
+ /**
18
+ * Sets the given synchronous storage with the provided rollout plan snapshot.
19
+ * If `matchingKey` is provided, the storage is handled as a client-side storage (segments and largeSegments are instances of MySegmentsCache).
20
+ * Otherwise, the storage is handled as a server-side storage (segments is an instance of SegmentsCache).
21
+ */
22
+ export function setRolloutPlan(log, rolloutPlan, storage, matchingKey) {
23
+ var splits = storage.splits, rbSegments = storage.rbSegments, segments = storage.segments, largeSegments = storage.largeSegments;
24
+ var _a = rolloutPlan.splitChanges, ff = _a.ff, rbs = _a.rbs;
25
+ log.debug("storage: set feature flags and segments" + (matchingKey ? " for key " + matchingKey : ''));
26
+ if (splits && ff) {
27
+ splits.clear();
28
+ splits.update(ff.d, [], ff.t);
29
+ }
30
+ if (rbSegments && rbs) {
31
+ rbSegments.clear();
32
+ rbSegments.update(rbs.d, [], rbs.t);
33
+ }
34
+ var segmentChanges = rolloutPlan.segmentChanges;
35
+ if (matchingKey) { // add memberships data (client-side)
36
+ var memberships = rolloutPlan.memberships && rolloutPlan.memberships[matchingKey];
37
+ if (!memberships && segmentChanges) {
38
+ memberships = {
39
+ ms: {
40
+ k: segmentChanges.filter(function (segment) {
41
+ return segment.added.indexOf(matchingKey) > -1;
42
+ }).map(function (segment) { return ({ n: segment.name }); })
43
+ }
44
+ };
45
+ }
46
+ if (memberships) {
47
+ if (memberships.ms)
48
+ segments.resetSegments(memberships.ms);
49
+ if (memberships.ls && largeSegments)
50
+ largeSegments.resetSegments(memberships.ls);
51
+ }
52
+ }
53
+ else { // add segments data (server-side)
54
+ if (segmentChanges) {
55
+ segments.clear();
56
+ segmentChanges.forEach(function (segment) {
57
+ segments.update(segment.name, segment.added, segment.removed, segment.till);
58
+ });
59
+ }
60
+ }
61
+ }
@@ -10,5 +10,4 @@ export { validateTrafficType } from './trafficType';
10
10
  export { validateIfNotDestroyed, validateIfOperational } from './isOperational';
11
11
  export { validateSplitExistence } from './splitExistence';
12
12
  export { validateTrafficTypeExistence } from './trafficTypeExistence';
13
- export { validatePreloadedData } from './preloadedData';
14
13
  export { validateEvaluationOptions } from './eventProperties';
@@ -5,6 +5,7 @@ import { STANDALONE_MODE, OPTIMIZED, LOCALHOST_MODE, DEBUG, FLAG_SPEC_VERSION }
5
5
  import { validImpressionsMode } from './impressionsMode';
6
6
  import { validateKey } from '../inputValidation/key';
7
7
  import { ERROR_MIN_CONFIG_PARAM, LOG_PREFIX_CLIENT_INSTANTIATION } from '../../logger/constants';
8
+ import { validateRolloutPlan } from '../../storages/setRolloutPlan';
8
9
  // Exported for telemetry
9
10
  export var base = {
10
11
  // Define which kind of object you want to retrieve from SplitFactory
@@ -128,6 +129,9 @@ export function settingsValidation(config, validationParams) {
128
129
  // @ts-ignore, modify readonly prop
129
130
  if (storage)
130
131
  withDefaults.storage = storage(withDefaults);
132
+ // @ts-ignore, modify readonly prop
133
+ if (withDefaults.initialRolloutPlan)
134
+ withDefaults.initialRolloutPlan = validateRolloutPlan(log, withDefaults);
131
135
  // Validate key and TT (for client-side)
132
136
  var maybeKey = withDefaults.core.key;
133
137
  if (validationParams.acceptKey) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@splitsoftware/splitio-commons",
3
- "version": "2.5.0-rc.0",
3
+ "version": "2.5.0",
4
4
  "description": "Split JavaScript SDK common components",
5
5
  "main": "cjs/index.js",
6
6
  "module": "esm/index.js",
@@ -1,3 +1,5 @@
1
+ import { IBetweenMatcherData } from '../../dtos/types';
2
+
1
3
  export function zeroSinceHH(millisSinceEpoch: number): number {
2
4
  return new Date(millisSinceEpoch).setUTCHours(0, 0, 0, 0);
3
5
  }
@@ -5,3 +7,11 @@ export function zeroSinceHH(millisSinceEpoch: number): number {
5
7
  export function zeroSinceSS(millisSinceEpoch: number): number {
6
8
  return new Date(millisSinceEpoch).setUTCSeconds(0, 0);
7
9
  }
10
+
11
+ export function betweenDateTimeTransform(betweenMatcherData: IBetweenMatcherData): IBetweenMatcherData {
12
+ return {
13
+ dataType: betweenMatcherData.dataType,
14
+ start: zeroSinceSS(betweenMatcherData.start),
15
+ end: zeroSinceSS(betweenMatcherData.end)
16
+ };
17
+ }
@@ -3,7 +3,7 @@ import { matcherTypes, matcherTypesMapper, matcherDataTypes } from '../matchers/
3
3
  import { segmentTransform } from './segment';
4
4
  import { whitelistTransform } from './whitelist';
5
5
  import { numericTransform } from './unaryNumeric';
6
- import { zeroSinceHH, zeroSinceSS } from '../convertions';
6
+ import { zeroSinceHH, zeroSinceSS, betweenDateTimeTransform } from '../convertions';
7
7
  import { IBetweenMatcherData, IInLargeSegmentMatcherData, IInSegmentMatcherData, ISplitMatcher, IUnaryNumericMatcherData } from '../../dtos/types';
8
8
  import { IMatcherDto } from '../types';
9
9
 
@@ -32,7 +32,7 @@ export function matchersTransform(matchers: ISplitMatcher[]): IMatcherDto[] {
32
32
  let type = matcherTypesMapper(matcherType);
33
33
  // As default input data type we use string (even for ALL_KEYS)
34
34
  let dataType = matcherDataTypes.STRING;
35
- let value = undefined;
35
+ let value;
36
36
 
37
37
  if (type === matcherTypes.IN_SEGMENT) {
38
38
  value = segmentTransform(userDefinedSegmentMatcherData as IInSegmentMatcherData);
@@ -60,8 +60,7 @@ export function matchersTransform(matchers: ISplitMatcher[]): IMatcherDto[] {
60
60
  dataType = matcherDataTypes.NUMBER;
61
61
 
62
62
  if (value.dataType === 'DATETIME') {
63
- value.start = zeroSinceSS(value.start);
64
- value.end = zeroSinceSS(value.end);
63
+ value = betweenDateTimeTransform(value);
65
64
  dataType = matcherDataTypes.DATETIME;
66
65
  }
67
66
  } else if (type === matcherTypes.BETWEEN_SEMVER) {
@@ -9,13 +9,15 @@ import { RETRIEVE_CLIENT_DEFAULT, NEW_SHARED_CLIENT, RETRIEVE_CLIENT_EXISTING, L
9
9
  import { SDK_SEGMENTS_ARRIVED } from '../readiness/constants';
10
10
  import { ISdkFactoryContext } from '../sdkFactory/types';
11
11
  import { buildInstanceId } from './identity';
12
+ import { setRolloutPlan } from '../storages/setRolloutPlan';
13
+ import { ISegmentsCacheSync } from '../storages/types';
12
14
 
13
15
  /**
14
16
  * Factory of client method for the client-side API variant where TT is ignored.
15
17
  * Therefore, clients don't have a bound TT for the track method.
16
18
  */
17
19
  export function sdkClientMethodCSFactory(params: ISdkFactoryContext): (key?: SplitIO.SplitKey) => SplitIO.IBrowserClient {
18
- const { clients, storage, syncManager, sdkReadinessManager, settings: { core: { key }, log } } = params;
20
+ const { clients, storage, syncManager, sdkReadinessManager, settings: { core: { key }, log, initialRolloutPlan } } = params;
19
21
 
20
22
  const mainClientInstance = clientCSDecorator(
21
23
  log,
@@ -56,6 +58,10 @@ export function sdkClientMethodCSFactory(params: ISdkFactoryContext): (key?: Spl
56
58
  sharedSdkReadiness.readinessManager.segments.emit(SDK_SEGMENTS_ARRIVED);
57
59
  });
58
60
 
61
+ if (sharedStorage && initialRolloutPlan) {
62
+ setRolloutPlan(log, initialRolloutPlan, { segments: sharedStorage.segments as ISegmentsCacheSync, largeSegments: sharedStorage.largeSegments as ISegmentsCacheSync }, matchingKey);
63
+ }
64
+
59
65
  // 3 possibilities:
60
66
  // - Standalone mode: both syncManager and sharedSyncManager are defined
61
67
  // - Consumer mode: both syncManager and sharedSyncManager are undefined
@@ -14,6 +14,9 @@ import { strategyOptimizedFactory } from '../trackers/strategy/strategyOptimized
14
14
  import { strategyNoneFactory } from '../trackers/strategy/strategyNone';
15
15
  import { uniqueKeysTrackerFactory } from '../trackers/uniqueKeysTracker';
16
16
  import { DEBUG, OPTIMIZED } from '../utils/constants';
17
+ import { setRolloutPlan } from '../storages/setRolloutPlan';
18
+ import { IStorageSync } from '../storages/types';
19
+ import { getMatching } from '../utils/key';
17
20
 
18
21
  /**
19
22
  * Modular SDK factory
@@ -24,7 +27,7 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ISDK | SplitIO.IA
24
27
  syncManagerFactory, SignalListener, impressionsObserverFactory,
25
28
  integrationsManagerFactory, sdkManagerFactory, sdkClientMethodFactory,
26
29
  filterAdapterFactory, lazyInit } = params;
27
- const { log, sync: { impressionsMode } } = settings;
30
+ const { log, sync: { impressionsMode }, initialRolloutPlan, core: { key } } = settings;
28
31
 
29
32
  // @TODO handle non-recoverable errors, such as, global `fetch` not available, invalid SDK Key, etc.
30
33
  // On non-recoverable errors, we should mark the SDK as destroyed and not start synchronization.
@@ -57,7 +60,11 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ISDK | SplitIO.IA
57
60
  }
58
61
  });
59
62
 
60
- // @TODO add support for dataloader: `if (params.dataLoader) params.dataLoader(storage);`
63
+ if (initialRolloutPlan) {
64
+ setRolloutPlan(log, initialRolloutPlan, storage as IStorageSync, key && getMatching(key));
65
+ if ((storage as IStorageSync).splits.getChangeNumber() > -1) readiness.splits.emit(SDK_SPLITS_CACHE_LOADED);
66
+ }
67
+
61
68
  const clients: Record<string, SplitIO.IBasicClient> = {};
62
69
  const telemetryTracker = telemetryTrackerFactory(storage.telemetry, platform.now);
63
70
  const integrationsManager = integrationsManagerFactory && integrationsManagerFactory({ settings, storage, telemetryTracker });
@@ -0,0 +1,72 @@
1
+ import SplitIO from '../../types/splitio';
2
+ import { IStorageSync } from './types';
3
+ import { setToArray } from '../utils/lang/sets';
4
+ import { getMatching } from '../utils/key';
5
+ import { ILogger } from '../logger/types';
6
+ import { RolloutPlan } from './types';
7
+ import { IMembershipsResponse, IMySegmentsResponse } from '../dtos/types';
8
+
9
+ /**
10
+ * Gets the rollout plan snapshot from the given synchronous storage.
11
+ */
12
+ export function getRolloutPlan(log: ILogger, storage: IStorageSync, options: SplitIO.RolloutPlanOptions = {}): RolloutPlan {
13
+
14
+ const { keys, exposeSegments } = options;
15
+ const { splits, segments, rbSegments } = storage;
16
+
17
+ log.debug(`storage: get feature flags${keys ? `, and memberships for keys: ${keys}` : ''}${exposeSegments ? ', and segments' : ''}`);
18
+
19
+ return {
20
+ splitChanges: {
21
+ ff: {
22
+ t: splits.getChangeNumber(),
23
+ s: -1,
24
+ d: splits.getAll(),
25
+ },
26
+ rbs: {
27
+ t: rbSegments.getChangeNumber(),
28
+ s: -1,
29
+ d: rbSegments.getAll(),
30
+ }
31
+ },
32
+ segmentChanges: exposeSegments ? // @ts-ignore accessing private prop
33
+ Object.keys(segments.segmentCache).map(segmentName => ({
34
+ name: segmentName, // @ts-ignore
35
+ added: setToArray(segments.segmentCache[segmentName] as Set<string>),
36
+ removed: [],
37
+ since: -1,
38
+ till: segments.getChangeNumber(segmentName)!
39
+ })) :
40
+ undefined,
41
+ memberships: keys ?
42
+ keys.reduce<Record<string, IMembershipsResponse>>((prev, key) => {
43
+ const matchingKey = getMatching(key);
44
+ if (storage.shared) { // Client-side segments
45
+ const sharedStorage = storage.shared(matchingKey);
46
+ prev[matchingKey] = {
47
+ ms: { // @ts-ignore
48
+ k: Object.keys(sharedStorage.segments.segmentCache).map(segmentName => ({ n: segmentName })),
49
+ },
50
+ ls: sharedStorage.largeSegments ? { // @ts-ignore
51
+ k: Object.keys(sharedStorage.largeSegments.segmentCache).map(segmentName => ({ n: segmentName })),
52
+ } : undefined
53
+ };
54
+ } else { // Server-side segments
55
+ prev[matchingKey] = {
56
+ ms: { // @ts-ignore
57
+ k: Object.keys(storage.segments.segmentCache).reduce<IMySegmentsResponse['k']>((prev, segmentName) => { // @ts-ignore
58
+ return storage.segments.segmentCache[segmentName].has(matchingKey) ?
59
+ prev!.concat({ n: segmentName }) :
60
+ prev;
61
+ }, [])
62
+ },
63
+ ls: {
64
+ k: []
65
+ }
66
+ };
67
+ }
68
+ return prev;
69
+ }, {}) :
70
+ undefined
71
+ };
72
+ }
@@ -17,7 +17,7 @@ const MILLIS_IN_A_DAY = 86400000;
17
17
  * @returns `true` if cache should be cleared, `false` otherwise
18
18
  */
19
19
  function validateExpiration(options: SplitIO.InLocalStorageOptions, settings: ISettings, keys: KeyBuilderCS, currentTimestamp: number, isThereCache: boolean) {
20
- const { log } = settings;
20
+ const { log, initialRolloutPlan } = settings;
21
21
 
22
22
  // Check expiration
23
23
  const lastUpdatedTimestamp = parseInt(localStorage.getItem(keys.buildLastUpdatedKey()) as string, 10);
@@ -41,7 +41,7 @@ function validateExpiration(options: SplitIO.InLocalStorageOptions, settings: IS
41
41
  } catch (e) {
42
42
  log.error(LOG_PREFIX + e);
43
43
  }
44
- if (isThereCache) {
44
+ if (isThereCache && !initialRolloutPlan) {
45
45
  log.info(LOG_PREFIX + 'SDK key, flags filter criteria, or flags spec version has changed. Cleaning up cache');
46
46
  return true;
47
47
  }
@@ -7,8 +7,6 @@ import { ImpressionCountsCacheInMemory } from './ImpressionCountsCacheInMemory';
7
7
  import { LOCALHOST_MODE, STORAGE_MEMORY } from '../../utils/constants';
8
8
  import { shouldRecordTelemetry, TelemetryCacheInMemory } from './TelemetryCacheInMemory';
9
9
  import { UniqueKeysCacheInMemoryCS } from './UniqueKeysCacheInMemoryCS';
10
- import { getMatching } from '../../utils/key';
11
- import { setCache } from '../dataLoader';
12
10
  import { RBSegmentsCacheInMemory } from './RBSegmentsCacheInMemory';
13
11
 
14
12
  /**
@@ -17,9 +15,7 @@ import { RBSegmentsCacheInMemory } from './RBSegmentsCacheInMemory';
17
15
  * @param params - parameters required by EventsCacheSync
18
16
  */
19
17
  export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorageSync {
20
- const { settings: { log, scheduler: { impressionsQueueSize, eventsQueueSize }, sync: { __splitFiltersValidation }, preloadedData }, onReadyFromCacheCb } = params;
21
-
22
- const storages: Record<string, IStorageSync> = {};
18
+ const { settings: { scheduler: { impressionsQueueSize, eventsQueueSize }, sync: { __splitFiltersValidation } } } = params;
23
19
 
24
20
  const splits = new SplitsCacheInMemory(__splitFiltersValidation);
25
21
  const rbSegments = new RBSegmentsCacheInMemory();
@@ -40,31 +36,20 @@ export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorag
40
36
  destroy() { },
41
37
 
42
38
  // When using shared instantiation with MEMORY we reuse everything but segments (they are unique per key)
43
- shared(matchingKey: string) {
44
- if (!storages[matchingKey]) {
45
- const segments = new MySegmentsCacheInMemory();
46
- const largeSegments = new MySegmentsCacheInMemory();
47
-
48
- if (preloadedData) {
49
- setCache(log, preloadedData, { segments, largeSegments }, matchingKey);
50
- }
51
-
52
- storages[matchingKey] = {
53
- splits: this.splits,
54
- rbSegments: this.rbSegments,
55
- segments,
56
- largeSegments,
57
- impressions: this.impressions,
58
- impressionCounts: this.impressionCounts,
59
- events: this.events,
60
- telemetry: this.telemetry,
61
- uniqueKeys: this.uniqueKeys,
62
-
63
- destroy() { }
64
- };
65
- }
39
+ shared() {
40
+ return {
41
+ splits: this.splits,
42
+ rbSegments: this.rbSegments,
43
+ segments: new MySegmentsCacheInMemory(),
44
+ largeSegments: new MySegmentsCacheInMemory(),
45
+ impressions: this.impressions,
46
+ impressionCounts: this.impressionCounts,
47
+ events: this.events,
48
+ telemetry: this.telemetry,
49
+ uniqueKeys: this.uniqueKeys,
66
50
 
67
- return storages[matchingKey];
51
+ destroy() { }
52
+ };
68
53
  },
69
54
  };
70
55
 
@@ -78,14 +63,6 @@ export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorag
78
63
  storage.uniqueKeys.track = noopTrack;
79
64
  }
80
65
 
81
- const matchingKey = getMatching(params.settings.core.key);
82
- storages[matchingKey] = storage;
83
-
84
- if (preloadedData) {
85
- setCache(log, preloadedData, storage, matchingKey);
86
- if (splits.getChangeNumber() > -1) onReadyFromCacheCb();
87
- }
88
-
89
66
  return storage;
90
67
  }
91
68
 
@@ -0,0 +1,71 @@
1
+ import SplitIO from '../../types/splitio';
2
+ import { IRBSegmentsCacheSync, ISegmentsCacheSync, ISplitsCacheSync } from './types';
3
+ import { ILogger } from '../logger/types';
4
+ import { isObject } from '../utils/lang';
5
+ import { isConsumerMode } from '../utils/settingsValidation/mode';
6
+ import { RolloutPlan } from './types';
7
+
8
+ /**
9
+ * Validates if the given rollout plan is valid.
10
+ */
11
+ export function validateRolloutPlan(log: ILogger, settings: SplitIO.ISettings): RolloutPlan | undefined {
12
+ const { mode, initialRolloutPlan } = settings;
13
+
14
+ if (isConsumerMode(mode)) {
15
+ log.warn('storage: initial rollout plan is ignored in consumer mode');
16
+ return;
17
+ }
18
+
19
+ if (isObject(initialRolloutPlan) && isObject((initialRolloutPlan as any).splitChanges)) return initialRolloutPlan as RolloutPlan;
20
+
21
+ log.error('storage: invalid rollout plan provided');
22
+ return;
23
+ }
24
+
25
+ /**
26
+ * Sets the given synchronous storage with the provided rollout plan snapshot.
27
+ * If `matchingKey` is provided, the storage is handled as a client-side storage (segments and largeSegments are instances of MySegmentsCache).
28
+ * Otherwise, the storage is handled as a server-side storage (segments is an instance of SegmentsCache).
29
+ */
30
+ export function setRolloutPlan(log: ILogger, rolloutPlan: RolloutPlan, storage: { splits?: ISplitsCacheSync, rbSegments?: IRBSegmentsCacheSync, segments: ISegmentsCacheSync, largeSegments?: ISegmentsCacheSync }, matchingKey?: string) {
31
+ const { splits, rbSegments, segments, largeSegments } = storage;
32
+ const { splitChanges: { ff, rbs } } = rolloutPlan;
33
+
34
+ log.debug(`storage: set feature flags and segments${matchingKey ? ` for key ${matchingKey}` : ''}`);
35
+
36
+ if (splits && ff) {
37
+ splits.clear();
38
+ splits.update(ff.d, [], ff.t);
39
+ }
40
+
41
+ if (rbSegments && rbs) {
42
+ rbSegments.clear();
43
+ rbSegments.update(rbs.d, [], rbs.t);
44
+ }
45
+
46
+ const segmentChanges = rolloutPlan.segmentChanges;
47
+ if (matchingKey) { // add memberships data (client-side)
48
+ let memberships = rolloutPlan.memberships && rolloutPlan.memberships[matchingKey];
49
+ if (!memberships && segmentChanges) {
50
+ memberships = {
51
+ ms: {
52
+ k: segmentChanges.filter(segment => {
53
+ return segment.added.indexOf(matchingKey) > -1;
54
+ }).map(segment => ({ n: segment.name }))
55
+ }
56
+ };
57
+ }
58
+
59
+ if (memberships) {
60
+ if (memberships.ms) segments.resetSegments(memberships.ms!);
61
+ if (memberships.ls && largeSegments) largeSegments.resetSegments(memberships.ls!);
62
+ }
63
+ } else { // add segments data (server-side)
64
+ if (segmentChanges) {
65
+ segments.clear();
66
+ segmentChanges.forEach(segment => {
67
+ segments.update(segment.name, segment.added, segment.removed, segment.till);
68
+ });
69
+ }
70
+ }
71
+ }
@@ -1,5 +1,5 @@
1
1
  import SplitIO from '../../types/splitio';
2
- import { MaybeThenable, ISplit, IRBSegment, IMySegmentsResponse } from '../dtos/types';
2
+ import { MaybeThenable, ISplit, IRBSegment, IMySegmentsResponse, IMembershipsResponse, ISegmentChangesResponse, ISplitChangesResponse } from '../dtos/types';
3
3
  import { MySegmentsData } from '../sync/polling/types';
4
4
  import { EventDataType, HttpErrors, HttpLatencies, ImpressionDataType, LastSync, Method, MethodExceptions, MethodLatencies, MultiMethodExceptions, MultiMethodLatencies, MultiConfigs, OperationType, StoredEventWithMetadata, StoredImpressionWithMetadata, StreamingEvent, UniqueKeysPayloadCs, UniqueKeysPayloadSs, TelemetryUsageStatsPayload, UpdatesFromSSEEnum } from '../sync/submitters/types';
5
5
  import { ISettings } from '../types';
@@ -505,7 +505,7 @@ export interface IStorageFactoryParams {
505
505
  */
506
506
  onReadyCb: (error?: any) => void,
507
507
  /**
508
- * For emitting SDK_READY_FROM_CACHE event in consumer mode with Redis and standalone mode with preloaded data
508
+ * For emitting SDK_READY_FROM_CACHE event in consumer mode with Redis to allow immediate evaluations
509
509
  */
510
510
  onReadyFromCacheCb: () => void,
511
511
  }
@@ -520,3 +520,21 @@ export type IStorageAsyncFactory = SplitIO.StorageAsyncFactory & {
520
520
  readonly type: SplitIO.StorageType,
521
521
  (params: IStorageFactoryParams): IStorageAsync
522
522
  }
523
+
524
+ export type RolloutPlan = {
525
+ /**
526
+ * Feature flags and rule-based segments.
527
+ */
528
+ splitChanges: ISplitChangesResponse;
529
+ /**
530
+ * Optional map of matching keys to their memberships.
531
+ */
532
+ memberships?: {
533
+ [matchingKey: string]: IMembershipsResponse;
534
+ };
535
+ /**
536
+ * Optional list of standard segments.
537
+ * This property is ignored if `memberships` is provided.
538
+ */
539
+ segmentChanges?: ISegmentChangesResponse[];
540
+ };
package/src/types.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import SplitIO from '../types/splitio';
2
2
  import { ISplitFiltersValidation } from './dtos/types';
3
3
  import { ILogger } from './logger/types';
4
+ import { RolloutPlan } from './storages/types';
4
5
 
5
6
  /**
6
7
  * SplitIO.ISettings interface extended with private properties for internal use
@@ -10,6 +11,7 @@ export interface ISettings extends SplitIO.ISettings {
10
11
  __splitFiltersValidation: ISplitFiltersValidation;
11
12
  };
12
13
  readonly log: ILogger;
14
+ readonly initialRolloutPlan?: RolloutPlan;
13
15
  }
14
16
 
15
17
  /**
@@ -10,5 +10,4 @@ export { validateTrafficType } from './trafficType';
10
10
  export { validateIfNotDestroyed, validateIfOperational } from './isOperational';
11
11
  export { validateSplitExistence } from './splitExistence';
12
12
  export { validateTrafficTypeExistence } from './trafficTypeExistence';
13
- export { validatePreloadedData } from './preloadedData';
14
13
  export { validateEvaluationOptions } from './eventProperties';
@@ -7,6 +7,7 @@ import { ISettingsValidationParams } from './types';
7
7
  import { ISettings } from '../../types';
8
8
  import { validateKey } from '../inputValidation/key';
9
9
  import { ERROR_MIN_CONFIG_PARAM, LOG_PREFIX_CLIENT_INSTANTIATION } from '../../logger/constants';
10
+ import { validateRolloutPlan } from '../../storages/setRolloutPlan';
10
11
 
11
12
  // Exported for telemetry
12
13
  export const base = {
@@ -152,6 +153,9 @@ export function settingsValidation(config: unknown, validationParams: ISettingsV
152
153
  // @ts-ignore, modify readonly prop
153
154
  if (storage) withDefaults.storage = storage(withDefaults);
154
155
 
156
+ // @ts-ignore, modify readonly prop
157
+ if (withDefaults.initialRolloutPlan) withDefaults.initialRolloutPlan = validateRolloutPlan(log, withDefaults);
158
+
155
159
  // Validate key and TT (for client-side)
156
160
  const maybeKey = withDefaults.core.key;
157
161
  if (validationParams.acceptKey) {
@@ -351,9 +351,10 @@ interface IClientSideSyncSharedSettings extends IClientSideSharedSettings, ISync
351
351
  */
352
352
  features?: SplitIO.MockedFeaturesMap;
353
353
  /**
354
- * Data to initialize the SDK storage with. If provided and valid, the SDK will be ready from cache immediately.
354
+ * Rollout plan object (i.e., feature flags and segment definitions) to initialize the SDK storage with. If provided and valid, the SDK will be ready from cache immediately.
355
+ * This object is derived from calling the Node.js SDK’s `getRolloutPlan` method.
355
356
  */
356
- preloadedData?: SplitIO.PreloadedData;
357
+ initialRolloutPlan?: SplitIO.RolloutPlan;
357
358
  /**
358
359
  * SDK Startup settings.
359
360
  */
@@ -559,7 +560,7 @@ declare namespace SplitIO {
559
560
  eventsFirstPushWindow: number;
560
561
  };
561
562
  readonly storage: StorageSyncFactory | StorageAsyncFactory | StorageOptions;
562
- readonly preloadedData?: SplitIO.PreloadedData;
563
+ readonly initialRolloutPlan?: SplitIO.RolloutPlan;
563
564
  readonly urls: {
564
565
  events: string;
565
566
  sdk: string;
@@ -1025,41 +1026,28 @@ declare namespace SplitIO {
1025
1026
  type: NodeSyncStorage | NodeAsyncStorage | BrowserStorage;
1026
1027
  prefix?: string;
1027
1028
  options?: Object;
1028
- }
1029
+ };
1029
1030
  /**
1030
- * Defines the format of rollout plan data to preload the factory storage (cache).
1031
+ * A JSON-serializable plain object that defines the format of rollout plan data to preload the SDK cache with feature flags and segments.
1031
1032
  */
1032
- type PreloadedData = {
1033
- /**
1034
- * Change number of feature flags.
1035
- */
1036
- since: number;
1037
- /**
1038
- * List of feature flags.
1039
- */
1040
- flags: Object[],
1041
- /**
1042
- * Change number of rule-based segments.
1043
- */
1044
- rbSince?: number,
1045
- /**
1046
- * List of rule-based segments.
1047
- */
1048
- rbSegments?: Object[],
1033
+ type RolloutPlan = Object;
1034
+ /**
1035
+ * Options for the `factory.getRolloutPlan` method.
1036
+ */
1037
+ type RolloutPlanOptions = {
1049
1038
  /**
1050
- * Optional map of user keys to their memberships.
1039
+ * Optional list of keys to generate the rollout plan snapshot with the memberships of the given keys.
1040
+ *
1041
+ * @defaultValue `undefined`
1051
1042
  */
1052
- memberships?: {
1053
- [key: string]: Object
1054
- },
1043
+ keys?: SplitKey[];
1055
1044
  /**
1056
- * Optional map of segments to their list of keys.
1057
- * This property is ignored if `memberships` is provided.
1045
+ * Optional flag to expose segments data in the rollout plan snapshot.
1046
+ *
1047
+ * @defaultValue `false`
1058
1048
  */
1059
- segments?: {
1060
- [segmentName: string]: string[]
1061
- },
1062
- }
1049
+ exposeSegments?: boolean;
1050
+ };
1063
1051
  /**
1064
1052
  * Impression listener interface. This is the interface that needs to be implemented
1065
1053
  * by the element you provide to the SDK as impression listener.
@@ -1082,7 +1070,7 @@ declare namespace SplitIO {
1082
1070
  type IntegrationFactory = {
1083
1071
  readonly type: string;
1084
1072
  (params: any): (Integration | void);
1085
- }
1073
+ };
1086
1074
  /**
1087
1075
  * A pair of user key and it's trafficType, required for tracking valid Split events.
1088
1076
  */
@@ -1606,10 +1594,17 @@ declare namespace SplitIO {
1606
1594
  /**
1607
1595
  * Returns the current snapshot of the SDK rollout plan in cache.
1608
1596
  *
1609
- * @param keys - Optional list of keys to generate the rollout plan snapshot with the memberships of the given keys, rather than the complete segments data.
1597
+ * Wait for the SDK client to be ready before calling this method.
1598
+ *
1599
+ * ```js
1600
+ * await factory.client().ready();
1601
+ * const rolloutPlan = factory.getRolloutPlan();
1602
+ * ```
1603
+ *
1604
+ * @param options - An object of type RolloutPlanOptions for advanced options.
1610
1605
  * @returns The current snapshot of the SDK rollout plan.
1611
1606
  */
1612
- getCache(keys?: SplitKey[]): PreloadedData,
1607
+ getRolloutPlan(options?: RolloutPlanOptions): RolloutPlan;
1613
1608
  }
1614
1609
  /**
1615
1610
  * This represents the interface for the SDK instance for server-side with asynchronous storage.