@exodus/bytes 1.0.0-rc.2 → 1.0.0-rc.3

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/array.js CHANGED
@@ -2,7 +2,7 @@ import { assertTypedArray } from './assert.js'
2
2
 
3
3
  const { Buffer } = globalThis // Buffer is optional
4
4
 
5
- export function fromTypedArray(arr, format) {
5
+ export function typedView(arr, format) {
6
6
  assertTypedArray(arr)
7
7
  switch (format) {
8
8
  case 'uint8':
package/assert.js CHANGED
@@ -12,7 +12,8 @@ const makeMessage = (name, extra) => `Expected${name ? ` ${name} to be` : ''} an
12
12
  const TypedArray = Object.getPrototypeOf(Uint8Array)
13
13
 
14
14
  export function assertTypedArray(arr) {
15
- assert(arr instanceof TypedArray, 'Expected a TypedArray instance')
15
+ if (arr instanceof TypedArray) return
16
+ throw new TypeError('Expected a TypedArray instance')
16
17
  }
17
18
 
18
19
  export function assertUint8(arr, { name, length, ...rest } = {}) {
package/base64.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { assert, assertUint8 } from './assert.js'
2
- import { fromTypedArray } from './array.js'
2
+ import { typedView } from './array.js'
3
+ import * as js from './fallback/base64.js'
3
4
 
4
5
  // See https://datatracker.ietf.org/doc/html/rfc4648
5
6
 
@@ -13,7 +14,7 @@ const { toBase64: web64 } = Uint8Array.prototype // Modern engines have this
13
14
  export function toBase64(x) {
14
15
  assertUint8(x)
15
16
  if (web64 && x.toBase64 === web64) return x.toBase64() // Modern
16
- if (!haveNativeBuffer) return toBase64js(x, BASE64, true) // Fallback
17
+ if (!haveNativeBuffer) return js.toBase64(x, false, true) // Fallback
17
18
  if (x.constructor === Buffer && Buffer.isBuffer(x)) return x.toString('base64') // Older Node.js
18
19
  return Buffer.from(x.buffer, x.byteOffset, x.byteLength).toString('base64') // Older Node.js
19
20
  }
@@ -22,7 +23,7 @@ export function toBase64(x) {
22
23
  export function toBase64url(x) {
23
24
  assertUint8(x)
24
25
  if (web64 && x.toBase64 === web64) return x.toBase64({ alphabet: 'base64url', omitPadding: true }) // Modern
25
- if (!haveNativeBuffer) return toBase64js(x, BASE64URL, false) // Fallback
26
+ if (!haveNativeBuffer) return js.toBase64(x, true, false) // Fallback
26
27
  if (x.constructor === Buffer && Buffer.isBuffer(x)) return x.toString('base64url') // Older Node.js
27
28
  return Buffer.from(x.buffer, x.byteOffset, x.byteLength).toString('base64url') // Older Node.js
28
29
  }
@@ -42,7 +43,7 @@ export function fromBase64(str, format = 'uint8') {
42
43
  assert(str[str.length - 3] !== '=', 'Excessive padding') // no more than two = at the end
43
44
  }
44
45
 
45
- return fromTypedArray(fromBase64common(str, false), format)
46
+ return typedView(fromBase64common(str, false), format)
46
47
  }
47
48
 
48
49
  // Accepts both only non-padded strict base64url
@@ -53,7 +54,7 @@ export function fromBase64url(str, format = 'uint8') {
53
54
  assert(str.length % 4 !== 1, 'Invalid base64 length') // JSC misses this in fromBase64
54
55
  assert(!str.endsWith('='), 'Did not expect padding in base64url input') // inclusion is checked separately
55
56
 
56
- return fromTypedArray(fromBase64common(str, true), format)
57
+ return typedView(fromBase64common(str, true), format)
57
58
  }
58
59
 
59
60
  let fromBase64common
@@ -87,7 +88,7 @@ if (Uint8Array.fromBase64) {
87
88
  if (at >= 0) assert(!/[^=]/iu.test(str.slice(at)), 'Invalid padding')
88
89
  }
89
90
 
90
- arr = haveNativeBuffer ? Buffer.from(str, 'base64') : fromBase64js(str)
91
+ arr = haveNativeBuffer ? Buffer.from(str, 'base64') : js.fromBase64(str)
91
92
  }
92
93
 
93
94
  if (arr.length % 3 !== 0) {
@@ -101,105 +102,3 @@ if (Uint8Array.fromBase64) {
101
102
  return arr
102
103
  }
103
104
  }
104
-
105
- const BASE64 = [...'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/']
106
- const BASE64URL = [...'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_']
107
-
108
- const BASE64_PAIRS = []
109
- const BASE64URL_PAIRS = []
110
-
111
- // We construct output by concatenating chars, this seems to be fine enough on modern JS engines
112
- function toBase64js(arr, alphabet, padding) {
113
- assertUint8(arr)
114
- const fullChunks = Math.floor(arr.length / 3)
115
- const fullChunksBytes = fullChunks * 3
116
- let o = ''
117
- let i = 0
118
-
119
- const pairs = alphabet === BASE64URL ? BASE64URL_PAIRS : BASE64_PAIRS
120
- if (pairs.length === 0) {
121
- for (let i = 0; i < 64; i++) {
122
- for (let j = 0; j < 64; j++) pairs.push(`${alphabet[i]}${alphabet[j]}`)
123
- }
124
- }
125
-
126
- // Fast path for complete blocks
127
- // This whole loop can be commented out, the algorithm won't change, it's just an optimization of the next loop
128
- for (; i < fullChunksBytes; i += 3) {
129
- const a = arr[i]
130
- const b = arr[i + 1]
131
- const c = arr[i + 2]
132
- o += pairs[(a << 4) | (b >> 4)] + pairs[((b & 0x0f) << 8) | c]
133
- }
134
-
135
- // If we have something left, process it with a full algo
136
- let carry = 0
137
- let shift = 2 // First byte needs to be shifted by 2 to get 6 bits
138
- const length = arr.length
139
- for (; i < length; i++) {
140
- const x = arr[i]
141
- o += alphabet[carry | (x >> shift)] // shift >= 2, so this fits
142
- if (shift === 6) {
143
- shift = 0
144
- o += alphabet[x & 0x3f]
145
- }
146
-
147
- carry = (x << (6 - shift)) & 0x3f
148
- shift += 2 // Each byte prints 6 bits and leaves 2 bits
149
- }
150
-
151
- if (shift !== 2) o += alphabet[carry] // shift 2 means we have no carry left
152
- if (padding) o += ['', '==', '='][length - fullChunksBytes]
153
-
154
- return o
155
- }
156
-
157
- // Assumes no chars after =, checked
158
- let fromBase64jsMap
159
-
160
- function fromBase64js(str) {
161
- const map = fromBase64jsMap || new Array(256)
162
- if (!fromBase64jsMap) {
163
- fromBase64jsMap = map
164
- BASE64.forEach((c, i) => (map[c.charCodeAt(0)] = i))
165
- map['-'.charCodeAt(0)] = map['+'.charCodeAt(0)] // for base64url
166
- map['_'.charCodeAt(0)] = map['/'.charCodeAt(0)] // for base64url
167
- }
168
-
169
- let inputLength = str.length
170
- while (str[inputLength - 1] === '=') inputLength--
171
-
172
- const arr = new Uint8Array(Math.floor((inputLength * 3) / 4))
173
- const tailLength = inputLength % 4
174
- const mainLength = inputLength - tailLength // multiples of 4
175
-
176
- let at = 0
177
- let i = 0
178
- let tmp
179
-
180
- while (i < mainLength) {
181
- tmp =
182
- (map[str.charCodeAt(i)] << 18) |
183
- (map[str.charCodeAt(i + 1)] << 12) |
184
- (map[str.charCodeAt(i + 2)] << 6) |
185
- map[str.charCodeAt(i + 3)]
186
- arr[at++] = tmp >> 16
187
- arr[at++] = (tmp >> 8) & 0xff
188
- arr[at++] = tmp & 0xff
189
- i += 4
190
- }
191
-
192
- if (tailLength === 3) {
193
- tmp =
194
- (map[str.charCodeAt(i)] << 10) |
195
- (map[str.charCodeAt(i + 1)] << 4) |
196
- (map[str.charCodeAt(i + 2)] >> 2)
197
- arr[at++] = (tmp >> 8) & 0xff
198
- arr[at++] = tmp & 0xff
199
- } else if (tailLength === 2) {
200
- tmp = (map[str.charCodeAt(i)] << 2) | (map[str.charCodeAt(i + 1)] >> 4)
201
- arr[at++] = tmp & 0xff
202
- }
203
-
204
- return arr
205
- }
@@ -0,0 +1,127 @@
1
+ import { assertUint8 } from '../assert.js'
2
+
3
+ // See https://datatracker.ietf.org/doc/html/rfc4648
4
+
5
+ const { TextDecoder } = globalThis
6
+ const nativeDecoder = TextDecoder?.toString().includes('[native code]') ? new TextDecoder() : null
7
+ const BASE64 = [...'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/']
8
+ const BASE64URL = [...'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_']
9
+ const BASE64_PAIRS = []
10
+ const BASE64URL_PAIRS = []
11
+ const BASE64_CODES = nativeDecoder ? new Uint8Array(64) : null
12
+ const BASE64URL_CODES = nativeDecoder ? new Uint8Array(64) : null
13
+
14
+ // Alternatively, we could have mapped 0-255 bytes to charcodes and just used btoa(ascii),
15
+ // but that approach is _slower_ than our toBase64js function, even on Hermes
16
+
17
+ // We construct output by concatenating chars, this seems to be fine enough on modern JS engines
18
+ export function toBase64(arr, isURL, padding) {
19
+ assertUint8(arr)
20
+ const fullChunks = Math.floor(arr.length / 3)
21
+ const fullChunksBytes = fullChunks * 3
22
+ let o = ''
23
+ let i = 0
24
+
25
+ const alphabet = isURL ? BASE64URL : BASE64
26
+ const pairs = isURL ? BASE64URL_PAIRS : BASE64_PAIRS
27
+ const map = isURL ? BASE64_CODES : BASE64URL_CODES
28
+ if (pairs.length === 0) {
29
+ for (let i = 0; i < 64; i++) {
30
+ for (let j = 0; j < 64; j++) pairs.push(`${alphabet[i]}${alphabet[j]}`)
31
+ if (map) map[i] = alphabet[i].charCodeAt(0)
32
+ }
33
+ }
34
+
35
+ // Fast path for complete blocks
36
+ // This whole loop can be commented out, the algorithm won't change, it's just an optimization of the next loop
37
+ if (nativeDecoder) {
38
+ const oa = new Uint8Array(fullChunks * 4)
39
+ for (let j = 0; i < fullChunksBytes; i += 3) {
40
+ const a = arr[i]
41
+ const b = arr[i + 1]
42
+ const c = arr[i + 2]
43
+ oa[j++] = map[a >> 2]
44
+ oa[j++] = map[((a & 0x3) << 4) | (b >> 4)]
45
+ oa[j++] = map[((b & 0xf) << 2) | (c >> 6)]
46
+ oa[j++] = map[c & 0x3f]
47
+ }
48
+
49
+ o = nativeDecoder.decode(oa)
50
+ } else {
51
+ for (; i < fullChunksBytes; i += 3) {
52
+ const a = arr[i]
53
+ const b = arr[i + 1]
54
+ const c = arr[i + 2]
55
+ o += pairs[(a << 4) | (b >> 4)] + pairs[((b & 0x0f) << 8) | c]
56
+ }
57
+ }
58
+
59
+ // If we have something left, process it with a full algo
60
+ let carry = 0
61
+ let shift = 2 // First byte needs to be shifted by 2 to get 6 bits
62
+ const length = arr.length
63
+ for (; i < length; i++) {
64
+ const x = arr[i]
65
+ o += alphabet[carry | (x >> shift)] // shift >= 2, so this fits
66
+ if (shift === 6) {
67
+ shift = 0
68
+ o += alphabet[x & 0x3f]
69
+ }
70
+
71
+ carry = (x << (6 - shift)) & 0x3f
72
+ shift += 2 // Each byte prints 6 bits and leaves 2 bits
73
+ }
74
+
75
+ if (shift !== 2) o += alphabet[carry] // shift 2 means we have no carry left
76
+ if (padding) o += ['', '==', '='][length - fullChunksBytes]
77
+
78
+ return o
79
+ }
80
+
81
+ let fromBase64jsMap
82
+
83
+ // Assumes valid input and no chars after =, checked at API
84
+ // Last chunk is rechecked at API too
85
+ export function fromBase64(str) {
86
+ const map = fromBase64jsMap || new Array(256)
87
+ if (!fromBase64jsMap) {
88
+ fromBase64jsMap = map
89
+ BASE64.forEach((c, i) => (map[c.charCodeAt(0)] = i))
90
+ map['-'.charCodeAt(0)] = map['+'.charCodeAt(0)] // for base64url
91
+ map['_'.charCodeAt(0)] = map['/'.charCodeAt(0)] // for base64url
92
+ }
93
+
94
+ let inputLength = str.length
95
+ while (str[inputLength - 1] === '=') inputLength--
96
+
97
+ const arr = new Uint8Array(Math.floor((inputLength * 3) / 4))
98
+ const tailLength = inputLength % 4
99
+ const mainLength = inputLength - tailLength // multiples of 4
100
+
101
+ let at = 0
102
+ let i = 0
103
+ let tmp
104
+
105
+ while (i < mainLength) {
106
+ // a [ b c ] d, each 6 bits
107
+ const bc = (map[str.charCodeAt(i + 1)] << 6) | map[str.charCodeAt(i + 2)]
108
+ arr[at++] = (map[str.charCodeAt(i)] << 2) | (bc >> 10)
109
+ arr[at++] = (bc >> 2) & 0xff
110
+ arr[at++] = ((bc << 6) & 0xff) | map[str.charCodeAt(i + 3)]
111
+ i += 4
112
+ }
113
+
114
+ if (tailLength === 3) {
115
+ tmp =
116
+ (map[str.charCodeAt(i)] << 10) |
117
+ (map[str.charCodeAt(i + 1)] << 4) |
118
+ (map[str.charCodeAt(i + 2)] >> 2)
119
+ arr[at++] = (tmp >> 8) & 0xff
120
+ arr[at++] = tmp & 0xff
121
+ } else if (tailLength === 2) {
122
+ tmp = (map[str.charCodeAt(i)] << 2) | (map[str.charCodeAt(i + 1)] >> 4)
123
+ arr[at++] = tmp & 0xff
124
+ }
125
+
126
+ return arr
127
+ }
@@ -0,0 +1,90 @@
1
+ import { assert, assertUint8 } from '../assert.js'
2
+
3
+ // We use TextEncoder here to parse strings to charcodes, this is faster than individual charCodeAt calls
4
+ const { TextEncoder } = globalThis // Buffer is optional, only used when native
5
+ const nativeEncoder = TextEncoder?.toString().includes('[native code]') ? new TextEncoder() : null
6
+
7
+ let hexArray
8
+ let dehexArray
9
+
10
+ function toHexPart(arr, start, end) {
11
+ let o = ''
12
+ let i = start
13
+ const last3 = end - 3
14
+ // Unrolled loop is faster
15
+ while (i < last3) {
16
+ const a = arr[i++]
17
+ const b = arr[i++]
18
+ const c = arr[i++]
19
+ const d = arr[i++]
20
+ o += hexArray[a]
21
+ o += hexArray[b]
22
+ o += hexArray[c]
23
+ o += hexArray[d]
24
+ }
25
+
26
+ while (i < end) o += hexArray[arr[i++]]
27
+ return o
28
+ }
29
+
30
+ export function toHex(arr) {
31
+ assertUint8(arr)
32
+
33
+ if (!hexArray) hexArray = Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, '0'))
34
+ const length = arr.length // this helps Hermes
35
+
36
+ if (length > 30_000) {
37
+ // Limit concatenation to avoid excessive GC
38
+ // Thresholds checked on Hermes
39
+ const concat = []
40
+ for (let i = 0; i < length; ) {
41
+ const step = i + 500
42
+ const end = step > length ? length : step
43
+ concat.push(toHexPart(arr, i, end))
44
+ i = end
45
+ }
46
+
47
+ const res = concat.join('')
48
+ concat.length = 0
49
+ return res
50
+ }
51
+
52
+ return toHexPart(arr, 0, length)
53
+ }
54
+
55
+ export function fromHex(str) {
56
+ if (typeof str !== 'string') throw new TypeError('Input is not a string')
57
+ assert(str.length % 2 === 0, 'Input is not a hex string')
58
+
59
+ // We don't use native Buffer impl, as rechecking input make it slower than pure js
60
+ // This path is used only on older engines though
61
+
62
+ if (!dehexArray) {
63
+ dehexArray = new Array(103) // f is 102
64
+ for (let i = 0; i < 16; i++) {
65
+ const s = i.toString(16)
66
+ dehexArray[s.charCodeAt(0)] = dehexArray[s.toUpperCase().charCodeAt(0)] = i
67
+ }
68
+ }
69
+
70
+ const length = str.length / 2 // this helps Hermes in loops
71
+ const arr = new Uint8Array(length)
72
+ let j = 0
73
+ if (nativeEncoder) {
74
+ // Native encoder path is beneficial even for small arrays in Hermes
75
+ const codes = nativeEncoder.encode(str)
76
+ for (let i = 0; i < length; i++) {
77
+ const a = dehexArray[codes[j++]] * 16 + dehexArray[codes[j++]]
78
+ if (!a && a !== 0) throw new Error('Input is not a hex string')
79
+ arr[i] = a
80
+ }
81
+ } else {
82
+ for (let i = 0; i < length; i++) {
83
+ const a = dehexArray[str.charCodeAt(j++)] * 16 + dehexArray[str.charCodeAt(j++)]
84
+ if (!a && a !== 0) throw new Error('Input is not a hex string')
85
+ arr[i] = a
86
+ }
87
+ }
88
+
89
+ return arr
90
+ }
package/hex.js CHANGED
@@ -1,78 +1,22 @@
1
- import { assertTypedArray, assert } from './assert.js'
2
- import { fromTypedArray } from './array.js'
1
+ import { assertTypedArray } from './assert.js'
2
+ import { typedView } from './array.js'
3
+ import * as js from './fallback/hex.js'
3
4
 
4
5
  const { Buffer } = globalThis // Buffer is optional, only used when native
5
6
  const haveNativeBuffer = Buffer && !Buffer.TYPED_ARRAY_SUPPORT
6
7
  const { toHex: webHex } = Uint8Array.prototype // Modern engines have this
7
8
 
8
- let hexArray
9
- let dehexArray
10
-
11
9
  export function toHex(arr) {
12
10
  assertTypedArray(arr)
13
11
  if (!(arr instanceof Uint8Array)) arr = new Uint8Array(arr.buffer, arr.byteOffset, arr.byteLength)
12
+ if (arr.length === 0) return ''
14
13
  if (webHex && arr.toHex === webHex) return arr.toHex()
15
- if (haveNativeBuffer) {
16
- if (arr.constructor === Buffer && Buffer.isBuffer(arr)) return arr.toString('hex')
17
- return Buffer.from(arr.buffer, arr.byteOffset, arr.byteLength).toString('hex')
18
- }
19
-
20
- if (!hexArray) hexArray = Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, '0'))
21
- const length = arr.length // this helps Hermes
22
-
23
- if (length > 30000) {
24
- // Limit concatenation to avoid excessive GC
25
- // Thresholds checked on Hermes
26
- const concat = []
27
- for (let i = 0; i < length; ) {
28
- const step = i + 500
29
- const end = step > length ? length : step
30
- let chunk = ''
31
- for (; i < end; i++) chunk += hexArray[arr[i]]
32
- concat.push(chunk)
33
- }
34
-
35
- const res = concat.join('')
36
- concat.length = 0
37
- return res
38
- }
39
-
40
- let out = ''
41
- for (let i = 0; i < length; i++) out += hexArray[arr[i]]
42
- return out
14
+ if (!haveNativeBuffer) return js.toHex(arr)
15
+ if (arr.constructor === Buffer && Buffer.isBuffer(arr)) return arr.toString('hex')
16
+ return Buffer.from(arr.buffer, arr.byteOffset, arr.byteLength).toString('hex')
43
17
  }
44
18
 
45
19
  // Unlike Buffer.from(), throws on invalid input
46
- let fromHex
47
- if (Uint8Array.fromHex) {
48
- fromHex = (str, format = 'uint8') => fromTypedArray(Uint8Array.fromHex(str), format)
49
- } else {
50
- fromHex = (str, format = 'uint8') => {
51
- if (typeof str !== 'string') throw new TypeError('Input is not a string')
52
- assert(str.length % 2 === 0, 'Input is not a hex string')
53
-
54
- // We don't use native Buffer impl, as rechecking input make it slower than pure js
55
- // This path is used only on older engines though
56
-
57
- if (!dehexArray) {
58
- dehexArray = new Array(103) // f is 102
59
- for (let i = 0; i < 16; i++) {
60
- const s = i.toString(16)
61
- dehexArray[s.charCodeAt(0)] = dehexArray[s.toUpperCase().charCodeAt(0)] = i
62
- }
63
- }
64
-
65
- const arr = new Uint8Array(str.length / 2)
66
- let j = 0
67
- const length = arr.length // this helps Hermes
68
- for (let i = 0; i < length; i++) {
69
- const a = dehexArray[str.charCodeAt(j++)] * 16 + dehexArray[str.charCodeAt(j++)]
70
- if (!a && Number.isNaN(a)) throw new Error('Input is not a hex string')
71
- arr[i] = a
72
- }
73
-
74
- return fromTypedArray(arr, format)
75
- }
76
- }
77
-
78
- export { fromHex }
20
+ export const fromHex = Uint8Array.fromHex
21
+ ? (str, format = 'uint8') => typedView(Uint8Array.fromHex(str), format)
22
+ : (str, format = 'uint8') => typedView(js.fromHex(str), format)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/bytes",
3
- "version": "1.0.0-rc.2",
3
+ "version": "1.0.0-rc.3",
4
4
  "description": "Various operations on Uint8Array data",
5
5
  "scripts": {
6
6
  "lint": "eslint .",
@@ -39,14 +39,16 @@
39
39
  },
40
40
  "type": "module",
41
41
  "files": [
42
+ "/fallback/base64.js",
43
+ "/fallback/hex.js",
44
+ "/array.js",
42
45
  "/assert.js",
43
46
  "/base64.js",
44
- "/array.js",
45
47
  "/hex.js"
46
48
  ],
47
49
  "exports": {
48
- "./base64.js": "./base64.js",
49
50
  "./array.js": "./array.js",
51
+ "./base64.js": "./base64.js",
50
52
  "./hex.js": "./hex.js"
51
53
  },
52
54
  "dependencies": {},