@safeaccess/inline 0.1.1 → 0.1.2

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 (179) hide show
  1. package/.gitattributes +1 -1
  2. package/CHANGELOG.md +10 -5
  3. package/LICENSE +1 -1
  4. package/README.md +56 -14
  5. package/dist/accessors/abstract-accessor.d.ts +22 -10
  6. package/dist/accessors/abstract-accessor.js +21 -8
  7. package/dist/accessors/abstract-integration-accessor.d.ts +22 -0
  8. package/dist/accessors/abstract-integration-accessor.js +23 -0
  9. package/dist/accessors/formats/any-accessor.d.ts +10 -8
  10. package/dist/accessors/formats/any-accessor.js +9 -8
  11. package/dist/accessors/formats/array-accessor.d.ts +2 -0
  12. package/dist/accessors/formats/array-accessor.js +2 -0
  13. package/dist/accessors/formats/env-accessor.d.ts +2 -0
  14. package/dist/accessors/formats/env-accessor.js +2 -0
  15. package/dist/accessors/formats/ini-accessor.d.ts +2 -0
  16. package/dist/accessors/formats/ini-accessor.js +2 -0
  17. package/dist/accessors/formats/json-accessor.d.ts +2 -0
  18. package/dist/accessors/formats/json-accessor.js +2 -0
  19. package/dist/accessors/formats/ndjson-accessor.d.ts +2 -0
  20. package/dist/accessors/formats/ndjson-accessor.js +2 -0
  21. package/dist/accessors/formats/object-accessor.d.ts +2 -0
  22. package/dist/accessors/formats/object-accessor.js +2 -0
  23. package/dist/accessors/formats/xml-accessor.d.ts +2 -0
  24. package/dist/accessors/formats/xml-accessor.js +2 -0
  25. package/dist/accessors/formats/yaml-accessor.d.ts +3 -1
  26. package/dist/accessors/formats/yaml-accessor.js +4 -2
  27. package/dist/cache/simple-path-cache.d.ts +51 -0
  28. package/dist/cache/simple-path-cache.js +72 -0
  29. package/dist/contracts/accessors-interface.d.ts +2 -0
  30. package/dist/contracts/factory-accessors-interface.d.ts +2 -0
  31. package/dist/contracts/filter-evaluator-interface.d.ts +28 -0
  32. package/dist/contracts/filter-evaluator-interface.js +1 -0
  33. package/dist/contracts/parse-integration-interface.d.ts +2 -0
  34. package/dist/contracts/parser-interface.d.ts +92 -0
  35. package/dist/contracts/parser-interface.js +1 -0
  36. package/dist/contracts/path-cache-interface.d.ts +7 -6
  37. package/dist/contracts/readable-accessors-interface.d.ts +11 -6
  38. package/dist/contracts/security-guard-interface.d.ts +2 -0
  39. package/dist/contracts/security-parser-interface.d.ts +2 -0
  40. package/dist/contracts/validatable-parser-interface.d.ts +59 -0
  41. package/dist/contracts/validatable-parser-interface.js +1 -0
  42. package/dist/contracts/writable-accessors-interface.d.ts +5 -0
  43. package/dist/core/accessor-factory.d.ts +124 -0
  44. package/dist/core/accessor-factory.js +157 -0
  45. package/dist/core/dot-notation-parser.d.ts +34 -5
  46. package/dist/core/dot-notation-parser.js +51 -10
  47. package/dist/core/inline-builder-accessor.d.ts +82 -0
  48. package/dist/core/inline-builder-accessor.js +107 -0
  49. package/dist/exceptions/accessor-exception.d.ts +9 -0
  50. package/dist/exceptions/accessor-exception.js +9 -0
  51. package/dist/exceptions/invalid-format-exception.d.ts +5 -0
  52. package/dist/exceptions/invalid-format-exception.js +5 -0
  53. package/dist/exceptions/parser-exception.d.ts +4 -0
  54. package/dist/exceptions/parser-exception.js +4 -0
  55. package/dist/exceptions/path-not-found-exception.d.ts +4 -0
  56. package/dist/exceptions/path-not-found-exception.js +4 -0
  57. package/dist/exceptions/readonly-violation-exception.d.ts +4 -0
  58. package/dist/exceptions/readonly-violation-exception.js +4 -0
  59. package/dist/exceptions/security-exception.d.ts +6 -0
  60. package/dist/exceptions/security-exception.js +6 -0
  61. package/dist/exceptions/unsupported-type-exception.d.ts +4 -0
  62. package/dist/exceptions/unsupported-type-exception.js +4 -0
  63. package/dist/exceptions/yaml-parse-exception.d.ts +4 -0
  64. package/dist/exceptions/yaml-parse-exception.js +4 -0
  65. package/dist/index.js +2 -1
  66. package/dist/inline.d.ts +22 -56
  67. package/dist/inline.js +39 -111
  68. package/dist/parser/xml-parser.js +23 -10
  69. package/dist/parser/yaml-parser.d.ts +54 -7
  70. package/dist/parser/yaml-parser.js +268 -51
  71. package/dist/path-query/segment-filter-parser.d.ts +142 -0
  72. package/dist/path-query/segment-filter-parser.js +384 -0
  73. package/dist/path-query/segment-parser.d.ts +98 -0
  74. package/dist/path-query/segment-parser.js +283 -0
  75. package/dist/path-query/segment-path-resolver.d.ts +149 -0
  76. package/dist/path-query/segment-path-resolver.js +351 -0
  77. package/dist/path-query/segment-type.d.ts +85 -0
  78. package/dist/path-query/segment-type.js +35 -0
  79. package/dist/security/forbidden-keys.d.ts +2 -2
  80. package/dist/security/forbidden-keys.js +5 -5
  81. package/dist/security/security-guard.d.ts +3 -1
  82. package/dist/security/security-guard.js +5 -2
  83. package/dist/security/security-parser.d.ts +10 -1
  84. package/dist/security/security-parser.js +10 -1
  85. package/dist/type-format.d.ts +2 -0
  86. package/dist/type-format.js +2 -0
  87. package/package.json +11 -3
  88. package/src/accessors/abstract-accessor.ts +23 -19
  89. package/src/accessors/abstract-integration-accessor.ts +27 -0
  90. package/src/accessors/formats/any-accessor.ts +11 -11
  91. package/src/accessors/formats/array-accessor.ts +2 -0
  92. package/src/accessors/formats/env-accessor.ts +2 -0
  93. package/src/accessors/formats/ini-accessor.ts +2 -0
  94. package/src/accessors/formats/json-accessor.ts +2 -0
  95. package/src/accessors/formats/ndjson-accessor.ts +2 -0
  96. package/src/accessors/formats/object-accessor.ts +2 -0
  97. package/src/accessors/formats/xml-accessor.ts +2 -0
  98. package/src/accessors/formats/yaml-accessor.ts +4 -2
  99. package/src/cache/simple-path-cache.ts +77 -0
  100. package/src/contracts/accessors-interface.ts +2 -0
  101. package/src/contracts/factory-accessors-interface.ts +2 -0
  102. package/src/contracts/filter-evaluator-interface.ts +30 -0
  103. package/src/contracts/parse-integration-interface.ts +2 -0
  104. package/src/contracts/parser-interface.ts +114 -0
  105. package/src/contracts/path-cache-interface.ts +8 -6
  106. package/src/contracts/readable-accessors-interface.ts +11 -6
  107. package/src/contracts/security-guard-interface.ts +2 -0
  108. package/src/contracts/security-parser-interface.ts +2 -0
  109. package/src/contracts/validatable-parser-interface.ts +64 -0
  110. package/src/contracts/writable-accessors-interface.ts +5 -0
  111. package/src/core/accessor-factory.ts +173 -0
  112. package/src/core/dot-notation-parser.ts +74 -11
  113. package/src/core/inline-builder-accessor.ts +163 -0
  114. package/src/exceptions/accessor-exception.ts +9 -0
  115. package/src/exceptions/invalid-format-exception.ts +5 -0
  116. package/src/exceptions/parser-exception.ts +4 -0
  117. package/src/exceptions/path-not-found-exception.ts +4 -0
  118. package/src/exceptions/readonly-violation-exception.ts +4 -0
  119. package/src/exceptions/security-exception.ts +6 -0
  120. package/src/exceptions/unsupported-type-exception.ts +4 -0
  121. package/src/exceptions/yaml-parse-exception.ts +4 -0
  122. package/src/index.ts +3 -1
  123. package/src/inline.ts +42 -120
  124. package/src/parser/xml-parser.ts +31 -10
  125. package/src/parser/yaml-parser.ts +310 -45
  126. package/src/path-query/segment-filter-parser.ts +444 -0
  127. package/src/path-query/segment-parser.ts +321 -0
  128. package/src/path-query/segment-path-resolver.ts +521 -0
  129. package/src/path-query/segment-type.ts +82 -0
  130. package/src/security/forbidden-keys.ts +5 -5
  131. package/src/security/security-guard.ts +7 -2
  132. package/src/security/security-parser.ts +18 -3
  133. package/src/type-format.ts +2 -0
  134. package/stryker.config.json +8 -10
  135. package/tests/accessors/abstract-accessor.test.ts +217 -0
  136. package/tests/accessors/abstract-integration-accessor.test.ts +37 -0
  137. package/tests/accessors/formats/any-accessor.test.ts +57 -0
  138. package/tests/accessors/formats/array-accessor.test.ts +42 -0
  139. package/tests/accessors/formats/env-accessor.test.ts +103 -0
  140. package/tests/accessors/formats/ini-accessor.test.ts +186 -0
  141. package/tests/accessors/{json-accessor.test.ts → formats/json-accessor.test.ts} +6 -6
  142. package/tests/accessors/formats/ndjson-accessor.test.ts +49 -0
  143. package/tests/accessors/formats/object-accessor.test.ts +172 -0
  144. package/tests/accessors/formats/xml-accessor.test.ts +162 -0
  145. package/tests/accessors/formats/yaml-accessor.test.ts +36 -0
  146. package/tests/cache/simple-path-cache.test.ts +168 -0
  147. package/tests/core/accessor-factory.test.ts +157 -0
  148. package/tests/core/dot-notation-parser-edge-cases.test.ts +415 -0
  149. package/tests/core/dot-notation-parser.test.ts +0 -288
  150. package/tests/core/inline-builder-accessor.test.ts +114 -0
  151. package/tests/exceptions/accessor-exception.test.ts +28 -0
  152. package/tests/exceptions/invalid-format-exception.test.ts +31 -0
  153. package/tests/exceptions/path-not-found-exception.test.ts +33 -0
  154. package/tests/exceptions/readonly-violation-exception.test.ts +35 -0
  155. package/tests/exceptions/security-exception.test.ts +33 -0
  156. package/tests/exceptions/unsupported-type-exception.test.ts +33 -0
  157. package/tests/exceptions/yaml-parse-exception.test.ts +38 -0
  158. package/tests/mocks/fake-path-cache.ts +4 -3
  159. package/tests/parity-from.test.ts +118 -0
  160. package/tests/parity.test.ts +227 -10
  161. package/tests/parser/xml-parser-mutations.test.ts +579 -0
  162. package/tests/parser/xml-parser-scanner.test.ts +332 -0
  163. package/tests/parser/xml-parser.test.ts +10 -334
  164. package/tests/parser/yaml-parser-mutations.test.ts +750 -0
  165. package/tests/parser/yaml-parser.test.ts +844 -18
  166. package/tests/path-query/segment-filter-parser-mutations.test.ts +735 -0
  167. package/tests/path-query/segment-filter-parser.test.ts +1091 -0
  168. package/tests/path-query/segment-parser-mutations.test.ts +539 -0
  169. package/tests/path-query/segment-parser.test.ts +606 -0
  170. package/tests/path-query/segment-path-resolver-mutations.test.ts +626 -0
  171. package/tests/path-query/segment-path-resolver.test.ts +1009 -0
  172. package/tests/security/security-guard-advanced.test.ts +413 -0
  173. package/tests/security/security-guard-forbidden-keys.test.ts +87 -0
  174. package/tests/security/security-guard.test.ts +3 -484
  175. package/tests/security/security-parser.test.ts +18 -14
  176. package/vitest.config.ts +3 -3
  177. package/benchmarks/get.bench.ts +0 -26
  178. package/benchmarks/parse.bench.ts +0 -41
  179. package/tests/accessors/accessors.test.ts +0 -1017
