@splitsoftware/splitio-commons 2.1.0 → 2.1.1-rc.1

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 (77) hide show
  1. package/CHANGES.txt +3 -0
  2. package/cjs/logger/constants.js +4 -6
  3. package/cjs/logger/messages/debug.js +1 -3
  4. package/cjs/logger/messages/error.js +1 -1
  5. package/cjs/logger/messages/warn.js +1 -1
  6. package/cjs/sdkClient/client.js +29 -19
  7. package/cjs/sdkClient/clientAttributesDecoration.js +19 -25
  8. package/cjs/sdkClient/clientInputValidation.js +28 -26
  9. package/cjs/storages/AbstractSplitsCacheAsync.js +12 -1
  10. package/cjs/storages/AbstractSplitsCacheSync.js +5 -7
  11. package/cjs/storages/KeyBuilder.js +0 -16
  12. package/cjs/storages/KeyBuilderCS.js +8 -2
  13. package/cjs/storages/dataLoader.js +1 -2
  14. package/cjs/storages/inLocalStorage/SplitsCacheInLocal.js +5 -2
  15. package/cjs/storages/inMemory/SplitsCacheInMemory.js +24 -31
  16. package/cjs/storages/inRedis/SplitsCacheInRedis.js +4 -21
  17. package/cjs/storages/pluggable/SplitsCachePluggable.js +2 -19
  18. package/cjs/storages/utils.js +1 -0
  19. package/cjs/sync/offline/syncTasks/fromObjectSyncTask.js +12 -13
  20. package/cjs/sync/polling/syncTasks/splitsSyncTask.js +1 -1
  21. package/cjs/sync/polling/updaters/splitChangesUpdater.js +9 -23
  22. package/cjs/sync/submitters/impressionsSubmitter.js +3 -2
  23. package/cjs/trackers/strategy/strategyOptimized.js +3 -0
  24. package/cjs/utils/inputValidation/eventProperties.js +12 -1
  25. package/cjs/utils/inputValidation/index.js +3 -1
  26. package/esm/logger/constants.js +1 -3
  27. package/esm/logger/messages/debug.js +1 -3
  28. package/esm/logger/messages/error.js +1 -1
  29. package/esm/logger/messages/warn.js +1 -1
  30. package/esm/sdkClient/client.js +29 -19
  31. package/esm/sdkClient/clientAttributesDecoration.js +19 -25
  32. package/esm/sdkClient/clientInputValidation.js +29 -27
  33. package/esm/storages/AbstractSplitsCacheAsync.js +12 -1
  34. package/esm/storages/AbstractSplitsCacheSync.js +5 -7
  35. package/esm/storages/KeyBuilder.js +0 -16
  36. package/esm/storages/KeyBuilderCS.js +8 -2
  37. package/esm/storages/dataLoader.js +1 -2
  38. package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +5 -2
  39. package/esm/storages/inMemory/SplitsCacheInMemory.js +24 -31
  40. package/esm/storages/inRedis/SplitsCacheInRedis.js +4 -21
  41. package/esm/storages/pluggable/SplitsCachePluggable.js +2 -19
  42. package/esm/storages/utils.js +1 -0
  43. package/esm/sync/offline/syncTasks/fromObjectSyncTask.js +12 -13
  44. package/esm/sync/polling/syncTasks/splitsSyncTask.js +1 -1
  45. package/esm/sync/polling/updaters/splitChangesUpdater.js +10 -24
  46. package/esm/sync/submitters/impressionsSubmitter.js +3 -2
  47. package/esm/trackers/strategy/strategyOptimized.js +3 -0
  48. package/esm/utils/inputValidation/eventProperties.js +10 -0
  49. package/esm/utils/inputValidation/index.js +1 -0
  50. package/package.json +1 -1
  51. package/src/logger/constants.ts +1 -3
  52. package/src/logger/messages/debug.ts +1 -3
  53. package/src/logger/messages/error.ts +1 -1
  54. package/src/logger/messages/warn.ts +1 -1
  55. package/src/sdkClient/client.ts +31 -21
  56. package/src/sdkClient/clientAttributesDecoration.ts +20 -27
  57. package/src/sdkClient/clientInputValidation.ts +30 -27
  58. package/src/storages/AbstractSplitsCacheAsync.ts +15 -5
  59. package/src/storages/AbstractSplitsCacheSync.ts +9 -13
  60. package/src/storages/KeyBuilder.ts +0 -20
  61. package/src/storages/KeyBuilderCS.ts +10 -3
  62. package/src/storages/dataLoader.ts +1 -2
  63. package/src/storages/inLocalStorage/SplitsCacheInLocal.ts +5 -2
  64. package/src/storages/inMemory/SplitsCacheInMemory.ts +22 -27
  65. package/src/storages/inRedis/SplitsCacheInRedis.ts +4 -21
  66. package/src/storages/pluggable/SplitsCachePluggable.ts +2 -19
  67. package/src/storages/types.ts +10 -16
  68. package/src/storages/utils.ts +1 -0
  69. package/src/sync/offline/syncTasks/fromObjectSyncTask.ts +14 -15
  70. package/src/sync/polling/syncTasks/splitsSyncTask.ts +1 -2
  71. package/src/sync/polling/updaters/splitChangesUpdater.ts +12 -27
  72. package/src/sync/submitters/impressionsSubmitter.ts +3 -2
  73. package/src/sync/submitters/types.ts +23 -33
  74. package/src/trackers/strategy/strategyOptimized.ts +3 -0
  75. package/src/utils/inputValidation/eventProperties.ts +10 -0
  76. package/src/utils/inputValidation/index.ts +1 -0
  77. package/types/splitio.d.ts +100 -35
