@welshman/lib 0.1.0 → 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.
Files changed (45) hide show
  1. package/LICENSE +21 -0
  2. package/{build/src → dist}/Deferred.d.ts +1 -0
  3. package/dist/Deferred.d.ts.map +1 -0
  4. package/dist/Deferred.js.map +1 -0
  5. package/{build/src → dist}/Emitter.d.ts +1 -0
  6. package/dist/Emitter.d.ts.map +1 -0
  7. package/dist/Emitter.js.map +1 -0
  8. package/{build/src → dist}/LRUCache.d.ts +1 -0
  9. package/dist/LRUCache.d.ts.map +1 -0
  10. package/dist/LRUCache.js.map +1 -0
  11. package/dist/TaskQueue.d.ts +18 -0
  12. package/dist/TaskQueue.d.ts.map +1 -0
  13. package/dist/TaskQueue.js +47 -0
  14. package/dist/TaskQueue.js.map +1 -0
  15. package/{build/src → dist}/Tools.d.ts +352 -335
  16. package/dist/Tools.d.ts.map +1 -0
  17. package/{build/src → dist}/Tools.js +582 -494
  18. package/dist/Tools.js.map +1 -0
  19. package/{build/src → dist}/index.d.ts +2 -6
  20. package/dist/index.d.ts.map +1 -0
  21. package/{build/src → dist}/index.js +1 -2
  22. package/dist/index.js.map +1 -0
  23. package/dist/normalize-url/index.d.ts +286 -0
  24. package/dist/normalize-url/index.d.ts.map +1 -0
  25. package/{build/src → dist}/normalize-url/index.js +53 -51
  26. package/dist/normalize-url/index.js.map +1 -0
  27. package/package.json +17 -17
  28. package/README.md +0 -13
  29. package/build/src/Context.d.ts +0 -20
  30. package/build/src/Context.js +0 -20
  31. package/build/src/Context.js.map +0 -1
  32. package/build/src/Deferred.js.map +0 -1
  33. package/build/src/Emitter.js.map +0 -1
  34. package/build/src/LRUCache.js.map +0 -1
  35. package/build/src/Tools.js.map +0 -1
  36. package/build/src/Worker.d.ts +0 -55
  37. package/build/src/Worker.js +0 -119
  38. package/build/src/Worker.js.map +0 -1
  39. package/build/src/index.js.map +0 -1
  40. package/build/src/normalize-url/index.d.ts +0 -285
  41. package/build/src/normalize-url/index.js.map +0 -1
  42. package/build/tsconfig.tsbuildinfo +0 -1
  43. /package/{build/src → dist}/Deferred.js +0 -0
  44. /package/{build/src → dist}/Emitter.js +0 -0
  45. /package/{build/src → dist}/LRUCache.js +0 -0
@@ -1,33 +1,9 @@
1
1
  import { bech32, utf8 } from "@scure/base";
2
- /** Checks if a value is null or undefined */
3
- export const isNil = (x) => [null, undefined].includes(x);
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
- /** Returns a function that returns the boolean negation of the given function */
50
- export const complement = (f) => (...args) => !f(...args);
51
- /** Converts a `Maybe<number>` to a number, defaulting to 0 */
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 => !isNil(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);
@@ -84,203 +111,126 @@ 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
113
  /**
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
114
+ * Checks if a number is between two values (exclusive)
115
+ * @param bounds - Lower and upper bounds
116
+ * @param n - Number to check
117
+ * @returns True if n is between low and high
98
118
  */
99
- export const take = (n, xs) => xs.slice(0, n);
119
+ export const between = ([low, high], n) => n > low && n < high;
100
120
  /**
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
121
+ * Checks if a number is between two values (inclusive)
122
+ * @param bounds - Lower and upper bounds
123
+ * @param n - Number to check
124
+ * @returns True if n is between low and high
105
125
  */
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
- };
126
+ export const within = ([low, high], n) => n >= low && n <= high;
113
127
  /**
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
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
118
132
  */
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
- };
133
+ export const clamp = ([min, max], n) => Math.min(max, Math.max(min, n));
128
134
  /**
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
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
133
139
  */
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
- };
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;
143
158
  /**
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
159
+ * Multiplies time unit by count
160
+ * @param unit - Time unit in seconds
161
+ * @param count - Number of units
162
+ * @returns Total seconds
149
163
  */
