flagsmith-nodejs 3.3.2 → 4.0.0-beta.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 (190) hide show
  1. package/.github/workflows/publish.yml +2 -2
  2. package/.github/workflows/pull_request.yaml +3 -4
  3. package/build/{flagsmith-engine → cjs/flagsmith-engine}/environments/models.d.ts +3 -3
  4. package/build/{flagsmith-engine → cjs/flagsmith-engine}/environments/models.js +20 -13
  5. package/build/{flagsmith-engine → cjs/flagsmith-engine}/environments/util.d.ts +1 -1
  6. package/build/cjs/flagsmith-engine/environments/util.js +23 -0
  7. package/build/cjs/flagsmith-engine/features/models.js +118 -0
  8. package/build/{flagsmith-engine → cjs/flagsmith-engine}/features/util.d.ts +1 -1
  9. package/build/cjs/flagsmith-engine/features/util.js +27 -0
  10. package/build/{flagsmith-engine → cjs/flagsmith-engine}/identities/models.d.ts +2 -2
  11. package/build/cjs/flagsmith-engine/identities/models.js +48 -0
  12. package/build/{flagsmith-engine → cjs/flagsmith-engine}/identities/traits/models.js +5 -4
  13. package/build/{flagsmith-engine → cjs/flagsmith-engine}/identities/util.d.ts +2 -2
  14. package/build/cjs/flagsmith-engine/identities/util.js +22 -0
  15. package/build/cjs/flagsmith-engine/index.d.ts +14 -0
  16. package/build/cjs/flagsmith-engine/index.js +75 -0
  17. package/build/cjs/flagsmith-engine/organisations/models.js +21 -0
  18. package/build/{flagsmith-engine → cjs/flagsmith-engine}/organisations/util.d.ts +1 -1
  19. package/build/cjs/flagsmith-engine/organisations/util.js +8 -0
  20. package/build/{flagsmith-engine → cjs/flagsmith-engine}/projects/models.d.ts +2 -2
  21. package/build/{flagsmith-engine → cjs/flagsmith-engine}/projects/models.js +8 -5
  22. package/build/{flagsmith-engine → cjs/flagsmith-engine}/projects/util.d.ts +1 -1
  23. package/build/cjs/flagsmith-engine/projects/util.js +15 -0
  24. package/build/{flagsmith-engine → cjs/flagsmith-engine}/segments/evaluators.d.ts +4 -4
  25. package/build/cjs/flagsmith-engine/segments/evaluators.js +37 -0
  26. package/build/{flagsmith-engine → cjs/flagsmith-engine}/segments/models.d.ts +1 -1
  27. package/build/cjs/flagsmith-engine/segments/models.js +114 -0
  28. package/build/{flagsmith-engine → cjs/flagsmith-engine}/segments/util.d.ts +1 -1
  29. package/build/{flagsmith-engine → cjs/flagsmith-engine}/segments/util.js +9 -11
  30. package/build/{flagsmith-engine → cjs/flagsmith-engine}/utils/collections.d.ts +1 -1
  31. package/build/cjs/flagsmith-engine/utils/collections.js +6 -0
  32. package/build/cjs/flagsmith-engine/utils/errors.js +6 -0
  33. package/build/{flagsmith-engine → cjs/flagsmith-engine}/utils/hashing/index.js +8 -11
  34. package/build/{flagsmith-engine → cjs/flagsmith-engine}/utils/index.js +5 -5
  35. package/build/{index.d.ts → cjs/index.d.ts} +3 -3
  36. package/build/{index.js → cjs/index.js} +17 -17
  37. package/build/cjs/package.json +1 -0
  38. package/build/{sdk → cjs/sdk}/analytics.d.ts +3 -0
  39. package/build/cjs/sdk/analytics.js +73 -0
  40. package/build/cjs/sdk/errors.js +9 -0
  41. package/build/{sdk → cjs/sdk}/index.d.ts +19 -18
  42. package/build/cjs/sdk/index.js +400 -0
  43. package/build/{sdk → cjs/sdk}/models.d.ts +2 -2
  44. package/build/cjs/sdk/models.js +101 -0
  45. package/build/{sdk → cjs/sdk}/offline_handlers.d.ts +1 -1
  46. package/build/cjs/sdk/offline_handlers.js +46 -0
  47. package/build/{sdk → cjs/sdk}/polling_manager.d.ts +1 -1
  48. package/build/cjs/sdk/polling_manager.js +29 -0
  49. package/build/{sdk → cjs/sdk}/types.d.ts +15 -7
  50. package/build/cjs/sdk/utils.d.ts +36 -0
  51. package/build/cjs/sdk/utils.js +63 -0
  52. package/build/esm/flagsmith-engine/environments/models.d.ts +22 -0
  53. package/build/esm/flagsmith-engine/environments/models.js +32 -0
  54. package/build/esm/flagsmith-engine/environments/util.d.ts +3 -0
  55. package/build/esm/flagsmith-engine/environments/util.js +18 -0
  56. package/build/esm/flagsmith-engine/features/constants.d.ts +4 -0
  57. package/build/esm/flagsmith-engine/features/constants.js +4 -0
  58. package/build/esm/flagsmith-engine/features/models.d.ts +37 -0
  59. package/build/esm/flagsmith-engine/features/models.js +110 -0
  60. package/build/esm/flagsmith-engine/features/util.d.ts +4 -0
  61. package/build/esm/flagsmith-engine/features/util.js +21 -0
  62. package/build/esm/flagsmith-engine/identities/models.d.ts +15 -0
  63. package/build/esm/flagsmith-engine/identities/models.js +44 -0
  64. package/build/esm/flagsmith-engine/identities/traits/models.d.ts +5 -0
  65. package/build/esm/flagsmith-engine/identities/traits/models.js +8 -0
  66. package/build/esm/flagsmith-engine/identities/util.d.ts +4 -0
  67. package/build/esm/flagsmith-engine/identities/util.js +17 -0
  68. package/build/esm/flagsmith-engine/index.d.ts +14 -0
  69. package/build/esm/flagsmith-engine/index.js +62 -0
  70. package/build/esm/flagsmith-engine/organisations/models.d.ts +9 -0
  71. package/build/esm/flagsmith-engine/organisations/models.js +17 -0
  72. package/build/esm/flagsmith-engine/organisations/util.d.ts +2 -0
  73. package/build/esm/flagsmith-engine/organisations/util.js +4 -0
  74. package/build/esm/flagsmith-engine/projects/models.d.ts +10 -0
  75. package/build/esm/flagsmith-engine/projects/models.js +13 -0
  76. package/build/esm/flagsmith-engine/projects/util.d.ts +2 -0
  77. package/build/esm/flagsmith-engine/projects/util.js +11 -0
  78. package/build/esm/flagsmith-engine/segments/constants.d.ts +34 -0
  79. package/build/esm/flagsmith-engine/segments/constants.js +36 -0
  80. package/build/esm/flagsmith-engine/segments/evaluators.d.ts +7 -0
  81. package/build/esm/flagsmith-engine/segments/evaluators.js +31 -0
  82. package/build/esm/flagsmith-engine/segments/models.d.ts +37 -0
  83. package/build/esm/flagsmith-engine/segments/models.js +102 -0
  84. package/build/esm/flagsmith-engine/segments/util.d.ts +6 -0
  85. package/build/esm/flagsmith-engine/segments/util.js +23 -0
  86. package/build/esm/flagsmith-engine/utils/collections.d.ts +3 -0
  87. package/build/esm/flagsmith-engine/utils/collections.js +2 -0
  88. package/build/esm/flagsmith-engine/utils/errors.d.ts +2 -0
  89. package/build/esm/flagsmith-engine/utils/errors.js +2 -0
  90. package/build/esm/flagsmith-engine/utils/hashing/index.d.ts +9 -0
  91. package/build/esm/flagsmith-engine/utils/hashing/index.js +50 -0
  92. package/build/esm/flagsmith-engine/utils/index.d.ts +1 -0
  93. package/build/esm/flagsmith-engine/utils/index.js +13 -0
  94. package/build/esm/index.d.ts +3 -0
  95. package/build/esm/index.js +4 -0
  96. package/build/esm/sdk/analytics.d.ts +35 -0
  97. package/build/esm/sdk/analytics.js +69 -0
  98. package/build/esm/sdk/errors.d.ts +4 -0
  99. package/build/esm/sdk/errors.js +4 -0
  100. package/build/esm/sdk/index.d.ts +131 -0
  101. package/build/esm/sdk/index.js +390 -0
  102. package/build/esm/sdk/models.d.ts +55 -0
  103. package/build/esm/sdk/models.js +94 -0
  104. package/build/esm/sdk/offline_handlers.d.ts +9 -0
  105. package/build/esm/sdk/offline_handlers.js +18 -0
  106. package/build/esm/sdk/polling_manager.d.ts +9 -0
  107. package/build/esm/sdk/polling_manager.js +25 -0
  108. package/build/esm/sdk/types.d.ts +38 -0
  109. package/build/esm/sdk/types.js +1 -0
  110. package/build/esm/sdk/utils.d.ts +36 -0
  111. package/build/esm/sdk/utils.js +56 -0
  112. package/flagsmith-engine/environments/models.ts +3 -3
  113. package/flagsmith-engine/environments/util.ts +4 -4
  114. package/flagsmith-engine/features/models.ts +1 -1
  115. package/flagsmith-engine/features/util.ts +1 -1
  116. package/flagsmith-engine/identities/models.ts +3 -4
  117. package/flagsmith-engine/identities/traits/models.ts +0 -1
  118. package/flagsmith-engine/identities/util.ts +4 -4
  119. package/flagsmith-engine/index.ts +13 -13
  120. package/flagsmith-engine/organisations/util.ts +1 -1
  121. package/flagsmith-engine/projects/models.ts +2 -2
  122. package/flagsmith-engine/projects/util.ts +4 -4
  123. package/flagsmith-engine/segments/evaluators.ts +6 -6
  124. package/flagsmith-engine/segments/models.ts +4 -4
  125. package/flagsmith-engine/segments/util.ts +3 -3
  126. package/flagsmith-engine/utils/collections.ts +1 -1
  127. package/flagsmith-engine/utils/index.ts +1 -1
  128. package/index.ts +4 -4
  129. package/package.json +21 -9
  130. package/sdk/analytics.ts +7 -5
  131. package/sdk/index.ts +55 -46
  132. package/sdk/models.ts +2 -3
  133. package/sdk/offline_handlers.ts +2 -2
  134. package/sdk/polling_manager.ts +2 -3
  135. package/sdk/types.ts +35 -24
  136. package/sdk/utils.ts +49 -29
  137. package/tests/engine/e2e/engine.test.ts +5 -5
  138. package/tests/engine/unit/engine.test.ts +5 -5
  139. package/tests/engine/unit/segments/segment_evaluators.test.ts +9 -9
  140. package/tests/engine/unit/utils/utils.test.ts +1 -1
  141. package/tests/sdk/analytics.test.ts +8 -13
  142. package/tests/sdk/data/identity-with-transient-traits.json +41 -0
  143. package/tests/sdk/data/transient-identity.json +29 -0
  144. package/tests/sdk/flagsmith-cache.test.ts +16 -32
  145. package/tests/sdk/flagsmith-environment-flags.test.ts +21 -36
  146. package/tests/sdk/flagsmith-identity-flags.test.ts +83 -32
  147. package/tests/sdk/flagsmith.test.ts +67 -99
  148. package/tests/sdk/offline-handlers.test.ts +4 -5
  149. package/tests/sdk/polling.test.ts +6 -8
  150. package/tests/sdk/utils.ts +19 -15
  151. package/tsconfig.cjs.json +7 -0
  152. package/tsconfig.esm.json +7 -0
  153. package/tsconfig.json +7 -3
  154. package/vitest.config.ts +17 -0
  155. package/build/flagsmith-engine/environments/util.js +0 -27
  156. package/build/flagsmith-engine/features/models.js +0 -132
  157. package/build/flagsmith-engine/features/util.js +0 -27
  158. package/build/flagsmith-engine/identities/models.js +0 -113
  159. package/build/flagsmith-engine/identities/util.js +0 -46
  160. package/build/flagsmith-engine/index.d.ts +0 -14
  161. package/build/flagsmith-engine/index.js +0 -127
  162. package/build/flagsmith-engine/organisations/models.js +0 -21
  163. package/build/flagsmith-engine/organisations/util.js +0 -8
  164. package/build/flagsmith-engine/projects/util.js +0 -15
  165. package/build/flagsmith-engine/segments/evaluators.js +0 -45
  166. package/build/flagsmith-engine/segments/models.js +0 -147
  167. package/build/flagsmith-engine/utils/collections.js +0 -26
  168. package/build/flagsmith-engine/utils/errors.js +0 -26
  169. package/build/sdk/analytics.js +0 -120
  170. package/build/sdk/errors.js +0 -34
  171. package/build/sdk/index.js +0 -594
  172. package/build/sdk/models.js +0 -149
  173. package/build/sdk/offline_handlers.js +0 -66
  174. package/build/sdk/polling_manager.js +0 -72
  175. package/build/sdk/utils.d.ts +0 -12
  176. package/build/sdk/utils.js +0 -100
  177. package/jest.config.js +0 -5
  178. package/tests/index.js +0 -0
  179. /package/build/{flagsmith-engine → cjs/flagsmith-engine}/features/constants.d.ts +0 -0
  180. /package/build/{flagsmith-engine → cjs/flagsmith-engine}/features/constants.js +0 -0
  181. /package/build/{flagsmith-engine → cjs/flagsmith-engine}/features/models.d.ts +0 -0
  182. /package/build/{flagsmith-engine → cjs/flagsmith-engine}/identities/traits/models.d.ts +0 -0
  183. /package/build/{flagsmith-engine → cjs/flagsmith-engine}/organisations/models.d.ts +0 -0
  184. /package/build/{flagsmith-engine → cjs/flagsmith-engine}/segments/constants.d.ts +0 -0
  185. /package/build/{flagsmith-engine → cjs/flagsmith-engine}/segments/constants.js +0 -0
  186. /package/build/{flagsmith-engine → cjs/flagsmith-engine}/utils/errors.d.ts +0 -0
  187. /package/build/{flagsmith-engine → cjs/flagsmith-engine}/utils/hashing/index.d.ts +0 -0
  188. /package/build/{flagsmith-engine → cjs/flagsmith-engine}/utils/index.d.ts +0 -0
  189. /package/build/{sdk → cjs/sdk}/errors.d.ts +0 -0
  190. /package/build/{sdk → cjs/sdk}/types.js +0 -0
