@configdirector/nuxt-sdk 1.12.1 → 1.13.1

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 (27) hide show
  1. package/dist/module.d.mts +75 -1
  2. package/dist/module.json +1 -1
  3. package/dist/runtime/app/composables/useConfigDirectorClient.d.mts +1 -1
  4. package/dist/runtime/app/composables/useConfigDirectorClient.d.ts +1 -1
  5. package/dist/runtime/app/composables/useConfigDirectorClientHooks.d.mts +1 -1
  6. package/dist/runtime/app/composables/useConfigDirectorClientHooks.d.ts +1 -1
  7. package/dist/runtime/app/composables/useConfigDirectorContext.d.mts +1 -1
  8. package/dist/runtime/app/composables/useConfigDirectorContext.d.ts +1 -1
  9. package/dist/runtime/app/composables/useConfigDirectorValue.d.mts +1 -1
  10. package/dist/runtime/app/composables/useConfigDirectorValue.d.ts +1 -1
  11. package/dist/runtime/{native-values-ChQdi7c7.d.mts → native-values-CfhP0yLS.d.mts} +17 -6
  12. package/dist/runtime/nitro/offline-values.d.mts +1 -1
  13. package/dist/runtime/nitro/offline-values.d.ts +1 -1
  14. package/dist/runtime/nitro/plugin.d.mts +2 -2
  15. package/dist/runtime/nitro/plugin.d.ts +2 -2
  16. package/dist/runtime/nitro/plugin.mjs +54 -30
  17. package/dist/runtime/plugin.client.mjs +89 -54
  18. package/dist/runtime/plugin.server.mjs +3 -0
  19. package/dist/runtime/server/composables/useConfigDirectorServerClient.d.mts +1 -1
  20. package/dist/runtime/server/composables/useConfigDirectorServerClient.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-CuzrFq9I.d.mts → types-BvqduMQP.d.mts} +11 -1
  24. package/dist/runtime/{types-BoDJiQsr.d.mts → types-qDEc6g8R.d.mts} +12 -1
  25. package/dist/testing.d.mts +27 -6
  26. package/dist/testing.mjs +72 -44
  27. package/package.json +1 -1
package/dist/module.d.mts CHANGED
@@ -2,6 +2,80 @@ import * as _$_nuxt_schema0 from "@nuxt/schema";
2
2
  import { LogLevel } from "consola";
3
3
 
4
4
  //#region ../shared/src/types.d.ts
5
+ type ConfigEnumLikeType = {
6
+ [key: string]: string | number;
7
+ };
8
+ type ConfigValueType = string | number | boolean | object | ConfigEnumLikeType;
9
+ /**
10
+ * Why an evaluation returned the value it did. `found-match` returns a value from ConfigDirector,
11
+ * and `cached` a value kept on the device while a client SDK waits for live values. `value-missing`
12
+ * returns the in-code default value because the config has no value. Every other reason returns the
13
+ * in-code default value because of an error: the config does not exist (`config-state-missing`), no
14
+ * values have arrived yet (`client-not-ready`), or the value cannot be read as the type of the in-code
15
+ * default value.
16
+ */
17
+ type EvaluationReason = "found-match" | "config-state-missing" | "client-not-ready" | "type-mismatch" | "value-missing" | "invalid-number" | "invalid-json" | "invalid-boolean" | "cached";
18
+ /**
19
+ * One evaluation of a config: the value it returned, why, and the context it was evaluated for.
20
+ */
21
+ type ConfigEvaluation<T extends ConfigValueType = ConfigValueType> = {
22
+ /** The config key that was evaluated. */key: string; /** The value returned, which is the in-code default value when {@link isDefaultValue} is `true`. */
23
+ value: T; /** The ID ConfigDirector assigned to the value returned, when ConfigDirector sent one with it. */
24
+ valueId?: string; /** Whether the evaluation returned the in-code default value. */
25
+ isDefaultValue: boolean; /** Why the evaluation returned this value. */
26
+ reason: EvaluationReason; /** The context the config was evaluated for, if any. */
27
+ context?: ConfigDirectorContext;
28
+ };
29
+ /**
30
+ * The context to be sent to ConfigDirector: the user or other entity, such as a device or an
31
+ * organization, that configs are evaluated for. This context will be used for targeting rules
32
+ * evaluation.
33
+ */
34
+ type ConfigDirectorContext = {
35
+ /**
36
+ * The context's identifier. This should be a value that uniquely identifies the user or other
37
+ * entity among contexts of the same {@link kind}.
38
+ * In the case of anonymous users, you could generate a UUID or alternatively not provide
39
+ * the {@link id}. In a client SDK, ConfigDirector then derives an identifier from an ID the SDK
40
+ * stores for the app install or the website, so it stays the same until the app is uninstalled
41
+ * or the site's data is cleared. However, keep in mind that this value is used for segmenting
42
+ * contexts in percentage rollouts, and changes to the {@link id} could result in the context
43
+ * being assigned to a different percentile.
44
+ */
45
+ id?: string;
46
+ /**
47
+ * The kind of entity this context represents, such as `user`, `device`, `organization`,
48
+ * `tenant` or `listing`. You can use any other kind that fits your application. A kind has 1
49
+ * to 64 characters, using only lowercase letters (`a`-`z`), digits, `-` and `_`. It is used
50
+ * exactly as given, without trimming or changing its case.
51
+ *
52
+ * A context with an invalid kind is ignored as a whole: the SDK logs an error and evaluates
53
+ * as if no context had been provided.
54
+ *
55
+ * @default "user"
56
+ */
57
+ kind?: string;
58
+ /**
59
+ * The context's display name. This will be shown in the ConfigDirector dashboard and may be
60
+ * used for targeting rules.
61
+ */
62
+ name?: string;
63
+ /**
64
+ * Any arbitrary traits for the context. They will be shown in the ConfigDirector dashboard
65
+ * and may be used for targeting rules.
66
+ */
67
+ traits?: {
68
+ [key: string]: unknown;
69
+ };
70
+ /**
71
+ * Whether or not to treat the context as anonymous during evaluation. When `true`, the
72
+ * context values will be used for targeting rules evaluation but the context will not be
73
+ * persisted and will not appear in the dashboard.
74
+ *
75
+ * @default false
76
+ */
77
+ anonymous?: boolean;
78
+ };
5
79
  type ConnectionMode = "streaming" | "polling" | "offline";
6
80
  //#endregion
7
81
  //#region ../shared/src/native-values.d.ts
@@ -219,4 +293,4 @@ interface ModuleOptions {
219
293
  }
220
294
  declare const _default: _$_nuxt_schema0.NuxtModule<ModuleOptions, ModuleOptions, false>;
221
295
  //#endregion
222
- export { ModuleOptions, type OfflineValue, type OfflineValues, _default as default };
296
+ export { type ConfigEvaluation, type EvaluationReason, ModuleOptions, type OfflineValue, type OfflineValues, _default as default };
package/dist/module.json CHANGED
@@ -4,5 +4,5 @@
4
4
  "compatibility": {
5
5
  "nuxt": ">=4.0.0"
6
6
  },
7
- "version": "1.12.1"
7
+ "version": "1.13.1"
8
8
  }
@@ -1,4 +1,4 @@
1
- import { n as ConfigDirectorClient } from "../../types-CuzrFq9I.mjs";
1
+ import { n as ConfigDirectorClient } from "../../types-BvqduMQP.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-CuzrFq9I.mjs";
1
+ import { n as ConfigDirectorClient } from "../../types-BvqduMQP.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-CuzrFq9I.mjs";
1
+ import { t as ClientHooks } from "../../types-BvqduMQP.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-CuzrFq9I.mjs";
1
+ import { t as ClientHooks } from "../../types-BvqduMQP.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 { n as ConfigDirectorContext } from "../../native-values-ChQdi7c7.mjs";
1
+ import { n as ConfigDirectorContext } from "../../native-values-CfhP0yLS.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 { n as ConfigDirectorContext } from "../../native-values-ChQdi7c7.mjs";
1
+ import { n as ConfigDirectorContext } from "../../native-values-CfhP0yLS.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 { a as ConfigValueType } from "../../native-values-ChQdi7c7.mjs";
1
+ import { a as ConfigValueType } from "../../native-values-CfhP0yLS.mjs";
2
2
  import { t as ClientStatus } from "../../types-C30VJ25l.mjs";
