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

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