@exodus/bytes 1.0.0-rc.0 → 1.0.0-rc.10

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.
@@ -0,0 +1,180 @@
1
+ import { decodeLatin1, encodeCharcodes } from './latin1.js'
2
+ import { isLE } from './_utils.js'
3
+
4
+ export const E_STRICT = 'Input is not well-formed utf16'
5
+ export const E_STRICT_UNICODE = 'Input is not well-formed Unicode'
6
+
7
+ const replacementCodepoint = 0xff_fd
8
+ const replacementCodepointSwapped = 0xfd_ff
9
+
10
+ const to16 = (a) => new Uint16Array(a.buffer, a.byteOffset, a.byteLength / 2) // Requires checked length and alignment!
11
+
12
+ export function to16input(u8, le) {
13
+ // Assume even number of bytes
14
+ if (le === isLE) return to16(u8.byteOffset % 2 === 0 ? u8 : Uint8Array.from(u8))
15
+
16
+ const res = new Uint8Array(u8.length)
17
+
18
+ let i = 0
19
+ for (const last3 = u8.length - 3; i < last3; i += 4) {
20
+ const x0 = u8[i]
21
+ const x1 = u8[i + 1]
22
+ const x2 = u8[i + 2]
23
+ const x3 = u8[i + 3]
24
+ res[i] = x1
25
+ res[i + 1] = x0
26
+ res[i + 2] = x3
27
+ res[i + 3] = x2
28
+ }
29
+
30
+ for (const last = u8.length - 1; i < last; i += 2) {
31
+ const x0 = u8[i]
32
+ const x1 = u8[i + 1]
33
+ res[i] = x1
34
+ res[i + 1] = x0
35
+ }
36
+
37
+ return to16(res)
38
+ }
39
+
40
+ export const decode = (u16, loose = false, checked = false) => {
41
+ if (checked || isWellFormed(u16)) return decodeLatin1(u16, 0, u16.length) // it's capable of decoding Uint16Array to UTF-16 as well
42
+ if (!loose) throw new TypeError(E_STRICT)
43
+ return decodeLatin1(toWellFormed(Uint16Array.from(u16)), 0, u16.length) // cloned for replacement
44
+ }
45
+
46
+ export function encode(str, loose = false, checked = false, swapped = false) {
47
+ const arr = new Uint16Array(str.length)
48
+ if (checked) return swapped ? encodeCheckedSwapped(str, arr) : encodeChecked(str, arr)
49
+ return swapped ? encodeUncheckedSwapped(str, arr, loose) : encodeUnchecked(str, arr, loose)
50
+ }
51
+
52
+ // Splitting paths into small functions helps (at least on SpiderMonkey)
53
+ /* eslint-disable @exodus/mutable/no-param-reassign-prop-only */
54
+
55
+ const encodeChecked = (str, arr) => encodeCharcodes(str, arr) // Same as encodeLatin1, but with Uint16Array
56
+
57
+ function encodeCheckedSwapped(str, arr) {
58
+ // TODO: faster path for Hermes? See encodeCharcodes
59
+ const length = str.length
60
+ for (let i = 0; i < length; i++) {
61
+ const x = str.charCodeAt(i)
62
+ arr[i] = ((x & 0xff) << 8) | (x >> 8)
63
+ }
64
+
65
+ return arr
66
+ }
67
+
68
+ // lead: d800 - dbff, trail: dc00 - dfff
69
+
70
+ function encodeUnchecked(str, arr, loose = false) {
71
+ // TODO: faster path for Hermes? See encodeCharcodes
72
+ const length = str.length
73
+ for (let i = 0; i < length; i++) {
74
+ const code = str.charCodeAt(i)
75
+ arr[i] = code
76
+ if (code >= 0xd8_00 && code < 0xe0_00) {
77
+ // An unexpected trail or a lead at the very end of input
78
+ if (code > 0xdb_ff || i + 1 >= length) {
79
+ if (!loose) throw new TypeError(E_STRICT_UNICODE)
80
+ arr[i] = replacementCodepoint
81
+ } else {
82
+ const next = str.charCodeAt(i + 1) // Process valid pairs immediately
83
+ if (next < 0xdc_00 || next >= 0xe0_00) {
84
+ if (!loose) throw new TypeError(E_STRICT_UNICODE)
85
+ arr[i] = replacementCodepoint
86
+ } else {
87
+ i++ // consume next
88
+ arr[i] = next
89
+ }
90
+ }
91
+ }
92
+ }
93
+
94
+ return arr
95
+ }
96
+
97
+ function encodeUncheckedSwapped(str, arr, loose = false) {
98
+ // TODO: faster path for Hermes? See encodeCharcodes
99
+ const length = str.length
100
+ for (let i = 0; i < length; i++) {
101
+ const code = str.charCodeAt(i)
102
+ arr[i] = ((code & 0xff) << 8) | (code >> 8)
103
+ if (code >= 0xd8_00 && code < 0xe0_00) {
104
+ // An unexpected trail or a lead at the very end of input
105
+ if (code > 0xdb_ff || i + 1 >= length) {
106
+ if (!loose) throw new TypeError(E_STRICT_UNICODE)
107
+ arr[i] = replacementCodepointSwapped
108
+ } else {
109
+ const next = str.charCodeAt(i + 1) // Process valid pairs immediately
110
+ if (next < 0xdc_00 || next >= 0xe0_00) {
111
+ if (!loose) throw new TypeError(E_STRICT_UNICODE)
112
+ arr[i] = replacementCodepointSwapped
113
+ } else {
114
+ i++ // consume next
115
+ arr[i] = ((next & 0xff) << 8) | (next >> 8)
116
+ }
117
+ }
118
+ }
119
+ }
120
+
121
+ return arr
122
+ }
123
+
124
+ export function toWellFormed(u16) {
125
+ const length = u16.length
126
+ for (let i = 0; i < length; i++) {
127
+ const code = u16[i]
128
+ if (code >= 0xd8_00 && code < 0xe0_00) {
129
+ // An unexpected trail or a lead at the very end of input
130
+ if (code > 0xdb_ff || i + 1 >= length) {
131
+ u16[i] = replacementCodepoint
132
+ } else {
133
+ const next = u16[i + 1] // Process valid pairs immediately
134
+ if (next < 0xdc_00 || next >= 0xe0_00) {
135
+ u16[i] = replacementCodepoint
136
+ } else {
137
+ i++ // consume next
138
+ }
139
+ }
140
+ }
141
+ }
142
+
143
+ return u16
144
+ }
145
+
146
+ export function isWellFormed(u16) {
147
+ const length = u16.length
148
+ let i = 0
149
+
150
+ // Speedup with u32, by skipping to the first surrogate
151
+ // Only implemented for aligned input for now, but almost all input is aligned (pooled Buffer or 0 offset)
152
+ if (length > 32 && u16.byteOffset % 4 === 0) {
153
+ const u32length = (u16.byteLength / 4) | 0
154
+ const u32 = new Uint32Array(u16.buffer, u16.byteOffset, u32length)
155
+ for (const last3 = u32length - 3; ; i += 4) {
156
+ if (i >= last3) break // loop is fast enough for moving this here to be _very_ useful, likely due to array access checks
157
+ const a = u32[i]
158
+ const b = u32[i + 1]
159
+ const c = u32[i + 2]
160
+ const d = u32[i + 3]
161
+ if (a & 0x80_00_80_00 || b & 0x80_00_80_00 || c & 0x80_00_80_00 || d & 0x80_00_80_00) break
162
+ }
163
+
164
+ for (; i < u32length; i++) if (u32[i] & 0x80_00_80_00) break
165
+ i *= 2
166
+ }
167
+
168
+ for (; i < length; i++) {
169
+ const code = u16[i]
170
+ if (code >= 0xd8_00 && code < 0xe0_00) {
171
+ // An unexpected trail or a lead at the very end of input
172
+ if (code > 0xdb_ff || i + 1 >= length) return false
173
+ i++ // consume next
174
+ const next = u16[i] // Process valid pairs immediately
175
+ if (next < 0xdc_00 || next >= 0xe0_00) return false
176
+ }
177
+ }
178
+
179
+ return true
180
+ }
@@ -0,0 +1,245 @@
1
+ export const E_STRICT = 'Input is not well-formed utf8'
2
+ export const E_STRICT_UNICODE = 'Input is not well-formed Unicode'
3
+
4
+ const replacementPoint = 0xff_fd
5
+
6
+ // https://encoding.spec.whatwg.org/#utf-8-decoder
7
+ // We are most likely in loose mode, for non-loose escape & decodeURIComponent solved everything
8
+ export function decode(arr, loose, start = 0) {
9
+ start |= 0
10
+ const end = arr.length
11
+ let out = ''
12
+ const chunkSize = 0x2_00 // far below MAX_ARGUMENTS_LENGTH in npmjs.com/buffer, we use smaller chunks
13
+ const tmpSize = Math.min(end - start, chunkSize + 1) // need 1 extra slot for last codepoint, which can be 2 charcodes
14
+ const tmp = new Array(tmpSize).fill(0)
15
+ let ti = 0
16
+
17
+ for (let i = start; i < end; i++) {
18
+ if (ti >= chunkSize) {
19
+ tmp.length = ti // can be larger by 1 if last codepoint is two charcodes
20
+ out += String.fromCharCode.apply(String, tmp)
21
+ if (tmp.length <= chunkSize) tmp.push(0) // restore 1 extra slot for last codepoint
22
+ ti = 0
23
+ }
24
+
25
+ const byte = arr[i]
26
+ if (byte < 0x80) {
27
+ tmp[ti++] = byte
28
+ // ascii fast path is in ../utf8.js, this is called only on non-ascii input
29
+ // so we don't unroll this anymore
30
+ } else if (byte < 0xc2) {
31
+ if (!loose) throw new TypeError(E_STRICT)
32
+ tmp[ti++] = replacementPoint
33
+ } else if (byte < 0xe0) {
34
+ // need 1 more
35
+ if (i + 1 >= end) {
36
+ if (!loose) throw new TypeError(E_STRICT)
37
+ tmp[ti++] = replacementPoint
38
+ break
39
+ }
40
+
41
+ const byte1 = arr[i + 1]
42
+ if (byte1 < 0x80 || byte1 > 0xbf) {
43
+ if (!loose) throw new TypeError(E_STRICT)
44
+ tmp[ti++] = replacementPoint
45
+ continue
46
+ }
47
+
48
+ i++
49
+ tmp[ti++] = ((byte & 0x1f) << 6) | (byte1 & 0x3f)
50
+ } else if (byte < 0xf0) {
51
+ // need 2 more
52
+ if (i + 1 >= end) {
53
+ if (!loose) throw new TypeError(E_STRICT)
54
+ tmp[ti++] = replacementPoint
55
+ break
56
+ }
57
+
58
+ const lower = byte === 0xe0 ? 0xa0 : 0x80
59
+ const upper = byte === 0xed ? 0x9f : 0xbf
60
+ const byte1 = arr[i + 1]
61
+ if (byte1 < lower || byte1 > upper) {
62
+ if (!loose) throw new TypeError(E_STRICT)
63
+ tmp[ti++] = replacementPoint
64
+ continue
65
+ }
66
+
67
+ i++
68
+ if (i + 1 >= end) {
69
+ if (!loose) throw new TypeError(E_STRICT)
70
+ tmp[ti++] = replacementPoint
71
+ break
72
+ }
73
+
74
+ const byte2 = arr[i + 1]
75
+ if (byte2 < 0x80 || byte2 > 0xbf) {
76
+ if (!loose) throw new TypeError(E_STRICT)
77
+ tmp[ti++] = replacementPoint
78
+ continue
79
+ }
80
+
81
+ i++
82
+ tmp[ti++] = ((byte & 0xf) << 12) | ((byte1 & 0x3f) << 6) | (byte2 & 0x3f)
83
+ } else if (byte <= 0xf4) {
84
+ // need 3 more
85
+ if (i + 1 >= end) {
86
+ if (!loose) throw new TypeError(E_STRICT)
87
+ tmp[ti++] = replacementPoint
88
+ break
89
+ }
90
+
91
+ const lower = byte === 0xf0 ? 0x90 : 0x80
92
+ const upper = byte === 0xf4 ? 0x8f : 0xbf
93
+ const byte1 = arr[i + 1]
94
+ if (byte1 < lower || byte1 > upper) {
95
+ if (!loose) throw new TypeError(E_STRICT)
96
+ tmp[ti++] = replacementPoint
97
+ continue
98
+ }
99
+
100
+ i++
101
+ if (i + 1 >= end) {
102
+ if (!loose) throw new TypeError(E_STRICT)
103
+ tmp[ti++] = replacementPoint
104
+ break
105
+ }
106
+
107
+ const byte2 = arr[i + 1]
108
+ if (byte2 < 0x80 || byte2 > 0xbf) {
109
+ if (!loose) throw new TypeError(E_STRICT)
110
+ tmp[ti++] = replacementPoint
111
+ continue
112
+ }
113
+
114
+ i++
115
+ if (i + 1 >= end) {
116
+ if (!loose) throw new TypeError(E_STRICT)
117
+ tmp[ti++] = replacementPoint
118
+ break
119
+ }
120
+
121
+ const byte3 = arr[i + 1]
122
+ if (byte3 < 0x80 || byte3 > 0xbf) {
123
+ if (!loose) throw new TypeError(E_STRICT)
124
+ tmp[ti++] = replacementPoint
125
+ continue
126
+ }
127
+
128
+ i++
129
+ const codePoint =
130
+ ((byte & 0xf) << 18) | ((byte1 & 0x3f) << 12) | ((byte2 & 0x3f) << 6) | (byte3 & 0x3f)
131
+ if (codePoint > 0xff_ff) {
132
+ // split into char codes as String.fromCharCode is faster than String.fromCodePoint
133
+ const u = codePoint - 0x1_00_00
134
+ tmp[ti++] = 0xd8_00 + ((u >> 10) & 0x3_ff)
135
+ tmp[ti++] = 0xdc_00 + (u & 0x3_ff)
136
+ } else {
137
+ tmp[ti++] = codePoint
138
+ }
139
+ // eslint-disable-next-line sonarjs/no-duplicated-branches
140
+ } else {
141
+ if (!loose) throw new TypeError(E_STRICT)
142
+ tmp[ti++] = replacementPoint
143
+ }
144
+ }
145
+
146
+ if (ti === 0) return out
147
+ tmp.length = ti
148
+ return out + String.fromCharCode.apply(String, tmp)
149
+ }
150
+
151
+ export function encode(string, loose) {
152
+ const length = string.length
153
+ let small = true
154
+ let bytes = new Uint8Array(length) // assume ascii
155
+ let p = 0
156
+
157
+ for (let i = 0; i < length; i++) {
158
+ let code = string.charCodeAt(i)
159
+ if (code < 0x80) {
160
+ bytes[p++] = code
161
+ // Unroll the loop a bit for faster ops
162
+ while (true) {
163
+ i++
164
+ if (i >= length) break
165
+ code = string.charCodeAt(i)
166
+ if (code >= 0x80) break
167
+ bytes[p++] = code
168
+ i++
169
+ if (i >= length) break
170
+ code = string.charCodeAt(i)
171
+ if (code >= 0x80) break
172
+ bytes[p++] = code
173
+ i++
174
+ if (i >= length) break
175
+ code = string.charCodeAt(i)
176
+ if (code >= 0x80) break
177
+ bytes[p++] = code
178
+ i++
179
+ if (i >= length) break
180
+ code = string.charCodeAt(i)
181
+ if (code >= 0x80) break
182
+ bytes[p++] = code
183
+ }
184
+
185
+ if (i >= length) break
186
+ // now, code is present and >= 0x80
187
+ }
188
+
189
+ if (small) {
190
+ // TODO: use resizable array buffers? will have to return a non-resizeable one
191
+ if (p !== i) throw new Error('Unreachable') // Here, p === i (only when small is still true)
192
+ const bytesNew = new Uint8Array(p + (length - i) * 3) // maximium can be 3x of the string length in charcodes
193
+ bytesNew.set(bytes)
194
+ bytes = bytesNew
195
+ small = false
196
+ }
197
+
198
+ // surrogate, charcodes = [d800 + a & 3ff, dc00 + b & 3ff]; codePoint = 0x1_00_00 | (a << 10) | b
199
+ // lead: d800 - dbff
200
+ // trail: dc00 - dfff
201
+ if (code >= 0xd8_00 && code < 0xe0_00) {
202
+ // Can't be a valid trail as we already processed that below
203
+
204
+ if (code > 0xdb_ff || i + 1 >= length) {
205
+ // An unexpected trail or a lead at the very end of input
206
+ if (!loose) throw new TypeError(E_STRICT_UNICODE)
207
+ bytes[p++] = 0xef
208
+ bytes[p++] = 0xbf
209
+ bytes[p++] = 0xbd
210
+ continue
211
+ }
212
+
213
+ const next = string.charCodeAt(i + 1) // Process valid pairs immediately
214
+ if (next >= 0xdc_00 && next < 0xe0_00) {
215
+ // here, codePoint is always between 0x1_00_00 and 0x11_00_00, we encode as 4 bytes
216
+ const codePoint = (((code - 0xd8_00) << 10) | (next - 0xdc_00)) + 0x1_00_00
217
+ bytes[p++] = (codePoint >> 18) | 0xf0
218
+ bytes[p++] = ((codePoint >> 12) & 0x3f) | 0x80
219
+ bytes[p++] = ((codePoint >> 6) & 0x3f) | 0x80
220
+ bytes[p++] = (codePoint & 0x3f) | 0x80
221
+ i++ // consume next
222
+ } else {
223
+ // Next is not a trail, leave next unconsumed but process unmatched lead error
224
+ if (!loose) throw new TypeError(E_STRICT_UNICODE)
225
+ bytes[p++] = 0xef
226
+ bytes[p++] = 0xbf
227
+ bytes[p++] = 0xbd
228
+ }
229
+
230
+ continue
231
+ }
232
+
233
+ // We are left with a non-pair char code above ascii, it gets encoded to 2 or 3 bytes
234
+ if (code < 0x8_00) {
235
+ bytes[p++] = (code >> 6) | 0xc0
236
+ bytes[p++] = (code & 0x3f) | 0x80
237
+ } else {
238
+ bytes[p++] = (code >> 12) | 0xe0
239
+ bytes[p++] = ((code >> 6) & 0x3f) | 0x80
240
+ bytes[p++] = (code & 0x3f) | 0x80
241
+ }
242
+ }
243
+
244
+ return bytes.length === p ? bytes : bytes.slice(0, p)
245
+ }
package/hex.js CHANGED
@@ -1,78 +1,19 @@
1
- import { assertTypedArray, assert } from './assert.js'
2
- import { fromTypedArray } from './array.js'
1
+ import { assertUint8 } from './assert.js'
2
+ import { typedView } from './array.js'
3
+ import { skipWeb } from './fallback/_utils.js'
4
+ import * as js from './fallback/hex.js'
3
5
 
