Skip to content

Commit 6342f62

Browse files
committed
Merge pull request #186 from optimizely/jordan/add-debug-options
Allow all debug options to be flagged on / off as overrides
2 parents 2e8b995 + 45a3131 commit 6342f62

File tree

7 files changed

+223
-69
lines changed

7 files changed

+223
-69
lines changed

docs/src/docs/07-api.md

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,38 @@ var reactor = Nuclear.Reactor(config)
1919

2020
**Configuration Options**
2121

22-
`config.debug` Boolean - if true it will log the entire app state for every dispatch.
22+
`config.debug` Boolean - if true it will enabled logging for dispatches and throw Errors in various circumstances described below.
23+
24+
**config.options** (added in 1.3)
25+
26+
If `config.debug` is true then all of the options below will be enabled.
27+
28+
`logDispatches` (default=`false`) console.logs for every action. If disabled `logAppState` and `logDirtyStores` will be ignored, as no dispatch logging is occurring.
29+
30+
`logAppState` (default=`false`) console.logs a snapshot of the entire app state after every dispatch. Disabling this can improve performance.
31+
32+
`logDirtyStores` (default=`false`) console.logs what stores have changed after each dispatched action.
33+
34+
`throwOnUndefinedDispatch` (default=`false`) if true, throws an Error if a store ever returns undefined.
35+
36+
`throwOnNonImmutableStore` (default=`false`) if true, throws an Error if a store returns a non-immutable value. Javascript primitive such as `String`, `Boolean` and `Number` count as immutable.
37+
38+
`throwOnDispatchInDispatch` (default=`true`) if true, throws an Error if a dispatch occurs in a change observer.
39+
40+
**Example**
41+
42+
```javascript
43+
var reactor = new Nuclear.Reactor({
44+
debug: true,
45+
options: {
46+
// do not log entire app state
47+
logAppState: false,
48+
// allow dispatch in dispatch
49+
throwOnDispatchInDispatch: false,
50+
},
51+
})
52+
```
53+
2354

2455
#### `Reactor#dispatch(messageType, messagePayload)`
2556

