@weborigami/async-tree 0.5.8 → 0.6.1
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/browser.js +1 -1
- package/index.ts +43 -35
- package/main.js +1 -2
- package/package.json +4 -7
- package/shared.js +74 -12
- package/src/Tree.js +11 -2
- package/src/drivers/AsyncMap.js +237 -0
- package/src/drivers/{BrowserFileTree.js → BrowserFileMap.js} +54 -38
- package/src/drivers/{calendarTree.js → CalendarMap.js} +80 -63
- package/src/drivers/ConstantMap.js +28 -0
- package/src/drivers/{ExplorableSiteTree.js → ExplorableSiteMap.js} +7 -7
- package/src/drivers/FileMap.js +238 -0
- package/src/drivers/{FunctionTree.js → FunctionMap.js} +19 -22
- package/src/drivers/ObjectMap.js +151 -0
- package/src/drivers/SetMap.js +13 -0
- package/src/drivers/{SiteTree.js → SiteMap.js} +17 -20
- package/src/drivers/SyncMap.js +260 -0
- package/src/jsonKeys.d.ts +2 -2
- package/src/jsonKeys.js +20 -5
- package/src/operations/addNextPrevious.js +35 -36
- package/src/operations/assign.js +27 -23
- package/src/operations/cache.js +30 -36
- package/src/operations/cachedKeyFunctions.js +1 -1
- package/src/operations/calendar.js +5 -0
- package/src/operations/child.js +35 -0
- package/src/operations/clear.js +13 -12
- package/src/operations/constant.js +5 -0
- package/src/operations/deepEntries.js +23 -0
- package/src/operations/deepMap.js +9 -9
- package/src/operations/deepMerge.js +36 -25
- package/src/operations/deepReverse.js +23 -16
- package/src/operations/deepTake.js +7 -7
- package/src/operations/deepText.js +4 -4
- package/src/operations/deepValues.js +3 -6
- package/src/operations/deepValuesIterator.js +11 -11
- package/src/operations/delete.js +8 -12
- package/src/operations/entries.js +17 -10
- package/src/operations/filter.js +9 -7
- package/src/operations/first.js +12 -10
- package/src/operations/forEach.js +10 -13
- package/src/operations/from.js +30 -39
- package/src/operations/globKeys.js +22 -17
- package/src/operations/group.js +2 -2
- package/src/operations/groupBy.js +24 -22
- package/src/operations/has.js +7 -9
- package/src/operations/indent.js +2 -2
- package/src/operations/inners.js +19 -15
- package/src/operations/invokeFunctions.js +22 -10
- package/src/operations/isAsyncMutableTree.js +5 -12
- package/src/operations/isAsyncTree.js +5 -20
- package/src/operations/isMap.js +39 -0
- package/src/operations/isMaplike.js +34 -0
- package/src/operations/isReadOnlyMap.js +14 -0
- package/src/operations/isTraversable.js +3 -3
- package/src/operations/isTreelike.js +5 -30
- package/src/operations/json.js +4 -12
- package/src/operations/keys.js +17 -8
- package/src/operations/length.js +9 -8
- package/src/operations/map.js +28 -30
- package/src/operations/mapExtension.js +20 -16
- package/src/operations/mapReduce.js +38 -24
- package/src/operations/mask.js +54 -29
- package/src/operations/match.js +13 -9
- package/src/operations/merge.js +43 -35
- package/src/operations/paginate.js +26 -18
- package/src/operations/parent.js +7 -7
- package/src/operations/paths.js +20 -22
- package/src/operations/plain.js +6 -6
- package/src/operations/reduce.js +16 -0
- package/src/operations/regExpKeys.js +21 -12
- package/src/operations/reverse.js +21 -15
- package/src/operations/root.js +6 -5
- package/src/operations/scope.js +31 -26
- package/src/operations/set.js +20 -0
- package/src/operations/shuffle.js +23 -16
- package/src/operations/size.js +13 -0
- package/src/operations/sort.js +55 -40
- package/src/operations/sync.js +14 -0
- package/src/operations/take.js +23 -11
- package/src/operations/text.js +4 -4
- package/src/operations/toFunction.js +7 -7
- package/src/operations/traverse.js +4 -4
- package/src/operations/traverseOrThrow.js +18 -9
- package/src/operations/traversePath.js +2 -2
- package/src/operations/values.js +18 -9
- package/src/operations/withKeys.js +22 -16
- package/src/symbols.js +1 -0
- package/src/utilities/castArraylike.js +24 -13
- package/src/utilities/getMapArgument.js +38 -0
- package/src/utilities/getParent.js +2 -2
- package/src/utilities/isStringlike.js +7 -5
- package/src/utilities/setParent.js +7 -7
- package/src/utilities/toFunction.js +2 -2
- package/src/utilities/toPlainValue.js +21 -19
- package/test/SampleAsyncMap.js +34 -0
- package/test/browser/assert.js +20 -0
- package/test/browser/index.html +53 -21
- package/test/drivers/AsyncMap.test.js +119 -0
- package/test/drivers/{BrowserFileTree.test.js → BrowserFileMap.test.js} +50 -33
- package/test/drivers/{calendarTree.test.js → CalendarMap.test.js} +17 -19
- package/test/drivers/ConstantMap.test.js +15 -0
- package/test/drivers/{ExplorableSiteTree.test.js → ExplorableSiteMap.test.js} +29 -14
- package/test/drivers/FileMap.test.js +156 -0
- package/test/drivers/FunctionMap.test.js +56 -0
- package/test/drivers/ObjectMap.test.js +194 -0
- package/test/drivers/SetMap.test.js +35 -0
- package/test/drivers/{SiteTree.test.js → SiteMap.test.js} +14 -10
- package/test/drivers/SyncMap.test.js +335 -0
- package/test/jsonKeys.test.js +18 -6
- package/test/operations/addNextPrevious.test.js +3 -2
- package/test/operations/assign.test.js +30 -35
- package/test/operations/cache.test.js +17 -12
- package/test/operations/cachedKeyFunctions.test.js +12 -9
- package/test/operations/child.test.js +34 -0
- package/test/operations/clear.test.js +6 -27
- package/test/operations/deepEntries.test.js +32 -0
- package/test/operations/deepMerge.test.js +23 -16
- package/test/operations/deepReverse.test.js +2 -2
- package/test/operations/deepTake.test.js +2 -2
- package/test/operations/deepText.test.js +4 -4
- package/test/operations/deepValuesIterator.test.js +2 -2
- package/test/operations/delete.test.js +2 -2
- package/test/operations/extensionKeyFunctions.test.js +6 -5
- package/test/operations/filter.test.js +3 -3
- package/test/operations/from.test.js +25 -31
- package/test/operations/globKeys.test.js +9 -9
- package/test/operations/groupBy.test.js +6 -5
- package/test/operations/inners.test.js +17 -14
- package/test/operations/invokeFunctions.test.js +2 -2
- package/test/operations/isMap.test.js +15 -0
- package/test/operations/isMaplike.test.js +15 -0
- package/test/operations/json.test.js +2 -2
- package/test/operations/keys.test.js +16 -3
- package/test/operations/map.test.js +40 -30
- package/test/operations/mapExtension.test.js +6 -6
- package/test/operations/mapReduce.test.js +14 -12
- package/test/operations/mask.test.js +16 -3
- package/test/operations/match.test.js +2 -2
- package/test/operations/merge.test.js +20 -14
- package/test/operations/paginate.test.js +5 -5
- package/test/operations/parent.test.js +3 -3
- package/test/operations/paths.test.js +20 -27
- package/test/operations/plain.test.js +8 -8
- package/test/operations/regExpKeys.test.js +22 -18
- package/test/operations/reverse.test.js +4 -3
- package/test/operations/scope.test.js +6 -5
- package/test/operations/set.test.js +11 -0
- package/test/operations/shuffle.test.js +3 -2
- package/test/operations/sort.test.js +7 -10
- package/test/operations/sync.test.js +43 -0
- package/test/operations/take.test.js +2 -2
- package/test/operations/toFunction.test.js +2 -2
- package/test/operations/traverse.test.js +17 -5
- package/test/operations/withKeys.test.js +2 -2
- package/test/utilities/castArrayLike.test.js +53 -0
- package/test/utilities/setParent.test.js +6 -6
- package/test/utilities/toFunction.test.js +2 -2
- package/test/utilities/toPlainValue.test.js +51 -12
- package/src/drivers/DeepMapTree.js +0 -23
- package/src/drivers/DeepObjectTree.js +0 -18
- package/src/drivers/DeferredTree.js +0 -81
- package/src/drivers/FileTree.js +0 -276
- package/src/drivers/MapTree.js +0 -70
- package/src/drivers/ObjectTree.js +0 -158
- package/src/drivers/SetTree.js +0 -34
- package/src/drivers/constantTree.js +0 -19
- package/src/drivers/limitConcurrency.js +0 -63
- package/src/internal.js +0 -16
- package/src/utilities/getTreeArgument.js +0 -43
- package/test/drivers/DeepMapTree.test.js +0 -17
- package/test/drivers/DeepObjectTree.test.js +0 -35
- package/test/drivers/DeferredTree.test.js +0 -22
- package/test/drivers/FileTree.test.js +0 -192
- package/test/drivers/FunctionTree.test.js +0 -46
- package/test/drivers/MapTree.test.js +0 -59
- package/test/drivers/ObjectTree.test.js +0 -163
- package/test/drivers/SetTree.test.js +0 -44
- package/test/drivers/constantTree.test.js +0 -13
- package/test/drivers/limitConcurrency.test.js +0 -41
- package/test/operations/isAsyncMutableTree.test.js +0 -17
- package/test/operations/isAsyncTree.test.js +0 -26
- package/test/operations/isTreelike.test.js +0 -13
|
@@ -1,33 +1,40 @@
|
|
|
1
|
-
import
|
|
1
|
+
import AsyncMap from "../drivers/AsyncMap.js";
|
|
2
|
+
import getMapArgument from "../utilities/getMapArgument.js";
|
|
3
|
+
import keys from "./keys.js";
|
|
2
4
|
|
|
3
5
|
/**
|
|
4
6
|
* Return a new tree with the original's keys shuffled
|
|
5
7
|
*
|
|
6
|
-
* @typedef
|
|
7
|
-
* @typedef {import("../../index.ts").Treelike} Treelike
|
|
8
|
+
* @typedef {import("../../index.ts").Maplike} Maplike
|
|
8
9
|
*
|
|
9
|
-
* @param {
|
|
10
|
+
* @param {Maplike} maplike
|
|
10
11
|
* @param {boolean?} reshuffle
|
|
11
|
-
* @returns {Promise<
|
|
12
|
+
* @returns {Promise<AsyncMap>}
|
|
12
13
|
*/
|
|
13
|
-
export default async function shuffle(
|
|
14
|
-
const
|
|
14
|
+
export default async function shuffle(maplike, reshuffle = false) {
|
|
15
|
+
const source = await getMapArgument(maplike, "shuffle");
|
|
15
16
|
|
|
16
|
-
let
|
|
17
|
+
let mapKeys;
|
|
18
|
+
|
|
19
|
+
return Object.assign(new AsyncMap(), {
|
|
20
|
+
description: "shuffle",
|
|
17
21
|
|
|
18
|
-
return {
|
|
19
22
|
async get(key) {
|
|
20
|
-
return
|
|
23
|
+
return source.get(key);
|
|
21
24
|
},
|
|
22
25
|
|
|
23
|
-
async keys() {
|
|
24
|
-
if (!
|
|
25
|
-
|
|
26
|
-
shuffleArray(
|
|
26
|
+
async *keys() {
|
|
27
|
+
if (!mapKeys || reshuffle) {
|
|
28
|
+
mapKeys = await keys(source);
|
|
29
|
+
shuffleArray(mapKeys);
|
|
27
30
|
}
|
|
28
|
-
|
|
31
|
+
yield* mapKeys;
|
|
29
32
|
},
|
|
30
|
-
|
|
33
|
+
|
|
34
|
+
source,
|
|
35
|
+
|
|
36
|
+
trailingSlashKeys: /** @type {any} */ (source).trailingSlashKeys,
|
|
37
|
+
});
|
|
31
38
|
}
|
|
32
39
|
|
|
33
40
|
/*
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import getMapArgument from "../utilities/getMapArgument.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Return the number of keys in the map.
|
|
5
|
+
*
|
|
6
|
+
* @typedef {import("../../index.ts").Maplike} Maplike
|
|
7
|
+
*
|
|
8
|
+
* @param {Maplike} maplike
|
|
9
|
+
*/
|
|
10
|
+
export default async function size(maplike) {
|
|
11
|
+
const map = await getMapArgument(maplike, "size");
|
|
12
|
+
return map.size;
|
|
13
|
+
}
|
package/src/operations/sort.js
CHANGED
|
@@ -1,21 +1,23 @@
|
|
|
1
|
-
import
|
|
1
|
+
import AsyncMap from "../drivers/AsyncMap.js";
|
|
2
|
+
import getMapArgument from "../utilities/getMapArgument.js";
|
|
3
|
+
import keys from "./keys.js";
|
|
2
4
|
|
|
3
5
|
/**
|
|
4
|
-
* Return a new
|
|
6
|
+
* Return a new map with the original's keys sorted. A comparison function can
|
|
5
7
|
* be provided; by default the keys will be sorted in [natural sort
|
|
6
8
|
* order](https://en.wikipedia.org/wiki/Natural_sort_order).
|
|
7
9
|
*
|
|
8
|
-
* @typedef {import("
|
|
9
|
-
* @typedef {(key: any,
|
|
10
|
+
* @typedef {import("../../index.ts").SyncOrAsyncMap} SyncOrAsyncMap
|
|
11
|
+
* @typedef {(key: any, map: SyncOrAsyncMap) => any} SortKeyFn
|
|
10
12
|
* @typedef {{ compare?: (a: any, b: any) => number, sortKey?: SortKeyFn }} SortOptions
|
|
11
|
-
* @typedef {import("../../index.ts").
|
|
13
|
+
* @typedef {import("../../index.ts").Maplike} Maplike
|
|
12
14
|
* @typedef {import("../../index.ts").ValueKeyFn} ValueKeyFn
|
|
13
15
|
*
|
|
14
|
-
* @param {
|
|
16
|
+
* @param {Maplike} maplike
|
|
15
17
|
* @param {SortOptions|ValueKeyFn} [options]
|
|
16
18
|
*/
|
|
17
|
-
export default async function sort(
|
|
18
|
-
const
|
|
19
|
+
export default async function sort(maplike, options) {
|
|
20
|
+
const source = await getMapArgument(maplike, "sort");
|
|
19
21
|
|
|
20
22
|
let sortKey;
|
|
21
23
|
let compare;
|
|
@@ -27,39 +29,52 @@ export default async function sort(treelike, options) {
|
|
|
27
29
|
sortKey = options?.sortKey;
|
|
28
30
|
}
|
|
29
31
|
|
|
30
|
-
const transformed = Object.
|
|
31
|
-
|
|
32
|
-
const keys = Array.from(await tree.keys());
|
|
32
|
+
const transformed = Object.assign(new AsyncMap(), {
|
|
33
|
+
descriptor: "sort",
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
35
|
+
async get(key) {
|
|
36
|
+
return source.get(key);
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
async *keys() {
|
|
40
|
+
const treeKeys = await keys(source);
|
|
41
|
+
|
|
42
|
+
let resultKeys;
|
|
43
|
+
if (sortKey) {
|
|
44
|
+
// Invoke the async sortKey function to get sort keys.
|
|
45
|
+
// Create { key, sortKey } tuples.
|
|
46
|
+
const tuples = await Promise.all(
|
|
47
|
+
treeKeys.map(async (key) => {
|
|
48
|
+
const value = await source.get(key);
|
|
49
|
+
const sort = await sortKey(value, key, source);
|
|
50
|
+
if (sort === undefined) {
|
|
51
|
+
throw new Error(
|
|
52
|
+
`sortKey function returned undefined for key ${key}`
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
return { key, sort };
|
|
56
|
+
})
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
// Wrap the comparison function so it applies to sort keys.
|
|
60
|
+
const defaultCompare = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
|
|
61
|
+
const originalCompare = compare ?? defaultCompare;
|
|
62
|
+
// Sort by the sort key.
|
|
63
|
+
tuples.sort((a, b) => originalCompare(a.sort, b.sort));
|
|
64
|
+
// Map back to the original keys.
|
|
65
|
+
resultKeys = tuples.map((pair) => pair.key);
|
|
66
|
+
} else {
|
|
67
|
+
// Use original keys as sort keys.
|
|
68
|
+
// If compare is undefined, this uses default sort order.
|
|
69
|
+
resultKeys = treeKeys.slice().sort(compare);
|
|
70
|
+
}
|
|
71
|
+
yield* resultKeys;
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
source,
|
|
75
|
+
|
|
76
|
+
trailingSlashKeys: /** @type {any} */ (source).trailingSlashKeys,
|
|
77
|
+
});
|
|
49
78
|
|
|
50
|
-
// Wrap the comparison function so it applies to sort keys.
|
|
51
|
-
const defaultCompare = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
|
|
52
|
-
const originalCompare = compare ?? defaultCompare;
|
|
53
|
-
// Sort by the sort key.
|
|
54
|
-
tuples.sort((a, b) => originalCompare(a.sort, b.sort));
|
|
55
|
-
// Map back to the original keys.
|
|
56
|
-
const sorted = tuples.map((pair) => pair.key);
|
|
57
|
-
return sorted;
|
|
58
|
-
} else {
|
|
59
|
-
// Use original keys as sort keys.
|
|
60
|
-
// If compare is undefined, this uses default sort order.
|
|
61
|
-
return keys.slice().sort(compare);
|
|
62
|
-
}
|
|
63
|
-
};
|
|
64
79
|
return transformed;
|
|
65
80
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import getMapArgument from "../utilities/getMapArgument.js";
|
|
2
|
+
import reduce from "./reduce.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Resolve the async tree to a synchronous tree.
|
|
6
|
+
*
|
|
7
|
+
* @typedef {import("../../index.ts").Maplike} Maplike
|
|
8
|
+
*
|
|
9
|
+
* @param {Maplike} source
|
|
10
|
+
*/
|
|
11
|
+
export default async function sync(source) {
|
|
12
|
+
const tree = await getMapArgument(source, "sync");
|
|
13
|
+
return reduce(tree, (mapped) => mapped);
|
|
14
|
+
}
|
package/src/operations/take.js
CHANGED
|
@@ -1,22 +1,34 @@
|
|
|
1
|
-
import
|
|
1
|
+
import AsyncMap from "../drivers/AsyncMap.js";
|
|
2
|
+
import getMapArgument from "../utilities/getMapArgument.js";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
|
-
* Returns a new
|
|
5
|
+
* Returns a new map with the number of keys limited to the indicated count.
|
|
5
6
|
*
|
|
6
|
-
* @param {import("../../index.ts").
|
|
7
|
+
* @param {import("../../index.ts").Maplike} maplike
|
|
7
8
|
* @param {number} count
|
|
8
9
|
*/
|
|
9
|
-
export default async function take(
|
|
10
|
-
const
|
|
10
|
+
export default async function take(maplike, count) {
|
|
11
|
+
const source = await getMapArgument(maplike, "take");
|
|
12
|
+
return Object.assign(new AsyncMap(), {
|
|
13
|
+
description: `take ${count}`,
|
|
11
14
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
|
|
15
|
+
async *keys() {
|
|
16
|
+
let i = 0;
|
|
17
|
+
for await (const key of source.keys()) {
|
|
18
|
+
yield key;
|
|
19
|
+
i += 1;
|
|
20
|
+
if (i >= count) {
|
|
21
|
+
break;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
16
24
|
},
|
|
17
25
|
|
|
18
26
|
async get(key) {
|
|
19
|
-
return
|
|
27
|
+
return source.get(key);
|
|
20
28
|
},
|
|
21
|
-
|
|
29
|
+
|
|
30
|
+
source: source,
|
|
31
|
+
|
|
32
|
+
trailingSlashKeys: /** @type {any} */ (source).trailingSlashKeys,
|
|
33
|
+
});
|
|
22
34
|
}
|
package/src/operations/text.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import toString from "../utilities/toString.js";
|
|
2
2
|
import deepText from "./deepText.js";
|
|
3
|
-
import
|
|
3
|
+
import isMaplike from "./isMaplike.js";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* A tagged template literal function that concatenate the deep text values in
|
|
7
|
-
*
|
|
6
|
+
* A tagged template literal function that concatenate the deep text values in
|
|
7
|
+
* any trees using `deepText`.
|
|
8
8
|
*
|
|
9
9
|
* @param {TemplateStringsArray} strings
|
|
10
10
|
* @param {...any} values
|
|
@@ -13,7 +13,7 @@ export default async function text(strings, ...values) {
|
|
|
13
13
|
// Convert all the values to strings
|
|
14
14
|
const valueTexts = await Promise.all(
|
|
15
15
|
values.map((value) =>
|
|
16
|
-
|
|
16
|
+
isMaplike(value) ? deepText(value) : toString(value)
|
|
17
17
|
)
|
|
18
18
|
);
|
|
19
19
|
// Splice all the strings together
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import
|
|
1
|
+
import getMapArgument from "../utilities/getMapArgument.js";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Returns a function that invokes the
|
|
4
|
+
* Returns a function that invokes the map's `get` method.
|
|
5
5
|
*
|
|
6
|
-
* @typedef {import("../../index.ts").
|
|
6
|
+
* @typedef {import("../../index.ts").Maplike} Maplike
|
|
7
7
|
*
|
|
8
|
-
* @param {
|
|
8
|
+
* @param {Maplike} maplike
|
|
9
9
|
* @returns {Promise<Function>}
|
|
10
10
|
*/
|
|
11
|
-
export default async function toFunction(
|
|
12
|
-
const
|
|
13
|
-
return
|
|
11
|
+
export default async function toFunction(maplike) {
|
|
12
|
+
const map = await getMapArgument(maplike, "toFunction");
|
|
13
|
+
return map.get.bind(map);
|
|
14
14
|
}
|
|
@@ -4,16 +4,16 @@ import traverseOrThrow from "./traverseOrThrow.js";
|
|
|
4
4
|
/**
|
|
5
5
|
* Return the value at the corresponding path of keys.
|
|
6
6
|
*
|
|
7
|
-
* @typedef {import("../../index.ts").
|
|
7
|
+
* @typedef {import("../../index.ts").Maplike} Maplike
|
|
8
8
|
*
|
|
9
|
-
* @param {
|
|
9
|
+
* @param {Maplike} maplike
|
|
10
10
|
* @param {...any} keys
|
|
11
11
|
*/
|
|
12
|
-
export default async function traverse(
|
|
12
|
+
export default async function traverse(maplike, ...keys) {
|
|
13
13
|
try {
|
|
14
14
|
// Await the result here so that, if the path doesn't exist, the catch
|
|
15
15
|
// block below will catch the exception.
|
|
16
|
-
return await traverseOrThrow(
|
|
16
|
+
return await traverseOrThrow(maplike, ...keys);
|
|
17
17
|
} catch (/** @type {any} */ error) {
|
|
18
18
|
if (error instanceof TraverseError) {
|
|
19
19
|
return undefined;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as trailingSlash from "../trailingSlash.js";
|
|
1
2
|
import TraverseError from "../TraverseError.js";
|
|
2
3
|
import isUnpackable from "../utilities/isUnpackable.js";
|
|
3
4
|
import from from "./from.js";
|
|
@@ -6,16 +7,15 @@ import from from "./from.js";
|
|
|
6
7
|
* Return the value at the corresponding path of keys. Throw if any interior
|
|
7
8
|
* step of the path doesn't lead to a result.
|
|
8
9
|
*
|
|
9
|
-
* @typedef {import("../../index.ts").
|
|
10
|
-
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
10
|
+
* @typedef {import("../../index.ts").Maplike} Maplike
|
|
11
11
|
*
|
|
12
|
-
* @param {
|
|
12
|
+
* @param {Maplike} maplike
|
|
13
13
|
* @param {...any} keys
|
|
14
14
|
*/
|
|
15
|
-
export default async function traverseOrThrow(
|
|
15
|
+
export default async function traverseOrThrow(maplike, ...keys) {
|
|
16
16
|
// Start our traversal at the root of the tree.
|
|
17
17
|
/** @type {any} */
|
|
18
|
-
let value =
|
|
18
|
+
let value = maplike;
|
|
19
19
|
let position = 0;
|
|
20
20
|
|
|
21
21
|
// Process all the keys.
|
|
@@ -24,7 +24,7 @@ export default async function traverseOrThrow(treelike, ...keys) {
|
|
|
24
24
|
while (remainingKeys.length > 0) {
|
|
25
25
|
if (value == null) {
|
|
26
26
|
throw new TraverseError("A null or undefined value can't be traversed", {
|
|
27
|
-
tree:
|
|
27
|
+
tree: maplike,
|
|
28
28
|
keys,
|
|
29
29
|
position,
|
|
30
30
|
});
|
|
@@ -44,16 +44,25 @@ export default async function traverseOrThrow(treelike, ...keys) {
|
|
|
44
44
|
key = null;
|
|
45
45
|
value = await fn(...args);
|
|
46
46
|
} else {
|
|
47
|
-
// Cast value to a
|
|
48
|
-
const
|
|
47
|
+
// Cast value to a map.
|
|
48
|
+
const map = from(value);
|
|
49
49
|
// Get the next key.
|
|
50
50
|
key = remainingKeys.shift();
|
|
51
|
+
// Remove trailing slash if not supported
|
|
52
|
+
const normalized = /** @type {any} */ (map).trailingSlashKeys
|
|
53
|
+
? key
|
|
54
|
+
: trailingSlash.remove(key);
|
|
51
55
|
// Get the value for the key.
|
|
52
|
-
value = await
|
|
56
|
+
value = await map.get(normalized);
|
|
53
57
|
}
|
|
54
58
|
|
|
55
59
|
position++;
|
|
56
60
|
}
|
|
57
61
|
|
|
62
|
+
// If last key ended in a slash and value is unpackable, unpack it.
|
|
63
|
+
if (key && trailingSlash.has(key) && isUnpackable(value)) {
|
|
64
|
+
value = await value.unpack();
|
|
65
|
+
}
|
|
66
|
+
|
|
58
67
|
return value;
|
|
59
68
|
}
|
|
@@ -5,9 +5,9 @@ import traverse from "./traverse.js";
|
|
|
5
5
|
* Given a slash-separated path like "foo/bar", traverse the keys "foo/" and
|
|
6
6
|
* "bar" and return the resulting value.
|
|
7
7
|
*
|
|
8
|
-
* @typedef {import("../../index.ts").
|
|
8
|
+
* @typedef {import("../../index.ts").Maplike} Maplike
|
|
9
9
|
*
|
|
10
|
-
* @param {
|
|
10
|
+
* @param {Maplike} tree
|
|
11
11
|
* @param {string} path
|
|
12
12
|
*/
|
|
13
13
|
export default async function traversePath(tree, path) {
|
package/src/operations/values.js
CHANGED
|
@@ -1,15 +1,24 @@
|
|
|
1
|
-
import
|
|
1
|
+
import getMapArgument from "../utilities/getMapArgument.js";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Return the values in the
|
|
4
|
+
* Return the values in the map.
|
|
5
5
|
*
|
|
6
|
-
* @typedef {import("../../index.ts").
|
|
6
|
+
* @typedef {import("../../index.ts").Maplike} Maplike
|
|
7
7
|
*
|
|
8
|
-
* @param {
|
|
8
|
+
* @param {Maplike} maplike
|
|
9
9
|
*/
|
|
10
|
-
export default async function values(
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
export default async function values(maplike) {
|
|
11
|
+
const map = await getMapArgument(maplike, "values");
|
|
12
|
+
let result;
|
|
13
|
+
/** @type {any} */
|
|
14
|
+
let iterable = map.values();
|
|
15
|
+
if (Symbol.asyncIterator in iterable) {
|
|
16
|
+
result = [];
|
|
17
|
+
for await (const key of iterable) {
|
|
18
|
+
result.push(key);
|
|
19
|
+
}
|
|
20
|
+
} else {
|
|
21
|
+
result = Array.from(iterable);
|
|
22
|
+
}
|
|
23
|
+
return result;
|
|
15
24
|
}
|
|
@@ -1,33 +1,39 @@
|
|
|
1
|
-
import
|
|
1
|
+
import AsyncMap from "../drivers/AsyncMap.js";
|
|
2
|
+
import getMapArgument from "../utilities/getMapArgument.js";
|
|
2
3
|
import values from "./values.js";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
|
-
* Return a
|
|
6
|
+
* Return a map whose keys are provided by the _values_ of a second map (e.g.,
|
|
6
7
|
* an array of keys).
|
|
7
8
|
*
|
|
8
|
-
* @typedef {import("../../index.ts").
|
|
9
|
-
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
9
|
+
* @typedef {import("../../index.ts").Maplike} Maplike
|
|
10
10
|
*
|
|
11
|
-
* @param {
|
|
12
|
-
* @param {
|
|
13
|
-
* @returns {Promise<
|
|
11
|
+
* @param {Maplike} maplike
|
|
12
|
+
* @param {Maplike} keysMaplike
|
|
13
|
+
* @returns {Promise<AsyncMap>}
|
|
14
14
|
*/
|
|
15
|
-
export default async function withKeys(
|
|
16
|
-
const
|
|
17
|
-
const
|
|
15
|
+
export default async function withKeys(maplike, keysMaplike) {
|
|
16
|
+
const source = await getMapArgument(maplike, "withKeys", { position: 0 });
|
|
17
|
+
const keysMap = await getMapArgument(keysMaplike, "withKeys", {
|
|
18
18
|
position: 1,
|
|
19
19
|
});
|
|
20
20
|
|
|
21
21
|
let keys;
|
|
22
22
|
|
|
23
|
-
return {
|
|
23
|
+
return Object.assign(new AsyncMap(), {
|
|
24
|
+
description: "withKeys",
|
|
25
|
+
|
|
24
26
|
async get(key) {
|
|
25
|
-
return
|
|
27
|
+
return source.get(key);
|
|
26
28
|
},
|
|
27
29
|
|
|
28
|
-
async keys() {
|
|
29
|
-
keys ??= await values(
|
|
30
|
-
|
|
30
|
+
async *keys() {
|
|
31
|
+
keys ??= await values(keysMap);
|
|
32
|
+
yield* keys;
|
|
31
33
|
},
|
|
32
|
-
|
|
34
|
+
|
|
35
|
+
source: source,
|
|
36
|
+
|
|
37
|
+
trailingSlashKeys: /** @type {any} */ (source).trailingSlashKeys,
|
|
38
|
+
});
|
|
33
39
|
}
|
package/src/symbols.js
CHANGED
|
@@ -1,25 +1,30 @@
|
|
|
1
|
+
import * as trailingSlash from "../trailingSlash.js";
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
|
-
*
|
|
4
|
+
* Cast the given map to a plain object or array.
|
|
3
5
|
*
|
|
4
6
|
* If the given plain object has only integer keys, and the set of integers is
|
|
5
7
|
* complete from 0 to length-1, assume the values are a result of array
|
|
6
8
|
* transformations and the values are the desired result; return them as is.
|
|
7
|
-
* Otherwise, create a plain object with the keys and values.
|
|
8
9
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
10
|
+
* Otherwise, call the createFn to create an object. By default, this will
|
|
11
|
+
* create a plain object from the map's entries.
|
|
12
|
+
*
|
|
13
|
+
* @param {Map} map
|
|
14
|
+
* @param {Function} [createFn]
|
|
11
15
|
*/
|
|
12
|
-
export default function castArraylike(
|
|
13
|
-
if (
|
|
16
|
+
export default function castArraylike(map, createFn = Object.fromEntries) {
|
|
17
|
+
if (map.size === 0) {
|
|
14
18
|
// Empty keys/values means an empty object, not an empty array
|
|
15
19
|
return {};
|
|
16
20
|
}
|
|
17
21
|
|
|
18
22
|
let onlyNumericKeys = true;
|
|
19
|
-
const numberSeen = new Array(
|
|
20
|
-
for (const key of keys) {
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
+
const numberSeen = new Array(map.size).fill(false);
|
|
24
|
+
for (const key of map.keys()) {
|
|
25
|
+
const normalized = trailingSlash.remove(key);
|
|
26
|
+
const n = Number(normalized);
|
|
27
|
+
if (isNaN(n) || !Number.isInteger(n) || n < 0 || n >= map.size) {
|
|
23
28
|
onlyNumericKeys = false;
|
|
24
29
|
break;
|
|
25
30
|
} else {
|
|
@@ -30,9 +35,15 @@ export default function castArraylike(keys, values) {
|
|
|
30
35
|
// If any number from 0..length-1 is missing, we can't treat this as an array
|
|
31
36
|
const allNumbersSeen = onlyNumericKeys && numberSeen.every((v) => v);
|
|
32
37
|
if (allNumbersSeen) {
|
|
33
|
-
return values;
|
|
38
|
+
return Array.from(map.values());
|
|
34
39
|
} else {
|
|
35
|
-
//
|
|
36
|
-
|
|
40
|
+
// Create a map with normalized keys, then call the createFn to build the
|
|
41
|
+
// result. By default this will create a plain object from the entries.
|
|
42
|
+
const normalizedMap = new Map();
|
|
43
|
+
for (const [key, value] of map.entries()) {
|
|
44
|
+
const normalized = trailingSlash.remove(key);
|
|
45
|
+
normalizedMap.set(normalized, value);
|
|
46
|
+
}
|
|
47
|
+
return createFn(normalizedMap);
|
|
37
48
|
}
|
|
38
49
|
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import from from "../operations/from.js";
|
|
2
|
+
import isUnpackable from "./isUnpackable.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Convert the indicated argument to a map, or throw an exception.
|
|
6
|
+
*
|
|
7
|
+
* Tree operations can use this to validate the map argument and provide more
|
|
8
|
+
* helpful error messages. This also unpacks a unpackable map argument.
|
|
9
|
+
*
|
|
10
|
+
* @typedef {import("../../index.ts").AsyncMap} AsyncMap
|
|
11
|
+
* @typedef {import("../../index.ts").Maplike} Maplike
|
|
12
|
+
* @typedef {import("../../index.ts").Unpackable} Unpackable
|
|
13
|
+
*
|
|
14
|
+
* @param {Maplike|Unpackable} maplike
|
|
15
|
+
* @param {string} operation
|
|
16
|
+
* @param {{ deep?: boolean, position?: number }} [options]
|
|
17
|
+
* @returns {Promise<Map|AsyncMap>}
|
|
18
|
+
*/
|
|
19
|
+
export default async function getMapArgument(maplike, operation, options = {}) {
|
|
20
|
+
const deep = options.deep;
|
|
21
|
+
const position = options.position ?? 0;
|
|
22
|
+
|
|
23
|
+
if (isUnpackable(maplike)) {
|
|
24
|
+
maplike = await maplike.unpack();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let map;
|
|
28
|
+
try {
|
|
29
|
+
map = from(maplike, { deep });
|
|
30
|
+
} catch (/** @type {any} */ error) {
|
|
31
|
+
let message = error.message ?? error;
|
|
32
|
+
message = `${operation}: ${message}`;
|
|
33
|
+
const newError = new TypeError(message);
|
|
34
|
+
/** @type {any} */ (newError).position = position;
|
|
35
|
+
throw newError;
|
|
36
|
+
}
|
|
37
|
+
return map;
|
|
38
|
+
}
|
|
@@ -5,11 +5,11 @@ import * as symbols from "../symbols.js";
|
|
|
5
5
|
*
|
|
6
6
|
* This is intended to be called by unpack functions.
|
|
7
7
|
*
|
|
8
|
-
* @typedef {import("
|
|
8
|
+
* @typedef {import("../../index.ts").SyncOrAsyncMap} SyncOrAsyncMap
|
|
9
9
|
*
|
|
10
10
|
* @param {any} packed
|
|
11
11
|
* @param {any} [options]
|
|
12
|
-
* @returns {
|
|
12
|
+
* @returns {SyncOrAsyncMap|null}
|
|
13
13
|
*/
|
|
14
14
|
export default function getParent(packed, options = {}) {
|
|
15
15
|
// Prefer parent set on options
|
|
@@ -6,15 +6,17 @@ import getRealmObjectPrototype from "./getRealmObjectPrototype.js";
|
|
|
6
6
|
*
|
|
7
7
|
* @typedef {import("../../index.ts").Stringlike} Stringlike
|
|
8
8
|
*
|
|
9
|
-
* @param {any}
|
|
9
|
+
* @param {any} object
|
|
10
10
|
* @returns {obj is Stringlike}
|
|
11
11
|
*/
|
|
12
|
-
export default function isStringlike(
|
|
13
|
-
if (typeof
|
|
12
|
+
export default function isStringlike(object) {
|
|
13
|
+
if (typeof object === "string") {
|
|
14
14
|
return true;
|
|
15
|
-
} else if (
|
|
15
|
+
} else if (typeof object === "symbol") {
|
|
16
16
|
return false;
|
|
17
|
-
} else if (
|
|
17
|
+
} else if (object?.toString === undefined) {
|
|
18
|
+
return false;
|
|
19
|
+
} else if (object.toString === getRealmObjectPrototype(object)?.toString) {
|
|
18
20
|
// The stupid Object.prototype.toString implementation always returns
|
|
19
21
|
// "[object Object]", so if that's the only toString method the object has,
|
|
20
22
|
// we return false.
|