@safeaccess/inline 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitattributes +1 -1
- package/CHANGELOG.md +10 -5
- package/LICENSE +1 -1
- package/README.md +56 -14
- package/dist/accessors/abstract-accessor.d.ts +22 -10
- package/dist/accessors/abstract-accessor.js +21 -8
- package/dist/accessors/abstract-integration-accessor.d.ts +22 -0
- package/dist/accessors/abstract-integration-accessor.js +23 -0
- package/dist/accessors/formats/any-accessor.d.ts +10 -8
- package/dist/accessors/formats/any-accessor.js +9 -8
- package/dist/accessors/formats/array-accessor.d.ts +2 -0
- package/dist/accessors/formats/array-accessor.js +2 -0
- package/dist/accessors/formats/env-accessor.d.ts +2 -0
- package/dist/accessors/formats/env-accessor.js +2 -0
- package/dist/accessors/formats/ini-accessor.d.ts +2 -0
- package/dist/accessors/formats/ini-accessor.js +2 -0
- package/dist/accessors/formats/json-accessor.d.ts +2 -0
- package/dist/accessors/formats/json-accessor.js +2 -0
- package/dist/accessors/formats/ndjson-accessor.d.ts +2 -0
- package/dist/accessors/formats/ndjson-accessor.js +2 -0
- package/dist/accessors/formats/object-accessor.d.ts +2 -0
- package/dist/accessors/formats/object-accessor.js +2 -0
- package/dist/accessors/formats/xml-accessor.d.ts +2 -0
- package/dist/accessors/formats/xml-accessor.js +2 -0
- package/dist/accessors/formats/yaml-accessor.d.ts +3 -1
- package/dist/accessors/formats/yaml-accessor.js +4 -2
- package/dist/cache/simple-path-cache.d.ts +51 -0
- package/dist/cache/simple-path-cache.js +72 -0
- package/dist/contracts/accessors-interface.d.ts +2 -0
- package/dist/contracts/factory-accessors-interface.d.ts +2 -0
- package/dist/contracts/filter-evaluator-interface.d.ts +28 -0
- package/dist/contracts/filter-evaluator-interface.js +1 -0
- package/dist/contracts/parse-integration-interface.d.ts +2 -0
- package/dist/contracts/parser-interface.d.ts +92 -0
- package/dist/contracts/parser-interface.js +1 -0
- package/dist/contracts/path-cache-interface.d.ts +7 -6
- package/dist/contracts/readable-accessors-interface.d.ts +11 -6
- package/dist/contracts/security-guard-interface.d.ts +2 -0
- package/dist/contracts/security-parser-interface.d.ts +2 -0
- package/dist/contracts/validatable-parser-interface.d.ts +59 -0
- package/dist/contracts/validatable-parser-interface.js +1 -0
- package/dist/contracts/writable-accessors-interface.d.ts +5 -0
- package/dist/core/accessor-factory.d.ts +124 -0
- package/dist/core/accessor-factory.js +157 -0
- package/dist/core/dot-notation-parser.d.ts +34 -5
- package/dist/core/dot-notation-parser.js +51 -10
- package/dist/core/inline-builder-accessor.d.ts +82 -0
- package/dist/core/inline-builder-accessor.js +107 -0
- package/dist/exceptions/accessor-exception.d.ts +9 -0
- package/dist/exceptions/accessor-exception.js +9 -0
- package/dist/exceptions/invalid-format-exception.d.ts +5 -0
- package/dist/exceptions/invalid-format-exception.js +5 -0
- package/dist/exceptions/parser-exception.d.ts +4 -0
- package/dist/exceptions/parser-exception.js +4 -0
- package/dist/exceptions/path-not-found-exception.d.ts +4 -0
- package/dist/exceptions/path-not-found-exception.js +4 -0
- package/dist/exceptions/readonly-violation-exception.d.ts +4 -0
- package/dist/exceptions/readonly-violation-exception.js +4 -0
- package/dist/exceptions/security-exception.d.ts +6 -0
- package/dist/exceptions/security-exception.js +6 -0
- package/dist/exceptions/unsupported-type-exception.d.ts +4 -0
- package/dist/exceptions/unsupported-type-exception.js +4 -0
- package/dist/exceptions/yaml-parse-exception.d.ts +4 -0
- package/dist/exceptions/yaml-parse-exception.js +4 -0
- package/dist/index.js +2 -1
- package/dist/inline.d.ts +22 -56
- package/dist/inline.js +39 -111
- package/dist/parser/xml-parser.js +23 -10
- package/dist/parser/yaml-parser.d.ts +54 -7
- package/dist/parser/yaml-parser.js +268 -51
- package/dist/path-query/segment-filter-parser.d.ts +142 -0
- package/dist/path-query/segment-filter-parser.js +384 -0
- package/dist/path-query/segment-parser.d.ts +98 -0
- package/dist/path-query/segment-parser.js +283 -0
- package/dist/path-query/segment-path-resolver.d.ts +149 -0
- package/dist/path-query/segment-path-resolver.js +351 -0
- package/dist/path-query/segment-type.d.ts +85 -0
- package/dist/path-query/segment-type.js +35 -0
- package/dist/security/forbidden-keys.d.ts +2 -2
- package/dist/security/forbidden-keys.js +5 -5
- package/dist/security/security-guard.d.ts +3 -1
- package/dist/security/security-guard.js +5 -2
- package/dist/security/security-parser.d.ts +10 -1
- package/dist/security/security-parser.js +10 -1
- package/dist/type-format.d.ts +2 -0
- package/dist/type-format.js +2 -0
- package/package.json +11 -3
- package/src/accessors/abstract-accessor.ts +23 -19
- package/src/accessors/abstract-integration-accessor.ts +27 -0
- package/src/accessors/formats/any-accessor.ts +11 -11
- package/src/accessors/formats/array-accessor.ts +2 -0
- package/src/accessors/formats/env-accessor.ts +2 -0
- package/src/accessors/formats/ini-accessor.ts +2 -0
- package/src/accessors/formats/json-accessor.ts +2 -0
- package/src/accessors/formats/ndjson-accessor.ts +2 -0
- package/src/accessors/formats/object-accessor.ts +2 -0
- package/src/accessors/formats/xml-accessor.ts +2 -0
- package/src/accessors/formats/yaml-accessor.ts +4 -2
- package/src/cache/simple-path-cache.ts +77 -0
- package/src/contracts/accessors-interface.ts +2 -0
- package/src/contracts/factory-accessors-interface.ts +2 -0
- package/src/contracts/filter-evaluator-interface.ts +30 -0
- package/src/contracts/parse-integration-interface.ts +2 -0
- package/src/contracts/parser-interface.ts +114 -0
- package/src/contracts/path-cache-interface.ts +8 -6
- package/src/contracts/readable-accessors-interface.ts +11 -6
- package/src/contracts/security-guard-interface.ts +2 -0
- package/src/contracts/security-parser-interface.ts +2 -0
- package/src/contracts/validatable-parser-interface.ts +64 -0
- package/src/contracts/writable-accessors-interface.ts +5 -0
- package/src/core/accessor-factory.ts +173 -0
- package/src/core/dot-notation-parser.ts +74 -11
- package/src/core/inline-builder-accessor.ts +163 -0
- package/src/exceptions/accessor-exception.ts +9 -0
- package/src/exceptions/invalid-format-exception.ts +5 -0
- package/src/exceptions/parser-exception.ts +4 -0
- package/src/exceptions/path-not-found-exception.ts +4 -0
- package/src/exceptions/readonly-violation-exception.ts +4 -0
- package/src/exceptions/security-exception.ts +6 -0
- package/src/exceptions/unsupported-type-exception.ts +4 -0
- package/src/exceptions/yaml-parse-exception.ts +4 -0
- package/src/index.ts +3 -1
- package/src/inline.ts +42 -120
- package/src/parser/xml-parser.ts +31 -10
- package/src/parser/yaml-parser.ts +310 -45
- package/src/path-query/segment-filter-parser.ts +444 -0
- package/src/path-query/segment-parser.ts +321 -0
- package/src/path-query/segment-path-resolver.ts +521 -0
- package/src/path-query/segment-type.ts +82 -0
- package/src/security/forbidden-keys.ts +5 -5
- package/src/security/security-guard.ts +7 -2
- package/src/security/security-parser.ts +18 -3
- package/src/type-format.ts +2 -0
- package/stryker.config.json +8 -10
- package/tests/accessors/abstract-accessor.test.ts +217 -0
- package/tests/accessors/abstract-integration-accessor.test.ts +37 -0
- package/tests/accessors/formats/any-accessor.test.ts +57 -0
- package/tests/accessors/formats/array-accessor.test.ts +42 -0
- package/tests/accessors/formats/env-accessor.test.ts +103 -0
- package/tests/accessors/formats/ini-accessor.test.ts +186 -0
- package/tests/accessors/{json-accessor.test.ts → formats/json-accessor.test.ts} +6 -6
- package/tests/accessors/formats/ndjson-accessor.test.ts +49 -0
- package/tests/accessors/formats/object-accessor.test.ts +172 -0
- package/tests/accessors/formats/xml-accessor.test.ts +162 -0
- package/tests/accessors/formats/yaml-accessor.test.ts +36 -0
- package/tests/cache/simple-path-cache.test.ts +168 -0
- package/tests/core/accessor-factory.test.ts +157 -0
- package/tests/core/dot-notation-parser-edge-cases.test.ts +415 -0
- package/tests/core/dot-notation-parser.test.ts +0 -288
- package/tests/core/inline-builder-accessor.test.ts +114 -0
- package/tests/exceptions/accessor-exception.test.ts +28 -0
- package/tests/exceptions/invalid-format-exception.test.ts +31 -0
- package/tests/exceptions/path-not-found-exception.test.ts +33 -0
- package/tests/exceptions/readonly-violation-exception.test.ts +35 -0
- package/tests/exceptions/security-exception.test.ts +33 -0
- package/tests/exceptions/unsupported-type-exception.test.ts +33 -0
- package/tests/exceptions/yaml-parse-exception.test.ts +38 -0
- package/tests/mocks/fake-path-cache.ts +4 -3
- package/tests/parity-from.test.ts +118 -0
- package/tests/parity.test.ts +227 -10
- package/tests/parser/xml-parser-mutations.test.ts +579 -0
- package/tests/parser/xml-parser-scanner.test.ts +332 -0
- package/tests/parser/xml-parser.test.ts +10 -334
- package/tests/parser/yaml-parser-mutations.test.ts +750 -0
- package/tests/parser/yaml-parser.test.ts +844 -18
- package/tests/path-query/segment-filter-parser-mutations.test.ts +735 -0
- package/tests/path-query/segment-filter-parser.test.ts +1091 -0
- package/tests/path-query/segment-parser-mutations.test.ts +539 -0
- package/tests/path-query/segment-parser.test.ts +606 -0
- package/tests/path-query/segment-path-resolver-mutations.test.ts +626 -0
- package/tests/path-query/segment-path-resolver.test.ts +1009 -0
- package/tests/security/security-guard-advanced.test.ts +413 -0
- package/tests/security/security-guard-forbidden-keys.test.ts +87 -0
- package/tests/security/security-guard.test.ts +3 -484
- package/tests/security/security-parser.test.ts +18 -14
- package/vitest.config.ts +3 -3
- package/benchmarks/get.bench.ts +0 -26
- package/benchmarks/parse.bench.ts +0 -41
- package/tests/accessors/accessors.test.ts +0 -1017
package/.gitattributes
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Distribution archive ignore
|
|
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,22 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to the `@safeaccess/inline` JavaScript/TypeScript package are documented in this file.
|
|
4
4
|
|
|
5
|
-
## [0.1.
|
|
5
|
+
## [0.1.2](https://github.com/felipesauer/safeaccess-inline/compare/js-v0.1.1...js-v0.1.2) (2026-04-08)
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
###
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* **js:** fix logo image URL in README ([16f4fc5](https://github.com/felipesauer/safeaccess-inline/commit/16f4fc5d69fa7ce86e3017bbbfc9f393925a5c37))
|
|
9
11
|
|
|
10
|
-
|
|
12
|
+
## [0.1.1](https://github.com/felipesauer/safeaccess-inline/compare/js-v0.1.0...js-v0.1.1) (2026-04-07)
|
|
13
|
+
|
|
14
|
+
### Features
|
|
11
15
|
|
|
16
|
+
- **js:** bootstrap release tracking for rebranded package ([5fc07d7](https://github.com/felipesauer/safeaccess-inline/commit/5fc07d7126870d72145bbfc80609370c9d1509c7))
|
|
12
17
|
|
|
13
18
|
### Bug Fixes
|
|
14
19
|
|
|
15
|
-
|
|
20
|
+
- **js:** add repository field for npm provenance validation ([b34cdef](https://github.com/felipesauer/safeaccess-inline/commit/b34cdeff01e7e7566921f04b11f33fbd391aa8d2))
|
|
16
21
|
|
|
17
22
|
## 0.1.0 (2026-04-07)
|
|
18
23
|
|
|
@@ -20,7 +25,7 @@ All notable changes to the `@safeaccess/inline` JavaScript/TypeScript package ar
|
|
|
20
25
|
|
|
21
26
|
- **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
27
|
|
|
23
|
-
## [0.1.0]
|
|
28
|
+
## [0.1.0] - 2026-04-06
|
|
24
29
|
|
|
25
30
|
### Features
|
|
26
31
|
|
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<img src="
|
|
2
|
+
<img src="https://github.com/user-attachments/assets/28202f8b-8ef1-4b94-b6d1-ec16f16c9cf9" width="80" alt="safeaccess-inline logo">
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
|
-
<h1 align="center">Safe Access Inline
|
|
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
|
|
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
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
|
|
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
|
-
|
|
44
|
+
### Basic Syntax
|
|
45
45
|
|
|
46
|
-
| Syntax
|
|
47
|
-
|
|
|
48
|
-
| `key.key`
|
|
49
|
-
| `key.0.key`
|
|
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
|
-
|
|
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
|
|
|
@@ -173,13 +214,13 @@ accessor.getMany({
|
|
|
173
214
|
}); // { 'a.b': 1, 'a.x': 'fallback' }
|
|
174
215
|
accessor.getRaw(); // original JSON string
|
|
175
216
|
|
|
176
|
-
// Write (immutable
|
|
217
|
+
// Write (immutable - every write returns a new instance)
|
|
177
218
|
const updated = accessor.set('a.d', 3);
|
|
178
219
|
const cleaned = updated.remove('a.c');
|
|
179
220
|
const merged = cleaned.merge('a', { e: 4 });
|
|
180
221
|
const full = merged.mergeAll({ f: 5 });
|
|
181
222
|
|
|
182
|
-
// Readonly mode
|
|
223
|
+
// Readonly mode - block all writes
|
|
183
224
|
const readonly = accessor.readonly();
|
|
184
225
|
readonly.get('a.b'); // 1 (reads work)
|
|
185
226
|
readonly.set('a.b', 99); // throws ReadonlyViolationException
|
|
@@ -243,7 +284,7 @@ const accessor = Inline.withSecurityGuard(guard).fromJson(data);
|
|
|
243
284
|
|
|
244
285
|
| Format | Protection |
|
|
245
286
|
| ------ | ------------------------------------------------ |
|
|
246
|
-
| XML | Rejects `<!DOCTYPE`
|
|
287
|
+
| XML | Rejects `<!DOCTYPE` - prevents XXE attacks |
|
|
247
288
|
| YAML | Blocks unsafe tags, anchors, aliases, merge keys |
|
|
248
289
|
| All | Forbidden key validation on every parsed key |
|
|
249
290
|
|
|
@@ -291,7 +332,7 @@ try {
|
|
|
291
332
|
|
|
292
333
|
| Exception | Extends | When |
|
|
293
334
|
| ---------------------------- | ------------------------ | ----------------------------------------- |
|
|
294
|
-
| `AccessorException` | `Error` | Root
|
|
335
|
+
| `AccessorException` | `Error` | Root - catch-all |
|
|
295
336
|
| `SecurityException` | `AccessorException` | Forbidden key, payload, structural limits |
|
|
296
337
|
| `InvalidFormatException` | `AccessorException` | Malformed JSON, XML, INI, NDJSON |
|
|
297
338
|
| `YamlParseException` | `InvalidFormatException` | Unsafe or malformed YAML |
|
|
@@ -315,6 +356,7 @@ const accessor = Inline.withStrictMode(false).fromJson(trustedPayload);
|
|
|
315
356
|
|
|
316
357
|
```typescript
|
|
317
358
|
// Implement PathCacheInterface for repeated lookups
|
|
359
|
+
const cacheMap = new Map();
|
|
318
360
|
const cache: PathCacheInterface = {
|
|
319
361
|
get: (path) => cacheMap.get(path) ?? null,
|
|
320
362
|
set: (path, segments) => {
|
|
@@ -1,13 +1,25 @@
|
|
|
1
1
|
import type { AccessorsInterface } from '../contracts/accessors-interface.js';
|
|
2
|
-
import {
|
|
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:
|
|
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:
|
|
22
|
+
constructor(parser: ValidatableParserInterface);
|
|
11
23
|
/**
|
|
12
24
|
* Convert raw input data into a normalized plain object.
|
|
13
25
|
*
|
|
@@ -59,7 +71,7 @@ export declare abstract class AbstractAccessor implements AccessorsInterface {
|
|
|
59
71
|
* Only use with fully trusted, application-controlled input.
|
|
60
72
|
*
|
|
61
73
|
* @example
|
|
62
|
-
* // Trust the input
|
|
74
|
+
* // Trust the input - skip all security checks
|
|
63
75
|
* const accessor = new JsonAccessor(parser).strict(false).from(trustedPayload);
|
|
64
76
|
*/
|
|
65
77
|
strict(strict?: boolean): this;
|
|
@@ -159,19 +171,19 @@ export declare abstract class AbstractAccessor implements AccessorsInterface {
|
|
|
159
171
|
*/
|
|
160
172
|
all(): Record<string, unknown>;
|
|
161
173
|
/**
|
|
162
|
-
* Count elements at a path, or the root if undefined.
|
|
174
|
+
* Count elements at a path, or the root if null/undefined.
|
|
163
175
|
*
|
|
164
|
-
* @param path - Dot-notation path, or undefined for root.
|
|
176
|
+
* @param path - Dot-notation path, or null/undefined for root.
|
|
165
177
|
* @returns Number of elements.
|
|
166
178
|
*/
|
|
167
|
-
count(path?: string): number;
|
|
179
|
+
count(path?: string | null): number;
|
|
168
180
|
/**
|
|
169
|
-
* Retrieve array keys at a path, or root keys if undefined.
|
|
181
|
+
* Retrieve array keys at a path, or root keys if null/undefined.
|
|
170
182
|
*
|
|
171
|
-
* @param path - Dot-notation path, or undefined for root.
|
|
183
|
+
* @param path - Dot-notation path, or null/undefined for root.
|
|
172
184
|
* @returns List of keys.
|
|
173
185
|
*/
|
|
174
|
-
keys(path?: string): string[];
|
|
186
|
+
keys(path?: string | null): string[];
|
|
175
187
|
/**
|
|
176
188
|
* Deep-merge an object into the value at a dot-notation path.
|
|
177
189
|
*
|
|
@@ -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
|
|
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
|
-
|
|
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
|
|
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
|
|
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 {
|
|
1
|
+
import { AbstractIntegrationAccessor } from '../abstract-integration-accessor.js';
|
|
2
2
|
import type { ParseIntegrationInterface } from '../../contracts/parse-integration-interface.js';
|
|
3
|
-
import type {
|
|
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
|
|
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:
|
|
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 {
|
|
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
|
|
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);
|
|
@@ -5,6 +5,8 @@ import { AbstractAccessor } from '../abstract-accessor.js';
|
|
|
5
5
|
* Handles nested objects and arrays of objects without JSON roundtrip.
|
|
6
6
|
* Respects the configured max depth to prevent DoS from deeply nested structures.
|
|
7
7
|
*
|
|
8
|
+
* @api
|
|
9
|
+
*
|
|
8
10
|
* @example
|
|
9
11
|
* const obj = { user: { name: 'Alice' } };
|
|
10
12
|
* const accessor = new ObjectAccessor(parser).from(obj);
|
|
@@ -7,6 +7,8 @@ import { SecurityException } from '../../exceptions/security-exception.js';
|
|
|
7
7
|
* Handles nested objects and arrays of objects without JSON roundtrip.
|
|
8
8
|
* Respects the configured max depth to prevent DoS from deeply nested structures.
|
|
9
9
|
*
|
|
10
|
+
* @api
|
|
11
|
+
*
|
|
10
12
|
* @example
|
|
11
13
|
* const obj = { user: { name: 'Alice' } };
|
|
12
14
|
* const accessor = new ObjectAccessor(parser).from(obj);
|
|
@@ -5,6 +5,8 @@ import { AbstractAccessor } from '../abstract-accessor.js';
|
|
|
5
5
|
* Parses XML using the DOM parser available in the current environment.
|
|
6
6
|
* Blocks DOCTYPE declarations to prevent XXE attacks.
|
|
7
7
|
*
|
|
8
|
+
* @api
|
|
9
|
+
*
|
|
8
10
|
* @example
|
|
9
11
|
* const accessor = new XmlAccessor(parser).from('<root><key>value</key></root>');
|
|
10
12
|
* accessor.get('key'); // 'value'
|
|
@@ -8,6 +8,8 @@ import { XmlParser } from '../../parser/xml-parser.js';
|
|
|
8
8
|
* Parses XML using the DOM parser available in the current environment.
|
|
9
9
|
* Blocks DOCTYPE declarations to prevent XXE attacks.
|
|
10
10
|
*
|
|
11
|
+
* @api
|
|
12
|
+
*
|
|
11
13
|
* @example
|
|
12
14
|
* const accessor = new XmlAccessor(parser).from('<root><key>value</key></root>');
|
|
13
15
|
* accessor.get('key'); // 'value'
|
|
@@ -6,6 +6,8 @@ import { AbstractAccessor } from '../abstract-accessor.js';
|
|
|
6
6
|
* depending on external YAML libraries. Tags, anchors, aliases, and
|
|
7
7
|
* merge keys are blocked as unsafe constructs.
|
|
8
8
|
*
|
|
9
|
+
* @api
|
|
10
|
+
*
|
|
9
11
|
* @example
|
|
10
12
|
* const accessor = new YamlAccessor(parser).from('key: value\nnested:\n a: 1');
|
|
11
13
|
* accessor.get('nested.a'); // 1
|
|
@@ -21,7 +23,7 @@ export declare class YamlAccessor extends AbstractAccessor {
|
|
|
21
23
|
* @throws {SecurityException} When payload size exceeds limit.
|
|
22
24
|
*
|
|
23
25
|
* @example
|
|
24
|
-
* accessor.from('name: Alice\nage: 30');
|
|
26
|
+
* accessor.from('name: Alice\nage: 30'); // { name: 'Alice', age: 30 }
|
|
25
27
|
*/
|
|
26
28
|
from(data: unknown): this;
|
|
27
29
|
/** {@inheritDoc} */
|