@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.
Files changed (182) hide show
  1. package/browser.js +1 -1
  2. package/index.ts +43 -35
  3. package/main.js +1 -2
  4. package/package.json +4 -7
  5. package/shared.js +74 -12
  6. package/src/Tree.js +11 -2
  7. package/src/drivers/AsyncMap.js +237 -0
  8. package/src/drivers/{BrowserFileTree.js → BrowserFileMap.js} +54 -38
  9. package/src/drivers/{calendarTree.js → CalendarMap.js} +80 -63
  10. package/src/drivers/ConstantMap.js +28 -0
  11. package/src/drivers/{ExplorableSiteTree.js → ExplorableSiteMap.js} +7 -7
  12. package/src/drivers/FileMap.js +238 -0
  13. package/src/drivers/{FunctionTree.js → FunctionMap.js} +19 -22
  14. package/src/drivers/ObjectMap.js +151 -0
  15. package/src/drivers/SetMap.js +13 -0
  16. package/src/drivers/{SiteTree.js → SiteMap.js} +17 -20
  17. package/src/drivers/SyncMap.js +260 -0
  18. package/src/jsonKeys.d.ts +2 -2
  19. package/src/jsonKeys.js +20 -5
  20. package/src/operations/addNextPrevious.js +35 -36
  21. package/src/operations/assign.js +27 -23
  22. package/src/operations/cache.js +30 -36
  23. package/src/operations/cachedKeyFunctions.js +1 -1
  24. package/src/operations/calendar.js +5 -0
  25. package/src/operations/child.js +35 -0
  26. package/src/operations/clear.js +13 -12
  27. package/src/operations/constant.js +5 -0
  28. package/src/operations/deepEntries.js +23 -0
  29. package/src/operations/deepMap.js +9 -9
  30. package/src/operations/deepMerge.js +36 -25
  31. package/src/operations/deepReverse.js +23 -16
  32. package/src/operations/deepTake.js +7 -7
  33. package/src/operations/deepText.js +4 -4
  34. package/src/operations/deepValues.js +3 -6
  35. package/src/operations/deepValuesIterator.js +11 -11
  36. package/src/operations/delete.js +8 -12
  37. package/src/operations/entries.js +17 -10
  38. package/src/operations/filter.js +9 -7
  39. package/src/operations/first.js +12 -10
  40. package/src/operations/forEach.js +10 -13
  41. package/src/operations/from.js +30 -39
  42. package/src/operations/globKeys.js +22 -17
  43. package/src/operations/group.js +2 -2
  44. package/src/operations/groupBy.js +24 -22
  45. package/src/operations/has.js +7 -9
  46. package/src/operations/indent.js +2 -2
  47. package/src/operations/inners.js +19 -15
  48. package/src/operations/invokeFunctions.js +22 -10
  49. package/src/operations/isAsyncMutableTree.js +5 -12
  50. package/src/operations/isAsyncTree.js +5 -20
  51. package/src/operations/isMap.js +39 -0
  52. package/src/operations/isMaplike.js +34 -0
  53. package/src/operations/isReadOnlyMap.js +14 -0
  54. package/src/operations/isTraversable.js +3 -3
  55. package/src/operations/isTreelike.js +5 -30
  56. package/src/operations/json.js +4 -12
  57. package/src/operations/keys.js +17 -8
  58. package/src/operations/length.js +9 -8
  59. package/src/operations/map.js +28 -30
  60. package/src/operations/mapExtension.js +20 -16
  61. package/src/operations/mapReduce.js +38 -24
  62. package/src/operations/mask.js +54 -29
  63. package/src/operations/match.js +13 -9
  64. package/src/operations/merge.js +43 -35
  65. package/src/operations/paginate.js +26 -18
  66. package/src/operations/parent.js +7 -7
  67. package/src/operations/paths.js +20 -22
  68. package/src/operations/plain.js +6 -6
  69. package/src/operations/reduce.js +16 -0
  70. package/src/operations/regExpKeys.js +21 -12
  71. package/src/operations/reverse.js +21 -15
  72. package/src/operations/root.js +6 -5
  73. package/src/operations/scope.js +31 -26
  74. package/src/operations/set.js +20 -0
  75. package/src/operations/shuffle.js +23 -16
  76. package/src/operations/size.js +13 -0
  77. package/src/operations/sort.js +55 -40
  78. package/src/operations/sync.js +14 -0
  79. package/src/operations/take.js +23 -11
  80. package/src/operations/text.js +4 -4
  81. package/src/operations/toFunction.js +7 -7
  82. package/src/operations/traverse.js +4 -4
  83. package/src/operations/traverseOrThrow.js +18 -9
  84. package/src/operations/traversePath.js +2 -2
  85. package/src/operations/values.js +18 -9
  86. package/src/operations/withKeys.js +22 -16
  87. package/src/symbols.js +1 -0
  88. package/src/utilities/castArraylike.js +24 -13
  89. package/src/utilities/getMapArgument.js +38 -0
  90. package/src/utilities/getParent.js +2 -2
  91. package/src/utilities/isStringlike.js +7 -5
  92. package/src/utilities/setParent.js +7 -7
  93. package/src/utilities/toFunction.js +2 -2
  94. package/src/utilities/toPlainValue.js +21 -19
  95. package/test/SampleAsyncMap.js +34 -0
  96. package/test/browser/assert.js +20 -0
  97. package/test/browser/index.html +53 -21
  98. package/test/drivers/AsyncMap.test.js +119 -0
  99. package/test/drivers/{BrowserFileTree.test.js → BrowserFileMap.test.js} +50 -33
  100. package/test/drivers/{calendarTree.test.js → CalendarMap.test.js} +17 -19
  101. package/test/drivers/ConstantMap.test.js +15 -0
  102. package/test/drivers/{ExplorableSiteTree.test.js → ExplorableSiteMap.test.js} +29 -14
  103. package/test/drivers/FileMap.test.js +156 -0
  104. package/test/drivers/FunctionMap.test.js +56 -0
  105. package/test/drivers/ObjectMap.test.js +194 -0
  106. package/test/drivers/SetMap.test.js +35 -0
  107. package/test/drivers/{SiteTree.test.js → SiteMap.test.js} +14 -10
  108. package/test/drivers/SyncMap.test.js +335 -0
  109. package/test/jsonKeys.test.js +18 -6
  110. package/test/operations/addNextPrevious.test.js +3 -2
  111. package/test/operations/assign.test.js +30 -35
  112. package/test/operations/cache.test.js +17 -12
  113. package/test/operations/cachedKeyFunctions.test.js +12 -9
  114. package/test/operations/child.test.js +34 -0
  115. package/test/operations/clear.test.js +6 -27
  116. package/test/operations/deepEntries.test.js +32 -0
  117. package/test/operations/deepMerge.test.js +23 -16
  118. package/test/operations/deepReverse.test.js +2 -2
  119. package/test/operations/deepTake.test.js +2 -2
  120. package/test/operations/deepText.test.js +4 -4
  121. package/test/operations/deepValuesIterator.test.js +2 -2
  122. package/test/operations/delete.test.js +2 -2
  123. package/test/operations/extensionKeyFunctions.test.js +6 -5
  124. package/test/operations/filter.test.js +3 -3
  125. package/test/operations/from.test.js +25 -31
  126. package/test/operations/globKeys.test.js +9 -9
  127. package/test/operations/groupBy.test.js +6 -5
  128. package/test/operations/inners.test.js +17 -14
  129. package/test/operations/invokeFunctions.test.js +2 -2
  130. package/test/operations/isMap.test.js +15 -0
  131. package/test/operations/isMaplike.test.js +15 -0
  132. package/test/operations/json.test.js +2 -2
  133. package/test/operations/keys.test.js +16 -3
  134. package/test/operations/map.test.js +40 -30
  135. package/test/operations/mapExtension.test.js +6 -6
  136. package/test/operations/mapReduce.test.js +14 -12
  137. package/test/operations/mask.test.js +16 -3
  138. package/test/operations/match.test.js +2 -2
  139. package/test/operations/merge.test.js +20 -14
  140. package/test/operations/paginate.test.js +5 -5
  141. package/test/operations/parent.test.js +3 -3
  142. package/test/operations/paths.test.js +20 -27
  143. package/test/operations/plain.test.js +8 -8
  144. package/test/operations/regExpKeys.test.js +22 -18
  145. package/test/operations/reverse.test.js +4 -3
  146. package/test/operations/scope.test.js +6 -5
  147. package/test/operations/set.test.js +11 -0
  148. package/test/operations/shuffle.test.js +3 -2
  149. package/test/operations/sort.test.js +7 -10
  150. package/test/operations/sync.test.js +43 -0
  151. package/test/operations/take.test.js +2 -2
  152. package/test/operations/toFunction.test.js +2 -2
  153. package/test/operations/traverse.test.js +17 -5
  154. package/test/operations/withKeys.test.js +2 -2
  155. package/test/utilities/castArrayLike.test.js +53 -0
  156. package/test/utilities/setParent.test.js +6 -6
  157. package/test/utilities/toFunction.test.js +2 -2
  158. package/test/utilities/toPlainValue.test.js +51 -12
  159. package/src/drivers/DeepMapTree.js +0 -23
  160. package/src/drivers/DeepObjectTree.js +0 -18
  161. package/src/drivers/DeferredTree.js +0 -81
  162. package/src/drivers/FileTree.js +0 -276
  163. package/src/drivers/MapTree.js +0 -70
  164. package/src/drivers/ObjectTree.js +0 -158
  165. package/src/drivers/SetTree.js +0 -34
  166. package/src/drivers/constantTree.js +0 -19
  167. package/src/drivers/limitConcurrency.js +0 -63
  168. package/src/internal.js +0 -16
  169. package/src/utilities/getTreeArgument.js +0 -43
  170. package/test/drivers/DeepMapTree.test.js +0 -17
  171. package/test/drivers/DeepObjectTree.test.js +0 -35
  172. package/test/drivers/DeferredTree.test.js +0 -22
  173. package/test/drivers/FileTree.test.js +0 -192
  174. package/test/drivers/FunctionTree.test.js +0 -46
  175. package/test/drivers/MapTree.test.js +0 -59
  176. package/test/drivers/ObjectTree.test.js +0 -163
  177. package/test/drivers/SetTree.test.js +0 -44
  178. package/test/drivers/constantTree.test.js +0 -13
  179. package/test/drivers/limitConcurrency.test.js +0 -41
  180. package/test/operations/isAsyncMutableTree.test.js +0 -17
  181. package/test/operations/isAsyncTree.test.js +0 -26
  182. package/test/operations/isTreelike.test.js +0 -13
