flagsmith-nodejs 6.2.0 → 7.0.2

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 (100) hide show
  1. package/.github/workflows/publish.yml +8 -5
  2. package/.github/workflows/pull_request.yaml +3 -0
  3. package/.gitmodules +1 -1
  4. package/.husky/pre-commit +1 -0
  5. package/.nvmrc +1 -0
  6. package/.release-please-manifest.json +1 -1
  7. package/CHANGELOG.md +40 -0
  8. package/build/cjs/flagsmith-engine/environments/models.d.ts +2 -1
  9. package/build/cjs/flagsmith-engine/environments/models.js +3 -1
  10. package/build/cjs/flagsmith-engine/environments/util.js +1 -1
  11. package/build/cjs/flagsmith-engine/evaluation/evaluationContext/evaluationContext.types.d.ts +230 -0
  12. package/build/cjs/flagsmith-engine/evaluation/evaluationContext/evaluationContext.types.js +8 -0
  13. package/build/cjs/flagsmith-engine/evaluation/evaluationContext/mappers.d.ts +5 -0
  14. package/build/cjs/flagsmith-engine/evaluation/evaluationContext/mappers.js +156 -0
  15. package/build/cjs/flagsmith-engine/evaluation/evaluationContext/types.d.ts +216 -0
  16. package/build/cjs/flagsmith-engine/evaluation/evaluationContext/types.js +8 -0
  17. package/build/cjs/flagsmith-engine/evaluation/evaluationResult/evaluationResult.types.d.ts +68 -0
  18. package/build/cjs/flagsmith-engine/evaluation/evaluationResult/evaluationResult.types.js +8 -0
  19. package/build/cjs/flagsmith-engine/evaluation/models.d.ts +50 -0
  20. package/build/cjs/flagsmith-engine/evaluation/models.js +26 -0
  21. package/build/cjs/flagsmith-engine/features/models.js +1 -1
  22. package/build/cjs/flagsmith-engine/features/types.d.ts +5 -0
  23. package/build/cjs/flagsmith-engine/features/types.js +9 -0
  24. package/build/cjs/flagsmith-engine/features/util.d.ts +1 -0
  25. package/build/cjs/flagsmith-engine/features/util.js +5 -1
  26. package/build/cjs/flagsmith-engine/index.d.ts +61 -9
  27. package/build/cjs/flagsmith-engine/index.js +176 -56
  28. package/build/cjs/flagsmith-engine/segments/constants.d.ts +1 -0
  29. package/build/cjs/flagsmith-engine/segments/constants.js +2 -1
  30. package/build/cjs/flagsmith-engine/segments/evaluators.d.ts +41 -7
  31. package/build/cjs/flagsmith-engine/segments/evaluators.js +136 -24
  32. package/build/cjs/flagsmith-engine/segments/models.d.ts +9 -4
  33. package/build/cjs/flagsmith-engine/segments/models.js +115 -13
  34. package/build/cjs/flagsmith-engine/utils/hashing/index.d.ts +1 -1
  35. package/build/cjs/flagsmith-engine/utils/hashing/index.js +4 -4
  36. package/build/cjs/sdk/index.d.ts +1 -3
  37. package/build/cjs/sdk/index.js +22 -19
  38. package/build/cjs/sdk/models.d.ts +8 -1
  39. package/build/cjs/sdk/models.js +29 -1
  40. package/build/esm/flagsmith-engine/environments/models.d.ts +2 -1
  41. package/build/esm/flagsmith-engine/environments/models.js +3 -1
  42. package/build/esm/flagsmith-engine/environments/util.js +1 -1
  43. package/build/esm/flagsmith-engine/evaluation/evaluationContext/evaluationContext.types.d.ts +230 -0
  44. package/build/esm/flagsmith-engine/evaluation/evaluationContext/evaluationContext.types.js +7 -0
  45. package/build/esm/flagsmith-engine/evaluation/evaluationContext/mappers.d.ts +5 -0
  46. package/build/esm/flagsmith-engine/evaluation/evaluationContext/mappers.js +152 -0
  47. package/build/esm/flagsmith-engine/evaluation/evaluationContext/types.d.ts +216 -0
  48. package/build/esm/flagsmith-engine/evaluation/evaluationContext/types.js +7 -0
  49. package/build/esm/flagsmith-engine/evaluation/evaluationResult/evaluationResult.types.d.ts +68 -0
  50. package/build/esm/flagsmith-engine/evaluation/evaluationResult/evaluationResult.types.js +7 -0
  51. package/build/esm/flagsmith-engine/evaluation/models.d.ts +50 -0
  52. package/build/esm/flagsmith-engine/evaluation/models.js +9 -0
  53. package/build/esm/flagsmith-engine/features/models.js +2 -2
  54. package/build/esm/flagsmith-engine/features/types.d.ts +5 -0
  55. package/build/esm/flagsmith-engine/features/types.js +6 -0
  56. package/build/esm/flagsmith-engine/features/util.d.ts +1 -0
  57. package/build/esm/flagsmith-engine/features/util.js +3 -0
  58. package/build/esm/flagsmith-engine/index.d.ts +61 -9
  59. package/build/esm/flagsmith-engine/index.js +161 -43
  60. package/build/esm/flagsmith-engine/segments/constants.d.ts +1 -0
  61. package/build/esm/flagsmith-engine/segments/constants.js +1 -0
  62. package/build/esm/flagsmith-engine/segments/evaluators.d.ts +41 -7
  63. package/build/esm/flagsmith-engine/segments/evaluators.js +137 -25
  64. package/build/esm/flagsmith-engine/segments/models.d.ts +9 -4
  65. package/build/esm/flagsmith-engine/segments/models.js +115 -13
  66. package/build/esm/flagsmith-engine/utils/hashing/index.d.ts +1 -1
  67. package/build/esm/flagsmith-engine/utils/hashing/index.js +2 -2
  68. package/build/esm/sdk/index.d.ts +1 -3
  69. package/build/esm/sdk/index.js +21 -18
  70. package/build/esm/sdk/models.d.ts +8 -1
  71. package/build/esm/sdk/models.js +29 -1
  72. package/flagsmith-engine/environments/models.ts +3 -1
  73. package/flagsmith-engine/environments/util.ts +2 -1
  74. package/flagsmith-engine/evaluation/evaluationContext/evaluationContext.types.ts +247 -0
  75. package/flagsmith-engine/evaluation/evaluationContext/mappers.ts +204 -0
  76. package/flagsmith-engine/evaluation/evaluationContext/types.ts +233 -0
  77. package/flagsmith-engine/evaluation/evaluationResult/evaluationResult.types.ts +71 -0
  78. package/flagsmith-engine/evaluation/models.ts +96 -0
  79. package/flagsmith-engine/features/models.ts +3 -2
  80. package/flagsmith-engine/features/types.ts +5 -0
  81. package/flagsmith-engine/features/util.ts +4 -0
  82. package/flagsmith-engine/index.ts +229 -72
  83. package/flagsmith-engine/segments/constants.ts +1 -0
  84. package/flagsmith-engine/segments/evaluators.ts +178 -62
  85. package/flagsmith-engine/segments/models.ts +171 -23
  86. package/flagsmith-engine/utils/hashing/index.ts +2 -2
  87. package/package.json +13 -2
  88. package/sdk/index.ts +36 -23
  89. package/sdk/models.ts +44 -2
  90. package/tests/engine/e2e/engine.test.ts +43 -38
  91. package/tests/engine/unit/engine.test.ts +306 -59
  92. package/tests/engine/unit/mappers.test.ts +353 -0
  93. package/tests/engine/unit/segments/segment_evaluators.test.ts +391 -49
  94. package/tests/engine/unit/segments/segments_model.test.ts +85 -0
  95. package/tests/engine/unit/utils/utils.test.ts +7 -7
  96. package/tests/engine/unit/utils.ts +1 -1
  97. package/tests/sdk/data/environment.json +1 -0
  98. package/tests/sdk/flagsmith.test.ts +29 -3
  99. package/tests/sdk/offline-handlers.test.ts +3 -1
  100. package/vitest.config.esm.ts +34 -0
