@useless-engineer/utils 0.1.2 → 0.1.3
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 +77 -5
- package/dist/index.cjs +32 -0
- package/dist/index.d.cts +26 -1
- package/dist/index.d.ts +26 -1
- package/dist/index.js +31 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -155,6 +155,78 @@ Returns a function with `.cancel()`.
|
|
|
155
155
|
|
|
156
156
|
</details>
|
|
157
157
|
|
|
158
|
+
<details>
|
|
159
|
+
<summary><code>delay(ms, options?)</code> <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>
|
|
160
|
+
|
|
161
|
+
<br>
|
|
162
|
+
|
|
163
|
+
`await`-able sleep that rejects bad input (negative, `NaN`, `Infinity`) instead of silently firing instantly, and can be cancelled.
|
|
164
|
+
|
|
165
|
+
```ts
|
|
166
|
+
await delay(500);
|
|
167
|
+
|
|
168
|
+
const ctrl = new AbortController();
|
|
169
|
+
delay(5000, { signal: ctrl.signal }).catch(() => {});
|
|
170
|
+
ctrl.abort(); // stops waiting; the promise rejects with the signal's reason
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
</details>
|
|
174
|
+
|
|
175
|
+
<details>
|
|
176
|
+
<summary><code>withTimeout(promise, ms, message?)</code> <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>
|
|
177
|
+
|
|
178
|
+
<br>
|
|
179
|
+
|
|
180
|
+
Rejects with a `TimeoutError` if the promise takes too long. Clears its timer once the promise settles.
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
try {
|
|
184
|
+
const user = await withTimeout(fetchUser(id), 3000);
|
|
185
|
+
} catch (e) {
|
|
186
|
+
if (e instanceof TimeoutError) showRetry();
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
> ⚠️ This stops _waiting_, not the work. A timed-out `fetch` keeps running unless you also pass it an `AbortSignal`.
|
|
191
|
+
|
|
192
|
+
</details>
|
|
193
|
+
|
|
194
|
+
<details>
|
|
195
|
+
<summary><code>safeParseJSON(text, fallback?)</code> <img alt="live" src="https://img.shields.io/badge/-live-22c55e?style=flat-square"> <img alt="json" src="https://img.shields.io/badge/-json-f97316?style=flat-square"></summary>
|
|
196
|
+
|
|
197
|
+
<br>
|
|
198
|
+
|
|
199
|
+
`JSON.parse` that never throws. Returns the fallback for invalid JSON or for `null`/`undefined` input (like a missing `AsyncStorage` key).
|
|
200
|
+
|
|
201
|
+
```ts
|
|
202
|
+
const settings = safeParseJSON(raw, { theme: "light" });
|
|
203
|
+
safeParseJSON("0", 99); // 0, valid falsy values are kept
|
|
204
|
+
safeParseJSON("{oops", 1); // 1
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
> The type `T` is a promise from you, not a check. Validate untrusted data before relying on its shape.
|
|
208
|
+
|
|
209
|
+
</details>
|
|
210
|
+
|
|
211
|
+
<details>
|
|
212
|
+
<summary><code>safeStringify(value, options?)</code> <img alt="live" src="https://img.shields.io/badge/-live-22c55e?style=flat-square"> <img alt="json" src="https://img.shields.io/badge/-json-f97316?style=flat-square"></summary>
|
|
213
|
+
|
|
214
|
+
<br>
|
|
215
|
+
|
|
216
|
+
`JSON.stringify` that never throws on circular references or `BigInt`.
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
const a = { name: "a" };
|
|
220
|
+
a.self = a;
|
|
221
|
+
safeStringify(a); // '{"name":"a","self":"[Circular]"}'
|
|
222
|
+
safeStringify({ n: 10n }); // '{"n":"10"}'
|
|
223
|
+
safeStringify(data, { space: 2 });
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Shared (non-circular) references are kept as they are. Returns `undefined` when there's no JSON form, like `JSON.stringify`.
|
|
227
|
+
|
|
228
|
+
</details>
|
|
229
|
+
|
|
158
230
|
### React Native
|
|
159
231
|
|
|
160
232
|
<details>
|
|
@@ -178,11 +250,11 @@ const behavior = isIOS ? "padding" : "height";
|
|
|
178
250
|
|
|
179
251
|
Released one or two at a time, each matching a post that's already live.
|
|
180
252
|
|
|
181
|
-
| Next up | Entry | Origin
|
|
182
|
-
| ------------------------------------------------------------------------------------------------------------------------ | ------------ |
|
|
183
|
-
| 
|
|
184
|
-
|  `getAccessibilityProps`, `createShadow` | react-native | Accessibility and iOS-only shadows
|
|
185
|
-
|  `useInterval`, `useCountdown`, `useIsMounted` | react | Stale closures and unmounted updates
|
|
253
|
+
| Next up | Entry | Origin |
|
|
254
|
+
| ------------------------------------------------------------------------------------------------------------------------ | ------------ | -------------------------------------- |
|
|
255
|
+
|  `deepClone`, `settle` helpers | core | Shallow-copy and `Promise.all` gotchas |
|
|
256
|
+
|  `getAccessibilityProps`, `createShadow` | react-native | Accessibility and iOS-only shadows |
|
|
257
|
+
|  `useInterval`, `useCountdown`, `useIsMounted` | react | Stale closures and unmounted updates |
|
|
186
258
|
|
|
187
259
|
---
|
|
188
260
|
|
package/dist/index.cjs
CHANGED
|
@@ -108,8 +108,40 @@ function withTimeout(promise, ms, message) {
|
|
|
108
108
|
});
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
+
// src/json/safeParseJSON.ts
|
|
112
|
+
function safeParseJSON(text, fallback) {
|
|
113
|
+
if (typeof text !== "string") return fallback;
|
|
114
|
+
try {
|
|
115
|
+
return JSON.parse(text);
|
|
116
|
+
} catch {
|
|
117
|
+
return fallback;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// src/json/safeStringify.ts
|
|
122
|
+
function safeStringify(value, { space } = {}) {
|
|
123
|
+
const ancestors = [];
|
|
124
|
+
function replacer(_key, val) {
|
|
125
|
+
if (typeof val === "bigint") return val.toString();
|
|
126
|
+
if (typeof val !== "object" || val === null) return val;
|
|
127
|
+
while (ancestors.length > 0 && ancestors[ancestors.length - 1] !== this) {
|
|
128
|
+
ancestors.pop();
|
|
129
|
+
}
|
|
130
|
+
if (ancestors.includes(val)) return "[Circular]";
|
|
131
|
+
ancestors.push(val);
|
|
132
|
+
return val;
|
|
133
|
+
}
|
|
134
|
+
try {
|
|
135
|
+
return JSON.stringify(value, replacer, space);
|
|
136
|
+
} catch {
|
|
137
|
+
return void 0;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
111
141
|
exports.TimeoutError = TimeoutError;
|
|
112
142
|
exports.debounce = debounce;
|
|
113
143
|
exports.delay = delay;
|
|
144
|
+
exports.safeParseJSON = safeParseJSON;
|
|
145
|
+
exports.safeStringify = safeStringify;
|
|
114
146
|
exports.throttle = throttle;
|
|
115
147
|
exports.withTimeout = withTimeout;
|
package/dist/index.d.cts
CHANGED
|
@@ -49,4 +49,29 @@ declare class TimeoutError extends Error {
|
|
|
49
49
|
*/
|
|
50
50
|
declare function withTimeout<T>(promise: PromiseLike<T>, ms: number, message?: string): Promise<T>;
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
/**
|
|
53
|
+
* Parses JSON without throwing. Returns `fallback` (or `undefined`) when the
|
|
54
|
+
* input is not a string or is not valid JSON.
|
|
55
|
+
*
|
|
56
|
+
* The result is NOT validated: `T` is a promise from you, not a check.
|
|
57
|
+
* Validate untrusted data before relying on its shape.
|
|
58
|
+
*/
|
|
59
|
+
declare function safeParseJSON<T>(text: string | null | undefined, fallback: T): T;
|
|
60
|
+
declare function safeParseJSON<T = unknown>(text: string | null | undefined): T | undefined;
|
|
61
|
+
|
|
62
|
+
interface SafeStringifyOptions {
|
|
63
|
+
/** Indentation, same as the third argument of JSON.stringify. */
|
|
64
|
+
space?: string | number;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Like JSON.stringify, but never throws:
|
|
68
|
+
* - circular references become "[Circular]" (shared, non-circular references are kept)
|
|
69
|
+
* - BigInt values become strings
|
|
70
|
+
* - anything else that throws (e.g. a failing toJSON) returns `undefined`
|
|
71
|
+
*
|
|
72
|
+
* Like JSON.stringify, returns `undefined` for top-level values with no JSON form
|
|
73
|
+
* (undefined, functions, symbols).
|
|
74
|
+
*/
|
|
75
|
+
declare function safeStringify(value: unknown, { space }?: SafeStringifyOptions): string | undefined;
|
|
76
|
+
|
|
77
|
+
export { type Debounced, type DelayOptions, type SafeStringifyOptions, type ThrottleOptions, type Throttled, TimeoutError, debounce, delay, safeParseJSON, safeStringify, throttle, withTimeout };
|
package/dist/index.d.ts
CHANGED
|
@@ -49,4 +49,29 @@ declare class TimeoutError extends Error {
|
|
|
49
49
|
*/
|
|
50
50
|
declare function withTimeout<T>(promise: PromiseLike<T>, ms: number, message?: string): Promise<T>;
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
/**
|
|
53
|
+
* Parses JSON without throwing. Returns `fallback` (or `undefined`) when the
|
|
54
|
+
* input is not a string or is not valid JSON.
|
|
55
|
+
*
|
|
56
|
+
* The result is NOT validated: `T` is a promise from you, not a check.
|
|
57
|
+
* Validate untrusted data before relying on its shape.
|
|
58
|
+
*/
|
|
59
|
+
declare function safeParseJSON<T>(text: string | null | undefined, fallback: T): T;
|
|
60
|
+
declare function safeParseJSON<T = unknown>(text: string | null | undefined): T | undefined;
|
|
61
|
+
|
|
62
|
+
interface SafeStringifyOptions {
|
|
63
|
+
/** Indentation, same as the third argument of JSON.stringify. */
|
|
64
|
+
space?: string | number;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Like JSON.stringify, but never throws:
|
|
68
|
+
* - circular references become "[Circular]" (shared, non-circular references are kept)
|
|
69
|
+
* - BigInt values become strings
|
|
70
|
+
* - anything else that throws (e.g. a failing toJSON) returns `undefined`
|
|
71
|
+
*
|
|
72
|
+
* Like JSON.stringify, returns `undefined` for top-level values with no JSON form
|
|
73
|
+
* (undefined, functions, symbols).
|
|
74
|
+
*/
|
|
75
|
+
declare function safeStringify(value: unknown, { space }?: SafeStringifyOptions): string | undefined;
|
|
76
|
+
|
|
77
|
+
export { type Debounced, type DelayOptions, type SafeStringifyOptions, type ThrottleOptions, type Throttled, TimeoutError, debounce, delay, safeParseJSON, safeStringify, throttle, withTimeout };
|
package/dist/index.js
CHANGED
|
@@ -106,4 +106,34 @@ function withTimeout(promise, ms, message) {
|
|
|
106
106
|
});
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
|
|
109
|
+
// src/json/safeParseJSON.ts
|
|
110
|
+
function safeParseJSON(text, fallback) {
|
|
111
|
+
if (typeof text !== "string") return fallback;
|
|
112
|
+
try {
|
|
113
|
+
return JSON.parse(text);
|
|
114
|
+
} catch {
|
|
115
|
+
return fallback;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// src/json/safeStringify.ts
|
|
120
|
+
function safeStringify(value, { space } = {}) {
|
|
121
|
+
const ancestors = [];
|
|
122
|
+
function replacer(_key, val) {
|
|
123
|
+
if (typeof val === "bigint") return val.toString();
|
|
124
|
+
if (typeof val !== "object" || val === null) return val;
|
|
125
|
+
while (ancestors.length > 0 && ancestors[ancestors.length - 1] !== this) {
|
|
126
|
+
ancestors.pop();
|
|
127
|
+
}
|
|
128
|
+
if (ancestors.includes(val)) return "[Circular]";
|
|
129
|
+
ancestors.push(val);
|
|
130
|
+
return val;
|
|
131
|
+
}
|
|
132
|
+
try {
|
|
133
|
+
return JSON.stringify(value, replacer, space);
|
|
134
|
+
} catch {
|
|
135
|
+
return void 0;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export { TimeoutError, debounce, delay, safeParseJSON, safeStringify, throttle, withTimeout };
|