@featurevisor/sdk 1.35.3 → 2.0.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.
Files changed (86) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +2 -381
  3. package/coverage/clover.xml +707 -645
  4. package/coverage/coverage-final.json +11 -9
  5. package/coverage/lcov-report/{segments.ts.html → bucketer.ts.html} +155 -77
  6. package/coverage/lcov-report/child.ts.html +940 -0
  7. package/coverage/lcov-report/conditions.ts.html +107 -158
  8. package/coverage/lcov-report/datafileReader.ts.html +763 -103
  9. package/coverage/lcov-report/emitter.ts.html +77 -59
  10. package/coverage/lcov-report/evaluate.ts.html +689 -416
  11. package/coverage/lcov-report/events.ts.html +334 -0
  12. package/coverage/lcov-report/helpers.ts.html +184 -0
  13. package/coverage/lcov-report/{bucket.ts.html → hooks.ts.html} +86 -239
  14. package/coverage/lcov-report/index.html +119 -89
  15. package/coverage/lcov-report/instance.ts.html +341 -773
  16. package/coverage/lcov-report/logger.ts.html +64 -64
  17. package/coverage/lcov.info +1433 -1226
  18. package/dist/bucketer.d.ts +11 -0
  19. package/dist/child.d.ts +26 -0
  20. package/dist/compareVersions.d.ts +4 -0
  21. package/dist/conditions.d.ts +4 -4
  22. package/dist/datafileReader.d.ts +26 -6
  23. package/dist/emitter.d.ts +8 -9
  24. package/dist/evaluate.d.ts +31 -29
  25. package/dist/events.d.ts +5 -0
  26. package/dist/helpers.d.ts +5 -0
  27. package/dist/hooks.d.ts +45 -0
  28. package/dist/index.d.ts +3 -2
  29. package/dist/index.js +1 -1
  30. package/dist/index.js.map +1 -1
  31. package/dist/index.mjs +1 -1
  32. package/dist/index.mjs.gz +0 -0
  33. package/dist/index.mjs.map +1 -1
  34. package/dist/instance.d.ts +40 -72
  35. package/dist/logger.d.ts +6 -5
  36. package/dist/murmurhash.d.ts +1 -0
  37. package/jest.config.js +2 -0
  38. package/lib/bucketer.d.ts +11 -0
  39. package/lib/child.d.ts +26 -0
  40. package/lib/compareVersions.d.ts +4 -0
  41. package/lib/conditions.d.ts +4 -4
  42. package/lib/datafileReader.d.ts +26 -6
  43. package/lib/emitter.d.ts +8 -9
  44. package/lib/evaluate.d.ts +31 -29
  45. package/lib/events.d.ts +5 -0
  46. package/lib/helpers.d.ts +5 -0
  47. package/lib/hooks.d.ts +45 -0
  48. package/lib/index.d.ts +3 -2
  49. package/lib/instance.d.ts +40 -72
  50. package/lib/logger.d.ts +6 -5
  51. package/lib/murmurhash.d.ts +1 -0
  52. package/package.json +3 -5
  53. package/src/bucketer.spec.ts +165 -0
  54. package/src/bucketer.ts +84 -0
  55. package/src/child.spec.ts +267 -0
  56. package/src/child.ts +285 -0
  57. package/src/compareVersions.ts +93 -0
  58. package/src/conditions.spec.ts +563 -353
  59. package/src/conditions.ts +46 -63
  60. package/src/datafileReader.spec.ts +396 -84
  61. package/src/datafileReader.ts +280 -60
  62. package/src/emitter.spec.ts +27 -86
  63. package/src/emitter.ts +38 -32
  64. package/src/evaluate.ts +349 -258
  65. package/src/events.spec.ts +154 -0
  66. package/src/events.ts +83 -0
  67. package/src/helpers.ts +33 -0
  68. package/src/hooks.ts +88 -0
  69. package/src/index.ts +3 -2
  70. package/src/instance.spec.ts +305 -489
  71. package/src/instance.ts +247 -391
  72. package/src/logger.spec.ts +212 -134
  73. package/src/logger.ts +36 -36
  74. package/src/murmurhash.ts +71 -0
  75. package/coverage/lcov-report/feature.ts.html +0 -508
  76. package/dist/bucket.d.ts +0 -30
  77. package/dist/feature.d.ts +0 -16
  78. package/dist/segments.d.ts +0 -5
  79. package/lib/bucket.d.ts +0 -30
  80. package/lib/feature.d.ts +0 -16
  81. package/lib/segments.d.ts +0 -5
  82. package/src/bucket.spec.ts +0 -37
  83. package/src/bucket.ts +0 -139
  84. package/src/feature.ts +0 -141
  85. package/src/segments.spec.ts +0 -468
  86. package/src/segments.ts +0 -58
@@ -1,4 +1,4 @@
1
- import { DatafileContent } from "@featurevisor/types";
1
+ import type { DatafileContent } from "@featurevisor/types";
2
2
 
3
3
  import { createInstance } from "./instance";
4
4
  import { createLogger } from "./logger";
@@ -11,110 +11,25 @@ describe("sdk: instance", function () {
11
11
  it("should create instance with datafile content", function () {
12
12
  const sdk = createInstance({
13
13
  datafile: {
14
- schemaVersion: "1",
14
+ schemaVersion: "2",
15
15
  revision: "1.0",
16
- features: [],
17
- attributes: [],
18
- segments: [],
16
+ features: {},
17
+ segments: {},
19
18
  },
20
19
  });
21
20
 
22
21
  expect(typeof sdk.getVariation).toEqual("function");
23
22
  });
24
23
 
25
- it("should trigger onReady event once", function (done) {
26
- let readyCount = 0;
27
-
28
- const sdk = createInstance({
29
- datafile: {
30
- schemaVersion: "1",
31
- revision: "1.0",
32
- features: [],
33
- attributes: [],
34
- segments: [],
35
- },
36
- onReady: () => {
37
- readyCount += 1;
38
- },
39
- });
40
-
41
- setTimeout(() => {
42
- expect(readyCount).toEqual(1);
43
- expect(sdk.isReady()).toEqual(true);
44
- done();
45
- }, 0);
46
- });
47
-
48
- it("should resolve onReady method as Promise when initialized synchronously", function (done) {
49
- let readyCount = 0;
50
-
51
- const sdk = createInstance({
52
- datafile: {
53
- schemaVersion: "1",
54
- revision: "1.0",
55
- features: [],
56
- attributes: [],
57
- segments: [],
58
- },
59
- onReady: () => {
60
- readyCount += 1;
61
- },
62
- });
63
-
64
- setTimeout(() => {
65
- sdk.onReady().then((f) => {
66
- expect(f.isReady()).toEqual(true);
67
- expect(readyCount).toEqual(1);
68
-
69
- done();
70
- });
71
- }, 0);
72
- });
73
-
74
- it("should resolve onReady method as Promise, when fetching datafile remotely", function (done) {
75
- let readyCount = 0;
76
-
77
- const sdk = createInstance({
78
- datafileUrl: "http://localhost:3000/datafile.json",
79
- handleDatafileFetch: function () {
80
- const content: DatafileContent = {
81
- schemaVersion: "1",
82
- revision: "1.0",
83
- features: [],
84
- attributes: [],
85
- segments: [],
86
- };
87
-
88
- return new Promise(function (resolve) {
89
- setTimeout(function () {
90
- resolve(content);
91
- }, 10);
92
- });
93
- },
94
- onReady: () => {
95
- readyCount += 1;
96
- },
97
- });
98
-
99
- setTimeout(() => {
100
- sdk.onReady().then((f) => {
101
- expect(f.isReady()).toEqual(true);
102
- expect(readyCount).toEqual(1);
103
-
104
- done();
105
- });
106
- }, 0);
107
- });
108
-
109
24
  it("should configure plain bucketBy", function () {
110
25
  let capturedBucketKey = "";
111
26
 
112
27
  const sdk = createInstance({
113
28
  datafile: {
114
- schemaVersion: "1",
29
+ schemaVersion: "2",
115
30
  revision: "1.0",
116
- features: [
117
- {
31
+ features: {
32
+ test: {
118
33
  key: "test",
119
34
  bucketBy: "userId",
120
35
  variations: [{ value: "control" }, { value: "treatment" }],
@@ -130,15 +45,19 @@ describe("sdk: instance", function () {
130
45
  },
131
46
  ],
132
47
  },
133
- ],
134
- attributes: [],
135
- segments: [],
48
+ },
49
+ segments: {},
136
50
  },
137
- configureBucketKey: function (feature, context, bucketKey) {
138
- capturedBucketKey = bucketKey;
51
+ hooks: [
52
+ {
53
+ name: "unit-test",
54
+ bucketKey: ({ bucketKey }) => {
55
+ capturedBucketKey = bucketKey;
139
56
 
140
- return bucketKey;
141
- },
57
+ return bucketKey;
58
+ },
59
+ },
60
+ ],
142
61
  });
143
62
 
144
63
  const featureKey = "test";
@@ -156,10 +75,10 @@ describe("sdk: instance", function () {
156
75
 
157
76
  const sdk = createInstance({
158
77
  datafile: {
159
- schemaVersion: "1",
78
+ schemaVersion: "2",
160
79
  revision: "1.0",
161
- features: [
162
- {
80
+ features: {
81
+ test: {
163
82
  key: "test",
164
83
  bucketBy: ["userId", "organizationId"],
165
84
  variations: [{ value: "control" }, { value: "treatment" }],
@@ -175,15 +94,19 @@ describe("sdk: instance", function () {
175
94
  },
176
95
  ],
177
96
  },
178
- ],
179
- attributes: [],
180
- segments: [],
97
+ },
98
+ segments: {},
181
99
  },
182
- configureBucketKey: function (feature, context, bucketKey) {
183
- capturedBucketKey = bucketKey;
100
+ hooks: [
101
+ {
102
+ name: "unit-test",
103
+ bucketKey: ({ bucketKey }) => {
104
+ capturedBucketKey = bucketKey;
184
105
 
185
- return bucketKey;
186
- },
106
+ return bucketKey;
107
+ },
108
+ },
109
+ ],
187
110
  });
188
111
 
189
112
  const featureKey = "test";
@@ -201,10 +124,10 @@ describe("sdk: instance", function () {
201
124
 
202
125
  const sdk = createInstance({
203
126
  datafile: {
204
- schemaVersion: "1",
127
+ schemaVersion: "2",
205
128
  revision: "1.0",
206
- features: [
207
- {
129
+ features: {
130
+ test: {
208
131
  key: "test",
209
132
  bucketBy: { or: ["userId", "deviceId"] },
210
133
  variations: [{ value: "control" }, { value: "treatment" }],
@@ -220,15 +143,19 @@ describe("sdk: instance", function () {
220
143
  },
221
144
  ],
222
145
  },
223
- ],
224
- attributes: [],
225
- segments: [],
146
+ },
147
+ segments: {},
226
148
  },
227
- configureBucketKey: function (feature, context, bucketKey) {
228
- capturedBucketKey = bucketKey;
149
+ hooks: [
150
+ {
151
+ name: "unit-test",
152
+ bucketKey: ({ bucketKey }) => {
153
+ capturedBucketKey = bucketKey;
229
154
 
230
- return bucketKey;
231
- },
155
+ return bucketKey;
156
+ },
157
+ },
158
+ ],
232
159
  });
233
160
 
234
161
  expect(
@@ -253,15 +180,17 @@ describe("sdk: instance", function () {
253
180
  expect(capturedBucketKey).toEqual("456.test");
254
181
  });
255
182
 
256
- it("should intercept context", function () {
183
+ it("should intercept context: before hook", function () {
257
184
  let intercepted = false;
185
+ let interceptedFeatureKey = "";
186
+ let interceptedVariableKey: string | undefined = "";
258
187
 
259
188
  const sdk = createInstance({
260
189
  datafile: {
261
- schemaVersion: "1",
190
+ schemaVersion: "2",
262
191
  revision: "1.0",
263
- features: [
264
- {
192
+ features: {
193
+ test: {
265
194
  key: "test",
266
195
  bucketBy: "userId",
267
196
  variations: [{ value: "control" }, { value: "treatment" }],
@@ -277,17 +206,23 @@ describe("sdk: instance", function () {
277
206
  },
278
207
  ],
279
208
  },
280
- ],
281
- attributes: [],
282
- segments: [],
209
+ },
210
+ segments: {},
283
211
  },
284
- interceptContext: function (context) {
285
- intercepted = true;
212
+ hooks: [
213
+ {
214
+ name: "unit-test",
215
+ before: (options) => {
216
+ const { featureKey, variableKey } = options;
286
217
 
287
- return {
288
- ...context,
289
- };
290
- },
218
+ intercepted = true;
219
+ interceptedFeatureKey = featureKey;
220
+ interceptedVariableKey = variableKey;
221
+
222
+ return options;
223
+ },
224
+ },
225
+ ],
291
226
  });
292
227
 
293
228
  const variation = sdk.getVariation("test", {
@@ -296,17 +231,21 @@ describe("sdk: instance", function () {
296
231
 
297
232
  expect(variation).toEqual("control");
298
233
  expect(intercepted).toEqual(true);
234
+ expect(interceptedFeatureKey).toEqual("test");
235
+ expect(interceptedVariableKey).toEqual(undefined);
299
236
  });
300
237
 
301
- it("should activate feature", function () {
302
- let activated = false;
238
+ it("should intercept value: after hook", function () {
239
+ let intercepted = false;
240
+ let interceptedFeatureKey = "";
241
+ let interceptedVariableKey: string | undefined = "";
303
242
 
304
243
  const sdk = createInstance({
305
244
  datafile: {
306
- schemaVersion: "1",
245
+ schemaVersion: "2",
307
246
  revision: "1.0",
308
- features: [
309
- {
247
+ features: {
248
+ test: {
310
249
  key: "test",
311
250
  bucketBy: "userId",
312
251
  variations: [{ value: "control" }, { value: "treatment" }],
@@ -322,109 +261,64 @@ describe("sdk: instance", function () {
322
261
  },
323
262
  ],
324
263
  },
325
- ],
326
- attributes: [],
327
- segments: [],
328
- },
329
- onActivation: function () {
330
- activated = true;
264
+ },
265
+ segments: {},
331
266
  },
332
- });
267
+ hooks: [
268
+ {
269
+ name: "unit-test",
270
+ after: (options) => {
271
+ const { featureKey, variableKey } = options;
333
272
 
334
- const variation = sdk.getVariation("test", {
335
- userId: "123",
336
- });
273
+ intercepted = true;
274
+ interceptedFeatureKey = featureKey;
275
+ interceptedVariableKey = variableKey;
337
276
 
338
- expect(activated).toEqual(false);
339
- expect(variation).toEqual("control");
277
+ options.variationValue = "control_intercepted"; // manipulating value here
340
278
 
341
- const activatedVariation = sdk.activate("test", {
279
+ return options;
280
+ },
281
+ },
282
+ ],
283
+ });
284
+
285
+ const variation = sdk.getVariation("test", {
342
286
  userId: "123",
343
287
  });
344
288
 
345
- expect(activated).toEqual(true);
346
- expect(activatedVariation).toEqual("control");
289
+ expect(variation).toEqual("control_intercepted"); // should not be "control" any more
290
+ expect(intercepted).toEqual(true);
291
+ expect(interceptedFeatureKey).toEqual("test");
292
+ expect(interceptedVariableKey).toEqual(undefined);
347
293
  });
348
294
 
349
- it("should refresh datafile", function (done) {
350
- let revision = 1;
351
- let refreshed = false;
352
- let updatedViaOption = false;
353
- let updatedViaEventListener = false;
354
-
355
- function getDatafileContent(): DatafileContent {
356
- const content: DatafileContent = {
357
- schemaVersion: "1",
358
- revision: revision.toString(),
359
- features: [
360
- {
361
- key: "test",
362
- bucketBy: "userId",
363
- variations: [{ value: "control" }, { value: "treatment" }],
364
- traffic: [
365
- {
366
- key: "1",
367
- segments: "*",
368
- percentage: 100000,
369
- allocation: [
370
- { variation: "control", range: [0, 100000] },
371
- { variation: "treatment", range: [0, 0] },
372
- ],
373
- },
374
- ],
375
- },
376
- ],
377
- attributes: [],
378
- segments: [],
379
- };
380
-
381
- revision += 1;
382
-
383
- return content;
384
- }
385
-
386
- const sdk = createInstance({
387
- datafileUrl: "http://localhost:3000/datafile.json",
388
- handleDatafileFetch: function () {
389
- return new Promise(function (resolve) {
390
- resolve(getDatafileContent());
391
- });
392
- },
393
- refreshInterval: 0.1,
394
- onRefresh() {
395
- refreshed = true;
396
- },
397
- onUpdate() {
398
- updatedViaOption = true;
295
+ it("should initialize with sticky features", function (done) {
296
+ const datafileContent: DatafileContent = {
297
+ schemaVersion: "2",
298
+ revision: "1.0",
299
+ features: {
300
+ test: {
301
+ key: "test",
302
+ bucketBy: "userId",
303
+ variations: [{ value: "control" }, { value: "treatment" }],
304
+ traffic: [
305
+ {
306
+ key: "1",
307
+ segments: "*",
308
+ percentage: 100000,
309
+ allocation: [
310
+ { variation: "control", range: [0, 0] },
311
+ { variation: "treatment", range: [0, 100000] },
312
+ ],
313
+ },
314
+ ],
315
+ },
399
316
  },
400
- });
401
-
402
- const onUpdateCallback = function () {
403
- updatedViaEventListener = true;
317
+ segments: {},
404
318
  };
405
319
 
406
- sdk.on("update", onUpdateCallback);
407
-
408
- expect(sdk.isReady()).toEqual(false);
409
-
410
- setTimeout(function () {
411
- expect(refreshed).toEqual(true);
412
- expect(updatedViaOption).toEqual(true);
413
- expect(updatedViaEventListener).toEqual(true);
414
-
415
- sdk.off("update", onUpdateCallback);
416
-
417
- expect(sdk.isReady()).toEqual(true);
418
-
419
- sdk.stopRefreshing();
420
-
421
- done();
422
- }, 200);
423
- });
424
-
425
- it("should initialize with sticky features", function (done) {
426
320
  const sdk = createInstance({
427
- stickyFeatures: {
321
+ sticky: {
428
322
  test: {
429
323
  enabled: true,
430
324
  variation: "control",
@@ -433,39 +327,6 @@ describe("sdk: instance", function () {
433
327
  },
434
328
  },
435
329
  },
436
- datafileUrl: "http://localhost:3000/datafile.json",
437
- handleDatafileFetch: function () {
438
- const content: DatafileContent = {
439
- schemaVersion: "1",
440
- revision: "1.0",
441
- features: [
442
- {
443
- key: "test",
444
- bucketBy: "userId",
445
- variations: [{ value: "control" }, { value: "treatment" }],
446
- traffic: [
447
- {
448
- key: "1",
449
- segments: "*",
450
- percentage: 100000,
451
- allocation: [
452
- { variation: "control", range: [0, 0] },
453
- { variation: "treatment", range: [0, 100000] },
454
- ],
455
- },
456
- ],
457
- },
458
- ],
459
- attributes: [],
460
- segments: [],
461
- };
462
-
463
- return new Promise(function (resolve) {
464
- setTimeout(function () {
465
- resolve(content);
466
- }, 50);
467
- });
468
- },
469
330
  });
470
331
 
471
332
  // initially control
@@ -480,8 +341,10 @@ describe("sdk: instance", function () {
480
341
  }),
481
342
  ).toEqual("red");
482
343
 
344
+ sdk.setDatafile(datafileContent);
345
+
483
346
  setTimeout(function () {
484
- // still control after fetching datafile
347
+ // still control after setting datafile
485
348
  expect(
486
349
  sdk.getVariation("test", {
487
350
  userId: "123",
@@ -489,77 +352,7 @@ describe("sdk: instance", function () {
489
352
  ).toEqual("control");
490
353
 
491
354
  // unsetting sticky features will make it treatment
492
- sdk.setStickyFeatures({});
493
- expect(
494
- sdk.getVariation("test", {
495
- userId: "123",
496
- }),
497
- ).toEqual("treatment");
498
-
499
- done();
500
- }, 75);
501
- });
502
-
503
- it("should initialize with initial features", function (done) {
504
- const sdk = createInstance({
505
- initialFeatures: {
506
- test: {
507
- enabled: true,
508
- variation: "control",
509
- variables: {
510
- color: "red",
511
- },
512
- },
513
- },
514
- datafileUrl: "http://localhost:3000/datafile.json",
515
- handleDatafileFetch: function () {
516
- const content: DatafileContent = {
517
- schemaVersion: "1",
518
- revision: "1.0",
519
- features: [
520
- {
521
- key: "test",
522
- bucketBy: "userId",
523
- variations: [{ value: "control" }, { value: "treatment" }],
524
- traffic: [
525
- {
526
- key: "1",
527
- segments: "*",
528
- percentage: 100000,
529
- allocation: [
530
- { variation: "control", range: [0, 0] },
531
- { variation: "treatment", range: [0, 100000] },
532
- ],
533
- },
534
- ],
535
- },
536
- ],
537
- attributes: [],
538
- segments: [],
539
- };
540
-
541
- return new Promise(function (resolve) {
542
- setTimeout(function () {
543
- resolve(content);
544
- }, 50);
545
- });
546
- },
547
- });
548
-
549
- // initially control
550
- expect(
551
- sdk.getVariation("test", {
552
- userId: "123",
553
- }),
554
- ).toEqual("control");
555
- expect(
556
- sdk.getVariable("test", "color", {
557
- userId: "123",
558
- }),
559
- ).toEqual("red");
560
-
561
- setTimeout(function () {
562
- // treatment after fetching datafile
355
+ sdk.setSticky({}, true);
563
356
  expect(
564
357
  sdk.getVariation("test", {
565
358
  userId: "123",
@@ -573,10 +366,10 @@ describe("sdk: instance", function () {
573
366
  it("should honour simple required features", function () {
574
367
  const sdk = createInstance({
575
368
  datafile: {
576
- schemaVersion: "1",
369
+ schemaVersion: "2",
577
370
  revision: "1.0",
578
- features: [
579
- {
371
+ features: {
372
+ requiredKey: {
580
373
  key: "requiredKey",
581
374
  bucketBy: "userId",
582
375
  traffic: [
@@ -589,7 +382,7 @@ describe("sdk: instance", function () {
589
382
  ],
590
383
  },
591
384
 
592
- {
385
+ myKey: {
593
386
  key: "myKey",
594
387
  bucketBy: "userId",
595
388
  required: ["requiredKey"],
@@ -602,9 +395,8 @@ describe("sdk: instance", function () {
602
395
  },
603
396
  ],
604
397
  },
605
- ],
606
- attributes: [],
607
- segments: [],
398
+ },
399
+ segments: {},
608
400
  },
609
401
  });
610
402
 
@@ -614,10 +406,10 @@ describe("sdk: instance", function () {
614
406
  // enabling required should enable the feature too
615
407
  const sdk2 = createInstance({
616
408
  datafile: {
617
- schemaVersion: "1",
409
+ schemaVersion: "2",
618
410
  revision: "1.0",
619
- features: [
620
- {
411
+ features: {
412
+ requiredKey: {
621
413
  key: "requiredKey",
622
414
  bucketBy: "userId",
623
415
  traffic: [
@@ -630,7 +422,7 @@ describe("sdk: instance", function () {
630
422
  ],
631
423
  },
632
424
 
633
- {
425
+ myKey: {
634
426
  key: "myKey",
635
427
  bucketBy: "userId",
636
428
  required: ["requiredKey"],
@@ -643,9 +435,8 @@ describe("sdk: instance", function () {
643
435
  },
644
436
  ],
645
437
  },
646
- ],
647
- attributes: [],
648
- segments: [],
438
+ },
439
+ segments: {},
649
440
  },
650
441
  });
651
442
  expect(sdk2.isEnabled("myKey")).toEqual(true);
@@ -655,10 +446,10 @@ describe("sdk: instance", function () {
655
446
  // should be disabled because required has different variation
656
447
  const sdk = createInstance({
657
448
  datafile: {
658
- schemaVersion: "1",
449
+ schemaVersion: "2",
659
450
  revision: "1.0",
660
- features: [
661
- {
451
+ features: {
452
+ requiredKey: {
662
453
  key: "requiredKey",
663
454
  bucketBy: "userId",
664
455
  variations: [{ value: "control" }, { value: "treatment" }],
@@ -675,7 +466,7 @@ describe("sdk: instance", function () {
675
466
  ],
676
467
  },
677
468
 
678
- {
469
+ myKey: {
679
470
  key: "myKey",
680
471
  bucketBy: "userId",
681
472
  required: [
@@ -693,9 +484,8 @@ describe("sdk: instance", function () {
693
484
  },
694
485
  ],
695
486
  },
696
- ],
697
- attributes: [],
698
- segments: [],
487
+ },
488
+ segments: {},
699
489
  },
700
490
  });
701
491
 
@@ -704,10 +494,10 @@ describe("sdk: instance", function () {
704
494
  // child should be enabled because required has desired variation
705
495
  const sdk2 = createInstance({
706
496
  datafile: {
707
- schemaVersion: "1",
497
+ schemaVersion: "2",
708
498
  revision: "1.0",
709
- features: [
710
- {
499
+ features: {
500
+ requiredKey: {
711
501
  key: "requiredKey",
712
502
  bucketBy: "userId",
713
503
  variations: [{ value: "control" }, { value: "treatment" }],
@@ -724,7 +514,7 @@ describe("sdk: instance", function () {
724
514
  ],
725
515
  },
726
516
 
727
- {
517
+ myKey: {
728
518
  key: "myKey",
729
519
  bucketBy: "userId",
730
520
  required: [
@@ -742,9 +532,8 @@ describe("sdk: instance", function () {
742
532
  },
743
533
  ],
744
534
  },
745
- ],
746
- attributes: [],
747
- segments: [],
535
+ },
536
+ segments: {},
748
537
  },
749
538
  });
750
539
  expect(sdk2.isEnabled("myKey")).toEqual(true);
@@ -755,10 +544,10 @@ describe("sdk: instance", function () {
755
544
 
756
545
  const sdk = createInstance({
757
546
  datafile: {
758
- schemaVersion: "1",
547
+ schemaVersion: "2",
759
548
  revision: "1.0",
760
- features: [
761
- {
549
+ features: {
550
+ test: {
762
551
  key: "test",
763
552
  bucketBy: "userId",
764
553
  variations: [{ value: "control" }, { value: "treatment" }],
@@ -774,7 +563,7 @@ describe("sdk: instance", function () {
774
563
  },
775
564
  ],
776
565
  },
777
- {
566
+ deprecatedTest: {
778
567
  key: "deprecatedTest",
779
568
  deprecated: true,
780
569
  bucketBy: "userId",
@@ -791,9 +580,8 @@ describe("sdk: instance", function () {
791
580
  },
792
581
  ],
793
582
  },
794
- ],
795
- attributes: [],
796
- segments: [],
583
+ },
584
+ segments: {},
797
585
  },
798
586
  logger: createLogger({
799
587
  handler: function (level, message) {
@@ -819,10 +607,10 @@ describe("sdk: instance", function () {
819
607
  it("should check if enabled for overridden flags from rules", function () {
820
608
  const sdk = createInstance({
821
609
  datafile: {
822
- schemaVersion: "1",
610
+ schemaVersion: "2",
823
611
  revision: "1.0",
824
- features: [
825
- {
612
+ features: {
613
+ test: {
826
614
  key: "test",
827
615
  bucketBy: "userId",
828
616
  traffic: [
@@ -841,10 +629,9 @@ describe("sdk: instance", function () {
841
629
  },
842
630
  ],
843
631
  },
844
- ],
845
- attributes: [],
846
- segments: [
847
- {
632
+ },
633
+ segments: {
634
+ netherlands: {
848
635
  key: "netherlands",
849
636
  conditions: JSON.stringify([
850
637
  {
@@ -854,7 +641,7 @@ describe("sdk: instance", function () {
854
641
  },
855
642
  ]),
856
643
  },
857
- ],
644
+ },
858
645
  },
859
646
  });
860
647
 
@@ -866,23 +653,27 @@ describe("sdk: instance", function () {
866
653
  let bucketValue = 10000;
867
654
 
868
655
  const sdk = createInstance({
869
- configureBucketValue: function () {
870
- return bucketValue;
871
- },
656
+ hooks: [
657
+ {
658
+ name: "unit-test",
659
+ bucketValue: function () {
660
+ return bucketValue;
661
+ },
662
+ },
663
+ ],
872
664
 
873
665
  datafile: {
874
- schemaVersion: "1",
666
+ schemaVersion: "2",
875
667
  revision: "1.0",
876
- features: [
877
- {
668
+ features: {
669
+ mutex: {
878
670
  key: "mutex",
879
671
  bucketBy: "userId",
880
672
  ranges: [[0, 50000]],
881
673
  traffic: [{ key: "1", segments: "*", percentage: 50000, allocation: [] }],
882
674
  },
883
- ],
884
- attributes: [],
885
- segments: [],
675
+ },
676
+ segments: {},
886
677
  },
887
678
  });
888
679
 
@@ -899,10 +690,10 @@ describe("sdk: instance", function () {
899
690
  it("should get variation", function () {
900
691
  const sdk = createInstance({
901
692
  datafile: {
902
- schemaVersion: "1",
693
+ schemaVersion: "2",
903
694
  revision: "1.0",
904
- features: [
905
- {
695
+ features: {
696
+ test: {
906
697
  key: "test",
907
698
  bucketBy: "userId",
908
699
  variations: [{ value: "control" }, { value: "treatment" }],
@@ -928,7 +719,7 @@ describe("sdk: instance", function () {
928
719
  },
929
720
  ],
930
721
  },
931
- {
722
+ testWithNoVariation: {
932
723
  key: "testWithNoVariation",
933
724
  bucketBy: "userId",
934
725
  traffic: [
@@ -940,10 +731,9 @@ describe("sdk: instance", function () {
940
731
  },
941
732
  ],
942
733
  },
943
- ],
944
- attributes: [],
945
- segments: [
946
- {
734
+ },
735
+ segments: {
736
+ netherlands: {
947
737
  key: "netherlands",
948
738
  conditions: JSON.stringify([
949
739
  {
@@ -953,7 +743,7 @@ describe("sdk: instance", function () {
953
743
  },
954
744
  ]),
955
745
  },
956
- ],
746
+ },
957
747
  },
958
748
  });
959
749
 
@@ -965,65 +755,65 @@ describe("sdk: instance", function () {
965
755
  expect(sdk.getVariation("test", { userId: "user-ch" })).toEqual("treatment");
966
756
 
967
757
  // non existing
968
- expect(sdk.getVariation("nonExistingFeature", context)).toEqual(undefined);
758
+ expect(sdk.getVariation("nonExistingFeature", context)).toEqual(null);
969
759
 
970
760
  // disabled
971
- expect(sdk.getVariation("test", { userId: "user-gb" })).toEqual(undefined);
972
- expect(sdk.getVariation("test", { userId: "user-gb" })).toEqual(undefined);
973
- expect(sdk.getVariation("test", { userId: "123", country: "nl" })).toEqual(undefined);
761
+ expect(sdk.getVariation("test", { userId: "user-gb" })).toEqual(null);
762
+ expect(sdk.getVariation("test", { userId: "user-gb" })).toEqual(null);
763
+ expect(sdk.getVariation("test", { userId: "123", country: "nl" })).toEqual(null);
974
764
 
975
765
  // no variation
976
- expect(sdk.getVariation("testWithNoVariation", context)).toEqual(undefined);
766
+ expect(sdk.getVariation("testWithNoVariation", context)).toEqual(null);
977
767
  });
978
768
 
979
769
  it("should get variable", function () {
980
770
  const sdk = createInstance({
981
771
  datafile: {
982
- schemaVersion: "1",
772
+ schemaVersion: "2",
983
773
  revision: "1.0",
984
- features: [
985
- {
774
+ features: {
775
+ test: {
986
776
  key: "test",
987
777
  bucketBy: "userId",
988
- variablesSchema: [
989
- {
778
+ variablesSchema: {
779
+ color: {
990
780
  key: "color",
991
781
  type: "string",
992
782
  defaultValue: "red",
993
783
  },
994
- {
784
+ showSidebar: {
995
785
  key: "showSidebar",
996
786
  type: "boolean",
997
787
  defaultValue: false,
998
788
  },
999
- {
789
+ sidebarTitle: {
1000
790
  key: "sidebarTitle",
1001
791
  type: "string",
1002
792
  defaultValue: "sidebar title",
1003
793
  },
1004
- {
794
+ count: {
1005
795
  key: "count",
1006
796
  type: "integer",
1007
797
  defaultValue: 0,
1008
798
  },
1009
- {
799
+ price: {
1010
800
  key: "price",
1011
801
  type: "double",
1012
802
  defaultValue: 9.99,
1013
803
  },
1014
- {
804
+ paymentMethods: {
1015
805
  key: "paymentMethods",
1016
806
  type: "array",
1017
807
  defaultValue: ["paypal", "creditcard"],
1018
808
  },
1019
- {
809
+ flatConfig: {
1020
810
  key: "flatConfig",
1021
811
  type: "object",
1022
812
  defaultValue: {
1023
813
  key: "value",
1024
814
  },
1025
815
  },
1026
- {
816
+ nestedConfig: {
1027
817
  key: "nestedConfig",
1028
818
  type: "json",
1029
819
  defaultValue: JSON.stringify({
@@ -1032,53 +822,49 @@ describe("sdk: instance", function () {
1032
822
  },
1033
823
  }),
1034
824
  },
1035
- ],
825
+ },
1036
826
  variations: [
1037
827
  { value: "control" },
1038
828
  {
1039
829
  value: "treatment",
1040
- variables: [
1041
- {
1042
- key: "showSidebar",
1043
- value: true,
1044
- overrides: [
1045
- {
1046
- segments: ["netherlands"],
1047
- value: false,
1048
- },
1049
- {
1050
- conditions: [
1051
- {
1052
- attribute: "country",
1053
- operator: "equals",
1054
- value: "de",
1055
- },
1056
- ],
1057
- value: false,
1058
- },
1059
- ],
1060
- },
1061
- {
1062
- key: "sidebarTitle",
1063
- value: "sidebar title from variation",
1064
- overrides: [
1065
- {
1066
- segments: ["netherlands"],
1067
- value: "Dutch title",
1068
- },
1069
- {
1070
- conditions: [
1071
- {
1072
- attribute: "country",
1073
- operator: "equals",
1074
- value: "de",
1075
- },
1076
- ],
1077
- value: "German title",
1078
- },
1079
- ],
1080
- },
1081
- ],
830
+ variables: {
831
+ showSidebar: true,
832
+ sidebarTitle: "sidebar title from variation",
833
+ },
834
+ variableOverrides: {
835
+ showSidebar: [
836
+ {
837
+ segments: ["netherlands"],
838
+ value: false,
839
+ },
840
+ {
841
+ conditions: [
842
+ {
843
+ attribute: "country",
844
+ operator: "equals",
845
+ value: "de",
846
+ },
847
+ ],
848
+ value: false,
849
+ },
850
+ ],
851
+ sidebarTitle: [
852
+ {
853
+ segments: ["netherlands"],
854
+ value: "Dutch title",
855
+ },
856
+ {
857
+ conditions: [
858
+ {
859
+ attribute: "country",
860
+ operator: "equals",
861
+ value: "de",
862
+ },
863
+ ],
864
+ value: "German title",
865
+ },
866
+ ],
867
+ },
1082
868
  },
1083
869
  ],
1084
870
  force: [
@@ -1136,13 +922,21 @@ describe("sdk: instance", function () {
1136
922
  },
1137
923
  ],
1138
924
  },
1139
- ],
1140
- attributes: [
1141
- { key: "userId", type: "string", capture: true },
1142
- { key: "country", type: "string" },
1143
- ],
1144
- segments: [
1145
- {
925
+ anotherTest: {
926
+ key: "test",
927
+ bucketBy: "userId",
928
+ traffic: [
929
+ // everyone
930
+ {
931
+ key: "1",
932
+ segments: "*",
933
+ percentage: 100000,
934
+ },
935
+ ],
936
+ },
937
+ },
938
+ segments: {
939
+ netherlands: {
1146
940
  key: "netherlands",
1147
941
  conditions: JSON.stringify([
1148
942
  {
@@ -1152,7 +946,7 @@ describe("sdk: instance", function () {
1152
946
  },
1153
947
  ]),
1154
948
  },
1155
- {
949
+ belgium: {
1156
950
  key: "belgium",
1157
951
  conditions: JSON.stringify([
1158
952
  {
@@ -1162,7 +956,7 @@ describe("sdk: instance", function () {
1162
956
  },
1163
957
  ]),
1164
958
  },
1165
- ],
959
+ },
1166
960
  },
1167
961
  });
1168
962
 
@@ -1170,6 +964,33 @@ describe("sdk: instance", function () {
1170
964
  userId: "123",
1171
965
  };
1172
966
 
967
+ const evaluatedFeatures = sdk.getAllEvaluations(context);
968
+ expect(evaluatedFeatures).toEqual({
969
+ test: {
970
+ enabled: true,
971
+ variation: "treatment",
972
+ variables: {
973
+ color: "red",
974
+ showSidebar: true,
975
+ sidebarTitle: "sidebar title from variation",
976
+ count: 0,
977
+ price: 9.99,
978
+ paymentMethods: ["paypal", "creditcard"],
979
+ flatConfig: {
980
+ key: "value",
981
+ },
982
+ nestedConfig: {
983
+ key: {
984
+ nested: "value",
985
+ },
986
+ },
987
+ },
988
+ },
989
+ anotherTest: {
990
+ enabled: true,
991
+ },
992
+ });
993
+
1173
994
  expect(sdk.getVariation("test", context)).toEqual("treatment");
1174
995
  expect(
1175
996
  sdk.getVariation("test", {
@@ -1249,24 +1070,20 @@ describe("sdk: instance", function () {
1249
1070
  });
1250
1071
 
1251
1072
  // non existing
1252
- expect(sdk.getVariable("test", "nonExisting", context)).toEqual(undefined);
1253
- expect(sdk.getVariable("nonExistingFeature", "nonExisting", context)).toEqual(undefined);
1073
+ expect(sdk.getVariable("test", "nonExisting", context)).toEqual(null);
1074
+ expect(sdk.getVariable("nonExistingFeature", "nonExisting", context)).toEqual(null);
1254
1075
 
1255
1076
  // disabled
1256
- expect(sdk.getVariable("test", "color", { userId: "user-gb" })).toEqual(undefined);
1077
+ expect(sdk.getVariable("test", "color", { userId: "user-gb" })).toEqual(null);
1257
1078
  });
1258
1079
 
1259
1080
  it("should get variables without any variations", function () {
1260
1081
  const sdk = createInstance({
1261
1082
  datafile: {
1262
- schemaVersion: "1",
1083
+ schemaVersion: "2",
1263
1084
  revision: "1.0",
1264
- attributes: [
1265
- { key: "userId", type: "string", capture: true },
1266
- { key: "country", type: "string" },
1267
- ],
1268
- segments: [
1269
- {
1085
+ segments: {
1086
+ netherlands: {
1270
1087
  key: "netherlands",
1271
1088
  conditions: JSON.stringify([
1272
1089
  {
@@ -1276,18 +1093,18 @@ describe("sdk: instance", function () {
1276
1093
  },
1277
1094
  ]),
1278
1095
  },
1279
- ],
1280
- features: [
1281
- {
1096
+ },
1097
+ features: {
1098
+ test: {
1282
1099
  key: "test",
1283
1100
  bucketBy: "userId",
1284
- variablesSchema: [
1285
- {
1101
+ variablesSchema: {
1102
+ color: {
1286
1103
  key: "color",
1287
1104
  type: "string",
1288
1105
  defaultValue: "red",
1289
1106
  },
1290
- ],
1107
+ },
1291
1108
  traffic: [
1292
1109
  {
1293
1110
  key: "1",
@@ -1306,7 +1123,7 @@ describe("sdk: instance", function () {
1306
1123
  },
1307
1124
  ],
1308
1125
  },
1309
- ],
1126
+ },
1310
1127
  },
1311
1128
  });
1312
1129
 
@@ -1333,10 +1150,10 @@ describe("sdk: instance", function () {
1333
1150
  it("should check if enabled for individually named segments", function () {
1334
1151
  const sdk = createInstance({
1335
1152
  datafile: {
1336
- schemaVersion: "1",
1153
+ schemaVersion: "2",
1337
1154
  revision: "1.0",
1338
- features: [
1339
- {
1155
+ features: {
1156
+ test: {
1340
1157
  key: "test",
1341
1158
  bucketBy: "userId",
1342
1159
  traffic: [
@@ -1349,10 +1166,9 @@ describe("sdk: instance", function () {
1349
1166
  },
1350
1167
  ],
1351
1168
  },
1352
- ],
1353
- attributes: [],
1354
- segments: [
1355
- {
1169
+ },
1170
+ segments: {
1171
+ netherlands: {
1356
1172
  key: "netherlands",
1357
1173
  conditions: JSON.stringify([
1358
1174
  {
@@ -1362,7 +1178,7 @@ describe("sdk: instance", function () {
1362
1178
  },
1363
1179
  ]),
1364
1180
  },
1365
- {
1181
+ iphone: {
1366
1182
  key: "iphone",
1367
1183
  conditions: JSON.stringify([
1368
1184
  {
@@ -1372,7 +1188,7 @@ describe("sdk: instance", function () {
1372
1188
  },
1373
1189
  ]),
1374
1190
  },
1375
- {
1191
+ unitedStates: {
1376
1192
  key: "unitedStates",
1377
1193
  conditions: JSON.stringify([
1378
1194
  {
@@ -1382,7 +1198,7 @@ describe("sdk: instance", function () {
1382
1198
  },
1383
1199
  ]),
1384
1200
  },
1385
- ],
1201
+ },
1386
1202
  },
1387
1203
  });
1388
1204