@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/.eslintrc.json +18 -0
- package/.nycrc.yml +3 -0
- package/.prettierrc +8 -0
- package/CHANGELOG.md +764 -0
- package/LICENSE +22 -0
- package/README.md +32 -0
- package/bookshelf.js +8 -0
- package/changes.json +14 -0
- package/lib/base/collection.js +802 -0
- package/lib/base/eager.js +111 -0
- package/lib/base/events.js +129 -0
- package/lib/base/model.js +995 -0
- package/lib/base/relation.js +69 -0
- package/lib/bookshelf.js +558 -0
- package/lib/collection.js +545 -0
- package/lib/constants.js +2 -0
- package/lib/eager.js +122 -0
- package/lib/errors.js +36 -0
- package/lib/extend.js +41 -0
- package/lib/helpers.js +212 -0
- package/lib/model.js +1568 -0
- package/lib/relation.js +947 -0
- package/lib/sync.js +244 -0
- package/package.json +111 -0
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
|
+
};
|