@ix-xs/node-comfort 1.2.0 → 2.1.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.
Files changed (78) hide show
  1. package/CHANGELOG.md +340 -0
  2. package/README.md +137 -548
  3. package/index.js +80 -35
  4. package/package.json +161 -33
  5. package/src/Arr.js +1050 -418
  6. package/src/Async.js +817 -0
  7. package/src/Cache.js +433 -0
  8. package/src/Checker.js +1202 -539
  9. package/src/Cli.js +1293 -0
  10. package/src/Color.js +556 -0
  11. package/src/Crypto.js +1082 -0
  12. package/src/Emitter.js +356 -0
  13. package/src/Env.js +689 -0
  14. package/src/FS.js +1626 -894
  15. package/src/Func.js +823 -356
  16. package/src/Http.js +700 -0
  17. package/src/Id.js +568 -200
  18. package/src/Logger.js +963 -378
  19. package/src/Num.js +965 -360
  20. package/src/Obj.js +1077 -379
  21. package/src/SQLite.js +1629 -1005
  22. package/src/Schema.js +2101 -0
  23. package/src/Str.js +1209 -432
  24. package/src/Sys.js +654 -0
  25. package/src/Time.js +1533 -440
  26. package/src/Utils.js +342 -325
  27. package/src/errors.js +335 -0
  28. package/src/internal/shared.js +415 -0
  29. package/types/index.d.ts +735 -526
  30. package/types/src/Arr.d.ts +533 -145
  31. package/types/src/Arr.d.ts.map +1 -1
  32. package/types/src/Async.d.ts +372 -0
  33. package/types/src/Async.d.ts.map +1 -0
  34. package/types/src/Cache.d.ts +221 -0
  35. package/types/src/Cache.d.ts.map +1 -0
  36. package/types/src/Checker.d.ts +651 -232
  37. package/types/src/Checker.d.ts.map +1 -1
  38. package/types/src/Cli.d.ts +664 -0
  39. package/types/src/Cli.d.ts.map +1 -0
  40. package/types/src/Color.d.ts +300 -0
  41. package/types/src/Color.d.ts.map +1 -0
  42. package/types/src/Crypto.d.ts +620 -0
  43. package/types/src/Crypto.d.ts.map +1 -0
  44. package/types/src/Emitter.d.ts +189 -0
  45. package/types/src/Emitter.d.ts.map +1 -0
  46. package/types/src/Env.d.ts +412 -0
  47. package/types/src/Env.d.ts.map +1 -0
  48. package/types/src/FS.d.ts +717 -238
  49. package/types/src/FS.d.ts.map +1 -1
  50. package/types/src/Func.d.ts +508 -135
  51. package/types/src/Func.d.ts.map +1 -1
  52. package/types/src/Http.d.ts +444 -0
  53. package/types/src/Http.d.ts.map +1 -0
  54. package/types/src/Id.d.ts +199 -66
  55. package/types/src/Id.d.ts.map +1 -1
  56. package/types/src/Logger.d.ts +414 -46
  57. package/types/src/Logger.d.ts.map +1 -1
  58. package/types/src/Num.d.ts +514 -137
  59. package/types/src/Num.d.ts.map +1 -1
  60. package/types/src/Obj.d.ts +453 -104
  61. package/types/src/Obj.d.ts.map +1 -1
  62. package/types/src/SQLite.d.ts +754 -308
  63. package/types/src/SQLite.d.ts.map +1 -1
  64. package/types/src/Schema.d.ts +1189 -0
  65. package/types/src/Schema.d.ts.map +1 -0
  66. package/types/src/Str.d.ts +617 -166
  67. package/types/src/Str.d.ts.map +1 -1
  68. package/types/src/Sys.d.ts +314 -0
  69. package/types/src/Sys.d.ts.map +1 -0
  70. package/types/src/Time.d.ts +759 -204
  71. package/types/src/Time.d.ts.map +1 -1
  72. package/types/src/Utils.d.ts +158 -102
  73. package/types/src/Utils.d.ts.map +1 -1
  74. package/types/src/errors.d.ts +316 -0
  75. package/types/src/errors.d.ts.map +1 -0
  76. package/types/src/internal/shared.d.ts +123 -0
  77. package/types/src/internal/shared.d.ts.map +1 -0
  78. package/types/index.d.ts.map +0 -1
