@weborigami/async-tree 0.5.3 → 0.5.5

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 (159) hide show
  1. package/index.ts +16 -6
  2. package/package.json +2 -2
  3. package/shared.js +20 -30
  4. package/src/Tree.js +62 -502
  5. package/src/constants.js +2 -0
  6. package/src/drivers/BrowserFileTree.js +9 -10
  7. package/src/drivers/DeepMapTree.js +3 -3
  8. package/src/drivers/DeepObjectTree.js +4 -5
  9. package/src/drivers/DeferredTree.js +2 -2
  10. package/src/drivers/FileTree.js +11 -33
  11. package/src/drivers/FunctionTree.js +1 -1
  12. package/src/drivers/MapTree.js +6 -6
  13. package/src/drivers/ObjectTree.js +4 -3
  14. package/src/drivers/SetTree.js +1 -1
  15. package/src/drivers/SiteTree.js +1 -1
  16. package/src/drivers/constantTree.js +1 -1
  17. package/src/extension.js +5 -3
  18. package/src/jsonKeys.js +5 -7
  19. package/src/operations/addNextPrevious.js +10 -9
  20. package/src/operations/assign.js +40 -0
  21. package/src/operations/cache.js +18 -12
  22. package/src/operations/cachedKeyFunctions.js +15 -4
  23. package/src/operations/clear.js +20 -0
  24. package/src/operations/concat.js +17 -0
  25. package/src/operations/deepMap.js +25 -0
  26. package/src/operations/deepMerge.js +11 -25
  27. package/src/operations/deepReverse.js +6 -7
  28. package/src/operations/deepTake.js +6 -7
  29. package/src/operations/deepText.js +4 -4
  30. package/src/operations/deepValuesIterator.js +8 -6
  31. package/src/operations/defineds.js +32 -0
  32. package/src/operations/delete.js +20 -0
  33. package/src/operations/entries.js +16 -0
  34. package/src/operations/extensionKeyFunctions.js +1 -1
  35. package/src/operations/filter.js +7 -8
  36. package/src/operations/first.js +18 -0
  37. package/src/operations/forEach.js +20 -0
  38. package/src/operations/from.js +77 -0
  39. package/src/operations/fromFn.js +26 -0
  40. package/src/operations/globKeys.js +8 -8
  41. package/src/operations/group.js +9 -7
  42. package/src/operations/has.js +16 -0
  43. package/src/operations/indent.js +4 -2
  44. package/src/operations/inners.js +29 -0
  45. package/src/operations/invokeFunctions.js +5 -4
  46. package/src/operations/isAsyncMutableTree.js +15 -0
  47. package/src/operations/isAsyncTree.js +21 -0
  48. package/src/operations/isTraversable.js +15 -0
  49. package/src/operations/isTreelike.js +33 -0
  50. package/src/operations/json.js +4 -3
  51. package/src/operations/keys.js +14 -0
  52. package/src/operations/length.js +15 -0
  53. package/src/operations/map.js +157 -95
  54. package/src/operations/mapExtension.js +27 -0
  55. package/src/operations/mapReduce.js +44 -0
  56. package/src/operations/mask.js +18 -16
  57. package/src/operations/match.js +74 -0
  58. package/src/operations/merge.js +22 -20
  59. package/src/operations/paginate.js +3 -5
  60. package/src/operations/parent.js +13 -0
  61. package/src/operations/paths.js +51 -0
  62. package/src/operations/plain.js +34 -0
  63. package/src/operations/regExpKeys.js +4 -5
  64. package/src/operations/remove.js +14 -0
  65. package/src/operations/reverse.js +4 -6
  66. package/src/operations/root.js +17 -0
  67. package/src/operations/scope.js +4 -6
  68. package/src/operations/setDeep.js +50 -0
  69. package/src/operations/shuffle.js +46 -0
  70. package/src/operations/sort.js +19 -12
  71. package/src/operations/take.js +3 -5
  72. package/src/operations/text.js +3 -3
  73. package/src/operations/toFunction.js +14 -0
  74. package/src/operations/traverse.js +25 -0
  75. package/src/operations/traverseOrThrow.js +64 -0
  76. package/src/operations/traversePath.js +16 -0
  77. package/src/operations/values.js +15 -0
  78. package/src/operations/withKeys.js +33 -0
  79. package/src/utilities/TypedArray.js +2 -0
  80. package/src/utilities/box.js +20 -0
  81. package/src/utilities/castArraylike.js +38 -0
  82. package/src/utilities/getParent.js +33 -0
  83. package/src/utilities/getRealmObjectPrototype.js +19 -0
  84. package/src/utilities/getTreeArgument.js +43 -0
  85. package/src/utilities/isPacked.js +20 -0
  86. package/src/utilities/isPlainObject.js +29 -0
  87. package/src/utilities/isPrimitive.js +13 -0
  88. package/src/utilities/isStringlike.js +25 -0
  89. package/src/utilities/isUnpackable.js +13 -0
  90. package/src/utilities/keysFromPath.js +34 -0
  91. package/src/utilities/naturalOrder.js +9 -0
  92. package/src/utilities/pathFromKeys.js +18 -0
  93. package/src/utilities/setParent.js +38 -0
  94. package/src/utilities/toFunction.js +41 -0
  95. package/src/utilities/toPlainValue.js +95 -0
  96. package/src/utilities/toString.js +37 -0
  97. package/test/drivers/ExplorableSiteTree.test.js +1 -1
  98. package/test/drivers/FileTree.test.js +1 -1
  99. package/test/drivers/calendarTree.test.js +1 -1
  100. package/test/jsonKeys.test.js +1 -1
  101. package/test/operations/assign.test.js +54 -0
  102. package/test/operations/cache.test.js +1 -1
  103. package/test/operations/cachedKeyFunctions.test.js +16 -16
  104. package/test/operations/clear.test.js +34 -0
  105. package/test/operations/deepMap.test.js +29 -0
  106. package/test/operations/deepMerge.test.js +2 -6
  107. package/test/operations/deepReverse.test.js +1 -1
  108. package/test/operations/defineds.test.js +25 -0
  109. package/test/operations/delete.test.js +20 -0
  110. package/test/operations/entries.test.js +18 -0
  111. package/test/operations/extensionKeyFunctions.test.js +10 -10
  112. package/test/operations/first.test.js +15 -0
  113. package/test/operations/fixtures/README.md +1 -0
  114. package/test/operations/forEach.test.js +22 -0
  115. package/test/operations/from.test.js +67 -0
  116. package/test/operations/globKeys.test.js +3 -3
  117. package/test/operations/has.test.js +15 -0
  118. package/test/operations/inners.test.js +30 -0
  119. package/test/operations/invokeFunctions.test.js +1 -1
  120. package/test/operations/isAsyncMutableTree.test.js +17 -0
  121. package/test/operations/isAsyncTree.test.js +26 -0
  122. package/test/operations/isTreelike.test.js +13 -0
  123. package/test/operations/keys.test.js +15 -0
  124. package/test/operations/length.test.js +15 -0
  125. package/test/operations/map.test.js +62 -45
  126. package/test/operations/mapExtension.test.js +0 -0
  127. package/test/operations/mapReduce.test.js +23 -0
  128. package/test/operations/mask.test.js +1 -1
  129. package/test/operations/match.test.js +33 -0
  130. package/test/operations/merge.test.js +23 -9
  131. package/test/operations/paginate.test.js +2 -1
  132. package/test/operations/parent.test.js +15 -0
  133. package/test/operations/paths.test.js +40 -0
  134. package/test/operations/plain.test.js +69 -0
  135. package/test/operations/reverse.test.js +1 -1
  136. package/test/operations/scope.test.js +1 -1
  137. package/test/operations/setDeep.test.js +53 -0
  138. package/test/operations/shuffle.test.js +18 -0
  139. package/test/operations/sort.test.js +3 -3
  140. package/test/operations/toFunction.test.js +16 -0
  141. package/test/operations/traverse.test.js +43 -0
  142. package/test/operations/traversePath.test.js +16 -0
  143. package/test/operations/values.test.js +18 -0
  144. package/test/operations/withKeys.test.js +21 -0
  145. package/test/utilities/box.test.js +26 -0
  146. package/test/utilities/getRealmObjectPrototype.test.js +11 -0
  147. package/test/utilities/isPlainObject.test.js +13 -0
  148. package/test/utilities/keysFromPath.test.js +14 -0
  149. package/test/utilities/naturalOrder.test.js +11 -0
  150. package/test/utilities/pathFromKeys.test.js +12 -0
  151. package/test/utilities/setParent.test.js +34 -0
  152. package/test/utilities/toFunction.test.js +34 -0
  153. package/test/utilities/toPlainValue.test.js +27 -0
  154. package/test/utilities/toString.test.js +22 -0
  155. package/src/Tree.d.ts +0 -24
  156. package/src/utilities.d.ts +0 -21
  157. package/src/utilities.js +0 -439
  158. package/test/Tree.test.js +0 -407
  159. package/test/utilities.test.js +0 -141
