nodester 0.1.5 → 0.2.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 +16 -55
- package/lib/application/index.js +174 -63
- package/lib/body/extract.js +15 -4
- package/lib/constants/Bounds.js +15 -0
- package/lib/constants/Clauses.js +13 -0
- package/lib/constants/ResponseFormats.js +2 -2
- package/lib/controllers/methods/index.js +7 -0
- package/lib/controllers/mixins/index.js +36 -36
- package/lib/database/connection.js +6 -0
- package/lib/database/migration.js +14 -4
- package/lib/facades/methods/index.js +10 -9
- package/lib/facades/mixins/index.js +67 -13
- package/lib/factories/responses/rest.js +25 -13
- package/lib/http/{request.js → request/index.js} +53 -75
- package/lib/http/request/utils.js +27 -0
- package/lib/http/response/headers.js +138 -0
- package/lib/http/response/index.js +248 -0
- package/lib/http/response/utils.js +38 -0
- package/lib/middlewares/SearchParams/index.js +25 -0
- package/lib/middlewares/cookies/index.js +44 -0
- package/lib/middlewares/etag/index.js +32 -15
- package/lib/middlewares/formidable/index.js +30 -25
- package/lib/middlewares/ql/sequelize/index.js +11 -2
- package/lib/middlewares/ql/sequelize/interpreter/QueryLexer.js +2 -1
- package/lib/middlewares/render/index.js +62 -0
- package/lib/models/associate.js +25 -1
- package/lib/models/define.js +19 -15
- package/lib/models/mixins.js +7 -0
- package/lib/query/traverse.js +96 -63
- package/lib/router/handlers.util.js +1 -0
- package/lib/router/index.js +193 -98
- package/lib/router/markers.js +7 -0
- package/lib/router/route.js +5 -0
- package/lib/router/routes.util.js +12 -14
- package/lib/router/utils.js +7 -0
- package/lib/stacks/MarkersStack.js +41 -3
- package/lib/stacks/MiddlewaresStack.js +200 -0
- package/lib/structures/Enum.js +46 -0
- package/lib/structures/Filter.js +156 -0
- package/lib/structures/Params.js +55 -0
- package/lib/tools/sql.tool.js +7 -0
- package/lib/utils/objects.util.js +31 -24
- package/lib/utils/sanitizations.util.js +10 -4
- package/lib/validators/arguments.js +68 -0
- package/lib/validators/dates.js +7 -0
- package/lib/validators/numbers.js +7 -0
- package/package.json +11 -8
- package/lib/database/utils.js +0 -19
- package/lib/enums/Enum.js +0 -16
- package/lib/filters/Filter.js +0 -109
- package/lib/http/response.js +0 -1074
- package/lib/http/utils.js +0 -254
- package/lib/params/Params.js +0 -37
- package/lib/policies/Role.js +0 -77
- package/lib/policies/RoleExtracting.js +0 -97
- package/lib/services/includes.service.js +0 -79
- package/lib/services/jwt.service.js +0 -147
- package/lib/stacks/MiddlewareStack.js +0 -159
package/lib/query/traverse.js
CHANGED
|
@@ -2,20 +2,40 @@
|
|
|
2
2
|
* /nodester
|
|
3
3
|
* MIT Licensed
|
|
4
4
|
*/
|
|
5
|
+
|
|
5
6
|
'use strict';
|
|
6
7
|
|
|
8
|
+
const BOUNDS = require('../constants/Bounds');
|
|
9
|
+
|
|
7
10
|
const { Op } = require('sequelize');
|
|
8
11
|
const NQueryError = require('../factories/errors/NodesterQueryError');
|
|
9
12
|
const httpCodes = require('nodester/http/codes');
|
|
10
13
|
|
|
14
|
+
const { ensure } = require('../validators/arguments');
|
|
15
|
+
|
|
11
16
|
|
|
12
17
|
module.exports = traverse;
|
|
13
18
|
|
|
14
|
-
function traverse(queryNode, filter=null, model) {
|
|
19
|
+
function traverse(queryNode, filter=null, model=null) {
|
|
20
|
+
const _model = model ?? filter.model;
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
ensure(queryNode, 'object,required', 'queryNode');
|
|
24
|
+
ensure(filter, 'object,required', 'filter');
|
|
25
|
+
if (!_model) {
|
|
26
|
+
const err = new TypeError(`'model' must be provided either in 'filter.model' or as a third argument.`);
|
|
27
|
+
throw err;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
catch(error) {
|
|
31
|
+
Error.captureStackTrace(error, traverse);
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
15
34
|
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
const
|
|
35
|
+
const rootModelName = _model.options.name;
|
|
36
|
+
const rootModelAssociations = _model.associations;
|
|
37
|
+
const { sequelize } = _model;
|
|
38
|
+
const fieldsAvailable = Object.keys(_model.tableAttributes);
|
|
19
39
|
|
|
20
40
|
const newQuery = {
|
|
21
41
|
attributes: [],
|
|
@@ -87,10 +107,9 @@ function traverse(queryNode, filter=null, model) {
|
|
|
87
107
|
const countParams = fnParams.args;
|
|
88
108
|
|
|
89
109
|
const [ countTarget ] = countParams;
|
|
90
|
-
const RootModelName = model.options.name;
|
|
91
110
|
// Count can be requested for this model,
|
|
92
111
|
// or for any of the available uncludes.
|
|
93
|
-
const isForRootModel = countTarget ===
|
|
112
|
+
const isForRootModel = countTarget === rootModelName.plural.toLowerCase();
|
|
94
113
|
|
|
95
114
|
// Compile request:
|
|
96
115
|
// Example:
|
|
@@ -108,7 +127,7 @@ function traverse(queryNode, filter=null, model) {
|
|
|
108
127
|
||
|
|
109
128
|
!filter?.includes[countTarget]
|
|
110
129
|
||
|
|
111
|
-
|
|
130
|
+
rootModelAssociations[countTarget] === undefined
|
|
112
131
|
) {
|
|
113
132
|
const err = new NQueryError(`Count for '${ countTarget }' is not available.`);
|
|
114
133
|
err.status = httpCodes.NOT_ACCEPTABLE;
|
|
@@ -118,8 +137,8 @@ function traverse(queryNode, filter=null, model) {
|
|
|
118
137
|
const {
|
|
119
138
|
foreignKey,
|
|
120
139
|
sourceKey
|
|
121
|
-
} =
|
|
122
|
-
rawSQL += `${ countTarget } where ${ countTarget }.${ foreignKey }=${
|
|
140
|
+
} = rootModelAssociations[countTarget];
|
|
141
|
+
rawSQL += `${ countTarget } where ${ countTarget }.${ foreignKey }=${ rootModelName.singular }.${ sourceKey })`;
|
|
123
142
|
countAttribute = `${ countTarget }_count`;
|
|
124
143
|
}
|
|
125
144
|
|
|
@@ -142,22 +161,26 @@ function traverse(queryNode, filter=null, model) {
|
|
|
142
161
|
}
|
|
143
162
|
|
|
144
163
|
switch(clauseName) {
|
|
145
|
-
case 'limit':
|
|
164
|
+
case 'limit': {
|
|
165
|
+
const _value = _setValueWithBounds(value, 'number', filter.bounds.clauses.limit);
|
|
166
|
+
|
|
146
167
|
// Do not set if -1:
|
|
147
|
-
if (
|
|
168
|
+
if (_value === -1)
|
|
148
169
|
continue;
|
|
149
170
|
|
|
150
|
-
newQuery.limit =
|
|
171
|
+
newQuery.limit = _value;
|
|
151
172
|
continue;
|
|
173
|
+
}
|
|
174
|
+
case 'skip': {
|
|
175
|
+
const _value = _setValueWithBounds(value, 'number', filter.bounds.clauses.skip);
|
|
152
176
|
|
|
153
|
-
case 'skip':
|
|
154
177
|
// Do not set if 0:
|
|
155
|
-
if (
|
|
178
|
+
if (_value === 0)
|
|
156
179
|
continue;
|
|
157
180
|
|
|
158
|
-
newQuery.offset =
|
|
181
|
+
newQuery.offset = _value;
|
|
159
182
|
continue;
|
|
160
|
-
|
|
183
|
+
}
|
|
161
184
|
case 'order':
|
|
162
185
|
order.order = value;
|
|
163
186
|
continue;
|
|
@@ -171,7 +194,7 @@ function traverse(queryNode, filter=null, model) {
|
|
|
171
194
|
}
|
|
172
195
|
}
|
|
173
196
|
|
|
174
|
-
// "statics" override or set any query
|
|
197
|
+
// "statics" override or set any query in clauses:
|
|
175
198
|
if (filter !== null) {
|
|
176
199
|
const staticClausesEntries = Object.entries(filter.statics.clauses);
|
|
177
200
|
|
|
@@ -196,6 +219,11 @@ function traverse(queryNode, filter=null, model) {
|
|
|
196
219
|
}
|
|
197
220
|
}
|
|
198
221
|
}
|
|
222
|
+
|
|
223
|
+
// Check for undefined clauses:
|
|
224
|
+
if (newQuery.limit === undefined && typeof filter.bounds.clauses.limit === 'object') {
|
|
225
|
+
newQuery.limit = filter.bounds.clauses.limit.max ?? BOUNDS.limit.max;
|
|
226
|
+
}
|
|
199
227
|
// Clauses\
|
|
200
228
|
|
|
201
229
|
|
|
@@ -234,21 +262,17 @@ function traverse(queryNode, filter=null, model) {
|
|
|
234
262
|
|
|
235
263
|
// Includes:
|
|
236
264
|
// If requested includes are not available:
|
|
237
|
-
const leftIncludes = includesAvailable.map(i => i.association);
|
|
238
265
|
for (let include of includes) {
|
|
239
266
|
const includeName = include.model;
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
if (includeIndex === -1) {
|
|
243
|
-
const err = new TypeError(`No include named '${ includeName }'`);
|
|
267
|
+
if (rootModelAssociations[includeName] === undefined) {
|
|
268
|
+
const err = new NQueryError(`No include named '${ includeName }'.`);
|
|
244
269
|
err.status = httpCodes.NOT_ACCEPTABLE;
|
|
270
|
+
Error.captureStackTrace(err, traverse);
|
|
245
271
|
throw err;
|
|
246
272
|
}
|
|
247
|
-
|
|
248
|
-
leftIncludes.splice(includeIndex, 1);
|
|
249
273
|
}
|
|
250
274
|
|
|
251
|
-
_traverseIncludes(includes,
|
|
275
|
+
_traverseIncludes(includes, _model, filter, newQuery)
|
|
252
276
|
// Includes\
|
|
253
277
|
|
|
254
278
|
|
|
@@ -268,50 +292,30 @@ function traverse(queryNode, filter=null, model) {
|
|
|
268
292
|
}
|
|
269
293
|
|
|
270
294
|
|
|
271
|
-
function _traverseIncludes(includes, model, filter
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
for (let include of includes) {
|
|
275
|
-
const includeName = include.model;
|
|
276
|
-
const association = model.associations[includeName];
|
|
295
|
+
function _traverseIncludes(includes, model, filter, resultQuery) {
|
|
296
|
+
const filterIncludesEntries = Object.entries(filter.includes);
|
|
297
|
+
for (let [ includeName, includeFilter ] of filterIncludesEntries) {
|
|
277
298
|
|
|
278
|
-
|
|
279
|
-
if (!association) {
|
|
280
|
-
const err = new TypeError(`No include '${ includeName }'`);
|
|
281
|
-
err.status = httpCodes.NOT_ACCEPTABLE;
|
|
282
|
-
throw err;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
const includeModel = association.target;
|
|
286
|
-
// Build query for this include.
|
|
287
|
-
const associationQuery = traverse(include, null, includeModel);
|
|
299
|
+
const association = model.associations[includeName];
|
|
288
300
|
|
|
289
|
-
|
|
301
|
+
// If no such association:
|
|
302
|
+
if (!association) {
|
|
303
|
+
const err = new NQueryError(`No include named '${ includeName }'.`);
|
|
304
|
+
err.status = httpCodes.NOT_ACCEPTABLE;
|
|
305
|
+
Error.captureStackTrace(err, _traverseIncludes);
|
|
306
|
+
throw err;
|
|
290
307
|
}
|
|
291
|
-
}
|
|
292
|
-
// Filter is present:
|
|
293
|
-
else {
|
|
294
|
-
const filterIncludeEntries = Object.entries(filter.includes);
|
|
295
|
-
for (let [includeName, includeFilter] of filterIncludeEntries) {
|
|
296
|
-
const association = model.associations[includeName];
|
|
297
|
-
// If no such association:
|
|
298
|
-
if (!association) {
|
|
299
|
-
const err = new TypeError(`No include ${ includeName }`);
|
|
300
|
-
err.status = httpCodes.NOT_ACCEPTABLE;
|
|
301
|
-
throw err;
|
|
302
|
-
}
|
|
303
308
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
309
|
+
// If include was not requested:
|
|
310
|
+
const include = includes.find(({ model }) => model === includeName);
|
|
311
|
+
if (!include)
|
|
312
|
+
continue;
|
|
308
313
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
314
|
+
const includeModel = association.target;
|
|
315
|
+
// Build query for this include.
|
|
316
|
+
const associationQuery = traverse(include, filter.includes[includeName], includeModel);
|
|
312
317
|
|
|
313
|
-
|
|
314
|
-
}
|
|
318
|
+
_addAssociationQuery(associationQuery, includeName, resultQuery);
|
|
315
319
|
}
|
|
316
320
|
}
|
|
317
321
|
|
|
@@ -387,3 +391,32 @@ function _parseValue(value, attribute) {
|
|
|
387
391
|
|
|
388
392
|
return value;
|
|
389
393
|
}
|
|
394
|
+
|
|
395
|
+
function _setValueWithBounds(value, type, bounds) {
|
|
396
|
+
if (typeof bounds === 'object') {
|
|
397
|
+
|
|
398
|
+
switch(type) {
|
|
399
|
+
case 'number': {
|
|
400
|
+
let _value = value;
|
|
401
|
+
|
|
402
|
+
const {
|
|
403
|
+
min,
|
|
404
|
+
max
|
|
405
|
+
} = bounds;
|
|
406
|
+
|
|
407
|
+
const _min = isNaN(min) ? 1 : min;
|
|
408
|
+
_value = _value < _min ? _min : _value;
|
|
409
|
+
|
|
410
|
+
const _max = isNaN(max) ? 1 : max;
|
|
411
|
+
_value = _value > _max ? _max : _value;
|
|
412
|
+
|
|
413
|
+
return _value;
|
|
414
|
+
}
|
|
415
|
+
default:
|
|
416
|
+
break;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// If bounds were not set, just use original value.
|
|
421
|
+
return value;
|
|
422
|
+
}
|