@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.
- package/index.ts +16 -6
- package/package.json +2 -2
- package/shared.js +20 -30
- package/src/Tree.js +62 -502
- package/src/constants.js +2 -0
- package/src/drivers/BrowserFileTree.js +9 -10
- package/src/drivers/DeepMapTree.js +3 -3
- package/src/drivers/DeepObjectTree.js +4 -5
- package/src/drivers/DeferredTree.js +2 -2
- package/src/drivers/FileTree.js +11 -33
- package/src/drivers/FunctionTree.js +1 -1
- package/src/drivers/MapTree.js +6 -6
- package/src/drivers/ObjectTree.js +4 -3
- package/src/drivers/SetTree.js +1 -1
- package/src/drivers/SiteTree.js +1 -1
- package/src/drivers/constantTree.js +1 -1
- package/src/extension.js +5 -3
- package/src/jsonKeys.js +5 -7
- package/src/operations/addNextPrevious.js +10 -9
- package/src/operations/assign.js +40 -0
- package/src/operations/cache.js +18 -12
- package/src/operations/cachedKeyFunctions.js +15 -4
- package/src/operations/clear.js +20 -0
- package/src/operations/concat.js +17 -0
- package/src/operations/deepMap.js +25 -0
- package/src/operations/deepMerge.js +11 -25
- package/src/operations/deepReverse.js +6 -7
- package/src/operations/deepTake.js +6 -7
- package/src/operations/deepText.js +4 -4
- package/src/operations/deepValuesIterator.js +8 -6
- package/src/operations/defineds.js +32 -0
- package/src/operations/delete.js +20 -0
- package/src/operations/entries.js +16 -0
- package/src/operations/extensionKeyFunctions.js +1 -1
- package/src/operations/filter.js +7 -8
- package/src/operations/first.js +18 -0
- package/src/operations/forEach.js +20 -0
- package/src/operations/from.js +77 -0
- package/src/operations/fromFn.js +26 -0
- package/src/operations/globKeys.js +8 -8
- package/src/operations/group.js +9 -7
- package/src/operations/has.js +16 -0
- package/src/operations/indent.js +4 -2
- package/src/operations/inners.js +29 -0
- package/src/operations/invokeFunctions.js +5 -4
- package/src/operations/isAsyncMutableTree.js +15 -0
- package/src/operations/isAsyncTree.js +21 -0
- package/src/operations/isTraversable.js +15 -0
- package/src/operations/isTreelike.js +33 -0
- package/src/operations/json.js +4 -3
- package/src/operations/keys.js +14 -0
- package/src/operations/length.js +15 -0
- package/src/operations/map.js +157 -95
- package/src/operations/mapExtension.js +27 -0
- package/src/operations/mapReduce.js +44 -0
- package/src/operations/mask.js +18 -16
- package/src/operations/match.js +74 -0
- package/src/operations/merge.js +22 -20
- package/src/operations/paginate.js +3 -5
- package/src/operations/parent.js +13 -0
- package/src/operations/paths.js +51 -0
- package/src/operations/plain.js +34 -0
- package/src/operations/regExpKeys.js +4 -5
- package/src/operations/remove.js +14 -0
- package/src/operations/reverse.js +4 -6
- package/src/operations/root.js +17 -0
- package/src/operations/scope.js +4 -6
- package/src/operations/setDeep.js +50 -0
- package/src/operations/shuffle.js +46 -0
- package/src/operations/sort.js +19 -12
- package/src/operations/take.js +3 -5
- package/src/operations/text.js +3 -3
- package/src/operations/toFunction.js +14 -0
- package/src/operations/traverse.js +25 -0
- package/src/operations/traverseOrThrow.js +64 -0
- package/src/operations/traversePath.js +16 -0
- package/src/operations/values.js +15 -0
- package/src/operations/withKeys.js +33 -0
- package/src/utilities/TypedArray.js +2 -0
- package/src/utilities/box.js +20 -0
- package/src/utilities/castArraylike.js +38 -0
- package/src/utilities/getParent.js +33 -0
- package/src/utilities/getRealmObjectPrototype.js +19 -0
- package/src/utilities/getTreeArgument.js +43 -0
- package/src/utilities/isPacked.js +20 -0
- package/src/utilities/isPlainObject.js +29 -0
- package/src/utilities/isPrimitive.js +13 -0
- package/src/utilities/isStringlike.js +25 -0
- package/src/utilities/isUnpackable.js +13 -0
- package/src/utilities/keysFromPath.js +34 -0
- package/src/utilities/naturalOrder.js +9 -0
- package/src/utilities/pathFromKeys.js +18 -0
- package/src/utilities/setParent.js +38 -0
- package/src/utilities/toFunction.js +41 -0
- package/src/utilities/toPlainValue.js +95 -0
- package/src/utilities/toString.js +37 -0
- package/test/drivers/ExplorableSiteTree.test.js +1 -1
- package/test/drivers/FileTree.test.js +1 -1
- package/test/drivers/calendarTree.test.js +1 -1
- package/test/jsonKeys.test.js +1 -1
- package/test/operations/assign.test.js +54 -0
- package/test/operations/cache.test.js +1 -1
- package/test/operations/cachedKeyFunctions.test.js +16 -16
- package/test/operations/clear.test.js +34 -0
- package/test/operations/deepMap.test.js +29 -0
- package/test/operations/deepMerge.test.js +2 -6
- package/test/operations/deepReverse.test.js +1 -1
- package/test/operations/defineds.test.js +25 -0
- package/test/operations/delete.test.js +20 -0
- package/test/operations/entries.test.js +18 -0
- package/test/operations/extensionKeyFunctions.test.js +10 -10
- package/test/operations/first.test.js +15 -0
- package/test/operations/fixtures/README.md +1 -0
- package/test/operations/forEach.test.js +22 -0
- package/test/operations/from.test.js +67 -0
- package/test/operations/globKeys.test.js +3 -3
- package/test/operations/has.test.js +15 -0
- package/test/operations/inners.test.js +30 -0
- package/test/operations/invokeFunctions.test.js +1 -1
- package/test/operations/isAsyncMutableTree.test.js +17 -0
- package/test/operations/isAsyncTree.test.js +26 -0
- package/test/operations/isTreelike.test.js +13 -0
- package/test/operations/keys.test.js +15 -0
- package/test/operations/length.test.js +15 -0
- package/test/operations/map.test.js +62 -45
- package/test/operations/mapExtension.test.js +0 -0
- package/test/operations/mapReduce.test.js +23 -0
- package/test/operations/mask.test.js +1 -1
- package/test/operations/match.test.js +33 -0
- package/test/operations/merge.test.js +23 -9
- package/test/operations/paginate.test.js +2 -1
- package/test/operations/parent.test.js +15 -0
- package/test/operations/paths.test.js +40 -0
- package/test/operations/plain.test.js +69 -0
- package/test/operations/reverse.test.js +1 -1
- package/test/operations/scope.test.js +1 -1
- package/test/operations/setDeep.test.js +53 -0
- package/test/operations/shuffle.test.js +18 -0
- package/test/operations/sort.test.js +3 -3
- package/test/operations/toFunction.test.js +16 -0
- package/test/operations/traverse.test.js +43 -0
- package/test/operations/traversePath.test.js +16 -0
- package/test/operations/values.test.js +18 -0
- package/test/operations/withKeys.test.js +21 -0
- package/test/utilities/box.test.js +26 -0
- package/test/utilities/getRealmObjectPrototype.test.js +11 -0
- package/test/utilities/isPlainObject.test.js +13 -0
- package/test/utilities/keysFromPath.test.js +14 -0
- package/test/utilities/naturalOrder.test.js +11 -0
- package/test/utilities/pathFromKeys.test.js +12 -0
- package/test/utilities/setParent.test.js +34 -0
- package/test/utilities/toFunction.test.js +34 -0
- package/test/utilities/toPlainValue.test.js +27 -0
- package/test/utilities/toString.test.js +22 -0
- package/src/Tree.d.ts +0 -24
- package/src/utilities.d.ts +0 -21
- package/src/utilities.js +0 -439
- package/test/Tree.test.js +0 -407
- 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
|
-
}
|