@weborigami/async-tree 0.0.49 → 0.0.51

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 (34) hide show
  1. package/main.js +12 -6
  2. package/package.json +2 -2
  3. package/src/BrowserFileTree.js +3 -2
  4. package/src/FileTree.js +9 -3
  5. package/src/Tree.js +1 -1
  6. package/src/operations/{mergeDeep.js → deepMerge.js} +3 -3
  7. package/src/operations/deepTake.js +15 -0
  8. package/src/operations/deepTakeFn.js +37 -0
  9. package/src/operations/deepValues.js +10 -0
  10. package/src/operations/group.js +12 -0
  11. package/src/operations/map.js +14 -0
  12. package/src/operations/sort.js +18 -0
  13. package/src/operations/take.js +11 -0
  14. package/src/transforms/{map.js → mapFn.js} +1 -0
  15. package/src/transforms/sortFn.js +67 -0
  16. package/src/transforms/takeFn.js +25 -0
  17. package/src/utilities.d.ts +1 -2
  18. package/src/utilities.js +5 -17
  19. package/test/operations/{mergeDeep.test.js → deepMerge.test.js} +1 -1
  20. package/test/operations/deepTakeFn.test.js +21 -0
  21. package/test/operations/deepValues.test.js +22 -0
  22. package/test/transforms/{groupBy.test.js → groupFn.test.js} +3 -3
  23. package/test/transforms/keyMapsForExtensions.test.js +1 -1
  24. package/test/transforms/{map.test.js → mapFn.test.js} +13 -13
  25. package/test/transforms/sortFn.test.js +49 -0
  26. package/test/transforms/takeFn.test.js +20 -0
  27. package/test/utilities.test.js +6 -0
  28. package/src/transforms/sort.js +0 -25
  29. package/src/transforms/sortBy.js +0 -33
  30. package/src/transforms/sortNatural.js +0 -10
  31. package/test/transforms/sort.test.js +0 -30
  32. package/test/transforms/sortBy.test.js +0 -22
  33. package/test/transforms/sortNatural.test.js +0 -21
  34. /package/src/{transforms/groupBy.js → operations/groupFn.js} +0 -0
package/main.js CHANGED
@@ -11,14 +11,20 @@ export { default as SiteTree } from "./src/SiteTree.js";
11
11
  export { DeepObjectTree, ObjectTree, Tree } from "./src/internal.js";
12
12
  export * as keysJson from "./src/keysJson.js";
13
13
  export { default as cache } from "./src/operations/cache.js";
14
+ export { default as deepMerge } from "./src/operations/deepMerge.js";
15
+ export { default as deepTake } from "./src/operations/deepTake.js";
16
+ export { default as deepTakeFn } from "./src/operations/deepTakeFn.js";
17
+ export { default as deepValues } from "./src/operations/deepValues.js";
18
+ export { default as group } from "./src/operations/group.js";
19
+ export { default as groupFn } from "./src/operations/groupFn.js";
20
+ export { default as map } from "./src/operations/map.js";
14
21
  export { default as merge } from "./src/operations/merge.js";
15
- export { default as mergeDeep } from "./src/operations/mergeDeep.js";
22
+ export { default as sort } from "./src/operations/sort.js";
23
+ export { default as take } from "./src/operations/take.js";
16
24
  export * as symbols from "./src/symbols.js";
17
25
  export { default as cachedKeyFunctions } from "./src/transforms/cachedKeyFunctions.js";
18
- export { default as groupBy } from "./src/transforms/groupBy.js";
19
26
  export { default as keyFunctionsForExtensions } from "./src/transforms/keyFunctionsForExtensions.js";
20
- export { default as map } from "./src/transforms/map.js";
21
- export { default as sort } from "./src/transforms/sort.js";
22
- export { default as sortBy } from "./src/transforms/sortBy.js";
23
- export { default as sortNatural } from "./src/transforms/sortNatural.js";
27
+ export { default as mapFn } from "./src/transforms/mapFn.js";
28
+ export { default as sortFn } from "./src/transforms/sortFn.js";
29
+ export { default as takeFn } from "./src/transforms/takeFn.js";
24
30
  export * from "./src/utilities.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@weborigami/async-tree",
3
- "version": "0.0.49",
3
+ "version": "0.0.51",
4
4
  "description": "Asynchronous tree drivers based on standard JavaScript classes",
5
5
  "type": "module",
