@twin.org/core 0.0.1-next.9 → 0.0.2-next.3

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 (77) hide show
  1. package/dist/cjs/index.cjs +1574 -833
  2. package/dist/esm/index.mjs +1571 -834
  3. package/dist/types/errors/baseError.d.ts +8 -1
  4. package/dist/types/factories/factory.d.ts +20 -1
  5. package/dist/types/helpers/arrayHelper.d.ts +13 -0
  6. package/dist/types/helpers/envHelper.d.ts +16 -0
  7. package/dist/types/helpers/errorHelper.d.ts +2 -1
  8. package/dist/types/helpers/jsonHelper.d.ts +30 -0
  9. package/dist/types/helpers/objectHelper.d.ts +25 -0
  10. package/dist/types/helpers/uint8ArrayHelper.d.ts +11 -0
  11. package/dist/types/index.d.ts +6 -0
  12. package/dist/types/models/IComponent.d.ts +12 -3
  13. package/dist/types/models/II18nShared.d.ts +29 -0
  14. package/dist/types/models/coerceType.d.ts +49 -0
  15. package/dist/types/models/compressionType.d.ts +1 -1
  16. package/dist/types/models/objectOrArray.d.ts +4 -0
  17. package/dist/types/utils/asyncCache.d.ts +10 -1
  18. package/dist/types/utils/coerce.d.ts +22 -0
  19. package/dist/types/utils/guards.d.ts +35 -0
  20. package/dist/types/utils/is.d.ts +12 -0
  21. package/dist/types/utils/sharedStore.d.ts +23 -0
  22. package/dist/types/utils/validation.d.ts +2 -0
  23. package/docs/changelog.md +323 -1
  24. package/docs/reference/classes/AlreadyExistsError.md +103 -27
  25. package/docs/reference/classes/ArrayHelper.md +71 -5
  26. package/docs/reference/classes/AsyncCache.md +75 -13
  27. package/docs/reference/classes/Base32.md +9 -5
  28. package/docs/reference/classes/Base58.md +9 -5
  29. package/docs/reference/classes/Base64.md +12 -6
  30. package/docs/reference/classes/Base64Url.md +9 -5
  31. package/docs/reference/classes/BaseError.md +101 -29
  32. package/docs/reference/classes/BitString.md +23 -11
  33. package/docs/reference/classes/Coerce.md +110 -12
  34. package/docs/reference/classes/Compression.md +19 -11
  35. package/docs/reference/classes/ConflictError.md +106 -28
  36. package/docs/reference/classes/Converter.md +72 -28
  37. package/docs/reference/classes/EnvHelper.md +45 -0
  38. package/docs/reference/classes/ErrorHelper.md +19 -7
  39. package/docs/reference/classes/Factory.md +95 -17
  40. package/docs/reference/classes/FilenameHelper.md +6 -4
  41. package/docs/reference/classes/GeneralError.md +101 -27
  42. package/docs/reference/classes/GuardError.md +106 -28
  43. package/docs/reference/classes/Guards.md +398 -80
  44. package/docs/reference/classes/HexHelper.md +18 -8
  45. package/docs/reference/classes/I18n.md +46 -20
  46. package/docs/reference/classes/Is.md +179 -51
  47. package/docs/reference/classes/JsonHelper.md +146 -10
  48. package/docs/reference/classes/NotFoundError.md +103 -27
  49. package/docs/reference/classes/NotImplementedError.md +97 -25
  50. package/docs/reference/classes/NotSupportedError.md +100 -26
  51. package/docs/reference/classes/ObjectHelper.md +197 -39
  52. package/docs/reference/classes/RandomHelper.md +6 -4
  53. package/docs/reference/classes/SharedStore.md +94 -0
  54. package/docs/reference/classes/StringHelper.md +54 -20
  55. package/docs/reference/classes/Uint8ArrayHelper.md +35 -0
  56. package/docs/reference/classes/UnauthorizedError.md +100 -26
  57. package/docs/reference/classes/UnprocessableError.md +101 -27
  58. package/docs/reference/classes/Url.md +37 -17
  59. package/docs/reference/classes/Urn.md +63 -27
  60. package/docs/reference/classes/Validation.md +349 -135
  61. package/docs/reference/classes/ValidationError.md +100 -26
  62. package/docs/reference/index.md +7 -0
  63. package/docs/reference/interfaces/IComponent.md +30 -8
  64. package/docs/reference/interfaces/IError.md +2 -2
  65. package/docs/reference/interfaces/II18nShared.md +47 -0
  66. package/docs/reference/interfaces/IKeyValue.md +3 -1
  67. package/docs/reference/interfaces/ILabelledValue.md +3 -1
  68. package/docs/reference/interfaces/ILocaleDictionary.md +1 -1
  69. package/docs/reference/interfaces/IPatchOperation.md +1 -1
  70. package/docs/reference/interfaces/IValidationFailure.md +1 -1
  71. package/docs/reference/type-aliases/CoerceType.md +5 -0
  72. package/docs/reference/type-aliases/CompressionType.md +1 -1
  73. package/docs/reference/type-aliases/ObjectOrArray.md +11 -0
  74. package/docs/reference/variables/CoerceType.md +67 -0
  75. package/docs/reference/variables/CompressionType.md +1 -1
  76. package/locales/en.json +14 -1
  77. package/package.json +7 -7
@@ -1,5 +1,5 @@
1
- import { IntlMessageFormat } from 'intl-messageformat';
2
1
  import { createPatch, applyPatch } from 'rfc6902';
2
+ import { IntlMessageFormat } from 'intl-messageformat';
3
3
 
4
4
  // Copyright 2024 IOTA Stiftung.
5
5
  // SPDX-License-Identifier: Apache-2.0.
@@ -111,7 +111,12 @@ class Is {
111
111
  }
112
112
  try {
113
113
  const json = JSON.parse(value);
114
- return typeof json === "object";
114
+ return (Is.object(json) ||
115
+ Is.array(json) ||
116
+ Is.string(json) ||
117
+ Is.number(json) ||
118
+ Is.boolean(json) ||
119
+ Is.null(json));
115
120
  }
116
121
  catch {
117
122
  return false;
@@ -135,7 +140,17 @@ class Is {
135
140
  static stringBase64Url(value) {
136
141
  return (Is.stringValue(value) &&
137
142
  // eslint-disable-next-line unicorn/better-regex
138
- /^(?:[A-Za-z0-9-_]{4})*(?:[A-Za-z0-9-_]{2}==|[A-Za-z0-9-_]{3}=)?$/.test(value));
143
+ /^([A-Za-z0-9-_])*$/.test(value));
144
+ }
145
+ /**
146
+ * Is the value a base58 string.
147
+ * @param value The value to test.
148
+ * @returns True if the value is a base58 string.
149
+ */
150
+ static stringBase58(value) {
151
+ return (Is.stringValue(value) &&
152
+ // eslint-disable-next-line unicorn/better-regex
153
+ /^[A-HJ-NP-Za-km-z1-9]*$/.test(value));
139
154
  }
140
155
  /**
141
156
  * Is the value a hex string.
@@ -349,6 +364,14 @@ class Is {
349
364
  static promise(value) {
350
365
  return value instanceof Promise;
351
366
  }
367
+ /**
368
+ * Is the value a regexp.
369
+ * @param value The value to test.
370
+ * @returns True if the value is a regexp.
371
+ */
372
+ static regexp(value) {
373
+ return value instanceof RegExp;
374
+ }
352
375
  }
353
376
 
354
377
  // Copyright 2024 IOTA Stiftung.
