@splitsoftware/splitio-commons 1.12.1-rc.3 → 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 (78) 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 +27 -21
  5. package/cjs/sdkClient/clientInputValidation.js +21 -21
  6. package/cjs/sdkManager/index.js +13 -15
  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/constants/index.js +16 -2
  17. package/cjs/utils/lang/sets.js +3 -3
  18. package/cjs/utils/settingsValidation/index.js +1 -1
  19. package/cjs/utils/settingsValidation/mode.js +10 -3
  20. package/cjs/utils/settingsValidation/splitFilters.js +20 -19
  21. package/esm/logger/constants.js +2 -2
  22. package/esm/logger/messages/warn.js +2 -2
  23. package/esm/sdkClient/client.js +28 -22
  24. package/esm/sdkClient/clientInputValidation.js +23 -23
  25. package/esm/sdkManager/index.js +7 -9
  26. package/esm/storages/KeyBuilder.js +11 -0
  27. package/esm/storages/KeyBuilderCS.js +1 -4
  28. package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +12 -16
  29. package/esm/storages/inLocalStorage/index.js +1 -1
  30. package/esm/storages/inRedis/SplitsCacheInRedis.js +3 -3
  31. package/esm/storages/inRedis/index.js +1 -1
  32. package/esm/storages/pluggable/SplitsCachePluggable.js +3 -3
  33. package/esm/storages/pluggable/index.js +15 -4
  34. package/esm/trackers/eventTracker.js +4 -4
  35. package/esm/utils/constants/index.js +14 -0
  36. package/esm/utils/lang/sets.js +1 -1
  37. package/esm/utils/settingsValidation/index.js +2 -2
  38. package/esm/utils/settingsValidation/mode.js +7 -1
  39. package/esm/utils/settingsValidation/splitFilters.js +11 -10
  40. package/package.json +1 -1
  41. package/src/logger/constants.ts +2 -2
  42. package/src/logger/messages/warn.ts +2 -2
  43. package/src/sdkClient/client.ts +26 -23
  44. package/src/sdkClient/clientInputValidation.ts +23 -23
  45. package/src/sdkManager/index.ts +7 -10
  46. package/src/storages/KeyBuilder.ts +14 -1
  47. package/src/storages/KeyBuilderCS.ts +1 -5
  48. package/src/storages/KeyBuilderSS.ts +4 -4
  49. package/src/storages/inLocalStorage/SplitsCacheInLocal.ts +16 -14
  50. package/src/storages/inLocalStorage/index.ts +1 -1
  51. package/src/storages/inRedis/SplitsCacheInRedis.ts +3 -3
  52. package/src/storages/inRedis/index.ts +1 -1
  53. package/src/storages/pluggable/SplitsCachePluggable.ts +3 -3
  54. package/src/storages/pluggable/index.ts +15 -5
  55. package/src/storages/types.ts +3 -3
  56. package/src/trackers/eventTracker.ts +4 -4
  57. package/src/utils/constants/index.ts +16 -0
  58. package/src/utils/lang/sets.ts +1 -1
  59. package/src/utils/murmur3/murmur3.ts +0 -1
  60. package/src/utils/settingsValidation/index.ts +2 -2
  61. package/src/utils/settingsValidation/mode.ts +8 -1
  62. package/src/utils/settingsValidation/splitFilters.ts +11 -10
  63. package/types/logger/constants.d.ts +2 -2
  64. package/types/storages/AbstractSplitsCache.d.ts +46 -0
  65. package/types/storages/KeyBuilder.d.ts +8 -1
  66. package/types/storages/KeyBuilderCS.d.ts +0 -1
  67. package/types/storages/KeyBuilderSS.d.ts +4 -4
  68. package/types/storages/inLocalStorage/SplitsCacheInLocal.d.ts +5 -5
  69. package/types/utils/constants/index.d.ts +12 -0
  70. package/types/utils/lang/sets.d.ts +1 -1
  71. package/types/utils/settingsValidation/mode.d.ts +5 -1
  72. package/types/utils/settingsValidation/splitFilters.d.ts +1 -1
  73. package/cjs/trackers/impressionObserver/utils.js +0 -11
  74. package/cjs/utils/redis/RedisMock.js +0 -31
  75. package/esm/trackers/impressionObserver/utils.js +0 -7
  76. package/esm/utils/redis/RedisMock.js +0 -28
  77. package/src/trackers/impressionObserver/utils.ts +0 -9
  78. package/src/utils/redis/RedisMock.ts +0 -31
@@ -12,12 +12,12 @@ import {
12
12
  validateIfOperational
13
13
  } from '../utils/inputValidation';
