@configdirector/nuxt-sdk 1.12.1 → 1.13.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 (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 +81 -51
  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 +64 -41
  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.0"
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.0"
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
@@ -1265,11 +1268,12 @@ const utf8ByteLength = (text) => {
1265
1268
  };
1266
1269
  //#endregion
1267
1270
  //#region ../js-client-core/src/cached-values.ts
1268
- const ENTRY_FORMAT_VERSION = 1;
1271
+ const ENTRY_FORMAT_VERSION = 2;
1272
+ const FIRST_ENTRY_FORMAT_VERSION = 1;
1269
1273
  const DEFAULT_CONTEXT_KIND = "user";
1270
1274
  const CACHED_VALUES_STORAGE_KEY_PREFIX = "configdirector-sdk:cached-values:";
1271
1275
  const DEFAULT_CACHING_SIZE_LIMIT_BYTES = 256 * 1024;
1272
- const planCaching = (connectionPlan, caching, platformProvider) => {
1276
+ const planCaching = (connectionPlan, caching, sha256HexDigest, platformCachingProvider) => {
1273
1277
  if (connectionPlan.kind === "stay-offline") return {
1274
1278
  kind: "no cache",
1275
1279
  reason: "offline mode"
@@ -1278,7 +1282,12 @@ const planCaching = (connectionPlan, caching, platformProvider) => {
1278
1282
  kind: "no cache",
1279
1283
  reason: "turned off"
1280
1284
  };
1281
- const provider = caching?.provider ?? platformProvider?.(connectionPlan.clientSdkKey);
1285
+ if (!sha256HexDigest) return {
1286
+ kind: "no cache",
1287
+ reason: "no SHA-256 available"
1288
+ };
1289
+ const clientSdkKeyDigest = sha256HexDigest(connectionPlan.clientSdkKey);
1290
+ const provider = caching?.provider ?? platformCachingProvider?.(clientSdkKeyDigest);
1282
1291
  if (!provider) return {
1283
1292
  kind: "no cache",
1284
1293
  reason: "no provider available"
@@ -1286,7 +1295,7 @@ const planCaching = (connectionPlan, caching, platformProvider) => {
1286
1295
  return {
1287
1296
  kind: "cache",
1288
1297
  provider,
1289
- clientSdkKey: connectionPlan.clientSdkKey
1298
+ clientSdkKeyDigest
1290
1299
  };
1291
1300
  };
1292
1301
  const contextTagFor = (context, instanceId) => {
@@ -1321,19 +1330,34 @@ const isConfigStateMap = (candidate) => {
1321
1330
  if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) return false;
1322
1331
  return Object.values(candidate).every((config) => config && typeof config === "object" && typeof config.id === "string" && typeof config.key === "string");
1323
1332
  };
1324
- const isEntryFor = (clientSdkKey, candidate) => {
1333
+ const isEntryFor = (clientSdkKeyDigest, candidate) => {
1325
1334
  if (!candidate || typeof candidate !== "object") return false;
1326
1335
  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";
1336
+ 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";
1337
+ };
1338
+ const hasFirstFormatVersion = (candidate) => !!candidate && typeof candidate === "object" && candidate.formatVersion === FIRST_ENTRY_FORMAT_VERSION;
1339
+ const parseEntry = (raw, clientSdkKeyDigest) => {
1340
+ let candidate;
1341
+ try {
1342
+ candidate = JSON.parse(raw);
1343
+ } catch {
1344
+ return { outcome: "unreadable" };
1345
+ }
1346
+ if (isEntryFor(clientSdkKeyDigest, candidate)) return {
1347
+ outcome: "stored",
1348
+ cachedValues: candidate
1349
+ };
1350
+ if (hasFirstFormatVersion(candidate)) return { outcome: "first format" };
1351
+ return { outcome: "unreadable" };
1328
1352
  };
1329
1353
  var CachedValuesStore = class {
1330
1354
  warned = /* @__PURE__ */ new Set();
1331
1355
  pendingRead;
1332
1356
  queuedCachedValues;
1333
1357
  writing = false;
1334
- constructor(provider, clientSdkKey, logger) {
1358
+ constructor(provider, clientSdkKeyDigest, logger) {
1335
1359
  this.provider = provider;
1336
- this.clientSdkKey = clientSdkKey;
1360
+ this.clientSdkKeyDigest = clientSdkKeyDigest;
1337
1361
  this.logger = logger;
1338
1362
  }
1339
1363
  read() {
@@ -1353,23 +1377,16 @@ var CachedValuesStore = class {
1353
1377
  return { outcome: "failed" };
1354
1378
  }
1355
1379
  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;
1380
+ const entryParseOutcome = parseEntry(raw, await this.clientSdkKeyDigest);
1381
+ switch (entryParseOutcome.outcome) {
1382
+ case "stored": return entryParseOutcome;
1383
+ case "first format":
1384
+ await this.removeEntry();
1385
+ return entryParseOutcome;
1386
+ case "unreadable":
1387
+ this.warnOnce("unreadable", "The values kept on the device could not be read and were removed, starting without them");
1388
+ await this.removeEntry();
1389
+ return entryParseOutcome;
1373
1390
  }
1374
1391
  }
1375
1392
  async writeQueued() {
@@ -1388,7 +1405,7 @@ var CachedValuesStore = class {
1388
1405
  async writeEntry(cachedValues) {
1389
1406
  const entry = {
1390
1407
  formatVersion: ENTRY_FORMAT_VERSION,
1391
- clientSdkKey: this.clientSdkKey,
1408
+ clientSdkKeyDigest: await this.clientSdkKeyDigest,
1392
1409
  ...cachedValues
1393
1410
  };
1394
1411
  const serialized = JSON.stringify(entry);
@@ -1452,7 +1469,7 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
1452
1469
  this.timeout = clientOptions?.connection?.timeout ?? 3e3;
1453
1470
  const urlFactory = internalClientOptions?.urlFactory ?? defaultUrlFactory;
1454
1471
  this.connectionPlan = planConnection(clientSdkKey, clientOptions?.connection, urlFactory);
1455
- this.cachedValuesStore = this.buildCachedValuesStore(planCaching(this.connectionPlan, clientOptions?.caching, internalClientOptions?.platformCachingProvider));
1472
+ this.cachedValuesStore = this.buildCachedValuesStore(planCaching(this.connectionPlan, clientOptions?.caching, internalClientOptions?.sha256HexDigest, internalClientOptions?.platformCachingProvider));
1456
1473
  this.telemetryClient = this.buildTelemetryClient(liveTelemetryClient, clientOptions);
1457
1474
  this.transport = internalClientOptions?.transport ?? this.buildTransport(sdkOptions, clientOptions, urlFactory, internalClientOptions);
1458
1475
  this.transport.on("configSetReceived", (configSet) => {
@@ -1495,7 +1512,7 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
1495
1512
  this.logger.debug(`[ConfigDirectorClient] No values are kept on the device: ${cachingPlan.reason}`);
1496
1513
  return;
1497
1514
  case "cache": {
1498
- const store = new CachedValuesStore(cachingPlan.provider, cachingPlan.clientSdkKey, this.logger);
1515
+ const store = new CachedValuesStore(cachingPlan.provider, cachingPlan.clientSdkKeyDigest, this.logger);
1499
1516
  store.read();
1500
1517
  return store;
1501
1518
  }
@@ -1511,6 +1528,7 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
1511
1528
  const read = await store.read();
1512
1529
  switch (read.outcome) {
1513
1530
  case "nothing stored":
1531
+ case "first format":
1514
1532
  case "unreadable":
1515
1533
  case "failed": return;
1516
1534
  case "stored": break;
@@ -1705,7 +1723,7 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
1705
1723
  updateWatchersForConfig(configKey) {
1706
1724
  const configState = this.configSet?.configs[configKey];
1707
1725
  this.handlersMap.get(configKey)?.forEach((h) => {
1708
- const value = this.getValueFromConfigState(configKey, configState, h.defaultValue);
1726
+ const value = this.evaluationOf(configKey, configState, h.defaultValue).value;
1709
1727
  h.handler(value);
1710
1728
  });
1711
1729
  }
@@ -1730,11 +1748,14 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
1730
1748
  } else handlers.splice(0);
1731
1749
  }
1732
1750
  getValue(configKey, defaultValue) {
1751
+ return this.evaluate(configKey, defaultValue).value;
1752
+ }
1753
+ evaluate(configKey, defaultValue) {
1733
1754
  this.validateDefaultValue(defaultValue);
1734
1755
  const configState = this.configSet?.configs[configKey];
1735
- return this.getValueFromConfigState(configKey, configState, defaultValue);
1756
+ return this.evaluationOf(configKey, configState, defaultValue);
1736
1757
  }
1737
- getValueFromConfigState(configKey, configState, defaultValue) {
1758
+ evaluationOf(configKey, configState, defaultValue) {
1738
1759
  if (!configState) {
1739
1760
  this.logger.debug(`[ConfigDirectorClient] No config state found for '${configKey}', returning default value '${defaultValue}'`);
1740
1761
  const reason = this.isReady ? "config-state-missing" : "client-not-ready";
@@ -1747,14 +1768,15 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
1747
1768
  usedDefault: true,
1748
1769
  evaluationReason: reason
1749
1770
  });
1750
- this.dispatchEvaluationEvent({
1771
+ const evaluation = {
1751
1772
  key: configKey,
1752
1773
  value: defaultValue,
1753
1774
  isDefaultValue: true,
1754
1775
  reason,
1755
1776
  context: this.context
1756
- });
1757
- return defaultValue;
1777
+ };
1778
+ this.dispatchEvaluationEvent(evaluation);
1779
+ return evaluation;
1758
1780
  }
1759
1781
  const parseResult = parseConfigValue(configState, defaultValue);
1760
1782
  const reason = this.servedReason(parseResult.reason);
@@ -1769,16 +1791,17 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
1769
1791
  usedDefault: parseResult.usedDefault,
1770
1792
  evaluationReason: reason
1771
1793
  });
1772
- this.dispatchEvaluationEvent({
1794
+ const evaluation = {
1773
1795
  key: configKey,
1774
1796
  value: parseResult.parsedValue,
1775
1797
  valueId: parseResult.parsedValueId ?? void 0,
1776
1798
  isDefaultValue: parseResult.usedDefault,
1777
1799
  reason,
1778
1800
  context: this.context
1779
- });
1801
+ };
1802
+ this.dispatchEvaluationEvent(evaluation);
1780
1803
  this.logger.debug(`[ConfigDirectorClient] Evaluated '${configKey}' to '${parseResult.parsedValue}'`);
1781
- return parseResult.parsedValue;
1804
+ return evaluation;
1782
1805
  }
1783
1806
  dispatchEvaluationEvent(event) {
1784
1807
  setTimeout(() => this.emit("configEvaluated", { evaluation: event }), 0);
@@ -2143,16 +2166,10 @@ const generateValueId = async (v) => {
2143
2166
  };
2144
2167
  //#endregion
2145
2168
  //#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) => {
2169
+ const localStorageCachingProvider = (clientSdkKeyDigest) => {
2153
2170
  const storage = localStorageIfAvailable();
2154
- if (!storage || !subtleCryptoAvailable()) return;
2155
- const storageKey = storageKeyFor(clientSdkKey);
2171
+ if (!storage) return;
2172
+ const storageKey = clientSdkKeyDigest.then((digest) => CACHED_VALUES_STORAGE_KEY_PREFIX + digest);
2156
2173
  return {
2157
2174
  read: async () => storage.getItem(await storageKey),
2158
2175
  write: async (value) => storage.setItem(await storageKey, value),
@@ -2161,6 +2178,16 @@ const localStorageCachingProvider = (clientSdkKey) => {
2161
2178
  };
2162
2179
  };
2163
2180
  //#endregion
2181
+ //#region ../js-browser-client/src/sha256-hex-digest.ts
2182
+ const toHex = (bytes) => Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
2183
+ const sha256HexDigestIfAvailable = () => {
2184
+ if (typeof crypto === "undefined" || crypto.subtle == null) return;
2185
+ return async (text) => {
2186
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(text));
2187
+ return toHex(new Uint8Array(digest));
2188
+ };
2189
+ };
2190
+ //#endregion
2164
2191
  //#region ../js-browser-client/src/BrowserConfigDirectorClient.ts
2165
2192
  /**
2166
2193
  * Creates a browser client. With a client SDK key it connects to ConfigDirector; with
@@ -2184,7 +2211,10 @@ const createBrowserClient = (clientSdkKeyOrOptions, sdkOptions, clientOptions) =
2184
2211
  urlFactory: defaultUrlFactory,
2185
2212
  valueIdGenerator: generateValueId,
2186
2213
  clientSession
2187
- }), clientSdkKeyOrOptions, sdkOptions, clientOptions, { platformCachingProvider: localStorageCachingProvider });
2214
+ }), clientSdkKeyOrOptions, sdkOptions, clientOptions, {
2215
+ sha256HexDigest: sha256HexDigestIfAvailable(),
2216
+ platformCachingProvider: localStorageCachingProvider
2217
+ });
2188
2218
  };
2189
2219
  //#endregion
2190
2220
  //#region ../js-client-core/src/installedTestClient.ts
@@ -2351,7 +2381,7 @@ const buildClient = async (configdirector, logger) => {
2351
2381
  return {
2352
2382
  client: createBrowserClient(configdirector?.clientSdkKey || void 0, {
2353
2383
  sdkName: "nuxt-sdk",
2354
- sdkVersion: "1.12.1"
2384
+ sdkVersion: "1.13.0"
2355
2385
  }, {
2356
2386
  metadata: {
2357
2387
  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
@@ -1258,9 +1261,10 @@ const utf8ByteLength = (text) => {
1258
1261
  };
1259
1262
  //#endregion
1260
1263
  //#region ../js-client-core/src/cached-values.ts
1261
- const ENTRY_FORMAT_VERSION = 1;
1264
+ const ENTRY_FORMAT_VERSION = 2;
1265
+ const FIRST_ENTRY_FORMAT_VERSION = 1;
1262
1266
  const DEFAULT_CONTEXT_KIND = "user";
1263
- const planCaching = (connectionPlan, caching, platformProvider) => {
1267
+ const planCaching = (connectionPlan, caching, sha256HexDigest, platformCachingProvider) => {
1264
1268
  if (connectionPlan.kind === "stay-offline") return {
1265
1269
  kind: "no cache",
1266
1270
  reason: "offline mode"
@@ -1269,7 +1273,12 @@ const planCaching = (connectionPlan, caching, platformProvider) => {
1269
1273
  kind: "no cache",
1270
1274
  reason: "turned off"
1271
1275
  };
1272
- const provider = caching?.provider ?? platformProvider?.(connectionPlan.clientSdkKey);
1276
+ if (!sha256HexDigest) return {
1277
+ kind: "no cache",
1278
+ reason: "no SHA-256 available"
1279
+ };
1280
+ const clientSdkKeyDigest = sha256HexDigest(connectionPlan.clientSdkKey);
1281
+ const provider = caching?.provider ?? platformCachingProvider?.(clientSdkKeyDigest);
1273
1282
  if (!provider) return {
1274
1283
  kind: "no cache",
1275
1284
  reason: "no provider available"
@@ -1277,7 +1286,7 @@ const planCaching = (connectionPlan, caching, platformProvider) => {
1277
1286
  return {
1278
1287
  kind: "cache",
1279
1288
  provider,
1280
- clientSdkKey: connectionPlan.clientSdkKey
1289
+ clientSdkKeyDigest
1281
1290
  };
1282
1291
  };
1283
1292
  const contextTagFor = (context, instanceId) => {
@@ -1312,19 +1321,34 @@ const isConfigStateMap = (candidate) => {
1312
1321
  if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) return false;
1313
1322
  return Object.values(candidate).every((config) => config && typeof config === "object" && typeof config.id === "string" && typeof config.key === "string");
1314
1323
  };
1315
- const isEntryFor = (clientSdkKey, candidate) => {
1324
+ const isEntryFor = (clientSdkKeyDigest, candidate) => {
1316
1325
  if (!candidate || typeof candidate !== "object") return false;
1317
1326
  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";
1327
+ 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";
1328
+ };
1329
+ const hasFirstFormatVersion = (candidate) => !!candidate && typeof candidate === "object" && candidate.formatVersion === FIRST_ENTRY_FORMAT_VERSION;
1330
+ const parseEntry = (raw, clientSdkKeyDigest) => {
1331
+ let candidate;
1332
+ try {
1333
+ candidate = JSON.parse(raw);
1334
+ } catch {
1335
+ return { outcome: "unreadable" };
1336
+ }
1337
+ if (isEntryFor(clientSdkKeyDigest, candidate)) return {
1338
+ outcome: "stored",
1339
+ cachedValues: candidate
1340
+ };
1341
+ if (hasFirstFormatVersion(candidate)) return { outcome: "first format" };
1342
+ return { outcome: "unreadable" };
1319
1343
  };
1320
1344
  var CachedValuesStore = class {
1321
1345
  warned = /* @__PURE__ */ new Set();
1322
1346
  pendingRead;
1323
1347
  queuedCachedValues;
1324
1348
  writing = false;
1325
- constructor(provider, clientSdkKey, logger) {
1349
+ constructor(provider, clientSdkKeyDigest, logger) {
1326
1350
  this.provider = provider;
1327
- this.clientSdkKey = clientSdkKey;
1351
+ this.clientSdkKeyDigest = clientSdkKeyDigest;
1328
1352
  this.logger = logger;
1329
1353
  }
1330
1354
  read() {
@@ -1344,23 +1368,16 @@ var CachedValuesStore = class {
1344
1368
  return { outcome: "failed" };
1345
1369
  }
1346
1370
  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;
1371
+ const entryParseOutcome = parseEntry(raw, await this.clientSdkKeyDigest);
1372
+ switch (entryParseOutcome.outcome) {
1373
+ case "stored": return entryParseOutcome;
1374
+ case "first format":
1375
+ await this.removeEntry();
1376
+ return entryParseOutcome;
1377
+ case "unreadable":
1378
+ this.warnOnce("unreadable", "The values kept on the device could not be read and were removed, starting without them");
1379
+ await this.removeEntry();
1380
+ return entryParseOutcome;
1364
1381
  }
1365
1382
  }
1366
1383
  async writeQueued() {
@@ -1379,7 +1396,7 @@ var CachedValuesStore = class {
1379
1396
  async writeEntry(cachedValues) {
1380
1397
  const entry = {
1381
1398
  formatVersion: ENTRY_FORMAT_VERSION,
1382
- clientSdkKey: this.clientSdkKey,
1399
+ clientSdkKeyDigest: await this.clientSdkKeyDigest,
1383
1400
  ...cachedValues
1384
1401
  };
1385
1402
  const serialized = JSON.stringify(entry);
@@ -1443,7 +1460,7 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
1443
1460
  this.timeout = clientOptions?.connection?.timeout ?? 3e3;
1444
1461
  const urlFactory = internalClientOptions?.urlFactory ?? defaultUrlFactory;
1445
1462
  this.connectionPlan = planConnection(clientSdkKey, clientOptions?.connection, urlFactory);
1446
- this.cachedValuesStore = this.buildCachedValuesStore(planCaching(this.connectionPlan, clientOptions?.caching, internalClientOptions?.platformCachingProvider));
1463
+ this.cachedValuesStore = this.buildCachedValuesStore(planCaching(this.connectionPlan, clientOptions?.caching, internalClientOptions?.sha256HexDigest, internalClientOptions?.platformCachingProvider));
1447
1464
  this.telemetryClient = this.buildTelemetryClient(liveTelemetryClient, clientOptions);
1448
1465
  this.transport = internalClientOptions?.transport ?? this.buildTransport(sdkOptions, clientOptions, urlFactory, internalClientOptions);
1449
1466
  this.transport.on("configSetReceived", (configSet) => {
@@ -1486,7 +1503,7 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
1486
1503
  this.logger.debug(`[ConfigDirectorClient] No values are kept on the device: ${cachingPlan.reason}`);
1487
1504
  return;
1488
1505
  case "cache": {
1489
- const store = new CachedValuesStore(cachingPlan.provider, cachingPlan.clientSdkKey, this.logger);
1506
+ const store = new CachedValuesStore(cachingPlan.provider, cachingPlan.clientSdkKeyDigest, this.logger);
1490
1507
  store.read();
1491
1508
  return store;
1492
1509
  }
@@ -1502,6 +1519,7 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
1502
1519
  const read = await store.read();
1503
1520
  switch (read.outcome) {
1504
1521
  case "nothing stored":
1522
+ case "first format":
1505
1523
  case "unreadable":
1506
1524
  case "failed": return;
1507
1525
  case "stored": break;
@@ -1696,7 +1714,7 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
1696
1714
  updateWatchersForConfig(configKey) {
1697
1715
  const configState = this.configSet?.configs[configKey];
1698
1716
  this.handlersMap.get(configKey)?.forEach((h) => {
1699
- const value = this.getValueFromConfigState(configKey, configState, h.defaultValue);
1717
+ const value = this.evaluationOf(configKey, configState, h.defaultValue).value;
1700
1718
  h.handler(value);
1701
1719
  });
1702
1720
  }
@@ -1721,11 +1739,14 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
1721
1739
  } else handlers.splice(0);
1722
1740
  }
1723
1741
  getValue(configKey, defaultValue) {
1742
+ return this.evaluate(configKey, defaultValue).value;
1743
+ }
1744
+ evaluate(configKey, defaultValue) {
1724
1745
  this.validateDefaultValue(defaultValue);
1725
1746
  const configState = this.configSet?.configs[configKey];
1726
- return this.getValueFromConfigState(configKey, configState, defaultValue);
1747
+ return this.evaluationOf(configKey, configState, defaultValue);
1727
1748
  }
1728
- getValueFromConfigState(configKey, configState, defaultValue) {
1749
+ evaluationOf(configKey, configState, defaultValue) {
1729
1750
  if (!configState) {
1730
1751
  this.logger.debug(`[ConfigDirectorClient] No config state found for '${configKey}', returning default value '${defaultValue}'`);
1731
1752
  const reason = this.isReady ? "config-state-missing" : "client-not-ready";
@@ -1738,14 +1759,15 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
1738
1759
  usedDefault: true,
1739
1760
  evaluationReason: reason
1740
1761
  });
1741
- this.dispatchEvaluationEvent({
1762
+ const evaluation = {
1742
1763
  key: configKey,
1743
1764
  value: defaultValue,
1744
1765
  isDefaultValue: true,
1745
1766
  reason,
1746
1767
  context: this.context
1747
- });
1748
- return defaultValue;
1768
+ };
1769
+ this.dispatchEvaluationEvent(evaluation);
1770
+ return evaluation;
1749
1771
  }
1750
1772
  const parseResult = parseConfigValue(configState, defaultValue);
1751
1773
  const reason = this.servedReason(parseResult.reason);
@@ -1760,16 +1782,17 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
1760
1782
  usedDefault: parseResult.usedDefault,
1761
1783
  evaluationReason: reason
1762
1784
  });
1763
- this.dispatchEvaluationEvent({
1785
+ const evaluation = {
1764
1786
  key: configKey,
1765
1787
  value: parseResult.parsedValue,
1766
1788
  valueId: parseResult.parsedValueId ?? void 0,
1767
1789
  isDefaultValue: parseResult.usedDefault,
1768
1790
  reason,
1769
1791
  context: this.context
1770
- });
1792
+ };
1793
+ this.dispatchEvaluationEvent(evaluation);
1771
1794
  this.logger.debug(`[ConfigDirectorClient] Evaluated '${configKey}' to '${parseResult.parsedValue}'`);
1772
- return parseResult.parsedValue;
1795
+ return evaluation;
1773
1796
  }
1774
1797
  dispatchEvaluationEvent(event) {
1775
1798
  setTimeout(() => this.emit("configEvaluated", { evaluation: event }), 0);
@@ -2131,7 +2154,7 @@ const installTestClient = (testClient) => {
2131
2154
  const createTestClient = (options) => {
2132
2155
  return createTestClient$1({
2133
2156
  sdkName: "nuxt-sdk",
2134
- sdkVersion: "1.12.1"
2157
+ sdkVersion: "1.13.0"
2135
2158
  }, options);
2136
2159
  };
2137
2160
  //#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.0",
4
4
  "description": "Nuxt SDK for ConfigDirector. ConfigDirector is a remote configuration and feature flag service.",
5
5
  "license": "MIT",
6
6
  "author": "ConfigDirector",