@bcts/uniform-resources 1.0.0-alpha.21 → 1.0.0-alpha.23

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.
package/dist/index.mjs CHANGED
@@ -1,6 +1,5 @@
1
1
  import { MajorType, cbor, decodeCbor } from "@bcts/dcbor";
2
2
  import { sha256 } from "@bcts/crypto";
3
-
4
3
  //#region src/error.ts
5
4
  /**
6
5
  * Copyright © 2023-2026 Blockchain Commons, LLC
@@ -94,7 +93,6 @@ var URDecodeError = class extends URError {
94
93
  function isError(result) {
95
94
  return result instanceof Error;
96
95
  }
97
-
98
96
  //#endregion
99
97
  //#region src/utils.ts
100
98
  /**
@@ -652,36 +650,102 @@ const BYTEMOJIS = [
652
650
  "🐳"
653
651
  ];
654
652
  /**
655
- * Encodes a 4-byte slice as a string of bytewords for identification.
653
+ * Encodes an arbitrary byte slice as a string of space-separated bytewords.
654
+ *
655
+ * Mirrors `bytewords::encode_to_words` in `bc-ur-rust` (≥ v0.19.1). Does not
656
+ * add a CRC32 checksum — use {@link encodeBytewords} for UR-style encoding.
656
657
  */
