nodester 0.0.9 → 0.1.4

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 (48) hide show
  1. package/Readme.md +16 -2
  2. package/lib/application/index.js +29 -8
  3. package/lib/constants/ErrorCodes.js +19 -0
  4. package/lib/constants/Operations.js +1 -1
  5. package/lib/controllers/methods/index.js +34 -10
  6. package/lib/controllers/mixins/index.js +72 -24
  7. package/lib/database/connection.js +34 -0
  8. package/lib/database/migration.js +42 -0
  9. package/lib/database/utils.js +19 -0
  10. package/lib/facades/methods/index.js +180 -0
  11. package/lib/facades/mixins/index.js +111 -0
  12. package/lib/factories/errors/CustomError.js +7 -0
  13. package/lib/factories/errors/NodesterQueryError.js +23 -0
  14. package/lib/factories/errors/index.js +10 -3
  15. package/lib/loggers/dev.js +28 -0
  16. package/lib/middlewares/formidable/index.js +37 -0
  17. package/lib/middlewares/ql/sequelize/interpreter/ModelsTree.js +26 -3
  18. package/lib/middlewares/ql/sequelize/interpreter/QueryLexer.js +67 -15
  19. package/lib/models/define.js +49 -1
  20. package/lib/models/mixins.js +76 -67
  21. package/lib/params/Params.js +10 -7
  22. package/lib/queries/Colander.js +107 -0
  23. package/lib/queries/NodesterQueryParams.js +6 -0
  24. package/lib/queries/traverse.js +381 -0
  25. package/lib/router/handlers.util.js +22 -2
  26. package/lib/router/index.js +97 -76
  27. package/lib/router/markers.js +78 -0
  28. package/lib/router/route.js +4 -4
  29. package/lib/router/routes.util.js +35 -5
  30. package/lib/router/utils.js +30 -0
  31. package/lib/stacks/MarkersStack.js +1 -1
  32. package/lib/stacks/MiddlewareStack.js +1 -1
  33. package/lib/utils/models.js +14 -0
  34. package/package.json +36 -7
  35. package/tests/nql.test.js +3 -3
  36. package/lib/_/n_controllers/Controller.js +0 -474
  37. package/lib/_/n_controllers/JWTController.js +0 -240
  38. package/lib/_/n_controllers/ServiceController.js +0 -109
  39. package/lib/_/n_controllers/WebController.js +0 -75
  40. package/lib/_facades/Facade.js +0 -388
  41. package/lib/_facades/FacadeParams.js +0 -11
  42. package/lib/_facades/ServiceFacade.js +0 -17
  43. package/lib/_facades/jwt.facade.js +0 -273
  44. package/lib/models/Extractor.js +0 -320
  45. package/lib/preprocessors/IncludesPreprocessor.js +0 -55
  46. package/lib/preprocessors/QueryPreprocessor.js +0 -64
  47. package/lib/utils/forms.util.js +0 -22
  48. /package/lib/{logger → loggers}/console.js +0 -0
@@ -10,10 +10,9 @@ module.exports = Params;
10
10
  * @param {Object} sourceObj
11
11
  * @param {Object} defaultValuesList
12
12
  *
13
- * @return {Function|Object} controller
13
+ * @return {Object} result
14
14
  *
15
15
  * @api public
16
- * @alias withDefaultCRUD
17
16
  */
