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