@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
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// Eager Base
|
|
2
|
+
// ---------------
|
|
3
|
+
|
|
4
|
+
// The EagerBase provides a scaffold for handling with eager relation
|
|
5
|
+
// pairing, by queueing the appropriate related method calls with
|
|
6
|
+
// a database specific `eagerFetch` method, which then may utilize
|
|
7
|
+
// `pushModels` for pairing the models depending on the database need.
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const _ = require('lodash');
|
|
12
|
+
const Promise = require('bluebird');
|
|
13
|
+
|
|
14
|
+
function EagerBase(parent, parentResponse, target) {
|
|
15
|
+
this.parent = parent;
|
|
16
|
+
this.parentResponse = parentResponse;
|
|
17
|
+
this.target = target;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
_.extend(EagerBase.prototype, {
|
|
21
|
+
// This helper function is used internally to determine which relations
|
|
22
|
+
// are necessary for fetching based on the `model.load` or `withRelated` option.
|
|
23
|
+
fetch: Promise.method(function(options) {
|
|
24
|
+
const target = this.target;
|
|
25
|
+
const handled = (this.handled = {});
|
|
26
|
+
const withRelated = this.prepWithRelated(options.withRelated);
|
|
27
|
+
const subRelated = {};
|
|
28
|
+
|
|
29
|
+
// Internal flag to determine whether to set the ctor(s) on the `Relation` object.
|
|
30
|
+
target._isEager = true;
|
|
31
|
+
|
|
32
|
+
// Eager load each of the `withRelated` relation item, splitting on '.'
|
|
33
|
+
// which indicates a nested eager load.
|
|
34
|
+
for (const key in withRelated) {
|
|
35
|
+
const related = key.split('.');
|
|
36
|
+
const relationName = related[0];
|
|
37
|
+
|
|
38
|
+
// Add additional eager items to an array, to load at the next level in the query.
|
|
39
|
+
if (related.length > 1) {
|
|
40
|
+
const relatedObj = {};
|
|
41
|
+
subRelated[relationName] = subRelated[relationName] || [];
|
|
42
|
+
relatedObj[related.slice(1).join('.')] = withRelated[key];
|
|
43
|
+
subRelated[relationName].push(relatedObj);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Only allow one of a certain nested type per-level.
|
|
47
|
+
if (handled[relationName]) continue;
|
|
48
|
+
|
|
49
|
+
if (!_.isFunction(target[relationName])) {
|
|
50
|
+
throw new Error(relationName + ' is not defined on the model.');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const relation = target[relationName]();
|
|
54
|
+
|
|
55
|
+
handled[relationName] = relation;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Delete the internal flag from the model.
|
|
59
|
+
delete target._isEager;
|
|
60
|
+
|
|
61
|
+
// Fetch all eager loaded models, loading them onto
|
|
62
|
+
// an array of pending deferred objects, which will handle
|
|
63
|
+
// all necessary pairing with parent objects, etc.
|
|
64
|
+
const pendingDeferred = [];
|
|
65
|
+
for (const relationName in handled) {
|
|
66
|
+
pendingDeferred.push(
|
|
67
|
+
this.eagerFetch(
|
|
68
|
+
relationName,
|
|
69
|
+
handled[relationName],
|
|
70
|
+
_.extend({}, options, {
|
|
71
|
+
isEager: true,
|
|
72
|
+
withRelated: subRelated[relationName],
|
|
73
|
+
_beforeFn: withRelated[relationName] || function() {}
|
|
74
|
+
})
|
|
75
|
+
)
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Return a deferred handler for all of the nested object sync
|
|
80
|
+
// returning the original response when these syncs & pairings are complete.
|
|
81
|
+
return Promise.all(pendingDeferred).return(this.parentResponse);
|
|
82
|
+
}),
|
|
83
|
+
|
|
84
|
+
// Prep the `withRelated` object, to normalize into an object, where the value
|
|
85
|
+
// of each key is a function that is called when running the query.
|
|
86
|
+
prepWithRelated: function(withRelated) {
|
|
87
|
+
if (!Array.isArray(withRelated)) withRelated = [withRelated];
|
|
88
|
+
const obj = {};
|
|
89
|
+
for (let i = 0, l = withRelated.length; i < l; i++) {
|
|
90
|
+
const related = withRelated[i];
|
|
91
|
+
if (_.isString(related)) {
|
|
92
|
+
obj[related] = function() {};
|
|
93
|
+
} else {
|
|
94
|
+
_.extend(obj, related);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return obj;
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
// Pushes each of the incoming models onto a new `related` array,
|
|
101
|
+
// which is used to correcly pair additional nested relations.
|
|
102
|
+
pushModels: function pushModels(relationName, handled, response, options) {
|
|
103
|
+
const models = this.parent;
|
|
104
|
+
const relatedData = handled.relatedData;
|
|
105
|
+
const related = _.map(response, (row) => relatedData.createModel(row));
|
|
106
|
+
|
|
107
|
+
return relatedData.eagerPair(relationName, related, models, options);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
module.exports = EagerBase;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// Events
|
|
2
|
+
// ---------------
|
|
3
|
+
|
|
4
|
+
'use strict';
|
|
5
|
+
|
|
6
|
+
const Promise = require('bluebird');
|
|
7
|
+
const events = require('events');
|
|
8
|
+
const _ = require('lodash');
|
|
9
|
+
const EventEmitter = events.EventEmitter;
|
|
10
|
+
const eventNames = (text) => text.split(/\s+/);
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @class Events
|
|
14
|
+
* @description
|
|
15
|
+
* Base Event class inherited by {@link Model} and {@link Collection}. It's not
|
|
16
|
+
* meant to be used directly, and is only displayed here for completeness.
|
|
17
|
+
*/
|
|
18
|
+
class Events extends EventEmitter {
|
|
19
|
+
/**
|
|
20
|
+
* Registers an event listener. The callback will be invoked whenever the event is fired. The event string may also be
|
|
21
|
+
* a space-delimited list of several event names.
|
|
22
|
+
*
|
|
23
|
+
* @method Events#on
|
|
24
|
+
* @param {string} nameOrNames The name or space separated names of events to register a callback for.
|
|
25
|
+
* @param {function} callback That callback to invoke whenever the event is fired.
|
|
26
|
+
* @return {mixed} The object where this is called on is returned to allow chaining this method call.
|
|
27
|
+
*/
|
|
28
|
+
on(nameOrNames, callback) {
|
|
29
|
+
eventNames(nameOrNames).forEach((name) => {
|
|
30
|
+
super.on(name, callback);
|
|
31
|
+
});
|
|
32
|
+
return this;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @method Events#off
|
|
37
|
+
* @description
|
|
38
|
+
* Remove a previously-bound callback event listener from an object. If no
|
|
39
|
+
* event name is specified, callbacks for all events will be removed.
|
|
40
|
+
*
|
|
41
|
+
* @param {string} nameOrNames
|
|
42
|
+
* The name of the event or space separated list of events to stop listening
|
|
43
|
+
* to.
|
|
44
|
+
* @param {function} callback That callback to remove.
|
|
45
|
+
*/
|
|
46
|
+
off(nameOrNames, callback) {
|
|
47
|
+
if (nameOrNames == null) {
|
|
48
|
+
return this.removeAllListeners();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
eventNames(nameOrNames).forEach((name) => {
|
|
52
|
+
if (callback === undefined) {
|
|
53
|
+
return this.removeAllListeners(name);
|
|
54
|
+
}
|
|
55
|
+
return this.removeListener(name, callback);
|
|
56
|
+
});
|
|
57
|
+
return this;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* @method Events#trigger
|
|
62
|
+
* @description
|
|
63
|
+
* Trigger callbacks for the given event, or space-delimited list of events.
|
|
64
|
+
* Subsequent arguments to `trigger` will be passed along to the event
|
|
65
|
+
* callback.
|
|
66
|
+
*
|
|
67
|
+
* @param {string} nameOrNames
|
|
68
|
+
* The name of the event to trigger. Also accepts a space separated list of
|
|
69
|
+
* event names.
|
|
70
|
+
* @param {...mixed} [args]
|
|
71
|
+
* Extra arguments to pass to the event listener callback function.
|
|
72
|
+
*/
|
|
73
|
+
trigger(nameOrNames) {
|
|
74
|
+
eventNames(nameOrNames).forEach((name) => {
|
|
75
|
+
this.emit.apply(this, [name].concat(Array.from(arguments)));
|
|
76
|
+
});
|
|
77
|
+
return this;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* A promise version of {@link Events#trigger}, returning a promise which
|
|
82
|
+
* resolves with all return values from triggered event handlers. If any of the
|
|
83
|
+
* event handlers throw an `Error` or return a rejected promise, the promise
|
|
84
|
+
* will be rejected. Used internally on the {@link Model#event:creating "creating"},
|
|
85
|
+
* {@link Model#event:updating "updating"}, {@link Model#event:saving "saving"}, and
|
|
86
|
+
* {@link Model@event:destroying "destroying"} events, and can be helpful when needing
|
|
87
|
+
* async event handlers (e.g. for validations).
|
|
88
|
+
*
|
|
89
|
+
* @method Events#triggerThen
|
|
90
|
+
* @param {string} name
|
|
91
|
+
* The event name or a whitespace-separated list of event names to be triggered.
|
|
92
|
+
* @param {...mixed} [args] Arguments to be passed to any registered event handlers.
|
|
93
|
+
* @returns {Promise}
|
|
94
|
+
* A promise resolving to the return values of any triggered handlers.
|
|
95
|
+
*/
|
|
96
|
+
triggerThen(nameOrNames) {
|
|
97
|
+
const names = eventNames(nameOrNames);
|
|
98
|
+
const listeners = _.flatMap(names, (name) => this.listeners(name));
|
|
99
|
+
const args = Array.from(arguments);
|
|
100
|
+
|
|
101
|
+
return Promise.mapSeries(listeners, (listener) => listener.apply(this, args.slice(1)));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* @method Events#once
|
|
106
|
+
* @description
|
|
107
|
+
* Just like {@link Events#on}, but causes the bound callback to fire only
|
|
108
|
+
* once before being removed. Handy for saying "the next time that X happens,
|
|
109
|
+
* do this". When multiple events are passed in using the space separated
|
|
110
|
+
* syntax, the event will fire once for every event you passed in, not once
|
|
111
|
+
* for a combination of all events.
|
|
112
|
+
*
|
|
113
|
+
* @param {string} nameOrNames
|
|
114
|
+
* The name of the event or space separated list of events to register a
|
|
115
|
+
* callback for.
|
|
116
|
+
* @param {function} callback
|
|
117
|
+
* That callback to invoke only once when the event is fired.
|
|
118
|
+
*/
|
|
119
|
+
once(name, callback) {
|
|
120
|
+
const wrapped = _.once(function() {
|
|
121
|
+
this.off(name, wrapped);
|
|
122
|
+
return callback.apply(this, arguments);
|
|
123
|
+
});
|
|
124
|
+
wrapped._callback = callback;
|
|
125
|
+
return this.on(name, wrapped);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
module.exports = Events;
|