cborg 4.5.8 → 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.
Files changed (73) hide show
  1. package/.github/dependabot.yml +4 -0
  2. package/.github/workflows/test-and-release.yml +2 -4
  3. package/CHANGELOG.md +44 -0
  4. package/README.md +213 -9
  5. package/cborg.js +4 -4
  6. package/example-extended.js +122 -0
  7. package/interface.ts +15 -3
  8. package/lib/0uint.js +2 -2
  9. package/lib/1negint.js +2 -2
  10. package/lib/2bytes.js +2 -2
  11. package/lib/3string.js +2 -2
  12. package/lib/4array.js +2 -2
  13. package/lib/5map.js +2 -2
  14. package/lib/6tag.js +2 -2
  15. package/lib/7float.js +5 -4
  16. package/lib/decode.js +94 -4
  17. package/lib/encode.js +7 -7
  18. package/lib/extended/extended.js +250 -0
  19. package/lib/json/decode.js +2 -2
  20. package/lib/json/encode.js +3 -3
  21. package/lib/jump.js +1 -1
  22. package/lib/length.js +3 -3
  23. package/lib/taglib.js +452 -0
  24. package/package.json +21 -17
  25. package/test/common.js +2 -1
  26. package/test/test-6tag.js +2 -1
  27. package/test/test-cbor-vectors.js +14 -6
  28. package/test/test-extended-vectors.js +293 -0
  29. package/test/test-extended.js +684 -0
  30. package/test/test-taglib.js +634 -0
  31. package/tsconfig.json +7 -11
  32. package/types/cborg.d.ts +4 -4
  33. package/types/cborg.d.ts.map +1 -1
  34. package/types/interface.d.ts +14 -3
  35. package/types/interface.d.ts.map +1 -1
  36. package/types/lib/0uint.d.ts +4 -4
  37. package/types/lib/0uint.d.ts.map +1 -1
  38. package/types/lib/1negint.d.ts +4 -4
  39. package/types/lib/1negint.d.ts.map +1 -1
  40. package/types/lib/2bytes.d.ts +2 -2
  41. package/types/lib/2bytes.d.ts.map +1 -1
  42. package/types/lib/3string.d.ts +2 -2
  43. package/types/lib/3string.d.ts.map +1 -1
  44. package/types/lib/4array.d.ts +2 -2
  45. package/types/lib/4array.d.ts.map +1 -1
  46. package/types/lib/5map.d.ts +2 -2
  47. package/types/lib/5map.d.ts.map +1 -1
  48. package/types/lib/6tag.d.ts +4 -4
  49. package/types/lib/6tag.d.ts.map +1 -1
  50. package/types/lib/7float.d.ts +6 -6
  51. package/types/lib/7float.d.ts.map +1 -1
  52. package/types/lib/byte-utils.d.ts +5 -2
  53. package/types/lib/byte-utils.d.ts.map +1 -1
  54. package/types/lib/decode.d.ts +4 -3
  55. package/types/lib/decode.d.ts.map +1 -1
  56. package/types/lib/encode.d.ts +8 -8
  57. package/types/lib/encode.d.ts.map +1 -1
  58. package/types/lib/extended/extended.d.ts +78 -0
  59. package/types/lib/extended/extended.d.ts.map +1 -0
  60. package/types/lib/json/decode.d.ts +5 -5
  61. package/types/lib/json/decode.d.ts.map +1 -1
  62. package/types/lib/json/encode.d.ts +3 -3
  63. package/types/lib/json/encode.d.ts.map +1 -1
  64. package/types/lib/jump.d.ts +1 -1
  65. package/types/lib/jump.d.ts.map +1 -1
  66. package/types/lib/length.d.ts +3 -3
  67. package/types/lib/length.d.ts.map +1 -1
  68. package/types/lib/taglib.d.ts +143 -0
  69. package/types/lib/taglib.d.ts.map +1 -0
  70. package/types/tsconfig.tsbuildinfo +1 -1
  71. package/taglib.js +0 -73
  72. package/types/taglib.d.ts +0 -18
  73. package/types/taglib.d.ts.map +0 -1
@@ -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.2.0
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.2.0
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,47 @@
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
+
1
45
  ## [4.5.8](https://github.com/rvagg/cborg/compare/v4.5.7...v4.5.8) (2026-01-21)
2
46
 
3
47
  ### Bug Fixes
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 array where the indexes correspond to the tag numbers that are encountered during decode, and the values are functions that are able to turn the following token(s) into a JavaScript object. Each tag token in CBOR is followed by a data item, often a byte array of arbitrary length, but can be a more complex series of tokens that form a nested data item. This token is supplied to the tag decoder function.
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
- This example is available from the cborg taglib as `bigIntDecoder` and `bigNegIntDecoder` (`import { bigIntDecoder, bigNegIntDecoder } as taglib from 'cborg/taglib'`) and implements CBOR tags 2 and 3 (bigint and negative bigint). This function would be registered using an options parameter:
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
- tags[2] = bigIntDecoder
390
- tags[3] = bigNegIntDecoder
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 (bytes) {
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 (bytes) {
407
- return -1n - bigIntDecoder(bytes)
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 serialization _and_ deserialization. Options that can be provided to `decode()` to impose some strictness requirements are:
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
- export type TagDecoder = (inner: any) => any
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?: TagDecoder[],
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
- dataView.setUint16(0, ((inp & 0x80000000) >> 16) | (mantissa >> 13), false)
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