@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.
- package/LICENSE +48 -0
- package/README.md +13 -0
- package/dist/index.cjs +9151 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +3107 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +3107 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.iife.js +9155 -0
- package/dist/index.iife.js.map +1 -0
- package/dist/index.mjs +9027 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +80 -0
- package/src/.claude-flow/metrics/agent-metrics.json +1 -0
- package/src/.claude-flow/metrics/performance.json +87 -0
- package/src/.claude-flow/metrics/task-metrics.json +10 -0
- package/src/byte-string.ts +300 -0
- package/src/cbor-codable.ts +170 -0
- package/src/cbor-tagged-codable.ts +72 -0
- package/src/cbor-tagged-decodable.ts +184 -0
- package/src/cbor-tagged-encodable.ts +138 -0
- package/src/cbor-tagged.ts +104 -0
- package/src/cbor.ts +869 -0
- package/src/conveniences.ts +840 -0
- package/src/date.ts +553 -0
- package/src/decode.ts +276 -0
- package/src/diag.ts +462 -0
- package/src/dump.ts +277 -0
- package/src/error.ts +259 -0
- package/src/exact.ts +714 -0
- package/src/float.ts +279 -0
- package/src/global.d.ts +34 -0
- package/src/globals.d.ts +0 -0
- package/src/index.ts +180 -0
- package/src/map.ts +308 -0
- package/src/prelude.ts +70 -0
- package/src/set.ts +515 -0
- package/src/simple.ts +153 -0
- package/src/stdlib.ts +55 -0
- package/src/string-util.ts +55 -0
- package/src/tag.ts +53 -0
- package/src/tags-store.ts +294 -0
- package/src/tags.ts +231 -0
- package/src/varint.ts +124 -0
- 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
|
+
};
|