@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
package/.gitattributes ADDED
@@ -0,0 +1,16 @@
1
+ # Distribution archive ignore — files excluded from npm/archive downloads
2
+ # Note: npm uses the `files` field in package.json for publish filtering.
3
+ # These export-ignore rules apply to git archive and the split repo.
4
+
5
+ /tests/ export-ignore
6
+ /benchmarks/ export-ignore
7
+ /vitest.config.ts export-ignore
8
+ /eslint.config.js export-ignore
9
+ /tsconfig.json export-ignore
10
+ /tsup.config.ts export-ignore
11
+ /stryker.config.json export-ignore
12
+ /.prettierrc export-ignore
13
+ /.gitattributes export-ignore
14
+ /.gitignore export-ignore
15
+ /coverage/ export-ignore
16
+ /reports/ export-ignore
package/.gitkeep ADDED
File without changes
package/CHANGELOG.md ADDED
@@ -0,0 +1,38 @@
1
+ # Changelog
2
+
3
+ All notable changes to the `@safeaccess/inline` JavaScript/TypeScript package are documented in this file.
4
+
5
+ ## [0.1.1](https://github.com/felipesauer/safeaccess-inline/compare/js-v0.1.0...js-v0.1.1) (2026-04-07)
6
+
7
+
8
+ ### Features
9
+
10
+ * **js:** bootstrap release tracking for rebranded package ([5fc07d7](https://github.com/felipesauer/safeaccess-inline/commit/5fc07d7126870d72145bbfc80609370c9d1509c7))
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * **js:** add repository field for npm provenance validation ([b34cdef](https://github.com/felipesauer/safeaccess-inline/commit/b34cdeff01e7e7566921f04b11f33fbd391aa8d2))
16
+
17
+ ## 0.1.0 (2026-04-07)
18
+
19
+ ### Bug Fixes
20
+
21
+ - **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
+
23
+ ## [0.1.0] — 2026-04-06
24
+
25
+ ### Features
26
+
27
+ - Initial release.
28
+ - `Inline` class: static and instance factory methods `fromArray`, `fromObject`, `fromJson`, `fromXml`, `fromYaml`, `fromIni`, `fromEnv`, `fromNdjson`, `fromAny`, `from`, `make`.
29
+ - Builder pattern: `withSecurityGuard`, `withSecurityParser`, `withPathCache`, `withParserIntegration`, `withStrictMode`.
30
+ - Dot-notation read API: `get`, `getOrFail`, `getAt`, `has`, `hasAt`, `getMany`, `all`, `count`, `keys`, `getRaw`.
31
+ - Dot-notation write API: `set`, `setAt`, `remove`, `removeAt`, `merge`, `mergeAll`; honours `readonly()` mode.
32
+ - `TypeFormat` enum with 9 cases: `Array`, `Object`, `Json`, `Xml`, `Yaml`, `Ini`, `Env`, `Ndjson`, `Any`.
33
+ - `SecurityGuard` with configurable depth limit, forbidden-key list (magic methods, prototype-pollution, Node.js-specific vectors), and `sanitize()` helper. All limits are `readonly`.
34
+ - `SecurityParser` with configurable payload-size, key-count, structural-depth, and resolve-depth limits. All limits are `readonly`.
35
+ - Custom-parser extension point via `ParseIntegrationInterface`.
36
+ - Path-result caching via `PathCacheInterface`.
37
+ - 8 typed exception classes extending `AccessorException`: `InvalidFormatException`, `ParserException`, `PathNotFoundException`, `ReadonlyViolationException`, `SecurityException`, `UnsupportedTypeException`, `YamlParseException`.
38
+ - Strict TypeScript types throughout; no `any`; full ESM output.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Felipe Sauer
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,454 @@
1
+ <p align="center">
2
+ <img src="../../public/logo.svg" width="80" alt="safeaccess-inline logo">
3
+ </p>
4
+
5
+ <h1 align="center">Safe Access Inline — TypeScript</h1>
6
+
7
+ <p align="center">
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>
9
+ <a href="../../LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="License: MIT"></a>
10
+ </p>
11
+
12
+ ---
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.
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ npm install @safeaccess/inline
20
+ ```
21
+
22
+ **Requirements:** Node.js 22+
23
+
24
+ ## Quick Start
25
+
26
+ ```typescript
27
+ import { Inline } from '@safeaccess/inline';
28
+
29
+ const accessor = Inline.fromJson('{"user": {"name": "Alice", "age": 30}}');
30
+
31
+ accessor.get('user.name'); // 'Alice'
32
+ accessor.get('user.email', 'N/A'); // 'N/A' (default when missing)
33
+ accessor.has('user.age'); // true
34
+ accessor.getOrFail('user.name'); // 'Alice' (throws if missing)
35
+
36
+ // Immutable writes — original is never modified
37
+ const updated = accessor.set('user.email', 'alice@example.com');
38
+ updated.get('user.email'); // 'alice@example.com'
39
+ accessor.has('user.email'); // false (original unchanged)
40
+ ```
41
+
42
+ ## Dot Notation Syntax
43
+
44
+ The TypeScript package supports dot-separated key access:
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) |
50
+
51
+ ```typescript
52
+ const data = Inline.fromJson('{"users": [{"name": "Alice"}, {"name": "Bob"}]}');
53
+ data.get('users.0.name'); // 'Alice'
54
+ data.get('users.1.name'); // 'Bob'
55
+ ```
56
+
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.
58
+
59
+ ## Supported Formats
60
+
61
+ Each format has a dedicated accessor with automatic parsing and security validation.
62
+
63
+ <details>
64
+ <summary><strong>JSON</strong></summary>
65
+
66
+ ```typescript
67
+ const accessor = Inline.fromJson('{"users": [{"name": "Alice"}, {"name": "Bob"}]}');
68
+ accessor.get('users.0.name'); // 'Alice'
69
+ ```
70
+
71
+ </details>
72
+
73
+ <details>
74
+ <summary><strong>YAML</strong></summary>
75
+
76
+ ```typescript
77
+ const yaml = `database:
78
+ host: localhost
79
+ port: 5432
80
+ credentials:
81
+ user: admin`;
82
+
83
+ const accessor = Inline.fromYaml(yaml);
84
+ accessor.get('database.credentials.user'); // 'admin'
85
+ ```
86
+
87
+ </details>
88
+
89
+ <details>
90
+ <summary><strong>XML</strong></summary>
91
+
92
+ ```typescript
93
+ const accessor = Inline.fromXml('<config><database><host>localhost</host></database></config>');
94
+ accessor.get('database.host'); // 'localhost'
95
+ ```
96
+
97
+ </details>
98
+
99
+ <details>
100
+ <summary><strong>INI</strong></summary>
101
+
102
+ ```typescript
103
+ const accessor = Inline.fromIni('[database]\nhost=localhost\nport=5432');
104
+ accessor.get('database.host'); // 'localhost'
105
+ ```
106
+
107
+ </details>
108
+
109
+ <details>
110
+ <summary><strong>ENV (dotenv)</strong></summary>
111
+
112
+ ```typescript
113
+ const accessor = Inline.fromEnv('APP_NAME=MyApp\nDB_HOST=localhost');
114
+ accessor.get('DB_HOST'); // 'localhost'
115
+ ```
116
+
117
+ </details>
118
+
119
+ <details>
120
+ <summary><strong>NDJSON</strong></summary>
121
+
122
+ ```typescript
123
+ const accessor = Inline.fromNdjson('{"id":1,"name":"Alice"}\n{"id":2,"name":"Bob"}');
124
+ accessor.get('0.name'); // 'Alice'
125
+ accessor.get('1.name'); // 'Bob'
126
+ ```
127
+
128
+ </details>
129
+
130
+ <details>
131
+ <summary><strong>Array / Object</strong></summary>
132
+
133
+ ```typescript
134
+ const accessor = Inline.fromArray({ users: [{ name: 'Alice' }, { name: 'Bob' }] });
135
+ accessor.get('users.0.name'); // 'Alice'
136
+
137
+ const objAccessor = Inline.fromObject({ name: 'Alice', age: 30 });
138
+ objAccessor.get('name'); // 'Alice'
139
+ ```
140
+
141
+ </details>
142
+
143
+ <details>
144
+ <summary><strong>Dynamic (by TypeFormat enum)</strong></summary>
145
+
146
+ ```typescript
147
+ import { Inline, TypeFormat } from '@safeaccess/inline';
148
+
149
+ const accessor = Inline.from(TypeFormat.Json, '{"key": "value"}');
150
+ accessor.get('key'); // 'value'
151
+ ```
152
+
153
+ </details>
154
+
155
+ ## Reading & Writing
156
+
157
+ ```typescript
158
+ const accessor = Inline.fromJson('{"a": {"b": 1, "c": 2}}');
159
+
160
+ // Read
161
+ accessor.get('a.b'); // 1
162
+ accessor.get('a.missing', 'default'); // 'default'
163
+ accessor.getOrFail('a.b'); // 1 (throws PathNotFoundException if missing)
164
+ accessor.has('a.b'); // true
165
+ accessor.all(); // { a: { b: 1, c: 2 } }
166
+ accessor.count(); // 1 (root keys)
167
+ accessor.count('a'); // 2 (keys under 'a')
168
+ accessor.keys(); // ['a']
169
+ accessor.keys('a'); // ['b', 'c']
170
+ accessor.getMany({
171
+ 'a.b': null,
172
+ 'a.x': 'fallback',
173
+ }); // { 'a.b': 1, 'a.x': 'fallback' }
174
+ accessor.getRaw(); // original JSON string
175
+
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 });
181
+
182
+ // Readonly mode — block all writes
183
+ const readonly = accessor.readonly();
184
+ readonly.get('a.b'); // 1 (reads work)
185
+ readonly.set('a.b', 99); // throws ReadonlyViolationException
186
+ ```
187
+
188
+ ## Configure
189
+
190
+ ### Builder Pattern
191
+
192
+ ```typescript
193
+ import { Inline, SecurityGuard, SecurityParser } from '@safeaccess/inline';
194
+
195
+ const accessor = Inline.withSecurityGuard(new SecurityGuard(512, ['secret']))
196
+ .withSecurityParser(new SecurityParser({ maxDepth: 5 }))
197
+ .withStrictMode(true)
198
+ .fromJson(untrustedInput);
199
+ ```
200
+
201
+ ### Builder Methods
202
+
203
+ | Method | Description |
204
+ | ------------------------------------ | ------------------------------------------------ |
205
+ | `withSecurityGuard(guard)` | Custom forbidden-key rules and depth limits |
206
+ | `withSecurityParser(parser)` | Custom payload size and structural limits |
207
+ | `withPathCache(cache)` | Custom path segment cache for repeated lookups |
208
+ | `withParserIntegration(integration)` | Custom format parser for `fromAny()` |
209
+ | `withStrictMode(false)` | Disable security validation (trusted input only) |
210
+
211
+ ## Security
212
+
213
+ All public entry points validate input **by default**. Every key passes through `SecurityGuard` and `SecurityParser`.
214
+
215
+ ### Forbidden Keys
216
+
217
+ | Category | Examples | Reason |
218
+ | ----------------------------- | ------------------------------------------------------------------------------------------------------------ | --------------------------------------------------- |
219
+ | Prototype pollution | `__proto__`, `constructor`, `prototype` | Prevents prototype pollution attacks |
220
+ | Legacy prototype manipulation | `__defineGetter__`, `__defineSetter__`, `__lookupGetter__`, `__lookupSetter__` | Prevents legacy prototype tampering |
221
+ | Property shadow | `hasOwnProperty` | Overriding it can bypass guard checks |
222
+ | Node.js globals | `__dirname`, `__filename` | Prevents path-injection via dynamic property access |
223
+ | Protocol / stream URIs | `javascript:`, `blob:`, `ws://`, `wss://`, `node:`, `file://`, `http://`, `https://`, `ftp://`, `data:`, ... | Prevents URI injection and XSS vectors |
224
+
225
+ Add custom forbidden keys:
226
+
227
+ ```typescript
228
+ const guard = new SecurityGuard(512, ['secret', 'internal_token']);
229
+ const accessor = Inline.withSecurityGuard(guard).fromJson(data);
230
+ ```
231
+
232
+ ### Structural Limits
233
+
234
+ | Limit | Default | Description |
235
+ | ------------------------ | ------- | ------------------------------------- |
236
+ | `maxPayloadBytes` | 10 MB | Maximum raw string input size |
237
+ | `maxKeys` | 10,000 | Maximum total key count |
238
+ | `maxDepth` | 512 | Maximum structural nesting depth |
239
+ | `maxResolveDepth` | 100 | Maximum recursion for path resolution |
240
+ | `maxCountRecursiveDepth` | 100 | Maximum recursion when counting keys |
241
+
242
+ ### Format-Specific Protections
243
+
244
+ | Format | Protection |
245
+ | ------ | ------------------------------------------------ |
246
+ | XML | Rejects `<!DOCTYPE` — prevents XXE attacks |
247
+ | YAML | Blocks unsafe tags, anchors, aliases, merge keys |
248
+ | All | Forbidden key validation on every parsed key |
249
+
250
+ > Disable for trusted input: `Inline.withStrictMode(false).fromJson(trustedInput)`
251
+
252
+ For vulnerability reports, see [SECURITY.md](../../SECURITY.md).
253
+
254
+ ## Error Handling
255
+
256
+ All exceptions extend `AccessorException`:
257
+
258
+ ```typescript
259
+ import {
260
+ Inline,
261
+ AccessorException,
262
+ InvalidFormatException,
263
+ SecurityException,
264
+ PathNotFoundException,
265
+ ReadonlyViolationException,
266
+ } from '@safeaccess/inline';
267
+
268
+ try {
269
+ const accessor = Inline.fromJson(untrustedInput);
270
+ const value = accessor.getOrFail('config.key');
271
+ } catch (e) {
272
+ if (e instanceof InvalidFormatException) {
273
+ // Malformed JSON, XML, INI, or NDJSON
274
+ }
275
+ if (e instanceof SecurityException) {
276
+ // Forbidden key, payload too large, depth/key-count exceeded
277
+ }
278
+ if (e instanceof PathNotFoundException) {
279
+ // Path does not exist
280
+ }
281
+ if (e instanceof ReadonlyViolationException) {
282
+ // Write on readonly accessor
283
+ }
284
+ if (e instanceof AccessorException) {
285
+ // Catch-all for any library error
286
+ }
287
+ }
288
+ ```
289
+
290
+ ### Exception Hierarchy
291
+
292
+ | Exception | Extends | When |
293
+ | ---------------------------- | ------------------------ | ----------------------------------------- |
294
+ | `AccessorException` | `Error` | Root — catch-all |
295
+ | `SecurityException` | `AccessorException` | Forbidden key, payload, structural limits |
296
+ | `InvalidFormatException` | `AccessorException` | Malformed JSON, XML, INI, NDJSON |
297
+ | `YamlParseException` | `InvalidFormatException` | Unsafe or malformed YAML |
298
+ | `PathNotFoundException` | `AccessorException` | `getOrFail()` on missing path |
299
+ | `ReadonlyViolationException` | `AccessorException` | Write on readonly accessor |
300
+ | `UnsupportedTypeException` | `AccessorException` | Unknown accessor class in `make()` |
301
+ | `ParserException` | `AccessorException` | Internal parser errors |
302
+
303
+ ## Advanced Usage
304
+
305
+ ### Strict Mode
306
+
307
+ ```typescript
308
+ // Disable all security validation for trusted input
309
+ const accessor = Inline.withStrictMode(false).fromJson(trustedPayload);
310
+ ```
311
+
312
+ > **Warning:** Disabling strict mode skips **all** validation. Only use with application-controlled input.
313
+
314
+ ### Path Cache
315
+
316
+ ```typescript
317
+ // Implement PathCacheInterface for repeated lookups
318
+ const cache: PathCacheInterface = {
319
+ get: (path) => cacheMap.get(path) ?? null,
320
+ set: (path, segments) => {
321
+ cacheMap.set(path, segments);
322
+ },
323
+ has: (path) => cacheMap.has(path),
324
+ clear: () => {
325
+ cacheMap.clear();
326
+ return cache;
327
+ },
328
+ };
329
+
330
+ const accessor = Inline.withPathCache(cache).fromJson(data);
331
+ accessor.get('deeply.nested.path'); // parses path
332
+ accessor.get('deeply.nested.path'); // cache hit
333
+ ```
334
+
335
+ ### Custom Format Integration
336
+
337
+ ```typescript
338
+ // Implement ParseIntegrationInterface for custom formats
339
+ const csvIntegration: ParseIntegrationInterface = {
340
+ assertFormat: (raw: unknown) => typeof raw === 'string' && raw.includes(','),
341
+ parse: (raw: unknown) => {
342
+ // Parse CSV to object
343
+ return parsed;
344
+ },
345
+ };
346
+
347
+ const accessor = Inline.withParserIntegration(csvIntegration).fromAny(csvString);
348
+ ```
349
+
350
+ ## API Reference
351
+
352
+ ### Facade: `Inline`
353
+
354
+ #### Static Factory Methods
355
+
356
+ | Method | Input | Returns |
357
+ | ----------------------------- | ---------------------------------------- | -------------------- |
358
+ | `fromArray(data)` | `Record<string, unknown>` or `unknown[]` | `ArrayAccessor` |
359
+ | `fromObject(data)` | `object` | `ObjectAccessor` |
360
+ | `fromJson(data)` | JSON `string` | `JsonAccessor` |
361
+ | `fromXml(data)` | XML `string` | `XmlAccessor` |
362
+ | `fromYaml(data)` | YAML `string` | `YamlAccessor` |
363
+ | `fromIni(data)` | INI `string` | `IniAccessor` |
364
+ | `fromEnv(data)` | dotenv `string` | `EnvAccessor` |
365
+ | `fromNdjson(data)` | NDJSON `string` | `NdjsonAccessor` |
366
+ | `fromAny(data, integration?)` | `unknown` | `AnyAccessor` |
367
+ | `from(typeFormat, data)` | `TypeFormat` enum | `AccessorsInterface` |
368
+ | `make(AccessorClass, data)` | Accessor constructor | `AbstractAccessor` |
369
+
370
+ #### Accessor Read Methods
371
+
372
+ | Method | Returns |
373
+ | --------------------------- | --------------------------------------- |
374
+ | `get(path, default?)` | Value at path, or default |
375
+ | `getOrFail(path)` | Value or throws `PathNotFoundException` |
376
+ | `getAt(segments, default?)` | Value at key segments |
377
+ | `has(path)` | `boolean` |
378
+ | `hasAt(segments)` | `boolean` |
379
+ | `getMany(paths)` | `Record<string, unknown>` |
380
+ | `all()` | `Record<string, unknown>` |
381
+ | `count(path?)` | `number` |
382
+ | `keys(path?)` | `string[]` |
383
+ | `getRaw()` | `unknown` |
384
+
385
+ #### Accessor Write Methods (immutable)
386
+
387
+ | Method | Description |
388
+ | ------------------------ | ---------------------- |
389
+ | `set(path, value)` | Set at path |
390
+ | `setAt(segments, value)` | Set at key segments |
391
+ | `remove(path)` | Remove at path |
392
+ | `removeAt(segments)` | Remove at key segments |
393
+ | `merge(path, value)` | Deep-merge at path |
394
+ | `mergeAll(value)` | Deep-merge at root |
395
+
396
+ #### Modifier Methods
397
+
398
+ | Method | Description |
399
+ | ----------------- | -------------------------- |
400
+ | `readonly(flag?)` | Block all writes |
401
+ | `strict(flag?)` | Toggle security validation |
402
+
403
+ ## Exports
404
+
405
+ The package uses **named exports only** (no default exports). All public types are available from the main entry point:
406
+
407
+ ```typescript
408
+ import {
409
+ Inline,
410
+ TypeFormat,
411
+ SecurityGuard,
412
+ SecurityParser,
413
+ // Accessors
414
+ AbstractAccessor,
415
+ ArrayAccessor,
416
+ ObjectAccessor,
417
+ JsonAccessor,
418
+ XmlAccessor,
419
+ YamlAccessor,
420
+ IniAccessor,
421
+ EnvAccessor,
422
+ NdjsonAccessor,
423
+ AnyAccessor,
424
+ // Exceptions
425
+ AccessorException,
426
+ SecurityException,
427
+ InvalidFormatException,
428
+ YamlParseException,
429
+ ParserException,
430
+ PathNotFoundException,
431
+ ReadonlyViolationException,
432
+ UnsupportedTypeException,
433
+ } from '@safeaccess/inline';
434
+
435
+ // Contracts (type-only imports)
436
+ import type {
437
+ AccessorsInterface,
438
+ ReadableAccessorsInterface,
439
+ WritableAccessorsInterface,
440
+ FactoryAccessorsInterface,
441
+ SecurityGuardInterface,
442
+ SecurityParserInterface,
443
+ PathCacheInterface,
444
+ ParseIntegrationInterface,
445
+ } from '@safeaccess/inline';
446
+ ```
447
+
448
+ ## Contributing
449
+
450
+ See [CONTRIBUTING.md](../../CONTRIBUTING.md) for development setup, commit conventions, and pull request guidelines.
451
+
452
+ ## License
453
+
454
+ [MIT](../../LICENSE) © Felipe Sauer
@@ -0,0 +1,26 @@
1
+ import { bench, describe } from 'vitest';
2
+ import { Inline } from '../src/inline.js';
3
+
4
+ const accessor = Inline.fromArray({
5
+ user: { profile: { name: 'Alice', age: 30 } },
6
+ config: { debug: false, version: '1.0.0' },
7
+ items: [1, 2, 3, 4, 5],
8
+ });
9
+
10
+ describe('ArrayAccessor.get', () => {
11
+ bench('shallow key', () => {
12
+ accessor.get('config.debug');
13
+ });
14
+
15
+ bench('deep key (3 levels)', () => {
16
+ accessor.get('user.profile.name');
17
+ });
18
+
19
+ bench('missing key with default', () => {
20
+ accessor.get('user.profile.missing', null);
21
+ });
22
+
23
+ bench('repeated path (cache)', () => {
24
+ accessor.get('user.profile.name');
25
+ });
26
+ });
@@ -0,0 +1,41 @@
1
+ import { bench, describe } from 'vitest';
2
+ import { Inline } from '../src/inline.js';
3
+
4
+ const arrayPayload = {
5
+ user: { profile: { name: 'Alice', age: 30 } },
6
+ config: { debug: false, version: '1.0.0' },
7
+ };
8
+
9
+ const jsonPayload = JSON.stringify(arrayPayload);
10
+
11
+ const yamlPayload = `user:
12
+ profile:
13
+ name: Alice
14
+ age: 30
15
+ config:
16
+ debug: false
17
+ version: '1.0.0'
18
+ `;
19
+
20
+ const iniPayload = `[config]
21
+ debug=false
22
+ version=1.0.0
23
+ `;
24
+
25
+ describe('Inline.from* (parse)', () => {
26
+ bench('fromArray', () => {
27
+ Inline.fromArray(arrayPayload);
28
+ });
29
+
30
+ bench('fromJson', () => {
31
+ Inline.fromJson(jsonPayload);
32
+ });
33
+
34
+ bench('fromYaml', () => {
35
+ Inline.fromYaml(yamlPayload);
36
+ });
37
+
38
+ bench('fromIni', () => {
39
+ Inline.fromIni(iniPayload);
40
+ });
41
+ });