@weborigami/async-tree 0.6.13 → 0.6.14
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/package.json +1 -1
- package/src/Tree.js +2 -0
- package/src/operations/addNextPrevious.js +2 -0
- package/src/operations/concat.js +12 -9
- package/src/operations/deepArrays.js +23 -0
- package/src/operations/deepEntries.js +7 -10
- package/src/operations/deepEntriesIterator.js +45 -0
- package/src/operations/deepValuesIterator.js +2 -2
- package/src/operations/flat.js +32 -8
- package/test/operations/assign.test.js +2 -2
- package/test/operations/deepArrays.test.js +32 -0
- package/test/operations/deepEntries.test.js +3 -8
- package/test/operations/deepEntriesIterator.test.js +78 -0
- package/test/operations/deepValuesIterator.test.js +1 -1
- package/test/operations/flat.test.js +36 -12
package/package.json
CHANGED
package/src/Tree.js
CHANGED
|
@@ -10,7 +10,9 @@ export { default as child } from "./operations/child.js";
|
|
|
10
10
|
export { default as clear } from "./operations/clear.js";
|
|
11
11
|
export { default as concat } from "./operations/concat.js";
|
|
12
12
|
export { default as constant } from "./operations/constant.js";
|
|
13
|
+
export { default as deepArrays } from "./operations/deepArrays.js";
|
|
13
14
|
export { default as deepEntries } from "./operations/deepEntries.js";
|
|
15
|
+
export { default as deepEntriesIterator } from "./operations/deepEntriesIterator.js";
|
|
14
16
|
export { default as deepMap } from "./operations/deepMap.js";
|
|
15
17
|
export { default as deepMerge } from "./operations/deepMerge.js";
|
|
16
18
|
export { default as deepReverse } from "./operations/deepReverse.js";
|
package/src/operations/concat.js
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import SyncMap from "../drivers/SyncMap.js";
|
|
2
|
+
import * as trailingSlash from "../trailingSlash.js";
|
|
3
|
+
import isUnpackable from "../utilities/isUnpackable.js";
|
|
4
|
+
import entries from "./entries.js";
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
7
|
* Concatenate the given trees. This is similar to a merge, but numeric keys
|
|
6
8
|
* will be renumbered starting with 0 and incrementing by 1.
|
|
7
9
|
*
|
|
8
|
-
*
|
|
10
|
+
* If the final result is array-like, returns an array, otherwise returns a map.
|
|
11
|
+
*
|
|
12
|
+
* @typedef {import("../../index.ts").Maplike} Maplike
|
|
9
13
|
*
|
|
10
14
|
* @param {(Maplike|null)[]} trees
|
|
11
15
|
*/
|
|
@@ -30,19 +34,18 @@ export default async function concat(...trees) {
|
|
|
30
34
|
let onlyNumericKeys = true;
|
|
31
35
|
const map = new SyncMap();
|
|
32
36
|
for (const source of sources) {
|
|
33
|
-
const
|
|
34
|
-
for (
|
|
35
|
-
|
|
36
|
-
if (
|
|
37
|
+
const sourceEntries = await entries(source);
|
|
38
|
+
for (let [key, value] of sourceEntries) {
|
|
39
|
+
key = trailingSlash.remove(key);
|
|
40
|
+
if (typeof key === "number" || /^\d+$/.test(key)) {
|
|
37
41
|
// Numeric key, renumber it.
|
|
38
42
|
key = String(index);
|
|
39
43
|
index++;
|
|
40
44
|
} else {
|
|
41
45
|
// Non-numeric key, keep it as is.
|
|
42
|
-
key = entryKey;
|
|
43
46
|
onlyNumericKeys = false;
|
|
44
47
|
}
|
|
45
|
-
map.set(key,
|
|
48
|
+
map.set(key, value);
|
|
46
49
|
}
|
|
47
50
|
}
|
|
48
51
|
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as args from "../utilities/args.js";
|
|
2
|
+
import entries from "./entries.js";
|
|
3
|
+
import isMap from "./isMap.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Return the deep nested entries in the tree as arrays of [key, value] pairs.
|
|
7
|
+
*
|
|
8
|
+
* @typedef {import("../../index.ts").Maplike} Maplike
|
|
9
|
+
*
|
|
10
|
+
* @param {Maplike} maplike
|
|
11
|
+
*/
|
|
12
|
+
export default async function deepEntries(maplike) {
|
|
13
|
+
const tree = await args.map(maplike, "Tree.deepEntries");
|
|
14
|
+
|
|
15
|
+
const treeEntries = await entries(tree);
|
|
16
|
+
const result = await Promise.all(
|
|
17
|
+
treeEntries.map(async ([key, value]) => {
|
|
18
|
+
const resolvedValue = isMap(value) ? await deepEntries(value) : value;
|
|
19
|
+
return [key, resolvedValue];
|
|
20
|
+
}),
|
|
21
|
+
);
|
|
22
|
+
return result;
|
|
23
|
+
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import * as args from "../utilities/args.js";
|
|
2
|
-
import
|
|
3
|
-
import isMap from "./isMap.js";
|
|
2
|
+
import deepEntriesIterator from "./deepEntriesIterator.js";
|
|
4
3
|
|
|
5
4
|
/**
|
|
6
5
|
* Return the deep nested entries in the tree as arrays of [key, value] pairs.
|
|
@@ -12,12 +11,10 @@ import isMap from "./isMap.js";
|
|
|
12
11
|
export default async function deepEntries(maplike) {
|
|
13
12
|
const tree = await args.map(maplike, "Tree.deepEntries");
|
|
14
13
|
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
);
|
|
22
|
-
return result;
|
|
14
|
+
const iterator = deepEntriesIterator(tree, { depth: Infinity });
|
|
15
|
+
const entries = [];
|
|
16
|
+
for await (const entry of iterator) {
|
|
17
|
+
entries.push(entry);
|
|
18
|
+
}
|
|
19
|
+
return entries;
|
|
23
20
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import * as args from "../utilities/args.js";
|
|
2
|
+
import isUnpackable from "../utilities/isUnpackable.js";
|
|
3
|
+
import isMap from "./isMap.js";
|
|
4
|
+
import isMaplike from "./isMaplike.js";
|
|
5
|
+
/**
|
|
6
|
+
* Return an iterator that yields all entries in a tree, including nested trees.
|
|
7
|
+
*
|
|
8
|
+
* If the `expand` option is true, maplike values (but not functions) will be
|
|
9
|
+
* expanded into nested trees and their values will be yielded. Packed values
|
|
10
|
+
* will be unpacked before expanding.
|
|
11
|
+
*
|
|
12
|
+
* If the `depth` option is specified, the iterator will only descend to the
|
|
13
|
+
* specified depth. A depth of 0 will yield values only at the tree's top level.
|
|
14
|
+
*
|
|
15
|
+
* @param {import("../../index.ts").Maplike} maplike
|
|
16
|
+
* @param {{ depth?: number, expand?: boolean }} [options]
|
|
17
|
+
* @returns {AsyncGenerator<[any, any], void, undefined>}
|
|
18
|
+
*/
|
|
19
|
+
export default async function* deepEntriesIterator(maplike, options = {}) {
|
|
20
|
+
const tree = await args.map(maplike, "Tree.deepEntriesIterator", {
|
|
21
|
+
deep: !options.expand,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const depth = options.depth ?? Infinity;
|
|
25
|
+
const expand = options.expand ?? false;
|
|
26
|
+
|
|
27
|
+
for await (let [key, value] of tree.entries()) {
|
|
28
|
+
value = await value;
|
|
29
|
+
|
|
30
|
+
if (expand && isUnpackable(value)) {
|
|
31
|
+
value = await value.unpack();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Recurse into child trees, but don't expand functions.
|
|
35
|
+
const recurse =
|
|
36
|
+
depth > 0 &&
|
|
37
|
+
(isMap(value) ||
|
|
38
|
+
(expand && typeof value !== "function" && isMaplike(value)));
|
|
39
|
+
if (recurse) {
|
|
40
|
+
yield* deepEntriesIterator(value, { depth: depth - 1, expand });
|
|
41
|
+
} else {
|
|
42
|
+
yield [key, value];
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -11,7 +11,7 @@ import isMaplike from "./isMaplike.js";
|
|
|
11
11
|
* will be unpacked before expanding.
|
|
12
12
|
*
|
|
13
13
|
* If the `depth` option is specified, the iterator will only descend to the
|
|
14
|
-
* specified depth. A depth of
|
|
14
|
+
* specified depth. A depth of 0 will yield values only at the tree's top level.
|
|
15
15
|
*
|
|
16
16
|
* @param {import("../../index.ts").Maplike} maplike
|
|
17
17
|
* @param {{ depth?: number, expand?: boolean }} [options]
|
|
@@ -34,7 +34,7 @@ export default async function* deepValuesIterator(maplike, options = {}) {
|
|
|
34
34
|
|
|
35
35
|
// Recurse into child trees, but don't expand functions.
|
|
36
36
|
const recurse =
|
|
37
|
-
depth >
|
|
37
|
+
depth > 0 &&
|
|
38
38
|
(isMap(value) ||
|
|
39
39
|
(expand && typeof value !== "function" && isMaplike(value)));
|
|
40
40
|
if (recurse) {
|
package/src/operations/flat.js
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
|
+
import SyncMap from "../drivers/SyncMap.js";
|
|
2
|
+
import * as trailingSlash from "../trailingSlash.js";
|
|
1
3
|
import * as args from "../utilities/args.js";
|
|
2
|
-
import
|
|
4
|
+
import deepEntriesIterator from "./deepEntriesIterator.js";
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
|
-
* Flatten the
|
|
7
|
+
* Flatten the entries in the tree to the given depth. If the depth is omitted,
|
|
8
|
+
* the tree will be flattened one level.
|
|
9
|
+
*
|
|
10
|
+
* Numeric keys will be renumbered.
|
|
11
|
+
*
|
|
12
|
+
* If the final result is array-like, returns an array, otherwise returns a map.
|
|
6
13
|
*
|
|
7
14
|
* @typedef {import("../../index.ts").Maplike} Maplike
|
|
8
15
|
*
|
|
@@ -11,11 +18,28 @@ import deepValuesIterator from "./deepValuesIterator.js";
|
|
|
11
18
|
*/
|
|
12
19
|
export default async function flat(maplike, depth = 1) {
|
|
13
20
|
const map = await args.map(maplike, "Tree.flat", { deep: true });
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
21
|
+
|
|
22
|
+
let index = 0;
|
|
23
|
+
let onlyNumericKeys = true;
|
|
24
|
+
const result = new SyncMap();
|
|
25
|
+
for await (let [key, value] of deepEntriesIterator(map, { depth })) {
|
|
26
|
+
key = trailingSlash.remove(key);
|
|
27
|
+
if (typeof key === "number" || /^\d+$/.test(key)) {
|
|
28
|
+
// Numeric key, renumber it.
|
|
29
|
+
key = String(index);
|
|
30
|
+
index++;
|
|
31
|
+
} else {
|
|
32
|
+
// Non-numeric key, keep it as is.
|
|
33
|
+
onlyNumericKeys = false;
|
|
34
|
+
}
|
|
35
|
+
result.set(key, value);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (onlyNumericKeys) {
|
|
39
|
+
// All keys are numeric, return an array.
|
|
40
|
+
return [...result.values()];
|
|
41
|
+
} else {
|
|
42
|
+
// Some keys are non-numeric, return a map.
|
|
43
|
+
return result;
|
|
19
44
|
}
|
|
20
|
-
return result;
|
|
21
45
|
}
|
|
@@ -3,8 +3,8 @@ import { describe, test } from "node:test";
|
|
|
3
3
|
import ObjectMap from "../../src/drivers/ObjectMap.js";
|
|
4
4
|
import SyncMap from "../../src/drivers/SyncMap.js";
|
|
5
5
|
import assign from "../../src/operations/assign.js";
|
|
6
|
+
import deepArrays from "../../src/operations/deepArrays.js";
|
|
6
7
|
import plain from "../../src/operations/plain.js";
|
|
7
|
-
import { deepEntries } from "../../src/Tree.js";
|
|
8
8
|
import SampleAsyncMap from "../SampleAsyncMap.js";
|
|
9
9
|
|
|
10
10
|
describe("assign", () => {
|
|
@@ -26,7 +26,7 @@ describe("assign", () => {
|
|
|
26
26
|
const result = await assign(target, source);
|
|
27
27
|
|
|
28
28
|
assert.equal(result, target);
|
|
29
|
-
assert.deepEqual(await
|
|
29
|
+
assert.deepEqual(await deepArrays(target), [
|
|
30
30
|
["a", 4],
|
|
31
31
|
["b", 2],
|
|
32
32
|
[
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import assert from "node:assert";
|
|
2
|
+
import { describe, test } from "node:test";
|
|
3
|
+
import deepArrays from "../../src/operations/deepArrays.js";
|
|
4
|
+
import SampleAsyncMap from "../SampleAsyncMap.js";
|
|
5
|
+
|
|
6
|
+
describe("deepArrays", () => {
|
|
7
|
+
test("converts an async tree to an array of nested entries arrays", async () => {
|
|
8
|
+
const fixture = new SampleAsyncMap([
|
|
9
|
+
["a", 1],
|
|
10
|
+
["b", 2],
|
|
11
|
+
[
|
|
12
|
+
"more",
|
|
13
|
+
[
|
|
14
|
+
["c", 3],
|
|
15
|
+
["d", 4],
|
|
16
|
+
],
|
|
17
|
+
],
|
|
18
|
+
]);
|
|
19
|
+
const result = await deepArrays(fixture);
|
|
20
|
+
assert.deepStrictEqual(result, [
|
|
21
|
+
["a", 1],
|
|
22
|
+
["b", 2],
|
|
23
|
+
[
|
|
24
|
+
"more",
|
|
25
|
+
[
|
|
26
|
+
["c", 3],
|
|
27
|
+
["d", 4],
|
|
28
|
+
],
|
|
29
|
+
],
|
|
30
|
+
]);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
@@ -4,7 +4,7 @@ import deepEntries from "../../src/operations/deepEntries.js";
|
|
|
4
4
|
import SampleAsyncMap from "../SampleAsyncMap.js";
|
|
5
5
|
|
|
6
6
|
describe("deepEntries", () => {
|
|
7
|
-
test("
|
|
7
|
+
test("returns an entry of the deep tree entries", async () => {
|
|
8
8
|
const fixture = new SampleAsyncMap([
|
|
9
9
|
["a", 1],
|
|
10
10
|
["b", 2],
|
|
@@ -20,13 +20,8 @@ describe("deepEntries", () => {
|
|
|
20
20
|
assert.deepStrictEqual(result, [
|
|
21
21
|
["a", 1],
|
|
22
22
|
["b", 2],
|
|
23
|
-
[
|
|
24
|
-
|
|
25
|
-
[
|
|
26
|
-
["c", 3],
|
|
27
|
-
["d", 4],
|
|
28
|
-
],
|
|
29
|
-
],
|
|
23
|
+
["c", 3],
|
|
24
|
+
["d", 4],
|
|
30
25
|
]);
|
|
31
26
|
});
|
|
32
27
|
});
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import assert from "node:assert";
|
|
2
|
+
import { describe, test } from "node:test";
|
|
3
|
+
import ObjectMap from "../../src/drivers/ObjectMap.js";
|
|
4
|
+
import deepEntriesIterator from "../../src/operations/deepEntriesIterator.js";
|
|
5
|
+
|
|
6
|
+
describe("deepEntriesIterator", () => {
|
|
7
|
+
test("returns an iterator of a tree's deep entries", async () => {
|
|
8
|
+
const tree = new ObjectMap({
|
|
9
|
+
a: 1,
|
|
10
|
+
b: 2,
|
|
11
|
+
more: {
|
|
12
|
+
c: 3,
|
|
13
|
+
d: 4,
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
const entries = [];
|
|
17
|
+
// The tree will be shallow, but we'll ask to expand the entries.
|
|
18
|
+
for await (const entry of deepEntriesIterator(tree, { expand: true })) {
|
|
19
|
+
entries.push(entry);
|
|
20
|
+
}
|
|
21
|
+
assert.deepEqual(entries, [
|
|
22
|
+
["a", 1],
|
|
23
|
+
["b", 2],
|
|
24
|
+
["c", 3],
|
|
25
|
+
["d", 4],
|
|
26
|
+
]);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("if depth is specified, only descends to specified depth", async () => {
|
|
30
|
+
const tree = new ObjectMap({
|
|
31
|
+
a: 1,
|
|
32
|
+
sub: {
|
|
33
|
+
b: 2,
|
|
34
|
+
more: {
|
|
35
|
+
c: 3,
|
|
36
|
+
deeper: {
|
|
37
|
+
d: 4,
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
const entries = [];
|
|
43
|
+
for await (const entry of deepEntriesIterator(tree, {
|
|
44
|
+
depth: 2,
|
|
45
|
+
expand: true,
|
|
46
|
+
})) {
|
|
47
|
+
entries.push(entry);
|
|
48
|
+
}
|
|
49
|
+
assert.deepEqual(entries, [
|
|
50
|
+
["a", 1],
|
|
51
|
+
["b", 2],
|
|
52
|
+
["c", 3],
|
|
53
|
+
["deeper", { d: 4 }],
|
|
54
|
+
]);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("can optionally unpack a packed value", async () => {
|
|
58
|
+
/** @type {any} */
|
|
59
|
+
const packed = new String("String that unpacks to data");
|
|
60
|
+
packed.unpack = async function () {
|
|
61
|
+
return {
|
|
62
|
+
message: "Hello",
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
const tree = {
|
|
66
|
+
a: 1,
|
|
67
|
+
packed,
|
|
68
|
+
};
|
|
69
|
+
const entries = [];
|
|
70
|
+
for await (const entry of deepEntriesIterator(tree, { expand: true })) {
|
|
71
|
+
entries.push(entry);
|
|
72
|
+
}
|
|
73
|
+
assert.deepEqual(entries, [
|
|
74
|
+
["a", 1],
|
|
75
|
+
["message", "Hello"],
|
|
76
|
+
]);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
@@ -5,7 +5,19 @@ import flat from "../../src/operations/flat.js";
|
|
|
5
5
|
import plain from "../../src/operations/plain.js";
|
|
6
6
|
|
|
7
7
|
describe("flat", () => {
|
|
8
|
-
test("flattens
|
|
8
|
+
test("flattens an array one level by default", async () => {
|
|
9
|
+
const result = await flat([1, 2, [3], [[4, [5]]]], 1);
|
|
10
|
+
assert.deepEqual(await plain(result), [1, 2, 3, [4, [5]]]);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test("flattens deep arrays", async () => {
|
|
14
|
+
assert.deepEqual(
|
|
15
|
+
await flat([1, 2, [3], [[4, [5]]]], Infinity),
|
|
16
|
+
[1, 2, 3, 4, 5],
|
|
17
|
+
);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("flattens an object one level by default", async () => {
|
|
9
21
|
const fixture = {
|
|
10
22
|
a: 1,
|
|
11
23
|
sub: {
|
|
@@ -15,10 +27,17 @@ describe("flat", () => {
|
|
|
15
27
|
},
|
|
16
28
|
},
|
|
17
29
|
};
|
|
18
|
-
|
|
30
|
+
const result = await flat(fixture);
|
|
31
|
+
assert.deepEqual(await plain(result), {
|
|
32
|
+
a: 1,
|
|
33
|
+
b: 2,
|
|
34
|
+
more: {
|
|
35
|
+
c: 3,
|
|
36
|
+
},
|
|
37
|
+
});
|
|
19
38
|
});
|
|
20
39
|
|
|
21
|
-
test("flattens
|
|
40
|
+
test("flattens deep objects", async () => {
|
|
22
41
|
const fixture = {
|
|
23
42
|
a: 1,
|
|
24
43
|
sub: {
|
|
@@ -28,14 +47,12 @@ describe("flat", () => {
|
|
|
28
47
|
},
|
|
29
48
|
},
|
|
30
49
|
};
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
test("flattens arrays", async () => {
|
|
38
|
-
assert.deepEqual(await flat([1, 2, [3]], Infinity), [1, 2, 3]);
|
|
50
|
+
const result = await flat(fixture, Infinity);
|
|
51
|
+
assert.deepEqual(await plain(result), {
|
|
52
|
+
a: 1,
|
|
53
|
+
b: 2,
|
|
54
|
+
c: 3,
|
|
55
|
+
});
|
|
39
56
|
});
|
|
40
57
|
|
|
41
58
|
test("flattens maplike objects", async () => {
|
|
@@ -53,6 +70,13 @@ describe("flat", () => {
|
|
|
53
70
|
],
|
|
54
71
|
Infinity,
|
|
55
72
|
);
|
|
56
|
-
assert.deepEqual(result,
|
|
73
|
+
assert.deepEqual(await plain(result), {
|
|
74
|
+
a: 1,
|
|
75
|
+
b: 2,
|
|
76
|
+
c: 3,
|
|
77
|
+
d: 4,
|
|
78
|
+
0: 5,
|
|
79
|
+
1: 6,
|
|
80
|
+
});
|
|
57
81
|
});
|
|
58
82
|
});
|