@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.
Files changed (182) hide show
  1. package/browser.js +1 -1
  2. package/index.ts +43 -35
  3. package/main.js +1 -2
  4. package/package.json +4 -7
  5. package/shared.js +74 -12
  6. package/src/Tree.js +11 -2
  7. package/src/drivers/AsyncMap.js +237 -0
  8. package/src/drivers/{BrowserFileTree.js → BrowserFileMap.js} +54 -38
  9. package/src/drivers/{calendarTree.js → CalendarMap.js} +80 -63
  10. package/src/drivers/ConstantMap.js +28 -0
  11. package/src/drivers/{ExplorableSiteTree.js → ExplorableSiteMap.js} +7 -7
  12. package/src/drivers/FileMap.js +238 -0
  13. package/src/drivers/{FunctionTree.js → FunctionMap.js} +19 -22
  14. package/src/drivers/ObjectMap.js +151 -0
  15. package/src/drivers/SetMap.js +13 -0
  16. package/src/drivers/{SiteTree.js → SiteMap.js} +17 -20
  17. package/src/drivers/SyncMap.js +260 -0
  18. package/src/jsonKeys.d.ts +2 -2
  19. package/src/jsonKeys.js +20 -5
  20. package/src/operations/addNextPrevious.js +35 -36
  21. package/src/operations/assign.js +27 -23
  22. package/src/operations/cache.js +30 -36
  23. package/src/operations/cachedKeyFunctions.js +1 -1
  24. package/src/operations/calendar.js +5 -0
  25. package/src/operations/child.js +35 -0
  26. package/src/operations/clear.js +13 -12
  27. package/src/operations/constant.js +5 -0
  28. package/src/operations/deepEntries.js +23 -0
  29. package/src/operations/deepMap.js +9 -9
  30. package/src/operations/deepMerge.js +36 -25
  31. package/src/operations/deepReverse.js +23 -16
  32. package/src/operations/deepTake.js +7 -7
  33. package/src/operations/deepText.js +4 -4
  34. package/src/operations/deepValues.js +3 -6
  35. package/src/operations/deepValuesIterator.js +11 -11
  36. package/src/operations/delete.js +8 -12
  37. package/src/operations/entries.js +17 -10
  38. package/src/operations/filter.js +9 -7
  39. package/src/operations/first.js +12 -10
  40. package/src/operations/forEach.js +10 -13
  41. package/src/operations/from.js +30 -39
  42. package/src/operations/globKeys.js +22 -17
  43. package/src/operations/group.js +2 -2
  44. package/src/operations/groupBy.js +24 -22
  45. package/src/operations/has.js +7 -9
  46. package/src/operations/indent.js +2 -2
  47. package/src/operations/inners.js +19 -15
  48. package/src/operations/invokeFunctions.js +22 -10
  49. package/src/operations/isAsyncMutableTree.js +5 -12
  50. package/src/operations/isAsyncTree.js +5 -20
  51. package/src/operations/isMap.js +39 -0
  52. package/src/operations/isMaplike.js +34 -0
  53. package/src/operations/isReadOnlyMap.js +14 -0
  54. package/src/operations/isTraversable.js +3 -3
  55. package/src/operations/isTreelike.js +5 -30
  56. package/src/operations/json.js +4 -12
  57. package/src/operations/keys.js +17 -8
  58. package/src/operations/length.js +9 -8
  59. package/src/operations/map.js +28 -30
  60. package/src/operations/mapExtension.js +20 -16
  61. package/src/operations/mapReduce.js +38 -24
  62. package/src/operations/mask.js +54 -29
  63. package/src/operations/match.js +13 -9
  64. package/src/operations/merge.js +43 -35
  65. package/src/operations/paginate.js +26 -18
  66. package/src/operations/parent.js +7 -7
  67. package/src/operations/paths.js +20 -22
  68. package/src/operations/plain.js +6 -6
  69. package/src/operations/reduce.js +16 -0
  70. package/src/operations/regExpKeys.js +21 -12
  71. package/src/operations/reverse.js +21 -15
  72. package/src/operations/root.js +6 -5
  73. package/src/operations/scope.js +31 -26
  74. package/src/operations/set.js +20 -0
  75. package/src/operations/shuffle.js +23 -16
  76. package/src/operations/size.js +13 -0
  77. package/src/operations/sort.js +55 -40
  78. package/src/operations/sync.js +14 -0
  79. package/src/operations/take.js +23 -11
  80. package/src/operations/text.js +4 -4
  81. package/src/operations/toFunction.js +7 -7
  82. package/src/operations/traverse.js +4 -4
  83. package/src/operations/traverseOrThrow.js +18 -9
  84. package/src/operations/traversePath.js +2 -2
  85. package/src/operations/values.js +18 -9
  86. package/src/operations/withKeys.js +22 -16
  87. package/src/symbols.js +1 -0
  88. package/src/utilities/castArraylike.js +24 -13
  89. package/src/utilities/getMapArgument.js +38 -0
  90. package/src/utilities/getParent.js +2 -2
  91. package/src/utilities/isStringlike.js +7 -5
  92. package/src/utilities/setParent.js +7 -7
  93. package/src/utilities/toFunction.js +2 -2
  94. package/src/utilities/toPlainValue.js +21 -19
  95. package/test/SampleAsyncMap.js +34 -0
  96. package/test/browser/assert.js +20 -0
  97. package/test/browser/index.html +53 -21
  98. package/test/drivers/AsyncMap.test.js +119 -0
  99. package/test/drivers/{BrowserFileTree.test.js → BrowserFileMap.test.js} +50 -33
  100. package/test/drivers/{calendarTree.test.js → CalendarMap.test.js} +17 -19
  101. package/test/drivers/ConstantMap.test.js +15 -0
  102. package/test/drivers/{ExplorableSiteTree.test.js → ExplorableSiteMap.test.js} +29 -14
  103. package/test/drivers/FileMap.test.js +156 -0
  104. package/test/drivers/FunctionMap.test.js +56 -0
  105. package/test/drivers/ObjectMap.test.js +194 -0
  106. package/test/drivers/SetMap.test.js +35 -0
  107. package/test/drivers/{SiteTree.test.js → SiteMap.test.js} +14 -10
  108. package/test/drivers/SyncMap.test.js +335 -0
  109. package/test/jsonKeys.test.js +18 -6
  110. package/test/operations/addNextPrevious.test.js +3 -2
  111. package/test/operations/assign.test.js +30 -35
  112. package/test/operations/cache.test.js +17 -12
  113. package/test/operations/cachedKeyFunctions.test.js +12 -9
  114. package/test/operations/child.test.js +34 -0
  115. package/test/operations/clear.test.js +6 -27
  116. package/test/operations/deepEntries.test.js +32 -0
  117. package/test/operations/deepMerge.test.js +23 -16
  118. package/test/operations/deepReverse.test.js +2 -2
  119. package/test/operations/deepTake.test.js +2 -2
  120. package/test/operations/deepText.test.js +4 -4
  121. package/test/operations/deepValuesIterator.test.js +2 -2
  122. package/test/operations/delete.test.js +2 -2
  123. package/test/operations/extensionKeyFunctions.test.js +6 -5
  124. package/test/operations/filter.test.js +3 -3
  125. package/test/operations/from.test.js +25 -31
  126. package/test/operations/globKeys.test.js +9 -9
  127. package/test/operations/groupBy.test.js +6 -5
  128. package/test/operations/inners.test.js +17 -14
  129. package/test/operations/invokeFunctions.test.js +2 -2
  130. package/test/operations/isMap.test.js +15 -0
  131. package/test/operations/isMaplike.test.js +15 -0
  132. package/test/operations/json.test.js +2 -2
  133. package/test/operations/keys.test.js +16 -3
  134. package/test/operations/map.test.js +40 -30
  135. package/test/operations/mapExtension.test.js +6 -6
  136. package/test/operations/mapReduce.test.js +14 -12
  137. package/test/operations/mask.test.js +16 -3
  138. package/test/operations/match.test.js +2 -2
  139. package/test/operations/merge.test.js +20 -14
  140. package/test/operations/paginate.test.js +5 -5
  141. package/test/operations/parent.test.js +3 -3
  142. package/test/operations/paths.test.js +20 -27
  143. package/test/operations/plain.test.js +8 -8
  144. package/test/operations/regExpKeys.test.js +22 -18
  145. package/test/operations/reverse.test.js +4 -3
  146. package/test/operations/scope.test.js +6 -5
  147. package/test/operations/set.test.js +11 -0
  148. package/test/operations/shuffle.test.js +3 -2
  149. package/test/operations/sort.test.js +7 -10
  150. package/test/operations/sync.test.js +43 -0
  151. package/test/operations/take.test.js +2 -2
  152. package/test/operations/toFunction.test.js +2 -2
  153. package/test/operations/traverse.test.js +17 -5
  154. package/test/operations/withKeys.test.js +2 -2
  155. package/test/utilities/castArrayLike.test.js +53 -0
  156. package/test/utilities/setParent.test.js +6 -6
  157. package/test/utilities/toFunction.test.js +2 -2
  158. package/test/utilities/toPlainValue.test.js +51 -12
  159. package/src/drivers/DeepMapTree.js +0 -23
  160. package/src/drivers/DeepObjectTree.js +0 -18
  161. package/src/drivers/DeferredTree.js +0 -81
  162. package/src/drivers/FileTree.js +0 -276
  163. package/src/drivers/MapTree.js +0 -70
  164. package/src/drivers/ObjectTree.js +0 -158
  165. package/src/drivers/SetTree.js +0 -34
  166. package/src/drivers/constantTree.js +0 -19
  167. package/src/drivers/limitConcurrency.js +0 -63
  168. package/src/internal.js +0 -16
  169. package/src/utilities/getTreeArgument.js +0 -43
  170. package/test/drivers/DeepMapTree.test.js +0 -17
  171. package/test/drivers/DeepObjectTree.test.js +0 -35
  172. package/test/drivers/DeferredTree.test.js +0 -22
  173. package/test/drivers/FileTree.test.js +0 -192
  174. package/test/drivers/FunctionTree.test.js +0 -46
  175. package/test/drivers/MapTree.test.js +0 -59
  176. package/test/drivers/ObjectTree.test.js +0 -163
  177. package/test/drivers/SetTree.test.js +0 -44
  178. package/test/drivers/constantTree.test.js +0 -13
  179. package/test/drivers/limitConcurrency.test.js +0 -41
  180. package/test/operations/isAsyncMutableTree.test.js +0 -17
  181. package/test/operations/isAsyncTree.test.js +0 -26
  182. package/test/operations/isTreelike.test.js +0 -13