@@ -1,210 +1,583 @@
1
1
  /**
2
- * Creates a debounced version of a function that delays invocation until
3
- * `wait` ms have elapsed since the last call.
4
- *
5
- * The returned function exposes `.cancel()` and `.flush()`.
2
+ * Options for `debounce()`.
3
+ */
4
+ export type DebounceOptions = {
5
+ /**
6
+ * Call right away at the start of a burst.
7
+ */
8
+ leading?: boolean | undefined;
9
+ /**
10
+ * Call once the burst is over. Defaults to `true`.
11
+ */
12
+ trailing?: boolean | undefined;
13
+ /**
14
+ * Never postpone a call longer than this, even if calls keep coming. In ms, or like `"2s"`.
15
+ */
16
+ maxWait?: string | number | undefined;
17
+ };
18
+ /**
19
+ * A debounced function. `flush()` runs the pending call now, `cancel()`
20
+ * drops it, `pending()` tells you if there's one.
21
+ */
22
+ export type DebouncedFunction<F extends (...args: any[]) => any> = ((...args: Parameters<F>) => void) & {
23
+ cancel(): void;
24
+ flush(): void;
25
+ pending(): boolean;
26
+ };
27
+ /**
28
+ * Options for `throttle()`.
29
+ */
30
+ export type ThrottleOptions = {
31
+ /**
32
+ * Call right away on the first call. Defaults to `true`.
33
+ */
34
+ leading?: boolean | undefined;
35
+ /**
36
+ * Make a last call with the latest arguments at the end of the interval. Defaults to `true`.
37
+ */
38
+ trailing?: boolean | undefined;
39
+ };
40
+ /**
41
+ * A throttled function, with `cancel()`.
42
+ */
43
+ export type ThrottledFunction<F extends (...args: any[]) => any> = ((...args: Parameters<F>) => void) & {
44
+ cancel(): void;
45
+ };
46
+ /**
47
+ * Options for `memoize()`.
48
+ */
49
+ export type MemoizeOptions = {
50
+ /**
51
+ * Builds the cache key from the arguments. By default, primitives are compared by value, plain objects and arrays by content, anything else by identity.
52
+ */
53
+ resolver?: ((...args: any[]) => unknown) | undefined;
54
+ /**
55
+ * How long a result stays cached, in ms or like `"5m"`.
56
+ */
57
+ ttl?: string | number | undefined;
58
+ /**
59
+ * Maximum number of results; the least recently used goes first.
60
+ */
61
+ max?: number | undefined;
62
+ /**
63
+ * Keep rejected promises. By default they're dropped so the next call tries again.
64
+ */
65
+ cacheRejections?: boolean | undefined;
66
+ };
67
+ /**
68
+ * A memoized function, with its `cache`, `clear()` and `delete(...args)`.
69
+ */
70
+ export type MemoizedFunction<F extends (...args: any[]) => any> = F & {
71
+ cache: Map<unknown, {
72
+ value: ReturnType<F>;
73
+ expires: number;
74
+ }>;
75
+ clear(): void;
76
+ delete(...args: Parameters<F>): boolean;
77
+ };
78
+ /**
79
+ * Options for `retry()`.
80
+ */
81
+ export type RetryOptions = {
82
+ /**
83
+ * Total attempts, the first one included. Defaults to `3`.
84
+ */
85
+ attempts?: number | undefined;
86
+ /**
87
+ * Wait before the next attempt, in ms or like `"1s"`, or a function that computes it.
88
+ */
89
+ delay?: string | number | ((attempt: number, error: unknown) => number) | undefined;
90
+ /**
91
+ * Multiplies the delay after each failure: `2` gives 200, 400, 800... Defaults to `1`.
92
+ */
93
+ backoff?: number | undefined;
94
+ /**
95
+ * Cap for the computed delay.
96
+ */
97
+ maxDelay?: string | number | undefined;
98
+ /**
99
+ * Randomizes the delay, from `0` (none) to `1` (anywhere between 0 and 2x). Stops many clients from retrying in sync.
100
+ */
101
+ jitter?: number | undefined;
102
+ /**
103
+ * Return `false` to give up early, for example on a 4xx error.
104
+ */
105
+ shouldRetry?: ((error: unknown, attempt: number) => boolean | Promise<boolean>) | undefined;
106
+ /**
107
+ * Called before each wait, for logs or metrics.
108
+ */
109
+ onRetry?: ((error: unknown, attempt: number, delay: number) => void) | undefined;
110
+ /**
111
+ * Stops retrying. The attempt in progress isn't interrupted.
112
+ */
113
+ signal?: AbortSignal | undefined;
114
+ };
115
+ /**
116
+ * Options for `batch()`.
117
+ */
118
+ export type BatchOptions = {
119
+ /**
120
+ * How long to collect calls before running. By default, calls made in the same tick go together.
121
+ */
122
+ wait?: string | number | undefined;
123
+ /**
124
+ * Run as soon as this many calls are waiting.
125
+ */
126
+ max?: number | undefined;
127
+ };
128
+ /**
129
+ * A batched function. `flush()` runs the waiting calls now.
130
+ */
131
+ export type BatchedFunction<I, R> = ((item: I) => Promise<R>) & {
132
+ flush(): void;
133
+ };
134
+ /**
135
+ * Options for `debounce()`.
136
+ * @typedef {object} DebounceOptions
137
+ * @property {boolean} [leading] Call right away at the start of a burst.
138
+ * @property {boolean} [trailing] Call once the burst is over. Defaults to `true`.
139
+ * @property {number | string} [maxWait] Never postpone a call longer than this, even if calls keep coming. In ms, or like `"2s"`.
140
+ */
141
+ /**
142
+ * A debounced function. `flush()` runs the pending call now, `cancel()`
143
+ * drops it, `pending()` tells you if there's one.
144
+ * @template {(...args: any[]) => any} F
145
+ * @typedef {((...args: Parameters<F>) => void) & { cancel(): void, flush(): void, pending(): boolean }} DebouncedFunction
146
+ */
147
+ /**
148
+ * Options for `throttle()`.
149
+ * @typedef {object} ThrottleOptions
150
+ * @property {boolean} [leading] Call right away on the first call. Defaults to `true`.
151
+ * @property {boolean} [trailing] Make a last call with the latest arguments at the end of the interval. Defaults to `true`.
152
+ */
153
+ /**
154
+ * A throttled function, with `cancel()`.
155
+ * @template {(...args: any[]) => any} F
156
+ * @typedef {((...args: Parameters<F>) => void) & { cancel(): void }} ThrottledFunction
157
+ */
158
+ /**
159
+ * Options for `memoize()`.
160
+ * @typedef {object} MemoizeOptions
161
+ * @property {(...args: any[]) => unknown} [resolver] Builds the cache key from the arguments. By default, primitives are compared by value, plain objects and arrays by content, anything else by identity.
162
+ * @property {number | string} [ttl] How long a result stays cached, in ms or like `"5m"`.
163
+ * @property {number} [max] Maximum number of results; the least recently used goes first.
164
+ * @property {boolean} [cacheRejections] Keep rejected promises. By default they're dropped so the next call tries again.
165
+ */
166
+ /**
167
+ * A memoized function, with its `cache`, `clear()` and `delete(...args)`.
168
+ * @template {(...args: any[]) => any} F
169
+ * @typedef {F & { cache: Map<unknown, { value: ReturnType<F>, expires: number }>, clear(): void, delete(...args: Parameters<F>): boolean }} MemoizedFunction
170
+ */
171
+ /**
172
+ * Options for `retry()`.
173
+ * @typedef {object} RetryOptions
174
+ * @property {number} [attempts] Total attempts, the first one included. Defaults to `3`.
175
+ * @property {number | string | ((attempt: number, error: unknown) => number)} [delay] Wait before the next attempt, in ms or like `"1s"`, or a function that computes it.
176
+ * @property {number} [backoff] Multiplies the delay after each failure: `2` gives 200, 400, 800... Defaults to `1`.
177
+ * @property {number | string} [maxDelay] Cap for the computed delay.
178
+ * @property {number} [jitter] Randomizes the delay, from `0` (none) to `1` (anywhere between 0 and 2x). Stops many clients from retrying in sync.
179
+ * @property {(error: unknown, attempt: number) => boolean | Promise<boolean>} [shouldRetry] Return `false` to give up early, for example on a 4xx error.
180
+ * @property {(error: unknown, attempt: number, delay: number) => void} [onRetry] Called before each wait, for logs or metrics.
181
+ * @property {AbortSignal} [signal] Stops retrying. The attempt in progress isn't interrupted.
182
+ */
183
+ /**
184
+ * Options for `batch()`.
185
+ * @typedef {object} BatchOptions
186
+ * @property {number | string} [wait] How long to collect calls before running. By default, calls made in the same tick go together.
187
+ * @property {number} [max] Run as soon as this many calls are waiting.
188
+ */
189
+ /**
190
+ * A batched function. `flush()` runs the waiting calls now.
191
+ * @template I
192
+ * @template R
193
+ * @typedef {((item: I) => Promise<R>) & { flush(): void }} BatchedFunction
194
+ */
195
+ /**
196
+ * Waits for a pause of `wait` ms between calls, then runs once with the
197
+ * latest arguments. For search boxes, resizing, auto-saving...
6
198
  *
7
199
  * @example
8
- * const onResize = nodeComfort.func.debounce(() => render(), 200);
9
- * window.on("resize", onResize);
10
- * onResize.cancel(); // drop any pending call
200
+ * const search = func.debounce((query) => api.search(query), 300);
201
+ * input.on("input", (e) => search(e.target.value));
202
+ *
203
+ * const save = func.debounce(persist, 1000, { maxWait: 5000 }); // saves at least every 5s
11
204
  *
12
205
  * @template {(...args: any[]) => any} F
13
- * @param {F} fn - Function to debounce.
14
- * @param {number} wait - Delay in milliseconds.
15
- * @param {{ leading?: boolean }} [options] - Set `leading` to fire on the first call.
16
- * @returns {F & { cancel: () => void, flush: () => void }} The debounced function.
17
- */
18
- export function debounce<F extends (...args: any[]) => any>(fn: F, wait: number, options?: {
19
- leading?: boolean;
20
- }): F & {
21
- cancel: () => void;
22
- flush: () => void;
23
- };
206
+ * @param {F} fn
207
+ * @param {number | string} wait Pause needed, in ms or like `"300ms"`.
208
+ * @param {DebounceOptions} [options]
209
+ * @returns {DebouncedFunction<F>}
210
+ */
211
+ export function debounce<F extends (...args: any[]) => any>(fn: F, wait: number | string, options?: DebounceOptions): DebouncedFunction<F>;
24
212
  /**
25
- * Creates a throttled version of a function that runs at most once per
26
- * `wait` ms.
213
+ * Runs at most once every `wait` ms, however often it's called. For scroll
214
+ * handlers, progress reports, rate-limited APIs...
27
215
  *
28
216
  * @example
29
- * const onScroll = nodeComfort.func.throttle(() => update(), 100);
217
+ * const report = func.throttle((pct) => console.log(`${pct}%`), 1000);
30
218
  *
31
219
  * @template {(...args: any[]) => any} F
32
- * @param {F} fn - Function to throttle.
33
- * @param {number} wait - Minimum interval in milliseconds.
34
- * @param {{ trailing?: boolean }} [options] - Set `trailing` (default true) to run a final call.
35
- * @returns {F & { cancel: () => void }} The throttled function.
36
- */
37
- export function throttle<F extends (...args: any[]) => any>(fn: F, wait: number, options?: {
38
- trailing?: boolean;
39
- }): F & {
40
- cancel: () => void;
41
- };
220
+ * @param {F} fn
221
+ * @param {number | string} interval Minimum gap between calls, in ms or like `"1s"`.
222
+ * @param {ThrottleOptions} [options]
223
+ * @returns {ThrottledFunction<F>}
224
+ */
225
+ export function throttle<F extends (...args: any[]) => any>(fn: F, interval: number | string, options?: ThrottleOptions): ThrottledFunction<F>;
42
226
  /**
43
- * Wraps a function so it can only ever run once; subsequent calls return
44
- * the first result.
227
+ * Runs `fn` the first time only; later calls return the same result.
45
228
  *
46
229
  * @example
47
- * const init = nodeComfort.func.once(() => expensiveSetup());
230
+ * const connect = func.once(() => createPool(config));
48
231
  *
49
232
  * @template {(...args: any[]) => any} F
50
- * @param {F} fn - Function to wrap.
51
- * @returns {F} The single-call function.
233
+ * @param {F} fn
234
+ * @returns {F}
52
235
  */
