nodester 0.0.1 → 0.0.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.
Files changed (68) hide show
  1. package/Readme.md +33 -39
  2. package/lib/application/index.js +110 -38
  3. package/lib/constants/Operations.js +23 -0
  4. package/lib/controllers/methods/index.js +194 -0
  5. package/lib/controllers/mixins/index.js +222 -0
  6. package/lib/database/connection.js +34 -0
  7. package/lib/database/migration.js +42 -0
  8. package/lib/database/utils.js +19 -0
  9. package/lib/enums/Enum.js +16 -0
  10. package/lib/facades/methods/index.js +173 -0
  11. package/lib/facades/mixins/index.js +111 -0
  12. package/lib/factories/errors/CustomError.js +7 -5
  13. package/lib/factories/responses/html.js +7 -2
  14. package/lib/factories/responses/rest.js +110 -0
  15. package/lib/http/codes/index.js +157 -0
  16. package/lib/{application/http → http}/request.js +6 -30
  17. package/lib/{application/http → http}/response.js +20 -53
  18. package/lib/middlewares/etag/index.js +62 -0
  19. package/lib/middlewares/ql/sequelize/index.js +34 -0
  20. package/lib/middlewares/ql/sequelize/interpreter/ModelsTree.js +121 -0
  21. package/lib/middlewares/ql/sequelize/interpreter/QueryLexer.js +456 -0
  22. package/lib/models/associate.js +17 -0
  23. package/lib/models/define.js +56 -14
  24. package/lib/models/mixins.js +100 -78
  25. package/lib/params/Params.js +37 -0
  26. package/lib/queries/Colander.js +84 -0
  27. package/lib/queries/NodesterQueryParams.js +139 -0
  28. package/lib/queries/traverse.js +311 -0
  29. package/lib/router/handlers.util.js +61 -0
  30. package/lib/router/index.js +386 -0
  31. package/lib/router/route.js +124 -0
  32. package/lib/router/routes.util.js +66 -0
  33. package/lib/stacks/MarkersStack.js +35 -0
  34. package/lib/{application → stacks}/MiddlewareStack.js +47 -13
  35. package/lib/utils/path.util.js +3 -1
  36. package/lib/utils/types.util.js +51 -1
  37. package/lib/validators/dates.js +25 -0
  38. package/lib/validators/numbers.js +14 -0
  39. package/package.json +31 -4
  40. package/tests/index.test.js +7 -2
  41. package/tests/nql.test.js +277 -0
  42. package/docs/App.md +0 -13
  43. package/docs/Queries.md +0 -61
  44. package/docs/Readme.md +0 -2
  45. package/docs/Routing.md +0 -34
  46. package/examples/goal/index.js +0 -23
  47. package/examples/rest/index.js +0 -25
  48. package/examples/rest/node_modules/.package-lock.json +0 -40
  49. package/examples/rest/package-lock.json +0 -72
  50. package/examples/rest/package.json +0 -14
  51. package/lib/constants/ConstantsEnum.js +0 -13
  52. package/lib/controllers/Controller.js +0 -474
  53. package/lib/controllers/JWTController.js +0 -240
  54. package/lib/controllers/ServiceController.js +0 -109
  55. package/lib/controllers/WebController.js +0 -75
  56. package/lib/facades/Facade.js +0 -388
  57. package/lib/facades/FacadeParams.js +0 -11
  58. package/lib/facades/ServiceFacade.js +0 -17
  59. package/lib/facades/jwt.facade.js +0 -273
  60. package/lib/factories/responses/api.js +0 -90
  61. package/lib/models/DisabledRefreshToken.js +0 -68
  62. package/lib/models/Extractor.js +0 -320
  63. package/lib/routers/Default/index.js +0 -143
  64. package/lib/routers/Default/layer.js +0 -50
  65. package/lib/routers/Main/index.js +0 -10
  66. package/lib/routers/Roles/index.js +0 -81
  67. package/lib/utils/params.util.js +0 -19
  68. /package/lib/{application/http → http}/utils.js +0 -0