3
3
  import { DeepReadonly, Ref, ShallowRef } from "vue";
4
4
 
@@ -1,4 +1,4 @@
1
- import { a as ConfigValueType } from "../../native-values-ChQdi7c7.mjs";
1
+ import { a as ConfigValueType } from "../../native-values-CfhP0yLS.mjs";
2
2
  import { t as ClientStatus } from "../../types-C30VJ25l.mjs";
3
3
  import { DeepReadonly, Ref, ShallowRef } from "vue";
4
4
 
@@ -12,13 +12,24 @@ type ConfigState = {
12
12
  value: string | undefined | null;
13
13
  valueId?: string | undefined | null;
14
14
  };
15
+ /**
16
+ * Why an evaluation returned the value it did. `found-match` returns a value from ConfigDirector,
17
+ * and `cached` a value kept on the device while a client SDK waits for live values. `value-missing`
18
+ * returns the in-code default value because the config has no value. Every other reason returns the
19
+ * in-code default value because of an error: the config does not exist (`config-state-missing`), no
20
+ * values have arrived yet (`client-not-ready`), or the value cannot be read as the type of the in-code
21
+ * default value.
22
+ */
15
23
  type EvaluationReason = "found-match" | "config-state-missing" | "client-not-ready" | "type-mismatch" | "value-missing" | "invalid-number" | "invalid-json" | "invalid-boolean" | "cached";
16
- type ConfigEvaluation = {
17
- key: string;
18
- value: ConfigValueType;
19
- valueId?: string;
20
- isDefaultValue: boolean;
21
- reason: EvaluationReason;
24
+ /**
25
+ * One evaluation of a config: the value it returned, why, and the context it was evaluated for.
26
+ */
27
+ type ConfigEvaluation<T extends ConfigValueType = ConfigValueType> = {
28
+ /** The config key that was evaluated. */key: string; /** The value returned, which is the in-code default value when {@link isDefaultValue} is `true`. */
29
+ value: T; /** The ID ConfigDirector assigned to the value returned, when ConfigDirector sent one with it. */
30
+ valueId?: string; /** Whether the evaluation returned the in-code default value. */
31
+ isDefaultValue: boolean; /** Why the evaluation returned this value. */
32
+ reason: EvaluationReason; /** The context the config was evaluated for, if any. */
22
33
  context?: ConfigDirectorContext;
23
34
  };
24
35
  /**
@@ -1,4 +1,4 @@
1
- import { t as NativeValues } from "../native-values-ChQdi7c7.mjs";
1
+ import { t as NativeValues } from "../native-values-CfhP0yLS.mjs";
2
2
  import * as _$h3 from "h3";
3
3
 
4
4
  //#region src/runtime/nitro/offline-values.d.ts
@@ -1,4 +1,4 @@
1
- import { t as NativeValues } from "../native-values-ChQdi7c7.mjs";
1
+ import { t as NativeValues } from "../native-values-CfhP0yLS.mjs";
2
2
  import * as _$h3 from "h3";
3
3
 
4
4
  //#region src/runtime/nitro/offline-values.d.ts
@@ -1,5 +1,5 @@
1
- import { o as ConnectionMode } from "../native-values-ChQdi7c7.mjs";
2
- import { n as ConfigDirectorClient } from "../types-BoDJiQsr.mjs";
1
+ import { o as ConnectionMode } from "../native-values-CfhP0yLS.mjs";
2
+ import { n as ConfigDirectorClient } from "../types-qDEc6g8R.mjs";
3
3
  import * as _$nitropack from "nitropack";
4
4
 
5
5
  //#region src/runtime/nitro/plugin.d.ts
@@ -1,5 +1,5 @@
1
- import { o as ConnectionMode } from "../native-values-ChQdi7c7.mjs";
2
- import { n as ConfigDirectorClient } from "../types-BoDJiQsr.mjs";
1
+ import { o as ConnectionMode } from "../native-values-CfhP0yLS.mjs";
2
+ import { n as ConfigDirectorClient } from "../types-qDEc6g8R.mjs";
3
3
  import * as _$nitropack from "nitropack";
4
4
 
5
5
  //#region src/runtime/nitro/plugin.d.ts
@@ -7252,19 +7252,22 @@ var ConfigEvaluator = class {
7252
7252
  this.logger = logger;
7253
7253
  }
7254
7254
  evaluate(config, context, segments) {
7255
+ const explanation = this.explain(config, context, segments);
7255
7256
  return {
7256
7257
  id: config.id,
7257
7258
  key: config.key,
7258
7259
  type: config.type,
7259
- value: this.explain(config, context, segments).value
7260
+ value: explanation.value,
7261
+ valueId: explanation.valueId
7260
7262
  };
7261
7263
  }
7262
7264
  /**
7263
7265
  * Evaluate a config for a context and record how the value was reached: every rule in the
7264
7266
  * order it was walked with its outcome, the conditions that were checked with what they
7265
7267
  * resolved to, and the share a rollout assigned the context to. `evaluate` is this walk with
7266
- * only the value kept, so the two cannot disagree. `segments` is the payload's segments map,
7267
- * which a segment condition looks its segment up in; a segment it does not hold never matches.
7268
+ * only the value and its value ID kept, so the two cannot disagree. `segments` is the payload's
7269
+ * segments map, which a segment condition looks its segment up in; a segment it does not hold
7270
+ * never matches.
7268
7271
  */
7269
7272
  explain(config, context, segments) {
7270
7273
  const rules = [...config.target?.rules ?? []].sort((a, b) => (a.order ?? Number.MAX_SAFE_INTEGER) - (b.order ?? Number.MAX_SAFE_INTEGER));
@@ -7282,17 +7285,24 @@ var ConfigEvaluator = class {
7282
7285
  }
7283
7286
  const result = this.explainRule(rule, config, context, segments);
7284
7287
  explanations.push(result.explanation);
7285
- if (result.value !== void 0) served = {
7288
+ if (result.served !== void 0) served = {
7286
7289
  ruleId: rule.id,
7287
- value: result.value
7290
+ ...result.served
7288
7291
  };
7289
7292
  }
7290
- return {
7291
- value: served?.value ?? config.target?.defaultValue,
7292
- servedBy: served ? {
7293
+ if (served) return {
7294
+ value: served.value,
7295
+ valueId: served.valueId,
7296
+ servedBy: {
7293
7297
  kind: "rule",
7294
7298
  ruleId: served.ruleId
7295
- } : { kind: "default" },
7299
+ },
7300
+ rules: explanations
7301
+ };
7302
+ return {
7303
+ value: config.target?.defaultValue,
7304
+ valueId: config.target?.defaultValueId,
7305
+ servedBy: { kind: "default" },
7296
7306
  rules: explanations
7297
7307
  };
7298
7308
  }
@@ -7304,13 +7314,13 @@ var ConfigEvaluator = class {
7304
7314
  bucket: void 0
7305
7315
  };
7306
7316
  try {
7307
- let value;
7308
- if (rule.type == "percentage") value = this.explainPercentage(rule.percentages ?? [], config, context, explanation);
7309
- else if (rule.type == "conditional") value = this.explainConditionalRule(rule, config, context, segments, explanation);
7310
- if (value !== void 0) explanation.outcome = "matched";
7317
+ let served;
7318
+ if (rule.type == "percentage") served = this.explainPercentage(rule.percentages ?? [], config, context, explanation);
7319
+ else if (rule.type == "conditional") served = this.explainConditionalRule(rule, config, context, segments, explanation);
7320
+ if (served !== void 0) explanation.outcome = "matched";
7311
7321
  return {
7312
7322
  explanation,
7313
- value
7323
+ served
7314
7324
  };
7315
7325
  } catch (error) {
7316
7326
  this.logger.warn(`There was an error while evaluating a targeting rule '${rule?.id}' for '${config?.key}'. The rule will be disregarded.`, {
@@ -7325,7 +7335,7 @@ var ConfigEvaluator = class {
7325
7335
  conditions: [],
7326
7336
  bucket: void 0
7327
7337
  },
7328
- value: void 0
7338
+ served: void 0
7329
7339
  };
7330
7340
  }
7331
7341
  }
@@ -7357,7 +7367,10 @@ var ConfigEvaluator = class {
7357
7367
  shares,
7358
7368
  selectedPercentageId: selected?.id
7359
7369
  };
7360
- return selected?.value != null ? selected.value.toString() : void 0;
7370
+ return selected?.value != null ? {
7371
+ value: selected.value.toString(),
7372
+ valueId: selected.valueId
7373
+ } : void 0;
7361
7374
  }
7362
7375
  explainConditionalRule(rule, config, context, segments, explanation) {
7363
7376
  let failed = false;
@@ -7374,7 +7387,10 @@ var ConfigEvaluator = class {
7374
7387
  failed = checked.outcome !== "matched";
7375
7388
  }
7376
7389
  if (failed) return;
7377
- if (rule.target == "value") return rule.value != null ? rule.value.toString() : void 0;
7390
+ if (rule.target == "value") return rule.value != null ? {
7391
+ value: rule.value.toString(),
7392
+ valueId: rule.valueId
7393
+ } : void 0;
7378
7394
  if (rule.target == "percentage") return this.explainPercentage(rule.percentages ?? [], config, context, explanation);
7379
7395
  }
7380
7396
  explainCondition(condition, context, segments) {
@@ -7551,9 +7567,12 @@ const parseJson = (jsonString, defaultValue, requestedType, valueId) => {
7551
7567
  };
7552
7568
  const isJsonTypeCompatible = (parsed, defaultValue) => {
7553
7569
  if (parsed === null) return false;
7554
- const defaultType = typeof defaultValue;
7555
- if (defaultType === "bigint") return typeof parsed === "number";
7556
- return typeof parsed === defaultType;
7570
+ return jsonShapeOf(parsed) === jsonShapeOf(defaultValue);
7571
+ };
7572
+ const jsonShapeOf = (value) => {
7573
+ if (Array.isArray(value)) return "array";
7574
+ const nativeType = typeof value;
7575
+ return nativeType === "bigint" ? "number" : nativeType;
7557
7576
  };
7558
7577
  //#endregion
7559
7578
  //#region ../shared/src/logger.ts
@@ -8282,7 +8301,7 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
8282
8301
  updateWatchersForConfig(configKey) {
8283
8302
  const configDefinition = this.configSet?.configs[configKey];
8284
8303
  this.handlersMap.get(configKey)?.forEach((h) => {
8285
- const value = this.getValueFromConfigDefinition(configKey, configDefinition, h.defaultValue, h.context);
8304
+ const value = this.evaluationOf(configKey, configDefinition, h.defaultValue, h.context).value;
8286
8305
  h.handler(value);
8287
8306
  });
8288
8307
  }
@@ -8308,11 +8327,14 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
8308
8327
  } else handlers.splice(0);
8309
8328
  }
