@configdirector/nuxt-sdk 1.7.0 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -0
- package/dist/module.d.mts +26 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +12 -7
- package/dist/runtime/app/composables/useConfigDirectorClient.d.mts +1 -1
- package/dist/runtime/app/composables/useConfigDirectorClient.d.ts +1 -1
- package/dist/runtime/app/composables/useConfigDirectorClientHooks.d.mts +1 -1
- package/dist/runtime/app/composables/useConfigDirectorClientHooks.d.ts +1 -1
- package/dist/runtime/app/composables/useConfigDirectorContext.mjs +8 -3
- package/dist/runtime/nitro/plugin.d.mts +1 -1
- package/dist/runtime/nitro/plugin.d.ts +1 -1
- package/dist/runtime/nitro/plugin.mjs +219 -80
- package/dist/runtime/plugin.client.mjs +186 -84
- package/dist/runtime/plugin.server.mjs +8 -3
- package/dist/runtime/server/composables/useConfigDirectorClient.d.mts +1 -1
- package/dist/runtime/server/composables/useConfigDirectorClient.d.ts +1 -1
- package/dist/runtime/server/composables/useConfigDirectorServerHooks.d.mts +1 -1
- package/dist/runtime/server/composables/useConfigDirectorServerHooks.d.ts +1 -1
- package/dist/runtime/{types-C54FjC1D.d.mts → types-Bgx2-3dY.d.mts} +1 -0
- package/dist/runtime/{types-BPvGEzJY.d.mts → types-CvmjergQ.d.mts} +6 -2
- package/dist/testing.d.mts +352 -0
- package/dist/testing.mjs +1645 -0
- package/package.json +11 -4
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import EventEmitter, { EventEmitter as EventEmitter$1 } from "node:events";
|
|
2
2
|
import { createHash, randomUUID } from "node:crypto";
|
|
3
|
-
import { findByPointer } from "@jsonjoy.com/json-pointer";
|
|
4
3
|
import * as semver from "semver";
|
|
5
4
|
import { rapidhash } from "rapidhash-js";
|
|
6
5
|
import { defineNitroPlugin, useRuntimeConfig } from "nitropack/runtime";
|
|
@@ -413,6 +412,7 @@ var StreamingTransport = class {
|
|
|
413
412
|
shouldReconnect: (state) => {
|
|
414
413
|
const reconnect = !this.isStatusFatal(state.status);
|
|
415
414
|
if (!reconnect) {
|
|
415
|
+
this.stopHeartbeat();
|
|
416
416
|
const error = this.prepareFatalError(state.status, state.error);
|
|
417
417
|
this.eventEmitter.emit("connectionError", error);
|
|
418
418
|
reject(error);
|
|
@@ -433,9 +433,14 @@ var StreamingTransport = class {
|
|
|
433
433
|
this.eventSource.connect();
|
|
434
434
|
});
|
|
435
435
|
this.heartbeatTimer = setInterval(() => this.sendHeartbeat(), HEARTBEAT_INTERVAL_MS);
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
436
|
+
let connectTimer;
|
|
437
|
+
try {
|
|
438
|
+
return await Promise.race([eventSourcePromise, new Promise((resolve) => {
|
|
439
|
+
connectTimer = setTimeout(() => resolve(this), timeout);
|
|
440
|
+
})]);
|
|
441
|
+
} finally {
|
|
442
|
+
clearTimeout(connectTimer);
|
|
443
|
+
}
|
|
439
444
|
}
|
|
440
445
|
get sessionId() {
|
|
441
446
|
return this._sessionId;
|
|
@@ -491,9 +496,12 @@ var StreamingTransport = class {
|
|
|
491
496
|
clear() {
|
|
492
497
|
this.eventEmitter.removeAllListeners();
|
|
493
498
|
}
|
|
494
|
-
|
|
499
|
+
stopHeartbeat() {
|
|
495
500
|
clearInterval(this.heartbeatTimer);
|
|
496
501
|
this.heartbeatTimer = void 0;
|
|
502
|
+
}
|
|
503
|
+
close() {
|
|
504
|
+
this.stopHeartbeat();
|
|
497
505
|
this.eventSource?.close();
|
|
498
506
|
}
|
|
499
507
|
dispose() {
|
|
@@ -506,7 +514,6 @@ var StreamingTransport = class {
|
|
|
506
514
|
var AbstractPollingTransport = class {
|
|
507
515
|
fatalError = false;
|
|
508
516
|
pollingInterval;
|
|
509
|
-
pollingIntervalSeconds = 60;
|
|
510
517
|
clearPollingInterval() {
|
|
511
518
|
clearInterval(this.pollingInterval);
|
|
512
519
|
}
|
|
@@ -555,6 +562,7 @@ var AbstractPollingTransport = class {
|
|
|
555
562
|
//#endregion
|
|
556
563
|
//#region ../js-server-sdk/src/PollingTransport.ts
|
|
557
564
|
var PollingTransport = class extends AbstractPollingTransport {
|
|
565
|
+
pollingIntervalSeconds;
|
|
558
566
|
logger;
|
|
559
567
|
eventEmitter = new EventEmitter$1();
|
|
560
568
|
url;
|
|
@@ -627,6 +635,31 @@ var PollingTransport = class extends AbstractPollingTransport {
|
|
|
627
635
|
}
|
|
628
636
|
};
|
|
629
637
|
//#endregion
|
|
638
|
+
//#region ../config-evaluator/src/json-pointer.ts
|
|
639
|
+
const ARRAY_INDEX_PATTERN = /^(?:0|[1-9][0-9]*)$/;
|
|
640
|
+
const INVALID_ESCAPE_PATTERN = /~(?![01])/;
|
|
641
|
+
const isSerializedMember = Object.prototype.propertyIsEnumerable;
|
|
642
|
+
const decodeReferenceToken = (token) => {
|
|
643
|
+
if (INVALID_ESCAPE_PATTERN.test(token)) return;
|
|
644
|
+
return token.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
645
|
+
};
|
|
646
|
+
const step = (current, token) => {
|
|
647
|
+
if (Array.isArray(current)) return ARRAY_INDEX_PATTERN.test(token) && isSerializedMember.call(current, token) ? current[Number(token)] : void 0;
|
|
648
|
+
if (typeof current === "object" && current !== null) return isSerializedMember.call(current, token) ? current[token] : void 0;
|
|
649
|
+
};
|
|
650
|
+
const resolveJsonPointer = (pointer, document) => {
|
|
651
|
+
if (pointer === "") return document;
|
|
652
|
+
if (!pointer.startsWith("/")) return;
|
|
653
|
+
let current = document;
|
|
654
|
+
for (const encodedToken of pointer.slice(1).split("/")) {
|
|
655
|
+
const token = decodeReferenceToken(encodedToken);
|
|
656
|
+
if (token === void 0) return;
|
|
657
|
+
current = step(current, token);
|
|
658
|
+
if (current === void 0) return;
|
|
659
|
+
}
|
|
660
|
+
return current;
|
|
661
|
+
};
|
|
662
|
+
//#endregion
|
|
630
663
|
//#region ../config-evaluator/src/text-comparison.ts
|
|
631
664
|
/**
|
|
632
665
|
* Text comparisons.
|
|
@@ -7036,19 +7069,46 @@ var ConditionEvaluator = class {
|
|
|
7036
7069
|
case "appVersion": return orAbsent(context?.metadata?.appVersion);
|
|
7037
7070
|
case "traits":
|
|
7038
7071
|
if (!condition.trait) return ABSENT;
|
|
7039
|
-
return orAbsent(
|
|
7072
|
+
return orAbsent(resolveJsonPointer(condition.trait, context?.context?.traits));
|
|
7040
7073
|
default: return UNKNOWN_ATTRIBUTE;
|
|
7041
7074
|
}
|
|
7042
7075
|
}
|
|
7043
|
-
|
|
7044
|
-
|
|
7045
|
-
|
|
7046
|
-
|
|
7047
|
-
|
|
7076
|
+
};
|
|
7077
|
+
const orAbsent = (value) => value === void 0 || value === null ? ABSENT : value;
|
|
7078
|
+
//#endregion
|
|
7079
|
+
//#region ../config-evaluator/src/SegmentEvaluator.ts
|
|
7080
|
+
var SegmentEvaluator = class {
|
|
7081
|
+
constructor(conditionEvaluator) {
|
|
7082
|
+
this.conditionEvaluator = conditionEvaluator;
|
|
7083
|
+
}
|
|
7084
|
+
explain(condition, segments, context) {
|
|
7085
|
+
const segment = segments?.[condition.segmentId];
|
|
7086
|
+
if (segment === void 0 || !holdsAttributeConditionsOnly(segment)) return {
|
|
7087
|
+
matched: false,
|
|
7088
|
+
segmentFound: false,
|
|
7089
|
+
matchedGroupIndex: void 0
|
|
7090
|
+
};
|
|
7091
|
+
const matchedGroupIndex = this.matchingGroupIndex(segment, context);
|
|
7092
|
+
const member = matchedGroupIndex !== void 0;
|
|
7093
|
+
return {
|
|
7094
|
+
matched: this.operatorHolds(condition, member),
|
|
7095
|
+
segmentFound: true,
|
|
7096
|
+
matchedGroupIndex
|
|
7097
|
+
};
|
|
7098
|
+
}
|
|
7099
|
+
operatorHolds(condition, member) {
|
|
7100
|
+
switch (condition.operator.toLowerCase()) {
|
|
7101
|
+
case "in": return member;
|
|
7102
|
+
case "not in": return !member;
|
|
7103
|
+
default: return false;
|
|
7048
7104
|
}
|
|
7049
7105
|
}
|
|
7106
|
+
matchingGroupIndex(segment, context) {
|
|
7107
|
+
const index = (segment.groups ?? []).findIndex((group) => (group ?? []).every((condition) => this.conditionEvaluator.evaluate(condition, context)));
|
|
7108
|
+
return index === -1 ? void 0 : index;
|
|
7109
|
+
}
|
|
7050
7110
|
};
|
|
7051
|
-
const
|
|
7111
|
+
const holdsAttributeConditionsOnly = (segment) => (segment.groups ?? []).every((group) => (group ?? []).every((condition) => condition.kind === void 0 || condition.kind === "attribute"));
|
|
7052
7112
|
//#endregion
|
|
7053
7113
|
//#region ../config-evaluator/src/percent-hashing.ts
|
|
7054
7114
|
const SEED = 15426857351n;
|
|
@@ -7060,26 +7120,28 @@ const assignPercentage = (data) => {
|
|
|
7060
7120
|
//#region ../config-evaluator/src/ConfigEvaluator.ts
|
|
7061
7121
|
var ConfigEvaluator = class {
|
|
7062
7122
|
conditionEvaluator = new ConditionEvaluator();
|
|
7123
|
+
segmentEvaluator = new SegmentEvaluator(this.conditionEvaluator);
|
|
7063
7124
|
constructor(logger) {
|
|
7064
7125
|
this.logger = logger;
|
|
7065
7126
|
if (!logger || !logger.warn || !logger.error || !logger.info) throw new TypeError("The provided logger is not a valid 'ConfigDirectorLogger'");
|
|
7066
7127
|
this.logger = logger;
|
|
7067
7128
|
}
|
|
7068
|
-
evaluate(config, context) {
|
|
7129
|
+
evaluate(config, context, segments) {
|
|
7069
7130
|
return {
|
|
7070
7131
|
id: config.id,
|
|
7071
7132
|
key: config.key,
|
|
7072
7133
|
type: config.type,
|
|
7073
|
-
value: this.explain(config, context).value
|
|
7134
|
+
value: this.explain(config, context, segments).value
|
|
7074
7135
|
};
|
|
7075
7136
|
}
|
|
7076
7137
|
/**
|
|
7077
7138
|
* Evaluate a config for a context and record how the value was reached: every rule in the
|
|
7078
7139
|
* order it was walked with its outcome, the conditions that were checked with what they
|
|
7079
7140
|
* resolved to, and the share a rollout assigned the context to. `evaluate` is this walk with
|
|
7080
|
-
* only the value kept, so the two cannot disagree.
|
|
7141
|
+
* only the value kept, so the two cannot disagree. `segments` is the payload's segments map,
|
|
7142
|
+
* which a segment condition looks its segment up in; a segment it does not hold never matches.
|
|
7081
7143
|
*/
|
|
7082
|
-
explain(config, context) {
|
|
7144
|
+
explain(config, context, segments) {
|
|
7083
7145
|
const rules = [...config.target?.rules ?? []].sort((a, b) => (a.order ?? Number.MAX_SAFE_INTEGER) - (b.order ?? Number.MAX_SAFE_INTEGER));
|
|
7084
7146
|
const explanations = [];
|
|
7085
7147
|
let served;
|
|
@@ -7093,7 +7155,7 @@ var ConfigEvaluator = class {
|
|
|
7093
7155
|
});
|
|
7094
7156
|
continue;
|
|
7095
7157
|
}
|
|
7096
|
-
const result = this.explainRule(rule, config, context);
|
|
7158
|
+
const result = this.explainRule(rule, config, context, segments);
|
|
7097
7159
|
explanations.push(result.explanation);
|
|
7098
7160
|
if (result.value !== void 0) served = {
|
|
7099
7161
|
ruleId: rule.id,
|
|
@@ -7109,7 +7171,7 @@ var ConfigEvaluator = class {
|
|
|
7109
7171
|
rules: explanations
|
|
7110
7172
|
};
|
|
7111
7173
|
}
|
|
7112
|
-
explainRule(rule, config, context) {
|
|
7174
|
+
explainRule(rule, config, context, segments) {
|
|
7113
7175
|
const explanation = {
|
|
7114
7176
|
ruleId: rule.id,
|
|
7115
7177
|
outcome: "not-matched",
|
|
@@ -7119,7 +7181,7 @@ var ConfigEvaluator = class {
|
|
|
7119
7181
|
try {
|
|
7120
7182
|
let value;
|
|
7121
7183
|
if (rule.type == "percentage") value = this.explainPercentage(rule.percentages ?? [], config, context, explanation);
|
|
7122
|
-
else if (rule.type == "conditional") value = this.explainConditionalRule(rule, config, context, explanation);
|
|
7184
|
+
else if (rule.type == "conditional") value = this.explainConditionalRule(rule, config, context, segments, explanation);
|
|
7123
7185
|
if (value !== void 0) explanation.outcome = "matched";
|
|
7124
7186
|
return {
|
|
7125
7187
|
explanation,
|
|
@@ -7171,7 +7233,7 @@ var ConfigEvaluator = class {
|
|
|
7171
7233
|
};
|
|
7172
7234
|
return selected?.value != null ? selected.value.toString() : void 0;
|
|
7173
7235
|
}
|
|
7174
|
-
explainConditionalRule(rule, config, context, explanation) {
|
|
7236
|
+
explainConditionalRule(rule, config, context, segments, explanation) {
|
|
7175
7237
|
let failed = false;
|
|
7176
7238
|
for (const condition of rule.conditions ?? []) {
|
|
7177
7239
|
if (failed) {
|
|
@@ -7181,19 +7243,35 @@ var ConfigEvaluator = class {
|
|
|
7181
7243
|
});
|
|
7182
7244
|
continue;
|
|
7183
7245
|
}
|
|
7184
|
-
const
|
|
7185
|
-
explanation.conditions.push(
|
|
7186
|
-
|
|
7187
|
-
outcome: check.matched ? "matched" : "not-matched",
|
|
7188
|
-
resolvedValue: check.resolvedValue,
|
|
7189
|
-
resolvedType: check.resolvedType
|
|
7190
|
-
});
|
|
7191
|
-
failed = !check.matched;
|
|
7246
|
+
const checked = this.explainCondition(condition, context, segments);
|
|
7247
|
+
explanation.conditions.push(checked);
|
|
7248
|
+
failed = checked.outcome !== "matched";
|
|
7192
7249
|
}
|
|
7193
7250
|
if (failed) return;
|
|
7194
7251
|
if (rule.target == "value") return rule.value != null ? rule.value.toString() : void 0;
|
|
7195
7252
|
if (rule.target == "percentage") return this.explainPercentage(rule.percentages ?? [], config, context, explanation);
|
|
7196
7253
|
}
|
|
7254
|
+
explainCondition(condition, context, segments) {
|
|
7255
|
+
if (condition.kind === "segment") {
|
|
7256
|
+
const check = this.segmentEvaluator.explain(condition, segments, context);
|
|
7257
|
+
return {
|
|
7258
|
+
conditionId: condition.id,
|
|
7259
|
+
kind: "segment",
|
|
7260
|
+
outcome: check.matched ? "matched" : "not-matched",
|
|
7261
|
+
segmentId: condition.segmentId,
|
|
7262
|
+
segmentFound: check.segmentFound,
|
|
7263
|
+
matchedGroupIndex: check.matchedGroupIndex
|
|
7264
|
+
};
|
|
7265
|
+
}
|
|
7266
|
+
const check = this.conditionEvaluator.explain(condition, context);
|
|
7267
|
+
return {
|
|
7268
|
+
conditionId: condition.id,
|
|
7269
|
+
kind: "attribute",
|
|
7270
|
+
outcome: check.matched ? "matched" : "not-matched",
|
|
7271
|
+
resolvedValue: check.resolvedValue,
|
|
7272
|
+
resolvedType: check.resolvedType
|
|
7273
|
+
};
|
|
7274
|
+
}
|
|
7197
7275
|
};
|
|
7198
7276
|
//#endregion
|
|
7199
7277
|
//#region ../shared/src/value-parser.ts
|
|
@@ -7223,7 +7301,7 @@ const configTypesNotReadableAsString = new Set([
|
|
|
7223
7301
|
const parseConfigValue = (configState, defaultValue) => {
|
|
7224
7302
|
const value = configState.value;
|
|
7225
7303
|
const requestedType = getRequestedType(defaultValue);
|
|
7226
|
-
if (value == null) return {
|
|
7304
|
+
if (value == null || value === "") return {
|
|
7227
7305
|
parsedValue: defaultValue,
|
|
7228
7306
|
parsedValueId: void 0,
|
|
7229
7307
|
requestedType,
|
|
@@ -7418,6 +7496,22 @@ const createDefaultLogger$1 = (level, messageDecorator) => {
|
|
|
7418
7496
|
//#region ../shared/src/url.ts
|
|
7419
7497
|
const defaultUrlFactory = (input, base) => new URL(input, base?.toString());
|
|
7420
7498
|
//#endregion
|
|
7499
|
+
//#region ../shared/src/transport/pollingInterval.ts
|
|
7500
|
+
const resolvePollingInterval = (configuredSeconds, defaults) => {
|
|
7501
|
+
if (configuredSeconds === void 0) return {
|
|
7502
|
+
seconds: defaults.defaultSeconds,
|
|
7503
|
+
warning: void 0
|
|
7504
|
+
};
|
|
7505
|
+
if (configuredSeconds >= defaults.minimumSeconds) return {
|
|
7506
|
+
seconds: configuredSeconds,
|
|
7507
|
+
warning: void 0
|
|
7508
|
+
};
|
|
7509
|
+
return {
|
|
7510
|
+
seconds: defaults.minimumSeconds,
|
|
7511
|
+
warning: `pollingInterval of ${configuredSeconds} seconds is below the minimum of ${defaults.minimumSeconds} seconds. Using ${defaults.minimumSeconds} seconds.`
|
|
7512
|
+
};
|
|
7513
|
+
};
|
|
7514
|
+
//#endregion
|
|
7421
7515
|
//#region ../shared/src/telemetry/utils.ts
|
|
7422
7516
|
const CONFIG_VALUE_MAX_LENGTH = 500;
|
|
7423
7517
|
const compactTelemetryValue = async (tv, valueIdGenerator) => {
|
|
@@ -7615,12 +7709,18 @@ var TelemetryEventCollector = class {
|
|
|
7615
7709
|
flushIntervalDelay;
|
|
7616
7710
|
flushTimeout;
|
|
7617
7711
|
collectEvents = true;
|
|
7712
|
+
initialFlushIntervalDelay;
|
|
7713
|
+
started = false;
|
|
7618
7714
|
constructor(options) {
|
|
7619
7715
|
this.logger = options.logger;
|
|
7620
7716
|
this.evaluationEventQueue = new EventQueue(options.evaluationQueueLimit ?? 1e3);
|
|
7621
7717
|
this.flushIntervalDelay = options.flushIntervalDelay ?? 3e4;
|
|
7622
|
-
|
|
7623
|
-
|
|
7718
|
+
this.initialFlushIntervalDelay = options.initialFlushIntervalDelay ?? 5e3;
|
|
7719
|
+
}
|
|
7720
|
+
start() {
|
|
7721
|
+
if (this.started || !this.collectEvents) return;
|
|
7722
|
+
this.started = true;
|
|
7723
|
+
this.flushTimeout = setTimeout(() => this.flushAndScheduleNext(), this.initialFlushIntervalDelay);
|
|
7624
7724
|
}
|
|
7625
7725
|
async flushAndScheduleNext() {
|
|
7626
7726
|
if (!this.collectEvents) return;
|
|
@@ -7814,10 +7914,11 @@ var DefaultConfigDirectorClient = class {
|
|
|
7814
7914
|
ready = false;
|
|
7815
7915
|
readyPromise;
|
|
7816
7916
|
readyResolve;
|
|
7917
|
+
readyTimer;
|
|
7817
7918
|
connectionMode;
|
|
7818
7919
|
configEvaluator;
|
|
7819
7920
|
metaContext;
|
|
7820
|
-
constructor(serverSdkKey, sdkOptions, clientOptions) {
|
|
7921
|
+
constructor(serverSdkKey, sdkOptions, clientOptions, internalClientOptions) {
|
|
7821
7922
|
this.logger = clientOptions?.logger ?? createDefaultLogger$1();
|
|
7822
7923
|
this.timeout = clientOptions?.connection?.timeout ?? 3e3;
|
|
7823
7924
|
this.metaContext = clientOptions?.metadata ?? {};
|
|
@@ -7825,47 +7926,32 @@ var DefaultConfigDirectorClient = class {
|
|
|
7825
7926
|
this.configEvaluator = new ConfigEvaluator(this.logger);
|
|
7826
7927
|
const baseUrl = this.parseUrl(clientOptions?.connection?.url) ?? defaultBaseUrl;
|
|
7827
7928
|
this.connectionMode = clientOptions?.connection?.mode ?? "streaming";
|
|
7828
|
-
|
|
7829
|
-
|
|
7830
|
-
this.usageEventCollector = new ServerTelemetryEventCollector({
|
|
7831
|
-
sdkKey: serverSdkKey,
|
|
7832
|
-
sdkIdentity: sdkOptions,
|
|
7833
|
-
metaContext: clientOptions?.metadata ?? {},
|
|
7834
|
-
logger: this.logger,
|
|
7835
|
-
baseUrl,
|
|
7836
|
-
flushIntervalDelay: clientOptions?.telemetry?.flushInterval ?? DEFAULT_FLUSH_INTERVAL,
|
|
7837
|
-
evaluationQueueLimit: Math.ceil(queueLimit * .7),
|
|
7838
|
-
contextLimit: Math.floor(queueLimit * .3),
|
|
7839
|
-
urlFactory: defaultUrlFactory,
|
|
7840
|
-
valueIdGenerator: generateValueId
|
|
7841
|
-
});
|
|
7842
|
-
this.transport = new transportConstructor({
|
|
7843
|
-
serverSdkKey,
|
|
7844
|
-
baseUrl,
|
|
7845
|
-
metaContext: {
|
|
7846
|
-
...clientOptions?.metadata,
|
|
7847
|
-
sdkName: sdkOptions.sdkName,
|
|
7848
|
-
sdkVersion: sdkOptions.sdkVersion
|
|
7849
|
-
},
|
|
7850
|
-
logger: this.logger,
|
|
7851
|
-
pollingInterval: clientOptions?.connection?.pollingInterval
|
|
7852
|
-
});
|
|
7929
|
+
this.usageEventCollector = internalClientOptions?.telemetry ?? this.buildTelemetryCollector(serverSdkKey, sdkOptions, baseUrl, clientOptions);
|
|
7930
|
+
this.transport = internalClientOptions?.transport ?? this.buildTransport(serverSdkKey, sdkOptions, baseUrl, clientOptions);
|
|
7853
7931
|
this.transport.on("configBundleReceived", (configBundle) => {
|
|
7854
|
-
this.readyResolve?.();
|
|
7855
|
-
const
|
|
7856
|
-
if (!this.configSet || configBundle.kind == "full")
|
|
7857
|
-
|
|
7858
|
-
this.eventEmitter.emit("configsUpdated", { keys: configKeys });
|
|
7859
|
-
this.updateWatchers(configBundle.configs);
|
|
7860
|
-
} else {
|
|
7932
|
+
this.readyResolve?.(false);
|
|
7933
|
+
const removedKeys = this.findKeysRemovedBy(configBundle);
|
|
7934
|
+
if (!this.configSet || configBundle.kind == "full") this.configSet = configBundle;
|
|
7935
|
+
else {
|
|
7861
7936
|
this.configSet.configs = {
|
|
7862
7937
|
...this.configSet.configs,
|
|
7863
7938
|
...configBundle.configs
|
|
7864
7939
|
};
|
|
7865
|
-
this.
|
|
7866
|
-
|
|
7940
|
+
this.configSet.segments = {
|
|
7941
|
+
...this.configSet.segments,
|
|
7942
|
+
...configBundle.segments
|
|
7943
|
+
};
|
|
7867
7944
|
}
|
|
7868
|
-
|
|
7945
|
+
const keys = Object.keys(configBundle.configs);
|
|
7946
|
+
this.eventEmitter.emit("configsUpdated", {
|
|
7947
|
+
keys,
|
|
7948
|
+
removedKeys
|
|
7949
|
+
});
|
|
7950
|
+
this.updateWatchers([...keys, ...removedKeys]);
|
|
7951
|
+
this.logger.debug(`[ConfigDirectorClient] ConfigSet updated from server with ${keys.length} key(s) and ${removedKeys.length} removed key(s)`, {
|
|
7952
|
+
keys,
|
|
7953
|
+
removedKeys
|
|
7954
|
+
});
|
|
7869
7955
|
});
|
|
7870
7956
|
this.transport.on("connectionError", (error) => {
|
|
7871
7957
|
this.logger.error("[ConfigDirectorClient] The connection encountered an unrecoverable error and will not be retried. Configs will no longer receive updates.", error);
|
|
@@ -7886,18 +7972,23 @@ var DefaultConfigDirectorClient = class {
|
|
|
7886
7972
|
this.ready = false;
|
|
7887
7973
|
this.readyPromise = new Promise((resolve) => {
|
|
7888
7974
|
this.readyResolve = resolve;
|
|
7889
|
-
}).then(() => {
|
|
7975
|
+
}).then((cancelled) => {
|
|
7976
|
+
if (cancelled) return;
|
|
7890
7977
|
this.ready = true;
|
|
7891
7978
|
this.eventEmitter.emit("clientReady");
|
|
7892
7979
|
this.logger.debug("[ConfigDirectorClient] Received initial payload from the server, client is ready");
|
|
7893
7980
|
});
|
|
7981
|
+
this.usageEventCollector.start();
|
|
7894
7982
|
const startTime = (/* @__PURE__ */ new Date()).getTime();
|
|
7895
7983
|
await this.transport.connect(this.timeout);
|
|
7896
7984
|
const elapsedTime = (/* @__PURE__ */ new Date()).getTime() - startTime;
|
|
7897
7985
|
const remainingTimeout = this.timeout - elapsedTime;
|
|
7898
|
-
if (remainingTimeout > 0)
|
|
7899
|
-
|
|
7900
|
-
|
|
7986
|
+
if (remainingTimeout > 0) {
|
|
7987
|
+
await Promise.race([this.readyPromise, new Promise((resolve) => {
|
|
7988
|
+
this.readyTimer = setTimeout(() => resolve(), remainingTimeout);
|
|
7989
|
+
})]);
|
|
7990
|
+
this.clearReadyTimer();
|
|
7991
|
+
}
|
|
7901
7992
|
if (!this.ready) {
|
|
7902
7993
|
const warningDetails = this.connectionMode === "streaming" ? "The client will continue to retry since there were no fatal errors detected. Configs will return the default value until the connection succeeds." : "Since the client was configured without streaming, configs may not update and always return the default value.";
|
|
7903
7994
|
this.logger.warn(`[ConfigDirectorClient] Timed out waiting for initialization after ${this.timeout}ms. ${warningDetails}`);
|
|
@@ -7906,18 +7997,60 @@ var DefaultConfigDirectorClient = class {
|
|
|
7906
7997
|
this.logger.error("[ConfigDirectorClient] An error occurred during initialization: ", error);
|
|
7907
7998
|
}
|
|
7908
7999
|
}
|
|
8000
|
+
resolvePollingIntervalForMode(configuredSeconds) {
|
|
8001
|
+
if (this.connectionMode !== "polling") return;
|
|
8002
|
+
const resolved = resolvePollingInterval(configuredSeconds, {
|
|
8003
|
+
defaultSeconds: 300,
|
|
8004
|
+
minimumSeconds: 60
|
|
8005
|
+
});
|
|
8006
|
+
if (resolved.warning) this.logger.warn(`[ConfigDirectorClient] ${resolved.warning}`);
|
|
8007
|
+
return resolved.seconds;
|
|
8008
|
+
}
|
|
8009
|
+
buildTelemetryCollector(serverSdkKey, sdkOptions, baseUrl, clientOptions) {
|
|
8010
|
+
const queueLimit = clientOptions?.telemetry?.eventQueueLimit ?? DEFAULT_EVENT_QUEUE_LIMIT;
|
|
8011
|
+
return new ServerTelemetryEventCollector({
|
|
8012
|
+
sdkKey: serverSdkKey,
|
|
8013
|
+
sdkIdentity: sdkOptions,
|
|
8014
|
+
metaContext: clientOptions?.metadata ?? {},
|
|
8015
|
+
logger: this.logger,
|
|
8016
|
+
baseUrl,
|
|
8017
|
+
flushIntervalDelay: clientOptions?.telemetry?.flushInterval ?? DEFAULT_FLUSH_INTERVAL,
|
|
8018
|
+
evaluationQueueLimit: Math.ceil(queueLimit * .7),
|
|
8019
|
+
contextLimit: Math.floor(queueLimit * .3),
|
|
8020
|
+
urlFactory: defaultUrlFactory,
|
|
8021
|
+
valueIdGenerator: generateValueId
|
|
8022
|
+
});
|
|
8023
|
+
}
|
|
8024
|
+
buildTransport(serverSdkKey, sdkOptions, baseUrl, clientOptions) {
|
|
8025
|
+
return new (this.getTransportConstructor(this.connectionMode))({
|
|
8026
|
+
serverSdkKey,
|
|
8027
|
+
baseUrl,
|
|
8028
|
+
metaContext: {
|
|
8029
|
+
...clientOptions?.metadata,
|
|
8030
|
+
sdkName: sdkOptions.sdkName,
|
|
8031
|
+
sdkVersion: sdkOptions.sdkVersion
|
|
8032
|
+
},
|
|
8033
|
+
logger: this.logger,
|
|
8034
|
+
pollingInterval: this.resolvePollingIntervalForMode(clientOptions?.connection?.pollingInterval)
|
|
8035
|
+
});
|
|
8036
|
+
}
|
|
7909
8037
|
getTransportConstructor(mode) {
|
|
7910
8038
|
switch (mode) {
|
|
7911
8039
|
case "polling": return PollingTransport;
|
|
7912
8040
|
default: return StreamingTransport;
|
|
7913
8041
|
}
|
|
7914
8042
|
}
|
|
7915
|
-
|
|
7916
|
-
|
|
8043
|
+
findKeysRemovedBy(configBundle) {
|
|
8044
|
+
if (!this.configSet || configBundle.kind != "full") return [];
|
|
8045
|
+
return Object.keys(this.configSet.configs).filter((key) => !Object.prototype.hasOwnProperty.call(configBundle.configs, key));
|
|
8046
|
+
}
|
|
8047
|
+
updateWatchers(configKeys) {
|
|
8048
|
+
configKeys.forEach((configKey) => this.updateWatchersForConfig(configKey));
|
|
7917
8049
|
}
|
|
7918
|
-
updateWatchersForConfig(
|
|
7919
|
-
this.
|
|
7920
|
-
|
|
8050
|
+
updateWatchersForConfig(configKey) {
|
|
8051
|
+
const configDefinition = this.configSet?.configs[configKey];
|
|
8052
|
+
this.handlersMap.get(configKey)?.forEach((h) => {
|
|
8053
|
+
const value = this.getValueFromConfigDefinition(configKey, configDefinition, h.defaultValue, h.context);
|
|
7921
8054
|
h.handler(value);
|
|
7922
8055
|
});
|
|
7923
8056
|
}
|
|
@@ -7952,7 +8085,7 @@ var DefaultConfigDirectorClient = class {
|
|
|
7952
8085
|
const configState = this.configEvaluator.evaluate(configDefinition, {
|
|
7953
8086
|
context,
|
|
7954
8087
|
metadata: this.metaContext
|
|
7955
|
-
});
|
|
8088
|
+
}, this.configSet?.segments);
|
|
7956
8089
|
const parseResult = parseConfigValue(configState, defaultValue);
|
|
7957
8090
|
this.usageEventCollector.evaluatedConfig({
|
|
7958
8091
|
context,
|
|
@@ -8044,16 +8177,22 @@ var DefaultConfigDirectorClient = class {
|
|
|
8044
8177
|
return Object.fromEntries(filtered.map(([key, config]) => [key, this.configEvaluator.evaluate(config, {
|
|
8045
8178
|
context: options?.context,
|
|
8046
8179
|
metadata: this.metaContext
|
|
8047
|
-
})]));
|
|
8180
|
+
}, this.configSet?.segments)]));
|
|
8048
8181
|
}
|
|
8049
8182
|
closeConnection() {
|
|
8050
8183
|
this.logger.debug("[ConfigDirectorClient] closeConnection() has been called, closing connection to server");
|
|
8051
8184
|
this.transport.close();
|
|
8052
8185
|
this.ready = false;
|
|
8053
8186
|
}
|
|
8187
|
+
clearReadyTimer() {
|
|
8188
|
+
clearTimeout(this.readyTimer);
|
|
8189
|
+
this.readyTimer = void 0;
|
|
8190
|
+
}
|
|
8054
8191
|
dispose() {
|
|
8055
8192
|
this.removeAllObservers();
|
|
8056
8193
|
this.closeConnection();
|
|
8194
|
+
this.readyResolve?.(true);
|
|
8195
|
+
this.clearReadyTimer();
|
|
8057
8196
|
this.usageEventCollector.close().catch((error) => {
|
|
8058
8197
|
this.logger.warn("[ConfigDirectorClient] Error flushing telemetry while disposing the client: ", error);
|
|
8059
8198
|
});
|
|
@@ -8080,7 +8219,7 @@ var plugin_default = defineNitroPlugin((nitroApp) => {
|
|
|
8080
8219
|
const { serverSdkKey, baseUrl, connection } = runtimeConfig.configdirector;
|
|
8081
8220
|
const client = new DefaultConfigDirectorClient(serverSdkKey, {
|
|
8082
8221
|
sdkName: "nuxt-sdk",
|
|
8083
|
-
sdkVersion: "1.
|
|
8222
|
+
sdkVersion: "1.9.0"
|
|
8084
8223
|
}, {
|
|
8085
8224
|
logger,
|
|
8086
8225
|
connection: {
|