@weborigami/async-tree 0.5.4 → 0.5.5

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 (158) hide show
  1. package/index.ts +16 -6
  2. package/package.json +2 -2
  3. package/shared.js +20 -30
  4. package/src/Tree.js +62 -513
  5. package/src/constants.js +2 -0
  6. package/src/drivers/BrowserFileTree.js +9 -10
  7. package/src/drivers/DeepMapTree.js +3 -3
  8. package/src/drivers/DeepObjectTree.js +4 -5
  9. package/src/drivers/DeferredTree.js +2 -2
  10. package/src/drivers/FileTree.js +11 -33
  11. package/src/drivers/FunctionTree.js +1 -1
  12. package/src/drivers/MapTree.js +6 -6
  13. package/src/drivers/ObjectTree.js +4 -3
  14. package/src/drivers/SetTree.js +1 -1
  15. package/src/drivers/SiteTree.js +1 -1
  16. package/src/drivers/constantTree.js +1 -1
  17. package/src/extension.js +5 -3
  18. package/src/jsonKeys.js +5 -7
  19. package/src/operations/addNextPrevious.js +10 -9
  20. package/src/operations/assign.js +40 -0
  21. package/src/operations/cache.js +18 -12
  22. package/src/operations/cachedKeyFunctions.js +15 -4
  23. package/src/operations/clear.js +20 -0
  24. package/src/operations/concat.js +17 -0
  25. package/src/operations/deepMap.js +25 -0
  26. package/src/operations/deepMerge.js +11 -25
  27. package/src/operations/deepReverse.js +6 -7
  28. package/src/operations/deepTake.js +6 -7
  29. package/src/operations/deepText.js +4 -4
  30. package/src/operations/deepValuesIterator.js +8 -6
  31. package/src/operations/defineds.js +32 -0
  32. package/src/operations/delete.js +20 -0
  33. package/src/operations/entries.js +16 -0
  34. package/src/operations/extensionKeyFunctions.js +1 -1
  35. package/src/operations/filter.js +7 -8
  36. package/src/operations/first.js +18 -0
  37. package/src/operations/forEach.js +20 -0
  38. package/src/operations/from.js +77 -0
  39. package/src/operations/fromFn.js +26 -0
  40. package/src/operations/globKeys.js +8 -8
  41. package/src/operations/group.js +9 -7
  42. package/src/operations/has.js +16 -0
  43. package/src/operations/indent.js +4 -2
  44. package/src/operations/inners.js +29 -0
  45. package/src/operations/invokeFunctions.js +5 -4
  46. package/src/operations/isAsyncMutableTree.js +15 -0
  47. package/src/operations/isAsyncTree.js +21 -0
  48. package/src/operations/isTraversable.js +15 -0
  49. package/src/operations/isTreelike.js +33 -0
  50. package/src/operations/json.js +4 -3
  51. package/src/operations/keys.js +14 -0
  52. package/src/operations/length.js +15 -0
  53. package/src/operations/map.js +151 -95
  54. package/src/operations/mapExtension.js +27 -0
  55. package/src/operations/mapReduce.js +44 -0
  56. package/src/operations/mask.js +18 -16
  57. package/src/operations/match.js +74 -0
  58. package/src/operations/merge.js +22 -20
  59. package/src/operations/paginate.js +3 -5
  60. package/src/operations/parent.js +13 -0
  61. package/src/operations/paths.js +51 -0
  62. package/src/operations/plain.js +34 -0
  63. package/src/operations/regExpKeys.js +4 -5
  64. package/src/operations/remove.js +14 -0
  65. package/src/operations/reverse.js +4 -6
  66. package/src/operations/root.js +17 -0
  67. package/src/operations/scope.js +4 -6
  68. package/src/operations/setDeep.js +50 -0
  69. package/src/operations/shuffle.js +46 -0
  70. package/src/operations/sort.js +19 -12
  71. package/src/operations/take.js +3 -5
  72. package/src/operations/text.js +3 -3
  73. package/src/operations/toFunction.js +14 -0
  74. package/src/operations/traverse.js +25 -0
  75. package/src/operations/traverseOrThrow.js +64 -0
  76. package/src/operations/traversePath.js +16 -0
  77. package/src/operations/values.js +15 -0
  78. package/src/operations/withKeys.js +33 -0
  79. package/src/utilities/TypedArray.js +2 -0
  80. package/src/utilities/box.js +20 -0
  81. package/src/utilities/castArraylike.js +38 -0
  82. package/src/utilities/getParent.js +33 -0
  83. package/src/utilities/getRealmObjectPrototype.js +19 -0
  84. package/src/utilities/getTreeArgument.js +43 -0
  85. package/src/utilities/isPacked.js +20 -0
  86. package/src/utilities/isPlainObject.js +29 -0
  87. package/src/utilities/isPrimitive.js +13 -0
  88. package/src/utilities/isStringlike.js +25 -0
  89. package/src/utilities/isUnpackable.js +13 -0
  90. package/src/utilities/keysFromPath.js +34 -0
  91. package/src/utilities/naturalOrder.js +9 -0
  92. package/src/utilities/pathFromKeys.js +18 -0
  93. package/src/utilities/setParent.js +38 -0
  94. package/src/utilities/toFunction.js +41 -0
  95. package/src/utilities/toPlainValue.js +95 -0
  96. package/src/utilities/toString.js +37 -0
  97. package/test/drivers/ExplorableSiteTree.test.js +1 -1
  98. package/test/drivers/FileTree.test.js +1 -1
  99. package/test/drivers/calendarTree.test.js +1 -1
  100. package/test/jsonKeys.test.js +1 -1
  101. package/test/operations/assign.test.js +54 -0
  102. package/test/operations/cache.test.js +1 -1
  103. package/test/operations/cachedKeyFunctions.test.js +16 -16
  104. package/test/operations/clear.test.js +34 -0
  105. package/test/operations/deepMap.test.js +29 -0
  106. package/test/operations/deepMerge.test.js +2 -6
  107. package/test/operations/deepReverse.test.js +1 -1
  108. package/test/operations/defineds.test.js +25 -0
  109. package/test/operations/delete.test.js +20 -0
  110. package/test/operations/entries.test.js +18 -0
  111. package/test/operations/extensionKeyFunctions.test.js +10 -10
  112. package/test/operations/first.test.js +15 -0
  113. package/test/operations/fixtures/README.md +1 -0
  114. package/test/operations/forEach.test.js +22 -0
  115. package/test/operations/from.test.js +67 -0
  116. package/test/operations/globKeys.test.js +3 -3
  117. package/test/operations/has.test.js +15 -0
  118. package/test/operations/inners.test.js +30 -0
  119. package/test/operations/invokeFunctions.test.js +1 -1
  120. package/test/operations/isAsyncMutableTree.test.js +17 -0
  121. package/test/operations/isAsyncTree.test.js +26 -0
  122. package/test/operations/isTreelike.test.js +13 -0
  123. package/test/operations/keys.test.js +15 -0
  124. package/test/operations/length.test.js +15 -0
  125. package/test/operations/map.test.js +61 -42
  126. package/test/operations/mapExtension.test.js +0 -0
  127. package/test/operations/mapReduce.test.js +23 -0
  128. package/test/operations/mask.test.js +1 -1
  129. package/test/operations/match.test.js +33 -0
  130. package/test/operations/merge.test.js +23 -9
  131. package/test/operations/parent.test.js +15 -0
  132. package/test/operations/paths.test.js +40 -0
  133. package/test/operations/plain.test.js +69 -0
  134. package/test/operations/reverse.test.js +1 -1
  135. package/test/operations/scope.test.js +1 -1
  136. package/test/operations/setDeep.test.js +53 -0
  137. package/test/operations/shuffle.test.js +18 -0
  138. package/test/operations/sort.test.js +3 -3
  139. package/test/operations/toFunction.test.js +16 -0
  140. package/test/operations/traverse.test.js +43 -0
  141. package/test/operations/traversePath.test.js +16 -0
  142. package/test/operations/values.test.js +18 -0
  143. package/test/operations/withKeys.test.js +21 -0
  144. package/test/utilities/box.test.js +26 -0
  145. package/test/utilities/getRealmObjectPrototype.test.js +11 -0
  146. package/test/utilities/isPlainObject.test.js +13 -0
  147. package/test/utilities/keysFromPath.test.js +14 -0
  148. package/test/utilities/naturalOrder.test.js +11 -0
  149. package/test/utilities/pathFromKeys.test.js +12 -0
  150. package/test/utilities/setParent.test.js +34 -0
  151. package/test/utilities/toFunction.test.js +34 -0
  152. package/test/utilities/toPlainValue.test.js +27 -0
  153. package/test/utilities/toString.test.js +22 -0
  154. package/src/Tree.d.ts +0 -24
  155. package/src/utilities.d.ts +0 -21
  156. package/src/utilities.js +0 -443
  157. package/test/Tree.test.js +0 -407
  158. package/test/utilities.test.js +0 -141
