nodester 0.2.5 → 0.2.8

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 (64) hide show
  1. package/.eslintrc.js +13 -0
  2. package/Readme.md +12 -4
  3. package/lib/application/index.js +61 -53
  4. package/lib/body/extract.js +10 -10
  5. package/lib/controllers/methods/index.js +29 -14
  6. package/lib/controllers/mixins/index.js +13 -13
  7. package/lib/database/connection.js +78 -17
  8. package/lib/database/migration.js +9 -3
  9. package/lib/errors/CustomError.js +7 -2
  10. package/lib/errors/NodesterError.js +10 -2
  11. package/lib/errors/NodesterQueryError.js +9 -2
  12. package/lib/errors/index.js +2 -2
  13. package/lib/facades/methods/index.js +15 -15
  14. package/lib/facades/mixins/index.js +12 -12
  15. package/lib/factories/responses/html.js +20 -9
  16. package/lib/factories/responses/rest.js +21 -19
  17. package/lib/http/codes/descriptions.js +4 -3
  18. package/lib/http/codes/index.js +3 -2
  19. package/lib/http/codes/symbols.js +3 -2
  20. package/lib/http/request/index.js +20 -250
  21. package/lib/http/request/utils.js +4 -4
  22. package/lib/http/response/headers.js +16 -19
  23. package/lib/http/response/index.js +25 -28
  24. package/lib/http/response/utils.js +7 -6
  25. package/lib/loggers/console.js +3 -4
  26. package/lib/loggers/dev.js +3 -4
  27. package/lib/middlewares/404/index.js +38 -0
  28. package/lib/middlewares/SearchParams/index.js +3 -3
  29. package/lib/middlewares/cookies/index.js +2 -2
  30. package/lib/middlewares/etag/index.js +10 -8
  31. package/lib/middlewares/formidable/index.js +2 -2
  32. package/lib/middlewares/ql/sequelize/index.js +2 -2
  33. package/lib/middlewares/ql/sequelize/interpreter/ModelsTree.js +25 -4
  34. package/lib/middlewares/ql/sequelize/interpreter/QueryLexer.js +30 -18
  35. package/lib/middlewares/render/index.js +3 -3
  36. package/lib/models/associate.js +3 -2
  37. package/lib/models/define.js +37 -18
  38. package/lib/models/mixins.js +9 -9
  39. package/lib/query/traverse.js +40 -32
  40. package/lib/router/handlers.util.js +5 -5
  41. package/lib/router/index.js +84 -70
  42. package/lib/router/markers.js +5 -5
  43. package/lib/router/route.js +18 -19
  44. package/lib/router/routes.util.js +4 -4
  45. package/lib/router/utils.js +2 -2
  46. package/lib/stacks/MarkersStack.js +11 -9
  47. package/lib/stacks/MiddlewaresStack.js +25 -21
  48. package/lib/structures/Enum.js +8 -2
  49. package/lib/structures/Filter.js +31 -29
  50. package/lib/structures/Params.js +3 -3
  51. package/lib/tools/nql.tool.js +10 -2
  52. package/lib/tools/sql.tool.js +19 -2
  53. package/lib/utils/json.util.js +28 -27
  54. package/lib/utils/models.js +3 -2
  55. package/lib/utils/objects.util.js +10 -11
  56. package/lib/utils/path.util.js +9 -3
  57. package/lib/utils/sanitizations.util.js +3 -2
  58. package/lib/utils/types.util.js +11 -4
  59. package/lib/validators/arguments.js +28 -9
  60. package/lib/validators/dates.js +4 -5
  61. package/lib/validators/numbers.js +4 -4
  62. package/package.json +43 -39
  63. package/tests/nql.test.js +20 -7
  64. /package/lib/constants/{Operations.js → Operators.js} +0 -0
@@ -1,5 +1,5 @@
1
- /*!
2
- * /nodester
1
+ /**
2
+ * nodester
3
3
  * MIT Licensed
4
4
  */
5
5
 
@@ -1,5 +1,5 @@
1
- /*!
2
- * /nodester
1
+ /**
2
+ * nodester
3
3
  * MIT Licensed
4
4
  */
5
5
 
@@ -14,29 +14,31 @@ const fs = require('fs');
14
14
  const getFileStats = promisify(fs.stat);
