@depup/bookshelf 1.2.0-depup.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/errors.js ADDED
@@ -0,0 +1,36 @@
1
+ const createError = require('create-error');
2
+
3
+ function ModelNotResolvedError() {
4
+ ModelNotResolvedError.prototype = Object.create(Error.prototype, {
5
+ constructor: {
6
+ value: ModelNotResolvedError,
7
+ enumerable: false,
8
+ writable: true,
9
+ configurable: true
10
+ }
11
+ });
12
+
13
+ Object.setPrototypeOf(ModelNotResolvedError, Error);
14
+
15
+ function ModelNotResolvedError() {
16
+ return Object.getPrototypeOf(ModelNotResolvedError).apply(this, arguments);
17
+ }
18
+
19
+ return ModelNotResolvedError;
20
+ }
21
+
22
+ module.exports = {
23
+ // Thrown when a model is not found.
24
+ NotFoundError: createError('NotFoundError'),
25
+
26
+ // Thrown when the collection is empty upon fetching it.
27
+ EmptyError: createError('EmptyError'),
28
+
29
+ // Thrown when an update affects no rows
30
+ NoRowsUpdatedError: createError('NoRowsUpdatedError'),
31
+
32
+ // Thrown when a delete affects no rows.
33
+ NoRowsDeletedError: createError('NoRowsDeletedError'),
34
+
35
+ ModelNotResolvedError: ModelNotResolvedError()
36
+ };
package/lib/extend.js ADDED
@@ -0,0 +1,41 @@
1
+ const _ = require('lodash');
2
+
3
+ // Uses a hash of prototype properties and class properties to be extended.
4
+ module.exports = function extend(protoProps, staticProps) {
5
+ const Parent = this;
6
+
7
+ // The constructor function for the new subclass is either defined by you
8
+ // (the "constructor" property in your `extend` definition), or defaulted
9
+ // by us to simply call the parent's constructor.
10
+ const Child =
11
+ protoProps && protoProps.hasOwnProperty('constructor')
12
+ ? protoProps.constructor
13
+ : function() {
14
+ return Parent.apply(this, arguments);
15
+ };
16
+
17
+ Object.assign(Child, Parent, staticProps);
18
+
19
+ // Set the prototype chain to inherit from `Parent`.
20
+ Child.prototype = Object.create(Parent.prototype, {
21
+ constructor: {
22
+ value: Child,
23
+ enumerable: false,
24
+ writable: true,
25
+ configurable: true
26
+ }
27
+ });
28
+
29
+ if (protoProps) {
30
+ Object.assign(Child.prototype, protoProps);
31
+ }
32
+
33
+ // Give child access to the parent prototype as part of "super"
34
+ Child.__super__ = Parent.prototype;
35
+
36
+ // If there is an "extended" function set on the parent,
37
+ // call it with the extended child object.
38
+ if (_.isFunction(Parent.extended)) Parent.extended(Child);
39
+
40
+ return Child;
41
+ };
package/lib/helpers.js ADDED
@@ -0,0 +1,212 @@
1
+ /* eslint no-console: 0 */
2
+
3
+ // Helpers
4
+ // ---------------
5
+
6
+ const _ = require('lodash');
7
+ const Promise = require('bluebird');
8
+ const Model = require('./base/model');
9
+
10
+ function ensureIntWithDefault(number, defaultValue) {
11
+ if (!number) return defaultValue;
12
+ const parsedNumber = parseInt(number, 10);
13
+ if (Number.isNaN(parsedNumber)) return defaultValue;
14
+
15
+ return parsedNumber;
16
+ }
17
+
18
+ module.exports = {
19
+ // This is used by both Model and Collection methods to paginate the results.
20
+ fetchPage(options) {
21
+ const DEFAULT_LIMIT = 10;
22
+ const DEFAULT_OFFSET = 0;
23
+ const DEFAULT_PAGE = 1;
24
+
25
+ const isModel = this instanceof Model;
26
+ const fetchOptions = _.omit(options, ['page', 'pageSize', 'limit', 'offset']);
27
+ const countOptions = _.omit(fetchOptions, ['require', 'columns', 'withRelated', 'lock']);
28
+ const fetchMethodName = isModel ? 'fetchAll' : 'fetch';
29
+ const targetModel = isModel ? this.constructor : this.target || this.model;
30
+ const tableName = targetModel.prototype.tableName;
31
+ const idAttribute = targetModel.prototype.idAttribute || 'id';
32
+ const targetIdColumn = [`${tableName}.${idAttribute}`];
33
+ let page;
34
+ let pageSize;
35
+ let limit;
36
+ let offset;
37
+
38
+ if (!options.limit && !options.offset) {
39
+ pageSize = ensureIntWithDefault(options.pageSize, DEFAULT_LIMIT);
40
+ page = ensureIntWithDefault(options.page, DEFAULT_PAGE);
41
+ limit = pageSize;
42
+ offset = limit * (page - 1);
43
+ } else {
44
+ limit = ensureIntWithDefault(options.limit, DEFAULT_LIMIT);
45
+ offset = ensureIntWithDefault(options.offset, DEFAULT_OFFSET);
46
+ }
47
+
48
+ const paginate = () => {
49
+ return this.clone()
50
+ .query((qb) => {
51
+ Object.assign(qb, this.query().clone());
52
+ qb.limit.apply(qb, [limit]);
53
+ qb.offset.apply(qb, [offset]);
54
+
55
+ return null;
56
+ })
57
+ [fetchMethodName](fetchOptions);
58
+ };
59
+
60
+ const metadata = !options.limit && !options.offset ? {page, pageSize} : {offset, limit};
61
+
62
+ if (options.disableCount) {
63
+ return paginate().then((rows) => {
64
+ return Object.assign(rows, {pagination: metadata});
65
+ });
66
+ }
67
+
68
+ const count = () => {
69
+ const notNeededQueries = ['orderByBasic', 'orderByRaw', 'groupByBasic', 'groupByRaw'];
70
+ const counter = this.clone();
71
+ const groupColumns = [];
72
+
73
+ return counter
74
+ .query((qb) => {
75
+ Object.assign(qb, this.query().clone());
76
+
77
+ // Remove grouping and ordering. Ordering is unnecessary for a count, and grouping returns the entire result
78
+ // set. What we want instead is to use `DISTINCT`.
79
+ _.remove(qb._statements, (statement) => {
80
+ if (statement.grouping === 'group') statement.value.forEach((value) => groupColumns.push(value));
81
+ if (statement.grouping === 'columns' && statement.distinct)
82
+ statement.value.forEach((value) => groupColumns.push(value));
83
+
84
+ return notNeededQueries.indexOf(statement.type) > -1 || statement.grouping === 'columns';
85
+ });
86
+
87
+ if (!isModel && counter.relatedData) {
88
+ // Remove joining columns that break COUNT operation, eg. pivotal coulmns for belongsToMany relation.
89
+ counter.relatedData.joinColumns = function() {};
90
+ }
91
+
92
+ qb.countDistinct.apply(qb, groupColumns.length > 0 ? groupColumns : targetIdColumn);
93
+ })
94
+ [fetchMethodName](countOptions)
95
+ .then((result) => {
96
+ if (result && result.length == 1) {
97
+ // We shouldn't have to do this, instead it should be result.models[0].get('count') but SQLite and MySQL
98
+ // return a really strange key name and Knex doesn't abstract that away yet:
99
+ // https://github.com/tgriesser/knex/issues/3315.
100
+ const keys = Object.keys(result.models[0].attributes);
101
+
102
+ if (keys.length === 1) {
103
+ const key = Object.keys(result.models[0].attributes)[0];
104
+ metadata.rowCount = parseInt(result.models[0].attributes[key]);
105
+ }
106
+ }
107
+ });
108
+ };
109
+
110
+ return Promise.join(paginate(), count(), (rows) => {
111
+ const pageCount = Math.ceil(metadata.rowCount / limit);
112
+ const pageData = Object.assign(metadata, {pageCount});
113
+ return Object.assign(rows, {pagination: pageData});
114
+ });
115
+ },
116
+
117
+ // Sets the constraints necessary during a `model.save` call.
118
+ saveConstraints: function(model, relatedData) {
119
+ const data = {};
120
+
121
+ if (
122
+ relatedData &&
123
+ !relatedData.isThrough() &&
124
+ relatedData.type !== 'belongsToMany' &&
125
+ relatedData.type !== 'belongsTo'
126
+ ) {
127
+ data[relatedData.key('foreignKey')] = relatedData.parentFk || model.get(relatedData.key('foreignKey'));
128
+ if (relatedData.isMorph()) data[relatedData.key('morphKey')] = relatedData.key('morphValue');
129
+ }
130
+
131
+ return model.set(model.parse(data));
132
+ },
133
+
134
+ // Finds the specific `morphTo` target Model we should be working with, or throws
135
+ // an error if none is matched.
136
+ morphCandidate: function(candidates, morphValue) {
137
+ const Target = _.find(candidates, (candidate) => candidate[1] === morphValue);
138
+
139
+ if (!Target)
140
+ throw new Error('The target polymorphic type "' + morphValue + '" is not one of the defined target types');
141
+
142
+ return Target[0];
143
+ },
144
+
145
+ // If there are no arguments, return the current object's
146
+ // query builder (or create and return a new one). If there are arguments,
147
+ // call the query builder with the first argument, applying the rest.
148
+ // If the first argument is an object, assume the keys are query builder
149
+ // methods, and the values are the arguments for the query.
150
+ query: function(obj, args) {
151
+ // Ensure the object has a query builder.
152
+ if (!obj._knex) {
153
+ const tableName = _.result(obj, 'tableName');
154
+ obj._knex = obj._builder(tableName);
155
+ }
156
+
157
+ // If there are no arguments, return the query builder.
158
+ if (args.length === 0) return obj._knex;
159
+
160
+ const method = args[0];
161
+
162
+ if (_.isFunction(method)) {
163
+ // `method` is a query builder callback. Call it on the query builder object.
164
+ method.call(obj._knex, obj._knex);
165
+ } else if (_.isObject(method)) {
166
+ // `method` is an object. Use keys as methods and values as arguments to
167
+ // the query builder.
168
+ for (const key in method) {
169
+ const target = Array.isArray(method[key]) ? method[key] : [method[key]];
170
+ obj._knex[key].apply(obj._knex, target);
171
+ }
172
+ } else {
173
+ // Otherwise assume that the `method` is string name of a query builder
174
+ // method, and use the remaining args as arguments to that method.
175
+ obj._knex[method].apply(obj._knex, args.slice(1));
176
+ }
177
+
178
+ return obj;
179
+ },
180
+
181
+ orderBy: function(obj, sort, order) {
182
+ let tableName;
183
+ let idAttribute;
184
+ let _sort;
185
+
186
+ if (obj.model) {
187
+ tableName = obj.model.prototype.tableName;
188
+ idAttribute = obj.model.prototype.idAttribute || 'id';
189
+ } else {
190
+ tableName = obj.constructor.prototype.tableName;
191
+ idAttribute = obj.constructor.prototype.idAttribute || 'id';
192
+ }
193
+
194
+ if (sort && sort.indexOf('-') === 0) {
195
+ _sort = sort.slice(1);
196
+ } else if (sort) {
197
+ _sort = sort;
198
+ } else {
199
+ _sort = idAttribute;
200
+ }
201
+
202
+ const _order = order || (sort && sort.indexOf('-') === 0 ? 'DESC' : 'ASC');
203
+
204
+ if (_sort.indexOf('.') === -1) {
205
+ _sort = `${tableName}.${_sort}`;
206
+ }
207
+
208
+ return obj.query((qb) => {
209
+ qb.orderBy(_sort, _order);
210
+ });
211
+ }
212
+ };