@@ -26,7 +26,8 @@ export class SplitsCacheInMemory extends AbstractSplitsCacheSync {
26
26
  this.segmentsCount = 0;
27
27
  }
28
28
 
29
- addSplit(name: string, split: ISplit): boolean {
29
+ addSplit(split: ISplit): boolean {
30
+ const name = split.name;
30
31
  const previousSplit = this.getSplit(name);
31
32
  if (previousSplit) { // We had this Split already
32
33
 
@@ -40,41 +41,35 @@ export class SplitsCacheInMemory extends AbstractSplitsCacheSync {
40
41
  if (usesSegments(previousSplit)) this.segmentsCount--;
41
42
  }
42
43
 
43
- if (split) {
44
- // Store the Split.
45
- this.splitsCache[name] = split;
46
- // Update TT cache
47
- const ttName = split.trafficTypeName;
48
- this.ttCache[ttName] = (this.ttCache[ttName] || 0) + 1;
49
- this.addToFlagSets(split);
44
+ // Store the Split.
45
+ this.splitsCache[name] = split;
46
+ // Update TT cache
47
+ const ttName = split.trafficTypeName;
48
+ this.ttCache[ttName] = (this.ttCache[ttName] || 0) + 1;
49
+ this.addToFlagSets(split);
50
50
 
51
- // Add to segments count for the new version of the Split
52
- if (usesSegments(split)) this.segmentsCount++;
51
+ // Add to segments count for the new version of the Split
52
+ if (usesSegments(split)) this.segmentsCount++;
53
53
 
54
- return true;
55
- } else {
56
- return false;
57
- }
54
+ return true;
58
55
  }
59
56
 
60
57
  removeSplit(name: string): boolean {
61
58
  const split = this.getSplit(name);
62
- if (split) {
63
- // Delete the Split
64
- delete this.splitsCache[name];
59
+ if (!split) return false;
65
60
 
66
- const ttName = split.trafficTypeName;
67
- this.ttCache[ttName]--; // Update tt cache
68
- if (!this.ttCache[ttName]) delete this.ttCache[ttName];
69
- this.removeFromFlagSets(split.name, split.sets);
61
+ // Delete the Split
62
+ delete this.splitsCache[name];
70
63
 
71
- // Update the segments count.
72
- if (usesSegments(split)) this.segmentsCount--;
64
+ const ttName = split.trafficTypeName;
65
+ this.ttCache[ttName]--; // Update tt cache
66
+ if (!this.ttCache[ttName]) delete this.ttCache[ttName];
67
+ this.removeFromFlagSets(split.name, split.sets);
73
68
 
74
- return true;
75
- } else {
76
- return false;
77
- }
69
+ // Update the segments count.
70
+ if (usesSegments(split)) this.segmentsCount--;
71
+
72
+ return true;
78
73
  }
79
74
 
80
75
  getSplit(name: string): ISplit | null {
@@ -82,7 +82,8 @@ export class SplitsCacheInRedis extends AbstractSplitsCacheAsync {
82
82
  * The returned promise is resolved when the operation success
83
83
  * or rejected if it fails (e.g., redis operation fails)
84
84
  */
85
- addSplit(name: string, split: ISplit): Promise<boolean> {
85
+ addSplit(split: ISplit): Promise<boolean> {
86
+ const name = split.name;
86
87
  const splitKey = this.keys.buildSplitKey(name);
87
88
  return this.redis.get(splitKey).then(splitFromStorage => {
88
89
 
@@ -107,18 +108,9 @@ export class SplitsCacheInRedis extends AbstractSplitsCacheAsync {
107
108
  }).then(() => true);
108
109
  }
109
110
 
110
- /**
111
- * Add a list of splits.
112
- * The returned promise is resolved when the operation success
113
- * or rejected if it fails (e.g., redis operation fails)
114
- */
115
- addSplits(entries: [string, ISplit][]): Promise<boolean[]> {
116
- return Promise.all(entries.map(keyValuePair => this.addSplit(keyValuePair[0], keyValuePair[1])));
117
- }
118
-
119
111
  /**
120
112
  * Remove a given split.
121
- * The returned promise is resolved when the operation success, with 1 or 0 indicating if the split existed or not.
113
+ * The returned promise is resolved when the operation success, with true or false indicating if the split existed (and was removed) or not.
122
114
  * or rejected if it fails (e.g., redis operation fails).
123
115
  */
124
116
  removeSplit(name: string) {
@@ -127,19 +119,10 @@ export class SplitsCacheInRedis extends AbstractSplitsCacheAsync {
127
119
  return this._decrementCounts(split).then(() => this._updateFlagSets(name, split.sets));
128
120
  }
129
121
  }).then(() => {
130
- return this.redis.del(this.keys.buildSplitKey(name));
122
+ return this.redis.del(this.keys.buildSplitKey(name)).then(status => status === 1);
131
123
  });
132
124
  }
133
125
 
134
- /**
135
- * Remove a list of splits.
136
- * The returned promise is resolved when the operation success,
137
- * or rejected if it fails (e.g., redis operation fails).
138
- */
139
- removeSplits(names: string[]): Promise<any> {
140
- return Promise.all(names.map(name => this.removeSplit(name)));
141
- }
142
-
143
126
  /**
144
127
  * Get split definition or null if it's not defined.
145
128
  * Returned promise is rejected if redis operation fails.
@@ -66,7 +66,8 @@ export class SplitsCachePluggable extends AbstractSplitsCacheAsync {
66
66
  * The returned promise is resolved when the operation success
67
67
  * or rejected if it fails (e.g., wrapper operation fails)
68
68
  */
69
- addSplit(name: string, split: ISplit): Promise<boolean> {
69
+ addSplit(split: ISplit): Promise<boolean> {
70
+ const name = split.name;
70
71
  const splitKey = this.keys.buildSplitKey(name);
71
72
  return this.wrapper.get(splitKey).then(splitFromStorage => {
72
73
 
@@ -91,15 +92,6 @@ export class SplitsCachePluggable extends AbstractSplitsCacheAsync {
91
92
  }).then(() => true);
92
93
  }
93
94
 
94
- /**
95
- * Add a list of splits.
96
- * The returned promise is resolved when the operation success
97
- * or rejected if it fails (e.g., wrapper operation fails)
98
- */
99
- addSplits(entries: [string, ISplit][]): Promise<boolean[]> {
100
- return Promise.all(entries.map(keyValuePair => this.addSplit(keyValuePair[0], keyValuePair[1])));
101
- }
102
-
103
95
  /**
104
96
  * Remove a given split.
105
97
  * The returned promise is resolved when the operation success, with a boolean indicating if the split existed or not.
@@ -115,15 +107,6 @@ export class SplitsCachePluggable extends AbstractSplitsCacheAsync {
115
107
  });
116
108
  }
117
109
 
118
- /**
119
- * Remove a list of splits.
120
- * The returned promise is resolved when the operation success, with a boolean array indicating if the splits existed or not.
121
- * or rejected if it fails (e.g., wrapper operation fails).
122
- */
123
- removeSplits(names: string[]): Promise<void> { // @ts-ignore
124
- return Promise.all(names.map(name => this.removeSplit(name)));
125
- }
126
-
127
110
  /**
128
111
  * Get split.
129
112
  * The returned promise is resolved with the split definition or null if it's not defined,
@@ -177,11 +177,9 @@ export interface IPluggableStorageWrapper {
177
177
  /** Splits cache */
178
178
 
179
179
  export interface ISplitsCacheBase {
180
- addSplits(entries: [string, ISplit][]): MaybeThenable<boolean[] | void>,
181
- removeSplits(names: string[]): MaybeThenable<boolean[] | void>,
180
+ update(toAdd: ISplit[], toRemove: ISplit[], changeNumber: number): MaybeThenable<boolean>,
182
181
  getSplit(name: string): MaybeThenable<ISplit | null>,
183
182
  getSplits(names: string[]): MaybeThenable<Record<string, ISplit | null>>, // `fetchMany` in spec
184
- setChangeNumber(changeNumber: number): MaybeThenable<boolean | void>,
185
183
  // should never reject or throw an exception. Instead return -1 by default, assuming no splits are present in the storage.
186
184
  getChangeNumber(): MaybeThenable<number>,
187
185
  getAll(): MaybeThenable<ISplit[]>,
@@ -198,11 +196,9 @@ export interface ISplitsCacheBase {
198
196
  }
199
197
 
200
198
  export interface ISplitsCacheSync extends ISplitsCacheBase {
201
- addSplits(entries: [string, ISplit][]): boolean[],
202
- removeSplits(names: string[]): boolean[],
199
+ update(toAdd: ISplit[], toRemove: ISplit[], changeNumber: number): boolean,
203
200
  getSplit(name: string): ISplit | null,
204
201
  getSplits(names: string[]): Record<string, ISplit | null>,
205
- setChangeNumber(changeNumber: number): boolean | void,
206
202
  getChangeNumber(): number,
207
203
  getAll(): ISplit[],
208
204
  getSplitNames(): string[],
@@ -215,11 +211,9 @@ export interface ISplitsCacheSync extends ISplitsCacheBase {
215
211
  }
216
212
 
217
213
  export interface ISplitsCacheAsync extends ISplitsCacheBase {
218
- addSplits(entries: [string, ISplit][]): Promise<boolean[] | void>,
219
- removeSplits(names: string[]): Promise<boolean[] | void>,
214
+ update(toAdd: ISplit[], toRemove: ISplit[], changeNumber: number): Promise<boolean>,
220
215
  getSplit(name: string): Promise<ISplit | null>,
221
216
  getSplits(names: string[]): Promise<Record<string, ISplit | null>>,
222
- setChangeNumber(changeNumber: number): Promise<boolean | void>,
223
217
  getChangeNumber(): Promise<number>,
224
218
  getAll(): Promise<ISplit[]>,
225
219
  getSplitNames(): Promise<string[]>,
@@ -428,13 +422,13 @@ export interface ITelemetryCacheAsync extends ITelemetryEvaluationProducerAsync,
428
422
  */
429
423
 
430
424
  export interface IStorageBase<
431
- TSplitsCache extends ISplitsCacheBase,
432
- TSegmentsCache extends ISegmentsCacheBase,
433
- TImpressionsCache extends IImpressionsCacheBase,
434
- TImpressionsCountCache extends IImpressionCountsCacheBase,
435
- TEventsCache extends IEventsCacheBase,
436
- TTelemetryCache extends ITelemetryCacheSync | ITelemetryCacheAsync,
437
- TUniqueKeysCache extends IUniqueKeysCacheBase
425
+ TSplitsCache extends ISplitsCacheBase = ISplitsCacheBase,
426
+ TSegmentsCache extends ISegmentsCacheBase = ISegmentsCacheBase,
427
+ TImpressionsCache extends IImpressionsCacheBase = IImpressionsCacheBase,
428
+ TImpressionsCountCache extends IImpressionCountsCacheBase = IImpressionCountsCacheBase,
429
+ TEventsCache extends IEventsCacheBase = IEventsCacheBase,
430
+ TTelemetryCache extends ITelemetryCacheSync | ITelemetryCacheAsync = ITelemetryCacheSync | ITelemetryCacheAsync,
431
+ TUniqueKeysCache extends IUniqueKeysCacheBase = IUniqueKeysCacheBase
438
432
  > {
439
433
  splits: TSplitsCache,
440
434
  segments: TSegmentsCache,
@@ -30,6 +30,7 @@ export function impressionsToJSON(impressions: SplitIO.ImpressionDTO[], metadata
30
30
  c: impression.changeNumber,
31
31
  m: impression.time,
32
32
  pt: impression.pt,
33
+ properties: impression.properties
33
34
  }
34
35
  };
35
36
 
@@ -24,7 +24,7 @@ export function fromObjectUpdaterFactory(
24
24
  let startingUp = true;
25
25
 
26
26
  return function objectUpdater() {
27
- const splits: [string, ISplit][] = [];
27
+ const splits: ISplit[] = [];
28
28
  let loadError = null;
29
29
  let splitsMock: false | Record<string, ISplitPartial> = {};
30
30
  try {
@@ -37,24 +37,23 @@ export function fromObjectUpdaterFactory(
37
37
  if (!loadError && splitsMock) {
38
38
  log.debug(SYNC_OFFLINE_DATA, [JSON.stringify(splitsMock)]);
39
39
 
40
- forOwn(splitsMock, function (val, name) {
41
- splits.push([ // @ts-ignore Split changeNumber and seed is undefined in localhost mode
42
- name, {
43
- name,
44
- status: 'ACTIVE',
45
- killed: false,
46
- trafficAllocation: 100,
47
- defaultTreatment: CONTROL,
48
- conditions: val.conditions || [],
49
- configurations: val.configurations,
50
- trafficTypeName: val.trafficTypeName
51
- }
52
- ]);
40
+ forOwn(splitsMock, (val, name) => {
41
+ // @ts-ignore Split changeNumber and seed is undefined in localhost mode
42
+ splits.push({
43
+ name,
44
+ status: 'ACTIVE',
45
+ killed: false,
46
+ trafficAllocation: 100,
47
+ defaultTreatment: CONTROL,
48
+ conditions: val.conditions || [],
49
+ configurations: val.configurations,
50
+ trafficTypeName: val.trafficTypeName
51
+ });
53
52
  });
54
53
 
55
54
  return Promise.all([
56
55
  splitsCache.clear(), // required to sync removed splits from mock
57
- splitsCache.addSplits(splits)
56
+ splitsCache.update(splits, [], Date.now())
58
57
  ]).then(() => {
59
58
  readiness.splits.emit(SDK_SPLITS_ARRIVED);
60
59
 
@@ -22,8 +22,7 @@ export function splitsSyncTaskFactory(
22
22
  splitChangesUpdaterFactory(
23
23
  settings.log,
24
24
  splitChangesFetcherFactory(fetchSplitChanges),
25
- storage.splits,
26
- storage.segments,
25
+ storage,
27
26
  settings.sync.__splitFiltersValidation,
28
27
  readiness.splits,
29
28
  settings.startup.requestTimeoutBeforeReady,
@@ -1,11 +1,11 @@
1
- import { ISegmentsCacheBase, ISplitsCacheBase } from '../../../storages/types';
1
+ import { ISegmentsCacheBase, IStorageBase } from '../../../storages/types';
2
2
  import { ISplitChangesFetcher } from '../fetchers/types';
3
3
  import { ISplit, ISplitChangesResponse, ISplitFiltersValidation } from '../../../dtos/types';
4
4
  import { ISplitsEventEmitter } from '../../../readiness/types';
5
5
  import { timeout } from '../../../utils/promise/timeout';
6
6
  import { SDK_SPLITS_ARRIVED, SDK_SPLITS_CACHE_LOADED } from '../../../readiness/constants';
7
7
  import { ILogger } from '../../../logger/types';
8
- import { SYNC_SPLITS_FETCH, SYNC_SPLITS_NEW, SYNC_SPLITS_REMOVED, SYNC_SPLITS_SEGMENTS, SYNC_SPLITS_FETCH_FAILS, SYNC_SPLITS_FETCH_RETRY } from '../../../logger/constants';
8
+ import { SYNC_SPLITS_FETCH, SYNC_SPLITS_UPDATE, SYNC_SPLITS_FETCH_FAILS, SYNC_SPLITS_FETCH_RETRY } from '../../../logger/constants';
9
9
  import { startsWith } from '../../../utils/lang';
10
10
  import { IN_SEGMENT } from '../../../utils/constants';
11
11
  import { setToArray } from '../../../utils/lang/sets';
@@ -42,8 +42,8 @@ export function parseSegments({ conditions }: ISplit): Set<string> {
42
42
  }
43
43
 
44
44
  interface ISplitMutations {
45
- added: [string, ISplit][],
46
- removed: string[],
45
+ added: ISplit[],
46
+ removed: ISplit[],
47
47
  segments: string[]
48
48
  }
49
49
 
@@ -77,13 +77,13 @@ export function computeSplitsMutation(entries: ISplit[], filters: ISplitFiltersV
77
77
  const segments = new Set<string>();
78
78
  const computed = entries.reduce((accum, split) => {
79
79
  if (split.status === 'ACTIVE' && matchFilters(split, filters)) {
80
- accum.added.push([split.name, split]);
80
+ accum.added.push(split);
81
81
 
82
82
  parseSegments(split).forEach((segmentName: string) => {
83
83
  segments.add(segmentName);
84
84
  });
85
85
  } else {
86
- accum.removed.push(split.name);
86
+ accum.removed.push(split);
87
87
  }
88
88
 
89
89
  return accum;
@@ -111,14 +111,14 @@ export function computeSplitsMutation(entries: ISplit[], filters: ISplitFiltersV
111
111
  export function splitChangesUpdaterFactory(
112
112
  log: ILogger,
113
113
  splitChangesFetcher: ISplitChangesFetcher,
114
- splits: ISplitsCacheBase,
115
- segments: ISegmentsCacheBase,
114
+ storage: Pick<IStorageBase, 'splits' | 'segments'>,
116
115
  splitFiltersValidation: ISplitFiltersValidation,
117
116
  splitsEventEmitter?: ISplitsEventEmitter,
118
117
  requestTimeoutBeforeReady: number = 0,
119
118
  retriesOnFailureBeforeReady: number = 0,
120
119
  isClientSide?: boolean
121
120
  ): ISplitChangesUpdater {
121
+ const { splits, segments } = storage;
122
122
 
123
123
  let startingUp = true;
124
124
 
@@ -128,16 +128,6 @@ export function splitChangesUpdaterFactory(
128
128
  return promise;
129
129
  }
130
130
 
131
- /** Returns true if at least one split was updated */
132
- function isThereUpdate(flagsChange: [boolean | void, void | boolean[], void | boolean[], boolean | void] | [any, any, any]) {
133
- const [, added, removed] = flagsChange;
134
- // There is at least one added or modified feature flag
135
- if (added && added.some((update: boolean) => update)) return true;
136
- // There is at least one removed feature flag
137
- if (removed && removed.some((update: boolean) => update)) return true;
138
- return false;
139
- }
140
-
141
131
  /**
142
132
  * SplitChanges updater returns a promise that resolves with a `false` boolean value if it fails to fetch splits or synchronize them with the storage.
143
133
  * Returned promise will not be rejected.
@@ -162,22 +152,17 @@ export function splitChangesUpdaterFactory(
162
152
 
163
153
  const mutation = computeSplitsMutation(splitChanges.splits, splitFiltersValidation);
164
154
 
165
- log.debug(SYNC_SPLITS_NEW, [mutation.added.length]);
166
- log.debug(SYNC_SPLITS_REMOVED, [mutation.removed.length]);
167
- log.debug(SYNC_SPLITS_SEGMENTS, [mutation.segments.length]);
155
+ log.debug(SYNC_SPLITS_UPDATE, [mutation.added.length, mutation.removed.length, mutation.segments.length]);
168
156
 
169
157
  // Write into storage
170
158
  // @TODO call `setChangeNumber` only if the other storage operations have succeeded, in order to keep storage consistency
171
159
  return Promise.all([
172
- // calling first `setChangenumber` method, to perform cache flush if split filter queryString changed
173
- splits.setChangeNumber(splitChanges.till),
174
- splits.addSplits(mutation.added),
175
- splits.removeSplits(mutation.removed),
160
+ splits.update(mutation.added, mutation.removed, splitChanges.till),
176
161
  segments.registerSegments(mutation.segments)
177
- ]).then((flagsChange) => {
162
+ ]).then(([isThereUpdate]) => {
178
163
  if (splitsEventEmitter) {
179
164
  // To emit SDK_SPLITS_ARRIVED for server-side SDK, we must check that all registered segments have been fetched
180
- return Promise.resolve(!splitsEventEmitter.splitsArrived || (since !== splitChanges.till && isThereUpdate(flagsChange) && (isClientSide || checkAllSegmentsExist(segments))))
165
+ return Promise.resolve(!splitsEventEmitter.splitsArrived || (since !== splitChanges.till && isThereUpdate && (isClientSide || checkAllSegmentsExist(segments))))
181
166
  .catch(() => false /** noop. just to handle a possible `checkAllSegmentsExist` rejection, before emitting SDK event */)
182
167
  .then(emitSplitsArrivedEvent => {
183
168
  // emit SDK events
@@ -25,8 +25,9 @@ export function fromImpressionsCollector(sendLabels: boolean, data: SplitIO.Impr
25
25
  m: entry.time, // Timestamp
26
26
  c: entry.changeNumber, // ChangeNumber
27
27
  r: sendLabels ? entry.label : undefined, // Rule
28
- b: entry.bucketingKey ? entry.bucketingKey : undefined, // Bucketing Key
29
- pt: entry.pt ? entry.pt : undefined // Previous time
28
+ b: entry.bucketingKey, // Bucketing Key
29
+ pt: entry.pt, // Previous time
30
+ properties: entry.properties // Properties
30
31
  };
31
32
 
32
33
  return keyImpression;
@@ -3,26 +3,30 @@ import { IMetadata } from '../../dtos/types';
3
3
  import SplitIO from '../../../types/splitio';
4
4
  import { ISyncTask } from '../types';
5
5
 
6
+ type ImpressionPayload = {
7
+ /** Matching Key */
8
+ k: string;
9
+ /** Bucketing Key */
10
+ b?: string;
11
+ /** Treatment */
12
+ t: string;
13
+ /** Timestamp */
14
+ m: number;
15
+ /** Change number */
16
+ c: number;
17
+ /** Rule label */
18
+ r?: string;
19
+ /** Previous time */
20
+ pt?: number;
21
+ /** Stringified JSON object with properties */
22
+ properties?: string;
23
+ };
24
+
6
25
  export type ImpressionsPayload = {
7
26
  /** Split name */
8
27
  f: string,
9
28
  /** Key Impressions */
10
- i: {
11
- /** User Key */
12
- k: string;
13
- /** Treatment */
14
- t: string;
15
- /** Timestamp */
16
- m: number;
17
- /** ChangeNumber */
18
- c: number;
19
- /** Rule label */
20
- r?: string;
21
- /** Bucketing Key */
22
- b?: string;
23
- /** Previous time */
24
- pt?: number;
25
- }[]
29
+ i: ImpressionPayload[]
26
30
  }[]
27
31
 
28
32
  export type ImpressionCountsPayload = {
@@ -60,23 +64,9 @@ export type StoredImpressionWithMetadata = {
60
64
  /** Metadata */
61
65
  m: IMetadata,
62
66
  /** Stored impression */
63
- i: {
64
- /** keyName */
65
- k: string,
66
- /** bucketingKey */
67
- b?: string,
68
- /** Split name */
69
- f: string,
70
- /** treatment */
71
- t: string,
72
- /** label */
73
- r: string,
74
- /** changeNumber */
75
- c: number,
76
- /** time */
77
- m: number
78
- /** previous time */
79
- pt?: number
67
+ i: ImpressionPayload & {
68
+ /** Feature flag name */
69
+ f: string
80
70
  }
81
71
  }
82
72
 
@@ -18,6 +18,9 @@ export function strategyOptimizedFactory(
18
18
 
19
19
  return {
20
20
  process(impression: SplitIO.ImpressionDTO) {
21
+ // DEBUG mode without previous time, for impressions with properties
22
+ if (impression.properties) return true;
23
+
21
24
  impression.pt = impressionsObserver.testAndSet(impression);
22
25
 
23
26
  const now = Date.now();
@@ -66,3 +66,13 @@ export function validateEventProperties(log: ILogger, maybeProperties: any, meth
66
66
 
67
67
  return output;
68
68
  }
69
+
70
+ export function validateEvaluationOptions(log: ILogger, maybeOptions: any, method: string): SplitIO.EvaluationOptions | undefined {
71
+ if (isObject(maybeOptions)) {
72
+ const properties = validateEventProperties(log, maybeOptions.properties, method).properties;
73
+ return properties && Object.keys(properties).length > 0 ? { properties } : undefined;
74
+ } else if (maybeOptions) {
75
+ log.error(ERROR_NOT_PLAIN_OBJECT, [method, 'evaluation options']);
76
+ }
77
+ return undefined;
78
+ }
@@ -11,3 +11,4 @@ export { validateIfNotDestroyed, validateIfOperational } from './isOperational';
11
11
  export { validateSplitExistence } from './splitExistence';
12
12
  export { validateTrafficTypeExistence } from './trafficTypeExistence';
13
13
  export { validatePreloadedData } from './preloadedData';
14
+ export { validateEvaluationOptions } from './eventProperties';