53
236
  export function once<F extends (...args: any[]) => any>(fn: F): F;
54
237
  /**
55
- * Memoizes a function, caching results by argument key.
56
- *
57
- * By default the cache key is a JSON serialization of the arguments; pass a
58
- * `resolver` for custom keys.
238
+ * Caches results by arguments. Supports expiry (`ttl`), a size limit
239
+ * (`max`) and async functions: a failed promise isn't cached, so the next
240
+ * call tries again.
59
241
  *
60
242
  * @example
61
- * const fib = nodeComfort.func.memoize((n) => (n < 2 ? n : fib(n - 1) + fib(n - 2)));
243
+ * const getUser = func.memoize((id) => db.users.find(id), { ttl: 60_000, max: 1000 });
244
+ * getUser.delete(42); // forget one entry
245
+ * getUser.clear(); // forget everything
62
246
  *
63
247
  * @template {(...args: any[]) => any} F
64
- * @param {F} fn - Function to memoize.
65
- * @param {(...args: any[]) => any} [resolver] - Cache-key resolver.
66
- * @returns {F & { cache: Map<any, any> }} The memoized function.
248
+ * @param {F} fn
249
+ * @param {MemoizeOptions | ((...args: Parameters<F>) => unknown)} [options] Options, or a function that builds the cache key.
250
+ * @returns {MemoizedFunction<F>}
67
251
  */
