@weborigami/async-tree 0.0.35

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.
Files changed (57) hide show
  1. package/ReadMe.md +1 -0
  2. package/browser.js +13 -0
  3. package/index.ts +44 -0
  4. package/main.js +23 -0
  5. package/package.json +20 -0
  6. package/src/BrowserFileTree.js +137 -0
  7. package/src/DeferredTree.js +72 -0
  8. package/src/FileTree.js +193 -0
  9. package/src/FunctionTree.js +47 -0
  10. package/src/MapTree.js +51 -0
  11. package/src/ObjectTree.js +103 -0
  12. package/src/SetTree.js +40 -0
  13. package/src/SiteTree.js +124 -0
  14. package/src/Tree.d.ts +22 -0
  15. package/src/Tree.js +404 -0
  16. package/src/keysJson.d.ts +4 -0
  17. package/src/keysJson.js +56 -0
  18. package/src/operations/cache.js +75 -0
  19. package/src/operations/merge.js +60 -0
  20. package/src/operations/mergeDeep.js +47 -0
  21. package/src/transforms/cachedKeyMaps.js +86 -0
  22. package/src/transforms/groupBy.js +40 -0
  23. package/src/transforms/keyMapsForExtensions.js +64 -0
  24. package/src/transforms/map.js +106 -0
  25. package/src/transforms/regExpKeys.js +65 -0
  26. package/src/transforms/sort.js +22 -0
  27. package/src/transforms/sortBy.js +29 -0
  28. package/src/transforms/sortNatural.js +10 -0
  29. package/src/utilities.d.ts +10 -0
  30. package/src/utilities.js +124 -0
  31. package/test/BrowserFileTree.test.js +119 -0
  32. package/test/DeferredTree.test.js +23 -0
  33. package/test/FileTree.test.js +134 -0
  34. package/test/FunctionTree.test.js +51 -0
  35. package/test/MapTree.test.js +37 -0
  36. package/test/ObjectTree.test.js +159 -0
  37. package/test/SetTree.test.js +32 -0
  38. package/test/SiteTree.test.js +110 -0
  39. package/test/Tree.test.js +347 -0
  40. package/test/browser/assert.js +45 -0
  41. package/test/browser/index.html +35 -0
  42. package/test/browser/testRunner.js +51 -0
  43. package/test/fixtures/markdown/Alice.md +1 -0
  44. package/test/fixtures/markdown/Bob.md +1 -0
  45. package/test/fixtures/markdown/Carol.md +1 -0
  46. package/test/operations/cache.test.js +57 -0
  47. package/test/operations/merge.test.js +39 -0
  48. package/test/operations/mergeDeep.test.js +38 -0
  49. package/test/transforms/cachedKeyMaps.test.js +41 -0
  50. package/test/transforms/groupBy.test.js +29 -0
  51. package/test/transforms/keyMapsForExtensions.test.js +70 -0
  52. package/test/transforms/map.test.js +174 -0
  53. package/test/transforms/regExpKeys.test.js +25 -0
  54. package/test/transforms/sort.test.js +30 -0
  55. package/test/transforms/sortBy.test.js +22 -0
  56. package/test/transforms/sortNatural.test.js +21 -0
  57. package/test/utilities.test.js +24 -0
