@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.
Files changed (82) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +4 -1
  3. package/dist/index.js +4 -2
  4. package/dist/index.js.map +18 -3
  5. package/package.json +3 -3
  6. package/scripts/build.ts +4 -4
  7. package/src/basic/README.md +143 -0
  8. package/src/basic/array.ts +872 -0
  9. package/src/basic/bigint.ts +114 -0
  10. package/src/basic/boolean.ts +180 -0
  11. package/src/basic/error.ts +51 -0
  12. package/src/basic/function.ts +453 -0
  13. package/src/basic/helper.ts +276 -0
  14. package/src/basic/index.ts +15 -0
  15. package/src/basic/is.ts +320 -0
  16. package/src/basic/number.ts +178 -0
  17. package/src/basic/object.ts +58 -0
  18. package/src/basic/promise.ts +464 -0
  19. package/src/basic/regexp.ts +7 -0
  20. package/src/basic/stream.ts +140 -0
  21. package/src/basic/string.ts +308 -0
  22. package/src/basic/symbol.ts +164 -0
  23. package/src/basic/temporal.ts +224 -0
  24. package/src/index.ts +2 -0
  25. package/src/type/README.md +330 -0
  26. package/src/type/array.ts +5 -0
  27. package/src/type/boolean.ts +471 -0
  28. package/src/type/class.ts +419 -0
  29. package/src/type/function.ts +1519 -0
  30. package/src/type/helper.ts +135 -0
  31. package/src/type/index.ts +14 -0
  32. package/src/type/intersection.ts +93 -0
  33. package/src/type/is.ts +247 -0
  34. package/src/type/iteration.ts +233 -0
  35. package/src/type/number.ts +732 -0
  36. package/src/type/object.ts +788 -0
  37. package/src/type/path.ts +73 -0
  38. package/src/type/string.ts +1004 -0
  39. package/src/type/tuple.ts +2424 -0
  40. package/src/type/union.ts +108 -0
  41. package/tests/unit/basic/array.spec.ts +290 -0
  42. package/tests/unit/basic/bigint.spec.ts +50 -0
  43. package/tests/unit/basic/boolean.spec.ts +74 -0
  44. package/tests/unit/basic/error.spec.ts +32 -0
  45. package/tests/unit/basic/function.spec.ts +175 -0
  46. package/tests/unit/basic/helper.spec.ts +118 -0
  47. package/tests/unit/basic/number.spec.ts +74 -0
  48. package/tests/unit/basic/object.spec.ts +15 -0
  49. package/tests/unit/basic/promise.spec.ts +232 -0
  50. package/tests/unit/basic/regexp.spec.ts +11 -0
  51. package/tests/unit/basic/stream.spec.ts +120 -0
  52. package/tests/unit/basic/string.spec.ts +74 -0
  53. package/tests/unit/basic/symbol.spec.ts +72 -0
  54. package/tests/unit/basic/temporal.spec.ts +78 -0
  55. package/dist/index.d.ts +0 -2
  56. package/dist/index.d.ts.map +0 -1
  57. package/dist/reactor/index.d.ts +0 -3
  58. package/dist/reactor/index.d.ts.map +0 -1
  59. package/dist/reactor/reactor-core/flags.d.ts +0 -99
  60. package/dist/reactor/reactor-core/flags.d.ts.map +0 -1
  61. package/dist/reactor/reactor-core/index.d.ts +0 -4
  62. package/dist/reactor/reactor-core/index.d.ts.map +0 -1
  63. package/dist/reactor/reactor-core/primitive.d.ts +0 -276
  64. package/dist/reactor/reactor-core/primitive.d.ts.map +0 -1
  65. package/dist/reactor/reactor-core/reactive-system.d.ts +0 -241
  66. package/dist/reactor/reactor-core/reactive-system.d.ts.map +0 -1
  67. package/dist/reactor/reactor-operators/branch.d.ts +0 -19
  68. package/dist/reactor/reactor-operators/branch.d.ts.map +0 -1
  69. package/dist/reactor/reactor-operators/convert.d.ts +0 -30
  70. package/dist/reactor/reactor-operators/convert.d.ts.map +0 -1
  71. package/dist/reactor/reactor-operators/create.d.ts +0 -26
  72. package/dist/reactor/reactor-operators/create.d.ts.map +0 -1
  73. package/dist/reactor/reactor-operators/filter.d.ts +0 -269
  74. package/dist/reactor/reactor-operators/filter.d.ts.map +0 -1
  75. package/dist/reactor/reactor-operators/index.d.ts +0 -8
  76. package/dist/reactor/reactor-operators/index.d.ts.map +0 -1
  77. package/dist/reactor/reactor-operators/join.d.ts +0 -48
  78. package/dist/reactor/reactor-operators/join.d.ts.map +0 -1
  79. package/dist/reactor/reactor-operators/map.d.ts +0 -165
  80. package/dist/reactor/reactor-operators/map.d.ts.map +0 -1
  81. package/dist/reactor/reactor-operators/utility.d.ts +0 -48
  82. 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
+ }