@safeaccess/inline 0.1.1
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/.gitattributes +16 -0
- package/.gitkeep +0 -0
- package/CHANGELOG.md +38 -0
- package/LICENSE +21 -0
- package/README.md +454 -0
- package/benchmarks/get.bench.ts +26 -0
- package/benchmarks/parse.bench.ts +41 -0
- package/dist/accessors/abstract-accessor.d.ts +213 -0
- package/dist/accessors/abstract-accessor.js +294 -0
- package/dist/accessors/formats/any-accessor.d.ts +35 -0
- package/dist/accessors/formats/any-accessor.js +44 -0
- package/dist/accessors/formats/array-accessor.d.ts +26 -0
- package/dist/accessors/formats/array-accessor.js +39 -0
- package/dist/accessors/formats/env-accessor.d.ts +27 -0
- package/dist/accessors/formats/env-accessor.js +64 -0
- package/dist/accessors/formats/ini-accessor.d.ts +41 -0
- package/dist/accessors/formats/ini-accessor.js +109 -0
- package/dist/accessors/formats/json-accessor.d.ts +26 -0
- package/dist/accessors/formats/json-accessor.js +56 -0
- package/dist/accessors/formats/ndjson-accessor.d.ts +28 -0
- package/dist/accessors/formats/ndjson-accessor.js +71 -0
- package/dist/accessors/formats/object-accessor.d.ts +48 -0
- package/dist/accessors/formats/object-accessor.js +90 -0
- package/dist/accessors/formats/xml-accessor.d.ts +27 -0
- package/dist/accessors/formats/xml-accessor.js +52 -0
- package/dist/accessors/formats/yaml-accessor.d.ts +29 -0
- package/dist/accessors/formats/yaml-accessor.js +46 -0
- package/dist/contracts/accessors-interface.d.ts +11 -0
- package/dist/contracts/accessors-interface.js +1 -0
- package/dist/contracts/factory-accessors-interface.d.ts +16 -0
- package/dist/contracts/factory-accessors-interface.js +1 -0
- package/dist/contracts/parse-integration-interface.d.ts +31 -0
- package/dist/contracts/parse-integration-interface.js +1 -0
- package/dist/contracts/path-cache-interface.d.ts +40 -0
- package/dist/contracts/path-cache-interface.js +1 -0
- package/dist/contracts/readable-accessors-interface.d.ts +79 -0
- package/dist/contracts/readable-accessors-interface.js +1 -0
- package/dist/contracts/security-guard-interface.d.ts +40 -0
- package/dist/contracts/security-guard-interface.js +1 -0
- package/dist/contracts/security-parser-interface.d.ts +67 -0
- package/dist/contracts/security-parser-interface.js +1 -0
- package/dist/contracts/writable-accessors-interface.d.ts +65 -0
- package/dist/contracts/writable-accessors-interface.js +1 -0
- package/dist/core/dot-notation-parser.d.ts +204 -0
- package/dist/core/dot-notation-parser.js +343 -0
- package/dist/exceptions/accessor-exception.d.ts +13 -0
- package/dist/exceptions/accessor-exception.js +16 -0
- package/dist/exceptions/invalid-format-exception.d.ts +14 -0
- package/dist/exceptions/invalid-format-exception.js +17 -0
- package/dist/exceptions/parser-exception.d.ts +14 -0
- package/dist/exceptions/parser-exception.js +17 -0
- package/dist/exceptions/path-not-found-exception.d.ts +14 -0
- package/dist/exceptions/path-not-found-exception.js +17 -0
- package/dist/exceptions/readonly-violation-exception.d.ts +15 -0
- package/dist/exceptions/readonly-violation-exception.js +18 -0
- package/dist/exceptions/security-exception.d.ts +18 -0
- package/dist/exceptions/security-exception.js +21 -0
- package/dist/exceptions/unsupported-type-exception.d.ts +14 -0
- package/dist/exceptions/unsupported-type-exception.js +17 -0
- package/dist/exceptions/yaml-parse-exception.d.ts +17 -0
- package/dist/exceptions/yaml-parse-exception.js +20 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.js +30 -0
- package/dist/inline.d.ts +402 -0
- package/dist/inline.js +512 -0
- package/dist/parser/xml-parser.d.ts +46 -0
- package/dist/parser/xml-parser.js +288 -0
- package/dist/parser/yaml-parser.d.ts +94 -0
- package/dist/parser/yaml-parser.js +286 -0
- package/dist/security/forbidden-keys.d.ts +34 -0
- package/dist/security/forbidden-keys.js +80 -0
- package/dist/security/security-guard.d.ts +94 -0
- package/dist/security/security-guard.js +172 -0
- package/dist/security/security-parser.d.ts +130 -0
- package/dist/security/security-parser.js +192 -0
- package/dist/type-format.d.ts +28 -0
- package/dist/type-format.js +29 -0
- package/eslint.config.js +1 -0
- package/package.json +39 -0
- package/src/accessors/abstract-accessor.ts +353 -0
- package/src/accessors/formats/any-accessor.ts +51 -0
- package/src/accessors/formats/array-accessor.ts +45 -0
- package/src/accessors/formats/env-accessor.ts +79 -0
- package/src/accessors/formats/ini-accessor.ts +124 -0
- package/src/accessors/formats/json-accessor.ts +66 -0
- package/src/accessors/formats/ndjson-accessor.ts +82 -0
- package/src/accessors/formats/object-accessor.ts +100 -0
- package/src/accessors/formats/xml-accessor.ts +58 -0
- package/src/accessors/formats/yaml-accessor.ts +52 -0
- package/src/contracts/accessors-interface.ts +12 -0
- package/src/contracts/factory-accessors-interface.ts +16 -0
- package/src/contracts/parse-integration-interface.ts +32 -0
- package/src/contracts/path-cache-interface.ts +43 -0
- package/src/contracts/readable-accessors-interface.ts +88 -0
- package/src/contracts/security-guard-interface.ts +43 -0
- package/src/contracts/security-parser-interface.ts +74 -0
- package/src/contracts/writable-accessors-interface.ts +70 -0
- package/src/core/dot-notation-parser.ts +419 -0
- package/src/exceptions/accessor-exception.ts +16 -0
- package/src/exceptions/invalid-format-exception.ts +18 -0
- package/src/exceptions/parser-exception.ts +18 -0
- package/src/exceptions/path-not-found-exception.ts +18 -0
- package/src/exceptions/readonly-violation-exception.ts +19 -0
- package/src/exceptions/security-exception.ts +22 -0
- package/src/exceptions/unsupported-type-exception.ts +18 -0
- package/src/exceptions/yaml-parse-exception.ts +21 -0
- package/src/index.ts +46 -0
- package/src/inline.ts +570 -0
- package/src/parser/xml-parser.ts +334 -0
- package/src/parser/yaml-parser.ts +368 -0
- package/src/security/forbidden-keys.ts +81 -0
- package/src/security/security-guard.ts +195 -0
- package/src/security/security-parser.ts +233 -0
- package/src/type-format.ts +28 -0
- package/stryker.config.json +24 -0
- package/tests/accessors/accessors.test.ts +1017 -0
- package/tests/accessors/json-accessor.test.ts +171 -0
- package/tests/core/dot-notation-parser.test.ts +587 -0
- package/tests/exceptions/parser-exception.test.ts +31 -0
- package/tests/inline.test.ts +445 -0
- package/tests/mocks/fake-parse-integration.ts +24 -0
- package/tests/mocks/fake-path-cache.ts +31 -0
- package/tests/parity.test.ts +164 -0
- package/tests/parser/xml-parser.test.ts +618 -0
- package/tests/parser/yaml-parser.test.ts +463 -0
- package/tests/security/security-guard.test.ts +646 -0
- package/tests/security/security-parser.test.ts +391 -0
- package/tsconfig.json +16 -0
- package/vitest.config.ts +19 -0
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
import { SecurityGuard } from '../security/security-guard.js';
|
|
2
|
+
import { SecurityParser } from '../security/security-parser.js';
|
|
3
|
+
/**
|
|
4
|
+
* Core dot-notation parser for reading, writing, and removing nested values.
|
|
5
|
+
*
|
|
6
|
+
* Provides path-based access to plain objects using dot-separated keys.
|
|
7
|
+
* Delegates security validation to SecurityGuard and SecurityParser.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* const parser = new DotNotationParser();
|
|
11
|
+
* parser.get({ user: { name: 'Alice' } }, 'user.name'); // 'Alice'
|
|
12
|
+
*/
|
|
13
|
+
export class DotNotationParser {
|
|
14
|
+
securityGuard;
|
|
15
|
+
securityParser;
|
|
16
|
+
pathCache;
|
|
17
|
+
/**
|
|
18
|
+
* @param securityGuard - Key-safety guard. Defaults to a new SecurityGuard instance.
|
|
19
|
+
* @param securityParser - Parser depth and size limits. Defaults to a new SecurityParser instance.
|
|
20
|
+
* @param pathCache - Optional path segment cache for repeated lookups.
|
|
21
|
+
*/
|
|
22
|
+
constructor(securityGuard, securityParser, pathCache) {
|
|
23
|
+
this.securityGuard = securityGuard ?? new SecurityGuard();
|
|
24
|
+
this.securityParser = securityParser ?? new SecurityParser();
|
|
25
|
+
this.pathCache = pathCache ?? null;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Resolve a dot-notation path against data, returning the matched value.
|
|
29
|
+
*
|
|
30
|
+
* @param data - Source data object.
|
|
31
|
+
* @param path - Dot-notation path string (e.g. "user.name").
|
|
32
|
+
* @param defaultValue - Fallback returned when the path does not exist.
|
|
33
|
+
* @returns Resolved value or the default.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* parser.get({ a: { b: 1 } }, 'a.b'); // 1
|
|
37
|
+
* parser.get({ a: 1 }, 'a.b', 'default'); // 'default'
|
|
38
|
+
*/
|
|
39
|
+
get(data, path, defaultValue = null) {
|
|
40
|
+
/* Stryker disable next-line ConditionalExpression,BlockStatement,StringLiteral -- equivalent: empty path on split produces [''] which misses all keys anyway */
|
|
41
|
+
if (path === '') {
|
|
42
|
+
return defaultValue;
|
|
43
|
+
}
|
|
44
|
+
const segments = this.parsePath(path);
|
|
45
|
+
return this.getAt(data, segments, defaultValue);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Set a value at a dot-notation path, returning a new object.
|
|
49
|
+
*
|
|
50
|
+
* @param data - Source data object.
|
|
51
|
+
* @param path - Dot-notation path string.
|
|
52
|
+
* @param value - Value to assign.
|
|
53
|
+
* @returns New object with the value set at the path.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* parser.set({}, 'user.name', 'Alice'); // { user: { name: 'Alice' } }
|
|
57
|
+
*/
|
|
58
|
+
set(data, path, value) {
|
|
59
|
+
const segments = this.parsePath(path);
|
|
60
|
+
return this.setAt(data, segments, value);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Check whether a dot-notation path exists in the data.
|
|
64
|
+
*
|
|
65
|
+
* @param data - Source data object.
|
|
66
|
+
* @param path - Dot-notation path string.
|
|
67
|
+
* @returns True if the path resolves to a value.
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* parser.has({ a: { b: 1 } }, 'a.b'); // true
|
|
71
|
+
* parser.has({ a: 1 }, 'a.b'); // false
|
|
72
|
+
*/
|
|
73
|
+
has(data, path) {
|
|
74
|
+
/* Stryker disable next-line ConditionalExpression,BlockStatement,StringLiteral -- equivalent: empty path produces [''] key which is never found → false anyway */
|
|
75
|
+
if (path === '') {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
const sentinel = Object.create(null);
|
|
79
|
+
return this.get(data, path, sentinel) !== sentinel;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Remove a value at a dot-notation path, returning a new object.
|
|
83
|
+
*
|
|
84
|
+
* @param data - Source data object.
|
|
85
|
+
* @param path - Dot-notation path string.
|
|
86
|
+
* @returns New object with the path removed.
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* parser.remove({ a: { b: 1 } }, 'a.b'); // { a: {} }
|
|
90
|
+
*/
|
|
91
|
+
remove(data, path) {
|
|
92
|
+
const segments = this.parsePath(path);
|
|
93
|
+
return this.removeAt(data, segments);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Resolve a pre-parsed segment array against data.
|
|
97
|
+
*
|
|
98
|
+
* @param data - Source data object.
|
|
99
|
+
* @param segments - Ordered list of keys.
|
|
100
|
+
* @param defaultValue - Fallback returned when the path does not exist.
|
|
101
|
+
* @returns Resolved value or the default.
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* parser.getAt({ a: { b: 1 } }, ['a', 'b']); // 1
|
|
105
|
+
*/
|
|
106
|
+
getAt(data, segments, defaultValue = null) {
|
|
107
|
+
let current = data;
|
|
108
|
+
for (const key of segments) {
|
|
109
|
+
if (
|
|
110
|
+
/* Stryker disable next-line ConditionalExpression -- equivalent: false||null-check||own-check still returns default for primitives */
|
|
111
|
+
typeof current !== 'object' ||
|
|
112
|
+
current === null ||
|
|
113
|
+
!Object.prototype.hasOwnProperty.call(current, key)) {
|
|
114
|
+
return defaultValue;
|
|
115
|
+
}
|
|
116
|
+
current = current[key];
|
|
117
|
+
}
|
|
118
|
+
return current;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Set a value using pre-parsed key segments, returning a new object.
|
|
122
|
+
*
|
|
123
|
+
* @param data - Source data object.
|
|
124
|
+
* @param segments - Ordered list of keys.
|
|
125
|
+
* @param value - Value to assign.
|
|
126
|
+
* @returns New object with the value set.
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* parser.setAt({}, ['user', 'name'], 'Alice'); // { user: { name: 'Alice' } }
|
|
130
|
+
*/
|
|
131
|
+
setAt(data, segments, value) {
|
|
132
|
+
if (segments.length === 0) {
|
|
133
|
+
return data;
|
|
134
|
+
}
|
|
135
|
+
return this.writeAt(data, segments, 0, value);
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Check whether a path exists using pre-parsed key segments.
|
|
139
|
+
*
|
|
140
|
+
* @param data - Source data object.
|
|
141
|
+
* @param segments - Ordered list of keys.
|
|
142
|
+
* @returns True if the path resolves to a value.
|
|
143
|
+
*
|
|
144
|
+
* @example
|
|
145
|
+
* parser.hasAt({ a: { b: 1 } }, ['a', 'b']); // true
|
|
146
|
+
*/
|
|
147
|
+
hasAt(data, segments) {
|
|
148
|
+
const sentinel = Object.create(null);
|
|
149
|
+
return this.getAt(data, segments, sentinel) !== sentinel;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Remove a value using pre-parsed key segments, returning a new object.
|
|
153
|
+
*
|
|
154
|
+
* @param data - Source data object.
|
|
155
|
+
* @param segments - Ordered list of keys.
|
|
156
|
+
* @returns New object without the specified path.
|
|
157
|
+
*
|
|
158
|
+
* @example
|
|
159
|
+
* parser.removeAt({ a: { b: 1 } }, ['a', 'b']); // { a: {} }
|
|
160
|
+
*/
|
|
161
|
+
removeAt(data, segments) {
|
|
162
|
+
/* Stryker disable next-line ConditionalExpression,BlockStatement -- equivalent: empty segments → eraseAt hits undefined key → hasOwnProperty false → returns data anyway */
|
|
163
|
+
if (segments.length === 0) {
|
|
164
|
+
return data;
|
|
165
|
+
}
|
|
166
|
+
return this.eraseAt(data, segments, 0);
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Deep-merge an object into the value at a dot-notation path.
|
|
170
|
+
*
|
|
171
|
+
* @param data - Source data object.
|
|
172
|
+
* @param path - Dot-notation path to the merge target, or empty string for root merge.
|
|
173
|
+
* @param value - Object to merge into the existing value.
|
|
174
|
+
* @returns New object with merged data.
|
|
175
|
+
*
|
|
176
|
+
* @example
|
|
177
|
+
* parser.merge({ a: { b: 1 } }, 'a', { c: 2 }); // { a: { b: 1, c: 2 } }
|
|
178
|
+
*/
|
|
179
|
+
merge(data, path, value) {
|
|
180
|
+
const existing = path !== '' ? this.get(data, path, {}) : data;
|
|
181
|
+
const merged = this.deepMerge(
|
|
182
|
+
/* Stryker disable next-line ConditionalExpression -- equivalent: existing !== null → true still passes {} when existing is not an object due to typeof check */
|
|
183
|
+
typeof existing === 'object' && existing !== null
|
|
184
|
+
? existing
|
|
185
|
+
: {}, value);
|
|
186
|
+
return path !== '' ? this.set(data, path, merged) : merged;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Run all security validations on a parsed data structure.
|
|
190
|
+
*
|
|
191
|
+
* @param data - Data to validate.
|
|
192
|
+
* @throws {SecurityException} When a security violation is detected.
|
|
193
|
+
*
|
|
194
|
+
* @example
|
|
195
|
+
* parser.validate({ name: 'Alice' }); // OK
|
|
196
|
+
* parser.validate({ __construct: 'bad' }); // throws SecurityException
|
|
197
|
+
*/
|
|
198
|
+
validate(data) {
|
|
199
|
+
this.securityParser.assertMaxKeys(data);
|
|
200
|
+
this.securityParser.assertMaxStructuralDepth(data, this.securityParser.getMaxDepth());
|
|
201
|
+
this.securityGuard.assertSafeKeys(data);
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Assert that a string payload does not exceed the configured byte limit.
|
|
205
|
+
*
|
|
206
|
+
* @param input - Raw input string to measure.
|
|
207
|
+
* @throws {SecurityException} When the payload exceeds the limit.
|
|
208
|
+
*
|
|
209
|
+
* @example
|
|
210
|
+
* parser.assertPayload('small text'); // OK
|
|
211
|
+
*/
|
|
212
|
+
assertPayload(input) {
|
|
213
|
+
this.securityParser.assertPayloadSize(input);
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Return the configured maximum structural nesting depth.
|
|
217
|
+
*
|
|
218
|
+
* @returns Maximum allowed depth from the security parser.
|
|
219
|
+
*/
|
|
220
|
+
getMaxDepth() {
|
|
221
|
+
return this.securityParser.getMaxDepth();
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Return the configured maximum total key count.
|
|
225
|
+
*
|
|
226
|
+
* @returns Maximum allowed key count from the security parser.
|
|
227
|
+
*/
|
|
228
|
+
getMaxKeys() {
|
|
229
|
+
return this.securityParser.getMaxKeys();
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Parse a dot-notation path into segments, using cache when available.
|
|
233
|
+
*
|
|
234
|
+
* @param path - Dot-notation path string.
|
|
235
|
+
* @returns Array of path segments.
|
|
236
|
+
*/
|
|
237
|
+
parsePath(path) {
|
|
238
|
+
if (this.pathCache !== null) {
|
|
239
|
+
const cached = this.pathCache.get(path);
|
|
240
|
+
if (cached !== null) {
|
|
241
|
+
return cached;
|
|
242
|
+
}
|
|
243
|
+
const segments = path.split('.');
|
|
244
|
+
this.pathCache.set(path, segments);
|
|
245
|
+
return segments;
|
|
246
|
+
}
|
|
247
|
+
return path.split('.');
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Recursively write a value at the given key path.
|
|
251
|
+
*
|
|
252
|
+
* @param data - Current level data.
|
|
253
|
+
* @param segments - Flat key segments.
|
|
254
|
+
* @param index - Current depth index.
|
|
255
|
+
* @param value - Value to write.
|
|
256
|
+
* @returns Modified copy of the data.
|
|
257
|
+
*
|
|
258
|
+
* @throws {SecurityException} When a key violates security rules.
|
|
259
|
+
*/
|
|
260
|
+
writeAt(data, segments, index, value) {
|
|
261
|
+
const key = segments[index];
|
|
262
|
+
this.securityGuard.assertSafeKey(key);
|
|
263
|
+
const copy = { ...data };
|
|
264
|
+
if (index === segments.length - 1) {
|
|
265
|
+
copy[key] = value;
|
|
266
|
+
return copy;
|
|
267
|
+
}
|
|
268
|
+
const child = copy[key];
|
|
269
|
+
const childObj =
|
|
270
|
+
/* Stryker disable next-line ConditionalExpression -- equivalent: child !== null → true still handles array via !Array.isArray */
|
|
271
|
+
typeof child === 'object' && child !== null && !Array.isArray(child)
|
|
272
|
+
? child
|
|
273
|
+
: {};
|
|
274
|
+
copy[key] = this.writeAt(childObj, segments, index + 1, value);
|
|
275
|
+
return copy;
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Recursively remove a key at the given key path.
|
|
279
|
+
*
|
|
280
|
+
* @param data - Current level data.
|
|
281
|
+
* @param segments - Flat key segments.
|
|
282
|
+
* @param index - Current depth index.
|
|
283
|
+
* @returns Modified copy of the data.
|
|
284
|
+
*
|
|
285
|
+
* @throws {SecurityException} When a key violates security rules.
|
|
286
|
+
*/
|
|
287
|
+
eraseAt(data, segments, index) {
|
|
288
|
+
const key = segments[index];
|
|
289
|
+
this.securityGuard.assertSafeKey(key);
|
|
290
|
+
/* Stryker disable next-line ConditionalExpression,BlockStatement -- equivalent: missing key → continue to delete copy[undefined] which is a no-op */
|
|
291
|
+
if (!Object.prototype.hasOwnProperty.call(data, key)) {
|
|
292
|
+
return data;
|
|
293
|
+
}
|
|
294
|
+
const copy = { ...data };
|
|
295
|
+
/* Stryker disable next-line ConditionalExpression -- equivalent: terminal vs recurse; extra recursion with empty segments produces the same delete */
|
|
296
|
+
if (index === segments.length - 1) {
|
|
297
|
+
delete copy[key];
|
|
298
|
+
return copy;
|
|
299
|
+
}
|
|
300
|
+
const child = copy[key];
|
|
301
|
+
/* Stryker disable next-line ConditionalExpression -- equivalent: false||null removes only null guard but hasOwnProperty on non-objects still returns copy unchanged */
|
|
302
|
+
if (typeof child !== 'object' || child === null) {
|
|
303
|
+
return copy;
|
|
304
|
+
}
|
|
305
|
+
copy[key] = this.eraseAt(child, segments, index + 1);
|
|
306
|
+
return copy;
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Recursively merge source into target, preserving nested structures.
|
|
310
|
+
*
|
|
311
|
+
* @param target - Base data.
|
|
312
|
+
* @param source - Data to merge on top.
|
|
313
|
+
* @param depth - Current recursion depth.
|
|
314
|
+
* @returns Merged result.
|
|
315
|
+
*
|
|
316
|
+
* @throws {SecurityException} When max resolve depth is exceeded.
|
|
317
|
+
*/
|
|
318
|
+
deepMerge(target, source, depth = 0) {
|
|
319
|
+
this.securityParser.assertMaxResolveDepth(depth);
|
|
320
|
+
const result = { ...target };
|
|
321
|
+
for (const [key, sourceVal] of Object.entries(source)) {
|
|
322
|
+
this.securityGuard.assertSafeKey(key);
|
|
323
|
+
const targetVal = result[key];
|
|
324
|
+
if (
|
|
325
|
+
/* Stryker disable next-line ConditionalExpression,LogicalOperator -- equivalent: null checks on objects that are already typed as unknown; isArray guards ensure correct behavior */
|
|
326
|
+
typeof sourceVal === 'object' &&
|
|
327
|
+
/* Stryker disable next-line ConditionalExpression -- equivalent: Array.isArray already prevents non-object arrays */
|
|
328
|
+
sourceVal !== null &&
|
|
329
|
+
!Array.isArray(sourceVal) &&
|
|
330
|
+
/* Stryker disable next-line ConditionalExpression -- equivalent */
|
|
331
|
+
typeof targetVal === 'object' &&
|
|
332
|
+
/* Stryker disable next-line ConditionalExpression -- equivalent: null guarded by Array.isArray check below */
|
|
333
|
+
targetVal !== null &&
|
|
334
|
+
!Array.isArray(targetVal)) {
|
|
335
|
+
result[key] = this.deepMerge(targetVal, sourceVal, depth + 1);
|
|
336
|
+
}
|
|
337
|
+
else {
|
|
338
|
+
result[key] = sourceVal;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return result;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base exception for all accessor-layer errors.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* throw new AccessorException('Something went wrong.');
|
|
6
|
+
*/
|
|
7
|
+
export declare class AccessorException extends Error {
|
|
8
|
+
/**
|
|
9
|
+
* @param message - Human-readable error description.
|
|
10
|
+
* @param options - Optional cause chaining via `ErrorOptions`.
|
|
11
|
+
*/
|
|
12
|
+
constructor(message: string, options?: ErrorOptions);
|
|
13
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base exception for all accessor-layer errors.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* throw new AccessorException('Something went wrong.');
|
|
6
|
+
*/
|
|
7
|
+
export class AccessorException extends Error {
|
|
8
|
+
/**
|
|
9
|
+
* @param message - Human-readable error description.
|
|
10
|
+
* @param options - Optional cause chaining via `ErrorOptions`.
|
|
11
|
+
*/
|
|
12
|
+
constructor(message, options) {
|
|
13
|
+
super(message, options);
|
|
14
|
+
this.name = 'AccessorException';
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { AccessorException } from './accessor-exception.js';
|
|
2
|
+
/**
|
|
3
|
+
* Thrown when the input data cannot be parsed as the expected format.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* throw new InvalidFormatException('Expected JSON string, got number.');
|
|
7
|
+
*/
|
|
8
|
+
export declare class InvalidFormatException extends AccessorException {
|
|
9
|
+
/**
|
|
10
|
+
* @param message - Description of the format violation.
|
|
11
|
+
* @param options - Optional cause chaining via `ErrorOptions`.
|
|
12
|
+
*/
|
|
13
|
+
constructor(message: string, options?: ErrorOptions);
|
|
14
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { AccessorException } from './accessor-exception.js';
|
|
2
|
+
/**
|
|
3
|
+
* Thrown when the input data cannot be parsed as the expected format.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* throw new InvalidFormatException('Expected JSON string, got number.');
|
|
7
|
+
*/
|
|
8
|
+
export class InvalidFormatException extends AccessorException {
|
|
9
|
+
/**
|
|
10
|
+
* @param message - Description of the format violation.
|
|
11
|
+
* @param options - Optional cause chaining via `ErrorOptions`.
|
|
12
|
+
*/
|
|
13
|
+
constructor(message, options) {
|
|
14
|
+
super(message, options);
|
|
15
|
+
this.name = 'InvalidFormatException';
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { AccessorException } from './accessor-exception.js';
|
|
2
|
+
/**
|
|
3
|
+
* Thrown when an underlying parser encounters a structural error.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* throw new ParserException('Parser failed to process input.');
|
|
7
|
+
*/
|
|
8
|
+
export declare class ParserException extends AccessorException {
|
|
9
|
+
/**
|
|
10
|
+
* @param message - Description of the parser failure.
|
|
11
|
+
* @param options - Optional cause chaining via `ErrorOptions`.
|
|
12
|
+
*/
|
|
13
|
+
constructor(message: string, options?: ErrorOptions);
|
|
14
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { AccessorException } from './accessor-exception.js';
|
|
2
|
+
/**
|
|
3
|
+
* Thrown when an underlying parser encounters a structural error.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* throw new ParserException('Parser failed to process input.');
|
|
7
|
+
*/
|
|
8
|
+
export class ParserException extends AccessorException {
|
|
9
|
+
/**
|
|
10
|
+
* @param message - Description of the parser failure.
|
|
11
|
+
* @param options - Optional cause chaining via `ErrorOptions`.
|
|
12
|
+
*/
|
|
13
|
+
constructor(message, options) {
|
|
14
|
+
super(message, options);
|
|
15
|
+
this.name = 'ParserException';
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { AccessorException } from './accessor-exception.js';
|
|
2
|
+
/**
|
|
3
|
+
* Thrown when a dot-notation path does not exist in the data.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* throw new PathNotFoundException("Path 'user.address.zip' not found.");
|
|
7
|
+
*/
|
|
8
|
+
export declare class PathNotFoundException extends AccessorException {
|
|
9
|
+
/**
|
|
10
|
+
* @param message - Description including the missing path.
|
|
11
|
+
* @param options - Optional cause chaining via `ErrorOptions`.
|
|
12
|
+
*/
|
|
13
|
+
constructor(message: string, options?: ErrorOptions);
|
|
14
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { AccessorException } from './accessor-exception.js';
|
|
2
|
+
/**
|
|
3
|
+
* Thrown when a dot-notation path does not exist in the data.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* throw new PathNotFoundException("Path 'user.address.zip' not found.");
|
|
7
|
+
*/
|
|
8
|
+
export class PathNotFoundException extends AccessorException {
|
|
9
|
+
/**
|
|
10
|
+
* @param message - Description including the missing path.
|
|
11
|
+
* @param options - Optional cause chaining via `ErrorOptions`.
|
|
12
|
+
*/
|
|
13
|
+
constructor(message, options) {
|
|
14
|
+
super(message, options);
|
|
15
|
+
this.name = 'PathNotFoundException';
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { AccessorException } from './accessor-exception.js';
|
|
2
|
+
/**
|
|
3
|
+
* Thrown when a write operation is attempted on a readonly accessor.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* const accessor = Inline.fromJson('{}').readonly(true);
|
|
7
|
+
* accessor.set('key', 'value'); // throws ReadonlyViolationException
|
|
8
|
+
*/
|
|
9
|
+
export declare class ReadonlyViolationException extends AccessorException {
|
|
10
|
+
/**
|
|
11
|
+
* @param message - Error message. Defaults to 'Cannot modify a readonly accessor.'
|
|
12
|
+
* @param options - Optional cause chaining via `ErrorOptions`.
|
|
13
|
+
*/
|
|
14
|
+
constructor(message?: string, options?: ErrorOptions);
|
|
15
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { AccessorException } from './accessor-exception.js';
|
|
2
|
+
/**
|
|
3
|
+
* Thrown when a write operation is attempted on a readonly accessor.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* const accessor = Inline.fromJson('{}').readonly(true);
|
|
7
|
+
* accessor.set('key', 'value'); // throws ReadonlyViolationException
|
|
8
|
+
*/
|
|
9
|
+
export class ReadonlyViolationException extends AccessorException {
|
|
10
|
+
/**
|
|
11
|
+
* @param message - Error message. Defaults to 'Cannot modify a readonly accessor.'
|
|
12
|
+
* @param options - Optional cause chaining via `ErrorOptions`.
|
|
13
|
+
*/
|
|
14
|
+
constructor(message = 'Cannot modify a readonly accessor.', options) {
|
|
15
|
+
super(message, options);
|
|
16
|
+
this.name = 'ReadonlyViolationException';
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { AccessorException } from './accessor-exception.js';
|
|
2
|
+
/**
|
|
3
|
+
* Thrown when a security validation check fails.
|
|
4
|
+
*
|
|
5
|
+
* Covers forbidden keys (prototype pollution vectors, legacy prototype manipulation
|
|
6
|
+
* methods, stream wrapper / protocol URI schemes, Node.js globals),
|
|
7
|
+
* payload size violations, key-count limits, and depth limit violations.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* throw new SecurityException("Forbidden key '__proto__' detected.");
|
|
11
|
+
*/
|
|
12
|
+
export declare class SecurityException extends AccessorException {
|
|
13
|
+
/**
|
|
14
|
+
* @param message - Description of the security violation.
|
|
15
|
+
* @param options - Optional cause chaining via `ErrorOptions`.
|
|
16
|
+
*/
|
|
17
|
+
constructor(message: string, options?: ErrorOptions);
|
|
18
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { AccessorException } from './accessor-exception.js';
|
|
2
|
+
/**
|
|
3
|
+
* Thrown when a security validation check fails.
|
|
4
|
+
*
|
|
5
|
+
* Covers forbidden keys (prototype pollution vectors, legacy prototype manipulation
|
|
6
|
+
* methods, stream wrapper / protocol URI schemes, Node.js globals),
|
|
7
|
+
* payload size violations, key-count limits, and depth limit violations.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* throw new SecurityException("Forbidden key '__proto__' detected.");
|
|
11
|
+
*/
|
|
12
|
+
export class SecurityException extends AccessorException {
|
|
13
|
+
/**
|
|
14
|
+
* @param message - Description of the security violation.
|
|
15
|
+
* @param options - Optional cause chaining via `ErrorOptions`.
|
|
16
|
+
*/
|
|
17
|
+
constructor(message, options) {
|
|
18
|
+
super(message, options);
|
|
19
|
+
this.name = 'SecurityException';
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { AccessorException } from './accessor-exception.js';
|
|
2
|
+
/**
|
|
3
|
+
* Thrown when the requested format or TypeFormat value is not supported.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* throw new UnsupportedTypeException('TypeFormat.Csv is not supported.');
|
|
7
|
+
*/
|
|
8
|
+
export declare class UnsupportedTypeException extends AccessorException {
|
|
9
|
+
/**
|
|
10
|
+
* @param message - Description of the unsupported type.
|
|
11
|
+
* @param options - Optional cause chaining via `ErrorOptions`.
|
|
12
|
+
*/
|
|
13
|
+
constructor(message: string, options?: ErrorOptions);
|
|
14
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { AccessorException } from './accessor-exception.js';
|
|
2
|
+
/**
|
|
3
|
+
* Thrown when the requested format or TypeFormat value is not supported.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* throw new UnsupportedTypeException('TypeFormat.Csv is not supported.');
|
|
7
|
+
*/
|
|
8
|
+
export class UnsupportedTypeException extends AccessorException {
|
|
9
|
+
/**
|
|
10
|
+
* @param message - Description of the unsupported type.
|
|
11
|
+
* @param options - Optional cause chaining via `ErrorOptions`.
|
|
12
|
+
*/
|
|
13
|
+
constructor(message, options) {
|
|
14
|
+
super(message, options);
|
|
15
|
+
this.name = 'UnsupportedTypeException';
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { InvalidFormatException } from './invalid-format-exception.js';
|
|
2
|
+
/**
|
|
3
|
+
* Thrown when a YAML string contains invalid syntax or unsafe constructs.
|
|
4
|
+
*
|
|
5
|
+
* Unsafe constructs include: tags (!! and !), anchors (&), aliases (*),
|
|
6
|
+
* and merge keys (<<).
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* throw new YamlParseException('YAML anchors are not supported (line 3).');
|
|
10
|
+
*/
|
|
11
|
+
export declare class YamlParseException extends InvalidFormatException {
|
|
12
|
+
/**
|
|
13
|
+
* @param message - Description of the YAML parse failure.
|
|
14
|
+
* @param options - Optional cause chaining via `ErrorOptions`.
|
|
15
|
+
*/
|
|
16
|
+
constructor(message: string, options?: ErrorOptions);
|
|
17
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { InvalidFormatException } from './invalid-format-exception.js';
|
|
2
|
+
/**
|
|
3
|
+
* Thrown when a YAML string contains invalid syntax or unsafe constructs.
|
|
4
|
+
*
|
|
5
|
+
* Unsafe constructs include: tags (!! and !), anchors (&), aliases (*),
|
|
6
|
+
* and merge keys (<<).
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* throw new YamlParseException('YAML anchors are not supported (line 3).');
|
|
10
|
+
*/
|
|
11
|
+
export class YamlParseException extends InvalidFormatException {
|
|
12
|
+
/**
|
|
13
|
+
* @param message - Description of the YAML parse failure.
|
|
14
|
+
* @param options - Optional cause chaining via `ErrorOptions`.
|
|
15
|
+
*/
|
|
16
|
+
constructor(message, options) {
|
|
17
|
+
super(message, options);
|
|
18
|
+
this.name = 'YamlParseException';
|
|
19
|
+
}
|
|
20
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export { Inline } from './inline.js';
|
|
2
|
+
export { TypeFormat } from './type-format.js';
|
|
3
|
+
export { AbstractAccessor } from './accessors/abstract-accessor.js';
|
|
4
|
+
export { ArrayAccessor } from './accessors/formats/array-accessor.js';
|
|
5
|
+
export { ObjectAccessor } from './accessors/formats/object-accessor.js';
|
|
6
|
+
export { JsonAccessor } from './accessors/formats/json-accessor.js';
|
|
7
|
+
export { XmlAccessor } from './accessors/formats/xml-accessor.js';
|
|
8
|
+
export { YamlAccessor } from './accessors/formats/yaml-accessor.js';
|
|
9
|
+
export { IniAccessor } from './accessors/formats/ini-accessor.js';
|
|
10
|
+
export { EnvAccessor } from './accessors/formats/env-accessor.js';
|
|
11
|
+
export { NdjsonAccessor } from './accessors/formats/ndjson-accessor.js';
|
|
12
|
+
export { AnyAccessor } from './accessors/formats/any-accessor.js';
|
|
13
|
+
export { SecurityGuard } from './security/security-guard.js';
|
|
14
|
+
export { SecurityParser } from './security/security-parser.js';
|
|
15
|
+
export { AccessorException } from './exceptions/accessor-exception.js';
|
|
16
|
+
export { InvalidFormatException } from './exceptions/invalid-format-exception.js';
|
|
17
|
+
export { YamlParseException } from './exceptions/yaml-parse-exception.js';
|
|
18
|
+
export { ParserException } from './exceptions/parser-exception.js';
|
|
19
|
+
export { PathNotFoundException } from './exceptions/path-not-found-exception.js';
|
|
20
|
+
export { ReadonlyViolationException } from './exceptions/readonly-violation-exception.js';
|
|
21
|
+
export { SecurityException } from './exceptions/security-exception.js';
|
|
22
|
+
export { UnsupportedTypeException } from './exceptions/unsupported-type-exception.js';
|
|
23
|
+
export type { AccessorsInterface } from './contracts/accessors-interface.js';
|
|
24
|
+
export type { ReadableAccessorsInterface } from './contracts/readable-accessors-interface.js';
|
|
25
|
+
export type { WritableAccessorsInterface } from './contracts/writable-accessors-interface.js';
|
|
26
|
+
export type { FactoryAccessorsInterface } from './contracts/factory-accessors-interface.js';
|
|
27
|
+
export type { SecurityGuardInterface } from './contracts/security-guard-interface.js';
|
|
28
|
+
export type { SecurityParserInterface } from './contracts/security-parser-interface.js';
|
|
29
|
+
export type { PathCacheInterface } from './contracts/path-cache-interface.js';
|
|
30
|
+
export type { ParseIntegrationInterface } from './contracts/parse-integration-interface.js';
|