@@ -0,0 +1,38 @@
1
+ import assert from "node:assert";
2
+ import { describe, test } from "node:test";
3
+ import * as Tree from "../../src/Tree.js";
4
+ import mergeDeep from "../../src/operations/mergeDeep.js";
5
+
6
+ describe("mergeDeep", () => {
7
+ test("can merge deep", async () => {
8
+ const fixture = mergeDeep(
9
+ Tree.from({
10
+ a: {
11
+ b: 1,
12
+ c: {
13
+ d: 2,
14
+ },
15
+ },
16
+ }),
17
+ Tree.from({
18
+ a: {
19
+ b: 0, // Will be obscured by `b` above
20
+ c: {
21
+ e: 3,
22
+ },
23
+ f: 4,
24
+ },
25
+ })
26
+ );
27
+ assert.deepEqual(await Tree.plain(fixture), {
28
+ a: {
29
+ b: 1,
30
+ c: {
31
+ d: 2,
32
+ e: 3,
33
+ },
34
+ f: 4,
35
+ },
36
+ });
37
+ });
38
+ });
@@ -0,0 +1,41 @@
1
+ import assert from "node:assert";
2
+ import { describe, test } from "node:test";
3
+ import ObjectTree from "../../src/ObjectTree.js";
4
+ import cachedKeyMaps from "../../src/transforms/cachedKeyMaps.js";
5
+
6
+ describe("cachedKeyMaps", () => {
7
+ test("maps keys with caching", async () => {
8
+ const tree = new ObjectTree({
9
+ a: "letter a",
10
+ b: "letter b",
11
+ });
12
+
13
+ let callCount = 0;
14
+ const underscoreKeys = async (sourceKey, tree) => {
15
+ callCount++;
16
+ return `_${sourceKey}`;
17
+ };
18
+
19
+ const { inverseKeyMap, keyMap } = cachedKeyMaps(underscoreKeys);
20
+
21
+ assert.equal(await inverseKeyMap("_a", tree), "a"); // Cache miss
22
+ assert.equal(callCount, 1);
23
+ assert.equal(await inverseKeyMap("_a", tree), "a");
24
+ assert.equal(callCount, 1);
25
+ assert.equal(await inverseKeyMap("_b", tree), "b"); // Cache miss
26
+ assert.equal(callCount, 2);
27
+
28
+ assert.equal(await keyMap("a", tree), "_a");
29
+ assert.equal(await keyMap("a", tree), "_a");
30
+ assert.equal(await keyMap("b", tree), "_b");
31
+ assert.equal(callCount, 2);
32
+
33
+ // `c` isn't in tree, so we should get undefined.
34
+ assert.equal(await inverseKeyMap("_c", tree), undefined);
35
+ // But key mapping is still possible.
36
+ assert.equal(await keyMap("c", tree), "_c");
37
+ // And now we have a cache hit.
38
+ assert.equal(await inverseKeyMap("_c", tree), "c");
39
+ assert.equal(callCount, 3);
40
+ });
41
+ });
@@ -0,0 +1,29 @@
1
+ import assert from "node:assert";
2
+ import { describe, test } from "node:test";
3
+ import * as Tree from "../../src/Tree.js";
4
+ import groupBy from "../../src/transforms/groupBy.js";
5
+
6
+ describe("groupBy transform", () => {
7
+ test("groups using a group key function", async () => {
8
+ const fonts = [
9
+ { name: "Aboreto", tags: ["Sans Serif"] },
10
+ { name: "Albert Sans", tags: ["Geometric", "Sans Serif"] },
11
+ { name: "Alegreya", tags: ["Serif"] },
12
+ { name: "Work Sans", tags: ["Grotesque", "Sans Serif"] },
13
+ ];
14
+ const tree = Tree.from(fonts);
15
+ const grouped = await groupBy((value, key, tree) => value.get("tags"))(
16
+ tree
17
+ );
18
+ assert.deepEqual(await Tree.plain(grouped), {
19
+ Geometric: [{ name: "Albert Sans", tags: ["Geometric", "Sans Serif"] }],
20
+ Grotesque: [{ name: "Work Sans", tags: ["Grotesque", "Sans Serif"] }],
21
+ "Sans Serif": [
22
+ { name: "Aboreto", tags: ["Sans Serif"] },
23
+ { name: "Albert Sans", tags: ["Geometric", "Sans Serif"] },
24
+ { name: "Work Sans", tags: ["Grotesque", "Sans Serif"] },
25
+ ],
26
+ Serif: [{ name: "Alegreya", tags: ["Serif"] }],
27
+ });
28
+ });
29
+ });
@@ -0,0 +1,70 @@
1
+ import assert from "node:assert";
2
+ import { describe, test } from "node:test";
3
+ import ObjectTree from "../../src/ObjectTree.js";
4
+ import * as Tree from "../../src/Tree.js";
5
+ import keyMapsForExtensions from "../../src/transforms/keyMapsForExtensions.js";
6
+ import map from "../../src/transforms/map.js";
7
+
8
+ describe("keyMapsForExtensions", () => {
9
+ test("returns key functions that pass a matching key through", async () => {
10
+ const { inverseKeyMap, keyMap } = keyMapsForExtensions({
11
+ sourceExtension: "txt",
12
+ });
13
+ assert.equal(await inverseKeyMap("file.txt"), "file.txt");
14
+ assert.equal(await keyMap("file.txt"), "file.txt");
15
+ assert.equal(await inverseKeyMap("file.foo"), undefined);
16
+ assert.equal(await keyMap("file.foo"), undefined);
17
+ });
18
+
19
+ test("returns key functions that can map extensions", async () => {
20
+ const { inverseKeyMap, keyMap } = keyMapsForExtensions({
21
+ resultExtension: "html",
22
+ sourceExtension: "md",
23
+ });
24
+ assert.equal(await inverseKeyMap("file.html"), "file.md");
25
+ assert.equal(await keyMap("file.md"), "file.html");
26
+ assert.equal(await inverseKeyMap("file.foo"), undefined);
27
+ assert.equal(await keyMap("file.foo"), undefined);
28
+ });
29
+
30
+ test("works with map to handle keys that end in a given resultExtension", async () => {
31
+ const files = new ObjectTree({
32
+ "file1.txt": "will be mapped",
33
+ file2: "won't be mapped",
34
+ "file3.foo": "won't be mapped",
35
+ });
36
+ const { inverseKeyMap, keyMap } = keyMapsForExtensions({
37
+ sourceExtension: "txt",
38
+ });
39
+ const transform = map({
40
+ inverseKeyMap,
41
+ keyMap,
42
+ valueMap: (sourceValue, sourceKey, tree) => sourceValue.toUpperCase(),
43
+ });
44
+ const fixture = transform(files);
45
+ assert.deepEqual(await Tree.plain(fixture), {
46
+ "file1.txt": "WILL BE MAPPED",
47
+ });
48
+ });
49
+
50
+ test("works with map to change a key's resultExtension", async () => {
51
+ const files = new ObjectTree({
52
+ "file1.txt": "will be mapped",
53
+ file2: "won't be mapped",
54
+ "file3.foo": "won't be mapped",
55
+ });
56
+ const { inverseKeyMap, keyMap } = keyMapsForExtensions({
57
+ resultExtension: "upper",
58
+ sourceExtension: "txt",
59
+ });
60
+ const transform = map({
61
+ inverseKeyMap,
62
+ keyMap,
63
+ valueMap: (sourceValue, sourceKey, tree) => sourceValue.toUpperCase(),
64
+ });
65
+ const fixture = transform(files);
66
+ assert.deepEqual(await Tree.plain(fixture), {
67
+ "file1.upper": "WILL BE MAPPED",
68
+ });
69
+ });
70
+ });
@@ -0,0 +1,174 @@
1
+ import assert from "node:assert";
2
+ import { describe, test } from "node:test";
3
+ import ObjectTree from "../../src/ObjectTree.js";
4
+ import * as Tree from "../../src/Tree.js";
5
+ import map from "../../src/transforms/map.js";
6
+
7
+ describe("map", () => {
8
+ test("returns identity graph if no keyMap or valueMap", async () => {
9
+ const tree = new ObjectTree({
10
+ a: "letter a",
11
+ b: "letter b",
12
+ });
13
+ const mapped = map({})(tree);
14
+ assert.deepEqual(await Tree.plain(mapped), {
15
+ a: "letter a",
16
+ b: "letter b",
17
+ });
18
+ });
19
+
20
+ test("maps values", async () => {
21
+ const tree = new ObjectTree({
22
+ a: "letter a",
23
+ b: "letter b",
24
+ c: undefined, // Won't be mapped
25
+ });
26
+ const uppercaseValues = map({
27
+ valueMap: (sourceValue, sourceKey, innerTree) => {
28
+ assert(sourceKey === "a" || sourceKey === "b");
29
+ assert.equal(innerTree, tree);
30
+ return sourceValue.toUpperCase();
31
+ },
32
+ });
33
+ const mapped = uppercaseValues(tree);
34
+ assert.deepEqual(await Tree.plain(mapped), {
35
+ a: "LETTER A",
36
+ b: "LETTER B",
37
+ c: undefined,
38
+ });
39
+ });
40
+
41
+ test("interprets a single function argument as the value function", async () => {
42
+ const tree = new ObjectTree({
43
+ a: "letter a",
44
+ b: "letter b",
45
+ });
46
+ const uppercaseValues = map((sourceValue, sourceKey, innerTree) => {
47
+ assert(sourceKey === "a" || sourceKey === "b");
48
+ assert.equal(innerTree, tree);
49
+ return sourceValue.toUpperCase();
50
+ });
51
+ const mapped = uppercaseValues(tree);
52
+ assert.deepEqual(await Tree.plain(mapped), {
53
+ a: "LETTER A",
54
+ b: "LETTER B",
55
+ });
56
+ });
57
+
58
+ test("maps keys using keyMap and inverseKeyMap", async () => {
59
+ const tree = new ObjectTree({
60
+ a: "letter a",
61
+ b: "letter b",
62
+ });
63
+ const doubleKeys = map({
64
+ keyMap: async (sourceKey, tree) => `_${sourceKey}`,
65
+ inverseKeyMap: async (resultKey, tree) => resultKey.slice(1),
66
+ });
67
+ const mapped = doubleKeys(tree);
68
+ assert.deepEqual(await Tree.plain(mapped), {
69
+ _a: "letter a",
70
+ _b: "letter b",
71
+ });
72
+ });
73
+
74
+ test("maps keys and values", async () => {
75
+ const tree = new ObjectTree({
76
+ a: "letter a",
77
+ b: "letter b",
78
+ });
79
+ const doubleKeysUppercaseValues = map({
80
+ keyMap: async (sourceKey, tree) => `_${sourceKey}`,
81
+ inverseKeyMap: async (resultKey, tree) => resultKey.slice(1),
82
+ valueMap: async (sourceValue, sourceKey, tree) =>
83
+ sourceValue.toUpperCase(),
84
+ });
85
+ const mapped = doubleKeysUppercaseValues(tree);
86
+ assert.deepEqual(await Tree.plain(mapped), {
87
+ _a: "LETTER A",
88
+ _b: "LETTER B",
89
+ });
90
+ });
91
+
92
+ test("a shallow map is applied to async subtrees too", async () => {
93
+ const tree = new ObjectTree({
94
+ a: "letter a",
95
+ more: {
96
+ b: "letter b",
97
+ },
98
+ });
99
+ const doubleKeys = map({
100
+ keyMap: async (sourceKey, tree) => `_${sourceKey}`,
101
+ inverseKeyMap: async (resultKey, tree) => resultKey.slice(1),
102
+ valueMap: async (sourceValue, sourceKey, tree) => sourceKey,
103
+ });
104
+ const mapped = doubleKeys(tree);
105
+ assert.deepEqual(await Tree.plain(mapped), {
106
+ _a: "a",
107
+ _more: "more",
108
+ });
109
+ });
110
+
111
+ test("deep maps values", async () => {
112
+ const tree = new ObjectTree({
113
+ a: "letter a",
114
+ more: {
115
+ b: "letter b",
116
+ },
117
+ });
118
+ const uppercaseValues = map({
119
+ deep: true,
120
+ valueMap: (sourceValue, sourceKey, tree) => sourceValue.toUpperCase(),
121
+ });
122
+ const mapped = uppercaseValues(tree);
123
+ assert.deepEqual(await Tree.plain(mapped), {
124
+ a: "LETTER A",
125
+ more: {
126
+ b: "LETTER B",
127
+ },
128
+ });
129
+ });
130
+
131
+ test("deep maps leaf keys", async () => {
132
+ const tree = new ObjectTree({
133
+ a: "letter a",
134
+ more: {
135
+ b: "letter b",
136
+ },
137
+ });
138
+ const doubleKeys = map({
139
+ deep: true,
140
+ keyMap: async (sourceKey, tree) => `_${sourceKey}`,
141
+ inverseKeyMap: async (resultKey, tree) => resultKey.slice(1),
142
+ });
143
+ const mapped = doubleKeys(tree);
144
+ assert.deepEqual(await Tree.plain(mapped), {
145
+ _a: "letter a",
146
+ more: {
147
+ _b: "letter b",
148
+ },
149
+ });
150
+ });
151
+
152
+ test("deep maps leaf keys and values", async () => {
153
+ const tree = new ObjectTree({
154
+ a: "letter a",
155
+ more: {
156
+ b: "letter b",
157
+ },
158
+ });
159
+ const doubleKeysUppercaseValues = map({
160
+ deep: true,
161
+ keyMap: async (sourceKey, tree) => `_${sourceKey}`,
162
+ inverseKeyMap: async (resultKey, tree) => resultKey.slice(1),
163
+ valueMap: async (sourceValue, sourceKey, tree) =>
164
+ sourceValue.toUpperCase(),
165
+ });
166
+ const mapped = doubleKeysUppercaseValues(tree);
167
+ assert.deepEqual(await Tree.plain(mapped), {
168
+ _a: "LETTER A",
169
+ more: {
170
+ _b: "LETTER B",
171
+ },
172
+ });
173
+ });
174
+ });
@@ -0,0 +1,25 @@
1
+ import assert from "node:assert";
2
+ import { describe, test } from "node:test";
3
+ import * as Tree from "../../src/Tree.js";
4
+ import regExpKeys from "../../src/transforms/regExpKeys.js";
5
+
6
+ describe("regExpKeys", () => {
7
+ test("matches keys using regular expressions", async () => {
8
+ const fixture = await regExpKeys(
9
+ Tree.from({
10
+ a: true,
11
+ "b.*": true,
12
+ c: {
13
+ d: true,
14
+ "e*": true,
15
+ },
16
+ })
17
+ );
18
+ assert(await Tree.traverse(fixture, "a"));
19
+ assert(!(await Tree.traverse(fixture, "alice")));
20
+ assert(await Tree.traverse(fixture, "bob"));
21
+ assert(await Tree.traverse(fixture, "brenda"));
22
+ assert(await Tree.traverse(fixture, "c", "d"));
23
+ assert(await Tree.traverse(fixture, "c", "eee"));
24
+ });
25
+ });
@@ -0,0 +1,30 @@
1
+ import assert from "node:assert";
2
+ import { describe, test } from "node:test";
3
+ import * as Tree from "../../src/Tree.js";
4
+ import sort from "../../src/transforms/sort.js";
5
+
6
+ describe("sort transform", () => {
7
+ test("sorts keys", async () => {
8
+ const tree = Tree.from({
9
+ b: 2,
10
+ c: 3,
11
+ a: 1,
12
+ });
13
+ const sortTransform = sort();
14
+ const sorted = await sortTransform(tree);
15
+ assert.deepEqual(Array.from(await sorted.keys()), ["a", "b", "c"]);
16
+ });
17
+
18
+ test("sorts keys using a comparison function", async () => {
19
+ const tree = Tree.from({
20
+ b: 2,
21
+ c: 3,
22
+ a: 1,
23
+ });
24
+ // Reverse order
25
+ const compareFn = (a, b) => (a > b ? -1 : a < b ? 1 : 0);
26
+ const sortTransform = sort(compareFn);
27
+ const sorted = await sortTransform(tree);
28
+ assert.deepEqual(Array.from(await sorted.keys()), ["c", "b", "a"]);
29
+ });
30
+ });
@@ -0,0 +1,22 @@
1
+ import assert from "node:assert";
2
+ import { describe, test } from "node:test";
3
+ import * as Tree from "../../src/Tree.js";
4
+ import sortBy from "../../src/transforms/sortBy.js";
5
+
6
+ describe("sortBy transform", () => {
7
+ test("sorts keys using a provided sort key function", async () => {
8
+ const tree = Tree.from({
9
+ Alice: { age: 48 },
10
+ Bob: { age: 36 },
11
+ Carol: { age: 42 },
12
+ });
13
+ const sorted = await sortBy((key, tree) => Tree.traverse(tree, key, "age"))(
14
+ tree
15
+ );
16
+ assert.deepEqual(Array.from(await sorted.keys()), [
17
+ "Bob",
18
+ "Carol",
19
+ "Alice",
20
+ ]);
21
+ });
22
+ });
@@ -0,0 +1,21 @@
1
+ import assert from "node:assert";
2
+ import { describe, test } from "node:test";
3
+ import * as Tree from "../../src/Tree.js";
4
+ import sortNatural from "../../src/transforms/sortNatural.js";
5
+
6
+ describe("sortNatural transform", () => {
7
+ test("sorts keys", async () => {
8
+ const tree = Tree.from({
9
+ file10: null,
10
+ file1: null,
11
+ file9: null,
12
+ });
13
+ const sortTransform = sortNatural();
14
+ const sorted = await sortTransform(tree);
15
+ assert.deepEqual(Array.from(await sorted.keys()), [
16
+ "file1",
17
+ "file9",
18
+ "file10",
19
+ ]);
20
+ });
21
+ });
@@ -0,0 +1,24 @@
1
+ import assert from "node:assert";
2
+ import { describe, test } from "node:test";
3
+ import * as utilities from "../src/utilities.js";
4
+
5
+ describe("utilities", () => {
6
+ test("getRealmObjectPrototype returns the object's root prototype", () => {
7
+ const object = {};
8
+ const proto = utilities.getRealmObjectPrototype(object);
9
+ assert.equal(proto, Object.prototype);
10
+ });
11
+
12
+ test("isPlainObject returns true if the object is a plain object", () => {
13
+ assert.equal(utilities.isPlainObject({}), true);
14
+ assert.equal(utilities.isPlainObject(new Object()), true);
15
+ assert.equal(utilities.isPlainObject(Object.create(null)), true);
16
+ class Foo {}
17
+ assert.equal(utilities.isPlainObject(new Foo()), false);
18
+ });
19
+
20
+ test("keysFromPath() returns the keys from a slash-separated path", () => {
21
+ assert.deepEqual(utilities.keysFromPath("a/b/c"), ["a", "b", "c"]);
22
+ assert.deepEqual(utilities.keysFromPath("foo/"), ["foo", ""]);
23
+ });
24
+ });