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
package/Readme.md CHANGED
@@ -28,8 +28,22 @@ app.listen(8080, function() {
28
28
 
29
29
  ## Documentation
30
30
 
31
+
31
32
  ### Core concepts
32
- [Go to core concepts documentaion](docs/CoreConcepts.md)
33
+ [Core concepts documentation ➡️](docs/CoreConcepts.md)
34
+
35
+
36
+ ### Queries & Querying - Nodester Query Language (NQR)
37
+ One of the main power points of nodester is it's query language. It's an extension of a REST API syntaxis for a broader integration with a database SQL. Read more about it in the documentation:
38
+
39
+ [NQR documentaion ➡️](docs/Queries.md)
40
+
41
+
42
+ ### Database
43
+ Nodester is built upon a powerful [Sequelize](https://sequelize.org/).
44
+ Supported drivers:
45
+ - MySQL
46
+ - PostgreSQL
33
47
 
34
48
 
35
49
  ### Extending Application functionality
@@ -95,7 +109,7 @@ The Philosophy of `nodester` is to provide a developer with a tool that can buil
95
109
 
96
110
  ### Goal
97
111
 
98
- The goal of `nodester` is to be a robust and flexible framework that makes iterative development easy.
112
+ The goal of `nodester` is to be a robust and flexible framework that makes development in iteratations easy, and further scale possible.
99
113
 
100
114
 
101
115
  ## LICENSE
@@ -7,19 +7,27 @@
7
7
 
8
8
  const Emitter = require('events');
9
9
  const DefaultRouter = require('../router');
10
+
10
11
  // Server:
11
12
  const http = require('http');
12
13
  const request = require('../http/request');
13
14
  const response = require('../http/response');
15
+
14
16
  // Middlewares:
15
17
  const nodesterQL = require('../middlewares/ql/sequelize');
18
+ const bodyParser = require('body-parser');
19
+
16
20
  // Utils:
17
21
  const {
18
22
  typeOf,
19
23
  isConstructor
20
24
  } = require('../utils/types.util');
25
+ const {
26
+ associateModels
27
+ } = require('../database/utils');
28
+
21
29
  const { merge } = require('../utils/objects.util');
22
- const consl = require('../logger/console');
30
+ const consl = require('nodester/loggers/console');
23
31
  const debug = require('debug')('nodester:application');
24
32
 
25
33
 
@@ -52,6 +60,13 @@ module.exports = class Application extends Emitter {
52
60
  beforeStart: ()=>{}
53
61
  };
54
62
 
63
+ // Default middlewares:
64
+ const _withoutMiddlewares = opts?.middlewares?.without ?? [];
65
+
66
+ if (_withoutMiddlewares.indexOf('body-parser') === -1) {
67
+ this.use(bodyParser.json());
68
+ }
69
+
55
70
  // Indicatorors.
56
71
  this.isListening = false;
57
72
  }
@@ -104,7 +119,7 @@ module.exports = class Application extends Emitter {
104
119
  *
105
120
  * @public
106
121
  */
107
- setDatabase(sequilizeConnection, crashOnError=true) {
122
+ async setDatabase(sequilizeConnection, crashOnError=true) {
108
123
  try {
109
124
  if (!sequilizeConnection) {
110
125
  const err = new TypeError('Connection to database (Sequilize) can not be null or undefined.');
@@ -116,10 +131,14 @@ module.exports = class Application extends Emitter {
116
131
  throw err;
117
132
  }
118
133
 
119
- const result = sequilizeConnection.authenticate();
134
+ // Test connection.
135
+ const result = await sequilizeConnection.authenticate();
136
+ // Associate models.
137
+ await associateModels(sequilizeConnection.models);
138
+ // Set database.
120
139
  this._database = sequilizeConnection;
121
140
 
122
- return result;
141
+ return Promise.resolve(result);
123
142
  }
124
143
  catch(error) {
125
144
  if (crashOnError === true) {
@@ -128,6 +147,8 @@ module.exports = class Application extends Emitter {
128
147
  else {
129
148
  consl.error(error);
130
149
  }
150
+
151
+ return Promise.reject(error);
131
152
  }
132
153
  }
133
154
 
@@ -188,14 +209,14 @@ module.exports = class Application extends Emitter {
188
209
 
189
210
 
190
211
  /*
191
- * Proxy to .add.middleware()
212
+ * Proxy to router.use()
192
213
  *
193
- * @param {Function} fn
214
+ * @param {Function|NodesterRouter} fnOrRouter
194
215
  *
195
216
  * @api public
196
217
  */
197
- use(fn) {
198
- return this._router.add.middleware(fn);
218
+ use(fnOrRouter) {
219
+ return this._router.use(fnOrRouter);
199
220
  }
200
221
 
201
222
 
@@ -0,0 +1,19 @@
1
+ const Enum = require('nodester/enum');
2
+
3
+ const NODESTER_QUERY_ERROR = 'NodesterQueryError';
4
+ const UNAUTHORIZED_ERROR = 'Unauthorized';
5
+ const NOT_FOUND_ERROR = 'NotFound';
6
+ const VALIDATION_ERROR = 'ValidationError';
7
+ const CONFLICT_ERROR = 'ConflictError';
8
+ const SEQUELIZE_UNIQUE_CONSTRAINT_ERROR = 'SequelizeUniqueConstraintError';
9
+ const INTERNAL_VALIDATION_ERROR = 'InternalValidationError';
10
+
11
+ module.exports = new Enum({
12
+ NODESTER_QUERY_ERROR,
13
+ UNAUTHORIZED_ERROR,
14
+ NOT_FOUND_ERROR,
15
+ VALIDATION_ERROR,
16
+ CONFLICT_ERROR,
17
+ SEQUELIZE_UNIQUE_CONSTRAINT_ERROR,
18
+ INTERNAL_VALIDATION_ERROR,
19
+ });
@@ -1,4 +1,4 @@
1
- const Enum = require('../enums/Enum');
1
+ const Enum = require('nodester/enum');
2
2
 
3
3
 
4
4
  module.exports = new Enum({
@@ -14,8 +14,13 @@ module.exports = {
14
14
  */
15
15
  async function _getOne(req, res) {
16
16
  try {
17
-
18
- const result = await this.facade.getOne();
17
+ const params = {
18
+ query: {
19
+ ...req.query,
20
+ where: req.params
21
+ }
22
+ }
23
+ const result = await this.facade.getOne(params);
19
24
 
20
25
  if (this.afterGetOne) {
21
26
  await this.afterGetOne(req, res, result);
@@ -26,7 +31,6 @@ async function _getOne(req, res) {
26
31
  });
27
32
  }
28
33
  catch(error) {
29
- console.error('getOne', error);
30
34
  if (this.processError) {
31
35
  return this.processError(error, req, res);
32
36
  }
@@ -47,7 +51,10 @@ async function _getOne(req, res) {
47
51
  */
48
52
  async function _getMany(req, res) {
49
53
  try {
50
- const result = await this.facade.getMany();
54
+ const params = {
55
+ query: req.query
56
+ }
57
+ const result = await this.facade.getMany(params);
51
58
 
52
59
  if (this.afterGetMany) {
53
60
  await this.afterGetMany(req, res, result);
@@ -78,8 +85,14 @@ async function _getMany(req, res) {
78
85
  */
79
86
  async function _createOne(req, res) {
80
87
  try {
81
-
82
- const result = await this.facade.createOne();
88
+ const params = {
89
+ query: {
90
+ ...req.query,
91
+ where: req.params
92
+ },
93
+ data: req.body,
94
+ }
95
+ const result = await this.facade.createOne(params);
83
96
 
84
97
  if (this.afterCreateOne) {
85
98
  await this.afterCreateOne(req, res, result);
@@ -110,8 +123,14 @@ async function _createOne(req, res) {
110
123
  */
111
124
  async function _updateOne(req, res) {
112
125
  try {
113
-
114
- const result = await this.facade.updateOne();
126
+ const params = {
127
+ query: {
128
+ ...req.query,
129
+ where: req.params
130
+ },
131
+ data: req.body,
132
+ }
133
+ const result = await this.facade.updateOne(params);
115
134
 
116
135
  if (this.afterUpdateOne) {
117
136
  await this.afterUpdateOne(req, res, result);
@@ -142,8 +161,13 @@ async function _updateOne(req, res) {
142
161
  */
143
162
  async function _deleteOne(req, res) {
144
163
  try {
145
-
146
- const result = await this.facade.deleteOne();
164
+ const params = {
165
+ query: {
166
+ ...req.query,
167
+ where: req.params
168
+ }
169
+ }
170
+ const result = await this.facade.deleteOne(params);
147
171
 
148
172
  if (this.afterDeleteOn) {
149
173
  await this.afterDeleteOne(req, res, result);
@@ -1,3 +1,13 @@
1
+ const {
2
+ NODESTER_QUERY_ERROR,
3
+ UNAUTHORIZED_ERROR,
4
+ NOT_FOUND_ERROR,
5
+ VALIDATION_ERROR,
6
+ CONFLICT_ERROR,
7
+ SEQUELIZE_UNIQUE_CONSTRAINT_ERROR,
8
+ INTERNAL_VALIDATION_ERROR
9
+ } = require('nodester/constants/ErrorCodes');
10
+
1
11
  const {
2
12
  getOne,
3
13
  getMany,
@@ -10,6 +20,8 @@ const {
10
20
  module.exports = {
11
21
  withDefaultCRUD: _withDefaultCRUD,
12
22
  withDefaultErrorProcessing: _withDefaultErrorProcessing,
23
+
24
+ setFacade: _setFacade,
13
25
  setMethod: _setMethod
14
26
  }
15
27
 
@@ -42,18 +54,7 @@ function _withDefaultCRUD(controller, opts={}) {
42
54
  throw err;
43
55
  }
44
56
 
45
- if (!facade) {
46
- const err = new TypeError(`'opts.facade' argument is invalid.`);
47
- throw err;
48
- }
49
-
50
- // Set main facade:
51
- if (facade.constructor.name === 'Function') {
52
- controller.facade = new facade();
53
- }
54
- else {
55
- controller.facade = facade;
56
- }
57
+ _setFacade(controller, facade);
57
58
 
58
59
  // Set model info:
59
60
  const model = facade.model;
@@ -134,6 +135,7 @@ function _withDefaultErrorProcessing(controller, opts={}) {
134
135
 
135
136
  // Set processError:
136
137
  controller.processError = function (error, req, res) {
138
+
137
139
  // Default error message.
138
140
  let errorMessage = error?.message ?? 'Internal server error';
139
141
  // Default HTTP status code.
@@ -142,56 +144,102 @@ function _withDefaultErrorProcessing(controller, opts={}) {
142
144
  let errorResponse = {};
143
145
 
144
146
  switch(error.name) {
145
- case('Unauthorized'): {
147
+ case UNAUTHORIZED_ERROR: {
146
148
  statusCode = 401;
147
149
  errorResponse.details = { message: 'Unauthorized' };
148
150
  break;
149
151
  }
150
- case('NotFound'): {
152
+ case NOT_FOUND_ERROR: {
151
153
  statusCode = 404;
152
154
  errorResponse.details = { message: errorMessage };
153
155
  break;
154
156
  }
155
- case('ValidationError'): {
156
- statusCode = 406;
157
+ case NODESTER_QUERY_ERROR: {
158
+ statusCode = 422;
159
+ errorResponse.details = { message: errorMessage };
160
+ break;
161
+ }
162
+ case VALIDATION_ERROR: {
163
+ statusCode = 422;
157
164
  errorResponse.details = error?.details;
158
165
  break;
159
166
  }
160
- case('ConflictError'): {
167
+ case CONFLICT_ERROR: {
161
168
  statusCode = 409;
162
169
  errorResponse.details = error?.details ?? error?.message;
163
170
  break;
164
171
  }
165
- case('SequelizeUniqueConstraintError'): {
172
+ case SEQUELIZE_UNIQUE_CONSTRAINT_ERROR: {
166
173
  statusCode = 409;
167
174
  errorResponse.details = error?.errors;
168
175
  break;
169
176
  }
170
- case('InternalValidationError'): {
177
+ case INTERNAL_VALIDATION_ERROR: {
171
178
  statusCode = 500;
172
- errorResponse.details = { message:'Error' };
179
+ errorResponse.details = { message: errorMessage };
173
180
  break;
174
181
  }
175
182
  default: {
176
- errorResponse.details = { message:errorMessage };
183
+ errorResponse.details = { message: errorMessage };
177
184
  break;
178
185
  }
179
186
  }
180
187
 
181
- // Send error response with provided status code.
182
- return this.respondNotOk(res, {
188
+ // Send error response with provided status code:
189
+ const data = {
183
190
  error: {
184
191
  ...errorResponse,
185
192
  code: statusCode
186
193
  },
187
194
  status: statusCode
188
- });
195
+ }
196
+
197
+ if (!!this.respondNotOk) {
198
+ return this.respondNotOk(res, data);
199
+ }
200
+
201
+ // Barebones response:
202
+ res.status(statusCode);
203
+ res.json(data);
189
204
  }
190
205
 
191
206
  return controller;
192
207
  }
193
208
 
194
209
 
210
+ /**
211
+ * Sets one of or all of CRUD methods to Controller.
212
+ *
213
+ * @param {Function|Object} controller
214
+ * @param {Function|Object} facade
215
+ * @param {String} facadeName
216
+ *
217
+ * @return {Function|Object} controller
218
+ *
219
+ * @api public
220
+ * @alias setFacade
221
+ */
222
+ function _setFacade(controller, facade, facadeName=null) {
223
+ if (!controller) {
224
+ const err = new TypeError(`'controller' argument is not provided.`);
225
+ throw err;
226
+ }
227
+
228
+ if (!facade) {
229
+ const err = new TypeError(`'facade' argument is invalid.`);
230
+ throw err;
231
+ }
232
+
233
+ // Set main facade:
234
+ if (facade.constructor.name === 'Function') {
235
+ controller[facadeName ?? 'facade'] = new facade();
236
+ }
237
+ else {
238
+ controller[facadeName ?? 'facade'] = facade;
239
+ }
240
+ }
241
+
242
+
195
243
  /**
196
244
  * Sets one of CRUD methods to Controller.
197
245
  *
@@ -0,0 +1,34 @@
1
+ // ORM.
2
+ const Sequelize = require('sequelize');
3
+
4
+
5
+ module.exports = {
6
+ buildConnection: _buildConnection
7
+ };
8
+
9
+ function _buildConnection(opts={}) {
10
+
11
+
12
+ const dbName = opts.name;
13
+ const username = opts.username;
14
+ const password = opts.password;
15
+
16
+
17
+ const connection = new Sequelize(
18
+ dbName,
19
+ username,
20
+ password,
21
+ {
22
+ host: opts.host,
23
+ port: opts.port,
24
+ dialect: opts.dialect,
25
+ pool: opts.pool,
26
+ charset: opts.charset,
27
+ collate: opts.collate,
28
+ timestamps: opts.timestamps,
29
+ logging: opts.logging
30
+ }
31
+ );
32
+
33
+ return connection;
34
+ }
@@ -0,0 +1,42 @@
1
+ const { associateModels } = require('./utils');
2
+
3
+
4
+ module.exports = {
5
+ migrate: _migrate
6
+ }
7
+
8
+ async function _migrate(databaseConnection, force=false) {
9
+ try {
10
+ // Validation of 'force' parameter.
11
+ if (typeof force !== 'boolean') {
12
+ const err = new Error('Wrong "force" parameter; must be boolean.');
13
+ throw err;
14
+ }
15
+
16
+ // Test connection.
17
+ await databaseConnection.authenticate();
18
+
19
+
20
+ const models = databaseConnection.models;
21
+ const modelNames = Object.keys(models);
22
+ console.info('Models to sync:', modelNames);
23
+ console.info('Forcefully?', force);
24
+ console.info('Syncing...\n');
25
+ await associateModels(models);
26
+ await databaseConnection.sync({ force });
27
+ console.info('Successful migration!');
28
+
29
+ const output = {
30
+ synced: true,
31
+ modelNames: modelNames,
32
+ models: models
33
+ }
34
+ return Promise.resolve(output);
35
+ }
36
+ catch(error) {
37
+ console.error('Migration failed!');
38
+ console.error(error);
39
+ return Promise.reject(error);
40
+ }
41
+ }
42
+
@@ -0,0 +1,19 @@
1
+
2
+ module.exports = {
3
+ associateModels: _associateModels
4
+ }
5
+
6
+ async function _associateModels(models) {
7
+ return new Promise((resolve, reject) => {
8
+ try {
9
+ Object.keys(models).map(modelName => (
10
+ models[modelName].associate(models)
11
+ ));
12
+
13
+ return resolve(models);
14
+ }
15
+ catch(error) {
16
+ reject(error);
17
+ }
18
+ });
19
+ }
@@ -0,0 +1,180 @@
1
+ const Params = require('nodester/params');
2
+
3
+ const log = require('nodester/loggers/dev');
4
+
5
+
6
+ module.exports = {
7
+ getOne: _getOne,
8
+ getMany: _getMany,
9
+ createOne: _createOne,
10
+ updateOne: _updateOne,
11
+ deleteOne: _deleteOne
12
+ }
13
+
14
+
15
+ /*
16
+ *
17
+ * @param {Object} params
18
+ *
19
+ * @alias getOne
20
+ * @api public
21
+ */
22
+ async function _getOne(params) {
23
+ try {
24
+ const {
25
+ query
26
+ } = Params(params, {
27
+ query: {}
28
+ });
29
+
30
+ const instance = await this.model.findOne(query);
31
+
32
+ const result = {
33
+ [this.modelName.singular]: instance,
34
+ count: 0 + (instance !== null)
35
+ }
36
+ return Promise.resolve(result);
37
+ }
38
+ catch(error) {
39
+ log.error(`${ [this.modelName.singular] }Facade.getOne error:`, error);
40
+ return Promise.reject(error);
41
+ }
42
+ }
43
+
44
+
45
+ /*
46
+ *
47
+ * @param {Object} params
48
+ *
49
+ * @alias getMany
50
+ * @api public
51
+ */
52
+ async function _getMany(params) {
53
+ try {
54
+ const {
55
+ query
56
+ } = Params(params, {
57
+ query: {}
58
+ });
59
+
60
+ const instances = await this.model.findAll(query);
61
+
62
+ const result = {
63
+ [this.modelName.plural]: instances,
64
+ count: instances.length
65
+ }
66
+ return Promise.resolve(result);
67
+ }
68
+ catch(error) {
69
+ log.error(`${ [this.modelName.singular] }Facade.getMany error:`, error);
70
+ return Promise.reject(error);
71
+ }
72
+ }
73
+
74
+
75
+ /*
76
+ *
77
+ * @param {Object} params
78
+ *
79
+ * @alias createOne
80
+ * @api public
81
+ */
82
+ async function _createOne(params) {
83
+ try {
84
+ const {
85
+ data,
86
+ includes,
87
+ } = Params(params, {
88
+ data: null,
89
+ includes: null,
90
+ });
91
+
92
+ const instance = await this.model.create({ ...data }, {
93
+ include: this.model.getIncludesList(data)
94
+ });
95
+
96
+ // If includes are set, "find" this record with includes:
97
+ if (!!includes && includes?.length > 0) {
98
+ await instance.reload({ include: includes });
99
+ }
100
+
101
+ const result = {
102
+ [this.modelName.singular]: instance,
103
+ count: 0 + (instance !== null)
104
+ }
105
+
106
+ // Call after create.
107
+ await this.afterCreateOne(instance, params, result);
108
+
109
+ return Promise.resolve(result);
110
+ }
111
+ catch(error) {
112
+ log.error(`${ [this.modelName.singular] }Facade.createOne error:`, error);
113
+ return Promise.reject(error);
114
+ }
115
+ }
116
+
117
+
118
+ /*
119
+ *
120
+ * @param {Object} params
121
+ *
122
+ * @alias updateOne
123
+ * @api public
124
+ */
125
+ async function _updateOne(params) {
126
+ try {
127
+ const {
128
+ query,
129
+ data
130
+ } = Params(params, {
131
+ query: {},
132
+ data: null
133
+ });
134
+
135
+ const updateResult = await this.model.updateOne(query.where, data);;
136
+
137
+ const [ isNewRecord, instance ] = updateResult;
138
+
139
+ const result = {
140
+ success: isNewRecord === false,
141
+ [this.modelName.singular]: instance,
142
+ count: 0 + (instance !== null)
143
+ }
144
+ return Promise.resolve(result);
145
+ }
146
+ catch(error) {
147
+ log.error(`${ [this.modelName.singular] }Facade.updateOne error:`, error);
148
+ return Promise.reject(error);
149
+ }
150
+ }
151
+
152
+
153
+ /*
154
+ *
155
+ * @param {Object} params
156
+ *
157
+ * @alias deleteOne
158
+ * @api public
159
+ */
160
+ async function _deleteOne(params) {
161
+ try {
162
+ const {
163
+ query
164
+ } = Params(params, {
165
+ query: null
166
+ });
167
+
168
+ const count = await this.model.deleteOne(query);
169
+
170
+ const result = {
171
+ success: count > 0,
172
+ count: count
173
+ };
174
+ return Promise.resolve(result);
175
+ }
176
+ catch(error) {
177
+ log.error(`${ [this.modelName.singular] }Facade.deleteOne error:`, error);
178
+ return Promise.reject(error);
179
+ }
180
+ }