@splitsoftware/splitio-commons 2.10.1 → 2.10.2-rc.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/CHANGES.txt CHANGED
@@ -1,5 +1,9 @@
1
+ 2.11.0 (January XX, 2026)
2
+ - Added metadata to SDK_UPDATE events to indicate the type of update (FLAGS_UPDATE or SEGMENTS_UPDATE) and the names of updated flags or segments.
3
+ - Added metadata to SDK_READY and SDK_READY_FROM_CACHE events, including `initialCacheLoad` (boolean indicating if SDK was loaded from cache) and `lastUpdateTimestamp` (Int64 milliseconds since epoch).
4
+
1
5
  2.10.1 (December 18, 2025)
2
- - Bugfix - Handle `null` prerequisites properly
6
+ - Bugfix - Handle `null` prerequisites properly.
3
7
 
4
8
  2.10.0 (December 16, 2025)
5
9
  - Added property `impressionsDisabled` in getTreatment(s) `evaluationOptions` parameter, to disable impressions per evaluations.
@@ -13,7 +13,7 @@ function splitsEventEmitterFactory(EventEmitter) {
13
13
  // `isSplitKill` condition avoids an edge-case of wrongly emitting SDK_READY if:
14
14
  // - `/memberships` fetch and SPLIT_KILL occurs before `/splitChanges` fetch, and
15
15
  // - storage has cached splits (for which case `splitsStorage.killLocally` can return true)
16
- splitsEventEmitter.on(constants_1.SDK_SPLITS_ARRIVED, function (isSplitKill) { if (!isSplitKill)
16
+ splitsEventEmitter.on(constants_1.SDK_SPLITS_ARRIVED, function (metadata, isSplitKill) { if (!isSplitKill)
17
17
  splitsEventEmitter.splitsArrived = true; });
18
18
  splitsEventEmitter.once(constants_1.SDK_SPLITS_CACHE_LOADED, function () { splitsEventEmitter.splitsCacheLoaded = true; });
19
19
  return splitsEventEmitter;
@@ -74,7 +74,11 @@ function readinessManagerFactory(EventEmitter, settings, splits, isShared) {
74
74
  if (!isReady && !isDestroyed) {
75
75
  try {
76
76
  syncLastUpdate();
77
- gate.emit(constants_1.SDK_READY_FROM_CACHE, isReady);
77
+ var metadata = {
78
+ initialCacheLoad: true,
79
+ lastUpdateTimestamp: lastUpdate
80
+ };
81
+ gate.emit(constants_1.SDK_READY_FROM_CACHE, metadata);
78
82
  }
79
83
  catch (e) {
80
84
  // throws user callback exceptions in next tick
@@ -82,13 +86,13 @@ function readinessManagerFactory(EventEmitter, settings, splits, isShared) {
82
86
  }
83
87
  }
84
88
  }
85
- function checkIsReadyOrUpdate(diff) {
89
+ function checkIsReadyOrUpdate(metadata) {
86
90
  if (isDestroyed)
87
91
  return;
88
92
  if (isReady) {
89
93
  try {
90
94
  syncLastUpdate();
91
- gate.emit(constants_1.SDK_UPDATE, diff);
95
+ gate.emit(constants_1.SDK_UPDATE, metadata);
92
96
  }
93
97
  catch (e) {
94
98
  // throws user callback exceptions in next tick
@@ -101,11 +105,20 @@ function readinessManagerFactory(EventEmitter, settings, splits, isShared) {
101
105
  isReady = true;
102
106
  try {
103
107
  syncLastUpdate();
108
+ var wasReadyFromCache = isReadyFromCache;
104
109
  if (!isReadyFromCache) {
105
110
  isReadyFromCache = true;
106
- gate.emit(constants_1.SDK_READY_FROM_CACHE, isReady);
111
+ var metadataFromCache = {
112
+ initialCacheLoad: false,
113
+ lastUpdateTimestamp: lastUpdate
114
+ };
115
+ gate.emit(constants_1.SDK_READY_FROM_CACHE, metadataFromCache);
107
116
  }
108
- gate.emit(constants_1.SDK_READY);
117
+ var metadataReady = {
118
+ initialCacheLoad: wasReadyFromCache,
119
+ lastUpdateTimestamp: lastUpdate
120
+ };
121
+ gate.emit(constants_1.SDK_READY, metadataReady);
109
122
  }
110
123
  catch (e) {
111
124
  // throws user callback exceptions in next tick
@@ -42,7 +42,7 @@ function fromObjectUpdaterFactory(splitsParser, storage, readiness, settings) {
42
42
  splitsCache.clear(),
43
43
  splitsCache.update(splits, [], Date.now())
44
44
  ]).then(function () {
45
- readiness.splits.emit(constants_2.SDK_SPLITS_ARRIVED);
45
+ readiness.splits.emit(constants_2.SDK_SPLITS_ARRIVED, { type: "FLAGS_UPDATE" /* FLAGS_UPDATE */, names: [] });
46
46
  if (startingUp) {
47
47
  startingUp = false;
48
48
  Promise.resolve(storage.validateCache ? storage.validateCache() : false).then(function (isCacheLoaded) {
@@ -50,7 +50,7 @@ function fromObjectUpdaterFactory(splitsParser, storage, readiness, settings) {
50
50
  if (isCacheLoaded)
51
51
  readiness.splits.emit(constants_2.SDK_SPLITS_CACHE_LOADED);
52
52
  // Emits SDK_READY
53
- readiness.segments.emit(constants_2.SDK_SEGMENTS_ARRIVED);
53
+ readiness.segments.emit(constants_2.SDK_SEGMENTS_ARRIVED, { type: "SEGMENTS_UPDATE" /* SEGMENTS_UPDATE */, names: [] });
54
54
  });
55
55
  }
56
56
  return true;
@@ -49,9 +49,9 @@ function pollingManagerCSFactory(params) {
49
49
  function add(matchingKey, readiness, storage) {
50
50
  var mySegmentsSyncTask = (0, mySegmentsSyncTask_1.mySegmentsSyncTaskFactory)(splitApi.fetchMemberships, storage, readiness, settings, matchingKey);
51
51
  // smart ready
52
- function smartReady() {
52
+ function smartReady(metadata) {
53
53
  if (!readiness.isReady() && !(0, AbstractSplitsCacheSync_1.usesSegmentsSync)(storage))
54
- readiness.segments.emit(constants_1.SDK_SEGMENTS_ARRIVED);
54
+ readiness.segments.emit(constants_1.SDK_SEGMENTS_ARRIVED, metadata);
55
55
  }
56
56
  if (!(0, AbstractSplitsCacheSync_1.usesSegmentsSync)(storage))
57
57
  setTimeout(smartReady, 0);
@@ -39,7 +39,7 @@ function mySegmentsUpdaterFactory(log, mySegmentsFetcher, storage, segmentsEvent
39
39
  // Notify update if required
40
40
  if ((0, AbstractSplitsCacheSync_1.usesSegmentsSync)(storage) && (shouldNotifyUpdate || readyOnAlreadyExistentState)) {
41
41
  readyOnAlreadyExistentState = false;
42
- segmentsEventEmitter.emit(constants_1.SDK_SEGMENTS_ARRIVED);
42
+ segmentsEventEmitter.emit(constants_1.SDK_SEGMENTS_ARRIVED, { type: "SEGMENTS_UPDATE" /* SEGMENTS_UPDATE */, names: [] });
43
43
  }
44
44
  }
45
45
  function _mySegmentsUpdater(retry, segmentsData, noCache, till) {
@@ -66,8 +66,12 @@ function segmentChangesUpdaterFactory(log, segmentChangesFetcher, segments, read
66
66
  // if at least one segment fetch succeeded, mark segments ready
67
67
  if (shouldUpdateFlags.some(function (update) { return update; }) || readyOnAlreadyExistentState) {
68
68
  readyOnAlreadyExistentState = false;
69
+ var metadata = {
70
+ type: "SEGMENTS_UPDATE" /* SEGMENTS_UPDATE */,
71
+ names: []
72
+ };
69
73
  if (readiness)
70
- readiness.segments.emit(constants_1.SDK_SEGMENTS_ARRIVED);
74
+ readiness.segments.emit(constants_1.SDK_SEGMENTS_ARRIVED, metadata);
71
75
  }
72
76
  return true;
73
77
  });
@@ -79,8 +79,9 @@ function computeMutation(rules, segments, filters) {
79
79
  else {
80
80
  accum.removed.push(ruleEntity);
81
81
  }
82
+ accum.names.push(ruleEntity.name);
82
83
  return accum;
83
- }, { added: [], removed: [] });
84
+ }, { added: [], removed: [], names: [] });
84
85
  }
85
86
  exports.computeMutation = computeMutation;
86
87
  /**
@@ -136,9 +137,11 @@ function splitChangesUpdaterFactory(log, splitChangesFetcher, storage, splitFilt
136
137
  splitChangesFetcher(since, noCache, till, rbSince, _promiseDecorator))
137
138
  .then(function (splitChanges) {
138
139
  var usedSegments = new Set();
140
+ var updatedFlags = [];
139
141
  var ffUpdate = false;
140
142
  if (splitChanges.ff) {
141
- var _a = computeMutation(splitChanges.ff.d, usedSegments, splitFiltersValidation), added = _a.added, removed = _a.removed;
143
+ var _a = computeMutation(splitChanges.ff.d, usedSegments, splitFiltersValidation), added = _a.added, removed = _a.removed, names = _a.names;
144
+ updatedFlags = names;
142
145
  log.debug(constants_2.SYNC_SPLITS_UPDATE, [added.length, removed.length]);
143
146
  ffUpdate = splits.update(added, removed, splitChanges.ff.t);
144
147
  }
@@ -161,9 +164,13 @@ function splitChangesUpdaterFactory(log, splitChangesFetcher, storage, splitFilt
161
164
  return Promise.resolve(!splitsEventEmitter.splitsArrived || ((ffChanged || rbsChanged) && (isClientSide || checkAllSegmentsExist(segments))))
162
165
  .catch(function () { return false; } /** noop. just to handle a possible `checkAllSegmentsExist` rejection, before emitting SDK event */)
163
166
  .then(function (emitSplitsArrivedEvent) {
167
+ var metadata = {
168
+ type: updatedFlags.length > 0 ? "FLAGS_UPDATE" /* FLAGS_UPDATE */ : "SEGMENTS_UPDATE" /* SEGMENTS_UPDATE */,
169
+ names: updatedFlags.length > 0 ? updatedFlags : []
170
+ };
164
171
  // emit SDK events
165
172
  if (emitSplitsArrivedEvent)
166
- splitsEventEmitter.emit(constants_1.SDK_SPLITS_ARRIVED);
173
+ splitsEventEmitter.emit(constants_1.SDK_SPLITS_ARRIVED, metadata);
167
174
  return true;
168
175
  });
169
176
  }
@@ -36,7 +36,7 @@ function submitterManagerFactory(params) {
36
36
  var promises = onlyTelemetry ? [] : submitters.map(function (submitter) { return submitter.execute(); });
37
37
  if (telemetrySubmitter)
38
38
  promises.push(telemetrySubmitter.execute());
39
- return Promise.all(promises);
39
+ return Promise.all(promises).then(function () { });
40
40
  },
41
41
  isExecuting: function () {
42
42
  return submitters.some(function (submitter) { return submitter.isExecuting(); });
@@ -10,7 +10,7 @@ function splitsEventEmitterFactory(EventEmitter) {
10
10
  // `isSplitKill` condition avoids an edge-case of wrongly emitting SDK_READY if:
11
11
  // - `/memberships` fetch and SPLIT_KILL occurs before `/splitChanges` fetch, and
12
12
  // - storage has cached splits (for which case `splitsStorage.killLocally` can return true)
13
- splitsEventEmitter.on(SDK_SPLITS_ARRIVED, function (isSplitKill) { if (!isSplitKill)
13
+ splitsEventEmitter.on(SDK_SPLITS_ARRIVED, function (metadata, isSplitKill) { if (!isSplitKill)
14
14
  splitsEventEmitter.splitsArrived = true; });
15
15
  splitsEventEmitter.once(SDK_SPLITS_CACHE_LOADED, function () { splitsEventEmitter.splitsCacheLoaded = true; });
16
16
  return splitsEventEmitter;
@@ -71,7 +71,11 @@ export function readinessManagerFactory(EventEmitter, settings, splits, isShared
71
71
  if (!isReady && !isDestroyed) {
72
72
  try {
73
73
  syncLastUpdate();
74
- gate.emit(SDK_READY_FROM_CACHE, isReady);
74
+ var metadata = {
75
+ initialCacheLoad: true,
76
+ lastUpdateTimestamp: lastUpdate
77
+ };
78
+ gate.emit(SDK_READY_FROM_CACHE, metadata);
75
79
  }
76
80
  catch (e) {
77
81
  // throws user callback exceptions in next tick
@@ -79,13 +83,13 @@ export function readinessManagerFactory(EventEmitter, settings, splits, isShared
79
83
  }
80
84
  }
81
85
  }
82
- function checkIsReadyOrUpdate(diff) {
86
+ function checkIsReadyOrUpdate(metadata) {
83
87
  if (isDestroyed)
84
88
  return;
85
89
  if (isReady) {
86
90
  try {
87
91
  syncLastUpdate();
88
- gate.emit(SDK_UPDATE, diff);
92
+ gate.emit(SDK_UPDATE, metadata);
89
93
  }
90
94
  catch (e) {
91
95
  // throws user callback exceptions in next tick
@@ -98,11 +102,20 @@ export function readinessManagerFactory(EventEmitter, settings, splits, isShared
98
102
  isReady = true;
99
103
  try {
100
104
  syncLastUpdate();
105
+ var wasReadyFromCache = isReadyFromCache;
101
106
  if (!isReadyFromCache) {
102
107
  isReadyFromCache = true;
103
- gate.emit(SDK_READY_FROM_CACHE, isReady);
108
+ var metadataFromCache = {
109
+ initialCacheLoad: false,
110
+ lastUpdateTimestamp: lastUpdate
111
+ };
112
+ gate.emit(SDK_READY_FROM_CACHE, metadataFromCache);
104
113
  }
105
- gate.emit(SDK_READY);
114
+ var metadataReady = {
115
+ initialCacheLoad: wasReadyFromCache,
116
+ lastUpdateTimestamp: lastUpdate
117
+ };
118
+ gate.emit(SDK_READY, metadataReady);
106
119
  }
107
120
  catch (e) {
108
121
  // throws user callback exceptions in next tick
@@ -39,7 +39,7 @@ export function fromObjectUpdaterFactory(splitsParser, storage, readiness, setti
39
39
  splitsCache.clear(),
40
40
  splitsCache.update(splits, [], Date.now())
41
41
  ]).then(function () {
42
- readiness.splits.emit(SDK_SPLITS_ARRIVED);
42
+ readiness.splits.emit(SDK_SPLITS_ARRIVED, { type: "FLAGS_UPDATE" /* FLAGS_UPDATE */, names: [] });
43
43
  if (startingUp) {
44
44
  startingUp = false;
45
45
  Promise.resolve(storage.validateCache ? storage.validateCache() : false).then(function (isCacheLoaded) {
@@ -47,7 +47,7 @@ export function fromObjectUpdaterFactory(splitsParser, storage, readiness, setti
47
47
  if (isCacheLoaded)
48
48
  readiness.splits.emit(SDK_SPLITS_CACHE_LOADED);
49
49
  // Emits SDK_READY
50
- readiness.segments.emit(SDK_SEGMENTS_ARRIVED);
50
+ readiness.segments.emit(SDK_SEGMENTS_ARRIVED, { type: "SEGMENTS_UPDATE" /* SEGMENTS_UPDATE */, names: [] });
51
51
  });
52
52
  }
53
53
  return true;
@@ -46,9 +46,9 @@ export function pollingManagerCSFactory(params) {
46
46
  function add(matchingKey, readiness, storage) {
47
47
  var mySegmentsSyncTask = mySegmentsSyncTaskFactory(splitApi.fetchMemberships, storage, readiness, settings, matchingKey);
48
48
  // smart ready
49
- function smartReady() {
49
+ function smartReady(metadata) {
50
50
  if (!readiness.isReady() && !usesSegmentsSync(storage))
51
- readiness.segments.emit(SDK_SEGMENTS_ARRIVED);
51
+ readiness.segments.emit(SDK_SEGMENTS_ARRIVED, metadata);
52
52
  }
53
53
  if (!usesSegmentsSync(storage))
54
54
  setTimeout(smartReady, 0);
@@ -36,7 +36,7 @@ export function mySegmentsUpdaterFactory(log, mySegmentsFetcher, storage, segmen
36
36
  // Notify update if required
37
37
  if (usesSegmentsSync(storage) && (shouldNotifyUpdate || readyOnAlreadyExistentState)) {
38
38
  readyOnAlreadyExistentState = false;
39
- segmentsEventEmitter.emit(SDK_SEGMENTS_ARRIVED);
39
+ segmentsEventEmitter.emit(SDK_SEGMENTS_ARRIVED, { type: "SEGMENTS_UPDATE" /* SEGMENTS_UPDATE */, names: [] });
40
40
  }
41
41
  }
42
42
  function _mySegmentsUpdater(retry, segmentsData, noCache, till) {
@@ -63,8 +63,12 @@ export function segmentChangesUpdaterFactory(log, segmentChangesFetcher, segment
63
63
  // if at least one segment fetch succeeded, mark segments ready
64
64
  if (shouldUpdateFlags.some(function (update) { return update; }) || readyOnAlreadyExistentState) {
65
65
  readyOnAlreadyExistentState = false;
66
+ var metadata = {
67
+ type: "SEGMENTS_UPDATE" /* SEGMENTS_UPDATE */,
68
+ names: []
69
+ };
66
70
  if (readiness)
67
- readiness.segments.emit(SDK_SEGMENTS_ARRIVED);
71
+ readiness.segments.emit(SDK_SEGMENTS_ARRIVED, metadata);
68
72
  }
69
73
  return true;
70
74
  });
@@ -75,8 +75,9 @@ export function computeMutation(rules, segments, filters) {
75
75
  else {
76
76
  accum.removed.push(ruleEntity);
77
77
  }
78
+ accum.names.push(ruleEntity.name);
78
79
  return accum;
79
- }, { added: [], removed: [] });
80
+ }, { added: [], removed: [], names: [] });
80
81
  }
81
82
  /**
82
83
  * factory of SplitChanges updater, a task that:
@@ -131,9 +132,11 @@ export function splitChangesUpdaterFactory(log, splitChangesFetcher, storage, sp
131
132
  splitChangesFetcher(since, noCache, till, rbSince, _promiseDecorator))
132
133
  .then(function (splitChanges) {
133
134
  var usedSegments = new Set();
135
+ var updatedFlags = [];
134
136
  var ffUpdate = false;
135
137
  if (splitChanges.ff) {
136
- var _a = computeMutation(splitChanges.ff.d, usedSegments, splitFiltersValidation), added = _a.added, removed = _a.removed;
138
+ var _a = computeMutation(splitChanges.ff.d, usedSegments, splitFiltersValidation), added = _a.added, removed = _a.removed, names = _a.names;
139
+ updatedFlags = names;
137
140
  log.debug(SYNC_SPLITS_UPDATE, [added.length, removed.length]);
138
141
  ffUpdate = splits.update(added, removed, splitChanges.ff.t);
139
142
  }
@@ -156,9 +159,13 @@ export function splitChangesUpdaterFactory(log, splitChangesFetcher, storage, sp
156
159
  return Promise.resolve(!splitsEventEmitter.splitsArrived || ((ffChanged || rbsChanged) && (isClientSide || checkAllSegmentsExist(segments))))
157
160
  .catch(function () { return false; } /** noop. just to handle a possible `checkAllSegmentsExist` rejection, before emitting SDK event */)
158
161
  .then(function (emitSplitsArrivedEvent) {
162
+ var metadata = {
163
+ type: updatedFlags.length > 0 ? "FLAGS_UPDATE" /* FLAGS_UPDATE */ : "SEGMENTS_UPDATE" /* SEGMENTS_UPDATE */,
164
+ names: updatedFlags.length > 0 ? updatedFlags : []
165
+ };
159
166
  // emit SDK events
160
167
  if (emitSplitsArrivedEvent)
161
- splitsEventEmitter.emit(SDK_SPLITS_ARRIVED);
168
+ splitsEventEmitter.emit(SDK_SPLITS_ARRIVED, metadata);
162
169
  return true;
163
170
  });
164
171
  }
@@ -33,7 +33,7 @@ export function submitterManagerFactory(params) {
33
33
  var promises = onlyTelemetry ? [] : submitters.map(function (submitter) { return submitter.execute(); });
34
34
  if (telemetrySubmitter)
35
35
  promises.push(telemetrySubmitter.execute());
36
- return Promise.all(promises);
36
+ return Promise.all(promises).then(function () { });
37
37
  },
38
38
  isExecuting: function () {
39
39
  return submitters.some(function (submitter) { return submitter.isExecuting(); });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@splitsoftware/splitio-commons",
3
- "version": "2.10.1",
3
+ "version": "2.10.2-rc.0",
4
4
  "description": "Split JavaScript SDK common components",
5
5
  "main": "cjs/index.js",
6
6
  "module": "esm/index.js",
@@ -15,7 +15,7 @@ function splitsEventEmitterFactory(EventEmitter: new () => SplitIO.IEventEmitter
15
15
  // `isSplitKill` condition avoids an edge-case of wrongly emitting SDK_READY if:
16
16
  // - `/memberships` fetch and SPLIT_KILL occurs before `/splitChanges` fetch, and
17
17
  // - storage has cached splits (for which case `splitsStorage.killLocally` can return true)
18
- splitsEventEmitter.on(SDK_SPLITS_ARRIVED, (isSplitKill: boolean) => { if (!isSplitKill) splitsEventEmitter.splitsArrived = true; });
18
+ splitsEventEmitter.on(SDK_SPLITS_ARRIVED, (metadata: SplitIO.SdkUpdateMetadata, isSplitKill: boolean) => { if (!isSplitKill) splitsEventEmitter.splitsArrived = true; });
19
19
  splitsEventEmitter.once(SDK_SPLITS_CACHE_LOADED, () => { splitsEventEmitter.splitsCacheLoaded = true; });
20
20
 
21
21
  return splitsEventEmitter;
@@ -90,7 +90,11 @@ export function readinessManagerFactory(
90
90
  if (!isReady && !isDestroyed) {
91
91
  try {
92
92
  syncLastUpdate();
93
- gate.emit(SDK_READY_FROM_CACHE, isReady);
93
+ const metadata: SplitIO.SdkReadyMetadata = {
94
+ initialCacheLoad: true,
95
+ lastUpdateTimestamp: lastUpdate
96
+ };
97
+ gate.emit(SDK_READY_FROM_CACHE, metadata);
94
98
  } catch (e) {
95
99
  // throws user callback exceptions in next tick
96
100
  setTimeout(() => { throw e; }, 0);
@@ -98,12 +102,12 @@ export function readinessManagerFactory(
98
102
  }
99
103
  }
100
104
 
101
- function checkIsReadyOrUpdate(diff: any) {
105
+ function checkIsReadyOrUpdate(metadata: SplitIO.SdkUpdateMetadata) {
102
106
  if (isDestroyed) return;
103
107
  if (isReady) {
104
108
  try {
105
109
  syncLastUpdate();
106
- gate.emit(SDK_UPDATE, diff);
110
+ gate.emit(SDK_UPDATE, metadata);
107
111
  } catch (e) {
108
112
  // throws user callback exceptions in next tick
109
113
  setTimeout(() => { throw e; }, 0);
@@ -114,11 +118,20 @@ export function readinessManagerFactory(
114
118
  isReady = true;
115
119
  try {
116
120
  syncLastUpdate();
121
+ const wasReadyFromCache = isReadyFromCache;
117
122
  if (!isReadyFromCache) {
118
123
  isReadyFromCache = true;
119
- gate.emit(SDK_READY_FROM_CACHE, isReady);
124
+ const metadataFromCache: SplitIO.SdkReadyMetadata = {
125
+ initialCacheLoad: false,
126
+ lastUpdateTimestamp: lastUpdate
127
+ };
128
+ gate.emit(SDK_READY_FROM_CACHE, metadataFromCache);
120
129
  }
121
- gate.emit(SDK_READY);
130
+ const metadataReady: SplitIO.SdkReadyMetadata = {
131
+ initialCacheLoad: wasReadyFromCache,
132
+ lastUpdateTimestamp: lastUpdate
133
+ };
134
+ gate.emit(SDK_READY, metadataReady);
122
135
  } catch (e) {
123
136
  // throws user callback exceptions in next tick
124
137
  setTimeout(() => { throw e; }, 0);
@@ -1,5 +1,30 @@
1
1
  import SplitIO from '../../types/splitio';
2
2
 
3
+ /** Readiness event types */
4
+
5
+ export type SDK_READY_TIMED_OUT = 'init::timeout'
6
+ export type SDK_READY = 'init::ready'
7
+ export type SDK_READY_FROM_CACHE = 'init::cache-ready'
8
+ export type SDK_UPDATE = 'state::update'
9
+ export type SDK_DESTROY = 'state::destroy'
10
+
11
+ export type IReadinessEvent = SDK_READY_TIMED_OUT | SDK_READY | SDK_READY_FROM_CACHE | SDK_UPDATE | SDK_DESTROY
12
+
13
+ export interface IReadinessEventEmitter extends SplitIO.IEventEmitter {
14
+ emit(event: IReadinessEvent, ...args: any[]): boolean
15
+ on(event: SDK_READY, listener: (metadata: SplitIO.SdkReadyMetadata) => void): this;
16
+ on(event: SDK_READY_FROM_CACHE, listener: (metadata: SplitIO.SdkReadyMetadata) => void): this;
17
+ on(event: SDK_UPDATE, listener: (metadata: SplitIO.SdkUpdateMetadata) => void): this;
18
+ on(event: string | symbol, listener: (...args: any[]) => void): this;
19
+ once(event: SDK_READY, listener: (metadata: SplitIO.SdkReadyMetadata) => void): this;
20
+ once(event: SDK_READY_FROM_CACHE, listener: (metadata: SplitIO.SdkReadyMetadata) => void): this;
21
+ once(event: SDK_UPDATE, listener: (metadata: SplitIO.SdkUpdateMetadata) => void): this;
22
+ once(event: string | symbol, listener: (...args: any[]) => void): this;
23
+ addListener(event: SDK_READY, listener: (metadata: SplitIO.SdkReadyMetadata) => void): this;
24
+ addListener(event: SDK_READY_FROM_CACHE, listener: (metadata: SplitIO.SdkReadyMetadata) => void): this;
25
+ addListener(event: SDK_UPDATE, listener: (metadata: SplitIO.SdkUpdateMetadata) => void): this;
26
+ addListener(event: string | symbol, listener: (...args: any[]) => void): this;
27
+ }
3
28
  /** Splits data emitter */
4
29
 
5
30
  type SDK_SPLITS_ARRIVED = 'state::splits-arrived'
@@ -9,6 +34,7 @@ type ISplitsEvent = SDK_SPLITS_ARRIVED | SDK_SPLITS_CACHE_LOADED
9
34
  export interface ISplitsEventEmitter extends SplitIO.IEventEmitter {
10
35
  emit(event: ISplitsEvent, ...args: any[]): boolean
11
36
  on(event: ISplitsEvent, listener: (...args: any[]) => void): this;
37
+ on(event: SDK_UPDATE, listener: (metadata: SplitIO.SdkUpdateMetadata) => void): this;
12
38
  once(event: ISplitsEvent, listener: (...args: any[]) => void): this;
13
39
  splitsArrived: boolean
14
40
  splitsCacheLoaded: boolean
@@ -24,23 +50,11 @@ type ISegmentsEvent = SDK_SEGMENTS_ARRIVED
24
50
  export interface ISegmentsEventEmitter extends SplitIO.IEventEmitter {
25
51
  emit(event: ISegmentsEvent, ...args: any[]): boolean
26
52
  on(event: ISegmentsEvent, listener: (...args: any[]) => void): this;
53
+ on(event: SDK_UPDATE, listener: (metadata: SplitIO.SdkUpdateMetadata) => void): this;
27
54
  once(event: ISegmentsEvent, listener: (...args: any[]) => void): this;
28
55
  segmentsArrived: boolean
29
56
  }
30
57
 
31
- /** Readiness emitter */
32
-
33
- export type SDK_READY_TIMED_OUT = 'init::timeout'
34
- export type SDK_READY = 'init::ready'
35
- export type SDK_READY_FROM_CACHE = 'init::cache-ready'
36
- export type SDK_UPDATE = 'state::update'
37
- export type SDK_DESTROY = 'state::destroy'
38
- export type IReadinessEvent = SDK_READY_TIMED_OUT | SDK_READY | SDK_READY_FROM_CACHE | SDK_UPDATE | SDK_DESTROY
39
-
40
- export interface IReadinessEventEmitter extends SplitIO.IEventEmitter {
41
- emit(event: IReadinessEvent, ...args: any[]): boolean
42
- }
43
-
44
58
  /** Readiness manager */
45
59
 
46
60
  export interface IReadinessManager {
@@ -9,6 +9,7 @@ import { ISettings } from '../../../types';
9
9
  import { CONTROL } from '../../../utils/constants';
10
10
  import { SDK_SPLITS_ARRIVED, SDK_SEGMENTS_ARRIVED, SDK_SPLITS_CACHE_LOADED } from '../../../readiness/constants';
11
11
  import { SYNC_OFFLINE_DATA, ERROR_SYNC_OFFLINE_LOADING } from '../../../logger/constants';
12
+ import { SdkUpdateMetadataKeys } from '../../../../types/splitio';
12
13
 
13
14
  /**
14
15
  * Offline equivalent of `splitChangesUpdaterFactory`
@@ -55,7 +56,7 @@ export function fromObjectUpdaterFactory(
55
56
  splitsCache.clear(), // required to sync removed splits from mock
56
57
  splitsCache.update(splits, [], Date.now())
57
58
  ]).then(() => {
58
- readiness.splits.emit(SDK_SPLITS_ARRIVED);
59
+ readiness.splits.emit(SDK_SPLITS_ARRIVED, { type: SdkUpdateMetadataKeys.FLAGS_UPDATE, names: [] });
59
60
 
60
61
  if (startingUp) {
61
62
  startingUp = false;
@@ -63,7 +64,7 @@ export function fromObjectUpdaterFactory(
63
64
  // Emits SDK_READY_FROM_CACHE
64
65
  if (isCacheLoaded) readiness.splits.emit(SDK_SPLITS_CACHE_LOADED);
65
66
  // Emits SDK_READY
66
- readiness.segments.emit(SDK_SEGMENTS_ARRIVED);
67
+ readiness.segments.emit(SDK_SEGMENTS_ARRIVED, { type: SdkUpdateMetadataKeys.SEGMENTS_UPDATE, names: [] });
67
68
  });
68
69
  }
69
70
  return true;
@@ -9,6 +9,7 @@ import { SDK_SPLITS_ARRIVED, SDK_SEGMENTS_ARRIVED } from '../../readiness/consta
9
9
  import { POLLING_SMART_PAUSING, POLLING_START, POLLING_STOP } from '../../logger/constants';
10
10
  import { ISdkFactoryContextSync } from '../../sdkFactory/types';
11
11
  import { usesSegmentsSync } from '../../storages/AbstractSplitsCacheSync';
12
+ import { SdkUpdateMetadata } from '../../../types/splitio';
12
13
 
13
14
  /**
14
15
  * Expose start / stop mechanism for polling data from services.
@@ -59,8 +60,8 @@ export function pollingManagerCSFactory(
59
60
  const mySegmentsSyncTask = mySegmentsSyncTaskFactory(splitApi.fetchMemberships, storage, readiness, settings, matchingKey);
60
61
 
61
62
  // smart ready
62
- function smartReady() {
63
- if (!readiness.isReady() && !usesSegmentsSync(storage)) readiness.segments.emit(SDK_SEGMENTS_ARRIVED);
63
+ function smartReady(metadata: SdkUpdateMetadata) {
64
+ if (!readiness.isReady() && !usesSegmentsSync(storage)) readiness.segments.emit(SDK_SEGMENTS_ARRIVED, metadata);
64
65
  }
65
66
  if (!usesSegmentsSync(storage)) setTimeout(smartReady, 0);
66
67
  else readiness.splits.once(SDK_SPLITS_ARRIVED, smartReady);
@@ -9,6 +9,7 @@ import { MySegmentsData } from '../types';
9
9
  import { IMembershipsResponse } from '../../../dtos/types';
10
10
  import { MEMBERSHIPS_LS_UPDATE } from '../../streaming/constants';
11
11
  import { usesSegmentsSync } from '../../../storages/AbstractSplitsCacheSync';
12
+ import { SdkUpdateMetadataKeys } from '../../../../types/splitio';
12
13
 
13
14
  type IMySegmentsUpdater = (segmentsData?: MySegmentsData, noCache?: boolean, till?: number) => Promise<boolean>
14
15
 
@@ -56,7 +57,7 @@ export function mySegmentsUpdaterFactory(
56
57
  // Notify update if required
57
58
  if (usesSegmentsSync(storage) && (shouldNotifyUpdate || readyOnAlreadyExistentState)) {
58
59
  readyOnAlreadyExistentState = false;
59
- segmentsEventEmitter.emit(SDK_SEGMENTS_ARRIVED);
60
+ segmentsEventEmitter.emit(SDK_SEGMENTS_ARRIVED, { type: SdkUpdateMetadataKeys.SEGMENTS_UPDATE, names: [] });
60
61
  }
61
62
  }
62
63
 
@@ -5,6 +5,8 @@ import { SDK_SEGMENTS_ARRIVED } from '../../../readiness/constants';
5
5
  import { ILogger } from '../../../logger/types';
6
6
  import { LOG_PREFIX_INSTANTIATION, LOG_PREFIX_SYNC_SEGMENTS } from '../../../logger/constants';
7
7
  import { timeout } from '../../../utils/promise/timeout';
8
+ import { SdkUpdateMetadata, SdkUpdateMetadataKeys } from '../../../../types/splitio';
9
+
8
10
 
9
11
  type ISegmentChangesUpdater = (fetchOnlyNew?: boolean, segmentName?: string, noCache?: boolean, till?: number) => Promise<boolean>
10
12
 
@@ -37,7 +39,7 @@ export function segmentChangesUpdaterFactory(
37
39
 
38
40
  function updateSegment(segmentName: string, noCache?: boolean, till?: number, fetchOnlyNew?: boolean, retries?: number): Promise<boolean> {
39
41
  log.debug(`${LOG_PREFIX_SYNC_SEGMENTS}Processing segment ${segmentName}`);
40
- let sincePromise = Promise.resolve(segments.getChangeNumber(segmentName));
42
+ const sincePromise = Promise.resolve(segments.getChangeNumber(segmentName));
41
43
 
42
44
  return sincePromise.then(since => {
43
45
  // if fetchOnlyNew flag, avoid processing already fetched segments
@@ -83,7 +85,11 @@ export function segmentChangesUpdaterFactory(
83
85
  // if at least one segment fetch succeeded, mark segments ready
84
86
  if (shouldUpdateFlags.some(update => update) || readyOnAlreadyExistentState) {
85
87
  readyOnAlreadyExistentState = false;
86
- if (readiness) readiness.segments.emit(SDK_SEGMENTS_ARRIVED);
88
+ const metadata: SdkUpdateMetadata = {
89
+ type: SdkUpdateMetadataKeys.SEGMENTS_UPDATE,
90
+ names: []
91
+ };
92
+ if (readiness) readiness.segments.emit(SDK_SEGMENTS_ARRIVED, metadata);
87
93
  }
88
94
  return true;
89
95
  });
@@ -10,6 +10,7 @@ import { startsWith } from '../../../utils/lang';
10
10
  import { IN_RULE_BASED_SEGMENT, IN_SEGMENT, RULE_BASED_SEGMENT, STANDARD_SEGMENT } from '../../../utils/constants';
11
11
  import { setToArray } from '../../../utils/lang/sets';
12
12
  import { SPLIT_UPDATE } from '../../streaming/constants';
13
+ import { SdkUpdateMetadata, SdkUpdateMetadataKeys } from '../../../../types/splitio';
13
14
 
14
15
  export type InstantUpdate = { payload: ISplit | IRBSegment, changeNumber: number, type: string };
15
16
  type SplitChangesUpdater = (noCache?: boolean, till?: number, instantUpdate?: InstantUpdate) => Promise<boolean>
@@ -54,7 +55,8 @@ export function parseSegments(ruleEntity: ISplit | IRBSegment, matcherType: type
54
55
 
55
56
  interface ISplitMutations<T extends ISplit | IRBSegment> {
56
57
  added: T[],
57
- removed: T[]
58
+ removed: T[],
59
+ names: string[]
58
60
  }
59
61
 
60
62
  /**
@@ -95,9 +97,10 @@ export function computeMutation<T extends ISplit | IRBSegment>(rules: Array<T>,
95
97
  } else {
96
98
  accum.removed.push(ruleEntity);
97
99
  }
100
+ accum.names.push(ruleEntity.name);
98
101
 
99
102
  return accum;
100
- }, { added: [], removed: [] } as ISplitMutations<T>);
103
+ }, { added: [], removed: [], names: [] } as ISplitMutations<T>);
101
104
  }
102
105
 
103
106
  /**
@@ -165,9 +168,11 @@ export function splitChangesUpdaterFactory(
165
168
  .then((splitChanges: ISplitChangesResponse) => {
166
169
  const usedSegments = new Set<string>();
167
170
 
171
+ let updatedFlags: string[] = [];
168
172
  let ffUpdate: MaybeThenable<boolean> = false;
169
173
  if (splitChanges.ff) {
170
- const { added, removed } = computeMutation(splitChanges.ff.d, usedSegments, splitFiltersValidation);
174
+ const { added, removed, names } = computeMutation(splitChanges.ff.d, usedSegments, splitFiltersValidation);
175
+ updatedFlags = names;
171
176
  log.debug(SYNC_SPLITS_UPDATE, [added.length, removed.length]);
172
177
  ffUpdate = splits.update(added, removed, splitChanges.ff.t);
173
178
  }
@@ -192,8 +197,12 @@ export function splitChangesUpdaterFactory(
192
197
  return Promise.resolve(!splitsEventEmitter.splitsArrived || ((ffChanged || rbsChanged) && (isClientSide || checkAllSegmentsExist(segments))))
193
198
  .catch(() => false /** noop. just to handle a possible `checkAllSegmentsExist` rejection, before emitting SDK event */)
194
199
  .then(emitSplitsArrivedEvent => {
200
+ const metadata: SdkUpdateMetadata = {
201
+ type: updatedFlags.length > 0 ? SdkUpdateMetadataKeys.FLAGS_UPDATE : SdkUpdateMetadataKeys.SEGMENTS_UPDATE,
202
+ names: updatedFlags.length > 0 ? updatedFlags : []
203
+ };
195
204
  // emit SDK events
196
- if (emitSplitsArrivedEvent) splitsEventEmitter.emit(SDK_SPLITS_ARRIVED);
205
+ if (emitSplitsArrivedEvent) splitsEventEmitter.emit(SDK_SPLITS_ARRIVED, metadata);
197
206
  return true;
198
207
  });
199
208
  }
@@ -38,7 +38,7 @@ export function submitterManagerFactory(params: ISdkFactoryContextSync): ISubmit
38
38
  execute(onlyTelemetry?: boolean) {
39
39
  const promises = onlyTelemetry ? [] : submitters.map(submitter => submitter.execute());
40
40
  if (telemetrySubmitter) promises.push(telemetrySubmitter.execute());
41
- return Promise.all(promises);
41
+ return Promise.all(promises).then(() => { });
42
42
  },
43
43
 
44
44
  isExecuting() {
@@ -237,5 +237,5 @@ export type TelemetryConfigStatsPayload = TelemetryConfigStats & {
237
237
  export interface ISubmitterManager extends ISyncTask {
238
238
  start(onlyTelemetry?: boolean): void,
239
239
  stop(allExceptTelemetry?: boolean): void,
240
- execute(onlyTelemetry?: boolean): Promise<any>
240
+ execute(onlyTelemetry?: boolean): Promise<void>
241
241
  }
package/src/sync/types.ts CHANGED
@@ -37,7 +37,7 @@ export interface ISyncTask<Input extends any[] = [], Output = any> extends ITask
37
37
  /** SyncManager */
38
38
 
39
39
  export interface ISyncManager extends ITask {
40
- flush(): Promise<any>,
40
+ flush(): Promise<void>,
41
41
  pushManager?: IPushManager,
42
42
  pollingManager?: IPollingManager,
43
43
  submitterManager?: ISubmitterManager
@@ -492,6 +492,52 @@ declare namespace SplitIO {
492
492
  removeItem(key: string): void | Promise<void>;
493
493
  }
494
494
 
495
+ /**
496
+ * Metadata keys for SDK update events.
497
+ */
498
+ const enum SdkUpdateMetadataKeys {
499
+ /**
500
+ * The update event emitted when the SDK cache is updated with new data for flags.
501
+ */
502
+ FLAGS_UPDATE = 'FLAGS_UPDATE',
503
+ /**
504
+ * The update event emitted when the SDK cache is updated with new data for segments.
505
+ */
506
+ SEGMENTS_UPDATE = 'SEGMENTS_UPDATE'
507
+ }
508
+
509
+ /**
510
+ * Metadata for the update event emitted when the SDK cache is updated with new data for flags or segments.
511
+ */
512
+ type SdkUpdateMetadata = {
513
+ /**
514
+ * The type of update event.
515
+ */
516
+ type: SdkUpdateMetadataKeys.FLAGS_UPDATE | SdkUpdateMetadataKeys.SEGMENTS_UPDATE
517
+ /**
518
+ * The names of the flags or segments that were updated.
519
+ */
520
+ names: string[]
521
+ }
522
+
523
+ /**
524
+ * Metadata for the ready events emitted when the SDK is ready to evaluate feature flags.
525
+ */
526
+ type SdkReadyMetadata = {
527
+ /**
528
+ * Indicates whether the SDK was loaded from cache initially.
529
+ * - `true` when SDK_READY_FROM_CACHE is emitted from cache (before SDK_READY)
530
+ * - `true` when SDK_READY is emitted and the SDK was ready from cache first
531
+ * - `false` when SDK_READY_FROM_CACHE is emitted because SDK became ready without cache
532
+ * - `false` when SDK_READY is emitted and the SDK was not ready from cache
533
+ */
534
+ initialCacheLoad: boolean
535
+ /**
536
+ * Timestamp in milliseconds since epoch when the event was emitted.
537
+ */
538
+ lastUpdateTimestamp: number
539
+ }
540
+
495
541
  /**
496
542
  * EventEmitter interface based on a subset of the Node.js EventEmitter methods.
497
543
  */
@@ -509,8 +555,17 @@ declare namespace SplitIO {
509
555
  * @see {@link https://nodejs.org/api/events.html}
510
556
  */
511
557
  interface EventEmitter extends IEventEmitter {
558
+ addListener(event: EventConsts['SDK_READY'], listener: (metadata: SdkReadyMetadata) => void): this;
559
+ addListener(event: EventConsts['SDK_READY_FROM_CACHE'], listener: (metadata: SdkReadyMetadata) => void): this;
560
+ addListener(event: EventConsts['SDK_UPDATE'], listener: (metadata: SdkUpdateMetadata) => void): this;
512
561
  addListener(event: string | symbol, listener: (...args: any[]) => void): this;
562
+ on(event: EventConsts['SDK_READY'], listener: (metadata: SdkReadyMetadata) => void): this;
563
+ on(event: EventConsts['SDK_READY_FROM_CACHE'], listener: (metadata: SdkReadyMetadata) => void): this;
564
+ on(event: EventConsts['SDK_UPDATE'], listener: (metadata: SdkUpdateMetadata) => void): this;
513
565
  on(event: string | symbol, listener: (...args: any[]) => void): this;
566
+ once(event: EventConsts['SDK_READY'], listener: (metadata: SdkReadyMetadata) => void): this;
567
+ once(event: EventConsts['SDK_READY_FROM_CACHE'], listener: (metadata: SdkReadyMetadata) => void): this;
568
+ once(event: EventConsts['SDK_UPDATE'], listener: (metadata: SdkUpdateMetadata) => void): this;
514
569
  once(event: string | symbol, listener: (...args: any[]) => void): this;
515
570
  removeListener(event: string | symbol, listener: (...args: any[]) => void): this;
516
571
  off(event: string | symbol, listener: (...args: any[]) => void): this;