diff --git a/src/action.js b/src/action.js index 1ff9b78a..f3aef3e8 100644 --- a/src/action.js +++ b/src/action.js @@ -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. diff --git a/src/action/states.js b/src/action/states.js index c5a9fe88..f08629a0 100644 --- a/src/action/states.js +++ b/src/action/states.js @@ -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, diff --git a/src/stores/meta.js b/src/domains/meta.js similarity index 50% rename from src/stores/meta.js rename to src/domains/meta.js index a7fe3843..63367de5 100644 --- a/src/stores/meta.js +++ b/src/domains/meta.js @@ -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' diff --git a/src/getStoreHandlers.js b/src/getDomainHandlers.js similarity index 64% rename from src/getStoreHandlers.js rename to src/getDomainHandlers.js index 81b2c624..be69fff7 100644 --- a/src/getStoreHandlers.js +++ b/src/getDomainHandlers.js @@ -6,15 +6,15 @@ 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 ` @@ -22,8 +22,8 @@ function getHandler (key, store, type) { } 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.`) } } @@ -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 } diff --git a/src/microcosm.js b/src/microcosm.js index 1d6a946e..77c6132d 100644 --- a/src/microcosm.js +++ b/src/microcosm.js @@ -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 @@ -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 @@ -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. */ @@ -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. * @@ -85,15 +88,15 @@ 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 @@ -101,7 +104,7 @@ export default class Microcosm extends Emitter { /** * 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 @@ -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 @@ -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)) { + 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 @@ -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. @@ -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. @@ -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 diff --git a/test/dispatch.test.js b/test/dispatch.test.js index 947d349c..a8c2d899 100644 --- a/test/dispatch.test.js +++ b/test/dispatch.test.js @@ -17,7 +17,7 @@ test('does not mutate base state on prior dispatches', t => { return true } - app.addStore({ + app.addDomain({ getInitialState() { return { toggled: false diff --git a/test/microcosm.test.js b/test/microcosm.test.js index bacbca8e..a79a0a97 100644 --- a/test/microcosm.test.js +++ b/test/microcosm.test.js @@ -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() } @@ -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 } @@ -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') +}) diff --git a/test/mutation.test.js b/test/mutation.test.js index 53e73896..7a80da01 100644 --- a/test/mutation.test.js +++ b/test/mutation.test.js @@ -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 } diff --git a/test/register.test.js b/test/register.test.js index b904ac59..e0197624 100644 --- a/test/register.test.js +++ b/test/register.test.js @@ -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() { @@ -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' } @@ -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' diff --git a/test/serialization.test.js b/test/serialization.test.js index 78b60295..e0892c82 100644 --- a/test/serialization.test.js +++ b/test/serialization.test.js @@ -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' }, @@ -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) } @@ -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 } @@ -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' }) diff --git a/test/store.test.js b/test/store.test.js index c42b399d..2c0b7929 100644 --- a/test/store.test.js +++ b/test/store.test.js @@ -1,10 +1,10 @@ import test from 'ava' import Microcosm from '../src/microcosm' -test('stores can be functions', t => { +test('domains can be functions', t => { const app = new Microcosm() - app.addStore('key', function() { + app.addDomain('key', function() { return { getInitialState: () => true } @@ -13,10 +13,10 @@ test('stores can be functions', t => { t.is(app.state.key, true) }) -test('stores can be objects with lifecycle methods', t => { +test('domains can be objects with lifecycle methods', t => { const app = new Microcosm() - app.addStore('key', { + app.addDomain('key', { getInitialState: () => true }) @@ -26,16 +26,16 @@ test('stores can be objects with lifecycle methods', t => { test('throws if a registry contains an undefined key', t => { const app = new Microcosm() - // This will throw when added to a store because it will dispatch + // This will throw when added to a domain because it will dispatch // "getInitialState" - const badStore = function() { + const badDomain = function() { return { [undefined]: n => n } } t.throws(function() { - app.addStore('test', badStore) + app.addDomain('test', badDomain) }, /\"undefined\" attribute within register/) }) @@ -43,7 +43,7 @@ test('throws if a register handler is undefined', t => { const app = new Microcosm() const action = n => n - app.addStore('key', function() { + app.addDomain('key', function() { return { [action]: undefined } @@ -51,5 +51,5 @@ test('throws if a register handler is undefined', t => { t.throws(function() { app.push(action) - }, /Check the register method for this store/) + }, /Check the register method for this domain/) })