@weborigami/async-tree 0.5.2 → 0.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@weborigami/async-tree",
3
- "version": "0.5.2",
3
+ "version": "0.5.4",
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
  "dependencies": {
10
- "@weborigami/types": "0.5.2"
10
+ "@weborigami/types": "0.5.4"
11
11
  },
12
12
  "devDependencies": {
13
13
  "@types/node": "24.3.0",
package/src/Tree.js CHANGED
@@ -337,10 +337,21 @@ export async function paths(treelike, options = {}) {
337
337
  /**
338
338
  * Converts an asynchronous tree into a synchronous plain JavaScript object.
339
339
  *
340
- * The result's keys will be the tree's keys cast to strings. Any tree value
341
- * that is itself a tree will be similarly converted to a plain object.
340
+ * The result's keys will be the tree's keys cast to strings. Any trailing
341
+ * slashes in keys will be removed.
342
342
  *
343
- * Any trailing slashes in keys will be removed.
343
+ * Any tree value that is itself a tree will be recursively converted to a plain
344
+ * object.
345
+ *
346
+ * If the tree is array-like (its keys are integers and fill the range
347
+ * 0..length-1), then the result will be an array. The order of the keys will
348
+ * determine the order of the values in the array -- but the numeric value of
349
+ * the keys will be ignored.
350
+ *
351
+ * For example, a tree like `{ 1: "b", 0: "a", 2: "c" }` is array-like because
352
+ * its keys are all integers and fill the range 0..2. The result will be the
353
+ * array `["b", "a", "c" ]` because the tree has the keys in that order. The
354
+ * specific values of the keys (0, 1, and 2) are ignored.
344
355
  *
345
356
  * @param {Treelike} treelike
346
357
  * @returns {Promise<PlainObject|Array>}
@@ -171,6 +171,12 @@ function validateOptions(options) {
171
171
  }
172
172
  }
173
173
 
174
+ if (!valueFn && !keyFn) {
175
+ throw new TypeError(
176
+ `map: You must specify a value function or a key function`
177
+ );
178
+ }
179
+
174
180
  deep ??= false;
175
181
  description ??= "key/value map";
176
182
  needsSourceValue ??= true;
package/src/utilities.js CHANGED
@@ -55,36 +55,40 @@ export function box(value) {
55
55
  /**
56
56
  * Create an array or plain object from the given keys and values.
57
57
  *
58
- * If the given plain object has only sequential integer keys, return the
59
- * values as an array. Otherwise, create a plain object with the keys and
60
- * values.
58
+ * If the given plain object has only integer keys, and the set of integers is
59
+ * complete from 0 to length-1, assume the values are a result of array
60
+ * transformations and the values are the desired result; return them as is.
61
+ * Otherwise, create a plain object with the keys and values.
61
62
  *
62
63
  * @param {any[]} keys
63
64
  * @param {any[]} values
64
65
  */
65
66
  export function castArrayLike(keys, values) {
66
- let isArrayLike = false;
67
+ if (keys.length === 0) {
68
+ // Empty keys/values means an empty object, not an empty array
69
+ return {};
70
+ }
67
71
 
68
- // Need at least one key to count as an array
69
- if (keys.length > 0) {
70
- // Assume it's an array
71
- isArrayLike = true;
72
- // Then check if all the keys are sequential integers
73
- let expectedIndex = 0;
74
- for (const key of keys) {
75
- const index = Number(key);
76
- if (key === "" || isNaN(index) || index !== expectedIndex) {
77
- // Not array-like
78
- isArrayLike = false;
79
- break;
80
- }
81
- expectedIndex++;
72
+ let onlyNumericKeys = true;
73
+ const numberSeen = new Array(keys.length);
74
+ for (const key of keys) {
75
+ const n = Number(key);
76
+ if (isNaN(n) || !Number.isInteger(n) || n < 0 || n >= keys.length) {
77
+ onlyNumericKeys = false;
78
+ break;
79
+ } else {
80
+ numberSeen[n] = true;
82
81
  }
83
82
  }
84
83
 
85
- return isArrayLike
86
- ? values
87
- : Object.fromEntries(keys.map((key, i) => [key, values[i]]));
84
+ // If any number from 0..length-1 is missing, we can't treat this as an array
85
+ const allNumbersSeen = onlyNumericKeys && numberSeen.every((v) => v);
86
+ if (allNumbersSeen) {
87
+ return values;
88
+ } else {
89
+ // Return a plain object with the (key, value) pairs
90
+ return Object.fromEntries(keys.map((key, i) => [key, values[i]]));
91
+ }
88
92
  }
89
93
 
90
94
  /**
@@ -328,6 +332,22 @@ export function setParent(child, parent) {
328
332
  }
329
333
  }
330
334
 
335
+ function toBase64(object) {
336
+ if (typeof Buffer !== "undefined") {
337
+ // Node.js environment
338
+ return Buffer.from(object).toString("base64");
339
+ } else {
340
+ // Browser environment
341
+ let binary = "";
342
+ const bytes = new Uint8Array(object);
343
+ const len = bytes.byteLength;
344
+ for (let i = 0; i < len; i++) {
345
+ binary += String.fromCharCode(bytes[i]);
346
+ }
347
+ return btoa(binary);
348
+ }
349
+ }
350
+
331
351
  /**
332
352
  * Convert the given input to the plainest possible JavaScript value. This
333
353
  * helper is intended for functions that want to accept an argument from the ori
@@ -390,22 +410,6 @@ export async function toPlainValue(input) {
390
410
  }
391
411
  }
392
412
 
393
- function toBase64(object) {
394
- if (typeof Buffer !== "undefined") {
395
- // Node.js environment
396
- return Buffer.from(object).toString("base64");
397
- } else {
398
- // Browser environment
399
- let binary = "";
400
- const bytes = new Uint8Array(object);
401
- const len = bytes.byteLength;
402
- for (let i = 0; i < len; i++) {
403
- binary += String.fromCharCode(bytes[i]);
404
- }
405
- return btoa(binary);
406
- }
407
- }
408
-
409
413
  /**
410
414
  * Return a string form of the object, handling cases not generally handled by
411
415
  * the standard JavaScript `toString()` method:
@@ -6,15 +6,13 @@ import map from "../../src/operations/map.js";
6
6
  import * as trailingSlash from "../../src/trailingSlash.js";
7
7
 
8
8
  describe("map", () => {
9
- test("returns identity graph if no key or value function is supplied", async () => {
9
+ test("throws if no key or value function is supplied", async () => {
10
10
  const tree = {
11
11
  a: "letter a",
12
12
  b: "letter b",
13
13
  };
14
- const mapped = map(tree, {});
15
- assert.deepEqual(await Tree.plain(mapped), {
16
- a: "letter a",
17
- b: "letter b",
14
+ assert.throws(() => {
15
+ map(tree, {});
18
16
  });
19
17
  });
20
18
 
@@ -13,7 +13,8 @@ describe("paginate", () => {
13
13
  e: 5,
14
14
  };
15
15
  const paginated = await paginate.call(null, treelike, 2);
16
- assert.deepEqual(await Tree.plain(paginated), {
16
+ const plain = await Tree.plain(paginated);
17
+ assert.deepEqual(await plain, {
17
18
  1: {
18
19
  items: { a: 1, b: 2 },
19
20
  nextPage: 2,