8310
8329
  getValue(configKey, defaultValue, context) {
8330
+ return this.evaluate(configKey, defaultValue, context).value;
8331
+ }
8332
+ evaluate(configKey, defaultValue, context) {
8311
8333
  this.validateDefaultValue(defaultValue);
8312
8334
  const configDefinition = this.configSet?.configs[configKey];
8313
- return this.getValueFromConfigDefinition(configKey, configDefinition, defaultValue, context);
8335
+ return this.evaluationOf(configKey, configDefinition, defaultValue, context);
8314
8336
  }
8315
- getValueFromConfigDefinition(configKey, configDefinition, defaultValue, suppliedContext) {
8337
+ evaluationOf(configKey, configDefinition, defaultValue, suppliedContext) {
8316
8338
  const context = this.contextToEvaluate(suppliedContext);
8317
8339
  if (configDefinition) {
8318
8340
  const configState = this.configEvaluator.evaluate(configDefinition, {
@@ -8333,16 +8355,17 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
8333
8355
  evaluationReason: parseResult.reason
8334
8356
  }
8335
8357
  });
8336
- this.dispatchEvaluationEvent({
8358
+ const evaluation = {
8337
8359
  key: configKey,
8338
8360
  value: parseResult.parsedValue,
8339
8361
  valueId: parseResult.parsedValueId ?? void 0,
8340
8362
  isDefaultValue: parseResult.usedDefault,
8341
8363
  reason: parseResult.reason,
8342
8364
  context
8343
- });
8365
+ };
8366
+ this.dispatchEvaluationEvent(evaluation);
8344
8367
  this.logger.debug(`[ConfigDirectorClient] Evaluated '${configKey}' to '${parseResult.parsedValue}'`);
8345
- return parseResult.parsedValue;
8368
+ return evaluation;
8346
8369
  } else {
8347
8370
  this.logger.debug(`[ConfigDirectorClient] No config state found for '${configKey}', returning default value '${defaultValue}'`);
8348
8371
  const reason = this.isReady ? "config-state-missing" : "client-not-ready";
@@ -8357,14 +8380,15 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
8357
8380
  evaluationReason: reason
8358
8381
  }
8359
8382
  });
8360
- this.dispatchEvaluationEvent({
8383
+ const evaluation = {
8361
8384
  key: configKey,
8362
8385
  value: defaultValue,
8363
8386
  isDefaultValue: true,
8364
8387
  reason,
8365
8388
  context
8366
- });
8367
- return defaultValue;
8389
+ };
8390
+ this.dispatchEvaluationEvent(evaluation);
8391
+ return evaluation;
8368
8392
  }
8369
8393
  }
