Join our webinar on Wednesday, June 26, at 1pm EDTHow Chia Mitigates Risk in the Crypto Industry.Register
Socket
Socket
Sign inDemoInstall

redux-orm

Package Overview
Dependencies
2
Maintainers
1
Versions
70
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 0.4.0 to 0.5.0

13

lib/Backend.js

@@ -32,2 +32,15 @@ 'use strict';

* @param {Object} userOpts - options to use.
* @param {string} [userOpts.idAttribute=id] - the id attribute of the entity.
* @param {Boolean} [userOpts.indexById=true] - a boolean indicating if the entities
* should be indexed by `idAttribute`.
* @param {string} [userOpts.arrName=items] - the state attribute where an array of
* either entity id's (if `indexById === true`)
* or the entity objects are stored.
* @param {string} [userOpts.mapName=itemsById] - if `indexById === true`, the state
* attribute where the entity objects
* are stored in a id to entity object
* map.
* @param {Boolean} [userOpts.withMutations=false] - a boolean indicating if the backend
* operations should be executed
* with or without mutations.
*/

@@ -34,0 +47,0 @@

23

lib/memoize.js

@@ -36,4 +36,25 @@ 'use strict';

*
* On each run, the memoizer check
* Memoization algorithm operates like this:
*
* 1. Has the selector been run before? If not, go to 5.
*
* 2. If the selector has other input selectors in addition to the
* ORM state selector, check their results for equality with the previous results.
* If they aren't equal, go to 5.
*
* 3. Is the ORM state referentially equal to the previous ORM state the selector
* was called with? If yes, return the previous result.
*
* 4. Check which Model's states the selector has accessed on previous runs.
* Check for equality with each of those states versus their states in the
* previous ORM state. If all of them are equal, return the previous result.
*
* 5. Run the selector. Check the Session object used by the selector for
* which Model's states were accessed, and merge them with the previously
* saved information about accessed models (if-else branching can change
* which models are accessed on different inputs). Save the ORM state and
* other arguments the selector was called with, overriding previously
* saved values. Save the selector result. Return the selector result.
*
* @private
* @param {Function} func - function to memoize

@@ -40,0 +61,0 @@ * @param {Function} equalityCheck - equality check function to use with normal

76

lib/Model.js

@@ -120,3 +120,3 @@ 'use strict';

/**
* Gets the id value of the current instance.
* Gets the id value of the current instance by looking up the id attribute.
* @return {*} The id value of the current instance.

@@ -141,2 +141,3 @@ */

* Returns a string representation of the {@link Model} instance.
*
* @return {string} A string representation of this {@link Model} instance.

@@ -154,6 +155,14 @@ */

}
/**
* Returns a boolean indicating if `otherModel` equals this {@link Model} instance.
* Equality is determined by shallow comparing their attributes.
*
* @param {Model} otherModel - a {@link Model} instance to compare
* @return {Boolean} a boolean indicating if the {@link Model} instance's are equal.
*/
}, {
key: 'equals',
value: function equals(otherModel) {
return this.getClass() === otherModel.getClass() && this.getId() === otherModel.getId();
return (0, _utils.objectShallowEquals)(this._fields, otherModel._fields);
}

@@ -164,2 +173,3 @@

* field value assignment.
*
* @param {string} propertyName - name of the property to set

@@ -178,2 +188,3 @@ * @param {*} value - value assigned to the property

* If the session is with mutations, updates the instance to reflect the new values.
*
* @param {Object} userMergeObj - an object that will be merged with this instance.

@@ -286,2 +297,6 @@ * @return {undefined}

* Returns the options object passed to the {@link Backend} class constructor.
* By default, returns an empty object (which means the {@link Backend} instance
* will use default options). You can either override this function to return the options
* you want to use, or assign the options object as a static property to the
* Model class.
*

@@ -293,5 +308,3 @@ * @return {Object} the options object used to instantiate a {@link Backend} class.

value: function backend() {
return {
branchName: this.modelName
};
return {};
}

@@ -304,5 +317,2 @@ }, {

}
if (typeof this.backend === 'undefined') {
throw new Error('You must declare either a \'backend\' class method or\n a \'backend\' class variable in your Model Class');
}
return this.backend;

@@ -328,2 +338,4 @@ }

* Gets the {@link Backend} instance linked to this {@link Model}.
*
* @private
* @return {Backend} The {@link Backend} instance linked to this {@link Model}.

@@ -353,2 +365,4 @@ */

* updates.
*
* @private
* @return {Object} The next state.

@@ -359,8 +373,2 @@ */