@@ -1,76 +1,196 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getEnvironmentFeatureStates = exports.getEnvironmentFeatureState = exports.getIdentityFeatureStates = exports.getIdentityFeatureState = exports.OrganisationModel = exports.SegmentModel = exports.TraitModel = exports.IdentityModel = exports.FeatureStateModel = exports.FeatureModel = exports.EnvironmentModel = void 0;
3
+ exports.isHigherPriority = exports.shouldApplyOverride = exports.evaluateFeatures = exports.processSegmentOverrides = exports.evaluateSegments = exports.getEvaluationResult = exports.OrganisationModel = exports.FeatureStateModel = exports.FeatureModel = exports.SegmentModel = exports.TraitModel = exports.IdentityModel = exports.EnvironmentModel = void 0;
4
4
  const evaluators_js_1 = require("./segments/evaluators.js");
5
- const errors_js_1 = require("./utils/errors.js");
5
+ const types_js_1 = require("./features/types.js");
6
+ const index_js_1 = require("./utils/hashing/index.js");
6
7
  var models_js_1 = require("./environments/models.js");
7
8
  Object.defineProperty(exports, "EnvironmentModel", { enumerable: true, get: function () { return models_js_1.EnvironmentModel; } });
8
- var models_js_2 = require("./features/models.js");
9
- Object.defineProperty(exports, "FeatureModel", { enumerable: true, get: function () { return models_js_2.FeatureModel; } });
10
- Object.defineProperty(exports, "FeatureStateModel", { enumerable: true, get: function () { return models_js_2.FeatureStateModel; } });
11
- var models_js_3 = require("./identities/models.js");
12
- Object.defineProperty(exports, "IdentityModel", { enumerable: true, get: function () { return models_js_3.IdentityModel; } });
13
- var models_js_4 = require("./identities/traits/models.js");
14
- Object.defineProperty(exports, "TraitModel", { enumerable: true, get: function () { return models_js_4.TraitModel; } });
15
- var models_js_5 = require("./segments/models.js");
16
- Object.defineProperty(exports, "SegmentModel", { enumerable: true, get: function () { return models_js_5.SegmentModel; } });
9
+ var models_js_2 = require("./identities/models.js");
10
+ Object.defineProperty(exports, "IdentityModel", { enumerable: true, get: function () { return models_js_2.IdentityModel; } });
11
+ var models_js_3 = require("./identities/traits/models.js");
12
+ Object.defineProperty(exports, "TraitModel", { enumerable: true, get: function () { return models_js_3.TraitModel; } });
13
+ var models_js_4 = require("./segments/models.js");
14
+ Object.defineProperty(exports, "SegmentModel", { enumerable: true, get: function () { return models_js_4.SegmentModel; } });
15
+ var models_js_5 = require("./features/models.js");
16
+ Object.defineProperty(exports, "FeatureModel", { enumerable: true, get: function () { return models_js_5.FeatureModel; } });
17
+ Object.defineProperty(exports, "FeatureStateModel", { enumerable: true, get: function () { return models_js_5.FeatureStateModel; } });
17
18
  var models_js_6 = require("./organisations/models.js");
