mobx-vue-bridge 1.3.0 → 1.5.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/CHANGELOG.md CHANGED
@@ -5,6 +5,104 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.5.0] - 2026-01-13
9
+
10
+ ### 🎯 Major Improvements
11
+
12
+ #### Declarative helper functions for improved readability
13
+ - **Feature**: Extracted bridge logic into self-documenting helper functions in `src/utils/helpers.js`
14
+ - **Structure**: 396 lines of declarative helpers organized by concern:
15
+ - Value reading & initialization
16
+ - Property type categorization
17
+ - Echo loop prevention
18
+ - Read-only detection
19
+ - Setter creation (lazy validated, read-only, write-only, two-way binding)
20
+ - MobX observation helpers
21
+ - **Benefit**: Code now reads like documentation with function names like `guardAgainstEchoLoop`, `createLazyValidatedSetter`, `safelyDisposeSubscription`
22
+
23
+ #### Improved circular reference handling in equality checks
24
+ - **Feature**: `isEqual()` now uses `Map` instead of `WeakSet` to track visited object pairs
25
+ - **Benefit**: Correctly compares objects where one has circular refs and the other doesn't
26
+ - **Previous bug**: `isEqual(circularObj, nonCircularObj)` incorrectly returned `true`
27
+ - **Fix**: Now tracks `(a, b)` pairs so revisiting `a` checks if it was paired with same `b`
28
+
29
+ ### 🐛 Bug Fixes
30
+
31
+ #### Fixed deepObserve stale subscription after property reassignment
32
+ - **Issue**: When a property was reassigned to a new object/array, `deepObserve` continued watching the old value
33
+ - **Example**: After `store.items = newArray`, mutations to `newArray` weren't detected
34
+ - **Fix**: Added `onValueChanged` callback to `observeProperty` that re-subscribes `deepObserve` when property value changes
35
+ - **Result**: Nested mutations are now correctly detected even after complete property reassignment
36
+
37
+ #### Fixed -0 vs +0 edge case in equality comparison
38
+ - **Issue**: `Object.is(-0, +0)` returns `false`, causing unnecessary updates for equivalent values
39
+ - **Fix**: Added explicit number handling that treats `-0` and `+0` as equal while correctly handling `NaN === NaN`
40
+
41
+ #### Improved warning messages
42
+ - **Change**: Direct mutation warnings now include actionable guidance
43
+ - **Before**: `"Direct mutation of 'name' is disabled"`
44
+ - **After**: `"Direct mutation of 'name' is disabled. Use actions instead."`
45
+
46
+ ### ✅ Testing
47
+
48
+ - **New test file**: `mobxVueBridgeBugVerification.test.js` - 10 tests verifying bug fixes
49
+ - Circular reference equality edge cases
50
+ - DeepObserve re-subscription after reassignment
51
+ - -0/+0 and NaN handling
52
+ - Single clone correctness
53
+ - Sibling nested array mutations
54
+ - Setter-only properties
55
+ - **New test file**: `mobxVueBridgeCrossClassComputed.test.js` - 3 tests for cross-class dependencies
56
+ - Computed properties depending on other class's computed properties
57
+ - Chain of three classes with computed dependencies
58
+ - Changes detected even when only bridging the dependent class
59
+
60
+ ## [1.4.0] - 2025-11-25
61
+
62
+ ### 🎯 Major Improvements
63
+
64
+ #### Zero-side-effect initialization with lazy detection
65
+ - **Feature**: Bridge no longer calls any setters during initialization, eliminating all side effects
66
+ - **Benefit**: Fixes production bugs where setter side effects (like `refreshDataOnTabChange()`) were triggered during bridge setup
67
+ - **Mechanism**: Lazy detection pattern - properties are optimistically marked as writable during init, tested on first actual write attempt
68
+ - **Caching**: `readOnlyDetected` Set caches detection results for O(1) lookups on subsequent writes
69
+ - **Impact**: User's `currentRoom` setter with side effects now works correctly - side effects only occur during actual user interactions
70
+
71
+ #### Modular architecture refactoring
72
+ - **Feature**: Core logic separated into focused utility modules
73
+ - **Structure**:
74
+ - `src/utils/memberDetection.js` (210 lines) - MobX member categorization without side effects
75
+ - `src/utils/equality.js` (47 lines) - Deep equality with circular reference protection
76
+ - `src/utils/deepProxy.js` (109 lines) - Nested reactivity with microtask batching
77
+ - **Main bridge**: Reduced from 523 lines → 321 lines (39% reduction)
78
+ - **Benefit**: Better maintainability, testability, and code organization
79
+
80
+ ### 🐛 Bug Fixes
81
+
82
+ #### Fixed computed property detection with MobX synthetic setters
83
+ - **Issue**: MobX adds synthetic setters to computed-only properties that throw "not possible to assign" errors
84
+ - **Previous approach**: Tested setters during initialization (caused side effects)
85
+ - **New approach**: Check descriptor existence only (`descriptor.get && descriptor.set`), test lazily on first write
86
+ - **Detection**: Catch MobX error message during first write attempt, cache as read-only
87
+ - **Result**: Accurate detection without initialization side effects
88
+
89
+ #### Fixed test equality assertions
90
+ - **Issue**: One test expected reference equality for cloned objects
91
+ - **Fix**: Changed `.toBe()` to `.toStrictEqual()` for deep equality comparison
92
+ - **Context**: Bridge clones objects to prevent reference sharing between Vue and MobX
93
+
94
+ ### ✅ Testing
95
+
96
+ - **Total tests**: 170 passing (was 168 + 2 skipped)
97
+ - **New active tests**: Unskipped and fixed 2 comprehensive two-way binding demo tests
98
+ - **Coverage**: All patterns verified including lazy detection, nested mutations, and error handling
99
+
100
+ ### 📚 Documentation
101
+
102
+ - **Architecture notes**: Added inline documentation explaining lazy detection pattern
103
+ - **Comments**: Clear explanation of why setters aren't called during init
104
+ - **Examples**: Test files demonstrate proper usage patterns
105
+
8
106
  ## [1.2.0] - 2025-10-01