@@ -644,7 +667,10 @@ class BaseError extends Error {
644
667
  let properties;
645
668
  let inner;
646
669
  let stack;
647
- if (Is.object(err)) {
670
+ if (Is.object(err) && Is.stringValue(err.error)) {
671
+ message = err.error;
672
+ }
673
+ else if (Is.object(err)) {
648
674
  if (Is.stringValue(err.name)) {
649
675
  name = err.name;
650
676
  }
@@ -664,9 +690,6 @@ class BaseError extends Error {
664
690
  stack = err.stack;
665
691
  }
666
692
  }
667
- else if (Is.object(err) && Is.stringValue(err.error)) {
668
- message = err.error;
669
- }
670
693
  else if (Is.stringValue(err)) {
671
694
  message = err;
672
695
  }
@@ -684,7 +707,7 @@ class BaseError extends Error {
684
707
  */
685
708
  static flatten(err) {
686
709
  const flattened = [];
687
- let e = BaseError.fromError(err).toJsonObject();
710
+ let e = BaseError.fromError(err).toJsonObject(true);
688
711
  while (e) {
689
712
  const inner = e.inner;
690
713
  e.inner = undefined;
@@ -778,11 +801,23 @@ class BaseError extends Error {
778
801
  static someErrorCode(error, code) {
779
802
  return BaseError.flatten(error).some(e => BaseError.isErrorCode(e, code));
780
803
  }
804
+ /**
805
+ * Is the error empty.
806
+ * @param err The error to check for being empty.
807
+ * @returns True if the error is empty.
808
+ */
809
+ static isEmpty(err) {
810
+ return (!Is.stringValue(err.message) &&
811
+ !Is.stringValue(err.source) &&
812
+ !Is.objectValue(err.properties) &&
813
+ Is.empty(err.inner));
814
+ }
781
815
  /**
782
816
  * Serialize the error to the error model.
817
+ * @param includeStackTrace Whether to include the error stack in the model, defaults to false.
783
818
  * @returns The error model.
784
819
  */
785
- toJsonObject() {
820
+ toJsonObject(includeStackTrace) {
786
821
  const err = {};
787
822
  if (Is.stringValue(this.name)) {
788
823
  err.name = this.name;
@@ -796,11 +831,11 @@ class BaseError extends Error {
796
831
  if (Is.object(this.properties)) {
797
832
  err.properties = this.properties;
798
833
  }
799
- if (Is.stringValue(this.stack)) {
834
+ if ((includeStackTrace ?? false) && Is.stringValue(this.stack)) {
800
835
  err.stack = this.stack;
801
836
  }
802
837
  if (Is.notEmpty(this.inner)) {
803
- err.inner = BaseError.fromError(this.inner).toJsonObject();
838
+ err.inner = BaseError.fromError(this.inner).toJsonObject(includeStackTrace);
804
839
  }
805
840
  return err;
806
841
  }
@@ -1173,7 +1208,7 @@ class Base64 {
1173
1208
  const maxChunkLength = 16383; // must be multiple of 3
1174
1209
  // go through the array every three bytes, we'll deal with trailing stuff later
1175
1210
  for (let i = 0, len2 = len - extraBytes; i < len2; i += maxChunkLength) {
1176
- parts.push(Base64.encodeChunk(bytes, i, i + maxChunkLength > len2 ? len2 : i + maxChunkLength));
1211
+ parts.push(Base64.encodeChunk(bytes, i, Math.min(i + maxChunkLength, len2)));
1177
1212
  }
1178
1213
  // pad the end with zeros, but make sure to not forget the extra bytes
1179
1214
  if (extraBytes === 1) {
@@ -1469,6 +1504,49 @@ class ValidationError extends BaseError {
1469
1504
  }
1470
1505
  }
1471
1506
 
1507
+ /**
1508
+ * Class to help with arrays.
1509
+ */
1510
+ class ArrayHelper {
1511
+ /**
1512
+ * Do the two arrays match.
1513
+ * @param arr1 The first array.
1514
+ * @param arr2 The second array.
1515
+ * @returns True if both arrays are empty of have the same values.
1516
+ */
1517
+ static matches(arr1, arr2) {
1518
+ if (Is.empty(arr1) && Is.empty(arr2)) {
1519
+ return true;
1520
+ }
1521
+ if (!((Is.array(arr1) && Is.array(arr2)) || (Is.typedArray(arr1) && Is.typedArray(arr2)))) {
1522
+ return false;
1523
+ }
1524
+ if (arr1.length !== arr2.length) {
1525
+ return false;
1526
+ }
1527
+ for (let i = 0; i < arr1.length; i++) {
1528
+ if (arr1[i] !== arr2[i]) {
1529
+ return false;
1530
+ }
1531
+ }
1532
+ return true;
1533
+ }
1534
+ /**
1535
+ * Convert an object or array to an array.
1536
+ * @param value The object or array to convert.
1537
+ * @returns The array.
1538
+ */
1539
+ static fromObjectOrArray(value) {
1540
+ if (Is.empty(value)) {
1541
+ return undefined;
1542
+ }
1543
+ if (Is.array(value)) {
1544
+ return value;
1545
+ }
1546
+ return [value];
1547
+ }
1548
+ }
1549
+
1472
1550
  // Copyright 2024 IOTA Stiftung.
1473
1551
  // SPDX-License-Identifier: Apache-2.0.
1474
1552
  /**
@@ -1514,6 +1592,18 @@ class Guards {
1514
1592
  throw new GuardError(source, "guard.stringEmpty", property, value);
1515
1593
  }
1516
1594
  }
1595
+ /**
1596
+ * Is the property a JSON value.
1597
+ * @param source The source of the error.
1598
+ * @param property The name of the property.
1599
+ * @param value The value to test.
1600
+ * @throws GuardError If the value does not match the assertion.
1601
+ */
1602
+ static json(source, property, value) {
1603
+ if (!Is.json(value)) {
1604
+ throw new GuardError(source, "guard.stringJson", property, value);
1605
+ }
1606
+ }
1517
1607
  /**
1518
1608
  * Is the property a base64 string.
1519
1609
  * @param source The source of the error.
@@ -1538,6 +1628,18 @@ class Guards {
1538
1628
  throw new GuardError(source, "guard.base64Url", property, value);
1539
1629
  }
1540
1630
  }
1631
+ /**
1632
+ * Is the property a base58 string.
1633
+ * @param source The source of the error.
1634
+ * @param property The name of the property.
1635
+ * @param value The value to test.
1636
+ * @throws GuardError If the value does not match the assertion.
1637
+ */
1638
+ static stringBase58(source, property, value) {
1639
+ if (!Is.stringBase58(value)) {
1640
+ throw new GuardError(source, "guard.base58", property, value);
1641
+ }
1642
+ }
1541
1643
  /**
1542
1644
  * Is the property a string with a hex value.
1543
1645
  * @param source The source of the error.
@@ -1727,6 +1829,50 @@ class Guards {
1727
1829
  throw new GuardError(source, "guard.arrayOneOf", property, value, options.join(", "));
1728
1830
  }
1729
1831
  }
1832
+ /**
1833
+ * Does the array start with the specified data.
1834
+ * @param source The source of the error.
1835
+ * @param property The name of the property.
1836
+ * @param value The value to test.
1837
+ * @param startValues The values that must start the array.
1838
+ * @throws GuardError If the value does not match the assertion.
1839
+ */
1840
+ static arrayStartsWith(source, property, value, startValues) {
1841
+ if (!Is.arrayValue(value)) {
1842
+ throw new GuardError(source, "guard.array", property, value);
1843
+ }
1844
+ const startValuesArray = ArrayHelper.fromObjectOrArray(startValues);
1845
+ if (!Is.arrayValue(startValuesArray)) {
1846
+ throw new GuardError(source, "guard.array", property, startValuesArray);
1847
+ }
1848
+ for (let i = 0; i < startValuesArray.length; i++) {
1849
+ if (value[i] !== startValuesArray[i]) {
1850
+ throw new GuardError(source, "guard.arrayStartsWith", property, value, startValuesArray.join(", "));
1851
+ }
1852
+ }
1853
+ }
1854
+ /**
1855
+ * Does the array end with the specified data.
1856
+ * @param source The source of the error.
1857
+ * @param property The name of the property.
1858
+ * @param value The value to test.
1859
+ * @param endValues The values that must end the array.
1860
+ * @throws GuardError If the value does not match the assertion.
1861
+ */
1862
+ static arrayEndsWith(source, property, value, endValues) {
1863
+ if (!Is.arrayValue(value)) {
1864
+ throw new GuardError(source, "guard.array", property, value);
1865
+ }
1866
+ const endValuesArray = ArrayHelper.fromObjectOrArray(endValues);
1867
+ if (!Is.arrayValue(endValuesArray)) {
1868
+ throw new GuardError(source, "guard.array", property, endValuesArray);
1869
+ }
1870
+ for (let i = 0; i < endValuesArray.length; i++) {
1871
+ if (value[value.length - i - 1] !== endValuesArray[endValuesArray.length - i - 1]) {
1872
+ throw new GuardError(source, "guard.arrayEndsWith", property, value, endValuesArray.join(", "));
1873
+ }
1874
+ }
1875
+ }
1730
1876
  /**
1731
1877
  * Is the property a Uint8Array.
1732
1878
  * @param source The source of the error.
@@ -1767,6 +1913,53 @@ class Guards {
1767
1913
  }
1768
1914
  }
1769
1915
 
1916
+ // Copyright 2024 IOTA Stiftung.
1917
+ // SPDX-License-Identifier: Apache-2.0.
1918
+ /**
1919
+ * Provide a store for shared objects which can be accesses through multiple
1920
+ * instance loads of a packages.
1921
+ */
1922
+ class SharedStore {
1923
+ /**
1924
+ * Get a property from the shared store.
1925
+ * @param prop The name of the property to get.
1926
+ * @returns The property if it exists.
1927
+ */
1928
+ static get(prop) {
1929
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1930
+ const shared = globalThis.__TWIN_SHARED__;
1931
+ if (Is.undefined(shared)) {
1932
+ return;
1933
+ }
1934
+ return shared[prop];
1935
+ }
1936
+ /**
1937
+ * Set the property in the shared store.
1938
+ * @param prop The name of the property to set.
1939
+ * @param value The value to set.
1940
+ */
1941
+ static set(prop, value) {
1942
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1943
+ if (Is.undefined(globalThis.__TWIN_SHARED__)) {
1944
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1945
+ globalThis.__TWIN_SHARED__ = {};
1946
+ }
1947
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1948
+ globalThis.__TWIN_SHARED__[prop] = value;
1949
+ }
1950
+ /**
1951
+ * Remove a property from the shared store.
1952
+ * @param prop The name of the property to remove.
1953
+ */
1954
+ static remove(prop) {
1955
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1956
+ const shared = globalThis.__TWIN_SHARED__;
1957
+ if (!Is.undefined(shared)) {
1958
+ delete shared[prop];
1959
+ }
1960
+ }
1961
+ }
1962
+
1770
1963
  /**
1771
1964
  * Factory for creating implementation of generic types.
1772
1965
  */
@@ -1776,11 +1969,6 @@ class Factory {
1776
1969
  * @internal
1777
1970
  */
1778
1971
  static _CLASS_NAME = "Factory";
1779
- /**
1780
- * Store all the created factories.
1781
- * @internal
1782
- */
1783
- static _factories = {};
1784
1972
  /**
1785
1973
  * Type name for the instances.
1786
1974
  * @internal
@@ -1834,10 +2022,41 @@ class Factory {
1834
2022
  * @returns The factory instance.
1835
2023
  */
1836
2024
  static createFactory(typeName, autoInstance = false, matcher) {
1837
- if (Is.undefined(Factory._factories[typeName])) {
1838
- Factory._factories[typeName] = new Factory(typeName, autoInstance, matcher);
2025
+ const factories = Factory.getFactories();
2026
+ if (Is.undefined(factories[typeName])) {
2027
+ factories[typeName] = new Factory(typeName, autoInstance, matcher);
2028
+ }
2029
+ return factories[typeName];
2030
+ }
2031
+ /**
2032
+ * Get all the factories.
2033
+ * @returns All the factories.
2034
+ */
2035
+ static getFactories() {
2036
+ let factories = SharedStore.get("factories");
2037
+ if (Is.undefined(factories)) {
2038
+ factories = {};
2039
+ SharedStore.set("factories", factories);
2040
+ }
2041
+ return factories;
2042
+ }
2043
+ /**
2044
+ * Reset all the factories, which removes any created instances, but not the registrations.
2045
+ */
2046
+ static resetFactories() {
2047
+ const factories = Factory.getFactories();
2048
+ for (const typeName in factories) {
2049
+ factories[typeName].reset();
2050
+ }
2051
+ }
2052
+ /**
2053
+ * Clear all the factories, which removes anything registered with the factories.
2054
+ */
2055
+ static clearFactories() {
2056
+ const factories = Factory.getFactories();
2057
+ for (const typeName in factories) {
2058
+ factories[typeName].clear();
1839
2059
  }
1840
- return Factory._factories[typeName];
1841
2060
  }
1842
2061
  /**
1843
2062
  * Register a new generator.
@@ -1910,7 +2129,7 @@ class Factory {
1910
2129
  }
1911
2130
  }
1912
2131
  /**
1913
- * Reset all the instances.
2132
+ * Remove all the instances and leave the generators intact.
1914
2133
  */
1915
2134
  reset() {
1916
2135
  for (const name in this._generators) {
@@ -1918,6 +2137,14 @@ class Factory {
1918
2137
  }
1919
2138
  this._instances = {};
1920
2139
  }
2140
+ /**
2141
+ * Remove all the instances and the generators.
2142
+ */
2143
+ clear() {
2144
+ this._instances = {};
2145
+ this._generators = {};
2146
+ this._orderCounter = 0;
2147
+ }
1921
2148
  /**
1922
2149
  * Get all the instances as a map.
1923
2150
  * @returns The instances as a map.
@@ -1992,518 +2219,265 @@ const ComponentFactory = Factory.createFactory("component");
1992
2219
 
1993
2220
  // Copyright 2024 IOTA Stiftung.
1994
2221
  // SPDX-License-Identifier: Apache-2.0.
2222
+ /* eslint-disable no-bitwise */
1995
2223
  /**
1996
- * Class to help with arrays.
1997
- */
1998
- class ArrayHelper {
1999
- /**
2000
- * Do the two arrays match.
2001
- * @param arr1 The first array.
2002
- * @param arr2 The second array.
2003
- * @returns True if both arrays are empty of have the same values.
2004
- */
2005
- static matches(arr1, arr2) {
2006
- if (Is.empty(arr1) && Is.empty(arr2)) {
2007
- return true;
2008
- }
2009
- if (!((Is.array(arr1) && Is.array(arr2)) || (Is.typedArray(arr1) && Is.typedArray(arr2)))) {
2010
- return false;
2011
- }
2012
- if (arr1.length !== arr2.length) {
2013
- return false;
2014
- }
2015
- for (let i = 0; i < arr1.length; i++) {
2016
- if (arr1[i] !== arr2[i]) {
2017
- return false;
2018
- }
2019
- }
2020
- return true;
2021
- }
2022
- }
2023
-
2024
- // Copyright 2024 IOTA Stiftung.
2025
- // SPDX-License-Identifier: Apache-2.0.
2026
- /**
2027
- * Class to perform internationalization.
2224
+ * Convert arrays to and from different formats.
2028
2225
  */
2029
- class I18n {
2030
- /**
2031
- * The default translation.
2032
- */
2033
- static DEFAULT_LOCALE = "en";
2034
- /**
2035
- * Dictionaries for lookups.
2036
- * @internal
2037
- */
2038
- static _localeDictionaries = {};
2039
- /**
2040
- * The current locale.
2041
- * @internal
2042
- */
2043
- static _currentLocale = I18n.DEFAULT_LOCALE;
2226
+ class Converter {
2044
2227
  /**
2045
- * Change handler for the locale being updated.
2228
+ * Lookup table for encoding.
2046
2229
  * @internal
2047
2230
  */
2048
- static _localeChangedHandlers = {};
2231
+ static _ENCODE_LOOKUP;
2049
2232
  /**
2050
- * Change handler for the dictionaries being updated.
2233
+ * Lookup table for decoding.
2051
2234
  * @internal
2052
2235
  */
2053
- static _dictionaryChangedHandlers = {};
2236
+ static _DECODE_LOOKUP;
2054
2237
  /**
2055
- * Set the locale.
2056
- * @param locale The new locale.
2238
+ * Encode a raw array to UTF8 string.
2239
+ * @param array The bytes to encode.
2240
+ * @param startIndex The index to start in the bytes.
2241
+ * @param length The length of bytes to read.
2242
+ * @returns The array formatted as UTF8.
2057
2243
  */
2058
- static setLocale(locale) {
2059
- I18n._currentLocale = locale;
2060
- for (const callback in I18n._localeChangedHandlers) {
2061
- I18n._localeChangedHandlers[callback](I18n._currentLocale);
2244
+ static bytesToUtf8(array, startIndex, length) {
2245
+ const start = startIndex ?? 0;
2246
+ const len = length ?? array.length;
2247
+ let str = "";
2248
+ for (let i = start; i < start + len; i++) {
2249
+ const value = array[i];
2250
+ if (value < 0x80) {
2251
+ str += String.fromCharCode(value);
2252
+ }
2253
+ else if (value > 0xbf && value < 0xe0) {
2254
+ str += String.fromCharCode(((value & 0x1f) << 6) | (array[i + 1] & 0x3f));
2255
+ i += 1;
2256
+ }
2257
+ else if (value > 0xdf && value < 0xf0) {
2258
+ str += String.fromCharCode(((value & 0x0f) << 12) | ((array[i + 1] & 0x3f) << 6) | (array[i + 2] & 0x3f));
2259
+ i += 2;
2260
+ }
2261
+ else {
2262
+ // surrogate pair
2263
+ const charCode = (((value & 0x07) << 18) |
2264
+ ((array[i + 1] & 0x3f) << 12) |
2265
+ ((array[i + 2] & 0x3f) << 6) |
2266
+ (array[i + 3] & 0x3f)) -
2267
+ 0x010000;
2268
+ str += String.fromCharCode((charCode >> 10) | 0xd800, (charCode & 0x03ff) | 0xdc00);
2269
+ i += 3;
2270
+ }
2062
2271
  }
2272
+ return str;
2063
2273
  }
2064
2274
  /**
2065
- * Get the locale.
2066
- * @returns The current locale.
2275
+ * Convert a UTF8 string to raw array.
2276
+ * @param utf8 The text to decode.
2277
+ * @returns The array.
2067
2278
  */
2068
- static getLocale() {
2069
- return I18n._currentLocale;
2279
+ static utf8ToBytes(utf8) {
2280
+ const bytes = [];
2281
+ for (let i = 0; i < utf8.length; i++) {
2282
+ let charCode = utf8.charCodeAt(i);
2283
+ if (charCode < 0x80) {
2284
+ bytes.push(charCode);
2285
+ }
2286
+ else if (charCode < 0x800) {
2287
+ bytes.push(0xc0 | (charCode >> 6), 0x80 | (charCode & 0x3f));
2288
+ }
2289
+ else if (charCode < 0xd800 || charCode >= 0xe000) {
2290
+ bytes.push(0xe0 | (charCode >> 12), 0x80 | ((charCode >> 6) & 0x3f), 0x80 | (charCode & 0x3f));
2291
+ }
2292
+ else {
2293
+ // surrogate pair
2294
+ i++;
2295
+ // UTF-16 encodes 0x10000-0x10FFFF by
2296
+ // subtracting 0x10000 and splitting the
2297
+ // 20 bits of 0x0-0xFFFFF into two halves
2298
+ charCode = 0x10000 + (((charCode & 0x3ff) << 10) | (utf8.charCodeAt(i) & 0x3ff));
2299
+ bytes.push(0xf0 | (charCode >> 18), 0x80 | ((charCode >> 12) & 0x3f), 0x80 | ((charCode >> 6) & 0x3f), 0x80 | (charCode & 0x3f));
2300
+ }
2301
+ }
2302
+ return Uint8Array.from(bytes);
2070
2303
  }
2071
2304
  /**
2072
- * Add a locale dictionary.
2073
- * @param locale The locale.
2074
- * @param dictionary The dictionary to add.
2305
+ * Encode a raw array to hex string.
2306
+ * @param array The bytes to encode.
2307
+ * @param includePrefix Include the 0x prefix on the returned hex.
2308
+ * @param startIndex The index to start in the bytes.
2309
+ * @param length The length of bytes to read.
2310
+ * @param reverse Reverse the combine direction.
2311
+ * @returns The array formatted as hex.
2075
2312
  */
2076
- static addDictionary(locale, dictionary) {
2077
- const mergedKeys = {};
2078
- I18n.flattenTranslationKeys(dictionary, "", mergedKeys);
2079
- I18n._localeDictionaries[locale] = mergedKeys;
2080
- for (const callback in I18n._dictionaryChangedHandlers) {
2081
- I18n._dictionaryChangedHandlers[callback](I18n._currentLocale);
2313
+ static bytesToHex(array, includePrefix = false, startIndex, length, reverse) {
2314
+ let hex = "";
2315
+ this.buildHexLookups();
2316
+ if (Converter._ENCODE_LOOKUP) {
2317
+ const len = length ?? array.length;
2318
+ const start = startIndex ?? 0;
2319
+ if (reverse) {
2320
+ for (let i = 0; i < len; i++) {
2321
+ hex = Converter._ENCODE_LOOKUP[array[start + i]] + hex;
2322
+ }
2323
+ }
2324
+ else {
2325
+ for (let i = 0; i < len; i++) {
2326
+ hex += Converter._ENCODE_LOOKUP[array[start + i]];
2327
+ }
2328
+ }
2082
2329
  }
2330
+ return includePrefix ? HexHelper.addPrefix(hex) : hex;
2083
2331
  }
2084
2332
  /**
2085
- * Get a locale dictionary.
2086
- * @param locale The locale.
2087
- * @returns The dictionary of undefined if it does not exist.
2333
+ * Decode a hex string to raw array.
2334
+ * @param hex The hex to decode.
2335
+ * @param reverse Store the characters in reverse.
2336
+ * @returns The array.
2088
2337
  */
2089
- static getDictionary(locale) {
2090
- return I18n._localeDictionaries[locale];
2338
+ static hexToBytes(hex, reverse) {
2339
+ const strippedHex = HexHelper.stripPrefix(hex);
2340
+ const sizeof = strippedHex.length >> 1;
2341
+ const length = sizeof << 1;
2342
+ const array = new Uint8Array(sizeof);
2343
+ this.buildHexLookups();
2344
+ if (Converter._DECODE_LOOKUP) {
2345
+ let i = 0;
2346
+ let n = 0;
2347
+ while (i < length) {
2348
+ array[n++] =
2349
+ (Converter._DECODE_LOOKUP[strippedHex.charCodeAt(i++)] << 4) |
2350
+ Converter._DECODE_LOOKUP[strippedHex.charCodeAt(i++)];
2351
+ }
2352
+ if (reverse) {
2353
+ array.reverse();
2354
+ }
2355
+ }
2356
+ return array;
2091
2357
  }
2092
2358
  /**
2093
- * Get all the locale dictionaries.
2094
- * @returns The dictionaries.
2359
+ * Convert the UTF8 to hex.
2360
+ * @param utf8 The text to convert.
2361
+ * @param includePrefix Include the 0x prefix on the returned hex.
2362
+ * @returns The hex version of the bytes.
2095
2363
  */
2096
- static getAllDictionaries() {
2097
- return I18n._localeDictionaries;
2364
+ static utf8ToHex(utf8, includePrefix = false) {
2365
+ const hex = Converter.bytesToHex(Converter.utf8ToBytes(utf8));
2366
+ return includePrefix ? HexHelper.addPrefix(hex) : hex;
2098
2367
  }
2099
2368
  /**
2100
- * Add a locale changed handler.
2101
- * @param id The id of the handler.
2102
- * @param handler The handler to add.
2369
+ * Convert the hex text to text.
2370
+ * @param hex The hex to convert.
2371
+ * @returns The UTF8 version of the bytes.
2103
2372
  */
2104
- static addLocaleHandler(id, handler) {
2105
- I18n._localeChangedHandlers[id] = handler;
2373
+ static hexToUtf8(hex) {
2374
+ return Converter.bytesToUtf8(Converter.hexToBytes(HexHelper.stripPrefix(hex)));
2106
2375
  }
2107
2376
  /**
2108
- * Remove a locale changed handler.
2109
- * @param id The id of the handler.
2377
+ * Convert bytes to binary string.
2378
+ * @param bytes The bytes to convert.
2379
+ * @returns A binary string of the bytes.
2110
2380
  */
2111
- static removeLocaleHandler(id) {
2112
- delete I18n._localeChangedHandlers[id];
2381
+ static bytesToBinary(bytes) {
2382
+ const b = [];
2383
+ for (let i = 0; i < bytes.length; i++) {
2384
+ b.push(bytes[i].toString(2).padStart(8, "0"));
2385
+ }
2386
+ return b.join("");
2113
2387
  }
2114
2388
  /**
2115
- * Add a dictionary changed handler.
2116
- * @param id The id of the handler.
2117
- * @param handler The handler to add.
2389
+ * Convert a binary string to bytes.
2390
+ * @param binary The binary string.
2391
+ * @returns The bytes.
2118
2392
  */
2119
- static addDictionaryHandler(id, handler) {
2120
- I18n._dictionaryChangedHandlers[id] = handler;
2393
+ static binaryToBytes(binary) {
2394
+ const bytes = new Uint8Array(Math.ceil(binary.length / 8));
2395
+ for (let i = 0; i < bytes.length; i++) {
2396
+ bytes[i] = Number.parseInt(binary.slice(i * 8, (i + 1) * 8), 2);
2397
+ }
2398
+ return bytes;
2121
2399
  }
2122
2400
  /**
2123
- * Remove a dictionary changed handler.
2124
- * @param id The id of the handler.
2401
+ * Convert bytes to base64 string.
2402
+ * @param bytes The bytes to convert.
2403
+ * @returns A base64 string of the bytes.
2125
2404
  */
2126
- static removeDictionaryHandler(id) {
2127
- delete I18n._dictionaryChangedHandlers[id];
2405
+ static bytesToBase64(bytes) {
2406
+ return Base64.encode(bytes);
2128
2407
  }
2129
2408
  /**
2130
- * Format a message.
2131
- * @param key The key of the message to format.
2132
- * @param values The values to substitute into the message.
2133
- * @param overrideLocale Override the locale.
2134
- * @returns The formatted string.
2409
+ * Convert a base64 string to bytes.
2410
+ * @param base64 The base64 string.
2411
+ * @returns The bytes.
2135
2412
  */
2136
- static formatMessage(key, values, overrideLocale) {
2137
- let cl = overrideLocale ?? I18n._currentLocale;
2138
- if (cl.startsWith("debug-")) {
2139
- cl = I18n.DEFAULT_LOCALE;
2140
- }
2141
- if (!I18n._localeDictionaries[cl]) {
2142
- return `!!Missing ${cl}`;
2143
- }
2144
- if (!I18n._localeDictionaries[cl][key]) {
2145
- return `!!Missing ${cl}.${key}`;
2146
- }
2147
- if (I18n._currentLocale === "debug-k") {
2148
- return key;
2149
- }
2150
- let ret = new IntlMessageFormat(I18n._localeDictionaries[cl][key], cl).format(values);
2151
- if (I18n._currentLocale === "debug-x") {
2152
- ret = ret.replace(/[a-z]/g, "x").replace(/[A-Z]/g, "x").replace(/\d/g, "n");
2153
- }
2154
- return ret;
2413
+ static base64ToBytes(base64) {
2414
+ return Base64.decode(base64);
2155
2415
  }
2156
2416
  /**
2157
- * Check if the dictionaries have a message for the given key.
2158
- * @param key The key to check for existence.
2159
- * @returns True if the key exists.
2417
+ * Convert bytes to base64 url string.
2418
+ * @param bytes The bytes to convert.
2419
+ * @returns A base64 url string of the bytes.
2160
2420
  */
2161
- static hasMessage(key) {
2162
- return Is.string(I18n._localeDictionaries[I18n._currentLocale]?.[key]);
2421
+ static bytesToBase64Url(bytes) {
2422
+ return Base64Url.encode(bytes);
2163
2423
  }
2164
2424
  /**
2165
- * Flatten the translation property paths for faster lookup.
2166
- * @param translation The translation to merge.
2167
- * @param propertyPath The current root path.
2168
- * @param mergedKeys The merged keys dictionary to populate.
2169
- * @internal
2425
+ * Convert a base64 url string to bytes.
2426
+ * @param base64Url The base64 url string.
2427
+ * @returns The bytes.
2170
2428
  */
2171
- static flattenTranslationKeys(translation, propertyPath, mergedKeys) {
2172
- for (const key in translation) {
2173
- const val = translation[key];
2174
- const mergedPath = propertyPath.length > 0 ? `${propertyPath}.${key}` : key;
2175
- if (Is.string(val)) {
2176
- mergedKeys[mergedPath] = val;
2177
- }
2178
- else if (Is.object(val)) {
2179
- I18n.flattenTranslationKeys(val, mergedPath, mergedKeys);
2180
- }
2181
- }
2429
+ static base64UrlToBytes(base64Url) {
2430
+ return Base64Url.decode(base64Url);
2182
2431
  }
2183
- }
2184
-
2185
- // Copyright 2024 IOTA Stiftung.
2186
- // SPDX-License-Identifier: Apache-2.0.
2187
- /**
2188
- * Error helper functions.
2189
- */
2190
- class ErrorHelper {
2191
2432
  /**
2192
- * Format Errors and returns just their messages.
2193
- * @param error The error to format.
2194
- * @returns The error formatted including any inner errors.
2433
+ * Convert bytes to base58 string.
2434
+ * @param bytes The bytes to convert.
2435
+ * @returns A base58 string of the bytes.
2195
2436
  */
2196
- static formatErrors(error) {
2197
- return ErrorHelper.localizeErrors(error).map(e => e.message);
2437
+ static bytesToBase58(bytes) {
2438
+ return Base58.encode(bytes);
2198
2439
  }
2199
2440
  /**
2200
- * Localize the content of an error and any inner errors.
2201
- * @param error The error to format.
2202
- * @returns The localized version of the errors flattened.
2441
+ * Convert a base58 string to bytes.
2442
+ * @param base58 The base58 string.
2443
+ * @returns The bytes.
2203
2444
  */
2204
- static localizeErrors(error) {
2205
- const formattedErrors = [];
2206
- if (Is.notEmpty(error)) {
2207
- const errors = BaseError.flatten(error);
2208
- for (const err of errors) {
2209
- const errorNameKey = `errorNames.${StringHelper.camelCase(err.name)}`;
2210
- const errorMessageKey = `error.${err.message}`;
2211
- // If there is no error message then it is probably
2212
- // from a 3rd party lib, so don't format it just display
2213
- const hasErrorName = I18n.hasMessage(errorNameKey);
2214
- const hasErrorMessage = I18n.hasMessage(errorMessageKey);
2215
- const localizedError = {
2216
- name: I18n.formatMessage(hasErrorName ? errorNameKey : "errorNames.error"),
2217
- message: hasErrorMessage
2218
- ? I18n.formatMessage(errorMessageKey, err.properties)
2219
- : err.message
2220
- };
2221
- if (Is.stringValue(err.source)) {
2222
- localizedError.source = err.source;
2223
- }
2224
- if (Is.stringValue(err.stack)) {
2225
- // Remove the first line from the stack traces as they
2226
- // just have the error type and message duplicated
2227
- const lines = err.stack.split("\n");
2228
- lines.shift();
2229
- localizedError.stack = lines.join("\n");
2230
- }
2231
- const additional = ErrorHelper.formatValidationErrors(err);
2232
- if (Is.stringValue(additional)) {
2233
- localizedError.additional = additional;
2234
- }
2235
- formattedErrors.push(localizedError);
2236
- }
2237
- }
2238
- return formattedErrors;
2445
+ static base58ToBytes(base58) {
2446
+ return Base58.decode(base58);
2239
2447
  }
2240
2448
  /**
2241
- * Localize the content of an error and any inner errors.
2242
- * @param error The error to format.
2243
- * @returns The localized version of the errors flattened.
2449
+ * Build the static lookup tables.
2450
+ * @internal
2244
2451
  */
2245
- static formatValidationErrors(error) {
2246
- if (Is.object(error.properties) &&
2247
- Object.keys(error.properties).length > 0 &&
2248
- Is.object(error.properties) &&
2249
- Is.arrayValue(error.properties.validationFailures)) {
2250
- const validationErrors = [];
2251
- for (const validationFailure of error.properties.validationFailures) {
2252
- const errorI18n = `error.${validationFailure.reason}`;
2253
- const errorMessage = I18n.hasMessage(errorI18n)
2254
- ? I18n.formatMessage(errorI18n, validationFailure.properties)
2255
- : errorI18n;
2256
- let v = `${validationFailure.property}: ${errorMessage}`;
2257
- if (Is.object(validationFailure.properties) &&
2258
- Is.notEmpty(validationFailure.properties.value)) {
2259
- v += ` = ${JSON.stringify(validationFailure.properties.value)}`;
2452
+ static buildHexLookups() {
2453
+ if (!Converter._ENCODE_LOOKUP || !Converter._DECODE_LOOKUP) {
2454
+ const alphabet = "0123456789abcdef";
2455
+ Converter._ENCODE_LOOKUP = [];
2456
+ Converter._DECODE_LOOKUP = [];
2457
+ for (let i = 0; i < 256; i++) {
2458
+ Converter._ENCODE_LOOKUP[i] = alphabet[(i >> 4) & 0xf] + alphabet[i & 0xf];
2459
+ if (i < 16) {
2460
+ if (i < 10) {
2461
+ Converter._DECODE_LOOKUP[0x30 + i] = i;
2462
+ }
2463
+ else {
2464
+ Converter._DECODE_LOOKUP[0x61 - 10 + i] = i;
2465
+ }
2260
2466
  }
2261
- validationErrors.push(v);
2262
2467
  }
2263
- return validationErrors.join("\n");
2264
2468
  }
2265
2469
  }
2266
2470
  }
2267
2471
 
2268
- // Copyright 2024 IOTA Stiftung.
2269
- // SPDX-License-Identifier: Apache-2.0.
2270
- /**
2271
- * Coerce an object from one type to another.
2272
- */
2273
- class Coerce {
2274
- /**
2275
- * Coerce the value to a string.
2276
- * @param value The value to coerce.
2277
- * @throws TypeError If the value can not be coerced.
2278
- * @returns The value if it can be coerced.
2279
- */
2280
- static string(value) {
2281
- if (Is.undefined(value)) {
2282
- return value;
2283
- }
2284
- if (Is.string(value)) {
2285
- return value;
2286
- }
2287
- if (Is.number(value)) {
2288
- return value.toString();
2289
- }
2290
- if (Is.boolean(value)) {
2291
- return value ? "true" : "false";
2292
- }
2293
- if (Is.date(value)) {
2294
- return value.toISOString();
2295
- }
2296
- }
2297
- /**
2298
- * Coerce the value to a number.
2299
- * @param value The value to coerce.
2300
- * @throws TypeError If the value can not be coerced.
2301
- * @returns The value if it can be coerced.
2302
- */
2303
- static number(value) {
2304
- if (Is.undefined(value)) {
2305
- return value;
2306
- }
2307
- if (Is.number(value)) {
2308
- return value;
2309
- }
2310
- if (Is.string(value)) {
2311
- const parsed = Number.parseFloat(value);
2312
- if (Is.number(parsed)) {
2313
- return parsed;
2314
- }
2315
- }
2316
- if (Is.boolean(value)) {
2317
- return value ? 1 : 0;
2318
- }
2319
- if (Is.date(value)) {
2320
- return value.getTime();
2321
- }
2322
- }
2323
- /**
2324
- * Coerce the value to a bigint.
2325
- * @param value The value to coerce.
2326
- * @throws TypeError If the value can not be coerced.
2327
- * @returns The value if it can be coerced.
2328
- */
2329
- static bigint(value) {
2330
- if (Is.undefined(value)) {
2331
- return value;
2332
- }
2333
- if (Is.bigint(value)) {
2334
- return value;
2335
- }
2336
- if (Is.number(value)) {
2337
- return BigInt(value);
2338
- }
2339
- if (Is.string(value)) {
2340
- const parsed = Number.parseFloat(value);
2341
- if (Is.integer(parsed)) {
2342
- return BigInt(parsed);
2343
- }
2344
- }
2345
- if (Is.boolean(value)) {
2346
- return value ? 1n : 0n;
2347
- }
2348
- }
2349
- /**
2350
- * Coerce the value to a boolean.
2351
- * @param value The value to coerce.
2352
- * @throws TypeError If the value can not be coerced.
2353
- * @returns The value if it can be coerced.
2354
- */
2355
- static boolean(value) {
2356
- if (Is.undefined(value)) {
2357
- return value;
2358
- }
2359
- if (Is.boolean(value)) {
2360
- return value;
2361
- }
2362
- if (Is.number(value)) {
2363
- // eslint-disable-next-line no-unneeded-ternary
2364
- return value ? true : false;
2365
- }
2366
- if (Is.string(value)) {
2367
- if (/true/i.test(value)) {
2368
- return true;
2369
- }
2370
- if (/false/i.test(value)) {
2371
- return false;
2372
- }
2373
- }
2374
- }
2375
- /**
2376
- * Coerce the value to a date.
2377
- * @param value The value to coerce.
2378
- * @throws TypeError If the value can not be coerced.
2379
- * @returns The value if it can be coerced.
2380
- */
2381
- static date(value) {
2382
- if (Is.undefined(value)) {
2383
- return value;
2384
- }
2385
- if (Is.date(value)) {
2386
- return value;
2387
- }
2388
- if (Is.number(value)) {
2389
- return new Date(value);
2390
- }
2391
- if (Is.string(value)) {
2392
- const dt = new Date(value);
2393
- if (!Number.isNaN(dt.getTime())) {
2394
- const utc = Date.UTC(dt.getUTCFullYear(), dt.getUTCMonth(), dt.getUTCDate());
2395
- return new Date(utc);
2396
- }
2397
- }
2398
- }
2399
- /**
2400
- * Coerce the value to a date/time.
2401
- * @param value The value to coerce.
2402
- * @throws TypeError If the value can not be coerced.
2403
- * @returns The value if it can be coerced.
2404
- */
2405
- static dateTime(value) {
2406
- if (Is.undefined(value)) {
2407
- return value;
2408
- }
2409
- if (Is.date(value)) {
2410
- return value;
2411
- }
2412
- if (Is.number(value)) {
2413
- return new Date(value);
2414
- }
2415
- if (Is.string(value)) {
2416
- const dt = new Date(value);
2417
- if (!Number.isNaN(dt.getTime())) {
2418
- const utc = Date.UTC(dt.getUTCFullYear(), dt.getUTCMonth(), dt.getUTCDate(), dt.getUTCHours(), dt.getUTCMinutes(), dt.getUTCSeconds(), dt.getUTCMilliseconds());
2419
- return new Date(utc);
2420
- }
2421
- }
2422
- }
2423
- /**
2424
- * Coerce the value to a time.
2425
- * @param value The value to coerce.
2426
- * @throws TypeError If the value can not be coerced.
2427
- * @returns The value if it can be coerced.
2428
- */
2429
- static time(value) {
2430
- if (Is.undefined(value)) {
2431
- return value;
2432
- }
2433
- if (Is.date(value)) {
2434
- return value;
2435
- }
2436
- if (Is.number(value)) {
2437
- const dt = new Date(value);
2438
- dt.setFullYear(1970, 0, 1);
2439
- return dt;
2440
- }
2441
- if (Is.string(value)) {
2442
- const dt = new Date(value);
2443
- if (!Number.isNaN(dt.getTime())) {
2444
- const utc = Date.UTC(1970, 0, 1, dt.getUTCHours(), dt.getUTCMinutes(), dt.getUTCSeconds(), dt.getUTCMilliseconds());
2445
- return new Date(utc);
2446
- }
2447
- }
2448
- }
2449
- /**
2450
- * Coerce the value to an object.
2451
- * @param value The value to coerce.
2452
- * @throws TypeError If the value can not be coerced.
2453
- * @returns The value if it can be coerced.
2454
- */
2455
- static object(value) {
2456
- if (Is.undefined(value)) {
2457
- return value;
2458
- }
2459
- if (Is.object(value)) {
2460
- return value;
2461
- }
2462
- if (Is.stringValue(value)) {
2463
- try {
2464
- return JSON.parse(value);
2465
- }
2466
- catch { }
2467
- }
2468
- }
2469
- }
2470
-
2471
- // Copyright 2024 IOTA Stiftung.
2472
- // SPDX-License-Identifier: Apache-2.0.
2473
- /**
2474
- * Class to help with filenames.
2475
- */
2476
- class FilenameHelper {
2477
- /**
2478
- * Replaces any unsafe characters in the filename.
2479
- * @param filename The filename to make safe.
2480
- * @returns The safe filename.
2481
- */
2482
- static safeFilename(filename) {
2483
- let safe = Coerce.string(filename);
2484
- if (Is.empty(safe)) {
2485
- return "";
2486
- }
2487
- // Common non filename characters
2488
- safe = safe.replace(/["*/:<>?\\|]/g, "_");
2489
- // Windows non filename characters
2490
- safe = safe.replace(/^(con|prn|aux|nul|com\d|lpt\d)$/i, "_");
2491
- // Control characters
2492
- safe = safe.replace(/[\u0000-\u001F\u0080-\u009F]/g, "_");
2493
- // Relative paths
2494
- safe = safe.replace(/^\.+/, "_");
2495
- // Trailing periods
2496
- safe = safe.replace(/\.+$/, "");
2497
- return safe;
2498
- }
2499
- }
2500
-
2501
- // Copyright 2024 IOTA Stiftung.
2502
- // SPDX-License-Identifier: Apache-2.0.
2503
2472
  /**
2504
2473
  * Helpers methods for JSON objects.
2505
2474
  */
2506
2475
  class JsonHelper {
2476
+ /**
2477
+ * Runtime name for the class.
2478
+ * @internal
2479
+ */
2480
+ static _CLASS_NAME = "JsonHelper";
2507
2481
  /**
2508
2482
  * Serializes in canonical format.
2509
2483
  * Based on https://www.rfc-editor.org/rfc/rfc8785.
@@ -2562,449 +2536,1043 @@ class JsonHelper {
2562
2536
  * @param object The object to patch.
2563
2537
  * @param patches The second object.
2564
2538
  * @returns The updated object.
2539
+ * @throws GeneralError if the patch fails.
2565
2540
  */
2566
2541
  static patch(object, patches) {
2567
- return applyPatch(object, patches);
2542
+ const clone = ObjectHelper.clone(object);
2543
+ const result = applyPatch(clone, patches);
2544
+ for (let i = 0; i < result.length; i++) {
2545
+ if (!Is.empty(result[i])) {
2546
+ throw new GeneralError(JsonHelper._CLASS_NAME, "failedPatch", { index: i }, result[i]);
2547
+ }
2548
+ }
2549
+ return clone;
2568
2550
  }
2569
- }
2570
-
2571
- // Copyright 2024 IOTA Stiftung.
2572
- // SPDX-License-Identifier: Apache-2.0.
2573
- /* eslint-disable no-bitwise */
2574
- /**
2575
- * Convert arrays to and from different formats.
2576
- */
2577
- class Converter {
2578
2551
  /**
2579
- * Lookup table for encoding.
2580
- * @internal
2552
+ * Stringify the JSON with support for extended data types date/bigint/uint8array.
2553
+ * @param object The object to stringify.
2554
+ * @param space Adds indentation, white space, and line break characters to the return-value JSON text to make it easier to read.
2555
+ * @returns The stringified object.
2581
2556
  */
2582
- static _ENCODE_LOOKUP;
2557
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2558
+ static stringifyEx(object, space) {
2559
+ // We want to keep the 'this' intact for the replacer
2560
+ // eslint-disable-next-line @typescript-eslint/unbound-method
2561
+ return JSON.stringify(object, JsonHelper.stringifyExReplacer, space);
2562
+ }
2583
2563
  /**
2584
- * Lookup table for decoding.
2585
- * @internal
2564
+ * Parse the JSON string with support for extended data types date/bigint/uint8array.
2565
+ * @param json The object to pause.
2566
+ * @returns The object.
2586
2567
  */
2587
- static _DECODE_LOOKUP;
2568
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2569
+ static parseEx(json) {
2570
+ // We want to keep the 'this' intact for the reviver
2571
+ // eslint-disable-next-line @typescript-eslint/unbound-method
2572
+ return JSON.parse(json, JsonHelper.parseExReviver);
2573
+ }
2574
+ /**
2575
+ * Replacer function to handle extended data types.
2576
+ * @param this The object.
2577
+ * @param key The key.
2578
+ * @param value The value.
2579
+ * @returns The value.
2580
+ */
2581
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2582
+ static stringifyExReplacer(key, value) {
2583
+ const rawValue = this[key];
2584
+ if (Is.bigint(rawValue)) {
2585
+ return {
2586
+ "@ext": "bigint",
2587
+ value: rawValue.toString()
2588
+ };
2589
+ }
2590
+ else if (Is.date(rawValue)) {
2591
+ return {
2592
+ "@ext": "date",
2593
+ value: rawValue.getTime()
2594
+ };
2595
+ }
2596
+ else if (Is.uint8Array(rawValue)) {
2597
+ return {
2598
+ "@ext": "uint8array",
2599
+ value: Converter.bytesToBase64(rawValue)
2600
+ };
2601
+ }
2602
+ return value;
2603
+ }
2604
+ /**
2605
+ * Reviver function to handle extended data types.
2606
+ * @param this The object.
2607
+ * @param key The key.
2608
+ * @param value The value.
2609
+ * @returns The value.
2610
+ */
2611
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2612
+ static parseExReviver(key, value) {
2613
+ if (Is.object(value)) {
2614
+ if (value["@ext"] === "bigint") {
2615
+ return BigInt(value.value);
2616
+ }
2617
+ else if (value["@ext"] === "date") {
2618
+ return new Date(value.value);
2619
+ }
2620
+ else if (value["@ext"] === "uint8array") {
2621
+ return Converter.base64ToBytes(value.value);
2622
+ }
2623
+ }
2624
+ return value;
2625
+ }
2626
+ }
2627
+
2628
+ /**
2629
+ * Class to help with objects.
2630
+ */
2631
+ class ObjectHelper {
2588
2632
  /**
2589
- * Encode a raw array to UTF8 string.
2590
- * @param array The bytes to encode.
2591
- * @param startIndex The index to start in the bytes.
2592
- * @param length The length of bytes to read.
2593
- * @returns The array formatted as UTF8.
2633
+ * Runtime name for the class.
2634
+ * @internal
2594
2635
  */
2595
- static bytesToUtf8(array, startIndex, length) {
2596
- const start = startIndex ?? 0;
2597
- const len = length ?? array.length;
2598
- let str = "";
2599
- for (let i = start; i < start + len; i++) {
2600
- const value = array[i];
2601
- if (value < 0x80) {
2602
- str += String.fromCharCode(value);
2636
+ static _CLASS_NAME = "ObjectHelper";
2637
+ /**
2638
+ * Convert an object to bytes.
2639
+ * @param obj The object to convert.
2640
+ * @param format Format the JSON content.
2641
+ * @returns The object as bytes.
2642
+ */
2643
+ static toBytes(obj, format = false) {
2644
+ if (obj === undefined) {
2645
+ return new Uint8Array();
2646
+ }
2647
+ const json = format ? JSON.stringify(obj, undefined, "\t") : JSON.stringify(obj);
2648
+ return Converter.utf8ToBytes(json);
2649
+ }
2650
+ /**
2651
+ * Convert a bytes to an object.
2652
+ * @param bytes The bytes to convert to an object.
2653
+ * @returns The object.
2654
+ * @throws GeneralError if there was an error parsing the JSON.
2655
+ */
2656
+ static fromBytes(bytes) {
2657
+ if (Is.empty(bytes) || bytes.length === 0) {
2658
+ return undefined;
2659
+ }
2660
+ try {
2661
+ const utf8 = Converter.bytesToUtf8(bytes);
2662
+ return JSON.parse(utf8);
2663
+ }
2664
+ catch (err) {
2665
+ throw new GeneralError(ObjectHelper._CLASS_NAME, "failedBytesToJSON", undefined, err);
2666
+ }
2667
+ }
2668
+ /**
2669
+ * Make a deep clone of an object.
2670
+ * @param obj The object to clone.
2671
+ * @returns The objects clone.
2672
+ */
2673
+ static clone(obj) {
2674
+ if (Is.undefined(obj)) {
2675
+ return undefined;
2676
+ }
2677
+ return structuredClone(obj);
2678
+ }
2679
+ /**
2680
+ * Deep merge objects.
2681
+ * @param obj1 The first object to merge.
2682
+ * @param obj2 The second object to merge.
2683
+ * @returns The combined deep merge of the objects.
2684
+ */
2685
+ static merge(obj1, obj2) {
2686
+ if (Is.empty(obj1)) {
2687
+ return ObjectHelper.clone(obj2);
2688
+ }
2689
+ if (Is.empty(obj2)) {
2690
+ return ObjectHelper.clone(obj1);
2691
+ }
2692
+ const obj1Clone = ObjectHelper.clone(obj1);
2693
+ if (Is.object(obj1Clone) && Is.object(obj2)) {
2694
+ const keys = Object.keys(obj2);
2695
+ for (const key of keys) {
2696
+ if (Is.object(obj1Clone[key]) && Is.object(obj2[key])) {
2697
+ ObjectHelper.propertySet(obj1Clone, key, ObjectHelper.merge(obj1Clone[key], obj2[key]));
2698
+ }
2699
+ else {
2700
+ ObjectHelper.propertySet(obj1Clone, key, obj2[key]);
2701
+ }
2603
2702
  }
2604
- else if (value > 0xbf && value < 0xe0) {
2605
- str += String.fromCharCode(((value & 0x1f) << 6) | (array[i + 1] & 0x3f));
2606
- i += 1;
2703
+ }
2704
+ return obj1Clone;
2705
+ }
2706
+ /**
2707
+ * Does one object equal another.
2708
+ * @param obj1 The first object to compare.
2709
+ * @param obj2 The second object to compare.
2710
+ * @param strictPropertyOrder Should the properties be in the same order, defaults to true.
2711
+ * @returns True is the objects are equal.
2712
+ */
2713
+ static equal(obj1, obj2, strictPropertyOrder) {
2714
+ if (strictPropertyOrder ?? true) {
2715
+ return JSON.stringify(obj1) === JSON.stringify(obj2);
2716
+ }
2717
+ return JsonHelper.canonicalize(obj1) === JsonHelper.canonicalize(obj2);
2718
+ }
2719
+ /**
2720
+ * Get the property of an unknown object.
2721
+ * @param obj The object to get the property from.
2722
+ * @param property The property to get, can be separated by dots for nested path.
2723
+ * @returns The property.
2724
+ */
2725
+ static propertyGet(obj, property) {
2726
+ const pathParts = property.split(".");
2727
+ let pathValue = obj;
2728
+ for (const pathPart of pathParts) {
2729
+ // Is the path part numeric i.e. an array index.
2730
+ const arrayMatch = /^(\d+)$/.exec(pathPart);
2731
+ if (arrayMatch) {
2732
+ const arrayIndex = Number.parseInt(arrayMatch[1], 10);
2733
+ if (Is.arrayValue(pathValue) && arrayIndex < pathValue.length) {
2734
+ // There is no prop name so this is a direct array index on the current object
2735
+ pathValue = pathValue[arrayIndex];
2736
+ }
2737
+ else {
2738
+ // Array index for non array object so return
2739
+ return undefined;
2740
+ }
2607
2741
  }
2608
- else if (value > 0xdf && value < 0xf0) {
2609
- str += String.fromCharCode(((value & 0x0f) << 12) | ((array[i + 1] & 0x3f) << 6) | (array[i + 2] & 0x3f));
2610
- i += 2;
2742
+ else if (Is.object(pathValue)) {
2743
+ // No array part in path so assume object sub property
2744
+ pathValue = pathValue[pathPart];
2611
2745
  }
2612
2746
  else {
2613
- // surrogate pair
2614
- const charCode = (((value & 0x07) << 18) |
2615
- ((array[i + 1] & 0x3f) << 12) |
2616
- ((array[i + 2] & 0x3f) << 6) |
2617
- (array[i + 3] & 0x3f)) -
2618
- 0x010000;
2619
- str += String.fromCharCode((charCode >> 10) | 0xd800, (charCode & 0x03ff) | 0xdc00);
2620
- i += 3;
2747
+ return undefined;
2621
2748
  }
2622
2749
  }
2623
- return str;
2750
+ return pathValue;
2624
2751
  }
2625
2752
  /**
2626
- * Convert a UTF8 string to raw array.
2627
- * @param utf8 The text to decode.
2628
- * @returns The array.
2753
+ * Set the property of an unknown object.
2754
+ * @param obj The object to set the property from.
2755
+ * @param property The property to set.
2756
+ * @param value The value to set.
2757
+ * @throws GeneralError if the property target is not an object.
2629
2758
  */
2630
- static utf8ToBytes(utf8) {
2631
- const bytes = [];
2632
- for (let i = 0; i < utf8.length; i++) {
2633
- let charCode = utf8.charCodeAt(i);
2634
- if (charCode < 0x80) {
2635
- bytes.push(charCode);
2759
+ static propertySet(obj, property, value) {
2760
+ const pathParts = property.split(".");
2761
+ let pathValue = obj;
2762
+ let parentObj;
2763
+ for (let i = 0; i < pathParts.length; i++) {
2764
+ const pathPart = pathParts[i];
2765
+ // Is the path part numeric i.e. an array index.
2766
+ const arrayMatch = /^(\d+)$/.exec(pathPart);
2767
+ const arrayIndex = arrayMatch ? Number.parseInt(arrayMatch[1], 10) : -1;
2768
+ if (i === pathParts.length - 1) {
2769
+ // Last part of path so set the value
2770
+ if (arrayIndex >= 0) {
2771
+ if (Is.array(pathValue)) {
2772
+ pathValue[arrayIndex] = value;
2773
+ }
2774
+ else if (Is.object(pathValue)) {
2775
+ pathValue[arrayIndex] = value;
2776
+ }
2777
+ else {
2778
+ throw new GeneralError(ObjectHelper._CLASS_NAME, "cannotSetArrayIndex", {
2779
+ property,
2780
+ index: arrayIndex
2781
+ });
2782
+ }
2783
+ }
2784
+ else if (Is.object(pathValue)) {
2785
+ pathValue[pathPart] = value;
2786
+ }
2787
+ else {
2788
+ throw new GeneralError(ObjectHelper._CLASS_NAME, "cannotSetProperty", { property });
2789
+ }
2636
2790
  }
2637
- else if (charCode < 0x800) {
2638
- bytes.push(0xc0 | (charCode >> 6), 0x80 | (charCode & 0x3f));
2791
+ else {
2792
+ parentObj = pathValue;
2793
+ if (Is.object(pathValue)) {
2794
+ pathValue = pathValue[pathPart];
2795
+ }
2796
+ else if (Is.array(pathValue)) {
2797
+ pathValue = pathValue[arrayIndex];
2798
+ }
2799
+ if (Is.empty(pathValue)) {
2800
+ const nextArrayMatch = /^(\d+)$/.exec(pathParts[i + 1]);
2801
+ const nextArrayIndex = nextArrayMatch ? Number.parseInt(nextArrayMatch[1], 10) : -1;
2802
+ if (nextArrayIndex >= 0) {
2803
+ pathValue = [];
2804
+ }
2805
+ else {
2806
+ pathValue = {};
2807
+ }
2808
+ if (Is.object(parentObj)) {
2809
+ parentObj[pathPart] = pathValue;
2810
+ }
2811
+ else if (Is.array(parentObj)) {
2812
+ parentObj[arrayIndex] = pathValue;
2813
+ }
2814
+ }
2639
2815
  }
2640
- else if (charCode < 0xd800 || charCode >= 0xe000) {
2641
- bytes.push(0xe0 | (charCode >> 12), 0x80 | ((charCode >> 6) & 0x3f), 0x80 | (charCode & 0x3f));
2816
+ }
2817
+ }
2818
+ /**
2819
+ * Delete the property of an unknown object.
2820
+ * @param obj The object to set the property from.
2821
+ * @param property The property to set
2822
+ */
2823
+ static propertyDelete(obj, property) {
2824
+ if (Is.object(obj)) {
2825
+ delete obj[property];
2826
+ }
2827
+ }
2828
+ /**
2829
+ * Extract a property from the object, providing alternative names.
2830
+ * @param obj The object to extract from.
2831
+ * @param propertyNames The possible names for the property.
2832
+ * @param removeProperties Remove the properties from the object, defaults to true.
2833
+ * @returns The property if available.
2834
+ */
2835
+ static extractProperty(obj, propertyNames, removeProperties = true) {
2836
+ let retVal;
2837
+ if (Is.object(obj)) {
2838
+ const names = Is.string(propertyNames) ? [propertyNames] : propertyNames;
2839
+ for (const prop of names) {
2840
+ retVal ??= ObjectHelper.propertyGet(obj, prop);
2841
+ if (removeProperties) {
2842
+ ObjectHelper.propertyDelete(obj, prop);
2843
+ }
2642
2844
  }
2643
- else {
2644
- // surrogate pair
2645
- i++;
2646
- // UTF-16 encodes 0x10000-0x10FFFF by
2647
- // subtracting 0x10000 and splitting the
2648
- // 20 bits of 0x0-0xFFFFF into two halves
2649
- charCode = 0x10000 + (((charCode & 0x3ff) << 10) | (utf8.charCodeAt(i) & 0x3ff));
2650
- bytes.push(0xf0 | (charCode >> 18), 0x80 | ((charCode >> 12) & 0x3f), 0x80 | ((charCode >> 6) & 0x3f), 0x80 | (charCode & 0x3f));
2845
+ }
2846
+ return retVal;
2847
+ }
2848
+ /**
2849
+ * Pick a subset of properties from an object.
2850
+ * @param obj The object to pick the properties from.
2851
+ * @param keys The property keys to pick.
2852
+ * @returns The partial object.
2853
+ */
2854
+ static pick(obj, keys) {
2855
+ if (Is.object(obj) && Is.arrayValue(keys)) {
2856
+ const result = {};
2857
+ for (const key of keys) {
2858
+ result[key] = obj[key];
2651
2859
  }
2860
+ return result;
2652
2861
  }
2653
- return Uint8Array.from(bytes);
2862
+ return obj;
2654
2863
  }
2655
2864
  /**
2656
- * Encode a raw array to hex string.
2657
- * @param array The bytes to encode.
2658
- * @param includePrefix Include the 0x prefix on the returned hex.
2659
- * @param startIndex The index to start in the bytes.
2660
- * @param length The length of bytes to read.
2661
- * @param reverse Reverse the combine direction.
2662
- * @returns The array formatted as hex.
2865
+ * Omit a subset of properties from an object.
2866
+ * @param obj The object to omit the properties from.
2867
+ * @param keys The property keys to omit.
2868
+ * @returns The partial object.
2663
2869
  */
2664
- static bytesToHex(array, includePrefix = false, startIndex, length, reverse) {
2665
- let hex = "";
2666
- this.buildHexLookups();
2667
- if (Converter._ENCODE_LOOKUP) {
2668
- const len = length ?? array.length;
2669
- const start = startIndex ?? 0;
2670
- if (reverse) {
2671
- for (let i = 0; i < len; i++) {
2672
- hex = Converter._ENCODE_LOOKUP[array[start + i]] + hex;
2870
+ static omit(obj, keys) {
2871
+ if (Is.object(obj) && Is.arrayValue(keys)) {
2872
+ const result = { ...obj };
2873
+ for (const key of keys) {
2874
+ delete result[key];
2875
+ }
2876
+ return result;
2877
+ }
2878
+ return obj;
2879
+ }
2880
+ /**
2881
+ * Converter the non JSON primitives to extended types.
2882
+ * @param obj The object to convert.
2883
+ * @returns The object with extended properties.
2884
+ */
2885
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2886
+ static toExtended(obj) {
2887
+ const jsonExtended = JsonHelper.stringifyEx(obj);
2888
+ return JSON.parse(jsonExtended);
2889
+ }
2890
+ /**
2891
+ * Converter the extended types to non JSON primitives.
2892
+ * @param obj The object to convert.
2893
+ * @returns The object with regular properties.
2894
+ */
2895
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2896
+ static fromExtended(obj) {
2897
+ const jsonExtended = JsonHelper.stringifyEx(obj);
2898
+ return JsonHelper.parseEx(jsonExtended);
2899
+ }
2900
+ /**
2901
+ * Remove empty properties from an object.
2902
+ * @param obj The object to remove the empty properties from.
2903
+ * @param options The options for the removal.
2904
+ * @param options.removeUndefined Remove undefined properties, defaults to true.
2905
+ * @param options.removeNull Remove null properties, defaults to false.
2906
+ * @returns The object with empty properties removed.
2907
+ */
2908
+ static removeEmptyProperties(obj, options) {
2909
+ if (Is.object(obj)) {
2910
+ const removeUndefined = options?.removeUndefined ?? true;
2911
+ const removeNull = options?.removeNull ?? false;
2912
+ const newObj = {};
2913
+ const keys = Object.keys(obj);
2914
+ for (const key of keys) {
2915
+ if (!((removeUndefined && Is.undefined(obj[key])) || (removeNull && Is.null(obj[key])))) {
2916
+ newObj[key] = ObjectHelper.removeEmptyProperties(obj[key], options);
2917
+ }
2918
+ }
2919
+ return newObj;
2920
+ }
2921
+ else if (Is.array(obj)) {
2922
+ const arr = [];
2923
+ for (const element of obj) {
2924
+ arr.push(ObjectHelper.removeEmptyProperties(element, options));
2925
+ }
2926
+ return arr;
2927
+ }
2928
+ return obj;
2929
+ }
2930
+ }
2931
+
2932
+ // Copyright 2024 IOTA Stiftung.
2933
+ // SPDX-License-Identifier: Apache-2.0.
2934
+ /**
2935
+ * Environment variable helper.
2936
+ */
2937
+ class EnvHelper {
2938
+ /**
2939
+ * Get the environment variable as an object with camel cased names.
2940
+ * @param envVars The environment variables.
2941
+ * @param prefix The prefix of the environment variables, if not provided gets all.
2942
+ * @returns The object with camel cased names.
2943
+ */
2944
+ static envToJson(envVars, prefix) {
2945
+ const result = {};
2946
+ if (!Is.empty(envVars)) {
2947
+ if (Is.empty(prefix)) {
2948
+ for (const envVar in envVars) {
2949
+ if (Is.stringValue(envVars[envVar])) {
2950
+ const camelCaseName = StringHelper.camelCase(envVar.toLowerCase());
2951
+ ObjectHelper.propertySet(result, camelCaseName, envVars[envVar]);
2952
+ }
2673
2953
  }
2674
2954
  }
2675
2955
  else {
2676
- for (let i = 0; i < len; i++) {
2677
- hex += Converter._ENCODE_LOOKUP[array[start + i]];
2956
+ for (const envVar in envVars) {
2957
+ if (envVar.startsWith(prefix) && Is.stringValue(envVars[envVar])) {
2958
+ const camelCaseName = StringHelper.camelCase(envVar.replace(prefix, "").toLowerCase());
2959
+ ObjectHelper.propertySet(result, camelCaseName, envVars[envVar]);
2960
+ }
2678
2961
  }
2679
2962
  }
2680
2963
  }
2681
- return includePrefix ? HexHelper.addPrefix(hex) : hex;
2964
+ return result;
2682
2965
  }
2966
+ }
2967
+
2968
+ // Copyright 2024 IOTA Stiftung.
2969
+ // SPDX-License-Identifier: Apache-2.0.
2970
+ /**
2971
+ * Class to perform internationalization.
2972
+ */
2973
+ class I18n {
2683
2974
  /**
2684
- * Decode a hex string to raw array.
2685
- * @param hex The hex to decode.
2686
- * @param reverse Store the characters in reverse.
2687
- * @returns The array.
2975
+ * The default translation.
2688
2976
  */
2689
- static hexToBytes(hex, reverse) {
2690
- const strippedHex = HexHelper.stripPrefix(hex);
2691
- const sizeof = strippedHex.length >> 1;
2692
- const length = sizeof << 1;
2693
- const array = new Uint8Array(sizeof);
2694
- this.buildHexLookups();
2695
- if (Converter._DECODE_LOOKUP) {
2696
- let i = 0;
2697
- let n = 0;
2698
- while (i < length) {
2699
- array[n++] =
2700
- (Converter._DECODE_LOOKUP[strippedHex.charCodeAt(i++)] << 4) |
2701
- Converter._DECODE_LOOKUP[strippedHex.charCodeAt(i++)];
2977
+ static DEFAULT_LOCALE = "en";
2978
+ /**
2979
+ * Set the locale.
2980
+ * @param locale The new locale.
2981
+ */
2982
+ static setLocale(locale) {
2983
+ const i18nShared = I18n.getI18nShared();
2984
+ i18nShared.currentLocale = locale;
2985
+ for (const callback in i18nShared.localeChangedHandlers) {
2986
+ i18nShared.localeChangedHandlers[callback](i18nShared.currentLocale);
2987
+ }
2988
+ }
2989
+ /**
2990
+ * Get the locale.
2991
+ * @returns The current locale.
2992
+ */
2993
+ static getLocale() {
2994
+ const i18nShared = I18n.getI18nShared();
2995
+ return i18nShared.currentLocale;
2996
+ }
2997
+ /**
2998
+ * Add a locale dictionary.
2999
+ * @param locale The locale.
3000
+ * @param dictionary The dictionary to add.
3001
+ */
3002
+ static addDictionary(locale, dictionary) {
3003
+ const i18nShared = I18n.getI18nShared();
3004
+ const mergedKeys = {};
3005
+ I18n.flattenTranslationKeys(dictionary, "", mergedKeys);
3006
+ i18nShared.localeDictionaries[locale] = mergedKeys;
3007
+ for (const callback in i18nShared.dictionaryChangedHandlers) {
3008
+ i18nShared.dictionaryChangedHandlers[callback](i18nShared.currentLocale);
3009
+ }
3010
+ }
3011
+ /**
3012
+ * Get a locale dictionary.
3013
+ * @param locale The locale.
3014
+ * @returns The dictionary of undefined if it does not exist.
3015
+ */
3016
+ static getDictionary(locale) {
3017
+ const i18nShared = I18n.getI18nShared();
3018
+ return i18nShared.localeDictionaries[locale];
3019
+ }
3020
+ /**
3021
+ * Get all the locale dictionaries.
3022
+ * @returns The dictionaries.
3023
+ */
3024
+ static getAllDictionaries() {
3025
+ const i18nShared = I18n.getI18nShared();
3026
+ return i18nShared.localeDictionaries;
3027
+ }
3028
+ /**
3029
+ * Add a locale changed handler.
3030
+ * @param id The id of the handler.
3031
+ * @param handler The handler to add.
3032
+ */
3033
+ static addLocaleHandler(id, handler) {
3034
+ const i18nShared = I18n.getI18nShared();
3035
+ i18nShared.localeChangedHandlers[id] = handler;
3036
+ }
3037
+ /**
3038
+ * Remove a locale changed handler.
3039
+ * @param id The id of the handler.
3040
+ */
3041
+ static removeLocaleHandler(id) {
3042
+ const i18nShared = I18n.getI18nShared();
3043
+ delete i18nShared.localeChangedHandlers[id];
3044
+ }
3045
+ /**
3046
+ * Add a dictionary changed handler.
3047
+ * @param id The id of the handler.
3048
+ * @param handler The handler to add.
3049
+ */
3050
+ static addDictionaryHandler(id, handler) {
3051
+ const i18nShared = I18n.getI18nShared();
3052
+ i18nShared.dictionaryChangedHandlers[id] = handler;
3053
+ }
3054
+ /**
3055
+ * Remove a dictionary changed handler.
3056
+ * @param id The id of the handler.
3057
+ */
3058
+ static removeDictionaryHandler(id) {
3059
+ const i18nShared = I18n.getI18nShared();
3060
+ delete i18nShared.dictionaryChangedHandlers[id];
3061
+ }
3062
+ /**
3063
+ * Format a message.
3064
+ * @param key The key of the message to format.
3065
+ * @param values The values to substitute into the message.
3066
+ * @param overrideLocale Override the locale.
3067
+ * @returns The formatted string.
3068
+ */
3069
+ static formatMessage(key, values, overrideLocale) {
3070
+ const i18nShared = I18n.getI18nShared();
3071
+ let cl = overrideLocale ?? i18nShared.currentLocale;
3072
+ if (cl.startsWith("debug-")) {
3073
+ cl = I18n.DEFAULT_LOCALE;
3074
+ }
3075
+ if (!i18nShared.localeDictionaries[cl]) {
3076
+ return `!!Missing ${cl}`;
3077
+ }
3078
+ if (!i18nShared.localeDictionaries[cl][key]) {
3079
+ return `!!Missing ${cl}.${key}`;
3080
+ }
3081
+ if (i18nShared.currentLocale === "debug-k") {
3082
+ return key;
3083
+ }
3084
+ let ret = new IntlMessageFormat(i18nShared.localeDictionaries[cl][key], cl).format(values);
3085
+ if (i18nShared.currentLocale === "debug-x") {
3086
+ ret = ret.replace(/[a-z]/g, "x").replace(/[A-Z]/g, "x").replace(/\d/g, "n");
3087
+ }
3088
+ return ret;
3089
+ }
3090
+ /**
3091
+ * Check if the dictionaries have a message for the given key.
3092
+ * @param key The key to check for existence.
3093
+ * @returns True if the key exists.
3094
+ */
3095
+ static hasMessage(key) {
3096
+ const i18nShared = I18n.getI18nShared();
3097
+ return Is.string(i18nShared.localeDictionaries[i18nShared.currentLocale]?.[key]);
3098
+ }
3099
+ /**
3100
+ * Flatten the translation property paths for faster lookup.
3101
+ * @param translation The translation to merge.
3102
+ * @param propertyPath The current root path.
3103
+ * @param mergedKeys The merged keys dictionary to populate.
3104
+ * @internal
3105
+ */
3106
+ static flattenTranslationKeys(translation, propertyPath, mergedKeys) {
3107
+ for (const key in translation) {
3108
+ const val = translation[key];
3109
+ const mergedPath = propertyPath.length > 0 ? `${propertyPath}.${key}` : key;
3110
+ if (Is.string(val)) {
3111
+ mergedKeys[mergedPath] = val;
2702
3112
  }
2703
- if (reverse) {
2704
- array.reverse();
3113
+ else if (Is.object(val)) {
3114
+ I18n.flattenTranslationKeys(val, mergedPath, mergedKeys);
3115
+ }
3116
+ }
3117
+ }
3118
+ /**
3119
+ * Get the I18n shared data.
3120
+ * @returns The I18n shared data.
3121
+ * @internal
3122
+ */
3123
+ static getI18nShared() {
3124
+ let i18nShared = SharedStore.get("i18n");
3125
+ if (Is.undefined(i18nShared)) {
3126
+ i18nShared = {
3127
+ localeDictionaries: {},
3128
+ currentLocale: I18n.DEFAULT_LOCALE,
3129
+ localeChangedHandlers: {},
3130
+ dictionaryChangedHandlers: {}
3131
+ };
3132
+ SharedStore.set("i18n", i18nShared);
3133
+ }
3134
+ return i18nShared;
3135
+ }
3136
+ }
3137
+
3138
+ // Copyright 2024 IOTA Stiftung.
3139
+ // SPDX-License-Identifier: Apache-2.0.
3140
+ /**
3141
+ * Error helper functions.
3142
+ */
3143
+ class ErrorHelper {
3144
+ /**
3145
+ * Format Errors and returns just their messages.
3146
+ * @param error The error to format.
3147
+ * @param includeDetails Whether to include error details, defaults to false.
3148
+ * @returns The error formatted including any inner errors.
3149
+ */
3150
+ static formatErrors(error, includeDetails) {
3151
+ const localizedErrors = ErrorHelper.localizeErrors(error);
3152
+ if (includeDetails ?? false) {
3153
+ const output = [];
3154
+ for (const err of localizedErrors) {
3155
+ let detailedError = err.message;
3156
+ if (Is.stringValue(err.stack)) {
3157
+ detailedError += `\n${err.stack}`;
3158
+ }
3159
+ output.push(detailedError);
3160
+ }
3161
+ return output;
3162
+ }
3163
+ return localizedErrors.map(e => e.message);
3164
+ }
3165
+ /**
3166
+ * Localize the content of an error and any inner errors.
3167
+ * @param error The error to format.
3168
+ * @returns The localized version of the errors flattened.
3169
+ */
3170
+ static localizeErrors(error) {
3171
+ const formattedErrors = [];
3172
+ if (Is.notEmpty(error)) {
3173
+ const errors = BaseError.flatten(error);
3174
+ for (const err of errors) {
3175
+ const errorNameKey = `errorNames.${StringHelper.camelCase(err.name)}`;
3176
+ const errorMessageKey = `error.${err.message}`;
3177
+ // If there is no error message then it is probably
3178
+ // from a 3rd party lib, so don't format it just display
3179
+ const hasErrorName = I18n.hasMessage(errorNameKey);
3180
+ const hasErrorMessage = I18n.hasMessage(errorMessageKey);
3181
+ const localizedError = {
3182
+ name: I18n.formatMessage(hasErrorName ? errorNameKey : "errorNames.error"),
3183
+ message: hasErrorMessage
3184
+ ? I18n.formatMessage(errorMessageKey, err.properties)
3185
+ : err.message
3186
+ };
3187
+ if (Is.stringValue(err.source)) {
3188
+ localizedError.source = err.source;
3189
+ }
3190
+ if (Is.stringValue(err.stack)) {
3191
+ // Remove the first line from the stack traces as they
3192
+ // just have the error type and message duplicated
3193
+ const lines = err.stack.split("\n");
3194
+ lines.shift();
3195
+ localizedError.stack = lines.join("\n");
3196
+ }
3197
+ const additional = ErrorHelper.formatValidationErrors(err);
3198
+ if (Is.stringValue(additional)) {
3199
+ localizedError.additional = additional;
3200
+ }
3201
+ formattedErrors.push(localizedError);
3202
+ }
3203
+ }
3204
+ return formattedErrors;
3205
+ }
3206
+ /**
3207
+ * Localize the content of an error and any inner errors.
3208
+ * @param error The error to format.
3209
+ * @returns The localized version of the errors flattened.
3210
+ */
3211
+ static formatValidationErrors(error) {
3212
+ if (Is.object(error.properties) &&
3213
+ Object.keys(error.properties).length > 0 &&
3214
+ Is.object(error.properties) &&
3215
+ Is.arrayValue(error.properties.validationFailures)) {
3216
+ const validationErrors = [];
3217
+ for (const validationFailure of error.properties.validationFailures) {
3218
+ const errorI18n = `error.${validationFailure.reason}`;
3219
+ const errorMessage = I18n.hasMessage(errorI18n)
3220
+ ? I18n.formatMessage(errorI18n, validationFailure.properties)
3221
+ : errorI18n;
3222
+ let v = `${validationFailure.property}: ${errorMessage}`;
3223
+ if (Is.object(validationFailure.properties) &&
3224
+ Is.notEmpty(validationFailure.properties.value)) {
3225
+ v += ` = ${JSON.stringify(validationFailure.properties.value)}`;
3226
+ }
3227
+ validationErrors.push(v);
2705
3228
  }
3229
+ return validationErrors.join("\n");
2706
3230
  }
2707
- return array;
2708
- }
2709
- /**
2710
- * Convert the UTF8 to hex.
2711
- * @param utf8 The text to convert.
2712
- * @param includePrefix Include the 0x prefix on the returned hex.
2713
- * @returns The hex version of the bytes.
2714
- */
2715
- static utf8ToHex(utf8, includePrefix = false) {
2716
- const hex = Converter.bytesToHex(Converter.utf8ToBytes(utf8));
2717
- return includePrefix ? HexHelper.addPrefix(hex) : hex;
2718
3231
  }
3232
+ }
3233
+
3234
+ // Copyright 2024 IOTA Stiftung.
3235
+ // SPDX-License-Identifier: Apache-2.0.
3236
+ /**
3237
+ * The types the extracted data can be coerced to.
3238
+ */
3239
+ // eslint-disable-next-line @typescript-eslint/naming-convention
3240
+ const CoerceType = {
2719
3241
  /**
2720
- * Convert the hex text to text.
2721
- * @param hex The hex to convert.
2722
- * @returns The UTF8 version of the bytes.
3242
+ * String.
2723
3243
  */
2724
- static hexToUtf8(hex) {
2725
- return Converter.bytesToUtf8(Converter.hexToBytes(HexHelper.stripPrefix(hex)));
2726
- }
3244
+ String: "string",
2727
3245
  /**
2728
- * Convert bytes to binary string.
2729
- * @param bytes The bytes to convert.
2730
- * @returns A binary string of the bytes.
3246
+ * Number.
2731
3247
  */
2732
- static bytesToBinary(bytes) {
2733
- const b = [];
2734
- for (let i = 0; i < bytes.length; i++) {
2735
- b.push(bytes[i].toString(2).padStart(8, "0"));
2736
- }
2737
- return b.join("");
2738
- }
3248
+ Number: "number",
2739
3249
  /**
2740
- * Convert a binary string to bytes.
2741
- * @param binary The binary string.
2742
- * @returns The bytes.
3250
+ * Integer.
2743
3251
  */
2744
- static binaryToBytes(binary) {
2745
- const bytes = new Uint8Array(Math.ceil(binary.length / 8));
2746
- for (let i = 0; i < bytes.length; i++) {
2747
- bytes[i] = Number.parseInt(binary.slice(i * 8, (i + 1) * 8), 2);
2748
- }
2749
- return bytes;
2750
- }
3252
+ Integer: "integer",
2751
3253
  /**
2752
- * Convert bytes to base64 string.
2753
- * @param bytes The bytes to convert.
2754
- * @returns A base64 string of the bytes.
3254
+ * Boolean.
2755
3255
  */
2756
- static bytesToBase64(bytes) {
2757
- return Base64.encode(bytes);
2758
- }
3256
+ Boolean: "boolean",
2759
3257
  /**
2760
- * Convert a base64 string to bytes.
2761
- * @param base64 The base64 string.
2762
- * @returns The bytes.
3258
+ * Big Integer.
2763
3259
  */
2764
- static base64ToBytes(base64) {
2765
- return Base64.decode(base64);
2766
- }
3260
+ BigInt: "bigint",
2767
3261
  /**
2768
- * Convert bytes to base64 url string.
2769
- * @param bytes The bytes to convert.
2770
- * @returns A base64 url string of the bytes.
3262
+ * Date.
2771
3263
  */
2772
- static bytesToBase64Url(bytes) {
2773
- return Base64Url.encode(bytes);
2774
- }
3264
+ Date: "date",
2775
3265
  /**
2776
- * Convert a base64 url string to bytes.
2777
- * @param base64Url The base64 url string.
2778
- * @returns The bytes.
3266
+ * Date Time.
2779
3267
  */
2780
- static base64UrlToBytes(base64Url) {
2781
- return Base64Url.decode(base64Url);
2782
- }
3268
+ DateTime: "datetime",
2783
3269
  /**
2784
- * Convert bytes to base58 string.
2785
- * @param bytes The bytes to convert.
2786
- * @returns A base58 string of the bytes.
3270
+ * Time.
2787
3271
  */
2788
- static bytesToBase58(bytes) {
2789
- return Base58.encode(bytes);
2790
- }
3272
+ Time: "time",
2791
3273
  /**
2792
- * Convert a base58 string to bytes.
2793
- * @param base58 The base58 string.
2794
- * @returns The bytes.
3274
+ * Object.
2795
3275
  */
2796
- static base58ToBytes(base58) {
2797
- return Base58.decode(base58);
2798
- }
3276
+ Object: "object",
2799
3277
  /**
2800
- * Build the static lookup tables.
2801
- * @internal
3278
+ * Uint8Array.
2802
3279
  */
2803
- static buildHexLookups() {
2804
- if (!Converter._ENCODE_LOOKUP || !Converter._DECODE_LOOKUP) {
2805
- const alphabet = "0123456789abcdef";
2806
- Converter._ENCODE_LOOKUP = [];
2807
- Converter._DECODE_LOOKUP = [];
2808
- for (let i = 0; i < 256; i++) {
2809
- Converter._ENCODE_LOOKUP[i] = alphabet[(i >> 4) & 0xf] + alphabet[i & 0xf];
2810
- if (i < 16) {
2811
- if (i < 10) {
2812
- Converter._DECODE_LOOKUP[0x30 + i] = i;
2813
- }
2814
- else {
2815
- Converter._DECODE_LOOKUP[0x61 - 10 + i] = i;
2816
- }
2817
- }
2818
- }
2819
- }
2820
- }
2821
- }
3280
+ Uint8Array: "uint8array"
3281
+ };
2822
3282
 
3283
+ // Copyright 2024 IOTA Stiftung.
3284
+ // SPDX-License-Identifier: Apache-2.0.
2823
3285
  /**
2824
- * Class to help with objects.
3286
+ * Coerce an object from one type to another.
2825
3287
  */
2826
- class ObjectHelper {
2827
- /**
2828
- * Runtime name for the class.
2829
- * @internal
2830
- */
2831
- static _CLASS_NAME = "ObjectHelper";
3288
+ class Coerce {
2832
3289
  /**
2833
- * Convert an object to bytes.
2834
- * @param obj The object to convert.
2835
- * @param format Format the JSON content.
2836
- * @returns The object as bytes.
3290
+ * Coerce the value to a string.
3291
+ * @param value The value to coerce.
3292
+ * @throws TypeError If the value can not be coerced.
3293
+ * @returns The value if it can be coerced.
2837
3294
  */
2838
- static toBytes(obj, format = false) {
2839
- if (obj === undefined) {
2840
- return new Uint8Array();
3295
+ static string(value) {
3296
+ if (Is.undefined(value)) {
3297
+ return value;
3298
+ }
3299
+ if (Is.string(value)) {
3300
+ return value;
3301
+ }
3302
+ if (Is.number(value)) {
3303
+ return value.toString();
3304
+ }
3305
+ if (Is.boolean(value)) {
3306
+ return value ? "true" : "false";
3307
+ }
3308
+ if (Is.date(value)) {
3309
+ return value.toISOString();
2841
3310
  }
2842
- const json = format ? JSON.stringify(obj, undefined, "\t") : JSON.stringify(obj);
2843
- return Converter.utf8ToBytes(json);
2844
3311
  }
2845
3312
  /**
2846
- * Convert a bytes to an object.
2847
- * @param bytes The bytes to convert to an object.
2848
- * @returns The object.
2849
- * @throws GeneralError if there was an error parsing the JSON.
3313
+ * Coerce the value to a number.
3314
+ * @param value The value to coerce.
3315
+ * @throws TypeError If the value can not be coerced.
3316
+ * @returns The value if it can be coerced.
2850
3317
  */
2851
- static fromBytes(bytes) {
2852
- if (Is.empty(bytes) || bytes.length === 0) {
2853
- return undefined;
3318
+ static number(value) {
3319
+ if (Is.undefined(value)) {
3320
+ return value;
2854
3321
  }
2855
- try {
2856
- const utf8 = Converter.bytesToUtf8(bytes);
2857
- return JSON.parse(utf8);
3322
+ if (Is.number(value)) {
3323
+ return value;
2858
3324
  }
2859
- catch (err) {
2860
- throw new GeneralError(ObjectHelper._CLASS_NAME, "failedBytesToJSON", undefined, err);
3325
+ if (Is.string(value)) {
3326
+ const parsed = Number.parseFloat(value);
3327
+ if (Is.number(parsed)) {
3328
+ return parsed;
3329
+ }
3330
+ }
3331
+ if (Is.boolean(value)) {
3332
+ return value ? 1 : 0;
3333
+ }
3334
+ if (Is.date(value)) {
3335
+ return value.getTime();
2861
3336
  }
2862
3337
  }
2863
3338
  /**
2864
- * Make a deep clone of an object.
2865
- * @param obj The object to clone.
2866
- * @returns The objects clone.
3339
+ * Coerce the value to an integer.
3340
+ * @param value The value to coerce.
3341
+ * @throws TypeError If the value can not be coerced.
3342
+ * @returns The value if it can be coerced.
2867
3343
  */
2868
- static clone(obj) {
2869
- if (Is.undefined(obj)) {
2870
- return undefined;
3344
+ static integer(value) {
3345
+ const num = Coerce.number(value);
3346
+ if (!Is.undefined(num)) {
3347
+ return Math.trunc(num);
2871
3348
  }
2872
- return structuredClone(obj);
2873
3349
  }
2874
3350
  /**
2875
- * Deep merge objects.
2876
- * @param obj1 The first object to merge.
2877
- * @param obj2 The second object to merge.
2878
- * @returns The combined deep merge of the objects.
3351
+ * Coerce the value to a bigint.
3352
+ * @param value The value to coerce.
3353
+ * @throws TypeError If the value can not be coerced.
3354
+ * @returns The value if it can be coerced.
2879
3355
  */
2880
- static merge(obj1, obj2) {
2881
- if (Is.empty(obj1)) {
2882
- return ObjectHelper.clone(obj2);
3356
+ static bigint(value) {
3357
+ if (Is.undefined(value)) {
3358
+ return value;
2883
3359
  }
2884
- if (Is.empty(obj2)) {
2885
- return ObjectHelper.clone(obj1);
3360
+ if (Is.bigint(value)) {
3361
+ return value;
2886
3362
  }
2887
- const obj1Clone = ObjectHelper.clone(obj1);
2888
- if (Is.object(obj1Clone) && Is.object(obj2)) {
2889
- const keys = Object.keys(obj2);
2890
- for (const key of keys) {
2891
- if (Is.object(obj1Clone[key]) && Is.object(obj2[key])) {
2892
- ObjectHelper.propertySet(obj1Clone, key, ObjectHelper.merge(obj1Clone[key], obj2[key]));
2893
- }
2894
- else {
2895
- ObjectHelper.propertySet(obj1Clone, key, obj2[key]);
2896
- }
3363
+ if (Is.number(value)) {
3364
+ return BigInt(value);
3365
+ }
3366
+ if (Is.string(value)) {
3367
+ const parsed = Number.parseFloat(value);
3368
+ if (Is.integer(parsed)) {
3369
+ return BigInt(parsed);
3370
+ }
3371
+ }
3372
+ if (Is.boolean(value)) {
3373
+ return value ? 1n : 0n;
3374
+ }
3375
+ }
3376
+ /**
3377
+ * Coerce the value to a boolean.
3378
+ * @param value The value to coerce.
3379
+ * @throws TypeError If the value can not be coerced.
3380
+ * @returns The value if it can be coerced.
3381
+ */
3382
+ static boolean(value) {
3383
+ if (Is.undefined(value)) {
3384
+ return value;
3385
+ }
3386
+ if (Is.boolean(value)) {
3387
+ return value;
3388
+ }
3389
+ if (Is.number(value)) {
3390
+ // eslint-disable-next-line no-unneeded-ternary
3391
+ return value ? true : false;
3392
+ }
3393
+ if (Is.string(value)) {
3394
+ if (/true/i.test(value)) {
3395
+ return true;
3396
+ }
3397
+ if (/false/i.test(value)) {
3398
+ return false;
2897
3399
  }
2898
3400
  }
2899
- return obj1Clone;
2900
3401
  }
2901
3402
  /**
2902
- * Does one object equal another.
2903
- * @param obj1 The first object to compare.
2904
- * @param obj2 The second object to compare.
2905
- * @param strictPropertyOrder Should the properties be in the same order, defaults to true.
2906
- * @returns True is the objects are equal.
3403
+ * Coerce the value to a date.
3404
+ * @param value The value to coerce.
3405
+ * @throws TypeError If the value can not be coerced.
3406
+ * @returns The value if it can be coerced.
2907
3407
  */
2908
- static equal(obj1, obj2, strictPropertyOrder) {
2909
- if (strictPropertyOrder ?? true) {
2910
- return JSON.stringify(obj1) === JSON.stringify(obj2);
3408
+ static date(value) {
3409
+ if (Is.undefined(value)) {
3410
+ return value;
3411
+ }
3412
+ if (Is.date(value)) {
3413
+ return value;
3414
+ }
3415
+ if (Is.number(value)) {
3416
+ return new Date(value);
3417
+ }
3418
+ if (Is.string(value)) {
3419
+ const dt = new Date(value);
3420
+ if (!Number.isNaN(dt.getTime())) {
3421
+ const utc = Date.UTC(dt.getUTCFullYear(), dt.getUTCMonth(), dt.getUTCDate());
3422
+ return new Date(utc);
3423
+ }
2911
3424
  }
2912
- return JsonHelper.canonicalize(obj1) === JsonHelper.canonicalize(obj2);
2913
3425
  }
2914
3426
  /**
2915
- * Get the property of an unknown object.
2916
- * @param obj The object to get the property from.
2917
- * @param property The property to get, can be separated by dots for nested path.
2918
- * @returns The property.
3427
+ * Coerce the value to a date/time.
3428
+ * @param value The value to coerce.
3429
+ * @throws TypeError If the value can not be coerced.
3430
+ * @returns The value if it can be coerced.
2919
3431
  */
2920
- static propertyGet(obj, property) {
2921
- if (property.includes(".")) {
2922
- const parts = property.split(".");
2923
- let value = obj;
2924
- for (const part of parts) {
2925
- if (Is.object(value)) {
2926
- value = value[part];
2927
- }
2928
- else {
2929
- return undefined;
2930
- }
2931
- }
3432
+ static dateTime(value) {
3433
+ if (Is.undefined(value)) {
2932
3434
  return value;
2933
3435
  }
2934
- return Is.object(obj) ? obj[property] : undefined;
3436
+ if (Is.date(value)) {
3437
+ return value;
3438
+ }
3439
+ if (Is.number(value)) {
3440
+ return new Date(value);
3441
+ }
3442
+ if (Is.string(value)) {
3443
+ const dt = new Date(value);
3444
+ if (!Number.isNaN(dt.getTime())) {
3445
+ const utc = Date.UTC(dt.getUTCFullYear(), dt.getUTCMonth(), dt.getUTCDate(), dt.getUTCHours(), dt.getUTCMinutes(), dt.getUTCSeconds(), dt.getUTCMilliseconds());
3446
+ return new Date(utc);
3447
+ }
3448
+ }
2935
3449
  }
2936
3450
  /**
2937
- * Set the property of an unknown object.
2938
- * @param obj The object to set the property from.
2939
- * @param property The property to set.
2940
- * @param value The value to set.
3451
+ * Coerce the value to a time.
3452
+ * @param value The value to coerce.
3453
+ * @throws TypeError If the value can not be coerced.
3454
+ * @returns The value if it can be coerced.
2941
3455
  */
2942
- static propertySet(obj, property, value) {
2943
- if (Is.object(obj)) {
2944
- obj[property] = value;
3456
+ static time(value) {
3457
+ if (Is.undefined(value)) {
3458
+ return value;
3459
+ }
3460
+ if (Is.date(value)) {
3461
+ return value;
3462
+ }
3463
+ if (Is.number(value)) {
3464
+ const dt = new Date(value);
3465
+ dt.setFullYear(1970, 0, 1);
3466
+ return dt;
3467
+ }
3468
+ if (Is.string(value)) {
3469
+ const dt = new Date(value);
3470
+ if (!Number.isNaN(dt.getTime())) {
3471
+ const utc = Date.UTC(1970, 0, 1, dt.getUTCHours(), dt.getUTCMinutes(), dt.getUTCSeconds(), dt.getUTCMilliseconds());
3472
+ return new Date(utc);
3473
+ }
2945
3474
  }
2946
3475
  }
2947
3476
  /**
2948
- * Delete the property of an unknown object.
2949
- * @param obj The object to set the property from.
2950
- * @param property The property to set
3477
+ * Coerce the value to an object.
3478
+ * @param value The value to coerce.
3479
+ * @throws TypeError If the value can not be coerced.
3480
+ * @returns The value if it can be coerced.
2951
3481
  */
2952
- static propertyDelete(obj, property) {
2953
- if (Is.object(obj)) {
2954
- delete obj[property];
3482
+ static object(value) {
3483
+ if (Is.undefined(value)) {
3484
+ return value;
3485
+ }
3486
+ if (Is.object(value)) {
3487
+ return value;
3488
+ }
3489
+ if (Is.stringValue(value)) {
3490
+ try {
3491
+ return JSON.parse(value);
3492
+ }
3493
+ catch { }
2955
3494
  }
2956
3495
  }
2957
3496
  /**
2958
- * Extract a property from the object, providing alternative names.
2959
- * @param obj The object to extract from.
2960
- * @param propertyNames The possible names for the property.
2961
- * @param removeProperties Remove the properties from the object, defaults to true.
2962
- * @returns The property if available.
3497
+ * Coerce the value to a Uint8Array.
3498
+ * @param value The value to coerce.
3499
+ * @throws TypeError If the value can not be coerced.
3500
+ * @returns The value if it can be coerced.
2963
3501
  */
2964
- static extractProperty(obj, propertyNames, removeProperties = true) {
2965
- let retVal;
2966
- if (Is.object(obj)) {
2967
- const names = Is.string(propertyNames) ? [propertyNames] : propertyNames;
2968
- for (const prop of names) {
2969
- retVal ??= ObjectHelper.propertyGet(obj, prop);
2970
- if (removeProperties) {
2971
- ObjectHelper.propertyDelete(obj, prop);
2972
- }
3502
+ static uint8Array(value) {
3503
+ if (Is.undefined(value)) {
3504
+ return value;
3505
+ }
3506
+ if (Is.string(value)) {
3507
+ if (Is.stringHex(value.toLowerCase(), true)) {
3508
+ return Converter.hexToBytes(value.toLowerCase());
3509
+ }
3510
+ if (Is.stringBase64(value)) {
3511
+ return Converter.base64ToBytes(value);
2973
3512
  }
2974
3513
  }
2975
- return retVal;
2976
3514
  }
2977
3515
  /**
2978
- * Pick a subset of properties from an object.
2979
- * @param obj The object to pick the properties from.
2980
- * @param keys The property keys to pick.
2981
- * @returns The partial object.
2982
- */
2983
- static pick(obj, keys) {
2984
- if (Is.object(obj) && Is.arrayValue(keys)) {
2985
- const result = {};
2986
- for (const key of keys) {
2987
- result[key] = obj[key];
2988
- }
2989
- return result;
3516
+ * Coerces a value based on the coercion type.
3517
+ * @param value The value to coerce.
3518
+ * @param type The coercion type to perform.
3519
+ * @returns The coerced value.
3520
+ */
3521
+ static byType(value, type) {
3522
+ switch (type) {
3523
+ case CoerceType.String:
3524
+ return Coerce.string(value);
3525
+ case CoerceType.Number:
3526
+ return Coerce.number(value);
3527
+ case CoerceType.Integer:
3528
+ return Coerce.integer(value);
3529
+ case CoerceType.BigInt:
3530
+ return Coerce.bigint(value);
3531
+ case CoerceType.Boolean:
3532
+ return Coerce.boolean(value);
3533
+ case CoerceType.Date:
3534
+ return Coerce.date(value);
3535
+ case CoerceType.DateTime:
3536
+ return Coerce.dateTime(value);
3537
+ case CoerceType.Time:
3538
+ return Coerce.time(value);
3539
+ case CoerceType.Object:
3540
+ return Coerce.object(value);
3541
+ case CoerceType.Uint8Array:
3542
+ return Coerce.uint8Array(value);
3543
+ default:
3544
+ return value;
2990
3545
  }
2991
- return obj;
2992
3546
  }
3547
+ }
3548
+
3549
+ // Copyright 2024 IOTA Stiftung.
3550
+ // SPDX-License-Identifier: Apache-2.0.
3551
+ /**
3552
+ * Class to help with filenames.
3553
+ */
3554
+ class FilenameHelper {
2993
3555
  /**
2994
- * Omit a subset of properties from an object.
2995
- * @param obj The object to omit the properties from.
2996
- * @param keys The property keys to omit.
2997
- * @returns The partial object.
3556
+ * Replaces any unsafe characters in the filename.
3557
+ * @param filename The filename to make safe.
3558
+ * @returns The safe filename.
2998
3559
  */
2999
- static omit(obj, keys) {
3000
- if (Is.object(obj) && Is.arrayValue(keys)) {
3001
- const result = { ...obj };
3002
- for (const key of keys) {
3003
- delete result[key];
3004
- }
3005
- return result;
3560
+ static safeFilename(filename) {
3561
+ let safe = Coerce.string(filename);
3562
+ if (Is.empty(safe)) {
3563
+ return "";
3006
3564
  }
3007
- return obj;
3565
+ // Common non filename characters
3566
+ safe = safe.replace(/["*/:<>?\\|]/g, "_");
3567
+ // Windows non filename characters
3568
+ safe = safe.replace(/^(con|prn|aux|nul|com\d|lpt\d)$/i, "_");
3569
+ // Control characters
3570
+ safe = safe.replace(/[\u0000-\u001F\u0080-\u009F]/g, "_");
3571
+ // Relative paths
3572
+ safe = safe.replace(/^\.+/, "_");
3573
+ // Trailing periods
3574
+ safe = safe.replace(/\.+$/, "");
3575
+ return safe;
3008
3576
  }
3009
3577
  }
3010
3578
 
@@ -3026,6 +3594,32 @@ class RandomHelper {
3026
3594
  }
3027
3595
  }
3028
3596
 
3597
+ // Copyright 2024 IOTA Stiftung.
3598
+ // SPDX-License-Identifier: Apache-2.0.
3599
+ /**
3600
+ * Class to help with uint8 arrays.
3601
+ */
3602
+ class Uint8ArrayHelper {
3603
+ /**
3604
+ * Concatenate multiple arrays.
3605
+ * @param arrays The array to concatenate.
3606
+ * @returns The combined array.
3607
+ */
3608
+ static concat(arrays) {
3609
+ let totalLength = 0;
3610
+ for (const array of arrays) {
3611
+ totalLength += array.length;
3612
+ }
3613
+ const concatBytes = new Uint8Array(totalLength);
3614
+ let offset = 0;
3615
+ for (const array of arrays) {
3616
+ concatBytes.set(array, offset);
3617
+ offset += array.length;
3618
+ }
3619
+ return concatBytes;
3620
+ }
3621
+ }
3622
+
3029
3623
  // Copyright 2024 IOTA Stiftung.
3030
3624
  // SPDX-License-Identifier: Apache-2.0.
3031
3625
  /**
@@ -3038,7 +3632,7 @@ const CompressionType = {
3038
3632
  */
3039
3633
  Gzip: "gzip",
3040
3634
  /**
3041
- * deflate.
3635
+ * Deflate.
3042
3636
  */
3043
3637
  Deflate: "deflate"
3044
3638
  };
@@ -3489,29 +4083,93 @@ class Urn {
3489
4083
  * Cache the results from asynchronous requests.
3490
4084
  */
3491
4085
  class AsyncCache {
3492
- /**
3493
- * Cache for the fetch requests.
3494
- * @internal
3495
- */
3496
- static _cache = {};
3497
4086
  /**
3498
4087
  * Execute an async request and cache the result.
3499
4088
  * @param key The key for the entry in the cache.
3500
4089
  * @param ttlMs The TTL of the entry in the cache.
3501
4090
  * @param requestMethod The method to call if not cached.
4091
+ * @param cacheFailures Cache failure results, defaults to false.
3502
4092
  * @returns The response.
3503
4093
  */
3504
- static exec(key, ttlMs, requestMethod) {
4094
+ static exec(key, ttlMs, requestMethod, cacheFailures) {
3505
4095
  const cacheEnabled = Is.integer(ttlMs) && ttlMs >= 0;
3506
4096
  if (cacheEnabled) {
3507
4097
  AsyncCache.cleanupExpired();
3508
- if (!this._cache[key]) {
3509
- this._cache[key] = {
3510
- response: requestMethod(),
3511
- expires: ttlMs === 0 ? 0 : Date.now() + ttlMs
3512
- };
4098
+ const cache = AsyncCache.getSharedCache();
4099
+ // Do we have a cache entry for the key
4100
+ if (cache[key]) {
4101
+ if (!Is.empty(cache[key].result)) {
4102
+ // If the cache has already resulted in a value, resolve it
4103
+ return Promise.resolve(cache[key].result);
4104
+ }
4105
+ else if (!Is.empty(cache[key].error)) {
4106
+ // If the cache has already resulted in an error, reject it
4107
+ return Promise.reject(cache[key].error);
4108
+ }
4109
+ // Otherwise create a promise to return and store the resolver
4110
+ // and rejector in the cache entry, so that we can call then
4111
+ // when the request is done
4112
+ let storedResolve;
4113
+ let storedReject;
4114
+ const wait = new Promise((resolve, reject) => {
4115
+ storedResolve = resolve;
4116
+ storedReject = reject;
4117
+ });
4118
+ if (!Is.empty(storedResolve) && !Is.empty(storedReject)) {
4119
+ cache[key].promiseQueue.push({
4120
+ requestMethod,
4121
+ resolve: storedResolve,
4122
+ reject: storedReject
4123
+ });
4124
+ }
4125
+ return wait;
3513
4126
  }
3514
- return this._cache[key].response;
4127
+ // If we don't have a cache entry, create a new one
4128
+ cache[key] = {
4129
+ promiseQueue: [],
4130
+ expires: ttlMs === 0 ? 0 : Date.now() + ttlMs
4131
+ };
4132
+ // Return a promise that wraps the original request method
4133
+ // so that we can store any results or errors in the cache
4134
+ return new Promise((resolve, reject) => {
4135
+ // Call the request method and store the result
4136
+ requestMethod()
4137
+ // eslint-disable-next-line promise/prefer-await-to-then
4138
+ .then(res => {
4139
+ // If the request was successful, store the result
4140
+ cache[key].result = res;
4141
+ // and resolve both this promise and all the waiters
4142
+ resolve(res);
4143
+ for (const wait of cache[key].promiseQueue) {
4144
+ wait.resolve(res);
4145
+ }
4146
+ return res;
4147
+ })
4148
+ // eslint-disable-next-line promise/prefer-await-to-then
4149
+ .catch((err) => {
4150
+ // Reject the promise
4151
+ reject(err);
4152
+ // Handle the waiters based on the cacheFailures flag
4153
+ if (cacheFailures ?? false) {
4154
+ // If we are caching failures, store the error and reject the waiters
4155
+ cache[key].error = err;
4156
+ for (const wait of cache[key].promiseQueue) {
4157
+ wait.reject(err);
4158
+ }
4159
+ // Clear the waiters so we don't call them again
4160
+ cache[key].promiseQueue = [];
4161
+ }
4162
+ else {
4163
+ // If not caching failures for any queued requests we
4164
+ // have no value to either resolve or reject, so we
4165
+ // just resolve with the original request method
4166
+ for (const wait of cache[key].promiseQueue) {
4167
+ wait.resolve(wait.requestMethod());
4168
+ }
4169
+ delete cache[key];
4170
+ }
4171
+ });
4172
+ });
3515
4173
  }
3516
4174
  }
3517
4175
  /**
@@ -3520,41 +4178,80 @@ class AsyncCache {
3520
4178
  * @returns The item from the cache if it exists.
3521
4179
  */
3522
4180
  static async get(key) {
3523
- return AsyncCache._cache[key]?.response;
4181
+ const cache = AsyncCache.getSharedCache();
4182
+ if (!Is.empty(cache[key].result)) {
4183
+ // If the cache has already resulted in a value, resolve it
4184
+ return cache[key].result;
4185
+ }
4186
+ else if (!Is.empty(cache[key].error)) {
4187
+ // If the cache has already resulted in an error, reject it
4188
+ throw cache[key].error;
4189
+ }
4190
+ }
4191
+ /**
4192
+ * Set an entry into the cache.
4193
+ * @param key The key to set in the cache.
4194
+ * @param value The value to set in the cache.
4195
+ * @param ttlMs The TTL of the entry in the cache in ms, defaults to 1s.
4196
+ * @returns Nothing.
4197
+ */
4198
+ static async set(key, value, ttlMs) {
4199
+ const cache = AsyncCache.getSharedCache();
4200
+ cache[key] = {
4201
+ result: value,
4202
+ promiseQueue: [],
4203
+ expires: Date.now() + (ttlMs ?? 1000)
4204
+ };
3524
4205
  }
3525
4206
  /**
3526
4207
  * Remove an entry from the cache.
3527
4208
  * @param key The key to remove from the cache.
3528
4209
  */
3529
4210
  static remove(key) {
3530
- delete AsyncCache._cache[key];
4211
+ const cache = AsyncCache.getSharedCache();
4212
+ delete cache[key];
3531
4213
  }
3532
4214
  /**
3533
4215
  * Clear the cache.
3534
4216
  * @param prefix Optional prefix to clear only entries with that prefix.
3535
4217
  */
3536
4218
  static clearCache(prefix) {
4219
+ const cache = AsyncCache.getSharedCache();
3537
4220
  if (Is.stringValue(prefix)) {
3538
- for (const entry in this._cache) {
4221
+ for (const entry in cache) {
3539
4222
  if (entry.startsWith(prefix)) {
3540
- delete this._cache[entry];
4223
+ delete cache[entry];
3541
4224
  }
3542
4225
  }
3543
4226
  }
3544
4227
  else {
3545
- AsyncCache._cache = {};
4228
+ SharedStore.set("asyncCache", {});
3546
4229
  }
3547
4230
  }
3548
4231
  /**
3549
4232
  * Perform a cleanup of the expired entries in the cache.
3550
4233
  */
3551
4234
  static cleanupExpired() {
3552
- for (const entry in this._cache) {
3553
- if (AsyncCache._cache[entry].expires > 0 && AsyncCache._cache[entry].expires < Date.now()) {
3554
- delete this._cache[entry];
4235
+ const cache = AsyncCache.getSharedCache();
4236
+ for (const entry in cache) {
4237
+ if (cache[entry].expires > 0 && cache[entry].expires < Date.now()) {
4238
+ delete cache[entry];
3555
4239
  }
3556
4240
  }
3557
4241
  }
4242
+ /**
4243
+ * Get the shared cache.
4244
+ * @returns The shared cache.
4245
+ * @internal
4246
+ */
4247
+ static getSharedCache() {
4248
+ let sharedCache = SharedStore.get("asyncCache");
4249
+ if (Is.undefined(sharedCache)) {
4250
+ sharedCache = {};
4251
+ SharedStore.set("asyncCache", sharedCache);
4252
+ }
4253
+ return sharedCache;
4254
+ }
3558
4255
  }
3559
4256
 
3560
4257
  /**
@@ -3576,15 +4273,14 @@ class Compression {
3576
4273
  Guards.uint8Array(Compression._CLASS_NAME, "bytes", bytes);
3577
4274
  Guards.arrayOneOf(Compression._CLASS_NAME, "type", type, Object.values(CompressionType));
3578
4275
  const blob = new Blob([bytes]);
3579
- const ds = new CompressionStream(type);
3580
- const compressedStream = blob.stream().pipeThrough(ds);
3581
- const compressedBlob = await new Response(compressedStream).blob();
3582
- const ab = await compressedBlob.arrayBuffer();
3583
- const compressedBytes = new Uint8Array(ab);
4276
+ const compressionStream = new CompressionStream(type);
4277
+ const compressionPipe = blob.stream().pipeThrough(compressionStream);
4278
+ const compressedBlob = await new Response(compressionPipe).blob();
4279
+ const compressedBytes = new Uint8Array(await compressedBlob.arrayBuffer());
3584
4280
  // GZIP header contains a byte which specifies the OS the
3585
4281
  // compression was performed on. We set this to 3 (Unix) to ensure
3586
4282
  // that we produce consistent results.
3587
- if (type === "gzip" && compressedBytes.length >= 10) {
4283
+ if (type === CompressionType.Gzip && compressedBytes.length >= 10) {
3588
4284
  compressedBytes[9] = 3;
3589
4285
  }
3590
4286
  return compressedBytes;
@@ -3599,11 +4295,10 @@ class Compression {
3599
4295
  Guards.uint8Array(Compression._CLASS_NAME, "compressedBytes", compressedBytes);
3600
4296
  Guards.arrayOneOf(Compression._CLASS_NAME, "type", type, Object.values(CompressionType));
3601
4297
  const blob = new Blob([compressedBytes]);
3602
- const ds = new DecompressionStream(type);
3603
- const decompressedStream = blob.stream().pipeThrough(ds);
3604
- const decompressedBlob = await new Response(decompressedStream).blob();
3605
- const ab = await decompressedBlob.arrayBuffer();
3606
- return new Uint8Array(ab);
4298
+ const decompressionStream = new DecompressionStream(type);
4299
+ const decompressionPipe = blob.stream().pipeThrough(decompressionStream);
4300
+ const decompressedBlob = await new Response(decompressionPipe).blob();
4301
+ return new Uint8Array(await decompressedBlob.bytes());
3607
4302
  }
3608
4303
  }
3609
4304
 
@@ -3661,6 +4356,7 @@ class Validation {
3661
4356
  * @param options Additional options for the validation.
3662
4357
  * @param options.minLength The minimum length of the string.
3663
4358
  * @param options.maxLength The maximum length of the string.
4359
+ * @param options.format Specific format to check.
3664
4360
  * @returns True if the value is a valid string.
3665
4361
  */
3666
4362
  static string(property, value, failures, fieldNameResource, options) {
@@ -3679,6 +4375,47 @@ class Validation {
3679
4375
  const maxLimitDefined = Is.integer(maxLength);
3680
4376
  const belowMin = minLimitDefined && value.length < minLength;
3681
4377
  const aboveMax = maxLimitDefined && value.length > maxLength;
4378
+ if (options?.format === "base58" && !Is.stringBase58(value)) {
4379
+ failures.push({
4380
+ property,
4381
+ reason: "validation.beTextBase58",
4382
+ properties: {
4383
+ fieldName: fieldNameResource ?? "validation.defaultFieldName",
4384
+ value
4385
+ }
4386
+ });
4387
+ }
4388
+ else if (options?.format === "base64" && !Is.stringBase64(value)) {
4389
+ failures.push({
4390
+ property,
4391
+ reason: "validation.beTextBase64",
4392
+ properties: {
4393
+ fieldName: fieldNameResource ?? "validation.defaultFieldName",
4394
+ value
4395
+ }
4396
+ });
4397
+ }
4398
+ else if (options?.format === "hex" && !Is.stringHex(value)) {
4399
+ failures.push({
4400
+ property,
4401
+ reason: "validation.beTextHex",
4402
+ properties: {
4403
+ fieldName: fieldNameResource ?? "validation.defaultFieldName",
4404
+ value
4405
+ }
4406
+ });
4407
+ }
4408
+ else if (Is.regexp(options?.format) && !options.format.test(value)) {
4409
+ failures.push({
4410
+ property,
4411
+ reason: "validation.beTextRegExp",
4412
+ properties: {
4413
+ fieldName: fieldNameResource ?? "validation.defaultFieldName",
4414
+ value,
4415
+ format: options?.format
4416
+ }
4417
+ });
4418
+ }
3682
4419
  if (minLimitDefined && maxLimitDefined && (belowMin || aboveMax)) {
3683
4420
  failures.push({
3684
4421
  property,
@@ -4338,4 +5075,4 @@ class Validation {
4338
5075
  }
4339
5076
  }
4340
5077
 
4341
- export { AlreadyExistsError, ArrayHelper, AsyncCache, Base32, Base58, Base64, Base64Url, BaseError, BitString, Coerce, ComponentFactory, Compression, CompressionType, ConflictError, Converter, ErrorHelper, Factory, FilenameHelper, GeneralError, GuardError, Guards, HexHelper, I18n, Is, JsonHelper, NotFoundError, NotImplementedError, NotSupportedError, ObjectHelper, RandomHelper, StringHelper, UnauthorizedError, UnprocessableError, Url, Urn, Validation, ValidationError };
5078
+ export { AlreadyExistsError, ArrayHelper, AsyncCache, Base32, Base58, Base64, Base64Url, BaseError, BitString, Coerce, CoerceType, ComponentFactory, Compression, CompressionType, ConflictError, Converter, EnvHelper, ErrorHelper, Factory, FilenameHelper, GeneralError, GuardError, Guards, HexHelper, I18n, Is, JsonHelper, NotFoundError, NotImplementedError, NotSupportedError, ObjectHelper, RandomHelper, SharedStore, StringHelper, Uint8ArrayHelper, UnauthorizedError, UnprocessableError, Url, Urn, Validation, ValidationError };