@atcute/uint8array 1.0.5 → 1.1.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/lib/index.ts CHANGED
@@ -135,56 +135,192 @@ export const encodeUtf8Into = (to: Uint8Array, str: string, offset?: number, len
135
135
  return result.written;
136
136
  };
137
137
 
138
- const fromCharCode = String.fromCharCode;
138
+ const _fromCharCode = String.fromCharCode;
139
+
140
+ // fully unrolled short string decoder, inspired by cbor-x
141
+ // returns null if non-ASCII byte encountered, signaling fallback to TextDecoder
142
+ const _shortString = (from: Uint8Array, p: number, length: number): string | null => {
143
+ if (length < 4) {
144
+ if (length < 2) {
145
+ if (length === 0) return '';
146
+ const a = from[p];
147
+ if (a & 0x80) return null;
148
+ return _fromCharCode(a);
149
+ }
150
+ const a = from[p];
151
+ const b = from[p + 1];
152
+ if ((a | b) & 0x80) return null;
153
+ if (length === 2) return _fromCharCode(a, b);
154
+ const c = from[p + 2];
155
+ if (c & 0x80) return null;
156
+ return _fromCharCode(a, b, c);
157
+ }
158
+ const a = from[p];
159
+ const b = from[p + 1];
160
+ const c = from[p + 2];
161
+ const d = from[p + 3];
162
+ if ((a | b | c | d) & 0x80) return null;
163
+ if (length < 8) {
164
+ if (length === 4) return _fromCharCode(a, b, c, d);
165
+ const e = from[p + 4];
166
+ if (e & 0x80) return null;
167
+ if (length === 5) return _fromCharCode(a, b, c, d, e);
168
+ const f = from[p + 5];
169
+ if (f & 0x80) return null;
170
+ if (length === 6) return _fromCharCode(a, b, c, d, e, f);
171
+ const g = from[p + 6];
172
+ if (g & 0x80) return null;
173
+ return _fromCharCode(a, b, c, d, e, f, g);
174
+ }
175
+ const e = from[p + 4];
176
+ const f = from[p + 5];
177
+ const g = from[p + 6];
178
+ const h = from[p + 7];
179
+ if ((e | f | g | h) & 0x80) return null;
180
+ if (length < 12) {
181
+ if (length === 8) return _fromCharCode(a, b, c, d, e, f, g, h);
182
+ const i = from[p + 8];
183
+ if (i & 0x80) return null;
184
+ if (length === 9) return _fromCharCode(a, b, c, d, e, f, g, h, i);
185
+ const j = from[p + 9];
186
+ if (j & 0x80) return null;
187
+ if (length === 10) return _fromCharCode(a, b, c, d, e, f, g, h, i, j);
188
+ const k = from[p + 10];
189
+ if (k & 0x80) return null;
190
+ return _fromCharCode(a, b, c, d, e, f, g, h, i, j, k);
191
+ }
192
+ const i = from[p + 8];
193
+ const j = from[p + 9];
194
+ const k = from[p + 10];
195
+ const l = from[p + 11];
196
+ if ((i | j | k | l) & 0x80) return null;
197
+ if (length === 12) return _fromCharCode(a, b, c, d, e, f, g, h, i, j, k, l);
198
+ const m = from[p + 12];
199
+ if (m & 0x80) return null;
200
+ if (length === 13) return _fromCharCode(a, b, c, d, e, f, g, h, i, j, k, l, m);
201
+ const n = from[p + 13];
202
+ if (n & 0x80) return null;
203
+ if (length === 14) return _fromCharCode(a, b, c, d, e, f, g, h, i, j, k, l, m, n);
204
+ const o = from[p + 14];
205
+ if (o & 0x80) return null;
206
+ return _fromCharCode(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o);
207
+ };
139
208
 
140
209
  /**
141
210
  * decodes a UTF-8 string from a given buffer
211
+ * @param from source buffer
212
+ * @param offset byte offset to start reading from
213
+ * @param length number of bytes to read
214
+ * @returns decoded string
142
215
  */
143
- export const decodeUtf8From = (from: Uint8Array, offset?: number, length?: number): string => {
144
- let buffer: Uint8Array;
145
-
146
- if (offset === undefined) {
147
- buffer = from;
148
- } else if (length === undefined) {
149
- buffer = from.subarray(offset);
150
- } else {
151
- buffer = from.subarray(offset, offset + length);
216
+ export const decodeUtf8From = (
217
+ from: Uint8Array,
218
+ offset: number = 0,
219
+ length: number = from.length,
220
+ ): string => {
221
+ if (length <= 15) {
222
+ const result = _shortString(from, offset, length);
223
+ if (result !== null) return result;
152
224
  }
225
+ return textDecoder.decode(from.subarray(offset, offset + length));
226
+ };
153
227
 
154
- const end = buffer.length;
155
- if (end > 24) {
156
- return textDecoder.decode(buffer);
157
- }
228
+ /**
229
+ * calculates the UTF-8 byte length of a string
230
+ * @param str string to measure
231
+ * @returns byte length when encoded as UTF-8
232
+ */
233
+ export const getUtf8Length = (str: string): number => {
234
+ const len = str.length;
158
235
 
159
- {
160
- let str = '';
161
- let idx = 0;
236
+ let u16pos = 0;
237
+ let u8pos = 0;
162
238
 
163
- for (; idx + 3 < end; idx += 4) {
164
- const a = buffer[idx];
165
- const b = buffer[idx + 1];
166
- const c = buffer[idx + 2];
167
- const d = buffer[idx + 3];
239
+ // ASCII fast-path: batch process 4 chars at a time
240
+ while (u16pos + 3 < len) {
241
+ const a = str.charCodeAt(u16pos);
242
+ const b = str.charCodeAt(u16pos + 1);
243
+ const c = str.charCodeAt(u16pos + 2);
244
+ const d = str.charCodeAt(u16pos + 3);
168
245
 
169
- if ((a | b | c | d) & 0x80) {
170
- return str + textDecoder.decode(buffer.subarray(idx));
171
- }
246
+ if ((a | b | c | d) >= 0x80) {
247
+ break;
248
+ }
249
+
250
+ u16pos += 4;
251
+ u8pos += 4;
252
+ }
172
253
 
173
- str += fromCharCode(a, b, c, d);
254
+ // handle remaining chars
255
+ while (u16pos < len) {
256
+ const code = str.charCodeAt(u16pos);
257
+
258
+ if (code < 0x80) {
259
+ u16pos += 1;
260
+ u8pos += 1;
261
+ } else if (code < 0x800) {
262
+ u16pos += 1;
263
+ u8pos += 2;
264
+ } else if (code < 0xd800 || code > 0xdbff) {
265
+ u16pos += 1;
266
+ u8pos += 3;
267
+ } else {
268
+ u16pos += 2;
269
+ u8pos += 4;
174
270
  }
271
+ }
175
272
 
176
- for (; idx < end; idx++) {
177
- const x = buffer[idx];
273
+ return u8pos;
274
+ };
178
275
 
179
- if (x & 0x80) {
180
- return str + textDecoder.decode(buffer.subarray(idx));
181
- }
276
+ /**
277
+ * checks if a string's UTF-8 byte length is within a given range.
278
+ * includes early-exit optimization when exceeding max length.
279
+ * @param str string to measure
280
+ * @param min minimum byte length (inclusive)
281
+ * @param max maximum byte length (inclusive)
282
+ * @returns true if byte length is within [min, max]
283
+ */
284
+ export const isUtf8LengthInRange = (str: string, min: number, max: number): boolean => {
285
+ const len = str.length;
286
+
287
+ // fast path: if max possible UTF-8 length is below min, fail
288
+ if (len * 3 < min) {
289
+ return false;
290
+ }
182
291
 
183
- str += fromCharCode(x);
292
+ // fast path: if UTF-16 length satisfies min and max possible satisfies max
293
+ if (len >= min && len * 3 <= max) {
294
+ return true;
295
+ }
296
+
297
+ let u16pos = 0;
298
+ let u8pos = 0;
299
+
300
+ while (u16pos < len) {
301
+ const code = str.charCodeAt(u16pos);
302
+
303
+ if (code < 0x80) {
304
+ u16pos += 1;
305
+ u8pos += 1;
306
+ } else if (code < 0x800) {
307
+ u16pos += 1;
308
+ u8pos += 2;
309
+ } else if (code < 0xd800 || code > 0xdbff) {
310
+ u16pos += 1;
311
+ u8pos += 3;
312
+ } else {
313
+ u16pos += 2;
314
+ u8pos += 4;
184
315
  }
185
316
 
186
- return str;
317
+ // early exit once we exceed max
318
+ if (u8pos > max) {
319
+ return false;
320
+ }
187
321
  }
322
+
323
+ return u8pos >= min;
188
324
  };
189
325
 
190
326
  /**
@@ -193,3 +329,12 @@ export const decodeUtf8From = (from: Uint8Array, offset?: number, length?: numbe
193
329
  export const toSha256 = async (buffer: Uint8Array<ArrayBuffer>): Promise<Uint8Array<ArrayBuffer>> => {
194
330
  return new Uint8Array(await subtle.digest('SHA-256', buffer));
195
331
  };
332
+
333
+ /**
334
+ * generates cryptographically secure random bytes
335
+ * @param size number of bytes to generate
336
+ * @returns buffer filled with random bytes
337
+ */
338
+ export const randomBytes = (size: number): Uint8Array<ArrayBuffer> => {
339
+ return crypto.getRandomValues(new Uint8Array(size));
340
+ };
package/package.json CHANGED
@@ -1,12 +1,11 @@
1
1
  {
2
- "type": "module",
3
2
  "name": "@atcute/uint8array",
4
- "version": "1.0.5",
3
+ "version": "1.1.0",
5
4
  "description": "uint8array utilities",
6
5
  "license": "0BSD",
7
6
  "repository": {
8
7
  "url": "https://github.com/mary-ext/atcute",
9
- "directory": "packages/utilities/uint8array"
8
+ "directory": "packages/misc/uint8array"
10
9
  },
11
10
  "files": [
12
11
  "dist/",
@@ -14,6 +13,8 @@
14
13
  "!lib/**/*.bench.ts",
15
14
  "!lib/**/*.test.ts"
16
15
  ],
16
+ "type": "module",
17
+ "sideEffects": false,
17
18
  "exports": {
18
19
  ".": {
19
20
  "bun": "./dist/index.bun.js",
@@ -21,13 +22,14 @@
21
22
  "default": "./dist/index.js"
22
23
  }
23
24
  },
24
- "sideEffects": false,
25
+ "publishConfig": {
26
+ "access": "public"
27
+ },
25
28
  "devDependencies": {
26
- "@types/bun": "^1.2.21"
29
+ "@types/bun": "^1.3.5"
27
30
  },
28
31
  "scripts": {
29
- "build": "tsc --project tsconfig.build.json",
30
- "test": "bun test --coverage",
32
+ "build": "tsgo --project tsconfig.build.json",
31
33
  "prepublish": "rm -rf dist; pnpm run build"
32
34
  }
33
35
  }