diff --git a/src/store/AutoObjectStore.js b/src/store/AutoObjectStore.js
new file mode 100644
index 0000000..165dbb7
--- /dev/null
+++ b/src/store/AutoObjectStore.js
@@ -0,0 +1,177 @@
+/**
+ * Convert objects to Stores upon retrieval
+ *
+ * Copyright (C) 2017 R-T Specialty, LLC.
+ *
+ * This file is part of the Liza Data Collection Framework.
+ *
+ * liza is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+'use strict';
+
+const { Trait, Class } = require( 'easejs' );
+const Store = require( './Store' );
+
+
+/**
+ * Convert objects into sub-stores containing its key/value pairs
+ *
+ * When retrieving a value that is an object, it will first be converted
+ * into a Store and populated with the key/value pairs of that
+ * object. Non-object values will remain untouched.
+ *
+ * This trait expects a constructor function to instantiate a new
+ * Store. Providing the same constructor as was used to instantiate the
+ * current object will allow for an object to be recursively converted into
+ * nested Stores.
+ *
+ * Sub-stores are cached until the value of the key is references
+ * changes, after which point another request to `#get` will instantiate a
+ * _new_ store. The previous store will not be modified to reflect the new
+ * value.
+ *
+ * @example
+ * store.get( 'foo' ); // new Store (1)
+ * store.get( 'foo' ); // existing Store (1)
+ * store.add( 'foo', {} );
+ * store.get( 'foo' ); // new Store (2)
+ * store.add( 'foo', "bar" );
+ * store.get( 'foo' ); // "bar"
+ */
+module.exports = Trait( 'AutoObjectStore' )
+ .implement( Store )
+ .extend(
+{
+ /**
+ * Constructor for object sub-stores
+ * @type {function(Object):Store}
+ */
+ 'private _ctor': null,
+
+ /**
+ * Store cache
+ * @type {Object.}
+ */
+ 'private _stores': {},
+
+
+ /**
+ * Initialize with Store constructor
+ *
+ * `ctor` will be used to instantiate Stores as needed.
+ *
+ * @param {function():Store} ctor Store constructor
+ */
+ __mixin( ctor )
+ {
+ this._ctor = ctor;
+ },
+
+
+ /**
+ * Add item to store under `key` with value `value`
+ *
+ * Any cached store for `key` will be cleared so that future `#get`
+ * requests return up-to-date data.
+ *
+ * @param {string} key store key
+ * @param {*} value value for key
+ *
+ * @return {Promise.} promise to add item to store, resolving to
+ * self (for chaining)
+ */
+ 'virtual abstract override public add'( key, value )
+ {
+ return this.__super( key, value )
+ .then( ret =>
+ {
+ delete this._stores[ key ];
+ return ret;
+ } );
+ },
+
+
+ /**
+ * Retrieve item from store under `key`
+ *
+ * If the returned value is an object, it will automatically be
+ * converted into a store and populated with the object's
+ * values; otherwise, the value will be returned unaltered.
+ *
+ * Only vanilla objects (that is---not instances of anything but
+ * `Object`) will be converted into a Store.
+ *
+ * @param {string} key store key
+ *
+ * @return {Promise} promise for the key value
+ */
+ 'virtual abstract override public get'( key )
+ {
+ if ( this._stores[ key ] !== undefined )
+ {
+ return Promise.resolve( this._stores[ key ] );
+ }
+
+ return this.__super( key )
+ .then( value =>
+ {
+ if ( !this._isConvertable( value ) )
+ {
+ return value;
+ }
+
+ // create and cache store (we cache _before_ populating,
+ // otherwise another request might come in and create yet
+ // another store before we have a chance to complete
+ // populating)
+ const substore = this._ctor();
+
+ this._stores[ key ] = substore;
+
+ return substore.populate( value )
+ .then( () => substore );
+ } );
+ },
+
+
+ /**
+ * Determine whether given value should be converted into a Store
+ *
+ * Only vanilla objects (that is---not instances of anything but
+ * `Object`) will be converted into a Store.
+ *
+ * @param {*} value value under consideration
+ *
+ * @return {boolean} whether to convert `value`
+ */
+ 'private _isConvertable'( value )
+ {
+ if ( typeof value !== 'object' )
+ {
+ return false;
+ };
+
+ const ctor = value.constructor || {};
+
+ // instances of prototypes should be left alone, so we should ignore
+ // everything that's not a vanilla object
+ if ( ctor !== Object )
+ {
+ return false;
+ }
+
+ return true;
+ },
+} );
diff --git a/test/store/AutoObjectStoreTest.js b/test/store/AutoObjectStoreTest.js
new file mode 100644
index 0000000..859521b
--- /dev/null
+++ b/test/store/AutoObjectStoreTest.js
@@ -0,0 +1,114 @@
+/**
+ * Tests AutoObjectStore
+ *
+ * Copyright (C) 2017 R-T Specialty, LLC.
+ *
+ * This file is part of the Liza Data Collection Framework.
+ *
+ * liza is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+'use strict';
+
+const chai = require( 'chai' );
+const expect = chai.expect;
+
+chai.use( require( 'chai-as-promised' ) );
+
+const {
+ AutoObjectStore: Sut,
+ MemoryStore: Store,
+} = require( '../../' ).store;
+
+
+
+describe( 'AutoObjectStore', () =>
+{
+ describe( "given an object value", () =>
+ {
+ it( "applies given ctor to objects", () =>
+ {
+ const obj = Store();
+ const dummy_ctor = () => obj;
+ const sut = Store.use( Sut( dummy_ctor ) )();
+
+ const foo = sut
+ .add( 'foo', {} )
+ .then( _ => sut.get( 'foo' ) );
+
+ return expect( foo )
+ .to.eventually.deep.equal( obj );
+ } );
+
+
+ it( "adds object values to new store", () =>
+ {
+ const obj = { bar: "baz" };
+ const sut = Store.use( Sut( Store ) )();
+
+ const bar = sut
+ .add( 'foo', obj )
+ .then( _ => sut.get( 'foo' ) )
+ .then( substore => substore.get( 'bar' ) );
+
+ return expect( bar ).to.eventually.equal( obj.bar );
+ } );
+
+
+ it( "caches sub-store until key changes", () =>
+ {
+ const obj = {};
+ const sut = Store.use( Sut( Store ) )();
+
+ return sut
+ .add( 'foo', {} )
+ .then( _ => sut.get( 'foo' ) )
+ .then( store1 =>
+ expect( sut.get( 'foo' ) ).to.eventually.equal( store1 )
+ .then( _ => sut.add( 'foo', "new" ) )
+ .then( _ => sut.get( 'foo' ) )
+ .then( store2 =>
+ expect( store2 ).to.not.equal( store1 )
+ )
+ );
+ } );
+ } );
+
+
+ it( "leaves non-objects untouched", () =>
+ {
+ const expected = "bar";
+ const sut = Store.use( Sut( () => null ) )();
+
+ const foo = sut
+ .add( 'foo', expected )
+ .then( _ => sut.get( 'foo' ) );
+
+ return expect( foo ).to.eventually.equal( expected );
+ } );
+
+
+ // includes class instances, since easejs generates prototypes
+ it( "leaves prototype instances untouched", () =>
+ {
+ const expected = ( new function() {} );
+ const sut = Store.use( Sut( () => null ) )();
+
+ const foo = sut
+ .add( 'foo', expected )
+ .then( _ => sut.get( 'foo' ) );
+
+ return expect( foo ).to.eventually.equal( expected );
+ } );
+} );