@vercel/flags-core 1.5.0 → 1.6.0-7d29945-20260616152249
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 +6 -0
- package/dist/{chunk-CSSU7O2G.cjs → chunk-LPIUDLYE.cjs} +484 -246
- package/dist/chunk-LPIUDLYE.cjs.map +1 -0
- package/dist/{chunk-K3VOC24J.cjs → chunk-NHWFZTK2.cjs} +483 -245
- package/dist/chunk-NHWFZTK2.cjs.map +1 -0
- package/dist/{chunk-WTGXBSWR.js → chunk-PSYHUXJC.js} +429 -191
- package/dist/chunk-PSYHUXJC.js.map +1 -0
- package/dist/{chunk-D3AZORK2.js → chunk-S62BO7JU.js} +429 -191
- package/dist/chunk-S62BO7JU.js.map +1 -0
- package/dist/index.default.cjs +2 -2
- package/dist/index.default.d.cts +18 -3
- package/dist/index.default.d.ts +18 -3
- package/dist/index.default.js +1 -1
- package/dist/index.next-js.cjs +2 -2
- package/dist/index.next-js.d.cts +18 -3
- package/dist/index.next-js.d.ts +18 -3
- package/dist/index.next-js.js +1 -1
- package/dist/openfeature.default.cjs +2 -2
- package/dist/openfeature.default.js +1 -1
- package/dist/openfeature.next-js.cjs +2 -2
- package/dist/openfeature.next-js.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-CSSU7O2G.cjs.map +0 -1
- package/dist/chunk-D3AZORK2.js.map +0 -1
- package/dist/chunk-K3VOC24J.cjs.map +0 -1
- package/dist/chunk-WTGXBSWR.js.map +0 -1
|
@@ -50,6 +50,22 @@ var MAX_REGEX_INPUT_LENGTH = 1e4;
|
|
|
50
50
|
var UINT32_MAX = 4294967295;
|
|
51
51
|
var SCALED_WEIGHTS = /* @__PURE__ */ Symbol("@vercel/flags-core:scaledWeights");
|
|
52
52
|
var COMPILED_REGEX = /* @__PURE__ */ Symbol("@vercel/flags-core:compiledRegex");
|
|
53
|
+
var EVALUATION_VARIANT_INDEX = /* @__PURE__ */ Symbol(
|
|
54
|
+
"@vercel/flags-core:evaluationVariantIndex"
|
|
55
|
+
);
|
|
56
|
+
function withVariantIndex(value, outcomeType, variantIndex) {
|
|
57
|
+
return Object.defineProperty(
|
|
58
|
+
{
|
|
59
|
+
value,
|
|
60
|
+
outcomeType
|
|
61
|
+
},
|
|
62
|
+
EVALUATION_VARIANT_INDEX,
|
|
63
|
+
{ value: variantIndex }
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
function getEvaluationVariantIndex(result) {
|
|
67
|
+
return result[EVALUATION_VARIANT_INDEX];
|
|
68
|
+
}
|
|
53
69
|
function getScaledWeights(outcome) {
|
|
54
70
|
const cached = outcome[SCALED_WEIGHTS];
|
|
55
71
|
if (cached) return cached;
|
|
@@ -313,10 +329,11 @@ function getVariant(variants, index) {
|
|
|
313
329
|
}
|
|
314
330
|
function handleOutcome(params, outcome) {
|
|
315
331
|
if (typeof outcome === "number") {
|
|
316
|
-
return
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
332
|
+
return withVariantIndex(
|
|
333
|
+
getVariant(params.definition.variants, outcome),
|
|
334
|
+
"value" /* VALUE */,
|
|
335
|
+
outcome
|
|
336
|
+
);
|
|
320
337
|
}
|
|
321
338
|
switch (outcome.type) {
|
|
322
339
|
case "split": {
|
|
@@ -326,15 +343,21 @@ function handleOutcome(params, outcome) {
|
|
|
326
343
|
outcome.defaultVariant
|
|
327
344
|
);
|
|
328
345
|
if (typeof lhs !== "string") {
|
|
329
|
-
return
|
|
346
|
+
return withVariantIndex(
|
|
347
|
+
defaultOutcome,
|
|
348
|
+
"split" /* SPLIT */,
|
|
349
|
+
outcome.defaultVariant
|
|
350
|
+
);
|
|
330
351
|
}
|
|
331
352
|
const value = hashInput(lhs, params.definition.seed);
|
|
332
353
|
const scaledWeights = getScaledWeights(outcome);
|
|
333
354
|
const variantIndex = findWeightedIndex(scaledWeights, value, UINT32_MAX);
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
355
|
+
const selectedVariantIndex = variantIndex === -1 ? outcome.defaultVariant : variantIndex;
|
|
356
|
+
return withVariantIndex(
|
|
357
|
+
selectedVariantIndex === outcome.defaultVariant ? defaultOutcome : getVariant(params.definition.variants, selectedVariantIndex),
|
|
358
|
+
"split" /* SPLIT */,
|
|
359
|
+
selectedVariantIndex
|
|
360
|
+
);
|
|
338
361
|
}
|
|
339
362
|
case "rollout": {
|
|
340
363
|
const lhs = access(outcome.base, params);
|
|
@@ -343,18 +366,20 @@ function handleOutcome(params, outcome) {
|
|
|
343
366
|
outcome.defaultVariant
|
|
344
367
|
);
|
|
345
368
|
if (typeof lhs !== "string") {
|
|
346
|
-
return
|
|
369
|
+
return withVariantIndex(
|
|
370
|
+
defaultOutcome,
|
|
371
|
+
"rollout" /* ROLLOUT */,
|
|
372
|
+
outcome.defaultVariant
|
|
373
|
+
);
|
|
347
374
|
}
|
|
348
375
|
const now = Date.now();
|
|
349
376
|
const elapsed = now - outcome.startTimestamp;
|
|
350
377
|
if (elapsed < 0 || outcome.slots.length === 0) {
|
|
351
|
-
return
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
outcomeType: "rollout" /* ROLLOUT */
|
|
357
|
-
};
|
|
378
|
+
return withVariantIndex(
|
|
379
|
+
getVariant(params.definition.variants, outcome.rollFromVariant),
|
|
380
|
+
"rollout" /* ROLLOUT */,
|
|
381
|
+
outcome.rollFromVariant
|
|
382
|
+
);
|
|
358
383
|
}
|
|
359
384
|
let cumulativeDuration = 0;
|
|
360
385
|
let currentPromille = 0;
|
|
@@ -369,32 +394,27 @@ function handleOutcome(params, outcome) {
|
|
|
369
394
|
}
|
|
370
395
|
if (exhausted) currentPromille = 1e5;
|
|
371
396
|
if (currentPromille <= 0) {
|
|
372
|
-
return
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
outcomeType: "rollout" /* ROLLOUT */
|
|
378
|
-
};
|
|
397
|
+
return withVariantIndex(
|
|
398
|
+
getVariant(params.definition.variants, outcome.rollFromVariant),
|
|
399
|
+
"rollout" /* ROLLOUT */,
|
|
400
|
+
outcome.rollFromVariant
|
|
401
|
+
);
|
|
379
402
|
}
|
|
380
403
|
if (currentPromille >= 1e5) {
|
|
381
|
-
return
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
outcomeType: "rollout" /* ROLLOUT */
|
|
387
|
-
};
|
|
404
|
+
return withVariantIndex(
|
|
405
|
+
getVariant(params.definition.variants, outcome.rollToVariant),
|
|
406
|
+
"rollout" /* ROLLOUT */,
|
|
407
|
+
outcome.rollToVariant
|
|
408
|
+
);
|
|
388
409
|
}
|
|
389
410
|
const value = hashInput(lhs, params.definition.seed);
|
|
390
411
|
const threshold = currentPromille / 1e5 * UINT32_MAX;
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
};
|
|
412
|
+
const selectedVariantIndex = value < threshold ? outcome.rollToVariant : outcome.rollFromVariant;
|
|
413
|
+
return withVariantIndex(
|
|
414
|
+
getVariant(params.definition.variants, selectedVariantIndex),
|
|
415
|
+
"rollout" /* ROLLOUT */,
|
|
416
|
+
selectedVariantIndex
|
|
417
|
+
);
|
|
398
418
|
}
|
|
399
419
|
default: {
|
|
400
420
|
const { type } = outcome;
|
|
@@ -482,7 +502,7 @@ function findWeightedIndex(weights, value, maxValue) {
|
|
|
482
502
|
}
|
|
483
503
|
|
|
484
504
|
// package.json
|
|
485
|
-
var version = "1.
|
|
505
|
+
var version = "1.6.0-7d29945-20260616152249";
|
|
486
506
|
|
|
487
507
|
// src/lib/report-value.ts
|
|
488
508
|
function internalReportValue(key, value, data) {
|
|
@@ -521,6 +541,14 @@ function getFallbackDatafile(id) {
|
|
|
521
541
|
if (ds.getFallbackDatafile) return ds.getFallbackDatafile();
|
|
522
542
|
throw new Error("flags: This data source does not support fallbacks");
|
|
523
543
|
}
|
|
544
|
+
function getVariantIdentifier(definition, result) {
|
|
545
|
+
const variantIndex = getEvaluationVariantIndex(result);
|
|
546
|
+
if (variantIndex === void 0) return "unknown";
|
|
547
|
+
return definition.variantIds?.[variantIndex] ?? String(variantIndex);
|
|
548
|
+
}
|
|
549
|
+
function trackEvaluation(controller, options) {
|
|
550
|
+
controller.trackEvaluation?.(options);
|
|
551
|
+
}
|
|
524
552
|
async function evaluate2(id, flagKey, defaultValue, entities) {
|
|
525
553
|
const controller = getInstance(id).controller;
|
|
526
554
|
let datafile;
|
|
@@ -528,11 +556,17 @@ async function evaluate2(id, flagKey, defaultValue, entities) {
|
|
|
528
556
|
datafile = await controller.read();
|
|
529
557
|
} catch (error) {
|
|
530
558
|
if (defaultValue !== void 0) {
|
|
531
|
-
|
|
559
|
+
const result2 = {
|
|
532
560
|
value: defaultValue,
|
|
533
561
|
reason: "error" /* ERROR */,
|
|
534
562
|
errorMessage: error instanceof Error ? error.message : "Failed to read datafile"
|
|
535
563
|
};
|
|
564
|
+
trackEvaluation(controller, {
|
|
565
|
+
flagKey,
|
|
566
|
+
variant: "unknown",
|
|
567
|
+
reason: result2.reason
|
|
568
|
+
});
|
|
569
|
+
return result2;
|
|
536
570
|
}
|
|
537
571
|
throw error;
|
|
538
572
|
}
|
|
@@ -545,7 +579,7 @@ async function evaluate2(id, flagKey, defaultValue, entities) {
|
|
|
545
579
|
reason: "error" /* ERROR */
|
|
546
580
|
});
|
|
547
581
|
}
|
|
548
|
-
|
|
582
|
+
const result2 = {
|
|
549
583
|
value: defaultValue,
|
|
550
584
|
reason: "error" /* ERROR */,
|
|
551
585
|
errorCode: "FLAG_NOT_FOUND" /* FLAG_NOT_FOUND */,
|
|
@@ -559,6 +593,12 @@ async function evaluate2(id, flagKey, defaultValue, entities) {
|
|
|
559
593
|
mode: datafile.metrics.mode
|
|
560
594
|
}
|
|
561
595
|
};
|
|
596
|
+
trackEvaluation(controller, {
|
|
597
|
+
flagKey,
|
|
598
|
+
variant: "unknown",
|
|
599
|
+
reason: result2.reason
|
|
600
|
+
});
|
|
601
|
+
return result2;
|
|
562
602
|
}
|
|
563
603
|
const evalStartTime = Date.now();
|
|
564
604
|
const result = evaluate({
|
|
@@ -577,6 +617,14 @@ async function evaluate2(id, flagKey, defaultValue, entities) {
|
|
|
577
617
|
outcomeType: result.reason !== "error" /* ERROR */ ? result.outcomeType : void 0
|
|
578
618
|
});
|
|
579
619
|
}
|
|
620
|
+
trackEvaluation(controller, {
|
|
621
|
+
flagKey,
|
|
622
|
+
variant: getVariantIdentifier(
|
|
623
|
+
flagDefinition,
|
|
624
|
+
result
|
|
625
|
+
),
|
|
626
|
+
reason: result.reason
|
|
627
|
+
});
|
|
580
628
|
return Object.assign(result, {
|
|
581
629
|
metrics: {
|
|
582
630
|
evaluationMs: evaluationDurationMs,
|
|
@@ -602,6 +650,11 @@ async function bulkEvaluate2(id, flags, entities) {
|
|
|
602
650
|
reason: "error" /* ERROR */,
|
|
603
651
|
errorMessage
|
|
604
652
|
};
|
|
653
|
+
trackEvaluation(controller, {
|
|
654
|
+
flagKey: flag.key,
|
|
655
|
+
variant: "unknown",
|
|
656
|
+
reason: "error" /* ERROR */
|
|
657
|
+
});
|
|
605
658
|
}
|
|
606
659
|
return results2;
|
|
607
660
|
}
|
|
@@ -633,6 +686,11 @@ async function bulkEvaluate2(id, flags, entities) {
|
|
|
633
686
|
errorMessage: `@vercel/flags-core: Definition not found for flag "${key}"`,
|
|
634
687
|
metrics: { evaluationMs: 0, ...baseMetrics }
|
|
635
688
|
};
|
|
689
|
+
trackEvaluation(controller, {
|
|
690
|
+
flagKey: key,
|
|
691
|
+
variant: "unknown",
|
|
692
|
+
reason: "error" /* ERROR */
|
|
693
|
+
});
|
|
636
694
|
continue;
|
|
637
695
|
}
|
|
638
696
|
toEvaluate[key] = { definition: flagDefinition, defaultValue };
|
|
@@ -654,6 +712,14 @@ async function bulkEvaluate2(id, flags, entities) {
|
|
|
654
712
|
outcomeType: result.reason !== "error" /* ERROR */ ? result.outcomeType : void 0
|
|
655
713
|
});
|
|
656
714
|
}
|
|
715
|
+
trackEvaluation(controller, {
|
|
716
|
+
flagKey: key,
|
|
717
|
+
variant: getVariantIdentifier(
|
|
718
|
+
toEvaluate[key].definition,
|
|
719
|
+
result
|
|
720
|
+
),
|
|
721
|
+
reason: result.reason
|
|
722
|
+
});
|
|
657
723
|
results[key] = Object.assign(result, {
|
|
658
724
|
metrics: { evaluationMs: evaluationDurationMs, ...baseMetrics }
|
|
659
725
|
});
|
|
@@ -798,8 +864,8 @@ async function readBundledDefinitions(auth) {
|
|
|
798
864
|
return { definitions: null, state: "missing-entry" };
|
|
799
865
|
}
|
|
800
866
|
|
|
801
|
-
// src/utils/
|
|
802
|
-
import {
|
|
867
|
+
// src/utils/ingest.ts
|
|
868
|
+
import { getVercelOidcToken } from "@vercel/oidc";
|
|
803
869
|
|
|
804
870
|
// src/utils/backoff.ts
|
|
805
871
|
var DEFAULT_BASE_MS = 250;
|
|
@@ -810,23 +876,72 @@ function getRetryDelayMs(attempt, options = {}) {
|
|
|
810
876
|
const ceiling = Math.min(capMs, baseMs * 2 ** Math.max(0, attempt - 1));
|
|
811
877
|
return Math.floor(Math.random() * ceiling);
|
|
812
878
|
}
|
|
813
|
-
function getJitteredWaitMs(baseMs, ratio) {
|
|
814
|
-
const min = baseMs * (1 - ratio);
|
|
815
|
-
const span = baseMs * 2 * ratio;
|
|
816
|
-
return Math.floor(min + Math.random() * span);
|
|
817
|
-
}
|
|
818
879
|
|
|
819
|
-
// src/utils/
|
|
820
|
-
var
|
|
880
|
+
// src/utils/ingest.ts
|
|
881
|
+
var MAX_RETRIES = 3;
|
|
882
|
+
var EVALUATING_OIDC_TOKEN_HEADER = "X-Vercel-Flags-Evaluating-OIDC-Token";
|
|
821
883
|
var isDebugMode = process.env.DEBUG?.includes("@vercel/flags-core");
|
|
822
884
|
var debugLog = (...args) => {
|
|
823
885
|
if (!isDebugMode) return;
|
|
824
886
|
console.log(...args);
|
|
825
887
|
};
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
888
|
+
async function getEvaluatingOidcToken(auth) {
|
|
889
|
+
if (!auth.sdkKey) return void 0;
|
|
890
|
+
try {
|
|
891
|
+
return await getVercelOidcToken();
|
|
892
|
+
} catch {
|
|
893
|
+
return void 0;
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
async function getIngestHeaders(options) {
|
|
897
|
+
const token = await options.auth.resolveToken();
|
|
898
|
+
const evaluatingOidcToken = await getEvaluatingOidcToken(options.auth);
|
|
899
|
+
return {
|
|
900
|
+
"Content-Type": "application/json",
|
|
901
|
+
Authorization: `Bearer ${token}`,
|
|
902
|
+
"User-Agent": `VercelFlagsCore/${version}`,
|
|
903
|
+
...process.env.VERCEL_ENV ? { "X-Vercel-Env": process.env.VERCEL_ENV } : null,
|
|
904
|
+
...evaluatingOidcToken ? { [EVALUATING_OIDC_TOKEN_HEADER]: evaluatingOidcToken } : null,
|
|
905
|
+
...isDebugMode ? { "x-vercel-debug-ingest": "1" } : null
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
async function sendIngestEvents(options, events, flushId) {
|
|
909
|
+
const eventsToSend = events.map((event) => event.ingestEvent());
|
|
910
|
+
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
911
|
+
try {
|
|
912
|
+
const response = await options.fetch(`${options.host}/v1/ingest`, {
|
|
913
|
+
method: "POST",
|
|
914
|
+
headers: await getIngestHeaders(options),
|
|
915
|
+
body: JSON.stringify(eventsToSend)
|
|
916
|
+
});
|
|
917
|
+
debugLog(
|
|
918
|
+
`@vercel/flags-core: Ingest response ${response.status} for ${eventsToSend.length} events on ${response.headers.get("x-vercel-id")}`
|
|
919
|
+
);
|
|
920
|
+
if (response.ok) {
|
|
921
|
+
break;
|
|
922
|
+
}
|
|
923
|
+
throw new Error(
|
|
924
|
+
`Ingest endpoint responded with status ${response.status} for ${eventsToSend.length} events on request ${response.headers.get("x-vercel-id")}.
|
|
925
|
+
Response body: ${await response.text().catch(() => null)}`
|
|
926
|
+
);
|
|
927
|
+
} catch (error) {
|
|
928
|
+
console.error(
|
|
929
|
+
`@vercel/flags-core: Error sending events (attempt=${attempt}/${MAX_RETRIES} flushId=${flushId}):`,
|
|
930
|
+
error
|
|
931
|
+
);
|
|
932
|
+
if (attempt < MAX_RETRIES) {
|
|
933
|
+
const delayMs = getRetryDelayMs(attempt);
|
|
934
|
+
await new Promise((res) => setTimeout(res, delayMs));
|
|
935
|
+
} else {
|
|
936
|
+
console.error(
|
|
937
|
+
`@vercel/flags-core: Dropped ${eventsToSend.length} events after ${MAX_RETRIES} attempts (flushId=${flushId})`
|
|
938
|
+
);
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
// src/utils/request-context.ts
|
|
830
945
|
var SYMBOL_FOR_REQ_CONTEXT = /* @__PURE__ */ Symbol.for("@vercel/request-context");
|
|
831
946
|
var fromSymbol = globalThis;
|
|
832
947
|
function getRequestContext() {
|
|
@@ -838,36 +953,227 @@ function getRequestContext() {
|
|
|
838
953
|
headers: ctx.headers
|
|
839
954
|
};
|
|
840
955
|
}
|
|
841
|
-
return { ctx, headers: void 0 };
|
|
956
|
+
return { ctx: void 0, headers: void 0 };
|
|
842
957
|
} catch {
|
|
843
958
|
return { ctx: void 0, headers: void 0 };
|
|
844
959
|
}
|
|
845
960
|
}
|
|
961
|
+
|
|
962
|
+
// src/utils/scheduler.ts
|
|
963
|
+
import { waitUntil } from "@vercel/functions";
|
|
964
|
+
var MAX_COUNT = 50;
|
|
965
|
+
var IDLE_FLUSH_WAIT_MS = 5e3;
|
|
966
|
+
var MAX_FLUSH_WAIT_MS = 6e4;
|
|
967
|
+
var Scheduler = class {
|
|
968
|
+
constructor(onFlush) {
|
|
969
|
+
this.onFlush = onFlush;
|
|
970
|
+
}
|
|
971
|
+
count = 0;
|
|
972
|
+
resolveWait = null;
|
|
973
|
+
pending = null;
|
|
974
|
+
idleTimeout = null;
|
|
975
|
+
maxTimeout = null;
|
|
976
|
+
increment() {
|
|
977
|
+
this.count += 1;
|
|
978
|
+
if (this.count >= MAX_COUNT) {
|
|
979
|
+
this.resolveScheduledFlush();
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
scheduleFlush() {
|
|
983
|
+
if (!this.pending) {
|
|
984
|
+
this.pending = (async () => {
|
|
985
|
+
await new Promise((res) => {
|
|
986
|
+
this.resolveWait = res;
|
|
987
|
+
});
|
|
988
|
+
this.reset();
|
|
989
|
+
await this.onFlush();
|
|
990
|
+
})();
|
|
991
|
+
try {
|
|
992
|
+
waitUntil(this.pending);
|
|
993
|
+
} catch {
|
|
994
|
+
}
|
|
995
|
+
this.maxTimeout = setTimeout(
|
|
996
|
+
() => this.resolveScheduledFlush(),
|
|
997
|
+
MAX_FLUSH_WAIT_MS
|
|
998
|
+
);
|
|
999
|
+
}
|
|
1000
|
+
this.resetIdleTimeout();
|
|
1001
|
+
}
|
|
1002
|
+
/**
|
|
1003
|
+
* Resolves any in-flight scheduled flush and waits for its ingest to finish.
|
|
1004
|
+
*/
|
|
1005
|
+
async shutdown() {
|
|
1006
|
+
this.clearTimeouts();
|
|
1007
|
+
const pending = this.pending;
|
|
1008
|
+
this.resolveWait?.();
|
|
1009
|
+
if (pending) await pending;
|
|
1010
|
+
}
|
|
1011
|
+
resetIdleTimeout() {
|
|
1012
|
+
if (this.idleTimeout) clearTimeout(this.idleTimeout);
|
|
1013
|
+
this.idleTimeout = setTimeout(
|
|
1014
|
+
() => this.resolveScheduledFlush(),
|
|
1015
|
+
IDLE_FLUSH_WAIT_MS
|
|
1016
|
+
);
|
|
1017
|
+
}
|
|
1018
|
+
resolveScheduledFlush() {
|
|
1019
|
+
this.clearTimeouts();
|
|
1020
|
+
this.resolveWait?.();
|
|
1021
|
+
}
|
|
1022
|
+
reset() {
|
|
1023
|
+
this.pending = null;
|
|
1024
|
+
this.resolveWait = null;
|
|
1025
|
+
this.count = 0;
|
|
1026
|
+
this.clearTimeouts();
|
|
1027
|
+
}
|
|
1028
|
+
clearTimeouts() {
|
|
1029
|
+
if (this.idleTimeout) {
|
|
1030
|
+
clearTimeout(this.idleTimeout);
|
|
1031
|
+
this.idleTimeout = null;
|
|
1032
|
+
}
|
|
1033
|
+
if (this.maxTimeout) {
|
|
1034
|
+
clearTimeout(this.maxTimeout);
|
|
1035
|
+
this.maxTimeout = null;
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
};
|
|
1039
|
+
|
|
1040
|
+
// src/utils/usage/flags-config-read.ts
|
|
1041
|
+
var FlagsConfigReadEvent = class {
|
|
1042
|
+
payload;
|
|
1043
|
+
constructor(headers, options) {
|
|
1044
|
+
this.payload = {
|
|
1045
|
+
deploymentId: process.env.VERCEL_DEPLOYMENT_ID,
|
|
1046
|
+
region: process.env.VERCEL_REGION
|
|
1047
|
+
};
|
|
1048
|
+
if (headers) {
|
|
1049
|
+
this.payload.vercelRequestId = headers["x-vercel-id"] ?? void 0;
|
|
1050
|
+
this.payload.invocationHost = headers.host ?? void 0;
|
|
1051
|
+
}
|
|
1052
|
+
if (options) {
|
|
1053
|
+
this.payload.configOrigin = options.configOrigin;
|
|
1054
|
+
if (options.cacheStatus !== void 0) {
|
|
1055
|
+
this.payload.cacheStatus = options.cacheStatus;
|
|
1056
|
+
}
|
|
1057
|
+
if (options.cacheAction !== void 0) {
|
|
1058
|
+
this.payload.cacheAction = options.cacheAction;
|
|
1059
|
+
}
|
|
1060
|
+
if (options.cacheIsFirstRead !== void 0) {
|
|
1061
|
+
this.payload.cacheIsFirstRead = options.cacheIsFirstRead;
|
|
1062
|
+
}
|
|
1063
|
+
if (options.cacheIsBlocking !== void 0) {
|
|
1064
|
+
this.payload.cacheIsBlocking = options.cacheIsBlocking;
|
|
1065
|
+
}
|
|
1066
|
+
if (options.duration !== void 0) {
|
|
1067
|
+
this.payload.duration = options.duration;
|
|
1068
|
+
}
|
|
1069
|
+
if (options.configUpdatedAt !== void 0) {
|
|
1070
|
+
this.payload.configUpdatedAt = options.configUpdatedAt;
|
|
1071
|
+
}
|
|
1072
|
+
if (options.mode !== void 0) {
|
|
1073
|
+
this.payload.mode = options.mode;
|
|
1074
|
+
}
|
|
1075
|
+
if (options.revision !== void 0) {
|
|
1076
|
+
this.payload.revision = String(options.revision);
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
const environment = process.env.VERCEL_ENV || process.env.NODE_ENV || void 0;
|
|
1080
|
+
if (environment) {
|
|
1081
|
+
this.payload.environment = environment;
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
ingestEvent() {
|
|
1085
|
+
return {
|
|
1086
|
+
type: "FLAGS_CONFIG_READ",
|
|
1087
|
+
ts: Date.now(),
|
|
1088
|
+
payload: this.payload
|
|
1089
|
+
};
|
|
1090
|
+
}
|
|
1091
|
+
};
|
|
1092
|
+
|
|
1093
|
+
// src/utils/usage/flags-evaluation.ts
|
|
1094
|
+
function evaluationBatchKey(options) {
|
|
1095
|
+
return JSON.stringify([
|
|
1096
|
+
options.flagKey,
|
|
1097
|
+
options.variant,
|
|
1098
|
+
options.reason,
|
|
1099
|
+
options.clientName ?? null
|
|
1100
|
+
]);
|
|
1101
|
+
}
|
|
1102
|
+
var FlagsEvaluationEvent = class {
|
|
1103
|
+
payload;
|
|
1104
|
+
constructor(eventOptions) {
|
|
1105
|
+
this.payload = {
|
|
1106
|
+
flagKey: eventOptions.flagKey,
|
|
1107
|
+
variant: eventOptions.variant,
|
|
1108
|
+
reason: eventOptions.reason,
|
|
1109
|
+
count: 1
|
|
1110
|
+
};
|
|
1111
|
+
if (eventOptions.clientName) {
|
|
1112
|
+
this.payload.clientName = eventOptions.clientName;
|
|
1113
|
+
}
|
|
1114
|
+
Object.defineProperty(this, "batchKey", {
|
|
1115
|
+
value: JSON.stringify([
|
|
1116
|
+
this.payload.flagKey,
|
|
1117
|
+
this.payload.variant,
|
|
1118
|
+
this.payload.reason,
|
|
1119
|
+
this.payload.clientName ?? null
|
|
1120
|
+
]),
|
|
1121
|
+
enumerable: false
|
|
1122
|
+
});
|
|
1123
|
+
}
|
|
1124
|
+
increment() {
|
|
1125
|
+
this.payload.count += 1;
|
|
1126
|
+
}
|
|
1127
|
+
ingestEvent() {
|
|
1128
|
+
return {
|
|
1129
|
+
type: "FLAGS_EVALUATION",
|
|
1130
|
+
ts: Date.now(),
|
|
1131
|
+
payload: this.payload
|
|
1132
|
+
};
|
|
1133
|
+
}
|
|
1134
|
+
};
|
|
1135
|
+
|
|
1136
|
+
// src/utils/weak-map.ts
|
|
1137
|
+
var BetterWeakMap = class {
|
|
1138
|
+
_values = [];
|
|
1139
|
+
_keys = [];
|
|
1140
|
+
set(key, value) {
|
|
1141
|
+
this._keys.push(key);
|
|
1142
|
+
this._values.push(value);
|
|
1143
|
+
}
|
|
1144
|
+
values() {
|
|
1145
|
+
return this._values;
|
|
1146
|
+
}
|
|
1147
|
+
has(key) {
|
|
1148
|
+
return this._keys.includes(key);
|
|
1149
|
+
}
|
|
1150
|
+
keys() {
|
|
1151
|
+
return this._keys;
|
|
1152
|
+
}
|
|
1153
|
+
clear() {
|
|
1154
|
+
this._values = [];
|
|
1155
|
+
this._keys = [];
|
|
1156
|
+
}
|
|
1157
|
+
};
|
|
1158
|
+
|
|
1159
|
+
// src/utils/usage-tracker.ts
|
|
846
1160
|
var UsageTracker = class {
|
|
847
|
-
|
|
1161
|
+
flushCount = 0;
|
|
848
1162
|
options;
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
resolveWait: null,
|
|
853
|
-
pending: null
|
|
854
|
-
};
|
|
1163
|
+
scheduler;
|
|
1164
|
+
readEvents = new BetterWeakMap();
|
|
1165
|
+
evaluationEvents = /* @__PURE__ */ new Map();
|
|
855
1166
|
constructor(options) {
|
|
856
1167
|
this.options = options;
|
|
1168
|
+
this.scheduler = new Scheduler(() => this.flushEvents());
|
|
857
1169
|
}
|
|
858
1170
|
/**
|
|
859
1171
|
* Triggers an immediate flush of any pending events.
|
|
860
1172
|
* Returns a promise that resolves when the flush completes.
|
|
861
1173
|
*/
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
return this.batcher.pending;
|
|
866
|
-
}
|
|
867
|
-
if (this.batcher.events.length > 0) {
|
|
868
|
-
return this.flushEvents();
|
|
869
|
-
}
|
|
870
|
-
return RESOLVED_VOID;
|
|
1174
|
+
async shutdown() {
|
|
1175
|
+
await this.scheduler.shutdown();
|
|
1176
|
+
await this.flushEvents();
|
|
871
1177
|
}
|
|
872
1178
|
/**
|
|
873
1179
|
* Tracks a config read event. Deduplicates by request context.
|
|
@@ -876,130 +1182,51 @@ var UsageTracker = class {
|
|
|
876
1182
|
try {
|
|
877
1183
|
const { ctx, headers } = getRequestContext();
|
|
878
1184
|
if (!ctx) return;
|
|
879
|
-
if (this.
|
|
880
|
-
|
|
881
|
-
const event = {
|
|
882
|
-
type: "FLAGS_CONFIG_READ",
|
|
883
|
-
ts: Date.now(),
|
|
884
|
-
payload: {
|
|
885
|
-
deploymentId: process.env.VERCEL_DEPLOYMENT_ID,
|
|
886
|
-
region: process.env.VERCEL_REGION
|
|
887
|
-
}
|
|
888
|
-
};
|
|
889
|
-
if (headers) {
|
|
890
|
-
event.payload.vercelRequestId = headers["x-vercel-id"] ?? void 0;
|
|
891
|
-
event.payload.invocationHost = headers.host ?? void 0;
|
|
892
|
-
}
|
|
893
|
-
if (options) {
|
|
894
|
-
event.payload.configOrigin = options.configOrigin;
|
|
895
|
-
if (options.cacheStatus !== void 0) {
|
|
896
|
-
event.payload.cacheStatus = options.cacheStatus;
|
|
897
|
-
}
|
|
898
|
-
if (options.cacheAction !== void 0) {
|
|
899
|
-
event.payload.cacheAction = options.cacheAction;
|
|
900
|
-
}
|
|
901
|
-
if (options.cacheIsFirstRead !== void 0) {
|
|
902
|
-
event.payload.cacheIsFirstRead = options.cacheIsFirstRead;
|
|
903
|
-
}
|
|
904
|
-
if (options.cacheIsBlocking !== void 0) {
|
|
905
|
-
event.payload.cacheIsBlocking = options.cacheIsBlocking;
|
|
906
|
-
}
|
|
907
|
-
if (options.duration !== void 0) {
|
|
908
|
-
event.payload.duration = options.duration;
|
|
909
|
-
}
|
|
910
|
-
if (options.configUpdatedAt !== void 0) {
|
|
911
|
-
event.payload.configUpdatedAt = options.configUpdatedAt;
|
|
912
|
-
}
|
|
913
|
-
if (options.mode !== void 0) {
|
|
914
|
-
event.payload.mode = options.mode;
|
|
915
|
-
}
|
|
916
|
-
if (options.revision !== void 0) {
|
|
917
|
-
event.payload.revision = String(options.revision);
|
|
918
|
-
}
|
|
919
|
-
}
|
|
920
|
-
const environment = process.env.VERCEL_ENV || process.env.NODE_ENV || void 0;
|
|
921
|
-
if (environment) {
|
|
922
|
-
event.payload.environment = environment;
|
|
1185
|
+
if (this.readEvents.has(ctx)) {
|
|
1186
|
+
return;
|
|
923
1187
|
}
|
|
924
|
-
this.
|
|
925
|
-
this.scheduleFlush();
|
|
1188
|
+
this.readEvents.set(ctx, new FlagsConfigReadEvent(headers, options));
|
|
1189
|
+
this.scheduler.scheduleFlush();
|
|
1190
|
+
this.scheduler.increment();
|
|
926
1191
|
} catch (error) {
|
|
927
1192
|
console.error("@vercel/flags-core: Failed to record event:", error);
|
|
928
1193
|
}
|
|
929
1194
|
}
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
this.
|
|
942
|
-
this.batcher.resolveWait = null;
|
|
943
|
-
if (timeout) clearTimeout(timeout);
|
|
944
|
-
await this.flushEvents();
|
|
945
|
-
})();
|
|
946
|
-
try {
|
|
947
|
-
waitUntil(pending);
|
|
948
|
-
} catch {
|
|
1195
|
+
/**
|
|
1196
|
+
* Tracks a flag evaluation event.
|
|
1197
|
+
*/
|
|
1198
|
+
trackEvaluation(options) {
|
|
1199
|
+
try {
|
|
1200
|
+
const batchKey = evaluationBatchKey(options);
|
|
1201
|
+
const existingEvent = this.evaluationEvents.get(batchKey);
|
|
1202
|
+
if (existingEvent) {
|
|
1203
|
+
existingEvent.increment();
|
|
1204
|
+
} else {
|
|
1205
|
+
this.evaluationEvents.set(batchKey, new FlagsEvaluationEvent(options));
|
|
1206
|
+
this.scheduler.increment();
|
|
949
1207
|
}
|
|
950
|
-
this.
|
|
951
|
-
}
|
|
952
|
-
|
|
953
|
-
|
|
1208
|
+
this.scheduler.scheduleFlush();
|
|
1209
|
+
} catch (error) {
|
|
1210
|
+
console.error(
|
|
1211
|
+
"@vercel/flags-core: Failed to record evaluation event:",
|
|
1212
|
+
error
|
|
1213
|
+
);
|
|
954
1214
|
}
|
|
955
1215
|
}
|
|
1216
|
+
/**
|
|
1217
|
+
* Send all events to the ingest service
|
|
1218
|
+
*/
|
|
956
1219
|
async flushEvents() {
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
method: "POST",
|
|
968
|
-
headers: {
|
|
969
|
-
"Content-Type": "application/json",
|
|
970
|
-
Authorization: `Bearer ${token}`,
|
|
971
|
-
"User-Agent": `VercelFlagsCore/${version}`,
|
|
972
|
-
...process.env.VERCEL_ENV ? { "X-Vercel-Env": process.env.VERCEL_ENV } : null,
|
|
973
|
-
...isDebugMode ? { "x-vercel-debug-ingest": "1" } : null
|
|
974
|
-
},
|
|
975
|
-
body: JSON.stringify(eventsToSend)
|
|
976
|
-
}
|
|
977
|
-
);
|
|
978
|
-
debugLog(
|
|
979
|
-
`@vercel/flags-core: Ingest response ${response.status} for ${eventsToSend.length} events on ${response.headers.get("x-vercel-id")}`
|
|
980
|
-
);
|
|
981
|
-
if (response.ok) {
|
|
982
|
-
break;
|
|
983
|
-
}
|
|
984
|
-
throw new Error(
|
|
985
|
-
`Ingest endpoint responded with status ${response.status} for ${eventsToSend.length} events on request ${response.headers.get("x-vercel-id")}.
|
|
986
|
-
Response body: ${await response.text().catch(() => null)}`
|
|
987
|
-
);
|
|
988
|
-
} catch (error) {
|
|
989
|
-
console.error(
|
|
990
|
-
`@vercel/flags-core: Error sending events (attempt=${attempt}/${MAX_RETRIES} flushId=${flushId}):`,
|
|
991
|
-
error
|
|
992
|
-
);
|
|
993
|
-
if (attempt < MAX_RETRIES) {
|
|
994
|
-
const delayMs = getRetryDelayMs(attempt);
|
|
995
|
-
await new Promise((res) => setTimeout(res, delayMs));
|
|
996
|
-
} else {
|
|
997
|
-
console.error(
|
|
998
|
-
`@vercel/flags-core: Dropped ${eventsToSend.length} events after ${MAX_RETRIES} attempts (flushId=${flushId})`
|
|
999
|
-
);
|
|
1000
|
-
}
|
|
1001
|
-
}
|
|
1002
|
-
}
|
|
1220
|
+
const events = [
|
|
1221
|
+
...this.readEvents.values(),
|
|
1222
|
+
...this.evaluationEvents.values()
|
|
1223
|
+
];
|
|
1224
|
+
if (events.length === 0) return;
|
|
1225
|
+
this.flushCount += 1;
|
|
1226
|
+
const flushId = this.flushCount;
|
|
1227
|
+
this.readEvents.clear();
|
|
1228
|
+
this.evaluationEvents.clear();
|
|
1229
|
+
await sendIngestEvents(this.options, events, flushId);
|
|
1003
1230
|
}
|
|
1004
1231
|
};
|
|
1005
1232
|
|
|
@@ -1156,7 +1383,8 @@ function normalizeOptions(options) {
|
|
|
1156
1383
|
polling,
|
|
1157
1384
|
buildStep,
|
|
1158
1385
|
fetch: options.fetch ?? globalThis.fetch,
|
|
1159
|
-
host: "https://flags.vercel.com"
|
|
1386
|
+
host: "https://flags.vercel.com",
|
|
1387
|
+
clientName: options.clientName
|
|
1160
1388
|
};
|
|
1161
1389
|
}
|
|
1162
1390
|
|
|
@@ -1741,7 +1969,7 @@ var Controller = class {
|
|
|
1741
1969
|
this.pollingSource.stop();
|
|
1742
1970
|
this.data = this.options.datafile ? tagData(this.options.datafile, "provided") : void 0;
|
|
1743
1971
|
this.transition("shutdown");
|
|
1744
|
-
await this.usageTracker.
|
|
1972
|
+
await this.usageTracker.shutdown();
|
|
1745
1973
|
}
|
|
1746
1974
|
/**
|
|
1747
1975
|
* Returns the datafile with metrics.
|
|
@@ -2119,10 +2347,20 @@ var Controller = class {
|
|
|
2119
2347
|
}
|
|
2120
2348
|
this.usageTracker.trackRead(trackOptions);
|
|
2121
2349
|
}
|
|
2350
|
+
/**
|
|
2351
|
+
* Tracks a flag evaluation for usage analytics.
|
|
2352
|
+
*/
|
|
2353
|
+
trackEvaluation(options) {
|
|
2354
|
+
if (this.unauthorized) return;
|
|
2355
|
+
this.usageTracker.trackEvaluation({
|
|
2356
|
+
...options,
|
|
2357
|
+
clientName: options.clientName ?? this.options.clientName
|
|
2358
|
+
});
|
|
2359
|
+
}
|
|
2122
2360
|
};
|
|
2123
2361
|
|
|
2124
2362
|
// src/controller/auth.ts
|
|
2125
|
-
import { getVercelOidcToken } from "@vercel/oidc";
|
|
2363
|
+
import { getVercelOidcToken as getVercelOidcToken2 } from "@vercel/oidc";
|
|
2126
2364
|
|
|
2127
2365
|
// src/utils/sdk-keys.ts
|
|
2128
2366
|
var SDK_KEY_REGEX = /^vf_(?:server|client)_/;
|
|
@@ -2141,7 +2379,7 @@ function parseSdkKeyFromFlagsConnectionString(text) {
|
|
|
2141
2379
|
// src/controller/auth.ts
|
|
2142
2380
|
async function getOidcToken() {
|
|
2143
2381
|
try {
|
|
2144
|
-
return await
|
|
2382
|
+
return await getVercelOidcToken2();
|
|
2145
2383
|
} catch {
|
|
2146
2384
|
throw new Error(
|
|
2147
2385
|
[
|
|
@@ -2261,4 +2499,4 @@ export {
|
|
|
2261
2499
|
resetDefaultFlagsClient,
|
|
2262
2500
|
createClient
|
|
2263
2501
|
};
|
|
2264
|
-
//# sourceMappingURL=chunk-
|
|
2502
|
+
//# sourceMappingURL=chunk-PSYHUXJC.js.map
|