@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@weborigami/async-tree",
3
- "version": "0.6.13",
3
+ "version": "0.6.14",
4
4
  "description": "Asynchronous tree drivers based on standard JavaScript classes",
5
5
  "type": "module",
6
6
  "main": "./main.js",
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";
@@ -51,6 +51,8 @@ export default async function addNextPrevious(maplike) {
51
51
  yield* sourceKeys;
52
52
  },
53
53
 
54
+ source,
55
+
54
56
  trailingSlashKeys: /** @type {any} */ (source).trailingSlashKeys,
55
57
  });
56
58
  }
@@ -1,11 +1,15 @@
1
- import { isUnpackable, SyncMap, Tree } from "@weborigami/async-tree";
2
- // import assignPropertyDescriptors from "./assignPropertyDescriptors.js";
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
- * @typedef {import("@weborigami/async-tree").Maplike} Maplike
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 entries = await Tree.entries(source);
34
- for (const [entryKey, entryValue] of entries) {
35
- let key;
36
- if (!isNaN(parseInt(entryKey))) {
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, entryValue);
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 entries from "./entries.js";
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 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;
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 1 will yield values only at the tree's top level.
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 > 1 &&
37
+ depth > 0 &&
38
38
  (isMap(value) ||
39
39
  (expand && typeof value !== "function" && isMaplike(value)));
40
40
  if (recurse) {
@@ -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 deepValuesIterator from "./deepValuesIterator.js";
4
+ import deepEntriesIterator from "./deepEntriesIterator.js";
3
5
 
4
6
  /**
5
- * Flatten the values in the tree to the given depth.
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
- /** @type {any} */
15
- const iterator = deepValuesIterator(map, { depth, expand: true });
16
- const result = [];
17
- for await (const key of iterator) {
18
- result.push(key);
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 deepEntries(target), [
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("converts an async tree to an array of nested entries arrays", async () => {
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
- "more",
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
+ });
@@ -36,7 +36,7 @@ describe("deepValuesIterator", () => {
36
36
  });
37
37
  const values = [];
38
38
  for await (const value of deepValuesIterator(tree, {
39
- depth: 3,
39
+ depth: 2,
40
40
  expand: true,
41
41
  })) {
42
42
  values.push(value);
@@ -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 the tree's values into an array", async () => {
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
- assert.deepEqual(await flat(fixture, Infinity), [1, 2, 3]);
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 one level by default", async () => {
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
- assert.deepEqual(await plain(await flat(fixture)), [
32
- 1,
33
- { b: 2, more: { c: 3 } },
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, [1, 2, 3, 4, 5, 6]);
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
  });