• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mobalazs / rotor-framework / 28603494815

02 Jul 2026 03:53PM UTC coverage: 90.958% (+0.8%) from 90.158%
28603494815

push

github

web-flow
Feat/combine reducers (#26)

58 of 67 new or added lines in 8 files covered. (86.57%)

23 existing lines in 2 files now uncovered.

2213 of 2433 relevant lines covered (90.96%)

1.26 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.59
/src/source/base/BaseReducer.bs
1
namespace Rotor
2

3
    ' =====================================================================
4
    ' Reducer (BaseReducer) - Base class for state reducers in the MVI pattern
5
    '
6
    ' Responsibilities:
7
    '   - Computes next state from current state + intent
8
    '   - Applies middleware pipeline for async operations, logging, validation
9
    '   - Provides pure state transformation logic
10
    '   - Integrates with Dispatcher for state updates
11
    '
12
    ' MVI Flow:
13
    '   Intent → Middleware → Reducer → New State → Model → View Update
14
    '
15
    ' Override Points:
16
    '   - reducer(state, intent): Define state transitions
17
    '   - applyMiddlewares(): Return array of middleware functions
18
    '   - middlewares: Set middleware array directly
19
    ' =====================================================================
20
    class Reducer
21

22
        ' =============================================================
23
        ' MEMBER VARIABLES
24
        ' =============================================================
25

26
        middlewares = [] as function[]  ' Middleware function array
27
        port as object                  ' Framework port for async operations
28

29
        ' Dispatcher integration
30
        connectDispatcher as function   ' Connect to dispatcher facade by ID
31
        getState as function            ' Get dispatcher's model state
32
        dispatch as function            ' Dispatch intent to owner dispatcher
33
        dispatchTo as function          ' Dispatch intent to dispatcher by ID
34
        getStateFrom as function        ' Get state from dispatcher by ID
35
        dispatcher as object            ' Owning dispatcher instance
36
        dispatcherId as string          ' Owning dispatcher ID
37

38
        ' Internal
39
        middlewareFnScoped as dynamic   ' Currently executing middleware
40
        rootState as object             ' Read-only combined state during reduceSlice (combined dispatcher only)
41
        
42
        ' =============================================================
43
        ' CONSTRUCTOR
44
        ' =============================================================
45

46
        ' ---------------------------------------------------------------------
47
        ' Constructor - Initializes reducer with framework integration
48
        '
49
        sub new()
50
            frameworkInstance = GetGlobalAA().rotor_framework_helper.frameworkInstance
1✔
51
            m.port = frameworkInstance.port
1✔
52

53
            ' Inject connectDispatcher method
54
            m.connectDispatcher = function(dispatcherId as string) as object
1✔
55
                return GetGlobalAA().rotor_framework_helper.frameworkInstance.dispatcherProvider.getFacade(dispatcherId, m)
56
            end function
57

58
            ' Inject dispatch method for dispatching intents
59
            m.dispatch = sub(intent as object)
1✔
60
                m.dispatcher.dispatch(intent)
61
            end sub
62

63
            ' Inject getState method for accessing current state
64
            m.getState = function() as Rotor.Model
1✔
65
                return m.dispatcher.getState()
66
            end function
67

68
            ' Inject dispatchTo method for dispatching intents to other dispatchers
69
            m.dispatchTo = sub(dispatcherId as string, dispatchObject as object)
1✔
70
                dispatcherFacade = m.connectDispatcher(dispatcherId)
71
                dispatcherFacade.dispatch(dispatchObject)
72
            end sub
73

74
            ' Inject getStateFrom method for accessing state from other dispatchers
75
            m.getStateFrom = function(dispatcherId as string, mapStateToProps = invalid as dynamic)
1✔
76
                dispatcherFacade = m.connectDispatcher(dispatcherId)
77
                return dispatcherFacade.getState(mapStateToProps)
78
            end function
79
        end sub
80

81
        ' =============================================================
82
        ' REDUCER LOGIC
83
        ' =============================================================
84

85
        ' ---------------------------------------------------------------------
86
        ' reducer - Pure function that computes next state
87
        '
88
        ' Override this method to implement custom state transitions based on intent type.
89
        ' Should return a new state object (immutable pattern).
90
        '
91
        ' @param {object} state - Current state
92
        ' @param {Intent} intent - Intent object { type, payload, ... }
93
        ' @return {object} New state
94
        '
95
        ' Example:
96
        '   function reducer(state as object, intent as Intent)
97
        '       if intent.type = "INCREMENT"
98
        '           newState = Rotor.Utils.deepCopy(state)
99
        '           newState.count = state.count + 1
100
        '           return newState
101
        '       end if
102
        '       return state
103
        '   end function
104
        '
105
        public function reducer(state as object, intent as Intent)
106
            return state
1✔
107
        end function
108

109
        ' ---------------------------------------------------------------------
110
        ' onCreateDispatcher - Lifecycle hook called after dispatcher is fully registered
111
        '
112
        ' Called by the framework after the dispatcher is created and registered
113
        ' with the DispatcherProvider. At this point, dispatcherId and all
114
        ' framework integrations are available.
115
        '
116
        ' Override this method to perform initialization that requires
117
        ' the dispatcher to be fully set up (e.g., source object registration).
118
        '
119
        sub onCreateDispatcher()
120
        end sub
121

122
        ' =============================================================
123
        ' MIDDLEWARE
124
        ' =============================================================
125

126
        ' ---------------------------------------------------------------------
127
        ' applyMiddlewares - Returns middleware function array
128
        '
129
        ' Override to implement middleware logic for:
130
        '   - Async operations (API calls, timers)
131
        '   - Logging and debugging
132
        '   - Intent validation
133
        '   - Intent transformation
134
        '
135
        ' @return {function[]} Array of middleware functions
136
        '
137
        ' Middleware signature:
138
        '   function(intent as Intent, state as object) as Dynamic
139
        '   - Return intent to continue pipeline
140
        '   - Return modified intent to transform
141
        '   - Return invalid to cancel reducer execution
142
        '
143
        public function applyMiddlewares() as function[]
144
            return [] as function[]
1✔
145
        end function
146

147
        ' =============================================================
148
        ' REDUCE PIPELINE
149
        ' =============================================================
150

151
        ' ---------------------------------------------------------------------
152
        ' reduce - Executes middleware pipeline and reducer
153
        '
154
        ' Process flow:
155
        '   1. Validate intent payload
156
        '   2. Execute middleware pipeline
157
        '   3. If intent survives, execute reducer
158
        '   4. Return new state or invalid
159
        '
160
        ' @param {object} state - Current state
161
        ' @param {Intent} intent - Intent to process
162
        ' @return {object} New state, or invalid if cancelled
163
        '
164
        function reduce(state as object, intent as Intent) as object
165
            if intent?.payload <> invalid and intent.payload.Count() > 1 and intent.payload = invalid
2✔
166
                throw "[WARNING] Intent payload is invalid."
167
            end if
168

169
            ' Execute middleware pipeline
170
            intent = m.runMiddlewares(intent, state)
1✔
171

172
            ' Middleware cancelled intent
173
            if intent = invalid then return invalid
2✔
174

175
            ' Execute reducer
176
            newState = m.reducer(state, intent)
1✔
177

178
            return newState
1✔
179
        end function
180

181
        ' ---------------------------------------------------------------------
182
        ' runMiddlewares - Executes the middleware pipeline for a given intent
183
        '
184
        ' Shared by reduce() and reduceSlice(). Returns the (possibly transformed)
185
        ' intent, or invalid if a middleware cancelled it.
186
        '
187
        ' @param {Intent} intent - Intent to process
188
        ' @param {object} state - State passed to each middleware
189
        ' @return {dynamic} Processed intent, or invalid if cancelled
190
        '
191
        protected function runMiddlewares(intent as Intent, state as object) as dynamic
192
            middlewares = m.applyMiddlewares()
1✔
193
            mwIndex = 0
1✔
194
            mwCount = middlewares.Count()
1✔
195
            while intent <> invalid and mwIndex < mwCount
1✔
196
                m.middlewareFnScoped = middlewares[mwIndex]
1✔
197
                intent = m.middlewareFnScoped(intent, state)
1✔
198
                mwIndex++
1✔
199
            end while
200
            m.middlewareFnScoped = invalid
1✔
201
            return intent
1✔
202
        end function
203

204
        ' ---------------------------------------------------------------------
205
        ' reduceSlice - Reduces a single slice within a combined dispatcher
206
        '
207
        ' Called by Rotor.CombinedReducer for each named slice. Runs this reducer's
208
        ' own middleware + reducer against the slice state. The full combined state
209
        ' is exposed read-only via m.rootState for the duration, so a slice can
210
        ' DERIVE from other slices (read), while only ever WRITING its own slice.
211
        ' A middleware cancel leaves the slice unchanged.
212
        '
213
        ' @param {object} sliceState - This slice's current state
214
        ' @param {Intent} intent - Intent to process
215
        ' @param {object} rootState - Full combined state (read-only)
216
        ' @return {object} New slice state (same reference if unchanged)
217
        '
218
        function reduceSlice(sliceState as object, intent as Intent, rootState as object) as object
219
            m.rootState = rootState
1✔
220
            processedIntent = m.runMiddlewares(intent, sliceState)
1✔
221
            if processedIntent = invalid
2✔
NEW
222
                m.rootState = invalid
×
NEW
223
                return sliceState
×
224
            end if
225
            newSlice = m.reducer(sliceState, processedIntent)
1✔
226
            m.rootState = invalid
1✔
227
            return newSlice
1✔
228
        end function
229

230
        ' =============================================================
231
        ' SOURCE OBJECT SUPPORT
232
        ' =============================================================
233

234
        ' ---------------------------------------------------------------------
235
        ' registerSourceObject - Creates and registers a source object with the framework
236
        '
237
        ' Creates a Roku source object by type name. The framework manages object
238
        ' creation, message port assignment, and event routing.
239
        ' - Identity-based types (roUrlTransfer, roChannelStore): new instance per call
240
        ' - Broadcast types (roDeviceInfo, roInput): singleton, shared across dispatchers
241
        '
242
        ' @param {string} typeName - Roku object type (e.g. "roUrlTransfer", "roDeviceInfo")
243
        ' @param {function} eventFilter - Optional filter function. Receives msg, returns boolean.
244
        ' @returns {object} The created (or shared) source object
245
        '
246
        function registerSourceObject(typeName as string, eventFilter = invalid as dynamic) as object
247
            frameworkInstance = GetGlobalAA().rotor_framework_helper.frameworkInstance
1✔
248
            return frameworkInstance.registerSourceObject(typeName, m.dispatcherId, eventFilter)
1✔
249
        end function
250

251
        ' ---------------------------------------------------------------------
252
        ' unregisterSourceObject - Unregisters a source object from the framework
253
        '
254
        ' @param {object} sourceObject - The source object to unregister
255
        '
256
        sub unregisterSourceObject(sourceObject as object)
257
            frameworkInstance = GetGlobalAA().rotor_framework_helper.frameworkInstance
1✔
258
            frameworkInstance.unregisterSourceObject(sourceObject, m.dispatcherId)
1✔
259
        end sub
260

261
        ' ---------------------------------------------------------------------
262
        ' onSourceEvent - Callback for source object events
263
        '
264
        ' @param {object} msg - Raw message from source object (roUrlEvent, roDeviceInfoEvent, etc.)
265
        '
266
        sub onSourceEvent(msg as object)
267
            ' Override in subclass to handle async responses
268
        end sub
269

270
        ' =============================================================
271
        ' CLEANUP
272
        ' =============================================================
273

274
        ' ---------------------------------------------------------------------
275
        ' destroy - Cleans up reducer references
276
        '
277
        sub destroy()
278
            m.dispatcher = invalid
1✔
279
            m.port = invalid
1✔
280
        end sub
281

282
    end class
283

284
end namespace
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc