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

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 (85) hide show
  1. package/dist/cjs/index.cjs +2266 -1470
  2. package/dist/esm/index.mjs +2263 -1471
  3. package/dist/types/errors/alreadyExistsError.d.ts +2 -2
  4. package/dist/types/errors/baseError.d.ts +25 -5
  5. package/dist/types/errors/conflictError.d.ts +2 -2
  6. package/dist/types/errors/generalError.d.ts +2 -2
  7. package/dist/types/errors/notFoundError.d.ts +2 -2
  8. package/dist/types/errors/notSupportedError.d.ts +2 -2
  9. package/dist/types/errors/unauthorizedError.d.ts +2 -2
  10. package/dist/types/errors/unprocessableError.d.ts +2 -2
  11. package/dist/types/factories/factory.d.ts +21 -2
  12. package/dist/types/helpers/arrayHelper.d.ts +13 -0
  13. package/dist/types/helpers/envHelper.d.ts +16 -0
  14. package/dist/types/helpers/errorHelper.d.ts +5 -4
  15. package/dist/types/helpers/jsonHelper.d.ts +30 -0
  16. package/dist/types/helpers/objectHelper.d.ts +25 -0
  17. package/dist/types/helpers/uint8ArrayHelper.d.ts +11 -0
  18. package/dist/types/index.d.ts +6 -0
  19. package/dist/types/models/IComponent.d.ts +6 -6
  20. package/dist/types/models/IError.d.ts +2 -2
  21. package/dist/types/models/II18nShared.d.ts +29 -0
  22. package/dist/types/models/coerceType.d.ts +49 -0
  23. package/dist/types/models/compressionType.d.ts +1 -1
  24. package/dist/types/models/objectOrArray.d.ts +4 -0
  25. package/dist/types/utils/asyncCache.d.ts +10 -1
  26. package/dist/types/utils/coerce.d.ts +22 -0
  27. package/dist/types/utils/guards.d.ts +35 -0
  28. package/dist/types/utils/is.d.ts +18 -0
  29. package/dist/types/utils/sharedStore.d.ts +23 -0
  30. package/dist/types/utils/validation.d.ts +2 -0
  31. package/docs/changelog.md +442 -1
  32. package/docs/reference/classes/AlreadyExistsError.md +166 -32
  33. package/docs/reference/classes/ArrayHelper.md +71 -5
  34. package/docs/reference/classes/AsyncCache.md +75 -13
  35. package/docs/reference/classes/Base32.md +9 -5
  36. package/docs/reference/classes/Base58.md +9 -5
  37. package/docs/reference/classes/Base64.md +12 -6
  38. package/docs/reference/classes/Base64Url.md +9 -5
  39. package/docs/reference/classes/BaseError.md +160 -34
  40. package/docs/reference/classes/BitString.md +23 -11
  41. package/docs/reference/classes/Coerce.md +110 -12
  42. package/docs/reference/classes/Compression.md +19 -11
  43. package/docs/reference/classes/ConflictError.md +169 -33
  44. package/docs/reference/classes/Converter.md +72 -28
  45. package/docs/reference/classes/EnvHelper.md +45 -0
  46. package/docs/reference/classes/ErrorHelper.md +22 -10
  47. package/docs/reference/classes/Factory.md +96 -18
  48. package/docs/reference/classes/FilenameHelper.md +6 -4
  49. package/docs/reference/classes/GeneralError.md +164 -32
  50. package/docs/reference/classes/GuardError.md +168 -32
  51. package/docs/reference/classes/Guards.md +398 -80
  52. package/docs/reference/classes/HexHelper.md +18 -8
  53. package/docs/reference/classes/I18n.md +46 -20
  54. package/docs/reference/classes/Is.md +207 -51
  55. package/docs/reference/classes/JsonHelper.md +146 -10
  56. package/docs/reference/classes/NotFoundError.md +166 -32
  57. package/docs/reference/classes/NotImplementedError.md +159 -29
  58. package/docs/reference/classes/NotSupportedError.md +163 -31
  59. package/docs/reference/classes/ObjectHelper.md +197 -39
  60. package/docs/reference/classes/RandomHelper.md +6 -4
  61. package/docs/reference/classes/SharedStore.md +94 -0
  62. package/docs/reference/classes/StringHelper.md +54 -20
  63. package/docs/reference/classes/Uint8ArrayHelper.md +35 -0
  64. package/docs/reference/classes/UnauthorizedError.md +163 -31
  65. package/docs/reference/classes/UnprocessableError.md +164 -32
  66. package/docs/reference/classes/Url.md +37 -17
  67. package/docs/reference/classes/Urn.md +63 -27
  68. package/docs/reference/classes/Validation.md +349 -135
  69. package/docs/reference/classes/ValidationError.md +162 -30
  70. package/docs/reference/index.md +7 -0
  71. package/docs/reference/interfaces/IComponent.md +21 -11
  72. package/docs/reference/interfaces/IError.md +4 -4
  73. package/docs/reference/interfaces/II18nShared.md +47 -0
  74. package/docs/reference/interfaces/IKeyValue.md +3 -1
  75. package/docs/reference/interfaces/ILabelledValue.md +3 -1
  76. package/docs/reference/interfaces/ILocaleDictionary.md +1 -1
  77. package/docs/reference/interfaces/IPatchOperation.md +1 -1
  78. package/docs/reference/interfaces/IValidationFailure.md +1 -1
  79. package/docs/reference/type-aliases/CoerceType.md +5 -0
  80. package/docs/reference/type-aliases/CompressionType.md +1 -1
  81. package/docs/reference/type-aliases/ObjectOrArray.md +11 -0
  82. package/docs/reference/variables/CoerceType.md +67 -0
  83. package/docs/reference/variables/CompressionType.md +2 -2
  84. package/locales/en.json +14 -1
  85. package/package.json +7 -7
@@ -1,5 +1,5 @@
1
- import { IntlMessageFormat } from 'intl-messageformat';
2
1
  import { createPatch, applyPatch } from 'rfc6902';
2
+ import { IntlMessageFormat } from 'intl-messageformat';
3
3
 
4
4
  // Copyright 2024 IOTA Stiftung.
5
5
  // SPDX-License-Identifier: Apache-2.0.
@@ -111,7 +111,12 @@ class Is {
111
111
  }
112
112
  try {
113
113
  const json = JSON.parse(value);
114
- return typeof json === "object";
114
+ return (Is.object(json) ||
115
+ Is.array(json) ||
116
+ Is.string(json) ||
117
+ Is.number(json) ||
118
+ Is.boolean(json) ||
119
+ Is.null(json));
115
120
  }
116
121
  catch {
117
122
  return false;
@@ -135,7 +140,17 @@ class Is {
135
140
  static stringBase64Url(value) {
136
141
  return (Is.stringValue(value) &&
137
142
  // eslint-disable-next-line unicorn/better-regex
138
- /^(?:[A-Za-z0-9-_]{4})*(?:[A-Za-z0-9-_]{2}==|[A-Za-z0-9-_]{3}=)?$/.test(value));
143
+ /^([A-Za-z0-9-_])*$/.test(value));
144
+ }
145
+ /**
146
+ * Is the value a base58 string.
147
+ * @param value The value to test.
148
+ * @returns True if the value is a base58 string.
149
+ */
150
+ static stringBase58(value) {
151
+ return (Is.stringValue(value) &&
152
+ // eslint-disable-next-line unicorn/better-regex
153
+ /^[A-HJ-NP-Za-km-z1-9]*$/.test(value));
139
154
  }
140
155
  /**
141
156
  * Is the value a hex string.
@@ -349,6 +364,27 @@ class Is {
349
364
  static promise(value) {
350
365
  return value instanceof Promise;
351
366
  }
367
+ /**
368
+ * Is the value a regexp.
369
+ * @param value The value to test.
370
+ * @returns True if the value is a regexp.
371
+ */
372
+ static regexp(value) {
373
+ return value instanceof RegExp;
374
+ }
375
+ /**
376
+ * Is the provided object a class constructor.
377
+ * @param obj The object to check.
378
+ * @returns True if the object is a class, false otherwise.
379
+ */
380
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
381
+ static class(obj) {
382
+ if (typeof obj !== "function") {
383
+ return false;
384
+ }
385
+ const str = Function.prototype.toString.call(obj);
386
+ return /^class\s/.test(str);
387
+ }
352
388
  }
353
389
 
354
390
  // Copyright 2024 IOTA Stiftung.