6
6
  "main": "./main.js",
@@ -11,7 +11,7 @@
11
11
  "typescript": "5.4.5"
12
12
  },
13
13
  "dependencies": {
14
- "@weborigami/types": "0.0.49"
14
+ "@weborigami/types": "0.0.51"
15
15
  },
16
16
  "scripts": {
17
17
  "test": "node --test --test-reporter=spec",
@@ -1,5 +1,5 @@
1
1
  import { Tree } from "./internal.js";
2
- import { hiddenFileNames, isStringLike, sortNatural } from "./utilities.js";
2
+ import { hiddenFileNames, isStringLike, naturalOrder } from "./utilities.js";
3
3
 
4
4
  const TypedArray = Object.getPrototypeOf(Uint8Array);
5
5
 
@@ -74,7 +74,8 @@ export default class BrowserFileTree {
74
74
  }
75
75
  // Filter out unhelpful file names.
76
76
  keys = keys.filter((key) => !hiddenFileNames.includes(key));
77
- sortNatural(keys);
77
+ keys.sort(naturalOrder);
78
+
78
79
  return keys;
79
80
  }
80
81
 
package/src/FileTree.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  getRealmObjectPrototype,
7
7
  hiddenFileNames,
8
8
  isPacked,
9
- sortNatural,
9
+ naturalOrder,
10
10
  } from "./utilities.js";
11
11
 
12
12
  /**
@@ -41,6 +41,11 @@ export default class FileTree {
41
41
  }
42
42
 
43
43
  async get(key) {
44
+ if (!key) {
45
+ // Undefined key or empty string key is invalid.
46
+ return undefined;
47
+ }
48
+
44
49
  const filePath = path.resolve(this.dirname, key);
45
50
 
46
51
  let stats;
@@ -91,7 +96,8 @@ export default class FileTree {
91
96
 
92
97
  // Node fs.readdir sort order appears to be unreliable; see, e.g.,
93
98
  // https://github.com/nodejs/node/issues/3232.
94
- sortNatural(names);
99
+ names.sort(naturalOrder);
100
+
95
101
  return names;
96
102
  }
97
103
 
@@ -101,7 +107,7 @@ export default class FileTree {
101
107
 
102
108
  async set(key, value) {
103
109
  // Where are we going to write this value?
104
- const stringKey = key ? String(key) : "";
110
+ const stringKey = key != null ? String(key) : "";
105
111
  const destPath = path.resolve(this.dirname, stringKey);
106
112
 
107
113
  if (value === undefined) {
package/src/Tree.js CHANGED
@@ -3,7 +3,7 @@ import FunctionTree from "./FunctionTree.js";
3
3
  import MapTree from "./MapTree.js";
4
4
  import SetTree from "./SetTree.js";
5
5
  import { DeepObjectTree, ObjectTree } from "./internal.js";
6
- import mapTransform from "./transforms/map.js";
6
+ import mapTransform from "./transforms/mapFn.js";
7
7
  import * as utilities from "./utilities.js";
8
8
  import {
9
9
  castArrayLike,
@@ -7,11 +7,11 @@ import { Tree } from "../internal.js";
7
7
  * @param {import("../../index.ts").Treelike[]} sources
8
8
  * @returns {AsyncTree & { description: string }}
9
9
  */
10
- export default function mergeDeep(...sources) {
10
+ export default function deepMerge(...sources) {
11
11
  let trees = sources.map((treelike) => Tree.from(treelike));
12
12
  let mergeParent;
13
13
  return {
14
- description: "mergeDeep",
14
+ description: "deepMerge",
15
15
 
16
16
  async get(key) {
17
17
  const subtrees = [];
@@ -25,7 +25,7 @@ export default function mergeDeep(...sources) {
25
25
  }
26
26
  }
27
27
 
28
- return subtrees.length > 0 ? mergeDeep(...subtrees) : undefined;
28
+ return subtrees.length > 0 ? deepMerge(...subtrees) : undefined;
29
29
  },
30
30
 
31
31
  async isKeyForSubtree(key) {
@@ -0,0 +1,15 @@
1
+ import deepTakeFn from "./deepTakeFn.js";
2
+
3
+ /**
4
+ * Returns a function that traverses a tree deeply and returns the values of the
5
+ * first `count` keys.
6
+ *
7
+ * This is similar to `deepValues`, but it is more efficient for large trees as
8
+ * stops after `count` values.
9
+ *
10
+ * @param {import("../../index.ts").Treelike} treelike
11
+ * @param {number} count
12
+ */
13
+ export default function deepTake(treelike, count) {
14
+ return deepTakeFn(count)(treelike);
15
+ }
@@ -0,0 +1,37 @@
1
+ import { Tree } from "../internal.js";
2
+
3
+ /**
4
+ * Returns a function that traverses a tree deeply and returns the values of the
5
+ * first `count` keys.
6
+ *
7
+ * @param {number} count
8
+ */
9
+ export default function deepTakeFn(count) {
10
+ /**
11
+ * @param {import("../../index.ts").Treelike} treelike
12
+ */
13
+ return async function deepTakeFn(treelike) {
14
+ const tree = await Tree.from(treelike);
15
+ const { values } = await traverse(tree, count);
16
+ return values;
17
+ };
18
+ }
19
+
20
+ async function traverse(tree, count) {
21
+ const values = [];
22
+ for (const key of await tree.keys()) {
23
+ if (count <= 0) {
24
+ break;
25
+ }
26
+ let value = await tree.get(key);
27
+ if (Tree.isAsyncTree(value)) {
28
+ const traversed = await traverse(value, count);
29
+ values.push(...traversed.values);
30
+ count = traversed.count;
31
+ } else {
32
+ values.push(value);
33
+ count--;
34
+ }
35
+ }
36
+ return { count, values };
37
+ }
@@ -0,0 +1,10 @@
1
+ import { Tree } from "../internal.js";
2
+
3
+ /**
4
+ * Return the in-order exterior values of a tree as a flat array.
5
+ *
6
+ * @param {import("../../index.ts").Treelike} treelike
7
+ */
8
+ export default async function deepValues(treelike) {
9
+ return Tree.mapReduce(treelike, null, async (values) => values.flat());
10
+ }
@@ -0,0 +1,12 @@
1
+ import groupFn from "./groupFn.js";
2
+
3
+ /**
4
+ * Given a function that returns a grouping key for a value, returns a transform
5
+ * that applies that grouping function to a tree.
6
+ *
7
+ * @param {import("../../index.ts").Treelike} treelike
8
+ * @param {import("../../index.ts").ValueKeyFn} groupKeyFn
9
+ */
10
+ export default function group(treelike, groupKeyFn) {
11
+ return groupFn(groupKeyFn)(treelike);
12
+ }
@@ -0,0 +1,14 @@
1
+ import mapFn from "../transforms/mapFn.js";
2
+
3
+ /**
4
+ * Transform 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 {import("../../index.ts").Treelike} treelike
10
+ * @param {ValueKeyFn|{ deep?: boolean, description?: string, needsSourceValue?: boolean, inverseKey?: KeyFn, key?: KeyFn, value?: ValueKeyFn }} options
11
+ */
12
+ export default function map(treelike, options = {}) {
13
+ return mapFn(options)(treelike);
14
+ }
@@ -0,0 +1,18 @@
1
+ import sortFn from "../transforms/sortFn.js";
2
+
3
+ /**
4
+ * Return a new tree with the original's keys sorted. A comparison function can
5
+ * be provided; by default the keys will be sorted in [natural sort
6
+ * order](https://en.wikipedia.org/wiki/Natural_sort_order).
7
+ *
8
+ * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
9
+ * @typedef {(key: any, tree: AsyncTree) => any} SortKeyFn
10
+ * @typedef {{ compare?: (a: any, b: any) => number, sortKey?: SortKeyFn }}
11
+ * SortOptions
12
+ *
13
+ * @param {import("../../index.ts").Treelike} treelike
14
+ * @param {SortOptions} [options]
15
+ */
16
+ export default function sort(treelike, options) {
17
+ return sortFn(options)(treelike);
18
+ }
@@ -0,0 +1,11 @@
1
+ import takeFn from "../transforms/takeFn.js";
2
+
3
+ /**
4
+ * Returns a new tree with the number of keys limited to the indicated count.
5
+ *
6
+ * @param {import("../../index.ts").Treelike} treelike
7
+ * @param {number} count
8
+ */
9
+ export default function take(treelike, count) {
10
+ return takeFn(count)(treelike);
11
+ }
@@ -8,6 +8,7 @@ import { isPlainObject } from "../utilities.js";
8
8
  * @typedef {import("../../index.ts").ValueKeyFn} ValueKeyFn
9
9
  *
10
10
  * @param {ValueKeyFn|{ deep?: boolean, description?: string, needsSourceValue?: boolean, inverseKey?: KeyFn, key?: KeyFn, value?: ValueKeyFn }} options
11
+ * @returns {import("../../index.ts").TreeTransform}
11
12
  */
12
13
  export default function createMapTransform(options = {}) {
13
14
  let deep;
@@ -0,0 +1,67 @@
1
+ import { Tree } from "../internal.js";
2
+
3
+ /**
4
+ * Return a transform function that sorts a tree's keys using a comparison
5
+ * function.
6
+ *
7
+ * If the `options` include a `sortKey` function, that will be invoked for each
8
+ * key in the tree to produce a sort key. If no `sortKey` function is provided,
9
+ * the original keys will be used as sort keys.
10
+ *
11
+ * If the `options` include a `compare` function, that will be used to compare
12
+ * sort keys.
13
+ *
14
+ * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
15
+ * @typedef {(key: any, tree: AsyncTree) => any} SortKeyFn
16
+ * @typedef {{ compare?: (a: any, b: any) => number, sortKey?: SortKeyFn }}
17
+ * SortOptions
18
+ *
19
+ * @param {SortOptions} [options]
20
+ */
21
+ export default function sortFn(options) {
22
+ const sortKey = options?.sortKey;
23
+ let compare = options?.compare;
24
+
25
+ /**
26
+ * @type {import("../../index.ts").TreeTransform}
27
+ */
28
+ return function sort(treelike) {
29
+ const tree = Tree.from(treelike);
30
+ const transformed = Object.create(tree);
31
+ transformed.keys = async () => {
32
+ const keys = Array.from(await tree.keys());
33
+
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 sort = await sortKey(key, tree);
40
+ if (sort === undefined) {
41
+ throw new Error(
42
+ `sortKey function returned undefined for key ${key}`
43
+ );
44
+ }
45
+ return { key, sort };
46
+ })
47
+ );
48
+
49
+ // Wrap the comparison function so it applies to sort keys.
50
+ const defaultCompare = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
51
+ const originalCompare = compare ?? defaultCompare;
52
+ // Sort by the sort key.
53
+ const sortedTuples = tuples.toSorted((a, b) =>
54
+ originalCompare(a.sort, b.sort)
55
+ );
56
+ // Map back to the original keys.
57
+ const sorted = sortedTuples.map((pair) => pair.key);
58
+ return sorted;
59
+ } else {
60
+ // Use original keys as sort keys.
61
+ // If compare is undefined, this uses default sort order.
62
+ return keys.toSorted(compare);
63
+ }
64
+ };
65
+ return transformed;
66
+ };
67
+ }
@@ -0,0 +1,25 @@
1
+ import { Tree } from "../internal.js";
2
+
3
+ /**
4
+ * Limit the number of keys to the indicated count.
5
+ *
6
+ * @param {number} count
7
+ */
8
+ export default function take(count) {
9
+ /**
10
+ * @type {import("../../index.ts").TreeTransform}
11
+ */
12
+ return (treelike) => {
13
+ const tree = Tree.from(treelike);
14
+ return {
15
+ async keys() {
16
+ const keys = Array.from(await tree.keys());
17
+ return keys.slice(0, count);
18
+ },
19
+
20
+ async get(key) {
21
+ return tree.get(key);
22
+ },
23
+ };
24
+ };
25
+ }
@@ -8,5 +8,4 @@ export function isPlainObject(object: any): object is PlainObject;
8
8
  export function isUnpackable(object): object is { unpack: () => any };
