@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.
- package/.gitattributes +1 -1
- package/CHANGELOG.md +23 -5
- package/LICENSE +1 -1
- package/README.md +79 -21
- package/dist/accessors/abstract-accessor.d.ts +24 -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 +26 -56
- package/dist/inline.js +43 -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 +4 -1
- package/dist/security/security-guard.js +7 -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 +25 -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 +46 -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 +10 -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 +379 -0
- package/tests/parser/xml-parser.test.ts +17 -330
- 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 +8 -479
- 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/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@safeaccess/inline",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"private": false,
|
|
5
|
-
"description": "Safe nested data access with dot notation
|
|
5
|
+
"description": "Safe nested data access with dot notation - JavaScript/TypeScript",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"main": "./dist/index.js",
|
|
@@ -13,6 +13,15 @@
|
|
|
13
13
|
"types": "./dist/index.d.ts"
|
|
14
14
|
}
|
|
15
15
|
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"safeaccess",
|
|
18
|
+
"dot-notation",
|
|
19
|
+
"nested-data",
|
|
20
|
+
"data-access",
|
|
21
|
+
"array-access",
|
|
22
|
+
"object-access",
|
|
23
|
+
"null-safe"
|
|
24
|
+
],
|
|
16
25
|
"scripts": {
|
|
17
26
|
"build": "tsc",
|
|
18
27
|
"test": "vitest run",
|
|
@@ -20,7 +29,6 @@
|
|
|
20
29
|
"test:typecheck": "tsc --noEmit",
|
|
21
30
|
"test:parity": "vitest run --reporter=verbose",
|
|
22
31
|
"lint": "npx eslint src/ tests/",
|
|
23
|
-
"bench": "npx vitest bench benchmarks/",
|
|
24
32
|
"test:coverage": "npx vitest run --coverage",
|
|
25
33
|
"test:mutation": "npx stryker run"
|
|
26
34
|
},
|
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
import type { AccessorsInterface } from '../contracts/accessors-interface.js';
|
|
2
|
-
import {
|
|
2
|
+
import type { ValidatableParserInterface } from '../contracts/validatable-parser-interface.js';
|
|
3
3
|
import { PathNotFoundException } from '../exceptions/path-not-found-exception.js';
|
|
4
4
|
import { ReadonlyViolationException } from '../exceptions/readonly-violation-exception.js';
|
|
5
5
|
|
|
6
|
+
/** @internal Mutable state grouped to allow O(1) shallow clone in mutations. */
|
|
7
|
+
interface AccessorState {
|
|
8
|
+
data: Record<string, unknown>;
|
|
9
|
+
isReadonly: boolean;
|
|
10
|
+
isStrict: boolean;
|
|
11
|
+
rawInput: unknown;
|
|
12
|
+
}
|
|
13
|
+
|
|
6
14
|
/**
|
|
7
15
|
* Base accessor providing read, write, and lifecycle operations.
|
|
8
16
|
*
|
|
@@ -12,14 +20,9 @@ import { ReadonlyViolationException } from '../exceptions/readonly-violation-exc
|
|
|
12
20
|
*
|
|
13
21
|
* Subclasses must implement `parse()` to convert raw input into
|
|
14
22
|
* a normalized plain object.
|
|
23
|
+
*
|
|
24
|
+
* @api
|
|
15
25
|
*/
|
|
16
|
-
interface AccessorState {
|
|
17
|
-
data: Record<string, unknown>;
|
|
18
|
-
isReadonly: boolean;
|
|
19
|
-
isStrict: boolean;
|
|
20
|
-
rawInput: unknown;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
26
|
export abstract class AbstractAccessor implements AccessorsInterface {
|
|
24
27
|
/** @internal Mutable state grouped to allow O(1) shallow clone in mutations. */
|
|
25
28
|
private _state: AccessorState = {
|
|
@@ -32,7 +35,7 @@ export abstract class AbstractAccessor implements AccessorsInterface {
|
|
|
32
35
|
/**
|
|
33
36
|
* @param parser - Dot-notation parser for path operations.
|
|
34
37
|
*/
|
|
35
|
-
constructor(protected readonly parser:
|
|
38
|
+
constructor(protected readonly parser: ValidatableParserInterface) {}
|
|
36
39
|
|
|
37
40
|
/**
|
|
38
41
|
* Convert raw input data into a normalized plain object.
|
|
@@ -72,6 +75,8 @@ export abstract class AbstractAccessor implements AccessorsInterface {
|
|
|
72
75
|
*
|
|
73
76
|
* @param data - Raw input in the format expected by the accessor.
|
|
74
77
|
* @returns Populated accessor instance.
|
|
78
|
+
* @throws {InvalidFormatException} When the raw input cannot be parsed.
|
|
79
|
+
* @throws {SecurityException} When payload exceeds size limit, data contains forbidden keys, or violates structural limits.
|
|
75
80
|
*/
|
|
76
81
|
abstract from(data: unknown): this;
|
|
77
82
|
|
|
@@ -107,7 +112,7 @@ export abstract class AbstractAccessor implements AccessorsInterface {
|
|
|
107
112
|
* Only use with fully trusted, application-controlled input.
|
|
108
113
|
*
|
|
109
114
|
* @example
|
|
110
|
-
* // Trust the input
|
|
115
|
+
* // Trust the input - skip all security checks
|
|
111
116
|
* const accessor = new JsonAccessor(parser).strict(false).from(trustedPayload);
|
|
112
117
|
*/
|
|
113
118
|
strict(strict: boolean = true): this {
|
|
@@ -179,7 +184,8 @@ export abstract class AbstractAccessor implements AccessorsInterface {
|
|
|
179
184
|
* @returns True if the path resolves to a value.
|
|
180
185
|
*/
|
|
181
186
|
hasAt(segments: Array<string | number>): boolean {
|
|
182
|
-
|
|
187
|
+
const sentinel = Object.create(null) as Record<string, never>;
|
|
188
|
+
return this.parser.getAt(this._state.data, segments, sentinel) !== sentinel;
|
|
183
189
|
}
|
|
184
190
|
|
|
185
191
|
/**
|
|
@@ -260,13 +266,13 @@ export abstract class AbstractAccessor implements AccessorsInterface {
|
|
|
260
266
|
}
|
|
261
267
|
|
|
262
268
|
/**
|
|
263
|
-
* Count elements at a path, or the root if undefined.
|
|
269
|
+
* Count elements at a path, or the root if null/undefined.
|
|
264
270
|
*
|
|
265
|
-
* @param path - Dot-notation path, or undefined for root.
|
|
271
|
+
* @param path - Dot-notation path, or null/undefined for root.
|
|
266
272
|
* @returns Number of elements.
|
|
267
273
|
*/
|
|
268
|
-
count(path?: string): number {
|
|
269
|
-
const target = path
|
|
274
|
+
count(path?: string | null): number {
|
|
275
|
+
const target = path != null ? this.get(path, {}) : this._state.data;
|
|
270
276
|
if (typeof target === 'object' && target !== null) {
|
|
271
277
|
return Object.keys(target).length;
|
|
272
278
|
}
|
|
@@ -274,13 +280,13 @@ export abstract class AbstractAccessor implements AccessorsInterface {
|
|
|
274
280
|
}
|
|
275
281
|
|
|
276
282
|
/**
|
|
277
|
-
* Retrieve array keys at a path, or root keys if undefined.
|
|
283
|
+
* Retrieve array keys at a path, or root keys if null/undefined.
|
|
278
284
|
*
|
|
279
|
-
* @param path - Dot-notation path, or undefined for root.
|
|
285
|
+
* @param path - Dot-notation path, or null/undefined for root.
|
|
280
286
|
* @returns List of keys.
|
|
281
287
|
*/
|
|
282
|
-
keys(path?: string): string[] {
|
|
283
|
-
const target = path
|
|
288
|
+
keys(path?: string | null): string[] {
|
|
289
|
+
const target = path != null ? this.get(path, {}) : this._state.data;
|
|
284
290
|
/* Stryker disable next-line ConditionalExpression -- equivalent: get() always returns an object-type value here; typeof check is a type guard only */
|
|
285
291
|
if (typeof target === 'object' && target !== null) {
|
|
286
292
|
return Object.keys(target);
|
|
@@ -0,0 +1,27 @@
|
|
|
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
|
+
/**
|
|
6
|
+
* Base accessor with custom format integration support.
|
|
7
|
+
*
|
|
8
|
+
* Extends {@link AbstractAccessor} to inject a {@link ParseIntegrationInterface}
|
|
9
|
+
* for user-defined format detection and parsing. Used exclusively by
|
|
10
|
+
* {@link AnyAccessor} to handle arbitrary input formats.
|
|
11
|
+
*
|
|
12
|
+
* @internal
|
|
13
|
+
*/
|
|
14
|
+
export abstract class AbstractIntegrationAccessor extends AbstractAccessor {
|
|
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(
|
|
22
|
+
parser: ValidatableParserInterface,
|
|
23
|
+
protected readonly integration: ParseIntegrationInterface,
|
|
24
|
+
) {
|
|
25
|
+
super(parser);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
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
|
import { InvalidFormatException } from '../../exceptions/invalid-format-exception.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -9,21 +9,20 @@ import { InvalidFormatException } from '../../exceptions/invalid-format-exceptio
|
|
|
9
9
|
* Delegates format detection and parsing to a user-provided integration.
|
|
10
10
|
* Validates string payloads against security constraints before parsing.
|
|
11
11
|
*
|
|
12
|
+
* @api
|
|
13
|
+
*
|
|
12
14
|
* @example
|
|
13
15
|
* const integration = new MyCsvIntegration();
|
|
14
16
|
* const accessor = Inline.withParserIntegration(integration).fromAny(csvString);
|
|
15
17
|
* accessor.get('0.name'); // first row, name column
|
|
16
18
|
*/
|
|
17
|
-
export class AnyAccessor extends
|
|
18
|
-
private readonly integration: ParseIntegrationInterface;
|
|
19
|
-
|
|
19
|
+
export class AnyAccessor extends AbstractIntegrationAccessor {
|
|
20
20
|
/**
|
|
21
21
|
* @param parser - Dot-notation parser with security configuration.
|
|
22
22
|
* @param integration - Custom format parser for detecting and parsing input.
|
|
23
23
|
*/
|
|
24
|
-
constructor(parser:
|
|
25
|
-
super(parser);
|
|
26
|
-
this.integration = integration;
|
|
24
|
+
constructor(parser: ValidatableParserInterface, integration: ParseIntegrationInterface) {
|
|
25
|
+
super(parser, integration);
|
|
27
26
|
}
|
|
28
27
|
|
|
29
28
|
/**
|
|
@@ -33,6 +32,9 @@ export class AnyAccessor extends AbstractAccessor {
|
|
|
33
32
|
* @returns Populated accessor instance.
|
|
34
33
|
* @throws {InvalidFormatException} When the integration rejects the format.
|
|
35
34
|
* @throws {SecurityException} When string input violates payload-size limits.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* const accessor = new AnyAccessor(parser, integration).from(rawData);
|
|
36
38
|
*/
|
|
37
39
|
from(data: unknown): this {
|
|
38
40
|
if (!this.integration.assertFormat(data)) {
|
|
@@ -42,9 +44,7 @@ export class AnyAccessor extends AbstractAccessor {
|
|
|
42
44
|
return this.ingest(data);
|
|
43
45
|
}
|
|
44
46
|
|
|
45
|
-
/**
|
|
46
|
-
* @internal
|
|
47
|
-
*/
|
|
47
|
+
/** {@inheritDoc} */
|
|
48
48
|
protected parse(raw: unknown): Record<string, unknown> {
|
|
49
49
|
return this.integration.parse(raw);
|
|
50
50
|
}
|
|
@@ -6,6 +6,8 @@ import { InvalidFormatException } from '../../exceptions/invalid-format-exceptio
|
|
|
6
6
|
*
|
|
7
7
|
* Accepts a plain object or array directly. No string parsing is involved.
|
|
8
8
|
*
|
|
9
|
+
* @api
|
|
10
|
+
*
|
|
9
11
|
* @example
|
|
10
12
|
* const accessor = new ArrayAccessor(parser).from({ key: 'value' });
|
|
11
13
|
* accessor.get('key'); // 'value'
|
|
@@ -7,6 +7,8 @@ import { InvalidFormatException } from '../../exceptions/invalid-format-exceptio
|
|
|
7
7
|
* Parses KEY=VALUE lines, skipping comments (#) and blank lines.
|
|
8
8
|
* Strips surrounding single and double quotes from values.
|
|
9
9
|
*
|
|
10
|
+
* @api
|
|
11
|
+
*
|
|
10
12
|
* @example
|
|
11
13
|
* const accessor = new EnvAccessor(parser).from('DB_HOST=localhost\nDEBUG=true');
|
|
12
14
|
* accessor.get('DB_HOST'); // 'localhost'
|
|
@@ -7,6 +7,8 @@ import { InvalidFormatException } from '../../exceptions/invalid-format-exceptio
|
|
|
7
7
|
* Parses sections (e.g. `[section]`) as nested keys.
|
|
8
8
|
* Type inference: numeric strings become numbers, `true`/`false` become booleans.
|
|
9
9
|
*
|
|
10
|
+
* @api
|
|
11
|
+
*
|
|
10
12
|
* @example
|
|
11
13
|
* const accessor = new IniAccessor(parser).from('[db]\nhost=localhost\nport=5432');
|
|
12
14
|
* accessor.get('db.host'); // 'localhost'
|
|
@@ -6,6 +6,8 @@ import { InvalidFormatException } from '../../exceptions/invalid-format-exceptio
|
|
|
6
6
|
*
|
|
7
7
|
* Decodes JSON via `JSON.parse()`. Validates payload size before parsing.
|
|
8
8
|
*
|
|
9
|
+
* @api
|
|
10
|
+
*
|
|
9
11
|
* @example
|
|
10
12
|
* const accessor = new JsonAccessor(parser).from('{"key":"value"}');
|
|
11
13
|
* accessor.get('key'); // 'value'
|
|
@@ -7,6 +7,8 @@ import { InvalidFormatException } from '../../exceptions/invalid-format-exceptio
|
|
|
7
7
|
* Parses each non-empty line as a standalone JSON object,
|
|
8
8
|
* producing an indexed record of parsed entries.
|
|
9
9
|
*
|
|
10
|
+
* @api
|
|
11
|
+
*
|
|
10
12
|
* @example
|
|
11
13
|
* const ndjson = '{"id":1}\n{"id":2}';
|
|
12
14
|
* const accessor = new NdjsonAccessor(parser).from(ndjson);
|
|
@@ -8,6 +8,8 @@ import { SecurityException } from '../../exceptions/security-exception.js';
|
|
|
8
8
|
* Handles nested objects and arrays of objects without JSON roundtrip.
|
|
9
9
|
* Respects the configured max depth to prevent DoS from deeply nested structures.
|
|
10
10
|
*
|
|
11
|
+
* @api
|
|
12
|
+
*
|
|
11
13
|
* @example
|
|
12
14
|
* const obj = { user: { name: 'Alice' } };
|
|
13
15
|
* const accessor = new ObjectAccessor(parser).from(obj);
|
|
@@ -9,6 +9,8 @@ import { XmlParser } from '../../parser/xml-parser.js';
|
|
|
9
9
|
* Parses XML using the DOM parser available in the current environment.
|
|
10
10
|
* Blocks DOCTYPE declarations to prevent XXE attacks.
|
|
11
11
|
*
|
|
12
|
+
* @api
|
|
13
|
+
*
|
|
12
14
|
* @example
|
|
13
15
|
* const accessor = new XmlAccessor(parser).from('<root><key>value</key></root>');
|
|
14
16
|
* accessor.get('key'); // 'value'
|
|
@@ -9,6 +9,8 @@ import { YamlParser } from '../../parser/yaml-parser.js';
|
|
|
9
9
|
* depending on external YAML libraries. Tags, anchors, aliases, and
|
|
10
10
|
* merge keys are blocked as unsafe constructs.
|
|
11
11
|
*
|
|
12
|
+
* @api
|
|
13
|
+
*
|
|
12
14
|
* @example
|
|
13
15
|
* const accessor = new YamlAccessor(parser).from('key: value\nnested:\n a: 1');
|
|
14
16
|
* accessor.get('nested.a'); // 1
|
|
@@ -24,7 +26,7 @@ export class YamlAccessor extends AbstractAccessor {
|
|
|
24
26
|
* @throws {SecurityException} When payload size exceeds limit.
|
|
25
27
|
*
|
|
26
28
|
* @example
|
|
27
|
-
* accessor.from('name: Alice\nage: 30');
|
|
29
|
+
* accessor.from('name: Alice\nage: 30'); // { name: 'Alice', age: 30 }
|
|
28
30
|
*/
|
|
29
31
|
from(data: unknown): this {
|
|
30
32
|
if (typeof data !== 'string') {
|
|
@@ -47,6 +49,6 @@ export class YamlAccessor extends AbstractAccessor {
|
|
|
47
49
|
}
|
|
48
50
|
/* c8 ignore stop */
|
|
49
51
|
|
|
50
|
-
return new YamlParser().parse(raw);
|
|
52
|
+
return new YamlParser(this.parser.getMaxDepth()).parse(raw);
|
|
51
53
|
}
|
|
52
54
|
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { PathCacheInterface } from '../contracts/path-cache-interface.js';
|
|
2
|
+
import type { Segment } from '../path-query/segment-type.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* LRU cache for parsed dot-notation path segments.
|
|
6
|
+
*
|
|
7
|
+
* Stores up to {@link maxSize} entries, evicting the least-recently-used
|
|
8
|
+
* entry when the capacity is reached. Recently accessed entries are
|
|
9
|
+
* promoted to the end of the internal map on read.
|
|
10
|
+
*
|
|
11
|
+
* @internal Consumers should type-hint against {@link PathCacheInterface};
|
|
12
|
+
* this concrete class is an implementation detail subject to change.
|
|
13
|
+
*
|
|
14
|
+
* @see PathCacheInterface
|
|
15
|
+
*/
|
|
16
|
+
export class SimplePathCache implements PathCacheInterface {
|
|
17
|
+
private readonly cache: Map<string, Segment[]> = new Map();
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Create a cache with the given maximum capacity.
|
|
21
|
+
*
|
|
22
|
+
* @param maxSize - Maximum number of cached path entries.
|
|
23
|
+
*/
|
|
24
|
+
constructor(private readonly maxSize: number = 1000) {}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Retrieve cached segments and promote to most-recently-used.
|
|
28
|
+
*
|
|
29
|
+
* @param path - Dot-notation path string.
|
|
30
|
+
* @returns Cached segments, or null on miss.
|
|
31
|
+
*/
|
|
32
|
+
get(path: string): Segment[] | null {
|
|
33
|
+
if (this.cache.has(path)) {
|
|
34
|
+
const value = this.cache.get(path)!;
|
|
35
|
+
this.cache.delete(path);
|
|
36
|
+
this.cache.set(path, value);
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Store segments, evicting the oldest entry if capacity is reached.
|
|
44
|
+
*
|
|
45
|
+
* @param path - Dot-notation path string.
|
|
46
|
+
* @param segments - Parsed segment array to cache.
|
|
47
|
+
*/
|
|
48
|
+
set(path: string, segments: Segment[]): void {
|
|
49
|
+
if (this.cache.size >= this.maxSize) {
|
|
50
|
+
const firstKey = this.cache.keys().next().value;
|
|
51
|
+
if (firstKey !== undefined) {
|
|
52
|
+
this.cache.delete(firstKey);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
this.cache.set(path, segments);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Check whether a path exists in the cache.
|
|
60
|
+
*
|
|
61
|
+
* @param path - Dot-notation path string.
|
|
62
|
+
* @returns True if cached.
|
|
63
|
+
*/
|
|
64
|
+
has(path: string): boolean {
|
|
65
|
+
return this.cache.has(path);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Clear all cached entries.
|
|
70
|
+
*
|
|
71
|
+
* @returns Same instance for fluent chaining.
|
|
72
|
+
*/
|
|
73
|
+
clear(): this {
|
|
74
|
+
this.cache.clear();
|
|
75
|
+
return this;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -7,6 +7,8 @@ import type { FactoryAccessorsInterface } from './factory-accessors-interface.js
|
|
|
7
7
|
*
|
|
8
8
|
* Marker interface that aggregates all accessor responsibilities into
|
|
9
9
|
* a single type, used as the base contract for AbstractAccessor.
|
|
10
|
+
*
|
|
11
|
+
* @api
|
|
10
12
|
*/
|
|
11
13
|
export interface AccessorsInterface
|
|
12
14
|
extends ReadableAccessorsInterface, WritableAccessorsInterface, FactoryAccessorsInterface {}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { FilterExpression } from '../path-query/segment-type.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Contract for parsing and evaluating filter predicate expressions.
|
|
5
|
+
*
|
|
6
|
+
* Handles the `[?expression]` segment syntax, converting string predicates
|
|
7
|
+
* into structured condition arrays and evaluating them against data items.
|
|
8
|
+
*
|
|
9
|
+
* @internal
|
|
10
|
+
*/
|
|
11
|
+
export interface FilterEvaluatorInterface {
|
|
12
|
+
/**
|
|
13
|
+
* Parse a filter expression string into a structured condition array.
|
|
14
|
+
*
|
|
15
|
+
* @param expression - Raw filter expression (e.g. "age>18 && active==true").
|
|
16
|
+
* @returns Parsed conditions and logical operators.
|
|
17
|
+
*
|
|
18
|
+
* @throws {InvalidFormatException} When the expression syntax is invalid.
|
|
19
|
+
*/
|
|
20
|
+
parse(expression: string): FilterExpression;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Evaluate a parsed expression against a single data item.
|
|
24
|
+
*
|
|
25
|
+
* @param item - Data item to test.
|
|
26
|
+
* @param expr - Parsed expression from {@link parse}.
|
|
27
|
+
* @returns True if the item satisfies the expression.
|
|
28
|
+
*/
|
|
29
|
+
evaluate(item: Record<string, unknown>, expr: FilterExpression): boolean;
|
|
30
|
+
}
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
* Enables the {@link AnyAccessor} to accept arbitrary input by delegating
|
|
5
5
|
* format validation and parsing to a user-provided implementation.
|
|
6
6
|
*
|
|
7
|
+
* @api
|
|
8
|
+
*
|
|
7
9
|
* @example
|
|
8
10
|
* class CsvIntegration implements ParseIntegrationInterface {
|
|
9
11
|
* assertFormat(raw: unknown): boolean { return typeof raw === 'string' && raw.includes(','); }
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core contract for dot-notation path operations on object data.
|
|
3
|
+
*
|
|
4
|
+
* Defines the fundamental CRUD operations for reading, writing, and
|
|
5
|
+
* removing values from nested objects using dot-notation path strings
|
|
6
|
+
* or pre-parsed segment arrays.
|
|
7
|
+
*
|
|
8
|
+
* @internal Not part of the public API - consumers should not implement
|
|
9
|
+
* or type-hint against this interface directly.
|
|
10
|
+
*/
|
|
11
|
+
export interface ParserInterface {
|
|
12
|
+
/**
|
|
13
|
+
* Retrieve a value at the given dot-notation path.
|
|
14
|
+
*
|
|
15
|
+
* @param data - Source data object.
|
|
16
|
+
* @param path - Dot-notation path (e.g. "user.address.city").
|
|
17
|
+
* @param defaultValue - Fallback value when the path does not exist.
|
|
18
|
+
* @returns Resolved value or the default.
|
|
19
|
+
*/
|
|
20
|
+
get(data: Record<string, unknown>, path: string, defaultValue?: unknown): unknown;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Check whether a dot-notation path exists in the data.
|
|
24
|
+
*
|
|
25
|
+
* @param data - Source data object.
|
|
26
|
+
* @param path - Dot-notation path to check.
|
|
27
|
+
* @returns True if the path resolves to an existing value.
|
|
28
|
+
*/
|
|
29
|
+
has(data: Record<string, unknown>, path: string): boolean;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Set a value at the given dot-notation path.
|
|
33
|
+
*
|
|
34
|
+
* @param data - Source data object.
|
|
35
|
+
* @param path - Dot-notation path for the target key.
|
|
36
|
+
* @param value - Value to assign.
|
|
37
|
+
* @returns New object with the value set.
|
|
38
|
+
*
|
|
39
|
+
* @throws {SecurityException} When the path contains forbidden keys.
|
|
40
|
+
*/
|
|
41
|
+
set(data: Record<string, unknown>, path: string, value: unknown): Record<string, unknown>;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Remove a value at the given dot-notation path.
|
|
45
|
+
*
|
|
46
|
+
* @param data - Source data object.
|
|
47
|
+
* @param path - Dot-notation path to remove.
|
|
48
|
+
* @returns New object with the key removed.
|
|
49
|
+
*
|
|
50
|
+
* @throws {SecurityException} When the path contains forbidden keys.
|
|
51
|
+
*/
|
|
52
|
+
remove(data: Record<string, unknown>, path: string): Record<string, unknown>;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Deep-merge an object into the value at the given path.
|
|
56
|
+
*
|
|
57
|
+
* @param data - Source data object.
|
|
58
|
+
* @param path - Dot-notation path to the merge target.
|
|
59
|
+
* @param value - Object to merge into the existing value.
|
|
60
|
+
* @returns New object with merged data.
|
|
61
|
+
*
|
|
62
|
+
* @throws {SecurityException} When merge depth exceeds the configured maximum.
|
|
63
|
+
* @throws {SecurityException} When keys contain forbidden values.
|
|
64
|
+
*/
|
|
65
|
+
merge(
|
|
66
|
+
data: Record<string, unknown>,
|
|
67
|
+
path: string,
|
|
68
|
+
value: Record<string, unknown>,
|
|
69
|
+
): Record<string, unknown>;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Retrieve a value using pre-parsed key segments.
|
|
73
|
+
*
|
|
74
|
+
* @param data - Source data object.
|
|
75
|
+
* @param segments - Ordered list of keys to traverse.
|
|
76
|
+
* @param defaultValue - Fallback value when the path does not exist.
|
|
77
|
+
* @returns Resolved value or the default.
|
|
78
|
+
*/
|
|
79
|
+
getAt(
|
|
80
|
+
data: Record<string, unknown>,
|
|
81
|
+
segments: Array<string | number>,
|
|
82
|
+
defaultValue?: unknown,
|
|
83
|
+
): unknown;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Set a value using pre-parsed key segments.
|
|
87
|
+
*
|
|
88
|
+
* @param data - Source data object.
|
|
89
|
+
* @param segments - Ordered list of keys to the target.
|
|
90
|
+
* @param value - Value to assign.
|
|
91
|
+
* @returns New object with the value set.
|
|
92
|
+
*
|
|
93
|
+
* @throws {SecurityException} When segments contain forbidden keys.
|
|
94
|
+
*/
|
|
95
|
+
setAt(
|
|
96
|
+
data: Record<string, unknown>,
|
|
97
|
+
segments: Array<string | number>,
|
|
98
|
+
value: unknown,
|
|
99
|
+
): Record<string, unknown>;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Remove a value using pre-parsed key segments.
|
|
103
|
+
*
|
|
104
|
+
* @param data - Source data object.
|
|
105
|
+
* @param segments - Ordered list of keys to the target.
|
|
106
|
+
* @returns New object with the key removed.
|
|
107
|
+
*
|
|
108
|
+
* @throws {SecurityException} When segments contain forbidden keys.
|
|
109
|
+
*/
|
|
110
|
+
removeAt(
|
|
111
|
+
data: Record<string, unknown>,
|
|
112
|
+
segments: Array<string | number>,
|
|
113
|
+
): Record<string, unknown>;
|
|
114
|
+
}
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
+
import type { Segment } from '../path-query/segment-type.js';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Contract for a path-segment cache.
|
|
3
5
|
*
|
|
4
6
|
* Provides O(1) lookup for previously parsed dot-notation path strings,
|
|
5
7
|
* avoiding repeated segment parsing on hot paths.
|
|
6
8
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
9
|
+
* Segments are structured typed arrays with {@link SegmentType} metadata,
|
|
10
|
+
* matching the PHP implementation.
|
|
11
|
+
*
|
|
12
|
+
* @api
|
|
11
13
|
*/
|
|
12
14
|
export interface PathCacheInterface {
|
|
13
15
|
/**
|
|
@@ -16,7 +18,7 @@ export interface PathCacheInterface {
|
|
|
16
18
|
* @param path - Dot-notation path string.
|
|
17
19
|
* @returns Cached segment array, or null if not cached.
|
|
18
20
|
*/
|
|
19
|
-
get(path: string):
|
|
21
|
+
get(path: string): Segment[] | null;
|
|
20
22
|
|
|
21
23
|
/**
|
|
22
24
|
* Store parsed segments for a path string.
|
|
@@ -24,7 +26,7 @@ export interface PathCacheInterface {
|
|
|
24
26
|
* @param path - Dot-notation path string.
|
|
25
27
|
* @param segments - Parsed segment array to cache.
|
|
26
28
|
*/
|
|
27
|
-
set(path: string, segments:
|
|
29
|
+
set(path: string, segments: Segment[]): void;
|
|
28
30
|
|
|
29
31
|
/**
|
|
30
32
|
* Check whether a path exists in the cache.
|
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Defines methods for retrieving, checking existence, counting,
|
|
5
5
|
* and inspecting keys within the accessor's internal data store.
|
|
6
|
+
*
|
|
7
|
+
* @api
|
|
8
|
+
*
|
|
9
|
+
* @see AbstractAccessor Base implementation.
|
|
10
|
+
* @see AccessorsInterface Composite interface extending this contract.
|
|
6
11
|
*/
|
|
7
12
|
export interface ReadableAccessorsInterface {
|
|
8
13
|
/**
|
|
@@ -71,18 +76,18 @@ export interface ReadableAccessorsInterface {
|
|
|
71
76
|
all(): Record<string, unknown>;
|
|
72
77
|
|
|
73
78
|
/**
|
|
74
|
-
* Count elements at a path, or the root if undefined.
|
|
79
|
+
* Count elements at a path, or the root if null/undefined.
|
|
75
80
|
*
|
|
76
|
-
* @param path - Dot-notation path, or undefined for root.
|
|
81
|
+
* @param path - Dot-notation path, or null/undefined for root.
|
|
77
82
|
* @returns Number of elements.
|
|
78
83
|
*/
|
|
79
|
-
count(path?: string): number;
|
|
84
|
+
count(path?: string | null): number;
|
|
80
85
|
|
|
81
86
|
/**
|
|
82
|
-
* Retrieve array keys at a path, or root keys if undefined.
|
|
87
|
+
* Retrieve array keys at a path, or root keys if null/undefined.
|
|
83
88
|
*
|
|
84
|
-
* @param path - Dot-notation path, or undefined for root.
|
|
89
|
+
* @param path - Dot-notation path, or null/undefined for root.
|
|
85
90
|
* @returns List of keys.
|
|
86
91
|
*/
|
|
87
|
-
keys(path?: string): string[];
|
|
92
|
+
keys(path?: string | null): string[];
|
|
88
93
|
}
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
* Prevents injection attacks by rejecting prototype pollution vectors,
|
|
5
5
|
* legacy prototype manipulation methods, stream wrapper / protocol URI schemes,
|
|
6
6
|
* and Node.js globals during data access and mutation operations.
|
|
7
|
+
*
|
|
8
|
+
* @api
|
|
7
9
|
*/
|
|
8
10
|
export interface SecurityGuardInterface {
|
|
9
11
|
/**
|