@configdirector/nuxt-sdk 1.9.2 → 1.10.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 (29) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/runtime/app/composables/useConfigDirectorClient.d.mts +1 -1
  3. package/dist/runtime/app/composables/useConfigDirectorClient.d.ts +1 -1
  4. package/dist/runtime/app/composables/useConfigDirectorClientHooks.d.mts +1 -1
  5. package/dist/runtime/app/composables/useConfigDirectorClientHooks.d.ts +1 -1
  6. package/dist/runtime/app/composables/useConfigDirectorContext.d.mts +1 -1
  7. package/dist/runtime/app/composables/useConfigDirectorContext.d.ts +1 -1
  8. package/dist/runtime/app/composables/useConfigDirectorContext.mjs +2 -1
  9. package/dist/runtime/app/composables/useConfigDirectorStatus.d.mts +1 -1
  10. package/dist/runtime/app/composables/useConfigDirectorStatus.d.ts +1 -1
  11. package/dist/runtime/app/composables/useConfigDirectorValue.d.mts +2 -2
  12. package/dist/runtime/app/composables/useConfigDirectorValue.d.ts +2 -2
  13. package/dist/runtime/context-kind-D6YBNij2.mjs +45 -0
  14. package/dist/runtime/nitro/plugin.d.mts +1 -1
  15. package/dist/runtime/nitro/plugin.d.ts +1 -1
  16. package/dist/runtime/nitro/plugin.mjs +98 -10
  17. package/dist/runtime/plugin.client.mjs +218 -38
  18. package/dist/runtime/plugin.server.mjs +50 -6
  19. package/dist/runtime/server/composables/useConfigDirectorClient.d.mts +1 -1
  20. package/dist/runtime/server/composables/useConfigDirectorClient.d.ts +1 -1
  21. package/dist/runtime/server/composables/useConfigDirectorServerHooks.d.mts +1 -1
  22. package/dist/runtime/server/composables/useConfigDirectorServerHooks.d.ts +1 -1
  23. package/dist/runtime/{types-Bgx2-3dY.d.mts → types-B9_4pADA.d.mts} +1 -1
  24. package/dist/runtime/{types-CvmjergQ.d.mts → types-DQhBe4Ei.d.mts} +13 -5
  25. package/dist/runtime/{types-CHmsCChN.d.mts → types-IPLvRsUO.d.mts} +22 -9
  26. package/dist/testing.d.mts +36 -14
  27. package/dist/testing.mjs +144 -36
  28. package/package.json +1 -1
  29. /package/dist/runtime/{types-CkzBXJwA.d.mts → types-F0fKKk6-.d.mts} +0 -0
package/dist/module.json CHANGED
@@ -4,5 +4,5 @@
4
4
  "compatibility": {
5
5
  "nuxt": ">=4.0.0"
6
6
  },
7
- "version": "1.9.2"
7
+ "version": "1.10.0"
8
8
  }
@@ -1,4 +1,4 @@
1
- import { n as ConfigDirectorClient } from "../../types-CvmjergQ.mjs";
1
+ import { n as ConfigDirectorClient } from "../../types-DQhBe4Ei.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-CvmjergQ.mjs";
1
+ import { n as ConfigDirectorClient } from "../../types-DQhBe4Ei.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-CvmjergQ.mjs";
1
+ import { t as ClientHooks } from "../../types-DQhBe4Ei.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-CvmjergQ.mjs";
1
+ import { t as ClientHooks } from "../../types-DQhBe4Ei.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 ConfigDirectorContext } from "../../types-CHmsCChN.mjs";
1
+ import { t as ConfigDirectorContext } from "../../types-IPLvRsUO.mjs";
2
2
  import { Ref } from "vue";
3
3
 
4
4
  //#region src/runtime/app/composables/useConfigDirectorContext.d.ts
@@ -1,4 +1,4 @@
1
- import { t as ConfigDirectorContext } from "../../types-CHmsCChN.mjs";
1
+ import { t as ConfigDirectorContext } from "../../types-IPLvRsUO.mjs";
2
2
  import { Ref } from "vue";
3
3
 
4
4
  //#region src/runtime/app/composables/useConfigDirectorContext.d.ts
@@ -1,4 +1,5 @@
1
1
  import { useConfigDirectorClient } from "./useConfigDirectorClient.mjs";
