@weborigami/async-tree 0.5.8 → 0.6.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/browser.js +1 -1
- package/index.ts +43 -35
- package/main.js +1 -2
- package/package.json +4 -7
- package/shared.js +74 -12
- package/src/Tree.js +11 -2
- package/src/drivers/AsyncMap.js +237 -0
- package/src/drivers/{BrowserFileTree.js → BrowserFileMap.js} +54 -38
- package/src/drivers/{calendarTree.js → CalendarMap.js} +80 -63
- package/src/drivers/ConstantMap.js +28 -0
- package/src/drivers/{ExplorableSiteTree.js → ExplorableSiteMap.js} +7 -7
- package/src/drivers/FileMap.js +238 -0
- package/src/drivers/{FunctionTree.js → FunctionMap.js} +19 -22
- package/src/drivers/ObjectMap.js +151 -0
- package/src/drivers/SetMap.js +13 -0
- package/src/drivers/{SiteTree.js → SiteMap.js} +17 -20
- package/src/drivers/SyncMap.js +260 -0
- package/src/jsonKeys.d.ts +2 -2
- package/src/jsonKeys.js +20 -5
- package/src/operations/addNextPrevious.js +35 -36
- package/src/operations/assign.js +27 -23
- package/src/operations/cache.js +30 -36
- package/src/operations/cachedKeyFunctions.js +1 -1
- package/src/operations/calendar.js +5 -0
- package/src/operations/child.js +35 -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 +30 -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 +28 -30
- package/src/operations/mapExtension.js +20 -16
- package/src/operations/mapReduce.js +38 -24
- package/src/operations/mask.js +54 -29
- 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 +20 -22
- package/src/operations/plain.js +6 -6
- package/src/operations/reduce.js +16 -0
- 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/set.js +20 -0
- 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 +14 -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 +18 -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 +24 -13
- 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 +21 -19
- package/test/SampleAsyncMap.js +34 -0
- package/test/browser/assert.js +20 -0
- package/test/browser/index.html +53 -21
- package/test/drivers/AsyncMap.test.js +119 -0
- package/test/drivers/{BrowserFileTree.test.js → BrowserFileMap.test.js} +50 -33
- package/test/drivers/{calendarTree.test.js → CalendarMap.test.js} +17 -19
- package/test/drivers/ConstantMap.test.js +15 -0
- package/test/drivers/{ExplorableSiteTree.test.js → ExplorableSiteMap.test.js} +29 -14
- package/test/drivers/FileMap.test.js +156 -0
- package/test/drivers/FunctionMap.test.js +56 -0
- package/test/drivers/ObjectMap.test.js +194 -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 +335 -0
- package/test/jsonKeys.test.js +18 -6
- package/test/operations/addNextPrevious.test.js +3 -2
- package/test/operations/assign.test.js +30 -35
- package/test/operations/cache.test.js +17 -12
- package/test/operations/cachedKeyFunctions.test.js +12 -9
- package/test/operations/child.test.js +34 -0
- package/test/operations/clear.test.js +6 -27
- package/test/operations/deepEntries.test.js +32 -0
- package/test/operations/deepMerge.test.js +23 -16
- 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 +25 -31
- package/test/operations/globKeys.test.js +9 -9
- package/test/operations/groupBy.test.js +6 -5
- package/test/operations/inners.test.js +17 -14
- 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 +40 -30
- package/test/operations/mapExtension.test.js +6 -6
- package/test/operations/mapReduce.test.js +14 -12
- package/test/operations/mask.test.js +16 -3
- package/test/operations/match.test.js +2 -2
- package/test/operations/merge.test.js +20 -14
- package/test/operations/paginate.test.js +5 -5
- package/test/operations/parent.test.js +3 -3
- package/test/operations/paths.test.js +20 -27
- package/test/operations/plain.test.js +8 -8
- package/test/operations/regExpKeys.test.js +22 -18
- package/test/operations/reverse.test.js +4 -3
- package/test/operations/scope.test.js +6 -5
- package/test/operations/set.test.js +11 -0
- 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 +17 -5
- package/test/operations/withKeys.test.js +2 -2
- package/test/utilities/castArrayLike.test.js +53 -0
- 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
|
@@ -1,21 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
* @param {any} obj
|
|
7
|
-
* @returns {obj is AsyncTree}
|
|
8
|
-
*/
|
|
9
|
-
export default function isAsyncTree(obj) {
|
|
10
|
-
return (
|
|
11
|
-
obj !== null &&
|
|
12
|
-
typeof obj === "object" &&
|
|
13
|
-
typeof obj.get === "function" &&
|
|
14
|
-
typeof obj.keys === "function" &&
|
|
15
|
-
// Regular JavaScript Map instances look like trees but can't have their
|
|
16
|
-
// prototype chains extended (the way map() does), so exclude them.
|
|
17
|
-
// Subclasses of Map that implement their own get() and keys() methods are
|
|
18
|
-
// allowed, because they shouldn't have the same limitations.
|
|
19
|
-
!(obj instanceof Map && Object.getPrototypeOf(obj) === Map.prototype)
|
|
20
|
-
);
|
|
1
|
+
import isMap from "./isMap.js";
|
|
2
|
+
|
|
3
|
+
export default function isAsyncTree(treelike) {
|
|
4
|
+
console.warn("Tree.isAsyncTree() is deprecated, use Tree.isMap() instead.");
|
|
5
|
+
return isMap(treelike);
|
|
21
6
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import AsyncMap from "../drivers/AsyncMap.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Return true if the indicated object is a Map instance or supports
|
|
5
|
+
* a `Map` compatible interface.
|
|
6
|
+
*
|
|
7
|
+
* @typedef {import("../../index.ts").SyncOrAsyncMap} SyncOrAsyncMap
|
|
8
|
+
*
|
|
9
|
+
* @param {any} object
|
|
10
|
+
* @returns {object is Map|AsyncMap}
|
|
11
|
+
*/
|
|
12
|
+
export default function isMap(object) {
|
|
13
|
+
if (object instanceof Map || object instanceof AsyncMap) {
|
|
14
|
+
// Known positive cases
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Check for Map-like interface
|
|
19
|
+
if (
|
|
20
|
+
object &&
|
|
21
|
+
object.clear instanceof Function &&
|
|
22
|
+
object.delete instanceof Function &&
|
|
23
|
+
object.entries instanceof Function &&
|
|
24
|
+
object.forEach instanceof Function &&
|
|
25
|
+
object.get instanceof Function &&
|
|
26
|
+
object.has instanceof Function &&
|
|
27
|
+
object.keys instanceof Function &&
|
|
28
|
+
object.set instanceof Function &&
|
|
29
|
+
object.values instanceof Function &&
|
|
30
|
+
object.constructor.groupBy instanceof Function &&
|
|
31
|
+
"size" in object &&
|
|
32
|
+
(object[Symbol.iterator] instanceof Function ||
|
|
33
|
+
object[Symbol.asyncIterator] instanceof Function)
|
|
34
|
+
) {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import isPlainObject from "../utilities/isPlainObject.js";
|
|
2
|
+
import isMap from "./isMap.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Returns true if the indicated object can be directly treated like a Map. This
|
|
6
|
+
* includes:
|
|
7
|
+
*
|
|
8
|
+
* - A function
|
|
9
|
+
* - An `Array` instance
|
|
10
|
+
* - An `AsyncMap` instance
|
|
11
|
+
* - An `Iterator` instance
|
|
12
|
+
* - A `Map` instance
|
|
13
|
+
* - A `Set` instance
|
|
14
|
+
* - A plain object
|
|
15
|
+
*
|
|
16
|
+
* Note: the `from()` method accepts any JavaScript object, but `isMaplike`
|
|
17
|
+
* returns `false` for an object that isn't one of the above types.
|
|
18
|
+
*
|
|
19
|
+
* @typedef {import("../../index.ts").Maplike} Maplike
|
|
20
|
+
*
|
|
21
|
+
* @param {any} object
|
|
22
|
+
* @returns {obj is Maplike}
|
|
23
|
+
*/
|
|
24
|
+
export default function isMaplike(object) {
|
|
25
|
+
return (
|
|
26
|
+
object instanceof Array ||
|
|
27
|
+
object instanceof Function ||
|
|
28
|
+
// @ts-ignore
|
|
29
|
+
(globalThis.Iterator && object instanceof Iterator) ||
|
|
30
|
+
object instanceof Set ||
|
|
31
|
+
isMap(object) ||
|
|
32
|
+
isPlainObject(object)
|
|
33
|
+
);
|
|
34
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import isMap from "./isMap.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Return true if the indicated object is a read-only map.
|
|
5
|
+
*
|
|
6
|
+
* @param {any} object
|
|
7
|
+
*/
|
|
8
|
+
export default function isReadOnlyMap(object) {
|
|
9
|
+
if (!isMap(object)) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
// Respect readOnly if defined, otherwise assume read/write
|
|
13
|
+
return "readOnly" in object ? object.readOnly : false;
|
|
14
|
+
}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import isPacked from "../utilities/isPacked.js";
|
|
2
|
-
import
|
|
2
|
+
import isMaplike from "./isMaplike.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Return true if the object can be traversed via the `traverse()` method. The
|
|
6
|
-
* object must be either
|
|
6
|
+
* object must be either maplike or a packed object with an `unpack()` method.
|
|
7
7
|
*
|
|
8
8
|
* @param {any} object
|
|
9
9
|
*/
|
|
10
10
|
export default function isTraversable(object) {
|
|
11
11
|
return (
|
|
12
|
-
|
|
12
|
+
isMaplike(object) ||
|
|
13
13
|
(isPacked(object) && /** @type {any} */ (object).unpack instanceof Function)
|
|
14
14
|
);
|
|
15
15
|
}
|
|
@@ -1,33 +1,8 @@
|
|
|
1
|
-
import
|
|
2
|
-
import isAsyncTree from "./isAsyncTree.js";
|
|
1
|
+
import isMaplike from "./isMaplike.js";
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
*
|
|
8
|
-
* - An object that implements the AsyncTree interface (including
|
|
9
|
-
* AsyncTree instances)
|
|
10
|
-
* - A function
|
|
11
|
-
* - An `Array` instance
|
|
12
|
-
* - A `Map` instance
|
|
13
|
-
* - A `Set` instance
|
|
14
|
-
* - A plain object
|
|
15
|
-
*
|
|
16
|
-
* Note: the `from()` method accepts any JavaScript object, but `isTreelike`
|
|
17
|
-
* returns `false` for an object that isn't one of the above types.
|
|
18
|
-
*
|
|
19
|
-
* @typedef {import("../../index.ts").Treelike} Treelike
|
|
20
|
-
*
|
|
21
|
-
* @param {any} obj
|
|
22
|
-
* @returns {obj is Treelike}
|
|
23
|
-
*/
|
|
24
|
-
export default function isTreelike(obj) {
|
|
25
|
-
return (
|
|
26
|
-
isAsyncTree(obj) ||
|
|
27
|
-
obj instanceof Array ||
|
|
28
|
-
obj instanceof Function ||
|
|
29
|
-
obj instanceof Map ||
|
|
30
|
-
obj instanceof Set ||
|
|
31
|
-
isPlainObject(obj)
|
|
3
|
+
export default function isTreelike(treelike) {
|
|
4
|
+
console.warn(
|
|
5
|
+
"Tree.isTreelike() is deprecated, use Tree.isMaplike() instead."
|
|
32
6
|
);
|
|
7
|
+
return isMaplike(treelike);
|
|
33
8
|
}
|
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,24 @@ 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
|
-
|
|
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);
|
|
127
|
+
transformed.trailingSlashKeys = /** @type {any} */ (tree).trailingSlashKeys;
|
|
130
128
|
return transformed;
|
|
131
129
|
};
|
|
132
130
|
}
|
|
@@ -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,58 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import getMapArgument from "../utilities/getMapArgument.js";
|
|
2
|
+
import isMap from "./isMap.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Map and reduce a tree.
|
|
5
|
+
* Map and reduce a `source` tree.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
* will be
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* reduceFn, which should consolidate those into a single result.
|
|
7
|
+
* Each of the tree's values will be requested in an asynchronous call, then
|
|
8
|
+
* those results will be awaited collectively. If a valueFn is provided, it will
|
|
9
|
+
* be invoked to convert each value to a mapped value; if the valueFn is null or
|
|
10
|
+
* undefined, values will be used as is, although any Promise values will be
|
|
11
|
+
* awaited.
|
|
13
12
|
*
|
|
14
|
-
*
|
|
13
|
+
* The resolved values will be added to a regular `Map` with the same keys as
|
|
14
|
+
* the source. This `Map` will be passed to the reduceFn, along with the
|
|
15
|
+
* original `source`.
|
|
16
|
+
*
|
|
17
|
+
* @typedef {import("../../index.ts").Maplike} Maplike
|
|
15
18
|
* @typedef {import("../../index.ts").ReduceFn} ReduceFn
|
|
16
19
|
* @typedef {import("../../index.ts").ValueKeyFn} ValueKeyFn
|
|
17
20
|
*
|
|
18
|
-
* @param {
|
|
19
|
-
* @param {ValueKeyFn|null}
|
|
21
|
+
* @param {Maplike} source
|
|
22
|
+
* @param {ValueKeyFn|null} valueFn
|
|
20
23
|
* @param {ReduceFn} reduceFn
|
|
21
24
|
*/
|
|
22
|
-
export default async function mapReduce(
|
|
23
|
-
const
|
|
25
|
+
export default async function mapReduce(source, valueFn, reduceFn) {
|
|
26
|
+
const sourceMap = await getMapArgument(source, "mapReduce");
|
|
24
27
|
|
|
25
28
|
// We're going to fire off all the get requests in parallel, as quickly as
|
|
26
29
|
// the keys come in. We call the tree's `get` method for each key, but
|
|
27
30
|
// *don't* wait for it yet.
|
|
28
|
-
const
|
|
29
|
-
const promises =
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
31
|
+
const mapped = new Map();
|
|
32
|
+
const promises = [];
|
|
33
|
+
for await (const key of sourceMap.keys()) {
|
|
34
|
+
mapped.set(key, null); // placeholder
|
|
35
|
+
const promise = (async () => {
|
|
36
|
+
const value = await sourceMap.get(key);
|
|
37
|
+
return isMap(value)
|
|
38
|
+
? mapReduce(value, valueFn, reduceFn) // subtree; recurse
|
|
39
|
+
: valueFn
|
|
40
|
+
? valueFn(value, key, sourceMap)
|
|
41
|
+
: value;
|
|
42
|
+
})();
|
|
43
|
+
promises.push(promise);
|
|
44
|
+
}
|
|
37
45
|
|
|
38
46
|
// Wait for all the promises to resolve. Because the promises were captured
|
|
39
47
|
// in the same order as the keys, the values will also be in the same order.
|
|
40
48
|
const values = await Promise.all(promises);
|
|
41
49
|
|
|
50
|
+
// Replace the placeholders with the actual values.
|
|
51
|
+
const keys = Array.from(mapped.keys());
|
|
52
|
+
for (let i = 0; i < values.length; i++) {
|
|
53
|
+
mapped.set(keys[i], values[i]);
|
|
54
|
+
}
|
|
55
|
+
|
|
42
56
|
// Reduce the values to a single result.
|
|
43
|
-
return reduceFn(
|
|
57
|
+
return reduceFn(mapped, sourceMap);
|
|
44
58
|
}
|
package/src/operations/mask.js
CHANGED
|
@@ -1,36 +1,44 @@
|
|
|
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 isMaplike from "./isMaplike.js";
|
|
5
|
+
import keys from "./keys.js";
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Given trees `a` and `b`, return a masked version of `a` where only the keys
|
|
8
9
|
* that exist in `b` and have truthy values are kept. The filter operation is
|
|
9
10
|
* deep: if a value from `a` is a subtree, it will be filtered recursively.
|
|
10
11
|
*
|
|
11
|
-
* @typedef {import("
|
|
12
|
-
* @typedef {import("../../index.ts").Treelike} Treelike
|
|
12
|
+
* @typedef {import("../../index.ts").Maplike} Maplike
|
|
13
13
|
*
|
|
14
|
-
* @param {
|
|
15
|
-
* @param {
|
|
16
|
-
* @returns {Promise<
|
|
14
|
+
* @param {Maplike} aMaplike
|
|
15
|
+
* @param {Maplike} bMaplike
|
|
16
|
+
* @returns {Promise<AsyncMap>}
|
|
17
17
|
*/
|
|
18
|
-
export default async function mask(
|
|
19
|
-
const
|
|
20
|
-
|
|
18
|
+
export default async function mask(aMaplike, bMaplike) {
|
|
19
|
+
const aMap = await getMapArgument(aMaplike, "filter", {
|
|
20
|
+
deep: true,
|
|
21
|
+
position: 0,
|
|
22
|
+
});
|
|
23
|
+
const bMap = await getMapArgument(bMaplike, "filter", {
|
|
21
24
|
deep: true,
|
|
22
25
|
position: 1,
|
|
23
26
|
});
|
|
24
27
|
|
|
25
|
-
return {
|
|
28
|
+
return Object.assign(new AsyncMap(), {
|
|
29
|
+
description: "mask",
|
|
30
|
+
|
|
26
31
|
async get(key) {
|
|
27
32
|
// The key must exist in b and return a truthy value
|
|
28
|
-
const bValue = await
|
|
33
|
+
const bValue = await bMap.get(key);
|
|
29
34
|
if (!bValue) {
|
|
30
35
|
return undefined;
|
|
31
36
|
}
|
|
32
|
-
|
|
33
|
-
|
|
37
|
+
const normalized = /** @type {any} */ (aMap).trailingSlashKeys
|
|
38
|
+
? key
|
|
39
|
+
: trailingSlash.remove(key);
|
|
40
|
+
let aValue = await aMap.get(normalized);
|
|
41
|
+
if (isMaplike(aValue)) {
|
|
34
42
|
// Filter the subtree
|
|
35
43
|
return mask(aValue, bValue);
|
|
36
44
|
} else {
|
|
@@ -38,20 +46,37 @@ export default async function mask(aTreelike, bTreelike) {
|
|
|
38
46
|
}
|
|
39
47
|
},
|
|
40
48
|
|
|
41
|
-
async keys() {
|
|
42
|
-
//
|
|
43
|
-
const aKeys =
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
49
|
+
async *keys() {
|
|
50
|
+
// Get keys from a and b
|
|
51
|
+
const [aKeys, bKeys] = await Promise.all([keys(aMap), keys(bMap)]);
|
|
52
|
+
|
|
53
|
+
const combined = Array.from(new Set([...aKeys, ...bKeys]));
|
|
54
|
+
|
|
55
|
+
// Get all the values from b. Because a and b may be defined by functions,
|
|
56
|
+
// they might have values that are not represented in their own keys.
|
|
57
|
+
const bValues = await Promise.all(combined.map((key) => bMap.get(key)));
|
|
58
|
+
|
|
59
|
+
// Find keys that have truthy values in b. While we're at it, we can add
|
|
60
|
+
// slashes even if a or b didn't have them.
|
|
61
|
+
const withSlashes = combined.map((key, index) => {
|
|
62
|
+
const bValue = bValues[index];
|
|
63
|
+
if (!bValue) {
|
|
64
|
+
// Mark for removal
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
return trailingSlash.toggle(
|
|
48
68
|
key,
|
|
49
|
-
trailingSlash.has(key) ||
|
|
50
|
-
)
|
|
51
|
-
);
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
69
|
+
trailingSlash.has(key) || isMaplike(bValue)
|
|
70
|
+
);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// Yield only the keys that have truthy values in b
|
|
74
|
+
const filtered = withSlashes.filter((value) => value !== undefined);
|
|
75
|
+
yield* new Set(filtered);
|
|
55
76
|
},
|
|
56
|
-
|
|
77
|
+
|
|
78
|
+
source: aMap,
|
|
79
|
+
|
|
80
|
+
trailingSlashKeys: true,
|
|
81
|
+
});
|
|
57
82
|
}
|