@weborigami/async-tree 0.6.12 → 0.6.13
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 +1 -1
- package/src/Tree.js +2 -0
- package/src/drivers/AsyncMap.js +43 -0
- package/src/drivers/SyncMap.js +44 -0
- package/src/operations/concat.js +56 -0
- package/src/operations/deepValuesIterator.js +21 -11
- package/src/operations/flat.js +21 -0
- package/src/operations/isMap.js +1 -0
- package/src/operations/merge.js +1 -1
- package/src/utilities/getParent.js +4 -1
- package/test/drivers/AsyncMap.test.js +50 -1
- package/test/drivers/SyncMap.test.js +45 -1
- package/test/operations/concat.test.js +66 -0
- package/test/operations/deepValuesIterator.test.js +42 -0
- package/test/operations/flat.test.js +58 -0
package/package.json
CHANGED
package/src/Tree.js
CHANGED
|
@@ -8,6 +8,7 @@ export { default as cache } from "./operations/cache.js";
|
|
|
8
8
|
export { default as calendar } from "./operations/calendar.js";
|
|
9
9
|
export { default as child } from "./operations/child.js";
|
|
10
10
|
export { default as clear } from "./operations/clear.js";
|
|
11
|
+
export { default as concat } from "./operations/concat.js";
|
|
11
12
|
export { default as constant } from "./operations/constant.js";
|
|
12
13
|
export { default as deepEntries } from "./operations/deepEntries.js";
|
|
13
14
|
export { default as deepMap } from "./operations/deepMap.js";
|
|
@@ -21,6 +22,7 @@ export { default as delete } from "./operations/delete.js";
|
|
|
21
22
|
export { default as entries } from "./operations/entries.js";
|
|
22
23
|
export { default as filter } from "./operations/filter.js";
|
|
23
24
|
export { default as first } from "./operations/first.js";
|
|
25
|
+
export { default as flat } from "./operations/flat.js";
|
|
24
26
|
export { default as forEach } from "./operations/forEach.js";
|
|
25
27
|
export { default as from } from "./operations/from.js";
|
|
26
28
|
export { default as globKeys } from "./operations/globKeys.js";
|
package/src/drivers/AsyncMap.js
CHANGED
|
@@ -106,6 +106,49 @@ export default class AsyncMap {
|
|
|
106
106
|
throw new Error("get() not implemented");
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
+
/**
|
|
110
|
+
* Returns the value associated with the key, or defaultValue if there is
|
|
111
|
+
* none. If defaultValue is returned, it is also inserted into the map for the
|
|
112
|
+
* given key. If the `readOnly` property is true, calling this method throws a
|
|
113
|
+
* `TypeError` if the key is not already present in the map.
|
|
114
|
+
*/
|
|
115
|
+
async getOrInsert(key, defaultValue) {
|
|
116
|
+
let value = await this.get(key);
|
|
117
|
+
if (value === undefined) {
|
|
118
|
+
if (this.readOnly) {
|
|
119
|
+
throw new TypeError(
|
|
120
|
+
"getOrInsert() can't insert into a new value into a read-only map.",
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
await this.set(key, defaultValue);
|
|
124
|
+
value = defaultValue;
|
|
125
|
+
}
|
|
126
|
+
return value;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Returns the value associated with the key, or the result of calling
|
|
131
|
+
* `defaultValueFn` if there is none. If the `readOnly` property is true,
|
|
132
|
+
* calling this method throws a `TypeError` if the key is not already present
|
|
133
|
+
* in the map.
|
|
134
|
+
*
|
|
135
|
+
* @param {any} key
|
|
136
|
+
* @param {() => any} defaultValueFn
|
|
137
|
+
*/
|
|
138
|
+
async getOrInsertComputed(key, defaultValueFn) {
|
|
139
|
+
let value = await this.get(key);
|
|
140
|
+
if (value === undefined) {
|
|
141
|
+
if (this.readOnly) {
|
|
142
|
+
throw new TypeError(
|
|
143
|
+
"getOrInsertComputed() can't insert into a new value into a read-only map.",
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
const defaultValue = await defaultValueFn();
|
|
147
|
+
await this.set(key, defaultValue);
|
|
148
|
+
value = defaultValue;
|
|
149
|
+
}
|
|
150
|
+
return value;
|
|
151
|
+
}
|
|
109
152
|
/**
|
|
110
153
|
* Groups items from an async iterable into an AsyncMap according to the keys
|
|
111
154
|
* returned by the given function.
|
package/src/drivers/SyncMap.js
CHANGED
|
@@ -139,6 +139,50 @@ export default class SyncMap extends Map {
|
|
|
139
139
|
return value;
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
+
/**
|
|
143
|
+
* Returns the value associated with the key, or defaultValue if there is
|
|
144
|
+
* none. If defaultValue is returned, it is also inserted into the map for the
|
|
145
|
+
* given key. If the `readOnly` property is true, calling this method throws a
|
|
146
|
+
* `TypeError` if the key is not already present in the map.
|
|
147
|
+
*/
|
|
148
|
+
getOrInsert(key, defaultValue) {
|
|
149
|
+
let value = this.get(key);
|
|
150
|
+
if (value === undefined) {
|
|
151
|
+
if (this.readOnly) {
|
|
152
|
+
throw new TypeError(
|
|
153
|
+
"getOrInsert() can't insert into a new value into a read-only map.",
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
this.set(key, defaultValue);
|
|
157
|
+
value = defaultValue;
|
|
158
|
+
}
|
|
159
|
+
return value;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Returns the value associated with the key, or the result of calling
|
|
164
|
+
* `defaultValueFn` if there is none. If the `readOnly` property is true,
|
|
165
|
+
* calling this method throws a `TypeError` if the key is not already present
|
|
166
|
+
* in the map.
|
|
167
|
+
*
|
|
168
|
+
* @param {any} key
|
|
169
|
+
* @param {() => any} defaultValueFn
|
|
170
|
+
*/
|
|
171
|
+
getOrInsertComputed(key, defaultValueFn) {
|
|
172
|
+
let value = this.get(key);
|
|
173
|
+
if (value === undefined) {
|
|
174
|
+
if (this.readOnly) {
|
|
175
|
+
throw new TypeError(
|
|
176
|
+
"getOrInsertComputed() can't insert into a new value into a read-only map.",
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
const defaultValue = defaultValueFn();
|
|
180
|
+
this.set(key, defaultValue);
|
|
181
|
+
value = defaultValue;
|
|
182
|
+
}
|
|
183
|
+
return value;
|
|
184
|
+
}
|
|
185
|
+
|
|
142
186
|
/**
|
|
143
187
|
* Returns true if the given key appears in the set returned by keys().
|
|
144
188
|
*
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { isUnpackable, SyncMap, Tree } from "@weborigami/async-tree";
|
|
2
|
+
// import assignPropertyDescriptors from "./assignPropertyDescriptors.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Concatenate the given trees. This is similar to a merge, but numeric keys
|
|
6
|
+
* will be renumbered starting with 0 and incrementing by 1.
|
|
7
|
+
*
|
|
8
|
+
* @typedef {import("@weborigami/async-tree").Maplike} Maplike
|
|
9
|
+
*
|
|
10
|
+
* @param {(Maplike|null)[]} trees
|
|
11
|
+
*/
|
|
12
|
+
export default async function concat(...trees) {
|
|
13
|
+
// Filter out null or undefined trees.
|
|
14
|
+
/** @type {Maplike[]}
|
|
15
|
+
* @ts-ignore */
|
|
16
|
+
const filtered = trees.filter((tree) => tree);
|
|
17
|
+
|
|
18
|
+
// Unpack any packed objects.
|
|
19
|
+
const sources = await Promise.all(
|
|
20
|
+
filtered.map((obj) =>
|
|
21
|
+
isUnpackable(obj) ? /** @type {any} */ (obj).unpack() : obj,
|
|
22
|
+
),
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
if (sources.length === 0) {
|
|
26
|
+
throw new TypeError("Tree.concat: all arguments are null or undefined");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let index = 0;
|
|
30
|
+
let onlyNumericKeys = true;
|
|
31
|
+
const map = new SyncMap();
|
|
32
|
+
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
|
+
// Numeric key, renumber it.
|
|
38
|
+
key = String(index);
|
|
39
|
+
index++;
|
|
40
|
+
} else {
|
|
41
|
+
// Non-numeric key, keep it as is.
|
|
42
|
+
key = entryKey;
|
|
43
|
+
onlyNumericKeys = false;
|
|
44
|
+
}
|
|
45
|
+
map.set(key, entryValue);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (onlyNumericKeys) {
|
|
50
|
+
// All keys are numeric, return an array.
|
|
51
|
+
return [...map.values()];
|
|
52
|
+
} else {
|
|
53
|
+
// Some keys are non-numeric, return a map.
|
|
54
|
+
return map;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as args from "../utilities/args.js";
|
|
2
|
+
import isUnpackable from "../utilities/isUnpackable.js";
|
|
2
3
|
import isMap from "./isMap.js";
|
|
3
4
|
import isMaplike from "./isMaplike.js";
|
|
4
5
|
|
|
@@ -6,29 +7,38 @@ import isMaplike from "./isMaplike.js";
|
|
|
6
7
|
* Return an iterator that yields all values in a tree, including nested trees.
|
|
7
8
|
*
|
|
8
9
|
* If the `expand` option is true, maplike values (but not functions) will be
|
|
9
|
-
* expanded into nested trees and their values will be yielded.
|
|
10
|
+
* expanded into nested trees and their values will be yielded. Packed values
|
|
11
|
+
* will be unpacked before expanding.
|
|
12
|
+
*
|
|
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.
|
|
10
15
|
*
|
|
11
16
|
* @param {import("../../index.ts").Maplike} maplike
|
|
12
|
-
* @param {{ expand?: boolean }} [options]
|
|
17
|
+
* @param {{ depth?: number, expand?: boolean }} [options]
|
|
13
18
|
* @returns {AsyncGenerator<any, void, undefined>}
|
|
14
19
|
*/
|
|
15
|
-
export default async function* deepValuesIterator(
|
|
16
|
-
maplike,
|
|
17
|
-
options = { expand: false },
|
|
18
|
-
) {
|
|
20
|
+
export default async function* deepValuesIterator(maplike, options = {}) {
|
|
19
21
|
const tree = await args.map(maplike, "Tree.deepValuesIterator", {
|
|
20
|
-
deep:
|
|
22
|
+
deep: !options.expand,
|
|
21
23
|
});
|
|
22
24
|
|
|
25
|
+
const depth = options.depth ?? Infinity;
|
|
26
|
+
const expand = options.expand ?? false;
|
|
27
|
+
|
|
23
28
|
for await (const key of tree.keys()) {
|
|
24
|
-
|
|
29
|
+
let value = await tree.get(key);
|
|
30
|
+
|
|
31
|
+
if (expand && isUnpackable(value)) {
|
|
32
|
+
value = await value.unpack();
|
|
33
|
+
}
|
|
25
34
|
|
|
26
35
|
// Recurse into child trees, but don't expand functions.
|
|
27
36
|
const recurse =
|
|
28
|
-
|
|
29
|
-
(
|
|
37
|
+
depth > 1 &&
|
|
38
|
+
(isMap(value) ||
|
|
39
|
+
(expand && typeof value !== "function" && isMaplike(value)));
|
|
30
40
|
if (recurse) {
|
|
31
|
-
yield* deepValuesIterator(value,
|
|
41
|
+
yield* deepValuesIterator(value, { depth: depth - 1, expand });
|
|
32
42
|
} else {
|
|
33
43
|
yield value;
|
|
34
44
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as args from "../utilities/args.js";
|
|
2
|
+
import deepValuesIterator from "./deepValuesIterator.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Flatten the values in the tree to the given depth.
|
|
6
|
+
*
|
|
7
|
+
* @typedef {import("../../index.ts").Maplike} Maplike
|
|
8
|
+
*
|
|
9
|
+
* @param {Maplike} maplike
|
|
10
|
+
* @param {number} [depth] The maximum depth to flatten
|
|
11
|
+
*/
|
|
12
|
+
export default async function flat(maplike, depth = 1) {
|
|
13
|
+
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);
|
|
19
|
+
}
|
|
20
|
+
return result;
|
|
21
|
+
}
|
package/src/operations/isMap.js
CHANGED
package/src/operations/merge.js
CHANGED
|
@@ -48,7 +48,7 @@ export default async function merge(...treelikes) {
|
|
|
48
48
|
const sources = unpacked.map((maplike) => from(maplike));
|
|
49
49
|
|
|
50
50
|
if (sources.length === 0) {
|
|
51
|
-
throw new TypeError("merge: all trees are null or undefined");
|
|
51
|
+
throw new TypeError("Tree.merge: all trees are null or undefined");
|
|
52
52
|
} else if (sources.length === 1) {
|
|
53
53
|
// Only one tree, no need to merge
|
|
54
54
|
return sources[0];
|
|
@@ -19,7 +19,10 @@ export default function getParent(packed, options = {}) {
|
|
|
19
19
|
|
|
20
20
|
// If the packed object has a `parent` property, use that. Exception: Node
|
|
21
21
|
// Buffer objects have a `parent` property that we ignore.
|
|
22
|
-
if (
|
|
22
|
+
if (
|
|
23
|
+
packed.parent &&
|
|
24
|
+
!(typeof Buffer !== "undefined" && packed instanceof Buffer)
|
|
25
|
+
) {
|
|
23
26
|
return packed.parent;
|
|
24
27
|
}
|
|
25
28
|
|
|
@@ -44,6 +44,55 @@ describe("AsyncMap", () => {
|
|
|
44
44
|
]);
|
|
45
45
|
});
|
|
46
46
|
|
|
47
|
+
test("getOrInsert", async () => {
|
|
48
|
+
const map = new SampleAsyncMap([
|
|
49
|
+
["a", 1],
|
|
50
|
+
["b", 2],
|
|
51
|
+
]);
|
|
52
|
+
assert.strictEqual(await map.getOrInsert("a", 100), 1);
|
|
53
|
+
assert.strictEqual(await map.getOrInsert("c", 3), 3);
|
|
54
|
+
assert.strictEqual(await map.get("c"), 3);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("getOrInsert on read-only map throws if key doesn't exist", async () => {
|
|
58
|
+
class Fixture extends AsyncMap {
|
|
59
|
+
constructor(entries) {
|
|
60
|
+
super();
|
|
61
|
+
this.map = new Map(entries);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async get(key) {
|
|
65
|
+
let value = this.map.get(key);
|
|
66
|
+
if (value instanceof Array) {
|
|
67
|
+
value = Reflect.construct(this.constructor, [value]);
|
|
68
|
+
}
|
|
69
|
+
return value;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const map = new Fixture([
|
|
74
|
+
["a", 1],
|
|
75
|
+
["b", 2],
|
|
76
|
+
]);
|
|
77
|
+
|
|
78
|
+
assert.strictEqual(await map.getOrInsert("a", 100), 1);
|
|
79
|
+
await assert.rejects(async () => await map.getOrInsert("c", 3), {
|
|
80
|
+
name: "TypeError",
|
|
81
|
+
message:
|
|
82
|
+
"getOrInsert() can't insert into a new value into a read-only map.",
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("getOrInsertComputed", async () => {
|
|
87
|
+
const map = new SampleAsyncMap([
|
|
88
|
+
["a", 1],
|
|
89
|
+
["b", 2],
|
|
90
|
+
]);
|
|
91
|
+
assert.strictEqual(await map.getOrInsertComputed("a", async () => 100), 1);
|
|
92
|
+
assert.strictEqual(await map.getOrInsertComputed("c", async () => 3), 3);
|
|
93
|
+
assert.strictEqual(await map.get("c"), 3);
|
|
94
|
+
});
|
|
95
|
+
|
|
47
96
|
test("static groupBy", async () => {
|
|
48
97
|
const items = [
|
|
49
98
|
{ name: "apple", type: "fruit" },
|
|
@@ -52,7 +101,7 @@ describe("AsyncMap", () => {
|
|
|
52
101
|
];
|
|
53
102
|
const map = await AsyncMap.groupBy(
|
|
54
103
|
items,
|
|
55
|
-
async (element, index) => element.type
|
|
104
|
+
async (element, index) => element.type,
|
|
56
105
|
);
|
|
57
106
|
assert.deepStrictEqual(Array.from(map.entries()), [
|
|
58
107
|
[
|
|
@@ -133,6 +133,50 @@ describe("SyncMap", () => {
|
|
|
133
133
|
assert.strictEqual(map.get("c"), undefined);
|
|
134
134
|
});
|
|
135
135
|
|
|
136
|
+
test("getOrInsert", () => {
|
|
137
|
+
const map = new SyncMap([
|
|
138
|
+
["a", 1],
|
|
139
|
+
["b", 2],
|
|
140
|
+
]);
|
|
141
|
+
assert.strictEqual(map.getOrInsert("a", 100), 1);
|
|
142
|
+
assert.strictEqual(map.getOrInsert("c", 3), 3);
|
|
143
|
+
assert.strictEqual(map.get("c"), 3);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("getOrInsert on read-only map throws if key doesn't exist", () => {
|
|
147
|
+
class Fixture extends SyncMap {
|
|
148
|
+
get(key) {
|
|
149
|
+
return super.get(key);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
const map = new Fixture([
|
|
153
|
+
["a", 1],
|
|
154
|
+
["b", 2],
|
|
155
|
+
]);
|
|
156
|
+
assert.strictEqual(map.getOrInsert("a", 100), 1);
|
|
157
|
+
assert.throws(() => map.getOrInsert("c", 3), {
|
|
158
|
+
name: "TypeError",
|
|
159
|
+
message:
|
|
160
|
+
"getOrInsert() can't insert into a new value into a read-only map.",
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test("getOrInsertComputed", () => {
|
|
165
|
+
const map = new SyncMap([
|
|
166
|
+
["a", 1],
|
|
167
|
+
["b", 2],
|
|
168
|
+
]);
|
|
169
|
+
assert.strictEqual(
|
|
170
|
+
map.getOrInsertComputed("a", () => 100),
|
|
171
|
+
1,
|
|
172
|
+
);
|
|
173
|
+
assert.strictEqual(
|
|
174
|
+
map.getOrInsertComputed("c", () => 3),
|
|
175
|
+
3,
|
|
176
|
+
);
|
|
177
|
+
assert.strictEqual(map.get("c"), 3);
|
|
178
|
+
});
|
|
179
|
+
|
|
136
180
|
test("has returns true if key exists in keys()", () => {
|
|
137
181
|
const map = new SyncMap();
|
|
138
182
|
map.keys = () => {
|
|
@@ -321,7 +365,7 @@ describe("SyncMap", () => {
|
|
|
321
365
|
]);
|
|
322
366
|
// @ts-ignore
|
|
323
367
|
const grouped = Map.groupBy(map, ([key, value]) =>
|
|
324
|
-
value % 2 === 0 ? "even" : "odd"
|
|
368
|
+
value % 2 === 0 ? "even" : "odd",
|
|
325
369
|
);
|
|
326
370
|
assert(grouped instanceof Map);
|
|
327
371
|
assert.strictEqual(grouped.size, 2);
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import assert from "node:assert";
|
|
2
|
+
import { describe, test } from "node:test";
|
|
3
|
+
import ObjectMap from "../../src/drivers/ObjectMap.js";
|
|
4
|
+
import concat from "../../src/operations/concat.js";
|
|
5
|
+
|
|
6
|
+
describe("concat", () => {
|
|
7
|
+
test("concatenates arrays", async () => {
|
|
8
|
+
const result = await concat(["a", "b"], ["c", "d"]);
|
|
9
|
+
assert.deepEqual(result, ["a", "b", "c", "d"]);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test("concatenates maplike objects", async () => {
|
|
13
|
+
const result = await concat(
|
|
14
|
+
{
|
|
15
|
+
1: "a",
|
|
16
|
+
2: "b",
|
|
17
|
+
},
|
|
18
|
+
new ObjectMap({
|
|
19
|
+
0: "c",
|
|
20
|
+
1: "d",
|
|
21
|
+
}),
|
|
22
|
+
["e", "f"],
|
|
23
|
+
);
|
|
24
|
+
assert.deepEqual(result, ["a", "b", "c", "d", "e", "f"]);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("copes with mixture of numeric and non-numeric keys", async () => {
|
|
28
|
+
const result = await concat(
|
|
29
|
+
["a", "b"],
|
|
30
|
+
{
|
|
31
|
+
x: 1,
|
|
32
|
+
y: 2,
|
|
33
|
+
},
|
|
34
|
+
["c", "d"],
|
|
35
|
+
);
|
|
36
|
+
assert.deepEqual(
|
|
37
|
+
[...result.entries()],
|
|
38
|
+
[
|
|
39
|
+
["0", "a"],
|
|
40
|
+
["1", "b"],
|
|
41
|
+
["x", 1],
|
|
42
|
+
["y", 2],
|
|
43
|
+
["2", "c"],
|
|
44
|
+
["3", "d"],
|
|
45
|
+
],
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("can unpack arguments", async () => {
|
|
50
|
+
/** @type {any} */
|
|
51
|
+
const packed1 = new String("Packed array");
|
|
52
|
+
packed1.unpack = async function () {
|
|
53
|
+
return ["a", "b"];
|
|
54
|
+
};
|
|
55
|
+
/** @type {any} */
|
|
56
|
+
const packed2 = new String("Packed object");
|
|
57
|
+
packed2.unpack = async function () {
|
|
58
|
+
return {
|
|
59
|
+
0: "c",
|
|
60
|
+
1: "d",
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
const result = await concat(packed1, packed2);
|
|
64
|
+
assert.deepEqual(result, ["a", "b", "c", "d"]);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
@@ -20,4 +20,46 @@ describe("deepValuesIterator", () => {
|
|
|
20
20
|
}
|
|
21
21
|
assert.deepEqual(values, [1, 2, 3, 4]);
|
|
22
22
|
});
|
|
23
|
+
|
|
24
|
+
test("if depth is specified, only descends to specified depth", async () => {
|
|
25
|
+
const tree = new ObjectMap({
|
|
26
|
+
a: 1,
|
|
27
|
+
sub: {
|
|
28
|
+
b: 2,
|
|
29
|
+
more: {
|
|
30
|
+
c: 3,
|
|
31
|
+
deeper: {
|
|
32
|
+
d: 4,
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
const values = [];
|
|
38
|
+
for await (const value of deepValuesIterator(tree, {
|
|
39
|
+
depth: 3,
|
|
40
|
+
expand: true,
|
|
41
|
+
})) {
|
|
42
|
+
values.push(value);
|
|
43
|
+
}
|
|
44
|
+
assert.deepEqual(values, [1, 2, 3, { d: 4 }]);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("can optionally unpack a packed value", async () => {
|
|
48
|
+
/** @type {any} */
|
|
49
|
+
const packed = new String("String that unpacks to data");
|
|
50
|
+
packed.unpack = async function () {
|
|
51
|
+
return {
|
|
52
|
+
message: "Hello",
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
const tree = {
|
|
56
|
+
a: 1,
|
|
57
|
+
packed,
|
|
58
|
+
};
|
|
59
|
+
const values = [];
|
|
60
|
+
for await (const value of deepValuesIterator(tree, { expand: true })) {
|
|
61
|
+
values.push(value);
|
|
62
|
+
}
|
|
63
|
+
assert.deepEqual(values, [1, "Hello"]);
|
|
64
|
+
});
|
|
23
65
|
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import assert from "node:assert";
|
|
2
|
+
import { describe, test } from "node:test";
|
|
3
|
+
import ObjectMap from "../../src/drivers/ObjectMap.js";
|
|
4
|
+
import flat from "../../src/operations/flat.js";
|
|
5
|
+
import plain from "../../src/operations/plain.js";
|
|
6
|
+
|
|
7
|
+
describe("flat", () => {
|
|
8
|
+
test("flattens the tree's values into an array", async () => {
|
|
9
|
+
const fixture = {
|
|
10
|
+
a: 1,
|
|
11
|
+
sub: {
|
|
12
|
+
b: 2,
|
|
13
|
+
more: {
|
|
14
|
+
c: 3,
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
assert.deepEqual(await flat(fixture, Infinity), [1, 2, 3]);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("flattens one level by default", async () => {
|
|
22
|
+
const fixture = {
|
|
23
|
+
a: 1,
|
|
24
|
+
sub: {
|
|
25
|
+
b: 2,
|
|
26
|
+
more: {
|
|
27
|
+
c: 3,
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
};
|
|
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]);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("flattens maplike objects", async () => {
|
|
42
|
+
const result = await flat(
|
|
43
|
+
[
|
|
44
|
+
{
|
|
45
|
+
a: 1,
|
|
46
|
+
b: 2,
|
|
47
|
+
},
|
|
48
|
+
new ObjectMap({
|
|
49
|
+
c: 3,
|
|
50
|
+
d: 4,
|
|
51
|
+
}),
|
|
52
|
+
[5, 6],
|
|
53
|
+
],
|
|
54
|
+
Infinity,
|
|
55
|
+
);
|
|
56
|
+
assert.deepEqual(result, [1, 2, 3, 4, 5, 6]);
|
|
57
|
+
});
|
|
58
|
+
});
|