@forthtilliath/ts-kit 0.2.0 → 0.3.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/README.md +83 -19
- package/dist/async/debounce.d.ts +20 -0
- package/dist/async/debounce.d.ts.map +1 -0
- package/dist/async/debounce.js +44 -0
- package/dist/async/index.d.ts +7 -0
- package/dist/async/index.d.ts.map +1 -0
- package/dist/async/index.js +6 -0
- package/dist/async/memoize.d.ts +19 -0
- package/dist/async/memoize.d.ts.map +1 -0
- package/dist/async/memoize.js +25 -0
- package/dist/async/retry.d.ts +25 -0
- package/dist/async/retry.d.ts.map +1 -0
- package/dist/async/retry.js +33 -0
- package/dist/async/sleep.d.ts +9 -0
- package/dist/async/sleep.d.ts.map +1 -0
- package/dist/async/sleep.js +10 -0
- package/dist/async/throttle.d.ts +20 -0
- package/dist/async/throttle.d.ts.map +1 -0
- package/dist/async/throttle.js +43 -0
- package/dist/async/withTimeout.d.ts +19 -0
- package/dist/async/withTimeout.d.ts.map +1 -0
- package/dist/async/withTimeout.js +34 -0
- package/dist/classes/array.d.ts +88 -0
- package/dist/classes/array.d.ts.map +1 -1
- package/dist/classes/array.js +74 -0
- package/dist/classes/index.d.ts +2 -0
- package/dist/classes/index.d.ts.map +1 -0
- package/dist/classes/index.js +1 -0
- package/dist/date/formatRelativeTime.d.ts +13 -0
- package/dist/date/formatRelativeTime.d.ts.map +1 -0
- package/dist/date/formatRelativeTime.js +30 -0
- package/dist/date/index.d.ts +4 -0
- package/dist/date/index.d.ts.map +1 -0
- package/dist/date/index.js +3 -0
- package/dist/date/isSameDay.d.ts +11 -0
- package/dist/date/isSameDay.d.ts.map +1 -0
- package/dist/date/isSameDay.js +14 -0
- package/dist/date/startOfDay.d.ts +10 -0
- package/dist/date/startOfDay.d.ts.map +1 -0
- package/dist/date/startOfDay.js +13 -0
- package/dist/files/index.d.ts +2 -0
- package/dist/files/index.d.ts.map +1 -0
- package/dist/files/index.js +1 -0
- package/dist/index.d.ts +10 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -1
- package/dist/number/clamp.d.ts +13 -0
- package/dist/number/clamp.d.ts.map +1 -0
- package/dist/number/clamp.js +14 -0
- package/dist/number/formatBytes.d.ts +12 -0
- package/dist/number/formatBytes.d.ts.map +1 -0
- package/dist/number/formatBytes.js +19 -0
- package/dist/number/formatDuration.d.ts +12 -0
- package/dist/number/formatDuration.d.ts.map +1 -0
- package/dist/number/formatDuration.js +29 -0
- package/dist/number/index.d.ts +5 -0
- package/dist/number/index.d.ts.map +1 -0
- package/dist/number/index.js +4 -0
- package/dist/number/round.d.ts +12 -0
- package/dist/number/round.d.ts.map +1 -0
- package/dist/number/round.js +14 -0
- package/dist/object/deepClone.d.ts +12 -0
- package/dist/object/deepClone.d.ts.map +1 -0
- package/dist/object/deepClone.js +16 -0
- package/dist/object/deepEqual.d.ts +12 -0
- package/dist/object/deepEqual.d.ts.map +1 -0
- package/dist/object/deepEqual.js +31 -0
- package/dist/object/groupBy.d.ts +12 -0
- package/dist/object/groupBy.d.ts.map +1 -0
- package/dist/object/groupBy.js +18 -0
- package/dist/object/index.d.ts +8 -0
- package/dist/object/index.d.ts.map +1 -0
- package/dist/object/index.js +7 -0
- package/dist/object/mapValues.d.ts +11 -0
- package/dist/object/mapValues.d.ts.map +1 -0
- package/dist/object/mapValues.js +16 -0
- package/dist/object/omit.d.ts +11 -0
- package/dist/object/omit.d.ts.map +1 -0
- package/dist/object/omit.js +16 -0
- package/dist/object/pick.d.ts +11 -0
- package/dist/object/pick.d.ts.map +1 -0
- package/dist/object/pick.js +18 -0
- package/dist/object/uniqueBy.d.ts +13 -0
- package/dist/object/uniqueBy.d.ts.map +1 -0
- package/dist/object/uniqueBy.js +23 -0
- package/dist/string/capitalize.d.ts +11 -0
- package/dist/string/capitalize.d.ts.map +1 -0
- package/dist/string/capitalize.js +14 -0
- package/dist/string/index.d.ts +5 -0
- package/dist/string/index.d.ts.map +1 -0
- package/dist/string/index.js +4 -0
- package/dist/string/pluralize.d.ts +17 -0
- package/dist/string/pluralize.d.ts.map +1 -0
- package/dist/string/pluralize.js +26 -0
- package/dist/string/slugify.d.ts +12 -0
- package/dist/string/slugify.d.ts.map +1 -0
- package/dist/string/slugify.js +18 -0
- package/dist/string/truncate.d.ts +13 -0
- package/dist/string/truncate.d.ts.map +1 -0
- package/dist/string/truncate.js +17 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
# @forthtilliath/ts-kit
|
|
2
2
|
|
|
3
|
-
Framework-agnostic TypeScript utility functions — arrays,
|
|
4
|
-
downloads, and a couple of
|
|
5
|
-
|
|
6
|
-
> **Status**: not currently consumed by any other package in this monorepo.
|
|
7
|
-
> Built, linted, tested and type-checked like everything else, but nothing
|
|
8
|
-
> imports it yet.
|
|
3
|
+
Framework-agnostic TypeScript utility functions — arrays, objects, strings,
|
|
4
|
+
async control-flow, dates, numbers, browser file downloads, and a couple of
|
|
5
|
+
`Array` extensions with better type-narrowing.
|
|
9
6
|
|
|
10
7
|
## Install
|
|
11
8
|
|
|
@@ -21,19 +18,19 @@ Within the workspace:
|
|
|
21
18
|
|
|
22
19
|
## Usage
|
|
23
20
|
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
Import everything you need from the package root — a single barrel re-exports
|
|
22
|
+
every category, so there's no need to remember (or type out) one import line
|
|
23
|
+
per module:
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { chunk, clamp, debounce, pick, slugify } from "@forthtilliath/ts-kit";
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Subpath imports still work if you'd rather import one module at a time (e.g.
|
|
30
|
+
to keep a bundle's included code obvious at a glance):
|
|
26
31
|
|
|
27
32
|
```ts
|
|
28
33
|
import { chunk } from "@forthtilliath/ts-kit/array/chunk";
|
|
29
|
-
import { flattenDeep } from "@forthtilliath/ts-kit/array/flattenDeep";
|
|
30
|
-
import { FArray } from "@forthtilliath/ts-kit/classes/array";
|
|
31
|
-
import {
|
|
32
|
-
downloadText,
|
|
33
|
-
downloadTextBlob,
|
|
34
|
-
} from "@forthtilliath/ts-kit/files/downloadText";
|
|
35
|
-
import { avg } from "@forthtilliath/ts-kit/maths/average";
|
|
36
|
-
import { sum } from "@forthtilliath/ts-kit/maths/sum";
|
|
37
34
|
```
|
|
38
35
|
|
|
39
36
|
### `array`
|
|
@@ -41,11 +38,46 @@ import { sum } from "@forthtilliath/ts-kit/maths/sum";
|
|
|
41
38
|
- `chunk(array, size)` — splits an array into chunks of a given size.
|
|
42
39
|
- `flattenDeep(array)` — recursively flattens nested arrays.
|
|
43
40
|
|
|
41
|
+
### `async`
|
|
42
|
+
|
|
43
|
+
- `sleep(ms)` — resolves after a delay.
|
|
44
|
+
- `retry(fn, options)` — retries an async operation with exponential backoff.
|
|
45
|
+
- `debounce(fn, wait)` — delays `fn` until calls stop for `wait` ms; exposes
|
|
46
|
+
`cancel()` / `flush()`.
|
|
47
|
+
- `throttle(fn, wait)` — runs `fn` at most once per `wait` ms (leading +
|
|
48
|
+
trailing edge); exposes `cancel()`.
|
|
49
|
+
- `memoize(fn, getKey?)` — caches `fn`'s results per argument tuple; exposes
|
|
50
|
+
the underlying `cache` map.
|
|
51
|
+
- `withTimeout(promise, ms, message?)` — rejects with a `TimeoutError` if
|
|
52
|
+
`promise` doesn't settle in time.
|
|
53
|
+
|
|
44
54
|
### `classes`
|
|
45
55
|
|
|
46
|
-
- `FArray` — an `Array` subclass
|
|
47
|
-
|
|
48
|
-
|
|
56
|
+
- `FArray` — an `Array` subclass that fixes a handful of spots where the
|
|
57
|
+
native `Array` typings are wrong or overly restrictive:
|
|
58
|
+
- `includes()` narrows the type of the searched value when it's found
|
|
59
|
+
(the native `Array.prototype.includes` only narrows the _array's_
|
|
60
|
+
element type).
|
|
61
|
+
- `indexOf()` / `lastIndexOf()` accept a search value outside of the
|
|
62
|
+
array's element type instead of rejecting it at compile-time (same
|
|
63
|
+
root cause as `includes`, fixed the same way).
|
|
64
|
+
- `filter(Boolean)` strips `null`/`undefined` from the result's type
|
|
65
|
+
(the native typings don't recognize `Boolean` as a type guard, so the
|
|
66
|
+
result keeps the nullish types even though they're removed at runtime).
|
|
67
|
+
- `FArray.from(...)` / `FArray.of(...)` are typed to return an `FArray`
|
|
68
|
+
instead of a plain array (the native static typings return a plain
|
|
69
|
+
array even though these methods already construct an instance of the
|
|
70
|
+
calling class at runtime).
|
|
71
|
+
- `toArray()` converts an `FArray` back into a plain `Array` instance —
|
|
72
|
+
rarely needed since `FArray` is already assignable to `T[]` anywhere a
|
|
73
|
+
plain array is expected, but useful when the runtime type matters too.
|
|
74
|
+
|
|
75
|
+
### `date`
|
|
76
|
+
|
|
77
|
+
- `startOfDay(date?)` — returns a new `Date` at local midnight.
|
|
78
|
+
- `isSameDay(a, b)` — checks whether two dates fall on the same local day.
|
|
79
|
+
- `formatRelativeTime(date, baseDate?, locale?)` — human-readable relative
|
|
80
|
+
time ("3 hours ago"), backed by `Intl.RelativeTimeFormat`.
|
|
49
81
|
|
|
50
82
|
### `files`
|
|
51
83
|
|
|
@@ -54,11 +86,43 @@ import { sum } from "@forthtilliath/ts-kit/maths/sum";
|
|
|
54
86
|
- `downloadTextBlob(filename, text)` — same, but via a `Blob` + object URL
|
|
55
87
|
(better for larger content).
|
|
56
88
|
|
|
89
|
+
### `id`
|
|
90
|
+
|
|
91
|
+
- `randomId()` — `crypto.randomUUID()` with fallbacks for older/insecure
|
|
92
|
+
contexts.
|
|
93
|
+
|
|
57
94
|
### `maths`
|
|
58
95
|
|
|
59
96
|
- `sum(numbers)` — sum of an array of numbers.
|
|
60
97
|
- `avg(numbers)` — average of an array of numbers.
|
|
61
98
|
|
|
99
|
+
### `number`
|
|
100
|
+
|
|
101
|
+
- `clamp(value, min, max)` — clamps a number between two bounds.
|
|
102
|
+
- `round(value, decimals?)` — rounds to a given number of decimal places.
|
|
103
|
+
- `formatBytes(bytes, decimals?)` — human-readable byte size ("1.5 MB").
|
|
104
|
+
- `formatDuration(ms)` — human-readable duration ("1d 1h 1m 1s").
|
|
105
|
+
|
|
106
|
+
### `object`
|
|
107
|
+
|
|
108
|
+
- `pick(obj, keys)` / `omit(obj, keys)` — keep or drop a set of keys.
|
|
109
|
+
- `groupBy(array, getKey)` — groups elements by a computed key.
|
|
110
|
+
- `uniqueBy(array, getKey)` — deduplicates elements by a computed key.
|
|
111
|
+
- `deepClone(value)` — deep clone (via `structuredClone`, with a JSON
|
|
112
|
+
fallback).
|
|
113
|
+
- `deepEqual(a, b)` — recursive structural equality check.
|
|
114
|
+
- `mapValues(obj, fn)` — maps every value of an object through `fn`.
|
|
115
|
+
|
|
116
|
+
### `string`
|
|
117
|
+
|
|
118
|
+
- `capitalize(str)` — uppercases the first letter, lowercases the rest.
|
|
119
|
+
- `truncate(str, maxLength, suffix?)` — truncates with an ellipsis (or custom
|
|
120
|
+
suffix).
|
|
121
|
+
- `slugify(str)` — URL-friendly slug (accents stripped, lower-cased,
|
|
122
|
+
hyphenated).
|
|
123
|
+
- `pluralize(count, singular, plural?)` — naive English pluralization, with an
|
|
124
|
+
override for irregular words.
|
|
125
|
+
|
|
62
126
|
## Scripts
|
|
63
127
|
|
|
64
128
|
```bash
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface DebouncedFunction<Args extends unknown[]> {
|
|
2
|
+
(...args: Args): void;
|
|
3
|
+
/** Cancels a pending invocation, if any. */
|
|
4
|
+
cancel: () => void;
|
|
5
|
+
/** Immediately invokes a pending call, if any. */
|
|
6
|
+
flush: () => void;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Returns a debounced version of `fn` that only runs after `wait`
|
|
10
|
+
* milliseconds have elapsed without another call.
|
|
11
|
+
*
|
|
12
|
+
* @param fn - The function to debounce.
|
|
13
|
+
* @param wait - The delay in milliseconds.
|
|
14
|
+
* @returns The debounced function, with `cancel()` and `flush()` helpers.
|
|
15
|
+
* @example
|
|
16
|
+
* const onResize = debounce(() => recompute(), 200);
|
|
17
|
+
* window.addEventListener("resize", onResize);
|
|
18
|
+
*/
|
|
19
|
+
export declare function debounce<Args extends unknown[]>(fn: (...args: Args) => void, wait: number): DebouncedFunction<Args>;
|
|
20
|
+
//# sourceMappingURL=debounce.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"debounce.d.ts","sourceRoot":"","sources":["../../src/async/debounce.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,iBAAiB,CAAC,IAAI,SAAS,OAAO,EAAE;IACvD,CAAC,GAAG,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IACtB,4CAA4C;IAC5C,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,kDAAkD;IAClD,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,QAAQ,CAAC,IAAI,SAAS,OAAO,EAAE,EAC7C,EAAE,EAAE,CAAC,GAAG,IAAI,EAAE,IAAI,KAAK,IAAI,EAC3B,IAAI,EAAE,MAAM,GACX,iBAAiB,CAAC,IAAI,CAAC,CA+BzB"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns a debounced version of `fn` that only runs after `wait`
|
|
3
|
+
* milliseconds have elapsed without another call.
|
|
4
|
+
*
|
|
5
|
+
* @param fn - The function to debounce.
|
|
6
|
+
* @param wait - The delay in milliseconds.
|
|
7
|
+
* @returns The debounced function, with `cancel()` and `flush()` helpers.
|
|
8
|
+
* @example
|
|
9
|
+
* const onResize = debounce(() => recompute(), 200);
|
|
10
|
+
* window.addEventListener("resize", onResize);
|
|
11
|
+
*/
|
|
12
|
+
export function debounce(fn, wait) {
|
|
13
|
+
let timer;
|
|
14
|
+
let pendingArgs;
|
|
15
|
+
const debounced = ((...args) => {
|
|
16
|
+
pendingArgs = args;
|
|
17
|
+
if (timer !== undefined)
|
|
18
|
+
clearTimeout(timer);
|
|
19
|
+
timer = setTimeout(() => {
|
|
20
|
+
timer = undefined;
|
|
21
|
+
const callArgs = pendingArgs;
|
|
22
|
+
pendingArgs = undefined;
|
|
23
|
+
if (callArgs)
|
|
24
|
+
fn(...callArgs);
|
|
25
|
+
}, wait);
|
|
26
|
+
});
|
|
27
|
+
debounced.cancel = () => {
|
|
28
|
+
if (timer !== undefined)
|
|
29
|
+
clearTimeout(timer);
|
|
30
|
+
timer = undefined;
|
|
31
|
+
pendingArgs = undefined;
|
|
32
|
+
};
|
|
33
|
+
debounced.flush = () => {
|
|
34
|
+
if (timer === undefined)
|
|
35
|
+
return;
|
|
36
|
+
clearTimeout(timer);
|
|
37
|
+
timer = undefined;
|
|
38
|
+
const callArgs = pendingArgs;
|
|
39
|
+
pendingArgs = undefined;
|
|
40
|
+
if (callArgs)
|
|
41
|
+
fn(...callArgs);
|
|
42
|
+
};
|
|
43
|
+
return debounced;
|
|
44
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/async/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface MemoizedFunction<Args extends unknown[], R> {
|
|
2
|
+
(...args: Args): R;
|
|
3
|
+
/** The underlying cache, exposed for inspection or manual clearing. */
|
|
4
|
+
cache: Map<string, R>;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Caches the result of `fn` per argument tuple (JSON-stringified by
|
|
8
|
+
* default).
|
|
9
|
+
*
|
|
10
|
+
* @param fn - The function to memoize.
|
|
11
|
+
* @param getKey - Computes the cache key for a set of arguments (defaults to `JSON.stringify`).
|
|
12
|
+
* @returns A memoized version of `fn`, with a `cache` map exposed.
|
|
13
|
+
* @example
|
|
14
|
+
* const square = memoize((n: number) => expensiveSquare(n));
|
|
15
|
+
* square(4); // computed
|
|
16
|
+
* square(4); // cached
|
|
17
|
+
*/
|
|
18
|
+
export declare function memoize<Args extends unknown[], R>(fn: (...args: Args) => R, getKey?: (...args: Args) => string): MemoizedFunction<Args, R>;
|
|
19
|
+
//# sourceMappingURL=memoize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memoize.d.ts","sourceRoot":"","sources":["../../src/async/memoize.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,gBAAgB,CAAC,IAAI,SAAS,OAAO,EAAE,EAAE,CAAC;IACzD,CAAC,GAAG,IAAI,EAAE,IAAI,GAAG,CAAC,CAAC;IACnB,uEAAuE;IACvE,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;CACvB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,OAAO,CAAC,IAAI,SAAS,OAAO,EAAE,EAAE,CAAC,EAC/C,EAAE,EAAE,CAAC,GAAG,IAAI,EAAE,IAAI,KAAK,CAAC,EACxB,MAAM,GAAE,CAAC,GAAG,IAAI,EAAE,IAAI,KAAK,MAA0C,GACpE,gBAAgB,CAAC,IAAI,EAAE,CAAC,CAAC,CAa3B"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Caches the result of `fn` per argument tuple (JSON-stringified by
|
|
3
|
+
* default).
|
|
4
|
+
*
|
|
5
|
+
* @param fn - The function to memoize.
|
|
6
|
+
* @param getKey - Computes the cache key for a set of arguments (defaults to `JSON.stringify`).
|
|
7
|
+
* @returns A memoized version of `fn`, with a `cache` map exposed.
|
|
8
|
+
* @example
|
|
9
|
+
* const square = memoize((n: number) => expensiveSquare(n));
|
|
10
|
+
* square(4); // computed
|
|
11
|
+
* square(4); // cached
|
|
12
|
+
*/
|
|
13
|
+
export function memoize(fn, getKey = (...args) => JSON.stringify(args)) {
|
|
14
|
+
const cache = new Map();
|
|
15
|
+
const memoized = ((...args) => {
|
|
16
|
+
const key = getKey(...args);
|
|
17
|
+
if (cache.has(key))
|
|
18
|
+
return cache.get(key);
|
|
19
|
+
const result = fn(...args);
|
|
20
|
+
cache.set(key, result);
|
|
21
|
+
return result;
|
|
22
|
+
});
|
|
23
|
+
memoized.cache = cache;
|
|
24
|
+
return memoized;
|
|
25
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface RetryOptions {
|
|
2
|
+
/** Maximum number of attempts, including the first one (default: 3). */
|
|
3
|
+
attempts?: number;
|
|
4
|
+
/** Base delay in milliseconds before retrying (default: 200). */
|
|
5
|
+
delayMs?: number;
|
|
6
|
+
/** Backoff multiplier applied to `delayMs` after each failed attempt (default: 2). */
|
|
7
|
+
backoffFactor?: number;
|
|
8
|
+
/** Called with the error and the 1-based attempt number after each failure. */
|
|
9
|
+
onRetry?: (error: unknown, attempt: number) => void;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Retries an async operation with exponential backoff.
|
|
13
|
+
*
|
|
14
|
+
* @param fn - The operation to retry. Receives the 1-based attempt number.
|
|
15
|
+
* @param options - Retry configuration.
|
|
16
|
+
* @returns The result of the first successful attempt.
|
|
17
|
+
* @throws The last error, if every attempt failed.
|
|
18
|
+
* @example
|
|
19
|
+
* const data = await retry(() => fetch("/api").then((r) => r.json()), {
|
|
20
|
+
* attempts: 5,
|
|
21
|
+
* delayMs: 300,
|
|
22
|
+
* });
|
|
23
|
+
*/
|
|
24
|
+
export declare function retry<T>(fn: (attempt: number) => Promise<T>, options?: RetryOptions): Promise<T>;
|
|
25
|
+
//# sourceMappingURL=retry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retry.d.ts","sourceRoot":"","sources":["../../src/async/retry.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,YAAY;IAC3B,wEAAwE;IACxE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iEAAiE;IACjE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,sFAAsF;IACtF,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,+EAA+E;IAC/E,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACrD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,KAAK,CAAC,CAAC,EAC3B,EAAE,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,EACnC,OAAO,GAAE,YAAiB,GACzB,OAAO,CAAC,CAAC,CAAC,CAoBZ"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { sleep } from "./sleep.js";
|
|
2
|
+
/**
|
|
3
|
+
* Retries an async operation with exponential backoff.
|
|
4
|
+
*
|
|
5
|
+
* @param fn - The operation to retry. Receives the 1-based attempt number.
|
|
6
|
+
* @param options - Retry configuration.
|
|
7
|
+
* @returns The result of the first successful attempt.
|
|
8
|
+
* @throws The last error, if every attempt failed.
|
|
9
|
+
* @example
|
|
10
|
+
* const data = await retry(() => fetch("/api").then((r) => r.json()), {
|
|
11
|
+
* attempts: 5,
|
|
12
|
+
* delayMs: 300,
|
|
13
|
+
* });
|
|
14
|
+
*/
|
|
15
|
+
export async function retry(fn, options = {}) {
|
|
16
|
+
const { attempts = 3, delayMs = 200, backoffFactor = 2, onRetry } = options;
|
|
17
|
+
let lastError;
|
|
18
|
+
let currentDelay = delayMs;
|
|
19
|
+
for (let attempt = 1; attempt <= attempts; attempt++) {
|
|
20
|
+
try {
|
|
21
|
+
return await fn(attempt);
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
lastError = error;
|
|
25
|
+
onRetry?.(error, attempt);
|
|
26
|
+
if (attempt < attempts) {
|
|
27
|
+
await sleep(currentDelay);
|
|
28
|
+
currentDelay *= backoffFactor;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
throw lastError;
|
|
33
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sleep.d.ts","sourceRoot":"","sources":["../../src/async/sleep.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE/C"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface ThrottledFunction<Args extends unknown[]> {
|
|
2
|
+
(...args: Args): void;
|
|
3
|
+
/** Cancels any pending trailing invocation. */
|
|
4
|
+
cancel: () => void;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Returns a throttled version of `fn` that runs at most once every `wait`
|
|
8
|
+
* milliseconds: immediately on the first call (leading edge), then once more
|
|
9
|
+
* at the end of the window if further calls came in during it (trailing
|
|
10
|
+
* edge).
|
|
11
|
+
*
|
|
12
|
+
* @param fn - The function to throttle.
|
|
13
|
+
* @param wait - The minimum delay in milliseconds between calls.
|
|
14
|
+
* @returns The throttled function, with a `cancel()` helper.
|
|
15
|
+
* @example
|
|
16
|
+
* const onScroll = throttle(() => updatePosition(), 100);
|
|
17
|
+
* window.addEventListener("scroll", onScroll);
|
|
18
|
+
*/
|
|
19
|
+
export declare function throttle<Args extends unknown[]>(fn: (...args: Args) => void, wait: number): ThrottledFunction<Args>;
|
|
20
|
+
//# sourceMappingURL=throttle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"throttle.d.ts","sourceRoot":"","sources":["../../src/async/throttle.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,iBAAiB,CAAC,IAAI,SAAS,OAAO,EAAE;IACvD,CAAC,GAAG,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IACtB,+CAA+C;IAC/C,MAAM,EAAE,MAAM,IAAI,CAAC;CACpB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,QAAQ,CAAC,IAAI,SAAS,OAAO,EAAE,EAC7C,EAAE,EAAE,CAAC,GAAG,IAAI,EAAE,IAAI,KAAK,IAAI,EAC3B,IAAI,EAAE,MAAM,GACX,iBAAiB,CAAC,IAAI,CAAC,CAkCzB"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns a throttled version of `fn` that runs at most once every `wait`
|
|
3
|
+
* milliseconds: immediately on the first call (leading edge), then once more
|
|
4
|
+
* at the end of the window if further calls came in during it (trailing
|
|
5
|
+
* edge).
|
|
6
|
+
*
|
|
7
|
+
* @param fn - The function to throttle.
|
|
8
|
+
* @param wait - The minimum delay in milliseconds between calls.
|
|
9
|
+
* @returns The throttled function, with a `cancel()` helper.
|
|
10
|
+
* @example
|
|
11
|
+
* const onScroll = throttle(() => updatePosition(), 100);
|
|
12
|
+
* window.addEventListener("scroll", onScroll);
|
|
13
|
+
*/
|
|
14
|
+
export function throttle(fn, wait) {
|
|
15
|
+
let lastCallTime;
|
|
16
|
+
let timer;
|
|
17
|
+
let pendingArgs;
|
|
18
|
+
const throttled = ((...args) => {
|
|
19
|
+
const now = Date.now();
|
|
20
|
+
if (lastCallTime === undefined || now - lastCallTime >= wait) {
|
|
21
|
+
lastCallTime = now;
|
|
22
|
+
fn(...args);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
pendingArgs = args;
|
|
26
|
+
timer ??= setTimeout(() => {
|
|
27
|
+
lastCallTime = Date.now();
|
|
28
|
+
timer = undefined;
|
|
29
|
+
const callArgs = pendingArgs;
|
|
30
|
+
pendingArgs = undefined;
|
|
31
|
+
if (callArgs)
|
|
32
|
+
fn(...callArgs);
|
|
33
|
+
}, wait - (now - lastCallTime));
|
|
34
|
+
});
|
|
35
|
+
throttled.cancel = () => {
|
|
36
|
+
if (timer !== undefined)
|
|
37
|
+
clearTimeout(timer);
|
|
38
|
+
timer = undefined;
|
|
39
|
+
pendingArgs = undefined;
|
|
40
|
+
lastCallTime = undefined;
|
|
41
|
+
};
|
|
42
|
+
return throttled;
|
|
43
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thrown by {@link withTimeout} when the wrapped promise does not settle in
|
|
3
|
+
* time.
|
|
4
|
+
*/
|
|
5
|
+
export declare class TimeoutError extends Error {
|
|
6
|
+
constructor(message?: string);
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Rejects with a {@link TimeoutError} if `promise` has not settled within
|
|
10
|
+
* `ms` milliseconds.
|
|
11
|
+
*
|
|
12
|
+
* @param promise - The promise to race against the timeout.
|
|
13
|
+
* @param ms - The timeout in milliseconds.
|
|
14
|
+
* @param message - Optional error message (defaults to a generic one).
|
|
15
|
+
* @example
|
|
16
|
+
* await withTimeout(fetch("/api"), 5000);
|
|
17
|
+
*/
|
|
18
|
+
export declare function withTimeout<T>(promise: Promise<T>, ms: number, message?: string): Promise<T>;
|
|
19
|
+
//# sourceMappingURL=withTimeout.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"withTimeout.d.ts","sourceRoot":"","sources":["../../src/async/withTimeout.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,qBAAa,YAAa,SAAQ,KAAK;gBACzB,OAAO,SAAwB;CAI5C;AAED;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAC3B,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EACnB,EAAE,EAAE,MAAM,EACV,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,CAAC,CAAC,CAgBZ"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thrown by {@link withTimeout} when the wrapped promise does not settle in
|
|
3
|
+
* time.
|
|
4
|
+
*/
|
|
5
|
+
export class TimeoutError extends Error {
|
|
6
|
+
constructor(message = "Operation timed out") {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = "TimeoutError";
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Rejects with a {@link TimeoutError} if `promise` has not settled within
|
|
13
|
+
* `ms` milliseconds.
|
|
14
|
+
*
|
|
15
|
+
* @param promise - The promise to race against the timeout.
|
|
16
|
+
* @param ms - The timeout in milliseconds.
|
|
17
|
+
* @param message - Optional error message (defaults to a generic one).
|
|
18
|
+
* @example
|
|
19
|
+
* await withTimeout(fetch("/api"), 5000);
|
|
20
|
+
*/
|
|
21
|
+
export function withTimeout(promise, ms, message) {
|
|
22
|
+
return new Promise((resolve, reject) => {
|
|
23
|
+
const timer = setTimeout(() => {
|
|
24
|
+
reject(new TimeoutError(message));
|
|
25
|
+
}, ms);
|
|
26
|
+
promise.then((value) => {
|
|
27
|
+
clearTimeout(timer);
|
|
28
|
+
resolve(value);
|
|
29
|
+
}, (error) => {
|
|
30
|
+
clearTimeout(timer);
|
|
31
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
}
|
package/dist/classes/array.d.ts
CHANGED
|
@@ -49,5 +49,93 @@ export declare class FArray<T> extends Array<T> {
|
|
|
49
49
|
* }
|
|
50
50
|
*/
|
|
51
51
|
includes<S>(searchElement: S | T, fromIndex?: number): searchElement is S & T;
|
|
52
|
+
/**
|
|
53
|
+
* Same as `Array.indexOf`, but accepts a search value outside of `T`
|
|
54
|
+
* instead of rejecting it at compile-time (the native typings only accept
|
|
55
|
+
* `T`, exactly like `Array.includes` before the override above).
|
|
56
|
+
*
|
|
57
|
+
* @param searchElement The value to search for — no longer restricted to `T`.
|
|
58
|
+
* @param fromIndex The position in this array at which to begin searching for searchElement.
|
|
59
|
+
* @returns The index of the first match, or -1 if not found.
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* // Normal operation of Array.indexOf
|
|
63
|
+
* const buz = "buz";
|
|
64
|
+
* type FBB = "foo" | "bar" | "baz";
|
|
65
|
+
* const arr: FBB[] = ["foo", "bar", "baz"];
|
|
66
|
+
* arr.indexOf(buz);
|
|
67
|
+
* // -----------^
|
|
68
|
+
* // The argument of type 'buz' is not assignable to the parameter of type 'FBB'.
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* // FArray.indexOf
|
|
72
|
+
* const buz = "buz";
|
|
73
|
+
* type FBB = "foo" | "bar" | "baz";
|
|
74
|
+
* const arr = new FArray<FBB>("foo", "bar", "baz");
|
|
75
|
+
* arr.indexOf(buz); // compiles fine, returns -1
|
|
76
|
+
*/
|
|
77
|
+
indexOf(searchElement: unknown, fromIndex?: number): number;
|
|
78
|
+
/**
|
|
79
|
+
* Same as `Array.lastIndexOf`, but accepts a search value outside of `T`
|
|
80
|
+
* instead of rejecting it at compile-time. See {@link FArray.indexOf}.
|
|
81
|
+
*
|
|
82
|
+
* @param searchElement The value to search for — no longer restricted to `T`.
|
|
83
|
+
* @param fromIndex The position in this array at which to start searching backwards. Defaults to the last index, like the native method.
|
|
84
|
+
* @returns The index of the last match, or -1 if not found.
|
|
85
|
+
*/
|
|
86
|
+
lastIndexOf(searchElement: unknown, fromIndex?: number): number;
|
|
87
|
+
/**
|
|
88
|
+
* Same as `Array.filter`, but recognizes the `Boolean` global constructor
|
|
89
|
+
* as a type guard — the native typings don't, so `arr.filter(Boolean)`
|
|
90
|
+
* keeps `null`/`undefined` in the result's element type even though
|
|
91
|
+
* they're removed at runtime.
|
|
92
|
+
*
|
|
93
|
+
* Note: like the native runtime behavior, this also drops other falsy
|
|
94
|
+
* values (`0`, `""`, `NaN`, `false`) — but the type only reflects the
|
|
95
|
+
* removal of `null`/`undefined`, since narrowing those further would
|
|
96
|
+
* require them to be literal members of `T`.
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* // Normal operation of Array.filter
|
|
100
|
+
* const arr: (number | null | undefined)[] = [1, null, 2, undefined];
|
|
101
|
+
* const result = arr.filter(Boolean); // still (number | null | undefined)[]
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* // FArray.filter
|
|
105
|
+
* const arr = new FArray<number | null | undefined>(1, null, 2, undefined);
|
|
106
|
+
* const result = arr.filter(Boolean); // FArray<number>
|
|
107
|
+
*/
|
|
108
|
+
filter(predicate: BooleanConstructor): FArray<NonNullable<T>>;
|
|
109
|
+
filter<S extends T>(predicate: (value: T, index: number, array: T[]) => value is S, thisArg?: unknown): FArray<S>;
|
|
110
|
+
filter(predicate: (value: T, index: number, array: T[]) => unknown, thisArg?: unknown): FArray<T>;
|
|
111
|
+
/**
|
|
112
|
+
* Same as `Array.from`, but returns an `FArray` — the native typings
|
|
113
|
+
* return a plain `T[]`/`U[]` even though `Array.from` (and `Array.of`
|
|
114
|
+
* below) already construct an instance of the calling class at runtime.
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* const fromPlain = FArray.from([1, 2, 3]); // FArray<number>, not number[]
|
|
118
|
+
*/
|
|
119
|
+
static from<T>(iterable: Iterable<T> | ArrayLike<T>): FArray<T>;
|
|
120
|
+
static from<T, U>(iterable: Iterable<T> | ArrayLike<T>, mapfn: (v: T, k: number) => U, thisArg?: unknown): FArray<U>;
|
|
121
|
+
/**
|
|
122
|
+
* Same as `Array.of`, but returns an `FArray`. See {@link FArray.from}.
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* const fa = FArray.of(1, 2, 3); // FArray<number>, not number[]
|
|
126
|
+
*/
|
|
127
|
+
static of<T>(...items: T[]): FArray<T>;
|
|
128
|
+
/**
|
|
129
|
+
* Converts this `FArray` back into a plain `Array` instance — the
|
|
130
|
+
* reverse of {@link FArray.from}/{@link FArray.of}. `FArray` is already
|
|
131
|
+
* assignable to `T[]` anywhere a plain array is expected (it extends
|
|
132
|
+
* `Array`), so this is mostly useful when the runtime type matters too
|
|
133
|
+
* (e.g. a library that special-cases `Array` and rejects subclasses).
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* const fa = new FArray(1, 2, 3);
|
|
137
|
+
* const plain = fa.toArray(); // Array(3) [1, 2, 3], not instanceof FArray
|
|
138
|
+
*/
|
|
139
|
+
toArray(): T[];
|
|
52
140
|
}
|
|
53
141
|
//# sourceMappingURL=array.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"array.d.ts","sourceRoot":"","sources":["../../src/classes/array.ts"],"names":[],"mappings":"AAAA,qBAAa,MAAM,CAAC,CAAC,CAAE,SAAQ,KAAK,CAAC,CAAC,CAAC;IACrC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAgDG;IACH,QAAQ,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC,GAAG,CAAC,EAAE,SAAS,SAAI,GAAG,aAAa,IAAI,CAAC,GAAG,CAAC;
|
|
1
|
+
{"version":3,"file":"array.d.ts","sourceRoot":"","sources":["../../src/classes/array.ts"],"names":[],"mappings":"AAAA,qBAAa,MAAM,CAAC,CAAC,CAAE,SAAQ,KAAK,CAAC,CAAC,CAAC;IACrC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAgDG;IACH,QAAQ,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC,GAAG,CAAC,EAAE,SAAS,SAAI,GAAG,aAAa,IAAI,CAAC,GAAG,CAAC;IAKxE;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACH,OAAO,CAAC,aAAa,EAAE,OAAO,EAAE,SAAS,SAAI,GAAG,MAAM;IAKtD;;;;;;;OAOG;IACH,WAAW,CAAC,aAAa,EAAE,OAAO,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM;IAS/D;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,MAAM,CAAC,SAAS,EAAE,kBAAkB,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;IAC7D,MAAM,CAAC,CAAC,SAAS,CAAC,EAChB,SAAS,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,KAAK,IAAI,CAAC,EAC9D,OAAO,CAAC,EAAE,OAAO,GAChB,MAAM,CAAC,CAAC,CAAC;IACZ,MAAM,CACJ,SAAS,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,OAAO,EAC3D,OAAO,CAAC,EAAE,OAAO,GAChB,MAAM,CAAC,CAAC,CAAC;IAQZ;;;;;;;OAOG;IACH,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC;IAC/D,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,EACd,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,EACpC,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC,EAC7B,OAAO,CAAC,EAAE,OAAO,GAChB,MAAM,CAAC,CAAC,CAAC;IAWZ;;;;;OAKG;IACH,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,EAAE,CAAC,EAAE,GAAG,MAAM,CAAC,CAAC,CAAC;IAItC;;;;;;;;;;OAUG;IACH,OAAO,IAAI,CAAC,EAAE;CAGf"}
|