@weborigami/async-tree 0.0.61 → 0.0.62
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/package.json +2 -2
- package/src/BrowserFileTree.js +12 -3
- package/src/DeepObjectTree.js +1 -5
- package/src/FileTree.js +7 -4
- package/src/FunctionTree.js +3 -1
- package/src/MapTree.js +3 -6
- package/src/ObjectTree.js +5 -7
- package/src/SetTree.js +3 -6
- package/src/SiteTree.js +11 -4
- package/src/Tree.d.ts +1 -1
- package/src/Tree.js +23 -18
- package/src/operations/deepMerge.js +1 -1
- package/src/operations/deepTakeFn.js +1 -1
- package/src/operations/deepValuesIterator.js +1 -1
- package/src/operations/merge.js +16 -7
- package/src/transforms/mapFn.js +2 -6
- package/src/utilities.d.ts +4 -1
- package/src/utilities.js +51 -0
- package/test/ObjectTree.test.js +9 -0
- package/test/Tree.test.js +19 -0
- package/test/operations/deepValues.test.js +6 -0
- package/test/operations/merge.test.js +3 -2
- package/test/utilities.test.js +50 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weborigami/async-tree",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.62",
|
|
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.5.3"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@weborigami/types": "0.0.
|
|
14
|
+
"@weborigami/types": "0.0.62"
|
|
15
15
|
},
|
|
16
16
|
"scripts": {
|
|
17
17
|
"test": "node --test --test-reporter=spec",
|
package/src/BrowserFileTree.js
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { Tree } from "./internal.js";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
hiddenFileNames,
|
|
4
|
+
isStringLike,
|
|
5
|
+
naturalOrder,
|
|
6
|
+
setParent,
|
|
7
|
+
} from "./utilities.js";
|
|
3
8
|
|
|
4
9
|
const TypedArray = Object.getPrototypeOf(Uint8Array);
|
|
5
10
|
|
|
@@ -34,7 +39,9 @@ export default class BrowserFileTree {
|
|
|
34
39
|
// Try the key as a subfolder name.
|
|
35
40
|
try {
|
|
36
41
|
const subfolderHandle = await directory.getDirectoryHandle(key);
|
|
37
|
-
|
|
42
|
+
const value = Reflect.construct(this.constructor, [subfolderHandle]);
|
|
43
|
+
setParent(value, this);
|
|
44
|
+
return value;
|
|
38
45
|
} catch (error) {
|
|
39
46
|
if (
|
|
40
47
|
!(
|
|
@@ -50,7 +57,9 @@ export default class BrowserFileTree {
|
|
|
50
57
|
try {
|
|
51
58
|
const fileHandle = await directory.getFileHandle(key);
|
|
52
59
|
const file = await fileHandle.getFile();
|
|
53
|
-
|
|
60
|
+
const buffer = file.arrayBuffer();
|
|
61
|
+
setParent(buffer, this);
|
|
62
|
+
return buffer;
|
|
54
63
|
} catch (error) {
|
|
55
64
|
if (!(error instanceof DOMException && error.name === "NotFoundError")) {
|
|
56
65
|
throw error;
|
package/src/DeepObjectTree.js
CHANGED
|
@@ -12,15 +12,11 @@ export default class DeepObjectTree extends ObjectTree {
|
|
|
12
12
|
value = Reflect.construct(this.constructor, [value]);
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
if (Tree.isAsyncTree(value) && !value.parent) {
|
|
16
|
-
value.parent = this;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
15
|
return value;
|
|
20
16
|
}
|
|
21
17
|
|
|
22
18
|
async isKeyForSubtree(key) {
|
|
23
|
-
const value = this.object[key];
|
|
19
|
+
const value = await this.object[key];
|
|
24
20
|
return isPlainObject(value) || Tree.isAsyncTree(value);
|
|
25
21
|
}
|
|
26
22
|
}
|
package/src/FileTree.js
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
isPacked,
|
|
9
9
|
isPlainObject,
|
|
10
10
|
naturalOrder,
|
|
11
|
+
setParent,
|
|
11
12
|
} from "./utilities.js";
|
|
12
13
|
|
|
13
14
|
/**
|
|
@@ -65,16 +66,18 @@ export default class FileTree {
|
|
|
65
66
|
throw error;
|
|
66
67
|
}
|
|
67
68
|
|
|
69
|
+
let value;
|
|
68
70
|
if (stats.isDirectory()) {
|
|
69
71
|
// Return subdirectory as a tree
|
|
70
|
-
|
|
71
|
-
subtree.parent = this;
|
|
72
|
-
return subtree;
|
|
72
|
+
value = Reflect.construct(this.constructor, [filePath]);
|
|
73
73
|
} else {
|
|
74
74
|
// Return file contents as a standard Uint8Array.
|
|
75
75
|
const buffer = await fs.readFile(filePath);
|
|
76
|
-
|
|
76
|
+
value = Uint8Array.from(buffer);
|
|
77
77
|
}
|
|
78
|
+
|
|
79
|
+
setParent(value, this);
|
|
80
|
+
return value;
|
|
78
81
|
}
|
|
79
82
|
|
|
80
83
|
async isKeyForSubtree(key) {
|
package/src/FunctionTree.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { setParent } from "./utilities.js";
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* A tree defined by a function and an optional domain.
|
|
3
5
|
*
|
|
@@ -30,7 +32,7 @@ export default class FunctionTree {
|
|
|
30
32
|
// eventually bind all parameters until only one remains. At that point,
|
|
31
33
|
// the above condition will apply and the function will be invoked.
|
|
32
34
|
Reflect.construct(this.constructor, [this.fn.bind(null, key)]);
|
|
33
|
-
|
|
35
|
+
setParent(value, this);
|
|
34
36
|
return value;
|
|
35
37
|
}
|
|
36
38
|
|
package/src/MapTree.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Tree } from "./internal.js";
|
|
2
|
+
import { setParent } from "./utilities.js";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* A tree backed by a JavaScript `Map` object.
|
|
@@ -22,12 +23,8 @@ export default class MapTree {
|
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
async get(key) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
if (Tree.isAsyncTree(value) && !value.parent) {
|
|
28
|
-
value.parent = this;
|
|
29
|
-
}
|
|
30
|
-
|
|
26
|
+
const value = this.map.get(key);
|
|
27
|
+
setParent(value, this);
|
|
31
28
|
return value;
|
|
32
29
|
}
|
|
33
30
|
|
package/src/ObjectTree.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Tree } from "./internal.js";
|
|
2
|
-
import
|
|
2
|
+
import * as symbols from "./symbols.js";
|
|
3
|
+
import { getRealmObjectPrototype, setParent } from "./utilities.js";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* A tree defined by a plain object or array.
|
|
@@ -15,7 +16,7 @@ export default class ObjectTree {
|
|
|
15
16
|
*/
|
|
16
17
|
constructor(object) {
|
|
17
18
|
this.object = object;
|
|
18
|
-
this.parent = null;
|
|
19
|
+
this.parent = object[symbols.parent] ?? null;
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
/**
|
|
@@ -35,11 +36,8 @@ export default class ObjectTree {
|
|
|
35
36
|
return undefined;
|
|
36
37
|
}
|
|
37
38
|
|
|
38
|
-
let value = this.object[key];
|
|
39
|
-
|
|
40
|
-
if (Tree.isAsyncTree(value) && !value.parent) {
|
|
41
|
-
value.parent = this;
|
|
42
|
-
}
|
|
39
|
+
let value = await this.object[key];
|
|
40
|
+
setParent(value, this);
|
|
43
41
|
|
|
44
42
|
if (typeof value === "function" && !Object.hasOwn(this.object, key)) {
|
|
45
43
|
// Value is an inherited method; bind it to the object.
|
package/src/SetTree.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Tree } from "./internal.js";
|
|
2
|
+
import { setParent } from "./utilities.js";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* A tree of Set objects.
|
|
@@ -16,12 +17,8 @@ export default class SetTree {
|
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
async get(key) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if (Tree.isAsyncTree(value) && !value.parent) {
|
|
22
|
-
value.parent = this;
|
|
23
|
-
}
|
|
24
|
-
|
|
20
|
+
const value = this.values[key];
|
|
21
|
+
setParent(value, this);
|
|
25
22
|
return value;
|
|
26
23
|
}
|
|
27
24
|
|
package/src/SiteTree.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Tree } from "./internal.js";
|
|
2
2
|
import * as keysJson from "./keysJson.js";
|
|
3
|
+
import { setParent } from "./utilities.js";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* A tree of values obtained via HTTP/HTTPS calls. These values will be strings
|
|
@@ -44,7 +45,9 @@ export default class SiteTree {
|
|
|
44
45
|
// If the (possibly adjusted) route ends with a slash and the site is an
|
|
45
46
|
// explorable site, we return a tree for the indicated route.
|
|
46
47
|
if (href.endsWith("/") && (await this.hasKeysJson())) {
|
|
47
|
-
|
|
48
|
+
const value = Reflect.construct(this.constructor, [href]);
|
|
49
|
+
setParent(value, this);
|
|
50
|
+
return value;
|
|
48
51
|
}
|
|
49
52
|
|
|
50
53
|
// Fetch the data at the given route.
|
|
@@ -67,9 +70,13 @@ export default class SiteTree {
|
|
|
67
70
|
}
|
|
68
71
|
|
|
69
72
|
const mediaType = response.headers?.get("Content-Type");
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
+
if (SiteTree.mediaTypeIsText(mediaType)) {
|
|
74
|
+
return response.text();
|
|
75
|
+
} else {
|
|
76
|
+
const buffer = response.arrayBuffer();
|
|
77
|
+
setParent(buffer, this);
|
|
78
|
+
return buffer;
|
|
79
|
+
}
|
|
73
80
|
}
|
|
74
81
|
|
|
75
82
|
async getKeyDictionary() {
|
package/src/Tree.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export function assign(target: Treelike, source: Treelike): Promise<AsyncTree>;
|
|
|
5
5
|
export function clear(AsyncTree: AsyncMutableTree): Promise<void>;
|
|
6
6
|
export function entries(AsyncTree: AsyncTree): Promise<IterableIterator<any>>;
|
|
7
7
|
export function forEach(AsyncTree: AsyncTree, callbackfn: (value: any, key: any) => Promise<void>): Promise<void>;
|
|
8
|
-
export function from(obj: any): AsyncTree;
|
|
8
|
+
export function from(obj: any, options?: { deep: boolean }): AsyncTree;
|
|
9
9
|
export function has(AsyncTree: AsyncTree, key: any): Promise<boolean>;
|
|
10
10
|
export function isAsyncMutableTree(obj: any): obj is AsyncMutableTree;
|
|
11
11
|
export function isAsyncTree(obj: any): obj is AsyncTree;
|
package/src/Tree.js
CHANGED
|
@@ -103,32 +103,37 @@ export async function forEach(tree, callbackFn) {
|
|
|
103
103
|
/**
|
|
104
104
|
* Attempts to cast the indicated object to an async tree.
|
|
105
105
|
*
|
|
106
|
-
*
|
|
106
|
+
* If the object is a plain object, it will be converted to an ObjectTree. The
|
|
107
|
+
* optional `deep` option can be set to `true` to convert a plain object to a
|
|
108
|
+
* DeepObjectTree.
|
|
109
|
+
*
|
|
110
|
+
* @param {Treelike | Object} object
|
|
111
|
+
* @param {{ deep?: true }} [options]
|
|
107
112
|
* @returns {AsyncTree}
|
|
108
113
|
*/
|
|
109
|
-
export function from(
|
|
110
|
-
if (isAsyncTree(
|
|
114
|
+
export function from(object, options = {}) {
|
|
115
|
+
if (isAsyncTree(object)) {
|
|
111
116
|
// Argument already supports the tree interface.
|
|
112
117
|
// @ts-ignore
|
|
113
|
-
return
|
|
114
|
-
} else if (typeof
|
|
115
|
-
return new FunctionTree(
|
|
116
|
-
} else if (
|
|
117
|
-
return new MapTree(
|
|
118
|
-
} else if (
|
|
119
|
-
return new SetTree(
|
|
120
|
-
} else if (isPlainObject(
|
|
121
|
-
return new DeepObjectTree(
|
|
122
|
-
} else if (isUnpackable(
|
|
118
|
+
return object;
|
|
119
|
+
} else if (typeof object === "function") {
|
|
120
|
+
return new FunctionTree(object);
|
|
121
|
+
} else if (object instanceof Map) {
|
|
122
|
+
return new MapTree(object);
|
|
123
|
+
} else if (object instanceof Set) {
|
|
124
|
+
return new SetTree(object);
|
|
125
|
+
} else if (isPlainObject(object) || object instanceof Array) {
|
|
126
|
+
return options.deep ? new DeepObjectTree(object) : new ObjectTree(object);
|
|
127
|
+
} else if (isUnpackable(object)) {
|
|
123
128
|
async function AsyncFunction() {} // Sample async function
|
|
124
|
-
return
|
|
129
|
+
return object.unpack instanceof AsyncFunction.constructor
|
|
125
130
|
? // Async unpack: return a deferred tree.
|
|
126
|
-
new DeferredTree(
|
|
131
|
+
new DeferredTree(object.unpack)
|
|
127
132
|
: // Synchronous unpack: cast the result of unpack() to a tree.
|
|
128
|
-
from(
|
|
129
|
-
} else if (
|
|
133
|
+
from(object.unpack());
|
|
134
|
+
} else if (object && typeof object === "object") {
|
|
130
135
|
// An instance of some class.
|
|
131
|
-
return new ObjectTree(
|
|
136
|
+
return new ObjectTree(object);
|
|
132
137
|
}
|
|
133
138
|
|
|
134
139
|
throw new TypeError("Couldn't convert argument to an async tree");
|
|
@@ -8,7 +8,7 @@ import { Tree } from "../internal.js";
|
|
|
8
8
|
* @returns {AsyncTree & { description: string }}
|
|
9
9
|
*/
|
|
10
10
|
export default function deepMerge(...sources) {
|
|
11
|
-
let trees = sources.map((treelike) => Tree.from(treelike));
|
|
11
|
+
let trees = sources.map((treelike) => Tree.from(treelike, { deep: true }));
|
|
12
12
|
let mergeParent;
|
|
13
13
|
return {
|
|
14
14
|
description: "deepMerge",
|
|
@@ -11,7 +11,7 @@ export default function deepTakeFn(count) {
|
|
|
11
11
|
* @param {import("../../index.ts").Treelike} treelike
|
|
12
12
|
*/
|
|
13
13
|
return async function deepTakeFn(treelike) {
|
|
14
|
-
const tree = await Tree.from(treelike);
|
|
14
|
+
const tree = await Tree.from(treelike, { deep: true });
|
|
15
15
|
const { values } = await traverse(tree, count);
|
|
16
16
|
return Tree.from(values);
|
|
17
17
|
};
|
|
@@ -14,7 +14,7 @@ export default async function* deepValuesIterator(
|
|
|
14
14
|
treelike,
|
|
15
15
|
options = { expand: false }
|
|
16
16
|
) {
|
|
17
|
-
const tree = Tree.from(treelike);
|
|
17
|
+
const tree = Tree.from(treelike, { deep: true });
|
|
18
18
|
for (const key of await tree.keys()) {
|
|
19
19
|
let value = await tree.get(key);
|
|
20
20
|
|
package/src/operations/merge.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Tree } from "../internal.js";
|
|
2
|
+
import * as symbols from "../symbols.js";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Return a tree that performs a shallow merge of the given trees.
|
|
@@ -14,18 +15,24 @@ import { Tree } from "../internal.js";
|
|
|
14
15
|
* @returns {AsyncTree & { description: string, trees: AsyncTree[]}}
|
|
15
16
|
*/
|
|
16
17
|
export default function merge(...sources) {
|
|
18
|
+
const trees = sources.map((treelike) => Tree.from(treelike));
|
|
17
19
|
return {
|
|
18
20
|
description: "merge",
|
|
19
21
|
|
|
20
22
|
async get(key) {
|
|
21
23
|
// Check trees for the indicated key in reverse order.
|
|
22
|
-
for (let index =
|
|
23
|
-
const tree =
|
|
24
|
+
for (let index = trees.length - 1; index >= 0; index--) {
|
|
25
|
+
const tree = trees[index];
|
|
24
26
|
const value = await tree.get(key);
|
|
25
27
|
if (value !== undefined) {
|
|
28
|
+
// Merged tree acts as parent instead of the source tree.
|
|
26
29
|
if (Tree.isAsyncTree(value) && value.parent === tree) {
|
|
27
|
-
// Merged tree acts as parent instead of the source tree.
|
|
28
30
|
value.parent = this;
|
|
31
|
+
} else if (
|
|
32
|
+
typeof value === "object" &&
|
|
33
|
+
value?.[symbols.parent] === tree
|
|
34
|
+
) {
|
|
35
|
+
value[symbols.parent] = this;
|
|
29
36
|
}
|
|
30
37
|
return value;
|
|
31
38
|
}
|
|
@@ -35,8 +42,8 @@ export default function merge(...sources) {
|
|
|
35
42
|
|
|
36
43
|
async isKeyForSubtree(key) {
|
|
37
44
|
// Check trees for the indicated key in reverse order.
|
|
38
|
-
for (let index =
|
|
39
|
-
if (await Tree.isKeyForSubtree(
|
|
45
|
+
for (let index = trees.length - 1; index >= 0; index--) {
|
|
46
|
+
if (await Tree.isKeyForSubtree(trees[index], key)) {
|
|
40
47
|
return true;
|
|
41
48
|
}
|
|
42
49
|
}
|
|
@@ -46,7 +53,7 @@ export default function merge(...sources) {
|
|
|
46
53
|
async keys() {
|
|
47
54
|
const keys = new Set();
|
|
48
55
|
// Collect keys in the order the trees were provided.
|
|
49
|
-
for (const tree of
|
|
56
|
+
for (const tree of trees) {
|
|
50
57
|
for (const key of await tree.keys()) {
|
|
51
58
|
keys.add(key);
|
|
52
59
|
}
|
|
@@ -54,6 +61,8 @@ export default function merge(...sources) {
|
|
|
54
61
|
return keys;
|
|
55
62
|
},
|
|
56
63
|
|
|
57
|
-
trees
|
|
64
|
+
get trees() {
|
|
65
|
+
return trees;
|
|
66
|
+
},
|
|
58
67
|
};
|
|
59
68
|
}
|
package/src/transforms/mapFn.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { isPlainObject } from "../utilities.js";
|
|
1
|
+
import { Tree } from "../internal.js";
|
|
3
2
|
|
|
4
3
|
/**
|
|
5
4
|
* Return a transform function that maps the keys and/or values of a tree.
|
|
@@ -47,10 +46,7 @@ export default function createMapTransform(options = {}) {
|
|
|
47
46
|
* @type {import("../../index.ts").TreeTransform}
|
|
48
47
|
*/
|
|
49
48
|
return function map(treelike) {
|
|
50
|
-
const tree =
|
|
51
|
-
!deep && isPlainObject(treelike) && !Tree.isAsyncTree(treelike)
|
|
52
|
-
? new ObjectTree(treelike)
|
|
53
|
-
: Tree.from(treelike);
|
|
49
|
+
const tree = Tree.from(treelike, { deep });
|
|
54
50
|
|
|
55
51
|
// The transformed tree is actually an extension of the original tree's
|
|
56
52
|
// prototype chain. This allows the transformed tree to inherit any
|
package/src/utilities.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { AsyncTree } from "@weborigami/types";
|
|
1
2
|
import { Packed, PlainObject, StringLike } from "../index.ts";
|
|
2
3
|
|
|
4
|
+
export function box(value: any): any;
|
|
3
5
|
export function castArrayLike(object: any): any;
|
|
4
6
|
export function getRealmObjectPrototype(object: any): any;
|
|
5
7
|
export const hiddenFileNames: string[];
|
|
@@ -10,5 +12,6 @@ export function isStringLike(object: any): object is StringLike;
|
|
|
10
12
|
export function keysFromPath(path: string): string[];
|
|
11
13
|
export const naturalOrder: (a: string, b: string) => number;
|
|
12
14
|
export function pipeline(start: any, ...functions: Function[]): Promise<any>;
|
|
15
|
+
export function setParent(child: any, parent: AsyncTree): void;
|
|
13
16
|
export function toPlainValue(object: any): Promise<any>;
|
|
14
|
-
export function toString(object: any): string;
|
|
17
|
+
export function toString(object: any): string;
|
package/src/utilities.js
CHANGED
|
@@ -1,8 +1,30 @@
|
|
|
1
1
|
import { Tree } from "./internal.js";
|
|
2
|
+
import * as symbols from "./symbols.js";
|
|
2
3
|
|
|
3
4
|
const textDecoder = new TextDecoder();
|
|
4
5
|
const TypedArray = Object.getPrototypeOf(Uint8Array);
|
|
5
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Return the value as an object. If the value is already an object it will be
|
|
9
|
+
* returned as is. If the value is a primitive, it will be wrapped in an object:
|
|
10
|
+
* a string will be wrapped in a String object, a number will be wrapped in a
|
|
11
|
+
* Number object, and a boolean will be wrapped in a Boolean object.
|
|
12
|
+
*
|
|
13
|
+
* @param {any} value
|
|
14
|
+
*/
|
|
15
|
+
export function box(value) {
|
|
16
|
+
switch (typeof value) {
|
|
17
|
+
case "string":
|
|
18
|
+
return new String(value);
|
|
19
|
+
case "number":
|
|
20
|
+
return new Number(value);
|
|
21
|
+
case "boolean":
|
|
22
|
+
return new Boolean(value);
|
|
23
|
+
default:
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
6
28
|
/**
|
|
7
29
|
* If the given plain object has only sequential integer keys, return it as an
|
|
8
30
|
* array. Otherwise return it as is.
|
|
@@ -170,6 +192,35 @@ export async function pipeline(start, ...fns) {
|
|
|
170
192
|
return fns.reduce(async (acc, fn) => fn(await acc), start);
|
|
171
193
|
}
|
|
172
194
|
|
|
195
|
+
/**
|
|
196
|
+
* If the child object doesn't have a parent yet, set it to the indicated
|
|
197
|
+
* parent. If the child is an AsyncTree, set the `parent` property. Otherwise,
|
|
198
|
+
* set the `symbols.parent` property.
|
|
199
|
+
*
|
|
200
|
+
* @param {*} child
|
|
201
|
+
* @param {*} parent
|
|
202
|
+
*/
|
|
203
|
+
export function setParent(child, parent) {
|
|
204
|
+
if (Tree.isAsyncTree(child)) {
|
|
205
|
+
// Value is a subtree; set its parent to this tree.
|
|
206
|
+
if (!child.parent) {
|
|
207
|
+
child.parent = parent;
|
|
208
|
+
}
|
|
209
|
+
} else if (Object.isExtensible(child) && !child[symbols.parent]) {
|
|
210
|
+
// Add parent reference as a symbol to avoid polluting the object. This
|
|
211
|
+
// reference will be used if the object is later used as a tree. We set
|
|
212
|
+
// `enumerable` to false even thought this makes no practical difference
|
|
213
|
+
// (symbols are never enumerated) because it can provide a hint in the
|
|
214
|
+
// debugger that the property is for internal use.
|
|
215
|
+
Object.defineProperty(child, symbols.parent, {
|
|
216
|
+
configurable: true,
|
|
217
|
+
enumerable: false,
|
|
218
|
+
value: parent,
|
|
219
|
+
writable: true,
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
173
224
|
/**
|
|
174
225
|
* Convert the given input to the plainest possible JavaScript value. This
|
|
175
226
|
* helper is intended for functions that want to accept an argument from the ori
|
package/test/ObjectTree.test.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import assert from "node:assert";
|
|
2
2
|
import { describe, test } from "node:test";
|
|
3
3
|
import { ObjectTree, Tree } from "../src/internal.js";
|
|
4
|
+
import * as symbols from "../src/symbols.js";
|
|
4
5
|
|
|
5
6
|
describe("ObjectTree", () => {
|
|
6
7
|
test("can get the keys of the tree", async () => {
|
|
@@ -76,6 +77,14 @@ describe("ObjectTree", () => {
|
|
|
76
77
|
assert.equal(await fixture.get("prop"), "Goodbye");
|
|
77
78
|
});
|
|
78
79
|
|
|
80
|
+
test("sets parent symbol on subobjects", async () => {
|
|
81
|
+
const fixture = new ObjectTree({
|
|
82
|
+
sub: {},
|
|
83
|
+
});
|
|
84
|
+
const sub = await fixture.get("sub");
|
|
85
|
+
assert.equal(sub[symbols.parent], fixture);
|
|
86
|
+
});
|
|
87
|
+
|
|
79
88
|
test("sets parent on subtrees", async () => {
|
|
80
89
|
const fixture = new ObjectTree({
|
|
81
90
|
a: 1,
|
package/test/Tree.test.js
CHANGED
|
@@ -97,6 +97,16 @@ describe("Tree", () => {
|
|
|
97
97
|
});
|
|
98
98
|
});
|
|
99
99
|
|
|
100
|
+
test("from returns a deep object tree if deep option is true", async () => {
|
|
101
|
+
const obj = {
|
|
102
|
+
sub: {
|
|
103
|
+
a: 1,
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
const tree = Tree.from(obj, { deep: true });
|
|
107
|
+
assert(tree instanceof DeepObjectTree);
|
|
108
|
+
});
|
|
109
|
+
|
|
100
110
|
test("from() creates a deferred tree if unpack() returns a promise", async () => {
|
|
101
111
|
const obj = new String();
|
|
102
112
|
/** @type {any} */ (obj).unpack = async () => ({
|
|
@@ -232,6 +242,15 @@ describe("Tree", () => {
|
|
|
232
242
|
assert.deepEqual(plain, original);
|
|
233
243
|
});
|
|
234
244
|
|
|
245
|
+
test("plain() awaits async properties", async () => {
|
|
246
|
+
const object = {
|
|
247
|
+
get name() {
|
|
248
|
+
return Promise.resolve("Alice");
|
|
249
|
+
},
|
|
250
|
+
};
|
|
251
|
+
assert.deepEqual(await Tree.plain(object), { name: "Alice" });
|
|
252
|
+
});
|
|
253
|
+
|
|
235
254
|
test("remove method removes a value", async () => {
|
|
236
255
|
const fixture = createFixture();
|
|
237
256
|
await Tree.remove(fixture, "Alice.md");
|
|
@@ -19,4 +19,10 @@ describe("deepValues", () => {
|
|
|
19
19
|
const values = await deepValues(tree);
|
|
20
20
|
assert.deepEqual(values, [1, 2, 3, 4]);
|
|
21
21
|
});
|
|
22
|
+
|
|
23
|
+
test("returns in-order array of values in nested arrays", async () => {
|
|
24
|
+
const tree = [1, [2, 3], 4];
|
|
25
|
+
const values = await deepValues(tree);
|
|
26
|
+
assert.deepEqual(values, [1, 2, 3, 4]);
|
|
27
|
+
});
|
|
22
28
|
});
|
|
@@ -2,6 +2,7 @@ import assert from "node:assert";
|
|
|
2
2
|
import { describe, test } from "node:test";
|
|
3
3
|
import { Tree } from "../../src/internal.js";
|
|
4
4
|
import merge from "../../src/operations/merge.js";
|
|
5
|
+
import * as symbols from "../../src/symbols.js";
|
|
5
6
|
|
|
6
7
|
describe("merge", () => {
|
|
7
8
|
test("performs a shallow merge", async () => {
|
|
@@ -38,7 +39,7 @@ describe("merge", () => {
|
|
|
38
39
|
assert.equal(c, undefined);
|
|
39
40
|
|
|
40
41
|
// Parent of a subvalue is the merged tree
|
|
41
|
-
const b = await
|
|
42
|
-
assert.equal(b.parent, fixture);
|
|
42
|
+
const b = await fixture.get("b");
|
|
43
|
+
assert.equal(b[symbols.parent], fixture);
|
|
43
44
|
});
|
|
44
45
|
});
|
package/test/utilities.test.js
CHANGED
|
@@ -1,8 +1,31 @@
|
|
|
1
1
|
import assert from "node:assert";
|
|
2
2
|
import { describe, test } from "node:test";
|
|
3
|
+
import { symbols } from "../main.js";
|
|
4
|
+
import { ObjectTree } from "../src/internal.js";
|
|
3
5
|
import * as utilities from "../src/utilities.js";
|
|
4
6
|
|
|
5
7
|
describe("utilities", () => {
|
|
8
|
+
test("box returns a boxed value", () => {
|
|
9
|
+
const string = "string";
|
|
10
|
+
const stringObject = utilities.box(string);
|
|
11
|
+
assert(stringObject instanceof String);
|
|
12
|
+
assert.equal(stringObject, string);
|
|
13
|
+
|
|
14
|
+
const number = 1;
|
|
15
|
+
const numberObject = utilities.box(number);
|
|
16
|
+
assert(numberObject instanceof Number);
|
|
17
|
+
assert.equal(numberObject, number);
|
|
18
|
+
|
|
19
|
+
const boolean = true;
|
|
20
|
+
const booleanObject = utilities.box(boolean);
|
|
21
|
+
assert(booleanObject instanceof Boolean);
|
|
22
|
+
assert.equal(booleanObject, boolean);
|
|
23
|
+
|
|
24
|
+
const object = {};
|
|
25
|
+
const boxedObject = utilities.box(object);
|
|
26
|
+
assert.equal(boxedObject, object);
|
|
27
|
+
});
|
|
28
|
+
|
|
6
29
|
test("getRealmObjectPrototype returns the object's root prototype", () => {
|
|
7
30
|
const object = {};
|
|
8
31
|
const proto = utilities.getRealmObjectPrototype(object);
|
|
@@ -36,6 +59,33 @@ describe("utilities", () => {
|
|
|
36
59
|
assert.equal(result, 16);
|
|
37
60
|
});
|
|
38
61
|
|
|
62
|
+
test("setParent sets a child's parent", () => {
|
|
63
|
+
const parent = new ObjectTree({});
|
|
64
|
+
|
|
65
|
+
// Set [symbols.parent] on a plain object.
|
|
66
|
+
const object = {};
|
|
67
|
+
utilities.setParent(object, parent);
|
|
68
|
+
assert.equal(object[symbols.parent], parent);
|
|
69
|
+
|
|
70
|
+
// Leave [symbols.parent] alone if it's already set.
|
|
71
|
+
const childWithParent = {
|
|
72
|
+
[symbols.parent]: "parent",
|
|
73
|
+
};
|
|
74
|
+
utilities.setParent(childWithParent, parent);
|
|
75
|
+
assert.equal(childWithParent[symbols.parent], "parent");
|
|
76
|
+
|
|
77
|
+
// Set `parent` on a tree.
|
|
78
|
+
const tree = new ObjectTree({});
|
|
79
|
+
utilities.setParent(tree, parent);
|
|
80
|
+
assert.equal(tree.parent, parent);
|
|
81
|
+
|
|
82
|
+
// Leave `parent` alone if it's already set.
|
|
83
|
+
const treeWithParent = new ObjectTree({});
|
|
84
|
+
treeWithParent.parent = "parent";
|
|
85
|
+
utilities.setParent(treeWithParent, parent);
|
|
86
|
+
assert.equal(treeWithParent.parent, "parent");
|
|
87
|
+
});
|
|
88
|
+
|
|
39
89
|
test("toPlainValue returns the plainest representation of an object", async () => {
|
|
40
90
|
class User {
|
|
41
91
|
constructor(name) {
|