package/src/utilities.js DELETED
@@ -1,439 +0,0 @@
1
- import { Tree } from "./internal.js";
2
- import * as symbols from "./symbols.js";
3
- import * as trailingSlash from "./trailingSlash.js";
4
-
5
- const textDecoder = new TextDecoder();
6
- const TypedArray = Object.getPrototypeOf(Uint8Array);
7
-
8
- /** @typedef {import("@weborigami/types").AsyncTree} AsyncTree */
9
-
10
- /**
11
- * If the given object isn't treelike, throw an exception.
12
- *
13
- * @param {any} object
14
- * @param {string} operation
15
- * @param {number} [position]
16
- */
17
- export function assertIsTreelike(object, operation, position = 0) {
18
- let message;
19
- if (!object) {
20
- message = `${operation}: The tree argument wasn't defined.`;
21
- } else if (object instanceof Promise) {
22
- // A common mistake
23
- message = `${operation}: The tree argument was a Promise. Did you mean to use await?`;
24
- } else if (!Tree.isTreelike) {
25
- message = `${operation}: The tree argument wasn't a treelike object.`;
26
- }
27
- if (message) {
28
- const error = new TypeError(message);
29
- /** @type {any} */ (error).position = position;
30
- throw error;
31
- }
32
- }
33
-
34
- /**
35
- * Return the value as an object. If the value is already an object it will be
36
- * returned as is. If the value is a primitive, it will be wrapped in an object:
37
- * a string will be wrapped in a String object, a number will be wrapped in a
38
- * Number object, and a boolean will be wrapped in a Boolean object.
39
- *
40
- * @param {any} value
41
- */
42
- export function box(value) {
43
- switch (typeof value) {
44
- case "string":
45
- return new String(value);
46
- case "number":
47
- return new Number(value);
48
- case "boolean":
49
- return new Boolean(value);
50
- default:
51
- return value;
52
- }
53
- }
54
-
55
- /**
56
- * Create an array or plain object from the given keys and values.
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.
61
- *
62
- * @param {any[]} keys
63
- * @param {any[]} values
64
- */
65
- export function castArrayLike(keys, values) {
66
- let isArrayLike = false;
67
-
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++;
82
- }
83
- }
84
-
85
- return isArrayLike
86
- ? values
87
- : Object.fromEntries(keys.map((key, i) => [key, values[i]]));
88
- }
89
-
90
- /**
91
- * Return a suitable parent for the packed file.
92
- *
93
- * This is intended to be called by unpack functions.
94
- *
95
- * @param {any} packed
96
- * @param {any} [options]
97
- * @returns {AsyncTree|null}
98
- */
99
- export function getParent(packed, options = {}) {
100
- // Prefer parent set on options
101
- if (options?.parent) {
102
- return options.parent;
103
- }
104
-
105
- // If the packed object has a `parent` property, use that. Exception: Node
106
- // Buffer objects have a `parent` property that we ignore.
107
- if (packed.parent && !(packed instanceof Buffer)) {
108
- return packed.parent;
109
- }
110
-
111
- // If the packed object has a parent symbol, use that.
112
- if (packed[symbols.parent]) {
113
- return packed[symbols.parent];
114
- }
115
-
116
- // Otherwise, return null.
117
- return null;
118
- }
119
-
120
- /**
121
- * Return the Object prototype at the root of the object's prototype chain.
122
- *
123
- * This is used by functions like isPlainObject() to handle cases where the
124
- * `Object` at the root prototype chain is in a different realm.
125
- *
126
- * @param {any} object
127
- */
128
- export function getRealmObjectPrototype(object) {
129
- if (Object.getPrototypeOf(object) === null) {
130
- // The object has no prototype.
131
- return null;
132
- }
133
- let proto = object;
134
- while (Object.getPrototypeOf(proto) !== null) {
135
- proto = Object.getPrototypeOf(proto);
136
- }
137
- return proto;
138
- }
139
-
140
- // Names of OS-generated files that should not be enumerated
141
- export const hiddenFileNames = [".DS_Store"];
142
-
143
- /**
144
- * Return true if the object is in a packed form (or can be readily packed into
145
- * a form) that can be given to fs.writeFile or response.write().
146
- *
147
- * @param {any} obj
148
- * @returns {obj is import("../index.ts").Packed}
149
- */
150
- export function isPacked(obj) {
151
- return (
152
- typeof obj === "string" ||
153
- obj instanceof ArrayBuffer ||
154
- obj instanceof ReadableStream ||
155
- obj instanceof String ||
156
- obj instanceof TypedArray
157
- );
158
- }
159
-
160
- /**
161
- * Return true if the object is a plain JavaScript object created by `{}`,
162
- * `new Object()`, or `Object.create(null)`.
163
- *
164
- * This function also considers object-like things with no prototype (like a
165
- * `Module`) as plain objects.
166
- *
167
- * @param {any} obj
168
- * @returns {obj is import("../index.ts").PlainObject}
169
- */
170
- export function isPlainObject(obj) {
171
- // From https://stackoverflow.com/q/51722354/76472
172
- if (typeof obj !== "object" || obj === null) {
173
- return false;
174
- }
175
-
176
- // We treat object-like things with no prototype (like a Module) as plain
177
- // objects.
178
- if (Object.getPrototypeOf(obj) === null) {
179
- return true;
180
- }
181
-
182
- // Do we inherit directly from Object in this realm?
183
- return Object.getPrototypeOf(obj) === getRealmObjectPrototype(obj);
184
- }
185
-
186
- /**
187
- * Return true if the value is a primitive JavaScript value.
188
- *
189
- * @param {any} value
190
- */
191
- export function isPrimitive(value) {
192
- // Check for null first, since typeof null === "object".
193
- if (value === null) {
194
- return true;
195
- }
196
- const type = typeof value;
197
- return type !== "object" && type !== "function";
198
- }
199
-
200
- /**
201
- * Return true if the object is a string or object with a non-trival `toString`
202
- * method.
203
- *
204
- * @param {any} obj
205
- * @returns {obj is import("../index.ts").StringLike}
206
- */
207
- export function isStringLike(obj) {
208
- if (typeof obj === "string") {
209
- return true;
210
- } else if (obj?.toString === undefined) {
211
- return false;
212
- } else if (obj.toString === getRealmObjectPrototype(obj)?.toString) {
213
- // The stupid Object.prototype.toString implementation always returns
214
- // "[object Object]", so if that's the only toString method the object has,
215
- // we return false.
216
- return false;
217
- } else {
218
- return true;
219
- }
220
- }
221
-
222
- export function isUnpackable(obj) {
223
- return (
224
- isPacked(obj) && typeof (/** @type {any} */ (obj).unpack) === "function"
225
- );
226
- }
227
-
228
- /**
229
- * Given a path like "/foo/bar/baz", return an array of keys like ["foo/",
230
- * "bar/", "baz"].
231
- *
232
- * Leading slashes are ignored. Consecutive slashes will be ignored. Trailing
233
- * slashes are preserved.
234
- *
235
- * @param {string} pathname
236
- */
237
- export function keysFromPath(pathname) {
238
- // Split the path at each slash
239
- let keys = pathname.split("/");
240
- if (keys[0] === "") {
241
- // The path begins with a slash; drop that part.
242
- keys.shift();
243
- }
244
- if (keys.at(-1) === "") {
245
- // The path ends with a slash; drop that part.
246
- keys.pop();
247
- }
248
- // Drop any empty keys
249
- keys = keys.filter((key) => key !== "");
250
- // Add the trailing slash back to all keys but the last
251
- for (let i = 0; i < keys.length - 1; i++) {
252
- keys[i] += "/";
253
- }
254
- // Add trailing slash to last key if path ended with a slash
255
- if (keys.length > 0 && trailingSlash.has(pathname)) {
256
- keys[keys.length - 1] += "/";
257
- }
258
- return keys;
259
- }
260
-
261
- /**
262
- * Compare two strings using [natural sort
263
- * order](https://en.wikipedia.org/wiki/Natural_sort_order).
264
- */
265
- export const naturalOrder = new Intl.Collator(undefined, {
266
- numeric: true,
267
- }).compare;
268
-
269
- /**
270
- * Return a slash-separated path for the given keys.
271
- *
272
- * This takes care to avoid adding consecutive slashes if they keys themselves
273
- * already have trailing slashes.
274
- *
275
- * @param {string[]} keys
276
- */
277
- export function pathFromKeys(keys) {
278
- // Ensure there's a slash between all keys. If the last key has a trailing
279
- // slash, leave it there.
280
- const normalized = keys.map((key, index) =>
281
- index < keys.length - 1 ? trailingSlash.add(key) : key
282
- );
283
- return normalized.join("");
284
- }
285
-
286
- /**
287
- * Apply a series of functions to a value, passing the result of each function
288
- * to the next one.
289
- *
290
- * @param {any} start
291
- * @param {...Function} fns
292
- */
293
- export async function pipeline(start, ...fns) {
294
- return fns.reduce(async (acc, fn) => fn(await acc), start);
295
- }
296
-
297
- /**
298
- * If the child object doesn't have a parent yet, set it to the indicated
299
- * parent. If the child is an AsyncTree, set the `parent` property. Otherwise,
300
- * set the `symbols.parent` property.
301
- *
302
- * @param {*} child
303
- * @param {AsyncTree|null} parent
304
- */
305
- export function setParent(child, parent) {
306
- if (Tree.isAsyncTree(child)) {
307
- // Value is a subtree; set its parent to this tree.
308
- if (!child.parent) {
309
- child.parent = parent;
310
- }
311
- } else if (Object.isExtensible(child) && !child[symbols.parent]) {
312
- try {
313
- // Add parent reference as a symbol to avoid polluting the object. This
314
- // reference will be used if the object is later used as a tree. We set
315
- // `enumerable` to false even thought this makes no practical difference
316
- // (symbols are never enumerated) because it can provide a hint in the
317
- // debugger that the property is for internal use.
318
- Object.defineProperty(child, symbols.parent, {
319
- configurable: true,
320
- enumerable: false,
321
- value: parent,
322
- writable: true,
323
- });
324
- } catch (error) {
325
- // Ignore exceptions. Some esoteric objects don't allow adding properties.
326
- // We can still treat them as trees, but they won't have a parent.
327
- }
328
- }
329
- }
330
-
331
- /**
332
- * Convert the given input to the plainest possible JavaScript value. This
333
- * helper is intended for functions that want to accept an argument from the ori
334
- * CLI, which could a string, a stream of data, or some other kind of JavaScript
335
- * object.
336
- *
337
- * If the input is a function, it will be invoked and its result will be
338
- * processed.
339
- *
340
- * If the input is a promise, it will be resolved and its result will be
341
- * processed.
342
- *
343
- * If the input is treelike, it will be converted to a plain JavaScript object,
344
- * recursively traversing the tree and converting all values to plain types.
345
- *
346
- * If the input is stringlike, its text will be returned.
347
- *
348
- * If the input is a ArrayBuffer or typed array, it will be interpreted as UTF-8
349
- * text if it does not contain unprintable characters. If it does, it will be
350
- * returned as a base64-encoded string.
351
- *
352
- * If the input has a custom class instance, its public properties will be
353
- * returned as a plain object.
354
- *
355
- * @param {any} input
356
- * @returns {Promise<any>}
357
- */
358
- export async function toPlainValue(input) {
359
- if (input instanceof Function) {
360
- // Invoke function
361
- input = input();
362
- }
363
- if (input instanceof Promise) {
364
- // Resolve promise
365
- input = await input;
366
- }
367
-
368
- if (isPrimitive(input) || input instanceof Date) {
369
- return input;
370
- } else if (Tree.isTreelike(input)) {
371
- const mapped = await Tree.map(input, (value) => toPlainValue(value));
372
- return Tree.plain(mapped);
373
- } else if (isStringLike(input)) {
374
- return toString(input);
375
- } else if (input instanceof ArrayBuffer || input instanceof TypedArray) {
376
- // Try to interpret the buffer as UTF-8 text, otherwise use base64.
377
- const text = toString(input);
378
- if (text !== null) {
379
- return text;
380
- } else {
381
- return toBase64(input);
382
- }
383
- } else {
384
- // Some other kind of class instance; return its public properties.
385
- const plain = {};
386
- for (const [key, value] of Object.entries(input)) {
387
- plain[key] = await toPlainValue(value);
388
- }
389
- return plain;
390
- }
391
- }
392
-
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
- /**
410
- * Return a string form of the object, handling cases not generally handled by
411
- * the standard JavaScript `toString()` method:
412
- *
413
- * 1. If the object is an ArrayBuffer or TypedArray, decode the array as UTF-8.
414
- * 2. If the object is otherwise a plain JavaScript object with the useless
415
- * default toString() method, return null instead of "[object Object]". In
416
- * practice, it's generally more useful to have this method fail than to
417
- * return a useless string.
418
- * 3. If the object is a defined primitive value, return the result of
419
- * String(object).
420
- *
421
- * Otherwise return null.
422
- *
423
- * @param {any} object
424
- * @returns {string|null}
425
- */
426
- export function toString(object) {
427
- if (object instanceof ArrayBuffer || object instanceof TypedArray) {
428
- // Treat the buffer as UTF-8 text.
429
- const decoded = textDecoder.decode(object);
430
- // If the result appears to contain non-printable characters, it's probably not a string.
431
- // https://stackoverflow.com/a/1677660/76472
432
- const hasNonPrintableCharacters = /[\x00-\x08\x0E-\x1F]/.test(decoded);
433
- return hasNonPrintableCharacters ? null : decoded;
434
- } else if (isStringLike(object) || (object !== null && isPrimitive(object))) {
435
- return String(object);
436
- } else {
437
- return null;
438
- }
439
- }