@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,56 @@
1
+ /**
2
+ * NACHOS Parser (Decoder) Module
3
+ *
4
+ * @module @marcuspuchalla/nachos/parser
5
+ */
6
+
7
+ // Export parser composables
8
+ export { useCborParser } from './composables/useCborParser'
9
+ export { useCborInteger } from './composables/useCborInteger'
10
+ export { useCborString } from './composables/useCborString'
11
+ export { useCborCollection } from './composables/useCborCollection'
12
+ export { useCborFloat } from './composables/useCborFloat'
13
+ export { useCborTag } from './composables/useCborTag'
14
+
15
+ // Export types
16
+ export type {
17
+ ParseResult,
18
+ ParseResultWithMap,
19
+ ParseOptions,
20
+ ParseError,
21
+ ParserLimits,
22
+ CborContext,
23
+ CborValue,
24
+ CborMap,
25
+ TaggedValue,
26
+ PlutusData,
27
+ PlutusConstr,
28
+ PlutusMap,
29
+ PlutusList,
30
+ PlutusInt,
31
+ PlutusBytes,
32
+ SourceMapEntry,
33
+ Result,
34
+ DupMapKeyMode
35
+ } from './types'
36
+
37
+ // Export constants and enums
38
+ export {
39
+ DEFAULT_OPTIONS,
40
+ DEFAULT_LIMITS,
41
+ CborMajorType,
42
+ CborAdditionalInfo,
43
+ CborSimpleValue,
44
+ CborTag
45
+ } from './types'
46
+
47
+ // Export utility functions
48
+ export {
49
+ hexToBytes,
50
+ bytesToHex,
51
+ readByte,
52
+ readUint,
53
+ readBigUint,
54
+ extractCborHeader,
55
+ validateUtf8Strict
56
+ } from './utils'
@@ -0,0 +1,371 @@
1
+ /**
2
+ * CBOR Parser Type Definitions
3
+ * Following RFC 8949 specification
4
+ */
5
+
6
+ /**
7
+ * Parser resource limits for DoS protection
8
+ */
9
+ export interface ParserLimits {
10
+ /** Maximum input size in bytes (default: 10 MB) */
11
+ maxInputSize?: number
12
+ /** Maximum output size in bytes (default: 100 MB) */
13
+ maxOutputSize?: number
14
+ /** Maximum string length in bytes (default: 1 MB) */
15
+ maxStringLength?: number
16
+ /** Maximum array length (default: 10,000) */
17
+ maxArrayLength?: number
18
+ /** Maximum map size (default: 10,000) */
19
+ maxMapSize?: number
20
+ /** Maximum nesting depth for arrays/maps (default: 100) */
21
+ maxDepth?: number
22
+ /** Maximum tag nesting depth (default: 100) - Prevents RUSTSEC-2019-0025 */
23
+ maxTagDepth?: number
24
+ /** Maximum bignum size in bytes for tags 2/3 (default: 1024 bytes = 8192 bits) - Prevents CVE-2020-28491 */
25
+ maxBignumBytes?: number
26
+ /** Maximum parse time in milliseconds (default: 1000) */
27
+ maxParseTime?: number
28
+ }
29
+
30
+ /**
31
+ * Duplicate map key handling modes
32
+ * Following RFC 8949 Section 5.6 guidance
33
+ */
34
+ export type DupMapKeyMode = 'allow' | 'warn' | 'reject'
35
+
36
+ /**
37
+ * Parser options for controlling behavior
38
+ */
39
+ export interface ParseOptions {
40
+ /** Enable strict Cardano mode (all validations) */
41
+ strict?: boolean
42
+ /** Validate canonical encoding (shortest form, sorted maps) */
43
+ validateCanonical?: boolean
44
+ /** Allow indefinite-length encoding (false in strict mode) */
45
+ allowIndefinite?: boolean
46
+ /**
47
+ * Duplicate map key handling (RFC 8949 Section 5.6)
48
+ * - 'allow': Allow duplicates (default, permissive)
49
+ * - 'warn': Warn on duplicates but continue parsing
50
+ * - 'reject': Throw error on duplicate keys (strict mode)
51
+ */
52
+ dupMapKeyMode?: DupMapKeyMode
53
+ /** Validate UTF-8 strictly (reject overlongs) */
54
+ validateUtf8Strict?: boolean
55
+ /** Validate set uniqueness (Tag 258 - reject duplicates) */
56
+ validateSetUniqueness?: boolean
57
+ /** Validate semantic tag constraints (Tag 4, Tag 5 array structure) */
58
+ validateTagSemantics?: boolean
59
+ /** Validate Plutus constructor semantics (Tags 102, 121-127, 1280-1400) */
60
+ validatePlutusSemantics?: boolean
61
+ /** Resource limits */
62
+ limits?: ParserLimits
63
+ }
64
+
65
+ /**
66
+ * Default resource limits for parser
67
+ */
68
+ export const DEFAULT_LIMITS: Required<ParserLimits> = {
69
+ maxInputSize: 10 * 1024 * 1024, // 10 MB
70
+ maxOutputSize: 100 * 1024 * 1024, // 100 MB
71
+ maxStringLength: 1024 * 1024, // 1 MB
72
+ maxArrayLength: 10000,
73
+ maxMapSize: 10000,
74
+ maxDepth: 100, // Collection nesting depth (increased for compatibility)
75
+ maxTagDepth: 100, // Tag nesting depth (RUSTSEC-2019-0025 mitigation)
76
+ maxBignumBytes: 1024, // 1 KB = 8192 bits (CVE-2020-28491 mitigation)
77
+ maxParseTime: 1000 // 1 second
78
+ }
79
+
80
+ /**
81
+ * Default parse options
82
+ */
83
+ export const DEFAULT_OPTIONS: Required<ParseOptions> = {
84
+ strict: false,
85
+ validateCanonical: false,
86
+ allowIndefinite: true,
87
+ dupMapKeyMode: 'allow',
88
+ validateUtf8Strict: false,
89
+ validateSetUniqueness: false,
90
+ validateTagSemantics: false,
91
+ validatePlutusSemantics: false,
92
+ limits: DEFAULT_LIMITS
93
+ }
94
+
95
+ /**
96
+ * Parsing context that tracks state during CBOR decoding
97
+ */
98
+ export interface CborContext {
99
+ /** Raw byte buffer */
100
+ buffer: Uint8Array
101
+ /** Current byte offset */
102
+ offset: number
103
+ /** Source map entries for visualization */
104
+ sourceMap: SourceMapEntry[]
105
+ /** Current nesting depth for arrays/maps (for limit checking) */
106
+ currentDepth?: number
107
+ /** Current tag nesting depth (for tag limit checking) */
108
+ currentTagDepth?: number
109
+ /** Parse start time (for timeout checking) */
110
+ startTime?: number
111
+ /** Bytes allocated (for output size tracking) */
112
+ bytesAllocated?: number
113
+ /** Parser options */
114
+ options?: ParseOptions
115
+ }
116
+
117
+ /**
118
+ * Result of parsing a CBOR value
119
+ */
120
+ export interface ParseResult {
121
+ /** Decoded CBOR value */
122
+ value: CborValue
123
+ /** Number of bytes consumed */
124
+ bytesRead: number
125
+ }
126
+
127
+ /**
128
+ * Parse result with source mapping information
129
+ */
130
+ export interface ParseResultWithMap extends ParseResult {
131
+ /** Source map for hex/JSON linking */
132
+ sourceMap: SourceMapEntry[]
133
+ }
134
+
135
+ /**
136
+ * Source map entry for bi-directional visualization
137
+ */
138
+ export interface SourceMapEntry {
139
+ /** JSON Pointer path (RFC 6901) */
140
+ path: string
141
+ /** Starting byte offset */
142
+ start: number
143
+ /** Ending byte offset (exclusive) */
144
+ end: number
145
+ /** CBOR major type (0-7) */
146
+ majorType: number
147
+ /** Human-readable type description */
148
+ type: string
149
+ /** Parent entry path (for nested structures like tags) */
150
+ parent?: string
151
+ /** Child entry paths (for container types) */
152
+ children?: string[]
153
+ /** Whether this entry represents a header (initial byte + length info) */
154
+ isHeader?: boolean
155
+ /** Whether this entry represents content (payload data) */
156
+ isContent?: boolean
157
+ /** Byte offset where header ends and content begins */
158
+ headerEnd?: number
159
+ /** JSON Pointer path to the content portion (for split header/content entries) */
160
+ contentPath?: string
161
+ }
162
+
163
+ /**
164
+ * CBOR byte string (can be definite or indefinite length)
165
+ */
166
+ export interface CborByteString {
167
+ readonly type: 'cbor-byte-string'
168
+ readonly bytes: Uint8Array
169
+ readonly chunks?: Uint8Array[] // Original chunks for indefinite byte strings
170
+ [INDEFINITE_SYMBOL]?: boolean
171
+ }
172
+
173
+ /**
174
+ * CBOR text string (can be definite or indefinite length)
175
+ */
176
+ export interface CborTextString {
177
+ readonly type: 'cbor-text-string'
178
+ readonly text: string
179
+ readonly chunks?: string[] // Original chunks for indefinite text strings
180
+ [INDEFINITE_SYMBOL]?: boolean
181
+ }
182
+
183
+ /**
184
+ * All possible CBOR values
185
+ */
186
+ export type CborValue =
187
+ | number
188
+ | bigint
189
+ | string
190
+ | boolean
191
+ | null
192
+ | undefined
193
+ | Uint8Array
194
+ | CborByteString
195
+ | CborTextString
196
+ | CborValue[]
197
+ | CborMap
198
+ | TaggedValue
199
+ | SimpleValue
200
+
201
+ /**
202
+ * Symbol to mark arrays/maps as indefinite-length encoded
203
+ * This allows round-trip preservation of encoding style
204
+ */
205
+ export const INDEFINITE_SYMBOL = Symbol('cbor.indefinite')
206
+
207
+ /**
208
+ * Symbol used to store all map entries including duplicates
209
+ * This allows byte-perfect round-trip when CBOR maps have duplicate keys
210
+ * Stores: Array<[key: CborValue, value: CborValue]>
211
+ */
212
+ export const ALL_ENTRIES_SYMBOL = Symbol('cbor.allEntries')
213
+
214
+ /**
215
+ * CBOR map type (Map with any CBOR value as keys)
216
+ *
217
+ * Uses JavaScript Map to preserve key types (integers, Uint8Arrays, etc.)
218
+ * This is essential for:
219
+ * - Cardano transactions (use integer keys 0-18)
220
+ * - Round-trip encoding (must preserve exact key types)
221
+ * - CBOR specification compliance (maps can have any type as keys)
222
+ *
223
+ * @example
224
+ * ```typescript
225
+ * // Cardano transaction body
226
+ * new Map([
227
+ * [0, inputs], // Integer key
228
+ * [1, outputs], // Integer key
229
+ * [2, 1000000] // Integer key (fee)
230
+ * ])
231
+ * ```
232
+ */
233
+ export type CborMap = Map<CborValue, CborValue> & { [INDEFINITE_SYMBOL]?: boolean }
234
+
235
+ /**
236
+ * CBOR array type with optional indefinite-length marker
237
+ */
238
+ export type CborArray = CborValue[] & { [INDEFINITE_SYMBOL]?: boolean }
239
+
240
+ /**
241
+ * Tagged CBOR value (Major Type 6)
242
+ */
243
+ export interface TaggedValue {
244
+ tag: number
245
+ value: CborValue
246
+ plutus?: PlutusConstr // Decoded Plutus constructor (for tags 102, 121-127, 1280-1400)
247
+ }
248
+
249
+ /**
250
+ * Simple CBOR value (Major Type 7, unassigned simple values)
251
+ */
252
+ export interface SimpleValue {
253
+ simpleValue: number
254
+ }
255
+
256
+ /**
257
+ * Plutus Data types for Cardano smart contracts
258
+ */
259
+ export type PlutusData =
260
+ | PlutusConstr
261
+ | PlutusMap
262
+ | PlutusList
263
+ | PlutusInt
264
+ | PlutusBytes
265
+
266
+ /**
267
+ * Plutus Constructor (algebraic data type)
268
+ */
269
+ export interface PlutusConstr {
270
+ constructor: number
271
+ fields: PlutusData[]
272
+ }
273
+
274
+ /**
275
+ * Plutus Map (key-value pairs)
276
+ */
277
+ export interface PlutusMap {
278
+ entries: Array<[PlutusData, PlutusData]>
279
+ }
280
+
281
+ /**
282
+ * Plutus List (sequential collection)
283
+ */
284
+ export type PlutusList = PlutusData[]
285
+
286
+ /**
287
+ * Plutus Integer (arbitrary precision)
288
+ */
289
+ export type PlutusInt = number | bigint
290
+
291
+ /**
292
+ * Plutus Bytes (bounded byte string, max 64 bytes)
293
+ */
294
+ export type PlutusBytes = Uint8Array
295
+
296
+ /**
297
+ * CBOR Major Types (0-7)
298
+ */
299
+ export enum CborMajorType {
300
+ UNSIGNED_INT = 0,
301
+ NEGATIVE_INT = 1,
302
+ BYTE_STRING = 2,
303
+ TEXT_STRING = 3,
304
+ ARRAY = 4,
305
+ MAP = 5,
306
+ TAG = 6,
307
+ SIMPLE = 7
308
+ }
309
+
310
+ /**
311
+ * Additional Information values
312
+ */
313
+ export enum CborAdditionalInfo {
314
+ DIRECT = 23, // Values 0-23
315
+ ONE_BYTE = 24, // 1 byte follows
316
+ TWO_BYTES = 25, // 2 bytes follow
317
+ FOUR_BYTES = 26, // 4 bytes follow
318
+ EIGHT_BYTES = 27, // 8 bytes follow
319
+ INDEFINITE = 31 // Indefinite length
320
+ }
321
+
322
+ /**
323
+ * Simple values (Major Type 7)
324
+ */
325
+ export enum CborSimpleValue {
326
+ FALSE = 20,
327
+ TRUE = 21,
328
+ NULL = 22,
329
+ UNDEFINED = 23,
330
+ FLOAT16 = 25,
331
+ FLOAT32 = 26,
332
+ FLOAT64 = 27,
333
+ BREAK = 31
334
+ }
335
+
336
+ /**
337
+ * Common semantic tags
338
+ */
339
+ export enum CborTag {
340
+ DATE_TIME_STRING = 0,
341
+ EPOCH_DATE_TIME = 1,
342
+ POSITIVE_BIGNUM = 2,
343
+ NEGATIVE_BIGNUM = 3,
344
+ DECIMAL_FRACTION = 4,
345
+ BIGFLOAT = 5,
346
+ BASE64URL = 21,
347
+ BASE64 = 22,
348
+ BASE16 = 23,
349
+ CBOR_ENCODED = 24,
350
+ URI = 32,
351
+ BASE64URL_NO_PAD = 33,
352
+ BASE64_NO_PAD = 34,
353
+ REGEXP = 35,
354
+ MIME_MESSAGE = 36
355
+ }
356
+
357
+ /**
358
+ * Parse error types
359
+ */
360
+ export interface ParseError {
361
+ type: 'INVALID_HEX' | 'UNEXPECTED_EOF' | 'INVALID_CBOR' | 'UNSUPPORTED_TYPE'
362
+ message: string
363
+ offset?: number
364
+ }
365
+
366
+ /**
367
+ * Result type for operations that can fail
368
+ */
369
+ export type Result<T, E> =
370
+ | { success: true; value: T }
371
+ | { success: false; error: E }
@@ -0,0 +1,259 @@
1
+ /**
2
+ * PathBuilder - Utilities for creating and manipulating source map paths
3
+ *
4
+ * Path format follows JSON Pointer (RFC 6901) with CBOR-specific extensions:
5
+ * - Array indices: [0], [1], [2]
6
+ * - Object/Map keys: .key, .nested.key
7
+ * - Special markers: [#header], [#content], [#value]
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * import { PathBuilder } from './pathBuilder'
12
+ *
13
+ * const path = PathBuilder.arrayIndex('', 0) // "[0]"
14
+ * const nested = PathBuilder.arrayIndex(path, 1) // "[0][1]"
15
+ * const key = PathBuilder.mapKey(nested, 'amount') // "[0][1].amount"
16
+ * const header = PathBuilder.header(key) // "[0][1].amount[#header]"
17
+ * ```
18
+ */
19
+
20
+ /**
21
+ * PathBuilder utility object with methods for creating and manipulating paths
22
+ */
23
+ export const PathBuilder = {
24
+ /**
25
+ * Create root path (empty string)
26
+ */
27
+ root: (): string => '',
28
+
29
+ /**
30
+ * Create path for array index
31
+ * @param parent - Parent path
32
+ * @param index - Array index (0-based)
33
+ * @returns Path string like "[0]" or "[0][1]"
34
+ */
35
+ arrayIndex: (parent: string, index: number): string => `${parent}[${index}]`,
36
+
37
+ /**
38
+ * Create path for map/object key
39
+ * @param parent - Parent path
40
+ * @param key - Key name (string or number)
41
+ * @returns Path string like ".key" or "[0].key"
42
+ */
43
+ mapKey: (parent: string, key: string | number): string => {
44
+ const keyStr = String(key)
45
+ // Escape special characters in key names
46
+ const escapedKey = keyStr.replace(/[.\[\]\\]/g, '\\$&')
47
+ return parent ? `${parent}.${escapedKey}` : `.${escapedKey}`
48
+ },
49
+
50
+ /**
51
+ * Create path for map key by index (for non-string keys)
52
+ * @param parent - Parent path
53
+ * @param index - Key index in map
54
+ * @returns Path string like "[#key:0]"
55
+ */
56
+ mapKeyIndex: (parent: string, index: number): string =>
57
+ `${parent}[#key:${index}]`,
58
+
59
+ /**
60
+ * Create path for header portion of a value
61
+ * Used for byte strings, text strings, arrays, maps, tags
62
+ * @param parent - Parent path
63
+ * @returns Path string like "[0][#header]"
64
+ */
65
+ header: (parent: string): string => `${parent}[#header]`,
66
+
67
+ /**
68
+ * Create path for content portion of a value
69
+ * Used for byte strings and text strings
70
+ * @param parent - Parent path
71
+ * @returns Path string like "[0][#content]"
72
+ */
73
+ content: (parent: string): string => `${parent}[#content]`,
74
+
75
+ /**
76
+ * Create path for tagged value
77
+ * @param parent - Parent path
78
+ * @returns Path string like "[0][#value]"
79
+ */
80
+ tagValue: (parent: string): string => `${parent}[#value]`,
81
+
82
+ /**
83
+ * Normalize a path by removing special markers (#header, #content, #value)
84
+ * Used for matching paths across different representations
85
+ * @param path - Path to normalize
86
+ * @returns Normalized path without special markers
87
+ */
88
+ normalize: (path: string): string => {
89
+ return path.replace(/\[#(header|content|value)\]$/, '')
90
+ },
91
+
92
+ /**
93
+ * Check if path is a header path
94
+ * @param path - Path to check
95
+ * @returns True if path ends with [#header]
96
+ */
97
+ isHeader: (path: string): boolean => path.endsWith('[#header]'),
98
+
99
+ /**
100
+ * Check if path is a content path
101
+ * @param path - Path to check
102
+ * @returns True if path ends with [#content]
103
+ */
104
+ isContent: (path: string): boolean => path.endsWith('[#content]'),
105
+
106
+ /**
107
+ * Check if path is a tag value path
108
+ * @param path - Path to check
109
+ * @returns True if path ends with [#value]
110
+ */
111
+ isTagValue: (path: string): boolean => path.endsWith('[#value]'),
112
+
113
+ /**
114
+ * Check if path has any special marker
115
+ * @param path - Path to check
116
+ * @returns True if path ends with any special marker
117
+ */
118
+ hasMarker: (path: string): boolean =>
119
+ /\[#(header|content|value)\]$/.test(path),
120
+
121
+ /**
122
+ * Get parent path (one level up)
123
+ * @param path - Path to get parent of
124
+ * @returns Parent path or null if already at root
125
+ */
126
+ getParent: (path: string): string | null => {
127
+ // First normalize to remove markers
128
+ const normalized = PathBuilder.normalize(path)
129
+ if (normalized === '') return null
130
+
131
+ // Match array index or object key at end
132
+ const arrayMatch = normalized.match(/^(.+)\[\d+\]$/)
133
+ if (arrayMatch && arrayMatch[1] !== undefined) return arrayMatch[1]
134
+
135
+ const keyMatch = normalized.match(/^(.+)\.[^.]+$/)
136
+ if (keyMatch && keyMatch[1] !== undefined) return keyMatch[1]
137
+
138
+ // Root level item
139
+ if (normalized.startsWith('[') || normalized.startsWith('.')) {
140
+ return ''
141
+ }
142
+
143
+ return null
144
+ },
145
+
146
+ /**
147
+ * Get the depth (nesting level) of a path
148
+ * @param path - Path to measure
149
+ * @returns Number indicating nesting depth (0 for root)
150
+ */
151
+ getDepth: (path: string): number => {
152
+ const normalized = PathBuilder.normalize(path)
153
+ if (normalized === '') return 0
154
+
155
+ // Count array indices and object keys
156
+ const arrayDepth = (normalized.match(/\[\d+\]/g) || []).length
157
+ const keyDepth = (normalized.match(/\./g) || []).length
158
+
159
+ return arrayDepth + keyDepth
160
+ },
161
+
162
+ /**
163
+ * Parse a path into its components
164
+ * @param path - Path to parse
165
+ * @returns Array of path segments
166
+ */
167
+ parse: (path: string): Array<{ type: 'index' | 'key' | 'marker'; value: string | number }> => {
168
+ const normalized = PathBuilder.normalize(path)
169
+ const segments: Array<{ type: 'index' | 'key' | 'marker'; value: string | number }> = []
170
+
171
+ // Check for marker
172
+ const markerMatch = path.match(/\[#(header|content|value)\]$/)
173
+
174
+ // Parse the normalized path
175
+ let remaining = normalized
176
+ while (remaining) {
177
+ // Array index
178
+ const indexMatch = remaining.match(/^\[(\d+)\]/)
179
+ if (indexMatch && indexMatch[1] !== undefined) {
180
+ segments.push({ type: 'index', value: parseInt(indexMatch[1], 10) })
181
+ remaining = remaining.slice(indexMatch[0].length)
182
+ continue
183
+ }
184
+
185
+ // Object key
186
+ const keyMatch = remaining.match(/^\.([^.\[\]]+)/)
187
+ if (keyMatch && keyMatch[1] !== undefined) {
188
+ // Unescape special characters
189
+ const key = keyMatch[1].replace(/\\(.)/g, '$1')
190
+ segments.push({ type: 'key', value: key })
191
+ remaining = remaining.slice(keyMatch[0].length)
192
+ continue
193
+ }
194
+
195
+ // Unknown format, break to avoid infinite loop
196
+ break
197
+ }
198
+
199
+ // Add marker if present
200
+ if (markerMatch && markerMatch[1] !== undefined) {
201
+ segments.push({ type: 'marker', value: markerMatch[1] })
202
+ }
203
+
204
+ return segments
205
+ },
206
+
207
+ /**
208
+ * Build a path from segments
209
+ * @param segments - Array of path segments
210
+ * @returns Path string
211
+ */
212
+ build: (segments: Array<{ type: 'index' | 'key' | 'marker'; value: string | number }>): string => {
213
+ let path = ''
214
+
215
+ for (const segment of segments) {
216
+ if (segment.type === 'index') {
217
+ path += `[${segment.value}]`
218
+ } else if (segment.type === 'key') {
219
+ const escapedKey = String(segment.value).replace(/[.\[\]\\]/g, '\\$&')
220
+ path += `.${escapedKey}`
221
+ } else if (segment.type === 'marker') {
222
+ path += `[#${segment.value}]`
223
+ }
224
+ }
225
+
226
+ return path
227
+ },
228
+
229
+ /**
230
+ * Join multiple path segments
231
+ * @param paths - Path segments to join
232
+ * @returns Combined path
233
+ */
234
+ join: (...paths: string[]): string => {
235
+ return paths.filter(p => p !== '').join('')
236
+ },
237
+
238
+ /**
239
+ * Check if a path is a descendant of another path
240
+ * @param path - Path to check
241
+ * @param ancestor - Potential ancestor path
242
+ * @returns True if path is a descendant of ancestor
243
+ */
244
+ isDescendantOf: (path: string, ancestor: string): boolean => {
245
+ const normalizedPath = PathBuilder.normalize(path)
246
+ const normalizedAncestor = PathBuilder.normalize(ancestor)
247
+
248
+ if (normalizedAncestor === '') {
249
+ return normalizedPath !== ''
250
+ }
251
+
252
+ return normalizedPath.startsWith(normalizedAncestor) &&
253
+ normalizedPath.length > normalizedAncestor.length &&
254
+ (normalizedPath[normalizedAncestor.length] === '[' ||
255
+ normalizedPath[normalizedAncestor.length] === '.')
256
+ }
257
+ }
258
+
259
+ export default PathBuilder