@weborigami/async-tree 0.0.46 → 0.0.48

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.
Files changed (48) hide show
  1. package/index.ts +17 -0
  2. package/main.js +2 -3
  3. package/package.json +4 -4
  4. package/src/BrowserFileTree.js +1 -1
  5. package/src/DeepMapTree.js +1 -1
  6. package/src/DeepObjectTree.js +1 -2
  7. package/src/DeferredTree.js +22 -9
  8. package/src/FileTree.js +18 -17
  9. package/src/FunctionTree.js +0 -4
  10. package/src/MapTree.js +1 -1
  11. package/src/ObjectTree.js +1 -1
  12. package/src/SetTree.js +1 -1
  13. package/src/SiteTree.js +1 -6
  14. package/src/Tree.d.ts +1 -0
  15. package/src/Tree.js +48 -37
  16. package/src/internal.js +15 -0
  17. package/src/keysJson.js +1 -1
  18. package/src/operations/cache.js +2 -2
  19. package/src/operations/mergeDeep.js +1 -1
  20. package/src/symbols.js +1 -0
  21. package/src/transforms/cachedKeyFunctions.js +1 -1
  22. package/src/transforms/groupBy.js +2 -3
  23. package/src/transforms/map.js +1 -1
  24. package/src/transforms/regExpKeys.js +1 -1
  25. package/src/transforms/sort.js +1 -1
  26. package/src/transforms/sortBy.js +1 -1
  27. package/src/utilities.d.ts +3 -1
  28. package/src/utilities.js +37 -9
  29. package/test/BrowserFileTree.test.js +1 -1
  30. package/test/DeepObjectTree.test.js +1 -2
  31. package/test/DeferredTree.test.js +1 -2
  32. package/test/FileTree.test.js +1 -1
  33. package/test/FunctionTree.test.js +0 -6
  34. package/test/ObjectTree.test.js +1 -2
  35. package/test/SetTree.test.js +1 -1
  36. package/test/SiteTree.test.js +1 -1
  37. package/test/Tree.test.js +12 -22
  38. package/test/operations/cache.test.js +1 -2
  39. package/test/operations/merge.test.js +1 -1
  40. package/test/operations/mergeDeep.test.js +1 -2
  41. package/test/transforms/cachedKeyMaps.test.js +1 -1
  42. package/test/transforms/groupBy.test.js +1 -1
  43. package/test/transforms/keyMapsForExtensions.test.js +1 -2
  44. package/test/transforms/map.test.js +1 -3
  45. package/test/transforms/regExpKeys.test.js +1 -2
  46. package/test/transforms/sort.test.js +1 -1
  47. package/test/transforms/sortBy.test.js +1 -1
  48. 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
  };
@@ -37,6 +43,17 @@ export type Treelike =
37
43
 
38
44
  export type TreeTransform = (treelike: Treelike) => AsyncTree;
39
45
 
46
+ export type TypedArray =
47
+ Float32Array |
48
+ Float64Array |
49
+ Int8Array |
50
+ Int16Array |
51
+ Int32Array |
52
+ Uint8Array |
53
+ Uint8ClampedArray |
54
+ Uint16Array |
55
+ Uint32Array;
56
+
40
57
  export type Unpackable<T> = {
41
58
  unpack(): Promise<T>
42
59
  };
package/main.js CHANGED
@@ -4,17 +4,16 @@ 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 * as Tree from "./src/Tree.js";
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";
16
+ export * as symbols from "./src/symbols.js";
18
17
  export { default as cachedKeyFunctions } from "./src/transforms/cachedKeyFunctions.js";
19
18
  export { default as groupBy } from "./src/transforms/groupBy.js";
20
19
  export { default as keyFunctionsForExtensions } from "./src/transforms/keyFunctionsForExtensions.js";
package/package.json CHANGED
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "name": "@weborigami/async-tree",
3
- "version": "0.0.46",
3
+ "version": "0.0.48",
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.7",
11
- "typescript": "5.3.3"
10
+ "@types/node": "20.11.27",
11
+ "typescript": "5.4.2"
12
12
  },
13
13
  "dependencies": {
14
- "@weborigami/types": "0.0.46"
14
+ "@weborigami/types": "0.0.48"
15
15
  },