14
14
  import { startsWith } from '../utils/lang';
15
- import { CONTROL, CONTROL_WITH_CONFIG } from '../utils/constants';
15
+ import { CONTROL, CONTROL_WITH_CONFIG, GET_TREATMENT, GET_TREATMENTS, GET_TREATMENTS_BY_FLAG_SET, GET_TREATMENTS_BY_FLAG_SETS, GET_TREATMENTS_WITH_CONFIG, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS, GET_TREATMENT_WITH_CONFIG, TRACK_FN_LABEL } from '../utils/constants';
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,14 +25,14 @@ 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
33
33
  */
34
34
  function validateEvaluationParams(maybeKey: SplitIO.SplitKey, maybeFeatureFlagNameOrNames: string | string[] | undefined, maybeAttributes: SplitIO.Attributes | undefined, methodName: string, maybeFlagSetNameOrNames?: string[]) {
35
- const multi = startsWith(methodName, 'getTreatments');
35
+ const multi = startsWith(methodName, GET_TREATMENTS);
36
36
  const key = validateKey(log, maybeKey, methodName);
37
37
  let splitOrSplits: string | string[] | false = false;
38
38
  let flagSetOrFlagSets: string[] = [];
@@ -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,11 +59,11 @@ 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) {
66
- const params = validateEvaluationParams(maybeKey, maybeFeatureFlagName, maybeAttributes, 'getTreatment');
66
+ const params = validateEvaluationParams(maybeKey, maybeFeatureFlagName, maybeAttributes, GET_TREATMENT);
67
67
 
68
68
  if (params.valid) {
69
69
  return client.getTreatment(params.key as SplitIO.SplitKey, params.splitOrSplits as string, params.attributes as SplitIO.Attributes | undefined);
@@ -73,7 +73,7 @@ export function clientInputValidationDecorator<TClient extends SplitIO.IClient |
73
73
  }
74
74
 
75
75
  function getTreatmentWithConfig(maybeKey: SplitIO.SplitKey, maybeFeatureFlagName: string, maybeAttributes?: SplitIO.Attributes) {
76
- const params = validateEvaluationParams(maybeKey, maybeFeatureFlagName, maybeAttributes, 'getTreatmentWithConfig');
76
+ const params = validateEvaluationParams(maybeKey, maybeFeatureFlagName, maybeAttributes, GET_TREATMENT_WITH_CONFIG);
77
77
 
78
78
  if (params.valid) {
79
79
  return client.getTreatmentWithConfig(params.key as SplitIO.SplitKey, params.splitOrSplits as string, params.attributes as SplitIO.Attributes | undefined);
@@ -83,7 +83,7 @@ export function clientInputValidationDecorator<TClient extends SplitIO.IClient |
83
83
  }
84
84
 
85
85
  function getTreatments(maybeKey: SplitIO.SplitKey, maybeFeatureFlagNames: string[], maybeAttributes?: SplitIO.Attributes) {
86
- const params = validateEvaluationParams(maybeKey, maybeFeatureFlagNames, maybeAttributes, 'getTreatments');
86
+ const params = validateEvaluationParams(maybeKey, maybeFeatureFlagNames, maybeAttributes, GET_TREATMENTS);
87
87
 
88
88
  if (params.valid) {
89
89
  return client.getTreatments(params.key as SplitIO.SplitKey, params.splitOrSplits as string[], params.attributes as SplitIO.Attributes | undefined);
@@ -96,7 +96,7 @@ export function clientInputValidationDecorator<TClient extends SplitIO.IClient |
96
96
  }
97
97
 
98
98
  function getTreatmentsWithConfig(maybeKey: SplitIO.SplitKey, maybeFeatureFlagNames: string[], maybeAttributes?: SplitIO.Attributes) {
99
- const params = validateEvaluationParams(maybeKey, maybeFeatureFlagNames, maybeAttributes, 'getTreatmentsWithConfig');
99
+ const params = validateEvaluationParams(maybeKey, maybeFeatureFlagNames, maybeAttributes, GET_TREATMENTS_WITH_CONFIG);
100
100
 
101
101
  if (params.valid) {
102
102
  return client.getTreatmentsWithConfig(params.key as SplitIO.SplitKey, params.splitOrSplits as string[], params.attributes as SplitIO.Attributes | undefined);
@@ -109,7 +109,7 @@ export function clientInputValidationDecorator<TClient extends SplitIO.IClient |
109
109
  }
110
110
 
111
111
  function getTreatmentsByFlagSets(maybeKey: SplitIO.SplitKey, maybeFlagSets: string[], maybeAttributes?: SplitIO.Attributes) {
112
- const params = validateEvaluationParams(maybeKey, undefined, maybeAttributes, 'getTreatmentsByFlagSets', maybeFlagSets);
112
+ const params = validateEvaluationParams(maybeKey, undefined, maybeAttributes, GET_TREATMENTS_BY_FLAG_SETS, maybeFlagSets);
113
113
 
114
114
  if (params.valid) {
115
115
  return client.getTreatmentsByFlagSets(params.key as SplitIO.SplitKey, params.flagSetOrFlagSets as string[], params.attributes as SplitIO.Attributes | undefined);
@@ -119,7 +119,7 @@ export function clientInputValidationDecorator<TClient extends SplitIO.IClient |
119
119
  }
120
120
 
121
121
  function getTreatmentsWithConfigByFlagSets(maybeKey: SplitIO.SplitKey, maybeFlagSets: string[], maybeAttributes?: SplitIO.Attributes) {
122
- const params = validateEvaluationParams(maybeKey, undefined, maybeAttributes, 'getTreatmentsWithConfigByFlagSets', maybeFlagSets);
122
+ const params = validateEvaluationParams(maybeKey, undefined, maybeAttributes, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS, maybeFlagSets);
123
123
 
124
124
  if (params.valid) {
125
125
  return client.getTreatmentsWithConfigByFlagSets(params.key as SplitIO.SplitKey, params.flagSetOrFlagSets as string[], params.attributes as SplitIO.Attributes | undefined);
@@ -129,7 +129,7 @@ export function clientInputValidationDecorator<TClient extends SplitIO.IClient |
129
129
  }
130
130
 
131
131
  function getTreatmentsByFlagSet(maybeKey: SplitIO.SplitKey, maybeFlagSet: string, maybeAttributes?: SplitIO.Attributes) {
132
- const params = validateEvaluationParams(maybeKey, undefined, maybeAttributes, 'getTreatmentsByFlagSet', [maybeFlagSet]);
132
+ const params = validateEvaluationParams(maybeKey, undefined, maybeAttributes, GET_TREATMENTS_BY_FLAG_SET, [maybeFlagSet]);
133
133
 
134
134
  if (params.valid) {
135
135
  return client.getTreatmentsByFlagSet(params.key as SplitIO.SplitKey, params.flagSetOrFlagSets[0] as string, params.attributes as SplitIO.Attributes | undefined);
@@ -139,7 +139,7 @@ export function clientInputValidationDecorator<TClient extends SplitIO.IClient |
139
139
  }
140
140
 
141
141
  function getTreatmentsWithConfigByFlagSet(maybeKey: SplitIO.SplitKey, maybeFlagSet: string, maybeAttributes?: SplitIO.Attributes) {
142
- const params = validateEvaluationParams(maybeKey, undefined, maybeAttributes, 'getTreatmentsWithConfigByFlagSet', [maybeFlagSet]);
142
+ const params = validateEvaluationParams(maybeKey, undefined, maybeAttributes, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET, [maybeFlagSet]);
143
143
 
144
144
  if (params.valid) {
145
145
  return client.getTreatmentsWithConfigByFlagSet(params.key as SplitIO.SplitKey, params.flagSetOrFlagSets[0] as string, params.attributes as SplitIO.Attributes | undefined);
@@ -149,17 +149,17 @@ export function clientInputValidationDecorator<TClient extends SplitIO.IClient |
149
149
  }
150
150
 
151
151
  function track(maybeKey: SplitIO.SplitKey, maybeTT: string, maybeEvent: string, maybeEventValue?: number, maybeProperties?: SplitIO.Properties) {
152
- const key = validateKey(log, maybeKey, 'track');
153
- const tt = validateTrafficType(log, maybeTT, 'track');
154
- const event = validateEvent(log, maybeEvent, 'track');
155
- const eventValue = validateEventValue(log, maybeEventValue, 'track');
156
- const { properties, size } = validateEventProperties(log, maybeProperties, 'track');
157
- const isNotDestroyed = validateIfNotDestroyed(log, readinessManager, 'track');
152
+ const key = validateKey(log, maybeKey, TRACK_FN_LABEL);
153
+ const tt = validateTrafficType(log, maybeTT, TRACK_FN_LABEL);
154
+ const event = validateEvent(log, maybeEvent, TRACK_FN_LABEL);
155
+ const eventValue = validateEventValue(log, maybeEventValue, TRACK_FN_LABEL);
156
+ const { properties, size } = validateEventProperties(log, maybeProperties, TRACK_FN_LABEL);
157
+ const isNotDestroyed = validateIfNotDestroyed(log, readinessManager, TRACK_FN_LABEL);
158
158
 
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,11 +6,8 @@ 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';
10
-
11
- const SPLIT_FN_LABEL = 'split';
12
- const SPLITS_FN_LABEL = 'splits';
13
- const NAMES_FN_LABEL = 'names';
9
+ import { isConsumerMode } from '../utils/settingsValidation/mode';
10
+ import { SPLIT_FN_LABEL, SPLITS_FN_LABEL, NAMES_FN_LABEL } from '../utils/constants';
14
11
 
15
12
  function collectTreatments(splitObject: ISplit) {
16
13
  const conditions = splitObject.conditions;
@@ -54,8 +51,8 @@ export function sdkManagerFactory<TSplitCache extends ISplitsCacheSync | ISplits
54
51
  { readinessManager, sdkStatus }: ISdkReadinessManager,
55
52
  ): TSplitCache extends ISplitsCacheAsync ? SplitIO.IAsyncManager : SplitIO.IManager {
56
53
 
57
- const log = settings.log;
58
- const isSync = isStorageSync(settings);
54
+ const { log, mode } = settings;
55
+ const isAsync = isConsumerMode(mode);
59
56
 
60
57
  return objectAssign(
61
58
  // Proto-linkage of the readiness Event Emitter
@@ -67,7 +64,7 @@ export function sdkManagerFactory<TSplitCache extends ISplitsCacheSync | ISplits
67
64
  split(featureFlagName: string) {
68
65
  const splitName = validateSplit(log, featureFlagName, SPLIT_FN_LABEL);
69
66
  if (!validateIfNotDestroyed(log, readinessManager, SPLIT_FN_LABEL) || !validateIfOperational(log, readinessManager, SPLIT_FN_LABEL) || !splitName) {
70
- return isSync ? null : Promise.resolve(null);
67
+ return isAsync ? Promise.resolve(null) : null;
71
68
  }
72
69
 
73
70
  const split = splits.getSplit(splitName);
@@ -88,7 +85,7 @@ export function sdkManagerFactory<TSplitCache extends ISplitsCacheSync | ISplits
88
85
  */
89
86
  splits() {
90
87
  if (!validateIfNotDestroyed(log, readinessManager, SPLITS_FN_LABEL) || !validateIfOperational(log, readinessManager, SPLITS_FN_LABEL)) {
91
- return isSync ? [] : Promise.resolve([]);
88
+ return isAsync ? Promise.resolve([]) : [];
92
89
  }
93
90
  const currentSplits = splits.getAll();
94
91
 
@@ -101,7 +98,7 @@ export function sdkManagerFactory<TSplitCache extends ISplitsCacheSync | ISplits
101
98
  */
102
99
  names() {
103
100
  if (!validateIfNotDestroyed(log, readinessManager, NAMES_FN_LABEL) || !validateIfOperational(log, readinessManager, NAMES_FN_LABEL)) {
104
- return isSync ? [] : Promise.resolve([]);
101
+ return isAsync ? Promise.resolve([]) : [];
105
102
  }
106
103
  const splitNames = splits.getSplitNames();
107
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);
@@ -39,6 +39,22 @@ export const CONSENT_GRANTED = 'GRANTED'; // The user has granted consent for tr
39
39
  export const CONSENT_DECLINED = 'DECLINED'; // The user has declined consent for tracking events and impressions
40
40
  export const CONSENT_UNKNOWN = 'UNKNOWN'; // The user has neither granted nor declined consent for tracking events and impressions
41
41
 
42
+ // Client method names
43
+ export const GET_TREATMENT = 'getTreatment';
44
+ export const GET_TREATMENTS = 'getTreatments';
45
+ export const GET_TREATMENT_WITH_CONFIG = 'getTreatmentWithConfig';
46
+ export const GET_TREATMENTS_WITH_CONFIG = 'getTreatmentsWithConfig';
47
+ export const GET_TREATMENTS_BY_FLAG_SET = 'getTreatmentsByFlagSet';
48
+ export const GET_TREATMENTS_BY_FLAG_SETS = 'getTreatmentsByFlagSets';
49
+ export const GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET = 'getTreatmentsWithConfigByFlagSet';
50
+ export const GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS = 'getTreatmentsWithConfigByFlagSets';
51
+ export const TRACK_FN_LABEL = 'track';
52
+
53
+ // Manager method names
54
+ export const SPLIT_FN_LABEL = 'split';
55
+ export const SPLITS_FN_LABEL = 'splits';
56
+ export const NAMES_FN_LABEL = 'names';
57
+
42
58
  // Telemetry
43
59
  export const QUEUED = 0;
44
60
  export const DROPPED = 1;
@@ -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
+ }