@exodus/bytes 1.14.1 → 1.15.1

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'
@@ -75,6 +74,7 @@ export function fromBase64url(str, options) {
75
74
 
76
75
  // By default accepts both padded and non-padded variants, base64 or base64url
77
76
  export function fromBase64any(str, { format = 'uint8', padding = 'both', ...rest } = {}) {
77
+ if (typeof str !== 'string') throw new TypeError(E_STRING)
78
78
  const isBase64url = !str.includes('+') && !str.includes('/') // likely to fail fast, as most input is non-url, also double scan is faster than regex
79
79
  return fromBase64common(str, isBase64url, padding, format, rest)
80
80
  }
@@ -96,7 +96,7 @@ function fromBase64common(str, isBase64url, padding, format, rest) {
96
96
  throw new TypeError('Invalid padding option')
97
97
  }
98
98
 
99
- return typedView(fromBase64impl(str, isBase64url, padding), format)
99
+ return fromBase64impl(str, isBase64url, padding, format)
100
100
  }
101
101
 
102
102
  // ASCII whitespace is U+0009 TAB, U+000A LF, U+000C FF, U+000D CR, or U+0020 SPACE
@@ -114,7 +114,7 @@ function noWhitespaceSeen(str, arr) {
114
114
  let fromBase64impl
115
115
  if (Uint8Array.fromBase64) {
116
116
  // 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) => {
117
+ fromBase64impl = (str, isBase64url, padding, format) => {
118
118
  const alphabet = isBase64url ? 'base64url' : 'base64'
119
119
 
120
120
  let arr
@@ -136,20 +136,22 @@ if (Uint8Array.fromBase64) {
136
136
  // We don't allow whitespace in input, but that can be rechecked based on output length
137
137
  // All other chars are checked natively
138
138
  if (!noWhitespaceSeen(str, arr)) throw new SyntaxError(E_CHAR)
139
- return arr
139
+ return fromUint8(arr, format)
140
140
  }
141
141
  } else if (haveNativeBuffer) {
142
- fromBase64impl = (str, isBase64url, padding) => {
143
- const arr = Buffer.from(str, 'base64')
142
+ fromBase64impl = (str, isBase64url, padding, format) => {
143
+ const size = Buffer.byteLength(str, 'base64')
144
+ const arr = Buffer.allocUnsafeSlow(size) // non-pooled
145
+ if (arr.base64Write(str) !== size) throw new SyntaxError(E_PADDING)
144
146
  // Rechecking by re-encoding is cheaper than regexes on Node.js
145
147
  const got = isBase64url ? maybeUnpad(str, padding === false) : maybePad(str, padding !== true)
146
148
  const valid = isBase64url ? arr.base64urlSlice(0, arr.length) : arr.base64Slice(0, arr.length)
147
149
  if (got !== valid) throw new SyntaxError(E_PADDING)
148
- return arr // fully checked
150
+ return fromBuffer(arr, format) // fully checked
149
151
  }
150
152
  } else if (shouldUseAtob) {
151
153
  // atob is faster than manual parsing on Hermes
152
- fromBase64impl = (str, isBase64url, padding) => {
154
+ fromBase64impl = (str, isBase64url, padding, format) => {
153
155
  let arr
154
156
  if (isBase64url) {
155
157
  if (/[\t\n\f\r +/]/.test(str)) throw new SyntaxError(E_CHAR) // atob verifies other invalid input
@@ -171,8 +173,9 @@ if (Uint8Array.fromBase64) {
171
173
  if (expected !== end) throw new SyntaxError(E_LAST)
172
174
  }
173
175
 
174
- return arr
176
+ return fromUint8(arr, format)
175
177
  }
176
178
  } else {
177
- fromBase64impl = (str, isBase64url, padding) => js.fromBase64(str, isBase64url) // validated in js
179
+ fromBase64impl = (str, isBase64url, padding, format) =>
180
+ fromUint8(js.fromBase64(str, isBase64url), format) // validated in js
178
181
  }
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'
@@ -7,7 +7,8 @@ export function assert(condition, msg) {
7
7
  }
8
8
 
9
9
  export function assertU8(arg) {
10
- if (!(arg instanceof Uint8Array)) throw new TypeError('Expected an Uint8Array')
10
+ if (arg && arg instanceof Uint8Array) return
11
+ throw new TypeError('Expected an Uint8Array')
11
12
  }
12
13
 
13
14
  // On arrays in heap (<= 64) it's cheaper to copy into a pooled buffer than lazy-create the ArrayBuffer storage
@@ -18,3 +19,42 @@ export const toBuf = (x) =>
18
19
 
19
20
  export const E_STRING = 'Input is not a string'
20
21
  export const E_STRICT_UNICODE = 'Input is not well-formed Unicode'
22
+
23
+ // Input is never pooled
24
+ export function fromUint8(arr, format) {
25
+ switch (format) {
26
+ case 'uint8':
27
+ if (arr.constructor !== Uint8Array) throw new Error('Unexpected')
28
+ return arr
29
+ case 'arraybuffer':
30
+ if (arr.byteLength !== arr.buffer.byteLength) throw new Error('Unexpected')
31
+ return arr.buffer
32
+ case 'buffer':
33
+ if (arr.length <= 64) return Buffer.from(arr)
34
+ return Buffer.from(arr.buffer, arr.byteOffset, arr.byteLength)
35
+ }
36
+
37
+ throw new TypeError('Unexpected format')
38
+ }
39
+
40
+ // Input can be pooled
41
+ export function fromBuffer(arr, format) {
42
+ switch (format) {
43
+ case 'uint8':
44
+ // byteOffset check is slightly faster and covers most pooling, so it comes first
45
+ if (arr.length <= 64 || arr.byteOffset !== 0 || arr.byteLength !== arr.buffer.byteLength) {
46
+ return new Uint8Array(arr)
47
+ }
48
+
49
+ return new Uint8Array(arr.buffer, arr.byteOffset, arr.byteLength)
50
+ case 'arraybuffer':
51
+ return arr.buffer.byteLength === arr.byteLength
52
+ ? arr.buffer
53
+ : arr.buffer.slice(arr.byteOffset, arr.byteOffset + arr.byteLength)
54
+ case 'buffer':
55
+ if (arr.constructor !== Buffer) throw new Error('Unexpected')
56
+ return arr
57
+ }
58
+
59
+ throw new TypeError('Unexpected format')
60
+ }
@@ -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
 
package/fallback/hex.js CHANGED
@@ -73,7 +73,7 @@ export function fromHex(str) {
73
73
  }
74
74
  }
75
75
 
76
- const codes = encodeAscii(str, E_HEX)
76
+ const codes = encodeAscii(str, E_HEX) // aligned
77
77
  const codes16 = new Uint16Array(codes.buffer, codes.byteOffset, codes.byteLength / 2)
78
78
  let i = 0
79
79
  for (const last3 = length - 3; i < last3; i += 4) {
@@ -140,6 +140,7 @@ export const encodeAscii = useEncodeInto
140
140
  : nativeBuffer
141
141
  ? (str, ERR) => {
142
142
  // TextEncoder is slow on Node.js 24 / 25 (was ok on 22)
143
+ // Node.js Buffer.from always returns 8-byte aligned allocations, this is safe for e.g. conversion to Uint32Array
143
144
  const codes = nativeBuffer.from(str, 'utf8') // ascii/latin1 coerces, we need to check
144
145
  if (codes.length !== str.length) throw new SyntaxError(ERR) // non-ascii
145
146
  return new Uint8Array(codes.buffer, codes.byteOffset, codes.byteLength)
@@ -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.1",
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
  */