@featurevisor/sdk 1.3.0 → 1.16.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/conditions.ts CHANGED
@@ -2,6 +2,8 @@ import { compareVersions } from "compare-versions";
2
2
 
3
3
  import { Context, Condition, PlainCondition } from "@featurevisor/types";
4
4
 
5
+ import { Logger } from "./logger";
6
+
5
7
  export function conditionIsMatched(condition: PlainCondition, context: Context): boolean {
6
8
  const { attribute, operator, value } = condition;
7
9
 
@@ -75,17 +77,30 @@ export function conditionIsMatched(condition: PlainCondition, context: Context):
75
77
  export function allConditionsAreMatched(
76
78
  conditions: Condition[] | Condition,
77
79
  context: Context,
80
+ logger: Logger,
78
81
  ): boolean {
79
82
  if ("attribute" in conditions) {
80
- return conditionIsMatched(conditions, context);
83
+ try {
84
+ return conditionIsMatched(conditions, context);
85
+ } catch (e) {
86
+ logger.warn(e.message, {
87
+ error: e,
88
+ details: {
89
+ condition: conditions,
90
+ context,
91
+ },
92
+ });
93
+
94
+ return false;
95
+ }
81
96
  }
82
97
 
83
98
  if ("and" in conditions && Array.isArray(conditions.and)) {
84
- return conditions.and.every((c) => allConditionsAreMatched(c, context));
99
+ return conditions.and.every((c) => allConditionsAreMatched(c, context, logger));
85
100
  }
86
101
 
87
102
  if ("or" in conditions && Array.isArray(conditions.or)) {
88
- return conditions.or.some((c) => allConditionsAreMatched(c, context));
103
+ return conditions.or.some((c) => allConditionsAreMatched(c, context, logger));
89
104
  }
90
105
 
91
106
  if ("not" in conditions && Array.isArray(conditions.not)) {
@@ -96,12 +111,13 @@ export function allConditionsAreMatched(
96
111
  and: conditions.not,
97
112
  },
98
113
  context,
114
+ logger,
99
115
  ) === false,
100
116
  );
101
117
  }
102
118
 
103
119
  if (Array.isArray(conditions)) {
104
- return conditions.every((c) => allConditionsAreMatched(c, context));
120
+ return conditions.every((c) => allConditionsAreMatched(c, context, logger));
105
121
  }
106
122
 
107
123
  return false;
package/src/feature.ts CHANGED
@@ -2,6 +2,7 @@ import { Allocation, Context, Traffic, Feature, Force } from "@featurevisor/type
2
2
  import { DatafileReader } from "./datafileReader";
3
3
  import { allGroupSegmentsAreMatched } from "./segments";
4
4
  import { allConditionsAreMatched } from "./conditions";
5
+ import { Logger } from "./logger";
5
6
 
