@weborigami/async-tree 0.0.53 → 0.0.55

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.
package/main.js CHANGED
@@ -23,8 +23,11 @@ export { default as sort } from "./src/operations/sort.js";
23
23
  export { default as take } from "./src/operations/take.js";
24
24
  export * as symbols from "./src/symbols.js";
25
25
  export { default as cachedKeyFunctions } from "./src/transforms/cachedKeyFunctions.js";
26
+ export { default as deepReverse } from "./src/transforms/deepReverse.js";
27
+ export { default as invokeFunctions } from "./src/transforms/invokeFunctions.js";
26
28
  export { default as keyFunctionsForExtensions } from "./src/transforms/keyFunctionsForExtensions.js";
27
29
  export { default as mapFn } from "./src/transforms/mapFn.js";
30
+ export { default as reverse } from "./src/transforms/reverse.js";
28
31
  export { default as sortFn } from "./src/transforms/sortFn.js";
29
32
  export { default as takeFn } from "./src/transforms/takeFn.js";
30
33
  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.53",
3
+ "version": "0.0.55",
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.53"
14
+ "@weborigami/types": "0.0.55"
15
15
  },
16
16
  "scripts": {
17
17
  "test": "node --test --test-reporter=spec",
@@ -16,10 +16,11 @@ export default function deepMerge(...sources) {
16
16
  async get(key) {
17
17
  const subtrees = [];
18
18
 
19
- for (const tree of trees) {
20
- const value = await tree.get(key);
19
+ // Check trees for the indicated key in reverse order.
20
+ for (let index = trees.length - 1; index >= 0; index--) {
21
+ const value = await trees[index].get(key);
21
22
  if (Tree.isAsyncTree(value)) {
22
- subtrees.push(value);
23
+ subtrees.unshift(value);
23
24
  } else if (value !== undefined) {
24
25
  return value;
25
26
  }
@@ -29,8 +30,9 @@ export default function deepMerge(...sources) {
29
30
  },
30
31
 
31
32
  async isKeyForSubtree(key) {
32
- for (const tree of trees) {
33
- if (await Tree.isKeyForSubtree(tree, key)) {
33
+ // Check trees for the indicated key in reverse order.
34
+ for (let index = trees.length - 1; index >= 0; index--) {
35
+ if (await Tree.isKeyForSubtree(trees[index], key)) {
34
36
  return true;
35
37
  }
36
38
  }
@@ -39,6 +41,7 @@ export default function deepMerge(...sources) {
39
41
 
40
42
  async keys() {
41
43
  const keys = new Set();
44
+ // Collect keys in the order the trees were provided.
42
45
  for (const tree of trees) {
43
46
  for (const key of await tree.keys()) {
44
47
  keys.add(key);
@@ -20,8 +20,9 @@ export default function merge(...sources) {
20
20
  description: "merge",
21
21
 
22
22
  async get(key) {
23
- for (const tree of trees) {
24
- const value = await tree.get(key);
23
+ // Check trees for the indicated key in reverse order.
24
+ for (let index = trees.length - 1; index >= 0; index--) {
25
+ const value = await trees[index].get(key);
25
26
  if (value !== undefined) {
26
27
  return value;
27
28
  }
@@ -30,8 +31,9 @@ export default function merge(...sources) {
30
31
  },
31
32
 
32
33
  async isKeyForSubtree(key) {
33
- for (const tree of trees) {
34
- if (await Tree.isKeyForSubtree(tree, key)) {
34
+ // Check trees for the indicated key in reverse order.
35
+ for (let index = trees.length - 1; index >= 0; index--) {
36
+ if (await Tree.isKeyForSubtree(trees[index], key)) {
35
37
  return true;
36
38
  }
37
39
  }
@@ -40,6 +42,7 @@ export default function merge(...sources) {
40
42
 
41
43
  async keys() {
42
44
  const keys = new Set();
45
+ // Collect keys in the order the trees were provided.
43
46
  for (const tree of trees) {
44
47
  for (const key of await tree.keys()) {
45
48
  keys.add(key);
@@ -0,0 +1,29 @@
1
+ import { Tree } from "../internal.js";
2
+
3
+ /**
4
+ * Reverse the order of keys at all levels of the tree.
5
+ *
6
+ * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
7
+ * @typedef {import("../../index.ts").Treelike} Treelike
8
+ *
9
+ * @param {Treelike} treelike
10
+ * @returns {AsyncTree}
11
+ */
12
+ export default function deepReverse(treelike) {
13
+ const tree = Tree.from(treelike);
14
+ return {
15
+ async get(key) {
16
+ let value = await tree.get(key);
17
+ if (Tree.isAsyncTree(value)) {
18
+ value = deepReverse(value);
19
+ }
20
+ return value;
21
+ },
22
+
23
+ async keys() {
24
+ const keys = Array.from(await tree.keys());
25
+ keys.reverse();
26
+ return keys;
27
+ },
28
+ };
29
+ }
@@ -0,0 +1,18 @@
1
+ import { Tree } from "../internal.js";
2
+
3
+ export default function invokeFunctions(treelike) {
4
+ const tree = Tree.from(treelike);
5
+ return {
6
+ async get(key) {
7
+ let value = await tree.get(key);
8
+ if (typeof value === "function") {
9
+ value = value();
10
+ }
11
+ return value;
12
+ },
13
+
14
+ async keys() {
15
+ return tree.keys();
16
+ },
17
+ };
18
+ }
@@ -0,0 +1,25 @@
1
+ import { Tree } from "../internal.js";
2
+
3
+ /**
4
+ * Reverse the order of the top-level keys in the tree.
5
+ *
6
+ * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
7
+ * @typedef {import("../../index.ts").Treelike} Treelike
8
+ *
9
+ * @param {Treelike} treelike
10
+ * @returns {AsyncTree}
11
+ */
12
+ export default function reverse(treelike) {
13
+ const tree = Tree.from(treelike);
14
+ return {
15
+ async get(key) {
16
+ return tree.get(key);
17
+ },
18
+
19
+ async keys() {
20
+ const keys = Array.from(await tree.keys());
21
+ keys.reverse();
22
+ return keys;
23
+ },
24
+ };
25
+ }
@@ -6,6 +6,8 @@ export const hiddenFileNames: string[];
6
6
  export function isPacked(object: any): object is Packed;
7
7
  export function isPlainObject(object: any): object is PlainObject;
8
8
  export function isUnpackable(object): object is { unpack: () => any };
9
- export function isStringLike(obj: any): obj is StringLike;
9
+ export function isStringLike(object: any): object is StringLike;
10
10
  export function keysFromPath(path: string): string[];
11
11
  export const naturalOrder: (a: string, b: string) => number;
12
+ export function pipeline(start: any, ...functions: Function[]): Promise<any>;
13
+ export function toString(object: any): string;
package/src/utilities.js CHANGED
@@ -1,3 +1,4 @@
1
+ const textDecoder = new TextDecoder();
1
2
  const TypedArray = Object.getPrototypeOf(Uint8Array);
2
3
 
3
4
  /**
@@ -142,3 +143,42 @@ export function keysFromPath(pathname) {
142
143
  export const naturalOrder = new Intl.Collator(undefined, {
143
144
  numeric: true,
144
145
  }).compare;
146
+
147
+ /**
148
+ * Apply a series of functions to a value, passing the result of each function
149
+ * to the next one.
150
+ *
151
+ * @param {any} start
152
+ * @param {...Function} fns
153
+ */
154
+ export async function pipeline(start, ...fns) {
155
+ return fns.reduce(async (acc, fn) => fn(await acc), start);
156
+ }
157
+
158
+ /**
159
+ * Return a string form of the object, handling cases not generally handled by
160
+ * the standard JavaScript `toString()` method:
161
+ *
162
+ * 1. If the object is an ArrayBuffer or TypedArray, decode the array as UTF-8.
163
+ * 2. If the object is otherwise a plain JavaScript object with the useless
164
+ * default toString() method, return null instead of "[object Object]". In
165
+ * practice, it's generally more useful to have this method fail than to
166
+ * return a useless string.
167
+ *
168
+ * @param {any} object
169
+ * @returns {string|null}
170
+ */
171
+ export function toString(object) {
172
+ if (object instanceof ArrayBuffer || object instanceof TypedArray) {
173
+ // Treat the buffer as UTF-8 text.
174
+ const decoded = textDecoder.decode(object);
175
+ // If the result appears to contain non-printable characters, it's probably not a string.
176
+ // https://stackoverflow.com/a/1677660/76472
177
+ const hasNonPrintableCharacters = /[\x00-\x08\x0E-\x1F]/.test(decoded);
178
+ return hasNonPrintableCharacters ? null : decoded;
179
+ } else if (isStringLike(object)) {
180
+ return String(object);
181
+ } else {
182
+ return null;
183
+ }
184
+ }
@@ -8,7 +8,7 @@ describe("mergeDeep", () => {
8
8
  const fixture = mergeDeep(
9
9
  new DeepObjectTree({
10
10
  a: {
11
- b: 1,
11
+ b: 0, // Will be obscured by `b` below
12
12
  c: {
13
13
  d: 2,
14
14
  },
@@ -16,7 +16,7 @@ describe("mergeDeep", () => {
16
16
  }),
17
17
  new DeepObjectTree({
18
18
  a: {
19
- b: 0, // Will be obscured by `b` above
19
+ b: 1,
20
20
  c: {
21
21
  e: 3,
22
22
  },
@@ -6,34 +6,34 @@ import merge from "../../src/operations/merge.js";
6
6
  describe("merge", () => {
7
7
  test("performs a shallow merge", async () => {
8
8
  const fixture = merge(
9
- Tree.from({
9
+ {
10
10
  a: 1,
11
+ // Will be obscured by `b` that follows
11
12
  b: {
12
13
  c: 2,
13
14
  },
14
- }),
15
- Tree.from({
16
- // Will be obscured by `b` above
15
+ },
16
+ {
17
17
  b: {
18
18
  d: 3,
19
19
  },
20
20
  e: {
21
21
  f: 4,
22
22
  },
23
- })
23
+ }
24
24
  );
25
25
 
26
26
  assert.deepEqual(await Tree.plain(fixture), {
27
27
  a: 1,
28
28
  b: {
29
- c: 2,
29
+ d: 3,
30
30
  },
31
31
  e: {
32
32
  f: 4,
33
33
  },
34
34
  });
35
35
 
36
- const d = await Tree.traverse(fixture, "b", "d");
37
- assert.equal(d, undefined);
36
+ const c = await Tree.traverse(fixture, "b", "c");
37
+ assert.equal(c, undefined);
38
38
  });
39
39
  });
@@ -0,0 +1,24 @@
1
+ import { Tree } from "@weborigami/async-tree";
2
+ import assert from "node:assert";
3
+ import { describe, test } from "node:test";
4
+ import deepReverse from "../../src/transforms/deepReverse.js";
5
+
6
+ describe("deepReverse", () => {
7
+ test("reverses keys at all levels of a tree", async () => {
8
+ const tree = {
9
+ a: 1,
10
+ b: {
11
+ c: 2,
12
+ d: 3,
13
+ },
14
+ };
15
+ const reversed = deepReverse.call(null, tree);
16
+ assert.deepEqual(await Tree.plain(reversed), {
17
+ b: {
18
+ d: 3,
19
+ c: 2,
20
+ },
21
+ a: 1,
22
+ });
23
+ });
24
+ });
@@ -0,0 +1,17 @@
1
+ import assert from "node:assert";
2
+ import { describe, test } from "node:test";
3
+ import { Tree } from "../../src/internal.js";
4
+ import invokeFunctions from "../../src/transforms/invokeFunctions.js";
5
+
6
+ describe("invokeFunctions", () => {
7
+ test("invokes function values, leaves other values as is", async () => {
8
+ const fixture = invokeFunctions({
9
+ a: 1,
10
+ b: () => 2,
11
+ });
12
+ assert.deepEqual(await Tree.plain(fixture), {
13
+ a: 1,
14
+ b: 2,
15
+ });
16
+ });
17
+ });
@@ -0,0 +1,23 @@
1
+ import { Tree } from "@weborigami/async-tree";
2
+ import assert from "node:assert";
3
+ import { describe, test } from "node:test";
4
+ import reverse from "../../src/transforms/reverse.js";
5
+
6
+ describe("reverse", () => {
7
+ test("reverses a tree's top-level keys", async () => {
8
+ const tree = {
9
+ a: "A",
10
+ b: "B",
11
+ c: "C",
12
+ };
13
+ const reversed = reverse.call(null, tree);
14
+ // @ts-ignore
15
+ assert.deepEqual(Array.from(await reversed.keys()), ["c", "b", "a"]);
16
+ // @ts-ignore
17
+ assert.deepEqual(await Tree.plain(reversed), {
18
+ c: "C",
19
+ b: "B",
20
+ a: "A",
21
+ });
22
+ });
23
+ });
@@ -27,4 +27,29 @@ describe("utilities", () => {
27
27
  strings.sort(utilities.naturalOrder);
28
28
  assert.deepEqual(strings, ["file1", "file9", "file10"]);
29
29
  });
30
+
31
+ test("pipeline applies a series of functions to a value", async () => {
32
+ const addOne = (n) => n + 1;
33
+ const double = (n) => n * 2;
34
+ const square = (n) => n * n;
35
+ const result = await utilities.pipeline(1, addOne, double, square);
36
+ assert.equal(result, 16);
37
+ });
38
+
39
+ test("toString returns the value of an object's `toString` method", () => {
40
+ const object = {
41
+ toString: () => "text",
42
+ };
43
+ assert.equal(utilities.toString(object), "text");
44
+ });
45
+
46
+ test("toString returns null for an object with no useful `toString`", () => {
47
+ const object = {};
48
+ assert.equal(utilities.toString(object), null);
49
+ });
50
+
51
+ test("toString decodes an ArrayBuffer as UTF-8", () => {
52
+ const buffer = Buffer.from("text", "utf8");
53
+ assert.equal(utilities.toString(buffer), "text");
54
+ });
30
55
  });