@thi.ng/resolve-map 5.1.5 → 6.1.0
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/CHANGELOG.md +20 -1
- package/README.md +11 -1
- package/index.d.ts +30 -2
- package/index.js +51 -84
- package/package.json +10 -10
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
-
- **Last updated**: 2022-
|
|
3
|
+
- **Last updated**: 2022-05-23T11:18:39Z
|
|
4
4
|
- **Generator**: [thi.ng/monopub](https://thi.ng/monopub)
|
|
5
5
|
|
|
6
6
|
All notable changes to this project will be documented in this file.
|
|
@@ -9,6 +9,25 @@ See [Conventional Commits](https://conventionalcommits.org/) for commit guidelin
|
|
|
9
9
|
**Note:** Unlisted _patch_ versions only involve non-code or otherwise excluded changes
|
|
10
10
|
and/or version bumps of transitive dependencies.
|
|
11
11
|
|
|
12
|
+
## [6.1.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/resolve-map@6.1.0) (2022-05-23)
|
|
13
|
+
|
|
14
|
+
#### 🚀 Features
|
|
15
|
+
|
|
16
|
+
- add support for protected values ([6280510](https://github.com/thi-ng/umbrella/commit/6280510))
|
|
17
|
+
- add `Resolved` wrapper & factory fn for protecting values from
|
|
18
|
+
future/duplicate resolution attempts
|
|
19
|
+
- add tests
|
|
20
|
+
- update docs/readme
|
|
21
|
+
|
|
22
|
+
# [6.0.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/resolve-map@6.0.0) (2022-05-02)
|
|
23
|
+
|
|
24
|
+
#### 🛑 Breaking changes
|
|
25
|
+
|
|
26
|
+
- add `Unresolved` type & type checking ([a997fd2](https://github.com/thi-ng/umbrella/commit/a997fd2))
|
|
27
|
+
- BREAKING CHANGE: add type checking to `resolve()`.
|
|
28
|
+
This MIGHT require additional type generics (of the result object type)
|
|
29
|
+
to be added to any call sites. See tests for examples.
|
|
30
|
+
|
|
12
31
|
### [5.1.5](https://github.com/thi-ng/umbrella/tree/@thi.ng/resolve-map@5.1.5) (2022-04-07)
|
|
13
32
|
|
|
14
33
|
#### ♻️ Refactoring
|
package/README.md
CHANGED
|
@@ -19,6 +19,7 @@ This project is part of the
|
|
|
19
19
|
- [Theme configuration](#theme-configuration)
|
|
20
20
|
- [API](#api)
|
|
21
21
|
- [`resolve(obj)`](#resolveobj)
|
|
22
|
+
- [Protecting values](#protecting-values)
|
|
22
23
|
- [Authors](#authors)
|
|
23
24
|
- [License](#license)
|
|
24
25
|
|
|
@@ -75,7 +76,7 @@ node --experimental-repl-await
|
|
|
75
76
|
> const resolveMap = await import("@thi.ng/resolve-map");
|
|
76
77
|
```
|
|
77
78
|
|
|
78
|
-
Package sizes (gzipped, pre-treeshake): ESM:
|
|
79
|
+
Package sizes (gzipped, pre-treeshake): ESM: 1.03 KB
|
|
79
80
|
|
|
80
81
|
## Dependencies
|
|
81
82
|
|
|
@@ -297,6 +298,15 @@ res.e(2);
|
|
|
297
298
|
// 20
|
|
298
299
|
```
|
|
299
300
|
|
|
301
|
+
#### Protecting values
|
|
302
|
+
|
|
303
|
+
Values can be protected from further resolution attempts by wrapping them via
|
|
304
|
+
[`resolved()`](https://docs.thi.ng/umbrella/resolve-map/modules.html#resolved).
|
|
305
|
+
The wrapped value can be later obtained via the standard [`IDeref`
|
|
306
|
+
interface/mechanism](https://docs.thi.ng/umbrella/api/interfaces/IDeref.html).
|
|
307
|
+
In lookup/resolution functions, the unwrapped value will be supplied, no
|
|
308
|
+
`.deref()` necessary there.
|
|
309
|
+
|
|
300
310
|
## Authors
|
|
301
311
|
|
|
302
312
|
Karsten Schmidt
|
package/index.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import type { NumOrString } from "@thi.ng/api";
|
|
1
|
+
import type { Fn, IDeref, NumOrString } from "@thi.ng/api";
|
|
2
|
+
export declare type Unresolved<T> = {
|
|
3
|
+
[K in keyof T]: Unresolved<T[K]> | Resolved<T[K]> | Fn<T, T[K]> | Fn<ResolveFn, T[K]> | Function | string;
|
|
4
|
+
};
|
|
2
5
|
export declare type ResolveFn = (path: string) => any;
|
|
3
6
|
export declare type LookupPath = NumOrString[];
|
|
4
7
|
/**
|
|
@@ -7,6 +10,7 @@ export declare type LookupPath = NumOrString[];
|
|
|
7
10
|
* references are not allowed and will throw an error. However, refs pointing to
|
|
8
11
|
* other refs are recursively resolved (again, provided there are no cycles).
|
|
9
12
|
*
|
|
13
|
+
* @remarks
|
|
10
14
|
* Reference values are special strings representing lookup paths of other
|
|
11
15
|
* values in the object and are prefixed with given `prefix` string (default:
|
|
12
16
|
* `@`) for relative refs or `@/` for absolute refs and both using `/` as path
|
|
@@ -15,6 +19,11 @@ export declare type LookupPath = NumOrString[];
|
|
|
15
19
|
* access any parent levels. Absolute refs are always resolved from the root
|
|
16
20
|
* level (the original object passed to this function).
|
|
17
21
|
*
|
|
22
|
+
* Values can be protected from further resolution attempts by wrapping them via
|
|
23
|
+
* {@link resolved}. The wrapped value can be later obtained via the standard
|
|
24
|
+
* {@link @thi.ng/api#IDeref} interface/mechanism. In lookup functions, the
|
|
25
|
+
* unwrapped value will be supplied, no `.deref()` necessary there.
|
|
26
|
+
*
|
|
18
27
|
* @example
|
|
19
28
|
* ```ts
|
|
20
29
|
* // `c` references sibling `d`
|
|
@@ -81,7 +90,8 @@ export declare type LookupPath = NumOrString[];
|
|
|
81
90
|
* @param root -
|
|
82
91
|
* @param prefix -
|
|
83
92
|
*/
|
|
84
|
-
export declare
|
|
93
|
+
export declare function resolve<T>(root: Unresolved<T>, prefix?: string): T;
|
|
94
|
+
export declare function resolve<T>(root: Unresolved<T[]>, prefix?: string): T[];
|
|
85
95
|
/**
|
|
86
96
|
* Takes the path for the current key and a lookup path string. Converts
|
|
87
97
|
* the possibly relative lookup path into its absolute form.
|
|
@@ -91,4 +101,22 @@ export declare const resolve: (root: any, prefix?: string) => any;
|
|
|
91
101
|
* @param idx -
|
|
92
102
|
*/
|
|
93
103
|
export declare const absPath: (curr: LookupPath, path: string, idx?: number) => NumOrString[];
|
|
104
|
+
/**
|
|
105
|
+
* Value wrapper to protect from future recursive resolution attempts. See
|
|
106
|
+
* {@link resolved} for further details.
|
|
107
|
+
*/
|
|
108
|
+
export declare class Resolved<T> implements IDeref<T> {
|
|
109
|
+
protected _value: T;
|
|
110
|
+
constructor(_value: T);
|
|
111
|
+
deref(): T;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Factory function for {@link Resolved} to wrap & protect values from further
|
|
115
|
+
* resolution attempts. The wrapped value can be later obtained via the standard
|
|
116
|
+
* {@link @thi.ng/api#IDeref} interface/mechanism. In lookup functions, the
|
|
117
|
+
* unwrapped value will be supplied, no `.deref()` necessary there.
|
|
118
|
+
*
|
|
119
|
+
* @param val
|
|
120
|
+
*/
|
|
121
|
+
export declare const resolved: <T>(val: T) => Resolved<T>;
|
|
94
122
|
//# sourceMappingURL=index.d.ts.map
|
package/index.js
CHANGED
|
@@ -4,91 +4,10 @@ import { isFunction } from "@thi.ng/checks/is-function";
|
|
|
4
4
|
import { isPlainObject } from "@thi.ng/checks/is-plain-object";
|
|
5
5
|
import { isString } from "@thi.ng/checks/is-string";
|
|
6
6
|
import { illegalArgs } from "@thi.ng/errors/illegal-arguments";
|
|
7
|
-
import { getInUnsafe } from "@thi.ng/paths/get-in";
|
|
8
7
|
import { mutInUnsafe } from "@thi.ng/paths/mut-in";
|
|
9
8
|
import { exists } from "@thi.ng/paths/path";
|
|
10
9
|
const RE_ARGS = /^(function\s+\w+)?\s*\(\{([\w\s,:]+)\}/;
|
|
11
|
-
|
|
12
|
-
* Visits all key-value pairs or array items in depth-first order, expands any
|
|
13
|
-
* reference values, mutates the original object and returns it. Cyclic
|
|
14
|
-
* references are not allowed and will throw an error. However, refs pointing to
|
|
15
|
-
* other refs are recursively resolved (again, provided there are no cycles).
|
|
16
|
-
*
|
|
17
|
-
* Reference values are special strings representing lookup paths of other
|
|
18
|
-
* values in the object and are prefixed with given `prefix` string (default:
|
|
19
|
-
* `@`) for relative refs or `@/` for absolute refs and both using `/` as path
|
|
20
|
-
* separator (Note: trailing slashes are NOT allowed!). Relative refs are
|
|
21
|
-
* resolved from the currently visited object and support "../" prefixes to
|
|
22
|
-
* access any parent levels. Absolute refs are always resolved from the root
|
|
23
|
-
* level (the original object passed to this function).
|
|
24
|
-
*
|
|
25
|
-
* @example
|
|
26
|
-
* ```ts
|
|
27
|
-
* // `c` references sibling `d`
|
|
28
|
-
* // `d` references parent `a`
|
|
29
|
-
* resolve({ a: 1, b: { c: "@d", d: "@/a" } })
|
|
30
|
-
* // { a: 1, b: { c: 1, d: 1 } }
|
|
31
|
-
* ```
|
|
32
|
-
*
|
|
33
|
-
* Any function values are called using two possible conventions:
|
|
34
|
-
*
|
|
35
|
-
* 1) If the user function uses ES6 object destructuring for its first
|
|
36
|
-
* argument, the given object keys are resolved prior to calling the
|
|
37
|
-
* function and the resolved values provided as first argument
|
|
38
|
-
* (object) and a general `resolve` function as second argument.
|
|
39
|
-
* 2) If no de-structure form is found in the function's arguments, the
|
|
40
|
-
* function is only called with `resolve` as argument.
|
|
41
|
-
*
|
|
42
|
-
* **Important:** Since ES6 var names can't contain special characters,
|
|
43
|
-
* destructured keys can ALWAYS only be looked up as siblings of the
|
|
44
|
-
* currently processed key.
|
|
45
|
-
*
|
|
46
|
-
* The `resolve` function provided as arg to the user function accepts a
|
|
47
|
-
* path (**without `@` prefix**) to look up any other values in the root
|
|
48
|
-
* object.
|
|
49
|
-
*
|
|
50
|
-
* ```
|
|
51
|
-
* // `c` uses ES6 destructuring form to look up `a` & `b` values
|
|
52
|
-
* // `d` uses provided resolve fn arg `$` to look up `c`
|
|
53
|
-
* resolve({ a: 1, b: 2, c: ({ a, b }) => a + b, d: ($) => $("c") })
|
|
54
|
-
* // { a: 1, b: 2, c: 3, d: 3 }
|
|
55
|
-
*
|
|
56
|
-
* // last item references item @ index = 2
|
|
57
|
-
* resolve([1, 2, ($) => $("0") + $("1"), "@2"])
|
|
58
|
-
* // [1, 2, 3, 3]
|
|
59
|
-
* ```
|
|
60
|
-
*
|
|
61
|
-
* The return value of the user provided function is used as final value
|
|
62
|
-
* for that key in the object. This mechanism can be used to compute
|
|
63
|
-
* derived values of other values stored anywhere in the root object.
|
|
64
|
-
* **Function values will always be called only once.** Therefore, in
|
|
65
|
-
* order to associate a function as final value to a key, it MUST be
|
|
66
|
-
* wrapped with an additional function, as shown for the `e` key in the
|
|
67
|
-
* example below. Similarly, if an actual string value should happen to
|
|
68
|
-
* start with `@`, it needs to be wrapped in a function (see `f` key
|
|
69
|
-
* below).
|
|
70
|
-
*
|
|
71
|
-
* ```
|
|
72
|
-
* // `a` is derived from 1st array element in `b.d`
|
|
73
|
-
* // `b.c` is looked up from `b.d[0]`
|
|
74
|
-
* // `b.d[1]` is derived from calling `e(2)`
|
|
75
|
-
* // `e` is a wrapped function
|
|
76
|
-
* res = resolve({
|
|
77
|
-
* a: ($) => $("b/c") * 100,
|
|
78
|
-
* b: { c: "@d/0", d: [2, ($) => $("../../e")(2) ] },
|
|
79
|
-
* e: () => (x) => x * 10,
|
|
80
|
-
* f: () => "@foo",
|
|
81
|
-
* })
|
|
82
|
-
* // { a: 200, b: { c: 2, d: [ 2, 20 ] }, e: [Function], f: "@foo" }
|
|
83
|
-
*
|
|
84
|
-
* res.e(2);
|
|
85
|
-
* // 20
|
|
86
|
-
* ```
|
|
87
|
-
*
|
|
88
|
-
* @param root -
|
|
89
|
-
* @param prefix -
|
|
90
|
-
*/
|
|
91
|
-
export const resolve = (root, prefix = "@") => {
|
|
10
|
+
export function resolve(root, prefix = "@") {
|
|
92
11
|
if (isPlainObject(root)) {
|
|
93
12
|
return resolveMap(root, prefix);
|
|
94
13
|
}
|
|
@@ -96,7 +15,7 @@ export const resolve = (root, prefix = "@") => {
|
|
|
96
15
|
return resolveArray(root, prefix);
|
|
97
16
|
}
|
|
98
17
|
return root;
|
|
99
|
-
}
|
|
18
|
+
}
|
|
100
19
|
const resolveMap = (obj, prefix, root, path = [], resolved = {}, stack = []) => {
|
|
101
20
|
root = root || obj;
|
|
102
21
|
for (let k in obj) {
|
|
@@ -128,8 +47,12 @@ const _resolve = (root, path, resolved, stack, prefix) => {
|
|
|
128
47
|
illegalArgs(`cyclic references not allowed: ${pathID}`);
|
|
129
48
|
}
|
|
130
49
|
// console.log(pp, resolved[pp], stack);
|
|
131
|
-
let v = getInUnsafe(root, path);
|
|
50
|
+
let [v, isResolved] = getInUnsafe(root, path);
|
|
132
51
|
if (!resolved[pathID]) {
|
|
52
|
+
if (isResolved) {
|
|
53
|
+
resolved[pathID] = true;
|
|
54
|
+
return v;
|
|
55
|
+
}
|
|
133
56
|
let res = SEMAPHORE;
|
|
134
57
|
stack.push(pathID);
|
|
135
58
|
if (isPlainObject(v)) {
|
|
@@ -270,3 +193,47 @@ export const absPath = (curr, path, idx = 1) => {
|
|
|
270
193
|
!curr.length && illegalArgs(`invalid lookup path: ${path}`);
|
|
271
194
|
return curr;
|
|
272
195
|
};
|
|
196
|
+
/**
|
|
197
|
+
* Value wrapper to protect from future recursive resolution attempts. See
|
|
198
|
+
* {@link resolved} for further details.
|
|
199
|
+
*/
|
|
200
|
+
export class Resolved {
|
|
201
|
+
constructor(_value) {
|
|
202
|
+
this._value = _value;
|
|
203
|
+
}
|
|
204
|
+
deref() {
|
|
205
|
+
return this._value;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Factory function for {@link Resolved} to wrap & protect values from further
|
|
210
|
+
* resolution attempts. The wrapped value can be later obtained via the standard
|
|
211
|
+
* {@link @thi.ng/api#IDeref} interface/mechanism. In lookup functions, the
|
|
212
|
+
* unwrapped value will be supplied, no `.deref()` necessary there.
|
|
213
|
+
*
|
|
214
|
+
* @param val
|
|
215
|
+
*/
|
|
216
|
+
export const resolved = (val) => new Resolved(val);
|
|
217
|
+
/**
|
|
218
|
+
* Special version of {@link @thi.ng/paths#getInUnsafe} with extra support for
|
|
219
|
+
* intermediate wrapped {@link Resolved} values and returning tuple of:
|
|
220
|
+
* `[val,isResolved]`.
|
|
221
|
+
*
|
|
222
|
+
* @param obj
|
|
223
|
+
* @param path
|
|
224
|
+
*
|
|
225
|
+
* @internal
|
|
226
|
+
*/
|
|
227
|
+
const getInUnsafe = (obj, path) => {
|
|
228
|
+
const n = path.length - 1;
|
|
229
|
+
let res = obj;
|
|
230
|
+
let isResolved = obj instanceof Resolved;
|
|
231
|
+
for (let i = 0; res != null && i <= n; i++) {
|
|
232
|
+
res = res[path[i]];
|
|
233
|
+
if (res instanceof Resolved) {
|
|
234
|
+
isResolved = true;
|
|
235
|
+
res = res.deref();
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return [res, isResolved];
|
|
239
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thi.ng/resolve-map",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.1.0",
|
|
4
4
|
"description": "DAG resolution of vanilla objects & arrays with internally linked values",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "./index.js",
|
|
@@ -34,18 +34,18 @@
|
|
|
34
34
|
"test": "testament test"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@thi.ng/api": "^8.3.
|
|
38
|
-
"@thi.ng/checks": "^3.1.
|
|
39
|
-
"@thi.ng/errors": "^2.1.
|
|
40
|
-
"@thi.ng/paths": "^5.1.
|
|
37
|
+
"@thi.ng/api": "^8.3.6",
|
|
38
|
+
"@thi.ng/checks": "^3.1.6",
|
|
39
|
+
"@thi.ng/errors": "^2.1.6",
|
|
40
|
+
"@thi.ng/paths": "^5.1.6"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
|
-
"@microsoft/api-extractor": "^7.
|
|
44
|
-
"@thi.ng/testament": "^0.2.
|
|
43
|
+
"@microsoft/api-extractor": "^7.23.1",
|
|
44
|
+
"@thi.ng/testament": "^0.2.7",
|
|
45
45
|
"rimraf": "^3.0.2",
|
|
46
46
|
"tools": "^0.0.1",
|
|
47
|
-
"typedoc": "^0.22.
|
|
48
|
-
"typescript": "^4.6.
|
|
47
|
+
"typedoc": "^0.22.15",
|
|
48
|
+
"typescript": "^4.6.4"
|
|
49
49
|
},
|
|
50
50
|
"keywords": [
|
|
51
51
|
"configuration",
|
|
@@ -76,5 +76,5 @@
|
|
|
76
76
|
],
|
|
77
77
|
"year": 2018
|
|
78
78
|
},
|
|
79
|
-
"gitHead": "
|
|
79
|
+
"gitHead": "0b6722006c2abbd917f115be290402c380fec87b\n"
|
|
80
80
|
}
|