@@ -1,33 +1,40 @@
1
- import getTreeArgument from "../utilities/getTreeArgument.js";
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 {import("@weborigami/types").AsyncTree} AsyncTree
7
- * @typedef {import("../../index.ts").Treelike} Treelike
8
+ * @typedef {import("../../index.ts").Maplike} Maplike
8
9
  *
9
- * @param {Treelike} treelike
10
+ * @param {Maplike} maplike
10
11
  * @param {boolean?} reshuffle
11
- * @returns {Promise<AsyncTree>}
12
+ * @returns {Promise<AsyncMap>}
12
13
  */
13
- export default async function shuffle(treelike, reshuffle = false) {
14
- const tree = await getTreeArgument(treelike, "shuffle");
14
+ export default async function shuffle(maplike, reshuffle = false) {
15
+ const source = await getMapArgument(maplike, "shuffle");
15
16
 
16
- let keys;
17
+ let mapKeys;
18
+
19
+ return Object.assign(new AsyncMap(), {
20
+ description: "shuffle",
17
21
 
18
- return {
19
22
  async get(key) {
20
- return tree.get(key);
23
+ return source.get(key);
21
24
  },
22
25
 
23
- async keys() {
24
- if (!keys || reshuffle) {
25
- keys = Array.from(await tree.keys());
26
- shuffleArray(keys);
26
+ async *keys() {
27
+ if (!mapKeys || reshuffle) {
28
+ mapKeys = await keys(source);
29
+ shuffleArray(mapKeys);
27
30
  }
28
- return keys;
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
+ }
@@ -1,21 +1,23 @@
1
- import getTreeArgument from "../utilities/getTreeArgument.js";
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 tree with the original's keys sorted. A comparison function can
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("@weborigami/types").AsyncTree} AsyncTree
9
- * @typedef {(key: any, tree: AsyncTree) => any} SortKeyFn
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").Treelike} Treelike
13
+ * @typedef {import("../../index.ts").Maplike} Maplike
12
14
  * @typedef {import("../../index.ts").ValueKeyFn} ValueKeyFn
13
15
  *
14
- * @param {Treelike} treelike
16
+ * @param {Maplike} maplike
15
17
  * @param {SortOptions|ValueKeyFn} [options]
16
18
  */
17
- export default async function sort(treelike, options) {
18
- const tree = await getTreeArgument(treelike, "sort");
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.create(tree);
31
- transformed.keys = async () => {
32
- const keys = Array.from(await tree.keys());
32
+ const transformed = Object.assign(new AsyncMap(), {
33
+ descriptor: "sort",
33
34
 
34
- if (sortKey) {
35
- // Invoke the async sortKey function to get sort keys.
36
- // Create { key, sortKey } tuples.
37
- const tuples = await Promise.all(
38
- keys.map(async (key) => {
39
- const value = await tree.get(key);
40
- const sort = await sortKey(value, key, tree);
41
- if (sort === undefined) {
42
- throw new Error(
43
- `sortKey function returned undefined for key ${key}`
44
- );
45
- }
46
- return { key, sort };
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
+ }
@@ -1,22 +1,34 @@
1
- import getTreeArgument from "../utilities/getTreeArgument.js";
1
+ import AsyncMap from "../drivers/AsyncMap.js";
2
+ import getMapArgument from "../utilities/getMapArgument.js";
2
3
 
3
4
  /**
4
- * Returns a new tree with the number of keys limited to the indicated count.
5
+ * Returns a new map with the number of keys limited to the indicated count.
5
6
  *
6
- * @param {import("../../index.ts").Treelike} treelike
7
+ * @param {import("../../index.ts").Maplike} maplike
7
8
  * @param {number} count
8
9
  */
9
- export default async function take(treelike, count) {
10
- const tree = await getTreeArgument(treelike, "take");
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
- return {
13
- async keys() {
14
- const keys = Array.from(await tree.keys());
15
- return keys.slice(0, count);
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 tree.get(key);
27
+ return source.get(key);
20
28
  },
21
- };
29
+
30
+ source: source,
31
+
32
+ trailingSlashKeys: /** @type {any} */ (source).trailingSlashKeys,
33
+ });
22
34
  }
@@ -1,10 +1,10 @@
1
1
  import toString from "../utilities/toString.js";
2
2
  import deepText from "./deepText.js";
3
- import isTreelike from "./isTreelike.js";
3
+ import isMaplike from "./isMaplike.js";
4
4
 
5
5
  /**
6
- * A tagged template literal function that concatenate the deep text values in a
7
- * tree. Any treelike values will be concatenated using `deepText`.
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
- isTreelike(value) ? deepText(value) : toString(value)
16
+ isMaplike(value) ? deepText(value) : toString(value)
17
17
  )
18
18
  );
19
19
  // Splice all the strings together
@@ -1,14 +1,14 @@
1
- import getTreeArgument from "../utilities/getTreeArgument.js";
1
+ import getMapArgument from "../utilities/getMapArgument.js";
2
2
 
3
3
  /**
4
- * Returns a function that invokes the tree's `get` method.
4
+ * Returns a function that invokes the map's `get` method.
5
5
  *
6
- * @typedef {import("../../index.ts").Treelike} Treelike
6
+ * @typedef {import("../../index.ts").Maplike} Maplike
7
7
  *
8
- * @param {Treelike} treelike
8
+ * @param {Maplike} maplike
9
9
  * @returns {Promise<Function>}
10
10
  */
11
- export default async function toFunction(treelike) {
12
- const tree = await getTreeArgument(treelike, "toFunction");
13
- return tree.get.bind(tree);
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").Treelike} Treelike
7
+ * @typedef {import("../../index.ts").Maplike} Maplike
8
8
  *
9
- * @param {Treelike} treelike
9
+ * @param {Maplike} maplike
10
10
  * @param {...any} keys
11
11
  */
12
- export default async function traverse(treelike, ...keys) {
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(treelike, ...keys);
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").Treelike} Treelike
10
- * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
10
+ * @typedef {import("../../index.ts").Maplike} Maplike
11
11
  *
12
- * @param {Treelike} treelike
12
+ * @param {Maplike} maplike
13
13
  * @param {...any} keys
14
14
  */
15
- export default async function traverseOrThrow(treelike, ...keys) {
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 = treelike;
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: treelike,
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 tree.
48
- const tree = from(value);
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 tree.get(key);
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").Treelike} Treelike
8
+ * @typedef {import("../../index.ts").Maplike} Maplike
9
9
  *
10
- * @param {Treelike} tree
10
+ * @param {Maplike} tree
11
11
  * @param {string} path
12
12
  */
13
13
  export default async function traversePath(tree, path) {
@@ -1,15 +1,24 @@
1
- import from from "./from.js";
1
+ import getMapArgument from "../utilities/getMapArgument.js";
2
2
 
3
3
  /**
4
- * Return the values in the specific node of the tree.
4
+ * Return the values in the map.
5
5
  *
6
- * @typedef {import("../../index.ts").Treelike} Treelike
6
+ * @typedef {import("../../index.ts").Maplike} Maplike
7
7
  *
8
- * @param {Treelike} treelike
8
+ * @param {Maplike} maplike
9
9
  */
10
- export default async function values(treelike) {
11
- const tree = from(treelike);
12
- const keys = Array.from(await tree.keys());
13
- const promises = keys.map(async (key) => tree.get(key));
14
- return Promise.all(promises);
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 getTreeArgument from "../utilities/getTreeArgument.js";
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 tree whose keys are provided by the _values_ of a second tree (e.g.,
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").Treelike} Treelike
9
- * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
9
+ * @typedef {import("../../index.ts").Maplike} Maplike
10
10
  *
11
- * @param {Treelike} treelike
12
- * @param {Treelike} keysTreelike
13
- * @returns {Promise<AsyncTree>}
11
+ * @param {Maplike} maplike
12
+ * @param {Maplike} keysMaplike
13
+ * @returns {Promise<AsyncMap>}
14
14
  */
15
- export default async function withKeys(treelike, keysTreelike) {
16
- const tree = await getTreeArgument(treelike, "withKeys", { position: 0 });
17
- const keysTree = await getTreeArgument(keysTreelike, "withKeys", {
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 tree.get(key);
27
+ return source.get(key);
26
28
  },
27
29
 
28
- async keys() {
29
- keys ??= await values(keysTree);
30
- return keys;
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,3 +1,4 @@
1
+ export const async = Symbol("async");
1
2
  export const deep = Symbol("deep");
2
3
  export const keys = Symbol("keys");
3
4
  export const parent = Symbol("parent");
@@ -1,25 +1,30 @@
1
+ import * as trailingSlash from "../trailingSlash.js";
2
+
1
3
  /**
2
- * Create an array or plain object from the given keys and values.
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
- * @param {any[]} keys
10
- * @param {any[]} values
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(keys, values) {
13
- if (keys.length === 0) {
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(keys.length);
20
- for (const key of keys) {
21
- const n = Number(key);
22
- if (isNaN(n) || !Number.isInteger(n) || n < 0 || n >= keys.length) {
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
- // Return a plain object with the (key, value) pairs
36
- return Object.fromEntries(keys.map((key, i) => [key, values[i]]));
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("@weborigami/types").AsyncTree} AsyncTree
8
+ * @typedef {import("../../index.ts").SyncOrAsyncMap} SyncOrAsyncMap
9
9
  *
10
10
  * @param {any} packed
11
11
  * @param {any} [options]
12
- * @returns {AsyncTree|null}
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} obj
9
+ * @param {any} object
10
10
  * @returns {obj is Stringlike}
11
11
  */
12
- export default function isStringlike(obj) {
13
- if (typeof obj === "string") {
12
+ export default function isStringlike(object) {
13
+ if (typeof object === "string") {
14
14
  return true;
15
- } else if (obj?.toString === undefined) {
15
+ } else if (typeof object === "symbol") {
16
16
  return false;
17
- } else if (obj.toString === getRealmObjectPrototype(obj)?.toString) {
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.