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

bookshelf

Package Overview
Dependencies
Maintainers
7
Versions
87
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

bookshelf - npm Package Compare versions

Comparing version 0.15.1 to 1.0.0

1

.eslintrc.json

@@ -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 @@

200

lib/base/collection.js

@@ -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 &gt; 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": {

@@ -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

SocketSocket SOC 2 Logo

Product

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

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc