@weborigami/async-tree 0.2.12 → 0.3.0

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.2.12",
3
+ "version": "0.3.0",
4
4
  "description": "Asynchronous tree drivers based on standard JavaScript classes",
5
5
  "type": "module",
6
6
  "main": "./main.js",
@@ -11,7 +11,7 @@
11
11
  "typescript": "5.8.2"
12
12
  },
13
13
  "dependencies": {
14
- "@weborigami/types": "0.2.12"
14
+ "@weborigami/types": "0.3.0"
15
15
  },
16
16
  "scripts": {
17
17
  "test": "node --test --test-reporter=spec",
package/shared.js CHANGED
@@ -26,6 +26,7 @@ export { default as filter } from "./src/operations/filter.js";
26
26
  export { default as group } from "./src/operations/group.js";
27
27
  export { default as invokeFunctions } from "./src/operations/invokeFunctions.js";
28
28
  export { default as map } from "./src/operations/map.js";
29
+ export { default as mask } from "./src/operations/mask.js";
29
30
  export { default as merge } from "./src/operations/merge.js";
30
31
  export { default as paginate } from "./src/operations/paginate.js";
31
32
  export { default as reverse } from "./src/operations/reverse.js";
@@ -1,11 +1,11 @@
1
- import { Tree } from "@weborigami/async-tree";
1
+ import { Tree } from "../internal.js";
2
2
  import { assertIsTreelike } from "../utilities.js";
3
3
 
4
4
  /**
5
5
  * Add nextKey/previousKey properties to values.
6
6
  *
7
7
  * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
8
- * @typedef {import("@weborigami/async-tree").PlainObject} PlainObject
8
+ * @typedef {import("../../index.ts").PlainObject} PlainObject
9
9
  *
10
10
  * @param {import("../../index.ts").Treelike} treelike
11
11
  * @returns {Promise<PlainObject|Array>}
@@ -1,53 +1,39 @@
1
- import { assertIsTreelike, trailingSlash, Tree } from "@weborigami/async-tree";
1
+ import { assertIsTreelike } from "../utilities.js";
2
+ import map from "./map.js";
2
3
 
3
4
  /**
4
- * Given trees `a` and `b`, return a filtered version of `a` where only the keys
5
- * that exist in `b` and have truthy values are kept. The filter operation is
6
- * deep: if a value from `a` is a subtree, it will be filtered recursively.
5
+ * Given a tree an a test function, return a new tree whose keys correspond to
6
+ * the values that pass the test function.
7
7
  *
8
8
  * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
9
9
  * @typedef {import("../../index.ts").Treelike} Treelike
10
10
  *
11
- * @param {Treelike} a
12
- * @param {Treelike} b
11
+ * @param {Treelike} treelike
12
+ * @param {function|any} options
13
13
  * @returns {AsyncTree}
14
14
  */
15
- export default function filter(a, b) {
16
- assertIsTreelike(a, "filter", 0);
17
- assertIsTreelike(b, "filter", 1);
18
- a = Tree.from(a);
19
- b = Tree.from(b, { deep: true });
15
+ export default function filter(treelike, options) {
16
+ assertIsTreelike(treelike, "map");
17
+ let testFn;
18
+ let deep;
19
+ if (typeof options === "function") {
20
+ testFn = options;
21
+ deep = false;
22
+ } else {
23
+ testFn = options.test;
24
+ deep = options.deep ?? false;
25
+ }
20
26
 
21
- return {
22
- async get(key) {
23
- // The key must exist in b and return a truthy value
24
- const bValue = await b.get(key);
25
- if (!bValue) {
26
- return undefined;
27
- }
28
- let aValue = await a.get(key);
29
- if (Tree.isTreelike(aValue)) {
30
- // Filter the subtree
31
- return filter(aValue, bValue);
32
- } else {
33
- return aValue;
34
- }
35
- },
27
+ return map(treelike, {
28
+ deep,
29
+
30
+ // Assume source key is the same as result key
31
+ inverseKey: async (resultKey) => resultKey,
36
32
 
37
- async keys() {
38
- // Use a's keys as the basis
39
- const aKeys = [...(await a.keys())];
40
- const bValues = await Promise.all(aKeys.map((key) => b.get(key)));
41
- // An async tree value in b implies that the a key should have a slash
42
- const aKeySlashes = aKeys.map((key, index) =>
43
- trailingSlash.toggle(
44
- key,
45
- trailingSlash.has(key) || Tree.isAsyncTree(bValues[index])
46
- )
47
- );
48
- // Remove keys that don't have values in b
49
- const keys = aKeySlashes.filter((key, index) => bValues[index] ?? false);
50
- return keys;
33
+ key: async (sourceKey, tree) => {
34
+ const value = await tree.get(sourceKey);
35
+ const passes = await testFn(value, sourceKey, tree);
36
+ return passes ? sourceKey : undefined;
51
37
  },
52
- };
38
+ });
53
39
  }
@@ -1,9 +1,13 @@
1
- import { ObjectTree, Tree, merge, trailingSlash } from "@weborigami/async-tree";
1
+ import { ObjectTree, Tree } from "../internal.js";
2
+ import * as trailingSlash from "../trailingSlash.js";
3
+ import { assertIsTreelike } from "../utilities.js";
4
+ import merge from "./merge.js";
2
5
 
3
6
  const globstar = "**";
4
7
  const globstarSlash = `${globstar}/`;
5
8
 
6
9
  export default function globKeys(treelike) {
10
+ assertIsTreelike(treelike, "globKeys");
7
11
  const globs = Tree.from(treelike, { deep: true });
8
12
  return {
9
13
  async get(key) {
@@ -0,0 +1,55 @@
1
+ import { Tree } from "../internal.js";
2
+ import * as trailingSlash from "../trailingSlash.js";
3
+ import { assertIsTreelike } from "../utilities.js";
4
+
5
+ /**
6
+ * Given trees `a` and `b`, return a masked version of `a` where only the keys
7
+ * that exist in `b` and have truthy values are kept. The filter operation is
8
+ * deep: if a value from `a` is a subtree, it will be filtered recursively.
9
+ *
10
+ * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
11
+ * @typedef {import("../../index.ts").Treelike} Treelike
12
+ *
13
+ * @param {Treelike} a
14
+ * @param {Treelike} b
15
+ * @returns {AsyncTree}
16
+ */
17
+ export default function mask(a, b) {
18
+ assertIsTreelike(a, "filter", 0);
19
+ assertIsTreelike(b, "filter", 1);
20
+ a = Tree.from(a);
21
+ b = Tree.from(b, { deep: true });
22
+
23
+ return {
24
+ async get(key) {
25
+ // The key must exist in b and return a truthy value
26
+ const bValue = await b.get(key);
27
+ if (!bValue) {
28
+ return undefined;
29
+ }
30
+ let aValue = await a.get(key);
31
+ if (Tree.isTreelike(aValue)) {
32
+ // Filter the subtree
33
+ return mask(aValue, bValue);
34
+ } else {
35
+ return aValue;
36
+ }
37
+ },
38
+
39
+ async keys() {
40
+ // Use a's keys as the basis
41
+ const aKeys = [...(await a.keys())];
42
+ const bValues = await Promise.all(aKeys.map((key) => b.get(key)));
43
+ // An async tree value in b implies that the a key should have a slash
44
+ const aKeySlashes = aKeys.map((key, index) =>
45
+ trailingSlash.toggle(
46
+ key,
47
+ trailingSlash.has(key) || Tree.isAsyncTree(bValues[index])
48
+ )
49
+ );
50
+ // Remove keys that don't have values in b
51
+ const keys = aKeySlashes.filter((key, index) => bValues[index] ?? false);
52
+ return keys;
53
+ },
54
+ };
55
+ }
@@ -1,7 +1,7 @@
1
- import { Tree } from "@weborigami/async-tree";
2
1
  import assert from "node:assert";
3
2
  import { describe, test } from "node:test";
4
3
  import constantTree from "../../src/drivers/constantTree.js";
4
+ import { Tree } from "../../src/internal.js";
5
5
 
6
6
  describe("constantTree", () => {
7
7
  test("returns a deep tree that returns constant for all keys", async () => {
@@ -1,6 +1,6 @@
1
- import { DeepObjectTree } from "@weborigami/async-tree";
2
1
  import assert from "node:assert";
3
2
  import { describe, test } from "node:test";
3
+ import { DeepObjectTree } from "../src/internal.js";
4
4
  import * as jsonKeys from "../src/jsonKeys.js";
5
5
 
6
6
  describe("jsonKeys", () => {
@@ -4,8 +4,24 @@ import { Tree } from "../../src/internal.js";
4
4
  import filter from "../../src/operations/filter.js";
5
5
 
6
6
  describe("filter", () => {
7
- test("removes keys and values whose filter values are falsy", async () => {
8
- const result = filter(
7
+ test("returns values that pass a filter function", async () => {
8
+ const result = await filter(
9
+ {
10
+ a: 1,
11
+ b: 2,
12
+ c: 3,
13
+ d: 4,
14
+ },
15
+ (value) => value % 2 === 1 // odd
16
+ );
17
+ assert.deepEqual(await Tree.plain(result), {
18
+ a: 1,
19
+ c: 3,
20
+ });
21
+ });
22
+
23
+ test("returns deep values that pass a filter function", async () => {
24
+ const result = await filter(
9
25
  {
10
26
  a: 1,
11
27
  b: 2,
@@ -15,13 +31,10 @@ describe("filter", () => {
15
31
  },
16
32
  },
17
33
  {
18
- a: true,
19
- c: {
20
- d: true,
21
- },
34
+ deep: true,
35
+ test: (value) => value % 2 === 1, // odd
22
36
  }
23
37
  );
24
- assert.deepEqual(await result.keys(), ["a", "c/"]);
25
38
  assert.deepEqual(await Tree.plain(result), {
26
39
  a: 1,
27
40
  c: {
@@ -1,6 +1,6 @@
1
- import { Tree } from "@weborigami/async-tree";
2
1
  import assert from "node:assert";
3
2
  import { describe, test } from "node:test";
3
+ import { Tree } from "../../src/internal.js";
4
4
  import globKeys from "../../src/operations/globKeys.js";
5
5
 
6
6
  describe("globKeys", () => {
@@ -0,0 +1,32 @@
1
+ import assert from "node:assert";
2
+ import { describe, test } from "node:test";
3
+ import { Tree } from "../../src/internal.js";
4
+ import mask from "../../src/operations/mask.js";
5
+
6
+ describe("mask", () => {
7
+ test("removes keys and values whose mask values are falsy", async () => {
8
+ const result = mask(
9
+ {
10
+ a: 1,
11
+ b: 2,
12
+ c: {
13
+ d: 3,
14
+ e: 4,
15
+ },
16
+ },
17
+ {
18
+ a: true,
19
+ c: {
20
+ d: true,
21
+ },
22
+ }
23
+ );
24
+ assert.deepEqual(await result.keys(), ["a", "c/"]);
25
+ assert.deepEqual(await Tree.plain(result), {
26
+ a: 1,
27
+ c: {
28
+ d: 3,
29
+ },
30
+ });
31
+ });
32
+ });