18
17
  function Params(
19
18
  sourceObj={},
@@ -23,11 +22,15 @@ function Params(
23
22
 
24
23
  const keys = Object.keys(defaultValuesList);
25
24
  for (const key of keys) {
26
- // If value is not set, use default one from 'defaultValuesList'.
27
- result[key] = !(key in sourceObj[key]) ?
28
- defaultValuesList[key]
29
- :
30
- sourceObj[key];
25
+
26
+ // If value is not set,
27
+ // use default one from 'defaultValuesList':
28
+ if (sourceObj[key] === undefined) {
29
+ result[key] = defaultValuesList[key];
30
+ continue;
31
+ }
32
+
33
+ result[key] = sourceObj[key];
31
34
  }
32
35
 
33
36
  return result;
@@ -0,0 +1,107 @@
1
+ /*!
2
+ * /nodester
3
+ * MIT Licensed
4
+ */
5
+ 'use strict';
6
+
7
+ const CLAUSES = ['limit', 'skip', 'order', 'order_by'];
8
+
9
+ const { isModel } = require('../utils/models');
10
+
11
+
12
+ module.exports = class Colander {
13
+
14
+ /*
15
+ *
16
+ * @param {Object|Model} optsOrModelDefinition
17
+ @ - @param {Model} model
18
+ * - @param {Array} fields
19
+ * - @param {Array} clauses
20
+ * - @param {Object} includes
21
+ * - @param {Object} statics
22
+ * -- @param {Object} attributes
23
+ * -- @param {Object} clauses
24
+ *
25
+ * @param {Boolean} noLimit
26
+ *
27
+ */
28
+ constructor(optsOrModelDefinition, noLimit=false) {
29
+ this._fields = [];
30
+ this._clauses = [];
31
+ this._includes = {};
32
+
33
+ this._statics = {
34
+ attributes: {},
35
+ clauses: {
36
+ limit: 3
37
+ }
38
+ }
39
+ if (noLimit === true) {
40
+ delete this._statics.clauses.limit;
41
+ }
42
+
43
+ // If model:
44
+ if (isModel(optsOrModelDefinition)) {
45
+ this._fields = Object.keys(optsOrModelDefinition.tableAttributes);
46
+ this._clauses = CLAUSES;
47
+ }
48
+ // If options:
49
+ else {
50
+ const {
51
+ model,
52
+ fields,
53
+ clauses,
54
+ includes,
55
+ statics,
56
+ } = optsOrModelDefinition;
57
+
58
+
59
+ // If fields are array:
60
+ if (Array.isArray(fields)) {
61
+ this._fields = fields;
62
+ }
63
+ // If fields were not provided,
64
+ // but we have full model definition:
65
+ else if (isModel(model)) {
66
+ this._fields = Object.keys(model.tableAttributes);
67
+ }
68
+
69
+
70
+ if (Array.isArray(clauses)) {
71
+ this._clauses = clauses;
72
+ }
73
+
74
+ if (typeof includes === 'object') {
75
+ this._includes = includes;
76
+ }
77
+
78
+ if (typeof statics === 'object') {
79
+ if (typeof statics.attributes === 'object') {
80
+ this._statics.attributes = statics.attributes;
81
+ }
82
+
83
+ if (typeof statics.clauses === 'object') {
84
+ this._statics.clauses = statics.clauses;
85
+ }
86
+ }
87
+ }
88
+ }
89
+
90
+ // Getters:
91
+ get fields() {
92
+ return this._fields;
93
+ }
94
+
95
+ get clauses() {
96
+ return this._clauses;
97
+ }
98
+
99
+ get includes() {
100
+ return this._includes;
101
+ }
102
+
103
+ get statics() {
104
+ return this._statics;
105
+ }
106
+ // Getters\
107
+ }
@@ -1,3 +1,9 @@
1
+ /*!
2
+ * /nodester
3
+ * MIT Licensed
4
+ */
5
+ 'use strict';
6
+
1
7
  // Dictionary of unsafe characters:
2
8
  const NOT_ALLOWED = [
3
9
  '{',
@@ -0,0 +1,381 @@
1
+ /*!
2
+ * /nodester
3
+ * MIT Licensed
4
+ */
5
+ 'use strict';
6
+
7
+ const { Op } = require('sequelize');
8
+ const NQueryError = require('../factories/errors/NodesterQueryError');
9
+
10
+
11
+ module.exports = traverse;
12
+
13
+ function traverse(queryNode, colander=null, model) {
14
+
15
+ const sequelize = model.sequelize;
16
+ const fieldsAvailable = Object.keys(model.tableAttributes);
17
+ const includesAvailable = model.getIncludesList();
18
+
19
+ const newQuery = {
20
+ attributes: [],
21
+ where: {},
22
+ include: []
23
+ };
24
+
25
+ const {
26
+ where,
27
+ includes,
28
+ fields,
29
+ functions,
30
+ clauses,
31
+ } = _disassembleQueryNode(queryNode);
32
+
33
+
34
+ // Fields:
35
+ //
36
+ // If Colander is not set,
37
+ // use every available field:
38
+ if (colander === null) {
39
+ for (let field of fieldsAvailable) {
40
+ // If no query filter or field is requested:
41
+ if (fields.length === 0 || fields.indexOf(field) > -1) {
42
+ newQuery.attributes.push(field);
43
+ continue;
44
+ }
45
+ }
46
+ }
47
+ // Colander is present:
48
+ else {
49
+ // If no query fields were set,
50
+ // use the ones from Colander,
51
+ // If query fields were set,
52
+ // put them through Colander:
53
+ for (let field of colander.fields) {
54
+ if (fieldsAvailable.indexOf(field) === -1) {
55
+ const err = new TypeError(`field ${ field } is not present in model.`);
56
+ throw err;
57
+ }
58
+
59
+ // If field is not in available set:
60
+ // if (colander.fields.indexOf(field) === -1) {
61
+ // continue;
62
+ // }
63
+
64
+ // If no query filter or field is requested:
65
+ if (fields.length === 0 || fields.indexOf(field) > -1) {
66
+ newQuery.attributes.push(field);
67
+ continue;
68
+ }
69
+ }
70
+ }
71
+
72
+ // At least 1 field is mandatory:
73
+ if (newQuery.attributes.length === 0) {
74
+ const err = new TypeError(`No fields were selected.`);
75
+ throw err;
76
+ }
77
+ // Fields\
78
+
79
+ // Functions:
80
+ for (const fnParams of functions) {
81
+
82
+ // If COUNT() is requested:
83
+ if (fnParams.fn === 'count') {
84
+ const countParams = fnParams.args;
85
+
86
+ const [ countTarget ] = countParams;
87
+ const RootModelName = model.options.name;
88
+ // Count can be requested for this model,
89
+ // or for any of the available uncludes.
90
+ const isForRootModel = countTarget === RootModelName.plural.toLowerCase();
91
+
92
+ // Compile request:
93
+ // Example:
94
+ // `(SELECT COUNT(*) FROM comments WHERE comments.morph_id=Morph.id)`
95
+
96
+ // Params for attribute:
97
+ let rawSQL = '(SELECT COUNT(*) FROM ';
98
+ let countAttribute = '_count';
99
+
100
+ // If request to count one of includes:
101
+ if (!isForRootModel) {
102
+ // Check if it's available:
103
+ if (
104
+ !colander
105
+ ||
106
+ !colander?.includes[countTarget]
107
+ ||
108
+ model.associations[countTarget] === undefined
109
+ ) {
110
+ const err = new NQueryError(`Count for ${ countTarget } is not available.`);
111
+ throw err;
112
+ }
113
+
114
+ const {
115
+ foreignKey,
116
+ sourceKey
117
+ } = model.associations[countTarget];
118
+ rawSQL += `${ countTarget } where ${ countTarget }.${ foreignKey }=${ RootModelName.singular }.${ sourceKey })`;
119
+ countAttribute = `${ countTarget }_count`;
120
+ }
121
+
122
+ newQuery.attributes.push(
123
+ [sequelize.literal(rawSQL), countAttribute]
124
+ );
125
+ }
126
+ }
127
+ // Functions\
128
+
129
+ // Clauses:
130
+ const order = {};
131
+
132
+ const clausesEntries = Object.entries(clauses);
133
+ for (let [clauseName, value] of clausesEntries) {
134
+ // If clause is not available:
135
+ if (colander != null) {
136
+ if (colander.clauses.indexOf(clauseName) === -1)
137
+ continue;
138
+ }
139
+
140
+ switch(clauseName) {
141
+ case 'limit':
142
+ // Do not set if -1:
143
+ if (value === -1)
144
+ continue;
145
+
146
+ newQuery.limit = value;
147
+ continue;
148
+
149
+ case 'skip':
150
+ // Do not set if 0:
151
+ if (value === 0)
152
+ continue;
153
+
154
+ newQuery.offset = value;
155
+ continue;
156
+
157
+ case 'order':
158
+ order.order = value;
159
+ continue;
160
+
161
+ case 'order_by':
162
+ order.by = value;
163
+ continue;
164
+
165
+ default:
166
+ continue;
167
+ }
168
+ }
169
+
170
+ // "statics" override or set any query Clause:
171
+ if (colander !== null) {
172
+ const staticClausesEntries = Object.entries(colander.statics.clauses);
173
+ for (let entry of staticClausesEntries) {
174
+ const [clauseName, staticClauseValue] = entry;
175
+
176
+ switch(clauseName) {
177
+ case 'limit':
178
+ newQuery.limit = staticClauseValue;
179
+ continue;
180
+ case 'skip':
181
+ newQuery.offset = staticClauseValue;
182
+ continue;
183
+ case 'order':
184
+ order.order = staticClauseValue;
185
+ continue;
186
+ case 'order_by':
187
+ order.by = staticClauseValue;
188
+ continue;
189
+ default:
190
+ break;
191
+ }
192
+ }
193
+ }
194
+ // Clauses\
195
+
196
+
197
+ // Order:
198
+ if ( ['rand', 'random'].indexOf(order.order) > -1) {
199
+ newQuery.order = sequelize.random();
200
+ }
201
+ else {
202
+ const column = sequelize.col( order.by );
203
+ switch (order.order) {
204
+ // MAX/MIN:
205
+ case 'max-asc':
206
+ case 'max':
207
+ case 'min-desc':
208
+ newQuery.order = sequelize.fn('max', column);
209
+ break;
210
+ case 'min':
211
+ case 'min-asc':
212
+ case 'max-desc':
213
+ newQuery.order = [ sequelize.fn('max', column), 'DESC' ];
214
+ break;
215
+ // MAX/MIN\
216
+
217
+ case null:
218
+ case undefined:
219
+ newQuery.order = [ ['id', 'desc'] ];
220
+ break;
221
+
222
+ default:
223
+ newQuery.order = [ [order.by, order.order] ];
224
+ break;
225
+ }
226
+ }
227
+ // Order\
228
+
229
+
230
+ // Includes:
231
+ // If requested includes are not available:
232
+ const leftIncludes = includesAvailable.map(i => i.association);
233
+ for (let include of includes) {
234
+ const includeName = include.model;
235
+
236
+ const includeIndex = leftIncludes.indexOf(includeName);
237
+ if (includeIndex === -1) {
238
+ const err = new TypeError(`No include named ${ includeName }`);
239
+ throw err;
240
+ }
241
+
242
+ leftIncludes.splice(includeIndex, 1);
243
+ }
244
+
245
+ _traverseIncludes(includes, model, colander, newQuery)
246
+ // Includes\
247
+
248
+
249
+ // Where:
250
+ const whereEntries = Object.entries(where);
251
+ for (let [attribute, value] of whereEntries) {
252
+ _parseWhereEntry(attribute, value, newQuery.where, colander.statics.attributes);
253
+ }
254
+
255
+ // If "where" was not set:
256
+ if (whereEntries.length === 0) {
257
+ delete newQuery.where;
258
+ }
259
+ // Where\
260
+
261
+ return newQuery;
262
+ }
263
+
264
+
265
+ function _traverseIncludes(includes, model, colander, resultQuery) {
266
+ // If no Colander:
267
+ if (colander === null) {
268
+ for (let include of includes) {
269
+ const includeName = include.model;
270
+ const association = model.associations[includeName];
271
+
272
+ // If no such association:
273
+ if (!association) {
274
+ const err = new TypeError(`No include ${ includeName }`);
275
+ throw err;
276
+ }
277
+
278
+ const includeModel = association.target;
279
+ // Build query for this include.
280
+ const associationQuery = traverse(include, null, includeModel);
281
+
282
+ _addAssociationQuery(associationQuery, includeName, resultQuery);
283
+ }
284
+ }
285
+ // Colander is present:
286
+ else {
287
+ const colanderIncludeEntries = Object.entries(colander.includes);
288
+ for (let [includeName, includeColander] of colanderIncludeEntries) {
289
+ const association = model.associations[includeName];
290
+ // If no such association:
291
+ if (!association) {
292
+ const err = new TypeError(`No include ${ includeName }`);
293
+ throw err;
294
+ }
295
+
296
+ // If include was not requested:
297
+ const include = includes.find(({ model }) => model === includeName);
298
+ if (!include)
299
+ continue;
300
+
301
+ const includeModel = association.target;
302
+ // Build query for this include.
303
+ const associationQuery = traverse(include, colander.includes[includeName], includeModel);
304
+
305
+ _addAssociationQuery(associationQuery, includeName, resultQuery);
306
+ }
307
+ }
308
+ }
309
+
310
+
311
+ function _addAssociationQuery(associationQuery, includeName, resultQuery) {
312
+
313
+ // Add all association info into query.
314
+ resultQuery.include.push({
315
+ association: includeName,
316
+ ...associationQuery
317
+ });
318
+ }
319
+
320
+
321
+ function _parseWhereEntry(attribute, value, whereHolder, staticAttributes) {
322
+ let _value = value;
323
+ const staticAttribute = staticAttributes[attribute];
324
+
325
+ // If attribute is Op (not, like, or, etc.):
326
+ if (attribute in Op) {
327
+ // Parse value:
328
+ _value = _parseValue(_value, attribute);
329
+
330
+ const op = Op[attribute];
331
+ whereHolder[op] = _value;
332
+ return;
333
+ }
334
+
335
+ // Static value overrides any other:
336
+ if (!!staticAttribute) {
337
+ whereHolder[attribute] = staticAttribute;
338
+ return;
339
+ }
340
+
341
+ whereHolder[attribute] = _parseValue(_value, attribute);
342
+ }
343
+
344
+ function _disassembleQueryNode(queryNode) {
345
+ // Disassemble current query node:
346
+ const {
347
+ where,
348
+ includes,
349
+ fields,
350
+ functions,
351
+ ...clauses
352
+ } = queryNode;
353
+ // delete queryNode.model;
354
+
355
+ return {
356
+ where: where ?? {},
357
+ includes: includes ?? [],
358
+ fields: fields ?? [],
359
+ functions: functions ?? [],
360
+ clauses: clauses ?? []
361
+ };
362
+ }
363
+
364
+ function _parseValue(value, attribute) {
365
+ // If value is Object:
366
+ if (typeof value === 'object' && Array.isArray(value) === false) {
367
+ const [opKey, rawValue] = (Object.entries(value))[0];
368
+
369
+ // If operation is "in":
370
+ if (opKey === 'in') {
371
+ // Unwrap rawValue.
372
+ return rawValue[0][attribute];
373
+ }
374
+ else {
375
+ const op = Op[opKey];
376
+ return { [op]: rawValue };
377
+ }
378
+ }
379
+
380
+ return value;
381
+ }
@@ -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
- actionName: undefined,
32
+ providerName: undefined
32
33
  };
33
34
 
34
35
  const {
36
+ action,
35
37
  before,
36
38
 
37
39
  controller,
38
40
  controlledBy,
39
- action
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
  }