cborg 4.5.4 → 4.5.6

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/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ ## [4.5.6](https://github.com/rvagg/cborg/compare/v4.5.5...v4.5.6) (2026-01-21)
2
+
3
+ ## [4.5.5](https://github.com/rvagg/cborg/compare/v4.5.4...v4.5.5) (2026-01-20)
4
+
5
+ ### Trivial Changes
6
+
7
+ * **bench:** output json to file ([ca32690](https://github.com/rvagg/cborg/commit/ca326908643ce9bc2ac56dd06b6c64502b8a4f03))
8
+
1
9
  ## [4.5.4](https://github.com/rvagg/cborg/compare/v4.5.3...v4.5.4) (2026-01-20)
2
10
 
3
11
  ## [4.5.3](https://github.com/rvagg/cborg/compare/v4.5.2...v4.5.3) (2026-01-20)
package/bench/bench.js CHANGED
@@ -5,12 +5,12 @@
5
5
  * Works in both Node.js and browser environments.
6
6
  *
7
7
  * Usage:
8
- * node bench/bench-new.js # run all benchmarks (dag-cbor mode)
9
- * node bench/bench-new.js --mode=raw # run with raw cborg (no tags)
10
- * node bench/bench-new.js --suite=bsky # run only bluesky suite
11
- * node bench/bench-new.js --json # output JSON for comparison
12
- * node bench/bench-new.js --compare=baseline.json # compare to baseline
13
- * node bench/bench-new.js --encode-into # use encodeInto instead of encode
8
+ * node bench/bench.js # run all benchmarks (dag-cbor mode)
9
+ * node bench/bench.js --mode=raw # run with raw cborg (no tags)
10
+ * node bench/bench.js --suite=bsky # run only bluesky suite
11
+ * node bench/bench.js --json=output.json # write JSON results to file
12
+ * node bench/bench.js --compare=baseline.json # compare to baseline
13
+ * node bench/bench.js --encode-into # use encodeInto instead of encode
14
14
  */
15
15
 
16
16
  import { encode, decode, encodeInto, Token, Type } from '../cborg.js'
@@ -129,7 +129,7 @@ const FIXTURE_SEED = 12345
129
129
  // Parse CLI args (Node.js only, ignored in browser)
130
130
  const args = typeof process !== 'undefined' ? process.argv.slice(2) : []
131
131
  const opts = {
132
- json: args.includes('--json'),
132
+ json: args.find(a => a.startsWith('--json='))?.split('=')[1] || null,
133
133
  suite: args.find(a => a.startsWith('--suite='))?.split('=')[1] || null,
134
134
  compare: args.find(a => a.startsWith('--compare='))?.split('=')[1] || null,
135
135
  duration: parseInt(args.find(a => a.startsWith('--duration='))?.split('=')[1] || DEFAULT_DURATION_MS),
@@ -184,11 +184,11 @@ function getOptions (suiteType = 'default') {
184
184
  }
185
185
  }
186
186
 
187
- // Output helpers
188
- const log = opts.json ? () => {} : console.log.bind(console)
187
+ // Output helpers - always show progress to console
188
+ const log = console.log.bind(console)
189
189
  const write = typeof process !== 'undefined' && process.stdout
190
190
  ? (s) => process.stdout.write(s)
191
- : (s) => log(s)
191
+ : (s) => console.log(s)
192
192
 
193
193
  /**
194
194
  * Run a benchmark function for a duration, return ops/sec
@@ -364,7 +364,7 @@ async function main () {
364
364
  const avgDecode = Math.round(allDecodeRates.reduce((a, b) => a + b, 0) / allDecodeRates.length * 10) / 10
365
365
  log(`Average throughput: encode ${avgEncode} MB/s, decode ${avgDecode} MB/s`)
366
366
 
367
- // JSON output
367
+ // JSON output to file
368
368
  if (opts.json) {
369
369
  const output = {
370
370
  timestamp: new Date().toISOString(),
@@ -375,7 +375,11 @@ async function main () {
375
375
  suites: allResults,
376
376
  summary: { avgEncodeMBps: avgEncode, avgDecodeMBps: avgDecode }
377
377
  }
378
- console.log(JSON.stringify(output, null, 2))
378
+ if (typeof process !== 'undefined') {
379
+ const fs = await import('fs')
380
+ fs.writeFileSync(opts.json, JSON.stringify(output, null, 2) + '\n')
381
+ log(`\nResults written to ${opts.json}`)
382
+ }
379
383
  }
380
384
 
381
385
  // Compare to baseline
package/lib/2bytes.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { Token, Type } from './token.js'
2
2
  import { assertEnoughData, decodeErrPrefix } from './common.js'
3
3
  import * as uint from './0uint.js'
4
- import { compare, fromString, slice } from './byte-utils.js'
4
+ import { compare, fromString } from './byte-utils.js'
5
5
 
6
6
  /**
7
7
  * @typedef {import('../interface').ByteWriter} ByteWriter
@@ -17,7 +17,7 @@ import { compare, fromString, slice } from './byte-utils.js'
17
17
  */
18
18
  function toToken (data, pos, prefix, length) {
19
19
  assertEnoughData(data, pos, prefix + length)
20
- const buf = slice(data, pos + prefix, pos + prefix + length)
20
+ const buf = data.slice(pos + prefix, pos + prefix + length)
21
21
  return new Token(Type.bytes, buf, prefix + length)
22
22
  }
23
23
 
package/lib/3string.js CHANGED
@@ -2,13 +2,42 @@ import { Token, Type } from './token.js'
2
2
  import { assertEnoughData, decodeErrPrefix } from './common.js'
3
3
  import * as uint from './0uint.js'
4
4
  import { encodeBytes } from './2bytes.js'
5
- import { toString, slice } from './byte-utils.js'
5
+
6
+ const textDecoder = new TextDecoder()
7
+
8
+ // Threshold for ASCII fast-path vs TextDecoder. Short ASCII strings (common for
9
+ // map keys) are faster to decode with a simple loop than TextDecoder overhead.
10
+ const ASCII_THRESHOLD = 32
6
11
 
7
12
  /**
8
13
  * @typedef {import('../interface').ByteWriter} ByteWriter
9
14
  * @typedef {import('../interface').DecodeOptions} DecodeOptions
10
15
  */
11
16
 
17
+ /**
18
+ * Decode UTF-8 bytes to string. For short ASCII strings (common case for map keys),
19
+ * a simple loop is faster than TextDecoder.
20
+ * @param {Uint8Array} bytes
21
+ * @param {number} start
22
+ * @param {number} end
23
+ * @returns {string}
24
+ */
25
+ function toStr (bytes, start, end) {
26
+ const len = end - start
27
+ if (len < ASCII_THRESHOLD) {
28
+ let str = ''
29
+ for (let i = start; i < end; i++) {
30
+ const c = bytes[i]
31
+ if (c & 0x80) { // non-ASCII, fall back to TextDecoder
32
+ return textDecoder.decode(bytes.subarray(start, end))
33
+ }
34
+ str += String.fromCharCode(c)
35
+ }
36
+ return str
37
+ }
38
+ return textDecoder.decode(bytes.subarray(start, end))
39
+ }
40
+
12
41
  /**
13
42
  * @param {Uint8Array} data
14
43
  * @param {number} pos
@@ -20,9 +49,9 @@ import { toString, slice } from './byte-utils.js'
20
49
  function toToken (data, pos, prefix, length, options) {
21
50
  const totLength = prefix + length
22
51
  assertEnoughData(data, pos, totLength)
23
- const tok = new Token(Type.string, toString(data, pos + prefix, pos + totLength), totLength)
52
+ const tok = new Token(Type.string, toStr(data, pos + prefix, pos + totLength), totLength)
24
53
  if (options.retainStringBytes === true) {
25
- tok.byteValue = slice(data, pos + prefix, pos + totLength)
54
+ tok.byteValue = data.slice(pos + prefix, pos + totLength)
26
55
  }
27
56
  return tok
28
57
  }
package/lib/byte-utils.js CHANGED
@@ -10,7 +10,6 @@ export const useBuffer = globalThis.process &&
10
10
  // @ts-ignore
11
11
  typeof globalThis.Buffer.isBuffer === 'function'
12
12
 
13
- const textDecoder = new TextDecoder()
14
13
  const textEncoder = new TextEncoder()
15
14
 
16
15
  /**
@@ -34,37 +33,6 @@ export function asU8A (buf) {
34
33
  return isBuffer(buf) ? new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength) : buf
35
34
  }
36
35
 
37
- // Threshold for switching between manual utf8Slice and native TextDecoder/Buffer
38
- // Manual decoding has overhead from array building; native is fast for strings > 32 bytes
39
- const UTF8_THRESHOLD = 32
40
-
41
- export const toString = useBuffer
42
- ? // eslint-disable-line operator-linebreak
43
- /**
44
- * @param {Uint8Array} bytes
45
- * @param {number} start
46
- * @param {number} end
47
- */
48
- (bytes, start, end) => {
49
- return end - start > UTF8_THRESHOLD
50
- ? // eslint-disable-line operator-linebreak
51
- // @ts-ignore
52
- globalThis.Buffer.from(bytes.subarray(start, end)).toString('utf8')
53
- : utf8Slice(bytes, start, end)
54
- }
55
- /* c8 ignore next 11 */
56
- : // eslint-disable-line operator-linebreak
57
- /**
58
- * @param {Uint8Array} bytes
59
- * @param {number} start
60
- * @param {number} end
61
- */
62
- (bytes, start, end) => {
63
- return end - start > UTF8_THRESHOLD
64
- ? textDecoder.decode(bytes.subarray(start, end))
65
- : utf8Slice(bytes, start, end)
66
- }
67
-
68
36
  export const fromString = useBuffer
69
37
  ? // eslint-disable-line operator-linebreak
70
38
  /**
@@ -102,6 +70,7 @@ export const slice = useBuffer
102
70
  * @param {number} start
103
71
  * @param {number} end
104
72
  */
73
+ // Buffer.slice() returns a view, not a copy, so we need special handling
105
74
  (bytes, start, end) => {
106
75
  if (isBuffer(bytes)) {
107
76
  return new Uint8Array(bytes.subarray(start, end))
@@ -317,85 +286,6 @@ function utf8ToBytes (str) {
317
286
  return out
318
287
  }
319
288
 
320
- // The below code is mostly taken from https://github.com/feross/buffer
321
- // Licensed MIT. Copyright (c) Feross Aboukhadijeh
322
-
323
- /**
324
- * @param {Uint8Array} buf
325
- * @param {number} offset
326
- * @param {number} end
327
- * @returns {string}
328
- */
329
- function utf8Slice (buf, offset, end) {
330
- const res = []
331
-
332
- while (offset < end) {
333
- const firstByte = buf[offset]
334
- let codePoint = null
335
- let bytesPerSequence = (firstByte > 0xef) ? 4 : (firstByte > 0xdf) ? 3 : (firstByte > 0xbf) ? 2 : 1
336
-
337
- if (offset + bytesPerSequence <= end) {
338
- let secondByte, thirdByte, fourthByte, tempCodePoint
339
-
340
- switch (bytesPerSequence) {
341
- case 1:
342
- if (firstByte < 0x80) {
343
- codePoint = firstByte
344
- }
345
- break
346
- case 2:
347
- secondByte = buf[offset + 1]
348
- if ((secondByte & 0xc0) === 0x80) {
349
- tempCodePoint = (firstByte & 0x1f) << 0x6 | (secondByte & 0x3f)
350
- if (tempCodePoint > 0x7f) {
351
- codePoint = tempCodePoint
352
- }
353
- }
354
- break
355
- case 3:
356
- secondByte = buf[offset + 1]
357
- thirdByte = buf[offset + 2]
358
- if ((secondByte & 0xc0) === 0x80 && (thirdByte & 0xc0) === 0x80) {
359
- tempCodePoint = (firstByte & 0xf) << 0xc | (secondByte & 0x3f) << 0x6 | (thirdByte & 0x3f)
360
- /* c8 ignore next 3 */
361
- if (tempCodePoint > 0x7ff && (tempCodePoint < 0xd800 || tempCodePoint > 0xdfff)) {
362
- codePoint = tempCodePoint
363
- }
364
- }
365
- break
366
- case 4:
367
- secondByte = buf[offset + 1]
368
- thirdByte = buf[offset + 2]
369
- fourthByte = buf[offset + 3]
370
- if ((secondByte & 0xc0) === 0x80 && (thirdByte & 0xc0) === 0x80 && (fourthByte & 0xc0) === 0x80) {
371
- tempCodePoint = (firstByte & 0xf) << 0x12 | (secondByte & 0x3f) << 0xc | (thirdByte & 0x3f) << 0x6 | (fourthByte & 0x3f)
372
- if (tempCodePoint > 0xffff && tempCodePoint < 0x110000) {
373
- codePoint = tempCodePoint
374
- }
375
- }
376
- }
377
- }
378
-
379
- /* c8 ignore next 5 */
380
- if (codePoint === null) {
381
- // we did not generate a valid codePoint so insert a
382
- // replacement char (U+FFFD) and advance only 1 byte
383
- codePoint = 0xfffd
384
- bytesPerSequence = 1
385
- } else if (codePoint > 0xffff) {
386
- // encode to utf16 (surrogate pair dance)
387
- codePoint -= 0x10000
388
- res.push(codePoint >>> 10 & 0x3ff | 0xd800)
389
- codePoint = 0xdc00 | codePoint & 0x3ff
390
- }
391
-
392
- res.push(codePoint)
393
- offset += bytesPerSequence
394
- }
395
-
396
- return decodeCodePointsArray(res)
397
- }
398
-
399
289
  // Based on http://stackoverflow.com/a/22747272/680742, the browser with
400
290
  // the lowest limit is Chrome, with 0x10000 args.
401
291
  // We go 1 magnitude less, for safety
package/lib/decode.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { decodeErrPrefix } from './common.js'
2
2
  import { Type } from './token.js'
3
3
  import { jump, quick } from './jump.js'
4
+ import { asU8A } from './byte-utils.js'
4
5
 
5
6
  /**
6
7
  * @typedef {import('./token.js').Token} Token
@@ -183,7 +184,9 @@ function decodeFirst (data, options) {
183
184
  throw new Error(`${decodeErrPrefix} data to decode must be a Uint8Array`)
184
185
  }
185
186
  options = Object.assign({}, defaultDecodeOptions, options)
186
- const tokeniser = options.tokenizer || new Tokeniser(data, options)
187
+ // Convert Buffer to plain Uint8Array for faster slicing in decode path
188
+ const u8aData = asU8A(data)
189
+ const tokeniser = options.tokenizer || new Tokeniser(u8aData, options)
187
190
  const decoded = tokensToObject(tokeniser, options)
188
191
  if (decoded === DONE) {
189
192
  throw new Error(`${decodeErrPrefix} did not find any content to decode`)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cborg",
3
- "version": "4.5.4",
3
+ "version": "4.5.6",
4
4
  "description": "Fast CBOR with a focus on strictness",
5
5
  "main": "cborg.js",
6
6
  "type": "module",
@@ -1 +1 @@
1
- {"version":3,"file":"3string.d.ts","sourceRoot":"","sources":["../../lib/3string.js"],"names":[],"mappings":"AA6BA;;;;;;GAMG;AACH,0CANW,UAAU,OACV,MAAM,SACN,MAAM,WACN,aAAa,GACX,KAAK,CAIjB;AAED;;;;;;GAMG;AACH,oCANW,UAAU,OACV,MAAM,UACN,MAAM,WACN,aAAa,GACX,KAAK,CAIjB;AAED;;;;;;GAMG;AACH,qCANW,UAAU,OACV,MAAM,UACN,MAAM,WACN,aAAa,GACX,KAAK,CAIjB;AAED;;;;;;GAMG;AACH,qCANW,UAAU,OACV,MAAM,UACN,MAAM,WACN,aAAa,GACX,KAAK,CAIjB;AAGD;;;;;;GAMG;AACH,qCANW,UAAU,OACV,MAAM,UACN,MAAM,WACN,aAAa,GACX,KAAK,CAQjB;AAED,8CAAuC;yBAlF1B,OAAO,cAAc,EAAE,UAAU;4BACjC,OAAO,cAAc,EAAE,aAAa;sBARrB,YAAY;4BAGZ,aAAa"}
1
+ {"version":3,"file":"3string.d.ts","sourceRoot":"","sources":["../../lib/3string.js"],"names":[],"mappings":"AA0DA;;;;;;GAMG;AACH,0CANW,UAAU,OACV,MAAM,SACN,MAAM,WACN,aAAa,GACX,KAAK,CAIjB;AAED;;;;;;GAMG;AACH,oCANW,UAAU,OACV,MAAM,UACN,MAAM,WACN,aAAa,GACX,KAAK,CAIjB;AAED;;;;;;GAMG;AACH,qCANW,UAAU,OACV,MAAM,UACN,MAAM,WACN,aAAa,GACX,KAAK,CAIjB;AAED;;;;;;GAMG;AACH,qCANW,UAAU,OACV,MAAM,UACN,MAAM,WACN,aAAa,GACX,KAAK,CAIjB;AAGD;;;;;;GAMG;AACH,qCANW,UAAU,OACV,MAAM,UACN,MAAM,WACN,aAAa,GACX,KAAK,CAQjB;AAED,8CAAuC;yBA1G1B,OAAO,cAAc,EAAE,UAAU;4BACjC,OAAO,cAAc,EAAE,aAAa;sBAbrB,YAAY;4BAGZ,aAAa"}
@@ -15,12 +15,6 @@ export function compare(b1: Uint8Array, b2: Uint8Array): number;
15
15
  */
16
16
  export function decodeCodePointsArray(codePoints: number[]): string;
17
17
  export const useBuffer: boolean;
18
- /**
19
- * @param {Uint8Array} bytes
20
- * @param {number} start
21
- * @param {number} end
22
- */
23
- export function toString(bytes: Uint8Array, start: number, end: number): string;
24
18
  export const fromString: ((string: string) => number[] | Buffer<ArrayBuffer>) | ((string: string) => number[] | Uint8Array<ArrayBuffer>);
25
19
  export function fromArray(arr: number[]): Uint8Array;
26
20
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"byte-utils.d.ts","sourceRoot":"","sources":["../../lib/byte-utils.js"],"names":[],"mappings":"AAwBA;;;GAGG;AACH,2BAHW,UAAU,GAAC,MAAM,EAAE,GACjB,UAAU,CAQtB;AAkOD;;;;GAIG;AACH,4BAJW,UAAU,MACV,UAAU,GACR,MAAM,CAgBlB;AA4HD;;;GAGG;AACH,kDAHW,MAAM,EAAE,GACN,MAAM,CAkBlB;AAnaD,gCAMkD;AAgC9C;;;;GAIG;AACH,gCAJW,UAAU,SACV,MAAM,OACN,MAAM,UAQhB;AAcL,mCAGe,MAAM,iDAYN,MAAM,yCAIhB;AAOE,+BAHI,MAAM,EAAE,GACN,UAAU,CAItB;AAIG;;;;GAIG;AACH,6BAJW,UAAU,SACV,MAAM,OACN,MAAM,2BAOhB;AAcD;;;;GAIG;AACH,+BAJW,UAAU,EAAE,UACZ,MAAM,GACJ,UAAU,CActB;AAwBD;;;GAGG;AACH,4BAHW,MAAM,GACJ,UAAU,CAMtB;AAaD;;;GAGG;AACH,yBAHW,UAAU,GACR,MAAM,CAQlB;AAiBH;;;GAGG;AACD,6BAHS,MAAM,GAAC,UAAU,GACf,UAAU,CAQpB"}
1
+ {"version":3,"file":"byte-utils.d.ts","sourceRoot":"","sources":["../../lib/byte-utils.js"],"names":[],"mappings":"AAuBA;;;GAGG;AACH,2BAHW,UAAU,GAAC,MAAM,EAAE,GACjB,UAAU,CAQtB;AAoMD;;;;GAIG;AACH,4BAJW,UAAU,MACV,UAAU,GACR,MAAM,CAgBlB;AA6CD;;;GAGG;AACH,kDAHW,MAAM,EAAE,GACN,MAAM,CAkBlB;AArTD,gCAMkD;AAyBlD,mCAGe,MAAM,iDAYN,MAAM,yCAIhB;AAOE,+BAHI,MAAM,EAAE,GACN,UAAU,CAItB;AAIG;;;;GAIG;AAEH,6BALW,UAAU,SACV,MAAM,OACN,MAAM,2BAQhB;AAcD;;;;GAIG;AACH,+BAJW,UAAU,EAAE,UACZ,MAAM,GACJ,UAAU,CActB;AAwBD;;;GAGG;AACH,4BAHW,MAAM,GACJ,UAAU,CAMtB;AAaD;;;GAGG;AACH,yBAHW,UAAU,GACR,MAAM,CAQlB;AAiBH;;;GAGG;AACD,6BAHS,MAAM,GAAC,UAAU,GACf,UAAU,CAQpB"}
@@ -1 +1 @@
1
- {"version":3,"file":"decode.d.ts","sourceRoot":"","sources":["../../lib/decode.js"],"names":[],"mappings":"oBAKa,OAAO,YAAY,EAAE,KAAK;4BAC1B,OAAO,cAAc,EAAE,aAAa;8BACpC,OAAO,cAAc,EAAE,eAAe;AAUnD;;GAEG;AACH,kCAFgB,eAAe;IAG7B;;;OAGG;IACH,kBAHW,UAAU,YACV,aAAa,EAMvB;IAHC,aAAa;IACb,kCAAgB;IAChB,8CAAsB;IAGxB,cAEC;IAED,gBAEC;IAED,mCAgBC;CACF;AA8ED;;;;GAIG;AACH,0CAJW,eAAe,WACf,aAAa,GACX,GAAG,6BAAW,CAoC1B;AAuBD;;;;GAIG;AACH,6BAJW,UAAU,YACV,aAAa,GACX,GAAG,CAQf;AAhCD;;;;GAIG;AACH,kCAJW,UAAU,YACV,aAAa,GACX,CAAC,GAAG,EAAE,UAAU,CAAC,CAgB7B;AAvID,mCAAiC;AADjC,kCAA+B"}
1
+ {"version":3,"file":"decode.d.ts","sourceRoot":"","sources":["../../lib/decode.js"],"names":[],"mappings":"oBAMa,OAAO,YAAY,EAAE,KAAK;4BAC1B,OAAO,cAAc,EAAE,aAAa;8BACpC,OAAO,cAAc,EAAE,eAAe;AAUnD;;GAEG;AACH,kCAFgB,eAAe;IAG7B;;;OAGG;IACH,kBAHW,UAAU,YACV,aAAa,EAMvB;IAHC,aAAa;IACb,kCAAgB;IAChB,8CAAsB;IAGxB,cAEC;IAED,gBAEC;IAED,mCAgBC;CACF;AA8ED;;;;GAIG;AACH,0CAJW,eAAe,WACf,aAAa,GACX,GAAG,6BAAW,CAoC1B;AAyBD;;;;GAIG;AACH,6BAJW,UAAU,YACV,aAAa,GACX,GAAG,CAQf;AAlCD;;;;GAIG;AACH,kCAJW,UAAU,YACV,aAAa,GACX,CAAC,GAAG,EAAE,UAAU,CAAC,CAkB7B;AAzID,mCAAiC;AADjC,kCAA+B"}