Comparing version 0.15.1 to 1.0.0
@@ -14,2 +14,3 @@ { | ||
"no-const-assign": 2, | ||
"no-undef": ["error", {"typeof": true}], | ||
"no-unused-vars": [2, {"vars": "all", "args": "none"}], | ||
@@ -16,0 +17,0 @@ "prefer-const": 1 |
## Change Log | ||
**1.0.0** <small>_Sep 13, 2019_</small> - [Diff](https://github.com/bookshelf/bookshelf/compare/0.15.1...1.0.0) | ||
#### Breaking changes | ||
- This version requires Node.js 8+ **if** using a Knex version greater than 0.18.4, otherwise Node.js 6 is still supported: [#1991](https://github.com/bookshelf/bookshelf/pull/1991) | ||
- Make `require: true` the default for Model#fetch: [#2006](https://github.com/bookshelf/bookshelf/pull/2006) | ||
- Remove some Model and Collection lodash based methods: [#2005](https://github.com/bookshelf/bookshelf/pull/2005) | ||
- Change Collection#where so it behaves like Model#where: [#2001](https://github.com/bookshelf/bookshelf/pull/2001) | ||
- Move all plugins to their own repositories: [#2000](https://github.com/bookshelf/bookshelf/pull/2000) | ||
- Promote some useful plugins to core: [#1992](https://github.com/bookshelf/bookshelf/pull/1992), [#1993](https://github.com/bookshelf/bookshelf/pull/1993), [#1996](https://github.com/bookshelf/bookshelf/pull/1996) | ||
#### Enhancements | ||
- Refresh model attributes after a save operation: [#2012](https://github.com/bookshelf/bookshelf/pull/2012) | ||
#### Bug fixes | ||
- Fix missing columns after save: [#2012](https://github.com/bookshelf/bookshelf/pull/2012) | ||
- Fix Case Converter plugin overriding any previously defined parse methods: [#2000](https://github.com/bookshelf/bookshelf/pull/2000), [case-converter-plugin@1.0.0](https://github.com/bookshelf/case-converter-plugin/releases/tag/v1.0.0) | ||
- Fix registry saving models inadvertently across different bookshelf instances: [#1996](https://github.com/bookshelf/bookshelf/pull/1996) | ||
#### Documentation | ||
- Add example of how to use custom collections: [#2015](https://github.com/bookshelf/bookshelf/pull/2015) | ||
- Improve documentation related to debug mode: [#2014](https://github.com/bookshelf/bookshelf/pull/2014) | ||
- Add note that count methods return String with Postgres: [#2013](https://github.com/bookshelf/bookshelf/pull/2013) | ||
- Fix typo in Readme: [#1998](https://github.com/bookshelf/bookshelf/pull/1998) | ||
- Better Plugin Docs: [#1992](https://github.com/bookshelf/bookshelf/pull/1992), [#1993](https://github.com/bookshelf/bookshelf/pull/1993), [#1996](https://github.com/bookshelf/bookshelf/pull/1996), [#2000](https://github.com/bookshelf/bookshelf/pull/2000) | ||
#### Dependencies | ||
- Update lint-staged to version 9.1.0: [#1994](https://github.com/bookshelf/bookshelf/pull/1994) | ||
- Update bluebird to 3.5.5: [#1991](https://github.com/bookshelf/bookshelf/pull/1991) | ||
- Update lodash to 4.17.14: [#1991](https://github.com/bookshelf/bookshelf/pull/1991) | ||
- Update mocha to version 6.1.4: [#1991](https://github.com/bookshelf/bookshelf/pull/1991) | ||
- Update mysql to version 2.17.1: [#1991](https://github.com/bookshelf/bookshelf/pull/1991) | ||
- Update pg to version 7.11.0: [#1991](https://github.com/bookshelf/bookshelf/pull/1991) | ||
- Update sinon to version 7.3.2: [#1991](https://github.com/bookshelf/bookshelf/pull/1991) | ||
- Update sinon-chai to version 3.3.0: [#1991](https://github.com/bookshelf/bookshelf/pull/1991) | ||
- Update sqlite3 to version 4.0.9: [#1991](https://github.com/bookshelf/bookshelf/pull/1991) | ||
- Update uuid to version 3.3.2: [#1991](https://github.com/bookshelf/bookshelf/pull/1991) | ||
- Update eslint-config-prettier to 6.0.0: [#1957](https://github.com/bookshelf/bookshelf/pull/1987) | ||
- Update eslint to version 6.0.0: [#1986](https://github.com/bookshelf/bookshelf/pull/1986) | ||
**0.15.1** <small>_Jun 13, 2019_</small> - [Diff](https://github.com/bookshelf/bookshelf/compare/0.15.0...0.15.1) | ||
@@ -4,0 +48,0 @@ |
@@ -13,2 +13,30 @@ // Base Collection | ||
// List of attributes attached directly from the constructor's options object. | ||
// | ||
// RE: 'relatedData' | ||
// It's okay for two `Collection`s to share a `Relation` instance. | ||
// `relatedData` does not mutate itself after declaration. This is only | ||
// here because `clone` needs to duplicate this property. It should not | ||
// be documented as a valid argument for consumer code. | ||
// | ||
// RE: 'attach', 'detach', 'updatePivot', 'withPivot', '_processPivot', '_processPlainPivot', '_processModelPivot' | ||
// It's okay to whitelist also given method references to be copied when cloning | ||
// a collection. These methods are present only when `relatedData` is present and | ||
// its `type` is 'belongsToMany'. So it is safe to put them in the list and use them | ||
// without any additional verification. | ||
// These should not be documented as a valid arguments for consumer code. | ||
const collectionProps = [ | ||
'model', | ||
'comparator', | ||
'relatedData', | ||
// `belongsToMany` pivotal collection properties | ||
'attach', | ||
'detach', | ||
'updatePivot', | ||
'withPivot', | ||
'_processPivot', | ||
'_processPlainPivot', | ||
'_processModelPivot' | ||
]; | ||
/** | ||
@@ -60,30 +88,2 @@ * @class CollectionBase | ||
// List of attributes attached directly from the constructor's options object. | ||
// | ||
// RE: 'relatedData' | ||
// It's okay for two `Collection`s to share a `Relation` instance. | ||
// `relatedData` does not mutate itself after declaration. This is only | ||
// here because `clone` needs to duplicate this property. It should not | ||
// be documented as a valid argument for consumer code. | ||
// | ||
// RE: 'attach', 'detach', 'updatePivot', 'withPivot', '_processPivot', '_processPlainPivot', '_processModelPivot' | ||
// It's okay to whitelist also given method references to be copied when cloning | ||
// a collection. These methods are present only when `relatedData` is present and | ||
// its `type` is 'belongsToMany'. So it is safe to put them in the list and use them | ||
// without any additional verification. | ||
// These should not be documented as a valid arguments for consumer code. | ||
const collectionProps = [ | ||
'model', | ||
'comparator', | ||
'relatedData', | ||
// `belongsToMany` pivotal collection properties | ||
'attach', | ||
'detach', | ||
'updatePivot', | ||
'withPivot', | ||
'_processPivot', | ||
'_processPlainPivot', | ||
'_processModelPivot' | ||
]; | ||
// Copied over from Backbone. | ||
@@ -137,2 +137,20 @@ const setOptions = {add: true, remove: true, merge: true}; | ||
/** | ||
* Returns the first model in the collection or `undefined` if the collection is empty. | ||
* | ||
* @return {Model|undefined} The first model or `undefined`. | ||
*/ | ||
CollectionBase.prototype.first = function() { | ||
return this.at(0); | ||
}; | ||
/** | ||
* Returns the last model in the collection or `undefined` if the collection is empty. | ||
* | ||
* @return {Model|undefined} The last model or `undefined`. | ||
*/ | ||
CollectionBase.prototype.last = function() { | ||
return this.slice(-1)[0]; | ||
}; | ||
/** | ||
* @method | ||
@@ -468,3 +486,3 @@ * @private | ||
delete this._byId[model.cid]; | ||
const index = this.indexOf(model); | ||
const index = this.models.indexOf(model); | ||
this.models.splice(index, 1); | ||
@@ -587,29 +605,2 @@ this.length = this.length - 1; | ||
* @method | ||
* @description | ||
* Return models with matching attributes. Useful for simple cases of `filter`. | ||
* @returns {Model[]} Array of matching models. | ||
*/ | ||
CollectionBase.prototype.where = function(attrs, first) { | ||
if (_.isEmpty(attrs)) return first ? void 0 : []; | ||
return this[first ? 'find' : 'filter'](function(model) { | ||
for (const key in attrs) { | ||
if (attrs[key] !== model.get(key)) return false; | ||
} | ||
return true; | ||
}); | ||
}; | ||
/** | ||
* @method | ||
* @description | ||
* Return the first model with matching attributes. Useful for simple cases of | ||
* `find`. | ||
* @returns {Model} The first matching model. | ||
*/ | ||
CollectionBase.prototype.findWhere = function(attrs) { | ||
return this.where(attrs, true); | ||
}; | ||
/** | ||
* @method | ||
* @private | ||
@@ -698,6 +689,2 @@ * @description | ||
/** | ||
* @method CollectionBase#each | ||
* @see http://lodash.com/docs/#each | ||
*/ | ||
/** | ||
* @method CollectionBase#map | ||
@@ -743,18 +730,2 @@ * @see http://lodash.com/docs/#map | ||
/** | ||
* @method CollectionBase#max | ||
* @see http://lodash.com/docs/#max | ||
*/ | ||
/** | ||
* @method CollectionBase#maxBy | ||
* @see http://lodash.com/docs/#maxBy | ||
*/ | ||
/** | ||
* @method CollectionBase#min | ||
* @see http://lodash.com/docs/#min | ||
*/ | ||
/** | ||
* @method CollectionBase#minBy | ||
* @see http://lodash.com/docs/#minBy | ||
*/ | ||
/** | ||
* @method CollectionBase#toArray | ||
@@ -764,61 +735,5 @@ * @see http://lodash.com/docs/#toArray | ||
/** | ||
* @method CollectionBase#size | ||
* @see http://lodash.com/docs/#size | ||
*/ | ||
/** | ||
* @method CollectionBase#first | ||
* @see http://lodash.com/docs/#first | ||
*/ | ||
/** | ||
* @method CollectionBase#head | ||
* @see http://lodash.com/docs/#head | ||
*/ | ||
/** | ||
* @method CollectionBase#take | ||
* @see http://lodash.com/docs/#take | ||
*/ | ||
/** | ||
* @method CollectionBase#initial | ||
* @see http://lodash.com/docs/#initial | ||
*/ | ||
/** | ||
* @method CollectionBase#tail | ||
* @see http://lodash.com/docs/#tail | ||
*/ | ||
/** | ||
* @method CollectionBase#drop | ||
* @see http://lodash.com/docs/#drop | ||
*/ | ||
/** | ||
* @method CollectionBase#last | ||
* @see http://lodash.com/docs/#last | ||
*/ | ||
/** | ||
* @method CollectionBase#without | ||
* @see http://lodash.com/docs/#without | ||
*/ | ||
/** | ||
* @method CollectionBase#difference | ||
* @see http://lodash.com/docs/#difference | ||
*/ | ||
/** | ||
* @method CollectionBase#indexOf | ||
* @see http://lodash.com/docs/#indexOf | ||
*/ | ||
/** | ||
* @method CollectionBase#shuffle | ||
* @see http://lodash.com/docs/#shuffle | ||
*/ | ||
/** | ||
* @method CollectionBase#lastIndexOf | ||
* @see http://lodash.com/docs/#lastIndexOf | ||
*/ | ||
/** | ||
* @method CollectionBase#isEmpty | ||
* @see http://lodash.com/docs/#isEmpty | ||
*/ | ||
/** | ||
* @method CollectionBase#chain | ||
* @see http://lodash.com/docs/#chain | ||
*/ | ||
// Lodash methods that we want to implement on the Collection. | ||
@@ -829,3 +744,2 @@ // 90% of the core usefulness of Backbone Collections is actually implemented | ||
'forEach', | ||
'each', | ||
'map', | ||
@@ -840,22 +754,4 @@ 'reduce', | ||
'invokeMap', | ||
'max', | ||
'min', | ||
'maxBy', | ||
'minBy', | ||
'toArray', | ||
'size', | ||
'first', | ||
'head', | ||
'take', | ||
'initial', | ||
'tail', | ||
'drop', | ||
'last', | ||
'without', | ||
'difference', | ||
'indexOf', | ||
'shuffle', | ||
'lastIndexOf', | ||
'isEmpty', | ||
'chain' | ||
'isEmpty' | ||
]; | ||
@@ -862,0 +758,0 @@ |
@@ -10,5 +10,2 @@ // Base Model | ||
// List of attributes attached directly from the `options` passed to the constructor. | ||
const modelProps = ['tableName', 'hasTimestamps']; | ||
/** | ||
@@ -32,6 +29,12 @@ * @class | ||
this.cid = _.uniqueId('c'); | ||
if (options) { | ||
_.extend(this, _.pick(options, modelProps)); | ||
if (options.parse) attrs = this.parse(attrs, options) || {}; | ||
if (options.parse) attrs = this.parse(attrs, options) || {}; | ||
if (options.visible) this.visible = _.clone(options.visible); | ||
if (options.hidden) this.hidden = _.clone(options.hidden); | ||
if (typeof options.requireFetch === 'boolean') this.requireFetch = options.requireFetch; | ||
if (options.tableName) this.tableName = options.tableName; | ||
if (typeof options.hasTimestamps === 'boolean' || Array.isArray(options.hasTimestamps)) { | ||
this.hasTimestamps = options.hasTimestamps; | ||
} | ||
this.set(attrs, options); | ||
@@ -106,3 +109,3 @@ this.initialize.apply(this, arguments); | ||
* | ||
* var Television = bookshelf.Model.extend({ | ||
* var Television = bookshelf.model('Television', { | ||
* tableName: 'televisions' | ||
@@ -149,5 +152,5 @@ * }); | ||
* | ||
* var MyModel = bookshelf.Model.extend({ | ||
* var MyModel = bookshelf.model('MyModel', { | ||
* defaults: {property1: 'foo', property2: 'bar'}, | ||
* tableName: 'my_table' | ||
* tableName: 'my_models' | ||
* }) | ||
@@ -162,2 +165,36 @@ * | ||
/** | ||
* Allows defining the default behavior when there are no results when fetching a model from the | ||
* database. This applies only when fetching a single model using {@link Model#fetch fetch} or | ||
* {@link Collection#fetchOne}. | ||
* | ||
* You can override this model option when fetching by passing the `{require: false}` or | ||
* `{require: true}` option to any of the fetch methods mentioned above. | ||
* | ||
* @type {boolean} | ||
* @default true | ||
* @since 1.0.0 | ||
* @example | ||
* | ||
* // Default behavior | ||
* const MyModel = bookshelf.model('MyModel', { | ||
* tableName: 'my_models' | ||
* }) | ||
* | ||
* new MyModel({id: 1}).fetch().catch(error => { | ||
* // Will throw NotFoundError if there are no results | ||
* }) | ||
* | ||
* // Overriding the default behavior | ||
* const MyModel = bookshelf.model('MyModel', { | ||
* requireFetch: false, | ||
* tableName: 'my_models' | ||
* }) | ||
* | ||
* new MyModel({id: 1}).fetch(model => { | ||
* // model will be null if there are no results | ||
}) | ||
*/ | ||
ModelBase.prototype.requireFetch = true; | ||
/** | ||
* @member {Boolean|Array} | ||
@@ -184,5 +221,5 @@ * @default false | ||
* | ||
* var MyModel = bookshelf.Model.extend({ | ||
* var MyModel = bookshelf.model('MyModel', { | ||
* hasTimestamps: true, | ||
* tableName: 'my_table' | ||
* tableName: 'my_models' | ||
* }) | ||
@@ -209,2 +246,75 @@ * | ||
/** | ||
* @member {null|Array} | ||
* @default null | ||
* @description | ||
* | ||
* List of model attributes to exclude from the output when serializing it. This works as a | ||
* blacklist, and all attributes not present in this list will be shown whan calling | ||
* {@link Model#toJSON toJSON}. | ||
* | ||
* By default this is `null` which means that no attributes will be excluded from the output. | ||
* | ||
* You can override this list by passing the `{hidden: ['list']}` option directly to the | ||
* {@link Model#toJSON toJSON} or {@link Model#serialize serialize} call. | ||
* | ||
* If both the `hidden` and the {@link Model#visible visible} model properties are set, the | ||
* `hidden` list will take precedence. | ||
* | ||
* @example | ||
* const MyModel = bookshelf.model('MyModel', { | ||
* tableName: 'my_models', | ||
* hidden: ['password'] | ||
* }) | ||
* | ||
* const myModel = MyModel.forge({ | ||
* name: 'blah', | ||
* password: 'secure' | ||
* }).save().then(function(savedModel) { | ||
* console.log(savedModel.toJSON()) | ||
* // { | ||
* // name: 'blah', | ||
* // created_at: 'Sun Mar 25 2018 15:07:11 GMT+0100 (WEST)', | ||
* // updated_at: 'Sun Mar 25 2018 15:07:11 GMT+0100 (WEST)' | ||
* // } | ||
* }) | ||
*/ | ||
ModelBase.prototype.hidden = null; | ||
/** | ||
* @member {null|Array} | ||
* @default null | ||
* @description | ||
* | ||
* List of model attributes to include in the output when serializing it. This works as a | ||
* whitelist, and all attributes not present in this list will be hidden whan calling | ||
* {@link Model#toJSON toJSON}. | ||
* | ||
* By default this is `null` which means that all attributes will be included in the output. | ||
* | ||
* You can override this list by passing the `{visible: ['list']}` option directly to the | ||
* {@link Model#toJSON toJSON} or {@link Model#serialize serialize} call. | ||
* | ||
* If both the {@link Model#hidden hidden} and the `visible` model properties are set, the | ||
* `hidden` list will take precedence. | ||
* | ||
* @example | ||
* const MyModel = bookshelf.model('MyModel', { | ||
* tableName: 'my_models', | ||
* visible: ['name', 'created_at'] | ||
* }) | ||
* | ||
* const myModel = MyModel.forge({ | ||
* name: 'blah', | ||
* password: 'secure' | ||
* }).save().then(function(savedModel) { | ||
* console.log(savedModel.toJSON()) | ||
* // { | ||
* // name: 'blah', | ||
* // created_at: 'Sun Mar 25 2018 15:07:11 GMT+0100 (WEST)', | ||
* // } | ||
* }) | ||
*/ | ||
ModelBase.prototype.visible = null; | ||
/** | ||
* @method | ||
@@ -257,3 +367,3 @@ * @private | ||
* | ||
* var Customer = bookshelf.Model.extend({ | ||
* var Customer = bookshelf.model('Customer', { | ||
* idAttribute: 'id', | ||
@@ -349,5 +459,2 @@ * parse: function(attrs) { | ||
/** | ||
* @method | ||
* @description | ||
* | ||
* Return a copy of the model's {@link Model#attributes attributes} for JSON | ||
@@ -359,3 +466,7 @@ * stringification. If the {@link Model model} has any relations defined, this | ||
* | ||
* `serialize` is called internally by {@link Model#toJSON toJSON}. Override | ||
* You can define a whitelist of model attributes to include on the ouput with | ||
* the `{visible: ['list', 'of', 'attributes']}` option. The `{hidden: []}` | ||
* option produces the opposite effect, hiding attributes from the output. | ||
* | ||
* This method is called internally by {@link Model#toJSON toJSON}. Override | ||
* this function if you want to customize its output. | ||
@@ -374,5 +485,12 @@ * | ||
* | ||
* @param {Object=} options | ||
* @param {Boolean} [options.shallow=false] Exclude relations. | ||
* @param {Boolean} [options.omitPivot=false] Exclude pivot values. | ||
* @param {Object} [options] | ||
* @param {Boolean} [options.shallow=false] Whether to exclude relations from the output or not. | ||
* @param {Boolean} [options.omitPivot=false] | ||
* Whether to exclude pivot values from the output or not. | ||
* @param {Array} [options.hidden] List of model attributes to exclude from the output. | ||
* @param {Array} [options.visible] | ||
List of model attributes to include on the output. All other attributes will be hidden. | ||
* @param {Boolean} [options.visibility=true] | ||
* Whether to use visibility options or not. If set to `false` the `hidden` and `visible` options | ||
* will be ignored. | ||
* @returns {Object} Serialized model as a plain object. | ||
@@ -382,10 +500,10 @@ */ | ||
if (!options) options = {}; | ||
if (options.visibility === null || options.visibility === undefined) options.visibility = true; | ||
if (options.omitNew && this.isNew()) { | ||
return null; | ||
} | ||
if (options.omitNew && this.isNew()) return null; | ||
if (!options.shallow) { | ||
let attributes = Object.assign({}, this.attributes); | ||
if (options.shallow !== true) { | ||
let relations = _.mapValues(this.relations, (relation) => (relation.toJSON ? relation.toJSON(options) : relation)); | ||
// Omit null relations from the omitNew option | ||
relations = _.omitBy(relations, _.isNull); | ||
@@ -396,6 +514,14 @@ | ||
return Object.assign({}, this.attributes, relations, pivotAttributes); | ||
attributes = Object.assign(attributes, relations, pivotAttributes); | ||
} | ||
return Object.assign({}, this.attributes); | ||
if (options.visibility) { | ||
const visible = options.visible || this.visible; | ||
const hidden = options.hidden || this.hidden; | ||
if (visible) attributes = _.pick(attributes, visible); | ||
if (hidden) attributes = _.omit(attributes, hidden); | ||
} | ||
return attributes; | ||
}; | ||
@@ -469,3 +595,3 @@ | ||
* // Example of a parser to convert snake_case to camelCase, using lodash | ||
* // This is just an example. You can use the built-in case-converter plugin | ||
* // This is just an example. You can use the official case converter plugin | ||
* // to achieve the same functionality. | ||
@@ -790,18 +916,2 @@ * model.parse = function(attrs) { | ||
/** | ||
* @method ModelBase#keys | ||
* @see http://lodash.com/docs/#keys | ||
*/ | ||
/** | ||
* @method ModelBase#values | ||
* @see http://lodash.com/docs/#values | ||
*/ | ||
/** | ||
* @method ModelBase#toPairs | ||
* @see http://lodash.com/docs/#toPairs | ||
*/ | ||
/** | ||
* @method ModelBase#invert | ||
* @see http://lodash.com/docs/#invert | ||
*/ | ||
/** | ||
* @method ModelBase#pick | ||
@@ -815,3 +925,3 @@ * @see http://lodash.com/docs/#pick | ||
// "_" methods that we want to implement on the Model. | ||
const modelMethods = ['keys', 'values', 'toPairs', 'invert', 'pick', 'omit']; | ||
const modelMethods = ['pick', 'omit']; | ||
@@ -835,3 +945,3 @@ // Mix in each "_" method as a proxy to `Model#attributes`. | ||
* | ||
* const Customer = bookshelf.Model.extend({ | ||
* const Customer = bookshelf.model('Customer', { | ||
* initialize() { | ||
@@ -858,3 +968,3 @@ * this.constructor.__super__.initialize.apply(this, arguments) | ||
* return new this({email: email.toLowerCase()}) | ||
* .fetch({require: true}) | ||
* .fetch() | ||
* .tap(function(customer) { | ||
@@ -861,0 +971,0 @@ * if (!compare(password, customer.get('password')) |
@@ -52,3 +52,3 @@ const _ = require('lodash'); | ||
/** | ||
* Clones a relation. Required by the Pagination plugin. | ||
* Clones a relation. Required by {@link Model#fetchPage}. | ||
* | ||
@@ -55,0 +55,0 @@ * @todo Can probably be removed for a simpler approach, or just the `instance` method. |
@@ -14,6 +14,10 @@ 'use strict'; | ||
const BookshelfRelation = require('./relation'); | ||
const Errors = require('./errors'); | ||
const errors = require('./errors'); | ||
function preventOverwrite(store, name) { | ||
if (store[name]) throw new Error(`${name} is already defined in the registry`); | ||
} | ||
/** | ||
* @class Bookshelf | ||
* @class | ||
* @classdesc | ||
@@ -32,4 +36,116 @@ * | ||
} | ||
function resolveModel(input) { | ||
if (typeof input !== 'string') return input; | ||
return ( | ||
bookshelf.collection(input) || | ||
bookshelf.model(input) || | ||
(function() { | ||
throw new errors.ModelNotResolvedError(`The model ${input} could not be resolved from the registry.`); | ||
})() | ||
); | ||
} | ||
/** @lends Bookshelf.prototype */ | ||
const bookshelf = { | ||
VERSION: require('../package.json').version | ||
registry: { | ||
collections: {}, | ||
models: {} | ||
}, | ||
VERSION: require('../package.json').version, | ||
collection(name, Collection, staticProperties) { | ||
if (Collection) { | ||
preventOverwrite(this.registry.collections, name); | ||
if (_.isPlainObject(Collection)) { | ||
Collection = this.Collection.extend(Collection, staticProperties); | ||
} | ||
this.registry.collections[name] = Collection; | ||
} | ||
return this.registry.collections[name] || bookshelf.resolve(name); | ||
}, | ||
/** | ||
* Registers a model. Omit the second argument `Model` to return a previously registered model that matches the | ||
* provided name. | ||
* | ||
* Note that when registering a model with this method it will also be available to all relation methods, allowing | ||
* you to use a string name in that case. See the calls to `hasMany()` in the examples above. | ||
* | ||
* @example | ||
* // Defining and registering a model | ||
* module.exports = bookshelf.model('Customer', { | ||
* tableName: 'customers', | ||
* orders() { | ||
* return this.hasMany('Order') | ||
* } | ||
* }) | ||
* | ||
* // Retrieving a previously registered model | ||
* const Customer = bookshelf.model('Customer') | ||
* | ||
* // Registering already defined models | ||
* // file: customer.js | ||
* const Customer = bookshelf.Model.extend({ | ||
* tableName: 'customers', | ||
* orders() { | ||
* return this.hasMany('Order') | ||
* } | ||
* }) | ||
* module.exports = bookshelf.model('Customer', Customer) | ||
* | ||
* // file: order.js | ||
* const Order = bookshelf.Model.extend({ | ||
* tableName: 'orders', | ||
* customer() { | ||
* return this.belongsTo('Customer') | ||
* } | ||
* }) | ||
* module.exports = bookshelf.model('Order', Order) | ||
* | ||
* @param {string} name | ||
* The name to save the model as, or the name of the model to retrieve if no further arguments are passed to this | ||
* method. | ||
* @param {Model|Object} [Model] | ||
* The model to register. If a plain object is passed it will be converted to a {@link Model}. See example above. | ||
* @param {Object} [staticProperties] | ||
* If a plain object is passed as second argument, this can be used to specify additional static properties and | ||
* methods for the new model that is created. | ||
* @return {Model} The registered model. | ||
*/ | ||
model(name, Model, staticProperties) { | ||
if (Model) { | ||
preventOverwrite(this.registry.models, name); | ||
if (_.isPlainObject(Model)) Model = this.Model.extend(Model, staticProperties); | ||
this.registry.models[name] = Model; | ||
} | ||
return this.registry.models[name] || bookshelf.resolve(name); | ||
}, | ||
/** | ||
* Override this in your bookshelf instance to define a custom function that will resolve the location of a model or | ||
* collection when using the {@link Bookshelf#model} method or when passing a string with a model name in any of the | ||
* collection methods (e.g. {@link Model#hasOne}, {@link Model#hasMany}, etc.). | ||
* | ||
* This will only be used if the specified name cannot be found in the registry. Note that this function | ||
* can return anything you'd like, so it's not restricted in functionality. | ||
* | ||
* @example | ||
* const Customer = bookshelf.model('Customer', { | ||
* tableName: 'customers' | ||
* }) | ||
* | ||
* bookshelf.resolve = (name) => { | ||
* if (name === 'SpecialCustomer') return Customer; | ||
* } | ||
* | ||
* @param {string} name The model name to resolve. | ||
* @return {*} The return value will depend on what your re-implementation of this function does. | ||
*/ | ||
resolve(name) {} | ||
}; | ||
@@ -41,7 +157,8 @@ | ||
// The `Model` constructor is referenced as a property on the `Bookshelf` | ||
// instance, mixing in the correct `builder` method, as well as the | ||
// `relation` method, passing in the correct `Model` & `Collection` | ||
// The `Model` constructor is referenced as a property on the `Bookshelf` instance, mixing in the correct | ||
// `builder` method, as well as the `relation` method, passing in the correct `Model` & `Collection` | ||
// constructors for later reference. | ||
_relation(type, Target, options) { | ||
Target = resolveModel(Target); | ||
if (type !== 'morphTo' && !_.isFunction(Target)) { | ||
@@ -53,2 +170,37 @@ throw new Error( | ||
return new Relation(type, Target, options); | ||
}, | ||
morphTo(relationName, ...args) { | ||
let candidates = args; | ||
let columnNames = null; | ||
if (Array.isArray(args[0]) || args[0] === null || args[0] === undefined) { | ||
candidates = args.slice(1); | ||
columnNames = args[0]; | ||
} | ||
if (Array.isArray(columnNames)) { | ||
// Try to use the columnNames as target instead | ||
try { | ||
columnNames[0] = resolveModel(columnNames[0]); | ||
} catch (error) { | ||
// If it did not work, they were real columnNames | ||
if (error instanceof errors.ModelNotResolvedError) throw error; | ||
} | ||
} | ||
const models = candidates.map((candidate) => { | ||
if (!Array.isArray(candidate)) return resolveModel(candidate); | ||
const model = candidate[0]; | ||
const morphValue = candidate[1]; | ||
return [resolveModel(model), morphValue]; | ||
}); | ||
return BookshelfModel.prototype.morphTo.apply(this, [relationName, columnNames].concat(models)); | ||
}, | ||
through(Source, ...rest) { | ||
return BookshelfModel.prototype.through.apply(this, [resolveModel(Source), ...rest]); | ||
} | ||
@@ -106,16 +258,20 @@ }, | ||
/** | ||
* Shortcut to a model's `count` method so you don't need to instantiate a new model to count | ||
* the number of records. | ||
* | ||
* @example | ||
* Duck.count().then((count) => { | ||
* console.log('number of ducks', count) | ||
* }) | ||
* | ||
* @method Model.count | ||
* @since 0.8.2 | ||
* @description | ||
* | ||
* Gets the number of matching records in the database, respecting any | ||
* previous calls to {@link Model#query query}. If a `column` is provided, | ||
* records with a null value in that column will be excluded from the count. | ||
* | ||
* @see Model#count | ||
* @param {string} [column='*'] | ||
* Specify a column to count - rows with null values in this column will be excluded. | ||
* @param {Object=} options | ||
* Hash of options. | ||
* @returns {Promise<Number>} | ||
* A promise resolving to the number of matching rows. | ||
* Specify a column to count. Rows with `null` values in this column will be excluded. | ||
* @param {Object} [options] Hash of options. | ||
* @param {boolean} [options.debug=false] | ||
* Whether to enable debugging mode or not. When enabled will show information about the | ||
* queries being run. | ||
* @returns {Promise<number|string>} | ||
*/ | ||
@@ -143,3 +299,6 @@ count(column, options) { | ||
{ | ||
_builder: builderFn | ||
_builder: builderFn, | ||
through(Source, ...args) { | ||
return BookshelfCollection.prototype.through.apply(this, [resolveModel(Source), ...args]); | ||
} | ||
}, | ||
@@ -192,3 +351,3 @@ { | ||
// the object. | ||
_.extend(bookshelf, Events, Errors, { | ||
_.extend(bookshelf, Events, errors, { | ||
/** | ||
@@ -272,6 +431,5 @@ * An alias to `{@link http://knexjs.org/#Transactions Knex#transaction}`. The `transaction` | ||
* You can add a plugin by specifying a string with the name of the plugin | ||
* to load. In this case it will try to find a module. It will first check | ||
* for a match within the `bookshelf/plugins` directory. If nothing is | ||
* found it will pass the string to `require()`, so you can either require | ||
* an npm dependency by name or one of your own modules by relative path: | ||
* to load. In this case it will try to find a module. It will pass the | ||
* string to `require()`, so you can either require a third-party dependency | ||
* by name or one of your own modules by relative path: | ||
* | ||
@@ -281,4 +439,5 @@ * bookshelf.plugin('./bookshelf-plugins/my-favourite-plugin'); | ||
* | ||
* There are a few built-in plugins already, along with many independently | ||
* developed ones. See [the list of available plugins](#plugins). | ||
* There are a few official plugins published in `npm`, along with many | ||
* independently developed ones. See | ||
* [the list of available plugins](index.html#official-plugins). | ||
* | ||
@@ -289,3 +448,3 @@ * You can also provide an array of strings or functions, which is the same | ||
* | ||
* bookshelf.plugin(['registry', './my-plugins/special-parse-format']); | ||
* bookshelf.plugin(['cool-plugin', './my-plugins/even-cooler-plugin']); | ||
* | ||
@@ -297,32 +456,68 @@ * Example plugin: | ||
* bookshelf.Model = bookshelf.Model.extend({ | ||
* set: function(key, value, options) { | ||
* if (!key) return this; | ||
* if (typeof value === 'string') value = value.toLowerCase(); | ||
* return bookshelf.Model.prototype.set.call(this, key, value, options); | ||
* set(key, value, options) { | ||
* if (!key) return this | ||
* if (typeof value === 'string') value = value.toLowerCase() | ||
* return bookshelf.Model.prototype.set.call(this, key, value, options) | ||
* } | ||
* }); | ||
* }) | ||
* } | ||
* | ||
* @param {string|array|Function} plugin | ||
* The plugin or plugins to add. If you provide a string it can | ||
* represent a built-in plugin, an npm package or a file somewhere on | ||
* your project. You can also pass a function as argument to add it as a | ||
* plugin. Finally, it's also possible to pass an array of strings or | ||
* functions to add them all at once. | ||
* The plugin or plugins to load. If you provide a string it can | ||
* represent an npm package or a file somewhere on your project. You can | ||
* also pass a function as argument to add it as a plugin. Finally, it's | ||
* also possible to pass an array of strings or functions to add them all | ||
* at once. | ||
* @param {mixed} options | ||
* This can be anything you want and it will be passed directly to the | ||
* plugin as the second argument when loading it. | ||
* @return {Bookshelf} The bookshelf instance for chaining. | ||
*/ | ||
plugin(plugin, options) { | ||
if (_.isString(plugin)) { | ||
try { | ||
require('./plugins/' + plugin)(this, options); | ||
} catch (e) { | ||
if (e.code !== 'MODULE_NOT_FOUND') { | ||
throw e; | ||
} | ||
if (!process.browser) { | ||
require(plugin)(this, options); | ||
} | ||
if (plugin === 'pagination') { | ||
const message = | ||
'Pagination plugin was moved into core Bookshelf. You can now use `fetchPage()` without having to ' + | ||
"call `.plugin('pagination')`. Remove any `.plugin('pagination')` calls to clear this message."; | ||
return console.warn(message); // eslint-disable-line no-console | ||
} | ||
if (plugin === 'visibility') { | ||
const message = | ||
'Visibility plugin was moved into core Bookshelf. You can now set the `hidden` and `visible` properties ' + | ||
"without having to call `.plugin('visibility')`. Remove any `.plugin('visibility')` calls to clear this " + | ||
'message.'; | ||
return console.warn(message); // eslint-disable-line no-console | ||
} | ||
if (plugin === 'registry') { | ||
const message = | ||
'Registry plugin was moved into core Bookshelf. You can now register models using `bookshelf.model()` ' + | ||
"and collections using `bookshelf.collection()` without having to call `.plugin('registry')`. Remove " + | ||
"any `.plugin('registry')` calls to clear this message."; | ||
return console.warn(message); // eslint-disable-line no-console | ||
} | ||
if (plugin === 'processor') { | ||
const message = | ||
'Processor plugin was removed from core Bookshelf. To migrate to the new standalone package follow the ' + | ||
'instructions in https://github.com/bookshelf/bookshelf/wiki/Migrating-from-0.15.1-to-1.0.0#processor-plugin'; | ||
return console.warn(message); // eslint-disable-line no-console | ||
} | ||
if (plugin === 'case-converter') { | ||
const message = | ||
'Case converter plugin was removed from core Bookshelf. To migrate to the new standalone package follow ' + | ||
'the instructions in https://github.com/bookshelf/bookshelf/wiki/Migrating-from-0.15.1-to-1.0.0#case-converter-plugin'; | ||
return console.warn(message); // eslint-disable-line no-console | ||
} | ||
if (plugin === 'virtuals') { | ||
const message = | ||
'Virtuals plugin was removed from core Bookshelf. To migrate to the new standalone package follow ' + | ||
'the instructions in https://github.com/bookshelf/bookshelf/wiki/Migrating-from-0.15.1-to-1.0.0#virtuals-plugin'; | ||
return console.warn(message); // eslint-disable-line no-console | ||
} | ||
require(plugin)(this, options); | ||
} else if (Array.isArray(plugin)) { | ||
@@ -340,3 +535,2 @@ plugin.forEach((p) => this.plugin(p, options)); | ||
* @member Bookshelf#knex | ||
* @memberOf Bookshelf | ||
* @type {Knex} | ||
@@ -343,0 +537,0 @@ * @description |
@@ -11,2 +11,39 @@ const _ = require('lodash'); | ||
/** | ||
* When creating a {@link Collection}, you may choose to pass in the initial array of | ||
* {@link Model models}. The collection's {@link Collection#comparator comparator} may be included | ||
* as an option. Passing `false` as the comparator option will prevent sorting. If you define an | ||
* {@link Collection#initialize initialize} function, it will be invoked when the collection is | ||
* created. | ||
* | ||
* If you would like to customize the Collection used by your models when calling | ||
* {@link Model#fetchAll} or {@link Model#fetchPage} you can use the following process: | ||
* | ||
* const Test = bookshelf.model('Test', { | ||
* tableName: 'test' | ||
* }, { | ||
* collection(...args) { | ||
* return new Tests(...args) | ||
* } | ||
* }) | ||
* const Tests = bookshelf.collection('Tests', { | ||
* get model() { | ||
* return Test | ||
* }, | ||
* initialize () { | ||
* this.constructor.__super__.initialize.apply(this, arguments) | ||
* // Collection will emit fetching event as expected even on eager queries. | ||
* this.on('fetching', () => {}) | ||
* }, | ||
* doStuff() { | ||
* // This method will be available in the results collection returned | ||
* // by Test.fetchAll() and Test.fetchPage() | ||
* } | ||
* }) | ||
* | ||
* @example | ||
* const TabSet = bookshelf.collection('TabSet', { | ||
* model: Tab | ||
* }) | ||
* const tabs = new TabSet([tab1, tab2, tab3]) | ||
* | ||
* @class Collection | ||
@@ -17,13 +54,2 @@ * @extends CollectionBase | ||
* {@link Model#fetchAll fetchAll} call. | ||
* | ||
* @description | ||
* When creating a {@link Collection}, you may choose to pass in the initial array of | ||
* {@link Model models}. The collection's {@link Collection#comparator comparator} may be included | ||
* as an option. Passing `false` as the comparator option will prevent sorting. If you define an | ||
* {@link Collection#initialize initialize} function, it will be invoked when the collection is | ||
* created. | ||
* | ||
* @example | ||
* let tabs = new TabSet([tab1, tab2, tab3]); | ||
* | ||
* @param {(Model[])=} models Initial array of models. | ||
@@ -48,3 +74,3 @@ * @param {Object=} options | ||
* @example | ||
* const Chapter = bookshelf.Model.extend({ | ||
* const Chapter = bookshelf.model('Chapter', { | ||
* tableName: 'chapters', | ||
@@ -56,3 +82,3 @@ * paragraphs() { | ||
* | ||
* const Paragraph = bookshelf.Model.extend({ | ||
* const Paragraph = bookshelf.model('Paragraph', { | ||
* tableName: 'paragraphs', | ||
@@ -64,3 +90,3 @@ * chapter() { | ||
* | ||
* const Book = bookshelf.Model.extend({ | ||
* const Book = bookshelf.model('Book', { | ||
* tableName: 'books', | ||
@@ -104,5 +130,5 @@ | ||
* Fetch the default set of models for this collection from the database, | ||
* resetting the collection when they arrive. If you wish to trigger an error | ||
* if the fetched collection is empty, pass `{require: true}` as one of the | ||
* options to the {@link Collection#fetch fetch} call. A {@link | ||
* resetting the collection when they arrive. If you wish to trigger an | ||
* error if the fetched collection is empty, pass `{require: true}` as one | ||
* of the options to the {@link Collection#fetch fetch} call. A {@link | ||
* Collection#fetched "fetched"} event will be fired when records are | ||
@@ -113,4 +139,4 @@ * successfully retrieved. If you need to constrain the query performed by | ||
* | ||
* *If you'd like to only fetch specific columns, you may specify a `columns` | ||
* property in the options for the `fetch` call.* | ||
* If you'd like to only fetch specific columns, you may specify a `columns` | ||
* property in the options for the `fetch` call. | ||
* | ||
@@ -125,10 +151,12 @@ * The `withRelated` option may be specified to fetch the models of the | ||
* @fires Collection#fetched | ||
* @throws {Collection.EmptyError} | ||
* Upon a sucessful query resulting in no records returned. Only fired if `require: true` is | ||
* passed as an option. | ||
* @throws {Collection.EmptyError} Thrown if no records are found. | ||
* @param {Object=} options | ||
* @param {Boolean} [options.require=false] | ||
* Whether or not to throw a {@link Collection.EmptyError} if no records are found. | ||
* You can pass the `require: true` option to override this behavior. | ||
* @param {string|string[]} [options.withRelated=[]] | ||
* A relation, or list of relations, to be eager loaded as part of the `fetch` operation. | ||
* @param {boolean} [options.debug=false] | ||
* Whether to enable debugging mode or not. When enabled will show information about the | ||
* queries being run. | ||
* @returns {Promise<Collection>} | ||
@@ -179,5 +207,3 @@ */ | ||
.catch(this.constructor.EmptyError, function(err) { | ||
if (options.require) { | ||
throw err; | ||
} | ||
if (options.require) throw err; | ||
this.reset([], {silent: true}); | ||
@@ -189,2 +215,7 @@ }) | ||
fetchPage(options) { | ||
if (!options) options = {}; | ||
return Helpers.fetchPage.call(this, options); | ||
}, | ||
/** | ||
@@ -195,15 +226,16 @@ * Get the number of records in the collection's table. | ||
* // select count(*) from shareholders where company_id = 1 and share > 0.1; | ||
* Company.forge({id:1}) | ||
* new Company({id: 1}) | ||
* .shareholders() | ||
* .query('where', 'share', '>', '0.1') | ||
* .where('share', '>', '0.1') | ||
* .count() | ||
* .then(function(count) { | ||
* assert(count === 3); | ||
* }); | ||
* .then((count) => { | ||
* assert(count === 3) | ||
* }) | ||
* | ||
* @since 0.8.2 | ||
* @see Model#count | ||
* @param {string} [column='*'] | ||
* Specify a column to count - rows with null values in this column will be excluded. | ||
* @param {Object=} options Hash of options. | ||
* @returns {Promise<Number>} A promise resolving to the number of matching rows. | ||
* Specify a column to count. Rows with `null` values in this column will be excluded. | ||
* @param {Object} [options] Hash of options. | ||
* @returns {Promise<number|string>} | ||
*/ | ||
@@ -237,5 +269,5 @@ count: Promise.method(function(column, options) { | ||
* @param {Object=} options | ||
* @param {Boolean} [options.require=false] | ||
* If `true`, will reject the returned response with a {@link | ||
* Model.NotFoundError NotFoundError} if no result is found. | ||
* @param {Boolean} [options.require=true] | ||
* Whether or not to reject the returned Promise with a {@link Model.NotFoundError} if no | ||
* records can be fetched from the database. | ||
* @param {(string|string[])} [options.columns='*'] | ||
@@ -248,6 +280,10 @@ * Limit the number of columns fetched. | ||
* option, and requires a database that supports it. | ||
* | ||
* @param {boolean} [options.debug=false] | ||
* Whether to enable debugging mode or not. When enabled will show information about the | ||
* queries being run. | ||
* @throws {Model.NotFoundError} | ||
* @returns {Promise<Model|null>} | ||
* A promise resolving to the fetched {@link Model model} or `null` if none exists. | ||
* A promise resolving to the fetched {@link Model} or `null` if none exists and the | ||
* `require: false` option is passed or {@link Model#requireFetch requireFetch} is set to | ||
* `false`. | ||
*/ | ||
@@ -268,8 +304,11 @@ fetchOne: Promise.method(function(options) { | ||
* @param {string|string[]} relations The relation, or relations, to be loaded. | ||
* @param {Object=} options Hash of options. | ||
* @param {Transaction=} options.transacting | ||
* @param {string=} options.lock | ||
* @param {Object} [options] Hash of options. | ||
* @param {Transaction} [options.transacting] | ||
* @param {string} [options.lock] | ||
* Type of row-level lock to use. Valid options are `forShare` and `forUpdate`. This only | ||
* works in conjunction with the `transacting` option, and requires a database that supports | ||
* it. | ||
* @param {boolean} [options.debug=false] | ||
* Whether to enable debugging mode or not. When enabled will show information about the | ||
* queries being run. | ||
* @returns {Promise<Collection>} A promise resolving to this {@link Collection collection}. | ||
@@ -307,4 +346,7 @@ */ | ||
* @param {Object} model A set of attributes to be set on the new model. | ||
* @param {Object=} options | ||
* @param {Transaction=} options.transacting | ||
* @param {Object} [options] | ||
* @param {Transaction} [options.transacting] | ||
* @param {boolean} [options.debug=false] | ||
* Whether to enable debugging mode or not. When enabled will show information about the | ||
* queries being run. | ||
* @returns {Promise<Model>} A promise resolving with the new {@link Model model}. | ||
@@ -389,2 +431,44 @@ */ | ||
/** | ||
* This is used as convenience for the most common {@link Collection#query query} method: | ||
* adding a `WHERE` clause to the builder. Any additional knex methods may be accessed using | ||
* {@link Collection#query query}. | ||
* | ||
* Accepts either `key, value` syntax, or a hash of attributes to constrain the results. | ||
* | ||
* @example | ||
* collection | ||
* .where('favorite_color', '<>', 'green') | ||
* .fetch() | ||
* .then(results => { | ||
* // ... | ||
* }) | ||
* | ||
* // or | ||
* | ||
* collection | ||
* .where('favorite_color', 'red') | ||
* .fetch() | ||
* .then(results => { | ||
* // ... | ||
* }) | ||
* | ||
* collection | ||
* .where({favorite_color: 'red', shoe_size: 12}) | ||
* .fetch() | ||
* .then(results => { | ||
* // ... | ||
* }) | ||
* | ||
* @see Collection#query | ||
* @param {Object|...string} conditions | ||
* Either `key, [operator], value` syntax, or a hash of attributes to match. Note that these | ||
* must be formatted as they are in the database, not how they are stored after | ||
* {@link Model#parse}. | ||
* @returns {Collection} Self, this method is chainable. | ||
*/ | ||
where() { | ||
return this.query.apply(this, ['where'].concat(Array.from(arguments))); | ||
}, | ||
/** | ||
* Specifies the column to sort on and sort order. | ||
@@ -468,5 +552,5 @@ * | ||
* @description | ||
* Thrown when no records are found by {@link Collection#fetch fetch}, | ||
* {@link Model#fetchAll}, or {@link Model.fetchAll} when called with | ||
* the `{require: true}` option. | ||
* Thrown by default when no records are found by {@link Collection#fetch fetch} or | ||
* {@link Collection#fetchOne}. This behavior can be overrided with the | ||
* {@link Model#requireFetch} option. | ||
*/ | ||
@@ -473,0 +557,0 @@ child.EmptyError = createError(this.EmptyError); |
const createError = require('create-error'); | ||
function ModelNotResolvedError() { | ||
ModelNotResolvedError.prototype = Object.create(Error.prototype, { | ||
constructor: { | ||
value: ModelNotResolvedError, | ||
enumerable: false, | ||
writable: true, | ||
configurable: true | ||
} | ||
}); | ||
Object.setPrototypeOf(ModelNotResolvedError, Error); | ||
function ModelNotResolvedError() { | ||
return Object.getPrototypeOf(ModelNotResolvedError).apply(this, arguments); | ||
} | ||
return ModelNotResolvedError; | ||
} | ||
module.exports = { | ||
// Thrown when the model is not found and {require: true} is passed in the fetch options | ||
// Thrown when a model is not found. | ||
NotFoundError: createError('NotFoundError'), | ||
// Thrown when the collection is empty and {require: true} is passed in model.fetchAll or | ||
// collection.fetch | ||
// Thrown when the collection is empty upon fetching it. | ||
EmptyError: createError('EmptyError'), | ||
// Thrown when an update affects no rows and {require: true} is passed in model.save. | ||
// Thrown when an update affects no rows | ||
NoRowsUpdatedError: createError('NoRowsUpdatedError'), | ||
// Thrown when a delete affects no rows and {require: true} is passed in model.destroy. | ||
NoRowsDeletedError: createError('NoRowsDeletedError') | ||
// Thrown when a delete affects no rows. | ||
NoRowsDeletedError: createError('NoRowsDeletedError'), | ||
ModelNotResolvedError: ModelNotResolvedError() | ||
}; |
@@ -5,7 +5,110 @@ /* eslint no-console: 0 */ | ||
// --------------- | ||
'use strict'; | ||
const _ = require('lodash'); | ||
const Promise = require('bluebird'); | ||
const Model = require('./base/model'); | ||
function ensureIntWithDefault(number, defaultValue) { | ||
if (!number) return defaultValue; | ||
const parsedNumber = parseInt(number, 10); | ||
if (Number.isNaN(parsedNumber)) return defaultValue; | ||
return parsedNumber; | ||
} | ||
module.exports = { | ||
// This is used by both Model and Collection methods to paginate the results. | ||
fetchPage(options) { | ||
const DEFAULT_LIMIT = 10; | ||
const DEFAULT_OFFSET = 0; | ||
const DEFAULT_PAGE = 1; | ||
const isModel = this instanceof Model; | ||
const fetchOptions = _.omit(options, ['page', 'pageSize', 'limit', 'offset']); | ||
const countOptions = _.omit(fetchOptions, ['require', 'columns', 'withRelated', 'lock']); | ||
const fetchMethodName = isModel ? 'fetchAll' : 'fetch'; | ||
const targetModel = isModel ? this.constructor : this.target || this.model; | ||
const tableName = targetModel.prototype.tableName; | ||
const idAttribute = targetModel.prototype.idAttribute || 'id'; | ||
const targetIdColumn = [`${tableName}.${idAttribute}`]; | ||
let page; | ||
let pageSize; | ||
let limit; | ||
let offset; | ||
if (!options.limit && !options.offset) { | ||
pageSize = ensureIntWithDefault(options.pageSize, DEFAULT_LIMIT); | ||
page = ensureIntWithDefault(options.page, DEFAULT_PAGE); | ||
limit = pageSize; | ||
offset = limit * (page - 1); | ||
} else { | ||
limit = ensureIntWithDefault(options.limit, DEFAULT_LIMIT); | ||
offset = ensureIntWithDefault(options.offset, DEFAULT_OFFSET); | ||
} | ||
const paginate = () => { | ||
return this.clone() | ||
.query((qb) => { | ||
Object.assign(qb, this.query().clone()); | ||
qb.limit.apply(qb, [limit]); | ||
qb.offset.apply(qb, [offset]); | ||
return null; | ||
}) | ||
[fetchMethodName](fetchOptions); | ||
}; | ||
const count = () => { | ||
const notNeededQueries = ['orderByBasic', 'orderByRaw', 'groupByBasic', 'groupByRaw']; | ||
const counter = this.clone(); | ||
const groupColumns = []; | ||
return counter | ||
.query((qb) => { | ||
Object.assign(qb, this.query().clone()); | ||
// Remove grouping and ordering. Ordering is unnecessary for a count, and grouping returns the entire result | ||
// set. What we want instead is to use `DISTINCT`. | ||
_.remove(qb._statements, (statement) => { | ||
if (statement.grouping === 'group') statement.value.forEach((value) => groupColumns.push(value)); | ||
if (statement.grouping === 'columns' && statement.distinct) | ||
statement.value.forEach((value) => groupColumns.push(value)); | ||
return notNeededQueries.indexOf(statement.type) > -1 || statement.grouping === 'columns'; | ||
}); | ||
if (!isModel && counter.relatedData) { | ||
// Remove joining columns that break COUNT operation, eg. pivotal coulmns for belongsToMany relation. | ||
counter.relatedData.joinColumns = function() {}; | ||
} | ||
qb.countDistinct.apply(qb, groupColumns.length > 0 ? groupColumns : targetIdColumn); | ||
}) | ||
[fetchMethodName](countOptions) | ||
.then((result) => { | ||
const metadata = !options.limit && !options.offset ? {page, pageSize} : {offset, limit}; | ||
if (result && result.length == 1) { | ||
// We shouldn't have to do this, instead it should be result.models[0].get('count') but SQLite and MySQL | ||
// return a really strange key name and Knex doesn't abstract that away yet: | ||
// https://github.com/tgriesser/knex/issues/3315. | ||
const keys = Object.keys(result.models[0].attributes); | ||
if (keys.length === 1) { | ||
const key = Object.keys(result.models[0].attributes)[0]; | ||
metadata.rowCount = parseInt(result.models[0].attributes[key]); | ||
} | ||
} | ||
return metadata; | ||
}); | ||
}; | ||
return Promise.join(paginate(), count(), (rows, metadata) => { | ||
const pageCount = Math.ceil(metadata.rowCount / limit); | ||
const pageData = Object.assign(metadata, {pageCount}); | ||
return Object.assign(rows, {pagination: pageData}); | ||
}); | ||
}, | ||
// Sets the constraints necessary during a `model.save` call. | ||
@@ -12,0 +115,0 @@ saveConstraints: function(model, relatedData) { |
@@ -758,3 +758,3 @@ const _ = require('lodash'); | ||
* | ||
* var Tag = bookshelf.Model.extend({ | ||
* var Tag = bookshelf.model('Tag', { | ||
* comments: function() { | ||
@@ -938,5 +938,3 @@ * return this.belongsToMany(Comment).withPivot(['created_at', 'order']); | ||
.set(fks) | ||
.fetch({ | ||
require: true | ||
}) | ||
.fetch() | ||
.then(function(instance) { | ||
@@ -943,0 +941,0 @@ if (method === 'delete') { |
@@ -7,5 +7,9 @@ // Sync | ||
const Promise = require('bluebird'); | ||
const supportsReturning = (client) => _.includes(['postgresql', 'postgres', 'pg', 'oracle', 'mssql'], client); | ||
const validLocks = ['forShare', 'forUpdate']; | ||
function supportsReturning(client = {}) { | ||
if (!client.config || !client.config.client) return false; | ||
return ['postgresql', 'postgres', 'pg', 'oracle', 'mssql'].includes(client.config.client); | ||
} | ||
// Sync is the dispatcher for any database queries, | ||
@@ -51,11 +55,12 @@ // taking the "syncing" `model` or `collection` being queried, along with | ||
// NOTE: `_.omit` returns an empty object, even if attributes are null. | ||
const whereAttributes = _.omitBy(attributes, _.isPlainObject); | ||
const whereAttributes = _.omitBy(attributes, (attribute, name) => { | ||
return _.isPlainObject(attribute) || name === model.idAttribute; | ||
}); | ||
const formattedAttributes = model.format(whereAttributes); | ||
if (!_.isEmpty(whereAttributes)) { | ||
// Format and prefix attributes. | ||
const formatted = this.prefixFields(model.format(whereAttributes)); | ||
query.where(formatted); | ||
if (model.idAttribute in attributes) { | ||
formattedAttributes[model.idAttribute] = attributes[model.idAttribute]; | ||
} | ||
// Limit to a single result. | ||
if (!_.isEmpty(formattedAttributes)) query.where(this.prefixFields(formattedAttributes)); | ||
query.limit(1); | ||
@@ -181,3 +186,3 @@ | ||
* @example | ||
* const MyModel = bookshelf.Model.extend({ | ||
* const MyModel = bookshelf.model('MyModel', { | ||
* initialize() { | ||
@@ -210,3 +215,3 @@ * this.on('fetching', function(model, columns, options) { | ||
syncing.format(_.extend(Object.create(null), syncing.attributes)), | ||
supportsReturning(this.query.client.config.client) ? syncing.idAttribute : null | ||
supportsReturning(this.query.client) ? '*' : null | ||
); | ||
@@ -227,2 +232,3 @@ }), | ||
} | ||
if (supportsReturning(query.client)) query.returning('*'); | ||
return query.update(updating); | ||
@@ -229,0 +235,0 @@ }), |
{ | ||
"name": "bookshelf", | ||
"version": "0.15.1", | ||
"version": "1.0.0", | ||
"description": "A lightweight ORM for PostgreSQL, MySQL, and SQLite3", | ||
@@ -9,11 +9,11 @@ "main": "bookshelf.js", | ||
"lint": "eslint bookshelf.js lib/", | ||
"cover": "npm run lint && istanbul cover _mocha -- --check-leaks -t 10000 -b -R spec test/index.js", | ||
"test": "npm run lint && mocha --check-leaks -t 10000 -b test/index.js", | ||
"cover": "npm run lint && istanbul cover _mocha -- --check-leaks -t 10000 -b", | ||
"test": "npm run lint && mocha --check-leaks -t 10000 -b", | ||
"jsdoc": "./scripts/jsdoc.sh", | ||
"postpublish": "./scripts/postpublish.sh" | ||
}, | ||
"homepage": "http://bookshelfjs.org", | ||
"homepage": "https://bookshelfjs.org", | ||
"repository": { | ||
"type": "git", | ||
"url": "git://github.com/bookshelf/bookshelf.git" | ||
"url": "https://github.com/bookshelf/bookshelf.git" | ||
}, | ||
@@ -29,6 +29,6 @@ "keywords": [ | ||
"dependencies": { | ||
"bluebird": "^3.4.3", | ||
"bluebird": "^3.5.5", | ||
"create-error": "~0.3.1", | ||
"inflection": "^1.5.1", | ||
"lodash": "^4.17.10" | ||
"inflection": "^1.12.0", | ||
"lodash": "^4.17.15" | ||
}, | ||
@@ -47,23 +47,23 @@ "husky": { | ||
"devDependencies": { | ||
"bookshelf-jsdoc-theme": "^1.0.0", | ||
"bookshelf-jsdoc-theme": "^1.0.1", | ||
"chai": "4.0.2", | ||
"eslint": "^5.10.0", | ||
"eslint-config-prettier": "^4.1.0", | ||
"eslint": "^6.0.0", | ||
"eslint-config-prettier": "^6.0.0", | ||
"eslint-plugin-prettier": "^3.0.0", | ||
"husky": "^2.4.1", | ||
"husky": "^3.0.0", | ||
"istanbul": "^0.4.5", | ||
"jsdoc": "^3.4.0", | ||
"knex": "^0.17.0", | ||
"lint-staged": "^8.0.0", | ||
"mocha": "^6.1.1", | ||
"mysql": "^2.15.0", | ||
"pg": "^7.4.1", | ||
"knex": "~0.19.4", | ||
"lint-staged": "^9.1.0", | ||
"mocha": "^6.1.4", | ||
"mysql": "^2.17.1", | ||
"pg": "^7.11.0", | ||
"prettier": "^1.14.2", | ||
"sinon": "^7.2.4", | ||
"sinon-chai": "^3.2.0", | ||
"sqlite3": "^4.0.4", | ||
"uuid": "^3.1.0" | ||
"sinon": "^7.3.2", | ||
"sinon-chai": "^3.3.0", | ||
"sqlite3": "^4.0.9", | ||
"uuid": "^3.3.2" | ||
}, | ||
"peerDependencies": { | ||
"knex": ">=0.13.0 <0.18.0" | ||
"knex": ">=0.15.0 <0.20.0" | ||
}, | ||
@@ -70,0 +70,0 @@ "author": { |
126
README.md
@@ -16,3 +16,3 @@ # bookshelf.js | ||
Bookshelf aims to provide a simple library for common tasks when querying databases in JavaScript, and forming relations between these objects, taking a lot of ideas from the the [Data Mapper Pattern](http://en.wikipedia.org/wiki/Data_mapper_pattern). | ||
Bookshelf aims to provide a simple library for common tasks when querying databases in JavaScript, and forming relations between these objects, taking a lot of ideas from the [Data Mapper Pattern](http://en.wikipedia.org/wiki/Data_mapper_pattern). | ||
@@ -28,4 +28,4 @@ With a concise, literate codebase, Bookshelf is simple to read, understand, and extend. It doesn't force you to use any specific validation scheme, and provides flexible, efficient relation/nested-relation loading and first-class transaction support. | ||
```js | ||
$ npm install knex --save | ||
$ npm install bookshelf --save | ||
$ npm install knex | ||
$ npm install bookshelf | ||
@@ -35,3 +35,2 @@ # Then add one of the following: | ||
$ npm install mysql | ||
$ npm install mariasql | ||
$ npm install sqlite3 | ||
@@ -43,3 +42,4 @@ ``` | ||
```js | ||
var knex = require('knex')({ | ||
// Setting up the database connection | ||
const knex = require('knex')({ | ||
client: 'mysql', | ||
@@ -53,9 +53,9 @@ connection: { | ||
} | ||
}); | ||
}) | ||
const bookshelf = require('bookshelf')(knex) | ||
var bookshelf = require('bookshelf')(knex); | ||
var User = bookshelf.Model.extend({ | ||
// Defining models | ||
const User = bookshelf.model('User', { | ||
tableName: 'users' | ||
}); | ||
}) | ||
``` | ||
@@ -66,12 +66,12 @@ | ||
```js | ||
// In a file named something like bookshelf.js | ||
var knex = require('knex')(dbConfig); | ||
module.exports = require('bookshelf')(knex); | ||
// In a file named, e.g. bookshelf.js | ||
const knex = require('knex')(dbConfig) | ||
module.exports = require('bookshelf')(knex) | ||
// elsewhere, to use the bookshelf client: | ||
var bookshelf = require('./bookshelf'); | ||
const bookshelf = require('./bookshelf') | ||
var Post = bookshelf.Model.extend({ | ||
const Post = bookshelf.model('Post', { | ||
// ... | ||
}); | ||
}) | ||
``` | ||
@@ -84,41 +84,38 @@ | ||
```js | ||
var knex = require('knex')({ | ||
const knex = require('knex')({ | ||
client: 'mysql', | ||
connection: process.env.MYSQL_DATABASE_CONNECTION | ||
}); | ||
var bookshelf = require('bookshelf')(knex); | ||
}) | ||
const bookshelf = require('bookshelf')(knex) | ||
var User = bookshelf.Model.extend({ | ||
const User = bookshelf.model('User', { | ||
tableName: 'users', | ||
posts: function() { | ||
return this.hasMany(Posts); | ||
posts() { | ||
return this.hasMany(Posts) | ||
} | ||
}); | ||
}) | ||
var Posts = bookshelf.Model.extend({ | ||
tableName: 'messages', | ||
tags: function() { | ||
return this.belongsToMany(Tag); | ||
const Post = bookshelf.model('Post', { | ||
tableName: 'posts', | ||
tags() { | ||
return this.belongsToMany(Tag) | ||
} | ||
}); | ||
}) | ||
var Tag = bookshelf.Model.extend({ | ||
const Tag = bookshelf.model('Tag', { | ||
tableName: 'tags' | ||
}) | ||
User.where('id', 1).fetch({withRelated: ['posts.tags']}).then(function(user) { | ||
console.log(user.related('posts').toJSON()); | ||
}).catch(function(err) { | ||
console.error(err); | ||
}); | ||
new User({id: 1}).fetch({withRelated: ['posts.tags']}).then((user) => { | ||
console.log(user.related('posts').toJSON()) | ||
}).catch((error) => { | ||
console.error(error) | ||
}) | ||
``` | ||
## Plugins | ||
## Official Plugins | ||
* [Registry](https://github.com/bookshelf/bookshelf/wiki/Plugin:-Model-Registry): Register models in a central location so that you can refer to them using a string in relations instead of having to require it every time. Helps deal with the challenges of circular module dependencies in Node. | ||
* [Virtuals](https://github.com/bookshelf/bookshelf/wiki/Plugin:-Virtuals): Define virtual properties on your model to compute new values. | ||
* [Visibility](https://github.com/bookshelf/bookshelf/wiki/Plugin:-Visibility): Specify a whitelist/blacklist of model attributes when serialized toJSON. | ||
* [Pagination](https://github.com/bookshelf/bookshelf/wiki/Plugin:-Pagination): Adds `fetchPage` methods to use for pagination in place of `fetch` and `fetchAll`. | ||
* [Case Converter](https://github.com/bookshelf/bookshelf/wiki/Plugin:-Case-Converter): Handles the conversion between the database's snake_cased and a model's camelCased properties automatically. | ||
* [Processor](https://github.com/bookshelf/bookshelf/wiki/Plugin:-Processor): Allows defining custom processor functions that handle transformation of values whenever they are `.set()` on a model. | ||
* [Virtuals](https://github.com/bookshelf/virtuals-plugin): Define virtual properties on your model to compute new values. | ||
* [Case Converter](https://github.com/bookshelf/case-converter-plugin): Handles the conversion between the database's snake_cased and a model's camelCased properties automatically. | ||
* [Processor](https://github.com/bookshelf/processor-plugin): Allows defining custom processor functions that handle transformation of values whenever they are `.set()` on a model. | ||
@@ -129,3 +126,3 @@ ## Community plugins | ||
* [bookshelf-json-columns](https://github.com/seegno/bookshelf-json-columns) - Parse and stringify JSON columns on save and fetch instead of manually define hooks for each model (PostgreSQL and SQLite). | ||
* [bookshelf-mask](https://github.com/seegno/bookshelf-mask) - Similar to [Visibility](https://github.com/bookshelf/bookshelf/wiki/Plugin:-Visibility) but supporting multiple scopes, masking models and collections using the [json-mask](https://github.com/nemtsov/json-mask) API. | ||
* [bookshelf-mask](https://github.com/seegno/bookshelf-mask) - Similar to the functionality of the {@link Model#visible} attribute but supporting multiple scopes, masking models and collections using the [json-mask](https://github.com/nemtsov/json-mask) API. | ||
* [bookshelf-schema](https://github.com/bogus34/bookshelf-schema) - A plugin for handling fields, relations, scopes and more. | ||
@@ -137,5 +134,5 @@ * [bookshelf-signals](https://github.com/bogus34/bookshelf-signals) - A plugin that translates Bookshelf events to a central hub. | ||
* [bookshelf-advanced-serialization](https://github.com/sequiturs/bookshelf-advanced-serialization) - A more powerful visibility plugin, supporting serializing models and collections according to access permissions, application context, and after ensuring relations have been loaded. | ||
* [bookshelf-plugin-mode](https://github.com/popodidi/bookshelf-plugin-mode) - Plugin inspired by [Visibility](https://github.com/tgriesser/bookshelf/wiki/Plugin:-Visibility) plugin, providing functionality to specify different modes with corresponding visible/hidden fields of model. | ||
* [bookshelf-plugin-mode](https://github.com/popodidi/bookshelf-plugin-mode) - Plugin inspired by the functionality of the {@link Model#visible} attribute, allowing to specify different modes with corresponding visible/hidden fields of model. | ||
* [bookshelf-secure-password](https://github.com/venables/bookshelf-secure-password) - A plugin for easily securing passwords using bcrypt. | ||
* [bookshelf-default-select](https://github.com/DJAndries/bookshelf-default-select) - Enables default column selection for models. Inspired by [Visibility](https://github.com/tgriesser/bookshelf/wiki/Plugin:-Visibility), but operates on the database level. | ||
* [bookshelf-default-select](https://github.com/DJAndries/bookshelf-default-select) - Enables default column selection for models. Inspired by the functionality of the {@link Model#visible} attribute, but operates on the database level. | ||
* [bookshelf-ez-fetch](https://github.com/DJAndries/bookshelf-ez-fetch) - Convenient fetching methods which allow for compact filtering, relation selection and error handling. | ||
@@ -146,3 +143,3 @@ * [bookshelf-manager](https://github.com/ericclemmons/bookshelf-manager) - Model & Collection manager to make it easy to create & save deep, nested JSON structures from API requests. | ||
Have questions about the library? Come join us in the [#bookshelf freenode IRC channel](http://webchat.freenode.net/?channels=bookshelf) for support on [knex.js](http://knexjs.org/) and bookshelf.js, or post an issue on [Stack Overflow](http://stackoverflow.com/questions/tagged/bookshelf.js) or in the GitHub [issue tracker](https://github.com/bookshelf/bookshelf/issues). | ||
Have questions about the library? Come join us in the [#bookshelf freenode IRC channel](http://webchat.freenode.net/?channels=bookshelf) for support on [knex.js](http://knexjs.org/) and bookshelf.js, or post an issue on [Stack Overflow](http://stackoverflow.com/questions/tagged/bookshelf.js). | ||
@@ -163,9 +160,9 @@ ## Contributing | ||
Yes - you can call `.asCallback(function(err, resp) {` on any "sync" method and use the standard `(err, result)` style callback interface if you prefer. | ||
Yes, you can call `.asCallback(function(err, resp) {` on any database operation method and use the standard `(err, result)` style callback interface if you prefer. | ||
### My relations don't seem to be loading, what's up? | ||
Make sure you check that the type is correct for the initial parameters passed to the initial model being fetched. For example `new Model({id: '1'}).load([relations...])` will not return the same as `Model({id: 1}).load([relations...])` - notice that the id is a string in one case and a number in the other. This can be a common mistake if retrieving the id from a url parameter. | ||
Make sure to check that the type is correct for the initial parameters passed to the initial model being fetched. For example `new Model({id: '1'}).load([relations...])` will not return the same as `new Model({id: 1}).load([relations...])` - notice that the id is a string in one case and a number in the other. This can be a common mistake if retrieving the id from a url parameter. | ||
This is only an issue if you're eager loading data with load without first fetching the original model. `Model({id: '1'}).fetch({withRelated: [relations...]})` should work just fine. | ||
This is only an issue if you're eager loading data with load without first fetching the original model. `new Model({id: '1'}).fetch({withRelated: [relations...]})` should work just fine. | ||
@@ -178,12 +175,31 @@ ### My process won't exit after my script is finished, why? | ||
If you pass `{debug: true}` as one of the options in your initialize settings, you can see all of the query calls being made. Sometimes you need to dive a bit further into the various calls and see what all is going on behind the scenes. I'd recommend [node-inspector](https://github.com/dannycoates/node-inspector), which allows you to debug code with `debugger` statements like you would in the browser. | ||
If you pass `debug: true` in the options object to your `knex` initialize call, you can see all of the query calls being made. You can also pass that same option to all methods that access the database, like `model.fetch()` or `model.destroy()`. Examples: | ||
Bookshelf uses its own copy of the "bluebird" promise library, you can read up here for more on debugging these promises... but in short, adding: | ||
```js | ||
process.stderr.on('data', function(data) { | ||
console.log(data); | ||
}); | ||
// Turning on debug mode for all queries | ||
const knex = require('knex')({ | ||
debug: true, | ||
client: 'mysql', | ||
connection: process.env.MYSQL_DATABASE_CONNECTION | ||
}) | ||
const bookshelf = require('bookshelf')(knex) | ||
// Debugging a single query | ||
new User({id: 1}).fetch({debug: true, withRelated: ['posts.tags']}).then(user => { | ||
// ... | ||
}) | ||
``` | ||
At the start of your application code will catch any errors not otherwise caught in the normal promise chain handlers, which is very helpful in debugging. | ||
Sometimes you need to dive a bit further into the various calls and see what all is going on behind the scenes. You can use [node-inspector](https://github.com/dannycoates/node-inspector), which allows you to debug code with `debugger` statements like you would in the browser. | ||
Bookshelf uses its own copy of the `bluebird` Promise library. You can read up [here](http://bluebirdjs.com/docs/api/promise.config.html) for more on debugging Promises. | ||
Adding the following block at the start of your application code will catch any errors not otherwise caught in the normal Promise chain handlers, which is very helpful in debugging: | ||
```js | ||
process.stderr.on('data', (data) => { | ||
console.log(data) | ||
}) | ||
``` | ||
### How do I run the test suite? | ||
@@ -196,3 +212,3 @@ | ||
While it primarily targets Node.js, all dependencies are browser compatible, and it could be adapted to work with other javascript environments supporting a sqlite3 database, by providing a custom [Knex adapter](http://knexjs.org/#Adapters). | ||
While it primarily targets Node.js, all dependencies are browser compatible, and it could be adapted to work with other javascript environments supporting a sqlite3 database, by providing a custom [Knex adapter](http://knexjs.org/#Adapters). No such adapter exists though. | ||
@@ -199,0 +215,0 @@ ### Which open-source projects are using Bookshelf? |
Sorry, the diff of this file is too big to display
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
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
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
291734
0
212
1
22
5872
+ Addedcolorette@1.1.0(transitive)
+ Addedcommander@3.0.2(transitive)
+ Addedgetopts@2.2.5(transitive)
+ Addedknex@0.19.5(transitive)
+ Addedpg-connection-string@2.1.0(transitive)
+ Addedtarn@2.0.0(transitive)
+ Addedtildify@2.0.0(transitive)
- Removed@babel/polyfill@7.12.1(transitive)
- Removed@types/bluebird@3.5.42(transitive)
- Removedcolorette@1.0.8(transitive)
- Removedcommander@2.20.3(transitive)
- Removedcore-js@2.6.12(transitive)
- Removedgetopts@2.2.4(transitive)
- Removedknex@0.17.6(transitive)
- Removedos-homedir@1.0.2(transitive)
- Removedpg-connection-string@2.0.0(transitive)
- Removedregenerator-runtime@0.13.11(transitive)
- Removedtarn@1.1.5(transitive)
- Removedtildify@1.2.0(transitive)
Updatedbluebird@^3.5.5
Updatedinflection@^1.12.0
Updatedlodash@^4.17.15