cborg 4.4.0 → 4.5.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/.github/workflows/test-and-release.yml +1 -0
- package/CHANGELOG.md +13 -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/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/json/encode.d.ts +1 -1
- package/types/lib/json/encode.d.ts.map +1 -1
package/bench/index.html
ADDED
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>cborg benchmarks</title>
|
|
6
|
+
<style>
|
|
7
|
+
body {
|
|
8
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, monospace;
|
|
9
|
+
max-width: 900px;
|
|
10
|
+
margin: 2rem auto;
|
|
11
|
+
padding: 0 1rem;
|
|
12
|
+
background: #1a1a1a;
|
|
13
|
+
color: #e0e0e0;
|
|
14
|
+
}
|
|
15
|
+
h1 { color: #fff; }
|
|
16
|
+
pre {
|
|
17
|
+
background: #2d2d2d;
|
|
18
|
+
padding: 1rem;
|
|
19
|
+
border-radius: 4px;
|
|
20
|
+
overflow-x: auto;
|
|
21
|
+
font-size: 14px;
|
|
22
|
+
line-height: 1.5;
|
|
23
|
+
}
|
|
24
|
+
.running { color: #ffa500; }
|
|
25
|
+
.done { color: #00ff00; }
|
|
26
|
+
button {
|
|
27
|
+
background: #4a4a4a;
|
|
28
|
+
color: #fff;
|
|
29
|
+
border: none;
|
|
30
|
+
padding: 0.5rem 1rem;
|
|
31
|
+
border-radius: 4px;
|
|
32
|
+
cursor: pointer;
|
|
33
|
+
font-size: 14px;
|
|
34
|
+
margin-right: 0.5rem;
|
|
35
|
+
}
|
|
36
|
+
button:hover { background: #5a5a5a; }
|
|
37
|
+
button:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
38
|
+
.controls { margin-bottom: 1rem; }
|
|
39
|
+
label { margin-right: 1rem; }
|
|
40
|
+
select, input {
|
|
41
|
+
background: #2d2d2d;
|
|
42
|
+
color: #e0e0e0;
|
|
43
|
+
border: 1px solid #4a4a4a;
|
|
44
|
+
padding: 0.25rem 0.5rem;
|
|
45
|
+
border-radius: 4px;
|
|
46
|
+
}
|
|
47
|
+
</style>
|
|
48
|
+
</head>
|
|
49
|
+
<body>
|
|
50
|
+
<h1>cborg benchmarks</h1>
|
|
51
|
+
|
|
52
|
+
<div class="controls">
|
|
53
|
+
<label>
|
|
54
|
+
Mode:
|
|
55
|
+
<select id="mode">
|
|
56
|
+
<option value="dag-cbor" selected>dag-cbor (tag 42 + strict)</option>
|
|
57
|
+
<option value="raw">raw cborg (no tags)</option>
|
|
58
|
+
</select>
|
|
59
|
+
</label>
|
|
60
|
+
<label>
|
|
61
|
+
Suite:
|
|
62
|
+
<select id="suite">
|
|
63
|
+
<option value="">All</option>
|
|
64
|
+
<option value="bsky">Bluesky (string-heavy)</option>
|
|
65
|
+
<option value="filecoin">Filecoin (bytes-heavy)</option>
|
|
66
|
+
<option value="micro">Micro-benchmarks</option>
|
|
67
|
+
</select>
|
|
68
|
+
</label>
|
|
69
|
+
<label>
|
|
70
|
+
Duration:
|
|
71
|
+
<select id="duration">
|
|
72
|
+
<option value="500">500ms</option>
|
|
73
|
+
<option value="1000" selected>1s</option>
|
|
74
|
+
<option value="2000">2s</option>
|
|
75
|
+
<option value="5000">5s</option>
|
|
76
|
+
</select>
|
|
77
|
+
</label>
|
|
78
|
+
<label>
|
|
79
|
+
<input type="checkbox" id="encodeInto"> Use encodeInto
|
|
80
|
+
</label>
|
|
81
|
+
<button id="run">Run Benchmarks</button>
|
|
82
|
+
<button id="copy" disabled>Copy JSON</button>
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
<pre id="output"><span class="running">Click "Run Benchmarks" to start...</span></pre>
|
|
86
|
+
|
|
87
|
+
<script type="module">
|
|
88
|
+
import { encode, decode, encodeInto, Token, Type } from '../cborg.js'
|
|
89
|
+
import { generateFixtures, BenchCID } from './fixtures.js'
|
|
90
|
+
|
|
91
|
+
const output = document.getElementById('output')
|
|
92
|
+
const runBtn = document.getElementById('run')
|
|
93
|
+
const copyBtn = document.getElementById('copy')
|
|
94
|
+
const modeSelect = document.getElementById('mode')
|
|
95
|
+
const suiteSelect = document.getElementById('suite')
|
|
96
|
+
const durationSelect = document.getElementById('duration')
|
|
97
|
+
const encodeIntoCheck = document.getElementById('encodeInto')
|
|
98
|
+
|
|
99
|
+
let lastResults = null
|
|
100
|
+
|
|
101
|
+
// ==========================================================================
|
|
102
|
+
// CID Tag Encoder/Decoder (matches @ipld/dag-cbor implementation)
|
|
103
|
+
// ==========================================================================
|
|
104
|
+
|
|
105
|
+
const CID_CBOR_TAG = 42
|
|
106
|
+
|
|
107
|
+
function cidEncoder(obj) {
|
|
108
|
+
if (obj.asCID !== obj && obj['/'] !== obj.bytes) {
|
|
109
|
+
return null
|
|
110
|
+
}
|
|
111
|
+
if (!(obj instanceof BenchCID)) {
|
|
112
|
+
return null
|
|
113
|
+
}
|
|
114
|
+
const bytes = new Uint8Array(obj.bytes.byteLength + 1)
|
|
115
|
+
bytes.set(obj.bytes, 1)
|
|
116
|
+
return [
|
|
117
|
+
new Token(Type.tag, CID_CBOR_TAG),
|
|
118
|
+
new Token(Type.bytes, bytes)
|
|
119
|
+
]
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function cidDecoder(bytes) {
|
|
123
|
+
if (bytes[0] !== 0) {
|
|
124
|
+
throw new Error('Invalid CID for CBOR tag 42; expected leading 0x00')
|
|
125
|
+
}
|
|
126
|
+
return new BenchCID(bytes.subarray(1))
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function numberEncoder(num) {
|
|
130
|
+
if (Number.isNaN(num)) {
|
|
131
|
+
throw new Error('`NaN` is not supported by the IPLD Data Model')
|
|
132
|
+
}
|
|
133
|
+
if (num === Infinity || num === -Infinity) {
|
|
134
|
+
throw new Error('`Infinity` is not supported by the IPLD Data Model')
|
|
135
|
+
}
|
|
136
|
+
return null
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function undefinedEncoder() {
|
|
140
|
+
throw new Error('`undefined` is not supported by the IPLD Data Model')
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Strict dag-cbor encode options (Filecoin, micro-benchmarks)
|
|
144
|
+
const dagCborEncodeOptions = {
|
|
145
|
+
float64: true,
|
|
146
|
+
typeEncoders: {
|
|
147
|
+
Object: cidEncoder,
|
|
148
|
+
number: numberEncoder,
|
|
149
|
+
undefined: undefinedEncoder
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Bluesky encode options - uses ignoreUndefinedProperties
|
|
154
|
+
const bskyEncodeOptions = {
|
|
155
|
+
float64: true,
|
|
156
|
+
ignoreUndefinedProperties: true,
|
|
157
|
+
typeEncoders: {
|
|
158
|
+
Object: cidEncoder,
|
|
159
|
+
number: numberEncoder
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const dagCborDecodeOptions = {
|
|
164
|
+
allowIndefinite: false,
|
|
165
|
+
coerceUndefinedToNull: true,
|
|
166
|
+
allowNaN: false,
|
|
167
|
+
allowInfinity: false,
|
|
168
|
+
allowBigInt: true,
|
|
169
|
+
strict: true,
|
|
170
|
+
useMaps: false,
|
|
171
|
+
rejectDuplicateMapKeys: true,
|
|
172
|
+
tags: []
|
|
173
|
+
}
|
|
174
|
+
dagCborDecodeOptions.tags[CID_CBOR_TAG] = cidDecoder
|
|
175
|
+
|
|
176
|
+
// Raw mode CID encoder - just encodes as bytes without tag
|
|
177
|
+
function rawCidEncoder(obj) {
|
|
178
|
+
if (obj.asCID !== obj && obj['/'] !== obj.bytes) {
|
|
179
|
+
return null
|
|
180
|
+
}
|
|
181
|
+
if (!(obj instanceof BenchCID)) {
|
|
182
|
+
return null
|
|
183
|
+
}
|
|
184
|
+
return [new Token(Type.bytes, obj.bytes)]
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const rawEncodeOptions = {
|
|
188
|
+
typeEncoders: {
|
|
189
|
+
Object: rawCidEncoder
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const rawBskyEncodeOptions = {
|
|
194
|
+
ignoreUndefinedProperties: true,
|
|
195
|
+
typeEncoders: {
|
|
196
|
+
Object: rawCidEncoder
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function getOptions(suiteType, mode) {
|
|
201
|
+
if (mode === 'raw') {
|
|
202
|
+
return {
|
|
203
|
+
encode: suiteType === 'bsky' ? rawBskyEncodeOptions : rawEncodeOptions,
|
|
204
|
+
decode: {}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return {
|
|
208
|
+
encode: suiteType === 'bsky' ? bskyEncodeOptions : dagCborEncodeOptions,
|
|
209
|
+
decode: dagCborDecodeOptions
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ==========================================================================
|
|
214
|
+
|
|
215
|
+
function log(msg) {
|
|
216
|
+
output.innerHTML += msg + '\n'
|
|
217
|
+
output.scrollTop = output.scrollHeight
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function clear() {
|
|
221
|
+
output.innerHTML = ''
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const WARMUP_ITERATIONS = 50
|
|
225
|
+
|
|
226
|
+
function bench(fn, durationMs) {
|
|
227
|
+
for (let i = 0; i < WARMUP_ITERATIONS; i++) fn()
|
|
228
|
+
|
|
229
|
+
const start = performance.now()
|
|
230
|
+
let ops = 0
|
|
231
|
+
while (performance.now() - start < durationMs) {
|
|
232
|
+
fn()
|
|
233
|
+
ops++
|
|
234
|
+
}
|
|
235
|
+
const elapsed = performance.now() - start
|
|
236
|
+
return {
|
|
237
|
+
opsPerSec: Math.round(ops / (elapsed / 1000)),
|
|
238
|
+
totalOps: ops,
|
|
239
|
+
elapsedMs: Math.round(elapsed)
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function benchFixtures(name, fixtures, opts, suiteType = 'default') {
|
|
244
|
+
const { duration, useEncodeInto, mode } = opts
|
|
245
|
+
const { encode: encodeOptions, decode: decodeOptions } = getOptions(suiteType, mode)
|
|
246
|
+
|
|
247
|
+
const encoded = fixtures.map(f => encode(f, encodeOptions))
|
|
248
|
+
const totalBytes = encoded.reduce((sum, b) => sum + b.length, 0)
|
|
249
|
+
const avgBytes = Math.round(totalBytes / encoded.length)
|
|
250
|
+
|
|
251
|
+
let encodeFn = (f) => encode(f, encodeOptions)
|
|
252
|
+
if (useEncodeInto) {
|
|
253
|
+
const dest = new Uint8Array(1024 * 1024)
|
|
254
|
+
encodeFn = (f) => encodeInto(f, dest, encodeOptions)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
log(` ${name} (${fixtures.length} items, avg ${avgBytes} bytes)`)
|
|
258
|
+
|
|
259
|
+
const encResult = bench(() => {
|
|
260
|
+
for (const f of fixtures) encodeFn(f)
|
|
261
|
+
}, duration)
|
|
262
|
+
const encOpsPerItem = encResult.opsPerSec * fixtures.length
|
|
263
|
+
const encMBps = Math.round((encResult.opsPerSec * totalBytes) / (1024 * 1024) * 10) / 10
|
|
264
|
+
log(` encode: ${encOpsPerItem.toLocaleString()} items/s (${encMBps} MB/s)`)
|
|
265
|
+
|
|
266
|
+
const decResult = bench(() => {
|
|
267
|
+
for (const e of encoded) decode(e, decodeOptions)
|
|
268
|
+
}, duration)
|
|
269
|
+
const decOpsPerItem = decResult.opsPerSec * encoded.length
|
|
270
|
+
const decMBps = Math.round((decResult.opsPerSec * totalBytes) / (1024 * 1024) * 10) / 10
|
|
271
|
+
log(` decode: ${decOpsPerItem.toLocaleString()} items/s (${decMBps} MB/s)`)
|
|
272
|
+
|
|
273
|
+
return {
|
|
274
|
+
name,
|
|
275
|
+
count: fixtures.length,
|
|
276
|
+
avgBytes,
|
|
277
|
+
totalBytes,
|
|
278
|
+
encode: { opsPerSec: encResult.opsPerSec, itemsPerSec: encOpsPerItem, mbPerSec: encMBps },
|
|
279
|
+
decode: { opsPerSec: decResult.opsPerSec, itemsPerSec: decOpsPerItem, mbPerSec: decMBps }
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function runSuite(name, fixtureGroups, opts, suiteType = 'default') {
|
|
284
|
+
log(`\n${name}`)
|
|
285
|
+
log('='.repeat(name.length))
|
|
286
|
+
|
|
287
|
+
const results = []
|
|
288
|
+
for (const [groupName, fixtures] of Object.entries(fixtureGroups)) {
|
|
289
|
+
if (Array.isArray(fixtures) && fixtures.length > 0) {
|
|
290
|
+
results.push(benchFixtures(groupName, fixtures, opts, suiteType))
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return { suite: name, results }
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
async function runBenchmarks() {
|
|
297
|
+
clear()
|
|
298
|
+
runBtn.disabled = true
|
|
299
|
+
copyBtn.disabled = true
|
|
300
|
+
|
|
301
|
+
const suite = suiteSelect.value || null
|
|
302
|
+
const duration = parseInt(durationSelect.value)
|
|
303
|
+
const useEncodeInto = encodeIntoCheck.checked
|
|
304
|
+
const mode = modeSelect.value
|
|
305
|
+
const opts = { duration, useEncodeInto, mode }
|
|
306
|
+
|
|
307
|
+
log('<span class="running">Generating fixtures...</span>')
|
|
308
|
+
await new Promise(r => setTimeout(r, 10)) // Allow UI update
|
|
309
|
+
|
|
310
|
+
const fixtures = generateFixtures(12345)
|
|
311
|
+
const modeDesc = mode === 'raw' ? 'raw cborg (no tags)' : 'dag-cbor mode (tag 42 + strict)'
|
|
312
|
+
log(`Done. Running benchmarks in ${modeDesc}...\n`)
|
|
313
|
+
|
|
314
|
+
const allResults = []
|
|
315
|
+
|
|
316
|
+
if (!suite || suite === 'bsky') {
|
|
317
|
+
allResults.push(runSuite('Bluesky (string-heavy)', {
|
|
318
|
+
posts: fixtures.bsky.posts,
|
|
319
|
+
follows: fixtures.bsky.follows,
|
|
320
|
+
likes: fixtures.bsky.likes,
|
|
321
|
+
reposts: fixtures.bsky.reposts,
|
|
322
|
+
profiles: fixtures.bsky.profiles,
|
|
323
|
+
mstNodes: fixtures.bsky.mstNodes
|
|
324
|
+
}, opts, 'bsky'))
|
|
325
|
+
await new Promise(r => setTimeout(r, 10))
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (!suite || suite === 'filecoin') {
|
|
329
|
+
allResults.push(runSuite('Filecoin (bytes-heavy)', {
|
|
330
|
+
messages: fixtures.filecoin.messages,
|
|
331
|
+
blockHeaders: fixtures.filecoin.blockHeaders,
|
|
332
|
+
hamtNodes: fixtures.filecoin.hamtNodes,
|
|
333
|
+
amtNodes: fixtures.filecoin.amtNodes,
|
|
334
|
+
cidArrays: fixtures.filecoin.cidArrays
|
|
335
|
+
}, opts))
|
|
336
|
+
await new Promise(r => setTimeout(r, 10))
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (!suite || suite === 'micro') {
|
|
340
|
+
allResults.push(runSuite('Maps (key sorting)', {
|
|
341
|
+
'small (10 keys)': fixtures.micro.mapsSmall,
|
|
342
|
+
'medium (50 keys)': fixtures.micro.mapsMedium,
|
|
343
|
+
'large (200 keys)': fixtures.micro.mapsLarge
|
|
344
|
+
}, opts))
|
|
345
|
+
|
|
346
|
+
allResults.push(runSuite('Nesting depth', {
|
|
347
|
+
'shallow (depth 3)': fixtures.micro.nestedShallow,
|
|
348
|
+
'deep (depth 10)': fixtures.micro.nestedDeep
|
|
349
|
+
}, opts))
|
|
350
|
+
|
|
351
|
+
allResults.push(runSuite('Strings', {
|
|
352
|
+
'short (5-20 chars)': fixtures.micro.stringsShort,
|
|
353
|
+
'medium (20-100 chars)': fixtures.micro.stringsMedium,
|
|
354
|
+
'long (100-500 chars)': fixtures.micro.stringsLong
|
|
355
|
+
}, opts))
|
|
356
|
+
|
|
357
|
+
allResults.push(runSuite('Integers', {
|
|
358
|
+
'small (0-23)': fixtures.micro.integersSmall,
|
|
359
|
+
'medium (0-65535)': fixtures.micro.integersMedium,
|
|
360
|
+
'large (64-bit)': fixtures.micro.integersLarge
|
|
361
|
+
}, opts))
|
|
362
|
+
|
|
363
|
+
allResults.push(runSuite('Bytes', {
|
|
364
|
+
'small (<64)': fixtures.micro.bytesSmall,
|
|
365
|
+
'medium (64-512)': fixtures.micro.bytesMedium,
|
|
366
|
+
'large (1KB+)': fixtures.micro.bytesLarge
|
|
367
|
+
}, opts))
|
|
368
|
+
await new Promise(r => setTimeout(r, 10))
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Summary
|
|
372
|
+
log('\n' + '='.repeat(50))
|
|
373
|
+
const allEncodeRates = allResults.flatMap(s => s.results.map(r => r.encode.mbPerSec))
|
|
374
|
+
const allDecodeRates = allResults.flatMap(s => s.results.map(r => r.decode.mbPerSec))
|
|
375
|
+
const avgEncode = Math.round(allEncodeRates.reduce((a, b) => a + b, 0) / allEncodeRates.length * 10) / 10
|
|
376
|
+
const avgDecode = Math.round(allDecodeRates.reduce((a, b) => a + b, 0) / allDecodeRates.length * 10) / 10
|
|
377
|
+
log(`<span class="done">Average throughput: encode ${avgEncode} MB/s, decode ${avgDecode} MB/s</span>`)
|
|
378
|
+
|
|
379
|
+
lastResults = {
|
|
380
|
+
timestamp: new Date().toISOString(),
|
|
381
|
+
userAgent: navigator.userAgent,
|
|
382
|
+
seed: 12345,
|
|
383
|
+
duration,
|
|
384
|
+
mode,
|
|
385
|
+
encodeInto: useEncodeInto,
|
|
386
|
+
suites: allResults,
|
|
387
|
+
summary: { avgEncodeMBps: avgEncode, avgDecodeMBps: avgDecode }
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
runBtn.disabled = false
|
|
391
|
+
copyBtn.disabled = false
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
runBtn.addEventListener('click', runBenchmarks)
|
|
395
|
+
|
|
396
|
+
copyBtn.addEventListener('click', () => {
|
|
397
|
+
if (lastResults) {
|
|
398
|
+
navigator.clipboard.writeText(JSON.stringify(lastResults, null, 2))
|
|
399
|
+
copyBtn.textContent = 'Copied!'
|
|
400
|
+
setTimeout(() => { copyBtn.textContent = 'Copy JSON' }, 1500)
|
|
401
|
+
}
|
|
402
|
+
})
|
|
403
|
+
</script>
|
|
404
|
+
</body>
|
|
405
|
+
</html>
|
package/cborg.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { encode, rfc8949EncodeOptions } from './lib/encode.js'
|
|
1
|
+
import { encode, encodeInto, rfc8949EncodeOptions } from './lib/encode.js'
|
|
2
2
|
import { decode, decodeFirst, Tokeniser, tokensToObject } from './lib/decode.js'
|
|
3
3
|
import { Token, Type } from './lib/token.js'
|
|
4
4
|
|
|
@@ -17,6 +17,7 @@ export {
|
|
|
17
17
|
Tokeniser as Tokenizer,
|
|
18
18
|
tokensToObject,
|
|
19
19
|
encode,
|
|
20
|
+
encodeInto,
|
|
20
21
|
rfc8949EncodeOptions,
|
|
21
22
|
Token,
|
|
22
23
|
Type
|
package/interface.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { Token } from './lib/token'
|
|
2
|
-
import { Bl } from './lib/bl'
|
|
3
2
|
|
|
4
3
|
export type TokenOrNestedTokens = Token | Token[] | TokenOrNestedTokens[]
|
|
5
4
|
|
|
@@ -14,7 +13,7 @@ export type OptionalTypeEncoder = (data: any, typ: string, options: EncodeOption
|
|
|
14
13
|
export type StrictTypeEncoder = (data: any, typ: string, options: EncodeOptions, refStack?: Reference) => TokenOrNestedTokens
|
|
15
14
|
|
|
16
15
|
export type TokenTypeEncoder = {
|
|
17
|
-
(
|
|
16
|
+
(writer: ByteWriter, token: Token, options?: EncodeOptions): void;
|
|
18
17
|
compareTokens(t1: Token, t2: Token): number;
|
|
19
18
|
// TODO: make this non-optional as a breaking change and remove the throw in length.js
|
|
20
19
|
encodedSize?(token: Token, options?: EncodeOptions): number;
|
|
@@ -59,3 +58,10 @@ export interface EncodeOptions {
|
|
|
59
58
|
*/
|
|
60
59
|
ignoreUndefinedProperties?: boolean,
|
|
61
60
|
}
|
|
61
|
+
|
|
62
|
+
export interface ByteWriter {
|
|
63
|
+
chunks: (Uint8Array | number[])[];
|
|
64
|
+
reset(): void;
|
|
65
|
+
push(bytes: Uint8Array | number[]): void;
|
|
66
|
+
toBytes(reset?: boolean | undefined): Uint8Array;
|
|
67
|
+
}
|
package/lib/0uint.js
CHANGED
|
@@ -6,7 +6,7 @@ import { decodeErrPrefix, assertEnoughData } from './common.js'
|
|
|
6
6
|
export const uintBoundaries = [24, 256, 65536, 4294967296, BigInt('18446744073709551616')]
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
* @typedef {import('
|
|
9
|
+
* @typedef {import('../interface').ByteWriter} ByteWriter
|
|
10
10
|
* @typedef {import('../interface').DecodeOptions} DecodeOptions
|
|
11
11
|
*/
|
|
12
12
|
|
|
@@ -131,35 +131,35 @@ export function decodeUint64 (data, pos, _minor, options) {
|
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
/**
|
|
134
|
-
* @param {
|
|
134
|
+
* @param {ByteWriter} writer
|
|
135
135
|
* @param {Token} token
|
|
136
136
|
*/
|
|
137
|
-
export function encodeUint (
|
|
138
|
-
return encodeUintValue(
|
|
137
|
+
export function encodeUint (writer, token) {
|
|
138
|
+
return encodeUintValue(writer, 0, token.value)
|
|
139
139
|
}
|
|
140
140
|
|
|
141
141
|
/**
|
|
142
|
-
* @param {
|
|
142
|
+
* @param {ByteWriter} writer
|
|
143
143
|
* @param {number} major
|
|
144
144
|
* @param {number|bigint} uint
|
|
145
145
|
*/
|
|
146
|
-
export function encodeUintValue (
|
|
146
|
+
export function encodeUintValue (writer, major, uint) {
|
|
147
147
|
if (uint < uintBoundaries[0]) {
|
|
148
148
|
const nuint = Number(uint)
|
|
149
149
|
// pack into one byte, minor=0, additional=value
|
|
150
|
-
|
|
150
|
+
writer.push([major | nuint])
|
|
151
151
|
} else if (uint < uintBoundaries[1]) {
|
|
152
152
|
const nuint = Number(uint)
|
|
153
153
|
// pack into two byte, minor=0, additional=24
|
|
154
|
-
|
|
154
|
+
writer.push([major | 24, nuint])
|
|
155
155
|
} else if (uint < uintBoundaries[2]) {
|
|
156
156
|
const nuint = Number(uint)
|
|
157
157
|
// pack into three byte, minor=0, additional=25
|
|
158
|
-
|
|
158
|
+
writer.push([major | 25, nuint >>> 8, nuint & 0xff])
|
|
159
159
|
} else if (uint < uintBoundaries[3]) {
|
|
160
160
|
const nuint = Number(uint)
|
|
161
161
|
// pack into five byte, minor=0, additional=26
|
|
162
|
-
|
|
162
|
+
writer.push([major | 26, (nuint >>> 24) & 0xff, (nuint >>> 16) & 0xff, (nuint >>> 8) & 0xff, nuint & 0xff])
|
|
163
163
|
} else {
|
|
164
164
|
const buint = BigInt(uint)
|
|
165
165
|
if (buint < uintBoundaries[4]) {
|
|
@@ -182,7 +182,7 @@ export function encodeUintValue (buf, major, uint) {
|
|
|
182
182
|
set[2] = hi & 0xff
|
|
183
183
|
hi = hi >> 8
|
|
184
184
|
set[1] = hi & 0xff
|
|
185
|
-
|
|
185
|
+
writer.push(set)
|
|
186
186
|
} else {
|
|
187
187
|
throw new Error(`${decodeErrPrefix} encountered BigInt larger than allowable range`)
|
|
188
188
|
}
|
package/lib/1negint.js
CHANGED
|
@@ -5,7 +5,7 @@ import * as uint from './0uint.js'
|
|
|
5
5
|
import { decodeErrPrefix } from './common.js'
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* @typedef {import('
|
|
8
|
+
* @typedef {import('../interface').ByteWriter} ByteWriter
|
|
9
9
|
* @typedef {import('../interface').DecodeOptions} DecodeOptions
|
|
10
10
|
*/
|
|
11
11
|
|
|
@@ -67,13 +67,13 @@ export function decodeNegint64 (data, pos, _minor, options) {
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
/**
|
|
70
|
-
* @param {
|
|
70
|
+
* @param {ByteWriter} writer
|
|
71
71
|
* @param {Token} token
|
|
72
72
|
*/
|
|
73
|
-
export function encodeNegint (
|
|
73
|
+
export function encodeNegint (writer, token) {
|
|
74
74
|
const negint = token.value
|
|
75
75
|
const unsigned = (typeof negint === 'bigint' ? (negint * neg1b - pos1b) : (negint * -1 - 1))
|
|
76
|
-
uint.encodeUintValue(
|
|
76
|
+
uint.encodeUintValue(writer, token.type.majorEncoded, unsigned)
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
/**
|
package/lib/2bytes.js
CHANGED
|
@@ -4,7 +4,7 @@ import * as uint from './0uint.js'
|
|
|
4
4
|
import { compare, fromString, slice } from './byte-utils.js'
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* @typedef {import('
|
|
7
|
+
* @typedef {import('../interface').ByteWriter} ByteWriter
|
|
8
8
|
* @typedef {import('../interface').DecodeOptions} DecodeOptions
|
|
9
9
|
*/
|
|
10
10
|
|
|
@@ -96,13 +96,13 @@ function tokenBytes (token) {
|
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
/**
|
|
99
|
-
* @param {
|
|
99
|
+
* @param {ByteWriter} writer
|
|
100
100
|
* @param {Token} token
|
|
101
101
|
*/
|
|
102
|
-
export function encodeBytes (
|
|
102
|
+
export function encodeBytes (writer, token) {
|
|
103
103
|
const bytes = tokenBytes(token)
|
|
104
|
-
uint.encodeUintValue(
|
|
105
|
-
|
|
104
|
+
uint.encodeUintValue(writer, token.type.majorEncoded, bytes.length)
|
|
105
|
+
writer.push(bytes)
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
/**
|
package/lib/3string.js
CHANGED
|
@@ -5,7 +5,7 @@ import { encodeBytes } from './2bytes.js'
|
|
|
5
5
|
import { toString, slice } from './byte-utils.js'
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* @typedef {import('
|
|
8
|
+
* @typedef {import('../interface').ByteWriter} ByteWriter
|
|
9
9
|
* @typedef {import('../interface').DecodeOptions} DecodeOptions
|
|
10
10
|
*/
|
|
11
11
|
|
package/lib/4array.js
CHANGED
|
@@ -3,7 +3,7 @@ import * as uint from './0uint.js'
|
|
|
3
3
|
import { decodeErrPrefix } from './common.js'
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* @typedef {import('
|
|
6
|
+
* @typedef {import('../interface').ByteWriter} ByteWriter
|
|
7
7
|
* @typedef {import('../interface').DecodeOptions} DecodeOptions
|
|
8
8
|
*/
|
|
9
9
|
|
|
@@ -93,11 +93,11 @@ export function decodeArrayIndefinite (data, pos, _minor, options) {
|
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
/**
|
|
96
|
-
* @param {
|
|
96
|
+
* @param {ByteWriter} writer
|
|
97
97
|
* @param {Token} token
|
|
98
98
|
*/
|
|
99
|
-
export function encodeArray (
|
|
100
|
-
uint.encodeUintValue(
|
|
99
|
+
export function encodeArray (writer, token) {
|
|
100
|
+
uint.encodeUintValue(writer, Type.array.majorEncoded, token.value)
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
// using an array as a map key, are you sure about this? we can only sort
|
package/lib/5map.js
CHANGED
|
@@ -3,7 +3,7 @@ import * as uint from './0uint.js'
|
|
|
3
3
|
import { decodeErrPrefix } from './common.js'
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* @typedef {import('
|
|
6
|
+
* @typedef {import('../interface').ByteWriter} ByteWriter
|
|
7
7
|
* @typedef {import('../interface').DecodeOptions} DecodeOptions
|
|
8
8
|
*/
|
|
9
9
|
|
|
@@ -93,11 +93,11 @@ export function decodeMapIndefinite (data, pos, _minor, options) {
|
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
/**
|
|
96
|
-
* @param {
|
|
96
|
+
* @param {ByteWriter} writer
|
|
97
97
|
* @param {Token} token
|
|
98
98
|
*/
|
|
99
|
-
export function encodeMap (
|
|
100
|
-
uint.encodeUintValue(
|
|
99
|
+
export function encodeMap (writer, token) {
|
|
100
|
+
uint.encodeUintValue(writer, Type.map.majorEncoded, token.value)
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
// using a map as a map key, are you sure about this? we can only sort
|
package/lib/6tag.js
CHANGED
|
@@ -2,7 +2,7 @@ import { Token, Type } from './token.js'
|
|
|
2
2
|
import * as uint from './0uint.js'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* @typedef {import('
|
|
5
|
+
* @typedef {import('../interface').ByteWriter} ByteWriter
|
|
6
6
|
* @typedef {import('../interface').DecodeOptions} DecodeOptions
|
|
7
7
|
*/
|
|
8
8
|
|
|
@@ -62,11 +62,11 @@ export function decodeTag64 (data, pos, _minor, options) {
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
/**
|
|
65
|
-
* @param {
|
|
65
|
+
* @param {ByteWriter} writer
|
|
66
66
|
* @param {Token} token
|
|
67
67
|
*/
|
|
68
|
-
export function encodeTag (
|
|
69
|
-
uint.encodeUintValue(
|
|
68
|
+
export function encodeTag (writer, token) {
|
|
69
|
+
uint.encodeUintValue(writer, Type.tag.majorEncoded, token.value)
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
encodeTag.compareTokens = uint.encodeUint.compareTokens
|