9
9
  export function isStringLike(obj: any): obj is StringLike;
10
10
  export function keysFromPath(path: string): string[];
11
- export const naturalSortCompareFn: (a: string, b: string) => number;
12
- export function sortNatural(array: string[]): void;
11
+ export const naturalOrder: (a: string, b: string) => number;
package/src/utilities.js CHANGED
@@ -135,22 +135,10 @@ export function keysFromPath(pathname) {
135
135
  return keys;
136
136
  }
137
137
 
138
- // Used for natural sort order
139
- export const naturalSortCompareFn = new Intl.Collator(undefined, {
140
- numeric: true,
141
- }).compare;
142
-
143
138
  /**
144
- * Sort the given array using [natural sort
145
- * order](https://en.wikipedia.org/wiki/Natural_sort_order). Like the native
146
- * `Array` `sort` function, this operation destructively modifies the array in
147
- * place.
148
- *
149
- * The default sort order for some sources like operating system files can be
150
- * unpredictable. Since it's quite common for file names to include numbers, it
151
- * can helpful to use natural sort order instead: ["file1", "file9", "file10"]
152
- * instead of ["file1", "file10", "file9"].
139
+ * Compare two strings using [natural sort
140
+ * order](https://en.wikipedia.org/wiki/Natural_sort_order).
153
141
  */