68
- export function memoize<F extends (...args: any[]) => any>(fn: F, resolver?: (...args: any[]) => any): F & {
69
- cache: Map<any, any>;
70
- };
252
+ export function memoize<F extends (...args: any[]) => any>(fn: F, options?: MemoizeOptions | ((...args: Parameters<F>) => unknown)): MemoizedFunction<F>;
71
253
  /**
72
- * Retries an async function until it succeeds or the attempt budget runs out.
73
- *
74
- * Supports fixed or exponential backoff and a `shouldRetry` guard.
254
+ * Calls `fn` until it succeeds, waiting between attempts. If every attempt
255
+ * fails, the last error is thrown.
75
256
  *
76
257
  * @example
77
- * const data = await nodeComfort.func.retry(() => fetch(url), {
258
+ * const data = await func.retry(() => fetchJSON(url), {
78
259
  * attempts: 5,
79
260
  * delay: 200,
80
- * backoff: 2, // 200, 400, 800, ...
261
+ * backoff: 2, // 200, 400, 800, 1600 ms
262
+ * shouldRetry: (error) => !(error instanceof nc.errors.HttpError && error.status < 500),
81
263
  * });
82
264
  *
83
265
  * @template T
84
- * @param {() => T|Promise<T>} fn - Function to run.
85
- * @param {{
86
- * attempts?: number,
87
- * delay?: number,
88
- * backoff?: number,
89
- * maxDelay?: number,
90
- * shouldRetry?: (error: unknown, attempt: number) => boolean,
91
- * onRetry?: (error: unknown, attempt: number) => void
92
- * }} [options] - Retry options.
93
- * @returns {Promise<T>} The resolved value.
94
- * @throws Re-throws the last error if all attempts fail.
95
- */
96
- export function retry<T>(fn: () => T | Promise<T>, options?: {
97
- attempts?: number;
98
- delay?: number;
99
- backoff?: number;
100
- maxDelay?: number;
101
- shouldRetry?: (error: unknown, attempt: number) => boolean;
102
- onRetry?: (error: unknown, attempt: number) => void;
103
- }): Promise<T>;
104
- /**
105
- * Runs a promise (or async function) with a timeout, rejecting if it takes
106
- * too long.
107
- *
108
- * @example
109
- * await nodeComfort.func.timeout(fetch(url), 5000, "fetch too slow");
266
+ * @param {(attempt: number) => T | PromiseLike<T>} fn Receives the attempt number, starting at 1.
267
+ * @param {RetryOptions} [options]
268
+ * @returns {Promise<Awaited<T>>}
269
+ * @throws The last error, or an `AbortError` if the signal fires.
270
+ */
271
+ export function retry<T>(fn: (attempt: number) => T | PromiseLike<T>, options?: RetryOptions): Promise<Awaited<T>>;
272
+ /**
273
+ * Rejects with a `TimeoutError` if the promise takes longer than `ms`. Pass
274
+ * a function to get an `AbortSignal` that fires when time is up, so the work
275
+ * actually stops instead of running on in the background.
276
+ *
277
+ * @example
278
+ * const res = await func.timeout(fetch(url), 5000);
279
+ * const rows = await func.timeout(() => slowQuery(), 2000, "Database too slow");
280
+ *
281
+ * @template T
282
+ * @param {PromiseLike<T> | ((signal: AbortSignal) => PromiseLike<T> | T)} promise A promise, or a function that returns one. The function gets a signal that aborts on timeout.
283
+ * @param {number | string} ms The limit, in ms or like `"5s"`. `Infinity` means no limit.
284
+ * @param {string} [message]
285
+ * @returns {Promise<Awaited<T>>}
286
+ * @throws {TimeoutError}
287
+ */
288
+ export function timeout<T>(promise: PromiseLike<T> | ((signal: AbortSignal) => PromiseLike<T> | T), ms: number | string, message?: string): Promise<Awaited<T>>;
289
+ /**
290
+ * Waits `ms`, then calls `fn` with the arguments and resolves with its result.
291
+ *
292
+ * @example
293
+ * await func.delay(() => console.log("1s later"), 1000);
294
+ *
295
+ * @template {(...args: any[]) => any} F
296
+ * @param {F} fn
297
+ * @param {number | string} ms In ms, or like `"2s"`.
298
+ * @param {Parameters<F>} args
299
+ * @returns {Promise<Awaited<ReturnType<F>>>}
300
+ */
301
+ export function delay<F extends (...args: any[]) => any>(fn: F, ms: number | string, ...args: Parameters<F>): Promise<Awaited<ReturnType<F>>>;
302
+ /**
303
+ * Awaits a promise and gives you `[error, value]` instead of throwing, so
304
+ * you don't need a try/catch.
305
+ *
306
+ * @example
307
+ * const [error, user] = await func.to(db.users.find(id));
308
+ * if (error) return res.status(500).send(error.message);
110
309
  *
111
310
  * @template T
112
- * @param {Promise<T>|(() => Promise<T>)} promise - Promise or async function.
113
- * @param {number} ms - Timeout in milliseconds.
114
- * @param {string} [message="Operation timed out"] - Error message on timeout.
115
- * @returns {Promise<T>} The resolved value.
116
- * @throws {Error} If the timeout elapses first.
311
+ * @template [E=Error]
312
+ * @param {PromiseLike<T> | (() => PromiseLike<T> | T)} promise A promise, or a function (sync errors are caught too).
313
+ * @returns {Promise<[E, undefined] | [null, Awaited<T>]>}
117
314
  */
