nodester 0.0.8 → 0.1.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.
- package/Readme.md +18 -59
- package/lib/application/index.js +28 -7
- package/lib/controllers/methods/index.js +34 -10
- package/lib/controllers/mixins/index.js +14 -5
- package/lib/database/connection.js +34 -0
- package/lib/database/migration.js +42 -0
- package/lib/database/utils.js +19 -0
- package/lib/facades/methods/index.js +173 -0
- package/lib/facades/mixins/index.js +111 -0
- package/lib/middlewares/formidable/index.js +37 -0
- package/lib/middlewares/ql/sequelize/interpreter/ModelsTree.js +2 -2
- package/lib/middlewares/ql/sequelize/interpreter/QueryLexer.js +3 -3
- package/lib/models/associate.js +17 -0
- package/lib/models/define.js +50 -1
- package/lib/models/mixins.js +81 -72
- package/lib/params/Params.js +10 -7
- package/lib/queries/Colander.js +84 -0
- package/lib/queries/traverse.js +311 -0
- package/lib/router/handlers.util.js +22 -2
- package/lib/router/index.js +96 -75
- package/lib/router/markers.js +78 -0
- package/lib/router/route.js +4 -4
- package/lib/router/routes.util.js +35 -5
- package/lib/router/utils.js +30 -0
- package/package.json +20 -7
- package/tests/nql.test.js +3 -3
- package/lib/_/n_controllers/Controller.js +0 -474
- package/lib/_/n_controllers/JWTController.js +0 -240
- package/lib/_/n_controllers/ServiceController.js +0 -109
- package/lib/_/n_controllers/WebController.js +0 -75
- package/lib/_facades/Facade.js +0 -388
- package/lib/_facades/FacadeParams.js +0 -11
- package/lib/_facades/ServiceFacade.js +0 -17
- package/lib/_facades/jwt.facade.js +0 -273
- package/lib/models/DisabledRefreshToken.js +0 -68
- package/lib/models/Extractor.js +0 -320
- package/lib/utils/forms.util.js +0 -22
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
const { Op } = require('sequelize');
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
module.exports = traverse;
|
|
5
|
+
|
|
6
|
+
function traverse(queryNode, colander=null, model) {
|
|
7
|
+
|
|
8
|
+
const fieldsAvailable = Object.keys(model.tableAttributes);
|
|
9
|
+
const includesAvailable = model.getIncludesList();
|
|
10
|
+
|
|
11
|
+
const newQuery = {
|
|
12
|
+
attributes: [],
|
|
13
|
+
where: {},
|
|
14
|
+
include: []
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const {
|
|
18
|
+
where,
|
|
19
|
+
includes,
|
|
20
|
+
fields,
|
|
21
|
+
clauses,
|
|
22
|
+
} = _disassembleQueryNode(queryNode);
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
// Fields:
|
|
26
|
+
//
|
|
27
|
+
// If Colander is not set,
|
|
28
|
+
// use every available field:
|
|
29
|
+
if (colander === null) {
|
|
30
|
+
for (let field of fieldsAvailable) {
|
|
31
|
+
// If no query filter or field is requested:
|
|
32
|
+
if (fields.length === 0 || fields.indexOf(field) > -1) {
|
|
33
|
+
newQuery.attributes.push(field);
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// Colander is present:
|
|
39
|
+
else {
|
|
40
|
+
// If no query fields were set,
|
|
41
|
+
// use the ones from Colander,
|
|
42
|
+
// If query fields were set,
|
|
43
|
+
// put them through Colander:
|
|
44
|
+
for (let field of colander.fields) {
|
|
45
|
+
if (fieldsAvailable.indexOf(field) === -1) {
|
|
46
|
+
const err = new TypeError(`field ${ field } is not present in model.`);
|
|
47
|
+
throw err;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// If field is not in available set:
|
|
51
|
+
// if (colander.fields.indexOf(field) === -1) {
|
|
52
|
+
// continue;
|
|
53
|
+
// }
|
|
54
|
+
|
|
55
|
+
// If no query filter or field is requested:
|
|
56
|
+
if (fields.length === 0 || fields.indexOf(field) > -1) {
|
|
57
|
+
newQuery.attributes.push(field);
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// At least 1 field is mandatory:
|
|
64
|
+
if (newQuery.attributes.length === 0) {
|
|
65
|
+
const err = new TypeError(`No fields were selected.`);
|
|
66
|
+
throw err;
|
|
67
|
+
}
|
|
68
|
+
// Fields\
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
// Clauses:
|
|
72
|
+
const order = {};
|
|
73
|
+
|
|
74
|
+
const clausesEntries = Object.entries(clauses);
|
|
75
|
+
for (let [clauseName, value] of clausesEntries) {
|
|
76
|
+
// If clause is not available:
|
|
77
|
+
if (colander != null) {
|
|
78
|
+
if (colander.clauses.indexOf(clauseName) === -1)
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
switch(clauseName) {
|
|
83
|
+
case 'limit':
|
|
84
|
+
// Do not set if -1:
|
|
85
|
+
if (value === -1)
|
|
86
|
+
continue;
|
|
87
|
+
|
|
88
|
+
newQuery.limit = value;
|
|
89
|
+
continue;
|
|
90
|
+
case 'skip':
|
|
91
|
+
// Do not set if 0:
|
|
92
|
+
if (value === 0)
|
|
93
|
+
continue;
|
|
94
|
+
|
|
95
|
+
newQuery.offset = value;
|
|
96
|
+
continue;
|
|
97
|
+
case 'order':
|
|
98
|
+
order.order = value;
|
|
99
|
+
continue;
|
|
100
|
+
case 'order_by':
|
|
101
|
+
order.by = value;
|
|
102
|
+
continue;
|
|
103
|
+
default:
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// "statics" override or set any query Clause:
|
|
109
|
+
if (colander !== null) {
|
|
110
|
+
for (let [clauseName, staticClauseValue] of Object.entries(colander.statics.clauses)) {
|
|
111
|
+
switch(clauseName) {
|
|
112
|
+
case 'limit':
|
|
113
|
+
newQuery.limit = staticClauseValue;
|
|
114
|
+
continue;
|
|
115
|
+
case 'skip':
|
|
116
|
+
newQuery.offset = staticClauseValue;
|
|
117
|
+
continue;
|
|
118
|
+
case 'order':
|
|
119
|
+
order.order = staticClauseValue;
|
|
120
|
+
continue;
|
|
121
|
+
case 'order_by':
|
|
122
|
+
order.by = staticClauseValue;
|
|
123
|
+
continue;
|
|
124
|
+
default:
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// Clauses\
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
// Order:
|
|
133
|
+
const sequelize = model.sequelize;
|
|
134
|
+
if ( ['rand', 'random'].indexOf(order.order) > -1) {
|
|
135
|
+
newQuery.order = sequelize.random();
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
const column = sequelize.col( order.by );
|
|
139
|
+
switch (order.order) {
|
|
140
|
+
// MAX/MIN:
|
|
141
|
+
case 'max-asc':
|
|
142
|
+
case 'max':
|
|
143
|
+
case 'min-desc':
|
|
144
|
+
newQuery.order = sequelize.fn('max', column);
|
|
145
|
+
break;
|
|
146
|
+
case 'min':
|
|
147
|
+
case 'min-asc':
|
|
148
|
+
case 'max-desc':
|
|
149
|
+
newQuery.order = [ sequelize.fn('max', column), 'DESC' ];
|
|
150
|
+
break;
|
|
151
|
+
// MAX/MIN\
|
|
152
|
+
|
|
153
|
+
default:
|
|
154
|
+
newQuery.order = [ [order.by, order.order] ];
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
// Order\
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
// Includes:
|
|
162
|
+
// If requested includes are not available:
|
|
163
|
+
const leftIncludes = includesAvailable.map(i => i.association);
|
|
164
|
+
for (let include of includes) {
|
|
165
|
+
const includeName = include.model;
|
|
166
|
+
|
|
167
|
+
const includeIndex = leftIncludes.indexOf(includeName);
|
|
168
|
+
if (includeIndex === -1) {
|
|
169
|
+
const err = new TypeError(`No include named ${ includeName }`);
|
|
170
|
+
throw err;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
leftIncludes.splice(includeIndex, 1);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
_traverseIncludes(includes, model, colander, newQuery)
|
|
177
|
+
// Includes\
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
// Where:
|
|
181
|
+
const whereEntries = Object.entries(where);
|
|
182
|
+
for (let [attribute, value] of whereEntries) {
|
|
183
|
+
_parseWhereEntry(attribute, value, newQuery.where, colander.statics.attributes);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// If "where" was not set:
|
|
187
|
+
if (whereEntries.length === 0) {
|
|
188
|
+
delete newQuery.where;
|
|
189
|
+
}
|
|
190
|
+
// Where\
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
return newQuery;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
function _traverseIncludes(includes, model, colander, resultQuery) {
|
|
198
|
+
// If no Colander:
|
|
199
|
+
if (colander === null) {
|
|
200
|
+
for (let include of includes) {
|
|
201
|
+
const includeName = include.model;
|
|
202
|
+
const association = model.associations[includeName];
|
|
203
|
+
|
|
204
|
+
// If no such association:
|
|
205
|
+
if (!association) {
|
|
206
|
+
const err = new TypeError(`No include ${ includeName }`);
|
|
207
|
+
throw err;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const includeModel = association.target;
|
|
211
|
+
// Build query for this include.
|
|
212
|
+
const associationQuery = traverse(include, null, includeModel);
|
|
213
|
+
|
|
214
|
+
_addAssociationQuery(associationQuery, includeName, resultQuery);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
// Colander is present:
|
|
218
|
+
else {
|
|
219
|
+
const colanderIncludeEntries = Object.entries(colander.includes);
|
|
220
|
+
for (let [includeName, includeColander] of colanderIncludeEntries) {
|
|
221
|
+
const association = model.associations[includeName];
|
|
222
|
+
// If no such association:
|
|
223
|
+
if (!association) {
|
|
224
|
+
const err = new TypeError(`No include ${ includeName }`);
|
|
225
|
+
throw err;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// If include was not requested:
|
|
229
|
+
const include = includes.find(({ model }) => model === includeName);
|
|
230
|
+
if (!include)
|
|
231
|
+
continue;
|
|
232
|
+
|
|
233
|
+
const includeModel = association.target;
|
|
234
|
+
// Build query for this include.
|
|
235
|
+
const associationQuery = traverse(include, colander.includes[includeName], includeModel);
|
|
236
|
+
|
|
237
|
+
_addAssociationQuery(associationQuery, includeName, resultQuery);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
function _addAssociationQuery(associationQuery, includeName, resultQuery) {
|
|
244
|
+
|
|
245
|
+
// Add all association info into query.
|
|
246
|
+
resultQuery.include.push({
|
|
247
|
+
association: includeName,
|
|
248
|
+
...associationQuery
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
function _parseWhereEntry(attribute, value, whereHolder, staticAttributes) {
|
|
254
|
+
let _value = value;
|
|
255
|
+
const static = staticAttributes[attribute];
|
|
256
|
+
|
|
257
|
+
// If attribute is Op (like, or, not, etc.):
|
|
258
|
+
if (attribute in Op) {
|
|
259
|
+
// Parse value:
|
|
260
|
+
_value = _parseValue(_value, attribute);
|
|
261
|
+
|
|
262
|
+
const op = Op[attribute];
|
|
263
|
+
whereHolder[op] = _value;
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Static value overrides any other:
|
|
268
|
+
if (!!static) {
|
|
269
|
+
whereHolder[attribute] = static;
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
whereHolder[attribute] = _parseValue(_value, attribute);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function _disassembleQueryNode(queryNode) {
|
|
277
|
+
// Disassemble current query node:
|
|
278
|
+
const {
|
|
279
|
+
where,
|
|
280
|
+
includes,
|
|
281
|
+
fields,
|
|
282
|
+
...clauses
|
|
283
|
+
} = queryNode;
|
|
284
|
+
// delete queryNode.model;
|
|
285
|
+
|
|
286
|
+
return {
|
|
287
|
+
where: where ?? {},
|
|
288
|
+
includes: includes ?? [],
|
|
289
|
+
fields: fields ?? [],
|
|
290
|
+
clauses: clauses ?? []
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function _parseValue(value, attribute) {
|
|
295
|
+
// If value is Object:
|
|
296
|
+
if (typeof value === 'object' && Array.isArray(value) === false) {
|
|
297
|
+
const [opKey, rawValue] = (Object.entries(value))[0];
|
|
298
|
+
|
|
299
|
+
// If operation is "in":
|
|
300
|
+
if (opKey === 'in') {
|
|
301
|
+
// Unwrap rawValue.
|
|
302
|
+
return rawValue[0][attribute];
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
const op = Op[opKey];
|
|
306
|
+
return { [op]: rawValue };
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return value;
|
|
311
|
+
}
|
|
@@ -26,19 +26,24 @@ function _parseRouteHandler(routeHandler={}) {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
const result = {
|
|
29
|
+
actionName: undefined,
|
|
29
30
|
before: null,
|
|
30
31
|
controllerName: undefined,
|
|
31
|
-
|
|
32
|
+
providerName: undefined
|
|
32
33
|
};
|
|
33
34
|
|
|
34
35
|
const {
|
|
36
|
+
action,
|
|
35
37
|
before,
|
|
36
38
|
|
|
37
39
|
controller,
|
|
38
40
|
controlledBy,
|
|
39
|
-
|
|
41
|
+
|
|
42
|
+
provider,
|
|
43
|
+
providedBy,
|
|
40
44
|
} = routeHandler;
|
|
41
45
|
|
|
46
|
+
// Controllers:
|
|
42
47
|
if (!!controlledBy) {
|
|
43
48
|
const parts = controlledBy.split('.');
|
|
44
49
|
const controllerName = parts[0];
|
|
@@ -49,6 +54,21 @@ function _parseRouteHandler(routeHandler={}) {
|
|
|
49
54
|
else if (!!controller) {
|
|
50
55
|
result.controllerName = `${ controller }`;
|
|
51
56
|
}
|
|
57
|
+
// Controllers\
|
|
58
|
+
|
|
59
|
+
// Providers:
|
|
60
|
+
else if (!!providedBy) {
|
|
61
|
+
const parts = providedBy.split('.');
|
|
62
|
+
const providerName = parts[0];
|
|
63
|
+
const actionName = parts[1];
|
|
64
|
+
result.providerName = providerName;
|
|
65
|
+
result.actionName = actionName;
|
|
66
|
+
}
|
|
67
|
+
else if (!!provider) {
|
|
68
|
+
result.providerName = `${ provider }`;
|
|
69
|
+
}
|
|
70
|
+
// Providers\
|
|
71
|
+
|
|
52
72
|
else if (!!action) {
|
|
53
73
|
result.actionName = `${ action }`;
|
|
54
74
|
}
|
package/lib/router/index.js
CHANGED
|
@@ -6,11 +6,17 @@
|
|
|
6
6
|
'use strict';
|
|
7
7
|
|
|
8
8
|
const MiddlewareStack = require('../stacks/MiddlewareStack');
|
|
9
|
-
const MarkersStack = require('../stacks/MarkersStack');
|
|
10
9
|
const Route = require('./route');
|
|
10
|
+
// Markers:
|
|
11
|
+
const MarkersStack = require('../stacks/MarkersStack');
|
|
12
|
+
const MarkerMethods = require('./markers');
|
|
11
13
|
// Utils:
|
|
12
14
|
const { typeOf } = require('../utils/types.util');
|
|
13
|
-
const {
|
|
15
|
+
const {
|
|
16
|
+
validateParsedRouteMethood,
|
|
17
|
+
wrapRouteHandler
|
|
18
|
+
} = require('./routes.util');
|
|
19
|
+
const { parseProviderFileNames } = require('./utils');
|
|
14
20
|
// File system:
|
|
15
21
|
const Path = require('path');
|
|
16
22
|
const fs = require('fs');
|
|
@@ -34,14 +40,17 @@ module.exports = class NodesterRouter {
|
|
|
34
40
|
* @api public
|
|
35
41
|
*/
|
|
36
42
|
constructor(opts={}) {
|
|
43
|
+
// Reference to the controllers stack.
|
|
44
|
+
this._controllers = new Map();
|
|
45
|
+
|
|
37
46
|
// Reference to middlewares stack.
|
|
38
47
|
this._middlewares = new MiddlewareStack({ finalhandlerEnabled: !!opts.finalhandlerEnabled });
|
|
39
48
|
|
|
40
49
|
// Reference to the markers stack.
|
|
41
50
|
this._markers = new MarkersStack();
|
|
42
51
|
|
|
43
|
-
// Reference to the
|
|
44
|
-
this.
|
|
52
|
+
// Reference to the providers stack.
|
|
53
|
+
this._providers = new Map();
|
|
45
54
|
|
|
46
55
|
this.codeFileExtensions = commonExtensions.code;
|
|
47
56
|
this.paths = {};
|
|
@@ -55,29 +64,20 @@ module.exports = class NodesterRouter {
|
|
|
55
64
|
this.codeFileExtensions = [...opts.codeFileExtensions];
|
|
56
65
|
}
|
|
57
66
|
|
|
58
|
-
// If "controllersPath" was set,
|
|
67
|
+
// If "controllersPath" was set,
|
|
68
|
+
// cache all controllers in directory:
|
|
59
69
|
if (!!opts.controllersPath) {
|
|
60
70
|
// Save path.
|
|
61
71
|
this.paths.controllers = opts.controllersPath;
|
|
62
72
|
|
|
63
|
-
const availableFileExtensions = this.codeFileExtensions;
|
|
64
73
|
// Only get files, which have available file extensions:
|
|
74
|
+
const availableFileExtensions = this.codeFileExtensions;
|
|
65
75
|
const fileNames = fs.readdirSync(this.paths.controllers);
|
|
66
|
-
for (const fileName of fileNames) {
|
|
67
|
-
|
|
68
|
-
const nameParts = fileName.split('.');
|
|
69
|
-
const extension = nameParts.pop();
|
|
70
|
-
if (availableFileExtensions.indexOf(extension) === -1) {
|
|
71
|
-
continue;
|
|
72
|
-
}
|
|
73
76
|
|
|
74
|
-
|
|
75
|
-
// but second part is not "controller":
|
|
76
|
-
if (nameParts.length > 1 && nameParts[1] !== 'controller')
|
|
77
|
-
continue;
|
|
77
|
+
const controllersNames = parseProviderFileNames(fileNames, availableFileExtensions, 'controller');
|
|
78
78
|
|
|
79
|
+
for (const { fileName, controllerName } of controllersNames) {
|
|
79
80
|
const controller = require(Path.join(this.paths.controllers, fileName));
|
|
80
|
-
const controllerName = nameParts[0];
|
|
81
81
|
this.addController(controller, controllerName);
|
|
82
82
|
}
|
|
83
83
|
}
|
|
@@ -94,6 +94,37 @@ module.exports = class NodesterRouter {
|
|
|
94
94
|
this.addController(controllerDefinition, controllerName);
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
|
+
|
|
98
|
+
// If "providersPath" was set,
|
|
99
|
+
// cache all providers in directory:
|
|
100
|
+
if (!!opts.providersPath) {
|
|
101
|
+
// Save path.
|
|
102
|
+
this.paths.providers = opts.providersPath;
|
|
103
|
+
|
|
104
|
+
// Only get files, which have available file extensions:
|
|
105
|
+
const availableFileExtensions = this.codeFileExtensions;
|
|
106
|
+
const fileNames = fs.readdirSync(this.paths.providers);
|
|
107
|
+
|
|
108
|
+
const providersNames = parseProviderFileNames(fileNames, availableFileExtensions, 'provider');
|
|
109
|
+
|
|
110
|
+
for (const { fileName, providerName } of providersNames) {
|
|
111
|
+
const provider = require(Path.join(this.paths.providers, fileName));
|
|
112
|
+
this.addProvider(provider, providerName);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// If "providers" were provided as an Object:
|
|
117
|
+
if (!!opts.providers) {
|
|
118
|
+
if (typeOf(opts.providers) !== 'Object') {
|
|
119
|
+
const err = new TypeError(`"providers" must be an Object.`);
|
|
120
|
+
throw err;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const entities = Object.entities(opts.providers);
|
|
124
|
+
for (const [providerName, providerDefinition] of entities) {
|
|
125
|
+
this.addProvider(providerDefinition, providerName);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
97
128
|
}
|
|
98
129
|
|
|
99
130
|
|
|
@@ -111,6 +142,7 @@ module.exports = class NodesterRouter {
|
|
|
111
142
|
controller: this.addController.bind(this),
|
|
112
143
|
middleware: this.addMiddleware.bind(this),
|
|
113
144
|
marker: this.addMarker.bind(this),
|
|
145
|
+
provider: this.addProvider.bind(this),
|
|
114
146
|
route: this.addRoute.bind(this),
|
|
115
147
|
routes: this.addRoutes.bind(this),
|
|
116
148
|
}
|
|
@@ -182,6 +214,33 @@ module.exports = class NodesterRouter {
|
|
|
182
214
|
}
|
|
183
215
|
|
|
184
216
|
|
|
217
|
+
/*
|
|
218
|
+
* Adds new provider to the providers stack.
|
|
219
|
+
*
|
|
220
|
+
* @param {Function|Object} provider
|
|
221
|
+
* @param {String} providerName
|
|
222
|
+
*
|
|
223
|
+
* @api public
|
|
224
|
+
*/
|
|
225
|
+
addProvider(fnOrObject, providerName=null) {
|
|
226
|
+
const providerType = typeOf(fnOrObject);
|
|
227
|
+
const name = providerName ?? fnOrObject?.name ?? fnOrObject.constructor.name;
|
|
228
|
+
|
|
229
|
+
// If provider was exported as Object:
|
|
230
|
+
if (providerType === 'Object') {
|
|
231
|
+
this._providers.set(name, fnOrObject);
|
|
232
|
+
}
|
|
233
|
+
// If provider was exported as a constructor function:
|
|
234
|
+
else if (providerType === 'function') {
|
|
235
|
+
this._providers.set(name, new fnOrObject());
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
const err = new TypeError(`Please check how you exported ${ name }, it should be either Object or constructor function.`);
|
|
239
|
+
throw err;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
|
|
185
244
|
/*
|
|
186
245
|
* Creates route middleware and adds it to the stack.
|
|
187
246
|
*
|
|
@@ -191,21 +250,19 @@ module.exports = class NodesterRouter {
|
|
|
191
250
|
* @api public
|
|
192
251
|
*/
|
|
193
252
|
addRoute(route='', handler) {
|
|
194
|
-
const
|
|
195
|
-
|
|
253
|
+
const parsedRoute = new Route(route);
|
|
254
|
+
// Will throw exception if not valid.
|
|
255
|
+
validateParsedRouteMethood(parsedRoute);
|
|
196
256
|
|
|
197
|
-
|
|
198
|
-
if (parsed.method === undefined) {
|
|
199
|
-
const err = new TypeError(`"route" should start with one of the following methods: [GET, POST, PUT, DELETE, QUERY, HEADER, OPTIONS]`);
|
|
200
|
-
throw err;
|
|
201
|
-
}
|
|
257
|
+
const handlerType = typeOf(handler);
|
|
202
258
|
|
|
203
|
-
if (handlerType === 'Object' && !this.paths.controllers) {
|
|
204
|
-
const
|
|
259
|
+
if (handlerType === 'Object' && !this.paths.controllers && !this.paths.providers) {
|
|
260
|
+
const msg = `Please set "controllersPath" or "providersPath" during Router initialization.`;
|
|
261
|
+
const err = new TypeError(msg);
|
|
205
262
|
throw err;
|
|
206
263
|
}
|
|
207
264
|
|
|
208
|
-
const wrapped = wrapRouteHandler.call(this,
|
|
265
|
+
const wrapped = wrapRouteHandler.call(this, parsedRoute, handler);
|
|
209
266
|
return this.addMiddleware(wrapped);
|
|
210
267
|
}
|
|
211
268
|
|
|
@@ -228,11 +285,18 @@ module.exports = class NodesterRouter {
|
|
|
228
285
|
/*
|
|
229
286
|
* Proxy to .add.middleware()
|
|
230
287
|
*
|
|
231
|
-
* @param {Function}
|
|
288
|
+
* @param {Function|NodesterRouter} fnOrRouter
|
|
232
289
|
*
|
|
233
290
|
* @api public
|
|
234
291
|
*/
|
|
235
|
-
use(
|
|
292
|
+
use(fnOrRouter) {
|
|
293
|
+
let fn = fnOrRouter;
|
|
294
|
+
|
|
295
|
+
// If Router:
|
|
296
|
+
if (fnOrRouter instanceof NodesterRouter) {
|
|
297
|
+
fn = fnOrRouter.handle.bind(fnOrRouter);
|
|
298
|
+
}
|
|
299
|
+
|
|
236
300
|
return this.add.middleware(fn);
|
|
237
301
|
}
|
|
238
302
|
|
|
@@ -249,51 +313,8 @@ module.exports = class NodesterRouter {
|
|
|
249
313
|
const markerFn = this._markers.get(markerName);
|
|
250
314
|
|
|
251
315
|
return {
|
|
252
|
-
route: (route, fn) =>
|
|
253
|
-
|
|
254
|
-
// ToDo: move it to separate validator:
|
|
255
|
-
if (parsed.method === undefined) {
|
|
256
|
-
const err = new TypeError(`"route" should start with one of the following methods: [GET, POST, PUT, DELETE, QUERY, HEADER, OPTIONS]`);
|
|
257
|
-
throw err;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
const wrapped = async (req, res, next) => {
|
|
261
|
-
const matched = await markerFn.call(self, req, res);
|
|
262
|
-
// Skip, if marker's condition was not matched:
|
|
263
|
-
if (!matched) {
|
|
264
|
-
return next();
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// Wrap and call:
|
|
268
|
-
const routeHandler = wrapRouteHandler.call(self, parsed, fn);
|
|
269
|
-
await routeHandler.call(self, req, res, next);
|
|
270
|
-
|
|
271
|
-
// If response was not sent,
|
|
272
|
-
// go to next one:
|
|
273
|
-
if (res.headersSent === false) {
|
|
274
|
-
next();
|
|
275
|
-
}
|
|
276
|
-
};
|
|
277
|
-
return self.add.route(wrapped);
|
|
278
|
-
},
|
|
279
|
-
use: (fn) => {
|
|
280
|
-
const wrapped = async (req, res, next) => {
|
|
281
|
-
const matched = await markerFn.call(self, req, res);
|
|
282
|
-
// Skip, if marker's condition was not matched:
|
|
283
|
-
if (!matched) {
|
|
284
|
-
return next();
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
await fn.call(self, req, res, next);
|
|
288
|
-
|
|
289
|
-
// If response was not sent,
|
|
290
|
-
// go to next one:
|
|
291
|
-
if (res.headersSent === false) {
|
|
292
|
-
next();
|
|
293
|
-
}
|
|
294
|
-
};
|
|
295
|
-
return self.use(wrapped);
|
|
296
|
-
}
|
|
316
|
+
route: (route, fn) => MarkerMethods.onlyRoute.call(self, route, fn, markerFn),
|
|
317
|
+
use: (fnOrRouter) => MarkerMethods.onlyUse.call(self, fnOrRouter, markerFn),
|
|
297
318
|
}
|
|
298
319
|
}
|
|
299
320
|
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
const Route = require('./route');
|
|
2
|
+
// Utils.
|
|
3
|
+
const {
|
|
4
|
+
validateParsedRouteMethood,
|
|
5
|
+
wrapRouteHandler
|
|
6
|
+
} = require('./routes.util');
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
module.exports = {
|
|
10
|
+
onlyRoute: _onlyRoute,
|
|
11
|
+
onlyUse: _onlyUse
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/*
|
|
15
|
+
*
|
|
16
|
+
* @param {String} route
|
|
17
|
+
* @param {Function} fn
|
|
18
|
+
* @param {Function} markerFn
|
|
19
|
+
*
|
|
20
|
+
*/
|
|
21
|
+
function _onlyRoute(route, fn, markerFn) {
|
|
22
|
+
const parsedRoute = new Route(route);
|
|
23
|
+
// Will throw exception if not valid.
|
|
24
|
+
validateParsedRouteMethood(parsedRoute);
|
|
25
|
+
|
|
26
|
+
const wrapped = async (req, res, next) => {
|
|
27
|
+
const matched = await markerFn.call(this, req, res);
|
|
28
|
+
// Skip, if marker's condition was not matched:
|
|
29
|
+
if (!matched) {
|
|
30
|
+
return next();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Wrap and call:
|
|
34
|
+
const routeHandler = wrapRouteHandler.call(this, parsedRoute, fn);
|
|
35
|
+
await routeHandler.call(this, req, res, next);
|
|
36
|
+
|
|
37
|
+
// If response was not sent,
|
|
38
|
+
// go to the next one:
|
|
39
|
+
if (res.headersSent === false) {
|
|
40
|
+
next();
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
return this.add.route(route, wrapped);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
/*
|
|
48
|
+
*
|
|
49
|
+
* @param {Function} fnOrRouter
|
|
50
|
+
* @param {Function} markerFn
|
|
51
|
+
*
|
|
52
|
+
*/
|
|
53
|
+
function _onlyUse(fnOrRouter, markerFn) {
|
|
54
|
+
|
|
55
|
+
const wrapped = async (req, res, next) => {
|
|
56
|
+
const matched = await markerFn.call(this, req, res);
|
|
57
|
+
|
|
58
|
+
// Skip, if marker's condition was not matched:
|
|
59
|
+
if (!matched) {
|
|
60
|
+
return next();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const isRouter = fnOrRouter.constructor.name === 'NodesterRouter';
|
|
64
|
+
|
|
65
|
+
const fn = isRouter ? fnOrRouter.handle.bind(fnOrRouter) : fnOrRouter;
|
|
66
|
+
await fn.call(this, req, res, next);
|
|
67
|
+
|
|
68
|
+
// If regular handler function:
|
|
69
|
+
if (!isRouter) {
|
|
70
|
+
// If response was not sent,
|
|
71
|
+
// go to the next one:
|
|
72
|
+
if (res.headersSent === false) {
|
|
73
|
+
next();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
return this.use(wrapped);
|
|
78
|
+
}
|
package/lib/router/route.js
CHANGED
|
@@ -40,17 +40,17 @@ module.exports = class NodesterRoute {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
// Parse:
|
|
43
|
+
const collapsedSpaces = routeStringOrOpts.replace(/\s\s+/g, ' ');
|
|
43
44
|
const parts = routeStringOrOpts.split(' ');
|
|
44
|
-
const cleared = parts.filter(p => p.length > 0);
|
|
45
45
|
|
|
46
46
|
// Set method:
|
|
47
|
-
if (
|
|
48
|
-
const method =
|
|
47
|
+
if (parts[0].indexOf('/') === -1) {
|
|
48
|
+
const method = parts.shift().toUpperCase();
|
|
49
49
|
this.method = method;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
// Build route one again and set it:
|
|
53
|
-
const clearRoute =
|
|
53
|
+
const clearRoute = parts.join('');
|
|
54
54
|
this.route = clearRoute;
|
|
55
55
|
|
|
56
56
|
// Parse path parts:
|