nodester 0.0.9 → 0.1.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/Readme.md +14 -1
- package/lib/application/index.js +28 -7
- package/lib/controllers/methods/index.js +34 -10
- package/lib/controllers/mixins/index.js +14 -5
- package/lib/database/connection.js +34 -0
- package/lib/database/migration.js +42 -0
- package/lib/database/utils.js +19 -0
- package/lib/facades/methods/index.js +173 -0
- package/lib/facades/mixins/index.js +111 -0
- package/lib/middlewares/formidable/index.js +37 -0
- package/lib/middlewares/ql/sequelize/interpreter/ModelsTree.js +2 -2
- package/lib/middlewares/ql/sequelize/interpreter/QueryLexer.js +3 -3
- package/lib/models/define.js +49 -1
- package/lib/models/mixins.js +76 -67
- package/lib/params/Params.js +10 -7
- package/lib/queries/Colander.js +84 -0
- package/lib/queries/traverse.js +311 -0
- package/lib/router/handlers.util.js +22 -2
- package/lib/router/index.js +96 -75
- package/lib/router/markers.js +78 -0
- package/lib/router/route.js +4 -4
- package/lib/router/routes.util.js +35 -5
- package/lib/router/utils.js +30 -0
- package/package.json +19 -7
- package/tests/nql.test.js +3 -3
- package/lib/_/n_controllers/Controller.js +0 -474
- package/lib/_/n_controllers/JWTController.js +0 -240
- package/lib/_/n_controllers/ServiceController.js +0 -109
- package/lib/_/n_controllers/WebController.js +0 -75
- package/lib/_facades/Facade.js +0 -388
- package/lib/_facades/FacadeParams.js +0 -11
- package/lib/_facades/ServiceFacade.js +0 -17
- package/lib/_facades/jwt.facade.js +0 -273
- package/lib/models/Extractor.js +0 -320
- package/lib/utils/forms.util.js +0 -22
package/lib/models/define.js
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
const { implementsCRUD } = require('./mixins');
|
|
3
3
|
// ORM.
|
|
4
4
|
const { DataTypes } = require('sequelize');
|
|
5
|
+
// NQL.
|
|
6
|
+
const Colander = require('../queries/Colander');
|
|
5
7
|
|
|
6
8
|
|
|
7
9
|
module.exports = defineModel;
|
|
@@ -41,10 +43,19 @@ function defineModel(
|
|
|
41
43
|
const model = databaseConnection.define(modelName, definitionObject, _options);
|
|
42
44
|
|
|
43
45
|
if (options.noCRUD !== true) {
|
|
44
|
-
// Add
|
|
46
|
+
// Add:
|
|
47
|
+
// - createWithIncludes;
|
|
48
|
+
// - findById;
|
|
49
|
+
// - updateById;
|
|
50
|
+
// - deleteById;
|
|
51
|
+
// - etc.
|
|
45
52
|
implementsCRUD(model);
|
|
46
53
|
}
|
|
47
54
|
|
|
55
|
+
// Association helpers:
|
|
56
|
+
model.associate = (models) => {};
|
|
57
|
+
model.getIncludesList = _getIncludesList.bind(model);
|
|
58
|
+
|
|
48
59
|
// Instance methods:
|
|
49
60
|
model.prototype.toJSON = function() {
|
|
50
61
|
const values = { ...this.get() };
|
|
@@ -54,3 +65,40 @@ function defineModel(
|
|
|
54
65
|
|
|
55
66
|
return model;
|
|
56
67
|
}
|
|
68
|
+
|
|
69
|
+
/* Association mixins: */
|
|
70
|
+
function _getIncludesList(facadeData=null) {
|
|
71
|
+
const result = [];
|
|
72
|
+
|
|
73
|
+
const associations = this.associations;
|
|
74
|
+
const associationEntries = Object.entries(associations);
|
|
75
|
+
|
|
76
|
+
associationEntries.forEach(([
|
|
77
|
+
associationName,
|
|
78
|
+
associationDefinition
|
|
79
|
+
]) => {
|
|
80
|
+
const a = { association: associationName };
|
|
81
|
+
|
|
82
|
+
if (!!facadeData) {
|
|
83
|
+
// If facade data is set, go deeper:
|
|
84
|
+
const keys = Object.keys( facadeData );
|
|
85
|
+
if (keys.indexOf(associationName) > 0) {
|
|
86
|
+
const associationModel = associationDefinition.target;
|
|
87
|
+
|
|
88
|
+
const a = { association: associationName };
|
|
89
|
+
if (Object.entries(associationModel.associations).length > 0) {
|
|
90
|
+
const deepData = facadeData[ associationName ];
|
|
91
|
+
a.include = associationModel.getIncludesList(Array.isArray(deepData) ? deepData[0] : deepData);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
result.push( a );
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
result.push( a );
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
return result;
|
|
103
|
+
}
|
|
104
|
+
/* Association mixins\ */
|
package/lib/models/mixins.js
CHANGED
|
@@ -8,6 +8,10 @@ const {
|
|
|
8
8
|
compileModelAssociationData,
|
|
9
9
|
} = require('../utils/modelAssociations.util');
|
|
10
10
|
|
|
11
|
+
// Nodester query:
|
|
12
|
+
const NQLexer = require('../middlewares/ql/sequelize/interpreter/QueryLexer');
|
|
13
|
+
const traverseNQuery = require('../queries/traverse');
|
|
14
|
+
|
|
11
15
|
|
|
12
16
|
module.exports = {
|
|
13
17
|
implementsCRUD: _implementsCRUD
|
|
@@ -34,17 +38,15 @@ function _implementsCRUD(modelDefinition) {
|
|
|
34
38
|
|
|
35
39
|
// Read:
|
|
36
40
|
modelDefinition.findById = _findById.bind(modelDefinition);
|
|
37
|
-
modelDefinition.
|
|
41
|
+
modelDefinition.findMany = _findMany.bind(modelDefinition);
|
|
38
42
|
|
|
39
43
|
// Update:
|
|
40
44
|
modelDefinition.updateOne = _updateOne.bind(modelDefinition);
|
|
41
45
|
modelDefinition.updateById = _updateById.bind(modelDefinition);
|
|
42
46
|
|
|
43
|
-
// Delete
|
|
47
|
+
// Delete:
|
|
48
|
+
modelDefinition.deleteOne = _deleteOne.bind(modelDefinition);
|
|
44
49
|
modelDefinition.deleteById = _deleteById.bind(modelDefinition);
|
|
45
|
-
|
|
46
|
-
// Associations:
|
|
47
|
-
modelDefinition.getIncludesList = _getIncludesList.bind(modelDefinition);
|
|
48
50
|
}
|
|
49
51
|
|
|
50
52
|
|
|
@@ -114,37 +116,76 @@ async function _createWithIncludes(
|
|
|
114
116
|
|
|
115
117
|
function _findById(
|
|
116
118
|
id=null,
|
|
117
|
-
|
|
118
|
-
paranoid=true
|
|
119
|
+
opts={}
|
|
119
120
|
) {
|
|
120
|
-
const query =
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
121
|
+
const { query } = opts;
|
|
122
|
+
|
|
123
|
+
let _query = {};
|
|
124
|
+
|
|
125
|
+
if (typeof query === 'string') {
|
|
126
|
+
const lexer = new NQLexer(query);
|
|
127
|
+
const nquery = lexer.query;
|
|
128
|
+
_query = traverseNQuery(nquery, null, this);
|
|
129
|
+
_query.where = {
|
|
130
|
+
..._query.where,
|
|
131
|
+
id: id
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
const {
|
|
136
|
+
include,
|
|
137
|
+
paranoid
|
|
138
|
+
} = opts;
|
|
139
|
+
|
|
140
|
+
_query = {
|
|
141
|
+
where: { id },
|
|
142
|
+
include: include,
|
|
143
|
+
paranoid: !!paranoid
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return this.findOne(_query);
|
|
126
148
|
}
|
|
127
149
|
|
|
128
|
-
function
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
150
|
+
function _findMany(opts={}) {
|
|
151
|
+
const { query } = opts;
|
|
152
|
+
|
|
153
|
+
let _query = {};
|
|
154
|
+
|
|
155
|
+
if (typeof query === 'string') {
|
|
156
|
+
const lexer = new NQLexer(query);
|
|
157
|
+
const nquery = lexer.query;
|
|
158
|
+
_query = traverseNQuery(nquery, null, this);
|
|
159
|
+
_query.where = {
|
|
160
|
+
..._query.where,
|
|
161
|
+
id: id
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
const {
|
|
166
|
+
include,
|
|
167
|
+
paranoid
|
|
168
|
+
} = opts;
|
|
169
|
+
|
|
170
|
+
_query = {
|
|
171
|
+
where: { id },
|
|
172
|
+
include: include,
|
|
173
|
+
paranoid: !!paranoid
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
return this.findAll(_query);
|
|
139
179
|
}
|
|
140
180
|
|
|
181
|
+
|
|
141
182
|
async function _updateOne(
|
|
142
183
|
where,
|
|
143
184
|
data,
|
|
144
185
|
include=[]
|
|
145
186
|
) {
|
|
146
187
|
try {
|
|
147
|
-
const instance = await this.
|
|
188
|
+
const instance = await this.findOne({ where, include });
|
|
148
189
|
|
|
149
190
|
if (!instance) {
|
|
150
191
|
const err = new Error(`Model not found`);
|
|
@@ -201,7 +242,7 @@ async function _updateOne(
|
|
|
201
242
|
|
|
202
243
|
// Select this instance again, if includes was set:
|
|
203
244
|
if (include?.length > 0) {
|
|
204
|
-
const updatedInstance = await this.
|
|
245
|
+
const updatedInstance = await this.findOne({ where, include });
|
|
205
246
|
fullInstanceData = updatedInstance.toJSON();
|
|
206
247
|
}
|
|
207
248
|
|
|
@@ -225,6 +266,15 @@ async function _updateById(
|
|
|
225
266
|
);
|
|
226
267
|
}
|
|
227
268
|
|
|
269
|
+
|
|
270
|
+
function _deleteOne(query={}) {
|
|
271
|
+
const _query = {
|
|
272
|
+
...query,
|
|
273
|
+
limit: 1
|
|
274
|
+
}
|
|
275
|
+
return this.destroy(_query);
|
|
276
|
+
}
|
|
277
|
+
|
|
228
278
|
function _deleteById(
|
|
229
279
|
id=null
|
|
230
280
|
) {
|
|
@@ -235,47 +285,6 @@ function _deleteById(
|
|
|
235
285
|
}
|
|
236
286
|
/* Main mixinis\ */
|
|
237
287
|
|
|
238
|
-
/* Association mixins: */
|
|
239
|
-
function _getIncludesList(facadeData=null) {
|
|
240
|
-
// console.log({ facadeData, model: this });
|
|
241
|
-
|
|
242
|
-
const result = [];
|
|
243
|
-
|
|
244
|
-
const associations = this.associations;
|
|
245
|
-
const associationEntries = Object.entries(associations);
|
|
246
|
-
|
|
247
|
-
associationEntries.forEach(([
|
|
248
|
-
associationName,
|
|
249
|
-
associationDefinition
|
|
250
|
-
]) => {
|
|
251
|
-
const a = { association: associationName };
|
|
252
|
-
|
|
253
|
-
if (!!facadeData) {
|
|
254
|
-
// If facade data is set, go deeper:
|
|
255
|
-
const keys = Object.keys( facadeData );
|
|
256
|
-
if (keys.indexOf(associationName) > 0) {
|
|
257
|
-
const associationModel = associationDefinition.target;
|
|
258
|
-
|
|
259
|
-
const a = { association: associationName };
|
|
260
|
-
if (Object.entries(associationModel.associations).length > 0) {
|
|
261
|
-
const deepData = facadeData[ associationName ];
|
|
262
|
-
a.include = associationModel.getIncludesList(Array.isArray(deepData) ? deepData[0] : deepData);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
result.push( a );
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
else {
|
|
269
|
-
result.push( a );
|
|
270
|
-
}
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
// console.log('getIncludesList', result);
|
|
274
|
-
|
|
275
|
-
return result;
|
|
276
|
-
}
|
|
277
|
-
/* Association mixins\ */
|
|
278
|
-
|
|
279
288
|
/* Subfunctions: */
|
|
280
289
|
async function _updateOrCreateOrDelete(
|
|
281
290
|
modelDefinition,
|
package/lib/params/Params.js
CHANGED
|
@@ -10,10 +10,9 @@ module.exports = Params;
|
|
|
10
10
|
* @param {Object} sourceObj
|
|
11
11
|
* @param {Object} defaultValuesList
|
|
12
12
|
*
|
|
13
|
-
* @return {
|
|
13
|
+
* @return {Object} result
|
|
14
14
|
*
|
|
15
15
|
* @api public
|
|
16
|
-
* @alias withDefaultCRUD
|
|
17
16
|
*/
|
|
18
17
|
function Params(
|
|
19
18
|
sourceObj={},
|
|
@@ -23,11 +22,15 @@ function Params(
|
|
|
23
22
|
|
|
24
23
|
const keys = Object.keys(defaultValuesList);
|
|
25
24
|
for (const key of keys) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
25
|
+
|
|
26
|
+
// If value is not set,
|
|
27
|
+
// use default one from 'defaultValuesList':
|
|
28
|
+
if (sourceObj[key] === undefined) {
|
|
29
|
+
result[key] = defaultValuesList[key];
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
result[key] = sourceObj[key];
|
|
31
34
|
}
|
|
32
35
|
|
|
33
36
|
return result;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
const CLAUSES = ['limit', 'skip', 'order', 'order_by'];
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
module.exports = class Colander {
|
|
5
|
+
|
|
6
|
+
/*
|
|
7
|
+
*
|
|
8
|
+
* @param {Object|Model} optsOrModelDefinition
|
|
9
|
+
* - @param {Array} fields
|
|
10
|
+
* - @param {Array} clauses
|
|
11
|
+
* - @param {Object} includes
|
|
12
|
+
* - @param {Object} statics
|
|
13
|
+
* -- @param {Object} attributes
|
|
14
|
+
* -- @param {Object} clauses
|
|
15
|
+
*
|
|
16
|
+
*/
|
|
17
|
+
constructor(optsOrModelDefinition) {
|
|
18
|
+
this._fields = [];
|
|
19
|
+
this._clauses = [];
|
|
20
|
+
this._includes = {};
|
|
21
|
+
|
|
22
|
+
this._statics = {
|
|
23
|
+
attributes: {},
|
|
24
|
+
clauses: {
|
|
25
|
+
limit: 3
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// If model:
|
|
30
|
+
if (!!optsOrModelDefinition.tableName && typeof optsOrModelDefinition._schema === 'object') {
|
|
31
|
+
this._fields = Object.keys(optsOrModelDefinition.tableAttributes);
|
|
32
|
+
this._clauses = CLAUSES;
|
|
33
|
+
}
|
|
34
|
+
// If options:
|
|
35
|
+
else {
|
|
36
|
+
const {
|
|
37
|
+
fields,
|
|
38
|
+
clauses,
|
|
39
|
+
includes,
|
|
40
|
+
statics,
|
|
41
|
+
} = optsOrModelDefinition;
|
|
42
|
+
|
|
43
|
+
if (Array.isArray(fields)) {
|
|
44
|
+
this._fields = fields;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (Array.isArray(clauses)) {
|
|
48
|
+
this._clauses = clauses;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (typeof includes === 'object') {
|
|
52
|
+
this._includes = includes;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (typeof statics === 'object') {
|
|
56
|
+
if (typeof statics.attributes === 'object') {
|
|
57
|
+
this._statics.attributes = statics.attributes;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (typeof statics.clauses === 'object') {
|
|
61
|
+
this._statics.clauses = statics.clauses;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Getters:
|
|
68
|
+
get fields() {
|
|
69
|
+
return this._fields;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
get clauses() {
|
|
73
|
+
return this._clauses;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
get includes() {
|
|
77
|
+
return this._includes;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
get statics() {
|
|
81
|
+
return this._statics;
|
|
82
|
+
}
|
|
83
|
+
// Getters\
|
|
84
|
+
}
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
const { Op } = require('sequelize');
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
module.exports = traverse;
|
|
5
|
+
|
|
6
|
+
function traverse(queryNode, colander=null, model) {
|
|
7
|
+
|
|
8
|
+
const fieldsAvailable = Object.keys(model.tableAttributes);
|
|
9
|
+
const includesAvailable = model.getIncludesList();
|
|
10
|
+
|
|
11
|
+
const newQuery = {
|
|
12
|
+
attributes: [],
|
|
13
|
+
where: {},
|
|
14
|
+
include: []
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const {
|
|
18
|
+
where,
|
|
19
|
+
includes,
|
|
20
|
+
fields,
|
|
21
|
+
clauses,
|
|
22
|
+
} = _disassembleQueryNode(queryNode);
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
// Fields:
|
|
26
|
+
//
|
|
27
|
+
// If Colander is not set,
|
|
28
|
+
// use every available field:
|
|
29
|
+
if (colander === null) {
|
|
30
|
+
for (let field of fieldsAvailable) {
|
|
31
|
+
// If no query filter or field is requested:
|
|
32
|
+
if (fields.length === 0 || fields.indexOf(field) > -1) {
|
|
33
|
+
newQuery.attributes.push(field);
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// Colander is present:
|
|
39
|
+
else {
|
|
40
|
+
// If no query fields were set,
|
|
41
|
+
// use the ones from Colander,
|
|
42
|
+
// If query fields were set,
|
|
43
|
+
// put them through Colander:
|
|
44
|
+
for (let field of colander.fields) {
|
|
45
|
+
if (fieldsAvailable.indexOf(field) === -1) {
|
|
46
|
+
const err = new TypeError(`field ${ field } is not present in model.`);
|
|
47
|
+
throw err;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// If field is not in available set:
|
|
51
|
+
// if (colander.fields.indexOf(field) === -1) {
|
|
52
|
+
// continue;
|
|
53
|
+
// }
|
|
54
|
+
|
|
55
|
+
// If no query filter or field is requested:
|
|
56
|
+
if (fields.length === 0 || fields.indexOf(field) > -1) {
|
|
57
|
+
newQuery.attributes.push(field);
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// At least 1 field is mandatory:
|
|
64
|
+
if (newQuery.attributes.length === 0) {
|
|
65
|
+
const err = new TypeError(`No fields were selected.`);
|
|
66
|
+
throw err;
|
|
67
|
+
}
|
|
68
|
+
// Fields\
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
// Clauses:
|
|
72
|
+
const order = {};
|
|
73
|
+
|
|
74
|
+
const clausesEntries = Object.entries(clauses);
|
|
75
|
+
for (let [clauseName, value] of clausesEntries) {
|
|
76
|
+
// If clause is not available:
|
|
77
|
+
if (colander != null) {
|
|
78
|
+
if (colander.clauses.indexOf(clauseName) === -1)
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
switch(clauseName) {
|
|
83
|
+
case 'limit':
|
|
84
|
+
// Do not set if -1:
|
|
85
|
+
if (value === -1)
|
|
86
|
+
continue;
|
|
87
|
+
|
|
88
|
+
newQuery.limit = value;
|
|
89
|
+
continue;
|
|
90
|
+
case 'skip':
|
|
91
|
+
// Do not set if 0:
|
|
92
|
+
if (value === 0)
|
|
93
|
+
continue;
|
|
94
|
+
|
|
95
|
+
newQuery.offset = value;
|
|
96
|
+
continue;
|
|
97
|
+
case 'order':
|
|
98
|
+
order.order = value;
|
|
99
|
+
continue;
|
|
100
|
+
case 'order_by':
|
|
101
|
+
order.by = value;
|
|
102
|
+
continue;
|
|
103
|
+
default:
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// "statics" override or set any query Clause:
|
|
109
|
+
if (colander !== null) {
|
|
110
|
+
for (let [clauseName, staticClauseValue] of Object.entries(colander.statics.clauses)) {
|
|
111
|
+
switch(clauseName) {
|
|
112
|
+
case 'limit':
|
|
113
|
+
newQuery.limit = staticClauseValue;
|
|
114
|
+
continue;
|
|
115
|
+
case 'skip':
|
|
116
|
+
newQuery.offset = staticClauseValue;
|
|
117
|
+
continue;
|
|
118
|
+
case 'order':
|
|
119
|
+
order.order = staticClauseValue;
|
|
120
|
+
continue;
|
|
121
|
+
case 'order_by':
|
|
122
|
+
order.by = staticClauseValue;
|
|
123
|
+
continue;
|
|
124
|
+
default:
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// Clauses\
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
// Order:
|
|
133
|
+
const sequelize = model.sequelize;
|
|
134
|
+
if ( ['rand', 'random'].indexOf(order.order) > -1) {
|
|
135
|
+
newQuery.order = sequelize.random();
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
const column = sequelize.col( order.by );
|
|
139
|
+
switch (order.order) {
|
|
140
|
+
// MAX/MIN:
|
|
141
|
+
case 'max-asc':
|
|
142
|
+
case 'max':
|
|
143
|
+
case 'min-desc':
|
|
144
|
+
newQuery.order = sequelize.fn('max', column);
|
|
145
|
+
break;
|
|
146
|
+
case 'min':
|
|
147
|
+
case 'min-asc':
|
|
148
|
+
case 'max-desc':
|
|
149
|
+
newQuery.order = [ sequelize.fn('max', column), 'DESC' ];
|
|
150
|
+
break;
|
|
151
|
+
// MAX/MIN\
|
|
152
|
+
|
|
153
|
+
default:
|
|
154
|
+
newQuery.order = [ [order.by, order.order] ];
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
// Order\
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
// Includes:
|
|
162
|
+
// If requested includes are not available:
|
|
163
|
+
const leftIncludes = includesAvailable.map(i => i.association);
|
|
164
|
+
for (let include of includes) {
|
|
165
|
+
const includeName = include.model;
|
|
166
|
+
|
|
167
|
+
const includeIndex = leftIncludes.indexOf(includeName);
|
|
168
|
+
if (includeIndex === -1) {
|
|
169
|
+
const err = new TypeError(`No include named ${ includeName }`);
|
|
170
|
+
throw err;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
leftIncludes.splice(includeIndex, 1);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
_traverseIncludes(includes, model, colander, newQuery)
|
|
177
|
+
// Includes\
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
// Where:
|
|
181
|
+
const whereEntries = Object.entries(where);
|
|
182
|
+
for (let [attribute, value] of whereEntries) {
|
|
183
|
+
_parseWhereEntry(attribute, value, newQuery.where, colander.statics.attributes);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// If "where" was not set:
|
|
187
|
+
if (whereEntries.length === 0) {
|
|
188
|
+
delete newQuery.where;
|
|
189
|
+
}
|
|
190
|
+
// Where\
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
return newQuery;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
function _traverseIncludes(includes, model, colander, resultQuery) {
|
|
198
|
+
// If no Colander:
|
|
199
|
+
if (colander === null) {
|
|
200
|
+
for (let include of includes) {
|
|
201
|
+
const includeName = include.model;
|
|
202
|
+
const association = model.associations[includeName];
|
|
203
|
+
|
|
204
|
+
// If no such association:
|
|
205
|
+
if (!association) {
|
|
206
|
+
const err = new TypeError(`No include ${ includeName }`);
|
|
207
|
+
throw err;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const includeModel = association.target;
|
|
211
|
+
// Build query for this include.
|
|
212
|
+
const associationQuery = traverse(include, null, includeModel);
|
|
213
|
+
|
|
214
|
+
_addAssociationQuery(associationQuery, includeName, resultQuery);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
// Colander is present:
|
|
218
|
+
else {
|
|
219
|
+
const colanderIncludeEntries = Object.entries(colander.includes);
|
|
220
|
+
for (let [includeName, includeColander] of colanderIncludeEntries) {
|
|
221
|
+
const association = model.associations[includeName];
|
|
222
|
+
// If no such association:
|
|
223
|
+
if (!association) {
|
|
224
|
+
const err = new TypeError(`No include ${ includeName }`);
|
|
225
|
+
throw err;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// If include was not requested:
|
|
229
|
+
const include = includes.find(({ model }) => model === includeName);
|
|
230
|
+
if (!include)
|
|
231
|
+
continue;
|
|
232
|
+
|
|
233
|
+
const includeModel = association.target;
|
|
234
|
+
// Build query for this include.
|
|
235
|
+
const associationQuery = traverse(include, colander.includes[includeName], includeModel);
|
|
236
|
+
|
|
237
|
+
_addAssociationQuery(associationQuery, includeName, resultQuery);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
function _addAssociationQuery(associationQuery, includeName, resultQuery) {
|
|
244
|
+
|
|
245
|
+
// Add all association info into query.
|
|
246
|
+
resultQuery.include.push({
|
|
247
|
+
association: includeName,
|
|
248
|
+
...associationQuery
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
function _parseWhereEntry(attribute, value, whereHolder, staticAttributes) {
|
|
254
|
+
let _value = value;
|
|
255
|
+
const static = staticAttributes[attribute];
|
|
256
|
+
|
|
257
|
+
// If attribute is Op (like, or, not, etc.):
|
|
258
|
+
if (attribute in Op) {
|
|
259
|
+
// Parse value:
|
|
260
|
+
_value = _parseValue(_value, attribute);
|
|
261
|
+
|
|
262
|
+
const op = Op[attribute];
|
|
263
|
+
whereHolder[op] = _value;
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Static value overrides any other:
|
|
268
|
+
if (!!static) {
|
|
269
|
+
whereHolder[attribute] = static;
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
whereHolder[attribute] = _parseValue(_value, attribute);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function _disassembleQueryNode(queryNode) {
|
|
277
|
+
// Disassemble current query node:
|
|
278
|
+
const {
|
|
279
|
+
where,
|
|
280
|
+
includes,
|
|
281
|
+
fields,
|
|
282
|
+
...clauses
|
|
283
|
+
} = queryNode;
|
|
284
|
+
// delete queryNode.model;
|
|
285
|
+
|
|
286
|
+
return {
|
|
287
|
+
where: where ?? {},
|
|
288
|
+
includes: includes ?? [],
|
|
289
|
+
fields: fields ?? [],
|
|
290
|
+
clauses: clauses ?? []
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function _parseValue(value, attribute) {
|
|
295
|
+
// If value is Object:
|
|
296
|
+
if (typeof value === 'object' && Array.isArray(value) === false) {
|
|
297
|
+
const [opKey, rawValue] = (Object.entries(value))[0];
|
|
298
|
+
|
|
299
|
+
// If operation is "in":
|
|
300
|
+
if (opKey === 'in') {
|
|
301
|
+
// Unwrap rawValue.
|
|
302
|
+
return rawValue[0][attribute];
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
const op = Op[opKey];
|
|
306
|
+
return { [op]: rawValue };
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return value;
|
|
311
|
+
}
|