@reactables/core 1.0.2 → 1.1.0-beta.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
@@ -4,287 +4,12 @@
4
4
 
5
5
  Reactive state management with RxJS. Core API for building reactables.
6
6
 
7
- ## Table of Contents
7
+ [See docs at https://reactables.github.io/reactables/](https://reactables.github.io/reactables/)
8
8
 
9
- 1. [Installation](#installation)
10
- 1. [Core concepts](#core-concepts)
11
- 1. [Reactables](#reactable-concept)
12
- 1. [Hub and Store](#hub-stores)
13
- 1. [Effects](#effects)
14
- 1. [Scoped Effects](#scoped-effects)
15
- 1. [Flow & Containment](#flow-containment)
16
- 1. [Examples](#examples)
17
- 1. [Basic Counter](#basic-counter-example)
18
- 1. [Scoped Effects - Updating Todos](#scoped-effects-example)
19
- 1. [API](#api)
20
- 1. [Reactable](#reactable)
21
- 1. [RxBuilder](#rx-builder)
22
- 1. [RxConfig](#rx-config)
23
- 1. [ofTypes](#of-types)
24
- 1. [storeValue](#store-value)
25
- 1. [Other Interfaces](#interfaces)
26
- 1. [Effect](#api-effect)
27
- 1. [ScopedEffects](#api-scoped-effects)
28
- 1. [Action](#api-action)
29
- 1. [Reducer](#api-reducer)
30
- 1. [Testing Reactables](#testing)
9
+ ### Contact
31
10
 
32
- ## Installation <a name="installation"></a>
33
-
34
- Installation will require [RxJS](https://rxjs.dev/) if not already installed.
35
-
36
- `npm i rxjs @reactables/core`
37
-
38
- ## Core concepts <a name="core-concepts"></a>
39
-
40
- **Prerequisite**: Basic understanding of [Redux](https://redux.js.org/introduction/core-concepts) and [RxJS](https://rxjs.dev/) is helpful.
41
-
42
- In this documentation the term *stream* will refer to an RxJS observable stream.
43
-
44
- ### Reactables <a name="reactable-concept"></a>
45
-
46
- [Reactables](#reactable) (prefixed with Rx) are objects that encapulate all the logic required for state management. They expose a `state$` observable and `actions` methods. Applications can subscribe to `state$` to receive state changes and call action methods to trigger them.
47
-
48
- ```javascript
49
- import { RxCounter } from '@reactables/examples';
50
-
51
- const [state$, actions] = RxCounter();
52
-
53
- const { increment, reset } = actions;
54
-
55
- state$.subscribe(({ count }) => {
56
- // Update the count when state changes.
57
- document.getElementById('count').innerHTML = count;
58
- });
59
-
60
- // Bind click handlers
61
- document.getElementById('increment').addEventListener('click', increment);
62
- document.getElementById('reset').addEventListener('click', reset);
63
-
64
- ```
65
- For a full example, see [Basic Counter Example](#basic-counter-example).
66
-
67
- ### Hub and Store <a name="hub-stores"></a>
68
-
69
- Internally, [Reactables](#reactable-concept) are composed of a hub and store.
70
-
71
- The hub is responsible for dispatching actions to the store. It is also responsible for handling side effects.
72
-
73
- <img src="https://raw.githubusercontent.com/reactables/reactables/main/documentation/SlideOneHubStore.jpg" width="600" />
74
-
75
- ### Effects<a name="effects"></a>
76
-
77
- When initializing a [Reactable](#reactable-concept) we can declare effects. The hub will listen for various actions and perform side effects as needed. The store will receive actions resulting from these effects.
78
-
79
- <img src="https://raw.githubusercontent.com/reactables/reactables/main/documentation/SlideTwoEffect.jpg" width="600" />
80
-
81
- ### Scoped Effects <a name="scoped-effects"></a>
82
-
83
- Scoped Effects are dynamically created streams scoped to a particular action & key combination when an action is dispatched.
84
-
85
- <img src="https://raw.githubusercontent.com/reactables/reactables/main/documentation/SlideThreeScopedEffects.jpg" width="600" />
86
-
87
- ### Flow & Containment <a name="flow-containment"></a>
88
- Actions and logic flow through the App in one direction and are **contained** in their respective streams. This makes state updates more predictable and traceable during debugging.
89
-
90
- <img src="https://raw.githubusercontent.com/reactables/reactables/main/documentation/SlideSevenEightUnidirectionalFlow.jpg" />
91
-
92
- ## Examples <a name="examples"></a>
93
-
94
- ### Basic Counter <a name="basic-counter-example"></a>
95
-
96
- A basic counter example. Button clicks dispatch actions to increment or reset the counter.
97
-
98
- Design Diagram | Reactable | Try it out on StackBlitz.<br /> Choose your framework
99
- :-------------------------:|:-------------------------:|:-------------------------:
100
- <img src="https://raw.githubusercontent.com/reactables/reactables/main/documentation/Slide11BasicCounterExample.jpg" width="400" /> | [See Code for RxCounter](https://github.com/reactables/reactables/tree/main/packages/examples/src/RxCounter/RxCounter.ts) | <a href="https://stackblitz.com/edit/github-qtpo1k?file=src%2Findex.js"><img src="https://raw.githubusercontent.com/reactables/reactables/main/documentation/VanillaJS.jpg" width="50" /></a><br /><a href="https://stackblitz.com/edit/github-hfk1t1?file=src%2FCounter.tsx"><img src="https://raw.githubusercontent.com/reactables/reactables/main/documentation/React.png" width="60" /></a><br /><a href="https://stackblitz.com/edit/github-98unub?file=src%2Fapp%2Fcounter%2Fcounter.component.ts"><img src="https://raw.githubusercontent.com/reactables/reactables/main/documentation/Angular.png" width="60" /></a>
101
-
102
- ### Scoped Effects - Updating Todos <a name="scoped-effects-example"></a>
103
-
104
- Updating statuses of todo items shows scoped effects in action. An 'update todo' stream is created for each todo during update. Pending async calls in their respective stream are cancelled if a new request comes in with a RxJS [switchMap](https://www.learnrxjs.io/learn-rxjs/operators/transformation/switchmap) operator.
105
-
106
- Design Diagram | Reactable | Try it out on StackBlitz.<br /> Choose your framework
107
- :-------------------------:|:-------------------------:|:-------------------------:
108
- <img src="https://raw.githubusercontent.com/reactables/reactables/main/documentation/Slide12ScopedEffectsExampleTodos.jpg" width="400" /> | [See Code for RxTodoUpdates](https://github.com/reactables/reactables/tree/main/packages/examples/src/RxTodoUpdates/RxTodoUpdates.ts) | <a href="https://stackblitz.com/edit/github-6pgtev?file=src%2Findex.js"><img src="https://raw.githubusercontent.com/reactables/reactables/main/documentation/VanillaJS.jpg" width="50" /></a><br /><a href="https://stackblitz.com/edit/github-1r6pki?file=src%2FTodoUpdates.tsx"><img src="https://raw.githubusercontent.com/reactables/reactables/main/documentation/React.png" width="60" /><br /><a href="https://stackblitz.com/edit/github-zfmupm?file=src%2Fapp%2Fapp.component.ts,src%2Fapp%2Ftodos%2Ftodos.component.ts"><img src="https://raw.githubusercontent.com/reactables/reactables/main/documentation/Angular.png" width="60" /></a>
109
-
110
- ## API <a name="api"></a>
111
-
112
- ### `Reactable` <a name="reactable"></a>
113
-
114
- Reactables provide the API for applications and UI components to receive and trigger state updates.
115
-
116
- It is a tuple with the first item being an Observable emitting state changes and the second item is a dictionary of action methods for triggering state updates.
117
-
118
- Reactables may also expose an optional third item - an observable emitting all the actions the received by the store during state updates. This can be helpful if other reactables also want to subscribe and react to any of those actions.
119
-
120
- ```typescript
121
- export type Reactable<T, S = ActionMap> = [Observable<T>, S, Observable<Action<unknown>>?];
122
-
123
- export interface ActionMap {
124
- [key: string | number]: (payload?: unknown) => void | ActionMap;
125
- }
126
- ```
127
-
128
- ### `RxBuilder` <a name="rx-builder"></a>
129
-
130
- Factory function for building [Reactables](#reactable). Accepts a [RxConfig](#rx-confg) configuration object.
131
-
132
- ```typescript
133
- declare const RxBuilder: <T, S extends Cases<T>>(config: RxConfig<T, S>) => Reactable<T, { [K in keyof S]: (payload?: unknown) => void; }>;
134
-
135
- ```
136
-
137
- #### `RxConfig` <a name="rx-config"></a>
138
-
139
- Configuration object for creating Reactables.
140
-
141
- ```typescript
142
- interface RxConfig <T, S extends Cases<T>>{
143
- initialState: T;
144
- reducers: S;
145
- debug?: boolean;
146
- effects?: Effect<unknown, unknown>[];
147
- sources?: Observable<Action<unknown>>[] | { [key: string]: Observable<unknown> };
148
- }
149
-
150
- interface Cases<T> {
151
- [key: string]: SingleActionReducer<T, unknown>
152
- | {
153
- reducer: SingleActionReducer<T, unknown>
154
- effects?: (payload?: unknown) => ScopedEffects<unknown>
155
- };
156
- }
157
-
158
- type SingleActionReducer<T, S> = (state: T, action: Action<S>) => T;
159
- ```
160
- | Property | Description |
161
- | -------- | ----------- |
162
- | initialState | Initial state of the Reactable |
163
- | reducers | Dictionary of cases for the Reactable to handle. Each case can be a reducer function or a configuration object. RxBuilder will use this to generate Actions, Reducers, and add [ScopedEffects](#api-scoped-effects). |
164
- | debug (optional) | to turn on debugging to console.log all messages received by the store and state changes |
165
- | effects (optional) | Array of [Effects](#api-effects) to be registered to the Reactable |
166
- | sources (optional) <a name="hub-sources"></a> | Additional [Action](#api-actions) Observables the Reactable is listening to. Can be an array or a dictionary where key is the action type and value is the Observable emitting the payload |
167
-
168
- Debug Example:
169
-
170
- <img src="https://raw.githubusercontent.com/reactables/reactables/main/documentation/SlideSixDebug.jpg" width="500" />
171
-
172
- ### `ofTypes` <a name="of-Types"></a>
173
-
174
- Function that accepts an array of action types (`string`) and returns an `OperatorFunction` that will filter for those `Action`s.
175
-
176
- ```typescript
177
- export declare const ofTypes: (types: string[]) => OperatorFunction<Action<unknown>, Action<unknown>>;
178
- ```
179
-
180
- ### `storeValue` <a name="store-value"></a>
181
-
182
- Decorator function used store the state value in a `ReplaySubject` instead of an `Observable` so subsequent subscriptions can access the latest stored value.
183
-
184
- Also add's a `destroy` action method to be called to teardown any Reactable decorated with `storeValue`.
185
-
186
- ```typescript
187
- interface DestroyAction {
188
- destroy: () => void;
189
- }
190
-
191
- declare const storeValue: <T, S>(reactable: Reactable<T, S>) => Reactable<T, S & DestroyAction>;
192
- ```
193
-
194
- ### Other Interfaces <a name="interfaces"></a>
195
-
196
- #### `Effect` <a name="api-effect"></a>
197
-
198
- Effects are expressed as [RxJS Operator Functions](https://rxjs.dev/api/index/interface/OperatorFunction). They pipe the [dispatcher$](#hub-dispatcher) stream and run side effects on incoming [Actions](#api-action).
199
-
200
- ```typescript
201
- type Effect<T, S> = OperatorFunction<Action<T>, Action<S>>;
202
- ```
203
-
204
- #### `ScopedEffects` <a name="api-scoped-effects"></a>
205
-
206
- Scoped Effects are declared when defining reducers in [RxConfig](#rx-config). They are dynamically created stream(s) scoped to an Action `type` & `key` combination.
207
-
208
- ```typescript
209
- interface ScopedEffects<T> {
210
- key?: string;
211
- effects: Effect<T, unknown>[];
212
- }
213
- ```
214
- | Property | Description |
215
- | -------- | ----------- |
216
- | key (optional) | key to be combined with the Action `type` to generate a unique signature for the effect stream(s). Example: An id for the entity the action is being performed on. |
217
- | effects | Array of [Effects](#api-effects) scoped to the Action `type` & `key` |
218
-
219
-
220
- #### `Action` <a name="api-action"></a>
221
- ```typescript
222
- interface Action<T = undefined> {
223
- type: string;
224
- payload?: T;
225
- scopedEffects?: ScopedEffects<T>;
226
- }
227
- ```
228
- | Property | Description |
229
- | -------- | ----------- |
230
- | type | type of Action being dispatched |
231
- | payload (optional) | payload associated with Action |
232
- | scopedEffects (optional) | [See ScopedEffects](#api-scoped-effects) |
233
-
234
- #### `Reducer` <a name="api-reducer"></a>
235
-
236
- From the [Redux Docs](https://redux.js.org/tutorials/fundamentals/part-3-state-actions-reducers):
237
- > Reducers are functions that take the current state and an action as arguments, and return a new state result
238
-
239
- ```typescript
240
- type Reducer<T> = (state?: T, action?: Action<unknown>) => T;
241
- ```
242
-
243
- ## Testing Reactables<a name="testing"></a>
244
-
245
- We can use RxJS's built in [Marble Testing](https://rxjs.dev/guide/testing/marble-testing) for testing [Reactables](#reactable).
246
-
247
- [Test for RxCounter](https://github.com/reactables/reactables/blob/main/packages/examples/src/RxCounter/RxCounter.test.ts)
248
-
249
- ```typescript
250
- import { RxCounter } from './RxCounter';
251
- import { Subscription } from 'rxjs';
252
- import { TestScheduler } from 'rxjs/testing';
253
-
254
- describe('RxCounter', () => {
255
- let testScheduler: TestScheduler;
256
- let subscription: Subscription;
257
-
258
- beforeEach(() => {
259
- testScheduler = new TestScheduler((actual, expected) => {
260
- expect(actual).toEqual(expected);
261
- });
262
- });
263
- afterEach(() => {
264
- subscription?.unsubscribe();
265
- });
266
-
267
- it('should increment and reset', () => {
268
- testScheduler.run(({ expectObservable, cold }) => {
269
- // Create Counter Reactable
270
- const [
271
- state$,
272
- { increment, reset },
273
- ] = RxCounter();
274
-
275
- // Call actions
276
- subscription = cold('--b-c', {
277
- b: increment,
278
- c: reset,
279
- }).subscribe((action) => action());
280
-
281
- // Assertions
282
- expectObservable(state$).toBe('a-b-c', {
283
- a: { count: 0 },
284
- b: { count: 1 },
285
- c: { count: 0 },
286
- });
287
- });
288
- });
289
- });
290
- ```
11
+ Dave Lai
12
+ email: <a href="dlai@dave-lai.com">dlai@dave-lai.com</a>
13
+ <br>
14
+ <br>
15
+ Github: https://github.com/laidav
@@ -1,3 +1,3 @@
1
1
  export { RxBuilder, EffectsAndSources, RxConfig } from './RxBuilder';
2
- export { storeValue } from './storeValue';
2
+ export { storeValue, DestroyAction } from './storeValue';
3
3
  export { Cases } from './createSlice';
package/package.json CHANGED
@@ -16,5 +16,5 @@
16
16
  "peerDependencies": {
17
17
  "rxjs": "^6.0.0 || ^7.0.0"
18
18
  },
19
- "version": "1.0.2"
19
+ "version": "1.1.0-beta.0"
20
20
  }