synstate 0.1.0 → 0.1.1

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 (154) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +124 -350
  3. package/assets/synstate-icon.png +0 -0
  4. package/dist/core/combine/combine.d.mts +32 -2
  5. package/dist/core/combine/combine.d.mts.map +1 -1
  6. package/dist/core/combine/combine.mjs +32 -2
  7. package/dist/core/combine/combine.mjs.map +1 -1
  8. package/dist/core/combine/merge.d.mts +30 -4
  9. package/dist/core/combine/merge.d.mts.map +1 -1
  10. package/dist/core/combine/merge.mjs +30 -4
  11. package/dist/core/combine/merge.mjs.map +1 -1
  12. package/dist/core/combine/zip.d.mts +28 -3
  13. package/dist/core/combine/zip.d.mts.map +1 -1
  14. package/dist/core/combine/zip.mjs +28 -3
  15. package/dist/core/combine/zip.mjs.map +1 -1
  16. package/dist/core/create/from-array.d.mts +21 -3
  17. package/dist/core/create/from-array.d.mts.map +1 -1
  18. package/dist/core/create/from-array.mjs +21 -3
  19. package/dist/core/create/from-array.mjs.map +1 -1
  20. package/dist/core/create/from-promise.d.mts +29 -7
  21. package/dist/core/create/from-promise.d.mts.map +1 -1
  22. package/dist/core/create/from-promise.mjs +29 -7
  23. package/dist/core/create/from-promise.mjs.map +1 -1
  24. package/dist/core/create/from-subscribable.d.mts +58 -0
  25. package/dist/core/create/from-subscribable.d.mts.map +1 -1
  26. package/dist/core/create/from-subscribable.mjs +58 -0
  27. package/dist/core/create/from-subscribable.mjs.map +1 -1
  28. package/dist/core/create/interval.d.mts +29 -4
  29. package/dist/core/create/interval.d.mts.map +1 -1
  30. package/dist/core/create/interval.mjs +29 -4
  31. package/dist/core/create/interval.mjs.map +1 -1
  32. package/dist/core/create/of.d.mts +22 -3
  33. package/dist/core/create/of.d.mts.map +1 -1
  34. package/dist/core/create/of.mjs +22 -3
  35. package/dist/core/create/of.mjs.map +1 -1
  36. package/dist/core/create/source.d.mts +20 -1
  37. package/dist/core/create/source.d.mts.map +1 -1
  38. package/dist/core/create/source.mjs.map +1 -1
  39. package/dist/core/create/timer.d.mts +23 -4
  40. package/dist/core/create/timer.d.mts.map +1 -1
  41. package/dist/core/create/timer.mjs +23 -4
  42. package/dist/core/create/timer.mjs.map +1 -1
  43. package/dist/core/operators/audit-time.d.mts +59 -0
  44. package/dist/core/operators/audit-time.d.mts.map +1 -1
  45. package/dist/core/operators/audit-time.mjs +59 -0
  46. package/dist/core/operators/audit-time.mjs.map +1 -1
  47. package/dist/core/operators/debounce-time.d.mts +22 -2
  48. package/dist/core/operators/debounce-time.d.mts.map +1 -1
  49. package/dist/core/operators/debounce-time.mjs +22 -2
  50. package/dist/core/operators/debounce-time.mjs.map +1 -1
  51. package/dist/core/operators/filter.d.mts +26 -1
  52. package/dist/core/operators/filter.d.mts.map +1 -1
  53. package/dist/core/operators/filter.mjs.map +1 -1
  54. package/dist/core/operators/map-with-index.d.mts +19 -4
  55. package/dist/core/operators/map-with-index.d.mts.map +1 -1
  56. package/dist/core/operators/map-with-index.mjs +19 -4
  57. package/dist/core/operators/map-with-index.mjs.map +1 -1
  58. package/dist/core/operators/merge-map.d.mts +47 -5
  59. package/dist/core/operators/merge-map.d.mts.map +1 -1
  60. package/dist/core/operators/merge-map.mjs +47 -5
  61. package/dist/core/operators/merge-map.mjs.map +1 -1
  62. package/dist/core/operators/pairwise.d.mts +30 -1
  63. package/dist/core/operators/pairwise.d.mts.map +1 -1
  64. package/dist/core/operators/pairwise.mjs +30 -1
  65. package/dist/core/operators/pairwise.mjs.map +1 -1
  66. package/dist/core/operators/scan.d.mts +23 -1
  67. package/dist/core/operators/scan.d.mts.map +1 -1
  68. package/dist/core/operators/scan.mjs +23 -1
  69. package/dist/core/operators/scan.mjs.map +1 -1
  70. package/dist/core/operators/skip-if-no-change.d.mts +25 -1
  71. package/dist/core/operators/skip-if-no-change.d.mts.map +1 -1
  72. package/dist/core/operators/skip-if-no-change.mjs +25 -1
  73. package/dist/core/operators/skip-if-no-change.mjs.map +1 -1
  74. package/dist/core/operators/skip-until.d.mts +50 -0
  75. package/dist/core/operators/skip-until.d.mts.map +1 -1
  76. package/dist/core/operators/skip-until.mjs +50 -0
  77. package/dist/core/operators/skip-until.mjs.map +1 -1
  78. package/dist/core/operators/skip-while.d.mts +48 -0
  79. package/dist/core/operators/skip-while.d.mts.map +1 -1
  80. package/dist/core/operators/skip-while.mjs +48 -0
  81. package/dist/core/operators/skip-while.mjs.map +1 -1
  82. package/dist/core/operators/switch-map.d.mts +39 -5
  83. package/dist/core/operators/switch-map.d.mts.map +1 -1
  84. package/dist/core/operators/switch-map.mjs +39 -5
  85. package/dist/core/operators/switch-map.mjs.map +1 -1
  86. package/dist/core/operators/take-until.d.mts +20 -1
  87. package/dist/core/operators/take-until.d.mts.map +1 -1
  88. package/dist/core/operators/take-until.mjs +20 -1
  89. package/dist/core/operators/take-until.mjs.map +1 -1
  90. package/dist/core/operators/take-while.d.mts +47 -0
  91. package/dist/core/operators/take-while.d.mts.map +1 -1
  92. package/dist/core/operators/take-while.mjs +47 -0
  93. package/dist/core/operators/take-while.mjs.map +1 -1
  94. package/dist/core/operators/throttle-time.d.mts +44 -5
  95. package/dist/core/operators/throttle-time.d.mts.map +1 -1
  96. package/dist/core/operators/throttle-time.mjs +44 -5
  97. package/dist/core/operators/throttle-time.mjs.map +1 -1
  98. package/dist/core/operators/with-buffered-from.d.mts +53 -0
  99. package/dist/core/operators/with-buffered-from.d.mts.map +1 -1
  100. package/dist/core/operators/with-buffered-from.mjs +53 -0
  101. package/dist/core/operators/with-buffered-from.mjs.map +1 -1
  102. package/dist/core/operators/with-current-value-from.d.mts +55 -0
  103. package/dist/core/operators/with-current-value-from.d.mts.map +1 -1
  104. package/dist/core/operators/with-current-value-from.mjs +55 -0
  105. package/dist/core/operators/with-current-value-from.mjs.map +1 -1
  106. package/dist/core/operators/with-initial-value.d.mts +24 -2
  107. package/dist/core/operators/with-initial-value.d.mts.map +1 -1
  108. package/dist/core/operators/with-initial-value.mjs +24 -2
  109. package/dist/core/operators/with-initial-value.mjs.map +1 -1
  110. package/dist/core/types/observable-family.d.mts +7 -7
  111. package/dist/utils/create-event-emitter.d.mts +20 -2
  112. package/dist/utils/create-event-emitter.d.mts.map +1 -1
  113. package/dist/utils/create-event-emitter.mjs +20 -2
  114. package/dist/utils/create-event-emitter.mjs.map +1 -1
  115. package/dist/utils/create-reducer.d.mts +13 -1
  116. package/dist/utils/create-reducer.d.mts.map +1 -1
  117. package/dist/utils/create-reducer.mjs +13 -1
  118. package/dist/utils/create-reducer.mjs.map +1 -1
  119. package/dist/utils/create-state.d.mts +24 -4
  120. package/dist/utils/create-state.d.mts.map +1 -1
  121. package/dist/utils/create-state.mjs +24 -4
  122. package/dist/utils/create-state.mjs.map +1 -1
  123. package/package.json +13 -12
  124. package/src/core/combine/combine.mts +32 -2
  125. package/src/core/combine/merge.mts +30 -4
  126. package/src/core/combine/zip.mts +28 -3
  127. package/src/core/create/from-array.mts +21 -3
  128. package/src/core/create/from-promise.mts +29 -7
  129. package/src/core/create/from-subscribable.mts +58 -0
  130. package/src/core/create/interval.mts +29 -4
  131. package/src/core/create/of.mts +22 -3
  132. package/src/core/create/source.mts +20 -1
  133. package/src/core/create/timer.mts +23 -4
  134. package/src/core/operators/audit-time.mts +59 -0
  135. package/src/core/operators/debounce-time.mts +22 -2
  136. package/src/core/operators/filter.mts +26 -1
  137. package/src/core/operators/map-with-index.mts +19 -4
  138. package/src/core/operators/merge-map.mts +47 -5
  139. package/src/core/operators/pairwise.mts +30 -1
  140. package/src/core/operators/scan.mts +23 -1
  141. package/src/core/operators/skip-if-no-change.mts +25 -1
  142. package/src/core/operators/skip-until.mts +50 -0
  143. package/src/core/operators/skip-while.mts +48 -0
  144. package/src/core/operators/switch-map.mts +39 -5
  145. package/src/core/operators/take-until.mts +20 -1
  146. package/src/core/operators/take-while.mts +47 -0
  147. package/src/core/operators/throttle-time.mts +44 -5
  148. package/src/core/operators/with-buffered-from.mts +53 -0
  149. package/src/core/operators/with-current-value-from.mts +55 -0
  150. package/src/core/operators/with-initial-value.mts +24 -2
  151. package/src/core/types/observable-family.mts +7 -7
  152. package/src/utils/create-event-emitter.mts +20 -2
  153. package/src/utils/create-reducer.mts +13 -1
  154. package/src/utils/create-state.mts +24 -4