118
- export function timeout<T>(promise: Promise<T> | (() => Promise<T>), ms: number, message?: string): Promise<T>;
315
+ export function to<T, E = Error>(promise: PromiseLike<T> | (() => PromiseLike<T> | T)): Promise<[E, undefined] | [null, Awaited<T>]>;
119
316
  /**
120
- * Composes functions left-to-right: `pipe(f, g)(x)` is `g(f(x))`.
317
+ * Wraps a function so it resolves to `[error, value]` instead of throwing.
121
318
  *
122
- * The first function may take multiple arguments; the rest take one.
319
+ * @example
320
+ * const safeParse = func.attempt(JSON.parse);
321
+ * const [error, data] = await safeParse(input);
322
+ *
323
+ * @template {(...args: any[]) => any} F
324
+ * @param {F} fn
325
+ * @returns {(...args: Parameters<F>) => Promise<[unknown, undefined] | [null, Awaited<ReturnType<F>>]>}
326
+ */
327
+ export function attempt<F extends (...args: any[]) => any>(fn: F): (...args: Parameters<F>) => Promise<[unknown, undefined] | [null, Awaited<ReturnType<F>>]>;
328
+ /**
329
+ * Turns a callback-style function into one that returns a promise.
123
330
  *
124
331
  * @example
125
- * const process = nodeComfort.func.pipe(
126
- * (s) => s.trim(),
127
- * (s) => s.toUpperCase(),
128
- * );
129
- * process(" hi "); // "HI"
332
+ * const lookup = func.promisify(dns.lookup);
333
+ * const address = await lookup("nodejs.org");
130
334
  *
131
- * @param {...((arg: any) => any)} fns - Functions to compose.
132
- * @returns {(...args: any[]) => any} The composed function.
335
+ * @param {(...args: any[]) => void} fn
336
+ * @returns {(...args: any[]) => Promise<any>}
337
+ */
338
+ export function promisify(fn: (...args: any[]) => void): (...args: any[]) => Promise<any>;
339
+ /**
340
+ * Turns many single calls into few grouped ones. Calls made close together
341
+ * are collected, `fn` runs once with all their items, and each caller gets
342
+ * its own result. The classic cure for "N+1 queries".
343
+ *
344
+ * @example
345
+ * const loadUser = func.batch(async (ids) => {
346
+ * const users = await db.getAll("users", { id: ids });
347
+ * return ids.map((id) => users.find((user) => user.id === id));
348
+ * });
349
+ * // One query, however many places ask at once:
350
+ * const [ada, bob] = await Promise.all([loadUser(1), loadUser(2)]);
351
+ *
352
+ * @template I
353
+ * @template R
354
+ * @param {(items: I[]) => ReadonlyArray<R | Error> | Map<I, R | Error> | PromiseLike<ReadonlyArray<R | Error> | Map<I, R | Error>>} fn Gets every waiting item and returns one result per item, in the same order, or a `Map` keyed by item. An `Error` as a result rejects that call only.
355
+ * @param {BatchOptions} [options]
356
+ * @returns {BatchedFunction<I, R>}
357
+ */
358
+ export function batch<I, R>(fn: (items: I[]) => ReadonlyArray<R | Error> | Map<I, R | Error> | PromiseLike<ReadonlyArray<R | Error> | Map<I, R | Error>>, options?: BatchOptions): BatchedFunction<I, R>;
359
+ /**
360
+ * Makes concurrent calls with the same arguments share one run. Once it
361
+ * settles, the next call starts a new one: nothing is cached.
362
+ *
363
+ * @example
364
+ * const refreshToken = func.dedupe(async () => (await api.post("auth/refresh")).data);
365
+ * // Ten requests hit a 401 at once: one refresh, ten answers.
366
+ *
367
+ * @template {(...args: any[]) => any} F
368
+ * @param {F} fn
369
+ * @param {(...args: Parameters<F>) => unknown} [keyOf] Builds the key that identifies a call. Defaults to its arguments.
370
+ * @returns {(...args: Parameters<F>) => Promise<Awaited<ReturnType<F>>>}
371
+ */
372
+ export function dedupe<F extends (...args: any[]) => any>(fn: F, keyOf?: (...args: Parameters<F>) => unknown): (...args: Parameters<F>) => Promise<Awaited<ReturnType<F>>>;
373
+ /**
374
+ * Chains functions from left to right: `pipe(f, g)(x)` is `g(f(x))`.
375
+ * Types flow through up to 6 functions.
376
+ *
377
+ * @example
378
+ * const toSlug = func.pipe(nc.str.deburr, nc.str.kebabCase);
379
+ * toSlug("Crème Brûlée"); // "creme-brulee"
380
+ *
381
+ * @template {any[]} A
382
+ * @template B
383
+ * @overload
384
+ * @param {(...args: A) => B} f1
385
+ * @returns {(...args: A) => B}
386
+ */
387
+ export function pipe<A extends any[], B>(f1: (...args: A) => B): (...args: A) => B;
388
+ /**
389
+ * Chains functions from left to right.
390
+ *
391
+ * @template {any[]} A
392
+ * @template B
393
+ * @template C
394
+ * @overload
395
+ * @param {(...args: A) => B} f1
396
+ * @param {(b: B) => C} f2
397
+ * @returns {(...args: A) => C}
398
+ */
399
+ export function pipe<A extends any[], B, C>(f1: (...args: A) => B, f2: (b: B) => C): (...args: A) => C;
400
+ /**
401
+ * Chains functions from left to right.
402
+ *
403
+ * @template {any[]} A
404
+ * @template B
405
+ * @template C
406
+ * @template D
407
+ * @overload
408
+ * @param {(...args: A) => B} f1
409
+ * @param {(b: B) => C} f2
410
+ * @param {(c: C) => D} f3
411
+ * @returns {(...args: A) => D}
412
+ */
413
+ export function pipe<A extends any[], B, C, D>(f1: (...args: A) => B, f2: (b: B) => C, f3: (c: C) => D): (...args: A) => D;
414
+ /**
415
+ * Chains functions from left to right.
416
+ *
417
+ * @template {any[]} A
418
+ * @template B
419
+ * @template C
420
+ * @template D
421
+ * @template E
422
+ * @overload
423
+ * @param {(...args: A) => B} f1
424
+ * @param {(b: B) => C} f2
425
+ * @param {(c: C) => D} f3
426
+ * @param {(d: D) => E} f4
427
+ * @returns {(...args: A) => E}
428
+ */
429
+ export function pipe<A extends any[], B, C, D, E>(f1: (...args: A) => B, f2: (b: B) => C, f3: (c: C) => D, f4: (d: D) => E): (...args: A) => E;
430
+ /**
431
+ * Chains functions from left to right.
432
+ *
433
+ * @template {any[]} A
434
+ * @template B
435
+ * @template C
436
+ * @template D
437
+ * @template E
438
+ * @template F
439
+ * @overload
440
+ * @param {(...args: A) => B} f1
441
+ * @param {(b: B) => C} f2
442
+ * @param {(c: C) => D} f3
443
+ * @param {(d: D) => E} f4
444
+ * @param {(e: E) => F} f5
445
+ * @returns {(...args: A) => F}
446
+ */
447
+ export function pipe<A extends any[], B, C, D, E, F>(f1: (...args: A) => B, f2: (b: B) => C, f3: (c: C) => D, f4: (d: D) => E, f5: (e: E) => F): (...args: A) => F;
448
+ /**
449
+ * Chains functions from left to right.
450
+ *
451
+ * @template {any[]} A
452
+ * @template B
453
+ * @template C
454
+ * @template D
455
+ * @template E
456
+ * @template F
457
+ * @template G
458
+ * @overload
459
+ * @param {(...args: A) => B} f1
460
+ * @param {(b: B) => C} f2
461
+ * @param {(c: C) => D} f3
462
+ * @param {(d: D) => E} f4
463
+ * @param {(e: E) => F} f5
464
+ * @param {(f: F) => G} f6
465
+ * @returns {(...args: A) => G}
466
+ */
467
+ export function pipe<A extends any[], B, C, D, E, F, G>(f1: (...args: A) => B, f2: (b: B) => C, f3: (c: C) => D, f4: (d: D) => E, f5: (e: E) => F, f6: (f: F) => G): (...args: A) => G;
468
+ /**
469
+ * Chains any number of functions from left to right.
470
+ *
471
+ * @overload
472
+ * @param {...((arg: any) => any)} fns
473
+ * @returns {(...args: any[]) => any}
133
474
  */
