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
@@ -0,0 +1,111 @@
1
+ const {
2
+ getOne,
3
+ getMany,
4
+ createOne,
5
+ updateOne,
6
+ deleteOne
7
+ } = require('../methods');
8
+ // Utils,
9
+ const { lowerCaseFirstLetter } = require('nodester/utils/strings');
10
+
11
+
12
+ module.exports = {
13
+ withDefaultCRUD: _withDefaultCRUD,
14
+ }
15
+
16
+ /**
17
+ * Sets one of or all of CRUD methods to Facade.
18
+ *
19
+ * @param {Function|Object} facade
20
+ * @param {Object} opts
21
+ * - @param {Function|Object} model
22
+ * - @param {String} name
23
+ * - @param {Array} only
24
+ *
25
+ * @return {Function|Object} facade
26
+ *
27
+ * @api public
28
+ * @alias withDefaultCRUD
29
+ */
30
+ function _withDefaultCRUD(facade, opts={}) {
31
+ const {
32
+ model,
33
+
34
+ // Optional:
35
+ name,
36
+ only
37
+ } = opts;
38
+
39
+ if (!facade) {
40
+ const err = new TypeError(`'facade' argument is not provided.`);
41
+ throw err;
42
+ }
43
+
44
+ // Set model info:
45
+ // Set model:
46
+ Object.defineProperty(facade, 'model', {
47
+ value: model,
48
+ writable: false
49
+ });
50
+
51
+ // Model name:
52
+ const modelName = model.options.name;
53
+ Object.defineProperty(facade, 'modelName', {
54
+ value: {
55
+ singular: lowerCaseFirstLetter(modelName.singular),
56
+ plural: lowerCaseFirstLetter(modelName.plural)
57
+ },
58
+ writable: false
59
+ });
60
+
61
+
62
+ // Set name of this facade:
63
+ Object.defineProperty(facade, 'name', {
64
+ value: name ?? `${ modelName.plural ?? facade.name }Facade`,
65
+ writable: false
66
+ });
67
+
68
+
69
+ // If only certain methods should be set:
70
+ if (!!only) {
71
+ for (const selectedMethod of only) {
72
+ switch(selectedMethod) {
73
+ case 'getOne':
74
+ facade.getOne = getOne.bind(facade);
75
+ break;
76
+ case 'getMany':
77
+ facade.getMany = getMany.bind(facade);
78
+ break;
79
+ case 'createOne':
80
+ facade.createOne = createOne.bind(facade);
81
+ break;
82
+ case 'updateOne':
83
+ facade.updateOne = updateOne.bind(facade);
84
+ break;
85
+ case 'deleteOne':
86
+ facade.deleteOne = deleteOne.bind(facade);
87
+ break;
88
+
89
+ default:
90
+ break;
91
+ }
92
+ }
93
+ }
94
+ // Or set all methods:
95
+ else {
96
+ facade.getOne = getOne.bind(facade);
97
+ facade.getMany = getMany.bind(facade);
98
+ facade.createOne = createOne.bind(facade);
99
+ facade.updateOne = updateOne.bind(facade);
100
+ facade.deleteOne = deleteOne.bind(facade);
101
+
102
+ // Set empty hooks:
103
+ facade.afterGetOne = async () => {};
104
+ facade.afterGetMany = async () => {};
105
+ facade.afterCreateOne = async () => {};
106
+ facade.afterUpdateOne = async () => {};
107
+ facade.afterDeleteOe = async () => {};
108
+ }
109
+
110
+ return facade;
111
+ }
@@ -1,3 +1,10 @@
1
+ /*!
2
+ * /nodester
3
+ * MIT Licensed
4
+ */
5
+
6
+ 'use strict';
7
+
1
8
 
