nodester 0.2.1 → 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 +1 -1
- package/lib/application/index.js +1 -1
- package/lib/controllers/mixins/index.js +1 -1
- package/lib/database/migration.js +5 -6
- package/lib/errors/NodesterError.js +18 -0
- package/lib/{factories/errors → errors}/NodesterQueryError.js +7 -4
- package/lib/{factories/errors → errors}/index.js +2 -0
- package/lib/facades/methods/index.js +28 -6
- package/lib/facades/mixins/index.js +3 -2
- package/lib/factories/responses/rest.js +62 -33
- package/lib/http/codes/index.js +2 -1
- package/lib/middlewares/cookies/index.js +10 -11
- package/lib/middlewares/formidable/index.js +11 -10
- package/lib/middlewares/ql/sequelize/index.js +10 -4
- package/lib/middlewares/ql/sequelize/interpreter/ModelsTree.js +15 -10
- package/lib/middlewares/ql/sequelize/interpreter/QueryLexer.js +1 -1
- package/lib/query/traverse.js +11 -13
- package/lib/router/index.js +1 -1
- package/lib/router/route.js +1 -1
- package/lib/stacks/MarkersStack.js +1 -1
- package/lib/stacks/MiddlewaresStack.js +4 -4
- package/lib/structures/Filter.js +1 -1
- package/lib/tools/nql.tool.js +45 -0
- package/lib/utils/objects.util.js +1 -1
- package/lib/validators/arguments.js +7 -3
- package/package.json +15 -7
- package/tests/ast.test.js +16 -0
- /package/lib/{factories/errors → errors}/CustomError.js +0 -0
package/Readme.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
> **nodester** is a modern and versatile Node.js framework designed to streamline the development of robust and scalable web applications.
|
|
7
7
|
|
|
8
|
-
|
|
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
9
|
|
|
10
10
|
|
|
11
11
|
## Installation
|
package/lib/application/index.js
CHANGED
|
@@ -30,7 +30,7 @@ const {
|
|
|
30
30
|
const { merge } = require('../utils/objects.util');
|
|
31
31
|
|
|
32
32
|
// Arguments validator.
|
|
33
|
-
const { ensure } = require('
|
|
33
|
+
const { ensure } = require('nodester/validators/arguments');
|
|
34
34
|
|
|
35
35
|
// Console:
|
|
36
36
|
const consl = require('nodester/loggers/console');
|
|
@@ -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
|
-
|
|
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('
|
|
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
|
|
18
|
+
module.exports = class NodesterQueryError extends NodesterError {
|
|
14
19
|
constructor(message) {
|
|
15
20
|
super(message);
|
|
16
21
|
|
|
17
|
-
this.
|
|
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
|
}
|
|
@@ -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
|
-
//
|
|
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('
|
|
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.
|
|
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
|
|
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
|
|
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:
|
|
55
|
+
error: null,
|
|
55
56
|
};
|
|
56
57
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
if (!!error) {
|
|
59
|
+
const details = {
|
|
60
|
+
message: error?.message
|
|
60
61
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
|
73
|
-
err
|
|
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 {
|
|
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
|
}
|
package/lib/http/codes/index.js
CHANGED
|
@@ -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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
|
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
|
|
41
|
-
|
|
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.
|
|
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.
|
|
80
|
+
this._includes.push(modelTreeNode);
|
|
76
81
|
return modelTreeNode;
|
|
77
82
|
}
|
|
78
83
|
|
|
@@ -383,7 +383,7 @@ module.exports = class QueryLexer {
|
|
|
383
383
|
const param = this.parseParamFromToken(token);
|
|
384
384
|
|
|
385
385
|
if (isSubQuery === true && param === 'includes') {
|
|
386
|
-
const err = new TypeError(`'include' is forbidden inside subquery (position ${ i }). Use: 'model.
|
|
386
|
+
const err = new TypeError(`'include' is forbidden inside subquery (position ${ i }). Use: 'model.submodel' or 'model.submodel1+submodel2'.`);
|
|
387
387
|
throw err;
|
|
388
388
|
}
|
|
389
389
|
|
package/lib/query/traverse.js
CHANGED
|
@@ -8,10 +8,10 @@
|
|
|
8
8
|
const BOUNDS = require('../constants/Bounds');
|
|
9
9
|
|
|
10
10
|
const { Op } = require('sequelize');
|
|
11
|
-
const
|
|
11
|
+
const { NodesterQueryError } = require('nodester/errors');
|
|
12
12
|
const httpCodes = require('nodester/http/codes');
|
|
13
13
|
|
|
14
|
-
const { ensure } = require('
|
|
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
|
|
77
|
-
err
|
|
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
|
|
97
|
-
err
|
|
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
|
|
133
|
-
err
|
|
132
|
+
const err = new NodesterQueryError(`Count for '${ countTarget }' is not available.`);
|
|
133
|
+
Error.captureStackTrace(err, traverse);
|
|
134
134
|
throw err;
|
|
135
135
|
}
|
|
136
136
|
|
|
@@ -266,8 +266,7 @@ function traverse(queryNode, filter=null, model=null) {
|
|
|
266
266
|
const includeName = include.model;
|
|
267
267
|
|
|
268
268
|
if (rootModelAssociations[includeName] === undefined) {
|
|
269
|
-
const err = new
|
|
270
|
-
err.status = httpCodes.NOT_ACCEPTABLE;
|
|
269
|
+
const err = new NodesterQueryError(`No include named '${ includeName }'`);
|
|
271
270
|
Error.captureStackTrace(err, traverse);
|
|
272
271
|
throw err;
|
|
273
272
|
}
|
|
@@ -301,8 +300,7 @@ function _traverseIncludes(includes, model, filter, resultQuery) {
|
|
|
301
300
|
|
|
302
301
|
// If no such association:
|
|
303
302
|
if (!association) {
|
|
304
|
-
const err = new
|
|
305
|
-
err.status = httpCodes.NOT_ACCEPTABLE;
|
|
303
|
+
const err = new NodesterQueryError(`No include named '${ includeName }'`);
|
|
306
304
|
Error.captureStackTrace(err, _traverseIncludes);
|
|
307
305
|
throw err;
|
|
308
306
|
}
|
package/lib/router/index.js
CHANGED
|
@@ -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('
|
|
28
|
+
const { ensure } = require('nodester/validators/arguments');
|
|
29
29
|
|
|
30
30
|
// Console:
|
|
31
31
|
const consl = require('nodester/loggers/console');
|
package/lib/router/route.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
const finalhandler = require('finalhandler');
|
|
9
9
|
|
|
10
10
|
// Arguments validator.
|
|
11
|
-
const { ensure } = require('
|
|
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
|
-
//
|
|
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) {
|
package/lib/structures/Filter.js
CHANGED
|
@@ -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('
|
|
12
|
+
const { ensure } = require('nodester/validators/arguments');
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
module.exports = class NodesterFilter {
|
|
@@ -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
|
+
}
|
|
@@ -17,7 +17,7 @@ module.exports = {
|
|
|
17
17
|
* @param {String} rules
|
|
18
18
|
* @param {String} argumentName
|
|
19
19
|
*
|
|
20
|
-
* @api
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
4
|
-
"description": "A
|
|
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/
|
|
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
|
-
"
|
|
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":
|
|
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
|
+
|
|
File without changes
|