@vercel/flags-core 1.8.0 → 1.8.1-27681ef-20260909081650
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 +24 -0
- package/README.md +2 -0
- package/dist/{chunk-ZV3PRKK2.js → chunk-2QUTYVSI.js} +208 -34
- package/dist/chunk-2QUTYVSI.js.map +1 -0
- package/dist/{chunk-C3FUDFOP.cjs → chunk-M6WI7BIA.cjs} +243 -69
- package/dist/chunk-M6WI7BIA.cjs.map +1 -0
- package/dist/{chunk-ZROGU3V7.js → chunk-UJEICRIP.js} +208 -34
- package/dist/chunk-UJEICRIP.js.map +1 -0
- package/dist/{chunk-ID4ZKPA3.cjs → chunk-XXXZP5SZ.cjs} +244 -70
- package/dist/chunk-XXXZP5SZ.cjs.map +1 -0
- package/dist/index.default.cjs +2 -2
- package/dist/index.default.d.cts +14 -6
- package/dist/index.default.d.ts +14 -6
- package/dist/index.default.js +1 -1
- package/dist/index.next-js.cjs +2 -2
- package/dist/index.next-js.d.cts +14 -6
- package/dist/index.next-js.d.ts +14 -6
- package/dist/index.next-js.js +1 -1
- package/dist/openfeature.default.cjs +2 -2
- package/dist/openfeature.default.d.cts +1 -1
- package/dist/openfeature.default.d.ts +1 -1
- package/dist/openfeature.default.js +1 -1
- package/dist/openfeature.next-js.cjs +2 -2
- package/dist/openfeature.next-js.d.cts +1 -1
- package/dist/openfeature.next-js.d.ts +1 -1
- package/dist/openfeature.next-js.js +1 -1
- package/dist/{types-C2-sIv2D.d.cts → types-CIONStRG.d.cts} +87 -5
- package/dist/{types-C2-sIv2D.d.ts → types-CIONStRG.d.ts} +87 -5
- package/package.json +2 -1
- package/dist/chunk-C3FUDFOP.cjs.map +0 -1
- package/dist/chunk-ID4ZKPA3.cjs.map +0 -1
- package/dist/chunk-ZROGU3V7.js.map +0 -1
- package/dist/chunk-ZV3PRKK2.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# @vercel/flags-core
|
|
2
2
|
|
|
3
|
+
## 1.8.1-27681ef-20260909081650
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#486](https://github.com/vercel/flags/pull/486) [`c9d2811`](https://github.com/vercel/flags/commit/c9d28116ebca661f4e3c73f301d4b2d35310c823) Thanks [@dferber90](https://github.com/dferber90)! - Add APIs for reporting flag exposures and override values.
|
|
8
|
+
|
|
9
|
+
- The `experimental_reportExposures` client option for supplying an exposure
|
|
10
|
+
reporter.
|
|
11
|
+
- The `experimental_reportOverride` client method for reporting values set by
|
|
12
|
+
the Flags SDK override cookie.
|
|
13
|
+
- The `experimental_exposureLogging` option on `evaluate()` and
|
|
14
|
+
`bulkEvaluate()` for disabling exposure reporting for an individual call.
|
|
15
|
+
- Experiment assignment metadata on `EvaluationResult.experiment`.
|
|
16
|
+
- The `experimental_EvaluationOptions`,
|
|
17
|
+
`experimental_ExperimentAssignment`, `experimental_Exposure`, and
|
|
18
|
+
`experimental_ReportExposures` types.
|
|
19
|
+
|
|
20
|
+
These APIs are not supported for general use yet. Do not use them unless
|
|
21
|
+
Vercel has explicitly enabled them for you.
|
|
22
|
+
|
|
23
|
+
- [#494](https://github.com/vercel/flags/pull/494) [`873d051`](https://github.com/vercel/flags/commit/873d05196e9e0fa5c251f745cb67c067ab890337) Thanks [@luismeyer](https://github.com/luismeyer)! - Request an uncompressed `/v1/stream` body when running on Bun.
|
|
24
|
+
|
|
25
|
+
Bun's `fetch` negotiates brotli or gzip by default, but its streaming decoder withholds small decoded output until more compressed input arrives. The stream's first datafile is followed by silence until the next ping, so on Bun the initial datafile never surfaced, init timed out, and every flag fell back to its default. Sending `Accept-Encoding: identity` on Bun avoids the decoder entirely; other runtimes are unchanged.
|
|
26
|
+
|
|
3
27
|
## 1.8.0
|
|
4
28
|
|
|
5
29
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -312,7 +312,43 @@ function getVariant(definition, index) {
|
|
|
312
312
|
variantId
|
|
313
313
|
};
|
|
314
314
|
}
|
|
315
|
-
function
|
|
315
|
+
function getWeightedVariantIndex(params, assignment, seed) {
|
|
316
|
+
const lhs = access(assignment.base, params);
|
|
317
|
+
if (typeof lhs !== "string") return assignment.defaultVariant;
|
|
318
|
+
const bucket = hashInput(lhs, seed);
|
|
319
|
+
const boundaries = getSplitBoundaries(assignment);
|
|
320
|
+
for (let index = 0; index < boundaries.length; index++) {
|
|
321
|
+
if (bucket < boundaries[index]) return index;
|
|
322
|
+
}
|
|
323
|
+
return assignment.defaultVariant;
|
|
324
|
+
}
|
|
325
|
+
function experimentAssignment(experiment, variantId, assignmentReason) {
|
|
326
|
+
if (variantId === null) return void 0;
|
|
327
|
+
return {
|
|
328
|
+
id: experiment.id,
|
|
329
|
+
variantId,
|
|
330
|
+
base: experiment.base,
|
|
331
|
+
rampId: experiment.rampId,
|
|
332
|
+
rampPercentage: experiment.rampPercentage,
|
|
333
|
+
assignmentReason
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
function outcomeAssignmentReason(outcome) {
|
|
337
|
+
if (typeof outcome === "number") return "variant";
|
|
338
|
+
switch (outcome.type) {
|
|
339
|
+
case "experiment":
|
|
340
|
+
return "experiment";
|
|
341
|
+
case "split":
|
|
342
|
+
return "split";
|
|
343
|
+
case "rollout":
|
|
344
|
+
return "rollout";
|
|
345
|
+
default: {
|
|
346
|
+
const { type } = outcome;
|
|
347
|
+
return exhaustivenessCheck(type);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
function resolveOutcome(params, outcome) {
|
|
316
352
|
if (typeof outcome === "number") {
|
|
317
353
|
const variant = getVariant(params.definition, outcome);
|
|
318
354
|
return {
|
|
@@ -322,32 +358,48 @@ function handleOutcome(params, outcome) {
|
|
|
322
358
|
}
|
|
323
359
|
switch (outcome.type) {
|
|
324
360
|
case "split": {
|
|
325
|
-
const
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
361
|
+
const index = getWeightedVariantIndex(
|
|
362
|
+
params,
|
|
363
|
+
outcome,
|
|
364
|
+
params.definition.seed
|
|
329
365
|
);
|
|
330
|
-
if (typeof lhs !== "string") {
|
|
331
|
-
return {
|
|
332
|
-
...defaultOutcome,
|
|
333
|
-
outcomeType: "split" /* SPLIT */
|
|
334
|
-
};
|
|
335
|
-
}
|
|
336
|
-
const bucket = hashInput(lhs, params.definition.seed);
|
|
337
|
-
const boundaries = getSplitBoundaries(outcome);
|
|
338
|
-
for (let index = 0; index < boundaries.length; index++) {
|
|
339
|
-
if (bucket < boundaries[index]) {
|
|
340
|
-
return {
|
|
341
|
-
...getVariant(params.definition, index),
|
|
342
|
-
outcomeType: "split" /* SPLIT */
|
|
343
|
-
};
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
366
|
return {
|
|
347
|
-
...
|
|
367
|
+
...getVariant(params.definition, index),
|
|
348
368
|
outcomeType: "split" /* SPLIT */
|
|
349
369
|
};
|
|
350
370
|
}
|
|
371
|
+
case "experiment": {
|
|
372
|
+
const experiment = params.definition.experiment;
|
|
373
|
+
if (!experiment) {
|
|
374
|
+
throw new Error("@vercel/flags-core: Experiment not found");
|
|
375
|
+
}
|
|
376
|
+
const unitValue = access(experiment.base, params);
|
|
377
|
+
const defaultVariant = getVariant(
|
|
378
|
+
params.definition,
|
|
379
|
+
experiment.defaultVariant
|
|
380
|
+
);
|
|
381
|
+
const assignment = (variant, assignmentReason) => ({
|
|
382
|
+
...variant,
|
|
383
|
+
outcomeType: "experiment" /* EXPERIMENT */,
|
|
384
|
+
experiment: experimentAssignment(
|
|
385
|
+
experiment,
|
|
386
|
+
variant.variantId,
|
|
387
|
+
assignmentReason
|
|
388
|
+
)
|
|
389
|
+
});
|
|
390
|
+
if (typeof unitValue !== "string") {
|
|
391
|
+
return assignment(defaultVariant, "not-enrolled");
|
|
392
|
+
}
|
|
393
|
+
const rampPercentage = experiment.rampPercentage ?? 100;
|
|
394
|
+
const enrolled = rampPercentage >= 100 || rampPercentage > 0 && hashInput(unitValue, experiment.enrollmentSeed) < boundaryFor(rampPercentage, 100);
|
|
395
|
+
if (!enrolled) return assignment(defaultVariant, "not-enrolled");
|
|
396
|
+
const index = getWeightedVariantIndex(
|
|
397
|
+
params,
|
|
398
|
+
experiment,
|
|
399
|
+
params.definition.seed
|
|
400
|
+
);
|
|
401
|
+
return assignment(getVariant(params.definition, index), "experiment");
|
|
402
|
+
}
|
|
351
403
|
case "rollout": {
|
|
352
404
|
const lhs = access(outcome.base, params);
|
|
353
405
|
const defaultOutcome = getVariant(
|
|
@@ -420,6 +472,19 @@ function handleOutcome(params, outcome) {
|
|
|
420
472
|
}
|
|
421
473
|
}
|
|
422
474
|
}
|
|
475
|
+
function handleOutcome(params, outcome, assignmentReason) {
|
|
476
|
+
const result = resolveOutcome(params, outcome);
|
|
477
|
+
const experiment = params.definition.experiment;
|
|
478
|
+
if (!experiment || result.experiment) return result;
|
|
479
|
+
return {
|
|
480
|
+
...result,
|
|
481
|
+
experiment: experimentAssignment(
|
|
482
|
+
experiment,
|
|
483
|
+
result.variantId,
|
|
484
|
+
assignmentReason ?? outcomeAssignmentReason(outcome)
|
|
485
|
+
)
|
|
486
|
+
};
|
|
487
|
+
}
|
|
423
488
|
function evaluate(params, _visited) {
|
|
424
489
|
const envConfig = params.definition.environments[params.environment];
|
|
425
490
|
if (typeof envConfig === "number") {
|
|
@@ -459,7 +524,7 @@ function evaluate(params, _visited) {
|
|
|
459
524
|
(targetList) => matchTargetList(targetList, params)
|
|
460
525
|
);
|
|
461
526
|
if (matchedIndex > -1) {
|
|
462
|
-
return Object.assign(handleOutcome(params, matchedIndex), {
|
|
527
|
+
return Object.assign(handleOutcome(params, matchedIndex, "targeted"), {
|
|
463
528
|
reason: "target_match" /* TARGET_MATCH */
|
|
464
529
|
});
|
|
465
530
|
}
|
|
@@ -493,7 +558,7 @@ function bulkEvaluate(flags, shared) {
|
|
|
493
558
|
}
|
|
494
559
|
|
|
495
560
|
// package.json
|
|
496
|
-
var version = "1.8.
|
|
561
|
+
var version = "1.8.1-27681ef-20260909081650";
|
|
497
562
|
|
|
498
563
|
// src/lib/report-value.ts
|
|
499
564
|
function internalReportValue(key, value, data) {
|
|
@@ -712,6 +777,8 @@ async function bulkEvaluate2(id, flags, entities) {
|
|
|
712
777
|
}
|
|
713
778
|
|
|
714
779
|
// src/create-raw-client.ts
|
|
780
|
+
import { waitUntil } from "@vercel/functions";
|
|
781
|
+
import { dequal } from "dequal/lite";
|
|
715
782
|
var idCount = 0;
|
|
716
783
|
async function performInitialize(instance, initFn) {
|
|
717
784
|
try {
|
|
@@ -725,7 +792,8 @@ async function performInitialize(instance, initFn) {
|
|
|
725
792
|
function createCreateRawClient(fns) {
|
|
726
793
|
return function createRawClient({
|
|
727
794
|
controller,
|
|
728
|
-
origin
|
|
795
|
+
origin,
|
|
796
|
+
experimental_reportExposures
|
|
729
797
|
}) {
|
|
730
798
|
const id = idCount++;
|
|
731
799
|
controllerInstanceMap.set(id, {
|
|
@@ -733,6 +801,35 @@ function createCreateRawClient(fns) {
|
|
|
733
801
|
initialized: false,
|
|
734
802
|
initPromise: null
|
|
735
803
|
});
|
|
804
|
+
function report(exposures, entity) {
|
|
805
|
+
if (!experimental_reportExposures || exposures.length === 0) return;
|
|
806
|
+
const pending = (async () => {
|
|
807
|
+
try {
|
|
808
|
+
await experimental_reportExposures(exposures, entity);
|
|
809
|
+
} catch (error) {
|
|
810
|
+
console.error(
|
|
811
|
+
"@vercel/flags-core: Failed to report experiment exposures",
|
|
812
|
+
error
|
|
813
|
+
);
|
|
814
|
+
}
|
|
815
|
+
})();
|
|
816
|
+
try {
|
|
817
|
+
waitUntil(pending);
|
|
818
|
+
} catch {
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
function getExposure(flagKey, result) {
|
|
822
|
+
if (!result.experiment) return null;
|
|
823
|
+
return {
|
|
824
|
+
flagKey,
|
|
825
|
+
experimentId: result.experiment.id,
|
|
826
|
+
variantId: result.experiment.variantId,
|
|
827
|
+
base: result.experiment.base,
|
|
828
|
+
...result.experiment.rampId === void 0 ? {} : { rampId: result.experiment.rampId },
|
|
829
|
+
...result.experiment.rampPercentage === void 0 ? {} : { rampPercentage: result.experiment.rampPercentage },
|
|
830
|
+
assignmentReason: result.experiment.assignmentReason
|
|
831
|
+
};
|
|
832
|
+
}
|
|
736
833
|
const api = {
|
|
737
834
|
origin,
|
|
738
835
|
initialize: async () => {
|
|
@@ -767,7 +864,7 @@ function createCreateRawClient(fns) {
|
|
|
767
864
|
getFallbackDatafile: () => {
|
|
768
865
|
return fns.getFallbackDatafile(id);
|
|
769
866
|
},
|
|
770
|
-
evaluate: async (flagKey, defaultValue, entities) => {
|
|
867
|
+
evaluate: async (flagKey, defaultValue, entities, options) => {
|
|
771
868
|
const instance = controllerInstanceMap.get(id);
|
|
772
869
|
if (!instance?.initialized) {
|
|
773
870
|
try {
|
|
@@ -775,9 +872,22 @@ function createCreateRawClient(fns) {
|
|
|
775
872
|
} catch {
|
|
776
873
|
}
|
|
777
874
|
}
|
|
778
|
-
|
|
875
|
+
const entity = entities ?? {};
|
|
876
|
+
const result = await fns.evaluate(
|
|
877
|
+
id,
|
|
878
|
+
flagKey,
|
|
879
|
+
defaultValue,
|
|
880
|
+
entity
|
|
881
|
+
);
|
|
882
|
+
if (experimental_reportExposures && options?.experimental_exposureLogging !== false) {
|
|
883
|
+
const exposure = getExposure(flagKey, result);
|
|
884
|
+
if (exposure) {
|
|
885
|
+
report([exposure], entity);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
return result;
|
|
779
889
|
},
|
|
780
|
-
bulkEvaluate: async (flags, entities) => {
|
|
890
|
+
bulkEvaluate: async (flags, entities, options) => {
|
|
781
891
|
const instance = controllerInstanceMap.get(id);
|
|
782
892
|
if (!instance?.initialized) {
|
|
783
893
|
try {
|
|
@@ -785,7 +895,61 @@ function createCreateRawClient(fns) {
|
|
|
785
895
|
} catch {
|
|
786
896
|
}
|
|
787
897
|
}
|
|
788
|
-
|
|
898
|
+
const entity = entities ?? {};
|
|
899
|
+
const results = await fns.bulkEvaluate(id, flags, entity);
|
|
900
|
+
if (experimental_reportExposures && options?.experimental_exposureLogging !== false) {
|
|
901
|
+
const exposures = [];
|
|
902
|
+
const seen = /* @__PURE__ */ new Set();
|
|
903
|
+
for (const flag of flags) {
|
|
904
|
+
if (seen.has(flag.key)) continue;
|
|
905
|
+
seen.add(flag.key);
|
|
906
|
+
const result = results[flag.key];
|
|
907
|
+
if (!result) continue;
|
|
908
|
+
const exposure = getExposure(flag.key, result);
|
|
909
|
+
if (exposure) exposures.push(exposure);
|
|
910
|
+
}
|
|
911
|
+
report(exposures, entity);
|
|
912
|
+
}
|
|
913
|
+
return results;
|
|
914
|
+
},
|
|
915
|
+
experimental_reportOverride: async ({
|
|
916
|
+
key,
|
|
917
|
+
value,
|
|
918
|
+
entities
|
|
919
|
+
}) => {
|
|
920
|
+
if (!experimental_reportExposures) return;
|
|
921
|
+
try {
|
|
922
|
+
const instance = controllerInstanceMap.get(id);
|
|
923
|
+
if (!instance?.initialized) await api.initialize();
|
|
924
|
+
const datafile = await fns.getDatafile(id);
|
|
925
|
+
const definition = datafile.definitions[key];
|
|
926
|
+
const experiment = definition?.experiment;
|
|
927
|
+
if (!experiment) return;
|
|
928
|
+
const variantIndex = definition.variants.findIndex(
|
|
929
|
+
(variant) => dequal(variant, value)
|
|
930
|
+
);
|
|
931
|
+
const variantId = variantIndex < 0 ? null : definition.variantIds?.[variantIndex] ?? null;
|
|
932
|
+
const entity = entities ?? {};
|
|
933
|
+
report(
|
|
934
|
+
[
|
|
935
|
+
{
|
|
936
|
+
flagKey: key,
|
|
937
|
+
experimentId: experiment.id,
|
|
938
|
+
variantId,
|
|
939
|
+
base: experiment.base,
|
|
940
|
+
rampId: experiment.rampId,
|
|
941
|
+
rampPercentage: experiment.rampPercentage,
|
|
942
|
+
assignmentReason: "override"
|
|
943
|
+
}
|
|
944
|
+
],
|
|
945
|
+
entity
|
|
946
|
+
);
|
|
947
|
+
} catch (error) {
|
|
948
|
+
console.error(
|
|
949
|
+
"@vercel/flags-core: Failed to report experiment override",
|
|
950
|
+
error
|
|
951
|
+
);
|
|
952
|
+
}
|
|
789
953
|
}
|
|
790
954
|
};
|
|
791
955
|
return api;
|
|
@@ -964,7 +1128,7 @@ function getRequestContext() {
|
|
|
964
1128
|
}
|
|
965
1129
|
|
|
966
1130
|
// src/utils/scheduler.ts
|
|
967
|
-
import { waitUntil } from "@vercel/functions";
|
|
1131
|
+
import { waitUntil as waitUntil2 } from "@vercel/functions";
|
|
968
1132
|
var IDLE_FLUSH_WAIT_MS = 5e3;
|
|
969
1133
|
var IDLE_FLUSH_JITTER_RATIO = 0.2;
|
|
970
1134
|
var MAX_FLUSH_WAIT_MS = 6e4;
|
|
@@ -987,7 +1151,7 @@ var Scheduler = class {
|
|
|
987
1151
|
await this.onFlush(reason);
|
|
988
1152
|
})();
|
|
989
1153
|
try {
|
|
990
|
-
|
|
1154
|
+
waitUntil2(this.pending);
|
|
991
1155
|
} catch {
|
|
992
1156
|
}
|
|
993
1157
|
this.maxTimeout = setTimeout(
|
|
@@ -1442,6 +1606,11 @@ var PollingSource = class extends TypedEmitter {
|
|
|
1442
1606
|
}
|
|
1443
1607
|
};
|
|
1444
1608
|
|
|
1609
|
+
// src/utils/runtime.ts
|
|
1610
|
+
function isBun() {
|
|
1611
|
+
return typeof globalThis.Bun !== "undefined";
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1445
1614
|
// src/utils/sleep.ts
|
|
1446
1615
|
function sleep(ms) {
|
|
1447
1616
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -1526,6 +1695,9 @@ async function connectStream(config, callbacks) {
|
|
|
1526
1695
|
"User-Agent": `VercelFlagsCore/${version}`,
|
|
1527
1696
|
"X-Retry-Attempt": String(retryCount)
|
|
1528
1697
|
};
|
|
1698
|
+
if (isBun()) {
|
|
1699
|
+
headers["Accept-Encoding"] = "identity";
|
|
1700
|
+
}
|
|
1529
1701
|
const vercelEnv = process.env.VERCEL_ENV;
|
|
1530
1702
|
if (vercelEnv) {
|
|
1531
1703
|
headers["X-Vercel-Env"] = vercelEnv;
|
|
@@ -2430,11 +2602,13 @@ function make(createRawClient) {
|
|
|
2430
2602
|
const optionsOnly = typeof sdkKeyOrConnectionStringOrOptions === "object" && sdkKeyOrConnectionStringOrOptions !== null;
|
|
2431
2603
|
const sdkKeyOrConnectionString = optionsOnly ? void 0 : sdkKeyOrConnectionStringOrOptions;
|
|
2432
2604
|
const createClientOptions = optionsOnly ? sdkKeyOrConnectionStringOrOptions : options;
|
|
2605
|
+
const { experimental_reportExposures, ...controllerOptions } = createClientOptions ?? {};
|
|
2433
2606
|
const auth = new Authentication(sdkKeyOrConnectionString);
|
|
2434
|
-
const controller = new Controller({ auth, ...
|
|
2607
|
+
const controller = new Controller({ auth, ...controllerOptions });
|
|
2435
2608
|
return createRawClient({
|
|
2436
2609
|
controller,
|
|
2437
|
-
origin: { provider: "vercel", sdkKey: auth.sdkKey }
|
|
2610
|
+
origin: { provider: "vercel", sdkKey: auth.sdkKey },
|
|
2611
|
+
...experimental_reportExposures ? { experimental_reportExposures } : {}
|
|
2438
2612
|
});
|
|
2439
2613
|
}
|
|
2440
2614
|
function resetDefaultFlagsClient2() {
|
|
@@ -2509,4 +2683,4 @@ export {
|
|
|
2509
2683
|
resetDefaultFlagsClient,
|
|
2510
2684
|
createClient
|
|
2511
2685
|
};
|
|
2512
|
-
//# sourceMappingURL=chunk-
|
|
2686
|
+
//# sourceMappingURL=chunk-2QUTYVSI.js.map
|