@featurevisor/sdk 2.7.0 → 2.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/coverage/clover.xml +182 -182
- package/coverage/coverage-final.json +9 -9
- package/coverage/lcov-report/bucketer.ts.html +16 -16
- package/coverage/lcov-report/child.ts.html +4 -4
- package/coverage/lcov-report/conditions.ts.html +3 -3
- package/coverage/lcov-report/datafileReader.ts.html +30 -30
- package/coverage/lcov-report/emitter.ts.html +2 -2
- package/coverage/lcov-report/evaluate.ts.html +71 -71
- package/coverage/lcov-report/events.ts.html +1 -1
- package/coverage/lcov-report/helpers.ts.html +8 -8
- package/coverage/lcov-report/hooks.ts.html +6 -6
- package/coverage/lcov-report/index.html +5 -5
- package/coverage/lcov-report/instance.ts.html +43 -43
- package/coverage/lcov-report/logger.ts.html +17 -17
- package/coverage/lcov.info +306 -306
- package/dist/child.d.ts +1 -1
- package/dist/evaluate.d.ts +2 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs.gz +0 -0
- package/dist/index.mjs.map +1 -1
- package/dist/instance.d.ts +1 -1
- package/lib/child.d.ts +1 -1
- package/lib/evaluate.d.ts +2 -2
- package/lib/instance.d.ts +1 -1
- package/package.json +3 -3
- package/src/child.ts +3 -3
- package/src/evaluate.ts +4 -4
- package/src/instance.spec.ts +324 -0
- package/src/instance.ts +3 -3
package/src/child.ts
CHANGED
|
@@ -205,13 +205,13 @@ export class FeaturevisorChildInstance {
|
|
|
205
205
|
);
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
-
getVariableArray(
|
|
208
|
+
getVariableArray<T = string>(
|
|
209
209
|
featureKey: FeatureKey,
|
|
210
210
|
variableKey: string,
|
|
211
211
|
context: Context = {},
|
|
212
212
|
options: OverrideOptions = {},
|
|
213
|
-
):
|
|
214
|
-
return this.parent.getVariableArray(
|
|
213
|
+
): T[] | null {
|
|
214
|
+
return this.parent.getVariableArray<T>(
|
|
215
215
|
featureKey,
|
|
216
216
|
variableKey,
|
|
217
217
|
{
|
package/src/evaluate.ts
CHANGED
|
@@ -9,7 +9,7 @@ import type {
|
|
|
9
9
|
VariationValue,
|
|
10
10
|
VariableKey,
|
|
11
11
|
VariableValue,
|
|
12
|
-
|
|
12
|
+
ResolvedVariableSchema,
|
|
13
13
|
EvaluatedFeature,
|
|
14
14
|
StickyFeatures,
|
|
15
15
|
Allocation,
|
|
@@ -74,7 +74,7 @@ export interface Evaluation {
|
|
|
74
74
|
// variable
|
|
75
75
|
variableKey?: VariableKey;
|
|
76
76
|
variableValue?: VariableValue;
|
|
77
|
-
variableSchema?:
|
|
77
|
+
variableSchema?: ResolvedVariableSchema;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
export interface EvaluateDependencies {
|
|
@@ -324,8 +324,8 @@ export function evaluate(options: EvaluateOptions): Evaluation {
|
|
|
324
324
|
logger.warn("feature is deprecated", { featureKey });
|
|
325
325
|
}
|
|
326
326
|
|
|
327
|
-
// variableSchema
|
|
328
|
-
let variableSchema:
|
|
327
|
+
// variableSchema (from datafile, always resolved)
|
|
328
|
+
let variableSchema: ResolvedVariableSchema | undefined;
|
|
329
329
|
|
|
330
330
|
if (variableKey) {
|
|
331
331
|
if (feature.variablesSchema && feature.variablesSchema[variableKey]) {
|
package/src/instance.spec.ts
CHANGED
|
@@ -1147,6 +1147,330 @@ describe("sdk: instance", function () {
|
|
|
1147
1147
|
).toEqual("orange");
|
|
1148
1148
|
});
|
|
1149
1149
|
|
|
1150
|
+
describe("array and object variables", function () {
|
|
1151
|
+
const arrayAndObjectDatafile: DatafileContent = {
|
|
1152
|
+
schemaVersion: "2",
|
|
1153
|
+
revision: "1.0",
|
|
1154
|
+
features: {
|
|
1155
|
+
withArray: {
|
|
1156
|
+
key: "withArray",
|
|
1157
|
+
bucketBy: "userId",
|
|
1158
|
+
variablesSchema: {
|
|
1159
|
+
simpleArray: {
|
|
1160
|
+
key: "simpleArray",
|
|
1161
|
+
type: "array",
|
|
1162
|
+
defaultValue: ["red", "blue", "green"],
|
|
1163
|
+
},
|
|
1164
|
+
simpleStringArray: {
|
|
1165
|
+
key: "simpleStringArray",
|
|
1166
|
+
type: "array",
|
|
1167
|
+
defaultValue: ["red", "blue", "green"],
|
|
1168
|
+
},
|
|
1169
|
+
objectArray: {
|
|
1170
|
+
key: "objectArray",
|
|
1171
|
+
type: "array",
|
|
1172
|
+
defaultValue: [
|
|
1173
|
+
{ color: "red", opacity: 100 },
|
|
1174
|
+
{ color: "blue", opacity: 90 },
|
|
1175
|
+
{ color: "green", opacity: 95 },
|
|
1176
|
+
],
|
|
1177
|
+
},
|
|
1178
|
+
},
|
|
1179
|
+
traffic: [
|
|
1180
|
+
{
|
|
1181
|
+
key: "1",
|
|
1182
|
+
segments: "*",
|
|
1183
|
+
percentage: 100000,
|
|
1184
|
+
allocation: [],
|
|
1185
|
+
},
|
|
1186
|
+
],
|
|
1187
|
+
},
|
|
1188
|
+
withObject: {
|
|
1189
|
+
key: "withObject",
|
|
1190
|
+
bucketBy: "userId",
|
|
1191
|
+
variablesSchema: {
|
|
1192
|
+
themeConfig: {
|
|
1193
|
+
key: "themeConfig",
|
|
1194
|
+
type: "object",
|
|
1195
|
+
defaultValue: {
|
|
1196
|
+
theme: "light",
|
|
1197
|
+
darkMode: false,
|
|
1198
|
+
},
|
|
1199
|
+
},
|
|
1200
|
+
layoutConfig: {
|
|
1201
|
+
key: "layoutConfig",
|
|
1202
|
+
type: "object",
|
|
1203
|
+
defaultValue: {
|
|
1204
|
+
width: 1200,
|
|
1205
|
+
align: "center",
|
|
1206
|
+
},
|
|
1207
|
+
},
|
|
1208
|
+
headerConfig: {
|
|
1209
|
+
key: "headerConfig",
|
|
1210
|
+
type: "object",
|
|
1211
|
+
defaultValue: {
|
|
1212
|
+
style: { fontSize: 18, bold: true },
|
|
1213
|
+
title: "Welcome",
|
|
1214
|
+
},
|
|
1215
|
+
},
|
|
1216
|
+
contentConfig: {
|
|
1217
|
+
key: "contentConfig",
|
|
1218
|
+
type: "object",
|
|
1219
|
+
defaultValue: {
|
|
1220
|
+
tags: ["news", "featured"],
|
|
1221
|
+
count: 2,
|
|
1222
|
+
},
|
|
1223
|
+
},
|
|
1224
|
+
panelConfig: {
|
|
1225
|
+
key: "panelConfig",
|
|
1226
|
+
type: "object",
|
|
1227
|
+
defaultValue: {
|
|
1228
|
+
sections: [
|
|
1229
|
+
{ id: "hero", visible: true },
|
|
1230
|
+
{ id: "sidebar", visible: false },
|
|
1231
|
+
],
|
|
1232
|
+
},
|
|
1233
|
+
},
|
|
1234
|
+
deepConfig: {
|
|
1235
|
+
key: "deepConfig",
|
|
1236
|
+
type: "object",
|
|
1237
|
+
defaultValue: {
|
|
1238
|
+
level1: {
|
|
1239
|
+
level2: {
|
|
1240
|
+
value: "deep",
|
|
1241
|
+
count: 1,
|
|
1242
|
+
},
|
|
1243
|
+
},
|
|
1244
|
+
},
|
|
1245
|
+
},
|
|
1246
|
+
mixedConfig: {
|
|
1247
|
+
key: "mixedConfig",
|
|
1248
|
+
type: "object",
|
|
1249
|
+
defaultValue: {
|
|
1250
|
+
name: "mixed",
|
|
1251
|
+
enabled: true,
|
|
1252
|
+
meta: {
|
|
1253
|
+
score: 0.95,
|
|
1254
|
+
items: ["a", "b"],
|
|
1255
|
+
},
|
|
1256
|
+
},
|
|
1257
|
+
},
|
|
1258
|
+
},
|
|
1259
|
+
traffic: [
|
|
1260
|
+
{
|
|
1261
|
+
key: "1",
|
|
1262
|
+
segments: "*",
|
|
1263
|
+
percentage: 100000,
|
|
1264
|
+
allocation: [],
|
|
1265
|
+
},
|
|
1266
|
+
],
|
|
1267
|
+
},
|
|
1268
|
+
},
|
|
1269
|
+
segments: {},
|
|
1270
|
+
};
|
|
1271
|
+
|
|
1272
|
+
const context = { userId: "user-1" };
|
|
1273
|
+
|
|
1274
|
+
it("should get array variables via getVariable and getVariableArray (no generics)", function () {
|
|
1275
|
+
const sdk = createInstance({ datafile: arrayAndObjectDatafile });
|
|
1276
|
+
|
|
1277
|
+
const simpleArray = sdk.getVariable("withArray", "simpleArray", context);
|
|
1278
|
+
expect(simpleArray).toEqual(["red", "blue", "green"]);
|
|
1279
|
+
|
|
1280
|
+
const simpleArrayTyped = sdk.getVariableArray("withArray", "simpleArray", context);
|
|
1281
|
+
expect(simpleArrayTyped).toEqual(["red", "blue", "green"]);
|
|
1282
|
+
|
|
1283
|
+
const simpleStringArray = sdk.getVariable("withArray", "simpleStringArray", context);
|
|
1284
|
+
expect(simpleStringArray).toEqual(["red", "blue", "green"]);
|
|
1285
|
+
|
|
1286
|
+
const simpleStringArrayTyped = sdk.getVariableArray(
|
|
1287
|
+
"withArray",
|
|
1288
|
+
"simpleStringArray",
|
|
1289
|
+
context,
|
|
1290
|
+
);
|
|
1291
|
+
expect(simpleStringArrayTyped).toEqual(["red", "blue", "green"]);
|
|
1292
|
+
|
|
1293
|
+
const objectArray = sdk.getVariable("withArray", "objectArray", context);
|
|
1294
|
+
expect(objectArray).toEqual([
|
|
1295
|
+
{ color: "red", opacity: 100 },
|
|
1296
|
+
{ color: "blue", opacity: 90 },
|
|
1297
|
+
{ color: "green", opacity: 95 },
|
|
1298
|
+
]);
|
|
1299
|
+
|
|
1300
|
+
const objectArrayTyped = sdk.getVariableArray("withArray", "objectArray", context);
|
|
1301
|
+
expect(objectArrayTyped).toEqual([
|
|
1302
|
+
{ color: "red", opacity: 100 },
|
|
1303
|
+
{ color: "blue", opacity: 90 },
|
|
1304
|
+
{ color: "green", opacity: 95 },
|
|
1305
|
+
]);
|
|
1306
|
+
});
|
|
1307
|
+
|
|
1308
|
+
it("should get array variables with generics for type safety", function () {
|
|
1309
|
+
const sdk = createInstance({ datafile: arrayAndObjectDatafile });
|
|
1310
|
+
|
|
1311
|
+
const stringArray = sdk.getVariableArray<string>("withArray", "simpleArray", context);
|
|
1312
|
+
expect(stringArray).toEqual(["red", "blue", "green"]);
|
|
1313
|
+
if (stringArray) {
|
|
1314
|
+
const first: string = stringArray[0];
|
|
1315
|
+
expect(first).toBe("red");
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
interface ColorOpacity {
|
|
1319
|
+
color: string;
|
|
1320
|
+
opacity: number;
|
|
1321
|
+
}
|
|
1322
|
+
const objectArray = sdk.getVariableArray<ColorOpacity>("withArray", "objectArray", context);
|
|
1323
|
+
expect(objectArray).toEqual([
|
|
1324
|
+
{ color: "red", opacity: 100 },
|
|
1325
|
+
{ color: "blue", opacity: 90 },
|
|
1326
|
+
{ color: "green", opacity: 95 },
|
|
1327
|
+
]);
|
|
1328
|
+
if (objectArray && objectArray.length > 0) {
|
|
1329
|
+
const first: ColorOpacity = objectArray[0];
|
|
1330
|
+
expect(first.color).toBe("red");
|
|
1331
|
+
expect(first.opacity).toBe(100);
|
|
1332
|
+
}
|
|
1333
|
+
});
|
|
1334
|
+
|
|
1335
|
+
it("should get object variables via getVariable and getVariableObject (no generics)", function () {
|
|
1336
|
+
const sdk = createInstance({ datafile: arrayAndObjectDatafile });
|
|
1337
|
+
|
|
1338
|
+
const themeConfig = sdk.getVariable("withObject", "themeConfig", context);
|
|
1339
|
+
expect(themeConfig).toEqual({ theme: "light", darkMode: false });
|
|
1340
|
+
|
|
1341
|
+
const themeConfigTyped = sdk.getVariableObject("withObject", "themeConfig", context);
|
|
1342
|
+
expect(themeConfigTyped).toEqual({ theme: "light", darkMode: false });
|
|
1343
|
+
|
|
1344
|
+
const layoutConfig = sdk.getVariable("withObject", "layoutConfig", context);
|
|
1345
|
+
expect(layoutConfig).toEqual({ width: 1200, align: "center" });
|
|
1346
|
+
|
|
1347
|
+
const headerConfig = sdk.getVariable("withObject", "headerConfig", context);
|
|
1348
|
+
expect(headerConfig).toEqual({
|
|
1349
|
+
style: { fontSize: 18, bold: true },
|
|
1350
|
+
title: "Welcome",
|
|
1351
|
+
});
|
|
1352
|
+
|
|
1353
|
+
const contentConfig = sdk.getVariable("withObject", "contentConfig", context);
|
|
1354
|
+
expect(contentConfig).toEqual({
|
|
1355
|
+
tags: ["news", "featured"],
|
|
1356
|
+
count: 2,
|
|
1357
|
+
});
|
|
1358
|
+
|
|
1359
|
+
const panelConfig = sdk.getVariableObject("withObject", "panelConfig", context);
|
|
1360
|
+
expect(panelConfig).toEqual({
|
|
1361
|
+
sections: [
|
|
1362
|
+
{ id: "hero", visible: true },
|
|
1363
|
+
{ id: "sidebar", visible: false },
|
|
1364
|
+
],
|
|
1365
|
+
});
|
|
1366
|
+
|
|
1367
|
+
const deepConfig = sdk.getVariableObject("withObject", "deepConfig", context);
|
|
1368
|
+
expect(deepConfig).toEqual({
|
|
1369
|
+
level1: {
|
|
1370
|
+
level2: {
|
|
1371
|
+
value: "deep",
|
|
1372
|
+
count: 1,
|
|
1373
|
+
},
|
|
1374
|
+
},
|
|
1375
|
+
});
|
|
1376
|
+
|
|
1377
|
+
const mixedConfig = sdk.getVariableObject("withObject", "mixedConfig", context);
|
|
1378
|
+
expect(mixedConfig).toEqual({
|
|
1379
|
+
name: "mixed",
|
|
1380
|
+
enabled: true,
|
|
1381
|
+
meta: {
|
|
1382
|
+
score: 0.95,
|
|
1383
|
+
items: ["a", "b"],
|
|
1384
|
+
},
|
|
1385
|
+
});
|
|
1386
|
+
});
|
|
1387
|
+
|
|
1388
|
+
it("should get object variables with generics for type safety", function () {
|
|
1389
|
+
const sdk = createInstance({ datafile: arrayAndObjectDatafile });
|
|
1390
|
+
|
|
1391
|
+
interface ThemeConfig {
|
|
1392
|
+
theme: string;
|
|
1393
|
+
darkMode: boolean;
|
|
1394
|
+
}
|
|
1395
|
+
const themeConfig = sdk.getVariableObject<ThemeConfig>("withObject", "themeConfig", context);
|
|
1396
|
+
expect(themeConfig).toEqual({ theme: "light", darkMode: false });
|
|
1397
|
+
if (themeConfig) {
|
|
1398
|
+
expect(themeConfig.theme).toBe("light");
|
|
1399
|
+
expect(themeConfig.darkMode).toBe(false);
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
interface HeaderConfig {
|
|
1403
|
+
style: { fontSize: number; bold: boolean };
|
|
1404
|
+
title: string;
|
|
1405
|
+
}
|
|
1406
|
+
const headerConfig = sdk.getVariableObject<HeaderConfig>(
|
|
1407
|
+
"withObject",
|
|
1408
|
+
"headerConfig",
|
|
1409
|
+
context,
|
|
1410
|
+
);
|
|
1411
|
+
expect(headerConfig).toEqual({
|
|
1412
|
+
style: { fontSize: 18, bold: true },
|
|
1413
|
+
title: "Welcome",
|
|
1414
|
+
});
|
|
1415
|
+
if (headerConfig) {
|
|
1416
|
+
expect(headerConfig.style.fontSize).toBe(18);
|
|
1417
|
+
expect(headerConfig.title).toBe("Welcome");
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
interface PanelSection {
|
|
1421
|
+
id: string;
|
|
1422
|
+
visible: boolean;
|
|
1423
|
+
}
|
|
1424
|
+
interface PanelConfig {
|
|
1425
|
+
sections: PanelSection[];
|
|
1426
|
+
}
|
|
1427
|
+
const panelConfig = sdk.getVariableObject<PanelConfig>("withObject", "panelConfig", context);
|
|
1428
|
+
expect(panelConfig?.sections).toHaveLength(2);
|
|
1429
|
+
expect(panelConfig?.sections[0]).toEqual({ id: "hero", visible: true });
|
|
1430
|
+
});
|
|
1431
|
+
|
|
1432
|
+
it("should return null for getVariableArray and getVariableObject when variable or feature missing", function () {
|
|
1433
|
+
const sdk = createInstance({ datafile: arrayAndObjectDatafile });
|
|
1434
|
+
|
|
1435
|
+
expect(sdk.getVariableArray("withArray", "nonExisting", context)).toBeNull();
|
|
1436
|
+
expect(sdk.getVariableObject("withObject", "nonExisting", context)).toBeNull();
|
|
1437
|
+
expect(sdk.getVariableArray("nonExistingFeature", "simpleArray", context)).toBeNull();
|
|
1438
|
+
expect(sdk.getVariableObject("nonExistingFeature", "themeConfig", context)).toBeNull();
|
|
1439
|
+
});
|
|
1440
|
+
|
|
1441
|
+
it("should include array and object variables in getAllEvaluations", function () {
|
|
1442
|
+
const sdk = createInstance({ datafile: arrayAndObjectDatafile });
|
|
1443
|
+
|
|
1444
|
+
const all = sdk.getAllEvaluations(context);
|
|
1445
|
+
|
|
1446
|
+
expect(all.withArray).toBeDefined();
|
|
1447
|
+
expect(all.withArray?.enabled).toBe(true);
|
|
1448
|
+
expect(all.withArray?.variables?.simpleArray).toEqual(["red", "blue", "green"]);
|
|
1449
|
+
expect(all.withArray?.variables?.simpleStringArray).toEqual(["red", "blue", "green"]);
|
|
1450
|
+
expect(all.withArray?.variables?.objectArray).toEqual([
|
|
1451
|
+
{ color: "red", opacity: 100 },
|
|
1452
|
+
{ color: "blue", opacity: 90 },
|
|
1453
|
+
{ color: "green", opacity: 95 },
|
|
1454
|
+
]);
|
|
1455
|
+
|
|
1456
|
+
expect(all.withObject).toBeDefined();
|
|
1457
|
+
expect(all.withObject?.enabled).toBe(true);
|
|
1458
|
+
expect(all.withObject?.variables?.themeConfig).toEqual({
|
|
1459
|
+
theme: "light",
|
|
1460
|
+
darkMode: false,
|
|
1461
|
+
});
|
|
1462
|
+
expect(all.withObject?.variables?.headerConfig).toEqual({
|
|
1463
|
+
style: { fontSize: 18, bold: true },
|
|
1464
|
+
title: "Welcome",
|
|
1465
|
+
});
|
|
1466
|
+
expect(all.withObject?.variables?.mixedConfig).toEqual({
|
|
1467
|
+
name: "mixed",
|
|
1468
|
+
enabled: true,
|
|
1469
|
+
meta: { score: 0.95, items: ["a", "b"] },
|
|
1470
|
+
});
|
|
1471
|
+
});
|
|
1472
|
+
});
|
|
1473
|
+
|
|
1150
1474
|
it("should check if enabled for individually named segments", function () {
|
|
1151
1475
|
const sdk = createInstance({
|
|
1152
1476
|
datafile: {
|
package/src/instance.ts
CHANGED
|
@@ -361,15 +361,15 @@ export class FeaturevisorInstance {
|
|
|
361
361
|
return getValueByType(variableValue, "double") as number | null;
|
|
362
362
|
}
|
|
363
363
|
|
|
364
|
-
getVariableArray(
|
|
364
|
+
getVariableArray<T = string>(
|
|
365
365
|
featureKey: FeatureKey,
|
|
366
366
|
variableKey: string,
|
|
367
367
|
context: Context = {},
|
|
368
368
|
options: OverrideOptions = {},
|
|
369
|
-
):
|
|
369
|
+
): T[] | null {
|
|
370
370
|
const variableValue = this.getVariable(featureKey, variableKey, context, options);
|
|
371
371
|
|
|
372
|
-
return getValueByType(variableValue, "array") as
|
|
372
|
+
return getValueByType(variableValue, "array") as T[] | null;
|
|
373
373
|
}
|
|
374
374
|
|
|
375
375
|
getVariableObject<T>(
|