@flexsurfer/reflex 0.1.13 โ†’ 0.1.15

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,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
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
10
10
  [![NPM Version](https://img.shields.io/npm/v/%40flexsurfer%2Freflex)](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
- - Step-by-Step Tutorial - TBD
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
- isDebugEnabled: () => isDebugEnabled,
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 import_immer = require("immer");
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,25 +761,31 @@ function registerInterceptors(id, cofxOrInterceptors, interceptors) {
756
761
  setInterceptors(id, allInterceptors);
757
762
  }
758
763
  }
759
- (0, import_immer.enablePatches)();
764
+ (0, import_immer2.enablePatches)();
760
765
  function eventHandlerInterceptor(handler) {
761
766
  return {
762
767
  id: "fx-handler",
763
768
  before(context) {
764
- const coeffects = context.coeffects;
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, import_immer.produceWithPatches)(
772
+ const [newDb, patches, reversePatches] = (0, import_immer2.produceWithPatches)(
769
773
  getAppDb(),
770
774
  (draftDb) => {
771
- coeffects.draftDb = draftDb;
772
- effects = handler({ ...coeffects }, ...params) || [];
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
- mergeTrace({ tags: { "patches": patches, "effects": effects } });
788
+ mergeTrace({ tags: { "patches": patches, "reversePatches": reversePatches, "effects": effects } });
778
789
  if (!Array.isArray(effects)) {
779
790
  consoleLog("warn", `[reflex] effects expects a vector, but was given ${typeof effects}`);
780
791
  } else {
@@ -1195,12 +1206,8 @@ function setupSubsHotReload() {
1195
1206
  return { dispose, accept };
1196
1207
  }
1197
1208
  function HotReloadWrapper({ children }) {
1198
- if (isDebugEnabled()) {
1199
- const key = useHotReloadKey();
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
- isDebugEnabled,
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
- }): string | number | boolean | React.ReactElement<any, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | React.FunctionComponentElement<{
195
+ }): React.FunctionComponentElement<{
187
196
  children?: React.ReactNode | undefined;
188
- }> | null | undefined;
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, isDebugEnabled, regCoeffect, regEffect, regEvent, regEventErrorHandler, regGlobalInterceptor, regSub, registerHotReloadCallback, registerTraceCb, setDebugEnabled, setupSubsHotReload, throttleAndDispatch, triggerHotReload, useHotReload, useHotReloadKey, useSubscription };
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
- }): string | number | boolean | React.ReactElement<any, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | React.FunctionComponentElement<{
195
+ }): React.FunctionComponentElement<{
187
196
  children?: React.ReactNode | undefined;
188
- }> | null | undefined;
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, isDebugEnabled, regCoeffect, regEffect, regEvent, regEventErrorHandler, regGlobalInterceptor, regSub, registerHotReloadCallback, registerTraceCb, setDebugEnabled, setupSubsHotReload, throttleAndDispatch, triggerHotReload, useHotReload, useHotReloadKey, useSubscription };
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,20 +694,26 @@ function eventHandlerInterceptor(handler) {
689
694
  return {
690
695
  id: "fx-handler",
691
696
  before(context) {
692
- const coeffects = context.coeffects;
693
- const event = coeffects.event;
697
+ const event = context.coeffects.event;
694
698
  const params = event.slice(1);
695
699
  let effects = [];
696
- const [newDb, patches] = produceWithPatches(
700
+ const [newDb, patches, reversePatches] = produceWithPatches(
697
701
  getAppDb(),
698
702
  (draftDb) => {
699
- coeffects.draftDb = draftDb;
700
- effects = handler({ ...coeffects }, ...params) || [];
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
- mergeTrace({ tags: { "patches": patches, "effects": effects } });
716
+ mergeTrace({ tags: { "patches": patches, "reversePatches": reversePatches, "effects": effects } });
706
717
  if (!Array.isArray(effects)) {
707
718
  consoleLog("warn", `[reflex] effects expects a vector, but was given ${typeof effects}`);
708
719
  } else {
@@ -1123,12 +1134,8 @@ function setupSubsHotReload() {
1123
1134
  return { dispose, accept };
1124
1135
  }
1125
1136
  function HotReloadWrapper({ children }) {
1126
- if (isDebugEnabled()) {
1127
- const key = useHotReloadKey();
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
- isDebugEnabled,
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.13",
3
+ "version": "0.1.15",
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",