@@ -1,21 +1,6 @@
1
- /**
2
- * Return true if the indicated object is an async tree.
3
- *
4
- * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
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 isTreelike from "./isTreelike.js";
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 treelike or a packed object with an `unpack()` method.
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
- isTreelike(object) ||
12
+ isMaplike(object) ||
13
13
  (isPacked(object) && /** @type {any} */ (object).unpack instanceof Function)
14
14
  );
15
15
  }
@@ -1,33 +1,8 @@
1
- import isPlainObject from "../utilities/isPlainObject.js";
2
- import isAsyncTree from "./isAsyncTree.js";
1
+ import isMaplike from "./isMaplike.js";
3
2
 
4
- /**
5
- * Returns true if the indicated object can be directly treated as an
6
- * asynchronous tree. This includes:
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
  }
@@ -1,21 +1,13 @@
1
- /** @typedef {import("@weborigami/types").AsyncTree} AsyncTree */
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").Treelike} [treelike]
7
+ * @param {import("../../index.ts").Maplike} maplike
10
8
  */
11
- export default async function json(treelike) {
12
- let tree = from(treelike);
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
  }
@@ -1,14 +1,23 @@
1
- import getTreeArgument from "../utilities/getTreeArgument.js";
1
+ import getMapArgument from "../utilities/getMapArgument.js";
2
2
 
3
3
  /**
4
- * Return the top-level keys in the tree as an array.
4
+ * Return the keys of the map.
5
5
  *
6
- * @typedef {import("../../index.ts").Treelike} Treelike
6
+ * @typedef {import("../../index.ts").Maplike} Maplike
7
7
  *
8
- * @param {Treelike} treelike
8
+ * @param {Maplike} maplike
9
9
  */
10
- export default async function keys(treelike) {
11
- const tree = await getTreeArgument(treelike, "keys");
12
- const keys = await tree.keys();
13
- return Array.from(keys);
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
  }
@@ -1,15 +1,16 @@
1
- import getTreeArgument from "../utilities/getTreeArgument.js";
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("@weborigami/types").AsyncTree} AsyncTree
7
- * @typedef {import("../../index.ts").Treelike} Treelike
7
+ * @typedef {import("../../index.ts").Maplike} Maplike
8
8
  *
9
- * @param {Treelike} treelike
9
+ * @param {Maplike} maplike
10
10
  */
11
- export default async function length(treelike) {
12
- const tree = await getTreeArgument(treelike, "length");
13
- const keys = Array.from(await tree.keys());
14
- return keys.length;
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
  }
@@ -1,33 +1,34 @@
1
+ import AsyncMap from "../drivers/AsyncMap.js";
1
2
  import * as trailingSlash from "../trailingSlash.js";
2
- import getTreeArgument from "../utilities/getTreeArgument.js";
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 isAsyncTree from "./isAsyncTree.js";
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").TreeMapOptions} MapOptions
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").Treelike} treelike
20
+ * @param {import("../../index.ts").Maplike} maplike
20
21
  * @param {MapOptions|ValueKeyFn} options
21
- * @returns {Promise<AsyncTree>}
22
+ * @returns {Promise<AsyncMap>}
22
23
  */