16
16
  "scripts": {
17
17
  "test": "node --test --test-reporter=spec",
@@ -1,4 +1,4 @@
1
- import * as Tree from "./Tree.js";
1
+ import { Tree } from "./internal.js";
2
2
  import { hiddenFileNames, isStringLike, sortNatural } from "./utilities.js";
3
3
 
4
4
  const TypedArray = Object.getPrototypeOf(Uint8Array);
@@ -1,5 +1,5 @@
1
+ import { Tree } from "./internal.js";
1
2
  import MapTree from "./MapTree.js";
2
- import * as Tree from "./Tree.js";
3
3
 
4
4
  export default class DeepMapTree extends MapTree {
5
5
  async get(key) {
@@ -1,5 +1,4 @@
1
- import ObjectTree from "./ObjectTree.js";
2
- import * as Tree from "./Tree.js";
1
+ import { ObjectTree, Tree } from "./internal.js";
3
2
  import { isPlainObject } from "./utilities.js";
4
3
 
5
4
  export default class DeepObjectTree extends ObjectTree {
@@ -1,4 +1,4 @@
1
- import * as Tree from "./Tree.js";
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._parent = null;
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._parent;
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._parent = parent;
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
- // If tree hasn't been loaded yet, setting scope has no effect.
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._parent) {
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._parent;
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._parent = null;
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 * as Tree from "./Tree.js";
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
  *
@@ -133,27 +132,29 @@ export default class FileTree {
133
132
  value = await value();
134
133
  }
135
134
 
135
+ let packed = false;
136
136
  if (value === null) {
137
137
  // Treat null value as empty string; will create an empty file.
138
138
  value = "";
139
- } else if (value instanceof ArrayBuffer) {
140
- // Convert ArrayBuffer to Uint8Array, which Node.js can write directly.
141
- value = new Uint8Array(value);
142
- }
143
-
144
- // True if fs.writeFile can directly write the value to a file.
145
- let isWriteable =
146
- value instanceof TypedArray ||
147
- value instanceof DataView ||
148
- (globalThis.ReadableStream && value instanceof ReadableStream);
149
-
150
- if (!isWriteable && isStringLike(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)) {
151
147
  // Value has a meaningful `toString` method, use that.
152
148
  value = String(value);
153
- isWriteable = true;
149
+ packed = true;
154
150
  }
155
151
 
156
- if (isWriteable) {
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
+ }
157
158
  // Ensure this directory exists.
158
159
  await fs.mkdir(this.dirname, { recursive: true });
159
160
  // Write out the value as the contents of a file.
@@ -41,8 +41,4 @@ export default class FunctionTree {
41
41
  async keys() {
42
42
  return this.domain;
43
43
  }
44
-
45
- async unpack() {
46
- return this.fn;
47
- }
48
44
  }
package/src/MapTree.js CHANGED
@@ -1,4 +1,4 @@
1
- import * as Tree from "./Tree.js";
1
+ import { Tree } from "./internal.js";
2
2
 
3
3
  /**
4
4
  * A tree backed by a JavaScript `Map` object.
package/src/ObjectTree.js CHANGED
@@ -1,4 +1,4 @@
1
- import * as Tree from "./Tree.js";
1
+ import { Tree } from "./internal.js";
2
2
  import { getRealmObjectPrototype } from "./utilities.js";
3
3
 
4
4
  /**
package/src/SetTree.js CHANGED
@@ -1,4 +1,4 @@
1
- import * as Tree from "./Tree.js";
1
+ import { Tree } from "./internal.js";
2
2
 
3
3
  /**
4
4
  * A tree of Set objects.
package/src/SiteTree.js CHANGED
@@ -1,4 +1,4 @@
1
- import * as Tree from "./Tree.js";
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,6 +10,7 @@ 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
15
  export function map(tree: Treelike, valueFn: ValueKeyFn): AsyncTree;
15
16
  export function mapReduce(tree: Treelike, mapFn: ValueKeyFn|null, reduceFn: ReduceFn): Promise<any>;
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 { castArrayLike, isPlainObject } from "./utilities.js";
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 && typeof obj === "object" && "unpack" in 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 `isTreeable`
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,27 +223,10 @@ 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
  *
@@ -364,24 +383,16 @@ export async function traverseOrThrow(treelike, ...keys) {
364
383
  throw new TraverseError(message, treelike, keys);
365
384
  }
366
385
 
367
- // Special case: one key left that's an empty string
368
- if (remainingKeys.length === 1 && remainingKeys[0] === "") {
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 (value instanceof Function) {
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.
@@ -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
@@ -1,4 +1,4 @@
1
- import * as Tree from "./Tree.js";
1
+ import { Tree } from "./internal.js";
2
2
 
3
3
  /**
4
4
  * The .keys.json file format lets a site expose the keys of a node in the site
@@ -1,4 +1,4 @@
1
- import { ObjectTree, Tree } from "@weborigami/async-tree";
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
@@ -21,7 +21,7 @@ export default function treeCache(sourceTreelike, cacheTree, filterTreelike) {
21
21
  const filter = filterTreelike ? Tree.from(filterTreelike) : undefined;
22
22
 
23
23
  /** @type {AsyncMutableTree} */
24
- const cache = cacheTree ?? new ObjectTree({});
24
+ const cache = cacheTree ?? new DeepObjectTree({});
25
25
  return {
26
26
  description: "cache",
27
27
 
@@ -1,4 +1,4 @@
1
- import * as Tree from "../Tree.js";
1
+ import { Tree } from "../internal.js";
2
2
 
3
3
  /**
4
4
  * Return a tree that performs a deep merge of the given trees.
package/src/symbols.js ADDED
@@ -0,0 +1 @@
1
+ export const parent = Symbol("parent");
@@ -1,4 +1,4 @@
1
- import * as Tree from "../Tree.js";
1
+ import { Tree } from "../internal.js";
2
2
 
3
3
  const treeToCaches = new WeakMap();
4
4
 
@@ -1,5 +1,4 @@
1
- import ObjectTree from "../ObjectTree.js";
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
@@ -37,6 +36,6 @@ export default function createGroupByTransform(groupKeyFn) {
37
36
  }
38
37
  }
39
38
 
40
- return new ObjectTree(result);
39
+ return new DeepObjectTree(result);
41
40
  };
42
41
  }
@@ -1,4 +1,4 @@
1
- import * as Tree from "../Tree.js";
1
+ import { Tree } from "../internal.js";
2
2
 
3
3
  /**
4
4
  * Return a transform function that maps the keys and/or values of a tree.
@@ -1,4 +1,4 @@
1
- import * as Tree from "../Tree.js";
1
+ import { Tree } from "../internal.js";
2
2
 
3
3
  /**
4
4
  * A tree whose keys are strings interpreted as regular expressions.
@@ -1,4 +1,4 @@
1
- import * as Tree from "../Tree.js";
1
+ import { Tree } from "../internal.js";
2
2
 
3
3
  /**
4
4
  * Return a transform function that sorts a tree's keys.
@@ -1,4 +1,4 @@
1
- import * as Tree from "../Tree.js";
1
+ import { Tree } from "../internal.js";
2
2
 
3
3
  /**
4
4
  * Return a transform function that sorts a tree's keys.
@@ -1,9 +1,11 @@
1
- import { PlainObject, StringLike } from "../index.ts";
1
+ import { Packed, PlainObject, StringLike } from "../index.ts";
2
2
 
3
3
  export function castArrayLike(object: any): any;
4
4
  export function getRealmObjectPrototype(object: any): any;
5
5
  export const hiddenFileNames: string[];
6
+ export function isPacked(object: any): object is Packed;
6
7
  export function isPlainObject(object: any): object is PlainObject;
8
+ export function isUnpackable(object): object is { unpack: () => any };
7
9
  export function isStringLike(obj: any): obj is StringLike;
8
10
  export function keysFromPath(path: string): string[];
9
11
  export const naturalSortCompareFn: (a: string, b: string) => number;
package/src/utilities.js CHANGED
@@ -1,3 +1,5 @@
1
+ const TypedArray = Object.getPrototypeOf(Uint8Array);
2
+
1
3
  /**
2
4
  * If the given plain object has only sequential integer keys, return it as an
3
5
  * array. Otherwise return it as is.
@@ -19,9 +21,6 @@ export function castArrayLike(object) {
19
21
  return hasKeys ? Object.values(object) : object;
20
22
  }
21
23
 
22
- // Names of OS-generated files that should not be enumerated
23
- export const hiddenFileNames = [".DS_Store"];
24
-
25
24
  /**
26
25
  * Return the Object prototype at the root of the object's prototype chain.
27
26
  *
@@ -38,6 +37,27 @@ export function getRealmObjectPrototype(object) {
38
37
  return proto;
39
38
  }
40
39
 
40
+ // Names of OS-generated files that should not be enumerated
41
+ export const hiddenFileNames = [".DS_Store"];
42
+
43
+ /**
44
+ * Return true if the object is in a packed form (or can be readily packed into
45
+ * a form) that can be given to fs.writeFile or response.write().
46
+ *
47
+ * @param {any} object
48
+ * @returns {object is import("../index.ts").Packed}
49
+ */
50
+ export function isPacked(object) {
51
+ return (
52
+ typeof object === "string" ||
53
+ object instanceof ArrayBuffer ||
54
+ object instanceof Buffer ||
55
+ object instanceof ReadableStream ||
56
+ object instanceof String ||
57
+ object instanceof TypedArray
58
+ );
59
+ }
60
+
41
61
  /**
42
62
  * Return true if the object is a plain JavaScript object created by `{}`,
43
63
  * `new Object()`, or `Object.create(null)`.
@@ -46,6 +66,7 @@ export function getRealmObjectPrototype(object) {
46
66
  * `Module`) as plain objects.
47
67
  *
48
68
  * @param {any} object
69
+ * @returns {object is import("../index.ts").PlainObject}
49
70
  */
50
71
  export function isPlainObject(object) {
51
72
  // From https://stackoverflow.com/q/51722354/76472
@@ -67,15 +88,15 @@ export function isPlainObject(object) {
67
88
  * Return true if the object is a string or object with a non-trival `toString`
68
89
  * method.
69
90
  *
70
- * @param {any} obj
71
- * @returns {obj is import("@weborigami/async-tree").StringLike}
91
+ * @param {any} object
92
+ * @returns {obj is import("../index.ts").StringLike}
72
93
  */
73
- export function isStringLike(obj) {
74
- if (typeof obj === "string") {
94
+ export function isStringLike(object) {
95
+ if (typeof object === "string") {
75
96
  return true;
76
- } else if (obj?.toString === undefined) {
97
+ } else if (object?.toString === undefined) {
77
98
  return false;
78
- } else if (obj.toString === getRealmObjectPrototype(obj).toString) {
99
+ } else if (object.toString === getRealmObjectPrototype(object).toString) {
79
100
  // The stupid Object.prototype.toString implementation always returns
80
101
  // "[object Object]", so if that's the only toString method the object has,
81
102
  // we return false.
@@ -85,6 +106,13 @@ export function isStringLike(obj) {
85
106
  }
86
107
  }
87
108
 
109
+ export function isUnpackable(object) {
110
+ return (
111
+ isPacked(object) &&
112
+ typeof (/** @type {any} */ (object).unpack) === "function"
113
+ );
114
+ }
115
+
88
116
  /**
89
117
  * Given a path like "/foo/bar/baz", return an array of keys like ["foo", "bar",
90
118
  * "baz"].
@@ -1,7 +1,7 @@
1
1
  import assert from "node:assert";
2
2
  import { describe, test } from "node:test";
3
3
  import BrowserFileTree from "../src/BrowserFileTree.js";
4
- import * as Tree from "../src/Tree.js";
4
+ import { Tree } from "../src/internal.js";
5
5
 
6
6
  // Skip these tests if we're not in a browser.
7
7
  const isBrowser = typeof window !== "undefined";
@@ -1,7 +1,6 @@
1
1
  import assert from "node:assert";
2
2
  import { describe, test } from "node:test";
3
- import DeepObjectTree from "../src/DeepObjectTree.js";
4
- import * as Tree from "../src/Tree.js";
3
+ import { DeepObjectTree, Tree } from "../src/internal.js";
5
4
 
6
5
  describe("DeepObjectTree", () => {
7
6
  test("returns an ObjectTree for value that's a plain sub-object or sub-array", async () => {
@@ -1,8 +1,7 @@
1
1
  import assert from "node:assert";
2
2
  import { describe, test } from "node:test";
3
3
  import DeferredTree from "../src/DeferredTree.js";
4
- import ObjectTree from "../src/ObjectTree.js";
5
- import * as Tree from "../src/Tree.js";
4
+ import { ObjectTree, Tree } from "../src/internal.js";
6
5
 
7
6
  describe("DeferredTree", () => {
8
7
  test("lazy-loads a treelike object", async () => {
@@ -4,7 +4,7 @@ import path from "node:path";
4
4
  import { describe, test } from "node:test";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import FileTree from "../src/FileTree.js";
7
- import * as Tree from "../src/Tree.js";
7
+ import { Tree } from "../src/internal.js";
8
8
 
9
9
  const dirname = path.dirname(fileURLToPath(import.meta.url));
10
10
  const tempDirectory = path.join(dirname, "fixtures/temp");
@@ -18,12 +18,6 @@ describe("FunctionTree", async () => {
18
18
  assert.equal(alice, "Hello, **Alice**.");
19
19
  });
20
20
 
21
- test("unpacking the tree returns the function itself", async () => {
22
- const fn = () => true;
23
- const fixture = new FunctionTree(fn);
24
- assert.equal(await fixture.unpack(), fn);
25
- });
26
-
27
21
  test("getting a value from function with multiple arguments curries the function", async () => {
28
22
  const fixture = new FunctionTree((a, b, c) => a + b + c);
29
23
  const fnA = await fixture.get(1);
@@ -1,7 +1,6 @@
1
1
  import assert from "node:assert";
2
2
  import { describe, test } from "node:test";
3
- import ObjectTree from "../src/ObjectTree.js";
4
- import * as Tree from "../src/Tree.js";
3
+ import { ObjectTree, Tree } from "../src/internal.js";
5
4
 
6
5
  describe("ObjectTree", () => {
7
6
  test("can get the keys of the tree", async () => {
@@ -1,7 +1,7 @@
1
1
  import assert from "node:assert";
2
2
  import { describe, test } from "node:test";
3
- import ObjectTree from "../src/ObjectTree.js";
4
3
  import SetTree from "../src/SetTree.js";
4
+ import { ObjectTree } from "../src/internal.js";
5
5
 
6
6
  describe("SetTree", () => {
7
7
  test("can get the keys of the tree", async () => {
@@ -1,7 +1,7 @@
1
1
  import assert from "node:assert";
2
2
  import { beforeEach, describe, mock, test } from "node:test";
3
3
  import SiteTree from "../src/SiteTree.js";
4
- import * as Tree from "../src/Tree.js";
4
+ import { Tree } from "../src/internal.js";
5
5
 
6
6
  const mockHost = "https://mock";
7
7
 
package/test/Tree.test.js CHANGED
@@ -1,9 +1,7 @@
1
1
  import assert from "node:assert";
2
2
  import { describe, test } from "node:test";
3
- import { DeepObjectTree } from "../main.js";
4
3
  import MapTree from "../src/MapTree.js";
5
- import ObjectTree from "../src/ObjectTree.js";
6
- import * as Tree from "../src/Tree.js";
4
+ import { DeepObjectTree, ObjectTree, Tree } from "../src/internal.js";
7
5
 
8
6
  describe("Tree", () => {
9
7
  test("assign applies one tree to another", async () => {
@@ -89,13 +87,10 @@ describe("Tree", () => {
89
87
  });
90
88
 
91
89
  test("from() uses an object's unpack() method if defined", async () => {
92
- const obj = {
93
- unpack() {
94
- return {
95
- a: "Hello, a.",
96
- };
97
- },
98
- };
90
+ const obj = new String();
91
+ /** @type {any} */ (obj).unpack = () => ({
92
+ a: "Hello, a.",
93
+ });
99
94
  const tree = Tree.from(obj);
100
95
  assert.deepEqual(await Tree.plain(tree), {
101
96
  a: "Hello, a.",
@@ -103,13 +98,10 @@ describe("Tree", () => {
103
98
  });
104
99
 
105
100
  test("from() creates a deferred tree if unpack() returns a promise", async () => {
106
- const obj = {
107
- async unpack() {
108
- return {
109
- a: "Hello, a.",
110
- };
111
- },
112
- };
101
+ const obj = new String();
102
+ /** @type {any} */ (obj).unpack = async () => ({
103
+ a: "Hello, a.",
104
+ });
113
105
  const tree = Tree.from(obj);
114
106
  assert.deepEqual(await Tree.plain(tree), {
115
107
  a: "Hello, a.",
@@ -299,12 +291,10 @@ describe("Tree", () => {
299
291
  });
300
292
 
301
293
  test("traversing a final empty string can unpack the last value", async () => {
294
+ const unpackable = new String();
295
+ /** @type {any} */ (unpackable).unpack = () => "Content";
302
296
  const tree = {
303
- unpackable: {
304
- unpack() {
305
- return "Content";
306
- },
307
- },
297
+ unpackable,
308
298
  };
309
299
  const result = await Tree.traverse(tree, "unpackable", "");
310
300
  assert.equal(result, "Content");
@@ -1,7 +1,6 @@
1
1
  import assert from "node:assert";
2
2
  import { describe, test } from "node:test";
3
- import ObjectTree from "../../src/ObjectTree.js";
4
- import * as Tree from "../../src/Tree.js";
3
+ import { ObjectTree, Tree } from "../../src/internal.js";
5
4
  import cache from "../../src/operations/cache.js";
6
5
 
7
6
  describe("cache", () => {
@@ -1,6 +1,6 @@
1
1
  import assert from "node:assert";
2
2
  import { describe, test } from "node:test";
3
- import * as Tree from "../../src/Tree.js";
3
+ import { Tree } from "../../src/internal.js";
4
4
  import merge from "../../src/operations/merge.js";
5
5
 
6
6
  describe("merge", () => {
@@ -1,7 +1,6 @@
1
1
  import assert from "node:assert";
2
2
  import { describe, test } from "node:test";
3
- import DeepObjectTree from "../../src/DeepObjectTree.js";
4
- import * as Tree from "../../src/Tree.js";
3
+ import { DeepObjectTree, Tree } from "../../src/internal.js";
5
4
  import mergeDeep from "../../src/operations/mergeDeep.js";
6
5
 
7
6
  describe("mergeDeep", () => {
@@ -1,6 +1,6 @@
1
1
  import assert from "node:assert";
2
2
  import { describe, test } from "node:test";
3
- import ObjectTree from "../../src/ObjectTree.js";
3
+ import { ObjectTree } from "../../src/internal.js";
4
4
  import cachedKeyFunctions from "../../src/transforms/cachedKeyFunctions.js";
5
5
 
6
6
  describe("cachedKeyFunctions", () => {
@@ -1,6 +1,6 @@
1
1
  import assert from "node:assert";
2
2
  import { describe, test } from "node:test";
3
- import * as Tree from "../../src/Tree.js";
3
+ import { Tree } from "../../src/internal.js";
4
4
  import groupBy from "../../src/transforms/groupBy.js";
5
5
 
6
6
  describe("groupBy transform", () => {
@@ -1,7 +1,6 @@
1
1
  import assert from "node:assert";
2
2
  import { describe, test } from "node:test";
3
- import ObjectTree from "../../src/ObjectTree.js";
4
- import * as Tree from "../../src/Tree.js";
3
+ import { ObjectTree, Tree } from "../../src/internal.js";
5
4
  import keyFunctionsForExtensions from "../../src/transforms/keyFunctionsForExtensions.js";
6
5
  import map from "../../src/transforms/map.js";
7
6
 
@@ -1,9 +1,7 @@
1
1
  import assert from "node:assert";
2
2
  import { describe, test } from "node:test";
3
- import { ObjectTree } from "../../main.js";
4
- import DeepObjectTree from "../../src/DeepObjectTree.js";
5
3
  import FunctionTree from "../../src/FunctionTree.js";
6
- import * as Tree from "../../src/Tree.js";
4
+ import { DeepObjectTree, ObjectTree, Tree } from "../../src/internal.js";
7
5
  import map from "../../src/transforms/map.js";
8
6
 
9
7
  describe("map", () => {
@@ -1,7 +1,6 @@
1
1
  import assert from "node:assert";
2
2
  import { describe, test } from "node:test";
3
- import DeepObjectTree from "../../src/DeepObjectTree.js";
4
- import * as Tree from "../../src/Tree.js";
3
+ import { DeepObjectTree, Tree } from "../../src/internal.js";
5
4
  import regExpKeys from "../../src/transforms/regExpKeys.js";
6
5
 
7
6
  describe("regExpKeys", () => {
@@ -1,6 +1,6 @@
1
1
  import assert from "node:assert";
2
2
  import { describe, test } from "node:test";
3
- import * as Tree from "../../src/Tree.js";
3
+ import { Tree } from "../../src/internal.js";
4
4
  import sort from "../../src/transforms/sort.js";
5
5
 
6
6
  describe("sort transform", () => {
@@ -1,6 +1,6 @@
1
1
  import assert from "node:assert";
2
2
  import { describe, test } from "node:test";
3
- import * as Tree from "../../src/Tree.js";
3
+ import { Tree } from "../../src/internal.js";
4
4
  import sortBy from "../../src/transforms/sortBy.js";
5
5
 
6
6
  describe("sortBy transform", () => {
@@ -1,6 +1,6 @@
1
1
  import assert from "node:assert";
2
2
  import { describe, test } from "node:test";
3
- import * as Tree from "../../src/Tree.js";
3
+ import { Tree } from "../../src/internal.js";
4
4
  import sortNatural from "../../src/transforms/sortNatural.js";
5
5
 
6
6
  describe("sortNatural transform", () => {