@planet-matrix/mobius-model 0.3.0 → 0.4.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/CHANGELOG.md +7 -0
- package/README.md +4 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +18 -3
- package/package.json +3 -3
- package/scripts/build.ts +4 -4
- package/src/basic/README.md +143 -0
- package/src/basic/array.ts +872 -0
- package/src/basic/bigint.ts +114 -0
- package/src/basic/boolean.ts +180 -0
- package/src/basic/error.ts +51 -0
- package/src/basic/function.ts +453 -0
- package/src/basic/helper.ts +276 -0
- package/src/basic/index.ts +15 -0
- package/src/basic/is.ts +320 -0
- package/src/basic/number.ts +178 -0
- package/src/basic/object.ts +58 -0
- package/src/basic/promise.ts +464 -0
- package/src/basic/regexp.ts +7 -0
- package/src/basic/stream.ts +140 -0
- package/src/basic/string.ts +308 -0
- package/src/basic/symbol.ts +164 -0
- package/src/basic/temporal.ts +224 -0
- package/src/index.ts +2 -0
- package/src/type/README.md +330 -0
- package/src/type/array.ts +5 -0
- package/src/type/boolean.ts +471 -0
- package/src/type/class.ts +419 -0
- package/src/type/function.ts +1519 -0
- package/src/type/helper.ts +135 -0
- package/src/type/index.ts +14 -0
- package/src/type/intersection.ts +93 -0
- package/src/type/is.ts +247 -0
- package/src/type/iteration.ts +233 -0
- package/src/type/number.ts +732 -0
- package/src/type/object.ts +788 -0
- package/src/type/path.ts +73 -0
- package/src/type/string.ts +1004 -0
- package/src/type/tuple.ts +2424 -0
- package/src/type/union.ts +108 -0
- package/tests/unit/basic/array.spec.ts +290 -0
- package/tests/unit/basic/bigint.spec.ts +50 -0
- package/tests/unit/basic/boolean.spec.ts +74 -0
- package/tests/unit/basic/error.spec.ts +32 -0
- package/tests/unit/basic/function.spec.ts +175 -0
- package/tests/unit/basic/helper.spec.ts +118 -0
- package/tests/unit/basic/number.spec.ts +74 -0
- package/tests/unit/basic/object.spec.ts +15 -0
- package/tests/unit/basic/promise.spec.ts +232 -0
- package/tests/unit/basic/regexp.spec.ts +11 -0
- package/tests/unit/basic/stream.spec.ts +120 -0
- package/tests/unit/basic/string.spec.ts +74 -0
- package/tests/unit/basic/symbol.spec.ts +72 -0
- package/tests/unit/basic/temporal.spec.ts +78 -0
- package/dist/index.d.ts +0 -2
- package/dist/index.d.ts.map +0 -1
- package/dist/reactor/index.d.ts +0 -3
- package/dist/reactor/index.d.ts.map +0 -1
- package/dist/reactor/reactor-core/flags.d.ts +0 -99
- package/dist/reactor/reactor-core/flags.d.ts.map +0 -1
- package/dist/reactor/reactor-core/index.d.ts +0 -4
- package/dist/reactor/reactor-core/index.d.ts.map +0 -1
- package/dist/reactor/reactor-core/primitive.d.ts +0 -276
- package/dist/reactor/reactor-core/primitive.d.ts.map +0 -1
- package/dist/reactor/reactor-core/reactive-system.d.ts +0 -241
- package/dist/reactor/reactor-core/reactive-system.d.ts.map +0 -1
- package/dist/reactor/reactor-operators/branch.d.ts +0 -19
- package/dist/reactor/reactor-operators/branch.d.ts.map +0 -1
- package/dist/reactor/reactor-operators/convert.d.ts +0 -30
- package/dist/reactor/reactor-operators/convert.d.ts.map +0 -1
- package/dist/reactor/reactor-operators/create.d.ts +0 -26
- package/dist/reactor/reactor-operators/create.d.ts.map +0 -1
- package/dist/reactor/reactor-operators/filter.d.ts +0 -269
- package/dist/reactor/reactor-operators/filter.d.ts.map +0 -1
- package/dist/reactor/reactor-operators/index.d.ts +0 -8
- package/dist/reactor/reactor-operators/index.d.ts.map +0 -1
- package/dist/reactor/reactor-operators/join.d.ts +0 -48
- package/dist/reactor/reactor-operators/join.d.ts.map +0 -1
- package/dist/reactor/reactor-operators/map.d.ts +0 -165
- package/dist/reactor/reactor-operators/map.d.ts.map +0 -1
- package/dist/reactor/reactor-operators/utility.d.ts +0 -48
- package/dist/reactor/reactor-operators/utility.d.ts.map +0 -1
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AnyAsyncFunction,
|
|
3
|
+
AnyFunction,
|
|
4
|
+
FunctionComposeAll,
|
|
5
|
+
FunctionPipeAll,
|
|
6
|
+
} from "#Source/type/index.ts"
|
|
7
|
+
|
|
8
|
+
import { asIs } from "./helper.ts"
|
|
9
|
+
import { isAsyncFunction, isSyncFunction } from "./is.ts"
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Immediately invoke a function with the provided arguments.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```
|
|
16
|
+
* // Expect: 3
|
|
17
|
+
* const example1 = functionIife((a: number, b: number) => a + b, 1, 2)
|
|
18
|
+
* // Expect: "ok"
|
|
19
|
+
* const example2 = functionIife(() => "ok")
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export const functionIife = <T extends AnyFunction>(
|
|
23
|
+
fn: T, ...args: Parameters<T>
|
|
24
|
+
): ReturnType<T> => {
|
|
25
|
+
// oxlint-disable-next-line no-unsafe-return
|
|
26
|
+
return fn(...args)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type FunctionOnce<T extends AnyFunction> =
|
|
30
|
+
(...args: Parameters<T>) => ReturnType<T>
|
|
31
|
+
/**
|
|
32
|
+
* Generate a one-off function for a given function.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```
|
|
36
|
+
* const onceFn = functionOnce((x) => x * 2)
|
|
37
|
+
* // Will return 10
|
|
38
|
+
* const result1 = onceFn(5)
|
|
39
|
+
* // Will still return 10, original function will not be called again
|
|
40
|
+
* const result2 = onceFn(10)
|
|
41
|
+
* // If timesSubscriber is provided, it will be called with the number of calls after the second call
|
|
42
|
+
* let callCount = 0
|
|
43
|
+
* const onceFnWithSubscriber = functionOnce((x) => x * 2, (times) => { callCount = times })
|
|
44
|
+
* onceFnWithSubscriber(5) // callCount is still 0
|
|
45
|
+
* onceFnWithSubscriber(10) // callCount becomes 2
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export const functionOnce = <T extends AnyFunction>(
|
|
49
|
+
fn: T,
|
|
50
|
+
timesSubscriber?: (times: number) => void,
|
|
51
|
+
): FunctionOnce<T> => {
|
|
52
|
+
let called = false
|
|
53
|
+
let result: ReturnType<typeof fn>
|
|
54
|
+
let times = 0
|
|
55
|
+
return (...args) => {
|
|
56
|
+
times = times + 1
|
|
57
|
+
if (called === false) {
|
|
58
|
+
// oxlint-disable-next-line no-unsafe-assignment
|
|
59
|
+
result = fn(...args)
|
|
60
|
+
called = true
|
|
61
|
+
}
|
|
62
|
+
if (times >= 2 && timesSubscriber !== undefined) {
|
|
63
|
+
timesSubscriber(times)
|
|
64
|
+
}
|
|
65
|
+
// oxlint-disable-next-line no-unsafe-return
|
|
66
|
+
return result
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export type FunctionDebouncedSimple<T extends AnyFunction> =
|
|
71
|
+
(...args: Parameters<T>) => void
|
|
72
|
+
/**
|
|
73
|
+
* Generate a simple debounced function that delays invoking
|
|
74
|
+
* the original function until after a specified wait time
|
|
75
|
+
* has elapsed since the last time the debounced function was called.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```
|
|
79
|
+
* const debouncedFn = functionDebounceSimple((x) => console.log(x), 100)
|
|
80
|
+
* // Will log 5 after 100ms
|
|
81
|
+
* debouncedFn(5)
|
|
82
|
+
* // Will not log anything, will reset the timer
|
|
83
|
+
* debouncedFn(10)
|
|
84
|
+
* ```
|
|
85
|
+
*/
|
|
86
|
+
export const functionDebounceSimple = <T extends AnyFunction>(
|
|
87
|
+
fn: T,
|
|
88
|
+
ms: number
|
|
89
|
+
): FunctionDebouncedSimple<T> => {
|
|
90
|
+
let timer: NodeJS.Timeout
|
|
91
|
+
return (...args) => {
|
|
92
|
+
clearTimeout(timer)
|
|
93
|
+
timer = setTimeout(() => {
|
|
94
|
+
fn(...args)
|
|
95
|
+
}, ms)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export type FunctionDebounced<T extends AnyFunction>
|
|
100
|
+
= (...args: Parameters<T>) => Promise<ReturnType<T>>
|
|
101
|
+
/**
|
|
102
|
+
* Generate a debounced function that returns a promise resolving
|
|
103
|
+
* to the result of the original function. If the debounced function is called
|
|
104
|
+
* multiple times within the debounce period, it will only execute the original
|
|
105
|
+
* function once, and all calls will receive the same result.
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* ```
|
|
109
|
+
* const debouncedFn = debounce(async (x) => x * 2, 100)
|
|
110
|
+
* // Will execute the original function after 100ms
|
|
111
|
+
* const result1 = debouncedFn(5)
|
|
112
|
+
* // Will not execute the original function, will receive the same result as result1
|
|
113
|
+
* const result2 = debouncedFn(10)
|
|
114
|
+
* // After 100ms, both result1 and result2 will resolve to 10
|
|
115
|
+
* ```
|
|
116
|
+
*/
|
|
117
|
+
export const debounce = <T extends AnyFunction>(
|
|
118
|
+
fn: T,
|
|
119
|
+
ms: number
|
|
120
|
+
): FunctionDebounced<T> => {
|
|
121
|
+
let timer: NodeJS.Timeout
|
|
122
|
+
let waiting: AnyFunction[] = []
|
|
123
|
+
return async (...args) => {
|
|
124
|
+
clearTimeout(timer)
|
|
125
|
+
timer = setTimeout((async () => {
|
|
126
|
+
// oxlint-disable-next-line no-unsafe-assignment
|
|
127
|
+
const res = await fn(...args)
|
|
128
|
+
waiting.forEach((resolve) => {
|
|
129
|
+
resolve(res)
|
|
130
|
+
})
|
|
131
|
+
waiting = []
|
|
132
|
+
}) as (() => void), ms)
|
|
133
|
+
|
|
134
|
+
// oxlint-disable-next-line no-unsafe-return
|
|
135
|
+
return await new Promise((resolve) => {
|
|
136
|
+
waiting.push(resolve)
|
|
137
|
+
})
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const THROTTLE_TYPE_ERROR = new TypeError("Throttle: fn must be a SyncFunction or AsyncFunction")
|
|
142
|
+
export type FunctionThrottleTimeSimple<T extends AnyFunction> =
|
|
143
|
+
(...args: Parameters<T>) => void
|
|
144
|
+
/**
|
|
145
|
+
* Generate a simple throttled function that only invokes the original function at most
|
|
146
|
+
* once per every specified milliseconds. The throttled function will execute the original
|
|
147
|
+
* function immediately on the first call, and then ignore subsequent calls until the
|
|
148
|
+
* specified time has elapsed.
|
|
149
|
+
*
|
|
150
|
+
* If `strict` is set to true, the throttled function will ensure that the window of time
|
|
151
|
+
* during which the original function can be invoked is greater than the sum of the
|
|
152
|
+
* specified milliseconds and the execution time of the original function.
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* ```
|
|
156
|
+
* let called = 0
|
|
157
|
+
* const throttled = functionThrottleTimeSimple(() => { called += 1 }, 100)
|
|
158
|
+
* throttled()
|
|
159
|
+
* throttled()
|
|
160
|
+
* // Expect: called === 1
|
|
161
|
+
* const example1 = called
|
|
162
|
+
* ```
|
|
163
|
+
*/
|
|
164
|
+
export const functionThrottleTimeSimple = <T extends AnyFunction>(
|
|
165
|
+
fn: T,
|
|
166
|
+
ms: number,
|
|
167
|
+
strict = false
|
|
168
|
+
): FunctionThrottleTimeSimple<T> => {
|
|
169
|
+
let isCalling = false
|
|
170
|
+
// NOTE: 对于不同的目标函数,分别返回不同的结果,避免在每次运行时再做条件判断。
|
|
171
|
+
if (isSyncFunction(fn) === true) {
|
|
172
|
+
return (...args) => {
|
|
173
|
+
if (isCalling === false) {
|
|
174
|
+
isCalling = true
|
|
175
|
+
setTimeout(() => {
|
|
176
|
+
isCalling = false
|
|
177
|
+
}, ms)
|
|
178
|
+
fn(...args)
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
else if (isAsyncFunction(fn) === true) {
|
|
183
|
+
return (...args) => {
|
|
184
|
+
let isExecuted = false
|
|
185
|
+
let timerExpired = false
|
|
186
|
+
if (isCalling === false) {
|
|
187
|
+
isCalling = true
|
|
188
|
+
setTimeout(() => {
|
|
189
|
+
if (strict === false) {
|
|
190
|
+
isCalling = false
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
if (isExecuted === true) {
|
|
194
|
+
isCalling = false
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
timerExpired = true
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}, ms);
|
|
201
|
+
; void (fn as AnyAsyncFunction)(...args).finally(() => {
|
|
202
|
+
isExecuted = true
|
|
203
|
+
if (timerExpired === true) {
|
|
204
|
+
isCalling = false
|
|
205
|
+
}
|
|
206
|
+
})
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
throw THROTTLE_TYPE_ERROR
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export type FunctionThrottleSimple<T extends AnyFunction> =
|
|
216
|
+
(...args: Parameters<T>) => void
|
|
217
|
+
/**
|
|
218
|
+
* Generate a throttled function that only invokes the original function
|
|
219
|
+
* when it is not currently executing.
|
|
220
|
+
*
|
|
221
|
+
* @example
|
|
222
|
+
* ```
|
|
223
|
+
* let called = 0
|
|
224
|
+
* const throttled = functionThrottleSimple(() => { called += 1 })
|
|
225
|
+
* throttled()
|
|
226
|
+
* throttled()
|
|
227
|
+
* // Expect: called === 1
|
|
228
|
+
* const example1 = called
|
|
229
|
+
* ```
|
|
230
|
+
*/
|
|
231
|
+
export const functionThrottleSimple = <T extends AnyFunction>(
|
|
232
|
+
fn: T
|
|
233
|
+
): FunctionThrottleSimple<T> => {
|
|
234
|
+
let isCalling = false
|
|
235
|
+
if (isSyncFunction(fn) === true) {
|
|
236
|
+
return (...args) => {
|
|
237
|
+
if (isCalling === false) {
|
|
238
|
+
isCalling = true
|
|
239
|
+
fn(...args)
|
|
240
|
+
isCalling = false
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
else if (isAsyncFunction(fn) === true) {
|
|
245
|
+
return (...args) => {
|
|
246
|
+
if (isCalling === false) {
|
|
247
|
+
isCalling = true;
|
|
248
|
+
; void (fn as AnyAsyncFunction)(...args).then(() => {
|
|
249
|
+
isCalling = false
|
|
250
|
+
})
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
throw THROTTLE_TYPE_ERROR
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export type FunctionThrottleTime<T extends AnyFunction> =
|
|
260
|
+
(...args: Parameters<T>) => Promise<ReturnType<T>>
|
|
261
|
+
/**
|
|
262
|
+
* Generate a throttled function that returns a promise resolving to the result
|
|
263
|
+
* of the original function. The throttled function will ensure that the original
|
|
264
|
+
* function is invoked at most once per every specified milliseconds.
|
|
265
|
+
*
|
|
266
|
+
* If `strict` is set to true, the window of time during which the original function
|
|
267
|
+
* can be invoked will be greater than the sum of the specified milliseconds and
|
|
268
|
+
* the execution time of the original function.
|
|
269
|
+
*
|
|
270
|
+
* @example
|
|
271
|
+
* ```
|
|
272
|
+
* const throttled = throttleTime(async (value: number) => value * 2, 100)
|
|
273
|
+
* const result1 = throttled(2)
|
|
274
|
+
* const result2 = throttled(3)
|
|
275
|
+
* // Expect: result1 and result2 resolve to 4
|
|
276
|
+
* ```
|
|
277
|
+
*/
|
|
278
|
+
export const throttleTime = <T extends AnyFunction>(
|
|
279
|
+
fn: T,
|
|
280
|
+
ms: number,
|
|
281
|
+
strict = false
|
|
282
|
+
): FunctionThrottleTime<T> => {
|
|
283
|
+
let isCalling = false
|
|
284
|
+
let waiting: AnyFunction[] = []
|
|
285
|
+
return async (...args) => {
|
|
286
|
+
let isExecuted = false
|
|
287
|
+
let timerExpired = false
|
|
288
|
+
if (isCalling === false) {
|
|
289
|
+
isCalling = true
|
|
290
|
+
setTimeout(() => {
|
|
291
|
+
if (strict === false) {
|
|
292
|
+
isCalling = false
|
|
293
|
+
}
|
|
294
|
+
else {
|
|
295
|
+
if (isExecuted === true) {
|
|
296
|
+
isCalling = false
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
timerExpired = true
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}, ms)
|
|
303
|
+
// NOTE: 一般来说,如果目标函数是同步函数,节流版本也会同步执行,
|
|
304
|
+
// 如果是异步函数,节流版本会异步运行,
|
|
305
|
+
// 需要拿到结果的节流版本一律按照异步处理。
|
|
306
|
+
void Promise.resolve(fn(...args)).then((res) => {
|
|
307
|
+
// 先广播执行结果
|
|
308
|
+
waiting.forEach((resolve) => {
|
|
309
|
+
resolve(res)
|
|
310
|
+
})
|
|
311
|
+
waiting = []
|
|
312
|
+
// 后重置节流状态
|
|
313
|
+
isExecuted = true
|
|
314
|
+
if (timerExpired === true) {
|
|
315
|
+
isCalling = false
|
|
316
|
+
}
|
|
317
|
+
})
|
|
318
|
+
}
|
|
319
|
+
// oxlint-disable-next-line no-unsafe-return
|
|
320
|
+
return (await new Promise((resolve) => {
|
|
321
|
+
waiting.push(resolve)
|
|
322
|
+
})) as ReturnType<T>
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
export type FunctionThrottle<T extends AnyFunction> =
|
|
327
|
+
(...args: Parameters<T>) => Promise<ReturnType<T>>
|
|
328
|
+
/**
|
|
329
|
+
* Generate a throttled function that returns a promise resolving to the result
|
|
330
|
+
* of the original function and coalesces concurrent calls.
|
|
331
|
+
*
|
|
332
|
+
* @example
|
|
333
|
+
* ```
|
|
334
|
+
* const throttled = functionThrottle(async () => "ok")
|
|
335
|
+
* const result1 = throttled()
|
|
336
|
+
* const result2 = throttled()
|
|
337
|
+
* // Expect: result1 and result2 resolve to "ok"
|
|
338
|
+
* ```
|
|
339
|
+
*/
|
|
340
|
+
export const functionThrottle = <T extends AnyFunction>(
|
|
341
|
+
fn: T
|
|
342
|
+
): FunctionThrottle<T> => {
|
|
343
|
+
let isCalling = false
|
|
344
|
+
let waiting: AnyFunction[] = []
|
|
345
|
+
return async () => {
|
|
346
|
+
if (isCalling === false) {
|
|
347
|
+
isCalling = true
|
|
348
|
+
void Promise.resolve(fn()).then((res) => {
|
|
349
|
+
// 先广播执行结果
|
|
350
|
+
waiting.forEach((resolve) => {
|
|
351
|
+
resolve(res)
|
|
352
|
+
})
|
|
353
|
+
waiting = []
|
|
354
|
+
// 后重置节流状态
|
|
355
|
+
isCalling = false
|
|
356
|
+
})
|
|
357
|
+
}
|
|
358
|
+
// oxlint-disable-next-line no-unsafe-return
|
|
359
|
+
return (await new Promise((resolve) => {
|
|
360
|
+
waiting.push(resolve)
|
|
361
|
+
})) as ReturnType<T>
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Compose functions from right to left.
|
|
367
|
+
*
|
|
368
|
+
* @example
|
|
369
|
+
* ```
|
|
370
|
+
* const addOne = (value: number) => value + 1
|
|
371
|
+
* const double = (value: number) => value * 2
|
|
372
|
+
* const composed = functionCompose(addOne, double)
|
|
373
|
+
* // Expect: 5
|
|
374
|
+
* const example1 = composed(2)
|
|
375
|
+
* ```
|
|
376
|
+
*/
|
|
377
|
+
export const functionCompose = <Fns extends AnyFunction[]>(
|
|
378
|
+
...fns: Fns
|
|
379
|
+
): FunctionComposeAll<Fns> => {
|
|
380
|
+
const reversedFns = fns.toReversed()
|
|
381
|
+
const initialFunction = reversedFns.shift() ?? asIs
|
|
382
|
+
const composedFunction = reversedFns.reduce((g, f) => {
|
|
383
|
+
// oxlint-disable-next-line no-explicit-any explicit-function-return-type
|
|
384
|
+
return (...args: any[]) => {
|
|
385
|
+
// oxlint-disable-next-line no-unsafe-return no-unsafe-argument
|
|
386
|
+
return f(g(...args))
|
|
387
|
+
}
|
|
388
|
+
}, initialFunction)
|
|
389
|
+
// oxlint-disable-next-line no-unsafe-type-assertion
|
|
390
|
+
return composedFunction as FunctionComposeAll<Fns>
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Pipe functions from left to right.
|
|
395
|
+
*
|
|
396
|
+
* @example
|
|
397
|
+
* ```
|
|
398
|
+
* const addOne = (value: number) => value + 1
|
|
399
|
+
* const double = (value: number) => value * 2
|
|
400
|
+
* const piped = functionPipe(addOne, double)
|
|
401
|
+
* // Expect: 6
|
|
402
|
+
* const example1 = piped(2)
|
|
403
|
+
* ```
|
|
404
|
+
*/
|
|
405
|
+
export const functionPipe = <Fns extends AnyFunction[]>(
|
|
406
|
+
...fns: Fns
|
|
407
|
+
): FunctionPipeAll<Fns> => {
|
|
408
|
+
// oxlint-disable-next-line no-unsafe-type-assertion
|
|
409
|
+
return functionCompose(...fns.toReversed()) as FunctionPipeAll<Fns>
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
export type FunctionMemorized<T extends AnyFunction> =
|
|
413
|
+
(...args: Parameters<T>) => ReturnType<T>
|
|
414
|
+
// oxlint-disable-next-line no-explicit-any
|
|
415
|
+
const defaultMemorizeHasher = (...args: any[]): string => JSON.stringify(args)
|
|
416
|
+
/**
|
|
417
|
+
* Generate a memoized function that caches results by arguments.
|
|
418
|
+
*
|
|
419
|
+
* @example
|
|
420
|
+
* ```
|
|
421
|
+
* let calls = 0
|
|
422
|
+
* const sum = (a: number, b: number) => {
|
|
423
|
+
* calls += 1
|
|
424
|
+
* return a + b
|
|
425
|
+
* }
|
|
426
|
+
* const memoized = functionMemorize(sum)
|
|
427
|
+
* // Expect: 3
|
|
428
|
+
* const example1 = memoized(1, 2)
|
|
429
|
+
* // Expect: 3
|
|
430
|
+
* const example2 = memoized(1, 2)
|
|
431
|
+
* // Expect: calls === 1
|
|
432
|
+
* const example3 = calls
|
|
433
|
+
* ```
|
|
434
|
+
*/
|
|
435
|
+
export const functionMemorize = <T extends AnyFunction>(
|
|
436
|
+
fn: T,
|
|
437
|
+
hashFunction?: (...args: Parameters<T>) => unknown
|
|
438
|
+
): FunctionMemorized<T> => {
|
|
439
|
+
const cache = new Map()
|
|
440
|
+
const hasher = hashFunction ?? defaultMemorizeHasher
|
|
441
|
+
return (...args) => {
|
|
442
|
+
const key = hasher(...args)
|
|
443
|
+
if (cache.has(key)) {
|
|
444
|
+
// oxlint-disable-next-line no-unsafe-return
|
|
445
|
+
return cache.get(key)
|
|
446
|
+
}
|
|
447
|
+
// oxlint-disable-next-line no-unsafe-assignment
|
|
448
|
+
const result = fn(...args)
|
|
449
|
+
cache.set(key, result)
|
|
450
|
+
// oxlint-disable-next-line no-unsafe-return
|
|
451
|
+
return result
|
|
452
|
+
}
|
|
453
|
+
}
|