@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,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
  }
@@ -1,23 +1,27 @@
1
+ import AsyncMap from "../drivers/AsyncMap.js";
2
+ import SyncMap from "../drivers/SyncMap.js";
1
3
  import * as trailingSlash from "../trailingSlash.js";
2
- import getTreeArgument from "../utilities/getTreeArgument.js";
4
+ import getMapArgument from "../utilities/getMapArgument.js";
5
+ import keys from "./keys.js";
3
6
 
4
7
  /**
5
- * Return a new grouping of the treelike's values into chunks of the specified
8
+ * Return a new grouping of the map's values into chunks of the specified
6
9
  * size.
7
10
  *
8
- * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
9
- * @typedef {import("../../index.ts").Treelike} Treelike
11
+ * @typedef {import("../../index.ts").Maplike} Maplike
10
12
  *
11
- * @param {Treelike} treelike
13
+ * @param {Maplike} maplike
12
14
  * @param {number} [size=10]
13
15
  */
14
- export default async function paginate(treelike, size = 10) {
15
- const tree = await getTreeArgument(treelike, "paginate");
16
+ export default async function paginate(maplike, size = 10) {
17
+ const source = await getMapArgument(maplike, "paginate");
16
18
 
17
- const keys = Array.from(await tree.keys());
18
- const pageCount = Math.ceil(keys.length / size);
19
+ const treeKeys = await keys(source);
20
+ const pageCount = Math.ceil(treeKeys.length / size);
21
+
22
+ const paginated = Object.assign(new AsyncMap(), {
23
+ description: "paginate",
19
24
 
20
- const paginated = {
21
25
  async get(pageKey) {
22
26
  const normalized = trailingSlash.remove(pageKey);
23
27
  // Note: page numbers are 1-based.
@@ -27,14 +31,14 @@ export default async function paginate(treelike, size = 10) {
27
31
  }
28
32
  const nextPage = pageNumber + 1 <= pageCount ? pageNumber + 1 : null;
29
33
  const previousPage = pageNumber - 1 >= 1 ? pageNumber - 1 : null;
30
- const items = new Map();
34
+ const items = new SyncMap();
31
35
  for (
32
36
  let index = (pageNumber - 1) * size;
33
- index < Math.min(keys.length, pageNumber * size);
37
+ index < Math.min(treeKeys.length, pageNumber * size);
34
38
  index++
35
39
  ) {
36
- const key = keys[index];
37
- items.set(key, await tree.get(keys[index]));
40
+ const key = treeKeys[index];
41
+ items.set(key, await source.get(treeKeys[index]));
38
42
  }
39
43
 
40
44
  return {
@@ -46,11 +50,15 @@ export default async function paginate(treelike, size = 10) {
46
50
  };
47
51
  },
48
52
 
49
- async keys() {
50
- // Return an array from 1..totalPages
51
- return Array.from({ length: pageCount }, (_, index) => index + 1);
53
+ async *keys() {
54
+ // Return from 1..totalPages
55
+ yield* Array.from({ length: pageCount }, (_, index) => index + 1);
52
56
  },
53
- };
57
+
58
+ source: source,
59
+
60
+ trailingSlashKeys: false,
61
+ });
54
62
 
55
63
  return paginated;
56
64
  }
@@ -1,13 +1,13 @@
1
- import getTreeArgument from "../utilities/getTreeArgument.js";
1
+ import getMapArgument from "../utilities/getMapArgument.js";
2
2
 
3
3
  /**
4
- * Returns the parent of the current tree.
4
+ * Returns the parent of the map, if any.
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 parent(treelike) {
11
- const tree = await getTreeArgument(treelike, "parent");
12
- return tree.parent;
10
+ export default async function parent(maplike) {
11
+ const map = await getMapArgument(maplike, "parent");
12
+ return "parent" in map ? map.parent : undefined;
13
13
  }
@@ -1,47 +1,45 @@
1
1
  import * as trailingSlash from "../trailingSlash.js";
2
+ import getMapArgument from "../utilities/getMapArgument.js";
2
3
  import from from "./from.js";
3
- import isAsyncTree from "./isAsyncTree.js";
4
+ import isMap from "./isMap.js";
5
+ import isMaplike from "./isMaplike.js";
4
6
 
5
7
  /**
6
8
  * Returns slash-separated paths for all values in the tree.
7
9
  *
8
10
  * The `base` argument is prepended to all paths.
9
11
  *
10
- * If `assumeSlashes` is true, then keys are assumed to have trailing slashes to
11
- * indicate subtrees. The default value of this option is false.
12
+ * @typedef {import("../../index.ts").Maplike} Maplike
12
13
  *
13
- * @typedef {import("../../index.ts").Treelike} Treelike
14
- *
15
- * @param {Treelike} treelike
16
- * @param {{ assumeSlashes?: boolean, base?: string }} options
14
+ * @param {Maplike} maplike
15
+ * @param {{ base?: string }} options
17
16
  */
18
- export default async function paths(treelike, options = {}) {
19
- const tree = from(treelike);
17
+ export default async function paths(maplike, options = {}) {
18
+ const tree = await getMapArgument(maplike, "paths");
20
19
  const base = options.base ?? "";
21
- const assumeSlashes = options.assumeSlashes ?? false;
22
20
  const result = [];
23
- for (const key of await tree.keys()) {
21
+ for await (const key of tree.keys()) {
24
22
  const separator = trailingSlash.has(base) ? "" : "/";
25
23
  const valuePath = base ? `${base}${separator}${key}` : key;
26
- let isSubtree;
27
24
  let value;
28
- if (assumeSlashes) {
25
+ if (/** @type {any} */ (tree).trailingSlashKeys) {
29
26
  // Subtree needs to have a trailing slash
30
- isSubtree = trailingSlash.has(key);
31
- if (isSubtree) {
27
+ if (trailingSlash.has(key)) {
32
28
  // We'll need the value to recurse
33
29
  value = await tree.get(key);
30
+ // If it's maplike, treat as subtree
31
+ if (isMaplike(value)) {
32
+ value = from(value);
33
+ }
34
34
  }
35
35
  } else {
36
- // Get value and check
36
+ // Get value
37
37
  value = await tree.get(key);
38
38
  }
39
- if (value) {
40
- // If we got the value we can check if it's a subtree
41
- isSubtree = isAsyncTree(value);
42
- }
43
- if (isSubtree) {
44
- const subPaths = await paths(value, { assumeSlashes, base: valuePath });
39
+
40
+ if (isMap(value)) {
41
+ // Subtree; recurse
42
+ const subPaths = await paths(value, { base: valuePath });
45
43
  result.push(...subPaths);
46
44
  } else {
47
45
  result.push(valuePath);
@@ -1,4 +1,4 @@
1
- import getTreeArgument from "../utilities/getTreeArgument.js";
1
+ import getMapArgument from "../utilities/getMapArgument.js";
2
2
  import toPlainValue from "../utilities/toPlainValue.js";
3
3
 
4
4
  /**
@@ -20,15 +20,15 @@ import toPlainValue from "../utilities/toPlainValue.js";
20
20
  * array `["b", "a", "c" ]` because the tree has the keys in that order. The
21
21
  * specific values of the keys (0, 1, and 2) are ignored.
22
22
  *
23
- * @typedef {import("../../index.ts").Treelike} Treelike
23
+ * @typedef {import("../../index.ts").Maplike} Maplike
24
24
  * @typedef {import("../../index.ts").PlainObject} PlainObject
25
25
  *
26
- * @param {Treelike} treelike
26
+ * @param {Maplike} maplike
27
27
  */
28
- export default async function plain(treelike) {
29
- if (treelike instanceof Function) {
28
+ export default async function plain(maplike) {
29
+ if (maplike instanceof Function) {
30
30
  throw new TypeError("plain: can't convert a function to a plain object");
31
31
  }
32
- const tree = await getTreeArgument(treelike, "plain");
32
+ const tree = await getMapArgument(maplike, "plain");
33
33
  return toPlainValue(tree);
34
34
  }
@@ -0,0 +1,16 @@
1
+ import getMapArgument from "../utilities/getMapArgument.js";
2
+ import mapReduce from "./mapReduce.js";
3
+
4
+ /**
5
+ * Reduce a tree by recursively applying a reducer function to its nodes.
6
+ *
7
+ * @typedef {import("../../index.ts").Maplike} Maplike
8
+ * @typedef {import("../../index.ts").ReduceFn} ReduceFn
9
+ *
10
+ * @param {Maplike} maplike
11
+ * @param {ReduceFn} reduceFn
12
+ */
13
+ export default async function reduce(maplike, reduceFn) {
14
+ const map = await getMapArgument(maplike, "reduce");
15
+ return mapReduce(map, null, reduceFn);
16
+ }
@@ -1,6 +1,8 @@
1
+ import AsyncMap from "../drivers/AsyncMap.js";
2
+ import SyncMap from "../drivers/SyncMap.js";
1
3
  import * as trailingSlash from "../trailingSlash.js";
2
- import getTreeArgument from "../utilities/getTreeArgument.js";
3
- import isAsyncTree from "./isAsyncTree.js";
4
+ import getMapArgument from "../utilities/getMapArgument.js";
5
+ import isMap from "./isMap.js";
4
6
 
5
7
  /**
6
8
  * A tree whose keys are strings interpreted as regular expressions.
@@ -10,16 +12,19 @@ import isAsyncTree from "./isAsyncTree.js";
10
12
  * taken to match the entire key -- if they do not already start and end with
11
13
  * `^` and `$` respectively, those are added.
12
14
  *
13
- * @type {import("../../index.ts").TreeTransform}
15
+ * @typedef {import("../../index.ts").Maplike} Maplike
16
+ *
17
+ * @param {Maplike} maplike
18
+ * @returns {Promise<AsyncMap>}
14
19
  */
15
- export default async function regExpKeys(treelike) {
16
- const tree = await getTreeArgument(treelike, "regExpKeys");
20
+ export default async function regExpKeys(maplike) {
21
+ const source = await getMapArgument(maplike, "regExpKeys", { deep: true });
17
22
 
18
- const map = new Map();
23
+ const map = new SyncMap();
19
24
 
20
25
  // We build the output tree first so that we can refer to it when setting
21
26
  // `parent` on subtrees below.
22
- let result = {
27
+ let result = Object.assign(new AsyncMap(), {
23
28
  // @ts-ignore
24
29
  description: "regExpKeys",
25
30
 
@@ -39,24 +44,28 @@ export default async function regExpKeys(treelike) {
39
44
  return undefined;
40
45
  },
41
46
 
42
- async keys() {
47
+ async *keys() {
43
48
  return map.keys();
44
49
  },
45
- };
50
+
51
+ source: source,
52
+
53
+ trailingSlashKeys: false,
54
+ });
46
55
 
47
56
  // Turn the input tree's string keys into regular expressions, then map those
48
57
  // to the corresponding values.
49
- for (const key of await tree.keys()) {
58
+ for await (const key of source.keys()) {
50
59
  if (typeof key !== "string") {
51
60
  // Skip non-string keys.
52
61
  continue;
53
62
  }
54
63
 
55
64
  // Get value.
56
- let value = await tree.get(key);
65
+ let value = await source.get(key);
57
66
 
58
67
  let regExp;
59
- if (trailingSlash.has(key) || isAsyncTree(value)) {
68
+ if (trailingSlash.has(key) || isMap(value)) {
60
69
  const baseKey = trailingSlash.remove(key);
61
70
  regExp = new RegExp("^" + baseKey + "/?$");
62
71
  // Subtree
@@ -1,26 +1,32 @@
1
- import getTreeArgument from "../utilities/getTreeArgument.js";
1
+ import AsyncMap from "../drivers/AsyncMap.js";
2
+ import getMapArgument from "../utilities/getMapArgument.js";
3
+ import keys from "./keys.js";
2
4
 
3
5
  /**
4
- * Reverse the order of the top-level keys in the tree.
6
+ * Return a new map with the keys reversed.
5
7
  *
6
- * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
7
- * @typedef {import("../../index.ts").Treelike} Treelike
8
+ * @typedef {import("../../index.ts").Maplike} Maplike
8
9
  *
9
- * @param {Treelike} treelike
10
- * @returns {Promise<AsyncTree>}
10
+ * @param {Maplike} maplike
11
+ * @returns {Promise<AsyncMap>}
11
12
  */
12
- export default async function reverse(treelike) {
13
- const tree = await getTreeArgument(treelike, "reverse");
13
+ export default async function reverse(maplike) {
14
+ const source = await getMapArgument(maplike, "reverse");
15
+ return Object.assign(new AsyncMap(), {
16
+ description: "reverse",
14
17
 
15
- return {
16
18
  async get(key) {
17
- return tree.get(key);
19
+ return source.get(key);
18
20
  },
19
21
 
20
- async keys() {
21
- const keys = Array.from(await tree.keys());
22
- keys.reverse();
23
- return keys;
22
+ async *keys() {
23
+ const treeKeys = await keys(source);
24
+ treeKeys.reverse();
25
+ yield* treeKeys;
24
26
  },
25
- };
27
+
28
+ source,
29
+
30
+ trailingSlashKeys: /** @type {any} */ (source).trailingSlashKeys,
31
+ });
26
32
  }
@@ -1,15 +1,16 @@
1
1
  import * as symbols from "../symbols.js";
2
- import from from "./from.js";
2
+ import getMapArgument from "../utilities/getMapArgument.js";
3
3
 
4
4
  /**
5
5
  * Walk up the `parent` chain to find the root of the tree.
6
6
  *
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 function root(treelike) {
12
- let current = from(treelike);
11
+ export default function root(maplike) {
12
+ /** @type {any} */
13
+ let current = getMapArgument(maplike, "root");
13
14
  while (current.parent || current[symbols.parent]) {
14
15
  current = current.parent || current[symbols.parent];
15
16
  }
@@ -1,48 +1,49 @@
1
- import getTreeArgument from "../utilities/getTreeArgument.js";
1
+ import AsyncMap from "../drivers/AsyncMap.js";
2
+ import getMapArgument from "../utilities/getMapArgument.js";
2
3
 
3
4
  /**
4
- * A tree's "scope" is the collection of everything in that tree and all of its
5
+ * A map's "scope" is the collection of everything in that map and all of its
5
6
  * ancestors.
6
7
  *
7
- * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
8
- * @typedef {import("../../index.ts").Treelike} Treelike
8
+ * @typedef {import("../../index.ts").Maplike} Maplike
9
9
  *
10
- * @param {Treelike} treelike
11
- * @returns {Promise<AsyncTree & {trees: AsyncTree[]}>}
10
+ * @param {Maplike} maplike
11
+ * @returns {Promise<AsyncMap>}
12
12
  */
13
- export default async function scope(treelike) {
14
- const tree = await getTreeArgument(treelike, "scope");
13
+ export default async function scope(maplike) {
14
+ const source = await getMapArgument(maplike, "scope");
15
15
 
16
- return {
17
- // Starting with this tree, search up the parent hierarchy.
16
+ return Object.assign(new AsyncMap(), {
17
+ description: "scope",
18
+
19
+ // Starting with this map, search up the parent hierarchy.
18
20
  async get(key) {
19
- /** @type {AsyncTree|null|undefined} */
20
- let current = tree;
21
+ /** @type {Map|AsyncMap|null} */
22
+ let current = source;
21
23
  let value;
22
24
  while (current) {
23
25
  value = await current.get(key);
24
26
  if (value !== undefined) {
25
27
  break;
26
28
  }
27
- current = current.parent;
29
+ current = "parent" in current ? current.parent : null;
28
30
  }
29
31
  return value;
30
32
  },
31
33
 
32
34
  // Collect all keys for this tree and all parents
33
- async keys() {
34
- const keys = new Set();
35
-
36
- /** @type {AsyncTree|null|undefined} */
37
- let current = tree;
35
+ async *keys() {
36
+ const scopeKeys = new Set();
37
+ /** @type {Map|AsyncMap|null} */
38
+ let current = source;
38
39
  while (current) {
39
- for (const key of await current.keys()) {
40
- keys.add(key);
40
+ for await (const key of current.keys()) {
41
+ scopeKeys.add(key);
41
42
  }
42
- current = current.parent;
43
+ current = "parent" in current ? current.parent : null;
43
44
  }
44
45
 
45
- return keys;
46
+ yield* scopeKeys;
46
47
  },
47
48
 
48
49
  // Collect all keys for this tree and all parents.
@@ -52,14 +53,18 @@ export default async function scope(treelike) {
52
53
  get trees() {
53
54
  const result = [];
54
55
 
55
- /** @type {AsyncTree|null|undefined} */
56
- let current = tree;
56
+ /** @type {Map|AsyncMap|null} */
57
+ let current = source;
57
58
  while (current) {
58
59
  result.push(current);
59
- current = current.parent;
60
+ current = "parent" in current ? current.parent : null;
60
61
  }
61
62
 
62
63
  return result;
63
64
  },
64
- };
65
+
66
+ source: source,
67
+
68
+ trailingSlashKeys: /** @type {any} */ (source).trailingSlashKeys,
69
+ });
65
70
  }
@@ -0,0 +1,20 @@
1
+ import getMapArgument from "../utilities/getMapArgument.js";
2
+
3
+ /**
4
+ * Set a key/value pair in a map.
5
+ *
6
+ * @typedef {import("../../index.ts").Maplike} Maplike
7
+ *
8
+ * @param {Maplike} maplike
9
+ * @param {any} key
10
+ * @param {any} value
11
+ */
12
+ export default async function set(maplike, key, value) {
13
+ const map = await getMapArgument(maplike, "set");
14
+ await map.set(key, value);
15
+
16
+ // Unlike Map.prototype.set, we return undefined. This is more useful when
17
+ // calling set in the console -- return the complete tree would result in it
18
+ // being dumped to the console.
19
+ return undefined;
20
+ }