2
+ import { n as admittedContextOrNone } from "../../context-kind-D6YBNij2.mjs";
2
3
  import { useState } from "#app";
3
4
  import { readonly, toRaw } from "vue";
4
5
  //#region src/runtime/app/composables/useConfigDirectorContext.ts
@@ -13,7 +14,7 @@ const useConfigDirectorContext = () => {
13
14
  const rawContext = toRaw(context);
14
15
  const oldContext = JSON.stringify(state.value);
15
16
  const newContext = JSON.stringify(rawContext);
16
- state.value = rawContext;
17
+ state.value = admittedContextOrNone(rawContext);
17
18
  if (oldContext != newContext) {
18
19
  const timeout = getEffectiveTimeout(options?.timeoutMilliseconds);
19
20
  let timer;
@@ -1,4 +1,4 @@
1
- import { t as ClientStatus } from "../../types-CkzBXJwA.mjs";
1
+ import { t as ClientStatus } from "../../types-F0fKKk6-.mjs";
2
2
  import { Ref, ShallowRef } from "vue";
3
3
 
4
4
  //#region src/runtime/app/composables/useConfigDirectorStatus.d.ts
@@ -1,4 +1,4 @@
1
- import { t as ClientStatus } from "../../types-CkzBXJwA.mjs";
1
+ import { t as ClientStatus } from "../../types-F0fKKk6-.mjs";
2
2
  import { Ref, ShallowRef } from "vue";
3
3
 
4
4
  //#region src/runtime/app/composables/useConfigDirectorStatus.d.ts
@@ -1,5 +1,5 @@
1
- import { i as ConfigValueType } from "../../types-CHmsCChN.mjs";
2
- import { t as ClientStatus } from "../../types-CkzBXJwA.mjs";
1
+ import { i as ConfigValueType } from "../../types-IPLvRsUO.mjs";
2
+ import { t as ClientStatus } from "../../types-F0fKKk6-.mjs";
3
3
  import { DeepReadonly, Ref, ShallowRef } from "vue";
4
4
 
5
5
  //#region src/runtime/app/composables/useConfigDirectorValue.d.ts
@@ -1,5 +1,5 @@
1
- import { i as ConfigValueType } from "../../types-CHmsCChN.mjs";
2
- import { t as ClientStatus } from "../../types-CkzBXJwA.mjs";
1
+ import { i as ConfigValueType } from "../../types-IPLvRsUO.mjs";
2
+ import { t as ClientStatus } from "../../types-F0fKKk6-.mjs";
3
3
  import { DeepReadonly, Ref, ShallowRef } from "vue";
4
4
 
5
5
  //#region src/runtime/app/composables/useConfigDirectorValue.d.ts
@@ -0,0 +1,45 @@
1
+ //#region ../shared/src/context-kind.ts
2
+ const CONTEXT_KIND_MAXIMUM_LENGTH = 64;
3
+ const KIND_SAMPLE_MAXIMUM_CHARACTERS = 128;
4
+ const isContextKindCharacter = (character) => character >= "a" && character <= "z" || character >= "0" && character <= "9" || character === "-" || character === "_";
5
+ const isValidContextKind = (kind) => {
6
+ if (kind.length < 1 || kind.length > CONTEXT_KIND_MAXIMUM_LENGTH) return false;
7
+ for (const character of kind) if (!isContextKindCharacter(character)) return false;
8
+ return true;
9
+ };
10
+ const isAcceptedKind = (kind) => kind === void 0 || kind === null || typeof kind === "string" && isValidContextKind(kind);
11
+ const stringFormOf = (kind) => {
12
+ if (typeof kind === "string") return kind;
13
+ try {
14
+ return String(kind);
15
+ } catch {
16
+ return Object.prototype.toString.call(kind);
17
+ }
18
+ };
19
+ const firstCharactersOf = (text, maximumCharacters) => {
20
+ let sample = "";
21
+ let characterCount = 0;
22
+ for (const character of text) {
23
+ if (characterCount === maximumCharacters) break;
24
+ sample += character;
25
+ characterCount += 1;
26
+ }
27
+ return sample;
28
+ };
29
+ const admittedContextOrNone = (context) => {
30
+ const admission = admitContext(context);
31
+ return admission.outcome === "admitted" ? admission.context : void 0;
32
+ };
33
+ const admitContext = (context) => {
34
+ const kind = context?.kind;
35
+ if (isAcceptedKind(kind)) return {
36
+ outcome: "admitted",
37
+ context
38
+ };
39
+ return {
40
+ outcome: "ignored",
41
+ kindSample: firstCharactersOf(stringFormOf(kind), KIND_SAMPLE_MAXIMUM_CHARACTERS)
42
+ };
43
+ };
44
+ //#endregion
45
+ export { admittedContextOrNone as n, admitContext as t };
@@ -1,4 +1,4 @@
1
- import { n as ConfigDirectorClient } from "../types-Bgx2-3dY.mjs";
1
+ import { n as ConfigDirectorClient } from "../types-B9_4pADA.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-Bgx2-3dY.mjs";
1
+ import { n as ConfigDirectorClient } from "../types-B9_4pADA.mjs";
2
2
  import * as _$nitropack from "nitropack";
3
3
 
4
4
  //#region src/runtime/nitro/plugin.d.ts
@@ -1,3 +1,4 @@
1
+ import { t as admitContext } from "../context-kind-D6YBNij2.mjs";
1
2
  import EventEmitter, { EventEmitter as EventEmitter$1 } from "node:events";
2
3
  import { createHash, randomUUID } from "node:crypto";
3
4
  import * as semver from "semver";
@@ -7017,6 +7018,7 @@ const compareArray = (givenValue, operator, targetValues) => {
7017
7018
  default: return false;
7018
7019
  }
7019
7020
  };
7021
+ const effectiveContextKind = (context) => context?.context?.kind ?? "user";
7020
7022
  //#endregion
7021
7023
  //#region ../config-evaluator/src/ConditionEvaluator.ts
7022
7024
  var ConditionEvaluator = class {
@@ -7065,6 +7067,7 @@ var ConditionEvaluator = class {
7065
7067
  switch (condition.attribute) {
7066
7068
  case "identifier": return orAbsent(context?.context?.id);
7067
7069
  case "name": return orAbsent(context?.context?.name);
7070
+ case "kind": return effectiveContextKind(context);
7068
7071
  case "appName": return orAbsent(context?.metadata?.appName);
7069
7072
  case "appVersion": return orAbsent(context?.metadata?.appVersion);
7070
7073
  case "traits":
@@ -7112,9 +7115,9 @@ const holdsAttributeConditionsOnly = (segment) => (segment.groups ?? []).every((
7112
7115
  //#endregion
7113
7116
  //#region ../config-evaluator/src/percent-hashing.ts
7114
7117
  const SEED = 15426857351n;
7115
- const assignPercentage = (data) => {
7116
- const value = `${data.contextIdentifier}-${data.configId}`;
7117
- return Number(rapidhash(value, { seed: SEED }) % 1000n) / 10;
7118
+ const assignPercentage = ({ configId, contextIdentifier, contextKind }) => {
7119
+ const hashInput = contextKind === "user" ? `${contextIdentifier}-${configId}` : `${contextIdentifier}-${configId}:${contextKind}`;
7120
+ return Number(rapidhash(hashInput, { seed: SEED }) % 1000n) / 10;
7118
7121
  };
7119
7122
  //#endregion
7120
7123
  //#region ../config-evaluator/src/ConfigEvaluator.ts
@@ -7208,7 +7211,8 @@ var ConfigEvaluator = class {
7208
7211
  const identifier = context?.context?.id ?? void 0;
7209
7212
  const assignedPercentage = identifier === void 0 ? 0 : assignPercentage({
7210
7213
  configId: config.id,
7211
- contextIdentifier: identifier
7214
+ contextIdentifier: identifier,
7215
+ contextKind: effectiveContextKind(context)
7212
7216
  });
7213
7217
  const shares = [];
7214
7218
  let sum = 0;
@@ -7511,6 +7515,59 @@ const resolvePollingInterval = (configuredSeconds, defaults) => {
7511
7515
  warning: `pollingInterval of ${configuredSeconds} seconds is below the minimum of ${defaults.minimumSeconds} seconds. Using ${defaults.minimumSeconds} seconds.`
7512
7516
  };
7513
7517
  };
7518
+ var InvalidContextSubmissions = class {
7519
+ countsByKindSample = /* @__PURE__ */ new Map();
7520
+ countBeyondSampleLimit = 0;
7521
+ startTime;
7522
+ record(kindSample) {
7523
+ this.startTime ??= /* @__PURE__ */ new Date();
7524
+ return this.add(kindSample, 1);
7525
+ }
7526
+ absorb(entries) {
7527
+ for (const entry of entries) {
7528
+ if (this.startTime === void 0 || entry.startTime < this.startTime) this.startTime = entry.startTime;
7529
+ this.add(entry.event.kindSample, entry.count);
7530
+ }
7531
+ }
7532
+ add(kindSample, count) {
7533
+ if (kindSample !== void 0) {
7534
+ const knownCount = this.countsByKindSample.get(kindSample);
7535
+ if (knownCount !== void 0) {
7536
+ this.countsByKindSample.set(kindSample, knownCount + count);
7537
+ return "known-kind-sample";
7538
+ }
7539
+ if (this.countsByKindSample.size < 20) {
7540
+ this.countsByKindSample.set(kindSample, count);
7541
+ return "new-kind-sample";
7542
+ }
7543
+ }
7544
+ const firstBeyondSampleLimit = this.countBeyondSampleLimit === 0;
7545
+ this.countBeyondSampleLimit += count;
7546
+ return firstBeyondSampleLimit ? "first-beyond-sample-limit" : "beyond-sample-limit";
7547
+ }
7548
+ takeSnapshot() {
7549
+ const endTime = /* @__PURE__ */ new Date();
7550
+ const startTime = this.startTime ?? endTime;
7551
+ const entryOf = (count, kindSample) => ({
7552
+ startTime,
7553
+ endTime,
7554
+ count,
7555
+ event: {
7556
+ reason: "invalid-context-kind",
7557
+ kindSample
7558
+ }
7559
+ });
7560
+ const sampledEntries = Array.from(this.countsByKindSample, ([kindSample, count]) => entryOf(count, kindSample));
7561
+ const entries = this.countBeyondSampleLimit > 0 ? [...sampledEntries, entryOf(this.countBeyondSampleLimit)] : sampledEntries;
7562
+ this.clear();
7563
+ return entries;
7564
+ }
7565
+ clear() {
7566
+ this.countsByKindSample.clear();
7567
+ this.countBeyondSampleLimit = 0;
7568
+ this.startTime = void 0;
7569
+ }
7570
+ };
7514
7571
  //#endregion
7515
7572
  //#region ../shared/src/telemetry/utils.ts
7516
7573
  const CONFIG_VALUE_MAX_LENGTH = 500;
@@ -7821,6 +7878,7 @@ var ServerTelemetryEventCollector = class extends TelemetryEventCollector {
7821
7878
  reporter;
7822
7879
  _context;
7823
7880
  contexts;
7881
+ invalidContextSubmissions = new InvalidContextSubmissions();
7824
7882
  constructor(options) {
7825
7883
  super(options);
7826
7884
  this.reporter = new ServerEventReporter(options);
@@ -7829,13 +7887,23 @@ var ServerTelemetryEventCollector = class extends TelemetryEventCollector {
7829
7887
  }
7830
7888
  evaluatedConfig(payload) {
7831
7889
  if (!this.collectEvents) return;
7832
- if (payload.context?.id && !payload.context.anonymous) this.contexts.set(payload.context.id, payload.context);
7890
+ const context = payload.context ?? void 0;
7891
+ const contextKind = effectiveContextKind({ context });
7892
+ const contextId = context?.anonymous ? void 0 : context?.id;
7893
+ if (contextId) this.contexts.set(`${contextKind}:${contextId}`, {
7894
+ ...context,
7895
+ kind: contextKind
7896
+ });
7833
7897
  const event = {
7834
7898
  ...payload.evaluation,
7835
- contextId: payload.context?.anonymous ? void 0 : payload.context?.id
7899
+ contextId,
7900
+ contextKind: contextId === void 0 ? void 0 : contextKind
7836
7901
  };
7837
7902
  this.evaluationEventQueue.push(this.sanitizeEvaluatedConfigEvent(event));
7838
7903
  }
7904
+ recordInvalidContext(kindSample) {
7905
+ return this.invalidContextSubmissions.record(kindSample);
7906
+ }
7839
7907
  sanitizeEvaluatedConfigEvent(event) {
7840
7908
  return {
7841
7909
  ...event,
@@ -7869,7 +7937,10 @@ var ServerTelemetryEventCollector = class extends TelemetryEventCollector {
7869
7937
  return await this.reporter?.report({
7870
7938
  context: this._context,
7871
7939
  discreteEvents: { capturedContexts: contextsSnapshot.contexts },
7872
- aggregatedEvents: { evaluatedConfig: this.aggregator.aggregate(evaluationSnapshot) },
7940
+ aggregatedEvents: {
7941
+ evaluatedConfig: this.aggregator.aggregate(evaluationSnapshot),
7942
+ invalidContext: this.invalidContextSubmissions.takeSnapshot()
7943
+ },
7873
7944
  droppedEvents: {
7874
7945
  evaluatedConfig: evaluationSnapshot.droppedCount,
7875
7946
  capturedContexts: contextsSnapshot.droppedCount
@@ -7903,6 +7974,12 @@ const generateValueId = async (v) => {
7903
7974
  const defaultBaseUrl = new URL("https://server-sdk-api.configdirector.com");
7904
7975
  const DEFAULT_FLUSH_INTERVAL = 3e4;
7905
7976
  const DEFAULT_EVENT_QUEUE_LIMIT = 5e3;
7977
+ const logInvalidContext = {
7978
+ "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 '_'.`),
7979
+ "known-kind-sample": () => {},
7980
+ "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.`),
7981
+ "beyond-sample-limit": () => {}
7982
+ };
7906
7983
  const usesAnySegment = (definition, segmentIds) => (definition.target?.rules ?? []).some((rule) => rule.type === "conditional" && (rule.conditions ?? []).some((condition) => condition.kind === "segment" && segmentIds.has(condition.segmentId)));
7907
7984
  var DefaultConfigDirectorClient = class {
7908
7985
  logger;
@@ -8086,7 +8163,8 @@ var DefaultConfigDirectorClient = class {
8086
8163
  const configDefinition = this.configSet?.configs[configKey];
8087
8164
  return this.getValueFromConfigDefinition(configKey, configDefinition, defaultValue, context);
8088
8165
  }
8089
- getValueFromConfigDefinition(configKey, configDefinition, defaultValue, context) {
8166
+ getValueFromConfigDefinition(configKey, configDefinition, defaultValue, suppliedContext) {
8167
+ const context = this.contextToEvaluate(suppliedContext);
8090
8168
  if (configDefinition) {
8091
8169
  const configState = this.configEvaluator.evaluate(configDefinition, {
8092
8170
  context,
@@ -8140,6 +8218,15 @@ var DefaultConfigDirectorClient = class {
8140
8218
  return defaultValue;
8141
8219
  }
8142
8220
  }
8221
+ contextToEvaluate(suppliedContext) {
8222
+ const admission = admitContext(suppliedContext);
8223
+ switch (admission.outcome) {
8224
+ case "admitted": return admission.context;
8225
+ case "ignored":
8226
+ logInvalidContext[this.usageEventCollector.recordInvalidContext(admission.kindSample)](this.logger, admission.kindSample);
8227
+ return;
8228
+ }
8229
+ }
8143
8230
  dispatchEvaluationEvent(event) {
8144
8231
  setTimeout(() => this.eventEmitter.emit("configEvaluated", { evaluation: event }), 0);
8145
8232
  }
@@ -8177,11 +8264,12 @@ var DefaultConfigDirectorClient = class {
8177
8264
  this.handlersMap.clear();
8178
8265
  }
8179
8266
  getAllConfigs(options) {
8267
+ const context = this.contextToEvaluate(options?.context);
8180
8268
  if (!this.configSet) return {};
8181
8269
  const entries = Object.entries(this.configSet.configs);
8182
8270
  const filtered = options?.configKeys ? entries.filter(([key]) => options.configKeys?.includes(key)) : entries;
8183
8271
  return Object.fromEntries(filtered.map(([key, config]) => [key, this.configEvaluator.evaluate(config, {
8184
- context: options?.context,
8272
+ context,
8185
8273
  metadata: this.metaContext
8186
8274
  }, this.configSet?.segments)]));
8187
8275
  }
@@ -8225,7 +8313,7 @@ var plugin_default = defineNitroPlugin((nitroApp) => {
8225
8313
  const { serverSdkKey, baseUrl, connection } = runtimeConfig.configdirector;
8226
8314
  const client = new DefaultConfigDirectorClient(serverSdkKey, {
8227
8315
  sdkName: "nuxt-sdk",
8228
- sdkVersion: "1.9.2"
8316
+ sdkVersion: "1.10.0"
8229
8317
  }, {
8230
8318
  logger,
8231
8319
  connection: {