@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.
- package/dist/module.d.mts +75 -1
- package/dist/module.json +1 -1
- package/dist/runtime/app/composables/useConfigDirectorClient.d.mts +1 -1
- package/dist/runtime/app/composables/useConfigDirectorClient.d.ts +1 -1
- package/dist/runtime/app/composables/useConfigDirectorClientHooks.d.mts +1 -1
- package/dist/runtime/app/composables/useConfigDirectorClientHooks.d.ts +1 -1
- package/dist/runtime/app/composables/useConfigDirectorContext.d.mts +1 -1
- package/dist/runtime/app/composables/useConfigDirectorContext.d.ts +1 -1
- package/dist/runtime/app/composables/useConfigDirectorValue.d.mts +1 -1
- package/dist/runtime/app/composables/useConfigDirectorValue.d.ts +1 -1
- package/dist/runtime/{native-values-ChQdi7c7.d.mts → native-values-CfhP0yLS.d.mts} +17 -6
- package/dist/runtime/nitro/offline-values.d.mts +1 -1
- package/dist/runtime/nitro/offline-values.d.ts +1 -1
- package/dist/runtime/nitro/plugin.d.mts +2 -2
- package/dist/runtime/nitro/plugin.d.ts +2 -2
- package/dist/runtime/nitro/plugin.mjs +54 -30
- package/dist/runtime/plugin.client.mjs +81 -51
- package/dist/runtime/plugin.server.mjs +3 -0
- package/dist/runtime/server/composables/useConfigDirectorServerClient.d.mts +1 -1
- package/dist/runtime/server/composables/useConfigDirectorServerClient.d.ts +1 -1
- package/dist/runtime/server/composables/useConfigDirectorServerHooks.d.mts +1 -1
- package/dist/runtime/server/composables/useConfigDirectorServerHooks.d.ts +1 -1
- package/dist/runtime/{types-CuzrFq9I.d.mts → types-BvqduMQP.d.mts} +11 -1
- package/dist/runtime/{types-BoDJiQsr.d.mts → types-qDEc6g8R.d.mts} +12 -1
- package/dist/testing.d.mts +27 -6
- package/dist/testing.mjs +64 -41
- 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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as ClientHooks } from "../../types-
|
|
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-
|
|
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;
|
|
@@ -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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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,5 +1,5 @@
|
|
|
1
|
-
import { o as ConnectionMode } from "../native-values-
|
|
2
|
-
import { n as ConfigDirectorClient } from "../types-
|
|
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-
|
|
2
|
-
import { n as ConfigDirectorClient } from "../types-
|
|
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:
|
|
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
|
|
7267
|
-
* which a segment condition looks its segment up in; a segment it does not hold
|
|
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.
|
|
7288
|
+
if (result.served !== void 0) served = {
|
|
7286
7289
|
ruleId: rule.id,
|
|
7287
|
-
|
|
7290
|
+
...result.served
|
|
7288
7291
|
};
|
|
7289
7292
|
}
|
|
7290
|
-
return {
|
|
7291
|
-
value: served
|
|
7292
|
-
|
|
7293
|
+
if (served) return {
|
|
7294
|
+
value: served.value,
|
|
7295
|
+
valueId: served.valueId,
|
|
7296
|
+
servedBy: {
|
|
7293
7297
|
kind: "rule",
|
|
7294
7298
|
ruleId: served.ruleId
|
|
7295
|
-
}
|
|
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
|
|
7308
|
-
if (rule.type == "percentage")
|
|
7309
|
-
else if (rule.type == "conditional")
|
|
7310
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
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 ?
|
|
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 ?
|
|
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
|
-
|
|
7555
|
-
|
|
7556
|
-
|
|
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.
|
|
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.
|
|
8335
|
+
return this.evaluationOf(configKey, configDefinition, defaultValue, context);
|
|
8314
8336
|
}
|
|
8315
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
8383
|
+
const evaluation = {
|
|
8361
8384
|
key: configKey,
|
|
8362
8385
|
value: defaultValue,
|
|
8363
8386
|
isDefaultValue: true,
|
|
8364
8387
|
reason,
|
|
8365
8388
|
context
|
|
8366
|
-
}
|
|
8367
|
-
|
|
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.
|
|
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
|
-
|
|
823
|
-
|
|
824
|
-
|
|
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 =
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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 = (
|
|
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.
|
|
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,
|
|
1358
|
+
constructor(provider, clientSdkKeyDigest, logger) {
|
|
1335
1359
|
this.provider = provider;
|
|
1336
|
-
this.
|
|
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
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
1756
|
+
return this.evaluationOf(configKey, configState, defaultValue);
|
|
1736
1757
|
}
|
|
1737
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
2155
|
-
const storageKey =
|
|
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, {
|
|
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.
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
package/dist/testing.d.mts
CHANGED
|
@@ -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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
820
|
-
|
|
821
|
-
|
|
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 =
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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 = (
|
|
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.
|
|
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,
|
|
1349
|
+
constructor(provider, clientSdkKeyDigest, logger) {
|
|
1326
1350
|
this.provider = provider;
|
|
1327
|
-
this.
|
|
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
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
1747
|
+
return this.evaluationOf(configKey, configState, defaultValue);
|
|
1727
1748
|
}
|
|
1728
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
2157
|
+
sdkVersion: "1.13.0"
|
|
2135
2158
|
}, options);
|
|
2136
2159
|
};
|
|
2137
2160
|
//#endregion
|
package/package.json
CHANGED