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