package/README.md CHANGED
@@ -1,11 +1,19 @@
1
- # SyncFlow
1
+ # SynState
2
2
 
3
- [![npm version](https://img.shields.io/npm/v/ts-data-forge.svg)](https://www.npmjs.com/package/ts-data-forge)
4
- [![npm downloads](https://img.shields.io/npm/dm/ts-data-forge.svg)](https://www.npmjs.com/package/ts-data-forge)
5
- [![License](https://img.shields.io/npm/l/ts-data-forge.svg)](./LICENSE)
6
- [![codecov](https://codecov.io/gh/noshiro-pf/ts-data-forge/branch/main/graph/badge.svg?token=69TA40HACZ)](https://codecov.io/gh/noshiro-pf/ts-data-forge)
3
+ <p align="center">
4
+ <img src="./assets/synstate-icon.png" alt="SynState Logo" width="400" />
5
+ </p>
7
6
 
8
- **SyncFlow** is a lightweight, type-safe state management library for TypeScript/JavaScript. Perfect for building reactive global state and event-driven systems in React, Vue, and other frameworks.
7
+ <p align="center">
8
+
9
+ [![npm version](https://img.shields.io/npm/v/synstate.svg)](https://www.npmjs.com/package/synstate)
10
+ [![npm downloads](https://img.shields.io/npm/dm/synstate.svg)](https://www.npmjs.com/package/synstate)
11
+ [![License](https://img.shields.io/npm/l/synstate.svg)](./LICENSE)
12
+ [![codecov](https://codecov.io/gh/noshiro-pf/synstate/graph/badge.svg?token=xrJgTVxMpr)](https://codecov.io/gh/noshiro-pf/synstate)
13
+
14
+ </p>
15
+
16
+ **SynState** is a lightweight, high-performance, type-safe state management library for TypeScript/JavaScript. Perfect for building reactive global state and event-driven systems in React, Vue, and other frameworks.
9
17
 
10
18
  ## Features
11
19
 
@@ -13,13 +21,14 @@
13
21
  - 📡 **Event System**: Built-in `createValueEmitter`, `createEventEmitter` for event-driven architecture
14
22
  - 🔄 **Reactive Updates**: Automatic propagation of state changes to subscribers
15
23
  - 🎨 **Type-Safe**: Full TypeScript support with precise type inference
16
- - 🚀 **Lightweight**: Minimal bundle size, zero external runtime dependencies
17
- - ⚡ **Framework Agnostic**: Works with React, Vue, Svelte, or vanilla JavaScript
18
- - 🔧 **Flexible**: Simple state management with optional advanced features
24
+ - 🚀 **Lightweight**: Minimal bundle size with only one external runtime dependency ([ts-data-forge](https://www.npmjs.com/package/ts-data-forge))
25
+ - ⚡ **High Performance**: Optimized for fast state updates and minimal re-renders
26
+ - 🌐 **Framework Agnostic**: Works with React, Vue, Svelte, or vanilla JavaScript
27
+ - 🔧 **Flexible**: Simple state management with optional advanced Observable-based features (operators like `map`, `filter`, `debounceTime`, `throttleTime`, and combinators like `merge`, `combine`)
19
28
 
20
29
  ## Documentation
21
30
 
22
- - API reference: <https://noshiro-pf.github.io/synstate/>
31
+ - API reference: TBD <!-- <https://noshiro-pf.github.io/synstate/> -->
23
32
 
24
33
  ## Installation
25
34
 
@@ -42,39 +51,26 @@ pnpm add synstate
42
51
  ### Simple State Management
43
52
 
44
53
  ```tsx
45
- import { createState } from 'synstate';
46
-
47
54
  // Create a reactive state
48
55
  const [state, setState, { updateState }] = createState(0);
49
56
 
57
+ const mut_history: number[] = [];
58
+
50
59
  // Subscribe to changes (in React components, Vue watchers, etc.)
51
60
  state.subscribe((count: number) => {
52
- console.log('Count:', count);
61
+ mut_history.push(count);
53
62
  });
54
63
 
64
+ assert.deepStrictEqual(mut_history, [0]);
65
+
55
66
  // Update state
56
67
  setState(1);
57
68
 
58
- updateState((prev: number) => prev + 1);
59
- ```
60
-
61
- ### Event Emitter
69
+ assert.deepStrictEqual(mut_history, [0, 1]);
62
70
 
63
- ```tsx
64
- import { createValueEmitter } from 'synstate';
65
-
66
- type User = Readonly<{ id: number; name: string }>;
67
-
68
- // Create event emitter
69
- const [userLoggedIn$, emitUserLoggedIn] = createValueEmitter<User>();
70
-
71
- // Subscribe to events
72
- userLoggedIn$.subscribe((user) => {
73
- console.log('User logged in:', user.name);
74
- });
71
+ updateState((prev: number) => prev + 1);
75
72
 
76
- // Emit events
77
- emitUserLoggedIn({ id: 1, name: 'Alice' });
73
+ assert.deepStrictEqual(mut_history, [0, 1, 2]);
78
74
  ```
79
75
 
80
76
  ### With React
@@ -125,7 +121,7 @@ const UserProfile = (): React.JSX.Element => {
125
121
 
126
122
  ### State Management
127
123
 
128
- SyncFlow provides simple, intuitive APIs for managing application state:
124
+ SynState provides simple, intuitive APIs for managing application state:
129
125
 
130
126
  - **`createState`**: Create mutable state with getter/setter
131
127
  - **`createReducer`**: Redux-style state management
@@ -142,6 +138,66 @@ Built-in event emitter for event-driven patterns:
142
138
 
143
139
  For advanced use cases, you can use observables to build complex reactive data flows. However, most applications will only need `createState`, `createReducer`, and `createValueEmitter`.
144
140
 
141
+ ## API Reference
142
+
143
+ <!-- ### State Management (Recommended)
144
+
145
+ #### createState
146
+
147
+ Create reactive state with getter and setter.
148
+
149
+ #### createBooleanState
150
+
151
+ Specialized state for boolean values.
152
+
153
+ #### createReducer
154
+
155
+ Create state with reducer pattern (like Redux).
156
+
157
+ ### Event System
158
+
159
+ #### createValueEmitter
160
+
161
+ Create type-safe event emitter with payload.
162
+
163
+ #### createEventEmitter
164
+
165
+ Create event emitter without payload.
166
+ -->
167
+
168
+ ### Advanced Features (Optional)
169
+
170
+ For complex scenarios, SynState provides observable-based APIs:
171
+
172
+ #### Creation Functions
173
+
174
+ - `source<T>()`: Create a new observable source
175
+ - `of(value)`: Create observable from a single value
176
+ - `fromArray(array)`: Create observable from array
177
+ - `fromPromise(promise)`: Create observable from promise
178
+ - `interval(ms)`: Emit values at intervals
179
+ - `timer(delay)`: Emit after delay
180
+
181
+ #### Operators
182
+
183
+ - `filter(predicate)`: Filter values
184
+ - `map(fn)`: Transform values
185
+ - `scan(reducer, seed)`: Accumulate values
186
+ - `debounceTime(ms)`: Debounce emissions
187
+ - `throttleTime(ms)`: Throttle emissions
188
+ - `skipIfNoChange()`: Skip duplicate values
189
+ - `takeUntil(notifier)`: Complete on notifier emission
190
+
191
+ #### Combination
192
+
193
+ - `combine(observables)`: Combine latest values from multiple sources
194
+ - `merge(observables)`: Merge multiple streams
195
+ - `zip(observables)`: Pair values by index
196
+
197
+ ## Examples
198
+
199
+ ### Global Counter State (React)
200
+
145
201
  ```tsx
146
202
  import * as React from 'react';
147
203
  import { createState } from 'synstate';
@@ -191,13 +247,7 @@ const ResetButton = (): React.JSX.Element => (
191
247
  );
192
248
  ```
193
249
 
194
- ## API Reference
195
-
196
- ### State Management (Recommended)
197
-
198
- #### createState
199
-
200
- Create reactive state with getter and setter:
250
+ ### Event-Driven Architecture (React)
201
251
 
202
252
  ```tsx
203
253
  import * as React from 'react';
@@ -261,9 +311,7 @@ const loginUser = async (): Promise<
261
311
  > => ({ id: 1, name: 'Alice' });
262
312
  ```
263
313
 
264
- #### createBooleanState
265
-
266
- Specialized state for boolean values:
314
+ ### Todo List with Reducer (React)
267
315
 
268
316
  ```tsx
269
317
  import * as React from 'react';
@@ -343,9 +391,7 @@ const TodoList = (): React.JSX.Element => {
343
391
  };
344
392
  ```
345
393
 
346
- #### createReducer
347
-
348
- Create state with reducer pattern (like Redux):
394
+ ### Boolean State (Dark Mode)
349
395
 
350
396
  ```tsx
351
397
  import * as React from 'react';
@@ -381,11 +427,7 @@ const ThemeToggle = (): React.JSX.Element => {
381
427
  };
382
428
  ```
383
429
 
384
- ### Event System
385
-
386
- #### createValueEmitter
387
-
388
- Create type-safe event emitter with payload:
430
+ ### Cross-Component Communication
389
431
 
390
432
  ```tsx
391
433
  import * as React from 'react';
@@ -460,9 +502,9 @@ const ItemList = (): React.JSX.Element => {
460
502
  };
461
503
  ```
462
504
 
463
- #### createEventEmitter
505
+ // Events
464
506
 
465
- Create event emitter without payload:
507
+ ### Advanced: Search with Debounce
466
508
 
467
509
  ```tsx
468
510
  import * as React from 'react';
@@ -532,302 +574,46 @@ const SearchBox = (): React.JSX.Element => {
532
574
  };
533
575
  ```
534
576
 
535
- ### Advanced Features (Optional)
536
-
537
- For complex scenarios, SyncFlow provides observable-based APIs:
538
-
539
- #### Creation Functions
540
-
541
- - `source<T>()`: Create a new observable source
542
- - `of(value)`: Create observable from a single value
543
- - `fromArray(array)`: Create observable from array
544
- - `fromPromise(promise)`: Create observable from promise
545
- - `interval(ms)`: Emit values at intervals
546
- - `timer(delay)`: Emit after delay
547
-
548
- #### Operators
549
-
550
- - `filter(predicate)`: Filter values
551
- - `map(fn)`: Transform values
552
- - `scan(reducer, seed)`: Accumulate values
553
- - `debounceTime(ms)`: Debounce emissions
554
- - `throttleTime(ms)`: Throttle emissions
555
- - `skipIfNoChange()`: Skip duplicate values
556
- - `takeUntil(notifier)`: Complete on notifier emission
557
-
558
- #### Combination
559
-
560
- - `combine(observables)`: Combine latest values from multiple sources
561
- - `merge(observables)`: Merge multiple streams
562
- - `zip(observables)`: Pair values by index
563
-
564
- ## Examples
565
-
566
- ### Global Counter State (React)
577
+ ### Advanced: Event Emitter with Throttle
567
578
 
568
579
  ```tsx
569
- import { createState } from 'synstate';
570
- import { useState, useEffect } from 'react';
571
-
572
- // Create global state
573
- export const counterState = createState(0);
574
-
575
- // Component 1
576
- function Counter() {
577
- const [count, setCount] = useState(counterState.getSnapshot());
578
-
579
- useEffect(() => {
580
- const sub = counterState.state.subscribe(setCount);
581
- return () => sub.unsubscribe();
582
- }, []);
583
-
584
- return (
585
- <div>
586
- <p>Count: {count}</p>
587
- <button onClick={() => counterState.updateState((n) => n + 1)}>
588
- Increment
589
- </button>
590
- </div>
591
- );
592
- }
593
-
594
- // Component 2 (synced automatically)
595
- function ResetButton() {
596
- return <button onClick={() => counterState.resetState()}>Reset</button>;
597
- }
598
- ```
580
+ import { createEventEmitter, throttleTime } from 'synstate';
599
581
 
600
- ### Event-Driven Architecture (React)
601
-
602
- ```tsx
603
- import { createValueEmitter } from 'synstate';
604
- import { useEffect } from 'react';
605
-
606
- // Global events
607
- export const [userLoggedIn$, emitUserLoggedIn] = createValueEmitter<{
608
- id: number;
609
- name: string;
610
- }>();
611
-
612
- export const [userLoggedOut$, emitUserLoggedOut] = createEventEmitter();
613
-
614
- // Component that emits events
615
- function LoginButton() {
616
- const handleLogin = async () => {
617
- const user = await loginUser();
618
- emitUserLoggedIn(user);
619
- };
620
-
621
- return <button onClick={handleLogin}>Login</button>;
622
- }
623
-
624
- // Component that listens to events
625
- function Notification() {
626
- const [message, setMessage] = useState('');
627
-
628
- useEffect(() => {
629
- const sub1 = userLoggedIn$.subscribe((user) => {
630
- setMessage(`Welcome, ${user.name}!`);
631
- });
632
-
633
- const sub2 = userLoggedOut$.subscribe(() => {
634
- setMessage('Logged out');
635
- });
636
-
637
- return () => {
638
- sub1.unsubscribe();
639
- sub2.unsubscribe();
640
- };
641
- }, []);
642
-
643
- return message ? <div className="notification">{message}</div> : null;
644
- }
645
- ```
646
-
647
- ### Todo List with Reducer (React)
648
-
649
- ```tsx
650
- import { createReducer } from 'synstate';
651
- import { useState, useEffect } from 'react';
652
-
653
- type Todo = { id: number; text: string; done: boolean };
654
- type Action =
655
- | { type: 'add'; text: string }
656
- | { type: 'toggle'; id: number }
657
- | { type: 'remove'; id: number };
658
-
659
- const todoState = createReducer<Todo[], Action>((todos, action) => {
660
- switch (action.type) {
661
- case 'add':
662
- return [
663
- ...todos,
664
- {
665
- id: Date.now(),
666
- text: action.text,
667
- done: false,
668
- },
669
- ];
670
- case 'toggle':
671
- return todos.map((t) =>
672
- t.id === action.id ? { ...t, done: !t.done } : t,
673
- );
674
- case 'remove':
675
- return todos.filter((t) => t.id !== action.id);
676
- }
677
- }, []);
678
-
679
- function TodoList() {
680
- const [todos, setTodos] = useState(todoState.getSnapshot());
681
-
682
- useEffect(() => {
683
- const sub = todoState.state.subscribe(setTodos);
684
- return () => sub.unsubscribe();
685
- }, []);
686
-
687
- return (
688
- <div>
689
- {todos.map((todo) => (
690
- <div key={todo.id}>
691
- <input
692
- type="checkbox"
693
- checked={todo.done}
694
- onChange={() =>
695
- todoState.dispatch({
696
- type: 'toggle',
697
- id: todo.id,
698
- })
699
- }
700
- />
701
- <span>{todo.text}</span>
702
- </div>
703
- ))}
704
- <button
705
- onClick={() =>
706
- todoState.dispatch({
707
- type: 'add',
708
- text: 'New Todo',
709
- })
710
- }
711
- >
712
- Add Todo
713
- </button>
714
- </div>
715
- );
716
- }
717
- ```
718
-
719
- ### Boolean State (Dark Mode)
720
-
721
- ```tsx
722
- import { createBooleanState } from 'synstate';
723
- import { useState, useEffect } from 'react';
724
-
725
- export const darkModeState = createBooleanState(false);
726
-
727
- function ThemeToggle() {
728
- const [isDark, setIsDark] = useState(darkModeState.getSnapshot());
729
-
730
- useEffect(() => {
731
- const sub = darkModeState.state.subscribe(setIsDark);
732
- return () => sub.unsubscribe();
733
- }, []);
734
-
735
- useEffect(() => {
736
- document.body.className = isDark ? 'dark' : 'light';
737
- }, [isDark]);
738
-
739
- return (
740
- <button onClick={() => darkModeState.toggle()}>
741
- {isDark ? '🌙' : '☀️'}
742
- </button>
743
- );
744
- }
745
- ```
746
-
747
- ### Cross-Component Communication
748
-
749
- ```tsx
750
- import { createValueEmitter, createState } from 'synstate';
751
- import { useState, useEffect } from 'react';
752
- ```
753
-
754
- // Events
755
-
756
- ### Advanced: Search with Debounce
757
-
758
- ```tsx
759
- import * as React from 'react';
760
- import {
761
- createState,
762
- debounceTime,
763
- filter,
764
- fromPromise,
765
- type Observable,
766
- switchMap,
767
- } from 'synstate';
768
- import { Result } from 'ts-data-forge';
769
-
770
- const [searchState, setSearchState] = createState('');
771
-
772
- // Advanced reactive pipeline (optional feature)
773
- const searchResults$: Observable<
774
- Result<readonly Readonly<{ id: string; name: string }>[], unknown>
775
- > = searchState
776
- .pipe(debounceTime(300))
777
- .pipe(filter((query) => query.length > 2))
778
- .pipe(
779
- switchMap((query) =>
780
- fromPromise(
781
- fetch(`/api/search?q=${query}`).then(
782
- (r) =>
783
- r.json() as Promise<
784
- readonly Readonly<{ id: string; name: string }>[]
785
- >,
786
- ),
787
- ),
788
- ),
789
- );
582
+ // Create event emitter
583
+ const [refreshClicked, onRefreshClick] = createEventEmitter();
790
584
 
791
- const SearchBox = (): React.JSX.Element => {
792
- const [results, setResults] = React.useState<
793
- readonly Readonly<{ id: string; name: string }>[]
794
- >([]);
585
+ // Subscribe to events
586
+ refreshClicked.subscribe(() => {
587
+ console.log('Refresh Clicked');
588
+ });
795
589
 
796
- React.useEffect(() => {
797
- const sub = searchResults$.subscribe((result) => {
798
- if (Result.isOk(result)) {
799
- setResults(result.value);
800
- }
801
- });
590
+ // Throttle refresh clicks to prevent rapid successive executions
591
+ const throttledRefresh = refreshClicked.pipe(throttleTime(2000));
802
592
 
803
- return () => {
804
- sub.unsubscribe();
805
- };
806
- }, []);
593
+ throttledRefresh.subscribe(() => {
594
+ console.log('Executing refresh...');
595
+ // Actual refresh logic here
596
+ // This will be called at most once every 2 seconds
597
+ });
807
598
 
808
- return (
809
- <div>
810
- <input
811
- placeholder={'Search...'}
812
- onChange={(e) => {
813
- setSearchState(e.target.value);
814
- }}
815
- />
816
- <ul>
817
- {results.map((item) => (
818
- <li key={item.id}>{item.name}</li>
819
- ))}
820
- </ul>
821
- </div>
822
- );
823
- };
599
+ const DataTable = (): React.JSX.Element => (
600
+ <div>
601
+ <button onClick={onRefreshClick}>{'Refresh'}</button>
602
+ <p>
603
+ {'Data: '}
604
+ {/* Display data here */}
605
+ </p>
606
+ </div>
607
+ );
824
608
  ```
825
609
 
826
- ## Why SyncFlow?
610
+ ## Why SynState?
827
611
 
828
612
  ### Simple State Management, Not Complex Reactive Programming
829
613
 
830
- Unlike RxJS, which can make code harder to read with many operators and complex streams, SyncFlow focuses on **simple, readable state management and event handling**. Most applications only need `createState`, `createReducer`, and `createValueEmitter` - clean, straightforward APIs that developers understand immediately.
614
+ SynState is a state management library for web frontends, similar to Redux, Jotai, Zustand, and MobX. It provides APIs for creating and managing global state across your application.
615
+
616
+ Under the hood, SynState is built on Observable patterns similar to those provided by RxJS. However, unlike RxJS, which can make code harder to read with many operators and complex streams, SynState focuses on **simple, readable state management and event handling**. Most applications only need `createState`, `createReducer`, and `createValueEmitter` - clean, straightforward APIs that developers understand immediately.
831
617
 
832
618
  **Advanced reactive features are optional** and only used when you actually need them (like debouncing search input). The library doesn't force you into a reactive programming mindset.
833
619
 
@@ -840,7 +626,7 @@ Unlike RxJS, which can make code harder to read with many operators and complex
840
626
 
841
627
  ### Use Cases
842
628
 
843
- **Use SyncFlow when you need:**
629
+ **Use SynState when you need:**
844
630
 
845
631
  - ✅ Global state management across components
846
632
  - ✅ Event-driven communication between components
@@ -855,19 +641,7 @@ Unlike RxJS, which can make code harder to read with many operators and complex
855
641
 
856
642
  ## Type Safety
857
643
 
858
- SyncFlow maintains full type information:
859
-
860
- ```tsx
861
- const userState = createState({ name: 'Alice', age: 25 });
862
- // state type: Observable<{ name: string; age: number }>
863
-
864
- const snapshot = userState.getSnapshot();
865
- // snapshot type: { name: string; age: number }
866
-
867
- const [onClick$, emitClick] = createValueEmitter<MouseEvent>();
868
- // onClick$ type: Observable<MouseEvent>
869
- // emitClick type: (event: MouseEvent) => void
870
- ```
644
+ SynState maintains full type information.
871
645
 
872
646
  ## License
873
647
 
Binary file
@@ -9,21 +9,51 @@ import { type CombineObservableRefined, type Observable } from '../types/index.m
9
9
  *
10
10
  * @example
11
11
  * ```ts
12
+ * // Timeline:
13
+ * //
14
+ * // name$ "Alice" "Bob"
15
+ * // age$ 25 30
16
+ * // user$ ["Alice",25] ["Bob",25] ["Bob",30]
17
+ * //
18
+ * // Explanation:
19
+ * // - combine waits for all sources to emit at least once
20
+ * // - Then emits the latest value from all sources whenever any source emits
21
+ * // - Always emits an array with the latest values from each source
22
+ *
12
23
  * const name$ = source<string>();
13
24
  *
14
25
  * const age$ = source<number>();
15
26
  *
16
27
  * const user$ = combine([name$, age$]);
17
28
  *
29
+ * const mut_history: (readonly [string, number])[] = [];
30
+ *
18
31
  * user$.subscribe(([name_, age]) => {
19
- * console.log({ name: name_, age });
32
+ * mut_history.push([name_, age]);
20
33
  * });
21
34
  *
22
- * name$.next('Alice');
35
+ * name$.next('Alice'); // nothing logged (age$ hasn't emitted yet)
36
+ *
37
+ * assert.deepStrictEqual(mut_history, []);
23
38
  *
24
39
  * age$.next(25); // logs: { name: 'Alice', age: 25 }
25
40
  *
41
+ * assert.deepStrictEqual(mut_history, [['Alice', 25]]);
42
+ *
26
43
  * name$.next('Bob'); // logs: { name: 'Bob', age: 25 }
44
+ *
45
+ * assert.deepStrictEqual(mut_history, [
46
+ * ['Alice', 25],
47
+ * ['Bob', 25],
48
+ * ]);
49
+ *
50
+ * age$.next(30); // logs: { name: 'Bob', age: 30 }
51
+ *
52
+ * assert.deepStrictEqual(mut_history, [
53
+ * ['Alice', 25],
54
+ * ['Bob', 25],
55
+ * ['Bob', 30],
56
+ * ]);
27
57
  * ```
28
58
  */
29
59
  export declare const combine: <const OS extends NonEmptyArray<Observable<unknown>>>(parents: OS) => CombineObservableRefined<OS>;
@@ -1 +1 @@
1
- {"version":3,"file":"combine.d.mts","sourceRoot":"","sources":["../../../src/core/combine/combine.mts"],"names":[],"mappings":"AAIA,OAAO,EAEL,KAAK,wBAAwB,EAI7B,KAAK,UAAU,EAIhB,MAAM,oBAAoB,CAAC;AAE5B;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,eAAO,MAAM,OAAO,GAAI,KAAK,CAAC,EAAE,SAAS,aAAa,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,EACzE,SAAS,EAAE,KACV,wBAAwB,CAAC,EAAE,CAIgB,CAAC;AAE/C;;;GAGG;AACH,eAAO,MAAM,aAAa,SAZI,EAAE,SAAS,aAAa,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,WAChE,EAAE,KACV,wBAAwB,CAAC,EAAE,CAUM,CAAC"}
1
+ {"version":3,"file":"combine.d.mts","sourceRoot":"","sources":["../../../src/core/combine/combine.mts"],"names":[],"mappings":"AAIA,OAAO,EAEL,KAAK,wBAAwB,EAI7B,KAAK,UAAU,EAIhB,MAAM,oBAAoB,CAAC;AAE5B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDG;AACH,eAAO,MAAM,OAAO,GAAI,KAAK,CAAC,EAAE,SAAS,aAAa,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,EACzE,SAAS,EAAE,KACV,wBAAwB,CAAC,EAAE,CAIgB,CAAC;AAE/C;;;GAGG;AACH,eAAO,MAAM,aAAa,SAZI,EAAE,SAAS,aAAa,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,WAChE,EAAE,KACV,wBAAwB,CAAC,EAAE,CAUM,CAAC"}