@weborigami/async-tree 0.0.35

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 (57) hide show
  1. package/ReadMe.md +1 -0
  2. package/browser.js +13 -0
  3. package/index.ts +44 -0
  4. package/main.js +23 -0
  5. package/package.json +20 -0
  6. package/src/BrowserFileTree.js +137 -0
  7. package/src/DeferredTree.js +72 -0
  8. package/src/FileTree.js +193 -0
  9. package/src/FunctionTree.js +47 -0
  10. package/src/MapTree.js +51 -0
  11. package/src/ObjectTree.js +103 -0
  12. package/src/SetTree.js +40 -0
  13. package/src/SiteTree.js +124 -0
  14. package/src/Tree.d.ts +22 -0
  15. package/src/Tree.js +404 -0
  16. package/src/keysJson.d.ts +4 -0
  17. package/src/keysJson.js +56 -0
  18. package/src/operations/cache.js +75 -0
  19. package/src/operations/merge.js +60 -0
  20. package/src/operations/mergeDeep.js +47 -0
  21. package/src/transforms/cachedKeyMaps.js +86 -0
  22. package/src/transforms/groupBy.js +40 -0
  23. package/src/transforms/keyMapsForExtensions.js +64 -0
  24. package/src/transforms/map.js +106 -0
  25. package/src/transforms/regExpKeys.js +65 -0
  26. package/src/transforms/sort.js +22 -0
  27. package/src/transforms/sortBy.js +29 -0
  28. package/src/transforms/sortNatural.js +10 -0
  29. package/src/utilities.d.ts +10 -0
  30. package/src/utilities.js +124 -0
  31. package/test/BrowserFileTree.test.js +119 -0
  32. package/test/DeferredTree.test.js +23 -0
  33. package/test/FileTree.test.js +134 -0
  34. package/test/FunctionTree.test.js +51 -0
  35. package/test/MapTree.test.js +37 -0
  36. package/test/ObjectTree.test.js +159 -0
  37. package/test/SetTree.test.js +32 -0
  38. package/test/SiteTree.test.js +110 -0
  39. package/test/Tree.test.js +347 -0
  40. package/test/browser/assert.js +45 -0
  41. package/test/browser/index.html +35 -0
  42. package/test/browser/testRunner.js +51 -0
  43. package/test/fixtures/markdown/Alice.md +1 -0
  44. package/test/fixtures/markdown/Bob.md +1 -0
  45. package/test/fixtures/markdown/Carol.md +1 -0
  46. package/test/operations/cache.test.js +57 -0
  47. package/test/operations/merge.test.js +39 -0
  48. package/test/operations/mergeDeep.test.js +38 -0
  49. package/test/transforms/cachedKeyMaps.test.js +41 -0
  50. package/test/transforms/groupBy.test.js +29 -0
  51. package/test/transforms/keyMapsForExtensions.test.js +70 -0
  52. package/test/transforms/map.test.js +174 -0
  53. package/test/transforms/regExpKeys.test.js +25 -0
  54. package/test/transforms/sort.test.js +30 -0
  55. package/test/transforms/sortBy.test.js +22 -0
  56. package/test/transforms/sortNatural.test.js +21 -0
  57. package/test/utilities.test.js +24 -0