@@ -0,0 +1,456 @@
1
+ const Enum = require('../../../../enums/Enum');
2
+ const { ModelsTree, ModelsTreeNode } = require('./ModelsTree');
3
+ const util = require('util');
4
+ const debug = require('debug')('nodester:interpreter:QueryLexer');
5
+
6
+ const OP_TOKENS = new Enum({
7
+ AND: 'and',
8
+ BETWEEN: 'between',
9
+ NOT_BETWEEN: 'notBetween',
10
+ BETWEEN_MARK: '~',
11
+ OR: 'or',
12
+ OR_MARK: '|',
13
+ XOR: 'xor',
14
+ NOT: 'not',
15
+ NOT_MARK: '!',
16
+ NOT_IN: 'notIn',
17
+ LIKE: 'like',
18
+ GREATER: 'gt',
19
+ GREATER_OR_EQUAL: 'gte',
20
+ LOWER: 'lt',
21
+ LOWER_OR_EQUAL: 'lte'
22
+ });
23
+
24
+
25
+ module.exports = class QueryLexer {
26
+ constructor(queryString='') {
27
+ this.tree = new ModelsTree();
28
+ this.query = !!queryString ?
29
+ this.parse(queryString).toObject()
30
+ :
31
+ {};
32
+ }
33
+
34
+ parse(queryString='', tree=this.tree) {
35
+ if (typeof queryString !== 'string') {
36
+ const err = new TypeError(`Invalid 'queryString'.`);
37
+ throw err;
38
+ }
39
+
40
+ // You never know if it's encoded or not.
41
+ const decoded = decodeURI(queryString);
42
+
43
+ this.parseIsolatedQuery(decoded, 0, this.tree);
44
+
45
+ return this.tree.root;
46
+ }
47
+
48
+ parseIsolatedQuery(queryString='', startAt=0, tree) {
49
+ const isSubQuery = tree.node.model !== 'root';
50
+ debug({ isSubQuery, startAt });
51
+
52
+ // Token is accumulated char-by-char.
53
+ let token = '';
54
+ // Value of param ('id=10' OR 'fields=id,text').
55
+ let value = [];
56
+ // Model, that was active before cursor went up in the tree.
57
+ let previousActive = null;
58
+
59
+ for (let i=startAt; i < queryString.length; i++) {
60
+ const char = queryString[i];
61
+
62
+ // ( can mean params of OP token,
63
+ // or subquery of a model:
64
+ if (char === '(') {
65
+ debug('char', char, { token, node: tree.node });
66
+
67
+ // If OP token:
68
+ if (OP_TOKENS.asArray.indexOf(token) > -1) {
69
+ // Set operation token.
70
+ tree.node.op = this.parseOP(token);
71
+ token = '';
72
+ continue;
73
+ }
74
+ // If model subquery:
75
+ else {
76
+ const model = token;
77
+ tree.use(model) ?? tree.include(model).use(model);
78
+ token = '';
79
+
80
+ // Process subquery:
81
+ i++;
82
+ const [ charsCount ] = this.parseIsolatedQuery(queryString, i, tree);
83
+ i += charsCount;
84
+
85
+ previousActive = model;
86
+ tree.up();
87
+
88
+ continue;
89
+ }
90
+ }
91
+
92
+ // ) can mean end of OP token params,
93
+ // or end of subquery:
94
+ if (char === ')') {
95
+ debug('char', char, { token, node: tree.node });
96
+
97
+ // If end of OP token:
98
+ if (!!tree.node.op) {
99
+
100
+ // If token is empty, error:
101
+ if (token === '') {
102
+ const err = UnexpectedCharError(i, char);
103
+ throw err;
104
+ }
105
+
106
+ // Structure of a value depends on OP:
107
+ let fullOp = {};
108
+ switch (tree.node.op) {
109
+ case 'not':
110
+ case 'like':
111
+ fullOp = { [tree.node.activeParam]: { [tree.node.op]: [token] } };
112
+ break;
113
+ default:
114
+ value.push({ [tree.node.activeParam]: [token] });
115
+ fullOp = { [tree.node.op]: value };
116
+ break;
117
+ }
118
+
119
+ tree.node.addWhere(fullOp);
120
+
121
+ // Reset:
122
+ tree.node.resetOP();
123
+ tree.node.activeParam = 'includes';
124
+ token = '';
125
+ value = [];
126
+ continue;
127
+ }
128
+
129
+ // If end of subquery:
130
+ if (!!tree.node.activeParam && tree.node.activeParam !== 'includes') {
131
+ // Set value.
132
+ this.setNodeParam(tree.node, token, value);
133
+ // Reset:
134
+ tree.node.resetActiveParam();
135
+ tree.node.resetOP();
136
+ }
137
+ const numberOfProcessedChars = i - startAt;
138
+ return [ numberOfProcessedChars ];
139
+ }
140
+
141
+ // , can mean n-th value in value array,
142
+ // or horizontal include:
143
+ if (char === ',') {
144
+ debug('char', char, { token, node: tree.node });
145
+
146
+ // If OP token:
147
+ if (!!tree.node.op) {
148
+ value.push({
149
+ [tree.node.activeParam]: [token]
150
+ });
151
+ token = '';
152
+ continue;
153
+ }
154
+
155
+ // If param value:
156
+ if (tree.node.activeParam !== 'includes') {
157
+ value.push(token);
158
+ token = '';
159
+ continue;
160
+ }
161
+
162
+ // Just quit from subquery:
163
+ if (token.length === 0) {
164
+ continue;
165
+ }
166
+
167
+ // Horizontal include:
168
+ if (tree.node.activeParam === 'includes') {
169
+ const model = token;
170
+ tree.use(model) ?? tree.include(model);
171
+
172
+ token = '';
173
+ continue;
174
+ }
175
+
176
+ const err = UnexpectedCharError(i, char);
177
+ throw err;
178
+ }
179
+
180
+ // . can mean vertical include
181
+ // or it can be a part of param for "where":
182
+ if (char === '.') {
183
+ debug('char', char, { token, node: tree.node });
184
+
185
+ // Vertical include:
186
+ if (!!previousActive) {
187
+ tree.use(previousActive);
188
+ tree.node.activeParam = 'includes';
189
+ token = '';
190
+ continue;
191
+ }
192
+
193
+ // If include of new model:
194
+ if (token.length > 0) {
195
+ const model = token;
196
+ tree.use(model) ?? tree.include(model).use(model);
197
+
198
+ // Prepare for more includes:
199
+ tree.node.activeParam = 'includes';
200
+
201
+ token = '';
202
+ continue;
203
+ }
204
+
205
+ const err = UnexpectedCharError(i, char);
206
+ throw err;
207
+ }
208
+
209
+ // + can only mean horizontal include:
210
+ if (char === '+') {
211
+ debug('char', char, { token, node: tree.node });
212
+
213
+ // If include of new model:
214
+ if (token.length > 0) {
215
+ const model = token;
216
+ // Include, but do not use:
217
+ tree.use(model) ?? tree.include(model).use(model);
218
+ tree.up();
219
+
220
+ // Prepare for more includes:
221
+ tree.node.activeParam = 'includes';
222
+
223
+ token = '';
224
+ continue;
225
+ }
226
+
227
+ if (tree.node.hasParent === false) {
228
+ const err = UnexpectedCharError(i, char);
229
+ throw err;
230
+ }
231
+
232
+ tree.up();
233
+ tree.node.activeParam = 'includes';
234
+
235
+ continue;
236
+ }
237
+
238
+ // & can mean the end of key=value pair,
239
+ // or the end of subincludes:
240
+ if (char === '&') {
241
+ debug('char', char, { token, node: tree.node });
242
+
243
+ // If any OP at all:
244
+ if (!!tree.node.op) {
245
+ const err = MissingCharError(i+1, ')');
246
+ throw err;
247
+ }
248
+
249
+ // If end of key=value pair:
250
+ if (!!tree.node.activeParam && tree.node.activeParam !== 'includes') {
251
+ // Set value.
252
+ this.setNodeParam(tree.node, token, value);
253
+ // Reset:
254
+ tree.node.resetActiveParam();
255
+ token = '';
256
+ value = [];
257
+ continue;
258
+ }
259
+ else if (tree.node.activeParam === 'includes') {
260
+ // If include of new model:
261
+ if (token.length > 0) {
262
+ const model = token;
263
+ // Just include, no use.
264
+ tree.include(model);
265
+ }
266
+
267
+ // Then jump to root.
268
+ tree.upToRoot();
269
+
270
+ // Reset:
271
+ token = '';
272
+ value = [];
273
+ continue;
274
+ }
275
+
276
+ // If end of subquery:
277
+ if (tree.node.hasParent === true) {
278
+ tree.up();
279
+ continue;
280
+ }
281
+ // If root:
282
+ else {
283
+ // Reset:
284
+ tree.node.resetActiveParam();
285
+ token = '';
286
+ value = [];
287
+ continue;
288
+ }
289
+
290
+ // Unknown case:
291
+ const err = UnexpectedCharError(i, char);
292
+ throw err;
293
+ }
294
+
295
+ // [ can only mean start of 'in':
296
+ if (char === '[') {
297
+ tree.node.op = 'in';
298
+ continue;
299
+ }
300
+
301
+ // ] can only mean end if 'in':
302
+ if (char === ']') {
303
+ // User missed first '[' :
304
+ if (tree.node.op !== 'in') {
305
+ const err = UnexpectedCharError(i, char);
306
+ throw err;
307
+ }
308
+
309
+ tree.node.addWhere({
310
+ [tree.node.activeParam]: {
311
+ [tree.node.op]: value
312
+ }
313
+ });
314
+ // Reset:
315
+ tree.node.resetOP();
316
+ value = [];
317
+ token = '';
318
+ continue;
319
+ }
320
+
321
+ // = can only mean the end of param name:
322
+ if (char === '=') {
323
+ const param = this.parseParamFromToken(token);
324
+
325
+ if (isSubQuery === true && param === 'includes') {
326
+ const err = new TypeError(`'include' is forbidden inside subquery (position ${ i }). Use: 'model.model' or 'model.model+model'.`);
327
+ throw err;
328
+ }
329
+
330
+ tree.node.activeParam = param;
331
+ token = '';
332
+ continue;
333
+ }
334
+
335
+ // Continue accumulating token.
336
+ token += char;
337
+
338
+ // If last char:
339
+ if (i === queryString.length-1) {
340
+ debug('last char', { token, node: tree.node });
341
+
342
+ // haven't up from 'in':
343
+ if (tree.node.op === 'in') {
344
+ const err = MissingCharError(i+1, ']');
345
+ throw err;
346
+ }
347
+
348
+ // If any OP at all:
349
+ if (!!tree.node.op) {
350
+ const err = MissingCharError(i+1, ')');
351
+ throw err;
352
+ }
353
+
354
+ this.setNodeParam(tree.node, token, value);
355
+
356
+ // If end of subquery:
357
+ if (isSubQuery === true) {
358
+ const numberOfProcessedChars = i+1 - startAt;
359
+ return [ numberOfProcessedChars ];
360
+ }
361
+ }
362
+ }
363
+
364
+ return [ queryString.length - startAt ];
365
+ }
366
+
367
+ parseParamFromToken(token) {
368
+ switch(token) {
369
+ case 'limit':
370
+ case 'l':
371
+ return 'limit';
372
+ case 'skip':
373
+ case 's':
374
+ case 'offset':
375
+ return 'skip';
376
+ case 'order':
377
+ case 'o':
378
+ return 'order';
379
+ case 'order_by':
380
+ case 'o_by':
381
+ return 'order_by';
382
+ case 'fields':
383
+ case 'f':
384
+ return 'fields';
385
+ case 'includes':
386
+ case 'in':
387
+ return 'includes';
388
+ default:
389
+ return token;
390
+ }
391
+ }
392
+
393
+ setNodeParam(treeNode, token, value) {
394
+ const param = treeNode.activeParam;
395
+
396
+ debug(`set param ${ param }`, { token, value });
397
+
398
+ switch(param) {
399
+ case 'limit':
400
+ treeNode.limit = parseInt(token);
401
+ break;
402
+ case 'skip':
403
+ case 'offset':
404
+ treeNode.skip = parseInt(token);
405
+ break;
406
+ case 'order':
407
+ treeNode.order = token;
408
+ break;
409
+ case 'order_by':
410
+ treeNode.order_by = token;
411
+ break;
412
+ case 'fields':
413
+ if (token)
414
+ value.push(token);
415
+ treeNode.fields = value;
416
+ break;
417
+ case 'includes':
418
+ const node = new ModelsTreeNode(token);
419
+ treeNode.include(node);
420
+ break;
421
+ default:
422
+ if (token)
423
+ value.push(token);
424
+ treeNode.addWhere({ [param]: value });
425
+ break;
426
+ }
427
+ }
428
+
429
+ parseOP(opToken) {
430
+ switch(opToken) {
431
+ case '|':
432
+ case 'or':
433
+ return 'or';
434
+ case 'not':
435
+ case '!':
436
+ return 'not';
437
+ default:
438
+ return opToken;
439
+ }
440
+ }
441
+
442
+ [util.inspect.custom](depth, opts) {
443
+ return this.tree.root;
444
+ }
445
+ }
446
+
447
+
448
+ function UnexpectedCharError(index, char) {
449
+ const err = new TypeError(`Unexpected ${ char } at position ${ index }`);
450
+ return err;
451
+ }
452
+
453
+ function MissingCharError(index, char) {
454
+ const err = new TypeError(`Missing ${ char } at position ${ index }`);
455
+ return err;
456
+ }
@@ -0,0 +1,17 @@
1
+
2
+ module.exports = async function associateModels(databaseConnection) {
3
+ try {
4
+ const models = databaseConnection.models;
5
+
6
+ const modelNames = Object.keys(models);
7
+
8
+ for (let modelName of modelNames) {
9
+ await models[modelName].associate(models);
10
+ }
11
+
12
+ return Promise.resolve(models);
13
+ }
14
+ catch(error) {
15
+ return Promise.reject(error);
16
+ }
17
+ }
@@ -2,26 +2,21 @@
2
2
  const { implementsCRUD } = require('./mixins');
