@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
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import getTreeArgument from "../utilities/getTreeArgument.js";
|
|
2
|
+
import toPlainValue from "../utilities/toPlainValue.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Converts an asynchronous tree into a synchronous plain JavaScript object.
|
|
6
|
+
*
|
|
7
|
+
* The result's keys will be the tree's keys cast to strings. Any trailing
|
|
8
|
+
* slashes in keys will be removed.
|
|
9
|
+
*
|
|
10
|
+
* Any tree value that is itself a tree will be recursively converted to a plain
|
|
11
|
+
* object.
|
|
12
|
+
*
|
|
13
|
+
* If the tree is array-like (its keys are integers and fill the range
|
|
14
|
+
* 0..length-1), then the result will be an array. The order of the keys will
|
|
15
|
+
* determine the order of the values in the array -- but the numeric value of
|
|
16
|
+
* the keys will be ignored.
|
|
17
|
+
*
|
|
18
|
+
* For example, a tree like `{ 1: "b", 0: "a", 2: "c" }` is array-like because
|
|
19
|
+
* its keys are all integers and fill the range 0..2. The result will be the
|
|
20
|
+
* array `["b", "a", "c" ]` because the tree has the keys in that order. The
|
|
21
|
+
* specific values of the keys (0, 1, and 2) are ignored.
|
|
22
|
+
*
|
|
23
|
+
* @typedef {import("../../index.ts").Treelike} Treelike
|
|
24
|
+
* @typedef {import("../../index.ts").PlainObject} PlainObject
|
|
25
|
+
*
|
|
26
|
+
* @param {Treelike} treelike
|
|
27
|
+
*/
|
|
28
|
+
export default async function plain(treelike) {
|
|
29
|
+
if (treelike instanceof Function) {
|
|
30
|
+
throw new TypeError("plain: can't convert a function to a plain object");
|
|
31
|
+
}
|
|
32
|
+
const tree = await getTreeArgument(treelike, "plain");
|
|
33
|
+
return toPlainValue(tree);
|
|
34
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { Tree } from "../internal.js";
|
|
2
1
|
import * as trailingSlash from "../trailingSlash.js";
|
|
3
|
-
import
|
|
2
|
+
import getTreeArgument from "../utilities/getTreeArgument.js";
|
|
3
|
+
import isAsyncTree from "./isAsyncTree.js";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* A tree whose keys are strings interpreted as regular expressions.
|
|
@@ -13,8 +13,7 @@ import { assertIsTreelike } from "../utilities.js";
|
|
|
13
13
|
* @type {import("../../index.ts").TreeTransform}
|
|
14
14
|
*/
|
|
15
15
|
export default async function regExpKeys(treelike) {
|
|
16
|
-
|
|
17
|
-
const tree = Tree.from(treelike);
|
|
16
|
+
const tree = await getTreeArgument(treelike, "regExpKeys");
|
|
18
17
|
|
|
19
18
|
const map = new Map();
|
|
20
19
|
|
|
@@ -57,7 +56,7 @@ export default async function regExpKeys(treelike) {
|
|
|
57
56
|
let value = await tree.get(key);
|
|
58
57
|
|
|
59
58
|
let regExp;
|
|
60
|
-
if (trailingSlash.has(key) ||
|
|
59
|
+
if (trailingSlash.has(key) || isAsyncTree(value)) {
|
|
61
60
|
const baseKey = trailingSlash.remove(key);
|
|
62
61
|
regExp = new RegExp("^" + baseKey + "/?$");
|
|
63
62
|
// Subtree
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { default as del } from "./delete.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Removes the value for the given key from the specific node of the tree.
|
|
5
|
+
*
|
|
6
|
+
* @typedef {import("@weborigami/types").AsyncMutableTree} AsyncMutableTree
|
|
7
|
+
*
|
|
8
|
+
* @param {AsyncMutableTree} tree
|
|
9
|
+
* @param {any} key
|
|
10
|
+
*/
|
|
11
|
+
export default async function remove(tree, key) {
|
|
12
|
+
console.warn("`Tree.remove` is deprecated. Use `Tree.delete` instead.");
|
|
13
|
+
return del(tree, key);
|
|
14
|
+
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { assertIsTreelike } from "../utilities.js";
|
|
1
|
+
import getTreeArgument from "../utilities/getTreeArgument.js";
|
|
3
2
|
|
|
4
3
|
/**
|
|
5
4
|
* Reverse the order of the top-level keys in the tree.
|
|
@@ -8,11 +7,10 @@ import { assertIsTreelike } from "../utilities.js";
|
|
|
8
7
|
* @typedef {import("../../index.ts").Treelike} Treelike
|
|
9
8
|
*
|
|
10
9
|
* @param {Treelike} treelike
|
|
11
|
-
* @returns {AsyncTree}
|
|
10
|
+
* @returns {Promise<AsyncTree>}
|
|
12
11
|
*/
|
|
13
|
-
export default function reverse(treelike) {
|
|
14
|
-
|
|
15
|
-
const tree = Tree.from(treelike);
|
|
12
|
+
export default async function reverse(treelike) {
|
|
13
|
+
const tree = await getTreeArgument(treelike, "reverse");
|
|
16
14
|
|
|
17
15
|
return {
|
|
18
16
|
async get(key) {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as symbols from "../symbols.js";
|
|
2
|
+
import from from "./from.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Walk up the `parent` chain to find the root of the tree.
|
|
6
|
+
*
|
|
7
|
+
* @typedef {import("../../index.ts").Treelike} Treelike
|
|
8
|
+
*
|
|
9
|
+
* @param {Treelike} treelike
|
|
10
|
+
*/
|
|
11
|
+
export default function root(treelike) {
|
|
12
|
+
let current = from(treelike);
|
|
13
|
+
while (current.parent || current[symbols.parent]) {
|
|
14
|
+
current = current.parent || current[symbols.parent];
|
|
15
|
+
}
|
|
16
|
+
return current;
|
|
17
|
+
}
|
package/src/operations/scope.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { assertIsTreelike } from "../utilities.js";
|
|
1
|
+
import getTreeArgument from "../utilities/getTreeArgument.js";
|
|
3
2
|
|
|
4
3
|
/**
|
|
5
4
|
* A tree's "scope" is the collection of everything in that tree and all of its
|
|
@@ -9,11 +8,10 @@ import { assertIsTreelike } from "../utilities.js";
|
|
|
9
8
|
* @typedef {import("../../index.ts").Treelike} Treelike
|
|
10
9
|
*
|
|
11
10
|
* @param {Treelike} treelike
|
|
12
|
-
* @returns {AsyncTree & {trees: AsyncTree[]}}
|
|
11
|
+
* @returns {Promise<AsyncTree & {trees: AsyncTree[]}>}
|
|
13
12
|
*/
|
|
14
|
-
export default function scope(treelike) {
|
|
15
|
-
|
|
16
|
-
const tree = Tree.from(treelike);
|
|
13
|
+
export default async function scope(treelike) {
|
|
14
|
+
const tree = await getTreeArgument(treelike, "scope");
|
|
17
15
|
|
|
18
16
|
return {
|
|
19
17
|
// Starting with this tree, search up the parent hierarchy.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import from from "./from.js";
|
|
2
|
+
import isAsyncTree from "./isAsyncTree.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {import("@weborigami/async-tree").Treelike} Treelike
|
|
6
|
+
*
|
|
7
|
+
* @param {Treelike} target
|
|
8
|
+
* @param {Treelike} source
|
|
9
|
+
*/
|
|
10
|
+
export default async function setDeep(target, source) {
|
|
11
|
+
console.warn("Tree.setDeep is deprecated, use Tree.assign instead.");
|
|
12
|
+
const targetTree = from(target);
|
|
13
|
+
const sourceTree = from(source);
|
|
14
|
+
await applyUpdates(sourceTree, targetTree);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Apply all updates from the source to the target.
|
|
18
|
+
async function applyUpdates(source, target) {
|
|
19
|
+
// Fire off requests to update all keys, then wait for all of them to finish.
|
|
20
|
+
const promises = [];
|
|
21
|
+
for (const key of await source.keys()) {
|
|
22
|
+
const updateKeyPromise = applyUpdateForKey(source, target, key);
|
|
23
|
+
promises.push(updateKeyPromise);
|
|
24
|
+
}
|
|
25
|
+
await Promise.all(promises);
|
|
26
|
+
|
|
27
|
+
// HACK: Transforms like KeysTransform that maintain caches will need to
|
|
28
|
+
// recalculate things now that updates have been applied. This should be an
|
|
29
|
+
// automatic part of calling set() -- but triggering those changes inside
|
|
30
|
+
// set() produces cases where set() and get() calls can be interleaved. The
|
|
31
|
+
// atomicity of set() needs to be reconsidered. For now, we work around the
|
|
32
|
+
// problem by triggering `onChange` after the updates have been applied.
|
|
33
|
+
target.onChange?.();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Copy the value for the given key from the source to the target.
|
|
37
|
+
async function applyUpdateForKey(source, target, key) {
|
|
38
|
+
const sourceValue = await source.get(key);
|
|
39
|
+
if (isAsyncTree(sourceValue)) {
|
|
40
|
+
const targetValue = await target.get(key);
|
|
41
|
+
if (isAsyncTree(targetValue)) {
|
|
42
|
+
// Both source and target are async dictionaries; recurse.
|
|
43
|
+
await applyUpdates(sourceValue, targetValue);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Copy the value from the source to the target.
|
|
49
|
+
await target.set(key, sourceValue);
|
|
50
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import getTreeArgument from "../utilities/getTreeArgument.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Return a new tree with the original's keys shuffled
|
|
5
|
+
*
|
|
6
|
+
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
7
|
+
* @typedef {import("../../index.ts").Treelike} Treelike
|
|
8
|
+
*
|
|
9
|
+
* @param {Treelike} treelike
|
|
10
|
+
* @param {boolean?} reshuffle
|
|
11
|
+
* @returns {Promise<AsyncTree>}
|
|
12
|
+
*/
|
|
13
|
+
export default async function shuffle(treelike, reshuffle = false) {
|
|
14
|
+
const tree = await getTreeArgument(treelike, "shuffle");
|
|
15
|
+
|
|
16
|
+
let keys;
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
async get(key) {
|
|
20
|
+
return tree.get(key);
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
async keys() {
|
|
24
|
+
if (!keys || reshuffle) {
|
|
25
|
+
keys = Array.from(await tree.keys());
|
|
26
|
+
shuffleArray(keys);
|
|
27
|
+
}
|
|
28
|
+
return keys;
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/*
|
|
34
|
+
* Shuffle an array.
|
|
35
|
+
*
|
|
36
|
+
* Performs a Fisher-Yates shuffle. From http://sedition.com/perl/javascript-fy.html
|
|
37
|
+
*/
|
|
38
|
+
export function shuffleArray(array) {
|
|
39
|
+
let i = array.length;
|
|
40
|
+
while (--i >= 0) {
|
|
41
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
42
|
+
const temp = array[i];
|
|
43
|
+
array[i] = array[j];
|
|
44
|
+
array[j] = temp;
|
|
45
|
+
}
|
|
46
|
+
}
|
package/src/operations/sort.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { assertIsTreelike } from "../utilities.js";
|
|
1
|
+
import getTreeArgument from "../utilities/getTreeArgument.js";
|
|
3
2
|
|
|
4
3
|
/**
|
|
5
4
|
* Return a new tree with the original's keys sorted. A comparison function can
|
|
@@ -8,18 +7,25 @@ import { assertIsTreelike } from "../utilities.js";
|
|
|
8
7
|
*
|
|
9
8
|
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
10
9
|
* @typedef {(key: any, tree: AsyncTree) => any} SortKeyFn
|
|
11
|
-
* @typedef {{ compare?: (a: any, b: any) => number, sortKey?: SortKeyFn }}
|
|
12
|
-
*
|
|
10
|
+
* @typedef {{ compare?: (a: any, b: any) => number, sortKey?: SortKeyFn }} SortOptions
|
|
11
|
+
* @typedef {import("../../index.ts").Treelike} Treelike
|
|
12
|
+
* @typedef {import("../../index.ts").ValueKeyFn} ValueKeyFn
|
|
13
13
|
*
|
|
14
|
-
* @param {
|
|
15
|
-
* @param {SortOptions} [options]
|
|
14
|
+
* @param {Treelike} treelike
|
|
15
|
+
* @param {SortOptions|ValueKeyFn} [options]
|
|
16
16
|
*/
|
|
17
|
-
export default function sort(treelike, options) {
|
|
18
|
-
|
|
19
|
-
const tree = Tree.from(treelike);
|
|
17
|
+
export default async function sort(treelike, options) {
|
|
18
|
+
const tree = await getTreeArgument(treelike, "sort");
|
|
20
19
|
|
|
21
|
-
|
|
22
|
-
let compare
|
|
20
|
+
let sortKey;
|
|
21
|
+
let compare;
|
|
22
|
+
if (options instanceof Function) {
|
|
23
|
+
// Take the function as the `sortKey` option
|
|
24
|
+
sortKey = options;
|
|
25
|
+
} else {
|
|
26
|
+
compare = options?.compare;
|
|
27
|
+
sortKey = options?.sortKey;
|
|
28
|
+
}
|
|
23
29
|
|
|
24
30
|
const transformed = Object.create(tree);
|
|
25
31
|
transformed.keys = async () => {
|
|
@@ -30,7 +36,8 @@ export default function sort(treelike, options) {
|
|
|
30
36
|
// Create { key, sortKey } tuples.
|
|
31
37
|
const tuples = await Promise.all(
|
|
32
38
|
keys.map(async (key) => {
|
|
33
|
-
const
|
|
39
|
+
const value = await tree.get(key);
|
|
40
|
+
const sort = await sortKey(value, key, tree);
|
|
34
41
|
if (sort === undefined) {
|
|
35
42
|
throw new Error(
|
|
36
43
|
`sortKey function returned undefined for key ${key}`
|
package/src/operations/take.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { assertIsTreelike } from "../utilities.js";
|
|
1
|
+
import getTreeArgument from "../utilities/getTreeArgument.js";
|
|
3
2
|
|
|
4
3
|
/**
|
|
5
4
|
* Returns a new tree with the number of keys limited to the indicated count.
|
|
@@ -7,9 +6,8 @@ import { assertIsTreelike } from "../utilities.js";
|
|
|
7
6
|
* @param {import("../../index.ts").Treelike} treelike
|
|
8
7
|
* @param {number} count
|
|
9
8
|
*/
|
|
10
|
-
export default function take(treelike, count) {
|
|
11
|
-
|
|
12
|
-
const tree = Tree.from(treelike);
|
|
9
|
+
export default async function take(treelike, count) {
|
|
10
|
+
const tree = await getTreeArgument(treelike, "take");
|
|
13
11
|
|
|
14
12
|
return {
|
|
15
13
|
async keys() {
|
package/src/operations/text.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { toString } from "../utilities.js";
|
|
1
|
+
import toString from "../utilities/toString.js";
|
|
3
2
|
import deepText from "./deepText.js";
|
|
3
|
+
import isTreelike from "./isTreelike.js";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* A tagged template literal function that concatenate the deep text values in a
|
|
@@ -13,7 +13,7 @@ export default async function text(strings, ...values) {
|
|
|
13
13
|
// Convert all the values to strings
|
|
14
14
|
const valueTexts = await Promise.all(
|
|
15
15
|
values.map((value) =>
|
|
16
|
-
|
|
16
|
+
isTreelike(value) ? deepText(value) : toString(value)
|
|
17
17
|
)
|
|
18
18
|
);
|
|
19
19
|
// Splice all the strings together
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import getTreeArgument from "../utilities/getTreeArgument.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Returns a function that invokes the tree's `get` method.
|
|
5
|
+
*
|
|
6
|
+
* @typedef {import("../../index.ts").Treelike} Treelike
|
|
7
|
+
*
|
|
8
|
+
* @param {Treelike} treelike
|
|
9
|
+
* @returns {Promise<Function>}
|
|
10
|
+
*/
|
|
11
|
+
export default async function toFunction(treelike) {
|
|
12
|
+
const tree = await getTreeArgument(treelike, "toFunction");
|
|
13
|
+
return tree.get.bind(tree);
|
|
14
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import TraverseError from "../TraverseError.js";
|
|
2
|
+
import traverseOrThrow from "./traverseOrThrow.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Return the value at the corresponding path of keys.
|
|
6
|
+
*
|
|
7
|
+
* @typedef {import("../../index.ts").Treelike} Treelike
|
|
8
|
+
*
|
|
9
|
+
* @this {any}
|
|
10
|
+
* @param {Treelike} treelike
|
|
11
|
+
* @param {...any} keys
|
|
12
|
+
*/
|
|
13
|
+
export default async function traverse(treelike, ...keys) {
|
|
14
|
+
try {
|
|
15
|
+
// Await the result here so that, if the path doesn't exist, the catch
|
|
16
|
+
// block below will catch the exception.
|
|
17
|
+
return await traverseOrThrow.call(this, treelike, ...keys);
|
|
18
|
+
} catch (/** @type {any} */ error) {
|
|
19
|
+
if (error instanceof TraverseError) {
|
|
20
|
+
return undefined;
|
|
21
|
+
} else {
|
|
22
|
+
throw error;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import TraverseError from "../TraverseError.js";
|
|
2
|
+
import isUnpackable from "../utilities/isUnpackable.js";
|
|
3
|
+
import from from "./from.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Return the value at the corresponding path of keys. Throw if any interior
|
|
7
|
+
* step of the path doesn't lead to a result.
|
|
8
|
+
*
|
|
9
|
+
* @typedef {import("../../index.ts").Treelike} Treelike
|
|
10
|
+
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
11
|
+
*
|
|
12
|
+
* @this {any}
|
|
13
|
+
* @param {Treelike} treelike
|
|
14
|
+
* @param {...any} keys
|
|
15
|
+
*/
|
|
16
|
+
export default async function traverseOrThrow(treelike, ...keys) {
|
|
17
|
+
// Start our traversal at the root of the tree.
|
|
18
|
+
/** @type {any} */
|
|
19
|
+
let value = treelike;
|
|
20
|
+
let position = 0;
|
|
21
|
+
|
|
22
|
+
// If traversal operation was called with a `this` context, use that as the
|
|
23
|
+
// target for function calls.
|
|
24
|
+
const target = this;
|
|
25
|
+
|
|
26
|
+
// Process all the keys.
|
|
27
|
+
const remainingKeys = keys.slice();
|
|
28
|
+
let key;
|
|
29
|
+
while (remainingKeys.length > 0) {
|
|
30
|
+
if (value == null) {
|
|
31
|
+
throw new TraverseError("A null or undefined value can't be traversed", {
|
|
32
|
+
tree: treelike,
|
|
33
|
+
keys,
|
|
34
|
+
position,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// If the value is packed and can be unpacked, unpack it.
|
|
39
|
+
if (isUnpackable(value)) {
|
|
40
|
+
value = await value.unpack();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (value instanceof Function) {
|
|
44
|
+
// Value is a function: call it with the remaining keys.
|
|
45
|
+
const fn = value;
|
|
46
|
+
// We'll take as many keys as the function's length, but at least one.
|
|
47
|
+
let fnKeyCount = Math.max(fn.length, 1);
|
|
48
|
+
const args = remainingKeys.splice(0, fnKeyCount);
|
|
49
|
+
key = null;
|
|
50
|
+
value = await fn.call(target, ...args);
|
|
51
|
+
} else {
|
|
52
|
+
// Cast value to a tree.
|
|
53
|
+
const tree = from(value);
|
|
54
|
+
// Get the next key.
|
|
55
|
+
key = remainingKeys.shift();
|
|
56
|
+
// Get the value for the key.
|
|
57
|
+
value = await tree.get(key);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
position++;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return value;
|
|
64
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import keysFromPath from "../utilities/keysFromPath.js";
|
|
2
|
+
import traverse from "./traverse.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Given a slash-separated path like "foo/bar", traverse the keys "foo/" and
|
|
6
|
+
* "bar" and return the resulting value.
|
|
7
|
+
*
|
|
8
|
+
* @typedef {import("../../index.ts").Treelike} Treelike
|
|
9
|
+
*
|
|
10
|
+
* @param {Treelike} tree
|
|
11
|
+
* @param {string} path
|
|
12
|
+
*/
|
|
13
|
+
export default async function traversePath(tree, path) {
|
|
14
|
+
const keys = keysFromPath(path);
|
|
15
|
+
return traverse(tree, ...keys);
|
|
16
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import from from "./from.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Return the values in the specific node of the tree.
|
|
5
|
+
*
|
|
6
|
+
* @typedef {import("../../index.ts").Treelike} Treelike
|
|
7
|
+
*
|
|
8
|
+
* @param {Treelike} treelike
|
|
9
|
+
*/
|
|
10
|
+
export default async function values(treelike) {
|
|
11
|
+
const tree = from(treelike);
|
|
12
|
+
const keys = Array.from(await tree.keys());
|
|
13
|
+
const promises = keys.map(async (key) => tree.get(key));
|
|
14
|
+
return Promise.all(promises);
|
|
15
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import getTreeArgument from "../utilities/getTreeArgument.js";
|
|
2
|
+
import values from "./values.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Return a tree whose keys are provided by the _values_ of a second tree (e.g.,
|
|
6
|
+
* an array of keys).
|
|
7
|
+
*
|
|
8
|
+
* @typedef {import("../../index.ts").Treelike} Treelike
|
|
9
|
+
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
10
|
+
*
|
|
11
|
+
* @param {Treelike} treelike
|
|
12
|
+
* @param {Treelike} keysTreelike
|
|
13
|
+
* @returns {Promise<AsyncTree>}
|
|
14
|
+
*/
|
|
15
|
+
export default async function withKeys(treelike, keysTreelike) {
|
|
16
|
+
const tree = await getTreeArgument(treelike, "withKeys", { position: 0 });
|
|
17
|
+
const keysTree = await getTreeArgument(keysTreelike, "withKeys", {
|
|
18
|
+
position: 1,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
let keys;
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
async get(key) {
|
|
25
|
+
return tree.get(key);
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
async keys() {
|
|
29
|
+
keys ??= await values(keysTree);
|
|
30
|
+
return keys;
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Return the value as an object. If the value is already an object it will be
|
|
3
|
+
* returned as is. If the value is a primitive, it will be wrapped in an object:
|
|
4
|
+
* a string will be wrapped in a String object, a number will be wrapped in a
|
|
5
|
+
* Number object, and a boolean will be wrapped in a Boolean object.
|
|
6
|
+
*
|
|
7
|
+
* @param {any} value
|
|
8
|
+
*/
|
|
9
|
+
export default function box(value) {
|
|
10
|
+
switch (typeof value) {
|
|
11
|
+
case "string":
|
|
12
|
+
return new String(value);
|
|
13
|
+
case "number":
|
|
14
|
+
return new Number(value);
|
|
15
|
+
case "boolean":
|
|
16
|
+
return new Boolean(value);
|
|
17
|
+
default:
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create an array or plain object from the given keys and values.
|
|
3
|
+
*
|
|
4
|
+
* If the given plain object has only integer keys, and the set of integers is
|
|
5
|
+
* complete from 0 to length-1, assume the values are a result of array
|
|
6
|
+
* transformations and the values are the desired result; return them as is.
|
|
7
|
+
* Otherwise, create a plain object with the keys and values.
|
|
8
|
+
*
|
|
9
|
+
* @param {any[]} keys
|
|
10
|
+
* @param {any[]} values
|
|
11
|
+
*/
|
|
12
|
+
export default function castArraylike(keys, values) {
|
|
13
|
+
if (keys.length === 0) {
|
|
14
|
+
// Empty keys/values means an empty object, not an empty array
|
|
15
|
+
return {};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let onlyNumericKeys = true;
|
|
19
|
+
const numberSeen = new Array(keys.length);
|
|
20
|
+
for (const key of keys) {
|
|
21
|
+
const n = Number(key);
|
|
22
|
+
if (isNaN(n) || !Number.isInteger(n) || n < 0 || n >= keys.length) {
|
|
23
|
+
onlyNumericKeys = false;
|
|
24
|
+
break;
|
|
25
|
+
} else {
|
|
26
|
+
numberSeen[n] = true;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// If any number from 0..length-1 is missing, we can't treat this as an array
|
|
31
|
+
const allNumbersSeen = onlyNumericKeys && numberSeen.every((v) => v);
|
|
32
|
+
if (allNumbersSeen) {
|
|
33
|
+
return values;
|
|
34
|
+
} else {
|
|
35
|
+
// Return a plain object with the (key, value) pairs
|
|
36
|
+
return Object.fromEntries(keys.map((key, i) => [key, values[i]]));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import * as symbols from "../symbols.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Return a suitable parent for the packed file.
|
|
5
|
+
*
|
|
6
|
+
* This is intended to be called by unpack functions.
|
|
7
|
+
*
|
|
8
|
+
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
9
|
+
*
|
|
10
|
+
* @param {any} packed
|
|
11
|
+
* @param {any} [options]
|
|
12
|
+
* @returns {AsyncTree|null}
|
|
13
|
+
*/
|
|
14
|
+
export default function getParent(packed, options = {}) {
|
|
15
|
+
// Prefer parent set on options
|
|
16
|
+
if (options?.parent) {
|
|
17
|
+
return options.parent;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// If the packed object has a `parent` property, use that. Exception: Node
|
|
21
|
+
// Buffer objects have a `parent` property that we ignore.
|
|
22
|
+
if (packed.parent && !(packed instanceof Buffer)) {
|
|
23
|
+
return packed.parent;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// If the packed object has a parent symbol, use that.
|
|
27
|
+
if (packed[symbols.parent]) {
|
|
28
|
+
return packed[symbols.parent];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Otherwise, return null.
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Return the Object prototype at the root of the object's prototype chain.
|
|
3
|
+
*
|
|
4
|
+
* This is used by functions like isPlainObject() to handle cases where the
|
|
5
|
+
* `Object` at the root prototype chain is in a different realm.
|
|
6
|
+
*
|
|
7
|
+
* @param {any} object
|
|
8
|
+
*/
|
|
9
|
+
export default function getRealmObjectPrototype(object) {
|
|
10
|
+
if (Object.getPrototypeOf(object) === null) {
|
|
11
|
+
// The object has no prototype.
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
let proto = object;
|
|
15
|
+
while (Object.getPrototypeOf(proto) !== null) {
|
|
16
|
+
proto = Object.getPrototypeOf(proto);
|
|
17
|
+
}
|
|
18
|
+
return proto;
|
|
19
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import from from "../operations/from.js";
|
|
2
|
+
import isUnpackable from "./isUnpackable.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Convert the indicated argument to a tree, or throw an exception.
|
|
6
|
+
*
|
|
7
|
+
* Tree operations can use this to validate the tree argument and provide more
|
|
8
|
+
* helpful error messages. This also unpacks a unpackable tree argument so that
|
|
9
|
+
* the caller can work with a simpler tree instead of a DeferredTree.
|
|
10
|
+
*
|
|
11
|
+
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
12
|
+
* @typedef {import("../../index.ts").Treelike} Treelike
|
|
13
|
+
* @typedef {import("../../index.ts").Unpackable} Unpackable
|
|
14
|
+
*
|
|
15
|
+
* @param {Treelike|Unpackable} treelike
|
|
16
|
+
* @param {string} operation
|
|
17
|
+
* @param {{ deep?: boolean, position?: number }} [options]
|
|
18
|
+
* @returns {Promise<AsyncTree>}
|
|
19
|
+
*/
|
|
20
|
+
export default async function getTreeArgument(
|
|
21
|
+
treelike,
|
|
22
|
+
operation,
|
|
23
|
+
options = {}
|
|
24
|
+
) {
|
|
25
|
+
const deep = options.deep;
|
|
26
|
+
const position = options.position ?? 0;
|
|
27
|
+
|
|
28
|
+
if (isUnpackable(treelike)) {
|
|
29
|
+
treelike = await treelike.unpack();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let tree;
|
|
33
|
+
try {
|
|
34
|
+
tree = from(treelike, { deep });
|
|
35
|
+
} catch (/** @type {any} */ error) {
|
|
36
|
+
let message = error.message ?? error;
|
|
37
|
+
message = `${operation}: ${message}`;
|
|
38
|
+
const newError = new TypeError(message);
|
|
39
|
+
/** @type {any} */ (newError).position = position;
|
|
40
|
+
throw newError;
|
|
41
|
+
}
|
|
42
|
+
return tree;
|
|
43
|
+
}
|