@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
package/.gitattributes CHANGED
@@ -1,4 +1,4 @@
1
- # Distribution archive ignore files excluded from npm/archive downloads
1
+ # Distribution archive ignore - files excluded from npm/archive downloads
2
2
  # Note: npm uses the `files` field in package.json for publish filtering.
3
3
  # These export-ignore rules apply to git archive and the split repo.
4
4
 
package/CHANGELOG.md CHANGED
@@ -2,17 +2,35 @@
2
2
 
3
3
  All notable changes to the `@safeaccess/inline` JavaScript/TypeScript package are documented in this file.
4
4
 
5
- ## [0.1.1](https://github.com/felipesauer/safeaccess-inline/compare/js-v0.1.0...js-v0.1.1) (2026-04-07)
5
+ ## [0.1.3](https://github.com/felipesauer/safeaccess-inline/compare/js-v0.1.2...js-v0.1.3) (2026-04-09)
6
6
 
7
7
 
8
- ### Features
8
+ ### Bug Fixes
9
+
10
+ * **js:** expose readonly extraForbiddenKeys on SecurityGuard for PHP parity ([2b428f6](https://github.com/felipesauer/safeaccess-inline/commit/2b428f6a1fef3607cb968ff18b52d8281158cc92))
11
+ * **php:** correct array<string,mixed> type annotations and NdjsonAccessor integer key coercion ([7849f89](https://github.com/felipesauer/safeaccess-inline/commit/7849f89365bd5970738105ed3be9d2b58a15cd93))
12
+
13
+ ## [0.1.2](https://github.com/felipesauer/safeaccess-inline/compare/js-v0.1.1...js-v0.1.2) (2026-04-08)
14
+
15
+ ### Bug Fixes
9
16
 
10
- * **js:** bootstrap release tracking for rebranded package ([5fc07d7](https://github.com/felipesauer/safeaccess-inline/commit/5fc07d7126870d72145bbfc80609370c9d1509c7))
17
+ - **js:** fix logo image URL in README ([16f4fc5](https://github.com/felipesauer/safeaccess-inline/commit/16f4fc5d69fa7ce86e3017bbbfc9f393925a5c37))
18
+
19
+ ### Internal Changes
20
+
21
+ - **js:** expose `readonly extraForbiddenKeys` on `SecurityGuard` for parity with PHP (`public readonly array $extraForbiddenKeys`)
22
+ - **js:** extract `ValidatableParserInterface` from `DotNotationParser` — `AbstractAccessor` now types its parser dependency against this contract instead of the concrete class
23
+ - **js:** `SecurityGuard.sanitize()` handles nested arrays via a dedicated `sanitizeArray()` private method, matching the PHP `sanitizeRecursive` pattern
24
+
25
+ ## [0.1.1](https://github.com/felipesauer/safeaccess-inline/compare/js-v0.1.0...js-v0.1.1) (2026-04-07)
26
+
27
+ ### Features
11
28
 
29
+ - **js:** bootstrap release tracking for rebranded package ([5fc07d7](https://github.com/felipesauer/safeaccess-inline/commit/5fc07d7126870d72145bbfc80609370c9d1509c7))
12
30
 
13
31
  ### Bug Fixes
14
32
 
15
- * **js:** add repository field for npm provenance validation ([b34cdef](https://github.com/felipesauer/safeaccess-inline/commit/b34cdeff01e7e7566921f04b11f33fbd391aa8d2))
33
+ - **js:** add repository field for npm provenance validation ([b34cdef](https://github.com/felipesauer/safeaccess-inline/commit/b34cdeff01e7e7566921f04b11f33fbd391aa8d2))
16
34
 
17
35
  ## 0.1.0 (2026-04-07)
18
36
 
@@ -20,7 +38,7 @@ All notable changes to the `@safeaccess/inline` JavaScript/TypeScript package ar
20
38
 
21
39
  - **ci:** achieve 100% branch coverage on Vitest 4.x and fix docs-ci workflow ([#14](https://github.com/felipesauer/safeaccess-inline/issues/14)) ([11daf5a](https://github.com/felipesauer/safeaccess-inline/commit/11daf5aaa1ff1b901c8297921533485f1584a330))
22
40
 
23
- ## [0.1.0] 2026-04-06
41
+ ## [0.1.0] - 2026-04-06
24
42
 
25
43
  ### Features
26
44
 
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 Felipe Sauer
3
+ Copyright (c) 2026 Safe Access Inline
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  <p align="center">
2
- <img src="../../public/logo.svg" width="80" alt="safeaccess-inline logo">
2
+ <img src="https://raw.githubusercontent.com/felipesauer/safeaccess-inline/main/.github/assets/logo.svg" width="80" alt="safeaccess-inline logo">
3
3
  </p>
4
4
 
5
- <h1 align="center">Safe Access Inline TypeScript</h1>
5
+ <h1 align="center">Safe Access Inline - TypeScript</h1>
6
6
 
7
7
  <p align="center">
8
8
  <a href="https://www.npmjs.com/package/@safeaccess/inline"><img src="https://img.shields.io/npm/v/@safeaccess/inline?label=npm" alt="npm"></a>
@@ -11,7 +11,7 @@
11
11
 
12
12
  ---
13
13
 
14
- Safe nested data access with dot notation for JavaScript and TypeScript. Navigate deeply nested objects, JSON, YAML, XML, INI, ENV, and NDJSON structures with built-in security validation, immutable writes, and a fluent builder API.
14
+ Safe nested data access with dot notation for JavaScript and TypeScript. Navigate deeply nested arrays, objects, JSON, YAML, XML, INI, ENV, and NDJSON structures - with built-in security validation, immutable writes, and a fluent builder API.
15
15
 
16
16
  ## Installation
17
17
 
@@ -33,7 +33,7 @@ accessor.get('user.email', 'N/A'); // 'N/A' (default when missing)
33
33
  accessor.has('user.age'); // true
34
34
  accessor.getOrFail('user.name'); // 'Alice' (throws if missing)
35
35
 
36
- // Immutable writes original is never modified
36
+ // Immutable writes - original is never modified
37
37
  const updated = accessor.set('user.email', 'alice@example.com');
38
38
  updated.get('user.email'); // 'alice@example.com'
39
39
  accessor.has('user.email'); // false (original unchanged)
@@ -41,12 +41,14 @@ accessor.has('user.email'); // false (original unchanged)
41
41
 
42
42
  ## Dot Notation Syntax
43
43
 
44
- The TypeScript package supports dot-separated key access:
44
+ ### Basic Syntax
45
45
 
46
- | Syntax | Example | Description |
47
- | ----------- | -------------- | ------------------------- |
48
- | `key.key` | `user.name` | Nested key access |
49
- | `key.0.key` | `users.0.name` | Numeric key (array index) |
46
+ | Syntax | Example | Description |
47
+ | ----------------- | ------------------ | ------------------------------- |
48
+ | `key.key` | `user.name` | Nested key access |
49
+ | `key.0.key` | `users.0.name` | Numeric key (array index) |
50
+ | `key\.with\.dots` | `config\.db\.host` | Escaped dots in key names |
51
+ | `$` or `$.path` | `$.user.name` | Optional root prefix (stripped) |
50
52
 
51
53
  ```typescript
52
54
  const data = Inline.fromJson('{"users": [{"name": "Alice"}, {"name": "Bob"}]}');
@@ -54,7 +56,46 @@ data.get('users.0.name'); // 'Alice'
54
56
  data.get('users.1.name'); // 'Bob'
55
57
  ```
56
58
 
57
- > **Note:** Advanced PathQuery features (wildcards, filters, slices, recursive descent, projections) are available in the PHP package only. See the [PHP README](../php/README.md#advanced-pathquery) for details.
59
+ ### Advanced PathQuery
60
+
61
+ | Syntax | Example | Description |
62
+ | --------------- | ------------------- | ----------------------------------------- |
63
+ | `[0]` | `users[0]` | Bracket index access |
64
+ | `*` or `[*]` | `users.*` | Wildcard - expand all children |
65
+ | `..key` | `..name` | Recursive descent - find key at any depth |
66
+ | `..['a','b']` | `..['name','age']` | Multi-key recursive descent |
67
+ | `[0,1,2]` | `users[0,1,2]` | Multi-index selection |
68
+ | `['a','b']` | `['name','age']` | Multi-key selection |
69
+ | `[0:5]` | `items[0:5]` | Slice - indices 0 through 4 |
70
+ | `[::2]` | `items[::2]` | Slice with step |
71
+ | `[::-1]` | `items[::-1]` | Reverse slice |
72
+ | `[?expr]` | `users[?age>18]` | Filter predicate expression |
73
+ | `.{fields}` | `.{name, age}` | Projection - select fields |
74
+ | `.{alias: src}` | `.{fullName: name}` | Aliased projection |
75
+
76
+ ### Filter Expressions
77
+
78
+ ```typescript
79
+ const data = Inline.fromJson(`[
80
+ {"name": "Alice", "age": 25, "role": "admin"},
81
+ {"name": "Bob", "age": 17, "role": "user"},
82
+ {"name": "Carol", "age": 30, "role": "admin"}
83
+ ]`);
84
+
85
+ // Comparison: ==, !=, >, <, >=, <=
86
+ data.get('[?age>18]'); // Alice and Carol
87
+
88
+ // Logical: && and ||
89
+ data.get('[?age>18 && role=="admin"]'); // Alice and Carol
90
+
91
+ // Built-in functions: starts_with, contains, values
92
+ data.get('[?starts_with(@.name, "A")]'); // Alice
93
+ data.get('[?contains(@.name, "ob")]'); // Bob
94
+
95
+ // Arithmetic in predicates: +, -, *, /
96
+ const orders = Inline.fromJson('[{"price": 10, "qty": 5}, {"price": 3, "qty": 2}]');
97
+ orders.get('[?@.price * @.qty > 20]'); // first order only
98
+ ```
58
99
 
59
100
  ## Supported Formats
60
101
 
@@ -110,7 +151,7 @@ accessor.get('database.host'); // 'localhost'
110
151
  <summary><strong>ENV (dotenv)</strong></summary>
111
152
 
112
153
  ```typescript
113
- const accessor = Inline.fromEnv('APP_NAME=MyApp\nDB_HOST=localhost');
154
+ const accessor = Inline.fromEnv('APP_NAME=MyApp\nAPP_DEBUG=true\nDB_HOST=localhost');
114
155
  accessor.get('DB_HOST'); // 'localhost'
115
156
  ```
116
157
 
@@ -140,6 +181,20 @@ objAccessor.get('name'); // 'Alice'
140
181
 
141
182
  </details>
142
183
 
184
+ <details>
185
+ <summary><strong>Any (custom format via integration)</strong></summary>
186
+
187
+ ```typescript
188
+ import { Inline } from '@safeaccess/inline';
189
+ import type { ParseIntegrationInterface } from '@safeaccess/inline';
190
+
191
+ // Requires implementing ParseIntegrationInterface
192
+ const accessor = Inline.withParserIntegration(new MyCsvIntegration()).fromAny(csvString);
193
+ accessor.get('0.column_name');
194
+ ```
195
+
196
+ </details>
197
+
143
198
  <details>
144
199
  <summary><strong>Dynamic (by TypeFormat enum)</strong></summary>
145
200
 
@@ -173,13 +228,11 @@ accessor.getMany({
173
228
  }); // { 'a.b': 1, 'a.x': 'fallback' }
174
229
  accessor.getRaw(); // original JSON string
175
230
 
176
- // Write (immutable every write returns a new instance)
177
- const updated = accessor.set('a.d', 3);
178
- const cleaned = updated.remove('a.c');
179
- const merged = cleaned.merge('a', { e: 4 });
180
- const full = merged.mergeAll({ f: 5 });
231
+ // Write (immutable - every write returns a new instance)
232
+ const updated = accessor.set('a.d', 3).remove('a.c').merge('a', { e: 4 }).mergeAll({ f: 5 });
233
+ updated.all(); // { a: { b: 1, d: 3, e: 4 }, f: 5 }
181
234
 
182
- // Readonly mode block all writes
235
+ // Readonly mode - block all writes
183
236
  const readonly = accessor.readonly();
184
237
  readonly.get('a.b'); // 1 (reads work)
185
238
  readonly.set('a.b', 99); // throws ReadonlyViolationException
@@ -204,7 +257,7 @@ const accessor = Inline.withSecurityGuard(new SecurityGuard(512, ['secret']))
204
257
  | ------------------------------------ | ------------------------------------------------ |
205
258
  | `withSecurityGuard(guard)` | Custom forbidden-key rules and depth limits |
206
259
  | `withSecurityParser(parser)` | Custom payload size and structural limits |
207
- | `withPathCache(cache)` | Custom path segment cache for repeated lookups |
260
+ | `withPathCache(cache)` | Path segment cache for repeated lookups |
208
261
  | `withParserIntegration(integration)` | Custom format parser for `fromAny()` |
209
262
  | `withStrictMode(false)` | Disable security validation (trusted input only) |
210
263
 
@@ -243,7 +296,7 @@ const accessor = Inline.withSecurityGuard(guard).fromJson(data);
243
296
 
244
297
  | Format | Protection |
245
298
  | ------ | ------------------------------------------------ |
246
- | XML | Rejects `<!DOCTYPE` prevents XXE attacks |
299
+ | XML | Rejects `<!DOCTYPE` - prevents XXE attacks |
247
300
  | YAML | Blocks unsafe tags, anchors, aliases, merge keys |
248
301
  | All | Forbidden key validation on every parsed key |
249
302
 
@@ -291,7 +344,7 @@ try {
291
344
 
292
345
  | Exception | Extends | When |
293
346
  | ---------------------------- | ------------------------ | ----------------------------------------- |
294
- | `AccessorException` | `Error` | Root catch-all |
347
+ | `AccessorException` | `Error` | Root - catch-all |
295
348
  | `SecurityException` | `AccessorException` | Forbidden key, payload, structural limits |
296
349
  | `InvalidFormatException` | `AccessorException` | Malformed JSON, XML, INI, NDJSON |
297
350
  | `YamlParseException` | `InvalidFormatException` | Unsafe or malformed YAML |
@@ -315,6 +368,7 @@ const accessor = Inline.withStrictMode(false).fromJson(trustedPayload);
315
368
 
316
369
  ```typescript
317
370
  // Implement PathCacheInterface for repeated lookups
371
+ const cacheMap = new Map();
318
372
  const cache: PathCacheInterface = {
319
373
  get: (path) => cacheMap.get(path) ?? null,
320
374
  set: (path, segments) => {
@@ -349,7 +403,7 @@ const accessor = Inline.withParserIntegration(csvIntegration).fromAny(csvString)
349
403
 
350
404
  ## API Reference
351
405
 
352
- ### Facade: `Inline`
406
+ ### `Inline` Facade
353
407
 
354
408
  #### Static Factory Methods
355
409
 
@@ -400,6 +454,10 @@ const accessor = Inline.withParserIntegration(csvIntegration).fromAny(csvString)
400
454
  | `readonly(flag?)` | Block all writes |
401
455
  | `strict(flag?)` | Toggle security validation |
402
456
 
457
+ #### TypeFormat Enum
458
+
459
+ `Array` · `Object` · `Json` · `Xml` · `Yaml` · `Ini` · `Env` · `Ndjson` · `Any`
460
+
403
461
  ## Exports
404
462
 
405
463
  The package uses **named exports only** (no default exports). All public types are available from the main entry point:
@@ -1,13 +1,25 @@
1
1
  import type { AccessorsInterface } from '../contracts/accessors-interface.js';
2
- import { DotNotationParser } from '../core/dot-notation-parser.js';
2
+ import type { ValidatableParserInterface } from '../contracts/validatable-parser-interface.js';
3
+ /**
4
+ * Base accessor providing read, write, and lifecycle operations.
5
+ *
6
+ * Implements all AccessorsInterface methods with immutable copy
7
+ * semantics for writes, optional readonly enforcement, and strict mode
8
+ * for security validation on data ingestion.
9
+ *
10
+ * Subclasses must implement `parse()` to convert raw input into
11
+ * a normalized plain object.
12
+ *
13
+ * @api
14
+ */
3
15
  export declare abstract class AbstractAccessor implements AccessorsInterface {
4
- protected readonly parser: DotNotationParser;
16
+ protected readonly parser: ValidatableParserInterface;
5
17
  /** @internal Mutable state grouped to allow O(1) shallow clone in mutations. */
6
18
  private _state;
7
19
  /**
8
20
  * @param parser - Dot-notation parser for path operations.
9
21
  */
10
- constructor(parser: DotNotationParser);
22
+ constructor(parser: ValidatableParserInterface);
11
23
  /**
12
24
  * Convert raw input data into a normalized plain object.
13
25
  *
@@ -33,6 +45,8 @@ export declare abstract class AbstractAccessor implements AccessorsInterface {
33
45
  *
34
46
  * @param data - Raw input in the format expected by the accessor.
35
47
  * @returns Populated accessor instance.
48
+ * @throws {InvalidFormatException} When the raw input cannot be parsed.
49
+ * @throws {SecurityException} When payload exceeds size limit, data contains forbidden keys, or violates structural limits.
36
50
  */
37
51
  abstract from(data: unknown): this;
38
52
  /**
@@ -59,7 +73,7 @@ export declare abstract class AbstractAccessor implements AccessorsInterface {
59
73
  * Only use with fully trusted, application-controlled input.
60
74
  *
61
75
  * @example
62
- * // Trust the input skip all security checks
76
+ * // Trust the input - skip all security checks
63
77
  * const accessor = new JsonAccessor(parser).strict(false).from(trustedPayload);
64
78
  */
65
79
  strict(strict?: boolean): this;
@@ -159,19 +173,19 @@ export declare abstract class AbstractAccessor implements AccessorsInterface {
159
173
  */
160
174
  all(): Record<string, unknown>;
161
175
  /**
162
- * Count elements at a path, or the root if undefined.
176
+ * Count elements at a path, or the root if null/undefined.
163
177
  *
164
- * @param path - Dot-notation path, or undefined for root.
178
+ * @param path - Dot-notation path, or null/undefined for root.
165
179
  * @returns Number of elements.
166
180
  */
167
- count(path?: string): number;
181
+ count(path?: string | null): number;
168
182
  /**
169
- * Retrieve array keys at a path, or root keys if undefined.
183
+ * Retrieve array keys at a path, or root keys if null/undefined.
170
184
  *
171
- * @param path - Dot-notation path, or undefined for root.
185
+ * @param path - Dot-notation path, or null/undefined for root.
172
186
  * @returns List of keys.
173
187
  */
174
- keys(path?: string): string[];
188
+ keys(path?: string | null): string[];
175
189
  /**
176
190
  * Deep-merge an object into the value at a dot-notation path.
177
191
  *
@@ -1,5 +1,17 @@
1
1
  import { PathNotFoundException } from '../exceptions/path-not-found-exception.js';
2
2
  import { ReadonlyViolationException } from '../exceptions/readonly-violation-exception.js';
3
+ /**
4
+ * Base accessor providing read, write, and lifecycle operations.
5
+ *
6
+ * Implements all AccessorsInterface methods with immutable copy
7
+ * semantics for writes, optional readonly enforcement, and strict mode
8
+ * for security validation on data ingestion.
9
+ *
10
+ * Subclasses must implement `parse()` to convert raw input into
11
+ * a normalized plain object.
12
+ *
13
+ * @api
14
+ */
3
15
  export class AbstractAccessor {
4
16
  parser;
5
17
  /** @internal Mutable state grouped to allow O(1) shallow clone in mutations. */
@@ -68,7 +80,7 @@ export class AbstractAccessor {
68
80
  * Only use with fully trusted, application-controlled input.
69
81
  *
70
82
  * @example
71
- * // Trust the input skip all security checks
83
+ * // Trust the input - skip all security checks
72
84
  * const accessor = new JsonAccessor(parser).strict(false).from(trustedPayload);
73
85
  */
74
86
  strict(strict = true) {
@@ -133,7 +145,8 @@ export class AbstractAccessor {
133
145
  * @returns True if the path resolves to a value.
134
146
  */
135
147
  hasAt(segments) {
136
- return this.parser.hasAt(this._state.data, segments);
148
+ const sentinel = Object.create(null);
149
+ return this.parser.getAt(this._state.data, segments, sentinel) !== sentinel;
137
150
  }
138
151
  /**
139
152
  * Set a value at a dot-notation path.
@@ -207,26 +220,26 @@ export class AbstractAccessor {
207
220
  return this._state.data;
208
221
  }
209
222
  /**
210
- * Count elements at a path, or the root if undefined.
223
+ * Count elements at a path, or the root if null/undefined.
211
224
  *
212
- * @param path - Dot-notation path, or undefined for root.
225
+ * @param path - Dot-notation path, or null/undefined for root.
213
226
  * @returns Number of elements.
214
227
  */
215
228
  count(path) {
216
- const target = path !== undefined ? this.get(path, {}) : this._state.data;
229
+ const target = path != null ? this.get(path, {}) : this._state.data;
217
230
  if (typeof target === 'object' && target !== null) {
218
231
  return Object.keys(target).length;
219
232
  }
220
233
  return 0;
221
234
  }
222
235
  /**
223
- * Retrieve array keys at a path, or root keys if undefined.
236
+ * Retrieve array keys at a path, or root keys if null/undefined.
224
237
  *
225
- * @param path - Dot-notation path, or undefined for root.
238
+ * @param path - Dot-notation path, or null/undefined for root.
226
239
  * @returns List of keys.
227
240
  */
228
241
  keys(path) {
229
- const target = path !== undefined ? this.get(path, {}) : this._state.data;
242
+ const target = path != null ? this.get(path, {}) : this._state.data;
230
243
  /* Stryker disable next-line ConditionalExpression -- equivalent: get() always returns an object-type value here; typeof check is a type guard only */
231
244
  if (typeof target === 'object' && target !== null) {
232
245
  return Object.keys(target);
@@ -0,0 +1,22 @@
1
+ import { AbstractAccessor } from './abstract-accessor.js';
2
+ import type { ParseIntegrationInterface } from '../contracts/parse-integration-interface.js';
3
+ import type { ValidatableParserInterface } from '../contracts/validatable-parser-interface.js';
4
+ /**
5
+ * Base accessor with custom format integration support.
6
+ *
7
+ * Extends {@link AbstractAccessor} to inject a {@link ParseIntegrationInterface}
8
+ * for user-defined format detection and parsing. Used exclusively by
9
+ * {@link AnyAccessor} to handle arbitrary input formats.
10
+ *
11
+ * @internal
12
+ */
13
+ export declare abstract class AbstractIntegrationAccessor extends AbstractAccessor {
14
+ protected readonly integration: ParseIntegrationInterface;
15
+ /**
16
+ * Create an accessor with parser and custom integration dependencies.
17
+ *
18
+ * @param parser - Dot-notation parser.
19
+ * @param integration - Custom format parser.
20
+ */
21
+ constructor(parser: ValidatableParserInterface, integration: ParseIntegrationInterface);
22
+ }
@@ -0,0 +1,23 @@
1
+ import { AbstractAccessor } from './abstract-accessor.js';
2
+ /**
3
+ * Base accessor with custom format integration support.
4
+ *
5
+ * Extends {@link AbstractAccessor} to inject a {@link ParseIntegrationInterface}
6
+ * for user-defined format detection and parsing. Used exclusively by
7
+ * {@link AnyAccessor} to handle arbitrary input formats.
8
+ *
9
+ * @internal
10
+ */
11
+ export class AbstractIntegrationAccessor extends AbstractAccessor {
12
+ integration;
13
+ /**
14
+ * Create an accessor with parser and custom integration dependencies.
15
+ *
16
+ * @param parser - Dot-notation parser.
17
+ * @param integration - Custom format parser.
18
+ */
19
+ constructor(parser, integration) {
20
+ super(parser);
21
+ this.integration = integration;
22
+ }
23
+ }
@@ -1,24 +1,25 @@
1
- import { AbstractAccessor } from '../abstract-accessor.js';
1
+ import { AbstractIntegrationAccessor } from '../abstract-integration-accessor.js';
2
2
  import type { ParseIntegrationInterface } from '../../contracts/parse-integration-interface.js';
3
- import type { DotNotationParser } from '../../core/dot-notation-parser.js';
3
+ import type { ValidatableParserInterface } from '../../contracts/validatable-parser-interface.js';
4
4
  /**
5
5
  * Accessor for arbitrary formats via a custom {@link ParseIntegrationInterface}.
6
6
  *
7
7
  * Delegates format detection and parsing to a user-provided integration.
8
8
  * Validates string payloads against security constraints before parsing.
9
9
  *
10
+ * @api
11
+ *
10
12
  * @example
11
13
  * const integration = new MyCsvIntegration();
12
14
  * const accessor = Inline.withParserIntegration(integration).fromAny(csvString);
13
15
  * accessor.get('0.name'); // first row, name column
14
16
  */
15
- export declare class AnyAccessor extends AbstractAccessor {
16
- private readonly integration;
17
+ export declare class AnyAccessor extends AbstractIntegrationAccessor {
17
18
  /**
18
19
  * @param parser - Dot-notation parser with security configuration.
19
20
  * @param integration - Custom format parser for detecting and parsing input.
20
21
  */
21
- constructor(parser: DotNotationParser, integration: ParseIntegrationInterface);
22
+ constructor(parser: ValidatableParserInterface, integration: ParseIntegrationInterface);
22
23
  /**
23
24
  * Hydrate from raw data via the custom integration.
24
25
  *
@@ -26,10 +27,11 @@ export declare class AnyAccessor extends AbstractAccessor {
26
27
  * @returns Populated accessor instance.
27
28
  * @throws {InvalidFormatException} When the integration rejects the format.
28
29
  * @throws {SecurityException} When string input violates payload-size limits.
30
+ *
31
+ * @example
32
+ * const accessor = new AnyAccessor(parser, integration).from(rawData);
29
33
  */
30
34
  from(data: unknown): this;
31
- /**
32
- * @internal
33
- */
35
+ /** {@inheritDoc} */
34
36
  protected parse(raw: unknown): Record<string, unknown>;
35
37
  }
@@ -1,4 +1,4 @@
1
- import { AbstractAccessor } from '../abstract-accessor.js';
1
+ import { AbstractIntegrationAccessor } from '../abstract-integration-accessor.js';
2
2
  import { InvalidFormatException } from '../../exceptions/invalid-format-exception.js';
3
3
  /**
4
4
  * Accessor for arbitrary formats via a custom {@link ParseIntegrationInterface}.
@@ -6,20 +6,20 @@ import { InvalidFormatException } from '../../exceptions/invalid-format-exceptio
6
6
  * Delegates format detection and parsing to a user-provided integration.
7
7
  * Validates string payloads against security constraints before parsing.
8
8
  *
9
+ * @api
10
+ *
9
11
  * @example
10
12
  * const integration = new MyCsvIntegration();
11
13
  * const accessor = Inline.withParserIntegration(integration).fromAny(csvString);
12
14
  * accessor.get('0.name'); // first row, name column
13
15
  */
14
- export class AnyAccessor extends AbstractAccessor {
15
- integration;
16
+ export class AnyAccessor extends AbstractIntegrationAccessor {
16
17
  /**
17
18
  * @param parser - Dot-notation parser with security configuration.
18
19
  * @param integration - Custom format parser for detecting and parsing input.
19
20
  */
20
21
  constructor(parser, integration) {
21
- super(parser);
22
- this.integration = integration;
22
+ super(parser, integration);
23
23
  }
24
24
  /**
25
25
  * Hydrate from raw data via the custom integration.
@@ -28,6 +28,9 @@ export class AnyAccessor extends AbstractAccessor {
28
28
  * @returns Populated accessor instance.
29
29
  * @throws {InvalidFormatException} When the integration rejects the format.
30
30
  * @throws {SecurityException} When string input violates payload-size limits.
31
+ *
32
+ * @example
33
+ * const accessor = new AnyAccessor(parser, integration).from(rawData);
31
34
  */
32
35
  from(data) {
33
36
  if (!this.integration.assertFormat(data)) {
@@ -35,9 +38,7 @@ export class AnyAccessor extends AbstractAccessor {
35
38
  }
36
39
  return this.ingest(data);
37
40
  }
38
- /**
39
- * @internal
40
- */
41
+ /** {@inheritDoc} */
41
42
  parse(raw) {
42
43
  return this.integration.parse(raw);
43
44
  }
@@ -4,6 +4,8 @@ import { AbstractAccessor } from '../abstract-accessor.js';
4
4
  *
5
5
  * Accepts a plain object or array directly. No string parsing is involved.
6
6
  *
7
+ * @api
8
+ *
7
9
  * @example
8
10
  * const accessor = new ArrayAccessor(parser).from({ key: 'value' });
9
11
  * accessor.get('key'); // 'value'
@@ -5,6 +5,8 @@ import { InvalidFormatException } from '../../exceptions/invalid-format-exceptio
5
5
  *
6
6
  * Accepts a plain object or array directly. No string parsing is involved.
7
7
  *
8
+ * @api
9
+ *
8
10
  * @example
9
11
  * const accessor = new ArrayAccessor(parser).from({ key: 'value' });
10
12
  * accessor.get('key'); // 'value'
@@ -5,6 +5,8 @@ import { AbstractAccessor } from '../abstract-accessor.js';
5
5
  * Parses KEY=VALUE lines, skipping comments (#) and blank lines.
6
6
  * Strips surrounding single and double quotes from values.
7
7
  *
8
+ * @api
9
+ *
8
10
  * @example
9
11
  * const accessor = new EnvAccessor(parser).from('DB_HOST=localhost\nDEBUG=true');
10
12
  * accessor.get('DB_HOST'); // 'localhost'
@@ -6,6 +6,8 @@ import { InvalidFormatException } from '../../exceptions/invalid-format-exceptio
6
6
  * Parses KEY=VALUE lines, skipping comments (#) and blank lines.
7
7
  * Strips surrounding single and double quotes from values.
8
8
  *
9
+ * @api
10
+ *
9
11
  * @example
10
12
  * const accessor = new EnvAccessor(parser).from('DB_HOST=localhost\nDEBUG=true');
11
13
  * accessor.get('DB_HOST'); // 'localhost'
@@ -5,6 +5,8 @@ import { AbstractAccessor } from '../abstract-accessor.js';
5
5
  * Parses sections (e.g. `[section]`) as nested keys.
6
6
  * Type inference: numeric strings become numbers, `true`/`false` become booleans.
7
7
  *
8
+ * @api
9
+ *
8
10
  * @example
9
11
  * const accessor = new IniAccessor(parser).from('[db]\nhost=localhost\nport=5432');
10
12
  * accessor.get('db.host'); // 'localhost'
@@ -6,6 +6,8 @@ import { InvalidFormatException } from '../../exceptions/invalid-format-exceptio
6
6
  * Parses sections (e.g. `[section]`) as nested keys.
7
7
  * Type inference: numeric strings become numbers, `true`/`false` become booleans.
8
8
  *
9
+ * @api
10
+ *
9
11
  * @example
10
12
  * const accessor = new IniAccessor(parser).from('[db]\nhost=localhost\nport=5432');
11
13
  * accessor.get('db.host'); // 'localhost'
@@ -4,6 +4,8 @@ import { AbstractAccessor } from '../abstract-accessor.js';
4
4
  *
5
5
  * Decodes JSON via `JSON.parse()`. Validates payload size before parsing.
6
6
  *
7
+ * @api
8
+ *
7
9
  * @example
8
10
  * const accessor = new JsonAccessor(parser).from('{"key":"value"}');
9
11
  * accessor.get('key'); // 'value'
@@ -5,6 +5,8 @@ import { InvalidFormatException } from '../../exceptions/invalid-format-exceptio
5
5
  *
6
6
  * Decodes JSON via `JSON.parse()`. Validates payload size before parsing.
7
7
  *
8
+ * @api
9
+ *
8
10
  * @example
9
11
  * const accessor = new JsonAccessor(parser).from('{"key":"value"}');
10
12
  * accessor.get('key'); // 'value'
@@ -5,6 +5,8 @@ import { AbstractAccessor } from '../abstract-accessor.js';
5
5
  * Parses each non-empty line as a standalone JSON object,
6
6
  * producing an indexed record of parsed entries.
7
7
  *
8
+ * @api
9
+ *
8
10
  * @example
9
11
  * const ndjson = '{"id":1}\n{"id":2}';
10
12
  * const accessor = new NdjsonAccessor(parser).from(ndjson);
@@ -6,6 +6,8 @@ import { InvalidFormatException } from '../../exceptions/invalid-format-exceptio
6
6
  * Parses each non-empty line as a standalone JSON object,
7
7
  * producing an indexed record of parsed entries.
8
8
  *
9
+ * @api
10
+ *
9
11
  * @example
10
12
  * const ndjson = '{"id":1}\n{"id":2}';
11
13
  * const accessor = new NdjsonAccessor(parser).from(ndjson);