4
- const { Buffer } = globalThis // Buffer is optional, only used when native
5
- const haveNativeBuffer = Buffer && !Buffer.TYPED_ARRAY_SUPPORT
6
6
  const { toHex: webHex } = Uint8Array.prototype // Modern engines have this
7
7
 
8
- let hexArray
9
- let dehexArray
10
-
11
8
  export function toHex(arr) {
12
- assertTypedArray(arr)
13
- if (!(arr instanceof Uint8Array)) arr = new Uint8Array(arr.buffer, arr.byteOffset, arr.byteLength)
14
- 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 > 3000) {
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
9
+ assertUint8(arr)
10
+ if (arr.length === 0) return ''
11
+ if (!skipWeb && webHex && arr.toHex === webHex) return arr.toHex()
12
+ return js.toHex(arr)
43
13
  }
44
14
 
45
15
  // 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 }
16
+ export const fromHex =
17
+ !skipWeb && Uint8Array.fromHex
18
+ ? (str, format = 'uint8') => typedView(Uint8Array.fromHex(str), format)
19
+ : (str, format = 'uint8') => typedView(js.fromHex(str), format)
package/hex.node.js ADDED
@@ -0,0 +1,28 @@
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
+ const denoBug = Buffer.from('ag', 'hex').length > 0
9
+
10
+ export function toHex(arr) {
11
+ assertUint8(arr)
12
+ if (arr.length === 0) return ''
13
+ if (webHex && arr.toHex === webHex) return arr.toHex()
14
+ if (arr.constructor === Buffer && Buffer.isBuffer(arr)) return arr.hexSlice(0, arr.byteLength)
15
+ return Buffer.from(arr.buffer, arr.byteOffset, arr.byteLength).hexSlice(0, arr.byteLength)
16
+ }
17
+
18
+ // Unlike Buffer.from(), throws on invalid input
19
+ export const fromHex = Uint8Array.fromHex
20
+ ? (str, format = 'uint8') => typedView(Uint8Array.fromHex(str), format)
21
+ : (str, format = 'uint8') => {
22
+ if (typeof str !== 'string') throw new TypeError('Input is not a string')
23
+ if (str.length % 2 !== 0) throw new SyntaxError(E_HEX)
24
+ 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)
28
+ }
package/multi-byte.js ADDED
@@ -0,0 +1,13 @@
1
+ import { assertUint8 } from './assert.js'
2
+ import { multibyteDecoder } from './fallback/multi-byte.js'
3
+
4
+ export function createMultibyteDecoder(encoding, loose = false) {
5
+ const jsDecoder = multibyteDecoder(encoding, loose) // asserts
6
+ let streaming = false
7
+ return (arr, stream = false) => {
8
+ assertUint8(arr)
9
+ if (!streaming && arr.byteLength === 0) return ''
10
+ streaming = stream
11
+ return jsDecoder(arr, stream)
12
+ }
13
+ }
@@ -0,0 +1,25 @@
1
+ import { assertUint8 } from './assert.js'
2
+ import { isDeno } from './fallback/_utils.js'
3
+ import { isAsciiSuperset, multibyteDecoder } from './fallback/multi-byte.js'
4
+ import { isAscii } from 'node:buffer'
5
+
6
+ const toBuf = (x) => Buffer.from(x.buffer, x.byteOffset, x.byteLength)
7
+
8
+ export function createMultibyteDecoder(encoding, loose = false) {
9
+ const jsDecoder = multibyteDecoder(encoding, loose) // asserts
10
+ let streaming = false
11
+ const asciiSuperset = isAsciiSuperset(encoding)
12
+ return (arr, stream = false) => {
13
+ assertUint8(arr)
14
+ if (!streaming) {
15
+ if (arr.byteLength === 0) return ''
16
+ if (asciiSuperset && isAscii(arr)) {
17
+ if (isDeno) return toBuf(arr).toString()
18
+ return toBuf(arr).latin1Slice(0, arr.byteLength) // .latin1Slice is faster than .asciiSlice
19
+ }
20
+ }
21
+
22
+ streaming = stream
23
+ return jsDecoder(arr, stream)
24
+ }
25
+ }