@configdirector/nuxt-sdk 1.7.0 → 1.8.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/README.md +34 -0
- package/dist/module.d.mts +26 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +12 -7
- 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.mjs +8 -3
- package/dist/runtime/nitro/plugin.d.mts +1 -1
- package/dist/runtime/nitro/plugin.d.ts +1 -1
- package/dist/runtime/nitro/plugin.mjs +149 -68
- package/dist/runtime/plugin.client.mjs +186 -84
- package/dist/runtime/plugin.server.mjs +8 -3
- package/dist/runtime/server/composables/useConfigDirectorClient.d.mts +1 -1
- package/dist/runtime/server/composables/useConfigDirectorClient.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-C54FjC1D.d.mts → types-Bgx2-3dY.d.mts} +1 -0
- package/dist/runtime/{types-BPvGEzJY.d.mts → types-CvmjergQ.d.mts} +6 -2
- package/dist/testing.d.mts +352 -0
- package/dist/testing.mjs +1645 -0
- package/package.json +11 -4
package/README.md
CHANGED
|
@@ -31,6 +31,40 @@ const { value: darkMode } = useConfigDirectorValue("dark-mode", false);
|
|
|
31
31
|
|
|
32
32
|
Full details are in the [official documentation](https://docs.configdirector.com/sdks/meta/nuxt).
|
|
33
33
|
|
|
34
|
+
## Test your code
|
|
35
|
+
|
|
36
|
+
`@configdirector/nuxt-sdk/testing` creates a real client connected to an in-memory server that your
|
|
37
|
+
test controls. Install it before the Nuxt app is created, at the top level of a test file or a
|
|
38
|
+
vitest setup file, and the ConfigDirector plugin provides it to the app instead of building a
|
|
39
|
+
client. Tests run in the `nuxt` environment of `@nuxt/test-utils` 4 (which needs vitest 4).
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import { nextTick } from "vue";
|
|
43
|
+
import { mountSuspended } from "@nuxt/test-utils/runtime";
|
|
44
|
+
import { createTestClient, installTestClient } from "@configdirector/nuxt-sdk/testing";
|
|
45
|
+
import Checkout from "~/components/Checkout.vue";
|
|
46
|
+
|
|
47
|
+
const testClient = createTestClient({ values: { "dark-mode": true } });
|
|
48
|
+
installTestClient(testClient);
|
|
49
|
+
|
|
50
|
+
test("renders the dark mode", async () => {
|
|
51
|
+
const component = await mountSuspended(Checkout);
|
|
52
|
+
expect(component.text()).toContain("dark-mode: true");
|
|
53
|
+
|
|
54
|
+
testClient.setValue("dark-mode", false);
|
|
55
|
+
await nextTick();
|
|
56
|
+
expect(component.text()).toContain("dark-mode: false");
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
`@nuxt/test-utils` 4 creates the app once per test file, inside a `beforeAll` hook, so install at
|
|
61
|
+
module level rather than in a hook, and reset the values between tests with `replaceValues`.
|
|
62
|
+
`@nuxt/test-utils` 3 creates the app before the test file loads, so the plugin cannot see a test
|
|
63
|
+
client installed there; upgrade to use the testing entry. While
|
|
64
|
+
installed, the plugin needs no `clientSdkKey`. Await `nextTick()` after `setValue`, `removeValue`,
|
|
65
|
+
and `replaceValues`. The test client also holds or fails `initialize` and `updateContext`, and
|
|
66
|
+
records every context in `contextUpdates`.
|
|
67
|
+
|
|
34
68
|
## Documentation
|
|
35
69
|
|
|
36
70
|
Refer to the [official documentation for the Nuxt SDK](https://docs.configdirector.com/sdks/meta/nuxt).
|
package/dist/module.d.mts
CHANGED
|
@@ -92,6 +92,32 @@ interface ConfigDirectorPublicRuntimeConfig {
|
|
|
92
92
|
* Can be set via the NUXT_PUBLIC_CONFIGDIRECTOR_BASE_URL environment variable.
|
|
93
93
|
*/
|
|
94
94
|
baseUrl?: string;
|
|
95
|
+
/**
|
|
96
|
+
* Connection options for the browser client used on the client side.
|
|
97
|
+
*/
|
|
98
|
+
connection?: {
|
|
99
|
+
/**
|
|
100
|
+
* The connection mode, one of `streaming` (default) or `polling`. In `streaming` mode the
|
|
101
|
+
* connection stays open and receives config updates as they happen. In `polling` mode configs
|
|
102
|
+
* are fetched once during initialization and then again on every `pollingInterval`.
|
|
103
|
+
* Can be set via the NUXT_PUBLIC_CONFIGDIRECTOR_CONNECTION_MODE environment variable.
|
|
104
|
+
*/
|
|
105
|
+
mode?: ConnectionMode;
|
|
106
|
+
/**
|
|
107
|
+
* The polling interval in seconds when `mode` is `polling`. Has no effect in `streaming` mode.
|
|
108
|
+
* When omitted (or `0`), it defaults to 60 seconds. A value below the minimum of 30 seconds is
|
|
109
|
+
* raised to 30 seconds and a warning is logged.
|
|
110
|
+
* Can be set via the NUXT_PUBLIC_CONFIGDIRECTOR_CONNECTION_POLLING_INTERVAL environment variable.
|
|
111
|
+
*/
|
|
112
|
+
pollingInterval?: number;
|
|
113
|
+
/**
|
|
114
|
+
* How long, in milliseconds, the browser client waits for its initial config payload during
|
|
115
|
+
* initialization before the app renders with default values. When omitted (or `0`), it defaults
|
|
116
|
+
* to 2000 milliseconds.
|
|
117
|
+
* Can be set via the NUXT_PUBLIC_CONFIGDIRECTOR_CONNECTION_TIMEOUT environment variable.
|
|
118
|
+
*/
|
|
119
|
+
timeout?: number;
|
|
120
|
+
};
|
|
95
121
|
}
|
|
96
122
|
declare module "nuxt/schema" {
|
|
97
123
|
interface RuntimeConfig {
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -25,14 +25,19 @@ var module_default = defineNuxtModule({
|
|
|
25
25
|
...serverDefaults,
|
|
26
26
|
logLevel: options.logLevel
|
|
27
27
|
} : serverDefaults);
|
|
28
|
-
|
|
29
|
-
clientSdkKey: "",
|
|
30
|
-
logLevel: options.logLevel,
|
|
31
|
-
baseUrl: ""
|
|
32
|
-
} : {
|
|
28
|
+
const publicDefaults = {
|
|
33
29
|
clientSdkKey: "",
|
|
34
|
-
baseUrl: ""
|
|
35
|
-
|
|
30
|
+
baseUrl: "",
|
|
31
|
+
connection: {
|
|
32
|
+
mode: "streaming",
|
|
33
|
+
pollingInterval: 0,
|
|
34
|
+
timeout: 0
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
nuxt.options.runtimeConfig.public.configdirector = defu(nuxt.options.runtimeConfig.public.configdirector, options.logLevel !== void 0 ? {
|
|
38
|
+
...publicDefaults,
|
|
39
|
+
logLevel: options.logLevel
|
|
40
|
+
} : publicDefaults);
|
|
36
41
|
logger.debug("ConfigDirector Nuxt module setup complete");
|
|
37
42
|
addServerPlugin(resolver.resolve("./runtime/nitro/plugin"));
|
|
38
43
|
addServerHandler({
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as ClientHooks } from "../../types-
|
|
1
|
+
import { t as ClientHooks } from "../../types-CvmjergQ.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-CvmjergQ.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/runtime/app/composables/useConfigDirectorClientHooks.d.ts
|
|
4
4
|
declare const useConfigDirectorClientHooks: (hooks: ClientHooks) => void;
|
|
@@ -16,9 +16,14 @@ const useConfigDirectorContext = () => {
|
|
|
16
16
|
state.value = rawContext;
|
|
17
17
|
if (oldContext != newContext) {
|
|
18
18
|
const timeout = getEffectiveTimeout(options?.timeoutMilliseconds);
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
let timer;
|
|
20
|
+
try {
|
|
21
|
+
await Promise.race([client.updateContext(rawContext), new Promise((resolve) => {
|
|
22
|
+
timer = setTimeout(() => resolve(), timeout);
|
|
23
|
+
})]);
|
|
24
|
+
} finally {
|
|
25
|
+
clearTimeout(timer);
|
|
26
|
+
}
|
|
22
27
|
}
|
|
23
28
|
};
|
|
24
29
|
return {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import EventEmitter, { EventEmitter as EventEmitter$1 } from "node:events";
|
|
2
2
|
import { createHash, randomUUID } from "node:crypto";
|
|
3
|
-
import { findByPointer } from "@jsonjoy.com/json-pointer";
|
|
4
3
|
import * as semver from "semver";
|
|
5
4
|
import { rapidhash } from "rapidhash-js";
|
|
6
5
|
import { defineNitroPlugin, useRuntimeConfig } from "nitropack/runtime";
|
|
@@ -413,6 +412,7 @@ var StreamingTransport = class {
|
|
|
413
412
|
shouldReconnect: (state) => {
|
|
414
413
|
const reconnect = !this.isStatusFatal(state.status);
|
|
415
414
|
if (!reconnect) {
|
|
415
|
+
this.stopHeartbeat();
|
|
416
416
|
const error = this.prepareFatalError(state.status, state.error);
|
|
417
417
|
this.eventEmitter.emit("connectionError", error);
|
|
418
418
|
reject(error);
|
|
@@ -433,9 +433,14 @@ var StreamingTransport = class {
|
|
|
433
433
|
this.eventSource.connect();
|
|
434
434
|
});
|
|
435
435
|
this.heartbeatTimer = setInterval(() => this.sendHeartbeat(), HEARTBEAT_INTERVAL_MS);
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
436
|
+
let connectTimer;
|
|
437
|
+
try {
|
|
438
|
+
return await Promise.race([eventSourcePromise, new Promise((resolve) => {
|
|
439
|
+
connectTimer = setTimeout(() => resolve(this), timeout);
|
|
440
|
+
})]);
|
|
441
|
+
} finally {
|
|
442
|
+
clearTimeout(connectTimer);
|
|
443
|
+
}
|
|
439
444
|
}
|
|
440
445
|
get sessionId() {
|
|
441
446
|
return this._sessionId;
|
|
@@ -491,9 +496,12 @@ var StreamingTransport = class {
|
|
|
491
496
|
clear() {
|
|
492
497
|
this.eventEmitter.removeAllListeners();
|
|
493
498
|
}
|
|
494
|
-
|
|
499
|
+
stopHeartbeat() {
|
|
495
500
|
clearInterval(this.heartbeatTimer);
|
|
496
501
|
this.heartbeatTimer = void 0;
|
|
502
|
+
}
|
|
503
|
+
close() {
|
|
504
|
+
this.stopHeartbeat();
|
|
497
505
|
this.eventSource?.close();
|
|
498
506
|
}
|
|
499
507
|
dispose() {
|
|
@@ -506,7 +514,6 @@ var StreamingTransport = class {
|
|
|
506
514
|
var AbstractPollingTransport = class {
|
|
507
515
|
fatalError = false;
|
|
508
516
|
pollingInterval;
|
|
509
|
-
pollingIntervalSeconds = 60;
|
|
510
517
|
clearPollingInterval() {
|
|
511
518
|
clearInterval(this.pollingInterval);
|
|
512
519
|
}
|
|
@@ -555,6 +562,7 @@ var AbstractPollingTransport = class {
|
|
|
555
562
|
//#endregion
|
|
556
563
|
//#region ../js-server-sdk/src/PollingTransport.ts
|
|
557
564
|
var PollingTransport = class extends AbstractPollingTransport {
|
|
565
|
+
pollingIntervalSeconds;
|
|
558
566
|
logger;
|
|
559
567
|
eventEmitter = new EventEmitter$1();
|
|
560
568
|
url;
|
|
@@ -627,6 +635,31 @@ var PollingTransport = class extends AbstractPollingTransport {
|
|
|
627
635
|
}
|
|
628
636
|
};
|
|
629
637
|
//#endregion
|
|
638
|
+
//#region ../config-evaluator/src/json-pointer.ts
|
|
639
|
+
const ARRAY_INDEX_PATTERN = /^(?:0|[1-9][0-9]*)$/;
|
|
640
|
+
const INVALID_ESCAPE_PATTERN = /~(?![01])/;
|
|
641
|
+
const isSerializedMember = Object.prototype.propertyIsEnumerable;
|
|
642
|
+
const decodeReferenceToken = (token) => {
|
|
643
|
+
if (INVALID_ESCAPE_PATTERN.test(token)) return;
|
|
644
|
+
return token.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
645
|
+
};
|
|
646
|
+
const step = (current, token) => {
|
|
647
|
+
if (Array.isArray(current)) return ARRAY_INDEX_PATTERN.test(token) && isSerializedMember.call(current, token) ? current[Number(token)] : void 0;
|
|
648
|
+
if (typeof current === "object" && current !== null) return isSerializedMember.call(current, token) ? current[token] : void 0;
|
|
649
|
+
};
|
|
650
|
+
const resolveJsonPointer = (pointer, document) => {
|
|
651
|
+
if (pointer === "") return document;
|
|
652
|
+
if (!pointer.startsWith("/")) return;
|
|
653
|
+
let current = document;
|
|
654
|
+
for (const encodedToken of pointer.slice(1).split("/")) {
|
|
655
|
+
const token = decodeReferenceToken(encodedToken);
|
|
656
|
+
if (token === void 0) return;
|
|
657
|
+
current = step(current, token);
|
|
658
|
+
if (current === void 0) return;
|
|
659
|
+
}
|
|
660
|
+
return current;
|
|
661
|
+
};
|
|
662
|
+
//#endregion
|
|
630
663
|
//#region ../config-evaluator/src/text-comparison.ts
|
|
631
664
|
/**
|
|
632
665
|
* Text comparisons.
|
|
@@ -7036,17 +7069,10 @@ var ConditionEvaluator = class {
|
|
|
7036
7069
|
case "appVersion": return orAbsent(context?.metadata?.appVersion);
|
|
7037
7070
|
case "traits":
|
|
7038
7071
|
if (!condition.trait) return ABSENT;
|
|
7039
|
-
return orAbsent(
|
|
7072
|
+
return orAbsent(resolveJsonPointer(condition.trait, context?.context?.traits));
|
|
7040
7073
|
default: return UNKNOWN_ATTRIBUTE;
|
|
7041
7074
|
}
|
|
7042
7075
|
}
|
|
7043
|
-
findTraitValue(traits, path) {
|
|
7044
|
-
try {
|
|
7045
|
-
return findByPointer(path, traits).val;
|
|
7046
|
-
} catch {
|
|
7047
|
-
return;
|
|
7048
|
-
}
|
|
7049
|
-
}
|
|
7050
7076
|
};
|
|
7051
7077
|
const orAbsent = (value) => value === void 0 || value === null ? ABSENT : value;
|
|
7052
7078
|
//#endregion
|
|
@@ -7223,7 +7249,7 @@ const configTypesNotReadableAsString = new Set([
|
|
|
7223
7249
|
const parseConfigValue = (configState, defaultValue) => {
|
|
7224
7250
|
const value = configState.value;
|
|
7225
7251
|
const requestedType = getRequestedType(defaultValue);
|
|
7226
|
-
if (value == null) return {
|
|
7252
|
+
if (value == null || value === "") return {
|
|
7227
7253
|
parsedValue: defaultValue,
|
|
7228
7254
|
parsedValueId: void 0,
|
|
7229
7255
|
requestedType,
|
|
@@ -7418,6 +7444,22 @@ const createDefaultLogger$1 = (level, messageDecorator) => {
|
|
|
7418
7444
|
//#region ../shared/src/url.ts
|
|
7419
7445
|
const defaultUrlFactory = (input, base) => new URL(input, base?.toString());
|
|
7420
7446
|
//#endregion
|
|
7447
|
+
//#region ../shared/src/transport/pollingInterval.ts
|
|
7448
|
+
const resolvePollingInterval = (configuredSeconds, defaults) => {
|
|
7449
|
+
if (configuredSeconds === void 0) return {
|
|
7450
|
+
seconds: defaults.defaultSeconds,
|
|
7451
|
+
warning: void 0
|
|
7452
|
+
};
|
|
7453
|
+
if (configuredSeconds >= defaults.minimumSeconds) return {
|
|
7454
|
+
seconds: configuredSeconds,
|
|
7455
|
+
warning: void 0
|
|
7456
|
+
};
|
|
7457
|
+
return {
|
|
7458
|
+
seconds: defaults.minimumSeconds,
|
|
7459
|
+
warning: `pollingInterval of ${configuredSeconds} seconds is below the minimum of ${defaults.minimumSeconds} seconds. Using ${defaults.minimumSeconds} seconds.`
|
|
7460
|
+
};
|
|
7461
|
+
};
|
|
7462
|
+
//#endregion
|
|
7421
7463
|
//#region ../shared/src/telemetry/utils.ts
|
|
7422
7464
|
const CONFIG_VALUE_MAX_LENGTH = 500;
|
|
7423
7465
|
const compactTelemetryValue = async (tv, valueIdGenerator) => {
|
|
@@ -7615,12 +7657,18 @@ var TelemetryEventCollector = class {
|
|
|
7615
7657
|
flushIntervalDelay;
|
|
7616
7658
|
flushTimeout;
|
|
7617
7659
|
collectEvents = true;
|
|
7660
|
+
initialFlushIntervalDelay;
|
|
7661
|
+
started = false;
|
|
7618
7662
|
constructor(options) {
|
|
7619
7663
|
this.logger = options.logger;
|
|
7620
7664
|
this.evaluationEventQueue = new EventQueue(options.evaluationQueueLimit ?? 1e3);
|
|
7621
7665
|
this.flushIntervalDelay = options.flushIntervalDelay ?? 3e4;
|
|
7622
|
-
|
|
7623
|
-
|
|
7666
|
+
this.initialFlushIntervalDelay = options.initialFlushIntervalDelay ?? 5e3;
|
|
7667
|
+
}
|
|
7668
|
+
start() {
|
|
7669
|
+
if (this.started || !this.collectEvents) return;
|
|
7670
|
+
this.started = true;
|
|
7671
|
+
this.flushTimeout = setTimeout(() => this.flushAndScheduleNext(), this.initialFlushIntervalDelay);
|
|
7624
7672
|
}
|
|
7625
7673
|
async flushAndScheduleNext() {
|
|
7626
7674
|
if (!this.collectEvents) return;
|
|
@@ -7814,10 +7862,11 @@ var DefaultConfigDirectorClient = class {
|
|
|
7814
7862
|
ready = false;
|
|
7815
7863
|
readyPromise;
|
|
7816
7864
|
readyResolve;
|
|
7865
|
+
readyTimer;
|
|
7817
7866
|
connectionMode;
|
|
7818
7867
|
configEvaluator;
|
|
7819
7868
|
metaContext;
|
|
7820
|
-
constructor(serverSdkKey, sdkOptions, clientOptions) {
|
|
7869
|
+
constructor(serverSdkKey, sdkOptions, clientOptions, internalClientOptions) {
|
|
7821
7870
|
this.logger = clientOptions?.logger ?? createDefaultLogger$1();
|
|
7822
7871
|
this.timeout = clientOptions?.connection?.timeout ?? 3e3;
|
|
7823
7872
|
this.metaContext = clientOptions?.metadata ?? {};
|
|
@@ -7825,47 +7874,26 @@ var DefaultConfigDirectorClient = class {
|
|
|
7825
7874
|
this.configEvaluator = new ConfigEvaluator(this.logger);
|
|
7826
7875
|
const baseUrl = this.parseUrl(clientOptions?.connection?.url) ?? defaultBaseUrl;
|
|
7827
7876
|
this.connectionMode = clientOptions?.connection?.mode ?? "streaming";
|
|
7828
|
-
|
|
7829
|
-
|
|
7830
|
-
this.usageEventCollector = new ServerTelemetryEventCollector({
|
|
7831
|
-
sdkKey: serverSdkKey,
|
|
7832
|
-
sdkIdentity: sdkOptions,
|
|
7833
|
-
metaContext: clientOptions?.metadata ?? {},
|
|
7834
|
-
logger: this.logger,
|
|
7835
|
-
baseUrl,
|
|
7836
|
-
flushIntervalDelay: clientOptions?.telemetry?.flushInterval ?? DEFAULT_FLUSH_INTERVAL,
|
|
7837
|
-
evaluationQueueLimit: Math.ceil(queueLimit * .7),
|
|
7838
|
-
contextLimit: Math.floor(queueLimit * .3),
|
|
7839
|
-
urlFactory: defaultUrlFactory,
|
|
7840
|
-
valueIdGenerator: generateValueId
|
|
7841
|
-
});
|
|
7842
|
-
this.transport = new transportConstructor({
|
|
7843
|
-
serverSdkKey,
|
|
7844
|
-
baseUrl,
|
|
7845
|
-
metaContext: {
|
|
7846
|
-
...clientOptions?.metadata,
|
|
7847
|
-
sdkName: sdkOptions.sdkName,
|
|
7848
|
-
sdkVersion: sdkOptions.sdkVersion
|
|
7849
|
-
},
|
|
7850
|
-
logger: this.logger,
|
|
7851
|
-
pollingInterval: clientOptions?.connection?.pollingInterval
|
|
7852
|
-
});
|
|
7877
|
+
this.usageEventCollector = internalClientOptions?.telemetry ?? this.buildTelemetryCollector(serverSdkKey, sdkOptions, baseUrl, clientOptions);
|
|
7878
|
+
this.transport = internalClientOptions?.transport ?? this.buildTransport(serverSdkKey, sdkOptions, baseUrl, clientOptions);
|
|
7853
7879
|
this.transport.on("configBundleReceived", (configBundle) => {
|
|
7854
|
-
this.readyResolve?.();
|
|
7855
|
-
const
|
|
7856
|
-
if (!this.configSet || configBundle.kind == "full")
|
|
7857
|
-
|
|
7858
|
-
this.
|
|
7859
|
-
|
|
7860
|
-
}
|
|
7861
|
-
|
|
7862
|
-
|
|
7863
|
-
|
|
7864
|
-
|
|
7865
|
-
|
|
7866
|
-
|
|
7867
|
-
}
|
|
7868
|
-
|
|
7880
|
+
this.readyResolve?.(false);
|
|
7881
|
+
const removedKeys = this.findKeysRemovedBy(configBundle);
|
|
7882
|
+
if (!this.configSet || configBundle.kind == "full") this.configSet = configBundle;
|
|
7883
|
+
else this.configSet.configs = {
|
|
7884
|
+
...this.configSet.configs,
|
|
7885
|
+
...configBundle.configs
|
|
7886
|
+
};
|
|
7887
|
+
const keys = Object.keys(configBundle.configs);
|
|
7888
|
+
this.eventEmitter.emit("configsUpdated", {
|
|
7889
|
+
keys,
|
|
7890
|
+
removedKeys
|
|
7891
|
+
});
|
|
7892
|
+
this.updateWatchers([...keys, ...removedKeys]);
|
|
7893
|
+
this.logger.debug(`[ConfigDirectorClient] ConfigSet updated from server with ${keys.length} key(s) and ${removedKeys.length} removed key(s)`, {
|
|
7894
|
+
keys,
|
|
7895
|
+
removedKeys
|
|
7896
|
+
});
|
|
7869
7897
|
});
|
|
7870
7898
|
this.transport.on("connectionError", (error) => {
|
|
7871
7899
|
this.logger.error("[ConfigDirectorClient] The connection encountered an unrecoverable error and will not be retried. Configs will no longer receive updates.", error);
|
|
@@ -7886,18 +7914,23 @@ var DefaultConfigDirectorClient = class {
|
|
|
7886
7914
|
this.ready = false;
|
|
7887
7915
|
this.readyPromise = new Promise((resolve) => {
|
|
7888
7916
|
this.readyResolve = resolve;
|
|
7889
|
-
}).then(() => {
|
|
7917
|
+
}).then((cancelled) => {
|
|
7918
|
+
if (cancelled) return;
|
|
7890
7919
|
this.ready = true;
|
|
7891
7920
|
this.eventEmitter.emit("clientReady");
|
|
7892
7921
|
this.logger.debug("[ConfigDirectorClient] Received initial payload from the server, client is ready");
|
|
7893
7922
|
});
|
|
7923
|
+
this.usageEventCollector.start();
|
|
7894
7924
|
const startTime = (/* @__PURE__ */ new Date()).getTime();
|
|
7895
7925
|
await this.transport.connect(this.timeout);
|
|
7896
7926
|
const elapsedTime = (/* @__PURE__ */ new Date()).getTime() - startTime;
|
|
7897
7927
|
const remainingTimeout = this.timeout - elapsedTime;
|
|
7898
|
-
if (remainingTimeout > 0)
|
|
7899
|
-
|
|
7900
|
-
|
|
7928
|
+
if (remainingTimeout > 0) {
|
|
7929
|
+
await Promise.race([this.readyPromise, new Promise((resolve) => {
|
|
7930
|
+
this.readyTimer = setTimeout(() => resolve(), remainingTimeout);
|
|
7931
|
+
})]);
|
|
7932
|
+
this.clearReadyTimer();
|
|
7933
|
+
}
|
|
7901
7934
|
if (!this.ready) {
|
|
7902
7935
|
const warningDetails = this.connectionMode === "streaming" ? "The client will continue to retry since there were no fatal errors detected. Configs will return the default value until the connection succeeds." : "Since the client was configured without streaming, configs may not update and always return the default value.";
|
|
7903
7936
|
this.logger.warn(`[ConfigDirectorClient] Timed out waiting for initialization after ${this.timeout}ms. ${warningDetails}`);
|
|
@@ -7906,18 +7939,60 @@ var DefaultConfigDirectorClient = class {
|
|
|
7906
7939
|
this.logger.error("[ConfigDirectorClient] An error occurred during initialization: ", error);
|
|
7907
7940
|
}
|
|
7908
7941
|
}
|
|
7942
|
+
resolvePollingIntervalForMode(configuredSeconds) {
|
|
7943
|
+
if (this.connectionMode !== "polling") return;
|
|
7944
|
+
const resolved = resolvePollingInterval(configuredSeconds, {
|
|
7945
|
+
defaultSeconds: 300,
|
|
7946
|
+
minimumSeconds: 60
|
|
7947
|
+
});
|
|
7948
|
+
if (resolved.warning) this.logger.warn(`[ConfigDirectorClient] ${resolved.warning}`);
|
|
7949
|
+
return resolved.seconds;
|
|
7950
|
+
}
|
|
7951
|
+
buildTelemetryCollector(serverSdkKey, sdkOptions, baseUrl, clientOptions) {
|
|
7952
|
+
const queueLimit = clientOptions?.telemetry?.eventQueueLimit ?? DEFAULT_EVENT_QUEUE_LIMIT;
|
|
7953
|
+
return new ServerTelemetryEventCollector({
|
|
7954
|
+
sdkKey: serverSdkKey,
|
|
7955
|
+
sdkIdentity: sdkOptions,
|
|
7956
|
+
metaContext: clientOptions?.metadata ?? {},
|
|
7957
|
+
logger: this.logger,
|
|
7958
|
+
baseUrl,
|
|
7959
|
+
flushIntervalDelay: clientOptions?.telemetry?.flushInterval ?? DEFAULT_FLUSH_INTERVAL,
|
|
7960
|
+
evaluationQueueLimit: Math.ceil(queueLimit * .7),
|
|
7961
|
+
contextLimit: Math.floor(queueLimit * .3),
|
|
7962
|
+
urlFactory: defaultUrlFactory,
|
|
7963
|
+
valueIdGenerator: generateValueId
|
|
7964
|
+
});
|
|
7965
|
+
}
|
|
7966
|
+
buildTransport(serverSdkKey, sdkOptions, baseUrl, clientOptions) {
|
|
7967
|
+
return new (this.getTransportConstructor(this.connectionMode))({
|
|
7968
|
+
serverSdkKey,
|
|
7969
|
+
baseUrl,
|
|
7970
|
+
metaContext: {
|
|
7971
|
+
...clientOptions?.metadata,
|
|
7972
|
+
sdkName: sdkOptions.sdkName,
|
|
7973
|
+
sdkVersion: sdkOptions.sdkVersion
|
|
7974
|
+
},
|
|
7975
|
+
logger: this.logger,
|
|
7976
|
+
pollingInterval: this.resolvePollingIntervalForMode(clientOptions?.connection?.pollingInterval)
|
|
7977
|
+
});
|
|
7978
|
+
}
|
|
7909
7979
|
getTransportConstructor(mode) {
|
|
7910
7980
|
switch (mode) {
|
|
7911
7981
|
case "polling": return PollingTransport;
|
|
7912
7982
|
default: return StreamingTransport;
|
|
7913
7983
|
}
|
|
7914
7984
|
}
|
|
7915
|
-
|
|
7916
|
-
|
|
7985
|
+
findKeysRemovedBy(configBundle) {
|
|
7986
|
+
if (!this.configSet || configBundle.kind != "full") return [];
|
|
7987
|
+
return Object.keys(this.configSet.configs).filter((key) => !Object.prototype.hasOwnProperty.call(configBundle.configs, key));
|
|
7988
|
+
}
|
|
7989
|
+
updateWatchers(configKeys) {
|
|
7990
|
+
configKeys.forEach((configKey) => this.updateWatchersForConfig(configKey));
|
|
7917
7991
|
}
|
|
7918
|
-
updateWatchersForConfig(
|
|
7919
|
-
this.
|
|
7920
|
-
|
|
7992
|
+
updateWatchersForConfig(configKey) {
|
|
7993
|
+
const configDefinition = this.configSet?.configs[configKey];
|
|
7994
|
+
this.handlersMap.get(configKey)?.forEach((h) => {
|
|
7995
|
+
const value = this.getValueFromConfigDefinition(configKey, configDefinition, h.defaultValue, h.context);
|
|
7921
7996
|
h.handler(value);
|
|
7922
7997
|
});
|
|
7923
7998
|
}
|
|
@@ -8051,9 +8126,15 @@ var DefaultConfigDirectorClient = class {
|
|
|
8051
8126
|
this.transport.close();
|
|
8052
8127
|
this.ready = false;
|
|
8053
8128
|
}
|
|
8129
|
+
clearReadyTimer() {
|
|
8130
|
+
clearTimeout(this.readyTimer);
|
|
8131
|
+
this.readyTimer = void 0;
|
|
8132
|
+
}
|
|
8054
8133
|
dispose() {
|
|
8055
8134
|
this.removeAllObservers();
|
|
8056
8135
|
this.closeConnection();
|
|
8136
|
+
this.readyResolve?.(true);
|
|
8137
|
+
this.clearReadyTimer();
|
|
8057
8138
|
this.usageEventCollector.close().catch((error) => {
|
|
8058
8139
|
this.logger.warn("[ConfigDirectorClient] Error flushing telemetry while disposing the client: ", error);
|
|
8059
8140
|
});
|
|
@@ -8080,7 +8161,7 @@ var plugin_default = defineNitroPlugin((nitroApp) => {
|
|
|
8080
8161
|
const { serverSdkKey, baseUrl, connection } = runtimeConfig.configdirector;
|
|
8081
8162
|
const client = new DefaultConfigDirectorClient(serverSdkKey, {
|
|
8082
8163
|
sdkName: "nuxt-sdk",
|
|
8083
|
-
sdkVersion: "1.
|
|
8164
|
+
sdkVersion: "1.8.0"
|
|
8084
8165
|
}, {
|
|
8085
8166
|
logger,
|
|
8086
8167
|
connection: {
|