Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/action.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export default class Action extends Emitter {

/**
* Check the state of the action to determine what `type` should be
* dispatched to stores for processing (via register()).
* dispatched to domains for processing (via register()).
*
* @private
* @return {String|Null} The action type to dspatch.
Expand Down
2 changes: 1 addition & 1 deletion src/action/states.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export default {
// the action has failed
failed : 16,

// prevent the action from dispatching to stores. this is used by
// prevent the action from dispatching to domains. this is used by
// the microcosm debugger to toggle actions within the history tree.
disabled : 32,

Expand Down
6 changes: 3 additions & 3 deletions src/stores/meta.js → src/domains/meta.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Meta Store
* A store for managing lifecycle methods and other
* default behavior for other stores.
* Meta Domain
* A domain for managing lifecycle methods and other
* default behavior for other domains.
*/

import lifecycle from '../lifecycle'
Expand Down
31 changes: 17 additions & 14 deletions src/getStoreHandlers.js → src/getDomainHandlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,24 @@ function format (string) {
return action ? `the ${ action } action's ${ state } state` : string
}

function getHandler (key, store, type) {
let handler = store[type]
function getHandler (key, domain, type) {
let handler = domain[type]

if (handler === undefined && store.register) {
const registrations = store.register()
if (handler === undefined && domain.register) {
const registrations = domain.register()

if (process.env.NODE_ENV !== 'production') {
if ('undefined' in registrations) {
throw new Error(`When dispatching ${ format(type) } to the ${ key } store, `
throw new Error(`When dispatching ${ format(type) } to the ${ key } domain, `
+ `we encountered an "undefined" attribute within register(). `
+ `This usually happens when an action is imported `
+ `from the wrong namespace, or by referencing an invalid `
+ `action state.`)
}

if (type in registrations && registrations[type] === undefined) {
throw new Error(`The handler for "${ format(type) }" within a store for "${ key }" `
+ `is undefined. Check the register method for this store.`)
throw new Error(`The handler for "${ format(type) }" within a domain for "${ key }" `
+ `is undefined. Check the register method for this domain.`)
}
}

Expand All @@ -33,13 +33,16 @@ function getHandler (key, store, type) {
return handler
}

export default function getStoreHandlers (entries, type) {
export default function getDomainHandlers (domains, type) {
const handlers = []

return entries.reduce(function (handlers, entry) {
let key = entry[0]
let store = entry[1]
let handler = getHandler(key, store, type)
domains.forEach(function (domain, key) {
let handler = getHandler(key, domain, type)

return handler === undefined ? handlers : handlers.concat({ key, store, handler })
}, [])
if (handler !== undefined) {
handlers.push({ key, domain, handler })
}
})

return handlers
}
70 changes: 40 additions & 30 deletions src/microcosm.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import Emitter from './emitter'
import MetaStore from './stores/meta'
import Tree from './tree'
import lifecycle from './lifecycle'
import getStoreHandlers from './getStoreHandlers'
import merge from './merge'
import update from './update'
import Emitter from './emitter'
import MetaDomain from './domains/meta'
import Tree from './tree'
import lifecycle from './lifecycle'
import getDomainHandlers from './getDomainHandlers'
import merge from './merge'
import update from './update'

/**
* A tree-like data structure that keeps track of the execution order
Expand All @@ -24,9 +24,12 @@ export default class Microcosm extends Emitter {

this.history = new Tree()
this.maxHistory = maxHistory
this.stores = []
this.domains = new Map()

// cache store registry methods for efficiency
// for backwards compatibility
this.addStore = this.addDomain

// cache domain registry methods for efficiency
this.registry = {}

// cache represents the result of dispatching all complete
Expand All @@ -37,13 +40,13 @@ export default class Microcosm extends Emitter {
// actions over cache
this.state = {}

// Standard store reduction behaviors
this.addStore(MetaStore)
// Standard domain reduction behaviors
this.addDomain(MetaDomain)
}

/**
* Generates the starting state for a Microcosm instance by asking every
* store store that subscribes to `getInitialState`.
* domain that subscribes to `getInitialState`.
*
* @return {Object} State object representing the initial state.
*/
Expand Down Expand Up @@ -72,9 +75,9 @@ export default class Microcosm extends Emitter {
}

/**
* Dispatch an action to a list of stores. This is used by state
* Dispatch an action to a list of domains. This is used by state
* management methods, like `rollforward` and `getInitialState` to
* compute state. Assuming there are no side-effects in store
* compute state. Assuming there are no side-effects in domain
* handlers, this is pure. Calling this method will not update
* application state.
*
Expand All @@ -85,23 +88,23 @@ export default class Microcosm extends Emitter {
*/
dispatch (state, { type, payload }) {
if (!this.registry[type]) {
this.registry[type] = getStoreHandlers(this.stores, type)
this.registry[type] = getDomainHandlers(this.domains, type)
}

const handlers = this.registry[type]

for (var i = 0, len = handlers.length; i < len; i++) {
const { key, store, handler } = handlers[i]
const { key, domain, handler } = handlers[i]

state = update.set(state, key, handler.call(store, update.get(state, key), payload))
state = update.set(state, key, handler.call(domain, update.get(state, key), payload))
}

return state
}

/**
* Run through the action history, dispatching their associated
* types and payloads to stores for processing. Emits "change".
* types and payloads to domains for processing. Emits "change".
*
* @private
* @return {Microcosm} self
Expand Down Expand Up @@ -156,15 +159,15 @@ export default class Microcosm extends Emitter {
}

/**
* Adds a store to the Microcosm instance. A store informs the
* Adds a domain to the Microcosm instance. A domain informs the
* microcosm how to process various action types. If no key
* is given, the store will operate on all application state.
* is given, the domain will operate on all application state.
*
* @param {String} key - The namespace within application state for the store.
* @param {Object|Function} config - Configuration options for the store
* @param {String} key - The namespace within application state for the domain.
* @param {Object|Function} config - Configuration options for the domain
* @return {Microcosm} self
*/
addStore (key, config) {
addDomain (key, config) {
if (arguments.length < 2) {
// Important! Assignment this way is important
// to support IE9, which has an odd way of referencing
Expand All @@ -177,8 +180,15 @@ export default class Microcosm extends Emitter {
config = { register: config }
}

this.stores = this.stores.concat([[ key, config ]])
if (process.env.NODE_ENV !== 'production') {
if (key && this.domains.has(key)) {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added this in for warning about duplicates, but only if a key is present

throw new Error(`You are attempting to add a "${ key }" domain, however a `
+ `domain of the same name already exists. The new domain will `
+ `overwrite the old one, which may cause strange behavior. `)
}
}

this.domains.set(key, config)
this.rebase()

return this
Expand Down Expand Up @@ -207,8 +217,8 @@ export default class Microcosm extends Emitter {
}

/**
* Deserialize a given payload by asking every store how to it
* should process it (via the deserialize store function).
* Deserialize a given payload by asking every domain how to it
* should process it (via the deserialize domain function).
*
* @param {Object} payload - A raw object to deserialize.
* @return {Object} The deserialized version of the provided payload.
Expand All @@ -222,8 +232,8 @@ export default class Microcosm extends Emitter {
}

/**
* Serialize application state by asking every store how to
* serialize the state they manage (via the serialize store
* Serialize application state by asking every domain how to
* serialize the state they manage (via the serialize domain
* function).
*
* @return {Object} The serialized version of application state.
Expand All @@ -242,8 +252,8 @@ export default class Microcosm extends Emitter {

/**
* Recalculate initial state by back-filling the cache object with
* the result of getInitialState(). This is used when a store is
* added to Microcosm to ensure the initial state of the store is
* the result of getInitialState(). This is used when a domain is
* added to Microcosm to ensure the initial state of the domain is
* respected. Emits a "change" event.
*
* @private
Expand Down
2 changes: 1 addition & 1 deletion test/dispatch.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ test('does not mutate base state on prior dispatches', t => {
return true
}

app.addStore({
app.addDomain({
getInitialState() {
return {
toggled: false
Expand Down
41 changes: 39 additions & 2 deletions test/microcosm.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Microcosm from '../src/microcosm'
test('deserializes when replace is invoked', t => {
const app = new Microcosm()

app.addStore('dummy', function() {
app.addDomain('dummy', function() {
return {
deserialize: state => state.toUpperCase()
}
Expand Down Expand Up @@ -62,7 +62,7 @@ test('can checkout a prior state', t => {
const app = new Microcosm({ maxHistory: Infinity })
const action = n => n

app.addStore('number', function() {
app.addDomain('number', function() {
return {
[action]: (a, b) => b
}
Expand All @@ -76,3 +76,40 @@ test('can checkout a prior state', t => {

t.is(app.state.number, 1)
})

test('warns of domain overwrites', t => {
const app = new Microcosm()
const domain1 = {}
const domain2 = {}

app.addDomain('foo', domain1)

t.throws(function() {
app.addDomain('foo', domain2)
}, Error)
})

test('can access actions via domains', t => {
const app = new Microcosm()
const domain = {
actions: {
barMe: n => n
},

register() {
return {
[domain.actions.barMe]: this.barMe
}
},

barMe() {
return 'bar'
}
}

app.addDomain('foo', domain)

app.push(app.domains.get('foo').actions.barMe)

t.is(app.state.foo, 'bar')
})
2 changes: 1 addition & 1 deletion test/mutation.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ test.cb('writes to application state', t => {
const action = function() {}
const app = new Microcosm()

app.addStore(function() {
app.addDomain(function() {
return {
getInitialState() {
return { test: false }
Expand Down
8 changes: 4 additions & 4 deletions test/register.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import lifecycle from '../src/lifecycle'

const action = a => a

test('sends actions in the context of the store', t => {
test('sends actions in the context of the domain', t => {
t.plan(1)

const app = new Microcosm()

app.addStore('test', {
app.addDomain('test', {
test: true,

register() {
Expand All @@ -29,7 +29,7 @@ test('returns the same state if a handler is not provided', t => {

const app = new Microcosm()

app.addStore('test', {
app.addDomain('test', {
getInitialState() {
return 'test'
}
Expand All @@ -43,7 +43,7 @@ test('returns the same state if a handler is not provided', t => {
test('allows lifecycle methods as registered actions', t => {
const app = new Microcosm()

app.addStore('test', {
app.addDomain('test', {
register() {
return {
[lifecycle.willStart]: () => 'test'
Expand Down
10 changes: 5 additions & 5 deletions test/serialization.test.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import test from 'ava'
import Microcosm from '../src/microcosm'

test('runs through serialize methods on stores', t => {
test('runs through serialize methods on domains', t => {
const app = new Microcosm()

app.addStore('serialize-test', {
app.addDomain('serialize-test', {
getInitialState() {
return 'this will not display'
},
Expand All @@ -21,7 +21,7 @@ test('sends all state as the second argument', t => {

const app = new Microcosm()

app.addStore('serialize-test', {
app.addDomain('serialize-test', {
serialize(subset, state) {
t.is(state, app.state)
}
Expand All @@ -35,7 +35,7 @@ test('defaults to getInitialState when no deserialize method is provided', t =>

const app = new Microcosm()

app.addStore('fiz', {
app.addDomain('fiz', {
getInitialState() {
return true
}
Expand All @@ -50,7 +50,7 @@ test('passes the raw data as the seconda argument of deserialize', t => {
t.plan(2)
const app = new Microcosm()

app.addStore('fiz', {
app.addDomain('fiz', {
deserialize(subset, raw) {
t.is(subset, 'buzz')
t.deepEqual(raw, { fiz: 'buzz' })
Expand Down
Loading