@weborigami/async-tree 0.5.3 → 0.5.5

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 (159) hide show
  1. package/index.ts +16 -6
  2. package/package.json +2 -2
  3. package/shared.js +20 -30
  4. package/src/Tree.js +62 -502
  5. package/src/constants.js +2 -0
  6. package/src/drivers/BrowserFileTree.js +9 -10
  7. package/src/drivers/DeepMapTree.js +3 -3
  8. package/src/drivers/DeepObjectTree.js +4 -5
  9. package/src/drivers/DeferredTree.js +2 -2
  10. package/src/drivers/FileTree.js +11 -33
  11. package/src/drivers/FunctionTree.js +1 -1
  12. package/src/drivers/MapTree.js +6 -6
  13. package/src/drivers/ObjectTree.js +4 -3
  14. package/src/drivers/SetTree.js +1 -1
  15. package/src/drivers/SiteTree.js +1 -1
  16. package/src/drivers/constantTree.js +1 -1
  17. package/src/extension.js +5 -3
  18. package/src/jsonKeys.js +5 -7
  19. package/src/operations/addNextPrevious.js +10 -9
  20. package/src/operations/assign.js +40 -0
  21. package/src/operations/cache.js +18 -12
  22. package/src/operations/cachedKeyFunctions.js +15 -4
  23. package/src/operations/clear.js +20 -0
  24. package/src/operations/concat.js +17 -0
  25. package/src/operations/deepMap.js +25 -0
  26. package/src/operations/deepMerge.js +11 -25
  27. package/src/operations/deepReverse.js +6 -7
  28. package/src/operations/deepTake.js +6 -7
  29. package/src/operations/deepText.js +4 -4
  30. package/src/operations/deepValuesIterator.js +8 -6
  31. package/src/operations/defineds.js +32 -0
  32. package/src/operations/delete.js +20 -0
  33. package/src/operations/entries.js +16 -0
  34. package/src/operations/extensionKeyFunctions.js +1 -1
  35. package/src/operations/filter.js +7 -8
  36. package/src/operations/first.js +18 -0
  37. package/src/operations/forEach.js +20 -0
  38. package/src/operations/from.js +77 -0
  39. package/src/operations/fromFn.js +26 -0
  40. package/src/operations/globKeys.js +8 -8
  41. package/src/operations/group.js +9 -7
  42. package/src/operations/has.js +16 -0
  43. package/src/operations/indent.js +4 -2
  44. package/src/operations/inners.js +29 -0
  45. package/src/operations/invokeFunctions.js +5 -4
  46. package/src/operations/isAsyncMutableTree.js +15 -0
  47. package/src/operations/isAsyncTree.js +21 -0
  48. package/src/operations/isTraversable.js +15 -0
  49. package/src/operations/isTreelike.js +33 -0
  50. package/src/operations/json.js +4 -3
  51. package/src/operations/keys.js +14 -0
  52. package/src/operations/length.js +15 -0
  53. package/src/operations/map.js +157 -95
  54. package/src/operations/mapExtension.js +27 -0
  55. package/src/operations/mapReduce.js +44 -0
  56. package/src/operations/mask.js +18 -16
  57. package/src/operations/match.js +74 -0
  58. package/src/operations/merge.js +22 -20
  59. package/src/operations/paginate.js +3 -5
  60. package/src/operations/parent.js +13 -0
  61. package/src/operations/paths.js +51 -0
  62. package/src/operations/plain.js +34 -0
  63. package/src/operations/regExpKeys.js +4 -5
  64. package/src/operations/remove.js +14 -0
  65. package/src/operations/reverse.js +4 -6
  66. package/src/operations/root.js +17 -0
  67. package/src/operations/scope.js +4 -6
  68. package/src/operations/setDeep.js +50 -0
  69. package/src/operations/shuffle.js +46 -0
  70. package/src/operations/sort.js +19 -12
  71. package/src/operations/take.js +3 -5
  72. package/src/operations/text.js +3 -3
  73. package/src/operations/toFunction.js +14 -0
  74. package/src/operations/traverse.js +25 -0
  75. package/src/operations/traverseOrThrow.js +64 -0
  76. package/src/operations/traversePath.js +16 -0
  77. package/src/operations/values.js +15 -0
  78. package/src/operations/withKeys.js +33 -0
  79. package/src/utilities/TypedArray.js +2 -0
  80. package/src/utilities/box.js +20 -0
  81. package/src/utilities/castArraylike.js +38 -0
  82. package/src/utilities/getParent.js +33 -0
  83. package/src/utilities/getRealmObjectPrototype.js +19 -0
  84. package/src/utilities/getTreeArgument.js +43 -0
  85. package/src/utilities/isPacked.js +20 -0
  86. package/src/utilities/isPlainObject.js +29 -0
  87. package/src/utilities/isPrimitive.js +13 -0
  88. package/src/utilities/isStringlike.js +25 -0
  89. package/src/utilities/isUnpackable.js +13 -0
  90. package/src/utilities/keysFromPath.js +34 -0
  91. package/src/utilities/naturalOrder.js +9 -0
  92. package/src/utilities/pathFromKeys.js +18 -0
  93. package/src/utilities/setParent.js +38 -0
  94. package/src/utilities/toFunction.js +41 -0
  95. package/src/utilities/toPlainValue.js +95 -0
  96. package/src/utilities/toString.js +37 -0
  97. package/test/drivers/ExplorableSiteTree.test.js +1 -1
  98. package/test/drivers/FileTree.test.js +1 -1
  99. package/test/drivers/calendarTree.test.js +1 -1
  100. package/test/jsonKeys.test.js +1 -1
  101. package/test/operations/assign.test.js +54 -0
  102. package/test/operations/cache.test.js +1 -1
  103. package/test/operations/cachedKeyFunctions.test.js +16 -16
  104. package/test/operations/clear.test.js +34 -0
  105. package/test/operations/deepMap.test.js +29 -0
  106. package/test/operations/deepMerge.test.js +2 -6
  107. package/test/operations/deepReverse.test.js +1 -1
  108. package/test/operations/defineds.test.js +25 -0
  109. package/test/operations/delete.test.js +20 -0
  110. package/test/operations/entries.test.js +18 -0
  111. package/test/operations/extensionKeyFunctions.test.js +10 -10
  112. package/test/operations/first.test.js +15 -0
  113. package/test/operations/fixtures/README.md +1 -0
  114. package/test/operations/forEach.test.js +22 -0
  115. package/test/operations/from.test.js +67 -0
  116. package/test/operations/globKeys.test.js +3 -3
  117. package/test/operations/has.test.js +15 -0
  118. package/test/operations/inners.test.js +30 -0
  119. package/test/operations/invokeFunctions.test.js +1 -1
  120. package/test/operations/isAsyncMutableTree.test.js +17 -0
  121. package/test/operations/isAsyncTree.test.js +26 -0
  122. package/test/operations/isTreelike.test.js +13 -0
  123. package/test/operations/keys.test.js +15 -0
  124. package/test/operations/length.test.js +15 -0
  125. package/test/operations/map.test.js +62 -45
  126. package/test/operations/mapExtension.test.js +0 -0
  127. package/test/operations/mapReduce.test.js +23 -0
  128. package/test/operations/mask.test.js +1 -1
  129. package/test/operations/match.test.js +33 -0
  130. package/test/operations/merge.test.js +23 -9
  131. package/test/operations/paginate.test.js +2 -1
  132. package/test/operations/parent.test.js +15 -0
  133. package/test/operations/paths.test.js +40 -0
  134. package/test/operations/plain.test.js +69 -0
  135. package/test/operations/reverse.test.js +1 -1
  136. package/test/operations/scope.test.js +1 -1
  137. package/test/operations/setDeep.test.js +53 -0
  138. package/test/operations/shuffle.test.js +18 -0
  139. package/test/operations/sort.test.js +3 -3
  140. package/test/operations/toFunction.test.js +16 -0
  141. package/test/operations/traverse.test.js +43 -0
  142. package/test/operations/traversePath.test.js +16 -0
  143. package/test/operations/values.test.js +18 -0
  144. package/test/operations/withKeys.test.js +21 -0
  145. package/test/utilities/box.test.js +26 -0
  146. package/test/utilities/getRealmObjectPrototype.test.js +11 -0
  147. package/test/utilities/isPlainObject.test.js +13 -0
  148. package/test/utilities/keysFromPath.test.js +14 -0
  149. package/test/utilities/naturalOrder.test.js +11 -0
  150. package/test/utilities/pathFromKeys.test.js +12 -0
  151. package/test/utilities/setParent.test.js +34 -0
  152. package/test/utilities/toFunction.test.js +34 -0
  153. package/test/utilities/toPlainValue.test.js +27 -0
  154. package/test/utilities/toString.test.js +22 -0
  155. package/src/Tree.d.ts +0 -24
  156. package/src/utilities.d.ts +0 -21
  157. package/src/utilities.js +0 -439
  158. package/test/Tree.test.js +0 -407
  159. package/test/utilities.test.js +0 -141