@@ -0,0 +1,36 @@
1
+ // Segment Rules
2
+ export const ALL_RULE = 'ALL';
3
+ export const ANY_RULE = 'ANY';
4
+ export const NONE_RULE = 'NONE';
5
+ export const RULE_TYPES = [ALL_RULE, ANY_RULE, NONE_RULE];
6
+ // Segment Condition Operators
7
+ export const EQUAL = 'EQUAL';
8
+ export const GREATER_THAN = 'GREATER_THAN';
9
+ export const LESS_THAN = 'LESS_THAN';
10
+ export const LESS_THAN_INCLUSIVE = 'LESS_THAN_INCLUSIVE';
11
+ export const CONTAINS = 'CONTAINS';
12
+ export const GREATER_THAN_INCLUSIVE = 'GREATER_THAN_INCLUSIVE';
13
+ export const NOT_CONTAINS = 'NOT_CONTAINS';
14
+ export const NOT_EQUAL = 'NOT_EQUAL';
15
+ export const REGEX = 'REGEX';
16
+ export const PERCENTAGE_SPLIT = 'PERCENTAGE_SPLIT';
17
+ export const IS_SET = 'IS_SET';
18
+ export const IS_NOT_SET = 'IS_NOT_SET';
19
+ export const MODULO = 'MODULO';
20
+ export const IN = 'IN';
21
+ export const CONDITION_OPERATORS = {
22
+ EQUAL,
23
+ GREATER_THAN,
24
+ LESS_THAN,
25
+ LESS_THAN_INCLUSIVE,
26
+ CONTAINS,
27
+ GREATER_THAN_INCLUSIVE,
28
+ NOT_CONTAINS,
29
+ NOT_EQUAL,
30
+ REGEX,
31
+ PERCENTAGE_SPLIT,
32
+ IS_SET,
33
+ IS_NOT_SET,
34
+ MODULO,
35
+ IN
36
+ };
@@ -0,0 +1,7 @@
1
+ import { EnvironmentModel } from '../environments/models.js';
2
+ import { IdentityModel } from '../identities/models.js';
3
+ import { TraitModel } from '../identities/traits/models.js';
4
+ import { SegmentConditionModel, SegmentModel } from './models.js';
5
+ export declare function getIdentitySegments(environment: EnvironmentModel, identity: IdentityModel, overrideTraits?: TraitModel[]): SegmentModel[];
6
+ export declare function evaluateIdentityInSegment(identity: IdentityModel, segment: SegmentModel, overrideTraits?: TraitModel[]): boolean;
7
+ export declare function traitsMatchSegmentCondition(identityTraits: TraitModel[], condition: SegmentConditionModel, segmentId: number | string, identityId: number | string): boolean;
@@ -0,0 +1,31 @@
1
+ import { getHashedPercentateForObjIds } from '../utils/hashing/index.js';
2
+ import { PERCENTAGE_SPLIT, IS_SET, IS_NOT_SET } from './constants.js';
3
+ export function getIdentitySegments(environment, identity, overrideTraits) {
4
+ return environment.project.segments.filter(segment => evaluateIdentityInSegment(identity, segment, overrideTraits));
5
+ }
6
+ export function evaluateIdentityInSegment(identity, segment, overrideTraits) {
7
+ return (segment.rules.length > 0 &&
8
+ segment.rules.filter(rule => traitsMatchSegmentRule(overrideTraits || identity.identityTraits, rule, segment.id, identity.djangoID || identity.compositeKey)).length === segment.rules.length);
9
+ }
10
+ function traitsMatchSegmentRule(identityTraits, rule, segmentId, identityId) {
11
+ const matchesConditions = rule.conditions.length > 0
12
+ ? rule.matchingFunction()(rule.conditions.map(condition => traitsMatchSegmentCondition(identityTraits, condition, segmentId, identityId)))
13
+ : true;
14
+ return (matchesConditions &&
15
+ rule.rules.filter(rule => traitsMatchSegmentRule(identityTraits, rule, segmentId, identityId)).length === rule.rules.length);
16
+ }
17
+ export function traitsMatchSegmentCondition(identityTraits, condition, segmentId, identityId) {
18
+ if (condition.operator == PERCENTAGE_SPLIT) {
19
+ var hashedPercentage = getHashedPercentateForObjIds([segmentId, identityId]);
20
+ return hashedPercentage <= parseFloat(String(condition.value));
21
+ }
22
+ const traits = identityTraits.filter(t => t.traitKey === condition.property_);
23
+ const trait = traits.length > 0 ? traits[0] : undefined;
24
+ if (condition.operator === IS_SET) {
25
+ return !!trait;
26
+ }
27
+ else if (condition.operator === IS_NOT_SET) {
28
+ return trait == undefined;
29
+ }
30
+ return trait ? condition.matchesTraitValue(trait.traitValue) : false;
31
+ }
@@ -0,0 +1,37 @@
1
+ import { FeatureStateModel } from '../features/models.js';
2
+ export declare const all: (iterable: Array<any>) => boolean;
3
+ export declare const any: (iterable: Array<any>) => boolean;
4
+ export declare const matchingFunctions: {
5
+ [x: string]: (thisValue: any, otherValue: any) => any;
6
+ };
7
+ export declare const semverMatchingFunction: {
8
+ [x: string]: ((thisValue: any, otherValue: any) => any) | ((thisValue: any, otherValue: any) => boolean);
9
+ };
10
+ export declare const getMatchingFunctions: (semver: boolean) => {
11
+ [x: string]: (thisValue: any, otherValue: any) => any;
12
+ };
13
+ export declare class SegmentConditionModel {
14
+ EXCEPTION_OPERATOR_METHODS: {
15
+ [key: string]: string;
16
+ };
17
+ operator: string;
18
+ value: string | null | undefined;
19
+ property_: string | null | undefined;
20
+ constructor(operator: string, value?: string | null | undefined, property?: string | null | undefined);
21
+ matchesTraitValue(traitValue: any): any;
22
+ }
23
+ export declare class SegmentRuleModel {
24
+ type: string;
25
+ rules: SegmentRuleModel[];
26
+ conditions: SegmentConditionModel[];
27
+ constructor(type: string);
28
+ static none(iterable: Array<any>): boolean;
29
+ matchingFunction(): CallableFunction;
30
+ }
31
+ export declare class SegmentModel {
32
+ id: number;
33
+ name: string;
34
+ rules: SegmentRuleModel[];
35
+ featureStates: FeatureStateModel[];
36
+ constructor(id: number, name: string);
37
+ }
@@ -0,0 +1,102 @@
1
+ import semver from 'semver';
2
+ import { getCastingFunction as getCastingFunction } from '../utils/index.js';
3
+ import { ALL_RULE, ANY_RULE, NONE_RULE, NOT_CONTAINS, REGEX, MODULO, IN, CONDITION_OPERATORS } from './constants.js';
4
+ import { isSemver } from './util.js';
5
+ export const all = (iterable) => iterable.filter(e => !!e).length === iterable.length;
6
+ export const any = (iterable) => iterable.filter(e => !!e).length > 0;
7
+ export const matchingFunctions = {
8
+ [CONDITION_OPERATORS.EQUAL]: (thisValue, otherValue) => thisValue == otherValue,
9
+ [CONDITION_OPERATORS.GREATER_THAN]: (thisValue, otherValue) => otherValue > thisValue,
10
+ [CONDITION_OPERATORS.GREATER_THAN_INCLUSIVE]: (thisValue, otherValue) => otherValue >= thisValue,
11
+ [CONDITION_OPERATORS.LESS_THAN]: (thisValue, otherValue) => thisValue > otherValue,
12
+ [CONDITION_OPERATORS.LESS_THAN_INCLUSIVE]: (thisValue, otherValue) => thisValue >= otherValue,
13
+ [CONDITION_OPERATORS.NOT_EQUAL]: (thisValue, otherValue) => thisValue != otherValue,
14
+ [CONDITION_OPERATORS.CONTAINS]: (thisValue, otherValue) => !!otherValue && otherValue.includes(thisValue),
15
+ };
16
+ export const semverMatchingFunction = {
17
+ ...matchingFunctions,
18
+ [CONDITION_OPERATORS.EQUAL]: (thisValue, otherValue) => semver.eq(thisValue, otherValue),
19
+ [CONDITION_OPERATORS.GREATER_THAN]: (thisValue, otherValue) => semver.gt(otherValue, thisValue),
20
+ [CONDITION_OPERATORS.GREATER_THAN_INCLUSIVE]: (thisValue, otherValue) => semver.gte(otherValue, thisValue),
21
+ [CONDITION_OPERATORS.LESS_THAN]: (thisValue, otherValue) => semver.gt(thisValue, otherValue),
22
+ [CONDITION_OPERATORS.LESS_THAN_INCLUSIVE]: (thisValue, otherValue) => semver.gte(thisValue, otherValue),
23
+ };
24
+ export const getMatchingFunctions = (semver) => (semver ? semverMatchingFunction : matchingFunctions);
25
+ export class SegmentConditionModel {
26
+ EXCEPTION_OPERATOR_METHODS = {
27
+ [NOT_CONTAINS]: 'evaluateNotContains',
28
+ [REGEX]: 'evaluateRegex',
29
+ [MODULO]: 'evaluateModulo',
30
+ [IN]: 'evaluateIn'
31
+ };
32
+ operator;
33
+ value;
34
+ property_;
35
+ constructor(operator, value, property) {
36
+ this.operator = operator;
37
+ this.value = value;
38
+ this.property_ = property;
39
+ }
40
+ matchesTraitValue(traitValue) {
41
+ const evaluators = {
42
+ evaluateNotContains: (traitValue) => {
43
+ return typeof traitValue == "string" &&
44
+ !!this.value &&
45
+ !traitValue.includes(this.value?.toString());
46
+ },
47
+ evaluateRegex: (traitValue) => {
48
+ return !!this.value && !!traitValue?.toString().match(new RegExp(this.value));
49
+ },
50
+ evaluateModulo: (traitValue) => {
51
+ if (isNaN(parseFloat(traitValue)) || !this.value) {
52
+ return false;
53
+ }
54
+ const parts = (this.value).split("|");
55
+ const [divisor, reminder] = [parseFloat(parts[0]), parseFloat(parts[1])];
56
+ return traitValue % divisor === reminder;
57
+ },
58
+ evaluateIn: (traitValue) => {
59
+ return this.value?.split(',').includes(traitValue.toString());
60
+ },
61
+ };
62
+ // TODO: move this logic to the evaluator module
63
+ if (this.EXCEPTION_OPERATOR_METHODS[this.operator]) {
64
+ const evaluatorFunction = evaluators[this.EXCEPTION_OPERATOR_METHODS[this.operator]];
65
+ return evaluatorFunction(traitValue);
66
+ }
67
+ const defaultFunction = (x, y) => false;
68
+ const matchingFunctionSet = getMatchingFunctions(isSemver(this.value));
69
+ const matchingFunction = matchingFunctionSet[this.operator] || defaultFunction;
70
+ const traitType = isSemver(this.value) ? 'semver' : typeof traitValue;
71
+ const castToTypeOfTraitValue = getCastingFunction(traitType);
72
+ return matchingFunction(castToTypeOfTraitValue(this.value), traitValue);
73
+ }
74
+ }
75
+ export class SegmentRuleModel {
76
+ type;
77
+ rules = [];
78
+ conditions = [];
79
+ constructor(type) {
80
+ this.type = type;
81
+ }
82
+ static none(iterable) {
83
+ return iterable.filter(e => !!e).length === 0;
84
+ }
85
+ matchingFunction() {
86
+ return {
87
+ [ANY_RULE]: any,
88
+ [ALL_RULE]: all,
89
+ [NONE_RULE]: SegmentRuleModel.none
90
+ }[this.type];
91
+ }
92
+ }
93
+ export class SegmentModel {
94
+ id;
95
+ name;
96
+ rules = [];
97
+ featureStates = [];
98
+ constructor(id, name) {
99
+ this.id = id;
100
+ this.name = name;
101
+ }
102
+ }
@@ -0,0 +1,6 @@
1
+ import { SegmentConditionModel, SegmentModel, SegmentRuleModel } from './models.js';
2
+ export declare function buildSegmentConditionModel(segmentConditionJSON: any): SegmentConditionModel;
3
+ export declare function buildSegmentRuleModel(ruleModelJSON: any): SegmentRuleModel;
4
+ export declare function buildSegmentModel(segmentModelJSON: any): SegmentModel;
5
+ export declare function isSemver(value: any): boolean;
6
+ export declare function removeSemverSuffix(value: string): string;
@@ -0,0 +1,23 @@
1
+ import { buildFeatureStateModel } from '../features/util.js';
2
+ import { SegmentConditionModel, SegmentModel, SegmentRuleModel } from './models.js';
3
+ export function buildSegmentConditionModel(segmentConditionJSON) {
4
+ return new SegmentConditionModel(segmentConditionJSON.operator, segmentConditionJSON.value, segmentConditionJSON.property_);
5
+ }
6
+ export function buildSegmentRuleModel(ruleModelJSON) {
7
+ const ruleModel = new SegmentRuleModel(ruleModelJSON.type);
8
+ ruleModel.rules = ruleModelJSON.rules.map((r) => buildSegmentRuleModel(r));
9
+ ruleModel.conditions = ruleModelJSON.conditions.map((c) => buildSegmentConditionModel(c));
10
+ return ruleModel;
11
+ }
12
+ export function buildSegmentModel(segmentModelJSON) {
13
+ const model = new SegmentModel(segmentModelJSON.id, segmentModelJSON.name);
14
+ model.featureStates = segmentModelJSON['feature_states'].map((fs) => buildFeatureStateModel(fs));
15
+ model.rules = segmentModelJSON['rules'].map((r) => buildSegmentRuleModel(r));
16
+ return model;
17
+ }
18
+ export function isSemver(value) {
19
+ return typeof value == 'string' && value.endsWith(':semver');
20
+ }
21
+ export function removeSemverSuffix(value) {
22
+ return value.replace(':semver', '');
23
+ }
@@ -0,0 +1,3 @@
1
+ import { FeatureStateModel } from '../features/models.js';
2
+ export declare class IdentityFeaturesList extends Array<FeatureStateModel> {
3
+ }
@@ -0,0 +1,2 @@
1
+ export class IdentityFeaturesList extends Array {
2
+ }
@@ -0,0 +1,2 @@
1
+ export declare class FeatureStateNotFound extends Error {
2
+ }
@@ -0,0 +1,2 @@
1
+ export class FeatureStateNotFound extends Error {
2
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Given a list of object ids, get a floating point number between 0 and 1 based on
3
+ * the hash of those ids. This should give the same value every time for any list of ids.
4
+ *
5
+ * @param {Array<any>} objectIds list of object ids to calculate the has for
6
+ * @param {} iterations=1 num times to include each id in the generated string to hash
7
+ * @returns number number between 0 (inclusive) and 100 (exclusive)
8
+ */
9
+ export declare function getHashedPercentateForObjIds(objectIds: Array<any>, iterations?: number): number;
@@ -0,0 +1,50 @@
1
+ import md5 from 'md5';
2
+ import bigInt from 'big-integer';
3
+ const makeRepeated = (arr, repeats) => Array.from({ length: repeats }, () => arr).flat();
4
+ // https://stackoverflow.com/questions/12532871/how-to-convert-a-very-large-hex-number-to-decimal-in-javascript
5
+ function h2d(s) {
6
+ function add(x, y) {
7
+ var c = 0, r = [];
8
+ var x = x.split('').map(Number);
9
+ var y = y.split('').map(Number);
10
+ while (x.length || y.length) {
11
+ var s = (x.pop() || 0) + (y.pop() || 0) + c;
12
+ r.unshift(s < 10 ? s : s - 10);
13
+ c = s < 10 ? 0 : 1;
14
+ }
15
+ if (c)
16
+ r.unshift(c);
17
+ return r.join('');
18
+ }
19
+ var dec = '0';
20
+ s.split('').forEach(function (chr) {
21
+ var n = parseInt(chr, 16);
22
+ for (var t = 8; t; t >>= 1) {
23
+ dec = add(dec, dec);
24
+ if (n & t)
25
+ dec = add(dec, '1');
26
+ }
27
+ });
28
+ return dec;
29
+ }
30
+ /**
31
+ * Given a list of object ids, get a floating point number between 0 and 1 based on
32
+ * the hash of those ids. This should give the same value every time for any list of ids.
33
+ *
34
+ * @param {Array<any>} objectIds list of object ids to calculate the has for
35
+ * @param {} iterations=1 num times to include each id in the generated string to hash
36
+ * @returns number number between 0 (inclusive) and 100 (exclusive)
37
+ */
38
+ export function getHashedPercentateForObjIds(objectIds, iterations = 1) {
39
+ let toHash = makeRepeated(objectIds, iterations).join(',');
40
+ const hashedValue = md5(toHash);
41
+ const hashedInt = bigInt(h2d(hashedValue));
42
+ const value = (hashedInt.mod(9999).toJSNumber() / 9998) * 100;
43
+ // we ignore this for it's nearly impossible use case to catch
44
+ /* istanbul ignore next */
45
+ if (value === 100) {
46
+ /* istanbul ignore next */
47
+ return getHashedPercentateForObjIds(objectIds, iterations + 1);
48
+ }
49
+ return value;
50
+ }
@@ -0,0 +1 @@
1
+ export declare function getCastingFunction(traitType: 'boolean' | 'string' | 'number' | 'semver' | any): CallableFunction;
@@ -0,0 +1,13 @@
1
+ import { removeSemverSuffix } from "../segments/util.js";
2
+ export function getCastingFunction(traitType) {
3
+ switch (traitType) {
4
+ case 'boolean':
5
+ return (x) => !['False', 'false'].includes(x);
6
+ case 'number':
7
+ return (x) => parseFloat(x);
8
+ case 'semver':
9
+ return (x) => removeSemverSuffix(x);
10
+ default:
11
+ return (x) => String(x);
12
+ }
13
+ }
@@ -0,0 +1,3 @@
1
+ export { AnalyticsProcessor, FlagsmithAPIError, FlagsmithClientError, EnvironmentDataPollingManager, FlagsmithCache, DefaultFlag, Flags, default } from './sdk/index.js';
2
+ export { FlagsmithConfig } from './sdk/types.js';
3
+ export { EnvironmentModel, FeatureStateModel, IdentityModel, TraitModel, SegmentModel, OrganisationModel } from './flagsmith-engine/index.js';
@@ -0,0 +1,4 @@
1
+ import Flagsmith from "./sdk/index.js";
2
+ export { AnalyticsProcessor, FlagsmithAPIError, FlagsmithClientError, EnvironmentDataPollingManager, DefaultFlag, Flags, default } from './sdk/index.js';
3
+ export { EnvironmentModel, FeatureStateModel, IdentityModel, TraitModel, SegmentModel, OrganisationModel } from './flagsmith-engine/index.js';
4
+ module.exports = Flagsmith;
@@ -0,0 +1,35 @@
1
+ import { Logger } from 'pino';
2
+ import { Fetch } from "./types.js";
3
+ export declare class AnalyticsProcessor {
4
+ private analyticsEndpoint;
5
+ private environmentKey;
6
+ private lastFlushed;
7
+ analyticsData: {
8
+ [key: string]: any;
9
+ };
10
+ private requestTimeoutMs;
11
+ private logger;
12
+ private currentFlush;
13
+ private customFetch;
14
+ /**
15
+ * AnalyticsProcessor is used to track how often individual Flags are evaluated within
16
+ * the Flagsmith SDK. Docs: https://docs.flagsmith.com/advanced-use/flag-analytics.
17
+ *
18
+ * @param data.environmentKey environment key obtained from the Flagsmith UI
19
+ * @param data.baseApiUrl base api url to override when using self hosted version
20
+ * @param data.requestTimeoutMs used to tell requests to stop waiting for a response after a
21
+ given number of milliseconds
22
+ */
23
+ constructor(data: {
24
+ environmentKey: string;
25
+ baseApiUrl: string;
26
+ requestTimeoutMs?: number;
27
+ logger?: Logger;
28
+ fetch?: Fetch;
29
+ });
30
+ /**
31
+ * Sends all the collected data to the api asynchronously and resets the timer
32
+ */
33
+ flush(): Promise<void>;
34
+ trackFeature(featureName: string): void;
35
+ }
@@ -0,0 +1,69 @@
1
+ import { pino } from 'pino';
2
+ const ANALYTICS_ENDPOINT = 'analytics/flags/';
3
+ // Used to control how often we send data(in seconds)
4
+ const ANALYTICS_TIMER = 10;
5
+ export class AnalyticsProcessor {
6
+ analyticsEndpoint;
7
+ environmentKey;
8
+ lastFlushed;
9
+ analyticsData;
10
+ requestTimeoutMs = 3000;
11
+ logger;
12
+ currentFlush;
13
+ customFetch;
14
+ /**
15
+ * AnalyticsProcessor is used to track how often individual Flags are evaluated within
16
+ * the Flagsmith SDK. Docs: https://docs.flagsmith.com/advanced-use/flag-analytics.
17
+ *
18
+ * @param data.environmentKey environment key obtained from the Flagsmith UI
19
+ * @param data.baseApiUrl base api url to override when using self hosted version
20
+ * @param data.requestTimeoutMs used to tell requests to stop waiting for a response after a
21
+ given number of milliseconds
22
+ */
23
+ constructor(data) {
24
+ this.analyticsEndpoint = data.baseApiUrl + ANALYTICS_ENDPOINT;
25
+ this.environmentKey = data.environmentKey;
26
+ this.lastFlushed = Date.now();
27
+ this.analyticsData = {};
28
+ this.requestTimeoutMs = data.requestTimeoutMs || this.requestTimeoutMs;
29
+ this.logger = data.logger || pino();
30
+ this.customFetch = data.fetch ?? fetch;
31
+ }
32
+ /**
33
+ * Sends all the collected data to the api asynchronously and resets the timer
34
+ */
35
+ async flush() {
36
+ if (this.currentFlush || !Object.keys(this.analyticsData).length) {
37
+ return;
38
+ }
39
+ try {
40
+ this.currentFlush = this.customFetch(this.analyticsEndpoint, {
41
+ method: 'POST',
42
+ body: JSON.stringify(this.analyticsData),
43
+ signal: AbortSignal.timeout(this.requestTimeoutMs),
44
+ headers: {
45
+ 'Content-Type': 'application/json',
46
+ 'X-Environment-Key': this.environmentKey
47
+ }
48
+ });
49
+ await this.currentFlush;
50
+ }
51
+ catch (error) {
52
+ // We don't want failing to write analytics to cause any exceptions in the main
53
+ // thread so we just swallow them here.
54
+ this.logger.warn('Failed to post analytics to Flagsmith API. Not clearing data, will retry.');
55
+ return;
56
+ }
57
+ finally {
58
+ this.currentFlush = undefined;
59
+ }
60
+ this.analyticsData = {};
61
+ this.lastFlushed = Date.now();
62
+ }
63
+ trackFeature(featureName) {
64
+ this.analyticsData[featureName] = (this.analyticsData[featureName] || 0) + 1;
65
+ if (Date.now() - this.lastFlushed > ANALYTICS_TIMER * 1000) {
66
+ this.flush();
67
+ }
68
+ }
69
+ }
@@ -0,0 +1,4 @@
1
+ export declare class FlagsmithClientError extends Error {
2
+ }
3
+ export declare class FlagsmithAPIError extends Error {
4
+ }
@@ -0,0 +1,4 @@
1
+ export class FlagsmithClientError extends Error {
2
+ }
3
+ export class FlagsmithAPIError extends Error {
4
+ }
@@ -0,0 +1,131 @@
1
+ import { Dispatcher } from 'undici-types';
2
+ import { EnvironmentModel } from '../flagsmith-engine/index.js';
3
+ import { IdentityModel } from '../flagsmith-engine/index.js';
4
+ import { BaseOfflineHandler } from './offline_handlers.js';
5
+ import { DefaultFlag, Flags } from './models.js';
6
+ import { EnvironmentDataPollingManager } from './polling_manager.js';
7
+ import { SegmentModel } from '../flagsmith-engine/index.js';
8
+ import { FlagsmithConfig, FlagsmithTraitValue, ITraitConfig } from './types.js';
9
+ export { AnalyticsProcessor } from './analytics.js';
10
+ export { FlagsmithAPIError, FlagsmithClientError } from './errors.js';
11
+ export { DefaultFlag, Flags } from './models.js';
12
+ export { EnvironmentDataPollingManager } from './polling_manager.js';
13
+ export { FlagsmithCache, FlagsmithConfig } from './types.js';
14
+ export declare class Flagsmith {
15
+ environmentKey?: string;
16
+ apiUrl?: string;
17
+ customHeaders?: {
18
+ [key: string]: any;
19
+ };
20
+ agent?: Dispatcher;
21
+ requestTimeoutMs?: number;
22
+ enableLocalEvaluation?: boolean;
23
+ environmentRefreshIntervalSeconds: number;
24
+ retries?: number;
25
+ enableAnalytics: boolean;
26
+ defaultFlagHandler?: (featureName: string) => DefaultFlag;
27
+ environmentFlagsUrl?: string;
28
+ identitiesUrl?: string;
29
+ environmentUrl?: string;
30
+ environmentDataPollingManager?: EnvironmentDataPollingManager;
31
+ environment: EnvironmentModel;
32
+ offlineMode: boolean;
33
+ offlineHandler?: BaseOfflineHandler;
34
+ identitiesWithOverridesByIdentifier?: Map<string, IdentityModel>;
35
+ private cache?;
36
+ private onEnvironmentChange?;
37
+ private analyticsProcessor?;
38
+ private logger;
39
+ private customFetch;
40
+ /**
41
+ * A Flagsmith client.
42
+ *
43
+ * Provides an interface for interacting with the Flagsmith http API.
44
+ * Basic Usage::
45
+ *
46
+ * import flagsmith from Flagsmith
47
+ * const flagsmith = new Flagsmith({environmentKey: '<your API key>'});
48
+ * const environmentFlags = flagsmith.getEnvironmentFlags();
49
+ * const featureEnabled = environmentFlags.isFeatureEnabled('foo');
50
+ * const identityFlags = flagsmith.getIdentityFlags('identifier', {'foo': 'bar'});
51
+ * const featureEnabledForIdentity = identityFlags.isFeatureEnabled("foo")
52
+ *
53
+ * @param {string} data.environmentKey: The environment key obtained from Flagsmith interface
54
+ * Required unless offlineMode is True.
55
+ @param {string} data.apiUrl: Override the URL of the Flagsmith API to communicate with
56
+ @param data.customHeaders: Additional headers to add to requests made to the
57
+ Flagsmith API
58
+ @param {number} data.requestTimeoutSeconds: Number of seconds to wait for a request to
59
+ complete before terminating the request
60
+ @param {boolean} data.enableLocalEvaluation: Enables local evaluation of flags
61
+ @param {number} data.environmentRefreshIntervalSeconds: If using local evaluation,
62
+ specify the interval period between refreshes of local environment data
63
+ @param {number} data.retries: a urllib3.Retry object to use on all http requests to the
64
+ Flagsmith API
65
+ @param {boolean} data.enableAnalytics: if enabled, sends additional requests to the Flagsmith
66
+ API to power flag analytics charts
67
+ @param data.defaultFlagHandler: callable which will be used in the case where
68
+ flags cannot be retrieved from the API or a non-existent feature is
69
+ requested
70
+ @param data.logger: an instance of the pino Logger class to use for logging
71
+ @param {boolean} data.offlineMode: sets the client into offline mode. Relies on offlineHandler for
72
+ evaluating flags.
73
+ @param {BaseOfflineHandler} data.offlineHandler: provide a handler for offline logic. Used to get environment
74
+ document from another source when in offlineMode. Works in place of
75
+ defaultFlagHandler if offlineMode is not set and using remote evaluation.
76
+ */
77
+ constructor(data?: FlagsmithConfig);
78
+ /**
79
+ * Get all the default for flags for the current environment.
80
+ *
81
+ * @returns Flags object holding all the flags for the current environment.
82
+ */
83
+ getEnvironmentFlags(): Promise<Flags>;
84
+ /**
85
+ * Get all the flags for the current environment for a given identity. Will also
86
+ upsert all traits to the Flagsmith API for future evaluations. Providing a
87
+ trait with a value of None will remove the trait from the identity if it exists.
88
+ *
89
+ * @param {string} identifier a unique identifier for the identity in the current
90
+ environment, e.g. email address, username, uuid
91
+ * @param {{[key:string]:any | ITraitConfig}} traits? a dictionary of traits to add / update on the identity in
92
+ Flagsmith, e.g. {"num_orders": 10} or {age: {value: 30, transient: true}}
93
+ * @returns Flags object holding all the flags for the given identity.
94
+ */
95
+ getIdentityFlags(identifier: string, traits?: {
96
+ [key: string]: FlagsmithTraitValue | ITraitConfig;
97
+ }, transient?: boolean): Promise<Flags>;
98
+ /**
99
+ * Get the segments for the current environment for a given identity. Will also
100
+ upsert all traits to the Flagsmith API for future evaluations. Providing a
101
+ trait with a value of None will remove the trait from the identity if it exists.
102
+ *
103
+ * @param {string} identifier a unique identifier for the identity in the current
104
+ environment, e.g. email address, username, uuid
105
+ * @param {{[key:string]:any}} traits? a dictionary of traits to add / update on the identity in
106
+ Flagsmith, e.g. {"num_orders": 10}
107
+ * @returns Segments that the given identity belongs to.
108
+ */
109
+ getIdentitySegments(identifier: string, traits?: {
110
+ [key: string]: any;
111
+ }): Promise<SegmentModel[]>;
112
+ /**
113
+ * Updates the environment state for local flag evaluation.
114
+ * Sets a local promise to prevent race conditions in getIdentityFlags / getIdentitySegments.
115
+ * You only need to call this if you wish to bypass environmentRefreshIntervalSeconds.
116
+ */
117
+ updateEnvironment(): Promise<void>;
118
+ close(): Promise<void>;
119
+ private getJSONResponse;
120
+ /**
121
+ * This promise ensures that the environment is retrieved before attempting to locally evaluate.
122
+ */
123
+ private environmentPromise;
124
+ private getEnvironmentFromApi;
125
+ private getEnvironmentFlagsFromDocument;
126
+ private getIdentityFlagsFromDocument;
127
+ private getEnvironmentFlagsFromApi;
128
+ private getIdentityFlagsFromApi;
129
+ private getIdentityModel;
130
+ }
131
+ export default Flagsmith;