23
- export default async function map(treelike, options = {}) {
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 getTreeArgument(treelike, "map", { deep: validated.deep });
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 = isAsyncTree(sourceValue)
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 && isAsyncTree(sourceValue)) {
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 = Array.from(await tree.keys());
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
- return resultKeys;
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 {AsyncTree} tree
115
- * @return {AsyncTree}
117
+ * @param {Map|AsyncMap} tree
118
+ * @return {AsyncMap}
116
119
  */
117
120
  return function mapFn(tree) {
118
- // The transformed tree is actually an extension of the original tree's
119
- // prototype chain. This allows the transformed tree to inherit any
120
- // properties/methods. For example, the `parent` of the transformed tree is
121
- // the original tree's parent.
122
- const transformed = Object.create(tree);
123
- transformed.description = description;
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);
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").TreeMapExtensionOptions} TreeMapExtensionOptions
7
- * @typedef {import("../../index.ts").Treelike} Treelike
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 {Treelike} treelike
14
+ * @param {Maplike} maplike
15
15
  * @param {string} extension
16
16
  */
17
17
 
18
18
  /**
19
19
  * @overload
20
- * @param {Treelike} treelike
21
- * @param {TreeMapExtensionOptions} options
20
+ * @param {Maplike} maplike
21
+ * @param {MapExtensionOptions} options
22
22
  */
23
23
 
24
24
  /**
25
25
  * @overload
26
- * @param {Treelike} treelike
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 {Treelike} treelike
33
+ * @param {Maplike} maplike
34
34
  * @param {string} extension
35
- * @param {TreeMapExtensionOptions} options
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 {Treelike} treelike
42
- * @param {string|TreeMapExtensionOptions} arg2
43
- * @param {ValueKeyFn|TreeMapExtensionOptions} [arg3]
44
- * @returns {Promise<AsyncTree>}
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(treelike, arg2, arg3) {
47
- /** @type {TreeMapExtensionOptions} */
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
- return map(treelike, options);
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 from from "./from.js";
2
- import isAsyncTree from "./isAsyncTree.js";
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
- * This is done in as parallel fashion as possible. Each of the tree's values
8
- * will be requested in an async call, then those results will be awaited
9
- * collectively. If a mapFn is provided, it will be invoked to convert each
10
- * value to a mapped value; otherwise, values will be used as is. When the
11
- * values have been obtained, all the values and keys will be passed to the
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
- * @typedef {import("../../index.ts").Treelike} Treelike
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 {Treelike} treelike
19
- * @param {ValueKeyFn|null} mapFn
21
+ * @param {Maplike} source
22
+ * @param {ValueKeyFn|null} valueFn
20
23
  * @param {ReduceFn} reduceFn
21
24
  */
22
- export default async function mapReduce(treelike, mapFn, reduceFn) {
23
- const tree = from(treelike);
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 keys = Array.from(await tree.keys());
29
- const promises = keys.map(async (key) => {
30
- const value = await tree.get(key);
31
- return isAsyncTree(value)
32
- ? mapReduce(value, mapFn, reduceFn) // subtree; recurse
33
- : mapFn
34
- ? mapFn(value, key, tree)
35
- : value;
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(values, keys, tree);
57
+ return reduceFn(mapped, sourceMap);
44
58
  }
@@ -1,36 +1,44 @@
1
+ import AsyncMap from "../drivers/AsyncMap.js";
1
2
  import * as trailingSlash from "../trailingSlash.js";
2
- import getTreeArgument from "../utilities/getTreeArgument.js";
3
- import isAsyncTree from "./isAsyncTree.js";
4
- import isTreelike from "./isTreelike.js";
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("@weborigami/types").AsyncTree} AsyncTree
12
- * @typedef {import("../../index.ts").Treelike} Treelike
12
+ * @typedef {import("../../index.ts").Maplike} Maplike
13
13
  *
14
- * @param {Treelike} aTreelike
15
- * @param {Treelike} bTreelike
16
- * @returns {Promise<AsyncTree>}
14
+ * @param {Maplike} aMaplike
15
+ * @param {Maplike} bMaplike
16
+ * @returns {Promise<AsyncMap>}
17
17
  */
18
- export default async function mask(aTreelike, bTreelike) {
19
- const aTree = await getTreeArgument(aTreelike, "filter", { position: 0 });
20
- const bTree = await getTreeArgument(bTreelike, "filter", {
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 bTree.get(key);
33
+ const bValue = await bMap.get(key);
29
34
  if (!bValue) {
30
35
  return undefined;
31
36
  }
32
- let aValue = await aTree.get(key);
33
- if (isTreelike(aValue)) {
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
- // Use a's keys as the basis
43
- const aKeys = [...(await aTree.keys())];
44
- const bValues = await Promise.all(aKeys.map((key) => bTree.get(key)));
45
- // An async tree value in b implies that the a key should have a slash
46
- const aKeySlashes = aKeys.map((key, index) =>
47
- trailingSlash.toggle(
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) || isAsyncTree(bValues[index])
50
- )
51
- );
52
- // Remove keys that don't have values in b
53
- const keys = aKeySlashes.filter((key, index) => bValues[index] ?? false);
54
- return keys;
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
  }