src/logging.js

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
1+
import { getOption } from './reactor/fns'
2+
13
/* eslint-disable no-console */
24
/**
35
* Wraps a Reactor.react invocation in a console.group
6+
* @param {ReactorState} reactorState
7+
* @param {String} type
8+
* @param {*} payload
49
*/
5-
exports.dispatchStart = function(type, payload) {
10+
exports.dispatchStart = function(reactorState, type, payload) {
11+
if (!getOption(reactorState, 'logDispatches')) {
12+
return
13+
}
14+
615
if (console.group) {
716
console.groupCollapsed('Dispatch: %s', type)
817
console.group('payload')
@@ -11,24 +20,30 @@ exports.dispatchStart = function(type, payload) {
1120
}
1221
}
1322

14-
exports.dispatchError = function(error) {
23+
exports.dispatchError = function(reactorState, error) {
24+
if (!getOption(reactorState, 'logDispatches')) {
25+
return
26+
}
27+
1528
if (console.group) {
1629
console.debug('Dispatch error: ' + error)
1730
console.groupEnd()
1831
}
1932
}
2033

21-
exports.storeHandled = function(id, before, after) {
22-
if (console.group) {
23-
if (before !== after) {
24-
console.debug('Store ' + id + ' handled action')
25-
}
34+
exports.dispatchEnd = function(reactorState, state, dirtyStores) {
35+
if (!getOption(reactorState, 'logDispatches')) {
36+
return
2637
}
27-
}
2838

29-
exports.dispatchEnd = function(state) {
3039
if (console.group) {
31-
console.debug('Dispatch done, new state: ', state.toJS())
40+
if (getOption(reactorState, 'logDirtyStores')) {
41+
console.log('Stores updated:', dirtyStores.toList().toJS())
42+
}
43+
44+
if (getOption(reactorState, 'logAppState')) {
45+
console.debug('Dispatch done, new state: ', state.toJS())
46+
}
3247
console.groupEnd()
3348
}
3449
}

src/reactor.js

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
import Immutable from 'immutable'
22
import createReactMixin from './create-react-mixin'
3-
import fns from './reactor/fns'
3+
import * as fns from './reactor/fns'
44
import { isKeyPath } from './key-path'
55
import { isGetter } from './getter'
66
import { toJS } from './immutable-helpers'
77
import { toFactory } from './utils'
8-
import { ReactorState, ObserverState } from './reactor/records'
8+
import {
9+
ReactorState,
10+
ObserverState,
11+
DEBUG_OPTIONS,
12+
PROD_OPTIONS,
13+
} from './reactor/records'
914

1015
/**
1116
* State is stored in NuclearJS Reactors. Reactors
@@ -18,8 +23,12 @@ import { ReactorState, ObserverState } from './reactor/records'
1823
*/
1924
class Reactor {
2025
constructor(config = {}) {
26+
const debug = !!config.debug
27+
const baseOptions = debug ? DEBUG_OPTIONS : PROD_OPTIONS
2128
const initialReactorState = new ReactorState({
22-
debug: config.debug,
29+
debug: debug,
30+
// merge config options with the defaults
31+
options: baseOptions.merge(config.options || {}),
2332
})
2433

2534
this.prevReactorState = initialReactorState
@@ -101,9 +110,11 @@ class Reactor {
101110
*/
102111
dispatch(actionType, payload) {
103112
if (this.__batchDepth === 0) {
104-
if (this.__isDispatching) {
105-
this.__isDispatching = false
106-
throw new Error('Dispatch may not be called while a dispatch is in progress')
113+
if (fns.getOption(this.reactorState, 'throwOnDispatchInDispatch')) {
114+
if (this.__isDispatching) {
115+
this.__isDispatching = false
116+
throw new Error('Dispatch may not be called while a dispatch is in progress')
117+
}
107118
}
108119
this.__isDispatching = true
109120
}

src/reactor/fns.js

Lines changed: 34 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,7 @@ function evaluateResult(result, reactorState) {
2323
* @param {Object<String, Store>} stores
2424
* @return {ReactorState}
2525
*/
26-
exports.registerStores = function(reactorState, stores) {
27-
const debug = reactorState.get('debug')
28-
26+
export function registerStores(reactorState, stores) {
2927
return reactorState.withMutations((reactorState) => {
3028
each(stores, (store, id) => {
3129
if (reactorState.getIn(['stores', id])) {
@@ -36,7 +34,7 @@ exports.registerStores = function(reactorState, stores) {
3634

3735
const initialState = store.getInitialState()
3836

39-
if (debug && !isImmutableValue(initialState)) {
37+
if (getOption(reactorState, 'throwOnNonImmutableStore') && !isImmutableValue(initialState)) {
4038
throw new Error('Store getInitialState() must return an immutable value, did you forget to call toImmutable')
4139
}
4240

@@ -56,15 +54,12 @@ exports.registerStores = function(reactorState, stores) {
5654
* @param {*} payload
5755
* @return {ReactorState}
5856
*/
59-
exports.dispatch = function(reactorState, actionType, payload) {
57+
export function dispatch(reactorState, actionType, payload) {
6058
const currState = reactorState.get('state')
61-
const debug = reactorState.get('debug')
6259
let dirtyStores = reactorState.get('dirtyStores')
6360

6461
const nextState = currState.withMutations(state => {
65-
if (debug) {
66-
logging.dispatchStart(actionType, payload)
67-
}
62+
logging.dispatchStart(reactorState, actionType, payload)
6863

6964
// let each store handle the message
7065
reactorState.get('stores').forEach((store, id) => {
@@ -75,13 +70,13 @@ exports.dispatch = function(reactorState, actionType, payload) {
7570
newState = store.handle(currState, actionType, payload)
7671
} catch(e) {
7772
// ensure console.group is properly closed
78-
logging.dispatchError(e.message)
73+
logging.dispatchError(reactorState, e.message)
7974
throw e
8075
}
8176

82-
if (debug && newState === undefined) {
77+
if (getOption(reactorState, 'throwOnUndefinedDispatch') && newState === undefined) {
8378
const errorMsg = 'Store handler must return a value, did you forget a return statement'
84-
logging.dispatchError(errorMsg)
79+
logging.dispatchError(reactorState, errorMsg)
8580
throw new Error(errorMsg)
8681
}
8782

@@ -91,15 +86,9 @@ exports.dispatch = function(reactorState, actionType, payload) {
9186
// if the store state changed add store to list of dirty stores
9287
dirtyStores = dirtyStores.add(id)
9388
}
94-
95-
if (debug) {
96-
logging.storeHandled(id, currState, newState)
97-
}
9889
})
9990

100-
if (debug) {
101-
logging.dispatchEnd(state)
102-
}
91+
logging.dispatchEnd(reactorState, state, dirtyStores)
10392
})
10493

10594
const nextReactorState = reactorState
@@ -115,7 +104,7 @@ exports.dispatch = function(reactorState, actionType, payload) {
115104
* @param {Immutable.Map} state
116105
* @return {ReactorState}
117106
*/
118-
exports.loadState = function(reactorState, state) {
107+
export function loadState(reactorState, state) {
119108
let dirtyStores = []
120109
const stateToLoad = toImmutable({}).withMutations(stateToLoad => {
121110
each(state, (serializedStoreState, storeId) => {
@@ -154,7 +143,7 @@ exports.loadState = function(reactorState, state) {
154143
* @param {function} handler
155144
* @return {ObserveResult}
156145
*/
157-
exports.addObserver = function(observerState, getter, handler) {
146+
export function addObserver(observerState, getter, handler) {
158147
// use the passed in getter as the key so we can rely on a byreference call for unobserve
159148
const getterKey = getter
160149
if (isKeyPath(getter)) {
@@ -180,7 +169,7 @@ exports.addObserver = function(observerState, getter, handler) {
180169
storeDeps.forEach(storeId => {
181170
let path = ['stores', storeId]
182171
if (!map.hasIn(path)) {
183-
map.setIn(path, Immutable.Set([]))
172+
map.setIn(path, Immutable.Set())
184173
}
185174
map.updateIn(['stores', storeId], observerIds => observerIds.add(currId))
186175
})
@@ -197,6 +186,19 @@ exports.addObserver = function(observerState, getter, handler) {
197186
}
198187
}
199188

189+
/**
190+
* @param {ReactorState} reactorState
191+
* @param {String} option
192+
* @return {Boolean}
193+
*/
194+
export function getOption(reactorState, option) {
195+
const value = reactorState.getIn(['options', option])
196+
if (value === undefined) {
197+
throw new Error('Invalid option: ' + option)
198+
}
199+
return value
200+
}
201+
200202
/**
201203
* Use cases
202204
* removeObserver(observerState, [])
@@ -210,7 +212,7 @@ exports.addObserver = function(observerState, getter, handler) {
210212
* @param {Function} handler
211213
* @return {ObserverState}
212214
*/
213-
exports.removeObserver = function(observerState, getter, handler) {
215+
export function removeObserver(observerState, getter, handler) {
214216
const entriesToRemove = observerState.get('observersMap').filter(entry => {
215217
// use the getterKey in the case of a keyPath is transformed to a getter in addObserver
216218
let entryGetter = entry.get('getterKey')
@@ -227,7 +229,7 @@ exports.removeObserver = function(observerState, getter, handler) {
227229
})
228230

229231
return observerState.withMutations(map => {
230-
entriesToRemove.forEach(entry => exports.removeObserverByEntry(map, entry))
232+
entriesToRemove.forEach(entry => removeObserverByEntry(map, entry))
231233
})
232234
}
233235

@@ -237,7 +239,7 @@ exports.removeObserver = function(observerState, getter, handler) {
237239
* @param {Immutable.Map} entry
238240
* @return {ObserverState}
239241
*/
240-
exports.removeObserverByEntry = function(observerState, entry) {
242+
export function removeObserverByEntry(observerState, entry) {
241243
return observerState.withMutations(map => {
242244
const id = entry.get('id')
243245
const storeDeps = entry.get('storeDeps')
@@ -264,8 +266,7 @@ exports.removeObserverByEntry = function(observerState, entry) {
264266
* @param {ReactorState} reactorState
265267
* @return {ReactorState}
266268
*/
267-
exports.reset = function(reactorState) {
268-
const debug = reactorState.get('debug')
269+
export function reset(reactorState) {
269270
const prevState = reactorState.get('state')
270271

271272
return reactorState.withMutations(reactorState => {
@@ -274,17 +275,17 @@ exports.reset = function(reactorState) {
274275
storeMap.forEach((store, id) => {
275276
const storeState = prevState.get(id)
276277
const resetStoreState = store.handleReset(storeState)
277-
if (debug && resetStoreState === undefined) {
278+
if (getOption(reactorState, 'throwOnUndefinedDispatch') && resetStoreState === undefined) {
278279
throw new Error('Store handleReset() must return a value, did you forget a return statement')
279280
}
280-
if (debug && !isImmutableValue(resetStoreState)) {
281+
if (getOption(reactorState, 'throwOnNonImmutableStore') && !isImmutableValue(resetStoreState)) {
281282
throw new Error('Store reset state must be an immutable value, did you forget to call toImmutable')
282283
}
283284
reactorState.setIn(['state', id], resetStoreState)
284285
})
285286

286287
reactorState.update('storeStates', storeStates => incrementStoreStates(storeStates, storeIds))
287-
exports.resetDirtyStores(reactorState)
288+
resetDirtyStores(reactorState)
288289
})
289290
}
290291

@@ -293,7 +294,7 @@ exports.reset = function(reactorState) {
293294
* @param {KeyPath|Gettter} keyPathOrGetter
294295
* @return {EvaluateResult}
295296
*/
296-
exports.evaluate = function evaluate(reactorState, keyPathOrGetter) {
297+
export function evaluate(reactorState, keyPathOrGetter) {
297298
const state = reactorState.get('state')
298299

299300
if (isKeyPath(keyPathOrGetter)) {
@@ -331,7 +332,7 @@ exports.evaluate = function evaluate(reactorState, keyPathOrGetter) {
331332
* @param {ReactorState} reactorState
332333
* @return {Object}
333334
*/
334-
exports.serialize = function(reactorState) {
335+
export function serialize(reactorState) {
335336
let serialized = {}
336337
reactorState.get('stores').forEach((store, id) => {
337338
let storeState = reactorState.getIn(['state', id])
@@ -348,7 +349,7 @@ exports.serialize = function(reactorState) {
348349
* @param {ReactorState} reactorState
349350
* @return {ReactorState}
350351
*/
351-
exports.resetDirtyStores = function(reactorState) {
352+
export function resetDirtyStores(reactorState) {
352353
return reactorState.set('dirtyStores', Immutable.Set())
353354
}
354355

0 commit comments

Comments
 (0)