@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.
Files changed (36) hide show
  1. package/dist/module.d.mts +76 -15
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +13 -4
  4. package/dist/runtime/app/composables/useConfigDirectorClient.d.mts +1 -1
  5. package/dist/runtime/app/composables/useConfigDirectorClient.d.ts +1 -1
  6. package/dist/runtime/app/composables/useConfigDirectorClientHooks.d.mts +1 -1
  7. package/dist/runtime/app/composables/useConfigDirectorClientHooks.d.ts +1 -1
  8. package/dist/runtime/app/composables/useConfigDirectorContext.d.mts +1 -1
  9. package/dist/runtime/app/composables/useConfigDirectorContext.d.ts +1 -1
  10. package/dist/runtime/app/composables/useConfigDirectorStatus.d.mts +1 -1
  11. package/dist/runtime/app/composables/useConfigDirectorStatus.d.ts +1 -1
  12. package/dist/runtime/app/composables/useConfigDirectorValue.d.mts +2 -2
  13. package/dist/runtime/app/composables/useConfigDirectorValue.d.ts +2 -2
  14. package/dist/runtime/{types-IPLvRsUO.d.mts → native-values-BV2k_xR2.d.mts} +11 -1
  15. package/dist/runtime/native-values-DZJjFBvk.mjs +116 -0
  16. package/dist/runtime/nitro/middleware.mjs +5 -1
  17. package/dist/runtime/nitro/offline-values.d.mts +7 -0
  18. package/dist/runtime/nitro/offline-values.d.ts +7 -0
  19. package/dist/runtime/nitro/offline-values.mjs +15 -0
  20. package/dist/runtime/nitro/plugin.d.mts +3 -1
  21. package/dist/runtime/nitro/plugin.d.ts +3 -1
  22. package/dist/runtime/nitro/plugin.mjs +232 -83
  23. package/dist/runtime/plugin.client.mjs +341 -86
  24. package/dist/runtime/server/composables/useConfigDirectorServerClient.d.mts +6 -0
  25. package/dist/runtime/server/composables/useConfigDirectorServerClient.d.ts +6 -0
  26. package/dist/runtime/server/composables/{useConfigDirectorClient.mjs → useConfigDirectorServerClient.mjs} +3 -3
  27. package/dist/runtime/server/composables/useConfigDirectorServerHooks.d.mts +1 -1
  28. package/dist/runtime/server/composables/useConfigDirectorServerHooks.d.ts +1 -1
  29. package/dist/runtime/{types-B9_4pADA.d.mts → types-CS7AMNWe.d.mts} +1 -1
  30. package/dist/runtime/{types-DQhBe4Ei.d.mts → types-Dt3bENKf.d.mts} +1 -1
  31. package/dist/testing.d.mts +13 -13
  32. package/dist/testing.mjs +292 -156
  33. package/package.json +1 -1
  34. package/dist/runtime/server/composables/useConfigDirectorClient.d.mts +0 -6
  35. package/dist/runtime/server/composables/useConfigDirectorClient.d.ts +0 -6
  36. /package/dist/runtime/{types-F0fKKk6-.d.mts → types-C30VJ25l.d.mts} +0 -0
@@ -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?: TestValues;
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: TestValue): void;
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: TestValues): void;
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
- connectionMode;
1266
+ connectionPlan;
1075
1267
  clientSession;
1076
- constructor(telemetryClient, clientSdkKey, sdkOptions, clientOptions, internalClientOptions) {
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
- const baseUrl = this.parseUrl(clientOptions?.connection?.url, urlFactory) ?? "https://client-sdk-api.configdirector.com";
1083
- this.connectionMode = clientOptions?.connection?.mode ?? "streaming";
1084
- this.telemetryClient = telemetryClient;
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
- resolvePollingIntervalForMode(configuredSeconds) {
1123
- if (this.connectionMode !== "polling") return;
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
- buildTransport(clientSdkKey, sdkOptions, baseUrl, urlFactory, clientOptions, internalClientOptions) {
1132
- return new (this.getTransportConstructor(this.connectionMode))({
1133
- clientSession: this.clientSession,
1134
- clientSdkKey,
1135
- baseUrl,
1136
- resolveUrl: urlFactory,
1137
- metaContext: {
1138
- ...clientOptions?.metadata,
1139
- sdkName: sdkOptions.sdkName,
1140
- sdkVersion: sdkOptions.sdkVersion,
1141
- userAgent: readUserAgent(),
1142
- host: readHost()
1143
- },
1144
- logger: this.logger,
1145
- fetch: internalClientOptions?.fetch,
1146
- connectionRetryDelay: internalClientOptions?.connectionRetryDelay ?? ((attempt) => {
1147
- return Math.pow(2, Math.min(attempt, MAX_EXPONENTIAL_DELAY)) * 1e3;
1148
- }),
1149
- pollingInterval: this.resolvePollingIntervalForMode(clientOptions?.connection?.pollingInterval)
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(connectionMode) {
1153
- switch (connectionMode) {
1362
+ getTransportConstructor(mode) {
1363
+ switch (mode) {
1154
1364
  case "polling": return PollingTransport;
1155
- default: return StreamingTransport;
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
- await this.submitContext(context, "context update");
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.contextToConnectWith(context, submission), submission);
1387
+ await this.connectToTransport(this.admittedContextFor(context, submission), submission);
1167
1388
  }
1168
- contextToConnectWith(suppliedContext, submission) {
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; connecting as if no context was supplied. A context kind has 1 to 64 characters, using only lowercase letters, digits, '-' and '_'.`);
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. The client will continue to retry since there were no fatal errors detected. Configs will return the default value until the connection succeeds.`);
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.logger.debug("[ConfigDirectorClient] pauseNetwork() called, pausing transport connection");
1341
- clearTimeout(this.timeoutTimer);
1342
- this.timeoutTimer = void 0;
1343
- this.transport.close();
1344
- this.ready = false;
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
- await this.connectToTransport(this.currentContext, "network resume");
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 = encodeTestValue(key, value, ++this.revision);
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, encodeTestValue(key, value, ++this.revision));
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(noopTelemetryClient, TEST_CLIENT_SDK_KEY, sdkOptions, {
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.10.0"
1885
+ sdkVersion: "1.11.0"
1750
1886
  }, options);
1751
1887
  };
1752
1888
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@configdirector/nuxt-sdk",
3
- "version": "1.10.0",
3
+ "version": "1.11.0",
4
4
  "description": "Nuxt SDK for ConfigDirector. ConfigDirector is a remote configuration and feature flag service.",
5
5
  "license": "MIT",
6
6
  "author": "ConfigDirector",
@@ -1,6 +0,0 @@
1
- import { n as ConfigDirectorClient } from "../../types-B9_4pADA.mjs";
2
-
3
- //#region src/runtime/server/composables/useConfigDirectorClient.d.ts
4
- declare const useConfigDirectorClient: () => ConfigDirectorClient;
5
- //#endregion
6
- export { useConfigDirectorClient };
@@ -1,6 +0,0 @@
1
- import { n as ConfigDirectorClient } from "../../types-B9_4pADA.mjs";
2
-
3
- //#region src/runtime/server/composables/useConfigDirectorClient.d.ts
4
- declare const useConfigDirectorClient: () => ConfigDirectorClient;
5
- //#endregion
6
- export { useConfigDirectorClient };