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.
Files changed (60) hide show
  1. package/Readme.md +25 -61
  2. package/lib/application/index.js +185 -63
  3. package/lib/body/extract.js +15 -4
  4. package/lib/constants/Bounds.js +15 -0
  5. package/lib/constants/Clauses.js +13 -0
  6. package/lib/constants/ResponseFormats.js +2 -2
  7. package/lib/controllers/methods/index.js +7 -0
  8. package/lib/controllers/mixins/index.js +36 -36
  9. package/lib/database/connection.js +6 -0
  10. package/lib/database/migration.js +14 -4
  11. package/lib/facades/methods/index.js +10 -9
  12. package/lib/facades/mixins/index.js +67 -13
  13. package/lib/factories/responses/rest.js +25 -13
  14. package/lib/http/{request.js → request/index.js} +53 -75
  15. package/lib/http/request/utils.js +27 -0
  16. package/lib/http/response/headers.js +138 -0
  17. package/lib/http/response/index.js +248 -0
  18. package/lib/http/response/utils.js +38 -0
  19. package/lib/middlewares/SearchParams/index.js +25 -0
  20. package/lib/middlewares/cookies/index.js +44 -0
  21. package/lib/middlewares/etag/index.js +32 -15
  22. package/lib/middlewares/formidable/index.js +30 -25
  23. package/lib/middlewares/ql/sequelize/index.js +11 -2
  24. package/lib/middlewares/ql/sequelize/interpreter/QueryLexer.js +10 -2
  25. package/lib/middlewares/render/index.js +62 -0
  26. package/lib/models/associate.js +25 -1
  27. package/lib/models/define.js +19 -15
  28. package/lib/models/mixins.js +7 -0
  29. package/lib/query/traverse.js +97 -63
  30. package/lib/router/handlers.util.js +1 -0
  31. package/lib/router/index.js +193 -98
  32. package/lib/router/markers.js +7 -0
  33. package/lib/router/route.js +5 -0
  34. package/lib/router/routes.util.js +12 -14
  35. package/lib/router/utils.js +7 -0
  36. package/lib/stacks/MarkersStack.js +41 -3
  37. package/lib/stacks/MiddlewaresStack.js +200 -0
  38. package/lib/structures/Enum.js +46 -0
  39. package/lib/structures/Filter.js +157 -0
  40. package/lib/structures/Params.js +55 -0
  41. package/lib/tools/sql.tool.js +7 -0
  42. package/lib/utils/objects.util.js +31 -24
  43. package/lib/utils/sanitizations.util.js +10 -4
  44. package/lib/validators/arguments.js +68 -0
  45. package/lib/validators/dates.js +7 -0
  46. package/lib/validators/numbers.js +7 -0
  47. package/package.json +11 -8
  48. package/tests/index.test.js +4 -4
  49. package/tests/nql.test.js +18 -1
  50. package/lib/database/utils.js +0 -19
  51. package/lib/enums/Enum.js +0 -16
  52. package/lib/filters/Filter.js +0 -109
  53. package/lib/http/response.js +0 -1074
  54. package/lib/http/utils.js +0 -254
  55. package/lib/params/Params.js +0 -37
  56. package/lib/policies/Role.js +0 -77
  57. package/lib/policies/RoleExtracting.js +0 -97
  58. package/lib/services/includes.service.js +0 -79
  59. package/lib/services/jwt.service.js +0 -147
  60. package/lib/stacks/MiddlewareStack.js +0 -159
@@ -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
- const sequelize = model.sequelize;
17
- const fieldsAvailable = Object.keys(model.tableAttributes);
18
- const includesAvailable = model.getIncludesList();
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 === RootModelName.plural.toLowerCase();
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
- model.associations[countTarget] === undefined
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
- } = model.associations[countTarget];
122
- rawSQL += `${ countTarget } where ${ countTarget }.${ foreignKey }=${ RootModelName.singular }.${ sourceKey })`;
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 (value === -1)
168
+ if (_value === -1)
148
169
  continue;
149
170
 
150
- newQuery.limit = value;
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 (value === 0)
178
+ if (_value === 0)
156
179
  continue;
157
180
 
158
- newQuery.offset = value;
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 Clause:
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
- // If requested includes are not available:
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
- const includeIndex = leftIncludes.indexOf(includeName);
242
- if (includeIndex === -1) {
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, model, filter, newQuery)
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=null, resultQuery) {
272
- // If no Filter:
273
- if (filter === null) {
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
- // If no such association:
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
- _addAssociationQuery(associationQuery, includeName, resultQuery);
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
- // If include was not requested:
305
- const include = includes.find(({ model }) => model === includeName);
306
- if (!include)
307
- continue;
310
+ // If include was not requested:
311
+ const include = includes.find(({ model }) => model === includeName);
312
+ if (!include)
313
+ continue;
308
314
 
309
- const includeModel = association.target;
310
- // Build query for this include.
311
- const associationQuery = traverse(include, filter.includes[includeName], includeModel);
315
+ const includeModel = association.target;
316
+ // Build query for this include.
317
+ const associationQuery = traverse(include, filter.includes[includeName], includeModel);
312
318
 
313
- _addAssociationQuery(associationQuery, includeName, resultQuery);
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
+ }
@@ -2,6 +2,7 @@
2
2
  * /nodester
3
3
  * MIT Licensed
4
4
  */
5
+
5
6
  'use strict';
6
7
 
7
8
  const { typeOf } = require('../utils/types.util');