@@ -0,0 +1,51 @@
1
+ import * as trailingSlash from "../trailingSlash.js";
2
+ import from from "./from.js";
3
+ import isAsyncTree from "./isAsyncTree.js";
4
+
5
+ /**
6
+ * Returns slash-separated paths for all values in the tree.
7
+ *
8
+ * The `base` argument is prepended to all paths.
9
+ *
10
+ * If `assumeSlashes` is true, then keys are assumed to have trailing slashes to
11
+ * indicate subtrees. The default value of this option is false.
12
+ *
13
+ * @typedef {import("../../index.ts").Treelike} Treelike
14
+ *
15
+ * @param {Treelike} treelike
16
+ * @param {{ assumeSlashes?: boolean, base?: string }} options
17
+ */
18
+ export default async function paths(treelike, options = {}) {
19
+ const tree = from(treelike);
20
+ const base = options.base ?? "";
21
+ const assumeSlashes = options.assumeSlashes ?? false;
22
+ const result = [];
23
+ for (const key of await tree.keys()) {
24
+ const separator = trailingSlash.has(base) ? "" : "/";
25
+ const valuePath = base ? `${base}${separator}${key}` : key;
26
+ let isSubtree;
27
+ let value;
28
+ if (assumeSlashes) {
29
+ // Subtree needs to have a trailing slash
30
+ isSubtree = trailingSlash.has(key);
31
+ if (isSubtree) {
32
+ // We'll need the value to recurse
33
+ value = await tree.get(key);
34
+ }
35
+ } else {
36
+ // Get value and check
37
+ value = await tree.get(key);
38
+ }
39
+ if (value) {
40
+ // If we got the value we can check if it's a subtree
41
+ isSubtree = isAsyncTree(value);
42
+ }
43
+ if (isSubtree) {
44
+ const subPaths = await paths(value, { assumeSlashes, base: valuePath });
45
+ result.push(...subPaths);
46
+ } else {
47
+ result.push(valuePath);
48
+ }
49
+ }
50
+ return result;
51
+ }
@@ -0,0 +1,34 @@
1
+ import getTreeArgument from "../utilities/getTreeArgument.js";
2
+ import toPlainValue from "../utilities/toPlainValue.js";
3
+
4
+ /**
5
+ * Converts an asynchronous tree into a synchronous plain JavaScript object.
6
+ *
7
+ * The result's keys will be the tree's keys cast to strings. Any trailing
8
+ * slashes in keys will be removed.
9
+ *
10
+ * Any tree value that is itself a tree will be recursively converted to a plain
11
+ * object.
12
+ *
13
+ * If the tree is array-like (its keys are integers and fill the range
14
+ * 0..length-1), then the result will be an array. The order of the keys will
15
+ * determine the order of the values in the array -- but the numeric value of
16
+ * the keys will be ignored.
17
+ *
18
+ * For example, a tree like `{ 1: "b", 0: "a", 2: "c" }` is array-like because
19
+ * its keys are all integers and fill the range 0..2. The result will be the
20
+ * array `["b", "a", "c" ]` because the tree has the keys in that order. The
21
+ * specific values of the keys (0, 1, and 2) are ignored.
22
+ *
23
+ * @typedef {import("../../index.ts").Treelike} Treelike
24
+ * @typedef {import("../../index.ts").PlainObject} PlainObject
25
+ *
26
+ * @param {Treelike} treelike
27
+ */
28
+ export default async function plain(treelike) {
29
+ if (treelike instanceof Function) {
30
+ throw new TypeError("plain: can't convert a function to a plain object");
31
+ }
32
+ const tree = await getTreeArgument(treelike, "plain");
33
+ return toPlainValue(tree);
34
+ }
@@ -1,6 +1,6 @@
1
- import { Tree } from "../internal.js";
2
1
  import * as trailingSlash from "../trailingSlash.js";
