@quonfig/node 0.0.1

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/dist/index.cjs ADDED
@@ -0,0 +1,1912 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ BoundQuonfig: () => BoundQuonfig,
34
+ Client: () => Client,
35
+ ConfigStore: () => ConfigStore,
36
+ ConfigType: () => ConfigType,
37
+ ConfigValueType: () => ConfigValueType,
38
+ ContextShapeCollector: () => ContextShapeCollector,
39
+ EvaluationSummaryCollector: () => EvaluationSummaryCollector,
40
+ Evaluator: () => Evaluator,
41
+ ExampleContextCollector: () => ExampleContextCollector,
42
+ LOG_LEVEL_PREFIX: () => LOG_LEVEL_PREFIX,
43
+ OP_ALWAYS_TRUE: () => OP_ALWAYS_TRUE,
44
+ OP_HIERARCHICAL_MATCH: () => OP_HIERARCHICAL_MATCH,
45
+ OP_IN_INT_RANGE: () => OP_IN_INT_RANGE,
46
+ OP_IN_SEG: () => OP_IN_SEG,
47
+ OP_NOT_IN_SEG: () => OP_NOT_IN_SEG,
48
+ OP_NOT_SET: () => OP_NOT_SET,
49
+ OP_PROP_AFTER: () => OP_PROP_AFTER,
50
+ OP_PROP_BEFORE: () => OP_PROP_BEFORE,
51
+ OP_PROP_CONTAINS_ONE_OF: () => OP_PROP_CONTAINS_ONE_OF,
52
+ OP_PROP_DOES_NOT_CONTAIN_ONE_OF: () => OP_PROP_DOES_NOT_CONTAIN_ONE_OF,
53
+ OP_PROP_DOES_NOT_END_WITH_ONE_OF: () => OP_PROP_DOES_NOT_END_WITH_ONE_OF,
54
+ OP_PROP_DOES_NOT_MATCH: () => OP_PROP_DOES_NOT_MATCH,
55
+ OP_PROP_DOES_NOT_START_WITH_ONE_OF: () => OP_PROP_DOES_NOT_START_WITH_ONE_OF,
56
+ OP_PROP_ENDS_WITH_ONE_OF: () => OP_PROP_ENDS_WITH_ONE_OF,
57
+ OP_PROP_GREATER_THAN: () => OP_PROP_GREATER_THAN,
58
+ OP_PROP_GREATER_THAN_OR_EQUAL: () => OP_PROP_GREATER_THAN_OR_EQUAL,
59
+ OP_PROP_IS_NOT_ONE_OF: () => OP_PROP_IS_NOT_ONE_OF,
60
+ OP_PROP_IS_ONE_OF: () => OP_PROP_IS_ONE_OF,
61
+ OP_PROP_LESS_THAN: () => OP_PROP_LESS_THAN,
62
+ OP_PROP_LESS_THAN_OR_EQUAL: () => OP_PROP_LESS_THAN_OR_EQUAL,
63
+ OP_PROP_MATCHES: () => OP_PROP_MATCHES,
64
+ OP_PROP_SEMVER_EQUAL: () => OP_PROP_SEMVER_EQUAL,
65
+ OP_PROP_SEMVER_GREATER_THAN: () => OP_PROP_SEMVER_GREATER_THAN,
66
+ OP_PROP_SEMVER_LESS_THAN: () => OP_PROP_SEMVER_LESS_THAN,
67
+ OP_PROP_STARTS_WITH_ONE_OF: () => OP_PROP_STARTS_WITH_ONE_OF,
68
+ ProvidedSource: () => ProvidedSource,
69
+ Quonfig: () => Quonfig,
70
+ Resolver: () => Resolver,
71
+ TelemetryReporter: () => TelemetryReporter,
72
+ Transport: () => Transport,
73
+ WeightedValueResolver: () => WeightedValueResolver,
74
+ compareSemver: () => compareSemver,
75
+ contextLookup: () => contextLookup,
76
+ decrypt: () => decrypt,
77
+ durationToMilliseconds: () => durationToMilliseconds,
78
+ encrypt: () => encrypt,
79
+ encryption: () => encryption,
80
+ evaluateCriterion: () => evaluateCriterion,
81
+ generateNewHexKey: () => generateNewHexKey,
82
+ getContextValue: () => getContextValue,
83
+ getProjectEnvFromSdkKey: () => getProjectEnvFromSdkKey,
84
+ hashZeroToOne: () => hashZeroToOne,
85
+ mergeContexts: () => mergeContexts,
86
+ parseLevel: () => parseLevel,
87
+ parseSemver: () => parseSemver,
88
+ shouldLog: () => shouldLog,
89
+ valueTypeStringForConfig: () => valueTypeStringForConfig,
90
+ wordLevelToNumber: () => wordLevelToNumber
91
+ });
92
+ module.exports = __toCommonJS(index_exports);
93
+
94
+ // src/quonfig.ts
95
+ var import_crypto3 = require("crypto");
96
+ var import_fs = require("fs");
97
+
98
+ // src/store.ts
99
+ var ConfigStore = class {
100
+ configs = /* @__PURE__ */ new Map();
101
+ version = "";
102
+ environmentId = "";
103
+ get(key) {
104
+ return this.configs.get(key);
105
+ }
106
+ keys() {
107
+ return Array.from(this.configs.keys());
108
+ }
109
+ getEnvironmentId() {
110
+ return this.environmentId;
111
+ }
112
+ getVersion() {
113
+ return this.version;
114
+ }
115
+ /**
116
+ * Replace all configs with those from the given envelope.
117
+ * Also normalizes values that need special deserialization (weighted_values, provided).
118
+ */
119
+ update(envelope) {
120
+ const next = /* @__PURE__ */ new Map();
121
+ for (const cfg of envelope.configs) {
122
+ normalizeConfigResponse(cfg);
123
+ next.set(cfg.key, cfg);
124
+ }
125
+ this.configs = next;
126
+ this.version = envelope.meta.version;
127
+ this.environmentId = envelope.meta.environment;
128
+ }
129
+ /**
130
+ * Load from a raw datafile (JSON object).
131
+ */
132
+ loadFromDatafile(data) {
133
+ this.update(data);
134
+ }
135
+ };
136
+ function normalizeConfigResponse(cfg) {
137
+ if (!cfg.default.rules) {
138
+ cfg.default.rules = [];
139
+ }
140
+ for (const rule of cfg.default.rules) {
141
+ normalizeValue(rule.value);
142
+ for (const criterion of rule.criteria ?? []) {
143
+ if (criterion.valueToMatch) {
144
+ normalizeValue(criterion.valueToMatch);
145
+ }
146
+ }
147
+ }
148
+ if (cfg.environment) {
149
+ if (!cfg.environment.rules) {
150
+ cfg.environment.rules = [];
151
+ }
152
+ for (const rule of cfg.environment.rules) {
153
+ normalizeValue(rule.value);
154
+ for (const criterion of rule.criteria ?? []) {
155
+ if (criterion.valueToMatch) {
156
+ normalizeValue(criterion.valueToMatch);
157
+ }
158
+ }
159
+ }
160
+ }
161
+ }
162
+ function normalizeValue(v) {
163
+ if (v.type === "weighted_values" && v.value && typeof v.value === "object") {
164
+ const wvd = v.value;
165
+ if (wvd.weightedValues) {
166
+ for (const wv of wvd.weightedValues) {
167
+ if (wv.value) {
168
+ normalizeValue(wv.value);
169
+ }
170
+ }
171
+ }
172
+ }
173
+ if (v.type === "provided" && v.value && typeof v.value === "object") {
174
+ const pd = v.value;
175
+ if (!pd.source) {
176
+ pd.source = "ENV_VAR";
177
+ }
178
+ }
179
+ }
180
+
181
+ // src/context.ts
182
+ function contextLookup(contexts, propertyName) {
183
+ if (propertyName === void 0) {
184
+ return void 0;
185
+ }
186
+ const dotIndex = propertyName.indexOf(".");
187
+ if (dotIndex === -1) {
188
+ const ctx2 = contexts[""];
189
+ if (ctx2 === void 0) {
190
+ return void 0;
191
+ }
192
+ return ctx2[propertyName];
193
+ }
194
+ const contextName = propertyName.slice(0, dotIndex);
195
+ const key = propertyName.slice(dotIndex + 1);
196
+ const ctx = contexts[contextName];
197
+ if (ctx === void 0) {
198
+ return void 0;
199
+ }
200
+ return ctx[key];
201
+ }
202
+ function mergeContexts(...sets) {
203
+ const result = {};
204
+ for (const cs of sets) {
205
+ if (cs === void 0) {
206
+ continue;
207
+ }
208
+ for (const [name, ctx] of Object.entries(cs)) {
209
+ if (result[name] === void 0) {
210
+ result[name] = { ...ctx };
211
+ } else {
212
+ result[name] = { ...result[name], ...ctx };
213
+ }
214
+ }
215
+ }
216
+ return result;
217
+ }
218
+ function getContextValue(contexts, propertyName) {
219
+ if (propertyName === "prefab.current-time" || propertyName === "quonfig.current-time" || propertyName === "reforge.current-time") {
220
+ return { value: Date.now(), exists: true };
221
+ }
222
+ const value = contextLookup(contexts, propertyName);
223
+ return { value, exists: value !== void 0 && value !== null };
224
+ }
225
+
226
+ // src/semver.ts
227
+ var SEMVER_PATTERN = /^(?<major>0|[1-9]\d*)\.(?<minor>0|[1-9]\d*)\.(?<patch>0|[1-9]\d*)(?:-(?<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/;
228
+ function parseSemver(version) {
229
+ if (!version) {
230
+ return void 0;
231
+ }
232
+ const match = SEMVER_PATTERN.exec(version);
233
+ if (!match || !match.groups) {
234
+ return void 0;
235
+ }
236
+ const major = parseInt(match.groups["major"], 10);
237
+ const minor = parseInt(match.groups["minor"], 10);
238
+ const patch = parseInt(match.groups["patch"], 10);
239
+ if (isNaN(major) || isNaN(minor) || isNaN(patch)) {
240
+ return void 0;
241
+ }
242
+ return {
243
+ major,
244
+ minor,
245
+ patch,
246
+ prerelease: match.groups["prerelease"] ?? "",
247
+ buildMetadata: match.groups["buildmetadata"] ?? ""
248
+ };
249
+ }
250
+ function isNumeric(s) {
251
+ return /^\d+$/.test(s);
252
+ }
253
+ function comparePreReleaseIdentifiers(id1, id2) {
254
+ if (isNumeric(id1) && isNumeric(id2)) {
255
+ const num1 = parseInt(id1, 10);
256
+ const num2 = parseInt(id2, 10);
257
+ if (num1 < num2) return -1;
258
+ if (num1 > num2) return 1;
259
+ return 0;
260
+ }
261
+ if (isNumeric(id1)) return -1;
262
+ if (isNumeric(id2)) return 1;
263
+ if (id1 < id2) return -1;
264
+ if (id1 > id2) return 1;
265
+ return 0;
266
+ }
267
+ function comparePreRelease(pre1, pre2) {
268
+ if (pre1 === "" && pre2 === "") return 0;
269
+ if (pre1 === "") return 1;
270
+ if (pre2 === "") return -1;
271
+ const ids1 = pre1.split(".");
272
+ const ids2 = pre2.split(".");
273
+ const minLen = Math.min(ids1.length, ids2.length);
274
+ for (let i = 0; i < minLen; i++) {
275
+ const cmp = comparePreReleaseIdentifiers(ids1[i], ids2[i]);
276
+ if (cmp !== 0) return cmp;
277
+ }
278
+ if (ids1.length < ids2.length) return -1;
279
+ if (ids1.length > ids2.length) return 1;
280
+ return 0;
281
+ }
282
+ function compareSemver(a, b) {
283
+ if (a.major !== b.major) return a.major > b.major ? 1 : -1;
284
+ if (a.minor !== b.minor) return a.minor > b.minor ? 1 : -1;
285
+ if (a.patch !== b.patch) return a.patch > b.patch ? 1 : -1;
286
+ return comparePreRelease(a.prerelease, b.prerelease);
287
+ }
288
+
289
+ // src/operators.ts
290
+ var OP_NOT_SET = "NOT_SET";
291
+ var OP_ALWAYS_TRUE = "ALWAYS_TRUE";
292
+ var OP_PROP_IS_ONE_OF = "PROP_IS_ONE_OF";
293
+ var OP_PROP_IS_NOT_ONE_OF = "PROP_IS_NOT_ONE_OF";
294
+ var OP_PROP_STARTS_WITH_ONE_OF = "PROP_STARTS_WITH_ONE_OF";
295
+ var OP_PROP_DOES_NOT_START_WITH_ONE_OF = "PROP_DOES_NOT_START_WITH_ONE_OF";
296
+ var OP_PROP_ENDS_WITH_ONE_OF = "PROP_ENDS_WITH_ONE_OF";
297
+ var OP_PROP_DOES_NOT_END_WITH_ONE_OF = "PROP_DOES_NOT_END_WITH_ONE_OF";
298
+ var OP_PROP_CONTAINS_ONE_OF = "PROP_CONTAINS_ONE_OF";
299
+ var OP_PROP_DOES_NOT_CONTAIN_ONE_OF = "PROP_DOES_NOT_CONTAIN_ONE_OF";
300
+ var OP_PROP_MATCHES = "PROP_MATCHES";
301
+ var OP_PROP_DOES_NOT_MATCH = "PROP_DOES_NOT_MATCH";
302
+ var OP_HIERARCHICAL_MATCH = "HIERARCHICAL_MATCH";
303
+ var OP_IN_INT_RANGE = "IN_INT_RANGE";
304
+ var OP_PROP_GREATER_THAN = "PROP_GREATER_THAN";
305
+ var OP_PROP_GREATER_THAN_OR_EQUAL = "PROP_GREATER_THAN_OR_EQUAL";
306
+ var OP_PROP_LESS_THAN = "PROP_LESS_THAN";
307
+ var OP_PROP_LESS_THAN_OR_EQUAL = "PROP_LESS_THAN_OR_EQUAL";
308
+ var OP_PROP_BEFORE = "PROP_BEFORE";
309
+ var OP_PROP_AFTER = "PROP_AFTER";
310
+ var OP_PROP_SEMVER_LESS_THAN = "PROP_SEMVER_LESS_THAN";
311
+ var OP_PROP_SEMVER_EQUAL = "PROP_SEMVER_EQUAL";
312
+ var OP_PROP_SEMVER_GREATER_THAN = "PROP_SEMVER_GREATER_THAN";
313
+ var OP_IN_SEG = "IN_SEG";
314
+ var OP_NOT_IN_SEG = "NOT_IN_SEG";
315
+ function toString(v) {
316
+ if (v === null || v === void 0) return "";
317
+ return String(v);
318
+ }
319
+ function toStringSlice(v) {
320
+ if (v === null || v === void 0) return [];
321
+ if (Array.isArray(v)) {
322
+ return v.map((item) => toString(item));
323
+ }
324
+ return [toString(v)];
325
+ }
326
+ function getStringList(v) {
327
+ if (v === void 0 || v === null) return void 0;
328
+ if (Array.isArray(v.value)) {
329
+ return v.value.map((item) => toString(item));
330
+ }
331
+ return void 0;
332
+ }
333
+ function isString(v) {
334
+ return typeof v === "string";
335
+ }
336
+ function isNumber(v) {
337
+ return typeof v === "number";
338
+ }
339
+ function isNumericValue(v) {
340
+ if (typeof v === "number") return true;
341
+ if (typeof v === "string") return !isNaN(Number(v)) && v.trim() !== "";
342
+ return false;
343
+ }
344
+ function toFloat64(v) {
345
+ if (typeof v === "number") return v;
346
+ if (typeof v === "string") {
347
+ const n = Number(v);
348
+ if (!isNaN(n)) return n;
349
+ }
350
+ return void 0;
351
+ }
352
+ function compareNumbers(a, b) {
353
+ const af = toFloat64(a);
354
+ const bf = toFloat64(b);
355
+ if (af === void 0 || bf === void 0) return void 0;
356
+ if (af < bf) return -1;
357
+ if (af > bf) return 1;
358
+ return 0;
359
+ }
360
+ function dateToMillis(val) {
361
+ if (typeof val === "number") return val;
362
+ if (typeof val === "string") {
363
+ const d = Date.parse(val);
364
+ if (!isNaN(d)) return d;
365
+ const n = parseInt(val, 10);
366
+ if (!isNaN(n)) return n;
367
+ }
368
+ return void 0;
369
+ }
370
+ function stringInSlice(s, list) {
371
+ return list.includes(s);
372
+ }
373
+ function startsWithAny(prefixes, target) {
374
+ return prefixes.some((p) => target.startsWith(p));
375
+ }
376
+ function endsWithAny(suffixes, target) {
377
+ return suffixes.some((s) => target.endsWith(s));
378
+ }
379
+ function containsAny(substrings, target) {
380
+ return substrings.some((s) => target.includes(s));
381
+ }
382
+ function extractIntRange(v) {
383
+ let start = Number.MIN_SAFE_INTEGER;
384
+ let end = Number.MAX_SAFE_INTEGER;
385
+ if (v === void 0 || v === null) return { start, end };
386
+ if (typeof v.value === "object" && v.value !== null && !Array.isArray(v.value)) {
387
+ if ("start" in v.value) {
388
+ const s = toFloat64(v.value.start);
389
+ if (s !== void 0) start = s;
390
+ }
391
+ if ("end" in v.value) {
392
+ const e = toFloat64(v.value.end);
393
+ if (e !== void 0) end = e;
394
+ }
395
+ }
396
+ return { start, end };
397
+ }
398
+ function evaluateCriterion(contextValue, contextExists, criterion, segmentResolver) {
399
+ const matchValue = criterion.valueToMatch;
400
+ switch (criterion.operator) {
401
+ case OP_NOT_SET:
402
+ return false;
403
+ case OP_ALWAYS_TRUE:
404
+ return true;
405
+ case OP_PROP_IS_ONE_OF:
406
+ case OP_PROP_IS_NOT_ONE_OF: {
407
+ if (contextExists && matchValue !== void 0) {
408
+ const matchStrings = getStringList(matchValue);
409
+ if (matchStrings !== void 0) {
410
+ const contextStrings = toStringSlice(contextValue);
411
+ let matchFound = false;
412
+ for (const cv of contextStrings) {
413
+ if (stringInSlice(cv, matchStrings)) {
414
+ matchFound = true;
415
+ break;
416
+ }
417
+ }
418
+ return matchFound === (criterion.operator === OP_PROP_IS_ONE_OF);
419
+ }
420
+ }
421
+ return criterion.operator === OP_PROP_IS_NOT_ONE_OF;
422
+ }
423
+ case OP_PROP_STARTS_WITH_ONE_OF:
424
+ case OP_PROP_DOES_NOT_START_WITH_ONE_OF: {
425
+ if (contextExists && matchValue !== void 0) {
426
+ const matchStrings = getStringList(matchValue);
427
+ if (matchStrings !== void 0) {
428
+ const cv = toString(contextValue);
429
+ const matchFound = startsWithAny(matchStrings, cv);
430
+ return matchFound === (criterion.operator === OP_PROP_STARTS_WITH_ONE_OF);
431
+ }
432
+ }
433
+ return criterion.operator === OP_PROP_DOES_NOT_START_WITH_ONE_OF;
434
+ }
435
+ case OP_PROP_ENDS_WITH_ONE_OF:
436
+ case OP_PROP_DOES_NOT_END_WITH_ONE_OF: {
437
+ if (contextExists && matchValue !== void 0) {
438
+ const matchStrings = getStringList(matchValue);
439
+ if (matchStrings !== void 0) {
440
+ const cv = toString(contextValue);
441
+ const matchFound = endsWithAny(matchStrings, cv);
442
+ return matchFound === (criterion.operator === OP_PROP_ENDS_WITH_ONE_OF);
443
+ }
444
+ }
445
+ return criterion.operator === OP_PROP_DOES_NOT_END_WITH_ONE_OF;
446
+ }
447
+ case OP_PROP_CONTAINS_ONE_OF:
448
+ case OP_PROP_DOES_NOT_CONTAIN_ONE_OF: {
449
+ if (contextExists && matchValue !== void 0) {
450
+ const matchStrings = getStringList(matchValue);
451
+ if (matchStrings !== void 0) {
452
+ const cv = toString(contextValue);
453
+ const matchFound = containsAny(matchStrings, cv);
454
+ return matchFound === (criterion.operator === OP_PROP_CONTAINS_ONE_OF);
455
+ }
456
+ }
457
+ return criterion.operator === OP_PROP_DOES_NOT_CONTAIN_ONE_OF;
458
+ }
459
+ case OP_PROP_MATCHES:
460
+ case OP_PROP_DOES_NOT_MATCH: {
461
+ if (contextExists && matchValue !== void 0 && isString(contextValue) && isString(matchValue.value)) {
462
+ try {
463
+ const re = new RegExp(matchValue.value);
464
+ const matched = re.test(contextValue);
465
+ return matched === (criterion.operator === OP_PROP_MATCHES);
466
+ } catch {
467
+ }
468
+ }
469
+ return false;
470
+ }
471
+ case OP_HIERARCHICAL_MATCH: {
472
+ if (contextExists && matchValue !== void 0) {
473
+ const cv = toString(contextValue);
474
+ const mv = toString(matchValue.value);
475
+ return cv.startsWith(mv);
476
+ }
477
+ return false;
478
+ }
479
+ case OP_IN_INT_RANGE: {
480
+ if (contextExists && matchValue !== void 0) {
481
+ const { start, end } = extractIntRange(matchValue);
482
+ const numVal = toFloat64(contextValue);
483
+ if (numVal !== void 0) {
484
+ return numVal >= start && numVal < end;
485
+ }
486
+ }
487
+ return false;
488
+ }
489
+ case OP_PROP_GREATER_THAN:
490
+ case OP_PROP_GREATER_THAN_OR_EQUAL:
491
+ case OP_PROP_LESS_THAN:
492
+ case OP_PROP_LESS_THAN_OR_EQUAL: {
493
+ if (contextExists && matchValue !== void 0 && isNumber(contextValue) && isNumericValue(matchValue.value)) {
494
+ const cmp = compareNumbers(contextValue, matchValue.value);
495
+ if (cmp !== void 0) {
496
+ switch (criterion.operator) {
497
+ case OP_PROP_GREATER_THAN:
498
+ return cmp > 0;
499
+ case OP_PROP_GREATER_THAN_OR_EQUAL:
500
+ return cmp >= 0;
501
+ case OP_PROP_LESS_THAN:
502
+ return cmp < 0;
503
+ case OP_PROP_LESS_THAN_OR_EQUAL:
504
+ return cmp <= 0;
505
+ }
506
+ }
507
+ }
508
+ return false;
509
+ }
510
+ case OP_PROP_BEFORE:
511
+ case OP_PROP_AFTER: {
512
+ if (contextExists && matchValue !== void 0) {
513
+ const contextMillis = dateToMillis(contextValue);
514
+ const matchMillis = dateToMillis(matchValue.value);
515
+ if (contextMillis !== void 0 && matchMillis !== void 0) {
516
+ if (criterion.operator === OP_PROP_BEFORE) {
517
+ return contextMillis < matchMillis;
518
+ }
519
+ return contextMillis > matchMillis;
520
+ }
521
+ }
522
+ return false;
523
+ }
524
+ case OP_PROP_SEMVER_LESS_THAN:
525
+ case OP_PROP_SEMVER_EQUAL:
526
+ case OP_PROP_SEMVER_GREATER_THAN: {
527
+ if (contextExists && matchValue !== void 0 && isString(contextValue) && isString(matchValue.value)) {
528
+ const svContext = parseSemver(contextValue);
529
+ const svMatch = parseSemver(matchValue.value);
530
+ if (svContext !== void 0 && svMatch !== void 0) {
531
+ const cmp = compareSemver(svContext, svMatch);
532
+ switch (criterion.operator) {
533
+ case OP_PROP_SEMVER_LESS_THAN:
534
+ return cmp < 0;
535
+ case OP_PROP_SEMVER_EQUAL:
536
+ return cmp === 0;
537
+ case OP_PROP_SEMVER_GREATER_THAN:
538
+ return cmp > 0;
539
+ }
540
+ }
541
+ }
542
+ return false;
543
+ }
544
+ case OP_IN_SEG:
545
+ case OP_NOT_IN_SEG: {
546
+ if (matchValue !== void 0 && segmentResolver !== void 0) {
547
+ const segmentKey = toString(matchValue.value);
548
+ const { result, found } = segmentResolver(segmentKey);
549
+ if (!found) {
550
+ return criterion.operator === OP_NOT_IN_SEG;
551
+ }
552
+ return result === (criterion.operator === OP_IN_SEG);
553
+ }
554
+ return criterion.operator === OP_NOT_IN_SEG;
555
+ }
556
+ default:
557
+ return false;
558
+ }
559
+ }
560
+
561
+ // src/hashing.ts
562
+ var import_murmurhash = __toESM(require("murmurhash"), 1);
563
+ function hashZeroToOne(value) {
564
+ const hash = import_murmurhash.default.v3(value);
565
+ return hash / 4294967295;
566
+ }
567
+
568
+ // src/weighted.ts
569
+ var WeightedValueResolver = class {
570
+ /**
571
+ * Resolve picks a value from the weighted distribution.
572
+ *
573
+ * If hashByPropertyName is set and the context has a value for that property,
574
+ * the selection is deterministic via Murmur3 hash. Otherwise, it falls back
575
+ * to Math.random().
576
+ *
577
+ * Returns the selected value and its index, or [undefined, -1] if no values.
578
+ */
579
+ resolve(wv, configKey, contexts) {
580
+ const fraction = this.getUserFraction(wv, configKey, contexts);
581
+ let totalWeight = 0;
582
+ for (const entry of wv.weightedValues) {
583
+ totalWeight += entry.weight;
584
+ }
585
+ const threshold = fraction * totalWeight;
586
+ let runningSum = 0;
587
+ for (let i = 0; i < wv.weightedValues.length; i++) {
588
+ runningSum += wv.weightedValues[i].weight;
589
+ if (runningSum >= threshold) {
590
+ return { value: { ...wv.weightedValues[i].value }, index: i };
591
+ }
592
+ }
593
+ if (wv.weightedValues.length > 0) {
594
+ return { value: { ...wv.weightedValues[0].value }, index: 0 };
595
+ }
596
+ return { value: void 0, index: -1 };
597
+ }
598
+ getUserFraction(wv, configKey, contexts) {
599
+ if (wv.hashByPropertyName) {
600
+ const value = contextLookup(contexts, wv.hashByPropertyName);
601
+ if (value !== void 0 && value !== null) {
602
+ const valueToHash = `${configKey}${value}`;
603
+ return hashZeroToOne(valueToHash);
604
+ }
605
+ }
606
+ return Math.random();
607
+ }
608
+ };
609
+
610
+ // src/evaluator.ts
611
+ var Evaluator = class {
612
+ configStore;
613
+ weighted;
614
+ constructor(configStore) {
615
+ this.configStore = configStore;
616
+ this.weighted = new WeightedValueResolver();
617
+ }
618
+ /**
619
+ * Evaluate a config for the given environment and context.
620
+ *
621
+ * Evaluation flow:
622
+ * 1. Find the environment block matching envID (if any)
623
+ * 2. Iterate its rules top-to-bottom; first match wins
624
+ * 3. If no env-specific match, fall back to default.rules
625
+ * 4. For each rule, all criteria must match (AND logic)
626
+ * 5. If matched value is weighted_values, resolve through WeightedValueResolver
627
+ */
628
+ evaluateConfig(cfg, envID, contexts) {
629
+ if (envID && cfg.environment && cfg.environment.id === envID) {
630
+ const match2 = this.evaluateRules(cfg, cfg.environment.rules ?? [], contexts, 0);
631
+ if (match2 !== void 0) {
632
+ return match2;
633
+ }
634
+ }
635
+ const match = this.evaluateRules(cfg, cfg.default.rules ?? [], contexts, 0);
636
+ if (match !== void 0) {
637
+ return match;
638
+ }
639
+ return { isMatch: false, ruleIndex: -1, weightedValueIndex: -1 };
640
+ }
641
+ evaluateRules(cfg, rules, contexts, ruleIndexOffset) {
642
+ for (let i = 0; i < rules.length; i++) {
643
+ const rule = rules[i];
644
+ if (this.evaluateAllCriteria(cfg, rule.criteria, contexts)) {
645
+ const value = { ...rule.value };
646
+ const match = {
647
+ isMatch: true,
648
+ value,
649
+ ruleIndex: ruleIndexOffset + i,
650
+ weightedValueIndex: -1
651
+ };
652
+ if (value.type === "weighted_values" && value.value) {
653
+ const wvData = value.value;
654
+ if (wvData && wvData.weightedValues) {
655
+ const resolved = this.weighted.resolve(wvData, cfg.key, contexts);
656
+ if (resolved.value !== void 0) {
657
+ match.value = resolved.value;
658
+ match.weightedValueIndex = resolved.index;
659
+ }
660
+ }
661
+ }
662
+ return match;
663
+ }
664
+ }
665
+ return void 0;
666
+ }
667
+ evaluateAllCriteria(cfg, criteria, contexts) {
668
+ for (const criterion of criteria) {
669
+ if (!this.evaluateSingleCriterion(cfg, criterion, contexts)) {
670
+ return false;
671
+ }
672
+ }
673
+ return true;
674
+ }
675
+ evaluateSingleCriterion(cfg, criterion, contexts) {
676
+ const propertyName = criterion.propertyName ?? "";
677
+ const { value: contextValue, exists: contextExists } = getContextValue(
678
+ contexts,
679
+ propertyName
680
+ );
681
+ const segmentResolver = (segmentKey) => {
682
+ const segConfig = this.configStore.get(segmentKey);
683
+ if (segConfig === void 0) {
684
+ return { result: false, found: false };
685
+ }
686
+ const segMatch = this.evaluateConfig(segConfig, "", contexts);
687
+ if (!segMatch.isMatch || segMatch.value === void 0) {
688
+ return { result: false, found: false };
689
+ }
690
+ return { result: !!segMatch.value.value, found: true };
691
+ };
692
+ return evaluateCriterion(contextValue, contextExists, criterion, segmentResolver);
693
+ }
694
+ };
695
+
696
+ // src/encryption.ts
697
+ var import_crypto = require("crypto");
698
+ var CIPHER_TYPE = "aes-256-gcm";
699
+ var SEPARATOR = "--";
700
+ var KEY_LENGTH = 32;
701
+ function generateNewHexKey() {
702
+ return (0, import_crypto.randomBytes)(KEY_LENGTH).toString("hex");
703
+ }
704
+ function encrypt(clearText, keyStringHex) {
705
+ if (keyStringHex.length !== KEY_LENGTH * 2) {
706
+ throw new Error(
707
+ `Invalid key length. Key must be a 64 character hex string (representing a 32-byte key). You provided ${keyStringHex.length} characters.`
708
+ );
709
+ }
710
+ const key = Buffer.from(keyStringHex, "hex");
711
+ const iv = (0, import_crypto.randomBytes)(12);
712
+ const cipher = (0, import_crypto.createCipheriv)(CIPHER_TYPE, key, iv);
713
+ let encrypted = cipher.update(clearText, "utf8", "hex");
714
+ encrypted += cipher.final("hex");
715
+ const tag = cipher.getAuthTag().toString("hex");
716
+ return [encrypted, iv.toString("hex"), tag].join(SEPARATOR);
717
+ }
718
+ function decrypt(encryptedString, keyStringHex) {
719
+ if (keyStringHex.length !== 64) {
720
+ throw new Error(
721
+ "Invalid key length. Key must be a 64-character hex string."
722
+ );
723
+ }
724
+ const key = Buffer.from(keyStringHex, "hex");
725
+ const parts = encryptedString.split(SEPARATOR);
726
+ if (parts.length !== 3) {
727
+ throw new Error(
728
+ "Invalid encrypted string. Must contain encrypted data, IV, and auth tag."
729
+ );
730
+ }
731
+ const encryptedDataPart = parts[0];
732
+ const ivPart = parts[1];
733
+ const authTagPart = parts[2];
734
+ if (encryptedDataPart === "") {
735
+ return "";
736
+ }
737
+ const encryptedData = Buffer.from(encryptedDataPart, "hex");
738
+ const iv = Buffer.from(ivPart, "hex");
739
+ const authTag = Buffer.from(authTagPart, "hex");
740
+ const decipher = (0, import_crypto.createDecipheriv)(CIPHER_TYPE, key, iv);
741
+ decipher.setAuthTag(authTag);
742
+ let decrypted = decipher.update(encryptedData);
743
+ decrypted = Buffer.concat([decrypted, decipher.final()]);
744
+ return decrypted.toString("utf8");
745
+ }
746
+
747
+ // src/duration.ts
748
+ var PATTERN = /P(?:(?<days>\d+(?:\.\d+)?)D)?(?:T(?:(?<hours>\d+(?:\.\d+)?)H)?(?:(?<minutes>\d+(?:\.\d+)?)M)?(?:(?<seconds>\d+(?:\.\d+)?)S)?)?/;
749
+ var MINUTES_IN_SECONDS = 60;
750
+ var HOURS_IN_SECONDS = 60 * MINUTES_IN_SECONDS;
751
+ var DAYS_IN_SECONDS = 24 * HOURS_IN_SECONDS;
752
+ function durationToMilliseconds(duration) {
753
+ const match = PATTERN.exec(duration);
754
+ if (match === null) {
755
+ return 0;
756
+ }
757
+ const days = parseFloat(match.groups?.["days"] ?? "0");
758
+ const hours = parseFloat(match.groups?.["hours"] ?? "0");
759
+ const minutes = parseFloat(match.groups?.["minutes"] ?? "0");
760
+ const seconds = parseFloat(match.groups?.["seconds"] ?? "0");
761
+ return (days * DAYS_IN_SECONDS + hours * HOURS_IN_SECONDS + minutes * MINUTES_IN_SECONDS + seconds) * 1e3;
762
+ }
763
+
764
+ // src/resolver.ts
765
+ var import_crypto2 = require("crypto");
766
+ var TRUE_VALUES = /* @__PURE__ */ new Set(["true", "1", "t", "yes"]);
767
+ var CONFIDENTIAL_PREFIX = "*****";
768
+ function makeConfidential(secret) {
769
+ const md5 = (0, import_crypto2.createHash)("md5").update(secret).digest("hex");
770
+ return `${CONFIDENTIAL_PREFIX}${md5.slice(-5)}`;
771
+ }
772
+ var Resolver = class {
773
+ store;
774
+ evaluator;
775
+ constructor(store, evaluator) {
776
+ this.store = store;
777
+ this.evaluator = evaluator;
778
+ }
779
+ /**
780
+ * Resolve a matched value. Handles:
781
+ * - provided values (ENV_VAR lookup)
782
+ * - decryption of confidential values
783
+ */
784
+ resolveValue(val, configKey, valueType, envID, contexts) {
785
+ if (val.type === "provided") {
786
+ const provided = val.value;
787
+ if (provided && provided.source === "ENV_VAR" && provided.lookup) {
788
+ const envValue = process.env[provided.lookup];
789
+ if (envValue === void 0) {
790
+ throw new Error(
791
+ `Environment variable "${provided.lookup}" not set for config "${configKey}"`
792
+ );
793
+ }
794
+ const coerced = coerceValue(envValue, valueType);
795
+ return {
796
+ resolved: {
797
+ type: valueTypeForCoerced(valueType),
798
+ value: coerced
799
+ }
800
+ };
801
+ }
802
+ return { resolved: val };
803
+ }
804
+ if (val.confidential && val.decryptWith) {
805
+ const keyCfg = this.store.get(val.decryptWith);
806
+ if (keyCfg === void 0) {
807
+ throw new Error(`Decryption key config "${val.decryptWith}" not found`);
808
+ }
809
+ const keyMatch = this.evaluator.evaluateConfig(keyCfg, envID, contexts);
810
+ if (!keyMatch.isMatch || keyMatch.value === void 0) {
811
+ throw new Error(`Decryption key config "${val.decryptWith}" did not match`);
812
+ }
813
+ const { resolved: resolvedKey } = this.resolveValue(
814
+ keyMatch.value,
815
+ keyCfg.key,
816
+ keyCfg.valueType,
817
+ envID,
818
+ contexts
819
+ );
820
+ const secretKey = String(resolvedKey.value);
821
+ if (!secretKey) {
822
+ throw new Error(`Decryption key from "${val.decryptWith}" is empty`);
823
+ }
824
+ const decrypted = decrypt(String(val.value), secretKey);
825
+ return {
826
+ resolved: {
827
+ type: "string",
828
+ value: decrypted,
829
+ confidential: true
830
+ },
831
+ reportableValue: makeConfidential(String(val.value))
832
+ };
833
+ }
834
+ if (val.confidential) {
835
+ return {
836
+ resolved: val,
837
+ reportableValue: makeConfidential(String(val.value))
838
+ };
839
+ }
840
+ return { resolved: val };
841
+ }
842
+ /**
843
+ * Unwrap a resolved value to a plain JS value.
844
+ */
845
+ unwrapValue(val) {
846
+ switch (val.type) {
847
+ case "bool":
848
+ return !!val.value;
849
+ case "int":
850
+ return typeof val.value === "number" ? val.value : parseInt(String(val.value), 10);
851
+ case "double":
852
+ return typeof val.value === "number" ? val.value : parseFloat(String(val.value));
853
+ case "string":
854
+ return String(val.value ?? "");
855
+ case "json":
856
+ if (typeof val.value === "string") {
857
+ try {
858
+ return JSON.parse(val.value);
859
+ } catch {
860
+ return val.value;
861
+ }
862
+ }
863
+ return val.value;
864
+ case "string_list":
865
+ if (Array.isArray(val.value)) {
866
+ return val.value.map((v) => String(v));
867
+ }
868
+ return [];
869
+ case "log_level":
870
+ return typeof val.value === "number" ? val.value : String(val.value ?? "");
871
+ case "duration":
872
+ return durationToMilliseconds(String(val.value ?? ""));
873
+ default:
874
+ return val.value;
875
+ }
876
+ }
877
+ };
878
+ function coerceValue(value, valueType) {
879
+ switch (valueType) {
880
+ case "string":
881
+ return value;
882
+ case "int": {
883
+ const n = parseInt(value, 10);
884
+ if (isNaN(n)) throw new Error(`Cannot convert "${value}" to int`);
885
+ return n;
886
+ }
887
+ case "double": {
888
+ const n = parseFloat(value);
889
+ if (isNaN(n)) throw new Error(`Cannot convert "${value}" to double`);
890
+ return n;
891
+ }
892
+ case "bool":
893
+ return TRUE_VALUES.has(value.toLowerCase());
894
+ case "string_list":
895
+ return value.split(/\s*,\s*/);
896
+ case "duration":
897
+ return value;
898
+ default:
899
+ return value;
900
+ }
901
+ }
902
+ function valueTypeForCoerced(valueType) {
903
+ switch (valueType) {
904
+ case "int":
905
+ return "int";
906
+ case "double":
907
+ return "double";
908
+ case "bool":
909
+ return "bool";
910
+ case "string_list":
911
+ return "string_list";
912
+ default:
913
+ return "string";
914
+ }
915
+ }
916
+
917
+ // src/transport.ts
918
+ var SDK_VERSION = "0.1.0";
919
+ var DEFAULT_TELEMETRY_URL = "https://telemetry.quonfig.com";
920
+ var Transport = class {
921
+ baseUrl;
922
+ telemetryBaseUrl;
923
+ sdkKey;
924
+ etag = "";
925
+ constructor(baseUrl, sdkKey, telemetryBaseUrl) {
926
+ this.baseUrl = baseUrl.replace(/\/$/, "");
927
+ const envUrl = process.env.QUONFIG_TELEMETRY_URL;
928
+ const url = envUrl || telemetryBaseUrl || DEFAULT_TELEMETRY_URL;
929
+ this.telemetryBaseUrl = url.replace(/\/$/, "");
930
+ this.sdkKey = sdkKey;
931
+ }
932
+ /**
933
+ * Build the Basic auth header value.
934
+ * Uses username "1" like the Go SDK: base64("1:{sdkKey}")
935
+ */
936
+ getAuthHeader() {
937
+ return "Basic " + Buffer.from(`1:${this.sdkKey}`).toString("base64");
938
+ }
939
+ /**
940
+ * Common headers for all requests.
941
+ */
942
+ getHeaders(extra) {
943
+ return {
944
+ Authorization: this.getAuthHeader(),
945
+ "X-Quonfig-SDK-Version": `node-${SDK_VERSION}`,
946
+ Accept: "application/json",
947
+ ...extra
948
+ };
949
+ }
950
+ /**
951
+ * Fetch configs from GET /api/v2/configs with ETag caching.
952
+ *
953
+ * Returns `{ notChanged: true }` if the server responds with 304.
954
+ */
955
+ async fetchConfigs() {
956
+ const headers = this.getHeaders();
957
+ if (this.etag) {
958
+ headers["If-None-Match"] = this.etag;
959
+ }
960
+ const response = await fetch(`${this.baseUrl}/api/v2/configs`, {
961
+ method: "GET",
962
+ headers
963
+ });
964
+ if (response.status === 304) {
965
+ return { notChanged: true };
966
+ }
967
+ if (!response.ok) {
968
+ const body = await response.text().catch(() => "");
969
+ throw new Error(`Unexpected status ${response.status}: ${body}`);
970
+ }
971
+ const etag = response.headers.get("ETag");
972
+ if (etag) {
973
+ this.etag = etag;
974
+ }
975
+ const envelope = await response.json();
976
+ return { envelope, notChanged: false };
977
+ }
978
+ /**
979
+ * Post telemetry data to the telemetry endpoint.
980
+ */
981
+ async postTelemetry(data) {
982
+ const headers = this.getHeaders({
983
+ "Content-Type": "application/json"
984
+ });
985
+ const response = await fetch(`${this.telemetryBaseUrl}/api/v1/telemetry/`, {
986
+ method: "POST",
987
+ headers,
988
+ body: JSON.stringify(data)
989
+ });
990
+ if (!response.ok) {
991
+ const body = await response.text().catch(() => "");
992
+ console.warn(`[quonfig] Telemetry POST failed: ${response.status} ${body}`);
993
+ }
994
+ }
995
+ /**
996
+ * Get the SSE URL for config streaming.
997
+ */
998
+ getSSEUrl() {
999
+ return `${this.baseUrl}/api/v2/sse/config`;
1000
+ }
1001
+ /**
1002
+ * Get auth headers for SSE connection.
1003
+ */
1004
+ getSSEHeaders() {
1005
+ return this.getHeaders({
1006
+ Accept: "text/event-stream"
1007
+ });
1008
+ }
1009
+ };
1010
+
1011
+ // src/sse.ts
1012
+ var SSEConnection = class {
1013
+ transport;
1014
+ eventSource = null;
1015
+ constructor(transport) {
1016
+ this.transport = transport;
1017
+ }
1018
+ /**
1019
+ * Start listening for SSE events.
1020
+ *
1021
+ * The onUpdate callback receives the new config envelope on each event.
1022
+ */
1023
+ start(onUpdate) {
1024
+ this.connectSSE(onUpdate).catch((err) => {
1025
+ console.warn("[quonfig] SSE connection failed:", err);
1026
+ });
1027
+ }
1028
+ async connectSSE(onUpdate) {
1029
+ try {
1030
+ const EventSourceModule = await import("eventsource");
1031
+ const EventSource = EventSourceModule.default || EventSourceModule;
1032
+ const url = this.transport.getSSEUrl();
1033
+ const headers = this.transport.getSSEHeaders();
1034
+ this.eventSource = new EventSource(url, { headers });
1035
+ this.eventSource.onmessage = (event) => {
1036
+ try {
1037
+ const envelope = JSON.parse(event.data);
1038
+ onUpdate(envelope);
1039
+ } catch (err) {
1040
+ console.warn("[quonfig] SSE message parse error:", err);
1041
+ }
1042
+ };
1043
+ this.eventSource.onerror = (err) => {
1044
+ console.warn("[quonfig] SSE error:", err);
1045
+ };
1046
+ } catch (err) {
1047
+ console.warn("[quonfig] Failed to initialize SSE:", err);
1048
+ }
1049
+ }
1050
+ /**
1051
+ * Close the SSE connection.
1052
+ */
1053
+ close() {
1054
+ if (this.eventSource) {
1055
+ this.eventSource.close();
1056
+ this.eventSource = null;
1057
+ }
1058
+ }
1059
+ };
1060
+
1061
+ // src/logger.ts
1062
+ var LOG_LEVEL_PREFIX = "log-level.";
1063
+ var WORD_LEVEL_LOOKUP = {
1064
+ trace: 1,
1065
+ debug: 2,
1066
+ info: 3,
1067
+ warn: 5,
1068
+ error: 6,
1069
+ fatal: 9
1070
+ };
1071
+ var NUMBER_LEVEL_LOOKUP = {
1072
+ 1: "trace",
1073
+ 2: "debug",
1074
+ 3: "info",
1075
+ 5: "warn",
1076
+ 6: "error",
1077
+ 9: "fatal"
1078
+ };
1079
+ function wordLevelToNumber(level) {
1080
+ return WORD_LEVEL_LOOKUP[level];
1081
+ }
1082
+ function parseLevel(level) {
1083
+ if (typeof level === "number") {
1084
+ return level;
1085
+ }
1086
+ if (typeof level === "string") {
1087
+ const lower = level.toLowerCase();
1088
+ if (WORD_LEVEL_LOOKUP[lower] !== void 0) {
1089
+ return WORD_LEVEL_LOOKUP[lower];
1090
+ }
1091
+ const n = parseInt(level, 10);
1092
+ if (!isNaN(n) && NUMBER_LEVEL_LOOKUP[n] !== void 0) {
1093
+ return n;
1094
+ }
1095
+ }
1096
+ return void 0;
1097
+ }
1098
+ function shouldLog(args) {
1099
+ const { loggerName, desiredLevel, defaultLevel, getConfig } = args;
1100
+ let loggerNameWithPrefix = LOG_LEVEL_PREFIX + loggerName;
1101
+ while (loggerNameWithPrefix.includes(".")) {
1102
+ const resolvedLevel = getConfig(loggerNameWithPrefix);
1103
+ if (resolvedLevel !== void 0) {
1104
+ return Number(resolvedLevel) <= desiredLevel;
1105
+ }
1106
+ loggerNameWithPrefix = loggerNameWithPrefix.slice(
1107
+ 0,
1108
+ loggerNameWithPrefix.lastIndexOf(".")
1109
+ );
1110
+ }
1111
+ return defaultLevel <= desiredLevel;
1112
+ }
1113
+
1114
+ // src/telemetry/evaluationSummaries.ts
1115
+ var EvaluationSummaryCollector = class {
1116
+ enabled;
1117
+ data = /* @__PURE__ */ new Map();
1118
+ startAt;
1119
+ maxDataSize;
1120
+ constructor(enabled, maxDataSize = 1e4) {
1121
+ this.enabled = enabled;
1122
+ this.maxDataSize = maxDataSize;
1123
+ }
1124
+ isEnabled() {
1125
+ return this.enabled;
1126
+ }
1127
+ push(evaluation) {
1128
+ if (!this.enabled) return;
1129
+ if (this.data.size >= this.maxDataSize) return;
1130
+ if (evaluation.unwrappedValue === void 0) return;
1131
+ if (evaluation.configType === "log_level") return;
1132
+ this.startAt = this.startAt ?? Date.now();
1133
+ const key = JSON.stringify([evaluation.configKey, evaluation.configType]);
1134
+ const counter = JSON.stringify([
1135
+ evaluation.configId,
1136
+ evaluation.ruleIndex,
1137
+ typeof evaluation.unwrappedValue,
1138
+ evaluation.reportableValue ?? evaluation.unwrappedValue,
1139
+ evaluation.weightedValueIndex
1140
+ ]);
1141
+ let countersForKey = this.data.get(key);
1142
+ if (countersForKey === void 0) {
1143
+ countersForKey = /* @__PURE__ */ new Map();
1144
+ this.data.set(key, countersForKey);
1145
+ }
1146
+ const currentCount = countersForKey.get(counter) ?? 0;
1147
+ countersForKey.set(counter, currentCount + 1);
1148
+ }
1149
+ /**
1150
+ * Drain collected summaries into a TelemetryEvent, or return undefined if empty.
1151
+ */
1152
+ drain() {
1153
+ if (this.data.size === 0) return void 0;
1154
+ const summaries = [];
1155
+ this.data.forEach((rawCounters, keyJSON) => {
1156
+ const [configKey, configType] = JSON.parse(keyJSON);
1157
+ const counters = [];
1158
+ rawCounters.forEach((count, counterJSON) => {
1159
+ const [configId, ruleIndex, valueType, value, weightedValueIndex] = JSON.parse(counterJSON);
1160
+ const counter = {
1161
+ configId,
1162
+ conditionalValueIndex: ruleIndex,
1163
+ configRowIndex: 0,
1164
+ selectedValue: { [valueType]: value },
1165
+ count
1166
+ };
1167
+ if (weightedValueIndex !== void 0 && weightedValueIndex >= 0) {
1168
+ counter.weightedValueIndex = weightedValueIndex;
1169
+ }
1170
+ counters.push(counter);
1171
+ });
1172
+ summaries.push({
1173
+ key: configKey,
1174
+ type: configType,
1175
+ counters
1176
+ });
1177
+ });
1178
+ const event = {
1179
+ summaries: {
1180
+ start: this.startAt ?? Date.now(),
1181
+ end: Date.now(),
1182
+ summaries
1183
+ }
1184
+ };
1185
+ this.data.clear();
1186
+ this.startAt = void 0;
1187
+ return event;
1188
+ }
1189
+ };
1190
+
1191
+ // src/telemetry/contextShapes.ts
1192
+ var ContextShapeCollector = class {
1193
+ enabled;
1194
+ data = /* @__PURE__ */ new Map();
1195
+ maxDataSize;
1196
+ constructor(contextUploadMode, maxDataSize = 1e4) {
1197
+ this.enabled = contextUploadMode !== "none";
1198
+ this.maxDataSize = maxDataSize;
1199
+ }
1200
+ isEnabled() {
1201
+ return this.enabled;
1202
+ }
1203
+ push(contexts) {
1204
+ if (!this.enabled) return;
1205
+ for (const [name, ctx] of Object.entries(contexts)) {
1206
+ for (const [key, value] of Object.entries(ctx)) {
1207
+ let shape = this.data.get(name);
1208
+ if (shape === void 0 && this.data.size >= this.maxDataSize) {
1209
+ continue;
1210
+ }
1211
+ shape = shape ?? {};
1212
+ if (shape[key] === void 0) {
1213
+ shape[key] = fieldTypeForValue(value);
1214
+ this.data.set(name, shape);
1215
+ }
1216
+ }
1217
+ }
1218
+ }
1219
+ /**
1220
+ * Drain collected shapes into a TelemetryEvent, or return undefined if empty.
1221
+ */
1222
+ drain() {
1223
+ if (this.data.size === 0) return void 0;
1224
+ const shapes = [];
1225
+ this.data.forEach((shape, name) => {
1226
+ shapes.push({ name, fieldTypes: shape });
1227
+ });
1228
+ this.data.clear();
1229
+ return {
1230
+ contextShapes: { shapes }
1231
+ };
1232
+ }
1233
+ };
1234
+ function fieldTypeForValue(value) {
1235
+ if (Number.isInteger(value)) return 1;
1236
+ if (typeof value === "number") return 4;
1237
+ if (typeof value === "boolean") return 5;
1238
+ if (Array.isArray(value)) return 10;
1239
+ return 2;
1240
+ }
1241
+
1242
+ // src/telemetry/exampleContexts.ts
1243
+ var ExampleContextCollector = class {
1244
+ enabled;
1245
+ data = [];
1246
+ seen = /* @__PURE__ */ new Map();
1247
+ maxDataSize;
1248
+ rateLimitMs;
1249
+ constructor(contextUploadMode, maxDataSize = 1e4, rateLimitMs = 60 * 60 * 1e3) {
1250
+ this.enabled = contextUploadMode === "periodic_example";
1251
+ this.maxDataSize = maxDataSize;
1252
+ this.rateLimitMs = rateLimitMs;
1253
+ }
1254
+ isEnabled() {
1255
+ return this.enabled;
1256
+ }
1257
+ push(contexts) {
1258
+ if (!this.enabled) return;
1259
+ if (this.data.length >= this.maxDataSize) return;
1260
+ const key = this.groupedKey(contexts);
1261
+ if (key.length === 0) return;
1262
+ const lastSeen = this.seen.get(key);
1263
+ if (lastSeen !== void 0 && Date.now() - lastSeen < this.rateLimitMs) {
1264
+ return;
1265
+ }
1266
+ this.data.push([Date.now(), contexts]);
1267
+ this.seen.set(key, Date.now());
1268
+ }
1269
+ /**
1270
+ * Drain collected examples into a TelemetryEvent, or return undefined if empty.
1271
+ */
1272
+ drain() {
1273
+ if (this.data.length === 0) return void 0;
1274
+ const examples = this.data.map(([timestamp, contexts]) => {
1275
+ const contextsList = Object.entries(contexts).map(([type, ctx]) => {
1276
+ const values = {};
1277
+ for (const [key, value] of Object.entries(ctx)) {
1278
+ values[key] = value;
1279
+ }
1280
+ return { type, values };
1281
+ });
1282
+ return {
1283
+ timestamp,
1284
+ contextSet: { contexts: contextsList }
1285
+ };
1286
+ });
1287
+ this.data.length = 0;
1288
+ this.pruneCache();
1289
+ return {
1290
+ exampleContexts: { examples }
1291
+ };
1292
+ }
1293
+ groupedKey(contexts) {
1294
+ return Object.values(contexts).map((ctx) => {
1295
+ const key = ctx["key"] ?? ctx["trackingId"];
1296
+ return typeof key === "string" ? key : JSON.stringify(key);
1297
+ }).filter((str) => str !== void 0 && str !== null && String(str).length > 0).sort().join("|");
1298
+ }
1299
+ pruneCache() {
1300
+ const now = Date.now();
1301
+ for (const [key, timestamp] of this.seen.entries()) {
1302
+ if (now - timestamp > this.rateLimitMs) {
1303
+ this.seen.delete(key);
1304
+ }
1305
+ }
1306
+ }
1307
+ };
1308
+
1309
+ // src/telemetry/reporter.ts
1310
+ var TelemetryReporter = class {
1311
+ transport;
1312
+ instanceHash;
1313
+ evaluationSummaries;
1314
+ contextShapes;
1315
+ exampleContexts;
1316
+ timer;
1317
+ initialDelay;
1318
+ maxDelay;
1319
+ currentDelay;
1320
+ stopped = false;
1321
+ constructor(args) {
1322
+ this.transport = args.transport;
1323
+ this.instanceHash = args.instanceHash;
1324
+ this.evaluationSummaries = args.evaluationSummaries;
1325
+ this.contextShapes = args.contextShapes;
1326
+ this.exampleContexts = args.exampleContexts;
1327
+ this.initialDelay = args.initialDelay ?? 8e3;
1328
+ this.maxDelay = args.maxDelay ?? 6e5;
1329
+ this.currentDelay = this.initialDelay;
1330
+ }
1331
+ /**
1332
+ * Start the periodic telemetry reporting loop.
1333
+ */
1334
+ start() {
1335
+ if (this.stopped) return;
1336
+ this.scheduleNext();
1337
+ }
1338
+ /**
1339
+ * Stop telemetry reporting.
1340
+ */
1341
+ stop() {
1342
+ this.stopped = true;
1343
+ if (this.timer !== void 0) {
1344
+ clearTimeout(this.timer);
1345
+ this.timer = void 0;
1346
+ }
1347
+ }
1348
+ scheduleNext() {
1349
+ if (this.stopped) return;
1350
+ this.timer = setTimeout(async () => {
1351
+ try {
1352
+ await this.sync();
1353
+ this.currentDelay = this.initialDelay;
1354
+ } catch (err) {
1355
+ console.warn("[quonfig] Telemetry sync error:", err);
1356
+ this.currentDelay = Math.min(this.currentDelay * 1.5, this.maxDelay);
1357
+ } finally {
1358
+ this.scheduleNext();
1359
+ }
1360
+ }, this.currentDelay);
1361
+ if (this.timer && typeof this.timer === "object" && "unref" in this.timer) {
1362
+ this.timer.unref();
1363
+ }
1364
+ }
1365
+ async sync() {
1366
+ const events = [];
1367
+ const summaryEvent = this.evaluationSummaries.drain();
1368
+ if (summaryEvent) events.push(summaryEvent);
1369
+ const shapesEvent = this.contextShapes.drain();
1370
+ if (shapesEvent) events.push(shapesEvent);
1371
+ const examplesEvent = this.exampleContexts.drain();
1372
+ if (examplesEvent) events.push(examplesEvent);
1373
+ if (events.length === 0) return;
1374
+ const payload = {
1375
+ instanceHash: this.instanceHash,
1376
+ events
1377
+ };
1378
+ await this.transport.postTelemetry(payload);
1379
+ }
1380
+ };
1381
+
1382
+ // src/quonfig.ts
1383
+ var DEFAULT_API_URL = "https://api.quonfig.com";
1384
+ var DEFAULT_POLL_INTERVAL = 6e4;
1385
+ var DEFAULT_INIT_TIMEOUT = 1e4;
1386
+ var DEFAULT_LOG_LEVEL = 5;
1387
+ var BoundQuonfig = class _BoundQuonfig {
1388
+ client;
1389
+ boundContexts;
1390
+ constructor(client, contexts) {
1391
+ this.client = client;
1392
+ this.boundContexts = contexts;
1393
+ }
1394
+ get(key, contexts, defaultValue) {
1395
+ return this.client.get(key, mergeContexts(this.boundContexts, contexts), defaultValue);
1396
+ }
1397
+ getString(key, contexts) {
1398
+ return this.client.getString(key, mergeContexts(this.boundContexts, contexts));
1399
+ }
1400
+ getNumber(key, contexts) {
1401
+ return this.client.getNumber(key, mergeContexts(this.boundContexts, contexts));
1402
+ }
1403
+ getBool(key, contexts) {
1404
+ return this.client.getBool(key, mergeContexts(this.boundContexts, contexts));
1405
+ }
1406
+ getStringList(key, contexts) {
1407
+ return this.client.getStringList(key, mergeContexts(this.boundContexts, contexts));
1408
+ }
1409
+ getDuration(key, contexts) {
1410
+ return this.client.getDuration(key, mergeContexts(this.boundContexts, contexts));
1411
+ }
1412
+ getJSON(key, contexts) {
1413
+ return this.client.getJSON(key, mergeContexts(this.boundContexts, contexts));
1414
+ }
1415
+ isFeatureEnabled(key, contexts) {
1416
+ return this.client.isFeatureEnabled(key, mergeContexts(this.boundContexts, contexts));
1417
+ }
1418
+ shouldLog(args) {
1419
+ return this.client.shouldLog({
1420
+ ...args,
1421
+ contexts: mergeContexts(this.boundContexts, args.contexts)
1422
+ });
1423
+ }
1424
+ keys() {
1425
+ return this.client.keys();
1426
+ }
1427
+ inContext(contexts) {
1428
+ return new _BoundQuonfig(this.client, mergeContexts(this.boundContexts, contexts));
1429
+ }
1430
+ };
1431
+ var Quonfig = class {
1432
+ sdkKey;
1433
+ apiUrl;
1434
+ telemetryUrl;
1435
+ enableSSE;
1436
+ enablePolling;
1437
+ pollInterval;
1438
+ namespace;
1439
+ onNoDefault;
1440
+ globalContext;
1441
+ initTimeout;
1442
+ datafile;
1443
+ store;
1444
+ evaluator;
1445
+ resolver;
1446
+ transport;
1447
+ sseConnection;
1448
+ pollTimer;
1449
+ telemetryReporter;
1450
+ instanceHash;
1451
+ environmentId = "";
1452
+ initialized = false;
1453
+ // Telemetry collectors
1454
+ evaluationSummaries;
1455
+ contextShapes;
1456
+ exampleContexts;
1457
+ constructor(options) {
1458
+ this.sdkKey = options.sdkKey;
1459
+ this.apiUrl = (options.apiUrl ?? DEFAULT_API_URL).replace(/\/$/, "");
1460
+ this.telemetryUrl = options.telemetryUrl;
1461
+ this.enableSSE = options.enableSSE ?? true;
1462
+ this.enablePolling = options.enablePolling ?? false;
1463
+ this.pollInterval = options.pollInterval ?? DEFAULT_POLL_INTERVAL;
1464
+ this.namespace = options.namespace;
1465
+ this.onNoDefault = options.onNoDefault ?? "error";
1466
+ this.globalContext = options.globalContext;
1467
+ this.initTimeout = options.initTimeout ?? DEFAULT_INIT_TIMEOUT;
1468
+ this.datafile = options.datafile;
1469
+ this.instanceHash = (0, import_crypto3.randomUUID)();
1470
+ this.store = new ConfigStore();
1471
+ this.evaluator = new Evaluator(this.store);
1472
+ this.resolver = new Resolver(this.store, this.evaluator);
1473
+ this.transport = new Transport(this.apiUrl, this.sdkKey, this.telemetryUrl);
1474
+ const contextUploadMode = options.contextUploadMode ?? "periodic_example";
1475
+ this.evaluationSummaries = new EvaluationSummaryCollector(
1476
+ options.collectEvaluationSummaries ?? true
1477
+ );
1478
+ this.contextShapes = new ContextShapeCollector(contextUploadMode);
1479
+ this.exampleContexts = new ExampleContextCollector(contextUploadMode);
1480
+ }
1481
+ /**
1482
+ * Initialize the SDK. Downloads configs from the API (or loads from datafile)
1483
+ * and starts background update mechanisms (SSE/polling).
1484
+ *
1485
+ * Must be called before using any get* methods.
1486
+ */
1487
+ async init() {
1488
+ if (this.datafile) {
1489
+ this.loadDatafile();
1490
+ this.initialized = true;
1491
+ return;
1492
+ }
1493
+ const fetchPromise = this.fetchAndInstall();
1494
+ const timeoutPromise = new Promise((_, reject) => {
1495
+ setTimeout(() => reject(new Error("Initialization timed out")), this.initTimeout);
1496
+ });
1497
+ try {
1498
+ await Promise.race([fetchPromise, timeoutPromise]);
1499
+ } catch (err) {
1500
+ console.warn("[quonfig] Initialization failed:", err);
1501
+ throw err;
1502
+ }
1503
+ this.initialized = true;
1504
+ if (this.enableSSE) {
1505
+ this.startSSE();
1506
+ }
1507
+ if (this.enablePolling) {
1508
+ this.startPolling();
1509
+ }
1510
+ this.startTelemetry();
1511
+ }
1512
+ /**
1513
+ * Get a config value by key. Evaluates rules against the provided context.
1514
+ */
1515
+ get(key, contexts, defaultValue) {
1516
+ this.requireInitialized();
1517
+ const mergedContexts = mergeContexts(this.globalContext, contexts);
1518
+ const config = this.store.get(key);
1519
+ if (config === void 0) {
1520
+ return this.handleNoDefault(key, defaultValue);
1521
+ }
1522
+ this.contextShapes.push(mergedContexts);
1523
+ this.exampleContexts.push(mergedContexts);
1524
+ const match = this.evaluator.evaluateConfig(
1525
+ config,
1526
+ this.environmentId,
1527
+ mergedContexts
1528
+ );
1529
+ if (!match.isMatch || match.value === void 0) {
1530
+ return this.handleNoDefault(key, defaultValue);
1531
+ }
1532
+ const { resolved, reportableValue } = this.resolver.resolveValue(
1533
+ match.value,
1534
+ config.key,
1535
+ config.valueType,
1536
+ this.environmentId,
1537
+ mergedContexts
1538
+ );
1539
+ const unwrapped = this.resolver.unwrapValue(resolved);
1540
+ const evaluation = {
1541
+ configId: config.id,
1542
+ configKey: config.key,
1543
+ configType: config.type,
1544
+ unwrappedValue: unwrapped,
1545
+ reportableValue,
1546
+ ruleIndex: match.ruleIndex,
1547
+ weightedValueIndex: match.weightedValueIndex >= 0 ? match.weightedValueIndex : void 0
1548
+ };
1549
+ this.evaluationSummaries.push(evaluation);
1550
+ return unwrapped;
1551
+ }
1552
+ /**
1553
+ * Get a string config value.
1554
+ */
1555
+ getString(key, contexts) {
1556
+ const value = this.get(key, contexts, void 0);
1557
+ if (value === void 0) return void 0;
1558
+ return String(value);
1559
+ }
1560
+ /**
1561
+ * Get a number config value.
1562
+ */
1563
+ getNumber(key, contexts) {
1564
+ const value = this.get(key, contexts, void 0);
1565
+ if (value === void 0) return void 0;
1566
+ return typeof value === "number" ? value : Number(value);
1567
+ }
1568
+ /**
1569
+ * Get a boolean config value.
1570
+ */
1571
+ getBool(key, contexts) {
1572
+ const value = this.get(key, contexts, void 0);
1573
+ if (value === void 0) return void 0;
1574
+ return !!value;
1575
+ }
1576
+ /**
1577
+ * Get a string list config value.
1578
+ */
1579
+ getStringList(key, contexts) {
1580
+ const value = this.get(key, contexts, void 0);
1581
+ if (value === void 0) return void 0;
1582
+ if (Array.isArray(value)) return value.map((v) => String(v));
1583
+ return void 0;
1584
+ }
1585
+ /**
1586
+ * Get a duration config value in milliseconds.
1587
+ */
1588
+ getDuration(key, contexts) {
1589
+ const value = this.get(key, contexts, void 0);
1590
+ if (value === void 0) return void 0;
1591
+ if (typeof value === "number") return value;
1592
+ if (typeof value === "string") return durationToMilliseconds(value);
1593
+ return void 0;
1594
+ }
1595
+ /**
1596
+ * Get a JSON config value (parsed).
1597
+ */
1598
+ getJSON(key, contexts) {
1599
+ const value = this.get(key, contexts, void 0);
1600
+ if (value === void 0) return void 0;
1601
+ return value;
1602
+ }
1603
+ /**
1604
+ * Check if a feature flag is enabled.
1605
+ * Returns false if the key is not found or the value is not a boolean.
1606
+ */
1607
+ isFeatureEnabled(key, contexts) {
1608
+ const value = this.get(key, contexts, void 0);
1609
+ if (typeof value === "boolean") return value;
1610
+ if (value === "true") return true;
1611
+ if (value === "false") return false;
1612
+ return false;
1613
+ }
1614
+ /**
1615
+ * Check if a log message should be logged at the given level.
1616
+ */
1617
+ shouldLog(args) {
1618
+ const desiredLevelNum = parseLevel(args.desiredLevel);
1619
+ if (desiredLevelNum === void 0) {
1620
+ console.warn(`[quonfig] Invalid desiredLevel "${args.desiredLevel}". Returning true.`);
1621
+ return true;
1622
+ }
1623
+ const defaultLevelNum = parseLevel(args.defaultLevel) ?? DEFAULT_LOG_LEVEL;
1624
+ return shouldLog({
1625
+ loggerName: args.loggerName,
1626
+ desiredLevel: desiredLevelNum,
1627
+ defaultLevel: defaultLevelNum,
1628
+ getConfig: (logKey) => {
1629
+ try {
1630
+ return this.get(logKey, args.contexts, void 0);
1631
+ } catch {
1632
+ return void 0;
1633
+ }
1634
+ }
1635
+ });
1636
+ }
1637
+ /**
1638
+ * Get all config keys currently in the store.
1639
+ */
1640
+ keys() {
1641
+ this.requireInitialized();
1642
+ return this.store.keys();
1643
+ }
1644
+ /**
1645
+ * Get the raw ConfigResponse for a key (for advanced usage / CLI tooling).
1646
+ */
1647
+ rawConfig(key) {
1648
+ this.requireInitialized();
1649
+ return this.store.get(key);
1650
+ }
1651
+ /**
1652
+ * Create a BoundQuonfig with the given context baked in.
1653
+ */
1654
+ inContext(contexts) {
1655
+ return new BoundQuonfig(this, mergeContexts(this.globalContext, contexts));
1656
+ }
1657
+ /**
1658
+ * Close the SDK. Stops SSE, polling, and telemetry.
1659
+ */
1660
+ close() {
1661
+ if (this.sseConnection) {
1662
+ this.sseConnection.close();
1663
+ this.sseConnection = void 0;
1664
+ }
1665
+ if (this.pollTimer) {
1666
+ clearTimeout(this.pollTimer);
1667
+ this.pollTimer = void 0;
1668
+ }
1669
+ if (this.telemetryReporter) {
1670
+ this.telemetryReporter.stop();
1671
+ this.telemetryReporter = void 0;
1672
+ }
1673
+ }
1674
+ // ---- Private methods ----
1675
+ requireInitialized() {
1676
+ if (!this.initialized) {
1677
+ throw new Error("[quonfig] Not initialized. Call init() first.");
1678
+ }
1679
+ }
1680
+ handleNoDefault(key, defaultValue) {
1681
+ if (defaultValue !== void 0) {
1682
+ return defaultValue;
1683
+ }
1684
+ switch (this.onNoDefault) {
1685
+ case "error":
1686
+ throw new Error(`No value found for key "${key}"`);
1687
+ case "warn":
1688
+ console.warn(`[quonfig] No value found for key "${key}"`);
1689
+ return void 0;
1690
+ case "ignore":
1691
+ return void 0;
1692
+ }
1693
+ }
1694
+ loadDatafile() {
1695
+ let data;
1696
+ if (typeof this.datafile === "string") {
1697
+ const raw = (0, import_fs.readFileSync)(this.datafile, "utf-8");
1698
+ data = JSON.parse(raw);
1699
+ } else if (typeof this.datafile === "object") {
1700
+ data = this.datafile;
1701
+ } else {
1702
+ throw new Error("Invalid datafile option");
1703
+ }
1704
+ this.store.update(data);
1705
+ this.environmentId = data.meta.environment;
1706
+ }
1707
+ async fetchAndInstall() {
1708
+ const result = await this.transport.fetchConfigs();
1709
+ if (result.notChanged) {
1710
+ return;
1711
+ }
1712
+ if (result.envelope) {
1713
+ this.store.update(result.envelope);
1714
+ this.environmentId = result.envelope.meta.environment;
1715
+ }
1716
+ }
1717
+ startSSE() {
1718
+ this.sseConnection = new SSEConnection(this.transport);
1719
+ this.sseConnection.start((envelope) => {
1720
+ this.store.update(envelope);
1721
+ this.environmentId = envelope.meta.environment;
1722
+ });
1723
+ }
1724
+ startPolling() {
1725
+ const poll = () => {
1726
+ this.fetchAndInstall().catch((err) => {
1727
+ console.warn("[quonfig] Polling error:", err);
1728
+ }).finally(() => {
1729
+ this.pollTimer = setTimeout(poll, this.pollInterval);
1730
+ if (this.pollTimer && typeof this.pollTimer === "object" && "unref" in this.pollTimer) {
1731
+ this.pollTimer.unref();
1732
+ }
1733
+ });
1734
+ };
1735
+ this.pollTimer = setTimeout(poll, this.pollInterval);
1736
+ if (this.pollTimer && typeof this.pollTimer === "object" && "unref" in this.pollTimer) {
1737
+ this.pollTimer.unref();
1738
+ }
1739
+ }
1740
+ startTelemetry() {
1741
+ const anyEnabled = this.evaluationSummaries.isEnabled() || this.contextShapes.isEnabled() || this.exampleContexts.isEnabled();
1742
+ if (!anyEnabled) return;
1743
+ this.telemetryReporter = new TelemetryReporter({
1744
+ transport: this.transport,
1745
+ instanceHash: this.instanceHash,
1746
+ evaluationSummaries: this.evaluationSummaries,
1747
+ contextShapes: this.contextShapes,
1748
+ exampleContexts: this.exampleContexts
1749
+ });
1750
+ this.telemetryReporter.start();
1751
+ }
1752
+ };
1753
+
1754
+ // src/types.ts
1755
+ var ConfigType = {
1756
+ FeatureFlag: "feature_flag",
1757
+ Config: "config",
1758
+ Segment: "segment",
1759
+ LogLevel: "log_level",
1760
+ Schema: "schema"
1761
+ };
1762
+ var ProvidedSource = {
1763
+ EnvVar: "ENV_VAR"
1764
+ };
1765
+
1766
+ // src/cli-compat.ts
1767
+ var Client = class {
1768
+ jwt;
1769
+ sdkKey;
1770
+ apiUrl;
1771
+ clientIdentifier;
1772
+ log;
1773
+ constructor(options) {
1774
+ this.jwt = options.jwt;
1775
+ this.sdkKey = options.sdkKey;
1776
+ this.apiUrl = options.apiUrl.replace(/\/$/, "");
1777
+ this.clientIdentifier = options.clientIdentifier;
1778
+ this.log = options.log ?? (() => {
1779
+ });
1780
+ }
1781
+ headers() {
1782
+ const h = {
1783
+ "Content-Type": "application/json",
1784
+ "X-Client-Version": this.clientIdentifier
1785
+ };
1786
+ if (this.jwt) {
1787
+ h["Authorization"] = `Bearer ${this.jwt}`;
1788
+ } else if (this.sdkKey) {
1789
+ h["Authorization"] = `Basic ${Buffer.from(this.sdkKey).toString("base64")}`;
1790
+ }
1791
+ return h;
1792
+ }
1793
+ async get(path) {
1794
+ const url = `${this.apiUrl}${path}`;
1795
+ this.log("ApiClient", `GET ${url}`);
1796
+ return fetch(url, { method: "GET", headers: this.headers() });
1797
+ }
1798
+ async post(path, payload) {
1799
+ const url = `${this.apiUrl}${path}`;
1800
+ this.log("ApiClient", `POST ${url}`);
1801
+ return fetch(url, {
1802
+ method: "POST",
1803
+ headers: this.headers(),
1804
+ body: JSON.stringify(payload)
1805
+ });
1806
+ }
1807
+ async put(path, payload) {
1808
+ const url = `${this.apiUrl}${path}`;
1809
+ this.log("ApiClient", `PUT ${url}`);
1810
+ return fetch(url, {
1811
+ method: "PUT",
1812
+ headers: this.headers(),
1813
+ body: JSON.stringify(payload)
1814
+ });
1815
+ }
1816
+ };
1817
+ function getProjectEnvFromSdkKey(sdkKey) {
1818
+ const parts = sdkKey.split("-");
1819
+ if (parts.length < 2) {
1820
+ throw new Error(`Invalid SDK key format: cannot extract projectEnvId`);
1821
+ }
1822
+ return {
1823
+ id: parts.slice(0, 2).join("-"),
1824
+ projectId: Number.parseInt(parts[0], 10) || 0
1825
+ };
1826
+ }
1827
+ var ConfigValueType = /* @__PURE__ */ ((ConfigValueType2) => {
1828
+ ConfigValueType2[ConfigValueType2["NotSetValue"] = 0] = "NotSetValue";
1829
+ ConfigValueType2[ConfigValueType2["Int"] = 1] = "Int";
1830
+ ConfigValueType2[ConfigValueType2["String"] = 2] = "String";
1831
+ ConfigValueType2[ConfigValueType2["Bytes"] = 3] = "Bytes";
1832
+ ConfigValueType2[ConfigValueType2["Double"] = 4] = "Double";
1833
+ ConfigValueType2[ConfigValueType2["Bool"] = 5] = "Bool";
1834
+ ConfigValueType2[ConfigValueType2["LimitDefinition"] = 7] = "LimitDefinition";
1835
+ ConfigValueType2[ConfigValueType2["LogLevel"] = 8] = "LogLevel";
1836
+ ConfigValueType2[ConfigValueType2["StringList"] = 9] = "StringList";
1837
+ ConfigValueType2[ConfigValueType2["IntRange"] = 10] = "IntRange";
1838
+ ConfigValueType2[ConfigValueType2["Duration"] = 11] = "Duration";
1839
+ ConfigValueType2[ConfigValueType2["Json"] = 12] = "Json";
1840
+ return ConfigValueType2;
1841
+ })(ConfigValueType || {});
1842
+ function valueTypeStringForConfig(config) {
1843
+ return config.valueType;
1844
+ }
1845
+
1846
+ // src/index.ts
1847
+ var encryption = {
1848
+ encrypt,
1849
+ generateNewHexKey
1850
+ };
1851
+ // Annotate the CommonJS export names for ESM import in node:
1852
+ 0 && (module.exports = {
1853
+ BoundQuonfig,
1854
+ Client,
1855
+ ConfigStore,
1856
+ ConfigType,
1857
+ ConfigValueType,
1858
+ ContextShapeCollector,
1859
+ EvaluationSummaryCollector,
1860
+ Evaluator,
1861
+ ExampleContextCollector,
1862
+ LOG_LEVEL_PREFIX,
1863
+ OP_ALWAYS_TRUE,
1864
+ OP_HIERARCHICAL_MATCH,
1865
+ OP_IN_INT_RANGE,
1866
+ OP_IN_SEG,
1867
+ OP_NOT_IN_SEG,
1868
+ OP_NOT_SET,
1869
+ OP_PROP_AFTER,
1870
+ OP_PROP_BEFORE,
1871
+ OP_PROP_CONTAINS_ONE_OF,
1872
+ OP_PROP_DOES_NOT_CONTAIN_ONE_OF,
1873
+ OP_PROP_DOES_NOT_END_WITH_ONE_OF,
1874
+ OP_PROP_DOES_NOT_MATCH,
1875
+ OP_PROP_DOES_NOT_START_WITH_ONE_OF,
1876
+ OP_PROP_ENDS_WITH_ONE_OF,
1877
+ OP_PROP_GREATER_THAN,
1878
+ OP_PROP_GREATER_THAN_OR_EQUAL,
1879
+ OP_PROP_IS_NOT_ONE_OF,
1880
+ OP_PROP_IS_ONE_OF,
1881
+ OP_PROP_LESS_THAN,
1882
+ OP_PROP_LESS_THAN_OR_EQUAL,
1883
+ OP_PROP_MATCHES,
1884
+ OP_PROP_SEMVER_EQUAL,
1885
+ OP_PROP_SEMVER_GREATER_THAN,
1886
+ OP_PROP_SEMVER_LESS_THAN,
1887
+ OP_PROP_STARTS_WITH_ONE_OF,
1888
+ ProvidedSource,
1889
+ Quonfig,
1890
+ Resolver,
1891
+ TelemetryReporter,
1892
+ Transport,
1893
+ WeightedValueResolver,
1894
+ compareSemver,
1895
+ contextLookup,
1896
+ decrypt,
1897
+ durationToMilliseconds,
1898
+ encrypt,
1899
+ encryption,
1900
+ evaluateCriterion,
1901
+ generateNewHexKey,
1902
+ getContextValue,
1903
+ getProjectEnvFromSdkKey,
1904
+ hashZeroToOne,
1905
+ mergeContexts,
1906
+ parseLevel,
1907
+ parseSemver,
1908
+ shouldLog,
1909
+ valueTypeStringForConfig,
1910
+ wordLevelToNumber
1911
+ });
1912
+ //# sourceMappingURL=index.cjs.map