@welshman/util 0.1.2 → 0.2.0
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/dist/lib/src/Deferred.d.ts +21 -0
- package/dist/lib/src/Deferred.d.ts.map +1 -0
- package/dist/lib/src/Deferred.js +21 -0
- package/dist/lib/src/Deferred.js.map +1 -0
- package/dist/lib/src/Emitter.d.ts +14 -0
- package/dist/lib/src/Emitter.d.ts.map +1 -0
- package/dist/lib/src/Emitter.js +18 -0
- package/dist/lib/src/Emitter.js.map +1 -0
- package/dist/lib/src/LRUCache.d.ts +42 -0
- package/dist/lib/src/LRUCache.d.ts.map +1 -0
- package/dist/lib/src/LRUCache.js +62 -0
- package/dist/lib/src/LRUCache.js.map +1 -0
- package/dist/lib/src/TaskQueue.d.ts +20 -0
- package/dist/lib/src/TaskQueue.d.ts.map +1 -0
- package/dist/lib/src/TaskQueue.js +57 -0
- package/dist/lib/src/TaskQueue.js.map +1 -0
- package/dist/lib/src/Tools.d.ts +727 -0
- package/dist/lib/src/Tools.d.ts.map +1 -0
- package/dist/lib/src/Tools.js +1224 -0
- package/dist/lib/src/Tools.js.map +1 -0
- package/dist/lib/src/index.d.ts +7 -0
- package/dist/lib/src/index.d.ts.map +1 -0
- package/dist/lib/src/index.js +7 -0
- package/dist/lib/src/index.js.map +1 -0
- package/dist/lib/src/normalize-url/index.d.ts +286 -0
- package/dist/lib/src/normalize-url/index.d.ts.map +1 -0
- package/dist/lib/src/normalize-url/index.js +252 -0
- package/dist/lib/src/normalize-url/index.js.map +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -0
- package/{build → dist/util}/src/Address.d.ts +1 -0
- package/dist/util/src/Address.d.ts.map +1 -0
- package/dist/util/src/Address.js.map +1 -0
- package/{build → dist/util}/src/Encryptable.d.ts +1 -0
- package/dist/util/src/Encryptable.d.ts.map +1 -0
- package/dist/util/src/Encryptable.js.map +1 -0
- package/{build → dist/util}/src/Events.d.ts +5 -4
- package/dist/util/src/Events.d.ts.map +1 -0
- package/{build → dist/util}/src/Events.js +5 -4
- package/dist/util/src/Events.js.map +1 -0
- package/{build → dist/util}/src/Filters.d.ts +1 -0
- package/dist/util/src/Filters.d.ts.map +1 -0
- package/dist/util/src/Filters.js.map +1 -0
- package/{build → dist/util}/src/Handler.d.ts +2 -1
- package/dist/util/src/Handler.d.ts.map +1 -0
- package/dist/util/src/Handler.js.map +1 -0
- package/{build → dist/util}/src/Kinds.d.ts +1 -0
- package/dist/util/src/Kinds.d.ts.map +1 -0
- package/dist/util/src/Kinds.js.map +1 -0
- package/{build → dist/util}/src/Links.d.ts +1 -0
- package/dist/util/src/Links.d.ts.map +1 -0
- package/dist/util/src/Links.js.map +1 -0
- package/{build → dist/util}/src/List.d.ts +3 -0
- package/dist/util/src/List.d.ts.map +1 -0
- package/{build → dist/util}/src/List.js +10 -3
- package/dist/util/src/List.js.map +1 -0
- package/{build → dist/util}/src/Profile.d.ts +4 -10
- package/dist/util/src/Profile.d.ts.map +1 -0
- package/{build → dist/util}/src/Profile.js +1 -0
- package/dist/util/src/Profile.js.map +1 -0
- package/{build → dist/util}/src/Relay.d.ts +7 -0
- package/dist/util/src/Relay.d.ts.map +1 -0
- package/{build → dist/util}/src/Relay.js +9 -2
- package/dist/util/src/Relay.js.map +1 -0
- package/{build → dist/util}/src/Tags.d.ts +1 -0
- package/dist/util/src/Tags.d.ts.map +1 -0
- package/dist/util/src/Tags.js.map +1 -0
- package/{build → dist/util}/src/Zaps.d.ts +1 -0
- package/dist/util/src/Zaps.d.ts.map +1 -0
- package/dist/util/src/Zaps.js.map +1 -0
- package/{build → dist/util}/src/index.d.ts +1 -0
- package/dist/util/src/index.d.ts.map +1 -0
- package/dist/util/src/index.js.map +1 -0
- package/package.json +16 -22
- package/README.md +0 -15
- package/build/src/Address.js.map +0 -1
- package/build/src/Encryptable.js.map +0 -1
- package/build/src/Events.js.map +0 -1
- package/build/src/Filters.js.map +0 -1
- package/build/src/Handler.js.map +0 -1
- package/build/src/Kinds.js.map +0 -1
- package/build/src/Links.js.map +0 -1
- package/build/src/List.js.map +0 -1
- package/build/src/Profile.js.map +0 -1
- package/build/src/Relay.js.map +0 -1
- package/build/src/Tags.js.map +0 -1
- package/build/src/Zaps.js.map +0 -1
- package/build/src/index.js.map +0 -1
- package/build/tsconfig.tsbuildinfo +0 -1
- /package/{build → dist/util}/src/Address.js +0 -0
- /package/{build → dist/util}/src/Encryptable.js +0 -0
- /package/{build → dist/util}/src/Filters.js +0 -0
- /package/{build → dist/util}/src/Handler.js +0 -0
- /package/{build → dist/util}/src/Kinds.js +0 -0
- /package/{build → dist/util}/src/Links.js +0 -0
- /package/{build → dist/util}/src/Tags.js +0 -0
- /package/{build → dist/util}/src/Zaps.js +0 -0
- /package/{build → dist/util}/src/index.js +0 -0
|
@@ -0,0 +1,1224 @@
|
|
|
1
|
+
import { bech32, utf8 } from "@scure/base";
|
|
2
|
+
export const isNil = (x, ...args) => x === undefined || x === null;
|
|
3
|
+
export const isNotNil = (x, ...args) => x !== undefined && x !== null;
|
|
4
|
+
export const assertNotNil = (x, ...args) => x;
|
|
5
|
+
// ----------------------------------------------------------------------------
|
|
6
|
+
// Basic functional programming utilities
|
|
7
|
+
// ----------------------------------------------------------------------------
|
|
8
|
+
/** Function that does nothing and returns undefined */
|
|
9
|
+
export const noop = (...args) => undefined;
|
|
10
|
+
/**
|
|
11
|
+
* Returns the input value unchanged
|
|
12
|
+
* @param x - Any value
|
|
13
|
+
* @returns The same value
|
|
14
|
+
*/
|
|
15
|
+
export const identity = (x, ...args) => x;
|
|
16
|
+
/**
|
|
17
|
+
* Creates a function that always returns the same value
|
|
18
|
+
* @param x - Value to return
|
|
19
|
+
* @returns Function that returns x
|
|
20
|
+
*/
|
|
21
|
+
export const always = (x, ...args) => () => x;
|
|
22
|
+
/**
|
|
23
|
+
* Returns the logical NOT of a value
|
|
24
|
+
* @param x - Value to negate
|
|
25
|
+
* @returns !x
|
|
26
|
+
*/
|
|
27
|
+
export const not = (x, ...args) => !x;
|
|
28
|
+
/**
|
|
29
|
+
* Deep equality comparison
|
|
30
|
+
* @param a - First value
|
|
31
|
+
* @param b - Second value
|
|
32
|
+
* @returns True if values are deeply equal
|
|
33
|
+
*/
|
|
34
|
+
export const equals = (a, b) => {
|
|
35
|
+
if (a === b)
|
|
36
|
+
return true;
|
|
37
|
+
if (a instanceof Set && b instanceof Set) {
|
|
38
|
+
a = Array.from(a);
|
|
39
|
+
b = Array.from(b);
|
|
40
|
+
}
|
|
41
|
+
if (a instanceof Set) {
|
|
42
|
+
if (!(b instanceof Set) || a.size !== b.size) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
return Array.from(a).every(x => b.has(x));
|
|
46
|
+
}
|
|
47
|
+
if (Array.isArray(a)) {
|
|
48
|
+
if (!Array.isArray(b) || a.length !== b.length) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
for (let i = 0; i < a.length; i++) {
|
|
52
|
+
if (!equals(a[i], b[i])) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
if (isPojo(a)) {
|
|
59
|
+
if (!isPojo(b)) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
const aKeys = Object.keys(a);
|
|
63
|
+
const bKeys = Object.keys(b);
|
|
64
|
+
if (aKeys.length !== bKeys.length) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
for (const k of aKeys) {
|
|
68
|
+
if (!equals(a[k], b[k])) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
return false;
|
|
75
|
+
};
|
|
76
|
+
// ----------------------------------------------------------------------------
|
|
77
|
+
// Numbers
|
|
78
|
+
// ----------------------------------------------------------------------------
|
|
79
|
+
/** Converts string or number to number */
|
|
80
|
+
export const ensureNumber = (x) => parseFloat(x);
|
|
81
|
+
/** Converts a `number | undefined` to a number, defaulting to 0 */
|
|
82
|
+
export const num = (x) => x || 0;
|
|
83
|
+
/** Adds two numbers, handling undefined values */
|
|
84
|
+
export const add = (x, y) => num(x) + num(y);
|
|
85
|
+
/** Subtracts two numbers, handling undefined values */
|
|
86
|
+
export const sub = (x, y) => num(x) - num(y);
|
|
87
|
+
/** Multiplies two numbers, handling undefined values */
|
|
88
|
+
export const mul = (x, y) => num(x) * num(y);
|
|
89
|
+
/** Divides two numbers, handling undefined values */
|
|
90
|
+
export const div = (x, y) => num(x) / y;
|
|
91
|
+
/** Increments a number by 1, handling undefined values */
|
|
92
|
+
export const inc = (x) => add(x, 1);
|
|
93
|
+
/** Decrements a number by 1, handling undefined values */
|
|
94
|
+
export const dec = (x) => sub(x, 1);
|
|
95
|
+
/** Less than comparison, handling undefined values */
|
|
96
|
+
export const lt = (x, y) => num(x) < num(y);
|
|
97
|
+
/** Less than or equal comparison, handling undefined values */
|
|
98
|
+
export const lte = (x, y) => num(x) <= num(y);
|
|
99
|
+
/** Greater than comparison, handling undefined values */
|
|
100
|
+
export const gt = (x, y) => num(x) > num(y);
|
|
101
|
+
/** Greater than or equal comparison, handling undefined values */
|
|
102
|
+
export const gte = (x, y) => num(x) >= num(y);
|
|
103
|
+
/** Returns maximum value in array, handling undefined values */
|
|
104
|
+
export const max = (xs) => xs.reduce((a, b) => Math.max(num(a), num(b)), 0);
|
|
105
|
+
/** Returns minimum value in array, handling undefined values */
|
|
106
|
+
export const min = (xs) => {
|
|
107
|
+
const [head, ...tail] = xs.filter(x => x !== undefined);
|
|
108
|
+
if (tail.length === 0)
|
|
109
|
+
return head || 0;
|
|
110
|
+
return tail.reduce((a, b) => Math.min(a, b), head);
|
|
111
|
+
};
|
|
112
|
+
/** Returns sum of array values, handling undefined values */
|
|
113
|
+
export const sum = (xs) => xs.reduce((a, b) => add(a, b), 0);
|
|
114
|
+
/** Returns average of array values, handling undefined values */
|
|
115
|
+
export const avg = (xs) => sum(xs) / xs.length;
|
|
116
|
+
/**
|
|
117
|
+
* Checks if a number is between two values (exclusive)
|
|
118
|
+
* @param bounds - Lower and upper bounds
|
|
119
|
+
* @param n - Number to check
|
|
120
|
+
* @returns True if n is between low and high
|
|
121
|
+
*/
|
|
122
|
+
export const between = ([low, high], n) => n > low && n < high;
|
|
123
|
+
/**
|
|
124
|
+
* Checks if a number is between two values (inclusive)
|
|
125
|
+
* @param bounds - Lower and upper bounds
|
|
126
|
+
* @param n - Number to check
|
|
127
|
+
* @returns True if n is between low and high
|
|
128
|
+
*/
|
|
129
|
+
export const within = ([low, high], n) => n >= low && n <= high;
|
|
130
|
+
/**
|
|
131
|
+
* Constrains number between min and max values
|
|
132
|
+
* @param bounds - Minimum and maximum allowed values
|
|
133
|
+
* @param n - Number to clamp
|
|
134
|
+
* @returns Clamped value
|
|
135
|
+
*/
|
|
136
|
+
export const clamp = ([min, max], n) => Math.min(max, Math.max(min, n));
|
|
137
|
+
/**
|
|
138
|
+
* Round a number to the nearest float precision
|
|
139
|
+
* @param precision - Number of decimal places
|
|
140
|
+
* @param x - Number to round
|
|
141
|
+
* @returns Formatted number
|
|
142
|
+
*/
|
|
143
|
+
export const round = (precision, x) => Math.round(x * Math.pow(10, precision)) / Math.pow(10, precision);
|
|
144
|
+
// ----------------------------------------------------------------------------
|
|
145
|
+
// Timestamps
|
|
146
|
+
// ----------------------------------------------------------------------------
|
|
147
|
+
/** One minute in seconds */
|
|
148
|
+
export const MINUTE = 60;
|
|
149
|
+
/** One hour in seconds */
|
|
150
|
+
export const HOUR = 60 * MINUTE;
|
|
151
|
+
/** One day in seconds */
|
|
152
|
+
export const DAY = 24 * HOUR;
|
|
153
|
+
/** One week in seconds */
|
|
154
|
+
export const WEEK = 7 * DAY;
|
|
155
|
+
/** One month in seconds (approximate) */
|
|
156
|
+
export const MONTH = 30 * DAY;
|
|
157
|
+
/** One quarter in seconds (approximate) */
|
|
158
|
+
export const QUARTER = 90 * DAY;
|
|
159
|
+
/** One year in seconds (approximate) */
|
|
160
|
+
export const YEAR = 365 * DAY;
|
|
161
|
+
/** User's default locale */
|
|
162
|
+
export const LOCALE = new Intl.DateTimeFormat().resolvedOptions().locale;
|
|
163
|
+
/** User's default timezone */
|
|
164
|
+
export const TIMEZONE = new Date().toString().match(/GMT[^\s]+/)[0];
|
|
165
|
+
/**
|
|
166
|
+
* Multiplies time unit by count
|
|
167
|
+
* @param unit - Time unit in seconds
|
|
168
|
+
* @param count - Number of units
|
|
169
|
+
* @returns Total seconds
|
|
170
|
+
*/
|
|
171
|
+
export const int = (unit, count = 1) => unit * count;
|
|
172
|
+
/** Returns current Unix timestamp in seconds */
|
|
173
|
+
export const now = () => Math.round(Date.now() / 1000);
|
|
174
|
+
/**
|
|
175
|
+
* Returns Unix timestamp from specified time ago
|
|
176
|
+
* @param unit - Time unit in seconds
|
|
177
|
+
* @param count - Number of units
|
|
178
|
+
* @returns Timestamp in seconds
|
|
179
|
+
*/
|
|
180
|
+
export const ago = (unit, count = 1) => now() - int(unit, count);
|
|
181
|
+
/**
|
|
182
|
+
* Converts seconds to milliseconds
|
|
183
|
+
* @param seconds - Time in seconds
|
|
184
|
+
* @returns Time in milliseconds
|
|
185
|
+
*/
|
|
186
|
+
export const ms = (seconds) => seconds * 1000;
|
|
187
|
+
/**
|
|
188
|
+
* Converts seconds to date
|
|
189
|
+
* @param seconds - Time in seconds
|
|
190
|
+
* @returns Date object
|
|
191
|
+
*/
|
|
192
|
+
export const secondsToDate = (seconds) => new Date(seconds * 1000);
|
|
193
|
+
/**
|
|
194
|
+
* Converts date object to seconds
|
|
195
|
+
* @param date - Date object
|
|
196
|
+
* @returns timestamp in seconds
|
|
197
|
+
*/
|
|
198
|
+
export const dateToSeconds = (date) => Math.round(date.valueOf() / 1000);
|
|
199
|
+
/**
|
|
200
|
+
* Creates a local date from a date string
|
|
201
|
+
* @param dateString - date string
|
|
202
|
+
* @param timezone - timezone string
|
|
203
|
+
* @returns timezone-aware Date object
|
|
204
|
+
*/
|
|
205
|
+
export const createLocalDate = (dateString, timezone = TIMEZONE) => new Date(`${dateString} ${timezone}`);
|
|
206
|
+
/** Formatter for date+time */
|
|
207
|
+
export const dateTimeFormatter = new Intl.DateTimeFormat(LOCALE, {
|
|
208
|
+
dateStyle: "short",
|
|
209
|
+
timeStyle: "short",
|
|
210
|
+
});
|
|
211
|
+
/**
|
|
212
|
+
* Formats seconds as a datetime
|
|
213
|
+
* @param seconds - timestamp in seconds
|
|
214
|
+
* @returns datetime string
|
|
215
|
+
*/
|
|
216
|
+
export const formatTimestamp = (seconds) => dateTimeFormatter.format(secondsToDate(seconds));
|
|
217
|
+
/** Formatter for date */
|
|
218
|
+
export const dateFormatter = new Intl.DateTimeFormat(LOCALE, {
|
|
219
|
+
year: "numeric",
|
|
220
|
+
month: "long",
|
|
221
|
+
day: "numeric",
|
|
222
|
+
});
|
|
223
|
+
/**
|
|
224
|
+
* Formats seconds as a date
|
|
225
|
+
* @param seconds - timestamp in seconds
|
|
226
|
+
* @returns date string
|
|
227
|
+
*/
|
|
228
|
+
export const formatTimestampAsDate = (ts) => dateFormatter.format(secondsToDate(ts));
|
|
229
|
+
/** Formatter for time */
|
|
230
|
+
export const timeFormatter = new Intl.DateTimeFormat(LOCALE, {
|
|
231
|
+
timeStyle: "short",
|
|
232
|
+
});
|
|
233
|
+
/**
|
|
234
|
+
* Formats seconds as a time
|
|
235
|
+
* @param seconds - timestamp in seconds
|
|
236
|
+
* @returns time string
|
|
237
|
+
*/
|
|
238
|
+
export const formatTimestampAsTime = (ts) => timeFormatter.format(secondsToDate(ts));
|
|
239
|
+
/**
|
|
240
|
+
* Formats seconds as a relative date (x minutes ago)
|
|
241
|
+
* @param seconds - timestamp in seconds
|
|
242
|
+
* @returns relative date string
|
|
243
|
+
*/
|
|
244
|
+
export const formatTimestampRelative = (ts) => {
|
|
245
|
+
let unit;
|
|
246
|
+
let delta = now() - ts;
|
|
247
|
+
if (delta < int(MINUTE)) {
|
|
248
|
+
unit = "second";
|
|
249
|
+
}
|
|
250
|
+
else if (delta < int(HOUR)) {
|
|
251
|
+
unit = "minute";
|
|
252
|
+
delta = Math.round(delta / int(MINUTE));
|
|
253
|
+
}
|
|
254
|
+
else if (delta < int(DAY, 2)) {
|
|
255
|
+
unit = "hour";
|
|
256
|
+
delta = Math.round(delta / int(HOUR));
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
unit = "day";
|
|
260
|
+
delta = Math.round(delta / int(DAY));
|
|
261
|
+
}
|
|
262
|
+
const locale = new Intl.RelativeTimeFormat().resolvedOptions().locale;
|
|
263
|
+
const formatter = new Intl.RelativeTimeFormat(locale, {
|
|
264
|
+
numeric: "auto",
|
|
265
|
+
});
|
|
266
|
+
return formatter.format(-delta, unit);
|
|
267
|
+
};
|
|
268
|
+
// ----------------------------------------------------------------------------
|
|
269
|
+
// Sequences
|
|
270
|
+
// ----------------------------------------------------------------------------
|
|
271
|
+
/**
|
|
272
|
+
* Returns the first element of an array
|
|
273
|
+
* @param xs - The array
|
|
274
|
+
* @returns First element or undefined
|
|
275
|
+
*/
|
|
276
|
+
export const first = (xs, ...args) => {
|
|
277
|
+
for (const x of xs) {
|
|
278
|
+
return x;
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
/**
|
|
282
|
+
* Returns the first element of the first array in a nested array
|
|
283
|
+
* @param xs - Array of arrays
|
|
284
|
+
* @returns First element of first array or undefined
|
|
285
|
+
*/
|
|
286
|
+
export const ffirst = (xs, ...args) => {
|
|
287
|
+
for (const chunk of xs) {
|
|
288
|
+
for (const x of chunk) {
|
|
289
|
+
return x;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
/**
|
|
294
|
+
* Returns the last element of an array
|
|
295
|
+
* @param xs - The array
|
|
296
|
+
* @returns Last element or undefined
|
|
297
|
+
*/
|
|
298
|
+
export const last = (xs, ...args) => {
|
|
299
|
+
const a = Array.from(xs);
|
|
300
|
+
return a[a.length - 1];
|
|
301
|
+
};
|
|
302
|
+
/**
|
|
303
|
+
* Returns array with first n elements removed
|
|
304
|
+
* @param n - Number of elements to drop
|
|
305
|
+
* @param xs - Input array
|
|
306
|
+
* @returns Array with first n elements removed
|
|
307
|
+
*/
|
|
308
|
+
export const drop = (n, xs) => Array.from(xs).slice(n);
|
|
309
|
+
/**
|
|
310
|
+
* Returns first n elements of array
|
|
311
|
+
* @param n - Number of elements to take
|
|
312
|
+
* @param xs - Input array
|
|
313
|
+
* @returns Array of first n elements
|
|
314
|
+
*/
|
|
315
|
+
export const take = (n, xs) => Array.from(xs).slice(0, n);
|
|
316
|
+
/**
|
|
317
|
+
* Concatenates multiple arrays, filtering out null/undefined
|
|
318
|
+
* @param xs - Arrays to concatenate
|
|
319
|
+
* @returns Combined array
|
|
320
|
+
*/
|
|
321
|
+
export const concat = (...xs) => xs.flatMap(x => (x === undefined ? [] : x));
|
|
322
|
+
/**
|
|
323
|
+
* Appends element to array
|
|
324
|
+
* @param x - Element to append
|
|
325
|
+
* @param xs - Array to append to
|
|
326
|
+
* @returns New array with element appended
|
|
327
|
+
*/
|
|
328
|
+
export const append = (x, xs) => concat(xs, [x]);
|
|
329
|
+
/**
|
|
330
|
+
* Creates union of two arrays
|
|
331
|
+
* @param a - First array
|
|
332
|
+
* @param b - Second array
|
|
333
|
+
* @returns Array containing unique elements from both arrays
|
|
334
|
+
*/
|
|
335
|
+
export const union = (a, b) => uniq([...a, ...b]);
|
|
336
|
+
/**
|
|
337
|
+
* Returns elements common to both arrays
|
|
338
|
+
* @param a - First array
|
|
339
|
+
* @param b - Second array
|
|
340
|
+
* @returns Array of elements present in both inputs
|
|
341
|
+
*/
|
|
342
|
+
export const intersection = (a, b) => {
|
|
343
|
+
const s = new Set(b);
|
|
344
|
+
return a.filter(x => s.has(x));
|
|
345
|
+
};
|
|
346
|
+
/**
|
|
347
|
+
* Returns elements in first array not present in second
|
|
348
|
+
* @param a - Source array
|
|
349
|
+
* @param b - Array of elements to exclude
|
|
350
|
+
* @returns Array containing elements unique to first array
|
|
351
|
+
*/
|
|
352
|
+
export const difference = (a, b) => {
|
|
353
|
+
const s = new Set(b);
|
|
354
|
+
return a.filter(x => !s.has(x));
|
|
355
|
+
};
|
|
356
|
+
/**
|
|
357
|
+
* Removes all instances of an element from array
|
|
358
|
+
* @param a - Element to remove
|
|
359
|
+
* @param xs - Source array
|
|
360
|
+
* @returns New array with element removed
|
|
361
|
+
*/
|
|
362
|
+
export const remove = (a, xs) => xs.filter(x => x !== a);
|
|
363
|
+
/**
|
|
364
|
+
* Removes element at index
|
|
365
|
+
* @param i - Index to remove
|
|
366
|
+
* @param xs - Source array
|
|
367
|
+
* @returns New array with element removed
|
|
368
|
+
*/
|
|
369
|
+
export const removeAt = (i, xs) => [...xs.slice(0, i), ...xs.slice(i + 1)];
|
|
370
|
+
/**
|
|
371
|
+
* Returns elements from second array not present in first
|
|
372
|
+
* @param a - Array of elements to exclude
|
|
373
|
+
* @param b - Source array
|
|
374
|
+
* @returns Filtered array
|
|
375
|
+
*/
|
|
376
|
+
export const without = (a, b) => b.filter(x => !a.includes(x));
|
|
377
|
+
/**
|
|
378
|
+
* Toggles presence of element in array
|
|
379
|
+
* @param x - Element to toggle
|
|
380
|
+
* @param xs - Source array
|
|
381
|
+
* @returns New array with element added or removed
|
|
382
|
+
*/
|
|
383
|
+
export const toggle = (x, xs) => (xs.includes(x) ? remove(x, xs) : append(x, xs));
|
|
384
|
+
/**
|
|
385
|
+
* Generates sequence of numbers from a to b
|
|
386
|
+
* @param a - Start number (inclusive)
|
|
387
|
+
* @param b - End number (exclusive)
|
|
388
|
+
* @param step - Increment between numbers
|
|
389
|
+
* @yields Numbers in sequence
|
|
390
|
+
*/
|
|
391
|
+
export function* range(a, b, step = 1) {
|
|
392
|
+
for (let i = a; i < b; i += step) {
|
|
393
|
+
yield i;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Yields indexed items
|
|
398
|
+
* @param items - A collection of items
|
|
399
|
+
* @yields tuples of [index, item]
|
|
400
|
+
*/
|
|
401
|
+
export function* enumerate(items) {
|
|
402
|
+
for (let i = 0; i < items.length; i += 1) {
|
|
403
|
+
yield [i, items[i]];
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
/** Returns a function that gets property value from object */
|
|
407
|
+
export const pluck = (k, xs) => xs.map(x => x[k]);
|
|
408
|
+
/**
|
|
409
|
+
* Creates object from array of key-value pairs
|
|
410
|
+
* @param pairs - Array of [key, value] tuples
|
|
411
|
+
* @returns Object with keys and values from pairs
|
|
412
|
+
*/
|
|
413
|
+
export const fromPairs = (pairs) => {
|
|
414
|
+
const r = {};
|
|
415
|
+
for (const [k, v] of pairs) {
|
|
416
|
+
if (k && v) {
|
|
417
|
+
r[k] = v;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
return r;
|
|
421
|
+
};
|
|
422
|
+
/**
|
|
423
|
+
* Flattens array of arrays into single array
|
|
424
|
+
* @param xs - Array of arrays to flatten
|
|
425
|
+
* @returns Flattened array
|
|
426
|
+
*/
|
|
427
|
+
export const flatten = (xs) => xs.flatMap(identity);
|
|
428
|
+
/**
|
|
429
|
+
* Splits array into two arrays based on predicate
|
|
430
|
+
* @param f - Function to test elements
|
|
431
|
+
* @param xs - Array to partition
|
|
432
|
+
* @returns Tuple of [matching, non-matching] arrays
|
|
433
|
+
*/
|
|
434
|
+
export const partition = (f, xs) => {
|
|
435
|
+
const a = [];
|
|
436
|
+
const b = [];
|
|
437
|
+
for (const x of xs) {
|
|
438
|
+
if (f(x)) {
|
|
439
|
+
a.push(x);
|
|
440
|
+
}
|
|
441
|
+
else {
|
|
442
|
+
b.push(x);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
return [a, b];
|
|
446
|
+
};
|
|
447
|
+
/**
|
|
448
|
+
* Returns array with duplicate elements removed
|
|
449
|
+
* @param xs - Array with possible duplicates
|
|
450
|
+
* @returns Array with unique elements
|
|
451
|
+
*/
|
|
452
|
+
export const uniq = (xs) => Array.from(new Set(xs));
|
|
453
|
+
/**
|
|
454
|
+
* Returns array with elements unique by key function
|
|
455
|
+
* @param f - Function to generate key for each element
|
|
456
|
+
* @param xs - Input array
|
|
457
|
+
* @returns Array with elements unique by key
|
|
458
|
+
*/
|
|
459
|
+
export const uniqBy = (f, xs) => {
|
|
460
|
+
const s = new Set();
|
|
461
|
+
const r = [];
|
|
462
|
+
for (const x of xs) {
|
|
463
|
+
const k = f(x);
|
|
464
|
+
if (s.has(k)) {
|
|
465
|
+
continue;
|
|
466
|
+
}
|
|
467
|
+
s.add(k);
|
|
468
|
+
r.push(x);
|
|
469
|
+
}
|
|
470
|
+
return r;
|
|
471
|
+
};
|
|
472
|
+
/**
|
|
473
|
+
* Returns sorted copy of array
|
|
474
|
+
* @param xs - Array to sort
|
|
475
|
+
* @returns New sorted array
|
|
476
|
+
*/
|
|
477
|
+
export const sort = (xs) => [...xs].sort();
|
|
478
|
+
/**
|
|
479
|
+
* Returns array sorted by key function
|
|
480
|
+
* @param f - Function to generate sort key
|
|
481
|
+
* @param xs - Array to sort
|
|
482
|
+
* @returns Sorted array
|
|
483
|
+
*/
|
|
484
|
+
export const sortBy = (f, xs) => [...xs].sort((a, b) => {
|
|
485
|
+
const x = f(a);
|
|
486
|
+
const y = f(b);
|
|
487
|
+
return x < y ? -1 : x > y ? 1 : 0;
|
|
488
|
+
});
|
|
489
|
+
/**
|
|
490
|
+
* Groups array elements by key function
|
|
491
|
+
* @param f - Function to generate group key
|
|
492
|
+
* @param xs - Array to group
|
|
493
|
+
* @returns Map of groups
|
|
494
|
+
*/
|
|
495
|
+
export const groupBy = (f, xs) => {
|
|
496
|
+
const r = new Map();
|
|
497
|
+
for (const x of xs) {
|
|
498
|
+
const k = f(x);
|
|
499
|
+
let v = r.get(k);
|
|
500
|
+
if (!v) {
|
|
501
|
+
v = [];
|
|
502
|
+
r.set(k, v);
|
|
503
|
+
}
|
|
504
|
+
v.push(x);
|
|
505
|
+
}
|
|
506
|
+
return r;
|
|
507
|
+
};
|
|
508
|
+
/**
|
|
509
|
+
* Counts array elements by key function
|
|
510
|
+
* @param f - Function to generate group key
|
|
511
|
+
* @param xs - Array to count entries
|
|
512
|
+
* @returns Map of counts
|
|
513
|
+
*/
|
|
514
|
+
export const countBy = (f, xs) => {
|
|
515
|
+
const r = new Map();
|
|
516
|
+
for (const [k, items] of groupBy(f, xs)) {
|
|
517
|
+
r.set(k, items.length);
|
|
518
|
+
}
|
|
519
|
+
return r;
|
|
520
|
+
};
|
|
521
|
+
/**
|
|
522
|
+
* Creates map from array using key function
|
|
523
|
+
* @param f - Function to generate key
|
|
524
|
+
* @param xs - Array to index
|
|
525
|
+
* @returns Map of values by key
|
|
526
|
+
*/
|
|
527
|
+
export const indexBy = (f, xs) => {
|
|
528
|
+
const r = new Map();
|
|
529
|
+
for (const x of xs) {
|
|
530
|
+
r.set(f(x), x);
|
|
531
|
+
}
|
|
532
|
+
return r;
|
|
533
|
+
};
|
|
534
|
+
/**
|
|
535
|
+
* Creates array of specified length using generator function
|
|
536
|
+
* @param n - Length of array
|
|
537
|
+
* @param f - Function to generate each element
|
|
538
|
+
* @returns Generated array
|
|
539
|
+
*/
|
|
540
|
+
export const initArray = (n, f) => {
|
|
541
|
+
const result = [];
|
|
542
|
+
for (let i = 0; i < n; i++) {
|
|
543
|
+
result.push(f());
|
|
544
|
+
}
|
|
545
|
+
return result;
|
|
546
|
+
};
|
|
547
|
+
/**
|
|
548
|
+
* Splits array into chunks of specified length
|
|
549
|
+
* @param chunkLength - Maximum length of each chunk
|
|
550
|
+
* @param xs - Array to split
|
|
551
|
+
* @returns Array of chunks
|
|
552
|
+
*/
|
|
553
|
+
export const chunk = (chunkLength, xs) => {
|
|
554
|
+
const result = [];
|
|
555
|
+
const current = [];
|
|
556
|
+
for (const item of xs) {
|
|
557
|
+
if (current.length < chunkLength) {
|
|
558
|
+
current.push(item);
|
|
559
|
+
}
|
|
560
|
+
else {
|
|
561
|
+
result.push(current.splice(0));
|
|
562
|
+
current.push(item);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
if (current.length > 0) {
|
|
566
|
+
result.push(current);
|
|
567
|
+
}
|
|
568
|
+
return result;
|
|
569
|
+
};
|
|
570
|
+
/**
|
|
571
|
+
* Splits array into specified number of chunks
|
|
572
|
+
* @param n - Number of chunks
|
|
573
|
+
* @param xs - Array to split
|
|
574
|
+
* @returns Array of n chunks
|
|
575
|
+
*/
|
|
576
|
+
export const chunks = (n, xs) => {
|
|
577
|
+
const result = initArray(n, () => []);
|
|
578
|
+
for (let i = 0; i < xs.length; i++) {
|
|
579
|
+
result[i % n].push(xs[i]);
|
|
580
|
+
}
|
|
581
|
+
return result;
|
|
582
|
+
};
|
|
583
|
+
/** Splits array into two parts at index */
|
|
584
|
+
export const splitAt = (n, xs) => [xs.slice(0, n), xs.slice(n)];
|
|
585
|
+
/** Inserts element into array at index */
|
|
586
|
+
export const insertAt = (n, x, xs) => [...xs.slice(0, n), x, ...xs.slice(n)];
|
|
587
|
+
/** Replaces array element at index */
|
|
588
|
+
export const replaceAt = (n, x, xs) => [...xs.slice(0, n), x, ...xs.slice(n + 1)];
|
|
589
|
+
/** Returns random element from array */
|
|
590
|
+
export const choice = (xs) => xs[Math.floor(xs.length * Math.random())];
|
|
591
|
+
/** Returns shuffled copy of iterable */
|
|
592
|
+
export const shuffle = (xs) => Array.from(xs).sort(() => (Math.random() > 0.5 ? 1 : -1));
|
|
593
|
+
/** Returns n random elements from array */
|
|
594
|
+
export const sample = (n, xs) => shuffle(xs).slice(0, n);
|
|
595
|
+
/** Checks if value is iterable */
|
|
596
|
+
export const isIterable = (x) => Symbol.iterator in Object(x);
|
|
597
|
+
/** Ensures value is iterable by wrapping in array if needed */
|
|
598
|
+
export const toIterable = (x) => (isIterable(x) ? x : [x]);
|
|
599
|
+
/** Ensures value is array by wrapping if needed */
|
|
600
|
+
export const ensurePlural = (x) => (x instanceof Array ? x : [x]);
|
|
601
|
+
/** Ensures values are not undefined */
|
|
602
|
+
export const removeNil = (xs) => xs.filter(isNotNil).map(assertNotNil);
|
|
603
|
+
// ----------------------------------------------------------------------------
|
|
604
|
+
// Objects
|
|
605
|
+
// ----------------------------------------------------------------------------
|
|
606
|
+
/**
|
|
607
|
+
* Checks if value is a plain object
|
|
608
|
+
* @param obj - Value to check
|
|
609
|
+
* @returns True if value is a plain object
|
|
610
|
+
*/
|
|
611
|
+
export const isPojo = (obj) => {
|
|
612
|
+
if (obj === null || typeof obj !== "object") {
|
|
613
|
+
return false;
|
|
614
|
+
}
|
|
615
|
+
return Object.getPrototypeOf(obj) === Object.prototype;
|
|
616
|
+
};
|
|
617
|
+
/**
|
|
618
|
+
* Creates new object with only specified keys
|
|
619
|
+
* @param ks - Keys to keep
|
|
620
|
+
* @param x - Source object
|
|
621
|
+
* @returns New object with only specified keys
|
|
622
|
+
*/
|
|
623
|
+
export const pick = (ks, x) => {
|
|
624
|
+
const r = { ...x };
|
|
625
|
+
for (const k of Object.keys(x)) {
|
|
626
|
+
if (!ks.includes(k)) {
|
|
627
|
+
delete r[k];
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
return r;
|
|
631
|
+
};
|
|
632
|
+
/**
|
|
633
|
+
* Creates new object with specified keys removed
|
|
634
|
+
* @param ks - Keys to remove
|
|
635
|
+
* @param x - Source object
|
|
636
|
+
* @returns New object without specified keys
|
|
637
|
+
*/
|
|
638
|
+
export const omit = (ks, x) => {
|
|
639
|
+
const r = { ...x };
|
|
640
|
+
for (const k of ks) {
|
|
641
|
+
delete r[k];
|
|
642
|
+
}
|
|
643
|
+
return r;
|
|
644
|
+
};
|
|
645
|
+
/**
|
|
646
|
+
* Creates new object excluding entries with specified values
|
|
647
|
+
* @param xs - Values to exclude
|
|
648
|
+
* @param x - Source object
|
|
649
|
+
* @returns New object without entries containing specified values
|
|
650
|
+
*/
|
|
651
|
+
export const omitVals = (xs, x) => {
|
|
652
|
+
const r = {};
|
|
653
|
+
for (const [k, v] of Object.entries(x)) {
|
|
654
|
+
if (!xs.includes(v)) {
|
|
655
|
+
r[k] = v;
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
return r;
|
|
659
|
+
};
|
|
660
|
+
/**
|
|
661
|
+
* Filters object values based on predicate
|
|
662
|
+
* @param f - Function to test values
|
|
663
|
+
* @param x - Object to filter
|
|
664
|
+
* @returns Object with only values that pass predicate
|
|
665
|
+
*/
|
|
666
|
+
export const filterVals = (f, x) => {
|
|
667
|
+
const r = {};
|
|
668
|
+
for (const k in x) {
|
|
669
|
+
if (f(x[k])) {
|
|
670
|
+
r[k] = x[k];
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
return r;
|
|
674
|
+
};
|
|
675
|
+
/**
|
|
676
|
+
* Creates new object with transformed keys
|
|
677
|
+
* @param f - Function to transform keys
|
|
678
|
+
* @param x - Source object
|
|
679
|
+
* @returns Object with transformed keys
|
|
680
|
+
*/
|
|
681
|
+
export const mapKeys = (f, x) => {
|
|
682
|
+
const r = {};
|
|
683
|
+
for (const [k, v] of Object.entries(x)) {
|
|
684
|
+
r[f(k)] = v;
|
|
685
|
+
}
|
|
686
|
+
return r;
|
|
687
|
+
};
|
|
688
|
+
/**
|
|
689
|
+
* Creates new object with transformed values
|
|
690
|
+
* @param f - Function to transform values
|
|
691
|
+
* @param x - Source object
|
|
692
|
+
* @returns Object with transformed values
|
|
693
|
+
*/
|
|
694
|
+
export const mapVals = (f, x) => {
|
|
695
|
+
const r = {};
|
|
696
|
+
for (const [k, v] of Object.entries(x)) {
|
|
697
|
+
r[k] = f(v);
|
|
698
|
+
}
|
|
699
|
+
return r;
|
|
700
|
+
};
|
|
701
|
+
/**
|
|
702
|
+
* Merges two objects, with left object taking precedence
|
|
703
|
+
* @param a - Left object
|
|
704
|
+
* @param b - Right object
|
|
705
|
+
* @returns Merged object with a"s properties overriding b"s
|
|
706
|
+
*/
|
|
707
|
+
export const mergeLeft = (a, b) => ({
|
|
708
|
+
...b,
|
|
709
|
+
...a,
|
|
710
|
+
});
|
|
711
|
+
/**
|
|
712
|
+
* Merges two objects, with right object taking precedence
|
|
713
|
+
* @param a - Left object
|
|
714
|
+
* @param b - Right object
|
|
715
|
+
* @returns Merged object with b"s properties overriding a"s
|
|
716
|
+
*/
|
|
717
|
+
export const mergeRight = (a, b) => ({
|
|
718
|
+
...a,
|
|
719
|
+
...b,
|
|
720
|
+
});
|
|
721
|
+
/** Deep merge two objects, prioritizing the first argument. */
|
|
722
|
+
export const deepMergeLeft = (a, b) => deepMergeRight(b, a);
|
|
723
|
+
/** Deep merge two objects, prioritizing the second argument. */
|
|
724
|
+
export const deepMergeRight = (a, b) => {
|
|
725
|
+
a = { ...a };
|
|
726
|
+
for (const [k, v] of Object.entries(b)) {
|
|
727
|
+
if (isPojo(v) && isPojo(a[k])) {
|
|
728
|
+
a[k] = deepMergeRight(a[k], v);
|
|
729
|
+
}
|
|
730
|
+
else {
|
|
731
|
+
a[k] = v;
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
return a;
|
|
735
|
+
};
|
|
736
|
+
/**
|
|
737
|
+
* Switches on key in object, with default fallback
|
|
738
|
+
* @param k - Key to look up
|
|
739
|
+
* @param m - Object with values and optional default
|
|
740
|
+
* @returns Value at key or default value
|
|
741
|
+
*/
|
|
742
|
+
export const switcher = (k, m) => m[k] === undefined ? m.default : m[k];
|
|
743
|
+
// ----------------------------------------------------------------------------
|
|
744
|
+
// Combinators
|
|
745
|
+
// ----------------------------------------------------------------------------
|
|
746
|
+
/** Returns a function that returns the boolean negation of the given function */
|
|
747
|
+
export const complement = (f) => (...args) => !f(...args);
|
|
748
|
+
/**
|
|
749
|
+
* Safely executes function and handles errors
|
|
750
|
+
* @param f - Function to execute
|
|
751
|
+
* @param onError - Optional error handler
|
|
752
|
+
* @returns Function result or undefined if error
|
|
753
|
+
*/
|
|
754
|
+
export const tryCatch = (f, onError) => {
|
|
755
|
+
try {
|
|
756
|
+
const r = f();
|
|
757
|
+
if (r instanceof Promise) {
|
|
758
|
+
r.catch(e => onError?.(e));
|
|
759
|
+
}
|
|
760
|
+
return r;
|
|
761
|
+
}
|
|
762
|
+
catch (e) {
|
|
763
|
+
onError?.(e);
|
|
764
|
+
}
|
|
765
|
+
return undefined;
|
|
766
|
+
};
|
|
767
|
+
/**
|
|
768
|
+
* Creates function that only executes once
|
|
769
|
+
* @param f - Function to wrap
|
|
770
|
+
* @returns Function that executes f only on first call
|
|
771
|
+
*/
|
|
772
|
+
export const once = (f) => {
|
|
773
|
+
let called = false;
|
|
774
|
+
return (...args) => {
|
|
775
|
+
if (!called) {
|
|
776
|
+
called = true;
|
|
777
|
+
f(...args);
|
|
778
|
+
}
|
|
779
|
+
};
|
|
780
|
+
};
|
|
781
|
+
/**
|
|
782
|
+
* Calls a function
|
|
783
|
+
* @param f - Function to call
|
|
784
|
+
* @returns Whatever f returns
|
|
785
|
+
*/
|
|
786
|
+
export const call = (f, ...args) => f();
|
|
787
|
+
/**
|
|
788
|
+
* Memoizes function results based on arguments
|
|
789
|
+
* @param f - Function to memoize
|
|
790
|
+
* @returns Memoized function
|
|
791
|
+
*/
|
|
792
|
+
export const memoize = (f) => {
|
|
793
|
+
let prevArgs;
|
|
794
|
+
let result;
|
|
795
|
+
return (...args) => {
|
|
796
|
+
if (!equals(prevArgs, args)) {
|
|
797
|
+
prevArgs = args;
|
|
798
|
+
result = f(...args);
|
|
799
|
+
}
|
|
800
|
+
return result;
|
|
801
|
+
};
|
|
802
|
+
};
|
|
803
|
+
/**
|
|
804
|
+
* Executes a function if the value is defined
|
|
805
|
+
* @param x - The value to check
|
|
806
|
+
* @param f - Function to execute if x is defined
|
|
807
|
+
* @returns Result of f(x) if x is defined, undefined otherwise
|
|
808
|
+
*/
|
|
809
|
+
export const ifLet = (x, f) => x === undefined ? undefined : f(x);
|
|
810
|
+
// ----------------------------------------------------------------------------
|
|
811
|
+
// Randomness
|
|
812
|
+
// ----------------------------------------------------------------------------
|
|
813
|
+
/**
|
|
814
|
+
* Generates random integer between min and max (inclusive)
|
|
815
|
+
* @param min - Minimum value
|
|
816
|
+
* @param max - Maximum value
|
|
817
|
+
* @returns Random integer
|
|
818
|
+
*/
|
|
819
|
+
export const randomInt = (min = 0, max = 9) => min + Math.round(Math.random() * (max - min));
|
|
820
|
+
/**
|
|
821
|
+
* Generates random string ID
|
|
822
|
+
* @returns Random string suitable for use as an ID
|
|
823
|
+
*/
|
|
824
|
+
export const randomId = () => Math.random().toString().slice(2);
|
|
825
|
+
// ----------------------------------------------------------------------------
|
|
826
|
+
// Async
|
|
827
|
+
// ----------------------------------------------------------------------------
|
|
828
|
+
/**
|
|
829
|
+
* Creates a promise that resolves after specified time
|
|
830
|
+
* @param t - Time in milliseconds
|
|
831
|
+
* @returns Promise that resolves after t milliseconds
|
|
832
|
+
*/
|
|
833
|
+
export const sleep = (t) => new Promise(resolve => setTimeout(resolve, t));
|
|
834
|
+
/**
|
|
835
|
+
* Creates a promise that resolves after the condition completes or timeout
|
|
836
|
+
* @param options - PollOptions
|
|
837
|
+
* @returns void Promise
|
|
838
|
+
*/
|
|
839
|
+
export const poll = ({ interval = 300, condition, signal }) => new Promise(resolve => {
|
|
840
|
+
const int = setInterval(() => {
|
|
841
|
+
if (condition()) {
|
|
842
|
+
resolve();
|
|
843
|
+
clearInterval(int);
|
|
844
|
+
}
|
|
845
|
+
}, interval);
|
|
846
|
+
signal.addEventListener("abort", () => {
|
|
847
|
+
resolve();
|
|
848
|
+
clearInterval(int);
|
|
849
|
+
});
|
|
850
|
+
});
|
|
851
|
+
/**
|
|
852
|
+
* Creates a microtask that yields to other tasks in the event loop
|
|
853
|
+
* @returns Promise that resolves after yielding
|
|
854
|
+
*/
|
|
855
|
+
export const yieldThread = () => {
|
|
856
|
+
if (typeof window !== "undefined" &&
|
|
857
|
+
"scheduler" in window &&
|
|
858
|
+
"yield" in window.scheduler) {
|
|
859
|
+
return window.scheduler.yield();
|
|
860
|
+
}
|
|
861
|
+
return new Promise(resolve => {
|
|
862
|
+
setTimeout(resolve, 0);
|
|
863
|
+
});
|
|
864
|
+
};
|
|
865
|
+
/**
|
|
866
|
+
* Creates throttled version of function
|
|
867
|
+
* @param ms - Minimum time between calls
|
|
868
|
+
* @param f - Function to throttle
|
|
869
|
+
* @returns Throttled function
|
|
870
|
+
*/
|
|
871
|
+
export const throttle = (ms, f) => {
|
|
872
|
+
if (ms === 0) {
|
|
873
|
+
return f;
|
|
874
|
+
}
|
|
875
|
+
let paused = false;
|
|
876
|
+
let nextArgs;
|
|
877
|
+
const unpause = () => {
|
|
878
|
+
if (nextArgs) {
|
|
879
|
+
f(...nextArgs);
|
|
880
|
+
nextArgs = undefined;
|
|
881
|
+
}
|
|
882
|
+
paused = false;
|
|
883
|
+
};
|
|
884
|
+
return (...thisArgs) => {
|
|
885
|
+
if (!paused) {
|
|
886
|
+
f(...thisArgs);
|
|
887
|
+
paused = true;
|
|
888
|
+
setTimeout(unpause, ms);
|
|
889
|
+
}
|
|
890
|
+
else {
|
|
891
|
+
nextArgs = thisArgs;
|
|
892
|
+
}
|
|
893
|
+
};
|
|
894
|
+
};
|
|
895
|
+
/**
|
|
896
|
+
* Creates throttled function that returns cached value
|
|
897
|
+
* @param ms - Minimum time between updates
|
|
898
|
+
* @param f - Function to throttle
|
|
899
|
+
* @returns Function returning latest value
|
|
900
|
+
*/
|
|
901
|
+
export const throttleWithValue = (ms, f) => {
|
|
902
|
+
let value;
|
|
903
|
+
const update = throttle(ms, () => {
|
|
904
|
+
value = f();
|
|
905
|
+
});
|
|
906
|
+
return () => {
|
|
907
|
+
update();
|
|
908
|
+
return value;
|
|
909
|
+
};
|
|
910
|
+
};
|
|
911
|
+
/**
|
|
912
|
+
* Creates batching function that collects items
|
|
913
|
+
* this function does not delay execution, if a series of items is passed in sequence
|
|
914
|
+
* the first item will be processed immediately, and the rest will be batched
|
|
915
|
+
* @param t - Time window for batching
|
|
916
|
+
* @param f - Function to process batch
|
|
917
|
+
* @returns Function that adds items to batch
|
|
918
|
+
*/
|
|
919
|
+
export const batch = (t, f) => {
|
|
920
|
+
const xs = [];
|
|
921
|
+
const cb = throttle(t, () => xs.length > 0 && f(xs.splice(0)));
|
|
922
|
+
return (x) => {
|
|
923
|
+
xs.push(x);
|
|
924
|
+
cb();
|
|
925
|
+
};
|
|
926
|
+
};
|
|
927
|
+
/**
|
|
928
|
+
* Creates batching function that returns results
|
|
929
|
+
* @param t - Time window for batching
|
|
930
|
+
* @param execute - Function to process batch
|
|
931
|
+
* @returns Function that returns promise of result
|
|
932
|
+
*/
|
|
933
|
+
export const batcher = (t, execute) => {
|
|
934
|
+
const queue = [];
|
|
935
|
+
const _execute = async () => {
|
|
936
|
+
const items = queue.splice(0);
|
|
937
|
+
const results = await execute(items.map(item => item.request));
|
|
938
|
+
results.forEach(async (r, i) => {
|
|
939
|
+
if (results.length === items.length) {
|
|
940
|
+
items[i].resolve(await r);
|
|
941
|
+
}
|
|
942
|
+
else {
|
|
943
|
+
items[i].reject("Execute must return a result for each request");
|
|
944
|
+
}
|
|
945
|
+
});
|
|
946
|
+
};
|
|
947
|
+
return (request) => new Promise((resolve, reject) => {
|
|
948
|
+
if (queue.length === 0) {
|
|
949
|
+
setTimeout(_execute, t);
|
|
950
|
+
}
|
|
951
|
+
queue.push({ request, resolve, reject });
|
|
952
|
+
});
|
|
953
|
+
};
|
|
954
|
+
/**
|
|
955
|
+
* Returns a promise that resolves after some proportion of promises complete
|
|
956
|
+
* @param threshold - number between 0 and 1 for how many promises to wait for
|
|
957
|
+
* @param promises - array of promises
|
|
958
|
+
* @returns promise
|
|
959
|
+
*/
|
|
960
|
+
export const race = (threshold, promises) => {
|
|
961
|
+
let count = 0;
|
|
962
|
+
if (threshold === 0) {
|
|
963
|
+
return Promise.resolve();
|
|
964
|
+
}
|
|
965
|
+
return new Promise((resolve, reject) => {
|
|
966
|
+
promises.forEach(p => {
|
|
967
|
+
p.then(() => {
|
|
968
|
+
count++;
|
|
969
|
+
if (count >= threshold * promises.length) {
|
|
970
|
+
resolve();
|
|
971
|
+
}
|
|
972
|
+
}).catch(reject);
|
|
973
|
+
});
|
|
974
|
+
});
|
|
975
|
+
};
|
|
976
|
+
// ----------------------------------------------------------------------------
|
|
977
|
+
// URLs
|
|
978
|
+
// ----------------------------------------------------------------------------
|
|
979
|
+
/**
|
|
980
|
+
* Removes protocol (http://, https://, etc) from URL
|
|
981
|
+
* @param url - URL to process
|
|
982
|
+
* @returns URL without protocol
|
|
983
|
+
*/
|
|
984
|
+
export const stripProtocol = (url) => url.replace(/.*:\/\//, "");
|
|
985
|
+
/**
|
|
986
|
+
* Formats URL for display by removing protocol, www, and trailing slash
|
|
987
|
+
* @param url - URL to format
|
|
988
|
+
* @returns Formatted URL
|
|
989
|
+
*/
|
|
990
|
+
export const displayUrl = (url) => stripProtocol(url)
|
|
991
|
+
.replace(/^(www\.)?/i, "")
|
|
992
|
+
.replace(/\/$/, "");
|
|
993
|
+
/**
|
|
994
|
+
* Extracts and formats domain from URL
|
|
995
|
+
* @param url - URL to process
|
|
996
|
+
* @returns Formatted domain name
|
|
997
|
+
*/
|
|
998
|
+
export const displayDomain = (url) => displayUrl(first(url.split(/[\/\?]/)) || "");
|
|
999
|
+
// ----------------------------------------------------------------------------
|
|
1000
|
+
// JSON, localStorage, fetch, event emitters, etc
|
|
1001
|
+
// ----------------------------------------------------------------------------
|
|
1002
|
+
/**
|
|
1003
|
+
* Safely parses JSON string
|
|
1004
|
+
* @param json - JSON string to parse
|
|
1005
|
+
* @returns Parsed object or null if invalid
|
|
1006
|
+
*/
|
|
1007
|
+
export const parseJson = (json) => {
|
|
1008
|
+
if (!json)
|
|
1009
|
+
return undefined;
|
|
1010
|
+
try {
|
|
1011
|
+
return JSON.parse(json);
|
|
1012
|
+
}
|
|
1013
|
+
catch (e) {
|
|
1014
|
+
return undefined;
|
|
1015
|
+
}
|
|
1016
|
+
};
|
|
1017
|
+
/**
|
|
1018
|
+
* Gets and parses JSON from localStorage
|
|
1019
|
+
* @param k - Storage key
|
|
1020
|
+
* @returns Parsed value or undefined if invalid/missing
|
|
1021
|
+
*/
|
|
1022
|
+
export const getJson = (k) => parseJson(localStorage.getItem(k) || "");
|
|
1023
|
+
/**
|
|
1024
|
+
* Stringifies and stores value in localStorage
|
|
1025
|
+
* @param k - Storage key
|
|
1026
|
+
* @param v - Value to store
|
|
1027
|
+
*/
|
|
1028
|
+
export const setJson = (k, v) => localStorage.setItem(k, JSON.stringify(v));
|
|
1029
|
+
/**
|
|
1030
|
+
* Fetches JSON from URL with options
|
|
1031
|
+
* @param url - URL to fetch from
|
|
1032
|
+
* @param opts - Fetch options
|
|
1033
|
+
* @returns Promise of parsed JSON response
|
|
1034
|
+
*/
|
|
1035
|
+
export const fetchJson = async (url, opts = {}) => {
|
|
1036
|
+
if (!opts.headers) {
|
|
1037
|
+
opts.headers = {};
|
|
1038
|
+
}
|
|
1039
|
+
if (!opts.headers["Accept"]) {
|
|
1040
|
+
opts.headers["Accept"] = "application/json";
|
|
1041
|
+
}
|
|
1042
|
+
const res = await fetch(url, opts);
|
|
1043
|
+
const json = await res.json();
|
|
1044
|
+
return json;
|
|
1045
|
+
};
|
|
1046
|
+
/**
|
|
1047
|
+
* Posts JSON data to URL
|
|
1048
|
+
* @param url - URL to post to
|
|
1049
|
+
* @param data - Data to send
|
|
1050
|
+
* @param opts - Additional fetch options
|
|
1051
|
+
* @returns Promise of parsed JSON response
|
|
1052
|
+
*/
|
|
1053
|
+
export const postJson = async (url, data, opts = {}) => {
|
|
1054
|
+
if (!opts.method) {
|
|
1055
|
+
opts.method = "POST";
|
|
1056
|
+
}
|
|
1057
|
+
if (!opts.headers) {
|
|
1058
|
+
opts.headers = {};
|
|
1059
|
+
}
|
|
1060
|
+
opts.headers["Content-Type"] = "application/json";
|
|
1061
|
+
opts.body = JSON.stringify(data);
|
|
1062
|
+
return fetchJson(url, opts);
|
|
1063
|
+
};
|
|
1064
|
+
/**
|
|
1065
|
+
* Uploads file to URL
|
|
1066
|
+
* @param url - Upload URL
|
|
1067
|
+
* @param file - File to upload
|
|
1068
|
+
* @returns Promise of parsed JSON response
|
|
1069
|
+
*/
|
|
1070
|
+
export const uploadFile = (url, file) => {
|
|
1071
|
+
const body = new FormData();
|
|
1072
|
+
body.append("file", file);
|
|
1073
|
+
return fetchJson(url, { method: "POST", body });
|
|
1074
|
+
};
|
|
1075
|
+
/**
|
|
1076
|
+
* A generic type-safe event listener function that works with event emitters.
|
|
1077
|
+
*
|
|
1078
|
+
* @param target - The event target object with add/remove listener methods
|
|
1079
|
+
* @param eventName - The name of the event to listen for
|
|
1080
|
+
* @param callback - The callback function to execute when the event occurs
|
|
1081
|
+
* @returns A function that removes the event listener when called
|
|
1082
|
+
*/
|
|
1083
|
+
export const on = (target, eventName, callback) => {
|
|
1084
|
+
target.on(eventName, callback);
|
|
1085
|
+
return () => {
|
|
1086
|
+
target.off(eventName, callback);
|
|
1087
|
+
};
|
|
1088
|
+
};
|
|
1089
|
+
// ----------------------------------------------------------------------------
|
|
1090
|
+
// Strings
|
|
1091
|
+
// ----------------------------------------------------------------------------
|
|
1092
|
+
/**
|
|
1093
|
+
* Truncates string to length, breaking at word boundaries
|
|
1094
|
+
* @param s - String to truncate
|
|
1095
|
+
* @param l - Maximum length
|
|
1096
|
+
* @param suffix - String to append if truncated
|
|
1097
|
+
* @returns Truncated string
|
|
1098
|
+
*/
|
|
1099
|
+
export const ellipsize = (s, l, suffix = "...") => {
|
|
1100
|
+
if (s.length < l * 1.1) {
|
|
1101
|
+
return s;
|
|
1102
|
+
}
|
|
1103
|
+
while (s.length > l && s.includes(" ")) {
|
|
1104
|
+
s = s.split(" ").slice(0, -1).join(" ");
|
|
1105
|
+
}
|
|
1106
|
+
return s + suffix;
|
|
1107
|
+
};
|
|
1108
|
+
/** Displays a list of items with oxford commas and a chosen conjunction */
|
|
1109
|
+
export const displayList = (xs, conj = "and", n = 6) => {
|
|
1110
|
+
if (xs.length > n + 2) {
|
|
1111
|
+
return `${xs.slice(0, n).join(", ")}, ${conj} ${xs.length - n} others`;
|
|
1112
|
+
}
|
|
1113
|
+
if (xs.length < 3) {
|
|
1114
|
+
return xs.join(` ${conj} `);
|
|
1115
|
+
}
|
|
1116
|
+
return `${xs.slice(0, -1).join(", ")}, ${conj} ${xs.slice(-1).join("")}`;
|
|
1117
|
+
};
|
|
1118
|
+
/** Generates a hash string from input string */
|
|
1119
|
+
export const hash = (s) => Math.abs(s.split("").reduce((a, b) => ((a << 5) - a + b.charCodeAt(0)) | 0, 0)).toString();
|
|
1120
|
+
// ----------------------------------------------------------------------------
|
|
1121
|
+
// Curried utilities for working with collections
|
|
1122
|
+
// ----------------------------------------------------------------------------
|
|
1123
|
+
/** Returns a function that gets the nth element of an array */
|
|
1124
|
+
export const nth = (i) => (xs, ...args) => xs[i];
|
|
1125
|
+
/** Returns a function that checks if nth element equals value */
|
|
1126
|
+
export const nthEq = (i, v) => (xs, ...args) => xs[i] === v;
|
|
1127
|
+
/** Returns a function that checks if nth element does not equal value */
|
|
1128
|
+
export const nthNe = (i, v) => (xs, ...args) => xs[i] !== v;
|
|
1129
|
+
/** Returns a function that checks if key/value pairs of x match all pairs in spec */
|
|
1130
|
+
export const spec = (values) => (x, ...args) => {
|
|
1131
|
+
if (Array.isArray(values)) {
|
|
1132
|
+
for (let i = 0; i < values.length; i++) {
|
|
1133
|
+
if (x[i] !== values[i]) {
|
|
1134
|
+
return false;
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
else {
|
|
1139
|
+
for (const [k, v] of Object.entries(values)) {
|
|
1140
|
+
if (x[k] !== v)
|
|
1141
|
+
return false;
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
return true;
|
|
1145
|
+
};
|
|
1146
|
+
/** Returns a function that checks equality with value */
|
|
1147
|
+
export const eq = (v) => (x, ...args) => x === v;
|
|
1148
|
+
/** Returns a function that checks inequality with value */
|
|
1149
|
+
export const ne = (v) => (x, ...args) => x !== v;
|
|
1150
|
+
/** Returns a function that gets property value from object */
|
|
1151
|
+
export const prop = (k) => (x) => x[k];
|
|
1152
|
+
/** Returns a function that adds/updates a property on object */
|
|
1153
|
+
export const assoc = (k, v) => (o) => ({ ...o, [k]: v });
|
|
1154
|
+
/** Returns a function that removes a property on object */
|
|
1155
|
+
export const dissoc = (k) => (o) => omit([k], o);
|
|
1156
|
+
/** Returns a function that checks whether a value is in the given sequence */
|
|
1157
|
+
export const member = (xs) => (x) => Array.from(xs).includes(x);
|
|
1158
|
+
// ----------------------------------------------------------------------------
|
|
1159
|
+
// Sets
|
|
1160
|
+
// ----------------------------------------------------------------------------
|
|
1161
|
+
/**
|
|
1162
|
+
* Adds value to Set at key in object
|
|
1163
|
+
* @param m - Object mapping keys to Sets
|
|
1164
|
+
* @param k - Key to add to
|
|
1165
|
+
* @param v - Value to add
|
|
1166
|
+
*/
|
|
1167
|
+
export const addToKey = (m, k, v) => {
|
|
1168
|
+
const s = m[k] || new Set();
|
|
1169
|
+
s.add(v);
|
|
1170
|
+
m[k] = s;
|
|
1171
|
+
};
|
|
1172
|
+
/**
|
|
1173
|
+
* Pushes value to array at key in object
|
|
1174
|
+
* @param m - Object mapping keys to arrays
|
|
1175
|
+
* @param k - Key to push to
|
|
1176
|
+
* @param v - Value to push
|
|
1177
|
+
*/
|
|
1178
|
+
export const pushToKey = (m, k, v) => {
|
|
1179
|
+
const a = m[k] || [];
|
|
1180
|
+
a.push(v);
|
|
1181
|
+
m[k] = a;
|
|
1182
|
+
};
|
|
1183
|
+
// ----------------------------------------------------------------------------
|
|
1184
|
+
// Maps
|
|
1185
|
+
// ----------------------------------------------------------------------------
|
|
1186
|
+
/**
|
|
1187
|
+
* Adds value to Set at key in Map
|
|
1188
|
+
* @param m - Map of Sets
|
|
1189
|
+
* @param k - Key to add to
|
|
1190
|
+
* @param v - Value to add
|
|
1191
|
+
*/
|
|
1192
|
+
export const addToMapKey = (m, k, v) => {
|
|
1193
|
+
const s = m.get(k) || new Set();
|
|
1194
|
+
s.add(v);
|
|
1195
|
+
m.set(k, s);
|
|
1196
|
+
};
|
|
1197
|
+
/**
|
|
1198
|
+
* Pushes value to array at key in Map
|
|
1199
|
+
* @param m - Map of arrays
|
|
1200
|
+
* @param k - Key to push to
|
|
1201
|
+
* @param v - Value to push
|
|
1202
|
+
*/
|
|
1203
|
+
export const pushToMapKey = (m, k, v) => {
|
|
1204
|
+
const a = m.get(k) || [];
|
|
1205
|
+
a.push(v);
|
|
1206
|
+
m.set(k, a);
|
|
1207
|
+
};
|
|
1208
|
+
// ----------------------------------------------------------------------------
|
|
1209
|
+
// Bech32 <-> hex encoding
|
|
1210
|
+
// ----------------------------------------------------------------------------
|
|
1211
|
+
/**
|
|
1212
|
+
* Converts hex string to bech32 format
|
|
1213
|
+
* @param prefix - Bech32 prefix
|
|
1214
|
+
* @param hex - Hex string to convert
|
|
1215
|
+
* @returns Bech32 encoded string
|
|
1216
|
+
*/
|
|
1217
|
+
export const hexToBech32 = (prefix, hex) => bech32.encode(prefix, bech32.toWords(utf8.decode(hex)), false);
|
|
1218
|
+
/**
|
|
1219
|
+
* Converts bech32 string to hex format
|
|
1220
|
+
* @param b32 - Bech32 string to convert
|
|
1221
|
+
* @returns Hex encoded string
|
|
1222
|
+
*/
|
|
1223
|
+
export const bech32ToHex = (b32) => utf8.encode(bech32.fromWords(bech32.decode(b32, false).words));
|
|
1224
|
+
//# sourceMappingURL=Tools.js.map
|