3
- import { assertIsTreelike } from "../utilities.js";
2
+ import getTreeArgument from "../utilities/getTreeArgument.js";
3
+ import isAsyncTree from "./isAsyncTree.js";
4
4
 
5
5
  /**
6
6
  * A tree whose keys are strings interpreted as regular expressions.
@@ -13,8 +13,7 @@ import { assertIsTreelike } from "../utilities.js";
13
13
  * @type {import("../../index.ts").TreeTransform}
14
14
  */
15
15
  export default async function regExpKeys(treelike) {
16
- assertIsTreelike(treelike, "regExpKeys");
17
- const tree = Tree.from(treelike);
16
+ const tree = await getTreeArgument(treelike, "regExpKeys");
18
17
 
19
18
  const map = new Map();
20
19
 
@@ -57,7 +56,7 @@ export default async function regExpKeys(treelike) {
57
56
  let value = await tree.get(key);
58
57
 
59
58
  let regExp;
60
- if (trailingSlash.has(key) || Tree.isAsyncTree(value)) {
59
+ if (trailingSlash.has(key) || isAsyncTree(value)) {
61
60
  const baseKey = trailingSlash.remove(key);
62
61
  regExp = new RegExp("^" + baseKey + "/?$");
63
62
  // Subtree
@@ -0,0 +1,14 @@
1
+ import { default as del } from "./delete.js";
2
+
3
+ /**
4
+ * Removes the value for the given key from the specific node of the tree.
5
+ *
6
+ * @typedef {import("@weborigami/types").AsyncMutableTree} AsyncMutableTree
7
+ *
8
+ * @param {AsyncMutableTree} tree
9
+ * @param {any} key
10
+ */
11
+ export default async function remove(tree, key) {
12
+ console.warn("`Tree.remove` is deprecated. Use `Tree.delete` instead.");
13
+ return del(tree, key);
14
+ }
@@ -1,5 +1,4 @@
1
- import { Tree } from "../internal.js";
2
- import { assertIsTreelike } from "../utilities.js";
1
+ import getTreeArgument from "../utilities/getTreeArgument.js";
3
2
 
4
3
  /**
5
4
  * Reverse the order of the top-level keys in the tree.
@@ -8,11 +7,10 @@ import { assertIsTreelike } from "../utilities.js";
8
7
  * @typedef {import("../../index.ts").Treelike} Treelike
9
8
  *
10
9
  * @param {Treelike} treelike
11
- * @returns {AsyncTree}
10
+ * @returns {Promise<AsyncTree>}
12
11
  */
13
- export default function reverse(treelike) {
14
- assertIsTreelike(treelike, "reverse");
15
- const tree = Tree.from(treelike);
12
+ export default async function reverse(treelike) {
13
+ const tree = await getTreeArgument(treelike, "reverse");
16
14
 
17
15
  return {
18
16
  async get(key) {
@@ -0,0 +1,17 @@
1
+ import * as symbols from "../symbols.js";
2
+ import from from "./from.js";
3
+
4
+ /**
5
+ * Walk up the `parent` chain to find the root of the tree.
6
+ *
7
+ * @typedef {import("../../index.ts").Treelike} Treelike
8
+ *
9
+ * @param {Treelike} treelike
10
+ */
11
+ export default function root(treelike) {
12
+ let current = from(treelike);
13
+ while (current.parent || current[symbols.parent]) {
14
+ current = current.parent || current[symbols.parent];
15
+ }
16
+ return current;
17
+ }
@@ -1,5 +1,4 @@
1
- import { Tree } from "../internal.js";
2
- import { assertIsTreelike } from "../utilities.js";
1
+ import getTreeArgument from "../utilities/getTreeArgument.js";
3
2
 
4
3
  /**
5
4
  * A tree's "scope" is the collection of everything in that tree and all of its
@@ -9,11 +8,10 @@ import { assertIsTreelike } from "../utilities.js";
9
8
  * @typedef {import("../../index.ts").Treelike} Treelike
10
9
  *
11
10
  * @param {Treelike} treelike
12
- * @returns {AsyncTree & {trees: AsyncTree[]}}
11
+ * @returns {Promise<AsyncTree & {trees: AsyncTree[]}>}
13
12
  */
14
- export default function scope(treelike) {
15
- assertIsTreelike(treelike, "scope");
16
- const tree = Tree.from(treelike);
13
+ export default async function scope(treelike) {
14
+ const tree = await getTreeArgument(treelike, "scope");
17
15
 
18
16
  return {
19
17
  // Starting with this tree, search up the parent hierarchy.
@@ -0,0 +1,50 @@
1
+ import from from "./from.js";
2
+ import isAsyncTree from "./isAsyncTree.js";
3
+
4
+ /**
5
+ * @typedef {import("@weborigami/async-tree").Treelike} Treelike
6
+ *
7
+ * @param {Treelike} target
8
+ * @param {Treelike} source
9
+ */
10
+ export default async function setDeep(target, source) {
11
+ console.warn("Tree.setDeep is deprecated, use Tree.assign instead.");
12
+ const targetTree = from(target);
13
+ const sourceTree = from(source);
14
+ await applyUpdates(sourceTree, targetTree);
15
+ }
16
+
17
+ // Apply all updates from the source to the target.
18
+ async function applyUpdates(source, target) {
19
+ // Fire off requests to update all keys, then wait for all of them to finish.
20
+ const promises = [];
21
+ for (const key of await source.keys()) {
22
+ const updateKeyPromise = applyUpdateForKey(source, target, key);
23
+ promises.push(updateKeyPromise);
24
+ }
25
+ await Promise.all(promises);
26
+
27
+ // HACK: Transforms like KeysTransform that maintain caches will need to
28
+ // recalculate things now that updates have been applied. This should be an
29
+ // automatic part of calling set() -- but triggering those changes inside
30
+ // set() produces cases where set() and get() calls can be interleaved. The
31
+ // atomicity of set() needs to be reconsidered. For now, we work around the
32
+ // problem by triggering `onChange` after the updates have been applied.
33
+ target.onChange?.();
34
+ }
35
+
36
+ // Copy the value for the given key from the source to the target.
37
+ async function applyUpdateForKey(source, target, key) {
38
+ const sourceValue = await source.get(key);
39
+ if (isAsyncTree(sourceValue)) {
40
+ const targetValue = await target.get(key);
41
+ if (isAsyncTree(targetValue)) {
42
+ // Both source and target are async dictionaries; recurse.
43
+ await applyUpdates(sourceValue, targetValue);
44
+ return;
45
+ }
46
+ }
47
+
48
+ // Copy the value from the source to the target.
49
+ await target.set(key, sourceValue);
50
+ }
@@ -0,0 +1,46 @@
1
+ import getTreeArgument from "../utilities/getTreeArgument.js";
2
+
3
+ /**
4
+ * Return a new tree with the original's keys shuffled
5
+ *
6
+ * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
7
+ * @typedef {import("../../index.ts").Treelike} Treelike
8
+ *
9
+ * @param {Treelike} treelike
10
+ * @param {boolean?} reshuffle
11
+ * @returns {Promise<AsyncTree>}
12
+ */
13
+ export default async function shuffle(treelike, reshuffle = false) {
14
+ const tree = await getTreeArgument(treelike, "shuffle");
15
+
16
+ let keys;
17
+
18
+ return {
19
+ async get(key) {
20
+ return tree.get(key);
21
+ },
22
+
23
+ async keys() {
24
+ if (!keys || reshuffle) {
25
+ keys = Array.from(await tree.keys());
26
+ shuffleArray(keys);
27
+ }
28
+ return keys;
29
+ },
30
+ };
31
+ }
32
+
33
+ /*
34
+ * Shuffle an array.
35
+ *
36
+ * Performs a Fisher-Yates shuffle. From http://sedition.com/perl/javascript-fy.html
37
+ */
38
+ export function shuffleArray(array) {
39
+ let i = array.length;
40
+ while (--i >= 0) {
41
+ const j = Math.floor(Math.random() * (i + 1));
42
+ const temp = array[i];
43
+ array[i] = array[j];
44
+ array[j] = temp;
45
+ }
46
+ }
@@ -1,5 +1,4 @@
1
- import { Tree } from "../internal.js";
2
- import { assertIsTreelike } from "../utilities.js";
1
+ import getTreeArgument from "../utilities/getTreeArgument.js";
3
2
 
4
3
  /**
5
4
  * Return a new tree with the original's keys sorted. A comparison function can
@@ -8,18 +7,25 @@ import { assertIsTreelike } from "../utilities.js";
8
7
  *
9
8
  * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
10
9
  * @typedef {(key: any, tree: AsyncTree) => any} SortKeyFn
11
- * @typedef {{ compare?: (a: any, b: any) => number, sortKey?: SortKeyFn }}
12
- * SortOptions
10
+ * @typedef {{ compare?: (a: any, b: any) => number, sortKey?: SortKeyFn }} SortOptions
11
+ * @typedef {import("../../index.ts").Treelike} Treelike
12
+ * @typedef {import("../../index.ts").ValueKeyFn} ValueKeyFn
13
13
  *
14
- * @param {import("../../index.ts").Treelike} treelike
15
- * @param {SortOptions} [options]
14
+ * @param {Treelike} treelike
15
+ * @param {SortOptions|ValueKeyFn} [options]
16
16
  */
17
- export default function sort(treelike, options) {
18
- assertIsTreelike(treelike, "sort");
19
- const tree = Tree.from(treelike);
17
+ export default async function sort(treelike, options) {
18
+ const tree = await getTreeArgument(treelike, "sort");
20
19
 
21
- const sortKey = options?.sortKey;
22
- let compare = options?.compare;
20
+ let sortKey;
21
+ let compare;
22
+ if (options instanceof Function) {
23
+ // Take the function as the `sortKey` option
24
+ sortKey = options;
25
+ } else {
26
+ compare = options?.compare;
27
+ sortKey = options?.sortKey;
28
+ }
23
29
 
24
30
  const transformed = Object.create(tree);
25
31
  transformed.keys = async () => {
@@ -30,7 +36,8 @@ export default function sort(treelike, options) {
30
36
  // Create { key, sortKey } tuples.
31
37
  const tuples = await Promise.all(
32
38
  keys.map(async (key) => {
33
- const sort = await sortKey(key, tree);
39
+ const value = await tree.get(key);
40
+ const sort = await sortKey(value, key, tree);
34
41
  if (sort === undefined) {
35
42
  throw new Error(
36
43
  `sortKey function returned undefined for key ${key}`
@@ -1,5 +1,4 @@
1
- import { Tree } from "../internal.js";
2
- import { assertIsTreelike } from "../utilities.js";
1
+ import getTreeArgument from "../utilities/getTreeArgument.js";
3
2
 
4
3
  /**
5
4
  * Returns a new tree with the number of keys limited to the indicated count.
@@ -7,9 +6,8 @@ import { assertIsTreelike } from "../utilities.js";
7
6
  * @param {import("../../index.ts").Treelike} treelike
8
7
  * @param {number} count
9
8
  */
10
- export default function take(treelike, count) {
11
- assertIsTreelike(treelike, "take");
12
- const tree = Tree.from(treelike);
9
+ export default async function take(treelike, count) {
10
+ const tree = await getTreeArgument(treelike, "take");
13
11
 
14
12
  return {
15
13
  async keys() {
@@ -1,6 +1,6 @@
1
- import { Tree } from "../internal.js";
2
- import { toString } from "../utilities.js";
1
+ import toString from "../utilities/toString.js";
3
2
  import deepText from "./deepText.js";
3
+ import isTreelike from "./isTreelike.js";
4
4
 
5
5
  /**
6
6
  * A tagged template literal function that concatenate the deep text values in a
@@ -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
- Tree.isTreelike(value) ? deepText(value) : toString(value)
16
+ isTreelike(value) ? deepText(value) : toString(value)
17
17
  )
18
18
  );
19
19
  // Splice all the strings together
@@ -0,0 +1,14 @@
1
+ import getTreeArgument from "../utilities/getTreeArgument.js";
2
+
3
+ /**
4
+ * Returns a function that invokes the tree's `get` method.
5
+ *
6
+ * @typedef {import("../../index.ts").Treelike} Treelike
7
+ *
8
+ * @param {Treelike} treelike
9
+ * @returns {Promise<Function>}
10
+ */
11
+ export default async function toFunction(treelike) {
12
+ const tree = await getTreeArgument(treelike, "toFunction");
13
+ return tree.get.bind(tree);
14
+ }
@@ -0,0 +1,25 @@
1
+ import TraverseError from "../TraverseError.js";
2
+ import traverseOrThrow from "./traverseOrThrow.js";
3
+
4
+ /**
5
+ * Return the value at the corresponding path of keys.
6
+ *
7
+ * @typedef {import("../../index.ts").Treelike} Treelike
8
+ *
9
+ * @this {any}
10
+ * @param {Treelike} treelike
11
+ * @param {...any} keys
12
+ */
13
+ export default async function traverse(treelike, ...keys) {
14
+ try {
15
+ // Await the result here so that, if the path doesn't exist, the catch
16
+ // block below will catch the exception.
17
+ return await traverseOrThrow.call(this, treelike, ...keys);
18
+ } catch (/** @type {any} */ error) {
19
+ if (error instanceof TraverseError) {
20
+ return undefined;
21
+ } else {
22
+ throw error;
23
+ }
24
+ }
25
+ }
@@ -0,0 +1,64 @@
1
+ import TraverseError from "../TraverseError.js";
2
+ import isUnpackable from "../utilities/isUnpackable.js";
3
+ import from from "./from.js";
4
+
5
+ /**
6
+ * Return the value at the corresponding path of keys. Throw if any interior
7
+ * step of the path doesn't lead to a result.
8
+ *
9
+ * @typedef {import("../../index.ts").Treelike} Treelike
10
+ * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
11
+ *
12
+ * @this {any}
13
+ * @param {Treelike} treelike
14
+ * @param {...any} keys
15
+ */
16
+ export default async function traverseOrThrow(treelike, ...keys) {
17
+ // Start our traversal at the root of the tree.
18
+ /** @type {any} */
19
+ let value = treelike;
20
+ let position = 0;
21
+
22
+ // If traversal operation was called with a `this` context, use that as the
23
+ // target for function calls.
24
+ const target = this;
25
+
26
+ // Process all the keys.
27
+ const remainingKeys = keys.slice();
28
+ let key;
29
+ while (remainingKeys.length > 0) {
30
+ if (value == null) {
31
+ throw new TraverseError("A null or undefined value can't be traversed", {
32
+ tree: treelike,
33
+ keys,
34
+ position,
35
+ });
36
+ }
37
+
38
+ // If the value is packed and can be unpacked, unpack it.
39
+ if (isUnpackable(value)) {
40
+ value = await value.unpack();
41
+ }
42
+
43
+ if (value instanceof Function) {
44
+ // Value is a function: call it with the remaining keys.
45
+ const fn = value;
46
+ // We'll take as many keys as the function's length, but at least one.
47
+ let fnKeyCount = Math.max(fn.length, 1);
48
+ const args = remainingKeys.splice(0, fnKeyCount);
49
+ key = null;
50
+ value = await fn.call(target, ...args);
51
+ } else {
52
+ // Cast value to a tree.
53
+ const tree = from(value);
54
+ // Get the next key.
55
+ key = remainingKeys.shift();
56
+ // Get the value for the key.
57
+ value = await tree.get(key);
58
+ }
59
+
60
+ position++;
61
+ }
62
+
63
+ return value;
64
+ }
@@ -0,0 +1,16 @@
1
+ import keysFromPath from "../utilities/keysFromPath.js";
2
+ import traverse from "./traverse.js";
3
+
4
+ /**
5
+ * Given a slash-separated path like "foo/bar", traverse the keys "foo/" and
6
+ * "bar" and return the resulting value.
7
+ *
8
+ * @typedef {import("../../index.ts").Treelike} Treelike
9
+ *
10
+ * @param {Treelike} tree
11
+ * @param {string} path
12
+ */
13
+ export default async function traversePath(tree, path) {
14
+ const keys = keysFromPath(path);
15
+ return traverse(tree, ...keys);
16
+ }
@@ -0,0 +1,15 @@
1
+ import from from "./from.js";
2
+
3
+ /**
4
+ * Return the values in the specific node of the tree.
5
+ *
6
+ * @typedef {import("../../index.ts").Treelike} Treelike
7
+ *
8
+ * @param {Treelike} treelike
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);
15
+ }
@@ -0,0 +1,33 @@
1
+ import getTreeArgument from "../utilities/getTreeArgument.js";
2
+ import values from "./values.js";
3
+
4
+ /**
5
+ * Return a tree whose keys are provided by the _values_ of a second tree (e.g.,
6
+ * an array of keys).
7
+ *
8
+ * @typedef {import("../../index.ts").Treelike} Treelike
9
+ * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
10
+ *
11
+ * @param {Treelike} treelike
12
+ * @param {Treelike} keysTreelike
13
+ * @returns {Promise<AsyncTree>}
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", {
18
+ position: 1,
19
+ });
20
+
21
+ let keys;
22
+
23
+ return {
24
+ async get(key) {
25
+ return tree.get(key);
26
+ },
27
+
28
+ async keys() {
29
+ keys ??= await values(keysTree);
30
+ return keys;
31
+ },
32
+ };
33
+ }
@@ -0,0 +1,2 @@
1
+ const TypedArray = Object.getPrototypeOf(Uint8Array);
2
+ export default TypedArray;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Return the value as an object. If the value is already an object it will be
3
+ * returned as is. If the value is a primitive, it will be wrapped in an object:
4
+ * a string will be wrapped in a String object, a number will be wrapped in a
5
+ * Number object, and a boolean will be wrapped in a Boolean object.
6
+ *
7
+ * @param {any} value
8
+ */
9
+ export default function box(value) {
10
+ switch (typeof value) {
11
+ case "string":
12
+ return new String(value);
13
+ case "number":
14
+ return new Number(value);
15
+ case "boolean":
16
+ return new Boolean(value);
17
+ default:
18
+ return value;
19
+ }
20
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Create an array or plain object from the given keys and values.
3
+ *
4
+ * If the given plain object has only integer keys, and the set of integers is
5
+ * complete from 0 to length-1, assume the values are a result of array
6
+ * 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
+ * @param {any[]} keys
10
+ * @param {any[]} values
11
+ */
12
+ export default function castArraylike(keys, values) {
13
+ if (keys.length === 0) {
14
+ // Empty keys/values means an empty object, not an empty array
15
+ return {};
16
+ }
17
+
18
+ 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
+ onlyNumericKeys = false;
24
+ break;
25
+ } else {
26
+ numberSeen[n] = true;
27
+ }
28
+ }
29
+
30
+ // If any number from 0..length-1 is missing, we can't treat this as an array
31
+ const allNumbersSeen = onlyNumericKeys && numberSeen.every((v) => v);
32
+ if (allNumbersSeen) {
33
+ return values;
34
+ } else {
35
+ // Return a plain object with the (key, value) pairs
36
+ return Object.fromEntries(keys.map((key, i) => [key, values[i]]));
37
+ }
38
+ }
@@ -0,0 +1,33 @@
1
+ import * as symbols from "../symbols.js";
2
+
3
+ /**
4
+ * Return a suitable parent for the packed file.
5
+ *
6
+ * This is intended to be called by unpack functions.
7
+ *
8
+ * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
9
+ *
10
+ * @param {any} packed
11
+ * @param {any} [options]
12
+ * @returns {AsyncTree|null}
13
+ */
14
+ export default function getParent(packed, options = {}) {
15
+ // Prefer parent set on options
16
+ if (options?.parent) {
17
+ return options.parent;
18
+ }
19
+
20
+ // If the packed object has a `parent` property, use that. Exception: Node
21
+ // Buffer objects have a `parent` property that we ignore.
22
+ if (packed.parent && !(packed instanceof Buffer)) {
23
+ return packed.parent;
24
+ }
25
+
26
+ // If the packed object has a parent symbol, use that.
27
+ if (packed[symbols.parent]) {
28
+ return packed[symbols.parent];
29
+ }
30
+
31
+ // Otherwise, return null.
32
+ return null;
33
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Return the Object prototype at the root of the object's prototype chain.
3
+ *
4
+ * This is used by functions like isPlainObject() to handle cases where the
5
+ * `Object` at the root prototype chain is in a different realm.
6
+ *
7
+ * @param {any} object
8
+ */
9
+ export default function getRealmObjectPrototype(object) {
10
+ if (Object.getPrototypeOf(object) === null) {
11
+ // The object has no prototype.
12
+ return null;
13
+ }
14
+ let proto = object;
15
+ while (Object.getPrototypeOf(proto) !== null) {
16
+ proto = Object.getPrototypeOf(proto);
17
+ }
18
+ return proto;
19
+ }