3
3
  // ORM.
4
4
  const { DataTypes } = require('sequelize');
5
+ // NQL.
6
+ const Colander = require('../queries/Colander');
5
7
 
6
- // Utils.
7
- // const {
8
- // pluralize,
9
- // underscore
10
- // } = require('../utils/strings.util');
11
8
 
12
-
13
- module.exports = _defineModel;
9
+ module.exports = defineModel;
14
10
 
15
11
  /*
16
- * params:
17
- * - databaseConnection (Instance of Sequilize)
18
- * - modelName (String)
19
- * - definition (function)
20
- * - options (Object)
12
+ * @param {SequilizeConnection} databaseConnection
13
+ * @param {String} modelName
14
+ * @param {Function} definition
15
+ * @param {Object} options
21
16
  * - ... Sequilize model options
22
17
  * - noCRUD (Bool)
23
18
  */
24
- function _defineModel(
19
+ function defineModel(
25
20
  databaseConnection,
26
21
  modelName='',
27
22
  definition=()=>{},
@@ -44,13 +39,23 @@ function _defineModel(
44
39
  // Add user-defined options (they can override upper ones).
45
40
  ...options
46
41
  };
42
+
47
43
  const model = databaseConnection.define(modelName, definitionObject, _options);
48
44
 
49
45
  if (options.noCRUD !== true) {
50
- // Add createWithIncludes, findById, updateById, deleteById, etc.
46
+ // Add:
47
+ // - createWithIncludes;
48
+ // - findById;
49
+ // - updateById;
50
+ // - deleteById;
51
+ // - etc.
51
52
  implementsCRUD(model);
52
53
  }
53
54
 
55
+ // Association helpers:
56
+ model.associate = (models) => {};
57
+ model.getIncludesList = _getIncludesList.bind(model);
58
+
54
59
  // Instance methods:
55
60
  model.prototype.toJSON = function() {
56
61
  const values = { ...this.get() };
@@ -60,3 +65,40 @@ function _defineModel(
60
65
 
61
66
  return model;
62
67
  }
68
+
69
+ /* Association mixins: */
70
+ function _getIncludesList(facadeData=null) {
71
+ const result = [];
72
+
73
+ const associations = this.associations;
74
+ const associationEntries = Object.entries(associations);
75
+
76
+ associationEntries.forEach(([
77
+ associationName,
78
+ associationDefinition
79
+ ]) => {
80
+ const a = { association: associationName };
81
+
82
+ if (!!facadeData) {
83
+ // If facade data is set, go deeper:
84
+ const keys = Object.keys( facadeData );
85
+ if (keys.indexOf(associationName) > 0) {
86
+ const associationModel = associationDefinition.target;
87
+
88
+ const a = { association: associationName };
89
+ if (Object.entries(associationModel.associations).length > 0) {
90
+ const deepData = facadeData[ associationName ];
91
+ a.include = associationModel.getIncludesList(Array.isArray(deepData) ? deepData[0] : deepData);
92
+ }
93
+
94
+ result.push( a );
95
+ }
96
+ }
97
+ else {
98
+ result.push( a );
99
+ }
100
+ });
101
+
102
+ return result;
103
+ }
104
+ /* Association mixins\ */