@configdirector/nuxt-sdk 1.6.1 → 1.8.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 CHANGED
@@ -31,6 +31,40 @@ const { value: darkMode } = useConfigDirectorValue("dark-mode", false);
31
31
 
32
32
  Full details are in the [official documentation](https://docs.configdirector.com/sdks/meta/nuxt).
33
33
 
34
+ ## Test your code
35
+
36
+ `@configdirector/nuxt-sdk/testing` creates a real client connected to an in-memory server that your
37
+ test controls. Install it before the Nuxt app is created, at the top level of a test file or a
38
+ vitest setup file, and the ConfigDirector plugin provides it to the app instead of building a
39
+ client. Tests run in the `nuxt` environment of `@nuxt/test-utils` 4 (which needs vitest 4).
40
+
41
+ ```ts
42
+ import { nextTick } from "vue";
43
+ import { mountSuspended } from "@nuxt/test-utils/runtime";
44
+ import { createTestClient, installTestClient } from "@configdirector/nuxt-sdk/testing";
45
+ import Checkout from "~/components/Checkout.vue";
46
+
47
+ const testClient = createTestClient({ values: { "dark-mode": true } });
48
+ installTestClient(testClient);
49
+
50
+ test("renders the dark mode", async () => {
51
+ const component = await mountSuspended(Checkout);
52
+ expect(component.text()).toContain("dark-mode: true");
53
+
54
+ testClient.setValue("dark-mode", false);
55
+ await nextTick();
56
+ expect(component.text()).toContain("dark-mode: false");
57
+ });
58
+ ```
59
+
60
+ `@nuxt/test-utils` 4 creates the app once per test file, inside a `beforeAll` hook, so install at
61
+ module level rather than in a hook, and reset the values between tests with `replaceValues`.
62
+ `@nuxt/test-utils` 3 creates the app before the test file loads, so the plugin cannot see a test
63
+ client installed there; upgrade to use the testing entry. While
64
+ installed, the plugin needs no `clientSdkKey`. Await `nextTick()` after `setValue`, `removeValue`,
65
+ and `replaceValues`. The test client also holds or fails `initialize` and `updateContext`, and
66
+ records every context in `contextUpdates`.
67
+
34
68
  ## Documentation
35
69
 
36
70
  Refer to the [official documentation for the Nuxt SDK](https://docs.configdirector.com/sdks/meta/nuxt).
package/dist/module.d.mts CHANGED
@@ -92,6 +92,32 @@ interface ConfigDirectorPublicRuntimeConfig {
92
92
  * Can be set via the NUXT_PUBLIC_CONFIGDIRECTOR_BASE_URL environment variable.
93
93
  */
94
94
  baseUrl?: string;
95
+ /**
96
+ * Connection options for the browser client used on the client side.
97
+ */
98
+ connection?: {
99
+ /**
100
+ * The connection mode, one of `streaming` (default) or `polling`. In `streaming` mode the
101
+ * connection stays open and receives config updates as they happen. In `polling` mode configs
102
+ * are fetched once during initialization and then again on every `pollingInterval`.
103
+ * Can be set via the NUXT_PUBLIC_CONFIGDIRECTOR_CONNECTION_MODE environment variable.
104
+ */
105
+ mode?: ConnectionMode;
106
+ /**
107
+ * The polling interval in seconds when `mode` is `polling`. Has no effect in `streaming` mode.
108
+ * When omitted (or `0`), it defaults to 60 seconds. A value below the minimum of 30 seconds is
109
+ * raised to 30 seconds and a warning is logged.
110
+ * Can be set via the NUXT_PUBLIC_CONFIGDIRECTOR_CONNECTION_POLLING_INTERVAL environment variable.
111
+ */
112
+ pollingInterval?: number;
113
+ /**
114
+ * How long, in milliseconds, the browser client waits for its initial config payload during
115
+ * initialization before the app renders with default values. When omitted (or `0`), it defaults
116
+ * to 2000 milliseconds.
117
+ * Can be set via the NUXT_PUBLIC_CONFIGDIRECTOR_CONNECTION_TIMEOUT environment variable.
118
+ */
119
+ timeout?: number;
120
+ };
95
121
  }
96
122
  declare module "nuxt/schema" {
97
123
  interface RuntimeConfig {
package/dist/module.json CHANGED
@@ -4,5 +4,5 @@
4
4
  "compatibility": {
5
5
  "nuxt": ">=4.0.0"
6
6
  },
7
- "version": "1.6.1"
7
+ "version": "1.8.0"
8
8
  }
package/dist/module.mjs CHANGED
@@ -25,14 +25,19 @@ var module_default = defineNuxtModule({
25
25
  ...serverDefaults,
26
26
  logLevel: options.logLevel
27
27
  } : serverDefaults);
28
- nuxt.options.runtimeConfig.public.configdirector = defu(nuxt.options.runtimeConfig.public.configdirector, options.logLevel !== void 0 ? {
29
- clientSdkKey: "",
30
- logLevel: options.logLevel,
31
- baseUrl: ""
32
- } : {
28
+ const publicDefaults = {
33
29
  clientSdkKey: "",
34
- baseUrl: ""
35
- });
30
+ baseUrl: "",
31
+ connection: {
32
+ mode: "streaming",
33
+ pollingInterval: 0,
34
+ timeout: 0
35
+ }
36
+ };
37
+ nuxt.options.runtimeConfig.public.configdirector = defu(nuxt.options.runtimeConfig.public.configdirector, options.logLevel !== void 0 ? {
38
+ ...publicDefaults,
39
+ logLevel: options.logLevel
40
+ } : publicDefaults);
36
41
  logger.debug("ConfigDirector Nuxt module setup complete");
37
42
  addServerPlugin(resolver.resolve("./runtime/nitro/plugin"));
38
43
  addServerHandler({
@@ -1,4 +1,4 @@
1
- import { n as ConfigDirectorClient } from "../../types-BPvGEzJY.mjs";
1
+ import { n as ConfigDirectorClient } from "../../types-CvmjergQ.mjs";
2
2
 
3
3
  //#region src/runtime/app/composables/useConfigDirectorClient.d.ts
4
4
  declare const useConfigDirectorClient: () => {
@@ -1,4 +1,4 @@
1
- import { n as ConfigDirectorClient } from "../../types-BPvGEzJY.mjs";
1
+ import { n as ConfigDirectorClient } from "../../types-CvmjergQ.mjs";
2
2
 
3
3
  //#region src/runtime/app/composables/useConfigDirectorClient.d.ts
4
4
  declare const useConfigDirectorClient: () => {
@@ -1,4 +1,4 @@
1
- import { t as ClientHooks } from "../../types-BPvGEzJY.mjs";
1
+ import { t as ClientHooks } from "../../types-CvmjergQ.mjs";
2
2
 
3
3
  //#region src/runtime/app/composables/useConfigDirectorClientHooks.d.ts
4
4
  declare const useConfigDirectorClientHooks: (hooks: ClientHooks) => void;
@@ -1,4 +1,4 @@
1
- import { t as ClientHooks } from "../../types-BPvGEzJY.mjs";
1
+ import { t as ClientHooks } from "../../types-CvmjergQ.mjs";
2
2
 
3
3
  //#region src/runtime/app/composables/useConfigDirectorClientHooks.d.ts
4
4
  declare const useConfigDirectorClientHooks: (hooks: ClientHooks) => void;
@@ -16,9 +16,14 @@ const useConfigDirectorContext = () => {
16
16
  state.value = rawContext;
17
17
  if (oldContext != newContext) {
18
18
  const timeout = getEffectiveTimeout(options?.timeoutMilliseconds);
19
- await Promise.race([client.updateContext(rawContext), new Promise((resolve) => {
20
- setTimeout(() => resolve(), timeout);
21
- })]);
19
+ let timer;
20
+ try {
21
+ await Promise.race([client.updateContext(rawContext), new Promise((resolve) => {
22
+ timer = setTimeout(() => resolve(), timeout);
23
+ })]);
24
+ } finally {
25
+ clearTimeout(timer);
26
+ }
22
27
  }
23
28
  };
24
29
  return {
@@ -1,4 +1,4 @@
1
- import { n as ConfigDirectorClient } from "../types-C54FjC1D.mjs";
1
+ import { n as ConfigDirectorClient } from "../types-Bgx2-3dY.mjs";
2
2
  import * as _$nitropack from "nitropack";
3
3
 
4
4
  //#region src/runtime/nitro/plugin.d.ts
@@ -1,4 +1,4 @@
1
- import { n as ConfigDirectorClient } from "../types-C54FjC1D.mjs";
1
+ import { n as ConfigDirectorClient } from "../types-Bgx2-3dY.mjs";
2
2
  import * as _$nitropack from "nitropack";
3
3
 
4
4
  //#region src/runtime/nitro/plugin.d.ts
@@ -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,17 +7069,10 @@ 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;
7048
- }
7049
- }
7050
7076
  };
7051
7077
  const orAbsent = (value) => value === void 0 || value === null ? ABSENT : value;
7052
7078
  //#endregion
@@ -7143,9 +7169,8 @@ var ConfigEvaluator = class {
7143
7169
  }
7144
7170
  }
7145
7171
  explainPercentage(percentages, config, context, explanation) {
7146
- const contextIdentifier = context?.context?.id;
7147
- const identifier = contextIdentifier ?? crypto.randomUUID();
7148
- const assignedPercentage = assignPercentage({
7172
+ const identifier = context?.context?.id ?? void 0;
7173
+ const assignedPercentage = identifier === void 0 ? 0 : assignPercentage({
7149
7174
  configId: config.id,
7150
7175
  contextIdentifier: identifier
7151
7176
  });
@@ -7165,7 +7190,7 @@ var ConfigEvaluator = class {
7165
7190
  }
7166
7191
  explanation.bucket = {
7167
7192
  identifier,
7168
- identifierWasGenerated: contextIdentifier == null,
7193
+ defaultedToPredefinedBucket: identifier === void 0,
7169
7194
  assignedPercentage,
7170
7195
  shares,
7171
7196
  selectedPercentageId: selected?.id
@@ -7224,7 +7249,7 @@ const configTypesNotReadableAsString = new Set([
7224
7249
  const parseConfigValue = (configState, defaultValue) => {
7225
7250
  const value = configState.value;
7226
7251
  const requestedType = getRequestedType(defaultValue);
7227
- if (value == null) return {
7252
+ if (value == null || value === "") return {
7228
7253
  parsedValue: defaultValue,
7229
7254
  parsedValueId: void 0,
7230
7255
  requestedType,
@@ -7419,6 +7444,22 @@ const createDefaultLogger$1 = (level, messageDecorator) => {
7419
7444
  //#region ../shared/src/url.ts
7420
7445
  const defaultUrlFactory = (input, base) => new URL(input, base?.toString());
7421
7446
  //#endregion
7447
+ //#region ../shared/src/transport/pollingInterval.ts
7448
+ const resolvePollingInterval = (configuredSeconds, defaults) => {
7449
+ if (configuredSeconds === void 0) return {
7450
+ seconds: defaults.defaultSeconds,
7451
+ warning: void 0
7452
+ };
7453
+ if (configuredSeconds >= defaults.minimumSeconds) return {
7454
+ seconds: configuredSeconds,
7455
+ warning: void 0
7456
+ };
7457
+ return {
7458
+ seconds: defaults.minimumSeconds,
7459
+ warning: `pollingInterval of ${configuredSeconds} seconds is below the minimum of ${defaults.minimumSeconds} seconds. Using ${defaults.minimumSeconds} seconds.`
7460
+ };
7461
+ };
7462
+ //#endregion
7422
7463
  //#region ../shared/src/telemetry/utils.ts
7423
7464
  const CONFIG_VALUE_MAX_LENGTH = 500;
7424
7465
  const compactTelemetryValue = async (tv, valueIdGenerator) => {
@@ -7616,12 +7657,18 @@ var TelemetryEventCollector = class {
7616
7657
  flushIntervalDelay;
7617
7658
  flushTimeout;
7618
7659
  collectEvents = true;
7660
+ initialFlushIntervalDelay;
7661
+ started = false;
7619
7662
  constructor(options) {
7620
7663
  this.logger = options.logger;
7621
7664
  this.evaluationEventQueue = new EventQueue(options.evaluationQueueLimit ?? 1e3);
7622
7665
  this.flushIntervalDelay = options.flushIntervalDelay ?? 3e4;
7623
- const initialDelay = options.initialFlushIntervalDelay ?? 5e3;
7624
- this.flushTimeout = setTimeout(() => this.flushAndScheduleNext(), initialDelay);
7666
+ this.initialFlushIntervalDelay = options.initialFlushIntervalDelay ?? 5e3;
7667
+ }
7668
+ start() {
7669
+ if (this.started || !this.collectEvents) return;
7670
+ this.started = true;
7671
+ this.flushTimeout = setTimeout(() => this.flushAndScheduleNext(), this.initialFlushIntervalDelay);
7625
7672
  }
7626
7673
  async flushAndScheduleNext() {
7627
7674
  if (!this.collectEvents) return;
@@ -7815,10 +7862,11 @@ var DefaultConfigDirectorClient = class {
7815
7862
  ready = false;
7816
7863
  readyPromise;
7817
7864
  readyResolve;
7865
+ readyTimer;
7818
7866
  connectionMode;
7819
7867
  configEvaluator;
7820
7868
  metaContext;
7821
- constructor(serverSdkKey, sdkOptions, clientOptions) {
7869
+ constructor(serverSdkKey, sdkOptions, clientOptions, internalClientOptions) {
7822
7870
  this.logger = clientOptions?.logger ?? createDefaultLogger$1();
7823
7871
  this.timeout = clientOptions?.connection?.timeout ?? 3e3;
7824
7872
  this.metaContext = clientOptions?.metadata ?? {};
@@ -7826,47 +7874,26 @@ var DefaultConfigDirectorClient = class {
7826
7874
  this.configEvaluator = new ConfigEvaluator(this.logger);
7827
7875
  const baseUrl = this.parseUrl(clientOptions?.connection?.url) ?? defaultBaseUrl;
7828
7876
  this.connectionMode = clientOptions?.connection?.mode ?? "streaming";
7829
- const transportConstructor = this.getTransportConstructor(this.connectionMode);
7830
- const queueLimit = clientOptions?.telemetry?.eventQueueLimit ?? DEFAULT_EVENT_QUEUE_LIMIT;
7831
- this.usageEventCollector = new ServerTelemetryEventCollector({
7832
- sdkKey: serverSdkKey,
7833
- sdkIdentity: sdkOptions,
7834
- metaContext: clientOptions?.metadata ?? {},
7835
- logger: this.logger,
7836
- baseUrl,
7837
- flushIntervalDelay: clientOptions?.telemetry?.flushInterval ?? DEFAULT_FLUSH_INTERVAL,
7838
- evaluationQueueLimit: Math.ceil(queueLimit * .7),
7839
- contextLimit: Math.floor(queueLimit * .3),
7840
- urlFactory: defaultUrlFactory,
7841
- valueIdGenerator: generateValueId
7842
- });
7843
- this.transport = new transportConstructor({
7844
- serverSdkKey,
7845
- baseUrl,
7846
- metaContext: {
7847
- ...clientOptions?.metadata,
7848
- sdkName: sdkOptions.sdkName,
7849
- sdkVersion: sdkOptions.sdkVersion
7850
- },
7851
- logger: this.logger,
7852
- pollingInterval: clientOptions?.connection?.pollingInterval
7853
- });
7877
+ this.usageEventCollector = internalClientOptions?.telemetry ?? this.buildTelemetryCollector(serverSdkKey, sdkOptions, baseUrl, clientOptions);
7878
+ this.transport = internalClientOptions?.transport ?? this.buildTransport(serverSdkKey, sdkOptions, baseUrl, clientOptions);
7854
7879
  this.transport.on("configBundleReceived", (configBundle) => {
7855
- this.readyResolve?.();
7856
- const configKeys = Object.keys(configBundle.configs);
7857
- if (!this.configSet || configBundle.kind == "full") {
7858
- this.configSet = configBundle;
7859
- this.eventEmitter.emit("configsUpdated", { keys: configKeys });
7860
- this.updateWatchers(configBundle.configs);
7861
- } else {
7862
- this.configSet.configs = {
7863
- ...this.configSet.configs,
7864
- ...configBundle.configs
7865
- };
7866
- this.eventEmitter.emit("configsUpdated", { keys: configKeys });
7867
- this.updateWatchers(configBundle.configs);
7868
- }
7869
- this.logger.debug(`[ConfigDirectorClient] ConfigSet updated from server with ${configKeys.length} key(s)`, { keys: configKeys });
7880
+ this.readyResolve?.(false);
7881
+ const removedKeys = this.findKeysRemovedBy(configBundle);
7882
+ if (!this.configSet || configBundle.kind == "full") this.configSet = configBundle;
7883
+ else this.configSet.configs = {
7884
+ ...this.configSet.configs,
7885
+ ...configBundle.configs
7886
+ };
7887
+ const keys = Object.keys(configBundle.configs);
7888
+ this.eventEmitter.emit("configsUpdated", {
7889
+ keys,
7890
+ removedKeys
7891
+ });
7892
+ this.updateWatchers([...keys, ...removedKeys]);
7893
+ this.logger.debug(`[ConfigDirectorClient] ConfigSet updated from server with ${keys.length} key(s) and ${removedKeys.length} removed key(s)`, {
7894
+ keys,
7895
+ removedKeys
7896
+ });
7870
7897
  });
7871
7898
  this.transport.on("connectionError", (error) => {
7872
7899
  this.logger.error("[ConfigDirectorClient] The connection encountered an unrecoverable error and will not be retried. Configs will no longer receive updates.", error);
@@ -7887,18 +7914,23 @@ var DefaultConfigDirectorClient = class {
7887
7914
  this.ready = false;
7888
7915
  this.readyPromise = new Promise((resolve) => {
7889
7916
  this.readyResolve = resolve;
7890
- }).then(() => {
7917
+ }).then((cancelled) => {
7918
+ if (cancelled) return;
7891
7919
  this.ready = true;
7892
7920
  this.eventEmitter.emit("clientReady");
7893
7921
  this.logger.debug("[ConfigDirectorClient] Received initial payload from the server, client is ready");
7894
7922
  });
7923
+ this.usageEventCollector.start();
7895
7924
  const startTime = (/* @__PURE__ */ new Date()).getTime();
7896
7925
  await this.transport.connect(this.timeout);
7897
7926
  const elapsedTime = (/* @__PURE__ */ new Date()).getTime() - startTime;
7898
7927
  const remainingTimeout = this.timeout - elapsedTime;
7899
- if (remainingTimeout > 0) await Promise.race([this.readyPromise, new Promise((resolve) => {
7900
- setTimeout(() => resolve(), remainingTimeout);
7901
- })]);
7928
+ if (remainingTimeout > 0) {
7929
+ await Promise.race([this.readyPromise, new Promise((resolve) => {
7930
+ this.readyTimer = setTimeout(() => resolve(), remainingTimeout);
7931
+ })]);
7932
+ this.clearReadyTimer();
7933
+ }
7902
7934
  if (!this.ready) {
7903
7935
  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.";
7904
7936
  this.logger.warn(`[ConfigDirectorClient] Timed out waiting for initialization after ${this.timeout}ms. ${warningDetails}`);
@@ -7907,18 +7939,60 @@ var DefaultConfigDirectorClient = class {
7907
7939
  this.logger.error("[ConfigDirectorClient] An error occurred during initialization: ", error);
7908
7940
  }
7909
7941
  }
7942
+ resolvePollingIntervalForMode(configuredSeconds) {
7943
+ if (this.connectionMode !== "polling") return;
7944
+ const resolved = resolvePollingInterval(configuredSeconds, {
7945
+ defaultSeconds: 300,
7946
+ minimumSeconds: 60
7947
+ });
7948
+ if (resolved.warning) this.logger.warn(`[ConfigDirectorClient] ${resolved.warning}`);
7949
+ return resolved.seconds;
7950
+ }
7951
+ buildTelemetryCollector(serverSdkKey, sdkOptions, baseUrl, clientOptions) {
7952
+ const queueLimit = clientOptions?.telemetry?.eventQueueLimit ?? DEFAULT_EVENT_QUEUE_LIMIT;
7953
+ return new ServerTelemetryEventCollector({
7954
+ sdkKey: serverSdkKey,
7955
+ sdkIdentity: sdkOptions,
7956
+ metaContext: clientOptions?.metadata ?? {},
7957
+ logger: this.logger,
7958
+ baseUrl,
7959
+ flushIntervalDelay: clientOptions?.telemetry?.flushInterval ?? DEFAULT_FLUSH_INTERVAL,
7960
+ evaluationQueueLimit: Math.ceil(queueLimit * .7),
7961
+ contextLimit: Math.floor(queueLimit * .3),
7962
+ urlFactory: defaultUrlFactory,
7963
+ valueIdGenerator: generateValueId
7964
+ });
7965
+ }
7966
+ buildTransport(serverSdkKey, sdkOptions, baseUrl, clientOptions) {
7967
+ return new (this.getTransportConstructor(this.connectionMode))({
7968
+ serverSdkKey,
7969
+ baseUrl,
7970
+ metaContext: {
7971
+ ...clientOptions?.metadata,
7972
+ sdkName: sdkOptions.sdkName,
7973
+ sdkVersion: sdkOptions.sdkVersion
7974
+ },
7975
+ logger: this.logger,
7976
+ pollingInterval: this.resolvePollingIntervalForMode(clientOptions?.connection?.pollingInterval)
7977
+ });
7978
+ }
7910
7979
  getTransportConstructor(mode) {
7911
7980
  switch (mode) {
7912
7981
  case "polling": return PollingTransport;
7913
7982
  default: return StreamingTransport;
7914
7983
  }
7915
7984
  }
7916
- updateWatchers(configsMap) {
7917
- Object.values(configsMap).forEach((v) => this.updateWatchersForConfig(v));
7985
+ findKeysRemovedBy(configBundle) {
7986
+ if (!this.configSet || configBundle.kind != "full") return [];
7987
+ return Object.keys(this.configSet.configs).filter((key) => !Object.prototype.hasOwnProperty.call(configBundle.configs, key));
7988
+ }
7989
+ updateWatchers(configKeys) {
7990
+ configKeys.forEach((configKey) => this.updateWatchersForConfig(configKey));
7918
7991
  }
7919
- updateWatchersForConfig(configDefinition) {
7920
- this.handlersMap.get(configDefinition.key)?.forEach((h) => {
7921
- const value = this.getValueFromConfigDefinition(configDefinition.key, configDefinition, h.defaultValue, h.context);
7992
+ updateWatchersForConfig(configKey) {
7993
+ const configDefinition = this.configSet?.configs[configKey];
7994
+ this.handlersMap.get(configKey)?.forEach((h) => {
7995
+ const value = this.getValueFromConfigDefinition(configKey, configDefinition, h.defaultValue, h.context);
7922
7996
  h.handler(value);
7923
7997
  });
7924
7998
  }
@@ -8052,9 +8126,15 @@ var DefaultConfigDirectorClient = class {
8052
8126
  this.transport.close();
8053
8127
  this.ready = false;
8054
8128
  }
8129
+ clearReadyTimer() {
8130
+ clearTimeout(this.readyTimer);
8131
+ this.readyTimer = void 0;
8132
+ }
8055
8133
  dispose() {
8056
8134
  this.removeAllObservers();
8057
8135
  this.closeConnection();
8136
+ this.readyResolve?.(true);
8137
+ this.clearReadyTimer();
8058
8138
  this.usageEventCollector.close().catch((error) => {
8059
8139
  this.logger.warn("[ConfigDirectorClient] Error flushing telemetry while disposing the client: ", error);
8060
8140
  });
@@ -8081,7 +8161,7 @@ var plugin_default = defineNitroPlugin((nitroApp) => {
8081
8161
  const { serverSdkKey, baseUrl, connection } = runtimeConfig.configdirector;
8082
8162
  const client = new DefaultConfigDirectorClient(serverSdkKey, {
8083
8163
  sdkName: "nuxt-sdk",
8084
- sdkVersion: "1.6.1"
8164
+ sdkVersion: "1.8.0"
8085
8165
  }, {
8086
8166
  logger,
8087
8167
  connection: {