@weborigami/async-tree 0.0.44 → 0.0.46
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 +1 -1
- package/main.js +2 -2
- package/package.json +2 -2
- package/src/FileTree.js +7 -4
- package/src/Tree.d.ts +1 -1
- package/src/Tree.js +8 -8
- package/src/operations/cache.js +7 -3
- package/src/transforms/{cachedKeyMaps.js → cachedKeyFunctions.js} +9 -9
- package/src/transforms/groupBy.js +2 -1
- package/src/transforms/{keyMapsForExtensions.js → keyFunctionsForExtensions.js} +4 -4
- package/src/transforms/map.js +28 -27
- package/src/transforms/regExpKeys.js +2 -1
- package/src/transforms/sort.js +4 -1
- package/src/transforms/sortBy.js +6 -2
- package/test/transforms/cachedKeyMaps.test.js +12 -12
- package/test/transforms/keyMapsForExtensions.test.js +19 -19
- package/test/transforms/map.test.js +36 -39
package/index.ts
CHANGED
|
@@ -35,7 +35,7 @@ export type Treelike =
|
|
|
35
35
|
NativeTreelike |
|
|
36
36
|
Unpackable<NativeTreelike>;
|
|
37
37
|
|
|
38
|
-
export type TreeTransform = (
|
|
38
|
+
export type TreeTransform = (treelike: Treelike) => AsyncTree;
|
|
39
39
|
|
|
40
40
|
export type Unpackable<T> = {
|
|
41
41
|
unpack(): Promise<T>
|
package/main.js
CHANGED
|
@@ -15,9 +15,9 @@ export * as keysJson from "./src/keysJson.js";
|
|
|
15
15
|
export { default as cache } from "./src/operations/cache.js";
|
|
16
16
|
export { default as merge } from "./src/operations/merge.js";
|
|
17
17
|
export { default as mergeDeep } from "./src/operations/mergeDeep.js";
|
|
18
|
-
export { default as
|
|
18
|
+
export { default as cachedKeyFunctions } from "./src/transforms/cachedKeyFunctions.js";
|
|
19
19
|
export { default as groupBy } from "./src/transforms/groupBy.js";
|
|
20
|
-
export { default as
|
|
20
|
+
export { default as keyFunctionsForExtensions } from "./src/transforms/keyFunctionsForExtensions.js";
|
|
21
21
|
export { default as map } from "./src/transforms/map.js";
|
|
22
22
|
export { default as sort } from "./src/transforms/sort.js";
|
|
23
23
|
export { default as sortBy } from "./src/transforms/sortBy.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.46",
|
|
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.3.3"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@weborigami/types": "0.0.
|
|
14
|
+
"@weborigami/types": "0.0.46"
|
|
15
15
|
},
|
|
16
16
|
"scripts": {
|
|
17
17
|
"test": "node --test --test-reporter=spec",
|
package/src/FileTree.js
CHANGED
|
@@ -128,12 +128,15 @@ export default class FileTree {
|
|
|
128
128
|
return this;
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
value =
|
|
131
|
+
if (typeof value === "function") {
|
|
132
|
+
// Invoke function; write out the result.
|
|
133
|
+
value = await value();
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
-
if (value
|
|
136
|
+
if (value === null) {
|
|
137
|
+
// Treat null value as empty string; will create an empty file.
|
|
138
|
+
value = "";
|
|
139
|
+
} else if (value instanceof ArrayBuffer) {
|
|
137
140
|
// Convert ArrayBuffer to Uint8Array, which Node.js can write directly.
|
|
138
141
|
value = new Uint8Array(value);
|
|
139
142
|
}
|
package/src/Tree.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ 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
13
|
export function isTreelike(obj: any): obj is Treelike;
|
|
14
|
-
export function map(tree: Treelike,
|
|
14
|
+
export function map(tree: Treelike, valueFn: ValueKeyFn): AsyncTree;
|
|
15
15
|
export function mapReduce(tree: Treelike, mapFn: ValueKeyFn|null, reduceFn: ReduceFn): Promise<any>;
|
|
16
16
|
export function plain(tree: Treelike): Promise<PlainObject>;
|
|
17
17
|
export function remove(AsyncTree: AsyncMutableTree, key: any): Promise<boolean>;
|
package/src/Tree.js
CHANGED
|
@@ -212,11 +212,11 @@ export async function isKeyForSubtree(tree, key) {
|
|
|
212
212
|
* Return a new tree with deeply-mapped values of the original tree.
|
|
213
213
|
*
|
|
214
214
|
* @param {Treelike} treelike
|
|
215
|
-
* @param {ValueKeyFn}
|
|
215
|
+
* @param {ValueKeyFn} valueFn
|
|
216
216
|
*/
|
|
217
|
-
export function map(treelike,
|
|
217
|
+
export function map(treelike, valueFn) {
|
|
218
218
|
const tree = from(treelike);
|
|
219
|
-
return mapTransform({ deep: true,
|
|
219
|
+
return mapTransform({ deep: true, value: valueFn })(tree);
|
|
220
220
|
}
|
|
221
221
|
|
|
222
222
|
/**
|
|
@@ -230,10 +230,10 @@ export function map(treelike, valueMap) {
|
|
|
230
230
|
* reduceFn, which should consolidate those into a single result.
|
|
231
231
|
*
|
|
232
232
|
* @param {Treelike} treelike
|
|
233
|
-
* @param {ValueKeyFn|null}
|
|
233
|
+
* @param {ValueKeyFn|null} valueFn
|
|
234
234
|
* @param {ReduceFn} reduceFn
|
|
235
235
|
*/
|
|
236
|
-
export async function mapReduce(treelike,
|
|
236
|
+
export async function mapReduce(treelike, valueFn, reduceFn) {
|
|
237
237
|
const tree = from(treelike);
|
|
238
238
|
|
|
239
239
|
// We're going to fire off all the get requests in parallel, as quickly as
|
|
@@ -244,9 +244,9 @@ export async function mapReduce(treelike, valueMap, reduceFn) {
|
|
|
244
244
|
tree.get(key).then((value) =>
|
|
245
245
|
// If the value is a subtree, recurse.
|
|
246
246
|
isAsyncTree(value)
|
|
247
|
-
? mapReduce(value,
|
|
248
|
-
:
|
|
249
|
-
?
|
|
247
|
+
? mapReduce(value, valueFn, reduceFn)
|
|
248
|
+
: valueFn
|
|
249
|
+
? valueFn(value, key, tree)
|
|
250
250
|
: value
|
|
251
251
|
)
|
|
252
252
|
);
|
package/src/operations/cache.js
CHANGED
|
@@ -9,13 +9,17 @@ 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
24
|
const cache = cacheTree ?? new ObjectTree({});
|
|
21
25
|
return {
|
|
@@ -3,18 +3,18 @@ import * as Tree from "../Tree.js";
|
|
|
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);
|
|
@@ -11,7 +11,8 @@ export default function createGroupByTransform(groupKeyFn) {
|
|
|
11
11
|
/**
|
|
12
12
|
* @type {import("../../index.ts").TreeTransform}
|
|
13
13
|
*/
|
|
14
|
-
return async function groupByTransform(
|
|
14
|
+
return async function groupByTransform(treelike) {
|
|
15
|
+
const tree = Tree.from(treelike);
|
|
15
16
|
const result = {};
|
|
16
17
|
for (const key of await tree.keys()) {
|
|
17
18
|
const value = await tree.get(key);
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
* Given a source resultExtension and a result resultExtension, return a pair of key
|
|
3
3
|
* functions that map between them.
|
|
4
4
|
*
|
|
5
|
-
* The resulting `
|
|
5
|
+
* The resulting `inverseKey` and `key` functions are compatible with those
|
|
6
6
|
* expected by map and other transforms.
|
|
7
7
|
*
|
|
8
8
|
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
9
9
|
* @param {{ resultExtension?: string, sourceExtension: string }}
|
|
10
10
|
* options
|
|
11
11
|
*/
|
|
12
|
-
export default function
|
|
12
|
+
export default function keyFunctionsForExtensions({
|
|
13
13
|
resultExtension,
|
|
14
14
|
sourceExtension,
|
|
15
15
|
}) {
|
|
@@ -18,12 +18,12 @@ export default function keyMapsForExtensions({
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
return {
|
|
21
|
-
async
|
|
21
|
+
async inverseKey(resultKey, tree) {
|
|
22
22
|
const basename = matchExtension(resultKey, resultExtension);
|
|
23
23
|
return basename ? `${basename}${dotPrefix(sourceExtension)}` : undefined;
|
|
24
24
|
},
|
|
25
25
|
|
|
26
|
-
async
|
|
26
|
+
async key(sourceKey, tree) {
|
|
27
27
|
const basename = matchExtension(sourceKey, sourceExtension);
|
|
28
28
|
return basename ? `${basename}${dotPrefix(resultExtension)}` : undefined;
|
|
29
29
|
},
|
package/src/transforms/map.js
CHANGED
|
@@ -6,63 +6,64 @@ import * as Tree from "../Tree.js";
|
|
|
6
6
|
* @typedef {import("../../index.ts").KeyFn} KeyFn
|
|
7
7
|
* @typedef {import("../../index.ts").ValueKeyFn} ValueKeyFn
|
|
8
8
|
*
|
|
9
|
-
* @param {ValueKeyFn|{ deep?: boolean, description?: string, needsSourceValue?: boolean,
|
|
9
|
+
* @param {ValueKeyFn|{ deep?: boolean, description?: string, needsSourceValue?: boolean, inverseKey?: KeyFn, key?: KeyFn, value?: ValueKeyFn }} options
|
|
10
10
|
*/
|
|
11
|
-
export default function createMapTransform(options) {
|
|
11
|
+
export default function createMapTransform(options = {}) {
|
|
12
12
|
let deep;
|
|
13
13
|
let description;
|
|
14
|
-
let
|
|
15
|
-
let
|
|
14
|
+
let inverseKeyFn;
|
|
15
|
+
let keyFn;
|
|
16
16
|
let needsSourceValue;
|
|
17
|
-
let
|
|
17
|
+
let valueFn;
|
|
18
18
|
if (typeof options === "function") {
|
|
19
|
-
// Take the single function argument as the
|
|
20
|
-
|
|
19
|
+
// Take the single function argument as the valueFn
|
|
20
|
+
valueFn = options;
|
|
21
21
|
} else {
|
|
22
22
|
deep = options.deep;
|
|
23
23
|
description = options.description;
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
inverseKeyFn = options.inverseKey;
|
|
25
|
+
keyFn = options.key;
|
|
26
26
|
needsSourceValue = options.needsSourceValue;
|
|
27
|
-
|
|
27
|
+
valueFn = options.value;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
deep ??= false;
|
|
31
31
|
description ??= "key/value map";
|
|
32
32
|
// @ts-ignore
|
|
33
|
-
|
|
33
|
+
inverseKeyFn ??= valueFn?.inverseKey;
|
|
34
34
|
// @ts-ignore
|
|
35
|
-
|
|
35
|
+
keyFn ??= valueFn?.key;
|
|
36
36
|
needsSourceValue ??= true;
|
|
37
37
|
|
|
38
|
-
if ((
|
|
38
|
+
if ((keyFn && !inverseKeyFn) || (!keyFn && inverseKeyFn)) {
|
|
39
39
|
throw new TypeError(
|
|
40
|
-
`map: You must specify both
|
|
40
|
+
`map: You must specify both key and inverseKey functions, or neither.`
|
|
41
41
|
);
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
/**
|
|
45
45
|
* @type {import("../../index.ts").TreeTransform}
|
|
46
46
|
*/
|
|
47
|
-
return function map(
|
|
47
|
+
return function map(treelike) {
|
|
48
|
+
const tree = Tree.from(treelike);
|
|
48
49
|
// The transformed tree is actually an extension of the original tree's
|
|
49
50
|
// prototype chain. This allows the transformed tree to inherit any
|
|
50
|
-
// properties/methods
|
|
51
|
-
//
|
|
51
|
+
// properties/methods. For example, the `parent` of the transformed tree is
|
|
52
|
+
// the original tree's parent.
|
|
52
53
|
const transformed = Object.create(tree);
|
|
53
54
|
|
|
54
55
|
transformed.description = description;
|
|
55
56
|
|
|
56
|
-
if (
|
|
57
|
+
if (keyFn || valueFn) {
|
|
57
58
|
transformed.get = async (resultKey) => {
|
|
58
59
|
// Step 1: Map the result key to the source key.
|
|
59
60
|
const isSubtree = deep && (await Tree.isKeyForSubtree(tree, resultKey));
|
|
60
61
|
const sourceKey =
|
|
61
|
-
!isSubtree &&
|
|
62
|
-
? await
|
|
62
|
+
!isSubtree && inverseKeyFn
|
|
63
|
+
? await inverseKeyFn(resultKey, tree)
|
|
63
64
|
: resultKey;
|
|
64
65
|
|
|
65
|
-
if (
|
|
66
|
+
if (sourceKey == null) {
|
|
66
67
|
// No source key means no value.
|
|
67
68
|
return undefined;
|
|
68
69
|
}
|
|
@@ -85,9 +86,9 @@ export default function createMapTransform(options) {
|
|
|
85
86
|
} else if (deep && Tree.isAsyncTree(sourceValue)) {
|
|
86
87
|
// Map a subtree.
|
|
87
88
|
resultValue = map(sourceValue);
|
|
88
|
-
} else if (
|
|
89
|
+
} else if (valueFn) {
|
|
89
90
|
// Map a single value.
|
|
90
|
-
resultValue = await
|
|
91
|
+
resultValue = await valueFn(sourceValue, sourceKey, tree);
|
|
91
92
|
} else {
|
|
92
93
|
// Return source value as is.
|
|
93
94
|
resultValue = sourceValue;
|
|
@@ -97,9 +98,9 @@ export default function createMapTransform(options) {
|
|
|
97
98
|
};
|
|
98
99
|
}
|
|
99
100
|
|
|
100
|
-
if (
|
|
101
|
+
if (keyFn) {
|
|
101
102
|
transformed.keys = async () => {
|
|
102
|
-
// Apply the
|
|
103
|
+
// Apply the keyFn to source keys for leaf values (not subtrees).
|
|
103
104
|
const sourceKeys = Array.from(await tree.keys());
|
|
104
105
|
const mapped = await Promise.all(
|
|
105
106
|
sourceKeys.map(async (sourceKey) => {
|
|
@@ -107,12 +108,12 @@ export default function createMapTransform(options) {
|
|
|
107
108
|
if (deep && (await Tree.isKeyForSubtree(tree, sourceKey))) {
|
|
108
109
|
resultKey = sourceKey;
|
|
109
110
|
} else {
|
|
110
|
-
resultKey = await
|
|
111
|
+
resultKey = await keyFn(sourceKey, tree);
|
|
111
112
|
}
|
|
112
113
|
return resultKey;
|
|
113
114
|
})
|
|
114
115
|
);
|
|
115
|
-
// Filter out any cases where the
|
|
116
|
+
// Filter out any cases where the keyFn returned undefined.
|
|
116
117
|
const resultKeys = mapped.filter((key) => key !== undefined);
|
|
117
118
|
return resultKeys;
|
|
118
119
|
};
|
|
@@ -10,7 +10,8 @@ import * as Tree from "../Tree.js";
|
|
|
10
10
|
*
|
|
11
11
|
* @type {import("../../index.ts").TreeTransform}
|
|
12
12
|
*/
|
|
13
|
-
export default async function regExpKeys(
|
|
13
|
+
export default async function regExpKeys(treelike) {
|
|
14
|
+
const tree = Tree.from(treelike);
|
|
14
15
|
const map = new Map();
|
|
15
16
|
|
|
16
17
|
// We build the output tree first so that we can refer to it when setting
|
package/src/transforms/sort.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import * as Tree from "../Tree.js";
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Return a transform function that sorts a tree's keys.
|
|
3
5
|
*
|
|
@@ -10,7 +12,8 @@ export default function createSortTransform(compareFn) {
|
|
|
10
12
|
/**
|
|
11
13
|
* @type {import("../../index.ts").TreeTransform}
|
|
12
14
|
*/
|
|
13
|
-
return function sortTransform(
|
|
15
|
+
return function sortTransform(treelike) {
|
|
16
|
+
const tree = Tree.from(treelike);
|
|
14
17
|
const transform = Object.create(tree);
|
|
15
18
|
transform.keys = async () => {
|
|
16
19
|
const keys = Array.from(await tree.keys());
|
package/src/transforms/sortBy.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import * as Tree from "../Tree.js";
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Return a transform function that sorts a tree's keys.
|
|
3
5
|
*
|
|
@@ -11,7 +13,8 @@ export default function createSortByTransform(sortKeyFn) {
|
|
|
11
13
|
/**
|
|
12
14
|
* @type {import("../../index.ts").TreeTransform}
|
|
13
15
|
*/
|
|
14
|
-
return function sortByTransform(
|
|
16
|
+
return function sortByTransform(treelike) {
|
|
17
|
+
const tree = Tree.from(treelike);
|
|
15
18
|
const transform = Object.create(tree);
|
|
16
19
|
transform.keys = async () => {
|
|
17
20
|
const keysAndSortKeys = [];
|
|
@@ -22,7 +25,8 @@ export default function createSortByTransform(sortKeyFn) {
|
|
|
22
25
|
keysAndSortKeys.sort((a, b) =>
|
|
23
26
|
a.sortKey < b.sortKey ? -1 : a.sortKey > b.sortKey ? 1 : 0
|
|
24
27
|
);
|
|
25
|
-
|
|
28
|
+
const sortedKeys = keysAndSortKeys.map(({ key }) => key);
|
|
29
|
+
return sortedKeys;
|
|
26
30
|
};
|
|
27
31
|
return transform;
|
|
28
32
|
};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import assert from "node:assert";
|
|
2
2
|
import { describe, test } from "node:test";
|
|
3
3
|
import ObjectTree from "../../src/ObjectTree.js";
|
|
4
|
-
import
|
|
4
|
+
import cachedKeyFunctions from "../../src/transforms/cachedKeyFunctions.js";
|
|
5
5
|
|
|
6
|
-
describe("
|
|
6
|
+
describe("cachedKeyFunctions", () => {
|
|
7
7
|
test("maps keys with caching", async () => {
|
|
8
8
|
const tree = new ObjectTree({
|
|
9
9
|
a: "letter a",
|
|
@@ -16,26 +16,26 @@ describe("cachedKeyMaps", () => {
|
|
|
16
16
|
return `_${sourceKey}`;
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
-
const {
|
|
19
|
+
const { inverseKey, key } = cachedKeyFunctions(underscoreKeys);
|
|
20
20
|
|
|
21
|
-
assert.equal(await
|
|
21
|
+
assert.equal(await inverseKey("_a", tree), "a"); // Cache miss
|
|
22
22
|
assert.equal(callCount, 1);
|
|
23
|
-
assert.equal(await
|
|
23
|
+
assert.equal(await inverseKey("_a", tree), "a");
|
|
24
24
|
assert.equal(callCount, 1);
|
|
25
|
-
assert.equal(await
|
|
25
|
+
assert.equal(await inverseKey("_b", tree), "b"); // Cache miss
|
|
26
26
|
assert.equal(callCount, 2);
|
|
27
27
|
|
|
28
|
-
assert.equal(await
|
|
29
|
-
assert.equal(await
|
|
30
|
-
assert.equal(await
|
|
28
|
+
assert.equal(await key("a", tree), "_a");
|
|
29
|
+
assert.equal(await key("a", tree), "_a");
|
|
30
|
+
assert.equal(await key("b", tree), "_b");
|
|
31
31
|
assert.equal(callCount, 2);
|
|
32
32
|
|
|
33
33
|
// `c` isn't in tree, so we should get undefined.
|
|
34
|
-
assert.equal(await
|
|
34
|
+
assert.equal(await inverseKey("_c", tree), undefined);
|
|
35
35
|
// But key mapping is still possible.
|
|
36
|
-
assert.equal(await
|
|
36
|
+
assert.equal(await key("c", tree), "_c");
|
|
37
37
|
// And now we have a cache hit.
|
|
38
|
-
assert.equal(await
|
|
38
|
+
assert.equal(await inverseKey("_c", tree), "c");
|
|
39
39
|
assert.equal(callCount, 3);
|
|
40
40
|
});
|
|
41
41
|
});
|
|
@@ -2,29 +2,29 @@ import assert from "node:assert";
|
|
|
2
2
|
import { describe, test } from "node:test";
|
|
3
3
|
import ObjectTree from "../../src/ObjectTree.js";
|
|
4
4
|
import * as Tree from "../../src/Tree.js";
|
|
5
|
-
import
|
|
5
|
+
import keyFunctionsForExtensions from "../../src/transforms/keyFunctionsForExtensions.js";
|
|
6
6
|
import map from "../../src/transforms/map.js";
|
|
7
7
|
|
|
8
8
|
describe("keyMapsForExtensions", () => {
|
|
9
9
|
test("returns key functions that pass a matching key through", async () => {
|
|
10
|
-
const {
|
|
10
|
+
const { inverseKey, key } = keyFunctionsForExtensions({
|
|
11
11
|
sourceExtension: "txt",
|
|
12
12
|
});
|
|
13
|
-
assert.equal(await
|
|
14
|
-
assert.equal(await
|
|
15
|
-
assert.equal(await
|
|
16
|
-
assert.equal(await
|
|
13
|
+
assert.equal(await inverseKey("file.txt"), "file.txt");
|
|
14
|
+
assert.equal(await key("file.txt"), "file.txt");
|
|
15
|
+
assert.equal(await inverseKey("file.foo"), undefined);
|
|
16
|
+
assert.equal(await key("file.foo"), undefined);
|
|
17
17
|
});
|
|
18
18
|
|
|
19
19
|
test("returns key functions that can map extensions", async () => {
|
|
20
|
-
const {
|
|
20
|
+
const { inverseKey, key } = keyFunctionsForExtensions({
|
|
21
21
|
resultExtension: "html",
|
|
22
22
|
sourceExtension: "md",
|
|
23
23
|
});
|
|
24
|
-
assert.equal(await
|
|
25
|
-
assert.equal(await
|
|
26
|
-
assert.equal(await
|
|
27
|
-
assert.equal(await
|
|
24
|
+
assert.equal(await inverseKey("file.html"), "file.md");
|
|
25
|
+
assert.equal(await key("file.md"), "file.html");
|
|
26
|
+
assert.equal(await inverseKey("file.foo"), undefined);
|
|
27
|
+
assert.equal(await key("file.foo"), undefined);
|
|
28
28
|
});
|
|
29
29
|
|
|
30
30
|
test("works with map to handle keys that end in a given resultExtension", async () => {
|
|
@@ -33,13 +33,13 @@ describe("keyMapsForExtensions", () => {
|
|
|
33
33
|
file2: "won't be mapped",
|
|
34
34
|
"file3.foo": "won't be mapped",
|
|
35
35
|
});
|
|
36
|
-
const {
|
|
36
|
+
const { inverseKey, key } = keyFunctionsForExtensions({
|
|
37
37
|
sourceExtension: "txt",
|
|
38
38
|
});
|
|
39
39
|
const transform = map({
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
inverseKey,
|
|
41
|
+
key,
|
|
42
|
+
value: (sourceValue, sourceKey, tree) => sourceValue.toUpperCase(),
|
|
43
43
|
});
|
|
44
44
|
const fixture = transform(files);
|
|
45
45
|
assert.deepEqual(await Tree.plain(fixture), {
|
|
@@ -53,14 +53,14 @@ describe("keyMapsForExtensions", () => {
|
|
|
53
53
|
file2: "won't be mapped",
|
|
54
54
|
"file3.foo": "won't be mapped",
|
|
55
55
|
});
|
|
56
|
-
const {
|
|
56
|
+
const { inverseKey, key } = keyFunctionsForExtensions({
|
|
57
57
|
resultExtension: "upper",
|
|
58
58
|
sourceExtension: "txt",
|
|
59
59
|
});
|
|
60
60
|
const transform = map({
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
inverseKey,
|
|
62
|
+
key,
|
|
63
|
+
value: (sourceValue, sourceKey, tree) => sourceValue.toUpperCase(),
|
|
64
64
|
});
|
|
65
65
|
const fixture = transform(files);
|
|
66
66
|
assert.deepEqual(await Tree.plain(fixture), {
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import assert from "node:assert";
|
|
2
2
|
import { describe, test } from "node:test";
|
|
3
|
+
import { ObjectTree } from "../../main.js";
|
|
3
4
|
import DeepObjectTree from "../../src/DeepObjectTree.js";
|
|
4
5
|
import FunctionTree from "../../src/FunctionTree.js";
|
|
5
|
-
import ObjectTree from "../../src/ObjectTree.js";
|
|
6
6
|
import * as Tree from "../../src/Tree.js";
|
|
7
7
|
import map from "../../src/transforms/map.js";
|
|
8
8
|
|
|
9
9
|
describe("map", () => {
|
|
10
|
-
test("returns identity graph if no
|
|
11
|
-
const tree =
|
|
10
|
+
test("returns identity graph if no key or value", async () => {
|
|
11
|
+
const tree = {
|
|
12
12
|
a: "letter a",
|
|
13
13
|
b: "letter b",
|
|
14
|
-
}
|
|
15
|
-
const mapped = map(
|
|
14
|
+
};
|
|
15
|
+
const mapped = map()(tree);
|
|
16
16
|
assert.deepEqual(await Tree.plain(mapped), {
|
|
17
17
|
a: "letter a",
|
|
18
18
|
b: "letter b",
|
|
@@ -26,7 +26,7 @@ describe("map", () => {
|
|
|
26
26
|
c: undefined, // Won't be mapped
|
|
27
27
|
});
|
|
28
28
|
const uppercaseValues = map({
|
|
29
|
-
|
|
29
|
+
value: (sourceValue, sourceKey, innerTree) => {
|
|
30
30
|
assert(sourceKey === "a" || sourceKey === "b");
|
|
31
31
|
assert.equal(innerTree, tree);
|
|
32
32
|
return sourceValue.toUpperCase();
|
|
@@ -41,13 +41,12 @@ describe("map", () => {
|
|
|
41
41
|
});
|
|
42
42
|
|
|
43
43
|
test("interprets a single function argument as the value function", async () => {
|
|
44
|
-
const tree =
|
|
44
|
+
const tree = {
|
|
45
45
|
a: "letter a",
|
|
46
46
|
b: "letter b",
|
|
47
|
-
}
|
|
48
|
-
const uppercaseValues = map((sourceValue, sourceKey,
|
|
47
|
+
};
|
|
48
|
+
const uppercaseValues = map((sourceValue, sourceKey, tree) => {
|
|
49
49
|
assert(sourceKey === "a" || sourceKey === "b");
|
|
50
|
-
assert.equal(innerTree, tree);
|
|
51
50
|
return sourceValue.toUpperCase();
|
|
52
51
|
});
|
|
53
52
|
const mapped = uppercaseValues(tree);
|
|
@@ -57,14 +56,14 @@ describe("map", () => {
|
|
|
57
56
|
});
|
|
58
57
|
});
|
|
59
58
|
|
|
60
|
-
test("maps keys using
|
|
61
|
-
const tree =
|
|
59
|
+
test("maps keys using key and inverseKey", async () => {
|
|
60
|
+
const tree = {
|
|
62
61
|
a: "letter a",
|
|
63
62
|
b: "letter b",
|
|
64
|
-
}
|
|
63
|
+
};
|
|
65
64
|
const doubleKeys = map({
|
|
66
|
-
|
|
67
|
-
|
|
65
|
+
key: async (sourceKey, tree) => `_${sourceKey}`,
|
|
66
|
+
inverseKey: async (resultKey, tree) => resultKey.slice(1),
|
|
68
67
|
});
|
|
69
68
|
const mapped = doubleKeys(tree);
|
|
70
69
|
assert.deepEqual(await Tree.plain(mapped), {
|
|
@@ -74,15 +73,14 @@ describe("map", () => {
|
|
|
74
73
|
});
|
|
75
74
|
|
|
76
75
|
test("maps keys and values", async () => {
|
|
77
|
-
const tree =
|
|
76
|
+
const tree = {
|
|
78
77
|
a: "letter a",
|
|
79
78
|
b: "letter b",
|
|
80
|
-
}
|
|
79
|
+
};
|
|
81
80
|
const doubleKeysUppercaseValues = map({
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
sourceValue.toUpperCase(),
|
|
81
|
+
key: async (sourceKey, tree) => `_${sourceKey}`,
|
|
82
|
+
inverseKey: async (resultKey, tree) => resultKey.slice(1),
|
|
83
|
+
value: async (sourceValue, sourceKey, tree) => sourceValue.toUpperCase(),
|
|
86
84
|
});
|
|
87
85
|
const mapped = doubleKeysUppercaseValues(tree);
|
|
88
86
|
assert.deepEqual(await Tree.plain(mapped), {
|
|
@@ -92,16 +90,16 @@ describe("map", () => {
|
|
|
92
90
|
});
|
|
93
91
|
|
|
94
92
|
test("a shallow map is applied to async subtrees too", async () => {
|
|
95
|
-
const tree =
|
|
93
|
+
const tree = {
|
|
96
94
|
a: "letter a",
|
|
97
95
|
more: {
|
|
98
96
|
b: "letter b",
|
|
99
97
|
},
|
|
100
|
-
}
|
|
98
|
+
};
|
|
101
99
|
const doubleKeys = map({
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
100
|
+
key: async (sourceKey, tree) => `_${sourceKey}`,
|
|
101
|
+
inverseKey: async (resultKey, tree) => resultKey.slice(1),
|
|
102
|
+
value: async (sourceValue, sourceKey, tree) => sourceKey,
|
|
105
103
|
});
|
|
106
104
|
const mapped = doubleKeys(tree);
|
|
107
105
|
assert.deepEqual(await Tree.plain(mapped), {
|
|
@@ -110,14 +108,14 @@ describe("map", () => {
|
|
|
110
108
|
});
|
|
111
109
|
});
|
|
112
110
|
|
|
113
|
-
test("
|
|
111
|
+
test("value can provide a default key and inverse key functions", async () => {
|
|
114
112
|
const uppercase = (s) => s.toUpperCase();
|
|
115
|
-
uppercase.
|
|
116
|
-
uppercase.
|
|
117
|
-
const tree =
|
|
113
|
+
uppercase.key = (sourceKey) => `_${sourceKey}`;
|
|
114
|
+
uppercase.inverseKey = (resultKey) => resultKey.slice(1);
|
|
115
|
+
const tree = {
|
|
118
116
|
a: "letter a",
|
|
119
117
|
b: "letter b",
|
|
120
|
-
}
|
|
118
|
+
};
|
|
121
119
|
const mapped = map(uppercase)(tree);
|
|
122
120
|
assert.deepEqual(await Tree.plain(mapped), {
|
|
123
121
|
_a: "LETTER A",
|
|
@@ -134,7 +132,7 @@ describe("map", () => {
|
|
|
134
132
|
});
|
|
135
133
|
const uppercaseValues = map({
|
|
136
134
|
deep: true,
|
|
137
|
-
|
|
135
|
+
value: (sourceValue, sourceKey, tree) => sourceValue.toUpperCase(),
|
|
138
136
|
});
|
|
139
137
|
const mapped = uppercaseValues(tree);
|
|
140
138
|
assert.deepEqual(await Tree.plain(mapped), {
|
|
@@ -154,8 +152,8 @@ describe("map", () => {
|
|
|
154
152
|
});
|
|
155
153
|
const doubleKeys = map({
|
|
156
154
|
deep: true,
|
|
157
|
-
|
|
158
|
-
|
|
155
|
+
key: async (sourceKey, tree) => `_${sourceKey}`,
|
|
156
|
+
inverseKey: async (resultKey, tree) => resultKey.slice(1),
|
|
159
157
|
});
|
|
160
158
|
const mapped = doubleKeys(tree);
|
|
161
159
|
assert.deepEqual(await Tree.plain(mapped), {
|
|
@@ -175,10 +173,9 @@ describe("map", () => {
|
|
|
175
173
|
});
|
|
176
174
|
const doubleKeysUppercaseValues = map({
|
|
177
175
|
deep: true,
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
sourceValue.toUpperCase(),
|
|
176
|
+
key: async (sourceKey, tree) => `_${sourceKey}`,
|
|
177
|
+
inverseKey: async (resultKey, tree) => resultKey.slice(1),
|
|
178
|
+
value: async (sourceValue, sourceKey, tree) => sourceValue.toUpperCase(),
|
|
182
179
|
});
|
|
183
180
|
const mapped = doubleKeysUppercaseValues(tree);
|
|
184
181
|
assert.deepEqual(await Tree.plain(mapped), {
|
|
@@ -196,7 +193,7 @@ describe("map", () => {
|
|
|
196
193
|
}, ["a", "b", "c"]);
|
|
197
194
|
const mapped = map({
|
|
198
195
|
needsSourceValue: false,
|
|
199
|
-
|
|
196
|
+
value: () => "X",
|
|
200
197
|
})(tree);
|
|
201
198
|
assert.deepEqual(await Tree.plain(mapped), {
|
|
202
199
|
a: "X",
|