@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.
- package/index.ts +16 -6
- package/package.json +2 -2
- package/shared.js +20 -30
- package/src/Tree.js +62 -513
- package/src/constants.js +2 -0
- package/src/drivers/BrowserFileTree.js +9 -10
- package/src/drivers/DeepMapTree.js +3 -3
- package/src/drivers/DeepObjectTree.js +4 -5
- package/src/drivers/DeferredTree.js +2 -2
- package/src/drivers/FileTree.js +11 -33
- package/src/drivers/FunctionTree.js +1 -1
- package/src/drivers/MapTree.js +6 -6
- package/src/drivers/ObjectTree.js +4 -3
- package/src/drivers/SetTree.js +1 -1
- package/src/drivers/SiteTree.js +1 -1
- package/src/drivers/constantTree.js +1 -1
- package/src/extension.js +5 -3
- package/src/jsonKeys.js +5 -7
- package/src/operations/addNextPrevious.js +10 -9
- package/src/operations/assign.js +40 -0
- package/src/operations/cache.js +18 -12
- package/src/operations/cachedKeyFunctions.js +15 -4
- package/src/operations/clear.js +20 -0
- package/src/operations/concat.js +17 -0
- package/src/operations/deepMap.js +25 -0
- package/src/operations/deepMerge.js +11 -25
- package/src/operations/deepReverse.js +6 -7
- package/src/operations/deepTake.js +6 -7
- package/src/operations/deepText.js +4 -4
- package/src/operations/deepValuesIterator.js +8 -6
- package/src/operations/defineds.js +32 -0
- package/src/operations/delete.js +20 -0
- package/src/operations/entries.js +16 -0
- package/src/operations/extensionKeyFunctions.js +1 -1
- package/src/operations/filter.js +7 -8
- package/src/operations/first.js +18 -0
- package/src/operations/forEach.js +20 -0
- package/src/operations/from.js +77 -0
- package/src/operations/fromFn.js +26 -0
- package/src/operations/globKeys.js +8 -8
- package/src/operations/group.js +9 -7
- package/src/operations/has.js +16 -0
- package/src/operations/indent.js +4 -2
- package/src/operations/inners.js +29 -0
- package/src/operations/invokeFunctions.js +5 -4
- package/src/operations/isAsyncMutableTree.js +15 -0
- package/src/operations/isAsyncTree.js +21 -0
- package/src/operations/isTraversable.js +15 -0
- package/src/operations/isTreelike.js +33 -0
- package/src/operations/json.js +4 -3
- package/src/operations/keys.js +14 -0
- package/src/operations/length.js +15 -0
- package/src/operations/map.js +151 -95
- package/src/operations/mapExtension.js +27 -0
- package/src/operations/mapReduce.js +44 -0
- package/src/operations/mask.js +18 -16
- package/src/operations/match.js +74 -0
- package/src/operations/merge.js +22 -20
- package/src/operations/paginate.js +3 -5
- package/src/operations/parent.js +13 -0
- package/src/operations/paths.js +51 -0
- package/src/operations/plain.js +34 -0
- package/src/operations/regExpKeys.js +4 -5
- package/src/operations/remove.js +14 -0
- package/src/operations/reverse.js +4 -6
- package/src/operations/root.js +17 -0
- package/src/operations/scope.js +4 -6
- package/src/operations/setDeep.js +50 -0
- package/src/operations/shuffle.js +46 -0
- package/src/operations/sort.js +19 -12
- package/src/operations/take.js +3 -5
- package/src/operations/text.js +3 -3
- package/src/operations/toFunction.js +14 -0
- package/src/operations/traverse.js +25 -0
- package/src/operations/traverseOrThrow.js +64 -0
- package/src/operations/traversePath.js +16 -0
- package/src/operations/values.js +15 -0
- package/src/operations/withKeys.js +33 -0
- package/src/utilities/TypedArray.js +2 -0
- package/src/utilities/box.js +20 -0
- package/src/utilities/castArraylike.js +38 -0
- package/src/utilities/getParent.js +33 -0
- package/src/utilities/getRealmObjectPrototype.js +19 -0
- package/src/utilities/getTreeArgument.js +43 -0
- package/src/utilities/isPacked.js +20 -0
- package/src/utilities/isPlainObject.js +29 -0
- package/src/utilities/isPrimitive.js +13 -0
- package/src/utilities/isStringlike.js +25 -0
- package/src/utilities/isUnpackable.js +13 -0
- package/src/utilities/keysFromPath.js +34 -0
- package/src/utilities/naturalOrder.js +9 -0
- package/src/utilities/pathFromKeys.js +18 -0
- package/src/utilities/setParent.js +38 -0
- package/src/utilities/toFunction.js +41 -0
- package/src/utilities/toPlainValue.js +95 -0
- package/src/utilities/toString.js +37 -0
- package/test/drivers/ExplorableSiteTree.test.js +1 -1
- package/test/drivers/FileTree.test.js +1 -1
- package/test/drivers/calendarTree.test.js +1 -1
- package/test/jsonKeys.test.js +1 -1
- package/test/operations/assign.test.js +54 -0
- package/test/operations/cache.test.js +1 -1
- package/test/operations/cachedKeyFunctions.test.js +16 -16
- package/test/operations/clear.test.js +34 -0
- package/test/operations/deepMap.test.js +29 -0
- package/test/operations/deepMerge.test.js +2 -6
- package/test/operations/deepReverse.test.js +1 -1
- package/test/operations/defineds.test.js +25 -0
- package/test/operations/delete.test.js +20 -0
- package/test/operations/entries.test.js +18 -0
- package/test/operations/extensionKeyFunctions.test.js +10 -10
- package/test/operations/first.test.js +15 -0
- package/test/operations/fixtures/README.md +1 -0
- package/test/operations/forEach.test.js +22 -0
- package/test/operations/from.test.js +67 -0
- package/test/operations/globKeys.test.js +3 -3
- package/test/operations/has.test.js +15 -0
- package/test/operations/inners.test.js +30 -0
- package/test/operations/invokeFunctions.test.js +1 -1
- package/test/operations/isAsyncMutableTree.test.js +17 -0
- package/test/operations/isAsyncTree.test.js +26 -0
- package/test/operations/isTreelike.test.js +13 -0
- package/test/operations/keys.test.js +15 -0
- package/test/operations/length.test.js +15 -0
- package/test/operations/map.test.js +61 -42
- package/test/operations/mapExtension.test.js +0 -0
- package/test/operations/mapReduce.test.js +23 -0
- package/test/operations/mask.test.js +1 -1
- package/test/operations/match.test.js +33 -0
- package/test/operations/merge.test.js +23 -9
- package/test/operations/parent.test.js +15 -0
- package/test/operations/paths.test.js +40 -0
- package/test/operations/plain.test.js +69 -0
- package/test/operations/reverse.test.js +1 -1
- package/test/operations/scope.test.js +1 -1
- package/test/operations/setDeep.test.js +53 -0
- package/test/operations/shuffle.test.js +18 -0
- package/test/operations/sort.test.js +3 -3
- package/test/operations/toFunction.test.js +16 -0
- package/test/operations/traverse.test.js +43 -0
- package/test/operations/traversePath.test.js +16 -0
- package/test/operations/values.test.js +18 -0
- package/test/operations/withKeys.test.js +21 -0
- package/test/utilities/box.test.js +26 -0
- package/test/utilities/getRealmObjectPrototype.test.js +11 -0
- package/test/utilities/isPlainObject.test.js +13 -0
- package/test/utilities/keysFromPath.test.js +14 -0
- package/test/utilities/naturalOrder.test.js +11 -0
- package/test/utilities/pathFromKeys.test.js +12 -0
- package/test/utilities/setParent.test.js +34 -0
- package/test/utilities/toFunction.test.js +34 -0
- package/test/utilities/toPlainValue.test.js +27 -0
- package/test/utilities/toString.test.js +22 -0
- package/src/Tree.d.ts +0 -24
- package/src/utilities.d.ts +0 -21
- package/src/utilities.js +0 -443
- package/test/Tree.test.js +0 -407
- package/test/utilities.test.js +0 -141
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
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 =
|
|
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.
|
package/src/drivers/FileTree.js
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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 (
|
|
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 (
|
|
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
|
|
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) {
|
package/src/drivers/MapTree.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import isAsyncTree from "../operations/isAsyncTree.js";
|
|
2
2
|
import * as trailingSlash from "../trailingSlash.js";
|
|
3
|
-
import
|
|
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} [
|
|
22
|
+
* @param {Iterable} [source]
|
|
23
23
|
*/
|
|
24
|
-
constructor(
|
|
25
|
-
this.map = new Map(
|
|
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
|
|
55
|
+
return isAsyncTree(value);
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
async keys() {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import
|
|
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
|
|
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
|
|
76
|
+
return isAsyncTree(value);
|
|
76
77
|
}
|
|
77
78
|
|
|
78
79
|
/**
|
package/src/drivers/SetTree.js
CHANGED
package/src/drivers/SiteTree.js
CHANGED
package/src/extension.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as trailingSlash from "./trailingSlash.js";
|
|
2
|
-
import
|
|
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 (!
|
|
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 (!
|
|
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
|
|
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 =
|
|
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
|
|
2
|
-
import
|
|
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
|
-
|
|
15
|
-
const tree = Tree.from(treelike);
|
|
16
|
+
const tree = await getTreeArgument(treelike, "addNextPrevious");
|
|
16
17
|
|
|
17
|
-
const
|
|
18
|
-
const keys =
|
|
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
|
-
|
|
24
|
+
treeEntries.map(async ([key, value], index) => {
|
|
24
25
|
let resultValue;
|
|
25
26
|
if (value === undefined) {
|
|
26
27
|
resultValue = undefined;
|
|
27
|
-
} else if (
|
|
28
|
-
resultValue = await
|
|
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
|
+
}
|
package/src/operations/cache.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
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 {
|
|
19
|
+
* @returns {Promise}
|
|
17
20
|
*/
|
|
18
|
-
export default function treeCache(sourceTreelike, cacheTreelike) {
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
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 && !
|
|
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 (
|
|
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 (!
|
|
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 =
|
|
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:
|
|
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(
|
|
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
|
-
* @
|
|
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
|
-
|
|
13
|
-
let
|
|
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
|
-
|
|
26
|
-
(
|
|
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
|
|
2
|
-
import
|
|
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
|
-
|
|
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 (
|
|
19
|
+
if (isAsyncTree(value)) {
|
|
21
20
|
value = deepReverse(value);
|
|
22
21
|
}
|
|
23
22
|
return value;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
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
|
-
|
|
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
|
|
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 (
|
|
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
|
|
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
|
-
|
|
11
|
-
|
|
11
|
+
const tree = await getTreeArgument(treelike, "deepText", { deep: true });
|
|
12
12
|
const strings = [];
|
|
13
|
-
for await (const value of deepValuesIterator(
|
|
13
|
+
for await (const value of deepValuesIterator(tree, { expand: true })) {
|
|
14
14
|
let string;
|
|
15
15
|
if (value === null) {
|
|
16
16
|
string = "null";
|