@weborigami/async-tree 0.0.45 → 0.0.47
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/index.ts +18 -1
- package/main.js +4 -5
- package/package.json +4 -4
- package/src/BrowserFileTree.js +1 -1
- package/src/DeepMapTree.js +1 -1
- package/src/DeepObjectTree.js +1 -2
- package/src/DeferredTree.js +22 -9
- package/src/FileTree.js +24 -20
- package/src/FunctionTree.js +0 -4
- package/src/MapTree.js +1 -1
- package/src/ObjectTree.js +1 -1
- package/src/SetTree.js +1 -1
- package/src/SiteTree.js +1 -6
- package/src/Tree.d.ts +2 -1
- package/src/Tree.js +56 -45
- package/src/internal.js +15 -0
- package/src/keysJson.js +1 -1
- package/src/operations/cache.js +9 -5
- package/src/operations/mergeDeep.js +1 -1
- package/src/symbols.js +1 -0
- package/src/transforms/{cachedKeyMaps.js → cachedKeyFunctions.js} +10 -10
- package/src/transforms/groupBy.js +4 -4
- package/src/transforms/{keyMapsForExtensions.js → keyFunctionsForExtensions.js} +4 -4
- package/src/transforms/map.js +29 -28
- package/src/transforms/regExpKeys.js +3 -2
- package/src/transforms/sort.js +4 -1
- package/src/transforms/sortBy.js +4 -1
- package/src/utilities.d.ts +3 -1
- package/src/utilities.js +37 -9
- package/test/BrowserFileTree.test.js +1 -1
- package/test/DeepObjectTree.test.js +1 -2
- package/test/DeferredTree.test.js +1 -2
- package/test/FileTree.test.js +1 -1
- package/test/FunctionTree.test.js +0 -6
- package/test/ObjectTree.test.js +1 -2
- package/test/SetTree.test.js +1 -1
- package/test/SiteTree.test.js +1 -1
- package/test/Tree.test.js +12 -22
- package/test/operations/cache.test.js +1 -2
- package/test/operations/merge.test.js +1 -1
- package/test/operations/mergeDeep.test.js +1 -2
- package/test/transforms/cachedKeyMaps.test.js +13 -13
- package/test/transforms/groupBy.test.js +1 -1
- package/test/transforms/keyMapsForExtensions.test.js +20 -21
- package/test/transforms/map.test.js +36 -41
- package/test/transforms/regExpKeys.test.js +1 -2
- package/test/transforms/sort.test.js +1 -1
- package/test/transforms/sortBy.test.js +1 -1
- package/test/transforms/sortNatural.test.js +1 -1
package/index.ts
CHANGED
|
@@ -15,6 +15,12 @@ export type HasString = {
|
|
|
15
15
|
toString(): string;
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* A packed value is one that can be written to a file via fs.writeFile or into
|
|
20
|
+
* an HTTP response via response.write, or readily converted to such a form.
|
|
21
|
+
*/
|
|
22
|
+
export type Packed = ArrayBuffer | Buffer | ReadableStream | string | String | TypedArray;
|
|
23
|
+
|
|
18
24
|
export type PlainObject = {
|
|
19
25
|
[key: string]: any;
|
|
20
26
|
};
|
|
@@ -35,7 +41,18 @@ export type Treelike =
|
|
|
35
41
|
NativeTreelike |
|
|
36
42
|
Unpackable<NativeTreelike>;
|
|
37
43
|
|
|
38
|
-
export type TreeTransform = (
|
|
44
|
+
export type TreeTransform = (treelike: Treelike) => AsyncTree;
|
|
45
|
+
|
|
46
|
+
export type TypedArray =
|
|
47
|
+
Float32Array |
|
|
48
|
+
Float64Array |
|
|
49
|
+
Int8Array |
|
|
50
|
+
Int16Array |
|
|
51
|
+
Int32Array |
|
|
52
|
+
Uint8Array |
|
|
53
|
+
Uint8ClampedArray |
|
|
54
|
+
Uint16Array |
|
|
55
|
+
Uint32Array;
|
|
39
56
|
|
|
40
57
|
export type Unpackable<T> = {
|
|
41
58
|
unpack(): Promise<T>
|
package/main.js
CHANGED
|
@@ -4,20 +4,19 @@ export { default as DeferredTree } from "./src/DeferredTree.js";
|
|
|
4
4
|
export { default as FileTree } from "./src/FileTree.js";
|
|
5
5
|
export { default as FunctionTree } from "./src/FunctionTree.js";
|
|
6
6
|
export { default as MapTree } from "./src/MapTree.js";
|
|
7
|
-
export { default as ObjectTree } from "./src/ObjectTree.js";
|
|
8
7
|
// Skip BrowserFileTree.js, which is browser-only.
|
|
9
8
|
export { default as DeepMapTree } from "./src/DeepMapTree.js";
|
|
10
|
-
export { default as DeepObjectTree } from "./src/DeepObjectTree.js";
|
|
11
9
|
export { default as SetTree } from "./src/SetTree.js";
|
|
12
10
|
export { default as SiteTree } from "./src/SiteTree.js";
|
|
13
|
-
export
|
|
11
|
+
export { DeepObjectTree, ObjectTree, Tree } from "./src/internal.js";
|
|
14
12
|
export * as keysJson from "./src/keysJson.js";
|
|
15
13
|
export { default as cache } from "./src/operations/cache.js";
|
|
16
14
|
export { default as merge } from "./src/operations/merge.js";
|
|
17
15
|
export { default as mergeDeep } from "./src/operations/mergeDeep.js";
|
|
18
|
-
export
|
|
16
|
+
export * as symbols from "./src/symbols.js";
|
|
17
|
+
export { default as cachedKeyFunctions } from "./src/transforms/cachedKeyFunctions.js";
|
|
19
18
|
export { default as groupBy } from "./src/transforms/groupBy.js";
|
|
20
|
-
export { default as
|
|
19
|
+
export { default as keyFunctionsForExtensions } from "./src/transforms/keyFunctionsForExtensions.js";
|
|
21
20
|
export { default as map } from "./src/transforms/map.js";
|
|
22
21
|
export { default as sort } from "./src/transforms/sort.js";
|
|
23
22
|
export { default as sortBy } from "./src/transforms/sortBy.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.47",
|
|
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.11.
|
|
11
|
-
"typescript": "5.
|
|
10
|
+
"@types/node": "20.11.27",
|
|
11
|
+
"typescript": "5.4.2"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@weborigami/types": "0.0.
|
|
14
|
+
"@weborigami/types": "0.0.47"
|
|
15
15
|
},
|
|
16
16
|
"scripts": {
|
|
17
17
|
"test": "node --test --test-reporter=spec",
|
package/src/BrowserFileTree.js
CHANGED
package/src/DeepMapTree.js
CHANGED
package/src/DeepObjectTree.js
CHANGED
package/src/DeferredTree.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { Tree } from "./internal.js";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* A tree that is loaded lazily.
|
|
@@ -19,7 +19,8 @@ export default class DeferredTree {
|
|
|
19
19
|
this.loader = loader;
|
|
20
20
|
this.treePromise = null;
|
|
21
21
|
this._tree = null;
|
|
22
|
-
this.
|
|
22
|
+
this._parentUntilLoaded = null;
|
|
23
|
+
this._scopeUntilLoaded = null;
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
async get(key) {
|
|
@@ -39,14 +40,17 @@ export default class DeferredTree {
|
|
|
39
40
|
return tree.keys();
|
|
40
41
|
}
|
|
41
42
|
|
|
43
|
+
// A deferred tree's parent generally comes from the loaded tree. However, if
|
|
44
|
+
// someone tries to get or set the parent before the tree is loaded, we store
|
|
45
|
+
// that parent reference and apply it once the tree is loaded.
|
|
42
46
|
get parent() {
|
|
43
|
-
return this._tree?.parent ?? this.
|
|
47
|
+
return this._tree?.parent ?? this._parentUntilLoaded;
|
|
44
48
|
}
|
|
45
49
|
set parent(parent) {
|
|
46
50
|
if (this._tree && !this._tree.parent) {
|
|
47
51
|
this._tree.parent = parent;
|
|
48
52
|
} else {
|
|
49
|
-
this.
|
|
53
|
+
this._parentUntilLoaded = parent;
|
|
50
54
|
}
|
|
51
55
|
}
|
|
52
56
|
|
|
@@ -61,9 +65,11 @@ export default class DeferredTree {
|
|
|
61
65
|
return /** @type {any} */ (this._tree)?.scope;
|
|
62
66
|
}
|
|
63
67
|
set scope(scope) {
|
|
64
|
-
//
|
|
65
|
-
if (this._tree) {
|
|
68
|
+
// As with `parent`, we can defer setting of scope.
|
|
69
|
+
if (this._tree && !(/** @type {any} */ (this._tree).scope)) {
|
|
66
70
|
/** @type {any} */ (this._tree).scope = scope;
|
|
71
|
+
} else {
|
|
72
|
+
this._scopeUntilLoaded = scope;
|
|
67
73
|
}
|
|
68
74
|
}
|
|
69
75
|
|
|
@@ -75,11 +81,18 @@ export default class DeferredTree {
|
|
|
75
81
|
// Use a promise to ensure the treelike is only converted to a tree once.
|
|
76
82
|
this.treePromise ??= this.loadResult().then((treelike) => {
|
|
77
83
|
this._tree = Tree.from(treelike);
|
|
78
|
-
if (this.
|
|
84
|
+
if (this._parentUntilLoaded) {
|
|
85
|
+
// Now that the tree has been loaded, we can set its parent.
|
|
79
86
|
if (!this._tree.parent) {
|
|
80
|
-
this._tree.parent = this.
|
|
87
|
+
this._tree.parent = this._parentUntilLoaded;
|
|
88
|
+
}
|
|
89
|
+
this._parentUntilLoaded = null;
|
|
90
|
+
}
|
|
91
|
+
if (this._scopeUntilLoaded) {
|
|
92
|
+
if (!(/** @type {any} */ (this._tree).scope)) {
|
|
93
|
+
/** @type {any} */ (this._tree).scope = this._scopeUntilLoaded;
|
|
81
94
|
}
|
|
82
|
-
this.
|
|
95
|
+
this._scopeUntilLoaded = null;
|
|
83
96
|
}
|
|
84
97
|
return this._tree;
|
|
85
98
|
});
|
package/src/FileTree.js
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
4
|
-
import
|
|
4
|
+
import { Tree } from "./internal.js";
|
|
5
5
|
import {
|
|
6
6
|
getRealmObjectPrototype,
|
|
7
7
|
hiddenFileNames,
|
|
8
|
+
isPacked,
|
|
8
9
|
sortNatural,
|
|
9
10
|
} from "./utilities.js";
|
|
10
11
|
|
|
11
|
-
const TypedArray = Object.getPrototypeOf(Uint8Array);
|
|
12
|
-
|
|
13
12
|
/**
|
|
14
13
|
* A file system tree via the Node file system API.
|
|
15
14
|
*
|
|
@@ -128,29 +127,34 @@ export default class FileTree {
|
|
|
128
127
|
return this;
|
|
129
128
|
}
|
|
130
129
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
value =
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
if (value instanceof ArrayBuffer) {
|
|
137
|
-
// Convert ArrayBuffer to Uint8Array, which Node.js can write directly.
|
|
138
|
-
value = new Uint8Array(value);
|
|
130
|
+
if (typeof value === "function") {
|
|
131
|
+
// Invoke function; write out the result.
|
|
132
|
+
value = await value();
|
|
139
133
|
}
|
|
140
134
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
value
|
|
144
|
-
value
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
135
|
+
let packed = false;
|
|
136
|
+
if (value === null) {
|
|
137
|
+
// Treat null value as empty string; will create an empty file.
|
|
138
|
+
value = "";
|
|
139
|
+
packed = true;
|
|
140
|
+
} else if (isPacked(value)) {
|
|
141
|
+
packed = true;
|
|
142
|
+
} else if (typeof value.pack === "function") {
|
|
143
|
+
// Pack the value for writing.
|
|
144
|
+
value = await value.pack();
|
|
145
|
+
packed = true;
|
|
146
|
+
} else if (isStringLike(value)) {
|
|
148
147
|
// Value has a meaningful `toString` method, use that.
|
|
149
148
|
value = String(value);
|
|
150
|
-
|
|
149
|
+
packed = true;
|
|
151
150
|
}
|
|
152
151
|
|
|
153
|
-
if (
|
|
152
|
+
if (packed) {
|
|
153
|
+
// Single writeable value.
|
|
154
|
+
if (value instanceof ArrayBuffer) {
|
|
155
|
+
// Convert ArrayBuffer to Uint8Array, which Node.js can write directly.
|
|
156
|
+
value = new Uint8Array(value);
|
|
157
|
+
}
|
|
154
158
|
// Ensure this directory exists.
|
|
155
159
|
await fs.mkdir(this.dirname, { recursive: true });
|
|
156
160
|
// Write out the value as the contents of a file.
|
package/src/FunctionTree.js
CHANGED
package/src/MapTree.js
CHANGED
package/src/ObjectTree.js
CHANGED
package/src/SetTree.js
CHANGED
package/src/SiteTree.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { Tree } from "./internal.js";
|
|
2
2
|
import * as keysJson from "./keysJson.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -152,11 +152,6 @@ export default class SiteTree {
|
|
|
152
152
|
return Reflect.construct(this.constructor, [href]);
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
-
async unpack() {
|
|
156
|
-
const response = await fetch(this.href);
|
|
157
|
-
return response.ok ? response.arrayBuffer() : undefined;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
155
|
get url() {
|
|
161
156
|
return new URL(this.href);
|
|
162
157
|
}
|
package/src/Tree.d.ts
CHANGED
|
@@ -10,8 +10,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;
|
|
12
12
|
export function isKeyForSubtree(tree: AsyncTree, obj: any): Promise<boolean>;
|
|
13
|
+
export function isTraversable(obj: any): boolean;
|
|
13
14
|
export function isTreelike(obj: any): obj is Treelike;
|
|
14
|
-
export function map(tree: Treelike,
|
|
15
|
+
export function map(tree: Treelike, valueFn: ValueKeyFn): AsyncTree;
|
|
15
16
|
export function mapReduce(tree: Treelike, mapFn: ValueKeyFn|null, reduceFn: ReduceFn): Promise<any>;
|
|
16
17
|
export function plain(tree: Treelike): Promise<PlainObject>;
|
|
17
18
|
export function remove(AsyncTree: AsyncMutableTree, key: any): Promise<boolean>;
|
package/src/Tree.js
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import DeferredTree from "./DeferredTree.js";
|
|
2
2
|
import FunctionTree from "./FunctionTree.js";
|
|
3
3
|
import MapTree from "./MapTree.js";
|
|
4
|
-
import ObjectTree from "./ObjectTree.js";
|
|
5
4
|
import SetTree from "./SetTree.js";
|
|
5
|
+
import { DeepObjectTree, ObjectTree } from "./internal.js";
|
|
6
6
|
import mapTransform from "./transforms/map.js";
|
|
7
7
|
import * as utilities from "./utilities.js";
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
castArrayLike,
|
|
10
|
+
isPacked,
|
|
11
|
+
isPlainObject,
|
|
12
|
+
isUnpackable,
|
|
13
|
+
} from "./utilities.js";
|
|
9
14
|
|
|
10
15
|
/**
|
|
11
16
|
* Helper functions for working with async trees
|
|
@@ -112,7 +117,9 @@ export function from(obj) {
|
|
|
112
117
|
return new MapTree(obj);
|
|
113
118
|
} else if (obj instanceof Set) {
|
|
114
119
|
return new SetTree(obj);
|
|
115
|
-
} else if (obj
|
|
120
|
+
} else if (isPlainObject(obj)) {
|
|
121
|
+
return new DeepObjectTree(obj);
|
|
122
|
+
} else if (isUnpackable(obj)) {
|
|
116
123
|
async function AsyncFunction() {} // Sample async function
|
|
117
124
|
return obj.unpack instanceof AsyncFunction.constructor
|
|
118
125
|
? // Async unpack: return a deferred tree.
|
|
@@ -163,23 +170,52 @@ export function isAsyncMutableTree(object) {
|
|
|
163
170
|
return isAsyncTree(object) && typeof object.set === "function";
|
|
164
171
|
}
|
|
165
172
|
|
|
173
|
+
/**
|
|
174
|
+
* Return true if the indicated key produces or is expected to produce an
|
|
175
|
+
* async tree.
|
|
176
|
+
*
|
|
177
|
+
* This defers to the tree's own isKeyForSubtree method. If not found, this
|
|
178
|
+
* gets the value of that key and returns true if the value is an async
|
|
179
|
+
* tree.
|
|
180
|
+
*/
|
|
181
|
+
export async function isKeyForSubtree(tree, key) {
|
|
182
|
+
if (tree.isKeyForSubtree) {
|
|
183
|
+
return tree.isKeyForSubtree(key);
|
|
184
|
+
}
|
|
185
|
+
const value = await tree.get(key);
|
|
186
|
+
return isAsyncTree(value);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Return true if the object can be traversed via the `traverse()` method. The
|
|
191
|
+
* object must be either treelike or a packed object with an `unpack()` method.
|
|
192
|
+
*
|
|
193
|
+
* @param {any} object
|
|
194
|
+
*/
|
|
195
|
+
export function isTraversable(object) {
|
|
196
|
+
return (
|
|
197
|
+
isTreelike(object) ||
|
|
198
|
+
(isPacked(object) && /** @type {any} */ (object).unpack instanceof Function)
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
166
202
|
/**
|
|
167
203
|
* Returns true if the indicated object can be directly treated as an
|
|
168
204
|
* asynchronous tree. This includes:
|
|
169
205
|
*
|
|
170
206
|
* - An object that implements the AsyncTree interface (including
|
|
171
207
|
* AsyncTree instances)
|
|
172
|
-
* - An object that implements the `unpack()` method
|
|
173
208
|
* - A function
|
|
174
209
|
* - An `Array` instance
|
|
175
210
|
* - A `Map` instance
|
|
176
211
|
* - A `Set` instance
|
|
177
212
|
* - A plain object
|
|
178
213
|
*
|
|
179
|
-
* Note: the `from()` method accepts any JavaScript object, but `
|
|
214
|
+
* Note: the `from()` method accepts any JavaScript object, but `isTreelike`
|
|
180
215
|
* returns `false` for an object that isn't one of the above types.
|
|
181
216
|
*
|
|
182
217
|
* @param {any} object
|
|
218
|
+
* @returns {obj is Treelike}
|
|
183
219
|
*/
|
|
184
220
|
export function isTreelike(object) {
|
|
185
221
|
return (
|
|
@@ -187,36 +223,19 @@ export function isTreelike(object) {
|
|
|
187
223
|
object instanceof Function ||
|
|
188
224
|
object instanceof Array ||
|
|
189
225
|
object instanceof Set ||
|
|
190
|
-
object?.unpack instanceof Function ||
|
|
191
226
|
isPlainObject(object)
|
|
192
227
|
);
|
|
193
228
|
}
|
|
194
229
|
|
|
195
|
-
/**
|
|
196
|
-
* Return true if the indicated key produces or is expected to produce an
|
|
197
|
-
* async tree.
|
|
198
|
-
*
|
|
199
|
-
* This defers to the tree's own isKeyForSubtree method. If not found, this
|
|
200
|
-
* gets the value of that key and returns true if the value is an async
|
|
201
|
-
* tree.
|
|
202
|
-
*/
|
|
203
|
-
export async function isKeyForSubtree(tree, key) {
|
|
204
|
-
if (tree.isKeyForSubtree) {
|
|
205
|
-
return tree.isKeyForSubtree(key);
|
|
206
|
-
}
|
|
207
|
-
const value = await tree.get(key);
|
|
208
|
-
return isAsyncTree(value);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
230
|
/**
|
|
212
231
|
* Return a new tree with deeply-mapped values of the original tree.
|
|
213
232
|
*
|
|
214
233
|
* @param {Treelike} treelike
|
|
215
|
-
* @param {ValueKeyFn}
|
|
234
|
+
* @param {ValueKeyFn} valueFn
|
|
216
235
|
*/
|
|
217
|
-
export function map(treelike,
|
|
236
|
+
export function map(treelike, valueFn) {
|
|
218
237
|
const tree = from(treelike);
|
|
219
|
-
return mapTransform({ deep: true,
|
|
238
|
+
return mapTransform({ deep: true, value: valueFn })(tree);
|
|
220
239
|
}
|
|
221
240
|
|
|
222
241
|
/**
|
|
@@ -230,10 +249,10 @@ export function map(treelike, valueMap) {
|
|
|
230
249
|
* reduceFn, which should consolidate those into a single result.
|
|
231
250
|
*
|
|
232
251
|
* @param {Treelike} treelike
|
|
233
|
-
* @param {ValueKeyFn|null}
|
|
252
|
+
* @param {ValueKeyFn|null} valueFn
|
|
234
253
|
* @param {ReduceFn} reduceFn
|
|
235
254
|
*/
|
|
236
|
-
export async function mapReduce(treelike,
|
|
255
|
+
export async function mapReduce(treelike, valueFn, reduceFn) {
|
|
237
256
|
const tree = from(treelike);
|
|
238
257
|
|
|
239
258
|
// We're going to fire off all the get requests in parallel, as quickly as
|
|
@@ -244,9 +263,9 @@ export async function mapReduce(treelike, valueMap, reduceFn) {
|
|
|
244
263
|
tree.get(key).then((value) =>
|
|
245
264
|
// If the value is a subtree, recurse.
|
|
246
265
|
isAsyncTree(value)
|
|
247
|
-
? mapReduce(value,
|
|
248
|
-
:
|
|
249
|
-
?
|
|
266
|
+
? mapReduce(value, valueFn, reduceFn)
|
|
267
|
+
: valueFn
|
|
268
|
+
? valueFn(value, key, tree)
|
|
250
269
|
: value
|
|
251
270
|
)
|
|
252
271
|
);
|
|
@@ -364,24 +383,16 @@ export async function traverseOrThrow(treelike, ...keys) {
|
|
|
364
383
|
throw new TraverseError(message, treelike, keys);
|
|
365
384
|
}
|
|
366
385
|
|
|
367
|
-
//
|
|
368
|
-
if (
|
|
369
|
-
// Unpack the value if it defines an `unpack` function, otherwise return
|
|
370
|
-
// the value itself.
|
|
371
|
-
return typeof value.unpack === "function" ? await value.unpack() : value;
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
// If the value is not a function or async tree already, but can be
|
|
375
|
-
// unpacked, unpack it.
|
|
376
|
-
if (
|
|
377
|
-
!(value instanceof Function) &&
|
|
378
|
-
!isAsyncTree(value) &&
|
|
379
|
-
value.unpack instanceof Function
|
|
380
|
-
) {
|
|
386
|
+
// If the value is packed and can be unpacked, unpack it.
|
|
387
|
+
if (isUnpackable(value)) {
|
|
381
388
|
value = await value.unpack();
|
|
382
389
|
}
|
|
383
390
|
|
|
384
|
-
if
|
|
391
|
+
// Peek ahead: if there's only one key left and it's an empty string, return
|
|
392
|
+
// the value itself.
|
|
393
|
+
if (remainingKeys.length === 1 && remainingKeys[0] === "") {
|
|
394
|
+
return value;
|
|
395
|
+
} else if (value instanceof Function) {
|
|
385
396
|
// Value is a function: call it with the remaining keys.
|
|
386
397
|
const fn = value;
|
|
387
398
|
// We'll take as many keys as the function's length, but at least one.
|
package/src/internal.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
//
|
|
2
|
+
// This library includes a number of modules with circular dependencies. This
|
|
3
|
+
// module exists to explicitly set the loading order for those modules. To
|
|
4
|
+
// enforce use of this loading order, other modules should only load the modules
|
|
5
|
+
// below via this module.
|
|
6
|
+
//
|
|
7
|
+
// About this pattern:
|
|
8
|
+
// https://medium.com/visual-development/how-to-fix-nasty-circular-dependency-issues-once-and-for-all-in-javascript-typescript-a04c987cf0de
|
|
9
|
+
//
|
|
10
|
+
|
|
11
|
+
export * as Tree from "./Tree.js";
|
|
12
|
+
|
|
13
|
+
export { default as ObjectTree } from "./ObjectTree.js";
|
|
14
|
+
|
|
15
|
+
export { default as DeepObjectTree } from "./DeepObjectTree.js";
|
package/src/keysJson.js
CHANGED
package/src/operations/cache.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { DeepObjectTree, Tree } from "../internal.js";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Caches values from a source tree in a second cache tree. If no second tree is
|
|
@@ -9,15 +9,19 @@ import { ObjectTree, Tree } from "@weborigami/async-tree";
|
|
|
9
9
|
*
|
|
10
10
|
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
11
11
|
* @typedef {import("@weborigami/types").AsyncMutableTree} AsyncMutableTree
|
|
12
|
+
* @typedef {import("../../index.ts").Treelike} Treelike
|
|
12
13
|
*
|
|
13
|
-
* @param {
|
|
14
|
+
* @param {Treelike} sourceTreelike
|
|
14
15
|
* @param {AsyncMutableTree} [cacheTree]
|
|
15
|
-
* @param {
|
|
16
|
+
* @param {Treelike} [filterTreelike]
|
|
16
17
|
* @returns {AsyncTree & { description: string }}
|
|
17
18
|
*/
|
|
18
|
-
export default function treeCache(
|
|
19
|
+
export default function treeCache(sourceTreelike, cacheTree, filterTreelike) {
|
|
20
|
+
const source = Tree.from(sourceTreelike);
|
|
21
|
+
const filter = filterTreelike ? Tree.from(filterTreelike) : undefined;
|
|
22
|
+
|
|
19
23
|
/** @type {AsyncMutableTree} */
|
|
20
|
-
const cache = cacheTree ?? new
|
|
24
|
+
const cache = cacheTree ?? new DeepObjectTree({});
|
|
21
25
|
return {
|
|
22
26
|
description: "cache",
|
|
23
27
|
|
package/src/symbols.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const parent = Symbol("parent");
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { Tree } from "../internal.js";
|
|
2
2
|
|
|
3
3
|
const treeToCaches = new WeakMap();
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* Given a
|
|
7
|
-
* the original
|
|
6
|
+
* Given a key function, return a new key function and inverse key function that
|
|
7
|
+
* cache the results of the original.
|
|
8
8
|
*
|
|
9
9
|
* @typedef {import("../../index.ts").KeyFn} KeyFn
|
|
10
10
|
*
|
|
11
|
-
* @param {KeyFn}
|
|
11
|
+
* @param {KeyFn} keyFn
|
|
12
12
|
* @param {boolean} [deep]
|
|
13
|
-
* @returns {{
|
|
13
|
+
* @returns {{ key: KeyFn, inverseKey: KeyFn }}
|
|
14
14
|
*/
|
|
15
|
-
export default function createCachedKeysTransform(
|
|
15
|
+
export default function createCachedKeysTransform(keyFn, deep = false) {
|
|
16
16
|
return {
|
|
17
|
-
async
|
|
17
|
+
async inverseKey(resultKey, tree) {
|
|
18
18
|
const caches = getCachesForTree(tree);
|
|
19
19
|
|
|
20
20
|
// First check to see if we've already computed an source key for this
|
|
@@ -37,7 +37,7 @@ export default function createCachedKeysTransform(keyMap, deep = false) {
|
|
|
37
37
|
if (deep && (await Tree.isKeyForSubtree(tree, sourceKey))) {
|
|
38
38
|
computedResultKey = sourceKey;
|
|
39
39
|
} else {
|
|
40
|
-
computedResultKey = await
|
|
40
|
+
computedResultKey = await keyFn(sourceKey, tree);
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
caches.sourceKeyToResultKey.set(sourceKey, computedResultKey);
|
|
@@ -52,7 +52,7 @@ export default function createCachedKeysTransform(keyMap, deep = false) {
|
|
|
52
52
|
return undefined;
|
|
53
53
|
},
|
|
54
54
|
|
|
55
|
-
async
|
|
55
|
+
async key(sourceKey, tree) {
|
|
56
56
|
const keyMaps = getCachesForTree(tree);
|
|
57
57
|
|
|
58
58
|
// First check to see if we've already computed an result key for this
|
|
@@ -62,7 +62,7 @@ export default function createCachedKeysTransform(keyMap, deep = false) {
|
|
|
62
62
|
return keyMaps.sourceKeyToResultKey.get(sourceKey);
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
const resultKey = await
|
|
65
|
+
const resultKey = await keyFn(sourceKey, tree);
|
|
66
66
|
|
|
67
67
|
// Cache the mappings from source key <-> result key for next time.
|
|
68
68
|
keyMaps.sourceKeyToResultKey.set(sourceKey, resultKey);
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import * as Tree from "../Tree.js";
|
|
1
|
+
import { DeepObjectTree, Tree } from "../internal.js";
|
|
3
2
|
|
|
4
3
|
/**
|
|
5
4
|
* Given a function that returns a grouping key for a value, returns a transform
|
|
@@ -11,7 +10,8 @@ export default function createGroupByTransform(groupKeyFn) {
|
|
|
11
10
|
/**
|
|
12
11
|
* @type {import("../../index.ts").TreeTransform}
|
|
13
12
|
*/
|
|
14
|
-
return async function groupByTransform(
|
|
13
|
+
return async function groupByTransform(treelike) {
|
|
14
|
+
const tree = Tree.from(treelike);
|
|
15
15
|
const result = {};
|
|
16
16
|
for (const key of await tree.keys()) {
|
|
17
17
|
const value = await tree.get(key);
|
|
@@ -36,6 +36,6 @@ export default function createGroupByTransform(groupKeyFn) {
|
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
return new
|
|
39
|
+
return new DeepObjectTree(result);
|
|
40
40
|
};
|
|
41
41
|
}
|