@weborigami/async-tree 0.6.17 → 0.7.0-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/index.ts +1 -1
- package/package.json +4 -4
- package/shared.js +1 -58
- package/src/Tree.js +0 -5
- package/src/drivers/ObjectMap.js +10 -2
- package/src/drivers/SetMap.js +11 -0
- package/src/drivers/SyncMap.js +1 -1
- package/src/operations/addNextPrevious.js +1 -3
- package/src/operations/cache.js +5 -0
- package/src/operations/combine.js +1 -1
- package/src/operations/deepEntriesIterator.js +1 -0
- package/src/operations/flat.js +1 -1
- package/src/operations/invokeFunctions.js +15 -1
- package/src/operations/map.js +9 -41
- package/src/operations/mapExtension.js +38 -8
- package/src/operations/merge.js +2 -1
- package/src/operations/root.js +3 -3
- package/src/operations/traverseOrThrow.js +7 -2
- package/src/utilities/args.js +1 -1
- package/src/utilities/assignPropertyDescriptors.js +26 -0
- package/src/utilities/castArraylike.js +4 -1
- package/test/drivers/SyncMap.test.js +1 -0
- package/test/operations/{length.test.js → size.test.js} +3 -3
- package/test/utilities/castArrayLike.test.js +32 -0
- package/src/operations/group.js +0 -6
- package/src/operations/isAsyncMutableTree.js +0 -8
- package/src/operations/isAsyncTree.js +0 -6
- package/src/operations/isTreelike.js +0 -8
- package/src/operations/length.js +0 -16
package/index.ts
CHANGED
|
@@ -27,7 +27,6 @@ export type MapExtensionOptions = {
|
|
|
27
27
|
deep?: boolean;
|
|
28
28
|
description?: string;
|
|
29
29
|
extension?: string;
|
|
30
|
-
needsSourceValue?: boolean;
|
|
31
30
|
value?: ValueKeyFn;
|
|
32
31
|
};
|
|
33
32
|
|
|
@@ -46,6 +45,7 @@ export type MapOptions = {
|
|
|
46
45
|
inverseKey?: KeyFn;
|
|
47
46
|
key?: ValueKeyFn;
|
|
48
47
|
keyNeedsSourceValue?: boolean;
|
|
48
|
+
needsSourceValue?: boolean;
|
|
49
49
|
value?: ValueKeyFn;
|
|
50
50
|
};
|
|
51
51
|
|
package/package.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weborigami/async-tree",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0-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": "25.
|
|
11
|
-
"puppeteer": "
|
|
12
|
-
"typescript": "
|
|
10
|
+
"@types/node": "25.9.1",
|
|
11
|
+
"puppeteer": "25.1.0",
|
|
12
|
+
"typescript": "6.0.3"
|
|
13
13
|
},
|
|
14
14
|
"scripts": {
|
|
15
15
|
"headlessTest": "node scripts/headlessTest.js",
|
package/shared.js
CHANGED
|
@@ -20,6 +20,7 @@ export * as trailingSlash from "./src/trailingSlash.js";
|
|
|
20
20
|
export { default as TraverseError } from "./src/TraverseError.js";
|
|
21
21
|
export * as Tree from "./src/Tree.js";
|
|
22
22
|
export * as args from "./src/utilities/args.js";
|
|
23
|
+
export { default as assignPropertyDescriptors } from "./src/utilities/assignPropertyDescriptors.js";
|
|
23
24
|
export { default as box } from "./src/utilities/box.js";
|
|
24
25
|
export { default as castArraylike } from "./src/utilities/castArraylike.js";
|
|
25
26
|
export { default as getParent } from "./src/utilities/getParent.js";
|
|
@@ -38,61 +39,3 @@ export { default as toPlainValue } from "./src/utilities/toPlainValue.js";
|
|
|
38
39
|
export { default as toString } from "./src/utilities/toString.js";
|
|
39
40
|
|
|
40
41
|
export { ExplorableSiteMap, FileMap, FunctionMap, ObjectMap, SetMap, SiteMap };
|
|
41
|
-
|
|
42
|
-
export class DeepObjectMap extends ObjectMap {
|
|
43
|
-
constructor(object) {
|
|
44
|
-
super(object, { deep: true });
|
|
45
|
-
console.warn("DeepObjectMap is deprecated. Please use ObjectMap instead.");
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export class ObjectTree extends ObjectMap {
|
|
50
|
-
constructor(...args) {
|
|
51
|
-
super(...args);
|
|
52
|
-
console.warn("ObjectTree is deprecated. Please use ObjectMap instead.");
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export class DeepObjectTree extends ObjectMap {
|
|
57
|
-
constructor(object) {
|
|
58
|
-
super(object, { deep: true });
|
|
59
|
-
console.warn("DeepObjectTree is deprecated. Please use ObjectMap instead.");
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export class ExplorableSiteTree extends ExplorableSiteMap {
|
|
64
|
-
constructor(href) {
|
|
65
|
-
super(href);
|
|
66
|
-
console.warn(
|
|
67
|
-
"ExplorableSiteTree is deprecated. Please use ExplorableSiteMap instead.",
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export class FileTree extends FileMap {
|
|
73
|
-
constructor(...args) {
|
|
74
|
-
super(...args);
|
|
75
|
-
console.warn("FileTree is deprecated. Please use FileMap instead.");
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export class FunctionTree extends FunctionMap {
|
|
80
|
-
constructor(...args) {
|
|
81
|
-
super(...args);
|
|
82
|
-
console.warn("FunctionTree is deprecated. Please use FunctionMap instead.");
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export class SetTree extends SetMap {
|
|
87
|
-
constructor(set) {
|
|
88
|
-
super(set);
|
|
89
|
-
console.warn("SetTree is deprecated. Please use SetMap instead.");
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
export class SiteTree extends SiteMap {
|
|
94
|
-
constructor(...args) {
|
|
95
|
-
super(...args);
|
|
96
|
-
console.warn("SiteTree is deprecated. Please use SiteMap instead.");
|
|
97
|
-
}
|
|
98
|
-
}
|
package/src/Tree.js
CHANGED
|
@@ -30,23 +30,18 @@ export { default as flat } from "./operations/flat.js";
|
|
|
30
30
|
export { default as forEach } from "./operations/forEach.js";
|
|
31
31
|
export { default as from } from "./operations/from.js";
|
|
32
32
|
export { default as globKeys } from "./operations/globKeys.js";
|
|
33
|
-
export { default as group } from "./operations/group.js";
|
|
34
33
|
export { default as groupBy } from "./operations/groupBy.js";
|
|
35
34
|
export { default as has } from "./operations/has.js";
|
|
36
35
|
export { default as indent } from "./operations/indent.js";
|
|
37
36
|
export { default as inflatePaths } from "./operations/inflatePaths.js";
|
|
38
37
|
export { default as inners } from "./operations/inners.js";
|
|
39
38
|
export { default as invokeFunctions } from "./operations/invokeFunctions.js";
|
|
40
|
-
export { default as isAsyncMutableTree } from "./operations/isAsyncMutableTree.js";
|
|
41
|
-
export { default as isAsyncTree } from "./operations/isAsyncTree.js";
|
|
42
39
|
export { default as isMap } from "./operations/isMap.js";
|
|
43
40
|
export { default as isMaplike } from "./operations/isMaplike.js";
|
|
44
41
|
export { default as isReadOnlyMap } from "./operations/isReadOnlyMap.js";
|
|
45
42
|
export { default as isTraversable } from "./operations/isTraversable.js";
|
|
46
|
-
export { default as isTreelike } from "./operations/isTreelike.js";
|
|
47
43
|
export { default as json } from "./operations/json.js";
|
|
48
44
|
export { default as keys } from "./operations/keys.js";
|
|
49
|
-
export { default as length } from "./operations/length.js";
|
|
50
45
|
export { default as map } from "./operations/map.js";
|
|
51
46
|
export { default as mapExtension } from "./operations/mapExtension.js";
|
|
52
47
|
export { default as mapReduce } from "./operations/mapReduce.js";
|
package/src/drivers/ObjectMap.js
CHANGED
|
@@ -20,7 +20,7 @@ export default class ObjectMap extends SyncMap {
|
|
|
20
20
|
// objects such as Node's `Module` class for representing an ES module.
|
|
21
21
|
if (typeof object !== "object" || object === null) {
|
|
22
22
|
throw new TypeError(
|
|
23
|
-
`${this.constructor.name}: Expected an object or array
|
|
23
|
+
`${this.constructor.name}: Expected an object or array.`,
|
|
24
24
|
);
|
|
25
25
|
}
|
|
26
26
|
this.object = object;
|
|
@@ -38,6 +38,13 @@ export default class ObjectMap extends SyncMap {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
get(key) {
|
|
41
|
+
if (key == null) {
|
|
42
|
+
// Reject nullish key
|
|
43
|
+
throw new ReferenceError(
|
|
44
|
+
`${this.constructor.name}: Cannot get a null or undefined key.`,
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
41
48
|
// Does the object have the key with or without a trailing slash?
|
|
42
49
|
const existingKey = findExistingKey(this.object, key);
|
|
43
50
|
if (existingKey === null) {
|
|
@@ -111,7 +118,8 @@ export default class ObjectMap extends SyncMap {
|
|
|
111
118
|
? name
|
|
112
119
|
: trailingSlash.toggle(
|
|
113
120
|
name,
|
|
114
|
-
descriptor.value !== undefined &&
|
|
121
|
+
descriptor.value !== undefined &&
|
|
122
|
+
this.isSubtree(descriptor.value),
|
|
115
123
|
);
|
|
116
124
|
result.add(key);
|
|
117
125
|
}
|
package/src/drivers/SetMap.js
CHANGED
|
@@ -13,4 +13,15 @@ export default class SetMap extends SyncMap {
|
|
|
13
13
|
const entries = Array.from(set).map((value, index) => [index, value]);
|
|
14
14
|
super(entries);
|
|
15
15
|
}
|
|
16
|
+
|
|
17
|
+
get(key) {
|
|
18
|
+
if (key == null) {
|
|
19
|
+
// Reject nullish key
|
|
20
|
+
throw new ReferenceError(
|
|
21
|
+
`${this.constructor.name}: Cannot get a null or undefined key.`,
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return super.get(key);
|
|
26
|
+
}
|
|
16
27
|
}
|
package/src/drivers/SyncMap.js
CHANGED
|
@@ -128,7 +128,7 @@ export default class SyncMap extends Map {
|
|
|
128
128
|
*/
|
|
129
129
|
get(key) {
|
|
130
130
|
let value = super.get.call(this._self, key);
|
|
131
|
-
if (value === undefined) {
|
|
131
|
+
if (value === undefined && this.trailingSlashKeys) {
|
|
132
132
|
// Try alternate key with trailing slash added or removed
|
|
133
133
|
value = super.get.call(this._self, trailingSlash.toggle(key));
|
|
134
134
|
}
|
|
@@ -10,7 +10,7 @@ import keys from "./keys.js";
|
|
|
10
10
|
*/
|
|
11
11
|
export default async function addNextPrevious(maplike) {
|
|
12
12
|
const source = await args.map(maplike, "Tree.addNextPrevious");
|
|
13
|
-
|
|
13
|
+
const sourceKeys = await keys(source);
|
|
14
14
|
|
|
15
15
|
return Object.assign(new AsyncMap(), {
|
|
16
16
|
async get(key) {
|
|
@@ -29,7 +29,6 @@ export default async function addNextPrevious(maplike) {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
// Find the index of the current key
|
|
32
|
-
sourceKeys ??= await keys(source);
|
|
33
32
|
const index = sourceKeys.indexOf(key);
|
|
34
33
|
if (index >= 0) {
|
|
35
34
|
// Extend result with nextKey/previousKey properties.
|
|
@@ -47,7 +46,6 @@ export default async function addNextPrevious(maplike) {
|
|
|
47
46
|
},
|
|
48
47
|
|
|
49
48
|
async *keys() {
|
|
50
|
-
sourceKeys ??= await keys(source);
|
|
51
49
|
yield* sourceKeys;
|
|
52
50
|
},
|
|
53
51
|
|
package/src/operations/cache.js
CHANGED
|
@@ -67,6 +67,11 @@ export default async function treeCache(sourceMaplike, cacheMaplike) {
|
|
|
67
67
|
},
|
|
68
68
|
|
|
69
69
|
async *keys() {
|
|
70
|
+
// REVIEW: Saving our own copy of the source keys can create issues when
|
|
71
|
+
// this operation is applied in an Origami site. Because this keys() call
|
|
72
|
+
// happens outside of the language package's system cache, the system
|
|
73
|
+
// cache may not detect a dependency. If the underlying keys change, the
|
|
74
|
+
// keys obtained here won't be invalidated.
|
|
70
75
|
sourceKeys ??= await keys(source);
|
|
71
76
|
yield* sourceKeys;
|
|
72
77
|
},
|
|
@@ -12,7 +12,7 @@ import keys from "./keys.js";
|
|
|
12
12
|
* that were not `undefined`. If all results were `undefined`, the overall
|
|
13
13
|
* result is itself `undefined`.
|
|
14
14
|
*
|
|
15
|
-
* @typedef {import("
|
|
15
|
+
* @typedef {import("../../index.ts").Maplike} Maplike
|
|
16
16
|
*
|
|
17
17
|
* @param {Maplike} maplike1
|
|
18
18
|
* @param {Maplike} maplike2
|
|
@@ -2,6 +2,7 @@ import * as args from "../utilities/args.js";
|
|
|
2
2
|
import isUnpackable from "../utilities/isUnpackable.js";
|
|
3
3
|
import isMap from "./isMap.js";
|
|
4
4
|
import isMaplike from "./isMaplike.js";
|
|
5
|
+
|
|
5
6
|
/**
|
|
6
7
|
* Return an iterator that yields all entries in a tree, including nested trees.
|
|
7
8
|
*
|
package/src/operations/flat.js
CHANGED
|
@@ -17,7 +17,7 @@ import deepEntriesIterator from "./deepEntriesIterator.js";
|
|
|
17
17
|
* @param {number} [depth] The maximum depth to flatten
|
|
18
18
|
*/
|
|
19
19
|
export default async function flat(maplike, depth = 1) {
|
|
20
|
-
const map = await args.map(maplike, "Tree.flat"
|
|
20
|
+
const map = await args.map(maplike, "Tree.flat");
|
|
21
21
|
|
|
22
22
|
let index = 0;
|
|
23
23
|
let onlyNumericKeys = true;
|
|
@@ -7,7 +7,7 @@ export default async function invokeFunctions(maplike) {
|
|
|
7
7
|
deep: true,
|
|
8
8
|
});
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
const result = Object.assign(new AsyncMap(), {
|
|
11
11
|
description: "invokeFunctions",
|
|
12
12
|
|
|
13
13
|
async get(key) {
|
|
@@ -30,4 +30,18 @@ export default async function invokeFunctions(maplike) {
|
|
|
30
30
|
|
|
31
31
|
trailingSlashKeys: /** @type {any} */ (source).trailingSlashKeys,
|
|
32
32
|
});
|
|
33
|
+
|
|
34
|
+
if (!(/** @type {any} */ (source).readOnly)) {
|
|
35
|
+
Object.assign(result, {
|
|
36
|
+
delete(key) {
|
|
37
|
+
return source.delete(key);
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
set(key, value) {
|
|
41
|
+
return source.set(key, value);
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return result;
|
|
33
47
|
}
|
package/src/operations/map.js
CHANGED
|
@@ -5,10 +5,8 @@ import isPlainObject from "../utilities/isPlainObject.js";
|
|
|
5
5
|
import isUnpackable from "../utilities/isUnpackable.js";
|
|
6
6
|
import toFunction from "../utilities/toFunction.js";
|
|
7
7
|
import cachedKeyFunctions from "./cachedKeyFunctions.js";
|
|
8
|
-
import extensionKeyFunctions from "./extensionKeyFunctions.js";
|
|
9
8
|
import isMap from "./isMap.js";
|
|
10
9
|
import keys from "./keys.js";
|
|
11
|
-
import parseExtensions from "./parseExtensions.js";
|
|
12
10
|
|
|
13
11
|
/**
|
|
14
12
|
* Transform the keys and/or values of a tree.
|
|
@@ -147,7 +145,6 @@ function validateOption(options, key) {
|
|
|
147
145
|
function validateOptions(options) {
|
|
148
146
|
let deep;
|
|
149
147
|
let description;
|
|
150
|
-
let extension;
|
|
151
148
|
let inverseKeyFn;
|
|
152
149
|
let keyFn;
|
|
153
150
|
let keyNeedsSourceValue;
|
|
@@ -162,7 +159,6 @@ function validateOptions(options) {
|
|
|
162
159
|
|
|
163
160
|
// Validate individual options
|
|
164
161
|
deep = validateOption(options, "deep");
|
|
165
|
-
extension = validateOption(options, "extension");
|
|
166
162
|
inverseKeyFn = validateOption(options, "inverseKey");
|
|
167
163
|
keyFn = validateOption(options, "key");
|
|
168
164
|
keyNeedsSourceValue = validateOption(options, "keyNeedsSourceValue");
|
|
@@ -196,49 +192,21 @@ function validateOptions(options) {
|
|
|
196
192
|
throw error;
|
|
197
193
|
}
|
|
198
194
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
if (extension && (keyFn || inverseKeyFn)) {
|
|
205
|
-
throw new TypeError(
|
|
206
|
-
`Tree.map: You can't specify extensions and also a key or inverseKey function`,
|
|
207
|
-
);
|
|
208
|
-
}
|
|
209
|
-
if (extension && keyNeedsSourceValue === true) {
|
|
195
|
+
// If key or inverseKey weren't specified, look for sidecar functions
|
|
196
|
+
inverseKeyFn ??= valueFn?.inverseKey;
|
|
197
|
+
keyFn ??= valueFn?.key;
|
|
198
|
+
|
|
199
|
+
if (!keyFn && inverseKeyFn) {
|
|
210
200
|
throw new TypeError(
|
|
211
|
-
`Tree.map:
|
|
201
|
+
`Tree.map: You can't specify an inverseKey function without a key function`,
|
|
212
202
|
);
|
|
213
203
|
}
|
|
214
204
|
|
|
215
|
-
if (
|
|
216
|
-
//
|
|
217
|
-
const
|
|
218
|
-
const keyFns = extensionKeyFunctions(
|
|
219
|
-
parsed.sourceExtension,
|
|
220
|
-
parsed.resultExtension,
|
|
221
|
-
);
|
|
205
|
+
if (keyFn && !inverseKeyFn) {
|
|
206
|
+
// Only keyFn was provided, so we need to generate the inverseKeyFn
|
|
207
|
+
const keyFns = cachedKeyFunctions(keyFn, deep);
|
|
222
208
|
keyFn = keyFns.key;
|
|
223
209
|
inverseKeyFn = keyFns.inverseKey;
|
|
224
|
-
keyNeedsSourceValue = false;
|
|
225
|
-
} else {
|
|
226
|
-
// If key or inverseKey weren't specified, look for sidecar functions
|
|
227
|
-
inverseKeyFn ??= valueFn?.inverseKey;
|
|
228
|
-
keyFn ??= valueFn?.key;
|
|
229
|
-
|
|
230
|
-
if (!keyFn && inverseKeyFn) {
|
|
231
|
-
throw new TypeError(
|
|
232
|
-
`Tree.map: You can't specify an inverseKey function without a key function`,
|
|
233
|
-
);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
if (keyFn && !inverseKeyFn) {
|
|
237
|
-
// Only keyFn was provided, so we need to generate the inverseKeyFn
|
|
238
|
-
const keyFns = cachedKeyFunctions(keyFn, deep);
|
|
239
|
-
keyFn = keyFns.key;
|
|
240
|
-
inverseKeyFn = keyFns.inverseKey;
|
|
241
|
-
}
|
|
242
210
|
}
|
|
243
211
|
|
|
244
212
|
if (!valueFn && !keyFn) {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import isPlainObject from "../utilities/isPlainObject.js";
|
|
2
2
|
import isUnpackable from "../utilities/isUnpackable.js";
|
|
3
|
+
import extensionKeyFunctions from "./extensionKeyFunctions.js";
|
|
3
4
|
import map from "./map.js";
|
|
5
|
+
import parseExtensions from "./parseExtensions.js";
|
|
4
6
|
|
|
5
7
|
/**
|
|
6
8
|
* @typedef {import("../../index.ts").AsyncMap} AsyncMap
|
|
@@ -44,14 +46,17 @@ import map from "./map.js";
|
|
|
44
46
|
* @returns {Promise<AsyncMap>}
|
|
45
47
|
*/
|
|
46
48
|
export default async function mapExtension(maplike, arg2, arg3) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
49
|
+
let extension;
|
|
50
|
+
|
|
51
|
+
/** @type {import("../../index.ts").MapOptions} */
|
|
52
|
+
let options = { keyNeedsSourceValue: false };
|
|
53
|
+
let optionsArg;
|
|
50
54
|
if (arg3 === undefined) {
|
|
51
55
|
if (typeof arg2 === "string") {
|
|
52
|
-
|
|
56
|
+
extension = arg2;
|
|
53
57
|
} else if (isPlainObject(arg2)) {
|
|
54
|
-
|
|
58
|
+
extension = arg2.extension;
|
|
59
|
+
optionsArg = arg2;
|
|
55
60
|
} else {
|
|
56
61
|
throw new TypeError(
|
|
57
62
|
"Tree.mapExtension: Expected a string or options object for the second argument.",
|
|
@@ -63,14 +68,14 @@ export default async function mapExtension(maplike, arg2, arg3) {
|
|
|
63
68
|
"Tree.mapExtension: Expected a string for the second argument.",
|
|
64
69
|
);
|
|
65
70
|
}
|
|
66
|
-
|
|
71
|
+
extension = arg2;
|
|
67
72
|
if (isUnpackable(arg3)) {
|
|
68
73
|
arg3 = await arg3.unpack();
|
|
69
74
|
}
|
|
70
75
|
if (typeof arg3 === "function") {
|
|
71
76
|
options.value = arg3;
|
|
72
77
|
} else if (isPlainObject(arg3)) {
|
|
73
|
-
|
|
78
|
+
optionsArg = arg3;
|
|
74
79
|
} else {
|
|
75
80
|
throw new TypeError(
|
|
76
81
|
"Tree.mapExtension: Expected a function or options object for the third argument.",
|
|
@@ -78,9 +83,34 @@ export default async function mapExtension(maplike, arg2, arg3) {
|
|
|
78
83
|
}
|
|
79
84
|
}
|
|
80
85
|
|
|
86
|
+
if (!extension) {
|
|
87
|
+
throw new TypeError(
|
|
88
|
+
"Tree.mapExtension: An extension mapping string is required.",
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (optionsArg?.deep !== undefined) {
|
|
93
|
+
options.deep = optionsArg.deep;
|
|
94
|
+
}
|
|
95
|
+
if (optionsArg?.description !== undefined) {
|
|
96
|
+
options.description = optionsArg.description;
|
|
97
|
+
}
|
|
98
|
+
if (optionsArg?.value !== undefined) {
|
|
99
|
+
options.value = optionsArg.value;
|
|
100
|
+
}
|
|
101
|
+
|
|
81
102
|
if (!options.description) {
|
|
82
|
-
options.description = `mapExtension ${
|
|
103
|
+
options.description = `mapExtension ${extension}`;
|
|
83
104
|
}
|
|
84
105
|
|
|
106
|
+
// Use the extension mapping to generate key and inverseKey functions
|
|
107
|
+
const parsed = parseExtensions(extension);
|
|
108
|
+
const keyFns = extensionKeyFunctions(
|
|
109
|
+
parsed.sourceExtension,
|
|
110
|
+
parsed.resultExtension,
|
|
111
|
+
);
|
|
112
|
+
options.key = keyFns.key;
|
|
113
|
+
options.inverseKey = keyFns.inverseKey;
|
|
114
|
+
|
|
85
115
|
return map(maplike, options);
|
|
86
116
|
}
|
package/src/operations/merge.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import AsyncMap from "../drivers/AsyncMap.js";
|
|
2
2
|
import * as trailingSlash from "../trailingSlash.js";
|
|
3
|
+
import assignPropertyDescriptors from "../utilities/assignPropertyDescriptors.js";
|
|
3
4
|
import isPlainObject from "../utilities/isPlainObject.js";
|
|
4
5
|
import isUnpackable from "../utilities/isUnpackable.js";
|
|
5
6
|
import from from "./from.js";
|
|
@@ -42,7 +43,7 @@ export default async function merge(...treelikes) {
|
|
|
42
43
|
|
|
43
44
|
// If all arguments are plain objects, return a plain object.
|
|
44
45
|
if (unpacked.every((source) => !isMap(source) && isPlainObject(source))) {
|
|
45
|
-
return
|
|
46
|
+
return assignPropertyDescriptors({}, ...unpacked);
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
const sources = unpacked.map((maplike) => from(maplike));
|
package/src/operations/root.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as symbols from "../symbols.js";
|
|
2
|
-
import
|
|
2
|
+
import from from "./from.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Walk up the `parent` chain to find the root of the tree.
|
|
@@ -8,9 +8,9 @@ import * as args from "../utilities/args.js";
|
|
|
8
8
|
*
|
|
9
9
|
* @param {Maplike} maplike
|
|
10
10
|
*/
|
|
11
|
-
export default
|
|
11
|
+
export default function root(maplike) {
|
|
12
12
|
/** @type {any} */
|
|
13
|
-
let current =
|
|
13
|
+
let current = from(maplike);
|
|
14
14
|
while (current.parent || current[symbols.parent]) {
|
|
15
15
|
current = current.parent || current[symbols.parent];
|
|
16
16
|
}
|
|
@@ -42,8 +42,12 @@ export default async function traverseOrThrow(maplike, ...keys) {
|
|
|
42
42
|
if (typeof (/** @type {any} */ (value).unpack) === "function") {
|
|
43
43
|
value = await value.unpack();
|
|
44
44
|
} else {
|
|
45
|
+
const type =
|
|
46
|
+
typeof value === "string" || value instanceof String
|
|
47
|
+
? "string"
|
|
48
|
+
: "binary";
|
|
45
49
|
throw new TraverseError(
|
|
46
|
-
|
|
50
|
+
`A path hit ${type} data that can't be unpacked.`,
|
|
47
51
|
{
|
|
48
52
|
head: maplike,
|
|
49
53
|
lastValue,
|
|
@@ -60,8 +64,9 @@ export default async function traverseOrThrow(maplike, ...keys) {
|
|
|
60
64
|
// We'll take as many keys as the function's length, but at least one.
|
|
61
65
|
let fnKeyCount = Math.max(fn.length, 1);
|
|
62
66
|
const args = remainingKeys.splice(0, fnKeyCount);
|
|
67
|
+
const normalized = args.map((key) => trailingSlash.remove(key));
|
|
63
68
|
key = null;
|
|
64
|
-
value = await fn(...
|
|
69
|
+
value = await fn(...normalized);
|
|
65
70
|
} else {
|
|
66
71
|
// Cast value to a map.
|
|
67
72
|
const map = from(value);
|
package/src/utilities/args.js
CHANGED
|
@@ -69,7 +69,7 @@ export async function map(arg, operation, options = {}) {
|
|
|
69
69
|
} catch (/** @type {any} */ error) {
|
|
70
70
|
let message = error.message ?? error;
|
|
71
71
|
message = `${operation}: ${message}`;
|
|
72
|
-
const newError = new
|
|
72
|
+
const newError = new TypeError(message);
|
|
73
73
|
/** @type {any} */ (newError).position = position;
|
|
74
74
|
throw newError;
|
|
75
75
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This is an analogue of Object.assign that destructively copies properties to
|
|
3
|
+
* a target object -- but avoids invoking property getters. Instead, it copies
|
|
4
|
+
* property descriptors over to the target object.
|
|
5
|
+
*
|
|
6
|
+
* @param {any} target
|
|
7
|
+
* @param {...any} sources
|
|
8
|
+
*/
|
|
9
|
+
export default function assignPropertyDescriptors(target, ...sources) {
|
|
10
|
+
for (const source of sources) {
|
|
11
|
+
if (!source) {
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
const descriptors = Object.getOwnPropertyDescriptors(source);
|
|
15
|
+
for (const [key, descriptor] of Object.entries(descriptors)) {
|
|
16
|
+
if (descriptor.value !== undefined) {
|
|
17
|
+
// Simple value, copy it
|
|
18
|
+
target[key] = descriptor.value;
|
|
19
|
+
} else {
|
|
20
|
+
// Getter and/or setter, copy the descriptor
|
|
21
|
+
Object.defineProperty(target, key, descriptor);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return target;
|
|
26
|
+
}
|
|
@@ -41,7 +41,10 @@ export default function castArraylike(map, createFn = Object.fromEntries) {
|
|
|
41
41
|
// result. By default this will create a plain object from the entries.
|
|
42
42
|
const normalizedMap = new Map();
|
|
43
43
|
for (const [key, value] of map.entries()) {
|
|
44
|
-
|
|
44
|
+
// Normalize the key by stripping trailing slashes, but only if there
|
|
45
|
+
// aren't multiple keys that only differ by trailing slashes.
|
|
46
|
+
const normalize = !map.has(trailingSlash.toggle(key));
|
|
47
|
+
const normalized = normalize ? trailingSlash.remove(key) : key;
|
|
45
48
|
normalizedMap.set(normalized, value);
|
|
46
49
|
}
|
|
47
50
|
return createFn(normalizedMap);
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import assert from "node:assert";
|
|
2
2
|
import { describe, test } from "node:test";
|
|
3
|
-
import
|
|
3
|
+
import size from "../../src/operations/size.js";
|
|
4
4
|
|
|
5
|
-
describe("
|
|
5
|
+
describe("size", () => {
|
|
6
6
|
test("returns the number of keys in the tree", async () => {
|
|
7
7
|
const obj = {
|
|
8
8
|
a: 1,
|
|
9
9
|
b: 2,
|
|
10
10
|
c: 3,
|
|
11
11
|
};
|
|
12
|
-
const result = await
|
|
12
|
+
const result = await size(obj);
|
|
13
13
|
assert.equal(result, 3);
|
|
14
14
|
});
|
|
15
15
|
});
|
|
@@ -50,4 +50,36 @@ describe("castArraylike", () => {
|
|
|
50
50
|
3: "c",
|
|
51
51
|
});
|
|
52
52
|
});
|
|
53
|
+
|
|
54
|
+
test("strips trailing slashes if map only has one form of the key", () => {
|
|
55
|
+
const map = new /** @type {any} */ (Map)([
|
|
56
|
+
["a/", 1],
|
|
57
|
+
["b", 2],
|
|
58
|
+
["c/", 3],
|
|
59
|
+
]);
|
|
60
|
+
const result = castArraylike(map);
|
|
61
|
+
assert.deepEqual(result, {
|
|
62
|
+
a: 1,
|
|
63
|
+
b: 2,
|
|
64
|
+
c: 3,
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test.only("preserves trailing slashes if map has both forms of the key", () => {
|
|
69
|
+
const map = new /** @type {any} */ (Map)([
|
|
70
|
+
["a/", 1],
|
|
71
|
+
["a", 2],
|
|
72
|
+
["b", 3],
|
|
73
|
+
["c/", 4],
|
|
74
|
+
["c", 5],
|
|
75
|
+
]);
|
|
76
|
+
const result = castArraylike(map);
|
|
77
|
+
assert.deepEqual(result, {
|
|
78
|
+
"a/": 1,
|
|
79
|
+
a: 2,
|
|
80
|
+
b: 3,
|
|
81
|
+
"c/": 4,
|
|
82
|
+
c: 5,
|
|
83
|
+
});
|
|
84
|
+
});
|
|
53
85
|
});
|
package/src/operations/group.js
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import isReadOnlyMap from "./isReadOnlyMap.js";
|
|
2
|
-
|
|
3
|
-
export default function isAsyncMutableTree(treelike) {
|
|
4
|
-
console.warn(
|
|
5
|
-
"Tree.isAsyncMutableTree() is deprecated, use Tree.isReadOnlyMap() instead, which returns the inverse."
|
|
6
|
-
);
|
|
7
|
-
return !isReadOnlyMap(treelike);
|
|
8
|
-
}
|
package/src/operations/length.js
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import * as args from "../utilities/args.js";
|
|
2
|
-
import keys from "./keys.js";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Return the number of keys in the tree.
|
|
6
|
-
*
|
|
7
|
-
* @typedef {import("../../index.ts").Maplike} Maplike
|
|
8
|
-
*
|
|
9
|
-
* @param {Maplike} maplike
|
|
10
|
-
*/
|
|
11
|
-
export default async function length(maplike) {
|
|
12
|
-
console.warn("Tree.length() is deprecated. Use Tree.size() instead.");
|
|
13
|
-
const tree = await args.map(maplike, "Tree.length");
|
|
14
|
-
const treeKeys = await keys(tree);
|
|
15
|
-
return treeKeys.length;
|
|
16
|
-
}
|