@weborigami/async-tree 0.0.38 → 0.0.40

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/main.js CHANGED
@@ -6,6 +6,7 @@ export { default as FunctionTree } from "./src/FunctionTree.js";
6
6
  export { default as MapTree } from "./src/MapTree.js";
7
7
  export { default as ObjectTree } from "./src/ObjectTree.js";
8
8
  // Skip BrowserFileTree.js, which is browser-only.
9
+ export { default as DeepObjectTree } from "./src/DeepObjectTree.js";
9
10
  export { default as SetTree } from "./src/SetTree.js";
10
11
  export { default as SiteTree } from "./src/SiteTree.js";
11
12
  export * as Tree from "./src/Tree.js";
package/package.json CHANGED
@@ -1,20 +1,20 @@
1
1
  {
2
2
  "name": "@weborigami/async-tree",
3
- "version": "0.0.38",
3
+ "version": "0.0.40",
4
4
  "description": "Asynchronous tree drivers based on standard JavaScript classes",
5
5
  "type": "module",
6
6
  "main": "./main.js",
7
7
  "browser": "./browser.js",
8
8
  "types": "./index.ts",
9
9
  "devDependencies": {
10
- "@types/node": "20.8.10",
11
- "typescript": "5.2.2"
10
+ "@types/node": "20.11.7",
11
+ "typescript": "5.3.3"
12
12
  },
13
13
  "dependencies": {
14
- "@weborigami/types": "*"
14
+ "@weborigami/types": "0.0.40"
15
15
  },
16
16
  "scripts": {
17
- "test": "node --test",
17
+ "test": "node --test --test-reporter=spec",
18
18
  "typecheck": "tsc"
19
19
  }
20
20
  }
@@ -0,0 +1,27 @@
1
+ import ObjectTree from "./ObjectTree.js";
2
+ import * as Tree from "./Tree.js";
3
+ import { isPlainObject } from "./utilities.js";
4
+
5
+ export default class DeepObjectTree extends ObjectTree {
6
+ async get(key) {
7
+ let value = await super.get(key);
8
+
9
+ const isPlain =
10
+ value instanceof Array ||
11
+ (isPlainObject(value) && !Tree.isAsyncTree(value));
12
+ if (isPlain) {
13
+ value = Reflect.construct(this.constructor, [value]);
14
+ }
15
+
16
+ if (Tree.isAsyncTree(value) && !value.parent) {
17
+ value.parent = this;
18
+ }
19
+
20
+ return value;
21
+ }
22
+
23
+ async isKeyForSubtree(key) {
24
+ const value = this.object[key];
25
+ return isPlainObject(value) || Tree.isAsyncTree(value);
26
+ }
27
+ }
package/src/MapTree.js CHANGED
@@ -24,10 +24,6 @@ export default class MapTree {
24
24
  async get(key) {
25
25
  let value = this.map.get(key);
26
26
 
27
- if (value instanceof Map) {
28
- value = Reflect.construct(this.constructor, [value]);
29
- }
30
-
31
27
  if (Tree.isAsyncTree(value) && !value.parent) {
32
28
  value.parent = this;
33
29
  }
@@ -37,7 +33,7 @@ export default class MapTree {
37
33
 
38
34
  async isKeyForSubtree(key) {
39
35
  const value = this.map.get(key);
40
- return value instanceof Map || Tree.isAsyncTree(value);
36
+ return Tree.isAsyncTree(value);
41
37
  }
42
38
 
43
39
  async keys() {
package/src/ObjectTree.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as Tree from "./Tree.js";
2
- import { getRealmObjectPrototype, isPlainObject } from "./utilities.js";
2
+ import { getRealmObjectPrototype } from "./utilities.js";
3
3
 
4
4
  /**
5
5
  * A tree defined by a plain object or array.
@@ -37,13 +37,6 @@ export default class ObjectTree {
37
37
 
38
38
  let value = this.object[key];
39
39
 
40
- const isPlain =
41
- value instanceof Array ||
42
- (isPlainObject(value) && !Tree.isAsyncTree(value));
43
- if (isPlain) {
44
- value = Reflect.construct(this.constructor, [value]);
45
- }
46
-
47
40
  if (Tree.isAsyncTree(value) && !value.parent) {
48
41
  value.parent = this;
49
42
  }
@@ -53,7 +46,7 @@ export default class ObjectTree {
53
46
 
54
47
  async isKeyForSubtree(key) {
55
48
  const value = this.object[key];
56
- return isPlainObject(value) || Tree.isAsyncTree(value);
49
+ return Tree.isAsyncTree(value);
57
50
  }
58
51
 
59
52
  /**
package/src/SetTree.js CHANGED
@@ -18,10 +18,6 @@ export default class SetTree {
18
18
  async get(key) {
19
19
  let value = this.values[key];
20
20
 
21
- if (value instanceof Set) {
22
- value = Reflect.construct(this.constructor, [value]);
23
- }
24
-
25
21
  if (Tree.isAsyncTree(value) && !value.parent) {
26
22
  value.parent = this;
27
23
  }
@@ -31,7 +27,7 @@ export default class SetTree {
31
27
 
32
28
  async isKeyForSubtree(key) {
33
29
  const value = this.values[key];
34
- return value instanceof Set || Tree.isAsyncTree(value);
30
+ return Tree.isAsyncTree(value);
35
31
  }
36
32
 
37
33
  async keys() {
package/src/Tree.js CHANGED
@@ -111,9 +111,12 @@ export function from(obj) {
111
111
  } else if (obj instanceof Set) {
112
112
  return new SetTree(obj);
113
113
  } else if (obj && typeof obj === "object" && "unpack" in obj) {
114
- // Invoke unpack and convert the result to a tree.
115
- let result = obj.unpack();
116
- return result instanceof Promise ? new DeferredTree(result) : from(result);
114
+ async function AsyncFunction() {} // Sample async function
115
+ return obj.unpack instanceof AsyncFunction.constructor
116
+ ? // Async unpack: return a deferred tree.
117
+ new DeferredTree(obj.unpack)
118
+ : // Synchronous unpack: cast the result of unpack() to a tree.
119
+ from(obj.unpack());
117
120
  } else if (obj && typeof obj === "object") {
118
121
  // An instance of some class.
119
122
  return new ObjectTree(obj);
@@ -32,9 +32,9 @@ export default function treeCache(source, cacheTree, filter) {
32
32
  // Cache miss or interior node cache hit.
33
33
  let value = await source.get(key);
34
34
  if (value !== undefined) {
35
- // Does this key match the filter?
36
- const filterValue = filter ? await filter.get(key) : true;
37
- const filterMatch = filterValue !== undefined;
35
+ // If a filter is defined, does the key match the filter?
36
+ const filterValue = await filter?.get(key);
37
+ const filterMatch = !filter || filterValue !== undefined;
38
38
  if (filterMatch) {
39
39
  if (Tree.isAsyncTree(value)) {
40
40
  // Construct merged tree for a tree result.
@@ -26,6 +26,7 @@ export default function createGroupByTransform(groupKeyFn) {
26
26
  // A single value was returned
27
27
  groups = [groups];
28
28
  }
29
+ groups = Tree.from(groups);
29
30
 
30
31
  // Add the value to each group.
31
32
  for (const groupKey of await groups.keys()) {
@@ -6,25 +6,35 @@ import * as Tree from "../Tree.js";
6
6
  * @typedef {import("../../index.ts").KeyFn} KeyFn
7
7
  * @typedef {import("../../index.ts").ValueKeyFn} ValueKeyFn
8
8
  *
9
- * @param {ValueKeyFn|{ deep?: boolean, description?: string, inverseKeyMap?: KeyFn, keyMap?: KeyFn, valueMap?: ValueKeyFn }} options
9
+ * @param {ValueKeyFn|{ deep?: boolean, description?: string, needsSourceValue?: boolean, inverseKeyMap?: KeyFn, keyMap?: KeyFn, valueMap?: ValueKeyFn }} options
10
10
  */
11
11
  export default function createMapTransform(options) {
12
12
  let deep;
13
13
  let description;
14
14
  let inverseKeyMap;
15
15
  let keyMap;
16
+ let needsSourceValue;
16
17
  let valueMap;
17
18
  if (typeof options === "function") {
18
19
  // Take the single function argument as the valueMap
19
20
  valueMap = options;
20
21
  } else {
21
- deep = options.deep ?? false;
22
- description = options.description ?? "key/value map";
22
+ deep = options.deep;
23
+ description = options.description;
23
24
  inverseKeyMap = options.inverseKeyMap;
24
25
  keyMap = options.keyMap;
26
+ needsSourceValue = options.needsSourceValue;
25
27
  valueMap = options.valueMap;
26
28
  }
27
29
 
30
+ deep ??= false;
31
+ description ??= "key/value map";
32
+ // @ts-ignore
33
+ inverseKeyMap ??= valueMap?.inverseKeyMap;
34
+ // @ts-ignore
35
+ keyMap ??= valueMap?.keyMap;
36
+ needsSourceValue ??= true;
37
+
28
38
  if ((keyMap && !inverseKeyMap) || (!keyMap && inverseKeyMap)) {
29
39
  throw new TypeError(
30
40
  `map: You must specify both keyMap and inverseKeyMap, or neither.`
@@ -58,11 +68,18 @@ export default function createMapTransform(options) {
58
68
  }
59
69
 
60
70
  // Step 2: Get the source value.
61
- const sourceValue = await tree.get(sourceKey);
71
+ let sourceValue;
72
+ if (needsSourceValue) {
73
+ // Normal case: get the value from the source tree.
74
+ sourceValue = await tree.get(sourceKey);
75
+ } else if (deep && (await Tree.isKeyForSubtree(tree, sourceKey))) {
76
+ // Only get the source value if it's a subtree.
77
+ sourceValue = tree;
78
+ }
62
79
 
63
80
  // Step 3: Map the source value to the result value.
64
81
  let resultValue;
65
- if (sourceValue === undefined) {
82
+ if (needsSourceValue && sourceValue === undefined) {
66
83
  // No source value means no result value.
67
84
  resultValue = undefined;
68
85
  } else if (deep && Tree.isAsyncTree(sourceValue)) {
@@ -0,0 +1,24 @@
1
+ import assert from "node:assert";
2
+ import { describe, test } from "node:test";
3
+ import DeepObjectTree from "../src/DeepObjectTree.js";
4
+ import * as Tree from "../src/Tree.js";
5
+
6
+ describe("DeepObjectTree", () => {
7
+ test("returns an ObjectTree for value that's a plain sub-object or sub-array", async () => {
8
+ const tree = new DeepObjectTree({
9
+ a: 1,
10
+ object: {
11
+ b: 2,
12
+ },
13
+ array: [3],
14
+ });
15
+
16
+ const object = await tree.get("object");
17
+ assert.equal(object instanceof DeepObjectTree, true);
18
+ assert.deepEqual(await Tree.plain(object), { b: 2 });
19
+
20
+ const array = await tree.get("array");
21
+ assert.equal(array instanceof DeepObjectTree, true);
22
+ assert.deepEqual(await Tree.plain(array), [3]);
23
+ });
24
+ });
@@ -76,28 +76,13 @@ describe("ObjectTree", () => {
76
76
  assert.equal(await fixture.get("prop"), "Goodbye");
77
77
  });
78
78
 
79
- test("creates an ObjectTree for subtrees", async () => {
80
- const object = {
81
- a: 1,
82
- more: {
83
- b: 2,
84
- },
85
- };
86
- const fixture = new ObjectTree(object);
87
- const more = await fixture.get("more");
88
- assert.equal(more.constructor, ObjectTree);
89
- const b = await more.get("b");
90
- assert.equal(b, 2);
91
- });
92
-
93
79
  test("sets parent on subtrees", async () => {
94
- const object = {
80
+ const fixture = new ObjectTree({
95
81
  a: 1,
96
- more: {
82
+ more: new ObjectTree({
97
83
  b: 2,
98
- },
99
- };
100
- const fixture = new ObjectTree(object);
84
+ }),
85
+ });
101
86
  const more = await fixture.get("more");
102
87
  assert.equal(more.parent, fixture);
103
88
  });
@@ -105,13 +90,13 @@ describe("ObjectTree", () => {
105
90
  test("isKeyForSubtree() indicates which values are subtrees", async () => {
106
91
  const tree = new ObjectTree({
107
92
  a1: 1,
108
- a2: {
93
+ a2: new ObjectTree({
109
94
  b1: 2,
110
- },
95
+ }),
111
96
  a3: 3,
112
- a4: {
97
+ a4: new ObjectTree({
113
98
  b2: 4,
114
- },
99
+ }),
115
100
  });
116
101
  const keys = Array.from(await tree.keys());
117
102
  const subtrees = await Promise.all(
@@ -120,24 +105,6 @@ describe("ObjectTree", () => {
120
105
  assert.deepEqual(subtrees, [false, true, false, true]);
121
106
  });
122
107
 
123
- test("returns an ObjectTree for value that's a plain sub-object or sub-array", async () => {
124
- const tree = new ObjectTree({
125
- a: 1,
126
- object: {
127
- b: 2,
128
- },
129
- array: [3],
130
- });
131
-
132
- const object = await tree.get("object");
133
- assert.equal(object instanceof ObjectTree, true);
134
- assert.deepEqual(await Tree.plain(object), { b: 2 });
135
-
136
- const array = await tree.get("array");
137
- assert.equal(array instanceof ObjectTree, true);
138
- assert.deepEqual(await Tree.plain(array), [3]);
139
- });
140
-
141
108
  test("returns an async tree value as is", async () => {
142
109
  const subtree = {
143
110
  async get(key) {},
@@ -1,5 +1,6 @@
1
1
  import assert from "node:assert";
2
2
  import { describe, test } from "node:test";
3
+ import ObjectTree from "../src/ObjectTree.js";
3
4
  import SetTree from "../src/SetTree.js";
4
5
 
5
6
  describe("SetTree", () => {
@@ -24,7 +25,7 @@ describe("SetTree", () => {
24
25
 
25
26
  test("sets parent on subtrees", async () => {
26
27
  const set = new Set();
27
- set.add(new Set("a"));
28
+ set.add(new ObjectTree({}));
28
29
  const fixture = new SetTree(set);
29
30
  const subtree = await fixture.get(0);
30
31
  assert.equal(subtree.parent, fixture);
package/test/Tree.test.js CHANGED
@@ -1,12 +1,13 @@
1
1
  import assert from "node:assert";
2
2
  import { describe, test } from "node:test";
3
+ import { DeepObjectTree } from "../main.js";
3
4
  import MapTree from "../src/MapTree.js";
4
5
  import ObjectTree from "../src/ObjectTree.js";
5
6
  import * as Tree from "../src/Tree.js";
6
7
 
7
8
  describe("Tree", () => {
8
9
  test("assign applies one tree to another", async () => {
9
- const target = new ObjectTree({
10
+ const target = new DeepObjectTree({
10
11
  a: 1,
11
12
  b: 2,
12
13
  more: {
@@ -14,7 +15,7 @@ describe("Tree", () => {
14
15
  },
15
16
  });
16
17
 
17
- const source = {
18
+ const source = new DeepObjectTree({
18
19
  a: 4, // Overwrite existing value
19
20
  b: undefined, // Delete
20
21
  c: 5, // Add
@@ -26,7 +27,7 @@ describe("Tree", () => {
26
27
  extra: {
27
28
  f: 7,
28
29
  },
29
- };
30
+ });
30
31
 
31
32
  // Apply changes.
32
33
  const result = await Tree.assign(target, source);
@@ -165,7 +166,7 @@ describe("Tree", () => {
165
166
  });
166
167
 
167
168
  test("isKeyForSubtree() returns true if the key is for a subtree", async () => {
168
- const tree = new ObjectTree({
169
+ const tree = new DeepObjectTree({
169
170
  a: 1,
170
171
  more: {
171
172
  b: 2,
@@ -176,12 +177,12 @@ describe("Tree", () => {
176
177
  });
177
178
 
178
179
  test("map() maps values", async () => {
179
- const tree = {
180
+ const tree = new DeepObjectTree({
180
181
  a: "Alice",
181
182
  more: {
182
183
  b: "Bob",
183
184
  },
184
- };
185
+ });
185
186
  const mapped = Tree.map(tree, (value) => value.toUpperCase());
186
187
  assert.deepEqual(await Tree.plain(mapped), {
187
188
  a: "ALICE",
@@ -192,14 +193,14 @@ describe("Tree", () => {
192
193
  });
193
194
 
194
195
  test("mapReduce() can map values and reduce them", async () => {
195
- const tree = {
196
+ const tree = new DeepObjectTree({
196
197
  a: 1,
197
198
  b: 2,
198
199
  more: {
199
200
  c: 3,
200
201
  },
201
202
  d: 4,
202
- };
203
+ });
203
204
  const reduced = await Tree.mapReduce(
204
205
  tree,
205
206
  (value) => value,
@@ -1,12 +1,13 @@
1
1
  import assert from "node:assert";
2
2
  import { describe, test } from "node:test";
3
+ import DeepObjectTree from "../../src/DeepObjectTree.js";
3
4
  import * as Tree from "../../src/Tree.js";
4
5
  import mergeDeep from "../../src/operations/mergeDeep.js";
5
6
 
6
7
  describe("mergeDeep", () => {
7
8
  test("can merge deep", async () => {
8
9
  const fixture = mergeDeep(
9
- Tree.from({
10
+ new DeepObjectTree({
10
11
  a: {
11
12
  b: 1,
12
13
  c: {
@@ -14,7 +15,7 @@ describe("mergeDeep", () => {
14
15
  },
15
16
  },
16
17
  }),
17
- Tree.from({
18
+ new DeepObjectTree({
18
19
  a: {
19
20
  b: 0, // Will be obscured by `b` above
20
21
  c: {
@@ -12,9 +12,7 @@ describe("groupBy transform", () => {
12
12
  { name: "Work Sans", tags: ["Grotesque", "Sans Serif"] },
13
13
  ];
14
14
  const tree = Tree.from(fonts);
15
- const grouped = await groupBy((value, key, tree) => value.get("tags"))(
16
- tree
17
- );
15
+ const grouped = await groupBy((value, key, tree) => value.tags)(tree);
18
16
  assert.deepEqual(await Tree.plain(grouped), {
19
17
  Geometric: [{ name: "Albert Sans", tags: ["Geometric", "Sans Serif"] }],
20
18
  Grotesque: [{ name: "Work Sans", tags: ["Grotesque", "Sans Serif"] }],
@@ -1,5 +1,7 @@
1
1
  import assert from "node:assert";
2
2
  import { describe, test } from "node:test";
3
+ import DeepObjectTree from "../../src/DeepObjectTree.js";
4
+ import FunctionTree from "../../src/FunctionTree.js";
3
5
  import ObjectTree from "../../src/ObjectTree.js";
4
6
  import * as Tree from "../../src/Tree.js";
5
7
  import map from "../../src/transforms/map.js";
@@ -108,8 +110,23 @@ describe("map", () => {
108
110
  });
109
111
  });
110
112
 
111
- test("deep maps values", async () => {
113
+ test("valueMap can provide a default keyMap and inverseKeyMap", async () => {
114
+ const uppercase = (s) => s.toUpperCase();
115
+ uppercase.keyMap = (sourceKey) => `_${sourceKey}`;
116
+ uppercase.inverseKeyMap = (resultKey) => resultKey.slice(1);
112
117
  const tree = new ObjectTree({
118
+ a: "letter a",
119
+ b: "letter b",
120
+ });
121
+ const mapped = map(uppercase)(tree);
122
+ assert.deepEqual(await Tree.plain(mapped), {
123
+ _a: "LETTER A",
124
+ _b: "LETTER B",
125
+ });
126
+ });
127
+
128
+ test("deep maps values", async () => {
129
+ const tree = new DeepObjectTree({
113
130
  a: "letter a",
114
131
  more: {
115
132
  b: "letter b",
@@ -129,7 +146,7 @@ describe("map", () => {
129
146
  });
130
147
 
131
148
  test("deep maps leaf keys", async () => {
132
- const tree = new ObjectTree({
149
+ const tree = new DeepObjectTree({
133
150
  a: "letter a",
134
151
  more: {
135
152
  b: "letter b",
@@ -150,7 +167,7 @@ describe("map", () => {
150
167
  });
151
168
 
152
169
  test("deep maps leaf keys and values", async () => {
153
- const tree = new ObjectTree({
170
+ const tree = new DeepObjectTree({
154
171
  a: "letter a",
155
172
  more: {
156
173
  b: "letter b",
@@ -171,4 +188,21 @@ describe("map", () => {
171
188
  },
172
189
  });
173
190
  });
191
+
192
+ test("needsSourceValue can be set to false in cases where the value isn't necessary", async () => {
193
+ let flag = false;
194
+ const tree = new FunctionTree(() => {
195
+ flag = true;
196
+ }, ["a", "b", "c"]);
197
+ const mapped = map({
198
+ needsSourceValue: false,
199
+ valueMap: () => "X",
200
+ })(tree);
201
+ assert.deepEqual(await Tree.plain(mapped), {
202
+ a: "X",
203
+ b: "X",
204
+ c: "X",
205
+ });
206
+ assert(!flag);
207
+ });
174
208
  });
@@ -1,12 +1,13 @@
1
1
  import assert from "node:assert";
2
2
  import { describe, test } from "node:test";
3
+ import DeepObjectTree from "../../src/DeepObjectTree.js";
3
4
  import * as Tree from "../../src/Tree.js";
4
5
  import regExpKeys from "../../src/transforms/regExpKeys.js";
5
6
 
6
7
  describe("regExpKeys", () => {
7
8
  test("matches keys using regular expressions", async () => {
8
9
  const fixture = await regExpKeys(
9
- Tree.from({
10
+ new DeepObjectTree({
10
11
  a: true,
11
12
  "b.*": true,
12
13
  c: {