@vercel/flags-core 1.0.1 → 1.1.0-3ca407e-20260306094417

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.
Files changed (35) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +2 -0
  3. package/dist/{chunk-5CDNQMQ6.js → chunk-MDVR4MXT.js} +990 -531
  4. package/dist/chunk-MDVR4MXT.js.map +1 -0
  5. package/dist/{chunk-BVKIEFFY.js → chunk-NEIY3ICC.js} +985 -527
  6. package/dist/chunk-NEIY3ICC.js.map +1 -0
  7. package/dist/chunk-OFRPUCDY.cjs +1976 -0
  8. package/dist/chunk-OFRPUCDY.cjs.map +1 -0
  9. package/dist/chunk-WRX7DC3S.cjs +1982 -0
  10. package/dist/chunk-WRX7DC3S.cjs.map +1 -0
  11. package/dist/index.default.cjs +22 -0
  12. package/dist/index.default.cjs.map +1 -0
  13. package/dist/index.default.d.cts +244 -0
  14. package/dist/index.default.d.ts +78 -83
  15. package/dist/index.default.js +4 -3
  16. package/dist/index.next-js.cjs +22 -0
  17. package/dist/index.next-js.cjs.map +1 -0
  18. package/dist/index.next-js.d.cts +231 -0
  19. package/dist/index.next-js.d.ts +78 -83
  20. package/dist/index.next-js.js +4 -3
  21. package/dist/openfeature.default.cjs +157 -0
  22. package/dist/openfeature.default.cjs.map +1 -0
  23. package/dist/openfeature.default.d.cts +48 -0
  24. package/dist/openfeature.default.d.ts +1 -1
  25. package/dist/openfeature.default.js +1 -1
  26. package/dist/openfeature.next-js.cjs +157 -0
  27. package/dist/openfeature.next-js.cjs.map +1 -0
  28. package/dist/openfeature.next-js.d.cts +49 -0
  29. package/dist/openfeature.next-js.d.ts +1 -1
  30. package/dist/openfeature.next-js.js +1 -1
  31. package/dist/{types-508sZTBu.d.ts → types-DtnecWgl.d.cts} +27 -5
  32. package/dist/types-DtnecWgl.d.ts +527 -0
  33. package/package.json +24 -11
  34. package/dist/chunk-5CDNQMQ6.js.map +0 -1
  35. package/dist/chunk-BVKIEFFY.js.map +0 -1
