@splitsoftware/splitio-commons 3.1.0 → 3.1.1-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGES.txt +4 -0
- package/cjs/evaluator/condition/index.js +1 -1
- package/cjs/evaluator/index.js +26 -46
- package/cjs/listeners/browser.js +2 -2
- package/cjs/sdkClient/client.js +18 -17
- package/cjs/services/authProvider.js +1 -1
- package/cjs/storages/utils.js +1 -0
- package/cjs/sync/submitters/impressionsSubmitter.js +5 -4
- package/esm/evaluator/condition/index.js +1 -1
- package/esm/evaluator/index.js +26 -46
- package/esm/listeners/browser.js +2 -2
- package/esm/sdkClient/client.js +18 -17
- package/esm/services/authProvider.js +1 -1
- package/esm/storages/utils.js +1 -0
- package/esm/sync/submitters/impressionsSubmitter.js +5 -4
- package/package.json +1 -1
- package/src/dtos/types.ts +7 -0
- package/src/evaluator/Engine.ts +3 -3
- package/src/evaluator/condition/index.ts +2 -2
- package/src/evaluator/index.ts +33 -79
- package/src/evaluator/types.ts +3 -6
- package/src/listeners/browser.ts +2 -2
- package/src/sdkClient/client.ts +24 -25
- package/src/sdkFactory/types.ts +0 -4
- package/src/services/authProvider.ts +1 -1
- package/src/storages/utils.ts +1 -0
- package/src/sync/submitters/impressionsSubmitter.ts +6 -6
- package/src/sync/submitters/types.ts +2 -1
- package/types/splitio.d.ts +1 -171
- package/cjs/evaluator/fallbackConfigsCalculator/fallbackSanitizer/index.js +0 -70
- package/cjs/evaluator/fallbackConfigsCalculator/index.js +0 -25
- package/esm/evaluator/fallbackConfigsCalculator/fallbackSanitizer/index.js +0 -64
- package/esm/evaluator/fallbackConfigsCalculator/index.js +0 -21
- package/src/evaluator/fallbackConfigsCalculator/fallbackSanitizer/index.ts +0 -76
- package/src/evaluator/fallbackConfigsCalculator/index.ts +0 -24
|
@@ -4,7 +4,7 @@ import { SUBMITTERS_PUSH_FULL_QUEUE } from '../../logger/constants';
|
|
|
4
4
|
/**
|
|
5
5
|
* Converts `impressions` data from cache into request payload.
|
|
6
6
|
*/
|
|
7
|
-
export function fromImpressionsCollector(sendLabels,
|
|
7
|
+
export function fromImpressionsCollector(sendLabels, data) {
|
|
8
8
|
var groupedByFeature = groupBy(data, 'feature');
|
|
9
9
|
var dto = [];
|
|
10
10
|
forOwn(groupedByFeature, function (value, name) {
|
|
@@ -20,7 +20,8 @@ export function fromImpressionsCollector(sendLabels, entityType, data) {
|
|
|
20
20
|
b: entry.bucketingKey,
|
|
21
21
|
pt: entry.pt,
|
|
22
22
|
properties: entry.properties,
|
|
23
|
-
|
|
23
|
+
// @ts-expect-error - entityType is not yet public. @TODO: add to SplitIO.ImpressionDTO type
|
|
24
|
+
et: entry.entityType, // Definition type
|
|
24
25
|
};
|
|
25
26
|
})
|
|
26
27
|
});
|
|
@@ -31,9 +32,9 @@ export function fromImpressionsCollector(sendLabels, entityType, data) {
|
|
|
31
32
|
* Submitter that periodically posts impressions data
|
|
32
33
|
*/
|
|
33
34
|
export function impressionsSubmitterFactory(params) {
|
|
34
|
-
var _a = params.settings, log = _a.log, impressionsRefreshRate = _a.scheduler.impressionsRefreshRate, labelsEnabled = _a.core.labelsEnabled, postTestImpressionsBulk = params.serviceApi.postTestImpressionsBulk, impressions = params.storage.impressions
|
|
35
|
+
var _a = params.settings, log = _a.log, impressionsRefreshRate = _a.scheduler.impressionsRefreshRate, labelsEnabled = _a.core.labelsEnabled, postTestImpressionsBulk = params.serviceApi.postTestImpressionsBulk, impressions = params.storage.impressions;
|
|
35
36
|
// retry impressions only once.
|
|
36
|
-
var syncTask = submitterFactory(log, postTestImpressionsBulk, impressions, impressionsRefreshRate, fromImpressionsCollector.bind(undefined, labelsEnabled
|
|
37
|
+
var syncTask = submitterFactory(log, postTestImpressionsBulk, impressions, impressionsRefreshRate, fromImpressionsCollector.bind(undefined, labelsEnabled), 1);
|
|
37
38
|
// register impressions submitter to be executed when impressions cache is full
|
|
38
39
|
impressions.setOnFullQueueCb(function () {
|
|
39
40
|
if (syncTask.isRunning()) {
|
package/package.json
CHANGED
package/src/dtos/types.ts
CHANGED
|
@@ -215,6 +215,9 @@ export interface IRBSegment extends TargetingEntity {
|
|
|
215
215
|
} | null
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
+
export type ConfigType = 'standard' | 'ai';
|
|
219
|
+
export type ConfigSubtype = 'llm_call';
|
|
220
|
+
|
|
218
221
|
export interface IDefinition extends TargetingEntity {
|
|
219
222
|
trafficTypeName: string;
|
|
220
223
|
sets?: string[] | null;
|
|
@@ -231,6 +234,10 @@ export interface IDefinition extends TargetingEntity {
|
|
|
231
234
|
configurations?: {
|
|
232
235
|
[treatmentName: string]: string | SplitIO.JsonObject
|
|
233
236
|
} | null;
|
|
237
|
+
/** Definition classification. Absent means a feature flag. */
|
|
238
|
+
type?: ConfigType;
|
|
239
|
+
/** Only meaningful when `type === 'AI'`. */
|
|
240
|
+
subtype?: ConfigSubtype;
|
|
234
241
|
}
|
|
235
242
|
|
|
236
243
|
/** Interface of the parsed JSON response of `/splitChanges` */
|
package/src/evaluator/Engine.ts
CHANGED
|
@@ -7,12 +7,12 @@ import { CONTROL } from '../utils/constants';
|
|
|
7
7
|
import { IDefinition, MaybeThenable } from '../dtos/types';
|
|
8
8
|
import SplitIO from '../../types/splitio';
|
|
9
9
|
import { IStorageAsync, IStorageSync } from '../storages/types';
|
|
10
|
-
import { IEvaluation,
|
|
10
|
+
import { IEvaluation, IDefinitionEvaluator } from './types';
|
|
11
11
|
import { ILogger } from '../logger/types';
|
|
12
12
|
import { ENGINE_DEFAULT } from '../logger/constants';
|
|
13
13
|
import { prerequisitesMatcherContext } from './matchers/prerequisites';
|
|
14
14
|
|
|
15
|
-
function evaluationResult(result: IEvaluation | undefined, defaultTreatment: string):
|
|
15
|
+
function evaluationResult(result: IEvaluation | undefined, defaultTreatment: string): IEvaluation {
|
|
16
16
|
return {
|
|
17
17
|
treatment: get(result, 'treatment', defaultTreatment),
|
|
18
18
|
label: get(result, 'label', NO_CONDITION_MATCH)
|
|
@@ -29,7 +29,7 @@ export function engineParser(log: ILogger, split: IDefinition, storage: IStorage
|
|
|
29
29
|
|
|
30
30
|
return {
|
|
31
31
|
|
|
32
|
-
getTreatment(key: SplitIO.SplitKey, attributes: SplitIO.Attributes | undefined, splitEvaluator: IDefinitionEvaluator): MaybeThenable<
|
|
32
|
+
getTreatment(key: SplitIO.SplitKey, attributes: SplitIO.Attributes | undefined, splitEvaluator: IDefinitionEvaluator): MaybeThenable<IEvaluation> {
|
|
33
33
|
|
|
34
34
|
const parsedKey = keyParser(key);
|
|
35
35
|
|
|
@@ -29,9 +29,9 @@ export function conditionContext(log: ILogger, matcherEvaluator: (key: SplitIO.S
|
|
|
29
29
|
// Whitelisting has more priority than traffic allocation, so we don't apply this filtering to those conditions.
|
|
30
30
|
if (conditionType === 'ROLLOUT' && !shouldApplyRollout(trafficAllocation!, key.bucketingKey, trafficAllocationSeed!)) {
|
|
31
31
|
return {
|
|
32
|
-
|
|
32
|
+
// treatment value is assigned later, at Engine's evaluationResult
|
|
33
33
|
label: NOT_IN_SPLIT
|
|
34
|
-
};
|
|
34
|
+
} as IEvaluation;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
// matcherEvaluator could be Async, this relays on matchers return value, so we need
|
package/src/evaluator/index.ts
CHANGED
|
@@ -4,11 +4,12 @@ import { EXCEPTION, NO_CONDITION_MATCH, DEFINITION_NOT_FOUND } from '../utils/la
|
|
|
4
4
|
import { CONTROL } from '../utils/constants';
|
|
5
5
|
import { IDefinition, MaybeThenable } from '../dtos/types';
|
|
6
6
|
import { IStorageAsync, IStorageSync } from '../storages/types';
|
|
7
|
-
import {
|
|
7
|
+
import { IEvaluation } from './types';
|
|
8
8
|
import SplitIO from '../../types/splitio';
|
|
9
9
|
import { ILogger } from '../logger/types';
|
|
10
10
|
import { returnSetsUnion, setToArray } from '../utils/lang/sets';
|
|
11
11
|
import { WARN_FLAGSET_WITHOUT_FLAGS } from '../logger/constants';
|
|
12
|
+
import { objectAssign } from '../utils/lang/objectAssign';
|
|
12
13
|
|
|
13
14
|
const EVALUATION_EXCEPTION = {
|
|
14
15
|
treatment: CONTROL,
|
|
@@ -23,7 +24,7 @@ const EVALUATION_DEFINITION_NOT_FOUND = {
|
|
|
23
24
|
};
|
|
24
25
|
|
|
25
26
|
function treatmentsException(definitionNames: string[]) {
|
|
26
|
-
const evaluations: Record<string,
|
|
27
|
+
const evaluations: Record<string, IEvaluation> = {};
|
|
27
28
|
definitionNames.forEach(definitionName => {
|
|
28
29
|
evaluations[definitionName] = EVALUATION_EXCEPTION;
|
|
29
30
|
});
|
|
@@ -36,8 +37,7 @@ export function evaluateFeature(
|
|
|
36
37
|
definitionName: string,
|
|
37
38
|
attributes: SplitIO.Attributes | undefined,
|
|
38
39
|
storage: IStorageSync | IStorageAsync,
|
|
39
|
-
|
|
40
|
-
): MaybeThenable<IEvaluationResult> {
|
|
40
|
+
): MaybeThenable<IEvaluation> {
|
|
41
41
|
let definition;
|
|
42
42
|
|
|
43
43
|
try {
|
|
@@ -47,29 +47,14 @@ export function evaluateFeature(
|
|
|
47
47
|
return EVALUATION_EXCEPTION;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
)).catch(
|
|
59
|
-
// Exception on async storage. For example, when the storage is redis or
|
|
60
|
-
// pluggable and there is a connection issue and we can't retrieve the split to be evaluated
|
|
61
|
-
() => EVALUATION_EXCEPTION
|
|
62
|
-
);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return getEvaluation(
|
|
66
|
-
log,
|
|
67
|
-
key,
|
|
68
|
-
definition,
|
|
69
|
-
attributes,
|
|
70
|
-
storage,
|
|
71
|
-
options,
|
|
72
|
-
);
|
|
50
|
+
return thenable(definition) ?
|
|
51
|
+
definition.then((definition) => getEvaluation(log, key, definition, attributes, storage))
|
|
52
|
+
.catch(
|
|
53
|
+
// Exception on async storage. For example, when the storage is redis or
|
|
54
|
+
// pluggable and there is a connection issue and we can't retrieve the split to be evaluated
|
|
55
|
+
() => EVALUATION_EXCEPTION
|
|
56
|
+
) :
|
|
57
|
+
getEvaluation(log, key, definition, attributes, storage);
|
|
73
58
|
}
|
|
74
59
|
|
|
75
60
|
export function evaluateFeatures(
|
|
@@ -78,8 +63,7 @@ export function evaluateFeatures(
|
|
|
78
63
|
definitionNames: string[],
|
|
79
64
|
attributes: SplitIO.Attributes | undefined,
|
|
80
65
|
storage: IStorageSync | IStorageAsync,
|
|
81
|
-
|
|
82
|
-
): MaybeThenable<Record<string, IEvaluationResult>> {
|
|
66
|
+
): MaybeThenable<Record<string, IEvaluation>> {
|
|
83
67
|
let definitions;
|
|
84
68
|
|
|
85
69
|
try {
|
|
@@ -90,13 +74,13 @@ export function evaluateFeatures(
|
|
|
90
74
|
}
|
|
91
75
|
|
|
92
76
|
return thenable(definitions) ?
|
|
93
|
-
definitions.then(definitions => getEvaluations(log, key, definitionNames, definitions, attributes, storage
|
|
94
|
-
.catch(
|
|
77
|
+
definitions.then(definitions => getEvaluations(log, key, definitionNames, definitions, attributes, storage))
|
|
78
|
+
.catch(
|
|
95
79
|
// Exception on async storage. For example, when the storage is redis or
|
|
96
80
|
// pluggable and there is a connection issue and we can't retrieve the split to be evaluated
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
getEvaluations(log, key, definitionNames, definitions, attributes, storage
|
|
81
|
+
() => treatmentsException(definitionNames)
|
|
82
|
+
) :
|
|
83
|
+
getEvaluations(log, key, definitionNames, definitions, attributes, storage);
|
|
100
84
|
}
|
|
101
85
|
|
|
102
86
|
export function evaluateFeaturesByFlagSets(
|
|
@@ -106,8 +90,7 @@ export function evaluateFeaturesByFlagSets(
|
|
|
106
90
|
attributes: SplitIO.Attributes | undefined,
|
|
107
91
|
storage: IStorageSync | IStorageAsync,
|
|
108
92
|
method: string,
|
|
109
|
-
|
|
110
|
-
): MaybeThenable<Record<string, IEvaluationResult>> {
|
|
93
|
+
): MaybeThenable<Record<string, IEvaluation>> {
|
|
111
94
|
let storedFlagNames: MaybeThenable<Set<string>[]>;
|
|
112
95
|
|
|
113
96
|
function evaluate(featureFlagsByFlagSets: Set<string>[]) {
|
|
@@ -122,7 +105,7 @@ export function evaluateFeaturesByFlagSets(
|
|
|
122
105
|
}
|
|
123
106
|
|
|
124
107
|
return featureFlags.size ?
|
|
125
|
-
evaluateFeatures(log, key, setToArray(featureFlags), attributes, storage
|
|
108
|
+
evaluateFeatures(log, key, setToArray(featureFlags), attributes, storage) :
|
|
126
109
|
{};
|
|
127
110
|
}
|
|
128
111
|
|
|
@@ -137,9 +120,7 @@ export function evaluateFeaturesByFlagSets(
|
|
|
137
120
|
// evaluate related features
|
|
138
121
|
return thenable(storedFlagNames) ?
|
|
139
122
|
storedFlagNames.then((storedFlagNames) => evaluate(storedFlagNames))
|
|
140
|
-
.catch(() => {
|
|
141
|
-
return {};
|
|
142
|
-
}) :
|
|
123
|
+
.catch(() => ({})) :
|
|
143
124
|
evaluate(storedFlagNames);
|
|
144
125
|
}
|
|
145
126
|
|
|
@@ -149,31 +130,15 @@ function getEvaluation(
|
|
|
149
130
|
definition: IDefinition | null,
|
|
150
131
|
attributes: SplitIO.Attributes | undefined,
|
|
151
132
|
storage: IStorageSync | IStorageAsync,
|
|
152
|
-
|
|
153
|
-
): MaybeThenable<IEvaluationResult> {
|
|
133
|
+
): MaybeThenable<IEvaluation> {
|
|
154
134
|
|
|
155
135
|
if (definition) {
|
|
156
136
|
const split = engineParser(log, definition, storage);
|
|
157
137
|
const evaluation = split.getTreatment(key, attributes, evaluateFeature);
|
|
158
138
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
result.changeNumber = definition.changeNumber;
|
|
163
|
-
result.config = definition.configurations && definition.configurations[result.treatment] || null;
|
|
164
|
-
// @ts-expect-error impressionsDisabled is not exposed in the public typings yet.
|
|
165
|
-
result.impressionsDisabled = options?.impressionsDisabled || definition.impressionsDisabled;
|
|
166
|
-
|
|
167
|
-
return result;
|
|
168
|
-
});
|
|
169
|
-
} else {
|
|
170
|
-
evaluation.changeNumber = definition.changeNumber;
|
|
171
|
-
evaluation.config = definition.configurations && definition.configurations[evaluation.treatment] || null;
|
|
172
|
-
// @ts-expect-error impressionsDisabled is not exposed in the public typings yet.
|
|
173
|
-
evaluation.impressionsDisabled = options?.impressionsDisabled || definition.impressionsDisabled;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
return evaluation;
|
|
139
|
+
return thenable(evaluation) ?
|
|
140
|
+
evaluation.then(result => objectAssign(result, { definition })) :
|
|
141
|
+
objectAssign(evaluation, { definition });
|
|
177
142
|
}
|
|
178
143
|
|
|
179
144
|
return EVALUATION_DEFINITION_NOT_FOUND;
|
|
@@ -186,26 +151,16 @@ function getEvaluations(
|
|
|
186
151
|
definitions: Record<string, IDefinition | null>,
|
|
187
152
|
attributes: SplitIO.Attributes | undefined,
|
|
188
153
|
storage: IStorageSync | IStorageAsync,
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
const result: Record<string, IEvaluationResult> = {};
|
|
154
|
+
): MaybeThenable<Record<string, IEvaluation>> {
|
|
155
|
+
const result: Record<string, IEvaluation> = {};
|
|
192
156
|
const thenables: Promise<void>[] = [];
|
|
193
157
|
definitionNames.forEach(definitionName => {
|
|
194
|
-
const evaluation = getEvaluation(
|
|
195
|
-
|
|
196
|
-
key,
|
|
197
|
-
definitions[definitionName],
|
|
198
|
-
attributes,
|
|
199
|
-
storage,
|
|
200
|
-
options
|
|
201
|
-
);
|
|
202
|
-
if (thenable(evaluation)) {
|
|
158
|
+
const evaluation = getEvaluation(log, key, definitions[definitionName], attributes, storage);
|
|
159
|
+
thenable(evaluation) ?
|
|
203
160
|
thenables.push(evaluation.then(res => {
|
|
204
161
|
result[definitionName] = res;
|
|
205
|
-
}))
|
|
206
|
-
} else {
|
|
162
|
+
})) :
|
|
207
163
|
result[definitionName] = evaluation;
|
|
208
|
-
}
|
|
209
164
|
});
|
|
210
165
|
|
|
211
166
|
return thenables.length > 0 ? Promise.all(thenables).then(() => result) : result;
|
|
@@ -214,7 +169,7 @@ function getEvaluations(
|
|
|
214
169
|
export function evaluateDefaultTreatment(
|
|
215
170
|
definitionName: string,
|
|
216
171
|
storage: IStorageSync | IStorageAsync,
|
|
217
|
-
): MaybeThenable<
|
|
172
|
+
): MaybeThenable<IEvaluation> {
|
|
218
173
|
let definition;
|
|
219
174
|
|
|
220
175
|
try {
|
|
@@ -230,13 +185,12 @@ export function evaluateDefaultTreatment(
|
|
|
230
185
|
|
|
231
186
|
function getDefaultTreatment(
|
|
232
187
|
definition: IDefinition | null,
|
|
233
|
-
): MaybeThenable<
|
|
188
|
+
): MaybeThenable<IEvaluation> {
|
|
234
189
|
if (definition) {
|
|
235
190
|
return {
|
|
236
191
|
treatment: definition.defaultTreatment,
|
|
237
192
|
label: NO_CONDITION_MATCH, // "default rule"
|
|
238
|
-
|
|
239
|
-
changeNumber: definition.changeNumber
|
|
193
|
+
definition,
|
|
240
194
|
};
|
|
241
195
|
}
|
|
242
196
|
|
package/src/evaluator/types.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { IBetweenMatcherData, IBetweenStringMatcherData, IDependencyMatcherData, MaybeThenable } from '../dtos/types';
|
|
1
|
+
import { IBetweenMatcherData, IBetweenStringMatcherData, IDefinition, IDependencyMatcherData, MaybeThenable } from '../dtos/types';
|
|
2
2
|
import { IStorageAsync, IStorageSync } from '../storages/types';
|
|
3
3
|
import SplitIO from '../../types/splitio';
|
|
4
4
|
import { ILogger } from '../logger/types';
|
|
@@ -19,14 +19,11 @@ export interface IMatcherDto {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
export interface IEvaluation {
|
|
22
|
-
treatment
|
|
22
|
+
treatment: string,
|
|
23
23
|
label: string,
|
|
24
|
-
|
|
25
|
-
config?: string | null | SplitIO.JsonObject
|
|
24
|
+
definition?: IDefinition
|
|
26
25
|
}
|
|
27
26
|
|
|
28
|
-
export type IEvaluationResult = IEvaluation & { treatment: string; impressionsDisabled?: boolean }
|
|
29
|
-
|
|
30
27
|
export type IDefinitionEvaluator = (log: ILogger, key: SplitIO.SplitKey, definitionName: string, attributes: SplitIO.Attributes | undefined, storage: IStorageSync | IStorageAsync) => MaybeThenable<IEvaluation>
|
|
31
28
|
|
|
32
29
|
export type IEvaluator = (key: SplitIO.SplitKeyObject, seed?: number, trafficAllocation?: number, trafficAllocationSeed?: number, attributes?: SplitIO.Attributes, splitEvaluator?: IDefinitionEvaluator) => MaybeThenable<IEvaluation | boolean | undefined>
|
package/src/listeners/browser.ts
CHANGED
|
@@ -28,14 +28,14 @@ export class BrowserSignalListener implements ISignalListener {
|
|
|
28
28
|
private serviceApi: IServiceApi;
|
|
29
29
|
private fromImpressionsCollector: (data: SplitIO.ImpressionDTO[]) => ImpressionsPayload;
|
|
30
30
|
|
|
31
|
-
constructor({ syncManager, settings, storage, serviceApi
|
|
31
|
+
constructor({ syncManager, settings, storage, serviceApi }: ISdkFactoryContextSync) {
|
|
32
32
|
this.syncManager = syncManager;
|
|
33
33
|
this.settings = settings;
|
|
34
34
|
this.storage = storage;
|
|
35
35
|
this.serviceApi = serviceApi;
|
|
36
36
|
this.flushData = this.flushData.bind(this);
|
|
37
37
|
this.flushDataIfHidden = this.flushDataIfHidden.bind(this);
|
|
38
|
-
this.fromImpressionsCollector = fromImpressionsCollector.bind(undefined, settings.core.labelsEnabled
|
|
38
|
+
this.fromImpressionsCollector = fromImpressionsCollector.bind(undefined, settings.core.labelsEnabled);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
/**
|
package/src/sdkClient/client.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { getMatching, getBucketing } from '../utils/key';
|
|
|
4
4
|
import { validateDefinitionExistence } from '../utils/inputValidation/definitionExistence';
|
|
5
5
|
import { SDK_NOT_READY } from '../utils/labels';
|
|
6
6
|
import { CONTROL, TREATMENT, TREATMENTS, TREATMENT_WITH_CONFIG, TREATMENTS_WITH_CONFIG, TREATMENTS_WITH_CONFIG_BY_FLAGSETS, TREATMENTS_BY_FLAGSETS, TREATMENTS_BY_FLAGSET, TREATMENTS_WITH_CONFIG_BY_FLAGSET, GET_TREATMENTS_WITH_CONFIG, GET_TREATMENTS_BY_FLAG_SETS, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS, GET_TREATMENTS_BY_FLAG_SET, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET, GET_TREATMENT_WITH_CONFIG, GET_TREATMENT, GET_TREATMENTS } from '../utils/constants';
|
|
7
|
-
import {
|
|
7
|
+
import { IEvaluation } from '../evaluator/types';
|
|
8
8
|
import SplitIO from '../../types/splitio';
|
|
9
9
|
import { IMPRESSION_QUEUEING } from '../logger/constants';
|
|
10
10
|
import { ISdkFactoryContext } from '../sdkFactory/types';
|
|
@@ -16,17 +16,17 @@ import { trackMethodFactory } from './trackMethod';
|
|
|
16
16
|
const treatmentNotReady = { treatment: CONTROL, label: SDK_NOT_READY };
|
|
17
17
|
|
|
18
18
|
function treatmentsNotReady(featureFlagNames: string[]) {
|
|
19
|
-
const evaluations: Record<string,
|
|
19
|
+
const evaluations: Record<string, IEvaluation> = {};
|
|
20
20
|
featureFlagNames.forEach(featureFlagName => {
|
|
21
21
|
evaluations[featureFlagName] = treatmentNotReady;
|
|
22
22
|
});
|
|
23
23
|
return evaluations;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
export function stringify(
|
|
27
|
-
if (
|
|
26
|
+
export function stringify(properties?: SplitIO.Properties) {
|
|
27
|
+
if (properties) {
|
|
28
28
|
try {
|
|
29
|
-
return JSON.stringify(
|
|
29
|
+
return JSON.stringify(properties);
|
|
30
30
|
} catch { /* JSON.stringify should never throw with validated options, but handling just in case */ }
|
|
31
31
|
}
|
|
32
32
|
}
|
|
@@ -42,9 +42,9 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
|
|
|
42
42
|
function getTreatment(key: SplitIO.SplitKey, featureFlagName: string, attributes?: SplitIO.Attributes, options?: SplitIO.EvaluationOptions, withConfig = false, methodName = GET_TREATMENT) {
|
|
43
43
|
const stopTelemetryTracker = telemetryTracker.trackEval(withConfig ? TREATMENT_WITH_CONFIG : TREATMENT);
|
|
44
44
|
|
|
45
|
-
const wrapUp = (evaluationResult:
|
|
45
|
+
const wrapUp = (evaluationResult: IEvaluation) => {
|
|
46
46
|
const queue: ImpressionDecorated[] = [];
|
|
47
|
-
const treatment = processEvaluation(evaluationResult, featureFlagName, key,
|
|
47
|
+
const treatment = processEvaluation(evaluationResult, featureFlagName, key, withConfig, methodName, queue, options);
|
|
48
48
|
impressionsTracker.track(queue, attributes);
|
|
49
49
|
|
|
50
50
|
stopTelemetryTracker(queue[0] && queue[0].imp.label);
|
|
@@ -52,7 +52,7 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
|
|
|
52
52
|
};
|
|
53
53
|
|
|
54
54
|
const evaluation = readinessManager.isReadyFromCache() ?
|
|
55
|
-
evaluateFeature(log, key, featureFlagName, attributes, storage
|
|
55
|
+
evaluateFeature(log, key, featureFlagName, attributes, storage) :
|
|
56
56
|
isAsync ? // If the SDK is not ready, treatment may be incorrect due to having splits but not segments data, or storage is not connected
|
|
57
57
|
Promise.resolve(treatmentNotReady) :
|
|
58
58
|
treatmentNotReady;
|
|
@@ -67,12 +67,11 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
|
|
|
67
67
|
function getTreatments(key: SplitIO.SplitKey, featureFlagNames: string[], attributes?: SplitIO.Attributes, options?: SplitIO.EvaluationOptions, withConfig = false, methodName = GET_TREATMENTS) {
|
|
68
68
|
const stopTelemetryTracker = telemetryTracker.trackEval(withConfig ? TREATMENTS_WITH_CONFIG : TREATMENTS);
|
|
69
69
|
|
|
70
|
-
const wrapUp = (evaluationResults: Record<string,
|
|
70
|
+
const wrapUp = (evaluationResults: Record<string, IEvaluation>) => {
|
|
71
71
|
const queue: ImpressionDecorated[] = [];
|
|
72
72
|
const treatments: SplitIO.Treatments | SplitIO.TreatmentsWithConfig = {};
|
|
73
|
-
const properties = stringify(options);
|
|
74
73
|
Object.keys(evaluationResults).forEach(featureFlagName => {
|
|
75
|
-
treatments[featureFlagName] = processEvaluation(evaluationResults[featureFlagName], featureFlagName, key,
|
|
74
|
+
treatments[featureFlagName] = processEvaluation(evaluationResults[featureFlagName], featureFlagName, key, withConfig, methodName, queue, options);
|
|
76
75
|
});
|
|
77
76
|
impressionsTracker.track(queue, attributes);
|
|
78
77
|
|
|
@@ -81,7 +80,7 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
|
|
|
81
80
|
};
|
|
82
81
|
|
|
83
82
|
const evaluations = readinessManager.isReadyFromCache() ?
|
|
84
|
-
evaluateFeatures(log, key, featureFlagNames, attributes, storage
|
|
83
|
+
evaluateFeatures(log, key, featureFlagNames, attributes, storage) :
|
|
85
84
|
isAsync ? // If the SDK is not ready, treatment may be incorrect due to having splits but not segments data, or storage is not connected
|
|
86
85
|
Promise.resolve(treatmentsNotReady(featureFlagNames)) :
|
|
87
86
|
treatmentsNotReady(featureFlagNames);
|
|
@@ -96,12 +95,11 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
|
|
|
96
95
|
function getTreatmentsByFlagSets(key: SplitIO.SplitKey, flagSetNames: string[], attributes?: SplitIO.Attributes, options?: SplitIO.EvaluationOptions, withConfig = false, method: Method = TREATMENTS_BY_FLAGSETS, methodName = GET_TREATMENTS_BY_FLAG_SETS) {
|
|
97
96
|
const stopTelemetryTracker = telemetryTracker.trackEval(method);
|
|
98
97
|
|
|
99
|
-
const wrapUp = (evaluationResults: Record<string,
|
|
98
|
+
const wrapUp = (evaluationResults: Record<string, IEvaluation>) => {
|
|
100
99
|
const queue: ImpressionDecorated[] = [];
|
|
101
100
|
const treatments: SplitIO.Treatments | SplitIO.TreatmentsWithConfig = {};
|
|
102
|
-
const properties = stringify(options);
|
|
103
101
|
Object.keys(evaluationResults).forEach(featureFlagName => {
|
|
104
|
-
treatments[featureFlagName] = processEvaluation(evaluationResults[featureFlagName], featureFlagName, key,
|
|
102
|
+
treatments[featureFlagName] = processEvaluation(evaluationResults[featureFlagName], featureFlagName, key, withConfig, methodName, queue, options);
|
|
105
103
|
});
|
|
106
104
|
impressionsTracker.track(queue, attributes);
|
|
107
105
|
|
|
@@ -110,7 +108,7 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
|
|
|
110
108
|
};
|
|
111
109
|
|
|
112
110
|
const evaluations = readinessManager.isReadyFromCache() ?
|
|
113
|
-
evaluateFeaturesByFlagSets(log, key, flagSetNames, attributes, storage, methodName
|
|
111
|
+
evaluateFeaturesByFlagSets(log, key, flagSetNames, attributes, storage, methodName) :
|
|
114
112
|
isAsync ?
|
|
115
113
|
Promise.resolve({}) :
|
|
116
114
|
{};
|
|
@@ -132,19 +130,20 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
|
|
|
132
130
|
|
|
133
131
|
// Internal function
|
|
134
132
|
function processEvaluation(
|
|
135
|
-
evaluation:
|
|
133
|
+
evaluation: IEvaluation,
|
|
136
134
|
featureFlagName: string,
|
|
137
135
|
key: SplitIO.SplitKey,
|
|
138
|
-
properties: string | undefined,
|
|
139
136
|
withConfig: boolean,
|
|
140
137
|
invokingMethodName: string,
|
|
141
|
-
queue: ImpressionDecorated[]
|
|
138
|
+
queue: ImpressionDecorated[],
|
|
139
|
+
options: SplitIO.EvaluationOptions = {}
|
|
142
140
|
): SplitIO.Treatment | SplitIO.TreatmentWithConfig {
|
|
143
141
|
const matchingKey = getMatching(key);
|
|
144
142
|
const bucketingKey = getBucketing(key);
|
|
145
143
|
|
|
146
|
-
const {
|
|
147
|
-
let { treatment, label
|
|
144
|
+
const { definition } = evaluation;
|
|
145
|
+
let { treatment, label } = evaluation;
|
|
146
|
+
let config = definition?.configurations?.[treatment] || null;
|
|
148
147
|
|
|
149
148
|
if (treatment === CONTROL) {
|
|
150
149
|
const fallbackTreatment = fallbackCalculator(featureFlagName, label);
|
|
@@ -163,10 +162,10 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
|
|
|
163
162
|
time: Date.now(),
|
|
164
163
|
bucketingKey,
|
|
165
164
|
label,
|
|
166
|
-
changeNumber: changeNumber
|
|
167
|
-
properties
|
|
168
|
-
},
|
|
169
|
-
disabled: impressionsDisabled
|
|
165
|
+
changeNumber: definition!.changeNumber,
|
|
166
|
+
properties: stringify(options.properties)
|
|
167
|
+
}, // @ts-expect-error impressionsDisabled is not exposed in the public typings yet.
|
|
168
|
+
disabled: options.impressionsDisabled || definition!.impressionsDisabled
|
|
170
169
|
});
|
|
171
170
|
}
|
|
172
171
|
|
package/src/sdkFactory/types.ts
CHANGED
|
@@ -43,9 +43,6 @@ export interface IPlatform {
|
|
|
43
43
|
SignalListener?: new (params: ISdkFactoryContext) => ISignalListener, // Used by BrowserSignalListener
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
// Definition type
|
|
47
|
-
export type EntityType = 'config' | 'flag';
|
|
48
|
-
|
|
49
46
|
export interface ISdkFactoryContext {
|
|
50
47
|
platform: IPlatform,
|
|
51
48
|
sdkReadinessManager: ISdkReadinessManager,
|
|
@@ -59,7 +56,6 @@ export interface ISdkFactoryContext {
|
|
|
59
56
|
syncManager?: ISyncManager,
|
|
60
57
|
clients: Record<string, SplitIO.IBasicClient>,
|
|
61
58
|
fallbackCalculator: IFallbackCalculator,
|
|
62
|
-
entityType?: EntityType
|
|
63
59
|
}
|
|
64
60
|
|
|
65
61
|
export interface ISdkFactoryContextSync extends ISdkFactoryContext {
|
|
@@ -28,7 +28,7 @@ export function authProviderFactory(settings: ISettings, splitHttpClient: ISplit
|
|
|
28
28
|
const { urls, log } = settings;
|
|
29
29
|
|
|
30
30
|
function fetchAuth() {
|
|
31
|
-
let url = `${urls.auth}/api/v3/auth?capabilities=config`;
|
|
31
|
+
let url = `${urls.auth}/api/v3/auth?capabilities=config,aiconfig`;
|
|
32
32
|
return splitHttpClient(url, undefined, telemetryTracker.trackHttp(TOKEN), false, true);
|
|
33
33
|
}
|
|
34
34
|
|
package/src/storages/utils.ts
CHANGED
|
@@ -3,12 +3,12 @@ import SplitIO from '../../../types/splitio';
|
|
|
3
3
|
import { submitterFactory } from './submitter';
|
|
4
4
|
import { ImpressionsPayload } from './types';
|
|
5
5
|
import { SUBMITTERS_PUSH_FULL_QUEUE } from '../../logger/constants';
|
|
6
|
-
import {
|
|
6
|
+
import { ISdkFactoryContextSync } from '../../sdkFactory/types';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Converts `impressions` data from cache into request payload.
|
|
10
10
|
*/
|
|
11
|
-
export function fromImpressionsCollector(sendLabels: boolean,
|
|
11
|
+
export function fromImpressionsCollector(sendLabels: boolean, data: SplitIO.ImpressionDTO[]): ImpressionsPayload {
|
|
12
12
|
let groupedByFeature = groupBy(data, 'feature');
|
|
13
13
|
let dto: ImpressionsPayload = [];
|
|
14
14
|
|
|
@@ -25,7 +25,8 @@ export function fromImpressionsCollector(sendLabels: boolean, entityType: Entity
|
|
|
25
25
|
b: entry.bucketingKey, // Bucketing Key
|
|
26
26
|
pt: entry.pt, // Previous time
|
|
27
27
|
properties: entry.properties, // Properties
|
|
28
|
-
|
|
28
|
+
// @ts-expect-error - entityType is not yet public. @TODO: add to SplitIO.ImpressionDTO type
|
|
29
|
+
et: entry.entityType, // Definition type
|
|
29
30
|
};
|
|
30
31
|
})
|
|
31
32
|
});
|
|
@@ -42,12 +43,11 @@ export function impressionsSubmitterFactory(params: ISdkFactoryContextSync) {
|
|
|
42
43
|
const {
|
|
43
44
|
settings: { log, scheduler: { impressionsRefreshRate }, core: { labelsEnabled } },
|
|
44
45
|
serviceApi: { postTestImpressionsBulk },
|
|
45
|
-
storage: { impressions }
|
|
46
|
-
entityType
|
|
46
|
+
storage: { impressions }
|
|
47
47
|
} = params;
|
|
48
48
|
|
|
49
49
|
// retry impressions only once.
|
|
50
|
-
const syncTask = submitterFactory(log, postTestImpressionsBulk, impressions, impressionsRefreshRate, fromImpressionsCollector.bind(undefined, labelsEnabled
|
|
50
|
+
const syncTask = submitterFactory(log, postTestImpressionsBulk, impressions, impressionsRefreshRate, fromImpressionsCollector.bind(undefined, labelsEnabled), 1);
|
|
51
51
|
|
|
52
52
|
// register impressions submitter to be executed when impressions cache is full
|
|
53
53
|
impressions.setOnFullQueueCb(() => {
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
import { IMetadata } from '../../dtos/types';
|
|
3
3
|
import SplitIO from '../../../types/splitio';
|
|
4
4
|
import { ISyncTask } from '../types';
|
|
5
|
-
|
|
5
|
+
|
|
6
|
+
type EntityType = 'config' | 'flag' | 'ai-config';
|
|
6
7
|
|
|
7
8
|
type ImpressionPayload = {
|
|
8
9
|
/** Matching Key */
|