@bcts/dcbor 1.0.0-alpha.10

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 (45) hide show
  1. package/LICENSE +48 -0
  2. package/README.md +13 -0
  3. package/dist/index.cjs +9151 -0
  4. package/dist/index.cjs.map +1 -0
  5. package/dist/index.d.cts +3107 -0
  6. package/dist/index.d.cts.map +1 -0
  7. package/dist/index.d.mts +3107 -0
  8. package/dist/index.d.mts.map +1 -0
  9. package/dist/index.iife.js +9155 -0
  10. package/dist/index.iife.js.map +1 -0
  11. package/dist/index.mjs +9027 -0
  12. package/dist/index.mjs.map +1 -0
  13. package/package.json +80 -0
  14. package/src/.claude-flow/metrics/agent-metrics.json +1 -0
  15. package/src/.claude-flow/metrics/performance.json +87 -0
  16. package/src/.claude-flow/metrics/task-metrics.json +10 -0
  17. package/src/byte-string.ts +300 -0
  18. package/src/cbor-codable.ts +170 -0
  19. package/src/cbor-tagged-codable.ts +72 -0
  20. package/src/cbor-tagged-decodable.ts +184 -0
  21. package/src/cbor-tagged-encodable.ts +138 -0
  22. package/src/cbor-tagged.ts +104 -0
  23. package/src/cbor.ts +869 -0
  24. package/src/conveniences.ts +840 -0
  25. package/src/date.ts +553 -0
  26. package/src/decode.ts +276 -0
  27. package/src/diag.ts +462 -0
  28. package/src/dump.ts +277 -0
  29. package/src/error.ts +259 -0
  30. package/src/exact.ts +714 -0
  31. package/src/float.ts +279 -0
  32. package/src/global.d.ts +34 -0
  33. package/src/globals.d.ts +0 -0
  34. package/src/index.ts +180 -0
  35. package/src/map.ts +308 -0
  36. package/src/prelude.ts +70 -0
  37. package/src/set.ts +515 -0
  38. package/src/simple.ts +153 -0
  39. package/src/stdlib.ts +55 -0
  40. package/src/string-util.ts +55 -0
  41. package/src/tag.ts +53 -0
  42. package/src/tags-store.ts +294 -0
  43. package/src/tags.ts +231 -0
  44. package/src/varint.ts +124 -0
  45. package/src/walk.ts +516 -0
