cborg 4.4.1 → 4.5.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/CHANGELOG.md +9 -0
- package/README.md +21 -0
- package/bench/README.md +115 -0
- package/bench/bench-comparative.js +133 -0
- package/bench/bench.js +414 -101
- package/bench/fixtures.js +558 -0
- package/bench/index.html +405 -0
- package/cborg.js +2 -1
- package/interface.ts +8 -2
- package/lib/0uint.js +11 -11
- package/lib/1negint.js +4 -4
- package/lib/2bytes.js +5 -5
- package/lib/3string.js +1 -1
- package/lib/4array.js +4 -4
- package/lib/5map.js +4 -4
- package/lib/6tag.js +4 -4
- package/lib/7float.js +10 -10
- package/lib/bl.js +46 -0
- package/lib/encode.js +40 -15
- package/lib/is.js +12 -31
- package/lib/json/encode.js +10 -10
- package/package.json +1 -1
- package/test/test-0uint.js +12 -1
- package/test/test-1negint.js +12 -1
- package/test/test-2bytes.js +11 -1
- package/test/test-3string.js +11 -1
- package/test/test-4array.js +11 -1
- package/test/test-5map.js +11 -3
- package/test/test-6tag.js +19 -1
- package/test/test-7float.js +11 -1
- package/test/test-cbor-vectors.js +13 -2
- package/test/test-encodeInto.js +246 -0
- package/types/cborg.d.ts +2 -1
- package/types/cborg.d.ts.map +1 -1
- package/types/interface.d.ts +7 -2
- package/types/interface.d.ts.map +1 -1
- package/types/lib/0uint.d.ts +6 -6
- package/types/lib/0uint.d.ts.map +1 -1
- package/types/lib/1negint.d.ts +4 -4
- package/types/lib/1negint.d.ts.map +1 -1
- package/types/lib/2bytes.d.ts +3 -3
- package/types/lib/2bytes.d.ts.map +1 -1
- package/types/lib/3string.d.ts +1 -1
- package/types/lib/3string.d.ts.map +1 -1
- package/types/lib/4array.d.ts +3 -3
- package/types/lib/4array.d.ts.map +1 -1
- package/types/lib/5map.d.ts +3 -3
- package/types/lib/5map.d.ts.map +1 -1
- package/types/lib/6tag.d.ts +4 -4
- package/types/lib/6tag.d.ts.map +1 -1
- package/types/lib/7float.d.ts +3 -3
- package/types/lib/7float.d.ts.map +1 -1
- package/types/lib/bl.d.ts +25 -0
- package/types/lib/bl.d.ts.map +1 -1
- package/types/lib/encode.d.ts +12 -1
- package/types/lib/encode.d.ts.map +1 -1
- package/types/lib/is.d.ts.map +1 -1
- package/types/lib/json/encode.d.ts +1 -1
- package/types/lib/json/encode.d.ts.map +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
## [4.5.1](https://github.com/rvagg/cborg/compare/v4.5.0...v4.5.1) (2026-01-20)
|
|
2
|
+
|
|
3
|
+
## [4.5.0](https://github.com/rvagg/cborg/compare/v4.4.1...v4.5.0) (2026-01-20)
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
* add encodeInto() for BYO buffer encoding ([a577843](https://github.com/rvagg/cborg/commit/a5778432faa44be491ce2fdd814eceddd1979d77))
|
|
8
|
+
* **bench:** add realistic IPLD workload benchmarks ([07d9618](https://github.com/rvagg/cborg/commit/07d9618f1a86262d59690ff00e268db0ad0c6d4a))
|
|
9
|
+
|
|
1
10
|
## [4.4.1](https://github.com/rvagg/cborg/compare/v4.4.0...v4.4.1) (2026-01-19)
|
|
2
11
|
|
|
3
12
|
### Trivial Changes
|
package/README.md
CHANGED
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
* [API](#api)
|
|
26
26
|
* [`encode(object[, options])`](#encodeobject-options)
|
|
27
27
|
* [Options](#options)
|
|
28
|
+
* [`encodeInto(data, destination[, options])`](#encodeintodata-destination-options)
|
|
28
29
|
* [`decode(data[, options])`](#decodedata-options)
|
|
29
30
|
* [Options](#options-1)
|
|
30
31
|
* [`decodeFirst(data[, options])`](#decodefirstdata-options)
|
|
@@ -221,6 +222,26 @@ Encode a JavaScript object and return a `Uint8Array` with the CBOR byte represen
|
|
|
221
222
|
* `mapSorter` (function): a function taking two arguments, where each argument is a `Token`, or an array of `Token`s representing the keys of a map being encoded. Similar to other JavaScript compare functions, a `-1`, `1` or `0` (which shouldn't be possible) should be returned depending on the sorting order of the keys. See the source code for the default sorting order which uses the length-first rule recommendation from [RFC 7049](https://tools.ietf.org/html/rfc7049).
|
|
222
223
|
* `ignoreUndefinedProperties` (boolean, default `false`): when encoding a plain object, properties with `undefined` values will be omitted. Does not apply to `Map`s or arrays.
|
|
223
224
|
|
|
225
|
+
### `encodeInto(data, destination[, options])`
|
|
226
|
+
|
|
227
|
+
```js
|
|
228
|
+
import { encodeInto } from 'cborg'
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Encode a JavaScript object directly into a provided `Uint8Array` destination buffer, returning an object with a `written` property indicating the number of bytes written.
|
|
232
|
+
|
|
233
|
+
This API mirrors [`TextEncoder.encodeInto()`](https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder/encodeInto) and is useful for performance-critical scenarios where you want to avoid allocations by reusing a buffer.
|
|
234
|
+
|
|
235
|
+
```js
|
|
236
|
+
const destination = new Uint8Array(1024)
|
|
237
|
+
const { written } = encodeInto({ hello: 'world' }, destination)
|
|
238
|
+
const encoded = destination.subarray(0, written)
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
If the destination buffer is too small to hold the encoded data, an error will be thrown. Use `encodedLength()` to pre-calculate the required size if needed.
|
|
242
|
+
|
|
243
|
+
The same encoding rules and options as [`encode()`](#encodeobject-options) apply.
|
|
244
|
+
|
|
224
245
|
### `decode(data[, options])`
|
|
225
246
|
|
|
226
247
|
```js
|
package/bench/README.md
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# cborg Benchmarks
|
|
2
|
+
|
|
3
|
+
Benchmarks for measuring cborg encode/decode performance with realistic IPLD workloads.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Run all benchmarks (dag-cbor mode with tag 42 + strict options)
|
|
9
|
+
node bench/bench.js
|
|
10
|
+
|
|
11
|
+
# Run specific suite
|
|
12
|
+
node bench/bench.js --suite=bsky # Bluesky (string-heavy)
|
|
13
|
+
node bench/bench.js --suite=filecoin # Filecoin (bytes-heavy)
|
|
14
|
+
node bench/bench.js --suite=micro # Micro-benchmarks (maps, strings, integers, etc.)
|
|
15
|
+
|
|
16
|
+
# Run in raw mode (no tags, minimal options) for baseline comparison
|
|
17
|
+
node bench/bench.js --mode=raw
|
|
18
|
+
|
|
19
|
+
# Test encodeInto() performance
|
|
20
|
+
node bench/bench.js --encode-into
|
|
21
|
+
|
|
22
|
+
# Adjust duration per test (default 1000ms)
|
|
23
|
+
node bench/bench.js --duration=2000
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Browser Benchmarks
|
|
27
|
+
|
|
28
|
+
Open `bench/index.html` in a browser. Select mode, suite, and duration, then click "Run Benchmarks".
|
|
29
|
+
|
|
30
|
+
## Capturing and Comparing Results
|
|
31
|
+
|
|
32
|
+
### Save a baseline
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# Save results to a file
|
|
36
|
+
node bench/bench.js --json > bench/baselines/before-change.json
|
|
37
|
+
|
|
38
|
+
# Or with a version/date identifier
|
|
39
|
+
node bench/bench.js --json > bench/baselines/v4.4.0.json
|
|
40
|
+
node bench/bench.js --json > bench/baselines/$(date +%Y%m%d).json
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Compare against baseline
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
node bench/bench.js --compare=bench/baselines/before-change.json
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
This shows percentage differences for each benchmark.
|
|
50
|
+
|
|
51
|
+
### Typical workflow
|
|
52
|
+
|
|
53
|
+
1. Capture baseline before making changes:
|
|
54
|
+
```bash
|
|
55
|
+
node bench/bench.js --json > bench/baselines/before.json
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
2. Make your changes to cborg
|
|
59
|
+
|
|
60
|
+
3. Compare:
|
|
61
|
+
```bash
|
|
62
|
+
node bench/bench.js --compare=bench/baselines/before.json
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
4. Optionally save the new results:
|
|
66
|
+
```bash
|
|
67
|
+
node bench/bench.js --json > bench/baselines/after.json
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Benchmark Modes
|
|
71
|
+
|
|
72
|
+
### dag-cbor mode (default)
|
|
73
|
+
|
|
74
|
+
Simulates `@ipld/dag-cbor` usage:
|
|
75
|
+
- CID encoding/decoding via CBOR tag 42
|
|
76
|
+
- `float64: true` (always 64-bit floats)
|
|
77
|
+
- `strict: true` on decode
|
|
78
|
+
- `rejectDuplicateMapKeys: true`
|
|
79
|
+
- Bluesky suite uses `ignoreUndefinedProperties: true`
|
|
80
|
+
- Filecoin/micro suites throw on undefined (strict IPLD)
|
|
81
|
+
|
|
82
|
+
### raw mode (`--mode=raw`)
|
|
83
|
+
|
|
84
|
+
Minimal cborg options for baseline comparison:
|
|
85
|
+
- No tag encoding/decoding
|
|
86
|
+
- Default encode/decode options
|
|
87
|
+
- Shows overhead of dag-cbor-specific features
|
|
88
|
+
|
|
89
|
+
## Files
|
|
90
|
+
|
|
91
|
+
- `bench.js` - Main benchmark runner (Node.js)
|
|
92
|
+
- `index.html` - Browser benchmark UI
|
|
93
|
+
- `fixtures.js` - Deterministic fixture generators (seeded PRNG)
|
|
94
|
+
- `bench-comparative.js` - Historical cborg vs borc comparison
|
|
95
|
+
- `json.js` - JSON encode/decode benchmarks
|
|
96
|
+
|
|
97
|
+
## Fixture Suites
|
|
98
|
+
|
|
99
|
+
### Bluesky (string-heavy)
|
|
100
|
+
- Posts, follows, likes, reposts, profiles
|
|
101
|
+
- MST (Merkle Search Tree) nodes
|
|
102
|
+
- Heavy on strings, DIDs, AT-URIs, timestamps
|
|
103
|
+
|
|
104
|
+
### Filecoin (bytes-heavy)
|
|
105
|
+
- Messages, block headers
|
|
106
|
+
- HAMT and AMT nodes
|
|
107
|
+
- CID arrays
|
|
108
|
+
- Heavy on byte arrays and CIDs
|
|
109
|
+
|
|
110
|
+
### Micro-benchmarks
|
|
111
|
+
- Maps (small/medium/large key counts)
|
|
112
|
+
- Nesting depth (shallow/deep)
|
|
113
|
+
- Strings (short/medium/long)
|
|
114
|
+
- Integers (small/medium/large)
|
|
115
|
+
- Bytes (small/medium/large)
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// can be run in a browser with `polendina --runner=bare-sync --timeout 6000 --cleanup bench.js`
|
|
2
|
+
// with additional dependencies for cborg installed here
|
|
3
|
+
|
|
4
|
+
import assert from 'assert'
|
|
5
|
+
import { garbage } from 'ipld-garbage'
|
|
6
|
+
import { decode, encode, encodeInto } from '../cborg.js'
|
|
7
|
+
import borc from 'borc'
|
|
8
|
+
|
|
9
|
+
const WITH_CBORG_FIXED_DESTINATION = true
|
|
10
|
+
|
|
11
|
+
let writebuf = ''
|
|
12
|
+
const write = process.stdout
|
|
13
|
+
? process.stdout.write.bind(process.stdout)
|
|
14
|
+
: (str) => {
|
|
15
|
+
writebuf += str
|
|
16
|
+
if (str.endsWith('\n')) {
|
|
17
|
+
console.log(writebuf.replace(/\n$/, ''))
|
|
18
|
+
writebuf = ''
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function runWith (description, count, targetTime, size, options) {
|
|
23
|
+
let borcDecoder = null
|
|
24
|
+
const borcDecode = (bytes) => {
|
|
25
|
+
if (!borcDecoder) {
|
|
26
|
+
// account for initial allocation & setup time in benchmark
|
|
27
|
+
borcDecoder = new borc.Decoder({ size: 10 * 1024 * 1024 })
|
|
28
|
+
}
|
|
29
|
+
return borcDecoder.decodeAll(bytes)[0]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let cborgEncoder = WITH_CBORG_FIXED_DESTINATION ? null : encode
|
|
33
|
+
|
|
34
|
+
const cborgEncode = (bytes) => {
|
|
35
|
+
if (!cborgEncoder) {
|
|
36
|
+
// account for initial allocation & setup time in benchmark
|
|
37
|
+
const fixedDestination = new Uint8Array(10 * 1024 * 1024)
|
|
38
|
+
cborgEncoder = (bytes) => {
|
|
39
|
+
const { written } = encodeInto(bytes, fixedDestination)
|
|
40
|
+
return fixedDestination.subarray(0, written)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return cborgEncoder(bytes)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const fixtures = []
|
|
47
|
+
|
|
48
|
+
console.log(`${description} @ ${count.toLocaleString()}`)
|
|
49
|
+
for (let i = 0; i < count; i++) {
|
|
50
|
+
const obj = garbage(size, options)
|
|
51
|
+
const cbyts = encode(obj)
|
|
52
|
+
/*
|
|
53
|
+
const bbyts = borc.encode(obj)
|
|
54
|
+
if (Buffer.compare(Buffer.from(cbyts), bbyts) !== 0) {
|
|
55
|
+
console.log(`mismatch for obj: ${JSON.stringify(obj)}`)
|
|
56
|
+
console.log('\t', Buffer.from(cbyts).toString('hex'))
|
|
57
|
+
console.log('\t', Buffer.from(bbyts).toString('hex'))
|
|
58
|
+
}
|
|
59
|
+
*/
|
|
60
|
+
if (cbyts.length <= size * 2) {
|
|
61
|
+
fixtures.push([obj, cbyts])
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const avgSize = Math.round(fixtures.reduce((p, c) => p + c[1].length, 0) / fixtures.length)
|
|
65
|
+
|
|
66
|
+
const enc = (encoder) => {
|
|
67
|
+
for (const [obj, byts] of fixtures) {
|
|
68
|
+
const ebyts = encoder(obj)
|
|
69
|
+
if (byts.length !== ebyts.length) {
|
|
70
|
+
throw new Error('bork')
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return fixtures.length
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const bench = (bfn) => {
|
|
77
|
+
const start = Date.now()
|
|
78
|
+
let opcount = 0
|
|
79
|
+
do {
|
|
80
|
+
opcount += bfn()
|
|
81
|
+
} while (Date.now() - start < targetTime)
|
|
82
|
+
const ops = Math.round(opcount / ((Date.now() - start) / 1000))
|
|
83
|
+
return ops
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const dec = (decoder) => {
|
|
87
|
+
for (const [obj, byts] of fixtures) {
|
|
88
|
+
const cobj = decoder(byts)
|
|
89
|
+
if (obj != null && typeof obj === 'object') {
|
|
90
|
+
assert.deepStrictEqual(Object.keys(cobj).length, Object.keys(obj).length)
|
|
91
|
+
} else {
|
|
92
|
+
assert.deepStrictEqual(obj, cobj)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return fixtures.length
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const cmp = (desc, cbfn, bofn) => {
|
|
99
|
+
write(`\t${desc} (avg ${avgSize.toLocaleString()} b):`)
|
|
100
|
+
const cborgOps = bench(cbfn)
|
|
101
|
+
write(` cborg @ ${cborgOps.toLocaleString()} op/s`)
|
|
102
|
+
const borcOps = bench(bofn)
|
|
103
|
+
write(` / borc @ ${borcOps.toLocaleString()} op/s`)
|
|
104
|
+
const percent = Math.round((cborgOps / borcOps) * 1000) / 10
|
|
105
|
+
write(` = ${(percent).toLocaleString()} %\n`)
|
|
106
|
+
return percent
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return [
|
|
110
|
+
cmp('encode', () => enc(cborgEncode), () => enc(borc.encode)),
|
|
111
|
+
cmp('decode', () => dec(decode), () => dec(borcDecode))
|
|
112
|
+
]
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const targetTime = 1000
|
|
116
|
+
const accum = []
|
|
117
|
+
accum.push(runWith('rnd-100', 1000, targetTime, 100, { weights: { CID: 0 } }))
|
|
118
|
+
accum.push(runWith('rnd-300', 1000, targetTime, 300, { weights: { CID: 0 } }))
|
|
119
|
+
accum.push(runWith('rnd-nomap-300', 1000, targetTime, 300, { weights: { CID: 0, map: 0 } }))
|
|
120
|
+
accum.push(runWith('rnd-nolist-300', 1000, targetTime, 300, { weights: { CID: 0, list: 0 } }))
|
|
121
|
+
accum.push(runWith('rnd-nofloat-300', 1000, targetTime, 300, { weights: { CID: 0, float: 0 } }))
|
|
122
|
+
accum.push(runWith('rnd-nomaj7-300', 1000, targetTime, 300, { weights: { CID: 0, float: 0, null: 0, boolean: 0 } }))
|
|
123
|
+
accum.push(runWith('rnd-nostr-300', 1000, targetTime, 300, { weights: { CID: 0, string: 0, bytes: 0 } }))
|
|
124
|
+
accum.push(runWith('rnd-nostrbyts-300', 1000, targetTime, 300, { weights: { CID: 0, string: 0 } }))
|
|
125
|
+
accum.push(runWith('rnd-1000', 1000, targetTime, 1000, { weights: { CID: 0 } }))
|
|
126
|
+
accum.push(runWith('rnd-2000', 1000, targetTime, 2000, { weights: { CID: 0 } }))
|
|
127
|
+
accum.push(runWith('rnd-fil-100', 1000, targetTime, 100, { weights: { float: 0, map: 0, CID: 0 } }))
|
|
128
|
+
accum.push(runWith('rnd-fil-300', 1000, targetTime, 300, { weights: { float: 0, map: 0, CID: 0 } }))
|
|
129
|
+
accum.push(runWith('rnd-fil-500', 1000, targetTime, 500, { weights: { float: 0, map: 0, CID: 0 } }))
|
|
130
|
+
accum.push(runWith('rnd-fil-1000', 1000, targetTime, 1000, { weights: { float: 0, map: 0, CID: 0 } }))
|
|
131
|
+
accum.push(runWith('rnd-fil-2000', 1000, targetTime, 2000, { weights: { float: 0, map: 0, CID: 0 } }))
|
|
132
|
+
console.log(`Avg encode: ${Math.round(accum.reduce((p, c) => p + c[0], 0) / accum.length).toLocaleString()} %`)
|
|
133
|
+
console.log(`Avg decode: ${Math.round(accum.reduce((p, c) => p + c[1], 0) / accum.length).toLocaleString()} %`)
|