nodester 0.1.5 → 0.2.1
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 +25 -61
- package/lib/application/index.js +185 -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 +10 -2
- 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 +97 -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 +157 -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/tests/index.test.js +4 -4
- package/tests/nql.test.js +18 -1
- 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;
|
|
15
21
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
+
}
|
|
34
|
+
|
|
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
|
|
|
@@ -233,22 +261,19 @@ function traverse(queryNode, filter=null, model) {
|
|
|
233
261
|
|
|
234
262
|
|
|
235
263
|
// Includes:
|
|
236
|
-
//
|
|
237
|
-
const leftIncludes = includesAvailable.map(i => i.association);
|
|
264
|
+
// Validate, if requested includes are available:
|
|
238
265
|
for (let include of includes) {
|
|
239
266
|
const includeName = include.model;
|
|
240
267
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
const err = new TypeError(`No include named '${ includeName }'`);
|
|
268
|
+
if (rootModelAssociations[includeName] === undefined) {
|
|
269
|
+
const err = new NQueryError(`No include named '${ includeName }'`);
|
|
244
270
|
err.status = httpCodes.NOT_ACCEPTABLE;
|
|
271
|
+
Error.captureStackTrace(err, traverse);
|
|
245
272
|
throw err;
|
|
246
273
|
}
|
|
247
|
-
|
|
248
|
-
leftIncludes.splice(includeIndex, 1);
|
|
249
274
|
}
|
|
250
275
|
|
|
251
|
-
_traverseIncludes(includes,
|
|
276
|
+
_traverseIncludes(includes, _model, filter, newQuery)
|
|
252
277
|
// Includes\
|
|
253
278
|
|
|
254
279
|
|
|
@@ -268,50 +293,30 @@ function traverse(queryNode, filter=null, model) {
|
|
|
268
293
|
}
|
|
269
294
|
|
|
270
295
|
|
|
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];
|
|
296
|
+
function _traverseIncludes(includes, model, filter, resultQuery) {
|
|
297
|
+
const filterIncludesEntries = Object.entries(filter.includes);
|
|
298
|
+
for (let [ includeName, includeFilter ] of filterIncludesEntries) {
|
|
277
299
|
|
|
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);
|
|
300
|
+
const association = model.associations[includeName];
|
|
288
301
|
|
|
289
|
-
|
|
302
|
+
// If no such association:
|
|
303
|
+
if (!association) {
|
|
304
|
+
const err = new NQueryError(`No include named '${ includeName }'`);
|
|
305
|
+
err.status = httpCodes.NOT_ACCEPTABLE;
|
|
306
|
+
Error.captureStackTrace(err, _traverseIncludes);
|
|
307
|
+
throw err;
|
|
290
308
|
}
|
|
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
309
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
310
|
+
// If include was not requested:
|
|
311
|
+
const include = includes.find(({ model }) => model === includeName);
|
|
312
|
+
if (!include)
|
|
313
|
+
continue;
|
|
308
314
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
315
|
+
const includeModel = association.target;
|
|
316
|
+
// Build query for this include.
|
|
317
|
+
const associationQuery = traverse(include, filter.includes[includeName], includeModel);
|
|
312
318
|
|
|
313
|
-
|
|
314
|
-
}
|
|
319
|
+
_addAssociationQuery(associationQuery, includeName, resultQuery);
|
|
315
320
|
}
|
|
316
321
|
}
|
|
317
322
|
|
|
@@ -387,3 +392,32 @@ function _parseValue(value, attribute) {
|
|
|
387
392
|
|
|
388
393
|
return value;
|
|
389
394
|
}
|
|
395
|
+
|
|
396
|
+
function _setValueWithBounds(value, type, bounds) {
|
|
397
|
+
if (typeof bounds === 'object') {
|
|
398
|
+
|
|
399
|
+
switch(type) {
|
|
400
|
+
case 'number': {
|
|
401
|
+
let _value = value;
|
|
402
|
+
|
|
403
|
+
const {
|
|
404
|
+
min,
|
|
405
|
+
max
|
|
406
|
+
} = bounds;
|
|
407
|
+
|
|
408
|
+
const _min = isNaN(min) ? 1 : min;
|
|
409
|
+
_value = _value < _min ? _min : _value;
|
|
410
|
+
|
|
411
|
+
const _max = isNaN(max) ? 1 : max;
|
|
412
|
+
_value = _value > _max ? _max : _value;
|
|
413
|
+
|
|
414
|
+
return _value;
|
|
415
|
+
}
|
|
416
|
+
default:
|
|
417
|
+
break;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// If bounds were not set, just use original value.
|
|
422
|
+
return value;
|
|
423
|
+
}
|