Skip to content

Commit

Permalink
Merge pull request #186 from optimizely/jordan/add-debug-options
Browse files Browse the repository at this point in the history
Allow all debug options to be flagged on / off as overrides
  • Loading branch information
jordangarcia committed Dec 23, 2015
2 parents 2e8b995 + 45a3131 commit 6342f62
Show file tree
Hide file tree
Showing 7 changed files with 223 additions and 69 deletions.
33 changes: 32 additions & 1 deletion docs/src/docs/07-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,38 @@ var reactor = Nuclear.Reactor(config)

**Configuration Options**

`config.debug` Boolean - if true it will log the entire app state for every dispatch.
`config.debug` Boolean - if true it will enabled logging for dispatches and throw Errors in various circumstances described below.

**config.options** (added in 1.3)

If `config.debug` is true then all of the options below will be enabled.

`logDispatches` (default=`false`) console.logs for every action. If disabled `logAppState` and `logDirtyStores` will be ignored, as no dispatch logging is occurring.

`logAppState` (default=`false`) console.logs a snapshot of the entire app state after every dispatch. Disabling this can improve performance.

`logDirtyStores` (default=`false`) console.logs what stores have changed after each dispatched action.

`throwOnUndefinedDispatch` (default=`false`) if true, throws an Error if a store ever returns undefined.

