@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # Change Log
2
2
 
3
- - **Last updated**: 2022-04-07T14:17:30Z
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: 922 bytes
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 const resolve: (root: any, prefix?: string) => any;
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": "5.1.5",
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.5",
38
- "@thi.ng/checks": "^3.1.5",
39
- "@thi.ng/errors": "^2.1.5",
40
- "@thi.ng/paths": "^5.1.5"
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.19.4",
44
- "@thi.ng/testament": "^0.2.5",
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.13",
48
- "typescript": "^4.6.2"
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": "5ee1feb590dd935593b1dd4e7f38a3ed3ba64765\n"
79
+ "gitHead": "0b6722006c2abbd917f115be290402c380fec87b\n"
80
80
  }