@configdirector/nuxt-sdk 1.10.0 → 1.11.1

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 (38) 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/useConfigDirectorServerClient.mjs +9 -0
  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/server/composables/useConfigDirectorServerHooks.mjs +2 -3
  30. package/dist/runtime/{types-B9_4pADA.d.mts → types-CS7AMNWe.d.mts} +1 -1
  31. package/dist/runtime/{types-DQhBe4Ei.d.mts → types-Dt3bENKf.d.mts} +1 -1
  32. package/dist/testing.d.mts +13 -13
  33. package/dist/testing.mjs +292 -156
  34. package/package.json +1 -1
  35. package/dist/runtime/server/composables/useConfigDirectorClient.d.mts +0 -6
  36. package/dist/runtime/server/composables/useConfigDirectorClient.d.ts +0 -6
  37. package/dist/runtime/server/composables/useConfigDirectorClient.mjs +0 -9
  38. /package/dist/runtime/{types-F0fKKk6-.d.mts → types-C30VJ25l.d.mts} +0 -0
@@ -491,6 +491,183 @@ var StreamingTransport = class {
491
491
  }
492
492
  };
493
493
  //#endregion
494
+ //#region ../shared/src/native-values.ts
495
+ const encodeNativeValue = (key, value) => {
496
+ if (key.trim().length == 0) throw new ConfigDirectorValidationError("Invalid config key. The key of a value cannot be empty.");
497
+ return encode(key, value);
498
+ };
499
+ const encode = (key, value) => {
500
+ switch (typeof value) {
501
+ case "boolean": return {
502
+ type: "boolean",
503
+ value: value ? "true" : "false"
504
+ };
505
+ case "number": return encodeNumber(key, value);
506
+ case "string": return {
507
+ type: "string",
508
+ value
509
+ };
510
+ case "object": if (value !== null) {
511
+ assertJsonContents(key, value, "");
512
+ return {
513
+ type: "json",
514
+ value: JSON.stringify(value)
515
+ };
516
+ }
517
+ }
518
+ throw new ConfigDirectorValidationError(`Invalid value for '${key}': ${describe(value)}. Use a boolean, a number, a string, a plain object, or an array.`);
519
+ };
520
+ const encodeNumber = (key, value) => {
521
+ if (!Number.isFinite(value)) throw new ConfigDirectorValidationError(`Invalid value for '${key}': ${describe(value)} is not a finite number.`);
522
+ if (Number.isInteger(value)) {
523
+ 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.`);
524
+ return {
525
+ type: "integer",
526
+ value: String(value)
527
+ };
528
+ }
529
+ return {
530
+ type: "float",
531
+ value: toPlainDecimal(value)
532
+ };
533
+ };
534
+ const toPlainDecimal = (value) => {
535
+ const text = String(value);
536
+ const exponentIndex = text.indexOf("e-");
537
+ if (exponentIndex < 0) return text;
538
+ const sign = text.startsWith("-") ? "-" : "";
539
+ const [integerDigits = "", fractionDigits = ""] = text.slice(sign.length, exponentIndex).split(".");
540
+ const leadingZeros = Number(text.slice(exponentIndex + 2)) - integerDigits.length;
541
+ return `${sign}0.${"0".repeat(leadingZeros)}${integerDigits}${fractionDigits}`;
542
+ };
543
+ const assertJsonContents = (key, value, path) => {
544
+ if (value === null || typeof value === "boolean" || typeof value === "string") return;
545
+ if (typeof value === "number") {
546
+ if (!Number.isFinite(value)) throw invalidJsonContents(key, path, `${describe(value)} is not a finite number`);
547
+ return;
548
+ }
549
+ if (Array.isArray(value)) {
550
+ value.forEach((item, index) => assertJsonContents(key, item, `${path}[${index}]`));
551
+ return;
552
+ }
553
+ if (isPlainObject$1(value)) {
554
+ for (const [property, item] of Object.entries(value)) assertJsonContents(key, item, path ? `${path}.${property}` : property);
555
+ return;
556
+ }
557
+ throw invalidJsonContents(key, path, `${describe(value)} cannot be encoded as JSON`);
558
+ };
559
+ 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.`);
560
+ const isPlainObject$1 = (value) => {
561
+ if (typeof value !== "object" || value === null) return false;
562
+ const prototype = Object.getPrototypeOf(value);
563
+ return prototype === Object.prototype || prototype === null;
564
+ };
565
+ const describe = (value) => {
566
+ if (value === null) return "null";
567
+ if (typeof value === "function") return "a function";
568
+ if (typeof value === "object") return `a ${value.constructor?.name ?? "object"} instance`;
569
+ if (typeof value === "symbol") return "a symbol";
570
+ return String(value);
571
+ };
572
+ //#endregion
573
+ //#region ../js-client-core/src/native-values.ts
574
+ const nativeValueConfigState = (id, key, value, valueId) => {
575
+ const encoded = encodeNativeValue(key, value);
576
+ return {
577
+ id,
578
+ key,
579
+ type: encoded.type,
580
+ value: encoded.value,
581
+ valueId
582
+ };
583
+ };
584
+ //#endregion
585
+ //#region ../js-client-core/src/OfflineTransport.ts
586
+ const OFFLINE_ENVIRONMENT = {
587
+ environmentId: "offline",
588
+ projectId: "offline"
589
+ };
590
+ var OfflineTransport = class {
591
+ emitter = new Emitter();
592
+ constructor(source) {
593
+ this.source = source;
594
+ }
595
+ async connect() {
596
+ const configs = loadOfflineConfigs(this.source);
597
+ this.emitter.emit("configSetReceived", {
598
+ ...OFFLINE_ENVIRONMENT,
599
+ kind: "full",
600
+ configs
601
+ });
602
+ return this;
603
+ }
604
+ on = (name, handler) => this.emitter.on(name, handler);
605
+ off = (name, handler) => this.emitter.off(name, handler);
606
+ clear() {
607
+ this.emitter.clear();
608
+ }
609
+ close() {}
610
+ dispose() {
611
+ this.clear();
612
+ }
613
+ };
614
+ const loadOfflineConfigs = (source) => {
615
+ switch (source.kind) {
616
+ case "in-code-defaults": return {};
617
+ case "values": return encodeOfflineValues(source.values);
618
+ case "rejected": throw new ConfigDirectorInitializationError(source.message);
619
+ }
620
+ };
621
+ const encodeOfflineValues = (values) => {
622
+ try {
623
+ return Object.fromEntries(Object.entries(values).map(([key, value]) => [key, nativeValueConfigState(`offline:${key}`, key, value, void 0)]));
624
+ } catch (error) {
625
+ if (error instanceof ConfigDirectorValidationError) throw new ConfigDirectorInitializationError(`The offline values could not be loaded. ${error.message}`);
626
+ throw error;
627
+ }
628
+ };
629
+ //#endregion
630
+ //#region ../shared/src/constants.ts
631
+ const CLIENT_BASE_URL = "https://client-sdk-api.configdirector.com";
632
+ //#endregion
633
+ //#region ../js-client-core/src/connection-plan.ts
634
+ const planConnection = (clientSdkKey, connection, urlFactory) => {
635
+ if (connection?.mode === "offline") return {
636
+ kind: "stay-offline",
637
+ source: offlineSourceOf(connection)
638
+ };
639
+ const mode = connection?.mode ?? "streaming";
640
+ 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");
641
+ if (connection?.values !== void 0) return {
642
+ kind: "stay-offline",
643
+ source: {
644
+ kind: "rejected",
645
+ message: `The connection option 'values' applies only when the connection mode is 'offline', but the mode is '${mode}'.`
646
+ }
647
+ };
648
+ return {
649
+ kind: "connect",
650
+ mode,
651
+ clientSdkKey,
652
+ baseUrl: baseUrlOf(connection?.url, urlFactory)
653
+ };
654
+ };
655
+ const baseUrlOf = (url, urlFactory) => {
656
+ if (!url) return CLIENT_BASE_URL;
657
+ try {
658
+ return urlFactory(url);
659
+ } catch (error) {
660
+ throw new ConfigDirectorValidationError(`Invalid base URL '${url}'. Parsing failed: ${error}`);
661
+ }
662
+ };
663
+ const offlineSourceOf = (connection) => {
664
+ if (connection.values !== void 0) return {
665
+ kind: "values",
666
+ values: connection.values
667
+ };
668
+ return { kind: "in-code-defaults" };
669
+ };
670
+ //#endregion
494
671
  //#region ../shared/src/value-parser.ts
495
672
  const getRequestedType = (defaultValue) => {
496
673
  const baseType = typeof defaultValue;
@@ -710,16 +887,16 @@ const createDefaultLogger$1 = (level, messageDecorator) => {
710
887
  return new DefaultConsoleLogger(level ?? "warn", messageDecorator ?? new LogMessageDecorator());
711
888
  };
712
889
  //#endregion
890
+ //#region ../js-client-core/src/telemetry/discarding.ts
891
+ const discardingTelemetryClient = {
892
+ updateContext: async () => {},
893
+ evaluatedConfig: () => {},
894
+ recordInvalidContext: () => {},
895
+ close: async () => {}
896
+ };
897
+ //#endregion
713
898
  //#region ../shared/src/url.ts
714
899
  const defaultUrlFactory = (input, base) => new URL(input, base?.toString());
715
- const parseUrl = (url) => {
716
- if (!url) return;
717
- try {
718
- return new URL(url);
719
- } catch (error) {
720
- throw new ConfigDirectorValidationError(`Invalid URL '${url}'. Parsing failed: ${error}`);
721
- }
722
- };
723
900
  //#endregion
724
901
  //#region ../shared/src/transport/pollingInterval.ts
725
902
  const resolvePollingInterval = (configuredSeconds, defaults) => {
@@ -1078,7 +1255,7 @@ const admitContext = (context) => {
1078
1255
  //#endregion
1079
1256
  //#region ../js-client-core/src/DefaultConfigDirectorClient.ts
1080
1257
  const MAX_EXPONENTIAL_DELAY = 9;
1081
- var DefaultConfigDirectorClient = class {
1258
+ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
1082
1259
  logger;
1083
1260
  telemetryClient;
1084
1261
  configSet;
@@ -1093,18 +1270,20 @@ var DefaultConfigDirectorClient = class {
1093
1270
  readyPromise;
1094
1271
  readyResolve;
1095
1272
  currentContext;
1096
- connectionMode;
1273
+ connectionPlan;
1097
1274
  clientSession;
1098
- constructor(telemetryClient, clientSdkKey, sdkOptions, clientOptions, internalClientOptions) {
1275
+ static from(liveTelemetryClient, clientSdkKeyOrOptions, sdkOptions, clientOptions) {
1276
+ if (typeof clientSdkKeyOrOptions === "object") return new DefaultConfigDirectorClient(liveTelemetryClient, void 0, sdkOptions, clientSdkKeyOrOptions);
1277
+ return new DefaultConfigDirectorClient(liveTelemetryClient, clientSdkKeyOrOptions, sdkOptions, clientOptions);
1278
+ }
1279
+ constructor(liveTelemetryClient, clientSdkKey, sdkOptions, clientOptions, internalClientOptions) {
1099
1280
  this.logger = clientOptions?.logger ?? createDefaultLogger$1();
1100
1281
  this.clientSession = resolveClientSession(this.logger, internalClientOptions?.instanceIdStorage);
1101
1282
  this.timeout = clientOptions?.connection?.timeout ?? 3e3;
1102
- this.validateSdkKeyPresence(clientSdkKey);
1103
1283
  const urlFactory = internalClientOptions?.urlFactory ?? defaultUrlFactory;
1104
- const baseUrl = this.parseUrl(clientOptions?.connection?.url, urlFactory) ?? "https://client-sdk-api.configdirector.com";
1105
- this.connectionMode = clientOptions?.connection?.mode ?? "streaming";
1106
- this.telemetryClient = telemetryClient;
1107
- this.transport = internalClientOptions?.transport ?? this.buildTransport(clientSdkKey, sdkOptions, baseUrl, urlFactory, clientOptions, internalClientOptions);
1284
+ this.connectionPlan = planConnection(clientSdkKey, clientOptions?.connection, urlFactory);
1285
+ this.telemetryClient = this.buildTelemetryClient(liveTelemetryClient, clientOptions);
1286
+ this.transport = internalClientOptions?.transport ?? this.buildTransport(sdkOptions, clientOptions, urlFactory, internalClientOptions);
1108
1287
  this.transport.on("configSetReceived", (configSet) => {
1109
1288
  this.hasReceivedConfigSet = true;
1110
1289
  this.readyResolve?.();
@@ -1141,8 +1320,8 @@ var DefaultConfigDirectorClient = class {
1141
1320
  const handlers = Array.isArray(handler) ? handler : [handler];
1142
1321
  for (const h of handlers) if (typeof h === "function") this.on(event, h);
1143
1322
  }
1144
- resolvePollingIntervalForMode(configuredSeconds) {
1145
- if (this.connectionMode !== "polling") return;
1323
+ pollingIntervalFor(mode, configuredSeconds) {
1324
+ if (mode !== "polling") return;
1146
1325
  const resolved = resolvePollingInterval(configuredSeconds, {
1147
1326
  defaultSeconds: 60,
1148
1327
  minimumSeconds: 30
@@ -1150,31 +1329,47 @@ var DefaultConfigDirectorClient = class {
1150
1329
  if (resolved.warning) this.logger.warn(`[ConfigDirectorClient] ${resolved.warning}`);
1151
1330
  return resolved.seconds;
1152
1331
  }
1153
- buildTransport(clientSdkKey, sdkOptions, baseUrl, urlFactory, clientOptions, internalClientOptions) {
1154
- return new (this.getTransportConstructor(this.connectionMode))({
1155
- clientSession: this.clientSession,
1156
- clientSdkKey,
1157
- baseUrl,
1158
- resolveUrl: urlFactory,
1159
- metaContext: {
1160
- ...clientOptions?.metadata,
1161
- sdkName: sdkOptions.sdkName,
1162
- sdkVersion: sdkOptions.sdkVersion,
1163
- userAgent: readUserAgent(),
1164
- host: readHost()
1165
- },
1166
- logger: this.logger,
1167
- fetch: internalClientOptions?.fetch,
1168
- connectionRetryDelay: internalClientOptions?.connectionRetryDelay ?? ((attempt) => {
1169
- return Math.pow(2, Math.min(attempt, MAX_EXPONENTIAL_DELAY)) * 1e3;
1170
- }),
1171
- pollingInterval: this.resolvePollingIntervalForMode(clientOptions?.connection?.pollingInterval)
1172
- });
1332
+ buildTelemetryClient(liveTelemetryClient, clientOptions) {
1333
+ const plan = this.connectionPlan;
1334
+ switch (plan.kind) {
1335
+ case "stay-offline": return discardingTelemetryClient;
1336
+ case "connect": return liveTelemetryClient({
1337
+ clientSdkKey: plan.clientSdkKey,
1338
+ baseUrl: plan.baseUrl,
1339
+ logger: this.logger,
1340
+ metadata: clientOptions?.metadata
1341
+ });
1342
+ }
1343
+ }
1344
+ buildTransport(sdkOptions, clientOptions, urlFactory, internalClientOptions) {
1345
+ const plan = this.connectionPlan;
1346
+ switch (plan.kind) {
1347
+ case "stay-offline": return new OfflineTransport(plan.source);
1348
+ case "connect": return new (this.getTransportConstructor(plan.mode))({
1349
+ clientSession: this.clientSession,
1350
+ clientSdkKey: plan.clientSdkKey,
1351
+ baseUrl: plan.baseUrl,
1352
+ resolveUrl: urlFactory,
1353
+ metaContext: {
1354
+ ...clientOptions?.metadata,
1355
+ sdkName: sdkOptions.sdkName,
1356
+ sdkVersion: sdkOptions.sdkVersion,
1357
+ userAgent: readUserAgent(),
1358
+ host: readHost()
1359
+ },
1360
+ logger: this.logger,
1361
+ fetch: internalClientOptions?.fetch,
1362
+ connectionRetryDelay: internalClientOptions?.connectionRetryDelay ?? ((attempt) => {
1363
+ return Math.pow(2, Math.min(attempt, MAX_EXPONENTIAL_DELAY)) * 1e3;
1364
+ }),
1365
+ pollingInterval: this.pollingIntervalFor(plan.mode, clientOptions?.connection?.pollingInterval)
1366
+ });
1367
+ }
1173
1368
  }
1174
- getTransportConstructor(connectionMode) {
1175
- switch (connectionMode) {
1369
+ getTransportConstructor(mode) {
1370
+ switch (mode) {
1176
1371
  case "polling": return PollingTransport;
1177
- default: return StreamingTransport;
1372
+ case "streaming": return StreamingTransport;
1178
1373
  }
1179
1374
  }
1180
1375
  async initialize(context) {
@@ -1182,17 +1377,28 @@ var DefaultConfigDirectorClient = class {
1182
1377
  await this.submitContext(context, "initialization");
1183
1378
  }
1184
1379
  async updateContext(context) {
1185
- await this.submitContext(context, "context update");
1380
+ switch (this.connectionPlan.kind) {
1381
+ case "connect":
1382
+ await this.submitContext(context, "context update");
1383
+ return;
1384
+ case "stay-offline":
1385
+ this.storeContext(this.admittedContextFor(context, "context update"));
1386
+ return;
1387
+ }
1388
+ }
1389
+ storeContext(context) {
1390
+ this.currentContext = context;
1391
+ this.emit("contextUpdated", { context });
1186
1392
  }
1187
1393
  async submitContext(context, submission) {
1188
- await this.connectToTransport(this.contextToConnectWith(context, submission), submission);
1394
+ await this.connectToTransport(this.admittedContextFor(context, submission), submission);
1189
1395
  }
1190
- contextToConnectWith(suppliedContext, submission) {
1396
+ admittedContextFor(suppliedContext, submission) {
1191
1397
  const admission = admitContext(suppliedContext);
1192
1398
  switch (admission.outcome) {
1193
1399
  case "admitted": return admission.context;
1194
1400
  case "ignored":
1195
- 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 '_'.`);
1401
+ 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 '_'.`);
1196
1402
  this.telemetryClient.recordInvalidContext(admission.kindSample);
1197
1403
  return;
1198
1404
  }
@@ -1224,11 +1430,21 @@ var DefaultConfigDirectorClient = class {
1224
1430
  if (remainingTimeout > 0) await Promise.race([this.readyPromise, new Promise((resolve) => {
1225
1431
  this.timeoutTimer = setTimeout(() => resolve(), remainingTimeout);
1226
1432
  })]).finally(() => clearTimeout(this.timeoutTimer));
1227
- 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.`);
1433
+ if (!this.ready) this.logger.warn(`[ConfigDirectorClient] Timed out waiting for ${caller} after ${this.timeout}ms. ${this.timeoutWarningDetails()}`);
1228
1434
  } catch (error) {
1435
+ if (error instanceof ConfigDirectorInitializationError) {
1436
+ this.initializing = false;
1437
+ throw error;
1438
+ }
1229
1439
  this.logger.error(`[ConfigDirectorClient] An error occurred during ${caller}: `, error);
1230
1440
  }
1231
1441
  }
1442
+ timeoutWarningDetails() {
1443
+ switch (this.connectionPlan.kind) {
1444
+ 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.";
1445
+ case "stay-offline": return "The offline values were not delivered, so configs return the default value.";
1446
+ }
1447
+ }
1232
1448
  findKeysRemovedBy(configSet) {
1233
1449
  if (!this.configSet || configSet.kind != "full") return [];
1234
1450
  return Object.keys(this.configSet.configs).filter((key) => !Object.prototype.hasOwnProperty.call(configSet.configs, key));
@@ -1323,17 +1539,6 @@ var DefaultConfigDirectorClient = class {
1323
1539
  this.logger.error(`[ConfigDirectorClient] Error executing event handlers for '${name}'`, error);
1324
1540
  }
1325
1541
  }
1326
- parseUrl(url, urlFactory) {
1327
- if (!url) return;
1328
- try {
1329
- return urlFactory(url);
1330
- } catch (error) {
1331
- throw new ConfigDirectorValidationError(`Invalid base URL '${url}'. Parsing failed: ${error}`);
1332
- }
1333
- }
1334
- validateSdkKeyPresence(sdkKey) {
1335
- 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");
1336
- }
1337
1542
  validateDefaultValue(defaultValue) {
1338
1543
  if (defaultValue === void 0 || defaultValue === null) throw new ConfigDirectorValidationError("Invalid default value. The default value for a config must be defined and non-null.");
1339
1544
  if (typeof defaultValue === "function") throw new ConfigDirectorValidationError("Invalid default value. The default value for a config cannot be a function.");
@@ -1359,14 +1564,24 @@ var DefaultConfigDirectorClient = class {
1359
1564
  this.handlersMap.clear();
1360
1565
  }
1361
1566
  pauseNetwork() {
1362
- this.logger.debug("[ConfigDirectorClient] pauseNetwork() called, pausing transport connection");
1363
- clearTimeout(this.timeoutTimer);
1364
- this.timeoutTimer = void 0;
1365
- this.transport.close();
1366
- this.ready = false;
1567
+ switch (this.connectionPlan.kind) {
1568
+ case "stay-offline": return;
1569
+ case "connect":
1570
+ this.logger.debug("[ConfigDirectorClient] pauseNetwork() called, pausing transport connection");
1571
+ clearTimeout(this.timeoutTimer);
1572
+ this.timeoutTimer = void 0;
1573
+ this.transport.close();
1574
+ this.ready = false;
1575
+ return;
1576
+ }
1367
1577
  }
1368
1578
  async resumeNetwork() {
1369
- await this.connectToTransport(this.currentContext, "network resume");
1579
+ switch (this.connectionPlan.kind) {
1580
+ case "stay-offline": return;
1581
+ case "connect":
1582
+ await this.connectToTransport(this.currentContext, "network resume");
1583
+ return;
1584
+ }
1370
1585
  }
1371
1586
  async close() {
1372
1587
  this.logger.debug("[ConfigDirectorClient] close() has been called, closing connection to server");
@@ -1665,27 +1880,28 @@ const generateValueId = async (v) => {
1665
1880
  };
1666
1881
  //#endregion
1667
1882
  //#region ../js-browser-client/src/BrowserConfigDirectorClient.ts
1668
- const createBrowserClient = (clientSdkKey, sdkOptions, clientOptions) => {
1669
- const logger = clientOptions?.logger ?? createDefaultLogger$1();
1670
- const baseUrl = parseUrl(clientOptions?.connection?.url) ?? "https://client-sdk-api.configdirector.com";
1671
- return new DefaultConfigDirectorClient(new WebWorkerTelemetryClient({
1883
+ /**
1884
+ * Creates a browser client. With a client SDK key it connects to ConfigDirector; with
1885
+ * {@link OfflineClientOptions} alone it runs in `offline` mode and serves `connection.values`. An
1886
+ * absent key with `clientOptions` is the same as the options alone when their mode is `offline`,
1887
+ * and fails like a missing key otherwise.
1888
+ */
1889
+ const createBrowserClient = (clientSdkKeyOrOptions, sdkOptions, clientOptions) => {
1890
+ return DefaultConfigDirectorClient.from(({ clientSdkKey, baseUrl, logger, metadata }) => new WebWorkerTelemetryClient({
1672
1891
  sdkKey: clientSdkKey,
1673
1892
  sdkIdentity: {
1674
1893
  sdkName: sdkOptions.sdkName,
1675
1894
  sdkVersion: sdkOptions.sdkVersion
1676
1895
  },
1677
1896
  metaContext: {
1678
- appName: clientOptions?.metadata?.appName,
1679
- appVersion: clientOptions?.metadata?.appVersion
1897
+ appName: metadata?.appName,
1898
+ appVersion: metadata?.appVersion
1680
1899
  },
1681
1900
  logger,
1682
1901
  baseUrl,
1683
1902
  urlFactory: defaultUrlFactory,
1684
1903
  valueIdGenerator: generateValueId
1685
- }), clientSdkKey, sdkOptions, {
1686
- ...clientOptions,
1687
- logger
1688
- });
1904
+ }), clientSdkKeyOrOptions, sdkOptions, clientOptions);
1689
1905
  };
1690
1906
  //#endregion
1691
1907
  //#region ../js-client-core/src/installedTestClient.ts
@@ -1717,6 +1933,13 @@ const decideMountAction = (client, context) => {
1717
1933
  return "none";
1718
1934
  };
1719
1935
  //#endregion
1936
+ //#region ../shared/src/browser-half.ts
1937
+ const decideBrowserHalfSource = (browserHalf, serverConnectionMode) => {
1938
+ if (browserHalf.mode !== void 0 || browserHalf.values !== void 0) return "as-given";
1939
+ if (serverConnectionMode === "offline") return "inherit-offline";
1940
+ return "connect";
1941
+ };
1942
+ //#endregion
1720
1943
  //#region src/runtime/app/composables/useConfigDirectorClient.ts
1721
1944
  const useConfigDirectorClient = () => {
1722
1945
  const { $configDirectorClient } = useNuxtApp();
@@ -1761,13 +1984,16 @@ const createDefaultLogger = (level) => {
1761
1984
  return logger;
1762
1985
  };
1763
1986
  //#endregion
1987
+ //#region src/runtime/offline-values-route.ts
1988
+ const OFFLINE_VALUES_ROUTE = "/_configdirector/offline-values";
1989
+ //#endregion
1764
1990
  //#region src/runtime/plugin.client.ts
1765
1991
  const DEFAULT_INITIALIZATION_TIMEOUT_MILLISECONDS = 2e3;
1766
- var plugin_client_default = defineNuxtPlugin((nuxtApp) => {
1992
+ var plugin_client_default = defineNuxtPlugin(async (nuxtApp) => {
1767
1993
  const runtimeConfig = useRuntimeConfig();
1768
1994
  const logger = createDefaultLogger(runtimeConfig.public?.configdirector?.logLevel);
1769
1995
  const installedTestClient = readInstalledTestClient();
1770
- const client = installedTestClient ?? buildClient(runtimeConfig.public?.configdirector, logger);
1996
+ const client = installedTestClient ?? await buildClient(runtimeConfig.public?.configdirector, logger);
1771
1997
  logger.debug(installedTestClient ? "Installed ConfigDirector Nuxt plugin with a test client" : "Installed ConfigDirector Nuxt plugin");
1772
1998
  const readyStatus = shallowRef(client.isReady ? "ready" : "loading");
1773
1999
  client.on("clientReady", () => {
@@ -1788,29 +2014,58 @@ var plugin_client_default = defineNuxtPlugin((nuxtApp) => {
1788
2014
  configDirectorClientReadyStatus: readonly(readyStatus)
1789
2015
  } };
1790
2016
  });
1791
- const buildClient = (configdirector, logger) => {
1792
- if (!configdirector?.clientSdkKey) {
2017
+ const buildClient = async (configdirector, logger) => {
2018
+ const browserHalf = await browserHalfConnectionOf(configdirector, logger);
2019
+ if (browserHalf.mode !== "offline" && !configdirector?.clientSdkKey) {
1793
2020
  const message = "The ConfigDirector clientSdkKey must be configured for the plugin to initialize. You can provide it in nuxt.config.ts under 'runtimeConfig.public.configdirector.clientSdkKey' or as a runtime environment variable named NUXT_PUBLIC_CONFIGDIRECTOR_CLIENT_SDK_KEY";
1794
2021
  logger.error(message);
1795
2022
  throw new ConfigDirectorInitializationError(message);
1796
2023
  }
1797
- const { clientSdkKey, appName, appVersion, baseUrl, connection } = configdirector;
1798
- return createBrowserClient(clientSdkKey, {
2024
+ return createBrowserClient(configdirector?.clientSdkKey || void 0, {
1799
2025
  sdkName: "nuxt-sdk",
1800
- sdkVersion: "1.10.0"
2026
+ sdkVersion: "1.11.1"
1801
2027
  }, {
1802
2028
  metadata: {
1803
- appName,
1804
- appVersion
2029
+ appName: configdirector?.appName,
2030
+ appVersion: configdirector?.appVersion
1805
2031
  },
1806
2032
  connection: {
1807
- url: baseUrl || void 0,
1808
- mode: connection?.mode || void 0,
1809
- pollingInterval: connection?.pollingInterval || void 0,
1810
- timeout: connection?.timeout || DEFAULT_INITIALIZATION_TIMEOUT_MILLISECONDS
2033
+ url: configdirector?.baseUrl || void 0,
2034
+ mode: browserHalf.mode,
2035
+ values: browserHalf.values,
2036
+ pollingInterval: configdirector?.connection?.pollingInterval || void 0,
2037
+ timeout: configdirector?.connection?.timeout || DEFAULT_INITIALIZATION_TIMEOUT_MILLISECONDS
1811
2038
  },
1812
2039
  logger
1813
2040
  });
1814
2041
  };
2042
+ const browserHalfConnectionOf = async (configdirector, logger) => {
2043
+ const givenConnection = {
2044
+ mode: configdirector?.connection?.mode || void 0,
2045
+ values: configdirector?.connection?.values
2046
+ };
2047
+ const source = decideBrowserHalfSource(givenConnection, configdirector?.serverConnectionMode);
2048
+ logger.debug(`Browser half source: ${source}`);
2049
+ switch (source) {
2050
+ case "as-given": return givenConnection;
2051
+ case "inherit-offline": return {
2052
+ mode: "offline",
2053
+ values: await fetchServerOfflineValues(logger)
2054
+ };
2055
+ case "connect": return {
2056
+ mode: void 0,
2057
+ values: void 0
2058
+ };
2059
+ }
2060
+ };
2061
+ const fetchServerOfflineValues = async (logger) => {
2062
+ try {
2063
+ return await $fetch(OFFLINE_VALUES_ROUTE);
2064
+ } catch (error) {
2065
+ const message = `The server runs ConfigDirector in offline mode, but its values could not be fetched from ${OFFLINE_VALUES_ROUTE}: ${error instanceof Error ? error.message : String(error)}. A statically generated site has no server to serve them; set 'runtimeConfig.public.configdirector.connection' to { mode: "offline", values } in nuxt.config.ts to give the browser its own values.`;
2066
+ logger.error(message);
2067
+ throw new ConfigDirectorInitializationError(message);
2068
+ }
2069
+ };
1815
2070
  //#endregion
1816
2071
  export { plugin_client_default as default };
@@ -0,0 +1,6 @@
1
+ import { n as ConfigDirectorClient } from "../../types-CS7AMNWe.mjs";
2
+
3
+ //#region src/runtime/server/composables/useConfigDirectorServerClient.d.ts
4
+ declare const useConfigDirectorServerClient: () => ConfigDirectorClient;
5
+ //#endregion
6
+ export { useConfigDirectorServerClient };
@@ -0,0 +1,6 @@
1
+ import { n as ConfigDirectorClient } from "../../types-CS7AMNWe.mjs";
2
+
3
+ //#region src/runtime/server/composables/useConfigDirectorServerClient.d.ts
4
+ declare const useConfigDirectorServerClient: () => ConfigDirectorClient;
5
+ //#endregion
6
+ export { useConfigDirectorServerClient };
@@ -0,0 +1,9 @@
1
+ import { useNitroApp } from "nitropack/runtime";
2
+ //#region src/runtime/server/composables/useConfigDirectorServerClient.ts
3
+ const useConfigDirectorServerClient = () => {
4
+ const serverSdkClient = useNitroApp().configDirectorClient;
5
+ if (!serverSdkClient) throw new Error("The ConfigDirector Nitro plugin was not initialized");
6
+ return serverSdkClient;
7
+ };
8
+ //#endregion
9
+ export { useConfigDirectorServerClient };
@@ -1,4 +1,4 @@
1
- import { t as ClientHooks } from "../../types-B9_4pADA.mjs";
1
+ import { t as ClientHooks } from "../../types-CS7AMNWe.mjs";
2
2
 
3
3
  //#region src/runtime/server/composables/useConfigDirectorServerHooks.d.ts
4
4
  declare const useConfigDirectorServerHooks: (hooks: ClientHooks) => void;
@@ -1,4 +1,4 @@
1
- import { t as ClientHooks } from "../../types-B9_4pADA.mjs";
1
+ import { t as ClientHooks } from "../../types-CS7AMNWe.mjs";
2
2
 
3
3
  //#region src/runtime/server/composables/useConfigDirectorServerHooks.d.ts
4
4
  declare const useConfigDirectorServerHooks: (hooks: ClientHooks) => void;
@@ -1,8 +1,7 @@
1
- import { useNitroApp } from "nitropack/runtime";
1
+ import { useConfigDirectorServerClient } from "./useConfigDirectorServerClient.mjs";
2
2
  //#region src/runtime/server/composables/useConfigDirectorServerHooks.ts
3
3
  const useConfigDirectorServerHooks = (hooks) => {
4
- const client = useNitroApp().configDirectorClient;
5
- if (!client) throw new Error("The ConfigDirector Nitro plugin was not initialized");
4
+ const client = useConfigDirectorServerClient();
6
5
  const entries = Object.entries(hooks);
7
6
  for (const [event, handler] of entries) {
8
7
  if (!handler) continue;
@@ -1,4 +1,4 @@
1
- import { i as ConfigValueType, n as ConfigEvaluation, r as ConfigState, t as ConfigDirectorContext } from "./types-IPLvRsUO.mjs";
1
+ import { a as ConfigValueType, i as ConfigState, n as ConfigDirectorContext, r as ConfigEvaluation } from "./native-values-BV2k_xR2.mjs";
2
2
 
3
3
  //#region ../js-server-sdk/src/types.d.ts
4
4
  type ClientEvents = {
@@ -1,4 +1,4 @@
1
- import { i as ConfigValueType, n as ConfigEvaluation, t as ConfigDirectorContext } from "./types-IPLvRsUO.mjs";
1
+ import { a as ConfigValueType, n as ConfigDirectorContext, r as ConfigEvaluation } from "./native-values-BV2k_xR2.mjs";
2
2
 
3
3
  //#region ../js-client-core/src/Emitter.d.ts
4
4
  type EventType = string | symbol;