@useless-engineer/utils 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -91,7 +91,7 @@ const padding = isIOS ? 20 : 16;
91
91
  ### Core
92
92
 
93
93
  <details>
94
- <summary><code>debounce(fn, delay)</code> &nbsp; ![live](https://img.shields.io/badge/-live-22c55e?style=flat-square) ![async](https://img.shields.io/badge/-async-6366f1?style=flat-square)</summary>
94
+ <summary><code>debounce(fn, delay)</code> &nbsp; <img alt="live" src="https://img.shields.io/badge/-live-22c55e?style=flat-square"> <img alt="async" src="https://img.shields.io/badge/-async-6366f1?style=flat-square"></summary>
95
95
 
96
96
  <br>
97
97
 
@@ -117,7 +117,7 @@ save.cancel();
117
117
  </details>
118
118
 
119
119
  <details>
120
- <summary><code>throttle(fn, interval, options?)</code> &nbsp; ![live](https://img.shields.io/badge/-live-22c55e?style=flat-square) ![async](https://img.shields.io/badge/-async-6366f1?style=flat-square)</summary>
120
+ <summary><code>throttle(fn, interval, options?)</code> &nbsp; <img alt="live" src="https://img.shields.io/badge/-live-22c55e?style=flat-square"> <img alt="async" src="https://img.shields.io/badge/-async-6366f1?style=flat-square"></summary>
121
121
 
122
122
  <br>
123
123
 
@@ -158,7 +158,7 @@ Returns a function with `.cancel()`.
158
158
  ### React Native
159
159
 
160
160
  <details>
161
- <summary><code>isIOS</code> / <code>isAndroid</code> &nbsp; ![live](https://img.shields.io/badge/-live-22c55e?style=flat-square) ![react native](https://img.shields.io/badge/-react%20native-61DAFB?style=flat-square&logoColor=black)</summary>
161
+ <summary><code>isIOS</code> / <code>isAndroid</code> &nbsp; <img alt="live" src="https://img.shields.io/badge/-live-22c55e?style=flat-square"> <img alt="react native" src="https://img.shields.io/badge/-react%20native-61DAFB?style=flat-square&logoColor=black"></summary>
162
162
 
163
163
  <br>
164
164
 
@@ -178,12 +178,11 @@ const behavior = isIOS ? "padding" : "height";
178
178
 
179
179
  Released one or two at a time, each matching a post that's already live.
180
180
 
181
- | Next up | Entry | Origin |
182
- | ------------------------------------------------------------------------------------------------------------------------ | ------------ | ----------------------------------------- |
183
- | ![next](https://img.shields.io/badge/-next-f59e0b?style=flat-square) `delay`, `withTimeout` | core | Validated timers, `Promise.race` timeouts |
184
- | ![planned](https://img.shields.io/badge/-planned-64748b?style=flat-square) `safeParseJSON`, `safeStringify`, `deepClone` | core | JSON and shallow-copy gotchas |
185
- | ![planned](https://img.shields.io/badge/-planned-64748b?style=flat-square) `getAccessibilityProps`, `createShadow` | react-native | Accessibility and iOS-only shadows |
186
- | ![planned](https://img.shields.io/badge/-planned-64748b?style=flat-square) `useInterval`, `useCountdown`, `useIsMounted` | react | Stale closures and unmounted updates |
181
+ | Next up | Entry | Origin |
182
+ | ------------------------------------------------------------------------------------------------------------------------ | ------------ | ------------------------------------ |
183
+ | ![next](https://img.shields.io/badge/-next-f59e0b?style=flat-square)`safeParseJSON`, `safeStringify`, `deepClone` | core | JSON and shallow-copy gotchas |
184
+ | ![planned](https://img.shields.io/badge/-planned-64748b?style=flat-square) `getAccessibilityProps`, `createShadow` | react-native | Accessibility and iOS-only shadows |
185
+ | ![planned](https://img.shields.io/badge/-planned-64748b?style=flat-square) `useInterval`, `useCountdown`, `useIsMounted` | react | Stale closures and unmounted updates |
187
186
 
188
187
  ---
189
188
 
package/dist/index.cjs CHANGED
@@ -1,14 +1,14 @@
1
1
  'use strict';
2
2
 
3
3
  // src/async/debounce.ts
4
- function debounce(fn, delay) {
4
+ function debounce(fn, delay2) {
5
5
  let timer;
6
6
  const debounced = (...args) => {
7
7
  if (timer !== void 0) clearTimeout(timer);
8
8
  timer = setTimeout(() => {
9
9
  timer = void 0;
10
10
  fn(...args);
11
- }, delay);
11
+ }, delay2);
12
12
  };
13
13
  debounced.cancel = () => {
14
14
  if (timer !== void 0) clearTimeout(timer);
@@ -56,5 +56,60 @@ function throttle(fn, interval, { leading = true, trailing = true } = {}) {
56
56
  return throttled;
57
57
  }
58
58
 
59
+ // src/async/delay.ts
60
+ function delay(ms, { signal } = {}) {
61
+ if (typeof ms !== "number" || !Number.isFinite(ms) || ms < 0) {
62
+ return Promise.reject(
63
+ new RangeError(`delay: expected a finite, non-negative number of ms, got ${String(ms)}`)
64
+ );
65
+ }
66
+ if (signal?.aborted) return Promise.reject(signal.reason);
67
+ return new Promise((resolve, reject) => {
68
+ const onAbort = () => {
69
+ clearTimeout(timer);
70
+ reject(signal.reason);
71
+ };
72
+ const timer = setTimeout(() => {
73
+ signal?.removeEventListener("abort", onAbort);
74
+ resolve();
75
+ }, ms);
76
+ signal?.addEventListener("abort", onAbort, { once: true });
77
+ });
78
+ }
79
+
80
+ // src/async/withTimeout.ts
81
+ var TimeoutError = class extends Error {
82
+ constructor(message = "Operation timed out") {
83
+ super(message);
84
+ this.name = "TimeoutError";
85
+ }
86
+ };
87
+ function withTimeout(promise, ms, message) {
88
+ if (typeof ms !== "number" || !Number.isFinite(ms) || ms < 0) {
89
+ return Promise.reject(
90
+ new RangeError(`withTimeout: expected a finite, non-negative number of ms, got ${String(ms)}`)
91
+ );
92
+ }
93
+ return new Promise((resolve, reject) => {
94
+ const timer = setTimeout(
95
+ () => reject(new TimeoutError(message ?? `Timed out after ${ms}ms`)),
96
+ ms
97
+ );
98
+ Promise.resolve(promise).then(
99
+ (value) => {
100
+ clearTimeout(timer);
101
+ resolve(value);
102
+ },
103
+ (error) => {
104
+ clearTimeout(timer);
105
+ reject(error);
106
+ }
107
+ );
108
+ });
109
+ }
110
+
111
+ exports.TimeoutError = TimeoutError;
59
112
  exports.debounce = debounce;
113
+ exports.delay = delay;
60
114
  exports.throttle = throttle;
115
+ exports.withTimeout = withTimeout;
package/dist/index.d.cts CHANGED
@@ -26,4 +26,27 @@ interface Throttled<A extends unknown[]> {
26
26
  */
27
27
  declare function throttle<A extends unknown[]>(fn: (...args: A) => void, interval: number, { leading, trailing }?: ThrottleOptions): Throttled<A>;
28
28
 
29
- export { type Debounced, type ThrottleOptions, type Throttled, debounce, throttle };
29
+ interface DelayOptions {
30
+ /** Abort to stop waiting early. The promise rejects with `signal.reason`. */
31
+ signal?: AbortSignal;
32
+ }
33
+ /**
34
+ * Resolves after `ms` milliseconds.
35
+ *
36
+ * Unlike a bare `setTimeout`, it rejects bad input (NaN, negative, Infinity)
37
+ * instead of silently firing immediately, and it can be cancelled.
38
+ */
39
+ declare function delay(ms: number, { signal }?: DelayOptions): Promise<void>;
40
+
41
+ declare class TimeoutError extends Error {
42
+ constructor(message?: string);
43
+ }
44
+ /**
45
+ * Rejects with `TimeoutError` if `promise` hasn't settled within `ms`.
46
+ *
47
+ * Note: this stops *waiting*, it does not stop the underlying work.
48
+ * To cancel a request too, pass an AbortSignal to it as well.
49
+ */
50
+ declare function withTimeout<T>(promise: PromiseLike<T>, ms: number, message?: string): Promise<T>;
51
+
52
+ export { type Debounced, type DelayOptions, type ThrottleOptions, type Throttled, TimeoutError, debounce, delay, throttle, withTimeout };
package/dist/index.d.ts CHANGED
@@ -26,4 +26,27 @@ interface Throttled<A extends unknown[]> {
26
26
  */
27
27
  declare function throttle<A extends unknown[]>(fn: (...args: A) => void, interval: number, { leading, trailing }?: ThrottleOptions): Throttled<A>;
28
28
 
29
- export { type Debounced, type ThrottleOptions, type Throttled, debounce, throttle };
29
+ interface DelayOptions {
30
+ /** Abort to stop waiting early. The promise rejects with `signal.reason`. */
31
+ signal?: AbortSignal;
32
+ }
33
+ /**
34
+ * Resolves after `ms` milliseconds.
35
+ *
36
+ * Unlike a bare `setTimeout`, it rejects bad input (NaN, negative, Infinity)
37
+ * instead of silently firing immediately, and it can be cancelled.
38
+ */
39
+ declare function delay(ms: number, { signal }?: DelayOptions): Promise<void>;
40
+
41
+ declare class TimeoutError extends Error {
42
+ constructor(message?: string);
43
+ }
44
+ /**
45
+ * Rejects with `TimeoutError` if `promise` hasn't settled within `ms`.
46
+ *
47
+ * Note: this stops *waiting*, it does not stop the underlying work.
48
+ * To cancel a request too, pass an AbortSignal to it as well.
49
+ */
50
+ declare function withTimeout<T>(promise: PromiseLike<T>, ms: number, message?: string): Promise<T>;
51
+
52
+ export { type Debounced, type DelayOptions, type ThrottleOptions, type Throttled, TimeoutError, debounce, delay, throttle, withTimeout };
package/dist/index.js CHANGED
@@ -1,12 +1,12 @@
1
1
  // src/async/debounce.ts
2
- function debounce(fn, delay) {
2
+ function debounce(fn, delay2) {
3
3
  let timer;
4
4
  const debounced = (...args) => {
5
5
  if (timer !== void 0) clearTimeout(timer);
6
6
  timer = setTimeout(() => {
7
7
  timer = void 0;
8
8
  fn(...args);
9
- }, delay);
9
+ }, delay2);
10
10
  };
11
11
  debounced.cancel = () => {
12
12
  if (timer !== void 0) clearTimeout(timer);
@@ -54,4 +54,56 @@ function throttle(fn, interval, { leading = true, trailing = true } = {}) {
54
54
  return throttled;
55
55
  }
56
56
 
57
- export { debounce, throttle };
57
+ // src/async/delay.ts
58
+ function delay(ms, { signal } = {}) {
59
+ if (typeof ms !== "number" || !Number.isFinite(ms) || ms < 0) {
60
+ return Promise.reject(
61
+ new RangeError(`delay: expected a finite, non-negative number of ms, got ${String(ms)}`)
62
+ );
63
+ }
64
+ if (signal?.aborted) return Promise.reject(signal.reason);
65
+ return new Promise((resolve, reject) => {
66
+ const onAbort = () => {
67
+ clearTimeout(timer);
68
+ reject(signal.reason);
69
+ };
70
+ const timer = setTimeout(() => {
71
+ signal?.removeEventListener("abort", onAbort);
72
+ resolve();
73
+ }, ms);
74
+ signal?.addEventListener("abort", onAbort, { once: true });
75
+ });
76
+ }
77
+
78
+ // src/async/withTimeout.ts
79
+ var TimeoutError = class extends Error {
80
+ constructor(message = "Operation timed out") {
81
+ super(message);
82
+ this.name = "TimeoutError";
83
+ }
84
+ };
85
+ function withTimeout(promise, ms, message) {
86
+ if (typeof ms !== "number" || !Number.isFinite(ms) || ms < 0) {
87
+ return Promise.reject(
88
+ new RangeError(`withTimeout: expected a finite, non-negative number of ms, got ${String(ms)}`)
89
+ );
90
+ }
91
+ return new Promise((resolve, reject) => {
92
+ const timer = setTimeout(
93
+ () => reject(new TimeoutError(message ?? `Timed out after ${ms}ms`)),
94
+ ms
95
+ );
96
+ Promise.resolve(promise).then(
97
+ (value) => {
98
+ clearTimeout(timer);
99
+ resolve(value);
100
+ },
101
+ (error) => {
102
+ clearTimeout(timer);
103
+ reject(error);
104
+ }
105
+ );
106
+ });
107
+ }
108
+
109
+ export { TimeoutError, debounce, delay, throttle, withTimeout };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@useless-engineer/utils",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "repo for small, utility functions by useless engineer",
5
5
  "author": "Useless Engineer",
6
6
  "keywords": [