@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.
@@ -1,4 +1,4 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2; var _class3;// src/index.next-js.ts
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2; var _class3; var _class4; var _class5;// src/index.next-js.ts
2
2
  var _cache = require('next/cache');
3
3
 
4
4
  // src/evaluate.ts
@@ -35,6 +35,22 @@ var MAX_REGEX_INPUT_LENGTH = 1e4;
35
35
  var UINT32_MAX = 4294967295;
36
36
  var SCALED_WEIGHTS = /* @__PURE__ */ Symbol("@vercel/flags-core:scaledWeights");
37
37
  var COMPILED_REGEX = /* @__PURE__ */ Symbol("@vercel/flags-core:compiledRegex");
38
+ var EVALUATION_VARIANT_INDEX = /* @__PURE__ */ Symbol(
39
+ "@vercel/flags-core:evaluationVariantIndex"
40
+ );
41
+ function withVariantIndex(value, outcomeType, variantIndex) {
42
+ return Object.defineProperty(
43
+ {
44
+ value,
45
+ outcomeType
46
+ },
47
+ EVALUATION_VARIANT_INDEX,
48
+ { value: variantIndex }
49
+ );
50
+ }
51
+ function getEvaluationVariantIndex(result) {
52
+ return result[EVALUATION_VARIANT_INDEX];
53
+ }
38
54
  function getScaledWeights(outcome) {
39
55
  const cached = outcome[SCALED_WEIGHTS];
40
56
  if (cached) return cached;
@@ -298,10 +314,11 @@ function getVariant(variants, index) {
298
314
  }
299
315
  function handleOutcome(params, outcome) {
300
316
  if (typeof outcome === "number") {
301
- return {
302
- value: getVariant(params.definition.variants, outcome),
303
- outcomeType: "value" /* VALUE */
304
- };
317
+ return withVariantIndex(
318
+ getVariant(params.definition.variants, outcome),
319
+ "value" /* VALUE */,
320
+ outcome
321
+ );
305
322
  }
306
323
  switch (outcome.type) {
307
324
  case "split": {
@@ -311,15 +328,21 @@ function handleOutcome(params, outcome) {
311
328
  outcome.defaultVariant
312
329
  );
313
330
  if (typeof lhs !== "string") {
314
- return { value: defaultOutcome, outcomeType: "split" /* SPLIT */ };
331
+ return withVariantIndex(
332
+ defaultOutcome,
333
+ "split" /* SPLIT */,
334
+ outcome.defaultVariant
335
+ );
315
336
  }
316
337
  const value = _jsxxhash.xxHash32.call(void 0, lhs, params.definition.seed);
317
338
  const scaledWeights = getScaledWeights(outcome);
318
339
  const variantIndex = findWeightedIndex(scaledWeights, value, UINT32_MAX);
319
- return {
320
- value: variantIndex === -1 ? defaultOutcome : getVariant(params.definition.variants, variantIndex),
321
- outcomeType: "split" /* SPLIT */
322
- };
340
+ const selectedVariantIndex = variantIndex === -1 ? outcome.defaultVariant : variantIndex;
341
+ return withVariantIndex(
342
+ selectedVariantIndex === outcome.defaultVariant ? defaultOutcome : getVariant(params.definition.variants, selectedVariantIndex),
343
+ "split" /* SPLIT */,
344
+ selectedVariantIndex
345
+ );
323
346
  }
324
347
  case "rollout": {
325
348
  const lhs = access(outcome.base, params);
@@ -328,18 +351,20 @@ function handleOutcome(params, outcome) {
328
351
  outcome.defaultVariant
329
352
  );
330
353
  if (typeof lhs !== "string") {
331
- return { value: defaultOutcome, outcomeType: "rollout" /* ROLLOUT */ };
354
+ return withVariantIndex(
355
+ defaultOutcome,
356
+ "rollout" /* ROLLOUT */,
357
+ outcome.defaultVariant
358
+ );
332
359
  }
333
360
  const now = Date.now();
334
361
  const elapsed = now - outcome.startTimestamp;
335
362
  if (elapsed < 0 || outcome.slots.length === 0) {
336
- return {
337
- value: getVariant(
338
- params.definition.variants,
339
- outcome.rollFromVariant
340
- ),
341
- outcomeType: "rollout" /* ROLLOUT */
342
- };
363
+ return withVariantIndex(
364
+ getVariant(params.definition.variants, outcome.rollFromVariant),
365
+ "rollout" /* ROLLOUT */,
366
+ outcome.rollFromVariant
367
+ );
343
368
  }
344
369
  let cumulativeDuration = 0;
345
370
  let currentPromille = 0;
@@ -354,32 +379,27 @@ function handleOutcome(params, outcome) {
354
379
  }
355
380
  if (exhausted) currentPromille = 1e5;
356
381
  if (currentPromille <= 0) {
357
- return {
358
- value: getVariant(
359
- params.definition.variants,
360
- outcome.rollFromVariant
361
- ),
362
- outcomeType: "rollout" /* ROLLOUT */
363
- };
382
+ return withVariantIndex(
383
+ getVariant(params.definition.variants, outcome.rollFromVariant),
384
+ "rollout" /* ROLLOUT */,
385
+ outcome.rollFromVariant
386
+ );
364
387
  }
365
388
  if (currentPromille >= 1e5) {
366
- return {
367
- value: getVariant(
368
- params.definition.variants,
369
- outcome.rollToVariant
370
- ),
371
- outcomeType: "rollout" /* ROLLOUT */
372
- };
389
+ return withVariantIndex(
390
+ getVariant(params.definition.variants, outcome.rollToVariant),
391
+ "rollout" /* ROLLOUT */,
392
+ outcome.rollToVariant
393
+ );
373
394
  }
374
395
  const value = _jsxxhash.xxHash32.call(void 0, lhs, params.definition.seed);
375
396
  const threshold = currentPromille / 1e5 * UINT32_MAX;
376
- return {
377
- value: value < threshold ? getVariant(params.definition.variants, outcome.rollToVariant) : getVariant(
378
- params.definition.variants,
379
- outcome.rollFromVariant
380
- ),
381
- outcomeType: "rollout" /* ROLLOUT */
382
- };
397
+ const selectedVariantIndex = value < threshold ? outcome.rollToVariant : outcome.rollFromVariant;
398
+ return withVariantIndex(
399
+ getVariant(params.definition.variants, selectedVariantIndex),
400
+ "rollout" /* ROLLOUT */,
401
+ selectedVariantIndex
402
+ );
383
403
  }
384
404
  default: {
385
405
  const { type } = outcome;
@@ -467,7 +487,7 @@ function findWeightedIndex(weights, value, maxValue) {
467
487
  }
468
488
 
469
489
  // package.json
470
- var version = "1.5.0";
490
+ var version = "1.6.0-7d29945-20260616152249";
471
491
 
472
492
  // src/lib/report-value.ts
473
493
  function internalReportValue(key, value, data) {
@@ -506,6 +526,14 @@ function getFallbackDatafile(id) {
506
526
  if (ds.getFallbackDatafile) return ds.getFallbackDatafile();
507
527
  throw new Error("flags: This data source does not support fallbacks");
508
528
  }
529
+ function getVariantIdentifier(definition, result) {
530
+ const variantIndex = getEvaluationVariantIndex(result);
531
+ if (variantIndex === void 0) return "unknown";
532
+ return _nullishCoalesce(_optionalChain([definition, 'access', _20 => _20.variantIds, 'optionalAccess', _21 => _21[variantIndex]]), () => ( String(variantIndex)));
533
+ }
534
+ function trackEvaluation(controller, options) {
535
+ _optionalChain([controller, 'access', _22 => _22.trackEvaluation, 'optionalCall', _23 => _23(options)]);
536
+ }
509
537
  async function evaluate2(id, flagKey, defaultValue, entities) {
510
538
  const controller = getInstance(id).controller;
511
539
  let datafile;
@@ -513,11 +541,17 @@ async function evaluate2(id, flagKey, defaultValue, entities) {
513
541
  datafile = await controller.read();
514
542
  } catch (error) {
515
543
  if (defaultValue !== void 0) {
516
- return {
544
+ const result2 = {
517
545
  value: defaultValue,
518
546
  reason: "error" /* ERROR */,
519
547
  errorMessage: error instanceof Error ? error.message : "Failed to read datafile"
520
548
  };
549
+ trackEvaluation(controller, {
550
+ flagKey,
551
+ variant: "unknown",
552
+ reason: result2.reason
553
+ });
554
+ return result2;
521
555
  }
522
556
  throw error;
523
557
  }
@@ -530,7 +564,7 @@ async function evaluate2(id, flagKey, defaultValue, entities) {
530
564
  reason: "error" /* ERROR */
531
565
  });
532
566
  }
533
- return {
567
+ const result2 = {
534
568
  value: defaultValue,
535
569
  reason: "error" /* ERROR */,
536
570
  errorCode: "FLAG_NOT_FOUND" /* FLAG_NOT_FOUND */,
@@ -544,6 +578,12 @@ async function evaluate2(id, flagKey, defaultValue, entities) {
544
578
  mode: datafile.metrics.mode
545
579
  }
546
580
  };
581
+ trackEvaluation(controller, {
582
+ flagKey,
583
+ variant: "unknown",
584
+ reason: result2.reason
585
+ });
586
+ return result2;
547
587
  }
548
588
  const evalStartTime = Date.now();
549
589
  const result = evaluate({
@@ -562,6 +602,14 @@ async function evaluate2(id, flagKey, defaultValue, entities) {
562
602
  outcomeType: result.reason !== "error" /* ERROR */ ? result.outcomeType : void 0
563
603
  });
564
604
  }
605
+ trackEvaluation(controller, {
606
+ flagKey,
607
+ variant: getVariantIdentifier(
608
+ flagDefinition,
609
+ result
610
+ ),
611
+ reason: result.reason
612
+ });
565
613
  return Object.assign(result, {
566
614
  metrics: {
567
615
  evaluationMs: evaluationDurationMs,
@@ -587,6 +635,11 @@ async function bulkEvaluate2(id, flags, entities) {
587
635
  reason: "error" /* ERROR */,
588
636
  errorMessage
589
637
  };
638
+ trackEvaluation(controller, {
639
+ flagKey: flag.key,
640
+ variant: "unknown",
641
+ reason: "error" /* ERROR */
642
+ });
590
643
  }
591
644
  return results2;
592
645
  }
@@ -618,6 +671,11 @@ async function bulkEvaluate2(id, flags, entities) {
618
671
  errorMessage: `@vercel/flags-core: Definition not found for flag "${key}"`,
619
672
  metrics: { evaluationMs: 0, ...baseMetrics }
620
673
  };
674
+ trackEvaluation(controller, {
675
+ flagKey: key,
676
+ variant: "unknown",
677
+ reason: "error" /* ERROR */
678
+ });
621
679
  continue;
622
680
  }
623
681
  toEvaluate[key] = { definition: flagDefinition, defaultValue };
@@ -639,6 +697,14 @@ async function bulkEvaluate2(id, flags, entities) {
639
697
  outcomeType: result.reason !== "error" /* ERROR */ ? result.outcomeType : void 0
640
698
  });
641
699
  }
700
+ trackEvaluation(controller, {
701
+ flagKey: key,
702
+ variant: getVariantIdentifier(
703
+ toEvaluate[key].definition,
704
+ result
705
+ ),
706
+ reason: result.reason
707
+ });
642
708
  results[key] = Object.assign(result, {
643
709
  metrics: { evaluationMs: evaluationDurationMs, ...baseMetrics }
644
710
  });
@@ -691,7 +757,7 @@ function createCreateRawClient(fns) {
691
757
  },
692
758
  getDatafile: async () => {
693
759
  const instance = controllerInstanceMap.get(id);
694
- if (_optionalChain([instance, 'optionalAccess', _20 => _20.initPromise])) {
760
+ if (_optionalChain([instance, 'optionalAccess', _24 => _24.initPromise])) {
695
761
  try {
696
762
  await instance.initPromise;
697
763
  } catch (e) {
@@ -704,7 +770,7 @@ function createCreateRawClient(fns) {
704
770
  },
705
771
  evaluate: async (flagKey, defaultValue, entities) => {
706
772
  const instance = controllerInstanceMap.get(id);
707
- if (!_optionalChain([instance, 'optionalAccess', _21 => _21.initialized])) {
773
+ if (!_optionalChain([instance, 'optionalAccess', _25 => _25.initialized])) {
708
774
  try {
709
775
  await api.initialize();
710
776
  } catch (e2) {
@@ -714,7 +780,7 @@ function createCreateRawClient(fns) {
714
780
  },
715
781
  bulkEvaluate: async (flags, entities) => {
716
782
  const instance = controllerInstanceMap.get(id);
717
- if (!_optionalChain([instance, 'optionalAccess', _22 => _22.initialized])) {
783
+ if (!_optionalChain([instance, 'optionalAccess', _26 => _26.initialized])) {
718
784
  try {
719
785
  await api.initialize();
720
786
  } catch (e3) {
@@ -783,8 +849,8 @@ async function readBundledDefinitions(auth) {
783
849
  return { definitions: null, state: "missing-entry" };
784
850
  }
785
851
 
786
- // src/utils/usage-tracker.ts
787
- var _functions = require('@vercel/functions');
852
+ // src/utils/ingest.ts
853
+ var _oidc = require('@vercel/oidc');
788
854
 
789
855
  // src/utils/backoff.ts
790
856
  var DEFAULT_BASE_MS = 250;
@@ -795,64 +861,304 @@ function getRetryDelayMs(attempt, options = {}) {
795
861
  const ceiling = Math.min(capMs, baseMs * 2 ** Math.max(0, attempt - 1));
796
862
  return Math.floor(Math.random() * ceiling);
797
863
  }
798
- function getJitteredWaitMs(baseMs, ratio) {
799
- const min = baseMs * (1 - ratio);
800
- const span = baseMs * 2 * ratio;
801
- return Math.floor(min + Math.random() * span);
802
- }
803
864
 
804
- // src/utils/usage-tracker.ts
805
- var RESOLVED_VOID = Promise.resolve();
806
- var isDebugMode = _optionalChain([process, 'access', _23 => _23.env, 'access', _24 => _24.DEBUG, 'optionalAccess', _25 => _25.includes, 'call', _26 => _26("@vercel/flags-core")]);
865
+ // src/utils/ingest.ts
866
+ var MAX_RETRIES = 3;
867
+ var EVALUATING_OIDC_TOKEN_HEADER = "X-Vercel-Flags-Evaluating-OIDC-Token";
868
+ var isDebugMode = _optionalChain([process, 'access', _27 => _27.env, 'access', _28 => _28.DEBUG, 'optionalAccess', _29 => _29.includes, 'call', _30 => _30("@vercel/flags-core")]);
807
869
  var debugLog = (...args) => {
808
870
  if (!isDebugMode) return;
809
871
  console.log(...args);
810
872
  };
811
- var MAX_RETRIES = 3;
812
- var MAX_BATCH_SIZE = 50;
813
- var MAX_BATCH_WAIT_MS = 5e3;
814
- var BATCH_WAIT_JITTER_RATIO = 0.2;
873
+ async function getEvaluatingOidcToken(auth) {
874
+ if (!auth.sdkKey) return void 0;
875
+ try {
876
+ return await _oidc.getVercelOidcToken.call(void 0, );
877
+ } catch (e4) {
878
+ return void 0;
879
+ }
880
+ }
881
+ async function getIngestHeaders(options) {
882
+ const token = await options.auth.resolveToken();
883
+ const evaluatingOidcToken = await getEvaluatingOidcToken(options.auth);
884
+ return {
885
+ "Content-Type": "application/json",
886
+ Authorization: `Bearer ${token}`,
887
+ "User-Agent": `VercelFlagsCore/${version}`,
888
+ ...process.env.VERCEL_ENV ? { "X-Vercel-Env": process.env.VERCEL_ENV } : null,
889
+ ...evaluatingOidcToken ? { [EVALUATING_OIDC_TOKEN_HEADER]: evaluatingOidcToken } : null,
890
+ ...isDebugMode ? { "x-vercel-debug-ingest": "1" } : null
891
+ };
892
+ }
893
+ async function sendIngestEvents(options, events, flushId) {
894
+ const eventsToSend = events.map((event) => event.ingestEvent());
895
+ for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
896
+ try {
897
+ const response = await options.fetch(`${options.host}/v1/ingest`, {
898
+ method: "POST",
899
+ headers: await getIngestHeaders(options),
900
+ body: JSON.stringify(eventsToSend)
901
+ });
902
+ debugLog(
903
+ `@vercel/flags-core: Ingest response ${response.status} for ${eventsToSend.length} events on ${response.headers.get("x-vercel-id")}`
904
+ );
905
+ if (response.ok) {
906
+ break;
907
+ }
908
+ throw new Error(
909
+ `Ingest endpoint responded with status ${response.status} for ${eventsToSend.length} events on request ${response.headers.get("x-vercel-id")}.
910
+ Response body: ${await response.text().catch(() => null)}`
911
+ );
912
+ } catch (error) {
913
+ console.error(
914
+ `@vercel/flags-core: Error sending events (attempt=${attempt}/${MAX_RETRIES} flushId=${flushId}):`,
915
+ error
916
+ );
917
+ if (attempt < MAX_RETRIES) {
918
+ const delayMs = getRetryDelayMs(attempt);
919
+ await new Promise((res) => setTimeout(res, delayMs));
920
+ } else {
921
+ console.error(
922
+ `@vercel/flags-core: Dropped ${eventsToSend.length} events after ${MAX_RETRIES} attempts (flushId=${flushId})`
923
+ );
924
+ }
925
+ }
926
+ }
927
+ }
928
+
929
+ // src/utils/request-context.ts
815
930
  var SYMBOL_FOR_REQ_CONTEXT = /* @__PURE__ */ Symbol.for("@vercel/request-context");
816
931
  var fromSymbol = globalThis;
817
932
  function getRequestContext() {
818
933
  try {
819
- const ctx = _optionalChain([fromSymbol, 'access', _27 => _27[SYMBOL_FOR_REQ_CONTEXT], 'optionalAccess', _28 => _28.get, 'optionalCall', _29 => _29()]);
934
+ const ctx = _optionalChain([fromSymbol, 'access', _31 => _31[SYMBOL_FOR_REQ_CONTEXT], 'optionalAccess', _32 => _32.get, 'optionalCall', _33 => _33()]);
820
935
  if (ctx && Object.hasOwn(ctx, "headers")) {
821
936
  return {
822
937
  ctx,
823
938
  headers: ctx.headers
824
939
  };
825
940
  }
826
- return { ctx, headers: void 0 };
827
- } catch (e4) {
941
+ return { ctx: void 0, headers: void 0 };
942
+ } catch (e5) {
828
943
  return { ctx: void 0, headers: void 0 };
829
944
  }
830
945
  }
831
- var UsageTracker = (_class = class {
832
- __init() {this.flushCounter = 0}
946
+
947
+ // src/utils/scheduler.ts
948
+ var _functions = require('@vercel/functions');
949
+ var MAX_COUNT = 50;
950
+ var IDLE_FLUSH_WAIT_MS = 5e3;
951
+ var MAX_FLUSH_WAIT_MS = 6e4;
952
+ var Scheduler = (_class = class {
953
+ constructor(onFlush) {;_class.prototype.__init.call(this);_class.prototype.__init2.call(this);_class.prototype.__init3.call(this);_class.prototype.__init4.call(this);_class.prototype.__init5.call(this);
954
+ this.onFlush = onFlush;
955
+ }
956
+ __init() {this.count = 0}
957
+ __init2() {this.resolveWait = null}
958
+ __init3() {this.pending = null}
959
+ __init4() {this.idleTimeout = null}
960
+ __init5() {this.maxTimeout = null}
961
+ increment() {
962
+ this.count += 1;
963
+ if (this.count >= MAX_COUNT) {
964
+ this.resolveScheduledFlush();
965
+ }
966
+ }
967
+ scheduleFlush() {
968
+ if (!this.pending) {
969
+ this.pending = (async () => {
970
+ await new Promise((res) => {
971
+ this.resolveWait = res;
972
+ });
973
+ this.reset();
974
+ await this.onFlush();
975
+ })();
976
+ try {
977
+ _functions.waitUntil.call(void 0, this.pending);
978
+ } catch (e6) {
979
+ }
980
+ this.maxTimeout = setTimeout(
981
+ () => this.resolveScheduledFlush(),
982
+ MAX_FLUSH_WAIT_MS
983
+ );
984
+ }
985
+ this.resetIdleTimeout();
986
+ }
987
+ /**
988
+ * Resolves any in-flight scheduled flush and waits for its ingest to finish.
989
+ */
990
+ async shutdown() {
991
+ this.clearTimeouts();
992
+ const pending = this.pending;
993
+ _optionalChain([this, 'access', _34 => _34.resolveWait, 'optionalCall', _35 => _35()]);
994
+ if (pending) await pending;
995
+ }
996
+ resetIdleTimeout() {
997
+ if (this.idleTimeout) clearTimeout(this.idleTimeout);
998
+ this.idleTimeout = setTimeout(
999
+ () => this.resolveScheduledFlush(),
1000
+ IDLE_FLUSH_WAIT_MS
1001
+ );
1002
+ }
1003
+ resolveScheduledFlush() {
1004
+ this.clearTimeouts();
1005
+ _optionalChain([this, 'access', _36 => _36.resolveWait, 'optionalCall', _37 => _37()]);
1006
+ }
1007
+ reset() {
1008
+ this.pending = null;
1009
+ this.resolveWait = null;
1010
+ this.count = 0;
1011
+ this.clearTimeouts();
1012
+ }
1013
+ clearTimeouts() {
1014
+ if (this.idleTimeout) {
1015
+ clearTimeout(this.idleTimeout);
1016
+ this.idleTimeout = null;
1017
+ }
1018
+ if (this.maxTimeout) {
1019
+ clearTimeout(this.maxTimeout);
1020
+ this.maxTimeout = null;
1021
+ }
1022
+ }
1023
+ }, _class);
1024
+
1025
+ // src/utils/usage/flags-config-read.ts
1026
+ var FlagsConfigReadEvent = class {
833
1027
 
834
- __init2() {this.trackedRequests = /* @__PURE__ */ new WeakSet()}
835
- __init3() {this.batcher = {
836
- events: [],
837
- resolveWait: null,
838
- pending: null
839
- }}
840
- constructor(options) {;_class.prototype.__init.call(this);_class.prototype.__init2.call(this);_class.prototype.__init3.call(this);
1028
+ constructor(headers, options) {
1029
+ this.payload = {
1030
+ deploymentId: process.env.VERCEL_DEPLOYMENT_ID,
1031
+ region: process.env.VERCEL_REGION
1032
+ };
1033
+ if (headers) {
1034
+ this.payload.vercelRequestId = _nullishCoalesce(headers["x-vercel-id"], () => ( void 0));
1035
+ this.payload.invocationHost = _nullishCoalesce(headers.host, () => ( void 0));
1036
+ }
1037
+ if (options) {
1038
+ this.payload.configOrigin = options.configOrigin;
1039
+ if (options.cacheStatus !== void 0) {
1040
+ this.payload.cacheStatus = options.cacheStatus;
1041
+ }
1042
+ if (options.cacheAction !== void 0) {
1043
+ this.payload.cacheAction = options.cacheAction;
1044
+ }
1045
+ if (options.cacheIsFirstRead !== void 0) {
1046
+ this.payload.cacheIsFirstRead = options.cacheIsFirstRead;
1047
+ }
1048
+ if (options.cacheIsBlocking !== void 0) {
1049
+ this.payload.cacheIsBlocking = options.cacheIsBlocking;
1050
+ }
1051
+ if (options.duration !== void 0) {
1052
+ this.payload.duration = options.duration;
1053
+ }
1054
+ if (options.configUpdatedAt !== void 0) {
1055
+ this.payload.configUpdatedAt = options.configUpdatedAt;
1056
+ }
1057
+ if (options.mode !== void 0) {
1058
+ this.payload.mode = options.mode;
1059
+ }
1060
+ if (options.revision !== void 0) {
1061
+ this.payload.revision = String(options.revision);
1062
+ }
1063
+ }
1064
+ const environment = process.env.VERCEL_ENV || process.env.NODE_ENV || void 0;
1065
+ if (environment) {
1066
+ this.payload.environment = environment;
1067
+ }
1068
+ }
1069
+ ingestEvent() {
1070
+ return {
1071
+ type: "FLAGS_CONFIG_READ",
1072
+ ts: Date.now(),
1073
+ payload: this.payload
1074
+ };
1075
+ }
1076
+ };
1077
+
1078
+ // src/utils/usage/flags-evaluation.ts
1079
+ function evaluationBatchKey(options) {
1080
+ return JSON.stringify([
1081
+ options.flagKey,
1082
+ options.variant,
1083
+ options.reason,
1084
+ _nullishCoalesce(options.clientName, () => ( null))
1085
+ ]);
1086
+ }
1087
+ var FlagsEvaluationEvent = class {
1088
+
1089
+ constructor(eventOptions) {
1090
+ this.payload = {
1091
+ flagKey: eventOptions.flagKey,
1092
+ variant: eventOptions.variant,
1093
+ reason: eventOptions.reason,
1094
+ count: 1
1095
+ };
1096
+ if (eventOptions.clientName) {
1097
+ this.payload.clientName = eventOptions.clientName;
1098
+ }
1099
+ Object.defineProperty(this, "batchKey", {
1100
+ value: JSON.stringify([
1101
+ this.payload.flagKey,
1102
+ this.payload.variant,
1103
+ this.payload.reason,
1104
+ _nullishCoalesce(this.payload.clientName, () => ( null))
1105
+ ]),
1106
+ enumerable: false
1107
+ });
1108
+ }
1109
+ increment() {
1110
+ this.payload.count += 1;
1111
+ }
1112
+ ingestEvent() {
1113
+ return {
1114
+ type: "FLAGS_EVALUATION",
1115
+ ts: Date.now(),
1116
+ payload: this.payload
1117
+ };
1118
+ }
1119
+ };
1120
+
1121
+ // src/utils/weak-map.ts
1122
+ var BetterWeakMap = (_class2 = class {constructor() { _class2.prototype.__init6.call(this);_class2.prototype.__init7.call(this); }
1123
+ __init6() {this._values = []}
1124
+ __init7() {this._keys = []}
1125
+ set(key, value) {
1126
+ this._keys.push(key);
1127
+ this._values.push(value);
1128
+ }
1129
+ values() {
1130
+ return this._values;
1131
+ }
1132
+ has(key) {
1133
+ return this._keys.includes(key);
1134
+ }
1135
+ keys() {
1136
+ return this._keys;
1137
+ }
1138
+ clear() {
1139
+ this._values = [];
1140
+ this._keys = [];
1141
+ }
1142
+ }, _class2);
1143
+
1144
+ // src/utils/usage-tracker.ts
1145
+ var UsageTracker = (_class3 = class {
1146
+ __init8() {this.flushCount = 0}
1147
+
1148
+
1149
+ __init9() {this.readEvents = new BetterWeakMap()}
1150
+ __init10() {this.evaluationEvents = /* @__PURE__ */ new Map()}
1151
+ constructor(options) {;_class3.prototype.__init8.call(this);_class3.prototype.__init9.call(this);_class3.prototype.__init10.call(this);
841
1152
  this.options = options;
1153
+ this.scheduler = new Scheduler(() => this.flushEvents());
842
1154
  }
843
1155
  /**
844
1156
  * Triggers an immediate flush of any pending events.
845
1157
  * Returns a promise that resolves when the flush completes.
846
1158
  */
847
- flush() {
848
- if (this.batcher.pending) {
849
- _optionalChain([this, 'access', _30 => _30.batcher, 'access', _31 => _31.resolveWait, 'optionalCall', _32 => _32()]);
850
- return this.batcher.pending;
851
- }
852
- if (this.batcher.events.length > 0) {
853
- return this.flushEvents();
854
- }
855
- return RESOLVED_VOID;
1159
+ async shutdown() {
1160
+ await this.scheduler.shutdown();
1161
+ await this.flushEvents();
856
1162
  }
857
1163
  /**
858
1164
  * Tracks a config read event. Deduplicates by request context.
@@ -861,132 +1167,53 @@ var UsageTracker = (_class = class {
861
1167
  try {
862
1168
  const { ctx, headers } = getRequestContext();
863
1169
  if (!ctx) return;
864
- if (this.trackedRequests.has(ctx)) return;
865
- this.trackedRequests.add(ctx);
866
- const event = {
867
- type: "FLAGS_CONFIG_READ",
868
- ts: Date.now(),
869
- payload: {
870
- deploymentId: process.env.VERCEL_DEPLOYMENT_ID,
871
- region: process.env.VERCEL_REGION
872
- }
873
- };
874
- if (headers) {
875
- event.payload.vercelRequestId = _nullishCoalesce(headers["x-vercel-id"], () => ( void 0));
876
- event.payload.invocationHost = _nullishCoalesce(headers.host, () => ( void 0));
877
- }
878
- if (options) {
879
- event.payload.configOrigin = options.configOrigin;
880
- if (options.cacheStatus !== void 0) {
881
- event.payload.cacheStatus = options.cacheStatus;
882
- }
883
- if (options.cacheAction !== void 0) {
884
- event.payload.cacheAction = options.cacheAction;
885
- }
886
- if (options.cacheIsFirstRead !== void 0) {
887
- event.payload.cacheIsFirstRead = options.cacheIsFirstRead;
888
- }
889
- if (options.cacheIsBlocking !== void 0) {
890
- event.payload.cacheIsBlocking = options.cacheIsBlocking;
891
- }
892
- if (options.duration !== void 0) {
893
- event.payload.duration = options.duration;
894
- }
895
- if (options.configUpdatedAt !== void 0) {
896
- event.payload.configUpdatedAt = options.configUpdatedAt;
897
- }
898
- if (options.mode !== void 0) {
899
- event.payload.mode = options.mode;
900
- }
901
- if (options.revision !== void 0) {
902
- event.payload.revision = String(options.revision);
903
- }
904
- }
905
- const environment = process.env.VERCEL_ENV || process.env.NODE_ENV || void 0;
906
- if (environment) {
907
- event.payload.environment = environment;
1170
+ if (this.readEvents.has(ctx)) {
1171
+ return;
908
1172
  }
909
- this.batcher.events.push(event);
910
- this.scheduleFlush();
1173
+ this.readEvents.set(ctx, new FlagsConfigReadEvent(headers, options));
1174
+ this.scheduler.scheduleFlush();
1175
+ this.scheduler.increment();
911
1176
  } catch (error) {
912
1177
  console.error("@vercel/flags-core: Failed to record event:", error);
913
1178
  }
914
1179
  }
915
- scheduleFlush() {
916
- if (!this.batcher.pending) {
917
- let timeout = null;
918
- const pending = (async () => {
919
- await new Promise((res) => {
920
- this.batcher.resolveWait = res;
921
- timeout = setTimeout(
922
- res,
923
- getJitteredWaitMs(MAX_BATCH_WAIT_MS, BATCH_WAIT_JITTER_RATIO)
924
- );
925
- });
926
- this.batcher.pending = null;
927
- this.batcher.resolveWait = null;
928
- if (timeout) clearTimeout(timeout);
929
- await this.flushEvents();
930
- })();
931
- try {
932
- _functions.waitUntil.call(void 0, pending);
933
- } catch (e5) {
1180
+ /**
1181
+ * Tracks a flag evaluation event.
1182
+ */
1183
+ trackEvaluation(options) {
1184
+ try {
1185
+ const batchKey = evaluationBatchKey(options);
1186
+ const existingEvent = this.evaluationEvents.get(batchKey);
1187
+ if (existingEvent) {
1188
+ existingEvent.increment();
1189
+ } else {
1190
+ this.evaluationEvents.set(batchKey, new FlagsEvaluationEvent(options));
1191
+ this.scheduler.increment();
934
1192
  }
935
- this.batcher.pending = pending;
936
- }
937
- if (this.batcher.events.length >= MAX_BATCH_SIZE) {
938
- _optionalChain([this, 'access', _33 => _33.batcher, 'access', _34 => _34.resolveWait, 'optionalCall', _35 => _35()]);
1193
+ this.scheduler.scheduleFlush();
1194
+ } catch (error) {
1195
+ console.error(
1196
+ "@vercel/flags-core: Failed to record evaluation event:",
1197
+ error
1198
+ );
939
1199
  }
940
1200
  }
1201
+ /**
1202
+ * Send all events to the ingest service
1203
+ */
941
1204
  async flushEvents() {
942
- if (this.batcher.events.length === 0) return;
943
- const eventsToSend = this.batcher.events;
944
- this.batcher.events = [];
945
- const flushId = ++this.flushCounter;
946
- for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
947
- try {
948
- const token = await this.options.auth.resolveToken();
949
- const response = await this.options.fetch(
950
- `${this.options.host}/v1/ingest`,
951
- {
952
- method: "POST",
953
- headers: {
954
- "Content-Type": "application/json",
955
- Authorization: `Bearer ${token}`,
956
- "User-Agent": `VercelFlagsCore/${version}`,
957
- ...process.env.VERCEL_ENV ? { "X-Vercel-Env": process.env.VERCEL_ENV } : null,
958
- ...isDebugMode ? { "x-vercel-debug-ingest": "1" } : null
959
- },
960
- body: JSON.stringify(eventsToSend)
961
- }
962
- );
963
- debugLog(
964
- `@vercel/flags-core: Ingest response ${response.status} for ${eventsToSend.length} events on ${response.headers.get("x-vercel-id")}`
965
- );
966
- if (response.ok) {
967
- break;
968
- }
969
- throw new Error(
970
- `Ingest endpoint responded with status ${response.status} for ${eventsToSend.length} events on request ${response.headers.get("x-vercel-id")}.
971
- Response body: ${await response.text().catch(() => null)}`
972
- );
973
- } catch (error) {
974
- console.error(
975
- `@vercel/flags-core: Error sending events (attempt=${attempt}/${MAX_RETRIES} flushId=${flushId}):`,
976
- error
977
- );
978
- if (attempt < MAX_RETRIES) {
979
- const delayMs = getRetryDelayMs(attempt);
980
- await new Promise((res) => setTimeout(res, delayMs));
981
- } else {
982
- console.error(
983
- `@vercel/flags-core: Dropped ${eventsToSend.length} events after ${MAX_RETRIES} attempts (flushId=${flushId})`
984
- );
985
- }
986
- }
987
- }
1205
+ const events = [
1206
+ ...this.readEvents.values(),
1207
+ ...this.evaluationEvents.values()
1208
+ ];
1209
+ if (events.length === 0) return;
1210
+ this.flushCount += 1;
1211
+ const flushId = this.flushCount;
1212
+ this.readEvents.clear();
1213
+ this.evaluationEvents.clear();
1214
+ await sendIngestEvents(this.options, events, flushId);
988
1215
  }
989
- }, _class);
1216
+ }, _class3);
990
1217
 
991
1218
  // src/errors.ts
992
1219
  var FallbackNotFoundError = class extends Error {
@@ -1045,7 +1272,7 @@ var BundledSource = class {
1045
1272
  */
1046
1273
  async tryLoad() {
1047
1274
  const result = await this.getResult();
1048
- if (_optionalChain([result, 'optionalAccess', _36 => _36.state]) === "ok" && result.definitions) {
1275
+ if (_optionalChain([result, 'optionalAccess', _38 => _38.state]) === "ok" && result.definitions) {
1049
1276
  return result.definitions;
1050
1277
  }
1051
1278
  return void 0;
@@ -1085,14 +1312,14 @@ async function fetchDatafile(options) {
1085
1312
  signal: controller.signal
1086
1313
  });
1087
1314
  clearTimeout(timeoutId);
1088
- _optionalChain([options, 'access', _37 => _37.signal, 'optionalAccess', _38 => _38.removeEventListener, 'call', _39 => _39("abort", onExternalAbort)]);
1315
+ _optionalChain([options, 'access', _39 => _39.signal, 'optionalAccess', _40 => _40.removeEventListener, 'call', _41 => _41("abort", onExternalAbort)]);
1089
1316
  if (!res.ok) {
1090
1317
  throw new Error(`Failed to fetch data: ${res.statusText}`);
1091
1318
  }
1092
1319
  return res.json();
1093
1320
  } catch (error) {
1094
1321
  clearTimeout(timeoutId);
1095
- _optionalChain([options, 'access', _40 => _40.signal, 'optionalAccess', _41 => _41.removeEventListener, 'call', _42 => _42("abort", onExternalAbort)]);
1322
+ _optionalChain([options, 'access', _42 => _42.signal, 'optionalAccess', _43 => _43.removeEventListener, 'call', _44 => _44("abort", onExternalAbort)]);
1096
1323
  throw error instanceof Error ? error : new Error("Unknown fetch error");
1097
1324
  }
1098
1325
  }
@@ -1141,13 +1368,14 @@ function normalizeOptions(options) {
1141
1368
  polling,
1142
1369
  buildStep,
1143
1370
  fetch: _nullishCoalesce(options.fetch, () => ( globalThis.fetch)),
1144
- host: "https://flags.vercel.com"
1371
+ host: "https://flags.vercel.com",
1372
+ clientName: options.clientName
1145
1373
  };
1146
1374
  }
1147
1375
 
1148
1376
  // src/controller/typed-emitter.ts
1149
- var TypedEmitter = (_class2 = class {constructor() { _class2.prototype.__init4.call(this); }
1150
- __init4() {this.handlers = /* @__PURE__ */ new Map()}
1377
+ var TypedEmitter = (_class4 = class {constructor() { _class4.prototype.__init11.call(this); }
1378
+ __init11() {this.handlers = /* @__PURE__ */ new Map()}
1151
1379
  on(event, handler) {
1152
1380
  let set = this.handlers.get(event);
1153
1381
  if (!set) {
@@ -1157,7 +1385,7 @@ var TypedEmitter = (_class2 = class {constructor() { _class2.prototype.__init4.c
1157
1385
  set.add(handler);
1158
1386
  }
1159
1387
  off(event, handler) {
1160
- _optionalChain([this, 'access', _43 => _43.handlers, 'access', _44 => _44.get, 'call', _45 => _45(event), 'optionalAccess', _46 => _46.delete, 'call', _47 => _47(handler)]);
1388
+ _optionalChain([this, 'access', _45 => _45.handlers, 'access', _46 => _46.get, 'call', _47 => _47(event), 'optionalAccess', _48 => _48.delete, 'call', _49 => _49(handler)]);
1161
1389
  }
1162
1390
  emit(event, ...args) {
1163
1391
  const set = this.handlers.get(event);
@@ -1167,7 +1395,7 @@ var TypedEmitter = (_class2 = class {constructor() { _class2.prototype.__init4.c
1167
1395
  }
1168
1396
  }
1169
1397
  }
1170
- }, _class2);
1398
+ }, _class4);
1171
1399
 
1172
1400
  // src/controller/polling-source.ts
1173
1401
  var PollingSource = class extends TypedEmitter {
@@ -1183,11 +1411,11 @@ var PollingSource = class extends TypedEmitter {
1183
1411
  * Emits 'data' on success, 'error' on failure.
1184
1412
  */
1185
1413
  async poll() {
1186
- if (_optionalChain([this, 'access', _48 => _48.abortController, 'optionalAccess', _49 => _49.signal, 'access', _50 => _50.aborted])) return;
1414
+ if (_optionalChain([this, 'access', _50 => _50.abortController, 'optionalAccess', _51 => _51.signal, 'access', _52 => _52.aborted])) return;
1187
1415
  try {
1188
1416
  const data = await fetchDatafile({
1189
1417
  ...this.config,
1190
- signal: _optionalChain([this, 'access', _51 => _51.abortController, 'optionalAccess', _52 => _52.signal])
1418
+ signal: _optionalChain([this, 'access', _53 => _53.abortController, 'optionalAccess', _54 => _54.signal])
1191
1419
  });
1192
1420
  this.emit("data", data);
1193
1421
  } catch (error) {
@@ -1216,7 +1444,7 @@ var PollingSource = class extends TypedEmitter {
1216
1444
  clearInterval(this.intervalId);
1217
1445
  this.intervalId = void 0;
1218
1446
  }
1219
- _optionalChain([this, 'access', _53 => _53.abortController, 'optionalAccess', _54 => _54.abort, 'call', _55 => _55()]);
1447
+ _optionalChain([this, 'access', _55 => _55.abortController, 'optionalAccess', _56 => _56.abort, 'call', _57 => _57()]);
1220
1448
  this.abortController = void 0;
1221
1449
  }
1222
1450
  };
@@ -1286,7 +1514,7 @@ async function connectStream(config, callbacks) {
1286
1514
  if (pingTimeoutId !== void 0) clearTimeout(pingTimeoutId);
1287
1515
  if (!initialDataReceived) return;
1288
1516
  pingTimeoutId = setTimeout(() => {
1289
- _optionalChain([responseBody, 'optionalAccess', _56 => _56.cancel, 'call', _57 => _57(), 'access', _58 => _58.catch, 'call', _59 => _59(() => {
1517
+ _optionalChain([responseBody, 'optionalAccess', _58 => _58.cancel, 'call', _59 => _59(), 'access', _60 => _60.catch, 'call', _61 => _61(() => {
1290
1518
  })]);
1291
1519
  connectionAbort.abort();
1292
1520
  }, PING_TIMEOUT_MS);
@@ -1305,7 +1533,7 @@ async function connectStream(config, callbacks) {
1305
1533
  if (vercelEnv) {
1306
1534
  headers["X-Vercel-Env"] = vercelEnv;
1307
1535
  }
1308
- const revision = _optionalChain([config, 'access', _60 => _60.revision, 'optionalCall', _61 => _61()]);
1536
+ const revision = _optionalChain([config, 'access', _62 => _62.revision, 'optionalCall', _63 => _63()]);
1309
1537
  if (revision !== void 0) {
1310
1538
  headers["X-Revision"] = String(revision);
1311
1539
  }
@@ -1351,7 +1579,7 @@ async function connectStream(config, callbacks) {
1351
1579
  let message;
1352
1580
  try {
1353
1581
  message = JSON.parse(line);
1354
- } catch (e6) {
1582
+ } catch (e7) {
1355
1583
  console.warn(
1356
1584
  "@vercel/flags-core: Failed to parse stream message, skipping"
1357
1585
  );
@@ -1367,7 +1595,7 @@ async function connectStream(config, callbacks) {
1367
1595
  resetPingTimeout();
1368
1596
  }
1369
1597
  if (message.type === "primed") {
1370
- _optionalChain([onPrimed, 'optionalCall', _62 => _62(message)]);
1598
+ _optionalChain([onPrimed, 'optionalCall', _64 => _64(message)]);
1371
1599
  retryCount = 0;
1372
1600
  if (!initialDataReceived) {
1373
1601
  initialDataReceived = true;
@@ -1390,7 +1618,7 @@ async function connectStream(config, callbacks) {
1390
1618
  clearTimeout(pingTimeoutId);
1391
1619
  abortController.signal.removeEventListener("abort", onMainAbort);
1392
1620
  if (!abortController.signal.aborted) {
1393
- _optionalChain([onDisconnect, 'optionalCall', _63 => _63()]);
1621
+ _optionalChain([onDisconnect, 'optionalCall', _65 => _65()]);
1394
1622
  retryCount++;
1395
1623
  const elapsed = Date.now() - lastAttemptTime;
1396
1624
  const minGap = Math.max(0, BASE_RETRY_DELAY_MS - elapsed);
@@ -1411,7 +1639,7 @@ async function connectStream(config, callbacks) {
1411
1639
  if (!connectionAbort.signal.aborted) {
1412
1640
  console.error("@vercel/flags-core: Stream error", error);
1413
1641
  }
1414
- _optionalChain([onDisconnect, 'optionalCall', _64 => _64()]);
1642
+ _optionalChain([onDisconnect, 'optionalCall', _66 => _66()]);
1415
1643
  retryCount++;
1416
1644
  const elapsed = Date.now() - lastAttemptTime;
1417
1645
  const minGap = Math.max(0, BASE_RETRY_DELAY_MS - elapsed);
@@ -1490,7 +1718,7 @@ var StreamSource = class extends TypedEmitter {
1490
1718
  * Stop the stream connection.
1491
1719
  */
1492
1720
  stop() {
1493
- _optionalChain([this, 'access', _65 => _65.abortController, 'optionalAccess', _66 => _66.abort, 'call', _67 => _67()]);
1721
+ _optionalChain([this, 'access', _67 => _67.abortController, 'optionalAccess', _68 => _68.abort, 'call', _69 => _69()]);
1494
1722
  this.abortController = void 0;
1495
1723
  this.promise = void 0;
1496
1724
  }
@@ -1522,34 +1750,34 @@ function parseConfigUpdatedAt(value) {
1522
1750
  }
1523
1751
  return void 0;
1524
1752
  }
1525
- var Controller = (_class3 = class {
1753
+ var Controller = (_class5 = class {
1526
1754
 
1527
1755
  // State machine
1528
- __init5() {this.state = "idle"}
1756
+ __init12() {this.state = "idle"}
1529
1757
  // Data state — tagged with origin
1530
1758
 
1531
1759
  // Memoized data spread for read() / getDatafile().
1532
1760
  // Rebuilt only when `this.data` reference changes (e.g. on stream/poll update).
1533
1761
  // Holds the result of stripping `_origin`; metrics are appended per-call.
1534
- __init6() {this.dataViewSource = void 0}
1535
- __init7() {this.dataViewBase = void 0}
1762
+ __init13() {this.dataViewSource = void 0}
1763
+ __init14() {this.dataViewBase = void 0}
1536
1764
  // Sources (I/O delegates)
1537
1765
 
1538
1766
 
1539
1767
 
1540
1768
  // Usage tracking
1541
1769
 
1542
- __init8() {this.isFirstGetData = true}
1770
+ __init15() {this.isFirstGetData = true}
1543
1771
  // Build-step deduplication
1544
- __init9() {this.buildDataPromise = null}
1545
- __init10() {this.buildReadTracked = false}
1772
+ __init16() {this.buildDataPromise = null}
1773
+ __init17() {this.buildReadTracked = false}
1546
1774
  // Suppresses usage tracking when the SDK key is unauthorized
1547
- __init11() {this.unauthorized = false}
1548
- constructor(options) {;_class3.prototype.__init5.call(this);_class3.prototype.__init6.call(this);_class3.prototype.__init7.call(this);_class3.prototype.__init8.call(this);_class3.prototype.__init9.call(this);_class3.prototype.__init10.call(this);_class3.prototype.__init11.call(this);_class3.prototype.__init12.call(this);_class3.prototype.__init13.call(this);_class3.prototype.__init14.call(this);_class3.prototype.__init15.call(this);_class3.prototype.__init16.call(this);_class3.prototype.__init17.call(this);
1775
+ __init18() {this.unauthorized = false}
1776
+ constructor(options) {;_class5.prototype.__init12.call(this);_class5.prototype.__init13.call(this);_class5.prototype.__init14.call(this);_class5.prototype.__init15.call(this);_class5.prototype.__init16.call(this);_class5.prototype.__init17.call(this);_class5.prototype.__init18.call(this);_class5.prototype.__init19.call(this);_class5.prototype.__init20.call(this);_class5.prototype.__init21.call(this);_class5.prototype.__init22.call(this);_class5.prototype.__init23.call(this);_class5.prototype.__init24.call(this);
1549
1777
  this.options = normalizeOptions(options);
1550
1778
  this.streamSource = new StreamSource(
1551
1779
  this.options,
1552
- () => _optionalChain([this, 'access', _68 => _68.data, 'optionalAccess', _69 => _69.revision])
1780
+ () => _optionalChain([this, 'access', _70 => _70.data, 'optionalAccess', _71 => _71.revision])
1553
1781
  );
1554
1782
  this.pollingSource = new PollingSource(this.options);
1555
1783
  this.bundledSource = new BundledSource({
@@ -1563,32 +1791,32 @@ var Controller = (_class3 = class {
1563
1791
  this.usageTracker = new UsageTracker(this.options);
1564
1792
  }
1565
1793
  // Source event handlers (stored for cleanup)
1566
- __init12() {this.onStreamData = (data) => {
1794
+ __init19() {this.onStreamData = (data) => {
1567
1795
  if (this.isNewerData(data)) {
1568
1796
  this.data = tagData(data, "stream");
1569
1797
  }
1570
1798
  }}
1571
- __init13() {this.onStreamPrimed = () => {
1799
+ __init20() {this.onStreamPrimed = () => {
1572
1800
  if (this.state === "degraded" || this.state === "initializing:stream") {
1573
1801
  this.transition("streaming");
1574
1802
  }
1575
1803
  }}
1576
- __init14() {this.onStreamConnected = () => {
1804
+ __init21() {this.onStreamConnected = () => {
1577
1805
  if (this.state === "degraded" || this.state === "initializing:stream") {
1578
1806
  this.transition("streaming");
1579
1807
  }
1580
1808
  }}
1581
- __init15() {this.onStreamDisconnected = () => {
1809
+ __init22() {this.onStreamDisconnected = () => {
1582
1810
  if (this.state === "streaming") {
1583
1811
  this.transition("degraded");
1584
1812
  }
1585
1813
  }}
1586
- __init16() {this.onPollData = (data) => {
1814
+ __init23() {this.onPollData = (data) => {
1587
1815
  if (this.isNewerData(data)) {
1588
1816
  this.data = tagData(data, "poll");
1589
1817
  }
1590
1818
  }}
1591
- __init17() {this.onPollError = (error) => {
1819
+ __init24() {this.onPollError = (error) => {
1592
1820
  console.error("@vercel/flags-core: Poll failed:", error);
1593
1821
  }}
1594
1822
  // ---------------------------------------------------------------------------
@@ -1657,7 +1885,7 @@ var Controller = (_class3 = class {
1657
1885
  if (bundled) {
1658
1886
  this.data = tagData(bundled, "bundled");
1659
1887
  }
1660
- } catch (e7) {
1888
+ } catch (e8) {
1661
1889
  }
1662
1890
  }
1663
1891
  if (this.data) {
@@ -1726,7 +1954,7 @@ var Controller = (_class3 = class {
1726
1954
  this.pollingSource.stop();
1727
1955
  this.data = this.options.datafile ? tagData(this.options.datafile, "provided") : void 0;
1728
1956
  this.transition("shutdown");
1729
- await this.usageTracker.flush();
1957
+ await this.usageTracker.shutdown();
1730
1958
  }
1731
1959
  /**
1732
1960
  * Returns the datafile with metrics.
@@ -1759,7 +1987,7 @@ var Controller = (_class3 = class {
1759
1987
  this.data = tagData(fetched, "fetched");
1760
1988
  result = this.data;
1761
1989
  cacheStatus = "MISS";
1762
- } catch (e8) {
1990
+ } catch (e9) {
1763
1991
  throw new Error(
1764
1992
  "@vercel/flags-core: No flag definitions available. Initialize the client or provide a datafile."
1765
1993
  );
@@ -1878,7 +2106,7 @@ var Controller = (_class3 = class {
1878
2106
  return true;
1879
2107
  }
1880
2108
  return false;
1881
- } catch (e9) {
2109
+ } catch (e10) {
1882
2110
  return false;
1883
2111
  }
1884
2112
  }
@@ -1903,7 +2131,7 @@ var Controller = (_class3 = class {
1903
2131
  return true;
1904
2132
  }
1905
2133
  return false;
1906
- } catch (e10) {
2134
+ } catch (e11) {
1907
2135
  clearTimeout(timeoutId);
1908
2136
  return false;
1909
2137
  }
@@ -1953,7 +2181,7 @@ var Controller = (_class3 = class {
1953
2181
  fetch: this.options.fetch
1954
2182
  });
1955
2183
  return tagData(fetched, "fetched");
1956
- } catch (e11) {
2184
+ } catch (e12) {
1957
2185
  }
1958
2186
  throw new Error(
1959
2187
  "@vercel/flags-core: No flag definitions available during build. Provide a datafile or bundled definitions."
@@ -1987,7 +2215,7 @@ var Controller = (_class3 = class {
1987
2215
  this.data = tagData(fetched, "fetched");
1988
2216
  this.transition("degraded");
1989
2217
  return;
1990
- } catch (e12) {
2218
+ } catch (e13) {
1991
2219
  }
1992
2220
  }
1993
2221
  throw new Error(
@@ -2039,7 +2267,7 @@ var Controller = (_class3 = class {
2039
2267
  this.data = tagData(fetched, "fetched");
2040
2268
  this.transition("degraded");
2041
2269
  return [this.data, "MISS"];
2042
- } catch (e13) {
2270
+ } catch (e14) {
2043
2271
  }
2044
2272
  }
2045
2273
  throw new Error(
@@ -2091,11 +2319,11 @@ var Controller = (_class3 = class {
2091
2319
  duration: Date.now() - startTime,
2092
2320
  mode: mode === "streaming" ? "stream" : mode === "polling" ? "poll" : mode
2093
2321
  };
2094
- const configUpdatedAt = _optionalChain([this, 'access', _70 => _70.data, 'optionalAccess', _71 => _71.configUpdatedAt]);
2322
+ const configUpdatedAt = _optionalChain([this, 'access', _72 => _72.data, 'optionalAccess', _73 => _73.configUpdatedAt]);
2095
2323
  if (typeof configUpdatedAt === "number") {
2096
2324
  trackOptions.configUpdatedAt = configUpdatedAt;
2097
2325
  }
2098
- const revision = _optionalChain([this, 'access', _72 => _72.data, 'optionalAccess', _73 => _73.revision]);
2326
+ const revision = _optionalChain([this, 'access', _74 => _74.data, 'optionalAccess', _75 => _75.revision]);
2099
2327
  if (typeof revision === "number") {
2100
2328
  trackOptions.revision = revision;
2101
2329
  }
@@ -2104,10 +2332,20 @@ var Controller = (_class3 = class {
2104
2332
  }
2105
2333
  this.usageTracker.trackRead(trackOptions);
2106
2334
  }
2107
- }, _class3);
2335
+ /**
2336
+ * Tracks a flag evaluation for usage analytics.
2337
+ */
2338
+ trackEvaluation(options) {
2339
+ if (this.unauthorized) return;
2340
+ this.usageTracker.trackEvaluation({
2341
+ ...options,
2342
+ clientName: _nullishCoalesce(options.clientName, () => ( this.options.clientName))
2343
+ });
2344
+ }
2345
+ }, _class5);
2108
2346
 
2109
2347
  // src/controller/auth.ts
2110
- var _oidc = require('@vercel/oidc');
2348
+
2111
2349
 
2112
2350
  // src/utils/sdk-keys.ts
2113
2351
  var SDK_KEY_REGEX = /^vf_(?:server|client)_/;
@@ -2118,7 +2356,7 @@ function parseSdkKeyFromFlagsConnectionString(text) {
2118
2356
  const params = new URLSearchParams(text.slice(6));
2119
2357
  const sdkKey = params.get("sdkKey");
2120
2358
  if (sdkKey && SDK_KEY_REGEX.test(sdkKey)) return sdkKey;
2121
- } catch (e14) {
2359
+ } catch (e15) {
2122
2360
  }
2123
2361
  return null;
2124
2362
  }
@@ -2127,7 +2365,7 @@ function parseSdkKeyFromFlagsConnectionString(text) {
2127
2365
  async function getOidcToken() {
2128
2366
  try {
2129
2367
  return await _oidc.getVercelOidcToken.call(void 0, );
2130
- } catch (e15) {
2368
+ } catch (e16) {
2131
2369
  throw new Error(
2132
2370
  [
2133
2371
  "@vercel/flags-core: Failed to get OIDC token.",
@@ -2222,7 +2460,7 @@ function setCacheLife() {
2222
2460
  try {
2223
2461
  _cache.cacheLife.call(void 0, { revalidate: 0, expire: 0 });
2224
2462
  _cache.cacheLife.call(void 0, { stale: 60 });
2225
- } catch (e16) {
2463
+ } catch (e17) {
2226
2464
  }
2227
2465
  }
2228
2466
  var cachedFns = {
@@ -2271,4 +2509,4 @@ var { flagsClient, resetDefaultFlagsClient, createClient } = make(
2271
2509
 
2272
2510
 
2273
2511
  exports.ResolutionReason = ResolutionReason; exports.evaluate = evaluate; exports.FallbackNotFoundError = FallbackNotFoundError; exports.FallbackEntryNotFoundError = FallbackEntryNotFoundError; exports.Controller = Controller; exports.flagsClient = flagsClient; exports.resetDefaultFlagsClient = resetDefaultFlagsClient; exports.createClient = createClient;
2274
- //# sourceMappingURL=chunk-CSSU7O2G.cjs.map
2512
+ //# sourceMappingURL=chunk-LPIUDLYE.cjs.map