@configdirector/nuxt-sdk 1.10.0 → 1.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/module.d.mts +76 -15
- package/dist/module.json +1 -1
- package/dist/module.mjs +13 -4
- 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/useConfigDirectorStatus.d.mts +1 -1
- package/dist/runtime/app/composables/useConfigDirectorStatus.d.ts +1 -1
- package/dist/runtime/app/composables/useConfigDirectorValue.d.mts +2 -2
- package/dist/runtime/app/composables/useConfigDirectorValue.d.ts +2 -2
- package/dist/runtime/{types-IPLvRsUO.d.mts → native-values-BV2k_xR2.d.mts} +11 -1
- package/dist/runtime/native-values-DZJjFBvk.mjs +116 -0
- package/dist/runtime/nitro/middleware.mjs +5 -1
- package/dist/runtime/nitro/offline-values.d.mts +7 -0
- package/dist/runtime/nitro/offline-values.d.ts +7 -0
- package/dist/runtime/nitro/offline-values.mjs +15 -0
- package/dist/runtime/nitro/plugin.d.mts +3 -1
- package/dist/runtime/nitro/plugin.d.ts +3 -1
- package/dist/runtime/nitro/plugin.mjs +232 -83
- package/dist/runtime/plugin.client.mjs +341 -86
- package/dist/runtime/server/composables/useConfigDirectorServerClient.d.mts +6 -0
- package/dist/runtime/server/composables/useConfigDirectorServerClient.d.ts +6 -0
- package/dist/runtime/server/composables/{useConfigDirectorClient.mjs → useConfigDirectorServerClient.mjs} +3 -3
- package/dist/runtime/server/composables/useConfigDirectorServerHooks.d.mts +1 -1
- package/dist/runtime/server/composables/useConfigDirectorServerHooks.d.ts +1 -1
- package/dist/runtime/{types-B9_4pADA.d.mts → types-CS7AMNWe.d.mts} +1 -1
- package/dist/runtime/{types-DQhBe4Ei.d.mts → types-Dt3bENKf.d.mts} +1 -1
- package/dist/testing.d.mts +13 -13
- package/dist/testing.mjs +292 -156
- package/package.json +1 -1
- package/dist/runtime/server/composables/useConfigDirectorClient.d.mts +0 -6
- package/dist/runtime/server/composables/useConfigDirectorClient.d.ts +0 -6
- /package/dist/runtime/{types-F0fKKk6-.d.mts → types-C30VJ25l.d.mts} +0 -0
package/dist/testing.d.mts
CHANGED
|
@@ -77,6 +77,15 @@ type ConfigDirectorContext = {
|
|
|
77
77
|
anonymous?: boolean;
|
|
78
78
|
};
|
|
79
79
|
//#endregion
|
|
80
|
+
//#region ../shared/src/native-values.d.ts
|
|
81
|
+
type NativeJsonValue = boolean | number | string | null | NativeJsonValue[] | {
|
|
82
|
+
[key: string]: NativeJsonValue;
|
|
83
|
+
};
|
|
84
|
+
type NativeValue = boolean | number | string | NativeJsonValue[] | {
|
|
85
|
+
[key: string]: NativeJsonValue;
|
|
86
|
+
};
|
|
87
|
+
type NativeValues = Record<string, NativeValue>;
|
|
88
|
+
//#endregion
|
|
80
89
|
//#region ../js-client-core/src/types.d.ts
|
|
81
90
|
type ClientConnectAction = "initialization" | "context update" | "network resume";
|
|
82
91
|
type ClientEvents = {
|
|
@@ -217,15 +226,6 @@ interface ConfigDirectorClient extends EventProvider<ClientEvents> {
|
|
|
217
226
|
dispose(): void;
|
|
218
227
|
}
|
|
219
228
|
//#endregion
|
|
220
|
-
//#region ../shared/src/testing/values.d.ts
|
|
221
|
-
type TestJsonValue = boolean | number | string | null | TestJsonValue[] | {
|
|
222
|
-
[key: string]: TestJsonValue;
|
|
223
|
-
};
|
|
224
|
-
type TestValue = boolean | number | string | TestJsonValue[] | {
|
|
225
|
-
[key: string]: TestJsonValue;
|
|
226
|
-
};
|
|
227
|
-
type TestValues = Record<string, TestValue>;
|
|
228
|
-
//#endregion
|
|
229
229
|
//#region ../js-client-core/src/testing/TestClient.d.ts
|
|
230
230
|
/**
|
|
231
231
|
* Options for {@link createTestClient}. Every option is optional.
|
|
@@ -236,7 +236,7 @@ type TestClientOptions = {
|
|
|
236
236
|
* config type follows from each value: a boolean, an integral number (`integer`), any other
|
|
237
237
|
* number (`float`), a string, or a plain object or array (`json`).
|
|
238
238
|
*/
|
|
239
|
-
values?:
|
|
239
|
+
values?: NativeValues;
|
|
240
240
|
/**
|
|
241
241
|
* The connection timeout of the SDK client, in milliseconds. It bounds how long a held
|
|
242
242
|
* `initialize` or `updateContext` waits. Defaults to the SDK's production timeout.
|
|
@@ -265,7 +265,7 @@ interface TestClient {
|
|
|
265
265
|
* Stores `value` under `key` and, when the client is connected, delivers it as an update, so
|
|
266
266
|
* reads, watchers, and `configsUpdated` handlers see it.
|
|
267
267
|
*/
|
|
268
|
-
setValue(key: string, value:
|
|
268
|
+
setValue(key: string, value: NativeValue): void;
|
|
269
269
|
/**
|
|
270
270
|
* Removes `key` and, when the client is connected, delivers a full update without it, so reads
|
|
271
271
|
* fall back to the in-code default value and watchers of `key` receive that default.
|
|
@@ -275,7 +275,7 @@ interface TestClient {
|
|
|
275
275
|
* Replaces every stored value with `values`, disarms any armed hold or failure, and, when the
|
|
276
276
|
* client is connected, delivers the new values as a full update.
|
|
277
277
|
*/
|
|
278
|
-
replaceValues(values:
|
|
278
|
+
replaceValues(values: NativeValues): void;
|
|
279
279
|
/**
|
|
280
280
|
* Makes the next `initialize` wait until {@link completeInitialization} or
|
|
281
281
|
* {@link failInitialization} is called, or until the client's timeout elapses.
|
|
@@ -371,4 +371,4 @@ declare class ConfigDirectorValidationError extends Error {
|
|
|
371
371
|
*/
|
|
372
372
|
declare const createTestClient: (options?: TestClientOptions) => TestClient;
|
|
373
373
|
//#endregion
|
|
374
|
-
export { ConfigDirectorConnectionError, ConfigDirectorValidationError, type TestClient, type TestClientOptions, type TestJsonValue, type TestValue, type TestValues, createTestClient, installTestClient };
|
|
374
|
+
export { ConfigDirectorConnectionError, ConfigDirectorValidationError, type TestClient, type TestClientOptions, type NativeJsonValue as TestJsonValue, type NativeValue as TestValue, type NativeValues as TestValues, createTestClient, installTestClient };
|
package/dist/testing.mjs
CHANGED
|
@@ -49,6 +49,13 @@ var ConfigDirectorValidationError = class ConfigDirectorValidationError extends
|
|
|
49
49
|
Object.setPrototypeOf(this, ConfigDirectorValidationError.prototype);
|
|
50
50
|
}
|
|
51
51
|
};
|
|
52
|
+
var ConfigDirectorInitializationError = class ConfigDirectorInitializationError extends Error {
|
|
53
|
+
name = "ConfigDirectorInitializationError";
|
|
54
|
+
constructor(message) {
|
|
55
|
+
super(message);
|
|
56
|
+
Object.setPrototypeOf(this, ConfigDirectorInitializationError.prototype);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
52
59
|
//#endregion
|
|
53
60
|
//#region ../eventsource/src/errors.ts
|
|
54
61
|
var StreamClosedError = class StreamClosedError extends Error {
|
|
@@ -481,6 +488,183 @@ var StreamingTransport = class {
|
|
|
481
488
|
}
|
|
482
489
|
};
|
|
483
490
|
//#endregion
|
|
491
|
+
//#region ../shared/src/native-values.ts
|
|
492
|
+
const encodeNativeValue = (key, value) => {
|
|
493
|
+
if (key.trim().length == 0) throw new ConfigDirectorValidationError("Invalid config key. The key of a value cannot be empty.");
|
|
494
|
+
return encode(key, value);
|
|
495
|
+
};
|
|
496
|
+
const encode = (key, value) => {
|
|
497
|
+
switch (typeof value) {
|
|
498
|
+
case "boolean": return {
|
|
499
|
+
type: "boolean",
|
|
500
|
+
value: value ? "true" : "false"
|
|
501
|
+
};
|
|
502
|
+
case "number": return encodeNumber(key, value);
|
|
503
|
+
case "string": return {
|
|
504
|
+
type: "string",
|
|
505
|
+
value
|
|
506
|
+
};
|
|
507
|
+
case "object": if (value !== null) {
|
|
508
|
+
assertJsonContents(key, value, "");
|
|
509
|
+
return {
|
|
510
|
+
type: "json",
|
|
511
|
+
value: JSON.stringify(value)
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
throw new ConfigDirectorValidationError(`Invalid value for '${key}': ${describe(value)}. Use a boolean, a number, a string, a plain object, or an array.`);
|
|
516
|
+
};
|
|
517
|
+
const encodeNumber = (key, value) => {
|
|
518
|
+
if (!Number.isFinite(value)) throw new ConfigDirectorValidationError(`Invalid value for '${key}': ${describe(value)} is not a finite number.`);
|
|
519
|
+
if (Number.isInteger(value)) {
|
|
520
|
+
if (!Number.isSafeInteger(value)) throw new ConfigDirectorValidationError(`Invalid value for '${key}': ${value} is not a safe integer, so the SDK could not read it back exactly.`);
|
|
521
|
+
return {
|
|
522
|
+
type: "integer",
|
|
523
|
+
value: String(value)
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
return {
|
|
527
|
+
type: "float",
|
|
528
|
+
value: toPlainDecimal(value)
|
|
529
|
+
};
|
|
530
|
+
};
|
|
531
|
+
const toPlainDecimal = (value) => {
|
|
532
|
+
const text = String(value);
|
|
533
|
+
const exponentIndex = text.indexOf("e-");
|
|
534
|
+
if (exponentIndex < 0) return text;
|
|
535
|
+
const sign = text.startsWith("-") ? "-" : "";
|
|
536
|
+
const [integerDigits = "", fractionDigits = ""] = text.slice(sign.length, exponentIndex).split(".");
|
|
537
|
+
const leadingZeros = Number(text.slice(exponentIndex + 2)) - integerDigits.length;
|
|
538
|
+
return `${sign}0.${"0".repeat(leadingZeros)}${integerDigits}${fractionDigits}`;
|
|
539
|
+
};
|
|
540
|
+
const assertJsonContents = (key, value, path) => {
|
|
541
|
+
if (value === null || typeof value === "boolean" || typeof value === "string") return;
|
|
542
|
+
if (typeof value === "number") {
|
|
543
|
+
if (!Number.isFinite(value)) throw invalidJsonContents(key, path, `${describe(value)} is not a finite number`);
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
if (Array.isArray(value)) {
|
|
547
|
+
value.forEach((item, index) => assertJsonContents(key, item, `${path}[${index}]`));
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
if (isPlainObject(value)) {
|
|
551
|
+
for (const [property, item] of Object.entries(value)) assertJsonContents(key, item, path ? `${path}.${property}` : property);
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
throw invalidJsonContents(key, path, `${describe(value)} cannot be encoded as JSON`);
|
|
555
|
+
};
|
|
556
|
+
const invalidJsonContents = (key, path, problem) => new ConfigDirectorValidationError(`Invalid value for '${key}'${path ? ` at '${path}'` : ""}: ${problem}. JSON contents can hold booleans, finite numbers, strings, null, arrays, and plain objects.`);
|
|
557
|
+
const isPlainObject = (value) => {
|
|
558
|
+
if (typeof value !== "object" || value === null) return false;
|
|
559
|
+
const prototype = Object.getPrototypeOf(value);
|
|
560
|
+
return prototype === Object.prototype || prototype === null;
|
|
561
|
+
};
|
|
562
|
+
const describe = (value) => {
|
|
563
|
+
if (value === null) return "null";
|
|
564
|
+
if (typeof value === "function") return "a function";
|
|
565
|
+
if (typeof value === "object") return `a ${value.constructor?.name ?? "object"} instance`;
|
|
566
|
+
if (typeof value === "symbol") return "a symbol";
|
|
567
|
+
return String(value);
|
|
568
|
+
};
|
|
569
|
+
//#endregion
|
|
570
|
+
//#region ../js-client-core/src/native-values.ts
|
|
571
|
+
const nativeValueConfigState = (id, key, value, valueId) => {
|
|
572
|
+
const encoded = encodeNativeValue(key, value);
|
|
573
|
+
return {
|
|
574
|
+
id,
|
|
575
|
+
key,
|
|
576
|
+
type: encoded.type,
|
|
577
|
+
value: encoded.value,
|
|
578
|
+
valueId
|
|
579
|
+
};
|
|
580
|
+
};
|
|
581
|
+
//#endregion
|
|
582
|
+
//#region ../js-client-core/src/OfflineTransport.ts
|
|
583
|
+
const OFFLINE_ENVIRONMENT = {
|
|
584
|
+
environmentId: "offline",
|
|
585
|
+
projectId: "offline"
|
|
586
|
+
};
|
|
587
|
+
var OfflineTransport = class {
|
|
588
|
+
emitter = new Emitter();
|
|
589
|
+
constructor(source) {
|
|
590
|
+
this.source = source;
|
|
591
|
+
}
|
|
592
|
+
async connect() {
|
|
593
|
+
const configs = loadOfflineConfigs(this.source);
|
|
594
|
+
this.emitter.emit("configSetReceived", {
|
|
595
|
+
...OFFLINE_ENVIRONMENT,
|
|
596
|
+
kind: "full",
|
|
597
|
+
configs
|
|
598
|
+
});
|
|
599
|
+
return this;
|
|
600
|
+
}
|
|
601
|
+
on = (name, handler) => this.emitter.on(name, handler);
|
|
602
|
+
off = (name, handler) => this.emitter.off(name, handler);
|
|
603
|
+
clear() {
|
|
604
|
+
this.emitter.clear();
|
|
605
|
+
}
|
|
606
|
+
close() {}
|
|
607
|
+
dispose() {
|
|
608
|
+
this.clear();
|
|
609
|
+
}
|
|
610
|
+
};
|
|
611
|
+
const loadOfflineConfigs = (source) => {
|
|
612
|
+
switch (source.kind) {
|
|
613
|
+
case "in-code-defaults": return {};
|
|
614
|
+
case "values": return encodeOfflineValues(source.values);
|
|
615
|
+
case "rejected": throw new ConfigDirectorInitializationError(source.message);
|
|
616
|
+
}
|
|
617
|
+
};
|
|
618
|
+
const encodeOfflineValues = (values) => {
|
|
619
|
+
try {
|
|
620
|
+
return Object.fromEntries(Object.entries(values).map(([key, value]) => [key, nativeValueConfigState(`offline:${key}`, key, value, void 0)]));
|
|
621
|
+
} catch (error) {
|
|
622
|
+
if (error instanceof ConfigDirectorValidationError) throw new ConfigDirectorInitializationError(`The offline values could not be loaded. ${error.message}`);
|
|
623
|
+
throw error;
|
|
624
|
+
}
|
|
625
|
+
};
|
|
626
|
+
//#endregion
|
|
627
|
+
//#region ../shared/src/constants.ts
|
|
628
|
+
const CLIENT_BASE_URL = "https://client-sdk-api.configdirector.com";
|
|
629
|
+
//#endregion
|
|
630
|
+
//#region ../js-client-core/src/connection-plan.ts
|
|
631
|
+
const planConnection = (clientSdkKey, connection, urlFactory) => {
|
|
632
|
+
if (connection?.mode === "offline") return {
|
|
633
|
+
kind: "stay-offline",
|
|
634
|
+
source: offlineSourceOf(connection)
|
|
635
|
+
};
|
|
636
|
+
const mode = connection?.mode ?? "streaming";
|
|
637
|
+
if (!clientSdkKey || clientSdkKey.trim().length == 0) throw new ConfigDirectorValidationError("No client SDK key was provided, the client cannot be instantiated without a valid client SDK key");
|
|
638
|
+
if (connection?.values !== void 0) return {
|
|
639
|
+
kind: "stay-offline",
|
|
640
|
+
source: {
|
|
641
|
+
kind: "rejected",
|
|
642
|
+
message: `The connection option 'values' applies only when the connection mode is 'offline', but the mode is '${mode}'.`
|
|
643
|
+
}
|
|
644
|
+
};
|
|
645
|
+
return {
|
|
646
|
+
kind: "connect",
|
|
647
|
+
mode,
|
|
648
|
+
clientSdkKey,
|
|
649
|
+
baseUrl: baseUrlOf(connection?.url, urlFactory)
|
|
650
|
+
};
|
|
651
|
+
};
|
|
652
|
+
const baseUrlOf = (url, urlFactory) => {
|
|
653
|
+
if (!url) return CLIENT_BASE_URL;
|
|
654
|
+
try {
|
|
655
|
+
return urlFactory(url);
|
|
656
|
+
} catch (error) {
|
|
657
|
+
throw new ConfigDirectorValidationError(`Invalid base URL '${url}'. Parsing failed: ${error}`);
|
|
658
|
+
}
|
|
659
|
+
};
|
|
660
|
+
const offlineSourceOf = (connection) => {
|
|
661
|
+
if (connection.values !== void 0) return {
|
|
662
|
+
kind: "values",
|
|
663
|
+
values: connection.values
|
|
664
|
+
};
|
|
665
|
+
return { kind: "in-code-defaults" };
|
|
666
|
+
};
|
|
667
|
+
//#endregion
|
|
484
668
|
//#region ../shared/src/value-parser.ts
|
|
485
669
|
const getRequestedType = (defaultValue) => {
|
|
486
670
|
const baseType = typeof defaultValue;
|
|
@@ -700,6 +884,14 @@ const createDefaultLogger = (level, messageDecorator) => {
|
|
|
700
884
|
return new DefaultConsoleLogger(level ?? "warn", messageDecorator ?? new LogMessageDecorator());
|
|
701
885
|
};
|
|
702
886
|
//#endregion
|
|
887
|
+
//#region ../js-client-core/src/telemetry/discarding.ts
|
|
888
|
+
const discardingTelemetryClient = {
|
|
889
|
+
updateContext: async () => {},
|
|
890
|
+
evaluatedConfig: () => {},
|
|
891
|
+
recordInvalidContext: () => {},
|
|
892
|
+
close: async () => {}
|
|
893
|
+
};
|
|
894
|
+
//#endregion
|
|
703
895
|
//#region ../shared/src/url.ts
|
|
704
896
|
const defaultUrlFactory = (input, base) => new URL(input, base?.toString());
|
|
705
897
|
//#endregion
|
|
@@ -1056,7 +1248,7 @@ const admitContext = (context) => {
|
|
|
1056
1248
|
//#endregion
|
|
1057
1249
|
//#region ../js-client-core/src/DefaultConfigDirectorClient.ts
|
|
1058
1250
|
const MAX_EXPONENTIAL_DELAY = 9;
|
|
1059
|
-
var DefaultConfigDirectorClient = class {
|
|
1251
|
+
var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
|
|
1060
1252
|
logger;
|
|
1061
1253
|
telemetryClient;
|
|
1062
1254
|
configSet;
|
|
@@ -1071,18 +1263,20 @@ var DefaultConfigDirectorClient = class {
|
|
|
1071
1263
|
readyPromise;
|
|
1072
1264
|
readyResolve;
|
|
1073
1265
|
currentContext;
|
|
1074
|
-
|
|
1266
|
+
connectionPlan;
|
|
1075
1267
|
clientSession;
|
|
1076
|
-
|
|
1268
|
+
static from(liveTelemetryClient, clientSdkKeyOrOptions, sdkOptions, clientOptions) {
|
|
1269
|
+
if (typeof clientSdkKeyOrOptions === "object") return new DefaultConfigDirectorClient(liveTelemetryClient, void 0, sdkOptions, clientSdkKeyOrOptions);
|
|
1270
|
+
return new DefaultConfigDirectorClient(liveTelemetryClient, clientSdkKeyOrOptions, sdkOptions, clientOptions);
|
|
1271
|
+
}
|
|
1272
|
+
constructor(liveTelemetryClient, clientSdkKey, sdkOptions, clientOptions, internalClientOptions) {
|
|
1077
1273
|
this.logger = clientOptions?.logger ?? createDefaultLogger();
|
|
1078
1274
|
this.clientSession = resolveClientSession(this.logger, internalClientOptions?.instanceIdStorage);
|
|
1079
1275
|
this.timeout = clientOptions?.connection?.timeout ?? 3e3;
|
|
1080
|
-
this.validateSdkKeyPresence(clientSdkKey);
|
|
1081
1276
|
const urlFactory = internalClientOptions?.urlFactory ?? defaultUrlFactory;
|
|
1082
|
-
|
|
1083
|
-
this.
|
|
1084
|
-
this.
|
|
1085
|
-
this.transport = internalClientOptions?.transport ?? this.buildTransport(clientSdkKey, sdkOptions, baseUrl, urlFactory, clientOptions, internalClientOptions);
|
|
1277
|
+
this.connectionPlan = planConnection(clientSdkKey, clientOptions?.connection, urlFactory);
|
|
1278
|
+
this.telemetryClient = this.buildTelemetryClient(liveTelemetryClient, clientOptions);
|
|
1279
|
+
this.transport = internalClientOptions?.transport ?? this.buildTransport(sdkOptions, clientOptions, urlFactory, internalClientOptions);
|
|
1086
1280
|
this.transport.on("configSetReceived", (configSet) => {
|
|
1087
1281
|
this.hasReceivedConfigSet = true;
|
|
1088
1282
|
this.readyResolve?.();
|
|
@@ -1119,8 +1313,8 @@ var DefaultConfigDirectorClient = class {
|
|
|
1119
1313
|
const handlers = Array.isArray(handler) ? handler : [handler];
|
|
1120
1314
|
for (const h of handlers) if (typeof h === "function") this.on(event, h);
|
|
1121
1315
|
}
|
|
1122
|
-
|
|
1123
|
-
if (
|
|
1316
|
+
pollingIntervalFor(mode, configuredSeconds) {
|
|
1317
|
+
if (mode !== "polling") return;
|
|
1124
1318
|
const resolved = resolvePollingInterval(configuredSeconds, {
|
|
1125
1319
|
defaultSeconds: 60,
|
|
1126
1320
|
minimumSeconds: 30
|
|
@@ -1128,31 +1322,47 @@ var DefaultConfigDirectorClient = class {
|
|
|
1128
1322
|
if (resolved.warning) this.logger.warn(`[ConfigDirectorClient] ${resolved.warning}`);
|
|
1129
1323
|
return resolved.seconds;
|
|
1130
1324
|
}
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1325
|
+
buildTelemetryClient(liveTelemetryClient, clientOptions) {
|
|
1326
|
+
const plan = this.connectionPlan;
|
|
1327
|
+
switch (plan.kind) {
|
|
1328
|
+
case "stay-offline": return discardingTelemetryClient;
|
|
1329
|
+
case "connect": return liveTelemetryClient({
|
|
1330
|
+
clientSdkKey: plan.clientSdkKey,
|
|
1331
|
+
baseUrl: plan.baseUrl,
|
|
1332
|
+
logger: this.logger,
|
|
1333
|
+
metadata: clientOptions?.metadata
|
|
1334
|
+
});
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
buildTransport(sdkOptions, clientOptions, urlFactory, internalClientOptions) {
|
|
1338
|
+
const plan = this.connectionPlan;
|
|
1339
|
+
switch (plan.kind) {
|
|
1340
|
+
case "stay-offline": return new OfflineTransport(plan.source);
|
|
1341
|
+
case "connect": return new (this.getTransportConstructor(plan.mode))({
|
|
1342
|
+
clientSession: this.clientSession,
|
|
1343
|
+
clientSdkKey: plan.clientSdkKey,
|
|
1344
|
+
baseUrl: plan.baseUrl,
|
|
1345
|
+
resolveUrl: urlFactory,
|
|
1346
|
+
metaContext: {
|
|
1347
|
+
...clientOptions?.metadata,
|
|
1348
|
+
sdkName: sdkOptions.sdkName,
|
|
1349
|
+
sdkVersion: sdkOptions.sdkVersion,
|
|
1350
|
+
userAgent: readUserAgent(),
|
|
1351
|
+
host: readHost()
|
|
1352
|
+
},
|
|
1353
|
+
logger: this.logger,
|
|
1354
|
+
fetch: internalClientOptions?.fetch,
|
|
1355
|
+
connectionRetryDelay: internalClientOptions?.connectionRetryDelay ?? ((attempt) => {
|
|
1356
|
+
return Math.pow(2, Math.min(attempt, MAX_EXPONENTIAL_DELAY)) * 1e3;
|
|
1357
|
+
}),
|
|
1358
|
+
pollingInterval: this.pollingIntervalFor(plan.mode, clientOptions?.connection?.pollingInterval)
|
|
1359
|
+
});
|
|
1360
|
+
}
|
|
1151
1361
|
}
|
|
1152
|
-
getTransportConstructor(
|
|
1153
|
-
switch (
|
|
1362
|
+
getTransportConstructor(mode) {
|
|
1363
|
+
switch (mode) {
|
|
1154
1364
|
case "polling": return PollingTransport;
|
|
1155
|
-
|
|
1365
|
+
case "streaming": return StreamingTransport;
|
|
1156
1366
|
}
|
|
1157
1367
|
}
|
|
1158
1368
|
async initialize(context) {
|
|
@@ -1160,17 +1370,28 @@ var DefaultConfigDirectorClient = class {
|
|
|
1160
1370
|
await this.submitContext(context, "initialization");
|
|
1161
1371
|
}
|
|
1162
1372
|
async updateContext(context) {
|
|
1163
|
-
|
|
1373
|
+
switch (this.connectionPlan.kind) {
|
|
1374
|
+
case "connect":
|
|
1375
|
+
await this.submitContext(context, "context update");
|
|
1376
|
+
return;
|
|
1377
|
+
case "stay-offline":
|
|
1378
|
+
this.storeContext(this.admittedContextFor(context, "context update"));
|
|
1379
|
+
return;
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
storeContext(context) {
|
|
1383
|
+
this.currentContext = context;
|
|
1384
|
+
this.emit("contextUpdated", { context });
|
|
1164
1385
|
}
|
|
1165
1386
|
async submitContext(context, submission) {
|
|
1166
|
-
await this.connectToTransport(this.
|
|
1387
|
+
await this.connectToTransport(this.admittedContextFor(context, submission), submission);
|
|
1167
1388
|
}
|
|
1168
|
-
|
|
1389
|
+
admittedContextFor(suppliedContext, submission) {
|
|
1169
1390
|
const admission = admitContext(suppliedContext);
|
|
1170
1391
|
switch (admission.outcome) {
|
|
1171
1392
|
case "admitted": return admission.context;
|
|
1172
1393
|
case "ignored":
|
|
1173
|
-
this.logger.error(`[ConfigDirectorClient] Ignored the context supplied for ${submission} because its kind '${admission.kindSample}' is invalid;
|
|
1394
|
+
this.logger.error(`[ConfigDirectorClient] Ignored the context supplied for ${submission} because its kind '${admission.kindSample}' is invalid; continuing as if no context was supplied. A context kind has 1 to 64 characters, using only lowercase letters, digits, '-' and '_'.`);
|
|
1174
1395
|
this.telemetryClient.recordInvalidContext(admission.kindSample);
|
|
1175
1396
|
return;
|
|
1176
1397
|
}
|
|
@@ -1202,11 +1423,21 @@ var DefaultConfigDirectorClient = class {
|
|
|
1202
1423
|
if (remainingTimeout > 0) await Promise.race([this.readyPromise, new Promise((resolve) => {
|
|
1203
1424
|
this.timeoutTimer = setTimeout(() => resolve(), remainingTimeout);
|
|
1204
1425
|
})]).finally(() => clearTimeout(this.timeoutTimer));
|
|
1205
|
-
if (!this.ready) this.logger.warn(`[ConfigDirectorClient] Timed out waiting for ${caller} after ${this.timeout}ms.
|
|
1426
|
+
if (!this.ready) this.logger.warn(`[ConfigDirectorClient] Timed out waiting for ${caller} after ${this.timeout}ms. ${this.timeoutWarningDetails()}`);
|
|
1206
1427
|
} catch (error) {
|
|
1428
|
+
if (error instanceof ConfigDirectorInitializationError) {
|
|
1429
|
+
this.initializing = false;
|
|
1430
|
+
throw error;
|
|
1431
|
+
}
|
|
1207
1432
|
this.logger.error(`[ConfigDirectorClient] An error occurred during ${caller}: `, error);
|
|
1208
1433
|
}
|
|
1209
1434
|
}
|
|
1435
|
+
timeoutWarningDetails() {
|
|
1436
|
+
switch (this.connectionPlan.kind) {
|
|
1437
|
+
case "connect": return "The client will continue to retry since there were no fatal errors detected. Configs will return the default value until the connection succeeds.";
|
|
1438
|
+
case "stay-offline": return "The offline values were not delivered, so configs return the default value.";
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1210
1441
|
findKeysRemovedBy(configSet) {
|
|
1211
1442
|
if (!this.configSet || configSet.kind != "full") return [];
|
|
1212
1443
|
return Object.keys(this.configSet.configs).filter((key) => !Object.prototype.hasOwnProperty.call(configSet.configs, key));
|
|
@@ -1301,17 +1532,6 @@ var DefaultConfigDirectorClient = class {
|
|
|
1301
1532
|
this.logger.error(`[ConfigDirectorClient] Error executing event handlers for '${name}'`, error);
|
|
1302
1533
|
}
|
|
1303
1534
|
}
|
|
1304
|
-
parseUrl(url, urlFactory) {
|
|
1305
|
-
if (!url) return;
|
|
1306
|
-
try {
|
|
1307
|
-
return urlFactory(url);
|
|
1308
|
-
} catch (error) {
|
|
1309
|
-
throw new ConfigDirectorValidationError(`Invalid base URL '${url}'. Parsing failed: ${error}`);
|
|
1310
|
-
}
|
|
1311
|
-
}
|
|
1312
|
-
validateSdkKeyPresence(sdkKey) {
|
|
1313
|
-
if (!sdkKey || sdkKey.trim().length == 0) throw new ConfigDirectorValidationError("No client SDK key was provided, the client cannot be instantiated without a valid client SDK key");
|
|
1314
|
-
}
|
|
1315
1535
|
validateDefaultValue(defaultValue) {
|
|
1316
1536
|
if (defaultValue === void 0 || defaultValue === null) throw new ConfigDirectorValidationError("Invalid default value. The default value for a config must be defined and non-null.");
|
|
1317
1537
|
if (typeof defaultValue === "function") throw new ConfigDirectorValidationError("Invalid default value. The default value for a config cannot be a function.");
|
|
@@ -1337,14 +1557,24 @@ var DefaultConfigDirectorClient = class {
|
|
|
1337
1557
|
this.handlersMap.clear();
|
|
1338
1558
|
}
|
|
1339
1559
|
pauseNetwork() {
|
|
1340
|
-
this.
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1560
|
+
switch (this.connectionPlan.kind) {
|
|
1561
|
+
case "stay-offline": return;
|
|
1562
|
+
case "connect":
|
|
1563
|
+
this.logger.debug("[ConfigDirectorClient] pauseNetwork() called, pausing transport connection");
|
|
1564
|
+
clearTimeout(this.timeoutTimer);
|
|
1565
|
+
this.timeoutTimer = void 0;
|
|
1566
|
+
this.transport.close();
|
|
1567
|
+
this.ready = false;
|
|
1568
|
+
return;
|
|
1569
|
+
}
|
|
1345
1570
|
}
|
|
1346
1571
|
async resumeNetwork() {
|
|
1347
|
-
|
|
1572
|
+
switch (this.connectionPlan.kind) {
|
|
1573
|
+
case "stay-offline": return;
|
|
1574
|
+
case "connect":
|
|
1575
|
+
await this.connectToTransport(this.currentContext, "network resume");
|
|
1576
|
+
return;
|
|
1577
|
+
}
|
|
1348
1578
|
}
|
|
1349
1579
|
async close() {
|
|
1350
1580
|
this.logger.debug("[ConfigDirectorClient] close() has been called, closing connection to server");
|
|
@@ -1361,97 +1591,6 @@ var DefaultConfigDirectorClient = class {
|
|
|
1361
1591
|
}
|
|
1362
1592
|
};
|
|
1363
1593
|
//#endregion
|
|
1364
|
-
//#region ../shared/src/testing/values.ts
|
|
1365
|
-
const encodeTestValue$1 = (key, value) => {
|
|
1366
|
-
if (key.trim().length == 0) throw new ConfigDirectorValidationError("Invalid config key. The key of a test value cannot be empty.");
|
|
1367
|
-
return encode(key, value);
|
|
1368
|
-
};
|
|
1369
|
-
const encode = (key, value) => {
|
|
1370
|
-
switch (typeof value) {
|
|
1371
|
-
case "boolean": return {
|
|
1372
|
-
type: "boolean",
|
|
1373
|
-
value: value ? "true" : "false"
|
|
1374
|
-
};
|
|
1375
|
-
case "number": return encodeNumber(key, value);
|
|
1376
|
-
case "string": return {
|
|
1377
|
-
type: "string",
|
|
1378
|
-
value
|
|
1379
|
-
};
|
|
1380
|
-
case "object": if (value !== null) {
|
|
1381
|
-
assertJsonContents(key, value, "");
|
|
1382
|
-
return {
|
|
1383
|
-
type: "json",
|
|
1384
|
-
value: JSON.stringify(value)
|
|
1385
|
-
};
|
|
1386
|
-
}
|
|
1387
|
-
}
|
|
1388
|
-
throw new ConfigDirectorValidationError(`Invalid test value for '${key}': ${describe(value)}. Use a boolean, a number, a string, a plain object, or an array.`);
|
|
1389
|
-
};
|
|
1390
|
-
const encodeNumber = (key, value) => {
|
|
1391
|
-
if (!Number.isFinite(value)) throw new ConfigDirectorValidationError(`Invalid test value for '${key}': ${describe(value)} is not a finite number.`);
|
|
1392
|
-
if (Number.isInteger(value)) {
|
|
1393
|
-
if (!Number.isSafeInteger(value)) throw new ConfigDirectorValidationError(`Invalid test value for '${key}': ${value} is not a safe integer, so the SDK could not read it back exactly.`);
|
|
1394
|
-
return {
|
|
1395
|
-
type: "integer",
|
|
1396
|
-
value: String(value)
|
|
1397
|
-
};
|
|
1398
|
-
}
|
|
1399
|
-
return {
|
|
1400
|
-
type: "float",
|
|
1401
|
-
value: toPlainDecimal(value)
|
|
1402
|
-
};
|
|
1403
|
-
};
|
|
1404
|
-
const toPlainDecimal = (value) => {
|
|
1405
|
-
const text = String(value);
|
|
1406
|
-
const exponentIndex = text.indexOf("e-");
|
|
1407
|
-
if (exponentIndex < 0) return text;
|
|
1408
|
-
const sign = text.startsWith("-") ? "-" : "";
|
|
1409
|
-
const [integerDigits = "", fractionDigits = ""] = text.slice(sign.length, exponentIndex).split(".");
|
|
1410
|
-
const leadingZeros = Number(text.slice(exponentIndex + 2)) - integerDigits.length;
|
|
1411
|
-
return `${sign}0.${"0".repeat(leadingZeros)}${integerDigits}${fractionDigits}`;
|
|
1412
|
-
};
|
|
1413
|
-
const assertJsonContents = (key, value, path) => {
|
|
1414
|
-
if (value === null || typeof value === "boolean" || typeof value === "string") return;
|
|
1415
|
-
if (typeof value === "number") {
|
|
1416
|
-
if (!Number.isFinite(value)) throw invalidJsonContents(key, path, `${describe(value)} is not a finite number`);
|
|
1417
|
-
return;
|
|
1418
|
-
}
|
|
1419
|
-
if (Array.isArray(value)) {
|
|
1420
|
-
value.forEach((item, index) => assertJsonContents(key, item, `${path}[${index}]`));
|
|
1421
|
-
return;
|
|
1422
|
-
}
|
|
1423
|
-
if (isPlainObject(value)) {
|
|
1424
|
-
for (const [property, item] of Object.entries(value)) assertJsonContents(key, item, path ? `${path}.${property}` : property);
|
|
1425
|
-
return;
|
|
1426
|
-
}
|
|
1427
|
-
throw invalidJsonContents(key, path, `${describe(value)} cannot be encoded as JSON`);
|
|
1428
|
-
};
|
|
1429
|
-
const invalidJsonContents = (key, path, problem) => new ConfigDirectorValidationError(`Invalid test value for '${key}'${path ? ` at '${path}'` : ""}: ${problem}. JSON contents can hold booleans, finite numbers, strings, null, arrays, and plain objects.`);
|
|
1430
|
-
const isPlainObject = (value) => {
|
|
1431
|
-
if (typeof value !== "object" || value === null) return false;
|
|
1432
|
-
const prototype = Object.getPrototypeOf(value);
|
|
1433
|
-
return prototype === Object.prototype || prototype === null;
|
|
1434
|
-
};
|
|
1435
|
-
const describe = (value) => {
|
|
1436
|
-
if (value === null) return "null";
|
|
1437
|
-
if (typeof value === "function") return "a function";
|
|
1438
|
-
if (typeof value === "object") return `a ${value.constructor?.name ?? "object"} instance`;
|
|
1439
|
-
if (typeof value === "symbol") return "a symbol";
|
|
1440
|
-
return String(value);
|
|
1441
|
-
};
|
|
1442
|
-
//#endregion
|
|
1443
|
-
//#region ../js-client-core/src/testing/values.ts
|
|
1444
|
-
const encodeTestValue = (key, value, revision) => {
|
|
1445
|
-
const encoded = encodeTestValue$1(key, value);
|
|
1446
|
-
return {
|
|
1447
|
-
id: `test-config:${key}`,
|
|
1448
|
-
key,
|
|
1449
|
-
type: encoded.type,
|
|
1450
|
-
value: encoded.value,
|
|
1451
|
-
valueId: `test-value:${key}:${revision}`
|
|
1452
|
-
};
|
|
1453
|
-
};
|
|
1454
|
-
//#endregion
|
|
1455
1594
|
//#region ../js-client-core/src/testing/InMemoryConnection.ts
|
|
1456
1595
|
const TEST_ENVIRONMENT = {
|
|
1457
1596
|
environmentId: "test-environment",
|
|
@@ -1486,7 +1625,7 @@ var InMemoryConnection = class {
|
|
|
1486
1625
|
return [...this.recordedContexts];
|
|
1487
1626
|
}
|
|
1488
1627
|
setValue(key, value) {
|
|
1489
|
-
const configState =
|
|
1628
|
+
const configState = this.encode(key, value);
|
|
1490
1629
|
this.configs.set(key, configState);
|
|
1491
1630
|
this.deliverIfConnected({
|
|
1492
1631
|
...TEST_ENVIRONMENT,
|
|
@@ -1631,19 +1770,16 @@ var InMemoryConnection = class {
|
|
|
1631
1770
|
}
|
|
1632
1771
|
encodeAll(values) {
|
|
1633
1772
|
const encoded = /* @__PURE__ */ new Map();
|
|
1634
|
-
for (const [key, value] of Object.entries(values)) encoded.set(key,
|
|
1773
|
+
for (const [key, value] of Object.entries(values)) encoded.set(key, this.encode(key, value));
|
|
1635
1774
|
return encoded;
|
|
1636
1775
|
}
|
|
1776
|
+
encode(key, value) {
|
|
1777
|
+
return nativeValueConfigState(`test-config:${key}`, key, value, `test-value:${key}:${++this.revision}`);
|
|
1778
|
+
}
|
|
1637
1779
|
};
|
|
1638
1780
|
//#endregion
|
|
1639
1781
|
//#region ../js-client-core/src/testing/TestClient.ts
|
|
1640
1782
|
const TEST_CLIENT_SDK_KEY = "test-client";
|
|
1641
|
-
const noopTelemetryClient = {
|
|
1642
|
-
updateContext: async () => {},
|
|
1643
|
-
evaluatedConfig: () => {},
|
|
1644
|
-
recordInvalidContext: () => {},
|
|
1645
|
-
close: async () => {}
|
|
1646
|
-
};
|
|
1647
1783
|
var InMemoryTestClient = class {
|
|
1648
1784
|
constructor(client, connection) {
|
|
1649
1785
|
this.client = client;
|
|
@@ -1683,7 +1819,7 @@ var InMemoryTestClient = class {
|
|
|
1683
1819
|
const createTestClient$1 = (sdkOptions, options) => {
|
|
1684
1820
|
const logger = options?.logger ?? createDefaultLogger();
|
|
1685
1821
|
const connection = new InMemoryConnection(logger, options?.values ?? {});
|
|
1686
|
-
return new InMemoryTestClient(new DefaultConfigDirectorClient(
|
|
1822
|
+
return new InMemoryTestClient(new DefaultConfigDirectorClient(() => discardingTelemetryClient, TEST_CLIENT_SDK_KEY, sdkOptions, {
|
|
1687
1823
|
logger,
|
|
1688
1824
|
connection: { timeout: options?.timeout }
|
|
1689
1825
|
}, { transport: connection }), connection);
|
|
@@ -1746,7 +1882,7 @@ const installTestClient = (testClient) => {
|
|
|
1746
1882
|
const createTestClient = (options) => {
|
|
1747
1883
|
return createTestClient$1({
|
|
1748
1884
|
sdkName: "nuxt-sdk",
|
|
1749
|
-
sdkVersion: "1.
|
|
1885
|
+
sdkVersion: "1.11.0"
|
|
1750
1886
|
}, options);
|
|
1751
1887
|
};
|
|
1752
1888
|
//#endregion
|
package/package.json
CHANGED
|
File without changes
|