feathers-memory
Advanced tools
Comparing version 0.4.0 to 0.4.1
286
lib/index.js
'use strict'; | ||
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; }; })(); | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
exports.specialFilters = undefined; | ||
exports.default = init; | ||
@@ -22,2 +24,4 @@ var _lodash = require('lodash'); | ||
var _utils = require('./utils'); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
@@ -27,185 +31,171 @@ | ||
var specialFilters = exports.specialFilters = { | ||
$in: function $in(key, ins) { | ||
return function (current) { | ||
return ins.indexOf(current[key]) !== -1; | ||
}; | ||
}, | ||
$nin: function $nin(key, nins) { | ||
return function (current) { | ||
return nins.indexOf(current[key]) === -1; | ||
}; | ||
}, | ||
$lt: function $lt(key, value) { | ||
return function (current) { | ||
return current[key] < value; | ||
}; | ||
}, | ||
$lte: function $lte(key, value) { | ||
return function (current) { | ||
return current[key] <= value; | ||
}; | ||
}, | ||
$gt: function $gt(key, value) { | ||
return function (current) { | ||
return current[key] > value; | ||
}; | ||
}, | ||
$gte: function $gte(key, value) { | ||
return function (current) { | ||
return current[key] >= value; | ||
}; | ||
}, | ||
$ne: function $ne(key, value) { | ||
return function (current) { | ||
return current[key] !== value; | ||
}; | ||
} | ||
}; | ||
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } | ||
function filterSpecials(values, query) { | ||
if (query.$or) { | ||
values = values.filter(function (current) { | ||
return _lodash2.default.some(query.$or, function (or) { | ||
return _lodash2.default.isMatch(current, or); | ||
}); | ||
}); | ||
delete query.$or; | ||
require('babel-polyfill'); | ||
var Service = (function () { | ||
function Service() { | ||
var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; | ||
_classCallCheck(this, Service); | ||
this.paginate = options.paginate || {}; | ||
this._id = options.idField || 'id'; | ||
this._uId = options.startId || 0; | ||
this.store = options.store || {}; | ||
} | ||
_lodash2.default.each(query, function (value, key) { | ||
if (_lodash2.default.isObject(value)) { | ||
_lodash2.default.each(value, function (target, prop) { | ||
if (specialFilters[prop]) { | ||
values = values.filter(specialFilters[prop](key, target)); | ||
} | ||
}); | ||
delete query[key]; | ||
_createClass(Service, [{ | ||
key: 'extend', | ||
value: function extend(obj) { | ||
return _uberproto2.default.extend(obj, this); | ||
} | ||
}); | ||
}, { | ||
key: 'find', | ||
value: function find(params) { | ||
var query = params.query || {}; | ||
var filters = (0, _feathersQueryFilters2.default)(query); | ||
return values; | ||
} | ||
var values = (0, _utils.filterSpecials)(_lodash2.default.values(this.store), query); | ||
function sorter($sort) { | ||
return function (first, second) { | ||
var comparator = 0; | ||
_lodash2.default.each($sort, function (modifier, key) { | ||
if (first[key] < second[key]) { | ||
comparator -= 1 * modifier; | ||
if (!_lodash2.default.isEmpty(query)) { | ||
values = _lodash2.default.where(values, query); | ||
} | ||
if (first[key] > second[key]) { | ||
comparator += 1 * modifier; | ||
var total = values.length; | ||
if (filters.$sort) { | ||
values.sort((0, _utils.sorter)(filters.$sort)); | ||
} | ||
}); | ||
return comparator; | ||
}; | ||
} | ||
var MemoryService = _uberproto2.default.extend({ | ||
init: function init(options) { | ||
options = options || {}; | ||
if (filters.$skip) { | ||
values = values.slice(parseInt(filters.$skip, 10)); | ||
} | ||
this.type = 'memory'; | ||
this._id = options.idField || 'id'; | ||
this._uId = options.startId || 0; | ||
this.store = options.store || {}; | ||
}, | ||
find: function find(params, cb) { | ||
var query = params.query || {}; | ||
var filters = (0, _feathersQueryFilters2.default)(query); | ||
var limit = parseInt(filters.$limit || this.paginate.default, 10); | ||
var values = filterSpecials(_lodash2.default.values(this.store), query); | ||
if (limit) { | ||
limit = Math.min(this.paginate.max || Number.MAX_VALUE, limit); | ||
values = values.slice(0, limit); | ||
} | ||
if (!_lodash2.default.isEmpty(query)) { | ||
values = _lodash2.default.where(values, query); | ||
} | ||
if (filters.$select) { | ||
values = values.map(function (value) { | ||
return _lodash2.default.pick(value, filters.$select); | ||
}); | ||
} | ||
// Handle $sort | ||
if (filters.$sort) { | ||
values.sort(sorter(filters.$sort)); | ||
} | ||
if (this.paginate.default) { | ||
return Promise.resolve({ | ||
total: total, | ||
limit: limit, | ||
skip: parseInt(filters.$skip || 0, 10), | ||
data: values | ||
}); | ||
} | ||
if (filters.$skip) { | ||
values = values.slice(parseInt(filters.$skip, 10)); | ||
return Promise.resolve(values); | ||
} | ||
}, { | ||
key: 'get', | ||
value: function get(id) { | ||
if (id in this.store) { | ||
return Promise.resolve(this.store[id]); | ||
} | ||
if (filters.$limit) { | ||
values = values.slice(0, parseInt(filters.$limit, 10)); | ||
return Promise.reject(new _feathersErrors.types.NotFound('No record found for id \'' + id + '\'')); | ||
} | ||
}, { | ||
key: 'create', | ||
value: function create(data) { | ||
var _this = this; | ||
if (filters.$select) { | ||
values = values.map(function (value) { | ||
return _lodash2.default.pick(value, filters.$select); | ||
}); | ||
} | ||
if (Array.isArray(data)) { | ||
return Promise.all(data.map(function (current) { | ||
return _this.create(current); | ||
})); | ||
} | ||
cb(null, values); | ||
}, | ||
get: function get(id, params, cb) { | ||
if (typeof id === 'function') { | ||
return id(new _feathersErrors.types.BadRequest('An id needs to be provided')); | ||
} | ||
var id = data[this._id] || this._uId++; | ||
var current = _lodash2.default.extend({}, data, _defineProperty({}, this._id, id)); | ||
if (id in this.store) { | ||
return cb(null, this.store[id]); | ||
if (this.store[id]) { | ||
return Promise.reject(new _feathersErrors.types.Conflict('A record with id: ' + id + ' already exists')); | ||
} | ||
return Promise.resolve(this.store[id] = current); | ||
} | ||
}, { | ||
key: 'update', | ||
value: function update(id, data) { | ||
if (id === null || Array.isArray(data)) { | ||
return Promise.reject(new _feathersErrors.types.BadRequest('You can not replace multiple instances. Did you mean \'patch\'?')); | ||
} | ||
cb(new _feathersErrors.types.NotFound('No record found for id \'' + id + '\'')); | ||
}, | ||
create: function create(data, params, cb) { | ||
var id = data[this._id] || this._uId++; | ||
data = _lodash2.default.extend({}, data, _defineProperty({}, this._id, id)); | ||
if (id in this.store) { | ||
data = _lodash2.default.extend({}, data, _defineProperty({}, this._id, id)); | ||
this.store[id] = data; | ||
if (this.store[id]) { | ||
return cb(new _feathersErrors.types.Conflict('A record with id: ' + id + ' already exists')); | ||
return Promise.resolve(this.store[id]); | ||
} | ||
return Promise.reject(new _feathersErrors.types.NotFound('No record found for id \'' + id + '\'')); | ||
} | ||
}, { | ||
key: 'patch', | ||
value: function patch(id, data, params) { | ||
var _this2 = this; | ||
this.store[id] = data; | ||
if (id === null) { | ||
return this.find(params).then(function (instances) { | ||
return Promise.all(instances.map(function (current) { | ||
return _this2.patch(current[_this2._id], data, params); | ||
})); | ||
}); | ||
} | ||
cb(null, data); | ||
}, | ||
update: function update(id, data, params, cb) { | ||
if (id in this.store) { | ||
data = _lodash2.default.extend({}, data, _defineProperty({}, this._id, id)); | ||
this.store[id] = data; | ||
if (id in this.store) { | ||
_lodash2.default.each(data, function (value, key) { | ||
if (key !== _this2._id) { | ||
_this2.store[id][key] = value; | ||
} | ||
}); | ||
return cb(null, this.store[id]); | ||
return Promise.resolve(this.store[id]); | ||
} | ||
return Promise.reject(new _feathersErrors.types.NotFound('No record found for id \'' + id + '\'')); | ||
} | ||
}, { | ||
key: 'remove', | ||
value: function remove(id, params) { | ||
var _this3 = this; | ||
cb(new _feathersErrors.types.NotFound('No record found for id \'' + id + '\'')); | ||
}, | ||
patch: function patch(id, data, params, cb) { | ||
var _this = this; | ||
if (id === null) { | ||
return this.find(params).then(function (data) { | ||
return Promise.all(data.map(function (current) { | ||
return _this3.remove(current[_this3._id]); | ||
})); | ||
}); | ||
} | ||
if (id in this.store) { | ||
_lodash2.default.each(data, function (value, key) { | ||
if (key !== _this._id) { | ||
_this.store[id][key] = value; | ||
} | ||
}); | ||
if (id in this.store) { | ||
var deleted = this.store[id]; | ||
delete this.store[id]; | ||
return cb(null, this.store[id]); | ||
} | ||
return Promise.resolve(deleted); | ||
} | ||
cb(new _feathersErrors.types.NotFound('No record found for id \'' + id + '\'')); | ||
}, | ||
remove: function remove(id, params, cb) { | ||
if (id in this.store) { | ||
var deleted = this.store[id]; | ||
delete this.store[id]; | ||
return cb(null, deleted); | ||
return Promise.reject(new _feathersErrors.types.NotFound('No record found for id \'' + id + '\'')); | ||
} | ||
}]); | ||
cb(new _feathersErrors.types.NotFound('No record found for id \'' + id + '\'')); | ||
} | ||
}); | ||
return Service; | ||
})(); | ||
module.exports = function (options) { | ||
return _uberproto2.default.create.call(MemoryService, options); | ||
}; | ||
function init(options) { | ||
return new Service(options); | ||
} | ||
module.exports.Service = MemoryService; | ||
init.Service = Service; | ||
module.exports = exports['default']; |
{ | ||
"name": "feathers-memory", | ||
"description": "An in memory service store", | ||
"version": "0.4.0", | ||
"version": "0.4.1", | ||
"homepage": "https://github.com/feathersjs/feathers-memory", | ||
@@ -43,3 +43,3 @@ "main": "lib/", | ||
"mocha": "mocha test/ --compilers js:babel-core/register", | ||
"test": "npm run jshint && npm run mocha" | ||
"test": "npm run compile && npm run jshint && npm run mocha" | ||
}, | ||
@@ -50,6 +50,7 @@ "directories": { | ||
"dependencies": { | ||
"babel-polyfill": "^6.2.0", | ||
"feathers-errors": "^0.2.5", | ||
"feathers-query-filters": "^1.1.1", | ||
"lodash": "^3.10.1", | ||
"uberproto": "^1.1.2" | ||
"uberproto": "^1.2.0" | ||
}, | ||
@@ -59,8 +60,18 @@ "devDependencies": { | ||
"babel-core": "^6.1.2", | ||
"babel-plugin-add-module-exports": "^0.1.1", | ||
"babel-preset-es2015": "^6.1.2", | ||
"body-parser": "^1.14.1", | ||
"feathers": "^1.1.1", | ||
"feathers-service-tests": "^0.3.0", | ||
"feathers-service-tests": "^0.5.0", | ||
"jshint": "^2.8.0", | ||
"mocha": "^2.3.3" | ||
}, | ||
"babel": { | ||
"plugins": [ | ||
"add-module-exports" | ||
], | ||
"presets": [ | ||
"es2015" | ||
] | ||
} | ||
} |
156
README.md
@@ -17,3 +17,3 @@ # feathers-memory | ||
Creating an in-memory service is this simple: | ||
You can create an in-memory service with no options: | ||
@@ -27,16 +27,14 @@ ```js | ||
### Complete Example | ||
## Complete Example | ||
Here is an example of a Feathers server with a `todos` in-memory service. | ||
Here is an example of a Feathers server with a `todos` in-memory service that supports pagination: | ||
```js | ||
// server.js | ||
var feathers = require('feathers'), | ||
bodyParser = require('body-parser'), | ||
memory = require('feathers-memory'); | ||
// app.js | ||
var feathers = require('feathers'); | ||
var bodyParser = require('body-parser'); | ||
var memory = require('feathers-memory'); | ||
// Create a feathers instance. | ||
var app = feathers() | ||
// Setup the public folder. | ||
.use(feathers.static(__dirname + '/public')) | ||
// Enable Socket.io | ||
@@ -49,9 +47,24 @@ .configure(feathers.socketio()) | ||
// Turn on URL-encoded parser for REST services | ||
.use(bodyParser.urlencoded({ extended: true })) | ||
.use(bodyParser.urlencoded({ extended: true })); | ||
// Connect to the db, create and register a Feathers service. | ||
app.use('/todos', memory()); | ||
// Create an in-memory Feathers service with a default page size of 2 items | ||
// and a maximum size of 4 | ||
app.use('/todos', memory({ | ||
paginate: { | ||
default: 2, | ||
max: 4 | ||
} | ||
})); | ||
// Create a dummy Todo | ||
app.service('todos').create({ | ||
text: 'Server todo', | ||
complete: false | ||
}).then(function(todo) { | ||
console.log('Created todo', todo); | ||
}); | ||
// Start the server. | ||
var port = 8080; | ||
var port = 3030; | ||
app.listen(port, function() { | ||
@@ -62,28 +75,96 @@ console.log('Feathers server listening on port ' + port); | ||
You can run this example by using `node examples/basic` and going to [localhost:8080/todos](http://localhost:8080/todos). You should see an empty array. That's because you don't have any Todos yet but you now have full CRUD for your new todos service. | ||
You can run this example by using `node examples/app` and going to [localhost:3030/todos](http://localhost:3030/todos). You will see the test Todo that we created at the end of that file. | ||
### Extending | ||
## Extending | ||
You can also extend any of the feathers services to do something custom. | ||
There are several ways to extend the basic CRUD functionality of this service. | ||
_Keep in mind that calling the original service methods will return a Promise that resolves with the value._ | ||
### feathers-hooks | ||
The most flexible option is weaving in functionality through [feathers-hooks](https://github.com/feathersjs/feathers-hooks), for example, `createdAt` and `updatedAt` timestamps could be added like this: | ||
```js | ||
var feathers = require('feathers'); | ||
var hooks = require('feathers-hooks'); | ||
var memory = require('feathers-memory'); | ||
var app = feathers(); | ||
var myUserService = memory().extend({ | ||
find: function(params, cb){ | ||
// Do something awesome! | ||
var app = feathers() | ||
.configure(hooks()) | ||
.use('/todos', memory({ | ||
paginate: { | ||
default: 2, | ||
max: 4 | ||
} | ||
})); | ||
console.log('I am extending the find method'); | ||
var stripIds = function() { | ||
delete hook.data.id; | ||
next(); | ||
} | ||
this._super.apply(this, arguments); | ||
var updateTimestamp = function(hook, next) { | ||
hook.data.updatedAt = new Date(); | ||
next(); | ||
} | ||
app.service('todos').before({ | ||
// You can create a single hook like this | ||
create: function(hook, next) { | ||
hook.data.createdAt = new Date(); | ||
next(); | ||
}, | ||
// Or you can chain multiple hooks like this | ||
update: [stripIds, updateTimeStamp] | ||
}); | ||
app.listen(3030); | ||
``` | ||
### Classes (ES6) | ||
The module also exports a Babel transpiled ES6 class as `Service` that can be directly extended like this: | ||
```js | ||
import { Service } from 'feathers-memory'; | ||
class MyService extends Service { | ||
create(data, params) { | ||
data.created_at = new Date(); | ||
return super.create(data, params).then(todo); | ||
} | ||
} | ||
app.use('/todos', new MyService({ | ||
paginate: { | ||
default: 2, | ||
max: 4 | ||
} | ||
})); | ||
``` | ||
### Uberproto (ES5) | ||
You can also use `.extend` on a service instance (extension is provided by [Uberproto](https://github.com/daffl/uberproto)): | ||
```js | ||
var myService = memory({ | ||
paginate: { | ||
default: 2, | ||
max: 4 | ||
} | ||
}).extend({ | ||
create: function(data) { | ||
data.created_at = new Date(); | ||
return this._super.apply(this, arguments); | ||
} | ||
}); | ||
app.configure(feathers.rest()) | ||
.use('/users', myUserService) | ||
.listen(8080); | ||
app.use('/todos', myService); | ||
``` | ||
**Note:** _this is more for backwards compatibility. We recommend the usage of hooks as they are easier to test, easier to maintain and are more flexible._ | ||
@@ -97,4 +178,22 @@ ## Options | ||
- `store` - An object with id to item assignments to pre-initialize the data store | ||
- `paginate` - A pagination object containing a `default` and `max` page size (see below) | ||
## Pagination | ||
When initializing the service you can set the following pagination options in the `paginate` object: | ||
- `default` - Sets the default number of items | ||
- `max` - Sets the maximum allowed number of items per page (even if the `$limit` query parameter is set higher) | ||
When `paginate.default` is set, `find` will return an object (instead of the normal array) in the following form: | ||
``` | ||
{ | ||
"total": "<total number of records>", | ||
"limit": "<max number of items per page>", | ||
"skip": "<number of skipped items (offset)>", | ||
"data": [/* data */] | ||
} | ||
``` | ||
## Query Parameters | ||
@@ -106,5 +205,5 @@ | ||
// Find all recipes that include salt, limit to 10, only include name field. | ||
{"ingredients":"salt", "$limit":10, "$select": { "name" :1 } } // JSON | ||
{"ingredients":"salt", "$limit":10, "$select": ["name"] } } // JSON | ||
GET /?ingredients=salt&$limit=10&$select[name]=1 // HTTP | ||
GET /?ingredients=salt&$limit=10&$select[]=name // HTTP | ||
``` | ||
@@ -158,3 +257,4 @@ | ||
`$select` support in a query allows you to pick which fields to include or exclude in the results. Note: you can use the include syntax or the exclude syntax, not both together. See the section on [`Projections`](https://github.com/louischatriot/nedb#projections) in the NeDB docs. | ||
`$select` support in a query allows you to pick which fields to include or exclude in the results. | ||
``` | ||
@@ -161,0 +261,0 @@ // Only retrieve name. |
Sorry, the diff of this file is not supported yet
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
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
21288
10
256
383
5
9
+ Addedbabel-polyfill@^6.2.0
+ Addedbabel-polyfill@6.26.0(transitive)
+ Addedbabel-runtime@6.26.0(transitive)
+ Addedcore-js@2.6.12(transitive)
+ Addedregenerator-runtime@0.10.50.11.1(transitive)
Updateduberproto@^1.2.0