@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.
Files changed (178) hide show
  1. package/browser.js +1 -1
  2. package/index.ts +31 -35
  3. package/main.js +1 -2
  4. package/package.json +4 -7
  5. package/shared.js +77 -12
  6. package/src/Tree.js +8 -2
  7. package/src/drivers/AsyncMap.js +210 -0
  8. package/src/drivers/{BrowserFileTree.js → BrowserFileMap.js} +36 -27
  9. package/src/drivers/{calendarTree.js → CalendarMap.js} +81 -62
  10. package/src/drivers/ConstantMap.js +30 -0
  11. package/src/drivers/DeepObjectMap.js +27 -0
  12. package/src/drivers/{ExplorableSiteTree.js → ExplorableSiteMap.js} +7 -7
  13. package/src/drivers/FileMap.js +245 -0
  14. package/src/drivers/{FunctionTree.js → FunctionMap.js} +19 -22
  15. package/src/drivers/ObjectMap.js +139 -0
  16. package/src/drivers/SetMap.js +13 -0
  17. package/src/drivers/{SiteTree.js → SiteMap.js} +16 -17
  18. package/src/drivers/SyncMap.js +245 -0
  19. package/src/jsonKeys.d.ts +2 -2
  20. package/src/jsonKeys.js +6 -5
  21. package/src/operations/addNextPrevious.js +35 -36
  22. package/src/operations/assign.js +30 -21
  23. package/src/operations/cache.js +29 -35
  24. package/src/operations/cachedKeyFunctions.js +1 -1
  25. package/src/operations/calendar.js +5 -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 +31 -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 +27 -30
  60. package/src/operations/mapExtension.js +20 -16
  61. package/src/operations/mapReduce.js +22 -17
  62. package/src/operations/mask.js +31 -22
  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 +8 -8
  68. package/src/operations/plain.js +6 -6
  69. package/src/operations/regExpKeys.js +21 -12
  70. package/src/operations/reverse.js +21 -15
  71. package/src/operations/root.js +6 -5
  72. package/src/operations/scope.js +31 -26
  73. package/src/operations/shuffle.js +23 -16
  74. package/src/operations/size.js +13 -0
  75. package/src/operations/sort.js +55 -40
  76. package/src/operations/sync.js +21 -0
  77. package/src/operations/take.js +23 -11
  78. package/src/operations/text.js +4 -4
  79. package/src/operations/toFunction.js +7 -7
  80. package/src/operations/traverse.js +4 -4
  81. package/src/operations/traverseOrThrow.js +13 -9
  82. package/src/operations/traversePath.js +2 -2
  83. package/src/operations/values.js +18 -9
  84. package/src/operations/withKeys.js +22 -16
  85. package/src/symbols.js +1 -0
  86. package/src/utilities/castArraylike.js +10 -2
  87. package/src/utilities/getMapArgument.js +38 -0
  88. package/src/utilities/getParent.js +2 -2
  89. package/src/utilities/isStringlike.js +7 -5
  90. package/src/utilities/setParent.js +7 -7
  91. package/src/utilities/toFunction.js +2 -2
  92. package/src/utilities/toPlainValue.js +22 -18
  93. package/test/SampleAsyncMap.js +34 -0
  94. package/test/browser/assert.js +20 -0
  95. package/test/browser/index.html +54 -21
  96. package/test/drivers/AsyncMap.test.js +119 -0
  97. package/test/drivers/{BrowserFileTree.test.js → BrowserFileMap.test.js} +42 -23
  98. package/test/drivers/{calendarTree.test.js → CalendarMap.test.js} +17 -19
  99. package/test/drivers/ConstantMap.test.js +15 -0
  100. package/test/drivers/DeepObjectMap.test.js +36 -0
  101. package/test/drivers/{ExplorableSiteTree.test.js → ExplorableSiteMap.test.js} +29 -14
  102. package/test/drivers/FileMap.test.js +185 -0
  103. package/test/drivers/FunctionMap.test.js +56 -0
  104. package/test/drivers/ObjectMap.test.js +166 -0
  105. package/test/drivers/SetMap.test.js +35 -0
  106. package/test/drivers/{SiteTree.test.js → SiteMap.test.js} +14 -10
  107. package/test/drivers/SyncMap.test.js +321 -0
  108. package/test/jsonKeys.test.js +2 -2
  109. package/test/operations/addNextPrevious.test.js +3 -2
  110. package/test/operations/assign.test.js +30 -35
  111. package/test/operations/cache.test.js +8 -6
  112. package/test/operations/cachedKeyFunctions.test.js +6 -5
  113. package/test/operations/clear.test.js +6 -27
  114. package/test/operations/deepEntries.test.js +32 -0
  115. package/test/operations/deepMerge.test.js +6 -5
  116. package/test/operations/deepReverse.test.js +2 -2
  117. package/test/operations/deepTake.test.js +2 -2
  118. package/test/operations/deepText.test.js +4 -4
  119. package/test/operations/deepValuesIterator.test.js +2 -2
  120. package/test/operations/delete.test.js +2 -2
  121. package/test/operations/extensionKeyFunctions.test.js +6 -5
  122. package/test/operations/filter.test.js +3 -3
  123. package/test/operations/from.test.js +23 -31
  124. package/test/operations/globKeys.test.js +9 -9
  125. package/test/operations/groupBy.test.js +6 -5
  126. package/test/operations/inners.test.js +4 -4
  127. package/test/operations/invokeFunctions.test.js +2 -2
  128. package/test/operations/isMap.test.js +15 -0
  129. package/test/operations/isMaplike.test.js +15 -0
  130. package/test/operations/json.test.js +2 -2
  131. package/test/operations/keys.test.js +16 -3
  132. package/test/operations/map.test.js +20 -18
  133. package/test/operations/mapExtension.test.js +6 -6
  134. package/test/operations/mapReduce.test.js +2 -2
  135. package/test/operations/mask.test.js +4 -3
  136. package/test/operations/match.test.js +2 -2
  137. package/test/operations/merge.test.js +15 -11
  138. package/test/operations/paginate.test.js +5 -5
  139. package/test/operations/parent.test.js +3 -3
  140. package/test/operations/paths.test.js +6 -6
  141. package/test/operations/plain.test.js +8 -8
  142. package/test/operations/regExpKeys.test.js +12 -11
  143. package/test/operations/reverse.test.js +4 -3
  144. package/test/operations/scope.test.js +6 -5
  145. package/test/operations/shuffle.test.js +3 -2
  146. package/test/operations/sort.test.js +7 -10
  147. package/test/operations/sync.test.js +43 -0
  148. package/test/operations/take.test.js +2 -2
  149. package/test/operations/toFunction.test.js +2 -2
  150. package/test/operations/traverse.test.js +4 -5
  151. package/test/operations/withKeys.test.js +2 -2
  152. package/test/utilities/setParent.test.js +6 -6
  153. package/test/utilities/toFunction.test.js +2 -2
  154. package/test/utilities/toPlainValue.test.js +51 -12
  155. package/src/drivers/DeepMapTree.js +0 -23
  156. package/src/drivers/DeepObjectTree.js +0 -18
  157. package/src/drivers/DeferredTree.js +0 -81
  158. package/src/drivers/FileTree.js +0 -276
  159. package/src/drivers/MapTree.js +0 -70
  160. package/src/drivers/ObjectTree.js +0 -158
  161. package/src/drivers/SetTree.js +0 -34
  162. package/src/drivers/constantTree.js +0 -19
  163. package/src/drivers/limitConcurrency.js +0 -63
  164. package/src/internal.js +0 -16
  165. package/src/utilities/getTreeArgument.js +0 -43
  166. package/test/drivers/DeepMapTree.test.js +0 -17
  167. package/test/drivers/DeepObjectTree.test.js +0 -35
  168. package/test/drivers/DeferredTree.test.js +0 -22
  169. package/test/drivers/FileTree.test.js +0 -192
  170. package/test/drivers/FunctionTree.test.js +0 -46
  171. package/test/drivers/MapTree.test.js +0 -59
  172. package/test/drivers/ObjectTree.test.js +0 -163
  173. package/test/drivers/SetTree.test.js +0 -44
  174. package/test/drivers/constantTree.test.js +0 -13
  175. package/test/drivers/limitConcurrency.test.js +0 -41
  176. package/test/operations/isAsyncMutableTree.test.js +0 -17
  177. package/test/operations/isAsyncTree.test.js +0 -26
  178. package/test/operations/isTreelike.test.js +0 -13
