nodester 0.2.0 → 0.2.2

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 CHANGED
@@ -1,9 +1,12 @@
1
1
  # nodester
2
- > A robust and flexible boilerplate framework that makes iterative development easy.
3
2
 
4
3
  [![NPM version](https://img.shields.io/npm/v/nodester)](https://www.npmjs.com/package/nodester)
5
4
  [![License](https://img.shields.io/npm/l/nodester)](https://www.npmjs.com/package/nodester)
6
5
 
6
+ > **nodester** is a modern and versatile Node.js framework designed to streamline the development of robust and scalable web applications.
7
+
8
+ The main reason of nodester's existence is the [nodester Query Language (NQL)](docs/Queries.md), an extension of standard REST API syntax, it lets you craft complex queries with hierarchical associations.
9
+
7
10
 
8
11
  ## Installation
9
12
 
@@ -18,7 +21,6 @@ npm install -S nodester
18
21
 
19
22
  - [Usage](#usage)
20
23
  - [Documentation](#documentation)
21
- - [Extending App](#extending-application-functionality)
22
24
  - [Philosophy](#philosophy)
23
25
  - [License](#license)
24
26
  - [Copyright](#copyright)
@@ -47,10 +49,11 @@ app.listen(8080, function() {
47
49
  [Core concepts documentation ➡️](docs/CoreConcepts.md)
48
50
 
49
51
 
50
- ### Queries & Querying - Nodester Query Language (NQR)
51
- One of the main power points of nodester is it's query language. It's an extension of a REST API syntaxis for a broader integration with a database SQL. Read more about it in the documentation:
52
+ ### Queries & Querying - Nodester Query Language (NQL)
53
+ The true strength of nodester lies in its query language. Serving as an extension of standard REST API syntax, it brings many aspects of SQL into REST requests, providing developers with a simple yet potent tool for expressive and efficient data querying.
52
54
 
53
- [NQR documentaion ➡️](docs/Queries.md)
55
+ Read more about it in the documentation:
56
+ [NQL documentaion ➡️](docs/Queries.md)
54
57
 
55
58
 
56
59
  ### Database
@@ -70,7 +73,7 @@ The Philosophy of `nodester` is to provide a developer with a tool that can buil
70
73
 
71
74
  ### Goal
72
75
 
73
- The goal of `nodester` is to be a robust and flexible framework that makes development in iteratations easy, and further scale possible.
76
+ The goal of `nodester` is to be a robust and flexible framework that makes development in iteratations easy, while laying the foundation for seamless scalability in the future.
74
77
 
75
78
 
76
79
  ## LICENSE
@@ -30,7 +30,7 @@ const {
30
30
  const { merge } = require('../utils/objects.util');
31
31
 
32
32
  // Arguments validator.
33
- const { ensure } = require('../validators/arguments');
33
+ const { ensure } = require('nodester/validators/arguments');
34
34
 
35
35
  // Console:
36
36
  const consl = require('nodester/loggers/console');
@@ -134,6 +134,17 @@ module.exports = class NodesterApplication extends Emitter {
134
134
  return this._router.isLocked;
135
135
  }
136
136
 
137
+ /**
138
+ * Indicates whether app is awaiting requests.
139
+ *
140
+ * @return {Boolean} isListening
141
+ *
142
+ * @api public
143
+ */
144
+ get isListening() {
145
+ return this._isListening;
146
+ }
147
+
137
148
  // Getters\
138
149
 
139
150
  /*
@@ -24,7 +24,7 @@ const {
24
24
  } = require('../methods');
25
25
 
26
26
  // Arguments validator.
27
- const { ensure } = require('../../validators/arguments');
27
+ const { ensure } = require('nodester/validators/arguments');
28
28
 
29
29
 
30
30
  module.exports = {
@@ -7,6 +7,9 @@
7
7
 
8
8
  const { associateModels } = require('nodester/models/associate');
9
9
 
10
+ // Arguments validator.
11
+ const { ensure } = require('nodester/validators/arguments');
12
+
10
13
 
11
14
  module.exports = {
12
15
  migrate: _migrate
@@ -14,11 +17,7 @@ module.exports = {
14
17
 
15
18
  async function _migrate(databaseConnection, force=false) {
16
19
  try {
17
- // Validation of 'force' parameter.
18
- if (typeof force !== 'boolean') {
19
- const err = new Error('Wrong "force" parameter; must be boolean.');
20
- throw err;
21
- }
20
+ ensure(force, 'boolean', 'force');
22
21
 
23
22
  // Test connection.
24
23
  await databaseConnection.authenticate();
@@ -44,7 +43,7 @@ async function _migrate(databaseConnection, force=false) {
44
43
  return Promise.resolve(output);
45
44
  }
46
45
  catch(error) {
47
- console.error(' Migration failed!');
46
+ console.error('Migration failed!');
48
47
  console.error(error);
49
48
  return Promise.reject(error);
50
49
  }
@@ -0,0 +1,18 @@
1
+ /*!
2
+ * /nodester
3
+ * MIT Licensed
4
+ */
5
+
6
+ 'use strict';
7
+
8
+
9
+ module.exports = class NodesterError extends Error {
10
+ constructor(message, status) {
11
+ super(message);
12
+
13
+ this.name = this.constructor.name;
14
+ this.status = status;
15
+
16
+ Error.captureStackTrace(this, this.constructor);
17
+ }
18
+ }
@@ -8,16 +8,19 @@
8
8
  const {
9
9
  NODESTER_QUERY_ERROR,
10
10
  } = require('nodester/constants/ErrorCodes');
11
+ const {
12
+ NOT_ACCEPTABLE
13
+ } = require('nodester/http/codes');
14
+
15
+ const NodesterError = require('./NodesterError');
11
16
 
12
17
 
13
- module.exports = class NodesterQueryError extends Error {
18
+ module.exports = class NodesterQueryError extends NodesterError {
14
19
  constructor(message) {
15
20
  super(message);
16
21
 
17
- this.name = NODESTER_QUERY_ERROR;
18
- this.status = 422;
22
+ this.status = NOT_ACCEPTABLE;
19
23
 
20
- // Remove constructor info from stack.
21
24
  Error.captureStackTrace(this, this.constructor);
22
25
  }
23
26
  }
@@ -7,10 +7,12 @@
7
7
 
8
8
 
9
9
  const Err = require('./CustomError');
10
+ const NodesterError = require('./NodesterError');
10
11
  const NodesterQueryError = require('./NodesterQueryError');
11
12
 
12
13
 
13
14
  module.exports = {
14
15
  Err,
16
+ NodesterError,
15
17
  NodesterQueryError
16
18
  }
@@ -21,7 +21,8 @@ module.exports = {
21
21
 
22
22
  /*
23
23
  *
24
- * @param {Object} params
24
+ * @param {Object} [params]
25
+ * @param {Object} params.query
25
26
  *
26
27
  * @alias getOne
27
28
  * @api public
@@ -40,6 +41,10 @@ async function _getOne(params) {
40
41
  [this.outputName.singular]: instance,
41
42
  count: 0 + (instance !== null)
42
43
  }
44
+
45
+ // Hook (checkout facades/mixins).
46
+ await this.afterGetOne(instance, params, result);
47
+
43
48
  return Promise.resolve(result);
44
49
  }
45
50
  catch(error) {
@@ -51,7 +56,8 @@ async function _getOne(params) {
51
56
 
52
57
  /*
53
58
  *
54
- * @param {Object} params
59
+ * @param {Object} [params]
60
+ * @param {Object} params.query
55
61
  *
56
62
  * @alias getMany
57
63
  * @api public
@@ -70,6 +76,10 @@ async function _getMany(params) {
70
76
  [this.outputName.plural]: instances,
71
77
  count: instances.length
72
78
  }
79
+
80
+ // Hook (checkout facades/mixins).
81
+ await this.afterGetMany(instances, params, result);
82
+
73
83
  return Promise.resolve(result);
74
84
  }
75
85
  catch(error) {
@@ -81,7 +91,8 @@ async function _getMany(params) {
81
91
 
82
92
  /*
83
93
  *
84
- * @param {Object} params
94
+ * @param {Object} [params]
95
+ * @param {Object} params.data
85
96
  *
86
97
  * @alias createOne
87
98
  * @api public
@@ -103,7 +114,7 @@ async function _createOne(params) {
103
114
  count: 0 + (instance !== null)
104
115
  }
105
116
 
106
- // Call after create.
117
+ // Hook (checkout facades/mixins).
107
118
  await this.afterCreateOne(instance, params, result);
108
119
 
109
120
  return Promise.resolve(result);
@@ -117,7 +128,9 @@ async function _createOne(params) {
117
128
 
118
129
  /*
119
130
  *
120
- * @param {Object} params
131
+ * @param {Object} [params]
132
+ * @param {Object} params.query
133
+ * @param {Object} params.data
121
134
  *
122
135
  * @alias updateOne
123
136
  * @api public
@@ -141,6 +154,10 @@ async function _updateOne(params) {
141
154
  [this.outputName.singular]: instance,
142
155
  count: 0 + (instance !== null)
143
156
  }
157
+
158
+ // Hook (checkout facades/mixins).
159
+ await this.afterUpdateOne(instance, params, result);
160
+
144
161
  return Promise.resolve(result);
145
162
  }
146
163
  catch(error) {
@@ -152,7 +169,8 @@ async function _updateOne(params) {
152
169
 
153
170
  /*
154
171
  *
155
- * @param {Object} params
172
+ * @param {Object} [params]
173
+ * @param {Object} params.query
156
174
  *
157
175
  * @alias deleteOne
158
176
  * @api public
@@ -171,6 +189,10 @@ async function _deleteOne(params) {
171
189
  success: count > 0,
172
190
  count: count
173
191
  };
192
+
193
+ // Hook (checkout facades/mixins).
194
+ await this.afterDeleteOne(null, params, result);
195
+
174
196
  return Promise.resolve(result);
175
197
  }
176
198
  catch(error) {
@@ -18,7 +18,7 @@ const { Sequelize } = require('sequelize');
18
18
  const { lowerCaseFirstLetter } = require('nodester/utils/strings');
19
19
 
20
20
  // Arguments validator.
21
- const { ensure } = require('../../validators/arguments');
21
+ const { ensure } = require('nodester/validators/arguments');
22
22
 
23
23
 
24
24
  module.exports = {
@@ -70,6 +70,7 @@ function _withDefaultCRUD(facade, options={}) {
70
70
  },
71
71
  writable: false
72
72
  });
73
+ // Model info\
73
74
 
74
75
  // Set name of this facade:
75
76
  Object.defineProperty(facade, 'name', {
@@ -138,7 +139,7 @@ function _withDefaultCRUD(facade, options={}) {
138
139
  facade.afterGetMany = async () => {};
139
140
  facade.afterCreateOne = async () => {};
140
141
  facade.afterUpdateOne = async () => {};
141
- facade.afterDeleteOe = async () => {};
142
+ facade.afterDeleteOne = async () => {};
142
143
  }
143
144
 
144
145
  return facade;
@@ -8,8 +8,8 @@
8
8
  /*
9
9
  * REST response factory.
10
10
  */
11
-
12
- const ResponseFormats = require('../../constants/ResponseFormats');
11
+ const Params = require('nodester/params');
12
+ const { NodesterError } = require('nodester/errors');
13
13
 
14
14
 
15
15
  module.exports = {
@@ -26,52 +26,83 @@ module.exports = {
26
26
  * }
27
27
  * Status code is sent in header.
28
28
  *
29
- * If error is not present, error should be null.
29
+ * If error is not present, error must be null.
30
30
  * If error is present, content can be null (But it's not required).
31
31
  *
32
32
  * @param {ServerResponse} res
33
- * @param {Object} options
33
+ * @param {Object} [options]
34
34
  * @param {Object} options.error
35
35
  * @param {Object} options.content (optional)
36
36
  * @param {Int} options.status
37
- * @param {String} options.format
38
37
  *
39
38
  * @alias createGenericResponse
40
39
  * @api public
41
40
  */
42
- function _createGenericResponse(
43
- res,
44
- options = {
45
- status: 200,
46
- content: {},
47
- error: null,
48
- format: ResponseFormats.JSON
49
- }
50
- ) {
41
+ function _createGenericResponse(res, options) {
51
42
  try {
43
+ let {
44
+ status,
45
+ content,
46
+ error
47
+ } = Params(options, {
48
+ status: 200,
49
+ content: {},
50
+ error: null
51
+ });
52
+
52
53
  const data = {
53
54
  content: options?.content ?? null,
54
- error: options?.error ?? null
55
+ error: null,
55
56
  };
56
57
 
57
- switch(options?.format) {
58
- case ResponseFormats.JSON: {
59
- return res.json(data);
58
+ if (!!error) {
59
+ const details = {
60
+ message: error?.message
60
61
  }
61
- case ResponseFormats.XML: {
62
- // TODO: format data into XML.
63
- return res.send(data);
62
+
63
+ switch(error.name) {
64
+ case 'Unauthorized': {
65
+ statusCode = 401;
66
+ break;
67
+ }
68
+ case 'NotFound': {
69
+ statusCode = 404;
70
+ break;
71
+ }
72
+ case 'ValidationError': {
73
+ statusCode = 422;
74
+ break;
75
+ }
76
+ case 'ConflictError': {
77
+ statusCode = 409;
78
+ break;
79
+ }
80
+ case 'SequelizeUniqueConstraintError': {
81
+ statusCode = 409;
82
+ details.errors = error?.errors;
83
+ break;
84
+ }
85
+ default:
86
+ statusCode = status;
87
+
88
+ if (!!error?.errors) {
89
+ details.errors = error?.errors;
90
+ }
91
+ break;
64
92
  }
65
- default: {
66
- const err = new TypeError("No format specified.");
67
- throw err;
93
+
94
+ data.error = {
95
+ details: details,
96
+ code: error.name
68
97
  }
69
98
  }
99
+
100
+ res.status(status);
101
+ return res.json(data);
70
102
  }
71
103
  catch(error) {
72
- const err = new Error(`Could not create generic response: ${error.message}`);
73
- err.name = error?.name;
74
- err.code = error?.code;
104
+ const err = new NodesterError(`Could not create generic response: ${ error.message }`);
105
+ Error.captureStackTrace(err, _createGenericResponse);
75
106
  throw err;
76
107
  }
77
108
  }
@@ -82,8 +113,9 @@ function _createGenericResponse(
82
113
  * Should be called on all successful respones.
83
114
  *
84
115
  * @param {ServerResponse} res
116
+ * @param {Object} [options]
85
117
  * @param {Object} options.content (optional)
86
- * @param {String} options.format
118
+ * @param {Object} options.status (optional)
87
119
  *
88
120
  * @alias createOKResponse
89
121
  * @api public
@@ -92,8 +124,7 @@ function _createOKResponse(res, options={}) {
92
124
 
93
125
  return this.createGenericResponse(res, {
94
126
  ...options,
95
- status: 200,
96
- format: options?.format ?? ResponseFormats.JSON
127
+ status: options?.status ?? 200,
97
128
  });
98
129
  }
99
130
 
@@ -103,11 +134,10 @@ function _createOKResponse(res, options={}) {
103
134
  * Should be called on all failed respones.
104
135
  *
105
136
  * @param {ServerResponse} res
106
- * @param {Object} options
137
+ * @param {Object} [options]
107
138
  * @param {Object} options.error
108
139
  * @param {Object} options.content (optional)
109
140
  * @param {Int} options.status
110
- * @param {String} options.format
111
141
  *
112
142
  * @alias createErrorResponse
113
143
  * @api public
@@ -117,6 +147,5 @@ function _createErrorResponse(res, options) {
117
147
  return this.createGenericResponse(res, {
118
148
  ...options,
119
149
  status: options?.status ?? 500,
120
- format: options?.format ?? ResponseFormats.JSON
121
150
  });
122
151
  }
@@ -79,4 +79,5 @@ module.exports = new Enum({
79
79
  LOOP_DETECTED: 508,
80
80
  NOT_EXTENDED: 510,
81
81
  NETWORK_AUTHENTICATION_REQUIRED: 511,
82
- });
82
+ })
83
+ .withKeyPrefix('HTTP_CODE_');
@@ -5,9 +5,15 @@
5
5
 
6
6
  'use strict';
7
7
 
8
+ const {
9
+ HTTP_CODE_UNPROCESSABLE_ENTITY
10
+ } = require('nodester/http/codes');
11
+
8
12
  const cookie = require('cookie');
9
13
  const cookieSignature = require('cookie-signature');
10
14
 
15
+ const { createErrorResponse } = require('nodester/factories/responses/rest');
16
+
11
17
 
12
18
  module.exports = function initCookiesMiddleware(options={}) {
13
19
  const context = {
@@ -26,19 +32,12 @@ function cookiesHandle(req, res, next) {
26
32
  const cookies = cookie.parse(req.headers.cookie);
27
33
  req.cookies = cookies;
28
34
 
29
- next();
35
+ return next();
30
36
  }
31
37
  catch(error) {
32
- console.error(error);
33
-
34
- const statusCode = error.status || 406;
35
- res.status(statusCode);
36
- res.json({
37
- error: {
38
- message: error.message,
39
- code: statusCode
40
- },
41
- status: statusCode
38
+ return createErrorResponse(res, {
39
+ error: error,
40
+ status: error.status ?? HTTP_CODE_UNPROCESSABLE_ENTITY
42
41
  });
43
42
  }
44
43
  }
@@ -5,8 +5,14 @@
5
5
 
6
6
  'use strict';
7
7
 
8
+ const {
9
+ HTTP_CODE_NOT_ACCEPTABLE
10
+ } = require('nodester/http/codes');
11
+
8
12
  const { formidable } = require('formidable');
9
13
 
14
+ const { createErrorResponse } = require('nodester/factories/responses/rest');
15
+
10
16
 
11
17
  module.exports = function initFormidableMiddleware(formidableOptions={}) {
12
18
  const context = {
@@ -26,17 +32,12 @@ async function formidableHandle(req, res, next) {
26
32
  files
27
33
  };
28
34
 
29
- next();
35
+ return next();
30
36
  }
31
37
  catch(error) {
32
- const statusCode = error.status || 406;
33
- res.status(statusCode);
34
- res.json({
35
- error: {
36
- message: error.message,
37
- code: statusCode
38
- },
39
- status: statusCode
40
- });
38
+ return createErrorResponse(res, {
39
+ error: error,
40
+ status: error.status ?? HTTP_CODE_NOT_ACCEPTABLE
41
+ })
41
42
  }
42
43
  }
@@ -5,8 +5,12 @@
5
5
 
6
6
  'use strict';
7
7
 
8
+ const {
9
+ HTTP_CODE_UNPROCESSABLE_ENTITY
10
+ } = require('nodester/http/codes');
11
+
8
12
  const QueryLexer = require('./interpreter/QueryLexer');
9
- const httpCodes = require('nodester/http/codes');
13
+ const { createErrorResponse } = require('nodester/factories/responses/rest');
10
14
 
11
15
 
12
16
  module.exports = function initNodesterQL() {
@@ -34,10 +38,12 @@ async function nqlHandle(req, res, next) {
34
38
 
35
39
  // Go on!
36
40
  req.nquery = lexer.query;
37
- next();
41
+ return next();
38
42
  }
39
43
  catch(error) {
40
- res.status(error.status ?? httpCodes.UNPROCESSABLE_ENTITY);
41
- res.json({ error: error.toString() });
44
+ return createErrorResponse(res, {
45
+ error: error,
46
+ status: error.status ?? HTTP_CODE_UNPROCESSABLE_ENTITY
47
+ });
42
48
  }
43
49
  }
@@ -2,6 +2,7 @@
2
2
  * /nodester
3
3
  * MIT Licensed
4
4
  */
5
+
5
6
  'use strict';
6
7
 
7
8
  const debug = require('debug')('nodester:interpreter:ModelsTree');
@@ -22,15 +23,27 @@ class ModelsTreeNode {
22
23
  this.skip = 0;
23
24
  this.limit = -1; // No limit
24
25
 
25
- this.includes = opts.includes ?? [];
26
+ this._includes = opts.includes ?? [];
26
27
  this.order = opts.order ?? 'asc';
27
28
  this.order_by = opts.order_by ?? 'id';
28
29
  }
29
30
 
31
+ get where() {
32
+ return this._where;
33
+ }
34
+
35
+ get functions() {
36
+ return this._functions;
37
+ }
38
+
30
39
  get hasParent() {
31
40
  return this.parent !== null;
32
41
  }
33
42
 
43
+ get includes() {
44
+ return this._includes;
45
+ }
46
+
34
47
  get includesCount() {
35
48
  return Object.values(this.includes).length;
36
49
  }
@@ -39,14 +52,6 @@ class ModelsTreeNode {
39
52
  return this.includesCount > 0;
40
53
  }
41
54
 
42
- get where() {
43
- return this._where;
44
- }
45
-
46
- get functions() {
47
- return this._functions;
48
- }
49
-
50
55
  resetActiveParam() {
51
56
  this.activeParam = null;
52
57
  }
@@ -72,7 +77,7 @@ class ModelsTreeNode {
72
77
 
73
78
  include(modelTreeNode) {
74
79
  modelTreeNode.parent = this;
75
- this.includes.push(modelTreeNode);
80
+ this._includes.push(modelTreeNode);
76
81
  return modelTreeNode;
77
82
  }
78
83
 
@@ -222,6 +222,13 @@ module.exports = class QueryLexer {
222
222
  const model = token;
223
223
  tree.use(model) ?? tree.include(model);
224
224
 
225
+ // Last token (model) was included,
226
+ // now jump to root and proceed to collect next token (model).
227
+ tree.node.resetActiveParam();
228
+ tree.upToRoot();
229
+
230
+ tree.node.activeParam = 'includes';
231
+
225
232
  token = '';
226
233
  continue;
227
234
  }
@@ -376,7 +383,7 @@ module.exports = class QueryLexer {
376
383
  const param = this.parseParamFromToken(token);
377
384
 
378
385
  if (isSubQuery === true && param === 'includes') {
379
- const err = new TypeError(`'include' is forbidden inside subquery (position ${ i }). Use: 'model.model1' or 'model.model1+model2'.`);
386
+ const err = new TypeError(`'include' is forbidden inside subquery (position ${ i }). Use: 'model.submodel' or 'model.submodel1+submodel2'.`);
380
387
  throw err;
381
388
  }
382
389
 
@@ -403,7 +410,7 @@ module.exports = class QueryLexer {
403
410
  const err = MissingCharError(i+1, ')');
404
411
  throw err;
405
412
  }
406
-
413
+
407
414
  this.setNodeParam(tree.node, token, value);
408
415
 
409
416
  // If end of subquery:
@@ -8,10 +8,10 @@
8
8
  const BOUNDS = require('../constants/Bounds');
9
9
 
10
10
  const { Op } = require('sequelize');
11
- const NQueryError = require('../factories/errors/NodesterQueryError');
11
+ const { NodesterQueryError } = require('nodester/errors');
12
12
  const httpCodes = require('nodester/http/codes');
13
13
 
14
- const { ensure } = require('../validators/arguments');
14
+ const { ensure } = require('nodester/validators/arguments');
15
15
 
16
16
 
17
17
  module.exports = traverse;
@@ -73,8 +73,8 @@ function traverse(queryNode, filter=null, model=null) {
73
73
  // put them through Filter:
74
74
  for (let field of filter.fields) {
75
75
  if (fieldsAvailable.indexOf(field) === -1) {
76
- const err = new TypeError(`Field '${ field }' is not present in model.`);
77
- err.status = httpCodes.NOT_ACCEPTABLE;
76
+ const err = new NodesterQueryError(`Field '${ field }' is not present in model.`);
77
+ Error.captureStackTrace(err, traverse);
78
78
  throw err;
79
79
  }
80
80
 
@@ -93,8 +93,8 @@ function traverse(queryNode, filter=null, model=null) {
93
93
 
94
94
  // At least 1 field is mandatory:
95
95
  if (newQuery.attributes.length === 0) {
96
- const err = new TypeError(`No fields were selected.`);
97
- err.status = httpCodes.NOT_ACCEPTABLE;
96
+ const err = new NodesterQueryError(`No fields were selected.`);
97
+ Error.captureStackTrace(err, traverse);
98
98
  throw err;
99
99
  }
100
100
  // Fields\
@@ -119,7 +119,7 @@ function traverse(queryNode, filter=null, model=null) {
119
119
  let rawSQL = '(SELECT COUNT(*) FROM ';
120
120
  let countAttribute = '_count';
121
121
 
122
- // If request to count one of includes:
122
+ // If request to count one of the includes:
123
123
  if (!isForRootModel) {
124
124
  // Check if it's available:
125
125
  if (
@@ -129,8 +129,8 @@ function traverse(queryNode, filter=null, model=null) {
129
129
  ||
130
130
  rootModelAssociations[countTarget] === undefined
131
131
  ) {
132
- const err = new NQueryError(`Count for '${ countTarget }' is not available.`);
133
- err.status = httpCodes.NOT_ACCEPTABLE;
132
+ const err = new NodesterQueryError(`Count for '${ countTarget }' is not available.`);
133
+ Error.captureStackTrace(err, traverse);
134
134
  throw err;
135
135
  }
136
136
 
@@ -261,12 +261,12 @@ function traverse(queryNode, filter=null, model=null) {
261
261
 
262
262
 
263
263
  // Includes:
264
- // If requested includes are not available:
264
+ // Validate, if requested includes are available:
265
265
  for (let include of includes) {
266
266
  const includeName = include.model;
267
+
267
268
  if (rootModelAssociations[includeName] === undefined) {
268
- const err = new NQueryError(`No include named '${ includeName }'.`);
269
- err.status = httpCodes.NOT_ACCEPTABLE;
269
+ const err = new NodesterQueryError(`No include named '${ includeName }'`);
270
270
  Error.captureStackTrace(err, traverse);
271
271
  throw err;
272
272
  }
@@ -300,8 +300,7 @@ function _traverseIncludes(includes, model, filter, resultQuery) {
300
300
 
301
301
  // If no such association:
302
302
  if (!association) {
303
- const err = new NQueryError(`No include named '${ includeName }'.`);
304
- err.status = httpCodes.NOT_ACCEPTABLE;
303
+ const err = new NodesterQueryError(`No include named '${ includeName }'`);
305
304
  Error.captureStackTrace(err, _traverseIncludes);
306
305
  throw err;
307
306
  }
@@ -25,7 +25,7 @@ const fs = require('fs');
25
25
  const commonExtensions = require('common-js-file-extensions');
26
26
 
27
27
  // Arguments validator.
28
- const { ensure } = require('../validators/arguments');
28
+ const { ensure } = require('nodester/validators/arguments');
29
29
 
30
30
  // Console:
31
31
  const consl = require('nodester/loggers/console');
@@ -9,7 +9,7 @@
9
9
  const { typeOf } = require('../utils/types.util');
10
10
 
11
11
  // Arguments validator.
12
- const { ensure } = require('../validators/arguments');
12
+ const { ensure } = require('nodester/validators/arguments');
13
13
 
14
14
 
15
15
  module.exports = class NodesterRoute {
@@ -6,7 +6,7 @@
6
6
  'use strict';
7
7
 
8
8
  // Arguments validator.
9
- const { ensure } = require('../validators/arguments');
9
+ const { ensure } = require('nodester/validators/arguments');
10
10
 
11
11
  // Console:
12
12
  const consl = require('nodester/loggers/console');
@@ -8,7 +8,7 @@
8
8
  const finalhandler = require('finalhandler');
9
9
 
10
10
  // Arguments validator.
11
- const { ensure } = require('../validators/arguments');
11
+ const { ensure } = require('nodester/validators/arguments');
12
12
 
13
13
  // Console:
14
14
  const consl = require('nodester/loggers/console');
@@ -175,17 +175,17 @@ module.exports = class MiddlewaresStack {
175
175
  process(req, res, next) {
176
176
  let middlewareOffset = -1;
177
177
 
178
- const _next = (...args) => {
178
+ const _next = async (...args) => {
179
179
  middlewareOffset += 1;
180
180
  const fn = this._middlewares[middlewareOffset];
181
181
 
182
182
  try {
183
183
  if (!fn && !!next) {
184
- // Middleware stack is finished:
184
+ // Middlewares stack is finished:
185
185
  return next.call(null, req, res, next, ...args);
186
186
  }
187
187
  else if (!!fn) {
188
- return fn.call(null, req, res, _next, ...args);
188
+ return await fn.call(null, req, res, _next, ...args);
189
189
  }
190
190
  }
191
191
  catch(error) {
@@ -9,7 +9,7 @@ const BOUNDS = require('../constants/Bounds');
9
9
  const CLAUSES = require('../constants/Clauses');
10
10
 
11
11
  const { isModel } = require('../utils/models');
12
- const { ensure } = require('../validators/arguments');
12
+ const { ensure } = require('nodester/validators/arguments');
13
13
 
14
14
 
15
15
  module.exports = class NodesterFilter {
@@ -97,7 +97,8 @@ module.exports = class NodesterFilter {
97
97
  }
98
98
 
99
99
  // If singular association:
100
- if (['HasMany', 'HasOne'].indexOf(association.associationType) === -1) {
100
+ // Fix of "Only HasMany associations support include.separate"
101
+ if ('HasMany' !== association.associationType) {
101
102
  // Empty bounds.
102
103
  includeFilter.noBounds = true;
103
104
  }
@@ -0,0 +1,45 @@
1
+ /*!
2
+ * /nodester
3
+ * MIT Licensed
4
+ */
5
+
6
+ 'use strict';
7
+
8
+
9
+ module.exports = {
10
+ toAST_ModelsTreeNode: _toAST_ModelsTreeNode
11
+ }
12
+
13
+ function _toAST_ModelsTreeNode(node, spacing=0) {
14
+ let spaces = '';
15
+ for (let i = 0; i < spacing; i++) {
16
+ spaces += ' ';
17
+ }
18
+
19
+ let ast = `${ spaces }[TreeNode]\n`;
20
+
21
+ spaces += ' ';
22
+
23
+ ast += `${ spaces }model: ${ node.model }\n\n`;
24
+
25
+ ast += `${ spaces }fields: [\n${ node.fields.map(f => ` • ${ f },\n`) }`;
26
+ ast += `${ spaces }]\n\n`;
27
+
28
+ ast += `${ spaces }functions: [\n${ node.functions.map(f => ` • ${ f },\n`) }`;
29
+ ast += `${ spaces }]\n\n`;
30
+
31
+ ast += `${ spaces }where: ${ JSON.stringify(node.where) }\n\n`;
32
+
33
+ ['skip','limit','order','order_by'].map(
34
+ c => ast += `${ spaces }${ c }: ${ node[c] }\n\n`
35
+ );
36
+
37
+ ast += `${ spaces }includes: [\n`
38
+ node.includes.map(n => ast += _toAST_ModelsTreeNode(n, spacing + 2));
39
+ ast += `${ spaces }]\n`;
40
+
41
+ spaces.slice(-1);
42
+ ast += `${ spaces }[TreeNode END]\n\n`;
43
+
44
+ return ast;
45
+ }
@@ -6,7 +6,7 @@
6
6
  'use strict';
7
7
 
8
8
  // Arguments validator.
9
- const { ensure } = require('../validators/arguments');
9
+ const { ensure } = require('nodester/validators/arguments');
10
10
 
11
11
 
12
12
  module.exports = {
@@ -17,7 +17,7 @@ module.exports = {
17
17
  * @param {String} rules
18
18
  * @param {String} argumentName
19
19
  *
20
- * @api private
20
+ * @api public
21
21
  * @alias ensure
22
22
  */
23
23
  function _ensure(argument, rules, argumentName) {
@@ -40,7 +40,9 @@ function _ensure(argument, rules, argumentName) {
40
40
 
41
41
  if (rule === 'required') {
42
42
  if (argument === undefined || argument === null) {
43
- throw new Error(`${ name } is required.`);
43
+ const err = new TypeError(`${ name } is required.`);
44
+ Error.captureStackTrace(err, _ensure);
45
+ throw err;
44
46
  }
45
47
 
46
48
  isRequired = true;
@@ -61,7 +63,9 @@ function _ensure(argument, rules, argumentName) {
61
63
  }
62
64
 
63
65
  if (mismatchedTypesCount === types.length && argument !== undefined) {
64
- throw new TypeError(`${ name } must be of type ${ types.join('|') }.`);
66
+ const err = new TypeError(`${ name } must be of type ${ types.join('|') }.`);
67
+ Error.captureStackTrace(err, _ensure);
68
+ throw err;
65
69
  }
66
70
 
67
71
  return true;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nodester",
3
- "version": "0.2.0",
4
- "description": "A boilerplate framework for Node.js",
3
+ "version": "0.2.2",
4
+ "description": "A versatile REST framework for Node.js",
5
5
  "exports": {
6
6
  ".": "./lib/application/index.js",
7
7
 
@@ -18,11 +18,12 @@
18
18
 
19
19
  "./enum": "./lib/structures/Enum.js",
20
20
 
21
+ "./errors": "./lib/errors/index.js",
22
+
21
23
  "./facades/methods": "./lib/facades/methods/index.js",
22
24
  "./facades/mixins": "./lib/facades/mixins/index.js",
23
25
 
24
- "./factories/errors": "./lib/factories/errors/index.js",
25
- "./factories/responses/rest": "./lib/factories/responses/rest/index.js",
26
+ "./factories/responses/rest": "./lib/factories/responses/rest.js",
26
27
 
27
28
  "./filter": "./lib/structures/Filter.js",
28
29
 
@@ -53,10 +54,14 @@
53
54
 
54
55
  "./utils/sql": "./lib/utils/sql.util.js",
55
56
  "./utils/strings": "./lib/utils/strings.util.js",
56
- "./utils/sanitizations": "./lib/utils/sanitizations.util.js"
57
+ "./utils/sanitizations": "./lib/utils/sanitizations.util.js",
58
+
59
+ "./validators/arguments": "./lib/validators/arguments.js"
57
60
  },
58
61
  "directories": {
59
- "doc": "docs"
62
+ "docs": "docs",
63
+ "lib": "lib",
64
+ "tests": "tests"
60
65
  },
61
66
  "source": [
62
67
  "lib"
@@ -67,7 +72,10 @@
67
72
  },
68
73
  "author": "Mark Khramko <markkhramko@gmail.com>",
69
74
  "license": "MIT",
70
- "repository": "MarkKhramko/nodester",
75
+ "repository": {
76
+ "type": "git",
77
+ "url": "git+https://github.com/MarkKhramko/nodester.git"
78
+ },
71
79
  "private": false,
72
80
  "bugs": {
73
81
  "url": "https://github.com/MarkKhramko/nodester/issues"
@@ -0,0 +1,16 @@
1
+ const { ModelsTree } = require('../lib/middlewares/ql/sequelize/interpreter/ModelsTree');
2
+ const { toAST_ModelsTreeNode } = require('../lib/tools/nql.tool');
3
+
4
+ const tree = new ModelsTree();
5
+ tree.node.addWhere({ id: ['1000'] });
6
+ tree.include('comments').use('comments');
7
+ tree.node.order = 'desc';
8
+ tree.up();
9
+ tree.include('users');
10
+ tree.include('likes') && tree.use('likes');
11
+ tree.node.order = 'rand';
12
+ tree.up();
13
+ tree.include('reposts');
14
+
15
+ console.debug(toAST_ModelsTreeNode(tree.root));
16
+
@@ -27,13 +27,13 @@ describe('nodester application', () => {
27
27
  test('Application start', () => {
28
28
  app.listen(PORT, function() {
29
29
  expect(app.port).toBe(PORT);
30
- expect(app.router._middlewares.isLocked).toBe(true);
31
- expect(app.router._middlewares.length).toBe(2);
30
+ expect(app.isLocked).toBe(true);
31
+ expect(app.isListening).toBe(true);
32
+ expect(app.middlewaresStack.length).toBe(4);
32
33
 
33
34
  app.stop();
34
35
 
35
- expect(app.router._middlewares.length).toBe(0);
36
- expect(app.router._middlewares.isLocked).toBe(false);
36
+ expect(app.isLocked).toBe(false);
37
37
  expect(app.isListening).toBe(false);
38
38
  });
39
39
  });
package/tests/nql.test.js CHANGED
@@ -49,6 +49,9 @@ describe('nodester Query Language', () => {
49
49
 
50
50
  // Like simple.
51
51
  'title=like(some_text)',
52
+
53
+ // Subinclude and isolated Horizontal.
54
+ 'in=comments.user,likes',
52
55
  ];
53
56
 
54
57
  it('query "Simple where"', () => {
@@ -262,7 +265,6 @@ describe('nodester Query Language', () => {
262
265
 
263
266
  expect(result).toMatchObject(expected);
264
267
  });
265
-
266
268
 
267
269
  test('Token "Like" simple', () => {
268
270
  const lexer = new QueryLexer( queryStrings[14] );
@@ -274,4 +276,19 @@ describe('nodester Query Language', () => {
274
276
 
275
277
  expect(result).toMatchObject(expected);
276
278
  });
279
+
280
+ it('query "Subinclude and isolated Horizontal"', () => {
281
+ const lexer = new QueryLexer( queryStrings[15] );
282
+ result = lexer.query;
283
+
284
+ const tree = new ModelsTree();
285
+ tree.include('comments').use('comments');
286
+ tree.include('user');
287
+ tree.up();
288
+ tree.include('likes');
289
+ const expected = tree.root.toObject();
290
+
291
+ expect(result).toMatchObject(expected);
292
+ });
293
+
277
294
  });
File without changes