@marcuspuchalla/nachos 0.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.
Files changed (100) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/LICENSE +674 -0
  3. package/README.md +345 -0
  4. package/dist/chunk-2FUTHZQQ.cjs +755 -0
  5. package/dist/chunk-2FUTHZQQ.cjs.map +1 -0
  6. package/dist/chunk-2HBCILJS.cjs +2034 -0
  7. package/dist/chunk-2HBCILJS.cjs.map +1 -0
  8. package/dist/chunk-7CFYWHS6.js +742 -0
  9. package/dist/chunk-7CFYWHS6.js.map +1 -0
  10. package/dist/chunk-PD72MVTX.cjs +160 -0
  11. package/dist/chunk-PD72MVTX.cjs.map +1 -0
  12. package/dist/chunk-ZDZ2B5PE.js +149 -0
  13. package/dist/chunk-ZDZ2B5PE.js.map +1 -0
  14. package/dist/chunk-ZRPJUEIZ.js +2020 -0
  15. package/dist/chunk-ZRPJUEIZ.js.map +1 -0
  16. package/dist/encoder/index.cjs +57 -0
  17. package/dist/encoder/index.cjs.map +1 -0
  18. package/dist/encoder/index.d.cts +72 -0
  19. package/dist/encoder/index.d.ts +72 -0
  20. package/dist/encoder/index.js +4 -0
  21. package/dist/encoder/index.js.map +1 -0
  22. package/dist/index.cjs +606 -0
  23. package/dist/index.cjs.map +1 -0
  24. package/dist/index.d.cts +494 -0
  25. package/dist/index.d.ts +494 -0
  26. package/dist/index.js +523 -0
  27. package/dist/index.js.map +1 -0
  28. package/dist/metafile-cjs.json +1 -0
  29. package/dist/metafile-esm.json +1 -0
  30. package/dist/parser/index.cjs +85 -0
  31. package/dist/parser/index.cjs.map +1 -0
  32. package/dist/parser/index.d.cts +72 -0
  33. package/dist/parser/index.d.ts +72 -0
  34. package/dist/parser/index.js +4 -0
  35. package/dist/parser/index.js.map +1 -0
  36. package/dist/types-DvNlfbKB.d.cts +301 -0
  37. package/dist/types-DvNlfbKB.d.ts +301 -0
  38. package/dist/useCborSimpleEncoder-ButVU988.d.cts +268 -0
  39. package/dist/useCborSimpleEncoder-TVxzNJ_9.d.ts +268 -0
  40. package/dist/useCborTag-B_iaShG6.d.ts +142 -0
  41. package/dist/useCborTag-BfTIV8HM.d.cts +142 -0
  42. package/package.json +102 -0
  43. package/src/__tests__/public-api.test.ts +326 -0
  44. package/src/encoder/__tests__/cbor-collection-encoder.test.ts +331 -0
  45. package/src/encoder/__tests__/cbor-integer-encoder.test.ts +283 -0
  46. package/src/encoder/__tests__/cbor-simple-encoder.test.ts +224 -0
  47. package/src/encoder/__tests__/cbor-string-encoder.test.ts +345 -0
  48. package/src/encoder/__tests__/cbor-tag-encoder.test.ts +565 -0
  49. package/src/encoder/composables/#useCborTagEncoder.ts# +158 -0
  50. package/src/encoder/composables/useCborCollectionEncoder.ts +424 -0
  51. package/src/encoder/composables/useCborEncoder.ts +203 -0
  52. package/src/encoder/composables/useCborIntegerEncoder.ts +188 -0
  53. package/src/encoder/composables/useCborSimpleEncoder.ts +266 -0
  54. package/src/encoder/composables/useCborStringEncoder.ts +266 -0
  55. package/src/encoder/composables/useCborTagEncoder.ts +158 -0
  56. package/src/encoder/index.ts +35 -0
  57. package/src/encoder/types.ts +88 -0
  58. package/src/encoder/utils.ts +80 -0
  59. package/src/index.ts +434 -0
  60. package/src/parser/__tests__/ast-tree-structure.test.ts +311 -0
  61. package/src/parser/__tests__/cbor-collection-errors.test.ts +296 -0
  62. package/src/parser/__tests__/cbor-collection.test.ts +369 -0
  63. package/src/parser/__tests__/cbor-deterministic-encoding.test.ts +432 -0
  64. package/src/parser/__tests__/cbor-diagnostic.test.ts +333 -0
  65. package/src/parser/__tests__/cbor-duplicate-keys.test.ts +235 -0
  66. package/src/parser/__tests__/cbor-float-errors.test.ts +222 -0
  67. package/src/parser/__tests__/cbor-float.test.ts +502 -0
  68. package/src/parser/__tests__/cbor-integer-errors.test.ts +139 -0
  69. package/src/parser/__tests__/cbor-integer.test.ts +200 -0
  70. package/src/parser/__tests__/cbor-map-duplicate-keys.test.ts +403 -0
  71. package/src/parser/__tests__/cbor-parser-errors.test.ts +126 -0
  72. package/src/parser/__tests__/cbor-security-dos-protection.test.ts +503 -0
  73. package/src/parser/__tests__/cbor-sequences.test.ts +150 -0
  74. package/src/parser/__tests__/cbor-source-map.test.ts +321 -0
  75. package/src/parser/__tests__/cbor-standard-tags.test.ts +340 -0
  76. package/src/parser/__tests__/cbor-string-errors.test.ts +227 -0
  77. package/src/parser/__tests__/cbor-string.test.ts +224 -0
  78. package/src/parser/__tests__/cbor-tag-advanced.test.ts +500 -0
  79. package/src/parser/__tests__/cbor-tag-errors.test.ts +447 -0
  80. package/src/parser/__tests__/cbor-tag-source-map.test.ts +360 -0
  81. package/src/parser/__tests__/cbor-tag.test.ts +684 -0
  82. package/src/parser/__tests__/extreme-edge-cases.test.ts +146 -0
  83. package/src/parser/__tests__/pathBuilder.test.ts +256 -0
  84. package/src/parser/__tests__/rfc-test-vectors.test.ts +607 -0
  85. package/src/parser/__tests__/security-limits.test.ts +248 -0
  86. package/src/parser/__tests__/utils-errors.test.ts +127 -0
  87. package/src/parser/composables/useCborCollection.ts +509 -0
  88. package/src/parser/composables/useCborDiagnostic.ts +381 -0
  89. package/src/parser/composables/useCborFloat.ts +256 -0
  90. package/src/parser/composables/useCborInteger.ts +114 -0
  91. package/src/parser/composables/useCborParser.ts +951 -0
  92. package/src/parser/composables/useCborString.ts +330 -0
  93. package/src/parser/composables/useCborStringTypes.ts +129 -0
  94. package/src/parser/composables/useCborTag.ts +739 -0
  95. package/src/parser/index.ts +56 -0
  96. package/src/parser/types.ts +371 -0
  97. package/src/parser/utils/pathBuilder.ts +259 -0
  98. package/src/parser/utils.ts +398 -0
  99. package/src/utils/__tests__/logger.test.ts +186 -0
  100. package/src/utils/logger.ts +96 -0
