@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 +2 -2
- package/shared.js +1 -0
- package/src/operations/addNextPrevious.js +2 -2
- package/src/operations/filter.js +27 -41
- package/src/operations/globKeys.js +5 -1
- package/src/operations/mask.js +55 -0
- package/test/drivers/constantTree.test.js +1 -1
- package/test/jsonKeys.test.js +1 -1
- package/test/operations/filter.test.js +20 -7
- package/test/operations/globKeys.test.js +1 -1
- package/test/operations/mask.test.js +32 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weborigami/async-tree",
|
|
3
|
-
"version": "0.
|
|
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.
|
|
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 "
|
|
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("
|
|
8
|
+
* @typedef {import("../../index.ts").PlainObject} PlainObject
|
|
9
9
|
*
|
|
10
10
|
* @param {import("../../index.ts").Treelike} treelike
|
|
11
11
|
* @returns {Promise<PlainObject|Array>}
|
package/src/operations/filter.js
CHANGED
|
@@ -1,53 +1,39 @@
|
|
|
1
|
-
import { assertIsTreelike
|
|
1
|
+
import { assertIsTreelike } from "../utilities.js";
|
|
2
|
+
import map from "./map.js";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
|
-
* Given
|
|
5
|
-
*
|
|
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}
|
|
12
|
-
* @param {
|
|
11
|
+
* @param {Treelike} treelike
|
|
12
|
+
* @param {function|any} options
|
|
13
13
|
* @returns {AsyncTree}
|
|
14
14
|
*/
|
|
15
|
-
export default function filter(
|
|
16
|
-
assertIsTreelike(
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
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
|
|
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 () => {
|
package/test/jsonKeys.test.js
CHANGED
|
@@ -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("
|
|
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
|
-
|
|
19
|
-
|
|
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
|
+
});
|