@posthog/core 1.48.9 → 1.48.11
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/dist/featureFlagLocalEvaluation.d.ts +35 -0
- package/dist/featureFlagLocalEvaluation.d.ts.map +1 -0
- package/dist/featureFlagLocalEvaluation.js +352 -0
- package/dist/featureFlagLocalEvaluation.mjs +294 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +164 -79
- package/dist/index.mjs +2 -1
- package/dist/posthog-core-stateless.d.ts +10 -16
- package/dist/posthog-core-stateless.d.ts.map +1 -1
- package/dist/posthog-core-stateless.js +13 -46
- package/dist/posthog-core-stateless.mjs +13 -46
- package/dist/surveys/index.d.ts +1 -0
- package/dist/surveys/index.d.ts.map +1 -1
- package/dist/surveys/index.js +13 -0
- package/dist/surveys/index.mjs +2 -1
- package/dist/surveys/property-matching.d.ts +11 -0
- package/dist/surveys/property-matching.d.ts.map +1 -0
- package/dist/surveys/property-matching.js +89 -0
- package/dist/surveys/property-matching.mjs +46 -0
- package/dist/types.d.ts +11 -3
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/featureFlagLocalEvaluation.ts +375 -0
- package/src/index.ts +19 -0
- package/src/logs/index.spec.ts +77 -0
- package/src/logs/index.ts +1 -1
- package/src/posthog-core-stateless.ts +34 -64
- package/src/surveys/index.ts +1 -0
- package/src/surveys/property-matching.spec.ts +174 -0
- package/src/surveys/property-matching.ts +75 -0
- package/src/types.ts +17 -3
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { FeatureFlagValue, JsonType } from './types';
|
|
2
|
+
export type FeatureFlagPropertyValue = string | number | (string | number)[] | boolean;
|
|
3
|
+
export type FeatureFlagProperty = {
|
|
4
|
+
key: string;
|
|
5
|
+
value: FeatureFlagPropertyValue;
|
|
6
|
+
operator?: string;
|
|
7
|
+
};
|
|
8
|
+
export type FeatureFlagSemverParsingPolicy = 'strict' | 'legacy-permissive';
|
|
9
|
+
export type MatchFeatureFlagPropertyOptions = {
|
|
10
|
+
warnFunction?: (message: string) => void;
|
|
11
|
+
semverParsingPolicy?: FeatureFlagSemverParsingPolicy;
|
|
12
|
+
};
|
|
13
|
+
export type FeatureFlagVariant = {
|
|
14
|
+
key: string;
|
|
15
|
+
rollout_percentage: number;
|
|
16
|
+
};
|
|
17
|
+
export type FeatureFlagVariantLookupEntry = {
|
|
18
|
+
valueMin: number;
|
|
19
|
+
valueMax: number;
|
|
20
|
+
key: string;
|
|
21
|
+
};
|
|
22
|
+
export declare class InconclusiveMatchError extends Error {
|
|
23
|
+
constructor(message: string);
|
|
24
|
+
}
|
|
25
|
+
type SemverTuple = [number, number, number];
|
|
26
|
+
export declare function parseFeatureFlagSemver(value: string, parsingPolicy?: FeatureFlagSemverParsingPolicy): SemverTuple;
|
|
27
|
+
export declare function relativeDateParseForFeatureFlagMatching(value: string): Date | null;
|
|
28
|
+
export declare function matchFeatureFlagProperty(property: FeatureFlagProperty, propertyValues: Record<string, any>, options?: MatchFeatureFlagPropertyOptions): boolean;
|
|
29
|
+
export declare function hashSHA1(text: string): Promise<string>;
|
|
30
|
+
export declare function getFeatureFlagHash(key: string, bucketingValue: string, salt?: string): Promise<number>;
|
|
31
|
+
export declare function getFeatureFlagVariantLookupTable(variants: readonly FeatureFlagVariant[]): FeatureFlagVariantLookupEntry[];
|
|
32
|
+
export declare function getFeatureFlagVariant(key: string, bucketingValue: string, variants: readonly FeatureFlagVariant[]): Promise<string | undefined>;
|
|
33
|
+
export declare function resolveFeatureFlagPayload(payloads: Readonly<Record<string, JsonType | undefined>> | null | undefined, flagValue: FeatureFlagValue | null | undefined): JsonType | null;
|
|
34
|
+
export {};
|
|
35
|
+
//# sourceMappingURL=featureFlagLocalEvaluation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"featureFlagLocalEvaluation.d.ts","sourceRoot":"","sources":["../src/featureFlagLocalEvaluation.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAEzD,MAAM,MAAM,wBAAwB,GAAG,MAAM,GAAG,MAAM,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,GAAG,OAAO,CAAA;AAEtF,MAAM,MAAM,mBAAmB,GAAG;IAChC,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,wBAAwB,CAAA;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB,CAAA;AAED,MAAM,MAAM,8BAA8B,GAAG,QAAQ,GAAG,mBAAmB,CAAA;AAE3E,MAAM,MAAM,+BAA+B,GAAG;IAC5C,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;IACxC,mBAAmB,CAAC,EAAE,8BAA8B,CAAA;CACrD,CAAA;AAED,MAAM,MAAM,kBAAkB,GAAG;IAC/B,GAAG,EAAE,MAAM,CAAA;IACX,kBAAkB,EAAE,MAAM,CAAA;CAC3B,CAAA;AAED,MAAM,MAAM,6BAA6B,GAAG;IAC1C,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,GAAG,EAAE,MAAM,CAAA;CACZ,CAAA;AASD,qBAAa,sBAAuB,SAAQ,KAAK;gBACnC,OAAO,EAAE,MAAM;CAK5B;AAWD,KAAK,WAAW,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;AAa3C,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,MAAM,EACb,aAAa,GAAE,8BAAyC,GACvD,WAAW,CAeb;AAwED,wBAAgB,uCAAuC,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAmBlF;AAED,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,mBAAmB,EAC7B,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACnC,OAAO,GAAE,+BAAoC,GAC5C,OAAO,CA+IT;AAED,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAQ5D;AAED,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,IAAI,GAAE,MAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAGhH;AAED,wBAAgB,gCAAgC,CAC9C,QAAQ,EAAE,SAAS,kBAAkB,EAAE,GACtC,6BAA6B,EAAE,CASjC;AAED,wBAAsB,qBAAqB,CACzC,GAAG,EAAE,MAAM,EACX,cAAc,EAAE,MAAM,EACtB,QAAQ,EAAE,SAAS,kBAAkB,EAAE,GACtC,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAK7B;AAED,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,GAAG,SAAS,CAAC,CAAC,GAAG,IAAI,GAAG,SAAS,EAC3E,SAAS,EAAE,gBAAgB,GAAG,IAAI,GAAG,SAAS,GAC7C,QAAQ,GAAG,IAAI,CAMjB"}
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __webpack_require__ = {};
|
|
3
|
+
(()=>{
|
|
4
|
+
__webpack_require__.d = (exports1, definition)=>{
|
|
5
|
+
for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
get: definition[key]
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
})();
|
|
11
|
+
(()=>{
|
|
12
|
+
__webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
|
|
13
|
+
})();
|
|
14
|
+
(()=>{
|
|
15
|
+
__webpack_require__.r = (exports1)=>{
|
|
16
|
+
if ('undefined' != typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
|
|
17
|
+
value: 'Module'
|
|
18
|
+
});
|
|
19
|
+
Object.defineProperty(exports1, '__esModule', {
|
|
20
|
+
value: true
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
})();
|
|
24
|
+
var __webpack_exports__ = {};
|
|
25
|
+
__webpack_require__.r(__webpack_exports__);
|
|
26
|
+
__webpack_require__.d(__webpack_exports__, {
|
|
27
|
+
getFeatureFlagHash: ()=>getFeatureFlagHash,
|
|
28
|
+
relativeDateParseForFeatureFlagMatching: ()=>relativeDateParseForFeatureFlagMatching,
|
|
29
|
+
InconclusiveMatchError: ()=>InconclusiveMatchError,
|
|
30
|
+
parseFeatureFlagSemver: ()=>parseFeatureFlagSemver,
|
|
31
|
+
resolveFeatureFlagPayload: ()=>resolveFeatureFlagPayload,
|
|
32
|
+
getFeatureFlagVariantLookupTable: ()=>getFeatureFlagVariantLookupTable,
|
|
33
|
+
hashSHA1: ()=>hashSHA1,
|
|
34
|
+
matchFeatureFlagProperty: ()=>matchFeatureFlagProperty,
|
|
35
|
+
getFeatureFlagVariant: ()=>getFeatureFlagVariant
|
|
36
|
+
});
|
|
37
|
+
const external_featureFlagUtils_js_namespaceObject = require("./featureFlagUtils.js");
|
|
38
|
+
const NULL_VALUES_ALLOWED_OPERATORS = [
|
|
39
|
+
'is_not',
|
|
40
|
+
'is_set'
|
|
41
|
+
];
|
|
42
|
+
const LONG_SCALE = 0xfffffffffffffff;
|
|
43
|
+
class InconclusiveMatchError extends Error {
|
|
44
|
+
constructor(message){
|
|
45
|
+
super(message);
|
|
46
|
+
this.name = this.constructor.name;
|
|
47
|
+
Object.setPrototypeOf(this, InconclusiveMatchError.prototype);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function isValidRegex(regex) {
|
|
51
|
+
try {
|
|
52
|
+
new RegExp(regex);
|
|
53
|
+
return true;
|
|
54
|
+
} catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function parseSemverNumericIdentifier(part, raw, parsingPolicy) {
|
|
59
|
+
if (!/^\d+$/.test(part) || 'strict' === parsingPolicy && part.length > 1 && '0' === part[0]) throw new InconclusiveMatchError(`Invalid semver: ${raw}`);
|
|
60
|
+
return parseInt(part, 10);
|
|
61
|
+
}
|
|
62
|
+
function parseFeatureFlagSemver(value, parsingPolicy = 'strict') {
|
|
63
|
+
const text = String(value).trim().replace(/^[vV]/, '');
|
|
64
|
+
const baseVersion = text.split('-')[0].split('+')[0];
|
|
65
|
+
if (!baseVersion || baseVersion.startsWith('.')) throw new InconclusiveMatchError(`Invalid semver: ${value}`);
|
|
66
|
+
const parts = baseVersion.split('.');
|
|
67
|
+
const parsePart = (part)=>{
|
|
68
|
+
if (void 0 === part || '' === part) return 0;
|
|
69
|
+
return parseSemverNumericIdentifier(part, value, parsingPolicy);
|
|
70
|
+
};
|
|
71
|
+
return [
|
|
72
|
+
parsePart(parts[0]),
|
|
73
|
+
parsePart(parts[1]),
|
|
74
|
+
parsePart(parts[2])
|
|
75
|
+
];
|
|
76
|
+
}
|
|
77
|
+
function compareSemverTuples(a, b) {
|
|
78
|
+
for(let i = 0; i < 3; i++){
|
|
79
|
+
if (a[i] < b[i]) return -1;
|
|
80
|
+
if (a[i] > b[i]) return 1;
|
|
81
|
+
}
|
|
82
|
+
return 0;
|
|
83
|
+
}
|
|
84
|
+
function computeTildeBounds(value, parsingPolicy) {
|
|
85
|
+
const parsed = parseFeatureFlagSemver(value, parsingPolicy);
|
|
86
|
+
return {
|
|
87
|
+
lower: [
|
|
88
|
+
parsed[0],
|
|
89
|
+
parsed[1],
|
|
90
|
+
parsed[2]
|
|
91
|
+
],
|
|
92
|
+
upper: [
|
|
93
|
+
parsed[0],
|
|
94
|
+
parsed[1] + 1,
|
|
95
|
+
0
|
|
96
|
+
]
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function computeCaretBounds(value, parsingPolicy) {
|
|
100
|
+
const [major, minor, patch] = parseFeatureFlagSemver(value, parsingPolicy);
|
|
101
|
+
const lower = [
|
|
102
|
+
major,
|
|
103
|
+
minor,
|
|
104
|
+
patch
|
|
105
|
+
];
|
|
106
|
+
let upper;
|
|
107
|
+
upper = major > 0 ? [
|
|
108
|
+
major + 1,
|
|
109
|
+
0,
|
|
110
|
+
0
|
|
111
|
+
] : minor > 0 ? [
|
|
112
|
+
0,
|
|
113
|
+
minor + 1,
|
|
114
|
+
0
|
|
115
|
+
] : [
|
|
116
|
+
0,
|
|
117
|
+
0,
|
|
118
|
+
patch + 1
|
|
119
|
+
];
|
|
120
|
+
return {
|
|
121
|
+
lower,
|
|
122
|
+
upper
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
function computeWildcardBounds(value, parsingPolicy) {
|
|
126
|
+
const text = String(value).trim().replace(/^[vV]/, '');
|
|
127
|
+
const cleanedText = text.replace(/\.\*$/, '').replace(/\*$/, '');
|
|
128
|
+
if (!cleanedText) throw new InconclusiveMatchError(`Invalid wildcard semver: ${value}`);
|
|
129
|
+
const parts = cleanedText.split('.');
|
|
130
|
+
const parseWildcardPart = (part)=>{
|
|
131
|
+
if ('legacy-permissive' === parsingPolicy) {
|
|
132
|
+
const parsed = parseInt(part, 10);
|
|
133
|
+
if (!isNaN(parsed)) return parsed;
|
|
134
|
+
} else try {
|
|
135
|
+
return parseSemverNumericIdentifier(part, value, parsingPolicy);
|
|
136
|
+
} catch {}
|
|
137
|
+
throw new InconclusiveMatchError(`Invalid wildcard semver: ${value}`);
|
|
138
|
+
};
|
|
139
|
+
const major = parseWildcardPart(parts[0]);
|
|
140
|
+
if (1 === parts.length) return {
|
|
141
|
+
lower: [
|
|
142
|
+
major,
|
|
143
|
+
0,
|
|
144
|
+
0
|
|
145
|
+
],
|
|
146
|
+
upper: [
|
|
147
|
+
major + 1,
|
|
148
|
+
0,
|
|
149
|
+
0
|
|
150
|
+
]
|
|
151
|
+
};
|
|
152
|
+
const minor = parseWildcardPart(parts[1]);
|
|
153
|
+
return {
|
|
154
|
+
lower: [
|
|
155
|
+
major,
|
|
156
|
+
minor,
|
|
157
|
+
0
|
|
158
|
+
],
|
|
159
|
+
upper: [
|
|
160
|
+
major,
|
|
161
|
+
minor + 1,
|
|
162
|
+
0
|
|
163
|
+
]
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
function convertToDateTime(value) {
|
|
167
|
+
if (value instanceof Date) return value;
|
|
168
|
+
if ('string' == typeof value || 'number' == typeof value) {
|
|
169
|
+
const date = new Date(value);
|
|
170
|
+
if (!isNaN(date.valueOf())) return date;
|
|
171
|
+
throw new InconclusiveMatchError(`${value} is in an invalid date format`);
|
|
172
|
+
}
|
|
173
|
+
throw new InconclusiveMatchError(`The date provided ${value} must be a string, number, or date object`);
|
|
174
|
+
}
|
|
175
|
+
function relativeDateParseForFeatureFlagMatching(value) {
|
|
176
|
+
const regex = /^-?(?<number>[0-9]+)(?<interval>[a-z])$/;
|
|
177
|
+
const match = value.match(regex);
|
|
178
|
+
const parsedDt = new Date(new Date().toISOString());
|
|
179
|
+
if (!match || !match.groups) return null;
|
|
180
|
+
const number = parseInt(match.groups['number']);
|
|
181
|
+
if (number >= 10000) return null;
|
|
182
|
+
const interval = match.groups['interval'];
|
|
183
|
+
if ('h' === interval) parsedDt.setUTCHours(parsedDt.getUTCHours() - number);
|
|
184
|
+
else if ('d' === interval) parsedDt.setUTCDate(parsedDt.getUTCDate() - number);
|
|
185
|
+
else if ('w' === interval) parsedDt.setUTCDate(parsedDt.getUTCDate() - 7 * number);
|
|
186
|
+
else if ('m' === interval) parsedDt.setUTCMonth(parsedDt.getUTCMonth() - number);
|
|
187
|
+
else {
|
|
188
|
+
if ('y' !== interval) return null;
|
|
189
|
+
parsedDt.setUTCFullYear(parsedDt.getUTCFullYear() - number);
|
|
190
|
+
}
|
|
191
|
+
return parsedDt;
|
|
192
|
+
}
|
|
193
|
+
function matchFeatureFlagProperty(property, propertyValues, options = {}) {
|
|
194
|
+
const key = property.key;
|
|
195
|
+
const value = property.value;
|
|
196
|
+
const operator = property.operator || 'exact';
|
|
197
|
+
const parsingPolicy = options.semverParsingPolicy ?? 'strict';
|
|
198
|
+
if (key in propertyValues) {
|
|
199
|
+
if ('is_not_set' === operator) return false;
|
|
200
|
+
} else {
|
|
201
|
+
if ('is_not_set' === operator) return true;
|
|
202
|
+
throw new InconclusiveMatchError(`Property ${key} not found in propertyValues`);
|
|
203
|
+
}
|
|
204
|
+
const overrideValue = propertyValues[key];
|
|
205
|
+
if (null == overrideValue && !NULL_VALUES_ALLOWED_OPERATORS.includes(operator)) {
|
|
206
|
+
options.warnFunction?.(`Property ${key} cannot have a value of null/undefined with the ${operator} operator`);
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
const computeExactMatch = (target, actual)=>{
|
|
210
|
+
if (Array.isArray(target)) return target.map((item)=>String(item).toLowerCase()).includes(String(actual).toLowerCase());
|
|
211
|
+
return String(target).toLowerCase() === String(actual).toLowerCase();
|
|
212
|
+
};
|
|
213
|
+
const compare = (lhs, rhs, comparisonOperator)=>{
|
|
214
|
+
if ('gt' === comparisonOperator) return lhs > rhs;
|
|
215
|
+
if ('gte' === comparisonOperator) return lhs >= rhs;
|
|
216
|
+
if ('lt' === comparisonOperator) return lhs < rhs;
|
|
217
|
+
if ('lte' === comparisonOperator) return lhs <= rhs;
|
|
218
|
+
throw new Error(`Invalid operator: ${comparisonOperator}`);
|
|
219
|
+
};
|
|
220
|
+
switch(operator){
|
|
221
|
+
case 'exact':
|
|
222
|
+
return computeExactMatch(value, overrideValue);
|
|
223
|
+
case 'is_not':
|
|
224
|
+
return !computeExactMatch(value, overrideValue);
|
|
225
|
+
case 'is_set':
|
|
226
|
+
return key in propertyValues;
|
|
227
|
+
case 'icontains':
|
|
228
|
+
return String(overrideValue).toLowerCase().includes(String(value).toLowerCase());
|
|
229
|
+
case 'not_icontains':
|
|
230
|
+
return !String(overrideValue).toLowerCase().includes(String(value).toLowerCase());
|
|
231
|
+
case 'starts_with':
|
|
232
|
+
return String(overrideValue).toLowerCase().startsWith(String(value).toLowerCase());
|
|
233
|
+
case 'not_starts_with':
|
|
234
|
+
return !String(overrideValue).toLowerCase().startsWith(String(value).toLowerCase());
|
|
235
|
+
case 'ends_with':
|
|
236
|
+
return String(overrideValue).toLowerCase().endsWith(String(value).toLowerCase());
|
|
237
|
+
case 'not_ends_with':
|
|
238
|
+
return !String(overrideValue).toLowerCase().endsWith(String(value).toLowerCase());
|
|
239
|
+
case 'regex':
|
|
240
|
+
return isValidRegex(String(value)) && null !== String(overrideValue).match(String(value));
|
|
241
|
+
case 'not_regex':
|
|
242
|
+
return isValidRegex(String(value)) && null === String(overrideValue).match(String(value));
|
|
243
|
+
case 'gt':
|
|
244
|
+
case 'gte':
|
|
245
|
+
case 'lt':
|
|
246
|
+
case 'lte':
|
|
247
|
+
{
|
|
248
|
+
const parsedValue = 'number' == typeof value ? value : parseFloat(String(value));
|
|
249
|
+
const parsedOverride = 'number' == typeof overrideValue ? overrideValue : null != overrideValue ? parseFloat(String(overrideValue)) : NaN;
|
|
250
|
+
if (Number.isFinite(parsedValue) && Number.isFinite(parsedOverride)) return compare(parsedOverride, parsedValue, operator);
|
|
251
|
+
return compare(String(overrideValue), String(value), operator);
|
|
252
|
+
}
|
|
253
|
+
case 'is_date_after':
|
|
254
|
+
case 'is_date_before':
|
|
255
|
+
{
|
|
256
|
+
if ('boolean' == typeof value) throw new InconclusiveMatchError('Date operations cannot be performed on boolean values');
|
|
257
|
+
let parsedDate = relativeDateParseForFeatureFlagMatching(String(value));
|
|
258
|
+
if (null == parsedDate) parsedDate = convertToDateTime(value);
|
|
259
|
+
const overrideDate = convertToDateTime(overrideValue);
|
|
260
|
+
return 'is_date_before' === operator ? overrideDate < parsedDate : overrideDate > parsedDate;
|
|
261
|
+
}
|
|
262
|
+
case 'semver_eq':
|
|
263
|
+
return 0 === compareSemverTuples(parseFeatureFlagSemver(String(overrideValue), parsingPolicy), parseFeatureFlagSemver(String(value), parsingPolicy));
|
|
264
|
+
case 'semver_neq':
|
|
265
|
+
return 0 !== compareSemverTuples(parseFeatureFlagSemver(String(overrideValue), parsingPolicy), parseFeatureFlagSemver(String(value), parsingPolicy));
|
|
266
|
+
case 'semver_gt':
|
|
267
|
+
return compareSemverTuples(parseFeatureFlagSemver(String(overrideValue), parsingPolicy), parseFeatureFlagSemver(String(value), parsingPolicy)) > 0;
|
|
268
|
+
case 'semver_gte':
|
|
269
|
+
return compareSemverTuples(parseFeatureFlagSemver(String(overrideValue), parsingPolicy), parseFeatureFlagSemver(String(value), parsingPolicy)) >= 0;
|
|
270
|
+
case 'semver_lt':
|
|
271
|
+
return compareSemverTuples(parseFeatureFlagSemver(String(overrideValue), parsingPolicy), parseFeatureFlagSemver(String(value), parsingPolicy)) < 0;
|
|
272
|
+
case 'semver_lte':
|
|
273
|
+
return compareSemverTuples(parseFeatureFlagSemver(String(overrideValue), parsingPolicy), parseFeatureFlagSemver(String(value), parsingPolicy)) <= 0;
|
|
274
|
+
case 'semver_tilde':
|
|
275
|
+
{
|
|
276
|
+
const overrideParsed = parseFeatureFlagSemver(String(overrideValue), parsingPolicy);
|
|
277
|
+
const { lower, upper } = computeTildeBounds(String(value), parsingPolicy);
|
|
278
|
+
return compareSemverTuples(overrideParsed, lower) >= 0 && compareSemverTuples(overrideParsed, upper) < 0;
|
|
279
|
+
}
|
|
280
|
+
case 'semver_caret':
|
|
281
|
+
{
|
|
282
|
+
const overrideParsed = parseFeatureFlagSemver(String(overrideValue), parsingPolicy);
|
|
283
|
+
const { lower, upper } = computeCaretBounds(String(value), parsingPolicy);
|
|
284
|
+
return compareSemverTuples(overrideParsed, lower) >= 0 && compareSemverTuples(overrideParsed, upper) < 0;
|
|
285
|
+
}
|
|
286
|
+
case 'semver_wildcard':
|
|
287
|
+
{
|
|
288
|
+
const overrideParsed = parseFeatureFlagSemver(String(overrideValue), parsingPolicy);
|
|
289
|
+
const { lower, upper } = computeWildcardBounds(String(value), parsingPolicy);
|
|
290
|
+
return compareSemverTuples(overrideParsed, lower) >= 0 && compareSemverTuples(overrideParsed, upper) < 0;
|
|
291
|
+
}
|
|
292
|
+
default:
|
|
293
|
+
throw new InconclusiveMatchError(`Unknown operator: ${operator}`);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
async function hashSHA1(text) {
|
|
297
|
+
const subtle = globalThis.crypto?.subtle;
|
|
298
|
+
if (!subtle) throw new Error('SubtleCrypto API not available');
|
|
299
|
+
const hashBuffer = await subtle.digest('SHA-1', new TextEncoder().encode(text));
|
|
300
|
+
return Array.from(new Uint8Array(hashBuffer)).map((byte)=>byte.toString(16).padStart(2, '0')).join('');
|
|
301
|
+
}
|
|
302
|
+
async function getFeatureFlagHash(key, bucketingValue, salt = '') {
|
|
303
|
+
const hashString = await hashSHA1(`${key}.${bucketingValue}${salt}`);
|
|
304
|
+
return parseInt(hashString.slice(0, 15), 16) / LONG_SCALE;
|
|
305
|
+
}
|
|
306
|
+
function getFeatureFlagVariantLookupTable(variants) {
|
|
307
|
+
const table = [];
|
|
308
|
+
let valueMin = 0;
|
|
309
|
+
for (const variant of variants){
|
|
310
|
+
const valueMax = valueMin + variant.rollout_percentage / 100.0;
|
|
311
|
+
table.push({
|
|
312
|
+
valueMin,
|
|
313
|
+
valueMax,
|
|
314
|
+
key: variant.key
|
|
315
|
+
});
|
|
316
|
+
valueMin = valueMax;
|
|
317
|
+
}
|
|
318
|
+
return table;
|
|
319
|
+
}
|
|
320
|
+
async function getFeatureFlagVariant(key, bucketingValue, variants) {
|
|
321
|
+
const hashValue = await getFeatureFlagHash(key, bucketingValue, 'variant');
|
|
322
|
+
return getFeatureFlagVariantLookupTable(variants).find((variant)=>hashValue >= variant.valueMin && hashValue < variant.valueMax)?.key;
|
|
323
|
+
}
|
|
324
|
+
function resolveFeatureFlagPayload(payloads, flagValue) {
|
|
325
|
+
if (false === flagValue || null == flagValue || !payloads) return null;
|
|
326
|
+
const payloadKey = 'boolean' == typeof flagValue ? flagValue.toString() : flagValue;
|
|
327
|
+
const payload = payloads[payloadKey] || null;
|
|
328
|
+
return null == payload ? null : (0, external_featureFlagUtils_js_namespaceObject.parsePayload)(payload);
|
|
329
|
+
}
|
|
330
|
+
exports.InconclusiveMatchError = __webpack_exports__.InconclusiveMatchError;
|
|
331
|
+
exports.getFeatureFlagHash = __webpack_exports__.getFeatureFlagHash;
|
|
332
|
+
exports.getFeatureFlagVariant = __webpack_exports__.getFeatureFlagVariant;
|
|
333
|
+
exports.getFeatureFlagVariantLookupTable = __webpack_exports__.getFeatureFlagVariantLookupTable;
|
|
334
|
+
exports.hashSHA1 = __webpack_exports__.hashSHA1;
|
|
335
|
+
exports.matchFeatureFlagProperty = __webpack_exports__.matchFeatureFlagProperty;
|
|
336
|
+
exports.parseFeatureFlagSemver = __webpack_exports__.parseFeatureFlagSemver;
|
|
337
|
+
exports.relativeDateParseForFeatureFlagMatching = __webpack_exports__.relativeDateParseForFeatureFlagMatching;
|
|
338
|
+
exports.resolveFeatureFlagPayload = __webpack_exports__.resolveFeatureFlagPayload;
|
|
339
|
+
for(var __webpack_i__ in __webpack_exports__)if (-1 === [
|
|
340
|
+
"InconclusiveMatchError",
|
|
341
|
+
"getFeatureFlagHash",
|
|
342
|
+
"getFeatureFlagVariant",
|
|
343
|
+
"getFeatureFlagVariantLookupTable",
|
|
344
|
+
"hashSHA1",
|
|
345
|
+
"matchFeatureFlagProperty",
|
|
346
|
+
"parseFeatureFlagSemver",
|
|
347
|
+
"relativeDateParseForFeatureFlagMatching",
|
|
348
|
+
"resolveFeatureFlagPayload"
|
|
349
|
+
].indexOf(__webpack_i__)) exports[__webpack_i__] = __webpack_exports__[__webpack_i__];
|
|
350
|
+
Object.defineProperty(exports, '__esModule', {
|
|
351
|
+
value: true
|
|
352
|
+
});
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import { parsePayload } from "./featureFlagUtils.mjs";
|
|
2
|
+
const NULL_VALUES_ALLOWED_OPERATORS = [
|
|
3
|
+
'is_not',
|
|
4
|
+
'is_set'
|
|
5
|
+
];
|
|
6
|
+
const LONG_SCALE = 0xfffffffffffffff;
|
|
7
|
+
class InconclusiveMatchError extends Error {
|
|
8
|
+
constructor(message){
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = this.constructor.name;
|
|
11
|
+
Object.setPrototypeOf(this, InconclusiveMatchError.prototype);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function isValidRegex(regex) {
|
|
15
|
+
try {
|
|
16
|
+
new RegExp(regex);
|
|
17
|
+
return true;
|
|
18
|
+
} catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function parseSemverNumericIdentifier(part, raw, parsingPolicy) {
|
|
23
|
+
if (!/^\d+$/.test(part) || 'strict' === parsingPolicy && part.length > 1 && '0' === part[0]) throw new InconclusiveMatchError(`Invalid semver: ${raw}`);
|
|
24
|
+
return parseInt(part, 10);
|
|
25
|
+
}
|
|
26
|
+
function parseFeatureFlagSemver(value, parsingPolicy = 'strict') {
|
|
27
|
+
const text = String(value).trim().replace(/^[vV]/, '');
|
|
28
|
+
const baseVersion = text.split('-')[0].split('+')[0];
|
|
29
|
+
if (!baseVersion || baseVersion.startsWith('.')) throw new InconclusiveMatchError(`Invalid semver: ${value}`);
|
|
30
|
+
const parts = baseVersion.split('.');
|
|
31
|
+
const parsePart = (part)=>{
|
|
32
|
+
if (void 0 === part || '' === part) return 0;
|
|
33
|
+
return parseSemverNumericIdentifier(part, value, parsingPolicy);
|
|
34
|
+
};
|
|
35
|
+
return [
|
|
36
|
+
parsePart(parts[0]),
|
|
37
|
+
parsePart(parts[1]),
|
|
38
|
+
parsePart(parts[2])
|
|
39
|
+
];
|
|
40
|
+
}
|
|
41
|
+
function compareSemverTuples(a, b) {
|
|
42
|
+
for(let i = 0; i < 3; i++){
|
|
43
|
+
if (a[i] < b[i]) return -1;
|
|
44
|
+
if (a[i] > b[i]) return 1;
|
|
45
|
+
}
|
|
46
|
+
return 0;
|
|
47
|
+
}
|
|
48
|
+
function computeTildeBounds(value, parsingPolicy) {
|
|
49
|
+
const parsed = parseFeatureFlagSemver(value, parsingPolicy);
|
|
50
|
+
return {
|
|
51
|
+
lower: [
|
|
52
|
+
parsed[0],
|
|
53
|
+
parsed[1],
|
|
54
|
+
parsed[2]
|
|
55
|
+
],
|
|
56
|
+
upper: [
|
|
57
|
+
parsed[0],
|
|
58
|
+
parsed[1] + 1,
|
|
59
|
+
0
|
|
60
|
+
]
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function computeCaretBounds(value, parsingPolicy) {
|
|
64
|
+
const [major, minor, patch] = parseFeatureFlagSemver(value, parsingPolicy);
|
|
65
|
+
const lower = [
|
|
66
|
+
major,
|
|
67
|
+
minor,
|
|
68
|
+
patch
|
|
69
|
+
];
|
|
70
|
+
let upper;
|
|
71
|
+
upper = major > 0 ? [
|
|
72
|
+
major + 1,
|
|
73
|
+
0,
|
|
74
|
+
0
|
|
75
|
+
] : minor > 0 ? [
|
|
76
|
+
0,
|
|
77
|
+
minor + 1,
|
|
78
|
+
0
|
|
79
|
+
] : [
|
|
80
|
+
0,
|
|
81
|
+
0,
|
|
82
|
+
patch + 1
|
|
83
|
+
];
|
|
84
|
+
return {
|
|
85
|
+
lower,
|
|
86
|
+
upper
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function computeWildcardBounds(value, parsingPolicy) {
|
|
90
|
+
const text = String(value).trim().replace(/^[vV]/, '');
|
|
91
|
+
const cleanedText = text.replace(/\.\*$/, '').replace(/\*$/, '');
|
|
92
|
+
if (!cleanedText) throw new InconclusiveMatchError(`Invalid wildcard semver: ${value}`);
|
|
93
|
+
const parts = cleanedText.split('.');
|
|
94
|
+
const parseWildcardPart = (part)=>{
|
|
95
|
+
if ('legacy-permissive' === parsingPolicy) {
|
|
96
|
+
const parsed = parseInt(part, 10);
|
|
97
|
+
if (!isNaN(parsed)) return parsed;
|
|
98
|
+
} else try {
|
|
99
|
+
return parseSemverNumericIdentifier(part, value, parsingPolicy);
|
|
100
|
+
} catch {}
|
|
101
|
+
throw new InconclusiveMatchError(`Invalid wildcard semver: ${value}`);
|
|
102
|
+
};
|
|
103
|
+
const major = parseWildcardPart(parts[0]);
|
|
104
|
+
if (1 === parts.length) return {
|
|
105
|
+
lower: [
|
|
106
|
+
major,
|
|
107
|
+
0,
|
|
108
|
+
0
|
|
109
|
+
],
|
|
110
|
+
upper: [
|
|
111
|
+
major + 1,
|
|
112
|
+
0,
|
|
113
|
+
0
|
|
114
|
+
]
|
|
115
|
+
};
|
|
116
|
+
const minor = parseWildcardPart(parts[1]);
|
|
117
|
+
return {
|
|
118
|
+
lower: [
|
|
119
|
+
major,
|
|
120
|
+
minor,
|
|
121
|
+
0
|
|
122
|
+
],
|
|
123
|
+
upper: [
|
|
124
|
+
major,
|
|
125
|
+
minor + 1,
|
|
126
|
+
0
|
|
127
|
+
]
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
function convertToDateTime(value) {
|
|
131
|
+
if (value instanceof Date) return value;
|
|
132
|
+
if ('string' == typeof value || 'number' == typeof value) {
|
|
133
|
+
const date = new Date(value);
|
|
134
|
+
if (!isNaN(date.valueOf())) return date;
|
|
135
|
+
throw new InconclusiveMatchError(`${value} is in an invalid date format`);
|
|
136
|
+
}
|
|
137
|
+
throw new InconclusiveMatchError(`The date provided ${value} must be a string, number, or date object`);
|
|
138
|
+
}
|
|
139
|
+
function relativeDateParseForFeatureFlagMatching(value) {
|
|
140
|
+
const regex = /^-?(?<number>[0-9]+)(?<interval>[a-z])$/;
|
|
141
|
+
const match = value.match(regex);
|
|
142
|
+
const parsedDt = new Date(new Date().toISOString());
|
|
143
|
+
if (!match || !match.groups) return null;
|
|
144
|
+
const number = parseInt(match.groups['number']);
|
|
145
|
+
if (number >= 10000) return null;
|
|
146
|
+
const interval = match.groups['interval'];
|
|
147
|
+
if ('h' === interval) parsedDt.setUTCHours(parsedDt.getUTCHours() - number);
|
|
148
|
+
else if ('d' === interval) parsedDt.setUTCDate(parsedDt.getUTCDate() - number);
|
|
149
|
+
else if ('w' === interval) parsedDt.setUTCDate(parsedDt.getUTCDate() - 7 * number);
|
|
150
|
+
else if ('m' === interval) parsedDt.setUTCMonth(parsedDt.getUTCMonth() - number);
|
|
151
|
+
else {
|
|
152
|
+
if ('y' !== interval) return null;
|
|
153
|
+
parsedDt.setUTCFullYear(parsedDt.getUTCFullYear() - number);
|
|
154
|
+
}
|
|
155
|
+
return parsedDt;
|
|
156
|
+
}
|
|
157
|
+
function matchFeatureFlagProperty(property, propertyValues, options = {}) {
|
|
158
|
+
const key = property.key;
|
|
159
|
+
const value = property.value;
|
|
160
|
+
const operator = property.operator || 'exact';
|
|
161
|
+
const parsingPolicy = options.semverParsingPolicy ?? 'strict';
|
|
162
|
+
if (key in propertyValues) {
|
|
163
|
+
if ('is_not_set' === operator) return false;
|
|
164
|
+
} else {
|
|
165
|
+
if ('is_not_set' === operator) return true;
|
|
166
|
+
throw new InconclusiveMatchError(`Property ${key} not found in propertyValues`);
|
|
167
|
+
}
|
|
168
|
+
const overrideValue = propertyValues[key];
|
|
169
|
+
if (null == overrideValue && !NULL_VALUES_ALLOWED_OPERATORS.includes(operator)) {
|
|
170
|
+
options.warnFunction?.(`Property ${key} cannot have a value of null/undefined with the ${operator} operator`);
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
const computeExactMatch = (target, actual)=>{
|
|
174
|
+
if (Array.isArray(target)) return target.map((item)=>String(item).toLowerCase()).includes(String(actual).toLowerCase());
|
|
175
|
+
return String(target).toLowerCase() === String(actual).toLowerCase();
|
|
176
|
+
};
|
|
177
|
+
const compare = (lhs, rhs, comparisonOperator)=>{
|
|
178
|
+
if ('gt' === comparisonOperator) return lhs > rhs;
|
|
179
|
+
if ('gte' === comparisonOperator) return lhs >= rhs;
|
|
180
|
+
if ('lt' === comparisonOperator) return lhs < rhs;
|
|
181
|
+
if ('lte' === comparisonOperator) return lhs <= rhs;
|
|
182
|
+
throw new Error(`Invalid operator: ${comparisonOperator}`);
|
|
183
|
+
};
|
|
184
|
+
switch(operator){
|
|
185
|
+
case 'exact':
|
|
186
|
+
return computeExactMatch(value, overrideValue);
|
|
187
|
+
case 'is_not':
|
|
188
|
+
return !computeExactMatch(value, overrideValue);
|
|
189
|
+
case 'is_set':
|
|
190
|
+
return key in propertyValues;
|
|
191
|
+
case 'icontains':
|
|
192
|
+
return String(overrideValue).toLowerCase().includes(String(value).toLowerCase());
|
|
193
|
+
case 'not_icontains':
|
|
194
|
+
return !String(overrideValue).toLowerCase().includes(String(value).toLowerCase());
|
|
195
|
+
case 'starts_with':
|
|
196
|
+
return String(overrideValue).toLowerCase().startsWith(String(value).toLowerCase());
|
|
197
|
+
case 'not_starts_with':
|
|
198
|
+
return !String(overrideValue).toLowerCase().startsWith(String(value).toLowerCase());
|
|
199
|
+
case 'ends_with':
|
|
200
|
+
return String(overrideValue).toLowerCase().endsWith(String(value).toLowerCase());
|
|
201
|
+
case 'not_ends_with':
|
|
202
|
+
return !String(overrideValue).toLowerCase().endsWith(String(value).toLowerCase());
|
|
203
|
+
case 'regex':
|
|
204
|
+
return isValidRegex(String(value)) && null !== String(overrideValue).match(String(value));
|
|
205
|
+
case 'not_regex':
|
|
206
|
+
return isValidRegex(String(value)) && null === String(overrideValue).match(String(value));
|
|
207
|
+
case 'gt':
|
|
208
|
+
case 'gte':
|
|
209
|
+
case 'lt':
|
|
210
|
+
case 'lte':
|
|
211
|
+
{
|
|
212
|
+
const parsedValue = 'number' == typeof value ? value : parseFloat(String(value));
|
|
213
|
+
const parsedOverride = 'number' == typeof overrideValue ? overrideValue : null != overrideValue ? parseFloat(String(overrideValue)) : NaN;
|
|
214
|
+
if (Number.isFinite(parsedValue) && Number.isFinite(parsedOverride)) return compare(parsedOverride, parsedValue, operator);
|
|
215
|
+
return compare(String(overrideValue), String(value), operator);
|
|
216
|
+
}
|
|
217
|
+
case 'is_date_after':
|
|
218
|
+
case 'is_date_before':
|
|
219
|
+
{
|
|
220
|
+
if ('boolean' == typeof value) throw new InconclusiveMatchError('Date operations cannot be performed on boolean values');
|
|
221
|
+
let parsedDate = relativeDateParseForFeatureFlagMatching(String(value));
|
|
222
|
+
if (null == parsedDate) parsedDate = convertToDateTime(value);
|
|
223
|
+
const overrideDate = convertToDateTime(overrideValue);
|
|
224
|
+
return 'is_date_before' === operator ? overrideDate < parsedDate : overrideDate > parsedDate;
|
|
225
|
+
}
|
|
226
|
+
case 'semver_eq':
|
|
227
|
+
return 0 === compareSemverTuples(parseFeatureFlagSemver(String(overrideValue), parsingPolicy), parseFeatureFlagSemver(String(value), parsingPolicy));
|
|
228
|
+
case 'semver_neq':
|
|
229
|
+
return 0 !== compareSemverTuples(parseFeatureFlagSemver(String(overrideValue), parsingPolicy), parseFeatureFlagSemver(String(value), parsingPolicy));
|
|
230
|
+
case 'semver_gt':
|
|
231
|
+
return compareSemverTuples(parseFeatureFlagSemver(String(overrideValue), parsingPolicy), parseFeatureFlagSemver(String(value), parsingPolicy)) > 0;
|
|
232
|
+
case 'semver_gte':
|
|
233
|
+
return compareSemverTuples(parseFeatureFlagSemver(String(overrideValue), parsingPolicy), parseFeatureFlagSemver(String(value), parsingPolicy)) >= 0;
|
|
234
|
+
case 'semver_lt':
|
|
235
|
+
return compareSemverTuples(parseFeatureFlagSemver(String(overrideValue), parsingPolicy), parseFeatureFlagSemver(String(value), parsingPolicy)) < 0;
|
|
236
|
+
case 'semver_lte':
|
|
237
|
+
return compareSemverTuples(parseFeatureFlagSemver(String(overrideValue), parsingPolicy), parseFeatureFlagSemver(String(value), parsingPolicy)) <= 0;
|
|
238
|
+
case 'semver_tilde':
|
|
239
|
+
{
|
|
240
|
+
const overrideParsed = parseFeatureFlagSemver(String(overrideValue), parsingPolicy);
|
|
241
|
+
const { lower, upper } = computeTildeBounds(String(value), parsingPolicy);
|
|
242
|
+
return compareSemverTuples(overrideParsed, lower) >= 0 && compareSemverTuples(overrideParsed, upper) < 0;
|
|
243
|
+
}
|
|
244
|
+
case 'semver_caret':
|
|
245
|
+
{
|
|
246
|
+
const overrideParsed = parseFeatureFlagSemver(String(overrideValue), parsingPolicy);
|
|
247
|
+
const { lower, upper } = computeCaretBounds(String(value), parsingPolicy);
|
|
248
|
+
return compareSemverTuples(overrideParsed, lower) >= 0 && compareSemverTuples(overrideParsed, upper) < 0;
|
|
249
|
+
}
|
|
250
|
+
case 'semver_wildcard':
|
|
251
|
+
{
|
|
252
|
+
const overrideParsed = parseFeatureFlagSemver(String(overrideValue), parsingPolicy);
|
|
253
|
+
const { lower, upper } = computeWildcardBounds(String(value), parsingPolicy);
|
|
254
|
+
return compareSemverTuples(overrideParsed, lower) >= 0 && compareSemverTuples(overrideParsed, upper) < 0;
|
|
255
|
+
}
|
|
256
|
+
default:
|
|
257
|
+
throw new InconclusiveMatchError(`Unknown operator: ${operator}`);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
async function hashSHA1(text) {
|
|
261
|
+
const subtle = globalThis.crypto?.subtle;
|
|
262
|
+
if (!subtle) throw new Error('SubtleCrypto API not available');
|
|
263
|
+
const hashBuffer = await subtle.digest('SHA-1', new TextEncoder().encode(text));
|
|
264
|
+
return Array.from(new Uint8Array(hashBuffer)).map((byte)=>byte.toString(16).padStart(2, '0')).join('');
|
|
265
|
+
}
|
|
266
|
+
async function getFeatureFlagHash(key, bucketingValue, salt = '') {
|
|
267
|
+
const hashString = await hashSHA1(`${key}.${bucketingValue}${salt}`);
|
|
268
|
+
return parseInt(hashString.slice(0, 15), 16) / LONG_SCALE;
|
|
269
|
+
}
|
|
270
|
+
function getFeatureFlagVariantLookupTable(variants) {
|
|
271
|
+
const table = [];
|
|
272
|
+
let valueMin = 0;
|
|
273
|
+
for (const variant of variants){
|
|
274
|
+
const valueMax = valueMin + variant.rollout_percentage / 100.0;
|
|
275
|
+
table.push({
|
|
276
|
+
valueMin,
|
|
277
|
+
valueMax,
|
|
278
|
+
key: variant.key
|
|
279
|
+
});
|
|
280
|
+
valueMin = valueMax;
|
|
281
|
+
}
|
|
282
|
+
return table;
|
|
283
|
+
}
|
|
284
|
+
async function getFeatureFlagVariant(key, bucketingValue, variants) {
|
|
285
|
+
const hashValue = await getFeatureFlagHash(key, bucketingValue, 'variant');
|
|
286
|
+
return getFeatureFlagVariantLookupTable(variants).find((variant)=>hashValue >= variant.valueMin && hashValue < variant.valueMax)?.key;
|
|
287
|
+
}
|
|
288
|
+
function resolveFeatureFlagPayload(payloads, flagValue) {
|
|
289
|
+
if (false === flagValue || null == flagValue || !payloads) return null;
|
|
290
|
+
const payloadKey = 'boolean' == typeof flagValue ? flagValue.toString() : flagValue;
|
|
291
|
+
const payload = payloads[payloadKey] || null;
|
|
292
|
+
return null == payload ? null : parsePayload(payload);
|
|
293
|
+
}
|
|
294
|
+
export { InconclusiveMatchError, getFeatureFlagHash, getFeatureFlagVariant, getFeatureFlagVariantLookupTable, hashSHA1, matchFeatureFlagProperty, parseFeatureFlagSemver, relativeDateParseForFeatureFlagMatching, resolveFeatureFlagPayload };
|