134
475
  export function pipe(...fns: ((arg: any) => any)[]): (...args: any[]) => any;
135
476
  /**
136
- * Composes functions right-to-left: `compose(f, g)(x)` is `f(g(x))`.
477
+ * Like `pipe()`, for functions that may be async: each one is awaited
478
+ * before the next runs.
137
479
  *
138
480
  * @example
139
- * const process = nodeComfort.func.compose(
140
- * (s) => s.toUpperCase(),
141
- * (s) => s.trim(),
142
- * );
143
- * process(" hi "); // "HI"
481
+ * const publish = func.pipeAsync(loadDraft, renderMarkdown, uploadToCDN);
482
+ * const url = await publish(postId);
144
483
  *
145
- * @param {...((arg: any) => any)} fns - Functions to compose.
146
- * @returns {(...args: any[]) => any} The composed function.
484
+ * @param {...((...args: any[]) => any)} fns
485
+ * @returns {(...args: any[]) => Promise<any>}
147
486
  */
148
- export function compose(...fns: ((arg: any) => any)[]): (...args: any[]) => any;
487
+ export function pipeAsync(...fns: ((...args: any[]) => any)[]): (...args: any[]) => Promise<any>;
149
488
  /**
150
- * Curries a function so it can be called with arguments one (or a few) at a time.
489
+ * Chains functions from right to left: `compose(f, g)(x)` is `f(g(x))`.
490
+ * `pipe()` reads in execution order and is better typed.
151
491
  *
152
492
  * @example
153
- * const add = nodeComfort.func.curry((a, b, c) => a + b + c);
154
- * add(1)(2)(3); // 6
155
- * add(1, 2)(3); // 6
493
+ * const shout = func.compose((s) => s + "!", (s) => s.toUpperCase());
494
+ * shout("hi"); // "HI!"
156
495
  *
157
- * @param {(...args: any[]) => any} fn - Function to curry.
158
- * @param {number} [arity=fn.length] - Number of arguments to wait for.
159
- * @returns {(...args: any[]) => any} The curried function.
496
+ * @param {...((...args: any[]) => any)} fns
497
+ * @returns {(...args: any[]) => any}
498
+ */
499
+ export function compose(...fns: ((...args: any[]) => any)[]): (...args: any[]) => any;
500
+ /**
501
+ * Lets you pass a function's arguments a few at a time.
502
+ *
503
+ * @example
504
+ * const add = func.curry((a, b, c) => a + b + c);
505
+ * add(1)(2)(3); // 6
506
+ * add(1, 2)(3); // 6
507
+ *
508
+ * @param {(...args: any[]) => any} fn
509
+ * @param {number} [arity=fn.length] How many arguments to collect before calling.
510
+ * @returns {(...args: any[]) => any}
160
511
  */
