@weborigami/async-tree 0.5.7 → 0.6.0

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 (178) hide show
  1. package/browser.js +1 -1
  2. package/index.ts +31 -35
  3. package/main.js +1 -2
  4. package/package.json +4 -7
  5. package/shared.js +77 -12
  6. package/src/Tree.js +8 -2
  7. package/src/drivers/AsyncMap.js +210 -0
  8. package/src/drivers/{BrowserFileTree.js → BrowserFileMap.js} +36 -27
  9. package/src/drivers/{calendarTree.js → CalendarMap.js} +81 -62
  10. package/src/drivers/ConstantMap.js +30 -0
  11. package/src/drivers/DeepObjectMap.js +27 -0
  12. package/src/drivers/{ExplorableSiteTree.js → ExplorableSiteMap.js} +7 -7
  13. package/src/drivers/FileMap.js +245 -0
  14. package/src/drivers/{FunctionTree.js → FunctionMap.js} +19 -22
  15. package/src/drivers/ObjectMap.js +139 -0
  16. package/src/drivers/SetMap.js +13 -0
  17. package/src/drivers/{SiteTree.js → SiteMap.js} +16 -17
  18. package/src/drivers/SyncMap.js +245 -0
  19. package/src/jsonKeys.d.ts +2 -2
  20. package/src/jsonKeys.js +6 -5
  21. package/src/operations/addNextPrevious.js +35 -36
  22. package/src/operations/assign.js +30 -21
  23. package/src/operations/cache.js +29 -35
  24. package/src/operations/cachedKeyFunctions.js +1 -1
  25. package/src/operations/calendar.js +5 -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 +31 -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 +27 -30
  60. package/src/operations/mapExtension.js +20 -16
  61. package/src/operations/mapReduce.js +22 -17
  62. package/src/operations/mask.js +31 -22
  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 +8 -8
  68. package/src/operations/plain.js +6 -6
  69. package/src/operations/regExpKeys.js +21 -12
  70. package/src/operations/reverse.js +21 -15
  71. package/src/operations/root.js +6 -5
  72. package/src/operations/scope.js +31 -26
  73. package/src/operations/shuffle.js +23 -16
  74. package/src/operations/size.js +13 -0
  75. package/src/operations/sort.js +55 -40
  76. package/src/operations/sync.js +21 -0
  77. package/src/operations/take.js +23 -11
  78. package/src/operations/text.js +4 -4
  79. package/src/operations/toFunction.js +7 -7
  80. package/src/operations/traverse.js +4 -4
  81. package/src/operations/traverseOrThrow.js +13 -9
  82. package/src/operations/traversePath.js +2 -2
  83. package/src/operations/values.js +18 -9
  84. package/src/operations/withKeys.js +22 -16
  85. package/src/symbols.js +1 -0
  86. package/src/utilities/castArraylike.js +10 -2
  87. package/src/utilities/getMapArgument.js +38 -0
  88. package/src/utilities/getParent.js +2 -2
  89. package/src/utilities/isStringlike.js +7 -5
  90. package/src/utilities/setParent.js +7 -7
  91. package/src/utilities/toFunction.js +2 -2
  92. package/src/utilities/toPlainValue.js +22 -18
  93. package/test/SampleAsyncMap.js +34 -0
  94. package/test/browser/assert.js +20 -0
  95. package/test/browser/index.html +54 -21
  96. package/test/drivers/AsyncMap.test.js +119 -0
  97. package/test/drivers/{BrowserFileTree.test.js → BrowserFileMap.test.js} +42 -23
  98. package/test/drivers/{calendarTree.test.js → CalendarMap.test.js} +17 -19
  99. package/test/drivers/ConstantMap.test.js +15 -0
  100. package/test/drivers/DeepObjectMap.test.js +36 -0
  101. package/test/drivers/{ExplorableSiteTree.test.js → ExplorableSiteMap.test.js} +29 -14
  102. package/test/drivers/FileMap.test.js +185 -0
  103. package/test/drivers/FunctionMap.test.js +56 -0
  104. package/test/drivers/ObjectMap.test.js +166 -0
  105. package/test/drivers/SetMap.test.js +35 -0
  106. package/test/drivers/{SiteTree.test.js → SiteMap.test.js} +14 -10
  107. package/test/drivers/SyncMap.test.js +321 -0
  108. package/test/jsonKeys.test.js +2 -2
  109. package/test/operations/addNextPrevious.test.js +3 -2
  110. package/test/operations/assign.test.js +30 -35
  111. package/test/operations/cache.test.js +8 -6
  112. package/test/operations/cachedKeyFunctions.test.js +6 -5
  113. package/test/operations/clear.test.js +6 -27
  114. package/test/operations/deepEntries.test.js +32 -0
  115. package/test/operations/deepMerge.test.js +6 -5
  116. package/test/operations/deepReverse.test.js +2 -2
  117. package/test/operations/deepTake.test.js +2 -2
  118. package/test/operations/deepText.test.js +4 -4
  119. package/test/operations/deepValuesIterator.test.js +2 -2
  120. package/test/operations/delete.test.js +2 -2
  121. package/test/operations/extensionKeyFunctions.test.js +6 -5
  122. package/test/operations/filter.test.js +3 -3
  123. package/test/operations/from.test.js +23 -31
  124. package/test/operations/globKeys.test.js +9 -9
  125. package/test/operations/groupBy.test.js +6 -5
  126. package/test/operations/inners.test.js +4 -4
  127. package/test/operations/invokeFunctions.test.js +2 -2
  128. package/test/operations/isMap.test.js +15 -0
  129. package/test/operations/isMaplike.test.js +15 -0
  130. package/test/operations/json.test.js +2 -2
  131. package/test/operations/keys.test.js +16 -3
  132. package/test/operations/map.test.js +20 -18
  133. package/test/operations/mapExtension.test.js +6 -6
  134. package/test/operations/mapReduce.test.js +2 -2
  135. package/test/operations/mask.test.js +4 -3
  136. package/test/operations/match.test.js +2 -2
  137. package/test/operations/merge.test.js +15 -11
  138. package/test/operations/paginate.test.js +5 -5
  139. package/test/operations/parent.test.js +3 -3
  140. package/test/operations/paths.test.js +6 -6
  141. package/test/operations/plain.test.js +8 -8
  142. package/test/operations/regExpKeys.test.js +12 -11
  143. package/test/operations/reverse.test.js +4 -3
  144. package/test/operations/scope.test.js +6 -5
  145. package/test/operations/shuffle.test.js +3 -2
  146. package/test/operations/sort.test.js +7 -10
  147. package/test/operations/sync.test.js +43 -0
  148. package/test/operations/take.test.js +2 -2
  149. package/test/operations/toFunction.test.js +2 -2
  150. package/test/operations/traverse.test.js +4 -5
  151. package/test/operations/withKeys.test.js +2 -2
  152. package/test/utilities/setParent.test.js +6 -6
  153. package/test/utilities/toFunction.test.js +2 -2
  154. package/test/utilities/toPlainValue.test.js +51 -12
  155. package/src/drivers/DeepMapTree.js +0 -23
  156. package/src/drivers/DeepObjectTree.js +0 -18
  157. package/src/drivers/DeferredTree.js +0 -81
  158. package/src/drivers/FileTree.js +0 -276
  159. package/src/drivers/MapTree.js +0 -70
  160. package/src/drivers/ObjectTree.js +0 -158
  161. package/src/drivers/SetTree.js +0 -34
  162. package/src/drivers/constantTree.js +0 -19
  163. package/src/drivers/limitConcurrency.js +0 -63
  164. package/src/internal.js +0 -16
  165. package/src/utilities/getTreeArgument.js +0 -43
  166. package/test/drivers/DeepMapTree.test.js +0 -17
  167. package/test/drivers/DeepObjectTree.test.js +0 -35
  168. package/test/drivers/DeferredTree.test.js +0 -22
  169. package/test/drivers/FileTree.test.js +0 -192
  170. package/test/drivers/FunctionTree.test.js +0 -46
  171. package/test/drivers/MapTree.test.js +0 -59
  172. package/test/drivers/ObjectTree.test.js +0 -163
  173. package/test/drivers/SetTree.test.js +0 -44
  174. package/test/drivers/constantTree.test.js +0 -13
  175. package/test/drivers/limitConcurrency.test.js +0 -41
  176. package/test/operations/isAsyncMutableTree.test.js +0 -17
  177. package/test/operations/isAsyncTree.test.js +0 -26
  178. package/test/operations/isTreelike.test.js +0 -13