150
- export function* range(a, b, step = 1) {
151
- for (let i = a; i < b; i += step) {
152
- yield i;
153
- }
154
- }
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);
155
167
  /**
156
- * Yields indexed items
157
- * @param items - A collection of items
158
- * @yields tuples of [index, item]
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
159
172
  */
160
- export function* enumerate(items) {
161
- for (let i = 0; i < items.length; i += 1) {
162
- yield [i, items[i]];
163
- }
164
- }
173
+ export const ago = (unit, count = 1) => now() - int(unit, count);
165
174
  /**
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
175
+ * Converts seconds to milliseconds
176
+ * @param seconds - Time in seconds
177
+ * @returns Time in milliseconds
170
178
  */
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
- };
179
+ export const ms = (seconds) => seconds * 1000;
180
+ // ----------------------------------------------------------------------------
181
+ // Sequences
182
+ // ----------------------------------------------------------------------------
178
183
  /**
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
184
+ * Returns the first element of an array
185
+ * @param xs - The array
186
+ * @returns First element or undefined
183
187
  */
184
- export const mapVals = (f, x) => {
185
- const r = {};
186
- for (const [k, v] of Object.entries(x)) {
187
- r[k] = f(v);
188
+ export const first = (xs, ...args) => {
189
+ for (const x of xs) {
190
+ return x;
188
191
  }
189
- return r;
190
192
  };
191
193
  /**
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
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
206
197
  */
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;
198
+ export const ffirst = (xs, ...args) => {
199
+ for (const chunk of xs) {
200
+ for (const x of chunk) {
201
+ return x;
222
202
  }
223
203
  }
224
- return a;
225
204
  };
226
205
  /**
227
- * Checks if a number is between two values (exclusive)
228
- * @param bounds - Lower and upper bounds
229
- * @param n - Number to check
230
- * @returns True if n is between low and high
231
- */
232
- export const between = ([low, high], n) => n > low && n < high;
233
- /**
234
- * Checks if a number is between two values (inclusive)
235
- * @param bounds - Lower and upper bounds
236
- * @param n - Number to check
237
- * @returns True if n is between low and high
238
- */
239
- export const within = ([low, high], n) => n >= low && n <= high;
240
- /**
241
- * Generates random integer between min and max (inclusive)
242
- * @param min - Minimum value
243
- * @param max - Maximum value
244
- * @returns Random integer
245
- */
246
- export const randomInt = (min = 0, max = 9) => min + Math.round(Math.random() * (max - min));
247
- /**
248
- * Generates random string ID
249
- * @returns Random string suitable for use as an ID
250
- */
251
- export const randomId = () => Math.random().toString().slice(2);
252
- /**
253
- * Removes protocol (http://, https://, etc) from URL
254
- * @param url - URL to process
255
- * @returns URL without protocol
256
- */
257
- export const stripProtocol = (url) => url.replace(/.*:\/\//, "");
258
- /**
259
- * Formats URL for display by removing protocol, www, and trailing slash
260
- * @param url - URL to format
261
- * @returns Formatted URL
206
+ * Returns the last element of an array
207
+ * @param xs - The array
208
+ * @returns Last element or undefined
262
209
  */
263
- export const displayUrl = (url) => stripProtocol(url)
264
- .replace(/^(www\.)?/i, "")
265
- .replace(/\/$/, "");
210
+ export const last = (xs, ...args) => {
211
+ const a = Array.from(xs);
212
+ return a[a.length - 1];
213
+ };
266
214
  /**
267
- * Extracts and formats domain from URL
268
- * @param url - URL to process
269
- * @returns Formatted domain name
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
270
219
  */
271
- export const displayDomain = (url) => displayUrl(first(url.split(/[\/\?]/)));
220
+ export const drop = (n, xs) => Array.from(xs).slice(n);
272
221
  /**
273
- * Creates a promise that resolves after specified time
274
- * @param t - Time in milliseconds
275
- * @returns Promise that resolves after t milliseconds
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
276
226
  */
277
- export const sleep = (t) => new Promise(resolve => setTimeout(resolve, t));
227
+ export const take = (n, xs) => Array.from(xs).slice(0, n);
278
228
  /**
279
229
  * Concatenates multiple arrays, filtering out null/undefined
280
230
  * @param xs - Arrays to concatenate
281
231
  * @returns Combined array
282
232
  */
283
- export const concat = (...xs) => xs.flatMap(x => (isNil(x) ? [] : x));
233
+ export const concat = (...xs) => xs.flatMap(x => (x === undefined ? [] : x));
284
234
  /**
285
235
  * Appends element to array
286
236
  * @param x - Element to append
@@ -319,213 +269,45 @@ export const difference = (a, b) => {
319
269
  * Removes all instances of an element from array
320
270
  * @param a - Element to remove
321
271
  * @param xs - Source array
322
- * @returns New array with element removed
323
- */
324
- export const remove = (a, xs) => xs.filter(x => x !== a);
325
- /**
326
- * Returns elements from second array not present in first
327
- * @param a - Array of elements to exclude
328
- * @param b - Source array
329
- * @returns Filtered array
330
- */
331
- export const without = (a, b) => b.filter(x => !a.includes(x));
332
- /**
333
- * Toggles presence of element in array
334
- * @param x - Element to toggle
335
- * @param xs - Source array
336
- * @returns New array with element added or removed
337
- */
338
- export const toggle = (x, xs) => (xs.includes(x) ? remove(x, xs) : append(x, xs));
339
- /**
340
- * Constrains number between min and max values
341
- * @param bounds - Minimum and maximum allowed values
342
- * @param n - Number to clamp
343
- * @returns Clamped value
344
- */
345
- export const clamp = ([min, max], n) => Math.min(max, Math.max(min, n));
346
- /**
347
- * Round a number to the nearest float precision
348
- * @param precision - Number of decimal places
349
- * @param x - Number to round
350
- * @returns Formatted number
351
- */
352
- export const round = (precision, x) => Math.round(x * Math.pow(10, precision)) / Math.pow(10, precision);
353
- /**
354
- * Safely parses JSON string
355
- * @param json - JSON string to parse
356
- * @returns Parsed object or null if invalid
357
- */
358
- export const parseJson = (json) => {
359
- if (!json)
360
- return undefined;
361
- try {
362
- return JSON.parse(json);
363
- }
364
- catch (e) {
365
- return undefined;
366
- }
367
- };
368
- /**
369
- * Gets and parses JSON from localStorage
370
- * @param k - Storage key
371
- * @returns Parsed value or undefined if invalid/missing
372
- */
373
- export const getJson = (k) => parseJson(localStorage.getItem(k) || "");
374
- /**
375
- * Stringifies and stores value in localStorage
376
- * @param k - Storage key
377
- * @param v - Value to store
378
- */
379
- export const setJson = (k, v) => localStorage.setItem(k, JSON.stringify(v));
380
- /**
381
- * Safely executes function and handles errors
382
- * @param f - Function to execute
383
- * @param onError - Optional error handler
384
- * @returns Function result or undefined if error
385
- */
386
- export const tryCatch = (f, onError) => {
387
- try {
388
- const r = f();
389
- if (r instanceof Promise) {
390
- r.catch(e => onError?.(e));
391
- }
392
- return r;
393
- }
394
- catch (e) {
395
- onError?.(e);
396
- }
397
- return undefined;
398
- };
399
- /**
400
- * Truncates string to length, breaking at word boundaries
401
- * @param s - String to truncate
402
- * @param l - Maximum length
403
- * @param suffix - String to append if truncated
404
- * @returns Truncated string
405
- */
406
- export const ellipsize = (s, l, suffix = "...") => {
407
- if (s.length < l * 1.1) {
408
- return s;
409
- }
410
- while (s.length > l && s.includes(" ")) {
411
- s = s.split(" ").slice(0, -1).join(" ");
412
- }
413
- return s + suffix;
414
- };
415
- /**
416
- * Checks if value is a plain object
417
- * @param obj - Value to check
418
- * @returns True if value is a plain object
419
- */
420
- export const isPojo = (obj) => {
421
- if (obj === null || typeof obj !== "object") {
422
- return false;
423
- }
424
- return Object.getPrototypeOf(obj) === Object.prototype;
425
- };
426
- /**
427
- * Deep equality comparison
428
- * @param a - First value
429
- * @param b - Second value
430
- * @returns True if values are deeply equal
431
- */
432
- export const equals = (a, b) => {
433
- if (a === b)
434
- return true;
435
- if (a instanceof Set && b instanceof Set) {
436
- a = Array.from(a);
437
- b = Array.from(b);
438
- }
439
- if (a instanceof Set) {
440
- if (!(b instanceof Set) || a.size !== b.size) {
441
- return false;
442
- }
443
- return Array.from(a).every(x => b.has(x));
444
- }
445
- if (Array.isArray(a)) {
446
- if (!Array.isArray(b) || a.length !== b.length) {
447
- return false;
448
- }
449
- for (let i = 0; i < a.length; i++) {
450
- if (!equals(a[i], b[i])) {
451
- return false;
452
- }
453
- }
454
- return true;
455
- }
456
- if (isPojo(a)) {
457
- if (!isPojo(b)) {
458
- return false;
459
- }
460
- const aKeys = Object.keys(a);
461
- const bKeys = Object.keys(b);
462
- if (aKeys.length !== bKeys.length) {
463
- return false;
464
- }
465
- for (const k of aKeys) {
466
- if (!equals(a[k], b[k])) {
467
- return false;
468
- }
469
- }
470
- return true;
471
- }
472
- return false;
473
- };
474
- // Curried utils
475
- /** Returns a function that gets the nth element of an array */
476
- export const nth = (i) => (xs, ...args) => xs[i];
477
- /** Returns a function that checks if nth element equals value */
478
- export const nthEq = (i, v) => (xs, ...args) => xs[i] === v;
479
- /** Returns a function that checks if nth element does not equal value */
480
- export const nthNe = (i, v) => (xs, ...args) => xs[i] !== v;
481
- /** Returns a function that checks if key/value pairs of x match all pairs in spec */
482
- export const spec = (values) => (x, ...args) => {
483
- if (Array.isArray(values)) {
484
- for (let i = 0; i < values.length; i++) {
485
- if (x[i] !== values[i]) {
486
- return false;
487
- }
488
- }
272
+ * @returns New array with element removed
273
+ */
274
+ export const remove = (a, xs) => xs.filter(x => x !== a);
275
+ /**
276
+ * Returns elements from second array not present in first
277
+ * @param a - Array of elements to exclude
278
+ * @param b - Source array
279
+ * @returns Filtered array
280
+ */
281
+ export const without = (a, b) => b.filter(x => !a.includes(x));
282
+ /**
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;
489
299
  }
490
- else {
491
- for (const [k, v] of Object.entries(values)) {
492
- if (x[k] !== v)
493
- return false;
494
- }
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]];
495
309
  }
496
- return true;
497
- };
498
- /** Returns a function that checks equality with value */
499
- export const eq = (v) => (x) => x === v;
500
- /** Returns a function that checks inequality with value */
501
- export const ne = (v) => (x) => x !== v;
502
- /** Returns a function that gets property value from object */
503
- export const prop = (k) => (x) => x[k];
504
- /** Returns a function that adds/updates a property on object */
505
- export const assoc = (k, v) => (o) => ({ ...o, [k]: v });
506
- /** Returns a function that removes a property on object */
507
- export const dissoc = (k) => (o) => omit([k], o);
508
- /** Generates a hash string from input string */
509
- export const hash = (s) => Math.abs(s.split("").reduce((a, b) => ((a << 5) - a + b.charCodeAt(0)) | 0, 0)).toString();
510
- // Collections
511
- /** Splits array into two parts at index */
512
- export const splitAt = (n, xs) => [xs.slice(0, n), xs.slice(n)];
513
- /** Inserts element into array at index */
514
- export const insert = (n, x, xs) => [...xs.slice(0, n), x, ...xs.slice(n)];
515
- /** Returns random element from array */
516
- export const choice = (xs) => xs[Math.floor(xs.length * Math.random())];
517
- /** Returns shuffled copy of iterable */
518
- export const shuffle = (xs) => Array.from(xs).sort(() => (Math.random() > 0.5 ? 1 : -1));
519
- /** Returns n random elements from array */
520
- export const sample = (n, xs) => shuffle(xs).slice(0, n);
521
- /** Checks if value is iterable */
522
- export const isIterable = (x) => Symbol.iterator in Object(x);
523
- /** Ensures value is iterable by wrapping in array if needed */
524
- export const toIterable = (x) => (isIterable(x) ? x : [x]);
525
- /** Ensures value is array by wrapping if needed */
526
- export const ensurePlural = (x) => (x instanceof Array ? x : [x]);
527
- /** Converts string or number to number */
528
- export const ensureNumber = (x) => parseFloat(x);
310
+ }
529
311
  /** Returns a function that gets property value from object */
530
312
  export const pluck = (k, xs) => xs.map(x => x[k]);
531
313
  /**
@@ -542,21 +324,6 @@ export const fromPairs = (pairs) => {
542
324
  }
543
325
  return r;
544
326
  };
545
- /**
546
- * Filters object values based on predicate
547
- * @param f - Function to test values
548
- * @param x - Object to filter
549
- * @returns Object with only values that pass predicate
550
- */
551
- export const filterVals = (f, x) => {
552
- const r = {};
553
- for (const k in x) {
554
- if (f(x[k])) {
555
- r[k] = x[k];
556
- }
557
- }
558
- return r;
559
- };
560
327
  /**
561
328
  * Flattens array of arrays into single array
562
329
  * @param xs - Array of arrays to flatten
@@ -698,12 +465,192 @@ export const chunk = (chunkLength, xs) => {
698
465
  * @param xs - Array to split
699
466
  * @returns Array of n chunks
700
467
  */
701
- export const chunks = (n, xs) => {
702
- const result = initArray(n, () => []);
703
- for (let i = 0; i < xs.length; i++) {
704
- result[i % n].push(xs[i]);
468
+ export const chunks = (n, xs) => {
469
+ const result = initArray(n, () => []);
470
+ for (let i = 0; i < xs.length; i++) {
471
+ result[i % n].push(xs[i]);
472
+ }
473
+ return result;
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;
705
649
  }
706
- return result;
650
+ catch (e) {
651
+ onError?.(e);
652
+ }
653
+ return undefined;
707
654
  };
708
655
  /**
709
656
  * Creates function that only executes once
@@ -719,6 +666,12 @@ export const once = (f) => {
719
666
  }
720
667
  };
721
668
  };
669
+ /**
670
+ * Calls a function
671
+ * @param f - Function to call
672
+ * @returns Whatever f returns
673
+ */
674
+ export const call = (f, ...args) => f();
722
675
  /**
723
676
  * Memoizes function results based on arguments
724
677
  * @param f - Function to memoize
@@ -735,6 +688,51 @@ export const memoize = (f) => {
735
688
  return result;
736
689
  };
737
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));
722
+ /**
723
+ * Creates a microtask that yields to other tasks in the event loop
724
+ * @returns Promise that resolves after yielding
725
+ */
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
+ });
735
+ };
738
736
  /**
739
737
  * Creates throttled version of function
740
738
  * @param ms - Minimum time between calls
@@ -824,93 +822,59 @@ export const batcher = (t, execute) => {
824
822
  queue.push({ request, resolve, reject });
825
823
  });
826
824
  };
825
+ // ----------------------------------------------------------------------------
826
+ // URLs
827
+ // ----------------------------------------------------------------------------
827
828
  /**
828
- * Adds value to Set at key in object
829
- * @param m - Object mapping keys to Sets
830
- * @param k - Key to add to
831
- * @param v - Value to add
829
+ * Removes protocol (http://, https://, etc) from URL
830
+ * @param url - URL to process
831
+ * @returns URL without protocol
832
832
  */
833
- export const addToKey = (m, k, v) => {
834
- const s = m[k] || new Set();
835
- s.add(v);
836
- m[k] = s;
837
- };
833
+ export const stripProtocol = (url) => url.replace(/.*:\/\//, "");
838
834
  /**
839
- * Pushes value to array at key in object
840
- * @param m - Object mapping keys to arrays
841
- * @param k - Key to push to
842
- * @param v - Value to push
835
+ * Formats URL for display by removing protocol, www, and trailing slash
836
+ * @param url - URL to format
837
+ * @returns Formatted URL
843
838
  */
844
- export const pushToKey = (m, k, v) => {
845
- const a = m[k] || [];
846
- a.push(v);
847
- m[k] = a;
848
- };
839
+ export const displayUrl = (url) => stripProtocol(url)
840
+ .replace(/^(www\.)?/i, "")
841
+ .replace(/\/$/, "");
849
842
  /**
850
- * Adds value to Set at key in Map
851
- * @param m - Map of Sets
852
- * @param k - Key to add to
853
- * @param v - Value to add
843
+ * Extracts and formats domain from URL
844
+ * @param url - URL to process
845
+ * @returns Formatted domain name
854
846
  */
855
- export const addToMapKey = (m, k, v) => {
856
- const s = m.get(k) || new Set();
857
- s.add(v);
858
- m.set(k, s);
859
- };
847
+ export const displayDomain = (url) => displayUrl(first(url.split(/[\/\?]/)) || "");
848
+ // ----------------------------------------------------------------------------
849
+ // JSON, localStorage, fetch, event emitters, etc
850
+ // ----------------------------------------------------------------------------
860
851
  /**
861
- * Pushes value to array at key in Map
862
- * @param m - Map of arrays
863
- * @param k - Key to push to
864
- * @param v - Value to push
852
+ * Safely parses JSON string
853
+ * @param json - JSON string to parse
854
+ * @returns Parsed object or null if invalid
865
855
  */
866
- export const pushToMapKey = (m, k, v) => {
867
- const a = m.get(k) || [];
868
- a.push(v);
869
- m.set(k, a);
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
+ }
870
865
  };
871
866
  /**
872
- * Switches on key in object, with default fallback
873
- * @param k - Key to look up
874
- * @param m - Object with values and optional default
875
- * @returns Value at key or default value
876
- */
877
- export const switcher = (k, m) => m[k] === undefined ? m.default : m[k];
878
- /** One minute in seconds */
879
- export const MINUTE = 60;
880
- /** One hour in seconds */
881
- export const HOUR = 60 * MINUTE;
882
- /** One day in seconds */
883
- export const DAY = 24 * HOUR;
884
- /** One week in seconds */
885
- export const WEEK = 7 * DAY;
886
- /** One month in seconds (approximate) */
887
- export const MONTH = 30 * DAY;
888
- /** One quarter in seconds (approximate) */
889
- export const QUARTER = 90 * DAY;
890
- /** One year in seconds (approximate) */
891
- export const YEAR = 365 * DAY;
892
- /**
893
- * Multiplies time unit by count
894
- * @param unit - Time unit in seconds
895
- * @param count - Number of units
896
- * @returns Total seconds
897
- */
898
- export const int = (unit, count = 1) => unit * count;
899
- /** Returns current Unix timestamp in seconds */
900
- export const now = () => Math.round(Date.now() / 1000);
901
- /**
902
- * Returns Unix timestamp from specified time ago
903
- * @param unit - Time unit in seconds
904
- * @param count - Number of units
905
- * @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
906
870
  */
907
- export const ago = (unit, count = 1) => now() - int(unit, count);
871
+ export const getJson = (k) => parseJson(localStorage.getItem(k) || "");
908
872
  /**
909
- * Converts seconds to milliseconds
910
- * @param seconds - Time in seconds
911
- * @returns Time in milliseconds
873
+ * Stringifies and stores value in localStorage
874
+ * @param k - Storage key
875
+ * @param v - Value to store
912
876
  */
913
- export const ms = (seconds) => seconds * 1000;
877
+ export const setJson = (k, v) => localStorage.setItem(k, JSON.stringify(v));
914
878
  /**
915
879
  * Fetches JSON from URL with options
916
880
  * @param url - URL to fetch from
@@ -957,6 +921,130 @@ export const uploadFile = (url, file) => {
957
921
  body.append("file", file);
958
922
  return fetchJson(url, { method: "POST", body });
959
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
+ // ----------------------------------------------------------------------------
960
1048
  /**
961
1049
  * Converts hex string to bech32 format
962
1050
  * @param prefix - Bech32 prefix