package/src/varint.ts ADDED
@@ -0,0 +1,124 @@
1
+ import { type CborNumber, isCborNumber, type MajorType } from "./cbor";
2
+ import { hasFractionalPart } from "./float";
3
+ import { CborError } from "./error";
4
+
5
+ const typeBits = (t: MajorType): number => {
6
+ return t << 5;
7
+ };
8
+
9
+ export const encodeVarInt = (value: CborNumber, majorType: MajorType): Uint8Array => {
10
+ // throw an error if the value is negative.
11
+ if (value < 0) {
12
+ throw new CborError({ type: "OutOfRange" });
13
+ }
14
+ // throw an error if the value is a number with a fractional part.
15
+ if (typeof value === "number" && hasFractionalPart(value)) {
16
+ throw new CborError({ type: "OutOfRange" });
17
+ }
18
+ const type = typeBits(majorType);
19
+ // If the value is a `number` or a `bigint` that can be represented as a `number`, convert it to a `number`.
20
+ if (isCborNumber(value) && value <= Number.MAX_SAFE_INTEGER) {
21
+ value = Number(value);
22
+ if (value <= 23) {
23
+ return new Uint8Array([value | type]);
24
+ } else if (value <= 0xff) {
25
+ // Fits in UInt8
26
+ return new Uint8Array([0x18 | type, value]);
27
+ } else if (value <= 0xffff) {
28
+ // Fits in UInt16
29
+ const buffer = new ArrayBuffer(3);
30
+ const view = new DataView(buffer);
31
+ view.setUint8(0, 0x19 | type);
32
+ view.setUint16(1, value);
33
+ return new Uint8Array(buffer);
34
+ } else if (value <= 0xffffffff) {
35
+ // Fits in UInt32
36
+ const buffer = new ArrayBuffer(5);
37
+ const view = new DataView(buffer);
38
+ view.setUint8(0, 0x1a | type);
39
+ view.setUint32(1, value);
40
+ return new Uint8Array(buffer);
41
+ } else {
42
+ // Fits in MAX_SAFE_INTEGER
43
+ const buffer = new ArrayBuffer(9);
44
+ const view = new DataView(buffer);
45
+ view.setUint8(0, 0x1b | type);
46
+ view.setBigUint64(1, BigInt(value));
47
+ return new Uint8Array(buffer);
48
+ }
49
+ } else {
50
+ value = BigInt(value);
51
+ const bitsNeeded = Math.ceil(Math.log2(Number(value)) / 8) * 8;
52
+ if (bitsNeeded > 64) {
53
+ throw new CborError({ type: "OutOfRange" });
54
+ }
55
+ const length = Math.ceil(bitsNeeded / 8) + 1;
56
+ const buffer = new ArrayBuffer(length);
57
+ const view = new DataView(buffer);
58
+ let i = length - 1;
59
+ while (value > 0) {
60
+ view.setUint8(i, Number(value & 0xffn));
61
+ value >>= 8n;
62
+ i--;
63
+ }
64
+ view.setUint8(0, 0x1b | type);
65
+ return new Uint8Array(buffer);
66
+ }
67
+ };
68
+
69
+ export const decodeVarIntData = (
70
+ dataView: DataView,
71
+ offset: number,
72
+ ): { majorType: MajorType; value: CborNumber; offset: number } => {
73
+ const initialByte = dataView.getUint8(offset);
74
+ const majorType = (initialByte >> 5) as MajorType;
75
+ const additionalInfo = initialByte & 0x1f;
76
+ let value: CborNumber;
77
+ offset += 1;
78
+ switch (additionalInfo) {
79
+ case 24: // 1-byte additional info
80
+ value = dataView.getUint8(offset);
81
+ offset += 1;
82
+ break;
83
+ case 25: // 2-byte additional info
84
+ value = ((dataView.getUint8(offset) << 8) | dataView.getUint8(offset + 1)) >>> 0;
85
+ offset += 2;
86
+ break;
87
+ case 26: // 4-byte additional info
88
+ value =
89
+ ((dataView.getUint8(offset) << 24) |
90
+ (dataView.getUint8(offset + 1) << 16) |
91
+ (dataView.getUint8(offset + 2) << 8) |
92
+ dataView.getUint8(offset + 3)) >>>
93
+ 0;
94
+ offset += 4;
95
+ break;
96
+ case 27: // 8-byte additional info
97
+ value = getUint64(dataView, offset, false);
98
+ if (value <= Number.MAX_SAFE_INTEGER) {
99
+ value = Number(value);
100
+ }
101
+ offset += 8;
102
+ break;
103
+ default: // no additional info
104
+ value = additionalInfo;
105
+ break;
106
+ }
107
+ return { majorType, value, offset };
108
+ };
109
+
110
+ export const decodeVarInt = (
111
+ data: Uint8Array,
112
+ ): { majorType: MajorType; value: CborNumber; offset: number } => {
113
+ return decodeVarIntData(new DataView(data.buffer, data.byteOffset, data.byteLength), 0);
114
+ };
115
+
116
+ function getUint64(view: DataView, byteOffset: number, littleEndian: boolean): bigint {
117
+ const lowWord = littleEndian
118
+ ? view.getUint32(byteOffset, true)
119
+ : view.getUint32(byteOffset + 4, false);
120
+ const highWord = littleEndian
121
+ ? view.getUint32(byteOffset + 4, true)
122
+ : view.getUint32(byteOffset, false);
123
+ return (BigInt(highWord) << BigInt(32)) + BigInt(lowWord);
124
+ }
package/src/walk.ts ADDED
@@ -0,0 +1,516 @@
1
+ /**
2
+ * Tree traversal system for CBOR data structures.
3
+ *
4
+ * This module provides a visitor pattern implementation for traversing
5
+ * CBOR trees, allowing users to inspect and process elements at any depth.
6
+ *
7
+ * @module walk
8
+ */
9
+
10
+ import {
11
+ type Cbor,
12
+ MajorType,
13
+ type CborMapType,
14
+ type CborArrayType,
15
+ type CborTaggedType,
16
+ } from "./cbor";
17
+ import { CborError } from "./error";
18
+
19
+ /**
20
+ * Types of edges in the CBOR tree traversal.
21
+ */
22
+ export enum EdgeType {
23
+ /** No specific edge type (root element) */
24
+ None = "none",
25
+ /** Element within an array */
26
+ ArrayElement = "array_element",
27
+ /** Key-value pair in a map (semantic unit) */
28
+ MapKeyValue = "map_key_value",
29
+ /** Key within a map */
30
+ MapKey = "map_key",
31
+ /** Value within a map */
32
+ MapValue = "map_value",
33
+ /** Content of a tagged value */
34
+ TaggedContent = "tagged_content",
35
+ }
36
+
37
+ /**
38
+ * Discriminated union for edge type information.
39
+ */
40
+ export type EdgeTypeVariant =
41
+ | { type: EdgeType.None }
42
+ | { type: EdgeType.ArrayElement; index: number }
43
+ | { type: EdgeType.MapKeyValue }
44
+ | { type: EdgeType.MapKey }
45
+ | { type: EdgeType.MapValue }
46
+ | { type: EdgeType.TaggedContent };
47
+
48
+ /**
49
+ * Returns a short text label for the edge type, or undefined if no label is needed.
50
+ *
51
+ * This is primarily used for tree formatting to identify relationships between elements.
52
+ *
53
+ * @param edge - The edge type variant to get a label for
54
+ * @returns Short label string, or undefined for None edge type
55
+ *
56
+ * @example
57
+ * ```typescript
58
+ * edgeLabel({ type: EdgeType.ArrayElement, index: 0 }); // Returns "arr[0]"
59
+ * edgeLabel({ type: EdgeType.MapKeyValue }); // Returns "kv"
60
+ * edgeLabel({ type: EdgeType.MapKey }); // Returns "key"
61
+ * edgeLabel({ type: EdgeType.MapValue }); // Returns "val"
62
+ * edgeLabel({ type: EdgeType.TaggedContent }); // Returns "content"
63
+ * edgeLabel({ type: EdgeType.None }); // Returns undefined
64
+ * ```
65
+ */
66
+ export const edgeLabel = (edge: EdgeTypeVariant): string | undefined => {
67
+ switch (edge.type) {
68
+ case EdgeType.ArrayElement:
69
+ return `arr[${edge.index}]`;
70
+ case EdgeType.MapKeyValue:
71
+ return "kv";
72
+ case EdgeType.MapKey:
73
+ return "key";
74
+ case EdgeType.MapValue:
75
+ return "val";
76
+ case EdgeType.TaggedContent:
77
+ return "content";
78
+ case EdgeType.None:
79
+ return undefined;
80
+ }
81
+ };
82
+
83
+ /**
84
+ * Element visited during tree traversal.
85
+ * Can be either a single CBOR value or a key-value pair from a map.
86
+ */
87
+ export type WalkElement =
88
+ | { type: "single"; cbor: Cbor }
89
+ | { type: "keyvalue"; key: Cbor; value: Cbor };
90
+
91
+ /**
92
+ * Helper functions for WalkElement
93
+ */
94
+
95
+ /**
96
+ * Returns the single CBOR element if this is a 'single' variant.
97
+ *
98
+ * @param element - The walk element to extract from
99
+ * @returns The CBOR value if single, undefined otherwise
100
+ *
101
+ * @example
102
+ * ```typescript
103
+ * const element: WalkElement = { type: 'single', cbor: someCbor };
104
+ * const cbor = asSingle(element); // Returns someCbor
105
+ * ```
106
+ */
107
+ export const asSingle = (element: WalkElement): Cbor | undefined => {
108
+ return element.type === "single" ? element.cbor : undefined;
109
+ };
110
+
111
+ /**
112
+ * Returns the key-value pair if this is a 'keyvalue' variant.
113
+ *
114
+ * @param element - The walk element to extract from
115
+ * @returns Tuple of [key, value] if keyvalue, undefined otherwise
116
+ *
117
+ * @example
118
+ * ```typescript
119
+ * const element: WalkElement = { type: 'keyvalue', key: keyValue, value: valValue };
120
+ * const pair = asKeyValue(element); // Returns [keyValue, valValue]
121
+ * ```
122
+ */
123
+ export const asKeyValue = (element: WalkElement): [Cbor, Cbor] | undefined => {
124
+ return element.type === "keyvalue" ? [element.key, element.value] : undefined;
125
+ };
126
+
127
+ /**
128
+ * Visitor function type with state threading.
129
+ *
130
+ * @template State - The type of state passed through the traversal
131
+ * @param element - The element being visited
132
+ * @param level - The depth level in the tree (0 = root)
133
+ * @param edge - Information about the edge leading to this element
134
+ * @param state - Current state value
135
+ * @returns Tuple of [newState, stopDescent] where:
136
+ * - newState: The updated state to pass to subsequent visits
137
+ * - stopDescent: If true, don't descend into children of this element
138
+ */
139
+ export type Visitor<State> = (
140
+ element: WalkElement,
141
+ level: number,
142
+ edge: EdgeTypeVariant,
143
+ state: State,
144
+ ) => [State, boolean];
145
+
146
+ /**
147
+ * Walk a CBOR tree, visiting each element with a visitor function.
148
+ *
149
+ * The visitor function is called for each element in the tree, in depth-first order.
150
+ * State is threaded through the traversal, allowing accumulation of results.
151
+ *
152
+ * For maps, the visitor is called with:
153
+ * 1. A 'keyvalue' element containing both key and value
154
+ * 2. The key individually (if descent wasn't stopped)
155
+ * 3. The value individually (if descent wasn't stopped)
156
+ *
157
+ * @template State - The type of state to thread through the traversal
158
+ * @param cbor - The CBOR value to traverse
159
+ * @param initialState - Initial state value
160
+ * @param visitor - Function to call for each element
161
+ * @returns Final state after traversal
162
+ *
163
+ * @example
164
+ * ```typescript
165
+ * // Count all text strings in a structure
166
+ * interface CountState { count: number }
167
+ *
168
+ * const structure = cbor({ name: 'Alice', tags: ['urgent', 'draft'] });
169
+ * const result = walk(structure, { count: 0 }, (element, level, edge, state) => {
170
+ * if (element.type === 'single' && element.cbor.type === MajorType.Text) {
171
+ * return [{ count: state.count + 1 }, false];
172
+ * }
173
+ * return [state, false];
174
+ * });
175
+ * console.log(result.count); // 3 (name, urgent, draft)
176
+ * ```
177
+ *
178
+ * @example
179
+ * ```typescript
180
+ * // Find first occurrence and stop
181
+ * const structure = cbor([1, 2, 3, 'found', 5, 6]);
182
+ * let found = false;
183
+ *
184
+ * walk(structure, null, (element, level, edge) => {
185
+ * if (element.type === 'single' &&
186
+ * element.cbor.type === MajorType.Text &&
187
+ * element.cbor.value === 'found') {
188
+ * found = true;
189
+ * return [null, true]; // Stop descending
190
+ * }
191
+ * return [null, false];
192
+ * });
193
+ * ```
194
+ */
195
+ export const walk = <State>(cbor: Cbor, initialState: State, visitor: Visitor<State>): State => {
196
+ return walkInternal(cbor, 0, { type: EdgeType.None }, initialState, visitor);
197
+ };
198
+
199
+ /**
200
+ * Internal recursive walk implementation.
201
+ *
202
+ * @internal
203
+ */
204
+ function walkInternal<State>(
205
+ cbor: Cbor,
206
+ level: number,
207
+ edge: EdgeTypeVariant,
208
+ state: State,
209
+ visitor: Visitor<State>,
210
+ skipVisit = false,
211
+ ): State {
212
+ let currentState = state;
213
+ let stopDescent = false;
214
+
215
+ // Visit the current element (unless skipVisit is true)
216
+ if (!skipVisit) {
217
+ const element: WalkElement = { type: "single", cbor };
218
+ const [newState, stop] = visitor(element, level, edge, currentState);
219
+ currentState = newState;
220
+ stopDescent = stop;
221
+
222
+ // If visitor says to stop descending, return immediately
223
+ if (stopDescent) {
224
+ return currentState;
225
+ }
226
+ }
227
+
228
+ // Recursively visit children based on CBOR type
229
+ // Only container types (Array, Map, Tagged) need special handling; leaf nodes use default
230
+ // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check
231
+ switch (cbor.type) {
232
+ case MajorType.Array:
233
+ currentState = walkArray(cbor, level, currentState, visitor);
234
+ break;
235
+
236
+ case MajorType.Map:
237
+ currentState = walkMap(cbor, level, currentState, visitor);
238
+ break;
239
+
240
+ case MajorType.Tagged:
241
+ currentState = walkTagged(cbor, level, currentState, visitor);
242
+ break;
243
+
244
+ // Leaf nodes: Unsigned, Negative, Bytes, Text, Simple
245
+ default:
246
+ // No children to visit
247
+ break;
248
+ }
249
+
250
+ return currentState;
251
+ }
252
+
253
+ /**
254
+ * Walk an array's elements.
255
+ *
256
+ * @internal
257
+ */
258
+ function walkArray<State>(
259
+ cbor: CborArrayType,
260
+ level: number,
261
+ state: State,
262
+ visitor: Visitor<State>,
263
+ ): State {
264
+ let currentState = state;
265
+
266
+ for (let index = 0; index < cbor.value.length; index++) {
267
+ const item = cbor.value[index];
268
+ if (item === undefined) {
269
+ throw new CborError({
270
+ type: "Custom",
271
+ message: `Array element at index ${index} is undefined`,
272
+ });
273
+ }
274
+ currentState = walkInternal(
275
+ item,
276
+ level + 1,
277
+ { type: EdgeType.ArrayElement, index },
278
+ currentState,
279
+ visitor,
280
+ );
281
+ }
282
+
283
+ return currentState;
284
+ }
285
+
286
+ /**
287
+ * Walk a map's key-value pairs.
288
+ *
289
+ * For each entry:
290
+ * 1. Visit the key-value pair as a semantic unit
291
+ * 2. If not stopped, visit the key individually
292
+ * 3. If not stopped, visit the value individually
293
+ *
294
+ * @internal
295
+ */
296
+ function walkMap<State>(
297
+ cbor: CborMapType,
298
+ level: number,
299
+ state: State,
300
+ visitor: Visitor<State>,
301
+ ): State {
302
+ let currentState = state;
303
+
304
+ for (const entry of cbor.value.entriesArray) {
305
+ const { key, value } = entry;
306
+
307
+ // First, visit the key-value pair as a semantic unit
308
+ const kvElement: WalkElement = { type: "keyvalue", key, value };
309
+ const [kvState, kvStop] = visitor(
310
+ kvElement,
311
+ level + 1,
312
+ { type: EdgeType.MapKeyValue },
313
+ currentState,
314
+ );
315
+ currentState = kvState;
316
+
317
+ // If not stopped, visit key and value individually
318
+ if (!kvStop) {
319
+ currentState = walkInternal(key, level + 1, { type: EdgeType.MapKey }, currentState, visitor);
320
+
321
+ currentState = walkInternal(
322
+ value,
323
+ level + 1,
324
+ { type: EdgeType.MapValue },
325
+ currentState,
326
+ visitor,
327
+ );
328
+ }
329
+ }
330
+
331
+ return currentState;
332
+ }
333
+
334
+ /**
335
+ * Walk a tagged value's content.
336
+ *
337
+ * @internal
338
+ */
339
+ function walkTagged<State>(
340
+ cbor: CborTaggedType,
341
+ level: number,
342
+ state: State,
343
+ visitor: Visitor<State>,
344
+ ): State {
345
+ return walkInternal(cbor.value, level + 1, { type: EdgeType.TaggedContent }, state, visitor);
346
+ }
347
+
348
+ /**
349
+ * Helper: Count all elements in a CBOR tree.
350
+ *
351
+ * @param cbor - The CBOR value to count
352
+ * @returns Total number of elements visited
353
+ *
354
+ * @example
355
+ * ```typescript
356
+ * const structure = cbor([1, 2, [3, 4]]);
357
+ * const count = countElements(structure);
358
+ * console.log(count); // 6 (array, 1, 2, inner array, 3, 4)
359
+ * ```
360
+ */
361
+ export const countElements = (cbor: Cbor): number => {
362
+ interface CountState {
363
+ count: number;
364
+ }
365
+
366
+ const result = walk<CountState>(cbor, { count: 0 }, (_element, _level, _edge, state) => {
367
+ return [{ count: state.count + 1 }, false];
368
+ });
369
+
370
+ return result.count;
371
+ };
372
+
373
+ /**
374
+ * Helper: Collect all elements at a specific depth level.
375
+ *
376
+ * @param cbor - The CBOR value to traverse
377
+ * @param targetLevel - The depth level to collect (0 = root)
378
+ * @returns Array of CBOR values at the target level
379
+ *
380
+ * @example
381
+ * ```typescript
382
+ * const structure = cbor([[1, 2], [3, 4]]);
383
+ * const level1 = collectAtLevel(structure, 1);
384
+ * // Returns: [[1, 2], [3, 4]]
385
+ * const level2 = collectAtLevel(structure, 2);
386
+ * // Returns: [1, 2, 3, 4]
387
+ * ```
388
+ */
389
+ export const collectAtLevel = (cbor: Cbor, targetLevel: number): Cbor[] => {
390
+ interface CollectState {
391
+ items: Cbor[];
392
+ }
393
+
394
+ const result = walk<CollectState>(cbor, { items: [] }, (element, level, _edge, state) => {
395
+ if (level === targetLevel && element.type === "single") {
396
+ return [{ items: [...state.items, element.cbor] }, false];
397
+ }
398
+ return [state, false];
399
+ });
400
+
401
+ return result.items;
402
+ };
403
+
404
+ /**
405
+ * Helper: Find first element matching a predicate.
406
+ *
407
+ * @template T - Type of extracted value
408
+ * @param cbor - The CBOR value to search
409
+ * @param predicate - Function to test each element
410
+ * @returns First matching element, or undefined if not found
411
+ *
412
+ * @example
413
+ * ```typescript
414
+ * const structure = cbor({ users: [
415
+ * { name: 'Alice', age: 30 },
416
+ * { name: 'Bob', age: 25 }
417
+ * ]});
418
+ *
419
+ * const bob = findFirst(structure, (element) => {
420
+ * if (element.type === 'single' &&
421
+ * element.cbor.type === MajorType.Text &&
422
+ * element.cbor.value === 'Bob') {
423
+ * return true;
424
+ * }
425
+ * return false;
426
+ * });
427
+ * ```
428
+ */
429
+ export const findFirst = (
430
+ cbor: Cbor,
431
+ predicate: (element: WalkElement) => boolean,
432
+ ): Cbor | undefined => {
433
+ interface FindState {
434
+ found?: Cbor;
435
+ }
436
+
437
+ const result = walk<FindState>(cbor, {}, (element, _level, _edge, state) => {
438
+ if (state.found !== undefined) {
439
+ // Already found, stop descending
440
+ return [state, true];
441
+ }
442
+
443
+ if (predicate(element)) {
444
+ if (element.type === "single") {
445
+ return [{ found: element.cbor }, true]; // Stop after finding
446
+ }
447
+ // Matched but not a single element, stop anyway
448
+ return [state, true];
449
+ }
450
+
451
+ return [state, false];
452
+ });
453
+
454
+ return result.found;
455
+ };
456
+
457
+ /**
458
+ * Helper: Collect all text strings in a CBOR tree.
459
+ *
460
+ * @param cbor - The CBOR value to traverse
461
+ * @returns Array of all text string values found
462
+ *
463
+ * @example
464
+ * ```typescript
465
+ * const doc = cbor({
466
+ * title: 'Document',
467
+ * tags: ['urgent', 'draft'],
468
+ * author: { name: 'Alice' }
469
+ * });
470
+ *
471
+ * const texts = collectAllText(doc);
472
+ * // Returns: ['Document', 'urgent', 'draft', 'Alice']
473
+ * ```
474
+ */
475
+ export const collectAllText = (cbor: Cbor): string[] => {
476
+ interface TextState {
477
+ texts: string[];
478
+ }
479
+
480
+ const result = walk<TextState>(cbor, { texts: [] }, (element, _level, _edge, state) => {
481
+ if (element.type === "single" && element.cbor.type === MajorType.Text) {
482
+ return [{ texts: [...state.texts, element.cbor.value] }, false];
483
+ }
484
+ return [state, false];
485
+ });
486
+
487
+ return result.texts;
488
+ };
489
+
490
+ /**
491
+ * Helper: Get the maximum depth of a CBOR tree.
492
+ *
493
+ * @param cbor - The CBOR value to measure
494
+ * @returns Maximum depth (0 for leaf values, 1+ for containers)
495
+ *
496
+ * @example
497
+ * ```typescript
498
+ * const flat = cbor([1, 2, 3]);
499
+ * console.log(maxDepth(flat)); // 1
500
+ *
501
+ * const nested = cbor([[[1]]]);
502
+ * console.log(maxDepth(nested)); // 3
503
+ * ```
504
+ */
505
+ export const maxDepth = (cbor: Cbor): number => {
506
+ interface DepthState {
507
+ maxDepth: number;
508
+ }
509
+
510
+ const result = walk<DepthState>(cbor, { maxDepth: 0 }, (_element, level, _edge, state) => {
511
+ const newMaxDepth = Math.max(state.maxDepth, level);
512
+ return [{ maxDepth: newMaxDepth }, false];
513
+ });
514
+
515
+ return result.maxDepth;
516
+ };