@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 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.6.17",
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.3.2",
11
- "puppeteer": "24.37.5",
12
- "typescript": "5.9.3"
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";
@@ -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 && this.isSubtree(descriptor.value)
121
+ descriptor.value !== undefined &&
122
+ this.isSubtree(descriptor.value),
115
123
  );
116
124
  result.add(key);
117
125
  }
@@ -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
  }
@@ -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
- let sourceKeys;
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
 
@@ -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("@weborigami/async-tree").Maplike} Maplike
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
  *
@@ -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", { deep: true });
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
- return Object.assign(new AsyncMap(), {
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
  }
@@ -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
- if (extension && !options._noExtensionWarning) {
200
- console.warn(
201
- `Tree.map: The 'extension' option for Tree.map() is deprecated and will be removed in a future release. Use Tree.mapExtension() instead.`,
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: using extensions sets keyNeedsSourceValue to be false`,
201
+ `Tree.map: You can't specify an inverseKey function without a key function`,
212
202
  );
213
203
  }
214
204
 
215
- if (extension) {
216
- // Use the extension mapping to generate key and inverseKey functions
217
- const parsed = parseExtensions(extension);
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
- /** @type {MapExtensionOptions} */
48
- // @ts-ignore
49
- let options = { _noExtensionWarning: true };
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
- options.extension = arg2;
56
+ extension = arg2;
53
57
  } else if (isPlainObject(arg2)) {
54
- Object.assign(options, arg2);
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
- options.extension = arg2;
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
- Object.assign(options, arg3);
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 ${options.extension}`;
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
  }
@@ -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 unpacked.reduce((acc, obj) => ({ ...acc, ...obj }), {});
46
+ return assignPropertyDescriptors({}, ...unpacked);
46
47
  }
47
48
 
48
49
  const sources = unpacked.map((maplike) => from(maplike));
@@ -1,5 +1,5 @@
1
1
  import * as symbols from "../symbols.js";
2
- import * as args from "../utilities/args.js";
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 async function root(maplike) {
11
+ export default function root(maplike) {
12
12
  /** @type {any} */
13
- let current = await args.map(maplike, "Tree.root");
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
- "A path hit binary file data that can't be unpacked.",
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(...args);
69
+ value = await fn(...normalized);
65
70
  } else {
66
71
  // Cast value to a map.
67
72
  const map = from(value);
@@ -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 error.constructor(message);
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
- const normalized = trailingSlash.remove(key);
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);
@@ -121,6 +121,7 @@ describe("SyncMap", () => {
121
121
  ["a", 1],
122
122
  ["b/", subMap],
123
123
  ]);
124
+ map.trailingSlashKeys = true;
124
125
  assert.strictEqual(map.get("a"), 1);
125
126
 
126
127
  const b = map.get("b/");
@@ -1,15 +1,15 @@
1
1
  import assert from "node:assert";
2
2
  import { describe, test } from "node:test";
3
- import length from "../../src/operations/length.js";
3
+ import size from "../../src/operations/size.js";
4
4
 
5
- describe("length", () => {
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 length(obj);
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
  });
@@ -1,6 +0,0 @@
1
- import groupBy from "./groupBy.js";
2
-
3
- export default async function group(maplike, groupKeyFn) {
4
- console.warn("Tree.group() is deprecated. Use Tree.groupBy() instead.");
5
- return groupBy(maplike, groupKeyFn);
6
- }
@@ -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
- }
@@ -1,6 +0,0 @@
1
- import isMap from "./isMap.js";
2
-
3
- export default function isAsyncTree(treelike) {
4
- console.warn("Tree.isAsyncTree() is deprecated, use Tree.isMap() instead.");
5
- return isMap(treelike);
6
- }
@@ -1,8 +0,0 @@
1
- import isMaplike from "./isMaplike.js";
2
-
3
- export default function isTreelike(treelike) {
4
- console.warn(
5
- "Tree.isTreelike() is deprecated, use Tree.isMaplike() instead."
6
- );
7
- return isMaplike(treelike);
8
- }
@@ -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
- }