value: function getNextState() {
if (typeof this.state === 'undefined') {
return this.getDefaultState();
}
var updates = this.session.getUpdatesFor(this);
var state = undefined;

@@ -373,2 +381,4 @@ if (this._sessionData.hasOwnProperty('nextState')) {

var updates = this.session.getUpdatesFor(this);
if (updates.length > 0) {

@@ -388,2 +398,3 @@ var nextState = updates.reduce(this.updateReducer.bind(this), state);

*
* @private
* @param {Object} state - the Model's state

@@ -403,4 +414,2 @@ * @param {Object} action - the internal redux-orm update action to apply

return backend.update(state, action.payload.idArr, action.payload.mergeObj);
case _constants.ORDER:
return backend.order(state, action.payload);
case _constants.DELETE:

@@ -433,2 +442,4 @@ return backend['delete'](state, action.payload);

* Delegates to a {@link Backend} instance.
*
* @private
* @return {Object} The default state.

@@ -494,2 +505,3 @@ */

*
* @private
* @param {Session} session - The session to connect to.

@@ -509,2 +521,3 @@ */

*
* @private
* @return {Session} The current {@link Session} instance.

@@ -518,2 +531,4 @@ */

* Adds the required backenddata about this {@link Model} to the update object.
*
* @private
* @param {Object} update - the update to add.

@@ -638,6 +653,7 @@ */

/**
* Returns a Model instance for the object with id `id`.
* Returns a {@link Model} instance for the object with id `id`.
*
* @param {*} id - the `id` of the object to get
* @throws If object with id `id` doesn't exist
* @return {Model} `Model` instance with id `id`
* @return {Model} {@link Model} instance with id `id`
*/

@@ -648,6 +664,9 @@ }, {

var ModelClass = this;
var ref = this.accessId(id);
if (typeof ref === 'undefined') {
if (!this.hasId(id)) {
throw new Error(this.modelName + ' instance with id ' + id + ' not found');
}
var ref = this.accessId(id);
return new ModelClass(ref);

@@ -657,2 +676,19 @@ }

/**
* Returns a boolean indicating if an entity with the id `id` exists
* in the state.
*
* @param {*} id - a value corresponding to the id attribute of the {@link Model} class.
* @return {Boolean} a boolean indicating if entity with `id` exists in the state
*/
}, {
key: 'hasId',
value: function hasId(id) {
var ref = this.accessId(id);
if (typeof ref === 'undefined') return false;
return true;
}
/**
* Gets the {@link Model} instance that matches properties in `lookupObj`.

@@ -659,0 +695,0 @@ * Throws an error if {@link Model} is not found.

@@ -37,2 +37,3 @@ 'use strict';

* Creates a QuerySet.
*
* @param {Model} modelClass - the model class of objects in this QuerySet.

@@ -105,3 +106,3 @@ * @param {number[]} idArr - an array of the id's this QuerySet includes.

/**
* Returns an array of the Model instances represented by the QuerySet.
* Returns an array of Model instances represented by the QuerySet.
* @return {Model[]} model instances represented by the QuerySet

@@ -121,2 +122,3 @@ */

* Returns the number of model instances represented by the QuerySet.
*
* @return {number} length of the QuerySet

@@ -131,4 +133,5 @@ */

/**
* Checks if QuerySet has any objects.
* @return {Boolean} `true` if QuerySet contains entities, else `false`.
* Checks if the {@link QuerySet} instance has any entities.
*
* @return {Boolean} `true` if the {@link QuerySet} instance contains entities, else `false`.
*/

@@ -142,8 +145,9 @@ }, {

/**
* Returns the {@link Model} instance at index `index` in the QuerySet if
* Returns the {@link Model} instance at index `index` in the {@link QuerySet} instance if
* `withRefs` flag is set to `false`, or a reference to the plain JavaScript
* object in the model state if `true`.
*
* @param {number} index - index of the model instance to get
* @return {Model|Object} a {@link Model} instance or a plain JavaScript
* object at index `index` in the QuerySet
* object at index `index` in the {@link QuerySet} instance
*/

@@ -160,3 +164,3 @@ }, {

/**
* Returns the {@link Model} instance at index 0 in the QuerySet.
* Returns the {@link Model} instance at index 0 in the {@link QuerySet} instance.
* @return {Model}

@@ -181,4 +185,4 @@ */

/**
* Returns a new QuerySet with the same objects.
* @return {QuerySet} a new QuerySet with the same objects.
* Returns a new {@link QuerySet} instance with the same entities.
* @return {QuerySet} a new QuerySet with the same entities.
*/

@@ -192,6 +196,6 @@ }, {

/**
* Returns a new {@link QuerySet} with objects that match properties in `lookupObj`.
* Returns a new {@link QuerySet} instance with entities that match properties in `lookupObj`.
*
* @param {Object} lookupObj - the properties to match objects with.
* @return {QuerySet} a new {@link QuerySet} with objects that passed the filter.
* @return {QuerySet} a new {@link QuerySet} instance with objects that passed the filter.
*/

@@ -205,6 +209,6 @@ }, {

/**
* Returns a new {@link QuerySet} with objects that do not match properties in `lookupObj`.
* Returns a new {@link QuerySet} instance with entities that do not match properties in `lookupObj`.
*
* @param {Object} lookupObj - the properties to unmatch objects with.
* @return {QuerySet} a new {@link QuerySet} with objects that passed the filter.
* @return {QuerySet} a new {@link QuerySet} instance with objects that passed the filter.
*/

@@ -252,5 +256,5 @@ }, {

/**
* Calls `func` for each object in the QuerySet.
* Calls `func` for each object in the {@link QuerySet} instance.
* The object is either a reference to the plain
* object in the database or a Model instance, depending
* object in the database or a {@link Model} instance, depending
* on the flag.

@@ -270,3 +274,3 @@ *

/**
* Maps the {@link Model} instances in the {@link QuerySet}.
* Maps the {@link Model} instances in the {@link QuerySet} instance.
* @param {Function} func - the mapping function that takes one argument, a

@@ -289,6 +293,15 @@ * {@link Model} instance or a reference to the plain

/**
* Returns a new {@link QuerySet} with objects ordered by `fieldNames` in ascending
* order.
* @param {string[]} fieldNames - the property names to order by.
* @return {QuerySet} a new {@link QuerySet} with objects ordered by `fieldNames`.
* Returns a new {@link QuerySet} instance with entities ordered by `iteratees` in ascending
* order, unless otherwise specified. Delegates to `lodash.sortByOrder`.
*
* @param {string[]|Function[]} iteratees - an array where each item can be a string or a
* function. If a string is supplied, it should
* correspond to property on the entity that will
* determine the order. If a function is supplied,
* it should return the value to order by.
* @param {Boolean[]} [orders] - the sort orders of `iteratees`. If unspecified, all iteratees
* will be sorted in ascending order. `true` and `'asc'`
* correspond to ascending order, and `false` and `'desc`
* to descending order.
* @return {QuerySet} a new {@link QuerySet} with objects ordered by `iteratees`.
*/

@@ -327,3 +340,5 @@ }, {

/**
* Records an update specified with `mergeObj` to all the objects in the {@link QuerySet}.
* Records an update specified with `mergeObj` to all the objects
* in the {@link QuerySet} instance.
*
* @param {Object} mergeObj - an object to merge with all the objects in this

@@ -346,3 +361,3 @@ * queryset.

/**
* Records a deletion of all the objects in this {@link QuerySet}.
* Records a deletion of all the objects in this {@link QuerySet} instance.
* @return {undefined}

@@ -349,0 +364,0 @@ */

@@ -66,12 +66,12 @@ 'use strict';

/**
* Defines a Model class with the provided options and registers
* Defines a {@link Model} class with the provided options and registers
* it to the schema instance.
*
* Note that you can also define Model classes by yourself
* Note that you can also define {@link Model} classes by yourself
* with ES6 classes.
*
* @param {string} modelName - the name of the model class
* @param {string} modelName - the name of the {@link Model} class
* @param {Object} [relatedFields] - a dictionary of `fieldName: fieldInstance`
* @param {Function} [reducer] - the reducer function to use for this model
* @param {Object} [backendOpts] -Backend options for this model.
* @param {Object} [backendOpts] - {@link Backend} options for this model.
* @return {Model} The defined model class.

@@ -109,3 +109,3 @@ */

/**
* Registers a model class to the schema.
* Registers a {@link Model} class to the schema.
*

@@ -116,3 +116,3 @@ * If the model has declared any ManyToMany fields, their

*
* @param {...Model} model - a model to register
* @param {...Model} model - a {@link Model} class to register
* @return {undefined}

@@ -178,6 +178,6 @@ */

/**
* Gets a model by its name from the registry.
* @param {string} modelName - the name of the model to get
* @throws If model is not found.
* @return {Model} the model class, if found
* Gets a {@link Model} class by its name from the registry.
* @param {string} modelName - the name of the {@link Model} class to get
* @throws If {@link Model} class is not found.
* @return {Model} the {@link Model} class, if found
*/

@@ -196,4 +196,4 @@ }, {

}, {
key: '_getModelClasses',
value: function _getModelClasses() {
key: 'getModelClasses',
value: function getModelClasses() {
this._setupModelPrototypes();

@@ -299,3 +299,3 @@ return this.registry.concat(this.implicitThroughModels);

/**
* Returns the default state
* Returns the default state.
* @return {Object} the default state

@@ -306,3 +306,3 @@ */

value: function getDefaultState() {
var models = this._getModelClasses();
var models = this.getModelClasses();
var state = {};

@@ -316,7 +316,7 @@ models.forEach(function (modelClass) {

/**
* Begins a database {@link Session}.
* Begins an immutable database session.
*
* @param {Object} state - the state the database manages
* @param {Object} [action] - the dispatched action object
* @return {Session} a new session instance
* @return {Session} a new {@link Session} instance
*/

@@ -326,8 +326,16 @@ }, {

value: function from(state, action) {
return new _Session2['default'](this._getModelClasses(), state, action);
return new _Session2['default'](this, state, action);
}
/**
* Begins a mutable database session.
*
* @param {Object} state - the state the database manages
* @param {Object} [action] - the dispatched action object
* @return {Session} a new {@link Session} instance
*/
}, {
key: 'withMutations',
value: function withMutations(state) {
return new _Session2['default'](this._getModelClasses(), state, undefined, true);
value: function withMutations(state, action) {
return new _Session2['default'](this, state, action, true);
}

@@ -381,3 +389,3 @@

* return session.Book.map(book => {
* return Object.assign(book.toPlain(), {
* return Object.assign({}, book.ref, {
* authors: book.authors.map(author => author.name),

@@ -384,0 +392,0 @@ * genres: book.genres.map(genre => genre.name),

@@ -27,3 +27,3 @@ 'use strict';

*
* @param {Schema} schema - a Schema instance
* @param {Schema} schema - a {@link Schema} instance
* @param {Object} state - the database state

@@ -35,3 +35,3 @@ * @param {Object} [action] - the current action in the dispatch cycle.

function Session(models, state, action, withMutations) {
function Session(schema, state, action, withMutations) {
var _this = this;

@@ -41,4 +41,4 @@

this.models = models;
this.state = state;
this.schema = schema;
this.state = state || schema.getDefaultState();
this.action = action;

@@ -52,3 +52,5 @@ this.withMutations = !!withMutations;

models.forEach(function (modelClass) {
this.models = schema.getModelClasses();
this.models.forEach(function (modelClass) {
Object.defineProperty(_this, modelClass.modelName, {

@@ -81,2 +83,4 @@ get: function get() {

* Records an update to the session.
*
* @private
* @param {Object} update - the update object. Must have keys

@@ -93,7 +97,6 @@ * `type`, `payload` and `meta`. `meta`

var modelState = this.getState(modelName);
var state = modelState || this[modelName].getDefaultState();
// The backend used in the updateReducer
// will mutate the model state.
this[modelName].updateReducer(state, update);
this[modelName].updateReducer(modelState, update);
} else {

@@ -106,4 +109,5 @@ this.updates.push(update);

* Gets the recorded updates for `modelClass` and
* deletes them from the Session instance updates list.
* deletes them from the {@link Session} instance updates list.
*
* @private
* @param {Model} modelClass - the model class to get updates for

@@ -125,21 +129,28 @@ * @return {Object[]} A list of the user-recorded updates for `modelClass`.

}
/**
* Returns the current state for a model with name `modelName`.
*
* @private
* @param {string} modelName - the name of the model to get state for.
* @return {*} The state for model with name `modelName`.
*/
}, {
key: 'getState',
value: function getState(modelName) {
if (this.state) {
return this.state[modelName];
}
return undefined;
return this.state[modelName];
}
/**
* Calls the user defined reducers and returns
* the next state.
* If the session uses mutations, just returns the state.
*
* Applies recorded updates and returns the next state.
* @param {Object} [opts] - Options object
* @param {Boolean} [opts.runReducers] - A boolean indicating if the user-defined
* model reducers should be run. If not specified,
* is set to `true` if an action object was specified
* on session instantiation, otherwise `false`.
* @return {Object} The next state
*/
}, {
key: 'reduce',
value: function reduce() {
key: 'getNextState',
value: function getNextState(userOpts) {
var _this2 = this;

@@ -149,12 +160,24 @@

var prevState = this.state || {};
var prevState = this.state;
var action = this.action;
var opts = userOpts || {};
// If the session does not have a specified action object,
// don't run the user-defined model reducers unless
// explicitly specified.
var runReducers = opts.hasOwnProperty('runReducers') ? opts.runReducers : !!action;
var nextState = this.models.reduce(function (_nextState, modelClass) {
var modelState = _this2.getState(modelClass.modelName);
var nextModelState = modelClass.reducer(modelState, action, modelClass, _this2);
var nextModelState = undefined;
if (runReducers) {
nextModelState = modelClass.reducer(modelState, action, modelClass, _this2);
}
if (typeof nextModelState === 'undefined') {
// If nothing was returned from the reducer,
// use the return value of getNextState.
// If the reducer wasn't run or it didn't
// return the next state,
// we get the next state manually.
nextModelState = modelClass.getNextState();

@@ -202,3 +225,16 @@ }

}
/**
* Calls the user-defined reducers and returns the next state.
* If the session uses mutations, just returns the state.
* Delegates to {@link Session#getNextState}
*
* @return {Object} the next state
*/
}, {
key: 'reduce',
value: function reduce() {
return this.getNextState({ runReducers: true });
}
}, {
key: 'accessedModels',

@@ -205,0 +241,0 @@ get: function get() {

@@ -56,6 +56,15 @@ 'use strict';

it('Models correctly create new instances', function () {
it('Models correctly indicate if id exists', function () {
var _session = session;
var Book = _session.Book;
(0, _chai.expect)(Book.hasId(0)).to.be['true'];
(0, _chai.expect)(Book.hasId(92384)).to.be['false'];
(0, _chai.expect)(Book.hasId()).to.be['false'];
});
it('Models correctly create new instances', function () {
var _session2 = session;
var Book = _session2.Book;
(0, _chai.expect)(session.updates).to.have.length(0);

@@ -75,4 +84,4 @@ var book = Book.create({

it('Model.create throws if passing duplicate ids to many-to-many field', function () {
var _session2 = session;
var Book = _session2.Book;
var _session3 = session;
var Book = _session3.Book;

@@ -92,4 +101,4 @@ var newProps = {

it('Models are correctly deleted', function () {
var _session3 = session;
var Book = _session3.Book;
var _session4 = session;
var Book = _session4.Book;

@@ -106,4 +115,4 @@ (0, _chai.expect)(Book.count()).to.equal(3);

it('Models correctly update when setting properties', function () {
var _session4 = session;
var Book = _session4.Book;
var _session5 = session;
var Book = _session5.Book;

@@ -122,4 +131,4 @@ var book = Book.first();

it('withId throws if model instance not found', function () {
var _session5 = session;
var Book = _session5.Book;
var _session6 = session;
var Book = _session6.Book;

@@ -132,5 +141,5 @@ (0, _chai.expect)(function () {

it('many-to-many relationship descriptors work', function () {
var _session6 = session;
var Book = _session6.Book;
var Genre = _session6.Genre;
var _session7 = session;
var Book = _session7.Book;
var Genre = _session7.Genre;

@@ -152,5 +161,5 @@ // Forward (from many-to-many field declaration)

it('adding related many-to-many entities works', function () {
var _session7 = session;
var Book = _session7.Book;
var Genre = _session7.Genre;
var _session8 = session;
var Book = _session8.Book;
var Genre = _session8.Genre;

@@ -168,4 +177,4 @@ var book = Book.withId(0);

it('trying to add existing related many-to-many entities throws', function () {
var _session8 = session;
var Book = _session8.Book;
var _session9 = session;
var Book = _session9.Book;

@@ -181,5 +190,5 @@ var book = Book.withId(0);

it('removing related many-to-many entities works', function () {
var _session9 = session;
var Book = _session9.Book;
var Genre = _session9.Genre;
var _session10 = session;
var Book = _session10.Book;
var Genre = _session10.Genre;

@@ -197,4 +206,4 @@ var book = Book.withId(0);

it('trying to remove unexisting related many-to-many entities throws', function () {
var _session10 = session;
var Book = _session10.Book;
var _session11 = session;
var Book = _session11.Book;

@@ -210,4 +219,4 @@ var book = Book.withId(0);

it('clearing related many-to-many entities works', function () {
var _session11 = session;
var Book = _session11.Book;
var _session12 = session;
var Book = _session12.Book;

@@ -225,5 +234,5 @@ var book = Book.withId(0);

it('foreign key relationship descriptors work', function () {
var _session12 = session;
var Book = _session12.Book;
var Author = _session12.Author;
var _session13 = session;
var Book = _session13.Book;
var Author = _session13.Author;

@@ -245,5 +254,5 @@ // Forward

it('one-to-one relationship descriptors work', function () {
var _session13 = session;
var Book = _session13.Book;
var Cover = _session13.Cover;
var _session14 = session;
var Book = _session14.Book;
var Cover = _session14.Cover;

@@ -250,0 +259,0 @@ // Forward

@@ -86,3 +86,3 @@ 'use strict';

expect(function () {
return schema._getModelClasses();
return schema.getModelClasses();
}).to['throw'](/field/);

@@ -126,3 +126,3 @@ });

expect(function () {
return schema._getModelClasses();
return schema.getModelClasses();
}).to['throw'](/field/);

@@ -166,3 +166,3 @@ });

expect(function () {
return schema._getModelClasses();
return schema.getModelClasses();
}).to['throw'](/field/);

@@ -169,0 +169,0 @@ });

@@ -7,2 +7,12 @@ 'use strict';

var _chai2 = _interopRequireDefault(_chai);
var _sinon = require('sinon');
var _sinon2 = _interopRequireDefault(_sinon);
var _sinonChai = require('sinon-chai');
var _sinonChai2 = _interopRequireDefault(_sinonChai);
var _Schema = require('../Schema');

@@ -14,2 +24,7 @@

var _constants = require('../constants');
_chai2['default'].use(_sinonChai2['default']);
var expect = _chai2['default'].expect;
describe('Session', function () {

@@ -36,13 +51,13 @@ var schema = undefined;

it('connects models', function () {
(0, _chai.expect)(Book.session).to.be.undefined;
(0, _chai.expect)(Cover.session).to.be.undefined;
(0, _chai.expect)(Genre.session).to.be.undefined;
(0, _chai.expect)(Cover.session).to.be.undefined;
expect(Book.session).to.be.undefined;
expect(Cover.session).to.be.undefined;
expect(Genre.session).to.be.undefined;
expect(Cover.session).to.be.undefined;
var session = schema.from(defaultState);
(0, _chai.expect)(Book.session).to.equal(session);
(0, _chai.expect)(Cover.session).to.equal(session);
(0, _chai.expect)(Genre.session).to.equal(session);
(0, _chai.expect)(Cover.session).to.equal(session);
expect(Book.session).to.equal(session);
expect(Cover.session).to.equal(session);
expect(Genre.session).to.equal(session);
expect(Cover.session).to.equal(session);
});

@@ -52,6 +67,6 @@

var session = schema.from(defaultState);
(0, _chai.expect)(session.Book).to.equal(Book);
(0, _chai.expect)(session.Author).to.equal(Author);
(0, _chai.expect)(session.Cover).to.equal(Cover);
(0, _chai.expect)(session.Genre).to.equal(Genre);
expect(session.Book).to.equal(Book);
expect(session.Author).to.equal(Author);
expect(session.Cover).to.equal(Cover);
expect(session.Genre).to.equal(Genre);
});

@@ -61,12 +76,12 @@

var session = schema.from(defaultState);
(0, _chai.expect)(session.accessedModels).to.have.length(0);
expect(session.accessedModels).to.have.length(0);
session.markAccessed(Book);
(0, _chai.expect)(session.accessedModels).to.have.length(1);
(0, _chai.expect)(session.accessedModels[0]).to.equal('Book');
expect(session.accessedModels).to.have.length(1);
expect(session.accessedModels[0]).to.equal('Book');
session.markAccessed(Book);
(0, _chai.expect)(session.accessedModels[0]).to.equal('Book');
expect(session.accessedModels[0]).to.equal('Book');
});

@@ -76,8 +91,84 @@

var session = schema.from(defaultState);
(0, _chai.expect)(session.updates).to.have.length(0);
expect(session.updates).to.have.length(0);
var updateObj = {};
session.addUpdate(updateObj);
(0, _chai.expect)(session.updates).to.have.length(1);
(0, _chai.expect)(session.updates[0]).to.equal(updateObj);
expect(session.updates).to.have.length(1);
expect(session.updates[0]).to.equal(updateObj);
});
describe('gets the next state', function () {
it('without any updates, the same state is returned', function () {
var session = schema.from(defaultState);
var nextState = session.getNextState();
expect(nextState).to.equal(defaultState);
});
it('with updates, a new state is returned', function () {
var session = schema.from(defaultState);
session.updates.push({
type: _constants.CREATE,
meta: {
name: Author.modelName
},
payload: {
name: 'Caesar'
}
});
var nextState = session.getNextState();
expect(nextState).to.not.equal(defaultState);
expect(nextState[Author.modelName]).to.not.equal(defaultState[Author.modelName]);
// All other model states should stay equal.
expect(nextState[Book.modelName]).to.equal(defaultState[Book.modelName]);
expect(nextState[Cover.modelName]).to.equal(defaultState[Cover.modelName]);
expect(nextState[Genre.modelName]).to.equal(defaultState[Genre.modelName]);
});
it('runs reducers if explicitly specified', function () {
var session = schema.from(defaultState);
var authorReducerSpy = _sinon2['default'].spy(Author, 'reducer');
var bookReducerSpy = _sinon2['default'].spy(Book, 'reducer');
var coverReducerSpy = _sinon2['default'].spy(Cover, 'reducer');
var genreReducerSpy = _sinon2['default'].spy(Genre, 'reducer');
session.getNextState({ runReducers: true });
expect(authorReducerSpy).to.be.calledOnce;
expect(bookReducerSpy).to.be.calledOnce;
expect(coverReducerSpy).to.be.calledOnce;
expect(genreReducerSpy).to.be.calledOnce;
});
it('doesn\'t run reducers if explicitly specified', function () {
var session = schema.from(defaultState);
var authorReducerSpy = _sinon2['default'].spy(Author, 'reducer');
var bookReducerSpy = _sinon2['default'].spy(Book, 'reducer');
var coverReducerSpy = _sinon2['default'].spy(Cover, 'reducer');
var genreReducerSpy = _sinon2['default'].spy(Genre, 'reducer');
session.getNextState({ runReducers: false });
expect(authorReducerSpy).not.to.be.called;
expect(bookReducerSpy).not.to.be.called;
expect(coverReducerSpy).not.to.be.called;
expect(genreReducerSpy).not.to.be.called;
});
});
it('reduce calls getNextState with correct arguments', function () {
var session = schema.from(defaultState);
var getNextStateSpy = _sinon2['default'].spy(session, 'getNextState');
session.reduce();
expect(getNextStateSpy).to.be.calledOnce;
expect(getNextStateSpy).to.be.calledWithMatch({ runReducers: true });
});
});

@@ -57,2 +57,4 @@ 'use strict';

* the corresponding properties in `entity`.
*
* @private
* @param {Object} lookupObj - properties to match against

@@ -130,2 +132,3 @@ * @param {Object} entity - object to match

*
* @private
* @param {string} declarationModelName - the name of the model the many-to-many relation was declared on

@@ -145,2 +148,3 @@ * @param {string} fieldName - the field name where the many-to-many relation was declared on

*
* @private
* @param {string} declarationModelName - the name of the model where the relation was declared

@@ -159,2 +163,3 @@ * @return {string} the field name in the through model for `declarationModelName`'s foreign key.

*
* @private
* @param {string} otherModelName - the name of the model that was the target of the many-to-many

@@ -227,2 +232,3 @@ * declaration.

*
* @private
* @param {*} entity - either a Model instance or an id value

@@ -249,2 +255,3 @@ * @return {*} the id value of `entity`

*
* @private
* @param {Object} target - the object to update

@@ -285,2 +292,20 @@ * @param {Object} source - the updated props

function objectShallowEquals(a, b) {
var keysInA = 0;
var keysInB = 0;
(0, _lodashObjectForOwn2['default'])(a, function (value, key) {
if (!b.hasOwnProperty(key) || b[key] !== value) {
return false;
}
keysInA++;
});
for (var key in b) {
if (b.hasOwnProperty(key)) keysInB++;
}
return keysInA === keysInB;
}
exports.match = match;

@@ -296,2 +321,3 @@ exports.attachQuerySetMethods = attachQuerySetMethods;

exports.arrayDiffActions = arrayDiffActions;
exports.reverseFieldErrorMessage = reverseFieldErrorMessage;
exports.reverseFieldErrorMessage = reverseFieldErrorMessage;
exports.objectShallowEquals = objectShallowEquals;
{
"name": "redux-orm",
"version": "0.4.0",
"version": "0.5.0",
"description": "Simple ORM to manage and query your state trees",

@@ -5,0 +5,0 @@ "main": "lib/index.js",

redux-orm
===============
A small, simple and immutable ORM to manage data in your Redux store.
A small, simple and immutable ORM to manage relational data in your Redux store.
Not ready for production.
See a [a guide to creating a simple app with Redux-ORM](https://github.com/tommikaikkonen/redux-orm-primer) (includes the source).
See an [example app using redux-orm](https://github.com/tommikaikkonen/redux-orm-example).
API can be unstable until 1.0.0. Minor version bumps before 1.0.0 can and will introduce breaking changes. They will be noted in the [changelog](https://github.com/tommikaikkonen/redux-orm#changelog).

@@ -152,3 +152,3 @@ [![npm version](https://img.shields.io/npm/v/redux-orm.svg?style=flat-square)](https://www.npmjs.com/package/redux-orm)

```javascript
```jsx
// components.js

@@ -267,2 +267,3 @@ import PureComponent from 'react-pure-render/component';

- `hasId(id)`: returns a boolean indicating if entity with id `id` exists in the state.
- `withId(id)`: gets the Model instance with id `id`.

@@ -279,5 +280,6 @@ - `get(matchObj)`: to get a Model instance based on matching properties in `matchObj`,

- `set`: marks a supplied `propertyName` to be updated to `value` at `Model.getNextState`. Returns `undefined`. Is equivalent to normal assignment.
- `update`: marks a supplied object of property names and values to be merged with the Model instance at `Model.getNextState()`. Returns `undefined`.
- `delete`: marks the Model instance to be deleted at `Model.getNextState()`. Returns `undefined`.
- `equals(otherModel)`: returns a boolean indicating equality with `otherModel`. Equality is determined by shallow comparison of both model's attributes.
- `set(propertyName, value)`: marks a supplied `propertyName` to be updated to `value` at `Model.getNextState`. Returns `undefined`. Is equivalent to normal assignment.
- `update(mergeObj)`: marks a supplied object of property names and values (`mergeObj`) to be merged with the Model instance at `Model.getNextState()`. Returns `undefined`.
- `delete()`: marks the Model instance to be deleted at `Model.getNextState()`. Returns `undefined`.

@@ -379,4 +381,8 @@ **Subclassing**:

**Instance properties**:
You can access all the registered Models in the schema for querying and updates as properties of this instance. For example, given a schema with `Book` and `Author` models,
- `getNextState(opts)`: applies all the recorded updates in the session and returns the next state. You may pass options with the `opts` object.
- `reduce()`: calls model-specific reducers and returns the next state.
Additionally, you can access all the registered Models in the schema for querying and updates as properties of this instance. For example, given a schema with `Book` and `Author` models,
```javascript

@@ -412,2 +418,15 @@ const session = schema.from(state, action);

### 0.5.0
**Breaking changes**:
- Model instance method `equals(otherModel)` now checks if the two model's attributes are shallow equal. Previously, it checked if the id's and model classes are equal.
- Session constructor now receives a Schema instance as its first argument, instead of an array of Model classes (this only affects you if you're manually instantiating Sessions with the `new` operator).
Other changes:
- Added `hasId` static method to the Model class. It tests for the existence of the supplied id in the model's state.
- Added instance method `getNextState` to the Session class. This enables you to get the next state without running model-reducers. Useful if you're bootstrapping data, writing tests, or otherwise operating on the data outside reducers. You can pass an options object that currently accepts a `runReducers` key. It's value indicates if reducers should be run or not.
- Improved API documentation.
### 0.4.0

@@ -414,0 +433,0 @@

@@ -12,2 +12,15 @@ import find from 'lodash/collection/find';

* @param {Object} userOpts - options to use.
* @param {string} [userOpts.idAttribute=id] - the id attribute of the entity.
* @param {Boolean} [userOpts.indexById=true] - a boolean indicating if the entities
* should be indexed by `idAttribute`.
* @param {string} [userOpts.arrName=items] - the state attribute where an array of
* either entity id's (if `indexById === true`)
* or the entity objects are stored.
* @param {string} [userOpts.mapName=itemsById] - if `indexById === true`, the state
* attribute where the entity objects
* are stored in a id to entity object
* map.
* @param {Boolean} [userOpts.withMutations=false] - a boolean indicating if the backend
* operations should be executed
* with or without mutations.
*/

@@ -14,0 +27,0 @@ constructor(userOpts) {

@@ -20,4 +20,25 @@ import values from 'lodash/object/values';

*
* On each run, the memoizer check
* Memoization algorithm operates like this:
*
* 1. Has the selector been run before? If not, go to 5.
*
* 2. If the selector has other input selectors in addition to the
* ORM state selector, check their results for equality with the previous results.
* If they aren't equal, go to 5.
*
* 3. Is the ORM state referentially equal to the previous ORM state the selector
* was called with? If yes, return the previous result.
*
* 4. Check which Model's states the selector has accessed on previous runs.
* Check for equality with each of those states versus their states in the
* previous ORM state. If all of them are equal, return the previous result.
*
* 5. Run the selector. Check the Session object used by the selector for
* which Model's states were accessed, and merge them with the previously
* saved information about accessed models (if-else branching can change
* which models are accessed on different inputs). Save the ORM state and
* other arguments the selector was called with, overriding previously
* saved values. Save the selector result. Return the selector result.
*
* @private
* @param {Function} func - function to memoize

@@ -24,0 +45,0 @@ * @param {Function} equalityCheck - equality check function to use with normal

@@ -13,3 +13,3 @@ import forOwn from 'lodash/object/forOwn';

} from './fields';
import {CREATE, UPDATE, DELETE, ORDER} from './constants';
import { CREATE, UPDATE, DELETE } from './constants';
import {

@@ -19,2 +19,3 @@ match,

arrayDiffActions,
objectShallowEquals,
} from './utils';

@@ -79,2 +80,6 @@

* Returns the options object passed to the {@link Backend} class constructor.
* By default, returns an empty object (which means the {@link Backend} instance
* will use default options). You can either override this function to return the options
* you want to use, or assign the options object as a static property to the
* Model class.
*

@@ -84,5 +89,3 @@ * @return {Object} the options object used to instantiate a {@link Backend} class.

static backend() {
return {
branchName: this.modelName,
};
return {};
}

@@ -94,6 +97,2 @@

}
if (typeof this.backend === 'undefined') {
throw new Error(`You must declare either a 'backend' class method or
a 'backend' class variable in your Model Class`);
}
return this.backend;

@@ -120,2 +119,4 @@ }

* Gets the {@link Backend} instance linked to this {@link Model}.
*
* @private
* @return {Backend} The {@link Backend} instance linked to this {@link Model}.

@@ -145,11 +146,7 @@ */

* updates.
*
* @private
* @return {Object} The next state.
*/
static getNextState() {
if (typeof this.state === 'undefined') {
return this.getDefaultState();
}
const updates = this.session.getUpdatesFor(this);
let state;

@@ -162,2 +159,4 @@ if (this._sessionData.hasOwnProperty('nextState')) {

const updates = this.session.getUpdatesFor(this);
if (updates.length > 0) {

@@ -177,2 +176,3 @@ const nextState = updates.reduce(this.updateReducer.bind(this), state);

*
* @private
* @param {Object} state - the Model's state

@@ -190,4 +190,2 @@ * @param {Object} action - the internal redux-orm update action to apply

return backend.update(state, action.payload.idArr, action.payload.mergeObj);
case ORDER:
return backend.order(state, action.payload);
case DELETE:

@@ -217,2 +215,4 @@ return backend.delete(state, action.payload);

* Delegates to a {@link Backend} instance.
*
* @private
* @return {Object} The default state.

@@ -272,2 +272,3 @@ */

*
* @private
* @param {Session} session - The session to connect to.

@@ -285,2 +286,3 @@ */

*
* @private
* @return {Session} The current {@link Session} instance.

@@ -295,2 +297,4 @@ */

* Adds the required backenddata about this {@link Model} to the update object.
*
* @private
* @param {Object} update - the update to add.

@@ -409,13 +413,17 @@ */

/**
* Returns a Model instance for the object with id `id`.
* Returns a {@link Model} instance for the object with id `id`.
*
* @param {*} id - the `id` of the object to get
* @throws If object with id `id` doesn't exist
* @return {Model} `Model` instance with id `id`
* @return {Model} {@link Model} instance with id `id`
*/
static withId(id) {
const ModelClass = this;
const ref = this.accessId(id);
if (typeof ref === 'undefined') {
if (!this.hasId(id)) {
throw new Error(`${this.modelName} instance with id ${id} not found`);
}
const ref = this.accessId(id);
return new ModelClass(ref);

@@ -425,2 +433,17 @@ }

/**
* Returns a boolean indicating if an entity with the id `id` exists
* in the state.
*
* @param {*} id - a value corresponding to the id attribute of the {@link Model} class.
* @return {Boolean} a boolean indicating if entity with `id` exists in the state
*/
static hasId(id) {
const ref = this.accessId(id);
if (typeof ref === 'undefined') return false;
return true;
}
/**
* Gets the {@link Model} instance that matches properties in `lookupObj`.

@@ -475,3 +498,3 @@ * Throws an error if {@link Model} is not found.

/**
* Gets the id value of the current instance.
* Gets the id value of the current instance by looking up the id attribute.
* @return {*} The id value of the current instance.

@@ -495,2 +518,3 @@ */

* Returns a string representation of the {@link Model} instance.
*
* @return {string} A string representation of this {@link Model} instance.

@@ -507,4 +531,11 @@ */

/**
* Returns a boolean indicating if `otherModel` equals this {@link Model} instance.
* Equality is determined by shallow comparing their attributes.
*
* @param {Model} otherModel - a {@link Model} instance to compare
* @return {Boolean} a boolean indicating if the {@link Model} instance's are equal.
*/
equals(otherModel) {
return this.getClass() === otherModel.getClass() && this.getId() === otherModel.getId();
return objectShallowEquals(this._fields, otherModel._fields);
}

@@ -515,2 +546,3 @@

* field value assignment.
*
* @param {string} propertyName - name of the property to set

@@ -527,2 +559,3 @@ * @param {*} value - value assigned to the property

* If the session is with mutations, updates the instance to reflect the new values.
*
* @param {Object} userMergeObj - an object that will be merged with this instance.

@@ -529,0 +562,0 @@ * @return {undefined}

@@ -20,2 +20,3 @@ import reject from 'lodash/collection/reject';

* Creates a QuerySet.
*
* @param {Model} modelClass - the model class of objects in this QuerySet.

@@ -107,3 +108,3 @@ * @param {number[]} idArr - an array of the id's this QuerySet includes.

/**
* Returns an array of the Model instances represented by the QuerySet.
* Returns an array of Model instances represented by the QuerySet.
* @return {Model[]} model instances represented by the QuerySet

@@ -119,2 +120,3 @@ */

* Returns the number of model instances represented by the QuerySet.
*
* @return {number} length of the QuerySet

@@ -127,4 +129,5 @@ */

/**
* Checks if QuerySet has any objects.
* @return {Boolean} `true` if QuerySet contains entities, else `false`.
* Checks if the {@link QuerySet} instance has any entities.
*
* @return {Boolean} `true` if the {@link QuerySet} instance contains entities, else `false`.
*/

@@ -136,8 +139,9 @@ exists() {

/**
* Returns the {@link Model} instance at index `index` in the QuerySet if
* Returns the {@link Model} instance at index `index` in the {@link QuerySet} instance if
* `withRefs` flag is set to `false`, or a reference to the plain JavaScript
* object in the model state if `true`.
*
* @param {number} index - index of the model instance to get
* @return {Model|Object} a {@link Model} instance or a plain JavaScript
* object at index `index` in the QuerySet
* object at index `index` in the {@link QuerySet} instance
*/

@@ -152,3 +156,3 @@ at(index) {

/**
* Returns the {@link Model} instance at index 0 in the QuerySet.
* Returns the {@link Model} instance at index 0 in the {@link QuerySet} instance.
* @return {Model}

@@ -169,4 +173,4 @@ */

/**
* Returns a new QuerySet with the same objects.
* @return {QuerySet} a new QuerySet with the same objects.
* Returns a new {@link QuerySet} instance with the same entities.
* @return {QuerySet} a new QuerySet with the same entities.
*/

@@ -178,6 +182,6 @@ all() {

/**
* Returns a new {@link QuerySet} with objects that match properties in `lookupObj`.
* Returns a new {@link QuerySet} instance with entities that match properties in `lookupObj`.
*
* @param {Object} lookupObj - the properties to match objects with.
* @return {QuerySet} a new {@link QuerySet} with objects that passed the filter.
* @return {QuerySet} a new {@link QuerySet} instance with objects that passed the filter.
*/

@@ -189,6 +193,6 @@ filter(lookupObj) {

/**
* Returns a new {@link QuerySet} with objects that do not match properties in `lookupObj`.
* Returns a new {@link QuerySet} instance with entities that do not match properties in `lookupObj`.
*
* @param {Object} lookupObj - the properties to unmatch objects with.
* @return {QuerySet} a new {@link QuerySet} with objects that passed the filter.
* @return {QuerySet} a new {@link QuerySet} instance with objects that passed the filter.
*/

@@ -229,5 +233,5 @@ exclude(lookupObj) {

/**
* Calls `func` for each object in the QuerySet.
* Calls `func` for each object in the {@link QuerySet} instance.
* The object is either a reference to the plain
* object in the database or a Model instance, depending
* object in the database or a {@link Model} instance, depending
* on the flag.

@@ -247,3 +251,3 @@ *

/**
* Maps the {@link Model} instances in the {@link QuerySet}.
* Maps the {@link Model} instances in the {@link QuerySet} instance.
* @param {Function} func - the mapping function that takes one argument, a

@@ -262,6 +266,15 @@ * {@link Model} instance or a reference to the plain

/**
* Returns a new {@link QuerySet} with objects ordered by `fieldNames` in ascending
* order.
* @param {string[]} fieldNames - the property names to order by.
* @return {QuerySet} a new {@link QuerySet} with objects ordered by `fieldNames`.
* Returns a new {@link QuerySet} instance with entities ordered by `iteratees` in ascending
* order, unless otherwise specified. Delegates to `lodash.sortByOrder`.
*
* @param {string[]|Function[]} iteratees - an array where each item can be a string or a
* function. If a string is supplied, it should
* correspond to property on the entity that will
* determine the order. If a function is supplied,
* it should return the value to order by.
* @param {Boolean[]} [orders] - the sort orders of `iteratees`. If unspecified, all iteratees
* will be sorted in ascending order. `true` and `'asc'`
* correspond to ascending order, and `false` and `'desc`
* to descending order.
* @return {QuerySet} a new {@link QuerySet} with objects ordered by `iteratees`.
*/

@@ -296,3 +309,5 @@ orderBy(iteratees, orders) {

/**
* Records an update specified with `mergeObj` to all the objects in the {@link QuerySet}.
* Records an update specified with `mergeObj` to all the objects
* in the {@link QuerySet} instance.
*
* @param {Object} mergeObj - an object to merge with all the objects in this

@@ -313,3 +328,3 @@ * queryset.

/**
* Records a deletion of all the objects in this {@link QuerySet}.
* Records a deletion of all the objects in this {@link QuerySet} instance.
* @return {undefined}

@@ -316,0 +331,0 @@ */

@@ -48,12 +48,12 @@ import {createSelectorCreator} from 'reselect';

/**
* Defines a Model class with the provided options and registers
* Defines a {@link Model} class with the provided options and registers
* it to the schema instance.
*
* Note that you can also define Model classes by yourself
* Note that you can also define {@link Model} classes by yourself
* with ES6 classes.
*
* @param {string} modelName - the name of the model class
* @param {string} modelName - the name of the {@link Model} class
* @param {Object} [relatedFields] - a dictionary of `fieldName: fieldInstance`
* @param {Function} [reducer] - the reducer function to use for this model
* @param {Object} [backendOpts] -Backend options for this model.
* @param {Object} [backendOpts] - {@link Backend} options for this model.
* @return {Model} The defined model class.

@@ -77,3 +77,3 @@ */

/**
* Registers a model class to the schema.
* Registers a {@link Model} class to the schema.
*

@@ -84,3 +84,3 @@ * If the model has declared any ManyToMany fields, their

*
* @param {...Model} model - a model to register
* @param {...Model} model - a {@link Model} class to register
* @return {undefined}

@@ -130,6 +130,6 @@ */

/**
* Gets a model by its name from the registry.
* @param {string} modelName - the name of the model to get
* @throws If model is not found.
* @return {Model} the model class, if found
* Gets a {@link Model} class by its name from the registry.
* @param {string} modelName - the name of the {@link Model} class to get
* @throws If {@link Model} class is not found.
* @return {Model} the {@link Model} class, if found
*/

@@ -144,3 +144,3 @@ get(modelName) {

_getModelClasses() {
getModelClasses() {
this._setupModelPrototypes();

@@ -290,7 +290,7 @@ return this.registry.concat(this.implicitThroughModels);

/**
* Returns the default state
* Returns the default state.
* @return {Object} the default state
*/
getDefaultState() {
const models = this._getModelClasses();
const models = this.getModelClasses();
const state = {};

@@ -304,14 +304,21 @@ models.forEach(modelClass => {

/**
* Begins a database {@link Session}.
* Begins an immutable database session.
*
* @param {Object} state - the state the database manages
* @param {Object} [action] - the dispatched action object
* @return {Session} a new session instance
* @return {Session} a new {@link Session} instance
*/
from(state, action) {
return new Session(this._getModelClasses(), state, action);
return new Session(this, state, action);
}
withMutations(state) {
return new Session(this._getModelClasses(), state, undefined, true);
/**
* Begins a mutable database session.
*
* @param {Object} state - the state the database manages
* @param {Object} [action] - the dispatched action object
* @return {Session} a new {@link Session} instance
*/
withMutations(state, action) {
return new Session(this, state, action, true);
}

@@ -361,3 +368,3 @@

* return session.Book.map(book => {
* return Object.assign(book.toPlain(), {
* return Object.assign({}, book.ref, {
* authors: book.authors.map(author => author.name),

@@ -364,0 +371,0 @@ * genres: book.genres.map(genre => genre.name),

@@ -11,3 +11,3 @@ import partition from 'lodash/collection/partition';

*
* @param {Schema} schema - a Schema instance
* @param {Schema} schema - a {@link Schema} instance
* @param {Object} state - the database state

@@ -18,5 +18,5 @@ * @param {Object} [action] - the current action in the dispatch cycle.

*/
constructor(models, state, action, withMutations) {
this.models = models;
this.state = state;
constructor(schema, state, action, withMutations) {
this.schema = schema;
this.state = state || schema.getDefaultState();
this.action = action;

@@ -30,3 +30,5 @@ this.withMutations = !!withMutations;

models.forEach(modelClass => {
this.models = schema.getModelClasses();
this.models.forEach(modelClass => {
Object.defineProperty(this, modelClass.modelName, {

@@ -60,2 +62,4 @@ get: () => modelClass,

* Records an update to the session.
*
* @private
* @param {Object} update - the update object. Must have keys

@@ -70,7 +74,6 @@ * `type`, `payload` and `meta`. `meta`

const modelState = this.getState(modelName);
const state = modelState || this[modelName].getDefaultState();
// The backend used in the updateReducer
// will mutate the model state.
this[modelName].updateReducer(state, update);
this[modelName].updateReducer(modelState, update);
} else {

@@ -83,4 +86,5 @@ this.updates.push(update);

* Gets the recorded updates for `modelClass` and
* deletes them from the Session instance updates list.
* deletes them from the {@link Session} instance updates list.
*
* @private
* @param {Model} modelClass - the model class to get updates for

@@ -99,29 +103,49 @@ * @return {Object[]} A list of the user-recorded updates for `modelClass`.

/**
* Returns the current state for a model with name `modelName`.
*
* @private
* @param {string} modelName - the name of the model to get state for.
* @return {*} The state for model with name `modelName`.
*/
getState(modelName) {
if (this.state) {
return this.state[modelName];
}
return undefined;
return this.state[modelName];
}
/**
* Calls the user defined reducers and returns
* the next state.
* If the session uses mutations, just returns the state.
*
* Applies recorded updates and returns the next state.
* @param {Object} [opts] - Options object
* @param {Boolean} [opts.runReducers] - A boolean indicating if the user-defined
* model reducers should be run. If not specified,
* is set to `true` if an action object was specified
* on session instantiation, otherwise `false`.
* @return {Object} The next state
*/
reduce() {
getNextState(userOpts) {
if (this.withMutations) return this.state;
const prevState = this.state || {};
const prevState = this.state;
const action = this.action;
const opts = userOpts || {};
// If the session does not have a specified action object,
// don't run the user-defined model reducers unless
// explicitly specified.
const runReducers = opts.hasOwnProperty('runReducers')
? opts.runReducers
: !!action;
const nextState = this.models.reduce((_nextState, modelClass) => {
const modelState = this.getState(modelClass.modelName);
let nextModelState = modelClass.reducer(modelState, action, modelClass, this);
let nextModelState;
if (runReducers) {
nextModelState = modelClass.reducer(modelState, action, modelClass, this);
}
if (typeof nextModelState === 'undefined') {
// If nothing was returned from the reducer,
// use the return value of getNextState.
// If the reducer wasn't run or it didn't
// return the next state,
// we get the next state manually.
nextModelState = modelClass.getNextState();

@@ -169,4 +193,15 @@ }

}
/**
* Calls the user-defined reducers and returns the next state.
* If the session uses mutations, just returns the state.
* Delegates to {@link Session#getNextState}
*
* @return {Object} the next state
*/
reduce() {
return this.getNextState({ runReducers: true });
}
};
export default Session;
import chai from 'chai';
import sinonChai from 'sinon-chai';
chai.use(sinonChai);
const {expect} = chai;
const { expect } = chai;
import Backend from '../Backend';
import {ListIterator} from '../utils';
import { ListIterator } from '../utils';

@@ -8,0 +8,0 @@ describe('Backend', () => {

@@ -1,2 +0,2 @@

import {expect} from 'chai';
import { expect } from 'chai';
import QuerySet from '../QuerySet';

@@ -48,2 +48,9 @@ import {

it('Models correctly indicate if id exists', () => {
const { Book } = session;
expect(Book.hasId(0)).to.be.true;
expect(Book.hasId(92384)).to.be.false;
expect(Book.hasId()).to.be.false;
});
it('Models correctly create new instances', () => {

@@ -50,0 +57,0 @@ const {Book} = session;

@@ -5,5 +5,5 @@ import chai from 'chai';

chai.use(sinonChai);
const {expect} = chai;
const { expect } = chai;
import BaseModel from '../Model';
import {UPDATE, DELETE, CREATE} from '../constants';
import { UPDATE, DELETE } from '../constants';

@@ -10,0 +10,0 @@ describe('Model', () => {

@@ -5,3 +5,3 @@ import chai from 'chai';

chai.use(sinonChai);
const {expect} = chai;
const { expect } = chai;

@@ -17,3 +17,2 @@ import Model from '../Model';

createTestModels,
createTestSchema,
createTestSessionWithData,

@@ -27,3 +26,3 @@ } from './utils';

beforeEach(() => {
({session} = createTestSessionWithData());
({ session } = createTestSessionWithData());
bookQs = session.Book.getQuerySet();

@@ -140,3 +139,3 @@ genreQs = session.Genre.getQuerySet();

schema.register(Book, Genre, Cover, Author);
const {session: sess} = createTestSessionWithData(schema);
const { session: sess } = createTestSessionWithData(schema);

@@ -143,0 +142,0 @@ const customQs = sess.Book.getQuerySet();

@@ -34,3 +34,3 @@ import chai from 'chai';

schema.register(A, B);
expect(() => schema._getModelClasses()).to.throw(/field/);
expect(() => schema.getModelClasses()).to.throw(/field/);
});

@@ -50,3 +50,3 @@

schema.register(A, B);
expect(() => schema._getModelClasses()).to.throw(/field/);
expect(() => schema.getModelClasses()).to.throw(/field/);
});

@@ -66,3 +66,3 @@

schema.register(A, B);
expect(() => schema._getModelClasses()).to.throw(/field/);
expect(() => schema.getModelClasses()).to.throw(/field/);
});

@@ -69,0 +69,0 @@ });

@@ -1,5 +0,12 @@

import {expect} from 'chai';
import chai from 'chai';
import sinon from 'sinon';
import sinonChai from 'sinon-chai';
import Schema from '../Schema';
import {createTestModels} from './utils';
import { CREATE } from '../constants';
chai.use(sinonChai);
const { expect } = chai;
describe('Session', () => {

@@ -68,2 +75,78 @@ let schema;

});
describe('gets the next state', () => {
it('without any updates, the same state is returned', () => {
const session = schema.from(defaultState);
const nextState = session.getNextState();
expect(nextState).to.equal(defaultState);
});
it('with updates, a new state is returned', () => {
const session = schema.from(defaultState);
session.updates.push({
type: CREATE,
meta: {
name: Author.modelName,
},
payload: {
name: 'Caesar',
},
});
const nextState = session.getNextState();
expect(nextState).to.not.equal(defaultState);
expect(nextState[Author.modelName]).to.not.equal(defaultState[Author.modelName]);
// All other model states should stay equal.
expect(nextState[Book.modelName]).to.equal(defaultState[Book.modelName]);
expect(nextState[Cover.modelName]).to.equal(defaultState[Cover.modelName]);
expect(nextState[Genre.modelName]).to.equal(defaultState[Genre.modelName]);
});
it('runs reducers if explicitly specified', () => {
const session = schema.from(defaultState);
const authorReducerSpy = sinon.spy(Author, 'reducer');
const bookReducerSpy = sinon.spy(Book, 'reducer');
const coverReducerSpy = sinon.spy(Cover, 'reducer');
const genreReducerSpy = sinon.spy(Genre, 'reducer');
session.getNextState({ runReducers: true });
expect(authorReducerSpy).to.be.calledOnce;
expect(bookReducerSpy).to.be.calledOnce;
expect(coverReducerSpy).to.be.calledOnce;
expect(genreReducerSpy).to.be.calledOnce;
});
it('doesn\'t run reducers if explicitly specified', () => {
const session = schema.from(defaultState);
const authorReducerSpy = sinon.spy(Author, 'reducer');
const bookReducerSpy = sinon.spy(Book, 'reducer');
const coverReducerSpy = sinon.spy(Cover, 'reducer');
const genreReducerSpy = sinon.spy(Genre, 'reducer');
session.getNextState({ runReducers: false });
expect(authorReducerSpy).not.to.be.called;
expect(bookReducerSpy).not.to.be.called;
expect(coverReducerSpy).not.to.be.called;
expect(genreReducerSpy).not.to.be.called;
});
});
it('reduce calls getNextState with correct arguments', () => {
const session = schema.from(defaultState);
const getNextStateSpy = sinon.spy(session, 'getNextState');
session.reduce();
expect(getNextStateSpy).to.be.calledOnce;
expect(getNextStateSpy).to.be.calledWithMatch({ runReducers: true });
});
});

@@ -71,2 +71,4 @@ import forOwn from 'lodash/object/forOwn';

* the corresponding properties in `entity`.
*
* @private
* @param {Object} lookupObj - properties to match against

@@ -97,2 +99,3 @@ * @param {Object} entity - object to match

*
* @private
* @param {string} declarationModelName - the name of the model the many-to-many relation was declared on

@@ -112,2 +115,3 @@ * @param {string} fieldName - the field name where the many-to-many relation was declared on

*
* @private
* @param {string} declarationModelName - the name of the model where the relation was declared

@@ -126,2 +130,3 @@ * @return {string} the field name in the through model for `declarationModelName`'s foreign key.

*
* @private
* @param {string} otherModelName - the name of the model that was the target of the many-to-many

@@ -192,2 +197,3 @@ * declaration.

*
* @private
* @param {*} entity - either a Model instance or an id value

@@ -216,2 +222,3 @@ * @return {*} the id value of `entity`

*
* @private
* @param {Object} target - the object to update

@@ -255,2 +262,20 @@ * @param {Object} source - the updated props

function objectShallowEquals(a, b) {
let keysInA = 0;
let keysInB = 0;
forOwn(a, (value, key) => {
if (!b.hasOwnProperty(key) || b[key] !== value) {
return false;
}
keysInA++;
});
for (const key in b) {
if (b.hasOwnProperty(key)) keysInB++;
}
return keysInA === keysInB;
}
export {

@@ -268,2 +293,3 @@ match,

reverseFieldErrorMessage,
objectShallowEquals,
};

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc