@fluxcontrolsdk/js-sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +260 -0
- package/dist/client.d.ts +45 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/evaluator.d.ts +40 -0
- package/dist/evaluator.d.ts.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.esm.js +452 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.js +465 -0
- package/dist/index.js.map +1 -0
- package/dist/react.d.ts +54 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/types.d.ts +94 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +66 -0
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
import React, { createContext, useState, useEffect, useContext } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Evaluates feature flags against targeting rules.
|
|
5
|
+
*/
|
|
6
|
+
class Evaluator {
|
|
7
|
+
/**
|
|
8
|
+
* Evaluate a flag.
|
|
9
|
+
*/
|
|
10
|
+
evaluate(flagKey, context, ruleset, defaultValue) {
|
|
11
|
+
const flag = ruleset.flags[flagKey];
|
|
12
|
+
if (!flag) {
|
|
13
|
+
return {
|
|
14
|
+
value: defaultValue,
|
|
15
|
+
variationKey: undefined,
|
|
16
|
+
reason: 'flag_not_found',
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
// Evaluate targeting rules
|
|
20
|
+
if (flag.rules) {
|
|
21
|
+
// Sort by priority (lower numbers = higher priority, evaluated first)
|
|
22
|
+
const sortedRules = [...flag.rules].sort((a, b) => {
|
|
23
|
+
const priorityA = a.priority ?? 999;
|
|
24
|
+
const priorityB = b.priority ?? 999;
|
|
25
|
+
return priorityA - priorityB;
|
|
26
|
+
});
|
|
27
|
+
for (const rule of sortedRules) {
|
|
28
|
+
if (this.evaluateConditions(rule.conditions, context, ruleset)) {
|
|
29
|
+
// Rule matched
|
|
30
|
+
if (rule.variationId) {
|
|
31
|
+
return this.serveVariation(flag, rule.variationId, 'rule_match');
|
|
32
|
+
}
|
|
33
|
+
else if (rule.rollout) {
|
|
34
|
+
// Handle boolean flag rollout format: {on: percentage}
|
|
35
|
+
return this.serveBooleanRollout(flag, rule.rollout, context, 'percentage_rollout');
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// No rules matched, return default variation
|
|
41
|
+
return this.serveVariation(flag, flag.defaultVariationId, 'default');
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Evaluate all conditions in a rule.
|
|
45
|
+
*/
|
|
46
|
+
evaluateConditions(conditions, context, ruleset) {
|
|
47
|
+
if (!conditions || conditions.length === 0) {
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
return conditions.every((condition) => this.evaluateCondition(condition, context, ruleset));
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Evaluate a single condition.
|
|
54
|
+
*/
|
|
55
|
+
evaluateCondition(condition, context, ruleset) {
|
|
56
|
+
// Get attribute value from context
|
|
57
|
+
let contextValue;
|
|
58
|
+
if (condition.attribute === 'userId' || condition.attribute === 'user_id') {
|
|
59
|
+
contextValue = context.userId;
|
|
60
|
+
}
|
|
61
|
+
else if (condition.attribute === 'sessionId') {
|
|
62
|
+
contextValue = context.sessionId;
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
contextValue = context.attributes?.[condition.attribute];
|
|
66
|
+
}
|
|
67
|
+
switch (condition.operator) {
|
|
68
|
+
case 'IS':
|
|
69
|
+
return contextValue === condition.value;
|
|
70
|
+
case 'IS_NOT':
|
|
71
|
+
return contextValue !== condition.value;
|
|
72
|
+
case 'CONTAINS':
|
|
73
|
+
return String(contextValue).includes(String(condition.value));
|
|
74
|
+
case 'DOES_NOT_CONTAIN':
|
|
75
|
+
return !String(contextValue).includes(String(condition.value));
|
|
76
|
+
case 'IN':
|
|
77
|
+
// Check if contextValue is in the array of values
|
|
78
|
+
if (Array.isArray(condition.value)) {
|
|
79
|
+
return condition.value.map(String).includes(String(contextValue));
|
|
80
|
+
}
|
|
81
|
+
return String(contextValue) === String(condition.value);
|
|
82
|
+
case 'ONE_OF':
|
|
83
|
+
case 'IS_ONE_OF':
|
|
84
|
+
return condition.values?.includes(contextValue) ?? false;
|
|
85
|
+
case 'NOT_ONE_OF':
|
|
86
|
+
return !condition.values?.includes(contextValue);
|
|
87
|
+
case 'GREATER_THAN':
|
|
88
|
+
return Number(contextValue) > Number(condition.value);
|
|
89
|
+
case 'LESS_THAN':
|
|
90
|
+
return Number(contextValue) < Number(condition.value);
|
|
91
|
+
case 'IN_SEGMENT':
|
|
92
|
+
if (condition.segment_key) {
|
|
93
|
+
const segment = ruleset.segments[condition.segment_key];
|
|
94
|
+
if (segment) {
|
|
95
|
+
return this.evaluateConditions(segment.conditions, context, ruleset);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return false;
|
|
99
|
+
default:
|
|
100
|
+
console.warn(`Unknown operator: ${condition.operator}`);
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Serve a specific variation.
|
|
106
|
+
*/
|
|
107
|
+
serveVariation(flag, variationId, reason) {
|
|
108
|
+
const variation = flag.variations.find((v) => v.id === variationId);
|
|
109
|
+
if (!variation) {
|
|
110
|
+
return {
|
|
111
|
+
value: null,
|
|
112
|
+
variationKey: undefined,
|
|
113
|
+
reason: 'variation_not_found',
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
value: variation.value,
|
|
118
|
+
variationKey: variation.key,
|
|
119
|
+
reason,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Serve variation based on percentage rollout.
|
|
124
|
+
*/
|
|
125
|
+
servePercentageRollout(flag, rollout, context, reason) {
|
|
126
|
+
const userId = context.userId || context.sessionId || 'anonymous';
|
|
127
|
+
const bucket = this.getBucket(userId, flag.key || 'default'); // FIXED: Use flag.key for consistency
|
|
128
|
+
let cumulative = 0;
|
|
129
|
+
for (const item of rollout.variations) {
|
|
130
|
+
cumulative += item.weight;
|
|
131
|
+
if (bucket < cumulative) {
|
|
132
|
+
return {
|
|
133
|
+
value: item.variation.value,
|
|
134
|
+
variationKey: item.variation.key,
|
|
135
|
+
reason,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// Fallback to last variation
|
|
140
|
+
const lastItem = rollout.variations[rollout.variations.length - 1];
|
|
141
|
+
return {
|
|
142
|
+
value: lastItem.variation.value,
|
|
143
|
+
variationKey: lastItem.variation.key,
|
|
144
|
+
reason,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Serve variation based on boolean rollout (simplified format: {on: percentage}).
|
|
149
|
+
* Used for boolean flags where 'on' represents the percentage for 'true'.
|
|
150
|
+
*/
|
|
151
|
+
serveBooleanRollout(flag, rollout, context, reason) {
|
|
152
|
+
const userId = context.userId || context.sessionId || 'anonymous';
|
|
153
|
+
const bucket = this.getBucket(userId, flag.key || 'default'); // FIXED: Use flag.key instead of flag.salt
|
|
154
|
+
const percentage = rollout.on || 0;
|
|
155
|
+
// Bucket is 0-9999, so we need to scale percentage (0-100) to 0-9999
|
|
156
|
+
const threshold = (percentage / 100) * 10000;
|
|
157
|
+
if (bucket < threshold) {
|
|
158
|
+
// User is in the rollout, serve 'true' variation
|
|
159
|
+
const trueVar = flag.variations.find(v => v.value === true);
|
|
160
|
+
if (trueVar) {
|
|
161
|
+
return {
|
|
162
|
+
value: trueVar.value,
|
|
163
|
+
variationKey: trueVar.key,
|
|
164
|
+
reason,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
// User is not in rollout, serve 'false' variation
|
|
169
|
+
const falseVar = flag.variations.find(v => v.value === false);
|
|
170
|
+
if (falseVar) {
|
|
171
|
+
return {
|
|
172
|
+
value: falseVar.value,
|
|
173
|
+
variationKey: falseVar.key,
|
|
174
|
+
reason,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
// Fallback
|
|
178
|
+
return {
|
|
179
|
+
value: false,
|
|
180
|
+
variationKey: undefined,
|
|
181
|
+
reason: 'rollout_fallback',
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Get consistent bucket (0-9999) for user using MD5 hash.
|
|
186
|
+
*/
|
|
187
|
+
/**
|
|
188
|
+
* Get consistent bucket (0-9999) for user using simple hash (DJB2).
|
|
189
|
+
* Format: flagKey:userId (matches Python SDK for cross-SDK consistency)
|
|
190
|
+
*/
|
|
191
|
+
getBucket(userId, flagKey) {
|
|
192
|
+
const input = `${flagKey}:${userId}`; // FIXED: Match Python SDK format
|
|
193
|
+
let hash = 5381;
|
|
194
|
+
for (let i = 0; i < input.length; i++) {
|
|
195
|
+
hash = ((hash << 5) + hash) + input.charCodeAt(i); /* hash * 33 + c */
|
|
196
|
+
}
|
|
197
|
+
return Math.abs(hash) % 10000;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* FluxControl JavaScript SDK (Thick Client)
|
|
203
|
+
*
|
|
204
|
+
* Downloads rulesets and evaluates flags locally.
|
|
205
|
+
*/
|
|
206
|
+
class FluxClient {
|
|
207
|
+
constructor(sdkKey, config = {}) {
|
|
208
|
+
this.ruleset = null;
|
|
209
|
+
this.sdkKey = sdkKey;
|
|
210
|
+
this.config = {
|
|
211
|
+
apiUrl: config.apiUrl || 'http://localhost:8000/api/v1/sdk',
|
|
212
|
+
pollingIntervalMs: config.pollingIntervalMs || 60000,
|
|
213
|
+
...config
|
|
214
|
+
};
|
|
215
|
+
this.evaluator = new Evaluator();
|
|
216
|
+
// Auto-init if in browser
|
|
217
|
+
if (typeof window !== 'undefined') {
|
|
218
|
+
this.init();
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Initialize the SDK (fetch ruleset).
|
|
223
|
+
*/
|
|
224
|
+
async init() {
|
|
225
|
+
await this.fetchRuleset();
|
|
226
|
+
if (this.config.pollingIntervalMs && this.config.pollingIntervalMs > 0) {
|
|
227
|
+
this.pollingInterval = setInterval(() => this.fetchRuleset(), this.config.pollingIntervalMs);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Fetch ruleset from API.
|
|
232
|
+
*/
|
|
233
|
+
async fetchRuleset() {
|
|
234
|
+
try {
|
|
235
|
+
// Environment context is usually passed or inferred.
|
|
236
|
+
// For now, hardcode or assume API defaults.
|
|
237
|
+
const url = `${this.config.apiUrl}/ruleset?environment=production`;
|
|
238
|
+
const res = await fetch(url, {
|
|
239
|
+
headers: {
|
|
240
|
+
'Authorization': `Bearer ${this.sdkKey}`
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
if (res.ok) {
|
|
244
|
+
this.ruleset = await res.json();
|
|
245
|
+
// Optional: Save to localStorage for offline boot
|
|
246
|
+
if (typeof window !== 'undefined') {
|
|
247
|
+
localStorage.setItem(`flux_ruleset_${this.sdkKey}`, JSON.stringify(this.ruleset));
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
console.warn('[FluxClient] Failed to fetch ruleset:', res.status);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
catch (e) {
|
|
255
|
+
console.error('[FluxClient] Error fetching ruleset:', e);
|
|
256
|
+
// Try load from local storage
|
|
257
|
+
if (typeof window !== 'undefined' && !this.ruleset) {
|
|
258
|
+
const stored = localStorage.getItem(`flux_ruleset_${this.sdkKey}`);
|
|
259
|
+
if (stored)
|
|
260
|
+
this.ruleset = JSON.parse(stored);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Evaluate a boolean flag.
|
|
266
|
+
* Note: Sync evaluation now possible if ruleset is loaded!
|
|
267
|
+
* But we keep async signature if needed or make it sync?
|
|
268
|
+
* Legacy SDK was async. Let's make it sync but helpful.
|
|
269
|
+
* Actually, if init() is async, we can't guarantee ruleset is there immediately.
|
|
270
|
+
*/
|
|
271
|
+
boolVariation(flagKey, context, defaultValue) {
|
|
272
|
+
const result = this.evaluate(flagKey, context, defaultValue);
|
|
273
|
+
return !!result;
|
|
274
|
+
}
|
|
275
|
+
stringVariation(flagKey, context, defaultValue) {
|
|
276
|
+
const result = this.evaluate(flagKey, context, defaultValue);
|
|
277
|
+
return String(result);
|
|
278
|
+
}
|
|
279
|
+
numberVariation(flagKey, context, defaultValue) {
|
|
280
|
+
const result = this.evaluate(flagKey, context, defaultValue);
|
|
281
|
+
return Number(result);
|
|
282
|
+
}
|
|
283
|
+
jsonVariation(flagKey, context, defaultValue) {
|
|
284
|
+
return this.evaluate(flagKey, context, defaultValue);
|
|
285
|
+
}
|
|
286
|
+
isEnabled(flagKey, contextOrId, defaultValue = false) {
|
|
287
|
+
// Compatibility helper
|
|
288
|
+
const context = typeof contextOrId === 'string' ? { userId: contextOrId } : contextOrId;
|
|
289
|
+
return this.boolVariation(flagKey, context, defaultValue);
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Internal evaluation.
|
|
293
|
+
*/
|
|
294
|
+
evaluate(flagKey, context, defaultValue) {
|
|
295
|
+
if (!this.ruleset) {
|
|
296
|
+
console.warn(`[FluxClient] Ruleset not loaded. Returning default for ${flagKey}`);
|
|
297
|
+
return defaultValue;
|
|
298
|
+
}
|
|
299
|
+
const result = this.evaluator.evaluate(flagKey, context, this.ruleset, defaultValue);
|
|
300
|
+
// TODO: Queue exposure event logic here
|
|
301
|
+
return result.value;
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Track a custom event.
|
|
305
|
+
*/
|
|
306
|
+
async track(eventName, context, properties, value) {
|
|
307
|
+
try {
|
|
308
|
+
await fetch(`${this.config.apiUrl}/events`, {
|
|
309
|
+
method: 'POST',
|
|
310
|
+
headers: {
|
|
311
|
+
'Content-Type': 'application/json',
|
|
312
|
+
'Authorization': `Bearer ${this.sdkKey}`
|
|
313
|
+
},
|
|
314
|
+
body: JSON.stringify({
|
|
315
|
+
events: [{
|
|
316
|
+
type: 'custom',
|
|
317
|
+
timestamp: new Date().toISOString(),
|
|
318
|
+
eventName,
|
|
319
|
+
context,
|
|
320
|
+
properties,
|
|
321
|
+
value
|
|
322
|
+
}]
|
|
323
|
+
})
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
catch (e) {
|
|
327
|
+
console.error('[FluxClient] Error tracking event:', e);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
close() {
|
|
331
|
+
if (this.pollingInterval)
|
|
332
|
+
clearInterval(this.pollingInterval);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* React hooks for FluxControl.
|
|
338
|
+
*
|
|
339
|
+
* Provides easy integration with React applications.
|
|
340
|
+
*/
|
|
341
|
+
const FluxReactContext = createContext({
|
|
342
|
+
client: null,
|
|
343
|
+
context: {}
|
|
344
|
+
});
|
|
345
|
+
/**
|
|
346
|
+
* Provider component for FluxControl.
|
|
347
|
+
*
|
|
348
|
+
* Wrap your app with this to enable hooks.
|
|
349
|
+
*
|
|
350
|
+
* @example
|
|
351
|
+
* <FluxProvider sdkKey="your-key" context={{ userId: "user-123" }}>
|
|
352
|
+
* <App />
|
|
353
|
+
* </FluxProvider>
|
|
354
|
+
*/
|
|
355
|
+
function FluxProvider({ sdkKey, config, context = {}, children }) {
|
|
356
|
+
const [client] = useState(() => new FluxClient(sdkKey, config));
|
|
357
|
+
useEffect(() => {
|
|
358
|
+
return () => {
|
|
359
|
+
client.close();
|
|
360
|
+
};
|
|
361
|
+
}, [client]);
|
|
362
|
+
return (React.createElement(FluxReactContext.Provider, { value: { client, context } }, children));
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Hook to evaluate a feature flag.
|
|
366
|
+
*
|
|
367
|
+
* @example
|
|
368
|
+
* const checkoutFlow = useFeature('checkout-flow', 'standard');
|
|
369
|
+
* if (checkoutFlow === 'one-click') {
|
|
370
|
+
* return <OneClickCheckout />;
|
|
371
|
+
* }
|
|
372
|
+
*/
|
|
373
|
+
function useFeature(flagKey, defaultValue, contextOverride) {
|
|
374
|
+
const { client, context: providerContext } = useContext(FluxReactContext);
|
|
375
|
+
const [value, setValue] = useState(defaultValue);
|
|
376
|
+
const [loading, setLoading] = useState(true);
|
|
377
|
+
useEffect(() => {
|
|
378
|
+
if (!client) {
|
|
379
|
+
setValue(defaultValue);
|
|
380
|
+
setLoading(false);
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
const context = { ...providerContext, ...contextOverride };
|
|
384
|
+
const evaluate = async () => {
|
|
385
|
+
try {
|
|
386
|
+
let result;
|
|
387
|
+
// Determine type and call appropriate method
|
|
388
|
+
if (typeof defaultValue === 'boolean') {
|
|
389
|
+
result = await client.boolVariation(flagKey, context, defaultValue);
|
|
390
|
+
}
|
|
391
|
+
else if (typeof defaultValue === 'number') {
|
|
392
|
+
result = await client.numberVariation(flagKey, context, defaultValue);
|
|
393
|
+
}
|
|
394
|
+
else if (typeof defaultValue === 'string') {
|
|
395
|
+
result = await client.stringVariation(flagKey, context, defaultValue);
|
|
396
|
+
}
|
|
397
|
+
else {
|
|
398
|
+
result = await client.jsonVariation(flagKey, context, defaultValue);
|
|
399
|
+
}
|
|
400
|
+
setValue(result);
|
|
401
|
+
}
|
|
402
|
+
catch (error) {
|
|
403
|
+
console.error(`FluxControl: Error evaluating ${flagKey}:`, error);
|
|
404
|
+
setValue(defaultValue);
|
|
405
|
+
}
|
|
406
|
+
finally {
|
|
407
|
+
setLoading(false);
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
evaluate();
|
|
411
|
+
}, [client, flagKey, defaultValue, providerContext, contextOverride]);
|
|
412
|
+
return value;
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Hook to get the FluxControl client instance.
|
|
416
|
+
*
|
|
417
|
+
* Use this for advanced scenarios like tracking custom events.
|
|
418
|
+
*
|
|
419
|
+
* @example
|
|
420
|
+
* const client = useFluxClient();
|
|
421
|
+
* client.track('button_clicked', context, { button: 'checkout' });
|
|
422
|
+
*/
|
|
423
|
+
function useFluxClient() {
|
|
424
|
+
const { client } = useContext(FluxReactContext);
|
|
425
|
+
return client;
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Hook to track a custom event.
|
|
429
|
+
*
|
|
430
|
+
* @example
|
|
431
|
+
* const trackPurchase = useTrack();
|
|
432
|
+
* trackPurchase('purchase_completed', { productId: '123' }, 99.99);
|
|
433
|
+
*/
|
|
434
|
+
function useTrack() {
|
|
435
|
+
const { client, context } = useContext(FluxReactContext);
|
|
436
|
+
return (eventName, properties, value, contextOverride) => {
|
|
437
|
+
if (!client) {
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
const finalContext = { ...context, ...contextOverride };
|
|
441
|
+
client.track(eventName, finalContext, properties, value);
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* FluxControl JavaScript SDK
|
|
447
|
+
*
|
|
448
|
+
* @packageDocumentation
|
|
449
|
+
*/
|
|
450
|
+
|
|
451
|
+
export { FluxClient, FluxProvider, FluxClient as default, useFeature, useFluxClient, useTrack };
|
|
452
|
+
//# sourceMappingURL=index.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.esm.js","sources":["../src/evaluator.ts","../src/client.ts","../src/react.tsx","../src/index.ts"],"sourcesContent":["// import { createHash } from 'crypto';\nimport {\n Context,\n Ruleset,\n FlagConfig,\n TargetingRule,\n Condition,\n EvaluationResult,\n Variation,\n PercentageRollout,\n} from './types';\n\n/**\n * Evaluates feature flags against targeting rules.\n */\nexport class Evaluator {\n /**\n * Evaluate a flag.\n */\n evaluate(\n flagKey: string,\n context: Context,\n ruleset: Ruleset,\n defaultValue: any\n ): EvaluationResult {\n const flag = ruleset.flags[flagKey];\n\n if (!flag) {\n return {\n value: defaultValue,\n variationKey: undefined,\n reason: 'flag_not_found',\n };\n }\n\n // Evaluate targeting rules\n if (flag.rules) {\n // Sort by priority (lower numbers = higher priority, evaluated first)\n const sortedRules = [...flag.rules].sort((a: any, b: any) => {\n const priorityA = a.priority ?? 999;\n const priorityB = b.priority ?? 999;\n return priorityA - priorityB;\n });\n\n for (const rule of sortedRules) {\n if (this.evaluateConditions(rule.conditions, context, ruleset)) {\n // Rule matched\n if (rule.variationId) {\n return this.serveVariation(flag, rule.variationId, 'rule_match');\n } else if (rule.rollout) {\n // Handle boolean flag rollout format: {on: percentage}\n return this.serveBooleanRollout(\n flag,\n rule.rollout,\n context,\n 'percentage_rollout'\n );\n }\n }\n }\n }\n\n // No rules matched, return default variation\n return this.serveVariation(flag, flag.defaultVariationId, 'default');\n }\n\n /**\n * Evaluate all conditions in a rule.\n */\n private evaluateConditions(\n conditions: Condition[],\n context: Context,\n ruleset: Ruleset\n ): boolean {\n if (!conditions || conditions.length === 0) {\n return true;\n }\n\n return conditions.every((condition) =>\n this.evaluateCondition(condition, context, ruleset)\n );\n }\n\n /**\n * Evaluate a single condition.\n */\n private evaluateCondition(\n condition: Condition,\n context: Context,\n ruleset: Ruleset\n ): boolean {\n // Get attribute value from context\n let contextValue: any;\n if (condition.attribute === 'userId' || condition.attribute === 'user_id') {\n contextValue = context.userId;\n } else if (condition.attribute === 'sessionId') {\n contextValue = context.sessionId;\n } else {\n contextValue = context.attributes?.[condition.attribute];\n }\n\n switch (condition.operator) {\n case 'IS':\n return contextValue === condition.value;\n case 'IS_NOT':\n return contextValue !== condition.value;\n case 'CONTAINS':\n return String(contextValue).includes(String(condition.value));\n case 'DOES_NOT_CONTAIN':\n return !String(contextValue).includes(String(condition.value));\n case 'IN':\n // Check if contextValue is in the array of values\n if (Array.isArray(condition.value)) {\n return condition.value.map(String).includes(String(contextValue));\n }\n return String(contextValue) === String(condition.value);\n case 'ONE_OF':\n case 'IS_ONE_OF':\n return condition.values?.includes(contextValue) ?? false;\n case 'NOT_ONE_OF':\n return !condition.values?.includes(contextValue);\n case 'GREATER_THAN':\n return Number(contextValue) > Number(condition.value);\n case 'LESS_THAN':\n return Number(contextValue) < Number(condition.value);\n case 'IN_SEGMENT':\n if (condition.segment_key) {\n const segment = ruleset.segments[condition.segment_key];\n if (segment) {\n return this.evaluateConditions(segment.conditions, context, ruleset);\n }\n }\n return false;\n default:\n console.warn(`Unknown operator: ${condition.operator}`);\n return false;\n }\n }\n\n /**\n * Serve a specific variation.\n */\n private serveVariation(\n flag: FlagConfig,\n variationId: string,\n reason: string\n ): EvaluationResult {\n const variation = flag.variations.find((v) => v.id === variationId);\n\n if (!variation) {\n return {\n value: null,\n variationKey: undefined,\n reason: 'variation_not_found',\n };\n }\n\n return {\n value: variation.value,\n variationKey: variation.key,\n reason,\n };\n }\n\n /**\n * Serve variation based on percentage rollout.\n */\n private servePercentageRollout(\n flag: FlagConfig,\n rollout: PercentageRollout,\n context: Context,\n reason: string\n ): EvaluationResult {\n const userId = context.userId || context.sessionId || 'anonymous';\n const bucket = this.getBucket(userId, flag.key || 'default'); // FIXED: Use flag.key for consistency\n\n let cumulative = 0;\n for (const item of rollout.variations) {\n cumulative += item.weight;\n if (bucket < cumulative) {\n return {\n value: item.variation.value,\n variationKey: item.variation.key,\n reason,\n };\n }\n }\n\n // Fallback to last variation\n const lastItem = rollout.variations[rollout.variations.length - 1];\n return {\n value: lastItem.variation.value,\n variationKey: lastItem.variation.key,\n reason,\n };\n }\n\n /**\n * Serve variation based on boolean rollout (simplified format: {on: percentage}).\n * Used for boolean flags where 'on' represents the percentage for 'true'.\n */\n private serveBooleanRollout(\n flag: FlagConfig,\n rollout: { on: number },\n context: Context,\n reason: string\n ): EvaluationResult {\n const userId = context.userId || context.sessionId || 'anonymous';\n const bucket = this.getBucket(userId, flag.key || 'default'); // FIXED: Use flag.key instead of flag.salt\n const percentage = rollout.on || 0;\n\n // Bucket is 0-9999, so we need to scale percentage (0-100) to 0-9999\n const threshold = (percentage / 100) * 10000;\n\n if (bucket < threshold) {\n // User is in the rollout, serve 'true' variation\n const trueVar = flag.variations.find(v => v.value === true);\n if (trueVar) {\n return {\n value: trueVar.value,\n variationKey: trueVar.key,\n reason,\n };\n }\n }\n\n // User is not in rollout, serve 'false' variation\n const falseVar = flag.variations.find(v => v.value === false);\n if (falseVar) {\n return {\n value: falseVar.value,\n variationKey: falseVar.key,\n reason,\n };\n }\n\n // Fallback\n return {\n value: false,\n variationKey: undefined,\n reason: 'rollout_fallback',\n };\n }\n\n /**\n * Get consistent bucket (0-9999) for user using MD5 hash.\n */\n /**\n * Get consistent bucket (0-9999) for user using simple hash (DJB2).\n * Format: flagKey:userId (matches Python SDK for cross-SDK consistency)\n */\n private getBucket(userId: string, flagKey: string): number {\n const input = `${flagKey}:${userId}`; // FIXED: Match Python SDK format\n let hash = 5381;\n for (let i = 0; i < input.length; i++) {\n hash = ((hash << 5) + hash) + input.charCodeAt(i); /* hash * 33 + c */\n }\n return Math.abs(hash) % 10000;\n }\n}\n","import { Evaluator } from './evaluator';\nimport { Context, Config, EvaluationResult, Ruleset } from './types';\nexport { Context, Config, EvaluationResult, Ruleset };\n\n/**\n * FluxControl JavaScript SDK (Thick Client)\n * \n * Downloads rulesets and evaluates flags locally.\n */\nexport class FluxClient {\n private sdkKey: string;\n private config: Config;\n private evaluator: Evaluator;\n private ruleset: Ruleset | null = null;\n private pollingInterval: any;\n\n constructor(sdkKey: string, config: Config = {}) {\n this.sdkKey = sdkKey;\n this.config = {\n apiUrl: config.apiUrl || 'http://localhost:8000/api/v1/sdk',\n pollingIntervalMs: config.pollingIntervalMs || 60000,\n ...config\n };\n this.evaluator = new Evaluator();\n\n // Auto-init if in browser\n if (typeof window !== 'undefined') {\n this.init();\n }\n }\n\n /**\n * Initialize the SDK (fetch ruleset).\n */\n async init(): Promise<void> {\n await this.fetchRuleset();\n\n if (this.config.pollingIntervalMs && this.config.pollingIntervalMs > 0) {\n this.pollingInterval = setInterval(() => this.fetchRuleset(), this.config.pollingIntervalMs);\n }\n }\n\n /**\n * Fetch ruleset from API.\n */\n async fetchRuleset(): Promise<void> {\n try {\n // Environment context is usually passed or inferred.\n // For now, hardcode or assume API defaults.\n const url = `${this.config.apiUrl}/ruleset?environment=production`;\n\n const res = await fetch(url, {\n headers: {\n 'Authorization': `Bearer ${this.sdkKey}`\n }\n });\n\n if (res.ok) {\n this.ruleset = await res.json();\n // Optional: Save to localStorage for offline boot\n if (typeof window !== 'undefined') {\n localStorage.setItem(`flux_ruleset_${this.sdkKey}`, JSON.stringify(this.ruleset));\n }\n } else {\n console.warn('[FluxClient] Failed to fetch ruleset:', res.status);\n }\n } catch (e) {\n console.error('[FluxClient] Error fetching ruleset:', e);\n // Try load from local storage\n if (typeof window !== 'undefined' && !this.ruleset) {\n const stored = localStorage.getItem(`flux_ruleset_${this.sdkKey}`);\n if (stored) this.ruleset = JSON.parse(stored);\n }\n }\n }\n\n /**\n * Evaluate a boolean flag.\n * Note: Sync evaluation now possible if ruleset is loaded!\n * But we keep async signature if needed or make it sync?\n * Legacy SDK was async. Let's make it sync but helpful.\n * Actually, if init() is async, we can't guarantee ruleset is there immediately.\n */\n boolVariation(flagKey: string, context: Context, defaultValue: boolean): boolean {\n const result = this.evaluate(flagKey, context, defaultValue);\n return !!result;\n }\n\n stringVariation(flagKey: string, context: Context, defaultValue: string): string {\n const result = this.evaluate(flagKey, context, defaultValue);\n return String(result);\n }\n\n numberVariation(flagKey: string, context: Context, defaultValue: number): number {\n const result = this.evaluate(flagKey, context, defaultValue);\n return Number(result);\n }\n\n jsonVariation<T = any>(flagKey: string, context: Context, defaultValue: T): T {\n return this.evaluate(flagKey, context, defaultValue) as T;\n }\n\n isEnabled(flagKey: string, contextOrId: Context | string, defaultValue: boolean = false): boolean {\n // Compatibility helper\n const context = typeof contextOrId === 'string' ? { userId: contextOrId } : contextOrId;\n return this.boolVariation(flagKey, context, defaultValue);\n }\n\n /**\n * Internal evaluation.\n */\n private evaluate(flagKey: string, context: Context, defaultValue: any): any {\n if (!this.ruleset) {\n console.warn(`[FluxClient] Ruleset not loaded. Returning default for ${flagKey}`);\n return defaultValue;\n }\n\n const result = this.evaluator.evaluate(flagKey, context, this.ruleset, defaultValue);\n\n // TODO: Queue exposure event logic here\n\n return result.value;\n }\n\n /**\n * Track a custom event.\n */\n async track(eventName: string, context: Context, properties?: Record<string, any>, value?: number): Promise<void> {\n try {\n await fetch(`${this.config.apiUrl}/events`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.sdkKey}`\n },\n body: JSON.stringify({\n events: [{\n type: 'custom',\n timestamp: new Date().toISOString(),\n eventName,\n context,\n properties,\n value\n }]\n })\n });\n } catch (e) {\n console.error('[FluxClient] Error tracking event:', e);\n }\n }\n\n close() {\n if (this.pollingInterval) clearInterval(this.pollingInterval);\n }\n}\n","/**\n * React hooks for FluxControl.\n * \n * Provides easy integration with React applications.\n */\n\nimport React, { createContext, useContext, useEffect, useState, ReactNode } from 'react';\nimport { FluxClient, Context as FluxContext } from './client';\n\ninterface FluxProviderProps {\n sdkKey: string;\n config?: any;\n context?: FluxContext;\n children: ReactNode;\n}\n\ninterface FluxContextType {\n client: FluxClient | null;\n context: FluxContext;\n}\n\nconst FluxReactContext = createContext<FluxContextType>({\n client: null,\n context: {}\n});\n\n/**\n * Provider component for FluxControl.\n * \n * Wrap your app with this to enable hooks.\n * \n * @example\n * <FluxProvider sdkKey=\"your-key\" context={{ userId: \"user-123\" }}>\n * <App />\n * </FluxProvider>\n */\nexport function FluxProvider({ sdkKey, config, context = {}, children }: FluxProviderProps) {\n const [client] = useState(() => new FluxClient(sdkKey, config));\n\n useEffect(() => {\n return () => {\n client.close();\n };\n }, [client]);\n\n return (\n <FluxReactContext.Provider value={{ client, context }}>\n {children}\n </FluxReactContext.Provider>\n );\n}\n\n/**\n * Hook to evaluate a feature flag.\n * \n * @example\n * const checkoutFlow = useFeature('checkout-flow', 'standard');\n * if (checkoutFlow === 'one-click') {\n * return <OneClickCheckout />;\n * }\n */\nexport function useFeature<T = any>(\n flagKey: string,\n defaultValue: T,\n contextOverride?: FluxContext\n): T {\n const { client, context: providerContext } = useContext(FluxReactContext);\n const [value, setValue] = useState<T>(defaultValue);\n const [loading, setLoading] = useState(true);\n\n useEffect(() => {\n if (!client) {\n setValue(defaultValue);\n setLoading(false);\n return;\n }\n\n const context = { ...providerContext, ...contextOverride };\n\n const evaluate = async () => {\n try {\n let result: any;\n\n // Determine type and call appropriate method\n if (typeof defaultValue === 'boolean') {\n result = await client.boolVariation(flagKey, context, defaultValue);\n } else if (typeof defaultValue === 'number') {\n result = await client.numberVariation(flagKey, context, defaultValue);\n } else if (typeof defaultValue === 'string') {\n result = await client.stringVariation(flagKey, context, defaultValue);\n } else {\n result = await client.jsonVariation(flagKey, context, defaultValue);\n }\n\n setValue(result as T);\n } catch (error) {\n console.error(`FluxControl: Error evaluating ${flagKey}:`, error);\n setValue(defaultValue);\n } finally {\n setLoading(false);\n }\n };\n\n evaluate();\n }, [client, flagKey, defaultValue, providerContext, contextOverride]);\n\n return value;\n}\n\n/**\n * Hook to get the FluxControl client instance.\n * \n * Use this for advanced scenarios like tracking custom events.\n * \n * @example\n * const client = useFluxClient();\n * client.track('button_clicked', context, { button: 'checkout' });\n */\nexport function useFluxClient(): FluxClient | null {\n const { client } = useContext(FluxReactContext);\n return client;\n}\n\n/**\n * Hook to track a custom event.\n * \n * @example\n * const trackPurchase = useTrack();\n * trackPurchase('purchase_completed', { productId: '123' }, 99.99);\n */\nexport function useTrack() {\n const { client, context } = useContext(FluxReactContext);\n\n return (\n eventName: string,\n properties?: Record<string, any>,\n value?: number,\n contextOverride?: FluxContext\n ) => {\n if (!client) {\n return;\n }\n\n const finalContext = { ...context, ...contextOverride };\n client.track(eventName, finalContext, properties, value);\n };\n}\n","/**\n * FluxControl JavaScript SDK\n * \n * @packageDocumentation\n */\n\nexport * from './types';\nexport * from './client';\nexport * from './react';\n\nimport { FluxClient } from './client';\nexport default FluxClient;\n"],"names":[],"mappings":"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"}
|