@@ -606,31 +642,33 @@ class BaseError extends Error {
606
642
  */
607
643
  properties;
608
644
  /**
609
- * The inner error if there was one.
645
+ * The cause of the error.
610
646
  */
611
- inner;
647
+ cause;
612
648
  /**
613
649
  * Create a new instance of BaseError.
614
650
  * @param name The name of the error.
615
651
  * @param source The source of the error.
616
652
  * @param message The message as a code.
617
653
  * @param properties Any additional information for the error.
618
- * @param inner The inner error if we have wrapped another error.
654
+ * @param cause The cause of error if we have wrapped another error.
619
655
  */
620
- constructor(name, source, message, properties, inner) {
656
+ constructor(name, source, message, properties, cause) {
621
657
  super(message);
622
658
  this.name = name;
623
659
  this.source = source;
660
+ this.cause = Is.notEmpty(cause) ? BaseError.fromError(cause).toJsonObject(true) : undefined;
661
+ this.properties = properties;
624
662
  // If the message is camel case but has no namespace then prefix it
625
- // with the source name in camel case
663
+ // with the source name in camel case.
626
664
  if (Is.stringValue(source) &&
627
665
  Is.stringValue(message) &&
628
666
  !message.includes(".") &&
667
+ // This comparison checks that it is most likely a camel case name
668
+ // and not a free text error with a dot in it
629
669
  StringHelper.camelCase(message) === message) {
630
670
  this.message = `${StringHelper.camelCase(source)}.${message}`;
631
671
  }
632
- this.properties = properties;
633
- this.inner = inner ? BaseError.fromError(inner).toJsonObject() : undefined;
634
672
  }
635
673
  /**
636
674
  * Construct an error from an existing one.
@@ -642,9 +680,12 @@ class BaseError extends Error {
642
680
  let message;
643
681
  let source;
644
682
  let properties;
645
- let inner;
683
+ let cause;
646
684
  let stack;
647
- if (Is.object(err)) {
685
+ if (Is.object(err) && Is.stringValue(err.error)) {
686
+ message = err.error;
687
+ }
688
+ else if (Is.object(err)) {
648
689
  if (Is.stringValue(err.name)) {
649
690
  name = err.name;
650
691
  }
@@ -657,23 +698,24 @@ class BaseError extends Error {
657
698
  if (Is.notEmpty(err.properties)) {
658
699
  properties = err.properties;
659
700
  }
660
- if (Is.notEmpty(err.inner)) {
661
- inner = err.inner;
701
+ if (BaseError.isAggregateError(err)) {
702
+ properties ??= {};
703
+ properties.errors = err.errors;
704
+ }
705
+ if (Is.notEmpty(err.cause)) {
706
+ cause = err.cause;
662
707
  }
663
708
  if (Is.notEmpty(err.stack)) {
664
709
  stack = err.stack;
665
710
  }
666
711
  }
667
- else if (Is.object(err) && Is.stringValue(err.error)) {
668
- message = err.error;
669
- }
670
712
  else if (Is.stringValue(err)) {
671
713
  message = err;
672
714
  }
673
715
  else {
674
716
  message = JSON.stringify(err);
675
717
  }
676
- const baseError = new BaseError(name, source ?? "", message ?? "", properties, inner);
718
+ const baseError = new BaseError(name, source ?? "", message ?? "", properties, cause);
677
719
  baseError.stack = stack;
678
720
  return baseError;
679
721
  }
@@ -684,12 +726,12 @@ class BaseError extends Error {
684
726
  */
685
727
  static flatten(err) {
686
728
  const flattened = [];
687
- let e = BaseError.fromError(err).toJsonObject();
729
+ let e = BaseError.fromError(err).toJsonObject(true);
688
730
  while (e) {
689
- const inner = e.inner;
690
- e.inner = undefined;
731
+ const cause = e.cause;
732
+ e.cause = undefined;
691
733
  flattened.push(e);
692
- e = inner;
734
+ e = cause;
693
735
  }
694
736
  return flattened;
695
737
  }
@@ -704,8 +746,8 @@ class BaseError extends Error {
704
746
  first = errors[0];
705
747
  let current = first;
706
748
  for (let i = 1; i < errors.length; i++) {
707
- current.inner = errors[i];
708
- current = current.inner;
749
+ current.cause = errors[i];
750
+ current = current.cause;
709
751
  }
710
752
  }
711
753
  return first;
@@ -778,11 +820,43 @@ class BaseError extends Error {
778
820
  static someErrorCode(error, code) {
779
821
  return BaseError.flatten(error).some(e => BaseError.isErrorCode(e, code));
780
822
  }
823
+ /**
824
+ * Is the error empty, i.e. does it have no message, source, properties, or cause?
825
+ * @param err The error to check for being empty.
826
+ * @returns True if the error is empty.
827
+ */
828
+ static isEmpty(err) {
829
+ return (!Is.stringValue(err.message) &&
830
+ !Is.stringValue(err.source) &&
831
+ !Is.objectValue(err.properties) &&
832
+ Is.empty(err.cause));
833
+ }
834
+ /**
835
+ * Is the error an aggregate error.
836
+ * @param err The error to check for being an aggregate error.
837
+ * @returns True if the error is an aggregate error.
838
+ */
839
+ static isAggregateError(err) {
840
+ return err instanceof AggregateError;
841
+ }
842
+ /**
843
+ * Convert the aggregate error to an array of errors.
844
+ * @param err The error to convert.
845
+ * @param includeStackTrace Whether to include the error stack in the model, defaults to false.
846
+ * @returns The array of errors.
847
+ */
848
+ static fromAggregate(err, includeStackTrace) {
849
+ if (BaseError.isAggregateError(err)) {
850
+ return err.errors.map(e => BaseError.fromError(e).toJsonObject(includeStackTrace));
851
+ }
852
+ return [BaseError.fromError(err).toJsonObject(includeStackTrace)];
853
+ }
781
854
  /**
782
855
  * Serialize the error to the error model.
856
+ * @param includeStackTrace Whether to include the error stack in the model, defaults to false.
783
857
  * @returns The error model.
784
858
  */
785
- toJsonObject() {
859
+ toJsonObject(includeStackTrace) {
786
860
  const err = {};
787
861
  if (Is.stringValue(this.name)) {
788
862
  err.name = this.name;
@@ -796,11 +870,11 @@ class BaseError extends Error {
796
870
  if (Is.object(this.properties)) {
797
871
  err.properties = this.properties;
798
872
  }
799
- if (Is.stringValue(this.stack)) {
873
+ if ((includeStackTrace ?? false) && Is.stringValue(this.stack)) {
800
874
  err.stack = this.stack;
801
875
  }
802
- if (Is.notEmpty(this.inner)) {
803
- err.inner = BaseError.fromError(this.inner).toJsonObject();
876
+ if (Is.notEmpty(this.cause)) {
877
+ err.cause = BaseError.fromError(this.cause).toJsonObject(includeStackTrace);
804
878
  }
805
879
  return err;
806
880
  }
@@ -819,176 +893,614 @@ class GeneralError extends BaseError {
819
893
  * @param source The source of the error.
820
894
  * @param message The message as a code.
821
895
  * @param properties Any additional information for the error.
822
- * @param inner The inner error if we have wrapped another error.
896
+ * @param cause The cause of the error if we have wrapped another error.
823
897
  */
824
- constructor(source, message, properties, inner) {
825
- super(GeneralError.CLASS_NAME, source, message, properties, inner);
898
+ constructor(source, message, properties, cause) {
899
+ super(GeneralError.CLASS_NAME, source, message, properties, cause);
826
900
  }
827
901
  }
828
902
 
829
- // Copyright 2024 IOTA Stiftung.
830
- // SPDX-License-Identifier: Apache-2.0.
831
- /* eslint-disable no-bitwise */
832
903
  /**
833
- * Class to help with base63 Encoding/Decoding.
904
+ * Class to handle errors which are triggered by data guards.
834
905
  */
835
- class Base32 {
906
+ class GuardError extends BaseError {
836
907
  /**
837
908
  * Runtime name for the class.
838
- * @internal
839
909
  */
840
- static _CLASS_NAME = "Base32";
910
+ static CLASS_NAME = "GuardError";
841
911
  /**
842
- * Alphabet table for encoding.
843
- * @internal
912
+ * Create a new instance of GuardError.
913
+ * @param source The source of the error.
914
+ * @param message The message as a code.
915
+ * @param propertyName The property which triggered the guard error for the item.
916
+ * @param propertyValue The property value which triggered the guard error for the item.
917
+ * @param propertyOptions The property options which might be allowed.
844
918
  */
845
- static _ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
919
+ constructor(source, message, propertyName, propertyValue, propertyOptions) {
920
+ super(GuardError.CLASS_NAME, source, message, {
921
+ property: propertyName ?? "property",
922
+ value: Is.undefined(propertyValue) ? "undefined" : propertyValue,
923
+ options: propertyOptions
924
+ });
925
+ }
926
+ }
927
+
928
+ /**
929
+ * Class to help with arrays.
930
+ */
931
+ class ArrayHelper {
846
932
  /**
847
- * Convert the base 32 string to a byte array.
848
- * @param base32 The base32 string to convert.
849
- * @returns The byte array.
850
- * @throws If the input string contains a character not in the Base32 alphabet.
933
+ * Do the two arrays match.
934
+ * @param arr1 The first array.
935
+ * @param arr2 The second array.
936
+ * @returns True if both arrays are empty of have the same values.
851
937
  */
852
- static decode(base32) {
853
- let bits = 0;
854
- let value = 0;
855
- base32 = base32.replace(/=+$/, "");
856
- let index = 0;
857
- const output = new Uint8Array(Math.trunc((base32.length * 5) / 8));
858
- for (let i = 0; i < base32.length; i++) {
859
- const idx = Base32._ALPHABET.indexOf(base32[i]);
860
- if (idx === -1) {
861
- throw new GeneralError(Base32._CLASS_NAME, "invalidCharacter", {
862
- invalidCharacter: base32[i]
863
- });
864
- }
865
- value = (value << 5) | idx;
866
- bits += 5;
867
- if (bits >= 8) {
868
- output[index++] = (value >>> (bits - 8)) & 255;
869
- bits -= 8;
938
+ static matches(arr1, arr2) {
939
+ if (Is.empty(arr1) && Is.empty(arr2)) {
940
+ return true;
941
+ }
942
+ if (!((Is.array(arr1) && Is.array(arr2)) || (Is.typedArray(arr1) && Is.typedArray(arr2)))) {
943
+ return false;
944
+ }
945
+ if (arr1.length !== arr2.length) {
946
+ return false;
947
+ }
948
+ for (let i = 0; i < arr1.length; i++) {
949
+ if (arr1[i] !== arr2[i]) {
950
+ return false;
870
951
  }
871
952
  }
872
- return output;
953
+ return true;
873
954
  }
874
955
  /**
875
- * Convert a byte array to base 32.
876
- * @param bytes The byte array to convert.
877
- * @returns The data as base32 string.
956
+ * Convert an object or array to an array.
957
+ * @param value The object or array to convert.
958
+ * @returns The array.
878
959
  */
879
- static encode(bytes) {
880
- let bits = 0;
881
- let value = 0;
882
- let output = "";
883
- for (let i = 0; i < bytes.byteLength; i++) {
884
- value = (value << 8) | bytes[i];
885
- bits += 8;
886
- while (bits >= 5) {
887
- output += Base32._ALPHABET[(value >>> (bits - 5)) & 31];
888
- bits -= 5;
889
- }
890
- }
891
- if (bits > 0) {
892
- output += Base32._ALPHABET[(value << (5 - bits)) & 31];
960
+ static fromObjectOrArray(value) {
961
+ if (Is.empty(value)) {
962
+ return undefined;
893
963
  }
894
- while (output.length % 8 !== 0) {
895
- output += "=";
964
+ if (Is.array(value)) {
965
+ return value;
896
966
  }
897
- return output;
967
+ return [value];
898
968
  }
899
969
  }
900
970
 
971
+ // Copyright 2024 IOTA Stiftung.
972
+ // SPDX-License-Identifier: Apache-2.0.
901
973
  /**
902
- * Class to help with base58 Encoding/Decoding.
974
+ * Class to handle guard operations for parameters.
903
975
  */
904
- class Base58 {
905
- /**
906
- * Runtime name for the class.
907
- * @internal
908
- */
909
- static _CLASS_NAME = "Base58";
976
+ class Guards {
910
977
  /**
911
- * Alphabet table for encoding.
912
- * @internal
978
+ * Is the property defined.
979
+ * @param source The source of the error.
980
+ * @param property The name of the property.
981
+ * @param value The value to test.
982
+ * @throws GuardError If the value does not match the assertion.
913
983
  */
914
- static _ALPHABET =
915
- // cspell:disable-next-line
916
- "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
984
+ static defined(source, property, value) {
985
+ if (Is.undefined(value)) {
986
+ throw new GuardError(source, "guard.undefined", property, value);
987
+ }
988
+ }
917
989
  /**
918
- * Reverse map for decoding.
919
- * @internal
990
+ * Is the property a string.
991
+ * @param source The source of the error.
992
+ * @param property The name of the property.
993
+ * @param value The value to test.
994
+ * @throws GuardError If the value does not match the assertion.
920
995
  */
921
- static _ALPHABET_REVERSE = [
922
- -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
923
- -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
924
- -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, -1, -1, -1, -1, -1, -1, -1, 9, 10, 11, 12, 13, 14, 15, 16, -1,
925
- 17, 18, 19, 20, 21, -1, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, -1, -1, -1, -1, -1, -1, 33,
926
- 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, -1, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56,
927
- 57, -1, -1, -1, -1, -1
928
- ];
996
+ static string(source, property, value) {
997
+ if (!Is.string(value)) {
998
+ throw new GuardError(source, "guard.string", property, value);
999
+ }
1000
+ }
929
1001
  /**
930
- * Convert the base 58 string to a byte array.
931
- * @param base58 The base58 string to convert.
932
- * @returns The byte array.
933
- * @throws If the input string contains a character not in the Base58 alphabet.
1002
+ * Is the property a string with a value.
1003
+ * @param source The source of the error.
1004
+ * @param property The name of the property.
1005
+ * @param value The value to test.
1006
+ * @throws GuardError If the value does not match the assertion.
934
1007
  */
935
- static decode(base58) {
936
- let zeroes = 0;
937
- for (let i = 0; i < base58.length; i++) {
938
- if (base58[i] !== "1") {
939
- break;
940
- }
941
- zeroes += 1;
942
- }
943
- const size = Math.trunc((base58.length * 733) / 1000) + 1;
944
- const b256 = new Uint8Array(size).fill(0);
945
- let length = 0;
946
- for (let i = zeroes; i < base58.length; i++) {
947
- const ch = base58.charCodeAt(i);
948
- if (ch & 0xff80) {
949
- throw new GeneralError(Base58._CLASS_NAME, "invalidCharacter", { invalidCharacter: ch });
950
- }
951
- const val = Base58._ALPHABET_REVERSE[ch];
952
- if (val === -1) {
953
- throw new GeneralError(Base58._CLASS_NAME, "invalidCharacter", { invalidCharacter: ch });
954
- }
955
- let carry = val;
956
- let j = 0;
957
- for (let k = size - 1; k >= 0; k--, j++) {
958
- if (carry === 0 && j >= length) {
959
- break;
960
- }
961
- carry += b256[k] * 58;
962
- b256[k] = carry;
963
- carry >>>= 8;
964
- }
965
- length = j;
1008
+ static stringValue(source, property, value) {
1009
+ if (!Is.string(value)) {
1010
+ throw new GuardError(source, "guard.string", property, value);
966
1011
  }
967
- const out = new Uint8Array(zeroes + length);
968
- let j;
969
- for (j = 0; j < zeroes; j++) {
970
- out[j] = 0;
1012
+ if (value.length === 0) {
1013
+ throw new GuardError(source, "guard.stringEmpty", property, value);
971
1014
  }
972
- let i = size - length;
973
- while (i < size) {
974
- out[j++] = b256[i++];
1015
+ }
1016
+ /**
1017
+ * Is the property a JSON value.
1018
+ * @param source The source of the error.
1019
+ * @param property The name of the property.
1020
+ * @param value The value to test.
1021
+ * @throws GuardError If the value does not match the assertion.
1022
+ */
1023
+ static json(source, property, value) {
1024
+ if (!Is.json(value)) {
1025
+ throw new GuardError(source, "guard.stringJson", property, value);
975
1026
  }
976
- return out;
977
1027
  }
978
1028
  /**
979
- * Convert a byte array to base 58.
980
- * @param bytes The byte array to encode.
981
- * @returns The data as base58 string.
1029
+ * Is the property a base64 string.
1030
+ * @param source The source of the error.
1031
+ * @param property The name of the property.
1032
+ * @param value The value to test.
1033
+ * @throws GuardError If the value does not match the assertion.
982
1034
  */
983
- static encode(bytes) {
984
- let zeroes = 0;
985
- for (let i = 0; i < bytes.length; i++) {
986
- if (bytes[i] !== 0) {
987
- break;
988
- }
989
- zeroes += 1;
1035
+ static stringBase64(source, property, value) {
1036
+ if (!Is.stringBase64(value)) {
1037
+ throw new GuardError(source, "guard.base64", property, value);
990
1038
  }
991
- const size = Math.trunc(((bytes.length - zeroes) * 138) / 100) + 1;
1039
+ }
1040
+ /**
1041
+ * Is the property a base64 url string.
1042
+ * @param source The source of the error.
1043
+ * @param property The name of the property.
1044
+ * @param value The value to test.
1045
+ * @throws GuardError If the value does not match the assertion.
1046
+ */
1047
+ static stringBase64Url(source, property, value) {
1048
+ if (!Is.stringBase64Url(value)) {
1049
+ throw new GuardError(source, "guard.base64Url", property, value);
1050
+ }
1051
+ }
1052
+ /**
1053
+ * Is the property a base58 string.
1054
+ * @param source The source of the error.
1055
+ * @param property The name of the property.
1056
+ * @param value The value to test.
1057
+ * @throws GuardError If the value does not match the assertion.
1058
+ */
1059
+ static stringBase58(source, property, value) {
1060
+ if (!Is.stringBase58(value)) {
1061
+ throw new GuardError(source, "guard.base58", property, value);
1062
+ }
1063
+ }
1064
+ /**
1065
+ * Is the property a string with a hex value.
1066
+ * @param source The source of the error.
1067
+ * @param property The name of the property.
1068
+ * @param value The value to test.
1069
+ * @param allowPrefix Allow the hex to have the 0x prefix.
1070
+ * @throws GuardError If the value does not match the assertion.
1071
+ */
1072
+ static stringHex(source, property, value, allowPrefix = false) {
1073
+ Guards.stringValue(source, property, value);
1074
+ if (!HexHelper.isHex(value, allowPrefix)) {
1075
+ throw new GuardError(source, "guard.stringHex", property, value);
1076
+ }
1077
+ }
1078
+ /**
1079
+ * Is the property a string with a hex value with fixed length.
1080
+ * @param source The source of the error.
1081
+ * @param property The name of the property.
1082
+ * @param value The value to test.
1083
+ * @param length The length of the string to match.
1084
+ * @param allowPrefix Allow the hex to have the 0x prefix.
1085
+ * @throws GuardError If the value does not match the assertion.
1086
+ */
1087
+ static stringHexLength(source, property, value, length, allowPrefix = false) {
1088
+ Guards.stringHex(source, property, value, allowPrefix);
1089
+ if (HexHelper.stripPrefix(value).length !== length) {
1090
+ throw new GuardError(source, "guard.stringHexLength", property, value.length, length.toString());
1091
+ }
1092
+ }
1093
+ /**
1094
+ * Is the property a number.
1095
+ * @param source The source of the error.
1096
+ * @param property The name of the property.
1097
+ * @param value The value to test.
1098
+ * @throws GuardError If the value does not match the assertion.
1099
+ */
1100
+ static number(source, property, value) {
1101
+ if (!Is.number(value)) {
1102
+ throw new GuardError(source, "guard.number", property, value);
1103
+ }
1104
+ }
1105
+ /**
1106
+ * Is the property an integer.
1107
+ * @param source The source of the error.
1108
+ * @param property The name of the property.
1109
+ * @param value The value to test.
1110
+ * @throws GuardError If the value does not match the assertion.
1111
+ */
1112
+ static integer(source, property, value) {
1113
+ if (!Is.integer(value)) {
1114
+ throw new GuardError(source, "guard.integer", property, value);
1115
+ }
1116
+ }
1117
+ /**
1118
+ * Is the property a bigint.
1119
+ * @param source The source of the error.
1120
+ * @param property The name of the property.
1121
+ * @param value The value to test.
1122
+ * @throws GuardError If the value does not match the assertion.
1123
+ */
1124
+ static bigint(source, property, value) {
1125
+ if (!Is.bigint(value)) {
1126
+ throw new GuardError(source, "guard.bigint", property, value);
1127
+ }
1128
+ }
1129
+ /**
1130
+ * Is the property a boolean.
1131
+ * @param source The source of the error.
1132
+ * @param property The name of the property.
1133
+ * @param value The value to test.
1134
+ * @throws GuardError If the value does not match the assertion.
1135
+ */
1136
+ static boolean(source, property, value) {
1137
+ if (!Is.boolean(value)) {
1138
+ throw new GuardError(source, "guard.boolean", property, value);
1139
+ }
1140
+ }
1141
+ /**
1142
+ * Is the property a date.
1143
+ * @param source The source of the error.
1144
+ * @param property The name of the property.
1145
+ * @param value The value to test.
1146
+ * @throws GuardError If the value does not match the assertion.
1147
+ */
1148
+ static date(source, property, value) {
1149
+ if (!Is.date(value)) {
1150
+ throw new GuardError(source, "guard.date", property, value);
1151
+ }
1152
+ }
1153
+ /**
1154
+ * Is the property a timestamp in milliseconds.
1155
+ * @param source The source of the error.
1156
+ * @param property The name of the property.
1157
+ * @param value The value to test.
1158
+ * @throws GuardError If the value does not match the assertion.
1159
+ */
1160
+ static timestampMilliseconds(source, property, value) {
1161
+ if (!Is.timestampMilliseconds(value)) {
1162
+ throw new GuardError(source, "guard.timestampMilliseconds", property, value);
1163
+ }
1164
+ }
1165
+ /**
1166
+ * Is the property a timestamp in seconds.
1167
+ * @param source The source of the error.
1168
+ * @param property The name of the property.
1169
+ * @param value The value to test.
1170
+ * @throws GuardError If the value does not match the assertion.
1171
+ */
1172
+ static timestampSeconds(source, property, value) {
1173
+ if (!Is.timestampSeconds(value)) {
1174
+ throw new GuardError(source, "guard.timestampSeconds", property, value);
1175
+ }
1176
+ }
1177
+ /**
1178
+ * Is the property an object.
1179
+ * @param source The source of the error.
1180
+ * @param property The name of the property.
1181
+ * @param value The value to test.
1182
+ * @throws GuardError If the value does not match the assertion.
1183
+ */
1184
+ static object(source, property, value) {
1185
+ if (Is.undefined(value)) {
1186
+ throw new GuardError(source, "guard.objectUndefined", property, value);
1187
+ }
1188
+ if (!Is.object(value)) {
1189
+ throw new GuardError(source, "guard.object", property, value);
1190
+ }
1191
+ }
1192
+ /**
1193
+ * Is the property is an object with at least one property.
1194
+ * @param source The source of the error.
1195
+ * @param property The name of the property.
1196
+ * @param value The value to test.
1197
+ * @throws GuardError If the value does not match the assertion.
1198
+ */
1199
+ static objectValue(source, property, value) {
1200
+ if (Is.undefined(value)) {
1201
+ throw new GuardError(source, "guard.objectUndefined", property, value);
1202
+ }
1203
+ if (!Is.object(value)) {
1204
+ throw new GuardError(source, "guard.object", property, value);
1205
+ }
1206
+ if (Object.keys(value || {}).length === 0) {
1207
+ throw new GuardError(source, "guard.objectValue", property, value);
1208
+ }
1209
+ }
1210
+ /**
1211
+ * Is the property is an array.
1212
+ * @param source The source of the error.
1213
+ * @param property The name of the property.
1214
+ * @param value The value to test.
1215
+ * @throws GuardError If the value does not match the assertion.
1216
+ */
1217
+ static array(source, property, value) {
1218
+ if (!Is.array(value)) {
1219
+ throw new GuardError(source, "guard.array", property, value);
1220
+ }
1221
+ }
1222
+ /**
1223
+ * Is the property is an array with at least one item.
1224
+ * @param source The source of the error.
1225
+ * @param property The name of the property.
1226
+ * @param value The value to test.
1227
+ * @throws GuardError If the value does not match the assertion.
1228
+ */
1229
+ static arrayValue(source, property, value) {
1230
+ if (!Is.array(value)) {
1231
+ throw new GuardError(source, "guard.array", property, value);
1232
+ }
1233
+ if (value.length === 0) {
1234
+ throw new GuardError(source, "guard.arrayValue", property, value);
1235
+ }
1236
+ }
1237
+ /**
1238
+ * Is the property one of a list of items.
1239
+ * @param source The source of the error.
1240
+ * @param property The name of the property.
1241
+ * @param value The value to test.
1242
+ * @param options The options the value must be one of.
1243
+ * @throws GuardError If the value does not match the assertion.
1244
+ */
1245
+ static arrayOneOf(source, property, value, options) {
1246
+ if (!Is.array(options)) {
1247
+ throw new GuardError(source, "guard.array", property, value);
1248
+ }
1249
+ if (!options.includes(value)) {
1250
+ throw new GuardError(source, "guard.arrayOneOf", property, value, options.join(", "));
1251
+ }
1252
+ }
1253
+ /**
1254
+ * Does the array start with the specified data.
1255
+ * @param source The source of the error.
1256
+ * @param property The name of the property.
1257
+ * @param value The value to test.
1258
+ * @param startValues The values that must start the array.
1259
+ * @throws GuardError If the value does not match the assertion.
1260
+ */
1261
+ static arrayStartsWith(source, property, value, startValues) {
1262
+ if (!Is.arrayValue(value)) {
1263
+ throw new GuardError(source, "guard.array", property, value);
1264
+ }
1265
+ const startValuesArray = ArrayHelper.fromObjectOrArray(startValues);
1266
+ if (!Is.arrayValue(startValuesArray)) {
1267
+ throw new GuardError(source, "guard.array", property, startValuesArray);
1268
+ }
1269
+ for (let i = 0; i < startValuesArray.length; i++) {
1270
+ if (value[i] !== startValuesArray[i]) {
1271
+ throw new GuardError(source, "guard.arrayStartsWith", property, value, startValuesArray.join(", "));
1272
+ }
1273
+ }
1274
+ }
1275
+ /**
1276
+ * Does the array end with the specified data.
1277
+ * @param source The source of the error.
1278
+ * @param property The name of the property.
1279
+ * @param value The value to test.
1280
+ * @param endValues The values that must end the array.
1281
+ * @throws GuardError If the value does not match the assertion.
1282
+ */
1283
+ static arrayEndsWith(source, property, value, endValues) {
1284
+ if (!Is.arrayValue(value)) {
1285
+ throw new GuardError(source, "guard.array", property, value);
1286
+ }
1287
+ const endValuesArray = ArrayHelper.fromObjectOrArray(endValues);
1288
+ if (!Is.arrayValue(endValuesArray)) {
1289
+ throw new GuardError(source, "guard.array", property, endValuesArray);
1290
+ }
1291
+ for (let i = 0; i < endValuesArray.length; i++) {
1292
+ if (value[value.length - i - 1] !== endValuesArray[endValuesArray.length - i - 1]) {
1293
+ throw new GuardError(source, "guard.arrayEndsWith", property, value, endValuesArray.join(", "));
1294
+ }
1295
+ }
1296
+ }
1297
+ /**
1298
+ * Is the property a Uint8Array.
1299
+ * @param source The source of the error.
1300
+ * @param property The name of the property.
1301
+ * @param value The value to test.
1302
+ * @throws GuardError If the value does not match the assertion.
1303
+ */
1304
+ static uint8Array(source, property, value) {
1305
+ if (!Is.uint8Array(value)) {
1306
+ throw new GuardError(source, "guard.uint8Array", property, value);
1307
+ }
1308
+ }
1309
+ /**
1310
+ * Is the property a function.
1311
+ * @param source The source of the error.
1312
+ * @param property The name of the property.
1313
+ * @param value The value to test.
1314
+ * @returns True if the value is a function.
1315
+ * @throws GuardError If the value does not match the assertion.
1316
+ */
1317
+ static function(source, property, value) {
1318
+ if (!Is.function(value)) {
1319
+ throw new GuardError(source, "guard.function", property, value);
1320
+ }
1321
+ return true;
1322
+ }
1323
+ /**
1324
+ * Is the property a string formatted as an email address.
1325
+ * @param source The source of the error.
1326
+ * @param property The name of the property.
1327
+ * @param value The value to test.
1328
+ * @throws GuardError If the value does not match the assertion.
1329
+ */
1330
+ static email(source, property, value) {
1331
+ if (!Is.email(value)) {
1332
+ throw new GuardError(source, "guard.email", property, value);
1333
+ }
1334
+ }
1335
+ }
1336
+
1337
+ // Copyright 2024 IOTA Stiftung.
1338
+ // SPDX-License-Identifier: Apache-2.0.
1339
+ /* eslint-disable no-bitwise */
1340
+ /**
1341
+ * Class to help with base63 Encoding/Decoding.
1342
+ */
1343
+ class Base32 {
1344
+ /**
1345
+ * Runtime name for the class.
1346
+ * @internal
1347
+ */
1348
+ static _CLASS_NAME = "Base32";
1349
+ /**
1350
+ * Alphabet table for encoding.
1351
+ * @internal
1352
+ */
1353
+ static _ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
1354
+ /**
1355
+ * Convert the base 32 string to a byte array.
1356
+ * @param base32 The base32 string to convert.
1357
+ * @returns The byte array.
1358
+ * @throws If the input string contains a character not in the Base32 alphabet.
1359
+ */
1360
+ static decode(base32) {
1361
+ Guards.string(Base32._CLASS_NAME, "base32", base32);
1362
+ let bits = 0;
1363
+ let value = 0;
1364
+ base32 = base32.replace(/=+$/, "");
1365
+ let index = 0;
1366
+ const output = new Uint8Array(Math.trunc((base32.length * 5) / 8));
1367
+ for (let i = 0; i < base32.length; i++) {
1368
+ const idx = Base32._ALPHABET.indexOf(base32[i]);
1369
+ if (idx === -1) {
1370
+ throw new GeneralError(Base32._CLASS_NAME, "invalidCharacter", {
1371
+ invalidCharacter: base32[i]
1372
+ });
1373
+ }
1374
+ value = (value << 5) | idx;
1375
+ bits += 5;
1376
+ if (bits >= 8) {
1377
+ output[index++] = (value >>> (bits - 8)) & 255;
1378
+ bits -= 8;
1379
+ }
1380
+ }
1381
+ return output;
1382
+ }
1383
+ /**
1384
+ * Convert a byte array to base 32.
1385
+ * @param bytes The byte array to convert.
1386
+ * @returns The data as base32 string.
1387
+ */
1388
+ static encode(bytes) {
1389
+ Guards.uint8Array(Base32._CLASS_NAME, "bytes", bytes);
1390
+ let bits = 0;
1391
+ let value = 0;
1392
+ let output = "";
1393
+ for (let i = 0; i < bytes.byteLength; i++) {
1394
+ value = (value << 8) | bytes[i];
1395
+ bits += 8;
1396
+ while (bits >= 5) {
1397
+ output += Base32._ALPHABET[(value >>> (bits - 5)) & 31];
1398
+ bits -= 5;
1399
+ }
1400
+ }
1401
+ if (bits > 0) {
1402
+ output += Base32._ALPHABET[(value << (5 - bits)) & 31];
1403
+ }
1404
+ while (output.length % 8 !== 0) {
1405
+ output += "=";
1406
+ }
1407
+ return output;
1408
+ }
1409
+ }
1410
+
1411
+ /**
1412
+ * Class to help with base58 Encoding/Decoding.
1413
+ */
1414
+ class Base58 {
1415
+ /**
1416
+ * Runtime name for the class.
1417
+ * @internal
1418
+ */
1419
+ static _CLASS_NAME = "Base58";
1420
+ /**
1421
+ * Alphabet table for encoding.
1422
+ * @internal
1423
+ */
1424
+ static _ALPHABET =
1425
+ // cspell:disable-next-line
1426
+ "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
1427
+ /**
1428
+ * Reverse map for decoding.
1429
+ * @internal
1430
+ */
1431
+ static _ALPHABET_REVERSE = [
1432
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
1433
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
1434
+ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, -1, -1, -1, -1, -1, -1, -1, 9, 10, 11, 12, 13, 14, 15, 16, -1,
1435
+ 17, 18, 19, 20, 21, -1, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, -1, -1, -1, -1, -1, -1, 33,
1436
+ 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, -1, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56,
1437
+ 57, -1, -1, -1, -1, -1
1438
+ ];
1439
+ /**
1440
+ * Convert the base 58 string to a byte array.
1441
+ * @param base58 The base58 string to convert.
1442
+ * @returns The byte array.
1443
+ * @throws If the input string contains a character not in the Base58 alphabet.
1444
+ */
1445
+ static decode(base58) {
1446
+ Guards.string(Base58._CLASS_NAME, "base58", base58);
1447
+ let zeroes = 0;
1448
+ for (let i = 0; i < base58.length; i++) {
1449
+ if (base58[i] !== "1") {
1450
+ break;
1451
+ }
1452
+ zeroes += 1;
1453
+ }
1454
+ const size = Math.trunc((base58.length * 733) / 1000) + 1;
1455
+ const b256 = new Uint8Array(size).fill(0);
1456
+ let length = 0;
1457
+ for (let i = zeroes; i < base58.length; i++) {
1458
+ const ch = base58.charCodeAt(i);
1459
+ if (ch & 0xff80) {
1460
+ throw new GeneralError(Base58._CLASS_NAME, "invalidCharacter", { invalidCharacter: ch });
1461
+ }
1462
+ const val = Base58._ALPHABET_REVERSE[ch];
1463
+ if (val === -1) {
1464
+ throw new GeneralError(Base58._CLASS_NAME, "invalidCharacter", { invalidCharacter: ch });
1465
+ }
1466
+ let carry = val;
1467
+ let j = 0;
1468
+ for (let k = size - 1; k >= 0; k--, j++) {
1469
+ if (carry === 0 && j >= length) {
1470
+ break;
1471
+ }
1472
+ carry += b256[k] * 58;
1473
+ b256[k] = carry;
1474
+ carry >>>= 8;
1475
+ }
1476
+ length = j;
1477
+ }
1478
+ const out = new Uint8Array(zeroes + length);
1479
+ let j;
1480
+ for (j = 0; j < zeroes; j++) {
1481
+ out[j] = 0;
1482
+ }
1483
+ let i = size - length;
1484
+ while (i < size) {
1485
+ out[j++] = b256[i++];
1486
+ }
1487
+ return out;
1488
+ }
1489
+ /**
1490
+ * Convert a byte array to base 58.
1491
+ * @param bytes The byte array to encode.
1492
+ * @returns The data as base58 string.
1493
+ */
1494
+ static encode(bytes) {
1495
+ Guards.uint8Array(Base58._CLASS_NAME, "bytes", bytes);
1496
+ let zeroes = 0;
1497
+ for (let i = 0; i < bytes.length; i++) {
1498
+ if (bytes[i] !== 0) {
1499
+ break;
1500
+ }
1501
+ zeroes += 1;
1502
+ }
1503
+ const size = Math.trunc(((bytes.length - zeroes) * 138) / 100) + 1;
992
1504
  const b58 = new Uint8Array(size).fill(0);
993
1505
  let length = 0;
994
1506
  for (let i = zeroes; i < bytes.length; i++) {
@@ -1125,6 +1637,7 @@ class Base64 {
1125
1637
  * @returns The byte array.
1126
1638
  */
1127
1639
  static decode(base64) {
1640
+ Guards.string(Base64._CLASS_NAME, "base64", base64);
1128
1641
  let tmp;
1129
1642
  const lens = Base64.getLengths(base64);
1130
1643
  const validLen = lens[0];
@@ -1166,6 +1679,7 @@ class Base64 {
1166
1679
  * @returns The data as base64 string.
1167
1680
  */
1168
1681
  static encode(bytes) {
1682
+ Guards.uint8Array(Base64._CLASS_NAME, "bytes", bytes);
1169
1683
  let tmp;
1170
1684
  const len = bytes.length;
1171
1685
  const extraBytes = len % 3; // if we have 1 byte left, pad 2 bytes
@@ -1173,7 +1687,7 @@ class Base64 {
1173
1687
  const maxChunkLength = 16383; // must be multiple of 3
1174
1688
  // go through the array every three bytes, we'll deal with trailing stuff later
1175
1689
  for (let i = 0, len2 = len - extraBytes; i < len2; i += maxChunkLength) {
1176
- parts.push(Base64.encodeChunk(bytes, i, i + maxChunkLength > len2 ? len2 : i + maxChunkLength));
1690
+ parts.push(Base64.encodeChunk(bytes, i, Math.min(i + maxChunkLength, len2)));
1177
1691
  }
1178
1692
  // pad the end with zeros, but make sure to not forget the extra bytes
1179
1693
  if (extraBytes === 1) {
@@ -1247,522 +1761,251 @@ class Base64 {
1247
1761
  }
1248
1762
  }
1249
1763
 
1250
- // Copyright 2024 IOTA Stiftung.
1251
- // SPDX-License-Identifier: Apache-2.0.
1252
1764
  /**
1253
1765
  * Class to help with base64 URL Encoding/Decoding.
1254
1766
  * https://www.rfc-editor.org/rfc/rfc4648#section-5.
1255
1767
  */
1256
1768
  class Base64Url {
1257
- /**
1258
- * Convert the base 64 string to a byte array.
1259
- * @param base64Url The base64 url string to convert.
1260
- * @returns The byte array.
1261
- */
1262
- static decode(base64Url) {
1263
- let base64 = base64Url;
1264
- // Base 64 url can have padding removed, so add it back if it is missing.
1265
- if (base64.length > 0 && !base64.endsWith("=")) {
1266
- const placeHoldersLen = 4 - (base64.length % 4);
1267
- if (placeHoldersLen > 0 && placeHoldersLen < 4) {
1268
- base64 = base64.padEnd(base64.length + placeHoldersLen, "=");
1269
- }
1270
- }
1271
- base64 = base64.replace(/-/g, "+").replace(/_/g, "/");
1272
- return Base64.decode(base64);
1273
- }
1274
- /**
1275
- * Convert a byte array to base 64 url.
1276
- * @param bytes The byte array to convert.
1277
- * @returns The data as base64 url string.
1278
- */
1279
- static encode(bytes) {
1280
- const base64 = Base64.encode(bytes);
1281
- // Base 64 url can have padding removed, so remove it.
1282
- return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
1283
- }
1284
- }
1285
-
1286
- /**
1287
- * Class to handle errors which are triggered by data already existing.
1288
- */
1289
- class AlreadyExistsError extends BaseError {
1290
- /**
1291
- * Runtime name for the class.
1292
- */
1293
- static CLASS_NAME = "AlreadyExistsError";
1294
- /**
1295
- * Create a new instance of AlreadyExistsError.
1296
- * @param source The source of the error.
1297
- * @param message The message as a code.
1298
- * @param existingId The id for the item.
1299
- * @param inner The inner error if we have wrapped another error.
1300
- */
1301
- constructor(source, message, existingId, inner) {
1302
- super(AlreadyExistsError.CLASS_NAME, source, message, { existingId }, inner);
1303
- }
1304
- }
1305
-
1306
- /**
1307
- * Class to handle errors which are triggered by conflicting data.
1308
- */
1309
- class ConflictError extends BaseError {
1310
- /**
1311
- * Runtime name for the class.
1312
- */
1313
- static CLASS_NAME = "ConflictError";
1314
- /**
1315
- * Create a new instance of ConflictError.
1316
- * @param source The source of the error.
1317
- * @param message The message as a code.
1318
- * @param conflictId The id that has conflicts.
1319
- * @param conflicts The conflicts that occurred.
1320
- * @param inner The inner error if we have wrapped another error.
1321
- */
1322
- constructor(source, message, conflictId, conflicts, inner) {
1323
- super(ConflictError.CLASS_NAME, source, message, { conflictId, conflicts }, inner);
1324
- }
1325
- }
1326
-
1327
- /**
1328
- * Class to handle errors which are triggered by data guards.
1329
- */
1330
- class GuardError extends BaseError {
1331
- /**
1332
- * Runtime name for the class.
1333
- */
1334
- static CLASS_NAME = "GuardError";
1335
- /**
1336
- * Create a new instance of GuardError.
1337
- * @param source The source of the error.
1338
- * @param message The message as a code.
1339
- * @param propertyName The property which triggered the guard error for the item.
1340
- * @param propertyValue The property value which triggered the guard error for the item.
1341
- * @param propertyOptions The property options which might be allowed.
1342
- */
1343
- constructor(source, message, propertyName, propertyValue, propertyOptions) {
1344
- super(GuardError.CLASS_NAME, source, message, {
1345
- property: propertyName ?? "property",
1346
- value: Is.undefined(propertyValue) ? "undefined" : propertyValue,
1347
- options: propertyOptions
1348
- });
1349
- }
1350
- }
1351
-
1352
- /**
1353
- * Class to handle errors which are triggered by data not being found.
1354
- */
1355
- class NotFoundError extends BaseError {
1356
- /**
1357
- * Runtime name for the class.
1358
- */
1359
- static CLASS_NAME = "NotFoundError";
1360
- /**
1361
- * Create a new instance of NotFoundError.
1362
- * @param source The source of the error.
1363
- * @param message The message as a code.
1364
- * @param notFoundId The id for the item.
1365
- * @param inner The inner error if we have wrapped another error.
1366
- */
1367
- constructor(source, message, notFoundId, inner) {
1368
- super(NotFoundError.CLASS_NAME, source, message, { notFoundId }, inner);
1369
- }
1370
- }
1371
-
1372
- /**
1373
- * Class to handle errors.
1374
- */
1375
- class NotImplementedError extends BaseError {
1376
- /**
1377
- * Runtime name for the class.
1378
- */
1379
- static CLASS_NAME = "NotImplementedError";
1380
- /**
1381
- * Create a new instance of NotImplementedError.
1382
- * @param source The source of the error.
1383
- * @param method The method for the error.
1384
- */
1385
- constructor(source, method) {
1386
- super(NotImplementedError.CLASS_NAME, source, "common.notImplementedMethod", {
1387
- method
1388
- });
1389
- }
1390
- }
1391
-
1392
- /**
1393
- * Class to handle errors when a feature is unsupported.
1394
- */
1395
- class NotSupportedError extends BaseError {
1396
- /**
1397
- * Runtime name for the class.
1398
- */
1399
- static CLASS_NAME = "NotSupportedError";
1400
- /**
1401
- * Create a new instance of NotSupportedError.
1402
- * @param source The source of the error.
1403
- * @param message The message as a code.
1404
- * @param inner The inner error if we have wrapped another error.
1405
- */
1406
- constructor(source, message, inner) {
1407
- super(NotSupportedError.CLASS_NAME, source, message, undefined, inner);
1408
- }
1409
- }
1410
-
1411
- /**
1412
- * Class to handle errors which are triggered by access not being unauthorized.
1413
- */
1414
- class UnauthorizedError extends BaseError {
1415
- /**
1416
- * Runtime name for the class.
1417
- */
1418
- static CLASS_NAME = "UnauthorizedError";
1419
- /**
1420
- * Create a new instance of UnauthorizedError.
1421
- * @param source The source of the error.
1422
- * @param message The message as a code.
1423
- * @param inner The inner error if we have wrapped another error.
1424
- */
1425
- constructor(source, message, inner) {
1426
- super(UnauthorizedError.CLASS_NAME, source, message, undefined, inner);
1427
- }
1428
- }
1429
-
1430
- /**
1431
- * Class to handle errors when some data can not be processed.
1432
- */
1433
- class UnprocessableError extends BaseError {
1434
1769
  /**
1435
1770
  * Runtime name for the class.
1771
+ * @internal
1436
1772
  */
1437
- static CLASS_NAME = "UnprocessableError";
1438
- /**
1439
- * Create a new instance of UnprocessableError.
1440
- * @param source The source of the error.
1441
- * @param message The message as a code.
1442
- * @param properties Any additional information for the error.
1443
- * @param inner The inner error if we have wrapped another error.
1444
- */
1445
- constructor(source, message, properties, inner) {
1446
- super(UnprocessableError.CLASS_NAME, source, message, properties, inner);
1447
- }
1448
- }
1449
-
1450
- /**
1451
- * Class to handle errors which are triggered by entity validation.
1452
- */
1453
- class ValidationError extends BaseError {
1454
- /**
1455
- * Runtime name for the class.s
1456
- */
1457
- static CLASS_NAME = "ValidationError";
1458
- /**
1459
- * Create a new instance of ValidationError.
1460
- * @param source The source of the error.
1461
- * @param validationObject The object that failed validation.
1462
- * @param validationFailures The validation failures.
1463
- */
1464
- constructor(source, validationObject, validationFailures) {
1465
- super(ValidationError.CLASS_NAME, source, "common.validation", {
1466
- validationObject,
1467
- validationFailures
1468
- });
1469
- }
1470
- }
1471
-
1472
- // Copyright 2024 IOTA Stiftung.
1473
- // SPDX-License-Identifier: Apache-2.0.
1474
- /**
1475
- * Class to handle guard operations for parameters.
1476
- */
1477
- class Guards {
1478
- /**
1479
- * Is the property defined.
1480
- * @param source The source of the error.
1481
- * @param property The name of the property.
1482
- * @param value The value to test.
1483
- * @throws GuardError If the value does not match the assertion.
1484
- */
1485
- static defined(source, property, value) {
1486
- if (Is.undefined(value)) {
1487
- throw new GuardError(source, "guard.undefined", property, value);
1488
- }
1489
- }
1490
- /**
1491
- * Is the property a string.
1492
- * @param source The source of the error.
1493
- * @param property The name of the property.
1494
- * @param value The value to test.
1495
- * @throws GuardError If the value does not match the assertion.
1496
- */
1497
- static string(source, property, value) {
1498
- if (!Is.string(value)) {
1499
- throw new GuardError(source, "guard.string", property, value);
1500
- }
1501
- }
1502
- /**
1503
- * Is the property a string with a value.
1504
- * @param source The source of the error.
1505
- * @param property The name of the property.
1506
- * @param value The value to test.
1507
- * @throws GuardError If the value does not match the assertion.
1508
- */
1509
- static stringValue(source, property, value) {
1510
- if (!Is.string(value)) {
1511
- throw new GuardError(source, "guard.string", property, value);
1512
- }
1513
- if (value.length === 0) {
1514
- throw new GuardError(source, "guard.stringEmpty", property, value);
1515
- }
1516
- }
1773
+ static _CLASS_NAME = "Base64";
1517
1774
  /**
1518
- * Is the property a base64 string.
1519
- * @param source The source of the error.
1520
- * @param property The name of the property.
1521
- * @param value The value to test.
1522
- * @throws GuardError If the value does not match the assertion.
1775
+ * Convert the base 64 string to a byte array.
1776
+ * @param base64Url The base64 url string to convert.
1777
+ * @returns The byte array.
1523
1778
  */
1524
- static stringBase64(source, property, value) {
1525
- if (!Is.stringBase64(value)) {
1526
- throw new GuardError(source, "guard.base64", property, value);
1779
+ static decode(base64Url) {
1780
+ Guards.string(Base64Url._CLASS_NAME, "base64Url", base64Url);
1781
+ let base64 = base64Url;
1782
+ // Base 64 url can have padding removed, so add it back if it is missing.
1783
+ if (base64.length > 0 && !base64.endsWith("=")) {
1784
+ const placeHoldersLen = 4 - (base64.length % 4);
1785
+ if (placeHoldersLen > 0 && placeHoldersLen < 4) {
1786
+ base64 = base64.padEnd(base64.length + placeHoldersLen, "=");
1787
+ }
1527
1788
  }
1789
+ base64 = base64.replace(/-/g, "+").replace(/_/g, "/");
1790
+ return Base64.decode(base64);
1528
1791
  }
1529
1792
  /**
1530
- * Is the property a base64 url string.
1531
- * @param source The source of the error.
1532
- * @param property The name of the property.
1533
- * @param value The value to test.
1534
- * @throws GuardError If the value does not match the assertion.
1793
+ * Convert a byte array to base 64 url.
1794
+ * @param bytes The byte array to convert.
1795
+ * @returns The data as base64 url string.
1535
1796
  */
1536
- static stringBase64Url(source, property, value) {
1537
- if (!Is.stringBase64Url(value)) {
1538
- throw new GuardError(source, "guard.base64Url", property, value);
1539
- }
1797
+ static encode(bytes) {
1798
+ Guards.uint8Array(Base64Url._CLASS_NAME, "bytes", bytes);
1799
+ const base64 = Base64.encode(bytes);
1800
+ // Base 64 url can have padding removed, so remove it.
1801
+ return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
1540
1802
  }
1803
+ }
1804
+
1805
+ /**
1806
+ * Class to handle errors which are triggered by data already existing.
1807
+ */
1808
+ class AlreadyExistsError extends BaseError {
1541
1809
  /**
1542
- * Is the property a string with a hex value.
1543
- * @param source The source of the error.
1544
- * @param property The name of the property.
1545
- * @param value The value to test.
1546
- * @param allowPrefix Allow the hex to have the 0x prefix.
1547
- * @throws GuardError If the value does not match the assertion.
1810
+ * Runtime name for the class.
1548
1811
  */
1549
- static stringHex(source, property, value, allowPrefix = false) {
1550
- Guards.stringValue(source, property, value);
1551
- if (!HexHelper.isHex(value, allowPrefix)) {
1552
- throw new GuardError(source, "guard.stringHex", property, value);
1553
- }
1554
- }
1812
+ static CLASS_NAME = "AlreadyExistsError";
1555
1813
  /**
1556
- * Is the property a string with a hex value with fixed length.
1814
+ * Create a new instance of AlreadyExistsError.
1557
1815
  * @param source The source of the error.
1558
- * @param property The name of the property.
1559
- * @param value The value to test.
1560
- * @param length The length of the string to match.
1561
- * @param allowPrefix Allow the hex to have the 0x prefix.
1562
- * @throws GuardError If the value does not match the assertion.
1816
+ * @param message The message as a code.
1817
+ * @param existingId The id for the item.
1818
+ * @param cause The cause of the error if we have wrapped another error.
1563
1819
  */
1564
- static stringHexLength(source, property, value, length, allowPrefix = false) {
1565
- Guards.stringHex(source, property, value, allowPrefix);
1566
- if (HexHelper.stripPrefix(value).length !== length) {
1567
- throw new GuardError(source, "guard.stringHexLength", property, value.length, length.toString());
1568
- }
1820
+ constructor(source, message, existingId, cause) {
1821
+ super(AlreadyExistsError.CLASS_NAME, source, message, { existingId });
1569
1822
  }
1823
+ }
1824
+
1825
+ /**
1826
+ * Class to handle errors which are triggered by conflicting data.
1827
+ */
1828
+ class ConflictError extends BaseError {
1570
1829
  /**
1571
- * Is the property a number.
1572
- * @param source The source of the error.
1573
- * @param property The name of the property.
1574
- * @param value The value to test.
1575
- * @throws GuardError If the value does not match the assertion.
1830
+ * Runtime name for the class.
1576
1831
  */
1577
- static number(source, property, value) {
1578
- if (!Is.number(value)) {
1579
- throw new GuardError(source, "guard.number", property, value);
1580
- }
1581
- }
1832
+ static CLASS_NAME = "ConflictError";
1582
1833
  /**
1583
- * Is the property an integer.
1834
+ * Create a new instance of ConflictError.
1584
1835
  * @param source The source of the error.
1585
- * @param property The name of the property.
1586
- * @param value The value to test.
1587
- * @throws GuardError If the value does not match the assertion.
1836
+ * @param message The message as a code.
1837
+ * @param conflictId The id that has conflicts.
1838
+ * @param conflicts The conflicts that occurred.
1839
+ * @param cause The cause or the error if we have wrapped another error.
1588
1840
  */
1589
- static integer(source, property, value) {
1590
- if (!Is.integer(value)) {
1591
- throw new GuardError(source, "guard.integer", property, value);
1592
- }
1841
+ constructor(source, message, conflictId, conflicts, cause) {
1842
+ super(ConflictError.CLASS_NAME, source, message, { conflictId, conflicts }, cause);
1593
1843
  }
1844
+ }
1845
+
1846
+ /**
1847
+ * Class to handle errors which are triggered by data not being found.
1848
+ */
1849
+ class NotFoundError extends BaseError {
1594
1850
  /**
1595
- * Is the property a bigint.
1596
- * @param source The source of the error.
1597
- * @param property The name of the property.
1598
- * @param value The value to test.
1599
- * @throws GuardError If the value does not match the assertion.
1851
+ * Runtime name for the class.
1600
1852
  */
1601
- static bigint(source, property, value) {
1602
- if (!Is.bigint(value)) {
1603
- throw new GuardError(source, "guard.bigint", property, value);
1604
- }
1605
- }
1853
+ static CLASS_NAME = "NotFoundError";
1606
1854
  /**
1607
- * Is the property a boolean.
1855
+ * Create a new instance of NotFoundError.
1608
1856
  * @param source The source of the error.
1609
- * @param property The name of the property.
1610
- * @param value The value to test.
1611
- * @throws GuardError If the value does not match the assertion.
1857
+ * @param message The message as a code.
1858
+ * @param notFoundId The id for the item.
1859
+ * @param cause The cause of the error if we have wrapped another error.
1612
1860
  */
1613
- static boolean(source, property, value) {
1614
- if (!Is.boolean(value)) {
1615
- throw new GuardError(source, "guard.boolean", property, value);
1616
- }
1861
+ constructor(source, message, notFoundId, cause) {
1862
+ super(NotFoundError.CLASS_NAME, source, message, { notFoundId }, cause);
1617
1863
  }
1864
+ }
1865
+
1866
+ /**
1867
+ * Class to handle errors.
1868
+ */
1869
+ class NotImplementedError extends BaseError {
1618
1870
  /**
1619
- * Is the property a date.
1620
- * @param source The source of the error.
1621
- * @param property The name of the property.
1622
- * @param value The value to test.
1623
- * @throws GuardError If the value does not match the assertion.
1871
+ * Runtime name for the class.
1624
1872
  */
1625
- static date(source, property, value) {
1626
- if (!Is.date(value)) {
1627
- throw new GuardError(source, "guard.date", property, value);
1628
- }
1629
- }
1873
+ static CLASS_NAME = "NotImplementedError";
1630
1874
  /**
1631
- * Is the property a timestamp in milliseconds.
1875
+ * Create a new instance of NotImplementedError.
1632
1876
  * @param source The source of the error.
1633
- * @param property The name of the property.
1634
- * @param value The value to test.
1635
- * @throws GuardError If the value does not match the assertion.
1877
+ * @param method The method for the error.
1636
1878
  */
1637
- static timestampMilliseconds(source, property, value) {
1638
- if (!Is.timestampMilliseconds(value)) {
1639
- throw new GuardError(source, "guard.timestampMilliseconds", property, value);
1640
- }
1879
+ constructor(source, method) {
1880
+ super(NotImplementedError.CLASS_NAME, source, "common.notImplementedMethod", {
1881
+ method
1882
+ });
1641
1883
  }
1884
+ }
1885
+
1886
+ /**
1887
+ * Class to handle errors when a feature is unsupported.
1888
+ */
1889
+ class NotSupportedError extends BaseError {
1642
1890
  /**
1643
- * Is the property a timestamp in seconds.
1644
- * @param source The source of the error.
1645
- * @param property The name of the property.
1646
- * @param value The value to test.
1647
- * @throws GuardError If the value does not match the assertion.
1891
+ * Runtime name for the class.
1648
1892
  */
1649
- static timestampSeconds(source, property, value) {
1650
- if (!Is.timestampSeconds(value)) {
1651
- throw new GuardError(source, "guard.timestampSeconds", property, value);
1652
- }
1653
- }
1893
+ static CLASS_NAME = "NotSupportedError";
1654
1894
  /**
1655
- * Is the property an object.
1895
+ * Create a new instance of NotSupportedError.
1656
1896
  * @param source The source of the error.
1657
- * @param property The name of the property.
1658
- * @param value The value to test.
1659
- * @throws GuardError If the value does not match the assertion.
1897
+ * @param message The message as a code.
1898
+ * @param cause The cause of the error if we have wrapped another error.
1660
1899
  */
1661
- static object(source, property, value) {
1662
- if (Is.undefined(value)) {
1663
- throw new GuardError(source, "guard.objectUndefined", property, value);
1664
- }
1665
- if (!Is.object(value)) {
1666
- throw new GuardError(source, "guard.object", property, value);
1667
- }
1900
+ constructor(source, message, cause) {
1901
+ super(NotSupportedError.CLASS_NAME, source, message, undefined, cause);
1668
1902
  }
1903
+ }
1904
+
1905
+ /**
1906
+ * Class to handle errors which are triggered by access not being unauthorized.
1907
+ */
1908
+ class UnauthorizedError extends BaseError {
1669
1909
  /**
1670
- * Is the property is an object with at least one property.
1671
- * @param source The source of the error.
1672
- * @param property The name of the property.
1673
- * @param value The value to test.
1674
- * @throws GuardError If the value does not match the assertion.
1910
+ * Runtime name for the class.
1675
1911
  */
1676
- static objectValue(source, property, value) {
1677
- if (Is.undefined(value)) {
1678
- throw new GuardError(source, "guard.objectUndefined", property, value);
1679
- }
1680
- if (!Is.object(value)) {
1681
- throw new GuardError(source, "guard.object", property, value);
1682
- }
1683
- if (Object.keys(value || {}).length === 0) {
1684
- throw new GuardError(source, "guard.objectValue", property, value);
1685
- }
1686
- }
1912
+ static CLASS_NAME = "UnauthorizedError";
1687
1913
  /**
1688
- * Is the property is an array.
1914
+ * Create a new instance of UnauthorizedError.
1689
1915
  * @param source The source of the error.
1690
- * @param property The name of the property.
1691
- * @param value The value to test.
1692
- * @throws GuardError If the value does not match the assertion.
1916
+ * @param message The message as a code.
1917
+ * @param cause The cause of the error if we have wrapped another error.
1693
1918
  */
1694
- static array(source, property, value) {
1695
- if (!Is.array(value)) {
1696
- throw new GuardError(source, "guard.array", property, value);
1697
- }
1919
+ constructor(source, message, cause) {
1920
+ super(UnauthorizedError.CLASS_NAME, source, message, undefined, cause);
1698
1921
  }
1922
+ }
1923
+
1924
+ /**
1925
+ * Class to handle errors when some data can not be processed.
1926
+ */
1927
+ class UnprocessableError extends BaseError {
1699
1928
  /**
1700
- * Is the property is an array with at least one item.
1929
+ * Runtime name for the class.
1930
+ */
1931
+ static CLASS_NAME = "UnprocessableError";
1932
+ /**
1933
+ * Create a new instance of UnprocessableError.
1701
1934
  * @param source The source of the error.
1702
- * @param property The name of the property.
1703
- * @param value The value to test.
1704
- * @throws GuardError If the value does not match the assertion.
1935
+ * @param message The message as a code.
1936
+ * @param properties Any additional information for the error.
1937
+ * @param cause The cause of the error if we have wrapped another error.
1705
1938
  */
1706
- static arrayValue(source, property, value) {
1707
- if (!Is.array(value)) {
1708
- throw new GuardError(source, "guard.array", property, value);
1709
- }
1710
- if (value.length === 0) {
1711
- throw new GuardError(source, "guard.arrayValue", property, value);
1712
- }
1939
+ constructor(source, message, properties, cause) {
1940
+ super(UnprocessableError.CLASS_NAME, source, message, properties, cause);
1713
1941
  }
1942
+ }
1943
+
1944
+ /**
1945
+ * Class to handle errors which are triggered by entity validation.
1946
+ */
1947
+ class ValidationError extends BaseError {
1714
1948
  /**
1715
- * Is the property one of a list of items.
1949
+ * Runtime name for the class.s
1950
+ */
1951
+ static CLASS_NAME = "ValidationError";
1952
+ /**
1953
+ * Create a new instance of ValidationError.
1716
1954
  * @param source The source of the error.
1717
- * @param property The name of the property.
1718
- * @param value The value to test.
1719
- * @param options The options the value must be one of.
1720
- * @throws GuardError If the value does not match the assertion.
1955
+ * @param validationObject The object that failed validation.
1956
+ * @param validationFailures The validation failures.
1721
1957
  */
1722
- static arrayOneOf(source, property, value, options) {
1723
- if (!Is.array(options)) {
1724
- throw new GuardError(source, "guard.array", property, value);
1725
- }
1726
- if (!options.includes(value)) {
1727
- throw new GuardError(source, "guard.arrayOneOf", property, value, options.join(", "));
1728
- }
1958
+ constructor(source, validationObject, validationFailures) {
1959
+ super(ValidationError.CLASS_NAME, source, "common.validation", {
1960
+ validationObject,
1961
+ validationFailures
1962
+ });
1729
1963
  }
1964
+ }
1965
+
1966
+ // Copyright 2024 IOTA Stiftung.
1967
+ // SPDX-License-Identifier: Apache-2.0.
1968
+ /**
1969
+ * Provide a store for shared objects which can be accesses through multiple
1970
+ * instance loads of a packages.
1971
+ */
1972
+ class SharedStore {
1730
1973
  /**
1731
- * Is the property a Uint8Array.
1732
- * @param source The source of the error.
1733
- * @param property The name of the property.
1734
- * @param value The value to test.
1735
- * @throws GuardError If the value does not match the assertion.
1974
+ * Get a property from the shared store.
1975
+ * @param prop The name of the property to get.
1976
+ * @returns The property if it exists.
1736
1977
  */
1737
- static uint8Array(source, property, value) {
1738
- if (!Is.uint8Array(value)) {
1739
- throw new GuardError(source, "guard.uint8Array", property, value);
1978
+ static get(prop) {
1979
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1980
+ const shared = globalThis.__TWIN_SHARED__;
1981
+ if (Is.undefined(shared)) {
1982
+ return;
1740
1983
  }
1984
+ return shared[prop];
1741
1985
  }
1742
1986
  /**
1743
- * Is the property a function.
1744
- * @param source The source of the error.
1745
- * @param property The name of the property.
1746
- * @param value The value to test.
1747
- * @returns True if the value is a function.
1748
- * @throws GuardError If the value does not match the assertion.
1987
+ * Set the property in the shared store.
1988
+ * @param prop The name of the property to set.
1989
+ * @param value The value to set.
1749
1990
  */
1750
- static function(source, property, value) {
1751
- if (!Is.function(value)) {
1752
- throw new GuardError(source, "guard.function", property, value);
1991
+ static set(prop, value) {
1992
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1993
+ if (Is.undefined(globalThis.__TWIN_SHARED__)) {
1994
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1995
+ globalThis.__TWIN_SHARED__ = {};
1753
1996
  }
1754
- return true;
1997
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1998
+ globalThis.__TWIN_SHARED__[prop] = value;
1755
1999
  }
1756
2000
  /**
1757
- * Is the property a string formatted as an email address.
1758
- * @param source The source of the error.
1759
- * @param property The name of the property.
1760
- * @param value The value to test.
1761
- * @throws GuardError If the value does not match the assertion.
2001
+ * Remove a property from the shared store.
2002
+ * @param prop The name of the property to remove.
1762
2003
  */
1763
- static email(source, property, value) {
1764
- if (!Is.email(value)) {
1765
- throw new GuardError(source, "guard.email", property, value);
2004
+ static remove(prop) {
2005
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2006
+ const shared = globalThis.__TWIN_SHARED__;
2007
+ if (!Is.undefined(shared)) {
2008
+ delete shared[prop];
1766
2009
  }
1767
2010
  }
1768
2011
  }
@@ -1776,11 +2019,6 @@ class Factory {
1776
2019
  * @internal
1777
2020
  */
1778
2021
  static _CLASS_NAME = "Factory";
1779
- /**
1780
- * Store all the created factories.
1781
- * @internal
1782
- */
1783
- static _factories = {};
1784
2022
  /**
1785
2023
  * Type name for the instances.
1786
2024
  * @internal
@@ -1834,10 +2072,41 @@ class Factory {
1834
2072
  * @returns The factory instance.
1835
2073
  */
1836
2074
  static createFactory(typeName, autoInstance = false, matcher) {
1837
- if (Is.undefined(Factory._factories[typeName])) {
1838
- Factory._factories[typeName] = new Factory(typeName, autoInstance, matcher);
2075
+ const factories = Factory.getFactories();
2076
+ if (Is.undefined(factories[typeName])) {
2077
+ factories[typeName] = new Factory(typeName, autoInstance, matcher);
2078
+ }
2079
+ return factories[typeName];
2080
+ }
2081
+ /**
2082
+ * Get all the factories.
2083
+ * @returns All the factories.
2084
+ */
2085
+ static getFactories() {
2086
+ let factories = SharedStore.get("factories");
2087
+ if (Is.undefined(factories)) {
2088
+ factories = {};
2089
+ SharedStore.set("factories", factories);
2090
+ }
2091
+ return factories;
2092
+ }
2093
+ /**
2094
+ * Reset all the factories, which removes any created instances, but not the registrations.
2095
+ */
2096
+ static resetFactories() {
2097
+ const factories = Factory.getFactories();
2098
+ for (const typeName in factories) {
2099
+ factories[typeName].reset();
2100
+ }
2101
+ }
2102
+ /**
2103
+ * Clear all the factories, which removes anything registered with the factories.
2104
+ */
2105
+ static clearFactories() {
2106
+ const factories = Factory.getFactories();
2107
+ for (const typeName in factories) {
2108
+ factories[typeName].clear();
1839
2109
  }
1840
- return Factory._factories[typeName];
1841
2110
  }
1842
2111
  /**
1843
2112
  * Register a new generator.
@@ -1883,6 +2152,7 @@ class Factory {
1883
2152
  * @throws GeneralError if no item exists to get.
1884
2153
  */
1885
2154
  get(name) {
2155
+ Guards.stringValue(Factory._CLASS_NAME, "name", name);
1886
2156
  const instance = this.getIfExists(name);
1887
2157
  if (!instance) {
1888
2158
  throw new GeneralError(Factory._CLASS_NAME, "noGet", {
@@ -1898,6 +2168,9 @@ class Factory {
1898
2168
  * @returns An instance of the item or undefined if it does not exist.
1899
2169
  */
1900
2170
  getIfExists(name) {
2171
+ if (Is.empty(name)) {
2172
+ return;
2173
+ }
1901
2174
  Guards.stringValue(Factory._CLASS_NAME, "name", name);
1902
2175
  const matchName = this._matcher(Object.keys(this._generators), name);
1903
2176
  if (Is.stringValue(matchName) && this._generators[matchName]) {
@@ -1910,7 +2183,7 @@ class Factory {
1910
2183
  }
1911
2184
  }
1912
2185
  /**
1913
- * Reset all the instances.
2186
+ * Remove all the instances and leave the generators intact.
1914
2187
  */
1915
2188
  reset() {
1916
2189
  for (const name in this._generators) {
@@ -1918,6 +2191,14 @@ class Factory {
1918
2191
  }
1919
2192
  this._instances = {};
1920
2193
  }
2194
+ /**
2195
+ * Remove all the instances and the generators.
2196
+ */
2197
+ clear() {
2198
+ this._instances = {};
2199
+ this._generators = {};
2200
+ this._orderCounter = 0;
2201
+ }
1921
2202
  /**
1922
2203
  * Get all the instances as a map.
1923
2204
  * @returns The instances as a map.
@@ -1951,1060 +2232,1402 @@ class Factory {
1951
2232
  order: this._generators[generator].order
1952
2233
  });
1953
2234
  }
1954
- return orderedNames.sort((a, b) => a.order - b.order).map(o => o.name);
2235
+ return orderedNames.sort((a, b) => a.order - b.order).map(o => o.name);
2236
+ }
2237
+ /**
2238
+ * Does the factory contain the name.
2239
+ * @param name The name of the instance to find.
2240
+ * @returns True if the factory has a matching name.
2241
+ */
2242
+ hasName(name) {
2243
+ Guards.stringValue(Factory._CLASS_NAME, "name", name);
2244
+ return Is.stringValue(this._matcher(Object.keys(this._generators), name));
2245
+ }
2246
+ /**
2247
+ * Remove any instances of the given name.
2248
+ * @param name The name of the instances to remove.
2249
+ * @internal
2250
+ */
2251
+ removeInstance(name) {
2252
+ delete this._instances[name];
2253
+ }
2254
+ /**
2255
+ * Match the requested name to the generator name.
2256
+ * @param names The list of names for all the generators.
2257
+ * @param name The name to match.
2258
+ * @returns The matched name or undefined if no match.
2259
+ * @internal
2260
+ */
2261
+ defaultMatcher(names, name) {
2262
+ return this._generators[name] ? name : undefined;
2263
+ }
2264
+ }
2265
+
2266
+ // Copyright 2024 IOTA Stiftung.
2267
+ // SPDX-License-Identifier: Apache-2.0.
2268
+ /**
2269
+ * Factory for creating implementation of component types.
2270
+ */
2271
+ // eslint-disable-next-line @typescript-eslint/naming-convention
2272
+ const ComponentFactory = Factory.createFactory("component");
2273
+
2274
+ // Copyright 2024 IOTA Stiftung.
2275
+ // SPDX-License-Identifier: Apache-2.0.
2276
+ /* eslint-disable no-bitwise */
2277
+ /**
2278
+ * Convert arrays to and from different formats.
2279
+ */
2280
+ class Converter {
2281
+ /**
2282
+ * Lookup table for encoding.
2283
+ * @internal
2284
+ */
2285
+ static _ENCODE_LOOKUP;
2286
+ /**
2287
+ * Lookup table for decoding.
2288
+ * @internal
2289
+ */
2290
+ static _DECODE_LOOKUP;
2291
+ /**
2292
+ * Encode a raw array to UTF8 string.
2293
+ * @param array The bytes to encode.
2294
+ * @param startIndex The index to start in the bytes.
2295
+ * @param length The length of bytes to read.
2296
+ * @returns The array formatted as UTF8.
2297
+ */
2298
+ static bytesToUtf8(array, startIndex, length) {
2299
+ const start = startIndex ?? 0;
2300
+ const len = length ?? array.length;
2301
+ let str = "";
2302
+ for (let i = start; i < start + len; i++) {
2303
+ const value = array[i];
2304
+ if (value < 0x80) {
2305
+ str += String.fromCharCode(value);
2306
+ }
2307
+ else if (value > 0xbf && value < 0xe0) {
2308
+ str += String.fromCharCode(((value & 0x1f) << 6) | (array[i + 1] & 0x3f));
2309
+ i += 1;
2310
+ }
2311
+ else if (value > 0xdf && value < 0xf0) {
2312
+ str += String.fromCharCode(((value & 0x0f) << 12) | ((array[i + 1] & 0x3f) << 6) | (array[i + 2] & 0x3f));
2313
+ i += 2;
2314
+ }
2315
+ else {
2316
+ // surrogate pair
2317
+ const charCode = (((value & 0x07) << 18) |
2318
+ ((array[i + 1] & 0x3f) << 12) |
2319
+ ((array[i + 2] & 0x3f) << 6) |
2320
+ (array[i + 3] & 0x3f)) -
2321
+ 0x010000;
2322
+ str += String.fromCharCode((charCode >> 10) | 0xd800, (charCode & 0x03ff) | 0xdc00);
2323
+ i += 3;
2324
+ }
2325
+ }
2326
+ return str;
2327
+ }
2328
+ /**
2329
+ * Convert a UTF8 string to raw array.
2330
+ * @param utf8 The text to decode.
2331
+ * @returns The array.
2332
+ */
2333
+ static utf8ToBytes(utf8) {
2334
+ const bytes = [];
2335
+ for (let i = 0; i < utf8.length; i++) {
2336
+ let charCode = utf8.charCodeAt(i);
2337
+ if (charCode < 0x80) {
2338
+ bytes.push(charCode);
2339
+ }
2340
+ else if (charCode < 0x800) {
2341
+ bytes.push(0xc0 | (charCode >> 6), 0x80 | (charCode & 0x3f));
2342
+ }
2343
+ else if (charCode < 0xd800 || charCode >= 0xe000) {
2344
+ bytes.push(0xe0 | (charCode >> 12), 0x80 | ((charCode >> 6) & 0x3f), 0x80 | (charCode & 0x3f));
2345
+ }
2346
+ else {
2347
+ // surrogate pair
2348
+ i++;
2349
+ // UTF-16 encodes 0x10000-0x10FFFF by
2350
+ // subtracting 0x10000 and splitting the
2351
+ // 20 bits of 0x0-0xFFFFF into two halves
2352
+ charCode = 0x10000 + (((charCode & 0x3ff) << 10) | (utf8.charCodeAt(i) & 0x3ff));
2353
+ bytes.push(0xf0 | (charCode >> 18), 0x80 | ((charCode >> 12) & 0x3f), 0x80 | ((charCode >> 6) & 0x3f), 0x80 | (charCode & 0x3f));
2354
+ }
2355
+ }
2356
+ return Uint8Array.from(bytes);
2357
+ }
2358
+ /**
2359
+ * Encode a raw array to hex string.
2360
+ * @param array The bytes to encode.
2361
+ * @param includePrefix Include the 0x prefix on the returned hex.
2362
+ * @param startIndex The index to start in the bytes.
2363
+ * @param length The length of bytes to read.
2364
+ * @param reverse Reverse the combine direction.
2365
+ * @returns The array formatted as hex.
2366
+ */
2367
+ static bytesToHex(array, includePrefix = false, startIndex, length, reverse) {
2368
+ let hex = "";
2369
+ this.buildHexLookups();
2370
+ if (Converter._ENCODE_LOOKUP) {
2371
+ const len = length ?? array.length;
2372
+ const start = startIndex ?? 0;
2373
+ if (reverse) {
2374
+ for (let i = 0; i < len; i++) {
2375
+ hex = Converter._ENCODE_LOOKUP[array[start + i]] + hex;
2376
+ }
2377
+ }
2378
+ else {
2379
+ for (let i = 0; i < len; i++) {
2380
+ hex += Converter._ENCODE_LOOKUP[array[start + i]];
2381
+ }
2382
+ }
2383
+ }
2384
+ return includePrefix ? HexHelper.addPrefix(hex) : hex;
2385
+ }
2386
+ /**
2387
+ * Decode a hex string to raw array.
2388
+ * @param hex The hex to decode.
2389
+ * @param reverse Store the characters in reverse.
2390
+ * @returns The array.
2391
+ */
2392
+ static hexToBytes(hex, reverse) {
2393
+ const strippedHex = HexHelper.stripPrefix(hex);
2394
+ const sizeof = strippedHex.length >> 1;
2395
+ const length = sizeof << 1;
2396
+ const array = new Uint8Array(sizeof);
2397
+ this.buildHexLookups();
2398
+ if (Converter._DECODE_LOOKUP) {
2399
+ let i = 0;
2400
+ let n = 0;
2401
+ while (i < length) {
2402
+ array[n++] =
2403
+ (Converter._DECODE_LOOKUP[strippedHex.charCodeAt(i++)] << 4) |
2404
+ Converter._DECODE_LOOKUP[strippedHex.charCodeAt(i++)];
2405
+ }
2406
+ if (reverse) {
2407
+ array.reverse();
2408
+ }
2409
+ }
2410
+ return array;
1955
2411
  }
1956
2412
  /**
1957
- * Does the factory contain the name.
1958
- * @param name The name of the instance to find.
1959
- * @returns True if the factory has a matching name.
2413
+ * Convert the UTF8 to hex.
2414
+ * @param utf8 The text to convert.
2415
+ * @param includePrefix Include the 0x prefix on the returned hex.
2416
+ * @returns The hex version of the bytes.
1960
2417
  */
1961
- hasName(name) {
1962
- Guards.stringValue(Factory._CLASS_NAME, "name", name);
1963
- return Is.stringValue(this._matcher(Object.keys(this._generators), name));
2418
+ static utf8ToHex(utf8, includePrefix = false) {
2419
+ const hex = Converter.bytesToHex(Converter.utf8ToBytes(utf8));
2420
+ return includePrefix ? HexHelper.addPrefix(hex) : hex;
1964
2421
  }
1965
2422
  /**
1966
- * Remove any instances of the given name.
1967
- * @param name The name of the instances to remove.
1968
- * @internal
2423
+ * Convert the hex text to text.
2424
+ * @param hex The hex to convert.
2425
+ * @returns The UTF8 version of the bytes.
1969
2426
  */
1970
- removeInstance(name) {
1971
- delete this._instances[name];
2427
+ static hexToUtf8(hex) {
2428
+ return Converter.bytesToUtf8(Converter.hexToBytes(HexHelper.stripPrefix(hex)));
1972
2429
  }
1973
2430
  /**
1974
- * Match the requested name to the generator name.
1975
- * @param names The list of names for all the generators.
1976
- * @param name The name to match.
1977
- * @returns The matched name or undefined if no match.
1978
- * @internal
2431
+ * Convert bytes to binary string.
2432
+ * @param bytes The bytes to convert.
2433
+ * @returns A binary string of the bytes.
1979
2434
  */
1980
- defaultMatcher(names, name) {
1981
- return this._generators[name] ? name : undefined;
2435
+ static bytesToBinary(bytes) {
2436
+ const b = [];
2437
+ for (let i = 0; i < bytes.length; i++) {
2438
+ b.push(bytes[i].toString(2).padStart(8, "0"));
2439
+ }
2440
+ return b.join("");
1982
2441
  }
1983
- }
1984
-
1985
- // Copyright 2024 IOTA Stiftung.
1986
- // SPDX-License-Identifier: Apache-2.0.
1987
- /**
1988
- * Factory for creating implementation of component types.
1989
- */
1990
- // eslint-disable-next-line @typescript-eslint/naming-convention
1991
- const ComponentFactory = Factory.createFactory("component");
1992
-
1993
- // Copyright 2024 IOTA Stiftung.
1994
- // SPDX-License-Identifier: Apache-2.0.
1995
- /**
1996
- * Class to help with arrays.
1997
- */
1998
- class ArrayHelper {
1999
2442
  /**
2000
- * Do the two arrays match.
2001
- * @param arr1 The first array.
2002
- * @param arr2 The second array.
2003
- * @returns True if both arrays are empty of have the same values.
2443
+ * Convert a binary string to bytes.
2444
+ * @param binary The binary string.
2445
+ * @returns The bytes.
2004
2446
  */
2005
- static matches(arr1, arr2) {
2006
- if (Is.empty(arr1) && Is.empty(arr2)) {
2007
- return true;
2008
- }
2009
- if (!((Is.array(arr1) && Is.array(arr2)) || (Is.typedArray(arr1) && Is.typedArray(arr2)))) {
2010
- return false;
2011
- }
2012
- if (arr1.length !== arr2.length) {
2013
- return false;
2014
- }
2015
- for (let i = 0; i < arr1.length; i++) {
2016
- if (arr1[i] !== arr2[i]) {
2017
- return false;
2018
- }
2447
+ static binaryToBytes(binary) {
2448
+ const bytes = new Uint8Array(Math.ceil(binary.length / 8));
2449
+ for (let i = 0; i < bytes.length; i++) {
2450
+ bytes[i] = Number.parseInt(binary.slice(i * 8, (i + 1) * 8), 2);
2019
2451
  }
2020
- return true;
2452
+ return bytes;
2021
2453
  }
2022
- }
2023
-
2024
- // Copyright 2024 IOTA Stiftung.
2025
- // SPDX-License-Identifier: Apache-2.0.
2026
- /**
2027
- * Class to perform internationalization.
2028
- */
2029
- class I18n {
2030
- /**
2031
- * The default translation.
2032
- */
2033
- static DEFAULT_LOCALE = "en";
2034
- /**
2035
- * Dictionaries for lookups.
2036
- * @internal
2037
- */
2038
- static _localeDictionaries = {};
2039
- /**
2040
- * The current locale.
2041
- * @internal
2042
- */
2043
- static _currentLocale = I18n.DEFAULT_LOCALE;
2044
- /**
2045
- * Change handler for the locale being updated.
2046
- * @internal
2047
- */
2048
- static _localeChangedHandlers = {};
2049
2454
  /**
2050
- * Change handler for the dictionaries being updated.
2051
- * @internal
2455
+ * Convert bytes to base64 string.
2456
+ * @param bytes The bytes to convert.
2457
+ * @returns A base64 string of the bytes.
2052
2458
  */
2053
- static _dictionaryChangedHandlers = {};
2459
+ static bytesToBase64(bytes) {
2460
+ return Base64.encode(bytes);
2461
+ }
2054
2462
  /**
2055
- * Set the locale.
2056
- * @param locale The new locale.
2463
+ * Convert a base64 string to bytes.
2464
+ * @param base64 The base64 string.
2465
+ * @returns The bytes.
2057
2466
  */
2058
- static setLocale(locale) {
2059
- I18n._currentLocale = locale;
2060
- for (const callback in I18n._localeChangedHandlers) {
2061
- I18n._localeChangedHandlers[callback](I18n._currentLocale);
2062
- }
2467
+ static base64ToBytes(base64) {
2468
+ return Base64.decode(base64);
2063
2469
  }
2064
2470
  /**
2065
- * Get the locale.
2066
- * @returns The current locale.
2471
+ * Convert bytes to base64 url string.
2472
+ * @param bytes The bytes to convert.
2473
+ * @returns A base64 url string of the bytes.
2067
2474
  */
2068
- static getLocale() {
2069
- return I18n._currentLocale;
2475
+ static bytesToBase64Url(bytes) {
2476
+ return Base64Url.encode(bytes);
2070
2477
  }
2071
2478
  /**
2072
- * Add a locale dictionary.
2073
- * @param locale The locale.
2074
- * @param dictionary The dictionary to add.
2479
+ * Convert a base64 url string to bytes.
2480
+ * @param base64Url The base64 url string.
2481
+ * @returns The bytes.
2075
2482
  */
2076
- static addDictionary(locale, dictionary) {
2077
- const mergedKeys = {};
2078
- I18n.flattenTranslationKeys(dictionary, "", mergedKeys);
2079
- I18n._localeDictionaries[locale] = mergedKeys;
2080
- for (const callback in I18n._dictionaryChangedHandlers) {
2081
- I18n._dictionaryChangedHandlers[callback](I18n._currentLocale);
2082
- }
2483
+ static base64UrlToBytes(base64Url) {
2484
+ return Base64Url.decode(base64Url);
2083
2485
  }
2084
2486
  /**
2085
- * Get a locale dictionary.
2086
- * @param locale The locale.
2087
- * @returns The dictionary of undefined if it does not exist.
2487
+ * Convert bytes to base58 string.
2488
+ * @param bytes The bytes to convert.
2489
+ * @returns A base58 string of the bytes.
2088
2490
  */
2089
- static getDictionary(locale) {
2090
- return I18n._localeDictionaries[locale];
2491
+ static bytesToBase58(bytes) {
2492
+ return Base58.encode(bytes);
2091
2493
  }
2092
2494
  /**
2093
- * Get all the locale dictionaries.
2094
- * @returns The dictionaries.
2495
+ * Convert a base58 string to bytes.
2496
+ * @param base58 The base58 string.
2497
+ * @returns The bytes.
2095
2498
  */
2096
- static getAllDictionaries() {
2097
- return I18n._localeDictionaries;
2499
+ static base58ToBytes(base58) {
2500
+ return Base58.decode(base58);
2098
2501
  }
2099
2502
  /**
2100
- * Add a locale changed handler.
2101
- * @param id The id of the handler.
2102
- * @param handler The handler to add.
2503
+ * Build the static lookup tables.
2504
+ * @internal
2103
2505
  */
2104
- static addLocaleHandler(id, handler) {
2105
- I18n._localeChangedHandlers[id] = handler;
2506
+ static buildHexLookups() {
2507
+ if (!Converter._ENCODE_LOOKUP || !Converter._DECODE_LOOKUP) {
2508
+ const alphabet = "0123456789abcdef";
2509
+ Converter._ENCODE_LOOKUP = [];
2510
+ Converter._DECODE_LOOKUP = [];
2511
+ for (let i = 0; i < 256; i++) {
2512
+ Converter._ENCODE_LOOKUP[i] = alphabet[(i >> 4) & 0xf] + alphabet[i & 0xf];
2513
+ if (i < 16) {
2514
+ if (i < 10) {
2515
+ Converter._DECODE_LOOKUP[0x30 + i] = i;
2516
+ }
2517
+ else {
2518
+ Converter._DECODE_LOOKUP[0x61 - 10 + i] = i;
2519
+ }
2520
+ }
2521
+ }
2522
+ }
2106
2523
  }
2524
+ }
2525
+
2526
+ /**
2527
+ * Helpers methods for JSON objects.
2528
+ */
2529
+ class JsonHelper {
2107
2530
  /**
2108
- * Remove a locale changed handler.
2109
- * @param id The id of the handler.
2531
+ * Runtime name for the class.
2532
+ * @internal
2110
2533
  */
2111
- static removeLocaleHandler(id) {
2112
- delete I18n._localeChangedHandlers[id];
2113
- }
2534
+ static _CLASS_NAME = "JsonHelper";
2114
2535
  /**
2115
- * Add a dictionary changed handler.
2116
- * @param id The id of the handler.
2117
- * @param handler The handler to add.
2536
+ * Serializes in canonical format.
2537
+ * Based on https://www.rfc-editor.org/rfc/rfc8785.
2538
+ * @param object The object to be serialized.
2539
+ * @returns The serialized object.
2118
2540
  */
2119
- static addDictionaryHandler(id, handler) {
2120
- I18n._dictionaryChangedHandlers[id] = handler;
2541
+ static canonicalize(object) {
2542
+ const buffer = [];
2543
+ if (object === null ||
2544
+ typeof object !== "object" ||
2545
+ ("toJSON" in object && object.toJSON instanceof Function)) {
2546
+ // Primitive data type
2547
+ buffer.push(JSON.stringify(object));
2548
+ }
2549
+ else if (Array.isArray(object)) {
2550
+ // Array maintain element order
2551
+ const parts = [];
2552
+ for (const element of object) {
2553
+ if (element === undefined) {
2554
+ parts.push("null");
2555
+ }
2556
+ else {
2557
+ parts.push(JsonHelper.canonicalize(element));
2558
+ }
2559
+ }
2560
+ buffer.push(`[${parts.join(",")}]`);
2561
+ }
2562
+ else {
2563
+ // Object sort properties
2564
+ const props = [];
2565
+ const keys = Object.keys(object).sort();
2566
+ const o = object;
2567
+ for (const key of keys) {
2568
+ if (o[key] !== undefined) {
2569
+ props.push(`${JSON.stringify(key)}:${JsonHelper.canonicalize(o[key])}`);
2570
+ }
2571
+ }
2572
+ buffer.push(`{${props.join(",")}}`);
2573
+ }
2574
+ return buffer.join("");
2121
2575
  }
2122
2576
  /**
2123
- * Remove a dictionary changed handler.
2124
- * @param id The id of the handler.
2577
+ * Creates a RFC 6902 diff set.
2578
+ * Based on https://www.rfc-editor.org/rfc/rfc6902.
2579
+ * @param object1 The first object.
2580
+ * @param object2 The second object.
2581
+ * @returns The list of patches.
2125
2582
  */
2126
- static removeDictionaryHandler(id) {
2127
- delete I18n._dictionaryChangedHandlers[id];
2583
+ static diff(object1, object2) {
2584
+ const operations = createPatch(object1, object2);
2585
+ return operations;
2128
2586
  }
2129
2587
  /**
2130
- * Format a message.
2131
- * @param key The key of the message to format.
2132
- * @param values The values to substitute into the message.
2133
- * @param overrideLocale Override the locale.
2134
- * @returns The formatted string.
2588
+ * Applies a RFC 6902 diff set to an object.
2589
+ * Based on https://www.rfc-editor.org/rfc/rfc6902.
2590
+ * @param object The object to patch.
2591
+ * @param patches The second object.
2592
+ * @returns The updated object.
2593
+ * @throws GeneralError if the patch fails.
2135
2594
  */
2136
- static formatMessage(key, values, overrideLocale) {
2137
- let cl = overrideLocale ?? I18n._currentLocale;
2138
- if (cl.startsWith("debug-")) {
2139
- cl = I18n.DEFAULT_LOCALE;
2140
- }
2141
- if (!I18n._localeDictionaries[cl]) {
2142
- return `!!Missing ${cl}`;
2143
- }
2144
- if (!I18n._localeDictionaries[cl][key]) {
2145
- return `!!Missing ${cl}.${key}`;
2146
- }
2147
- if (I18n._currentLocale === "debug-k") {
2148
- return key;
2149
- }
2150
- let ret = new IntlMessageFormat(I18n._localeDictionaries[cl][key], cl).format(values);
2151
- if (I18n._currentLocale === "debug-x") {
2152
- ret = ret.replace(/[a-z]/g, "x").replace(/[A-Z]/g, "x").replace(/\d/g, "n");
2595
+ static patch(object, patches) {
2596
+ const clone = ObjectHelper.clone(object);
2597
+ const result = applyPatch(clone, patches);
2598
+ for (let i = 0; i < result.length; i++) {
2599
+ if (!Is.empty(result[i])) {
2600
+ throw new GeneralError(JsonHelper._CLASS_NAME, "failedPatch", { index: i }, result[i]);
2601
+ }
2153
2602
  }
2154
- return ret;
2603
+ return clone;
2155
2604
  }
2156
2605
  /**
2157
- * Check if the dictionaries have a message for the given key.
2158
- * @param key The key to check for existence.
2159
- * @returns True if the key exists.
2606
+ * Stringify the JSON with support for extended data types date/bigint/uint8array.
2607
+ * @param object The object to stringify.
2608
+ * @param space Adds indentation, white space, and line break characters to the return-value JSON text to make it easier to read.
2609
+ * @returns The stringified object.
2160
2610
  */
2161
- static hasMessage(key) {
2162
- return Is.string(I18n._localeDictionaries[I18n._currentLocale]?.[key]);
2611
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2612
+ static stringifyEx(object, space) {
2613
+ // We want to keep the 'this' intact for the replacer
2614
+ // eslint-disable-next-line @typescript-eslint/unbound-method
2615
+ return JSON.stringify(object, JsonHelper.stringifyExReplacer, space);
2163
2616
  }
2164
2617
  /**
2165
- * Flatten the translation property paths for faster lookup.
2166
- * @param translation The translation to merge.
2167
- * @param propertyPath The current root path.
2168
- * @param mergedKeys The merged keys dictionary to populate.
2169
- * @internal
2618
+ * Parse the JSON string with support for extended data types date/bigint/uint8array.
2619
+ * @param json The object to pause.
2620
+ * @returns The object.
2170
2621
  */
2171
- static flattenTranslationKeys(translation, propertyPath, mergedKeys) {
2172
- for (const key in translation) {
2173
- const val = translation[key];
2174
- const mergedPath = propertyPath.length > 0 ? `${propertyPath}.${key}` : key;
2175
- if (Is.string(val)) {
2176
- mergedKeys[mergedPath] = val;
2622
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2623
+ static parseEx(json) {
2624
+ // We want to keep the 'this' intact for the reviver
2625
+ // eslint-disable-next-line @typescript-eslint/unbound-method
2626
+ return JSON.parse(json, JsonHelper.parseExReviver);
2627
+ }
2628
+ /**
2629
+ * Replacer function to handle extended data types.
2630
+ * @param this The object.
2631
+ * @param key The key.
2632
+ * @param value The value.
2633
+ * @returns The value.
2634
+ */
2635
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2636
+ static stringifyExReplacer(key, value) {
2637
+ const rawValue = this[key];
2638
+ if (Is.bigint(rawValue)) {
2639
+ return {
2640
+ "@ext": "bigint",
2641
+ value: rawValue.toString()
2642
+ };
2643
+ }
2644
+ else if (Is.date(rawValue)) {
2645
+ return {
2646
+ "@ext": "date",
2647
+ value: rawValue.getTime()
2648
+ };
2649
+ }
2650
+ else if (Is.uint8Array(rawValue)) {
2651
+ return {
2652
+ "@ext": "uint8array",
2653
+ value: Converter.bytesToBase64(rawValue)
2654
+ };
2655
+ }
2656
+ return value;
2657
+ }
2658
+ /**
2659
+ * Reviver function to handle extended data types.
2660
+ * @param this The object.
2661
+ * @param key The key.
2662
+ * @param value The value.
2663
+ * @returns The value.
2664
+ */
2665
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2666
+ static parseExReviver(key, value) {
2667
+ if (Is.object(value)) {
2668
+ if (value["@ext"] === "bigint") {
2669
+ return BigInt(value.value);
2177
2670
  }
2178
- else if (Is.object(val)) {
2179
- I18n.flattenTranslationKeys(val, mergedPath, mergedKeys);
2671
+ else if (value["@ext"] === "date") {
2672
+ return new Date(value.value);
2673
+ }
2674
+ else if (value["@ext"] === "uint8array") {
2675
+ return Converter.base64ToBytes(value.value);
2180
2676
  }
2181
2677
  }
2678
+ return value;
2182
2679
  }
2183
2680
  }
2184
2681
 
2185
- // Copyright 2024 IOTA Stiftung.
2186
- // SPDX-License-Identifier: Apache-2.0.
2187
2682
  /**
2188
- * Error helper functions.
2683
+ * Class to help with objects.
2189
2684
  */
2190
- class ErrorHelper {
2685
+ class ObjectHelper {
2191
2686
  /**
2192
- * Format Errors and returns just their messages.
2193
- * @param error The error to format.
2194
- * @returns The error formatted including any inner errors.
2687
+ * Runtime name for the class.
2688
+ * @internal
2195
2689
  */
2196
- static formatErrors(error) {
2197
- return ErrorHelper.localizeErrors(error).map(e => e.message);
2198
- }
2690
+ static _CLASS_NAME = "ObjectHelper";
2199
2691
  /**
2200
- * Localize the content of an error and any inner errors.
2201
- * @param error The error to format.
2202
- * @returns The localized version of the errors flattened.
2692
+ * Convert an object to bytes.
2693
+ * @param obj The object to convert.
2694
+ * @param format Format the JSON content.
2695
+ * @returns The object as bytes.
2203
2696
  */
2204
- static localizeErrors(error) {
2205
- const formattedErrors = [];
2206
- if (Is.notEmpty(error)) {
2207
- const errors = BaseError.flatten(error);
2208
- for (const err of errors) {
2209
- const errorNameKey = `errorNames.${StringHelper.camelCase(err.name)}`;
2210
- const errorMessageKey = `error.${err.message}`;
2211
- // If there is no error message then it is probably
2212
- // from a 3rd party lib, so don't format it just display
2213
- const hasErrorName = I18n.hasMessage(errorNameKey);
2214
- const hasErrorMessage = I18n.hasMessage(errorMessageKey);
2215
- const localizedError = {
2216
- name: I18n.formatMessage(hasErrorName ? errorNameKey : "errorNames.error"),
2217
- message: hasErrorMessage
2218
- ? I18n.formatMessage(errorMessageKey, err.properties)
2219
- : err.message
2220
- };
2221
- if (Is.stringValue(err.source)) {
2222
- localizedError.source = err.source;
2223
- }
2224
- if (Is.stringValue(err.stack)) {
2225
- // Remove the first line from the stack traces as they
2226
- // just have the error type and message duplicated
2227
- const lines = err.stack.split("\n");
2228
- lines.shift();
2229
- localizedError.stack = lines.join("\n");
2230
- }
2231
- const additional = ErrorHelper.formatValidationErrors(err);
2232
- if (Is.stringValue(additional)) {
2233
- localizedError.additional = additional;
2234
- }
2235
- formattedErrors.push(localizedError);
2236
- }
2697
+ static toBytes(obj, format = false) {
2698
+ if (obj === undefined) {
2699
+ return new Uint8Array();
2237
2700
  }
2238
- return formattedErrors;
2701
+ const json = format ? JSON.stringify(obj, undefined, "\t") : JSON.stringify(obj);
2702
+ return Converter.utf8ToBytes(json);
2239
2703
  }
2240
2704
  /**
2241
- * Localize the content of an error and any inner errors.
2242
- * @param error The error to format.
2243
- * @returns The localized version of the errors flattened.
2705
+ * Convert a bytes to an object.
2706
+ * @param bytes The bytes to convert to an object.
2707
+ * @returns The object.
2708
+ * @throws GeneralError if there was an error parsing the JSON.
2244
2709
  */
2245
- static formatValidationErrors(error) {
2246
- if (Is.object(error.properties) &&
2247
- Object.keys(error.properties).length > 0 &&
2248
- Is.object(error.properties) &&
2249
- Is.arrayValue(error.properties.validationFailures)) {
2250
- const validationErrors = [];
2251
- for (const validationFailure of error.properties.validationFailures) {
2252
- const errorI18n = `error.${validationFailure.reason}`;
2253
- const errorMessage = I18n.hasMessage(errorI18n)
2254
- ? I18n.formatMessage(errorI18n, validationFailure.properties)
2255
- : errorI18n;
2256
- let v = `${validationFailure.property}: ${errorMessage}`;
2257
- if (Is.object(validationFailure.properties) &&
2258
- Is.notEmpty(validationFailure.properties.value)) {
2259
- v += ` = ${JSON.stringify(validationFailure.properties.value)}`;
2260
- }
2261
- validationErrors.push(v);
2262
- }
2263
- return validationErrors.join("\n");
2710
+ static fromBytes(bytes) {
2711
+ if (Is.empty(bytes) || bytes.length === 0) {
2712
+ return undefined;
2713
+ }
2714
+ try {
2715
+ const utf8 = Converter.bytesToUtf8(bytes);
2716
+ return JSON.parse(utf8);
2717
+ }
2718
+ catch (err) {
2719
+ throw new GeneralError(ObjectHelper._CLASS_NAME, "failedBytesToJSON", undefined, err);
2264
2720
  }
2265
2721
  }
2266
- }
2267
-
2268
- // Copyright 2024 IOTA Stiftung.
2269
- // SPDX-License-Identifier: Apache-2.0.
2270
- /**
2271
- * Coerce an object from one type to another.
2272
- */
2273
- class Coerce {
2274
2722
  /**
2275
- * Coerce the value to a string.
2276
- * @param value The value to coerce.
2277
- * @throws TypeError If the value can not be coerced.
2278
- * @returns The value if it can be coerced.
2723
+ * Make a deep clone of an object.
2724
+ * @param obj The object to clone.
2725
+ * @returns The objects clone.
2279
2726
  */
2280
- static string(value) {
2281
- if (Is.undefined(value)) {
2282
- return value;
2283
- }
2284
- if (Is.string(value)) {
2285
- return value;
2727
+ static clone(obj) {
2728
+ if (Is.undefined(obj)) {
2729
+ return undefined;
2286
2730
  }
2287
- if (Is.number(value)) {
2288
- return value.toString();
2731
+ return structuredClone(obj);
2732
+ }
2733
+ /**
2734
+ * Deep merge objects.
2735
+ * @param obj1 The first object to merge.
2736
+ * @param obj2 The second object to merge.
2737
+ * @returns The combined deep merge of the objects.
2738
+ */
2739
+ static merge(obj1, obj2) {
2740
+ if (Is.empty(obj1)) {
2741
+ return ObjectHelper.clone(obj2);
2289
2742
  }
2290
- if (Is.boolean(value)) {
2291
- return value ? "true" : "false";
2743
+ if (Is.empty(obj2)) {
2744
+ return ObjectHelper.clone(obj1);
2292
2745
  }
2293
- if (Is.date(value)) {
2294
- return value.toISOString();
2746
+ const obj1Clone = ObjectHelper.clone(obj1);
2747
+ if (Is.object(obj1Clone) && Is.object(obj2)) {
2748
+ const keys = Object.keys(obj2);
2749
+ for (const key of keys) {
2750
+ if (Is.object(obj1Clone[key]) && Is.object(obj2[key])) {
2751
+ ObjectHelper.propertySet(obj1Clone, key, ObjectHelper.merge(obj1Clone[key], obj2[key]));
2752
+ }
2753
+ else {
2754
+ ObjectHelper.propertySet(obj1Clone, key, obj2[key]);
2755
+ }
2756
+ }
2295
2757
  }
2758
+ return obj1Clone;
2296
2759
  }
2297
2760
  /**
2298
- * Coerce the value to a number.
2299
- * @param value The value to coerce.
2300
- * @throws TypeError If the value can not be coerced.
2301
- * @returns The value if it can be coerced.
2761
+ * Does one object equal another.
2762
+ * @param obj1 The first object to compare.
2763
+ * @param obj2 The second object to compare.
2764
+ * @param strictPropertyOrder Should the properties be in the same order, defaults to true.
2765
+ * @returns True is the objects are equal.
2302
2766
  */
2303
- static number(value) {
2304
- if (Is.undefined(value)) {
2305
- return value;
2306
- }
2307
- if (Is.number(value)) {
2308
- return value;
2767
+ static equal(obj1, obj2, strictPropertyOrder) {
2768
+ if (strictPropertyOrder ?? true) {
2769
+ return JSON.stringify(obj1) === JSON.stringify(obj2);
2309
2770
  }
2310
- if (Is.string(value)) {
2311
- const parsed = Number.parseFloat(value);
2312
- if (Is.number(parsed)) {
2313
- return parsed;
2771
+ return JsonHelper.canonicalize(obj1) === JsonHelper.canonicalize(obj2);
2772
+ }
2773
+ /**
2774
+ * Get the property of an unknown object.
2775
+ * @param obj The object to get the property from.
2776
+ * @param property The property to get, can be separated by dots for nested path.
2777
+ * @returns The property.
2778
+ */
2779
+ static propertyGet(obj, property) {
2780
+ const pathParts = property.split(".");
2781
+ let pathValue = obj;
2782
+ for (const pathPart of pathParts) {
2783
+ // Is the path part numeric i.e. an array index.
2784
+ const arrayMatch = /^(\d+)$/.exec(pathPart);
2785
+ if (arrayMatch) {
2786
+ const arrayIndex = Number.parseInt(arrayMatch[1], 10);
2787
+ if (Is.arrayValue(pathValue) && arrayIndex < pathValue.length) {
2788
+ // There is no prop name so this is a direct array index on the current object
2789
+ pathValue = pathValue[arrayIndex];
2790
+ }
2791
+ else {
2792
+ // Array index for non array object so return
2793
+ return undefined;
2794
+ }
2795
+ }
2796
+ else if (Is.object(pathValue)) {
2797
+ // No array part in path so assume object sub property
2798
+ pathValue = pathValue[pathPart];
2799
+ }
2800
+ else {
2801
+ return undefined;
2314
2802
  }
2315
2803
  }
2316
- if (Is.boolean(value)) {
2317
- return value ? 1 : 0;
2804
+ return pathValue;
2805
+ }
2806
+ /**
2807
+ * Set the property of an unknown object.
2808
+ * @param obj The object to set the property from.
2809
+ * @param property The property to set.
2810
+ * @param value The value to set.
2811
+ * @throws GeneralError if the property target is not an object.
2812
+ */
2813
+ static propertySet(obj, property, value) {
2814
+ const pathParts = property.split(".");
2815
+ let pathValue = obj;
2816
+ let parentObj;
2817
+ for (let i = 0; i < pathParts.length; i++) {
2818
+ const pathPart = pathParts[i];
2819
+ // Is the path part numeric i.e. an array index.
2820
+ const arrayMatch = /^(\d+)$/.exec(pathPart);
2821
+ const arrayIndex = arrayMatch ? Number.parseInt(arrayMatch[1], 10) : -1;
2822
+ if (i === pathParts.length - 1) {
2823
+ // Last part of path so set the value
2824
+ if (arrayIndex >= 0) {
2825
+ if (Is.array(pathValue)) {
2826
+ pathValue[arrayIndex] = value;
2827
+ }
2828
+ else if (Is.object(pathValue)) {
2829
+ pathValue[arrayIndex] = value;
2830
+ }
2831
+ else {
2832
+ throw new GeneralError(ObjectHelper._CLASS_NAME, "cannotSetArrayIndex", {
2833
+ property,
2834
+ index: arrayIndex
2835
+ });
2836
+ }
2837
+ }
2838
+ else if (Is.object(pathValue)) {
2839
+ pathValue[pathPart] = value;
2840
+ }
2841
+ else {
2842
+ throw new GeneralError(ObjectHelper._CLASS_NAME, "cannotSetProperty", { property });
2843
+ }
2844
+ }
2845
+ else {
2846
+ parentObj = pathValue;
2847
+ if (Is.object(pathValue)) {
2848
+ pathValue = pathValue[pathPart];
2849
+ }
2850
+ else if (Is.array(pathValue)) {
2851
+ pathValue = pathValue[arrayIndex];
2852
+ }
2853
+ if (Is.empty(pathValue)) {
2854
+ const nextArrayMatch = /^(\d+)$/.exec(pathParts[i + 1]);
2855
+ const nextArrayIndex = nextArrayMatch ? Number.parseInt(nextArrayMatch[1], 10) : -1;
2856
+ if (nextArrayIndex >= 0) {
2857
+ pathValue = [];
2858
+ }
2859
+ else {
2860
+ pathValue = {};
2861
+ }
2862
+ if (Is.object(parentObj)) {
2863
+ parentObj[pathPart] = pathValue;
2864
+ }
2865
+ else if (Is.array(parentObj)) {
2866
+ parentObj[arrayIndex] = pathValue;
2867
+ }
2868
+ }
2869
+ }
2318
2870
  }
2319
- if (Is.date(value)) {
2320
- return value.getTime();
2871
+ }
2872
+ /**
2873
+ * Delete the property of an unknown object.
2874
+ * @param obj The object to set the property from.
2875
+ * @param property The property to set
2876
+ */
2877
+ static propertyDelete(obj, property) {
2878
+ if (Is.object(obj)) {
2879
+ delete obj[property];
2321
2880
  }
2322
2881
  }
2323
2882
  /**
2324
- * Coerce the value to a bigint.
2325
- * @param value The value to coerce.
2326
- * @throws TypeError If the value can not be coerced.
2327
- * @returns The value if it can be coerced.
2883
+ * Extract a property from the object, providing alternative names.
2884
+ * @param obj The object to extract from.
2885
+ * @param propertyNames The possible names for the property.
2886
+ * @param removeProperties Remove the properties from the object, defaults to true.
2887
+ * @returns The property if available.
2328
2888
  */
2329
- static bigint(value) {
2330
- if (Is.undefined(value)) {
2331
- return value;
2332
- }
2333
- if (Is.bigint(value)) {
2334
- return value;
2335
- }
2336
- if (Is.number(value)) {
2337
- return BigInt(value);
2338
- }
2339
- if (Is.string(value)) {
2340
- const parsed = Number.parseFloat(value);
2341
- if (Is.integer(parsed)) {
2342
- return BigInt(parsed);
2889
+ static extractProperty(obj, propertyNames, removeProperties = true) {
2890
+ let retVal;
2891
+ if (Is.object(obj)) {
2892
+ const names = Is.string(propertyNames) ? [propertyNames] : propertyNames;
2893
+ for (const prop of names) {
2894
+ retVal ??= ObjectHelper.propertyGet(obj, prop);
2895
+ if (removeProperties) {
2896
+ ObjectHelper.propertyDelete(obj, prop);
2897
+ }
2343
2898
  }
2344
2899
  }
2345
- if (Is.boolean(value)) {
2346
- return value ? 1n : 0n;
2347
- }
2900
+ return retVal;
2348
2901
  }
2349
2902
  /**
2350
- * Coerce the value to a boolean.
2351
- * @param value The value to coerce.
2352
- * @throws TypeError If the value can not be coerced.
2353
- * @returns The value if it can be coerced.
2903
+ * Pick a subset of properties from an object.
2904
+ * @param obj The object to pick the properties from.
2905
+ * @param keys The property keys to pick.
2906
+ * @returns The partial object.
2354
2907
  */
2355
- static boolean(value) {
2356
- if (Is.undefined(value)) {
2357
- return value;
2358
- }
2359
- if (Is.boolean(value)) {
2360
- return value;
2361
- }
2362
- if (Is.number(value)) {
2363
- // eslint-disable-next-line no-unneeded-ternary
2364
- return value ? true : false;
2365
- }
2366
- if (Is.string(value)) {
2367
- if (/true/i.test(value)) {
2368
- return true;
2369
- }
2370
- if (/false/i.test(value)) {
2371
- return false;
2908
+ static pick(obj, keys) {
2909
+ if (Is.object(obj) && Is.arrayValue(keys)) {
2910
+ const result = {};
2911
+ for (const key of keys) {
2912
+ result[key] = obj[key];
2372
2913
  }
2914
+ return result;
2373
2915
  }
2916
+ return obj;
2374
2917
  }
2375
2918
  /**
2376
- * Coerce the value to a date.
2377
- * @param value The value to coerce.
2378
- * @throws TypeError If the value can not be coerced.
2379
- * @returns The value if it can be coerced.
2919
+ * Omit a subset of properties from an object.
2920
+ * @param obj The object to omit the properties from.
2921
+ * @param keys The property keys to omit.
2922
+ * @returns The partial object.
2380
2923
  */
2381
- static date(value) {
2382
- if (Is.undefined(value)) {
2383
- return value;
2384
- }
2385
- if (Is.date(value)) {
2386
- return value;
2387
- }
2388
- if (Is.number(value)) {
2389
- return new Date(value);
2390
- }
2391
- if (Is.string(value)) {
2392
- const dt = new Date(value);
2393
- if (!Number.isNaN(dt.getTime())) {
2394
- const utc = Date.UTC(dt.getUTCFullYear(), dt.getUTCMonth(), dt.getUTCDate());
2395
- return new Date(utc);
2924
+ static omit(obj, keys) {
2925
+ if (Is.object(obj) && Is.arrayValue(keys)) {
2926
+ const result = { ...obj };
2927
+ for (const key of keys) {
2928
+ delete result[key];
2396
2929
  }
2930
+ return result;
2397
2931
  }
2932
+ return obj;
2398
2933
  }
2399
2934
  /**
2400
- * Coerce the value to a date/time.
2401
- * @param value The value to coerce.
2402
- * @throws TypeError If the value can not be coerced.
2403
- * @returns The value if it can be coerced.
2935
+ * Converter the non JSON primitives to extended types.
2936
+ * @param obj The object to convert.
2937
+ * @returns The object with extended properties.
2404
2938
  */
2405
- static dateTime(value) {
2406
- if (Is.undefined(value)) {
2407
- return value;
2408
- }
2409
- if (Is.date(value)) {
2410
- return value;
2411
- }
2412
- if (Is.number(value)) {
2413
- return new Date(value);
2414
- }
2415
- if (Is.string(value)) {
2416
- const dt = new Date(value);
2417
- if (!Number.isNaN(dt.getTime())) {
2418
- const utc = Date.UTC(dt.getUTCFullYear(), dt.getUTCMonth(), dt.getUTCDate(), dt.getUTCHours(), dt.getUTCMinutes(), dt.getUTCSeconds(), dt.getUTCMilliseconds());
2419
- return new Date(utc);
2420
- }
2421
- }
2939
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2940
+ static toExtended(obj) {
2941
+ const jsonExtended = JsonHelper.stringifyEx(obj);
2942
+ return JSON.parse(jsonExtended);
2422
2943
  }
2423
2944
  /**
2424
- * Coerce the value to a time.
2425
- * @param value The value to coerce.
2426
- * @throws TypeError If the value can not be coerced.
2427
- * @returns The value if it can be coerced.
2945
+ * Converter the extended types to non JSON primitives.
2946
+ * @param obj The object to convert.
2947
+ * @returns The object with regular properties.
2428
2948
  */
2429
- static time(value) {
2430
- if (Is.undefined(value)) {
2431
- return value;
2432
- }
2433
- if (Is.date(value)) {
2434
- return value;
2435
- }
2436
- if (Is.number(value)) {
2437
- const dt = new Date(value);
2438
- dt.setFullYear(1970, 0, 1);
2439
- return dt;
2440
- }
2441
- if (Is.string(value)) {
2442
- const dt = new Date(value);
2443
- if (!Number.isNaN(dt.getTime())) {
2444
- const utc = Date.UTC(1970, 0, 1, dt.getUTCHours(), dt.getUTCMinutes(), dt.getUTCSeconds(), dt.getUTCMilliseconds());
2445
- return new Date(utc);
2446
- }
2447
- }
2949
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2950
+ static fromExtended(obj) {
2951
+ const jsonExtended = JsonHelper.stringifyEx(obj);
2952
+ return JsonHelper.parseEx(jsonExtended);
2448
2953
  }
2449
2954
  /**
2450
- * Coerce the value to an object.
2451
- * @param value The value to coerce.
2452
- * @throws TypeError If the value can not be coerced.
2453
- * @returns The value if it can be coerced.
2955
+ * Remove empty properties from an object.
2956
+ * @param obj The object to remove the empty properties from.
2957
+ * @param options The options for the removal.
2958
+ * @param options.removeUndefined Remove undefined properties, defaults to true.
2959
+ * @param options.removeNull Remove null properties, defaults to false.
2960
+ * @returns The object with empty properties removed.
2454
2961
  */
2455
- static object(value) {
2456
- if (Is.undefined(value)) {
2457
- return value;
2458
- }
2459
- if (Is.object(value)) {
2460
- return value;
2962
+ static removeEmptyProperties(obj, options) {
2963
+ if (Is.object(obj)) {
2964
+ const removeUndefined = options?.removeUndefined ?? true;
2965
+ const removeNull = options?.removeNull ?? false;
2966
+ const newObj = {};
2967
+ const keys = Object.keys(obj);
2968
+ for (const key of keys) {
2969
+ if (!((removeUndefined && Is.undefined(obj[key])) || (removeNull && Is.null(obj[key])))) {
2970
+ newObj[key] = ObjectHelper.removeEmptyProperties(obj[key], options);
2971
+ }
2972
+ }
2973
+ return newObj;
2461
2974
  }
2462
- if (Is.stringValue(value)) {
2463
- try {
2464
- return JSON.parse(value);
2975
+ else if (Is.array(obj)) {
2976
+ const arr = [];
2977
+ for (const element of obj) {
2978
+ arr.push(ObjectHelper.removeEmptyProperties(element, options));
2465
2979
  }
2466
- catch { }
2980
+ return arr;
2467
2981
  }
2982
+ return obj;
2468
2983
  }
2469
2984
  }
2470
2985
 
2471
2986
  // Copyright 2024 IOTA Stiftung.
2472
2987
  // SPDX-License-Identifier: Apache-2.0.
2473
2988
  /**
2474
- * Class to help with filenames.
2989
+ * Environment variable helper.
2475
2990
  */
2476
- class FilenameHelper {
2477
- /**
2478
- * Replaces any unsafe characters in the filename.
2479
- * @param filename The filename to make safe.
2480
- * @returns The safe filename.
2481
- */
2482
- static safeFilename(filename) {
2483
- let safe = Coerce.string(filename);
2484
- if (Is.empty(safe)) {
2485
- return "";
2991
+ class EnvHelper {
2992
+ /**
2993
+ * Get the environment variable as an object with camel cased names.
2994
+ * @param envVars The environment variables.
2995
+ * @param prefix The prefix of the environment variables, if not provided gets all.
2996
+ * @returns The object with camel cased names.
2997
+ */
2998
+ static envToJson(envVars, prefix) {
2999
+ const result = {};
3000
+ if (!Is.empty(envVars)) {
3001
+ if (Is.empty(prefix)) {
3002
+ for (const envVar in envVars) {
3003
+ if (Is.stringValue(envVars[envVar])) {
3004
+ const camelCaseName = StringHelper.camelCase(envVar.toLowerCase());
3005
+ ObjectHelper.propertySet(result, camelCaseName, envVars[envVar]);
3006
+ }
3007
+ }
3008
+ }
3009
+ else {
3010
+ for (const envVar in envVars) {
3011
+ if (envVar.startsWith(prefix) && Is.stringValue(envVars[envVar])) {
3012
+ const camelCaseName = StringHelper.camelCase(envVar.replace(prefix, "").toLowerCase());
3013
+ ObjectHelper.propertySet(result, camelCaseName, envVars[envVar]);
3014
+ }
3015
+ }
3016
+ }
2486
3017
  }
2487
- // Common non filename characters
2488
- safe = safe.replace(/["*/:<>?\\|]/g, "_");
2489
- // Windows non filename characters
2490
- safe = safe.replace(/^(con|prn|aux|nul|com\d|lpt\d)$/i, "_");
2491
- // Control characters
2492
- safe = safe.replace(/[\u0000-\u001F\u0080-\u009F]/g, "_");
2493
- // Relative paths
2494
- safe = safe.replace(/^\.+/, "_");
2495
- // Trailing periods
2496
- safe = safe.replace(/\.+$/, "");
2497
- return safe;
3018
+ return result;
2498
3019
  }
2499
3020
  }
2500
3021
 
2501
3022
  // Copyright 2024 IOTA Stiftung.
2502
3023
  // SPDX-License-Identifier: Apache-2.0.
2503
3024
  /**
2504
- * Helpers methods for JSON objects.
3025
+ * Class to perform internationalization.
2505
3026
  */
2506
- class JsonHelper {
3027
+ class I18n {
2507
3028
  /**
2508
- * Serializes in canonical format.
2509
- * Based on https://www.rfc-editor.org/rfc/rfc8785.
2510
- * @param object The object to be serialized.
2511
- * @returns The serialized object.
3029
+ * The default translation.
2512
3030
  */
2513
- static canonicalize(object) {
2514
- const buffer = [];
2515
- if (object === null ||
2516
- typeof object !== "object" ||
2517
- ("toJSON" in object && object.toJSON instanceof Function)) {
2518
- // Primitive data type
2519
- buffer.push(JSON.stringify(object));
2520
- }
2521
- else if (Array.isArray(object)) {
2522
- // Array maintain element order
2523
- const parts = [];
2524
- for (const element of object) {
2525
- if (element === undefined) {
2526
- parts.push("null");
2527
- }
2528
- else {
2529
- parts.push(JsonHelper.canonicalize(element));
2530
- }
2531
- }
2532
- buffer.push(`[${parts.join(",")}]`);
3031
+ static DEFAULT_LOCALE = "en";
3032
+ /**
3033
+ * Set the locale.
3034
+ * @param locale The new locale.
3035
+ */
3036
+ static setLocale(locale) {
3037
+ const i18nShared = I18n.getI18nShared();
3038
+ i18nShared.currentLocale = locale;
3039
+ for (const callback in i18nShared.localeChangedHandlers) {
3040
+ i18nShared.localeChangedHandlers[callback](i18nShared.currentLocale);
2533
3041
  }
2534
- else {
2535
- // Object sort properties
2536
- const props = [];
2537
- const keys = Object.keys(object).sort();
2538
- const o = object;
2539
- for (const key of keys) {
2540
- if (o[key] !== undefined) {
2541
- props.push(`${JSON.stringify(key)}:${JsonHelper.canonicalize(o[key])}`);
2542
- }
2543
- }
2544
- buffer.push(`{${props.join(",")}}`);
3042
+ }
3043
+ /**
3044
+ * Get the locale.
3045
+ * @returns The current locale.
3046
+ */
3047
+ static getLocale() {
3048
+ const i18nShared = I18n.getI18nShared();
3049
+ return i18nShared.currentLocale;
3050
+ }
3051
+ /**
3052
+ * Add a locale dictionary.
3053
+ * @param locale The locale.
3054
+ * @param dictionary The dictionary to add.
3055
+ */
3056
+ static addDictionary(locale, dictionary) {
3057
+ const i18nShared = I18n.getI18nShared();
3058
+ const mergedKeys = {};
3059
+ I18n.flattenTranslationKeys(dictionary, "", mergedKeys);
3060
+ i18nShared.localeDictionaries[locale] = mergedKeys;
3061
+ for (const callback in i18nShared.dictionaryChangedHandlers) {
3062
+ i18nShared.dictionaryChangedHandlers[callback](i18nShared.currentLocale);
2545
3063
  }
2546
- return buffer.join("");
2547
3064
  }
2548
3065
  /**
2549
- * Creates a RFC 6902 diff set.
2550
- * Based on https://www.rfc-editor.org/rfc/rfc6902.
2551
- * @param object1 The first object.
2552
- * @param object2 The second object.
2553
- * @returns The list of patches.
3066
+ * Get a locale dictionary.
3067
+ * @param locale The locale.
3068
+ * @returns The dictionary of undefined if it does not exist.
2554
3069
  */
2555
- static diff(object1, object2) {
2556
- const operations = createPatch(object1, object2);
2557
- return operations;
3070
+ static getDictionary(locale) {
3071
+ const i18nShared = I18n.getI18nShared();
3072
+ return i18nShared.localeDictionaries[locale];
2558
3073
  }
2559
3074
  /**
2560
- * Applies a RFC 6902 diff set to an object.
2561
- * Based on https://www.rfc-editor.org/rfc/rfc6902.
2562
- * @param object The object to patch.
2563
- * @param patches The second object.
2564
- * @returns The updated object.
3075
+ * Get all the locale dictionaries.
3076
+ * @returns The dictionaries.
2565
3077
  */
2566
- static patch(object, patches) {
2567
- return applyPatch(object, patches);
3078
+ static getAllDictionaries() {
3079
+ const i18nShared = I18n.getI18nShared();
3080
+ return i18nShared.localeDictionaries;
2568
3081
  }
2569
- }
2570
-
2571
- // Copyright 2024 IOTA Stiftung.
2572
- // SPDX-License-Identifier: Apache-2.0.
2573
- /* eslint-disable no-bitwise */
2574
- /**
2575
- * Convert arrays to and from different formats.
2576
- */
2577
- class Converter {
2578
3082
  /**
2579
- * Lookup table for encoding.
2580
- * @internal
3083
+ * Add a locale changed handler.
3084
+ * @param id The id of the handler.
3085
+ * @param handler The handler to add.
2581
3086
  */
2582
- static _ENCODE_LOOKUP;
3087
+ static addLocaleHandler(id, handler) {
3088
+ const i18nShared = I18n.getI18nShared();
3089
+ i18nShared.localeChangedHandlers[id] = handler;
3090
+ }
2583
3091
  /**
2584
- * Lookup table for decoding.
2585
- * @internal
3092
+ * Remove a locale changed handler.
3093
+ * @param id The id of the handler.
2586
3094
  */
2587
- static _DECODE_LOOKUP;
3095
+ static removeLocaleHandler(id) {
3096
+ const i18nShared = I18n.getI18nShared();
3097
+ delete i18nShared.localeChangedHandlers[id];
3098
+ }
2588
3099
  /**
2589
- * Encode a raw array to UTF8 string.
2590
- * @param array The bytes to encode.
2591
- * @param startIndex The index to start in the bytes.
2592
- * @param length The length of bytes to read.
2593
- * @returns The array formatted as UTF8.
3100
+ * Add a dictionary changed handler.
3101
+ * @param id The id of the handler.
3102
+ * @param handler The handler to add.
2594
3103
  */
2595
- static bytesToUtf8(array, startIndex, length) {
2596
- const start = startIndex ?? 0;
2597
- const len = length ?? array.length;
2598
- let str = "";
2599
- for (let i = start; i < start + len; i++) {
2600
- const value = array[i];
2601
- if (value < 0x80) {
2602
- str += String.fromCharCode(value);
2603
- }
2604
- else if (value > 0xbf && value < 0xe0) {
2605
- str += String.fromCharCode(((value & 0x1f) << 6) | (array[i + 1] & 0x3f));
2606
- i += 1;
2607
- }
2608
- else if (value > 0xdf && value < 0xf0) {
2609
- str += String.fromCharCode(((value & 0x0f) << 12) | ((array[i + 1] & 0x3f) << 6) | (array[i + 2] & 0x3f));
2610
- i += 2;
2611
- }
2612
- else {
2613
- // surrogate pair
2614
- const charCode = (((value & 0x07) << 18) |
2615
- ((array[i + 1] & 0x3f) << 12) |
2616
- ((array[i + 2] & 0x3f) << 6) |
2617
- (array[i + 3] & 0x3f)) -
2618
- 0x010000;
2619
- str += String.fromCharCode((charCode >> 10) | 0xd800, (charCode & 0x03ff) | 0xdc00);
2620
- i += 3;
2621
- }
3104
+ static addDictionaryHandler(id, handler) {
3105
+ const i18nShared = I18n.getI18nShared();
3106
+ i18nShared.dictionaryChangedHandlers[id] = handler;
3107
+ }
3108
+ /**
3109
+ * Remove a dictionary changed handler.
3110
+ * @param id The id of the handler.
3111
+ */
3112
+ static removeDictionaryHandler(id) {
3113
+ const i18nShared = I18n.getI18nShared();
3114
+ delete i18nShared.dictionaryChangedHandlers[id];
3115
+ }
3116
+ /**
3117
+ * Format a message.
3118
+ * @param key The key of the message to format.
3119
+ * @param values The values to substitute into the message.
3120
+ * @param overrideLocale Override the locale.
3121
+ * @returns The formatted string.
3122
+ */
3123
+ static formatMessage(key, values, overrideLocale) {
3124
+ const i18nShared = I18n.getI18nShared();
3125
+ let cl = overrideLocale ?? i18nShared.currentLocale;
3126
+ if (cl.startsWith("debug-")) {
3127
+ cl = I18n.DEFAULT_LOCALE;
2622
3128
  }
2623
- return str;
3129
+ if (!i18nShared.localeDictionaries[cl]) {
3130
+ return `!!Missing ${cl}`;
3131
+ }
3132
+ if (!i18nShared.localeDictionaries[cl][key]) {
3133
+ return `!!Missing ${cl}.${key}`;
3134
+ }
3135
+ if (i18nShared.currentLocale === "debug-k") {
3136
+ return key;
3137
+ }
3138
+ let ret = new IntlMessageFormat(i18nShared.localeDictionaries[cl][key], cl).format(values);
3139
+ if (i18nShared.currentLocale === "debug-x") {
3140
+ ret = ret.replace(/[a-z]/g, "x").replace(/[A-Z]/g, "x").replace(/\d/g, "n");
3141
+ }
3142
+ return ret;
2624
3143
  }
2625
3144
  /**
2626
- * Convert a UTF8 string to raw array.
2627
- * @param utf8 The text to decode.
2628
- * @returns The array.
3145
+ * Check if the dictionaries have a message for the given key.
3146
+ * @param key The key to check for existence.
3147
+ * @returns True if the key exists.
2629
3148
  */
2630
- static utf8ToBytes(utf8) {
2631
- const bytes = [];
2632
- for (let i = 0; i < utf8.length; i++) {
2633
- let charCode = utf8.charCodeAt(i);
2634
- if (charCode < 0x80) {
2635
- bytes.push(charCode);
2636
- }
2637
- else if (charCode < 0x800) {
2638
- bytes.push(0xc0 | (charCode >> 6), 0x80 | (charCode & 0x3f));
2639
- }
2640
- else if (charCode < 0xd800 || charCode >= 0xe000) {
2641
- bytes.push(0xe0 | (charCode >> 12), 0x80 | ((charCode >> 6) & 0x3f), 0x80 | (charCode & 0x3f));
3149
+ static hasMessage(key) {
3150
+ const i18nShared = I18n.getI18nShared();
3151
+ return Is.string(i18nShared.localeDictionaries[i18nShared.currentLocale]?.[key]);
3152
+ }
3153
+ /**
3154
+ * Flatten the translation property paths for faster lookup.
3155
+ * @param translation The translation to merge.
3156
+ * @param propertyPath The current root path.
3157
+ * @param mergedKeys The merged keys dictionary to populate.
3158
+ * @internal
3159
+ */
3160
+ static flattenTranslationKeys(translation, propertyPath, mergedKeys) {
3161
+ for (const key in translation) {
3162
+ const val = translation[key];
3163
+ const mergedPath = propertyPath.length > 0 ? `${propertyPath}.${key}` : key;
3164
+ if (Is.string(val)) {
3165
+ mergedKeys[mergedPath] = val;
2642
3166
  }
2643
- else {
2644
- // surrogate pair
2645
- i++;
2646
- // UTF-16 encodes 0x10000-0x10FFFF by
2647
- // subtracting 0x10000 and splitting the
2648
- // 20 bits of 0x0-0xFFFFF into two halves
2649
- charCode = 0x10000 + (((charCode & 0x3ff) << 10) | (utf8.charCodeAt(i) & 0x3ff));
2650
- bytes.push(0xf0 | (charCode >> 18), 0x80 | ((charCode >> 12) & 0x3f), 0x80 | ((charCode >> 6) & 0x3f), 0x80 | (charCode & 0x3f));
3167
+ else if (Is.object(val)) {
3168
+ I18n.flattenTranslationKeys(val, mergedPath, mergedKeys);
2651
3169
  }
2652
3170
  }
2653
- return Uint8Array.from(bytes);
2654
3171
  }
2655
3172
  /**
2656
- * Encode a raw array to hex string.
2657
- * @param array The bytes to encode.
2658
- * @param includePrefix Include the 0x prefix on the returned hex.
2659
- * @param startIndex The index to start in the bytes.
2660
- * @param length The length of bytes to read.
2661
- * @param reverse Reverse the combine direction.
2662
- * @returns The array formatted as hex.
3173
+ * Get the I18n shared data.
3174
+ * @returns The I18n shared data.
3175
+ * @internal
2663
3176
  */
2664
- static bytesToHex(array, includePrefix = false, startIndex, length, reverse) {
2665
- let hex = "";
2666
- this.buildHexLookups();
2667
- if (Converter._ENCODE_LOOKUP) {
2668
- const len = length ?? array.length;
2669
- const start = startIndex ?? 0;
2670
- if (reverse) {
2671
- for (let i = 0; i < len; i++) {
2672
- hex = Converter._ENCODE_LOOKUP[array[start + i]] + hex;
3177
+ static getI18nShared() {
3178
+ let i18nShared = SharedStore.get("i18n");
3179
+ if (Is.undefined(i18nShared)) {
3180
+ i18nShared = {
3181
+ localeDictionaries: {},
3182
+ currentLocale: I18n.DEFAULT_LOCALE,
3183
+ localeChangedHandlers: {},
3184
+ dictionaryChangedHandlers: {}
3185
+ };
3186
+ SharedStore.set("i18n", i18nShared);
3187
+ }
3188
+ return i18nShared;
3189
+ }
3190
+ }
3191
+
3192
+ // Copyright 2024 IOTA Stiftung.
3193
+ // SPDX-License-Identifier: Apache-2.0.
3194
+ /**
3195
+ * Error helper functions.
3196
+ */
3197
+ class ErrorHelper {
3198
+ /**
3199
+ * Format Errors and returns just their messages.
3200
+ * @param error The error to format.
3201
+ * @param includeDetails Whether to include error details, defaults to false.
3202
+ * @returns The error formatted including any causes errors.
3203
+ */
3204
+ static formatErrors(error, includeDetails) {
3205
+ const localizedErrors = ErrorHelper.localizeErrors(error);
3206
+ if (includeDetails ?? false) {
3207
+ const output = [];
3208
+ for (const err of localizedErrors) {
3209
+ let detailedError = err.message;
3210
+ if (Is.stringValue(err.stack)) {
3211
+ detailedError += `\n${err.stack}`;
2673
3212
  }
3213
+ output.push(detailedError);
2674
3214
  }
2675
- else {
2676
- for (let i = 0; i < len; i++) {
2677
- hex += Converter._ENCODE_LOOKUP[array[start + i]];
3215
+ return output;
3216
+ }
3217
+ return localizedErrors.map(e => e.message);
3218
+ }
3219
+ /**
3220
+ * Localize the content of an error and any causes.
3221
+ * @param error The error to format.
3222
+ * @returns The localized version of the errors flattened.
3223
+ */
3224
+ static localizeErrors(error) {
3225
+ const formattedErrors = [];
3226
+ if (Is.notEmpty(error)) {
3227
+ const errors = BaseError.flatten(error);
3228
+ for (const err of errors) {
3229
+ const errorNameKey = `errorNames.${StringHelper.camelCase(err.name)}`;
3230
+ const errorMessageKey = `error.${err.message}`;
3231
+ // If there is no error message then it is probably
3232
+ // from a 3rd party lib, so don't format it just display
3233
+ const hasErrorName = I18n.hasMessage(errorNameKey);
3234
+ const hasErrorMessage = I18n.hasMessage(errorMessageKey);
3235
+ const localizedError = {
3236
+ name: I18n.formatMessage(hasErrorName ? errorNameKey : "errorNames.error"),
3237
+ message: hasErrorMessage
3238
+ ? I18n.formatMessage(errorMessageKey, err.properties)
3239
+ : err.message
3240
+ };
3241
+ if (Is.stringValue(err.source)) {
3242
+ localizedError.source = err.source;
3243
+ }
3244
+ if (Is.stringValue(err.stack)) {
3245
+ // Remove the first line from the stack traces as they
3246
+ // just have the error type and message duplicated
3247
+ const lines = err.stack.split("\n");
3248
+ lines.shift();
3249
+ localizedError.stack = lines.join("\n");
3250
+ }
3251
+ const additional = ErrorHelper.formatValidationErrors(err);
3252
+ if (Is.stringValue(additional)) {
3253
+ localizedError.additional = additional;
2678
3254
  }
3255
+ formattedErrors.push(localizedError);
2679
3256
  }
2680
3257
  }
2681
- return includePrefix ? HexHelper.addPrefix(hex) : hex;
3258
+ return formattedErrors;
2682
3259
  }
2683
3260
  /**
2684
- * Decode a hex string to raw array.
2685
- * @param hex The hex to decode.
2686
- * @param reverse Store the characters in reverse.
2687
- * @returns The array.
3261
+ * Localize the content of an error and any causes.
3262
+ * @param error The error to format.
3263
+ * @returns The localized version of the errors flattened.
2688
3264
  */
2689
- static hexToBytes(hex, reverse) {
2690
- const strippedHex = HexHelper.stripPrefix(hex);
2691
- const sizeof = strippedHex.length >> 1;
2692
- const length = sizeof << 1;
2693
- const array = new Uint8Array(sizeof);
2694
- this.buildHexLookups();
2695
- if (Converter._DECODE_LOOKUP) {
2696
- let i = 0;
2697
- let n = 0;
2698
- while (i < length) {
2699
- array[n++] =
2700
- (Converter._DECODE_LOOKUP[strippedHex.charCodeAt(i++)] << 4) |
2701
- Converter._DECODE_LOOKUP[strippedHex.charCodeAt(i++)];
2702
- }
2703
- if (reverse) {
2704
- array.reverse();
3265
+ static formatValidationErrors(error) {
3266
+ if (Is.object(error.properties) &&
3267
+ Object.keys(error.properties).length > 0 &&
3268
+ Is.object(error.properties) &&
3269
+ Is.arrayValue(error.properties.validationFailures)) {
3270
+ const validationErrors = [];
3271
+ for (const validationFailure of error.properties.validationFailures) {
3272
+ const errorI18n = `error.${validationFailure.reason}`;
3273
+ const errorMessage = I18n.hasMessage(errorI18n)
3274
+ ? I18n.formatMessage(errorI18n, validationFailure.properties)
3275
+ : errorI18n;
3276
+ let v = `${validationFailure.property}: ${errorMessage}`;
3277
+ if (Is.object(validationFailure.properties) &&
3278
+ Is.notEmpty(validationFailure.properties.value)) {
3279
+ v += ` = ${JSON.stringify(validationFailure.properties.value)}`;
3280
+ }
3281
+ validationErrors.push(v);
2705
3282
  }
3283
+ return validationErrors.join("\n");
2706
3284
  }
2707
- return array;
2708
- }
2709
- /**
2710
- * Convert the UTF8 to hex.
2711
- * @param utf8 The text to convert.
2712
- * @param includePrefix Include the 0x prefix on the returned hex.
2713
- * @returns The hex version of the bytes.
2714
- */
2715
- static utf8ToHex(utf8, includePrefix = false) {
2716
- const hex = Converter.bytesToHex(Converter.utf8ToBytes(utf8));
2717
- return includePrefix ? HexHelper.addPrefix(hex) : hex;
2718
3285
  }
3286
+ }
3287
+
3288
+ // Copyright 2024 IOTA Stiftung.
3289
+ // SPDX-License-Identifier: Apache-2.0.
3290
+ /**
3291
+ * The types the extracted data can be coerced to.
3292
+ */
3293
+ // eslint-disable-next-line @typescript-eslint/naming-convention
3294
+ const CoerceType = {
2719
3295
  /**
2720
- * Convert the hex text to text.
2721
- * @param hex The hex to convert.
2722
- * @returns The UTF8 version of the bytes.
3296
+ * String.
2723
3297
  */
2724
- static hexToUtf8(hex) {
2725
- return Converter.bytesToUtf8(Converter.hexToBytes(HexHelper.stripPrefix(hex)));
2726
- }
3298
+ String: "string",
2727
3299
  /**
2728
- * Convert bytes to binary string.
2729
- * @param bytes The bytes to convert.
2730
- * @returns A binary string of the bytes.
3300
+ * Number.
2731
3301
  */
2732
- static bytesToBinary(bytes) {
2733
- const b = [];
2734
- for (let i = 0; i < bytes.length; i++) {
2735
- b.push(bytes[i].toString(2).padStart(8, "0"));
2736
- }
2737
- return b.join("");
2738
- }
3302
+ Number: "number",
2739
3303
  /**
2740
- * Convert a binary string to bytes.
2741
- * @param binary The binary string.
2742
- * @returns The bytes.
3304
+ * Integer.
2743
3305
  */
2744
- static binaryToBytes(binary) {
2745
- const bytes = new Uint8Array(Math.ceil(binary.length / 8));
2746
- for (let i = 0; i < bytes.length; i++) {
2747
- bytes[i] = Number.parseInt(binary.slice(i * 8, (i + 1) * 8), 2);
2748
- }
2749
- return bytes;
2750
- }
3306
+ Integer: "integer",
2751
3307
  /**
2752
- * Convert bytes to base64 string.
2753
- * @param bytes The bytes to convert.
2754
- * @returns A base64 string of the bytes.
3308
+ * Boolean.
2755
3309
  */
2756
- static bytesToBase64(bytes) {
2757
- return Base64.encode(bytes);
2758
- }
3310
+ Boolean: "boolean",
2759
3311
  /**
2760
- * Convert a base64 string to bytes.
2761
- * @param base64 The base64 string.
2762
- * @returns The bytes.
3312
+ * Big Integer.
2763
3313
  */
2764
- static base64ToBytes(base64) {
2765
- return Base64.decode(base64);
2766
- }
3314
+ BigInt: "bigint",
2767
3315
  /**
2768
- * Convert bytes to base64 url string.
2769
- * @param bytes The bytes to convert.
2770
- * @returns A base64 url string of the bytes.
3316
+ * Date.
2771
3317
  */
2772
- static bytesToBase64Url(bytes) {
2773
- return Base64Url.encode(bytes);
2774
- }
3318
+ Date: "date",
2775
3319
  /**
2776
- * Convert a base64 url string to bytes.
2777
- * @param base64Url The base64 url string.
2778
- * @returns The bytes.
3320
+ * Date Time.
2779
3321
  */
2780
- static base64UrlToBytes(base64Url) {
2781
- return Base64Url.decode(base64Url);
2782
- }
3322
+ DateTime: "datetime",
2783
3323
  /**
2784
- * Convert bytes to base58 string.
2785
- * @param bytes The bytes to convert.
2786
- * @returns A base58 string of the bytes.
3324
+ * Time.
2787
3325
  */
2788
- static bytesToBase58(bytes) {
2789
- return Base58.encode(bytes);
2790
- }
3326
+ Time: "time",
2791
3327
  /**
2792
- * Convert a base58 string to bytes.
2793
- * @param base58 The base58 string.
2794
- * @returns The bytes.
3328
+ * Object.
2795
3329
  */
2796
- static base58ToBytes(base58) {
2797
- return Base58.decode(base58);
2798
- }
3330
+ Object: "object",
2799
3331
  /**
2800
- * Build the static lookup tables.
2801
- * @internal
3332
+ * Uint8Array.
2802
3333
  */
2803
- static buildHexLookups() {
2804
- if (!Converter._ENCODE_LOOKUP || !Converter._DECODE_LOOKUP) {
2805
- const alphabet = "0123456789abcdef";
2806
- Converter._ENCODE_LOOKUP = [];
2807
- Converter._DECODE_LOOKUP = [];
2808
- for (let i = 0; i < 256; i++) {
2809
- Converter._ENCODE_LOOKUP[i] = alphabet[(i >> 4) & 0xf] + alphabet[i & 0xf];
2810
- if (i < 16) {
2811
- if (i < 10) {
2812
- Converter._DECODE_LOOKUP[0x30 + i] = i;
2813
- }
2814
- else {
2815
- Converter._DECODE_LOOKUP[0x61 - 10 + i] = i;
2816
- }
2817
- }
2818
- }
2819
- }
2820
- }
2821
- }
3334
+ Uint8Array: "uint8array"
3335
+ };
2822
3336
 
3337
+ // Copyright 2024 IOTA Stiftung.
3338
+ // SPDX-License-Identifier: Apache-2.0.
2823
3339
  /**
2824
- * Class to help with objects.
3340
+ * Coerce an object from one type to another.
2825
3341
  */
2826
- class ObjectHelper {
2827
- /**
2828
- * Runtime name for the class.
2829
- * @internal
2830
- */
2831
- static _CLASS_NAME = "ObjectHelper";
3342
+ class Coerce {
2832
3343
  /**
2833
- * Convert an object to bytes.
2834
- * @param obj The object to convert.
2835
- * @param format Format the JSON content.
2836
- * @returns The object as bytes.
3344
+ * Coerce the value to a string.
3345
+ * @param value The value to coerce.
3346
+ * @throws TypeError If the value can not be coerced.
3347
+ * @returns The value if it can be coerced.
2837
3348
  */
2838
- static toBytes(obj, format = false) {
2839
- if (obj === undefined) {
2840
- return new Uint8Array();
3349
+ static string(value) {
3350
+ if (Is.undefined(value)) {
3351
+ return value;
3352
+ }
3353
+ if (Is.string(value)) {
3354
+ return value;
3355
+ }
3356
+ if (Is.number(value)) {
3357
+ return value.toString();
3358
+ }
3359
+ if (Is.boolean(value)) {
3360
+ return value ? "true" : "false";
3361
+ }
3362
+ if (Is.date(value)) {
3363
+ return value.toISOString();
2841
3364
  }
2842
- const json = format ? JSON.stringify(obj, undefined, "\t") : JSON.stringify(obj);
2843
- return Converter.utf8ToBytes(json);
2844
3365
  }
2845
3366
  /**
2846
- * Convert a bytes to an object.
2847
- * @param bytes The bytes to convert to an object.
2848
- * @returns The object.
2849
- * @throws GeneralError if there was an error parsing the JSON.
3367
+ * Coerce the value to a number.
3368
+ * @param value The value to coerce.
3369
+ * @throws TypeError If the value can not be coerced.
3370
+ * @returns The value if it can be coerced.
2850
3371
  */
2851
- static fromBytes(bytes) {
2852
- if (Is.empty(bytes) || bytes.length === 0) {
2853
- return undefined;
3372
+ static number(value) {
3373
+ if (Is.undefined(value)) {
3374
+ return value;
2854
3375
  }
2855
- try {
2856
- const utf8 = Converter.bytesToUtf8(bytes);
2857
- return JSON.parse(utf8);
3376
+ if (Is.number(value)) {
3377
+ return value;
2858
3378
  }
2859
- catch (err) {
2860
- throw new GeneralError(ObjectHelper._CLASS_NAME, "failedBytesToJSON", undefined, err);
3379
+ if (Is.string(value)) {
3380
+ const parsed = Number.parseFloat(value);
3381
+ if (Is.number(parsed)) {
3382
+ return parsed;
3383
+ }
3384
+ }
3385
+ if (Is.boolean(value)) {
3386
+ return value ? 1 : 0;
3387
+ }
3388
+ if (Is.date(value)) {
3389
+ return value.getTime();
2861
3390
  }
2862
3391
  }
2863
3392
  /**
2864
- * Make a deep clone of an object.
2865
- * @param obj The object to clone.
2866
- * @returns The objects clone.
3393
+ * Coerce the value to an integer.
3394
+ * @param value The value to coerce.
3395
+ * @throws TypeError If the value can not be coerced.
3396
+ * @returns The value if it can be coerced.
2867
3397
  */
2868
- static clone(obj) {
2869
- if (Is.undefined(obj)) {
2870
- return undefined;
3398
+ static integer(value) {
3399
+ const num = Coerce.number(value);
3400
+ if (!Is.undefined(num)) {
3401
+ return Math.trunc(num);
2871
3402
  }
2872
- return structuredClone(obj);
2873
3403
  }
2874
3404
  /**
2875
- * Deep merge objects.
2876
- * @param obj1 The first object to merge.
2877
- * @param obj2 The second object to merge.
2878
- * @returns The combined deep merge of the objects.
3405
+ * Coerce the value to a bigint.
3406
+ * @param value The value to coerce.
3407
+ * @throws TypeError If the value can not be coerced.
3408
+ * @returns The value if it can be coerced.
2879
3409
  */
2880
- static merge(obj1, obj2) {
2881
- if (Is.empty(obj1)) {
2882
- return ObjectHelper.clone(obj2);
3410
+ static bigint(value) {
3411
+ if (Is.undefined(value)) {
3412
+ return value;
2883
3413
  }
2884
- if (Is.empty(obj2)) {
2885
- return ObjectHelper.clone(obj1);
3414
+ if (Is.bigint(value)) {
3415
+ return value;
2886
3416
  }
2887
- const obj1Clone = ObjectHelper.clone(obj1);
2888
- if (Is.object(obj1Clone) && Is.object(obj2)) {
2889
- const keys = Object.keys(obj2);
2890
- for (const key of keys) {
2891
- if (Is.object(obj1Clone[key]) && Is.object(obj2[key])) {
2892
- ObjectHelper.propertySet(obj1Clone, key, ObjectHelper.merge(obj1Clone[key], obj2[key]));
2893
- }
2894
- else {
2895
- ObjectHelper.propertySet(obj1Clone, key, obj2[key]);
2896
- }
3417
+ if (Is.number(value)) {
3418
+ return BigInt(value);
3419
+ }
3420
+ if (Is.string(value)) {
3421
+ const parsed = Number.parseFloat(value);
3422
+ if (Is.integer(parsed)) {
3423
+ return BigInt(parsed);
3424
+ }
3425
+ }
3426
+ if (Is.boolean(value)) {
3427
+ return value ? 1n : 0n;
3428
+ }
3429
+ }
3430
+ /**
3431
+ * Coerce the value to a boolean.
3432
+ * @param value The value to coerce.
3433
+ * @throws TypeError If the value can not be coerced.
3434
+ * @returns The value if it can be coerced.
3435
+ */
3436
+ static boolean(value) {
3437
+ if (Is.undefined(value)) {
3438
+ return value;
3439
+ }
3440
+ if (Is.boolean(value)) {
3441
+ return value;
3442
+ }
3443
+ if (Is.number(value)) {
3444
+ // eslint-disable-next-line no-unneeded-ternary
3445
+ return value ? true : false;
3446
+ }
3447
+ if (Is.string(value)) {
3448
+ if (/true/i.test(value)) {
3449
+ return true;
3450
+ }
3451
+ if (/false/i.test(value)) {
3452
+ return false;
2897
3453
  }
2898
3454
  }
2899
- return obj1Clone;
2900
3455
  }
2901
3456
  /**
2902
- * Does one object equal another.
2903
- * @param obj1 The first object to compare.
2904
- * @param obj2 The second object to compare.
2905
- * @param strictPropertyOrder Should the properties be in the same order, defaults to true.
2906
- * @returns True is the objects are equal.
3457
+ * Coerce the value to a date.
3458
+ * @param value The value to coerce.
3459
+ * @throws TypeError If the value can not be coerced.
3460
+ * @returns The value if it can be coerced.
2907
3461
  */
2908
- static equal(obj1, obj2, strictPropertyOrder) {
2909
- if (strictPropertyOrder ?? true) {
2910
- return JSON.stringify(obj1) === JSON.stringify(obj2);
3462
+ static date(value) {
3463
+ if (Is.undefined(value)) {
3464
+ return value;
3465
+ }
3466
+ if (Is.date(value)) {
3467
+ return value;
3468
+ }
3469
+ if (Is.number(value)) {
3470
+ return new Date(value);
3471
+ }
3472
+ if (Is.string(value)) {
3473
+ const dt = new Date(value);
3474
+ if (!Number.isNaN(dt.getTime())) {
3475
+ const utc = Date.UTC(dt.getUTCFullYear(), dt.getUTCMonth(), dt.getUTCDate());
3476
+ return new Date(utc);
3477
+ }
2911
3478
  }
2912
- return JsonHelper.canonicalize(obj1) === JsonHelper.canonicalize(obj2);
2913
3479
  }
2914
3480
  /**
2915
- * Get the property of an unknown object.
2916
- * @param obj The object to get the property from.
2917
- * @param property The property to get, can be separated by dots for nested path.
2918
- * @returns The property.
3481
+ * Coerce the value to a date/time.
3482
+ * @param value The value to coerce.
3483
+ * @throws TypeError If the value can not be coerced.
3484
+ * @returns The value if it can be coerced.
2919
3485
  */
2920
- static propertyGet(obj, property) {
2921
- if (property.includes(".")) {
2922
- const parts = property.split(".");
2923
- let value = obj;
2924
- for (const part of parts) {
2925
- if (Is.object(value)) {
2926
- value = value[part];
2927
- }
2928
- else {
2929
- return undefined;
2930
- }
2931
- }
3486
+ static dateTime(value) {
3487
+ if (Is.undefined(value)) {
2932
3488
  return value;
2933
3489
  }
2934
- return Is.object(obj) ? obj[property] : undefined;
3490
+ if (Is.date(value)) {
3491
+ return value;
3492
+ }
3493
+ if (Is.number(value)) {
3494
+ return new Date(value);
3495
+ }
3496
+ if (Is.string(value)) {
3497
+ const dt = new Date(value);
3498
+ if (!Number.isNaN(dt.getTime())) {
3499
+ const utc = Date.UTC(dt.getUTCFullYear(), dt.getUTCMonth(), dt.getUTCDate(), dt.getUTCHours(), dt.getUTCMinutes(), dt.getUTCSeconds(), dt.getUTCMilliseconds());
3500
+ return new Date(utc);
3501
+ }
3502
+ }
2935
3503
  }
2936
3504
  /**
2937
- * Set the property of an unknown object.
2938
- * @param obj The object to set the property from.
2939
- * @param property The property to set.
2940
- * @param value The value to set.
3505
+ * Coerce the value to a time.
3506
+ * @param value The value to coerce.
3507
+ * @throws TypeError If the value can not be coerced.
3508
+ * @returns The value if it can be coerced.
2941
3509
  */
2942
- static propertySet(obj, property, value) {
2943
- if (Is.object(obj)) {
2944
- obj[property] = value;
3510
+ static time(value) {
3511
+ if (Is.undefined(value)) {
3512
+ return value;
3513
+ }
3514
+ if (Is.date(value)) {
3515
+ return value;
3516
+ }
3517
+ if (Is.number(value)) {
3518
+ const dt = new Date(value);
3519
+ dt.setFullYear(1970, 0, 1);
3520
+ return dt;
3521
+ }
3522
+ if (Is.string(value)) {
3523
+ const dt = new Date(value);
3524
+ if (!Number.isNaN(dt.getTime())) {
3525
+ const utc = Date.UTC(1970, 0, 1, dt.getUTCHours(), dt.getUTCMinutes(), dt.getUTCSeconds(), dt.getUTCMilliseconds());
3526
+ return new Date(utc);
3527
+ }
2945
3528
  }
2946
3529
  }
2947
3530
  /**
2948
- * Delete the property of an unknown object.
2949
- * @param obj The object to set the property from.
2950
- * @param property The property to set
3531
+ * Coerce the value to an object.
3532
+ * @param value The value to coerce.
3533
+ * @throws TypeError If the value can not be coerced.
3534
+ * @returns The value if it can be coerced.
2951
3535
  */
2952
- static propertyDelete(obj, property) {
2953
- if (Is.object(obj)) {
2954
- delete obj[property];
3536
+ static object(value) {
3537
+ if (Is.undefined(value)) {
3538
+ return value;
3539
+ }
3540
+ if (Is.object(value)) {
3541
+ return value;
3542
+ }
3543
+ if (Is.stringValue(value)) {
3544
+ try {
3545
+ return JSON.parse(value);
3546
+ }
3547
+ catch { }
2955
3548
  }
2956
3549
  }
2957
3550
  /**
2958
- * Extract a property from the object, providing alternative names.
2959
- * @param obj The object to extract from.
2960
- * @param propertyNames The possible names for the property.
2961
- * @param removeProperties Remove the properties from the object, defaults to true.
2962
- * @returns The property if available.
3551
+ * Coerce the value to a Uint8Array.
3552
+ * @param value The value to coerce.
3553
+ * @throws TypeError If the value can not be coerced.
3554
+ * @returns The value if it can be coerced.
2963
3555
  */
2964
- static extractProperty(obj, propertyNames, removeProperties = true) {
2965
- let retVal;
2966
- if (Is.object(obj)) {
2967
- const names = Is.string(propertyNames) ? [propertyNames] : propertyNames;
2968
- for (const prop of names) {
2969
- retVal ??= ObjectHelper.propertyGet(obj, prop);
2970
- if (removeProperties) {
2971
- ObjectHelper.propertyDelete(obj, prop);
2972
- }
3556
+ static uint8Array(value) {
3557
+ if (Is.undefined(value)) {
3558
+ return value;
3559
+ }
3560
+ if (Is.string(value)) {
3561
+ if (Is.stringHex(value.toLowerCase(), true)) {
3562
+ return Converter.hexToBytes(value.toLowerCase());
3563
+ }
3564
+ if (Is.stringBase64(value)) {
3565
+ return Converter.base64ToBytes(value);
2973
3566
  }
2974
3567
  }
2975
- return retVal;
2976
3568
  }
2977
3569
  /**
2978
- * Pick a subset of properties from an object.
2979
- * @param obj The object to pick the properties from.
2980
- * @param keys The property keys to pick.
2981
- * @returns The partial object.
2982
- */
2983
- static pick(obj, keys) {
2984
- if (Is.object(obj) && Is.arrayValue(keys)) {
2985
- const result = {};
2986
- for (const key of keys) {
2987
- result[key] = obj[key];
2988
- }
2989
- return result;
3570
+ * Coerces a value based on the coercion type.
3571
+ * @param value The value to coerce.
3572
+ * @param type The coercion type to perform.
3573
+ * @returns The coerced value.
3574
+ */
3575
+ static byType(value, type) {
3576
+ // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check
3577
+ switch (type) {
3578
+ case CoerceType.String:
3579
+ return Coerce.string(value);
3580
+ case CoerceType.Number:
3581
+ return Coerce.number(value);
3582
+ case CoerceType.Integer:
3583
+ return Coerce.integer(value);
3584
+ case CoerceType.BigInt:
3585
+ return Coerce.bigint(value);
3586
+ case CoerceType.Boolean:
3587
+ return Coerce.boolean(value);
3588
+ case CoerceType.Date:
3589
+ return Coerce.date(value);
3590
+ case CoerceType.DateTime:
3591
+ return Coerce.dateTime(value);
3592
+ case CoerceType.Time:
3593
+ return Coerce.time(value);
3594
+ case CoerceType.Object:
3595
+ return Coerce.object(value);
3596
+ case CoerceType.Uint8Array:
3597
+ return Coerce.uint8Array(value);
3598
+ default:
3599
+ return value;
2990
3600
  }
2991
- return obj;
2992
3601
  }
3602
+ }
3603
+
3604
+ // Copyright 2024 IOTA Stiftung.
3605
+ // SPDX-License-Identifier: Apache-2.0.
3606
+ /**
3607
+ * Class to help with filenames.
3608
+ */
3609
+ class FilenameHelper {
2993
3610
  /**
2994
- * Omit a subset of properties from an object.
2995
- * @param obj The object to omit the properties from.
2996
- * @param keys The property keys to omit.
2997
- * @returns The partial object.
3611
+ * Replaces any unsafe characters in the filename.
3612
+ * @param filename The filename to make safe.
3613
+ * @returns The safe filename.
2998
3614
  */
2999
- static omit(obj, keys) {
3000
- if (Is.object(obj) && Is.arrayValue(keys)) {
3001
- const result = { ...obj };
3002
- for (const key of keys) {
3003
- delete result[key];
3004
- }
3005
- return result;
3615
+ static safeFilename(filename) {
3616
+ let safe = Coerce.string(filename);
3617
+ if (Is.empty(safe)) {
3618
+ return "";
3006
3619
  }
3007
- return obj;
3620
+ // Common non filename characters
3621
+ safe = safe.replace(/["*/:<>?\\|]/g, "_");
3622
+ // Windows non filename characters
3623
+ safe = safe.replace(/^(con|prn|aux|nul|com\d|lpt\d)$/i, "_");
3624
+ // Control characters
3625
+ safe = safe.replace(/[\u0000-\u001F\u0080-\u009F]/g, "_");
3626
+ // Relative paths
3627
+ safe = safe.replace(/^\.+/, "_");
3628
+ // Trailing periods
3629
+ safe = safe.replace(/\.+$/, "");
3630
+ return safe;
3008
3631
  }
3009
3632
  }
3010
3633
 
@@ -3026,6 +3649,32 @@ class RandomHelper {
3026
3649
  }
3027
3650
  }
3028
3651
 
3652
+ // Copyright 2024 IOTA Stiftung.
3653
+ // SPDX-License-Identifier: Apache-2.0.
3654
+ /**
3655
+ * Class to help with uint8 arrays.
3656
+ */
3657
+ class Uint8ArrayHelper {
3658
+ /**
3659
+ * Concatenate multiple arrays.
3660
+ * @param arrays The array to concatenate.
3661
+ * @returns The combined array.
3662
+ */
3663
+ static concat(arrays) {
3664
+ let totalLength = 0;
3665
+ for (const array of arrays) {
3666
+ totalLength += array.length;
3667
+ }
3668
+ const concatBytes = new Uint8Array(totalLength);
3669
+ let offset = 0;
3670
+ for (const array of arrays) {
3671
+ concatBytes.set(array, offset);
3672
+ offset += array.length;
3673
+ }
3674
+ return concatBytes;
3675
+ }
3676
+ }
3677
+
3029
3678
  // Copyright 2024 IOTA Stiftung.
3030
3679
  // SPDX-License-Identifier: Apache-2.0.
3031
3680
  /**
@@ -3038,7 +3687,7 @@ const CompressionType = {
3038
3687
  */
3039
3688
  Gzip: "gzip",
3040
3689
  /**
3041
- * deflate.
3690
+ * Deflate.
3042
3691
  */
3043
3692
  Deflate: "deflate"
3044
3693
  };
@@ -3489,29 +4138,93 @@ class Urn {
3489
4138
  * Cache the results from asynchronous requests.
3490
4139
  */
3491
4140
  class AsyncCache {
3492
- /**
3493
- * Cache for the fetch requests.
3494
- * @internal
3495
- */
3496
- static _cache = {};
3497
4141
  /**
3498
4142
  * Execute an async request and cache the result.
3499
4143
  * @param key The key for the entry in the cache.
3500
4144
  * @param ttlMs The TTL of the entry in the cache.
3501
4145
  * @param requestMethod The method to call if not cached.
4146
+ * @param cacheFailures Cache failure results, defaults to false.
3502
4147
  * @returns The response.
3503
4148
  */
3504
- static exec(key, ttlMs, requestMethod) {
4149
+ static exec(key, ttlMs, requestMethod, cacheFailures) {
3505
4150
  const cacheEnabled = Is.integer(ttlMs) && ttlMs >= 0;
3506
4151
  if (cacheEnabled) {
3507
4152
  AsyncCache.cleanupExpired();
3508
- if (!this._cache[key]) {
3509
- this._cache[key] = {
3510
- response: requestMethod(),
3511
- expires: ttlMs === 0 ? 0 : Date.now() + ttlMs
3512
- };
4153
+ const cache = AsyncCache.getSharedCache();
4154
+ // Do we have a cache entry for the key
4155
+ if (cache[key]) {
4156
+ if (!Is.empty(cache[key].result)) {
4157
+ // If the cache has already resulted in a value, resolve it
4158
+ return Promise.resolve(cache[key].result);
4159
+ }
4160
+ else if (!Is.empty(cache[key].error)) {
4161
+ // If the cache has already resulted in an error, reject it
4162
+ return Promise.reject(cache[key].error);
4163
+ }
4164
+ // Otherwise create a promise to return and store the resolver
4165
+ // and rejector in the cache entry, so that we can call then
4166
+ // when the request is done
4167
+ let storedResolve;
4168
+ let storedReject;
4169
+ const wait = new Promise((resolve, reject) => {
4170
+ storedResolve = resolve;
4171
+ storedReject = reject;
4172
+ });
4173
+ if (!Is.empty(storedResolve) && !Is.empty(storedReject)) {
4174
+ cache[key].promiseQueue.push({
4175
+ requestMethod,
4176
+ resolve: storedResolve,
4177
+ reject: storedReject
4178
+ });
4179
+ }
4180
+ return wait;
3513
4181
  }
3514
- return this._cache[key].response;
4182
+ // If we don't have a cache entry, create a new one
4183
+ cache[key] = {
4184
+ promiseQueue: [],
4185
+ expires: ttlMs === 0 ? 0 : Date.now() + ttlMs
4186
+ };
4187
+ // Return a promise that wraps the original request method
4188
+ // so that we can store any results or errors in the cache
4189
+ return new Promise((resolve, reject) => {
4190
+ // Call the request method and store the result
4191
+ requestMethod()
4192
+ // eslint-disable-next-line promise/prefer-await-to-then
4193
+ .then(res => {
4194
+ // If the request was successful, store the result
4195
+ cache[key].result = res;
4196
+ // and resolve both this promise and all the waiters
4197
+ resolve(res);
4198
+ for (const wait of cache[key].promiseQueue) {
4199
+ wait.resolve(res);
4200
+ }
4201
+ return res;
4202
+ })
4203
+ // eslint-disable-next-line promise/prefer-await-to-then
4204
+ .catch((err) => {
4205
+ // Reject the promise
4206
+ reject(err);
4207
+ // Handle the waiters based on the cacheFailures flag
4208
+ if (cacheFailures ?? false) {
4209
+ // If we are caching failures, store the error and reject the waiters
4210
+ cache[key].error = err;
4211
+ for (const wait of cache[key].promiseQueue) {
4212
+ wait.reject(err);
4213
+ }
4214
+ // Clear the waiters so we don't call them again
4215
+ cache[key].promiseQueue = [];
4216
+ }
4217
+ else {
4218
+ // If not caching failures for any queued requests we
4219
+ // have no value to either resolve or reject, so we
4220
+ // just resolve with the original request method
4221
+ for (const wait of cache[key].promiseQueue) {
4222
+ wait.resolve(wait.requestMethod());
4223
+ }
4224
+ delete cache[key];
4225
+ }
4226
+ });
4227
+ });
3515
4228
  }
3516
4229
  }
3517
4230
  /**
@@ -3520,41 +4233,80 @@ class AsyncCache {
3520
4233
  * @returns The item from the cache if it exists.
3521
4234
  */
3522
4235
  static async get(key) {
3523
- return AsyncCache._cache[key]?.response;
4236
+ const cache = AsyncCache.getSharedCache();
4237
+ if (!Is.empty(cache[key].result)) {
4238
+ // If the cache has already resulted in a value, resolve it
4239
+ return cache[key].result;
4240
+ }
4241
+ else if (!Is.empty(cache[key].error)) {
4242
+ // If the cache has already resulted in an error, reject it
4243
+ throw cache[key].error;
4244
+ }
4245
+ }
4246
+ /**
4247
+ * Set an entry into the cache.
4248
+ * @param key The key to set in the cache.
4249
+ * @param value The value to set in the cache.
4250
+ * @param ttlMs The TTL of the entry in the cache in ms, defaults to 1s.
4251
+ * @returns Nothing.
4252
+ */
4253
+ static async set(key, value, ttlMs) {
4254
+ const cache = AsyncCache.getSharedCache();
4255
+ cache[key] = {
4256
+ result: value,
4257
+ promiseQueue: [],
4258
+ expires: Date.now() + (ttlMs ?? 1000)
4259
+ };
3524
4260
  }
3525
4261
  /**
3526
4262
  * Remove an entry from the cache.
3527
4263
  * @param key The key to remove from the cache.
3528
4264
  */
3529
4265
  static remove(key) {
3530
- delete AsyncCache._cache[key];
4266
+ const cache = AsyncCache.getSharedCache();
4267
+ delete cache[key];
3531
4268
  }
3532
4269
  /**
3533
4270
  * Clear the cache.
3534
4271
  * @param prefix Optional prefix to clear only entries with that prefix.
3535
4272
  */
3536
4273
  static clearCache(prefix) {
4274
+ const cache = AsyncCache.getSharedCache();
3537
4275
  if (Is.stringValue(prefix)) {
3538
- for (const entry in this._cache) {
4276
+ for (const entry in cache) {
3539
4277
  if (entry.startsWith(prefix)) {
3540
- delete this._cache[entry];
4278
+ delete cache[entry];
3541
4279
  }
3542
4280
  }
3543
4281
  }
3544
4282
  else {
3545
- AsyncCache._cache = {};
4283
+ SharedStore.set("asyncCache", {});
3546
4284
  }
3547
4285
  }
3548
4286
  /**
3549
4287
  * Perform a cleanup of the expired entries in the cache.
3550
4288
  */
3551
4289
  static cleanupExpired() {
3552
- for (const entry in this._cache) {
3553
- if (AsyncCache._cache[entry].expires > 0 && AsyncCache._cache[entry].expires < Date.now()) {
3554
- delete this._cache[entry];
4290
+ const cache = AsyncCache.getSharedCache();
4291
+ for (const entry in cache) {
4292
+ if (cache[entry].expires > 0 && cache[entry].expires < Date.now()) {
4293
+ delete cache[entry];
3555
4294
  }
3556
4295
  }
3557
4296
  }
4297
+ /**
4298
+ * Get the shared cache.
4299
+ * @returns The shared cache.
4300
+ * @internal
4301
+ */
4302
+ static getSharedCache() {
4303
+ let sharedCache = SharedStore.get("asyncCache");
4304
+ if (Is.undefined(sharedCache)) {
4305
+ sharedCache = {};
4306
+ SharedStore.set("asyncCache", sharedCache);
4307
+ }
4308
+ return sharedCache;
4309
+ }
3558
4310
  }
3559
4311
 
3560
4312
  /**
@@ -3575,16 +4327,15 @@ class Compression {
3575
4327
  static async compress(bytes, type) {
3576
4328
  Guards.uint8Array(Compression._CLASS_NAME, "bytes", bytes);
3577
4329
  Guards.arrayOneOf(Compression._CLASS_NAME, "type", type, Object.values(CompressionType));
3578
- const blob = new Blob([bytes]);
3579
- const ds = new CompressionStream(type);
3580
- const compressedStream = blob.stream().pipeThrough(ds);
3581
- const compressedBlob = await new Response(compressedStream).blob();
3582
- const ab = await compressedBlob.arrayBuffer();
3583
- const compressedBytes = new Uint8Array(ab);
4330
+ const blob = new Blob([new Uint8Array(bytes)]);
4331
+ const compressionStream = new CompressionStream(type);
4332
+ const compressionPipe = blob.stream().pipeThrough(compressionStream);
4333
+ const compressedBlob = await new Response(compressionPipe).blob();
4334
+ const compressedBytes = new Uint8Array(await compressedBlob.arrayBuffer());
3584
4335
  // GZIP header contains a byte which specifies the OS the
3585
4336
  // compression was performed on. We set this to 3 (Unix) to ensure
3586
4337
  // that we produce consistent results.
3587
- if (type === "gzip" && compressedBytes.length >= 10) {
4338
+ if (type === CompressionType.Gzip && compressedBytes.length >= 10) {
3588
4339
  compressedBytes[9] = 3;
3589
4340
  }
3590
4341
  return compressedBytes;
@@ -3598,12 +4349,11 @@ class Compression {
3598
4349
  static async decompress(compressedBytes, type) {
3599
4350
  Guards.uint8Array(Compression._CLASS_NAME, "compressedBytes", compressedBytes);
3600
4351
  Guards.arrayOneOf(Compression._CLASS_NAME, "type", type, Object.values(CompressionType));
3601
- const blob = new Blob([compressedBytes]);
3602
- const ds = new DecompressionStream(type);
3603
- const decompressedStream = blob.stream().pipeThrough(ds);
3604
- const decompressedBlob = await new Response(decompressedStream).blob();
3605
- const ab = await decompressedBlob.arrayBuffer();
3606
- return new Uint8Array(ab);
4352
+ const blob = new Blob([new Uint8Array(compressedBytes)]);
4353
+ const decompressionStream = new DecompressionStream(type);
4354
+ const decompressionPipe = blob.stream().pipeThrough(decompressionStream);
4355
+ const decompressedBlob = await new Response(decompressionPipe).blob();
4356
+ return new Uint8Array(await decompressedBlob.bytes());
3607
4357
  }
3608
4358
  }
3609
4359
 
@@ -3661,6 +4411,7 @@ class Validation {
3661
4411
  * @param options Additional options for the validation.
3662
4412
  * @param options.minLength The minimum length of the string.
3663
4413
  * @param options.maxLength The maximum length of the string.
4414
+ * @param options.format Specific format to check.
3664
4415
  * @returns True if the value is a valid string.
3665
4416
  */
3666
4417
  static string(property, value, failures, fieldNameResource, options) {
@@ -3679,6 +4430,47 @@ class Validation {
3679
4430
  const maxLimitDefined = Is.integer(maxLength);
3680
4431
  const belowMin = minLimitDefined && value.length < minLength;
3681
4432
  const aboveMax = maxLimitDefined && value.length > maxLength;
4433
+ if (options?.format === "base58" && !Is.stringBase58(value)) {
4434
+ failures.push({
4435
+ property,
4436
+ reason: "validation.beTextBase58",
4437
+ properties: {
4438
+ fieldName: fieldNameResource ?? "validation.defaultFieldName",
4439
+ value
4440
+ }
4441
+ });
4442
+ }
4443
+ else if (options?.format === "base64" && !Is.stringBase64(value)) {
4444
+ failures.push({
4445
+ property,
4446
+ reason: "validation.beTextBase64",
4447
+ properties: {
4448
+ fieldName: fieldNameResource ?? "validation.defaultFieldName",
4449
+ value
4450
+ }
4451
+ });
4452
+ }
4453
+ else if (options?.format === "hex" && !Is.stringHex(value)) {
4454
+ failures.push({
4455
+ property,
4456
+ reason: "validation.beTextHex",
4457
+ properties: {
4458
+ fieldName: fieldNameResource ?? "validation.defaultFieldName",
4459
+ value
4460
+ }
4461
+ });
4462
+ }
4463
+ else if (Is.regexp(options?.format) && !options.format.test(value)) {
4464
+ failures.push({
4465
+ property,
4466
+ reason: "validation.beTextRegExp",
4467
+ properties: {
4468
+ fieldName: fieldNameResource ?? "validation.defaultFieldName",
4469
+ value,
4470
+ format: options?.format
4471
+ }
4472
+ });
4473
+ }
3682
4474
  if (minLimitDefined && maxLimitDefined && (belowMin || aboveMax)) {
3683
4475
  failures.push({
3684
4476
  property,
@@ -4338,4 +5130,4 @@ class Validation {
4338
5130
  }
4339
5131
  }
4340
5132
 
4341
- export { AlreadyExistsError, ArrayHelper, AsyncCache, Base32, Base58, Base64, Base64Url, BaseError, BitString, Coerce, ComponentFactory, Compression, CompressionType, ConflictError, Converter, ErrorHelper, Factory, FilenameHelper, GeneralError, GuardError, Guards, HexHelper, I18n, Is, JsonHelper, NotFoundError, NotImplementedError, NotSupportedError, ObjectHelper, RandomHelper, StringHelper, UnauthorizedError, UnprocessableError, Url, Urn, Validation, ValidationError };
5133
+ export { AlreadyExistsError, ArrayHelper, AsyncCache, Base32, Base58, Base64, Base64Url, BaseError, BitString, Coerce, CoerceType, ComponentFactory, Compression, CompressionType, ConflictError, Converter, EnvHelper, ErrorHelper, Factory, FilenameHelper, GeneralError, GuardError, Guards, HexHelper, I18n, Is, JsonHelper, NotFoundError, NotImplementedError, NotSupportedError, ObjectHelper, RandomHelper, SharedStore, StringHelper, Uint8ArrayHelper, UnauthorizedError, UnprocessableError, Url, Urn, Validation, ValidationError };