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.
Files changed (58) hide show
  1. package/Readme.md +16 -55
  2. package/lib/application/index.js +174 -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 +2 -1
  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 +96 -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 +156 -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/lib/database/utils.js +0 -19
  49. package/lib/enums/Enum.js +0 -16
  50. package/lib/filters/Filter.js +0 -109
  51. package/lib/http/response.js +0 -1074
  52. package/lib/http/utils.js +0 -254
  53. package/lib/params/Params.js +0 -37
  54. package/lib/policies/Role.js +0 -77
  55. package/lib/policies/RoleExtracting.js +0 -97
  56. package/lib/services/includes.service.js +0 -79
  57. package/lib/services/jwt.service.js +0 -147
  58. 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;
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 sequelize = model.sequelize;
17
- const fieldsAvailable = Object.keys(model.tableAttributes);
18
- const includesAvailable = model.getIncludesList();
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
 
@@ -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
- const includeIndex = leftIncludes.indexOf(includeName);
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, model, filter, newQuery)
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=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];
295
+ function _traverseIncludes(includes, model, filter, resultQuery) {
296
+ const filterIncludesEntries = Object.entries(filter.includes);
297
+ for (let [ includeName, includeFilter ] of filterIncludesEntries) {
277
298
 
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);
299
+ const association = model.associations[includeName];
288
300
 
289
- _addAssociationQuery(associationQuery, includeName, resultQuery);
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
- // If include was not requested:
305
- const include = includes.find(({ model }) => model === includeName);
306
- if (!include)
307
- continue;
309
+ // If include was not requested:
310
+ const include = includes.find(({ model }) => model === includeName);
311
+ if (!include)
312
+ continue;
308
313
 
309
- const includeModel = association.target;
310
- // Build query for this include.
311
- const associationQuery = traverse(include, filter.includes[includeName], includeModel);
314
+ const includeModel = association.target;
315
+ // Build query for this include.
316
+ const associationQuery = traverse(include, filter.includes[includeName], includeModel);
312
317
 
313
- _addAssociationQuery(associationQuery, includeName, resultQuery);
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
+ }
@@ -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');