@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.
- package/.gitattributes +16 -0
- package/.gitkeep +0 -0
- package/CHANGELOG.md +38 -0
- package/LICENSE +21 -0
- package/README.md +454 -0
- package/benchmarks/get.bench.ts +26 -0
- package/benchmarks/parse.bench.ts +41 -0
- package/dist/accessors/abstract-accessor.d.ts +213 -0
- package/dist/accessors/abstract-accessor.js +294 -0
- package/dist/accessors/formats/any-accessor.d.ts +35 -0
- package/dist/accessors/formats/any-accessor.js +44 -0
- package/dist/accessors/formats/array-accessor.d.ts +26 -0
- package/dist/accessors/formats/array-accessor.js +39 -0
- package/dist/accessors/formats/env-accessor.d.ts +27 -0
- package/dist/accessors/formats/env-accessor.js +64 -0
- package/dist/accessors/formats/ini-accessor.d.ts +41 -0
- package/dist/accessors/formats/ini-accessor.js +109 -0
- package/dist/accessors/formats/json-accessor.d.ts +26 -0
- package/dist/accessors/formats/json-accessor.js +56 -0
- package/dist/accessors/formats/ndjson-accessor.d.ts +28 -0
- package/dist/accessors/formats/ndjson-accessor.js +71 -0
- package/dist/accessors/formats/object-accessor.d.ts +48 -0
- package/dist/accessors/formats/object-accessor.js +90 -0
- package/dist/accessors/formats/xml-accessor.d.ts +27 -0
- package/dist/accessors/formats/xml-accessor.js +52 -0
- package/dist/accessors/formats/yaml-accessor.d.ts +29 -0
- package/dist/accessors/formats/yaml-accessor.js +46 -0
- package/dist/contracts/accessors-interface.d.ts +11 -0
- package/dist/contracts/accessors-interface.js +1 -0
- package/dist/contracts/factory-accessors-interface.d.ts +16 -0
- package/dist/contracts/factory-accessors-interface.js +1 -0
- package/dist/contracts/parse-integration-interface.d.ts +31 -0
- package/dist/contracts/parse-integration-interface.js +1 -0
- package/dist/contracts/path-cache-interface.d.ts +40 -0
- package/dist/contracts/path-cache-interface.js +1 -0
- package/dist/contracts/readable-accessors-interface.d.ts +79 -0
- package/dist/contracts/readable-accessors-interface.js +1 -0
- package/dist/contracts/security-guard-interface.d.ts +40 -0
- package/dist/contracts/security-guard-interface.js +1 -0
- package/dist/contracts/security-parser-interface.d.ts +67 -0
- package/dist/contracts/security-parser-interface.js +1 -0
- package/dist/contracts/writable-accessors-interface.d.ts +65 -0
- package/dist/contracts/writable-accessors-interface.js +1 -0
- package/dist/core/dot-notation-parser.d.ts +204 -0
- package/dist/core/dot-notation-parser.js +343 -0
- package/dist/exceptions/accessor-exception.d.ts +13 -0
- package/dist/exceptions/accessor-exception.js +16 -0
- package/dist/exceptions/invalid-format-exception.d.ts +14 -0
- package/dist/exceptions/invalid-format-exception.js +17 -0
- package/dist/exceptions/parser-exception.d.ts +14 -0
- package/dist/exceptions/parser-exception.js +17 -0
- package/dist/exceptions/path-not-found-exception.d.ts +14 -0
- package/dist/exceptions/path-not-found-exception.js +17 -0
- package/dist/exceptions/readonly-violation-exception.d.ts +15 -0
- package/dist/exceptions/readonly-violation-exception.js +18 -0
- package/dist/exceptions/security-exception.d.ts +18 -0
- package/dist/exceptions/security-exception.js +21 -0
- package/dist/exceptions/unsupported-type-exception.d.ts +14 -0
- package/dist/exceptions/unsupported-type-exception.js +17 -0
- package/dist/exceptions/yaml-parse-exception.d.ts +17 -0
- package/dist/exceptions/yaml-parse-exception.js +20 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.js +30 -0
- package/dist/inline.d.ts +402 -0
- package/dist/inline.js +512 -0
- package/dist/parser/xml-parser.d.ts +46 -0
- package/dist/parser/xml-parser.js +288 -0
- package/dist/parser/yaml-parser.d.ts +94 -0
- package/dist/parser/yaml-parser.js +286 -0
- package/dist/security/forbidden-keys.d.ts +34 -0
- package/dist/security/forbidden-keys.js +80 -0
- package/dist/security/security-guard.d.ts +94 -0
- package/dist/security/security-guard.js +172 -0
- package/dist/security/security-parser.d.ts +130 -0
- package/dist/security/security-parser.js +192 -0
- package/dist/type-format.d.ts +28 -0
- package/dist/type-format.js +29 -0
- package/eslint.config.js +1 -0
- package/package.json +39 -0
- package/src/accessors/abstract-accessor.ts +353 -0
- package/src/accessors/formats/any-accessor.ts +51 -0
- package/src/accessors/formats/array-accessor.ts +45 -0
- package/src/accessors/formats/env-accessor.ts +79 -0
- package/src/accessors/formats/ini-accessor.ts +124 -0
- package/src/accessors/formats/json-accessor.ts +66 -0
- package/src/accessors/formats/ndjson-accessor.ts +82 -0
- package/src/accessors/formats/object-accessor.ts +100 -0
- package/src/accessors/formats/xml-accessor.ts +58 -0
- package/src/accessors/formats/yaml-accessor.ts +52 -0
- package/src/contracts/accessors-interface.ts +12 -0
- package/src/contracts/factory-accessors-interface.ts +16 -0
- package/src/contracts/parse-integration-interface.ts +32 -0
- package/src/contracts/path-cache-interface.ts +43 -0
- package/src/contracts/readable-accessors-interface.ts +88 -0
- package/src/contracts/security-guard-interface.ts +43 -0
- package/src/contracts/security-parser-interface.ts +74 -0
- package/src/contracts/writable-accessors-interface.ts +70 -0
- package/src/core/dot-notation-parser.ts +419 -0
- package/src/exceptions/accessor-exception.ts +16 -0
- package/src/exceptions/invalid-format-exception.ts +18 -0
- package/src/exceptions/parser-exception.ts +18 -0
- package/src/exceptions/path-not-found-exception.ts +18 -0
- package/src/exceptions/readonly-violation-exception.ts +19 -0
- package/src/exceptions/security-exception.ts +22 -0
- package/src/exceptions/unsupported-type-exception.ts +18 -0
- package/src/exceptions/yaml-parse-exception.ts +21 -0
- package/src/index.ts +46 -0
- package/src/inline.ts +570 -0
- package/src/parser/xml-parser.ts +334 -0
- package/src/parser/yaml-parser.ts +368 -0
- package/src/security/forbidden-keys.ts +81 -0
- package/src/security/security-guard.ts +195 -0
- package/src/security/security-parser.ts +233 -0
- package/src/type-format.ts +28 -0
- package/stryker.config.json +24 -0
- package/tests/accessors/accessors.test.ts +1017 -0
- package/tests/accessors/json-accessor.test.ts +171 -0
- package/tests/core/dot-notation-parser.test.ts +587 -0
- package/tests/exceptions/parser-exception.test.ts +31 -0
- package/tests/inline.test.ts +445 -0
- package/tests/mocks/fake-parse-integration.ts +24 -0
- package/tests/mocks/fake-path-cache.ts +31 -0
- package/tests/parity.test.ts +164 -0
- package/tests/parser/xml-parser.test.ts +618 -0
- package/tests/parser/yaml-parser.test.ts +463 -0
- package/tests/security/security-guard.test.ts +646 -0
- package/tests/security/security-parser.test.ts +391 -0
- package/tsconfig.json +16 -0
- package/vitest.config.ts +19 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import type { AccessorsInterface } from '../contracts/accessors-interface.js';
|
|
2
|
+
import { DotNotationParser } from '../core/dot-notation-parser.js';
|
|
3
|
+
export declare abstract class AbstractAccessor implements AccessorsInterface {
|
|
4
|
+
protected readonly parser: DotNotationParser;
|
|
5
|
+
/** @internal Mutable state grouped to allow O(1) shallow clone in mutations. */
|
|
6
|
+
private _state;
|
|
7
|
+
/**
|
|
8
|
+
* @param parser - Dot-notation parser for path operations.
|
|
9
|
+
*/
|
|
10
|
+
constructor(parser: DotNotationParser);
|
|
11
|
+
/**
|
|
12
|
+
* Convert raw input data into a normalized plain object.
|
|
13
|
+
*
|
|
14
|
+
* @param raw - Raw input in the format expected by the accessor.
|
|
15
|
+
* @returns Parsed data structure.
|
|
16
|
+
* @throws {InvalidFormatException} When the input is malformed.
|
|
17
|
+
*/
|
|
18
|
+
protected abstract parse(raw: unknown): Record<string, unknown>;
|
|
19
|
+
/**
|
|
20
|
+
* Ingest raw data, optionally validating via strict mode.
|
|
21
|
+
*
|
|
22
|
+
* When strict mode is enabled (default), validates payload size for string
|
|
23
|
+
* inputs and runs structural/key-safety validation on parsed data.
|
|
24
|
+
*
|
|
25
|
+
* @param raw - Raw input data.
|
|
26
|
+
* @returns Same instance with data populated.
|
|
27
|
+
* @throws {InvalidFormatException} When the raw data cannot be parsed.
|
|
28
|
+
* @throws {SecurityException} When payload exceeds size limit, data contains forbidden keys, or violates limits.
|
|
29
|
+
*/
|
|
30
|
+
protected ingest(raw: unknown): this;
|
|
31
|
+
/**
|
|
32
|
+
* Hydrate the accessor from raw input data.
|
|
33
|
+
*
|
|
34
|
+
* @param data - Raw input in the format expected by the accessor.
|
|
35
|
+
* @returns Populated accessor instance.
|
|
36
|
+
*/
|
|
37
|
+
abstract from(data: unknown): this;
|
|
38
|
+
/**
|
|
39
|
+
* Retrieve the original raw input data before parsing.
|
|
40
|
+
*
|
|
41
|
+
* @returns Original input passed to `from()`.
|
|
42
|
+
*/
|
|
43
|
+
getRaw(): unknown;
|
|
44
|
+
/**
|
|
45
|
+
* Return a new instance with the given readonly state.
|
|
46
|
+
*
|
|
47
|
+
* @param isReadonly - Whether the new instance should block mutations.
|
|
48
|
+
* @returns New accessor instance with the readonly state applied.
|
|
49
|
+
*/
|
|
50
|
+
readonly(isReadonly?: boolean): this;
|
|
51
|
+
/**
|
|
52
|
+
* Return a new instance with the given strict mode state.
|
|
53
|
+
*
|
|
54
|
+
* @param strict - Whether to enable strict security validation.
|
|
55
|
+
* @returns New accessor instance with the strict mode applied.
|
|
56
|
+
*
|
|
57
|
+
* @security Passing `false` disables all SecurityGuard and SecurityParser
|
|
58
|
+
* validation (key safety, payload size, depth and key-count limits).
|
|
59
|
+
* Only use with fully trusted, application-controlled input.
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* // Trust the input — skip all security checks
|
|
63
|
+
* const accessor = new JsonAccessor(parser).strict(false).from(trustedPayload);
|
|
64
|
+
*/
|
|
65
|
+
strict(strict?: boolean): this;
|
|
66
|
+
/**
|
|
67
|
+
* Retrieve a value at a dot-notation path.
|
|
68
|
+
*
|
|
69
|
+
* @param path - Dot-notation path (e.g. "user.name").
|
|
70
|
+
* @param defaultValue - Fallback when the path does not exist.
|
|
71
|
+
* @returns Resolved value or the default.
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* accessor.get('user.name', 'unknown');
|
|
75
|
+
*/
|
|
76
|
+
get(path: string, defaultValue?: unknown): unknown;
|
|
77
|
+
/**
|
|
78
|
+
* Retrieve a value or throw when the path does not exist.
|
|
79
|
+
*
|
|
80
|
+
* @param path - Dot-notation path.
|
|
81
|
+
* @returns Resolved value.
|
|
82
|
+
* @throws {PathNotFoundException} When the path is missing.
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* accessor.getOrFail('user.name'); // throws if not found
|
|
86
|
+
*/
|
|
87
|
+
getOrFail(path: string): unknown;
|
|
88
|
+
/**
|
|
89
|
+
* Retrieve a value using pre-parsed key segments.
|
|
90
|
+
*
|
|
91
|
+
* @param segments - Ordered list of keys.
|
|
92
|
+
* @param defaultValue - Fallback when the path does not exist.
|
|
93
|
+
* @returns Resolved value or the default.
|
|
94
|
+
*/
|
|
95
|
+
getAt(segments: Array<string | number>, defaultValue?: unknown): unknown;
|
|
96
|
+
/**
|
|
97
|
+
* Check whether a dot-notation path exists.
|
|
98
|
+
*
|
|
99
|
+
* @param path - Dot-notation path.
|
|
100
|
+
* @returns True if the path resolves to a value.
|
|
101
|
+
*/
|
|
102
|
+
has(path: string): boolean;
|
|
103
|
+
/**
|
|
104
|
+
* Check whether a path exists using pre-parsed key segments.
|
|
105
|
+
*
|
|
106
|
+
* @param segments - Ordered list of keys.
|
|
107
|
+
* @returns True if the path resolves to a value.
|
|
108
|
+
*/
|
|
109
|
+
hasAt(segments: Array<string | number>): boolean;
|
|
110
|
+
/**
|
|
111
|
+
* Set a value at a dot-notation path.
|
|
112
|
+
*
|
|
113
|
+
* @param path - Dot-notation path.
|
|
114
|
+
* @param value - Value to assign.
|
|
115
|
+
* @returns New accessor instance with the value set.
|
|
116
|
+
* @throws {ReadonlyViolationException} When the accessor is readonly.
|
|
117
|
+
* @throws {SecurityException} When the path contains forbidden keys.
|
|
118
|
+
*/
|
|
119
|
+
set(path: string, value: unknown): this;
|
|
120
|
+
/**
|
|
121
|
+
* Set a value using pre-parsed key segments.
|
|
122
|
+
*
|
|
123
|
+
* @param segments - Ordered list of keys.
|
|
124
|
+
* @param value - Value to assign.
|
|
125
|
+
* @returns New accessor instance with the value set.
|
|
126
|
+
* @throws {ReadonlyViolationException} When the accessor is readonly.
|
|
127
|
+
* @throws {SecurityException} When segments contain forbidden keys.
|
|
128
|
+
*/
|
|
129
|
+
setAt(segments: Array<string | number>, value: unknown): this;
|
|
130
|
+
/**
|
|
131
|
+
* Remove a value at a dot-notation path.
|
|
132
|
+
*
|
|
133
|
+
* @param path - Dot-notation path to remove.
|
|
134
|
+
* @returns New accessor instance without the specified path.
|
|
135
|
+
* @throws {ReadonlyViolationException} When the accessor is readonly.
|
|
136
|
+
* @throws {SecurityException} When the path contains forbidden keys.
|
|
137
|
+
*/
|
|
138
|
+
remove(path: string): this;
|
|
139
|
+
/**
|
|
140
|
+
* Remove a value using pre-parsed key segments.
|
|
141
|
+
*
|
|
142
|
+
* @param segments - Ordered list of keys.
|
|
143
|
+
* @returns New accessor instance without the specified path.
|
|
144
|
+
* @throws {ReadonlyViolationException} When the accessor is readonly.
|
|
145
|
+
* @throws {SecurityException} When segments contain forbidden keys.
|
|
146
|
+
*/
|
|
147
|
+
removeAt(segments: Array<string | number>): this;
|
|
148
|
+
/**
|
|
149
|
+
* Retrieve multiple values by their paths with individual defaults.
|
|
150
|
+
*
|
|
151
|
+
* @param paths - Map of path to default value.
|
|
152
|
+
* @returns Map of path to resolved value.
|
|
153
|
+
*/
|
|
154
|
+
getMany(paths: Record<string, unknown>): Record<string, unknown>;
|
|
155
|
+
/**
|
|
156
|
+
* Return all parsed data as a plain object.
|
|
157
|
+
*
|
|
158
|
+
* @returns Complete internal data.
|
|
159
|
+
*/
|
|
160
|
+
all(): Record<string, unknown>;
|
|
161
|
+
/**
|
|
162
|
+
* Count elements at a path, or the root if undefined.
|
|
163
|
+
*
|
|
164
|
+
* @param path - Dot-notation path, or undefined for root.
|
|
165
|
+
* @returns Number of elements.
|
|
166
|
+
*/
|
|
167
|
+
count(path?: string): number;
|
|
168
|
+
/**
|
|
169
|
+
* Retrieve array keys at a path, or root keys if undefined.
|
|
170
|
+
*
|
|
171
|
+
* @param path - Dot-notation path, or undefined for root.
|
|
172
|
+
* @returns List of keys.
|
|
173
|
+
*/
|
|
174
|
+
keys(path?: string): string[];
|
|
175
|
+
/**
|
|
176
|
+
* Deep-merge an object into the value at a dot-notation path.
|
|
177
|
+
*
|
|
178
|
+
* @param path - Dot-notation path to the merge target.
|
|
179
|
+
* @param value - Object to merge into the existing value.
|
|
180
|
+
* @returns New accessor instance with merged data.
|
|
181
|
+
* @throws {ReadonlyViolationException} When the accessor is readonly.
|
|
182
|
+
* @throws {SecurityException} When the path or values contain forbidden keys.
|
|
183
|
+
*/
|
|
184
|
+
merge(path: string, value: Record<string, unknown>): this;
|
|
185
|
+
/**
|
|
186
|
+
* Deep-merge an object into the root data.
|
|
187
|
+
*
|
|
188
|
+
* @param value - Object to merge into the root.
|
|
189
|
+
* @returns New accessor instance with merged data.
|
|
190
|
+
* @throws {ReadonlyViolationException} When the accessor is readonly.
|
|
191
|
+
* @throws {SecurityException} When values contain forbidden keys.
|
|
192
|
+
*/
|
|
193
|
+
mergeAll(value: Record<string, unknown>): this;
|
|
194
|
+
/**
|
|
195
|
+
* Assert that the accessor is not in readonly mode.
|
|
196
|
+
*
|
|
197
|
+
* @throws {ReadonlyViolationException} When the accessor is readonly.
|
|
198
|
+
*/
|
|
199
|
+
private assertNotReadOnly;
|
|
200
|
+
/**
|
|
201
|
+
* Create a shallow clone of this accessor.
|
|
202
|
+
*
|
|
203
|
+
* @returns Cloned accessor instance.
|
|
204
|
+
*/
|
|
205
|
+
private cloneInstance;
|
|
206
|
+
/**
|
|
207
|
+
* Create a clone with new internal data.
|
|
208
|
+
*
|
|
209
|
+
* @param newData - New data for the clone.
|
|
210
|
+
* @returns Cloned accessor with updated data.
|
|
211
|
+
*/
|
|
212
|
+
private mutateTo;
|
|
213
|
+
}
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import { PathNotFoundException } from '../exceptions/path-not-found-exception.js';
|
|
2
|
+
import { ReadonlyViolationException } from '../exceptions/readonly-violation-exception.js';
|
|
3
|
+
export class AbstractAccessor {
|
|
4
|
+
parser;
|
|
5
|
+
/** @internal Mutable state grouped to allow O(1) shallow clone in mutations. */
|
|
6
|
+
_state = {
|
|
7
|
+
data: {},
|
|
8
|
+
isReadonly: false,
|
|
9
|
+
isStrict: true,
|
|
10
|
+
rawInput: null,
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* @param parser - Dot-notation parser for path operations.
|
|
14
|
+
*/
|
|
15
|
+
constructor(parser) {
|
|
16
|
+
this.parser = parser;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Ingest raw data, optionally validating via strict mode.
|
|
20
|
+
*
|
|
21
|
+
* When strict mode is enabled (default), validates payload size for string
|
|
22
|
+
* inputs and runs structural/key-safety validation on parsed data.
|
|
23
|
+
*
|
|
24
|
+
* @param raw - Raw input data.
|
|
25
|
+
* @returns Same instance with data populated.
|
|
26
|
+
* @throws {InvalidFormatException} When the raw data cannot be parsed.
|
|
27
|
+
* @throws {SecurityException} When payload exceeds size limit, data contains forbidden keys, or violates limits.
|
|
28
|
+
*/
|
|
29
|
+
ingest(raw) {
|
|
30
|
+
this._state.rawInput = raw;
|
|
31
|
+
if (this._state.isStrict && typeof raw === 'string') {
|
|
32
|
+
this.parser.assertPayload(raw);
|
|
33
|
+
}
|
|
34
|
+
const parsed = this.parse(raw);
|
|
35
|
+
if (this._state.isStrict) {
|
|
36
|
+
this.parser.validate(parsed);
|
|
37
|
+
}
|
|
38
|
+
this._state.data = parsed;
|
|
39
|
+
return this;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Retrieve the original raw input data before parsing.
|
|
43
|
+
*
|
|
44
|
+
* @returns Original input passed to `from()`.
|
|
45
|
+
*/
|
|
46
|
+
getRaw() {
|
|
47
|
+
return this._state.rawInput;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Return a new instance with the given readonly state.
|
|
51
|
+
*
|
|
52
|
+
* @param isReadonly - Whether the new instance should block mutations.
|
|
53
|
+
* @returns New accessor instance with the readonly state applied.
|
|
54
|
+
*/
|
|
55
|
+
readonly(isReadonly = true) {
|
|
56
|
+
const copy = this.cloneInstance();
|
|
57
|
+
copy._state = { ...copy._state, isReadonly };
|
|
58
|
+
return copy;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Return a new instance with the given strict mode state.
|
|
62
|
+
*
|
|
63
|
+
* @param strict - Whether to enable strict security validation.
|
|
64
|
+
* @returns New accessor instance with the strict mode applied.
|
|
65
|
+
*
|
|
66
|
+
* @security Passing `false` disables all SecurityGuard and SecurityParser
|
|
67
|
+
* validation (key safety, payload size, depth and key-count limits).
|
|
68
|
+
* Only use with fully trusted, application-controlled input.
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* // Trust the input — skip all security checks
|
|
72
|
+
* const accessor = new JsonAccessor(parser).strict(false).from(trustedPayload);
|
|
73
|
+
*/
|
|
74
|
+
strict(strict = true) {
|
|
75
|
+
const copy = this.cloneInstance();
|
|
76
|
+
copy._state = { ...copy._state, isStrict: strict };
|
|
77
|
+
return copy;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Retrieve a value at a dot-notation path.
|
|
81
|
+
*
|
|
82
|
+
* @param path - Dot-notation path (e.g. "user.name").
|
|
83
|
+
* @param defaultValue - Fallback when the path does not exist.
|
|
84
|
+
* @returns Resolved value or the default.
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* accessor.get('user.name', 'unknown');
|
|
88
|
+
*/
|
|
89
|
+
get(path, defaultValue = null) {
|
|
90
|
+
return this.parser.get(this._state.data, path, defaultValue);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Retrieve a value or throw when the path does not exist.
|
|
94
|
+
*
|
|
95
|
+
* @param path - Dot-notation path.
|
|
96
|
+
* @returns Resolved value.
|
|
97
|
+
* @throws {PathNotFoundException} When the path is missing.
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* accessor.getOrFail('user.name'); // throws if not found
|
|
101
|
+
*/
|
|
102
|
+
getOrFail(path) {
|
|
103
|
+
const sentinel = Object.create(null);
|
|
104
|
+
const result = this.parser.get(this._state.data, path, sentinel);
|
|
105
|
+
if (result === sentinel) {
|
|
106
|
+
throw new PathNotFoundException(`Path '${path}' not found.`);
|
|
107
|
+
}
|
|
108
|
+
return result;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Retrieve a value using pre-parsed key segments.
|
|
112
|
+
*
|
|
113
|
+
* @param segments - Ordered list of keys.
|
|
114
|
+
* @param defaultValue - Fallback when the path does not exist.
|
|
115
|
+
* @returns Resolved value or the default.
|
|
116
|
+
*/
|
|
117
|
+
getAt(segments, defaultValue = null) {
|
|
118
|
+
return this.parser.getAt(this._state.data, segments, defaultValue);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Check whether a dot-notation path exists.
|
|
122
|
+
*
|
|
123
|
+
* @param path - Dot-notation path.
|
|
124
|
+
* @returns True if the path resolves to a value.
|
|
125
|
+
*/
|
|
126
|
+
has(path) {
|
|
127
|
+
return this.parser.has(this._state.data, path);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Check whether a path exists using pre-parsed key segments.
|
|
131
|
+
*
|
|
132
|
+
* @param segments - Ordered list of keys.
|
|
133
|
+
* @returns True if the path resolves to a value.
|
|
134
|
+
*/
|
|
135
|
+
hasAt(segments) {
|
|
136
|
+
return this.parser.hasAt(this._state.data, segments);
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Set a value at a dot-notation path.
|
|
140
|
+
*
|
|
141
|
+
* @param path - Dot-notation path.
|
|
142
|
+
* @param value - Value to assign.
|
|
143
|
+
* @returns New accessor instance with the value set.
|
|
144
|
+
* @throws {ReadonlyViolationException} When the accessor is readonly.
|
|
145
|
+
* @throws {SecurityException} When the path contains forbidden keys.
|
|
146
|
+
*/
|
|
147
|
+
set(path, value) {
|
|
148
|
+
this.assertNotReadOnly();
|
|
149
|
+
return this.mutateTo(this.parser.set(this._state.data, path, value));
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Set a value using pre-parsed key segments.
|
|
153
|
+
*
|
|
154
|
+
* @param segments - Ordered list of keys.
|
|
155
|
+
* @param value - Value to assign.
|
|
156
|
+
* @returns New accessor instance with the value set.
|
|
157
|
+
* @throws {ReadonlyViolationException} When the accessor is readonly.
|
|
158
|
+
* @throws {SecurityException} When segments contain forbidden keys.
|
|
159
|
+
*/
|
|
160
|
+
setAt(segments, value) {
|
|
161
|
+
this.assertNotReadOnly();
|
|
162
|
+
return this.mutateTo(this.parser.setAt(this._state.data, segments, value));
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Remove a value at a dot-notation path.
|
|
166
|
+
*
|
|
167
|
+
* @param path - Dot-notation path to remove.
|
|
168
|
+
* @returns New accessor instance without the specified path.
|
|
169
|
+
* @throws {ReadonlyViolationException} When the accessor is readonly.
|
|
170
|
+
* @throws {SecurityException} When the path contains forbidden keys.
|
|
171
|
+
*/
|
|
172
|
+
remove(path) {
|
|
173
|
+
this.assertNotReadOnly();
|
|
174
|
+
return this.mutateTo(this.parser.remove(this._state.data, path));
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Remove a value using pre-parsed key segments.
|
|
178
|
+
*
|
|
179
|
+
* @param segments - Ordered list of keys.
|
|
180
|
+
* @returns New accessor instance without the specified path.
|
|
181
|
+
* @throws {ReadonlyViolationException} When the accessor is readonly.
|
|
182
|
+
* @throws {SecurityException} When segments contain forbidden keys.
|
|
183
|
+
*/
|
|
184
|
+
removeAt(segments) {
|
|
185
|
+
this.assertNotReadOnly();
|
|
186
|
+
return this.mutateTo(this.parser.removeAt(this._state.data, segments));
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Retrieve multiple values by their paths with individual defaults.
|
|
190
|
+
*
|
|
191
|
+
* @param paths - Map of path to default value.
|
|
192
|
+
* @returns Map of path to resolved value.
|
|
193
|
+
*/
|
|
194
|
+
getMany(paths) {
|
|
195
|
+
const results = {};
|
|
196
|
+
for (const [path, defaultValue] of Object.entries(paths)) {
|
|
197
|
+
results[path] = this.get(path, defaultValue);
|
|
198
|
+
}
|
|
199
|
+
return results;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Return all parsed data as a plain object.
|
|
203
|
+
*
|
|
204
|
+
* @returns Complete internal data.
|
|
205
|
+
*/
|
|
206
|
+
all() {
|
|
207
|
+
return this._state.data;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Count elements at a path, or the root if undefined.
|
|
211
|
+
*
|
|
212
|
+
* @param path - Dot-notation path, or undefined for root.
|
|
213
|
+
* @returns Number of elements.
|
|
214
|
+
*/
|
|
215
|
+
count(path) {
|
|
216
|
+
const target = path !== undefined ? this.get(path, {}) : this._state.data;
|
|
217
|
+
if (typeof target === 'object' && target !== null) {
|
|
218
|
+
return Object.keys(target).length;
|
|
219
|
+
}
|
|
220
|
+
return 0;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Retrieve array keys at a path, or root keys if undefined.
|
|
224
|
+
*
|
|
225
|
+
* @param path - Dot-notation path, or undefined for root.
|
|
226
|
+
* @returns List of keys.
|
|
227
|
+
*/
|
|
228
|
+
keys(path) {
|
|
229
|
+
const target = path !== undefined ? this.get(path, {}) : this._state.data;
|
|
230
|
+
/* Stryker disable next-line ConditionalExpression -- equivalent: get() always returns an object-type value here; typeof check is a type guard only */
|
|
231
|
+
if (typeof target === 'object' && target !== null) {
|
|
232
|
+
return Object.keys(target);
|
|
233
|
+
}
|
|
234
|
+
return [];
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Deep-merge an object into the value at a dot-notation path.
|
|
238
|
+
*
|
|
239
|
+
* @param path - Dot-notation path to the merge target.
|
|
240
|
+
* @param value - Object to merge into the existing value.
|
|
241
|
+
* @returns New accessor instance with merged data.
|
|
242
|
+
* @throws {ReadonlyViolationException} When the accessor is readonly.
|
|
243
|
+
* @throws {SecurityException} When the path or values contain forbidden keys.
|
|
244
|
+
*/
|
|
245
|
+
merge(path, value) {
|
|
246
|
+
this.assertNotReadOnly();
|
|
247
|
+
return this.mutateTo(this.parser.merge(this._state.data, path, value));
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Deep-merge an object into the root data.
|
|
251
|
+
*
|
|
252
|
+
* @param value - Object to merge into the root.
|
|
253
|
+
* @returns New accessor instance with merged data.
|
|
254
|
+
* @throws {ReadonlyViolationException} When the accessor is readonly.
|
|
255
|
+
* @throws {SecurityException} When values contain forbidden keys.
|
|
256
|
+
*/
|
|
257
|
+
mergeAll(value) {
|
|
258
|
+
this.assertNotReadOnly();
|
|
259
|
+
return this.mutateTo(this.parser.merge(this._state.data, '', value));
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Assert that the accessor is not in readonly mode.
|
|
263
|
+
*
|
|
264
|
+
* @throws {ReadonlyViolationException} When the accessor is readonly.
|
|
265
|
+
*/
|
|
266
|
+
assertNotReadOnly() {
|
|
267
|
+
if (this._state.isReadonly) {
|
|
268
|
+
throw new ReadonlyViolationException();
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Create a shallow clone of this accessor.
|
|
273
|
+
*
|
|
274
|
+
* @returns Cloned accessor instance.
|
|
275
|
+
*/
|
|
276
|
+
cloneInstance() {
|
|
277
|
+
const copy = Object.create(Object.getPrototypeOf(this));
|
|
278
|
+
Object.assign(copy, this);
|
|
279
|
+
// Shallow-clone state so mutations on copy don't affect original
|
|
280
|
+
copy._state = { ...this._state };
|
|
281
|
+
return copy;
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Create a clone with new internal data.
|
|
285
|
+
*
|
|
286
|
+
* @param newData - New data for the clone.
|
|
287
|
+
* @returns Cloned accessor with updated data.
|
|
288
|
+
*/
|
|
289
|
+
mutateTo(newData) {
|
|
290
|
+
const copy = this.cloneInstance();
|
|
291
|
+
copy._state = { ...copy._state, data: newData };
|
|
292
|
+
return copy;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { AbstractAccessor } from '../abstract-accessor.js';
|
|
2
|
+
import type { ParseIntegrationInterface } from '../../contracts/parse-integration-interface.js';
|
|
3
|
+
import type { DotNotationParser } from '../../core/dot-notation-parser.js';
|
|
4
|
+
/**
|
|
5
|
+
* Accessor for arbitrary formats via a custom {@link ParseIntegrationInterface}.
|
|
6
|
+
*
|
|
7
|
+
* Delegates format detection and parsing to a user-provided integration.
|
|
8
|
+
* Validates string payloads against security constraints before parsing.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* const integration = new MyCsvIntegration();
|
|
12
|
+
* const accessor = Inline.withParserIntegration(integration).fromAny(csvString);
|
|
13
|
+
* accessor.get('0.name'); // first row, name column
|
|
14
|
+
*/
|
|
15
|
+
export declare class AnyAccessor extends AbstractAccessor {
|
|
16
|
+
private readonly integration;
|
|
17
|
+
/**
|
|
18
|
+
* @param parser - Dot-notation parser with security configuration.
|
|
19
|
+
* @param integration - Custom format parser for detecting and parsing input.
|
|
20
|
+
*/
|
|
21
|
+
constructor(parser: DotNotationParser, integration: ParseIntegrationInterface);
|
|
22
|
+
/**
|
|
23
|
+
* Hydrate from raw data via the custom integration.
|
|
24
|
+
*
|
|
25
|
+
* @param data - Raw input data in any format supported by the integration.
|
|
26
|
+
* @returns Populated accessor instance.
|
|
27
|
+
* @throws {InvalidFormatException} When the integration rejects the format.
|
|
28
|
+
* @throws {SecurityException} When string input violates payload-size limits.
|
|
29
|
+
*/
|
|
30
|
+
from(data: unknown): this;
|
|
31
|
+
/**
|
|
32
|
+
* @internal
|
|
33
|
+
*/
|
|
34
|
+
protected parse(raw: unknown): Record<string, unknown>;
|
|
35
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { AbstractAccessor } from '../abstract-accessor.js';
|
|
2
|
+
import { InvalidFormatException } from '../../exceptions/invalid-format-exception.js';
|
|
3
|
+
/**
|
|
4
|
+
* Accessor for arbitrary formats via a custom {@link ParseIntegrationInterface}.
|
|
5
|
+
*
|
|
6
|
+
* Delegates format detection and parsing to a user-provided integration.
|
|
7
|
+
* Validates string payloads against security constraints before parsing.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* const integration = new MyCsvIntegration();
|
|
11
|
+
* const accessor = Inline.withParserIntegration(integration).fromAny(csvString);
|
|
12
|
+
* accessor.get('0.name'); // first row, name column
|
|
13
|
+
*/
|
|
14
|
+
export class AnyAccessor extends AbstractAccessor {
|
|
15
|
+
integration;
|
|
16
|
+
/**
|
|
17
|
+
* @param parser - Dot-notation parser with security configuration.
|
|
18
|
+
* @param integration - Custom format parser for detecting and parsing input.
|
|
19
|
+
*/
|
|
20
|
+
constructor(parser, integration) {
|
|
21
|
+
super(parser);
|
|
22
|
+
this.integration = integration;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Hydrate from raw data via the custom integration.
|
|
26
|
+
*
|
|
27
|
+
* @param data - Raw input data in any format supported by the integration.
|
|
28
|
+
* @returns Populated accessor instance.
|
|
29
|
+
* @throws {InvalidFormatException} When the integration rejects the format.
|
|
30
|
+
* @throws {SecurityException} When string input violates payload-size limits.
|
|
31
|
+
*/
|
|
32
|
+
from(data) {
|
|
33
|
+
if (!this.integration.assertFormat(data)) {
|
|
34
|
+
throw new InvalidFormatException(`AnyAccessor failed, got ${typeof data}`);
|
|
35
|
+
}
|
|
36
|
+
return this.ingest(data);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* @internal
|
|
40
|
+
*/
|
|
41
|
+
parse(raw) {
|
|
42
|
+
return this.integration.parse(raw);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { AbstractAccessor } from '../abstract-accessor.js';
|
|
2
|
+
/**
|
|
3
|
+
* Accessor for plain objects and arrays.
|
|
4
|
+
*
|
|
5
|
+
* Accepts a plain object or array directly. No string parsing is involved.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* const accessor = new ArrayAccessor(parser).from({ key: 'value' });
|
|
9
|
+
* accessor.get('key'); // 'value'
|
|
10
|
+
*/
|
|
11
|
+
export declare class ArrayAccessor extends AbstractAccessor {
|
|
12
|
+
/**
|
|
13
|
+
* Hydrate from a plain object or array.
|
|
14
|
+
*
|
|
15
|
+
* @param data - Object or array input.
|
|
16
|
+
* @returns Populated accessor instance.
|
|
17
|
+
* @throws {InvalidFormatException} When input is neither an object nor an array.
|
|
18
|
+
* @throws {SecurityException} When data contains forbidden keys.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* accessor.from({ name: 'Alice' });
|
|
22
|
+
*/
|
|
23
|
+
from(data: unknown): this;
|
|
24
|
+
/** {@inheritDoc} */
|
|
25
|
+
protected parse(raw: unknown): Record<string, unknown>;
|
|
26
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { AbstractAccessor } from '../abstract-accessor.js';
|
|
2
|
+
import { InvalidFormatException } from '../../exceptions/invalid-format-exception.js';
|
|
3
|
+
/**
|
|
4
|
+
* Accessor for plain objects and arrays.
|
|
5
|
+
*
|
|
6
|
+
* Accepts a plain object or array directly. No string parsing is involved.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* const accessor = new ArrayAccessor(parser).from({ key: 'value' });
|
|
10
|
+
* accessor.get('key'); // 'value'
|
|
11
|
+
*/
|
|
12
|
+
export class ArrayAccessor extends AbstractAccessor {
|
|
13
|
+
/**
|
|
14
|
+
* Hydrate from a plain object or array.
|
|
15
|
+
*
|
|
16
|
+
* @param data - Object or array input.
|
|
17
|
+
* @returns Populated accessor instance.
|
|
18
|
+
* @throws {InvalidFormatException} When input is neither an object nor an array.
|
|
19
|
+
* @throws {SecurityException} When data contains forbidden keys.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* accessor.from({ name: 'Alice' });
|
|
23
|
+
*/
|
|
24
|
+
from(data) {
|
|
25
|
+
if (typeof data !== 'object' || data === null) {
|
|
26
|
+
/* Stryker disable StringLiteral -- error message content is cosmetic */
|
|
27
|
+
throw new InvalidFormatException(`ArrayAccessor expects an object or array, got ${typeof data}`);
|
|
28
|
+
/* Stryker restore StringLiteral */
|
|
29
|
+
}
|
|
30
|
+
const resolved = Array.isArray(data)
|
|
31
|
+
? Object.fromEntries(data.map((v, i) => [String(i), v]))
|
|
32
|
+
: data;
|
|
33
|
+
return this.ingest(resolved);
|
|
34
|
+
}
|
|
35
|
+
/** {@inheritDoc} */
|
|
36
|
+
parse(raw) {
|
|
37
|
+
return raw;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { AbstractAccessor } from '../abstract-accessor.js';
|
|
2
|
+
/**
|
|
3
|
+
* Accessor for dotenv-formatted strings.
|
|
4
|
+
*
|
|
5
|
+
* Parses KEY=VALUE lines, skipping comments (#) and blank lines.
|
|
6
|
+
* Strips surrounding single and double quotes from values.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* const accessor = new EnvAccessor(parser).from('DB_HOST=localhost\nDEBUG=true');
|
|
10
|
+
* accessor.get('DB_HOST'); // 'localhost'
|
|
11
|
+
*/
|
|
12
|
+
export declare class EnvAccessor extends AbstractAccessor {
|
|
13
|
+
/**
|
|
14
|
+
* Hydrate from a dotenv-formatted string.
|
|
15
|
+
*
|
|
16
|
+
* @param data - Dotenv string input.
|
|
17
|
+
* @returns Populated accessor instance.
|
|
18
|
+
* @throws {InvalidFormatException} When input is not a string.
|
|
19
|
+
* @throws {SecurityException} When payload size exceeds limit.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* accessor.from('APP_ENV=production\nPORT=3000');
|
|
23
|
+
*/
|
|
24
|
+
from(data: unknown): this;
|
|
25
|
+
/** {@inheritDoc} */
|
|
26
|
+
protected parse(raw: unknown): Record<string, unknown>;
|
|
27
|
+
}
|