154
- export function sortNatural(array) {
155
- array.sort(naturalSortCompareFn);
156
- }
142
+ export const naturalOrder = new Intl.Collator(undefined, {
143
+ numeric: true,
144
+ }).compare;
@@ -1,7 +1,7 @@
1
1
  import assert from "node:assert";
2
2
  import { describe, test } from "node:test";
3
3
  import { DeepObjectTree, Tree } from "../../src/internal.js";
4
- import mergeDeep from "../../src/operations/mergeDeep.js";
4
+ import mergeDeep from "../../src/operations/deepMerge.js";
5
5
 
6
6
  describe("mergeDeep", () => {
7
7
  test("can merge deep", async () => {
@@ -0,0 +1,21 @@
1
+ import assert from "node:assert";
2
+ import { describe, test } from "node:test";
3
+ import deepTakeFn from "../../src/operations/deepTakeFn.js";
4
+
5
+ describe("deepTakeFn", () => {
6
+ test("traverses deeply and returns a limited number of items", async () => {
7
+ const tree = {
8
+ a: 1,
9
+ b: {
10
+ c: 2,
11
+ d: {
12
+ e: 3,
13
+ },
14
+ f: 4,
15
+ },
16
+ g: 5,
17
+ };
18
+ const result = await deepTakeFn(4)(tree);
19
+ assert.deepEqual(result, [1, 2, 3, 4]);
20
+ });
21
+ });
@@ -0,0 +1,22 @@
1
+ import assert from "node:assert";
2
+ import { describe, test } from "node:test";
3
+ import deepValues from "../../src/operations/deepValues.js";
4
+
5
+ describe("deepValues", () => {
6
+ test("returns in-order array of a tree's values", async () => {
7
+ const tree = {
8
+ a: 1,
9
+ b: {
10
+ c: 2,
11
+ d: {
12
+ e: 3,
13
+ },
14
+ },
15
+ f: {
16
+ g: 4,
17
+ },
18
+ };
19
+ const values = await deepValues(tree);
20
+ assert.deepEqual(values, [1, 2, 3, 4]);
21
+ });
22
+ });
@@ -1,9 +1,9 @@
1
1
  import assert from "node:assert";
2
2
  import { describe, test } from "node:test";
3
3
  import { Tree } from "../../src/internal.js";
4
- import groupBy from "../../src/transforms/groupBy.js";
4
+ import groupFn from "../../src/operations/groupFn.js";
5
5
 
6
- describe("groupBy transform", () => {
6
+ describe("groupFn transform", () => {
7
7
  test("groups using a group key function", async () => {
8
8
  const fonts = [
9
9
  { name: "Aboreto", tags: ["Sans Serif"] },
@@ -12,7 +12,7 @@ describe("groupBy transform", () => {
12
12
  { name: "Work Sans", tags: ["Grotesque", "Sans Serif"] },
13
13
  ];
14
14
  const tree = Tree.from(fonts);
15
- const grouped = await groupBy((value, key, tree) => value.tags)(tree);
15
+ const grouped = await groupFn((value, key, tree) => value.tags)(tree);
16
16
  assert.deepEqual(await Tree.plain(grouped), {
17
17
  Geometric: [{ name: "Albert Sans", tags: ["Geometric", "Sans Serif"] }],
18
18
  Grotesque: [{ name: "Work Sans", tags: ["Grotesque", "Sans Serif"] }],
@@ -2,7 +2,7 @@ import assert from "node:assert";
2
2
  import { describe, test } from "node:test";
3
3
  import { ObjectTree, Tree } from "../../src/internal.js";
4
4
  import keyFunctionsForExtensions from "../../src/transforms/keyFunctionsForExtensions.js";
5
- import map from "../../src/transforms/map.js";
5
+ import map from "../../src/transforms/mapFn.js";
6
6
 
7
7
  describe("keyMapsForExtensions", () => {
8
8
  test("returns key functions that pass a matching key through", async () => {
@@ -2,15 +2,15 @@ import assert from "node:assert";
2
2
  import { describe, test } from "node:test";
3
3
  import FunctionTree from "../../src/FunctionTree.js";
4
4
  import { DeepObjectTree, ObjectTree, Tree } from "../../src/internal.js";
5
- import map from "../../src/transforms/map.js";
5
+ import mapFn from "../../src/transforms/mapFn.js";
6
6
 
7
- describe("map", () => {
7
+ describe("mapFn", () => {
8
8
  test("returns identity graph if no key or value", async () => {
9
9
  const tree = {
10
10
  a: "letter a",
11
11
  b: "letter b",
12
12
  };
13
- const mapped = map()(tree);
13
+ const mapped = mapFn()(tree);
14
14
  assert.deepEqual(await Tree.plain(mapped), {
15
15
  a: "letter a",
16
16
  b: "letter b",
@@ -23,7 +23,7 @@ describe("map", () => {
23
23
  b: "letter b",
24
24
  c: undefined, // Won't be mapped
25
25
  });
26
- const uppercaseValues = map({
26
+ const uppercaseValues = mapFn({
27
27
  value: (sourceValue, sourceKey, innerTree) => {
28
28
  assert(sourceKey === "a" || sourceKey === "b");
29
29
  assert.equal(innerTree, tree);
@@ -43,7 +43,7 @@ describe("map", () => {
43
43
  a: "letter a",
44
44
  b: "letter b",
45
45
  };
46
- const uppercaseValues = map((sourceValue, sourceKey, tree) => {
46
+ const uppercaseValues = mapFn((sourceValue, sourceKey, tree) => {
47
47
  assert(sourceKey === "a" || sourceKey === "b");
48
48
  return sourceValue.toUpperCase();
49
49
  });
@@ -59,7 +59,7 @@ describe("map", () => {
59
59
  a: "letter a",
60
60
  b: "letter b",
61
61
  };
62
- const doubleKeys = map({
62
+ const doubleKeys = mapFn({
63
63
  key: async (sourceKey, tree) => `_${sourceKey}`,
64
64
  inverseKey: async (resultKey, tree) => resultKey.slice(1),
65
65
  });
@@ -75,7 +75,7 @@ describe("map", () => {
75
75
  a: "letter a",
76
76
  b: "letter b",
77
77
  };
78
- const doubleKeysUppercaseValues = map({
78
+ const doubleKeysUppercaseValues = mapFn({
79
79
  key: async (sourceKey, tree) => `_${sourceKey}`,
80
80
  inverseKey: async (resultKey, tree) => resultKey.slice(1),
81
81
  value: async (sourceValue, sourceKey, tree) => sourceValue.toUpperCase(),
@@ -94,7 +94,7 @@ describe("map", () => {
94
94
  b: "letter b",
95
95
  },
96
96
  };
97
- const doubleKeys = map({
97
+ const doubleKeys = mapFn({
98
98
  key: async (sourceKey, tree) => `_${sourceKey}`,
99
99
  inverseKey: async (resultKey, tree) => resultKey.slice(1),
100
100
  value: async (sourceValue, sourceKey, tree) => sourceKey,
@@ -114,7 +114,7 @@ describe("map", () => {
114
114
  a: "letter a",
115
115
  b: "letter b",
116
116
  };
117
- const mapped = map(uppercase)(tree);
117
+ const mapped = mapFn(uppercase)(tree);
118
118
  assert.deepEqual(await Tree.plain(mapped), {
119
119
  _a: "LETTER A",
120
120
  _b: "LETTER B",
@@ -128,7 +128,7 @@ describe("map", () => {
128
128
  b: "letter b",
129
129
  },
130
130
  });
131
- const uppercaseValues = map({
131
+ const uppercaseValues = mapFn({
132
132
  deep: true,
133
133
  value: (sourceValue, sourceKey, tree) => sourceValue.toUpperCase(),
134
134
  });
@@ -148,7 +148,7 @@ describe("map", () => {
148
148
  b: "letter b",
149
149
  },
150
150
  });
151
- const doubleKeys = map({
151
+ const doubleKeys = mapFn({
152
152
  deep: true,
153
153
  key: async (sourceKey, tree) => `_${sourceKey}`,
154
154
  inverseKey: async (resultKey, tree) => resultKey.slice(1),
@@ -169,7 +169,7 @@ describe("map", () => {
169
169
  b: "letter b",
170
170
  },
171
171
  });
172
- const doubleKeysUppercaseValues = map({
172
+ const doubleKeysUppercaseValues = mapFn({
173
173
  deep: true,
174
174
  key: async (sourceKey, tree) => `_${sourceKey}`,
175
175
  inverseKey: async (resultKey, tree) => resultKey.slice(1),
@@ -189,7 +189,7 @@ describe("map", () => {
189
189
  const tree = new FunctionTree(() => {
190
190
  flag = true;
191
191
  }, ["a", "b", "c"]);
192
- const mapped = map({
192
+ const mapped = mapFn({
193
193
  needsSourceValue: false,
194
194
  value: () => "X",
195
195
  })(tree);
@@ -0,0 +1,49 @@
1
+ import assert from "node:assert";
2
+ import { describe, test } from "node:test";
3
+ import { Tree } from "../../src/internal.js";
4
+ import sortFn from "../../src/transforms/sortFn.js";
5
+
6
+ describe("sortFn", () => {
7
+ test("sorts keys using default sort order", async () => {
8
+ const tree = Tree.from({
9
+ file10: null,
10
+ file1: null,
11
+ file9: null,
12
+ });
13
+ const sorted = sortFn()(tree);
14
+ assert.deepEqual(Array.from(await sorted.keys()), [
15
+ "file1",
16
+ "file10",
17
+ "file9",
18
+ ]);
19
+ });
20
+
21
+ test("invokes a comparison function", async () => {
22
+ const tree = Tree.from({
23
+ b: 2,
24
+ c: 3,
25
+ a: 1,
26
+ });
27
+ // Reverse order
28
+ const compare = (a, b) => (a > b ? -1 : a < b ? 1 : 0);
29
+ const sorted = sortFn({ compare })(tree);
30
+ assert.deepEqual(Array.from(await sorted.keys()), ["c", "b", "a"]);
31
+ });
32
+
33
+ test("invokes a sortKey function", async () => {
34
+ const tree = {
35
+ Alice: { age: 48 },
36
+ Bob: { age: 36 },
37
+ Carol: { age: 42 },
38
+ };
39
+ const transform = await sortFn({
40
+ sortKey: async (key, tree) => Tree.traverse(tree, key, "age"),
41
+ });
42
+ const result = transform(tree);
43
+ assert.deepEqual(Array.from(await result.keys()), [
44
+ "Bob",
45
+ "Carol",
46
+ "Alice",
47
+ ]);
48
+ });
49
+ });
@@ -0,0 +1,20 @@
1
+ import assert from "node:assert";
2
+ import { describe, test } from "node:test";
3
+ import { Tree } from "../../src/internal.js";
4
+ import takeFn from "../../src/transforms/takeFn.js";
5
+
6
+ describe("takeFn", () => {
7
+ test("limits the number of keys to the indicated count", async () => {
8
+ const tree = {
9
+ a: 1,
10
+ b: 2,
11
+ c: 3,
12
+ d: 4,
13
+ };
14
+ const result = await takeFn(2)(tree);
15
+ assert.deepEqual(await Tree.plain(result), {
16
+ a: 1,
17
+ b: 2,
18
+ });
19
+ });
20
+ });
@@ -21,4 +21,10 @@ describe("utilities", () => {
21
21
  assert.deepEqual(utilities.keysFromPath("a/b/c"), ["a", "b", "c"]);
22
22
  assert.deepEqual(utilities.keysFromPath("foo/"), ["foo", ""]);
23
23
  });
24
+
25
+ test("naturalOrder compares strings in natural order", () => {
26
+ const strings = ["file10", "file1", "file9"];
27
+ strings.sort(utilities.naturalOrder);
28
+ assert.deepEqual(strings, ["file1", "file9", "file10"]);
29
+ });
24
30
  });
@@ -1,25 +0,0 @@
1
- import { Tree } from "../internal.js";
2
-
3
- /**
4
- * Return a transform function that sorts a tree's keys.
5
- *
6
- * For sorting, the keys are converted to strings, then sorted according to each
7
- * character's Unicode code point value.
8
- *
9
- * @param {(a: any, b: any) => number} [compareFn]
10
- */
11
- export default function createSortTransform(compareFn) {
12
- /**
13
- * @type {import("../../index.ts").TreeTransform}
14
- */
15
- return function sortTransform(treelike) {
16
- const tree = Tree.from(treelike);
17
- const transform = Object.create(tree);
18
- transform.keys = async () => {
19
- const keys = Array.from(await tree.keys());
20
- keys.sort(compareFn);
21
- return keys;
22
- };
23
- return transform;
24
- };
25
- }
@@ -1,33 +0,0 @@
1
- import { Tree } from "../internal.js";
2
-
3
- /**
4
- * Return a transform function that sorts a tree's keys.
5
- *
6
- * The given `sortKeyFn` is invoked for each key/value pair in the tree, and
7
- * should produce a sort key for that pair. The sort keys are then compared to
8
- * determine the sort order for the tree's keys.
9
- *
10
- * @param {import("./map.js").KeyFn} sortKeyFn
11
- */
12
- export default function createSortByTransform(sortKeyFn) {
13
- /**
14
- * @type {import("../../index.ts").TreeTransform}
15
- */
16
- return function sortByTransform(treelike) {
17
- const tree = Tree.from(treelike);
18
- const transform = Object.create(tree);
19
- transform.keys = async () => {
20
- const keysAndSortKeys = [];
21
- for (const key of await tree.keys()) {
22
- const sortKey = await sortKeyFn(key, tree);
23
- keysAndSortKeys.push({ key, sortKey });
24
- }
25
- keysAndSortKeys.sort((a, b) =>
26
- a.sortKey < b.sortKey ? -1 : a.sortKey > b.sortKey ? 1 : 0
27
- );
28
- const sortedKeys = keysAndSortKeys.map(({ key }) => key);
29
- return sortedKeys;
30
- };
31
- return transform;
32
- };
33
- }
@@ -1,10 +0,0 @@
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
- }
@@ -1,30 +0,0 @@
1
- import assert from "node:assert";
2
- import { describe, test } from "node:test";
3
- import { Tree } from "../../src/internal.js";
4
- import sort from "../../src/transforms/sort.js";
5
-
6
- describe("sort transform", () => {
7
- test("sorts keys", async () => {
8
- const tree = Tree.from({
9
- b: 2,
10
- c: 3,
11
- a: 1,
12
- });
13
- const sortTransform = sort();
14
- const sorted = await sortTransform(tree);
15
- assert.deepEqual(Array.from(await sorted.keys()), ["a", "b", "c"]);
16
- });
17
-
18
- test("sorts keys using a comparison function", async () => {
19
- const tree = Tree.from({
20
- b: 2,
21
- c: 3,
22
- a: 1,
23
- });
24
- // Reverse order
25
- const compareFn = (a, b) => (a > b ? -1 : a < b ? 1 : 0);
26
- const sortTransform = sort(compareFn);
27
- const sorted = await sortTransform(tree);
28
- assert.deepEqual(Array.from(await sorted.keys()), ["c", "b", "a"]);
29
- });
30
- });
@@ -1,22 +0,0 @@
1
- import assert from "node:assert";
2
- import { describe, test } from "node:test";
3
- import { Tree } from "../../src/internal.js";
4
- import sortBy from "../../src/transforms/sortBy.js";
5
-
6
- describe("sortBy transform", () => {
7
- test("sorts keys using a provided sort key function", async () => {
8
- const tree = Tree.from({
9
- Alice: { age: 48 },
10
- Bob: { age: 36 },
11
- Carol: { age: 42 },
12
- });
13
- const sorted = await sortBy((key, tree) => Tree.traverse(tree, key, "age"))(
14
- tree
15
- );
16
- assert.deepEqual(Array.from(await sorted.keys()), [
17
- "Bob",
18
- "Carol",
19
- "Alice",
20
- ]);
21
- });
22
- });
@@ -1,21 +0,0 @@
1
- import assert from "node:assert";
2
- import { describe, test } from "node:test";
3
- import { Tree } from "../../src/internal.js";
4
- import sortNatural from "../../src/transforms/sortNatural.js";
5
-
6
- describe("sortNatural transform", () => {
7
- test("sorts keys", async () => {
8
- const tree = Tree.from({
9
- file10: null,
10
- file1: null,
11
- file9: null,
12
- });
13
- const sortTransform = sortNatural();
14
- const sorted = await sortTransform(tree);
15
- assert.deepEqual(Array.from(await sorted.keys()), [
16
- "file1",
17
- "file9",
18
- "file10",
19
- ]);
20
- });
21
- });