@@ -1,4 +1,4 @@
1
- import { Tree } from "../internal.js";
1
+ import from from "../operations/from.js";
2
2
 
3
3
  /**
4
4
  * A tree that is loaded lazily.
@@ -64,7 +64,7 @@ export default class DeferredTree {
64
64
  this.treePromise ??= this.loadResult().then((treelike) => {
65
65
  const options =
66
66
  this._deep !== undefined ? { deep: this._deep } : undefined;
67
- this._tree = Tree.from(treelike, options);
67
+ this._tree = from(treelike, options);
68
68
  if (this._parentUntilLoaded) {
69
69
  // Now that the tree has been loaded, we can set its parent if it hasn't
70
70
  // already been set.
@@ -1,16 +1,15 @@
1
1
  import * as fs from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { fileURLToPath, pathToFileURL } from "node:url";
4
- import { Tree } from "../internal.js";
4
+ import { hiddenFileNames } from "../constants.js";
5
+ import assign from "../operations/assign.js";
6
+ import isTreelike from "../operations/isTreelike.js";
5
7
  import * as trailingSlash from "../trailingSlash.js";
6
- import {
7
- getRealmObjectPrototype,
8
- hiddenFileNames,
9
- isPacked,
10
- isPlainObject,
11
- naturalOrder,
12
- setParent,
13
- } from "../utilities.js";
8
+ import isPacked from "../utilities/isPacked.js";
9
+ import isPlainObject from "../utilities/isPlainObject.js";
10
+ import isStringlike from "../utilities/isStringlike.js";
11
+ import naturalOrder from "../utilities/naturalOrder.js";
12
+ import setParent from "../utilities/setParent.js";
14
13
  import limitConcurrency from "./limitConcurrency.js";
15
14
 
16
15
  // As of July 2025, Node doesn't provide any way to limit the number of
@@ -188,7 +187,7 @@ export default class FileTree {
188
187
  // Pack the value for writing.
189
188
  value = await value.pack();
190
189
  packed = true;
191
- } else if (isStringLike(value)) {
190
+ } else if (isStringlike(value)) {
192
191
  // Value has a meaningful `toString` method, use that.
193
192
  value = String(value);
194
193
  packed = true;
@@ -199,13 +198,13 @@ export default class FileTree {
199
198
  } else if (isPlainObject(value) && Object.keys(value).length === 0) {
200
199
  // Special case: empty object means create an empty directory.
201
200
  await fs.mkdir(destPath, { recursive: true });
202
- } else if (Tree.isTreelike(value)) {
201
+ } else if (isTreelike(value)) {
203
202
  // Treat value as a subtree and write it out as a subdirectory.
204
203
  const destTree = Reflect.construct(this.constructor, [destPath]);
205
204
  // Create the directory here, even if the subtree is empty.
206
205
  await fs.mkdir(destPath, { recursive: true });
207
206
  // Write out the subtree.
208
- await Tree.assign(destTree, value);
207
+ await assign(destTree, value);
209
208
  } else {
210
209
  const typeName = value?.constructor?.name ?? "unknown";
211
210
  throw new TypeError(
@@ -238,27 +237,6 @@ async function isDirectory(entry, dirname) {
238
237
  return entry.isDirectory();
239
238
  }
240
239
 
241
- /**
242
- * Return true if the object is a string or object with a non-trival `toString`
243
- * method.
244
- *
245
- * @param {any} obj
246
- */
247
- function isStringLike(obj) {
248
- if (typeof obj === "string") {
249
- return true;
250
- } else if (obj?.toString === undefined) {
251
- return false;
252
- } else if (obj.toString === getRealmObjectPrototype(obj)?.toString) {
253
- // The stupid Object.prototype.toString implementation always returns
254
- // "[object Object]", so if that's the only toString method the object has,
255
- // we return false.
256
- return false;
257
- } else {
258
- return true;
259
- }
260
- }
261
-
262
240
  // Return the file information for the file/folder at the given path.
