@weborigami/async-tree 0.5.4 → 0.5.6
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 -29
- package/src/Tree.js +59 -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 +3 -3
- package/src/drivers/MapTree.js +6 -6
- package/src/drivers/ObjectTree.js +6 -8
- 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/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/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/globKeys.js +8 -8
- package/src/operations/group.js +3 -46
- package/src/operations/groupBy.js +51 -0
- 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 +156 -95
- package/src/operations/mapExtension.js +78 -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/reverse.js +4 -6
- package/src/operations/root.js +17 -0
- package/src/operations/scope.js +4 -6
- 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 +24 -0
- package/src/operations/traverseOrThrow.js +59 -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 +40 -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/deepMerge.test.js +2 -6
- package/test/operations/deepReverse.test.js +1 -1
- 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/{group.test.js → groupBy.test.js} +4 -4
- 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 +39 -70
- package/test/operations/mapExtension.test.js +53 -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/paginate.test.js +1 -1
- 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/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,51 @@
|
|
|
1
|
+
import * as trailingSlash from "../trailingSlash.js";
|
|
2
|
+
import from from "./from.js";
|
|
3
|
+
import isAsyncTree from "./isAsyncTree.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Returns slash-separated paths for all values in the tree.
|
|
7
|
+
*
|
|
8
|
+
* The `base` argument is prepended to all paths.
|
|
9
|
+
*
|
|
10
|
+
* If `assumeSlashes` is true, then keys are assumed to have trailing slashes to
|
|
11
|
+
* indicate subtrees. The default value of this option is false.
|
|
12
|
+
*
|
|
13
|
+
* @typedef {import("../../index.ts").Treelike} Treelike
|
|
14
|
+
*
|
|
15
|
+
* @param {Treelike} treelike
|
|
16
|
+
* @param {{ assumeSlashes?: boolean, base?: string }} options
|
|
17
|
+
*/
|
|
18
|
+
export default async function paths(treelike, options = {}) {
|
|
19
|
+
const tree = from(treelike);
|
|
20
|
+
const base = options.base ?? "";
|
|
21
|
+
const assumeSlashes = options.assumeSlashes ?? false;
|
|
22
|
+
const result = [];
|
|
23
|
+
for (const key of await tree.keys()) {
|
|
24
|
+
const separator = trailingSlash.has(base) ? "" : "/";
|
|
25
|
+
const valuePath = base ? `${base}${separator}${key}` : key;
|
|
26
|
+
let isSubtree;
|
|
27
|
+
let value;
|
|
28
|
+
if (assumeSlashes) {
|
|
29
|
+
// Subtree needs to have a trailing slash
|
|
30
|
+
isSubtree = trailingSlash.has(key);
|
|
31
|
+
if (isSubtree) {
|
|
32
|
+
// We'll need the value to recurse
|
|
33
|
+
value = await tree.get(key);
|
|
34
|
+
}
|
|
35
|
+
} else {
|
|
36
|
+
// Get value and check
|
|
37
|
+
value = await tree.get(key);
|
|
38
|
+
}
|
|
39
|
+
if (value) {
|
|
40
|
+
// If we got the value we can check if it's a subtree
|
|
41
|
+
isSubtree = isAsyncTree(value);
|
|
42
|
+
}
|
|
43
|
+
if (isSubtree) {
|
|
44
|
+
const subPaths = await paths(value, { assumeSlashes, base: valuePath });
|
|
45
|
+
result.push(...subPaths);
|
|
46
|
+
} else {
|
|
47
|
+
result.push(valuePath);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
@@ -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
|
|
@@ -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,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,24 @@
|
|
|
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
|
+
* @param {Treelike} treelike
|
|
10
|
+
* @param {...any} keys
|
|
11
|
+
*/
|
|
12
|
+
export default async function traverse(treelike, ...keys) {
|
|
13
|
+
try {
|
|
14
|
+
// Await the result here so that, if the path doesn't exist, the catch
|
|
15
|
+
// block below will catch the exception.
|
|
16
|
+
return await traverseOrThrow(treelike, ...keys);
|
|
17
|
+
} catch (/** @type {any} */ error) {
|
|
18
|
+
if (error instanceof TraverseError) {
|
|
19
|
+
return undefined;
|
|
20
|
+
} else {
|
|
21
|
+
throw error;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
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
|
+
* @param {Treelike} treelike
|
|
13
|
+
* @param {...any} keys
|
|
14
|
+
*/
|
|
15
|
+
export default async function traverseOrThrow(treelike, ...keys) {
|
|
16
|
+
// Start our traversal at the root of the tree.
|
|
17
|
+
/** @type {any} */
|
|
18
|
+
let value = treelike;
|
|
19
|
+
let position = 0;
|
|
20
|
+
|
|
21
|
+
// Process all the keys.
|
|
22
|
+
const remainingKeys = keys.slice();
|
|
23
|
+
let key;
|
|
24
|
+
while (remainingKeys.length > 0) {
|
|
25
|
+
if (value == null) {
|
|
26
|
+
throw new TraverseError("A null or undefined value can't be traversed", {
|
|
27
|
+
tree: treelike,
|
|
28
|
+
keys,
|
|
29
|
+
position,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// If the value is packed and can be unpacked, unpack it.
|
|
34
|
+
if (isUnpackable(value)) {
|
|
35
|
+
value = await value.unpack();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (value instanceof Function) {
|
|
39
|
+
// Value is a function: call it with the remaining keys.
|
|
40
|
+
const fn = value;
|
|
41
|
+
// We'll take as many keys as the function's length, but at least one.
|
|
42
|
+
let fnKeyCount = Math.max(fn.length, 1);
|
|
43
|
+
const args = remainingKeys.splice(0, fnKeyCount);
|
|
44
|
+
key = null;
|
|
45
|
+
value = await fn(...args);
|
|
46
|
+
} else {
|
|
47
|
+
// Cast value to a tree.
|
|
48
|
+
const tree = from(value);
|
|
49
|
+
// Get the next key.
|
|
50
|
+
key = remainingKeys.shift();
|
|
51
|
+
// Get the value for the key.
|
|
52
|
+
value = await tree.get(key);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
position++;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return value;
|
|
59
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import TypedArray from "./TypedArray.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Return true if the object is in a packed form (or can be readily packed into
|
|
5
|
+
* a form) that can be given to fs.writeFile or response.write().
|
|
6
|
+
*
|
|
7
|
+
* @typedef {import("../../index.ts").Packed} Packed
|
|
8
|
+
*
|
|
9
|
+
* @param {any} obj
|
|
10
|
+
* @returns {obj is Packed}
|
|
11
|
+
*/
|
|
12
|
+
export default function isPacked(obj) {
|
|
13
|
+
return (
|
|
14
|
+
typeof obj === "string" ||
|
|
15
|
+
obj instanceof ArrayBuffer ||
|
|
16
|
+
obj instanceof ReadableStream ||
|
|
17
|
+
obj instanceof String ||
|
|
18
|
+
obj instanceof TypedArray
|
|
19
|
+
);
|
|
20
|
+
}
|