Comparing version 0.1.1 to 1.0.0
@@ -6,8 +6,8 @@ 'use strict'; | ||
}); | ||
exports.default = undefined; | ||
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); | ||
var _desc, _value, _class, _descriptor, _descriptor2, _descriptor3; /* globals Class */ | ||
var _desc, _value, _class, _descriptor, _descriptor2, _descriptor3; | ||
var _mobx = require('mobx'); | ||
@@ -19,12 +19,12 @@ | ||
var _lodash = require('lodash.difference'); | ||
var _lodash = require('lodash'); | ||
var _lodash2 = _interopRequireDefault(_lodash); | ||
var _apiClient = require('./apiClient'); | ||
var _lodash3 = require('lodash.last'); | ||
var _apiClient2 = _interopRequireDefault(_apiClient); | ||
var _lodash4 = _interopRequireDefault(_lodash3); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; } | ||
function _initDefineProp(target, property, descriptor, context) { | ||
@@ -76,5 +76,5 @@ if (!descriptor) return; | ||
var Collection = (_class = function () { | ||
// eslint-disable-line | ||
function Collection() { | ||
var data = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; | ||
function Collection(data, Api) { | ||
_classCallCheck(this, Collection); | ||
@@ -88,5 +88,3 @@ | ||
this.api = new Api(this.url()); | ||
if (data) this.set(data); | ||
this.set(data); | ||
} | ||
@@ -96,2 +94,4 @@ | ||
* Returns the URL where the model's resource would be located on the server. | ||
* | ||
* @abstract | ||
*/ | ||
@@ -103,3 +103,3 @@ | ||
value: function url() { | ||
return '/'; | ||
throw new Error('You must implement this method'); | ||
} | ||
@@ -118,2 +118,25 @@ | ||
/** | ||
* Questions whether the request exists | ||
* and matches a certain label | ||
*/ | ||
}, { | ||
key: 'isRequest', | ||
value: function isRequest(label) { | ||
if (!this.request) return false; | ||
return this.request.label === label; | ||
} | ||
/** | ||
* Wether the collection is empty | ||
*/ | ||
}, { | ||
key: 'isEmpty', | ||
value: function isEmpty() { | ||
return (0, _lodash.isEmpty)(this.models); | ||
} | ||
/** | ||
* Gets the ids of all the items in the collection | ||
@@ -125,8 +148,5 @@ */ | ||
value: function _ids() { | ||
var ids = this.models.map(function (item) { | ||
return this.models.map(function (item) { | ||
return item.id; | ||
}).filter(Boolean); | ||
// LOL flow: https://github.com/facebook/flow/issues/1414 | ||
return ids.filter(Boolean); | ||
} | ||
@@ -157,2 +177,32 @@ | ||
/** | ||
* Get resources matching criteria | ||
*/ | ||
}, { | ||
key: 'filter', | ||
value: function filter() { | ||
var query = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | ||
return (0, _lodash.filter)(this.models, function (_ref) { | ||
var attributes = _ref.attributes; | ||
return (0, _lodash.isMatch)(attributes.toJS(), query); | ||
}); | ||
} | ||
/** | ||
* Finds an element with the given matcher | ||
*/ | ||
}, { | ||
key: 'find', | ||
value: function find(query) { | ||
return (0, _lodash.find)(this.models, function (_ref2) { | ||
var attributes = _ref2.attributes; | ||
return (0, _lodash.isMatch)(attributes.toJS(), query); | ||
}); | ||
} | ||
/** | ||
* Adds a collection of models. | ||
@@ -164,13 +214,10 @@ * Returns the added models. | ||
key: 'add', | ||
value: function add(models) { | ||
value: function add(data) { | ||
var _this = this; | ||
var Model = this.model(); | ||
var instances = models.map(function (attr) { | ||
return new Model(_this, attr); | ||
var models = data.map(function (d) { | ||
return _this.build(d); | ||
}); | ||
this.models = this.models.concat(instances); | ||
return instances; | ||
this.models = this.models.concat(models); | ||
return models; | ||
} | ||
@@ -206,11 +253,10 @@ | ||
var _ref = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; | ||
var _ref3 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, | ||
_ref3$add = _ref3.add, | ||
add = _ref3$add === undefined ? true : _ref3$add, | ||
_ref3$change = _ref3.change, | ||
change = _ref3$change === undefined ? true : _ref3$change, | ||
_ref3$remove = _ref3.remove, | ||
remove = _ref3$remove === undefined ? true : _ref3$remove; | ||
var _ref$add = _ref.add; | ||
var add = _ref$add === undefined ? true : _ref$add; | ||
var _ref$change = _ref.change; | ||
var change = _ref$change === undefined ? true : _ref$change; | ||
var _ref$remove = _ref.remove; | ||
var remove = _ref$remove === undefined ? true : _ref$remove; | ||
if (remove) { | ||
@@ -220,3 +266,4 @@ var ids = models.map(function (d) { | ||
}); | ||
this.remove((0, _lodash2.default)(this._ids(), ids)); | ||
var toRemove = (0, _lodash.difference)(this._ids(), ids); | ||
if (toRemove.length) this.remove(toRemove); | ||
} | ||
@@ -233,2 +280,18 @@ | ||
/** | ||
* Creates a new model instance with the given attributes | ||
*/ | ||
}, { | ||
key: 'build', | ||
value: function build() { | ||
var attributes = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | ||
var ModelClass = this.model(); | ||
var model = new ModelClass(attributes); | ||
model.collection = this; | ||
return model; | ||
} | ||
/** | ||
* Creates the model and saves it on the backend | ||
@@ -242,42 +305,98 @@ * | ||
key: 'create', | ||
value: function create(attributes) { | ||
var _this4 = this; | ||
value: function () { | ||
var _ref4 = _asyncToGenerator(regeneratorRuntime.mark(function _callee(attributesOrModel) { | ||
var _this4 = this; | ||
var _ref2 = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; | ||
var _ref5 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, | ||
_ref5$optimistic = _ref5.optimistic, | ||
optimistic = _ref5$optimistic === undefined ? true : _ref5$optimistic; | ||
var _ref2$optimistic = _ref2.optimistic; | ||
var optimistic = _ref2$optimistic === undefined ? true : _ref2$optimistic; | ||
var model, attributes, label, onProgress, _apiClient$post, abort, promise, data; | ||
var label = 'creating'; | ||
return regeneratorRuntime.wrap(function _callee$(_context) { | ||
while (1) { | ||
switch (_context.prev = _context.next) { | ||
case 0: | ||
model = void 0; | ||
attributes = attributesOrModel instanceof _Model2.default ? attributesOrModel.attributes.toJS() : attributesOrModel; | ||
label = 'creating'; | ||
onProgress = (0, _lodash.debounce)(function onProgress(progress) { | ||
if (optimistic && model.request) { | ||
model.request.progress = progress; | ||
} | ||
var _api$post = this.api.post('', attributes); | ||
if (this.request) { | ||
this.request.progress = progress; | ||
} | ||
}, 300); | ||
_apiClient$post = (0, _apiClient2.default)().post(this.url(), attributes, { onProgress: onProgress }), abort = _apiClient$post.abort, promise = _apiClient$post.promise; | ||
var abort = _api$post.abort; | ||
var promise = _api$post.promise; | ||
var model = void 0; | ||
if (optimistic) { | ||
model = attributesOrModel instanceof _Model2.default ? attributesOrModel : (0, _lodash.last)(this.add([attributesOrModel])); | ||
model.request = { | ||
label: label, | ||
abort: (0, _mobx.asReference)(abort), | ||
progress: 0 | ||
}; | ||
} | ||
if (optimistic) { | ||
model = (0, _lodash4.default)(this.add([attributes])); | ||
model.request = { label: label, abort: abort }; | ||
} | ||
this.request = { | ||
label: label, | ||
abort: (0, _mobx.asReference)(abort), | ||
progress: 0 | ||
}; | ||
return new Promise(function (resolve, reject) { | ||
promise.then(function (data) { | ||
if (model) { | ||
model.set(data); | ||
model.request = null; | ||
} else { | ||
_this4.add([data]); | ||
data = void 0; | ||
_context.prev = 8; | ||
_context.next = 11; | ||
return promise; | ||
case 11: | ||
data = _context.sent; | ||
_context.next = 18; | ||
break; | ||
case 14: | ||
_context.prev = 14; | ||
_context.t0 = _context['catch'](8); | ||
(0, _mobx.runInAction)('create-error', function () { | ||
if (model) { | ||
_this4.remove([model.id]); | ||
} | ||
_this4.error = { label: label, body: _context.t0 }; | ||
_this4.request = null; | ||
}); | ||
throw _context.t0; | ||
case 18: | ||
(0, _mobx.runInAction)('create-done', function () { | ||
if (model) { | ||
model.set(data); | ||
model.request = null; | ||
} else { | ||
_this4.add([data]); | ||
} | ||
_this4.request = null; | ||
}); | ||
return _context.abrupt('return', data); | ||
case 20: | ||
case 'end': | ||
return _context.stop(); | ||
} | ||
} | ||
}, _callee, this, [[8, 14]]); | ||
})); | ||
resolve(data); | ||
}).catch(function (body) { | ||
if (model) _this4.remove([model.id]); | ||
_this4.error = { label: label, body: body }; | ||
function create(_x5) { | ||
return _ref4.apply(this, arguments); | ||
} | ||
reject(body); | ||
}); | ||
}); | ||
} | ||
return create; | ||
}() | ||
@@ -294,31 +413,68 @@ /** | ||
key: 'fetch', | ||
value: function fetch() { | ||
var _this5 = this; | ||
value: function () { | ||
var _ref6 = _asyncToGenerator(regeneratorRuntime.mark(function _callee2() { | ||
var _this5 = this; | ||
var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; | ||
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | ||
var label = 'fetching'; | ||
var label, _apiClient$get, abort, promise, data; | ||
var _api$fetch = this.api.fetch(); | ||
return regeneratorRuntime.wrap(function _callee2$(_context2) { | ||
while (1) { | ||
switch (_context2.prev = _context2.next) { | ||
case 0: | ||
label = 'fetching'; | ||
_apiClient$get = (0, _apiClient2.default)().get(this.url(), options.data), abort = _apiClient$get.abort, promise = _apiClient$get.promise; | ||
var abort = _api$fetch.abort; | ||
var promise = _api$fetch.promise; | ||
this.request = { | ||
label: label, | ||
abort: (0, _mobx.asReference)(abort), | ||
progress: 0 | ||
}; | ||
this.request = { label: label, abort: abort }; | ||
data = void 0; | ||
_context2.prev = 4; | ||
_context2.next = 7; | ||
return promise; | ||
return new Promise(function (resolve, reject) { | ||
promise.then(function (data) { | ||
_this5.request = null; | ||
_this5.set(data, options); | ||
case 7: | ||
data = _context2.sent; | ||
_context2.next = 14; | ||
break; | ||
resolve(data); | ||
}).catch(function (body) { | ||
_this5.request = null; | ||
_this5.error = { label: label, body: body }; | ||
case 10: | ||
_context2.prev = 10; | ||
_context2.t0 = _context2['catch'](4); | ||
reject(body); | ||
}); | ||
}); | ||
} | ||
(0, _mobx.runInAction)('fetch-error', function () { | ||
_this5.error = { label: label, body: _context2.t0 }; | ||
_this5.request = null; | ||
}); | ||
throw _context2.t0; | ||
case 14: | ||
(0, _mobx.runInAction)('fetch-done', function () { | ||
_this5.set(data, options); | ||
_this5.request = null; | ||
}); | ||
return _context2.abrupt('return', data); | ||
case 16: | ||
case 'end': | ||
return _context2.stop(); | ||
} | ||
} | ||
}, _callee2, this, [[4, 10]]); | ||
})); | ||
function fetch() { | ||
return _ref6.apply(this, arguments); | ||
} | ||
return fetch; | ||
}() | ||
}]); | ||
@@ -325,0 +481,0 @@ |
@@ -6,3 +6,3 @@ 'use strict'; | ||
}); | ||
exports.Model = exports.Collection = undefined; | ||
exports.apiClient = exports.Model = exports.Collection = undefined; | ||
@@ -17,5 +17,10 @@ var _Collection = require('./Collection'); | ||
var _apiClient = require('./apiClient'); | ||
var _apiClient2 = _interopRequireDefault(_apiClient); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
exports.Collection = _Collection2.default; | ||
exports.Model = _Model2.default; | ||
exports.Model = _Model2.default; | ||
exports.apiClient = _apiClient2.default; |
473
lib/Model.js
@@ -6,2 +6,3 @@ 'use strict'; | ||
}); | ||
exports.default = undefined; | ||
@@ -18,8 +19,12 @@ var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); | ||
var _nodeUuid = require('node-uuid'); | ||
var _lodash = require('lodash'); | ||
var _nodeUuid2 = _interopRequireDefault(_nodeUuid); | ||
var _apiClient = require('./apiClient'); | ||
var _apiClient2 = _interopRequireDefault(_apiClient); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; } | ||
function _initDefineProp(target, property, descriptor, context) { | ||
@@ -71,3 +76,5 @@ if (!descriptor) return; | ||
var Model = (_class = function () { | ||
function Model(collection, attributes) { | ||
function Model() { | ||
var attributes = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | ||
_classCallCheck(this, Model); | ||
@@ -79,13 +86,64 @@ | ||
this.uuid = _nodeUuid2.default.v4(); | ||
this.collection = collection; | ||
this.attributes = (0, _mobx.observable)((0, _mobx.asMap)(attributes)); | ||
this.optimisticId = (0, _lodash.uniqueId)('i_'); | ||
this.collection = null; | ||
this.attributes = (0, _mobx.asMap)(attributes); | ||
} | ||
/** | ||
* Return the url for this given REST resource | ||
* | ||
* @abstract | ||
*/ | ||
_createClass(Model, [{ | ||
key: 'url', | ||
value: function url() { | ||
if (this.collection) { | ||
return this.collection.url() + '/' + this.get('id'); | ||
} | ||
throw new Error('`url` method not implemented'); | ||
} | ||
/** | ||
* Get the attribute from the model. | ||
* | ||
* Since we want to be sure changes on | ||
* the schema don't fail silently we | ||
* throw an error if the field does not | ||
* exist. | ||
* | ||
* If you want to deal with flexible schemas | ||
* use `has` to check wether the field | ||
* exists. | ||
*/ | ||
}, { | ||
key: 'get', | ||
value: function get(attribute) { | ||
return this.attributes.get(attribute); | ||
if (this.has(attribute)) { | ||
return this.attributes.get(attribute); | ||
} | ||
throw new Error('Attribute "' + attribute + '" not found'); | ||
} | ||
/** | ||
* Returns whether the given field exists | ||
* for the model. | ||
*/ | ||
}, { | ||
key: 'has', | ||
value: function has(attribute) { | ||
return this.attributes.has(attribute); | ||
} | ||
/** | ||
* Get an id from the model. It will use either | ||
* the backend assigned one or the client. | ||
*/ | ||
}, { | ||
key: 'set', | ||
@@ -95,106 +153,367 @@ value: function set(data) { | ||
} | ||
/** | ||
* Fetches the model from the backend. | ||
*/ | ||
}, { | ||
key: 'save', | ||
value: function save(attributes) { | ||
var _this = this; | ||
key: 'fetch', | ||
value: function () { | ||
var _ref = _asyncToGenerator(regeneratorRuntime.mark(function _callee() { | ||
var _this = this; | ||
var _ref = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; | ||
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | ||
var _ref$optimistic = _ref.optimistic; | ||
var optimistic = _ref$optimistic === undefined ? true : _ref$optimistic; | ||
var _ref$patch = _ref.patch; | ||
var patch = _ref$patch === undefined ? true : _ref$patch; | ||
var label, _apiClient$get, abort, promise, data; | ||
var originalAttributes = this.attributes.toJS(); | ||
var newAttributes = void 0; | ||
var data = void 0; | ||
return regeneratorRuntime.wrap(function _callee$(_context) { | ||
while (1) { | ||
switch (_context.prev = _context.next) { | ||
case 0: | ||
label = 'fetching'; | ||
_apiClient$get = (0, _apiClient2.default)().get(this.url(), options.data), abort = _apiClient$get.abort, promise = _apiClient$get.promise; | ||
if (!this.get('id')) { | ||
return this.collection.create(attributes, { optimistic: optimistic }); | ||
} | ||
var label = 'updating'; | ||
this.request = { | ||
label: label, | ||
abort: (0, _mobx.asReference)(abort), | ||
progress: 0 | ||
}; | ||
if (patch) { | ||
newAttributes = Object.assign({}, originalAttributes, attributes); | ||
data = Object.assign({}, attributes); | ||
} else { | ||
newAttributes = Object.assign({}, attributes); | ||
data = Object.assign({}, originalAttributes, attributes); | ||
data = void 0; | ||
_context.prev = 4; | ||
_context.next = 7; | ||
return promise; | ||
case 7: | ||
data = _context.sent; | ||
_context.next = 14; | ||
break; | ||
case 10: | ||
_context.prev = 10; | ||
_context.t0 = _context['catch'](4); | ||
(0, _mobx.runInAction)('fetch-error', function () { | ||
_this.error = { label: label, body: _context.t0 }; | ||
_this.request = null; | ||
}); | ||
throw _context.t0; | ||
case 14: | ||
(0, _mobx.runInAction)('fetch-done', function () { | ||
_this.set(data); | ||
_this.request = null; | ||
}); | ||
return _context.abrupt('return', data); | ||
case 16: | ||
case 'end': | ||
return _context.stop(); | ||
} | ||
} | ||
}, _callee, this, [[4, 10]]); | ||
})); | ||
function fetch() { | ||
return _ref.apply(this, arguments); | ||
} | ||
// TODO: use PATCH | ||
return fetch; | ||
}() | ||
var _collection$api$put = this.collection.api.put('/' + this.id, data); | ||
/** | ||
* Saves the resource on the backend. | ||
* | ||
* If the item has an `id` it updates it, | ||
* otherwise it creates the new resource. | ||
* | ||
* It supports optimistic and patch updates. | ||
*/ | ||
var promise = _collection$api$put.promise; | ||
var abort = _collection$api$put.abort; | ||
}, { | ||
key: 'save', | ||
value: function () { | ||
var _ref2 = _asyncToGenerator(regeneratorRuntime.mark(function _callee2(attributes) { | ||
var _this2 = this; | ||
var _ref3 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, | ||
_ref3$optimistic = _ref3.optimistic, | ||
optimistic = _ref3$optimistic === undefined ? true : _ref3$optimistic, | ||
_ref3$patch = _ref3.patch, | ||
patch = _ref3$patch === undefined ? true : _ref3$patch; | ||
if (optimistic) this.attributes = (0, _mobx.asMap)(newAttributes); | ||
var originalAttributes, newAttributes, data, label, _apiClient$put, promise, abort, response; | ||
this.request = { label: label, abort: abort }; | ||
return regeneratorRuntime.wrap(function _callee2$(_context2) { | ||
while (1) { | ||
switch (_context2.prev = _context2.next) { | ||
case 0: | ||
originalAttributes = this.attributes.toJS(); | ||
newAttributes = void 0; | ||
data = void 0; | ||
return new Promise(function (resolve, reject) { | ||
promise.then(function (data) { | ||
_this.request = null; | ||
_this.set(data); | ||
if (this.has('id')) { | ||
_context2.next = 8; | ||
break; | ||
} | ||
resolve(data); | ||
}).catch(function (body) { | ||
_this.request = null; | ||
_this.attributes = (0, _mobx.asMap)(originalAttributes); | ||
_this.error = { label: label, body: body }; | ||
this.set(Object.assign({}, attributes)); | ||
reject(body); | ||
}); | ||
}); | ||
} | ||
if (!this.collection) { | ||
_context2.next = 7; | ||
break; | ||
} | ||
return _context2.abrupt('return', this.collection.create(this, { optimistic: optimistic })); | ||
case 7: | ||
throw new Error('This model does not have a collection defined'); | ||
case 8: | ||
label = 'updating'; | ||
if (patch) { | ||
newAttributes = Object.assign({}, originalAttributes, attributes); | ||
data = Object.assign({}, attributes); | ||
} else { | ||
newAttributes = Object.assign({}, attributes); | ||
data = Object.assign({}, originalAttributes, attributes); | ||
} | ||
_apiClient$put = (0, _apiClient2.default)().put(this.url(), data, { method: patch ? 'PATCH' : 'PUT' }), promise = _apiClient$put.promise, abort = _apiClient$put.abort; | ||
if (optimistic) this.set(newAttributes); | ||
this.request = { | ||
label: label, | ||
abort: (0, _mobx.asReference)(abort), | ||
progress: 0 | ||
}; | ||
response = void 0; | ||
_context2.prev = 14; | ||
_context2.next = 17; | ||
return promise; | ||
case 17: | ||
response = _context2.sent; | ||
_context2.next = 24; | ||
break; | ||
case 20: | ||
_context2.prev = 20; | ||
_context2.t0 = _context2['catch'](14); | ||
(0, _mobx.runInAction)('save-fail', function () { | ||
_this2.request = null; | ||
_this2.set(originalAttributes); | ||
_this2.error = { label: label, body: _context2.t0 }; | ||
}); | ||
throw (0, _lodash.isString)(_context2.t0) ? new Error(_context2.t0) : _context2.t0; | ||
case 24: | ||
(0, _mobx.runInAction)('save-done', function () { | ||
_this2.request = null; | ||
_this2.set(response); | ||
}); | ||
return _context2.abrupt('return', response); | ||
case 26: | ||
case 'end': | ||
return _context2.stop(); | ||
} | ||
} | ||
}, _callee2, this, [[14, 20]]); | ||
})); | ||
function save(_x3) { | ||
return _ref2.apply(this, arguments); | ||
} | ||
return save; | ||
}() | ||
/** | ||
* Destroys the resurce on the client and | ||
* requests the backend to delete it there | ||
* too | ||
*/ | ||
}, { | ||
key: 'destroy', | ||
value: function destroy() { | ||
var _this2 = this; | ||
value: function () { | ||
var _ref4 = _asyncToGenerator(regeneratorRuntime.mark(function _callee3() { | ||
var _this3 = this; | ||
var _ref2 = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; | ||
var _ref5 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, | ||
_ref5$optimistic = _ref5.optimistic, | ||
optimistic = _ref5$optimistic === undefined ? true : _ref5$optimistic; | ||
var _ref2$optimistic = _ref2.optimistic; | ||
var optimistic = _ref2$optimistic === undefined ? true : _ref2$optimistic; | ||
var label, _apiClient$del, promise, abort; | ||
if (!this.get('id')) { | ||
this.collection.remove([this.uuid], { optimistic: optimistic }); | ||
return Promise.resolve(); | ||
return regeneratorRuntime.wrap(function _callee3$(_context3) { | ||
while (1) { | ||
switch (_context3.prev = _context3.next) { | ||
case 0: | ||
if (!(!this.has('id') && this.collection)) { | ||
_context3.next = 3; | ||
break; | ||
} | ||
this.collection.remove([this.optimisticId], { optimistic: optimistic }); | ||
return _context3.abrupt('return', Promise.resolve()); | ||
case 3: | ||
label = 'destroying'; | ||
_apiClient$del = (0, _apiClient2.default)().del(this.url()), promise = _apiClient$del.promise, abort = _apiClient$del.abort; | ||
if (optimistic && this.collection) { | ||
this.collection.remove([this.id]); | ||
} | ||
this.request = { | ||
label: label, | ||
abort: (0, _mobx.asReference)(abort), | ||
progress: 0 | ||
}; | ||
_context3.prev = 7; | ||
_context3.next = 10; | ||
return promise; | ||
case 10: | ||
_context3.next = 16; | ||
break; | ||
case 12: | ||
_context3.prev = 12; | ||
_context3.t0 = _context3['catch'](7); | ||
(0, _mobx.runInAction)('destroy-fail', function () { | ||
if (optimistic && _this3.collection) { | ||
_this3.collection.add([_this3.attributes.toJS()]); | ||
} | ||
_this3.error = { label: label, body: _context3.t0 }; | ||
_this3.request = null; | ||
}); | ||
throw _context3.t0; | ||
case 16: | ||
(0, _mobx.runInAction)('destroy-done', function () { | ||
if (!optimistic && _this3.collection) { | ||
_this3.collection.remove([_this3.id]); | ||
} | ||
_this3.request = null; | ||
}); | ||
return _context3.abrupt('return', null); | ||
case 18: | ||
case 'end': | ||
return _context3.stop(); | ||
} | ||
} | ||
}, _callee3, this, [[7, 12]]); | ||
})); | ||
function destroy() { | ||
return _ref4.apply(this, arguments); | ||
} | ||
var label = 'destroying'; | ||
return destroy; | ||
}() | ||
var _collection$api$del = this.collection.api.del('/' + this.id); | ||
/** | ||
* Call an RPC action for all those | ||
* non-REST endpoints that you may have in | ||
* your API. | ||
*/ | ||
var promise = _collection$api$del.promise; | ||
var abort = _collection$api$del.abort; | ||
}, { | ||
key: 'rpc', | ||
value: function () { | ||
var _ref6 = _asyncToGenerator(regeneratorRuntime.mark(function _callee4(method, body) { | ||
var _this4 = this; | ||
var label, _apiClient$post, promise, abort, response; | ||
if (optimistic) this.collection.remove([this.id]); | ||
return regeneratorRuntime.wrap(function _callee4$(_context4) { | ||
while (1) { | ||
switch (_context4.prev = _context4.next) { | ||
case 0: | ||
label = 'updating'; // TODO: Maybe differentiate? | ||
this.request = { label: label, abort: abort }; | ||
_apiClient$post = (0, _apiClient2.default)().post(this.url() + '/' + method, body || {}), promise = _apiClient$post.promise, abort = _apiClient$post.abort; | ||
return new Promise(function (resolve, reject) { | ||
return promise.then(function () { | ||
if (!optimistic) _this2.collection.remove([_this2.id]); | ||
_this2.request = null; | ||
resolve(); | ||
}).catch(function (body) { | ||
if (optimistic) _this2.collection.add([_this2.attributes.toJS()]); | ||
_this2.error = { label: label, body: body }; | ||
_this2.request = null; | ||
this.request = { | ||
label: label, | ||
abort: (0, _mobx.asReference)(abort), | ||
progress: 0 | ||
}; | ||
reject(body); | ||
}); | ||
}); | ||
} | ||
response = void 0; | ||
_context4.prev = 4; | ||
_context4.next = 7; | ||
return promise; | ||
case 7: | ||
response = _context4.sent; | ||
_context4.next = 14; | ||
break; | ||
case 10: | ||
_context4.prev = 10; | ||
_context4.t0 = _context4['catch'](4); | ||
(0, _mobx.runInAction)('accept-fail', function () { | ||
_this4.request = null; | ||
_this4.error = { label: label, body: _context4.t0 }; | ||
}); | ||
throw _context4.t0; | ||
case 14: | ||
this.request = null; | ||
return _context4.abrupt('return', response); | ||
case 16: | ||
case 'end': | ||
return _context4.stop(); | ||
} | ||
} | ||
}, _callee4, this, [[4, 10]]); | ||
})); | ||
function rpc(_x6, _x7) { | ||
return _ref6.apply(this, arguments); | ||
} | ||
return rpc; | ||
}() | ||
}, { | ||
key: 'id', | ||
get: function get() { | ||
return this.get('id') || this.uuid; | ||
return this.has('id') ? this.get('id') : this.optimisticId; | ||
} | ||
/** | ||
* Merge the given attributes with | ||
* the current ones | ||
*/ | ||
}]); | ||
@@ -211,5 +530,5 @@ | ||
initializer: function initializer() { | ||
return null; | ||
return (0, _mobx.asFlat)(null); | ||
} | ||
}), _applyDecoratedDescriptor(_class.prototype, 'set', [_mobx.action], Object.getOwnPropertyDescriptor(_class.prototype, 'set'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'save', [_mobx.action], Object.getOwnPropertyDescriptor(_class.prototype, 'save'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'destroy', [_mobx.action], Object.getOwnPropertyDescriptor(_class.prototype, 'destroy'), _class.prototype)), _class); | ||
}), _applyDecoratedDescriptor(_class.prototype, 'set', [_mobx.action], Object.getOwnPropertyDescriptor(_class.prototype, 'set'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'fetch', [_mobx.action], Object.getOwnPropertyDescriptor(_class.prototype, 'fetch'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'save', [_mobx.action], Object.getOwnPropertyDescriptor(_class.prototype, 'save'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'destroy', [_mobx.action], Object.getOwnPropertyDescriptor(_class.prototype, 'destroy'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'rpc', [_mobx.action], Object.getOwnPropertyDescriptor(_class.prototype, 'rpc'), _class.prototype)), _class); | ||
exports.default = Model; |
{ | ||
"name": "mobx-rest", | ||
"version": "0.1.1", | ||
"version": "1.0.0", | ||
"description": "REST conventions for mobx.", | ||
@@ -10,17 +10,18 @@ "repository": { | ||
"license": "MIT", | ||
"dependencies": { | ||
"jquery": "^3.1.0", | ||
"lodash.difference": "^4.3.0", | ||
"lodash.last": "^3.0.0", | ||
"lodash.without": "^4.2.0", | ||
"node-uuid": "^1.4.7", | ||
"mobx": "^2.3.2" | ||
"jest": { | ||
"testRegex": "/__tests__/.*\\.spec\\.js$" | ||
}, | ||
"standard": { | ||
"parser": "babel-eslint" | ||
"parser": "babel-eslint", | ||
"globals": [ "it", "describe", "beforeEach", "expect", "Class", "jest" ] | ||
}, | ||
"dependencies": { | ||
"lodash": "^4.17.4", | ||
"mobx": "^2.7.0" | ||
}, | ||
"devDependencies": { | ||
"babel-cli": "^6.10.1", | ||
"babel-core": "^6.10.4", | ||
"babel-eslint": "^6.1.1", | ||
"babel-eslint": "^7.1.1", | ||
"babel-jest": "^18.0.0", | ||
"babel-plugin-transform-decorators-legacy": "^1.3.4", | ||
@@ -32,7 +33,6 @@ "babel-plugin-transform-flow-strip-types": "^6.8.0", | ||
"babel-register": "^6.9.0", | ||
"flow-bin": "^0.28.0", | ||
"mocha": "^2.3.4", | ||
"sinon": "^1.17.4", | ||
"snazzy": "^4.0.0", | ||
"standard": "^7.1.2" | ||
"flow-bin": "^0.38.0", | ||
"jest": "^18.1.0", | ||
"snazzy": "^6.0.0", | ||
"standard": "^8.6.0" | ||
}, | ||
@@ -43,7 +43,7 @@ "main": "lib", | ||
"prepublish": "npm run compile", | ||
"unit": "./node_modules/mocha/bin/mocha test/* --require babel-core/register", | ||
"jest": "BABEL_ENV=test NODE_PATH=src jest --no-cache", | ||
"lint": "standard --verbose | snazzy", | ||
"flow": "flow", | ||
"test": "npm run flow && npm run lint && npm run unit" | ||
"test": "npm run flow && npm run lint && npm run jest" | ||
} | ||
} |
@@ -9,23 +9,30 @@ # REST Mobx | ||
## TODO | ||
Make API dependency injectable | ||
## Installation | ||
``` | ||
npm install rest-mobx --save | ||
npm install mobx-rest --save | ||
``` | ||
## What is it? | ||
MobX is great to represent RESTful resources. Each resource can be represented | ||
with a store which will the expected REST actions (`create`, `fetch`, `save`, `destroy`, ...). | ||
Instead of writing hundreds of boilerplate lines we can leverage REST conventions | ||
to deal with your API interactions. | ||
## Example | ||
```js | ||
const apiPath = 'http://localhost:8000/api' | ||
import { Collection, Model } from 'mobx-rest' | ||
import { observer } from 'mobx-react' | ||
const apiPath = '/api' | ||
import jqueryAdapter from 'mobx-rest-jquery-adapter' | ||
import { apiClient, Collection, Model } from 'mobx-rest' | ||
// Set the adapter | ||
apiClient(jqueryAdapter, { apiPath }) | ||
class Task extends Model { } | ||
class Tasks extends Collection { | ||
basePath () { | ||
return `${apiPath}/tasks` | ||
url () { | ||
return `/tasks` | ||
} | ||
@@ -40,2 +47,4 @@ | ||
import { observer } from 'mobx-react' | ||
@observer | ||
@@ -52,4 +61,4 @@ class Companies extends React.Component { | ||
render () { | ||
if (tasks.request) { | ||
return <span>Loading...</span> | ||
if (tasks.isRequest('fetching')) { | ||
return <span>Fetching tasks...</span> | ||
} | ||
@@ -69,22 +78,23 @@ | ||
models: [ | ||
{ // Information at the resource level | ||
uuid: String, // Client side id. Used for optimistic updates | ||
request: { // An ongoing request | ||
label: String, // Examples: 'updating', 'creating', 'fetching', 'destroying' ... | ||
abort: Function, // A method to abort the ongoing request | ||
{ // Information at the resource level | ||
optimisticId: String, // Client side id. Used for optimistic updates | ||
request: { // An ongoing request | ||
label: String, // Examples: 'updating', 'creating', 'fetching', 'destroying' ... | ||
abort: Function, // A method to abort the ongoing request | ||
}, | ||
error: { // A failed request | ||
label: String, // Examples: 'updating', 'creating', 'fetching', 'destroying' ... | ||
body: String, // A string representing the error | ||
error: { // A failed request | ||
label: String, // Examples: 'updating', 'creating', 'fetching', 'destroying' ... | ||
body: String, // A string representing the error | ||
}, | ||
attributes: Object // The resource attributes | ||
attributes: Object // The resource attributes | ||
} | ||
] // Information at the collection level | ||
request: { // An ongoing request | ||
label: String, // Examples: 'updating', 'creating', 'fetching', 'destroying' ... | ||
abort: Function, // A method to abort the ongoing request | ||
] // Information at the collection level | ||
request: { // An ongoing request | ||
label: String, // Examples: 'updating', 'creating', 'fetching', 'destroying' ... | ||
abort: Function, // A method to abort the ongoing request | ||
progress: number // If uploading a file, represents the progress | ||
}, | ||
error: { // A failed request | ||
label: String, // Examples: 'updating', 'creating', 'fetching', 'destroying' ... | ||
body: Object, // A string representing the error | ||
error: { // A failed request | ||
label: String, // Examples: 'updating', 'creating', 'fetching', 'destroying' ... | ||
body: Object, // A string representing the error | ||
} | ||
@@ -97,3 +107,3 @@ ``` | ||
Copyright (c) 2016 Pau Ramon <masylum@gmail.com> | ||
Copyright (c) 2017 Pau Ramon <masylum@gmail.com> | ||
@@ -100,0 +110,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: |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
58306
2
16
1436
1
110
3
+ Addedlodash@^4.17.4
+ Addedlodash@4.17.21(transitive)
- Removedjquery@^3.1.0
- Removedlodash.difference@^4.3.0
- Removedlodash.last@^3.0.0
- Removedlodash.without@^4.2.0
- Removednode-uuid@^1.4.7
- Removedjquery@3.7.1(transitive)
- Removedlodash.difference@4.5.0(transitive)
- Removedlodash.last@3.0.0(transitive)
- Removedlodash.without@4.4.0(transitive)
- Removednode-uuid@1.4.8(transitive)
Updatedmobx@^2.7.0