@configdirector/nuxt-sdk 1.9.2 → 1.11.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.
Files changed (40) hide show
  1. package/dist/module.d.mts +76 -15
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +13 -4
  4. package/dist/runtime/app/composables/useConfigDirectorClient.d.mts +1 -1
  5. package/dist/runtime/app/composables/useConfigDirectorClient.d.ts +1 -1
  6. package/dist/runtime/app/composables/useConfigDirectorClientHooks.d.mts +1 -1
  7. package/dist/runtime/app/composables/useConfigDirectorClientHooks.d.ts +1 -1
  8. package/dist/runtime/app/composables/useConfigDirectorContext.d.mts +1 -1
  9. package/dist/runtime/app/composables/useConfigDirectorContext.d.ts +1 -1
  10. package/dist/runtime/app/composables/useConfigDirectorContext.mjs +2 -1
  11. package/dist/runtime/app/composables/useConfigDirectorStatus.d.mts +1 -1
  12. package/dist/runtime/app/composables/useConfigDirectorStatus.d.ts +1 -1
  13. package/dist/runtime/app/composables/useConfigDirectorValue.d.mts +2 -2
  14. package/dist/runtime/app/composables/useConfigDirectorValue.d.ts +2 -2
  15. package/dist/runtime/context-kind-D6YBNij2.mjs +45 -0
  16. package/dist/runtime/native-values-BV2k_xR2.d.mts +83 -0
  17. package/dist/runtime/native-values-DZJjFBvk.mjs +116 -0
  18. package/dist/runtime/nitro/middleware.mjs +5 -1
  19. package/dist/runtime/nitro/offline-values.d.mts +7 -0
  20. package/dist/runtime/nitro/offline-values.d.ts +7 -0
  21. package/dist/runtime/nitro/offline-values.mjs +15 -0
  22. package/dist/runtime/nitro/plugin.d.mts +3 -1
  23. package/dist/runtime/nitro/plugin.d.ts +3 -1
  24. package/dist/runtime/nitro/plugin.mjs +329 -92
  25. package/dist/runtime/plugin.client.mjs +553 -118
  26. package/dist/runtime/plugin.server.mjs +50 -6
  27. package/dist/runtime/server/composables/useConfigDirectorServerClient.d.mts +6 -0
  28. package/dist/runtime/server/composables/useConfigDirectorServerClient.d.ts +6 -0
  29. package/dist/runtime/server/composables/{useConfigDirectorClient.mjs → useConfigDirectorServerClient.mjs} +3 -3
  30. package/dist/runtime/server/composables/useConfigDirectorServerHooks.d.mts +1 -1
  31. package/dist/runtime/server/composables/useConfigDirectorServerHooks.d.ts +1 -1
  32. package/dist/runtime/{types-Bgx2-3dY.d.mts → types-CS7AMNWe.d.mts} +1 -1
  33. package/dist/runtime/{types-CvmjergQ.d.mts → types-Dt3bENKf.d.mts} +13 -5
  34. package/dist/testing.d.mts +49 -27
  35. package/dist/testing.mjs +429 -185
  36. package/package.json +1 -1
  37. package/dist/runtime/server/composables/useConfigDirectorClient.d.mts +0 -6
  38. package/dist/runtime/server/composables/useConfigDirectorClient.d.ts +0 -6
  39. package/dist/runtime/types-CHmsCChN.d.mts +0 -60
  40. /package/dist/runtime/{types-CkzBXJwA.d.mts → types-C30VJ25l.d.mts} +0 -0
@@ -1,34 +1,13 @@
1
+ import { t as admitContext } from "../context-kind-D6YBNij2.mjs";
2
+ import { a as ConfigDirectorValidationError, i as ConfigDirectorInitializationError, n as encodeNativeValue, r as ConfigDirectorConnectionError } from "../native-values-DZJjFBvk.mjs";
1
3
  import EventEmitter, { EventEmitter as EventEmitter$1 } from "node:events";
2
4
  import { createHash, randomUUID } from "node:crypto";
5
+ import { readFile } from "node:fs/promises";
6
+ import { resolve } from "node:path";
3
7
  import * as semver from "semver";
4
8
  import { rapidhash } from "rapidhash-js";
5
9
  import { defineNitroPlugin, useRuntimeConfig } from "nitropack/runtime";
6
10
  import { consola } from "consola";
7
- //#region ../shared/src/errors.ts
8
- var ConfigDirectorConnectionError = class ConfigDirectorConnectionError extends Error {
9
- name = "ConfigDirectorConnectionError";
10
- status;
11
- constructor(message, status) {
12
- super(message);
13
- this.status = status;
14
- Object.setPrototypeOf(this, ConfigDirectorConnectionError.prototype);
15
- }
16
- };
17
- var ConfigDirectorValidationError = class ConfigDirectorValidationError extends Error {
18
- name = "ConfigDirectorValidationError";
19
- constructor(message) {
20
- super(message);
21
- Object.setPrototypeOf(this, ConfigDirectorValidationError.prototype);
22
- }
23
- };
24
- var ConfigDirectorInitializationError = class ConfigDirectorInitializationError extends Error {
25
- name = "ConfigDirectorInitializationError";
26
- constructor(message) {
27
- super(message);
28
- Object.setPrototypeOf(this, ConfigDirectorInitializationError.prototype);
29
- }
30
- };
31
- //#endregion
32
11
  //#region ../eventsource/src/errors.ts
33
12
  var StreamClosedError = class StreamClosedError extends Error {
34
13
  name = "StreamClosedError";
@@ -635,6 +614,150 @@ var PollingTransport = class extends AbstractPollingTransport {
635
614
  }
636
615
  };
637
616
  //#endregion
617
+ //#region ../js-server-sdk/src/native-values.ts
618
+ const unconditionalConfig = (id, key, value) => {
619
+ const encoded = encodeNativeValue(key, value);
620
+ return {
621
+ id,
622
+ key,
623
+ type: encoded.type,
624
+ variations: [{
625
+ name: null,
626
+ value: encoded.value
627
+ }],
628
+ target: {
629
+ defaultValue: encoded.value,
630
+ rules: []
631
+ }
632
+ };
633
+ };
634
+ //#endregion
635
+ //#region ../js-server-sdk/src/OfflineTransport.ts
636
+ const OFFLINE_ENVIRONMENT = {
637
+ environmentId: "offline",
638
+ projectId: "offline"
639
+ };
640
+ var OfflineTransport = class {
641
+ emitter = new EventEmitter();
642
+ connected = false;
643
+ constructor(source) {
644
+ this.source = source;
645
+ }
646
+ async connect() {
647
+ const configs = await loadOfflineConfigs(this.source);
648
+ this.connected = true;
649
+ this.emitter.emit("configBundleReceived", {
650
+ ...OFFLINE_ENVIRONMENT,
651
+ kind: "full",
652
+ configs
653
+ });
654
+ return this;
655
+ }
656
+ get isConnected() {
657
+ return this.connected;
658
+ }
659
+ on(name, handler) {
660
+ this.emitter.on(name, handler);
661
+ }
662
+ off(name, handler) {
663
+ if (handler) this.emitter.off(name, handler);
664
+ else this.emitter.removeAllListeners(name);
665
+ }
666
+ close() {
667
+ this.connected = false;
668
+ }
669
+ dispose() {
670
+ this.close();
671
+ this.emitter.removeAllListeners();
672
+ }
673
+ };
674
+ const loadOfflineConfigs = async (source) => {
675
+ switch (source.kind) {
676
+ case "in-code-defaults": return {};
677
+ case "values": return encodeOfflineValues(source.values, "The offline values");
678
+ case "file": return encodeOfflineValues(await readOfflineValuesFile(source.path), `The offline values file '${source.path}'`);
679
+ case "rejected": throw new ConfigDirectorInitializationError(source.message);
680
+ }
681
+ };
682
+ const readOfflineValuesFile = async (path) => {
683
+ let text;
684
+ try {
685
+ text = await readFile(resolve(path), "utf8");
686
+ } catch (error) {
687
+ throw new ConfigDirectorInitializationError(`The offline values file '${path}' could not be read: ${messageOf(error)}`);
688
+ }
689
+ let parsed;
690
+ try {
691
+ parsed = JSON.parse(text);
692
+ } catch (error) {
693
+ throw new ConfigDirectorInitializationError(`The offline values file '${path}' is not valid JSON: ${messageOf(error)}`);
694
+ }
695
+ if (!isJsonObject(parsed)) throw new ConfigDirectorInitializationError(`The offline values file '${path}' must hold a JSON object with one value per config key, but holds ${describeJson(parsed)}.`);
696
+ return parsed;
697
+ };
698
+ const encodeOfflineValues = (values, subject) => {
699
+ try {
700
+ return Object.fromEntries(Object.entries(values).map(([key, value]) => [key, unconditionalConfig(`offline:${key}`, key, value)]));
701
+ } catch (error) {
702
+ if (error instanceof ConfigDirectorValidationError) throw new ConfigDirectorInitializationError(`${subject} could not be loaded. ${error.message}`);
703
+ throw error;
704
+ }
705
+ };
706
+ const isJsonObject = (parsed) => typeof parsed === "object" && parsed !== null && !Array.isArray(parsed);
707
+ const describeJson = (parsed) => {
708
+ if (parsed === null) return "null";
709
+ if (Array.isArray(parsed)) return "an array";
710
+ return `a ${typeof parsed}`;
711
+ };
712
+ const messageOf = (error) => error instanceof Error ? error.message : String(error);
713
+ //#endregion
714
+ //#region ../js-server-sdk/src/connection-plan.ts
715
+ const defaultBaseUrl = new URL("https://server-sdk-api.configdirector.com");
716
+ const planConnection = (serverSdkKey, connection) => {
717
+ if (connection?.mode === "offline") return {
718
+ kind: "stay-offline",
719
+ source: offlineSourceOf(connection)
720
+ };
721
+ const mode = connection?.mode ?? "streaming";
722
+ if (!serverSdkKey || serverSdkKey.trim().length == 0) throw new ConfigDirectorValidationError("No server SDK key was provided, the client cannot be instantiated without a valid server SDK key");
723
+ if (connection?.file !== void 0 || connection?.values !== void 0) return {
724
+ kind: "stay-offline",
725
+ source: {
726
+ kind: "rejected",
727
+ message: `The connection options 'file' and 'values' apply only when the connection mode is 'offline', but the mode is '${mode}'.`
728
+ }
729
+ };
730
+ return {
731
+ kind: "connect",
732
+ mode,
733
+ serverSdkKey,
734
+ baseUrl: baseUrlOf(connection?.url)
735
+ };
736
+ };
737
+ const baseUrlOf = (url) => {
738
+ if (!url) return defaultBaseUrl;
739
+ try {
740
+ return new URL(url);
741
+ } catch (error) {
742
+ throw new ConfigDirectorValidationError(`Invalid base URL '${url}'. Parsing failed: ${error}`);
743
+ }
744
+ };
745
+ const offlineSourceOf = (connection) => {
746
+ if (connection.file !== void 0 && connection.values !== void 0) return {
747
+ kind: "rejected",
748
+ message: "An offline connection takes either 'file' or 'values', not both."
749
+ };
750
+ if (connection.file !== void 0) return {
751
+ kind: "file",
752
+ path: connection.file
753
+ };
754
+ if (connection.values !== void 0) return {
755
+ kind: "values",
756
+ values: connection.values
757
+ };
758
+ return { kind: "in-code-defaults" };
759
+ };
760
+ //#endregion
638
761
  //#region ../config-evaluator/src/json-pointer.ts
639
762
  const ARRAY_INDEX_PATTERN = /^(?:0|[1-9][0-9]*)$/;
640
763
  const INVALID_ESCAPE_PATTERN = /~(?![01])/;
@@ -7017,6 +7140,7 @@ const compareArray = (givenValue, operator, targetValues) => {
7017
7140
  default: return false;
7018
7141
  }
7019
7142
  };
7143
+ const effectiveContextKind = (context) => context?.context?.kind ?? "user";
7020
7144
  //#endregion
7021
7145
  //#region ../config-evaluator/src/ConditionEvaluator.ts
7022
7146
  var ConditionEvaluator = class {
@@ -7065,6 +7189,7 @@ var ConditionEvaluator = class {
7065
7189
  switch (condition.attribute) {
7066
7190
  case "identifier": return orAbsent(context?.context?.id);
7067
7191
  case "name": return orAbsent(context?.context?.name);
7192
+ case "kind": return effectiveContextKind(context);
7068
7193
  case "appName": return orAbsent(context?.metadata?.appName);
7069
7194
  case "appVersion": return orAbsent(context?.metadata?.appVersion);
7070
7195
  case "traits":
@@ -7112,9 +7237,9 @@ const holdsAttributeConditionsOnly = (segment) => (segment.groups ?? []).every((
7112
7237
  //#endregion
7113
7238
  //#region ../config-evaluator/src/percent-hashing.ts
7114
7239
  const SEED = 15426857351n;
7115
- const assignPercentage = (data) => {
7116
- const value = `${data.contextIdentifier}-${data.configId}`;
7117
- return Number(rapidhash(value, { seed: SEED }) % 1000n) / 10;
7240
+ const assignPercentage = ({ configId, contextIdentifier, contextKind }) => {
7241
+ const hashInput = contextKind === "user" ? `${contextIdentifier}-${configId}` : `${contextIdentifier}-${configId}:${contextKind}`;
7242
+ return Number(rapidhash(hashInput, { seed: SEED }) % 1000n) / 10;
7118
7243
  };
7119
7244
  //#endregion
7120
7245
  //#region ../config-evaluator/src/ConfigEvaluator.ts
@@ -7208,7 +7333,8 @@ var ConfigEvaluator = class {
7208
7333
  const identifier = context?.context?.id ?? void 0;
7209
7334
  const assignedPercentage = identifier === void 0 ? 0 : assignPercentage({
7210
7335
  configId: config.id,
7211
- contextIdentifier: identifier
7336
+ contextIdentifier: identifier,
7337
+ contextKind: effectiveContextKind(context)
7212
7338
  });
7213
7339
  const shares = [];
7214
7340
  let sum = 0;
@@ -7511,6 +7637,59 @@ const resolvePollingInterval = (configuredSeconds, defaults) => {
7511
7637
  warning: `pollingInterval of ${configuredSeconds} seconds is below the minimum of ${defaults.minimumSeconds} seconds. Using ${defaults.minimumSeconds} seconds.`
7512
7638
  };
7513
7639
  };
7640
+ var InvalidContextSubmissions = class {
7641
+ countsByKindSample = /* @__PURE__ */ new Map();
7642
+ countBeyondSampleLimit = 0;
7643
+ startTime;
7644
+ record(kindSample) {
7645
+ this.startTime ??= /* @__PURE__ */ new Date();
7646
+ return this.add(kindSample, 1);
7647
+ }
7648
+ absorb(entries) {
7649
+ for (const entry of entries) {
7650
+ if (this.startTime === void 0 || entry.startTime < this.startTime) this.startTime = entry.startTime;
7651
+ this.add(entry.event.kindSample, entry.count);
7652
+ }
7653
+ }
7654
+ add(kindSample, count) {
7655
+ if (kindSample !== void 0) {
7656
+ const knownCount = this.countsByKindSample.get(kindSample);
7657
+ if (knownCount !== void 0) {
7658
+ this.countsByKindSample.set(kindSample, knownCount + count);
7659
+ return "known-kind-sample";
7660
+ }
7661
+ if (this.countsByKindSample.size < 20) {
7662
+ this.countsByKindSample.set(kindSample, count);
7663
+ return "new-kind-sample";
7664
+ }
7665
+ }
7666
+ const firstBeyondSampleLimit = this.countBeyondSampleLimit === 0;
7667
+ this.countBeyondSampleLimit += count;
7668
+ return firstBeyondSampleLimit ? "first-beyond-sample-limit" : "beyond-sample-limit";
7669
+ }
7670
+ takeSnapshot() {
7671
+ const endTime = /* @__PURE__ */ new Date();
7672
+ const startTime = this.startTime ?? endTime;
7673
+ const entryOf = (count, kindSample) => ({
7674
+ startTime,
7675
+ endTime,
7676
+ count,
7677
+ event: {
7678
+ reason: "invalid-context-kind",
7679
+ kindSample
7680
+ }
7681
+ });
7682
+ const sampledEntries = Array.from(this.countsByKindSample, ([kindSample, count]) => entryOf(count, kindSample));
7683
+ const entries = this.countBeyondSampleLimit > 0 ? [...sampledEntries, entryOf(this.countBeyondSampleLimit)] : sampledEntries;
7684
+ this.clear();
7685
+ return entries;
7686
+ }
7687
+ clear() {
7688
+ this.countsByKindSample.clear();
7689
+ this.countBeyondSampleLimit = 0;
7690
+ this.startTime = void 0;
7691
+ }
7692
+ };
7514
7693
  //#endregion
7515
7694
  //#region ../shared/src/telemetry/utils.ts
7516
7695
  const CONFIG_VALUE_MAX_LENGTH = 500;
@@ -7821,6 +8000,7 @@ var ServerTelemetryEventCollector = class extends TelemetryEventCollector {
7821
8000
  reporter;
7822
8001
  _context;
7823
8002
  contexts;
8003
+ invalidContextSubmissions = new InvalidContextSubmissions();
7824
8004
  constructor(options) {
7825
8005
  super(options);
7826
8006
  this.reporter = new ServerEventReporter(options);
@@ -7829,13 +8009,23 @@ var ServerTelemetryEventCollector = class extends TelemetryEventCollector {
7829
8009
  }
7830
8010
  evaluatedConfig(payload) {
7831
8011
  if (!this.collectEvents) return;
7832
- if (payload.context?.id && !payload.context.anonymous) this.contexts.set(payload.context.id, payload.context);
8012
+ const context = payload.context ?? void 0;
8013
+ const contextKind = effectiveContextKind({ context });
8014
+ const contextId = context?.anonymous ? void 0 : context?.id;
8015
+ if (contextId) this.contexts.set(`${contextKind}:${contextId}`, {
8016
+ ...context,
8017
+ kind: contextKind
8018
+ });
7833
8019
  const event = {
7834
8020
  ...payload.evaluation,
7835
- contextId: payload.context?.anonymous ? void 0 : payload.context?.id
8021
+ contextId,
8022
+ contextKind: contextId === void 0 ? void 0 : contextKind
7836
8023
  };
7837
8024
  this.evaluationEventQueue.push(this.sanitizeEvaluatedConfigEvent(event));
7838
8025
  }
8026
+ recordInvalidContext(kindSample) {
8027
+ return this.invalidContextSubmissions.record(kindSample);
8028
+ }
7839
8029
  sanitizeEvaluatedConfigEvent(event) {
7840
8030
  return {
7841
8031
  ...event,
@@ -7869,7 +8059,10 @@ var ServerTelemetryEventCollector = class extends TelemetryEventCollector {
7869
8059
  return await this.reporter?.report({
7870
8060
  context: this._context,
7871
8061
  discreteEvents: { capturedContexts: contextsSnapshot.contexts },
7872
- aggregatedEvents: { evaluatedConfig: this.aggregator.aggregate(evaluationSnapshot) },
8062
+ aggregatedEvents: {
8063
+ evaluatedConfig: this.aggregator.aggregate(evaluationSnapshot),
8064
+ invalidContext: this.invalidContextSubmissions.takeSnapshot()
8065
+ },
7873
8066
  droppedEvents: {
7874
8067
  evaluatedConfig: evaluationSnapshot.droppedCount,
7875
8068
  capturedContexts: contextsSnapshot.droppedCount
@@ -7878,6 +8071,14 @@ var ServerTelemetryEventCollector = class extends TelemetryEventCollector {
7878
8071
  }
7879
8072
  };
7880
8073
  //#endregion
8074
+ //#region ../js-server-sdk/src/telemetry/discarding.ts
8075
+ const discardingTelemetryClient = {
8076
+ start: () => {},
8077
+ evaluatedConfig: () => {},
8078
+ recordInvalidContext: () => "new-kind-sample",
8079
+ close: async () => {}
8080
+ };
8081
+ //#endregion
7881
8082
  //#region ../shared/src/base62-encoder.ts
7882
8083
  const BASE62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
7883
8084
  const toBase62 = (buf, length) => {
@@ -7900,11 +8101,20 @@ const generateValueId = async (v) => {
7900
8101
  };
7901
8102
  //#endregion
7902
8103
  //#region ../js-server-sdk/src/DefaultConfigDirectorClient.ts
7903
- const defaultBaseUrl = new URL("https://server-sdk-api.configdirector.com");
7904
8104
  const DEFAULT_FLUSH_INTERVAL = 3e4;
7905
8105
  const DEFAULT_EVENT_QUEUE_LIMIT = 5e3;
8106
+ const liveTimeoutWarningDetails = {
8107
+ streaming: "The client will continue to retry since there were no fatal errors detected. Configs will return the default value until the connection succeeds.",
8108
+ polling: "Since the client was configured without streaming, configs may not update and always return the default value."
8109
+ };
8110
+ const logInvalidContext = {
8111
+ "new-kind-sample": (logger, kindSample) => logger.error(`[ConfigDirectorClient] Ignored a context with the invalid kind '${kindSample}' and evaluated as if no context was supplied. A context kind has 1 to 64 characters, using only lowercase letters, digits, '-' and '_'.`),
8112
+ "known-kind-sample": () => {},
8113
+ "first-beyond-sample-limit": (logger) => logger.error(`[ConfigDirectorClient] Ignored a context with another invalid kind and evaluated as if no context was supplied. 20 different invalid kinds were logged recently, so further invalid kinds are not logged individually.`),
8114
+ "beyond-sample-limit": () => {}
8115
+ };
7906
8116
  const usesAnySegment = (definition, segmentIds) => (definition.target?.rules ?? []).some((rule) => rule.type === "conditional" && (rule.conditions ?? []).some((condition) => condition.kind === "segment" && segmentIds.has(condition.segmentId)));
7907
- var DefaultConfigDirectorClient = class {
8117
+ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
7908
8118
  logger;
7909
8119
  usageEventCollector;
7910
8120
  configSet;
@@ -7916,19 +8126,21 @@ var DefaultConfigDirectorClient = class {
7916
8126
  readyPromise;
7917
8127
  readyResolve;
7918
8128
  readyTimer;
7919
- connectionMode;
8129
+ connectionPlan;
7920
8130
  configEvaluator;
7921
8131
  metaContext;
8132
+ static from(serverSdkKeyOrOptions, sdkOptions, clientOptions) {
8133
+ if (typeof serverSdkKeyOrOptions === "object") return new DefaultConfigDirectorClient(void 0, sdkOptions, serverSdkKeyOrOptions);
8134
+ return new DefaultConfigDirectorClient(serverSdkKeyOrOptions, sdkOptions, clientOptions);
8135
+ }
7922
8136
  constructor(serverSdkKey, sdkOptions, clientOptions, internalClientOptions) {
7923
8137
  this.logger = clientOptions?.logger ?? createDefaultLogger$1();
7924
8138
  this.timeout = clientOptions?.connection?.timeout ?? 3e3;
7925
8139
  this.metaContext = clientOptions?.metadata ?? {};
7926
- this.validateSdkKeyPresence(serverSdkKey);
8140
+ this.connectionPlan = planConnection(serverSdkKey, clientOptions?.connection);
7927
8141
  this.configEvaluator = new ConfigEvaluator(this.logger);
7928
- const baseUrl = this.parseUrl(clientOptions?.connection?.url) ?? defaultBaseUrl;
7929
- this.connectionMode = clientOptions?.connection?.mode ?? "streaming";
7930
- this.usageEventCollector = internalClientOptions?.telemetry ?? this.buildTelemetryCollector(serverSdkKey, sdkOptions, baseUrl, clientOptions);
7931
- this.transport = internalClientOptions?.transport ?? this.buildTransport(serverSdkKey, sdkOptions, baseUrl, clientOptions);
8142
+ this.usageEventCollector = internalClientOptions?.telemetry ?? this.buildTelemetryClient(sdkOptions, clientOptions);
8143
+ this.transport = internalClientOptions?.transport ?? this.buildTransport(sdkOptions, clientOptions);
7932
8144
  this.transport.on("configBundleReceived", (configBundle) => {
7933
8145
  this.readyResolve?.(false);
7934
8146
  const removedKeys = this.findKeysRemovedBy(configBundle);
@@ -7990,16 +8202,20 @@ var DefaultConfigDirectorClient = class {
7990
8202
  })]);
7991
8203
  this.clearReadyTimer();
7992
8204
  }
7993
- if (!this.ready) {
7994
- 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.";
7995
- this.logger.warn(`[ConfigDirectorClient] Timed out waiting for initialization after ${this.timeout}ms. ${warningDetails}`);
7996
- }
8205
+ if (!this.ready) this.logger.warn(`[ConfigDirectorClient] Timed out waiting for initialization after ${this.timeout}ms. ${this.timeoutWarningDetails()}`);
7997
8206
  } catch (error) {
8207
+ if (error instanceof ConfigDirectorInitializationError) throw error;
7998
8208
  this.logger.error("[ConfigDirectorClient] An error occurred during initialization: ", error);
7999
8209
  }
8000
8210
  }
8001
- resolvePollingIntervalForMode(configuredSeconds) {
8002
- if (this.connectionMode !== "polling") return;
8211
+ timeoutWarningDetails() {
8212
+ switch (this.connectionPlan.kind) {
8213
+ case "connect": return liveTimeoutWarningDetails[this.connectionPlan.mode];
8214
+ case "stay-offline": return "The offline values were not delivered, so configs return the default value.";
8215
+ }
8216
+ }
8217
+ pollingIntervalFor(mode, configuredSeconds) {
8218
+ if (mode !== "polling") return;
8003
8219
  const resolved = resolvePollingInterval(configuredSeconds, {
8004
8220
  defaultSeconds: 300,
8005
8221
  minimumSeconds: 60
@@ -8007,38 +8223,48 @@ var DefaultConfigDirectorClient = class {
8007
8223
  if (resolved.warning) this.logger.warn(`[ConfigDirectorClient] ${resolved.warning}`);
8008
8224
  return resolved.seconds;
8009
8225
  }
8010
- buildTelemetryCollector(serverSdkKey, sdkOptions, baseUrl, clientOptions) {
8011
- const queueLimit = clientOptions?.telemetry?.eventQueueLimit ?? DEFAULT_EVENT_QUEUE_LIMIT;
8012
- return new ServerTelemetryEventCollector({
8013
- sdkKey: serverSdkKey,
8014
- sdkIdentity: sdkOptions,
8015
- metaContext: clientOptions?.metadata ?? {},
8016
- logger: this.logger,
8017
- baseUrl,
8018
- flushIntervalDelay: clientOptions?.telemetry?.flushInterval ?? DEFAULT_FLUSH_INTERVAL,
8019
- evaluationQueueLimit: Math.ceil(queueLimit * .7),
8020
- contextLimit: Math.floor(queueLimit * .3),
8021
- urlFactory: defaultUrlFactory,
8022
- valueIdGenerator: generateValueId
8023
- });
8226
+ buildTelemetryClient(sdkOptions, clientOptions) {
8227
+ const plan = this.connectionPlan;
8228
+ switch (plan.kind) {
8229
+ case "stay-offline": return discardingTelemetryClient;
8230
+ case "connect": {
8231
+ const queueLimit = clientOptions?.telemetry?.eventQueueLimit ?? DEFAULT_EVENT_QUEUE_LIMIT;
8232
+ return new ServerTelemetryEventCollector({
8233
+ sdkKey: plan.serverSdkKey,
8234
+ sdkIdentity: sdkOptions,
8235
+ metaContext: clientOptions?.metadata ?? {},
8236
+ logger: this.logger,
8237
+ baseUrl: plan.baseUrl,
8238
+ flushIntervalDelay: clientOptions?.telemetry?.flushInterval ?? DEFAULT_FLUSH_INTERVAL,
8239
+ evaluationQueueLimit: Math.ceil(queueLimit * .7),
8240
+ contextLimit: Math.floor(queueLimit * .3),
8241
+ urlFactory: defaultUrlFactory,
8242
+ valueIdGenerator: generateValueId
8243
+ });
8244
+ }
8245
+ }
8024
8246
  }
8025
- buildTransport(serverSdkKey, sdkOptions, baseUrl, clientOptions) {
8026
- return new (this.getTransportConstructor(this.connectionMode))({
8027
- serverSdkKey,
8028
- baseUrl,
8029
- metaContext: {
8030
- ...clientOptions?.metadata,
8031
- sdkName: sdkOptions.sdkName,
8032
- sdkVersion: sdkOptions.sdkVersion
8033
- },
8034
- logger: this.logger,
8035
- pollingInterval: this.resolvePollingIntervalForMode(clientOptions?.connection?.pollingInterval)
8036
- });
8247
+ buildTransport(sdkOptions, clientOptions) {
8248
+ const plan = this.connectionPlan;
8249
+ switch (plan.kind) {
8250
+ case "stay-offline": return new OfflineTransport(plan.source);
8251
+ case "connect": return new (this.getTransportConstructor(plan.mode))({
8252
+ serverSdkKey: plan.serverSdkKey,
8253
+ baseUrl: plan.baseUrl,
8254
+ metaContext: {
8255
+ ...clientOptions?.metadata,
8256
+ sdkName: sdkOptions.sdkName,
8257
+ sdkVersion: sdkOptions.sdkVersion
8258
+ },
8259
+ logger: this.logger,
8260
+ pollingInterval: this.pollingIntervalFor(plan.mode, clientOptions?.connection?.pollingInterval)
8261
+ });
8262
+ }
8037
8263
  }
8038
8264
  getTransportConstructor(mode) {
8039
8265
  switch (mode) {
8040
8266
  case "polling": return PollingTransport;
8041
- default: return StreamingTransport;
8267
+ case "streaming": return StreamingTransport;
8042
8268
  }
8043
8269
  }
8044
8270
  findKeysRemovedBy(configBundle) {
@@ -8086,7 +8312,8 @@ var DefaultConfigDirectorClient = class {
8086
8312
  const configDefinition = this.configSet?.configs[configKey];
8087
8313
  return this.getValueFromConfigDefinition(configKey, configDefinition, defaultValue, context);
8088
8314
  }
8089
- getValueFromConfigDefinition(configKey, configDefinition, defaultValue, context) {
8315
+ getValueFromConfigDefinition(configKey, configDefinition, defaultValue, suppliedContext) {
8316
+ const context = this.contextToEvaluate(suppliedContext);
8090
8317
  if (configDefinition) {
8091
8318
  const configState = this.configEvaluator.evaluate(configDefinition, {
8092
8319
  context,
@@ -8140,19 +8367,17 @@ var DefaultConfigDirectorClient = class {
8140
8367
  return defaultValue;
8141
8368
  }
8142
8369
  }
8143
- dispatchEvaluationEvent(event) {
8144
- setTimeout(() => this.eventEmitter.emit("configEvaluated", { evaluation: event }), 0);
8145
- }
8146
- parseUrl(url) {
8147
- if (!url) return;
8148
- try {
8149
- return new URL(url);
8150
- } catch (error) {
8151
- throw new ConfigDirectorValidationError(`Invalid base URL '${url}'. Parsing failed: ${error}`);
8370
+ contextToEvaluate(suppliedContext) {
8371
+ const admission = admitContext(suppliedContext);
8372
+ switch (admission.outcome) {
8373
+ case "admitted": return admission.context;
8374
+ case "ignored":
8375
+ logInvalidContext[this.usageEventCollector.recordInvalidContext(admission.kindSample)](this.logger, admission.kindSample);
8376
+ return;
8152
8377
  }
8153
8378
  }
8154
- validateSdkKeyPresence(sdkKey) {
8155
- if (!sdkKey || sdkKey.trim().length == 0) throw new ConfigDirectorValidationError("No server SDK key was provided, the client cannot be instantiated without a valid server SDK key");
8379
+ dispatchEvaluationEvent(event) {
8380
+ setTimeout(() => this.eventEmitter.emit("configEvaluated", { evaluation: event }), 0);
8156
8381
  }
8157
8382
  validateDefaultValue(defaultValue) {
8158
8383
  if (defaultValue === void 0 || defaultValue === null) throw new ConfigDirectorValidationError("Invalid default value. The default value for a config must be defined and non-null.");
@@ -8177,11 +8402,12 @@ var DefaultConfigDirectorClient = class {
8177
8402
  this.handlersMap.clear();
8178
8403
  }
8179
8404
  getAllConfigs(options) {
8405
+ const context = this.contextToEvaluate(options?.context);
8180
8406
  if (!this.configSet) return {};
8181
8407
  const entries = Object.entries(this.configSet.configs);
8182
8408
  const filtered = options?.configKeys ? entries.filter(([key]) => options.configKeys?.includes(key)) : entries;
8183
8409
  return Object.fromEntries(filtered.map(([key, config]) => [key, this.configEvaluator.evaluate(config, {
8184
- context: options?.context,
8410
+ context,
8185
8411
  metadata: this.metaContext
8186
8412
  }, this.configSet?.segments)]));
8187
8413
  }
@@ -8216,27 +8442,38 @@ const createDefaultLogger = (level) => {
8216
8442
  var plugin_default = defineNitroPlugin((nitroApp) => {
8217
8443
  const runtimeConfig = useRuntimeConfig();
8218
8444
  const logger = createDefaultLogger(runtimeConfig.configdirector?.logLevel);
8219
- if (!runtimeConfig.configdirector.serverSdkKey) {
8445
+ const { serverSdkKey, baseUrl, connection } = runtimeConfig.configdirector;
8446
+ const connectionMode = connection?.mode || "streaming";
8447
+ if (connectionMode !== "offline" && !serverSdkKey) {
8220
8448
  const message = "The ConfigDirector serverSdkKey must be configured for the plugin to initialize. You can provide it in nuxt.config.ts under 'runtimeConfig.configdirector.serverSdkKey' or as a runtime environment variable named NUXT_CONFIGDIRECTOR_SERVER_SDK_KEY";
8221
8449
  logger.error(message);
8222
8450
  throw new ConfigDirectorInitializationError(message);
8223
8451
  }
8224
8452
  logger.debug("Initializing ConfigDirector Nitro plugin");
8225
- const { serverSdkKey, baseUrl, connection } = runtimeConfig.configdirector;
8226
- const client = new DefaultConfigDirectorClient(serverSdkKey, {
8453
+ const client = new DefaultConfigDirectorClient(serverSdkKey || void 0, {
8227
8454
  sdkName: "nuxt-sdk",
8228
- sdkVersion: "1.9.2"
8455
+ sdkVersion: "1.11.0"
8229
8456
  }, {
8230
8457
  logger,
8231
8458
  connection: {
8232
8459
  url: baseUrl || void 0,
8233
- mode: connection?.mode || void 0,
8460
+ mode: connectionMode,
8461
+ file: connection?.file || void 0,
8462
+ values: connection?.values,
8234
8463
  pollingInterval: connection?.pollingInterval || void 0,
8235
8464
  timeout: connection?.timeout || void 0
8236
8465
  }
8237
8466
  });
8467
+ const initialization = client.initialize();
8468
+ initialization.catch((error) => {
8469
+ logger.error("The ConfigDirector server client could not initialize, so every request fails:", error);
8470
+ });
8238
8471
  nitroApp.configDirectorClient = client;
8239
- nitroApp.configDirectorInitialization = client.initialize();
8472
+ nitroApp.configDirectorConnectionMode = connectionMode;
8473
+ nitroApp.configDirectorInitialization = initialization;
8474
+ nitroApp.hooks.hook("request", (event) => {
8475
+ useRuntimeConfig(event).public.configdirector.serverConnectionMode = connectionMode;
8476
+ });
8240
8477
  });
8241
8478
  //#endregion
8242
8479
  export { plugin_default as default };