@configdirector/nuxt-sdk 1.7.0 → 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.7.0"
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
@@ -7223,7 +7249,7 @@ const configTypesNotReadableAsString = new Set([
7223
7249
  const parseConfigValue = (configState, defaultValue) => {
7224
7250
  const value = configState.value;
7225
7251
  const requestedType = getRequestedType(defaultValue);
7226
- if (value == null) return {
7252
+ if (value == null || value === "") return {
7227
7253
  parsedValue: defaultValue,
7228
7254
  parsedValueId: void 0,
7229
7255
  requestedType,
@@ -7418,6 +7444,22 @@ const createDefaultLogger$1 = (level, messageDecorator) => {
7418
7444
  //#region ../shared/src/url.ts
7419
7445
  const defaultUrlFactory = (input, base) => new URL(input, base?.toString());
7420
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
7421
7463
  //#region ../shared/src/telemetry/utils.ts
7422
7464
  const CONFIG_VALUE_MAX_LENGTH = 500;
7423
7465
  const compactTelemetryValue = async (tv, valueIdGenerator) => {
@@ -7615,12 +7657,18 @@ var TelemetryEventCollector = class {
7615
7657
  flushIntervalDelay;
7616
7658
  flushTimeout;
7617
7659
  collectEvents = true;
7660
+ initialFlushIntervalDelay;
7661
+ started = false;
7618
7662
  constructor(options) {
7619
7663
  this.logger = options.logger;
7620
7664
  this.evaluationEventQueue = new EventQueue(options.evaluationQueueLimit ?? 1e3);
7621
7665
  this.flushIntervalDelay = options.flushIntervalDelay ?? 3e4;
7622
- const initialDelay = options.initialFlushIntervalDelay ?? 5e3;
7623
- 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);
7624
7672
  }
7625
7673
  async flushAndScheduleNext() {
7626
7674
  if (!this.collectEvents) return;
@@ -7814,10 +7862,11 @@ var DefaultConfigDirectorClient = class {
7814
7862
  ready = false;
7815
7863
  readyPromise;
7816
7864
  readyResolve;
7865
+ readyTimer;
7817
7866
  connectionMode;
7818
7867
  configEvaluator;
7819
7868
  metaContext;
7820
- constructor(serverSdkKey, sdkOptions, clientOptions) {
7869
+ constructor(serverSdkKey, sdkOptions, clientOptions, internalClientOptions) {
7821
7870
  this.logger = clientOptions?.logger ?? createDefaultLogger$1();
7822
7871
  this.timeout = clientOptions?.connection?.timeout ?? 3e3;
7823
7872
  this.metaContext = clientOptions?.metadata ?? {};
@@ -7825,47 +7874,26 @@ var DefaultConfigDirectorClient = class {
7825
7874
  this.configEvaluator = new ConfigEvaluator(this.logger);
7826
7875
  const baseUrl = this.parseUrl(clientOptions?.connection?.url) ?? defaultBaseUrl;
7827
7876
  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
- });
7877
+ this.usageEventCollector = internalClientOptions?.telemetry ?? this.buildTelemetryCollector(serverSdkKey, sdkOptions, baseUrl, clientOptions);
7878
+ this.transport = internalClientOptions?.transport ?? this.buildTransport(serverSdkKey, sdkOptions, baseUrl, clientOptions);
7853
7879
  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 {
7861
- this.configSet.configs = {
7862
- ...this.configSet.configs,
7863
- ...configBundle.configs
7864
- };
7865
- this.eventEmitter.emit("configsUpdated", { keys: configKeys });
7866
- this.updateWatchers(configBundle.configs);
7867
- }
7868
- 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
+ });
7869
7897
  });
7870
7898
  this.transport.on("connectionError", (error) => {
7871
7899
  this.logger.error("[ConfigDirectorClient] The connection encountered an unrecoverable error and will not be retried. Configs will no longer receive updates.", error);
@@ -7886,18 +7914,23 @@ var DefaultConfigDirectorClient = class {
7886
7914
  this.ready = false;
7887
7915
  this.readyPromise = new Promise((resolve) => {
7888
7916
  this.readyResolve = resolve;
7889
- }).then(() => {
7917
+ }).then((cancelled) => {
7918
+ if (cancelled) return;
7890
7919
  this.ready = true;
7891
7920
  this.eventEmitter.emit("clientReady");
7892
7921
  this.logger.debug("[ConfigDirectorClient] Received initial payload from the server, client is ready");
7893
7922
  });
7923
+ this.usageEventCollector.start();
7894
7924
  const startTime = (/* @__PURE__ */ new Date()).getTime();
7895
7925
  await this.transport.connect(this.timeout);
7896
7926
  const elapsedTime = (/* @__PURE__ */ new Date()).getTime() - startTime;
7897
7927
  const remainingTimeout = this.timeout - elapsedTime;
7898
- if (remainingTimeout > 0) await Promise.race([this.readyPromise, new Promise((resolve) => {
7899
- setTimeout(() => resolve(), remainingTimeout);
7900
- })]);
7928
+ if (remainingTimeout > 0) {
7929
+ await Promise.race([this.readyPromise, new Promise((resolve) => {
7930
+ this.readyTimer = setTimeout(() => resolve(), remainingTimeout);
7931
+ })]);
7932
+ this.clearReadyTimer();
7933
+ }
7901
7934
  if (!this.ready) {
7902
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.";
7903
7936
  this.logger.warn(`[ConfigDirectorClient] Timed out waiting for initialization after ${this.timeout}ms. ${warningDetails}`);
@@ -7906,18 +7939,60 @@ var DefaultConfigDirectorClient = class {
7906
7939
  this.logger.error("[ConfigDirectorClient] An error occurred during initialization: ", error);
7907
7940
  }
7908
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
+ }
7909
7979
  getTransportConstructor(mode) {
7910
7980
  switch (mode) {
7911
7981
  case "polling": return PollingTransport;
7912
7982
  default: return StreamingTransport;
7913
7983
  }
7914
7984
  }
7915
- updateWatchers(configsMap) {
7916
- 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));
7917
7991
  }
7918
- updateWatchersForConfig(configDefinition) {
7919
- this.handlersMap.get(configDefinition.key)?.forEach((h) => {
7920
- 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);
7921
7996
  h.handler(value);
7922
7997
  });
7923
7998
  }
@@ -8051,9 +8126,15 @@ var DefaultConfigDirectorClient = class {
8051
8126
  this.transport.close();
8052
8127
  this.ready = false;
8053
8128
  }
8129
+ clearReadyTimer() {
8130
+ clearTimeout(this.readyTimer);
8131
+ this.readyTimer = void 0;
8132
+ }
8054
8133
  dispose() {
8055
8134
  this.removeAllObservers();
8056
8135
  this.closeConnection();
8136
+ this.readyResolve?.(true);
8137
+ this.clearReadyTimer();
8057
8138
  this.usageEventCollector.close().catch((error) => {
8058
8139
  this.logger.warn("[ConfigDirectorClient] Error flushing telemetry while disposing the client: ", error);
8059
8140
  });
@@ -8080,7 +8161,7 @@ var plugin_default = defineNitroPlugin((nitroApp) => {
8080
8161
  const { serverSdkKey, baseUrl, connection } = runtimeConfig.configdirector;
8081
8162
  const client = new DefaultConfigDirectorClient(serverSdkKey, {
8082
8163
  sdkName: "nuxt-sdk",
8083
- sdkVersion: "1.7.0"
8164
+ sdkVersion: "1.8.0"
8084
8165
  }, {
8085
8166
  logger,
8086
8167
  connection: {