@flexsurfer/reflex 0.1.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/LICENSE +21 -0
- package/README.md +282 -0
- package/dist/index.cjs +1097 -0
- package/dist/index.d.cts +150 -0
- package/dist/index.d.ts +150 -0
- package/dist/index.js +1041 -0
- package/package.json +61 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 flexsurfer
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<img src="reflex-logo-300kb.png" alt="Reflex Logo" width="200" />
|
|
3
|
+
</div>
|
|
4
|
+
|
|
5
|
+
**re-frame for the JavaScript world**
|
|
6
|
+
|
|
7
|
+
A reactive, functional state management library that brings the elegance and power of ClojureScript's re-frame to JavaScript and React/RN applications.
|
|
8
|
+
|
|
9
|
+
> β οΈ **Development Status**: This library is in active development and not yet ready for production use. Perfect for pet projects, experiments, and learning! The community is warmly invited to contribute to the development and improvement of the library, including optimizations for data comparison at different lifecycle stages.
|
|
10
|
+
|
|
11
|
+
[](https://badge.fury.io/js/@flexsurfer%2Freflex)
|
|
12
|
+
[](https://opensource.org/licenses/MIT)
|
|
13
|
+
|
|
14
|
+
π **Want to understand the philosophy behind this approach?** Check out the amazing [re-frame documentation](https://day8.github.io/re-frame/re-frame/) which describes the greatness of this framework in the finest details. Everything you learn there applies to reflex! Though we do lose some of ClojureScript's natural immutability magic. Immer helps bridge this gap, but it's not quite as elegant or efficient as CLJS persistent data structures.
|
|
15
|
+
|
|
16
|
+
## β¨ Why Reflex?
|
|
17
|
+
|
|
18
|
+
After 10+ years of building applications with re-frame in the ClojureScript world, I wanted to bring the same architectural elegance to the JavaScript/TypeScript ecosystem. Reflex is not just another state management libraryβit's a battle-tested pattern that promotes:
|
|
19
|
+
|
|
20
|
+
π― **Predictable State Management** - Unidirectional data flow with pure functions
|
|
21
|
+
π§© **Composable Architecture** - Build complex apps from simple, reusable pieces
|
|
22
|
+
π **Reactive Subscriptions** - UI automatically updates when state changes
|
|
23
|
+
β‘ **Interceptor Pattern** - Powerful middleware system for cross-cutting concerns
|
|
24
|
+
π‘οΈ **Type Safety** - Full TypeScript support with excellent IDE experience
|
|
25
|
+
π§ͺ **Testability** - Pure functions make testing straightforward and reliable
|
|
26
|
+
|
|
27
|
+
## π Quick Start
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install @flexsurfer/reflex
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Basic Example
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
import {
|
|
37
|
+
initAppDb,
|
|
38
|
+
regEvent,
|
|
39
|
+
regSub,
|
|
40
|
+
dispatch,
|
|
41
|
+
useSubscription
|
|
42
|
+
} from '@flexsurfer/reflex';
|
|
43
|
+
|
|
44
|
+
// Initialize your app database
|
|
45
|
+
initAppDb({ counter: 0 });
|
|
46
|
+
|
|
47
|
+
// Register events (state transitions)
|
|
48
|
+
regEvent('increment', ({ draftDb }) => {
|
|
49
|
+
draftDb.counter += 1;
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
regEvent('decrement', ({ draftDb }) => {
|
|
53
|
+
draftDb.counter -= 1;
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Register subscriptions (reactive queries)
|
|
57
|
+
regSub('counter');
|
|
58
|
+
|
|
59
|
+
// React component
|
|
60
|
+
function Counter() {
|
|
61
|
+
const counter = useSubscription<number>(['counter']);
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<div>
|
|
65
|
+
<h1>Count: {counter}</h1>
|
|
66
|
+
<button onClick={() => dispatch(['increment'])}>+</button>
|
|
67
|
+
<button onClick={() => dispatch(['decrement'])}>-</button>
|
|
68
|
+
</div>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## ποΈ Core Concepts
|
|
74
|
+
|
|
75
|
+
### Events & Effects
|
|
76
|
+
|
|
77
|
+
Events are pure functions that describe state transitions:
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
// Simple state update
|
|
81
|
+
regEvent('set-name', ({ draftDb }, name) => {
|
|
82
|
+
draftDb.user.name = name;
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Dispatch with parameters
|
|
86
|
+
dispatch(['set-name', 'John Doe']);
|
|
87
|
+
|
|
88
|
+
// Event with side effects
|
|
89
|
+
regEvent('save-user', ({ draftDb }, user) => {
|
|
90
|
+
draftDb.user = user;
|
|
91
|
+
draftDb.saving = true;
|
|
92
|
+
return [
|
|
93
|
+
['http', {
|
|
94
|
+
method: 'POST',
|
|
95
|
+
url: '/api/users',
|
|
96
|
+
body: user,
|
|
97
|
+
onSuccess: ['save-user-success'],
|
|
98
|
+
onFailure: ['save-user-error']
|
|
99
|
+
}]
|
|
100
|
+
]
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// Dispatch with parameters
|
|
104
|
+
dispatch(['save-user', { id: 1, name: 'John', email: 'john@example.com' }]);
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Subscriptions
|
|
108
|
+
|
|
109
|
+
Create reactive queries that automatically update your UI:
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
regSub('user');
|
|
113
|
+
regSub('display-prefix');
|
|
114
|
+
regSub('user-name', (user) => user.name, () => [['user']]);
|
|
115
|
+
|
|
116
|
+
// Computed subscription with dependencies
|
|
117
|
+
regSub('user-display-name',
|
|
118
|
+
(name, prefix) => `${prefix}: ${name}`,
|
|
119
|
+
() => [['user-name'], ['display-prefix']]
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
// Parameterized subscription
|
|
123
|
+
regSub(
|
|
124
|
+
'todo-by-id',
|
|
125
|
+
(todos, id) => todos.find(todo => todo.id === id),
|
|
126
|
+
() => [['todos']]
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
regSub(
|
|
130
|
+
'todo-text-by-id',
|
|
131
|
+
(todo, _id) => todo.text,
|
|
132
|
+
(id) => [['todo-by-id' id]]
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
// Use in React components
|
|
136
|
+
function UserProfile() {
|
|
137
|
+
const name = useSubscription<string>(['user-display-name']);
|
|
138
|
+
const todo = useSubscription(['todo-by-id', 123])
|
|
139
|
+
const todoText = useSubscription(['todo-text-by-id', 123]);
|
|
140
|
+
|
|
141
|
+
return <div>{name}</div>;
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Effects & Co-effects
|
|
146
|
+
|
|
147
|
+
Handle side effects in a controlled, testable way:
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
import {
|
|
151
|
+
regEffect,
|
|
152
|
+
regCoeffect
|
|
153
|
+
} from '@flexsurfer/reflex';
|
|
154
|
+
|
|
155
|
+
// Register custom effects
|
|
156
|
+
regEffect('local-storage', (payload) => {
|
|
157
|
+
localStorage.setItem(payload.key, JSON.stringify(payload.value));
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// Use in events
|
|
161
|
+
regEvent('save-to-storage', (_coeffects, data) => {
|
|
162
|
+
return [['local-storage', { key: 'app-data', value: data }]]
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// Dispatch with data parameter
|
|
166
|
+
dispatch(['save-to-storage', { user: 'John', preferences: { theme: 'dark' } }]);
|
|
167
|
+
|
|
168
|
+
// Register co-effects
|
|
169
|
+
regCoeffect('timestamp', (coeffects) => {
|
|
170
|
+
coeffects.timestamp = Date.now();
|
|
171
|
+
return coeffects;
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
regCoeffect('random', (coeffects) => {
|
|
175
|
+
coeffects.random = Math.random();
|
|
176
|
+
return coeffects;
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// Use co-effect in events
|
|
180
|
+
regEvent('log-action',
|
|
181
|
+
({ draftDb, timestamp, random }, action) => {
|
|
182
|
+
draftDb.actionLog.push({
|
|
183
|
+
action,
|
|
184
|
+
timestamp: timestamp,
|
|
185
|
+
id: random.toString(36)
|
|
186
|
+
});
|
|
187
|
+
},
|
|
188
|
+
[['timestamp'], ['random']]
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
// Dispatch with action parameter
|
|
192
|
+
dispatch(['log-action', 'some-action']);
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Interceptors
|
|
196
|
+
|
|
197
|
+
Compose functionality with interceptors:
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
const loggingInterceptor = {
|
|
201
|
+
id: 'logging',
|
|
202
|
+
before: (context) => {
|
|
203
|
+
console.log('Event:', context.coeffects.event);
|
|
204
|
+
return context;
|
|
205
|
+
},
|
|
206
|
+
after: (context) => {
|
|
207
|
+
console.log('Updated DB:', context.coeffects.newDb);
|
|
208
|
+
return context;
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
regEvent('my-event', handler, [loggingInterceptor]);
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## π― Why Re-frame Pattern?
|
|
216
|
+
|
|
217
|
+
The re-frame pattern has proven itself in production applications over many years:
|
|
218
|
+
|
|
219
|
+
- **Separation of Concerns**: Clear boundaries between events, effects, and subscriptions
|
|
220
|
+
- **Time Travel Debugging**: Every state change is an event that can be replayed
|
|
221
|
+
- **Testability**: Pure functions make unit testing straightforward
|
|
222
|
+
- **Composability**: Build complex features from simple, reusable parts
|
|
223
|
+
- **Maintainability**: Code becomes self-documenting and easy to reason about
|
|
224
|
+
|
|
225
|
+
## π Migration from Other Libraries
|
|
226
|
+
|
|
227
|
+
### From Redux
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
// Redux style
|
|
231
|
+
const counterSlice = createSlice({
|
|
232
|
+
name: 'count',
|
|
233
|
+
initialState: { value: 0 },
|
|
234
|
+
reducers: {
|
|
235
|
+
increment: (state) => { state.value += 1; }
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
// Reflex style
|
|
240
|
+
initAppDb({ count: 0 });
|
|
241
|
+
regEvent('increment', ({ draftDb }) => {
|
|
242
|
+
draftDb.count += 1;
|
|
243
|
+
});
|
|
244
|
+
regSub('count');
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### From Zustand
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
// Zustand style
|
|
251
|
+
const useStore = create((set) => ({
|
|
252
|
+
count: 0,
|
|
253
|
+
increment: () => set((state) => ({ count: state.count + 1 }))
|
|
254
|
+
}));
|
|
255
|
+
|
|
256
|
+
// Reflex style
|
|
257
|
+
initAppDb({ count: 0 });
|
|
258
|
+
regEvent('increment', ({ draftDb }) => {
|
|
259
|
+
draftDb.count += 1;
|
|
260
|
+
});
|
|
261
|
+
regSub('count');
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
## π Learn More
|
|
265
|
+
|
|
266
|
+
- [re-frame Documentation](https://day8.github.io/re-frame/re-frame/) - The original and comprehensive guide to understanding the philosophy and patterns
|
|
267
|
+
- Step-by-Step Tutorial - TBD
|
|
268
|
+
- API Reference - TBD
|
|
269
|
+
- Examples - TBD
|
|
270
|
+
- Best Practices - TBD
|
|
271
|
+
|
|
272
|
+
## π€ Contributing
|
|
273
|
+
|
|
274
|
+
Contributions are welcome! Please feel free to submit a Pull Request or file an issue with questions, suggestions, or ideas.
|
|
275
|
+
|
|
276
|
+
## π License
|
|
277
|
+
|
|
278
|
+
MIT Β© [flexsurfer](https://github.com/flexsurfer)
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
*Bringing the wisdom of ClojureScript's re-frame to the JavaScript world. Now your React applications can enjoy the same architectural benefits that have made re-frame a joy to work with for over a decade.*
|