@@ -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,12 +44,16 @@ 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++;
@@ -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");
@@ -9,7 +9,11 @@
9
9
  * @param {any[]} keys
10
10
  * @param {any[]} values
11
11
  */
12
- export default function castArraylike(keys, values) {
12
+ export default function castArraylike(
13
+ keys,
14
+ values,
15
+ createFn = Object.fromEntries
16
+ ) {
13
17
  if (keys.length === 0) {
14
18
  // Empty keys/values means an empty object, not an empty array
15
19
  return {};
@@ -33,6 +37,10 @@ export default function castArraylike(keys, values) {
33
37
  return values;
34
38
  } else {
35
39
  // Return a plain object with the (key, value) pairs
36
- return Object.fromEntries(keys.map((key, i) => [key, values[i]]));
40
+ const entries = [];
41
+ for (let i = 0; i < keys.length; i++) {
42
+ entries.push([keys[i], values[i]]);
43
+ }
44
+ return createFn(entries);
37
45
  }
38
46
  }
@@ -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.
@@ -1,21 +1,21 @@
1
- import isAsyncTree from "../operations/isAsyncTree.js";
1
+ import isMap from "../operations/isMap.js";
2
2
  import * as symbols from "../symbols.js";
3
3
 
4
4
  /**
5
5
  * If the child object doesn't have a parent yet, set it to the indicated
6
- * parent. If the child is an AsyncTree, set the `parent` property. Otherwise,
6
+ * parent. If the child is a map, set the `parent` property. Otherwise,
7
7
  * set the `symbols.parent` property.
8
8
  *
9
- * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
9
+ * @typedef {import("../../index.ts").SyncOrAsyncMap} SyncOrAsyncMap
10
10
  *
11
11
  * @param {*} child
12
- * @param {AsyncTree|null} parent
12
+ * @param {SyncOrAsyncMap|null} parent
13
13
  */
14
14
  export default function setParent(child, parent) {
15
- if (isAsyncTree(child)) {
15
+ if (isMap(child)) {
16
16
  // Value is a subtree; set its parent to this tree.
17
- if (!child.parent) {
18
- child.parent = parent;
17
+ if ("parent" in child && !child.parent) {
18
+ /** @type {any} */ (child).parent = parent;
19
19
  }
20
20
  } else if (Object.isExtensible(child) && !child[symbols.parent]) {
21
21
  try {
@@ -1,5 +1,5 @@
1
1
  import from from "../operations/from.js";
2
- import isTreelike from "../operations/isTreelike.js";
2
+ import isMaplike from "../operations/isMaplike.js";
3
3
  import isUnpackable from "./isUnpackable.js";
4
4
 
5
5
  /**
@@ -29,7 +29,7 @@ export default function toFunction(obj) {
29
29
  const fn = await fnPromise;
30
30
  return fn(...args);
31
31
  };
32
- } else if (isTreelike(obj)) {
32
+ } else if (isMaplike(obj)) {
33
33
  // Return a function that invokes the tree's getter.
34
34
  const tree = from(obj);
35
35
  return tree.get.bind(tree);
@@ -1,5 +1,5 @@
1
- import ObjectTree from "../drivers/ObjectTree.js";
2
- import isTreelike from "../operations/isTreelike.js";
1
+ import ObjectMap from "../drivers/ObjectMap.js";
2
+ import isMaplike from "../operations/isMaplike.js";
3
3
  import mapReduce from "../operations/mapReduce.js";
4
4
  import * as trailingSlash from "../trailingSlash.js";
5
5
  import castArraylike from "./castArraylike.js";
@@ -9,10 +9,8 @@ import toString from "./toString.js";
9
9
  import TypedArray from "./TypedArray.js";
10
10
 
11
11
  /**
12
- * Convert the given input to the plainest possible JavaScript value. This
13
- * helper is intended for functions that want to accept an argument from the ori
14
- * CLI, which could a string, a stream of data, or some other kind of JavaScript
15
- * object.
12
+ * Convert the given input to the plainest possible JavaScript value. This is
13
+ * useful for rendering data for display or serialization.
16
14
  *
17
15
  * If the input is a function, it will be invoked and its result will be
18
16
  * processed.
@@ -20,7 +18,7 @@ import TypedArray from "./TypedArray.js";
20
18
  * If the input is a promise, it will be resolved and its result will be
21
19
  * processed.
22
20
  *
23
- * If the input is treelike, it will be converted to a plain JavaScript object,
21
+ * If the input is maplike, it will be converted to a plain JavaScript object,
24
22
  * recursively traversing the tree and converting all values to plain types.
25
23
  *
26
24
  * If the input is stringlike, its text will be returned.
@@ -33,9 +31,13 @@ import TypedArray from "./TypedArray.js";
33
31
  * returned as a plain object.
34
32
  *
35
33
  * @param {any} input
34
+ * @param {import("../../index.ts").ReduceFn} [reduceFn]
36
35
  * @returns {Promise<any>}
37
36
  */
38
- export default async function toPlainValue(input) {
37
+ export default async function toPlainValue(
38
+ input,
39
+ reduceFn = reduceToPlainObject
40
+ ) {
39
41
  if (input instanceof Function) {
40
42
  // Invoke function
41
43
  input = input();
@@ -47,17 +49,9 @@ export default async function toPlainValue(input) {
47
49
 
48
50
  if (isPrimitive(input) || input instanceof Date) {
49
51
  return input;
50
- } else if (isTreelike(input)) {
52
+ } else if (isMaplike(input)) {
51
53
  // Recursively convert tree to plain object.
52
- return mapReduce(input, toPlainValue, (values, keys, tree) => {
53
- // Special case for an empty tree: if based on array, return array.
54
- if (tree instanceof ObjectTree && keys.length === 0) {
55
- return /** @type {any} */ (tree).object instanceof Array ? [] : {};
56
- }
57
- // Normalize slashes in keys.
58
- keys = keys.map(trailingSlash.remove);
59
- return castArraylike(keys, values);
60
- });
54
+ return mapReduce(input, (value) => toPlainValue(value, reduceFn), reduceFn);
61
55
  } else if (input instanceof ArrayBuffer || input instanceof TypedArray) {
62
56
  // Try to interpret the buffer as UTF-8 text, otherwise use base64.
63
57
  const text = toString(input);
@@ -78,6 +72,16 @@ export default async function toPlainValue(input) {
78
72
  }
79
73
  }
80
74
 
75
+ function reduceToPlainObject(values, keys, map) {
76
+ // Special case for an empty tree: if based on array, return array.
77
+ if (map instanceof ObjectMap && keys.length === 0) {
78
+ return /** @type {any} */ (map).object instanceof Array ? [] : {};
79
+ }
80
+ // Normalize slashes in keys.
81
+ keys = keys.map(trailingSlash.remove);
82
+ return castArraylike(keys, values);
83
+ }
84
+
81
85
  function toBase64(object) {
82
86
  if (typeof Buffer !== "undefined") {
83
87
  // Node.js environment
@@ -0,0 +1,34 @@
1
+ import AsyncMap from "../src/drivers/AsyncMap.js";
2
+ import * as trailingSlash from "../src/trailingSlash.js";
3
+
4
+ /**
5
+ * For testing AsyncMaps
6
+ */
7
+ export default class SampleAsyncMap extends AsyncMap {
8
+ constructor(entries) {
9
+ super();
10
+ this.map = new Map(entries);
11
+ }
12
+
13
+ async delete(key) {
14
+ const normalized = trailingSlash.remove(key);
15
+ return this.map.delete(normalized);
16
+ }
17
+
18
+ async get(key) {
19
+ let value = this.map.get(key);
20
+ if (value instanceof Array) {
21
+ value = Reflect.construct(this.constructor, [value]);
22
+ }
23
+ return value;
24
+ }
25
+
26
+ async *keys() {
27
+ yield* this.map.keys();
28
+ }
29
+
30
+ async set(key, value) {
31
+ this.map.set(key, value);
32
+ return this;
33
+ }
34
+ }
@@ -44,6 +44,10 @@ assert.deepEqual = (actual, expected) => {
44
44
  throw new Error(`Expected ${expected} but got ${actual}`);
45
45
  };
46
46
 
47
+ // For browser testing purposes we treat these the same
48
+ assert.strictEqual = assert.equal;
49
+ assert.deepStrictEqual = assert.deepEqual;
50
+
47
51
  assert.rejects = async (fn) => {
48
52
  try {
49
53
  await fn();
@@ -52,3 +56,19 @@ assert.rejects = async (fn) => {
52
56
  }
53
57
  throw new Error("Expected promise to reject but it resolved");
54
58
  };
59
+
60
+ assert.throws = (fn, expected) => {
61
+ try {
62
+ fn();
63
+ } catch (/** @type {any} */ error) {
64
+ if (expected) {
65
+ if (error.name !== expected.name || error.message !== expected.message) {
66
+ throw new Error(
67
+ `Expected error ${expected.name}: ${expected.message} but got ${error.name}: ${error.message}`
68
+ );
69
+ }
70
+ }
71
+ return;
72
+ }
73
+ throw new Error("Expected function to throw but it did not");
74
+ };
@@ -11,48 +11,81 @@
11
11
  }
12
12
  }
13
13
  </script>
14
- <!-- Omit FileTree.test.js, which is Node.js only -->
15
- <!-- Omit SiteTree.test.js, which requires mocks -->
14
+ <!-- Omit FileMap.test.js, which is Node.js only -->
15
+ <!-- Omit SiteMap.test.js, which requires mocks -->
16
16
 
17
- <!-- Unclear why drivers/constantTree.test.js won't load -->
18
-
19
- <script type="module" src="../Tree.test.js"></script>
20
- <script type="module" src="../drivers/BrowserFileTree.test.js"></script>
21
- <script type="module" src="../drivers/DeepMapTree.test.js"></script>
22
- <script type="module" src="../drivers/DeepObjectTree.test.js"></script>
23
- <script type="module" src="../drivers/DeferredTree.test.js"></script>
24
- <script type="module" src="../drivers/FunctionTree.test.js"></script>
25
- <script type="module" src="../drivers/MapTree.test.js"></script>
26
- <script type="module" src="../drivers/ObjectTree.test.js"></script>
27
- <script type="module" src="../drivers/SetTree.test.js"></script>
28
- <script type="module" src="../drivers/calendarTree.test.js"></script>
17
+ <script type="module" src="../drivers/AsyncMap.test.js"></script>
18
+ <script type="module" src="../drivers/BrowserFileMap.test.js"></script>
19
+ <script type="module" src="../drivers/CalendarMap.test.js"></script>
20
+ <script type="module" src="../drivers/ConstantMap.test.js"></script>
21
+ <script type="module" src="../drivers/DeepObjectMap.test.js"></script>
22
+ <script type="module" src="../drivers/FunctionMap.test.js"></script>
23
+ <script type="module" src="../drivers/ObjectMap.test.js"></script>
24
+ <script type="module" src="../drivers/SetMap.test.js"></script>
25
+ <script type="module" src="../drivers/SyncMap.test.js"></script>
29
26
  <script type="module" src="../operations/addNextPrevious.test.js"></script>
27
+ <script type="module" src="../operations/assign.test.js"></script>
30
28
  <script type="module" src="../operations/cache.test.js"></script>
31
- <script type="module" src="../operations/cache.test.js"></script>
32
- <script type="module" src="../operations/cachedKeyFunctions.test.js"></script>
33
- <script type="module" src="../operations/concat.test.js"></script>
29
+ <script
30
+ type="module"
31
+ src="../operations/cachedKeyFunctions.test.js"
32
+ ></script>
33
+ <script type="module" src="../operations/clear.test.js"></script>
34
+ <script type="module" src="../operations/deepEntries.test.js"></script>
34
35
  <script type="module" src="../operations/deepMerge.test.js"></script>
35
36
  <script type="module" src="../operations/deepReverse.test.js"></script>
36
37
  <script type="module" src="../operations/deepTake.test.js"></script>
37
38
  <script type="module" src="../operations/deepText.test.js"></script>
38
39
  <script type="module" src="../operations/deepValues.test.js"></script>
39
- <script type="module" src="../operations/deepValuesIterator.test.js"></script>
40
- <script type="module" src="../operations/extensionKeyFunctions.test.js"></script>
40
+ <script
41
+ type="module"
42
+ src="../operations/deepValuesIterator.test.js"
43
+ ></script>
44
+ <script type="module" src="../operations/delete.test.js"></script>
45
+ <script type="module" src="../operations/entries.test.js"></script>
46
+ <script
47
+ type="module"
48
+ src="../operations/extensionKeyFunctions.test.js"
49
+ ></script>
41
50
  <script type="module" src="../operations/filter.test.js"></script>
51
+ <script type="module" src="../operations/first.test.js"></script>
52
+ <script type="module" src="../operations/forEach.test.js"></script>
53
+ <script type="module" src="../operations/from.test.js"></script>
42
54
  <script type="module" src="../operations/globKeys.test.js"></script>
43
- <script type="module" src="../operations/group.test.js"></script>
55
+ <script type="module" src="../operations/groupBy.test.js"></script>
56
+ <script type="module" src="../operations/groupBy.test.js"></script>
57
+ <script type="module" src="../operations/has.test.js"></script>
58
+ <script type="module" src="../operations/indent.test.js"></script>
59
+ <script type="module" src="../operations/inners.test.js"></script>
44
60
  <script type="module" src="../operations/invokeFunctions.test.js"></script>
61
+ <script type="module" src="../operations/isMap.test.js"></script>
62
+ <script type="module" src="../operations/isMaplike.test.js"></script>
63
+ <script type="module" src="../operations/json.test.js"></script>
64
+ <script type="module" src="../operations/keys.test.js"></script>
65
+ <script type="module" src="../operations/length.test.js"></script>
45
66
  <script type="module" src="../operations/map.test.js"></script>
67
+ <script type="module" src="../operations/mapExtension.test.js"></script>
68
+ <script type="module" src="../operations/mapReduce.test.js"></script>
46
69
  <script type="module" src="../operations/mask.test.js"></script>
70
+ <script type="module" src="../operations/match.test.js"></script>
47
71
  <script type="module" src="../operations/merge.test.js"></script>
48
72
  <script type="module" src="../operations/paginate.test.js"></script>
49
73
  <script type="module" src="../operations/parseExtensions.test.js"></script>
74
+ <script type="module" src="../operations/paths.test.js"></script>
75
+ <script type="module" src="../operations/plain.test.js"></script>
50
76
  <script type="module" src="../operations/regExpKeys.test.js"></script>
51
77
  <script type="module" src="../operations/reverse.test.js"></script>
52
78
  <script type="module" src="../operations/scope.test.js"></script>
79
+ <script type="module" src="../operations/shuffle.test.js"></script>
53
80
  <script type="module" src="../operations/sort.test.js"></script>
81
+ <script type="module" src="../operations/sync.test.js"></script>
54
82
  <script type="module" src="../operations/take.test.js"></script>
55
- <script type="module" src="../utilities.test.js"></script>
83
+ <script type="module" src="../operations/text.test.js"></script>
84
+ <script type="module" src="../operations/toFunction.test.js"></script>
85
+ <script type="module" src="../operations/traverse.test.js"></script>
86
+ <script type="module" src="../operations/traversePath.test.js"></script>
87
+ <script type="module" src="../operations/values.test.js"></script>
88
+ <script type="module" src="../operations/withKeys.test.js"></script>
56
89
  </head>
57
90
  <body></body>
58
91
  </html>