@splitsoftware/splitio-commons 1.12.1-rc.4 → 1.12.1-rc.5

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 (74) hide show
  1. package/CHANGES.txt +1 -1
  2. package/cjs/logger/constants.js +3 -3
  3. package/cjs/logger/messages/warn.js +2 -2
  4. package/cjs/sdkClient/client.js +11 -8
  5. package/cjs/sdkClient/clientInputValidation.js +6 -6
  6. package/cjs/sdkManager/index.js +6 -6
  7. package/cjs/storages/KeyBuilder.js +13 -1
  8. package/cjs/storages/KeyBuilderCS.js +1 -4
  9. package/cjs/storages/inLocalStorage/SplitsCacheInLocal.js +12 -16
  10. package/cjs/storages/inLocalStorage/index.js +1 -1
  11. package/cjs/storages/inRedis/SplitsCacheInRedis.js +2 -2
  12. package/cjs/storages/inRedis/index.js +1 -1
  13. package/cjs/storages/pluggable/SplitsCachePluggable.js +2 -2
  14. package/cjs/storages/pluggable/index.js +14 -3
  15. package/cjs/trackers/eventTracker.js +4 -4
  16. package/cjs/utils/lang/sets.js +3 -3
  17. package/cjs/utils/settingsValidation/index.js +1 -1
  18. package/cjs/utils/settingsValidation/mode.js +10 -3
  19. package/cjs/utils/settingsValidation/splitFilters.js +20 -19
  20. package/esm/logger/constants.js +2 -2
  21. package/esm/logger/messages/warn.js +2 -2
  22. package/esm/sdkClient/client.js +11 -8
  23. package/esm/sdkClient/clientInputValidation.js +7 -7
  24. package/esm/sdkManager/index.js +6 -6
  25. package/esm/storages/KeyBuilder.js +11 -0
  26. package/esm/storages/KeyBuilderCS.js +1 -4
  27. package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +12 -16
  28. package/esm/storages/inLocalStorage/index.js +1 -1
  29. package/esm/storages/inRedis/SplitsCacheInRedis.js +3 -3
  30. package/esm/storages/inRedis/index.js +1 -1
  31. package/esm/storages/pluggable/SplitsCachePluggable.js +3 -3
  32. package/esm/storages/pluggable/index.js +15 -4
  33. package/esm/trackers/eventTracker.js +4 -4
  34. package/esm/utils/lang/sets.js +1 -1
  35. package/esm/utils/settingsValidation/index.js +2 -2
  36. package/esm/utils/settingsValidation/mode.js +7 -1
  37. package/esm/utils/settingsValidation/splitFilters.js +11 -10
  38. package/package.json +1 -1
  39. package/src/logger/constants.ts +2 -2
  40. package/src/logger/messages/warn.ts +2 -2
  41. package/src/sdkClient/client.ts +11 -8
  42. package/src/sdkClient/clientInputValidation.ts +7 -7
  43. package/src/sdkManager/index.ts +6 -6
  44. package/src/storages/KeyBuilder.ts +14 -1
  45. package/src/storages/KeyBuilderCS.ts +1 -5
  46. package/src/storages/KeyBuilderSS.ts +4 -4
  47. package/src/storages/inLocalStorage/SplitsCacheInLocal.ts +16 -14
  48. package/src/storages/inLocalStorage/index.ts +1 -1
  49. package/src/storages/inRedis/SplitsCacheInRedis.ts +3 -3
  50. package/src/storages/inRedis/index.ts +1 -1
  51. package/src/storages/pluggable/SplitsCachePluggable.ts +3 -3
  52. package/src/storages/pluggable/index.ts +15 -5
  53. package/src/storages/types.ts +3 -3
  54. package/src/trackers/eventTracker.ts +4 -4
  55. package/src/utils/lang/sets.ts +1 -1
  56. package/src/utils/murmur3/murmur3.ts +0 -1
  57. package/src/utils/settingsValidation/index.ts +2 -2
  58. package/src/utils/settingsValidation/mode.ts +8 -1
  59. package/src/utils/settingsValidation/splitFilters.ts +11 -10
  60. package/types/logger/constants.d.ts +2 -2
  61. package/types/storages/AbstractSplitsCache.d.ts +46 -0
  62. package/types/storages/KeyBuilder.d.ts +8 -1
  63. package/types/storages/KeyBuilderCS.d.ts +0 -1
  64. package/types/storages/KeyBuilderSS.d.ts +4 -4
  65. package/types/storages/inLocalStorage/SplitsCacheInLocal.d.ts +5 -5
  66. package/types/utils/lang/sets.d.ts +1 -1
  67. package/types/utils/settingsValidation/mode.d.ts +5 -1
  68. package/types/utils/settingsValidation/splitFilters.d.ts +1 -1
  69. package/cjs/trackers/impressionObserver/utils.js +0 -11
  70. package/cjs/utils/redis/RedisMock.js +0 -31
  71. package/esm/trackers/impressionObserver/utils.js +0 -7
  72. package/esm/utils/redis/RedisMock.js +0 -28
  73. package/src/trackers/impressionObserver/utils.ts +0 -9
  74. package/src/utils/redis/RedisMock.ts +0 -31