161
512
  export function curry(fn: (...args: any[]) => any, arity?: number): (...args: any[]) => any;
162
513
  /**
163
- * Returns a function that negates the result of `predicate`.
514
+ * Fixes the first arguments of a function.
515
+ *
516
+ * @example
517
+ * const hello = func.partial((greeting, name) => `${greeting}, ${name}!`, "Hello");
518
+ * hello("Ada"); // "Hello, Ada!"
519
+ *
520
+ * @template {any[]} P
521
+ * @template {any[]} R
522
+ * @template T
523
+ * @param {(...args: [...P, ...R]) => T} fn
524
+ * @param {P} preset
525
+ * @returns {(...args: R) => T}
526
+ */
527
+ export function partial<P extends any[], R extends any[], T>(fn: (...args: [...P, ...R]) => T, ...preset: P): (...args: R) => T;
528
+ /**
529
+ * The opposite of a predicate.
164
530
  *
165
531
  * @example
166
- * const isOdd = nodeComfort.func.negate((n) => n % 2 === 0);
532
+ * files.filter(func.negate((name) => name.startsWith(".")));
167
533
  *
168
- * @param {(...args: any[]) => boolean} predicate - Predicate to negate.
169
- * @returns {(...args: any[]) => boolean} The negated predicate.
534
+ * @template {any[]} A
535
+ * @param {(...args: A) => unknown} predicate
536
+ * @returns {(...args: A) => boolean}
170
537
  */
