@flexsurfer/reflex 0.1.13 โ 0.1.14
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 +6 -248
- package/dist/index.cjs +36 -29
- package/dist/index.d.cts +20 -11
- package/dist/index.d.ts +20 -11
- package/dist/index.mjs +31 -24
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
**re-frame for the JavaScript world**
|
|
6
6
|
|
|
7
|
-
A reactive, functional state management library that brings the elegance and power of ClojureScript's re-frame to JavaScript and React/ReactNative applications.
|
|
7
|
+
A reactive, functional state management library that brings the elegance and power of ClojureScript's re-frame to JavaScript/TypeScript and React/ReactNative applications.
|
|
8
8
|
|
|
9
9
|
[](https://opensource.org/licenses/MIT)
|
|
10
10
|
[](https://www.npmjs.com/package/@flexsurfer/reflex)
|
|
@@ -26,259 +26,17 @@ After many years of building applications with re-frame in the ClojureScript wor
|
|
|
26
26
|
๐ก๏ธ **Type Safety** - Full TypeScript support with excellent IDE experience
|
|
27
27
|
๐งช **Testability** - Pure functions make testing straightforward and reliable
|
|
28
28
|
|
|
29
|
-
## ๐ Quick Start
|
|
30
|
-
|
|
31
|
-
```bash
|
|
32
|
-
npm install @flexsurfer/reflex
|
|
33
|
-
npm install --save-dev @flexsurfer/reflex-devtools
|
|
34
|
-
|
|
35
|
-
npx reflex-devtools
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
### Basic Example
|
|
39
|
-
|
|
40
|
-
```typescript
|
|
41
|
-
import {
|
|
42
|
-
initAppDb,
|
|
43
|
-
regEvent,
|
|
44
|
-
regSub,
|
|
45
|
-
dispatch,
|
|
46
|
-
useSubscription,
|
|
47
|
-
enableTracing
|
|
48
|
-
} from '@flexsurfer/reflex';
|
|
49
|
-
import { enableDevtools } from '@flexsurfer/reflex-devtools'
|
|
50
|
-
|
|
51
|
-
enableTracing()
|
|
52
|
-
enableDevtools();
|
|
53
|
-
|
|
54
|
-
// Initialize your app database
|
|
55
|
-
initAppDb({ counter: 0 });
|
|
56
|
-
|
|
57
|
-
// Register events (state transitions)
|
|
58
|
-
regEvent('increment', ({ draftDb }) => {
|
|
59
|
-
draftDb.counter += 1;
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
regEvent('decrement', ({ draftDb }) => {
|
|
63
|
-
draftDb.counter -= 1;
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
// Register subscriptions (reactive queries)
|
|
67
|
-
regSub('counter');
|
|
68
|
-
|
|
69
|
-
// React component
|
|
70
|
-
const Counter = () => {
|
|
71
|
-
const counter = useSubscription<number>(['counter']);
|
|
72
|
-
|
|
73
|
-
return (
|
|
74
|
-
<div>
|
|
75
|
-
<h1>Count: {counter}</h1>
|
|
76
|
-
<button onClick={() => dispatch(['increment'])}>+</button>
|
|
77
|
-
<button onClick={() => dispatch(['decrement'])}>-</button>
|
|
78
|
-
</div>
|
|
79
|
-
);
|
|
80
|
-
}
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
## ๐๏ธ Core Concepts
|
|
84
|
-
|
|
85
|
-
### Events & Effects
|
|
86
|
-
|
|
87
|
-
Events define state transitions and may declare side effects:
|
|
88
|
-
|
|
89
|
-
```typescript
|
|
90
|
-
// Simple state update
|
|
91
|
-
regEvent('set-name', ({ draftDb }, name) => {
|
|
92
|
-
draftDb.user.name = name;
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
// Dispatch with parameters
|
|
96
|
-
dispatch(['set-name', 'John Doe']);
|
|
97
|
-
|
|
98
|
-
// Event with side effects
|
|
99
|
-
regEvent('save-user', ({ draftDb }, user) => {
|
|
100
|
-
draftDb.saving = true;
|
|
101
|
-
return [
|
|
102
|
-
['http', {
|
|
103
|
-
method: 'POST',
|
|
104
|
-
url: '/api/users',
|
|
105
|
-
body: user,
|
|
106
|
-
onSuccess: ['save-user-success'],
|
|
107
|
-
onFailure: ['save-user-error']
|
|
108
|
-
}]
|
|
109
|
-
]
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
// Dispatch with parameters
|
|
113
|
-
dispatch(['save-user', { id: 1, name: 'John', email: 'john@example.com' }]);
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
### Subscriptions
|
|
117
|
-
|
|
118
|
-
Create reactive queries that automatically update your UI:
|
|
119
|
-
|
|
120
|
-
```typescript
|
|
121
|
-
regSub('user');
|
|
122
|
-
regSub('display-prefix');
|
|
123
|
-
regSub('user-name', (user) => user.name, () => [['user']]);
|
|
124
|
-
|
|
125
|
-
// Computed subscription with dependencies
|
|
126
|
-
regSub('user-display-name',
|
|
127
|
-
(name, prefix) => `${prefix}: ${name}`,
|
|
128
|
-
() => [['user-name'], ['display-prefix']]
|
|
129
|
-
);
|
|
130
|
-
|
|
131
|
-
// Parameterized subscription
|
|
132
|
-
regSub(
|
|
133
|
-
'todo-by-id',
|
|
134
|
-
(todos, id) => todos.find(todo => todo.id === id),
|
|
135
|
-
() => [['todos']]
|
|
136
|
-
);
|
|
137
|
-
|
|
138
|
-
regSub(
|
|
139
|
-
'todo-text-by-id',
|
|
140
|
-
(todo, _id) => todo.text,
|
|
141
|
-
(id) => [['todo-by-id' id]]
|
|
142
|
-
);
|
|
143
|
-
|
|
144
|
-
// Use in React components
|
|
145
|
-
function UserProfile() {
|
|
146
|
-
const name = useSubscription<string>(['user-display-name']);
|
|
147
|
-
const todo = useSubscription(['todo-by-id', 123])
|
|
148
|
-
const todoText = useSubscription(['todo-text-by-id', 123]);
|
|
149
|
-
|
|
150
|
-
return <div>{name}</div>;
|
|
151
|
-
}
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
### Effects & Co-effects
|
|
155
|
-
|
|
156
|
-
Handle side effects in a controlled, testable way:
|
|
157
|
-
|
|
158
|
-
```typescript
|
|
159
|
-
import {
|
|
160
|
-
regEffect,
|
|
161
|
-
regCoeffect
|
|
162
|
-
} from '@flexsurfer/reflex';
|
|
163
|
-
|
|
164
|
-
// Register custom effects
|
|
165
|
-
regEffect('local-storage', (payload) => {
|
|
166
|
-
localStorage.setItem(payload.key, JSON.stringify(payload.value));
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
// Use in events
|
|
170
|
-
regEvent('save-to-storage', (_coeffects, data) => {
|
|
171
|
-
return [['local-storage', { key: 'app-data', value: data }]]
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
// Dispatch with data parameter
|
|
175
|
-
dispatch(['save-to-storage', { user: 'John', preferences: { theme: 'dark' } }]);
|
|
176
|
-
|
|
177
|
-
// Register co-effects
|
|
178
|
-
regCoeffect('timestamp', (coeffects) => {
|
|
179
|
-
coeffects.timestamp = Date.now();
|
|
180
|
-
return coeffects;
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
regCoeffect('random', (coeffects) => {
|
|
184
|
-
coeffects.random = Math.random();
|
|
185
|
-
return coeffects;
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
// Use co-effect in events
|
|
189
|
-
regEvent('log-action',
|
|
190
|
-
({ draftDb, timestamp, random }, action) => {
|
|
191
|
-
draftDb.actionLog.push({
|
|
192
|
-
action,
|
|
193
|
-
timestamp: timestamp,
|
|
194
|
-
id: random.toString(36)
|
|
195
|
-
});
|
|
196
|
-
},
|
|
197
|
-
[['timestamp'], ['random']]
|
|
198
|
-
);
|
|
199
|
-
|
|
200
|
-
// Dispatch with action parameter
|
|
201
|
-
dispatch(['log-action', 'some-action']);
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
### Interceptors
|
|
205
|
-
|
|
206
|
-
Compose functionality with interceptors:
|
|
207
|
-
|
|
208
|
-
```typescript
|
|
209
|
-
const loggingInterceptor = {
|
|
210
|
-
id: 'logging',
|
|
211
|
-
before: (context) => {
|
|
212
|
-
console.log('Event:', context.coeffects.event);
|
|
213
|
-
return context;
|
|
214
|
-
},
|
|
215
|
-
after: (context) => {
|
|
216
|
-
console.log('Updated DB:', context.coeffects.newDb);
|
|
217
|
-
return context;
|
|
218
|
-
}
|
|
219
|
-
};
|
|
220
|
-
|
|
221
|
-
regEvent('my-event', handler, [loggingInterceptor]);
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
## ๐ฏ Why Re-frame Pattern?
|
|
225
|
-
|
|
226
|
-
The re-frame pattern has proven itself in production applications over many years:
|
|
227
|
-
|
|
228
|
-
- **Separation of Concerns**: Clear boundaries between events, effects, and subscriptions
|
|
229
|
-
- **Time Travel Debugging**: Every state change is an event that can be replayed
|
|
230
|
-
- **Testability**: Pure functions make unit testing straightforward
|
|
231
|
-
- **Composability**: Build complex features from simple, reusable parts
|
|
232
|
-
- **Maintainability**: Code becomes self-documenting and easy to reason about
|
|
233
|
-
|
|
234
|
-
## ๐ Migration from Other Libraries
|
|
235
|
-
|
|
236
|
-
### From Redux
|
|
237
|
-
|
|
238
|
-
```typescript
|
|
239
|
-
// Redux style
|
|
240
|
-
const counterSlice = createSlice({
|
|
241
|
-
name: 'count',
|
|
242
|
-
initialState: { value: 0 },
|
|
243
|
-
reducers: {
|
|
244
|
-
increment: (state) => { state.value += 1; }
|
|
245
|
-
}
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
// Reflex style
|
|
249
|
-
initAppDb({ count: 0 });
|
|
250
|
-
regEvent('increment', ({ draftDb }) => {
|
|
251
|
-
draftDb.count += 1;
|
|
252
|
-
});
|
|
253
|
-
regSub('count');
|
|
254
|
-
```
|
|
255
|
-
|
|
256
|
-
### From Zustand
|
|
257
|
-
|
|
258
|
-
```typescript
|
|
259
|
-
// Zustand style
|
|
260
|
-
const useStore = create((set) => ({
|
|
261
|
-
count: 0,
|
|
262
|
-
increment: () => set((state) => ({ count: state.count + 1 }))
|
|
263
|
-
}));
|
|
264
|
-
|
|
265
|
-
// Reflex style
|
|
266
|
-
initAppDb({ count: 0 });
|
|
267
|
-
regEvent('increment', ({ draftDb }) => {
|
|
268
|
-
draftDb.count += 1;
|
|
269
|
-
});
|
|
270
|
-
regSub('count');
|
|
271
|
-
```
|
|
272
|
-
|
|
273
29
|
## ๐ Learn More
|
|
274
30
|
|
|
31
|
+
- [Documentation](https://reflex.js.org/docs/)
|
|
32
|
+
- [Step-by-Step Tutorial](https://reflex.js.org/docs/quick-start.html)
|
|
33
|
+
- [Best Practices](https://reflex.js.org/docs/api-reference.html)
|
|
34
|
+
- [API Reference](https://reflex.js.org/docs/best-practices.html)
|
|
275
35
|
- [re-frame Documentation](https://day8.github.io/re-frame/re-frame/) - The original and comprehensive guide to understanding the philosophy and patterns
|
|
276
|
-
|
|
277
|
-
- API Reference - TBD
|
|
36
|
+
|
|
278
37
|
- Examples
|
|
279
38
|
- [TodoMVC](https://github.com/flexsurfer/reflex/tree/main/examples/todomvc) - Classic todo app implementation showcasing core reflex patterns
|
|
280
39
|
- [Einbรผrgerungstest](https://github.com/flexsurfer/einburgerungstest/) - German citizenship test app built with reflex ([Live Demo](https://www.ebtest.org/))
|
|
281
|
-
- Best Practices - TBD
|
|
282
40
|
|
|
283
41
|
## ๐ค Contributing
|
|
284
42
|
|
package/dist/index.cjs
CHANGED
|
@@ -40,6 +40,7 @@ __export(src_exports, {
|
|
|
40
40
|
clearHotReloadCallbacks: () => clearHotReloadCallbacks,
|
|
41
41
|
clearReactions: () => clearReactions,
|
|
42
42
|
clearSubs: () => clearSubs,
|
|
43
|
+
current: () => current,
|
|
43
44
|
debounceAndDispatch: () => debounceAndDispatch,
|
|
44
45
|
defaultErrorHandler: () => defaultErrorHandler,
|
|
45
46
|
disableTracing: () => disableTracing,
|
|
@@ -51,7 +52,7 @@ __export(src_exports, {
|
|
|
51
52
|
getHandler: () => getHandler,
|
|
52
53
|
getSubscriptionValue: () => getSubscriptionValue,
|
|
53
54
|
initAppDb: () => initAppDb,
|
|
54
|
-
|
|
55
|
+
original: () => original,
|
|
55
56
|
regCoeffect: () => regCoeffect,
|
|
56
57
|
regEffect: () => regEffect,
|
|
57
58
|
regEvent: () => regEvent,
|
|
@@ -60,7 +61,6 @@ __export(src_exports, {
|
|
|
60
61
|
regSub: () => regSub,
|
|
61
62
|
registerHotReloadCallback: () => registerHotReloadCallback,
|
|
62
63
|
registerTraceCb: () => registerTraceCb,
|
|
63
|
-
setDebugEnabled: () => setDebugEnabled,
|
|
64
64
|
setupSubsHotReload: () => setupSubsHotReload,
|
|
65
65
|
throttleAndDispatch: () => throttleAndDispatch,
|
|
66
66
|
triggerHotReload: () => triggerHotReload,
|
|
@@ -230,6 +230,15 @@ function updateAppDbWithPatches(newDb, patches) {
|
|
|
230
230
|
}
|
|
231
231
|
}
|
|
232
232
|
|
|
233
|
+
// src/immer-utils.ts
|
|
234
|
+
var import_immer = require("immer");
|
|
235
|
+
function original(value) {
|
|
236
|
+
return (0, import_immer.isDraft)(value) ? (0, import_immer.original)(value) : value;
|
|
237
|
+
}
|
|
238
|
+
function current(value) {
|
|
239
|
+
return (0, import_immer.isDraft)(value) ? (0, import_immer.current)(value) : value;
|
|
240
|
+
}
|
|
241
|
+
|
|
233
242
|
// src/interceptor.ts
|
|
234
243
|
function isInterceptor(m) {
|
|
235
244
|
if (typeof m !== "object" || m === null)
|
|
@@ -563,20 +572,15 @@ regEffect(DISPATCH, (value) => {
|
|
|
563
572
|
});
|
|
564
573
|
|
|
565
574
|
// src/events.ts
|
|
566
|
-
var
|
|
575
|
+
var import_immer2 = require("immer");
|
|
567
576
|
|
|
568
577
|
// src/settings.ts
|
|
569
578
|
var store = {
|
|
570
|
-
globalInterceptors: []
|
|
571
|
-
debugEnabled: true
|
|
572
|
-
// Default to true, can be configured via setDebugEnabled
|
|
579
|
+
globalInterceptors: []
|
|
573
580
|
};
|
|
574
581
|
function replaceGlobalInterceptor(globalInterceptors, interceptor) {
|
|
575
582
|
return globalInterceptors.reduce((ret, existingInterceptor) => {
|
|
576
583
|
if (interceptor.id === existingInterceptor.id) {
|
|
577
|
-
if (store.debugEnabled) {
|
|
578
|
-
consoleLog("warn", "[reflex] replacing duplicate global interceptor id:", interceptor.id);
|
|
579
|
-
}
|
|
580
584
|
return [...ret, interceptor];
|
|
581
585
|
} else {
|
|
582
586
|
return [...ret, existingInterceptor];
|
|
@@ -602,12 +606,6 @@ function clearGlobalInterceptors(id) {
|
|
|
602
606
|
store.globalInterceptors = store.globalInterceptors.filter((interceptor) => interceptor.id !== id);
|
|
603
607
|
}
|
|
604
608
|
}
|
|
605
|
-
function setDebugEnabled(enabled) {
|
|
606
|
-
store.debugEnabled = enabled;
|
|
607
|
-
}
|
|
608
|
-
function isDebugEnabled() {
|
|
609
|
-
return store.debugEnabled;
|
|
610
|
-
}
|
|
611
609
|
|
|
612
610
|
// src/trace.ts
|
|
613
611
|
var nextId = 1;
|
|
@@ -708,6 +706,13 @@ function enableTracePrint() {
|
|
|
708
706
|
});
|
|
709
707
|
}
|
|
710
708
|
|
|
709
|
+
// src/env.ts
|
|
710
|
+
var IS_DEV = (
|
|
711
|
+
// Node.js check
|
|
712
|
+
typeof process !== "undefined" && process.env?.NODE_ENV === "development" || // React Native / bundler check
|
|
713
|
+
typeof __DEV__ !== "undefined" && __DEV__
|
|
714
|
+
);
|
|
715
|
+
|
|
711
716
|
// src/events.ts
|
|
712
717
|
var KIND3 = "event";
|
|
713
718
|
function regEvent(id, handler, cofxOrInterceptors, interceptors) {
|
|
@@ -756,22 +761,28 @@ function registerInterceptors(id, cofxOrInterceptors, interceptors) {
|
|
|
756
761
|
setInterceptors(id, allInterceptors);
|
|
757
762
|
}
|
|
758
763
|
}
|
|
759
|
-
(0,
|
|
764
|
+
(0, import_immer2.enablePatches)();
|
|
760
765
|
function eventHandlerInterceptor(handler) {
|
|
761
766
|
return {
|
|
762
767
|
id: "fx-handler",
|
|
763
768
|
before(context) {
|
|
764
|
-
const
|
|
765
|
-
const event = coeffects.event;
|
|
769
|
+
const event = context.coeffects.event;
|
|
766
770
|
const params = event.slice(1);
|
|
767
771
|
let effects = [];
|
|
768
|
-
const [newDb, patches] = (0,
|
|
772
|
+
const [newDb, patches] = (0, import_immer2.produceWithPatches)(
|
|
769
773
|
getAppDb(),
|
|
770
774
|
(draftDb) => {
|
|
771
|
-
|
|
772
|
-
effects = handler(
|
|
775
|
+
const coeffectsWithDb = { ...context.coeffects, draftDb };
|
|
776
|
+
effects = handler(coeffectsWithDb, ...params) || [];
|
|
773
777
|
}
|
|
774
778
|
);
|
|
779
|
+
if (IS_DEV) {
|
|
780
|
+
try {
|
|
781
|
+
JSON.stringify(effects);
|
|
782
|
+
} catch (e) {
|
|
783
|
+
consoleLog("warn", `[reflex] Effects ${effects} contain Proxy (probably an Immer draft). Use current() for draftDb values.`);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
775
786
|
context.newDb = newDb;
|
|
776
787
|
context.patches = patches;
|
|
777
788
|
mergeTrace({ tags: { "patches": patches, "effects": effects } });
|
|
@@ -1195,12 +1206,8 @@ function setupSubsHotReload() {
|
|
|
1195
1206
|
return { dispose, accept };
|
|
1196
1207
|
}
|
|
1197
1208
|
function HotReloadWrapper({ children }) {
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
return import_react2.default.createElement(import_react2.default.Fragment, { key }, children);
|
|
1201
|
-
} else {
|
|
1202
|
-
return children;
|
|
1203
|
-
}
|
|
1209
|
+
const key = useHotReloadKey();
|
|
1210
|
+
return import_react2.default.createElement(import_react2.default.Fragment, { key }, children);
|
|
1204
1211
|
}
|
|
1205
1212
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1206
1213
|
0 && (module.exports = {
|
|
@@ -1214,6 +1221,7 @@ function HotReloadWrapper({ children }) {
|
|
|
1214
1221
|
clearHotReloadCallbacks,
|
|
1215
1222
|
clearReactions,
|
|
1216
1223
|
clearSubs,
|
|
1224
|
+
current,
|
|
1217
1225
|
debounceAndDispatch,
|
|
1218
1226
|
defaultErrorHandler,
|
|
1219
1227
|
disableTracing,
|
|
@@ -1225,7 +1233,7 @@ function HotReloadWrapper({ children }) {
|
|
|
1225
1233
|
getHandler,
|
|
1226
1234
|
getSubscriptionValue,
|
|
1227
1235
|
initAppDb,
|
|
1228
|
-
|
|
1236
|
+
original,
|
|
1229
1237
|
regCoeffect,
|
|
1230
1238
|
regEffect,
|
|
1231
1239
|
regEvent,
|
|
@@ -1234,7 +1242,6 @@ function HotReloadWrapper({ children }) {
|
|
|
1234
1242
|
regSub,
|
|
1235
1243
|
registerHotReloadCallback,
|
|
1236
1244
|
registerTraceCb,
|
|
1237
|
-
setDebugEnabled,
|
|
1238
1245
|
setupSubsHotReload,
|
|
1239
1246
|
throttleAndDispatch,
|
|
1240
1247
|
triggerHotReload,
|
package/dist/index.d.cts
CHANGED
|
@@ -42,6 +42,23 @@ interface Interceptor<T = Record<string, any>> {
|
|
|
42
42
|
declare function initAppDb<T = Record<string, any>>(value: Db<T>): void;
|
|
43
43
|
declare function getAppDb<T = Record<string, any>>(): Db<T>;
|
|
44
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Safe versions of immer's original and current functions
|
|
47
|
+
* These check if the value is actually a draft before calling the immer functions
|
|
48
|
+
*/
|
|
49
|
+
/**
|
|
50
|
+
* Safe version of immer's original function
|
|
51
|
+
* Returns the original (frozen) version of a draft if the value is a draft,
|
|
52
|
+
* otherwise returns the value as-is
|
|
53
|
+
*/
|
|
54
|
+
declare function original<T>(value: T): T;
|
|
55
|
+
/**
|
|
56
|
+
* Safe version of immer's current function
|
|
57
|
+
* Returns the current draft state as a plain object if the value is a draft,
|
|
58
|
+
* otherwise returns the value as-is
|
|
59
|
+
*/
|
|
60
|
+
declare function current<T>(value: T): T;
|
|
61
|
+
|
|
45
62
|
/** Register an event handler with only a handler function (db event) */
|
|
46
63
|
declare function regEvent<T = Record<string, any>>(id: Id, handler: EventHandler<T>): void;
|
|
47
64
|
/** Register an event handler with interceptors and handler function (backward compatibility) */
|
|
@@ -105,14 +122,6 @@ declare function getGlobalInterceptors(): Interceptor[];
|
|
|
105
122
|
*/
|
|
106
123
|
declare function clearGlobalInterceptors(): void;
|
|
107
124
|
declare function clearGlobalInterceptors(id: string): void;
|
|
108
|
-
/**
|
|
109
|
-
* Enable or disable debug mode
|
|
110
|
-
*/
|
|
111
|
-
declare function setDebugEnabled(enabled: boolean): void;
|
|
112
|
-
/**
|
|
113
|
-
* Check if debug mode is enabled
|
|
114
|
-
*/
|
|
115
|
-
declare function isDebugEnabled(): boolean;
|
|
116
125
|
|
|
117
126
|
type Kind = 'event' | 'fx' | 'cofx' | 'sub' | 'subDeps' | 'error';
|
|
118
127
|
type RegistryHandler = EventHandler | EffectHandler | CoEffectHandler | ErrorHandler | SubHandler | SubDepsHandler;
|
|
@@ -183,9 +192,9 @@ declare function setupSubsHotReload(): {
|
|
|
183
192
|
*/
|
|
184
193
|
declare function HotReloadWrapper({ children }: {
|
|
185
194
|
children: React.ReactNode;
|
|
186
|
-
}):
|
|
195
|
+
}): React.FunctionComponentElement<{
|
|
187
196
|
children?: React.ReactNode | undefined;
|
|
188
|
-
}
|
|
197
|
+
}>;
|
|
189
198
|
|
|
190
199
|
type TraceID = number;
|
|
191
200
|
interface TraceOpts {
|
|
@@ -206,4 +215,4 @@ declare function disableTracing(): void;
|
|
|
206
215
|
declare function registerTraceCb(key: string, cb: TraceCallback): void;
|
|
207
216
|
declare function enableTracePrint(): void;
|
|
208
217
|
|
|
209
|
-
export { CoEffectHandler, CoEffects, Context, DISPATCH, DISPATCH_LATER, Db, DispatchLaterEffect, EffectHandler, Effects, ErrorHandler, EventHandler, EventVector, HotReloadWrapper, Id, Interceptor, NOW, RANDOM, SubVector, clearGlobalInterceptors, clearHandlers, clearHotReloadCallbacks, clearReactions, clearSubs, debounceAndDispatch, defaultErrorHandler, disableTracing, dispatch, enableTracePrint, enableTracing, getAppDb, getGlobalInterceptors, getHandler, getSubscriptionValue, initAppDb,
|
|
218
|
+
export { CoEffectHandler, CoEffects, Context, DISPATCH, DISPATCH_LATER, Db, DispatchLaterEffect, EffectHandler, Effects, ErrorHandler, EventHandler, EventVector, HotReloadWrapper, Id, Interceptor, NOW, RANDOM, SubVector, clearGlobalInterceptors, clearHandlers, clearHotReloadCallbacks, clearReactions, clearSubs, current, debounceAndDispatch, defaultErrorHandler, disableTracing, dispatch, enableTracePrint, enableTracing, getAppDb, getGlobalInterceptors, getHandler, getSubscriptionValue, initAppDb, original, regCoeffect, regEffect, regEvent, regEventErrorHandler, regGlobalInterceptor, regSub, registerHotReloadCallback, registerTraceCb, setupSubsHotReload, throttleAndDispatch, triggerHotReload, useHotReload, useHotReloadKey, useSubscription };
|
package/dist/index.d.ts
CHANGED
|
@@ -42,6 +42,23 @@ interface Interceptor<T = Record<string, any>> {
|
|
|
42
42
|
declare function initAppDb<T = Record<string, any>>(value: Db<T>): void;
|
|
43
43
|
declare function getAppDb<T = Record<string, any>>(): Db<T>;
|
|
44
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Safe versions of immer's original and current functions
|
|
47
|
+
* These check if the value is actually a draft before calling the immer functions
|
|
48
|
+
*/
|
|
49
|
+
/**
|
|
50
|
+
* Safe version of immer's original function
|
|
51
|
+
* Returns the original (frozen) version of a draft if the value is a draft,
|
|
52
|
+
* otherwise returns the value as-is
|
|
53
|
+
*/
|
|
54
|
+
declare function original<T>(value: T): T;
|
|
55
|
+
/**
|
|
56
|
+
* Safe version of immer's current function
|
|
57
|
+
* Returns the current draft state as a plain object if the value is a draft,
|
|
58
|
+
* otherwise returns the value as-is
|
|
59
|
+
*/
|
|
60
|
+
declare function current<T>(value: T): T;
|
|
61
|
+
|
|
45
62
|
/** Register an event handler with only a handler function (db event) */
|
|
46
63
|
declare function regEvent<T = Record<string, any>>(id: Id, handler: EventHandler<T>): void;
|
|
47
64
|
/** Register an event handler with interceptors and handler function (backward compatibility) */
|
|
@@ -105,14 +122,6 @@ declare function getGlobalInterceptors(): Interceptor[];
|
|
|
105
122
|
*/
|
|
106
123
|
declare function clearGlobalInterceptors(): void;
|
|
107
124
|
declare function clearGlobalInterceptors(id: string): void;
|
|
108
|
-
/**
|
|
109
|
-
* Enable or disable debug mode
|
|
110
|
-
*/
|
|
111
|
-
declare function setDebugEnabled(enabled: boolean): void;
|
|
112
|
-
/**
|
|
113
|
-
* Check if debug mode is enabled
|
|
114
|
-
*/
|
|
115
|
-
declare function isDebugEnabled(): boolean;
|
|
116
125
|
|
|
117
126
|
type Kind = 'event' | 'fx' | 'cofx' | 'sub' | 'subDeps' | 'error';
|
|
118
127
|
type RegistryHandler = EventHandler | EffectHandler | CoEffectHandler | ErrorHandler | SubHandler | SubDepsHandler;
|
|
@@ -183,9 +192,9 @@ declare function setupSubsHotReload(): {
|
|
|
183
192
|
*/
|
|
184
193
|
declare function HotReloadWrapper({ children }: {
|
|
185
194
|
children: React.ReactNode;
|
|
186
|
-
}):
|
|
195
|
+
}): React.FunctionComponentElement<{
|
|
187
196
|
children?: React.ReactNode | undefined;
|
|
188
|
-
}
|
|
197
|
+
}>;
|
|
189
198
|
|
|
190
199
|
type TraceID = number;
|
|
191
200
|
interface TraceOpts {
|
|
@@ -206,4 +215,4 @@ declare function disableTracing(): void;
|
|
|
206
215
|
declare function registerTraceCb(key: string, cb: TraceCallback): void;
|
|
207
216
|
declare function enableTracePrint(): void;
|
|
208
217
|
|
|
209
|
-
export { CoEffectHandler, CoEffects, Context, DISPATCH, DISPATCH_LATER, Db, DispatchLaterEffect, EffectHandler, Effects, ErrorHandler, EventHandler, EventVector, HotReloadWrapper, Id, Interceptor, NOW, RANDOM, SubVector, clearGlobalInterceptors, clearHandlers, clearHotReloadCallbacks, clearReactions, clearSubs, debounceAndDispatch, defaultErrorHandler, disableTracing, dispatch, enableTracePrint, enableTracing, getAppDb, getGlobalInterceptors, getHandler, getSubscriptionValue, initAppDb,
|
|
218
|
+
export { CoEffectHandler, CoEffects, Context, DISPATCH, DISPATCH_LATER, Db, DispatchLaterEffect, EffectHandler, Effects, ErrorHandler, EventHandler, EventVector, HotReloadWrapper, Id, Interceptor, NOW, RANDOM, SubVector, clearGlobalInterceptors, clearHandlers, clearHotReloadCallbacks, clearReactions, clearSubs, current, debounceAndDispatch, defaultErrorHandler, disableTracing, dispatch, enableTracePrint, enableTracing, getAppDb, getGlobalInterceptors, getHandler, getSubscriptionValue, initAppDb, original, regCoeffect, regEffect, regEvent, regEventErrorHandler, regGlobalInterceptor, regSub, registerHotReloadCallback, registerTraceCb, setupSubsHotReload, throttleAndDispatch, triggerHotReload, useHotReload, useHotReloadKey, useSubscription };
|
package/dist/index.mjs
CHANGED
|
@@ -158,6 +158,15 @@ function updateAppDbWithPatches(newDb, patches) {
|
|
|
158
158
|
}
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
+
// src/immer-utils.ts
|
|
162
|
+
import { isDraft, original as immerOriginal, current as immerCurrent } from "immer";
|
|
163
|
+
function original(value) {
|
|
164
|
+
return isDraft(value) ? immerOriginal(value) : value;
|
|
165
|
+
}
|
|
166
|
+
function current(value) {
|
|
167
|
+
return isDraft(value) ? immerCurrent(value) : value;
|
|
168
|
+
}
|
|
169
|
+
|
|
161
170
|
// src/interceptor.ts
|
|
162
171
|
function isInterceptor(m) {
|
|
163
172
|
if (typeof m !== "object" || m === null)
|
|
@@ -495,16 +504,11 @@ import { enablePatches, produceWithPatches } from "immer";
|
|
|
495
504
|
|
|
496
505
|
// src/settings.ts
|
|
497
506
|
var store = {
|
|
498
|
-
globalInterceptors: []
|
|
499
|
-
debugEnabled: true
|
|
500
|
-
// Default to true, can be configured via setDebugEnabled
|
|
507
|
+
globalInterceptors: []
|
|
501
508
|
};
|
|
502
509
|
function replaceGlobalInterceptor(globalInterceptors, interceptor) {
|
|
503
510
|
return globalInterceptors.reduce((ret, existingInterceptor) => {
|
|
504
511
|
if (interceptor.id === existingInterceptor.id) {
|
|
505
|
-
if (store.debugEnabled) {
|
|
506
|
-
consoleLog("warn", "[reflex] replacing duplicate global interceptor id:", interceptor.id);
|
|
507
|
-
}
|
|
508
512
|
return [...ret, interceptor];
|
|
509
513
|
} else {
|
|
510
514
|
return [...ret, existingInterceptor];
|
|
@@ -530,12 +534,6 @@ function clearGlobalInterceptors(id) {
|
|
|
530
534
|
store.globalInterceptors = store.globalInterceptors.filter((interceptor) => interceptor.id !== id);
|
|
531
535
|
}
|
|
532
536
|
}
|
|
533
|
-
function setDebugEnabled(enabled) {
|
|
534
|
-
store.debugEnabled = enabled;
|
|
535
|
-
}
|
|
536
|
-
function isDebugEnabled() {
|
|
537
|
-
return store.debugEnabled;
|
|
538
|
-
}
|
|
539
537
|
|
|
540
538
|
// src/trace.ts
|
|
541
539
|
var nextId = 1;
|
|
@@ -636,6 +634,13 @@ function enableTracePrint() {
|
|
|
636
634
|
});
|
|
637
635
|
}
|
|
638
636
|
|
|
637
|
+
// src/env.ts
|
|
638
|
+
var IS_DEV = (
|
|
639
|
+
// Node.js check
|
|
640
|
+
typeof process !== "undefined" && process.env?.NODE_ENV === "development" || // React Native / bundler check
|
|
641
|
+
typeof __DEV__ !== "undefined" && __DEV__
|
|
642
|
+
);
|
|
643
|
+
|
|
639
644
|
// src/events.ts
|
|
640
645
|
var KIND3 = "event";
|
|
641
646
|
function regEvent(id, handler, cofxOrInterceptors, interceptors) {
|
|
@@ -689,17 +694,23 @@ function eventHandlerInterceptor(handler) {
|
|
|
689
694
|
return {
|
|
690
695
|
id: "fx-handler",
|
|
691
696
|
before(context) {
|
|
692
|
-
const
|
|
693
|
-
const event = coeffects.event;
|
|
697
|
+
const event = context.coeffects.event;
|
|
694
698
|
const params = event.slice(1);
|
|
695
699
|
let effects = [];
|
|
696
700
|
const [newDb, patches] = produceWithPatches(
|
|
697
701
|
getAppDb(),
|
|
698
702
|
(draftDb) => {
|
|
699
|
-
|
|
700
|
-
effects = handler(
|
|
703
|
+
const coeffectsWithDb = { ...context.coeffects, draftDb };
|
|
704
|
+
effects = handler(coeffectsWithDb, ...params) || [];
|
|
701
705
|
}
|
|
702
706
|
);
|
|
707
|
+
if (IS_DEV) {
|
|
708
|
+
try {
|
|
709
|
+
JSON.stringify(effects);
|
|
710
|
+
} catch (e) {
|
|
711
|
+
consoleLog("warn", `[reflex] Effects ${effects} contain Proxy (probably an Immer draft). Use current() for draftDb values.`);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
703
714
|
context.newDb = newDb;
|
|
704
715
|
context.patches = patches;
|
|
705
716
|
mergeTrace({ tags: { "patches": patches, "effects": effects } });
|
|
@@ -1123,12 +1134,8 @@ function setupSubsHotReload() {
|
|
|
1123
1134
|
return { dispose, accept };
|
|
1124
1135
|
}
|
|
1125
1136
|
function HotReloadWrapper({ children }) {
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
return React.createElement(React.Fragment, { key }, children);
|
|
1129
|
-
} else {
|
|
1130
|
-
return children;
|
|
1131
|
-
}
|
|
1137
|
+
const key = useHotReloadKey();
|
|
1138
|
+
return React.createElement(React.Fragment, { key }, children);
|
|
1132
1139
|
}
|
|
1133
1140
|
export {
|
|
1134
1141
|
DISPATCH,
|
|
@@ -1141,6 +1148,7 @@ export {
|
|
|
1141
1148
|
clearHotReloadCallbacks,
|
|
1142
1149
|
clearReactions,
|
|
1143
1150
|
clearSubs,
|
|
1151
|
+
current,
|
|
1144
1152
|
debounceAndDispatch,
|
|
1145
1153
|
defaultErrorHandler,
|
|
1146
1154
|
disableTracing,
|
|
@@ -1152,7 +1160,7 @@ export {
|
|
|
1152
1160
|
getHandler,
|
|
1153
1161
|
getSubscriptionValue,
|
|
1154
1162
|
initAppDb,
|
|
1155
|
-
|
|
1163
|
+
original,
|
|
1156
1164
|
regCoeffect,
|
|
1157
1165
|
regEffect,
|
|
1158
1166
|
regEvent,
|
|
@@ -1161,7 +1169,6 @@ export {
|
|
|
1161
1169
|
regSub,
|
|
1162
1170
|
registerHotReloadCallback,
|
|
1163
1171
|
registerTraceCb,
|
|
1164
|
-
setDebugEnabled,
|
|
1165
1172
|
setupSubsHotReload,
|
|
1166
1173
|
throttleAndDispatch,
|
|
1167
1174
|
triggerHotReload,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flexsurfer/reflex",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.14",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -30,7 +30,9 @@
|
|
|
30
30
|
"dev": "tsup --watch",
|
|
31
31
|
"test": "jest --passWithNoTests",
|
|
32
32
|
"test:clean": "jest --passWithNoTests --silent",
|
|
33
|
-
"test:watch": "jest --passWithNoTests --watch"
|
|
33
|
+
"test:watch": "jest --passWithNoTests --watch",
|
|
34
|
+
"test:ci": "jest --passWithNoTests --coverage --silent",
|
|
35
|
+
"prepublishOnly": "npm run test:ci && npm run build"
|
|
34
36
|
},
|
|
35
37
|
"devDependencies": {
|
|
36
38
|
"@testing-library/dom": "^10.4.0",
|