@exodus/bytes 1.0.0-rc.5 → 1.0.0-rc.7

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/hex.js CHANGED
@@ -2,30 +2,16 @@ import { assertUint8 } from './assert.js'
2
2
  import { typedView } from './array.js'
3
3
  import * as js from './fallback/hex.js'
4
4
 
5
- const { Buffer } = globalThis // Buffer is optional, only used when native
6
- const haveNativeBuffer = Buffer && !Buffer.TYPED_ARRAY_SUPPORT
7
5
  const { toHex: webHex } = Uint8Array.prototype // Modern engines have this
8
6
 
9
- const { E_HEX } = js
10
-
11
7
  export function toHex(arr) {
12
8
  assertUint8(arr)
13
9
  if (arr.length === 0) return ''
14
10
  if (webHex && arr.toHex === webHex) return arr.toHex()
15
- if (!haveNativeBuffer) return js.toHex(arr)
16
- if (arr.constructor === Buffer && Buffer.isBuffer(arr)) return arr.toString('hex')
17
- return Buffer.from(arr.buffer, arr.byteOffset, arr.byteLength).toString('hex')
11
+ return js.toHex(arr)
18
12
  }
19
13
 
20
14
  // Unlike Buffer.from(), throws on invalid input
21
15
  export const fromHex = Uint8Array.fromHex
22
16
  ? (str, format = 'uint8') => typedView(Uint8Array.fromHex(str), format)
