UNPKG

13.3 kBJavaScriptView Raw
1'use strict';
2
3Object.defineProperty(exports, "__esModule", {
4 value: true
5});
6
7var _lodash = require('lodash');
8
9var _helpers = require('./helpers');
10
11var _helpers2 = _interopRequireDefault(_helpers);
12
13var _events = require('./base/events');
14
15var _events2 = _interopRequireDefault(_events);
16
17var _model = require('./model');
18
19var _model2 = _interopRequireDefault(_model);
20
21var _collection = require('./collection');
22
23var _collection2 = _interopRequireDefault(_collection);
24
25var _relation2 = require('./relation');
26
27var _relation3 = _interopRequireDefault(_relation2);
28
29var _errors = require('./errors');
30
31var _errors2 = _interopRequireDefault(_errors);
32
33function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
34
35/**
36 * @class Bookshelf
37 * @classdesc
38 *
39 * The Bookshelf library is initialized by passing an initialized Knex client
40 * instance. The knex documentation provides a number of examples for different
41 * databases.
42 *
43 * @constructor
44 * @param {Knex} knex Knex instance.
45 */
46
47
48// All core modules required for the bookshelf instance.
49function Bookshelf(knex) {
50 if (!knex || knex.name !== 'knex') {
51 throw new Error('Invalid knex instance');
52 }
53 var bookshelf = {
54 VERSION: require('../package.json').version
55 };
56
57 var Model = bookshelf.Model = _model2.default.extend({
58 _builder: builderFn,
59
60 // The `Model` constructor is referenced as a property on the `Bookshelf`
61 // instance, mixing in the correct `builder` method, as well as the
62 // `relation` method, passing in the correct `Model` & `Collection`
63 // constructors for later reference.
64 _relation: function _relation(type, Target, options) {
65 if (type !== 'morphTo' && !(0, _lodash.isFunction)(Target)) {
66 throw new Error('A valid target model must be defined for the ' + (0, _lodash.result)(this, 'tableName') + ' ' + type + ' relation');
67 }
68 return new Relation(type, Target, options);
69 }
70 }, {
71
72 /**
73 * @method Model.forge
74 * @belongsTo Model
75 * @description
76 *
77 * A simple helper function to instantiate a new Model without needing `new`.
78 *
79 * @param {Object=} attributes Initial values for this model's attributes.
80 * @param {Object=} options Hash of options.
81 * @param {string=} options.tableName Initial value for {@linkcode Model#tableName tableName}.
82 * @param {Boolean=} [options.hasTimestamps=false]
83 *
84 * Initial value for {@linkcode Model#hasTimestamps hasTimestamps}.
85 *
86 * @param {Boolean} [options.parse=false]
87 *
88 * Convert attributes by {@linkcode Model#parse parse} before being
89 * {@linkcode Model#set set} on the `model`.
90 */
91 forge: forge,
92
93 /**
94 * @method Model.collection
95 * @belongsTo Model
96 * @description
97 *
98 * A simple static helper to instantiate a new {@link Collection}, setting
99 * the current `model` as the collection's target.
100 *
101 * @example
102 *
103 * Customer.collection().fetch().then(function(collection) {
104 * // ...
105 * });
106 *
107 * @param {(Model[])=} models
108 * @param {Object=} options
109 * @returns {Collection}
110 */
111 collection: function collection(models, options) {
112 return new bookshelf.Collection(models || [], (0, _lodash.extend)({}, options, { model: this }));
113 },
114
115
116 /**
117 * @method Model.count
118 * @belongsTo Model
119 * @since 0.8.2
120 * @description
121 *
122 * Gets the number of matching records in the database, respecting any
123 * previous calls to {@link Model#query query}. If a `column` is provided,
124 * records with a null value in that column will be excluded from the count.
125 *
126 * @param {string} [column='*']
127 * Specify a column to count - rows with null values in this column will be excluded.
128 * @param {Object=} options
129 * Hash of options.
130 * @returns {Promise<Number>}
131 * A promise resolving to the number of matching rows.
132 */
133 count: function count(column, options) {
134 return this.forge().count(column, options);
135 },
136
137
138 /**
139 * @method Model.fetchAll
140 * @belongsTo Model
141 * @description
142 *
143 * Simple helper function for retrieving all instances of the given model.
144 *
145 * @see Model#fetchAll
146 * @returns {Promise<Collection>}
147 */
148 fetchAll: function fetchAll(options) {
149 return this.forge().fetchAll(options);
150 }
151 });
152
153 var Collection = bookshelf.Collection = _collection2.default.extend({
154
155 _builder: builderFn
156
157 }, {
158
159 /**
160 * @method Collection.forge
161 * @belongsTo Collection
162 * @description
163 *
164 * A simple helper function to instantiate a new Collection without needing
165 * new.
166 *
167 * @param {(Object[]|Model[])=} [models]
168 * Set of models (or attribute hashes) with which to initialize the
169 * collection.
170 * @param {Object} options Hash of options.
171 *
172 * @example
173 *
174 * var Promise = require('bluebird');
175 * var Accounts = bookshelf.Collection.extend({
176 * model: Account
177 * });
178 *
179 * var accounts = Accounts.forge([
180 * {name: 'Person1'},
181 * {name: 'Person2'}
182 * ]);
183 *
184 * Promise.all(accounts.invokeMap('save')).then(function() {
185 * // collection models should now be saved...
186 * });
187 */
188 forge: forge
189
190 });
191
192 // The collection also references the correct `Model`, specified above, for
193 // creating new `Model` instances in the collection.
194 Collection.prototype.model = Model;
195 Model.prototype.Collection = Collection;
196
197 var Relation = _relation3.default.extend({
198 Model: Model, Collection: Collection
199 });
200
201 // A `Bookshelf` instance may be used as a top-level pub-sub bus, as it mixes
202 // in the `Events` object. It also contains the version number, and a
203 // `Transaction` method referencing the correct version of `knex` passed into
204 // the object.
205 (0, _lodash.extend)(bookshelf, _events2.default, _errors2.default, {
206
207 /**
208 * @method Bookshelf#transaction
209 * @memberOf Bookshelf
210 * @description
211 *
212 * An alias to `{@link http://knexjs.org/#Transactions
213 * Knex#transaction}`, the `transaction` object must be passed along in the
214 * options of any relevant Bookshelf calls, to ensure all queries are on the
215 * same connection. The entire transaction block is a promise that will
216 * resolve when the transaction is committed, or fail if the transaction is
217 * rolled back.
218 *
219 * var Promise = require('bluebird');
220 *
221 * Bookshelf.transaction(function(t) {
222 * return new Library({name: 'Old Books'})
223 * .save(null, {transacting: t})
224 * .tap(function(model) {
225 * return Promise.map([
226 * {title: 'Canterbury Tales'},
227 * {title: 'Moby Dick'},
228 * {title: 'Hamlet'}
229 * ], function(info) {
230 * // Some validation could take place here.
231 * return new Book(info).save({'shelf_id': model.id}, {transacting: t});
232 * });
233 * });
234 * }).then(function(library) {
235 * console.log(library.related('books').pluck('title'));
236 * }).catch(function(err) {
237 * console.error(err);
238 * });
239 *
240 * @param {Bookshelf~transactionCallback} transactionCallback
241 * Callback containing transaction logic. The callback should return a
242 * promise.
243 *
244 * @returns {Promise<mixed>}
245 * A promise resolving to the value returned from {@link
246 * Bookshelf~transactionCallback transactionCallback}.
247 */
248 transaction: function transaction() {
249 return this.knex.transaction.apply(this, arguments);
250 },
251
252
253 /**
254 * @callback Bookshelf~transactionCallback
255 * @description
256 *
257 * A transaction block to be provided to {@link Bookshelf#transaction}.
258 *
259 * @see {@link http://knexjs.org/#Transactions Knex#transaction}
260 * @see Bookshelf#transaction
261 *
262 * @param {Transaction} transaction
263 * @returns {Promise<mixed>}
264 */
265
266 /**
267 * @method Bookshelf#plugin
268 * @memberOf Bookshelf
269 * @description
270 *
271 * This method provides a nice, tested, standardized way of adding plugins
272 * to a `Bookshelf` instance, injecting the current instance into the
273 * plugin, which should be a `module.exports`.
274 *
275 * You can add a plugin by specifying a string with the name of the plugin
276 * to load. In this case it will try to find a module. It will first check
277 * for a match within the `bookshelf/plugins` directory. If nothing is
278 * found it will pass the string to `require()`, so you can either require
279 * an npm dependency by name or one of your own modules by relative path:
280 *
281 * bookshelf.plugin('./bookshelf-plugins/my-favourite-plugin');
282 * bookshelf.plugin('plugin-from-npm');
283 *
284 * There are a few built-in plugins already, along with many independently
285 * developed ones. See [the list of available plugins](#plugins).
286 *
287 * You can also provide an array of strings or functions, which is the same
288 * as calling `bookshelf.plugin()` multiple times. In this case the same
289 * options object will be reused:
290 *
291 * bookshelf.plugin(['registry', './my-plugins/special-parse-format']);
292 *
293 * Example plugin:
294 *
295 * // Converts all string values to lower case when setting attributes on a model
296 * module.exports = function(bookshelf) {
297 * bookshelf.Model = bookshelf.Model.extend({
298 * set: function(key, value, options) {
299 * if (!key) return this;
300 * if (typeof value === 'string') value = value.toLowerCase();
301 * return bookshelf.Model.prototype.set.call(this, key, value, options);
302 * }
303 * });
304 * }
305 *
306 * @param {string|array|Function} plugin
307 * The plugin or plugins to add. If you provide a string it can
308 * represent a built-in plugin, an npm package or a file somewhere on
309 * your project. You can also pass a function as argument to add it as a
310 * plugin. Finally, it's also possible to pass an array of strings or
311 * functions to add them all at once.
312 * @param {mixed} options
313 * This can be anything you want and it will be passed directly to the
314 * plugin as the second argument when loading it.
315 */
316 plugin: function plugin(_plugin, options) {
317 var _this = this;
318
319 if ((0, _lodash.isString)(_plugin)) {
320 try {
321 require('./plugins/' + _plugin)(this, options);
322 } catch (e) {
323 if (e.code !== 'MODULE_NOT_FOUND') {
324 throw e;
325 }
326 if (!process.browser) {
327 require(_plugin)(this, options);
328 }
329 }
330 } else if ((0, _lodash.isArray)(_plugin)) {
331 (0, _lodash.each)(_plugin, function (p) {
332 _this.plugin(p, options);
333 });
334 } else {
335 _plugin(this, options);
336 }
337 return this;
338 }
339 });
340
341 /**
342 * @member Bookshelf#knex
343 * @memberOf Bookshelf
344 * @type {Knex}
345 * @description
346 * A reference to the {@link http://knexjs.org Knex.js} instance being used by Bookshelf.
347 */
348 bookshelf.knex = knex;
349
350 // The `forge` function properly instantiates a new Model or Collection
351 // without needing the `new` operator... to make object creation cleaner
352 // and more chainable.
353 function forge() {
354 return new (Function.prototype.bind.apply(this, [null].concat(Array.prototype.slice.call(arguments))))();
355 }
356
357 function builderFn(tableNameOrBuilder) {
358 var _this2 = this;
359
360 var builder = null;
361
362 if ((0, _lodash.isString)(tableNameOrBuilder)) {
363 builder = bookshelf.knex(tableNameOrBuilder);
364 } else if (tableNameOrBuilder == null) {
365 builder = bookshelf.knex.queryBuilder();
366 } else {
367 // Assuming here that `tableNameOrBuilder` is a QueryBuilder instance. Not
368 // aware of a way to check that this is the case (ie. using
369 // `Knex.isQueryBuilder` or equivalent).
370 builder = tableNameOrBuilder;
371 }
372
373 return builder.on('query', function (data) {
374 return _this2.trigger('query', data);
375 });
376 }
377
378 // Attach `where`, `query`, and `fetchAll` as static methods.
379 ['where', 'query'].forEach(function (method) {
380 Model[method] = Collection[method] = function () {
381 var model = this.forge();
382 return model[method].apply(model, arguments);
383 };
384 });
385
386 return bookshelf;
387}
388
389// Constructor for a new `Bookshelf` object, it accepts an active `knex`
390// instance and initializes the appropriate `Model` and `Collection`
391// constructors for use in the current instance.
392
393
394// We've supplemented `Events` with a `triggerThen` method to allow for
395// asynchronous event handling via promises. We also mix this into the
396// prototypes of the main objects in the library.
397Bookshelf.initialize = function (knex) {
398 _helpers2.default.warn("Bookshelf.initialize is deprecated, pass knex directly: require('bookshelf')(knex)");
399 return new Bookshelf(knex);
400};
401
402// Finally, export `Bookshelf` to the world.
403exports.default = Bookshelf;
\No newline at end of file