@weborigami/async-tree 0.0.54 → 0.0.56
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 +3 -0
- package/package.json +2 -2
- package/src/FileTree.js +4 -0
- package/src/operations/cache.js +6 -10
- package/src/transforms/deepReverse.js +29 -0
- package/src/transforms/invokeFunctions.js +18 -0
- package/src/transforms/reverse.js +25 -0
- package/src/utilities.d.ts +3 -1
- package/src/utilities.js +40 -0
- package/test/FileTree.test.js +18 -1
- package/test/transforms/deepReverse.test.js +24 -0
- package/test/transforms/invokeFunctions.test.js +17 -0
- package/test/transforms/reverse.test.js +23 -0
- package/test/utilities.test.js +25 -0
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.
|
|
3
|
+
"version": "0.0.56",
|
|
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.
|
|
14
|
+
"@weborigami/types": "0.0.56"
|
|
15
15
|
},
|
|
16
16
|
"scripts": {
|
|
17
17
|
"test": "node --test --test-reporter=spec",
|
package/src/FileTree.js
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
getRealmObjectPrototype,
|
|
7
7
|
hiddenFileNames,
|
|
8
8
|
isPacked,
|
|
9
|
+
isPlainObject,
|
|
9
10
|
naturalOrder,
|
|
10
11
|
} from "./utilities.js";
|
|
11
12
|
|
|
@@ -165,6 +166,9 @@ export default class FileTree {
|
|
|
165
166
|
await fs.mkdir(this.dirname, { recursive: true });
|
|
166
167
|
// Write out the value as the contents of a file.
|
|
167
168
|
await fs.writeFile(destPath, value);
|
|
169
|
+
} else if (isPlainObject(value) && Object.keys(value).length === 0) {
|
|
170
|
+
// Special case: empty object means create an empty directory.
|
|
171
|
+
await fs.mkdir(destPath, { recursive: true });
|
|
168
172
|
} else if (Tree.isTreelike(value)) {
|
|
169
173
|
// Treat value as a tree and write it out as a subdirectory.
|
|
170
174
|
const destTree = Reflect.construct(this.constructor, [destPath]);
|
package/src/operations/cache.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { DeepObjectTree, Tree } from "../internal.js";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Caches values from a source tree in a second cache tree.
|
|
5
|
-
*
|
|
4
|
+
* Caches values from a source tree in a second cache tree. Cache source tree
|
|
5
|
+
* keys in memory.
|
|
6
|
+
*
|
|
7
|
+
* If no second tree is supplied, an in-memory value cache is used.
|
|
6
8
|
*
|
|
7
9
|
* An optional third filter tree can be supplied. If a filter tree is supplied,
|
|
8
10
|
* only values for keys that match the filter will be cached.
|
|
@@ -22,6 +24,7 @@ export default function treeCache(sourceTreelike, cacheTree, filterTreelike) {
|
|
|
22
24
|
|
|
23
25
|
/** @type {AsyncMutableTree} */
|
|
24
26
|
const cache = cacheTree ?? new DeepObjectTree({});
|
|
27
|
+
let keys;
|
|
25
28
|
return {
|
|
26
29
|
description: "cache",
|
|
27
30
|
|
|
@@ -65,14 +68,7 @@ export default function treeCache(sourceTreelike, cacheTree, filterTreelike) {
|
|
|
65
68
|
},
|
|
66
69
|
|
|
67
70
|
async keys() {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
// We also add the cache's keys in case the keys provided by the source
|
|
71
|
-
// tree have changed since the cache was updated.
|
|
72
|
-
for (const key of await cache.keys()) {
|
|
73
|
-
keys.add(key);
|
|
74
|
-
}
|
|
75
|
-
|
|
71
|
+
keys ??= source.keys();
|
|
76
72
|
return keys;
|
|
77
73
|
},
|
|
78
74
|
};
|
|
@@ -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
|
+
}
|
package/src/utilities.d.ts
CHANGED
|
@@ -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(
|
|
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
|
+
}
|
package/test/FileTree.test.js
CHANGED
|
@@ -9,7 +9,7 @@ import { Tree } from "../src/internal.js";
|
|
|
9
9
|
const dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
10
10
|
const tempDirectory = path.join(dirname, "fixtures/temp");
|
|
11
11
|
|
|
12
|
-
describe("FileTree", async () => {
|
|
12
|
+
describe.only("FileTree", async () => {
|
|
13
13
|
test("can get the keys of the tree", async () => {
|
|
14
14
|
const fixture = createFixture("fixtures/markdown");
|
|
15
15
|
assert.deepEqual(Array.from(await fixture.keys()), [
|
|
@@ -61,6 +61,23 @@ describe("FileTree", async () => {
|
|
|
61
61
|
await removeTempDirectory();
|
|
62
62
|
});
|
|
63
63
|
|
|
64
|
+
test.only("can create empty subfolder via set()", async () => {
|
|
65
|
+
await createTempDirectory();
|
|
66
|
+
|
|
67
|
+
// Write out new, empty folder called "empty".
|
|
68
|
+
const tempFiles = new FileTree(tempDirectory);
|
|
69
|
+
await tempFiles.set("empty", {});
|
|
70
|
+
|
|
71
|
+
// Verify folder exists and has no contents.
|
|
72
|
+
const folderPath = path.join(tempDirectory, "empty");
|
|
73
|
+
const stats = await fs.stat(folderPath);
|
|
74
|
+
assert(stats.isDirectory());
|
|
75
|
+
const files = await fs.readdir(folderPath);
|
|
76
|
+
assert.deepEqual(files, []);
|
|
77
|
+
|
|
78
|
+
await removeTempDirectory();
|
|
79
|
+
});
|
|
80
|
+
|
|
64
81
|
test("can write out subfolder via set()", async () => {
|
|
65
82
|
await createTempDirectory();
|
|
66
83
|
|
|
@@ -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
|
+
});
|
package/test/utilities.test.js
CHANGED
|
@@ -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
|
});
|