15
15
 
16
16
 
17
+ module.exports = initETagMiddleware;
18
+
17
19
  /**
18
20
  * Initialize `etag` middleware.
19
21
  *
20
- * @param {Object} options
21
- * @param {Boolean} options.weak
22
+ * @param {Object} [options]
23
+ * @param {boolean} options.weak
22
24
  *
23
25
  * @return {Function}
24
26
  *
25
- * @api public
27
+ * @access public
26
28
  */
27
- module.exports = function initETagMiddleware(options) {
29
+ function initETagMiddleware(options) {
28
30
  const context = {
29
31
  options
30
32
  }
31
33
  return handle.bind(context);
32
34
  }
33
35
 
34
- /*
36
+ /**
35
37
  * Add ETag header field.
36
38
  */
37
39
  function handle(req, res, next) {
38
40
  // console.log('e', req.headers);
39
- next();
41
+ return next();
40
42
  }
41
43
 
42
44
  async function _getResponseEntity(res) {
@@ -1,5 +1,5 @@
1
- /*!
2
- * /nodester
1
+ /**
2
+ * nodester
3
3
  * MIT Licensed
4
4
  */
5
5
 
@@ -1,5 +1,5 @@
1
- /*!
2
- * /nodester
1
+ /**
2
+ * nodester
3
3
  * MIT Licensed
4
4
  */
5
5
 
@@ -1,5 +1,5 @@
1
- /*!
2
- * /nodester
1
+ /**
2
+ * nodester
3
3
  * MIT Licensed
4
4
  */
5
5
 
@@ -8,6 +8,11 @@
8
8
  const debug = require('debug')('nodester:interpreter:ModelsTree');
9
9
 
10
10
 
11
+ /**
12
+ * @class
13
+ *
14
+ * @access public
15
+ */
11
16
  class ModelsTreeNode {
12
17
  constructor(model, parent=null, opts={}) {
13
18
  this.model = model;
@@ -17,7 +22,7 @@ class ModelsTreeNode {
17
22
  this.fn = null;
18
23
 
19
24
  // for override:
20
- this.fields = [];
25
+ this._attributes = [];
21
26
  this._where = {};
22
27
  this._functions = [];
23
28
  this.skip = 0;
@@ -28,6 +33,11 @@ class ModelsTreeNode {
28
33
  this.order_by = opts.order_by ?? 'id';
29
34
  }
30
35
 
36
+ // Getters:
37
+ get attributes() {
38
+ return this._attributes;
39
+ }
40
+
31
41
  get where() {
32
42
  return this._where;
33
43
  }
@@ -51,6 +61,12 @@ class ModelsTreeNode {
51
61
  get hasIncludes() {
52
62
  return this.includesCount > 0;
53
63
  }
64
+ // Getters\
65
+
66
+ // Setters:
67
+ set attributes(array) {
68
+ this._attributes = array;
69
+ }
54
70
 
55
71
  resetActiveParam() {
56
72
  this.activeParam = null;
@@ -85,7 +101,7 @@ class ModelsTreeNode {
85
101
  return {
86
102
  model: this.model,
87
103
 
88
- fields: this.fields,
104
+ attributes: this.attributes,
89
105
  functions: this.functions,
90
106
 
91
107
  where: this.where,
@@ -101,6 +117,11 @@ class ModelsTreeNode {
101
117
  }
102
118
  }
103
119
 
120
+ /**
121
+ * @class
122
+ *
123
+ * @access public
124
+ */
104
125
  class ModelsTree {
105
126
  constructor() {
106
127
  this.root = new ModelsTreeNode('root', null);
@@ -1,5 +1,5 @@
1
- /*!
2
- * /nodester
1
+ /**
2
+ * nodester
3
3
  * MIT Licensed
4
4
  */
5
5
 
@@ -12,24 +12,29 @@ const util = require('util');
12
12
  const debug = require('debug')('nodester:interpreter:QueryLexer');
13
13
 
14
14
  const PARAM_TOKENS = new Enum({
15
- FIELDS: Symbol('fields'),
16
- INCLUDES: Symbol('includes'),
15
+ ATTRIBUTES: Symbol('attributes'),
16
+
17
17
  LIMIT: Symbol('limit'),
18
18
  ORDER: Symbol('order'),
19
19
  ORDER_BY: Symbol('order_by'),
20
20
  SKIP: Symbol('skip'),
21
+
22
+ INCLUDES: Symbol('includes'),
21
23
  });
22
24
 
23
25
  const OP_TOKENS = new Enum({
24
26
  AND: 'and',
27
+
25
28
  BETWEEN: 'between',
26
29
  NOT_BETWEEN: 'notBetween',
27
30
  BETWEEN_MARK: '~',
31
+
28
32
  OR: 'or',
29
- OR_MARK: '|',
33
+ OR_SHORT: '|',
30
34
  XOR: 'xor',
35
+
31
36
  NOT: 'not',
32
- NOT_MARK: '!',
37
+ NOT_SHORT: '!',
33
38
 
34
39
  IN: 'in',
35
40
  NOT_IN: 'notIn',
@@ -49,6 +54,14 @@ const FN_TOKENS = new Enum({
49
54
  });
50
55
 
51
56
 
57
+ /**
58
+ * @class
59
+ * @classdef constructs ModelTree based on the querystring
60
+ *
61
+ * @param {string} queryString
62
+ *
63
+ * @access public
64
+ */
52
65
  module.exports = class QueryLexer {
53
66
  constructor(queryString='') {
54
67
  this.tree = new ModelsTree();
@@ -77,7 +90,7 @@ module.exports = class QueryLexer {
77
90
 
78
91
  // Token is a String, accumulated char-by-char.
79
92
  let token = '';
80
- // Value of param ('id=10' OR 'fields=id,text').
93
+ // Value of param ('id=10' OR 'attributes=id,text').
81
94
  let value = [];
82
95
  // Model, that was active before a cursor went up in the tree.
83
96
  let previousActive = null;
@@ -489,13 +502,16 @@ module.exports = class QueryLexer {
489
502
 
490
503
  parseParamFromToken(token) {
491
504
  switch(token) {
505
+ case 'attributes':
506
+ case 'a':
507
+ return PARAM_TOKENS.ATTRIBUTES;
508
+
492
509
  case 'limit':
493
510
  case 'l':
494
511
  return PARAM_TOKENS.LIMIT;
495
512
 
496
513
  case 'skip':
497
514
  case 's':
498
- case 'offset':
499
515
  return PARAM_TOKENS.SKIP;
500
516
 
501
517
  case 'order':
@@ -503,13 +519,9 @@ module.exports = class QueryLexer {
503
519
  return PARAM_TOKENS.ORDER;
504
520
 
505
521
  case 'order_by':
506
- case 'o_by':
522
+ case 'oby':
507
523
  return PARAM_TOKENS.ORDER_BY;
508
524
 
509
- case 'fields':
510
- case 'f':
511
- return PARAM_TOKENS.FIELDS;
512
-
513
525
  case 'includes':
514
526
  case 'in':
515
527
  return PARAM_TOKENS.INCLUDES;
@@ -525,6 +537,11 @@ module.exports = class QueryLexer {
525
537
  debug(`set param`, { param, token, value });
526
538
 
527
539
  switch(param) {
540
+ case PARAM_TOKENS.ATTRIBUTES:
541
+ if (token) value.push(token);
542
+ treeNode.attributes = value;
543
+ break;
544
+
528
545
  case PARAM_TOKENS.LIMIT:
529
546
  treeNode.limit = parseInt(token);
530
547
  break;
@@ -541,11 +558,6 @@ module.exports = class QueryLexer {
541
558
  treeNode.order_by = token;
542
559
  break;
543
560
 
544
- case PARAM_TOKENS.FIELDS:
545
- if (token) value.push(token);
546
- treeNode.fields = value;
547
- break;
548
-
549
561
  case PARAM_TOKENS.INCLUDES:
550
562
  const node = new ModelsTreeNode(token);
551
563
  treeNode.include(node);
@@ -1,5 +1,5 @@
1
- /*!
2
- * /nodester
1
+ /**
2
+ * nodester
3
3
  * MIT Licensed
4
4
  */
5
5
 
@@ -29,7 +29,7 @@ function handle(req, res, next) {
29
29
  * - `filename` filename of the view being rendered
30
30
  *
31
31
  * @alias render
32
- * @api public
32
+ * @access public
33
33
  */
34
34
  function _render(view, options, callback) {
35
35
  const app = this.req.app;
@@ -1,5 +1,5 @@
1
- /*!
2
- * /nodester
1
+ /**
2
+ * nodester
3
3
  * MIT Licensed
4
4
  */
5
5
 
@@ -11,6 +11,7 @@ module.exports = {
11
11
  associateModelsSync: _associateModelsSync
12
12
  }
13
13
 
14
+
14
15
  async function _associateModels(databaseConnection) {
15
16
  try {
16
17
  const models = databaseConnection.models;
@@ -1,5 +1,5 @@
1
- /*!
2
- * /nodester
1
+ /**
2
+ * nodester
3
3
  * MIT Licensed
4
4
  */
5
5
 
@@ -13,13 +13,15 @@ const { DataTypes } = require('sequelize');
13
13
 
14
14
  module.exports = defineModel;
15
15
 
16
- /*
16
+ /**
17
17
  * @param {SequilizeConnection} databaseConnection
18
- * @param {String} modelName
18
+ * @param {string} modelName
19
19
  * @param {Function} definition
20
- * @param {Object} options
21
- * - ... Sequilize model options
22
- * - @param {Boolean} noCRUD
20
+ * @param {Object} [options]
21
+ * ... Sequilize model options
22
+ * @param {Object} [options.nodester]
23
+ * @param {boolean} [options.nodester.noCRUD]
24
+ * @param {string} [options.nodester.output]
23
25
  */
24
26
  function defineModel(
25
27
  databaseConnection,
@@ -46,16 +48,18 @@ function defineModel(
46
48
 
47
49
  // Configs related to nodester:
48
50
  nodester: {
51
+ noCRUD: false,
49
52
  output: 'underscored'
50
53
  },
51
54
 
52
- // Add user-defined options (they can override upper ones).
55
+ // Add user-defined options
56
+ // (they can fully override upper ones).
53
57
  ...options
54
58
  };
55
59
 
56
60
  const model = databaseConnection.define(modelName, definitionObject, _options);
57
61
 
58
- if (options.noCRUD !== true) {
62
+ if (_options.nodester.noCRUD !== true) {
59
63
  // Add:
60
64
  // - createWithIncludes;
61
65
  // - findById;
@@ -67,7 +71,7 @@ function defineModel(
67
71
 
68
72
  // Associations:
69
73
  model.associate = (models) => {};
70
- model.getIncludesList = _getIncludesList.bind(model);
74
+ model.getIncludesTree = _getIncludesTree.bind(model);
71
75
 
72
76
  // Instance methods:
73
77
  model.prototype.toJSON = function() {
@@ -78,8 +82,22 @@ function defineModel(
78
82
  return model;
79
83
  }
80
84
 
81
- /* Association mixins: */
82
- function _getIncludesList(facadeData=null) {
85
+ // Associations mixins:
86
+ /**
87
+ * @example
88
+ * [
89
+ * <include_name_0>: {
90
+ * <subinclude_name_0>: { ... }
91
+ * },
92
+ * <include_name_1>: { ... }
93
+ * ]
94
+ * @param {Object|null} data
95
+ *
96
+ * @return {Array} associationsTree
97
+ *
98
+ * @alias getIncludesTree
99
+ */
100
+ function _getIncludesTree(data=null) {
83
101
  const result = [];
84
102
 
85
103
  const associations = this.associations;
@@ -88,15 +106,16 @@ function _getIncludesList(facadeData=null) {
88
106
  for (const [ associationName, associationDefinition ] of associationEntries) {
89
107
  const formatted = { association: associationName };
90
108
 
91
- if (!!facadeData) {
92
- // If facade data is set, go deeper:
93
- const keys = Object.keys( facadeData );
109
+ if (typeof data === 'object') {
110
+ // If data (for example during create)
111
+ // is set, go deeper:
112
+ const keys = Object.keys( data );
94
113
  if (keys.indexOf(associationName) > 0) {
95
114
  const associationModel = associationDefinition.target;
96
115
 
97
116
  if (Object.entries(associationModel.associations).length > 0) {
98
- const deepData = facadeData[ associationName ];
99
- formatted.include = associationModel.getIncludesList(
117
+ const deepData = data[ associationName ];
118
+ formatted.include = associationModel.getIncludesTree(
100
119
  Array.isArray(deepData) ? deepData[0] : deepData
101
120
  );
102
121
  }
@@ -108,4 +127,4 @@ function _getIncludesList(facadeData=null) {
108
127
 
109
128
  return result;
110
129
  }
111
- /* Association mixins\ */
130
+ // Associations mixins\
@@ -1,13 +1,14 @@
1
- /*!
2
- * /nodester
1
+ /**
2
+ * nodester
3
3
  * MIT Licensed
4
4
  */
5
5
 
6
6
  'use strict';
7
7
 
8
- /*
8
+ /**
9
9
  * CRUD mixins for any model:
10
10
  */
11
+
11
12
  // Utils:
12
13
  const {
13
14
  modelHasAssociations,
@@ -30,8 +31,7 @@ module.exports = {
30
31
  *
31
32
  * @param {Object} modelDefinition
32
33
  *
33
- *
34
- * @api public
34
+ * @access public
35
35
  * @alias implementsCRUD
36
36
  */
37
37
  function _implementsCRUD(modelDefinition) {
@@ -57,7 +57,7 @@ function _implementsCRUD(modelDefinition) {
57
57
  }
58
58
 
59
59
 
60
- /* Main mixinis: */
60
+ /** Main mixinis: */
61
61
  async function _createWithIncludes(
62
62
  data={}
63
63
  ) {
@@ -290,9 +290,9 @@ function _deleteById(
290
290
  };
291
291
  return this.destroy(query);
292
292
  }
293
- /* Main mixinis\ */
293
+ /** Main mixinis\ */
294
294
 
295
- /* Subfunctions: */
295
+ /** Subfunctions: */
296
296
  async function _updateOrCreateOrDelete(
297
297
  modelDefinition,
298
298
  data
@@ -395,4 +395,4 @@ function _unwrapUpdateOrCreateOrDeleteOperationsResults(
395
395
 
396
396
  return result;
397
397
  }
398
- /* Subfunctions\ */
398
+ /** Subfunctions\ */
@@ -1,5 +1,5 @@
1
- /*!
2
- * /nodester
1
+ /**
2
+ * nodester
3
3
  * MIT Licensed
4
4
  */
5
5
 
@@ -16,6 +16,14 @@ const { ensure } = require('nodester/validators/arguments');
16
16
 
17
17
  module.exports = traverse;
18
18
 
19
+ /**
20
+ *
21
+ * @param {ModelsTreeNode} queryNode
22
+ * @param {NodesterFilter} filter
23
+ * @param {Model} model
24
+ *
25
+ * @access public
26
+ */
19
27
  function traverse(queryNode, filter=null, model=null) {
20
28
  const _model = model ?? filter.model;
21
29
 
@@ -35,7 +43,7 @@ function traverse(queryNode, filter=null, model=null) {
35
43
  const rootModelName = _model.options.name;
36
44
  const rootModelAssociations = _model.associations;
37
45
  const { sequelize } = _model;
38
- const fieldsAvailable = Object.keys(_model.tableAttributes);
46
+ const attributesAvailable = Object.keys(_model.tableAttributes);
39
47
 
40
48
  const newQuery = {
41
49
  attributes: [],
@@ -44,60 +52,61 @@ function traverse(queryNode, filter=null, model=null) {
44
52
  };
45
53
 
46
54
  const {
55
+ attributes,
56
+ clauses,
57
+ functions,
47
58
  where,
59
+
48
60
  includes,
49
- fields,
50
- functions,
51
- clauses,
52
61
  } = _disassembleQueryNode(queryNode);
53
62
 
54
63
 
55
- // Fields:
64
+ // Attribute:
56
65
  //
57
66
  // If Filter is not set,
58
- // use every available field:
67
+ // use every available attribute:
59
68
  if (filter === null) {
60
- for (let field of fieldsAvailable) {
61
- // If no query filter or field is requested:
62
- if (fields.length === 0 || fields.indexOf(field) > -1) {
63
- newQuery.attributes.push(field);
69
+ for (let attribute of attributesAvailable) {
70
+ // If no query filter or attribute is requested:
71
+ if (attributes.length === 0 || attributes.indexOf(attribute) > -1) {
72
+ newQuery.attributes.push(attribute);
64
73
  continue;
65
74
  }
66
75
  }
67
76
  }
68
77
  // Filter is present:
69
78
  else {
70
- // If no query fields were set,
79
+ // If no query attributes were set,
71
80
  // use the ones from Filter,
72
- // If query fields were set,
81
+ // If query attributes were set,
73
82
  // put them through Filter:
74
- for (let field of filter.fields) {
75
- if (fieldsAvailable.indexOf(field) === -1) {
76
- const err = new NodesterQueryError(`Field '${ field }' is not present in model.`);
83
+ for (let attribute of filter.attributes) {
84
+ if (attributesAvailable.indexOf(attribute) === -1) {
85
+ const err = new NodesterQueryError(`Field '${ attribute }' is not present in model.`);
77
86
  Error.captureStackTrace(err, traverse);
78
87
  throw err;
79
88
  }
80
89
 
81
- // If field is not in available set:
82
- // if (filter.fields.indexOf(field) === -1) {
90
+ // If attribute is not in available set:
91
+ // if (filter.attributes.indexOf(attribute) === -1) {
83
92
  // continue;
84
93
  // }
85
94
 
86
- // If no query filter or field is requested:
87
- if (fields.length === 0 || fields.indexOf(field) > -1) {
88
- newQuery.attributes.push(field);
95
+ // If no query filter or attribute is requested:
96
+ if (attributes.length === 0 || attributes.indexOf(attribute) > -1) {
97
+ newQuery.attributes.push(attribute);
89
98
  continue;
90
99
  }
91
100
  }
92
101
  }
93
102
 
94
- // At least 1 field is mandatory:
103
+ // At least 1 attribute is mandatory:
95
104
  if (newQuery.attributes.length === 0) {
96
- const err = new NodesterQueryError(`No fields were selected.`);
105
+ const err = new NodesterQueryError(`No attributes were selected.`);
97
106
  Error.captureStackTrace(err, traverse);
98
107
  throw err;
99
108
  }
100
- // Fields\
109
+ // Attribute\
101
110
 
102
111
  // Functions:
103
112
  for (const fnParams of functions) {
@@ -312,7 +321,7 @@ function traverse(queryNode, filter=null, model=null) {
312
321
  * @param {NodesterFilter} filter
313
322
  * @param {Object} resultQuery
314
323
  *
315
- * @api private
324
+ * @access private
316
325
  */
317
326
  function _traverseIncludes(includes, rootModel, filter, resultQuery) {
318
327
  const filterIncludesEntries = Object.entries(filter.includes);
@@ -377,20 +386,19 @@ function _parseWhereEntry(attribute, value, whereHolder, staticAttributes) {
377
386
  function _disassembleQueryNode(queryNode) {
378
387
  // Disassemble current query node:
379
388
  const {
389
+ attributes,
390
+ functions,
380
391
  where,
381
392
  includes,
382
- fields,
383
- functions,
384
393
  ...clauses
385
394
  } = queryNode;
386
- // delete queryNode.model;
387
395
 
388
396
  return {
397
+ attributes: attributes ?? [],
398
+ clauses: clauses ?? [],
399
+ functions: functions ?? [],
389
400
  where: where ?? {},
390
401
  includes: includes ?? [],
391
- fields: fields ?? [],
392
- functions: functions ?? [],
393
- clauses: clauses ?? []
394
402
  };
395
403
  }
396
404
 
@@ -1,11 +1,11 @@
1
- /*!
2
- * /nodester
1
+ /**
2
+ * nodester
3
3
  * MIT Licensed
4
4
  */
5
5
 
6
6
  'use strict';
7
7
 
8
- const { typeOf } = require('../utils/types.util');
8
+ const { typeOf } = require('nodester/utils/types');
9
9
 
10
10
 
11
11
  module.exports = {
@@ -13,12 +13,12 @@ module.exports = {
13
13
  }
14
14
 
15
15
 
16
- /*
16
+ /**
17
17
  * @param {Object} routeHandler
18
18
  * @return {Object} parsedRouteHandler
19
19
  *
20
20
  * @alias parseRouteHandler
21
- * @public
21
+ * @access public
22
22
  */
23
23
  function _parseRouteHandler(routeHandler={}) {
24
24
  if (!routeHandler) {