@welshman/lib 0.1.1 → 0.1.2
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/LICENSE +21 -0
- package/{build/src → dist}/Deferred.d.ts +1 -0
- package/dist/Deferred.d.ts.map +1 -0
- package/dist/Deferred.js.map +1 -0
- package/{build/src → dist}/Emitter.d.ts +1 -0
- package/dist/Emitter.d.ts.map +1 -0
- package/dist/Emitter.js.map +1 -0
- package/{build/src → dist}/LRUCache.d.ts +1 -0
- package/dist/LRUCache.d.ts.map +1 -0
- package/dist/LRUCache.js.map +1 -0
- package/{build/src → dist}/TaskQueue.d.ts +1 -0
- package/dist/TaskQueue.d.ts.map +1 -0
- package/dist/TaskQueue.js.map +1 -0
- package/{build/src → dist}/Tools.d.ts +343 -350
- package/dist/Tools.d.ts.map +1 -0
- package/{build/src → dist}/Tools.js +575 -522
- package/dist/Tools.js.map +1 -0
- package/{build/src → dist}/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js.map +1 -0
- package/dist/normalize-url/index.d.ts +286 -0
- package/dist/normalize-url/index.d.ts.map +1 -0
- package/{build/src → dist}/normalize-url/index.js +53 -51
- package/dist/normalize-url/index.js.map +1 -0
- package/package.json +14 -17
- package/README.md +0 -13
- package/build/src/Deferred.js.map +0 -1
- package/build/src/Emitter.js.map +0 -1
- package/build/src/LRUCache.js.map +0 -1
- package/build/src/TaskQueue.js.map +0 -1
- package/build/src/Tools.js.map +0 -1
- package/build/src/index.js.map +0 -1
- package/build/src/normalize-url/index.d.ts +0 -285
- package/build/src/normalize-url/index.js.map +0 -1
- package/build/tsconfig.tsbuildinfo +0 -1
- /package/{build/src → dist}/Deferred.js +0 -0
- /package/{build/src → dist}/Emitter.js +0 -0
- /package/{build/src → dist}/LRUCache.js +0 -0
- /package/{build/src → dist}/TaskQueue.js +0 -0
- /package/{build/src → dist}/index.js +0 -0
|
@@ -1,33 +1,9 @@
|
|
|
1
1
|
import { bech32, utf8 } from "@scure/base";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
* Executes a function if the value is defined
|
|
6
|
-
* @param x - The value to check
|
|
7
|
-
* @param f - Function to execute if x is defined
|
|
8
|
-
* @returns Result of f(x) if x is defined, undefined otherwise
|
|
9
|
-
*/
|
|
10
|
-
export const ifLet = (x, f) => x === undefined ? undefined : f(x);
|
|
2
|
+
// ----------------------------------------------------------------------------
|
|
3
|
+
// Basic functional programming utilities
|
|
4
|
+
// ----------------------------------------------------------------------------
|
|
11
5
|
/** Function that does nothing and returns undefined */
|
|
12
6
|
export const noop = (...args) => undefined;
|
|
13
|
-
/**
|
|
14
|
-
* Returns the first element of an array
|
|
15
|
-
* @param xs - The array
|
|
16
|
-
* @returns First element or undefined
|
|
17
|
-
*/
|
|
18
|
-
export const first = (xs, ...args) => xs[0];
|
|
19
|
-
/**
|
|
20
|
-
* Returns the first element of the first array in a nested array
|
|
21
|
-
* @param xs - Array of arrays
|
|
22
|
-
* @returns First element of first array or undefined
|
|
23
|
-
*/
|
|
24
|
-
export const ffirst = (xs, ...args) => xs[0][0];
|
|
25
|
-
/**
|
|
26
|
-
* Returns the last element of an array
|
|
27
|
-
* @param xs - The array
|
|
28
|
-
* @returns Last element or undefined
|
|
29
|
-
*/
|
|
30
|
-
export const last = (xs, ...args) => xs[xs.length - 1];
|
|
31
7
|
/**
|
|
32
8
|
* Returns the input value unchanged
|
|
33
9
|
* @param x - Any value
|
|
@@ -46,9 +22,60 @@ export const always = (x, ...args) => () => x;
|
|
|
46
22
|
* @returns !x
|
|
47
23
|
*/
|
|
48
24
|
export const not = (x, ...args) => !x;
|
|
49
|
-
/**
|
|
50
|
-
|
|
51
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Deep equality comparison
|
|
27
|
+
* @param a - First value
|
|
28
|
+
* @param b - Second value
|
|
29
|
+
* @returns True if values are deeply equal
|
|
30
|
+
*/
|
|
31
|
+
export const equals = (a, b) => {
|
|
32
|
+
if (a === b)
|
|
33
|
+
return true;
|
|
34
|
+
if (a instanceof Set && b instanceof Set) {
|
|
35
|
+
a = Array.from(a);
|
|
36
|
+
b = Array.from(b);
|
|
37
|
+
}
|
|
38
|
+
if (a instanceof Set) {
|
|
39
|
+
if (!(b instanceof Set) || a.size !== b.size) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
return Array.from(a).every(x => b.has(x));
|
|
43
|
+
}
|
|
44
|
+
if (Array.isArray(a)) {
|
|
45
|
+
if (!Array.isArray(b) || a.length !== b.length) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
for (let i = 0; i < a.length; i++) {
|
|
49
|
+
if (!equals(a[i], b[i])) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
if (isPojo(a)) {
|
|
56
|
+
if (!isPojo(b)) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
const aKeys = Object.keys(a);
|
|
60
|
+
const bKeys = Object.keys(b);
|
|
61
|
+
if (aKeys.length !== bKeys.length) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
for (const k of aKeys) {
|
|
65
|
+
if (!equals(a[k], b[k])) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
return false;
|
|
72
|
+
};
|
|
73
|
+
// ----------------------------------------------------------------------------
|
|
74
|
+
// Numbers
|
|
75
|
+
// ----------------------------------------------------------------------------
|
|
76
|
+
/** Converts string or number to number */
|
|
77
|
+
export const ensureNumber = (x) => parseFloat(x);
|
|
78
|
+
/** Converts a `number | undefined` to a number, defaulting to 0 */
|
|
52
79
|
export const num = (x) => x || 0;
|
|
53
80
|
/** Adds two numbers, handling undefined values */
|
|
54
81
|
export const add = (x, y) => num(x) + num(y);
|
|
@@ -74,7 +101,7 @@ export const gte = (x, y) => num(x) >= num(y);
|
|
|
74
101
|
export const max = (xs) => xs.reduce((a, b) => Math.max(num(a), num(b)), 0);
|
|
75
102
|
/** Returns minimum value in array, handling undefined values */
|
|
76
103
|
export const min = (xs) => {
|
|
77
|
-
const [head, ...tail] = xs.filter(x =>
|
|
104
|
+
const [head, ...tail] = xs.filter(x => x !== undefined);
|
|
78
105
|
if (tail.length === 0)
|
|
79
106
|
return head || 0;
|
|
80
107
|
return tail.reduce((a, b) => Math.min(a, b), head);
|
|
@@ -83,146 +110,6 @@ export const min = (xs) => {
|
|
|
83
110
|
export const sum = (xs) => xs.reduce((a, b) => add(a, b), 0);
|
|
84
111
|
/** Returns average of array values, handling undefined values */
|
|
85
112
|
export const avg = (xs) => sum(xs) / xs.length;
|
|
86
|
-
/**
|
|
87
|
-
* Returns array with first n elements removed
|
|
88
|
-
* @param n - Number of elements to drop
|
|
89
|
-
* @param xs - Input array
|
|
90
|
-
* @returns Array with first n elements removed
|
|
91
|
-
*/
|
|
92
|
-
export const drop = (n, xs) => xs.slice(n);
|
|
93
|
-
/**
|
|
94
|
-
* Returns first n elements of array
|
|
95
|
-
* @param n - Number of elements to take
|
|
96
|
-
* @param xs - Input array
|
|
97
|
-
* @returns Array of first n elements
|
|
98
|
-
*/
|
|
99
|
-
export const take = (n, xs) => xs.slice(0, n);
|
|
100
|
-
/**
|
|
101
|
-
* Creates new object with specified keys removed
|
|
102
|
-
* @param ks - Keys to remove
|
|
103
|
-
* @param x - Source object
|
|
104
|
-
* @returns New object without specified keys
|
|
105
|
-
*/
|
|
106
|
-
export const omit = (ks, x) => {
|
|
107
|
-
const r = { ...x };
|
|
108
|
-
for (const k of ks) {
|
|
109
|
-
delete r[k];
|
|
110
|
-
}
|
|
111
|
-
return r;
|
|
112
|
-
};
|
|
113
|
-
/**
|
|
114
|
-
* Creates new object excluding entries with specified values
|
|
115
|
-
* @param xs - Values to exclude
|
|
116
|
-
* @param x - Source object
|
|
117
|
-
* @returns New object without entries containing specified values
|
|
118
|
-
*/
|
|
119
|
-
export const omitVals = (xs, x) => {
|
|
120
|
-
const r = {};
|
|
121
|
-
for (const [k, v] of Object.entries(x)) {
|
|
122
|
-
if (!xs.includes(v)) {
|
|
123
|
-
r[k] = v;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
return r;
|
|
127
|
-
};
|
|
128
|
-
/**
|
|
129
|
-
* Creates new object with only specified keys
|
|
130
|
-
* @param ks - Keys to keep
|
|
131
|
-
* @param x - Source object
|
|
132
|
-
* @returns New object with only specified keys
|
|
133
|
-
*/
|
|
134
|
-
export const pick = (ks, x) => {
|
|
135
|
-
const r = { ...x };
|
|
136
|
-
for (const k of Object.keys(x)) {
|
|
137
|
-
if (!ks.includes(k)) {
|
|
138
|
-
delete r[k];
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
return r;
|
|
142
|
-
};
|
|
143
|
-
/**
|
|
144
|
-
* Generates sequence of numbers from a to b
|
|
145
|
-
* @param a - Start number (inclusive)
|
|
146
|
-
* @param b - End number (exclusive)
|
|
147
|
-
* @param step - Increment between numbers
|
|
148
|
-
* @yields Numbers in sequence
|
|
149
|
-
*/
|
|
150
|
-
export function* range(a, b, step = 1) {
|
|
151
|
-
for (let i = a; i < b; i += step) {
|
|
152
|
-
yield i;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
/**
|
|
156
|
-
* Yields indexed items
|
|
157
|
-
* @param items - A collection of items
|
|
158
|
-
* @yields tuples of [index, item]
|
|
159
|
-
*/
|
|
160
|
-
export function* enumerate(items) {
|
|
161
|
-
for (let i = 0; i < items.length; i += 1) {
|
|
162
|
-
yield [i, items[i]];
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
/**
|
|
166
|
-
* Creates new object with transformed keys
|
|
167
|
-
* @param f - Function to transform keys
|
|
168
|
-
* @param x - Source object
|
|
169
|
-
* @returns Object with transformed keys
|
|
170
|
-
*/
|
|
171
|
-
export const mapKeys = (f, x) => {
|
|
172
|
-
const r = {};
|
|
173
|
-
for (const [k, v] of Object.entries(x)) {
|
|
174
|
-
r[f(k)] = v;
|
|
175
|
-
}
|
|
176
|
-
return r;
|
|
177
|
-
};
|
|
178
|
-
/**
|
|
179
|
-
* Creates new object with transformed values
|
|
180
|
-
* @param f - Function to transform values
|
|
181
|
-
* @param x - Source object
|
|
182
|
-
* @returns Object with transformed values
|
|
183
|
-
*/
|
|
184
|
-
export const mapVals = (f, x) => {
|
|
185
|
-
const r = {};
|
|
186
|
-
for (const [k, v] of Object.entries(x)) {
|
|
187
|
-
r[k] = f(v);
|
|
188
|
-
}
|
|
189
|
-
return r;
|
|
190
|
-
};
|
|
191
|
-
/**
|
|
192
|
-
* Merges two objects, with left object taking precedence
|
|
193
|
-
* @param a - Left object
|
|
194
|
-
* @param b - Right object
|
|
195
|
-
* @returns Merged object with a"s properties overriding b"s
|
|
196
|
-
*/
|
|
197
|
-
export const mergeLeft = (a, b) => ({
|
|
198
|
-
...b,
|
|
199
|
-
...a,
|
|
200
|
-
});
|
|
201
|
-
/**
|
|
202
|
-
* Merges two objects, with right object taking precedence
|
|
203
|
-
* @param a - Left object
|
|
204
|
-
* @param b - Right object
|
|
205
|
-
* @returns Merged object with b"s properties overriding a"s
|
|
206
|
-
*/
|
|
207
|
-
export const mergeRight = (a, b) => ({
|
|
208
|
-
...a,
|
|
209
|
-
...b,
|
|
210
|
-
});
|
|
211
|
-
/** Deep merge two objects, prioritizing the first argument. */
|
|
212
|
-
export const deepMergeLeft = (a, b) => deepMergeRight(b, a);
|
|
213
|
-
/** Deep merge two objects, prioritizing the second argument. */
|
|
214
|
-
export const deepMergeRight = (a, b) => {
|
|
215
|
-
a = { ...a };
|
|
216
|
-
for (const [k, v] of Object.entries(b)) {
|
|
217
|
-
if (isPojo(v) && isPojo(a[k])) {
|
|
218
|
-
a[k] = deepMergeRight(a[k], v);
|
|
219
|
-
}
|
|
220
|
-
else {
|
|
221
|
-
a[k] = v;
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
return a;
|
|
225
|
-
};
|
|
226
113
|
/**
|
|
227
114
|
* Checks if a number is between two values (exclusive)
|
|
228
115
|
* @param bounds - Lower and upper bounds
|
|
@@ -238,63 +125,112 @@ export const between = ([low, high], n) => n > low && n < high;
|
|
|
238
125
|
*/
|
|
239
126
|
export const within = ([low, high], n) => n >= low && n <= high;
|
|
240
127
|
/**
|
|
241
|
-
*
|
|
242
|
-
* @param
|
|
243
|
-
* @param
|
|
244
|
-
* @returns
|
|
128
|
+
* Constrains number between min and max values
|
|
129
|
+
* @param bounds - Minimum and maximum allowed values
|
|
130
|
+
* @param n - Number to clamp
|
|
131
|
+
* @returns Clamped value
|
|
245
132
|
*/
|
|
246
|
-
export const
|
|
133
|
+
export const clamp = ([min, max], n) => Math.min(max, Math.max(min, n));
|
|
247
134
|
/**
|
|
248
|
-
*
|
|
249
|
-
* @
|
|
135
|
+
* Round a number to the nearest float precision
|
|
136
|
+
* @param precision - Number of decimal places
|
|
137
|
+
* @param x - Number to round
|
|
138
|
+
* @returns Formatted number
|
|
250
139
|
*/
|
|
251
|
-
export const
|
|
140
|
+
export const round = (precision, x) => Math.round(x * Math.pow(10, precision)) / Math.pow(10, precision);
|
|
141
|
+
// ----------------------------------------------------------------------------
|
|
142
|
+
// Timestamps
|
|
143
|
+
// ----------------------------------------------------------------------------
|
|
144
|
+
/** One minute in seconds */
|
|
145
|
+
export const MINUTE = 60;
|
|
146
|
+
/** One hour in seconds */
|
|
147
|
+
export const HOUR = 60 * MINUTE;
|
|
148
|
+
/** One day in seconds */
|
|
149
|
+
export const DAY = 24 * HOUR;
|
|
150
|
+
/** One week in seconds */
|
|
151
|
+
export const WEEK = 7 * DAY;
|
|
152
|
+
/** One month in seconds (approximate) */
|
|
153
|
+
export const MONTH = 30 * DAY;
|
|
154
|
+
/** One quarter in seconds (approximate) */
|
|
155
|
+
export const QUARTER = 90 * DAY;
|
|
156
|
+
/** One year in seconds (approximate) */
|
|
157
|
+
export const YEAR = 365 * DAY;
|
|
252
158
|
/**
|
|
253
|
-
*
|
|
254
|
-
* @param
|
|
255
|
-
* @
|
|
159
|
+
* Multiplies time unit by count
|
|
160
|
+
* @param unit - Time unit in seconds
|
|
161
|
+
* @param count - Number of units
|
|
162
|
+
* @returns Total seconds
|
|
256
163
|
*/
|
|
257
|
-
export const
|
|
164
|
+
export const int = (unit, count = 1) => unit * count;
|
|
165
|
+
/** Returns current Unix timestamp in seconds */
|
|
166
|
+
export const now = () => Math.round(Date.now() / 1000);
|
|
258
167
|
/**
|
|
259
|
-
*
|
|
260
|
-
* @param
|
|
261
|
-
* @
|
|
168
|
+
* Returns Unix timestamp from specified time ago
|
|
169
|
+
* @param unit - Time unit in seconds
|
|
170
|
+
* @param count - Number of units
|
|
171
|
+
* @returns Timestamp in seconds
|
|
262
172
|
*/
|
|
263
|
-
export const
|
|
264
|
-
.replace(/^(www\.)?/i, "")
|
|
265
|
-
.replace(/\/$/, "");
|
|
173
|
+
export const ago = (unit, count = 1) => now() - int(unit, count);
|
|
266
174
|
/**
|
|
267
|
-
*
|
|
268
|
-
* @param
|
|
269
|
-
* @returns
|
|
175
|
+
* Converts seconds to milliseconds
|
|
176
|
+
* @param seconds - Time in seconds
|
|
177
|
+
* @returns Time in milliseconds
|
|
270
178
|
*/
|
|
271
|
-
export const
|
|
179
|
+
export const ms = (seconds) => seconds * 1000;
|
|
180
|
+
// ----------------------------------------------------------------------------
|
|
181
|
+
// Sequences
|
|
182
|
+
// ----------------------------------------------------------------------------
|
|
272
183
|
/**
|
|
273
|
-
*
|
|
274
|
-
* @param
|
|
275
|
-
* @returns
|
|
184
|
+
* Returns the first element of an array
|
|
185
|
+
* @param xs - The array
|
|
186
|
+
* @returns First element or undefined
|
|
276
187
|
*/
|
|
277
|
-
export const
|
|
188
|
+
export const first = (xs, ...args) => {
|
|
189
|
+
for (const x of xs) {
|
|
190
|
+
return x;
|
|
191
|
+
}
|
|
192
|
+
};
|
|
278
193
|
/**
|
|
279
|
-
*
|
|
280
|
-
* @
|
|
194
|
+
* Returns the first element of the first array in a nested array
|
|
195
|
+
* @param xs - Array of arrays
|
|
196
|
+
* @returns First element of first array or undefined
|
|
281
197
|
*/
|
|
282
|
-
export const
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
198
|
+
export const ffirst = (xs, ...args) => {
|
|
199
|
+
for (const chunk of xs) {
|
|
200
|
+
for (const x of chunk) {
|
|
201
|
+
return x;
|
|
202
|
+
}
|
|
287
203
|
}
|
|
288
|
-
return new Promise(resolve => {
|
|
289
|
-
setTimeout(resolve, 0);
|
|
290
|
-
});
|
|
291
204
|
};
|
|
205
|
+
/**
|
|
206
|
+
* Returns the last element of an array
|
|
207
|
+
* @param xs - The array
|
|
208
|
+
* @returns Last element or undefined
|
|
209
|
+
*/
|
|
210
|
+
export const last = (xs, ...args) => {
|
|
211
|
+
const a = Array.from(xs);
|
|
212
|
+
return a[a.length - 1];
|
|
213
|
+
};
|
|
214
|
+
/**
|
|
215
|
+
* Returns array with first n elements removed
|
|
216
|
+
* @param n - Number of elements to drop
|
|
217
|
+
* @param xs - Input array
|
|
218
|
+
* @returns Array with first n elements removed
|
|
219
|
+
*/
|
|
220
|
+
export const drop = (n, xs) => Array.from(xs).slice(n);
|
|
221
|
+
/**
|
|
222
|
+
* Returns first n elements of array
|
|
223
|
+
* @param n - Number of elements to take
|
|
224
|
+
* @param xs - Input array
|
|
225
|
+
* @returns Array of first n elements
|
|
226
|
+
*/
|
|
227
|
+
export const take = (n, xs) => Array.from(xs).slice(0, n);
|
|
292
228
|
/**
|
|
293
229
|
* Concatenates multiple arrays, filtering out null/undefined
|
|
294
230
|
* @param xs - Arrays to concatenate
|
|
295
231
|
* @returns Combined array
|
|
296
232
|
*/
|
|
297
|
-
export const concat = (...xs) => xs.flatMap(x => (
|
|
233
|
+
export const concat = (...xs) => xs.flatMap(x => (x === undefined ? [] : x));
|
|
298
234
|
/**
|
|
299
235
|
* Appends element to array
|
|
300
236
|
* @param x - Element to append
|
|
@@ -344,202 +280,34 @@ export const remove = (a, xs) => xs.filter(x => x !== a);
|
|
|
344
280
|
*/
|
|
345
281
|
export const without = (a, b) => b.filter(x => !a.includes(x));
|
|
346
282
|
/**
|
|
347
|
-
* Toggles presence of element in array
|
|
348
|
-
* @param x - Element to toggle
|
|
349
|
-
* @param xs - Source array
|
|
350
|
-
* @returns New array with element added or removed
|
|
351
|
-
*/
|
|
352
|
-
export const toggle = (x, xs) => (xs.includes(x) ? remove(x, xs) : append(x, xs));
|
|
353
|
-
/**
|
|
354
|
-
*
|
|
355
|
-
* @param
|
|
356
|
-
* @param
|
|
357
|
-
* @
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
*
|
|
369
|
-
|
|
370
|
-
*
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
try {
|
|
376
|
-
return JSON.parse(json);
|
|
377
|
-
}
|
|
378
|
-
catch (e) {
|
|
379
|
-
return undefined;
|
|
380
|
-
}
|
|
381
|
-
};
|
|
382
|
-
/**
|
|
383
|
-
* Gets and parses JSON from localStorage
|
|
384
|
-
* @param k - Storage key
|
|
385
|
-
* @returns Parsed value or undefined if invalid/missing
|
|
386
|
-
*/
|
|
387
|
-
export const getJson = (k) => parseJson(localStorage.getItem(k) || "");
|
|
388
|
-
/**
|
|
389
|
-
* Stringifies and stores value in localStorage
|
|
390
|
-
* @param k - Storage key
|
|
391
|
-
* @param v - Value to store
|
|
392
|
-
*/
|
|
393
|
-
export const setJson = (k, v) => localStorage.setItem(k, JSON.stringify(v));
|
|
394
|
-
/**
|
|
395
|
-
* Safely executes function and handles errors
|
|
396
|
-
* @param f - Function to execute
|
|
397
|
-
* @param onError - Optional error handler
|
|
398
|
-
* @returns Function result or undefined if error
|
|
399
|
-
*/
|
|
400
|
-
export const tryCatch = (f, onError) => {
|
|
401
|
-
try {
|
|
402
|
-
const r = f();
|
|
403
|
-
if (r instanceof Promise) {
|
|
404
|
-
r.catch(e => onError?.(e));
|
|
405
|
-
}
|
|
406
|
-
return r;
|
|
407
|
-
}
|
|
408
|
-
catch (e) {
|
|
409
|
-
onError?.(e);
|
|
410
|
-
}
|
|
411
|
-
return undefined;
|
|
412
|
-
};
|
|
413
|
-
/**
|
|
414
|
-
* Truncates string to length, breaking at word boundaries
|
|
415
|
-
* @param s - String to truncate
|
|
416
|
-
* @param l - Maximum length
|
|
417
|
-
* @param suffix - String to append if truncated
|
|
418
|
-
* @returns Truncated string
|
|
419
|
-
*/
|
|
420
|
-
export const ellipsize = (s, l, suffix = "...") => {
|
|
421
|
-
if (s.length < l * 1.1) {
|
|
422
|
-
return s;
|
|
423
|
-
}
|
|
424
|
-
while (s.length > l && s.includes(" ")) {
|
|
425
|
-
s = s.split(" ").slice(0, -1).join(" ");
|
|
426
|
-
}
|
|
427
|
-
return s + suffix;
|
|
428
|
-
};
|
|
429
|
-
/**
|
|
430
|
-
* Checks if value is a plain object
|
|
431
|
-
* @param obj - Value to check
|
|
432
|
-
* @returns True if value is a plain object
|
|
433
|
-
*/
|
|
434
|
-
export const isPojo = (obj) => {
|
|
435
|
-
if (obj === null || typeof obj !== "object") {
|
|
436
|
-
return false;
|
|
437
|
-
}
|
|
438
|
-
return Object.getPrototypeOf(obj) === Object.prototype;
|
|
439
|
-
};
|
|
440
|
-
/**
|
|
441
|
-
* Deep equality comparison
|
|
442
|
-
* @param a - First value
|
|
443
|
-
* @param b - Second value
|
|
444
|
-
* @returns True if values are deeply equal
|
|
445
|
-
*/
|
|
446
|
-
export const equals = (a, b) => {
|
|
447
|
-
if (a === b)
|
|
448
|
-
return true;
|
|
449
|
-
if (a instanceof Set && b instanceof Set) {
|
|
450
|
-
a = Array.from(a);
|
|
451
|
-
b = Array.from(b);
|
|
452
|
-
}
|
|
453
|
-
if (a instanceof Set) {
|
|
454
|
-
if (!(b instanceof Set) || a.size !== b.size) {
|
|
455
|
-
return false;
|
|
456
|
-
}
|
|
457
|
-
return Array.from(a).every(x => b.has(x));
|
|
458
|
-
}
|
|
459
|
-
if (Array.isArray(a)) {
|
|
460
|
-
if (!Array.isArray(b) || a.length !== b.length) {
|
|
461
|
-
return false;
|
|
462
|
-
}
|
|
463
|
-
for (let i = 0; i < a.length; i++) {
|
|
464
|
-
if (!equals(a[i], b[i])) {
|
|
465
|
-
return false;
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
return true;
|
|
469
|
-
}
|
|
470
|
-
if (isPojo(a)) {
|
|
471
|
-
if (!isPojo(b)) {
|
|
472
|
-
return false;
|
|
473
|
-
}
|
|
474
|
-
const aKeys = Object.keys(a);
|
|
475
|
-
const bKeys = Object.keys(b);
|
|
476
|
-
if (aKeys.length !== bKeys.length) {
|
|
477
|
-
return false;
|
|
478
|
-
}
|
|
479
|
-
for (const k of aKeys) {
|
|
480
|
-
if (!equals(a[k], b[k])) {
|
|
481
|
-
return false;
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
return true;
|
|
485
|
-
}
|
|
486
|
-
return false;
|
|
487
|
-
};
|
|
488
|
-
// Curried utils
|
|
489
|
-
/** Returns a function that gets the nth element of an array */
|
|
490
|
-
export const nth = (i) => (xs, ...args) => xs[i];
|
|
491
|
-
/** Returns a function that checks if nth element equals value */
|
|
492
|
-
export const nthEq = (i, v) => (xs, ...args) => xs[i] === v;
|
|
493
|
-
/** Returns a function that checks if nth element does not equal value */
|
|
494
|
-
export const nthNe = (i, v) => (xs, ...args) => xs[i] !== v;
|
|
495
|
-
/** Returns a function that checks if key/value pairs of x match all pairs in spec */
|
|
496
|
-
export const spec = (values) => (x, ...args) => {
|
|
497
|
-
if (Array.isArray(values)) {
|
|
498
|
-
for (let i = 0; i < values.length; i++) {
|
|
499
|
-
if (x[i] !== values[i]) {
|
|
500
|
-
return false;
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
else {
|
|
505
|
-
for (const [k, v] of Object.entries(values)) {
|
|
506
|
-
if (x[k] !== v)
|
|
507
|
-
return false;
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
return true;
|
|
511
|
-
};
|
|
512
|
-
/** Returns a function that checks equality with value */
|
|
513
|
-
export const eq = (v) => (x) => x === v;
|
|
514
|
-
/** Returns a function that checks inequality with value */
|
|
515
|
-
export const ne = (v) => (x) => x !== v;
|
|
516
|
-
/** Returns a function that gets property value from object */
|
|
517
|
-
export const prop = (k) => (x) => x[k];
|
|
518
|
-
/** Returns a function that adds/updates a property on object */
|
|
519
|
-
export const assoc = (k, v) => (o) => ({ ...o, [k]: v });
|
|
520
|
-
/** Returns a function that removes a property on object */
|
|
521
|
-
export const dissoc = (k) => (o) => omit([k], o);
|
|
522
|
-
/** Generates a hash string from input string */
|
|
523
|
-
export const hash = (s) => Math.abs(s.split("").reduce((a, b) => ((a << 5) - a + b.charCodeAt(0)) | 0, 0)).toString();
|
|
524
|
-
// Collections
|
|
525
|
-
/** Splits array into two parts at index */
|
|
526
|
-
export const splitAt = (n, xs) => [xs.slice(0, n), xs.slice(n)];
|
|
527
|
-
/** Inserts element into array at index */
|
|
528
|
-
export const insert = (n, x, xs) => [...xs.slice(0, n), x, ...xs.slice(n)];
|
|
529
|
-
/** Returns random element from array */
|
|
530
|
-
export const choice = (xs) => xs[Math.floor(xs.length * Math.random())];
|
|
531
|
-
/** Returns shuffled copy of iterable */
|
|
532
|
-
export const shuffle = (xs) => Array.from(xs).sort(() => (Math.random() > 0.5 ? 1 : -1));
|
|
533
|
-
/** Returns n random elements from array */
|
|
534
|
-
export const sample = (n, xs) => shuffle(xs).slice(0, n);
|
|
535
|
-
/** Checks if value is iterable */
|
|
536
|
-
export const isIterable = (x) => Symbol.iterator in Object(x);
|
|
537
|
-
/** Ensures value is iterable by wrapping in array if needed */
|
|
538
|
-
export const toIterable = (x) => (isIterable(x) ? x : [x]);
|
|
539
|
-
/** Ensures value is array by wrapping if needed */
|
|
540
|
-
export const ensurePlural = (x) => (x instanceof Array ? x : [x]);
|
|
541
|
-
/** Converts string or number to number */
|
|
542
|
-
export const ensureNumber = (x) => parseFloat(x);
|
|
283
|
+
* Toggles presence of element in array
|
|
284
|
+
* @param x - Element to toggle
|
|
285
|
+
* @param xs - Source array
|
|
286
|
+
* @returns New array with element added or removed
|
|
287
|
+
*/
|
|
288
|
+
export const toggle = (x, xs) => (xs.includes(x) ? remove(x, xs) : append(x, xs));
|
|
289
|
+
/**
|
|
290
|
+
* Generates sequence of numbers from a to b
|
|
291
|
+
* @param a - Start number (inclusive)
|
|
292
|
+
* @param b - End number (exclusive)
|
|
293
|
+
* @param step - Increment between numbers
|
|
294
|
+
* @yields Numbers in sequence
|
|
295
|
+
*/
|
|
296
|
+
export function* range(a, b, step = 1) {
|
|
297
|
+
for (let i = a; i < b; i += step) {
|
|
298
|
+
yield i;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Yields indexed items
|
|
303
|
+
* @param items - A collection of items
|
|
304
|
+
* @yields tuples of [index, item]
|
|
305
|
+
*/
|
|
306
|
+
export function* enumerate(items) {
|
|
307
|
+
for (let i = 0; i < items.length; i += 1) {
|
|
308
|
+
yield [i, items[i]];
|
|
309
|
+
}
|
|
310
|
+
}
|
|
543
311
|
/** Returns a function that gets property value from object */
|
|
544
312
|
export const pluck = (k, xs) => xs.map(x => x[k]);
|
|
545
313
|
/**
|
|
@@ -556,21 +324,6 @@ export const fromPairs = (pairs) => {
|
|
|
556
324
|
}
|
|
557
325
|
return r;
|
|
558
326
|
};
|
|
559
|
-
/**
|
|
560
|
-
* Filters object values based on predicate
|
|
561
|
-
* @param f - Function to test values
|
|
562
|
-
* @param x - Object to filter
|
|
563
|
-
* @returns Object with only values that pass predicate
|
|
564
|
-
*/
|
|
565
|
-
export const filterVals = (f, x) => {
|
|
566
|
-
const r = {};
|
|
567
|
-
for (const k in x) {
|
|
568
|
-
if (f(x[k])) {
|
|
569
|
-
r[k] = x[k];
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
return r;
|
|
573
|
-
};
|
|
574
327
|
/**
|
|
575
328
|
* Flattens array of arrays into single array
|
|
576
329
|
* @param xs - Array of arrays to flatten
|
|
@@ -719,6 +472,186 @@ export const chunks = (n, xs) => {
|
|
|
719
472
|
}
|
|
720
473
|
return result;
|
|
721
474
|
};
|
|
475
|
+
/** Splits array into two parts at index */
|
|
476
|
+
export const splitAt = (n, xs) => [xs.slice(0, n), xs.slice(n)];
|
|
477
|
+
/** Inserts element into array at index */
|
|
478
|
+
export const insert = (n, x, xs) => [...xs.slice(0, n), x, ...xs.slice(n)];
|
|
479
|
+
/** Returns random element from array */
|
|
480
|
+
export const choice = (xs) => xs[Math.floor(xs.length * Math.random())];
|
|
481
|
+
/** Returns shuffled copy of iterable */
|
|
482
|
+
export const shuffle = (xs) => Array.from(xs).sort(() => (Math.random() > 0.5 ? 1 : -1));
|
|
483
|
+
/** Returns n random elements from array */
|
|
484
|
+
export const sample = (n, xs) => shuffle(xs).slice(0, n);
|
|
485
|
+
/** Checks if value is iterable */
|
|
486
|
+
export const isIterable = (x) => Symbol.iterator in Object(x);
|
|
487
|
+
/** Ensures value is iterable by wrapping in array if needed */
|
|
488
|
+
export const toIterable = (x) => (isIterable(x) ? x : [x]);
|
|
489
|
+
/** Ensures value is array by wrapping if needed */
|
|
490
|
+
export const ensurePlural = (x) => (x instanceof Array ? x : [x]);
|
|
491
|
+
// ----------------------------------------------------------------------------
|
|
492
|
+
// Objects
|
|
493
|
+
// ----------------------------------------------------------------------------
|
|
494
|
+
/**
|
|
495
|
+
* Checks if value is a plain object
|
|
496
|
+
* @param obj - Value to check
|
|
497
|
+
* @returns True if value is a plain object
|
|
498
|
+
*/
|
|
499
|
+
export const isPojo = (obj) => {
|
|
500
|
+
if (obj === null || typeof obj !== "object") {
|
|
501
|
+
return false;
|
|
502
|
+
}
|
|
503
|
+
return Object.getPrototypeOf(obj) === Object.prototype;
|
|
504
|
+
};
|
|
505
|
+
/**
|
|
506
|
+
* Creates new object with only specified keys
|
|
507
|
+
* @param ks - Keys to keep
|
|
508
|
+
* @param x - Source object
|
|
509
|
+
* @returns New object with only specified keys
|
|
510
|
+
*/
|
|
511
|
+
export const pick = (ks, x) => {
|
|
512
|
+
const r = { ...x };
|
|
513
|
+
for (const k of Object.keys(x)) {
|
|
514
|
+
if (!ks.includes(k)) {
|
|
515
|
+
delete r[k];
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
return r;
|
|
519
|
+
};
|
|
520
|
+
/**
|
|
521
|
+
* Creates new object with specified keys removed
|
|
522
|
+
* @param ks - Keys to remove
|
|
523
|
+
* @param x - Source object
|
|
524
|
+
* @returns New object without specified keys
|
|
525
|
+
*/
|
|
526
|
+
export const omit = (ks, x) => {
|
|
527
|
+
const r = { ...x };
|
|
528
|
+
for (const k of ks) {
|
|
529
|
+
delete r[k];
|
|
530
|
+
}
|
|
531
|
+
return r;
|
|
532
|
+
};
|
|
533
|
+
/**
|
|
534
|
+
* Creates new object excluding entries with specified values
|
|
535
|
+
* @param xs - Values to exclude
|
|
536
|
+
* @param x - Source object
|
|
537
|
+
* @returns New object without entries containing specified values
|
|
538
|
+
*/
|
|
539
|
+
export const omitVals = (xs, x) => {
|
|
540
|
+
const r = {};
|
|
541
|
+
for (const [k, v] of Object.entries(x)) {
|
|
542
|
+
if (!xs.includes(v)) {
|
|
543
|
+
r[k] = v;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
return r;
|
|
547
|
+
};
|
|
548
|
+
/**
|
|
549
|
+
* Filters object values based on predicate
|
|
550
|
+
* @param f - Function to test values
|
|
551
|
+
* @param x - Object to filter
|
|
552
|
+
* @returns Object with only values that pass predicate
|
|
553
|
+
*/
|
|
554
|
+
export const filterVals = (f, x) => {
|
|
555
|
+
const r = {};
|
|
556
|
+
for (const k in x) {
|
|
557
|
+
if (f(x[k])) {
|
|
558
|
+
r[k] = x[k];
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
return r;
|
|
562
|
+
};
|
|
563
|
+
/**
|
|
564
|
+
* Creates new object with transformed keys
|
|
565
|
+
* @param f - Function to transform keys
|
|
566
|
+
* @param x - Source object
|
|
567
|
+
* @returns Object with transformed keys
|
|
568
|
+
*/
|
|
569
|
+
export const mapKeys = (f, x) => {
|
|
570
|
+
const r = {};
|
|
571
|
+
for (const [k, v] of Object.entries(x)) {
|
|
572
|
+
r[f(k)] = v;
|
|
573
|
+
}
|
|
574
|
+
return r;
|
|
575
|
+
};
|
|
576
|
+
/**
|
|
577
|
+
* Creates new object with transformed values
|
|
578
|
+
* @param f - Function to transform values
|
|
579
|
+
* @param x - Source object
|
|
580
|
+
* @returns Object with transformed values
|
|
581
|
+
*/
|
|
582
|
+
export const mapVals = (f, x) => {
|
|
583
|
+
const r = {};
|
|
584
|
+
for (const [k, v] of Object.entries(x)) {
|
|
585
|
+
r[k] = f(v);
|
|
586
|
+
}
|
|
587
|
+
return r;
|
|
588
|
+
};
|
|
589
|
+
/**
|
|
590
|
+
* Merges two objects, with left object taking precedence
|
|
591
|
+
* @param a - Left object
|
|
592
|
+
* @param b - Right object
|
|
593
|
+
* @returns Merged object with a"s properties overriding b"s
|
|
594
|
+
*/
|
|
595
|
+
export const mergeLeft = (a, b) => ({
|
|
596
|
+
...b,
|
|
597
|
+
...a,
|
|
598
|
+
});
|
|
599
|
+
/**
|
|
600
|
+
* Merges two objects, with right object taking precedence
|
|
601
|
+
* @param a - Left object
|
|
602
|
+
* @param b - Right object
|
|
603
|
+
* @returns Merged object with b"s properties overriding a"s
|
|
604
|
+
*/
|
|
605
|
+
export const mergeRight = (a, b) => ({
|
|
606
|
+
...a,
|
|
607
|
+
...b,
|
|
608
|
+
});
|
|
609
|
+
/** Deep merge two objects, prioritizing the first argument. */
|
|
610
|
+
export const deepMergeLeft = (a, b) => deepMergeRight(b, a);
|
|
611
|
+
/** Deep merge two objects, prioritizing the second argument. */
|
|
612
|
+
export const deepMergeRight = (a, b) => {
|
|
613
|
+
a = { ...a };
|
|
614
|
+
for (const [k, v] of Object.entries(b)) {
|
|
615
|
+
if (isPojo(v) && isPojo(a[k])) {
|
|
616
|
+
a[k] = deepMergeRight(a[k], v);
|
|
617
|
+
}
|
|
618
|
+
else {
|
|
619
|
+
a[k] = v;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
return a;
|
|
623
|
+
};
|
|
624
|
+
/**
|
|
625
|
+
* Switches on key in object, with default fallback
|
|
626
|
+
* @param k - Key to look up
|
|
627
|
+
* @param m - Object with values and optional default
|
|
628
|
+
* @returns Value at key or default value
|
|
629
|
+
*/
|
|
630
|
+
export const switcher = (k, m) => m[k] === undefined ? m.default : m[k];
|
|
631
|
+
// ----------------------------------------------------------------------------
|
|
632
|
+
// Combinators
|
|
633
|
+
// ----------------------------------------------------------------------------
|
|
634
|
+
/** Returns a function that returns the boolean negation of the given function */
|
|
635
|
+
export const complement = (f) => (...args) => !f(...args);
|
|
636
|
+
/**
|
|
637
|
+
* Safely executes function and handles errors
|
|
638
|
+
* @param f - Function to execute
|
|
639
|
+
* @param onError - Optional error handler
|
|
640
|
+
* @returns Function result or undefined if error
|
|
641
|
+
*/
|
|
642
|
+
export const tryCatch = (f, onError) => {
|
|
643
|
+
try {
|
|
644
|
+
const r = f();
|
|
645
|
+
if (r instanceof Promise) {
|
|
646
|
+
r.catch(e => onError?.(e));
|
|
647
|
+
}
|
|
648
|
+
return r;
|
|
649
|
+
}
|
|
650
|
+
catch (e) {
|
|
651
|
+
onError?.(e);
|
|
652
|
+
}
|
|
653
|
+
return undefined;
|
|
654
|
+
};
|
|
722
655
|
/**
|
|
723
656
|
* Creates function that only executes once
|
|
724
657
|
* @param f - Function to wrap
|
|
@@ -738,22 +671,67 @@ export const once = (f) => {
|
|
|
738
671
|
* @param f - Function to call
|
|
739
672
|
* @returns Whatever f returns
|
|
740
673
|
*/
|
|
741
|
-
export const call = (f, ...args) => f();
|
|
674
|
+
export const call = (f, ...args) => f();
|
|
675
|
+
/**
|
|
676
|
+
* Memoizes function results based on arguments
|
|
677
|
+
* @param f - Function to memoize
|
|
678
|
+
* @returns Memoized function
|
|
679
|
+
*/
|
|
680
|
+
export const memoize = (f) => {
|
|
681
|
+
let prevArgs;
|
|
682
|
+
let result;
|
|
683
|
+
return (...args) => {
|
|
684
|
+
if (!equals(prevArgs, args)) {
|
|
685
|
+
prevArgs = args;
|
|
686
|
+
result = f(...args);
|
|
687
|
+
}
|
|
688
|
+
return result;
|
|
689
|
+
};
|
|
690
|
+
};
|
|
691
|
+
/**
|
|
692
|
+
* Executes a function if the value is defined
|
|
693
|
+
* @param x - The value to check
|
|
694
|
+
* @param f - Function to execute if x is defined
|
|
695
|
+
* @returns Result of f(x) if x is defined, undefined otherwise
|
|
696
|
+
*/
|
|
697
|
+
export const ifLet = (x, f) => x === undefined ? undefined : f(x);
|
|
698
|
+
// ----------------------------------------------------------------------------
|
|
699
|
+
// Randomness
|
|
700
|
+
// ----------------------------------------------------------------------------
|
|
701
|
+
/**
|
|
702
|
+
* Generates random integer between min and max (inclusive)
|
|
703
|
+
* @param min - Minimum value
|
|
704
|
+
* @param max - Maximum value
|
|
705
|
+
* @returns Random integer
|
|
706
|
+
*/
|
|
707
|
+
export const randomInt = (min = 0, max = 9) => min + Math.round(Math.random() * (max - min));
|
|
708
|
+
/**
|
|
709
|
+
* Generates random string ID
|
|
710
|
+
* @returns Random string suitable for use as an ID
|
|
711
|
+
*/
|
|
712
|
+
export const randomId = () => Math.random().toString().slice(2);
|
|
713
|
+
// ----------------------------------------------------------------------------
|
|
714
|
+
// Async
|
|
715
|
+
// ----------------------------------------------------------------------------
|
|
716
|
+
/**
|
|
717
|
+
* Creates a promise that resolves after specified time
|
|
718
|
+
* @param t - Time in milliseconds
|
|
719
|
+
* @returns Promise that resolves after t milliseconds
|
|
720
|
+
*/
|
|
721
|
+
export const sleep = (t) => new Promise(resolve => setTimeout(resolve, t));
|
|
742
722
|
/**
|
|
743
|
-
*
|
|
744
|
-
* @
|
|
745
|
-
* @returns Memoized function
|
|
723
|
+
* Creates a microtask that yields to other tasks in the event loop
|
|
724
|
+
* @returns Promise that resolves after yielding
|
|
746
725
|
*/
|
|
747
|
-
export const
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
};
|
|
726
|
+
export const yieldThread = () => {
|
|
727
|
+
if (typeof window !== "undefined" &&
|
|
728
|
+
"scheduler" in window &&
|
|
729
|
+
"yield" in window.scheduler) {
|
|
730
|
+
return window.scheduler.yield();
|
|
731
|
+
}
|
|
732
|
+
return new Promise(resolve => {
|
|
733
|
+
setTimeout(resolve, 0);
|
|
734
|
+
});
|
|
757
735
|
};
|
|
758
736
|
/**
|
|
759
737
|
* Creates throttled version of function
|
|
@@ -844,108 +822,59 @@ export const batcher = (t, execute) => {
|
|
|
844
822
|
queue.push({ request, resolve, reject });
|
|
845
823
|
});
|
|
846
824
|
};
|
|
825
|
+
// ----------------------------------------------------------------------------
|
|
826
|
+
// URLs
|
|
827
|
+
// ----------------------------------------------------------------------------
|
|
847
828
|
/**
|
|
848
|
-
*
|
|
849
|
-
* @param
|
|
850
|
-
* @
|
|
851
|
-
* @param v - Value to add
|
|
852
|
-
*/
|
|
853
|
-
export const addToKey = (m, k, v) => {
|
|
854
|
-
const s = m[k] || new Set();
|
|
855
|
-
s.add(v);
|
|
856
|
-
m[k] = s;
|
|
857
|
-
};
|
|
858
|
-
/**
|
|
859
|
-
* Pushes value to array at key in object
|
|
860
|
-
* @param m - Object mapping keys to arrays
|
|
861
|
-
* @param k - Key to push to
|
|
862
|
-
* @param v - Value to push
|
|
829
|
+
* Removes protocol (http://, https://, etc) from URL
|
|
830
|
+
* @param url - URL to process
|
|
831
|
+
* @returns URL without protocol
|
|
863
832
|
*/
|
|
864
|
-
export const
|
|
865
|
-
const a = m[k] || [];
|
|
866
|
-
a.push(v);
|
|
867
|
-
m[k] = a;
|
|
868
|
-
};
|
|
833
|
+
export const stripProtocol = (url) => url.replace(/.*:\/\//, "");
|
|
869
834
|
/**
|
|
870
|
-
*
|
|
871
|
-
* @param
|
|
872
|
-
* @
|
|
873
|
-
* @param v - Value to add
|
|
835
|
+
* Formats URL for display by removing protocol, www, and trailing slash
|
|
836
|
+
* @param url - URL to format
|
|
837
|
+
* @returns Formatted URL
|
|
874
838
|
*/
|
|
875
|
-
export const
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
m.set(k, s);
|
|
879
|
-
};
|
|
839
|
+
export const displayUrl = (url) => stripProtocol(url)
|
|
840
|
+
.replace(/^(www\.)?/i, "")
|
|
841
|
+
.replace(/\/$/, "");
|
|
880
842
|
/**
|
|
881
|
-
*
|
|
882
|
-
* @param
|
|
883
|
-
* @
|
|
884
|
-
* @param v - Value to push
|
|
843
|
+
* Extracts and formats domain from URL
|
|
844
|
+
* @param url - URL to process
|
|
845
|
+
* @returns Formatted domain name
|
|
885
846
|
*/
|
|
886
|
-
export const
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
};
|
|
847
|
+
export const displayDomain = (url) => displayUrl(first(url.split(/[\/\?]/)) || "");
|
|
848
|
+
// ----------------------------------------------------------------------------
|
|
849
|
+
// JSON, localStorage, fetch, event emitters, etc
|
|
850
|
+
// ----------------------------------------------------------------------------
|
|
891
851
|
/**
|
|
892
|
-
*
|
|
893
|
-
*
|
|
894
|
-
*
|
|
895
|
-
* @param target - The event target object with add/remove listener methods
|
|
896
|
-
* @param eventName - The name of the event to listen for
|
|
897
|
-
* @param callback - The callback function to execute when the event occurs
|
|
898
|
-
* @returns A function that removes the event listener when called
|
|
852
|
+
* Safely parses JSON string
|
|
853
|
+
* @param json - JSON string to parse
|
|
854
|
+
* @returns Parsed object or null if invalid
|
|
899
855
|
*/
|
|
900
|
-
export const
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
856
|
+
export const parseJson = (json) => {
|
|
857
|
+
if (!json)
|
|
858
|
+
return undefined;
|
|
859
|
+
try {
|
|
860
|
+
return JSON.parse(json);
|
|
861
|
+
}
|
|
862
|
+
catch (e) {
|
|
863
|
+
return undefined;
|
|
864
|
+
}
|
|
905
865
|
};
|
|
906
866
|
/**
|
|
907
|
-
*
|
|
908
|
-
* @param k -
|
|
909
|
-
* @
|
|
910
|
-
* @returns Value at key or default value
|
|
911
|
-
*/
|
|
912
|
-
export const switcher = (k, m) => m[k] === undefined ? m.default : m[k];
|
|
913
|
-
/** One minute in seconds */
|
|
914
|
-
export const MINUTE = 60;
|
|
915
|
-
/** One hour in seconds */
|
|
916
|
-
export const HOUR = 60 * MINUTE;
|
|
917
|
-
/** One day in seconds */
|
|
918
|
-
export const DAY = 24 * HOUR;
|
|
919
|
-
/** One week in seconds */
|
|
920
|
-
export const WEEK = 7 * DAY;
|
|
921
|
-
/** One month in seconds (approximate) */
|
|
922
|
-
export const MONTH = 30 * DAY;
|
|
923
|
-
/** One quarter in seconds (approximate) */
|
|
924
|
-
export const QUARTER = 90 * DAY;
|
|
925
|
-
/** One year in seconds (approximate) */
|
|
926
|
-
export const YEAR = 365 * DAY;
|
|
927
|
-
/**
|
|
928
|
-
* Multiplies time unit by count
|
|
929
|
-
* @param unit - Time unit in seconds
|
|
930
|
-
* @param count - Number of units
|
|
931
|
-
* @returns Total seconds
|
|
932
|
-
*/
|
|
933
|
-
export const int = (unit, count = 1) => unit * count;
|
|
934
|
-
/** Returns current Unix timestamp in seconds */
|
|
935
|
-
export const now = () => Math.round(Date.now() / 1000);
|
|
936
|
-
/**
|
|
937
|
-
* Returns Unix timestamp from specified time ago
|
|
938
|
-
* @param unit - Time unit in seconds
|
|
939
|
-
* @param count - Number of units
|
|
940
|
-
* @returns Timestamp in seconds
|
|
867
|
+
* Gets and parses JSON from localStorage
|
|
868
|
+
* @param k - Storage key
|
|
869
|
+
* @returns Parsed value or undefined if invalid/missing
|
|
941
870
|
*/
|
|
942
|
-
export const
|
|
871
|
+
export const getJson = (k) => parseJson(localStorage.getItem(k) || "");
|
|
943
872
|
/**
|
|
944
|
-
*
|
|
945
|
-
* @param
|
|
946
|
-
* @
|
|
873
|
+
* Stringifies and stores value in localStorage
|
|
874
|
+
* @param k - Storage key
|
|
875
|
+
* @param v - Value to store
|
|
947
876
|
*/
|
|
948
|
-
export const
|
|
877
|
+
export const setJson = (k, v) => localStorage.setItem(k, JSON.stringify(v));
|
|
949
878
|
/**
|
|
950
879
|
* Fetches JSON from URL with options
|
|
951
880
|
* @param url - URL to fetch from
|
|
@@ -992,6 +921,130 @@ export const uploadFile = (url, file) => {
|
|
|
992
921
|
body.append("file", file);
|
|
993
922
|
return fetchJson(url, { method: "POST", body });
|
|
994
923
|
};
|
|
924
|
+
/**
|
|
925
|
+
* A generic type-safe event listener function that works with event emitters.
|
|
926
|
+
*
|
|
927
|
+
* @param target - The event target object with add/remove listener methods
|
|
928
|
+
* @param eventName - The name of the event to listen for
|
|
929
|
+
* @param callback - The callback function to execute when the event occurs
|
|
930
|
+
* @returns A function that removes the event listener when called
|
|
931
|
+
*/
|
|
932
|
+
export const on = (target, eventName, callback) => {
|
|
933
|
+
target.on(eventName, callback);
|
|
934
|
+
return () => {
|
|
935
|
+
target.off(eventName, callback);
|
|
936
|
+
};
|
|
937
|
+
};
|
|
938
|
+
// ----------------------------------------------------------------------------
|
|
939
|
+
// Strings
|
|
940
|
+
// ----------------------------------------------------------------------------
|
|
941
|
+
/**
|
|
942
|
+
* Truncates string to length, breaking at word boundaries
|
|
943
|
+
* @param s - String to truncate
|
|
944
|
+
* @param l - Maximum length
|
|
945
|
+
* @param suffix - String to append if truncated
|
|
946
|
+
* @returns Truncated string
|
|
947
|
+
*/
|
|
948
|
+
export const ellipsize = (s, l, suffix = "...") => {
|
|
949
|
+
if (s.length < l * 1.1) {
|
|
950
|
+
return s;
|
|
951
|
+
}
|
|
952
|
+
while (s.length > l && s.includes(" ")) {
|
|
953
|
+
s = s.split(" ").slice(0, -1).join(" ");
|
|
954
|
+
}
|
|
955
|
+
return s + suffix;
|
|
956
|
+
};
|
|
957
|
+
/** Generates a hash string from input string */
|
|
958
|
+
export const hash = (s) => Math.abs(s.split("").reduce((a, b) => ((a << 5) - a + b.charCodeAt(0)) | 0, 0)).toString();
|
|
959
|
+
// ----------------------------------------------------------------------------
|
|
960
|
+
// Curried utilities for working with collections
|
|
961
|
+
// ----------------------------------------------------------------------------
|
|
962
|
+
/** Returns a function that gets the nth element of an array */
|
|
963
|
+
export const nth = (i) => (xs, ...args) => xs[i];
|
|
964
|
+
/** Returns a function that checks if nth element equals value */
|
|
965
|
+
export const nthEq = (i, v) => (xs, ...args) => xs[i] === v;
|
|
966
|
+
/** Returns a function that checks if nth element does not equal value */
|
|
967
|
+
export const nthNe = (i, v) => (xs, ...args) => xs[i] !== v;
|
|
968
|
+
/** Returns a function that checks if key/value pairs of x match all pairs in spec */
|
|
969
|
+
export const spec = (values) => (x, ...args) => {
|
|
970
|
+
if (Array.isArray(values)) {
|
|
971
|
+
for (let i = 0; i < values.length; i++) {
|
|
972
|
+
if (x[i] !== values[i]) {
|
|
973
|
+
return false;
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
else {
|
|
978
|
+
for (const [k, v] of Object.entries(values)) {
|
|
979
|
+
if (x[k] !== v)
|
|
980
|
+
return false;
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
return true;
|
|
984
|
+
};
|
|
985
|
+
/** Returns a function that checks equality with value */
|
|
986
|
+
export const eq = (v) => (x) => x === v;
|
|
987
|
+
/** Returns a function that checks inequality with value */
|
|
988
|
+
export const ne = (v) => (x) => x !== v;
|
|
989
|
+
/** Returns a function that gets property value from object */
|
|
990
|
+
export const prop = (k) => (x) => x[k];
|
|
991
|
+
/** Returns a function that adds/updates a property on object */
|
|
992
|
+
export const assoc = (k, v) => (o) => ({ ...o, [k]: v });
|
|
993
|
+
/** Returns a function that removes a property on object */
|
|
994
|
+
export const dissoc = (k) => (o) => omit([k], o);
|
|
995
|
+
// ----------------------------------------------------------------------------
|
|
996
|
+
// Sets
|
|
997
|
+
// ----------------------------------------------------------------------------
|
|
998
|
+
/**
|
|
999
|
+
* Adds value to Set at key in object
|
|
1000
|
+
* @param m - Object mapping keys to Sets
|
|
1001
|
+
* @param k - Key to add to
|
|
1002
|
+
* @param v - Value to add
|
|
1003
|
+
*/
|
|
1004
|
+
export const addToKey = (m, k, v) => {
|
|
1005
|
+
const s = m[k] || new Set();
|
|
1006
|
+
s.add(v);
|
|
1007
|
+
m[k] = s;
|
|
1008
|
+
};
|
|
1009
|
+
/**
|
|
1010
|
+
* Pushes value to array at key in object
|
|
1011
|
+
* @param m - Object mapping keys to arrays
|
|
1012
|
+
* @param k - Key to push to
|
|
1013
|
+
* @param v - Value to push
|
|
1014
|
+
*/
|
|
1015
|
+
export const pushToKey = (m, k, v) => {
|
|
1016
|
+
const a = m[k] || [];
|
|
1017
|
+
a.push(v);
|
|
1018
|
+
m[k] = a;
|
|
1019
|
+
};
|
|
1020
|
+
// ----------------------------------------------------------------------------
|
|
1021
|
+
// Maps
|
|
1022
|
+
// ----------------------------------------------------------------------------
|
|
1023
|
+
/**
|
|
1024
|
+
* Adds value to Set at key in Map
|
|
1025
|
+
* @param m - Map of Sets
|
|
1026
|
+
* @param k - Key to add to
|
|
1027
|
+
* @param v - Value to add
|
|
1028
|
+
*/
|
|
1029
|
+
export const addToMapKey = (m, k, v) => {
|
|
1030
|
+
const s = m.get(k) || new Set();
|
|
1031
|
+
s.add(v);
|
|
1032
|
+
m.set(k, s);
|
|
1033
|
+
};
|
|
1034
|
+
/**
|
|
1035
|
+
* Pushes value to array at key in Map
|
|
1036
|
+
* @param m - Map of arrays
|
|
1037
|
+
* @param k - Key to push to
|
|
1038
|
+
* @param v - Value to push
|
|
1039
|
+
*/
|
|
1040
|
+
export const pushToMapKey = (m, k, v) => {
|
|
1041
|
+
const a = m.get(k) || [];
|
|
1042
|
+
a.push(v);
|
|
1043
|
+
m.set(k, a);
|
|
1044
|
+
};
|
|
1045
|
+
// ----------------------------------------------------------------------------
|
|
1046
|
+
// Bech32 <-> hex encoding
|
|
1047
|
+
// ----------------------------------------------------------------------------
|
|
995
1048
|
/**
|
|
996
1049
|
* Converts hex string to bech32 format
|
|
997
1050
|
* @param prefix - Bech32 prefix
|