263
241
  // If it does not exist, return undefined.
264
242
  async function stat(filePath) {
@@ -1,4 +1,4 @@
1
- import { setParent } from "../utilities.js";
1
+ import setParent from "../utilities/setParent.js";
2
2
 
3
3
  /**
4
4
  * A tree defined by a function and an optional domain.
@@ -1,6 +1,6 @@
1
- import { Tree } from "../internal.js";
1
+ import isAsyncTree from "../operations/isAsyncTree.js";
2
2
  import * as trailingSlash from "../trailingSlash.js";
3
- import { setParent } from "../utilities.js";
3
+ import setParent from "../utilities/setParent.js";
4
4
 
5
5
  /**
6
6
  * A tree backed by a JavaScript `Map` object.
@@ -19,10 +19,10 @@ export default class MapTree {
19
19
  * Constructs a new `MapTree` instance. This `iterable` parameter can be a
20
20
  * `Map` instance, or any other iterable of key-value pairs.
21
21
  *
22
- * @param {Iterable} [iterable]
22
+ * @param {Iterable} [source]
23
23
  */
24
- constructor(iterable = []) {
25
- this.map = new Map(iterable);
24
+ constructor(source = []) {
25
+ this.map = source instanceof Map ? source : new Map(source);
26
26
  this.parent = null;
27
27
  }
28
28
 
@@ -52,7 +52,7 @@ export default class MapTree {
52
52
 
53
53
  /** @returns {boolean} */
54
54
  isSubtree(value) {
55
- return Tree.isAsyncTree(value);
55
+ return isAsyncTree(value);
56
56
  }
57
57
 
58
58
  async keys() {
@@ -1,7 +1,8 @@
1
- import { Tree } from "../internal.js";
1
+ import isAsyncTree from "../operations/isAsyncTree.js";
2
2
  import * as symbols from "../symbols.js";
3
3
  import * as trailingSlash from "../trailingSlash.js";
4
- import { getRealmObjectPrototype, setParent } from "../utilities.js";
4
+ import getRealmObjectPrototype from "../utilities/getRealmObjectPrototype.js";
5
+ import setParent from "../utilities/setParent.js";
5
6
 
6
7
  /**
7
8
  * A tree defined by a plain object or array.
@@ -72,7 +73,7 @@ export default class ObjectTree {
72
73
 
73
74
  /** @returns {boolean} */
74
75
  isSubtree(value) {
75
- return Tree.isAsyncTree(value);
76
+ return isAsyncTree(value);
76
77
  }
77
78
 
78
79
  /**
@@ -1,4 +1,4 @@
1
- import { setParent } from "../utilities.js";
1
+ import setParent from "../utilities/setParent.js";
2
2
 
3
3
  /**
4
4
  * A tree of Set objects.
@@ -1,5 +1,5 @@
1
1
  import * as trailingSlash from "../trailingSlash.js";
2
- import { setParent } from "../utilities.js";
2
+ import setParent from "../utilities/setParent.js";
3
3
 
4
4
  /**
5
5
  * A tree of values obtained via HTTP/HTTPS calls. These values will be strings
@@ -1,4 +1,4 @@
1
- import { trailingSlash } from "../../main.js";
1
+ import * as trailingSlash from "../trailingSlash.js";
2
2
 
3
3
  /**
4
4
  * A tree that returns a constant value for any key. If the key ends with a
package/src/extension.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import * as trailingSlash from "./trailingSlash.js";
2
- import { isStringLike, toString } from "./utilities.js";
2
+ import isStringlike from "./utilities/isStringlike.js";
3
+ import toString from "./utilities/toString.js";
3
4
 
4
5
  /**
5
6
  * Replicate the logic of Node POSIX path.extname at
@@ -78,7 +79,7 @@ export function extname(path) {
78
79
  * dot.
79
80
  */
80
81
  export function match(key, ext) {
81
- if (!isStringLike(key)) {
82
+ if (!isStringlike(key)) {
82
83
  return null;
83
84
  }
84
85
  key = toString(key);
@@ -113,9 +114,10 @@ export function match(key, ext) {
113
114
  * @param {string} resultExtension
114
115
  */
115
116
  export function replace(key, sourceExtension, resultExtension) {
116
- if (!isStringLike(key)) {
117
+ if (!isStringlike(key)) {
117
118
  return null;
118
119
  }
120
+ // @ts-ignore
119
121
  key = toString(key);
120
122
 
121
123
  if (!match(key, sourceExtension)) {
package/src/jsonKeys.js CHANGED
@@ -1,6 +1,9 @@
1
- import { Tree } from "./internal.js";
1
+ import from from "./operations/from.js";
2
2
 
3
3
  /**
4
+ * Given a tree node, return a JSON string that can be written to a .keys.json
5
+ * file.
6
+ *
4
7
  * The JSON Keys protocol lets a site expose the keys of a node in the site so
5
8
  * that they can be read by SiteTree.
6
9
  *
@@ -8,13 +11,8 @@ import { Tree } from "./internal.js";
8
11
  * "index.html" for a specific resource available at the node, or a string with
9
12
  * a trailing slash like "about/" for a subtree of that node.
10
13
  */
11
-
12
- /**
13
- * Given a tree node, return a JSON string that can be written to a .keys.json
14
- * file.
15
- */
16
14
  export async function stringify(treelike) {
17
- const tree = Tree.from(treelike);
15
+ const tree = from(treelike);
18
16
  let keys = Array.from(await tree.keys());
19
17
  // Skip the key `.keys.json` if present.
20
18
  keys = keys.filter((key) => key !== ".keys.json");
@@ -1,5 +1,7 @@
1
- import { Tree } from "../internal.js";
2
- import { assertIsTreelike } from "../utilities.js";
1
+ import getTreeArgument from "../utilities/getTreeArgument.js";
2
+ import entries from "./entries.js";
3
+ import isTreelike from "./isTreelike.js";
4
+ import plain from "./plain.js";
3
5
 
4
6
  /**
5
7
  * Add nextKey/previousKey properties to values.
@@ -11,21 +13,20 @@ import { assertIsTreelike } from "../utilities.js";
11
13
  * @returns {Promise<PlainObject|Array>}
12
14
  */
13
15
  export default async function addNextPrevious(treelike) {
14
- assertIsTreelike(treelike, "addNextPrevious");
15
- const tree = Tree.from(treelike);
16
+ const tree = await getTreeArgument(treelike, "addNextPrevious");
16
17
 
17
- const entries = [...(await Tree.entries(tree))];
18
- const keys = entries.map(([key]) => key);
18
+ const treeEntries = [...(await entries(tree))];
19
+ const keys = treeEntries.map(([key]) => key);
19
20
 
20
21
  // Map to an array of [key, result] pairs, where the result includes
21
22
  // nextKey/previousKey properties.
22
23
  const mappedEntries = await Promise.all(
23
- entries.map(async ([key, value], index) => {
24
+ treeEntries.map(async ([key, value], index) => {
24
25
  let resultValue;
25
26
  if (value === undefined) {
26
27
  resultValue = undefined;
27
- } else if (Tree.isTreelike(value)) {
28
- resultValue = await Tree.plain(value);
28
+ } else if (isTreelike(value)) {
29
+ resultValue = await plain(value);
29
30
  } else if (typeof value === "object") {
30
31
  // Clone value to avoid modifying the original object
31
32
  resultValue = { ...value };
@@ -0,0 +1,40 @@
1
+ import from from "./from.js";
2
+ import isAsyncMutableTree from "./isAsyncMutableTree.js";
3
+ import isAsyncTree from "./isAsyncTree.js";
4
+
5
+ /**
6
+ * Apply the key/values pairs from the source tree to the target tree.
7
+ *
8
+ * If a key exists in both trees, and the values in both trees are
9
+ * subtrees, then the subtrees will be merged recursively. Otherwise, the
10
+ * value from the source tree will overwrite the value in the target tree.
11
+ *
12
+ * @typedef {import("../../index.ts").Treelike} Treelike
13
+ *
14
+ * @param {Treelike} target
15
+ * @param {Treelike} source
16
+ */
17
+ export default async function assign(target, source) {
18
+ const targetTree = from(target);
19
+ const sourceTree = from(source);
20
+ if (!isAsyncMutableTree(targetTree)) {
21
+ throw new TypeError("Target must be a mutable asynchronous tree");
22
+ }
23
+ // Fire off requests to update all keys, then wait for all of them to finish.
24
+ const keys = Array.from(await sourceTree.keys());
25
+ const promises = keys.map(async (key) => {
26
+ const sourceValue = await sourceTree.get(key);
27
+ if (isAsyncTree(sourceValue)) {
28
+ const targetValue = await targetTree.get(key);
29
+ if (isAsyncMutableTree(targetValue)) {
30
+ // Both source and target are trees; recurse.
31
+ await assign(targetValue, sourceValue);
32
+ return;
33
+ }
34
+ }
35
+ // Copy the value from the source to the target.
36
+ await /** @type {any} */ (targetTree).set(key, sourceValue);
37
+ });
38
+ await Promise.all(promises);
39
+ return targetTree;
40
+ }
@@ -1,5 +1,8 @@
1
- import { ObjectTree, Tree } from "../internal.js";
2
- import { assertIsTreelike } from "../utilities.js";
1
+ import ObjectTree from "../drivers/ObjectTree.js";
2
+ import getTreeArgument from "../utilities/getTreeArgument.js";
3
+ import from from "./from.js";
4
+ import isAsyncMutableTree from "./isAsyncMutableTree.js";
5
+ import isAsyncTree from "./isAsyncTree.js";
3
6
 
4
7
  /**
5
8
  * Caches values from a source tree in a second cache tree. Cache source tree
@@ -13,18 +16,21 @@ import { assertIsTreelike } from "../utilities.js";
13
16
  *
14
17
  * @param {Treelike} sourceTreelike
15
18
  * @param {AsyncMutableTree} [cacheTreelike]
16
- * @returns {AsyncTree & { description: string }}
19
+ * @returns {Promise}
17
20
  */
18
- export default function treeCache(sourceTreelike, cacheTreelike) {
19
- assertIsTreelike(sourceTreelike, "cache");
20
- const source = Tree.from(sourceTreelike);
21
+ export default async function treeCache(sourceTreelike, cacheTreelike) {
22
+ const source = await getTreeArgument(sourceTreelike, "cache", {
23
+ position: 0,
24
+ });
21
25
 
22
26
  /** @type {AsyncMutableTree} */
23
27
  let cache;
24
28
  if (cacheTreelike) {
29
+ cache = /** @type {any} */ (
30
+ await getTreeArgument(cacheTreelike, "cache", { position: 1 })
31
+ );
25
32
  // @ts-ignore
26
- cache = Tree.from(cacheTreelike);
27
- if (!Tree.isAsyncMutableTree(cache)) {
33
+ if (!isAsyncMutableTree(cache)) {
28
34
  throw new Error("Cache tree must define a set() method.");
29
35
  }
30
36
  } else {
@@ -38,24 +44,24 @@ export default function treeCache(sourceTreelike, cacheTreelike) {
38
44
  async get(key) {
39
45
  // Check cache tree first.
40
46
  let cacheValue = await cache.get(key);
41
- if (cacheValue !== undefined && !Tree.isAsyncTree(cacheValue)) {
47
+ if (cacheValue !== undefined && !isAsyncTree(cacheValue)) {
42
48
  // Leaf node cache hit
43
49
  return cacheValue;
44
50
  }
45
51
 
46
52
  // Cache miss or interior node cache hit.
47
53
  let value = await source.get(key);
48
- if (Tree.isAsyncTree(value)) {
54
+ if (isAsyncTree(value)) {
49
55
  // Construct merged tree for a tree result.
50
56
  if (cacheValue === undefined) {
51
57
  // Construct new empty container in cache
52
58
  await cache.set(key, {});
53
59
  cacheValue = await cache.get(key);
54
- if (!Tree.isAsyncTree(cacheValue)) {
60
+ if (!isAsyncTree(cacheValue)) {
55
61
  // Coerce to tree and then save it back to the cache. This is
56
62
  // necessary, e.g., if cache is an ObjectTree; we want the
57
63
  // subtree to also be an ObjectTree, not a plain object.
58
- cacheValue = Tree.from(cacheValue);
64
+ cacheValue = from(cacheValue);
59
65
  await cache.set(key, cacheValue);
60
66
  }
61
67
  }
@@ -13,10 +13,11 @@ const treeMap = new Map();
13
13
  * are keys for subtrees, returning the source key unmodified.
14
14
  *
15
15
  * @typedef {import("../../index.ts").KeyFn} KeyFn
16
+ * @typedef {import("../../index.ts").ValueKeyFn} ValueKeyFn
16
17
  *
17
18
  * @param {KeyFn} keyFn
18
19
  * @param {boolean?} deep
19
- * @returns {{ key: KeyFn, inverseKey: KeyFn }}
20
+ * @returns {{ key: ValueKeyFn, inverseKey: KeyFn }}
20
21
  */
21
22
  export default function cachedKeyFunctions(keyFn, deep = false) {
22
23
  return {
@@ -40,10 +41,13 @@ export default function cachedKeyFunctions(keyFn, deep = false) {
40
41
  continue;
41
42
  }
42
43
 
44
+ const sourceValue = await tree.get(sourceKey);
45
+
43
46
  const computedResultKey = await computeAndCacheResultKey(
44
47
  tree,
45
48
  keyFn,
46
49
  deep,
50
+ sourceValue,
47
51
  sourceKey
48
52
  );
49
53
 
@@ -59,7 +63,7 @@ export default function cachedKeyFunctions(keyFn, deep = false) {
59
63
  return undefined;
60
64
  },
61
65
 
62
- async key(sourceKey, tree) {
66
+ async key(sourceValue, sourceKey, tree) {
63
67
  const { sourceKeyToResultKey } = getKeyMapsForTreeKeyFn(tree, keyFn);
64
68
 
65
69
  const cachedResultKey = searchKeyMap(sourceKeyToResultKey, sourceKey);
@@ -71,6 +75,7 @@ export default function cachedKeyFunctions(keyFn, deep = false) {
71
75
  tree,
72
76
  keyFn,
73
77
  deep,
78
+ sourceValue,
74
79
  sourceKey
75
80
  );
76
81
  return resultKey;
@@ -78,7 +83,13 @@ export default function cachedKeyFunctions(keyFn, deep = false) {
78
83
  };
79
84
  }
80
85
 
81
- async function computeAndCacheResultKey(tree, keyFn, deep, sourceKey) {
86
+ async function computeAndCacheResultKey(
87
+ tree,
88
+ keyFn,
89
+ deep,
90
+ sourceValue,
91
+ sourceKey
92
+ ) {
82
93
  const { resultKeyToSourceKey, sourceKeyToResultKey } = getKeyMapsForTreeKeyFn(
83
94
  tree,
84
95
  keyFn
@@ -87,7 +98,7 @@ async function computeAndCacheResultKey(tree, keyFn, deep, sourceKey) {
87
98
  const resultKey =
88
99
  deep && trailingSlash.has(sourceKey)
89
100
  ? sourceKey
90
- : await keyFn(sourceKey, tree);
101
+ : await keyFn(sourceValue, sourceKey, tree);
91
102
 
92
103
  sourceKeyToResultKey.set(sourceKey, resultKey);
93
104
  resultKeyToSourceKey.set(resultKey, sourceKey);
@@ -0,0 +1,20 @@
1
+ import from from "./from.js";
2
+ import isAsyncMutableTree from "./isAsyncMutableTree.js";
3
+
4
+ /**
5
+ * Remove all entries from the tree.
6
+ *
7
+ * @typedef {import("../../index.ts").Treelike} Treelike
8
+ *
9
+ * @param {Treelike} treelike
10
+ */
11
+ export default async function clear(treelike) {
12
+ const tree = from(treelike);
13
+ if (!isAsyncMutableTree(tree)) {
14
+ throw new TypeError("clear: can't clear a read-only tree.");
15
+ }
16
+ const keys = Array.from(await tree.keys());
17
+ const promises = keys.map((key) => tree.set(key, undefined));
18
+ await Promise.all(promises);
19
+ return tree;
20
+ }
@@ -0,0 +1,17 @@
1
+ import deepText from "./deepText.js";
2
+ import from from "./from.js";
3
+
4
+ /**
5
+ * Concatenate the text content of objects or trees.
6
+ *
7
+ * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
8
+ *
9
+ * @param {any[]} args
10
+ */
11
+ export default async function concat(...args) {
12
+ console.warn(
13
+ "Warning: the Tree.concat function is deprecated, use Tree.deepText instead."
14
+ );
15
+ const tree = from(args);
16
+ return deepText(tree);
17
+ }
@@ -0,0 +1,25 @@
1
+ import getTreeArgument from "../utilities/getTreeArgument.js";
2
+ import isPlainObject from "../utilities/isPlainObject.js";
3
+ import map from "./map.js";
4
+
5
+ /**
6
+ * Shorthand for calling `map` with the `deep: true` option.
7
+ *
8
+ * @typedef {import("../../index.ts").TreeMapOptions} TreeMapOptions
9
+ * @typedef {import("../../index.ts").Treelike} Treelike
10
+ * @typedef {import("../../index.ts").ValueKeyFn} ValueKeyFn
11
+ * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
12
+ *
13
+ * @param {Treelike} treelike
14
+ * @param {ValueKeyFn|TreeMapOptions} options
15
+ * @returns {Promise<AsyncTree>}
16
+ */
17
+ export default async function deepMap(treelike, options) {
18
+ const tree = await getTreeArgument(treelike, "deepMap", { deep: true });
19
+ const withDeep = isPlainObject(options)
20
+ ? // Dictionary
21
+ { ...options, deep: true }
22
+ : // Function
23
+ { deep: true, value: options };
24
+ return map(tree, withDeep);
25
+ }
@@ -1,16 +1,21 @@
1
- import { Tree } from "../internal.js";
2
1
  import * as trailingSlash from "../trailingSlash.js";
2
+ import from from "./from.js";
3
+ import isAsyncTree from "./isAsyncTree.js";
4
+ import isTreelike from "./isTreelike.js";
3
5
 
4
6
  /**
5
7
  * Return a tree that performs a deep merge of the given trees.
6
8
  *
7
9
  * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
8
- * @param {import("../../index.ts").Treelike[]} sources
10
+ * @typedef {import("../../index.ts").Treelike} Treelike
11
+ *
12
+ * @param {Treelike[]} sources
9
13
  * @returns {AsyncTree & { description: string }}
10
14
  */
11
15
  export default function deepMerge(...sources) {
12
- let trees = sources.map((treelike) => Tree.from(treelike, { deep: true }));
13
- let mergeParent;
16
+ const filtered = sources.filter((source) => source);
17
+ let trees = filtered.map((treelike) => from(treelike, { deep: true }));
18
+
14
19
  return {
15
20
  description: "deepMerge",
16
21
 
@@ -22,13 +27,9 @@ export default function deepMerge(...sources) {
22
27
  const tree = trees[index];
23
28
  const value = await tree.get(key);
24
29
  if (
25
- Tree.isAsyncTree(value) ||
26
- (Tree.isTreelike(value) && trailingSlash.has(key))
30
+ isAsyncTree(value) ||
31
+ (isTreelike(value) && trailingSlash.has(key))
27
32
  ) {
28
- if (/** @type {any} */ (value).parent === tree) {
29
- // Merged tree acts as parent instead of the source tree.
30
- /** @type {any} */ (value).parent = this;
31
- }
32
33
  subtrees.unshift(value);
33
34
  } else if (value !== undefined) {
34
35
  return value;
@@ -37,7 +38,6 @@ export default function deepMerge(...sources) {
37
38
 
38
39
  if (subtrees.length > 1) {
39
40
  const merged = deepMerge(...subtrees);
40
- merged.parent = this;
41
41
  return merged;
42
42
  } else if (subtrees.length === 1) {
43
43
  return subtrees[0];
@@ -62,19 +62,5 @@ export default function deepMerge(...sources) {
62
62
  }
63
63
  return keys;
64
64
  },
65
-
66
- get parent() {
67
- return mergeParent;
68
- },
69
- set parent(parent) {
70
- mergeParent = parent;
71
- trees = sources.map((treelike) => {
72
- const tree = Tree.isAsyncTree(treelike)
73
- ? Object.create(treelike)
74
- : Tree.from(treelike);
75
- tree.parent = parent;
76
- return tree;
77
- });
78
- },
79
65
  };
80
66
  }
@@ -1,5 +1,5 @@
1
- import { Tree } from "../internal.js";
2
- import { assertIsTreelike } from "../utilities.js";
1
+ import getTreeArgument from "../utilities/getTreeArgument.js";
2
+ import isAsyncTree from "./isAsyncTree.js";
3
3
 
4
4
  /**
5
5
  * Reverse the order of keys at all levels of the tree.
@@ -8,16 +8,15 @@ import { assertIsTreelike } from "../utilities.js";
8
8
  * @typedef {import("../../index.ts").Treelike} Treelike
9
9
  *
10
10
  * @param {Treelike} treelike
11
- * @returns {AsyncTree}
11
+ * @returns {Promise<AsyncTree>}
12
12
  */
13
- export default function deepReverse(treelike) {
14
- assertIsTreelike(treelike, "deepReverse");
13
+ export default async function deepReverse(treelike) {
14
+ const tree = await getTreeArgument(treelike, "deepReverse", { deep: true });
15
15
 
16
- const tree = Tree.from(treelike, { deep: true });
17
16
  return {
18
17
  async get(key) {
19
18
  let value = await tree.get(key);
20
- if (Tree.isAsyncTree(value)) {
19
+ if (isAsyncTree(value)) {
21
20
  value = deepReverse(value);
22
21
  }
23
22
  return value;
@@ -1,5 +1,6 @@
1
- import { Tree } from "../internal.js";
2
- import { assertIsTreelike } from "../utilities.js";
1
+ import getTreeArgument from "../utilities/getTreeArgument.js";
2
+ import from from "./from.js";
3
+ import isAsyncTree from "./isAsyncTree.js";
3
4
 
4
5
  /**
5
6
  * Returns a function that traverses a tree deeply and returns the values of the
@@ -12,11 +13,9 @@ import { assertIsTreelike } from "../utilities.js";
12
13
  * @param {number} count
13
14
  */
14
15
  export default async function deepTake(treelike, count) {
15
- assertIsTreelike(treelike, "deepTake");
16
- const tree = await Tree.from(treelike, { deep: true });
17
-
16
+ const tree = await getTreeArgument(treelike, "deepTake", { deep: true });
18
17
  const { values } = await traverse(tree, count);
19
- return Tree.from(values, { deep: true });
18
+ return from(values, { deep: true });
20
19
  }
21
20
 
22
21
  async function traverse(tree, count) {
@@ -26,7 +25,7 @@ async function traverse(tree, count) {
26
25
  break;
27
26
  }
28
27
  let value = await tree.get(key);
29
- if (Tree.isAsyncTree(value)) {
28
+ if (isAsyncTree(value)) {
30
29
  const traversed = await traverse(value, count);
31
30
  values.push(...traversed.values);
32
31
  count = traversed.count;
@@ -1,4 +1,5 @@
1
- import { assertIsTreelike, toString } from "../utilities.js";
1
+ import getTreeArgument from "../utilities/getTreeArgument.js";
2
+ import toString from "../utilities/toString.js";
2
3
  import deepValuesIterator from "./deepValuesIterator.js";
3
4
 
4
5
  /**
@@ -7,10 +8,9 @@ import deepValuesIterator from "./deepValuesIterator.js";
7
8
  * @param {import("../../index.ts").Treelike} treelike
8
9
  */
9
10
  export default async function deepText(treelike) {
10
- assertIsTreelike(treelike, "text");
11
-
11
+ const tree = await getTreeArgument(treelike, "deepText", { deep: true });
12
12
  const strings = [];
13
- for await (const value of deepValuesIterator(treelike, { expand: true })) {
13
+ for await (const value of deepValuesIterator(tree, { expand: true })) {
14
14
  let string;
15
15
  if (value === null) {
16
16
  string = "null";