@safeaccess/inline 0.1.1 → 0.1.3

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 +23 -5
  3. package/LICENSE +1 -1
  4. package/README.md +79 -21
  5. package/dist/accessors/abstract-accessor.d.ts +24 -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 +26 -56
  67. package/dist/inline.js +43 -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 +4 -1
  82. package/dist/security/security-guard.js +7 -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 +25 -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 +46 -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 +10 -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 +379 -0
  163. package/tests/parser/xml-parser.test.ts +17 -330
  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 +8 -479
  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,321 @@
1
+ import type { FilterEvaluatorInterface } from '../contracts/filter-evaluator-interface.js';
2
+ import { InvalidFormatException } from '../exceptions/invalid-format-exception.js';
3
+ import { SegmentType } from './segment-type.js';
4
+ import type { Segment } from './segment-type.js';
5
+
6
+ /**
7
+ * Parse dot-notation path strings into typed segment arrays.
8
+ *
9
+ * Converts path expressions (e.g. "users[0].address..city") into ordered
10
+ * segment arrays with {@link SegmentType} metadata for resolution by
11
+ * {@link SegmentPathResolver}. Supports key, index, wildcard, descent,
12
+ * multi-key/index, filter, slice, and projection segment types.
13
+ *
14
+ * @internal
15
+ *
16
+ * @see SegmentType Enum of all segment types produced.
17
+ * @see SegmentPathResolver Consumer that resolves segments against data.
18
+ * @see FilterEvaluatorInterface Delegate for filter expression parsing.
19
+ */
20
+ export class SegmentParser {
21
+ /**
22
+ * Create a segment parser with a filter evaluator.
23
+ *
24
+ * @param segmentFilterParser - Delegate for [?filter] parsing.
25
+ */
26
+ constructor(private readonly segmentFilterParser: FilterEvaluatorInterface) {}
27
+
28
+ /**
29
+ * Parse a dot-notation path into an ordered array of typed segments.
30
+ *
31
+ * @param path - Dot-notation path expression.
32
+ * @returns Typed segment array.
33
+ *
34
+ * @throws {InvalidFormatException} When slice step is zero.
35
+ */
36
+ parseSegments(path: string): Segment[] {
37
+ const segments: Segment[] = [];
38
+ const len = path.length;
39
+ const pos = { i: 0 };
40
+
41
+ if (path[0] === '$') {
42
+ pos.i = 1;
43
+ if (path[pos.i] === '.') {
44
+ pos.i++;
45
+ }
46
+ }
47
+
48
+ while (pos.i < len) {
49
+ if (path[pos.i] === '.') {
50
+ if (path[pos.i + 1] === '.') {
51
+ pos.i += 2;
52
+ segments.push(this.parseDescent(path, pos, len));
53
+ continue;
54
+ }
55
+ pos.i++;
56
+ const segment = this.parseProjection(path, pos, len);
57
+ if (segment !== null) {
58
+ segments.push(segment);
59
+ }
60
+ continue;
61
+ }
62
+
63
+ if (path[pos.i] === '[' && path[pos.i + 1] === '?') {
64
+ segments.push(this.parseFilter(path, pos, len));
65
+ continue;
66
+ }
67
+
68
+ if (path[pos.i] === '[') {
69
+ segments.push(this.parseBracket(path, pos, len));
70
+ continue;
71
+ }
72
+
73
+ if (path[pos.i] === '*') {
74
+ segments.push({ type: SegmentType.Wildcard });
75
+ pos.i++;
76
+ continue;
77
+ }
78
+
79
+ segments.push(this.parseKey(path, pos, len));
80
+ }
81
+
82
+ return segments;
83
+ }
84
+
85
+ /**
86
+ * Parse a recursive descent segment (`..key` or `..[...]`).
87
+ *
88
+ * @param path - Full path string.
89
+ * @param pos - Current position (mutated in place).
90
+ * @param len - Total path length.
91
+ * @returns Parsed descent segment.
92
+ */
93
+ private parseDescent(path: string, pos: { i: number }, len: number): Segment {
94
+ if (path[pos.i] === '[') {
95
+ let j = pos.i + 1;
96
+ while (j < len && path[j] !== ']') {
97
+ j++;
98
+ }
99
+
100
+ const inner = path.substring(pos.i + 1, j);
101
+ pos.i = j + 1;
102
+
103
+ if (inner.includes(',') && this.allQuoted(inner.split(','))) {
104
+ const parts = inner.split(',').map((p) => p.trim());
105
+ const keys = parts.map((p) => p.substring(1, p.length - 1));
106
+ return { type: SegmentType.DescentMulti, keys };
107
+ }
108
+
109
+ const quotedMatch = inner.match(/^(['"])(.*?)\1$/);
110
+ if (quotedMatch) {
111
+ return { type: SegmentType.Descent, key: quotedMatch[2] };
112
+ }
113
+
114
+ return { type: SegmentType.Descent, key: inner };
115
+ }
116
+
117
+ let key = '';
118
+ while (pos.i < len && path[pos.i] !== '.' && path[pos.i] !== '[') {
119
+ if (path[pos.i] === '\\' && path[pos.i + 1] === '.') {
120
+ key += '.';
121
+ pos.i += 2;
122
+ } else {
123
+ key += path[pos.i];
124
+ pos.i++;
125
+ }
126
+ }
127
+
128
+ return { type: SegmentType.Descent, key };
129
+ }
130
+
131
+ /**
132
+ * Parse a projection segment (`.{field1, field2}` or `.{alias: field}`).
133
+ *
134
+ * @param path - Full path string.
135
+ * @param pos - Current position (mutated in place).
136
+ * @param len - Total path length.
137
+ * @returns Parsed projection segment, or null if not a projection.
138
+ */
139
+ private parseProjection(path: string, pos: { i: number }, len: number): Segment | null {
140
+ if (path[pos.i] !== '{') {
141
+ return null;
142
+ }
143
+
144
+ let j = pos.i + 1;
145
+ while (j < len && path[j] !== '}') {
146
+ j++;
147
+ }
148
+ const inner = path.substring(pos.i + 1, j);
149
+ pos.i = j + 1;
150
+
151
+ const fields: Array<{ alias: string; source: string }> = [];
152
+ for (const entry of inner
153
+ .split(',')
154
+ .map((e) => e.trim())
155
+ .filter((e) => e !== '')) {
156
+ const colonIdx = entry.indexOf(':');
157
+ if (colonIdx !== -1) {
158
+ fields.push({
159
+ alias: entry.substring(0, colonIdx).trim(),
160
+ source: entry.substring(colonIdx + 1).trim(),
161
+ });
162
+ } else {
163
+ fields.push({ alias: entry, source: entry });
164
+ }
165
+ }
166
+
167
+ return { type: SegmentType.Projection, fields };
168
+ }
169
+
170
+ /**
171
+ * Parse a filter segment (`[?expression]`).
172
+ *
173
+ * @param path - Full path string.
174
+ * @param pos - Current position (mutated in place).
175
+ * @param len - Total path length.
176
+ * @returns Parsed filter segment.
177
+ */
178
+ private parseFilter(path: string, pos: { i: number }, len: number): Segment {
179
+ let depth = 1;
180
+ let j = pos.i + 1;
181
+ while (j < len && depth > 0) {
182
+ j++;
183
+ if (path[j] === '[') {
184
+ depth++;
185
+ }
186
+ if (path[j] === ']') {
187
+ depth--;
188
+ }
189
+ }
190
+ const filterExpr = path.substring(pos.i + 2, j);
191
+ pos.i = j + 1;
192
+
193
+ return { type: SegmentType.Filter, expression: this.segmentFilterParser.parse(filterExpr) };
194
+ }
195
+
196
+ /**
197
+ * Parse a bracket segment (`[0]`, `[0,1,2]`, `[0:5]`, `['key']`, `[*]`).
198
+ *
199
+ * @param path - Full path string.
200
+ * @param pos - Current position (mutated in place).
201
+ * @param len - Total path length.
202
+ * @returns Parsed bracket segment.
203
+ *
204
+ * @throws {InvalidFormatException} When slice step is zero.
205
+ */
206
+ private parseBracket(path: string, pos: { i: number }, len: number): Segment {
207
+ let j = pos.i + 1;
208
+ while (j < len && path[j] !== ']') {
209
+ j++;
210
+ }
211
+ const inner = path.substring(pos.i + 1, j);
212
+ pos.i = j + 1;
213
+
214
+ if (inner.includes(',')) {
215
+ const parts = inner.split(',').map((p) => p.trim());
216
+
217
+ if (this.allQuoted(parts)) {
218
+ const keys = parts.map((p) => p.substring(1, p.length - 1));
219
+ return { type: SegmentType.MultiKey, keys };
220
+ }
221
+
222
+ let allNumeric = true;
223
+ for (const p of parts) {
224
+ if (isNaN(Number(p.trim())) || p.trim() === '') {
225
+ allNumeric = false;
226
+ break;
227
+ }
228
+ }
229
+ if (allNumeric) {
230
+ return { type: SegmentType.MultiIndex, indices: parts.map((p) => parseInt(p, 10)) };
231
+ }
232
+ }
233
+
234
+ const quotedMatch = inner.match(/^(['"])(.*?)\1$/);
235
+ if (quotedMatch) {
236
+ return { type: SegmentType.Key, value: quotedMatch[2] };
237
+ }
238
+
239
+ if (inner.includes(':')) {
240
+ const sliceParts = inner.split(':');
241
+ const start = sliceParts[0] !== '' ? parseInt(sliceParts[0], 10) : null;
242
+ const end =
243
+ sliceParts.length > 1 && sliceParts[1] !== '' ? parseInt(sliceParts[1], 10) : null;
244
+ const rawStep =
245
+ sliceParts.length > 2 && sliceParts[2] !== '' ? parseInt(sliceParts[2], 10) : null;
246
+ if (rawStep === 0) {
247
+ throw new InvalidFormatException('Slice step cannot be zero.');
248
+ }
249
+ return { type: SegmentType.Slice, start, end, step: rawStep };
250
+ }
251
+
252
+ if (inner === '*') {
253
+ return { type: SegmentType.Wildcard };
254
+ }
255
+
256
+ return { type: SegmentType.Key, value: inner };
257
+ }
258
+
259
+ /**
260
+ * Parse a regular dot-separated key with escaped-dot support.
261
+ *
262
+ * @param path - Full path string.
263
+ * @param pos - Current position (mutated in place).
264
+ * @param len - Total path length.
265
+ * @returns Parsed key segment.
266
+ */
267
+ private parseKey(path: string, pos: { i: number }, len: number): Segment {
268
+ let key = '';
269
+ while (pos.i < len && path[pos.i] !== '.' && path[pos.i] !== '[') {
270
+ if (path[pos.i] === '\\' && path[pos.i + 1] === '.') {
271
+ key += '.';
272
+ pos.i += 2;
273
+ } else {
274
+ key += path[pos.i];
275
+ pos.i++;
276
+ }
277
+ }
278
+
279
+ return { type: SegmentType.Key, value: key };
280
+ }
281
+
282
+ /**
283
+ * Check if all parts in a comma-separated list are quoted strings.
284
+ *
285
+ * @param parts - Raw parts from split.
286
+ * @returns True if every part is single- or double-quoted.
287
+ */
288
+ private allQuoted(parts: string[]): boolean {
289
+ for (const raw of parts) {
290
+ const p = raw.trim();
291
+ if (
292
+ !(p.startsWith("'") && p.endsWith("'")) &&
293
+ !(p.startsWith('"') && p.endsWith('"'))
294
+ ) {
295
+ return false;
296
+ }
297
+ }
298
+ return true;
299
+ }
300
+
301
+ /**
302
+ * Parse a simple dot-notation path into plain string keys.
303
+ *
304
+ * Handles bracket notation and escaped dots. Does not produce typed
305
+ * segments - used for set/remove operations via {@link DotNotationParser}.
306
+ *
307
+ * @param path - Simple dot-notation path.
308
+ * @returns Ordered list of key strings.
309
+ */
310
+ parseKeys(path: string): string[] {
311
+ let normalized = path.replace(/\[([^\]]+)\]/g, '.$1');
312
+
313
+ const placeholder = '\x00ESC_DOT\x00';
314
+ normalized = normalized.replace(/\\\./g, placeholder);
315
+ const keys = normalized.split('.');
316
+
317
+ return keys.map((k) =>
318
+ k.replace(new RegExp(placeholder.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), '.'),
319
+ );
320
+ }
321
+ }