@weborigami/async-tree 0.5.7 → 0.6.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/browser.js +1 -1
- package/index.ts +31 -35
- package/main.js +1 -2
- package/package.json +4 -7
- package/shared.js +77 -12
- package/src/Tree.js +8 -2
- package/src/drivers/AsyncMap.js +210 -0
- package/src/drivers/{BrowserFileTree.js → BrowserFileMap.js} +36 -27
- package/src/drivers/{calendarTree.js → CalendarMap.js} +81 -62
- package/src/drivers/ConstantMap.js +30 -0
- package/src/drivers/DeepObjectMap.js +27 -0
- package/src/drivers/{ExplorableSiteTree.js → ExplorableSiteMap.js} +7 -7
- package/src/drivers/FileMap.js +245 -0
- package/src/drivers/{FunctionTree.js → FunctionMap.js} +19 -22
- package/src/drivers/ObjectMap.js +139 -0
- package/src/drivers/SetMap.js +13 -0
- package/src/drivers/{SiteTree.js → SiteMap.js} +16 -17
- package/src/drivers/SyncMap.js +245 -0
- package/src/jsonKeys.d.ts +2 -2
- package/src/jsonKeys.js +6 -5
- package/src/operations/addNextPrevious.js +35 -36
- package/src/operations/assign.js +30 -21
- package/src/operations/cache.js +29 -35
- package/src/operations/cachedKeyFunctions.js +1 -1
- package/src/operations/calendar.js +5 -0
- package/src/operations/clear.js +13 -12
- package/src/operations/constant.js +5 -0
- package/src/operations/deepEntries.js +23 -0
- package/src/operations/deepMap.js +9 -9
- package/src/operations/deepMerge.js +36 -25
- package/src/operations/deepReverse.js +23 -16
- package/src/operations/deepTake.js +7 -7
- package/src/operations/deepText.js +4 -4
- package/src/operations/deepValues.js +3 -6
- package/src/operations/deepValuesIterator.js +11 -11
- package/src/operations/delete.js +8 -12
- package/src/operations/entries.js +17 -10
- package/src/operations/filter.js +9 -7
- package/src/operations/first.js +12 -10
- package/src/operations/forEach.js +10 -13
- package/src/operations/from.js +31 -39
- package/src/operations/globKeys.js +22 -17
- package/src/operations/group.js +2 -2
- package/src/operations/groupBy.js +24 -22
- package/src/operations/has.js +7 -9
- package/src/operations/indent.js +2 -2
- package/src/operations/inners.js +19 -15
- package/src/operations/invokeFunctions.js +22 -10
- package/src/operations/isAsyncMutableTree.js +5 -12
- package/src/operations/isAsyncTree.js +5 -20
- package/src/operations/isMap.js +39 -0
- package/src/operations/isMaplike.js +34 -0
- package/src/operations/isReadOnlyMap.js +14 -0
- package/src/operations/isTraversable.js +3 -3
- package/src/operations/isTreelike.js +5 -30
- package/src/operations/json.js +4 -12
- package/src/operations/keys.js +17 -8
- package/src/operations/length.js +9 -8
- package/src/operations/map.js +27 -30
- package/src/operations/mapExtension.js +20 -16
- package/src/operations/mapReduce.js +22 -17
- package/src/operations/mask.js +31 -22
- package/src/operations/match.js +13 -9
- package/src/operations/merge.js +43 -35
- package/src/operations/paginate.js +26 -18
- package/src/operations/parent.js +7 -7
- package/src/operations/paths.js +8 -8
- package/src/operations/plain.js +6 -6
- package/src/operations/regExpKeys.js +21 -12
- package/src/operations/reverse.js +21 -15
- package/src/operations/root.js +6 -5
- package/src/operations/scope.js +31 -26
- package/src/operations/shuffle.js +23 -16
- package/src/operations/size.js +13 -0
- package/src/operations/sort.js +55 -40
- package/src/operations/sync.js +21 -0
- package/src/operations/take.js +23 -11
- package/src/operations/text.js +4 -4
- package/src/operations/toFunction.js +7 -7
- package/src/operations/traverse.js +4 -4
- package/src/operations/traverseOrThrow.js +13 -9
- package/src/operations/traversePath.js +2 -2
- package/src/operations/values.js +18 -9
- package/src/operations/withKeys.js +22 -16
- package/src/symbols.js +1 -0
- package/src/utilities/castArraylike.js +10 -2
- package/src/utilities/getMapArgument.js +38 -0
- package/src/utilities/getParent.js +2 -2
- package/src/utilities/isStringlike.js +7 -5
- package/src/utilities/setParent.js +7 -7
- package/src/utilities/toFunction.js +2 -2
- package/src/utilities/toPlainValue.js +22 -18
- package/test/SampleAsyncMap.js +34 -0
- package/test/browser/assert.js +20 -0
- package/test/browser/index.html +54 -21
- package/test/drivers/AsyncMap.test.js +119 -0
- package/test/drivers/{BrowserFileTree.test.js → BrowserFileMap.test.js} +42 -23
- package/test/drivers/{calendarTree.test.js → CalendarMap.test.js} +17 -19
- package/test/drivers/ConstantMap.test.js +15 -0
- package/test/drivers/DeepObjectMap.test.js +36 -0
- package/test/drivers/{ExplorableSiteTree.test.js → ExplorableSiteMap.test.js} +29 -14
- package/test/drivers/FileMap.test.js +185 -0
- package/test/drivers/FunctionMap.test.js +56 -0
- package/test/drivers/ObjectMap.test.js +166 -0
- package/test/drivers/SetMap.test.js +35 -0
- package/test/drivers/{SiteTree.test.js → SiteMap.test.js} +14 -10
- package/test/drivers/SyncMap.test.js +321 -0
- package/test/jsonKeys.test.js +2 -2
- package/test/operations/addNextPrevious.test.js +3 -2
- package/test/operations/assign.test.js +30 -35
- package/test/operations/cache.test.js +8 -6
- package/test/operations/cachedKeyFunctions.test.js +6 -5
- package/test/operations/clear.test.js +6 -27
- package/test/operations/deepEntries.test.js +32 -0
- package/test/operations/deepMerge.test.js +6 -5
- package/test/operations/deepReverse.test.js +2 -2
- package/test/operations/deepTake.test.js +2 -2
- package/test/operations/deepText.test.js +4 -4
- package/test/operations/deepValuesIterator.test.js +2 -2
- package/test/operations/delete.test.js +2 -2
- package/test/operations/extensionKeyFunctions.test.js +6 -5
- package/test/operations/filter.test.js +3 -3
- package/test/operations/from.test.js +23 -31
- package/test/operations/globKeys.test.js +9 -9
- package/test/operations/groupBy.test.js +6 -5
- package/test/operations/inners.test.js +4 -4
- package/test/operations/invokeFunctions.test.js +2 -2
- package/test/operations/isMap.test.js +15 -0
- package/test/operations/isMaplike.test.js +15 -0
- package/test/operations/json.test.js +2 -2
- package/test/operations/keys.test.js +16 -3
- package/test/operations/map.test.js +20 -18
- package/test/operations/mapExtension.test.js +6 -6
- package/test/operations/mapReduce.test.js +2 -2
- package/test/operations/mask.test.js +4 -3
- package/test/operations/match.test.js +2 -2
- package/test/operations/merge.test.js +15 -11
- package/test/operations/paginate.test.js +5 -5
- package/test/operations/parent.test.js +3 -3
- package/test/operations/paths.test.js +6 -6
- package/test/operations/plain.test.js +8 -8
- package/test/operations/regExpKeys.test.js +12 -11
- package/test/operations/reverse.test.js +4 -3
- package/test/operations/scope.test.js +6 -5
- package/test/operations/shuffle.test.js +3 -2
- package/test/operations/sort.test.js +7 -10
- package/test/operations/sync.test.js +43 -0
- package/test/operations/take.test.js +2 -2
- package/test/operations/toFunction.test.js +2 -2
- package/test/operations/traverse.test.js +4 -5
- package/test/operations/withKeys.test.js +2 -2
- package/test/utilities/setParent.test.js +6 -6
- package/test/utilities/toFunction.test.js +2 -2
- package/test/utilities/toPlainValue.test.js +51 -12
- package/src/drivers/DeepMapTree.js +0 -23
- package/src/drivers/DeepObjectTree.js +0 -18
- package/src/drivers/DeferredTree.js +0 -81
- package/src/drivers/FileTree.js +0 -276
- package/src/drivers/MapTree.js +0 -70
- package/src/drivers/ObjectTree.js +0 -158
- package/src/drivers/SetTree.js +0 -34
- package/src/drivers/constantTree.js +0 -19
- package/src/drivers/limitConcurrency.js +0 -63
- package/src/internal.js +0 -16
- package/src/utilities/getTreeArgument.js +0 -43
- package/test/drivers/DeepMapTree.test.js +0 -17
- package/test/drivers/DeepObjectTree.test.js +0 -35
- package/test/drivers/DeferredTree.test.js +0 -22
- package/test/drivers/FileTree.test.js +0 -192
- package/test/drivers/FunctionTree.test.js +0 -46
- package/test/drivers/MapTree.test.js +0 -59
- package/test/drivers/ObjectTree.test.js +0 -163
- package/test/drivers/SetTree.test.js +0 -44
- package/test/drivers/constantTree.test.js +0 -13
- package/test/drivers/limitConcurrency.test.js +0 -41
- package/test/operations/isAsyncMutableTree.test.js +0 -17
- package/test/operations/isAsyncTree.test.js +0 -26
- package/test/operations/isTreelike.test.js +0 -13
package/src/operations/json.js
CHANGED
|
@@ -1,21 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
import isUnpackable from "../utilities/isUnpackable.js";
|
|
1
|
+
import getMapArgument from "../utilities/getMapArgument.js";
|
|
3
2
|
import toPlainValue from "../utilities/toPlainValue.js";
|
|
4
|
-
import from from "./from.js";
|
|
5
3
|
|
|
6
4
|
/**
|
|
7
5
|
* Render the given tree in JSON format.
|
|
8
6
|
*
|
|
9
|
-
* @param {import("../../index.ts").
|
|
7
|
+
* @param {import("../../index.ts").Maplike} maplike
|
|
10
8
|
*/
|
|
11
|
-
export default async function json(
|
|
12
|
-
|
|
13
|
-
if (tree === undefined) {
|
|
14
|
-
return undefined;
|
|
15
|
-
}
|
|
16
|
-
if (isUnpackable(tree)) {
|
|
17
|
-
tree = await tree.unpack();
|
|
18
|
-
}
|
|
9
|
+
export default async function json(maplike) {
|
|
10
|
+
const tree = await getMapArgument(maplike, "json");
|
|
19
11
|
const value = await toPlainValue(tree);
|
|
20
12
|
return JSON.stringify(value, null, 2);
|
|
21
13
|
}
|
package/src/operations/keys.js
CHANGED
|
@@ -1,14 +1,23 @@
|
|
|
1
|
-
import
|
|
1
|
+
import getMapArgument from "../utilities/getMapArgument.js";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Return the
|
|
4
|
+
* Return the keys of the map.
|
|
5
5
|
*
|
|
6
|
-
* @typedef {import("../../index.ts").
|
|
6
|
+
* @typedef {import("../../index.ts").Maplike} Maplike
|
|
7
7
|
*
|
|
8
|
-
* @param {
|
|
8
|
+
* @param {Maplike} maplike
|
|
9
9
|
*/
|
|
10
|
-
export default async function keys(
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
export default async function keys(maplike) {
|
|
11
|
+
const map = await getMapArgument(maplike, "keys");
|
|
12
|
+
let keys;
|
|
13
|
+
let iterable = map.keys();
|
|
14
|
+
if (Symbol.asyncIterator in iterable) {
|
|
15
|
+
keys = [];
|
|
16
|
+
for await (const key of iterable) {
|
|
17
|
+
keys.push(key);
|
|
18
|
+
}
|
|
19
|
+
} else {
|
|
20
|
+
keys = Array.from(iterable);
|
|
21
|
+
}
|
|
22
|
+
return keys;
|
|
14
23
|
}
|
package/src/operations/length.js
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
import
|
|
1
|
+
import getMapArgument from "../utilities/getMapArgument.js";
|
|
2
|
+
import keys from "./keys.js";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Return the number of keys in the tree.
|
|
5
6
|
*
|
|
6
|
-
* @typedef {import("
|
|
7
|
-
* @typedef {import("../../index.ts").Treelike} Treelike
|
|
7
|
+
* @typedef {import("../../index.ts").Maplike} Maplike
|
|
8
8
|
*
|
|
9
|
-
* @param {
|
|
9
|
+
* @param {Maplike} maplike
|
|
10
10
|
*/
|
|
11
|
-
export default async function length(
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
|
|
11
|
+
export default async function length(maplike) {
|
|
12
|
+
console.warn("Tree.length() is deprecated. Use Tree.size() instead.");
|
|
13
|
+
const tree = await getMapArgument(maplike, "length");
|
|
14
|
+
const treeKeys = await keys(tree);
|
|
15
|
+
return treeKeys.length;
|
|
15
16
|
}
|
package/src/operations/map.js
CHANGED
|
@@ -1,33 +1,34 @@
|
|
|
1
|
+
import AsyncMap from "../drivers/AsyncMap.js";
|
|
1
2
|
import * as trailingSlash from "../trailingSlash.js";
|
|
2
|
-
import
|
|
3
|
+
import getMapArgument from "../utilities/getMapArgument.js";
|
|
3
4
|
import isPlainObject from "../utilities/isPlainObject.js";
|
|
4
5
|
import isUnpackable from "../utilities/isUnpackable.js";
|
|
5
6
|
import toFunction from "../utilities/toFunction.js";
|
|
6
7
|
import cachedKeyFunctions from "./cachedKeyFunctions.js";
|
|
7
8
|
import extensionKeyFunctions from "./extensionKeyFunctions.js";
|
|
8
|
-
import
|
|
9
|
+
import isMap from "./isMap.js";
|
|
10
|
+
import keys from "./keys.js";
|
|
9
11
|
import parseExtensions from "./parseExtensions.js";
|
|
10
12
|
|
|
11
13
|
/**
|
|
12
14
|
* Transform the keys and/or values of a tree.
|
|
13
15
|
*
|
|
14
16
|
* @typedef {import("../../index.ts").KeyFn} KeyFn
|
|
15
|
-
* @typedef {import("../../index.ts").
|
|
17
|
+
* @typedef {import("../../index.ts").MapOptions} MapOptions
|
|
16
18
|
* @typedef {import("../../index.ts").ValueKeyFn} ValueKeyFn
|
|
17
|
-
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
18
19
|
*
|
|
19
|
-
* @param {import("../../index.ts").
|
|
20
|
+
* @param {import("../../index.ts").Maplike} maplike
|
|
20
21
|
* @param {MapOptions|ValueKeyFn} options
|
|
21
|
-
* @returns {Promise<
|
|
22
|
+
* @returns {Promise<AsyncMap>}
|
|
22
23
|
*/
|
|
23
|
-
export default async function map(
|
|
24
|
+
export default async function map(maplike, options = {}) {
|
|
24
25
|
if (isUnpackable(options)) {
|
|
25
26
|
options = await options.unpack();
|
|
26
27
|
}
|
|
27
28
|
const validated = validateOptions(options);
|
|
28
29
|
const mapFn = createMapFn(validated);
|
|
29
30
|
|
|
30
|
-
const tree = await
|
|
31
|
+
const tree = await getMapArgument(maplike, "map", { deep: validated.deep });
|
|
31
32
|
return mapFn(tree);
|
|
32
33
|
}
|
|
33
34
|
|
|
@@ -47,9 +48,7 @@ function createGet(tree, options, mapFn) {
|
|
|
47
48
|
// Special case: deep tree and value is expected to be a subtree
|
|
48
49
|
const sourceValue = await tree.get(resultKey);
|
|
49
50
|
// If we did get a subtree, apply the map to it
|
|
50
|
-
const resultValue =
|
|
51
|
-
? mapFn(sourceValue)
|
|
52
|
-
: undefined;
|
|
51
|
+
const resultValue = isMap(sourceValue) ? mapFn(sourceValue) : undefined;
|
|
53
52
|
return resultValue;
|
|
54
53
|
} else {
|
|
55
54
|
// No inverseKeyFn, or it returned undefined; use resultKey
|
|
@@ -69,7 +68,7 @@ function createGet(tree, options, mapFn) {
|
|
|
69
68
|
if (sourceValue === undefined) {
|
|
70
69
|
// No source value means no result value
|
|
71
70
|
resultValue = undefined;
|
|
72
|
-
} else if (deep &&
|
|
71
|
+
} else if (deep && isMap(sourceValue)) {
|
|
73
72
|
// We weren't expecting a subtree but got one; map it
|
|
74
73
|
resultValue = mapFn(sourceValue);
|
|
75
74
|
} else if (valueFn) {
|
|
@@ -87,9 +86,14 @@ function createGet(tree, options, mapFn) {
|
|
|
87
86
|
// Create a keys() function for the map
|
|
88
87
|
function createKeys(tree, options) {
|
|
89
88
|
const { deep, keyFn, keyNeedsSourceValue } = options;
|
|
90
|
-
return async ()
|
|
89
|
+
return async function* () {
|
|
91
90
|
// Apply the keyFn to source keys for leaf values (not subtrees).
|
|
92
|
-
const sourceKeys =
|
|
91
|
+
const sourceKeys = await keys(tree);
|
|
92
|
+
if (!keyFn) {
|
|
93
|
+
// Return keys as is
|
|
94
|
+
yield* sourceKeys;
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
93
97
|
const sourceValues = keyNeedsSourceValue
|
|
94
98
|
? await Promise.all(sourceKeys.map((sourceKey) => tree.get(sourceKey)))
|
|
95
99
|
: sourceKeys.map(() => null);
|
|
@@ -103,30 +107,23 @@ function createKeys(tree, options) {
|
|
|
103
107
|
);
|
|
104
108
|
// Filter out any cases where the keyFn returned undefined.
|
|
105
109
|
const resultKeys = mapped.filter((key) => key !== undefined);
|
|
106
|
-
|
|
110
|
+
yield* resultKeys;
|
|
107
111
|
};
|
|
108
112
|
}
|
|
109
113
|
|
|
110
114
|
// Create a map function for the given options
|
|
111
115
|
function createMapFn(options) {
|
|
112
|
-
const { description, keyFn, valueFn } = options;
|
|
113
116
|
/**
|
|
114
|
-
* @param {
|
|
115
|
-
* @return {
|
|
117
|
+
* @param {Map|AsyncMap} tree
|
|
118
|
+
* @return {AsyncMap}
|
|
116
119
|
*/
|
|
117
120
|
return function mapFn(tree) {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
transformed.
|
|
124
|
-
if (keyFn || valueFn) {
|
|
125
|
-
transformed.get = createGet(tree, options, mapFn);
|
|
126
|
-
}
|
|
127
|
-
if (keyFn) {
|
|
128
|
-
transformed.keys = createKeys(tree, options);
|
|
129
|
-
}
|
|
121
|
+
/** @type {any} */
|
|
122
|
+
const transformed = new AsyncMap();
|
|
123
|
+
transformed.description = options.description;
|
|
124
|
+
transformed.source = tree;
|
|
125
|
+
transformed.get = createGet(tree, options, mapFn);
|
|
126
|
+
transformed.keys = createKeys(tree, options);
|
|
130
127
|
return transformed;
|
|
131
128
|
};
|
|
132
129
|
}
|
|
@@ -3,48 +3,48 @@ import isUnpackable from "../utilities/isUnpackable.js";
|
|
|
3
3
|
import map from "./map.js";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* @typedef {import("../../index.ts").
|
|
7
|
-
* @typedef {import("../../index.ts").
|
|
6
|
+
* @typedef {import("../../index.ts").AsyncMap} AsyncMap
|
|
7
|
+
* @typedef {import("../../index.ts").MapExtensionOptions} MapExtensionOptions
|
|
8
|
+
* @typedef {import("../../index.ts").Maplike} Maplike
|
|
8
9
|
* @typedef {import("../../index.ts").ValueKeyFn} ValueKeyFn
|
|
9
|
-
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* @overload
|
|
14
|
-
* @param {
|
|
14
|
+
* @param {Maplike} maplike
|
|
15
15
|
* @param {string} extension
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* @overload
|
|
20
|
-
* @param {
|
|
21
|
-
* @param {
|
|
20
|
+
* @param {Maplike} maplike
|
|
21
|
+
* @param {MapExtensionOptions} options
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* @overload
|
|
26
|
-
* @param {
|
|
26
|
+
* @param {Maplike} maplike
|
|
27
27
|
* @param {string} extension
|
|
28
28
|
* @param {ValueKeyFn} fn
|
|
29
29
|
*/
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
32
|
* @overload
|
|
33
|
-
* @param {
|
|
33
|
+
* @param {Maplike} maplike
|
|
34
34
|
* @param {string} extension
|
|
35
|
-
* @param {
|
|
35
|
+
* @param {MapExtensionOptions} options
|
|
36
36
|
*/
|
|
37
37
|
|
|
38
38
|
/**
|
|
39
39
|
* Shorthand for calling `map` with the `deep: true` option.
|
|
40
40
|
*
|
|
41
|
-
* @param {
|
|
42
|
-
* @param {string|
|
|
43
|
-
* @param {ValueKeyFn|
|
|
44
|
-
* @returns {Promise<
|
|
41
|
+
* @param {Maplike} maplike
|
|
42
|
+
* @param {string|MapExtensionOptions} arg2
|
|
43
|
+
* @param {ValueKeyFn|MapExtensionOptions} [arg3]
|
|
44
|
+
* @returns {Promise<AsyncMap>}
|
|
45
45
|
*/
|
|
46
|
-
export default async function mapExtension(
|
|
47
|
-
/** @type {
|
|
46
|
+
export default async function mapExtension(maplike, arg2, arg3) {
|
|
47
|
+
/** @type {MapExtensionOptions} */
|
|
48
48
|
// @ts-ignore
|
|
49
49
|
let options = { _noExtensionWarning: true };
|
|
50
50
|
if (arg3 === undefined) {
|
|
@@ -78,5 +78,9 @@ export default async function mapExtension(treelike, arg2, arg3) {
|
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
|
|
81
|
+
if (!options.description) {
|
|
82
|
+
options.description = `mapExtension ${options.extension}`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return map(maplike, options);
|
|
82
86
|
}
|
|
@@ -1,44 +1,49 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import getMapArgument from "../utilities/getMapArgument.js";
|
|
2
|
+
import isMap from "./isMap.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Map and reduce a tree.
|
|
6
6
|
*
|
|
7
7
|
* This is done in as parallel fashion as possible. Each of the tree's values
|
|
8
|
-
* will be requested in an
|
|
8
|
+
* will be requested in an asynchronous call, then those results will be awaited
|
|
9
9
|
* collectively. If a mapFn is provided, it will be invoked to convert each
|
|
10
10
|
* value to a mapped value; otherwise, values will be used as is. When the
|
|
11
11
|
* values have been obtained, all the values and keys will be passed to the
|
|
12
12
|
* reduceFn, which should consolidate those into a single result.
|
|
13
13
|
*
|
|
14
|
-
* @typedef {import("../../index.ts").
|
|
14
|
+
* @typedef {import("../../index.ts").Maplike} Maplike
|
|
15
15
|
* @typedef {import("../../index.ts").ReduceFn} ReduceFn
|
|
16
16
|
* @typedef {import("../../index.ts").ValueKeyFn} ValueKeyFn
|
|
17
17
|
*
|
|
18
|
-
* @param {
|
|
18
|
+
* @param {Maplike} maplike
|
|
19
19
|
* @param {ValueKeyFn|null} mapFn
|
|
20
20
|
* @param {ReduceFn} reduceFn
|
|
21
21
|
*/
|
|
22
|
-
export default async function mapReduce(
|
|
23
|
-
const
|
|
22
|
+
export default async function mapReduce(maplike, mapFn, reduceFn) {
|
|
23
|
+
const map = await getMapArgument(maplike, "mapReduce");
|
|
24
24
|
|
|
25
25
|
// We're going to fire off all the get requests in parallel, as quickly as
|
|
26
26
|
// the keys come in. We call the tree's `get` method for each key, but
|
|
27
27
|
// *don't* wait for it yet.
|
|
28
|
-
const
|
|
29
|
-
const promises =
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
28
|
+
const treeKeys = [];
|
|
29
|
+
const promises = [];
|
|
30
|
+
for await (const key of map.keys()) {
|
|
31
|
+
treeKeys.push(key);
|
|
32
|
+
const promise = (async () => {
|
|
33
|
+
const value = await map.get(key);
|
|
34
|
+
return isMap(value)
|
|
35
|
+
? mapReduce(value, mapFn, reduceFn) // subtree; recurse
|
|
36
|
+
: mapFn
|
|
37
|
+
? mapFn(value, key, map)
|
|
38
|
+
: value;
|
|
39
|
+
})();
|
|
40
|
+
promises.push(promise);
|
|
41
|
+
}
|
|
37
42
|
|
|
38
43
|
// Wait for all the promises to resolve. Because the promises were captured
|
|
39
44
|
// in the same order as the keys, the values will also be in the same order.
|
|
40
45
|
const values = await Promise.all(promises);
|
|
41
46
|
|
|
42
47
|
// Reduce the values to a single result.
|
|
43
|
-
return reduceFn(values,
|
|
48
|
+
return reduceFn(values, treeKeys, map);
|
|
44
49
|
}
|
package/src/operations/mask.js
CHANGED
|
@@ -1,36 +1,39 @@
|
|
|
1
|
+
import AsyncMap from "../drivers/AsyncMap.js";
|
|
1
2
|
import * as trailingSlash from "../trailingSlash.js";
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
3
|
+
import getMapArgument from "../utilities/getMapArgument.js";
|
|
4
|
+
import isMap from "./isMap.js";
|
|
5
|
+
import isMaplike from "./isMaplike.js";
|
|
6
|
+
import keys from "./keys.js";
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* Given trees `a` and `b`, return a masked version of `a` where only the keys
|
|
8
10
|
* that exist in `b` and have truthy values are kept. The filter operation is
|
|
9
11
|
* deep: if a value from `a` is a subtree, it will be filtered recursively.
|
|
10
12
|
*
|
|
11
|
-
* @typedef {import("
|
|
12
|
-
* @typedef {import("../../index.ts").Treelike} Treelike
|
|
13
|
+
* @typedef {import("../../index.ts").Maplike} Maplike
|
|
13
14
|
*
|
|
14
|
-
* @param {
|
|
15
|
-
* @param {
|
|
16
|
-
* @returns {Promise<
|
|
15
|
+
* @param {Maplike} aMaplike
|
|
16
|
+
* @param {Maplike} bMaplike
|
|
17
|
+
* @returns {Promise<AsyncMap>}
|
|
17
18
|
*/
|
|
18
|
-
export default async function mask(
|
|
19
|
-
const
|
|
20
|
-
const
|
|
19
|
+
export default async function mask(aMaplike, bMaplike) {
|
|
20
|
+
const aMap = await getMapArgument(aMaplike, "filter", { position: 0 });
|
|
21
|
+
const bMap = await getMapArgument(bMaplike, "filter", {
|
|
21
22
|
deep: true,
|
|
22
23
|
position: 1,
|
|
23
24
|
});
|
|
24
25
|
|
|
25
|
-
return {
|
|
26
|
+
return Object.assign(new AsyncMap(), {
|
|
27
|
+
description: "mask",
|
|
28
|
+
|
|
26
29
|
async get(key) {
|
|
27
30
|
// The key must exist in b and return a truthy value
|
|
28
|
-
const bValue = await
|
|
31
|
+
const bValue = await bMap.get(key);
|
|
29
32
|
if (!bValue) {
|
|
30
33
|
return undefined;
|
|
31
34
|
}
|
|
32
|
-
let aValue = await
|
|
33
|
-
if (
|
|
35
|
+
let aValue = await aMap.get(key);
|
|
36
|
+
if (isMaplike(aValue)) {
|
|
34
37
|
// Filter the subtree
|
|
35
38
|
return mask(aValue, bValue);
|
|
36
39
|
} else {
|
|
@@ -38,20 +41,26 @@ export default async function mask(aTreelike, bTreelike) {
|
|
|
38
41
|
}
|
|
39
42
|
},
|
|
40
43
|
|
|
41
|
-
async keys() {
|
|
44
|
+
async *keys() {
|
|
42
45
|
// Use a's keys as the basis
|
|
43
|
-
const aKeys =
|
|
44
|
-
const bValues = await Promise.all(aKeys.map((key) =>
|
|
46
|
+
const aKeys = await keys(aMap);
|
|
47
|
+
const bValues = await Promise.all(aKeys.map((key) => bMap.get(key)));
|
|
45
48
|
// An async tree value in b implies that the a key should have a slash
|
|
46
49
|
const aKeySlashes = aKeys.map((key, index) =>
|
|
47
50
|
trailingSlash.toggle(
|
|
48
51
|
key,
|
|
49
|
-
trailingSlash.has(key) ||
|
|
52
|
+
trailingSlash.has(key) || isMap(bValues[index])
|
|
50
53
|
)
|
|
51
54
|
);
|
|
52
55
|
// Remove keys that don't have values in b
|
|
53
|
-
const
|
|
54
|
-
|
|
56
|
+
const treeKeys = aKeySlashes.filter(
|
|
57
|
+
(key, index) => bValues[index] ?? false
|
|
58
|
+
);
|
|
59
|
+
yield* treeKeys;
|
|
55
60
|
},
|
|
56
|
-
|
|
61
|
+
|
|
62
|
+
source: aMap,
|
|
63
|
+
|
|
64
|
+
trailingSlashKeys: /** @type {any} */ (aMap).trailingSlashKeys,
|
|
65
|
+
});
|
|
57
66
|
}
|
package/src/operations/match.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import AsyncMap from "../drivers/AsyncMap.js";
|
|
2
|
+
import isMap from "./isMap.js";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Return a tree with the indicated keys (if provided).
|
|
@@ -13,13 +14,12 @@ import isAsyncTree from "./isAsyncTree.js";
|
|
|
13
14
|
* If a key is requested, match against the given pattern and, if matches,
|
|
14
15
|
* invokes the given function with an object containing the matched values.
|
|
15
16
|
*
|
|
16
|
-
* @typedef
|
|
17
|
-
* @typedef {import("../../index.ts").Treelike} Treelike
|
|
17
|
+
* @typedef {import("../../index.ts").Maplike} Maplike
|
|
18
18
|
* @typedef {import("../../index.ts").Invocable} Invocable
|
|
19
19
|
*
|
|
20
20
|
* @param {string|RegExp} pattern
|
|
21
21
|
* @param {Invocable} resultFn
|
|
22
|
-
* @param {
|
|
22
|
+
* @param {Maplike} [keys]
|
|
23
23
|
*/
|
|
24
24
|
export default function match(pattern, resultFn, keys = []) {
|
|
25
25
|
let regex;
|
|
@@ -36,7 +36,9 @@ export default function match(pattern, resultFn, keys = []) {
|
|
|
36
36
|
throw new Error(`match(): Unsupported pattern`);
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
const result = {
|
|
39
|
+
const result = Object.assign(new AsyncMap(), {
|
|
40
|
+
description: "match",
|
|
41
|
+
|
|
40
42
|
async get(key) {
|
|
41
43
|
const keyMatch = regex.exec(key);
|
|
42
44
|
if (!keyMatch) {
|
|
@@ -45,7 +47,7 @@ export default function match(pattern, resultFn, keys = []) {
|
|
|
45
47
|
|
|
46
48
|
if (
|
|
47
49
|
typeof resultFn !== "function" &&
|
|
48
|
-
!(
|
|
50
|
+
!(isMap(resultFn) && "parent" in resultFn)
|
|
49
51
|
) {
|
|
50
52
|
// Simple return value; return as is
|
|
51
53
|
return resultFn;
|
|
@@ -65,10 +67,12 @@ export default function match(pattern, resultFn, keys = []) {
|
|
|
65
67
|
return value;
|
|
66
68
|
},
|
|
67
69
|
|
|
68
|
-
async keys() {
|
|
69
|
-
|
|
70
|
+
async *keys() {
|
|
71
|
+
yield* typeof keys === "function" ? await keys() : keys;
|
|
70
72
|
},
|
|
71
|
-
|
|
73
|
+
|
|
74
|
+
trailingSlashKeys: false,
|
|
75
|
+
});
|
|
72
76
|
|
|
73
77
|
return result;
|
|
74
78
|
}
|
package/src/operations/merge.js
CHANGED
|
@@ -1,51 +1,59 @@
|
|
|
1
|
+
import AsyncMap from "../drivers/AsyncMap.js";
|
|
1
2
|
import * as trailingSlash from "../trailingSlash.js";
|
|
2
3
|
import isPlainObject from "../utilities/isPlainObject.js";
|
|
4
|
+
import isUnpackable from "../utilities/isUnpackable.js";
|
|
3
5
|
import from from "./from.js";
|
|
4
|
-
import
|
|
6
|
+
import isMap from "./isMap.js";
|
|
7
|
+
import keys from "./keys.js";
|
|
5
8
|
|
|
6
9
|
/**
|
|
7
10
|
* Return a tree that performs a shallow merge of the given trees.
|
|
8
11
|
*
|
|
9
|
-
* This is similar to an object spread in JavaScript extended to
|
|
10
|
-
* Given a set of trees, the `get` method looks at each tree in turn,
|
|
11
|
-
* from the *last* tree and working backwards to the first. If a tree
|
|
12
|
-
* defined value for the key, that value is returned. If none of the
|
|
13
|
-
* return a defined value, the `get` method returns undefined.
|
|
12
|
+
* This is similar to an object spread in JavaScript extended to asynchronous
|
|
13
|
+
* trees. Given a set of trees, the `get` method looks at each tree in turn,
|
|
14
|
+
* starting from the *last* tree and working backwards to the first. If a tree
|
|
15
|
+
* returns a defined value for the key, that value is returned. If none of the
|
|
16
|
+
* trees return a defined value, the `get` method returns undefined.
|
|
14
17
|
*
|
|
15
|
-
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
16
18
|
* @typedef {import("../../index.ts").PlainObject} PlainObject
|
|
17
|
-
* @typedef {import("../../index.ts").
|
|
19
|
+
* @typedef {import("../../index.ts").Maplike} Maplike
|
|
18
20
|
*
|
|
19
|
-
* @param {
|
|
20
|
-
* @returns {
|
|
21
|
+
* @param {Maplike[]} treelikes
|
|
22
|
+
* @returns {Promise}
|
|
21
23
|
*/
|
|
22
|
-
export default function merge(...
|
|
23
|
-
const filtered =
|
|
24
|
+
export default async function merge(...treelikes) {
|
|
25
|
+
const filtered = treelikes.filter((source) => source);
|
|
26
|
+
const unpacked = await Promise.all(
|
|
27
|
+
filtered.map(async (source) =>
|
|
28
|
+
isUnpackable(source) ? await source.unpack() : source
|
|
29
|
+
)
|
|
30
|
+
);
|
|
24
31
|
|
|
25
32
|
// If all arguments are plain objects, return a plain object.
|
|
26
|
-
if (
|
|
27
|
-
|
|
28
|
-
) {
|
|
29
|
-
return filtered.reduce((acc, obj) => ({ ...acc, ...obj }), {});
|
|
33
|
+
if (unpacked.every((source) => !isMap(source) && isPlainObject(source))) {
|
|
34
|
+
return unpacked.reduce((acc, obj) => ({ ...acc, ...obj }), {});
|
|
30
35
|
}
|
|
31
36
|
|
|
32
|
-
const
|
|
37
|
+
const sources = unpacked.map((maplike) => from(maplike));
|
|
33
38
|
|
|
34
|
-
if (
|
|
39
|
+
if (sources.length === 0) {
|
|
35
40
|
throw new TypeError("merge: all trees are null or undefined");
|
|
36
|
-
} else if (
|
|
41
|
+
} else if (sources.length === 1) {
|
|
37
42
|
// Only one tree, no need to merge
|
|
38
|
-
return
|
|
43
|
+
return sources[0];
|
|
39
44
|
}
|
|
40
45
|
|
|
41
|
-
return {
|
|
46
|
+
return Object.assign(new AsyncMap(), {
|
|
42
47
|
description: "merge",
|
|
43
48
|
|
|
44
49
|
async get(key) {
|
|
45
50
|
// Check trees for the indicated key in reverse order.
|
|
46
|
-
for (let index =
|
|
47
|
-
const
|
|
48
|
-
const
|
|
51
|
+
for (let index = sources.length - 1; index >= 0; index--) {
|
|
52
|
+
const source = sources[index];
|
|
53
|
+
const normalized = /** @type {any} */ (source).trailingSlashKeys
|
|
54
|
+
? key
|
|
55
|
+
: trailingSlash.remove(key);
|
|
56
|
+
const value = await source.get(normalized);
|
|
49
57
|
if (value !== undefined) {
|
|
50
58
|
return value;
|
|
51
59
|
}
|
|
@@ -53,25 +61,25 @@ export default function merge(...sources) {
|
|
|
53
61
|
return undefined;
|
|
54
62
|
},
|
|
55
63
|
|
|
56
|
-
async keys() {
|
|
57
|
-
const
|
|
64
|
+
async *keys() {
|
|
65
|
+
const treeKeys = new Set();
|
|
58
66
|
// Collect keys in the order the trees were provided.
|
|
59
|
-
for (const tree of
|
|
60
|
-
for (const key of await
|
|
67
|
+
for (const tree of sources) {
|
|
68
|
+
for (const key of await keys(tree)) {
|
|
61
69
|
// Remove the alternate form of the key (if it exists)
|
|
62
70
|
const alternateKey = trailingSlash.toggle(key);
|
|
63
71
|
if (alternateKey !== key) {
|
|
64
|
-
|
|
72
|
+
treeKeys.delete(alternateKey);
|
|
65
73
|
}
|
|
66
74
|
|
|
67
|
-
|
|
75
|
+
treeKeys.add(key);
|
|
68
76
|
}
|
|
69
77
|
}
|
|
70
|
-
|
|
78
|
+
yield* treeKeys;
|
|
71
79
|
},
|
|
72
80
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
};
|
|
81
|
+
sources,
|
|
82
|
+
|
|
83
|
+
trailingSlashKeys: true,
|
|
84
|
+
});
|
|
77
85
|
}
|