171
- export function negate(predicate: (...args: any[]) => boolean): (...args: any[]) => boolean;
538
+ export function negate<A extends any[]>(predicate: (...args: A) => unknown): (...args: A) => boolean;
172
539
  /**
173
- * Wraps a function so that it is only actually invoked from the `n`-th call on.
540
+ * Does nothing for the first `n - 1` calls, then calls `fn` every time.
174
541
  *
175
542
  * @example
176
- * const save = nodeComfort.func.after(2, () => persist());
177
- * save(); save(); // persists on the 2nd call
543
+ * const done = func.after(3, () => console.log("All 3 uploads finished"));
544
+ * uploads.forEach((upload) => upload.on("end", done));
178
545
  *
179
546
  * @template {(...args: any[]) => any} F
180
- * @param {number} n - Number of calls before `fn` fires.
181
- * @param {F} fn - Function to wrap.
182
- * @returns {F} The wrapped function.
547
+ * @param {number} n
548
+ * @param {F} fn
549
+ * @returns {(...args: Parameters<F>) => ReturnType<F> | undefined}
183
550
  */
184
- export function after<F extends (...args: any[]) => any>(n: number, fn: F): F;
551
+ export function after<F extends (...args: any[]) => any>(n: number, fn: F): (...args: Parameters<F>) => ReturnType<F> | undefined;
185
552
  /**
186
- * Turns a Node-style callback function (`(err, result) => ...`) into one that
187
- * returns a Promise.
553
+ * Calls `fn` for the first `n - 1` calls, then keeps returning the last
554
+ * result.
188
555
  *
189
556
  * @example
190
- * const readFile = nodeComfort.func.promisify(fs.readFile);
191
- * const data = await readFile("a.txt", "utf8");
557
+ * const tryLogin = func.before(4, login); // 3 real attempts at most
558
+ *
559
+ * @template {(...args: any[]) => any} F
560
+ * @param {number} n
561
+ * @param {F} fn
562
+ * @returns {(...args: Parameters<F>) => ReturnType<F> | undefined}
563
+ */
564
+ export function before<F extends (...args: any[]) => any>(n: number, fn: F): (...args: Parameters<F>) => ReturnType<F> | undefined;
565
+ /**
566
+ * Does nothing. Useful as a default callback.
192
567
  *
193
- * @param {(...args: any[]) => void} fn - Callback-style function.
194
- * @returns {(...args: any[]) => Promise<any>} A promise-returning function.
568
+ * @param {...unknown} _args
569
+ * @returns {void}
195
570
  */
196
- export function promisify(fn: (...args: any[]) => void): (...args: any[]) => Promise<any>;
571
+ export function noop(..._args: unknown[]): void;
197
572
  /**
198
- * Wraps an async function so it resolves to a `[error, result]` tuple instead
199
- * of throwing • Go-style error handling.
573
+ * Returns its argument.
200
574
  *
201
575
  * @example
202
- * const [err, user] = await nodeComfort.func.attempt(() => getUser(id))();
203
- * if (err) return handle(err);
576
+ * [0, 1, 2].filter(func.identity); // [1, 2]
204
577
  *
205
578
  * @template T
206
- * @param {(...args: any[]) => Promise<T>|T} fn - Function to wrap.
207
- * @returns {(...args: any[]) => Promise<[unknown, T|undefined]>} The safe function.
579
+ * @param {T} value
580
+ * @returns {T}
208
581
  */
209
- export function attempt<T>(fn: (...args: any[]) => Promise<T> | T): (...args: any[]) => Promise<[unknown, T | undefined]>;
582
+ export function identity<T>(value: T): T;
210
583
  //# sourceMappingURL=Func.d.ts.map