@@ -16,8 +16,8 @@ import { CONTROL, CONTROL_WITH_CONFIG, GET_TREATMENT, GET_TREATMENTS, GET_TREATM
16
16
  import { IReadinessManager } from '../readiness/types';
17
17
  import { MaybeThenable } from '../dtos/types';
18
18
  import { ISettings, SplitIO } from '../types';
19
- import { isStorageSync } from '../trackers/impressionObserver/utils';
20
- import { flagSetsAreValid } from '../utils/settingsValidation/splitFilters';
19
+ import { isConsumerMode } from '../utils/settingsValidation/mode';
20
+ import { validateFlagSets } from '../utils/settingsValidation/splitFilters';
21
21
 
22
22
  /**
23
23
  * Decorator that validates the input before actually executing the client methods.
@@ -25,8 +25,8 @@ import { flagSetsAreValid } from '../utils/settingsValidation/splitFilters';
25
25
  */
26
26
  export function clientInputValidationDecorator<TClient extends SplitIO.IClient | SplitIO.IAsyncClient>(settings: ISettings, client: TClient, readinessManager: IReadinessManager): TClient {
27
27
 
28
- const log = settings.log;
29
- const isSync = isStorageSync(settings);
28
+ const { log, mode } = settings;
29
+ const isAsync = isConsumerMode(mode);
30
30
 
31
31
  /**
32
32
  * Avoid repeating this validations code
@@ -42,7 +42,7 @@ export function clientInputValidationDecorator<TClient extends SplitIO.IClient |
42
42
  const attributes = validateAttributes(log, maybeAttributes, methodName);
43
43
  const isNotDestroyed = validateIfNotDestroyed(log, readinessManager, methodName);
44
44
  if (maybeFlagSetNameOrNames) {
45
- flagSetOrFlagSets = flagSetsAreValid(log, methodName, maybeFlagSetNameOrNames, settings.sync.__splitFiltersValidation.groupedFilters.bySet);
45
+ flagSetOrFlagSets = validateFlagSets(log, methodName, maybeFlagSetNameOrNames, settings.sync.__splitFiltersValidation.groupedFilters.bySet);
46
46
  }
47
47
 
48
48
  validateIfOperational(log, readinessManager, methodName, splitOrSplits);
@@ -59,7 +59,7 @@ export function clientInputValidationDecorator<TClient extends SplitIO.IClient |
59
59
  }
60
60
 
61
61
  function wrapResult<T>(value: T): MaybeThenable<T> {
62
- return isSync ? value : Promise.resolve(value);
62
+ return isAsync ? Promise.resolve(value) : value;
63
63
  }
64
64
 
65
65
  function getTreatment(maybeKey: SplitIO.SplitKey, maybeFeatureFlagName: string, maybeAttributes?: SplitIO.Attributes) {
@@ -159,7 +159,7 @@ export function clientInputValidationDecorator<TClient extends SplitIO.IClient |
159
159
  if (isNotDestroyed && key && tt && event && eventValue !== false && properties !== false) { // @ts-expect-error
160
160
  return client.track(key, tt, event, eventValue, properties, size);
161
161
  } else {
162
- return isSync ? false : Promise.resolve(false);
162
+ return isAsync ? Promise.resolve(false) : false;
163
163
  }
164
164
  }
165
165
 
@@ -6,7 +6,7 @@ import { ISplitsCacheAsync, ISplitsCacheSync } from '../storages/types';
6
6
  import { ISdkReadinessManager } from '../readiness/types';
7
7
  import { ISplit } from '../dtos/types';
8
8
  import { ISettings, SplitIO } from '../types';
9
- import { isStorageSync } from '../trackers/impressionObserver/utils';
9
+ import { isConsumerMode } from '../utils/settingsValidation/mode';
10
10
  import { SPLIT_FN_LABEL, SPLITS_FN_LABEL, NAMES_FN_LABEL } from '../utils/constants';
11
11
 
12
12
  function collectTreatments(splitObject: ISplit) {
@@ -51,8 +51,8 @@ export function sdkManagerFactory<TSplitCache extends ISplitsCacheSync | ISplits
51
51
  { readinessManager, sdkStatus }: ISdkReadinessManager,
52
52
  ): TSplitCache extends ISplitsCacheAsync ? SplitIO.IAsyncManager : SplitIO.IManager {
53
53
 
54
- const log = settings.log;
55
- const isSync = isStorageSync(settings);
54
+ const { log, mode } = settings;
55
+ const isAsync = isConsumerMode(mode);
56
56
 
57
57
  return objectAssign(
58
58
  // Proto-linkage of the readiness Event Emitter
@@ -64,7 +64,7 @@ export function sdkManagerFactory<TSplitCache extends ISplitsCacheSync | ISplits
64
64
  split(featureFlagName: string) {
65
65
  const splitName = validateSplit(log, featureFlagName, SPLIT_FN_LABEL);
66
66
  if (!validateIfNotDestroyed(log, readinessManager, SPLIT_FN_LABEL) || !validateIfOperational(log, readinessManager, SPLIT_FN_LABEL) || !splitName) {
67
- return isSync ? null : Promise.resolve(null);
67
+ return isAsync ? Promise.resolve(null) : null;
68
68
  }
69
69
 
70
70
  const split = splits.getSplit(splitName);
@@ -85,7 +85,7 @@ export function sdkManagerFactory<TSplitCache extends ISplitsCacheSync | ISplits
85
85
  */
86
86
  splits() {
87
87
  if (!validateIfNotDestroyed(log, readinessManager, SPLITS_FN_LABEL) || !validateIfOperational(log, readinessManager, SPLITS_FN_LABEL)) {
88
- return isSync ? [] : Promise.resolve([]);
88
+ return isAsync ? Promise.resolve([]) : [];
89
89
  }
90
90
  const currentSplits = splits.getAll();
91
91
 
@@ -98,7 +98,7 @@ export function sdkManagerFactory<TSplitCache extends ISplitsCacheSync | ISplits
98
98
  */
99
99
  names() {
100
100
  if (!validateIfNotDestroyed(log, readinessManager, NAMES_FN_LABEL) || !validateIfOperational(log, readinessManager, NAMES_FN_LABEL)) {
101
- return isSync ? [] : Promise.resolve([]);
101
+ return isAsync ? Promise.resolve([]) : [];
102
102
  }
103
103
  const splitNames = splits.getSplitNames();
104
104
 
@@ -1,4 +1,6 @@
1
+ import { ISettings } from '../types';
1
2
  import { startsWith } from '../utils/lang';
3
+ import { hash } from '../utils/murmur3/murmur3';
2
4
 
3
5
  const everythingAtTheEnd = /[^.]+$/;
4
6
 
@@ -10,7 +12,7 @@ export function validatePrefix(prefix: unknown) {
10
12
 
11
13
  export class KeyBuilder {
12
14
 
13
- protected readonly prefix: string;
15
+ readonly prefix: string;
14
16
 
15
17
  constructor(prefix: string = DEFAULT_PREFIX) {
16
18
  this.prefix = prefix;
@@ -73,4 +75,15 @@ export class KeyBuilder {
73
75
  }
74
76
  }
75
77
 
78
+ buildHashKey() {
79
+ return `${this.prefix}.hash`;
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Generates a murmur32 hash based on the authorization key and the feature flags filter query.
85
+ * The hash is in hexadecimal format (8 characters max, 32 bits).
86
+ */
87
+ export function getStorageHash(settings: ISettings) {
88
+ return hash(`${settings.core.authorizationKey}::${settings.sync.__splitFiltersValidation.queryString}`).toString(16);
76
89
  }
@@ -9,7 +9,7 @@ export class KeyBuilderCS extends KeyBuilder {
9
9
  constructor(prefix: string, matchingKey: string) {
10
10
  super(prefix);
11
11
  this.matchingKey = matchingKey;
12
- this.regexSplitsCacheKey = new RegExp(`^${prefix}\\.(splits?|trafficType)\\.`);
12
+ this.regexSplitsCacheKey = new RegExp(`^${prefix}\\.`);
13
13
  }
14
14
 
15
15
  /**
@@ -45,8 +45,4 @@ export class KeyBuilderCS extends KeyBuilder {
45
45
  isSplitsCacheKey(key: string) {
46
46
  return this.regexSplitsCacheKey.test(key);
47
47
  }
48
-
49
- buildSplitsFilterQueryKey() {
50
- return `${this.prefix}.splits.filterQuery`;
51
- }
52
48
  }
@@ -16,10 +16,10 @@ export const METHOD_NAMES: Record<Method, string> = {
16
16
 
17
17
  export class KeyBuilderSS extends KeyBuilder {
18
18
 
19
- latencyPrefix: string;
20
- exceptionPrefix: string;
21
- initPrefix: string;
22
- private versionablePrefix: string;
19
+ readonly latencyPrefix: string;
20
+ readonly exceptionPrefix: string;
21
+ readonly initPrefix: string;
22
+ private readonly versionablePrefix: string;
23
23
 
24
24
  constructor(prefix: string, metadata: IMetadata) {
25
25
  super(prefix);
@@ -1,10 +1,12 @@
1
- import { ISplit, ISplitFiltersValidation } from '../../dtos/types';
1
+ import { ISplit } from '../../dtos/types';
2
2
  import { AbstractSplitsCacheSync, usesSegments } from '../AbstractSplitsCacheSync';
3
3
  import { isFiniteNumber, toNumber, isNaNNumber } from '../../utils/lang';
4
4
  import { KeyBuilderCS } from '../KeyBuilderCS';
5
5
  import { ILogger } from '../../logger/types';
6
6
  import { LOG_PREFIX } from './constants';
7
7
  import { ISet, _Set, setToArray } from '../../utils/lang/sets';
8
+ import { ISettings } from '../../types';
9
+ import { getStorageHash } from '../KeyBuilder';
8
10
 
9
11
  /**
10
12
  * ISplitsCacheSync implementation that stores split definitions in browser LocalStorage.
@@ -12,7 +14,8 @@ import { ISet, _Set, setToArray } from '../../utils/lang/sets';
12
14
  export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
13
15
 
14
16
  private readonly keys: KeyBuilderCS;
15
- private readonly splitFiltersValidation: ISplitFiltersValidation;
17
+ private readonly log: ILogger;
18
+ private readonly storageHash: string;
16
19
  private readonly flagSetsFilter: string[];
17
20
  private hasSync?: boolean;
18
21
  private updateNewFilter?: boolean;
@@ -22,11 +25,12 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
22
25
  * @param {number | undefined} expirationTimestamp
23
26
  * @param {ISplitFiltersValidation} splitFiltersValidation
24
27
  */
25
- constructor(private readonly log: ILogger, keys: KeyBuilderCS, expirationTimestamp?: number, splitFiltersValidation: ISplitFiltersValidation = { queryString: null, groupedFilters: { bySet: [], byName: [], byPrefix: [] }, validFilters: [] }) {
28
+ constructor(settings: ISettings, keys: KeyBuilderCS, expirationTimestamp?: number) {
26
29
  super();
27
30
  this.keys = keys;
28
- this.splitFiltersValidation = splitFiltersValidation;
29
- this.flagSetsFilter = this.splitFiltersValidation.groupedFilters.bySet;
31
+ this.log = settings.log;
32
+ this.storageHash = getStorageHash(settings);
33
+ this.flagSetsFilter = settings.sync.__splitFiltersValidation.groupedFilters.bySet;
30
34
 
31
35
  this._checkExpiration(expirationTimestamp);
32
36
 
@@ -142,12 +146,10 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
142
146
 
143
147
  // when using a new split query, we must update it at the store
144
148
  if (this.updateNewFilter) {
145
- this.log.info(LOG_PREFIX + 'Split filter query was modified. Updating cache.');
146
- const queryKey = this.keys.buildSplitsFilterQueryKey();
147
- const queryString = this.splitFiltersValidation.queryString;
149
+ this.log.info(LOG_PREFIX + 'SDK key or feature flag filter criteria was modified. Updating cache.');
150
+ const storageHashKey = this.keys.buildHashKey();
148
151
  try {
149
- if (queryString) localStorage.setItem(queryKey, queryString);
150
- else localStorage.removeItem(queryKey);
152
+ localStorage.setItem(storageHashKey, this.storageHash);
151
153
  } catch (e) {
152
154
  this.log.error(LOG_PREFIX + e);
153
155
  }
@@ -237,12 +239,12 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
237
239
  }
238
240
  }
239
241
 
242
+ // @TODO eventually remove `_checkFilterQuery`. Cache should be cleared at the storage level, reusing same logic than PluggableStorage
240
243
  private _checkFilterQuery() {
241
- const { queryString } = this.splitFiltersValidation;
242
- const queryKey = this.keys.buildSplitsFilterQueryKey();
243
- const currentQueryString = localStorage.getItem(queryKey);
244
+ const storageHashKey = this.keys.buildHashKey();
245
+ const storageHash = localStorage.getItem(storageHashKey);
244
246
 
245
- if (currentQueryString !== queryString) {
247
+ if (storageHash !== this.storageHash) {
246
248
  try {
247
249
  // mark cache to update the new query filter on first successful splits fetch
248
250
  this.updateNewFilter = true;
@@ -41,7 +41,7 @@ export function InLocalStorage(options: InLocalStorageOptions = {}): IStorageSyn
41
41
  const keys = new KeyBuilderCS(prefix, matchingKey as string);
42
42
  const expirationTimestamp = Date.now() - DEFAULT_CACHE_EXPIRATION_IN_MILLIS;
43
43
 
44
- const splits = new SplitsCacheInLocal(log, keys, expirationTimestamp, __splitFiltersValidation);
44
+ const splits = new SplitsCacheInLocal(settings, keys, expirationTimestamp);
45
45
  const segments = new MySegmentsCacheInLocal(log, keys);
46
46
 
47
47
  return {
@@ -4,7 +4,7 @@ import { ILogger } from '../../logger/types';
4
4
  import { LOG_PREFIX } from './constants';
5
5
  import { ISplit, ISplitFiltersValidation } from '../../dtos/types';
6
6
  import { AbstractSplitsCacheAsync } from '../AbstractSplitsCacheAsync';
7
- import { ISet, _Set, returnListDifference } from '../../utils/lang/sets';
7
+ import { ISet, _Set, returnDifference } from '../../utils/lang/sets';
8
8
  import type { RedisAdapter } from './RedisAdapter';
9
9
 
10
10
  /**
@@ -60,9 +60,9 @@ export class SplitsCacheInRedis extends AbstractSplitsCacheAsync {
60
60
  }
61
61
 
62
62
  private _updateFlagSets(featureFlagName: string, flagSetsOfRemovedFlag?: string[], flagSetsOfAddedFlag?: string[]) {
63
- const removeFromFlagSets = returnListDifference(flagSetsOfRemovedFlag, flagSetsOfAddedFlag);
63
+ const removeFromFlagSets = returnDifference(flagSetsOfRemovedFlag, flagSetsOfAddedFlag);
64
64
 
65
- let addToFlagSets = returnListDifference(flagSetsOfAddedFlag, flagSetsOfRemovedFlag);
65
+ let addToFlagSets = returnDifference(flagSetsOfAddedFlag, flagSetsOfRemovedFlag);
66
66
  if (this.flagSetsFilter.length > 0) {
67
67
  addToFlagSets = addToFlagSets.filter(flagSet => {
68
68
  return this.flagSetsFilter.some(filterFlagSet => filterFlagSet === flagSet);
@@ -45,7 +45,7 @@ export function InRedisStorage(options: InRedisStorageOptions = {}): IStorageAsy
45
45
  });
46
46
 
47
47
  return {
48
- splits: new SplitsCacheInRedis(log, keys, redisClient),
48
+ splits: new SplitsCacheInRedis(log, keys, redisClient, settings.sync.__splitFiltersValidation),
49
49
  segments: new SegmentsCacheInRedis(log, keys, redisClient),
50
50
  impressions: new ImpressionsCacheInRedis(log, keys.buildImpressionsKey(), redisClient, metadata),
51
51
  impressionCounts: impressionCountsCache,
@@ -5,7 +5,7 @@ import { ILogger } from '../../logger/types';
5
5
  import { ISplit, ISplitFiltersValidation } from '../../dtos/types';
6
6
  import { LOG_PREFIX } from './constants';
7
7
  import { AbstractSplitsCacheAsync } from '../AbstractSplitsCacheAsync';
8
- import { ISet, _Set, returnListDifference } from '../../utils/lang/sets';
8
+ import { ISet, _Set, returnDifference } from '../../utils/lang/sets';
9
9
 
10
10
  /**
11
11
  * ISplitsCacheAsync implementation for pluggable storages.
@@ -44,9 +44,9 @@ export class SplitsCachePluggable extends AbstractSplitsCacheAsync {
44
44
  }
45
45
 
46
46
  private _updateFlagSets(featureFlagName: string, flagSetsOfRemovedFlag?: string[], flagSetsOfAddedFlag?: string[]) {
47
- const removeFromFlagSets = returnListDifference(flagSetsOfRemovedFlag, flagSetsOfAddedFlag);
47
+ const removeFromFlagSets = returnDifference(flagSetsOfRemovedFlag, flagSetsOfAddedFlag);
48
48
 
49
- let addToFlagSets = returnListDifference(flagSetsOfAddedFlag, flagSetsOfRemovedFlag);
49
+ let addToFlagSets = returnDifference(flagSetsOfAddedFlag, flagSetsOfRemovedFlag);
50
50
  if (this.flagSetsFilter.length > 0) {
51
51
  addToFlagSets = addToFlagSets.filter(flagSet => {
52
52
  return this.flagSetsFilter.some(filterFlagSet => filterFlagSet === flagSet);
@@ -7,7 +7,7 @@ import { ImpressionsCachePluggable } from './ImpressionsCachePluggable';
7
7
  import { EventsCachePluggable } from './EventsCachePluggable';
8
8
  import { wrapperAdapter, METHODS_TO_PROMISE_WRAP } from './wrapperAdapter';
9
9
  import { isObject } from '../../utils/lang';
10
- import { validatePrefix } from '../KeyBuilder';
10
+ import { getStorageHash, validatePrefix } from '../KeyBuilder';
11
11
  import { CONSUMER_PARTIAL_MODE, DEBUG, NONE, STORAGE_PLUGGABLE } from '../../utils/constants';
12
12
  import { ImpressionsCacheInMemory } from '../inMemory/ImpressionsCacheInMemory';
13
13
  import { EventsCacheInMemory } from '../inMemory/EventsCacheInMemory';
@@ -90,13 +90,23 @@ export function PluggableStorage(options: PluggableStorageOptions): IStorageAsyn
90
90
 
91
91
  // Connects to wrapper and emits SDK_READY event on main client
92
92
  const connectPromise = wrapper.connect().then(() => {
93
- onReadyCb();
94
-
95
- // Start periodic flush of async storages if not running synchronizer (producer mode)
96
- if (!isSyncronizer) {
93
+ if (isSyncronizer) {
94
+ // In standalone or producer mode, clear storage if SDK key or feature flag filter has changed
95
+ return wrapper.get(keys.buildHashKey()).then((hash) => {
96
+ const currentHash = getStorageHash(settings);
97
+ if (hash !== currentHash) {
98
+ return wrapper.getKeysByPrefix(`${keys.prefix}.`).then(storageKeys => {
99
+ return Promise.all(storageKeys.map(storageKey => wrapper.del(storageKey)));
100
+ }).then(() => wrapper.set(keys.buildHashKey(), currentHash));
101
+ }
102
+ }).then(onReadyCb);
103
+ } else {
104
+ // Start periodic flush of async storages if not running synchronizer (producer mode)
97
105
  if (impressionCountsCache && (impressionCountsCache as ImpressionCountsCachePluggable).start) (impressionCountsCache as ImpressionCountsCachePluggable).start();
98
106
  if (uniqueKeysCache && (uniqueKeysCache as UniqueKeysCachePluggable).start) (uniqueKeysCache as UniqueKeysCachePluggable).start();
99
107
  if (telemetry && (telemetry as ITelemetryCacheAsync).recordConfig) (telemetry as ITelemetryCacheAsync).recordConfig();
108
+
109
+ onReadyCb();
100
110
  }
101
111
  }).catch((e) => {
102
112
  e = e || new Error('Error connecting wrapper');
@@ -457,7 +457,7 @@ export interface IStorageBase<
457
457
  TEventsCache extends IEventsCacheBase,
458
458
  TTelemetryCache extends ITelemetryCacheSync | ITelemetryCacheAsync,
459
459
  TUniqueKeysCache extends IUniqueKeysCacheBase
460
- > {
460
+ > {
461
461
  splits: TSplitsCache,
462
462
  segments: TSegmentsCache,
463
463
  impressions: TImpressionsCache,
@@ -477,7 +477,7 @@ export interface IStorageSync extends IStorageBase<
477
477
  IEventsCacheSync,
478
478
  ITelemetryCacheSync,
479
479
  IUniqueKeysCacheSync
480
- > { }
480
+ > { }
481
481
 
482
482
  export interface IStorageAsync extends IStorageBase<
483
483
  ISplitsCacheAsync,
@@ -487,7 +487,7 @@ export interface IStorageAsync extends IStorageBase<
487
487
  IEventsCacheAsync | IEventsCacheSync,
488
488
  ITelemetryCacheAsync | ITelemetryCacheSync,
489
489
  IUniqueKeysCacheBase
490
- > { }
490
+ > { }
491
491
 
492
492
  /** StorageFactory */
493
493
 
@@ -5,7 +5,7 @@ import { IEventsHandler, IEventTracker } from './types';
5
5
  import { ISettings, SplitIO } from '../types';
6
6
  import { EVENTS_TRACKER_SUCCESS, ERROR_EVENTS_TRACKER } from '../logger/constants';
7
7
  import { CONSENT_DECLINED, DROPPED, QUEUED } from '../utils/constants';
8
- import { isStorageSync } from './impressionObserver/utils';
8
+ import { isConsumerMode } from '../utils/settingsValidation/mode';
9
9
 
10
10
  /**
11
11
  * Event tracker stores events in cache and pass them to the integrations manager if provided.
@@ -20,8 +20,8 @@ export function eventTrackerFactory(
20
20
  telemetryCache?: ITelemetryCacheSync | ITelemetryCacheAsync
21
21
  ): IEventTracker {
22
22
 
23
- const log = settings.log;
24
- const isSync = isStorageSync(settings);
23
+ const { log, mode } = settings;
24
+ const isAsync = isConsumerMode(mode);
25
25
 
26
26
  function queueEventsCallback(eventData: SplitIO.EventData, tracked: boolean) {
27
27
  const { eventTypeId, trafficTypeName, key, value, timestamp, properties } = eventData;
@@ -50,7 +50,7 @@ export function eventTrackerFactory(
50
50
  return {
51
51
  track(eventData: SplitIO.EventData, size?: number) {
52
52
  if (settings.userConsent === CONSENT_DECLINED) {
53
- return isSync ? false : Promise.resolve(false);
53
+ return isAsync ? Promise.resolve(false) : false;
54
54
  }
55
55
 
56
56
  const tracked = eventsCache.track(eventData, size);
@@ -120,7 +120,7 @@ export function returnSetsUnion<T>(set: ISet<T>, set2: ISet<T>): ISet<T> {
120
120
  return result;
121
121
  }
122
122
 
123
- export function returnListDifference<T>(list: T[] = [], list2: T[] = []): T[] {
123
+ export function returnDifference<T>(list: T[] = [], list2: T[] = []): T[] {
124
124
  const result = new _Set(list);
125
125
  list2.forEach(item => {
126
126
  result.delete(item);
@@ -66,7 +66,6 @@ function hash32(key?: string, seed?: number) {
66
66
  }
67
67
 
68
68
  export function hash(str: string, seed?: number): number {
69
-
70
69
  return hash32(UTF16ToUTF8(str), seed as number >>> 0);
71
70
  }
72
71
 
@@ -1,5 +1,5 @@
1
1
  import { merge, get } from '../lang';
2
- import { mode } from './mode';
2
+ import { validateMode } from './mode';
3
3
  import { validateSplitFilters } from './splitFilters';
4
4
  import { STANDALONE_MODE, OPTIMIZED, LOCALHOST_MODE, DEBUG } from '../constants';
5
5
  import { validImpressionsMode } from './impressionsMode';
@@ -146,7 +146,7 @@ export function settingsValidation(config: unknown, validationParams: ISettingsV
146
146
 
147
147
  // ensure a valid SDK mode
148
148
  // @ts-ignore, modify readonly prop
149
- withDefaults.mode = mode(withDefaults.core.authorizationKey, withDefaults.mode);
149
+ withDefaults.mode = validateMode(withDefaults.core.authorizationKey, withDefaults.mode);
150
150
 
151
151
  // ensure a valid Storage based on mode defined.
152
152
  // @ts-ignore, modify readonly prop
@@ -1,6 +1,6 @@
1
1
  import { LOCALHOST_MODE, STANDALONE_MODE, PRODUCER_MODE, CONSUMER_MODE, CONSUMER_PARTIAL_MODE } from '../constants';
2
2
 
3
- export function mode(key: string, mode: string) {
3
+ export function validateMode(key: string, mode: string) {
4
4
  // Leaving the comparison as is, in case we change the mode name but not the setting.
5
5
  if (key === 'localhost') return LOCALHOST_MODE;
6
6
 
@@ -8,3 +8,10 @@ export function mode(key: string, mode: string) {
8
8
 
9
9
  return mode;
10
10
  }
11
+
12
+ /**
13
+ * Storage is async if mode is consumer or partial consumer
14
+ */
15
+ export function isConsumerMode(mode: string) {
16
+ return CONSUMER_MODE === mode || CONSUMER_PARTIAL_MODE === mode;
17
+ }
@@ -1,11 +1,11 @@
1
- import { CONSUMER_MODE, CONSUMER_PARTIAL_MODE } from '../constants';
2
1
  import { validateSplits } from '../inputValidation/splits';
3
2
  import { ISplitFiltersValidation } from '../../dtos/types';
4
3
  import { SplitIO } from '../../types';
5
4
  import { ILogger } from '../../logger/types';
6
- import { WARN_SPLITS_FILTER_IGNORED, WARN_SPLITS_FILTER_EMPTY, WARN_SPLITS_FILTER_INVALID, SETTINGS_SPLITS_FILTER, LOG_PREFIX_SETTINGS, ERROR_SETS_FILTER_EXCLUSIVE, WARN_SPLITS_FILTER_LOWERCASE_SET, WARN_SPLITS_FILTER_INVALID_SET, WARN_FLAGSET_NOT_CONFIGURED } from '../../logger/constants';
5
+ import { WARN_SPLITS_FILTER_IGNORED, WARN_SPLITS_FILTER_EMPTY, WARN_SPLITS_FILTER_INVALID, SETTINGS_SPLITS_FILTER, LOG_PREFIX_SETTINGS, ERROR_SETS_FILTER_EXCLUSIVE, WARN_LOWERCASE_FLAGSET, WARN_INVALID_FLAGSET, WARN_FLAGSET_NOT_CONFIGURED } from '../../logger/constants';
7
6
  import { objectAssign } from '../lang/objectAssign';
8
7
  import { find, uniq } from '../lang';
8
+ import { isConsumerMode } from './mode';
9
9
 
10
10
  // Split filters metadata.
11
11
  // Ordered according to their precedency when forming the filter query string: `&names=<values>&prefixes=<values>`
@@ -54,7 +54,7 @@ function validateSplitFilter(log: ILogger, type: SplitIO.SplitFilterType, values
54
54
  if (result) {
55
55
 
56
56
  if (type === 'bySet') {
57
- result = sanitizeFlagSets(log, result);
57
+ result = sanitizeFlagSets(log, result, LOG_PREFIX_SETTINGS);
58
58
  }
59
59
 
60
60
  // check max length
@@ -88,7 +88,7 @@ function queryStringBuilder(groupedFilters: Record<SplitIO.SplitFilterType, stri
88
88
  }
89
89
 
90
90
  /**
91
- * Sanitizes set names list taking in account:
91
+ * Sanitizes set names list taking into account:
92
92
  * - It should be lowercase
93
93
  * - Must adhere the following regular expression /^[a-z0-9][_a-z0-9]{0,49}$/ that means
94
94
  * - must start with a letter or number
@@ -98,20 +98,21 @@ function queryStringBuilder(groupedFilters: Record<SplitIO.SplitFilterType, stri
98
98
  *
99
99
  * @param {ILogger} log
100
100
  * @param {string[]} flagSets
101
+ * @param {string} method
101
102
  * @returns sanitized list of set names
102
103
  */
103
- function sanitizeFlagSets(log: ILogger, flagSets: string[]) {
104
+ function sanitizeFlagSets(log: ILogger, flagSets: string[], method: string) {
104
105
  let sanitizedSets = flagSets
105
106
  .map(flagSet => {
106
107
  if (CAPITAL_LETTERS_REGEX.test(flagSet)) {
107
- log.warn(WARN_SPLITS_FILTER_LOWERCASE_SET, [flagSet]);
108
+ log.warn(WARN_LOWERCASE_FLAGSET, [method, flagSet]);
108
109
  flagSet = flagSet.toLowerCase();
109
110
  }
110
111
  return flagSet;
111
112
  })
112
113
  .filter(flagSet => {
113
114
  if (!VALID_FLAGSET_REGEX.test(flagSet)) {
114
- log.warn(WARN_SPLITS_FILTER_INVALID_SET, [flagSet, VALID_FLAGSET_REGEX, flagSet]);
115
+ log.warn(WARN_INVALID_FLAGSET, [method, flagSet, VALID_FLAGSET_REGEX, flagSet]);
115
116
  return false;
116
117
  }
117
118
  if (typeof flagSet !== 'string') return false;
@@ -148,7 +149,7 @@ export function validateSplitFilters(log: ILogger, maybeSplitFilters: any, mode:
148
149
  // do nothing if `splitFilters` param is not a non-empty array or mode is not STANDALONE
149
150
  if (!maybeSplitFilters) return res;
150
151
  // Warn depending on the mode
151
- if (mode === CONSUMER_MODE || mode === CONSUMER_PARTIAL_MODE) {
152
+ if (isConsumerMode(mode)) {
152
153
  log.warn(WARN_SPLITS_FILTER_IGNORED);
153
154
  return res;
154
155
  }
@@ -188,9 +189,9 @@ export function validateSplitFilters(log: ILogger, maybeSplitFilters: any, mode:
188
189
  return res;
189
190
  }
190
191
 
191
- export function flagSetsAreValid(log: ILogger, method: string, flagSets: string[], flagSetsInConfig: string[]): string[] {
192
+ export function validateFlagSets(log: ILogger, method: string, flagSets: string[], flagSetsInConfig: string[]): string[] {
192
193
  const sets = validateSplits(log, flagSets, method, 'flag sets', 'flag set');
193
- let toReturn = sets ? sanitizeFlagSets(log, sets) : [];
194
+ let toReturn = sets ? sanitizeFlagSets(log, sets, method) : [];
194
195
  if (flagSetsInConfig.length > 0) {
195
196
  toReturn = toReturn.filter(flagSet => {
196
197
  if (flagSetsInConfig.indexOf(flagSet) > -1) {
@@ -95,8 +95,8 @@ export declare const WARN_SPLITS_FILTER_EMPTY = 221;
95
95
  export declare const WARN_SDK_KEY = 222;
96
96
  export declare const STREAMING_PARSING_MY_SEGMENTS_UPDATE_V2 = 223;
97
97
  export declare const STREAMING_PARSING_SPLIT_UPDATE = 224;
98
- export declare const WARN_SPLITS_FILTER_INVALID_SET = 225;
99
- export declare const WARN_SPLITS_FILTER_LOWERCASE_SET = 226;
98
+ export declare const WARN_INVALID_FLAGSET = 225;
99
+ export declare const WARN_LOWERCASE_FLAGSET = 226;
100
100
  export declare const WARN_FLAGSET_NOT_CONFIGURED = 227;
101
101
  export declare const WARN_FLAGSET_WITHOUT_FLAGS = 228;
102
102
  export declare const ERROR_ENGINE_COMBINER_IFELSEIF = 300;
@@ -0,0 +1,46 @@
1
+ import { ISplit, MaybeThenable } from '../dtos/types';
2
+ /**
3
+ * This class provides a skeletal implementation of the ISplitsCacheAsync interface
4
+ * to minimize the effort required to implement this interface.
5
+ */
6
+ export declare abstract class AbstractSplitsCache {
7
+ /**
8
+ * Check if the splits information is already stored in cache. This data can be preloaded.
9
+ * It is used as condition to emit SDK_SPLITS_CACHE_LOADED, and then SDK_READY_FROM_CACHE.
10
+ */
11
+ checkCache(): boolean;
12
+ protected abstract addSplit(name: string, split: ISplit): MaybeThenable<boolean>;
13
+ /**
14
+ * Add a list of splits.
15
+ * The returned promise is resolved when the operation success or rejected if it fails (e.g., wrapper operation fails).
16
+ */
17
+ protected addSplits(entries: [string, ISplit][]): Promise<boolean[]>;
18
+ protected abstract removeSplit(name: string): MaybeThenable<boolean>;
19
+ /**
20
+ * Remove a list of splits.
21
+ * The returned promise is resolved when the operation success, with a boolean array indicating if the splits existed or not.
22
+ * or rejected if it fails (e.g., wrapper operation fails).
23
+ */
24
+ protected removeSplits(names: string[]): Promise<boolean[]>;
25
+ protected abstract setChangeNumber(changeNumber: number): MaybeThenable<boolean | void>;
26
+ /**
27
+ * Updates the cache with the provided changeNumber, feature flags to add and feature flags to remove.
28
+ *
29
+ * @returns {Promise<boolean>} a promise that resolved once the operation is performed successfully. The fulfillment value is `true` if at least one feature flag was added, modified or removed; or `false` if there was no change.
30
+ * The promise will reject if some storage operation rejects.
31
+ */
32
+ update(changeNumber: number, toAdd: [string, ISplit][], toRemove?: string[]): Promise<boolean>;
33
+ abstract getSplit(name: string): MaybeThenable<ISplit | null>;
34
+ /**
35
+ * Kill `name` split and set `defaultTreatment` and `changeNumber`.
36
+ * Used for SPLIT_KILL push notifications.
37
+ *
38
+ * @param {string} name
39
+ * @param {string} defaultTreatment
40
+ * @param {number} changeNumber
41
+ * @returns {Promise} a promise that is resolved once the split kill operation is performed. The fulfillment value is a boolean: `true` if the operation successed updating the split or `false` if no split is updated,
42
+ * for instance, if the `changeNumber` is old, or if the split is not found (e.g., `/splitchanges` hasn't been fetched yet), or if the storage fails to apply the update.
43
+ * The promise will never be rejected.
44
+ */
45
+ killLocally(name: string, defaultTreatment: string, changeNumber: number): Promise<boolean>;
46
+ }
@@ -1,6 +1,7 @@
1
+ import { ISettings } from '../types';
1
2
  export declare function validatePrefix(prefix: unknown): string;
2
3
  export declare class KeyBuilder {
3
- protected readonly prefix: string;
4
+ readonly prefix: string;
4
5
  constructor(prefix?: string);
5
6
  buildTrafficTypeKey(trafficType: string): string;
6
7
  buildFlagSetKey(flagSet: string): string;
@@ -12,4 +13,10 @@ export declare class KeyBuilder {
12
13
  buildSegmentNameKey(segmentName: string): string;
13
14
  buildSegmentTillKey(segmentName: string): string;
14
15
  extractKey(builtKey: string): string;
16
+ buildHashKey(): string;
15
17
  }
18
+ /**
19
+ * Generates a murmur32 hash based on the authorization key and the feature flags filter query.
20
+ * The hash is in hexadecimal format (8 characters max, 32 bits).
21
+ */
22
+ export declare function getStorageHash(settings: ISettings): string;
@@ -12,5 +12,4 @@ export declare class KeyBuilderCS extends KeyBuilder {
12
12
  extractOldSegmentKey(builtSegmentKeyName: string): string | undefined;
13
13
  buildLastUpdatedKey(): string;
14
14
  isSplitsCacheKey(key: string): boolean;
15
- buildSplitsFilterQueryKey(): string;
16
15
  }
@@ -3,10 +3,10 @@ import { IMetadata } from '../dtos/types';
3
3
  import { Method } from '../sync/submitters/types';
4
4
  export declare const METHOD_NAMES: Record<Method, string>;
5
5
  export declare class KeyBuilderSS extends KeyBuilder {
6
- latencyPrefix: string;
7
- exceptionPrefix: string;
8
- initPrefix: string;
9
- private versionablePrefix;
6
+ readonly latencyPrefix: string;
7
+ readonly exceptionPrefix: string;
8
+ readonly initPrefix: string;
9
+ private readonly versionablePrefix;
10
10
  constructor(prefix: string, metadata: IMetadata);
11
11
  buildRegisteredSegmentsKey(): string;
12
12
  buildImpressionsKey(): string;