cborg 5.0.1 → 5.1.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/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## [5.1.0](https://github.com/rvagg/cborg/compare/v5.0.1...v5.1.0) (2026-04-07)
2
+
3
+ ### Features
4
+
5
+ * add Tagged class for one-off tag emission and round-trip preservation ([#174](https://github.com/rvagg/cborg/issues/174)) ([e2e7c20](https://github.com/rvagg/cborg/commit/e2e7c207e79d7131e3e941772f1d70ce1363b94f))
6
+
1
7
  ## [5.0.1](https://github.com/rvagg/cborg/compare/v5.0.0...v5.0.1) (2026-04-02)
2
8
 
3
9
  ### Bug Fixes
package/README.md CHANGED
@@ -6,8 +6,6 @@
6
6
 
7
7
  **cborg** is also fast, and is suitable for the browser (is `Uint8Array` native) and Node.js.
8
8
 
9
- **cborg** supports CBOR tags, but does not ship with them enabled by default. If you want tags, you need to plug them in to the encoder and decoder.
10
-
11
9
  * [Example](#example)
12
10
  * [CLI](#cli)
13
11
  * [`cborg bin2diag [binary input]`](#cborg-bin2diag-binary-input)
@@ -30,6 +28,7 @@
30
28
  * [Options](#options-1)
31
29
  * [`decodeFirst(data[, options])`](#decodefirstdata-options)
32
30
  * [`encodedLength(data[, options])`](#encodedlengthdata-options)
31
+ * [`Tagged`](#tagged)
33
32
  * [Type encoders](#type-encoders)
34
33
  * [Tag decoders](#tag-decoders)
35
34
  * [Decoding with a custom tokeniser](#decoding-with-a-custom-tokeniser)
@@ -325,17 +324,81 @@ Calculate the byte length of the given data when encoded as CBOR with the option
325
324
 
326
325
  A `tokensToLength()` function is available which deals directly with a tokenized form of the object, but this only recommended for advanced users.
327
326
 
327
+ ### `Tagged`
328
+
329
+ For applications that need to wrap a value in an application-specific CBOR tag (COSE envelopes, dCBOR application tags, custom protocols) without the ceremony of registering a `typeEncoders` entry and a matching tag decoder, cborg exports a `Tagged` wrapper class. `Tagged` is symmetric: pass it to `encode()` to emit a tag, and use `Tagged.decoder(tag)` or `Tagged.preserve(...tags)` on the decode side to round-trip the tag without losing it.
330
+
331
+ ```js
332
+ import { encode, decode, Tagged } from 'cborg'
333
+
334
+ // Encode: wrap any value in a tag
335
+ const bytes = encode(new Tagged(1234, 'hello'))
336
+
337
+ // Decode: preserve the tag through decode
338
+ const decoded = decode(bytes, { tags: Tagged.preserve(1234) })
339
+ decoded instanceof Tagged // true
340
+ decoded.tag // 1234
341
+ decoded.value // 'hello'
342
+ ```
343
+
344
+ `Tagged.value` can be anything cborg can encode, including arrays, maps, other `Tagged` instances, or values handled by your own `typeEncoders` — cborg recurses into it with the same encode pipeline.
345
+
346
+ For finer-grained control on decode, use `Tagged.decoder(tag)` directly to mix preserved tags with other decoders:
347
+
348
+ ```js
349
+ import { decode, Tagged } from 'cborg'
350
+
351
+ const value = decode(bytes, {
352
+ tags: {
353
+ 16: Tagged.decoder(16), // preserve tag 16 as Tagged
354
+ 96: Tagged.decoder(96), // preserve tag 96 as Tagged
355
+ 42: cidDecoder // a custom decoder for tag 42
356
+ }
357
+ })
358
+ ```
359
+
360
+ When to prefer `Tagged` over a custom `typeEncoders` entry:
361
+
362
+ - You have a one-off or application-specific tag and don't want to define a JS class for it.
363
+ - You want to inspect the tag number on the decode side rather than collapsing it into a JS type.
364
+ - Your downstream code already speaks `{ tag, value }`.
365
+
366
+ When to prefer a `typeEncoders` entry instead:
367
+
368
+ - You're systematically mapping a JS type to a tag (e.g. `CID` → tag 42, `Date` → tag 1) and want it to apply automatically wherever that type appears.
369
+ - You need a tighter binary representation than the default recursive encoding (e.g. flatten the value to a `bytes` token rather than a nested CBOR structure).
370
+
371
+ The default `Tagged` behaviour is itself implemented as a `typeEncoders` entry under the type name `'Tagged'`. You can override it by supplying your own `typeEncoders.Tagged` and returning `null` from it to fall through to the default for cases your override doesn't want to handle.
372
+
328
373
  ### Type encoders
329
374
 
330
375
  The `typeEncoders` property to the `options` argument to `encode()` allows you to add additional functionality to cborg, or override existing functionality.
331
376
 
332
377
  When converting JavaScript objects, types are differentiated using the method and naming used by [@sindresorhus/is](https://github.com/sindresorhus/is) _(a custom implementation is used internally for performance reasons)_ and an internal set of type encoders are used to convert objects to their appropriate CBOR form. Supported types are: `null`, `undefined`, `number`, `bigint`, `string`, `boolean`, `Array`, `Object`, `Map`, `Buffer`, `ArrayBuffer`, `DataView`, `Uint8Array` and all other `TypedArray`s (their underlying byte array is encoded, so they will all round-trip as a `Uint8Array` since the type information is lost). Any object that doesn't match a type in this list will cause an error to be thrown during decode. e.g. `encode(new Date())` will throw an error because there is no internal `Date` type encoder.
333
378
 
334
- The `typeEncoders` option is an object whose property names match to @sindresorhus/is type names. When this option is provided and a property exists for any given object's type, the function provided as the value to that property is called with the object as an argument.
379
+ The `typeEncoders` option is an object whose property names match to @sindresorhus/is type names. When this option is provided and a property exists for any given object's type, the function is called with the signature `(obj, typ, options, refStack)`:
380
+
381
+ * `obj` - the value being encoded
382
+ * `typ` - the resolved type name (the same string used as the property key)
383
+ * `options` - the full encode options object, useful for recursive encoding (see below)
384
+ * `refStack` - an internal circular-reference tracker, opaque to user code, that should be threaded through any recursive call
335
385
 
336
386
  If a type encoder function returns `null`, the default encoder, if any, is used instead.
337
387
 
338
- If a type encoder function returns an array, cborg will expect it to contain zero or more `Token` objects that will be encoded to binary form.
388
+ If a type encoder function returns an array, cborg will expect it to contain zero or more `Token` objects (or nested arrays thereof) that will be encoded to binary form. To **recursively encode a nested JavaScript value** as part of your tokens (so that `typeEncoders` for the nested value still apply), import `objectToTokens` from `cborg` and call `objectToTokens(value, options, refStack)` from inside your encoder. This is how the built-in `Tagged` encoder, `mapEncoder`, and `setEncoder` recurse into their contents:
389
+
390
+ ```js
391
+ import { Token, Type, objectToTokens } from 'cborg'
392
+
393
+ function myWrapperEncoder (obj, _typ, options, refStack) {
394
+ return [
395
+ new Token(Type.tag, 1234),
396
+ objectToTokens(obj.inner, options, refStack)
397
+ ]
398
+ }
399
+ ```
400
+
401
+ For tag encoders that flatten their content to a leaf type (e.g. encode a `BigInt` as a `Type.bytes` token under tag 2), you don't need to recurse - just return the flat token sequence directly, as the `bigIntEncoder` example below does.
339
402
 
340
403
  `Token`s map directly to CBOR entities. Each one has a `Type` and a `value`. A type encoder is responsible for turning a JavaScript object into a set of tags.
341
404
 
package/cborg.js CHANGED
@@ -1,5 +1,6 @@
1
- import { encode, encodeInto, rfc8949EncodeOptions } from './lib/encode.js'
1
+ import { encode, encodeInto, objectToTokens, rfc8949EncodeOptions } from './lib/encode.js'
2
2
  import { decode, decodeFirst, Tokeniser, tokensToObject } from './lib/decode.js'
3
+ import { Tagged } from './lib/tagged.js'
3
4
  import { Token, Type } from './lib/token.js'
4
5
 
5
6
  /**
@@ -19,7 +20,9 @@ export {
19
20
  tokensToObject,
20
21
  encode,
21
22
  encodeInto,
23
+ objectToTokens,
22
24
  rfc8949EncodeOptions,
25
+ Tagged,
23
26
  Token,
24
27
  Type
25
28
  }
package/lib/encode.js CHANGED
@@ -294,6 +294,24 @@ const typeEncoders = {
294
294
  return [new Token(Type.map, entries.length), entries, new Token(Type.break)]
295
295
  }
296
296
  return [new Token(Type.map, entries.length), entries]
297
+ },
298
+
299
+ /**
300
+ * Encode a `Tagged` wrapper as a CBOR tag header followed by the encoded
301
+ * form of the wrapped value. The value is recursively tokenised through
302
+ * `objectToTokens()` so any registered `typeEncoders` apply to it.
303
+ *
304
+ * @param {any} obj
305
+ * @param {string} _typ
306
+ * @param {EncodeOptions} options
307
+ * @param {Reference} [refStack]
308
+ * @returns {TokenOrNestedTokens}
309
+ */
310
+ Tagged (obj, _typ, options, refStack) {
311
+ return [
312
+ new Token(Type.tag, obj.tag),
313
+ objectToTokens(obj.value, options, refStack)
314
+ ]
297
315
  }
298
316
  }
299
317
 
package/lib/is.js CHANGED
@@ -29,7 +29,8 @@ const objectTypeNames = [
29
29
  'Float32Array',
30
30
  'Float64Array',
31
31
  'BigInt64Array',
32
- 'BigUint64Array'
32
+ 'BigUint64Array',
33
+ 'Tagged'
33
34
  ]
34
35
 
35
36
  /**
package/lib/tagged.js ADDED
@@ -0,0 +1,78 @@
1
+ /**
2
+ * @typedef {import('../interface.js').TagDecodeControl} TagDecodeControl
3
+ * @typedef {(decode: TagDecodeControl) => Tagged} TaggedTagDecoder
4
+ */
5
+
6
+ /**
7
+ * A wrapper class for representing a CBOR tag with an arbitrary nested value.
8
+ *
9
+ * `Tagged` is a symmetric primitive: it can be passed to `encode()` to emit
10
+ * a CBOR tag header followed by the encoded form of `value`, and it can be
11
+ * returned from a tag decoder (via `Tagged.decoder(tag)` or `Tagged.preserve()`)
12
+ * to round-trip a tag through decode without losing the tag number.
13
+ *
14
+ * Use `Tagged` for one-off tag handling where defining a dedicated
15
+ * `typeEncoders` entry and tag decoder pair would be heavyweight, e.g. when
16
+ * wrapping a structure in a single application-specific tag (COSE, dCBOR
17
+ * envelopes, etc.).
18
+ *
19
+ * For systematic mapping of a JS type to a tag (e.g. CID -> tag 42), prefer
20
+ * a dedicated `typeEncoders` entry instead.
21
+ */
22
+ export class Tagged {
23
+ /**
24
+ * @param {number} tag - CBOR tag number, a non-negative integer
25
+ * @param {any} value - The value to be tagged; encoded recursively
26
+ */
27
+ constructor (tag, value) {
28
+ if (typeof tag !== 'number' || !Number.isInteger(tag) || tag < 0) {
29
+ throw new TypeError('Tagged: tag must be a non-negative integer')
30
+ }
31
+ this.tag = tag
32
+ this.value = value
33
+ }
34
+
35
+ /**
36
+ * Build a tag decoder for use in `decode()`'s `tags` option that returns the
37
+ * decoded content wrapped in a `Tagged` instance, preserving the tag number
38
+ * for the caller to inspect.
39
+ *
40
+ * @param {number} tag - The CBOR tag number this decoder will be registered for
41
+ * @returns {TaggedTagDecoder}
42
+ *
43
+ * @example
44
+ * import { decode, Tagged } from 'cborg'
45
+ * const value = decode(bytes, { tags: { 16: Tagged.decoder(16) } })
46
+ * // value instanceof Tagged; value.tag === 16
47
+ */
48
+ static decoder (tag) {
49
+ return (decode) => new Tagged(tag, decode())
50
+ }
51
+
52
+ /**
53
+ * Build a `tags` option for `decode()` that wraps each listed tag number in
54
+ * a `Tagged` instance, preserving those tags through decode without
55
+ * registering a dedicated decoder per tag.
56
+ *
57
+ * @param {...number} tagNumbers - One or more CBOR tag numbers to preserve
58
+ * @returns {{[tagNumber: number]: TaggedTagDecoder}}
59
+ *
60
+ * @example
61
+ * import { decode, Tagged } from 'cborg'
62
+ * const value = decode(bytes, { tags: Tagged.preserve(16, 96) })
63
+ */
64
+ static preserve (...tagNumbers) {
65
+ /** @type {{[tagNumber: number]: TaggedTagDecoder}} */
66
+ const tags = {}
67
+ for (const tag of tagNumbers) {
68
+ tags[tag] = Tagged.decoder(tag)
69
+ }
70
+ return tags
71
+ }
72
+ }
73
+
74
+ // Symbol.toStringTag so the internal `is()` type detection returns 'Tagged'
75
+ // for instances, allowing the default Tagged typeEncoder to match.
76
+ Object.defineProperty(Tagged.prototype, Symbol.toStringTag, {
77
+ value: 'Tagged'
78
+ })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cborg",
3
- "version": "5.0.1",
3
+ "version": "5.1.0",
4
4
  "description": "Fast CBOR with a focus on strictness",
5
5
  "main": "cborg.js",
6
6
  "type": "module",
@@ -0,0 +1,231 @@
1
+ /* eslint-env mocha */
2
+
3
+ import * as chai from 'chai'
4
+
5
+ import { decode, encode, encodeInto, rfc8949EncodeOptions, Tagged, Token, Type } from '../cborg.js'
6
+ import { encodedLength } from '../lib/length.js'
7
+ import { fromHex, toHex } from '../lib/byte-utils.js'
8
+
9
+ const { assert } = chai
10
+
11
+ describe('Tagged', () => {
12
+ describe('class', () => {
13
+ it('exposes tag and value', () => {
14
+ const t = new Tagged(16, 'hello')
15
+ assert.strictEqual(t.tag, 16)
16
+ assert.strictEqual(t.value, 'hello')
17
+ })
18
+
19
+ it('rejects non-integer tag', () => {
20
+ assert.throws(() => new Tagged(1.5, null), TypeError)
21
+ assert.throws(() => new Tagged(-1, null), TypeError)
22
+ // @ts-expect-error - intentional bad input
23
+ assert.throws(() => new Tagged('16', null), TypeError)
24
+ // @ts-expect-error - intentional bad input
25
+ assert.throws(() => new Tagged(undefined, null), TypeError)
26
+ assert.throws(() => new Tagged(NaN, null), TypeError)
27
+ })
28
+
29
+ it('Symbol.toStringTag is "Tagged"', () => {
30
+ assert.strictEqual(Object.prototype.toString.call(new Tagged(0, null)), '[object Tagged]')
31
+ })
32
+ })
33
+
34
+ describe('encode', () => {
35
+ it('encodes a small tag with primitive value', () => {
36
+ // RFC 8949 example: tag 1 wrapping 1363896240
37
+ const bytes = encode(new Tagged(1, 1363896240))
38
+ assert.strictEqual(toHex(bytes), 'c11a514b67b0')
39
+ })
40
+
41
+ it('encodes a 1-byte tag header (>= 24)', () => {
42
+ const bytes = encode(new Tagged(32, 'http://example.com'))
43
+ assert.strictEqual(bytes[0], 0xd8)
44
+ assert.strictEqual(bytes[1], 32)
45
+ })
46
+
47
+ it('encodes a 2-byte tag header (>= 256)', () => {
48
+ const bytes = encode(new Tagged(259, new Map()))
49
+ // d9 01 03 = tag(259), then a0 = map(0)
50
+ assert.strictEqual(toHex(bytes), 'd90103a0')
51
+ })
52
+
53
+ it('encodes a 4-byte tag header (>= 65536)', () => {
54
+ const bytes = encode(new Tagged(0x10000, 0))
55
+ // da 00 01 00 00 = tag(65536), then 00 = uint(0)
56
+ assert.strictEqual(toHex(bytes), 'da0001000000')
57
+ })
58
+
59
+ it('recurses into nested object value', () => {
60
+ const bytes = encode(new Tagged(16, [new Uint8Array([1, 2, 3]), new Map(), null]))
61
+ const decoded = decode(bytes, { tags: Tagged.preserve(16), useMaps: true })
62
+ assert.instanceOf(decoded, Tagged)
63
+ assert.strictEqual(decoded.tag, 16)
64
+ assert.deepEqual(decoded.value[0], new Uint8Array([1, 2, 3]))
65
+ assert.instanceOf(decoded.value[1], Map)
66
+ assert.strictEqual(decoded.value[2], null)
67
+ })
68
+
69
+ it('Tagged is overridable via typeEncoders (returning null falls through)', () => {
70
+ // Override that only special-cases tag 99 and falls through for everything else
71
+ let calls = 0
72
+ const bytes = encode(new Tagged(99, 'ignored'), {
73
+ typeEncoders: {
74
+ Tagged: (obj) => {
75
+ calls++
76
+ if (obj.tag !== 999) return null // fall through
77
+ return [new Token(Type.string, 'replaced')]
78
+ }
79
+ }
80
+ })
81
+ assert.strictEqual(calls, 1)
82
+ // Falling through means default behaviour: tag(99) text("ignored")
83
+ const decoded = decode(bytes, { tags: Tagged.preserve(99) })
84
+ assert.strictEqual(decoded.tag, 99)
85
+ assert.strictEqual(decoded.value, 'ignored')
86
+ })
87
+
88
+ it('Tagged is overridable via typeEncoders (full replacement)', () => {
89
+ const bytes = encode(new Tagged(99, 'foo'), {
90
+ typeEncoders: {
91
+ Tagged: () => [new Token(Type.string, 'replaced')]
92
+ }
93
+ })
94
+ assert.strictEqual(decode(bytes), 'replaced')
95
+ })
96
+
97
+ it('typeEncoders for inner types apply to wrapped value', () => {
98
+ // Custom Date encoder should fire when Date appears inside Tagged.value
99
+ let dateEncoderCalled = false
100
+ const bytes = encode(new Tagged(16, [new Date(0)]), {
101
+ typeEncoders: {
102
+ Date: (d) => {
103
+ dateEncoderCalled = true
104
+ return [new Token(Type.uint, d.getTime())]
105
+ }
106
+ }
107
+ })
108
+ assert.isTrue(dateEncoderCalled)
109
+ const decoded = decode(bytes, { tags: Tagged.preserve(16) })
110
+ assert.strictEqual(decoded.tag, 16)
111
+ assert.deepEqual(decoded.value, [0])
112
+ })
113
+
114
+ it('rfc8949EncodeOptions sorts maps inside Tagged.value', () => {
115
+ // Build a Map whose insertion order differs from RFC 8949 bytewise order.
116
+ // RFC 8949 sorts keys by their canonical encoding bytes; for short
117
+ // strings the longer key sorts later.
118
+ const inner = new Map([
119
+ ['bb', 2],
120
+ ['a', 1]
121
+ ])
122
+ const sortedDefault = encode(new Tagged(16, inner))
123
+ const sortedRfc8949 = encode(new Tagged(16, inner), rfc8949EncodeOptions)
124
+
125
+ // Default (RFC 7049, length-first) and RFC 8949 (bytewise) agree here:
126
+ // 'a' (1 byte) sorts before 'bb' (2 bytes) under both rules.
127
+ assert.deepEqual(sortedDefault, sortedRfc8949)
128
+
129
+ // Verify the tag header is present and the inner map is sorted
130
+ const decodedDefault = decode(sortedDefault, { tags: Tagged.preserve(16), useMaps: true })
131
+ assert.strictEqual(decodedDefault.tag, 16)
132
+ assert.instanceOf(decodedDefault.value, Map)
133
+ assert.deepEqual([...decodedDefault.value.keys()], ['a', 'bb'])
134
+ })
135
+
136
+ it('detects circular references inside Tagged.value', () => {
137
+ const arr = []
138
+ arr.push(arr)
139
+ assert.throws(() => encode(new Tagged(16, arr)), /circular/i)
140
+ })
141
+
142
+ it('works with encodeInto', () => {
143
+ const dest = new Uint8Array(64)
144
+ const { written } = encodeInto(new Tagged(1, 1363896240), dest)
145
+ assert.strictEqual(toHex(dest.subarray(0, written)), 'c11a514b67b0')
146
+ })
147
+
148
+ it('works with encodedLength', () => {
149
+ const value = new Tagged(16, [new Uint8Array([1, 2, 3]), new Map([[1, 2]]), null])
150
+ const expected = encode(value).length
151
+ assert.strictEqual(encodedLength(value), expected)
152
+ })
153
+ })
154
+
155
+ describe('decode', () => {
156
+ it('Tagged.decoder builds a passthrough decoder', () => {
157
+ const bytes = fromHex('c11a514b67b0') // tag(1) 1363896240
158
+ const value = decode(bytes, { tags: { 1: Tagged.decoder(1) } })
159
+ assert.instanceOf(value, Tagged)
160
+ assert.strictEqual(value.tag, 1)
161
+ assert.strictEqual(value.value, 1363896240)
162
+ })
163
+
164
+ it('Tagged.preserve builds a tags map for multiple tags', () => {
165
+ const tags = Tagged.preserve(16, 96)
166
+ assert.typeOf(tags[16], 'function')
167
+ assert.typeOf(tags[96], 'function')
168
+ assert.isUndefined(tags[1])
169
+ })
170
+
171
+ it('Tagged.preserve with no arguments yields an empty tags map', () => {
172
+ const tags = Tagged.preserve()
173
+ assert.deepEqual(Object.keys(tags), [])
174
+ })
175
+
176
+ it('unregistered tags still throw when Tagged.preserve is partial', () => {
177
+ // tag(2) wrapping bytes(0)
178
+ const bytes = fromHex('c240')
179
+ assert.throws(() => decode(bytes, { tags: Tagged.preserve(16) }), /tag not supported/)
180
+ })
181
+ })
182
+
183
+ describe('round-trip', () => {
184
+ it('round-trips a COSE_Encrypt0-shaped envelope', () => {
185
+ // Build a structure resembling COSE_Encrypt0 (RFC 9052):
186
+ // tag(16) [protected_bstr, unprotected_map, ciphertext_or_nil]
187
+ const protectedHeaders = encode(new Map([[1, -7]])) // alg = ES256
188
+ const unprotected = new Map([[5, new Uint8Array([0xaa, 0xbb])]]) // iv
189
+ const envelope = new Tagged(16, [protectedHeaders, unprotected, null])
190
+
191
+ const bytes = encode(envelope)
192
+ const decoded = decode(bytes, { tags: Tagged.preserve(16), useMaps: true })
193
+
194
+ assert.instanceOf(decoded, Tagged)
195
+ assert.strictEqual(decoded.tag, 16)
196
+ assert.deepEqual(decoded.value[0], protectedHeaders)
197
+ assert.instanceOf(decoded.value[1], Map)
198
+ assert.deepEqual(decoded.value[1].get(5), new Uint8Array([0xaa, 0xbb]))
199
+ assert.strictEqual(decoded.value[2], null)
200
+ })
201
+
202
+ it('round-trips deeply nested Tagged instances', () => {
203
+ let v = /** @type {any} */ ('leaf')
204
+ for (let i = 1; i <= 5; i++) {
205
+ v = new Tagged(i, v)
206
+ }
207
+ const bytes = encode(v)
208
+ const decoded = decode(bytes, { tags: Tagged.preserve(1, 2, 3, 4, 5) })
209
+ let cur = decoded
210
+ for (let i = 5; i >= 1; i--) {
211
+ assert.instanceOf(cur, Tagged)
212
+ assert.strictEqual(cur.tag, i)
213
+ cur = cur.value
214
+ }
215
+ assert.strictEqual(cur, 'leaf')
216
+ })
217
+
218
+ it('Tagged round-trips through rfc8949EncodeOptions', () => {
219
+ const value = new Tagged(16, [
220
+ new Tagged(2, new Uint8Array([0xff])),
221
+ new Map([['z', 1], ['a', 2]])
222
+ ])
223
+ const bytes = encode(value, rfc8949EncodeOptions)
224
+ const decoded = decode(bytes, { tags: Tagged.preserve(2, 16), useMaps: true })
225
+ assert.strictEqual(decoded.tag, 16)
226
+ assert.strictEqual(decoded.value[0].tag, 2)
227
+ assert.deepEqual(decoded.value[0].value, new Uint8Array([0xff]))
228
+ assert.deepEqual([...decoded.value[1].keys()], ['a', 'z'])
229
+ })
230
+ })
231
+ })
package/types/cborg.d.ts CHANGED
@@ -24,8 +24,10 @@ import { Tokeniser } from './lib/decode.js';
24
24
  import { tokensToObject } from './lib/decode.js';
25
25
  import { encode } from './lib/encode.js';
26
26
  import { encodeInto } from './lib/encode.js';
27
+ import { objectToTokens } from './lib/encode.js';
27
28
  import { rfc8949EncodeOptions } from './lib/encode.js';
29
+ import { Tagged } from './lib/tagged.js';
28
30
  import { Token } from './lib/token.js';
29
31
  import { Type } from './lib/token.js';
30
- export { decode, decodeFirst, Tokeniser as Tokenizer, tokensToObject, encode, encodeInto, rfc8949EncodeOptions, Token, Type };
32
+ export { decode, decodeFirst, Tokeniser as Tokenizer, tokensToObject, encode, encodeInto, objectToTokens, rfc8949EncodeOptions, Tagged, Token, Type };
31
33
  //# sourceMappingURL=cborg.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cborg.d.ts","sourceRoot":"","sources":["../cborg.js"],"names":[],"mappings":";;;+BAMa,OAAO,gBAAgB,EAAE,gBAAgB;;;;yBACzC,OAAO,gBAAgB,EAAE,UAAU;;;;0BAEnC,OAAO,gBAAgB,EAAE,mBAAmB;;;;4BAC5C,OAAO,gBAAgB,EAAE,aAAa;;;;4BACtC,OAAO,gBAAgB,EAAE,aAAa;uBAVY,iBAAiB;4BAAjB,iBAAiB;0BAAjB,iBAAiB;+BAAjB,iBAAiB;uBADvB,iBAAiB;2BAAjB,iBAAiB;qCAAjB,iBAAiB;sBAE9C,gBAAgB;qBAAhB,gBAAgB"}
1
+ {"version":3,"file":"cborg.d.ts","sourceRoot":"","sources":["../cborg.js"],"names":[],"mappings":";;;+BAOa,OAAO,gBAAgB,EAAE,gBAAgB;;;;yBACzC,OAAO,gBAAgB,EAAE,UAAU;;;;0BAEnC,OAAO,gBAAgB,EAAE,mBAAmB;;;;4BAC5C,OAAO,gBAAgB,EAAE,aAAa;;;;4BACtC,OAAO,gBAAgB,EAAE,aAAa;uBAXY,iBAAiB;4BAAjB,iBAAiB;0BAAjB,iBAAiB;+BAAjB,iBAAiB;uBADP,iBAAiB;2BAAjB,iBAAiB;+BAAjB,iBAAiB;qCAAjB,iBAAiB;uBAEnE,iBAAiB;sBACZ,gBAAgB;qBAAhB,gBAAgB"}
@@ -1 +1 @@
1
- {"version":3,"file":"encode.d.ts","sourceRoot":"","sources":["../../lib/encode.js"],"names":[],"mappings":"AAwCA,oCAAoC;AACpC,oCADc,gBAAgB,EAAE,CAY/B;AAnBD,4BAA4B;AAC5B,mCADW,aAAa,CAKtB;sBA+XW,KAAK,GAAG;IAAE,SAAS,CAAC,EAAE,UAAU,CAAA;CAAE;4BApZlC,OAAO,iBAAiB,EAAE,aAAa;kCACvC,OAAO,iBAAiB,EAAE,mBAAmB;wBAC7C,OAAO,iBAAiB,EAAE,SAAS;gCACnC,OAAO,iBAAiB,EAAE,iBAAiB;+BAC3C,OAAO,iBAAiB,EAAE,gBAAgB;kCAC1C,OAAO,iBAAiB,EAAE,mBAAmB;yBAC7C,OAAO,iBAAiB,EAAE,UAAU;AA0RjD;;;;;GAKG;AACH,oCALW,GAAG,YACH,aAAa,aACb,SAAS,GACP,mBAAmB,CAgB/B;AAiUD;;;;GAIG;AACH,6BAJW,GAAG,YACH,aAAa,GACX,UAAU,CAatB;AA3DD;;;;;;GAMG;AACH,mCANW,GAAG,YACH,gBAAgB,EAAE,WAClB,aAAa,gBACb,UAAU,GACR,UAAU,CAoCtB;AAoBD;;;;;GAKG;AACH,iCALW,GAAG,eACH,UAAU,YACV,aAAa,GACX;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,CAc/B;AAhnBD,8BAA8B;AAC9B,4BADiB,SAAS;IA0BxB;;;;OAIG;IACH,0BAJW,SAAS,GAAC,SAAS,OACnB,MAAM,GAAC,GAAG,EAAE,GACV,SAAS,CAOrB;IAlCD;;;OAGG;IACH,iBAHW,MAAM,GAAC,GAAG,EAAE,UACZ,SAAS,GAAC,SAAS,EAK7B;IAFC,oBAAc;IACd,wDAAoB;IAGtB;;;OAGG;IACH,cAHW,MAAM,GAAC,GAAG,EAAE,GACV,OAAO,CAWnB;CAaF;sBA9F2B,YAAY"}
1
+ {"version":3,"file":"encode.d.ts","sourceRoot":"","sources":["../../lib/encode.js"],"names":[],"mappings":"AAwCA,oCAAoC;AACpC,oCADc,gBAAgB,EAAE,CAY/B;AAnBD,4BAA4B;AAC5B,mCADW,aAAa,CAKtB;sBAiZW,KAAK,GAAG;IAAE,SAAS,CAAC,EAAE,UAAU,CAAA;CAAE;4BAtalC,OAAO,iBAAiB,EAAE,aAAa;kCACvC,OAAO,iBAAiB,EAAE,mBAAmB;wBAC7C,OAAO,iBAAiB,EAAE,SAAS;gCACnC,OAAO,iBAAiB,EAAE,iBAAiB;+BAC3C,OAAO,iBAAiB,EAAE,gBAAgB;kCAC1C,OAAO,iBAAiB,EAAE,mBAAmB;yBAC7C,OAAO,iBAAiB,EAAE,UAAU;AA4SjD;;;;;GAKG;AACH,oCALW,GAAG,YACH,aAAa,aACb,SAAS,GACP,mBAAmB,CAgB/B;AAiUD;;;;GAIG;AACH,6BAJW,GAAG,YACH,aAAa,GACX,UAAU,CAatB;AA3DD;;;;;;GAMG;AACH,mCANW,GAAG,YACH,gBAAgB,EAAE,WAClB,aAAa,gBACb,UAAU,GACR,UAAU,CAoCtB;AAoBD;;;;;GAKG;AACH,iCALW,GAAG,eACH,UAAU,YACV,aAAa,GACX;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,CAc/B;AAloBD,8BAA8B;AAC9B,4BADiB,SAAS;IA0BxB;;;;OAIG;IACH,0BAJW,SAAS,GAAC,SAAS,OACnB,MAAM,GAAC,GAAG,EAAE,GACV,SAAS,CAOrB;IAlCD;;;OAGG;IACH,iBAHW,MAAM,GAAC,GAAG,EAAE,UACZ,SAAS,GAAC,SAAS,EAK7B;IAFC,oBAAc;IACd,wDAAoB;IAGtB;;;OAGG;IACH,cAHW,MAAM,GAAC,GAAG,EAAE,GACV,OAAO,CAWnB;CAaF;sBA9F2B,YAAY"}
@@ -1 +1 @@
1
- {"version":3,"file":"is.d.ts","sourceRoot":"","sources":["../../lib/is.js"],"names":[],"mappings":"AAkCA;;;GAGG;AACH,0BAHW,GAAG,GACD,MAAM,CAqClB"}
1
+ {"version":3,"file":"is.d.ts","sourceRoot":"","sources":["../../lib/is.js"],"names":[],"mappings":"AAmCA;;;GAGG;AACH,0BAHW,GAAG,GACD,MAAM,CAqClB"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * @typedef {import('../interface.js').TagDecodeControl} TagDecodeControl
3
+ * @typedef {(decode: TagDecodeControl) => Tagged} TaggedTagDecoder
4
+ */
5
+ /**
6
+ * A wrapper class for representing a CBOR tag with an arbitrary nested value.
7
+ *
8
+ * `Tagged` is a symmetric primitive: it can be passed to `encode()` to emit
9
+ * a CBOR tag header followed by the encoded form of `value`, and it can be
10
+ * returned from a tag decoder (via `Tagged.decoder(tag)` or `Tagged.preserve()`)
11
+ * to round-trip a tag through decode without losing the tag number.
12
+ *
13
+ * Use `Tagged` for one-off tag handling where defining a dedicated
14
+ * `typeEncoders` entry and tag decoder pair would be heavyweight, e.g. when
15
+ * wrapping a structure in a single application-specific tag (COSE, dCBOR
16
+ * envelopes, etc.).
17
+ *
18
+ * For systematic mapping of a JS type to a tag (e.g. CID -> tag 42), prefer
19
+ * a dedicated `typeEncoders` entry instead.
20
+ */
21
+ export class Tagged {
22
+ /**
23
+ * Build a tag decoder for use in `decode()`'s `tags` option that returns the
24
+ * decoded content wrapped in a `Tagged` instance, preserving the tag number
25
+ * for the caller to inspect.
26
+ *
27
+ * @param {number} tag - The CBOR tag number this decoder will be registered for
28
+ * @returns {TaggedTagDecoder}
29
+ *
30
+ * @example
31
+ * import { decode, Tagged } from 'cborg'
32
+ * const value = decode(bytes, { tags: { 16: Tagged.decoder(16) } })
33
+ * // value instanceof Tagged; value.tag === 16
34
+ */
35
+ static decoder(tag: number): TaggedTagDecoder;
36
+ /**
37
+ * Build a `tags` option for `decode()` that wraps each listed tag number in
38
+ * a `Tagged` instance, preserving those tags through decode without
39
+ * registering a dedicated decoder per tag.
40
+ *
41
+ * @param {...number} tagNumbers - One or more CBOR tag numbers to preserve
42
+ * @returns {{[tagNumber: number]: TaggedTagDecoder}}
43
+ *
44
+ * @example
45
+ * import { decode, Tagged } from 'cborg'
46
+ * const value = decode(bytes, { tags: Tagged.preserve(16, 96) })
47
+ */
48
+ static preserve(...tagNumbers: number[]): {
49
+ [tagNumber: number]: TaggedTagDecoder;
50
+ };
51
+ /**
52
+ * @param {number} tag - CBOR tag number, a non-negative integer
53
+ * @param {any} value - The value to be tagged; encoded recursively
54
+ */
55
+ constructor(tag: number, value: any);
56
+ tag: number;
57
+ value: any;
58
+ }
59
+ export type TagDecodeControl = import("../interface.js").TagDecodeControl;
60
+ export type TaggedTagDecoder = (decode: TagDecodeControl) => Tagged;
61
+ //# sourceMappingURL=tagged.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tagged.d.ts","sourceRoot":"","sources":["../../lib/tagged.js"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;;;;;;;;;;;GAeG;AACH;IAaE;;;;;;;;;;;;OAYG;IACH,oBARW,MAAM,GACJ,gBAAgB,CAS5B;IAED;;;;;;;;;;;OAWG;IACH,+BAPc,MAAM,EAAA,GACP;QAAC,CAAC,SAAS,EAAE,MAAM,GAAG,gBAAgB,CAAA;KAAC,CAanD;IAhDD;;;OAGG;IACH,iBAHW,MAAM,SACN,GAAG,EAQb;IAFC,YAAc;IACd,WAAkB;CAwCrB;+BAtEY,OAAO,iBAAiB,EAAE,gBAAgB;+BAC1C,CAAC,MAAM,EAAE,gBAAgB,KAAK,MAAM"}
@@ -1 +1 @@
1
- {"root":["../cborg.js","../interface.ts","../lib/0uint.js","../lib/1negint.js","../lib/2bytes.js","../lib/3string.js","../lib/4array.js","../lib/5map.js","../lib/6tag.js","../lib/7float.js","../lib/bl.js","../lib/byte-utils.js","../lib/common.js","../lib/decode.js","../lib/diagnostic.js","../lib/diagnostic_test.js","../lib/encode.js","../lib/is.js","../lib/jump.js","../lib/length.js","../lib/taglib.js","../lib/token.js","../lib/extended/extended.js","../lib/json/decode.js","../lib/json/encode.js","../lib/json/json.js"],"version":"6.0.2"}
1
+ {"root":["../cborg.js","../interface.ts","../lib/0uint.js","../lib/1negint.js","../lib/2bytes.js","../lib/3string.js","../lib/4array.js","../lib/5map.js","../lib/6tag.js","../lib/7float.js","../lib/bl.js","../lib/byte-utils.js","../lib/common.js","../lib/decode.js","../lib/diagnostic.js","../lib/diagnostic_test.js","../lib/encode.js","../lib/is.js","../lib/jump.js","../lib/length.js","../lib/tagged.js","../lib/taglib.js","../lib/token.js","../lib/extended/extended.js","../lib/json/decode.js","../lib/json/encode.js","../lib/json/json.js"],"version":"6.0.2"}