@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/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
- ): string[] | null {
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
- VariableSchema,
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?: 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: VariableSchema | undefined;
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]) {
@@ -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
- ): string[] | null {
369
+ ): T[] | null {
370
370
  const variableValue = this.getVariable(featureKey, variableKey, context, options);
371
371
 
372
- return getValueByType(variableValue, "array") as string[] | null;
372
+ return getValueByType(variableValue, "array") as T[] | null;
373
373
  }
374
374
 
375
375
  getVariableObject<T>(