@@ -0,0 +1,1982 @@
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
2
+ var _cache = require('next/cache');
3
+
4
+ // src/evaluate.ts
5
+ var _jsxxhash = require('js-xxhash');
6
+
7
+ // src/types.ts
8
+ var ResolutionReason = /* @__PURE__ */ ((ResolutionReason2) => {
9
+ ResolutionReason2["PAUSED"] = "paused";
10
+ ResolutionReason2["TARGET_MATCH"] = "target_match";
11
+ ResolutionReason2["RULE_MATCH"] = "rule_match";
12
+ ResolutionReason2["FALLTHROUGH"] = "fallthrough";
13
+ ResolutionReason2["ERROR"] = "error";
14
+ return ResolutionReason2;
15
+ })(ResolutionReason || {});
16
+ var Original;
17
+ ((Original2) => {
18
+ let AccessorType;
19
+ ((AccessorType2) => {
20
+ AccessorType2["SEGMENT"] = "segment";
21
+ AccessorType2["ENTITY"] = "entity";
22
+ })(AccessorType = Original2.AccessorType || (Original2.AccessorType = {}));
23
+ })(Original || (Original = {}));
24
+ var Packed;
25
+ ((Packed2) => {
26
+ let AccessorType;
27
+ ((AccessorType2) => {
28
+ AccessorType2["SEGMENT"] = "segment";
29
+ AccessorType2["ENTITY"] = "entity";
30
+ })(AccessorType = Packed2.AccessorType || (Packed2.AccessorType = {}));
31
+ })(Packed || (Packed = {}));
32
+
33
+ // src/evaluate.ts
34
+ var MAX_REGEX_INPUT_LENGTH = 1e4;
35
+ function exhaustivenessCheck(_) {
36
+ throw new Error("Exhaustiveness check failed");
37
+ }
38
+ function getProperty(obj, pathArray) {
39
+ return pathArray.reduce((acc, key) => {
40
+ if (acc && key in acc) {
41
+ return acc[key];
42
+ }
43
+ return void 0;
44
+ }, obj);
45
+ }
46
+ function access(lhs, params) {
47
+ if (Array.isArray(lhs)) return getProperty(params.entities, lhs);
48
+ if (lhs === Packed.AccessorType.SEGMENT)
49
+ throw new Error("Unexpected segment");
50
+ throw new Error("Accessor not implemented");
51
+ }
52
+ function isString(input) {
53
+ return typeof input === "string";
54
+ }
55
+ function isNumber(input) {
56
+ return typeof input === "number";
57
+ }
58
+ function isArray(input) {
59
+ return Array.isArray(input);
60
+ }
61
+ function lower(input) {
62
+ if (typeof input === "string") return input.toLowerCase();
63
+ if (Array.isArray(input)) return input.map(lower);
64
+ return input;
65
+ }
66
+ var IGNORE_CASE_COMPARATORS = /* @__PURE__ */ new Set([
67
+ "eq" /* EQ */,
68
+ "!eq" /* NOT_EQ */,
69
+ "oneOf" /* ONE_OF */,
70
+ "!oneOf" /* NOT_ONE_OF */,
71
+ "containsAllOf" /* CONTAINS_ALL_OF */,
72
+ "containsAnyOf" /* CONTAINS_ANY_OF */,
73
+ "containsNoneOf" /* CONTAINS_NONE_OF */,
74
+ "startsWith" /* STARTS_WITH */,
75
+ "!startsWith" /* NOT_STARTS_WITH */,
76
+ "endsWith" /* ENDS_WITH */,
77
+ "!endsWith" /* NOT_ENDS_WITH */,
78
+ "contains" /* CONTAINS */,
79
+ "!contains" /* NOT_CONTAINS */
80
+ ]);
81
+ function matchTargetList(targets, params) {
82
+ for (const kind in targets) {
83
+ const attributes = targets[kind];
84
+ for (const attribute in attributes) {
85
+ const entity = access([kind, attribute], params);
86
+ if (isString(entity) && attributes[attribute].includes(entity))
87
+ return true;
88
+ }
89
+ }
90
+ return false;
91
+ }
92
+ function matchSegment(segment, params) {
93
+ if (segment.include && matchTargetList(segment.include, params)) return true;
94
+ if (segment.exclude && matchTargetList(segment.exclude, params)) return false;
95
+ if (!_optionalChain([segment, 'access', _2 => _2.rules, 'optionalAccess', _3 => _3.length])) return false;
96
+ const firstMatchingRule = segment.rules.find(
97
+ (rule) => matchConditions(rule.conditions, params)
98
+ );
99
+ if (!firstMatchingRule) return false;
100
+ return handleSegmentOutcome(params, firstMatchingRule.outcome);
101
+ }
102
+ function matchSegmentCondition(cmp, rhs, params) {
103
+ switch (cmp) {
104
+ case "eq" /* EQ */: {
105
+ const segment = _optionalChain([params, 'access', _4 => _4.segments, 'optionalAccess', _5 => _5[rhs]]);
106
+ if (!segment) return false;
107
+ return matchSegment(segment, params);
108
+ }
109
+ case "!eq" /* NOT_EQ */: {
110
+ const segment = _optionalChain([params, 'access', _6 => _6.segments, 'optionalAccess', _7 => _7[rhs]]);
111
+ if (!segment) return false;
112
+ return !matchSegment(segment, params);
113
+ }
114
+ case "oneOf" /* ONE_OF */: {
115
+ if (!isArray(rhs)) return false;
116
+ const segmentIds = rhs;
117
+ return segmentIds.some((segmentId) => {
118
+ const segment = _optionalChain([params, 'access', _8 => _8.segments, 'optionalAccess', _9 => _9[segmentId]]);
119
+ if (!segment) return false;
120
+ return matchSegment(segment, params);
121
+ });
122
+ }
123
+ case "!oneOf" /* NOT_ONE_OF */: {
124
+ const segmentIds = rhs;
125
+ return segmentIds.every((segmentId) => {
126
+ const segment = _optionalChain([params, 'access', _10 => _10.segments, 'optionalAccess', _11 => _11[segmentId]]);
127
+ if (!segment) return false;
128
+ return !matchSegment(segment, params);
129
+ });
130
+ }
131
+ default:
132
+ throw new Error(`Comparator ${cmp} not implemented for segment`);
133
+ }
134
+ }
135
+ function matchConditions(conditions, params) {
136
+ return conditions.every((condition) => {
137
+ const [lhsAccessor, cmpKey, rawRhs, options] = condition;
138
+ const hasIgnoreCaseFlag = typeof options === "string" && options.includes("i");
139
+ const hasIgnoreCaseOption = typeof options === "object" && options !== null && options.i === true;
140
+ const ignoreCase = IGNORE_CASE_COMPARATORS.has(cmpKey) && (hasIgnoreCaseFlag || hasIgnoreCaseOption);
141
+ if (lhsAccessor === Packed.AccessorType.SEGMENT) {
142
+ return rawRhs && matchSegmentCondition(cmpKey, rawRhs, params);
143
+ }
144
+ const lhs = ignoreCase ? lower(access(lhsAccessor, params)) : access(lhsAccessor, params);
145
+ const rhs = ignoreCase ? lower(rawRhs) : rawRhs;
146
+ try {
147
+ switch (cmpKey) {
148
+ case "eq" /* EQ */:
149
+ return lhs === rhs;
150
+ case "!eq" /* NOT_EQ */:
151
+ return lhs !== rhs;
152
+ case "oneOf" /* ONE_OF */:
153
+ return isArray(rhs) && rhs.includes(lhs);
154
+ case "!oneOf" /* NOT_ONE_OF */:
155
+ return isArray(rhs) && typeof lhs !== "undefined" && !rhs.includes(lhs);
156
+ case "containsAllOf" /* CONTAINS_ALL_OF */: {
157
+ if (!Array.isArray(rhs) || !Array.isArray(lhs)) return false;
158
+ const lhsSet = new Set(lhs.filter(isString));
159
+ if (lhsSet.size === lhs.length) {
160
+ return rhs.filter(isString).every((item) => lhsSet.has(item));
161
+ }
162
+ return rhs.every((item) => lhs.includes(item));
163
+ }
164
+ case "containsAnyOf" /* CONTAINS_ANY_OF */: {
165
+ if (!Array.isArray(rhs) || !Array.isArray(lhs)) return false;
166
+ const rhsSet = new Set(rhs.filter(isString));
167
+ return lhs.some(
168
+ rhsSet.size === rhs.length ? (
169
+ // try to use a set if the rhs is a list of strings - O(1)
170
+ (item) => rhsSet.has(item)
171
+ ) : (
172
+ // otherwise we need to iterate over the values - O(n)
173
+ (item) => rhs.includes(item)
174
+ )
175
+ );
176
+ }
177
+ case "containsNoneOf" /* CONTAINS_NONE_OF */: {
178
+ if (!Array.isArray(rhs)) return false;
179
+ if (!Array.isArray(lhs)) return true;
180
+ const rhsSet = new Set(rhs.filter(isString));
181
+ return lhs.every(
182
+ rhsSet.size === rhs.length ? (
183
+ // try to use a set if the rhs is a list of strings - O(1)
184
+ (item) => !rhsSet.has(item)
185
+ ) : (
186
+ // otherwise we need to iterate over the values - O(n)
187
+ (item) => !rhs.includes(item)
188
+ )
189
+ );
190
+ }
191
+ case "startsWith" /* STARTS_WITH */:
192
+ return isString(lhs) && isString(rhs) && lhs.startsWith(rhs);
193
+ case "!startsWith" /* NOT_STARTS_WITH */:
194
+ return isString(lhs) && isString(rhs) && !lhs.startsWith(rhs);
195
+ case "endsWith" /* ENDS_WITH */:
196
+ return isString(lhs) && isString(rhs) && lhs.endsWith(rhs);
197
+ case "!endsWith" /* NOT_ENDS_WITH */:
198
+ return isString(lhs) && isString(rhs) && !lhs.endsWith(rhs);
199
+ case "contains" /* CONTAINS */:
200
+ return isString(lhs) && isString(rhs) && lhs.includes(rhs);
201
+ case "!contains" /* NOT_CONTAINS */:
202
+ return isString(lhs) && isString(rhs) && !lhs.includes(rhs);
203
+ case "ex" /* EXISTS */:
204
+ return lhs !== void 0 && lhs !== null;
205
+ case "!ex" /* NOT_EXISTS */:
206
+ return lhs === void 0 || lhs === null;
207
+ case "gt" /* GT */:
208
+ if (lhs === null || lhs === void 0) return false;
209
+ return (isNumber(rhs) || isString(rhs)) && lhs > rhs;
210
+ case "gte" /* GTE */:
211
+ if (lhs === null || lhs === void 0) return false;
212
+ return (isNumber(rhs) || isString(rhs)) && lhs >= rhs;
213
+ case "lt" /* LT */:
214
+ if (lhs === null || lhs === void 0) return false;
215
+ return (isNumber(rhs) || isString(rhs)) && lhs < rhs;
216
+ case "lte" /* LTE */:
217
+ if (lhs === null || lhs === void 0) return false;
218
+ return (isNumber(rhs) || isString(rhs)) && lhs <= rhs;
219
+ case "regex" /* REGEX */:
220
+ if (isString(lhs) && lhs.length <= MAX_REGEX_INPUT_LENGTH && typeof rhs === "object" && !Array.isArray(rhs) && _optionalChain([rhs, 'optionalAccess', _12 => _12.type]) === "regex") {
221
+ return new RegExp(rhs.pattern, rhs.flags).test(lhs);
222
+ }
223
+ return false;
224
+ case "!regex" /* NOT_REGEX */:
225
+ if (isString(lhs) && lhs.length <= MAX_REGEX_INPUT_LENGTH && typeof rhs === "object" && !Array.isArray(rhs) && _optionalChain([rhs, 'optionalAccess', _13 => _13.type]) === "regex") {
226
+ return !new RegExp(rhs.pattern, rhs.flags).test(lhs);
227
+ }
228
+ return false;
229
+ case "before" /* BEFORE */: {
230
+ if (!isString(lhs) || !isString(rhs)) return false;
231
+ const a = new Date(lhs);
232
+ const b = new Date(rhs);
233
+ return a.getTime() < b.getTime();
234
+ }
235
+ case "after" /* AFTER */: {
236
+ if (!isString(lhs) || !isString(rhs)) return false;
237
+ const a = new Date(lhs);
238
+ const b = new Date(rhs);
239
+ return a.getTime() > b.getTime();
240
+ }
241
+ default: {
242
+ const _x = cmpKey;
243
+ return false;
244
+ }
245
+ }
246
+ } catch (error) {
247
+ console.error("flags: Error matching conditions", error);
248
+ return false;
249
+ }
250
+ });
251
+ }
252
+ function sum(list) {
253
+ return list.reduce((acc, n) => acc + n, 0);
254
+ }
255
+ function handleSegmentOutcome(params, outcome) {
256
+ if (outcome === 1) return true;
257
+ switch (outcome.type) {
258
+ case "split": {
259
+ const lhs = access(outcome.base, params);
260
+ if (typeof lhs !== "string") return false;
261
+ const maxValue = 1e5;
262
+ if (outcome.passPromille <= 0) return false;
263
+ if (outcome.passPromille >= maxValue) return true;
264
+ const value = _jsxxhash.xxHash32.call(void 0, lhs, params.definition.seed) % maxValue;
265
+ return value < outcome.passPromille;
266
+ }
267
+ default: {
268
+ const { type } = outcome;
269
+ exhaustivenessCheck(type);
270
+ }
271
+ }
272
+ }
273
+ function getVariant(variants, index) {
274
+ if (index < 0 || index >= variants.length) {
275
+ throw new Error(
276
+ `@vercel/flags-core: Invalid variant index ${index}, variants length is ${variants.length}`
277
+ );
278
+ }
279
+ return variants[index];
280
+ }
281
+ function handleOutcome(params, outcome) {
282
+ if (typeof outcome === "number") {
283
+ return {
284
+ value: getVariant(params.definition.variants, outcome),
285
+ outcomeType: "value" /* VALUE */
286
+ };
287
+ }
288
+ switch (outcome.type) {
289
+ case "split": {
290
+ const lhs = access(outcome.base, params);
291
+ const defaultOutcome = getVariant(
292
+ params.definition.variants,
293
+ outcome.defaultVariant
294
+ );
295
+ if (typeof lhs !== "string") {
296
+ return { value: defaultOutcome, outcomeType: "split" /* SPLIT */ };
297
+ }
298
+ const maxValue = 4294967295;
299
+ const value = _jsxxhash.xxHash32.call(void 0, lhs, params.definition.seed);
300
+ const sumOfWeights = sum(outcome.weights);
301
+ const scaledWeights = outcome.weights.map(
302
+ (weight) => weight / sumOfWeights * maxValue
303
+ );
304
+ const variantIndex = findWeightedIndex(scaledWeights, value, maxValue);
305
+ return {
306
+ value: variantIndex === -1 ? defaultOutcome : getVariant(params.definition.variants, variantIndex),
307
+ outcomeType: "split" /* SPLIT */
308
+ };
309
+ }
310
+ default: {
311
+ const { type } = outcome;
312
+ exhaustivenessCheck(type);
313
+ }
314
+ }
315
+ }
316
+ function evaluate(params, _visited) {
317
+ const envConfig = params.definition.environments[params.environment];
318
+ if (typeof envConfig === "number") {
319
+ return Object.assign(handleOutcome(params, envConfig), {
320
+ reason: "paused" /* PAUSED */
321
+ });
322
+ }
323
+ if (!envConfig) {
324
+ return {
325
+ reason: "error" /* ERROR */,
326
+ errorMessage: `Could not find envConfig for "${params.environment}"`,
327
+ value: params.defaultValue
328
+ };
329
+ }
330
+ if ("reuse" in envConfig) {
331
+ const reuseEnvConfig = params.definition.environments[envConfig.reuse];
332
+ if (reuseEnvConfig === void 0) {
333
+ throw new Error(
334
+ `Could not find envConfig for "${envConfig.reuse}" when reusing`
335
+ );
336
+ }
337
+ const visited = _nullishCoalesce(_visited, () => ( /* @__PURE__ */ new Set()));
338
+ if (visited.has(envConfig.reuse)) {
339
+ return {
340
+ reason: "error" /* ERROR */,
341
+ errorMessage: `Circular environment reuse detected: "${envConfig.reuse}"`,
342
+ value: params.defaultValue
343
+ };
344
+ }
345
+ visited.add(params.environment);
346
+ return evaluate({ ...params, environment: envConfig.reuse }, visited);
347
+ }
348
+ if (envConfig.targets) {
349
+ const matchedIndex = envConfig.targets.findIndex(
350
+ (targetList) => matchTargetList(targetList, params)
351
+ );
352
+ if (matchedIndex > -1) {
353
+ return Object.assign(handleOutcome(params, matchedIndex), {
354
+ reason: "target_match" /* TARGET_MATCH */
355
+ });
356
+ }
357
+ }
358
+ const firstMatchingRule = envConfig.rules ? envConfig.rules.find((rule) => matchConditions(rule.conditions, params)) : void 0;
359
+ if (firstMatchingRule) {
360
+ return Object.assign(handleOutcome(params, firstMatchingRule.outcome), {
361
+ reason: "rule_match" /* RULE_MATCH */
362
+ });
363
+ }
364
+ return Object.assign(handleOutcome(params, envConfig.fallthrough), {
365
+ reason: "fallthrough" /* FALLTHROUGH */
366
+ });
367
+ }
368
+ function findWeightedIndex(weights, value, maxValue) {
369
+ if (value < 0 || value >= maxValue) return -1;
370
+ let sum2 = 0;
371
+ for (let i = 0; i < weights.length; i++) {
372
+ sum2 += weights[i];
373
+ if (value < sum2) return i;
374
+ }
375
+ return -1;
376
+ }
377
+
378
+ // package.json
379
+ var version = "1.1.0-3ca407e-20260306094417";
380
+
381
+ // src/lib/report-value.ts
382
+ function internalReportValue(key, value, data) {
383
+ const symbol = /* @__PURE__ */ Symbol.for("@vercel/request-context");
384
+ const ctx = _optionalChain([Reflect, 'access', _14 => _14.get, 'call', _15 => _15(globalThis, symbol), 'optionalAccess', _16 => _16.get, 'call', _17 => _17()]);
385
+ _optionalChain([ctx, 'optionalAccess', _18 => _18.flags, 'optionalAccess', _19 => _19.reportValue, 'call', _20 => _20(key, value, {
386
+ sdkVersion: version,
387
+ ...data
388
+ })]);
389
+ }
390
+
391
+ // src/controller-fns.ts
392
+ var controllerInstanceMap = /* @__PURE__ */ new Map();
393
+ function getInstance(id) {
394
+ const instance = controllerInstanceMap.get(id);
395
+ if (!instance) {
396
+ throw new Error(
397
+ `@vercel/flags-core: Client instance ${id} not found. It may have been shut down.`
398
+ );
399
+ }
400
+ return instance;
401
+ }
402
+ function initialize(id) {
403
+ return getInstance(id).controller.initialize();
404
+ }
405
+ function shutdown(id) {
406
+ return getInstance(id).controller.shutdown();
407
+ }
408
+ function getDatafile(id) {
409
+ return getInstance(id).controller.getDatafile();
410
+ }
411
+ function getFallbackDatafile(id) {
412
+ const ds = getInstance(id).controller;
413
+ if (ds.getFallbackDatafile) return ds.getFallbackDatafile();
414
+ throw new Error("flags: This data source does not support fallbacks");
415
+ }
416
+ async function evaluate2(id, flagKey, defaultValue, entities) {
417
+ const controller = getInstance(id).controller;
418
+ let datafile;
419
+ try {
420
+ datafile = await controller.read();
421
+ } catch (error) {
422
+ if (defaultValue !== void 0) {
423
+ return {
424
+ value: defaultValue,
425
+ reason: "error" /* ERROR */,
426
+ errorMessage: error instanceof Error ? error.message : "Failed to read datafile"
427
+ };
428
+ }
429
+ throw error;
430
+ }
431
+ const flagDefinition = datafile.definitions[flagKey];
432
+ if (flagDefinition === void 0) {
433
+ if (datafile.projectId) {
434
+ internalReportValue(flagKey, defaultValue, {
435
+ originProjectId: datafile.projectId,
436
+ originProvider: "vercel",
437
+ reason: "error" /* ERROR */
438
+ });
439
+ }
440
+ return {
441
+ value: defaultValue,
442
+ reason: "error" /* ERROR */,
443
+ errorCode: "FLAG_NOT_FOUND" /* FLAG_NOT_FOUND */,
444
+ errorMessage: `@vercel/flags-core: Definition not found for flag "${flagKey}"`,
445
+ metrics: {
446
+ evaluationMs: 0,
447
+ readMs: datafile.metrics.readMs,
448
+ source: datafile.metrics.source,
449
+ cacheStatus: datafile.metrics.cacheStatus,
450
+ connectionState: datafile.metrics.connectionState,
451
+ mode: datafile.metrics.mode
452
+ }
453
+ };
454
+ }
455
+ const evalStartTime = Date.now();
456
+ const result = evaluate({
457
+ defaultValue,
458
+ definition: flagDefinition,
459
+ environment: datafile.environment,
460
+ entities: _nullishCoalesce(entities, () => ( {})),
461
+ segments: datafile.segments
462
+ });
463
+ const evaluationDurationMs = Date.now() - evalStartTime;
464
+ if (datafile.projectId) {
465
+ internalReportValue(flagKey, result.value, {
466
+ originProjectId: datafile.projectId,
467
+ originProvider: "vercel",
468
+ reason: result.reason,
469
+ outcomeType: result.reason !== "error" /* ERROR */ ? result.outcomeType : void 0
470
+ });
471
+ }
472
+ return Object.assign(result, {
473
+ metrics: {
474
+ evaluationMs: evaluationDurationMs,
475
+ readMs: datafile.metrics.readMs,
476
+ source: datafile.metrics.source,
477
+ cacheStatus: datafile.metrics.cacheStatus,
478
+ connectionState: datafile.metrics.connectionState,
479
+ mode: datafile.metrics.mode
480
+ }
481
+ });
482
+ }
483
+
484
+ // src/create-raw-client.ts
485
+ var idCount = 0;
486
+ async function performInitialize(instance, initFn) {
487
+ try {
488
+ await initFn();
489
+ instance.initialized = true;
490
+ } catch (error) {
491
+ instance.initPromise = null;
492
+ throw error;
493
+ }
494
+ }
495
+ function createCreateRawClient(fns) {
496
+ return function createRawClient({
497
+ controller,
498
+ origin
499
+ }) {
500
+ const id = idCount++;
501
+ controllerInstanceMap.set(id, {
502
+ controller,
503
+ initialized: false,
504
+ initPromise: null
505
+ });
506
+ const api = {
507
+ origin,
508
+ initialize: async () => {
509
+ let instance = controllerInstanceMap.get(id);
510
+ if (!instance) {
511
+ instance = { controller, initialized: false, initPromise: null };
512
+ controllerInstanceMap.set(id, instance);
513
+ }
514
+ if (instance.initialized) return;
515
+ if (!instance.initPromise) {
516
+ instance.initPromise = performInitialize(
517
+ instance,
518
+ () => fns.initialize(id)
519
+ );
520
+ }
521
+ return instance.initPromise;
522
+ },
523
+ shutdown: async () => {
524
+ await fns.shutdown(id);
525
+ controllerInstanceMap.delete(id);
526
+ },
527
+ getDatafile: async () => {
528
+ const instance = controllerInstanceMap.get(id);
529
+ if (_optionalChain([instance, 'optionalAccess', _21 => _21.initPromise])) {
530
+ try {
531
+ await instance.initPromise;
532
+ } catch (e) {
533
+ }
534
+ }
535
+ return fns.getDatafile(id);
536
+ },
537
+ getFallbackDatafile: () => {
538
+ return fns.getFallbackDatafile(id);
539
+ },
540
+ evaluate: async (flagKey, defaultValue, entities) => {
541
+ const instance = controllerInstanceMap.get(id);
542
+ if (!_optionalChain([instance, 'optionalAccess', _22 => _22.initialized])) {
543
+ try {
544
+ await api.initialize();
545
+ } catch (e2) {
546
+ }
547
+ }
548
+ return fns.evaluate(id, flagKey, defaultValue, entities);
549
+ }
550
+ };
551
+ return api;
552
+ };
553
+ }
554
+
555
+ // src/utils/read-bundled-definitions.ts
556
+ var sdkKeyHashCache = /* @__PURE__ */ new Map();
557
+ async function computeHash(sdkKey) {
558
+ const hashBuffer = await crypto.subtle.digest(
559
+ "SHA-256",
560
+ new TextEncoder().encode(sdkKey)
561
+ );
562
+ const hashArray = new Uint8Array(hashBuffer);
563
+ return Array.from(hashArray).map((b) => b.toString(16).padStart(2, "0")).join("");
564
+ }
565
+ function hashSdkKey(sdkKey) {
566
+ const cached = sdkKeyHashCache.get(sdkKey);
567
+ if (cached) return cached;
568
+ const promise = computeHash(sdkKey);
569
+ sdkKeyHashCache.set(sdkKey, promise);
570
+ return promise;
571
+ }
572
+ async function readBundledDefinitions(sdkKey) {
573
+ let get;
574
+ try {
575
+ const module = await Promise.resolve().then(() => _interopRequireWildcard(require(
576
+ /* turbopackOptional: true */
577
+ // @ts-expect-error this only exists at build time
578
+ "@vercel/flags-definitions"
579
+ )));
580
+ get = module.get;
581
+ } catch (error) {
582
+ if (error && typeof error === "object" && "code" in error && error.code === "MODULE_NOT_FOUND") {
583
+ return { definitions: null, state: "missing-file" };
584
+ }
585
+ return { definitions: null, state: "unexpected-error", error };
586
+ }
587
+ const entry = get(sdkKey);
588
+ if (entry) return { definitions: entry, state: "ok" };
589
+ try {
590
+ const hashedKey = await hashSdkKey(sdkKey);
591
+ const hashedEntry = get(hashedKey);
592
+ if (hashedEntry) return { definitions: hashedEntry, state: "ok" };
593
+ } catch (error) {
594
+ return { definitions: null, state: "unexpected-error", error };
595
+ }
596
+ return { definitions: null, state: "missing-entry" };
597
+ }
598
+
599
+ // src/utils/usage-tracker.ts
600
+ var _functions = require('@vercel/functions');
601
+ var RESOLVED_VOID = Promise.resolve();
602
+ var isDebugMode = _optionalChain([process, 'access', _23 => _23.env, 'access', _24 => _24.DEBUG, 'optionalAccess', _25 => _25.includes, 'call', _26 => _26("@vercel/flags-core")]);
603
+ var debugLog = (...args) => {
604
+ if (!isDebugMode) return;
605
+ console.log(...args);
606
+ };
607
+ var MAX_RETRIES = 3;
608
+ var MAX_BATCH_SIZE = 50;
609
+ var MAX_BATCH_WAIT_MS = 5e3;
610
+ var SYMBOL_FOR_REQ_CONTEXT = /* @__PURE__ */ Symbol.for("@vercel/request-context");
611
+ var fromSymbol = globalThis;
612
+ function getRequestContext() {
613
+ try {
614
+ const ctx = _optionalChain([fromSymbol, 'access', _27 => _27[SYMBOL_FOR_REQ_CONTEXT], 'optionalAccess', _28 => _28.get, 'optionalCall', _29 => _29()]);
615
+ if (ctx && Object.hasOwn(ctx, "headers")) {
616
+ return {
617
+ ctx,
618
+ headers: ctx.headers
619
+ };
620
+ }
621
+ return { ctx, headers: void 0 };
622
+ } catch (e3) {
623
+ return { ctx: void 0, headers: void 0 };
624
+ }
625
+ }
626
+ var UsageTracker = (_class = class {
627
+ __init() {this.flushCounter = 0}
628
+
629
+ __init2() {this.trackedRequests = /* @__PURE__ */ new WeakSet()}
630
+ __init3() {this.batcher = {
631
+ events: [],
632
+ resolveWait: null,
633
+ pending: null
634
+ }}
635
+ constructor(options) {;_class.prototype.__init.call(this);_class.prototype.__init2.call(this);_class.prototype.__init3.call(this);
636
+ this.options = options;
637
+ }
638
+ /**
639
+ * Triggers an immediate flush of any pending events.
640
+ * Returns a promise that resolves when the flush completes.
641
+ */
642
+ flush() {
643
+ if (this.batcher.pending) {
644
+ _optionalChain([this, 'access', _30 => _30.batcher, 'access', _31 => _31.resolveWait, 'optionalCall', _32 => _32()]);
645
+ return this.batcher.pending;
646
+ }
647
+ if (this.batcher.events.length > 0) {
648
+ return this.flushEvents();
649
+ }
650
+ return RESOLVED_VOID;
651
+ }
652
+ /**
653
+ * Tracks a config read event. Deduplicates by request context.
654
+ */
655
+ trackRead(options) {
656
+ try {
657
+ const { ctx, headers } = getRequestContext();
658
+ if (ctx) {
659
+ if (this.trackedRequests.has(ctx)) return;
660
+ this.trackedRequests.add(ctx);
661
+ }
662
+ const event = {
663
+ type: "FLAGS_CONFIG_READ",
664
+ ts: Date.now(),
665
+ payload: {
666
+ deploymentId: process.env.VERCEL_DEPLOYMENT_ID,
667
+ region: process.env.VERCEL_REGION
668
+ }
669
+ };
670
+ if (headers) {
671
+ event.payload.vercelRequestId = _nullishCoalesce(headers["x-vercel-id"], () => ( void 0));
672
+ event.payload.invocationHost = _nullishCoalesce(headers.host, () => ( void 0));
673
+ }
674
+ if (options) {
675
+ event.payload.configOrigin = options.configOrigin;
676
+ if (options.cacheStatus !== void 0) {
677
+ event.payload.cacheStatus = options.cacheStatus;
678
+ }
679
+ if (options.cacheAction !== void 0) {
680
+ event.payload.cacheAction = options.cacheAction;
681
+ }
682
+ if (options.cacheIsFirstRead !== void 0) {
683
+ event.payload.cacheIsFirstRead = options.cacheIsFirstRead;
684
+ }
685
+ if (options.cacheIsBlocking !== void 0) {
686
+ event.payload.cacheIsBlocking = options.cacheIsBlocking;
687
+ }
688
+ if (options.duration !== void 0) {
689
+ event.payload.duration = options.duration;
690
+ }
691
+ if (options.configUpdatedAt !== void 0) {
692
+ event.payload.configUpdatedAt = options.configUpdatedAt;
693
+ }
694
+ if (options.mode !== void 0) {
695
+ event.payload.mode = options.mode;
696
+ }
697
+ if (options.revision !== void 0) {
698
+ event.payload.revision = String(options.revision);
699
+ }
700
+ }
701
+ const environment = process.env.VERCEL_ENV || process.env.NODE_ENV || void 0;
702
+ if (environment) {
703
+ event.payload.environment = environment;
704
+ }
705
+ this.batcher.events.push(event);
706
+ this.scheduleFlush();
707
+ } catch (error) {
708
+ console.error("@vercel/flags-core: Failed to record event:", error);
709
+ }
710
+ }
711
+ scheduleFlush() {
712
+ if (!this.batcher.pending) {
713
+ let timeout = null;
714
+ const pending = (async () => {
715
+ await new Promise((res) => {
716
+ this.batcher.resolveWait = res;
717
+ timeout = setTimeout(res, MAX_BATCH_WAIT_MS);
718
+ });
719
+ this.batcher.pending = null;
720
+ this.batcher.resolveWait = null;
721
+ if (timeout) clearTimeout(timeout);
722
+ await this.flushEvents();
723
+ })();
724
+ try {
725
+ _functions.waitUntil.call(void 0, pending);
726
+ } catch (e4) {
727
+ }
728
+ this.batcher.pending = pending;
729
+ }
730
+ if (this.batcher.events.length >= MAX_BATCH_SIZE) {
731
+ _optionalChain([this, 'access', _33 => _33.batcher, 'access', _34 => _34.resolveWait, 'optionalCall', _35 => _35()]);
732
+ }
733
+ }
734
+ async flushEvents() {
735
+ if (this.batcher.events.length === 0) return;
736
+ const eventsToSend = this.batcher.events;
737
+ this.batcher.events = [];
738
+ const flushId = ++this.flushCounter;
739
+ for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
740
+ try {
741
+ const response = await this.options.fetch(
742
+ `${this.options.host}/v1/ingest`,
743
+ {
744
+ method: "POST",
745
+ headers: {
746
+ "Content-Type": "application/json",
747
+ Authorization: `Bearer ${this.options.sdkKey}`,
748
+ "User-Agent": `VercelFlagsCore/${version}`,
749
+ ...isDebugMode ? { "x-vercel-debug-ingest": "1" } : null
750
+ },
751
+ body: JSON.stringify(eventsToSend)
752
+ }
753
+ );
754
+ debugLog(
755
+ `@vercel/flags-core: Ingest response ${response.status} for ${eventsToSend.length} events on ${response.headers.get("x-vercel-id")}`
756
+ );
757
+ if (response.ok) {
758
+ break;
759
+ }
760
+ throw new Error(
761
+ `Ingest endpoint responded with status ${response.status} for ${eventsToSend.length} events on request ${response.headers.get("x-vercel-id")}.
762
+ Response body: ${await response.text().catch(() => null)}`
763
+ );
764
+ } catch (error) {
765
+ console.error(
766
+ `@vercel/flags-core: Error sending events (attempt=${attempt}/${MAX_RETRIES} flushId=${flushId}):`,
767
+ error
768
+ );
769
+ if (attempt < MAX_RETRIES) {
770
+ await new Promise((res) => setTimeout(res, attempt * 100));
771
+ }
772
+ }
773
+ }
774
+ }
775
+ }, _class);
776
+
777
+ // src/errors.ts
778
+ var FallbackNotFoundError = class extends Error {
779
+ constructor() {
780
+ super("@vercel/flags-core: Bundled definitions file not found.");
781
+ this.name = "FallbackNotFoundError";
782
+ }
783
+ };
784
+ var FallbackEntryNotFoundError = class extends Error {
785
+ constructor() {
786
+ super("@vercel/flags-core: No bundled definitions found for SDK key.");
787
+ this.name = "FallbackEntryNotFoundError";
788
+ }
789
+ };
790
+
791
+ // src/controller/bundled-source.ts
792
+ var BundledSource = class {
793
+
794
+
795
+ constructor(options) {
796
+ this.options = options;
797
+ }
798
+ /**
799
+ * Load bundled definitions.
800
+ * Throws if bundled definitions are not available.
801
+ */
802
+ async load() {
803
+ const result = await this.getResult();
804
+ if (result.state === "ok" && result.definitions) {
805
+ return result.definitions;
806
+ }
807
+ throw new Error(
808
+ "@vercel/flags-core: No flag definitions available. Bundled definitions not found."
809
+ );
810
+ }
811
+ /**
812
+ * Get the raw BundledDefinitions (for getFallbackDatafile).
813
+ * Throws typed errors if not available.
814
+ */
815
+ async getRaw() {
816
+ const result = await this.getResult();
817
+ switch (result.state) {
818
+ case "ok":
819
+ return result.definitions;
820
+ case "missing-file":
821
+ throw new FallbackNotFoundError();
822
+ case "missing-entry":
823
+ throw new FallbackEntryNotFoundError();
824
+ case "unexpected-error":
825
+ throw new Error(
826
+ "@vercel/flags-core: Failed to read bundled definitions: " + String(result.error)
827
+ );
828
+ }
829
+ }
830
+ /**
831
+ * Check if bundled definitions loaded successfully (without throwing).
832
+ */
833
+ async tryLoad() {
834
+ const result = await this.getResult();
835
+ if (_optionalChain([result, 'optionalAccess', _36 => _36.state]) === "ok" && result.definitions) {
836
+ return result.definitions;
837
+ }
838
+ return void 0;
839
+ }
840
+ getResult() {
841
+ if (!this.promise) {
842
+ this.promise = this.options.readBundledDefinitions(this.options.sdkKey);
843
+ }
844
+ return this.promise;
845
+ }
846
+ };
847
+
848
+ // src/controller/fetch-datafile.ts
849
+ var DEFAULT_FETCH_TIMEOUT_MS = 1e4;
850
+ async function fetchDatafile(options) {
851
+ const controller = new AbortController();
852
+ const timeoutId = setTimeout(
853
+ () => controller.abort(),
854
+ DEFAULT_FETCH_TIMEOUT_MS
855
+ );
856
+ const onExternalAbort = () => controller.abort();
857
+ if (options.signal) {
858
+ if (options.signal.aborted) {
859
+ clearTimeout(timeoutId);
860
+ throw new Error("Fetch aborted");
861
+ }
862
+ options.signal.addEventListener("abort", onExternalAbort, { once: true });
863
+ }
864
+ try {
865
+ const res = await options.fetch(`${options.host}/v1/datafile`, {
866
+ headers: {
867
+ Authorization: `Bearer ${options.sdkKey}`,
868
+ "User-Agent": `VercelFlagsCore/${version}`
869
+ },
870
+ signal: controller.signal
871
+ });
872
+ clearTimeout(timeoutId);
873
+ _optionalChain([options, 'access', _37 => _37.signal, 'optionalAccess', _38 => _38.removeEventListener, 'call', _39 => _39("abort", onExternalAbort)]);
874
+ if (!res.ok) {
875
+ throw new Error(`Failed to fetch data: ${res.statusText}`);
876
+ }
877
+ return res.json();
878
+ } catch (error) {
879
+ clearTimeout(timeoutId);
880
+ _optionalChain([options, 'access', _40 => _40.signal, 'optionalAccess', _41 => _41.removeEventListener, 'call', _42 => _42("abort", onExternalAbort)]);
881
+ throw error instanceof Error ? error : new Error("Unknown fetch error");
882
+ }
883
+ }
884
+
885
+ // src/controller/normalized-options.ts
886
+ var DEFAULT_STREAM_INIT_TIMEOUT_MS = 3e3;
887
+ var DEFAULT_POLLING_INTERVAL_MS = 3e4;
888
+ var MIN_POLLING_INTERVAL_MS = 3e4;
889
+ var DEFAULT_POLLING_INIT_TIMEOUT_MS = 3e3;
890
+ function normalizeOptions(options) {
891
+ const autoDetectedBuildStep = process.env.CI === "1" || process.env.NEXT_PHASE === "phase-production-build";
892
+ const buildStep = _nullishCoalesce(options.buildStep, () => ( autoDetectedBuildStep));
893
+ let stream;
894
+ if (options.stream === void 0 || options.stream === true) {
895
+ stream = { enabled: true, initTimeoutMs: DEFAULT_STREAM_INIT_TIMEOUT_MS };
896
+ } else if (options.stream === false) {
897
+ stream = { enabled: false, initTimeoutMs: 0 };
898
+ } else {
899
+ stream = { enabled: true, initTimeoutMs: options.stream.initTimeoutMs };
900
+ }
901
+ let polling;
902
+ if (options.polling === void 0 || options.polling === true) {
903
+ polling = {
904
+ enabled: true,
905
+ intervalMs: DEFAULT_POLLING_INTERVAL_MS,
906
+ initTimeoutMs: DEFAULT_POLLING_INIT_TIMEOUT_MS
907
+ };
908
+ } else if (options.polling === false) {
909
+ polling = { enabled: false, intervalMs: 0, initTimeoutMs: 0 };
910
+ } else {
911
+ if (options.polling.intervalMs < MIN_POLLING_INTERVAL_MS) {
912
+ throw new Error(
913
+ `@vercel/flags-core: Polling interval must be at least ${MIN_POLLING_INTERVAL_MS}ms, got ${options.polling.intervalMs}ms.`
914
+ );
915
+ }
916
+ polling = {
917
+ enabled: true,
918
+ intervalMs: options.polling.intervalMs,
919
+ initTimeoutMs: options.polling.initTimeoutMs
920
+ };
921
+ }
922
+ return {
923
+ sdkKey: options.sdkKey,
924
+ datafile: options.datafile,
925
+ stream,
926
+ polling,
927
+ buildStep,
928
+ fetch: _nullishCoalesce(options.fetch, () => ( globalThis.fetch)),
929
+ host: "https://flags.vercel.com"
930
+ };
931
+ }
932
+
933
+ // src/controller/typed-emitter.ts
934
+ var TypedEmitter = (_class2 = class {constructor() { _class2.prototype.__init4.call(this); }
935
+ __init4() {this.handlers = /* @__PURE__ */ new Map()}
936
+ on(event, handler) {
937
+ let set = this.handlers.get(event);
938
+ if (!set) {
939
+ set = /* @__PURE__ */ new Set();
940
+ this.handlers.set(event, set);
941
+ }
942
+ set.add(handler);
943
+ }
944
+ off(event, handler) {
945
+ _optionalChain([this, 'access', _43 => _43.handlers, 'access', _44 => _44.get, 'call', _45 => _45(event), 'optionalAccess', _46 => _46.delete, 'call', _47 => _47(handler)]);
946
+ }
947
+ emit(event, ...args) {
948
+ const set = this.handlers.get(event);
949
+ if (set) {
950
+ for (const handler of set) {
951
+ handler(...args);
952
+ }
953
+ }
954
+ }
955
+ }, _class2);
956
+
957
+ // src/controller/polling-source.ts
958
+ var PollingSource = class extends TypedEmitter {
959
+
960
+
961
+
962
+ constructor(config) {
963
+ super();
964
+ this.config = config;
965
+ }
966
+ /**
967
+ * Perform a single poll request.
968
+ * Emits 'data' on success, 'error' on failure.
969
+ */
970
+ async poll() {
971
+ if (_optionalChain([this, 'access', _48 => _48.abortController, 'optionalAccess', _49 => _49.signal, 'access', _50 => _50.aborted])) return;
972
+ try {
973
+ const data = await fetchDatafile({
974
+ ...this.config,
975
+ signal: _optionalChain([this, 'access', _51 => _51.abortController, 'optionalAccess', _52 => _52.signal])
976
+ });
977
+ this.emit("data", data);
978
+ } catch (error) {
979
+ const err = error instanceof Error ? error : new Error("Unknown poll error");
980
+ this.emit("error", err);
981
+ }
982
+ }
983
+ /**
984
+ * Start interval-based polling.
985
+ * Polls at the configured interval. Does not perform an initial poll —
986
+ * callers should call poll() first if an immediate poll is needed.
987
+ */
988
+ startInterval() {
989
+ if (this.intervalId) return;
990
+ this.abortController = new AbortController();
991
+ this.intervalId = setInterval(
992
+ () => void this.poll(),
993
+ this.config.polling.intervalMs
994
+ );
995
+ }
996
+ /**
997
+ * Stop interval-based polling.
998
+ */
999
+ stop() {
1000
+ if (this.intervalId) {
1001
+ clearInterval(this.intervalId);
1002
+ this.intervalId = void 0;
1003
+ }
1004
+ _optionalChain([this, 'access', _53 => _53.abortController, 'optionalAccess', _54 => _54.abort, 'call', _55 => _55()]);
1005
+ this.abortController = void 0;
1006
+ }
1007
+ };
1008
+
1009
+ // src/utils/sleep.ts
1010
+ function sleep(ms) {
1011
+ return new Promise((resolve) => setTimeout(resolve, ms));
1012
+ }
1013
+
1014
+ // src/controller/stream-connection.ts
1015
+ var MAX_RETRY_COUNT = 15;
1016
+ var BASE_RETRY_DELAY_MS = 1e3;
1017
+ var MAX_RETRY_DELAY_MS = 6e4;
1018
+ var PING_TIMEOUT_MS = 9e4;
1019
+ function backoff(retryCount) {
1020
+ if (retryCount === 1) return 0;
1021
+ const delay = Math.min(
1022
+ BASE_RETRY_DELAY_MS * 2 ** (retryCount - 2),
1023
+ MAX_RETRY_DELAY_MS
1024
+ );
1025
+ return delay + Math.random() * 1e3;
1026
+ }
1027
+ var UnauthorizedError = class extends Error {
1028
+ constructor() {
1029
+ super("stream: unauthorized (401)");
1030
+ this.name = "UnauthorizedError";
1031
+ }
1032
+ };
1033
+ async function connectStream(config, callbacks) {
1034
+ const {
1035
+ host,
1036
+ sdkKey,
1037
+ abortController,
1038
+ fetch: fetchFn = globalThis.fetch
1039
+ } = config;
1040
+ const { onDatafile, onPrimed, onDisconnect } = callbacks;
1041
+ let retryCount = 0;
1042
+ let lastAttemptTime = 0;
1043
+ let resolveInit;
1044
+ let rejectInit;
1045
+ const initPromise = new Promise((resolve, reject) => {
1046
+ resolveInit = resolve;
1047
+ rejectInit = reject;
1048
+ });
1049
+ void (async () => {
1050
+ let initialDataReceived = false;
1051
+ while (!abortController.signal.aborted) {
1052
+ if (retryCount > MAX_RETRY_COUNT) {
1053
+ console.error("@vercel/flags-core: Max retry count exceeded");
1054
+ if (!initialDataReceived) {
1055
+ rejectInit(
1056
+ new Error("stream: max retry count exceeded before receiving data")
1057
+ );
1058
+ }
1059
+ abortController.abort();
1060
+ break;
1061
+ }
1062
+ const connectionAbort = new AbortController();
1063
+ const onMainAbort = () => connectionAbort.abort();
1064
+ abortController.signal.addEventListener("abort", onMainAbort, {
1065
+ once: true
1066
+ });
1067
+ let pingTimeoutId;
1068
+ let responseBody;
1069
+ const resetPingTimeout = () => {
1070
+ if (pingTimeoutId !== void 0) clearTimeout(pingTimeoutId);
1071
+ if (!initialDataReceived) return;
1072
+ pingTimeoutId = setTimeout(() => {
1073
+ _optionalChain([responseBody, 'optionalAccess', _56 => _56.cancel, 'call', _57 => _57(), 'access', _58 => _58.catch, 'call', _59 => _59(() => {
1074
+ })]);
1075
+ connectionAbort.abort();
1076
+ }, PING_TIMEOUT_MS);
1077
+ };
1078
+ try {
1079
+ lastAttemptTime = Date.now();
1080
+ const headers = {
1081
+ Authorization: `Bearer ${sdkKey}`,
1082
+ "User-Agent": `VercelFlagsCore/${version}`,
1083
+ "X-Retry-Attempt": String(retryCount)
1084
+ };
1085
+ const revision = _optionalChain([config, 'access', _60 => _60.revision, 'optionalCall', _61 => _61()]);
1086
+ if (revision !== void 0) {
1087
+ headers["X-Revision"] = String(revision);
1088
+ }
1089
+ const response = await fetchFn(`${host}/v1/stream`, {
1090
+ headers,
1091
+ signal: connectionAbort.signal
1092
+ });
1093
+ if (!response.ok) {
1094
+ if (response.status === 401) {
1095
+ if (!initialDataReceived) {
1096
+ rejectInit(new UnauthorizedError());
1097
+ }
1098
+ abortController.abort();
1099
+ break;
1100
+ }
1101
+ throw new Error(`stream was not ok: ${response.status}`);
1102
+ }
1103
+ if (!response.body) {
1104
+ throw new Error("stream body was not present");
1105
+ }
1106
+ responseBody = response.body;
1107
+ const reader = response.body.getReader();
1108
+ const decoder = new TextDecoder();
1109
+ const bufferChunks = [];
1110
+ const onConnectionAbort = () => {
1111
+ reader.cancel().catch(() => {
1112
+ });
1113
+ };
1114
+ connectionAbort.signal.addEventListener("abort", onConnectionAbort, {
1115
+ once: true
1116
+ });
1117
+ try {
1118
+ while (true) {
1119
+ const { done, value: chunk } = await reader.read();
1120
+ if (done || abortController.signal.aborted) break;
1121
+ bufferChunks.push(decoder.decode(chunk, { stream: true }));
1122
+ const combined = bufferChunks.join("");
1123
+ bufferChunks.length = 0;
1124
+ const lines = combined.split("\n");
1125
+ bufferChunks.push(lines.pop());
1126
+ for (const line of lines) {
1127
+ if (line === "") continue;
1128
+ let message;
1129
+ try {
1130
+ message = JSON.parse(line);
1131
+ } catch (e5) {
1132
+ console.warn(
1133
+ "@vercel/flags-core: Failed to parse stream message, skipping"
1134
+ );
1135
+ continue;
1136
+ }
1137
+ if (message.type === "datafile") {
1138
+ onDatafile(message.data);
1139
+ retryCount = 0;
1140
+ if (!initialDataReceived) {
1141
+ initialDataReceived = true;
1142
+ resolveInit();
1143
+ }
1144
+ resetPingTimeout();
1145
+ }
1146
+ if (message.type === "primed") {
1147
+ _optionalChain([onPrimed, 'optionalCall', _62 => _62(message)]);
1148
+ retryCount = 0;
1149
+ if (!initialDataReceived) {
1150
+ initialDataReceived = true;
1151
+ resolveInit();
1152
+ }
1153
+ resetPingTimeout();
1154
+ }
1155
+ if (message.type === "ping" && initialDataReceived) {
1156
+ retryCount = 0;
1157
+ resetPingTimeout();
1158
+ }
1159
+ }
1160
+ }
1161
+ } finally {
1162
+ connectionAbort.signal.removeEventListener(
1163
+ "abort",
1164
+ onConnectionAbort
1165
+ );
1166
+ }
1167
+ clearTimeout(pingTimeoutId);
1168
+ abortController.signal.removeEventListener("abort", onMainAbort);
1169
+ if (!abortController.signal.aborted) {
1170
+ _optionalChain([onDisconnect, 'optionalCall', _63 => _63()]);
1171
+ retryCount++;
1172
+ const elapsed = Date.now() - lastAttemptTime;
1173
+ const minGap = Math.max(0, BASE_RETRY_DELAY_MS - elapsed);
1174
+ await sleep(Math.max(backoff(retryCount), minGap));
1175
+ continue;
1176
+ }
1177
+ } catch (error) {
1178
+ clearTimeout(pingTimeoutId);
1179
+ abortController.signal.removeEventListener("abort", onMainAbort);
1180
+ if (abortController.signal.aborted) {
1181
+ break;
1182
+ }
1183
+ if (!connectionAbort.signal.aborted) {
1184
+ console.error("@vercel/flags-core: Stream error", error);
1185
+ }
1186
+ _optionalChain([onDisconnect, 'optionalCall', _64 => _64()]);
1187
+ retryCount++;
1188
+ const elapsed = Date.now() - lastAttemptTime;
1189
+ const minGap = Math.max(0, BASE_RETRY_DELAY_MS - elapsed);
1190
+ await sleep(Math.max(backoff(retryCount), minGap));
1191
+ }
1192
+ }
1193
+ if (!initialDataReceived) {
1194
+ rejectInit(new Error("stream: aborted before receiving data"));
1195
+ }
1196
+ })();
1197
+ return initPromise;
1198
+ }
1199
+
1200
+ // src/controller/stream-source.ts
1201
+ var StreamSource = class extends TypedEmitter {
1202
+
1203
+
1204
+
1205
+
1206
+ constructor(options, revision) {
1207
+ super();
1208
+ this.options = options;
1209
+ this.revision = revision;
1210
+ }
1211
+ /**
1212
+ * Start the stream connection.
1213
+ * Returns a promise that resolves when the first datafile or primed message arrives.
1214
+ * If already started, returns the existing promise.
1215
+ */
1216
+ start() {
1217
+ if (this.promise) return this.promise;
1218
+ const abortController = new AbortController();
1219
+ this.abortController = abortController;
1220
+ abortController.signal.addEventListener(
1221
+ "abort",
1222
+ () => {
1223
+ if (this.abortController === abortController) {
1224
+ this.promise = void 0;
1225
+ this.abortController = void 0;
1226
+ }
1227
+ },
1228
+ { once: true }
1229
+ );
1230
+ try {
1231
+ const promise = connectStream(
1232
+ {
1233
+ host: this.options.host,
1234
+ sdkKey: this.options.sdkKey,
1235
+ abortController,
1236
+ fetch: this.options.fetch,
1237
+ revision: this.revision
1238
+ },
1239
+ {
1240
+ onDatafile: (newData) => {
1241
+ this.emit("data", newData);
1242
+ this.emit("connected");
1243
+ },
1244
+ onPrimed: (message) => {
1245
+ this.emit("primed", message);
1246
+ this.emit("connected");
1247
+ },
1248
+ onDisconnect: () => {
1249
+ this.emit("disconnected");
1250
+ }
1251
+ }
1252
+ );
1253
+ this.promise = promise;
1254
+ return promise;
1255
+ } catch (error) {
1256
+ this.promise = void 0;
1257
+ this.abortController = void 0;
1258
+ throw error;
1259
+ }
1260
+ }
1261
+ /**
1262
+ * Stop the stream connection.
1263
+ */
1264
+ stop() {
1265
+ _optionalChain([this, 'access', _65 => _65.abortController, 'optionalAccess', _66 => _66.abort, 'call', _67 => _67()]);
1266
+ this.abortController = void 0;
1267
+ this.promise = void 0;
1268
+ }
1269
+ };
1270
+
1271
+ // src/controller/tagged-data.ts
1272
+ function tagData(data, origin) {
1273
+ return Object.assign(data, { _origin: origin });
1274
+ }
1275
+ function originToMetricsSource(origin) {
1276
+ switch (origin) {
1277
+ case "stream":
1278
+ case "poll":
1279
+ case "provided":
1280
+ return "in-memory";
1281
+ case "fetched":
1282
+ return "remote";
1283
+ case "bundled":
1284
+ return "embedded";
1285
+ }
1286
+ }
1287
+
1288
+ // src/controller/index.ts
1289
+ function parseConfigUpdatedAt(value) {
1290
+ if (typeof value === "number") return value;
1291
+ if (typeof value === "string") {
1292
+ const parsed = Number(value);
1293
+ return Number.isNaN(parsed) ? void 0 : parsed;
1294
+ }
1295
+ return void 0;
1296
+ }
1297
+ var Controller = (_class3 = class {
1298
+
1299
+ // State machine
1300
+ __init5() {this.state = "idle"}
1301
+ // Data state — tagged with origin
1302
+
1303
+ // Sources (I/O delegates)
1304
+
1305
+
1306
+
1307
+ // Usage tracking
1308
+
1309
+ __init6() {this.isFirstGetData = true}
1310
+ // Build-step deduplication
1311
+ __init7() {this.buildDataPromise = null}
1312
+ __init8() {this.buildReadTracked = false}
1313
+ // Suppresses usage tracking when the SDK key is unauthorized
1314
+ __init9() {this.unauthorized = false}
1315
+ 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);
1316
+ if (!options.sdkKey || typeof options.sdkKey !== "string" || !options.sdkKey.startsWith("vf_")) {
1317
+ throw new Error(
1318
+ '@vercel/flags-core: SDK key must be a string starting with "vf_"'
1319
+ );
1320
+ }
1321
+ this.options = normalizeOptions(options);
1322
+ this.streamSource = new StreamSource(
1323
+ this.options,
1324
+ () => _optionalChain([this, 'access', _68 => _68.data, 'optionalAccess', _69 => _69.revision])
1325
+ );
1326
+ this.pollingSource = new PollingSource(this.options);
1327
+ this.bundledSource = new BundledSource({
1328
+ sdkKey: this.options.sdkKey,
1329
+ readBundledDefinitions
1330
+ });
1331
+ this.wireSourceEvents();
1332
+ if (this.options.datafile) {
1333
+ this.data = tagData(this.options.datafile, "provided");
1334
+ }
1335
+ this.usageTracker = new UsageTracker(this.options);
1336
+ }
1337
+ // Source event handlers (stored for cleanup)
1338
+ __init10() {this.onStreamData = (data) => {
1339
+ if (this.isNewerData(data)) {
1340
+ this.data = tagData(data, "stream");
1341
+ }
1342
+ }}
1343
+ __init11() {this.onStreamPrimed = () => {
1344
+ if (this.state === "degraded" || this.state === "initializing:stream") {
1345
+ this.transition("streaming");
1346
+ }
1347
+ }}
1348
+ __init12() {this.onStreamConnected = () => {
1349
+ if (this.state === "degraded" || this.state === "initializing:stream") {
1350
+ this.transition("streaming");
1351
+ }
1352
+ }}
1353
+ __init13() {this.onStreamDisconnected = () => {
1354
+ if (this.state === "streaming") {
1355
+ this.transition("degraded");
1356
+ }
1357
+ }}
1358
+ __init14() {this.onPollData = (data) => {
1359
+ if (this.isNewerData(data)) {
1360
+ this.data = tagData(data, "poll");
1361
+ }
1362
+ }}
1363
+ __init15() {this.onPollError = (error) => {
1364
+ console.error("@vercel/flags-core: Poll failed:", error);
1365
+ }}
1366
+ // ---------------------------------------------------------------------------
1367
+ // Source event wiring
1368
+ // ---------------------------------------------------------------------------
1369
+ wireSourceEvents() {
1370
+ this.streamSource.on("data", this.onStreamData);
1371
+ this.streamSource.on("primed", this.onStreamPrimed);
1372
+ this.streamSource.on("connected", this.onStreamConnected);
1373
+ this.streamSource.on("disconnected", this.onStreamDisconnected);
1374
+ this.pollingSource.on("data", this.onPollData);
1375
+ this.pollingSource.on("error", this.onPollError);
1376
+ }
1377
+ unwireSourceEvents() {
1378
+ this.streamSource.off("data", this.onStreamData);
1379
+ this.streamSource.off("primed", this.onStreamPrimed);
1380
+ this.streamSource.off("connected", this.onStreamConnected);
1381
+ this.streamSource.off("disconnected", this.onStreamDisconnected);
1382
+ this.pollingSource.off("data", this.onPollData);
1383
+ this.pollingSource.off("error", this.onPollError);
1384
+ }
1385
+ // ---------------------------------------------------------------------------
1386
+ // State machine
1387
+ // ---------------------------------------------------------------------------
1388
+ transition(to) {
1389
+ this.state = to;
1390
+ }
1391
+ get isConnected() {
1392
+ return this.state === "streaming";
1393
+ }
1394
+ get mode() {
1395
+ if (this.options.buildStep) return "build";
1396
+ switch (this.state) {
1397
+ case "streaming":
1398
+ return "streaming";
1399
+ case "polling":
1400
+ return "polling";
1401
+ default:
1402
+ return "offline";
1403
+ }
1404
+ }
1405
+ // ---------------------------------------------------------------------------
1406
+ // Public API (DataSource interface)
1407
+ // ---------------------------------------------------------------------------
1408
+ /**
1409
+ * Initializes the data source.
1410
+ *
1411
+ * Build step: datafile → bundled → one-time fetch
1412
+ * Streaming mode: stream → datafile → bundled
1413
+ * Polling mode (no stream): poll → datafile → bundled
1414
+ * Offline mode (neither): datafile → bundled → one-time fetch
1415
+ */
1416
+ async initialize() {
1417
+ if (this.options.buildStep) {
1418
+ this.transition("build:loading");
1419
+ await this.initializeForBuildStep();
1420
+ this.transition("build:ready");
1421
+ return;
1422
+ }
1423
+ if (!this.data && this.options.datafile) {
1424
+ this.data = tagData(this.options.datafile, "provided");
1425
+ }
1426
+ if (!this.data) {
1427
+ try {
1428
+ const bundled = await this.bundledSource.tryLoad();
1429
+ if (bundled) {
1430
+ this.data = tagData(bundled, "bundled");
1431
+ }
1432
+ } catch (e6) {
1433
+ }
1434
+ }
1435
+ if (this.data) {
1436
+ if (this.options.stream.enabled) {
1437
+ this.transition("initializing:stream");
1438
+ await this.tryInitializeStream();
1439
+ } else if (this.options.polling.enabled) {
1440
+ this.transition("initializing:polling");
1441
+ await this.tryInitializePolling();
1442
+ } else {
1443
+ this.transition("degraded");
1444
+ }
1445
+ return;
1446
+ }
1447
+ if (this.options.stream.enabled) {
1448
+ this.transition("initializing:stream");
1449
+ const streamSuccess = await this.tryInitializeStream();
1450
+ if (streamSuccess) {
1451
+ this.transition("streaming");
1452
+ return;
1453
+ }
1454
+ } else if (this.options.polling.enabled) {
1455
+ this.transition("initializing:polling");
1456
+ const pollingSuccess = await this.tryInitializePolling();
1457
+ if (pollingSuccess) {
1458
+ this.transition("polling");
1459
+ return;
1460
+ }
1461
+ }
1462
+ await this.initializeFromFallbacks();
1463
+ }
1464
+ /**
1465
+ * Reads the current datafile with metrics.
1466
+ */
1467
+ async read() {
1468
+ const startTime = Date.now();
1469
+ const cacheHadDefinitions = this.data !== void 0;
1470
+ const isFirstRead = this.isFirstGetData;
1471
+ this.isFirstGetData = false;
1472
+ const [result, cacheStatus] = await this.resolveData();
1473
+ const readMs = Date.now() - startTime;
1474
+ const { _origin, ...data } = result;
1475
+ const source = originToMetricsSource(_origin);
1476
+ this.trackRead(startTime, cacheHadDefinitions, isFirstRead, source);
1477
+ return {
1478
+ ...data,
1479
+ metrics: {
1480
+ readMs,
1481
+ source,
1482
+ cacheStatus,
1483
+ connectionState: this.isConnected ? "connected" : "disconnected",
1484
+ mode: this.mode
1485
+ }
1486
+ };
1487
+ }
1488
+ /**
1489
+ * Shuts down the data source and releases resources.
1490
+ */
1491
+ async shutdown() {
1492
+ this.unwireSourceEvents();
1493
+ this.streamSource.stop();
1494
+ this.pollingSource.stop();
1495
+ this.data = this.options.datafile ? tagData(this.options.datafile, "provided") : void 0;
1496
+ this.transition("shutdown");
1497
+ await this.usageTracker.flush();
1498
+ }
1499
+ /**
1500
+ * Returns the datafile with metrics.
1501
+ * Uses in-memory data if available, otherwise falls back to bundled,
1502
+ * then to a one-time fetch if called without prior initialization.
1503
+ */
1504
+ async getDatafile() {
1505
+ const startTime = Date.now();
1506
+ this.isFirstGetData = false;
1507
+ let result;
1508
+ let cacheStatus;
1509
+ if (this.options.buildStep) {
1510
+ [result, cacheStatus] = await this.resolveDataForBuildStep();
1511
+ } else if (this.data) {
1512
+ cacheStatus = this.isConnected ? "HIT" : "STALE";
1513
+ result = this.data;
1514
+ } else {
1515
+ const bundled = await this.bundledSource.tryLoad();
1516
+ if (bundled) {
1517
+ this.data = tagData(bundled, "bundled");
1518
+ result = this.data;
1519
+ cacheStatus = "MISS";
1520
+ } else {
1521
+ try {
1522
+ const fetched = await fetchDatafile({
1523
+ host: this.options.host,
1524
+ sdkKey: this.options.sdkKey,
1525
+ fetch: this.options.fetch
1526
+ });
1527
+ this.data = tagData(fetched, "fetched");
1528
+ result = this.data;
1529
+ cacheStatus = "MISS";
1530
+ } catch (e7) {
1531
+ throw new Error(
1532
+ "@vercel/flags-core: No flag definitions available. Initialize the client or provide a datafile."
1533
+ );
1534
+ }
1535
+ }
1536
+ }
1537
+ const source = originToMetricsSource(result._origin);
1538
+ return {
1539
+ ...result,
1540
+ metrics: {
1541
+ readMs: Date.now() - startTime,
1542
+ source,
1543
+ cacheStatus,
1544
+ connectionState: this.isConnected ? "connected" : "disconnected",
1545
+ mode: this.mode
1546
+ }
1547
+ };
1548
+ }
1549
+ /**
1550
+ * Returns the bundled fallback datafile.
1551
+ */
1552
+ async getFallbackDatafile() {
1553
+ return this.bundledSource.getRaw();
1554
+ }
1555
+ // ---------------------------------------------------------------------------
1556
+ // Data resolution (shared by read() and getDatafile())
1557
+ // ---------------------------------------------------------------------------
1558
+ /**
1559
+ * Resolves the current data, using the appropriate strategy for the
1560
+ * current mode. Returns tagged data and cache status.
1561
+ *
1562
+ * Build step: cached → bundled → one-time fetch
1563
+ * Runtime with cache: return cached data
1564
+ * Runtime without cache: stream/poll → datafile → bundled → fetch → throw
1565
+ */
1566
+ async resolveData() {
1567
+ if (this.options.buildStep) {
1568
+ return this.resolveDataForBuildStep();
1569
+ }
1570
+ if (this.data) {
1571
+ const cacheStatus = this.isConnected ? "HIT" : "STALE";
1572
+ return [this.data, cacheStatus];
1573
+ }
1574
+ return this.resolveDataWithFallbacks();
1575
+ }
1576
+ // ---------------------------------------------------------------------------
1577
+ // Stream initialization
1578
+ // ---------------------------------------------------------------------------
1579
+ /**
1580
+ * Attempts to initialize via stream with timeout.
1581
+ * Returns true if stream connected successfully within timeout.
1582
+ */
1583
+ async tryInitializeStream() {
1584
+ if (this.options.stream.initTimeoutMs <= 0) {
1585
+ try {
1586
+ await this.streamSource.start();
1587
+ return true;
1588
+ } catch (error) {
1589
+ if (error instanceof UnauthorizedError) {
1590
+ this.unauthorized = true;
1591
+ }
1592
+ return false;
1593
+ }
1594
+ }
1595
+ let timeoutId;
1596
+ const timeoutPromise = new Promise((resolve) => {
1597
+ timeoutId = setTimeout(
1598
+ () => resolve("timeout"),
1599
+ this.options.stream.initTimeoutMs
1600
+ );
1601
+ });
1602
+ try {
1603
+ const result = await Promise.race([
1604
+ this.streamSource.start(),
1605
+ timeoutPromise
1606
+ ]);
1607
+ clearTimeout(timeoutId);
1608
+ if (result === "timeout") {
1609
+ console.warn(
1610
+ "@vercel/flags-core: Stream initialization timeout, falling back"
1611
+ );
1612
+ void this.streamSource.start().catch(() => {
1613
+ });
1614
+ return false;
1615
+ }
1616
+ return true;
1617
+ } catch (error) {
1618
+ clearTimeout(timeoutId);
1619
+ if (error instanceof Error && error.message.includes("401")) {
1620
+ this.unauthorized = true;
1621
+ }
1622
+ return false;
1623
+ }
1624
+ }
1625
+ // ---------------------------------------------------------------------------
1626
+ // Polling initialization
1627
+ // ---------------------------------------------------------------------------
1628
+ /**
1629
+ * Attempts to initialize via polling with timeout.
1630
+ * Returns true if first poll succeeded within timeout.
1631
+ *
1632
+ * Only used when streaming is disabled and polling is the primary source.
1633
+ */
1634
+ async tryInitializePolling() {
1635
+ const pollPromise = this.pollingSource.poll();
1636
+ if (this.options.polling.initTimeoutMs <= 0) {
1637
+ try {
1638
+ await pollPromise;
1639
+ if (this.data) {
1640
+ this.pollingSource.startInterval();
1641
+ return true;
1642
+ }
1643
+ return false;
1644
+ } catch (e8) {
1645
+ return false;
1646
+ }
1647
+ }
1648
+ let timeoutId;
1649
+ const timeoutPromise = new Promise((resolve) => {
1650
+ timeoutId = setTimeout(
1651
+ () => resolve("timeout"),
1652
+ this.options.polling.initTimeoutMs
1653
+ );
1654
+ });
1655
+ try {
1656
+ const result = await Promise.race([pollPromise, timeoutPromise]);
1657
+ clearTimeout(timeoutId);
1658
+ if (result === "timeout") {
1659
+ console.warn(
1660
+ "@vercel/flags-core: Polling initialization timeout, falling back"
1661
+ );
1662
+ return false;
1663
+ }
1664
+ if (this.data) {
1665
+ this.pollingSource.startInterval();
1666
+ return true;
1667
+ }
1668
+ return false;
1669
+ } catch (e9) {
1670
+ clearTimeout(timeoutId);
1671
+ return false;
1672
+ }
1673
+ }
1674
+ // ---------------------------------------------------------------------------
1675
+ // Build step helpers
1676
+ // ---------------------------------------------------------------------------
1677
+ /**
1678
+ * Initializes data for build step environments.
1679
+ */
1680
+ async initializeForBuildStep() {
1681
+ if (this.data) return;
1682
+ if (!this.buildDataPromise) {
1683
+ this.buildDataPromise = this.loadBuildData();
1684
+ }
1685
+ this.data = await this.buildDataPromise;
1686
+ }
1687
+ /**
1688
+ * Retrieves data during build steps.
1689
+ * Concurrent callers share a single load promise. The first caller to
1690
+ * populate `this.data` gets cacheStatus MISS; subsequent callers get HIT.
1691
+ */
1692
+ async resolveDataForBuildStep() {
1693
+ if (this.data) {
1694
+ return [this.data, "HIT"];
1695
+ }
1696
+ if (!this.buildDataPromise) {
1697
+ this.buildDataPromise = this.loadBuildData();
1698
+ }
1699
+ const data = await this.buildDataPromise;
1700
+ if (!this.data) {
1701
+ this.data = data;
1702
+ return [data, "MISS"];
1703
+ }
1704
+ return [this.data, "HIT"];
1705
+ }
1706
+ /**
1707
+ * Loads data for a build step: bundled → one-time fetch.
1708
+ */
1709
+ async loadBuildData() {
1710
+ const bundled = await this.bundledSource.tryLoad();
1711
+ if (bundled) return tagData(bundled, "bundled");
1712
+ try {
1713
+ const fetched = await fetchDatafile({
1714
+ host: this.options.host,
1715
+ sdkKey: this.options.sdkKey,
1716
+ fetch: this.options.fetch
1717
+ });
1718
+ return tagData(fetched, "fetched");
1719
+ } catch (e10) {
1720
+ }
1721
+ throw new Error(
1722
+ "@vercel/flags-core: No flag definitions available during build. Provide a datafile or bundled definitions."
1723
+ );
1724
+ }
1725
+ // ---------------------------------------------------------------------------
1726
+ // Fallback helpers
1727
+ // ---------------------------------------------------------------------------
1728
+ /**
1729
+ * Shared fallback chain used by both initialize() and resolveData().
1730
+ */
1731
+ async initializeFromFallbacks() {
1732
+ this.transition("initializing:fallback");
1733
+ if (this.data) {
1734
+ this.transition("degraded");
1735
+ return;
1736
+ }
1737
+ const bundled = await this.bundledSource.tryLoad();
1738
+ if (bundled) {
1739
+ this.data = tagData(bundled, "bundled");
1740
+ this.transition("degraded");
1741
+ return;
1742
+ }
1743
+ if (!this.options.stream.enabled && !this.options.polling.enabled) {
1744
+ try {
1745
+ const fetched = await fetchDatafile({
1746
+ host: this.options.host,
1747
+ sdkKey: this.options.sdkKey,
1748
+ fetch: this.options.fetch
1749
+ });
1750
+ this.data = tagData(fetched, "fetched");
1751
+ this.transition("degraded");
1752
+ return;
1753
+ } catch (e11) {
1754
+ }
1755
+ }
1756
+ throw new Error(
1757
+ "@vercel/flags-core: No flag definitions available. Bundled definitions not found."
1758
+ );
1759
+ }
1760
+ /**
1761
+ * Retrieves data using the fallback chain (called when no cached data exists).
1762
+ * Streaming mode: stream → datafile → bundled.
1763
+ * Polling mode: poll → datafile → bundled.
1764
+ * Offline mode: datafile → bundled → one-time fetch.
1765
+ */
1766
+ async resolveDataWithFallbacks() {
1767
+ if (this.options.stream.enabled) {
1768
+ this.transition("initializing:stream");
1769
+ const streamSuccess = await this.tryInitializeStream();
1770
+ if (streamSuccess && this.data) {
1771
+ this.transition("streaming");
1772
+ return [this.data, "MISS"];
1773
+ }
1774
+ } else if (this.options.polling.enabled) {
1775
+ this.transition("initializing:polling");
1776
+ const pollingSuccess = await this.tryInitializePolling();
1777
+ if (pollingSuccess && this.data) {
1778
+ this.transition("polling");
1779
+ return [this.data, "MISS"];
1780
+ }
1781
+ }
1782
+ this.transition("initializing:fallback");
1783
+ if (this.options.datafile) {
1784
+ this.data = tagData(this.options.datafile, "provided");
1785
+ this.transition("degraded");
1786
+ return [this.data, "STALE"];
1787
+ }
1788
+ const bundled = await this.bundledSource.tryLoad();
1789
+ if (bundled) {
1790
+ console.warn("@vercel/flags-core: Using bundled definitions as fallback");
1791
+ this.data = tagData(bundled, "bundled");
1792
+ this.transition("degraded");
1793
+ return [this.data, "STALE"];
1794
+ }
1795
+ if (!this.options.stream.enabled && !this.options.polling.enabled) {
1796
+ try {
1797
+ const fetched = await fetchDatafile({
1798
+ host: this.options.host,
1799
+ sdkKey: this.options.sdkKey,
1800
+ fetch: this.options.fetch
1801
+ });
1802
+ this.data = tagData(fetched, "fetched");
1803
+ this.transition("degraded");
1804
+ return [this.data, "MISS"];
1805
+ } catch (e12) {
1806
+ }
1807
+ }
1808
+ throw new Error(
1809
+ "@vercel/flags-core: No flag definitions available. Provide a datafile or bundled definitions."
1810
+ );
1811
+ }
1812
+ // ---------------------------------------------------------------------------
1813
+ // Data comparison
1814
+ // ---------------------------------------------------------------------------
1815
+ /**
1816
+ * Checks if the incoming data is newer than the current in-memory data.
1817
+ * Returns true if the update should proceed, false if it should be skipped.
1818
+ *
1819
+ * Always accepts the update if:
1820
+ * - There is no current data
1821
+ * - The current data has no configUpdatedAt
1822
+ * - The incoming data has no configUpdatedAt
1823
+ *
1824
+ * Skips the update only when both have configUpdatedAt and incoming is not newer.
1825
+ */
1826
+ isNewerData(incoming) {
1827
+ if (!this.data) return true;
1828
+ const currentTs = parseConfigUpdatedAt(this.data.configUpdatedAt);
1829
+ const incomingTs = parseConfigUpdatedAt(incoming.configUpdatedAt);
1830
+ if (currentTs === void 0 || incomingTs === void 0) {
1831
+ return true;
1832
+ }
1833
+ return incomingTs > currentTs;
1834
+ }
1835
+ // ---------------------------------------------------------------------------
1836
+ // Usage tracking
1837
+ // ---------------------------------------------------------------------------
1838
+ /**
1839
+ * Tracks a read operation for usage analytics.
1840
+ * During build steps, only the first read is tracked.
1841
+ */
1842
+ trackRead(startTime, cacheHadDefinitions, isFirstRead, source) {
1843
+ if (this.unauthorized) return;
1844
+ if (this.options.buildStep && this.buildReadTracked) return;
1845
+ if (this.options.buildStep) this.buildReadTracked = true;
1846
+ const configOrigin = source === "embedded" ? "embedded" : "in-memory";
1847
+ const cacheAction = this.state === "streaming" ? "FOLLOWING" : this.state === "polling" ? "REFRESHING" : "NONE";
1848
+ const mode = this.mode;
1849
+ const trackOptions = {
1850
+ configOrigin,
1851
+ cacheStatus: cacheHadDefinitions ? "HIT" : "MISS",
1852
+ cacheAction,
1853
+ cacheIsBlocking: !cacheHadDefinitions,
1854
+ duration: Date.now() - startTime,
1855
+ mode: mode === "streaming" ? "stream" : mode === "polling" ? "poll" : mode
1856
+ };
1857
+ const configUpdatedAt = _optionalChain([this, 'access', _70 => _70.data, 'optionalAccess', _71 => _71.configUpdatedAt]);
1858
+ if (typeof configUpdatedAt === "number") {
1859
+ trackOptions.configUpdatedAt = configUpdatedAt;
1860
+ }
1861
+ const revision = _optionalChain([this, 'access', _72 => _72.data, 'optionalAccess', _73 => _73.revision]);
1862
+ if (typeof revision === "number") {
1863
+ trackOptions.revision = revision;
1864
+ }
1865
+ if (isFirstRead) {
1866
+ trackOptions.cacheIsFirstRead = true;
1867
+ }
1868
+ this.usageTracker.trackRead(trackOptions);
1869
+ }
1870
+ }, _class3);
1871
+
1872
+ // src/utils/sdk-keys.ts
1873
+ function parseSdkKeyFromFlagsConnectionString(text) {
1874
+ if (text.startsWith("vf_")) return text;
1875
+ try {
1876
+ if (!text.startsWith("flags:")) return null;
1877
+ const params = new URLSearchParams(text.slice(6));
1878
+ return params.get("sdkKey");
1879
+ } catch (e13) {
1880
+ }
1881
+ return null;
1882
+ }
1883
+
1884
+ // src/index.make.ts
1885
+ function make(createRawClient) {
1886
+ let _defaultFlagsClient = null;
1887
+ function createClient2(sdkKeyOrConnectionString, options) {
1888
+ if (!sdkKeyOrConnectionString)
1889
+ throw new Error("@vercel/flags-core: Missing sdkKey");
1890
+ if (typeof sdkKeyOrConnectionString !== "string")
1891
+ throw new Error(
1892
+ `@vercel/flags-core: Invalid sdkKey. Expected string, got ${typeof sdkKeyOrConnectionString}`
1893
+ );
1894
+ const sdkKey = parseSdkKeyFromFlagsConnectionString(
1895
+ sdkKeyOrConnectionString
1896
+ );
1897
+ if (!sdkKey) {
1898
+ throw new Error(
1899
+ "@vercel/flags-core: Missing sdkKey in connection string"
1900
+ );
1901
+ }
1902
+ const controller = new Controller({ sdkKey, ...options });
1903
+ return createRawClient({
1904
+ controller,
1905
+ origin: { provider: "vercel", sdkKey }
1906
+ });
1907
+ }
1908
+ function resetDefaultFlagsClient2() {
1909
+ _defaultFlagsClient = null;
1910
+ }
1911
+ const flagsClient2 = new Proxy({}, {
1912
+ get(_, prop) {
1913
+ if (!_defaultFlagsClient) {
1914
+ if (!process.env.FLAGS) {
1915
+ throw new Error("flags: Missing environment variable FLAGS");
1916
+ }
1917
+ const sdkKey = parseSdkKeyFromFlagsConnectionString(process.env.FLAGS);
1918
+ if (!sdkKey) {
1919
+ throw new Error("@vercel/flags-core: Missing sdkKey");
1920
+ }
1921
+ _defaultFlagsClient = createClient2(sdkKey);
1922
+ }
1923
+ return _defaultFlagsClient[prop];
1924
+ }
1925
+ });
1926
+ return {
1927
+ flagsClient: flagsClient2,
1928
+ resetDefaultFlagsClient: resetDefaultFlagsClient2,
1929
+ createClient: createClient2
1930
+ };
1931
+ }
1932
+
1933
+ // src/index.next-js.ts
1934
+ function setCacheLife() {
1935
+ try {
1936
+ _cache.cacheLife.call(void 0, { revalidate: 0, expire: 0 });
1937
+ _cache.cacheLife.call(void 0, { stale: 60 });
1938
+ } catch (e14) {
1939
+ }
1940
+ }
1941
+ var cachedFns = {
1942
+ initialize: async (...args) => {
1943
+ "use cache";
1944
+ setCacheLife();
1945
+ return initialize(...args);
1946
+ },
1947
+ shutdown: async (...args) => {
1948
+ "use cache";
1949
+ setCacheLife();
1950
+ return shutdown(...args);
1951
+ },
1952
+ getDatafile: async (...args) => {
1953
+ "use cache";
1954
+ setCacheLife();
1955
+ return getDatafile(...args);
1956
+ },
1957
+ getFallbackDatafile: async (...args) => {
1958
+ "use cache";
1959
+ setCacheLife();
1960
+ return getFallbackDatafile(...args);
1961
+ },
1962
+ evaluate: async (...args) => {
1963
+ "use cache";
1964
+ setCacheLife();
1965
+ return evaluate2(...args);
1966
+ }
1967
+ };
1968
+ var { flagsClient, resetDefaultFlagsClient, createClient } = make(
1969
+ createCreateRawClient(cachedFns)
1970
+ );
1971
+
1972
+
1973
+
1974
+
1975
+
1976
+
1977
+
1978
+
1979
+
1980
+
1981
+ exports.ResolutionReason = ResolutionReason; exports.evaluate = evaluate; exports.FallbackNotFoundError = FallbackNotFoundError; exports.FallbackEntryNotFoundError = FallbackEntryNotFoundError; exports.Controller = Controller; exports.flagsClient = flagsClient; exports.resetDefaultFlagsClient = resetDefaultFlagsClient; exports.createClient = createClient;
1982
+ //# sourceMappingURL=chunk-WRX7DC3S.cjs.map