2
9
  module.exports = class CustomError extends Error {
3
10
  constructor(message) {
@@ -0,0 +1,23 @@
1
+ /*!
2
+ * /nodester
3
+ * MIT Licensed
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ const {
9
+ NODESTER_QUERY_ERROR,
10
+ } = require('nodester/constants/ErrorCodes');
11
+
12
+
13
+ module.exports = class NodesterQueryError extends Error {
14
+ constructor(message) {
15
+ super(message);
16
+
17
+ this.name = NODESTER_QUERY_ERROR;
18
+ this.status = 422;
19
+
20
+ // Remove constructor info from stack.
21
+ Error.captureStackTrace(this, this.constructor);
22
+ }
23
+ }
@@ -1,9 +1,16 @@
1
- /*
2
- * Add all your custom errors in this file.
1
+ /*!
2
+ * /nodester
3
+ * MIT Licensed
3
4
  */
5
+
6
+ 'use strict';
7
+
8
+
4
9
  const Err = require('./CustomError');
10
+ const NodesterQueryError = require('./NodesterQueryError');
5
11
 
6
12
 
7
13
  module.exports = {
8
- Err
14
+ Err,
15
+ NodesterQueryError
9
16
  }
@@ -0,0 +1,28 @@
1
+ /*!
2
+ * /nodester
3
+ * MIT Licensed
4
+ */
5
+
6
+ 'use strict';
7
+
8
+
9
+ exports = module.exports = {
10
+ error: _error
11
+ }
12
+
13
+ /**
14
+ * Log error using console.error.
15
+ *
16
+ * @param {Array} args
17
+ *
18
+ * @alias error
19
+ * @public
20
+ */
21
+
22
+ function _error(...args) {
23
+ const activeEnv = process.env.NODE_ENV
24
+
25
+ if (activeEnv === 'development' || activeEnv === 'testing') {
26
+ console.error(...args);
27
+ }
28
+ }
@@ -0,0 +1,37 @@
1
+ /*!
2
+ * /nodester
3
+ * MIT Licensed
4
+ */
5
+ 'use strict';
6
+
7
+ const { formidable } = require('formidable');
8
+
9
+
10
+ module.exports = function FormidableMiddleware(formidableOptions={}) {
11
+ return async function(req, res, next) {
12
+ try {
13
+ const form = formidable(formidableOptions);
14
+ const [fields, files] = await form.parse(req);
15
+
16
+ // Add to request:
17
+ req.form = {
18
+ fields,
19
+ files
20
+ };
21
+
22
+ next();
23
+ }
24
+ catch(error) {
25
+ const statusCode = error.status || 406;
26
+ res.status(statusCode);
27
+ res.json({
28
+ error: {
29
+ message: error.message,
30
+ code: statusCode
31
+ },
32
+ status: statusCode
33
+ });
34
+ }
35
+ }
36
+ };
37
+
@@ -1,3 +1,9 @@
1
+ /*!
2
+ * /nodester
3
+ * MIT Licensed
4
+ */
5
+ 'use strict';
6
+
1
7
  const debug = require('debug')('nodester:interpreter:ModelsTree');
2
8
 
3
9
 
@@ -7,16 +13,18 @@ class ModelsTreeNode {
7
13
  this.parent = parent;
8
14
  this.activeParam = null;
9
15
  this.op = null;
16
+ this.fn = null;
10
17
 
11
18
  // for override:
12
19
  this.fields = [];
13
20
  this._where = {};
21
+ this._functions = [];
14
22
  this.skip = 0;
15
23
  this.limit = -1; // No limit
16
24
 
17
25
  this.includes = opts.includes ?? [];
18
26
  this.order = opts.order ?? 'asc';
19
- this.orderBy = opts.orderBy ?? 'id';
27
+ this.order_by = opts.order_by ?? 'id';
20
28
  }
21
29
 
22
30
  get hasParent() {
@@ -35,6 +43,10 @@ class ModelsTreeNode {
35
43
  return this._where;
36
44
  }
37
45
 
46
+ get functions() {
47
+ return this._functions;
48
+ }
49
+
38
50
  resetActiveParam() {
39
51
  this.activeParam = null;
40
52
  }
@@ -43,6 +55,10 @@ class ModelsTreeNode {
43
55
  this.op = null;
44
56
  }
45
57
 
58
+ resetFN() {
59
+ this.fn = null;
60
+ }
61
+
46
62
  addWhere(condition={}) {
47
63
  this._where = {
48
64
  ...this.where,
@@ -50,6 +66,10 @@ class ModelsTreeNode {
50
66
  }
51
67
  }
52
68
 
69
+ addFunction(fnParams={}) {
70
+ this._functions.push(fnParams);
71
+ }
72
+
53
73
  include(modelTreeNode) {
54
74
  modelTreeNode.parent = this;
55
75
  this.includes.push(modelTreeNode);
@@ -60,13 +80,16 @@ class ModelsTreeNode {
60
80
  return {
61
81
  model: this.model,
62
82
 
83
+ fields: this.fields,
84
+ functions: this.functions,
85
+
63
86
  where: this.where,
87
+
64
88
  skip: this.skip,
65
89
  limit: this.limit,
66
90
  order: this.order,
67
- orderBy: this.orderBy,
91
+ order_by: this.order_by,
68
92
 
69
- fields: this.fields,
70
93
 
71
94
  includes: this.includes.map(i => i.toObject())
72
95
  }
@@ -1,3 +1,10 @@
1
+ /*!
2
+ * /nodester
3
+ * MIT Licensed
4
+ */
5
+ 'use strict';
6
+
7
+
1
8
  const Enum = require('../../../../enums/Enum');
2
9
  const { ModelsTree, ModelsTreeNode } = require('./ModelsTree');
3
10
  const util = require('util');
@@ -21,6 +28,10 @@ const OP_TOKENS = new Enum({
21
28
  LOWER_OR_EQUAL: 'lte'
22
29
  });
23
30
 
31
+ const FN_TOKENS = new Enum({
32
+ COUNT: 'count',
33
+ });
34
+
24
35
 
25
36
  module.exports = class QueryLexer {
26
37
  constructor(queryString='') {
@@ -71,22 +82,29 @@ module.exports = class QueryLexer {
71
82
  token = '';
72
83
  continue;
73
84
  }
74
- // If model subquery:
75
- else {
76
- const model = token;
77
- tree.use(model) ?? tree.include(model).use(model);
85
+
86
+ // If FN token:
87
+ if (FN_TOKENS.asArray.indexOf(token) > -1) {
88
+ // Set function token.
89
+ tree.node.fn = token;
78
90
  token = '';
91
+ continue;
92
+ }
79
93
 
80
- // Process subquery:
81
- i++;
82
- const [ charsCount ] = this.parseIsolatedQuery(queryString, i, tree);
83
- i += charsCount;
94
+ // If model subquery:
95
+ const model = token;
96
+ tree.use(model) ?? tree.include(model).use(model);
97
+ token = '';
84
98
 
85
- previousActive = model;
86
- tree.up();
99
+ // Process subquery:
100
+ i++;
101
+ const [ charsCount ] = this.parseIsolatedQuery(queryString, i, tree);
102
+ i += charsCount;
87
103
 
88
- continue;
89
- }
104
+ previousActive = model;
105
+ tree.up();
106
+
107
+ continue;
90
108
  }
91
109
 
92
110
  // ) can mean end of OP token params,
@@ -126,6 +144,40 @@ module.exports = class QueryLexer {
126
144
  continue;
127
145
  }
128
146
 
147
+ // If end of FN token:
148
+ if (!!tree.node.fn) {
149
+ // If token is empty, error:
150
+ if (token === '') {
151
+ const err = UnexpectedCharError(i, char);
152
+ throw err;
153
+ }
154
+
155
+ let fnParams = {};
156
+ switch (tree.node.fn) {
157
+ case 'count':
158
+ fnParams = {
159
+ fn: 'count',
160
+ args: [token]
161
+ };
162
+ break;
163
+ default:
164
+ fnParams = {
165
+ fn: [tree.node.fn],
166
+ args: [token]
167
+ };
168
+ break;
169
+ }
170
+
171
+ tree.node.addFunction(fnParams);
172
+
173
+ // Reset:
174
+ tree.node.resetFN();
175
+ tree.node.activeParam = 'includes';
176
+ token = '';
177
+ value = [];
178
+ continue;
179
+ }
180
+
129
181
  // If end of subquery:
130
182
  if (!!tree.node.activeParam && tree.node.activeParam !== 'includes') {
131
183
  // Set value.
@@ -378,7 +430,7 @@ module.exports = class QueryLexer {
378
430
  return 'order';
379
431
  case 'order_by':
380
432
  case 'o_by':
381
- return 'orderBy';
433
+ return 'order_by';
382
434
  case 'fields':
383
435
  case 'f':
384
436
  return 'fields';
@@ -406,8 +458,8 @@ module.exports = class QueryLexer {
406
458
  case 'order':
407
459
  treeNode.order = token;
408
460
  break;
409
- case 'orderBy':
410
- treeNode.orderBy = token;
461
+ case 'order_by':
462
+ treeNode.order_by = token;
411
463
  break;
412
464
  case 'fields':
413
465
  if (token)
@@ -2,6 +2,8 @@
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
8
 
7
9
  module.exports = defineModel;
@@ -41,10 +43,19 @@ function defineModel(
41
43
  const model = databaseConnection.define(modelName, definitionObject, _options);
42
44
 
43
45
  if (options.noCRUD !== true) {
44
- // Add createWithIncludes, findById, updateById, deleteById, etc.
46
+ // Add:
47
+ // - createWithIncludes;
48
+ // - findById;
49
+ // - updateById;
50
+ // - deleteById;
51
+ // - etc.
45
52
  implementsCRUD(model);
46
53
  }
47
54
 
55
+ // Association helpers:
56
+ model.associate = (models) => {};
57
+ model.getIncludesList = _getIncludesList.bind(model);
58
+
48
59
  // Instance methods:
49
60
  model.prototype.toJSON = function() {
50
61
  const values = { ...this.get() };
@@ -54,3 +65,40 @@ function defineModel(
54
65
 
55
66
  return model;
56
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\ */
@@ -8,6 +8,10 @@ const {
8
8
  compileModelAssociationData,
9
9
  } = require('../utils/modelAssociations.util');
10
10
 
11
+ // Nodester query:
12
+ const NQLexer = require('../middlewares/ql/sequelize/interpreter/QueryLexer');
13
+ const traverseNQuery = require('../queries/traverse');
14
+
11
15
 
12
16
  module.exports = {
13
17
  implementsCRUD: _implementsCRUD
@@ -34,17 +38,15 @@ function _implementsCRUD(modelDefinition) {
34
38
 
35
39
  // Read:
36
40
  modelDefinition.findById = _findById.bind(modelDefinition);
37
- modelDefinition.findOneWithIncludes = _findOneWithIncludes.bind(modelDefinition);
41
+ modelDefinition.findMany = _findMany.bind(modelDefinition);
38
42
 
39
43
  // Update:
40
44
  modelDefinition.updateOne = _updateOne.bind(modelDefinition);
41
45
  modelDefinition.updateById = _updateById.bind(modelDefinition);
42
46
 
43
- // Delete.
47
+ // Delete:
48
+ modelDefinition.deleteOne = _deleteOne.bind(modelDefinition);
44
49
  modelDefinition.deleteById = _deleteById.bind(modelDefinition);
45
-
46
- // Associations:
47
- modelDefinition.getIncludesList = _getIncludesList.bind(modelDefinition);
48
50
  }
49
51
 
50
52
 
@@ -114,37 +116,76 @@ async function _createWithIncludes(
114
116
 
115
117
  function _findById(
116
118
  id=null,
117
- include=[],
118
- paranoid=true
119
+ opts={}
119
120
  ) {
120
- const query = {
121
- where: { id },
122
- include: include,
123
- paranoid: !!paranoid
124
- };
125
- return this.findOne(query);
121
+ const { query } = opts;
122
+
123
+ let _query = {};
124
+
125
+ if (typeof query === 'string') {
126
+ const lexer = new NQLexer(query);
127
+ const nquery = lexer.query;
128
+ _query = traverseNQuery(nquery, null, this);
129
+ _query.where = {
130
+ ..._query.where,
131
+ id: id
132
+ }
133
+ }
134
+ else {
135
+ const {
136
+ include,
137
+ paranoid
138
+ } = opts;
139
+
140
+ _query = {
141
+ where: { id },
142
+ include: include,
143
+ paranoid: !!paranoid
144
+ };
145
+ }
146
+
147
+ return this.findOne(_query);
126
148
  }
127
149
 
128
- function _findOneWithIncludes(
129
- where={},
130
- include=[],
131
- paranoid=true
132
- ) {
133
- const query = {
134
- where: where,
135
- include: include,
136
- paranoid: !!paranoid
137
- };
138
- return this.findOne(query);
150
+ function _findMany(opts={}) {
151
+ const { query } = opts;
152
+
153
+ let _query = {};
154
+
155
+ if (typeof query === 'string') {
156
+ const lexer = new NQLexer(query);
157
+ const nquery = lexer.query;
158
+ _query = traverseNQuery(nquery, null, this);
159
+ _query.where = {
160
+ ..._query.where,
161
+ id: id
162
+ }
163
+ }
164
+ else {
165
+ const {
166
+ include,
167
+ paranoid
168
+ } = opts;
169
+
170
+ _query = {
171
+ where: { id },
172
+ include: include,
173
+ paranoid: !!paranoid
174
+ };
175
+ }
176
+
177
+
178
+ return this.findAll(_query);
139
179
  }
140
180
 
181
+
141
182
  async function _updateOne(
142
183
  where,
143
184
  data,
144
185
  include=[]
145
186
  ) {
146
187
  try {
147
- const instance = await this.findOneWithIncludes(where, include);
188
+ const instance = await this.findOne({ where, include });
148
189
 
149
190
  if (!instance) {
150
191
  const err = new Error(`Model not found`);
@@ -201,7 +242,7 @@ async function _updateOne(
201
242
 
202
243
  // Select this instance again, if includes was set:
203
244
  if (include?.length > 0) {
204
- const updatedInstance = await this.findOneWithIncludes(where, include);
245
+ const updatedInstance = await this.findOne({ where, include });
205
246
  fullInstanceData = updatedInstance.toJSON();
206
247
  }
207
248
 
@@ -225,6 +266,15 @@ async function _updateById(
225
266
  );
226
267
  }
227
268
 
269
+
270
+ function _deleteOne(query={}) {
271
+ const _query = {
272
+ ...query,
273
+ limit: 1
274
+ }
275
+ return this.destroy(_query);
276
+ }
277
+
228
278
  function _deleteById(
229
279
  id=null
230
280
  ) {
@@ -235,47 +285,6 @@ function _deleteById(
235
285
  }
236
286
  /* Main mixinis\ */
237
287
 
238
- /* Association mixins: */
239
- function _getIncludesList(facadeData=null) {
240
- // console.log({ facadeData, model: this });
241
-
242
- const result = [];
243
-
244
- const associations = this.associations;
245
- const associationEntries = Object.entries(associations);
246
-
247
- associationEntries.forEach(([
248
- associationName,
249
- associationDefinition
250
- ]) => {
251
- const a = { association: associationName };
252
-
253
- if (!!facadeData) {
254
- // If facade data is set, go deeper:
255
- const keys = Object.keys( facadeData );
256
- if (keys.indexOf(associationName) > 0) {
257
- const associationModel = associationDefinition.target;
258
-
259
- const a = { association: associationName };
260
- if (Object.entries(associationModel.associations).length > 0) {
261
- const deepData = facadeData[ associationName ];
262
- a.include = associationModel.getIncludesList(Array.isArray(deepData) ? deepData[0] : deepData);
263
- }
264
-
265
- result.push( a );
266
- }
267
- }
268
- else {
269
- result.push( a );
270
- }
271
- });
272
-
273
- // console.log('getIncludesList', result);
274
-
275
- return result;
276
- }
277
- /* Association mixins\ */
278
-
279
288
  /* Subfunctions: */
280
289
  async function _updateOrCreateOrDelete(
281
290
  modelDefinition,