@configdirector/react-native-sdk 1.7.0 → 1.9.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.
@@ -89,6 +89,13 @@ var ConfigDirectorValidationError = class ConfigDirectorValidationError extends
89
89
  Object.setPrototypeOf(this, ConfigDirectorValidationError.prototype);
90
90
  }
91
91
  };
92
+ var ConfigDirectorInitializationError = class ConfigDirectorInitializationError extends Error {
93
+ constructor(message) {
94
+ super(message);
95
+ _defineProperty(this, "name", "ConfigDirectorInitializationError");
96
+ Object.setPrototypeOf(this, ConfigDirectorInitializationError.prototype);
97
+ }
98
+ };
92
99
  //#endregion
93
100
  //#region ../js-client-core/src/errors.ts
94
101
  const isFetchErrorFatal = (fetchError) => {
@@ -535,6 +542,187 @@ var StreamingTransport = class {
535
542
  }
536
543
  };
537
544
  //#endregion
545
+ //#region ../shared/src/native-values.ts
546
+ const encodeNativeValue = (key, value) => {
547
+ if (key.trim().length == 0) throw new ConfigDirectorValidationError("Invalid config key. The key of a value cannot be empty.");
548
+ return encode(key, value);
549
+ };
550
+ const encode = (key, value) => {
551
+ switch (typeof value) {
552
+ case "boolean": return {
553
+ type: "boolean",
554
+ value: value ? "true" : "false"
555
+ };
556
+ case "number": return encodeNumber(key, value);
557
+ case "string": return {
558
+ type: "string",
559
+ value
560
+ };
561
+ case "object": if (value !== null) {
562
+ assertJsonContents(key, value, "");
563
+ return {
564
+ type: "json",
565
+ value: JSON.stringify(value)
566
+ };
567
+ }
568
+ }
569
+ throw new ConfigDirectorValidationError(`Invalid value for '${key}': ${describe(value)}. Use a boolean, a number, a string, a plain object, or an array.`);
570
+ };
571
+ const encodeNumber = (key, value) => {
572
+ if (!Number.isFinite(value)) throw new ConfigDirectorValidationError(`Invalid value for '${key}': ${describe(value)} is not a finite number.`);
573
+ if (Number.isInteger(value)) {
574
+ 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.`);
575
+ return {
576
+ type: "integer",
577
+ value: String(value)
578
+ };
579
+ }
580
+ return {
581
+ type: "float",
582
+ value: toPlainDecimal(value)
583
+ };
584
+ };
585
+ const toPlainDecimal = (value) => {
586
+ const text = String(value);
587
+ const exponentIndex = text.indexOf("e-");
588
+ if (exponentIndex < 0) return text;
589
+ const sign = text.startsWith("-") ? "-" : "";
590
+ const [integerDigits = "", fractionDigits = ""] = text.slice(sign.length, exponentIndex).split(".");
591
+ const leadingZeros = Number(text.slice(exponentIndex + 2)) - integerDigits.length;
592
+ return `${sign}0.${"0".repeat(leadingZeros)}${integerDigits}${fractionDigits}`;
593
+ };
594
+ const assertJsonContents = (key, value, path) => {
595
+ if (value === null || typeof value === "boolean" || typeof value === "string") return;
596
+ if (typeof value === "number") {
597
+ if (!Number.isFinite(value)) throw invalidJsonContents(key, path, `${describe(value)} is not a finite number`);
598
+ return;
599
+ }
600
+ if (Array.isArray(value)) {
601
+ value.forEach((item, index) => assertJsonContents(key, item, `${path}[${index}]`));
602
+ return;
603
+ }
604
+ if (isPlainObject(value)) {
605
+ for (const [property, item] of Object.entries(value)) assertJsonContents(key, item, path ? `${path}.${property}` : property);
606
+ return;
607
+ }
608
+ throw invalidJsonContents(key, path, `${describe(value)} cannot be encoded as JSON`);
609
+ };
610
+ 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.`);
611
+ const isPlainObject = (value) => {
612
+ if (typeof value !== "object" || value === null) return false;
613
+ const prototype = Object.getPrototypeOf(value);
614
+ return prototype === Object.prototype || prototype === null;
615
+ };
616
+ const describe = (value) => {
617
+ if (value === null) return "null";
618
+ if (typeof value === "function") return "a function";
619
+ if (typeof value === "object") {
620
+ var _value$constructor$na, _value$constructor;
621
+ return `a ${(_value$constructor$na = (_value$constructor = value.constructor) === null || _value$constructor === void 0 ? void 0 : _value$constructor.name) !== null && _value$constructor$na !== void 0 ? _value$constructor$na : "object"} instance`;
622
+ }
623
+ if (typeof value === "symbol") return "a symbol";
624
+ return String(value);
625
+ };
626
+ //#endregion
627
+ //#region ../js-client-core/src/native-values.ts
628
+ const nativeValueConfigState = (id, key, value, valueId) => {
629
+ const encoded = encodeNativeValue(key, value);
630
+ return {
631
+ id,
632
+ key,
633
+ type: encoded.type,
634
+ value: encoded.value,
635
+ valueId
636
+ };
637
+ };
638
+ //#endregion
639
+ //#region ../js-client-core/src/OfflineTransport.ts
640
+ const OFFLINE_ENVIRONMENT = {
641
+ environmentId: "offline",
642
+ projectId: "offline"
643
+ };
644
+ var OfflineTransport = class {
645
+ constructor(source) {
646
+ this.source = source;
647
+ _defineProperty(this, "emitter", new Emitter());
648
+ _defineProperty(this, "on", (name, handler) => this.emitter.on(name, handler));
649
+ _defineProperty(this, "off", (name, handler) => this.emitter.off(name, handler));
650
+ }
651
+ async connect() {
652
+ const configs = loadOfflineConfigs(this.source);
653
+ this.emitter.emit("configSetReceived", {
654
+ ...OFFLINE_ENVIRONMENT,
655
+ kind: "full",
656
+ configs
657
+ });
658
+ return this;
659
+ }
660
+ clear() {
661
+ this.emitter.clear();
662
+ }
663
+ close() {}
664
+ dispose() {
665
+ this.clear();
666
+ }
667
+ };
668
+ const loadOfflineConfigs = (source) => {
669
+ switch (source.kind) {
670
+ case "in-code-defaults": return {};
671
+ case "values": return encodeOfflineValues(source.values);
672
+ case "rejected": throw new ConfigDirectorInitializationError(source.message);
673
+ }
674
+ };
675
+ const encodeOfflineValues = (values) => {
676
+ try {
677
+ return Object.fromEntries(Object.entries(values).map(([key, value]) => [key, nativeValueConfigState(`offline:${key}`, key, value, void 0)]));
678
+ } catch (error) {
679
+ if (error instanceof ConfigDirectorValidationError) throw new ConfigDirectorInitializationError(`The offline values could not be loaded. ${error.message}`);
680
+ throw error;
681
+ }
682
+ };
683
+ //#endregion
684
+ //#region ../shared/src/constants.ts
685
+ const CLIENT_BASE_URL = "https://client-sdk-api.configdirector.com";
686
+ //#endregion
687
+ //#region ../js-client-core/src/connection-plan.ts
688
+ const planConnection = (clientSdkKey, connection, urlFactory) => {
689
+ var _connection$mode;
690
+ if ((connection === null || connection === void 0 ? void 0 : connection.mode) === "offline") return {
691
+ kind: "stay-offline",
692
+ source: offlineSourceOf(connection)
693
+ };
694
+ const mode = (_connection$mode = connection === null || connection === void 0 ? void 0 : connection.mode) !== null && _connection$mode !== void 0 ? _connection$mode : "streaming";
695
+ 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");
696
+ if ((connection === null || connection === void 0 ? void 0 : connection.values) !== void 0) return {
697
+ kind: "stay-offline",
698
+ source: {
699
+ kind: "rejected",
700
+ message: `The connection option 'values' applies only when the connection mode is 'offline', but the mode is '${mode}'.`
701
+ }
702
+ };
703
+ return {
704
+ kind: "connect",
705
+ mode,
706
+ clientSdkKey,
707
+ baseUrl: baseUrlOf(connection === null || connection === void 0 ? void 0 : connection.url, urlFactory)
708
+ };
709
+ };
710
+ const baseUrlOf = (url, urlFactory) => {
711
+ if (!url) return CLIENT_BASE_URL;
712
+ try {
713
+ return urlFactory(url);
714
+ } catch (error) {
715
+ throw new ConfigDirectorValidationError(`Invalid base URL '${url}'. Parsing failed: ${error}`);
716
+ }
717
+ };
718
+ const offlineSourceOf = (connection) => {
719
+ if (connection.values !== void 0) return {
720
+ kind: "values",
721
+ values: connection.values
722
+ };
723
+ return { kind: "in-code-defaults" };
724
+ };
725
+ //#endregion
538
726
  //#region ../shared/src/value-parser.ts
539
727
  const getRequestedType = (defaultValue) => {
540
728
  const baseType = typeof defaultValue;
@@ -758,12 +946,17 @@ const createDefaultLogger = (level, messageDecorator) => {
758
946
  return new DefaultConsoleLogger(level !== null && level !== void 0 ? level : "warn", messageDecorator !== null && messageDecorator !== void 0 ? messageDecorator : new LogMessageDecorator());
759
947
  };
760
948
  //#endregion
949
+ //#region ../js-client-core/src/telemetry/discarding.ts
950
+ const discardingTelemetryClient = {
951
+ updateContext: async () => {},
952
+ evaluatedConfig: () => {},
953
+ recordInvalidContext: () => {},
954
+ close: async () => {}
955
+ };
956
+ //#endregion
761
957
  //#region ../shared/src/url.ts
762
958
  const defaultUrlFactory = (input, base) => new URL(input, base === null || base === void 0 ? void 0 : base.toString());
763
959
  //#endregion
764
- //#region ../shared/src/constants.ts
765
- const CLIENT_BASE_URL = "https://client-sdk-api.configdirector.com";
766
- //#endregion
767
960
  //#region ../shared/src/transport/pollingInterval.ts
768
961
  const resolvePollingInterval = (configuredSeconds, defaults) => {
769
962
  if (configuredSeconds === void 0) return {
@@ -789,10 +982,34 @@ const generateInstanceId = () => {
789
982
  });
790
983
  };
791
984
  //#endregion
985
+ //#region ../js-client-core/src/browser-globals.ts
986
+ const read = (readValue) => {
987
+ try {
988
+ return readValue(globalThis);
989
+ } catch {
990
+ return;
991
+ }
992
+ };
993
+ const readUserAgent = () => read((globals) => {
994
+ var _globals$navigator;
995
+ return (_globals$navigator = globals.navigator) === null || _globals$navigator === void 0 ? void 0 : _globals$navigator.userAgent;
996
+ });
997
+ const readHost = () => read((globals) => {
998
+ var _globals$location;
999
+ return (_globals$location = globals.location) === null || _globals$location === void 0 ? void 0 : _globals$location.host;
1000
+ });
1001
+ const localStorageIfAvailable = () => {
1002
+ try {
1003
+ var _localStorage;
1004
+ return (_localStorage = globalThis.localStorage) !== null && _localStorage !== void 0 ? _localStorage : void 0;
1005
+ } catch {
1006
+ return;
1007
+ }
1008
+ };
1009
+ //#endregion
792
1010
  //#region ../js-client-core/src/client-session.ts
793
1011
  const STORAGE_KEY = "configdirector-sdk:instance-id";
794
1012
  const RECORD_VERSION = 1;
795
- const MAX_AGE_MS = 360 * 60 * 1e3;
796
1013
  const STORAGE_DEADLINE_MS = 1e3;
797
1014
  const CHECKSUM_SALT = "configdirector.instanceId.v1";
798
1015
  const UUID_V4_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
@@ -809,14 +1026,6 @@ const checksumFor = (id, createdAt) => {
809
1026
  const low = fnv1a(payload, 2166136261);
810
1027
  return fnv1a(`${low}|${payload}`, 16777619).toString(16).padStart(8, "0") + low.toString(16).padStart(8, "0");
811
1028
  };
812
- const localStorageIfAvailable = () => {
813
- try {
814
- var _localStorage;
815
- return (_localStorage = globalThis.localStorage) !== null && _localStorage !== void 0 ? _localStorage : void 0;
816
- } catch {
817
- return;
818
- }
819
- };
820
1029
  const isUsableRecord = (record) => {
821
1030
  if (!record || typeof record !== "object") return false;
822
1031
  const { v, id, createdAt, checksum } = record;
@@ -845,11 +1054,6 @@ const readStoredInstanceId = async (storage, logger) => {
845
1054
  logger.debug("[ConfigDirectorClient] The stored instance id failed validation, generating a new one");
846
1055
  return { outcome: "nothing usable" };
847
1056
  }
848
- const age = Date.now() - record.createdAt;
849
- if (age < 0 || age >= MAX_AGE_MS) {
850
- logger.debug("[ConfigDirectorClient] The stored instance id has expired, generating a new one");
851
- return { outcome: "nothing usable" };
852
- }
853
1057
  return {
854
1058
  outcome: "stored",
855
1059
  instanceId: record.id
@@ -1069,23 +1273,6 @@ var PollingTransport = class extends AbstractPollingTransport {
1069
1273
  }
1070
1274
  };
1071
1275
  //#endregion
1072
- //#region ../js-client-core/src/browser-globals.ts
1073
- const read = (readValue) => {
1074
- try {
1075
- return readValue(globalThis);
1076
- } catch {
1077
- return;
1078
- }
1079
- };
1080
- const readUserAgent = () => read((globals) => {
1081
- var _globals$navigator;
1082
- return (_globals$navigator = globals.navigator) === null || _globals$navigator === void 0 ? void 0 : _globals$navigator.userAgent;
1083
- });
1084
- const readHost = () => read((globals) => {
1085
- var _globals$location;
1086
- return (_globals$location = globals.location) === null || _globals$location === void 0 ? void 0 : _globals$location.host;
1087
- });
1088
- //#endregion
1089
1276
  //#region ../shared/src/context-kind.ts
1090
1277
  const CONTEXT_KIND_MAXIMUM_LENGTH = 64;
1091
1278
  const KIND_SAMPLE_MAXIMUM_CHARACTERS = 128;
@@ -1126,11 +1313,190 @@ const admitContext = (context) => {
1126
1313
  };
1127
1314
  };
1128
1315
  //#endregion
1316
+ //#region ../shared/src/utf8-byte-length.ts
1317
+ const isHighSurrogate = (codeUnit) => codeUnit >= 55296 && codeUnit <= 56319;
1318
+ const isLowSurrogate = (codeUnit) => codeUnit >= 56320 && codeUnit <= 57343;
1319
+ const utf8ByteLength = (text) => {
1320
+ let bytes = 0;
1321
+ for (let i = 0; i < text.length; i++) {
1322
+ const codeUnit = text.charCodeAt(i);
1323
+ if (codeUnit <= 127) bytes += 1;
1324
+ else if (codeUnit <= 2047) bytes += 2;
1325
+ else if (isHighSurrogate(codeUnit) && i + 1 < text.length && isLowSurrogate(text.charCodeAt(i + 1))) {
1326
+ bytes += 4;
1327
+ i++;
1328
+ } else bytes += 3;
1329
+ }
1330
+ return bytes;
1331
+ };
1332
+ //#endregion
1333
+ //#region ../js-client-core/src/cached-values.ts
1334
+ const ENTRY_FORMAT_VERSION = 1;
1335
+ const DEFAULT_CONTEXT_KIND = "user";
1336
+ const CACHED_VALUES_STORAGE_KEY_PREFIX = "configdirector-sdk:cached-values:";
1337
+ const DEFAULT_CACHING_SIZE_LIMIT_BYTES = 256 * 1024;
1338
+ const planCaching = (connectionPlan, caching, platformProvider) => {
1339
+ var _caching$provider;
1340
+ if (connectionPlan.kind === "stay-offline") return {
1341
+ kind: "no cache",
1342
+ reason: "offline mode"
1343
+ };
1344
+ if ((caching === null || caching === void 0 ? void 0 : caching.enabled) === false) return {
1345
+ kind: "no cache",
1346
+ reason: "turned off"
1347
+ };
1348
+ const provider = (_caching$provider = caching === null || caching === void 0 ? void 0 : caching.provider) !== null && _caching$provider !== void 0 ? _caching$provider : platformProvider === null || platformProvider === void 0 ? void 0 : platformProvider(connectionPlan.clientSdkKey);
1349
+ if (!provider) return {
1350
+ kind: "no cache",
1351
+ reason: "no provider available"
1352
+ };
1353
+ return {
1354
+ kind: "cache",
1355
+ provider,
1356
+ clientSdkKey: connectionPlan.clientSdkKey
1357
+ };
1358
+ };
1359
+ const contextTagFor = (context, instanceId) => {
1360
+ var _context$kind;
1361
+ const contextKind = (_context$kind = context === null || context === void 0 ? void 0 : context.kind) !== null && _context$kind !== void 0 ? _context$kind : DEFAULT_CONTEXT_KIND;
1362
+ if ((context === null || context === void 0 ? void 0 : context.id) != null) return {
1363
+ contextKind,
1364
+ contextId: context.id
1365
+ };
1366
+ return {
1367
+ contextKind,
1368
+ anonymousInstanceId: instanceId
1369
+ };
1370
+ };
1371
+ const sameContextTag = (stored, launch) => {
1372
+ if (stored.contextKind !== launch.contextKind) return false;
1373
+ if ("contextId" in stored && "contextId" in launch) return stored.contextId === launch.contextId;
1374
+ if ("anonymousInstanceId" in stored && "anonymousInstanceId" in launch) return stored.anonymousInstanceId === launch.anonymousInstanceId;
1375
+ return false;
1376
+ };
1377
+ const decideCachedValuesAdoption = (cachedValues, launchContextTag, hasReceivedLiveValues) => {
1378
+ if (hasReceivedLiveValues) return "live values already arrived";
1379
+ if (!sameContextTag(cachedValues.contextTag, launchContextTag)) return "context mismatch";
1380
+ return "adopt";
1381
+ };
1382
+ const isContextTag = (candidate) => {
1383
+ if (!candidate || typeof candidate !== "object") return false;
1384
+ const { contextKind, contextId, anonymousInstanceId } = candidate;
1385
+ if (typeof contextKind !== "string") return false;
1386
+ return typeof contextId === "string" || typeof anonymousInstanceId === "string";
1387
+ };
1388
+ const isConfigStateMap = (candidate) => {
1389
+ if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) return false;
1390
+ return Object.values(candidate).every((config) => config && typeof config === "object" && typeof config.id === "string" && typeof config.key === "string");
1391
+ };
1392
+ const isEntryFor = (clientSdkKey, candidate) => {
1393
+ if (!candidate || typeof candidate !== "object") return false;
1394
+ const entry = candidate;
1395
+ return entry.formatVersion === ENTRY_FORMAT_VERSION && entry.clientSdkKey === clientSdkKey && isContextTag(entry.contextTag) && typeof entry.environmentId === "string" && typeof entry.projectId === "string" && isConfigStateMap(entry.configs) && typeof entry.receivedAt === "number";
1396
+ };
1397
+ var CachedValuesStore = class {
1398
+ constructor(provider, clientSdkKey, logger) {
1399
+ this.provider = provider;
1400
+ this.clientSdkKey = clientSdkKey;
1401
+ this.logger = logger;
1402
+ _defineProperty(this, "warned", /* @__PURE__ */ new Set());
1403
+ _defineProperty(this, "pendingRead", void 0);
1404
+ _defineProperty(this, "queuedCachedValues", void 0);
1405
+ _defineProperty(this, "writing", false);
1406
+ }
1407
+ read() {
1408
+ var _this$pendingRead;
1409
+ (_this$pendingRead = this.pendingRead) !== null && _this$pendingRead !== void 0 || (this.pendingRead = this.readEntry());
1410
+ return this.pendingRead;
1411
+ }
1412
+ replace(cachedValues) {
1413
+ this.queuedCachedValues = cachedValues;
1414
+ this.writeQueued();
1415
+ }
1416
+ async readEntry() {
1417
+ let raw;
1418
+ try {
1419
+ raw = await this.provider.read();
1420
+ } catch (error) {
1421
+ this.warnOnce("read failed", "Could not read the values kept on the device, starting without them:", error);
1422
+ return { outcome: "failed" };
1423
+ }
1424
+ if (raw == null || raw === "") return { outcome: "nothing stored" };
1425
+ const entry = this.parseEntry(raw);
1426
+ if (!entry) {
1427
+ this.warnOnce("unreadable", "The values kept on the device could not be read and were removed, starting without them");
1428
+ await this.removeEntry();
1429
+ return { outcome: "unreadable" };
1430
+ }
1431
+ return {
1432
+ outcome: "stored",
1433
+ cachedValues: entry
1434
+ };
1435
+ }
1436
+ parseEntry(raw) {
1437
+ try {
1438
+ const candidate = JSON.parse(raw);
1439
+ return isEntryFor(this.clientSdkKey, candidate) ? candidate : void 0;
1440
+ } catch {
1441
+ return;
1442
+ }
1443
+ }
1444
+ async writeQueued() {
1445
+ if (this.writing) return;
1446
+ this.writing = true;
1447
+ try {
1448
+ while (this.queuedCachedValues) {
1449
+ const cachedValues = this.queuedCachedValues;
1450
+ this.queuedCachedValues = void 0;
1451
+ await this.writeEntry(cachedValues);
1452
+ }
1453
+ } finally {
1454
+ this.writing = false;
1455
+ }
1456
+ }
1457
+ async writeEntry(cachedValues) {
1458
+ const entry = {
1459
+ formatVersion: ENTRY_FORMAT_VERSION,
1460
+ clientSdkKey: this.clientSdkKey,
1461
+ ...cachedValues
1462
+ };
1463
+ const serialized = JSON.stringify(entry);
1464
+ const size = utf8ByteLength(serialized);
1465
+ const limit = this.provider.sizeLimit();
1466
+ if (size > limit) {
1467
+ this.warnOnce("over the size limit", `The values received (${size} bytes) exceed the caching provider's limit of ${limit} bytes, so no values are kept on the device`);
1468
+ await this.removeEntry();
1469
+ return;
1470
+ }
1471
+ try {
1472
+ await this.provider.write(serialized);
1473
+ } catch (error) {
1474
+ this.warnOnce("write failed", "Could not keep the values on the device:", error);
1475
+ }
1476
+ }
1477
+ async removeEntry() {
1478
+ try {
1479
+ await this.provider.remove();
1480
+ } catch (error) {
1481
+ this.warnOnce("remove failed", "Could not remove the values kept on the device:", error);
1482
+ }
1483
+ }
1484
+ warnOnce(warning, message, ...details) {
1485
+ if (this.warned.has(warning)) return;
1486
+ this.warned.add(warning);
1487
+ this.logger.warn(`[ConfigDirectorClient] ${message}`, ...details);
1488
+ }
1489
+ };
1490
+ //#endregion
1129
1491
  //#region ../js-client-core/src/DefaultConfigDirectorClient.ts
1130
1492
  const MAX_EXPONENTIAL_DELAY = 9;
1131
- var DefaultConfigDirectorClient = class {
1132
- constructor(telemetryClient, clientSdkKey, sdkOptions, clientOptions, internalClientOptions) {
1133
- var _clientOptions$logger, _clientOptions$connec, _clientOptions$connec2, _internalClientOption, _this$parseUrl, _clientOptions$connec3, _clientOptions$connec4, _clientOptions$connec5, _internalClientOption2, _clientOptions$hooks, _clientOptions$hooks2, _clientOptions$hooks3, _clientOptions$hooks4, _clientOptions$hooks5;
1493
+ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
1494
+ static from(liveTelemetryClient, clientSdkKeyOrOptions, sdkOptions, clientOptions, internalClientOptions) {
1495
+ if (typeof clientSdkKeyOrOptions === "object") return new DefaultConfigDirectorClient(liveTelemetryClient, void 0, sdkOptions, clientSdkKeyOrOptions, internalClientOptions);
1496
+ return new DefaultConfigDirectorClient(liveTelemetryClient, clientSdkKeyOrOptions, sdkOptions, clientOptions, internalClientOptions);
1497
+ }
1498
+ constructor(liveTelemetryClient, clientSdkKey, sdkOptions, clientOptions, internalClientOptions) {
1499
+ var _clientOptions$logger, _clientOptions$connec, _clientOptions$connec2, _internalClientOption, _internalClientOption2, _clientOptions$hooks, _clientOptions$hooks2, _clientOptions$hooks3, _clientOptions$hooks4, _clientOptions$hooks5, _clientOptions$hooks6;
1134
1500
  _defineProperty(this, "logger", void 0);
1135
1501
  _defineProperty(this, "telemetryClient", void 0);
1136
1502
  _defineProperty(this, "configSet", void 0);
@@ -1145,20 +1511,24 @@ var DefaultConfigDirectorClient = class {
1145
1511
  _defineProperty(this, "readyPromise", void 0);
1146
1512
  _defineProperty(this, "readyResolve", void 0);
1147
1513
  _defineProperty(this, "currentContext", void 0);
1148
- _defineProperty(this, "connectionMode", void 0);
1514
+ _defineProperty(this, "submittedContext", void 0);
1515
+ _defineProperty(this, "connectionPlan", void 0);
1149
1516
  _defineProperty(this, "clientSession", void 0);
1517
+ _defineProperty(this, "cachedValuesStore", void 0);
1518
+ _defineProperty(this, "servingCachedValues", false);
1519
+ _defineProperty(this, "cachedValuesAdoptionStarted", false);
1150
1520
  this.logger = (_clientOptions$logger = clientOptions === null || clientOptions === void 0 ? void 0 : clientOptions.logger) !== null && _clientOptions$logger !== void 0 ? _clientOptions$logger : createDefaultLogger();
1151
1521
  this.clientSession = resolveClientSession(this.logger, internalClientOptions === null || internalClientOptions === void 0 ? void 0 : internalClientOptions.instanceIdStorage);
1152
1522
  this.timeout = (_clientOptions$connec = clientOptions === null || clientOptions === void 0 || (_clientOptions$connec2 = clientOptions.connection) === null || _clientOptions$connec2 === void 0 ? void 0 : _clientOptions$connec2.timeout) !== null && _clientOptions$connec !== void 0 ? _clientOptions$connec : 3e3;
1153
- this.validateSdkKeyPresence(clientSdkKey);
1154
1523
  const urlFactory = (_internalClientOption = internalClientOptions === null || internalClientOptions === void 0 ? void 0 : internalClientOptions.urlFactory) !== null && _internalClientOption !== void 0 ? _internalClientOption : defaultUrlFactory;
1155
- const baseUrl = (_this$parseUrl = this.parseUrl(clientOptions === null || clientOptions === void 0 || (_clientOptions$connec3 = clientOptions.connection) === null || _clientOptions$connec3 === void 0 ? void 0 : _clientOptions$connec3.url, urlFactory)) !== null && _this$parseUrl !== void 0 ? _this$parseUrl : CLIENT_BASE_URL;
1156
- this.connectionMode = (_clientOptions$connec4 = clientOptions === null || clientOptions === void 0 || (_clientOptions$connec5 = clientOptions.connection) === null || _clientOptions$connec5 === void 0 ? void 0 : _clientOptions$connec5.mode) !== null && _clientOptions$connec4 !== void 0 ? _clientOptions$connec4 : "streaming";
1157
- this.telemetryClient = telemetryClient;
1158
- this.transport = (_internalClientOption2 = internalClientOptions === null || internalClientOptions === void 0 ? void 0 : internalClientOptions.transport) !== null && _internalClientOption2 !== void 0 ? _internalClientOption2 : this.buildTransport(clientSdkKey, sdkOptions, baseUrl, urlFactory, clientOptions, internalClientOptions);
1524
+ this.connectionPlan = planConnection(clientSdkKey, clientOptions === null || clientOptions === void 0 ? void 0 : clientOptions.connection, urlFactory);
1525
+ this.cachedValuesStore = this.buildCachedValuesStore(planCaching(this.connectionPlan, clientOptions === null || clientOptions === void 0 ? void 0 : clientOptions.caching, internalClientOptions === null || internalClientOptions === void 0 ? void 0 : internalClientOptions.platformCachingProvider));
1526
+ this.telemetryClient = this.buildTelemetryClient(liveTelemetryClient, clientOptions);
1527
+ this.transport = (_internalClientOption2 = internalClientOptions === null || internalClientOptions === void 0 ? void 0 : internalClientOptions.transport) !== null && _internalClientOption2 !== void 0 ? _internalClientOption2 : this.buildTransport(sdkOptions, clientOptions, urlFactory, internalClientOptions);
1159
1528
  this.transport.on("configSetReceived", (configSet) => {
1160
1529
  var _this$readyResolve;
1161
1530
  this.hasReceivedConfigSet = true;
1531
+ this.servingCachedValues = false;
1162
1532
  (_this$readyResolve = this.readyResolve) === null || _this$readyResolve === void 0 || _this$readyResolve.call(this);
1163
1533
  const removedKeys = this.findKeysRemovedBy(configSet);
1164
1534
  if (!this.configSet || configSet.kind == "full") this.configSet = configSet;
@@ -1166,6 +1536,7 @@ var DefaultConfigDirectorClient = class {
1166
1536
  ...this.configSet.configs,
1167
1537
  ...configSet.configs
1168
1538
  };
1539
+ this.keepValuesOnTheDevice(this.configSet);
1169
1540
  const keys = Object.keys(configSet.configs);
1170
1541
  this.emit("configsUpdated", {
1171
1542
  keys,
@@ -1187,14 +1558,88 @@ var DefaultConfigDirectorClient = class {
1187
1558
  this.registerHandler("configsUpdated", clientOptions === null || clientOptions === void 0 || (_clientOptions$hooks3 = clientOptions.hooks) === null || _clientOptions$hooks3 === void 0 ? void 0 : _clientOptions$hooks3.configsUpdated);
1188
1559
  this.registerHandler("contextUpdated", clientOptions === null || clientOptions === void 0 || (_clientOptions$hooks4 = clientOptions.hooks) === null || _clientOptions$hooks4 === void 0 ? void 0 : _clientOptions$hooks4.contextUpdated);
1189
1560
  this.registerHandler("connectionError", clientOptions === null || clientOptions === void 0 || (_clientOptions$hooks5 = clientOptions.hooks) === null || _clientOptions$hooks5 === void 0 ? void 0 : _clientOptions$hooks5.connectionError);
1561
+ this.registerHandler("cachedValuesLoaded", clientOptions === null || clientOptions === void 0 || (_clientOptions$hooks6 = clientOptions.hooks) === null || _clientOptions$hooks6 === void 0 ? void 0 : _clientOptions$hooks6.cachedValuesLoaded);
1562
+ }
1563
+ buildCachedValuesStore(cachingPlan) {
1564
+ switch (cachingPlan.kind) {
1565
+ case "no cache":
1566
+ this.logger.debug(`[ConfigDirectorClient] No values are kept on the device: ${cachingPlan.reason}`);
1567
+ return;
1568
+ case "cache": {
1569
+ const store = new CachedValuesStore(cachingPlan.provider, cachingPlan.clientSdkKey, this.logger);
1570
+ store.read();
1571
+ return store;
1572
+ }
1573
+ }
1574
+ }
1575
+ startCachedValuesAdoption() {
1576
+ const store = this.cachedValuesStore;
1577
+ if (!store || this.cachedValuesAdoptionStarted) return;
1578
+ this.cachedValuesAdoptionStarted = true;
1579
+ this.adoptCachedValues(store);
1580
+ }
1581
+ async adoptCachedValues(store) {
1582
+ const read = await store.read();
1583
+ switch (read.outcome) {
1584
+ case "nothing stored":
1585
+ case "unreadable":
1586
+ case "failed": return;
1587
+ case "stored": break;
1588
+ }
1589
+ const { instanceId } = await this.clientSession;
1590
+ const launchContextTag = contextTagFor(this.submittedContext, instanceId);
1591
+ const adoption = decideCachedValuesAdoption(read.cachedValues, launchContextTag, this.hasReceivedConfigSet);
1592
+ switch (adoption) {
1593
+ case "adopt":
1594
+ this.serveCachedValues(read.cachedValues);
1595
+ return;
1596
+ case "live values already arrived":
1597
+ case "context mismatch":
1598
+ this.logger.debug(`[ConfigDirectorClient] The values kept on the device were not adopted: ${adoption}`);
1599
+ return;
1600
+ }
1601
+ }
1602
+ serveCachedValues(cachedValues) {
1603
+ const { environmentId, projectId, configs } = cachedValues;
1604
+ this.configSet = {
1605
+ environmentId,
1606
+ projectId,
1607
+ configs,
1608
+ kind: "full"
1609
+ };
1610
+ this.servingCachedValues = true;
1611
+ const keys = Object.keys(configs);
1612
+ this.emit("cachedValuesLoaded", {});
1613
+ this.emit("configsUpdated", {
1614
+ keys,
1615
+ removedKeys: []
1616
+ });
1617
+ this.updateWatchers(keys);
1618
+ this.logger.debug("[ConfigDirectorClient] Serving the values kept on the device until live values arrive:", { keys });
1619
+ }
1620
+ keepValuesOnTheDevice(configSet) {
1621
+ const store = this.cachedValuesStore;
1622
+ if (!store) return;
1623
+ const context = this.submittedContext;
1624
+ const receivedAt = Date.now();
1625
+ this.clientSession.then(({ instanceId }) => store.replace({
1626
+ contextTag: contextTagFor(context, instanceId),
1627
+ environmentId: configSet.environmentId,
1628
+ projectId: configSet.projectId,
1629
+ configs: configSet.configs,
1630
+ receivedAt
1631
+ }));
1632
+ }
1633
+ servedReason(reason) {
1634
+ return this.servingCachedValues && reason === "found-match" ? "cached" : reason;
1190
1635
  }
1191
1636
  registerHandler(event, handler) {
1192
1637
  if (!handler) return;
1193
1638
  const handlers = Array.isArray(handler) ? handler : [handler];
1194
1639
  for (const h of handlers) if (typeof h === "function") this.on(event, h);
1195
1640
  }
1196
- resolvePollingIntervalForMode(configuredSeconds) {
1197
- if (this.connectionMode !== "polling") return;
1641
+ pollingIntervalFor(mode, configuredSeconds) {
1642
+ if (mode !== "polling") return;
1198
1643
  const resolved = resolvePollingInterval(configuredSeconds, {
1199
1644
  defaultSeconds: 60,
1200
1645
  minimumSeconds: 30
@@ -1202,32 +1647,49 @@ var DefaultConfigDirectorClient = class {
1202
1647
  if (resolved.warning) this.logger.warn(`[ConfigDirectorClient] ${resolved.warning}`);
1203
1648
  return resolved.seconds;
1204
1649
  }
1205
- buildTransport(clientSdkKey, sdkOptions, baseUrl, urlFactory, clientOptions, internalClientOptions) {
1206
- var _internalClientOption3, _clientOptions$connec6;
1207
- return new (this.getTransportConstructor(this.connectionMode))({
1208
- clientSession: this.clientSession,
1209
- clientSdkKey,
1210
- baseUrl,
1211
- resolveUrl: urlFactory,
1212
- metaContext: {
1213
- ...clientOptions === null || clientOptions === void 0 ? void 0 : clientOptions.metadata,
1214
- sdkName: sdkOptions.sdkName,
1215
- sdkVersion: sdkOptions.sdkVersion,
1216
- userAgent: readUserAgent(),
1217
- host: readHost()
1218
- },
1219
- logger: this.logger,
1220
- fetch: internalClientOptions === null || internalClientOptions === void 0 ? void 0 : internalClientOptions.fetch,
1221
- connectionRetryDelay: (_internalClientOption3 = internalClientOptions === null || internalClientOptions === void 0 ? void 0 : internalClientOptions.connectionRetryDelay) !== null && _internalClientOption3 !== void 0 ? _internalClientOption3 : ((attempt) => {
1222
- return Math.pow(2, Math.min(attempt, MAX_EXPONENTIAL_DELAY)) * 1e3;
1223
- }),
1224
- pollingInterval: this.resolvePollingIntervalForMode(clientOptions === null || clientOptions === void 0 || (_clientOptions$connec6 = clientOptions.connection) === null || _clientOptions$connec6 === void 0 ? void 0 : _clientOptions$connec6.pollingInterval)
1225
- });
1650
+ buildTelemetryClient(liveTelemetryClient, clientOptions) {
1651
+ const plan = this.connectionPlan;
1652
+ switch (plan.kind) {
1653
+ case "stay-offline": return discardingTelemetryClient;
1654
+ case "connect": return liveTelemetryClient({
1655
+ clientSdkKey: plan.clientSdkKey,
1656
+ baseUrl: plan.baseUrl,
1657
+ logger: this.logger,
1658
+ metadata: clientOptions === null || clientOptions === void 0 ? void 0 : clientOptions.metadata
1659
+ });
1660
+ }
1226
1661
  }
1227
- getTransportConstructor(connectionMode) {
1228
- switch (connectionMode) {
1662
+ buildTransport(sdkOptions, clientOptions, urlFactory, internalClientOptions) {
1663
+ const plan = this.connectionPlan;
1664
+ switch (plan.kind) {
1665
+ case "stay-offline": return new OfflineTransport(plan.source);
1666
+ case "connect":
1667
+ var _internalClientOption3, _clientOptions$connec3;
1668
+ return new (this.getTransportConstructor(plan.mode))({
1669
+ clientSession: this.clientSession,
1670
+ clientSdkKey: plan.clientSdkKey,
1671
+ baseUrl: plan.baseUrl,
1672
+ resolveUrl: urlFactory,
1673
+ metaContext: {
1674
+ ...clientOptions === null || clientOptions === void 0 ? void 0 : clientOptions.metadata,
1675
+ sdkName: sdkOptions.sdkName,
1676
+ sdkVersion: sdkOptions.sdkVersion,
1677
+ userAgent: readUserAgent(),
1678
+ host: readHost()
1679
+ },
1680
+ logger: this.logger,
1681
+ fetch: internalClientOptions === null || internalClientOptions === void 0 ? void 0 : internalClientOptions.fetch,
1682
+ connectionRetryDelay: (_internalClientOption3 = internalClientOptions === null || internalClientOptions === void 0 ? void 0 : internalClientOptions.connectionRetryDelay) !== null && _internalClientOption3 !== void 0 ? _internalClientOption3 : ((attempt) => {
1683
+ return Math.pow(2, Math.min(attempt, MAX_EXPONENTIAL_DELAY)) * 1e3;
1684
+ }),
1685
+ pollingInterval: this.pollingIntervalFor(plan.mode, clientOptions === null || clientOptions === void 0 || (_clientOptions$connec3 = clientOptions.connection) === null || _clientOptions$connec3 === void 0 ? void 0 : _clientOptions$connec3.pollingInterval)
1686
+ });
1687
+ }
1688
+ }
1689
+ getTransportConstructor(mode) {
1690
+ switch (mode) {
1229
1691
  case "polling": return PollingTransport;
1230
- default: return StreamingTransport;
1692
+ case "streaming": return StreamingTransport;
1231
1693
  }
1232
1694
  }
1233
1695
  async initialize(context) {
@@ -1235,17 +1697,28 @@ var DefaultConfigDirectorClient = class {
1235
1697
  await this.submitContext(context, "initialization");
1236
1698
  }
1237
1699
  async updateContext(context) {
1238
- await this.submitContext(context, "context update");
1700
+ switch (this.connectionPlan.kind) {
1701
+ case "connect":
1702
+ await this.submitContext(context, "context update");
1703
+ return;
1704
+ case "stay-offline":
1705
+ this.storeContext(this.admittedContextFor(context, "context update"));
1706
+ return;
1707
+ }
1708
+ }
1709
+ storeContext(context) {
1710
+ this.currentContext = context;
1711
+ this.emit("contextUpdated", { context });
1239
1712
  }
1240
1713
  async submitContext(context, submission) {
1241
- await this.connectToTransport(this.contextToConnectWith(context, submission), submission);
1714
+ await this.connectToTransport(this.admittedContextFor(context, submission), submission);
1242
1715
  }
1243
- contextToConnectWith(suppliedContext, submission) {
1716
+ admittedContextFor(suppliedContext, submission) {
1244
1717
  const admission = admitContext(suppliedContext);
1245
1718
  switch (admission.outcome) {
1246
1719
  case "admitted": return admission.context;
1247
1720
  case "ignored":
1248
- 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 '_'.`);
1721
+ 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 '_'.`);
1249
1722
  this.telemetryClient.recordInvalidContext(admission.kindSample);
1250
1723
  return;
1251
1724
  }
@@ -1254,6 +1727,8 @@ var DefaultConfigDirectorClient = class {
1254
1727
  return this.currentContext;
1255
1728
  }
1256
1729
  async connectToTransport(context, caller) {
1730
+ this.submittedContext = context;
1731
+ if (caller === "initialization") this.startCachedValuesAdoption();
1257
1732
  try {
1258
1733
  this.ready = false;
1259
1734
  this.readyPromise = new Promise((resolve) => {
@@ -1277,11 +1752,21 @@ var DefaultConfigDirectorClient = class {
1277
1752
  if (remainingTimeout > 0) await Promise.race([this.readyPromise, new Promise((resolve) => {
1278
1753
  this.timeoutTimer = setTimeout(() => resolve(), remainingTimeout);
1279
1754
  })]).finally(() => clearTimeout(this.timeoutTimer));
1280
- 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.`);
1755
+ if (!this.ready) this.logger.warn(`[ConfigDirectorClient] Timed out waiting for ${caller} after ${this.timeout}ms. ${this.timeoutWarningDetails()}`);
1281
1756
  } catch (error) {
1757
+ if (error instanceof ConfigDirectorInitializationError) {
1758
+ this.initializing = false;
1759
+ throw error;
1760
+ }
1282
1761
  this.logger.error(`[ConfigDirectorClient] An error occurred during ${caller}: `, error);
1283
1762
  }
1284
1763
  }
1764
+ timeoutWarningDetails() {
1765
+ switch (this.connectionPlan.kind) {
1766
+ 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.";
1767
+ case "stay-offline": return "The offline values were not delivered, so configs return the default value.";
1768
+ }
1769
+ }
1285
1770
  findKeysRemovedBy(configSet) {
1286
1771
  if (!this.configSet || configSet.kind != "full") return [];
1287
1772
  return Object.keys(this.configSet.configs).filter((key) => !Object.prototype.hasOwnProperty.call(configSet.configs, key));
@@ -1348,6 +1833,7 @@ var DefaultConfigDirectorClient = class {
1348
1833
  return defaultValue;
1349
1834
  }
1350
1835
  const parseResult = parseConfigValue(configState, defaultValue);
1836
+ const reason = this.servedReason(parseResult.reason);
1351
1837
  this.telemetryClient.evaluatedConfig({
1352
1838
  contextId: (_this$currentContext2 = this.currentContext) === null || _this$currentContext2 === void 0 ? void 0 : _this$currentContext2.id,
1353
1839
  type: configState.type,
@@ -1357,14 +1843,14 @@ var DefaultConfigDirectorClient = class {
1357
1843
  evaluatedValue: parseResult.parsedValue,
1358
1844
  evaluatedValueId: parseResult.parsedValueId,
1359
1845
  usedDefault: parseResult.usedDefault,
1360
- evaluationReason: parseResult.reason
1846
+ evaluationReason: reason
1361
1847
  });
1362
1848
  this.dispatchEvaluationEvent({
1363
1849
  key: configKey,
1364
1850
  value: parseResult.parsedValue,
1365
1851
  valueId: (_parseResult$parsedVa = parseResult.parsedValueId) !== null && _parseResult$parsedVa !== void 0 ? _parseResult$parsedVa : void 0,
1366
1852
  isDefaultValue: parseResult.usedDefault,
1367
- reason: parseResult.reason,
1853
+ reason,
1368
1854
  context: this.context
1369
1855
  });
1370
1856
  this.logger.debug(`[ConfigDirectorClient] Evaluated '${configKey}' to '${parseResult.parsedValue}'`);
@@ -1380,17 +1866,6 @@ var DefaultConfigDirectorClient = class {
1380
1866
  this.logger.error(`[ConfigDirectorClient] Error executing event handlers for '${name}'`, error);
1381
1867
  }
1382
1868
  }
1383
- parseUrl(url, urlFactory) {
1384
- if (!url) return;
1385
- try {
1386
- return urlFactory(url);
1387
- } catch (error) {
1388
- throw new ConfigDirectorValidationError(`Invalid base URL '${url}'. Parsing failed: ${error}`);
1389
- }
1390
- }
1391
- validateSdkKeyPresence(sdkKey) {
1392
- 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");
1393
- }
1394
1869
  validateDefaultValue(defaultValue) {
1395
1870
  if (defaultValue === void 0 || defaultValue === null) throw new ConfigDirectorValidationError("Invalid default value. The default value for a config must be defined and non-null.");
1396
1871
  if (typeof defaultValue === "function") throw new ConfigDirectorValidationError("Invalid default value. The default value for a config cannot be a function.");
@@ -1416,14 +1891,24 @@ var DefaultConfigDirectorClient = class {
1416
1891
  this.handlersMap.clear();
1417
1892
  }
1418
1893
  pauseNetwork() {
1419
- this.logger.debug("[ConfigDirectorClient] pauseNetwork() called, pausing transport connection");
1420
- clearTimeout(this.timeoutTimer);
1421
- this.timeoutTimer = void 0;
1422
- this.transport.close();
1423
- this.ready = false;
1894
+ switch (this.connectionPlan.kind) {
1895
+ case "stay-offline": return;
1896
+ case "connect":
1897
+ this.logger.debug("[ConfigDirectorClient] pauseNetwork() called, pausing transport connection");
1898
+ clearTimeout(this.timeoutTimer);
1899
+ this.timeoutTimer = void 0;
1900
+ this.transport.close();
1901
+ this.ready = false;
1902
+ return;
1903
+ }
1424
1904
  }
1425
1905
  async resumeNetwork() {
1426
- await this.connectToTransport(this.currentContext, "network resume");
1906
+ switch (this.connectionPlan.kind) {
1907
+ case "stay-offline": return;
1908
+ case "connect":
1909
+ await this.connectToTransport(this.currentContext, "network resume");
1910
+ return;
1911
+ }
1427
1912
  }
1428
1913
  async close() {
1429
1914
  var _this$readyResolve2;
@@ -1445,4 +1930,4 @@ var DefaultConfigDirectorClient = class {
1445
1930
  const INSTALLED_TEST_CLIENT_SLOT = Symbol.for("@configdirector/installed-test-client");
1446
1931
  const readInstalledTestClient = () => globalThis[INSTALLED_TEST_CLIENT_SLOT];
1447
1932
  //#endregion
1448
- export { CLIENT_BASE_URL as a, ConfigDirectorConnectionError as c, _defineProperty as d, fetchWithTimeout as i, ConfigDirectorValidationError as l, readInstalledTestClient as n, createDefaultLogger as o, DefaultConfigDirectorClient as r, isFetchErrorFatal as s, INSTALLED_TEST_CLIENT_SLOT as t, Emitter as u };
1933
+ export { DEFAULT_CACHING_SIZE_LIMIT_BYTES as a, createDefaultLogger as c, ConfigDirectorConnectionError as d, ConfigDirectorValidationError as f, CACHED_VALUES_STORAGE_KEY_PREFIX as i, nativeValueConfigState as l, _defineProperty as m, readInstalledTestClient as n, fetchWithTimeout as o, Emitter as p, DefaultConfigDirectorClient as r, discardingTelemetryClient as s, INSTALLED_TEST_CLIENT_SLOT as t, isFetchErrorFatal as u };