@@ -0,0 +1,56 @@
1
+ import * as Tree from "./Tree.js";
2
+
3
+ /**
4
+ * The .keys.json file format lets a site expose the keys of a node in the site
5
+ * so that they can be read by SiteTree.
6
+ *
7
+ * This file format is a JSON array of key descriptors: a string like
8
+ * "index.html" for a specific resource available at the node, or a string with
9
+ * a trailing slash like "about/" for a subtree of that node.
10
+ */
11
+
12
+ /**
13
+ * Parse the JSON in a .keys.json file.
14
+ *
15
+ * This returns a flat dictionary of flags which are true for subtrees and
16
+ * false otherwise.
17
+ *
18
+ * Example: the JSON `["index.html","about/"]` parses as:
19
+ *
20
+ * {
21
+ * "index.html": false,
22
+ * about: true,
23
+ * }
24
+ */
25
+ export function parse(json) {
26
+ const descriptors = JSON.parse(json);
27
+ const result = {};
28
+ for (const descriptor of descriptors) {
29
+ if (descriptor.endsWith("/")) {
30
+ result[descriptor.slice(0, -1)] = true;
31
+ } else {
32
+ result[descriptor] = false;
33
+ }
34
+ }
35
+ return result;
36
+ }
37
+
38
+ /**
39
+ * Given a tree node, return a JSON string that can be written to a .keys.json
40
+ * file.
41
+ */
42
+ export async function stringify(treelike) {
43
+ const tree = Tree.from(treelike);
44
+ const keyDescriptors = [];
45
+ for (const key of await tree.keys()) {
46
+ // Skip the key `.keys.json` if present.
47
+ if (key === ".keys.json") {
48
+ continue;
49
+ }
50
+ const isKeyForSubtree = await Tree.isKeyForSubtree(tree, key);
51
+ const keyDescriptor = isKeyForSubtree ? `${key}/` : key;
52
+ keyDescriptors.push(keyDescriptor);
53
+ }
54
+ const json = JSON.stringify(keyDescriptors);
55
+ return json;
56
+ }
@@ -0,0 +1,75 @@
1
+ import { ObjectTree, Tree } from "@weborigami/async-tree";
2
+
3
+ /**
4
+ * Caches values from a source tree in a second cache tree. If no second tree is
5
+ * supplied, an in-memory cache is used.
6
+ *
7
+ * An optional third filter tree can be supplied. If a filter tree is supplied,
8
+ * only values for keys that match the filter will be cached.
9
+ *
10
+ * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
11
+ * @typedef {import("@weborigami/types").AsyncMutableTree} AsyncMutableTree
12
+ *
13
+ * @param {AsyncTree} source
14
+ * @param {AsyncMutableTree} [cacheTree]
15
+ * @param {AsyncTree} [filter]
16
+ * @returns {AsyncTree & { description: string }}
17
+ */
18
+ export default function treeCache(source, cacheTree, filter) {
19
+ /** @type {AsyncMutableTree} */
20
+ const cache = cacheTree ?? new ObjectTree({});
21
+ return {
22
+ description: "cache",
23
+
24
+ async get(key) {
25
+ // Check cache tree first.
26
+ let cacheValue = await cache.get(key);
27
+ if (cacheValue !== undefined && !Tree.isAsyncTree(cacheValue)) {
28
+ // Leaf node cache hit
29
+ return cacheValue;
30
+ }
31
+
32
+ // Cache miss or interior node cache hit.
33
+ let value = await source.get(key);
34
+ if (value !== undefined) {
35
+ // Does this key match the filter?
36
+ const filterValue = filter ? await filter.get(key) : true;
37
+ const filterMatch = filterValue !== undefined;
38
+ if (filterMatch) {
39
+ if (Tree.isAsyncTree(value)) {
40
+ // Construct merged tree for a tree result.
41
+ if (cacheValue === undefined) {
42
+ // Construct new container in cache
43
+ await cache.set(key, {});
44
+ cacheValue = await cache.get(key);
45
+ }
46
+ value = treeCache(value, cacheValue, filterValue);
47
+ } else {
48
+ // Save in cache before returning.
49
+ await cache.set(key, value);
50
+ }
51
+ }
52
+
53
+ return value;
54
+ }
55
+
56
+ return undefined;
57
+ },
58
+
59
+ async isKeyForSubtree(key) {
60
+ return Tree.isKeyForSubtree(source, key);
61
+ },
62
+
63
+ async keys() {
64
+ const keys = new Set(await source.keys());
65
+
66
+ // We also add the cache's keys in case the keys provided by the source
67
+ // tree have changed since the cache was updated.
68
+ for (const key of await cache.keys()) {
69
+ keys.add(key);
70
+ }
71
+
72
+ return keys;
73
+ },
74
+ };
75
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Return a tree that performs a shallow merge of the given trees.
3
+ *
4
+ * Given a set of trees, the `get` method looks at each tree in turn. The first
5
+ * tree is asked for the value with the key. If an tree returns a defined value
6
+ * (i.e., not undefined), that value is returned. If the first tree returns
7
+ * undefined, the second tree will be asked, and so on. If none of the trees
8
+ * return a defined value, the `get` method returns undefined.
9
+ *
10
+ * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
11
+ * @returns {AsyncTree & { description: string }}
12
+ */
13
+ export default function merge(...sources) {
14
+ let trees = sources;
15
+ let mergeParent;
16
+ return {
17
+ description: "merge",
18
+
19
+ async get(key) {
20
+ for (const tree of trees) {
21
+ const value = await tree.get(key);
22
+ if (value !== undefined) {
23
+ return value;
24
+ }
25
+ }
26
+ return undefined;
27
+ },
28
+
29
+ async isKeyForSubtree(key) {
30
+ for (const tree of trees) {
31
+ if (await tree.isKeyForSubtree(key)) {
32
+ return true;
33
+ }
34
+ }
35
+ return false;
36
+ },
37
+
38
+ async keys() {
39
+ const keys = new Set();
40
+ for (const tree of trees) {
41
+ for (const key of await tree.keys()) {
42
+ keys.add(key);
43
+ }
44
+ }
45
+ return keys;
46
+ },
47
+
48
+ get parent() {
49
+ return mergeParent;
50
+ },
51
+ set parent(parent) {
52
+ mergeParent = parent;
53
+ trees = sources.map((source) => {
54
+ const tree = Object.create(source);
55
+ tree.parent = parent;
56
+ return tree;
57
+ });
58
+ },
59
+ };
60
+ }
@@ -0,0 +1,47 @@
1
+ import * as Tree from "../Tree.js";
2
+
3
+ /**
4
+ * Return a tree that performs a deep merge of the given trees.
5
+ *
6
+ * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
7
+ * @returns {AsyncTree & { description: string }}
8
+ */
9
+ export default function mergeDeep(...trees) {
10
+ return {
11
+ description: "mergeDeep",
12
+
13
+ async get(key) {
14
+ const subtrees = [];
15
+
16
+ for (const tree of trees) {
17
+ const value = await tree.get(key);
18
+ if (Tree.isAsyncTree(value)) {
19
+ subtrees.push(value);
20
+ } else if (value !== undefined) {
21
+ return value;
22
+ }
23
+ }
24
+
25
+ return subtrees.length > 0 ? mergeDeep(...subtrees) : undefined;
26
+ },
27
+
28
+ async isKeyForSubtree(key) {
29
+ for (const tree of trees) {
30
+ if (await tree.isKeyForSubtree(key)) {
31
+ return true;
32
+ }
33
+ }
34
+ return false;
35
+ },
36
+
37
+ async keys() {
38
+ const keys = new Set();
39
+ for (const tree of trees) {
40
+ for (const key of await tree.keys()) {
41
+ keys.add(key);
42
+ }
43
+ }
44
+ return keys;
45
+ },
46
+ };
47
+ }
@@ -0,0 +1,86 @@
1
+ import * as Tree from "../Tree.js";
2
+
3
+ const treeToCaches = new WeakMap();
4
+
5
+ /**
6
+ * Given a keyMap, return a new keyMap and inverseKeyMap that cache the results of
7
+ * the original keyMap.
8
+ *
9
+ * @typedef {import("../../index.ts").KeyFn} KeyFn
10
+ *
11
+ * @param {KeyFn} keyMap
12
+ * @param {boolean} [deep]
13
+ * @returns {{ keyMap: KeyFn, inverseKeyMap: KeyFn }}
14
+ */
15
+ export default function createCachedKeysTransform(keyMap, deep = false) {
16
+ return {
17
+ async inverseKeyMap(resultKey, tree) {
18
+ const caches = getCachesForTree(tree);
19
+
20
+ // First check to see if we've already computed an source key for this
21
+ // result key. Again, we have to use `has()` for this check.
22
+ if (caches.resultKeyToSourceKey.has(resultKey)) {
23
+ return caches.resultKeyToSourceKey.get(resultKey);
24
+ }
25
+
26
+ // Iterate through the tree's keys, calculating source keys as we go,
27
+ // until we find a match. Cache all the intermediate results and the
28
+ // final match. This is O(n), but we stop as soon as we find a match,
29
+ // and subsequent calls will benefit from the intermediate results.
30
+ for (const sourceKey of await tree.keys()) {
31
+ // Skip any source keys we already know about.
32
+ if (caches.sourceKeyToResultKey.has(sourceKey)) {
33
+ continue;
34
+ }
35
+
36
+ let computedResultKey;
37
+ if (deep && (await Tree.isKeyForSubtree(tree, sourceKey))) {
38
+ computedResultKey = sourceKey;
39
+ } else {
40
+ computedResultKey = await keyMap(sourceKey, tree);
41
+ }
42
+
43
+ caches.sourceKeyToResultKey.set(sourceKey, computedResultKey);
44
+ caches.resultKeyToSourceKey.set(computedResultKey, sourceKey);
45
+
46
+ if (computedResultKey === resultKey) {
47
+ // Match found.
48
+ return sourceKey;
49
+ }
50
+ }
51
+
52
+ return undefined;
53
+ },
54
+
55
+ async keyMap(sourceKey, tree) {
56
+ const keyMaps = getCachesForTree(tree);
57
+
58
+ // First check to see if we've already computed an result key for this
59
+ // source key. The cached result key may be undefined, so we have to use
60
+ // `has()` instead of calling `get()` and checking for undefined.
61
+ if (keyMaps.sourceKeyToResultKey.has(sourceKey)) {
62
+ return keyMaps.sourceKeyToResultKey.get(sourceKey);
63
+ }
64
+
65
+ const resultKey = await keyMap(sourceKey, tree);
66
+
67
+ // Cache the mappings from source key <-> result key for next time.
68
+ keyMaps.sourceKeyToResultKey.set(sourceKey, resultKey);
69
+ keyMaps.resultKeyToSourceKey.set(resultKey, sourceKey);
70
+
71
+ return resultKey;
72
+ },
73
+ };
74
+ }
75
+
76
+ function getCachesForTree(tree) {
77
+ let keyMaps = treeToCaches.get(tree);
78
+ if (!keyMaps) {
79
+ keyMaps = {
80
+ resultKeyToSourceKey: new Map(),
81
+ sourceKeyToResultKey: new Map(),
82
+ };
83
+ treeToCaches.set(tree, keyMaps);
84
+ }
85
+ return keyMaps;
86
+ }
@@ -0,0 +1,40 @@
1
+ import ObjectTree from "../ObjectTree.js";
2
+ import * as Tree from "../Tree.js";
3
+
4
+ /**
5
+ * Given a function that returns a grouping key for a value, returns a transform
6
+ * that applies that grouping function to a tree.
7
+ *
8
+ * @param {import("../../index.ts").ValueKeyFn} groupKeyFn
9
+ */
10
+ export default function createGroupByTransform(groupKeyFn) {
11
+ /**
12
+ * @type {import("../../index.ts").TreeTransform}
13
+ */
14
+ return async function groupByTransform(tree) {
15
+ const result = {};
16
+ for (const key of await tree.keys()) {
17
+ const value = await tree.get(key);
18
+
19
+ // Get the groups for this value.
20
+ let groups = await groupKeyFn(value, key, tree);
21
+ if (!groups) {
22
+ continue;
23
+ }
24
+
25
+ if (!Tree.isTreelike(groups)) {
26
+ // A single value was returned
27
+ groups = [groups];
28
+ }
29
+
30
+ // Add the value to each group.
31
+ for (const groupKey of await groups.keys()) {
32
+ const group = await groups.get(groupKey);
33
+ result[group] ??= [];
34
+ result[group].push(value);
35
+ }
36
+ }
37
+
38
+ return new ObjectTree(result);
39
+ };
40
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Given a source resultExtension and a result resultExtension, return a pair of key
3
+ * functions that map between them.
4
+ *
5
+ * The resulting `inverseKeyMap` and `keyMap` functions are compatible with those
6
+ * expected by map and other transforms.
7
+ *
8
+ * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
9
+ * @param {{ resultExtension?: string, sourceExtension: string }}
10
+ * options
11
+ */
12
+ export default function keyMapsForExtensions({
13
+ resultExtension,
14
+ sourceExtension,
15
+ }) {
16
+ if (!resultExtension) {
17
+ resultExtension = sourceExtension;
18
+ }
19
+
20
+ return {
21
+ async inverseKeyMap(resultKey, tree) {
22
+ const basename = matchExtension(resultKey, resultExtension);
23
+ return basename ? `${basename}${dotPrefix(sourceExtension)}` : undefined;
24
+ },
25
+
26
+ async keyMap(sourceKey, tree) {
27
+ const basename = matchExtension(sourceKey, sourceExtension);
28
+ return basename ? `${basename}${dotPrefix(resultExtension)}` : undefined;
29
+ },
30
+ };
31
+ }
32
+
33
+ function dotPrefix(resultExtension) {
34
+ return resultExtension ? `.${resultExtension}` : "";
35
+ }
36
+
37
+ /**
38
+ * See if the key ends with the given resultExtension. If it does, return the base
39
+ * name without the resultExtension; if it doesn't return null.
40
+ *
41
+ * An empty/null resultExtension means: match any key that does *not* contain a
42
+ * period.
43
+ *
44
+ * This uses a different, more general interpretation of "resultExtension" to mean any
45
+ * suffix, rather than Node's interpretation `path.extname`. In particular, this
46
+ * will match an "resultExtension" like ".foo.bar" that contains more than one dot.
47
+ */
48
+ function matchExtension(key, resultExtension) {
49
+ if (resultExtension) {
50
+ // Key matches if it ends with the same resultExtension
51
+ const dotExtension = dotPrefix(resultExtension);
52
+ if (
53
+ key.length > dotExtension.length &&
54
+ key.toLowerCase().endsWith(dotExtension)
55
+ ) {
56
+ return key.substring(0, key.length - dotExtension.length);
57
+ }
58
+ } else if (!key.includes?.(".")) {
59
+ // Key matches if it has no resultExtension
60
+ return key;
61
+ }
62
+ // Didn't match
63
+ return null;
64
+ }
@@ -0,0 +1,106 @@
1
+ import * as Tree from "../Tree.js";
2
+
3
+ /**
4
+ * Return a transform function that maps the keys and/or values of a tree.
5
+ *
6
+ * @typedef {import("../../index.ts").KeyFn} KeyFn
7
+ * @typedef {import("../../index.ts").ValueKeyFn} ValueKeyFn
8
+ *
9
+ * @param {ValueKeyFn|{ deep?: boolean, description?: string, inverseKeyMap?: KeyFn, keyMap?: KeyFn, valueMap?: ValueKeyFn }} options
10
+ */
11
+ export default function createMapTransform(options) {
12
+ let deep;
13
+ let description;
14
+ let inverseKeyMap;
15
+ let keyMap;
16
+ let valueMap;
17
+ if (typeof options === "function") {
18
+ // Take the single function argument as the valueMap
19
+ valueMap = options;
20
+ } else {
21
+ deep = options.deep ?? false;
22
+ description = options.description ?? "key/value map";
23
+ inverseKeyMap = options.inverseKeyMap;
24
+ keyMap = options.keyMap;
25
+ valueMap = options.valueMap;
26
+ }
27
+
28
+ if ((keyMap && !inverseKeyMap) || (!keyMap && inverseKeyMap)) {
29
+ throw new TypeError(
30
+ `map: You must specify both keyMap and inverseKeyMap, or neither.`
31
+ );
32
+ }
33
+
34
+ /**
35
+ * @type {import("../../index.ts").TreeTransform}
36
+ */
37
+ return function map(tree) {
38
+ // The transformed tree is actually an extension of the original tree's
39
+ // prototype chain. This allows the transformed tree to inherit any
40
+ // properties/methods that do not need to be specified. For example, the
41
+ // `parent` of the transformed tree is the original tree's parent.
42
+ const transformed = Object.create(tree);
43
+
44
+ transformed.description = description;
45
+
46
+ if (keyMap || valueMap) {
47
+ transformed.get = async (resultKey) => {
48
+ // Step 1: Map the result key to the source key.
49
+ const isSubtree = deep && (await Tree.isKeyForSubtree(tree, resultKey));
50
+ const sourceKey =
51
+ !isSubtree && inverseKeyMap
52
+ ? await inverseKeyMap(resultKey, tree)
53
+ : resultKey;
54
+
55
+ if (!sourceKey) {
56
+ // No source key means no value.
57
+ return undefined;
58
+ }
59
+
60
+ // Step 2: Get the source value.
61
+ const sourceValue = await tree.get(sourceKey);
62
+
63
+ // Step 3: Map the source value to the result value.
64
+ let resultValue;
65
+ if (sourceValue === undefined) {
66
+ // No source value means no result value.
67
+ resultValue = undefined;
68
+ } else if (deep && Tree.isAsyncTree(sourceValue)) {
69
+ // Map a subtree.
70
+ resultValue = map(sourceValue);
71
+ } else if (valueMap) {
72
+ // Map a single value.
73
+ resultValue = await valueMap(sourceValue, sourceKey, tree);
74
+ } else {
75
+ // Return source value as is.
76
+ resultValue = sourceValue;
77
+ }
78
+
79
+ return resultValue;
80
+ };
81
+ }
82
+
83
+ if (keyMap) {
84
+ transformed.keys = async () => {
85
+ // Apply the keyMap to source keys for leaf values (not subtrees).
86
+ const sourceKeys = [...(await tree.keys())];
87
+ const mapped = await Promise.all(
88
+ sourceKeys.map(async (sourceKey) => {
89
+ let resultKey;
90
+ if (deep && (await Tree.isKeyForSubtree(tree, sourceKey))) {
91
+ resultKey = sourceKey;
92
+ } else {
93
+ resultKey = await keyMap(sourceKey, tree);
94
+ }
95
+ return resultKey;
96
+ })
97
+ );
98
+ // Filter out any cases where the keyMap returned undefined.
99
+ const resultKeys = mapped.filter((key) => key !== undefined);
100
+ return resultKeys;
101
+ };
102
+ }
103
+
104
+ return transformed;
105
+ };
106
+ }
@@ -0,0 +1,65 @@
1
+ import * as Tree from "../Tree.js";
2
+
3
+ /**
4
+ * A tree whose keys are strings interpreted as regular expressions.
5
+ *
6
+ * Requests to `get` a key are matched against the regular expressions, and the
7
+ * value for the first matching key is returned. The regular expresions are
8
+ * taken to match the entire key -- if they do not already start and end with
9
+ * `^` and `$` respectively, those are added.
10
+ *
11
+ * @type {import("../../index.ts").TreeTransform}
12
+ */
13
+ export default async function regExpKeys(tree) {
14
+ const map = new Map();
15
+
16
+ // We build the output tree first so that we can refer to it when setting
17
+ // `parent` on subtrees below.
18
+ let result = {
19
+ // @ts-ignore
20
+ description: "regExpKeys",
21
+
22
+ async get(key) {
23
+ for (const [regExp, value] of map) {
24
+ if (regExp.test(key)) {
25
+ return value;
26
+ }
27
+ }
28
+ return undefined;
29
+ },
30
+
31
+ async keys() {
32
+ return [...map.keys()];
33
+ },
34
+ };
35
+
36
+ // Turn the input tree's string keys into regular expressions, then map those
37
+ // to the corresponding values.
38
+ for (const key of await tree.keys()) {
39
+ if (typeof key !== "string") {
40
+ // Skip non-string keys.
41
+ continue;
42
+ }
43
+
44
+ // Construct regular expression.
45
+ let text = key;
46
+ if (!text.startsWith("^")) {
47
+ text = "^" + text;
48
+ }
49
+ if (!text.endsWith("$")) {
50
+ text = text + "$";
51
+ }
52
+ const regExp = new RegExp(text);
53
+
54
+ // Get value.
55
+ let value = await tree.get(key);
56
+ if (Tree.isAsyncTree(value)) {
57
+ value = regExpKeys(value);
58
+ value.parent = result;
59
+ }
60
+
61
+ map.set(regExp, value);
62
+ }
63
+
64
+ return result;
65
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Return a transform function that sorts a tree's keys.
3
+ *
4
+ * For sorting, the keys are converted to strings, then sorted according to each
5
+ * character's Unicode code point value.
6
+ *
7
+ * @param {(a: any, b: any) => number} [compareFn]
8
+ */
9
+ export default function createSortTransform(compareFn) {
10
+ /**
11
+ * @type {import("../../index.ts").TreeTransform}
12
+ */
13
+ return function sortTransform(tree) {
14
+ const transform = Object.create(tree);
15
+ transform.keys = async () => {
16
+ const keys = [...(await tree.keys())];
17
+ keys.sort(compareFn);
18
+ return keys;
19
+ };
20
+ return transform;
21
+ };
22
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Return a transform function that sorts a tree's keys.
3
+ *
4
+ * The given `sortKeyFn` is invoked for each key/value pair in the tree, and
5
+ * should produce a sort key for that pair. The sort keys are then compared to
6
+ * determine the sort order for the tree's keys.
7
+ *
8
+ * @param {import("./map.js").KeyFn} sortKeyFn
9
+ */
10
+ export default function createSortByTransform(sortKeyFn) {
11
+ /**
12
+ * @type {import("../../index.ts").TreeTransform}
13
+ */
14
+ return function sortByTransform(tree) {
15
+ const transform = Object.create(tree);
16
+ transform.keys = async () => {
17
+ const keysAndSortKeys = [];
18
+ for (const key of await tree.keys()) {
19
+ const sortKey = await sortKeyFn(key, tree);
20
+ keysAndSortKeys.push({ key, sortKey });
21
+ }
22
+ keysAndSortKeys.sort((a, b) =>
23
+ a.sortKey < b.sortKey ? -1 : a.sortKey > b.sortKey ? 1 : 0
24
+ );
25
+ return keysAndSortKeys.map(({ key }) => key);
26
+ };
27
+ return transform;
28
+ };
29
+ }
@@ -0,0 +1,10 @@
1
+ import { naturalSortCompareFn } from "../utilities.js";
2
+ import sort from "./sort.js";
3
+
4
+ /**
5
+ * Return a transform function that sorts a tree's keys using [natural sort
6
+ * order](https://en.wikipedia.org/wiki/Natural_sort_order).
7
+ */
8
+ export default function sortNaturalTransform() {
9
+ return sort(naturalSortCompareFn);
10
+ }
@@ -0,0 +1,10 @@
1
+ import { PlainObject, StringLike } from "../index.ts";
2
+
3
+ export function castArrayLike(object: any): any;
4
+ export function getRealmObjectPrototype(object: any): any;
5
+ export const hiddenFileNames: string[];
6
+ export function isPlainObject(object: any): object is PlainObject;
7
+ export function isStringLike(obj: any): obj is StringLike;
8
+ export function keysFromPath(path: string): string[];
9
+ export const naturalSortCompareFn: (a: string, b: string) => number;
10
+ export function sortNatural(array: string[]): void;