23
- : haveNativeBuffer
24
- ? (str, format = 'uint8') => {
25
- if (typeof str !== 'string') throw new TypeError('Input is not a string')
26
- if (str.length % 2 !== 0) throw new SyntaxError(E_HEX)
27
- const buf = Buffer.from(str, 'hex') // will stop on first non-hex character, so we can just validate length
28
- if (buf.length * 2 !== str.length) throw new SyntaxError(E_HEX)
29
- return typedView(buf, format)
30
- }
31
- : (str, format = 'uint8') => typedView(js.fromHex(str), format)
17
+ : (str, format = 'uint8') => typedView(js.fromHex(str), format)
package/hex.node.js ADDED
@@ -0,0 +1,26 @@
1
+ import { assertUint8 } from './assert.js'
2
+ import { typedView } from './array.js'
3
+ import { E_HEX } from './fallback/hex.js'
4
+
5
+ if (Buffer.TYPED_ARRAY_SUPPORT) throw new Error('Unexpected Buffer polyfill')
6
+
7
+ const { toHex: webHex } = Uint8Array.prototype // Modern engines have this
8
+
9
+ export function toHex(arr) {
10
+ assertUint8(arr)
11
+ if (arr.length === 0) return ''
12
+ if (webHex && arr.toHex === webHex) return arr.toHex()
13
+ if (arr.constructor === Buffer && Buffer.isBuffer(arr)) return arr.hexSlice(0, arr.byteLength)
14
+ return Buffer.from(arr.buffer, arr.byteOffset, arr.byteLength).hexSlice(0, arr.byteLength)
15
+ }
16
+
17
+ // Unlike Buffer.from(), throws on invalid input
18
+ export const fromHex = Uint8Array.fromHex
19
+ ? (str, format = 'uint8') => typedView(Uint8Array.fromHex(str), format)
20
+ : (str, format = 'uint8') => {
21
+ if (typeof str !== 'string') throw new TypeError('Input is not a string')
22
+ if (str.length % 2 !== 0) throw new SyntaxError(E_HEX)
23
+ const buf = Buffer.from(str, 'hex') // will stop on first non-hex character, so we can just validate length
24
+ if (buf.length * 2 !== str.length) throw new SyntaxError(E_HEX)
25
+ return typedView(buf, format)
26
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/bytes",
3
- "version": "1.0.0-rc.5",
3
+ "version": "1.0.0-rc.7",
4
4
  "description": "Various operations on Uint8Array data",
5
5
  "scripts": {
6
6
  "lint": "eslint .",
@@ -39,6 +39,7 @@
39
39
  "type": "module",
40
40
  "files": [
41
41
  "/fallback/_utils.js",
42
+ "/fallback/latin1.js",
42
43
  "/fallback/base32.js",
43
44
  "/fallback/base64.js",
44
45
  "/fallback/hex.js",
@@ -49,7 +50,9 @@
49
50
  "/base58.js",
50
51
  "/base58check.js",
51
52
  "/base64.js",
53
+ "/hex.node.js",
52
54
  "/hex.js",
55
+ "/utf8.node.js",
53
56
  "/utf8.js"
54
57
  ],
55
58
  "exports": {
@@ -58,8 +61,18 @@
58
61
  "./base58.js": "./base58.js",
59
62
  "./base58check.js": "./base58check.js",
60
63
  "./base64.js": "./base64.js",
61
- "./hex.js": "./hex.js",
62
- "./utf8.js": "./utf8.js"
64
+ "./hex.js": {
65
+ "node": "./hex.node.js",
66
+ "default": "./hex.js"
67
+ },
68
+ "./utf16.js": {
69
+ "node": "./utf16.node.js",
70
+ "default": "./utf16.js"
71
+ },
72
+ "./utf8.js": {
73
+ "node": "./utf8.node.js",
74
+ "default": "./utf8.js"
75
+ }
63
76
  },
64
77
  "peerDependencies": {
65
78
  "@exodus/crypto": "^1.0.0-rc.4"
@@ -74,12 +87,12 @@
74
87
  "@exodus/crypto": "1.0.0-rc.29",
75
88
  "@exodus/eslint-config": "^5.24.0",
76
89
  "@exodus/prettier": "^1.0.0",
77
- "@exodus/test": "^1.0.0-rc.107",
90
+ "@exodus/test": "^1.0.0-rc.108",
78
91
  "@noble/hashes": "^2.0.1",
79
92
  "@scure/base": "^1.2.6",
80
93
  "@stablelib/base64": "^2.0.1",
81
94
  "@stablelib/hex": "^2.0.1",
82
- "@types/node": "^24.0.10",
95
+ "@types/node": "^22.13.0",
83
96
  "base-x": "^5.0.1",
84
97
  "base32.js": "^0.1.0",
85
98
  "base64-js": "^1.5.1",
@@ -91,9 +104,12 @@
91
104
  "eslint": "^8.44.0",
92
105
  "fast-base64-decode": "^2.0.0",
93
106
  "fast-base64-encode": "^1.0.0",
107
+ "hextreme": "^1.0.7",
94
108
  "hi-base32": "^0.5.1",
109
+ "iconv-lite": "^0.7.0",
95
110
  "jsvu": "^3.0.0",
96
- "text-encoding": "^0.7.0"
111
+ "text-encoding": "^0.7.0",
112
+ "typescript": "^5.9.3"
97
113
  },
98
114
  "prettier": "@exodus/prettier",
99
115
  "packageManager": "pnpm@10.12.1+sha256.889bac470ec93ccc3764488a19d6ba8f9c648ad5e50a9a6e4be3768a5de387a3"
package/utf8.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { assertUint8 } from './assert.js'
2
2
  import { typedView } from './array.js'
3
3
  import * as js from './fallback/utf8.js'
4
+ import { asciiPrefix, decodeLatin1 } from './fallback/latin1.js'
4
5
 
5
6
  const { Buffer, TextEncoder, TextDecoder, decodeURIComponent, escape } = globalThis // Buffer is optional
6
7
  const haveNativeBuffer = Buffer && !Buffer.TYPED_ARRAY_SUPPORT
@@ -18,7 +19,7 @@ const { E_STRICT, E_STRICT_UNICODE } = js
18
19
  const shouldUseEscapePath = Boolean(globalThis.HermesInternal) // faster only on Hermes, js path beats it on normal engines
19
20
 
20
21
  function deLoose(str, loose, res) {
21
- if (loose) return res
22
+ if (loose || str.length === res.length) return res // length is equal only for ascii, which is automatically fine
22
23
  if (isWellFormed) {
23
24
  // We have a fast native method
24
25
  if (isWellFormed.call(str)) return res
@@ -51,64 +52,27 @@ function encode(str, loose = false) {
51
52
  return js.encode(str, loose)
52
53
  }
53
54
 
54
- let escapes
55
-
56
- function toEscapesPart(arr, start, end) {
57
- let o = ''
58
- let i = start
59
- const last3 = end - 3
60
- // Unrolled loop is faster
61
- while (i < last3) {
62
- const a = arr[i++]
63
- const b = arr[i++]
64
- const c = arr[i++]
65
- const d = arr[i++]
66
- o += escapes[a]
67
- o += escapes[b]
68
- o += escapes[c]
69
- o += escapes[d]
70
- }
71
-
72
- while (i < end) o += escapes[arr[i++]]
73
- return o
74
- }
75
-
76
55
  function decode(arr, loose = false) {
77
56
  assertUint8(arr)
78
57
  if (haveDecoder) return loose ? decoderLoose.decode(arr) : decoderFatal.decode(arr) // Node.js and browsers
79
58
  // No reason to use native Buffer: it's not faster than TextDecoder, needs rechecks in non-loose mode, and Node.js has TextDecoder
80
59
 
81
- // This codepath gives a ~2x perf boost on Hermes
82
- if (shouldUseEscapePath && escape && decodeURIComponent) {
83
- if (!escapes) escapes = Array.from({ length: 256 }, (_, i) => escape(String.fromCharCode(i)))
84
- const length = arr.length
85
- let o
86
- if (length > 30_000) {
87
- // Limit concatenation to avoid excessive GC
88
- // TODO: recheck thresholds on Hermes (taken from hex)
89
- const concat = []
90
- for (let i = 0; i < length; ) {
91
- const step = i + 500
92
- const end = step > length ? length : step
93
- concat.push(toEscapesPart(arr, i, end))
94
- i = end
95
- }
96
-
97
- o = concat.join('')
98
- concat.length = 0
99
- } else {
100
- o = toEscapesPart(arr, 0, length)
101
- }
60
+ // Fast path for ASCII prefix, this is faster than all alternatives below
61
+ const prefix = decodeLatin1(arr, 0, asciiPrefix(arr))
62
+ if (prefix.length === arr.length) return prefix
102
63
 
64
+ // This codepath gives a ~3x perf boost on Hermes
65
+ if (shouldUseEscapePath && escape && decodeURIComponent) {
66
+ const o = escape(decodeLatin1(arr, prefix.length, arr.length))
103
67
  try {
104
- return decodeURIComponent(o) // asci to utf8, escape() is precalucated
68
+ return prefix + decodeURIComponent(o) // Latin1 to utf8
105
69
  } catch {
106
70
  if (!loose) throw new TypeError(E_STRICT)
107
71
  // Ok, we have to use manual implementation for loose decoder
108
72
  }
109
73
  }
110
74
 
111
- return js.decode(arr, loose)
75
+ return prefix + js.decode(arr, loose, prefix.length)
112
76
  }
113
77
 
114
78
  export const utf8fromString = (str, format = 'uint8') => typedView(encode(str, false), format)
package/utf8.node.js ADDED
@@ -0,0 +1,33 @@
1
+ import { assertUint8 } from './assert.js'
2
+ import { typedView } from './array.js'
3
+ import { E_STRICT_UNICODE } from './fallback/utf8.js'
4
+ import { isAscii } from 'node:buffer'
5
+
6
+ if (Buffer.TYPED_ARRAY_SUPPORT) throw new Error('Unexpected Buffer polyfill')
7
+
8
+ const decoderFatal = new TextDecoder('utf8', { ignoreBOM: true, fatal: true })
9
+ const decoderLoose = new TextDecoder('utf8', { ignoreBOM: true })
10
+ const { isWellFormed } = String.prototype
11
+
12
+ function encode(str, loose = false) {
13
+ if (typeof str !== 'string') throw new TypeError('Input is not a string')
14
+ const res = Buffer.from(str)
15
+ if (loose || str.length === res.length || isWellFormed.call(str)) return res // length is equal only for ascii, which is automatically fine
16
+ throw new TypeError(E_STRICT_UNICODE)
17
+ }
18
+
19
+ function decode(arr, loose = false) {
20
+ assertUint8(arr)
21
+ if (isAscii(arr)) {
22
+ // On non-ascii strings, this loses ~10% * [relative position of the first non-ascii byte] (up to 10% total)
23
+ // On ascii strings, this wins 1.5x on loose = false and 1.3x on loose = true
24
+ return Buffer.from(arr.buffer, arr.byteOffset, arr.byteLength).latin1Slice(0, arr.byteLength) // .latin1Slice is faster than .asciiSlice
25
+ }
26
+
27
+ return loose ? decoderLoose.decode(arr) : decoderFatal.decode(arr)
28
+ }
29
+
30
+ export const utf8fromString = (str, format = 'uint8') => typedView(encode(str, false), format)
31
+ export const utf8fromStringLoose = (str, format = 'uint8') => typedView(encode(str, true), format)
32
+ export const utf8toString = (arr) => decode(arr, false)
33
+ export const utf8toStringLoose = (arr) => decode(arr, true)