@configdirector/nuxt-sdk 0.1.1 → 0.5.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 +4 -142
- package/dist/module.json +1 -1
- package/dist/module.mjs +25 -23
- 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/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/nitro/middleware.d.mts +1 -119
- package/dist/runtime/nitro/middleware.d.ts +1 -119
- package/dist/runtime/nitro/plugin.d.mts +1 -1
- package/dist/runtime/nitro/plugin.d.ts +1 -1
- package/dist/runtime/nitro/plugin.mjs +200 -34
- package/dist/runtime/plugin.client.mjs +134 -53
- package/dist/runtime/server/composables/useConfigDirectorClient.d.mts +1 -1
- package/dist/runtime/server/composables/useConfigDirectorClient.d.ts +1 -1
- package/dist/runtime/{types-CQWNx-gI.d.mts → types-BWxsGnk2.d.mts} +9 -1
- package/dist/runtime/{types-KSnINlzh.d.mts → types-D9IS8RoC.d.mts} +1 -1
- package/dist/runtime/{types-D9MFb76Q.d.mts → types-DGQ1B6mZ.d.mts} +12 -1
- package/package.json +7 -7
|
@@ -380,7 +380,7 @@ var StreamingTransport = class {
|
|
|
380
380
|
this.dispatchMessage(data);
|
|
381
381
|
},
|
|
382
382
|
onConnect: () => {
|
|
383
|
-
this.logger.debug("[
|
|
383
|
+
this.logger.debug("[StreamingTransport] Connected");
|
|
384
384
|
resolve(this);
|
|
385
385
|
},
|
|
386
386
|
shouldReconnect: (state) => {
|
|
@@ -390,11 +390,13 @@ var StreamingTransport = class {
|
|
|
390
390
|
},
|
|
391
391
|
calculateReconnectDelay: (state) => {
|
|
392
392
|
const delay = Math.pow(2, Math.min(state.attempt, MAX_EXPONENTIAL_DELAY)) * 1e3;
|
|
393
|
-
|
|
393
|
+
const logMessage = `[StreamingTransport] Scheduling reconnect attempt #${state.attempt} in ${delay}ms.`;
|
|
394
|
+
if (state.attempt <= 5) this.logger.info(logMessage);
|
|
395
|
+
else this.logger.warn(logMessage);
|
|
394
396
|
return delay;
|
|
395
397
|
},
|
|
396
398
|
onDisconnect: () => {
|
|
397
|
-
this.logger.debug("[
|
|
399
|
+
this.logger.debug("[StreamingTransport] Disconnected");
|
|
398
400
|
}
|
|
399
401
|
});
|
|
400
402
|
this.eventSource.connect();
|
|
@@ -408,7 +410,7 @@ var StreamingTransport = class {
|
|
|
408
410
|
const json = JSON.parse(data);
|
|
409
411
|
this.eventEmitter.emit("configBundleReceived", json);
|
|
410
412
|
} catch (error) {
|
|
411
|
-
this.logger.error("[
|
|
413
|
+
this.logger.error("[StreamingTransport] Error parsing and dispatching config data update: ", error);
|
|
412
414
|
}
|
|
413
415
|
}
|
|
414
416
|
prepareFatalError(responseStatus, error) {
|
|
@@ -440,6 +442,148 @@ var StreamingTransport = class {
|
|
|
440
442
|
}
|
|
441
443
|
};
|
|
442
444
|
//#endregion
|
|
445
|
+
//#region ../shared/src/transport/AbstractPollingTransport.ts
|
|
446
|
+
var AbstractPollingTransport = class {
|
|
447
|
+
fatalError = false;
|
|
448
|
+
pollingInterval;
|
|
449
|
+
pollingIntervalSeconds = 60;
|
|
450
|
+
clearPollingInterval() {
|
|
451
|
+
clearInterval(this.pollingInterval);
|
|
452
|
+
}
|
|
453
|
+
schedulePollingInterval(callback) {
|
|
454
|
+
clearInterval(this.pollingInterval);
|
|
455
|
+
if (this.pollingIntervalSeconds <= 0) return;
|
|
456
|
+
this.pollingInterval = setInterval(callback, this.pollingIntervalSeconds * 1e3);
|
|
457
|
+
}
|
|
458
|
+
async handleNonOkResponse(response) {
|
|
459
|
+
if (!response.ok) if (this.isStatusFatal(response.status)) {
|
|
460
|
+
this.fatalError = true;
|
|
461
|
+
throw this.prepareFatalResponseStatusError(response.status, await response.text());
|
|
462
|
+
} else throw new ConfigDirectorConnectionError(`Connection failed with status: ${response.status}`, response.status);
|
|
463
|
+
}
|
|
464
|
+
handleFetchError(fetchError) {
|
|
465
|
+
if (this.isFetchErrorFatal(fetchError)) {
|
|
466
|
+
this.fatalError = true;
|
|
467
|
+
this.close();
|
|
468
|
+
throw new ConfigDirectorConnectionError(`Connection failed with fatal error: ${fetchError}. This is an unrecoverable error, retry attempts will be ignored.`);
|
|
469
|
+
} else if (fetchError instanceof SyntaxError) throw new ConfigDirectorConnectionError(`Failed to parse the response from the server: ${fetchError}`);
|
|
470
|
+
else throw new ConfigDirectorConnectionError(`Connection failed with error: ${fetchError}.`);
|
|
471
|
+
}
|
|
472
|
+
prepareFatalResponseStatusError(responseStatus, errorBody) {
|
|
473
|
+
const status = responseStatus ?? 0;
|
|
474
|
+
return new ConfigDirectorConnectionError(`${`Connection failed with status: ${responseStatus ?? "unknown"}`}${(errorBody?.trim()?.length ?? 0) > 0 ? ` (${errorBody})` : ""}. This is an unrecoverable error, retry attempts will be ignored.`, status);
|
|
475
|
+
}
|
|
476
|
+
isStatusFatal(status) {
|
|
477
|
+
return !!status && status >= 400 && status < 500;
|
|
478
|
+
}
|
|
479
|
+
isFetchErrorFatal(fetchError) {
|
|
480
|
+
if (fetchError?.name === "NotAllowedError") return true;
|
|
481
|
+
else if (fetchError instanceof TypeError) return true;
|
|
482
|
+
return false;
|
|
483
|
+
}
|
|
484
|
+
get isConnected() {
|
|
485
|
+
return this.pollingInterval !== void 0;
|
|
486
|
+
}
|
|
487
|
+
close() {
|
|
488
|
+
clearInterval(this.pollingInterval);
|
|
489
|
+
this.pollingInterval = void 0;
|
|
490
|
+
}
|
|
491
|
+
dispose() {
|
|
492
|
+
this.close();
|
|
493
|
+
this.clear();
|
|
494
|
+
}
|
|
495
|
+
};
|
|
496
|
+
//#endregion
|
|
497
|
+
//#region ../shared/src/fetchWithTimeout.ts
|
|
498
|
+
const fetchWithTimeout = async (timeout, resource, options, logger) => {
|
|
499
|
+
const abortController = new AbortController();
|
|
500
|
+
const abortTimeoutId = setTimeout(() => {
|
|
501
|
+
logger.debug("[fetchWithTimeout] Reached timeout, aborting request");
|
|
502
|
+
abortController.abort();
|
|
503
|
+
}, timeout);
|
|
504
|
+
try {
|
|
505
|
+
const response = await fetch(typeof resource === "string" || resource instanceof Request ? resource : resource.toString(), {
|
|
506
|
+
...options,
|
|
507
|
+
signal: abortController.signal
|
|
508
|
+
});
|
|
509
|
+
clearTimeout(abortTimeoutId);
|
|
510
|
+
return response;
|
|
511
|
+
} catch (error) {
|
|
512
|
+
logger.warn("[fetchWithTimeout] Fetch error: ", error);
|
|
513
|
+
clearTimeout(abortTimeoutId);
|
|
514
|
+
throw error;
|
|
515
|
+
}
|
|
516
|
+
};
|
|
517
|
+
//#endregion
|
|
518
|
+
//#region ../js-server-sdk/src/PollingTransport.ts
|
|
519
|
+
var PollingTransport = class extends AbstractPollingTransport {
|
|
520
|
+
logger;
|
|
521
|
+
eventEmitter = new EventEmitter$1();
|
|
522
|
+
url;
|
|
523
|
+
lastUpdateTimestamp;
|
|
524
|
+
constructor(options) {
|
|
525
|
+
super();
|
|
526
|
+
this.options = options;
|
|
527
|
+
this.options = options;
|
|
528
|
+
this.logger = options.logger;
|
|
529
|
+
this.url = new URL("server/polling/v1", options.baseUrl);
|
|
530
|
+
this.pollingIntervalSeconds = options.pollingInterval ?? 60;
|
|
531
|
+
}
|
|
532
|
+
async connect(timeout) {
|
|
533
|
+
this.clearPollingInterval();
|
|
534
|
+
await this.fetchConfigs(timeout);
|
|
535
|
+
this.schedulePollingInterval(() => {
|
|
536
|
+
this.fetchConfigs(timeout).catch((error) => {
|
|
537
|
+
this.logger.error("[PollingTransport] Error during polling:", error);
|
|
538
|
+
});
|
|
539
|
+
});
|
|
540
|
+
return this;
|
|
541
|
+
}
|
|
542
|
+
async fetchConfigs(timeout) {
|
|
543
|
+
if (this.fatalError) {
|
|
544
|
+
this.logger.warn("[PollingTransport] There was a prior unrecoverable error. Ignoring attempt to reconnect.");
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
try {
|
|
548
|
+
const response = await fetchWithTimeout(timeout, this.url, {
|
|
549
|
+
method: "POST",
|
|
550
|
+
headers: { "Content-Type": "application/json" },
|
|
551
|
+
body: JSON.stringify({
|
|
552
|
+
metaContext: this.options.metaContext,
|
|
553
|
+
serverSdkKey: this.options.serverSdkKey,
|
|
554
|
+
lastUpdateTimestamp: this.lastUpdateTimestamp
|
|
555
|
+
})
|
|
556
|
+
}, this.logger);
|
|
557
|
+
await this.handleNonOkResponse(response);
|
|
558
|
+
if (response.status === 200) {
|
|
559
|
+
const json = JSON.parse(await response.text());
|
|
560
|
+
this.lastUpdateTimestamp = json.timestamp;
|
|
561
|
+
this.eventEmitter.emit("configBundleReceived", json);
|
|
562
|
+
}
|
|
563
|
+
} catch (fetchError) {
|
|
564
|
+
this.handleFetchError(fetchError);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
on(name, handler) {
|
|
568
|
+
this.eventEmitter.on(name, handler);
|
|
569
|
+
}
|
|
570
|
+
off(name, handler) {
|
|
571
|
+
if (handler) this.eventEmitter.off(name, handler);
|
|
572
|
+
else this.eventEmitter.removeAllListeners(name);
|
|
573
|
+
}
|
|
574
|
+
clear() {
|
|
575
|
+
this.eventEmitter.removeAllListeners();
|
|
576
|
+
}
|
|
577
|
+
};
|
|
578
|
+
//#endregion
|
|
579
|
+
//#region ../js-server-sdk/src/OneTimeTransport.ts
|
|
580
|
+
var OneTimeTransport = class extends PollingTransport {
|
|
581
|
+
constructor(options) {
|
|
582
|
+
super(options);
|
|
583
|
+
this.pollingIntervalSeconds = 0;
|
|
584
|
+
}
|
|
585
|
+
};
|
|
586
|
+
//#endregion
|
|
443
587
|
//#region ../config-evaluator/src/text-comparison.ts
|
|
444
588
|
const compareText = (value, operator, targetValues) => {
|
|
445
589
|
switch (operator.toLowerCase()) {
|
|
@@ -709,6 +853,7 @@ const parseConfigValue = (configState, defaultValue) => {
|
|
|
709
853
|
usedDefault: true,
|
|
710
854
|
reason: "value-missing"
|
|
711
855
|
};
|
|
856
|
+
if (configState.type === "json") return parseJson(value, defaultValue, requestedType);
|
|
712
857
|
if (typeof defaultValue === "string") return {
|
|
713
858
|
parsedValue: value,
|
|
714
859
|
requestedType,
|
|
@@ -770,6 +915,29 @@ const parseConfigFloat = (value) => {
|
|
|
770
915
|
if (isNaN(num) || !isFinite(num)) return;
|
|
771
916
|
return num;
|
|
772
917
|
};
|
|
918
|
+
const parseJson = (jsonString, defaultValue, requestedType) => {
|
|
919
|
+
if (typeof defaultValue === "string") return {
|
|
920
|
+
parsedValue: jsonString,
|
|
921
|
+
requestedType,
|
|
922
|
+
usedDefault: false,
|
|
923
|
+
reason: "found-match"
|
|
924
|
+
};
|
|
925
|
+
try {
|
|
926
|
+
return {
|
|
927
|
+
parsedValue: JSON.parse(jsonString),
|
|
928
|
+
requestedType,
|
|
929
|
+
usedDefault: false,
|
|
930
|
+
reason: "found-match"
|
|
931
|
+
};
|
|
932
|
+
} catch {
|
|
933
|
+
return {
|
|
934
|
+
parsedValue: defaultValue,
|
|
935
|
+
requestedType,
|
|
936
|
+
usedDefault: true,
|
|
937
|
+
reason: "invalid-json"
|
|
938
|
+
};
|
|
939
|
+
}
|
|
940
|
+
};
|
|
773
941
|
//#endregion
|
|
774
942
|
//#region ../shared/src/logger.ts
|
|
775
943
|
const LEVELS = {
|
|
@@ -840,7 +1008,7 @@ const defaultUrlFactory = (input, base) => new URL(input, base?.toString());
|
|
|
840
1008
|
//#region ../shared/src/telemetry/utils.ts
|
|
841
1009
|
const CONFIG_VALUE_MAX_LENGTH = 500;
|
|
842
1010
|
const sanitizeValue = (value, type) => {
|
|
843
|
-
if (type === "json") try {
|
|
1011
|
+
if (type === "json" || type == null && typeof value === "object") try {
|
|
844
1012
|
return djb2Hash(JSON.stringify(value));
|
|
845
1013
|
} catch {
|
|
846
1014
|
return value.toString().slice(0, CONFIG_VALUE_MAX_LENGTH);
|
|
@@ -872,27 +1040,6 @@ const isDroppedEventsEmpty = (droppedEvents) => {
|
|
|
872
1040
|
return true;
|
|
873
1041
|
};
|
|
874
1042
|
//#endregion
|
|
875
|
-
//#region ../shared/src/fetchWithTimeout.ts
|
|
876
|
-
const fetchWithTimeout = async (timeout, resource, options, logger) => {
|
|
877
|
-
const abortController = new AbortController();
|
|
878
|
-
const abortTimeoutId = setTimeout(() => {
|
|
879
|
-
logger.debug("[fetchWithTimeout] Reached timeout, aborting request");
|
|
880
|
-
abortController.abort();
|
|
881
|
-
}, timeout);
|
|
882
|
-
try {
|
|
883
|
-
const response = await fetch(typeof resource === "string" || resource instanceof Request ? resource : resource.toString(), {
|
|
884
|
-
...options,
|
|
885
|
-
signal: abortController.signal
|
|
886
|
-
});
|
|
887
|
-
clearTimeout(abortTimeoutId);
|
|
888
|
-
return response;
|
|
889
|
-
} catch (error) {
|
|
890
|
-
logger.warn("[fetchWithTimeout] Fetch error: ", error);
|
|
891
|
-
clearTimeout(abortTimeoutId);
|
|
892
|
-
throw error;
|
|
893
|
-
}
|
|
894
|
-
};
|
|
895
|
-
//#endregion
|
|
896
1043
|
//#region ../js-server-sdk/src/telemetry/ServerEventReporter.ts
|
|
897
1044
|
var ServerEventReporter = class {
|
|
898
1045
|
sdkKey;
|
|
@@ -1180,7 +1327,7 @@ var DefaultConfigDirectorClient = class {
|
|
|
1180
1327
|
ready = false;
|
|
1181
1328
|
readyPromise;
|
|
1182
1329
|
readyResolve;
|
|
1183
|
-
|
|
1330
|
+
connectionMode;
|
|
1184
1331
|
configEvaluator;
|
|
1185
1332
|
metaContext;
|
|
1186
1333
|
constructor(serverSdkKey, sdkOptions, clientOptions) {
|
|
@@ -1189,8 +1336,8 @@ var DefaultConfigDirectorClient = class {
|
|
|
1189
1336
|
this.metaContext = clientOptions?.metadata ?? {};
|
|
1190
1337
|
this.configEvaluator = new ConfigEvaluator(this.logger);
|
|
1191
1338
|
const baseUrl = this.parseUrl(clientOptions?.connection?.url) ?? defaultBaseUrl;
|
|
1192
|
-
this.
|
|
1193
|
-
const transportConstructor =
|
|
1339
|
+
this.connectionMode = clientOptions?.connection?.mode ?? "streaming";
|
|
1340
|
+
const transportConstructor = this.getTransportConstructor(this.connectionMode);
|
|
1194
1341
|
const queueLimit = clientOptions?.telemetry?.eventQueueLimit ?? DEFAULT_EVENT_QUEUE_LIMIT;
|
|
1195
1342
|
this.usageEventCollector = new ServerTelemetryEventCollector({
|
|
1196
1343
|
sdkKey: serverSdkKey,
|
|
@@ -1209,7 +1356,8 @@ var DefaultConfigDirectorClient = class {
|
|
|
1209
1356
|
sdkName: sdkOptions.sdkName,
|
|
1210
1357
|
sdkVersion: sdkOptions.sdkVersion
|
|
1211
1358
|
},
|
|
1212
|
-
logger: this.logger
|
|
1359
|
+
logger: this.logger,
|
|
1360
|
+
pollingInterval: clientOptions?.connection?.pollingInterval
|
|
1213
1361
|
});
|
|
1214
1362
|
this.transport.on("configBundleReceived", (configBundle) => {
|
|
1215
1363
|
this.readyResolve?.();
|
|
@@ -1247,13 +1395,20 @@ var DefaultConfigDirectorClient = class {
|
|
|
1247
1395
|
setTimeout(() => resolve(), remainingTimeout);
|
|
1248
1396
|
})]);
|
|
1249
1397
|
if (!this.ready) {
|
|
1250
|
-
const warningDetails = this.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.";
|
|
1398
|
+
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.";
|
|
1251
1399
|
this.logger.warn(`[ConfigDirectorClient] Timed out waiting for initialization after ${this.timeout}ms. ${warningDetails}`);
|
|
1252
1400
|
}
|
|
1253
1401
|
} catch (error) {
|
|
1254
1402
|
this.logger.error("[ConfigDirectorClient] An error occurred during initialization: ", error);
|
|
1255
1403
|
}
|
|
1256
1404
|
}
|
|
1405
|
+
getTransportConstructor(mode) {
|
|
1406
|
+
switch (mode) {
|
|
1407
|
+
case "one-time": return OneTimeTransport;
|
|
1408
|
+
case "polling": return PollingTransport;
|
|
1409
|
+
default: return StreamingTransport;
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1257
1412
|
updateWatchers(configsMap) {
|
|
1258
1413
|
Object.values(configsMap).forEach((v) => this.updateWatchersForConfig(v));
|
|
1259
1414
|
}
|
|
@@ -1291,14 +1446,16 @@ var DefaultConfigDirectorClient = class {
|
|
|
1291
1446
|
}
|
|
1292
1447
|
getValueFromConfigDefinition(configKey, configDefinition, defaultValue, context) {
|
|
1293
1448
|
if (configDefinition) {
|
|
1294
|
-
const
|
|
1449
|
+
const configState = this.configEvaluator.evaluate(configDefinition, {
|
|
1295
1450
|
context,
|
|
1296
1451
|
metadata: this.metaContext
|
|
1297
|
-
})
|
|
1452
|
+
});
|
|
1453
|
+
const parseResult = parseConfigValue(configState, defaultValue);
|
|
1298
1454
|
this.usageEventCollector.evaluatedConfig({
|
|
1299
1455
|
context,
|
|
1300
1456
|
evaluation: {
|
|
1301
1457
|
key: configKey,
|
|
1458
|
+
type: configState.type,
|
|
1302
1459
|
defaultValue,
|
|
1303
1460
|
requestedType: parseResult.requestedType,
|
|
1304
1461
|
evaluatedValue: parseResult.parsedValue,
|
|
@@ -1354,6 +1511,15 @@ var DefaultConfigDirectorClient = class {
|
|
|
1354
1511
|
unwatchAll() {
|
|
1355
1512
|
this.handlersMap.clear();
|
|
1356
1513
|
}
|
|
1514
|
+
getAllConfigs(options) {
|
|
1515
|
+
if (!this.configSet) return {};
|
|
1516
|
+
const entries = Object.entries(this.configSet.configs);
|
|
1517
|
+
const filtered = options?.configKeys ? entries.filter(([key]) => options.configKeys?.includes(key)) : entries;
|
|
1518
|
+
return Object.fromEntries(filtered.map(([key, config]) => [key, this.configEvaluator.evaluate(config, {
|
|
1519
|
+
context: options?.context,
|
|
1520
|
+
metadata: this.metaContext
|
|
1521
|
+
})]));
|
|
1522
|
+
}
|
|
1357
1523
|
closeConnection() {
|
|
1358
1524
|
this.logger.debug("[ConfigDirectorClient] closeConnection() has been called, closing connection to server");
|
|
1359
1525
|
this.transport.close();
|
|
@@ -1385,7 +1551,7 @@ var plugin_default = defineNitroPlugin((nitroApp) => {
|
|
|
1385
1551
|
const { serverSdkKey, baseUrl } = runtimeConfig.configdirector;
|
|
1386
1552
|
const client = new DefaultConfigDirectorClient(serverSdkKey, {
|
|
1387
1553
|
sdkName: "nuxt-sdk",
|
|
1388
|
-
sdkVersion: "0.
|
|
1554
|
+
sdkVersion: "0.5.0"
|
|
1389
1555
|
}, {
|
|
1390
1556
|
logger,
|
|
1391
1557
|
...baseUrl ? { connection: { url: baseUrl } } : {}
|