8370
8394
  contextToEvaluate(suppliedContext) {
@@ -8452,7 +8476,7 @@ var plugin_default = defineNitroPlugin((nitroApp) => {
8452
8476
  logger.debug("Initializing ConfigDirector Nitro plugin");
8453
8477
  const client = new DefaultConfigDirectorClient(serverSdkKey || void 0, {
8454
8478
  sdkName: "nuxt-sdk",
8455
- sdkVersion: "1.12.1"
8479
+ sdkVersion: "1.13.1"
8456
8480
  }, {
8457
8481
  logger,
8458
8482
  connection: {
@@ -819,9 +819,12 @@ const parseJson = (jsonString, defaultValue, requestedType, valueId) => {
819
819
  };
820
820
  const isJsonTypeCompatible = (parsed, defaultValue) => {
821
821
  if (parsed === null) return false;
822
- const defaultType = typeof defaultValue;
823
- if (defaultType === "bigint") return typeof parsed === "number";
824
- return typeof parsed === defaultType;
822
+ return jsonShapeOf(parsed) === jsonShapeOf(defaultValue);
823
+ };
824
+ const jsonShapeOf = (value) => {
825
+ if (Array.isArray(value)) return "array";
826
+ const nativeType = typeof value;
827
+ return nativeType === "bigint" ? "number" : nativeType;
825
828
  };
826
829
  //#endregion
827
830
  //#region ../shared/src/logger.ts
@@ -1140,8 +1143,8 @@ var PollingTransport = class extends AbstractPollingTransport {
1140
1143
  this.pollingIntervalSeconds = options.pollingInterval ?? 60;
1141
1144
  }
1142
1145
  async connect(context, timeout) {
1143
- const generation = ++this.connectionGeneration;
1144
- this.clearPollingInterval();
1146
+ this.endCurrentConnection();
1147
+ const generation = this.connectionGeneration;
1145
1148
  const clientSession = await this.options.clientSession;
1146
1149
  if (generation !== this.connectionGeneration) return this;
1147
1150
  try {
@@ -1156,9 +1159,14 @@ var PollingTransport = class extends AbstractPollingTransport {
1156
1159
  return this;
1157
1160
  }
1158
1161
  close() {
1159
- this.connectionGeneration++;
1162
+ this.endCurrentConnection();
1160
1163
  super.close();
1161
1164
  }
1165
+ endCurrentConnection() {
1166
+ this.connectionGeneration++;
1167
+ this.clearPollingInterval();
1168
+ this.lastUpdateTimestamp = void 0;
1169
+ }
1162
1170
  async fetchConfigs(context, timeout, clientSession) {
1163
1171
  if (this.fatalError) {
1164
1172
  this.logger.warn("[PollingTransport] There was a prior unrecoverable error. Ignoring attempt to reconnect.");
@@ -1265,11 +1273,12 @@ const utf8ByteLength = (text) => {
1265
1273
  };
1266
1274
  //#endregion
1267
1275
  //#region ../js-client-core/src/cached-values.ts
1268
- const ENTRY_FORMAT_VERSION = 1;
1276
+ const ENTRY_FORMAT_VERSION = 2;
1277
+ const FIRST_ENTRY_FORMAT_VERSION = 1;
1269
1278
  const DEFAULT_CONTEXT_KIND = "user";
1270
1279
  const CACHED_VALUES_STORAGE_KEY_PREFIX = "configdirector-sdk:cached-values:";
1271
1280
  const DEFAULT_CACHING_SIZE_LIMIT_BYTES = 256 * 1024;
1272
- const planCaching = (connectionPlan, caching, platformProvider) => {
1281
+ const planCaching = (connectionPlan, caching, sha256HexDigest, platformCachingProvider) => {
1273
1282
  if (connectionPlan.kind === "stay-offline") return {
1274
1283
  kind: "no cache",
1275
1284
  reason: "offline mode"
@@ -1278,7 +1287,12 @@ const planCaching = (connectionPlan, caching, platformProvider) => {
1278
1287
  kind: "no cache",
1279
1288
  reason: "turned off"
1280
1289
  };
1281
- const provider = caching?.provider ?? platformProvider?.(connectionPlan.clientSdkKey);
1290
+ if (!sha256HexDigest) return {
1291
+ kind: "no cache",
1292
+ reason: "no SHA-256 available"
1293
+ };
1294
+ const clientSdkKeyDigest = sha256HexDigest(connectionPlan.clientSdkKey);
1295
+ const provider = caching?.provider ?? platformCachingProvider?.(clientSdkKeyDigest);
1282
1296
  if (!provider) return {
1283
1297
  kind: "no cache",
1284
1298
  reason: "no provider available"
@@ -1286,7 +1300,7 @@ const planCaching = (connectionPlan, caching, platformProvider) => {
1286
1300
  return {
1287
1301
  kind: "cache",
1288
1302
  provider,
1289
- clientSdkKey: connectionPlan.clientSdkKey
1303
+ clientSdkKeyDigest
1290
1304
  };
1291
1305
  };
1292
1306
  const contextTagFor = (context, instanceId) => {
@@ -1321,19 +1335,34 @@ const isConfigStateMap = (candidate) => {
1321
1335
  if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) return false;
1322
1336
  return Object.values(candidate).every((config) => config && typeof config === "object" && typeof config.id === "string" && typeof config.key === "string");
1323
1337
  };
1324
- const isEntryFor = (clientSdkKey, candidate) => {
1338
+ const isEntryFor = (clientSdkKeyDigest, candidate) => {
1325
1339
  if (!candidate || typeof candidate !== "object") return false;
1326
1340
  const entry = candidate;
1327
- return entry.formatVersion === ENTRY_FORMAT_VERSION && entry.clientSdkKey === clientSdkKey && isContextTag(entry.contextTag) && typeof entry.environmentId === "string" && typeof entry.projectId === "string" && isConfigStateMap(entry.configs) && typeof entry.receivedAt === "number";
1341
+ return entry.formatVersion === ENTRY_FORMAT_VERSION && entry.clientSdkKeyDigest === clientSdkKeyDigest && isContextTag(entry.contextTag) && typeof entry.environmentId === "string" && typeof entry.projectId === "string" && isConfigStateMap(entry.configs) && typeof entry.receivedAt === "number";
1342
+ };
1343
+ const hasFirstFormatVersion = (candidate) => !!candidate && typeof candidate === "object" && candidate.formatVersion === FIRST_ENTRY_FORMAT_VERSION;
1344
+ const parseEntry = (raw, clientSdkKeyDigest) => {
1345
+ let candidate;
1346
+ try {
1347
+ candidate = JSON.parse(raw);
1348
+ } catch {
1349
+ return { outcome: "unreadable" };
1350
+ }
1351
+ if (isEntryFor(clientSdkKeyDigest, candidate)) return {
1352
+ outcome: "stored",
1353
+ cachedValues: candidate
1354
+ };
1355
+ if (hasFirstFormatVersion(candidate)) return { outcome: "first format" };
1356
+ return { outcome: "unreadable" };
1328
1357
  };
1329
1358
  var CachedValuesStore = class {
1330
1359
  warned = /* @__PURE__ */ new Set();
1331
1360
  pendingRead;
1332
1361
  queuedCachedValues;
1333
1362
  writing = false;
1334
- constructor(provider, clientSdkKey, logger) {
1363
+ constructor(provider, clientSdkKeyDigest, logger) {
1335
1364
  this.provider = provider;
1336
- this.clientSdkKey = clientSdkKey;
1365
+ this.clientSdkKeyDigest = clientSdkKeyDigest;
1337
1366
  this.logger = logger;
1338
1367
  }
1339
1368
  read() {
@@ -1353,23 +1382,16 @@ var CachedValuesStore = class {
1353
1382
  return { outcome: "failed" };
1354
1383
  }
1355
1384
  if (raw == null || raw === "") return { outcome: "nothing stored" };
1356
- const entry = this.parseEntry(raw);
1357
- if (!entry) {
1358
- this.warnOnce("unreadable", "The values kept on the device could not be read and were removed, starting without them");
1359
- await this.removeEntry();
1360
- return { outcome: "unreadable" };
1361
- }
1362
- return {
1363
- outcome: "stored",
1364
- cachedValues: entry
1365
- };
1366
- }
1367
- parseEntry(raw) {
1368
- try {
1369
- const candidate = JSON.parse(raw);
1370
- return isEntryFor(this.clientSdkKey, candidate) ? candidate : void 0;
1371
- } catch {
1372
- return;
1385
+ const entryParseOutcome = parseEntry(raw, await this.clientSdkKeyDigest);
1386
+ switch (entryParseOutcome.outcome) {
1387
+ case "stored": return entryParseOutcome;
1388
+ case "first format":
1389
+ await this.removeEntry();
1390
+ return entryParseOutcome;
1391
+ case "unreadable":
1392
+ this.warnOnce("unreadable", "The values kept on the device could not be read and were removed, starting without them");
1393
+ await this.removeEntry();
1394
+ return entryParseOutcome;
1373
1395
  }
1374
1396
  }
1375
1397
  async writeQueued() {
@@ -1388,7 +1410,7 @@ var CachedValuesStore = class {
1388
1410
  async writeEntry(cachedValues) {
1389
1411
  const entry = {
1390
1412
  formatVersion: ENTRY_FORMAT_VERSION,
1391
- clientSdkKey: this.clientSdkKey,
1413
+ clientSdkKeyDigest: await this.clientSdkKeyDigest,
1392
1414
  ...cachedValues
1393
1415
  };
1394
1416
  const serialized = JSON.stringify(entry);
@@ -1452,7 +1474,7 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
1452
1474
  this.timeout = clientOptions?.connection?.timeout ?? 3e3;
1453
1475
  const urlFactory = internalClientOptions?.urlFactory ?? defaultUrlFactory;
1454
1476
  this.connectionPlan = planConnection(clientSdkKey, clientOptions?.connection, urlFactory);
1455
- this.cachedValuesStore = this.buildCachedValuesStore(planCaching(this.connectionPlan, clientOptions?.caching, internalClientOptions?.platformCachingProvider));
1477
+ this.cachedValuesStore = this.buildCachedValuesStore(planCaching(this.connectionPlan, clientOptions?.caching, internalClientOptions?.sha256HexDigest, internalClientOptions?.platformCachingProvider));
1456
1478
  this.telemetryClient = this.buildTelemetryClient(liveTelemetryClient, clientOptions);
1457
1479
  this.transport = internalClientOptions?.transport ?? this.buildTransport(sdkOptions, clientOptions, urlFactory, internalClientOptions);
1458
1480
  this.transport.on("configSetReceived", (configSet) => {
@@ -1495,7 +1517,7 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
1495
1517
  this.logger.debug(`[ConfigDirectorClient] No values are kept on the device: ${cachingPlan.reason}`);
1496
1518
  return;
1497
1519
  case "cache": {
1498
- const store = new CachedValuesStore(cachingPlan.provider, cachingPlan.clientSdkKey, this.logger);
1520
+ const store = new CachedValuesStore(cachingPlan.provider, cachingPlan.clientSdkKeyDigest, this.logger);
1499
1521
  store.read();
1500
1522
  return store;
1501
1523
  }
@@ -1511,6 +1533,7 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
1511
1533
  const read = await store.read();
1512
1534
  switch (read.outcome) {
1513
1535
  case "nothing stored":
1536
+ case "first format":
1514
1537
  case "unreadable":
1515
1538
  case "failed": return;
1516
1539
  case "stored": break;
@@ -1705,7 +1728,7 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
1705
1728
  updateWatchersForConfig(configKey) {
1706
1729
  const configState = this.configSet?.configs[configKey];
1707
1730
  this.handlersMap.get(configKey)?.forEach((h) => {
1708
- const value = this.getValueFromConfigState(configKey, configState, h.defaultValue);
1731
+ const value = this.evaluationOf(configKey, configState, h.defaultValue).value;
1709
1732
  h.handler(value);
1710
1733
  });
1711
1734
  }
@@ -1730,11 +1753,14 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
1730
1753
  } else handlers.splice(0);
1731
1754
  }
1732
1755
  getValue(configKey, defaultValue) {
1756
+ return this.evaluate(configKey, defaultValue).value;
1757
+ }
1758
+ evaluate(configKey, defaultValue) {
1733
1759
  this.validateDefaultValue(defaultValue);
1734
1760
  const configState = this.configSet?.configs[configKey];
1735
- return this.getValueFromConfigState(configKey, configState, defaultValue);
1761
+ return this.evaluationOf(configKey, configState, defaultValue);
1736
1762
  }
1737
- getValueFromConfigState(configKey, configState, defaultValue) {
1763
+ evaluationOf(configKey, configState, defaultValue) {
1738
1764
  if (!configState) {
1739
1765
  this.logger.debug(`[ConfigDirectorClient] No config state found for '${configKey}', returning default value '${defaultValue}'`);
1740
1766
  const reason = this.isReady ? "config-state-missing" : "client-not-ready";
@@ -1747,14 +1773,15 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
1747
1773
  usedDefault: true,
1748
1774
  evaluationReason: reason
1749
1775
  });
1750
- this.dispatchEvaluationEvent({
1776
+ const evaluation = {
1751
1777
  key: configKey,
1752
1778
  value: defaultValue,
1753
1779
  isDefaultValue: true,
1754
1780
  reason,
1755
1781
  context: this.context
1756
- });
1757
- return defaultValue;
1782
+ };
1783
+ this.dispatchEvaluationEvent(evaluation);
1784
+ return evaluation;
1758
1785
  }
1759
1786
  const parseResult = parseConfigValue(configState, defaultValue);
1760
1787
  const reason = this.servedReason(parseResult.reason);
@@ -1769,16 +1796,17 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
1769
1796
  usedDefault: parseResult.usedDefault,
1770
1797
  evaluationReason: reason
1771
1798
  });
1772
- this.dispatchEvaluationEvent({
1799
+ const evaluation = {
1773
1800
  key: configKey,
1774
1801
  value: parseResult.parsedValue,
1775
1802
  valueId: parseResult.parsedValueId ?? void 0,
1776
1803
  isDefaultValue: parseResult.usedDefault,
1777
1804
  reason,
1778
1805
  context: this.context
1779
- });
1806
+ };
1807
+ this.dispatchEvaluationEvent(evaluation);
1780
1808
  this.logger.debug(`[ConfigDirectorClient] Evaluated '${configKey}' to '${parseResult.parsedValue}'`);
1781
- return parseResult.parsedValue;
1809
+ return evaluation;
1782
1810
  }
1783
1811
  dispatchEvaluationEvent(event) {
1784
1812
  setTimeout(() => this.emit("configEvaluated", { evaluation: event }), 0);
@@ -2143,16 +2171,10 @@ const generateValueId = async (v) => {
2143
2171
  };
2144
2172
  //#endregion
2145
2173
  //#region ../js-browser-client/src/local-storage-caching-provider.ts
2146
- const toHex = (bytes) => Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
2147
- const storageKeyFor = async (clientSdkKey) => {
2148
- const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(clientSdkKey));
2149
- return CACHED_VALUES_STORAGE_KEY_PREFIX + toHex(new Uint8Array(digest));
2150
- };
2151
- const subtleCryptoAvailable = () => typeof crypto !== "undefined" && crypto.subtle != null;
2152
- const localStorageCachingProvider = (clientSdkKey) => {
2174
+ const localStorageCachingProvider = (clientSdkKeyDigest) => {
2153
2175
  const storage = localStorageIfAvailable();
2154
- if (!storage || !subtleCryptoAvailable()) return;
2155
- const storageKey = storageKeyFor(clientSdkKey);
2176
+ if (!storage) return;
2177
+ const storageKey = clientSdkKeyDigest.then((digest) => CACHED_VALUES_STORAGE_KEY_PREFIX + digest);
2156
2178
  return {
2157
2179
  read: async () => storage.getItem(await storageKey),
2158
2180
  write: async (value) => storage.setItem(await storageKey, value),
@@ -2161,6 +2183,16 @@ const localStorageCachingProvider = (clientSdkKey) => {
2161
2183
  };
2162
2184
  };
2163
2185
  //#endregion
2186
+ //#region ../js-browser-client/src/sha256-hex-digest.ts
2187
+ const toHex = (bytes) => Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
2188
+ const sha256HexDigestIfAvailable = () => {
2189
+ if (typeof crypto === "undefined" || crypto.subtle == null) return;
2190
+ return async (text) => {
2191
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(text));
2192
+ return toHex(new Uint8Array(digest));
2193
+ };
2194
+ };
2195
+ //#endregion
2164
2196
  //#region ../js-browser-client/src/BrowserConfigDirectorClient.ts
2165
2197
  /**
2166
2198
  * Creates a browser client. With a client SDK key it connects to ConfigDirector; with
@@ -2184,7 +2216,10 @@ const createBrowserClient = (clientSdkKeyOrOptions, sdkOptions, clientOptions) =
2184
2216
  urlFactory: defaultUrlFactory,
2185
2217
  valueIdGenerator: generateValueId,
2186
2218
  clientSession
2187
- }), clientSdkKeyOrOptions, sdkOptions, clientOptions, { platformCachingProvider: localStorageCachingProvider });
2219
+ }), clientSdkKeyOrOptions, sdkOptions, clientOptions, {
2220
+ sha256HexDigest: sha256HexDigestIfAvailable(),
2221
+ platformCachingProvider: localStorageCachingProvider
2222
+ });
2188
2223
  };
2189
2224
  //#endregion
2190
2225
  //#region ../js-client-core/src/installedTestClient.ts
@@ -2351,7 +2386,7 @@ const buildClient = async (configdirector, logger) => {
2351
2386
  return {
2352
2387
  client: createBrowserClient(configdirector?.clientSdkKey || void 0, {
2353
2388
  sdkName: "nuxt-sdk",
2354
- sdkVersion: "1.12.1"
2389
+ sdkVersion: "1.13.1"
2355
2390
  }, {
2356
2391
  metadata: {
2357
2392
  appName: configdirector?.appName,
@@ -176,6 +176,9 @@ var SsrClient = class {
176
176
  getValue(configKey, defaultValue) {
177
177
  return this.serverClient.getValue(configKey, defaultValue, this.suppliedContext);
178
178
  }
179
+ evaluate(configKey, defaultValue) {
180
+ return this.serverClient.evaluate(configKey, defaultValue, this.suppliedContext);
181
+ }
179
182
  watch() {
180
183
  return () => {};
181
184
  }
@@ -1,4 +1,4 @@
1
- import { n as ConfigDirectorClient } from "../../types-BoDJiQsr.mjs";
1
+ import { n as ConfigDirectorClient } from "../../types-qDEc6g8R.mjs";
2
2
 
3
3
  //#region src/runtime/server/composables/useConfigDirectorServerClient.d.ts
4
4
  declare const useConfigDirectorServerClient: () => ConfigDirectorClient;
@@ -1,4 +1,4 @@
1
- import { n as ConfigDirectorClient } from "../../types-BoDJiQsr.mjs";
1
+ import { n as ConfigDirectorClient } from "../../types-qDEc6g8R.mjs";
2
2
 
3
3
  //#region src/runtime/server/composables/useConfigDirectorServerClient.d.ts
4
4
  declare const useConfigDirectorServerClient: () => ConfigDirectorClient;
@@ -1,4 +1,4 @@
1
- import { t as ClientHooks } from "../../types-BoDJiQsr.mjs";
1
+ import { t as ClientHooks } from "../../types-qDEc6g8R.mjs";
2
2
 
3
3
  //#region src/runtime/server/composables/useConfigDirectorServerHooks.d.ts
4
4
  declare const useConfigDirectorServerHooks: (hooks: ClientHooks) => void;
@@ -1,4 +1,4 @@
1
- import { t as ClientHooks } from "../../types-BoDJiQsr.mjs";
1
+ import { t as ClientHooks } from "../../types-qDEc6g8R.mjs";
2
2
 
3
3
  //#region src/runtime/server/composables/useConfigDirectorServerHooks.d.ts
4
4
  declare const useConfigDirectorServerHooks: (hooks: ClientHooks) => void;
@@ -1,4 +1,4 @@
1
- import { a as ConfigValueType, n as ConfigDirectorContext, r as ConfigEvaluation } from "./native-values-ChQdi7c7.mjs";
1
+ import { a as ConfigValueType, n as ConfigDirectorContext, r as ConfigEvaluation } from "./native-values-CfhP0yLS.mjs";
2
2
 
3
3
  //#region ../js-client-core/src/Emitter.d.ts
4
4
  type EventType = string | symbol;
@@ -113,6 +113,16 @@ interface ConfigDirectorClient extends EventProvider<ClientEvents> {
113
113
  * is called before initialization is done.
114
114
  */
115
115
  getValue<T extends ConfigValueType>(configKey: string, defaultValue: T): T;
116
+ /**
117
+ * Evaluates a config as {@link getValue} does and returns the evaluation: the value, the reason it
118
+ * was served and its value ID. Like every read, it fires `configEvaluated` and is counted in
119
+ * telemetry.
120
+ *
121
+ * @returns The evaluation, whose `value` is what {@link getValue} returns
122
+ * @param configKey The config key to evaluate
123
+ * @param defaultValue The in-code default value, returned when no value from ConfigDirector can be served
124
+ */
125
+ evaluate<T extends ConfigValueType>(configKey: string, defaultValue: T): ConfigEvaluation<T>;
116
126
  /**
117
127
  * Watches for changes to a a config evaluation value. Whenever the config value changes, the
118
128
  * provided callback function will be called with the new value. Changes can happen due to updates
@@ -1,4 +1,4 @@
1
- import { a as ConfigValueType, i as ConfigState, n as ConfigDirectorContext, r as ConfigEvaluation } from "./native-values-ChQdi7c7.mjs";
1
+ import { a as ConfigValueType, i as ConfigState, n as ConfigDirectorContext, r as ConfigEvaluation } from "./native-values-CfhP0yLS.mjs";
2
2
 
3
3
  //#region ../js-server-sdk/src/types.d.ts
4
4
  type ClientEvents = {
@@ -59,6 +59,17 @@ interface ConfigDirectorClient {
59
59
  * @param context The user's context to be used for targeting rules evaluation (optional)
60
60
  */
61
61
  getValue<T extends ConfigValueType>(configKey: string, defaultValue: T, context?: ConfigDirectorContext): T;
62
+ /**
63
+ * Evaluates a config as {@link getValue} does and returns the evaluation: the value, the reason it
64
+ * was served and its value ID. Like every read, it fires `configEvaluated` and is counted in
65
+ * telemetry.
66
+ *
67
+ * @returns The evaluation, whose `value` is what {@link getValue} returns
68
+ * @param configKey The config key to evaluate
69
+ * @param defaultValue The in-code default value, returned when no value from ConfigDirector can be served
70
+ * @param context The user's context to be used for targeting rules evaluation (optional)
71
+ */
72
+ evaluate<T extends ConfigValueType>(configKey: string, defaultValue: T, context?: ConfigDirectorContext): ConfigEvaluation<T>;
62
73
  /**
63
74
  * Watches for changes to a a config evaluation value. Whenever the config value changes, the
64
75
  * provided callback function will be called with the new value. Changes can happen due to updates
@@ -19,13 +19,24 @@ interface ConfigDirectorLogger {
19
19
  warn(message: string, ...args: any): void;
20
20
  error(message: string, ...args: any): void;
21
21
  }
22
+ /**
23
+ * Why an evaluation returned the value it did. `found-match` returns a value from ConfigDirector,
24
+ * and `cached` a value kept on the device while a client SDK waits for live values. `value-missing`
25
+ * returns the in-code default value because the config has no value. Every other reason returns the
26
+ * in-code default value because of an error: the config does not exist (`config-state-missing`), no
27
+ * values have arrived yet (`client-not-ready`), or the value cannot be read as the type of the in-code
28
+ * default value.
29
+ */
22
30
  type EvaluationReason = "found-match" | "config-state-missing" | "client-not-ready" | "type-mismatch" | "value-missing" | "invalid-number" | "invalid-json" | "invalid-boolean" | "cached";
23
- type ConfigEvaluation = {
24
- key: string;
25
- value: ConfigValueType;
26
- valueId?: string;
27
- isDefaultValue: boolean;
28
- reason: EvaluationReason;
31
+ /**
32
+ * One evaluation of a config: the value it returned, why, and the context it was evaluated for.
33
+ */
34
+ type ConfigEvaluation<T extends ConfigValueType = ConfigValueType> = {
35
+ /** The config key that was evaluated. */key: string; /** The value returned, which is the in-code default value when {@link isDefaultValue} is `true`. */
36
+ value: T; /** The ID ConfigDirector assigned to the value returned, when ConfigDirector sent one with it. */
37
+ valueId?: string; /** Whether the evaluation returned the in-code default value. */
38
+ isDefaultValue: boolean; /** Why the evaluation returned this value. */
39
+ reason: EvaluationReason; /** The context the config was evaluated for, if any. */
29
40
  context?: ConfigDirectorContext;
30
41
  };
31
42
  /**
@@ -182,6 +193,16 @@ interface ConfigDirectorClient extends EventProvider<ClientEvents> {
182
193
  * is called before initialization is done.
183
194
  */
184
195
  getValue<T extends ConfigValueType>(configKey: string, defaultValue: T): T;
196
+ /**
197
+ * Evaluates a config as {@link getValue} does and returns the evaluation: the value, the reason it
198
+ * was served and its value ID. Like every read, it fires `configEvaluated` and is counted in
199
+ * telemetry.
200
+ *
201
+ * @returns The evaluation, whose `value` is what {@link getValue} returns
202
+ * @param configKey The config key to evaluate
203
+ * @param defaultValue The in-code default value, returned when no value from ConfigDirector can be served
204
+ */
205
+ evaluate<T extends ConfigValueType>(configKey: string, defaultValue: T): ConfigEvaluation<T>;
185
206
  /**
186
207
  * Watches for changes to a a config evaluation value. Whenever the config value changes, the
187
208
  * provided callback function will be called with the new value. Changes can happen due to updates
package/dist/testing.mjs CHANGED
@@ -816,9 +816,12 @@ const parseJson = (jsonString, defaultValue, requestedType, valueId) => {
816
816
  };
817
817
  const isJsonTypeCompatible = (parsed, defaultValue) => {
818
818
  if (parsed === null) return false;
819
- const defaultType = typeof defaultValue;
820
- if (defaultType === "bigint") return typeof parsed === "number";
821
- return typeof parsed === defaultType;
819
+ return jsonShapeOf(parsed) === jsonShapeOf(defaultValue);
820
+ };
821
+ const jsonShapeOf = (value) => {
822
+ if (Array.isArray(value)) return "array";
823
+ const nativeType = typeof value;
824
+ return nativeType === "bigint" ? "number" : nativeType;
822
825
  };
823
826
  //#endregion
824
827
  //#region ../shared/src/logger.ts
@@ -1137,8 +1140,8 @@ var PollingTransport = class extends AbstractPollingTransport {
1137
1140
  this.pollingIntervalSeconds = options.pollingInterval ?? 60;
1138
1141
  }
1139
1142
  async connect(context, timeout) {
1140
- const generation = ++this.connectionGeneration;
1141
- this.clearPollingInterval();
1143
+ this.endCurrentConnection();
1144
+ const generation = this.connectionGeneration;
1142
1145
  const clientSession = await this.options.clientSession;
1143
1146
  if (generation !== this.connectionGeneration) return this;
1144
1147
  try {
@@ -1153,9 +1156,14 @@ var PollingTransport = class extends AbstractPollingTransport {
1153
1156
  return this;
1154
1157
  }
1155
1158
  close() {
1156
- this.connectionGeneration++;
1159
+ this.endCurrentConnection();
1157
1160
  super.close();
1158
1161
  }
1162
+ endCurrentConnection() {
1163
+ this.connectionGeneration++;
1164
+ this.clearPollingInterval();
1165
+ this.lastUpdateTimestamp = void 0;
1166
+ }
1159
1167
  async fetchConfigs(context, timeout, clientSession) {
1160
1168
  if (this.fatalError) {
1161
1169
  this.logger.warn("[PollingTransport] There was a prior unrecoverable error. Ignoring attempt to reconnect.");
@@ -1258,9 +1266,10 @@ const utf8ByteLength = (text) => {
1258
1266
  };
1259
1267
  //#endregion
1260
1268
  //#region ../js-client-core/src/cached-values.ts
1261
- const ENTRY_FORMAT_VERSION = 1;
1269
+ const ENTRY_FORMAT_VERSION = 2;
1270
+ const FIRST_ENTRY_FORMAT_VERSION = 1;
1262
1271
  const DEFAULT_CONTEXT_KIND = "user";
1263
- const planCaching = (connectionPlan, caching, platformProvider) => {
1272
+ const planCaching = (connectionPlan, caching, sha256HexDigest, platformCachingProvider) => {
1264
1273
  if (connectionPlan.kind === "stay-offline") return {
1265
1274
  kind: "no cache",
1266
1275
  reason: "offline mode"
@@ -1269,7 +1278,12 @@ const planCaching = (connectionPlan, caching, platformProvider) => {
1269
1278
  kind: "no cache",
1270
1279
  reason: "turned off"
1271
1280
  };
1272
- const provider = caching?.provider ?? platformProvider?.(connectionPlan.clientSdkKey);
1281
+ if (!sha256HexDigest) return {
1282
+ kind: "no cache",
1283
+ reason: "no SHA-256 available"
1284
+ };
1285
+ const clientSdkKeyDigest = sha256HexDigest(connectionPlan.clientSdkKey);
1286
+ const provider = caching?.provider ?? platformCachingProvider?.(clientSdkKeyDigest);
1273
1287
  if (!provider) return {
1274
1288
  kind: "no cache",
1275
1289
  reason: "no provider available"
@@ -1277,7 +1291,7 @@ const planCaching = (connectionPlan, caching, platformProvider) => {
1277
1291
  return {
1278
1292
  kind: "cache",
1279
1293
  provider,
1280
- clientSdkKey: connectionPlan.clientSdkKey
1294
+ clientSdkKeyDigest
1281
1295
  };
1282
1296
  };
1283
1297
  const contextTagFor = (context, instanceId) => {
@@ -1312,19 +1326,34 @@ const isConfigStateMap = (candidate) => {
1312
1326
  if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) return false;
1313
1327
  return Object.values(candidate).every((config) => config && typeof config === "object" && typeof config.id === "string" && typeof config.key === "string");
1314
1328
  };
1315
- const isEntryFor = (clientSdkKey, candidate) => {
1329
+ const isEntryFor = (clientSdkKeyDigest, candidate) => {
1316
1330
  if (!candidate || typeof candidate !== "object") return false;
1317
1331
  const entry = candidate;
1318
- return entry.formatVersion === ENTRY_FORMAT_VERSION && entry.clientSdkKey === clientSdkKey && isContextTag(entry.contextTag) && typeof entry.environmentId === "string" && typeof entry.projectId === "string" && isConfigStateMap(entry.configs) && typeof entry.receivedAt === "number";
1332
+ return entry.formatVersion === ENTRY_FORMAT_VERSION && entry.clientSdkKeyDigest === clientSdkKeyDigest && isContextTag(entry.contextTag) && typeof entry.environmentId === "string" && typeof entry.projectId === "string" && isConfigStateMap(entry.configs) && typeof entry.receivedAt === "number";
1333
+ };
1334
+ const hasFirstFormatVersion = (candidate) => !!candidate && typeof candidate === "object" && candidate.formatVersion === FIRST_ENTRY_FORMAT_VERSION;
1335
+ const parseEntry = (raw, clientSdkKeyDigest) => {
1336
+ let candidate;
1337
+ try {
1338
+ candidate = JSON.parse(raw);
1339
+ } catch {
1340
+ return { outcome: "unreadable" };
1341
+ }
1342
+ if (isEntryFor(clientSdkKeyDigest, candidate)) return {
1343
+ outcome: "stored",
1344
+ cachedValues: candidate
1345
+ };
1346
+ if (hasFirstFormatVersion(candidate)) return { outcome: "first format" };
1347
+ return { outcome: "unreadable" };
1319
1348
  };
1320
1349
  var CachedValuesStore = class {
1321
1350
  warned = /* @__PURE__ */ new Set();
1322
1351
  pendingRead;
1323
1352
  queuedCachedValues;
1324
1353
  writing = false;
1325
- constructor(provider, clientSdkKey, logger) {
1354
+ constructor(provider, clientSdkKeyDigest, logger) {
1326
1355
  this.provider = provider;
1327
- this.clientSdkKey = clientSdkKey;
1356
+ this.clientSdkKeyDigest = clientSdkKeyDigest;
1328
1357
  this.logger = logger;
1329
1358
  }
1330
1359
  read() {
@@ -1344,23 +1373,16 @@ var CachedValuesStore = class {
1344
1373
  return { outcome: "failed" };
1345
1374
  }
1346
1375
  if (raw == null || raw === "") return { outcome: "nothing stored" };
1347
- const entry = this.parseEntry(raw);
1348
- if (!entry) {
1349
- this.warnOnce("unreadable", "The values kept on the device could not be read and were removed, starting without them");
1350
- await this.removeEntry();
1351
- return { outcome: "unreadable" };
1352
- }
1353
- return {
1354
- outcome: "stored",
1355
- cachedValues: entry
1356
- };
1357
- }
1358
- parseEntry(raw) {
1359
- try {
1360
- const candidate = JSON.parse(raw);
1361
- return isEntryFor(this.clientSdkKey, candidate) ? candidate : void 0;
1362
- } catch {
1363
- return;
1376
+ const entryParseOutcome = parseEntry(raw, await this.clientSdkKeyDigest);
1377
+ switch (entryParseOutcome.outcome) {
1378
+ case "stored": return entryParseOutcome;
1379
+ case "first format":
1380
+ await this.removeEntry();
1381
+ return entryParseOutcome;
1382
+ case "unreadable":
1383
+ this.warnOnce("unreadable", "The values kept on the device could not be read and were removed, starting without them");
1384
+ await this.removeEntry();
1385
+ return entryParseOutcome;
1364
1386
  }
1365
1387
  }
1366
1388
  async writeQueued() {
@@ -1379,7 +1401,7 @@ var CachedValuesStore = class {
1379
1401
  async writeEntry(cachedValues) {
1380
1402
  const entry = {
1381
1403
  formatVersion: ENTRY_FORMAT_VERSION,
1382
- clientSdkKey: this.clientSdkKey,
1404
+ clientSdkKeyDigest: await this.clientSdkKeyDigest,
1383
1405
  ...cachedValues
1384
1406
  };
1385
1407
  const serialized = JSON.stringify(entry);
@@ -1443,7 +1465,7 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
1443
1465
  this.timeout = clientOptions?.connection?.timeout ?? 3e3;
1444
1466
  const urlFactory = internalClientOptions?.urlFactory ?? defaultUrlFactory;
1445
1467
  this.connectionPlan = planConnection(clientSdkKey, clientOptions?.connection, urlFactory);
1446
- this.cachedValuesStore = this.buildCachedValuesStore(planCaching(this.connectionPlan, clientOptions?.caching, internalClientOptions?.platformCachingProvider));
1468
+ this.cachedValuesStore = this.buildCachedValuesStore(planCaching(this.connectionPlan, clientOptions?.caching, internalClientOptions?.sha256HexDigest, internalClientOptions?.platformCachingProvider));
1447
1469
  this.telemetryClient = this.buildTelemetryClient(liveTelemetryClient, clientOptions);
1448
1470
  this.transport = internalClientOptions?.transport ?? this.buildTransport(sdkOptions, clientOptions, urlFactory, internalClientOptions);
1449
1471
  this.transport.on("configSetReceived", (configSet) => {
@@ -1486,7 +1508,7 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
1486
1508
  this.logger.debug(`[ConfigDirectorClient] No values are kept on the device: ${cachingPlan.reason}`);
1487
1509
  return;
1488
1510
  case "cache": {
1489
- const store = new CachedValuesStore(cachingPlan.provider, cachingPlan.clientSdkKey, this.logger);
1511
+ const store = new CachedValuesStore(cachingPlan.provider, cachingPlan.clientSdkKeyDigest, this.logger);
1490
1512
  store.read();
1491
1513
  return store;
1492
1514
  }
@@ -1502,6 +1524,7 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
1502
1524
  const read = await store.read();
1503
1525
  switch (read.outcome) {
1504
1526
  case "nothing stored":
1527
+ case "first format":
1505
1528
  case "unreadable":
1506
1529
  case "failed": return;
1507
1530
  case "stored": break;
@@ -1696,7 +1719,7 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
1696
1719
  updateWatchersForConfig(configKey) {
1697
1720
  const configState = this.configSet?.configs[configKey];
1698
1721
  this.handlersMap.get(configKey)?.forEach((h) => {
1699
- const value = this.getValueFromConfigState(configKey, configState, h.defaultValue);
1722
+ const value = this.evaluationOf(configKey, configState, h.defaultValue).value;
1700
1723
  h.handler(value);
1701
1724
  });
1702
1725
  }
@@ -1721,11 +1744,14 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
1721
1744
  } else handlers.splice(0);
1722
1745
  }
1723
1746
  getValue(configKey, defaultValue) {
1747
+ return this.evaluate(configKey, defaultValue).value;
1748
+ }
1749
+ evaluate(configKey, defaultValue) {
1724
1750
  this.validateDefaultValue(defaultValue);
1725
1751
  const configState = this.configSet?.configs[configKey];
1726
- return this.getValueFromConfigState(configKey, configState, defaultValue);
1752
+ return this.evaluationOf(configKey, configState, defaultValue);
1727
1753
  }
1728
- getValueFromConfigState(configKey, configState, defaultValue) {
1754
+ evaluationOf(configKey, configState, defaultValue) {
1729
1755
  if (!configState) {
1730
1756
  this.logger.debug(`[ConfigDirectorClient] No config state found for '${configKey}', returning default value '${defaultValue}'`);
1731
1757
  const reason = this.isReady ? "config-state-missing" : "client-not-ready";
@@ -1738,14 +1764,15 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
1738
1764
  usedDefault: true,
1739
1765
  evaluationReason: reason
1740
1766
  });
1741
- this.dispatchEvaluationEvent({
1767
+ const evaluation = {
1742
1768
  key: configKey,
1743
1769
  value: defaultValue,
1744
1770
  isDefaultValue: true,
1745
1771
  reason,
1746
1772
  context: this.context
1747
- });
1748
- return defaultValue;
1773
+ };
1774
+ this.dispatchEvaluationEvent(evaluation);
1775
+ return evaluation;
1749
1776
  }
1750
1777
  const parseResult = parseConfigValue(configState, defaultValue);
1751
1778
  const reason = this.servedReason(parseResult.reason);
@@ -1760,16 +1787,17 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
1760
1787
  usedDefault: parseResult.usedDefault,
1761
1788
  evaluationReason: reason
1762
1789
  });
1763
- this.dispatchEvaluationEvent({
1790
+ const evaluation = {
1764
1791
  key: configKey,
1765
1792
  value: parseResult.parsedValue,
1766
1793
  valueId: parseResult.parsedValueId ?? void 0,
1767
1794
  isDefaultValue: parseResult.usedDefault,
1768
1795
  reason,
1769
1796
  context: this.context
1770
- });
1797
+ };
1798
+ this.dispatchEvaluationEvent(evaluation);
1771
1799
  this.logger.debug(`[ConfigDirectorClient] Evaluated '${configKey}' to '${parseResult.parsedValue}'`);
1772
- return parseResult.parsedValue;
1800
+ return evaluation;
1773
1801
  }
1774
1802
  dispatchEvaluationEvent(event) {
1775
1803
  setTimeout(() => this.emit("configEvaluated", { evaluation: event }), 0);
@@ -2131,7 +2159,7 @@ const installTestClient = (testClient) => {
2131
2159
  const createTestClient = (options) => {
2132
2160
  return createTestClient$1({
2133
2161
  sdkName: "nuxt-sdk",
2134
- sdkVersion: "1.12.1"
2162
+ sdkVersion: "1.13.1"
2135
2163
  }, options);
2136
2164
  };
2137
2165
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@configdirector/nuxt-sdk",
3
- "version": "1.12.1",
3
+ "version": "1.13.1",
4
4
  "description": "Nuxt SDK for ConfigDirector. ConfigDirector is a remote configuration and feature flag service.",
5
5
  "license": "MIT",
6
6
  "author": "ConfigDirector",