6
7
  export function getMatchedAllocation(
7
8
  traffic: Traffic,
@@ -30,10 +31,16 @@ export function getMatchedTraffic(
30
31
  traffic: Traffic[],
31
32
  context: Context,
32
33
  datafileReader: DatafileReader,
34
+ logger: Logger,
33
35
  ): Traffic | undefined {
34
36
  return traffic.find((t) => {
35
37
  if (
36
- !allGroupSegmentsAreMatched(parseFromStringifiedSegments(t.segments), context, datafileReader)
38
+ !allGroupSegmentsAreMatched(
39
+ parseFromStringifiedSegments(t.segments),
40
+ context,
41
+ datafileReader,
42
+ logger,
43
+ )
37
44
  ) {
38
45
  return false;
39
46
  }
@@ -52,12 +59,14 @@ export function getMatchedTrafficAndAllocation(
52
59
  context: Context,
53
60
  bucketValue: number,
54
61
  datafileReader: DatafileReader,
62
+ logger: Logger,
55
63
  ): MatchedTrafficAndAllocation {
56
64
  const matchedTraffic = traffic.find((t) => {
57
65
  return allGroupSegmentsAreMatched(
58
66
  parseFromStringifiedSegments(t.segments),
59
67
  context,
60
68
  datafileReader,
69
+ logger,
61
70
  );
62
71
  });
63
72
 
@@ -80,6 +89,7 @@ export function findForceFromFeature(
80
89
  feature: Feature,
81
90
  context: Context,
82
91
  datafileReader: DatafileReader,
92
+ logger: Logger,
83
93
  ): Force | undefined {
84
94
  if (!feature.force) {
85
95
  return undefined;
@@ -87,11 +97,11 @@ export function findForceFromFeature(
87
97
 
88
98
  return feature.force.find((f: Force) => {
89
99
  if (f.conditions) {
90
- return allConditionsAreMatched(f.conditions, context);
100
+ return allConditionsAreMatched(f.conditions, context, logger);
91
101
  }
92
102
 
93
103
  if (f.segments) {
94
- return allGroupSegmentsAreMatched(f.segments, context, datafileReader);
104
+ return allGroupSegmentsAreMatched(f.segments, context, datafileReader, logger);
95
105
  }
96
106
 
97
107
  return false;
package/src/index.ts CHANGED
@@ -2,3 +2,4 @@ export * from "./bucket";
2
2
  export * from "./instance";
3
3
  export * from "./logger";
4
4
  export * from "./conditions";
5
+ export * from "./emitter";
package/src/instance.ts CHANGED
@@ -521,7 +521,7 @@ export class FeaturevisorInstance {
521
521
  const finalContext = this.interceptContext ? this.interceptContext(context) : context;
522
522
 
523
523
  // forced
524
- const force = findForceFromFeature(feature, context, this.datafileReader);
524
+ const force = findForceFromFeature(feature, context, this.datafileReader, this.logger);
525
525
 
526
526
  if (force && typeof force.enabled !== "undefined") {
527
527
  evaluation = {
@@ -579,7 +579,12 @@ export class FeaturevisorInstance {
579
579
  // bucketing
580
580
  const bucketValue = this.getBucketValue(feature, finalContext);
581
581
 
582
- const matchedTraffic = getMatchedTraffic(feature.traffic, finalContext, this.datafileReader);
582
+ const matchedTraffic = getMatchedTraffic(
583
+ feature.traffic,
584
+ finalContext,
585
+ this.datafileReader,
586
+ this.logger,
587
+ );
583
588
 
584
589
  if (matchedTraffic) {
585
590
  // check if mutually exclusive
@@ -774,7 +779,7 @@ export class FeaturevisorInstance {
774
779
  const finalContext = this.interceptContext ? this.interceptContext(context) : context;
775
780
 
776
781
  // forced
777
- const force = findForceFromFeature(feature, context, this.datafileReader);
782
+ const force = findForceFromFeature(feature, context, this.datafileReader, this.logger);
778
783
 
779
784
  if (force && force.variation) {
780
785
  const variation = feature.variations.find((v) => v.value === force.variation);
@@ -800,6 +805,7 @@ export class FeaturevisorInstance {
800
805
  finalContext,
801
806
  bucketValue,
802
807
  this.datafileReader,
808
+ this.logger,
803
809
  );
804
810
 
805
811
  if (matchedTraffic) {
@@ -1040,7 +1046,7 @@ export class FeaturevisorInstance {
1040
1046
  const finalContext = this.interceptContext ? this.interceptContext(context) : context;
1041
1047
 
1042
1048
  // forced
1043
- const force = findForceFromFeature(feature, context, this.datafileReader);
1049
+ const force = findForceFromFeature(feature, context, this.datafileReader, this.logger);
1044
1050
 
1045
1051
  if (force && force.variables && typeof force.variables[variableKey] !== "undefined") {
1046
1052
  evaluation = {
@@ -1064,6 +1070,7 @@ export class FeaturevisorInstance {
1064
1070
  finalContext,
1065
1071
  bucketValue,
1066
1072
  this.datafileReader,
1073
+ this.logger,
1067
1074
  );
1068
1075
 
1069
1076
  if (matchedTraffic) {
@@ -1109,6 +1116,7 @@ export class FeaturevisorInstance {
1109
1116
  return allConditionsAreMatched(
1110
1117
  typeof o.conditions === "string" ? JSON.parse(o.conditions) : o.conditions,
1111
1118
  finalContext,
1119
+ this.logger,
1112
1120
  );
1113
1121
  }
1114
1122
 
@@ -1117,6 +1125,7 @@ export class FeaturevisorInstance {
1117
1125
  parseFromStringifiedSegments(o.segments),
1118
1126
  finalContext,
1119
1127
  this.datafileReader,
1128
+ this.logger,
1120
1129
  );
1121
1130
  }
1122
1131
 
@@ -1,6 +1,8 @@
1
+ import { DatafileContent, GroupSegment } from "@featurevisor/types";
2
+
1
3
  import { DatafileReader } from "./datafileReader";
2
4
  import { allGroupSegmentsAreMatched } from "./segments";
3
- import { DatafileContent, GroupSegment } from "@featurevisor/types";
5
+ import { createLogger } from "./logger";
4
6
 
5
7
  interface Group {
6
8
  key: string;
@@ -8,6 +10,8 @@ interface Group {
8
10
  }
9
11
 
10
12
  describe("sdk: Segments", function () {
13
+ const logger = createLogger();
14
+
11
15
  it("should be a function", function () {
12
16
  expect(typeof allGroupSegmentsAreMatched).toEqual("function");
13
17
  });
@@ -178,13 +182,13 @@ describe("sdk: Segments", function () {
178
182
  const group = groups.find((g) => g.key === "*") as Group;
179
183
 
180
184
  // match
181
- expect(allGroupSegmentsAreMatched(group.segments, {}, datafileReader)).toEqual(true);
182
- expect(allGroupSegmentsAreMatched(group.segments, { foo: "foo" }, datafileReader)).toEqual(
183
- true,
184
- );
185
- expect(allGroupSegmentsAreMatched(group.segments, { bar: "bar" }, datafileReader)).toEqual(
186
- true,
187
- );
185
+ expect(allGroupSegmentsAreMatched(group.segments, {}, datafileReader, logger)).toEqual(true);
186
+ expect(
187
+ allGroupSegmentsAreMatched(group.segments, { foo: "foo" }, datafileReader, logger),
188
+ ).toEqual(true);
189
+ expect(
190
+ allGroupSegmentsAreMatched(group.segments, { bar: "bar" }, datafileReader, logger),
191
+ ).toEqual(true);
188
192
  });
189
193
 
190
194
  it("should match dutchMobileUsers", function () {
@@ -196,6 +200,7 @@ describe("sdk: Segments", function () {
196
200
  group.segments,
197
201
  { country: "nl", deviceType: "mobile" },
198
202
  datafileReader,
203
+ logger,
199
204
  ),
200
205
  ).toEqual(true);
201
206
  expect(
@@ -203,16 +208,18 @@ describe("sdk: Segments", function () {
203
208
  group.segments,
204
209
  { country: "nl", deviceType: "mobile", browser: "chrome" },
205
210
  datafileReader,
211
+ logger,
206
212
  ),
207
213
  ).toEqual(true);
208
214
 
209
215
  // not match
210
- expect(allGroupSegmentsAreMatched(group.segments, {}, datafileReader)).toEqual(false);
216
+ expect(allGroupSegmentsAreMatched(group.segments, {}, datafileReader, logger)).toEqual(false);
211
217
  expect(
212
218
  allGroupSegmentsAreMatched(
213
219
  group.segments,
214
220
  { country: "de", deviceType: "mobile" },
215
221
  datafileReader,
222
+ logger,
216
223
  ),
217
224
  ).toEqual(false);
218
225
  });
@@ -226,6 +233,7 @@ describe("sdk: Segments", function () {
226
233
  group.segments,
227
234
  { country: "nl", deviceType: "mobile" },
228
235
  datafileReader,
236
+ logger,
229
237
  ),
230
238
  ).toEqual(true);
231
239
  expect(
@@ -233,16 +241,18 @@ describe("sdk: Segments", function () {
233
241
  group.segments,
234
242
  { country: "nl", deviceType: "mobile", browser: "chrome" },
235
243
  datafileReader,
244
+ logger,
236
245
  ),
237
246
  ).toEqual(true);
238
247
 
239
248
  // not match
240
- expect(allGroupSegmentsAreMatched(group.segments, {}, datafileReader)).toEqual(false);
249
+ expect(allGroupSegmentsAreMatched(group.segments, {}, datafileReader, logger)).toEqual(false);
241
250
  expect(
242
251
  allGroupSegmentsAreMatched(
243
252
  group.segments,
244
253
  { country: "de", deviceType: "mobile" },
245
254
  datafileReader,
255
+ logger,
246
256
  ),
247
257
  ).toEqual(false);
248
258
  });
@@ -256,6 +266,7 @@ describe("sdk: Segments", function () {
256
266
  group.segments,
257
267
  { country: "nl", deviceType: "mobile" },
258
268
  datafileReader,
269
+ logger,
259
270
  ),
260
271
  ).toEqual(true);
261
272
  expect(
@@ -263,6 +274,7 @@ describe("sdk: Segments", function () {
263
274
  group.segments,
264
275
  { country: "nl", deviceType: "mobile", browser: "chrome" },
265
276
  datafileReader,
277
+ logger,
266
278
  ),
267
279
  ).toEqual(true);
268
280
  expect(
@@ -270,6 +282,7 @@ describe("sdk: Segments", function () {
270
282
  group.segments,
271
283
  { country: "nl", deviceType: "desktop" },
272
284
  datafileReader,
285
+ logger,
273
286
  ),
274
287
  ).toEqual(true);
275
288
  expect(
@@ -277,16 +290,18 @@ describe("sdk: Segments", function () {
277
290
  group.segments,
278
291
  { country: "nl", deviceType: "desktop", browser: "chrome" },
279
292
  datafileReader,
293
+ logger,
280
294
  ),
281
295
  ).toEqual(true);
282
296
 
283
297
  // not match
284
- expect(allGroupSegmentsAreMatched(group.segments, {}, datafileReader)).toEqual(false);
298
+ expect(allGroupSegmentsAreMatched(group.segments, {}, datafileReader, logger)).toEqual(false);
285
299
  expect(
286
300
  allGroupSegmentsAreMatched(
287
301
  group.segments,
288
302
  { country: "de", deviceType: "mobile" },
289
303
  datafileReader,
304
+ logger,
290
305
  ),
291
306
  ).toEqual(false);
292
307
  expect(
@@ -294,6 +309,7 @@ describe("sdk: Segments", function () {
294
309
  group.segments,
295
310
  { country: "de", deviceType: "desktop" },
296
311
  datafileReader,
312
+ logger,
297
313
  ),
298
314
  ).toEqual(false);
299
315
  });
@@ -307,6 +323,7 @@ describe("sdk: Segments", function () {
307
323
  group.segments,
308
324
  { country: "nl", deviceType: "mobile" },
309
325
  datafileReader,
326
+ logger,
310
327
  ),
311
328
  ).toEqual(true);
312
329
  expect(
@@ -314,6 +331,7 @@ describe("sdk: Segments", function () {
314
331
  group.segments,
315
332
  { country: "nl", deviceType: "mobile", browser: "chrome" },
316
333
  datafileReader,
334
+ logger,
317
335
  ),
318
336
  ).toEqual(true);
319
337
  expect(
@@ -321,6 +339,7 @@ describe("sdk: Segments", function () {
321
339
  group.segments,
322
340
  { country: "nl", deviceType: "desktop" },
323
341
  datafileReader,
342
+ logger,
324
343
  ),
325
344
  ).toEqual(true);
326
345
  expect(
@@ -328,16 +347,18 @@ describe("sdk: Segments", function () {
328
347
  group.segments,
329
348
  { country: "nl", deviceType: "desktop", browser: "chrome" },
330
349
  datafileReader,
350
+ logger,
331
351
  ),
332
352
  ).toEqual(true);
333
353
 
334
354
  // not match
335
- expect(allGroupSegmentsAreMatched(group.segments, {}, datafileReader)).toEqual(false);
355
+ expect(allGroupSegmentsAreMatched(group.segments, {}, datafileReader, logger)).toEqual(false);
336
356
  expect(
337
357
  allGroupSegmentsAreMatched(
338
358
  group.segments,
339
359
  { country: "de", deviceType: "mobile" },
340
360
  datafileReader,
361
+ logger,
341
362
  ),
342
363
  ).toEqual(false);
343
364
  expect(
@@ -345,6 +366,7 @@ describe("sdk: Segments", function () {
345
366
  group.segments,
346
367
  { country: "de", deviceType: "desktop" },
347
368
  datafileReader,
369
+ logger,
348
370
  ),
349
371
  ).toEqual(false);
350
372
  });
@@ -358,6 +380,7 @@ describe("sdk: Segments", function () {
358
380
  group.segments,
359
381
  { country: "de", deviceType: "mobile" },
360
382
  datafileReader,
383
+ logger,
361
384
  ),
362
385
  ).toEqual(true);
363
386
  expect(
@@ -365,16 +388,18 @@ describe("sdk: Segments", function () {
365
388
  group.segments,
366
389
  { country: "de", deviceType: "mobile", browser: "chrome" },
367
390
  datafileReader,
391
+ logger,
368
392
  ),
369
393
  ).toEqual(true);
370
394
 
371
395
  // not match
372
- expect(allGroupSegmentsAreMatched(group.segments, {}, datafileReader)).toEqual(false);
396
+ expect(allGroupSegmentsAreMatched(group.segments, {}, datafileReader, logger)).toEqual(false);
373
397
  expect(
374
398
  allGroupSegmentsAreMatched(
375
399
  group.segments,
376
400
  { country: "nl", deviceType: "mobile" },
377
401
  datafileReader,
402
+ logger,
378
403
  ),
379
404
  ).toEqual(false);
380
405
  });
@@ -388,6 +413,7 @@ describe("sdk: Segments", function () {
388
413
  group.segments,
389
414
  { country: "de", deviceType: "desktop" },
390
415
  datafileReader,
416
+ logger,
391
417
  ),
392
418
  ).toEqual(true);
393
419
  expect(
@@ -395,16 +421,18 @@ describe("sdk: Segments", function () {
395
421
  group.segments,
396
422
  { country: "de", deviceType: "desktop", browser: "chrome" },
397
423
  datafileReader,
424
+ logger,
398
425
  ),
399
426
  ).toEqual(true);
400
427
 
401
428
  // not match
402
- expect(allGroupSegmentsAreMatched(group.segments, {}, datafileReader)).toEqual(false);
429
+ expect(allGroupSegmentsAreMatched(group.segments, {}, datafileReader, logger)).toEqual(false);
403
430
  expect(
404
431
  allGroupSegmentsAreMatched(
405
432
  group.segments,
406
433
  { country: "nl", deviceType: "desktop" },
407
434
  datafileReader,
435
+ logger,
408
436
  ),
409
437
  ).toEqual(false);
410
438
  });
@@ -413,28 +441,28 @@ describe("sdk: Segments", function () {
413
441
  const group = groups.find((g) => g.key === "notVersion5.5") as Group;
414
442
 
415
443
  // match
416
- expect(allGroupSegmentsAreMatched(group.segments, {}, datafileReader)).toEqual(true);
417
- expect(allGroupSegmentsAreMatched(group.segments, {}, datafileReader)).toEqual(true);
444
+ expect(allGroupSegmentsAreMatched(group.segments, {}, datafileReader, logger)).toEqual(true);
445
+ expect(allGroupSegmentsAreMatched(group.segments, {}, datafileReader, logger)).toEqual(true);
446
+ expect(
447
+ allGroupSegmentsAreMatched(group.segments, { version: "5.6" }, datafileReader, logger),
448
+ ).toEqual(true);
418
449
  expect(
419
- allGroupSegmentsAreMatched(group.segments, { version: "5.6" }, datafileReader),
450
+ allGroupSegmentsAreMatched(group.segments, { version: 5.6 }, datafileReader, logger),
420
451
  ).toEqual(true);
421
- expect(allGroupSegmentsAreMatched(group.segments, { version: 5.6 }, datafileReader)).toEqual(
422
- true,
423
- );
424
452
  expect(
425
- allGroupSegmentsAreMatched(group.segments, { version: "5.7" }, datafileReader),
453
+ allGroupSegmentsAreMatched(group.segments, { version: "5.7" }, datafileReader, logger),
454
+ ).toEqual(true);
455
+ expect(
456
+ allGroupSegmentsAreMatched(group.segments, { version: 5.7 }, datafileReader, logger),
426
457
  ).toEqual(true);
427
- expect(allGroupSegmentsAreMatched(group.segments, { version: 5.7 }, datafileReader)).toEqual(
428
- true,
429
- );
430
458
 
431
459
  // not match
432
460
  expect(
433
- allGroupSegmentsAreMatched(group.segments, { version: "5.5" }, datafileReader),
461
+ allGroupSegmentsAreMatched(group.segments, { version: "5.5" }, datafileReader, logger),
462
+ ).toEqual(false);
463
+ expect(
464
+ allGroupSegmentsAreMatched(group.segments, { version: 5.5 }, datafileReader, logger),
434
465
  ).toEqual(false);
435
- expect(allGroupSegmentsAreMatched(group.segments, { version: 5.5 }, datafileReader)).toEqual(
436
- false,
437
- );
438
466
  });
439
467
  });
440
468
  });
package/src/segments.ts CHANGED
@@ -1,15 +1,17 @@
1
1
  import { Context, GroupSegment, Segment, Condition } from "@featurevisor/types";
2
2
  import { allConditionsAreMatched } from "./conditions";
3
3
  import { DatafileReader } from "./datafileReader";
4
+ import { Logger } from "./logger";
4
5
 
5
- export function segmentIsMatched(segment: Segment, context: Context): boolean {
6
- return allConditionsAreMatched(segment.conditions as Condition | Condition[], context);
6
+ export function segmentIsMatched(segment: Segment, context: Context, logger: Logger): boolean {
7
+ return allConditionsAreMatched(segment.conditions as Condition | Condition[], context, logger);
7
8
  }
8
9
 
9
10
  export function allGroupSegmentsAreMatched(
10
11
  groupSegments: GroupSegment | GroupSegment[] | "*",
11
12
  context: Context,
12
13
  datafileReader: DatafileReader,
14
+ logger: Logger,
13
15
  ): boolean {
14
16
  if (groupSegments === "*") {
15
17
  return true;
@@ -19,7 +21,7 @@ export function allGroupSegmentsAreMatched(
19
21
  const segment = datafileReader.getSegment(groupSegments);
20
22
 
21
23
  if (segment) {
22
- return segmentIsMatched(segment, context);
24
+ return segmentIsMatched(segment, context, logger);
23
25
  }
24
26
 
25
27
  return false;
@@ -28,27 +30,27 @@ export function allGroupSegmentsAreMatched(
28
30
  if (typeof groupSegments === "object") {
29
31
  if ("and" in groupSegments && Array.isArray(groupSegments.and)) {
30
32
  return groupSegments.and.every((groupSegment) =>
31
- allGroupSegmentsAreMatched(groupSegment, context, datafileReader),
33
+ allGroupSegmentsAreMatched(groupSegment, context, datafileReader, logger),
32
34
  );
33
35
  }
34
36
 
35
37
  if ("or" in groupSegments && Array.isArray(groupSegments.or)) {
36
38
  return groupSegments.or.some((groupSegment) =>
37
- allGroupSegmentsAreMatched(groupSegment, context, datafileReader),
39
+ allGroupSegmentsAreMatched(groupSegment, context, datafileReader, logger),
38
40
  );
39
41
  }
40
42
 
41
43
  if ("not" in groupSegments && Array.isArray(groupSegments.not)) {
42
44
  return groupSegments.not.every(
43
45
  (groupSegment) =>
44
- allGroupSegmentsAreMatched(groupSegment, context, datafileReader) === false,
46
+ allGroupSegmentsAreMatched(groupSegment, context, datafileReader, logger) === false,
45
47
  );
46
48
  }
47
49
  }
48
50
 
49
51
  if (Array.isArray(groupSegments)) {
50
52
  return groupSegments.every((groupSegment) =>
51
- allGroupSegmentsAreMatched(groupSegment, context, datafileReader),
53
+ allGroupSegmentsAreMatched(groupSegment, context, datafileReader, logger),
52
54
  );
53
55
  }
54
56