@weborigami/async-tree 0.0.64 → 0.0.65-beta.1
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/browser.js +1 -1
- package/main.js +3 -3
- package/package.json +4 -4
- package/src/BrowserFileTree.js +7 -0
- package/src/FileTree.js +5 -3
- package/src/MapTree.js +7 -0
- package/src/ObjectTree.js +5 -9
- package/src/SetTree.js +7 -0
- package/src/SiteTree.js +9 -2
- package/src/Tree.js +10 -14
- package/src/{keysJson.js → jsonKeys.js} +2 -2
- package/test/BrowserFileTree.test.js +10 -0
- package/test/FileTree.test.js +12 -2
- package/test/MapTree.test.js +10 -0
- package/test/ObjectTree.test.js +10 -0
- package/test/SetTree.test.js +11 -0
- package/test/SiteTree.test.js +10 -0
- package/test/jsonKeys.test.js +24 -0
- /package/src/{keysJson.d.ts → jsonKeys.d.ts} +0 -0
package/browser.js
CHANGED
|
@@ -4,10 +4,10 @@ export { default as DeferredTree } from "./src/DeferredTree.js";
|
|
|
4
4
|
// Skip FileTree.js, which is Node.js only.
|
|
5
5
|
export { default as BrowserFileTree } from "./src/BrowserFileTree.js";
|
|
6
6
|
export { default as FunctionTree } from "./src/FunctionTree.js";
|
|
7
|
+
export * as keysJson from "./src/jsonKeys.js";
|
|
7
8
|
export { default as MapTree } from "./src/MapTree.js";
|
|
8
9
|
export { default as ObjectTree } from "./src/ObjectTree.js";
|
|
9
10
|
export { default as SetTree } from "./src/SetTree.js";
|
|
10
11
|
export { default as SiteTree } from "./src/SiteTree.js";
|
|
11
12
|
export * as Tree from "./src/Tree.js";
|
|
12
|
-
export * as keysJson from "./src/keysJson.js";
|
|
13
13
|
export * from "./src/utilities.js";
|
package/main.js
CHANGED
|
@@ -6,10 +6,8 @@ export { default as FunctionTree } from "./src/FunctionTree.js";
|
|
|
6
6
|
export { default as MapTree } from "./src/MapTree.js";
|
|
7
7
|
// Skip BrowserFileTree.js, which is browser-only.
|
|
8
8
|
export { default as DeepMapTree } from "./src/DeepMapTree.js";
|
|
9
|
-
export { default as SetTree } from "./src/SetTree.js";
|
|
10
|
-
export { default as SiteTree } from "./src/SiteTree.js";
|
|
11
9
|
export { DeepObjectTree, ObjectTree, Tree } from "./src/internal.js";
|
|
12
|
-
export * as
|
|
10
|
+
export * as jsonKeys from "./src/jsonKeys.js";
|
|
13
11
|
export { default as cache } from "./src/operations/cache.js";
|
|
14
12
|
export { default as concat } from "./src/operations/concat.js";
|
|
15
13
|
export { default as deepMerge } from "./src/operations/deepMerge.js";
|
|
@@ -24,6 +22,8 @@ export { default as merge } from "./src/operations/merge.js";
|
|
|
24
22
|
export { default as scope } from "./src/operations/scope.js";
|
|
25
23
|
export { default as sort } from "./src/operations/sort.js";
|
|
26
24
|
export { default as take } from "./src/operations/take.js";
|
|
25
|
+
export { default as SetTree } from "./src/SetTree.js";
|
|
26
|
+
export { default as SiteTree } from "./src/SiteTree.js";
|
|
27
27
|
export * as symbols from "./src/symbols.js";
|
|
28
28
|
export { default as cachedKeyFunctions } from "./src/transforms/cachedKeyFunctions.js";
|
|
29
29
|
export { default as deepReverse } from "./src/transforms/deepReverse.js";
|
package/package.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weborigami/async-tree",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.65-beta.1",
|
|
4
4
|
"description": "Asynchronous tree drivers based on standard JavaScript classes",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./main.js",
|
|
7
7
|
"browser": "./browser.js",
|
|
8
8
|
"types": "./index.ts",
|
|
9
9
|
"devDependencies": {
|
|
10
|
-
"@types/node": "
|
|
11
|
-
"typescript": "5.5.
|
|
10
|
+
"@types/node": "22.5.4",
|
|
11
|
+
"typescript": "5.5.4"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@weborigami/types": "0.0.
|
|
14
|
+
"@weborigami/types": "0.0.65-beta.1"
|
|
15
15
|
},
|
|
16
16
|
"scripts": {
|
|
17
17
|
"test": "node --test --test-reporter=spec",
|
package/src/BrowserFileTree.js
CHANGED
|
@@ -34,6 +34,13 @@ export default class BrowserFileTree {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
async get(key) {
|
|
37
|
+
if (key == null) {
|
|
38
|
+
// Reject nullish key.
|
|
39
|
+
throw new ReferenceError(
|
|
40
|
+
`${this.constructor.name}: Cannot get a null or undefined key.`
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
37
44
|
const directory = await this.getDirectory();
|
|
38
45
|
|
|
39
46
|
// Try the key as a subfolder name.
|
package/src/FileTree.js
CHANGED
|
@@ -49,9 +49,11 @@ export default class FileTree {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
async get(key) {
|
|
52
|
-
if (
|
|
53
|
-
//
|
|
54
|
-
|
|
52
|
+
if (key == null) {
|
|
53
|
+
// Reject nullish key.
|
|
54
|
+
throw new ReferenceError(
|
|
55
|
+
`${this.constructor.name}: Cannot get a null or undefined key.`
|
|
56
|
+
);
|
|
55
57
|
}
|
|
56
58
|
|
|
57
59
|
const filePath = path.resolve(this.dirname, key);
|
package/src/MapTree.js
CHANGED
|
@@ -23,6 +23,13 @@ export default class MapTree {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
async get(key) {
|
|
26
|
+
if (key == null) {
|
|
27
|
+
// Reject nullish key.
|
|
28
|
+
throw new ReferenceError(
|
|
29
|
+
`${this.constructor.name}: Cannot get a null or undefined key.`
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
26
33
|
const value = this.map.get(key);
|
|
27
34
|
setParent(value, this);
|
|
28
35
|
return value;
|
package/src/ObjectTree.js
CHANGED
|
@@ -25,15 +25,11 @@ export default class ObjectTree {
|
|
|
25
25
|
* @param {any} key
|
|
26
26
|
*/
|
|
27
27
|
async get(key) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
key &&
|
|
34
|
-
!this.object.hasOwnProperty(key)
|
|
35
|
-
) {
|
|
36
|
-
return undefined;
|
|
28
|
+
if (key == null) {
|
|
29
|
+
// Reject nullish key.
|
|
30
|
+
throw new ReferenceError(
|
|
31
|
+
`${this.constructor.name}: Cannot get a null or undefined key.`
|
|
32
|
+
);
|
|
37
33
|
}
|
|
38
34
|
|
|
39
35
|
let value = await this.object[key];
|
package/src/SetTree.js
CHANGED
|
@@ -17,6 +17,13 @@ export default class SetTree {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
async get(key) {
|
|
20
|
+
if (key == null) {
|
|
21
|
+
// Reject nullish key.
|
|
22
|
+
throw new ReferenceError(
|
|
23
|
+
`${this.constructor.name}: Cannot get a null or undefined key.`
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
20
27
|
const value = this.values[key];
|
|
21
28
|
setParent(value, this);
|
|
22
29
|
return value;
|
package/src/SiteTree.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Tree } from "./internal.js";
|
|
2
|
-
import * as
|
|
2
|
+
import * as jsonKeys from "./jsonKeys.js";
|
|
3
3
|
import { setParent } from "./utilities.js";
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -31,6 +31,13 @@ export default class SiteTree {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
async get(key) {
|
|
34
|
+
if (key == null) {
|
|
35
|
+
// Reject nullish key.
|
|
36
|
+
throw new ReferenceError(
|
|
37
|
+
`${this.constructor.name}: Cannot get a null or undefined key.`
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
34
41
|
// If there is only one key and it's the empty string, and the site is
|
|
35
42
|
// explorable, we take the route as "index.html". With this and subsequent
|
|
36
43
|
// checks, we try to avoid sniffing the site to see if it's explorable, as
|
|
@@ -86,7 +93,7 @@ export default class SiteTree {
|
|
|
86
93
|
.then((response) => (response.ok ? response.text() : null))
|
|
87
94
|
.then((text) => {
|
|
88
95
|
try {
|
|
89
|
-
return text ?
|
|
96
|
+
return text ? jsonKeys.parse(text) : null;
|
|
90
97
|
} catch (error) {
|
|
91
98
|
// Got a response, but it's not JSON. Most likely the site doesn't
|
|
92
99
|
// actually have a .keys.json file, and is returning a Not Found page,
|
package/src/Tree.js
CHANGED
|
@@ -402,13 +402,10 @@ export async function traverse(treelike, ...keys) {
|
|
|
402
402
|
* @param {...any} keys
|
|
403
403
|
*/
|
|
404
404
|
export async function traverseOrThrow(treelike, ...keys) {
|
|
405
|
-
if (!treelike) {
|
|
406
|
-
throw new TraverseError("Tried to traverse a null or undefined value");
|
|
407
|
-
}
|
|
408
|
-
|
|
409
405
|
// Start our traversal at the root of the tree.
|
|
410
406
|
/** @type {any} */
|
|
411
407
|
let value = treelike;
|
|
408
|
+
let position = 0;
|
|
412
409
|
|
|
413
410
|
// If traversal operation was called with a `this` context, use that as the
|
|
414
411
|
// target for function calls.
|
|
@@ -419,13 +416,11 @@ export async function traverseOrThrow(treelike, ...keys) {
|
|
|
419
416
|
let key;
|
|
420
417
|
while (remainingKeys.length > 0) {
|
|
421
418
|
if (value === undefined) {
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
.join("/")}`;
|
|
428
|
-
throw new TraverseError(message, treelike, keys);
|
|
419
|
+
throw new TraverseError("Tried to traverse a null or undefined value", {
|
|
420
|
+
tree: treelike,
|
|
421
|
+
keys,
|
|
422
|
+
position,
|
|
423
|
+
});
|
|
429
424
|
}
|
|
430
425
|
|
|
431
426
|
// If the value is packed and can be unpacked, unpack it.
|
|
@@ -466,6 +461,8 @@ export async function traverseOrThrow(treelike, ...keys) {
|
|
|
466
461
|
value = originalValue;
|
|
467
462
|
}
|
|
468
463
|
}
|
|
464
|
+
|
|
465
|
+
position++;
|
|
469
466
|
}
|
|
470
467
|
|
|
471
468
|
return value;
|
|
@@ -485,11 +482,10 @@ export async function traversePath(tree, path) {
|
|
|
485
482
|
|
|
486
483
|
// Error class thrown by traverseOrThrow()
|
|
487
484
|
class TraverseError extends ReferenceError {
|
|
488
|
-
constructor(message,
|
|
485
|
+
constructor(message, options) {
|
|
489
486
|
super(message);
|
|
490
|
-
this.tree = tree;
|
|
491
487
|
this.name = "TraverseError";
|
|
492
|
-
this
|
|
488
|
+
Object.assign(this, options);
|
|
493
489
|
}
|
|
494
490
|
}
|
|
495
491
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Tree } from "./internal.js";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* The
|
|
5
|
-
*
|
|
4
|
+
* The JSON Keys protocol lets a site expose the keys of a node in the site so
|
|
5
|
+
* that they can be read by SiteTree.
|
|
6
6
|
*
|
|
7
7
|
* This file format is a JSON array of key descriptors: a string like
|
|
8
8
|
* "index.html" for a specific resource available at the node, or a string with
|
|
@@ -30,6 +30,16 @@ if (isBrowser) {
|
|
|
30
30
|
assert.equal(await fixture.get("xyz"), undefined);
|
|
31
31
|
});
|
|
32
32
|
|
|
33
|
+
test("getting a null/undefined key throws an exception", async () => {
|
|
34
|
+
const fixture = await createFixture();
|
|
35
|
+
await assert.rejects(async () => {
|
|
36
|
+
await fixture.get(null);
|
|
37
|
+
});
|
|
38
|
+
await assert.rejects(async () => {
|
|
39
|
+
await fixture.get(undefined);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
33
43
|
test("can set a value", async () => {
|
|
34
44
|
const fixture = await createFixture();
|
|
35
45
|
|
package/test/FileTree.test.js
CHANGED
|
@@ -11,7 +11,7 @@ const tempDirectory = path.join(dirname, "fixtures/temp");
|
|
|
11
11
|
|
|
12
12
|
const textDecoder = new TextDecoder();
|
|
13
13
|
|
|
14
|
-
describe
|
|
14
|
+
describe("FileTree", async () => {
|
|
15
15
|
test("can get the keys of the tree", async () => {
|
|
16
16
|
const fixture = createFixture("fixtures/markdown");
|
|
17
17
|
assert.deepEqual(Array.from(await fixture.keys()), [
|
|
@@ -33,6 +33,16 @@ describe.only("FileTree", async () => {
|
|
|
33
33
|
assert.equal(await fixture.get("xyz"), undefined);
|
|
34
34
|
});
|
|
35
35
|
|
|
36
|
+
test("getting a null/undefined key throws an exception", async () => {
|
|
37
|
+
const fixture = createFixture("fixtures/markdown");
|
|
38
|
+
await assert.rejects(async () => {
|
|
39
|
+
await fixture.get(null);
|
|
40
|
+
});
|
|
41
|
+
await assert.rejects(async () => {
|
|
42
|
+
await fixture.get(undefined);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
36
46
|
test("sets parent on subtrees", async () => {
|
|
37
47
|
const fixture = createFixture("fixtures");
|
|
38
48
|
const markdown = await fixture.get("markdown");
|
|
@@ -64,7 +74,7 @@ describe.only("FileTree", async () => {
|
|
|
64
74
|
await removeTempDirectory();
|
|
65
75
|
});
|
|
66
76
|
|
|
67
|
-
test
|
|
77
|
+
test("can create empty subfolder via set()", async () => {
|
|
68
78
|
await createTempDirectory();
|
|
69
79
|
|
|
70
80
|
// Write out new, empty folder called "empty".
|
package/test/MapTree.test.js
CHANGED
|
@@ -26,6 +26,16 @@ describe("MapTree", () => {
|
|
|
26
26
|
const fixture = createFixture();
|
|
27
27
|
assert.equal(await fixture.get("d"), undefined);
|
|
28
28
|
});
|
|
29
|
+
|
|
30
|
+
test("getting a null/undefined key throws an exception", async () => {
|
|
31
|
+
const fixture = createFixture();
|
|
32
|
+
await assert.rejects(async () => {
|
|
33
|
+
await fixture.get(null);
|
|
34
|
+
});
|
|
35
|
+
await assert.rejects(async () => {
|
|
36
|
+
await fixture.get(undefined);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
29
39
|
});
|
|
30
40
|
|
|
31
41
|
function createFixture() {
|
package/test/ObjectTree.test.js
CHANGED
|
@@ -24,6 +24,16 @@ describe("ObjectTree", () => {
|
|
|
24
24
|
assert.equal(await fixture.get("xyz"), undefined);
|
|
25
25
|
});
|
|
26
26
|
|
|
27
|
+
test("getting a null/undefined key throws an exception", async () => {
|
|
28
|
+
const fixture = createFixture();
|
|
29
|
+
await assert.rejects(async () => {
|
|
30
|
+
await fixture.get(null);
|
|
31
|
+
});
|
|
32
|
+
await assert.rejects(async () => {
|
|
33
|
+
await fixture.get(undefined);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
27
37
|
test("can set a value", async () => {
|
|
28
38
|
const tree = new ObjectTree({
|
|
29
39
|
a: 1,
|
package/test/SetTree.test.js
CHANGED
|
@@ -23,6 +23,17 @@ describe("SetTree", () => {
|
|
|
23
23
|
assert.equal(await fixture.get(3), undefined);
|
|
24
24
|
});
|
|
25
25
|
|
|
26
|
+
test("getting a null/undefined key throws an exception", async () => {
|
|
27
|
+
const set = new Set(["a", "b", "c"]);
|
|
28
|
+
const fixture = new SetTree(set);
|
|
29
|
+
await assert.rejects(async () => {
|
|
30
|
+
await fixture.get(null);
|
|
31
|
+
});
|
|
32
|
+
await assert.rejects(async () => {
|
|
33
|
+
await fixture.get(undefined);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
26
37
|
test("sets parent on subtrees", async () => {
|
|
27
38
|
const set = new Set();
|
|
28
39
|
set.add(new ObjectTree({}));
|
package/test/SiteTree.test.js
CHANGED
|
@@ -66,6 +66,16 @@ describe("SiteTree", () => {
|
|
|
66
66
|
assert.equal(await fixture.get("xyz"), undefined);
|
|
67
67
|
});
|
|
68
68
|
|
|
69
|
+
test("getting a null/undefined key throws an exception", async () => {
|
|
70
|
+
const fixture = new SiteTree(mockHost);
|
|
71
|
+
await assert.rejects(async () => {
|
|
72
|
+
await fixture.get(null);
|
|
73
|
+
});
|
|
74
|
+
await assert.rejects(async () => {
|
|
75
|
+
await fixture.get(undefined);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
69
79
|
test("a redirect on a site with keys returns a SiteTree for the new URL", async () => {
|
|
70
80
|
const fixture = new SiteTree(mockHost);
|
|
71
81
|
const about = await fixture.get("about");
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { DeepObjectTree } from "@weborigami/async-tree";
|
|
2
|
+
import assert from "node:assert";
|
|
3
|
+
import { describe, test } from "node:test";
|
|
4
|
+
import * as jsonKeys from "../src/jsonKeys.js";
|
|
5
|
+
|
|
6
|
+
describe("jsonKeys", () => {
|
|
7
|
+
test("parses JSON Keys", async () => {
|
|
8
|
+
const json = '["index.html","about/"]';
|
|
9
|
+
const parsed = jsonKeys.parse(json);
|
|
10
|
+
assert.deepStrictEqual(parsed, {
|
|
11
|
+
"index.html": false,
|
|
12
|
+
about: true,
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test("stringifies JSON Keys", async () => {
|
|
17
|
+
const tree = new DeepObjectTree({
|
|
18
|
+
about: {},
|
|
19
|
+
"index.html": "Home",
|
|
20
|
+
});
|
|
21
|
+
const json = await jsonKeys.stringify(tree);
|
|
22
|
+
assert.strictEqual(json, '["about/","index.html"]');
|
|
23
|
+
});
|
|
24
|
+
});
|
|
File without changes
|