657
- function encodeBytewordsIdentifier(data) {
658
- if (data.length !== 4) throw new Error("Identifier data must be exactly 4 bytes");
658
+ function encodeToWords(data) {
659
659
  const words = [];
660
- for (let i = 0; i < 4; i++) {
661
- const byte = data[i];
662
- if (byte === void 0) throw new Error("Invalid byte");
660
+ for (const byte of data) {
663
661
  const word = BYTEWORDS[byte];
664
- if (word === "" || word === void 0) throw new Error("Invalid byteword mapping");
662
+ if (word === void 0) throw new Error(`Invalid byte value: ${byte}`);
665
663
  words.push(word);
666
664
  }
667
665
  return words.join(" ");
668
666
  }
669
667
  /**
670
- * Encodes a 4-byte slice as a string of bytemojis for identification.
668
+ * Encodes an arbitrary byte slice as a string of space-separated bytemojis.
669
+ *
670
+ * Mirrors `bytewords::encode_to_bytemojis` in `bc-ur-rust` (≥ v0.19.1).
671
671
  */
672
- function encodeBytemojisIdentifier(data) {
673
- if (data.length !== 4) throw new Error("Identifier data must be exactly 4 bytes");
672
+ function encodeToBytemojis(data) {
674
673
  const emojis = [];
675
- for (let i = 0; i < 4; i++) {
676
- const byte = data[i];
677
- if (byte === void 0) throw new Error("Invalid byte");
674
+ for (const byte of data) {
678
675
  const emoji = BYTEMOJIS[byte];
679
- if (emoji === "" || emoji === void 0) throw new Error("Invalid bytemoji mapping");
676
+ if (emoji === void 0) throw new Error(`Invalid byte value: ${byte}`);
680
677
  emojis.push(emoji);
681
678
  }
682
679
  return emojis.join(" ");
683
680
  }
684
681
  /**
682
+ * Encodes an arbitrary byte slice as minimal bytewords (first + last letter of
683
+ * each word, concatenated with no separator).
684
+ *
685
+ * Mirrors `bytewords::encode_to_minimal_bytewords` in `bc-ur-rust`
686
+ * (≥ v0.19.1). Does not add a CRC32 checksum.
687
+ */
688
+ function encodeToMinimalBytewords(data) {
689
+ let out = "";
690
+ for (const byte of data) {
691
+ const word = BYTEWORDS[byte];
692
+ if (word === void 0) throw new Error(`Invalid byte value: ${byte}`);
693
+ out += word[0] + word[word.length - 1];
694
+ }
695
+ return out;
696
+ }
697
+ /**
698
+ * Encodes a 4-byte slice as a string of bytewords for identification.
699
+ *
700
+ * Thin wrapper over {@link encodeToWords} that enforces the 4-byte length
701
+ * contract historically used by `bc-ur-rust`'s `bytewords::identifier`.
702
+ */
703
+ function encodeBytewordsIdentifier(data) {
704
+ if (data.length !== 4) throw new Error("Identifier data must be exactly 4 bytes");
705
+ return encodeToWords(data);
706
+ }
707
+ /**
708
+ * Encodes a 4-byte slice as a string of bytemojis for identification.
709
+ *
710
+ * Thin wrapper over {@link encodeToBytemojis} that enforces the 4-byte length
711
+ * contract historically used by `bc-ur-rust`'s `bytewords::bytemoji_identifier`.
712
+ */
713
+ function encodeBytemojisIdentifier(data) {
714
+ if (data.length !== 4) throw new Error("Identifier data must be exactly 4 bytes");
715
+ return encodeToBytemojis(data);
716
+ }
717
+ /**
718
+ * Returns `true` if `emoji` is one of the 256 bytemojis.
719
+ *
720
+ * Mirrors `bytewords::is_valid_bytemoji` in `bc-ur-rust` (≥ v0.19.1).
721
+ */
722
+ function isValidBytemoji(emoji) {
723
+ return BYTEMOJI_SET.has(emoji);
724
+ }
725
+ /**
726
+ * Canonicalises a byteword token (2–4 ASCII letters, case-insensitive) to its
727
+ * full 4-letter lowercase form. Returns `undefined` if the token is not a
728
+ * valid byteword or any of its short forms.
729
+ *
730
+ * Mirrors `bytewords::canonicalize_byteword` in `bc-ur-rust` (≥ v0.19.1).
731
+ *
732
+ * - 2-letter tokens are matched against the first + last letter of each
733
+ * byteword (identical to the minimal bytewords encoding).
734
+ * - 3-letter tokens are matched against the first 3 and the last 3 letters of
735
+ * each byteword; if both match different entries, the first-3 match wins
736
+ * (matching rust's `or_else` priority).
737
+ * - 4-letter tokens must exactly match a full byteword (after lower-casing).
738
+ */
739
+ function canonicalizeByteword(token) {
740
+ const lower = token.toLowerCase();
741
+ switch (lower.length) {
742
+ case 4: return BYTEWORDS_MAP.has(lower) ? lower : void 0;
743
+ case 2: return BYTEWORD_FIRST_LAST_MAP.get(lower);
744
+ case 3: return BYTEWORD_FIRST_THREE_MAP.get(lower) ?? BYTEWORD_LAST_THREE_MAP.get(lower);
745
+ default: return;
746
+ }
747
+ }
748
+ /**
685
749
  * Bytewords encoding style.
686
750
  */
687
751
  let BytewordsStyle = /* @__PURE__ */ function(BytewordsStyle) {
@@ -706,6 +770,38 @@ function createMinimalBytewordsMap() {
706
770
  }
707
771
  const MINIMAL_BYTEWORDS_MAP = createMinimalBytewordsMap();
708
772
  /**
773
+ * Set of all 256 bytemojis for fast membership testing. Backs
774
+ * {@link isValidBytemoji}.
775
+ */
776
+ const BYTEMOJI_SET = new Set(BYTEMOJIS);
777
+ /**
778
+ * Lookup from a 2-letter (first+last) byteword short-form to its full
779
+ * lowercase 4-letter form. Backs {@link canonicalizeByteword}.
780
+ */
781
+ const BYTEWORD_FIRST_LAST_MAP = (() => {
782
+ const map = /* @__PURE__ */ new Map();
783
+ for (const word of BYTEWORDS) map.set(word[0] + word[word.length - 1], word);
784
+ return map;
785
+ })();
786
+ /**
787
+ * Lookup from the first 3 letters of a byteword to its full lowercase 4-letter
788
+ * form. Backs {@link canonicalizeByteword}.
789
+ */
790
+ const BYTEWORD_FIRST_THREE_MAP = (() => {
791
+ const map = /* @__PURE__ */ new Map();
792
+ for (const word of BYTEWORDS) map.set(word.slice(0, 3), word);
793
+ return map;
794
+ })();
795
+ /**
796
+ * Lookup from the last 3 letters of a byteword to its full lowercase 4-letter
797
+ * form. Backs {@link canonicalizeByteword}.
798
+ */
799
+ const BYTEWORD_LAST_THREE_MAP = (() => {
800
+ const map = /* @__PURE__ */ new Map();
801
+ for (const word of BYTEWORDS) map.set(word.slice(1), word);
802
+ return map;
803
+ })();
804
+ /**
709
805
  * CRC32 lookup table (IEEE polynomial).
710
806
  */
711
807
  const CRC32_TABLE = (() => {
@@ -740,7 +836,7 @@ function uint32ToBytes(value) {
740
836
  * Encode data as bytewords with the specified style.
741
837
  * Includes CRC32 checksum.
742
838
  */
743
- function encodeBytewords(data, style = BytewordsStyle.Minimal) {
839
+ function encodeBytewords(data, style = "minimal") {
744
840
  const checksumBytes = uint32ToBytes(crc32(data));
745
841
  const dataWithChecksum = new Uint8Array(data.length + 4);
746
842
  dataWithChecksum.set(data);
@@ -750,46 +846,46 @@ function encodeBytewords(data, style = BytewordsStyle.Minimal) {
750
846
  const word = BYTEWORDS[byte];
751
847
  if (word === void 0) throw new Error(`Invalid byte value: ${byte}`);
752
848
  switch (style) {
753
- case BytewordsStyle.Standard:
849
+ case "standard":
754
850
  words.push(word);
755
851
  break;
756
- case BytewordsStyle.Uri:
852
+ case "uri":
757
853
  words.push(word);
758
854
  break;
759
- case BytewordsStyle.Minimal:
855
+ case "minimal":
760
856
  words.push(word[0] + word[3]);
761
857
  break;
762
858
  }
763
859
  }
764
860
  switch (style) {
765
- case BytewordsStyle.Standard: return words.join(" ");
766
- case BytewordsStyle.Uri: return words.join("-");
767
- case BytewordsStyle.Minimal: return words.join("");
861
+ case "standard": return words.join(" ");
862
+ case "uri": return words.join("-");
863
+ case "minimal": return words.join("");
768
864
  }
769
865
  }
770
866
  /**
771
867
  * Decode bytewords string back to data.
772
868
  * Validates and removes CRC32 checksum.
773
869
  */
774
- function decodeBytewords(encoded, style = BytewordsStyle.Minimal) {
870
+ function decodeBytewords(encoded, style = "minimal") {
775
871
  const lowercased = encoded.toLowerCase();
776
872
  let bytes;
777
873
  switch (style) {
778
- case BytewordsStyle.Standard:
874
+ case "standard":
779
875
  bytes = lowercased.split(" ").map((word) => {
780
876
  const index = BYTEWORDS_MAP.get(word);
781
877
  if (index === void 0) throw new Error(`Invalid byteword: ${word}`);
782
878
  return index;
783
879
  });
784
880
  break;
785
- case BytewordsStyle.Uri:
881
+ case "uri":
786
882
  bytes = lowercased.split("-").map((word) => {
787
883
  const index = BYTEWORDS_MAP.get(word);
788
884
  if (index === void 0) throw new Error(`Invalid byteword: ${word}`);
789
885
  return index;
790
886
  });
791
887
  break;
792
- case BytewordsStyle.Minimal:
888
+ case "minimal":
793
889
  if (lowercased.length % 2 !== 0) throw new Error("Invalid minimal bytewords length");
794
890
  bytes = [];
795
891
  for (let i = 0; i < lowercased.length; i += 2) {
@@ -809,7 +905,6 @@ function decodeBytewords(encoded, style = BytewordsStyle.Minimal) {
809
905
  if (expectedChecksum !== actualChecksum) throw new Error(`Bytewords checksum mismatch: expected ${expectedChecksum.toString(16)}, got ${actualChecksum.toString(16)}`);
810
906
  return data;
811
907
  }
812
-
813
908
  //#endregion
814
909
  //#region src/ur-type.ts
815
910
  /**
@@ -893,7 +988,6 @@ var URType = class URType {
893
988
  }
894
989
  }
895
990
  };
896
-
897
991
  //#endregion
898
992
  //#region src/ur.ts
899
993
  /**
@@ -1036,7 +1130,7 @@ var UR = class UR {
1036
1130
  */
1037
1131
  var URStringEncoder = class {
1038
1132
  static encode(urType, cborData) {
1039
- return `ur:${urType}/${encodeBytewords(cborData, BytewordsStyle.Minimal)}`;
1133
+ return `ur:${urType}/${encodeBytewords(cborData, "minimal")}`;
1040
1134
  }
1041
1135
  };
1042
1136
  /**
@@ -1053,14 +1147,13 @@ var URStringDecoder = class {
1053
1147
  try {
1054
1148
  return {
1055
1149
  urType,
1056
- cbor: decodeCbor(decodeBytewords(data, BytewordsStyle.Minimal))
1150
+ cbor: decodeCbor(decodeBytewords(data, "minimal"))
1057
1151
  };
1058
1152
  } catch (error) {
1059
1153
  throw new URError(`Failed to decode UR: ${error instanceof Error ? error.message : String(error)}`);
1060
1154
  }
1061
1155
  }
1062
1156
  };
1063
-
1064
1157
  //#endregion
1065
1158
  //#region src/ur-encodable.ts
1066
1159
  /**
@@ -1069,7 +1162,6 @@ var URStringDecoder = class {
1069
1162
  function isUREncodable(obj) {
1070
1163
  return typeof obj === "object" && obj !== null && "ur" in obj && "urString" in obj && typeof obj["ur"] === "function" && typeof obj["urString"] === "function";
1071
1164
  }
1072
-
1073
1165
  //#endregion
1074
1166
  //#region src/ur-decodable.ts
1075
1167
  /**
@@ -1078,7 +1170,6 @@ function isUREncodable(obj) {
1078
1170
  function isURDecodable(obj) {
1079
1171
  return typeof obj === "object" && obj !== null && "fromUR" in obj && typeof obj["fromUR"] === "function";
1080
1172
  }
1081
-
1082
1173
  //#endregion
1083
1174
  //#region src/ur-codable.ts
1084
1175
  /**
@@ -1087,7 +1178,6 @@ function isURDecodable(obj) {
1087
1178
  function isURCodable(obj) {
1088
1179
  return typeof obj === "object" && obj !== null && "ur" in obj && "urString" in obj && "fromUR" in obj && typeof obj["ur"] === "function" && typeof obj["urString"] === "function" && typeof obj["fromUR"] === "function";
1089
1180
  }
1090
-
1091
1181
  //#endregion
1092
1182
  //#region src/xoshiro.ts
1093
1183
  /**
@@ -1299,7 +1389,6 @@ function createSeed(checksum, seqNum) {
1299
1389
  seed8[7] = checksum & 255;
1300
1390
  return sha256(seed8);
1301
1391
  }
1302
-
1303
1392
  //#endregion
1304
1393
  //#region src/fountain.ts
1305
1394
  /**
@@ -1552,7 +1641,6 @@ var FountainDecoder = class {
1552
1641
  this.mixedParts.clear();
1553
1642
  }
1554
1643
  };
1555
-
1556
1644
  //#endregion
1557
1645
  //#region src/multipart-encoder.ts
1558
1646
  /**
@@ -1601,7 +1689,8 @@ var MultipartEncoder = class {
1601
1689
  constructor(ur, maxFragmentLen) {
1602
1690
  if (maxFragmentLen < 1) throw new URError("Max fragment length must be at least 1");
1603
1691
  this._ur = ur;
1604
- this._fountainEncoder = new FountainEncoder(ur.cbor().toData(), maxFragmentLen);
1692
+ const cborData = ur.cbor().toData();
1693
+ this._fountainEncoder = new FountainEncoder(cborData, maxFragmentLen);
1605
1694
  }
1606
1695
  /**
1607
1696
  * Gets the next part of the encoding.
@@ -1627,7 +1716,7 @@ var MultipartEncoder = class {
1627
1716
  */
1628
1717
  _encodePart(part) {
1629
1718
  if (part.seqLen === 1) return this._ur.string();
1630
- const encoded = encodeBytewords(this._encodePartData(part), BytewordsStyle.Minimal);
1719
+ const encoded = encodeBytewords(this._encodePartData(part), "minimal");
1631
1720
  return `ur:${this._ur.urTypeStr()}/${part.seqNum}-${part.seqLen}/${encoded}`;
1632
1721
  }
1633
1722
  /**
@@ -1659,7 +1748,6 @@ var MultipartEncoder = class {
1659
1748
  return this._fountainEncoder.seqLen;
1660
1749
  }
1661
1750
  };
1662
-
1663
1751
  //#endregion
1664
1752
  //#region src/multipart-decoder.ts
1665
1753
  /**
@@ -1748,7 +1836,7 @@ var MultipartDecoder = class {
1748
1836
  * The multipart body is a CBOR array: [seqNum, seqLen, messageLen, checksum, data]
1749
1837
  */
1750
1838
  _decodeFountainPart(partInfo) {
1751
- const decoded = decodeCbor(decodeBytewords(partInfo.encodedData, BytewordsStyle.Minimal));
1839
+ const decoded = decodeCbor(decodeBytewords(partInfo.encodedData, "minimal"));
1752
1840
  if (decoded.type !== MajorType.Array) throw new URError("Invalid multipart data: expected CBOR array");
1753
1841
  const items = decoded.value;
1754
1842
  if (items.length !== 5) throw new URError(`Invalid multipart data: expected 5 elements, got ${items.length}`);
@@ -1781,7 +1869,7 @@ var MultipartDecoder = class {
1781
1869
  return this._decodedMessage;
1782
1870
  }
1783
1871
  };
1784
-
1785
1872
  //#endregion
1786
- export { BYTEMOJIS, BYTEWORDS, BytewordsError, BytewordsStyle, CBORError, InvalidSchemeError, InvalidTypeError, MultipartDecoder, MultipartEncoder, NotSinglePartError, TypeUnspecifiedError, UR, URDecodeError, URError, URType, UnexpectedTypeError, decodeBytewords, encodeBytemojisIdentifier, encodeBytewords, encodeBytewordsIdentifier, isError, isURCodable, isURDecodable, isUREncodable };
1873
+ export { BYTEMOJIS, BYTEWORDS, BytewordsError, BytewordsStyle, CBORError, InvalidSchemeError, InvalidTypeError, MultipartDecoder, MultipartEncoder, NotSinglePartError, TypeUnspecifiedError, UR, URDecodeError, URError, URType, UnexpectedTypeError, canonicalizeByteword, decodeBytewords, encodeBytemojisIdentifier, encodeBytewords, encodeBytewordsIdentifier, encodeToBytemojis, encodeToMinimalBytewords, encodeToWords, isError, isURCodable, isURDecodable, isUREncodable, isValidBytemoji };
1874
+
1787
1875
  //# sourceMappingURL=index.mjs.map