@configdirector/nuxt-sdk 1.6.1 → 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 +152 -72
- 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
|
|
@@ -7143,9 +7169,8 @@ var ConfigEvaluator = class {
|
|
|
7143
7169
|
}
|
|
7144
7170
|
}
|
|
7145
7171
|
explainPercentage(percentages, config, context, explanation) {
|
|
7146
|
-
const
|
|
7147
|
-
const
|
|
7148
|
-
const assignedPercentage = assignPercentage({
|
|
7172
|
+
const identifier = context?.context?.id ?? void 0;
|
|
7173
|
+
const assignedPercentage = identifier === void 0 ? 0 : assignPercentage({
|
|
7149
7174
|
configId: config.id,
|
|
7150
7175
|
contextIdentifier: identifier
|
|
7151
7176
|
});
|
|
@@ -7165,7 +7190,7 @@ var ConfigEvaluator = class {
|
|
|
7165
7190
|
}
|
|
7166
7191
|
explanation.bucket = {
|
|
7167
7192
|
identifier,
|
|
7168
|
-
|
|
7193
|
+
defaultedToPredefinedBucket: identifier === void 0,
|
|
7169
7194
|
assignedPercentage,
|
|
7170
7195
|
shares,
|
|
7171
7196
|
selectedPercentageId: selected?.id
|
|
@@ -7224,7 +7249,7 @@ const configTypesNotReadableAsString = new Set([
|
|
|
7224
7249
|
const parseConfigValue = (configState, defaultValue) => {
|
|
7225
7250
|
const value = configState.value;
|
|
7226
7251
|
const requestedType = getRequestedType(defaultValue);
|
|
7227
|
-
if (value == null) return {
|
|
7252
|
+
if (value == null || value === "") return {
|
|
7228
7253
|
parsedValue: defaultValue,
|
|
7229
7254
|
parsedValueId: void 0,
|
|
7230
7255
|
requestedType,
|
|
@@ -7419,6 +7444,22 @@ const createDefaultLogger$1 = (level, messageDecorator) => {
|
|
|
7419
7444
|
//#region ../shared/src/url.ts
|
|
7420
7445
|
const defaultUrlFactory = (input, base) => new URL(input, base?.toString());
|
|
7421
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
|
|
7422
7463
|
//#region ../shared/src/telemetry/utils.ts
|
|
7423
7464
|
const CONFIG_VALUE_MAX_LENGTH = 500;
|
|
7424
7465
|
const compactTelemetryValue = async (tv, valueIdGenerator) => {
|
|
@@ -7616,12 +7657,18 @@ var TelemetryEventCollector = class {
|
|
|
7616
7657
|
flushIntervalDelay;
|
|
7617
7658
|
flushTimeout;
|
|
7618
7659
|
collectEvents = true;
|
|
7660
|
+
initialFlushIntervalDelay;
|
|
7661
|
+
started = false;
|
|
7619
7662
|
constructor(options) {
|
|
7620
7663
|
this.logger = options.logger;
|
|
7621
7664
|
this.evaluationEventQueue = new EventQueue(options.evaluationQueueLimit ?? 1e3);
|
|
7622
7665
|
this.flushIntervalDelay = options.flushIntervalDelay ?? 3e4;
|
|
7623
|
-
|
|
7624
|
-
|
|
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);
|
|
7625
7672
|
}
|
|
7626
7673
|
async flushAndScheduleNext() {
|
|
7627
7674
|
if (!this.collectEvents) return;
|
|
@@ -7815,10 +7862,11 @@ var DefaultConfigDirectorClient = class {
|
|
|
7815
7862
|
ready = false;
|
|
7816
7863
|
readyPromise;
|
|
7817
7864
|
readyResolve;
|
|
7865
|
+
readyTimer;
|
|
7818
7866
|
connectionMode;
|
|
7819
7867
|
configEvaluator;
|
|
7820
7868
|
metaContext;
|
|
7821
|
-
constructor(serverSdkKey, sdkOptions, clientOptions) {
|
|
7869
|
+
constructor(serverSdkKey, sdkOptions, clientOptions, internalClientOptions) {
|
|
7822
7870
|
this.logger = clientOptions?.logger ?? createDefaultLogger$1();
|
|
7823
7871
|
this.timeout = clientOptions?.connection?.timeout ?? 3e3;
|
|
7824
7872
|
this.metaContext = clientOptions?.metadata ?? {};
|
|
@@ -7826,47 +7874,26 @@ var DefaultConfigDirectorClient = class {
|
|
|
7826
7874
|
this.configEvaluator = new ConfigEvaluator(this.logger);
|
|
7827
7875
|
const baseUrl = this.parseUrl(clientOptions?.connection?.url) ?? defaultBaseUrl;
|
|
7828
7876
|
this.connectionMode = clientOptions?.connection?.mode ?? "streaming";
|
|
7829
|
-
|
|
7830
|
-
|
|
7831
|
-
this.usageEventCollector = new ServerTelemetryEventCollector({
|
|
7832
|
-
sdkKey: serverSdkKey,
|
|
7833
|
-
sdkIdentity: sdkOptions,
|
|
7834
|
-
metaContext: clientOptions?.metadata ?? {},
|
|
7835
|
-
logger: this.logger,
|
|
7836
|
-
baseUrl,
|
|
7837
|
-
flushIntervalDelay: clientOptions?.telemetry?.flushInterval ?? DEFAULT_FLUSH_INTERVAL,
|
|
7838
|
-
evaluationQueueLimit: Math.ceil(queueLimit * .7),
|
|
7839
|
-
contextLimit: Math.floor(queueLimit * .3),
|
|
7840
|
-
urlFactory: defaultUrlFactory,
|
|
7841
|
-
valueIdGenerator: generateValueId
|
|
7842
|
-
});
|
|
7843
|
-
this.transport = new transportConstructor({
|
|
7844
|
-
serverSdkKey,
|
|
7845
|
-
baseUrl,
|
|
7846
|
-
metaContext: {
|
|
7847
|
-
...clientOptions?.metadata,
|
|
7848
|
-
sdkName: sdkOptions.sdkName,
|
|
7849
|
-
sdkVersion: sdkOptions.sdkVersion
|
|
7850
|
-
},
|
|
7851
|
-
logger: this.logger,
|
|
7852
|
-
pollingInterval: clientOptions?.connection?.pollingInterval
|
|
7853
|
-
});
|
|
7877
|
+
this.usageEventCollector = internalClientOptions?.telemetry ?? this.buildTelemetryCollector(serverSdkKey, sdkOptions, baseUrl, clientOptions);
|
|
7878
|
+
this.transport = internalClientOptions?.transport ?? this.buildTransport(serverSdkKey, sdkOptions, baseUrl, clientOptions);
|
|
7854
7879
|
this.transport.on("configBundleReceived", (configBundle) => {
|
|
7855
|
-
this.readyResolve?.();
|
|
7856
|
-
const
|
|
7857
|
-
if (!this.configSet || configBundle.kind == "full")
|
|
7858
|
-
|
|
7859
|
-
this.
|
|
7860
|
-
|
|
7861
|
-
}
|
|
7862
|
-
|
|
7863
|
-
|
|
7864
|
-
|
|
7865
|
-
|
|
7866
|
-
|
|
7867
|
-
|
|
7868
|
-
}
|
|
7869
|
-
|
|
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
|
+
});
|
|
7870
7897
|
});
|
|
7871
7898
|
this.transport.on("connectionError", (error) => {
|
|
7872
7899
|
this.logger.error("[ConfigDirectorClient] The connection encountered an unrecoverable error and will not be retried. Configs will no longer receive updates.", error);
|
|
@@ -7887,18 +7914,23 @@ var DefaultConfigDirectorClient = class {
|
|
|
7887
7914
|
this.ready = false;
|
|
7888
7915
|
this.readyPromise = new Promise((resolve) => {
|
|
7889
7916
|
this.readyResolve = resolve;
|
|
7890
|
-
}).then(() => {
|
|
7917
|
+
}).then((cancelled) => {
|
|
7918
|
+
if (cancelled) return;
|
|
7891
7919
|
this.ready = true;
|
|
7892
7920
|
this.eventEmitter.emit("clientReady");
|
|
7893
7921
|
this.logger.debug("[ConfigDirectorClient] Received initial payload from the server, client is ready");
|
|
7894
7922
|
});
|
|
7923
|
+
this.usageEventCollector.start();
|
|
7895
7924
|
const startTime = (/* @__PURE__ */ new Date()).getTime();
|
|
7896
7925
|
await this.transport.connect(this.timeout);
|
|
7897
7926
|
const elapsedTime = (/* @__PURE__ */ new Date()).getTime() - startTime;
|
|
7898
7927
|
const remainingTimeout = this.timeout - elapsedTime;
|
|
7899
|
-
if (remainingTimeout > 0)
|
|
7900
|
-
|
|
7901
|
-
|
|
7928
|
+
if (remainingTimeout > 0) {
|
|
7929
|
+
await Promise.race([this.readyPromise, new Promise((resolve) => {
|
|
7930
|
+
this.readyTimer = setTimeout(() => resolve(), remainingTimeout);
|
|
7931
|
+
})]);
|
|
7932
|
+
this.clearReadyTimer();
|
|
7933
|
+
}
|
|
7902
7934
|
if (!this.ready) {
|
|
7903
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.";
|
|
7904
7936
|
this.logger.warn(`[ConfigDirectorClient] Timed out waiting for initialization after ${this.timeout}ms. ${warningDetails}`);
|
|
@@ -7907,18 +7939,60 @@ var DefaultConfigDirectorClient = class {
|
|
|
7907
7939
|
this.logger.error("[ConfigDirectorClient] An error occurred during initialization: ", error);
|
|
7908
7940
|
}
|
|
7909
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
|
+
}
|
|
7910
7979
|
getTransportConstructor(mode) {
|
|
7911
7980
|
switch (mode) {
|
|
7912
7981
|
case "polling": return PollingTransport;
|
|
7913
7982
|
default: return StreamingTransport;
|
|
7914
7983
|
}
|
|
7915
7984
|
}
|
|
7916
|
-
|
|
7917
|
-
|
|
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));
|
|
7918
7991
|
}
|
|
7919
|
-
updateWatchersForConfig(
|
|
7920
|
-
this.
|
|
7921
|
-
|
|
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);
|
|
7922
7996
|
h.handler(value);
|
|
7923
7997
|
});
|
|
7924
7998
|
}
|
|
@@ -8052,9 +8126,15 @@ var DefaultConfigDirectorClient = class {
|
|
|
8052
8126
|
this.transport.close();
|
|
8053
8127
|
this.ready = false;
|
|
8054
8128
|
}
|
|
8129
|
+
clearReadyTimer() {
|
|
8130
|
+
clearTimeout(this.readyTimer);
|
|
8131
|
+
this.readyTimer = void 0;
|
|
8132
|
+
}
|
|
8055
8133
|
dispose() {
|
|
8056
8134
|
this.removeAllObservers();
|
|
8057
8135
|
this.closeConnection();
|
|
8136
|
+
this.readyResolve?.(true);
|
|
8137
|
+
this.clearReadyTimer();
|
|
8058
8138
|
this.usageEventCollector.close().catch((error) => {
|
|
8059
8139
|
this.logger.warn("[ConfigDirectorClient] Error flushing telemetry while disposing the client: ", error);
|
|
8060
8140
|
});
|
|
@@ -8081,7 +8161,7 @@ var plugin_default = defineNitroPlugin((nitroApp) => {
|
|
|
8081
8161
|
const { serverSdkKey, baseUrl, connection } = runtimeConfig.configdirector;
|
|
8082
8162
|
const client = new DefaultConfigDirectorClient(serverSdkKey, {
|
|
8083
8163
|
sdkName: "nuxt-sdk",
|
|
8084
|
-
sdkVersion: "1.
|
|
8164
|
+
sdkVersion: "1.8.0"
|
|
8085
8165
|
}, {
|
|
8086
8166
|
logger,
|
|
8087
8167
|
connection: {
|