@softsky/utils 2.1.0 → 2.3.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/README.md CHANGED
@@ -183,6 +183,18 @@ ${\textsf{\color{CornflowerBlue}function}}$ log - Format logging
183
183
  ---
184
184
  ${\textsf{\color{CornflowerBlue}function}}$ capitalizeFirstLetter - Capitalize first letter
185
185
 
186
+ ---
187
+ ${\textsf{\color{CornflowerBlue}function}}$ pipe - pipe() can be called on one or more functions, each of which can take the return of previous value.
188
+
189
+ ```ts
190
+ // Takes string, converts to int, calc sqrt, convert and return date
191
+ pipe(
192
+ (x: string) => Number.parseInt(x),
193
+ (x) => Math.sqrt(x),
194
+ (x) => new Date(x)
195
+ )('69')
196
+ ```
197
+
186
198
  ---
187
199
 
188
200
 
@@ -254,6 +266,105 @@ this.registerSubclass()
254
266
  ---
255
267
 
256
268
 
269
+ ## Signals
270
+ Reactive signals
271
+
272
+ ${\textsf{\color{CornflowerBlue}function}}$ signal - __SIGNALS SYSTEM__
273
+
274
+ Signal can hold any data (except functions),
275
+ when this data has changed any effects containing
276
+ this signal will be rerun.
277
+
278
+ ```ts
279
+ const $mySignal = signal<number|undefined>(1) // Create signal with initial value 1
280
+ $mySignal(5) // Set to 5
281
+ $mySignal(undefined) // Set to undefined
282
+ $mySignal(prev=>prev+1) // Increment
283
+ // Will print signal on change
284
+ effect(()=>{
285
+ console.log($mySignal())
286
+ })
287
+ ```
288
+
289
+ ---
290
+ ${\textsf{\color{CornflowerBlue}function}}$ effect - __SIGNALS SYSTEM__
291
+
292
+ Effects are simplest way to react to signal changes.
293
+ Returned data from handler function will be passed to it on next signal change.
294
+ Returns a function that will clear the effect.
295
+
296
+ ```ts
297
+ // Will print signal on change
298
+ effect(()=>{
299
+ console.log($mySignal())
300
+ })
301
+ // Use previous state as a reference
302
+ effect((last)=>{
303
+ const mySignal = $mySignal()
304
+ if(last>mySignal) console.log('Increment!')
305
+ return mySignal;
306
+ })
307
+
308
+ ---
309
+ ${\textsf{\color{CornflowerBlue}function}}$ untrack - __SIGNALS SYSTEM__
310
+
311
+ Untrack helps to not react to changes in effects.
312
+ ```ts
313
+ const $a = signal(1)
314
+ const $b = signal(2)
315
+ // Will only run on changes to $b
316
+ effect(()=>{
317
+ console.log(untrack($a)+$b())
318
+ })
319
+ ```
320
+
321
+ ---
322
+ ${\textsf{\color{CornflowerBlue}function}}$ derived - __SIGNALS SYSTEM__
323
+
324
+ Creates a derived reactive memoized signal.
325
+
326
+ ```ts
327
+ // Sum of all changes of $a()
328
+ const { signal: $sumOfTwo, clear: clearSum } = derived((value) => value + $a(), 0)
329
+ ```
330
+
331
+ ---
332
+ ${\textsf{\color{CornflowerBlue}function}}$ batch - __SIGNALS SYSTEM__
333
+
334
+ Batches multiple edits, so they don't call same effects multiple times
335
+
336
+ ```ts
337
+ const $a = signal(1)
338
+ const $b = signal(2)
339
+ effect(()=>{
340
+ console.log($a()+$b())
341
+ })
342
+ $a(2); // Prints 4
343
+ $b(3); // Prints 5
344
+ // Prints only 10
345
+ batch(()=>{
346
+ $a(5);
347
+ $b(5);
348
+ })
349
+ ```
350
+
351
+ ---
352
+ ${\textsf{\color{CornflowerBlue}function}}$ when - __SIGNALS SYSTEM__
353
+
354
+ Returns ImmediatePromise that is resolved when check function returns truthy value.
355
+ If you want to, you can resolve or reject promise beforehand.
356
+
357
+ ```ts
358
+ await when(() => $a()>5)
359
+ // With timeout
360
+ const promise = when(() => $a() > 5)
361
+ const timeout = setTimeout(() => promise.reject('Timeout')}, 5000)
362
+ primise.then(() => clearTimeout(timeout))
363
+ ```
364
+
365
+ ---
366
+
367
+
257
368
  ## Time
258
369
  Timers, CRON, etc.
259
370
 
@@ -289,6 +400,9 @@ Damn, I **love** TypeScript.
289
400
 
290
401
  ${\textsf{\color{Magenta}type}}$ Primitive - Values that are copied by value, not by reference
291
402
 
403
+ ---
404
+ ${\textsf{\color{Magenta}type}}$ AnyFunction - Function with any arguments or return type
405
+
292
406
  ---
293
407
  ${\textsf{\color{Magenta}type}}$ Falsy - Values that convert to false
294
408
 
package/dist/control.d.ts CHANGED
@@ -31,11 +31,12 @@ export declare function createThrottledFunction<T, V extends unknown[]>(function
31
31
  /** Create debounced function. Basically create function that will be called with delay,
32
32
  * but if another call comes in, we reset the timer. */
33
33
  export declare function createDelayedFunction<T, V extends unknown[]>(function_: (...arguments_: V) => T, time: number): (...arguments_: V) => Promise<T>;
34
+ type ResolveFunction<T> = undefined extends T ? (value?: T | PromiseLike<T>) => void : (value: T | PromiseLike<T>) => void;
34
35
  /** Promise that accepts no callback, but exposes `resolve` and `reject` methods */
35
36
  export declare class ImmediatePromise<T> extends Promise<T> {
36
- resolve: (value: T | PromiseLike<T>) => void;
37
+ resolve: ResolveFunction<T>;
37
38
  reject: (reason?: unknown) => void;
38
- constructor(execute?: (resolve: (value: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void);
39
+ constructor(execute?: (resolve: ResolveFunction<T>, reject: (reason?: any) => void) => void);
39
40
  }
40
41
  /** Recursively resolves promises in objects and arrays */
41
42
  export default function deepPromiseAll<T>(input: T): Promise<AwaitedObject<T>>;
@@ -45,3 +46,4 @@ export declare function wait(time: number): Promise<unknown>;
45
46
  export declare function noop(): void;
46
47
  /** Run array of async tasks concurrently */
47
48
  export declare function concurrentRun<T>(tasks: (() => Promise<T>)[], concurrency?: number): Promise<T[]>;
49
+ export {};
package/dist/control.js CHANGED
@@ -113,6 +113,7 @@ export class ImmediatePromise extends Promise {
113
113
  resolve;
114
114
  reject;
115
115
  constructor(execute) {
116
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
116
117
  if (execute)
117
118
  super(execute);
118
119
  else {
@@ -40,3 +40,26 @@ export declare function formatBytes(bytes: number): string;
40
40
  export declare function log(...agrs: unknown[]): void;
41
41
  /** Capitalize first letter */
42
42
  export declare function capitalizeFirstLetter(value: string): string;
43
+ /**
44
+ * pipe() can be called on one or more functions, each of which can take the return of previous value.
45
+ *
46
+ * ```ts
47
+ * // Takes string, converts to int, calc sqrt, convert and return date
48
+ * pipe(
49
+ * (x: string) => Number.parseInt(x),
50
+ * (x) => Math.sqrt(x),
51
+ * (x) => new Date(x)
52
+ * )('69')
53
+ * ```
54
+ */
55
+ export declare function pipe(): <T>(x: T) => T;
56
+ export declare function pipe<T, A>(function1: (x: T) => A): (x: T) => A;
57
+ export declare function pipe<T, A, B>(function1: (x: T) => A, function2: (x: A) => B): (x: T) => B;
58
+ export declare function pipe<T, A, B, C>(function1: (x: T) => A, function2: (x: A) => B, function3: (x: B) => C): (x: T) => C;
59
+ export declare function pipe<T, A, B, C, D>(function1: (x: T) => A, function2: (x: A) => B, function3: (x: B) => C, function4: (x: C) => D): (x: T) => D;
60
+ export declare function pipe<T, A, B, C, D, E>(function1: (x: T) => A, function2: (x: A) => B, function3: (x: B) => C, function4: (x: C) => D, function5: (x: D) => E): (x: T) => E;
61
+ export declare function pipe<T, A, B, C, D, E, F>(function1: (x: T) => A, function2: (x: A) => B, function3: (x: B) => C, function4: (x: C) => D, function5: (x: D) => E, function6: (x: E) => F): (x: T) => F;
62
+ export declare function pipe<T, A, B, C, D, E, F, G>(function1: (x: T) => A, function2: (x: A) => B, function3: (x: B) => C, function4: (x: C) => D, function5: (x: D) => E, function6: (x: E) => F, function7: (x: F) => G): (x: T) => G;
63
+ export declare function pipe<T, A, B, C, D, E, F, G, H>(function1: (x: T) => A, function2: (x: A) => B, function3: (x: B) => C, function4: (x: C) => D, function5: (x: D) => E, function6: (x: E) => F, function7: (x: F) => G, function8: (x: G) => H): (x: T) => H;
64
+ export declare function pipe<T, A, B, C, D, E, F, G, H, I>(function1: (x: T) => A, function2: (x: A) => B, function3: (x: B) => C, function4: (x: C) => D, function5: (x: D) => E, function6: (x: E) => F, function7: (x: F) => G, function8: (x: G) => H, function9: (x: H) => I): (x: T) => I;
65
+ export declare function pipe<T, A, B, C, D, E, F, G, H>(function1: (x: T) => A, function2: (x: A) => B, function3: (x: B) => C, function4: (x: C) => D, function5: (x: D) => E, function6: (x: E) => F, function7: (x: F) => G, function8: (x: G) => H, function9: (x: H) => unknown, ...fns: ((x: unknown) => unknown)[]): (x: T) => unknown;
@@ -117,3 +117,10 @@ export function log(...agrs) {
117
117
  export function capitalizeFirstLetter(value) {
118
118
  return value.charAt(0).toUpperCase() + value.slice(1);
119
119
  }
120
+ export function pipe(...fns) {
121
+ return (input) => {
122
+ for (let index = 0; index < fns.length; index++)
123
+ input = fns[index](input);
124
+ return input;
125
+ };
126
+ }
package/dist/index.d.ts CHANGED
@@ -5,5 +5,6 @@ export * from './errors';
5
5
  export * from './formatting';
6
6
  export * from './numbers';
7
7
  export * from './objects';
8
+ export * from './signals';
8
9
  export * from './time';
9
10
  export * from './types';
package/dist/index.js CHANGED
@@ -5,5 +5,6 @@ export * from './errors';
5
5
  export * from './formatting';
6
6
  export * from './numbers';
7
7
  export * from './objects';
8
+ export * from './signals';
8
9
  export * from './time';
9
10
  export * from './types';
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Reactive signals
3
+ */
4
+ import { ImmediatePromise } from './control';
5
+ import { AnyFunction } from './types';
6
+ export type Signal<T> = (setTo?: T | ((previous: T) => T)) => T;
7
+ /**
8
+ * __SIGNALS SYSTEM__
9
+ *
10
+ * Signal can hold any data (except functions),
11
+ * when this data has changed any effects containing
12
+ * this signal will be rerun.
13
+ *
14
+ * ```ts
15
+ * const $mySignal = signal<number|undefined>(1) // Create signal with initial value 1
16
+ * $mySignal(5) // Set to 5
17
+ * $mySignal(undefined) // Set to undefined
18
+ * $mySignal(prev=>prev+1) // Increment
19
+ * // Will print signal on change
20
+ * effect(()=>{
21
+ * console.log($mySignal())
22
+ * })
23
+ * ```
24
+ */
25
+ export declare function signal<T>(): Signal<T | undefined>;
26
+ export declare function signal<T>(value: T): Signal<T>;
27
+ /**
28
+ * __SIGNALS SYSTEM__
29
+ *
30
+ * Effects are simplest way to react to signal changes.
31
+ * Returned data from handler function will be passed to it on next signal change.
32
+ * Returns a function that will clear the effect.
33
+ *
34
+ * ```ts
35
+ * // Will print signal on change
36
+ * effect(()=>{
37
+ * console.log($mySignal())
38
+ * })
39
+ * // Use previous state as a reference
40
+ * effect((last)=>{
41
+ * const mySignal = $mySignal()
42
+ * if(last>mySignal) console.log('Increment!')
43
+ * return mySignal;
44
+ * })
45
+ */
46
+ export declare function effect<T>(handler: (argument: T | undefined) => T, initialValue?: T): () => void;
47
+ /**
48
+ * __SIGNALS SYSTEM__
49
+ *
50
+ * Untrack helps to not react to changes in effects.
51
+ * ```ts
52
+ * const $a = signal(1)
53
+ * const $b = signal(2)
54
+ * // Will only run on changes to $b
55
+ * effect(()=>{
56
+ * console.log(untrack($a)+$b())
57
+ * })
58
+ * ```
59
+ */
60
+ export declare function untrack<T>(handler: () => T): T;
61
+ /**
62
+ * __SIGNALS SYSTEM__
63
+ *
64
+ * Creates a derived reactive memoized signal.
65
+ *
66
+ * ```ts
67
+ * // Sum of all changes of $a()
68
+ * const { signal: $sumOfTwo, clear: clearSum } = derived((value) => value + $a(), 0)
69
+ * ```
70
+ */
71
+ export declare function derived<T>(handler: (argument: T | undefined) => T): {
72
+ signal: Signal<T>;
73
+ clear: () => void;
74
+ };
75
+ export declare function derived<T>(handler: (argument: T) => T, initialValue: T): {
76
+ signal: Signal<T>;
77
+ clear: () => void;
78
+ };
79
+ /**
80
+ * __SIGNALS SYSTEM__
81
+ *
82
+ * Batches multiple edits, so they don't call same effects multiple times
83
+ *
84
+ * ```ts
85
+ * const $a = signal(1)
86
+ * const $b = signal(2)
87
+ * effect(()=>{
88
+ * console.log($a()+$b())
89
+ * })
90
+ * $a(2); // Prints 4
91
+ * $b(3); // Prints 5
92
+ * // Prints only 10
93
+ * batch(()=>{
94
+ * $a(5);
95
+ * $b(5);
96
+ * })
97
+ * ```
98
+ */
99
+ export declare function batch(handler: AnyFunction): void;
100
+ /**
101
+ * __SIGNALS SYSTEM__
102
+ *
103
+ * Returns ImmediatePromise that is resolved when check function returns truthy value.
104
+ * If you want to, you can resolve or reject promise beforehand.
105
+ *
106
+ * ```ts
107
+ * await when(() => $a()>5)
108
+ * // With timeout
109
+ * const promise = when(() => $a() > 5)
110
+ * const timeout = setTimeout(() => promise.reject('Timeout')}, 5000)
111
+ * primise.then(() => clearTimeout(timeout))
112
+ * ```
113
+ */
114
+ export declare function when(check: () => unknown): ImmediatePromise<undefined>;
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Reactive signals
3
+ */
4
+ import { ImmediatePromise } from './control';
5
+ let currentEffect;
6
+ const effectsMap = new WeakMap();
7
+ let batchedEffects;
8
+ export function signal(value) {
9
+ const subscribers = new Set();
10
+ return (...arguments_) => {
11
+ if (arguments_.length === 1) {
12
+ const argument = arguments_[0];
13
+ value =
14
+ typeof argument === 'function'
15
+ ? argument(value)
16
+ : argument;
17
+ if (batchedEffects)
18
+ for (const subscriber of subscribers)
19
+ batchedEffects.add(subscriber);
20
+ else
21
+ for (const subscriber of subscribers)
22
+ subscriber();
23
+ }
24
+ else if (currentEffect) {
25
+ subscribers.add(currentEffect);
26
+ // This is for clear() of effects
27
+ let effectSubscribers = effectsMap.get(currentEffect);
28
+ if (!effectSubscribers) {
29
+ effectSubscribers = new Set();
30
+ effectsMap.set(currentEffect, effectSubscribers);
31
+ }
32
+ effectSubscribers.add(subscribers);
33
+ }
34
+ return value;
35
+ };
36
+ }
37
+ function clearEffect(handler) {
38
+ const signalSubscribers = effectsMap.get(handler);
39
+ if (signalSubscribers)
40
+ for (const subscribers of signalSubscribers)
41
+ subscribers.delete(handler);
42
+ }
43
+ /**
44
+ * __SIGNALS SYSTEM__
45
+ *
46
+ * Effects are simplest way to react to signal changes.
47
+ * Returned data from handler function will be passed to it on next signal change.
48
+ * Returns a function that will clear the effect.
49
+ *
50
+ * ```ts
51
+ * // Will print signal on change
52
+ * effect(()=>{
53
+ * console.log($mySignal())
54
+ * })
55
+ * // Use previous state as a reference
56
+ * effect((last)=>{
57
+ * const mySignal = $mySignal()
58
+ * if(last>mySignal) console.log('Increment!')
59
+ * return mySignal;
60
+ * })
61
+ */
62
+ export function effect(handler, initialValue) {
63
+ let lastValue = initialValue;
64
+ const wrappedHandler = () => {
65
+ lastValue = handler(lastValue);
66
+ };
67
+ currentEffect = wrappedHandler;
68
+ wrappedHandler();
69
+ currentEffect = undefined;
70
+ return () => {
71
+ clearEffect(wrappedHandler);
72
+ };
73
+ }
74
+ /**
75
+ * __SIGNALS SYSTEM__
76
+ *
77
+ * Untrack helps to not react to changes in effects.
78
+ * ```ts
79
+ * const $a = signal(1)
80
+ * const $b = signal(2)
81
+ * // Will only run on changes to $b
82
+ * effect(()=>{
83
+ * console.log(untrack($a)+$b())
84
+ * })
85
+ * ```
86
+ */
87
+ export function untrack(handler) {
88
+ const lastEffect = currentEffect;
89
+ currentEffect = undefined;
90
+ const data = handler();
91
+ currentEffect = lastEffect;
92
+ return data;
93
+ }
94
+ export function derived(handler, initialValue) {
95
+ const signal$ = signal(initialValue);
96
+ const wrappedHandler = () => signal$((value) => handler(value));
97
+ currentEffect = wrappedHandler;
98
+ wrappedHandler();
99
+ currentEffect = undefined;
100
+ return {
101
+ signal: signal$,
102
+ clear: () => {
103
+ clearEffect(wrappedHandler);
104
+ },
105
+ };
106
+ }
107
+ /**
108
+ * __SIGNALS SYSTEM__
109
+ *
110
+ * Batches multiple edits, so they don't call same effects multiple times
111
+ *
112
+ * ```ts
113
+ * const $a = signal(1)
114
+ * const $b = signal(2)
115
+ * effect(()=>{
116
+ * console.log($a()+$b())
117
+ * })
118
+ * $a(2); // Prints 4
119
+ * $b(3); // Prints 5
120
+ * // Prints only 10
121
+ * batch(()=>{
122
+ * $a(5);
123
+ * $b(5);
124
+ * })
125
+ * ```
126
+ */
127
+ export function batch(handler) {
128
+ batchedEffects = new Set();
129
+ handler();
130
+ for (const effect of batchedEffects)
131
+ effect();
132
+ batchedEffects = undefined;
133
+ }
134
+ /**
135
+ * __SIGNALS SYSTEM__
136
+ *
137
+ * Returns ImmediatePromise that is resolved when check function returns truthy value.
138
+ * If you want to, you can resolve or reject promise beforehand.
139
+ *
140
+ * ```ts
141
+ * await when(() => $a()>5)
142
+ * // With timeout
143
+ * const promise = when(() => $a() > 5)
144
+ * const timeout = setTimeout(() => promise.reject('Timeout')}, 5000)
145
+ * primise.then(() => clearTimeout(timeout))
146
+ * ```
147
+ */
148
+ export function when(check) {
149
+ const promise = new ImmediatePromise();
150
+ const clear = effect(() => {
151
+ if (check())
152
+ promise.resolve();
153
+ });
154
+ void promise.finally(() => {
155
+ clear();
156
+ });
157
+ return promise;
158
+ }
package/dist/types.d.ts CHANGED
@@ -3,6 +3,8 @@
3
3
  */
4
4
  /** Values that are copied by value, not by reference */
5
5
  export type Primitive = string | number | bigint | boolean | symbol | null | undefined;
6
+ /** Function with any arguments or return type */
7
+ export type AnyFunction = (...arguments_: any[]) => any;
6
8
  /** Values that convert to false */
7
9
  export type Falsy = false | '' | 0 | null | undefined;
8
10
  /** Make keys in object optional */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softsky/utils",
3
- "version": "2.1.0",
3
+ "version": "2.3.0",
4
4
  "description": "JavaScript/TypeScript utilities",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {