@exodus/bytes 1.14.1 → 1.15.0

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/base64.js CHANGED
@@ -1,6 +1,5 @@
1
1
  import { assertEmptyRest } from './assert.js'
2
- import { typedView } from './array.js'
3
- import { assertU8, E_STRING } from './fallback/_utils.js'
2
+ import { assertU8, fromUint8, fromBuffer, E_STRING } from './fallback/_utils.js'
4
3
  import { isHermes } from './fallback/platform.js'
5
4
  import { decodeLatin1, encodeLatin1 } from './fallback/latin1.js'
6
5
  import * as js from './fallback/base64.js'
@@ -96,7 +95,7 @@ function fromBase64common(str, isBase64url, padding, format, rest) {
96
95
  throw new TypeError('Invalid padding option')
97
96
  }
98
97
 
99
- return typedView(fromBase64impl(str, isBase64url, padding), format)
98
+ return fromBase64impl(str, isBase64url, padding, format)
100
99
  }
101
100
 
102
101
  // ASCII whitespace is U+0009 TAB, U+000A LF, U+000C FF, U+000D CR, or U+0020 SPACE
@@ -114,7 +113,7 @@ function noWhitespaceSeen(str, arr) {
114
113
  let fromBase64impl
115
114
  if (Uint8Array.fromBase64) {
116
115
  // NOTICE: this is actually slower than our JS impl in older JavaScriptCore and (slightly) in SpiderMonkey, but faster on V8 and new JavaScriptCore
117
- fromBase64impl = (str, isBase64url, padding) => {
116
+ fromBase64impl = (str, isBase64url, padding, format) => {
118
117
  const alphabet = isBase64url ? 'base64url' : 'base64'
119
118
 
120
119
  let arr
@@ -136,20 +135,22 @@ if (Uint8Array.fromBase64) {
136
135
  // We don't allow whitespace in input, but that can be rechecked based on output length
137
136
  // All other chars are checked natively
138
137
  if (!noWhitespaceSeen(str, arr)) throw new SyntaxError(E_CHAR)
139
- return arr
138
+ return fromUint8(arr, format)
140
139
  }
141
140
  } else if (haveNativeBuffer) {
142
- fromBase64impl = (str, isBase64url, padding) => {
143
- const arr = Buffer.from(str, 'base64')
141
+ fromBase64impl = (str, isBase64url, padding, format) => {
142
+ const size = Buffer.byteLength(str, 'base64')
143
+ const arr = Buffer.allocUnsafeSlow(size) // non-pooled
144
+ if (arr.base64Write(str) !== size) throw new SyntaxError(E_PADDING)
144
145
  // Rechecking by re-encoding is cheaper than regexes on Node.js
145
146
  const got = isBase64url ? maybeUnpad(str, padding === false) : maybePad(str, padding !== true)
146
147
  const valid = isBase64url ? arr.base64urlSlice(0, arr.length) : arr.base64Slice(0, arr.length)
147
148
  if (got !== valid) throw new SyntaxError(E_PADDING)
148
- return arr // fully checked
149
+ return fromBuffer(arr, format) // fully checked
149
150
  }
150
151
  } else if (shouldUseAtob) {
151
152
  // atob is faster than manual parsing on Hermes
152
- fromBase64impl = (str, isBase64url, padding) => {
153
+ fromBase64impl = (str, isBase64url, padding, format) => {
153
154
  let arr
154
155
  if (isBase64url) {
155
156
  if (/[\t\n\f\r +/]/.test(str)) throw new SyntaxError(E_CHAR) // atob verifies other invalid input
@@ -171,8 +172,9 @@ if (Uint8Array.fromBase64) {
171
172
  if (expected !== end) throw new SyntaxError(E_LAST)
172
173
  }
173
174
 
174
- return arr
175
+ return fromUint8(arr, format)
175
176
  }
176
177
  } else {
177
- fromBase64impl = (str, isBase64url, padding) => js.fromBase64(str, isBase64url) // validated in js
178
+ fromBase64impl = (str, isBase64url, padding, format) =>
179
+ fromUint8(js.fromBase64(str, isBase64url), format) // validated in js
178
180
  }
package/bigint.d.ts CHANGED
@@ -34,8 +34,9 @@ export interface FromBigIntOptions {
34
34
  * @returns The converted bytes in big-endian format
35
35
  */
36
36
  export function fromBigInt(bigint: bigint, options: { length: number; format?: 'uint8' }): Uint8ArrayBuffer;
37
+ export function fromBigInt(bigint: bigint, options: { length: number; format: 'arraybuffer' }): ArrayBuffer;
37
38
  export function fromBigInt(bigint: bigint, options: { length: number; format: 'buffer' }): Buffer;
38
- export function fromBigInt(bigint: bigint, options: FromBigIntOptions): Uint8ArrayBuffer | Buffer;
39
+ export function fromBigInt(bigint: bigint, options: FromBigIntOptions): Uint8ArrayBuffer | ArrayBuffer | Buffer;
39
40
 
40
41
  /**
41
42
  * Convert a Uint8Array or Buffer to a BigInt
@@ -2,6 +2,8 @@
2
2
  * Same as `@exodus/bytes/encoding.js`, but in browsers instead of polyfilling just uses whatever the
3
3
  * browser provides, drastically reducing the bundle size (to less than 2 KiB gzipped).
4
4
  *
5
+ * Does not provide `isomorphicDecode` and `isomorphicEncode` exports.
6
+ *
5
7
  * ```js
6
8
  * import { TextDecoder, TextEncoder } from '@exodus/bytes/encoding-browser.js'
7
9
  * import { TextDecoderStream, TextEncoderStream } from '@exodus/bytes/encoding-browser.js' // Requires Streams
@@ -21,4 +23,13 @@
21
23
  * @module @exodus/bytes/encoding-browser.js
22
24
  */
23
25
 
24
- export * from './encoding.js'
26
+ export {
27
+ TextDecoder,
28
+ TextEncoder,
29
+ TextDecoderStream,
30
+ TextEncoderStream,
31
+ normalizeEncoding,
32
+ getBOMEncoding,
33
+ labelToName,
34
+ legacyHookDecode,
35
+ } from './encoding.js'
@@ -1 +1 @@
1
- export * from './encoding.js'
1
+ export * from './encoding-browser.native.js'
@@ -1 +1,10 @@
1
- export * from './encoding.js'
1
+ export {
2
+ TextDecoder,
3
+ TextEncoder,
4
+ TextDecoderStream,
5
+ TextEncoderStream,
6
+ normalizeEncoding,
7
+ getBOMEncoding,
8
+ labelToName,
9
+ legacyHookDecode,
10
+ } from './encoding.js'
@@ -1,11 +1,12 @@
1
1
  /**
2
2
  * The exact same exports as `@exodus/bytes/encoding.js` are also exported as
3
3
  * `@exodus/bytes/encoding-lite.js`, with the difference that the lite version does not load
4
- * multi-byte `TextDecoder` encodings by default to reduce bundle size 10x.
4
+ * multi-byte `TextDecoder` encodings by default to reduce bundle size ~12x.
5
5
  *
6
6
  * ```js
7
7
  * import { TextDecoder, TextEncoder } from '@exodus/bytes/encoding-lite.js'
8
8
  * import { TextDecoderStream, TextEncoderStream } from '@exodus/bytes/encoding-lite.js' // Requires Streams
9
+ * import { isomorphicDecode, isomorphicEncode } from '@exodus/bytes/encoding-lite.js'
9
10
  *
10
11
  * // Hooks for standards
11
12
  * import { getBOMEncoding, legacyHookDecode, labelToName, normalizeEncoding } from '@exodus/bytes/encoding-lite.js'
package/encoding-lite.js CHANGED
@@ -7,4 +7,6 @@ export {
7
7
  getBOMEncoding,
8
8
  labelToName,
9
9
  legacyHookDecode,
10
+ isomorphicDecode,
11
+ isomorphicEncode,
10
12
  } from './fallback/encoding.js'
package/encoding.d.ts CHANGED
@@ -9,6 +9,7 @@
9
9
  * ```js
10
10
  * import { TextDecoder, TextEncoder } from '@exodus/bytes/encoding.js'
11
11
  * import { TextDecoderStream, TextEncoderStream } from '@exodus/bytes/encoding.js' // Requires Streams
12
+ * import { isomorphicDecode, isomorphicEncode } from '@exodus/bytes/encoding.js'
12
13
  *
13
14
  * // Hooks for standards
14
15
  * import { getBOMEncoding, legacyHookDecode, labelToName, normalizeEncoding } from '@exodus/bytes/encoding.js'
@@ -90,6 +91,34 @@ export function legacyHookDecode(
90
91
  fallbackEncoding?: string
91
92
  ): string;
92
93
 
94
+ /**
95
+ * Implements [isomorphic decode](https://infra.spec.whatwg.org/#isomorphic-decode).
96
+ *
97
+ * Given a `TypedArray` or an `ArrayBuffer` instance `input`, creates a string of the same length
98
+ * as input byteLength, using bytes from input as codepoints.
99
+ *
100
+ * E.g. for `Uint8Array` input, this is similar to `String.fromCodePoint(...input)`.
101
+ *
102
+ * Wider `TypedArray` inputs, e.g. `Uint16Array`, are interpreted as underlying _bytes_.
103
+ *
104
+ * @param input - The bytes to decode
105
+ * @returns The decoded string
106
+ */
107
+ export function isomorphicDecode(input: ArrayBufferLike | ArrayBufferView): string;
108
+
109
+ /**
110
+ * Implements [isomorphic encode](https://infra.spec.whatwg.org/#isomorphic-encode).
111
+ *
112
+ * Given a string, creates an `Uint8Array` of the same length with the string codepoints as byte values.
113
+ *
114
+ * Accepts only [isomorphic string](https://infra.spec.whatwg.org/#isomorphic-string) input
115
+ * and asserts that, throwing on any strings containing codepoints higher than `U+00FF`.
116
+ *
117
+ * @param input - The bytes to decode
118
+ * @returns An Uint8Array containing the input bytes.
119
+ */
120
+ export function isomorphicEncode(str: string): Uint8Array;
121
+
93
122
  /**
94
123
  * Implements [get an encoding from a string `label`](https://encoding.spec.whatwg.org/#concept-encoding-get).
95
124
  *
package/encoding.js CHANGED
@@ -13,4 +13,6 @@ export {
13
13
  getBOMEncoding,
14
14
  labelToName,
15
15
  legacyHookDecode,
16
+ isomorphicDecode,
17
+ isomorphicEncode,
16
18
  } from './fallback/encoding.js'
@@ -18,3 +18,40 @@ export const toBuf = (x) =>
18
18
 
19
19
  export const E_STRING = 'Input is not a string'
20
20
  export const E_STRICT_UNICODE = 'Input is not well-formed Unicode'
21
+
22
+ // Input is never pooled
23
+ export function fromUint8(arr, format) {
24
+ switch (format) {
25
+ case 'uint8':
26
+ if (arr.constructor !== Uint8Array) throw new Error('Unexpected')
27
+ return arr
28
+ case 'arraybuffer':
29
+ if (arr.byteLength !== arr.buffer.byteLength) throw new Error('Unexpected')
30
+ return arr.buffer
31
+ case 'buffer':
32
+ if (arr.length <= 64) return Buffer.from(arr)
33
+ return Buffer.from(arr.buffer, arr.byteOffset, arr.byteLength)
34
+ }
35
+
36
+ throw new TypeError('Unexpected format')
37
+ }
38
+
39
+ // Input can be pooled
40
+ export function fromBuffer(arr, format) {
41
+ switch (format) {
42
+ case 'uint8':
43
+ // byteOffset check is slightly faster and covers most pooling, so it comes first
44
+ if (arr.length <= 64 || arr.byteOffset !== 0 || arr.byteLength !== arr.buffer.byteLength) {
45
+ return new Uint8Array(arr)
46
+ }
47
+
48
+ return new Uint8Array(arr.buffer, arr.byteOffset, arr.byteLength)
49
+ case 'arraybuffer':
50
+ return fromBuffer(arr, 'uint8').buffer
51
+ case 'buffer':
52
+ if (arr.constructor !== Buffer) throw new Error('Unexpected')
53
+ return arr
54
+ }
55
+
56
+ throw new TypeError('Unexpected format')
57
+ }
@@ -4,10 +4,12 @@ import { encodeAscii, decodeAscii } from './latin1.js'
4
4
 
5
5
  // See https://datatracker.ietf.org/doc/html/rfc4648
6
6
 
7
- const BASE32 = [...'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'] // RFC 4648, #6
8
- const BASE32HEX = [...'0123456789ABCDEFGHIJKLMNOPQRSTUV'] // RFC 4648, #7
9
- const BASE32_HELPERS = {}
10
- const BASE32HEX_HELPERS = {}
7
+ const BASE32_HELPERS = [{}, {}, {}]
8
+ const BASE32_ALPHABETS = [
9
+ [...'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'], // RFC 4648, #6
10
+ [...'0123456789ABCDEFGHIJKLMNOPQRSTUV'], // RFC 4648, #7
11
+ [...'0123456789ABCDEFGHJKMNPQRSTVWXYZ'], // Crockford, base (see extra below in fromMap)
12
+ ]
11
13
 
12
14
  export const E_CHAR = 'Invalid character in base32 input'
13
15
  export const E_PADDING = 'Invalid base32 padding'
@@ -17,15 +19,15 @@ export const E_LAST = 'Invalid last chunk'
17
19
  const useTemplates = isHermes // Faster on Hermes and JSC, but we use it only on Hermes
18
20
 
19
21
  // We construct output by concatenating chars, this seems to be fine enough on modern JS engines
20
- export function toBase32(arr, isBase32Hex, padding) {
22
+ export function toBase32(arr, mode, padding) {
21
23
  assertU8(arr)
22
24
  const fullChunks = Math.floor(arr.length / 5)
23
25
  const fullChunksBytes = fullChunks * 5
24
26
  let o = ''
25
27
  let i = 0
26
28
 
27
- const alphabet = isBase32Hex ? BASE32HEX : BASE32
28
- const helpers = isBase32Hex ? BASE32HEX_HELPERS : BASE32_HELPERS
29
+ const alphabet = BASE32_ALPHABETS[mode]
30
+ const helpers = BASE32_HELPERS[mode]
29
31
  if (!helpers.pairs) {
30
32
  helpers.pairs = []
31
33
  if (nativeDecoder) {
@@ -125,7 +127,7 @@ export function toBase32(arr, isBase32Hex, padding) {
125
127
  // TODO: can this be optimized? This only affects non-Hermes barebone engines though
126
128
  const mapSize = nativeEncoder ? 128 : 65_536 // we have to store 64 KiB map or recheck everything if we can't decode to byte array
127
129
 
128
- export function fromBase32(str, isBase32Hex) {
130
+ export function fromBase32(str, mode) {
129
131
  let inputLength = str.length
130
132
  while (str[inputLength - 1] === '=') inputLength--
131
133
  const paddingLength = str.length - inputLength
@@ -136,14 +138,21 @@ export function fromBase32(str, isBase32Hex) {
136
138
  throw new SyntaxError(E_PADDING)
137
139
  }
138
140
 
139
- const alphabet = isBase32Hex ? BASE32HEX : BASE32
140
- const helpers = isBase32Hex ? BASE32HEX_HELPERS : BASE32_HELPERS
141
+ const alphabet = BASE32_ALPHABETS[mode]
142
+ const helpers = BASE32_HELPERS[mode]
141
143
 
142
144
  if (!helpers.fromMap) {
143
145
  helpers.fromMap = new Int8Array(mapSize).fill(-1) // no regex input validation here, so we map all other bytes to -1 and recheck sign
146
+ const m = helpers.fromMap
144
147
  alphabet.forEach((c, i) => {
145
- helpers.fromMap[c.charCodeAt(0)] = helpers.fromMap[c.toLowerCase().charCodeAt(0)] = i
148
+ m[c.charCodeAt(0)] = m[c.toLowerCase().charCodeAt(0)] = i
146
149
  })
150
+
151
+ if (mode === 2) {
152
+ // Extra Crockford mapping
153
+ m[73] = m[76] = m[105] = m[108] = m[49] // ILil -> 1
154
+ m[79] = m[111] = m[48] // Oo -> 0
155
+ }
147
156
  }
148
157
 
149
158
  const m = helpers.fromMap
@@ -1,6 +1,5 @@
1
- import { typedView } from '@exodus/bytes/array.js'
2
1
  import { toBase58, fromBase58 } from '@exodus/bytes/base58.js'
3
- import { assertU8 } from './_utils.js'
2
+ import { assertU8, fromUint8 } from './_utils.js'
4
3
 
5
4
  const E_CHECKSUM = 'Invalid checksum'
6
5
 
@@ -10,7 +9,7 @@ function encodeWithChecksum(arr, checksum) {
10
9
  // arr type in already validated in input
11
10
  const res = new Uint8Array(arr.length + 4)
12
11
  res.set(arr, 0)
13
- res.set(checksum.subarray(0, 4), arr.length)
12
+ res.set(checksum.slice(0, 4), arr.length)
14
13
  return toBase58(res)
15
14
  }
16
15
 
@@ -18,7 +17,7 @@ function decodeWithChecksum(str) {
18
17
  const arr = fromBase58(str) // checks input
19
18
  const payloadSize = arr.length - 4
20
19
  if (payloadSize < 0) throw new Error(E_CHECKSUM)
21
- return [arr.subarray(0, payloadSize), arr.subarray(payloadSize)]
20
+ return [arr.slice(0, payloadSize), arr.slice(payloadSize)]
22
21
  }
23
22
 
24
23
  function assertChecksum(c, r) {
@@ -34,7 +33,7 @@ export const makeBase58check = (hashAlgo, hashAlgoSync) => {
34
33
  async decode(str, format = 'uint8') {
35
34
  const [payload, checksum] = decodeWithChecksum(str)
36
35
  assertChecksum(checksum, await hashAlgo(payload))
37
- return typedView(payload, format)
36
+ return fromUint8(payload, format)
38
37
  },
39
38
  }
40
39
  if (!hashAlgoSync) return apis
@@ -47,7 +46,7 @@ export const makeBase58check = (hashAlgo, hashAlgoSync) => {
47
46
  decodeSync(str, format = 'uint8') {
48
47
  const [payload, checksum] = decodeWithChecksum(str)
49
48
  assertChecksum(checksum, hashAlgoSync(payload))
50
- return typedView(payload, format)
49
+ return fromUint8(payload, format)
51
50
  },
52
51
  }
53
52
  }
@@ -3,7 +3,11 @@
3
3
 
4
4
  import { utf16toString, utf16toStringLoose } from '@exodus/bytes/utf16.js'
5
5
  import { utf8fromStringLoose, utf8toString, utf8toStringLoose } from '@exodus/bytes/utf8.js'
6
- import { createSinglebyteDecoder } from '@exodus/bytes/single-byte.js'
6
+ import {
7
+ createSinglebyteDecoder,
8
+ latin1toString,
9
+ latin1fromString,
10
+ } from '@exodus/bytes/single-byte.js'
7
11
  import labels from './encoding.labels.js'
8
12
  import { fromSource, getBOMEncoding } from './encoding.api.js'
9
13
  import { unfinishedBytes, mergePrefix } from './encoding.util.js'
@@ -210,9 +214,7 @@ export class TextEncoder {
210
214
 
211
215
  encode(str = '') {
212
216
  if (typeof str !== 'string') str = `${str}`
213
- const res = utf8fromStringLoose(str)
214
- // match new Uint8Array (per spec), which is non-pooled
215
- return res.byteOffset === 0 && res.length === res.buffer.byteLength ? res : res.slice(0)
217
+ return utf8fromStringLoose(str) // non-pooled
216
218
  }
217
219
 
218
220
  encodeInto(str, target) {
@@ -357,3 +359,11 @@ export function legacyHookDecode(input, fallbackEncoding = 'utf-8') {
357
359
 
358
360
  return createSinglebyteDecoder(enc, true)(u8)
359
361
  }
362
+
363
+ export function isomorphicDecode(input) {
364
+ return latin1toString(fromSource(input))
365
+ }
366
+
367
+ export function isomorphicEncode(str) {
368
+ return latin1fromString(str)
369
+ }
@@ -36,27 +36,28 @@ export function unfinishedBytes(u, len, enc) {
36
36
  // otherwise returns a prefix with no unfinished bytes
37
37
  export function mergePrefix(u, chunk, enc) {
38
38
  if (u.length === 0) return chunk
39
+ const cl = chunk.length
39
40
  if (u.length < 3) {
40
41
  // No reason to bruteforce offsets, also it's possible this doesn't yet end the sequence
41
- const a = new Uint8Array(u.length + chunk.length)
42
+ const a = new Uint8Array(cl + u.length)
42
43
  a.set(chunk)
43
- a.set(u, chunk.length)
44
+ a.set(u, cl)
44
45
  return a
45
46
  }
46
47
 
47
48
  // Slice off a small portion of u into prefix chunk so we can decode them separately without extending array size
48
- const t = new Uint8Array(chunk.length + 3) // We have 1-3 bytes and need 1-3 more bytes
49
+ const t = new Uint8Array(cl + 3) // We have 1-3 bytes and need 1-3 more bytes
49
50
  t.set(chunk)
50
- t.set(u.subarray(0, 3), chunk.length)
51
+ t.set(u.subarray(0, 3), cl)
51
52
 
52
53
  // Stop at the first offset where unfinished bytes reaches 0 or fits into u
53
54
  // If that doesn't happen (u too short), just concat chunk and u completely (above)
54
55
  for (let i = 1; i <= 3; i++) {
55
- const unfinished = unfinishedBytes(t, chunk.length + i, enc) // 0-3
56
+ const unfinished = unfinishedBytes(t, cl + i, enc) // 0-3
56
57
  if (unfinished <= i) {
57
58
  // Always reachable at 3, but we still need 'unfinished' value for it
58
59
  const add = i - unfinished // 0-3
59
- return add > 0 ? t.subarray(0, chunk.length + add) : chunk
60
+ return add > 0 ? t.subarray(0, cl + add) : chunk
60
61
  }
61
62
  }
62
63
 
@@ -1,5 +1,6 @@
1
1
  import { E_STRING } from './_utils.js'
2
- import { asciiPrefix, decodeAscii, decodeLatin1, decodeUCS2, encodeAscii } from './latin1.js'
2
+ import { nativeEncoder } from './platform.js'
3
+ import { asciiPrefix, decodeAscii, decodeLatin1, decodeUCS2 } from './latin1.js'
3
4
  import { getTable } from './multi-byte.table.js'
4
5
 
5
6
  export const E_STRICT = 'Input is not well-formed for this encoding'
@@ -776,10 +777,9 @@ export function multibyteEncoder(enc, onError) {
776
777
  if (iso2022jp && !katakana) katakana = getTable('iso-2022-jp-katakana')
777
778
  return (str) => {
778
779
  if (typeof str !== 'string') throw new TypeError(E_STRING)
779
- if (ascii && !NON_LATIN.test(str)) {
780
- try {
781
- return encodeAscii(str, E_STRICT)
782
- } catch {}
780
+ if (ascii && nativeEncoder && !NON_LATIN.test(str)) {
781
+ const u8 = nativeEncoder.encode(str)
782
+ if (u8.length === str.length) return u8
783
783
  }
784
784
 
785
785
  const length = str.length
@@ -957,6 +957,6 @@ export function multibyteEncoder(enc, onError) {
957
957
  }
958
958
  }
959
959
 
960
- return i === u8.length ? u8 : u8.subarray(0, i)
960
+ return i === u8.length ? u8 : u8.slice(0, i)
961
961
  }
962
962
  }
package/hex.d.ts CHANGED
@@ -31,5 +31,6 @@ export function toHex(arr: Uint8Array): string;
31
31
  * @returns The decoded bytes
32
32
  */
33
33
  export function fromHex(string: string, format?: 'uint8'): Uint8ArrayBuffer;
34
+ export function fromHex(string: string, format: 'arraybuffer'): ArrayBuffer;
34
35
  export function fromHex(string: string, format: 'buffer'): Buffer;
35
- export function fromHex(string: string, format?: OutputFormat): Uint8ArrayBuffer | Buffer;
36
+ export function fromHex(string: string, format?: OutputFormat): Uint8ArrayBuffer | ArrayBuffer | Buffer;
package/hex.js CHANGED
@@ -1,5 +1,4 @@
1
- import { typedView } from './array.js'
2
- import { assertU8 } from './fallback/_utils.js'
1
+ import { assertU8, fromUint8 } from './fallback/_utils.js'
3
2
  import * as js from './fallback/hex.js'
4
3
 
5
4
  const { toHex: webHex } = Uint8Array.prototype // Modern engines have this
@@ -13,5 +12,5 @@ export function toHex(arr) {
13
12
 
14
13
  // Unlike Buffer.from(), throws on invalid input
15
14
  export const fromHex = Uint8Array.fromHex
16
- ? (str, format = 'uint8') => typedView(Uint8Array.fromHex(str), format)
17
- : (str, format = 'uint8') => typedView(js.fromHex(str), format)
15
+ ? (str, format = 'uint8') => fromUint8(Uint8Array.fromHex(str), format)
16
+ : (str, format = 'uint8') => fromUint8(js.fromHex(str), format)
package/hex.node.js CHANGED
@@ -1,5 +1,4 @@
1
- import { typedView } from './array.js'
2
- import { assertU8, E_STRING } from './fallback/_utils.js'
1
+ import { assertU8, fromBuffer, fromUint8, E_STRING } from './fallback/_utils.js'
3
2
  import { E_HEX } from './fallback/hex.js'
4
3
 
5
4
  if (Buffer.TYPED_ARRAY_SUPPORT) throw new Error('Unexpected Buffer polyfill')
@@ -17,12 +16,22 @@ export function toHex(arr) {
17
16
 
18
17
  // Unlike Buffer.from(), throws on invalid input
19
18
  export const fromHex = Uint8Array.fromHex
20
- ? (str, format = 'uint8') => typedView(Uint8Array.fromHex(str), format)
19
+ ? (str, format = 'uint8') => fromUint8(Uint8Array.fromHex(str), format)
21
20
  : (str, format = 'uint8') => {
22
21
  if (typeof str !== 'string') throw new TypeError(E_STRING)
23
22
  if (str.length % 2 !== 0) throw new SyntaxError(E_HEX)
24
23
  if (denoBug && /[^\dA-Fa-f]/.test(str)) throw new SyntaxError(E_HEX)
25
- const buf = Buffer.from(str, 'hex') // will stop on first non-hex character, so we can just validate length
26
- if (buf.length * 2 !== str.length) throw new SyntaxError(E_HEX)
27
- return typedView(buf, format)
24
+
25
+ // 64 bytes or less, in heap
26
+ if (str.length <= 128) {
27
+ const buf = Buffer.from(str, 'hex')
28
+ if (buf.length * 2 !== str.length) throw new SyntaxError(E_HEX)
29
+ return fromBuffer(buf, format)
30
+ }
31
+
32
+ const length = str.length / 2
33
+ const buf = format === 'buffer' ? Buffer.allocUnsafe(length) : Buffer.allocUnsafeSlow(length) // avoid pooling
34
+ const count = buf.hexWrite(str, 0, length)
35
+ if (count !== length) throw new SyntaxError(E_HEX) // will stop on first non-hex character, so we can just validate length
36
+ return fromBuffer(buf, format)
28
37
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/bytes",
3
- "version": "1.14.1",
3
+ "version": "1.15.0",
4
4
  "description": "Various operations on Uint8Array data",
5
5
  "keywords": [
6
6
  "encoding",
@@ -273,7 +273,7 @@
273
273
  "buffer": "^6.0.3",
274
274
  "c8": "^10.1.3",
275
275
  "decode-utf8": "^1.0.1",
276
- "electron": "36.5.0",
276
+ "electron": "39.4.0",
277
277
  "encode-utf8": "^2.0.0",
278
278
  "esbuild": "^0.27.3",
279
279
  "eslint": "^8.44.0",
@@ -291,7 +291,7 @@
291
291
  "utf8": "^3.0.0",
292
292
  "web-streams-polyfill": "^4.2.0",
293
293
  "wif": "^5.0.0",
294
- "workerd": "^1.20260210.0"
294
+ "workerd": "^1.20260301.1"
295
295
  },
296
296
  "prettier": "@exodus/prettier",
297
297
  "packageManager": "pnpm@10.12.1+sha256.889bac470ec93ccc3764488a19d6ba8f9c648ad5e50a9a6e4be3768a5de387a3"
package/single-byte.d.ts CHANGED
@@ -108,6 +108,10 @@ export function createSinglebyteEncoder(
108
108
  * > This is different from `new TextDecoder('iso-8859-1')` and `new TextDecoder('latin1')`, as those
109
109
  * > alias to `new TextDecoder('windows-1252')`.
110
110
  *
111
+ * Prefer using `isomorphicDecode()` from `@exodus/bytes/encoding.js` or `@exodus/bytes/encoding-lite.js`,
112
+ * which is identical to this but allows more input types.
113
+ *
114
+ * @deprecated Use `import { isomorphicDecode } from '@exodus/bytes/encoding-lite.js'`
111
115
  * @param arr - The bytes to decode
112
116
  * @returns The decoded string
113
117
  */
@@ -123,6 +127,9 @@ export function latin1toString(arr: Uint8Array): string;
123
127
  * const latin1fromString = createSinglebyteEncoder('iso-8859-1', { mode: 'fatal' })
124
128
  * ```
125
129
  *
130
+ * Prefer using `isomorphicEncode()` from `@exodus/bytes/encoding.js` or `@exodus/bytes/encoding-lite.js`.
131
+ *
132
+ * @deprecated Use `import { isomorphicEncode } from '@exodus/bytes/encoding-lite.js'`
126
133
  * @param string - The string to encode
127
134
  * @returns The encoded bytes
128
135
  */
package/single-byte.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { assertU8, E_STRING } from './fallback/_utils.js'
2
2
  import { nativeDecoderLatin1, nativeEncoder } from './fallback/platform.js'
3
- import { encodeAscii, encodeAsciiPrefix, encodeLatin1 } from './fallback/latin1.js'
3
+ import { encodeAsciiPrefix, encodeLatin1 } from './fallback/latin1.js'
4
4
  import { assertEncoding, encodingDecoder, encodeMap, E_STRICT } from './fallback/single-byte.js'
5
5
 
6
6
  const { TextDecoder, btoa } = globalThis
@@ -90,37 +90,38 @@ function encode(s, m) {
90
90
  // fromBase64+btoa path is faster on everything where fromBase64 is fast
91
91
  const useLatin1btoa = Uint8Array.fromBase64 && btoa
92
92
 
93
+ export function latin1fromString(s) {
94
+ if (typeof s !== 'string') throw new TypeError(E_STRING)
95
+ // max limit is to not produce base64 strings that are too long
96
+ if (useLatin1btoa && s.length >= 1024 && s.length < 1e8) {
97
+ try {
98
+ return Uint8Array.fromBase64(btoa(s)) // fails on non-latin1
99
+ } catch {
100
+ throw new TypeError(E_STRICT)
101
+ }
102
+ }
103
+
104
+ if (NON_LATIN.test(s)) throw new TypeError(E_STRICT)
105
+ return encodeLatin1(s)
106
+ }
107
+
93
108
  export function createSinglebyteEncoder(encoding, { mode = 'fatal' } = {}) {
94
109
  // TODO: replacement, truncate (replacement will need varying length)
95
110
  if (mode !== 'fatal') throw new Error('Unsupported mode')
111
+ if (encoding === 'iso-8859-1') return latin1fromString
96
112
  const m = encodeMap(encoding) // asserts
97
- const isLatin1 = encoding === 'iso-8859-1'
98
113
 
99
114
  // No single-byte encoder produces surrogate pairs, so any surrogate is invalid
100
115
  // This needs special treatment only to decide how many replacement chars to output, one or two
101
116
  // Not much use in running isWellFormed, most likely cause of error is unmapped chars, not surrogate pairs
102
117
  return (s) => {
103
118
  if (typeof s !== 'string') throw new TypeError(E_STRING)
104
- if (isLatin1) {
105
- // max limit is to not produce base64 strings that are too long
106
- if (useLatin1btoa && s.length >= 1024 && s.length < 1e8) {
107
- try {
108
- return Uint8Array.fromBase64(btoa(s)) // fails on non-latin1
109
- } catch {
110
- throw new TypeError(E_STRICT)
111
- }
112
- }
113
-
114
- if (NON_LATIN.test(s)) throw new TypeError(E_STRICT)
115
- return encodeLatin1(s)
116
- }
117
119
 
118
120
  // Instead of an ASCII regex check, encode optimistically - this is faster
119
121
  // Check for 8-bit string with a regex though, this is instant on 8-bit strings so doesn't hurt the ASCII fast path
120
122
  if (nativeEncoder && !NON_LATIN.test(s)) {
121
- try {
122
- return encodeAscii(s, E_STRICT)
123
- } catch {}
123
+ const u8 = nativeEncoder.encode(s)
124
+ if (u8.length === s.length) return u8
124
125
  }
125
126
 
126
127
  const res = encode(s, m)
@@ -130,6 +131,5 @@ export function createSinglebyteEncoder(encoding, { mode = 'fatal' } = {}) {
130
131
  }
131
132
 
132
133
  export const latin1toString = /* @__PURE__ */ createSinglebyteDecoder('iso-8859-1')
133
- export const latin1fromString = /* @__PURE__ */ createSinglebyteEncoder('iso-8859-1')
134
134
  export const windows1252toString = /* @__PURE__ */ createSinglebyteDecoder('windows-1252')
135
135
  export const windows1252fromString = /* @__PURE__ */ createSinglebyteEncoder('windows-1252')