9
107
 
10
108
  ### ✨ New Features
package/README.md CHANGED
@@ -14,6 +14,8 @@ A seamless bridge between MobX observables and Vue 3's reactivity system, enabli
14
14
  - 🔒 **Type-safe bridging** between reactive systems
15
15
  - 🚀 **Optimized performance** with intelligent change detection
16
16
  - 🛡️ **Error handling** for edge cases and circular references
17
+ - ⚡ **Zero-side-effect initialization** with lazy detection (v1.4.0+)
18
+ - 📦 **Modular architecture** for better maintainability
17
19
 
18
20
  ## 📦 Installation
19
21
 
@@ -296,6 +298,51 @@ presenter.items.push(newItem)
296
298
  console.log(presenter.items) // Immediately updated!
297
299
  ```
298
300
 
301
+ ## 🏗️ Architecture & Implementation
302
+
303
+ ### Modular Design (v1.4.0+)
304
+
305
+ The bridge uses a clean, modular architecture for better maintainability:
306
+
307
+ ```
308
+ src/
309
+ ├── mobxVueBridge.js # Main bridge (321 lines)
310
+ └── utils/
311
+ ├── memberDetection.js # MobX property categorization (210 lines)
312
+ ├── equality.js # Deep equality with circular protection (47 lines)
313
+ └── deepProxy.js # Nested reactivity with batching (109 lines)
314
+ ```
315
+
316
+ ### Zero-Side-Effect Initialization
317
+
318
+ The bridge uses **lazy detection** to avoid calling setters during initialization:
319
+
320
+ ```javascript
321
+ class GuestPresenter {
322
+ get currentRoom() {
323
+ return this.repository.currentRoomId
324
+ }
325
+
326
+ set currentRoom(val) {
327
+ this.repository.currentRoomId = val
328
+ this.refreshDataOnTabChange() // Side effect!
329
+ }
330
+ }
331
+
332
+ // ✅ v1.4.0+: No side effects during bridge creation
333
+ const state = useMobxBridge(presenter) // refreshDataOnTabChange() NOT called
334
+
335
+ // ✅ Side effects only happen during actual mutations
336
+ state.currentRoom = 'room-123' // refreshDataOnTabChange() called here
337
+ ```
338
+
339
+ **How it works:**
340
+ 1. **Detection phase**: Checks descriptor existence only (`descriptor.get && descriptor.set`)
341
+ 2. **First write**: Tests if setter actually works by attempting the write
342
+ 3. **Caching**: Results stored in `readOnlyDetected` Set for O(1) future lookups
343
+
344
+ This prevents bugs where setter side effects were triggered during bridge setup, while maintaining accurate runtime behavior.
345
+
299
346
  ### Error Handling
300
347
  The bridge gracefully handles edge cases:
301
348
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mobx-vue-bridge",
3
- "version": "1.3.0",
3
+ "version": "1.5.0",
4
4
  "description": "A lightweight bridge for seamless two-way data binding between MobX observables and Vue 3 reactivity",
5
5
  "main": "src/mobxVueBridge.js",
6
6
  "module": "src/mobxVueBridge.js",