@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,28 +1,26 @@
1
- import { Attribute, DatafileContent } from "@featurevisor/types";
1
+ import type { DatafileContent, GroupSegment } from "@featurevisor/types";
2
+
2
3
  import { DatafileReader } from "./datafileReader";
4
+ import { createLogger } from "./logger";
5
+
6
+ interface Group {
7
+ key: string;
8
+ segments: GroupSegment | GroupSegment[] | "*";
9
+ }
3
10
 
4
11
  describe("sdk: DatafileReader", function () {
12
+ const logger = createLogger();
13
+
5
14
  it("should be a function", function () {
6
15
  expect(typeof DatafileReader).toEqual("function");
7
16
  });
8
17
 
9
- it("v1 datafile schema: should return requested entities", function () {
18
+ it("v2 datafile schema: should return requested entities", function () {
10
19
  const datafileJson: DatafileContent = {
11
- schemaVersion: "1",
20
+ schemaVersion: "2",
12
21
  revision: "1",
13
- attributes: [
14
- {
15
- key: "userId",
16
- type: "string",
17
- capture: true,
18
- },
19
- {
20
- key: "country",
21
- type: "string",
22
- },
23
- ],
24
- segments: [
25
- {
22
+ segments: {
23
+ netherlands: {
26
24
  key: "netherlands",
27
25
  conditions: [
28
26
  {
@@ -32,7 +30,7 @@ describe("sdk: DatafileReader", function () {
32
30
  },
33
31
  ],
34
32
  },
35
- {
33
+ germany: {
36
34
  key: "germany",
37
35
  conditions: JSON.stringify([
38
36
  {
@@ -42,21 +40,18 @@ describe("sdk: DatafileReader", function () {
42
40
  },
43
41
  ]),
44
42
  },
45
- ],
46
- features: [
47
- {
43
+ },
44
+ features: {
45
+ test: {
48
46
  key: "test",
49
47
  bucketBy: "userId",
50
48
  variations: [
51
49
  { value: "control" },
52
50
  {
53
51
  value: "treatment",
54
- variables: [
55
- {
56
- key: "showSidebar",
57
- value: true,
58
- },
59
- ],
52
+ variables: {
53
+ showSidebar: true,
54
+ },
60
55
  },
61
56
  ],
62
57
  traffic: [
@@ -71,38 +66,138 @@ describe("sdk: DatafileReader", function () {
71
66
  },
72
67
  ],
73
68
  },
74
- ],
69
+ },
75
70
  };
76
71
 
77
- const reader = new DatafileReader(datafileJson);
72
+ const reader = new DatafileReader({
73
+ datafile: datafileJson,
74
+ logger,
75
+ });
78
76
 
79
77
  expect(reader.getRevision()).toEqual("1");
80
- expect(reader.getSchemaVersion()).toEqual("1");
81
- expect(reader.getAllAttributes()).toEqual(datafileJson.attributes);
82
- expect(reader.getAttribute("userId")).toEqual(datafileJson.attributes[0]);
83
- expect(reader.getSegment("netherlands")).toEqual(datafileJson.segments[0]);
78
+ expect(reader.getSchemaVersion()).toEqual("2");
79
+ expect(reader.getSegment("netherlands")).toEqual(datafileJson.segments.netherlands);
84
80
  expect((reader.getSegment("germany") as any).conditions[0].value).toEqual("de");
85
81
  expect(reader.getSegment("belgium")).toEqual(undefined);
86
- expect(reader.getFeature("test")).toEqual(datafileJson.features[0]);
82
+ expect(reader.getFeature("test")).toEqual(datafileJson.features.test);
87
83
  expect(reader.getFeature("test2")).toEqual(undefined);
88
84
  });
89
85
 
90
- it("v2 datafile schema: should return requested entities", function () {
91
- const datafileJson: DatafileContent = {
92
- schemaVersion: "2",
93
- revision: "1",
94
- attributes: {
95
- userId: {
96
- key: "userId",
97
- type: "string",
98
- capture: true,
86
+ describe("segments", function () {
87
+ const groups = [
88
+ // everyone
89
+ {
90
+ key: "*",
91
+ segments: "*",
92
+ },
93
+
94
+ // dutch
95
+ {
96
+ key: "dutchMobileUsers",
97
+ segments: ["mobileUsers", "netherlands"],
98
+ },
99
+ {
100
+ key: "dutchMobileUsers2",
101
+ segments: {
102
+ and: ["mobileUsers", "netherlands"],
99
103
  },
100
- country: {
101
- key: "country",
102
- type: "string",
104
+ },
105
+ {
106
+ key: "dutchMobileOrDesktopUsers",
107
+ segments: ["netherlands", { or: ["mobileUsers", "desktopUsers"] }],
108
+ },
109
+ {
110
+ key: "dutchMobileOrDesktopUsers2",
111
+ segments: {
112
+ and: ["netherlands", { or: ["mobileUsers", "desktopUsers"] }],
103
113
  },
104
114
  },
115
+
116
+ // german
117
+ {
118
+ key: "germanMobileUsers",
119
+ segments: [
120
+ {
121
+ and: ["mobileUsers", "germany"],
122
+ },
123
+ ],
124
+ },
125
+ {
126
+ key: "germanNonMobileUsers",
127
+ segments: [
128
+ {
129
+ and: [
130
+ "germany",
131
+ {
132
+ not: ["mobileUsers"],
133
+ },
134
+ ],
135
+ },
136
+ ],
137
+ },
138
+
139
+ // version
140
+ {
141
+ key: "notVersion5.5",
142
+ segments: [
143
+ {
144
+ not: ["version_5.5"],
145
+ },
146
+ ],
147
+ },
148
+ ];
149
+
150
+ const datafileContent: DatafileContent = {
151
+ schemaVersion: "2",
152
+ revision: "1",
153
+ features: {},
154
+
105
155
  segments: {
156
+ // deviceType
157
+ mobileUsers: {
158
+ key: "mobileUsers",
159
+ conditions: [
160
+ {
161
+ attribute: "deviceType",
162
+ operator: "equals",
163
+ value: "mobile",
164
+ },
165
+ ],
166
+ },
167
+ desktopUsers: {
168
+ key: "desktopUsers",
169
+ conditions: [
170
+ {
171
+ attribute: "deviceType",
172
+ operator: "equals",
173
+ value: "desktop",
174
+ },
175
+ ],
176
+ },
177
+
178
+ // browser
179
+ chromeBrowser: {
180
+ key: "chromeBrowser",
181
+ conditions: [
182
+ {
183
+ attribute: "browser",
184
+ operator: "equals",
185
+ value: "chrome",
186
+ },
187
+ ],
188
+ },
189
+ firefoxBrowser: {
190
+ key: "firefoxBrowser",
191
+ conditions: [
192
+ {
193
+ attribute: "browser",
194
+ operator: "equals",
195
+ value: "firefox",
196
+ },
197
+ ],
198
+ },
199
+
200
+ // country
106
201
  netherlands: {
107
202
  key: "netherlands",
108
203
  conditions: [
@@ -115,39 +210,31 @@ describe("sdk: DatafileReader", function () {
115
210
  },
116
211
  germany: {
117
212
  key: "germany",
118
- conditions: JSON.stringify([
213
+ conditions: [
119
214
  {
120
215
  attribute: "country",
121
216
  operator: "equals",
122
217
  value: "de",
123
218
  },
124
- ]),
219
+ ],
125
220
  },
126
- },
127
- features: {
128
- test: {
129
- key: "test",
130
- bucketBy: "userId",
131
- variations: [
132
- { value: "control" },
221
+
222
+ // version
223
+ "version_5.5": {
224
+ key: "version_5.5",
225
+ conditions: [
133
226
  {
134
- value: "treatment",
135
- variables: [
227
+ or: [
136
228
  {
137
- key: "showSidebar",
138
- value: true,
229
+ attribute: "version",
230
+ operator: "equals",
231
+ value: "5.5",
232
+ },
233
+ {
234
+ attribute: "version",
235
+ operator: "equals",
236
+ value: 5.5,
139
237
  },
140
- ],
141
- },
142
- ],
143
- traffic: [
144
- {
145
- key: "1",
146
- segments: "*",
147
- percentage: 100000,
148
- allocation: [
149
- { variation: "control", range: [0, 0] },
150
- { variation: "treatment", range: [0, 100000] },
151
238
  ],
152
239
  },
153
240
  ],
@@ -155,21 +242,246 @@ describe("sdk: DatafileReader", function () {
155
242
  },
156
243
  };
157
244
 
158
- const reader = new DatafileReader(datafileJson);
245
+ const datafileReader = new DatafileReader({
246
+ datafile: datafileContent,
247
+ logger,
248
+ });
159
249
 
160
- expect(reader.getRevision()).toEqual("1");
161
- expect(reader.getSchemaVersion()).toEqual("2");
162
- expect(reader.getAllAttributes()).toEqual(
163
- Object.keys(datafileJson.attributes).reduce((acc, key) => {
164
- acc.push(datafileJson.attributes[key]);
165
- return acc;
166
- }, [] as Attribute[]),
167
- );
168
- expect(reader.getAttribute("userId")).toEqual(datafileJson.attributes.userId);
169
- expect(reader.getSegment("netherlands")).toEqual(datafileJson.segments.netherlands);
170
- expect((reader.getSegment("germany") as any).conditions[0].value).toEqual("de");
171
- expect(reader.getSegment("belgium")).toEqual(undefined);
172
- expect(reader.getFeature("test")).toEqual(datafileJson.features.test);
173
- expect(reader.getFeature("test2")).toEqual(undefined);
250
+ it("should match everyone", function () {
251
+ const group = groups.find((g) => g.key === "*") as Group;
252
+
253
+ // match
254
+ expect(datafileReader.allSegmentsAreMatched(group.segments, {})).toEqual(true);
255
+ expect(datafileReader.allSegmentsAreMatched(group.segments, { foo: "foo" })).toEqual(true);
256
+ expect(datafileReader.allSegmentsAreMatched(group.segments, { bar: "bar" })).toEqual(true);
257
+ });
258
+
259
+ it("should match dutchMobileUsers", function () {
260
+ const group = groups.find((g) => g.key === "dutchMobileUsers") as Group;
261
+
262
+ // match
263
+ expect(
264
+ datafileReader.allSegmentsAreMatched(group.segments, {
265
+ country: "nl",
266
+ deviceType: "mobile",
267
+ }),
268
+ ).toEqual(true);
269
+ expect(
270
+ datafileReader.allSegmentsAreMatched(group.segments, {
271
+ country: "nl",
272
+ deviceType: "mobile",
273
+ browser: "chrome",
274
+ }),
275
+ ).toEqual(true);
276
+
277
+ // not match
278
+ expect(datafileReader.allSegmentsAreMatched(group.segments, {})).toEqual(false);
279
+ expect(
280
+ datafileReader.allSegmentsAreMatched(group.segments, {
281
+ country: "de",
282
+ deviceType: "mobile",
283
+ }),
284
+ ).toEqual(false);
285
+ });
286
+
287
+ it("should match dutchMobileUsers2", function () {
288
+ const group = groups.find((g) => g.key === "dutchMobileUsers") as Group;
289
+
290
+ // match
291
+ expect(
292
+ datafileReader.allSegmentsAreMatched(group.segments, {
293
+ country: "nl",
294
+ deviceType: "mobile",
295
+ }),
296
+ ).toEqual(true);
297
+ expect(
298
+ datafileReader.allSegmentsAreMatched(group.segments, {
299
+ country: "nl",
300
+ deviceType: "mobile",
301
+ browser: "chrome",
302
+ }),
303
+ ).toEqual(true);
304
+
305
+ // not match
306
+ expect(datafileReader.allSegmentsAreMatched(group.segments, {})).toEqual(false);
307
+ expect(
308
+ datafileReader.allSegmentsAreMatched(group.segments, {
309
+ country: "de",
310
+ deviceType: "mobile",
311
+ }),
312
+ ).toEqual(false);
313
+ });
314
+
315
+ it("should match dutchMobileOrDesktopUsers", function () {
316
+ const group = groups.find((g) => g.key === "dutchMobileOrDesktopUsers") as Group;
317
+
318
+ // match
319
+ expect(
320
+ datafileReader.allSegmentsAreMatched(group.segments, {
321
+ country: "nl",
322
+ deviceType: "mobile",
323
+ }),
324
+ ).toEqual(true);
325
+ expect(
326
+ datafileReader.allSegmentsAreMatched(group.segments, {
327
+ country: "nl",
328
+ deviceType: "mobile",
329
+ browser: "chrome",
330
+ }),
331
+ ).toEqual(true);
332
+ expect(
333
+ datafileReader.allSegmentsAreMatched(group.segments, {
334
+ country: "nl",
335
+ deviceType: "desktop",
336
+ }),
337
+ ).toEqual(true);
338
+ expect(
339
+ datafileReader.allSegmentsAreMatched(group.segments, {
340
+ country: "nl",
341
+ deviceType: "desktop",
342
+ browser: "chrome",
343
+ }),
344
+ ).toEqual(true);
345
+
346
+ // not match
347
+ expect(datafileReader.allSegmentsAreMatched(group.segments, {})).toEqual(false);
348
+ expect(
349
+ datafileReader.allSegmentsAreMatched(group.segments, {
350
+ country: "de",
351
+ deviceType: "mobile",
352
+ }),
353
+ ).toEqual(false);
354
+ expect(
355
+ datafileReader.allSegmentsAreMatched(group.segments, {
356
+ country: "de",
357
+ deviceType: "desktop",
358
+ }),
359
+ ).toEqual(false);
360
+ });
361
+
362
+ it("should match dutchMobileOrDesktopUsers2", function () {
363
+ const group = groups.find((g) => g.key === "dutchMobileOrDesktopUsers2") as Group;
364
+
365
+ // match
366
+ expect(
367
+ datafileReader.allSegmentsAreMatched(group.segments, {
368
+ country: "nl",
369
+ deviceType: "mobile",
370
+ }),
371
+ ).toEqual(true);
372
+ expect(
373
+ datafileReader.allSegmentsAreMatched(group.segments, {
374
+ country: "nl",
375
+ deviceType: "mobile",
376
+ browser: "chrome",
377
+ }),
378
+ ).toEqual(true);
379
+ expect(
380
+ datafileReader.allSegmentsAreMatched(group.segments, {
381
+ country: "nl",
382
+ deviceType: "desktop",
383
+ }),
384
+ ).toEqual(true);
385
+ expect(
386
+ datafileReader.allSegmentsAreMatched(group.segments, {
387
+ country: "nl",
388
+ deviceType: "desktop",
389
+ browser: "chrome",
390
+ }),
391
+ ).toEqual(true);
392
+
393
+ // not match
394
+ expect(datafileReader.allSegmentsAreMatched(group.segments, {})).toEqual(false);
395
+ expect(
396
+ datafileReader.allSegmentsAreMatched(group.segments, {
397
+ country: "de",
398
+ deviceType: "mobile",
399
+ }),
400
+ ).toEqual(false);
401
+ expect(
402
+ datafileReader.allSegmentsAreMatched(group.segments, {
403
+ country: "de",
404
+ deviceType: "desktop",
405
+ }),
406
+ ).toEqual(false);
407
+ });
408
+
409
+ it("should match germanMobileUsers", function () {
410
+ const group = groups.find((g) => g.key === "germanMobileUsers") as Group;
411
+
412
+ // match
413
+ expect(
414
+ datafileReader.allSegmentsAreMatched(group.segments, {
415
+ country: "de",
416
+ deviceType: "mobile",
417
+ }),
418
+ ).toEqual(true);
419
+ expect(
420
+ datafileReader.allSegmentsAreMatched(group.segments, {
421
+ country: "de",
422
+ deviceType: "mobile",
423
+ browser: "chrome",
424
+ }),
425
+ ).toEqual(true);
426
+
427
+ // not match
428
+ expect(datafileReader.allSegmentsAreMatched(group.segments, {})).toEqual(false);
429
+ expect(
430
+ datafileReader.allSegmentsAreMatched(group.segments, {
431
+ country: "nl",
432
+ deviceType: "mobile",
433
+ }),
434
+ ).toEqual(false);
435
+ });
436
+
437
+ it("should match germanNonMobileUsers", function () {
438
+ const group = groups.find((g) => g.key === "germanNonMobileUsers") as Group;
439
+
440
+ // match
441
+ expect(
442
+ datafileReader.allSegmentsAreMatched(group.segments, {
443
+ country: "de",
444
+ deviceType: "desktop",
445
+ }),
446
+ ).toEqual(true);
447
+ expect(
448
+ datafileReader.allSegmentsAreMatched(group.segments, {
449
+ country: "de",
450
+ deviceType: "desktop",
451
+ browser: "chrome",
452
+ }),
453
+ ).toEqual(true);
454
+
455
+ // not match
456
+ expect(datafileReader.allSegmentsAreMatched(group.segments, {})).toEqual(false);
457
+ expect(
458
+ datafileReader.allSegmentsAreMatched(group.segments, {
459
+ country: "nl",
460
+ deviceType: "desktop",
461
+ }),
462
+ ).toEqual(false);
463
+ });
464
+
465
+ it("should match notVersion5.5", function () {
466
+ const group = groups.find((g) => g.key === "notVersion5.5") as Group;
467
+
468
+ // match
469
+ expect(datafileReader.allSegmentsAreMatched(group.segments, {})).toEqual(true);
470
+ expect(datafileReader.allSegmentsAreMatched(group.segments, {})).toEqual(true);
471
+ expect(datafileReader.allSegmentsAreMatched(group.segments, { version: "5.6" })).toEqual(
472
+ true,
473
+ );
474
+ expect(datafileReader.allSegmentsAreMatched(group.segments, { version: 5.6 })).toEqual(true);
475
+ expect(datafileReader.allSegmentsAreMatched(group.segments, { version: "5.7" })).toEqual(
476
+ true,
477
+ );
478
+ expect(datafileReader.allSegmentsAreMatched(group.segments, { version: 5.7 })).toEqual(true);
479
+
480
+ // not match
481
+ expect(datafileReader.allSegmentsAreMatched(group.segments, { version: "5.5" })).toEqual(
482
+ false,
483
+ );
484
+ expect(datafileReader.allSegmentsAreMatched(group.segments, { version: 5.5 })).toEqual(false);
485
+ });
174
486
  });
175
487
  });