@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 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 keysJson from "./src/keysJson.js";
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.64",
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": "20.14.9",
11
- "typescript": "5.5.3"
10
+ "@types/node": "22.5.4",
11
+ "typescript": "5.5.4"
12
12
  },
13
13
  "dependencies": {
14
- "@weborigami/types": "0.0.64"
14
+ "@weborigami/types": "0.0.65-beta.1"
15
15
  },
16
16
  "scripts": {
17
17
  "test": "node --test --test-reporter=spec",
@@ -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 (!key) {
53
- // Undefined key or empty string key is invalid.
54
- return undefined;
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
- // If the value is an array, we require that the key be one of its own
29
- // properties: we don't want to return Array prototype methods like `map`
30
- // and `find`.
31
- if (
32
- this.object instanceof Array &&
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 keysJson from "./keysJson.js";
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 ? keysJson.parse(text) : null;
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
- // Attempted to traverse an undefined value
423
- const message = key
424
- ? `${key} does not exist`
425
- : `Couldn't traverse the path: ${keys
426
- .map((key) => String(key))
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, tree, keys) {
485
+ constructor(message, options) {
489
486
  super(message);
490
- this.tree = tree;
491
487
  this.name = "TraverseError";
492
- this.keys = keys;
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 .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.
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
 
@@ -11,7 +11,7 @@ const tempDirectory = path.join(dirname, "fixtures/temp");
11
11
 
12
12
  const textDecoder = new TextDecoder();
13
13
 
14
- describe.only("FileTree", async () => {
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.only("can create empty subfolder via set()", async () => {
77
+ test("can create empty subfolder via set()", async () => {
68
78
  await createTempDirectory();
69
79
 
70
80
  // Write out new, empty folder called "empty".
@@ -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() {
@@ -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,
@@ -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({}));
@@ -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