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/lib/taglib.js
ADDED
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
import { Token, Type } from '../cborg.js'
|
|
2
|
+
import { objectToTokens, Ref } from './encode.js'
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
A collection of standard CBOR tags for extended JavaScript type support.
|
|
6
|
+
|
|
7
|
+
There are no tags included by default in the cborg encoder or decoder, you have
|
|
8
|
+
to include them by passing options. `typeEncoders` for encode() and `tags` for
|
|
9
|
+
decode().
|
|
10
|
+
|
|
11
|
+
The encoders here can be included with these options (see the tests for how this
|
|
12
|
+
can be done), or as examples for writing additional tags.
|
|
13
|
+
|
|
14
|
+
For convenience, cborg/extended provides a pre-configured encode/decode that
|
|
15
|
+
includes all of these, with type support similar to the browser's structured
|
|
16
|
+
clone algorithm.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
// =============================================================================
|
|
20
|
+
// Tag Constants
|
|
21
|
+
// =============================================================================
|
|
22
|
+
|
|
23
|
+
// Standard Tags (RFC 8949)
|
|
24
|
+
export const TAG_DATE_STRING = 0 // RFC 3339 date/time string
|
|
25
|
+
export const TAG_DATE_EPOCH = 1 // Epoch-based date/time (integer or float)
|
|
26
|
+
export const TAG_BIGINT_POS = 2 // Unsigned bignum
|
|
27
|
+
export const TAG_BIGINT_NEG = 3 // Negative bignum
|
|
28
|
+
|
|
29
|
+
// TypedArray Tags (RFC 8746) - Single-byte arrays (no endianness)
|
|
30
|
+
export const TAG_UINT8_ARRAY = 64
|
|
31
|
+
export const TAG_UINT8_CLAMPED_ARRAY = 68
|
|
32
|
+
export const TAG_INT8_ARRAY = 72
|
|
33
|
+
|
|
34
|
+
// TypedArray Tags (RFC 8746) - Little-endian multi-byte arrays
|
|
35
|
+
export const TAG_UINT16_ARRAY_LE = 69
|
|
36
|
+
export const TAG_UINT32_ARRAY_LE = 70
|
|
37
|
+
export const TAG_BIGUINT64_ARRAY_LE = 71
|
|
38
|
+
export const TAG_INT16_ARRAY_LE = 77
|
|
39
|
+
export const TAG_INT32_ARRAY_LE = 78
|
|
40
|
+
export const TAG_BIGINT64_ARRAY_LE = 79
|
|
41
|
+
export const TAG_FLOAT32_ARRAY_LE = 85
|
|
42
|
+
export const TAG_FLOAT64_ARRAY_LE = 86
|
|
43
|
+
|
|
44
|
+
// Generic Object Tag (IANA Registry)
|
|
45
|
+
export const TAG_OBJECT_CLASS = 27 // Serialised object with class name and constructor arguments
|
|
46
|
+
|
|
47
|
+
// Extended Tags (IANA Registry)
|
|
48
|
+
export const TAG_SET = 258 // Mathematical finite set
|
|
49
|
+
export const TAG_MAP = 259 // Map datatype
|
|
50
|
+
export const TAG_REGEXP = 21066 // ECMAScript RegExp
|
|
51
|
+
|
|
52
|
+
// =============================================================================
|
|
53
|
+
// BigInt (Tags 2/3) - RFC 8949 Section 3.4.3
|
|
54
|
+
// =============================================================================
|
|
55
|
+
|
|
56
|
+
const neg1b = BigInt(-1)
|
|
57
|
+
const pos1b = BigInt(1)
|
|
58
|
+
const zerob = BigInt(0)
|
|
59
|
+
const eightb = BigInt(8)
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Decode a positive bignum from bytes (Tag 2)
|
|
63
|
+
* @param {import('../interface.js').TagDecodeControl} decode
|
|
64
|
+
* @returns {bigint}
|
|
65
|
+
*/
|
|
66
|
+
export function bigIntDecoder (decode) {
|
|
67
|
+
const bytes = /** @type {Uint8Array} */ (decode())
|
|
68
|
+
let bi = zerob
|
|
69
|
+
for (let ii = 0; ii < bytes.length; ii++) {
|
|
70
|
+
bi = (bi << eightb) + BigInt(bytes[ii])
|
|
71
|
+
}
|
|
72
|
+
return bi
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Convert a BigInt to bytes
|
|
77
|
+
* @param {bigint} bi
|
|
78
|
+
* @returns {Uint8Array}
|
|
79
|
+
*/
|
|
80
|
+
const ffb = BigInt(0xff)
|
|
81
|
+
/**
|
|
82
|
+
* @param {bigint} bi
|
|
83
|
+
*/
|
|
84
|
+
function fromBigInt (bi) {
|
|
85
|
+
const buf = []
|
|
86
|
+
while (bi > 0) {
|
|
87
|
+
// Use BigInt operations to avoid Number precision loss
|
|
88
|
+
buf.unshift(Number(bi & ffb))
|
|
89
|
+
bi >>= eightb
|
|
90
|
+
}
|
|
91
|
+
return Uint8Array.from(buf.length ? buf : [0])
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// For IPLD compatibility: only tag BigInts outside 64-bit range
|
|
95
|
+
const maxSafeBigInt = BigInt('18446744073709551615') // 2^64 - 1
|
|
96
|
+
const minSafeBigInt = BigInt('-18446744073709551616') // -2^64
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Encode a BigInt, only using tags for values outside 64-bit range (IPLD compatible)
|
|
100
|
+
* @param {bigint} obj
|
|
101
|
+
* @returns {Token[]|null}
|
|
102
|
+
*/
|
|
103
|
+
export function bigIntEncoder (obj) {
|
|
104
|
+
if (obj >= minSafeBigInt && obj <= maxSafeBigInt) {
|
|
105
|
+
return null // null = encode as native CBOR integer
|
|
106
|
+
}
|
|
107
|
+
return [
|
|
108
|
+
new Token(Type.tag, obj >= zerob ? TAG_BIGINT_POS : TAG_BIGINT_NEG),
|
|
109
|
+
new Token(Type.bytes, fromBigInt(obj >= zerob ? obj : obj * neg1b - pos1b))
|
|
110
|
+
]
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Encode a BigInt, always using tags 2/3 (for extended mode, full round-trip fidelity)
|
|
115
|
+
* @param {bigint} obj
|
|
116
|
+
* @returns {Token[]}
|
|
117
|
+
*/
|
|
118
|
+
export function structBigIntEncoder (obj) {
|
|
119
|
+
return [
|
|
120
|
+
new Token(Type.tag, obj >= zerob ? TAG_BIGINT_POS : TAG_BIGINT_NEG),
|
|
121
|
+
new Token(Type.bytes, fromBigInt(obj >= zerob ? obj : obj * neg1b - pos1b))
|
|
122
|
+
]
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Decode a negative bignum from bytes (Tag 3)
|
|
127
|
+
* @param {import('../interface.js').TagDecodeControl} decode
|
|
128
|
+
* @returns {bigint}
|
|
129
|
+
*/
|
|
130
|
+
export function bigNegIntDecoder (decode) {
|
|
131
|
+
const bytes = /** @type {Uint8Array} */ (decode())
|
|
132
|
+
let bi = zerob
|
|
133
|
+
for (let ii = 0; ii < bytes.length; ii++) {
|
|
134
|
+
bi = (bi << eightb) + BigInt(bytes[ii])
|
|
135
|
+
}
|
|
136
|
+
return neg1b - bi
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// =============================================================================
|
|
140
|
+
// Date (Tag 1) - RFC 8949 Section 3.4.2
|
|
141
|
+
// =============================================================================
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Encode a Date as Tag 1 (epoch seconds as float)
|
|
145
|
+
* @param {Date} date
|
|
146
|
+
* @returns {Token[]}
|
|
147
|
+
*/
|
|
148
|
+
export function dateEncoder (date) {
|
|
149
|
+
// Use float for millisecond precision
|
|
150
|
+
const seconds = date.getTime() / 1000
|
|
151
|
+
return [
|
|
152
|
+
new Token(Type.tag, TAG_DATE_EPOCH),
|
|
153
|
+
new Token(Type.float, seconds)
|
|
154
|
+
]
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Decode Tag 1 (epoch seconds) to a Date
|
|
159
|
+
* @param {import('../interface.js').TagDecodeControl} decode
|
|
160
|
+
* @returns {Date}
|
|
161
|
+
*/
|
|
162
|
+
export function dateDecoder (decode) {
|
|
163
|
+
const seconds = /** @type {number} */ (decode())
|
|
164
|
+
return new Date(seconds * 1000)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// =============================================================================
|
|
168
|
+
// RegExp (Tag 21066) - IANA Registry
|
|
169
|
+
// =============================================================================
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Encode a RegExp as Tag 21066
|
|
173
|
+
* @param {RegExp} re
|
|
174
|
+
* @returns {Token[]}
|
|
175
|
+
*/
|
|
176
|
+
export function regExpEncoder (re) {
|
|
177
|
+
if (re.flags) {
|
|
178
|
+
return [
|
|
179
|
+
new Token(Type.tag, TAG_REGEXP),
|
|
180
|
+
new Token(Type.array, 2),
|
|
181
|
+
new Token(Type.string, re.source),
|
|
182
|
+
new Token(Type.string, re.flags)
|
|
183
|
+
]
|
|
184
|
+
}
|
|
185
|
+
return [
|
|
186
|
+
new Token(Type.tag, TAG_REGEXP),
|
|
187
|
+
new Token(Type.array, 1),
|
|
188
|
+
new Token(Type.string, re.source)
|
|
189
|
+
]
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Decode Tag 21066 to a RegExp
|
|
194
|
+
* @param {import('../interface.js').TagDecodeControl} decode
|
|
195
|
+
* @returns {RegExp}
|
|
196
|
+
*/
|
|
197
|
+
export function regExpDecoder (decode) {
|
|
198
|
+
const val = /** @type {string[]|string} */ (decode())
|
|
199
|
+
if (Array.isArray(val)) {
|
|
200
|
+
return new RegExp(val[0], val[1] || '')
|
|
201
|
+
}
|
|
202
|
+
return new RegExp(val)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// =============================================================================
|
|
206
|
+
// Set (Tag 258) - IANA Registry
|
|
207
|
+
// =============================================================================
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Encode a Set as Tag 258 + array
|
|
211
|
+
* This is a typeEncoder, receives (obj, typ, options, refStack)
|
|
212
|
+
* @param {Set<any>} set
|
|
213
|
+
* @param {string} _typ
|
|
214
|
+
* @param {import('../interface.js').EncodeOptions} options
|
|
215
|
+
* @param {import('../interface.js').Reference} [refStack]
|
|
216
|
+
* @returns {import('../interface.js').TokenOrNestedTokens[]}
|
|
217
|
+
*/
|
|
218
|
+
export function setEncoder (set, _typ, options, refStack) {
|
|
219
|
+
if (set.size === 0) {
|
|
220
|
+
return [
|
|
221
|
+
new Token(Type.tag, TAG_SET),
|
|
222
|
+
new Token(Type.array, 0)
|
|
223
|
+
]
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
refStack = Ref.createCheck(refStack, set)
|
|
227
|
+
const values = []
|
|
228
|
+
for (const v of set) {
|
|
229
|
+
values.push(objectToTokens(v, options, refStack))
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return [
|
|
233
|
+
new Token(Type.tag, TAG_SET),
|
|
234
|
+
new Token(Type.array, set.size),
|
|
235
|
+
values
|
|
236
|
+
]
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Decode Tag 258 to a Set
|
|
241
|
+
* @param {import('../interface.js').TagDecodeControl} decode
|
|
242
|
+
* @returns {Set<any>}
|
|
243
|
+
*/
|
|
244
|
+
export function setDecoder (decode) {
|
|
245
|
+
const val = /** @type {any[]} */ (decode())
|
|
246
|
+
return new Set(val)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// =============================================================================
|
|
250
|
+
// Map (Tag 259) - IANA Registry
|
|
251
|
+
// Tag 259 wraps a CBOR map to indicate it should decode as a JS Map
|
|
252
|
+
// =============================================================================
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Encode a Map as Tag 259 + CBOR map
|
|
256
|
+
* This is a typeEncoder, receives (obj, typ, options, refStack)
|
|
257
|
+
* @param {Map<any, any>} map
|
|
258
|
+
* @param {string} _typ
|
|
259
|
+
* @param {import('../interface.js').EncodeOptions} options
|
|
260
|
+
* @param {import('../interface.js').Reference} [refStack]
|
|
261
|
+
* @returns {import('../interface.js').TokenOrNestedTokens[]}
|
|
262
|
+
*/
|
|
263
|
+
export function mapEncoder (map, _typ, options, refStack) {
|
|
264
|
+
if (map.size === 0) {
|
|
265
|
+
return [
|
|
266
|
+
new Token(Type.tag, TAG_MAP),
|
|
267
|
+
new Token(Type.map, 0)
|
|
268
|
+
]
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
refStack = Ref.createCheck(refStack, map)
|
|
272
|
+
const entries = []
|
|
273
|
+
for (const [key, value] of map) {
|
|
274
|
+
entries.push([
|
|
275
|
+
objectToTokens(key, options, refStack),
|
|
276
|
+
objectToTokens(value, options, refStack)
|
|
277
|
+
])
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Sort entries if mapSorter is provided (for deterministic encoding)
|
|
281
|
+
if (options.mapSorter) {
|
|
282
|
+
entries.sort(options.mapSorter)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return [
|
|
286
|
+
new Token(Type.tag, TAG_MAP),
|
|
287
|
+
new Token(Type.map, map.size),
|
|
288
|
+
entries
|
|
289
|
+
]
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Decode Tag 259 to a Map
|
|
294
|
+
* Uses decode.entries() to preserve key types (integers, etc.) regardless of useMaps setting
|
|
295
|
+
* @param {import('../interface.js').TagDecodeControl} decode
|
|
296
|
+
* @returns {Map<any, any>}
|
|
297
|
+
*/
|
|
298
|
+
export function mapDecoder (decode) {
|
|
299
|
+
return new Map(decode.entries())
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// =============================================================================
|
|
303
|
+
// TypedArrays (Tags 64-87) - RFC 8746
|
|
304
|
+
// Uses little-endian tags for multi-byte arrays (JS native byte order)
|
|
305
|
+
// =============================================================================
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Helper to create a TypedArray from an ArrayBuffer
|
|
309
|
+
* @template {ArrayBufferView} T
|
|
310
|
+
* @param {new (buffer: ArrayBuffer) => T} TypedArrayClass
|
|
311
|
+
* @returns {(decode: import('../interface.js').TagDecodeControl) => T}
|
|
312
|
+
*/
|
|
313
|
+
function createTypedArrayDecoder (TypedArrayClass) {
|
|
314
|
+
return function (decode) {
|
|
315
|
+
const bytes = /** @type {Uint8Array} */ (decode())
|
|
316
|
+
// bytes is a Uint8Array, need to get properly sliced ArrayBuffer
|
|
317
|
+
const buffer = /** @type {ArrayBuffer} */ (bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength))
|
|
318
|
+
return new TypedArrayClass(buffer)
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Helper to create a TypedArray encoder
|
|
324
|
+
* @param {number} tag
|
|
325
|
+
* @returns {(arr: ArrayBufferView) => Token[]}
|
|
326
|
+
*/
|
|
327
|
+
function createTypedArrayEncoder (tag) {
|
|
328
|
+
return function (arr) {
|
|
329
|
+
// Get the bytes from the TypedArray's underlying buffer
|
|
330
|
+
const bytes = new Uint8Array(arr.buffer, arr.byteOffset, arr.byteLength)
|
|
331
|
+
return [
|
|
332
|
+
new Token(Type.tag, tag),
|
|
333
|
+
new Token(Type.bytes, bytes)
|
|
334
|
+
]
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Uint8Array (Tag 64), no endianness concerns
|
|
339
|
+
export const uint8ArrayEncoder = createTypedArrayEncoder(TAG_UINT8_ARRAY)
|
|
340
|
+
export const uint8ArrayDecoder = createTypedArrayDecoder(Uint8Array)
|
|
341
|
+
|
|
342
|
+
// Uint8ClampedArray (Tag 68), no endianness concerns
|
|
343
|
+
export const uint8ClampedArrayEncoder = createTypedArrayEncoder(TAG_UINT8_CLAMPED_ARRAY)
|
|
344
|
+
export const uint8ClampedArrayDecoder = createTypedArrayDecoder(Uint8ClampedArray)
|
|
345
|
+
|
|
346
|
+
// Int8Array (Tag 72), no endianness concerns
|
|
347
|
+
export const int8ArrayEncoder = createTypedArrayEncoder(TAG_INT8_ARRAY)
|
|
348
|
+
export const int8ArrayDecoder = createTypedArrayDecoder(Int8Array)
|
|
349
|
+
|
|
350
|
+
// Uint16Array (Tag 69, little endian)
|
|
351
|
+
export const uint16ArrayEncoder = createTypedArrayEncoder(TAG_UINT16_ARRAY_LE)
|
|
352
|
+
export const uint16ArrayDecoder = createTypedArrayDecoder(Uint16Array)
|
|
353
|
+
|
|
354
|
+
// Uint32Array (Tag 70, little endian)
|
|
355
|
+
export const uint32ArrayEncoder = createTypedArrayEncoder(TAG_UINT32_ARRAY_LE)
|
|
356
|
+
export const uint32ArrayDecoder = createTypedArrayDecoder(Uint32Array)
|
|
357
|
+
|
|
358
|
+
// BigUint64Array (Tag 71, little endian)
|
|
359
|
+
export const bigUint64ArrayEncoder = createTypedArrayEncoder(TAG_BIGUINT64_ARRAY_LE)
|
|
360
|
+
export const bigUint64ArrayDecoder = createTypedArrayDecoder(BigUint64Array)
|
|
361
|
+
|
|
362
|
+
// Int16Array (Tag 77, little endian)
|
|
363
|
+
export const int16ArrayEncoder = createTypedArrayEncoder(TAG_INT16_ARRAY_LE)
|
|
364
|
+
export const int16ArrayDecoder = createTypedArrayDecoder(Int16Array)
|
|
365
|
+
|
|
366
|
+
// Int32Array (Tag 78, little endian)
|
|
367
|
+
export const int32ArrayEncoder = createTypedArrayEncoder(TAG_INT32_ARRAY_LE)
|
|
368
|
+
export const int32ArrayDecoder = createTypedArrayDecoder(Int32Array)
|
|
369
|
+
|
|
370
|
+
// BigInt64Array (Tag 79, little endian)
|
|
371
|
+
export const bigInt64ArrayEncoder = createTypedArrayEncoder(TAG_BIGINT64_ARRAY_LE)
|
|
372
|
+
export const bigInt64ArrayDecoder = createTypedArrayDecoder(BigInt64Array)
|
|
373
|
+
|
|
374
|
+
// Float32Array (Tag 85, little endian)
|
|
375
|
+
export const float32ArrayEncoder = createTypedArrayEncoder(TAG_FLOAT32_ARRAY_LE)
|
|
376
|
+
export const float32ArrayDecoder = createTypedArrayDecoder(Float32Array)
|
|
377
|
+
|
|
378
|
+
// Float64Array (Tag 86, little endian)
|
|
379
|
+
export const float64ArrayEncoder = createTypedArrayEncoder(TAG_FLOAT64_ARRAY_LE)
|
|
380
|
+
export const float64ArrayDecoder = createTypedArrayDecoder(Float64Array)
|
|
381
|
+
|
|
382
|
+
// =============================================================================
|
|
383
|
+
// Error (Tag 27) - IANA "object with class name and constructor arguments"
|
|
384
|
+
// Format: Tag 27: [className, message, options?]
|
|
385
|
+
// =============================================================================
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Known JavaScript Error constructors
|
|
389
|
+
* @type {Record<string, ErrorConstructor>}
|
|
390
|
+
*/
|
|
391
|
+
const errorConstructors = {
|
|
392
|
+
Error,
|
|
393
|
+
EvalError,
|
|
394
|
+
RangeError,
|
|
395
|
+
ReferenceError,
|
|
396
|
+
SyntaxError,
|
|
397
|
+
TypeError,
|
|
398
|
+
URIError
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Encode an Error as Tag 27: [className, message]
|
|
403
|
+
* @param {Error} err
|
|
404
|
+
* @returns {Token[]}
|
|
405
|
+
*/
|
|
406
|
+
export function errorEncoder (err) {
|
|
407
|
+
const className = err.name
|
|
408
|
+
// Only encode name and message (not stack, which is environment-specific)
|
|
409
|
+
return [
|
|
410
|
+
new Token(Type.tag, TAG_OBJECT_CLASS),
|
|
411
|
+
new Token(Type.array, 2),
|
|
412
|
+
new Token(Type.string, className),
|
|
413
|
+
new Token(Type.string, err.message)
|
|
414
|
+
]
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Decode Tag 27 to an Error (or Error subclass)
|
|
419
|
+
* @param {import('../interface.js').TagDecodeControl} decode
|
|
420
|
+
* @returns {Error}
|
|
421
|
+
*/
|
|
422
|
+
export function errorDecoder (decode) {
|
|
423
|
+
const arr = /** @type {[string, string]} */ (decode())
|
|
424
|
+
const [className, message] = arr
|
|
425
|
+
const Ctor = errorConstructors[className] || Error
|
|
426
|
+
const err = new Ctor(message)
|
|
427
|
+
// If the constructor doesn't match (e.g., custom error name), set the name
|
|
428
|
+
if (err.name !== className && className in errorConstructors) {
|
|
429
|
+
err.name = className
|
|
430
|
+
}
|
|
431
|
+
return err
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// =============================================================================
|
|
435
|
+
// Negative Zero (-0) Support
|
|
436
|
+
// CBOR can represent -0 as a float, but by default numbers encode as integers.
|
|
437
|
+
// This encoder ensures -0 is preserved by encoding it as a float.
|
|
438
|
+
// =============================================================================
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Encode a number, preserving -0 as a float
|
|
442
|
+
* Use this as a typeEncoder for 'number' to preserve -0 fidelity
|
|
443
|
+
* @param {number} num
|
|
444
|
+
* @returns {Token[] | null}
|
|
445
|
+
*/
|
|
446
|
+
export function negativeZeroEncoder (num) {
|
|
447
|
+
if (Object.is(num, -0)) {
|
|
448
|
+
return [new Token(Type.float, -0)]
|
|
449
|
+
}
|
|
450
|
+
// Return null to fall through to default number encoding
|
|
451
|
+
return null
|
|
452
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cborg",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"description": "Fast CBOR with a focus on strictness",
|
|
5
5
|
"main": "cborg.js",
|
|
6
6
|
"type": "module",
|
|
@@ -13,8 +13,9 @@
|
|
|
13
13
|
"build:types": "tsc --build",
|
|
14
14
|
"prepublishOnly": "npm run build",
|
|
15
15
|
"test:node": "c8 --check-coverage --exclude=test/** mocha test/test-*.js",
|
|
16
|
+
"test:node-bin": "mocha test/node-test-bin.js",
|
|
16
17
|
"test:browser": "polendina --cleanup test/test-*.js",
|
|
17
|
-
"test": "npm run lint && npm run build && npm run test:node && npm run test:browser",
|
|
18
|
+
"test": "npm run lint && npm run build && npm run test:node && npm run test:node-bin && npm run test:browser",
|
|
18
19
|
"test:ci": "npm run test",
|
|
19
20
|
"coverage": "c8 --reporter=html --reporter=text mocha test/test-*.js && npx st -d coverage -p 8888"
|
|
20
21
|
},
|
|
@@ -29,23 +30,23 @@
|
|
|
29
30
|
"license": "Apache-2.0",
|
|
30
31
|
"devDependencies": {
|
|
31
32
|
"@semantic-release/changelog": "^6.0.3",
|
|
32
|
-
"@semantic-release/commit-analyzer": "^13.0.
|
|
33
|
+
"@semantic-release/commit-analyzer": "^13.0.1",
|
|
33
34
|
"@semantic-release/git": "^10.0.1",
|
|
34
|
-
"@semantic-release/github": "^12.0.
|
|
35
|
-
"@semantic-release/npm": "^13.
|
|
36
|
-
"@semantic-release/release-notes-generator": "^14.0
|
|
37
|
-
"@types/chai": "^5.
|
|
38
|
-
"@types/mocha": "^10.0.
|
|
39
|
-
"@types/node": "^25.
|
|
40
|
-
"c8": "^
|
|
41
|
-
"chai": "^6.
|
|
42
|
-
"conventional-changelog-conventionalcommits": "^9.
|
|
35
|
+
"@semantic-release/github": "^12.0.6",
|
|
36
|
+
"@semantic-release/npm": "^13.1.5",
|
|
37
|
+
"@semantic-release/release-notes-generator": "^14.1.0",
|
|
38
|
+
"@types/chai": "^5.2.3",
|
|
39
|
+
"@types/mocha": "^10.0.10",
|
|
40
|
+
"@types/node": "^25.5.0",
|
|
41
|
+
"c8": "^11.0.0",
|
|
42
|
+
"chai": "^6.2.2",
|
|
43
|
+
"conventional-changelog-conventionalcommits": "^9.3.0",
|
|
43
44
|
"ipld-garbage": "^5.0.0",
|
|
44
|
-
"mocha": "^11.
|
|
45
|
-
"polendina": "^3.2.
|
|
46
|
-
"semantic-release": "^25.0.
|
|
45
|
+
"mocha": "^11.7.5",
|
|
46
|
+
"polendina": "^3.2.20",
|
|
47
|
+
"semantic-release": "^25.0.3",
|
|
47
48
|
"standard": "^17.1.2",
|
|
48
|
-
"typescript": "^
|
|
49
|
+
"typescript": "^6.0.2"
|
|
49
50
|
},
|
|
50
51
|
"exports": {
|
|
51
52
|
".": {
|
|
@@ -57,13 +58,17 @@
|
|
|
57
58
|
"types": "./types/lib/length.d.ts"
|
|
58
59
|
},
|
|
59
60
|
"./taglib": {
|
|
60
|
-
"import": "./taglib.js",
|
|
61
|
-
"types": "./types/taglib.d.ts"
|
|
61
|
+
"import": "./lib/taglib.js",
|
|
62
|
+
"types": "./types/lib/taglib.d.ts"
|
|
62
63
|
},
|
|
63
64
|
"./json": {
|
|
64
65
|
"import": "./lib/json/json.js",
|
|
65
66
|
"types": "./types/lib/json/json.d.ts"
|
|
66
67
|
},
|
|
68
|
+
"./extended": {
|
|
69
|
+
"import": "./lib/extended/extended.js",
|
|
70
|
+
"types": "./types/lib/extended/extended.d.ts"
|
|
71
|
+
},
|
|
67
72
|
"./interface": {
|
|
68
73
|
"types": "./types/interface.d.ts"
|
|
69
74
|
}
|
package/test/common.js
CHANGED
package/test/node-test-bin.js
CHANGED
|
@@ -97,17 +97,17 @@ describe('Bin', () => {
|
|
|
97
97
|
assert.strictEqual(e.stderr,
|
|
98
98
|
`Usage: cborg <command> <args>
|
|
99
99
|
Valid commands:
|
|
100
|
-
\tbin2diag [binary input]
|
|
100
|
+
\tbin2diag [--width <width>] [binary input]
|
|
101
101
|
\tbin2hex [binary input]
|
|
102
102
|
\tbin2json [--pretty] [binary input]
|
|
103
103
|
\tdiag2bin [diagnostic input]
|
|
104
104
|
\tdiag2hex [diagnostic input]
|
|
105
105
|
\tdiag2json [--pretty] [diagnostic input]
|
|
106
106
|
\thex2bin [hex input]
|
|
107
|
-
\thex2diag [hex input]
|
|
107
|
+
\thex2diag [--width <width>] [hex input]
|
|
108
108
|
\thex2json [--pretty] [hex input]
|
|
109
109
|
\tjson2bin '[json input]'
|
|
110
|
-
\tjson2diag '[json input]'
|
|
110
|
+
\tjson2diag [--width <width>] '[json input]'
|
|
111
111
|
\tjson2hex '[json input]'
|
|
112
112
|
Input may either be supplied as an argument or piped via stdin
|
|
113
113
|
`)
|
|
@@ -124,17 +124,17 @@ Input may either be supplied as an argument or piped via stdin
|
|
|
124
124
|
`Unknown command: 'blip'
|
|
125
125
|
Usage: cborg <command> <args>
|
|
126
126
|
Valid commands:
|
|
127
|
-
\tbin2diag [binary input]
|
|
127
|
+
\tbin2diag [--width <width>] [binary input]
|
|
128
128
|
\tbin2hex [binary input]
|
|
129
129
|
\tbin2json [--pretty] [binary input]
|
|
130
130
|
\tdiag2bin [diagnostic input]
|
|
131
131
|
\tdiag2hex [diagnostic input]
|
|
132
132
|
\tdiag2json [--pretty] [diagnostic input]
|
|
133
133
|
\thex2bin [hex input]
|
|
134
|
-
\thex2diag [hex input]
|
|
134
|
+
\thex2diag [--width <width>] [hex input]
|
|
135
135
|
\thex2json [--pretty] [hex input]
|
|
136
136
|
\tjson2bin '[json input]'
|
|
137
|
-
\tjson2diag '[json input]'
|
|
137
|
+
\tjson2diag [--width <width>] '[json input]'
|
|
138
138
|
\tjson2hex '[json input]'
|
|
139
139
|
Input may either be supplied as an argument or piped via stdin
|
|
140
140
|
`)
|
|
@@ -147,17 +147,17 @@ Input may either be supplied as an argument or piped via stdin
|
|
|
147
147
|
assert.strictEqual(stderr,
|
|
148
148
|
`Usage: cborg <command> <args>
|
|
149
149
|
Valid commands:
|
|
150
|
-
\tbin2diag [binary input]
|
|
150
|
+
\tbin2diag [--width <width>] [binary input]
|
|
151
151
|
\tbin2hex [binary input]
|
|
152
152
|
\tbin2json [--pretty] [binary input]
|
|
153
153
|
\tdiag2bin [diagnostic input]
|
|
154
154
|
\tdiag2hex [diagnostic input]
|
|
155
155
|
\tdiag2json [--pretty] [diagnostic input]
|
|
156
156
|
\thex2bin [hex input]
|
|
157
|
-
\thex2diag [hex input]
|
|
157
|
+
\thex2diag [--width <width>] [hex input]
|
|
158
158
|
\thex2json [--pretty] [hex input]
|
|
159
159
|
\tjson2bin '[json input]'
|
|
160
|
-
\tjson2diag '[json input]'
|
|
160
|
+
\tjson2diag [--width <width>] '[json input]'
|
|
161
161
|
\tjson2hex '[json input]'
|
|
162
162
|
Input may either be supplied as an argument or piped via stdin
|
|
163
163
|
`)
|
|
@@ -341,6 +341,23 @@ Input may either be supplied as an argument or piped via stdin
|
|
|
341
341
|
})
|
|
342
342
|
|
|
343
343
|
describe('diag length bytes', () => {
|
|
344
|
+
// issue #137 - array and map length bytes were missing for lengths >= 24
|
|
345
|
+
it('array with 24 elements', async () => {
|
|
346
|
+
// 98 18 = array(24), then 24 uint(1) values
|
|
347
|
+
const hex = '9818' + '01'.repeat(24)
|
|
348
|
+
const { stdout, stderr } = await execBin(`hex2diag ${hex}`)
|
|
349
|
+
assert.strictEqual(stderr, '')
|
|
350
|
+
assert.ok(stdout.startsWith('98 18 # array(24)\n'))
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
it('map with 24 entries', async () => {
|
|
354
|
+
// b8 18 = map(24), then 24 pairs of string('a')=uint(1)
|
|
355
|
+
const hex = 'b818' + '616101'.repeat(24)
|
|
356
|
+
const { stdout, stderr } = await execBin(`hex2diag ${hex}`)
|
|
357
|
+
assert.strictEqual(stderr, '')
|
|
358
|
+
assert.ok(stdout.startsWith('b8 18 # map(24)\n'))
|
|
359
|
+
})
|
|
360
|
+
|
|
344
361
|
it('compact', async () => {
|
|
345
362
|
const { stdout, stderr } = await execBin('json2diag', '"aaaaaaaaaaaaaaaaaaaaaaa"')
|
|
346
363
|
assert.strictEqual(stderr, '')
|
package/test/test-6tag.js
CHANGED
|
@@ -17,7 +17,8 @@ const encodeIntoBytes = (data, dest, options) => {
|
|
|
17
17
|
|
|
18
18
|
const fixedDest = new Uint8Array(1024)
|
|
19
19
|
|
|
20
|
-
function Uint16ArrayDecoder (
|
|
20
|
+
function Uint16ArrayDecoder (decode) {
|
|
21
|
+
const obj = decode()
|
|
21
22
|
if (typeof obj !== 'string') {
|
|
22
23
|
throw new Error('expected string for tag 23')
|
|
23
24
|
}
|
|
@@ -21,14 +21,16 @@ const fixedDest = new Uint8Array(1024)
|
|
|
21
21
|
const tags = []
|
|
22
22
|
const typeEncoders = {}
|
|
23
23
|
|
|
24
|
-
tags[0] = function (
|
|
24
|
+
tags[0] = function (decode) {
|
|
25
|
+
const obj = decode()
|
|
25
26
|
if (typeof obj !== 'string') {
|
|
26
27
|
throw new Error('expected string for tag 1')
|
|
27
28
|
}
|
|
28
29
|
return `0("${new Date(obj).toISOString().replace(/\.000Z$/, 'Z')}")`
|
|
29
30
|
}
|
|
30
31
|
|
|
31
|
-
tags[1] = function (
|
|
32
|
+
tags[1] = function (decode) {
|
|
33
|
+
const obj = decode()
|
|
32
34
|
if (typeof obj !== 'number') {
|
|
33
35
|
throw new Error('expected number for tag 1')
|
|
34
36
|
}
|
|
@@ -39,19 +41,25 @@ tags[2] = taglib.bigIntDecoder
|
|
|
39
41
|
typeEncoders.bigint = taglib.bigIntEncoder
|
|
40
42
|
tags[3] = taglib.bigNegIntDecoder
|
|
41
43
|
|
|
42
|
-
tags[23] = function (
|
|
44
|
+
tags[23] = function (decode) {
|
|
43
45
|
// expected conversion to base16
|
|
46
|
+
const obj = decode()
|
|
44
47
|
if (!(obj instanceof Uint8Array)) {
|
|
45
48
|
throw new Error('expected byte array for tag 23')
|
|
46
49
|
}
|
|
47
50
|
return `23(h'${toHex(obj)}')`
|
|
48
51
|
}
|
|
49
52
|
|
|
50
|
-
tags[24] = function (
|
|
51
|
-
|
|
53
|
+
tags[24] = function (decode) { // embedded cbor, oh my
|
|
54
|
+
const obj = decode()
|
|
55
|
+
if (!(obj instanceof Uint8Array)) {
|
|
56
|
+
throw new Error('expected byte array for tag 24')
|
|
57
|
+
}
|
|
58
|
+
return `24(h'${toHex(obj)}')`
|
|
52
59
|
}
|
|
53
60
|
|
|
54
|
-
tags[32] = function (
|
|
61
|
+
tags[32] = function (decode) { // url
|
|
62
|
+
const obj = decode()
|
|
55
63
|
if (typeof obj !== 'string') {
|
|
56
64
|
throw new Error('expected string for tag 32')
|
|
57
65
|
}
|