resource-schema
Advanced tools
Comparing version 0.7.0 to 0.8.0
384
lib/index.js
// Generated by CoffeeScript 1.8.0 | ||
var RESERVED_KEYWORDS, ResourceSchema, dot, q, _, | ||
var RESERVED_KEYWORDS, ResourceSchema, clone, deepExtend, dot, q, _, | ||
__bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, | ||
@@ -12,5 +12,9 @@ __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; | ||
RESERVED_KEYWORDS = ['$find', '$get', '$set', '$field', '$optional']; | ||
clone = require('clone'); | ||
deepExtend = require('./deep_extend'); | ||
RESERVED_KEYWORDS = ['$find', '$get', '$set', '$field', '$optional', '$validate', '$match', '$type', '$isArray']; | ||
/* | ||
@@ -24,5 +28,7 @@ normalized schema: | ||
dynamicField: { | ||
$find: -> | ||
$get: -> | ||
$set: -> | ||
$validate: (value) -> | ||
match: -> | ||
$find: (value, done) -> | ||
$get: (resources, request, done) -> | ||
$set: (models, request, done) -> | ||
} | ||
@@ -36,8 +42,8 @@ } | ||
this.options = options != null ? options : {}; | ||
this._normalizeQueryParams = __bind(this._normalizeQueryParams, this); | ||
this._normalizeSchema = __bind(this._normalizeSchema, this); | ||
this._getSchemaFromModel = __bind(this._getSchemaFromModel, this); | ||
this._generateSchemaFromModel = __bind(this._generateSchemaFromModel, this); | ||
this._getResourceAndModelFields = __bind(this._getResourceAndModelFields, this); | ||
this._convertKeysToDotStrings = __bind(this._convertKeysToDotStrings, this); | ||
this._selectValidResourceSearchFields = __bind(this._selectValidResourceSearchFields, this); | ||
this._selectValidQuerySearchFields = __bind(this._selectValidQuerySearchFields, this); | ||
this._getModelSelectFields = __bind(this._getModelSelectFields, this); | ||
@@ -48,7 +54,7 @@ this._getAddFields = __bind(this._getAddFields, this); | ||
this._getGroupQuery = __bind(this._getGroupQuery, this); | ||
this._resolveResourceGetPromises = __bind(this._resolveResourceGetPromises, this); | ||
this._resolveResourceSetPromises = __bind(this._resolveResourceSetPromises, this); | ||
this._applyGetters = __bind(this._applyGetters, this); | ||
this._applySetters = __bind(this._applySetters, this); | ||
this._createResourceFromModel = __bind(this._createResourceFromModel, this); | ||
this._createModelFromResource = __bind(this._createModelFromResource, this); | ||
this._getQueryConfigPromise = __bind(this._getQueryConfigPromise, this); | ||
this._getMongoQuery = __bind(this._getMongoQuery, this); | ||
this.send = __bind(this.send, this); | ||
@@ -60,3 +66,3 @@ this._getOne = __bind(this._getOne, this); | ||
} else { | ||
this.schema = this._getSchemaFromModel(this.Model); | ||
this.schema = this._generateSchemaFromModel(this.Model); | ||
} | ||
@@ -80,2 +86,5 @@ } | ||
var limit, modelSelect, sendResources; | ||
if (!this._isValid(req.query, res)) { | ||
return; | ||
} | ||
sendResources = (function(_this) { | ||
@@ -87,3 +96,6 @@ return function(err, modelsFound) { | ||
}); | ||
return _this._resolveResourceGetPromises(resources, modelsFound, req.query).then(function() { | ||
return _this._applyGetters(resources, modelsFound, { | ||
req: req, | ||
res: res | ||
}).then(function() { | ||
res.body = resources; | ||
@@ -96,12 +108,15 @@ return next(); | ||
modelSelect = this._getModelSelectFields(req.query); | ||
return this._getQueryConfigPromise(req.query).then((function(_this) { | ||
return function(queryConfig) { | ||
return this._getMongoQuery(req.query, { | ||
req: req, | ||
res: res | ||
}).then((function(_this) { | ||
return function(mongoQuery) { | ||
var modelQuery; | ||
if (_this.options.groupBy) { | ||
modelQuery = _this.Model.aggregate(); | ||
modelQuery.match(queryConfig); | ||
modelQuery.match(mongoQuery); | ||
modelQuery.group(_this._getGroupQuery()); | ||
} | ||
if (!_this.options.groupBy) { | ||
modelQuery = _this.Model.find(queryConfig); | ||
modelQuery = _this.Model.find(mongoQuery); | ||
modelQuery.select(modelSelect); | ||
@@ -122,2 +137,5 @@ modelQuery.lean(); | ||
var idValue, modelQuery, query, select; | ||
if (!_this._isValid(req.query, res)) { | ||
return; | ||
} | ||
select = _this._getModelSelectFields(req.query); | ||
@@ -141,3 +159,6 @@ idValue = req.params[paramId]; | ||
resource = _this._createResourceFromModel(modelFound, req.query.$select); | ||
return _this._resolveResourceGetPromises([resource], [modelFound], req.query).then(function() { | ||
return _this._applyGetters([resource], [modelFound], { | ||
req: req, | ||
res: res | ||
}).then(function() { | ||
res.body = resource; | ||
@@ -159,14 +180,27 @@ return next(); | ||
return function(req, res, next) { | ||
var model, newModelData; | ||
newModelData = _this._createModelFromResource(req.body); | ||
model = new _this.Model(newModelData); | ||
return model.save(function(err, modelSaved) { | ||
var resource; | ||
if (err) { | ||
res.send(400, err); | ||
} | ||
resource = _this._createResourceFromModel(modelSaved, req.query.$select); | ||
res.status(201); | ||
res.body = resource; | ||
return next(); | ||
var newModelData, resource; | ||
if (!_this._isValid(req.query, res)) { | ||
return; | ||
} | ||
resource = req.body; | ||
if (!_this._isValid(resource, res)) { | ||
return; | ||
} | ||
_this._convertTypes(resource, res); | ||
newModelData = _this._createModelFromResource(resource); | ||
return _this._applySetters([resource], [newModelData], { | ||
req: req, | ||
res: res | ||
}).then(function() { | ||
var model; | ||
model = new _this.Model(newModelData); | ||
return model.save(function(err, modelSaved) { | ||
if (err) { | ||
return res.status(400).send(err); | ||
} | ||
resource = _this._createResourceFromModel(modelSaved, req.query.$select); | ||
res.status(201); | ||
res.body = resource; | ||
return next(); | ||
}); | ||
}); | ||
@@ -186,2 +220,8 @@ }; | ||
var idValue, newModelData, query; | ||
if (!_this._isValid(req.query, res)) { | ||
return; | ||
} | ||
if (!_this._isValid(req.body, res)) { | ||
return; | ||
} | ||
newModelData = _this._createModelFromResource(req.body); | ||
@@ -191,19 +231,20 @@ idValue = req.params[paramId]; | ||
query[paramId] = idValue; | ||
return _this.Model.findOne(query).lean().exec(function(err, modelFound) { | ||
return _this._resolveResourceSetPromises(req.body, modelFound, {}).then(function() { | ||
return _this.Model.findOneAndUpdate(query, newModelData, { | ||
upsert: true | ||
}).lean().exec(function(err, modelUpdated) { | ||
var resource; | ||
if (err) { | ||
res.send(400, err); | ||
} | ||
if (!modelUpdated) { | ||
res.send(404, 'resource not found'); | ||
} | ||
resource = _this._createResourceFromModel(modelUpdated, req.query.$select); | ||
res.status(200); | ||
res.body = resource; | ||
return next(); | ||
}); | ||
return _this._applySetters([req.body], [newModelData], { | ||
req: req, | ||
res: res | ||
}).then(function() { | ||
return _this.Model.findOneAndUpdate(query, newModelData, { | ||
upsert: true | ||
}).lean().exec(function(err, modelUpdated) { | ||
var resource; | ||
if (err) { | ||
return res.send(400, err); | ||
} | ||
if (!modelUpdated) { | ||
return res.send(404, 'resource not found'); | ||
} | ||
resource = _this._createResourceFromModel(modelUpdated, req.query.$select); | ||
res.status(200); | ||
res.body = resource; | ||
return next(); | ||
}); | ||
@@ -224,2 +265,5 @@ }); | ||
var idValue, query; | ||
if (!_this._isValid(req.query, res)) { | ||
return; | ||
} | ||
idValue = req.params[paramId]; | ||
@@ -230,6 +274,6 @@ query = {}; | ||
if (err) { | ||
res.send(400, err); | ||
return res.status(400).send(err); | ||
} | ||
if (removedInstance == null) { | ||
res.send(404, "Resource with id " + idValue + " not found from " + _this.Model.modelName + " collection"); | ||
res.status(404).send("Resource with id " + idValue + " not found from " + _this.Model.modelName + " collection"); | ||
} | ||
@@ -261,9 +305,10 @@ res.status(204); | ||
ResourceSchema.prototype._getQueryConfigPromise = function(requestQuery) { | ||
var d, deferred, modelQuery, queryField, queryPromises, querySearchFields, resourceField, resourceSearchFields, value; | ||
modelQuery = _.clone(this.options.defaultQuery) || {}; | ||
ResourceSchema.prototype._getMongoQuery = function(requestQuery, _arg) { | ||
var d, deferred, modelQuery, queryPromises, req, res, resourceField, resourceSearchFields, value; | ||
req = _arg.req, res = _arg.res; | ||
modelQuery = clone(this.options.defaultQuery) || {}; | ||
deferred = q.defer(); | ||
queryPromises = []; | ||
resourceSearchFields = this._selectValidResourceSearchFields(requestQuery); | ||
querySearchFields = this._selectValidQuerySearchFields(requestQuery); | ||
this._convertTypes(resourceSearchFields); | ||
if (resourceSearchFields) { | ||
@@ -274,6 +319,11 @@ for (resourceField in resourceSearchFields) { | ||
d = q.defer(); | ||
this.schema[resourceField].$find(value, function(err, query) { | ||
_(modelQuery).extend(query); | ||
return d.resolve(); | ||
}); | ||
this.schema[resourceField].$find(value, { | ||
req: req, | ||
res: res | ||
}, (function(_this) { | ||
return function(err, query) { | ||
deepExtend(modelQuery, query); | ||
return d.resolve(); | ||
}; | ||
})(this)); | ||
queryPromises.push(d.promise); | ||
@@ -285,13 +335,2 @@ } else if (this.schema[resourceField].$field) { | ||
} | ||
if (querySearchFields) { | ||
for (queryField in querySearchFields) { | ||
value = querySearchFields[queryField]; | ||
d = q.defer(); | ||
this.options.queryParams[queryField](value, function(err, query) { | ||
_(modelQuery).extend(query); | ||
return d.resolve(); | ||
}); | ||
queryPromises.push(d.promise); | ||
} | ||
} | ||
q.all(queryPromises).then(function() { | ||
@@ -354,4 +393,5 @@ return deferred.resolve(modelQuery); | ||
ResourceSchema.prototype._resolveResourceSetPromises = function(resource, model, queryParams) { | ||
var config, d, resourceField, setPromises, _ref; | ||
ResourceSchema.prototype._applySetters = function(resources, models, _arg) { | ||
var config, d, req, res, resourceField, setPromises, _ref; | ||
req = _arg.req, res = _arg.res; | ||
setPromises = []; | ||
@@ -363,3 +403,7 @@ _ref = this.schema; | ||
d = q.defer(); | ||
config.$set(resource[resourceField], model, queryParams, function(err, results) { | ||
config.$set(models, { | ||
req: req, | ||
res: res, | ||
resources: resources | ||
}, function(err, results) { | ||
return d.resolve(); | ||
@@ -378,6 +422,7 @@ }); | ||
ResourceSchema.prototype._resolveResourceGetPromises = function(resources, models, query) { | ||
var config, getPromises, resourceField, resourceSelectFields, _ref; | ||
ResourceSchema.prototype._applyGetters = function(resources, models, _arg) { | ||
var config, getPromises, req, res, resourceField, resourceSelectFields, _ref; | ||
req = _arg.req, res = _arg.res; | ||
getPromises = []; | ||
resourceSelectFields = this._getResourceSelectFields(query); | ||
resourceSelectFields = this._getResourceSelectFields(req.query); | ||
_ref = this.schema; | ||
@@ -390,5 +435,9 @@ for (resourceField in _ref) { | ||
d = q.defer(); | ||
config.$get(resources, models, query, function(err, results) { | ||
config.$get(resources, { | ||
req: req, | ||
res: res, | ||
models: models | ||
}, function(err, results) { | ||
if (err) { | ||
console.log(err); | ||
throw new Error(err); | ||
} | ||
@@ -440,3 +489,3 @@ return d.resolve(); | ||
ResourceSchema.prototype._getLimit = function(query) { | ||
return query.$limit || this.options.defaultLimit || 100; | ||
return query.$limit || this.options.defaultLimit; | ||
}; | ||
@@ -464,3 +513,4 @@ | ||
/* | ||
Get all valid $add fields from the query. Used to select $optional fields from schema | ||
Get all valid $add fields from the query. The add fields are used to | ||
select $optional fields from schema | ||
@param [Object] query - query params from client | ||
@@ -504,26 +554,2 @@ @returns [Array] valid keys to add from schema | ||
/* | ||
Select valid properties from query that can be used for filtering resources | ||
@param [Object] query - query params from client | ||
@returns [Object] valid query fields and their values | ||
*/ | ||
ResourceSchema.prototype._selectValidQuerySearchFields = function(query) { | ||
var field, queryDotString, queryParamFields, validFields, value; | ||
if (!this.options.queryParams) { | ||
return {}; | ||
} | ||
queryDotString = this._convertKeysToDotStrings(query); | ||
queryParamFields = Object.keys(this.options.queryParams); | ||
validFields = {}; | ||
for (field in queryDotString) { | ||
value = queryDotString[field]; | ||
if (__indexOf.call(queryParamFields, field) >= 0) { | ||
dot.set(validFields, field, value); | ||
} | ||
} | ||
return this._convertKeysToDotStrings(validFields); | ||
}; | ||
/* | ||
Select valid properties from query that can be used for filtering resources in the schema | ||
@@ -550,3 +576,4 @@ @param [Object] query - query params from client | ||
/* | ||
Collapse all nested fields dot format | ||
Collapse all nested fields to dot format. Ignore Reserved Keywords. | ||
This is used for the schema, the query params, and the incoming resources | ||
@example {a: {b: 1}} -> {'a.b': 1} | ||
@@ -594,3 +621,3 @@ */ | ||
ResourceSchema.prototype._getSchemaFromModel = function(Model) { | ||
ResourceSchema.prototype._generateSchemaFromModel = function(Model) { | ||
var schema, schemaKey, schemaKeys, _i, _len; | ||
@@ -646,7 +673,158 @@ schemaKeys = Object.keys(Model.schema.paths); | ||
} | ||
_(normalizedSchema).extend(this._normalizeQueryParams()); | ||
return normalizedSchema; | ||
}; | ||
ResourceSchema.prototype._normalizeQueryParams = function() { | ||
var config, normalizedParams, param, _ref; | ||
normalizedParams = {}; | ||
if (this.options.queryParams) { | ||
_ref = this.options.queryParams; | ||
for (param in _ref) { | ||
config = _ref[param]; | ||
if (typeof config === 'function') { | ||
normalizedParams[param] = { | ||
$find: config | ||
}; | ||
} else if (typeof config === 'object') { | ||
normalizedParams[param] = config; | ||
} else { | ||
throw new Error("QueryParam config for " + param + " must be either a configuration object or a function"); | ||
} | ||
} | ||
} | ||
return normalizedParams; | ||
}; | ||
/* | ||
Check validity of object with $validate and $match on schema | ||
*/ | ||
ResourceSchema.prototype._isValid = function(obj, res) { | ||
var key, normalizedObj, v, validateValue, value, _i, _len; | ||
validateValue = (function(_this) { | ||
return function(key, value, res) { | ||
var _ref, _ref1; | ||
if ((_ref = _this.schema[key]) != null ? _ref.$validate : void 0) { | ||
if (!_this.schema[key].$validate(value)) { | ||
res.status(400).send("'" + key + "' is invalid"); | ||
return false; | ||
} | ||
} | ||
if ((_ref1 = _this.schema[key]) != null ? _ref1.$match : void 0) { | ||
if (!_this.schema[key].$match.test(value)) { | ||
res.status(400).send("'" + key + "' is invalid"); | ||
return false; | ||
} | ||
} | ||
return true; | ||
}; | ||
})(this); | ||
normalizedObj = this._convertKeysToDotStrings(obj); | ||
for (key in normalizedObj) { | ||
value = normalizedObj[key]; | ||
if (Array.isArray(value)) { | ||
for (_i = 0, _len = value.length; _i < _len; _i++) { | ||
v = value[_i]; | ||
if (!validateValue(key, v, res)) { | ||
return false; | ||
} | ||
} | ||
} else { | ||
if (!validateValue(key, value, res)) { | ||
return false; | ||
} | ||
} | ||
} | ||
return true; | ||
}; | ||
/* | ||
By default, all query parameters are sent as strings. | ||
This method converts those strings to the appropriate types for data manipulation | ||
Supports: | ||
- String | ||
- Date | ||
- Number | ||
- Boolean | ||
- mongoose.Types.ObjectId and other newable objects | ||
TODO: needs to be tested | ||
*/ | ||
ResourceSchema.prototype._convertTypes = function(obj, res) { | ||
var convert, i, key, send400, v, value, _ref, _ref1, _results; | ||
send400 = (function(_this) { | ||
return function(type, key, value) { | ||
return res.status(400).send("'" + value + "' is an invalid Date for field '" + key + "'"); | ||
}; | ||
})(this); | ||
convert = (function(_this) { | ||
return function(key, value) { | ||
var date, e, newValue, number; | ||
switch (_this.schema[key].$type) { | ||
case String: | ||
return value; | ||
case Number: | ||
number = parseFloat(value); | ||
if (isNaN(number)) { | ||
send400('Number', key, value); | ||
} | ||
return number; | ||
case Boolean: | ||
if ((value === 'true') || (value === true)) { | ||
return true; | ||
} else if ((value === 'false') || (value === true)) { | ||
return false; | ||
} else { | ||
return send400('Boolean', key, value); | ||
} | ||
break; | ||
case Date: | ||
date = new Date(value); | ||
if (isNaN(date.getTime())) { | ||
send400('Date', key, value); | ||
} | ||
return date; | ||
default: | ||
try { | ||
newValue = new _this.schema[key].$type(value); | ||
return newValue; | ||
} catch (_error) { | ||
e = _error; | ||
return res.status(400).send(e); | ||
} | ||
} | ||
}; | ||
})(this); | ||
_results = []; | ||
for (key in obj) { | ||
value = obj[key]; | ||
if (((_ref = this.schema[key]) != null ? _ref.$type : void 0) == null) { | ||
continue; | ||
} | ||
if ((_ref1 = this.schema[key]) != null ? _ref1.$isArray : void 0) { | ||
if (!Array.isArray(value)) { | ||
obj[key] = [value]; | ||
} | ||
_results.push((function() { | ||
var _ref2, _results1; | ||
_ref2 = obj[key]; | ||
_results1 = []; | ||
for (i in _ref2) { | ||
v = _ref2[i]; | ||
_results1.push(obj[key][i] = convert(key, v)); | ||
} | ||
return _results1; | ||
})()); | ||
} else { | ||
_results.push(obj[key] = convert(key, value)); | ||
} | ||
} | ||
return _results; | ||
}; | ||
return ResourceSchema; | ||
})(); |
{ | ||
"name": "resource-schema", | ||
"version": "0.7.0", | ||
"version": "0.8.0", | ||
"description": "Define schemas for RESTful resources from mongoose models, and generate middleware to GET, POST, PUT, and DELETE to those resources.", | ||
@@ -21,3 +21,5 @@ "author": "Good Eggs <open-source@goodeggs.com>", | ||
"q": "^1.0.1", | ||
"async": "^0.9.0" | ||
"async": "^0.9.0", | ||
"deep-extend": "^0.3.2", | ||
"clone": "^0.1.18" | ||
}, | ||
@@ -24,0 +26,0 @@ "devDependencies": { |
304
README.md
@@ -8,2 +8,306 @@ # Resource Schema | ||
## Why ResourceSchema? | ||
ResourceSchema allows you to define complex RESTful resources in a simple and declarative way. For example: | ||
```coffeescript | ||
schema = { | ||
'_id' | ||
'name' | ||
'isActive': 'active' | ||
'totalQuantitySold': | ||
$optional: true | ||
$get: addTotalQuantitySold # method defined elsewhere | ||
} | ||
queryParams = | ||
'soldOn': | ||
$type: String | ||
$isArray: true | ||
$match: /[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/ | ||
$find: fibrous (days) -> { 'day': $in: days } | ||
'fromLastWeek': | ||
$type: Boolean | ||
$find: fibrous (days) -> { 'day': $gt: '2014-10-12' } | ||
resource = ResourceSchema(Product, schema, {queryParams}) | ||
app.get '/', resource.get(), resource.send | ||
app.post '/', resource.post(), resource.send | ||
app.put '/:_id', resource.put('_id'), resource.send | ||
app.get '/:_id', resource.get('_id'), resource.send | ||
app.delete '/:_id', resource.delete('_id'), resource.send | ||
``` | ||
This abstracts away a lot of the boilerplate such as error handling or validating query parameters, and allows you to focus on higher-level resource design. | ||
## Generating Middleware | ||
Once you have defined a new resource, call get, post, put, or delete to generate the appropriate middleware to handle the request. | ||
``` coffeescript | ||
resource = new ResourceSchema(Model, schema, options) | ||
app.get '/products', resource.get(), (req, res, next) -> | ||
# resources are on res.body | ||
``` | ||
the middleware will attach the resources to res.body, which can then be used by other pieces of middleware, or sent immediately back to the client | ||
### resource.get() | ||
Generate middleware to handle GET requests for multiple resources. | ||
### resource.post() | ||
Generate middleware to handle POST requests to a resource. | ||
### resource.put() | ||
Generate middleware to handle PUT requests to a resource. | ||
### resource.delete() | ||
Generate middleware to handle DELETE requests to a resource. | ||
### resource.send | ||
Convenience method for sending the resource back to the client. | ||
``` coffeescript | ||
resource = new ResourceSchema(Model, schema, options) | ||
app.get '/products', resource.get(), resource.send | ||
``` | ||
## Defining a schema | ||
### $field | ||
Maps a mongoose model field to a resource field. | ||
``` javascript | ||
schema = { | ||
'name': { $field: 'name' } | ||
} | ||
``` | ||
We can also define this with a shorthand notation: | ||
``` javascript | ||
schema = { | ||
'name': 'name' | ||
} | ||
``` | ||
Or even simpler with coffeescript: | ||
``` javascript | ||
schema = { | ||
'name' | ||
} | ||
``` | ||
Note, this can be used to rename a model field to a new name on the resource: | ||
``` javascript | ||
schema = { | ||
'category.name': 'categoryName' | ||
} | ||
// { | ||
// category: { | ||
// name: 'value' | ||
// } | ||
// } | ||
``` | ||
### $get | ||
Dynamically get the value whenever a resource is retrieved. | ||
``` javascript | ||
schema = { | ||
'totalProductsSold': { | ||
$get: (resourcesToReturn, {models, req, res, next}, done) -> | ||
resourcesToReturn.forEach (resource) -> | ||
resource.totalProductsSold = 10 | ||
done() | ||
} | ||
} | ||
``` | ||
### $set | ||
Dynamically set the value whenever a resource is saved or updated | ||
``` javascript | ||
schema = { | ||
'name': { | ||
$set: (modelsToSave, {resources, req, res, next}, done) -> | ||
modelsToSave.forEach (model) -> | ||
model.name = model.name.toLowerCase() | ||
done() | ||
} | ||
} | ||
``` | ||
### $find | ||
Dynamically find resources with the provided query value. Return an object that will extend the mongoose query. $find is used to define query parameters. | ||
``` javascript | ||
schema = { | ||
'soldOn': | ||
$find: fibrous (days, {req, res, next}) -> | ||
{ 'day': $in: days } | ||
} | ||
``` | ||
### $optional | ||
If true, do not include the value in the resource unless specifically requested by the client with the '$add' query parameter | ||
``` coffeescript | ||
// GET /api/products?$add=name | ||
schema = { | ||
'name': { | ||
$optional: true | ||
$field: 'name' | ||
} | ||
} | ||
``` | ||
### $validate | ||
Return a 400 invalid request if the provided value does not pass the validation test. | ||
``` coffeescript | ||
schema = { | ||
'date': { | ||
$validate: (value) -> | ||
/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/.test(value) | ||
} | ||
} | ||
``` | ||
### $match | ||
Return a 400 invalid request if the provided value does match the given regular expression. | ||
``` coffeescript | ||
schema = { | ||
'date': { | ||
$match:/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/ | ||
} | ||
} | ||
``` | ||
### $type | ||
Convert the type of the value. | ||
Valid types include | ||
- String | ||
- Date | ||
- Number | ||
- Boolean | ||
- and any other "newable" class | ||
``` coffeescript | ||
schema = { | ||
'active': { | ||
$type: Boolean | ||
} | ||
} | ||
``` | ||
This is especially valuable for query parameters, since they are all a string by default. | ||
### $isArray | ||
Ensure that the query parameter is an Array. | ||
``` coffeescript | ||
schema = { | ||
'daysToSelect': { | ||
$isArray: Boolean | ||
$find: (days, context, done) -> ... | ||
} | ||
} | ||
``` | ||
## options | ||
### options.defaultLimit | ||
Set the default number of the resources that will be sent for GET requests. | ||
``` coffeescript | ||
new ResourceSchema(Product, schema, { | ||
defaultLimit: 100 | ||
}) | ||
``` | ||
### options.defaultQuery | ||
Set the default query for this resource. All other query parameters will extend this query. | ||
``` coffeescript | ||
new ResourceSchema(Product, schema, { | ||
defaultQuery: { | ||
active: true | ||
createdAt: $gt: '2013-01-01' | ||
} | ||
}) | ||
``` | ||
### options.queryParams | ||
Define query parameters for this resource using the $find method. Note that these query parameters can be defined directly on the schema, but you can define them here if you prefer (since query parameters are often not part of the resource being returned). | ||
```coffeescript | ||
queryParams = | ||
'soldOn': | ||
$type: String | ||
$isArray: true | ||
$match: /[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/ | ||
$find: fibrous (days) -> { 'day': $in: days } | ||
'fromLastWeek': | ||
$type: Boolean | ||
$find: fibrous (days) -> { 'day': $gt: '2014-10-12' } | ||
``` | ||
## Querying the resources | ||
ResourceSchema automatically adds several utilities for interacting with your resources. | ||
### Querying by resource field | ||
Query by any resource field with a $field or a $find attribute. | ||
``` | ||
GET /products?name=strawberry | ||
GET /products?categrory[name]=fruit | ||
``` | ||
Note that you can query nested fields with Express' [bracket] notation. | ||
### $select | ||
Select fields to return on the resource. Similar to mongoose select. | ||
``` | ||
GET /products?$select=name&$select=active | ||
GET /products?$select[]=name&$select[]=active | ||
GET /products?$select=name%20active | ||
``` | ||
### $limit | ||
Limit the number of resources to return in the response | ||
``` | ||
GET /products?$limit=10 | ||
``` | ||
### $add | ||
Add an $optional field to the response. See $optional schema fields for more details. | ||
``` | ||
GET /products?$add=quantitySold | ||
``` | ||
## Contributing | ||
@@ -10,0 +314,0 @@ |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
56105
9
752
328
6
+ Addedclone@^0.1.18
+ Addeddeep-extend@^0.3.2
+ Addedclone@0.1.19(transitive)
+ Addeddeep-extend@0.3.3(transitive)