@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.
@@ -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
- return Promise.race([eventSourcePromise, new Promise((resolve) => {
437
- setTimeout(() => resolve(this), timeout);
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
- close() {
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(this.findTraitValue(context?.context?.traits, condition.trait));
7072
+ return orAbsent(resolveJsonPointer(condition.trait, context?.context?.traits));
7040
7073
  default: return UNKNOWN_ATTRIBUTE;
7041
7074
  }
7042
7075
  }
7043
- findTraitValue(traits, path) {
7044
- try {
7045
- return findByPointer(path, traits).val;
7046
- } catch {
7047
- return;
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 orAbsent = (value) => value === void 0 || value === null ? ABSENT : value;
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 check = this.conditionEvaluator.explain(condition, context);
7185
- explanation.conditions.push({
7186
- conditionId: condition.id,
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
- const initialDelay = options.initialFlushIntervalDelay ?? 5e3;
7623
- this.flushTimeout = setTimeout(() => this.flushAndScheduleNext(), initialDelay);
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
- const transportConstructor = this.getTransportConstructor(this.connectionMode);
7829
- const queueLimit = clientOptions?.telemetry?.eventQueueLimit ?? DEFAULT_EVENT_QUEUE_LIMIT;
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 configKeys = Object.keys(configBundle.configs);
7856
- if (!this.configSet || configBundle.kind == "full") {
7857
- this.configSet = configBundle;
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.eventEmitter.emit("configsUpdated", { keys: configKeys });
7866
- this.updateWatchers(configBundle.configs);
7940
+ this.configSet.segments = {
7941
+ ...this.configSet.segments,
7942
+ ...configBundle.segments
7943
+ };
7867
7944
  }
7868
- this.logger.debug(`[ConfigDirectorClient] ConfigSet updated from server with ${configKeys.length} key(s)`, { keys: configKeys });
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) await Promise.race([this.readyPromise, new Promise((resolve) => {
7899
- setTimeout(() => resolve(), remainingTimeout);
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
- updateWatchers(configsMap) {
7916
- Object.values(configsMap).forEach((v) => this.updateWatchersForConfig(v));
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(configDefinition) {
7919
- this.handlersMap.get(configDefinition.key)?.forEach((h) => {
7920
- const value = this.getValueFromConfigDefinition(configDefinition.key, configDefinition, h.defaultValue, h.context);
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.7.0"
8222
+ sdkVersion: "1.9.0"
8084
8223
  }, {
8085
8224
  logger,
8086
8225
  connection: {