`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.

`throwOnDispatchInDispatch` (default=`true`) if true, throws an Error if a dispatch occurs in a change observer.

**Example**

```javascript
var reactor = new Nuclear.Reactor({
debug: true,
options: {
// do not log entire app state
logAppState: false,
// allow dispatch in dispatch
throwOnDispatchInDispatch: false,
},
})
```


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

Expand Down
35 changes: 25 additions & 10 deletions src/logging.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
import { getOption } from './reactor/fns'

/* eslint-disable no-console */
/**
* Wraps a Reactor.react invocation in a console.group
* @param {ReactorState} reactorState
* @param {String} type
* @param {*} payload
*/
exports.dispatchStart = function(type, payload) {
exports.dispatchStart = function(reactorState, type, payload) {
if (!getOption(reactorState, 'logDispatches')) {
return
}

if (console.group) {
console.groupCollapsed('Dispatch: %s', type)
console.group('payload')
Expand All @@ -11,24 +20,30 @@ exports.dispatchStart = function(type, payload) {
}
}

exports.dispatchError = function(error) {
exports.dispatchError = function(reactorState, error) {
if (!getOption(reactorState, 'logDispatches')) {
return
}

if (console.group) {
console.debug('Dispatch error: ' + error)
console.groupEnd()
}
}

exports.storeHandled = function(id, before, after) {
if (console.group) {
if (before !== after) {
console.debug('Store ' + id + ' handled action')
}
exports.dispatchEnd = function(reactorState, state, dirtyStores) {
if (!getOption(reactorState, 'logDispatches')) {
return
}
}

exports.dispatchEnd = function(state) {
if (console.group) {
console.debug('Dispatch done, new state: ', state.toJS())
if (getOption(reactorState, 'logDirtyStores')) {
console.log('Stores updated:', dirtyStores.toList().toJS())
}

if (getOption(reactorState, 'logAppState')) {
console.debug('Dispatch done, new state: ', state.toJS())
}
console.groupEnd()
}
}
Expand Down
23 changes: 17 additions & 6 deletions src/reactor.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import Immutable from 'immutable'
import createReactMixin from './create-react-mixin'
import fns from './reactor/fns'
import * as fns from './reactor/fns'
import { isKeyPath } from './key-path'
import { isGetter } from './getter'
import { toJS } from './immutable-helpers'
import { toFactory } from './utils'
import { ReactorState, ObserverState } from './reactor/records'
import {
ReactorState,
ObserverState,
DEBUG_OPTIONS,
PROD_OPTIONS,
} from './reactor/records'

/**
* State is stored in NuclearJS Reactors. Reactors
Expand All @@ -18,8 +23,12 @@ import { ReactorState, ObserverState } from './reactor/records'
*/
class Reactor {
constructor(config = {}) {
const debug = !!config.debug
const baseOptions = debug ? DEBUG_OPTIONS : PROD_OPTIONS
const initialReactorState = new ReactorState({
debug: config.debug,
debug: debug,
// merge config options with the defaults
options: baseOptions.merge(config.options || {}),
})

this.prevReactorState = initialReactorState
Expand Down Expand Up @@ -101,9 +110,11 @@ class Reactor {
*/
dispatch(actionType, payload) {
if (this.__batchDepth === 0) {
if (this.__isDispatching) {
this.__isDispatching = false
throw new Error('Dispatch may not be called while a dispatch is in progress')
if (fns.getOption(this.reactorState, 'throwOnDispatchInDispatch')) {
if (this.__isDispatching) {
this.__isDispatching = false
throw new Error('Dispatch may not be called while a dispatch is in progress')
}
}
this.__isDispatching = true
}
Expand Down
67 changes: 34 additions & 33 deletions src/reactor/fns.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,7 @@ function evaluateResult(result, reactorState) {
* @param {Object<String, Store>} stores
* @return {ReactorState}
*/
exports.registerStores = function(reactorState, stores) {
const debug = reactorState.get('debug')

export function registerStores(reactorState, stores) {
return reactorState.withMutations((reactorState) => {
each(stores, (store, id) => {
if (reactorState.getIn(['stores', id])) {
Expand All @@ -36,7 +34,7 @@ exports.registerStores = function(reactorState, stores) {

const initialState = store.getInitialState()

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

Expand All @@ -56,15 +54,12 @@ exports.registerStores = function(reactorState, stores) {
* @param {*} payload
* @return {ReactorState}
*/
exports.dispatch = function(reactorState, actionType, payload) {
export function dispatch(reactorState, actionType, payload) {
const currState = reactorState.get('state')
const debug = reactorState.get('debug')
let dirtyStores = reactorState.get('dirtyStores')

const nextState = currState.withMutations(state => {
if (debug) {
logging.dispatchStart(actionType, payload)
}
logging.dispatchStart(reactorState, actionType, payload)

// let each store handle the message
reactorState.get('stores').forEach((store, id) => {
Expand All @@ -75,13 +70,13 @@ exports.dispatch = function(reactorState, actionType, payload) {
newState = store.handle(currState, actionType, payload)
} catch(e) {
// ensure console.group is properly closed
logging.dispatchError(e.message)
logging.dispatchError(reactorState, e.message)
throw e
}

if (debug && newState === undefined) {
if (getOption(reactorState, 'throwOnUndefinedDispatch') && newState === undefined) {
const errorMsg = 'Store handler must return a value, did you forget a return statement'
logging.dispatchError(errorMsg)
logging.dispatchError(reactorState, errorMsg)
throw new Error(errorMsg)
}

Expand All @@ -91,15 +86,9 @@ exports.dispatch = function(reactorState, actionType, payload) {
// if the store state changed add store to list of dirty stores
dirtyStores = dirtyStores.add(id)
}

if (debug) {
logging.storeHandled(id, currState, newState)
}
})

if (debug) {
logging.dispatchEnd(state)
}
logging.dispatchEnd(reactorState, state, dirtyStores)
})

const nextReactorState = reactorState
Expand All @@ -115,7 +104,7 @@ exports.dispatch = function(reactorState, actionType, payload) {
* @param {Immutable.Map} state
* @return {ReactorState}
*/
exports.loadState = function(reactorState, state) {
export function loadState(reactorState, state) {
let dirtyStores = []
const stateToLoad = toImmutable({}).withMutations(stateToLoad => {
each(state, (serializedStoreState, storeId) => {
Expand Down Expand Up @@ -154,7 +143,7 @@ exports.loadState = function(reactorState, state) {
* @param {function} handler
* @return {ObserveResult}
*/
exports.addObserver = function(observerState, getter, handler) {
export function addObserver(observerState, getter, handler) {
// use the passed in getter as the key so we can rely on a byreference call for unobserve
const getterKey = getter
if (isKeyPath(getter)) {
Expand All @@ -180,7 +169,7 @@ exports.addObserver = function(observerState, getter, handler) {
storeDeps.forEach(storeId => {
let path = ['stores', storeId]
if (!map.hasIn(path)) {
map.setIn(path, Immutable.Set([]))
map.setIn(path, Immutable.Set())
}
map.updateIn(['stores', storeId], observerIds => observerIds.add(currId))
})
Expand All @@ -197,6 +186,19 @@ exports.addObserver = function(observerState, getter, handler) {
}
}

/**
* @param {ReactorState} reactorState
* @param {String} option
* @return {Boolean}
*/
export function getOption(reactorState, option) {
const value = reactorState.getIn(['options', option])
if (value === undefined) {
throw new Error('Invalid option: ' + option)
}
return value
}

/**
* Use cases
* removeObserver(observerState, [])
Expand All @@ -210,7 +212,7 @@ exports.addObserver = function(observerState, getter, handler) {
* @param {Function} handler
* @return {ObserverState}
*/
exports.removeObserver = function(observerState, getter, handler) {
export function removeObserver(observerState, getter, handler) {
const entriesToRemove = observerState.get('observersMap').filter(entry => {
// use the getterKey in the case of a keyPath is transformed to a getter in addObserver
let entryGetter = entry.get('getterKey')
Expand All @@ -227,7 +229,7 @@ exports.removeObserver = function(observerState, getter, handler) {
})

return observerState.withMutations(map => {
entriesToRemove.forEach(entry => exports.removeObserverByEntry(map, entry))
entriesToRemove.forEach(entry => removeObserverByEntry(map, entry))
})
}

Expand All @@ -237,7 +239,7 @@ exports.removeObserver = function(observerState, getter, handler) {
* @param {Immutable.Map} entry
* @return {ObserverState}
*/
exports.removeObserverByEntry = function(observerState, entry) {
export function removeObserverByEntry(observerState, entry) {
return observerState.withMutations(map => {
const id = entry.get('id')
const storeDeps = entry.get('storeDeps')
Expand All @@ -264,8 +266,7 @@ exports.removeObserverByEntry = function(observerState, entry) {
* @param {ReactorState} reactorState
* @return {ReactorState}
*/
exports.reset = function(reactorState) {
const debug = reactorState.get('debug')
export function reset(reactorState) {
const prevState = reactorState.get('state')

return reactorState.withMutations(reactorState => {
Expand All @@ -274,17 +275,17 @@ exports.reset = function(reactorState) {
storeMap.forEach((store, id) => {
const storeState = prevState.get(id)
const resetStoreState = store.handleReset(storeState)
if (debug && resetStoreState === undefined) {
if (getOption(reactorState, 'throwOnUndefinedDispatch') && resetStoreState === undefined) {
throw new Error('Store handleReset() must return a value, did you forget a return statement')
}
if (debug && !isImmutableValue(resetStoreState)) {
if (getOption(reactorState, 'throwOnNonImmutableStore') && !isImmutableValue(resetStoreState)) {
throw new Error('Store reset state must be an immutable value, did you forget to call toImmutable')
}
reactorState.setIn(['state', id], resetStoreState)
})

reactorState.update('storeStates', storeStates => incrementStoreStates(storeStates, storeIds))
exports.resetDirtyStores(reactorState)
resetDirtyStores(reactorState)
})
}

Expand All @@ -293,7 +294,7 @@ exports.reset = function(reactorState) {
* @param {KeyPath|Gettter} keyPathOrGetter
* @return {EvaluateResult}
*/
exports.evaluate = function evaluate(reactorState, keyPathOrGetter) {
export function evaluate(reactorState, keyPathOrGetter) {
const state = reactorState.get('state')

if (isKeyPath(keyPathOrGetter)) {
Expand Down Expand Up @@ -331,7 +332,7 @@ exports.evaluate = function evaluate(reactorState, keyPathOrGetter) {
* @param {ReactorState} reactorState
* @return {Object}
*/
exports.serialize = function(reactorState) {
export function serialize(reactorState) {
let serialized = {}
reactorState.get('stores').forEach((store, id) => {
let storeState = reactorState.getIn(['state', id])
Expand All @@ -348,7 +349,7 @@ exports.serialize = function(reactorState) {
* @param {ReactorState} reactorState
* @return {ReactorState}
*/
exports.resetDirtyStores = function(reactorState) {
export function resetDirtyStores(reactorState) {
return reactorState.set('dirtyStores', Immutable.Set())
}

Expand Down
Loading

0 comments on commit 6342f62

Please sign in to comment.