@@ -0,0 +1,424 @@
1
+ /**
2
+ * CBOR Collection Encoder Composable
3
+ * Handles Major Type 4 (Arrays) and Major Type 5 (Maps)
4
+ * Following RFC 8949 specification
5
+ */
6
+
7
+ import type { EncodeResult, EncodeOptions, EncodeContext, EncodableValue } from '../types'
8
+ import { DEFAULT_ENCODE_OPTIONS, INDEFINITE_SYMBOL, ALL_ENTRIES_SYMBOL } from '../types'
9
+ import { bytesToHex, concatenateUint8Arrays, compareBytes } from '../utils'
10
+ import { useCborIntegerEncoder } from './useCborIntegerEncoder'
11
+ import { useCborStringEncoder } from './useCborStringEncoder'
12
+ import { useCborByteString, useCborTextString } from '../../parser/composables/useCborStringTypes'
13
+
14
+ interface CollectionEncodeOptions {
15
+ indefinite?: boolean
16
+ }
17
+
18
+ /**
19
+ * CBOR Collection Encoder Composable
20
+ *
21
+ * Provides functions to encode arrays and maps:
22
+ * - Major Type 4: Arrays
23
+ * - Major Type 5: Maps (objects or Map instances)
24
+ *
25
+ * Supports both definite-length and indefinite-length encoding.
26
+ * Handles canonical encoding with sorted map keys.
27
+ * Enforces depth and size limits.
28
+ *
29
+ * @param options - Global encoder options
30
+ *
31
+ * @example
32
+ * ```ts
33
+ * const { encodeArray, encodeMap } = useCborCollectionEncoder()
34
+ *
35
+ * // Encode array
36
+ * const arr = [1, 2, 3]
37
+ * const result1 = encodeArray(arr)
38
+ * // result1: { bytes: Uint8Array([0x83, 0x01, 0x02, 0x03]), hex: '83010203' }
39
+ *
40
+ * // Encode map
41
+ * const map = { amount: 1000000 }
42
+ * const result2 = encodeMap(map)
43
+ * // result2: { bytes: ..., hex: 'a166616d6f756e741a000f4240' }
44
+ * ```
45
+ */
46
+ export function useCborCollectionEncoder(globalOptions?: Partial<EncodeOptions>) {
47
+ const options = { ...DEFAULT_ENCODE_OPTIONS, ...globalOptions }
48
+
49
+ // Get other encoders
50
+ const { encodeInteger } = useCborIntegerEncoder()
51
+ const { encodeTextString, encodeByteString } = useCborStringEncoder(globalOptions)
52
+ const { isCborByteString } = useCborByteString()
53
+ const { isCborTextString } = useCborTextString()
54
+
55
+ /**
56
+ * Encode a single value (recursive)
57
+ *
58
+ * NOTE: This is set by the main encoder to enable recursive encoding of all types
59
+ * including tagged values. This avoids circular dependencies.
60
+ */
61
+ let mainEncode: ((value: EncodableValue) => EncodeResult) | null = null
62
+
63
+ /**
64
+ * Set the main encode function for recursive encoding
65
+ * This must be called by useCborEncoder before encoding collections
66
+ */
67
+ const setMainEncode = (encodeFn: (value: EncodableValue) => EncodeResult) => {
68
+ mainEncode = encodeFn
69
+ }
70
+
71
+ /**
72
+ * Encode a single value (recursive)
73
+ *
74
+ * @param value - Value to encode
75
+ * @param ctx - Encoding context for depth tracking
76
+ * @returns Encoded CBOR bytes
77
+ */
78
+ const encodeValue = (value: EncodableValue, ctx: EncodeContext): Uint8Array => {
79
+ // Check depth limit
80
+ if (ctx.depth > ctx.options.maxDepth) {
81
+ throw new Error('Maximum nesting depth exceeded')
82
+ }
83
+
84
+ // Encode based on type
85
+ if (value === null || value === undefined) {
86
+ // null/undefined -> CBOR null (0xf6)
87
+ return new Uint8Array([0xf6])
88
+ }
89
+ else if (typeof value === 'boolean') {
90
+ // true: 0xf5, false: 0xf4
91
+ return new Uint8Array([value ? 0xf5 : 0xf4])
92
+ }
93
+ else if (typeof value === 'number' || typeof value === 'bigint') {
94
+ return encodeInteger(value).bytes
95
+ }
96
+ else if (typeof value === 'string' || isCborTextString(value)) {
97
+ return encodeTextString(value).bytes
98
+ }
99
+ else if (value instanceof Uint8Array || isCborByteString(value)) {
100
+ return encodeByteString(value).bytes
101
+ }
102
+ else if (Array.isArray(value)) {
103
+ // Recursive array encoding - check for indefinite marker
104
+ const isIndefinite = (value as any)[INDEFINITE_SYMBOL] === true
105
+ const newCtx = { ...ctx, depth: ctx.depth + 1 }
106
+
107
+ if (isIndefinite) {
108
+ // Use indefinite encoding - but need to recursively encode items
109
+ const parts: Uint8Array[] = [new Uint8Array([0x9f])] // Start marker
110
+ for (const item of value) {
111
+ const encoded = encodeValue(item, newCtx)
112
+ parts.push(encoded)
113
+ }
114
+ parts.push(new Uint8Array([0xff])) // Break marker
115
+ return concatenateUint8Arrays(parts)
116
+ } else {
117
+ return encodeArrayInternal(value, newCtx).bytes
118
+ }
119
+ }
120
+ else if (typeof value === 'object' && value !== null && 'tag' in value && 'value' in value) {
121
+ // Tagged value - delegate to main encoder if available
122
+ if (mainEncode) {
123
+ return mainEncode(value).bytes
124
+ }
125
+ throw new Error('Tagged value encoding requires main encoder to be set')
126
+ }
127
+ else if (value instanceof Map || (typeof value === 'object' && value !== null)) {
128
+ // Recursive map encoding - check for indefinite marker
129
+ const isIndefinite = (value as any)[INDEFINITE_SYMBOL] === true
130
+ const newCtx = { ...ctx, depth: ctx.depth + 1 }
131
+
132
+ if (isIndefinite) {
133
+ // Use indefinite encoding
134
+ const entries: Array<[EncodableValue, EncodableValue]> =
135
+ value instanceof Map
136
+ ? Array.from(value.entries())
137
+ : Object.entries(value)
138
+
139
+ const parts: Uint8Array[] = [new Uint8Array([0xbf])] // Start marker
140
+ for (const [key, val] of entries) {
141
+ const encodedKey = encodeValue(key, newCtx)
142
+ const encodedValue = encodeValue(val, newCtx)
143
+ parts.push(encodedKey)
144
+ parts.push(encodedValue)
145
+ }
146
+ parts.push(new Uint8Array([0xff])) // Break marker
147
+ return concatenateUint8Arrays(parts)
148
+ } else {
149
+ return encodeMapInternal(value as Map<EncodableValue, EncodableValue> | { [key: string]: EncodableValue }, newCtx).bytes
150
+ }
151
+ }
152
+ else {
153
+ throw new Error(`Unsupported value type: ${typeof value}`)
154
+ }
155
+ }
156
+
157
+ /**
158
+ * Encode length header for arrays and maps
159
+ *
160
+ * @param majorType - Major type (4 for arrays, 5 for maps)
161
+ * @param length - Number of elements
162
+ * @returns Encoded length header
163
+ */
164
+ const encodeLengthHeader = (majorType: number, length: number): Uint8Array => {
165
+ const baseValue = majorType << 5
166
+
167
+ if (length <= 23) {
168
+ return new Uint8Array([baseValue | length])
169
+ }
170
+ else if (length <= 255) {
171
+ return new Uint8Array([baseValue | 24, length])
172
+ }
173
+ else if (length <= 65535) {
174
+ return new Uint8Array([baseValue | 25, length >> 8, length & 0xff])
175
+ }
176
+ else if (length <= 4294967295) {
177
+ return new Uint8Array([
178
+ baseValue | 26,
179
+ (length >> 24) & 0xff,
180
+ (length >> 16) & 0xff,
181
+ (length >> 8) & 0xff,
182
+ length & 0xff
183
+ ])
184
+ }
185
+ else {
186
+ throw new Error('Collection too large to encode')
187
+ }
188
+ }
189
+
190
+ /**
191
+ * Internal array encoding (used by recursive calls)
192
+ */
193
+ const encodeArrayInternal = (
194
+ array: EncodableValue[],
195
+ ctx: EncodeContext
196
+ ): EncodeResult => {
197
+ const header = encodeLengthHeader(4, array.length)
198
+ const parts: Uint8Array[] = [header]
199
+
200
+ // Encode each element
201
+ for (const item of array) {
202
+ const encoded = encodeValue(item, ctx)
203
+ parts.push(encoded)
204
+ }
205
+
206
+ const result = concatenateUint8Arrays(parts)
207
+
208
+ // Check output size
209
+ ctx.bytesWritten += result.length
210
+ if (ctx.bytesWritten > ctx.options.maxOutputSize) {
211
+ throw new Error('Encoded output exceeds maximum size')
212
+ }
213
+
214
+ return {
215
+ bytes: result,
216
+ hex: bytesToHex(result)
217
+ }
218
+ }
219
+
220
+ /**
221
+ * Encode array (Major Type 4)
222
+ *
223
+ * @param array - Array of encodable values
224
+ * @param encodeOptions - Encoding options
225
+ * @returns Encoded CBOR bytes and hex string
226
+ */
227
+ const encodeArray = (
228
+ array: EncodableValue[],
229
+ encodeOptions?: CollectionEncodeOptions
230
+ ): EncodeResult => {
231
+ // Check if array was originally encoded with indefinite length
232
+ const isIndefinite = (array as any)[INDEFINITE_SYMBOL] === true
233
+
234
+ // Handle indefinite-length encoding
235
+ if (encodeOptions?.indefinite || isIndefinite) {
236
+ if (options.canonical) {
237
+ throw new Error('Indefinite-length encoding not allowed in canonical mode')
238
+ }
239
+ return encodeArrayIndefinite(array)
240
+ }
241
+
242
+ // Definite-length encoding
243
+ const ctx: EncodeContext = {
244
+ depth: 0,
245
+ bytesWritten: 0,
246
+ options
247
+ }
248
+
249
+ return encodeArrayInternal(array, ctx)
250
+ }
251
+
252
+ /**
253
+ * Encode array with indefinite length
254
+ *
255
+ * @param array - Array of encodable values
256
+ * @returns Encoded CBOR bytes and hex string
257
+ */
258
+ const encodeArrayIndefinite = (array: EncodableValue[]): EncodeResult => {
259
+ if (options.canonical) {
260
+ throw new Error('Indefinite-length encoding not allowed in canonical mode')
261
+ }
262
+
263
+ const parts: Uint8Array[] = [new Uint8Array([0x9f])] // Start marker
264
+
265
+ const ctx: EncodeContext = {
266
+ depth: 0,
267
+ bytesWritten: 0,
268
+ options
269
+ }
270
+
271
+ // Encode each element
272
+ for (const item of array) {
273
+ const encoded = encodeValue(item, ctx)
274
+ parts.push(encoded)
275
+ }
276
+
277
+ parts.push(new Uint8Array([0xff])) // Break marker
278
+
279
+ const result = concatenateUint8Arrays(parts)
280
+
281
+ return {
282
+ bytes: result,
283
+ hex: bytesToHex(result)
284
+ }
285
+ }
286
+
287
+ /**
288
+ * Internal map encoding (used by recursive calls)
289
+ */
290
+ const encodeMapInternal = (
291
+ map: Map<EncodableValue, EncodableValue> | { [key: string]: EncodableValue },
292
+ ctx: EncodeContext
293
+ ): EncodeResult => {
294
+ // Check for ALL_ENTRIES_SYMBOL for byte-perfect preservation with duplicates
295
+ let entries: Array<[EncodableValue, EncodableValue]>
296
+
297
+ if ((map as any)[ALL_ENTRIES_SYMBOL]) {
298
+ // Use the preserved entries (includes duplicates and original order)
299
+ entries = (map as any)[ALL_ENTRIES_SYMBOL]
300
+ } else {
301
+ // Convert object to Map if needed
302
+ entries = map instanceof Map
303
+ ? Array.from(map.entries())
304
+ : Object.entries(map)
305
+ }
306
+
307
+ // In canonical mode, sort entries by encoded key (unless using allEntries for byte-perfect)
308
+ if (ctx.options.canonical && !(map as any)[ALL_ENTRIES_SYMBOL]) {
309
+ entries.sort((a, b) => {
310
+ const keyA = encodeValue(a[0], { ...ctx, depth: ctx.depth + 1 })
311
+ const keyB = encodeValue(b[0], { ...ctx, depth: ctx.depth + 1 })
312
+ return compareBytes(keyA, keyB)
313
+ })
314
+ }
315
+
316
+ const header = encodeLengthHeader(5, entries.length)
317
+ const parts: Uint8Array[] = [header]
318
+
319
+ // Encode each key-value pair
320
+ for (const [key, value] of entries) {
321
+ const encodedKey = encodeValue(key, ctx)
322
+ const encodedValue = encodeValue(value, ctx)
323
+ parts.push(encodedKey)
324
+ parts.push(encodedValue)
325
+ }
326
+
327
+ const result = concatenateUint8Arrays(parts)
328
+
329
+ // Check output size
330
+ ctx.bytesWritten += result.length
331
+ if (ctx.bytesWritten > ctx.options.maxOutputSize) {
332
+ throw new Error('Encoded output exceeds maximum size')
333
+ }
334
+
335
+ return {
336
+ bytes: result,
337
+ hex: bytesToHex(result)
338
+ }
339
+ }
340
+
341
+ /**
342
+ * Encode map (Major Type 5)
343
+ *
344
+ * @param map - Map or plain object
345
+ * @param encodeOptions - Encoding options
346
+ * @returns Encoded CBOR bytes and hex string
347
+ */
348
+ const encodeMap = (
349
+ map: Map<EncodableValue, EncodableValue> | { [key: string]: EncodableValue },
350
+ encodeOptions?: CollectionEncodeOptions
351
+ ): EncodeResult => {
352
+ // Check if map was originally encoded with indefinite length
353
+ const isIndefinite = (map as any)[INDEFINITE_SYMBOL] === true
354
+
355
+ // Handle indefinite-length encoding
356
+ if (encodeOptions?.indefinite || isIndefinite) {
357
+ if (options.canonical) {
358
+ throw new Error('Indefinite-length encoding not allowed in canonical mode')
359
+ }
360
+ return encodeMapIndefinite(map)
361
+ }
362
+
363
+ // Definite-length encoding
364
+ const ctx: EncodeContext = {
365
+ depth: 0,
366
+ bytesWritten: 0,
367
+ options
368
+ }
369
+
370
+ return encodeMapInternal(map, ctx)
371
+ }
372
+
373
+ /**
374
+ * Encode map with indefinite length
375
+ *
376
+ * @param map - Map or plain object
377
+ * @returns Encoded CBOR bytes and hex string
378
+ */
379
+ const encodeMapIndefinite = (
380
+ map: Map<EncodableValue, EncodableValue> | { [key: string]: EncodableValue }
381
+ ): EncodeResult => {
382
+ if (options.canonical) {
383
+ throw new Error('Indefinite-length encoding not allowed in canonical mode')
384
+ }
385
+
386
+ const entries: Array<[EncodableValue, EncodableValue]> =
387
+ map instanceof Map
388
+ ? Array.from(map.entries())
389
+ : Object.entries(map)
390
+
391
+ const parts: Uint8Array[] = [new Uint8Array([0xbf])] // Start marker
392
+
393
+ const ctx: EncodeContext = {
394
+ depth: 0,
395
+ bytesWritten: 0,
396
+ options
397
+ }
398
+
399
+ // Encode each key-value pair
400
+ for (const [key, value] of entries) {
401
+ const encodedKey = encodeValue(key, ctx)
402
+ const encodedValue = encodeValue(value, ctx)
403
+ parts.push(encodedKey)
404
+ parts.push(encodedValue)
405
+ }
406
+
407
+ parts.push(new Uint8Array([0xff])) // Break marker
408
+
409
+ const result = concatenateUint8Arrays(parts)
410
+
411
+ return {
412
+ bytes: result,
413
+ hex: bytesToHex(result)
414
+ }
415
+ }
416
+
417
+ return {
418
+ encodeArray,
419
+ encodeArrayIndefinite,
420
+ encodeMap,
421
+ encodeMapIndefinite,
422
+ setMainEncode
423
+ }
424
+ }
@@ -0,0 +1,203 @@
1
+ /**
2
+ * Main CBOR Encoder Composable
3
+ * High-level API for encoding JavaScript values to CBOR
4
+ * Following RFC 8949 specification
5
+ */
6
+
7
+ import type { EncodeResult, EncodeOptions, EncodableValue, TaggedValue } from '../types'
8
+ import { DEFAULT_ENCODE_OPTIONS } from '../types'
9
+ import { useCborIntegerEncoder } from './useCborIntegerEncoder'
10
+ import { useCborStringEncoder } from './useCborStringEncoder'
11
+ import { useCborCollectionEncoder } from './useCborCollectionEncoder'
12
+ import { useCborSimpleEncoder } from './useCborSimpleEncoder'
13
+ import { useCborTagEncoder } from './useCborTagEncoder'
14
+ import { useCborByteString, useCborTextString } from '../../parser/composables/useCborStringTypes'
15
+
16
+ /**
17
+ * Main CBOR Encoder Composable
18
+ *
19
+ * Provides a unified interface for encoding any JavaScript value to CBOR.
20
+ * Automatically selects the appropriate encoder based on value type.
21
+ *
22
+ * @param options - Global encoder options
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * const { encode } = useCborEncoder()
27
+ *
28
+ * // Encode various types
29
+ * encode(42) // Integer
30
+ * encode("hello") // Text string
31
+ * encode([1, 2, 3]) // Array
32
+ * encode({ a: 1 }) // Map
33
+ * encode(true) // Boolean
34
+ * encode(3.14) // Float
35
+ * encode(new Uint8Array([0xff])) // Byte string
36
+ *
37
+ * // With options
38
+ * const { encode: encodeCanonical } = useCborEncoder({ canonical: true })
39
+ * encodeCanonical({ z: 1, a: 2 }) // Keys will be sorted
40
+ * ```
41
+ */
42
+ export function useCborEncoder(globalOptions?: Partial<EncodeOptions>) {
43
+ const options = { ...DEFAULT_ENCODE_OPTIONS, ...globalOptions }
44
+
45
+ // Get all specialized encoders
46
+ const { encodeInteger } = useCborIntegerEncoder()
47
+ const { encodeTextString, encodeByteString } = useCborStringEncoder(options)
48
+ const { encodeArray, encodeMap, setMainEncode } = useCborCollectionEncoder(options)
49
+ const { encodeSimple, encodeFloat } = useCborSimpleEncoder(options)
50
+ const { encodeTaggedValue } = useCborTagEncoder()
51
+
52
+ // Get type guards for composable string types
53
+ const { isCborByteString } = useCborByteString()
54
+ const { isCborTextString } = useCborTextString()
55
+
56
+ /**
57
+ * Encode any JavaScript value to CBOR
58
+ *
59
+ * Automatically detects the type and uses the appropriate encoder:
60
+ * - number/bigint → Integer or Float
61
+ * - string → Text string
62
+ * - boolean/null/undefined → Simple values
63
+ * - Uint8Array → Byte string
64
+ * - Array → CBOR array
65
+ * - {tag: number, value: any} → Tagged value
66
+ * - Object/Map → CBOR map
67
+ *
68
+ * @param value - Value to encode
69
+ * @returns Encoded CBOR bytes and hex string
70
+ * @throws Error if value type is unsupported
71
+ */
72
+ const encode = (value: EncodableValue): EncodeResult => {
73
+ // Handle null/undefined/boolean
74
+ if (value === null || value === undefined || typeof value === 'boolean') {
75
+ return encodeSimple(value)
76
+ }
77
+
78
+ // Handle numbers
79
+ if (typeof value === 'number') {
80
+ // Check if it's an integer
81
+ if (Number.isInteger(value) && Number.isSafeInteger(value)) {
82
+ return encodeInteger(value)
83
+ }
84
+ // It's a float
85
+ return encodeFloat(value)
86
+ }
87
+
88
+ // Handle bigint
89
+ if (typeof value === 'bigint') {
90
+ return encodeInteger(value)
91
+ }
92
+
93
+ // Handle composable text strings (check before primitive strings)
94
+ if (isCborTextString(value)) {
95
+ return encodeTextString(value)
96
+ }
97
+
98
+ // Handle strings
99
+ if (typeof value === 'string') {
100
+ return encodeTextString(value)
101
+ }
102
+
103
+ // Handle composable byte strings (check before Uint8Array)
104
+ if (isCborByteString(value)) {
105
+ return encodeByteString(value)
106
+ }
107
+
108
+ // Handle Uint8Array (byte strings)
109
+ if (value instanceof Uint8Array) {
110
+ return encodeByteString(value)
111
+ }
112
+
113
+ // Handle arrays
114
+ if (Array.isArray(value)) {
115
+ return encodeArray(value)
116
+ }
117
+
118
+ // Handle Map
119
+ if (value instanceof Map) {
120
+ return encodeMap(value)
121
+ }
122
+
123
+ // Handle tagged values (MUST come before plain objects)
124
+ // Check for {tag: number, value: any} structure
125
+ if (typeof value === 'object' && value !== null && 'tag' in value && 'value' in value && typeof (value as { tag: unknown }).tag === 'number') {
126
+ return encodeTaggedValue(value as TaggedValue, encode)
127
+ }
128
+
129
+ // Handle plain objects
130
+ if (typeof value === 'object' && value !== null) {
131
+ return encodeMap(value as { [key: string]: EncodableValue })
132
+ }
133
+
134
+ throw new Error(`Unsupported value type: ${typeof value}`)
135
+ }
136
+
137
+ // Set the main encode function for recursive collection encoding
138
+ // This allows the collection encoder to handle nested tagged values
139
+ setMainEncode(encode)
140
+
141
+ /**
142
+ * Encode value and return only the hex string
143
+ *
144
+ * @param value - Value to encode
145
+ * @returns Hex string representation
146
+ */
147
+ const encodeToHex = (value: EncodableValue): string => {
148
+ return encode(value).hex
149
+ }
150
+
151
+ /**
152
+ * Encode value and return only the bytes
153
+ *
154
+ * @param value - Value to encode
155
+ * @returns Uint8Array bytes
156
+ */
157
+ const encodeToBytes = (value: EncodableValue): Uint8Array => {
158
+ return encode(value).bytes
159
+ }
160
+
161
+ /**
162
+ * Encode multiple values in sequence
163
+ *
164
+ * Useful for CBOR sequences (RFC 8742)
165
+ *
166
+ * @param values - Values to encode
167
+ * @returns Concatenated CBOR encoding
168
+ */
169
+ const encodeSequence = (values: EncodableValue[]): EncodeResult => {
170
+ const allBytes: Uint8Array[] = []
171
+
172
+ for (const value of values) {
173
+ const result = encode(value)
174
+ allBytes.push(result.bytes)
175
+ }
176
+
177
+ // Concatenate all encoded values
178
+ const totalLength = allBytes.reduce((sum, arr) => sum + arr.length, 0)
179
+ const concatenated = new Uint8Array(totalLength)
180
+
181
+ let offset = 0
182
+ for (const bytes of allBytes) {
183
+ concatenated.set(bytes, offset)
184
+ offset += bytes.length
185
+ }
186
+
187
+ const hex = Array.from(concatenated)
188
+ .map(b => b.toString(16).padStart(2, '0'))
189
+ .join('')
190
+
191
+ return {
192
+ bytes: concatenated,
193
+ hex
194
+ }
195
+ }
196
+
197
+ return {
198
+ encode,
199
+ encodeToHex,
200
+ encodeToBytes,
201
+ encodeSequence
202
+ }
203
+ }