18
19
  Object.defineProperty(exports, "OrganisationModel", { enumerable: true, get: function () { return models_js_6.OrganisationModel; } });
19
- function getIdentityFeatureStatesDict(environment, identity, overrideTraits) {
20
- // Get feature states from the environment
21
- const featureStates = {};
22
- for (const fs of environment.featureStates) {
23
- featureStates[fs.feature.id] = fs;
20
+ /**
21
+ * Evaluates flags and segments for the given context.
22
+ *
23
+ * This is the main entry point for the evaluation engine. It processes segments,
24
+ * applies feature overrides based on segment priority, and returns the final flag states with
25
+ * evaluation reasons.
26
+ *
27
+ * @param context - EvaluationContext containing environment, identity, and segment data
28
+ * @returns EvaluationResult with flags, segments, and original context
29
+ */
30
+ function getEvaluationResult(context) {
31
+ const enrichedContext = getEnrichedContext(context);
32
+ const { segments, segmentOverrides } = evaluateSegments(enrichedContext);
33
+ const flags = evaluateFeatures(enrichedContext, segmentOverrides);
34
+ return { flags, segments };
35
+ }
36
+ exports.getEvaluationResult = getEvaluationResult;
37
+ function getEnrichedContext(context) {
38
+ const identityKey = getIdentityKey(context);
39
+ if (!identityKey)
40
+ return context;
41
+ return {
42
+ ...context,
43
+ ...(context.identity && {
44
+ identity: {
45
+ identifier: context.identity.identifier,
46
+ key: identityKey,
47
+ traits: context.identity.traits || {}
48
+ }
49
+ })
50
+ };
51
+ }
52
+ /**
53
+ * Evaluates which segments the identity belongs to and collects feature overrides.
54
+ *
55
+ * @param context - EvaluationContext containing identity and segment definitions
56
+ * @returns Object containing segments the identity belongs to and any feature overrides
57
+ */
58
+ function evaluateSegments(context) {
59
+ if (!context.identity || !context.segments) {
60
+ return {
61
+ segments: [],
62
+ segmentOverrides: {}
63
+ };
24
64
  }
25
- // Override with any feature states defined by matching segments
26
- const identitySegments = (0, evaluators_js_1.getIdentitySegments)(environment, identity, overrideTraits);
27
- for (const matchingSegment of identitySegments) {
28
- for (const featureState of matchingSegment.featureStates) {
29
- if (featureStates[featureState.feature.id]) {
30
- if (featureStates[featureState.feature.id].isHigherSegmentPriority(featureState)) {
31
- continue;
65
+ const identitySegments = (0, evaluators_js_1.getIdentitySegments)(context);
66
+ const segments = identitySegments.map(segment => ({
67
+ name: segment.name,
68
+ ...(segment.metadata
69
+ ? {
70
+ metadata: {
71
+ ...segment.metadata
32
72
  }
33
73
  }
34
- featureStates[featureState.feature.id] = featureState;
35
- }
36
- }
37
- // Override with any feature states defined directly the identity
38
- for (const fs of identity.identityFeatures) {
39
- if (featureStates[fs.feature.id]) {
40
- featureStates[fs.feature.id] = fs;
74
+ : {})
75
+ }));
76
+ const segmentOverrides = processSegmentOverrides(identitySegments);
77
+ return { segments, segmentOverrides };
78
+ }
79
+ exports.evaluateSegments = evaluateSegments;
80
+ /**
81
+ * Processes feature overrides from segments, applying priority rules.
82
+ *
83
+ * When multiple segments override the same feature, the segment with
84
+ * higher priority (lower numeric value) takes precedence.
85
+ *
86
+ * @param identitySegments - Segments that the identity belongs to
87
+ * @returns Map of feature keys to their highest-priority segment overrides
88
+ */
89
+ function processSegmentOverrides(identitySegments) {
90
+ const segmentOverrides = {};
91
+ for (const segment of identitySegments) {
92
+ if (!segment.overrides)
93
+ continue;
94
+ const overridesList = Array.isArray(segment.overrides) ? segment.overrides : [];
95
+ for (const override of overridesList) {
96
+ if (shouldApplyOverride(override, segmentOverrides)) {
97
+ segmentOverrides[override.name] = {
98
+ feature: override,
99
+ segmentName: segment.name
100
+ };
101
+ }
41
102
  }
42
103
  }
43
- return featureStates;
104
+ return segmentOverrides;
44
105
  }
45
- function getIdentityFeatureState(environment, identity, featureName, overrideTraits) {
46
- const featureStates = getIdentityFeatureStatesDict(environment, identity, overrideTraits);
47
- const matchingFeature = Object.values(featureStates).filter(f => f.feature.name === featureName);
48
- if (matchingFeature.length === 0) {
49
- throw new errors_js_1.FeatureStateNotFound('Feature State Not Found');
106
+ exports.processSegmentOverrides = processSegmentOverrides;
107
+ /**
108
+ * Evaluates all features in the context, applying segment overrides where applicable.
109
+ * For each feature:
110
+ * - Checks if a segment override exists
111
+ * - Uses override values if present, otherwise evaluates the base feature
112
+ * - Determines appropriate evaluation reason
113
+ * - Handles multivariate evaluation for features without overrides
114
+ *
115
+ * @param context - EvaluationContext containing features and identity
116
+ * @param segmentOverrides - Map of feature keys to their segment overrides
117
+ * @returns EvaluationResultFlags containing evaluated flag results
118
+ */
119
+ function evaluateFeatures(context, segmentOverrides) {
120
+ const flags = {};
121
+ for (const feature of Object.values(context.features || {})) {
122
+ const segmentOverride = segmentOverrides[feature.name];
123
+ const finalFeature = segmentOverride ? segmentOverride.feature : feature;
124
+ const { value: evaluatedValue, reason: evaluatedReason } = evaluateFeatureValue(finalFeature, getIdentityKey(context));
125
+ flags[finalFeature.name] = {
126
+ name: finalFeature.name,
127
+ enabled: finalFeature.enabled,
128
+ value: evaluatedValue,
129
+ ...(finalFeature.metadata ? { metadata: finalFeature.metadata } : {}),
130
+ reason: evaluatedReason ??
131
+ getTargetingMatchReason({ type: 'SEGMENT', override: segmentOverride })
132
+ };
50
133
  }
51
- return matchingFeature[0];
134
+ return flags;
52
135
  }
53
- exports.getIdentityFeatureState = getIdentityFeatureState;
54
- function getIdentityFeatureStates(environment, identity, overrideTraits) {
55
- const featureStates = Object.values(getIdentityFeatureStatesDict(environment, identity, overrideTraits));
56
- if (environment.project.hideDisabledFlags) {
57
- return featureStates.filter(fs => !!fs.enabled);
136
+ exports.evaluateFeatures = evaluateFeatures;
137
+ function evaluateFeatureValue(feature, identityKey) {
138
+ if (!!feature.variants && feature.variants.length > 0 && !!identityKey) {
139
+ return getMultivariateFeatureValue(feature, identityKey);
58
140
  }
59
- return featureStates;
141
+ return { value: feature.value, reason: undefined };
60
142
  }
61
- exports.getIdentityFeatureStates = getIdentityFeatureStates;
62
- function getEnvironmentFeatureState(environment, featureName) {
63
- const featuresStates = environment.featureStates.filter(f => f.feature.name === featureName);
64
- if (featuresStates.length === 0) {
65
- throw new errors_js_1.FeatureStateNotFound('Feature State Not Found');
143
+ /**
144
+ * Evaluates a multivariate feature flag to determine which variant value to return for a given identity.
145
+ *
146
+ * Uses deterministic hashing to ensure the same identity always receives the same variant,
147
+ * while distributing variants according to their configured weight percentages.
148
+ *
149
+ * @param feature - The feature context containing variants and their weights
150
+ * @param identityKey - The identity key used for deterministic variant selection
151
+ * @returns The variant value if the identity falls within a variant's range, otherwise the default feature value
152
+ */
153
+ function getMultivariateFeatureValue(feature, identityKey) {
154
+ const percentageValue = (0, index_js_1.getHashedPercentageForObjIds)([feature.key, identityKey]);
155
+ const sortedVariants = [...(feature?.variants || [])].sort((a, b) => {
156
+ return (a.priority ?? Infinity) - (b.priority ?? Infinity);
157
+ });
158
+ let startPercentage = 0;
159
+ for (const variant of sortedVariants) {
160
+ const limit = startPercentage + variant.weight;
161
+ if (startPercentage <= percentageValue && percentageValue < limit) {
162
+ return {
163
+ value: variant.value,
164
+ reason: getTargetingMatchReason({ type: 'SPLIT', weight: variant.weight })
165
+ };
166
+ }
167
+ startPercentage = limit;
66
168
  }
67
- return featuresStates[0];
169
+ return { value: feature.value, reason: undefined };
68
170
  }
69
- exports.getEnvironmentFeatureState = getEnvironmentFeatureState;
70
- function getEnvironmentFeatureStates(environment) {
71
- if (environment.project.hideDisabledFlags) {
72
- return environment.featureStates.filter(fs => !!fs.enabled);
73
- }
74
- return environment.featureStates;
171
+ function shouldApplyOverride(override, existingOverrides) {
172
+ const currentOverride = existingOverrides[override.name];
173
+ return (!currentOverride || isHigherPriority(override.priority, currentOverride.feature.priority));
75
174
  }
76
- exports.getEnvironmentFeatureStates = getEnvironmentFeatureStates;
175
+ exports.shouldApplyOverride = shouldApplyOverride;
176
+ function isHigherPriority(priorityA, priorityB) {
177
+ return (priorityA ?? Infinity) < (priorityB ?? Infinity);
178
+ }
179
+ exports.isHigherPriority = isHigherPriority;
180
+ const getTargetingMatchReason = (matchObject) => {
181
+ const { type } = matchObject;
182
+ if (type === 'SEGMENT') {
183
+ return matchObject.override
184
+ ? `${types_js_1.TARGETING_REASONS.TARGETING_MATCH}; segment=${matchObject.override.segmentName}`
185
+ : types_js_1.TARGETING_REASONS.DEFAULT;
186
+ }
187
+ if (type === 'SPLIT') {
188
+ return `${types_js_1.TARGETING_REASONS.SPLIT}; weight=${matchObject.weight}`;
189
+ }
190
+ return types_js_1.TARGETING_REASONS.DEFAULT;
191
+ };
192
+ const getIdentityKey = (context) => {
193
+ if (!context.identity)
194
+ return undefined;
195
+ return context.identity.key || `${context.environment.key}_${context.identity?.identifier}`;
196
+ };
@@ -2,6 +2,7 @@ export declare const ALL_RULE = "ALL";
2
2
  export declare const ANY_RULE = "ANY";
3
3
  export declare const NONE_RULE = "NONE";
4
4
  export declare const RULE_TYPES: string[];
5
+ export declare const IDENTITY_OVERRIDE_SEGMENT_NAME = "identity_overrides";
5
6
  export declare const EQUAL = "EQUAL";
6
7
  export declare const GREATER_THAN = "GREATER_THAN";
7
8
  export declare const LESS_THAN = "LESS_THAN";
@@ -1,11 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.CONDITION_OPERATORS = exports.IN = exports.MODULO = exports.IS_NOT_SET = exports.IS_SET = exports.PERCENTAGE_SPLIT = exports.REGEX = exports.NOT_EQUAL = exports.NOT_CONTAINS = exports.GREATER_THAN_INCLUSIVE = exports.CONTAINS = exports.LESS_THAN_INCLUSIVE = exports.LESS_THAN = exports.GREATER_THAN = exports.EQUAL = exports.RULE_TYPES = exports.NONE_RULE = exports.ANY_RULE = exports.ALL_RULE = void 0;
3
+ exports.CONDITION_OPERATORS = exports.IN = exports.MODULO = exports.IS_NOT_SET = exports.IS_SET = exports.PERCENTAGE_SPLIT = exports.REGEX = exports.NOT_EQUAL = exports.NOT_CONTAINS = exports.GREATER_THAN_INCLUSIVE = exports.CONTAINS = exports.LESS_THAN_INCLUSIVE = exports.LESS_THAN = exports.GREATER_THAN = exports.EQUAL = exports.IDENTITY_OVERRIDE_SEGMENT_NAME = exports.RULE_TYPES = exports.NONE_RULE = exports.ANY_RULE = exports.ALL_RULE = void 0;
4
4
  // Segment Rules
5
5
  exports.ALL_RULE = 'ALL';
6
6
  exports.ANY_RULE = 'ANY';
7
7
  exports.NONE_RULE = 'NONE';
8
8
  exports.RULE_TYPES = [exports.ALL_RULE, exports.ANY_RULE, exports.NONE_RULE];
9
+ exports.IDENTITY_OVERRIDE_SEGMENT_NAME = 'identity_overrides';
9
10
  // Segment Condition Operators
10
11
  exports.EQUAL = 'EQUAL';
11
12
  exports.GREATER_THAN = 'GREATER_THAN';
@@ -1,7 +1,41 @@
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;
1
+ import { GenericEvaluationContext, InSegmentCondition, SegmentCondition, SegmentContext } from '../evaluation/models.js';
2
+ /**
3
+ * Returns all segments that the identity belongs to based on segment rules evaluation.
4
+ *
5
+ * An identity belongs to a segment if it matches ALL of the segment's rules.
6
+ * If the context has no identity or segments, returns an empty array.
7
+ *
8
+ * @param context - Evaluation context containing identity and segment definitions
9
+ * @returns Array of segments that the identity matches
10
+ */
11
+ export declare function getIdentitySegments(context: GenericEvaluationContext): SegmentContext[];
12
+ /**
13
+ * Evaluates whether a segment condition matches the identity's traits or context values.
14
+ *
15
+ * Handles different types of conditions:
16
+ * - PERCENTAGE_SPLIT: Deterministic percentage-based bucketing using identity key
17
+ * - IS_SET/IS_NOT_SET: Checks for trait existence
18
+ * - Standard operators: EQUAL, NOT_EQUAL, etc. via SegmentConditionModel
19
+ * - JSONPath expressions: $.identity.identifier, $.environment.name, etc.
20
+ *
21
+ * @param condition - The condition to evaluate (property, operator, value)
22
+ * @param segmentKey - Key of the segment (used for percentage split hashing)
23
+ * @param context - Evaluation context containing identity, traits, and environment
24
+ * @returns true if the condition matches
25
+ */
26
+ export declare function traitsMatchSegmentCondition(condition: SegmentCondition | InSegmentCondition, segmentKey: string, context?: GenericEvaluationContext): boolean;
27
+ /**
28
+ * Evaluates JSONPath expressions against the evaluation context.
29
+ *
30
+ * Supports accessing nested context values using JSONPath syntax.
31
+ * Commonly used paths:
32
+ * - $.identity.identifier - User's unique identifier
33
+ * - $.identity.key - User's internal key
34
+ * - $.environment.name - Environment name
35
+ * - $.environment.key - Environment key
36
+ *
37
+ * @param jsonPath - JSONPath expression starting with '$.'
38
+ * @param context - Evaluation context to query against
39
+ * @returns The resolved value, or undefined if path doesn't exist or is invalid
40
+ */
41
+ export declare function getContextValue(jsonPath: string, context?: GenericEvaluationContext): any;
@@ -1,37 +1,149 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.traitsMatchSegmentCondition = exports.evaluateIdentityInSegment = exports.getIdentitySegments = void 0;
3
+ exports.getContextValue = exports.traitsMatchSegmentCondition = exports.getIdentitySegments = void 0;
4
+ const jsonpathModule = require("jsonpath");
4
5
  const index_js_1 = require("../utils/hashing/index.js");
6
+ const models_js_1 = require("./models.js");
5
7
  const constants_js_1 = require("./constants.js");
6
- function getIdentitySegments(environment, identity, overrideTraits) {
7
- return environment.project.segments.filter(segment => evaluateIdentityInSegment(identity, segment, overrideTraits));
8
+ // Handle ESM/CJS interop - jsonpath exports default in ESM
9
+ const jsonpath = jsonpathModule.default || jsonpathModule;
10
+ /**
11
+ * Returns all segments that the identity belongs to based on segment rules evaluation.
12
+ *
13
+ * An identity belongs to a segment if it matches ALL of the segment's rules.
14
+ * If the context has no identity or segments, returns an empty array.
15
+ *
16
+ * @param context - Evaluation context containing identity and segment definitions
17
+ * @returns Array of segments that the identity matches
18
+ */
19
+ function getIdentitySegments(context) {
20
+ if (!context.identity || !context.segments)
21
+ return [];
22
+ return Object.values(context.segments).filter(segment => {
23
+ if (segment.rules.length === 0)
24
+ return false;
25
+ return segment.rules.every(rule => traitsMatchSegmentRule(rule, segment.key, context));
26
+ });
8
27
  }
9
28
  exports.getIdentitySegments = getIdentitySegments;
10
- function evaluateIdentityInSegment(identity, segment, overrideTraits) {
11
- return (segment.rules.length > 0 &&
12
- segment.rules.filter(rule => traitsMatchSegmentRule(overrideTraits || identity.identityTraits, rule, segment.id, identity.djangoID || identity.compositeKey)).length === segment.rules.length);
13
- }
14
- exports.evaluateIdentityInSegment = evaluateIdentityInSegment;
15
- function traitsMatchSegmentRule(identityTraits, rule, segmentId, identityId) {
16
- const matchesConditions = rule.conditions.length > 0
17
- ? rule.matchingFunction()(rule.conditions.map(condition => traitsMatchSegmentCondition(identityTraits, condition, segmentId, identityId)))
18
- : true;
19
- return (matchesConditions &&
20
- rule.rules.filter(rule => traitsMatchSegmentRule(identityTraits, rule, segmentId, identityId)).length === rule.rules.length);
21
- }
22
- function traitsMatchSegmentCondition(identityTraits, condition, segmentId, identityId) {
23
- if (condition.operator == constants_js_1.PERCENTAGE_SPLIT) {
24
- var hashedPercentage = (0, index_js_1.getHashedPercentateForObjIds)([segmentId, identityId]);
29
+ /**
30
+ * Evaluates whether a segment condition matches the identity's traits or context values.
31
+ *
32
+ * Handles different types of conditions:
33
+ * - PERCENTAGE_SPLIT: Deterministic percentage-based bucketing using identity key
34
+ * - IS_SET/IS_NOT_SET: Checks for trait existence
35
+ * - Standard operators: EQUAL, NOT_EQUAL, etc. via SegmentConditionModel
36
+ * - JSONPath expressions: $.identity.identifier, $.environment.name, etc.
37
+ *
38
+ * @param condition - The condition to evaluate (property, operator, value)
39
+ * @param segmentKey - Key of the segment (used for percentage split hashing)
40
+ * @param context - Evaluation context containing identity, traits, and environment
41
+ * @returns true if the condition matches
42
+ */
43
+ function traitsMatchSegmentCondition(condition, segmentKey, context) {
44
+ if (condition.operator === constants_js_1.PERCENTAGE_SPLIT) {
45
+ let splitKey;
46
+ if (!condition.property) {
47
+ splitKey = context?.identity?.key;
48
+ }
49
+ else {
50
+ splitKey = getContextValue(condition.property, context);
51
+ }
52
+ if (!splitKey) {
53
+ return false;
54
+ }
55
+ const hashedPercentage = (0, index_js_1.getHashedPercentageForObjIds)([segmentKey, splitKey]);
25
56
  return hashedPercentage <= parseFloat(String(condition.value));
26
57
  }
27
- const traits = identityTraits.filter(t => t.traitKey === condition.property_);
28
- const trait = traits.length > 0 ? traits[0] : undefined;
58
+ if (!condition.property) {
59
+ return false;
60
+ }
61
+ const traitValue = getTraitValue(condition.property, context);
29
62
  if (condition.operator === constants_js_1.IS_SET) {
30
- return !!trait;
63
+ return traitValue !== undefined && traitValue !== null;
64
+ }
65
+ if (condition.operator === constants_js_1.IS_NOT_SET) {
66
+ return traitValue === undefined || traitValue === null;
31
67
  }
32
- else if (condition.operator === constants_js_1.IS_NOT_SET) {
33
- return trait == undefined;
68
+ if (traitValue !== undefined && traitValue !== null) {
69
+ const segmentCondition = new models_js_1.SegmentConditionModel(condition.operator, condition.value, condition.property);
70
+ return segmentCondition.matchesTraitValue(traitValue);
34
71
  }
35
- return trait ? condition.matchesTraitValue(trait.traitValue) : false;
72
+ return false;
36
73
  }
37
74
  exports.traitsMatchSegmentCondition = traitsMatchSegmentCondition;
75
+ function traitsMatchSegmentRule(rule, segmentKey, context) {
76
+ const matchesConditions = evaluateConditions(rule, segmentKey, context);
77
+ const matchesSubRules = evaluateSubRules(rule, segmentKey, context);
78
+ return matchesConditions && matchesSubRules;
79
+ }
80
+ function evaluateConditions(rule, segmentKey, context) {
81
+ if (!rule.conditions || rule.conditions.length === 0)
82
+ return true;
83
+ const conditionResults = rule.conditions.map((condition) => traitsMatchSegmentCondition(condition, segmentKey, context));
84
+ return evaluateRuleConditions(rule.type, conditionResults);
85
+ }
86
+ function evaluateSubRules(rule, segmentKey, context) {
87
+ if (!rule.rules || rule.rules.length === 0)
88
+ return true;
89
+ return rule.rules.every((subRule) => traitsMatchSegmentRule(subRule, segmentKey, context));
90
+ }
91
+ function evaluateRuleConditions(ruleType, conditionResults) {
92
+ switch (ruleType) {
93
+ case 'ALL':
94
+ return conditionResults.length === 0 || conditionResults.every(result => result);
95
+ case 'ANY':
96
+ return conditionResults.length > 0 && conditionResults.some(result => result);
97
+ case 'NONE':
98
+ return conditionResults.length === 0 || conditionResults.every(result => !result);
99
+ default:
100
+ return false;
101
+ }
102
+ }
103
+ function getTraitValue(property, context) {
104
+ if (property.startsWith('$.')) {
105
+ const contextValue = getContextValue(property, context);
106
+ if (contextValue !== undefined && isPrimitive(contextValue)) {
107
+ return contextValue;
108
+ }
109
+ }
110
+ const traits = context?.identity?.traits || {};
111
+ return traits[property];
112
+ }
113
+ function isPrimitive(value) {
114
+ if (value === null || value === undefined) {
115
+ return true;
116
+ }
117
+ // Objects and arrays are non-primitive
118
+ return typeof value !== 'object';
119
+ }
120
+ /**
121
+ * Evaluates JSONPath expressions against the evaluation context.
122
+ *
123
+ * Supports accessing nested context values using JSONPath syntax.
124
+ * Commonly used paths:
125
+ * - $.identity.identifier - User's unique identifier
126
+ * - $.identity.key - User's internal key
127
+ * - $.environment.name - Environment name
128
+ * - $.environment.key - Environment key
129
+ *
130
+ * @param jsonPath - JSONPath expression starting with '$.'
131
+ * @param context - Evaluation context to query against
132
+ * @returns The resolved value, or undefined if path doesn't exist or is invalid
133
+ */
134
+ function getContextValue(jsonPath, context) {
135
+ if (!context || !jsonPath?.startsWith('$.'))
136
+ return undefined;
137
+ try {
138
+ const normalizedPath = normalizeJsonPath(jsonPath);
139
+ const results = jsonpath.query(context, normalizedPath);
140
+ return results.length > 0 ? results[0] : undefined;
141
+ }
142
+ catch (error) {
143
+ return undefined;
144
+ }
145
+ }
146
+ exports.getContextValue = getContextValue;
147
+ function normalizeJsonPath(jsonPath) {
148
+ return jsonPath.replace(/\.([^.\[\]]+)$/, "['$1']");
149
+ }
@@ -1,11 +1,13 @@
1
1
  import { FeatureStateModel } from '../features/models.js';
2
+ import { EvaluationContext } from '../evaluation/evaluationContext/evaluationContext.types.js';
3
+ import { EvaluationResultSegments } from '../evaluation/models.js';
2
4
  export declare const all: (iterable: Array<any>) => boolean;
3
5
  export declare const any: (iterable: Array<any>) => boolean;
4
6
  export declare const matchingFunctions: {
5
7
  [x: string]: (thisValue: any, otherValue: any) => any;
6
8
  };
7
9
  export declare const semverMatchingFunction: {
8
- [x: string]: ((thisValue: any, otherValue: any) => any) | ((thisValue: any, otherValue: any) => boolean);
10
+ [x: string]: ((thisValue: any, otherValue: any) => any) | ((conditionValue: any, traitValue: any) => boolean);
9
11
  };
10
12
  export declare const getMatchingFunctions: (semver: boolean) => {
11
13
  [x: string]: (thisValue: any, otherValue: any) => any;
@@ -15,9 +17,9 @@ export declare class SegmentConditionModel {
15
17
  [key: string]: string;
16
18
  };
17
19
  operator: string;
18
- value: string | null | undefined;
19
- property_: string | null | undefined;
20
- constructor(operator: string, value?: string | null | undefined, property?: string | null | undefined);
20
+ value: string | null | undefined | string[];
21
+ property: string | null | undefined;
22
+ constructor(operator: string, value?: string | null | undefined | string[], property?: string | null | undefined);
21
23
  matchesTraitValue(traitValue: any): any;
22
24
  }
23
25
  export declare class SegmentRuleModel {
@@ -34,4 +36,7 @@ export declare class SegmentModel {
34
36
  rules: SegmentRuleModel[];
35
37
  featureStates: FeatureStateModel[];
36
38
  constructor(id: number, name: string);
39
+ static fromSegmentResult(segmentResults: EvaluationResultSegments, evaluationContext: EvaluationContext): SegmentModel[];
40
+ private static createFeatureStatesFromOverrides;
41
+ private static createMultivariateValues;
37
42
  }