@vercel/flags-core 1.6.0-fa24822-20260629103652 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -2
- package/dist/{chunk-75KYNTIB.js → chunk-FBTDSIHP.js} +38 -42
- package/dist/chunk-FBTDSIHP.js.map +1 -0
- package/dist/{chunk-KIUEUJE2.cjs → chunk-HTK4XPSW.cjs} +84 -88
- package/dist/chunk-HTK4XPSW.cjs.map +1 -0
- package/dist/{chunk-ETOLBVKS.js → chunk-L653ZWQA.js} +38 -42
- package/dist/chunk-L653ZWQA.js.map +1 -0
- package/dist/{chunk-7PXEYYPZ.cjs → chunk-VT24U6D2.cjs} +84 -88
- package/dist/chunk-VT24U6D2.cjs.map +1 -0
- package/dist/index.default.cjs +2 -2
- package/dist/index.default.d.cts +5 -0
- package/dist/index.default.d.ts +5 -0
- package/dist/index.default.js +1 -1
- package/dist/index.next-js.cjs +2 -2
- package/dist/index.next-js.d.cts +5 -0
- package/dist/index.next-js.d.ts +5 -0
- 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-75KYNTIB.js.map +0 -1
- package/dist/chunk-7PXEYYPZ.cjs.map +0 -1
- package/dist/chunk-ETOLBVKS.js.map +0 -1
- package/dist/chunk-KIUEUJE2.cjs.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,10 +1,25 @@
|
|
|
1
1
|
# @vercel/flags-core
|
|
2
2
|
|
|
3
|
-
## 1.6.0
|
|
3
|
+
## 1.6.0
|
|
4
4
|
|
|
5
5
|
### Minor Changes
|
|
6
6
|
|
|
7
|
-
- [#401](https://github.com/vercel/flags/pull/401) [`
|
|
7
|
+
- [#401](https://github.com/vercel/flags/pull/401) [`9dff590`](https://github.com/vercel/flags/commit/9dff590bd5628bd93098637c2e9b3d1a043e4d4b) Thanks [@luismeyer](https://github.com/luismeyer)! - Add aggregated flag evaluation telemetry and a `clientName` option for the Vercel Flags client.
|
|
8
|
+
|
|
9
|
+
## 1.5.2
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- [#416](https://github.com/vercel/flags/pull/416) [`f60c99d`](https://github.com/vercel/flags/commit/f60c99d70741e5e8e5af0a069deaf34a3129a27e) Thanks [@dferber90](https://github.com/dferber90)! - Fix datafile serialization across the RSC server/client boundary.
|
|
14
|
+
|
|
15
|
+
Evaluation memoized scaled split weights and compiled regexes by attaching
|
|
16
|
+
symbol-keyed properties directly onto objects inside the datafile. While
|
|
17
|
+
symbols are invisible to `JSON.stringify`, React Server Components serialization
|
|
18
|
+
walks objects directly and chokes on these (notably the non-serializable
|
|
19
|
+
`RegExp`), so datafiles could no longer be passed from server to client
|
|
20
|
+
components. Memoization now uses module-level `WeakMap`s keyed by the
|
|
21
|
+
outcome/rhs objects, leaving datafile objects pristine while keeping identical
|
|
22
|
+
caching semantics and lifetime.
|
|
8
23
|
|
|
9
24
|
## 1.5.1
|
|
10
25
|
|
|
@@ -48,21 +48,21 @@ var Packed;
|
|
|
48
48
|
// src/evaluate.ts
|
|
49
49
|
var MAX_REGEX_INPUT_LENGTH = 1e4;
|
|
50
50
|
var UINT32_MAX = 4294967295;
|
|
51
|
-
var
|
|
52
|
-
var
|
|
51
|
+
var scaledWeightsCache = /* @__PURE__ */ new WeakMap();
|
|
52
|
+
var compiledRegexCache = /* @__PURE__ */ new WeakMap();
|
|
53
53
|
function getScaledWeights(outcome) {
|
|
54
|
-
const cached = outcome
|
|
54
|
+
const cached = scaledWeightsCache.get(outcome);
|
|
55
55
|
if (cached) return cached;
|
|
56
56
|
const total = sum(outcome.weights);
|
|
57
57
|
const scaled = outcome.weights.map((w) => w / total * UINT32_MAX);
|
|
58
|
-
outcome
|
|
58
|
+
scaledWeightsCache.set(outcome, scaled);
|
|
59
59
|
return scaled;
|
|
60
60
|
}
|
|
61
61
|
function getCompiledRegex(rhs) {
|
|
62
|
-
const cached = rhs
|
|
62
|
+
const cached = compiledRegexCache.get(rhs);
|
|
63
63
|
if (cached) return cached;
|
|
64
64
|
const compiled = new RegExp(rhs.pattern, rhs.flags);
|
|
65
|
-
rhs
|
|
65
|
+
compiledRegexCache.set(rhs, compiled);
|
|
66
66
|
return compiled;
|
|
67
67
|
}
|
|
68
68
|
function exhaustivenessCheck(_) {
|
|
@@ -494,7 +494,7 @@ function findWeightedIndex(weights, value, maxValue) {
|
|
|
494
494
|
}
|
|
495
495
|
|
|
496
496
|
// package.json
|
|
497
|
-
var version = "1.6.0
|
|
497
|
+
var version = "1.6.0";
|
|
498
498
|
|
|
499
499
|
// src/lib/report-value.ts
|
|
500
500
|
function internalReportValue(key, value, data) {
|
|
@@ -869,7 +869,9 @@ function getJitteredWaitMs(baseMs, ratio) {
|
|
|
869
869
|
|
|
870
870
|
// src/utils/ingest.ts
|
|
871
871
|
var MAX_RETRIES = 3;
|
|
872
|
+
var MAX_EVENTS_PER_REQUEST = 2e3;
|
|
872
873
|
var EVALUATING_OIDC_TOKEN_HEADER = "X-Vercel-Flags-OIDC-Token";
|
|
874
|
+
var FLUSH_REASON_HEADER = "X-Vercel-Flags-Flush-Reason";
|
|
873
875
|
var isDebugMode = process.env.DEBUG?.includes("@vercel/flags-core");
|
|
874
876
|
var debugLog = (...args) => {
|
|
875
877
|
if (!isDebugMode) return;
|
|
@@ -883,25 +885,36 @@ async function getEvaluatingOidcToken(auth) {
|
|
|
883
885
|
return void 0;
|
|
884
886
|
}
|
|
885
887
|
}
|
|
886
|
-
async function getIngestHeaders(options) {
|
|
888
|
+
async function getIngestHeaders(options, flushReason) {
|
|
887
889
|
const token = await options.auth.resolveToken();
|
|
888
890
|
const evaluatingOidcToken = await getEvaluatingOidcToken(options.auth);
|
|
889
891
|
return {
|
|
890
892
|
"Content-Type": "application/json",
|
|
891
893
|
Authorization: `Bearer ${token}`,
|
|
892
894
|
"User-Agent": `VercelFlagsCore/${version}`,
|
|
895
|
+
[FLUSH_REASON_HEADER]: flushReason,
|
|
893
896
|
...process.env.VERCEL_ENV ? { "X-Vercel-Env": process.env.VERCEL_ENV } : null,
|
|
894
897
|
...evaluatingOidcToken ? { [EVALUATING_OIDC_TOKEN_HEADER]: evaluatingOidcToken } : null,
|
|
895
898
|
...isDebugMode ? { "x-vercel-debug-ingest": "1" } : null
|
|
896
899
|
};
|
|
897
900
|
}
|
|
898
|
-
async function sendIngestEvents(options, events, flushId) {
|
|
901
|
+
async function sendIngestEvents(options, events, flushId, flushReason) {
|
|
899
902
|
const eventsToSend = events.map((event) => event.ingestEvent());
|
|
903
|
+
for (let i = 0; i < eventsToSend.length; i += MAX_EVENTS_PER_REQUEST) {
|
|
904
|
+
await sendIngestChunk(
|
|
905
|
+
options,
|
|
906
|
+
eventsToSend.slice(i, i + MAX_EVENTS_PER_REQUEST),
|
|
907
|
+
flushId,
|
|
908
|
+
flushReason
|
|
909
|
+
);
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
async function sendIngestChunk(options, eventsToSend, flushId, flushReason) {
|
|
900
913
|
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
901
914
|
try {
|
|
902
915
|
const response = await options.fetch(`${options.host}/v1/ingest`, {
|
|
903
916
|
method: "POST",
|
|
904
|
-
headers: await getIngestHeaders(options),
|
|
917
|
+
headers: await getIngestHeaders(options, flushReason),
|
|
905
918
|
body: JSON.stringify(eventsToSend)
|
|
906
919
|
});
|
|
907
920
|
debugLog(
|
|
@@ -951,7 +964,6 @@ function getRequestContext() {
|
|
|
951
964
|
|
|
952
965
|
// src/utils/scheduler.ts
|
|
953
966
|
import { waitUntil } from "@vercel/functions";
|
|
954
|
-
var MAX_COUNT = 50;
|
|
955
967
|
var IDLE_FLUSH_WAIT_MS = 5e3;
|
|
956
968
|
var IDLE_FLUSH_JITTER_RATIO = 0.2;
|
|
957
969
|
var MAX_FLUSH_WAIT_MS = 6e4;
|
|
@@ -959,32 +971,25 @@ var Scheduler = class {
|
|
|
959
971
|
constructor(onFlush) {
|
|
960
972
|
this.onFlush = onFlush;
|
|
961
973
|
}
|
|
962
|
-
count = 0;
|
|
963
974
|
resolveWait = null;
|
|
964
975
|
pending = null;
|
|
965
976
|
idleTimeout = null;
|
|
966
977
|
maxTimeout = null;
|
|
967
|
-
increment() {
|
|
968
|
-
this.count += 1;
|
|
969
|
-
if (this.count >= MAX_COUNT) {
|
|
970
|
-
this.resolveScheduledFlush();
|
|
971
|
-
}
|
|
972
|
-
}
|
|
973
978
|
scheduleFlush() {
|
|
974
979
|
if (!this.pending) {
|
|
975
980
|
this.pending = (async () => {
|
|
976
|
-
await new Promise((res) => {
|
|
981
|
+
const reason = await new Promise((res) => {
|
|
977
982
|
this.resolveWait = res;
|
|
978
983
|
});
|
|
979
984
|
this.reset();
|
|
980
|
-
await this.onFlush();
|
|
985
|
+
await this.onFlush(reason);
|
|
981
986
|
})();
|
|
982
987
|
try {
|
|
983
988
|
waitUntil(this.pending);
|
|
984
989
|
} catch {
|
|
985
990
|
}
|
|
986
991
|
this.maxTimeout = setTimeout(
|
|
987
|
-
() => this.resolveScheduledFlush(),
|
|
992
|
+
() => this.resolveScheduledFlush("max_timeout"),
|
|
988
993
|
MAX_FLUSH_WAIT_MS
|
|
989
994
|
);
|
|
990
995
|
}
|
|
@@ -996,24 +1001,23 @@ var Scheduler = class {
|
|
|
996
1001
|
async shutdown() {
|
|
997
1002
|
this.clearTimeouts();
|
|
998
1003
|
const pending = this.pending;
|
|
999
|
-
this.
|
|
1004
|
+
this.resolveScheduledFlush("shutdown");
|
|
1000
1005
|
if (pending) await pending;
|
|
1001
1006
|
}
|
|
1002
1007
|
resetIdleTimeout() {
|
|
1003
1008
|
if (this.idleTimeout) clearTimeout(this.idleTimeout);
|
|
1004
1009
|
this.idleTimeout = setTimeout(
|
|
1005
|
-
() => this.resolveScheduledFlush(),
|
|
1010
|
+
() => this.resolveScheduledFlush("idle_timeout"),
|
|
1006
1011
|
getJitteredWaitMs(IDLE_FLUSH_WAIT_MS, IDLE_FLUSH_JITTER_RATIO)
|
|
1007
1012
|
);
|
|
1008
1013
|
}
|
|
1009
|
-
resolveScheduledFlush() {
|
|
1014
|
+
resolveScheduledFlush(reason) {
|
|
1010
1015
|
this.clearTimeouts();
|
|
1011
|
-
this.resolveWait?.();
|
|
1016
|
+
this.resolveWait?.(reason);
|
|
1012
1017
|
}
|
|
1013
1018
|
reset() {
|
|
1014
1019
|
this.pending = null;
|
|
1015
1020
|
this.resolveWait = null;
|
|
1016
|
-
this.count = 0;
|
|
1017
1021
|
this.clearTimeouts();
|
|
1018
1022
|
}
|
|
1019
1023
|
clearTimeouts() {
|
|
@@ -1124,10 +1128,6 @@ var FlagsEvaluationEvent = class {
|
|
|
1124
1128
|
};
|
|
1125
1129
|
|
|
1126
1130
|
// src/utils/usage-tracker.ts
|
|
1127
|
-
function evaluationsDisabled() {
|
|
1128
|
-
const value = process.env.VERCEL_FLAGS_ENABLE_METRICS?.toLowerCase();
|
|
1129
|
-
return value !== "true";
|
|
1130
|
-
}
|
|
1131
1131
|
var UsageTracker = class {
|
|
1132
1132
|
flushCount = 0;
|
|
1133
1133
|
options;
|
|
@@ -1137,7 +1137,7 @@ var UsageTracker = class {
|
|
|
1137
1137
|
evaluationEvents = /* @__PURE__ */ new Map();
|
|
1138
1138
|
constructor(options) {
|
|
1139
1139
|
this.options = options;
|
|
1140
|
-
this.scheduler = new Scheduler(() => this.flushEvents());
|
|
1140
|
+
this.scheduler = new Scheduler((reason) => this.flushEvents(reason));
|
|
1141
1141
|
}
|
|
1142
1142
|
/**
|
|
1143
1143
|
* Triggers an immediate flush of any pending events.
|
|
@@ -1145,7 +1145,7 @@ var UsageTracker = class {
|
|
|
1145
1145
|
*/
|
|
1146
1146
|
async shutdown() {
|
|
1147
1147
|
await this.scheduler.shutdown();
|
|
1148
|
-
await this.flushEvents();
|
|
1148
|
+
await this.flushEvents("shutdown");
|
|
1149
1149
|
}
|
|
1150
1150
|
/**
|
|
1151
1151
|
* Tracks a config read event. Deduplicates by request context.
|
|
@@ -1158,7 +1158,6 @@ var UsageTracker = class {
|
|
|
1158
1158
|
this.trackedRequests.add(ctx);
|
|
1159
1159
|
this.readEvents.push(new FlagsConfigReadEvent(headers, options));
|
|
1160
1160
|
this.scheduler.scheduleFlush();
|
|
1161
|
-
this.scheduler.increment();
|
|
1162
1161
|
} catch (error) {
|
|
1163
1162
|
console.error("@vercel/flags-core: Failed to record event:", error);
|
|
1164
1163
|
}
|
|
@@ -1168,9 +1167,6 @@ var UsageTracker = class {
|
|
|
1168
1167
|
*/
|
|
1169
1168
|
trackEvaluation(options) {
|
|
1170
1169
|
try {
|
|
1171
|
-
if (evaluationsDisabled()) {
|
|
1172
|
-
return;
|
|
1173
|
-
}
|
|
1174
1170
|
const bucketedOptions = {
|
|
1175
1171
|
...options,
|
|
1176
1172
|
bucketTs: minuteBucketTs()
|
|
@@ -1184,7 +1180,6 @@ var UsageTracker = class {
|
|
|
1184
1180
|
batchKey,
|
|
1185
1181
|
new FlagsEvaluationEvent(bucketedOptions)
|
|
1186
1182
|
);
|
|
1187
|
-
this.scheduler.increment();
|
|
1188
1183
|
}
|
|
1189
1184
|
this.scheduler.scheduleFlush();
|
|
1190
1185
|
} catch (error) {
|
|
@@ -1197,14 +1192,14 @@ var UsageTracker = class {
|
|
|
1197
1192
|
/**
|
|
1198
1193
|
* Send all events to the ingest service
|
|
1199
1194
|
*/
|
|
1200
|
-
async flushEvents() {
|
|
1195
|
+
async flushEvents(flushReason) {
|
|
1201
1196
|
const events = [...this.readEvents, ...this.evaluationEvents.values()];
|
|
1202
1197
|
if (events.length === 0) return;
|
|
1203
1198
|
this.flushCount += 1;
|
|
1204
1199
|
const flushId = this.flushCount;
|
|
1205
1200
|
this.readEvents = [];
|
|
1206
1201
|
this.evaluationEvents.clear();
|
|
1207
|
-
await sendIngestEvents(this.options, events, flushId);
|
|
1202
|
+
await sendIngestEvents(this.options, events, flushId, flushReason);
|
|
1208
1203
|
}
|
|
1209
1204
|
};
|
|
1210
1205
|
|
|
@@ -1362,7 +1357,8 @@ function normalizeOptions(options) {
|
|
|
1362
1357
|
buildStep,
|
|
1363
1358
|
fetch: options.fetch ?? globalThis.fetch,
|
|
1364
1359
|
host: "https://flags.vercel.com",
|
|
1365
|
-
clientName: options.clientName
|
|
1360
|
+
clientName: options.clientName,
|
|
1361
|
+
disableMetrics: options.disableMetrics ?? false
|
|
1366
1362
|
};
|
|
1367
1363
|
}
|
|
1368
1364
|
|
|
@@ -2333,7 +2329,7 @@ var Controller = class {
|
|
|
2333
2329
|
* Tracks a flag evaluation for usage analytics.
|
|
2334
2330
|
*/
|
|
2335
2331
|
trackEvaluation(options) {
|
|
2336
|
-
if (this.unauthorized) return;
|
|
2332
|
+
if (this.unauthorized || this.options.disableMetrics) return;
|
|
2337
2333
|
this.usageTracker.trackEvaluation({
|
|
2338
2334
|
...options,
|
|
2339
2335
|
clientName: options.clientName ?? this.options.clientName
|
|
@@ -2484,4 +2480,4 @@ export {
|
|
|
2484
2480
|
resetDefaultFlagsClient,
|
|
2485
2481
|
createClient
|
|
2486
2482
|
};
|
|
2487
|
-
//# sourceMappingURL=chunk-
|
|
2483
|
+
//# sourceMappingURL=chunk-FBTDSIHP.js.map
|