@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/sync.js ADDED
@@ -0,0 +1,244 @@
1
+ // Sync
2
+ // ---------------
3
+ 'use strict';
4
+
5
+ const _ = require('lodash');
6
+ const Promise = require('bluebird');
7
+ const validLocks = ['forShare', 'forUpdate'];
8
+
9
+ function supportsReturning(client = {}) {
10
+ if (!client.config || !client.config.client) return false;
11
+ return ['postgresql', 'postgres', 'pg', 'oracle', 'mssql'].includes(client.config.client);
12
+ }
13
+
14
+ // Sync is the dispatcher for any database queries,
15
+ // taking the "syncing" `model` or `collection` being queried, along with
16
+ // a hash of options that are used in the various query methods.
17
+ // If the `transacting` option is set, the query is assumed to be
18
+ // part of a transaction, and this information is passed along to `Knex`.
19
+ const Sync = function(syncing, options) {
20
+ options = options || {};
21
+ this.query = syncing.query();
22
+ this.syncing = syncing.resetQuery();
23
+ this.options = options;
24
+ if (options.debug) this.query.debug();
25
+ if (options.transacting) {
26
+ this.query.transacting(options.transacting);
27
+ if (validLocks.indexOf(options.lock) > -1) this.query[options.lock]();
28
+ }
29
+ if (options.withSchema) this.query.withSchema(options.withSchema);
30
+ };
31
+
32
+ _.extend(Sync.prototype, {
33
+ // Prefix all keys of the passed in object with the
34
+ // current table name
35
+ prefixFields: function(fields) {
36
+ const tableName = this.syncing.tableName;
37
+ const prefixed = {};
38
+ for (const key in fields) {
39
+ prefixed[tableName + '.' + key] = fields[key];
40
+ }
41
+ return prefixed;
42
+ },
43
+
44
+ // Select the first item from the database - only used by models.
45
+ first: Promise.method(function(attributes) {
46
+ const model = this.syncing;
47
+ const query = this.query;
48
+
49
+ // We'll never use an JSON object for a search, because even
50
+ // PostgreSQL, which has JSON type columns, does not support the `=`
51
+ // operator.
52
+ //
53
+ // NOTE: `_.omit` returns an empty object, even if attributes are null.
54
+ const whereAttributes = _.omitBy(attributes, (attribute, name) => {
55
+ return _.isPlainObject(attribute) || name === model.idAttribute;
56
+ });
57
+ const formattedAttributes = model.format(whereAttributes);
58
+
59
+ if (model.idAttribute in attributes) {
60
+ formattedAttributes[model.idAttribute] = attributes[model.idAttribute];
61
+ }
62
+
63
+ if (!_.isEmpty(formattedAttributes)) query.where(this.prefixFields(formattedAttributes));
64
+ query.limit(1);
65
+
66
+ return this.select();
67
+ }),
68
+
69
+ // Runs a `count` query on the database, adding any necessary relational
70
+ // constraints. Returns a promise that resolves to an integer count.
71
+ count: Promise.method(function(column) {
72
+ const knex = this.query,
73
+ options = this.options,
74
+ relatedData = this.syncing.relatedData,
75
+ fks = {};
76
+
77
+ return Promise.bind(this)
78
+ .then(function() {
79
+ // Inject all appropriate select costraints dealing with the relation
80
+ // into the `knex` query builder for the current instance.
81
+ if (relatedData)
82
+ return Promise.try(function() {
83
+ if (relatedData.isThrough()) {
84
+ fks[relatedData.key('foreignKey')] = relatedData.parentFk;
85
+ const through = new relatedData.throughTarget(fks);
86
+ relatedData.pivotColumns = through.parse(relatedData.pivotColumns);
87
+ } else if (relatedData.type === 'hasMany') {
88
+ const fk = relatedData.key('foreignKey');
89
+ knex.where(fk, relatedData.parentFk);
90
+ }
91
+ });
92
+ })
93
+ .then(function() {
94
+ options.query = knex;
95
+
96
+ /**
97
+ * Counting event.
98
+ *
99
+ * Fired before a `count` query. A promise may be
100
+ * returned from the event handler for async behaviour.
101
+ *
102
+ * @event Model#counting
103
+ * @tutorial events
104
+ * @param {Model} model The model firing the event.
105
+ * @param {Object} options Options object passed to {@link Model#count count}.
106
+ * @returns {Promise}
107
+ */
108
+ return this.syncing.triggerThen('counting', this.syncing, options);
109
+ })
110
+ .then(function() {
111
+ return knex.count((column || '*') + ' as count');
112
+ })
113
+ .then(function(rows) {
114
+ return rows[0].count;
115
+ });
116
+ }),
117
+
118
+ // Runs a `select` query on the database, adding any necessary relational
119
+ // constraints, resetting the query when complete. If there are results and
120
+ // eager loaded relations, those are fetched and returned on the model before
121
+ // the promise is resolved. Any `success` handler passed in the
122
+ // options will be called - used by both models & collections.
123
+ select: Promise.method(function() {
124
+ const knex = this.query;
125
+ const options = this.options;
126
+ const relatedData = this.syncing.relatedData;
127
+ const fks = {};
128
+ let columns = null;
129
+
130
+ // Check if any `select` style statements have been called with column
131
+ // specifications. This could include `distinct()` with no arguments, which
132
+ // does not affect inform the columns returned.
133
+ const queryContainsColumns = _(knex._statements)
134
+ .filter({grouping: 'columns'})
135
+ .some('value.length');
136
+
137
+ return Promise.bind(this)
138
+ .then(function() {
139
+ // Set the query builder on the options, in-case we need to
140
+ // access in the `fetching` event handlers.
141
+ options.query = knex;
142
+
143
+ // Inject all appropriate select costraints dealing with the relation
144
+ // into the `knex` query builder for the current instance.
145
+ if (relatedData)
146
+ return Promise.try(function() {
147
+ if (relatedData.isThrough()) {
148
+ fks[relatedData.key('foreignKey')] = relatedData.parentFk;
149
+ const through = new relatedData.throughTarget(fks);
150
+
151
+ return through.triggerThen('fetching', through, relatedData.pivotColumns, options).then(function() {
152
+ relatedData.pivotColumns = through.parse(relatedData.pivotColumns);
153
+ });
154
+ }
155
+ });
156
+ })
157
+ .tap(() => {
158
+ // If this is a relation, apply the appropriate constraints.
159
+ if (relatedData) {
160
+ relatedData.selectConstraints(knex, options);
161
+ } else {
162
+ // Call the function, if one exists, to constrain the eager loaded query.
163
+ if (options._beforeFn) options._beforeFn.call(knex, knex);
164
+
165
+ if (options.columns) {
166
+ // Normalize single column name into array.
167
+ columns = Array.isArray(options.columns) ? options.columns : [options.columns];
168
+ } else if (!queryContainsColumns) {
169
+ // If columns have already been selected via the `query` method
170
+ // we will use them. Otherwise, select all columns in this table.
171
+ columns = [_.result(this.syncing, 'tableName') + '.*'];
172
+ }
173
+ }
174
+
175
+ // Set the query builder on the options, for access in the `fetching`
176
+ // event handlers.
177
+ options.query = knex;
178
+
179
+ /**
180
+ * Fired before a `fetch` operation. A promise may be returned from the event handler for
181
+ * async behaviour.
182
+ *
183
+ * @example
184
+ * const MyModel = bookshelf.model('MyModel', {
185
+ * initialize() {
186
+ * this.on('fetching', function(model, columns, options) {
187
+ * options.query.where('status', 'active')
188
+ * })
189
+ * }
190
+ * })
191
+ *
192
+ * @event Model#fetching
193
+ * @tutorial events
194
+ * @param {Model} model The model which is about to be fetched.
195
+ * @param {string[]} columns The columns to be retrieved by the query.
196
+ * @param {Object} options Options object passed to {@link Model#fetch fetch}.
197
+ * @param {QueryBuilder} options.query
198
+ * Query builder to be used for fetching. This can be used to modify or add to the query
199
+ * before it is executed. See example above.
200
+ * @return {Promise}
201
+ */
202
+ return this.syncing.triggerThen('fetching', this.syncing, columns, options);
203
+ })
204
+ .then(() => knex.select(columns));
205
+ }),
206
+
207
+ // Issues an `insert` command on the query - only used by models.
208
+ insert: Promise.method(function() {
209
+ const syncing = this.syncing;
210
+ return this.query.insert(
211
+ syncing.format(_.extend(Object.create(null), syncing.attributes)),
212
+ supportsReturning(this.query.client) && this.options.autoRefresh !== false ? '*' : null
213
+ );
214
+ }),
215
+
216
+ // Issues an `update` command on the query - only used by models.
217
+ update: Promise.method(function(attrs) {
218
+ const syncing = this.syncing,
219
+ query = this.query;
220
+ if (syncing.id != null) query.where(syncing.format({[syncing.idAttribute]: syncing.id}));
221
+ if (_.filter(query._statements, {grouping: 'where'}).length === 0) {
222
+ throw new Error('A model cannot be updated without a "where" clause or an idAttribute.');
223
+ }
224
+ var updating = syncing.format(_.extend(Object.create(null), attrs));
225
+ if (syncing.id === updating[syncing.idAttribute]) {
226
+ delete updating[syncing.idAttribute];
227
+ }
228
+ if (supportsReturning(query.client) && this.options.autoRefresh !== false) query.returning('*');
229
+ return query.update(updating);
230
+ }),
231
+
232
+ // Issues a `delete` command on the query.
233
+ del: Promise.method(function() {
234
+ const query = this.query,
235
+ syncing = this.syncing;
236
+ if (syncing.id != null) query.where(syncing.format({[syncing.idAttribute]: syncing.id}));
237
+ if (_.filter(query._statements, {grouping: 'where'}).length === 0) {
238
+ throw new Error('A model cannot be destroyed without a "where" clause or an idAttribute.');
239
+ }
240
+ return this.query.del();
241
+ })
242
+ });
243
+
244
+ module.exports = Sync;
package/package.json ADDED
@@ -0,0 +1,111 @@
1
+ {
2
+ "name": "@depup/bookshelf",
3
+ "version": "1.2.0-depup.0",
4
+ "description": "[DepUp] A lightweight ORM for PostgreSQL, MySQL, and SQLite3",
5
+ "main": "bookshelf.js",
6
+ "scripts": {
7
+ "format": "prettier --write \"{lib,scripts,test}/**/*.js\"",
8
+ "lint": "eslint bookshelf.js lib/",
9
+ "cover": "npm run lint && nyc mocha --check-leaks -t 10000 -b",
10
+ "test": "npm run lint && mocha --check-leaks -t 10000 -b",
11
+ "jsdoc": "./scripts/jsdoc.sh",
12
+ "postpublish": "./scripts/postpublish.sh"
13
+ },
14
+ "homepage": "https://bookshelfjs.org",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "https://github.com/bookshelf/bookshelf.git"
18
+ },
19
+ "keywords": [
20
+ "depup",
21
+ "dependency-bumped",
22
+ "updated-deps",
23
+ "bookshelf",
24
+ "orm",
25
+ "mysql",
26
+ "postgresql",
27
+ "sqlite",
28
+ "datamapper",
29
+ "active record"
30
+ ],
31
+ "dependencies": {
32
+ "bluebird": "^3.7.2",
33
+ "create-error": "~0.3.1",
34
+ "inflection": "^3.0.2",
35
+ "lodash": "^4.17.23"
36
+ },
37
+ "husky": {
38
+ "hooks": {
39
+ "pre-commit": "lint-staged"
40
+ }
41
+ },
42
+ "lint-staged": {
43
+ "*.{js,json}": [
44
+ "prettier --write",
45
+ "git add"
46
+ ]
47
+ },
48
+ "devDependencies": {
49
+ "bookshelf-jsdoc-theme": "^1.0.1",
50
+ "chai": "^4.2.0",
51
+ "eslint": "^6.8.0",
52
+ "eslint-config-prettier": "^6.10.0",
53
+ "eslint-plugin-prettier": "^3.1.2",
54
+ "husky": "^3.0.5",
55
+ "jsdoc": "^3.6.3",
56
+ "knex": "~0.21.0",
57
+ "lint-staged": "^9.2.5",
58
+ "mocha": "^7.1.2",
59
+ "mysql": "^2.18.1",
60
+ "nyc": "^15.0.0",
61
+ "pg": "^8.0.3",
62
+ "prettier": "^1.18.2",
63
+ "sinon": "^8.1.1",
64
+ "sinon-chai": "^3.3.0",
65
+ "sqlite3": "^4.1.1",
66
+ "uuid": "^3.3.3"
67
+ },
68
+ "peerDependencies": {
69
+ "knex": ">=0.15.0 <0.22.0"
70
+ },
71
+ "author": {
72
+ "name": "Tim Griesser",
73
+ "url": "https://github.com/tgriesser"
74
+ },
75
+ "contributors": [
76
+ {
77
+ "name": "Edward Greve",
78
+ "url": "https://github.com/anyong"
79
+ },
80
+ {
81
+ "name": "Rhys van der Waerden",
82
+ "url": "https://github.com/rhys-vdw"
83
+ },
84
+ {
85
+ "name": "Ricardo Graça",
86
+ "url": "https://github.com/ricardograca"
87
+ }
88
+ ],
89
+ "license": "MIT",
90
+ "readmeFilename": "README.md",
91
+ "engines": {
92
+ "node": ">=6"
93
+ },
94
+ "depup": {
95
+ "changes": {
96
+ "inflection": {
97
+ "from": "^1.12.0",
98
+ "to": "^3.0.2"
99
+ },
100
+ "lodash": {
101
+ "from": "^4.17.15",
102
+ "to": "^4.17.23"
103
+ }
104
+ },
105
+ "depsUpdated": 2,
106
+ "originalPackage": "bookshelf",
107
+ "originalVersion": "1.2.0",
108
+ "processedAt": "2026-03-17T18:41:05.185Z",
109
+ "smokeTest": "passed"
110
+ }
111
+ }