@@ -0,0 +1,64 @@
1
+ import type { ParserInterface } from './parser-interface.js';
2
+
3
+ /**
4
+ * Extended parser contract adding security validation capabilities.
5
+ *
6
+ * Adds structural validation and payload size assertion on top of
7
+ * the base {@link ParserInterface} CRUD operations.
8
+ *
9
+ * @internal Not part of the public API - used only by AbstractAccessor internally.
10
+ */
11
+ export interface ValidatableParserInterface extends ParserInterface {
12
+ /**
13
+ * Retrieve a value at the given path, throwing when not found.
14
+ *
15
+ * @param data - Source data object.
16
+ * @param path - Dot-notation path.
17
+ * @returns Resolved value.
18
+ *
19
+ * @throws {PathNotFoundException} When the path does not exist.
20
+ */
21
+ getStrict(data: Record<string, unknown>, path: string): unknown;
22
+
23
+ /**
24
+ * Validate data structure against security constraints.
25
+ *
26
+ * Assert key safety, maximum keys, and structural depth
27
+ * using configured security guards and parser options.
28
+ *
29
+ * @param data - Data to validate.
30
+ *
31
+ * @throws {SecurityException} When any constraint is violated.
32
+ */
33
+ validate(data: Record<string, unknown>): void;
34
+
35
+ /**
36
+ * Assert that a raw string payload does not exceed size limits.
37
+ *
38
+ * @param input - Raw input string to check.
39
+ *
40
+ * @throws {SecurityException} When the payload exceeds the configured maximum.
41
+ */
42
+ assertPayload(input: string): void;
43
+
44
+ /**
45
+ * Return the configured maximum structural nesting depth.
46
+ *
47
+ * Used by accessors that perform their own recursive traversal
48
+ * (e.g. ObjectAccessor) before the post-parse validation step runs.
49
+ *
50
+ * @returns Maximum allowed structural depth.
51
+ */
52
+ getMaxDepth(): number;
53
+
54
+ /**
55
+ * Return the configured maximum total key count.
56
+ *
57
+ * Used by format parsers that enforce a document element-count limit before
58
+ * structural traversal runs. Accessor implementations that wrap XML parsers
59
+ * can pass this value as an upper bound to prevent document-bombing attacks.
60
+ *
61
+ * @returns Maximum allowed key count.
62
+ */
63
+ getMaxKeys(): number;
64
+ }
@@ -3,6 +3,11 @@
3
3
  *
