cborg 4.5.7 → 5.0.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/dependabot.yml +4 -0
- package/.github/workflows/test-and-release.yml +2 -4
- package/CHANGELOG.md +51 -0
- package/README.md +213 -9
- package/cborg.js +4 -4
- package/example-extended.js +122 -0
- package/interface.ts +15 -3
- package/lib/0uint.js +2 -2
- package/lib/1negint.js +2 -2
- package/lib/2bytes.js +2 -2
- package/lib/3string.js +2 -2
- package/lib/4array.js +2 -2
- package/lib/5map.js +2 -2
- package/lib/6tag.js +2 -2
- package/lib/7float.js +5 -4
- package/lib/decode.js +94 -4
- package/lib/diagnostic.js +10 -3
- package/lib/encode.js +7 -7
- package/lib/extended/extended.js +250 -0
- package/lib/json/decode.js +2 -2
- package/lib/json/encode.js +3 -3
- package/lib/jump.js +1 -1
- package/lib/length.js +3 -3
- package/lib/taglib.js +452 -0
- package/package.json +23 -18
- package/test/common.js +2 -1
- package/test/node-test-bin.js +26 -9
- package/test/test-6tag.js +2 -1
- package/test/test-cbor-vectors.js +14 -6
- package/test/test-extended-vectors.js +293 -0
- package/test/test-extended.js +684 -0
- package/test/test-taglib.js +634 -0
- package/tsconfig.json +7 -11
- package/types/cborg.d.ts +4 -4
- package/types/cborg.d.ts.map +1 -1
- package/types/interface.d.ts +14 -3
- package/types/interface.d.ts.map +1 -1
- package/types/lib/0uint.d.ts +4 -4
- 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 +2 -2
- package/types/lib/2bytes.d.ts.map +1 -1
- package/types/lib/3string.d.ts +2 -2
- package/types/lib/3string.d.ts.map +1 -1
- package/types/lib/4array.d.ts +2 -2
- package/types/lib/4array.d.ts.map +1 -1
- package/types/lib/5map.d.ts +2 -2
- 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 +6 -6
- package/types/lib/7float.d.ts.map +1 -1
- package/types/lib/byte-utils.d.ts +5 -2
- package/types/lib/byte-utils.d.ts.map +1 -1
- package/types/lib/decode.d.ts +4 -3
- package/types/lib/decode.d.ts.map +1 -1
- package/types/lib/diagnostic.d.ts.map +1 -1
- package/types/lib/encode.d.ts +8 -8
- package/types/lib/encode.d.ts.map +1 -1
- package/types/lib/extended/extended.d.ts +78 -0
- package/types/lib/extended/extended.d.ts.map +1 -0
- package/types/lib/json/decode.d.ts +5 -5
- package/types/lib/json/decode.d.ts.map +1 -1
- package/types/lib/json/encode.d.ts +3 -3
- package/types/lib/json/encode.d.ts.map +1 -1
- package/types/lib/jump.d.ts +1 -1
- package/types/lib/jump.d.ts.map +1 -1
- package/types/lib/length.d.ts +3 -3
- package/types/lib/length.d.ts.map +1 -1
- package/types/lib/taglib.d.ts +143 -0
- package/types/lib/taglib.d.ts.map +1 -0
- package/types/tsconfig.tsbuildinfo +1 -1
- package/taglib.js +0 -73
- package/types/taglib.d.ts +0 -18
- package/types/taglib.d.ts.map +0 -1
package/.github/dependabot.yml
CHANGED
|
@@ -7,6 +7,8 @@ updates:
|
|
|
7
7
|
commit-message:
|
|
8
8
|
prefix: 'chore'
|
|
9
9
|
include: 'scope'
|
|
10
|
+
cooldown:
|
|
11
|
+
default-days: 5
|
|
10
12
|
- package-ecosystem: 'npm'
|
|
11
13
|
directory: '/'
|
|
12
14
|
schedule:
|
|
@@ -14,3 +16,5 @@ updates:
|
|
|
14
16
|
commit-message:
|
|
15
17
|
prefix: 'chore'
|
|
16
18
|
include: 'scope'
|
|
19
|
+
cooldown:
|
|
20
|
+
default-days: 5
|
|
@@ -12,7 +12,7 @@ jobs:
|
|
|
12
12
|
- name: Checkout Repository
|
|
13
13
|
uses: actions/checkout@v6
|
|
14
14
|
- name: Use Node.js ${{ matrix.node }}
|
|
15
|
-
uses: actions/setup-node@v6.
|
|
15
|
+
uses: actions/setup-node@v6.3.0
|
|
16
16
|
with:
|
|
17
17
|
node-version: ${{ matrix.node }}
|
|
18
18
|
- name: Install Dependencies
|
|
@@ -22,8 +22,6 @@ jobs:
|
|
|
22
22
|
run: |
|
|
23
23
|
npm config set script-shell bash
|
|
24
24
|
npm run test:ci
|
|
25
|
-
- name: Typecheck
|
|
26
|
-
uses: gozala/typescript-error-reporter-action@v1.0.9
|
|
27
25
|
release:
|
|
28
26
|
name: Release
|
|
29
27
|
needs: test
|
|
@@ -40,7 +38,7 @@ jobs:
|
|
|
40
38
|
with:
|
|
41
39
|
fetch-depth: 0
|
|
42
40
|
- name: Setup Node.js
|
|
43
|
-
uses: actions/setup-node@v6.
|
|
41
|
+
uses: actions/setup-node@v6.3.0
|
|
44
42
|
with:
|
|
45
43
|
node-version: lts/*
|
|
46
44
|
- name: Install dependencies
|
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,54 @@
|
|
|
1
|
+
## [5.0.0](https://github.com/rvagg/cborg/compare/v4.5.8...v5.0.0) (2026-03-31)
|
|
2
|
+
|
|
3
|
+
### ⚠ BREAKING CHANGES
|
|
4
|
+
|
|
5
|
+
* **extended:** Tag decoder signature changed from receiving the decoded
|
|
6
|
+
value to receiving a decode control object. See migration guide below.
|
|
7
|
+
|
|
8
|
+
Add new `cborg/extended` entry point providing encode/decode with built-in
|
|
9
|
+
support for Date, RegExp, Set, Map, BigInt, Error, and all TypedArrays using
|
|
10
|
+
standard CBOR tags. Type support is similar to the browser's structured
|
|
11
|
+
clone algorithm.
|
|
12
|
+
|
|
13
|
+
Key features:
|
|
14
|
+
- Date (Tag 1), RegExp (Tag 21066), Set (Tag 258), Map (Tag 259)
|
|
15
|
+
- BigInt always tagged (Tags 2/3) for round-trip fidelity
|
|
16
|
+
- All TypedArrays via RFC 8746 tags (64-87)
|
|
17
|
+
- Error types via Tag 27 (Error, TypeError, RangeError, etc.)
|
|
18
|
+
- Negative zero (-0) round-trips correctly
|
|
19
|
+
- Objects round-trip as objects, Maps round-trip as Maps
|
|
20
|
+
- Map and object key insertion order preserved (not sorted)
|
|
21
|
+
- Mixed structures like { myMap: new Map([[1, 'a']]) } work correctly
|
|
22
|
+
|
|
23
|
+
The Map/object fidelity is achieved through a new tag decoder API that
|
|
24
|
+
gives decoders control over how their content is decoded. Tag 259 (Map)
|
|
25
|
+
uses `decode.entries()` to preserve key types regardless of the `useMaps`
|
|
26
|
+
setting, while plain CBOR maps decode as objects.
|
|
27
|
+
|
|
28
|
+
Extends `cborg/taglib` with encoders and decoders for all supported types
|
|
29
|
+
(previously only BigInt). Moves taglib.js to lib/taglib.js for consistency
|
|
30
|
+
(external API unchanged via package.json exports).
|
|
31
|
+
|
|
32
|
+
Also fixes a bug in lib/7float.js where -0 lost its sign bit during
|
|
33
|
+
half-precision float encoding (bitwise ops on floats convert to int32).
|
|
34
|
+
|
|
35
|
+
### Features
|
|
36
|
+
|
|
37
|
+
* **extended:** add cborg/extended module with full JavaScript type fidelity ([#168](https://github.com/rvagg/cborg/issues/168)) ([652730e](https://github.com/rvagg/cborg/commit/652730e5477e90cb95da2bd2b7bb56d171532d6e))
|
|
38
|
+
|
|
39
|
+
### Trivial Changes
|
|
40
|
+
|
|
41
|
+
* **deps-dev:** bump typescript from 5.9.3 to 6.0.2 ([5382bfa](https://github.com/rvagg/cborg/commit/5382bfa267b8764047a4263d556e28c0a491c3d6))
|
|
42
|
+
* **deps:** bump actions/setup-node from 6.2.0 to 6.3.0 ([#171](https://github.com/rvagg/cborg/issues/171)) ([a2525b9](https://github.com/rvagg/cborg/commit/a2525b94ff8ae217cb572572e22cd46233d5f8b9))
|
|
43
|
+
* update deps and upgrade to typescript 6 ([463bdfd](https://github.com/rvagg/cborg/commit/463bdfdbe89702214f392b5b99693a26a0ce96a3))
|
|
44
|
+
|
|
45
|
+
## [4.5.8](https://github.com/rvagg/cborg/compare/v4.5.7...v4.5.8) (2026-01-21)
|
|
46
|
+
|
|
47
|
+
### Bug Fixes
|
|
48
|
+
|
|
49
|
+
* **cli:** don't omit array length bytes in diagnostic output ([b8dacb6](https://github.com/rvagg/cborg/commit/b8dacb652c02566c7b64c2638b7bcb36bb519b6b))
|
|
50
|
+
* **test:** add CLI tests to `npm test` ([b19bc87](https://github.com/rvagg/cborg/commit/b19bc87ea04691f9173a5b13284e21192e0f1e3c))
|
|
51
|
+
|
|
1
52
|
## [4.5.7](https://github.com/rvagg/cborg/compare/v4.5.6...v4.5.7) (2026-01-21)
|
|
2
53
|
|
|
3
54
|
## [4.5.6](https://github.com/rvagg/cborg/compare/v4.5.5...v4.5.6) (2026-01-21)
|
package/README.md
CHANGED
|
@@ -39,6 +39,14 @@
|
|
|
39
39
|
* [JSON mode](#json-mode)
|
|
40
40
|
* [Example](#example-1)
|
|
41
41
|
* [Advanced types and tags](#advanced-types-and-tags)
|
|
42
|
+
* [Extended JavaScript types (`cborg/extended`)](#extended-javascript-types-cborgextended)
|
|
43
|
+
* [When to use `cborg/extended`](#when-to-use-cborgextended)
|
|
44
|
+
* [Supported types](#supported-types)
|
|
45
|
+
* [Type fidelity: objects vs Maps](#type-fidelity-objects-vs-maps)
|
|
46
|
+
* [Interoperability](#interoperability)
|
|
47
|
+
* [Selective type support (`cborg/taglib`)](#selective-type-support-cborgtaglib)
|
|
48
|
+
* [`cborg/extended` vs `cborg/taglib`](#cborgextended-vs-cborgtaglib)
|
|
49
|
+
* [Available exports](#available-exports)
|
|
42
50
|
* [License and Copyright](#license-and-copyright)
|
|
43
51
|
|
|
44
52
|
## Example
|
|
@@ -380,14 +388,19 @@ Using type encoders we can:
|
|
|
380
388
|
|
|
381
389
|
### Tag decoders
|
|
382
390
|
|
|
383
|
-
By default cborg does not support decoding of any tags. Where a tag is encountered during decode, an error will be thrown. If tag support is needed, they will need to be supplied as options to the `decode()` function. The `tags` property should contain an
|
|
391
|
+
By default cborg does not support decoding of any tags. Where a tag is encountered during decode, an error will be thrown. If tag support is needed, they will need to be supplied as options to the `decode()` function. The `tags` property should contain an object mapping tag numbers to decoder functions.
|
|
384
392
|
|
|
385
|
-
|
|
393
|
+
Tag decoder functions receive a `decode` control object with two methods:
|
|
394
|
+
- `decode()`: decode the tagged content and return it
|
|
395
|
+
- `decode.entries()`: for map content, returns `[[key, value], ...]` preserving key types
|
|
396
|
+
|
|
397
|
+
This example is available from the cborg taglib as `bigIntDecoder` and `bigNegIntDecoder` (`import { bigIntDecoder, bigNegIntDecoder } from 'cborg/taglib'`) and implements CBOR tags 2 and 3 (bigint and negative bigint). This function would be registered using an options parameter:
|
|
386
398
|
|
|
387
399
|
```js
|
|
388
|
-
const tags =
|
|
389
|
-
|
|
390
|
-
|
|
400
|
+
const tags = {
|
|
401
|
+
2: bigIntDecoder,
|
|
402
|
+
3: bigNegIntDecoder
|
|
403
|
+
}
|
|
391
404
|
|
|
392
405
|
decode(bytes, { tags })
|
|
393
406
|
```
|
|
@@ -395,7 +408,8 @@ decode(bytes, { tags })
|
|
|
395
408
|
Implementation:
|
|
396
409
|
|
|
397
410
|
```js
|
|
398
|
-
function bigIntDecoder (
|
|
411
|
+
function bigIntDecoder (decode) {
|
|
412
|
+
const bytes = decode() // get the tagged byte content
|
|
399
413
|
let bi = 0n
|
|
400
414
|
for (let ii = 0; ii < bytes.length; ii++) {
|
|
401
415
|
bi = (bi << 8n) + BigInt(bytes[ii])
|
|
@@ -403,8 +417,22 @@ function bigIntDecoder (bytes) {
|
|
|
403
417
|
return bi
|
|
404
418
|
}
|
|
405
419
|
|
|
406
|
-
function bigNegIntDecoder (
|
|
407
|
-
|
|
420
|
+
function bigNegIntDecoder (decode) {
|
|
421
|
+
const bytes = decode()
|
|
422
|
+
let bi = 0n
|
|
423
|
+
for (let ii = 0; ii < bytes.length; ii++) {
|
|
424
|
+
bi = (bi << 8n) + BigInt(bytes[ii])
|
|
425
|
+
}
|
|
426
|
+
return -1n - bi
|
|
427
|
+
}
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
For tags that wrap CBOR maps and need to preserve non-string key types, use `decode.entries()`:
|
|
431
|
+
|
|
432
|
+
```js
|
|
433
|
+
// Tag 259: Map with any key type
|
|
434
|
+
function mapDecoder (decode) {
|
|
435
|
+
return new Map(decode.entries())
|
|
408
436
|
}
|
|
409
437
|
```
|
|
410
438
|
|
|
@@ -466,7 +494,7 @@ By default, cborg will always **encode** objects to the same bytes by applying s
|
|
|
466
494
|
* Omitting support for tags (therefore omitting support for exotic object types).
|
|
467
495
|
* Applying deterministic rules to `number` differentiation - if a fractional part is missing and it's within the safe integer boundary, it's encoded as an integer, otherwise it's encoded as a float.
|
|
468
496
|
|
|
469
|
-
By default, cborg allows for some flexibility on **decode** of objects, which will present some challenges if users wish to impose strictness requirements at both
|
|
497
|
+
By default, cborg allows for some flexibility on **decode** of objects, which will present some challenges if users wish to impose strictness requirements at both serialisation _and_ deserialisation. Options that can be provided to `decode()` to impose some strictness requirements are:
|
|
470
498
|
|
|
471
499
|
* `strict: true` to impose strict sizing rules for int, negative ints and lengths of lengthed objects
|
|
472
500
|
* `allowNaN: false` and `allowInfinity` to prevent decoding of any value that would resolve to `NaN`, `Infinity` or `-Infinity`, using CBOR tokens or IEEE 754 representation—as long as your application can do without these symbols.
|
|
@@ -547,6 +575,182 @@ encoded (string): {"this":{"is":"JSON!","yay":true}}
|
|
|
547
575
|
|
|
548
576
|
As demonstrated above, the ability to provide custom `typeEncoders` to `encode()`, `tags` and even a custom `tokenizer` to `decode()` allow for quite a bit of flexibility in manipulating both the encode and decode process. An advanced example that uses all of these features can be found in [example-bytestrings.js](./example-bytestrings.js) which demonstrates how one might implement [RFC 8746](https://www.rfc-editor.org/rfc/rfc8746.html) to allow typed arrays to round-trip through CBOR and retain their original types. Since cborg is designed to speak purely in terms of `Uint8Array`s, its default behaviour will squash all typed arrays down to their byte array forms and materialise them as plain `Uint8Arrays`. Where round-trip fidelity is important and CBOR tags are an option, this form of usage is an option.
|
|
549
577
|
|
|
578
|
+
## Extended JavaScript types (`cborg/extended`)
|
|
579
|
+
|
|
580
|
+
Need to serialise `Date`, `Map`, `Set`, `RegExp`, `BigInt`, `Error`, or `TypedArray`? The `cborg/extended` module provides encode/decode with built-in support for native JavaScript types that JSON can't handle.
|
|
581
|
+
|
|
582
|
+
The type support is similar to the browser's **[structured clone algorithm](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm)**, the built-in deep-copy mechanism that handles these same types. `cborg/extended` provides similar type fidelity in a compact binary serialisation format.
|
|
583
|
+
|
|
584
|
+
See [example-extended.js](./example-extended.js) for a runnable demo.
|
|
585
|
+
|
|
586
|
+
```js
|
|
587
|
+
import { encode, decode } from 'cborg/extended'
|
|
588
|
+
|
|
589
|
+
const data = {
|
|
590
|
+
date: new Date(),
|
|
591
|
+
pattern: /foo.*bar/gi,
|
|
592
|
+
mapping: new Map([['key', 'value'], [42, 'number key']]),
|
|
593
|
+
collection: new Set([1, 2, 3]),
|
|
594
|
+
binary: new Uint16Array([1, 2, 3]),
|
|
595
|
+
bignum: 12345678901234567890n,
|
|
596
|
+
error: new TypeError('something went wrong')
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
const encoded = encode(data)
|
|
600
|
+
const decoded = decode(encoded)
|
|
601
|
+
|
|
602
|
+
// All types are preserved
|
|
603
|
+
decoded.date instanceof Date // true
|
|
604
|
+
decoded.pattern instanceof RegExp // true
|
|
605
|
+
decoded.mapping instanceof Map // true
|
|
606
|
+
decoded.collection instanceof Set // true
|
|
607
|
+
decoded.binary instanceof Uint16Array // true
|
|
608
|
+
typeof decoded.bignum === 'bigint' // true
|
|
609
|
+
decoded.error instanceof TypeError // true
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
### When to use `cborg/extended`
|
|
613
|
+
|
|
614
|
+
Use `cborg/extended` instead of base `cborg` or JSON when you need:
|
|
615
|
+
|
|
616
|
+
- **Date serialisation**: JSON requires manual `toISOString()`/`new Date()` conversion
|
|
617
|
+
- **BigInt support**: JSON throws on BigInt; base cborg only preserves BigInts outside 64-bit range
|
|
618
|
+
- **Map with non-string keys**: `new Map([[1, 'one'], [{}, 'object key']])` just works
|
|
619
|
+
- **Set preservation**: Sets round-trip as Sets, not Arrays
|
|
620
|
+
- **TypedArray types**: `Float32Array` stays `Float32Array`, not `Uint8Array`
|
|
621
|
+
- **Error serialisation**: `Error`, `TypeError`, `RangeError`, etc. preserve type and message
|
|
622
|
+
- **Negative zero**: `-0` round-trips correctly (base cborg encodes as `0`)
|
|
623
|
+
- **Insertion order**: Map and object key order is preserved (not sorted)
|
|
624
|
+
- **Binary efficiency**: ~30-50% smaller than JSON for typical data
|
|
625
|
+
|
|
626
|
+
### Supported types
|
|
627
|
+
|
|
628
|
+
| Type | CBOR Tag | Notes |
|
|
629
|
+
|------|----------|-------|
|
|
630
|
+
| `Date` | 1 | Epoch seconds as float (millisecond precision) |
|
|
631
|
+
| `RegExp` | 21066 | Pattern and flags preserved |
|
|
632
|
+
| `Set` | 258 | IANA registered finite set |
|
|
633
|
+
| `Map` | 259 | Supports any key type |
|
|
634
|
+
| `BigInt` | 2, 3 | Always tagged for round-trip fidelity |
|
|
635
|
+
| `Error` | 27 | All standard error types (`TypeError`, `RangeError`, etc.) |
|
|
636
|
+
| `-0` | *(float)* | Negative zero encoded as half-precision float |
|
|
637
|
+
| `Uint8Array` | 64 | RFC 8746 |
|
|
638
|
+
| `Uint8ClampedArray` | 68 | RFC 8746 |
|
|
639
|
+
| `Int8Array` | 72 | RFC 8746 |
|
|
640
|
+
| `Uint16Array` | 69 | RFC 8746 (little-endian) |
|
|
641
|
+
| `Int16Array` | 77 | RFC 8746 (little-endian) |
|
|
642
|
+
| `Uint32Array` | 70 | RFC 8746 (little-endian) |
|
|
643
|
+
| `Int32Array` | 78 | RFC 8746 (little-endian) |
|
|
644
|
+
| `Float32Array` | 85 | RFC 8746 (little-endian) |
|
|
645
|
+
| `Float64Array` | 86 | RFC 8746 (little-endian) |
|
|
646
|
+
| `BigUint64Array` | 71 | RFC 8746 (little-endian) |
|
|
647
|
+
| `BigInt64Array` | 79 | RFC 8746 (little-endian) |
|
|
648
|
+
|
|
649
|
+
### Type fidelity: objects vs Maps
|
|
650
|
+
|
|
651
|
+
`cborg/extended` preserves the distinction between plain objects and `Map` instances:
|
|
652
|
+
|
|
653
|
+
```js
|
|
654
|
+
// Plain objects round-trip as plain objects
|
|
655
|
+
const obj = { name: 'Alice', age: 30 }
|
|
656
|
+
decode(encode(obj)) // { name: 'Alice', age: 30 }
|
|
657
|
+
|
|
658
|
+
// Maps round-trip as Maps (including non-string keys)
|
|
659
|
+
const map = new Map([[1, 'one'], ['two', 2]])
|
|
660
|
+
decode(encode(map)) // Map { 1 => 'one', 'two' => 2 }
|
|
661
|
+
|
|
662
|
+
// Mixed structures work correctly
|
|
663
|
+
const data = {
|
|
664
|
+
users: new Map([['alice', { role: 'admin' }]])
|
|
665
|
+
}
|
|
666
|
+
const decoded = decode(encode(data))
|
|
667
|
+
decoded.users instanceof Map // true
|
|
668
|
+
decoded.users.get('alice') // { role: 'admin' } (plain object)
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
This works because `Map` instances are encoded with CBOR Tag 259, while plain objects use untagged CBOR maps. The decoder uses `decode.entries()` internally to preserve key types for tagged maps.
|
|
672
|
+
|
|
673
|
+
### Interoperability and limitations
|
|
674
|
+
|
|
675
|
+
The tags used by `cborg/extended` are standard CBOR tags registered with IANA:
|
|
676
|
+
|
|
677
|
+
- Tags 1, 2, 3 (Date, BigInt): RFC 8949
|
|
678
|
+
- Tag 27 (Error): IANA "object with class name and constructor arguments"
|
|
679
|
+
- Tags 64-87 (TypedArrays): RFC 8746
|
|
680
|
+
- Tags 258, 259 (Set, Map): IANA registry
|
|
681
|
+
- Tag 21066 (RegExp): IANA registry
|
|
682
|
+
|
|
683
|
+
**Important considerations:**
|
|
684
|
+
|
|
685
|
+
- **Parser support varies**: CBOR parsers that don't recognise these tags will either error or return raw tagged values. Many minimal CBOR implementations only handle core types. Test interoperability with your specific target platforms.
|
|
686
|
+
|
|
687
|
+
- **Not for content addressing**: `cborg/extended` prioritises JavaScript type fidelity over deterministic encoding. Map and object keys preserve insertion order (not sorted), floating-point dates lose sub-millisecond precision, and Set iteration order depends on insertion. The same data structure built differently may encode to different bytes. For content-addressed systems (IPLD, CIDs), use base `cborg` with `@ipld/dag-cbor` conventions instead.
|
|
688
|
+
|
|
689
|
+
- **Implementation differences**: Even among parsers that support these tags, behaviour may differ. For example, Date precision (seconds vs milliseconds), RegExp flag handling, or TypedArray endianness assumptions. The CBOR specs allow flexibility that can cause subtle incompatibilities.
|
|
690
|
+
|
|
691
|
+
- **JavaScript-centric**: Types like `RegExp` and JavaScript's specific TypedArray variants don't have equivalents in many languages. Data encoded with `cborg/extended` is best suited for JavaScript-to-JavaScript communication.
|
|
692
|
+
|
|
693
|
+
## Selective type support (`cborg/taglib`)
|
|
694
|
+
|
|
695
|
+
If you don't need all extended types, `cborg/taglib` exports individual encoders and decoders. Use this when you want to enable specific types without the full `cborg/extended` configuration, or when you need to customise behaviour.
|
|
696
|
+
|
|
697
|
+
> **Tip:** See [lib/extended/extended.js](./lib/extended/extended.js) for how `cborg/extended` assembles the taglib components, use it as a template for your own configuration.
|
|
698
|
+
|
|
699
|
+
```js
|
|
700
|
+
import { encode, decode } from 'cborg'
|
|
701
|
+
import {
|
|
702
|
+
dateEncoder,
|
|
703
|
+
dateDecoder,
|
|
704
|
+
bigIntEncoder,
|
|
705
|
+
bigIntDecoder,
|
|
706
|
+
bigNegIntDecoder,
|
|
707
|
+
TAG_DATE_EPOCH
|
|
708
|
+
} from 'cborg/taglib'
|
|
709
|
+
|
|
710
|
+
// Enable just Date and BigInt support
|
|
711
|
+
const encoded = encode(data, {
|
|
712
|
+
typeEncoders: {
|
|
713
|
+
Date: dateEncoder,
|
|
714
|
+
bigint: bigIntEncoder
|
|
715
|
+
}
|
|
716
|
+
})
|
|
717
|
+
|
|
718
|
+
const decoded = decode(encoded, {
|
|
719
|
+
tags: {
|
|
720
|
+
[TAG_DATE_EPOCH]: dateDecoder,
|
|
721
|
+
2: bigIntDecoder,
|
|
722
|
+
3: bigNegIntDecoder
|
|
723
|
+
}
|
|
724
|
+
})
|
|
725
|
+
```
|
|
726
|
+
|
|
727
|
+
### `cborg/extended` vs `cborg/taglib`
|
|
728
|
+
|
|
729
|
+
| Use case | Module |
|
|
730
|
+
|----------|--------|
|
|
731
|
+
| Serialize all JS types, minimal config | `cborg/extended` |
|
|
732
|
+
| Only need Date and BigInt | `cborg/taglib` with selective imports |
|
|
733
|
+
| Custom encode/decode logic | `cborg/taglib` as building blocks |
|
|
734
|
+
| Interop with IPLD/content-addressed systems | `cborg/taglib` with `bigIntEncoder` (not `structBigIntEncoder`) |
|
|
735
|
+
|
|
736
|
+
### Available exports
|
|
737
|
+
|
|
738
|
+
**Encoders:**
|
|
739
|
+
- `dateEncoder`: Date as epoch float (Tag 1)
|
|
740
|
+
- `regExpEncoder`: RegExp as [pattern, flags] (Tag 21066)
|
|
741
|
+
- `setEncoder`: Set as tagged array (Tag 258)
|
|
742
|
+
- `mapEncoder`: Map as tagged CBOR map (Tag 259)
|
|
743
|
+
- `bigIntEncoder`: BigInt, only tags values outside 64-bit range (IPLD compatible)
|
|
744
|
+
- `structBigIntEncoder`: BigInt, always tags (full round-trip as bigint)
|
|
745
|
+
- TypedArray encoders: `uint8ArrayEncoder`, `uint8ClampedArrayEncoder`, `int8ArrayEncoder`, `uint16ArrayEncoder`, `int16ArrayEncoder`, `uint32ArrayEncoder`, `int32ArrayEncoder`, `float32ArrayEncoder`, `float64ArrayEncoder`, `bigUint64ArrayEncoder`, `bigInt64ArrayEncoder`
|
|
746
|
+
|
|
747
|
+
**Decoders:**
|
|
748
|
+
- `dateDecoder`, `regExpDecoder`, `setDecoder`, `mapDecoder`
|
|
749
|
+
- `bigIntDecoder` (Tag 2), `bigNegIntDecoder` (Tag 3)
|
|
750
|
+
- TypedArray decoders: `uint8ArrayDecoder`, `uint8ClampedArrayDecoder`, `int8ArrayDecoder`, `uint16ArrayDecoder`, `int16ArrayDecoder`, `uint32ArrayDecoder`, `int32ArrayDecoder`, `float32ArrayDecoder`, `float64ArrayDecoder`, `bigUint64ArrayDecoder`, `bigInt64ArrayDecoder`
|
|
751
|
+
|
|
752
|
+
**Tag constants:** `TAG_DATE_STRING`, `TAG_DATE_EPOCH`, `TAG_BIGINT_POS`, `TAG_BIGINT_NEG`, `TAG_UINT8_ARRAY`, `TAG_UINT8_CLAMPED_ARRAY`, `TAG_INT8_ARRAY`, `TAG_UINT16_ARRAY_LE`, `TAG_INT16_ARRAY_LE`, `TAG_UINT32_ARRAY_LE`, `TAG_INT32_ARRAY_LE`, `TAG_FLOAT32_ARRAY_LE`, `TAG_FLOAT64_ARRAY_LE`, `TAG_BIGUINT64_ARRAY_LE`, `TAG_BIGINT64_ARRAY_LE`, `TAG_SET`, `TAG_MAP`, `TAG_REGEXP`
|
|
753
|
+
|
|
550
754
|
## License and Copyright
|
|
551
755
|
|
|
552
756
|
Copyright 2020 Rod Vagg
|
package/cborg.js
CHANGED
|
@@ -4,11 +4,11 @@ import { Token, Type } from './lib/token.js'
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Export the types that were present in the original manual cborg.d.ts
|
|
7
|
-
* @typedef {import('./interface').TagDecoder} TagDecoder
|
|
7
|
+
* @typedef {import('./interface.js').TagDecoder} TagDecoder
|
|
8
8
|
* There was originally just `TypeEncoder` so don't break types by renaming or not exporting
|
|
9
|
-
* @typedef {import('./interface').OptionalTypeEncoder} TypeEncoder
|
|
10
|
-
* @typedef {import('./interface').DecodeOptions} DecodeOptions
|
|
11
|
-
* @typedef {import('./interface').EncodeOptions} EncodeOptions
|
|
9
|
+
* @typedef {import('./interface.js').OptionalTypeEncoder} TypeEncoder
|
|
10
|
+
* @typedef {import('./interface.js').DecodeOptions} DecodeOptions
|
|
11
|
+
* @typedef {import('./interface.js').EncodeOptions} EncodeOptions
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
export {
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/*
|
|
2
|
+
cborg/extended provides built-in support for extended JavaScript types.
|
|
3
|
+
|
|
4
|
+
Compare this to example-bytestrings.js which manually implements TypedArray support
|
|
5
|
+
in ~150 lines. With cborg/extended, it's just import and use.
|
|
6
|
+
|
|
7
|
+
The type support is similar to the browser's structured clone algorithm - Date, Map,
|
|
8
|
+
Set, RegExp, BigInt, Error, and all TypedArrays round-trip with full type fidelity.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { encode, decode } from 'cborg/extended'
|
|
12
|
+
|
|
13
|
+
// All these types "just work" - no configuration needed
|
|
14
|
+
const original = {
|
|
15
|
+
// Date with millisecond precision
|
|
16
|
+
timestamp: new Date('2024-01-15T12:30:45.123Z'),
|
|
17
|
+
|
|
18
|
+
// RegExp with flags
|
|
19
|
+
pattern: /hello\s+world/gi,
|
|
20
|
+
|
|
21
|
+
// Map with non-string keys (impossible in JSON)
|
|
22
|
+
lookup: new Map([
|
|
23
|
+
['string-key', 'string value'],
|
|
24
|
+
[42, 'number key'],
|
|
25
|
+
[true, 'boolean key']
|
|
26
|
+
]),
|
|
27
|
+
|
|
28
|
+
// Set (not just array)
|
|
29
|
+
uniqueIds: new Set([1, 2, 3, 2, 1]), // duplicates removed
|
|
30
|
+
|
|
31
|
+
// BigInt (JSON throws on these)
|
|
32
|
+
bigNumber: 9007199254740993n, // beyond Number.MAX_SAFE_INTEGER
|
|
33
|
+
|
|
34
|
+
// Error types preserve their class and message
|
|
35
|
+
error: new TypeError('something went wrong'),
|
|
36
|
+
|
|
37
|
+
// TypedArrays preserve their exact type
|
|
38
|
+
floats: new Float32Array([1.5, 2.5, 3.5]),
|
|
39
|
+
integers: new Int16Array([-32768, 0, 32767]),
|
|
40
|
+
bigInts: new BigUint64Array([0n, 18446744073709551615n]),
|
|
41
|
+
|
|
42
|
+
// Negative zero is preserved (JSON and base cborg lose the sign)
|
|
43
|
+
negativeZero: -0
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
console.log('Original:')
|
|
47
|
+
console.log(original)
|
|
48
|
+
console.log()
|
|
49
|
+
|
|
50
|
+
// Encode to CBOR bytes
|
|
51
|
+
const encoded = encode(original)
|
|
52
|
+
console.log('Encoded size:', encoded.length, 'bytes')
|
|
53
|
+
console.log()
|
|
54
|
+
|
|
55
|
+
// Decode back - all types preserved
|
|
56
|
+
// By default, plain objects stay as plain objects (useMaps: false)
|
|
57
|
+
const decoded = decode(encoded)
|
|
58
|
+
console.log('Decoded:')
|
|
59
|
+
console.log(decoded)
|
|
60
|
+
console.log()
|
|
61
|
+
|
|
62
|
+
// Verify types are preserved
|
|
63
|
+
console.log('Type checks:')
|
|
64
|
+
console.log(' timestamp is Date:', decoded.timestamp instanceof Date)
|
|
65
|
+
console.log(' pattern is RegExp:', decoded.pattern instanceof RegExp)
|
|
66
|
+
console.log(' lookup is Map:', decoded.lookup instanceof Map)
|
|
67
|
+
console.log(' uniqueIds is Set:', decoded.uniqueIds instanceof Set)
|
|
68
|
+
console.log(' bigNumber is bigint:', typeof decoded.bigNumber === 'bigint')
|
|
69
|
+
console.log(' error is TypeError:', decoded.error instanceof TypeError)
|
|
70
|
+
console.log(' floats is Float32Array:', decoded.floats instanceof Float32Array)
|
|
71
|
+
console.log(' integers is Int16Array:', decoded.integers instanceof Int16Array)
|
|
72
|
+
console.log(' bigInts is BigUint64Array:', decoded.bigInts instanceof BigUint64Array)
|
|
73
|
+
console.log(' negativeZero is -0:', Object.is(decoded.negativeZero, -0))
|
|
74
|
+
console.log()
|
|
75
|
+
|
|
76
|
+
// Maps with non-string keys work correctly
|
|
77
|
+
console.log('Map key types preserved:')
|
|
78
|
+
console.log(' lookup.get(42):', decoded.lookup.get(42))
|
|
79
|
+
console.log(' lookup.get(true):', decoded.lookup.get(true))
|
|
80
|
+
|
|
81
|
+
/* Expected output:
|
|
82
|
+
|
|
83
|
+
Original:
|
|
84
|
+
{
|
|
85
|
+
timestamp: 2024-01-15T12:30:45.123Z,
|
|
86
|
+
pattern: /hello\s+world/gi,
|
|
87
|
+
lookup: Map(3) { 'string-key' => 'string value', 42 => 'number key', true => 'boolean key' },
|
|
88
|
+
uniqueIds: Set(3) { 1, 2, 3 },
|
|
89
|
+
bigNumber: 9007199254740993n,
|
|
90
|
+
error: TypeError: something went wrong,
|
|
91
|
+
floats: Float32Array(3) [ 1.5, 2.5, 3.5 ],
|
|
92
|
+
integers: Int16Array(3) [ -32768, 0, 32767 ],
|
|
93
|
+
bigInts: BigUint64Array(2) [ 0n, 18446744073709551615n ],
|
|
94
|
+
negativeZero: -0
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
Encoded size: ~230 bytes
|
|
98
|
+
|
|
99
|
+
Decoded:
|
|
100
|
+
{
|
|
101
|
+
timestamp: 2024-01-15T12:30:45.123Z,
|
|
102
|
+
pattern: /hello\s+world/gi,
|
|
103
|
+
lookup: Map(3) { 'string-key' => 'string value', 42 => 'number key', true => 'boolean key' },
|
|
104
|
+
...
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
Type checks:
|
|
108
|
+
timestamp is Date: true
|
|
109
|
+
pattern is RegExp: true
|
|
110
|
+
lookup is Map: true
|
|
111
|
+
uniqueIds is Set: true
|
|
112
|
+
bigNumber is bigint: true
|
|
113
|
+
error is TypeError: true
|
|
114
|
+
floats is Float32Array: true
|
|
115
|
+
integers is Int16Array: true
|
|
116
|
+
bigInts is BigUint64Array: true
|
|
117
|
+
negativeZero is -0: true
|
|
118
|
+
|
|
119
|
+
Map key types preserved:
|
|
120
|
+
lookup.get(42): number key
|
|
121
|
+
lookup.get(true): boolean key
|
|
122
|
+
*/
|
package/interface.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Token } from './lib/token'
|
|
1
|
+
import { Token } from './lib/token.js'
|
|
2
2
|
|
|
3
3
|
export type TokenOrNestedTokens = Token | Token[] | TokenOrNestedTokens[]
|
|
4
4
|
|
|
@@ -29,7 +29,19 @@ export interface DecodeTokenizer {
|
|
|
29
29
|
pos(): number,
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
/**
|
|
33
|
+
* Control object passed to tag decoders, providing methods to decode the tagged content.
|
|
34
|
+
*/
|
|
35
|
+
export interface TagDecodeControl {
|
|
36
|
+
/** Decode the tagged content */
|
|
37
|
+
(): unknown
|
|
38
|
+
/** Decode CBOR map content as [key, value] entries array (preserves key types) */
|
|
39
|
+
entries(): Array<[unknown, unknown]>
|
|
40
|
+
/** @internal Track whether decode was called */
|
|
41
|
+
readonly _called?: boolean
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type TagDecoder = (decode: TagDecodeControl) => any
|
|
33
45
|
|
|
34
46
|
export interface DecodeOptions {
|
|
35
47
|
allowIndefinite?: boolean
|
|
@@ -42,7 +54,7 @@ export interface DecodeOptions {
|
|
|
42
54
|
useMaps?: boolean
|
|
43
55
|
rejectDuplicateMapKeys?: boolean
|
|
44
56
|
retainStringBytes?: boolean
|
|
45
|
-
tags?:
|
|
57
|
+
tags?: { [tagNumber: number]: TagDecoder },
|
|
46
58
|
tokenizer?: DecodeTokenizer
|
|
47
59
|
}
|
|
48
60
|
|
package/lib/0uint.js
CHANGED
|
@@ -6,8 +6,8 @@ import { decodeErrPrefix, assertEnoughData } from './common.js'
|
|
|
6
6
|
export const uintBoundaries = [24, 256, 65536, 4294967296, BigInt('18446744073709551616')]
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
* @typedef {import('../interface').ByteWriter} ByteWriter
|
|
10
|
-
* @typedef {import('../interface').DecodeOptions} DecodeOptions
|
|
9
|
+
* @typedef {import('../interface.js').ByteWriter} ByteWriter
|
|
10
|
+
* @typedef {import('../interface.js').DecodeOptions} DecodeOptions
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
/**
|
package/lib/1negint.js
CHANGED
|
@@ -5,8 +5,8 @@ import * as uint from './0uint.js'
|
|
|
5
5
|
import { decodeErrPrefix } from './common.js'
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* @typedef {import('../interface').ByteWriter} ByteWriter
|
|
9
|
-
* @typedef {import('../interface').DecodeOptions} DecodeOptions
|
|
8
|
+
* @typedef {import('../interface.js').ByteWriter} ByteWriter
|
|
9
|
+
* @typedef {import('../interface.js').DecodeOptions} DecodeOptions
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
/**
|
package/lib/2bytes.js
CHANGED
|
@@ -4,8 +4,8 @@ import * as uint from './0uint.js'
|
|
|
4
4
|
import { compare, fromString } from './byte-utils.js'
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* @typedef {import('../interface').ByteWriter} ByteWriter
|
|
8
|
-
* @typedef {import('../interface').DecodeOptions} DecodeOptions
|
|
7
|
+
* @typedef {import('../interface.js').ByteWriter} ByteWriter
|
|
8
|
+
* @typedef {import('../interface.js').DecodeOptions} DecodeOptions
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
/**
|
package/lib/3string.js
CHANGED
|
@@ -10,8 +10,8 @@ const textDecoder = new TextDecoder()
|
|
|
10
10
|
const ASCII_THRESHOLD = 32
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
* @typedef {import('../interface').ByteWriter} ByteWriter
|
|
14
|
-
* @typedef {import('../interface').DecodeOptions} DecodeOptions
|
|
13
|
+
* @typedef {import('../interface.js').ByteWriter} ByteWriter
|
|
14
|
+
* @typedef {import('../interface.js').DecodeOptions} DecodeOptions
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
/**
|
package/lib/4array.js
CHANGED
|
@@ -3,8 +3,8 @@ import * as uint from './0uint.js'
|
|
|
3
3
|
import { decodeErrPrefix } from './common.js'
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* @typedef {import('../interface').ByteWriter} ByteWriter
|
|
7
|
-
* @typedef {import('../interface').DecodeOptions} DecodeOptions
|
|
6
|
+
* @typedef {import('../interface.js').ByteWriter} ByteWriter
|
|
7
|
+
* @typedef {import('../interface.js').DecodeOptions} DecodeOptions
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
/**
|
package/lib/5map.js
CHANGED
|
@@ -3,8 +3,8 @@ import * as uint from './0uint.js'
|
|
|
3
3
|
import { decodeErrPrefix } from './common.js'
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* @typedef {import('../interface').ByteWriter} ByteWriter
|
|
7
|
-
* @typedef {import('../interface').DecodeOptions} DecodeOptions
|
|
6
|
+
* @typedef {import('../interface.js').ByteWriter} ByteWriter
|
|
7
|
+
* @typedef {import('../interface.js').DecodeOptions} DecodeOptions
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
/**
|
package/lib/6tag.js
CHANGED
|
@@ -2,8 +2,8 @@ import { Token, Type } from './token.js'
|
|
|
2
2
|
import * as uint from './0uint.js'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* @typedef {import('../interface').ByteWriter} ByteWriter
|
|
6
|
-
* @typedef {import('../interface').DecodeOptions} DecodeOptions
|
|
5
|
+
* @typedef {import('../interface.js').ByteWriter} ByteWriter
|
|
6
|
+
* @typedef {import('../interface.js').DecodeOptions} DecodeOptions
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
/**
|
package/lib/7float.js
CHANGED
|
@@ -6,9 +6,9 @@ import { decodeErrPrefix } from './common.js'
|
|
|
6
6
|
import { encodeUint } from './0uint.js'
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
* @typedef {import('../interface').ByteWriter} ByteWriter
|
|
10
|
-
* @typedef {import('../interface').DecodeOptions} DecodeOptions
|
|
11
|
-
* @typedef {import('../interface').EncodeOptions} EncodeOptions
|
|
9
|
+
* @typedef {import('../interface.js').ByteWriter} ByteWriter
|
|
10
|
+
* @typedef {import('../interface.js').DecodeOptions} DecodeOptions
|
|
11
|
+
* @typedef {import('../interface.js').EncodeOptions} EncodeOptions
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
export const MINOR_FALSE = 20
|
|
@@ -195,7 +195,8 @@ function encodeFloat16 (inp) {
|
|
|
195
195
|
dataView.setUint16(0, 0x7c00, false)
|
|
196
196
|
} else if (exponent === 0x00) {
|
|
197
197
|
// 0.0, -0.0 and subnormals, shouldn't be possible to get here because 0.0 should be counted as an int
|
|
198
|
-
|
|
198
|
+
// Use valu32 for sign bit since bitwise ops on floats lose -0 sign
|
|
199
|
+
dataView.setUint16(0, ((valu32 & 0x80000000) >> 16) | (mantissa >> 13), false)
|
|
199
200
|
} else { // standard numbers
|
|
200
201
|
// chunks of logic here borrowed from https://github.com/PJK/libcbor/blob/c78f437182533e3efa8d963ff4b945bb635c2284/src/cbor/encoding.c#L127
|
|
201
202
|
const logicalExponent = exponent - 127
|