@@ -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,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
- 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);
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").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,49 @@
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
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 async call, then those results will be awaited
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").Treelike} Treelike
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 {Treelike} treelike
18
+ * @param {Maplike} maplike
19
19
  * @param {ValueKeyFn|null} mapFn
20
20
  * @param {ReduceFn} reduceFn
21
21
  */
22
- export default async function mapReduce(treelike, mapFn, reduceFn) {
23
- const tree = from(treelike);
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 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
- });
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, keys, tree);
48
+ return reduceFn(values, treeKeys, map);
44
49
  }
@@ -1,36 +1,39 @@
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 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("@weborigami/types").AsyncTree} AsyncTree
12
- * @typedef {import("../../index.ts").Treelike} Treelike
13
+ * @typedef {import("../../index.ts").Maplike} Maplike
13
14
  *
14
- * @param {Treelike} aTreelike
15
- * @param {Treelike} bTreelike
16
- * @returns {Promise<AsyncTree>}
15
+ * @param {Maplike} aMaplike
16
+ * @param {Maplike} bMaplike
17
+ * @returns {Promise<AsyncMap>}
17
18
  */
18
- export default async function mask(aTreelike, bTreelike) {
19
- const aTree = await getTreeArgument(aTreelike, "filter", { position: 0 });
20
- const bTree = await getTreeArgument(bTreelike, "filter", {
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 bTree.get(key);
31
+ const bValue = await bMap.get(key);
29
32
  if (!bValue) {
30
33
  return undefined;
31
34
  }
32
- let aValue = await aTree.get(key);
33
- if (isTreelike(aValue)) {
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 = [...(await aTree.keys())];
44
- const bValues = await Promise.all(aKeys.map((key) => bTree.get(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) || isAsyncTree(bValues[index])
52
+ trailingSlash.has(key) || isMap(bValues[index])
50
53
  )
51
54
  );
52
55
  // Remove keys that don't have values in b
53
- const keys = aKeySlashes.filter((key, index) => bValues[index] ?? false);
54
- return keys;
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
  }
@@ -1,4 +1,5 @@
1
- import isAsyncTree from "./isAsyncTree.js";
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 {import("@weborigami/types").AsyncTree} AsyncTree
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 {Treelike} [keys]
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
- !(isAsyncTree(resultFn) && "parent" in resultFn)
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
- return typeof keys === "function" ? await keys() : keys;
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
  }
@@ -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 isAsyncTree from "./isAsyncTree.js";
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 async trees.
10
- * Given a set of trees, the `get` method looks at each tree in turn, starting
11
- * from the *last* tree and working backwards to the first. If a tree returns a
12
- * defined value for the key, that value is returned. If none of the trees
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").Treelike} Treelike
19
+ * @typedef {import("../../index.ts").Maplike} Maplike
18
20
  *
19
- * @param {Treelike[]} sources
20
- * @returns {(AsyncTree & { description?: string, trees?: AsyncTree[]}) | PlainObject}
21
+ * @param {Maplike[]} treelikes
22
+ * @returns {Promise}
21
23
  */
22
- export default function merge(...sources) {
23
- const filtered = sources.filter((source) => source);
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
- filtered.every((source) => !isAsyncTree(source) && isPlainObject(source))
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 trees = filtered.map((treelike) => from(treelike));
37
+ const sources = unpacked.map((maplike) => from(maplike));
33
38
 
34
- if (trees.length === 0) {
39
+ if (sources.length === 0) {
35
40
  throw new TypeError("merge: all trees are null or undefined");
36
- } else if (trees.length === 1) {
41
+ } else if (sources.length === 1) {
37
42
  // Only one tree, no need to merge
38
- return trees[0];
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 = trees.length - 1; index >= 0; index--) {
47
- const tree = trees[index];
48
- const value = await tree.get(key);
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 keys = new Set();
64
+ async *keys() {
65
+ const treeKeys = new Set();
58
66
  // Collect keys in the order the trees were provided.
59
- for (const tree of trees) {
60
- for (const key of await tree.keys()) {
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
- keys.delete(alternateKey);
72
+ treeKeys.delete(alternateKey);
65
73
  }
66
74
 
67
- keys.add(key);
75
+ treeKeys.add(key);
68
76
  }
69
77
  }
70
- return keys;
78
+ yield* treeKeys;
71
79
  },
72
80
 
73
- get trees() {
74
- return trees;
75
- },
76
- };
81
+ sources,
82
+
83
+ trailingSlashKeys: true,
84
+ });
77
85
  }