4
4
  * All mutations return a new instance with the modification applied,
5
5
  * preserving the original accessor instance.
6
+ *
7
+ * @api
8
+ *
9
+ * @see AccessorsInterface Composite interface extending this contract.
10
+ * @see AbstractAccessor Base implementation enforcing readonly guards.
6
11
  */
7
12
  export interface WritableAccessorsInterface {
8
13
  /**
@@ -0,0 +1,173 @@
1
+ import type { ValidatableParserInterface } from '../contracts/validatable-parser-interface.js';
2
+ import type { ParseIntegrationInterface } from '../contracts/parse-integration-interface.js';
3
+ import { AbstractAccessor } from '../accessors/abstract-accessor.js';
4
+ import { ArrayAccessor } from '../accessors/formats/array-accessor.js';
5
+ import { ObjectAccessor } from '../accessors/formats/object-accessor.js';
6
+ import { JsonAccessor } from '../accessors/formats/json-accessor.js';
7
+ import { XmlAccessor } from '../accessors/formats/xml-accessor.js';
8
+ import { YamlAccessor } from '../accessors/formats/yaml-accessor.js';
9
+ import { IniAccessor } from '../accessors/formats/ini-accessor.js';
10
+ import { EnvAccessor } from '../accessors/formats/env-accessor.js';
11
+ import { NdjsonAccessor } from '../accessors/formats/ndjson-accessor.js';
12
+ import { AnyAccessor } from '../accessors/formats/any-accessor.js';
13
+ import { InvalidFormatException } from '../exceptions/invalid-format-exception.js';
14
+
15
+ /**
16
+ * Factory for creating typed format-specific accessors.
17
+ *
18
+ * Encapsulates the wiring between a parser and accessor construction,
19
+ * providing one method per supported format. Used internally by
20
+ * {@link Inline} to create accessors.
21
+ *
22
+ * @internal
23
+ */
24
+ export class AccessorFactory {
25
+ /**
26
+ * Initialize the factory with a parser, optional integration, and optional strict mode.
27
+ *
28
+ * @param parser - Parser for dot-notation resolution.
29
+ * @param defaultIntegration - Default integration for AnyAccessor.
30
+ * @param strictMode - Override strict mode for created accessors.
31
+ */
32
+ constructor(
33
+ private readonly parser: ValidatableParserInterface,
34
+ private readonly defaultIntegration: ParseIntegrationInterface | null = null,
35
+ private readonly strictMode: boolean | null = null,
36
+ ) {}
37
+
38
+ /**
39
+ * Return the underlying parser used by this factory.
40
+ *
41
+ * @returns The parser instance.
42
+ */
43
+ getParser(): ValidatableParserInterface {
44
+ return this.parser;
45
+ }
46
+
47
+ /**
48
+ * Apply configured strict mode to a new accessor before hydration.
49
+ *
50
+ * @param accessor - Unhydrated accessor instance.
51
+ * @returns Same accessor with strict mode applied if configured.
52
+ */
53
+ private applyOptions<T extends AbstractAccessor>(accessor: T): T {
54
+ if (this.strictMode !== null) {
55
+ return accessor.strict(this.strictMode) as T;
56
+ }
57
+ return accessor;
58
+ }
59
+
60
+ /**
61
+ * Create an ArrayAccessor from raw array data.
62
+ *
63
+ * @param data - Source array or object.
64
+ * @returns Populated ArrayAccessor.
65
+ * @throws {SecurityException} When security constraints are violated.
66
+ */
67
+ array(data: Record<string, unknown> | unknown[]): ArrayAccessor {
68
+ return this.applyOptions(new ArrayAccessor(this.parser)).from(data);
69
+ }
70
+
71
+ /**
72
+ * Create an ObjectAccessor from a source object.
73
+ *
74
+ * @param data - Source object.
75
+ * @returns Populated ObjectAccessor.
76
+ * @throws {SecurityException} When security constraints are violated.
77
+ */
78
+ object(data: object): ObjectAccessor {
79
+ return this.applyOptions(new ObjectAccessor(this.parser)).from(data);
80
+ }
81
+
82
+ /**
83
+ * Create a JsonAccessor from a JSON string.
84
+ *
85
+ * @param data - Raw JSON string.
86
+ * @returns Populated JsonAccessor.
87
+ * @throws {InvalidFormatException} When the JSON is malformed.
88
+ * @throws {SecurityException} When security constraints are violated.
89
+ */
90
+ json(data: string): JsonAccessor {
91
+ return this.applyOptions(new JsonAccessor(this.parser)).from(data);
92
+ }
93
+
94
+ /**
95
+ * Create an XmlAccessor from an XML string.
96
+ *
97
+ * @param data - Raw XML string.
98
+ * @returns Populated XmlAccessor.
99
+ * @throws {InvalidFormatException} When the XML is malformed.
100
+ * @throws {SecurityException} When DOCTYPE is detected.
101
+ */
102
+ xml(data: string): XmlAccessor {
103
+ return this.applyOptions(new XmlAccessor(this.parser)).from(data);
104
+ }
105
+
106
+ /**
107
+ * Create a YamlAccessor from a YAML string.
108
+ *
109
+ * @param data - Raw YAML string.
110
+ * @returns Populated YamlAccessor.
111
+ * @throws {YamlParseException} When the YAML is malformed.
112
+ * @throws {SecurityException} When security constraints are violated.
113
+ */
114
+ yaml(data: string): YamlAccessor {
115
+ return this.applyOptions(new YamlAccessor(this.parser)).from(data);
116
+ }
117
+
118
+ /**
119
+ * Create an IniAccessor from an INI string.
120
+ *
121
+ * @param data - Raw INI string.
122
+ * @returns Populated IniAccessor.
123
+ * @throws {InvalidFormatException} When the INI is malformed.
124
+ * @throws {SecurityException} When security constraints are violated.
125
+ */
126
+ ini(data: string): IniAccessor {
127
+ return this.applyOptions(new IniAccessor(this.parser)).from(data);
128
+ }
129
+
130
+ /**
131
+ * Create an EnvAccessor from a dotenv-formatted string.
132
+ *
133
+ * @param data - Raw dotenv string.
134
+ * @returns Populated EnvAccessor.
135
+ * @throws {SecurityException} When security constraints are violated.
136
+ */
137
+ env(data: string): EnvAccessor {
138
+ return this.applyOptions(new EnvAccessor(this.parser)).from(data);
139
+ }
140
+
141
+ /**
142
+ * Create an NdjsonAccessor from a newline-delimited JSON string.
143
+ *
144
+ * @param data - Raw NDJSON string.
145
+ * @returns Populated NdjsonAccessor.
146
+ * @throws {InvalidFormatException} When any JSON line is malformed.
147
+ * @throws {SecurityException} When security constraints are violated.
148
+ */
149
+ ndjson(data: string): NdjsonAccessor {
150
+ return this.applyOptions(new NdjsonAccessor(this.parser)).from(data);
151
+ }
152
+
153
+ /**
154
+ * Create an AnyAccessor with automatic format detection.
155
+ *
156
+ * @param data - Raw data in any supported format.
157
+ * @param integration - Override integration (falls back to default).
158
+ * @returns Populated AnyAccessor.
159
+ * @throws {InvalidFormatException} When no integration is available.
160
+ */
161
+ any(data: unknown, integration?: ParseIntegrationInterface | null): AnyAccessor {
162
+ const resolved = integration ?? this.defaultIntegration;
163
+
164
+ if (resolved === null) {
165
+ throw new InvalidFormatException(
166
+ 'AnyAccessor requires a ParseIntegrationInterface. ' +
167
+ 'Pass one directly or configure a default via Inline.withParserIntegration(i).fromAny(data).',
168
+ );
169
+ }
170
+
171
+ return this.applyOptions(new AnyAccessor(this.parser, resolved)).from(data);
172
+ }
173
+ }
@@ -1,8 +1,14 @@
1
1
  import type { SecurityGuardInterface } from '../contracts/security-guard-interface.js';
2
2
  import type { SecurityParserInterface } from '../contracts/security-parser-interface.js';
3
3
  import type { PathCacheInterface } from '../contracts/path-cache-interface.js';
4
+ import type { ValidatableParserInterface } from '../contracts/validatable-parser-interface.js';
4
5
  import { SecurityGuard } from '../security/security-guard.js';
5
6
  import { SecurityParser } from '../security/security-parser.js';
7
+ import { PathNotFoundException } from '../exceptions/path-not-found-exception.js';
8
+ import { SegmentFilterParser } from '../path-query/segment-filter-parser.js';
9
+ import { SegmentParser } from '../path-query/segment-parser.js';
10
+ import { SegmentPathResolver } from '../path-query/segment-path-resolver.js';
11
+ import type { Segment } from '../path-query/segment-type.js';
6
12
 
7
13
  /**
8
14
  * Core dot-notation parser for reading, writing, and removing nested values.
@@ -10,28 +16,40 @@ import { SecurityParser } from '../security/security-parser.js';
10
16
  * Provides path-based access to plain objects using dot-separated keys.
11
17
  * Delegates security validation to SecurityGuard and SecurityParser.
12
18
  *
19
+ * @internal
20
+ *
13
21
  * @example
14
22
  * const parser = new DotNotationParser();
15
23
  * parser.get({ user: { name: 'Alice' } }, 'user.name'); // 'Alice'
16
24
  */
17
- export class DotNotationParser {
25
+ export class DotNotationParser implements ValidatableParserInterface {
18
26
  private readonly securityGuard: SecurityGuardInterface;
19
27
  private readonly securityParser: SecurityParserInterface;
20
28
  private readonly pathCache: PathCacheInterface | null;
29
+ private readonly segmentParser: SegmentParser;
30
+ private readonly segmentPathResolver: SegmentPathResolver;
21
31
 
22
32
  /**
23
33
  * @param securityGuard - Key-safety guard. Defaults to a new SecurityGuard instance.
24
34
  * @param securityParser - Parser depth and size limits. Defaults to a new SecurityParser instance.
25
35
  * @param pathCache - Optional path segment cache for repeated lookups.
36
+ * @param segmentParser - Path-string → segment converter.
37
+ * @param segmentPathResolver - Segment → value resolver.
26
38
  */
27
39
  constructor(
28
40
  securityGuard?: SecurityGuardInterface,
29
41
  securityParser?: SecurityParserInterface,
30
42
  pathCache?: PathCacheInterface,
43
+ segmentParser?: SegmentParser,
44
+ segmentPathResolver?: SegmentPathResolver,
31
45
  ) {
32
46
  this.securityGuard = securityGuard ?? new SecurityGuard();
33
47
  this.securityParser = securityParser ?? new SecurityParser();
34
48
  this.pathCache = pathCache ?? null;
49
+
50
+ const filterParser = new SegmentFilterParser(this.securityGuard);
51
+ this.segmentParser = segmentParser ?? new SegmentParser(filterParser);
52
+ this.segmentPathResolver = segmentPathResolver ?? new SegmentPathResolver(filterParser);
35
53
  }
36
54
 
37
55
  /**
@@ -52,8 +70,50 @@ export class DotNotationParser {
52
70
  return defaultValue;
53
71
  }
54
72
 
55
- const segments = this.parsePath(path);
56
- return this.getAt(data, segments, defaultValue);
73
+ const segments = this.segmentPathCache(path);
74
+ return this.resolve(data, segments, defaultValue);
75
+ }
76
+
77
+ /**
78
+ * Resolve a pre-parsed segment array against data, returning the matched value.
79
+ *
80
+ * @param data - Source data object.
81
+ * @param segments - Typed segments from {@link SegmentParser}.
82
+ * @param defaultValue - Fallback returned when the path does not exist.
83
+ * @returns Resolved value or the default.
84
+ */
85
+ resolve(
86
+ data: Record<string, unknown>,
87
+ segments: Segment[],
88
+ defaultValue: unknown = null,
89
+ ): unknown {
90
+ return this.segmentPathResolver.resolve(
91
+ data,
92
+ segments,
93
+ 0,
94
+ defaultValue,
95
+ this.securityParser.getMaxResolveDepth(),
96
+ );
97
+ }
98
+
99
+ /**
100
+ * Retrieve a value at the given path, throwing when not found.
101
+ *
102
+ * @param data - Source data object.
103
+ * @param path - Dot-notation path string.
104
+ * @returns Resolved value.
105
+ *
106
+ * @throws {PathNotFoundException} When the path does not exist.
107
+ */
108
+ getStrict(data: Record<string, unknown>, path: string): unknown {
109
+ const sentinel = Object.create(null) as Record<string, never>;
110
+ const result = this.get(data, path, sentinel);
111
+
112
+ if (result === sentinel) {
113
+ throw new PathNotFoundException(`Path '${path}' not found.`);
114
+ }
115
+
116
+ return result;
57
117
  }
58
118
 
59
119
  /**
@@ -68,7 +128,7 @@ export class DotNotationParser {
68
128
  * parser.set({}, 'user.name', 'Alice'); // { user: { name: 'Alice' } }
69
129
  */
70
130
  set(data: Record<string, unknown>, path: string, value: unknown): Record<string, unknown> {
71
- const segments = this.parsePath(path);
131
+ const segments = this.segmentParser.parseKeys(path);
72
132
  return this.setAt(data, segments, value);
73
133
  }
74
134
 
@@ -103,7 +163,7 @@ export class DotNotationParser {
103
163
  * parser.remove({ a: { b: 1 } }, 'a.b'); // { a: {} }
104
164
  */
105
165
  remove(data: Record<string, unknown>, path: string): Record<string, unknown> {
106
- const segments = this.parsePath(path);
166
+ const segments = this.segmentParser.parseKeys(path);
107
167
  return this.removeAt(data, segments);
108
168
  }
109
169
 
@@ -188,7 +248,10 @@ export class DotNotationParser {
188
248
  * @example
189
249
  * parser.removeAt({ a: { b: 1 } }, ['a', 'b']); // { a: {} }
190
250
  */
191
- removeAt(data: Record<string, unknown>, segments: Array<string | number>): Record<string, unknown> {
251
+ removeAt(
252
+ data: Record<string, unknown>,
253
+ segments: Array<string | number>,
254
+ ): Record<string, unknown> {
192
255
  /* Stryker disable next-line ConditionalExpression,BlockStatement -- equivalent: empty segments → eraseAt hits undefined key → hasOwnProperty false → returns data anyway */
193
256
  if (segments.length === 0) {
194
257
  return data;
@@ -273,22 +336,22 @@ export class DotNotationParser {
273
336
  }
274
337
 
275
338
  /**
276
- * Parse a dot-notation path into segments, using cache when available.
339
+ * Retrieve parsed segments from cache or parse and cache the path.
277
340
  *
278
341
  * @param path - Dot-notation path string.
279
- * @returns Array of path segments.
342
+ * @returns Cached or freshly parsed typed segments.
280
343
  */
281
- private parsePath(path: string): string[] {
344
+ private segmentPathCache(path: string): Segment[] {
282
345
  if (this.pathCache !== null) {
283
346
  const cached = this.pathCache.get(path);
284
347
  if (cached !== null) {
285
348
  return cached;
286
349
  }
287
- const segments = path.split('.');
350
+ const segments = this.segmentParser.parseSegments(path);
288
351
  this.pathCache.set(path, segments);
289
352
  return segments;
290
353
  }
291
- return path.split('.');
354
+ return this.segmentParser.parseSegments(path);
292
355
  }
293
356
 
294
357
  /**
@@ -0,0 +1,163 @@
1
+ import type { SecurityGuardInterface } from '../contracts/security-guard-interface.js';
2
+ import type { SecurityParserInterface } from '../contracts/security-parser-interface.js';
3
+ import type { ParseIntegrationInterface } from '../contracts/parse-integration-interface.js';
4
+ import type { PathCacheInterface } from '../contracts/path-cache-interface.js';
5
+ import { SecurityGuard } from '../security/security-guard.js';
6
+ import { SecurityParser } from '../security/security-parser.js';
7
+ import { SimplePathCache } from '../cache/simple-path-cache.js';
8
+ import { DotNotationParser } from './dot-notation-parser.js';
9
+ import { AccessorFactory } from './accessor-factory.js';
10
+ import { SegmentFilterParser } from '../path-query/segment-filter-parser.js';
11
+ import { SegmentParser } from '../path-query/segment-parser.js';
12
+ import { SegmentPathResolver } from '../path-query/segment-path-resolver.js';
13
+
14
+ /**
15
+ * Builder for configuring and constructing the internal components of SafeAccess Inline.
16
+ *
17
+ * Provides an immutable builder API: each `withXxx()` method returns a new
18
+ * instance with the specified override, leaving the original unchanged.
19
+ *
20
+ * Note: PHP's equivalent uses `__callStatic`/`__call` magic to expose
21
+ * protected methods publicly. TypeScript has no equivalent mechanism, so
22
+ * builder methods are public directly and static forwarding is explicit
23
+ * in the {@link Inline} subclass.
24
+ *
25
+ * @internal
26
+ *
27
+ * @see Inline
28
+ */
29
+ export class InlineBuilderAccessor {
30
+ protected readonly _guard: SecurityGuardInterface;
31
+ protected readonly _secParser: SecurityParserInterface;
32
+ protected readonly _pathCache: PathCacheInterface | null;
33
+ protected readonly _integration: ParseIntegrationInterface | null;
34
+ protected readonly _strictMode: boolean | null;
35
+
36
+ /**
37
+ * Create a builder with optional component overrides.
38
+ *
39
+ * @param guard - Custom security guard, or undefined for default.
40
+ * @param secParser - Custom security parser, or undefined for default.
41
+ * @param pathCache - Custom path cache, or null/undefined for default.
42
+ * @param integration - Custom format integration, or null/undefined for none.
43
+ * @param strictMode - Strict mode override, or null/undefined for accessor default.
44
+ */
45
+ constructor(
46
+ guard?: SecurityGuardInterface,
47
+ secParser?: SecurityParserInterface,
48
+ pathCache?: PathCacheInterface | null,
49
+ integration?: ParseIntegrationInterface | null,
50
+ strictMode?: boolean | null,
51
+ ) {
52
+ this._guard = guard ?? new SecurityGuard();
53
+ this._secParser = secParser ?? new SecurityParser();
54
+ this._pathCache = pathCache ?? null;
55
+ this._integration = integration ?? null;
56
+ this._strictMode = strictMode ?? null;
57
+ }
58
+
59
+ /**
60
+ * Initialize the builder with default or provided components.
61
+ *
62
+ * @returns Configured factory ready to create typed accessors.
63
+ */
64
+ builder(): AccessorFactory {
65
+ const filterParser = new SegmentFilterParser(this._guard);
66
+ const segmentParser = new SegmentParser(filterParser);
67
+ const segmentPathResolver = new SegmentPathResolver(filterParser);
68
+
69
+ const dotNotationParser = new DotNotationParser(
70
+ this._guard,
71
+ this._secParser,
72
+ this._pathCache ?? new SimplePathCache(),
73
+ segmentParser,
74
+ segmentPathResolver,
75
+ );
76
+
77
+ return new AccessorFactory(dotNotationParser, this._integration, this._strictMode);
78
+ }
79
+
80
+ /**
81
+ * Set a custom parser integration implementation.
82
+ *
83
+ * @param integration - Custom format integration to use.
84
+ * @returns New builder instance with the integration configured.
85
+ */
86
+ withParserIntegration(integration: ParseIntegrationInterface): this {
87
+ return new (this.constructor as new (
88
+ guard: SecurityGuardInterface,
89
+ secParser: SecurityParserInterface,
90
+ pathCache: PathCacheInterface | null,
91
+ integration: ParseIntegrationInterface | null,
92
+ strictMode: boolean | null,
93
+ ) => this)(this._guard, this._secParser, this._pathCache, integration, this._strictMode);
94
+ }
95
+
96
+ /**
97
+ * Set a custom security guard implementation.
98
+ *
99
+ * @param guard - Custom guard implementation to use.
100
+ * @returns New builder instance with the guard configured.
101
+ */
102
+ withSecurityGuard(guard: SecurityGuardInterface): this {
103
+ return new (this.constructor as new (
104
+ guard: SecurityGuardInterface,
105
+ secParser: SecurityParserInterface,
106
+ pathCache: PathCacheInterface | null,
107
+ integration: ParseIntegrationInterface | null,
108
+ strictMode: boolean | null,
109
+ ) => this)(guard, this._secParser, this._pathCache, this._integration, this._strictMode);
110
+ }
111
+
112
+ /**
113
+ * Set a custom security parser implementation.
114
+ *
115
+ * @param parser - Custom parser implementation to use.
116
+ * @returns New builder instance with the parser configured.
117
+ */
118
+ withSecurityParser(parser: SecurityParserInterface): this {
119
+ return new (this.constructor as new (
120
+ guard: SecurityGuardInterface,
121
+ secParser: SecurityParserInterface,
122
+ pathCache: PathCacheInterface | null,
123
+ integration: ParseIntegrationInterface | null,
124
+ strictMode: boolean | null,
125
+ ) => this)(this._guard, parser, this._pathCache, this._integration, this._strictMode);
126
+ }
127
+
128
+ /**
129
+ * Set a custom path cache implementation.
130
+ *
131
+ * @param cache - Custom cache implementation to use.
132
+ * @returns New builder instance with the cache configured.
133
+ */
134
+ withPathCache(cache: PathCacheInterface): this {
135
+ return new (this.constructor as new (
136
+ guard: SecurityGuardInterface,
137
+ secParser: SecurityParserInterface,
138
+ pathCache: PathCacheInterface | null,
139
+ integration: ParseIntegrationInterface | null,
140
+ strictMode: boolean | null,
141
+ ) => this)(this._guard, this._secParser, cache, this._integration, this._strictMode);
142
+ }
143
+
144
+ /**
145
+ * Set the strict mode for all accessors created by this builder.
146
+ *
147
+ * @param strict - Whether to enable strict security validation.
148
+ * @returns New builder instance with the strict mode configured.
149
+ *
150
+ * @security Passing `false` disables all SecurityGuard and SecurityParser
151
+ * validation (key safety, payload size, depth and key-count limits).
152
+ * Only use with fully trusted, application-controlled input.
153
+ */
154
+ withStrictMode(strict: boolean): this {
155
+ return new (this.constructor as new (
156
+ guard: SecurityGuardInterface,
157
+ secParser: SecurityParserInterface,
158
+ pathCache: PathCacheInterface | null,
159
+ integration: ParseIntegrationInterface | null,
160
+ strictMode: boolean | null,
161
+ ) => this)(this._guard, this._secParser, this._pathCache, this._integration, strict);
162
+ }
163
+ }
@@ -1,6 +1,15 @@
1
1
  /**
2
2
  * Base exception for all accessor-layer errors.
3
3
  *
4
+ * @api
5
+ *
6
+ * @see InvalidFormatException Thrown on malformed input data.
7
+ * @see ParserException Thrown on parser-level operational errors.
8
+ * @see PathNotFoundException Thrown when a requested path does not exist.
9
+ * @see SecurityException Thrown on security constraint violations.
10
+ * @see ReadonlyViolationException Thrown on write attempts to a readonly accessor.
11
+ * @see UnsupportedTypeException Thrown when an unsupported format is requested.
12
+ *
4
13
  * @example
5
14
  * throw new AccessorException('Something went wrong.');
6
15
  */
@@ -3,6 +3,11 @@ import { AccessorException } from './accessor-exception.js';
3
3
  /**
4
4
  * Thrown when the input data cannot be parsed as the expected format.
5
5
  *
6
+ * @api
7
+ *
8
+ * @see AccessorException Parent exception class.
9
+ * @see YamlParseException Specialized subclass for YAML parsing errors.
10
+ *
6
11
  * @example
7
12
  * throw new InvalidFormatException('Expected JSON string, got number.');
8
13
  */
@@ -3,6 +3,10 @@ import { AccessorException } from './accessor-exception.js';
3
3
  /**
4
4
  * Thrown when an underlying parser encounters a structural error.
5
5
  *
6
+ * @api
7
+ *
8
+ * @see AccessorException Parent exception class.
9
+ *
6
10
  * @example
7
11
  * throw new ParserException('Parser failed to process input.');
8
12
  */
@@ -3,6 +3,10 @@ import { AccessorException } from './accessor-exception.js';
3
3
  /**
4
4
  * Thrown when a dot-notation path does not exist in the data.
5
5
  *
6
+ * @api
7
+ *
8
+ * @see AccessorException Parent exception class.
9
+ *
6
10
  * @example
7
11
  * throw new PathNotFoundException("Path 'user.address.zip' not found.");
8
12
  */
@@ -3,6 +3,10 @@ import { AccessorException } from './accessor-exception.js';
3
3
  /**
4
4
  * Thrown when a write operation is attempted on a readonly accessor.
5
5
  *
6
+ * @api
7
+ *
8
+ * @see AccessorException Parent exception class.
9
+ *
6
10
  * @example
7
11
  * const accessor = Inline.fromJson('{}').readonly(true);
8
12
  * accessor.set('key', 'value'); // throws ReadonlyViolationException
@@ -7,6 +7,12 @@ import { AccessorException } from './accessor-exception.js';
7
7
  * methods, stream wrapper / protocol URI schemes, Node.js globals),
8
8
  * payload size violations, key-count limits, and depth limit violations.
9
9
  *
10
+ * @api
11
+ *
12
+ * @see AccessorException Parent exception class.
13
+ * @see SecurityGuard Validates keys against the forbidden list.
14
+ * @see SecurityParser Enforces payload, depth, and key-count limits.
15
+ *
10
16
  * @example
11
17
  * throw new SecurityException("Forbidden key '__proto__' detected.");
12
18
  */
@@ -3,6 +3,10 @@ import { AccessorException } from './accessor-exception.js';
3
3
  /**
4
4
  * Thrown when the requested format or TypeFormat value is not supported.
5
5
  *
6
+ * @api
7
+ *
8
+ * @see AccessorException Parent exception class.
9
+ *
6
10
  * @example
7
11
  * throw new UnsupportedTypeException('TypeFormat.Csv is not supported.');
8
12
  */
@@ -6,6 +6,10 @@ import { InvalidFormatException } from './invalid-format-exception.js';
6
6
  * Unsafe constructs include: tags (!! and !), anchors (&), aliases (*),
7
7
  * and merge keys (<<).
8
8
  *
9
+ * @api
10
+ *
11
+ * @see InvalidFormatException Parent exception class.
12
+ *
9
13
  * @example
10
14
  * throw new YamlParseException('YAML anchors are not supported (line 3).');
11
15
  */