@vercel/flags-core 1.6.0 → 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # @vercel/flags-core
2
2
 
3
+ ## 1.7.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#450](https://github.com/vercel/flags/pull/450) [`ceb1519`](https://github.com/vercel/flags/commit/ceb15198f73a973ac559648b8d978c23d23ef0c5) Thanks [@dferber90](https://github.com/dferber90)! - Strip the `g` and `y` flags from RegEx conditions to prevent cached `RegExp` instances from retaining `lastIndex` state.
8
+
9
+ ## 1.7.0
10
+
11
+ ### Minor Changes
12
+
13
+ - [#427](https://github.com/vercel/flags/pull/427) [`50b1aa4`](https://github.com/vercel/flags/commit/50b1aa4f9614b8e78f76cc9e3ae539e58a46fa7e) Thanks [@dferber90](https://github.com/dferber90)! - Align rollout, split, and segment split user assignment onto a single hash bucketing scheme.
14
+
15
+ Previously splits and rollouts bucketed users with opposite conventions, so switching a flag between a split and a rollout (or locking in a rollout as a split) could reassign users even when the effective distribution was unchanged. All three now derive their cut points from one shared boundary function over the full hash space, so a rollout at a given percentage is identical to the equivalent split.
16
+
17
+ Split assignments are effectively unchanged. Rollouts and segment splits are re-bucketed once with this release; after that, converting a flag between outcome types never reassigns anyone.
18
+
3
19
  ## 1.6.0
4
20
 
5
21
  ### Minor Changes
@@ -47,21 +47,30 @@ var Packed;
47
47
 
48
48
  // src/evaluate.ts
49
49
  var MAX_REGEX_INPUT_LENGTH = 1e4;
50
- var UINT32_MAX = 4294967295;
51
- var scaledWeightsCache = /* @__PURE__ */ new WeakMap();
50
+ var HASH_SPACE = 2 ** 32;
51
+ var PROMILLE_SCALE = 1e5;
52
+ function boundaryFor(numerator, denominator) {
53
+ return Math.floor(numerator / denominator * HASH_SPACE);
54
+ }
55
+ var splitBoundariesCache = /* @__PURE__ */ new WeakMap();
52
56
  var compiledRegexCache = /* @__PURE__ */ new WeakMap();
53
- function getScaledWeights(outcome) {
54
- const cached = scaledWeightsCache.get(outcome);
57
+ function getSplitBoundaries(outcome) {
58
+ const cached = splitBoundariesCache.get(outcome);
55
59
  if (cached) return cached;
56
60
  const total = sum(outcome.weights);
57
- const scaled = outcome.weights.map((w) => w / total * UINT32_MAX);
58
- scaledWeightsCache.set(outcome, scaled);
59
- return scaled;
61
+ const boundaries = [];
62
+ let cumulative = 0;
63
+ for (const weight of outcome.weights) {
64
+ cumulative += weight;
65
+ boundaries.push(boundaryFor(cumulative, total));
66
+ }
67
+ splitBoundariesCache.set(outcome, boundaries);
68
+ return boundaries;
60
69
  }
61
70
  function getCompiledRegex(rhs) {
62
71
  const cached = compiledRegexCache.get(rhs);
63
72
  if (cached) return cached;
64
- const compiled = new RegExp(rhs.pattern, rhs.flags);
73
+ const compiled = new RegExp(rhs.pattern, rhs.flags.replace(/[gy]/, ""));
65
74
  compiledRegexCache.set(rhs, compiled);
66
75
  return compiled;
67
76
  }
@@ -291,11 +300,10 @@ function handleSegmentOutcome(params, outcome) {
291
300
  case "split": {
292
301
  const lhs = access(outcome.base, params);
293
302
  if (typeof lhs !== "string") return false;
294
- const maxValue = 1e5;
295
303
  if (outcome.passPromille <= 0) return false;
296
- if (outcome.passPromille >= maxValue) return true;
297
- const value = hashInput(lhs, params.definition.seed) % maxValue;
298
- return value < outcome.passPromille;
304
+ if (outcome.passPromille >= PROMILLE_SCALE) return true;
305
+ const bucket = hashInput(lhs, params.definition.seed);
306
+ return bucket < boundaryFor(outcome.passPromille, PROMILLE_SCALE);
299
307
  }
300
308
  default: {
301
309
  const { type } = outcome;
@@ -340,12 +348,18 @@ function handleOutcome(params, outcome) {
340
348
  outcomeType: "split" /* SPLIT */
341
349
  };
342
350
  }
343
- const value = hashInput(lhs, params.definition.seed);
344
- const scaledWeights = getScaledWeights(outcome);
345
- const variantIndex = findWeightedIndex(scaledWeights, value, UINT32_MAX);
346
- const variant = variantIndex === -1 ? defaultOutcome : getVariant(params.definition, variantIndex);
351
+ const bucket = hashInput(lhs, params.definition.seed);
352
+ const boundaries = getSplitBoundaries(outcome);
353
+ for (let index = 0; index < boundaries.length; index++) {
354
+ if (bucket < boundaries[index]) {
355
+ return {
356
+ ...getVariant(params.definition, index),
357
+ outcomeType: "split" /* SPLIT */
358
+ };
359
+ }
360
+ }
347
361
  return {
348
- ...variant,
362
+ ...defaultOutcome,
349
363
  outcomeType: "split" /* SPLIT */
350
364
  };
351
365
  }
@@ -381,7 +395,7 @@ function handleOutcome(params, outcome) {
381
395
  break;
382
396
  }
383
397
  }
384
- if (exhausted) currentPromille = 1e5;
398
+ if (exhausted) currentPromille = PROMILLE_SCALE;
385
399
  if (currentPromille <= 0) {
386
400
  return {
387
401
  ...rollFromVariant,
@@ -392,17 +406,26 @@ function handleOutcome(params, outcome) {
392
406
  params.definition,
393
407
  outcome.rollToVariant
394
408
  );
395
- if (currentPromille >= 1e5) {
409
+ if (currentPromille >= PROMILLE_SCALE) {
396
410
  return {
397
411
  ...rollToVariant,
398
412
  outcomeType: "rollout" /* ROLLOUT */
399
413
  };
400
414
  }
401
- const value = hashInput(lhs, params.definition.seed);
402
- const threshold = currentPromille / 1e5 * UINT32_MAX;
403
- const variant = value < threshold ? rollToVariant : rollFromVariant;
415
+ const bucket = hashInput(lhs, params.definition.seed);
416
+ if (outcome.rollToVariant < outcome.rollFromVariant) {
417
+ const rollToBoundary = boundaryFor(currentPromille, PROMILLE_SCALE);
418
+ return {
419
+ ...bucket < rollToBoundary ? rollToVariant : rollFromVariant,
420
+ outcomeType: "rollout" /* ROLLOUT */
421
+ };
422
+ }
423
+ const rollFromBoundary = boundaryFor(
424
+ PROMILLE_SCALE - currentPromille,
425
+ PROMILLE_SCALE
426
+ );
404
427
  return {
405
- ...variant,
428
+ ...bucket < rollFromBoundary ? rollFromVariant : rollToVariant,
406
429
  outcomeType: "rollout" /* ROLLOUT */
407
430
  };
408
431
  }
@@ -483,18 +506,9 @@ function bulkEvaluate(flags, shared) {
483
506
  }
484
507
  return results;
485
508
  }
486
- function findWeightedIndex(weights, value, maxValue) {
487
- if (value < 0 || value >= maxValue) return -1;
488
- let sum2 = 0;
489
- for (let i = 0; i < weights.length; i++) {
490
- sum2 += weights[i];
491
- if (value < sum2) return i;
492
- }
493
- return -1;
494
- }
495
509
 
496
510
  // package.json
497
- var version = "1.6.0";
511
+ var version = "1.7.1";
498
512
 
499
513
  // src/lib/report-value.ts
500
514
  function internalReportValue(key, value, data) {
@@ -971,6 +985,7 @@ var Scheduler = class {
971
985
  constructor(onFlush) {
972
986
  this.onFlush = onFlush;
973
987
  }
988
+ onFlush;
974
989
  resolveWait = null;
975
990
  pending = null;
976
991
  idleTimeout = null;
@@ -1222,6 +1237,7 @@ var BundledSource = class {
1222
1237
  constructor(options) {
1223
1238
  this.options = options;
1224
1239
  }
1240
+ options;
1225
1241
  promise;
1226
1242
  /**
1227
1243
  * Load bundled definitions.
@@ -2480,4 +2496,4 @@ export {
2480
2496
  resetDefaultFlagsClient,
2481
2497
  createClient
2482
2498
  };
2483
- //# sourceMappingURL=chunk-FBTDSIHP.js.map
2499
+ //# sourceMappingURL=chunk-PLRBLION.js.map