Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

mobx-rest

Package Overview
Dependencies
Maintainers
3
Versions
71
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

mobx-rest - npm Package Compare versions

Comparing version 3.0.8 to 4.0.0

.npmignore

5

CHANGELOG.md
# Changelog
## `4.0.0`
- Added indexes to optimize filter and find
## `3.0.5`

@@ -4,0 +9,0 @@

36

lib/Collection.d.ts

@@ -6,4 +6,7 @@ import Base from './Base';

import { CreateOptions, SetOptions, GetOptions, FindOptions, Id } from './types';
declare type IndexTree<T> = Map<string, Index<T>>;
declare type Index<T> = Map<any, Array<T>>;
export default abstract class Collection<T extends Model> extends Base {
models: IObservableArray<T>;
indexes: Array<string>;
constructor(data?: Array<{

@@ -13,2 +16,21 @@ [key: string]: any;

/**
* Define which is the primary key
* of the model's in the collection.
*
* FIXME: This contains a hack to use the `primaryKey` as
* an instance method. Ideally it should be static but that
* would not be backward compatible and Typescript sucks at
* static polymorphism (https://github.com/microsoft/TypeScript/issues/5863).
*/
readonly primaryKey: string;
/**
* Returns a hash with all the indexes for that
* collection.
*
* We keep the indexes in memory for as long as the
* collection is alive, even if no one is referencing it.
* This way we can ensure to calculate it only once.
*/
readonly index: IndexTree<T>;
/**
* Alias for models.length

@@ -27,10 +49,6 @@ */

* Returns the URL where the model's resource would be located on the server.
*
* @abstract
*/
url(): string;
abstract url(): string;
/**
* Specifies the model class for that collection
*
* @abstract
*/

@@ -65,3 +83,3 @@ abstract model(attributes?: {

*/
_ids(): Array<Id>;
private readonly _ids;
/**

@@ -80,3 +98,6 @@ * Get a resource at a given position

/**
* Get resources matching criteria
* Get resources matching criteria.
*
* If passing an object of key:value conditions, it will
* use the indexes to efficiently retrieve the data.
*/

@@ -146,1 +167,2 @@ filter(query: {

}
export {};

170

lib/index.js

@@ -17,3 +17,4 @@ 'use strict';

var difference = _interopDefault(require('lodash/difference'));
var isMatch = _interopDefault(require('lodash/isMatch'));
var intersection = _interopDefault(require('lodash/intersection'));
var entries = _interopDefault(require('lodash/entries'));

@@ -164,7 +165,11 @@ /*! *****************************************************************************

_this.request = null;
_this.requests.remove(request);
mobx.runInAction('remove request', function () {
_this.requests.remove(request);
});
return response;
})
.catch(function (error) {
_this.requests.remove(request);
mobx.runInAction('remove request', function () {
_this.requests.remove(request);
});
throw new ErrorObject(error);

@@ -217,2 +222,3 @@ });

var dontMergeArrays = function (_oldArray, newArray) { return newArray; };
var DEFAULT_PRIMARY = 'id';
var Model = /** @class */ (function (_super) {

@@ -244,9 +250,7 @@ __extends(Model, _super);

/**
* Determine what attribute do you use
* as a primary id
*
* @abstract
* Define which is the primary
* key of the model.
*/
get: function () {
return 'id';
return DEFAULT_PRIMARY;
},

@@ -401,11 +405,2 @@ enumerable: true,

/**
* Merges old attributes with new ones.
* By default it doesn't merge arrays.
*/
Model.prototype.applyPatchChanges = function (oldAttributes, changes) {
return deepmerge(oldAttributes, changes, {
arrayMerge: dontMergeArrays
});
};
/**
* Saves the resource on the backend.

@@ -446,3 +441,3 @@ *

this.set(patch
? this.applyPatchChanges(currentAttributes, attributes)
? applyPatchChanges(currentAttributes, attributes)
: attributes);

@@ -462,3 +457,3 @@ }

if (keepChanges) {
_this.set(_this.applyPatchChanges(data, changes));
_this.set(applyPatchChanges(data, changes));
}

@@ -536,2 +531,11 @@ });

}(Base));
/**
* Merges old attributes with new ones.
* By default it doesn't merge arrays.
*/
var applyPatchChanges = function (oldAttributes, changes) {
return deepmerge(oldAttributes, changes, {
arrayMerge: dontMergeArrays
});
};
var getChangedAttributesBetween = function (source, target) {

@@ -557,5 +561,49 @@ var keys = union(Object.keys(source), Object.keys(target));

_this.models = mobx.observable.array([]);
_this.indexes = [];
_this.set(data);
return _this;
}
Object.defineProperty(Collection.prototype, "primaryKey", {
/**
* Define which is the primary key
* of the model's in the collection.
*
* FIXME: This contains a hack to use the `primaryKey` as
* an instance method. Ideally it should be static but that
* would not be backward compatible and Typescript sucks at
* static polymorphism (https://github.com/microsoft/TypeScript/issues/5863).
*/
get: function () {
var ModelClass = this.model();
return (new ModelClass()).primaryKey;
},
enumerable: true,
configurable: true
});
Object.defineProperty(Collection.prototype, "index", {
/**
* Returns a hash with all the indexes for that
* collection.
*
* We keep the indexes in memory for as long as the
* collection is alive, even if no one is referencing it.
* This way we can ensure to calculate it only once.
*/
get: function () {
var _this = this;
var indexes = this.indexes.concat([this.primaryKey]);
return indexes.reduce(function (tree, attr) {
var newIndex = _this.models.reduce(function (index, model) {
var value = model.has(attr)
? model.get(attr)
: null;
var oldModels = index.get(value) || [];
return index.set(value, oldModels.concat(model));
}, new Map());
return tree.set(attr, newIndex);
}, new Map());
},
enumerable: true,
configurable: true
});
Object.defineProperty(Collection.prototype, "length", {

@@ -584,10 +632,2 @@ /**

/**
* Returns the URL where the model's resource would be located on the server.
*
* @abstract
*/
Collection.prototype.url = function () {
throw new Error('You must implement this method');
};
/**
* Returns a JSON representation

@@ -622,9 +662,13 @@ * of the collection

});
Object.defineProperty(Collection.prototype, "_ids", {
/**
* Gets the ids of all the items in the collection
*/
get: function () {
return Array.from(this.index.get(this.primaryKey).keys());
},
enumerable: true,
configurable: true
});
/**
* Gets the ids of all the items in the collection
*/
Collection.prototype._ids = function () {
return this.models.map(function (item) { return item.id; }).filter(Boolean);
};
/**
* Get a resource at a given position

@@ -640,5 +684,6 @@ */

var _b = (_a === void 0 ? {} : _a).required, required = _b === void 0 ? false : _b;
var model = this.models.find(function (item) { return item.id === id; });
var models = this.index.get(this.primaryKey).get(id);
var model = models && models[0];
if (!model && required) {
throw new Error("Invariant: Model must be found with id: " + id);
throw new Error("Invariant: Model must be found with " + this.primaryKey + ": " + id);
}

@@ -654,10 +699,31 @@ return model;

/**
* Get resources matching criteria
* Get resources matching criteria.
*
* If passing an object of key:value conditions, it will
* use the indexes to efficiently retrieve the data.
*/
Collection.prototype.filter = function (query) {
return this.models.filter(function (model) {
return typeof query === 'function'
? query(model)
: isMatch(model.toJS(), query);
});
var _this = this;
if (typeof query === 'function') {
return this.models.filter(function (model) { return query(model); });
}
else {
// Sort the query to hit the indexes first
var optimizedQuery = entries(query).sort(function (A, B) {
return Number(_this.index.has(B[0])) - Number(_this.index.has(A[0]));
});
return optimizedQuery.reduce(function (values, _a) {
var attr = _a[0], value = _a[1];
// Hitting index
if (_this.index.has(attr)) {
var newValues = _this.index.get(attr).get(value) || [];
return values ? intersection(values, newValues) : newValues;
}
else {
// Either Re-filter or Full scan
var target = values || _this.models;
return target.filter(function (model) { return model.get(attr) === value; });
}
}, null);
}
};

@@ -669,7 +735,5 @@ /**

var _b = (_a === void 0 ? {} : _a).required, required = _b === void 0 ? false : _b;
var model = this.models.find(function (model) {
return typeof query === 'function'
? query(model)
: isMatch(model.toJS(), query);
});
var model = typeof query === 'function'
? this.models.find(function (model) { return query(model); })
: this.filter(query)[0];
if (!model && required) {

@@ -722,3 +786,3 @@ throw new Error("Invariant: Model must be found");

if (!model) {
return console.warn(_this.constructor.name + ": Model with id " + id + " not found.");
return console.warn(_this.constructor.name + ": Model with " + _this.primaryKey + " " + id + " not found.");
}

@@ -738,4 +802,4 @@ _this.models.splice(_this.models.indexOf(model), 1);

if (remove) {
var ids = resources.map(function (r) { return r.id; });
var toRemove = difference(this._ids(), ids);
var ids = resources.map(function (r) { return r[_this.primaryKey]; });
var toRemove = difference(this._ids, ids);
if (toRemove.length)

@@ -745,3 +809,3 @@ this.remove(toRemove);

resources.forEach(function (resource) {
var model = _this.get(resource.id);
var model = _this.get(resource[_this.primaryKey]);
if (model && change)

@@ -817,2 +881,5 @@ model.set(resource);

__decorate([
mobx.computed({ keepAlive: true })
], Collection.prototype, "index", null);
__decorate([
mobx.computed

@@ -824,2 +891,5 @@ ], Collection.prototype, "length", null);

__decorate([
mobx.computed
], Collection.prototype, "_ids", null);
__decorate([
mobx.action

@@ -826,0 +896,0 @@ ], Collection.prototype, "add", null);

@@ -6,4 +6,8 @@ import { ObservableMap } from 'mobx';

import { OptimisticId, Id, DestroyOptions, SaveOptions } from './types';
declare type Attributes = {
[key: string]: any;
};
export declare const DEFAULT_PRIMARY = "id";
export default class Model extends Base {
defaultAttributes: {};
defaultAttributes: Attributes;
attributes: ObservableMap;

@@ -13,7 +17,3 @@ committedAttributes: ObservableMap;

collection: Collection<this> | null;
constructor(attributes?: {
[key: string]: any;
}, defaultAttributes?: {
[key: string]: any;
});
constructor(attributes?: Attributes, defaultAttributes?: Attributes);
/**

@@ -25,6 +25,4 @@ * Returns a JSON representation

/**
* Determine what attribute do you use
* as a primary id
*
* @abstract
* Define which is the primary
* key of the model.
*/

@@ -38,3 +36,3 @@ readonly primaryKey: string;

*/
urlRoot(): any;
urlRoot(): string | null;
/**

@@ -107,7 +105,2 @@ * Return the url for this given REST resource

/**
* Merges old attributes with new ones.
* By default it doesn't merge arrays.
*/
applyPatchChanges(oldAttributes: {}, changes: {}): {};
/**
* Saves the resource on the backend.

@@ -128,1 +121,2 @@ *

}
export {};
{
"name": "mobx-rest",
"version": "3.0.8",
"version": "4.0.0",
"description": "REST conventions for mobx.",

@@ -26,2 +26,3 @@ "jest": {

"@typescript-eslint/parser": "1.9.0",
"benchmark": "2.1.4",
"eslint": "5.16.0",

@@ -43,3 +44,3 @@ "husky": "0.13.4",

"build:clean": "rimraf lib",
"build:lib": "yarn build:clean && rollup --config",
"benchmark": "yarn build && node __tests__/benchmark.js",
"jest": "NODE_PATH=src jest --no-cache",

@@ -60,7 +61,7 @@ "lint": "eslint --ext .ts --cache src/ __tests__/",

"dependencies": {
"@types/lodash": "^4.14.134",
"@types/lodash": "4.14.136",
"deepmerge": "3.2.0",
"lodash": "4.17.11",
"lodash": "4.17.13",
"mobx": "5.9.4"
}
}

@@ -74,15 +74,2 @@ # mobx-rest

You can also overwrite it to provide default attributes like this:
```js
class User extends Model {
constructor(attributes) {
super(Object.assign({
token: null,
email_verified: false,
}, attributes))
}
}
```
#### `defaultAttributes: Object`

@@ -94,4 +81,8 @@

An `ObservableMap` that holds the attributes of the model.
An `ObservableMap` that holds the attributes of the model in the client.
#### `commitedAttributes: ObservableMap`
An `ObservableMap` that holds the attributes of the model in the server.
#### `collection: ?Collection`

@@ -333,2 +324,18 @@

#### `indexes: Array<String>`
Indexes allow you to determine which attributes you want to index your collection by.
This allows you to trade-off memory for speed. By default we index all the models by
`primaryKey` but you can add more indexes that will be used automatically when using `filter`,
`find` and `mustFind` with the object form.
```js
users.find({ id: 123 }) // This will hit the index. Fast!
users.find(user => user.get('id') === 123) // This will do a full scan of the table. Slow.
```
You can query your collection by a combination of attributes that are indexed and others
that are not indexed. `mobx-rest` will take care to sort your query in order to scan the least
number of models.
#### `request: ?Request`

@@ -412,8 +419,16 @@

```js
// using a query object
const resolvedTasks = tasksCollection.filter({ resolved: true })
resolvedTasks.length // => 3
// using a query function
const resolvedTasks = tasksCollection.filter(model => model.resolved)
resolvedTasks.length // => 3
```
#### `find(query: Object, { required?: boolean = false }): ?Model`
It's important to notice that using the object API we can optimize
the filtering using indexes.
#### `find(query: Object | Function, { required?: boolean = false }): ?Model`
Same as `filter` but it will halt and return when the first model matches

@@ -425,9 +440,14 @@ the conditions. If `required` it will raise an error if not found.

```js
const pau = usersCollection.find({ name: 'pau' })
pau.get('name') // => 'pau'
// using a query object
const user = usersCollection.find({ name: 'paco' })
user.get('name') // => 'paco'
// using a query function
const user = usersCollection.find(model => model.name === 'paco')
user.get('name') // => 'paco'
usersCollection.find({ name: 'foo'}) // => Error(`Invariant: Model must be found`)
```
#### `mustFind(query: Object): Model`
#### `mustFind(query: Object | Function): Model`

@@ -708,3 +728,4 @@ Same as `find` but it will raise an Error if the model is not found.

```js
import usersCollection from './UsersCollections'
import users from './UsersCollections'
import comments from './CommentsCollections'
import { computed } from 'mobx'

@@ -715,12 +736,12 @@

author () {
const userId = this.get('userId')
return usersCollection.get(userId) ||
usersCollection.nullObject()
return users.mustGet(this.get('user_id'))
}
@computed
comments () {
return comments.filter({ task_id: this.get('id') })
}
}
```
I recommend to always fallback with a null object which will facilitate
a ton to write code like `task.author.get('name')`.
## Where is it used?

@@ -734,3 +755,3 @@

Copyright (c) 2017 Pau Ramon <masylum@gmail.com>
Copyright (c) 2019 Pau Ramon <masylum@gmail.com>

@@ -737,0 +758,0 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

SocketSocket SOC 2 Logo

Product

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

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc