nodester 0.0.9 → 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.
Files changed (35) hide show
  1. package/Readme.md +14 -1
  2. package/lib/application/index.js +28 -7
  3. package/lib/controllers/methods/index.js +34 -10
  4. package/lib/controllers/mixins/index.js +14 -5
  5. package/lib/database/connection.js +34 -0
  6. package/lib/database/migration.js +42 -0
  7. package/lib/database/utils.js +19 -0
  8. package/lib/facades/methods/index.js +173 -0
  9. package/lib/facades/mixins/index.js +111 -0
  10. package/lib/middlewares/formidable/index.js +37 -0
  11. package/lib/middlewares/ql/sequelize/interpreter/ModelsTree.js +2 -2
  12. package/lib/middlewares/ql/sequelize/interpreter/QueryLexer.js +3 -3
  13. package/lib/models/define.js +49 -1
  14. package/lib/models/mixins.js +76 -67
  15. package/lib/params/Params.js +10 -7
  16. package/lib/queries/Colander.js +84 -0
  17. package/lib/queries/traverse.js +311 -0
  18. package/lib/router/handlers.util.js +22 -2
  19. package/lib/router/index.js +96 -75
  20. package/lib/router/markers.js +78 -0
  21. package/lib/router/route.js +4 -4
  22. package/lib/router/routes.util.js +35 -5
  23. package/lib/router/utils.js +30 -0
  24. package/package.json +19 -7
  25. package/tests/nql.test.js +3 -3
  26. package/lib/_/n_controllers/Controller.js +0 -474
  27. package/lib/_/n_controllers/JWTController.js +0 -240
  28. package/lib/_/n_controllers/ServiceController.js +0 -109
  29. package/lib/_/n_controllers/WebController.js +0 -75
  30. package/lib/_facades/Facade.js +0 -388
  31. package/lib/_facades/FacadeParams.js +0 -11
  32. package/lib/_facades/ServiceFacade.js +0 -17
  33. package/lib/_facades/jwt.facade.js +0 -273
  34. package/lib/models/Extractor.js +0 -320
  35. package/lib/utils/forms.util.js +0 -22
package/Readme.md CHANGED
@@ -28,9 +28,22 @@ app.listen(8080, function() {
28
28
 
29
29
  ## Documentation
30
30
 
31
+
31
32
  ### Core concepts
32
33
  [Go to core concepts documentaion](docs/CoreConcepts.md)
33
34
 
35
+ ### Queries & Querying - Nodester Query Language (NQR)
36
+ 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:
37
+
38
+ [Go to NQR documentaion](docs/Queries.md)
39
+
40
+
41
+ ### Database
42
+ Nodester is built upon a powerful [Sequelize](https://sequelize.org/).
43
+ Supported drivers:
44
+ - MySQL
45
+ - PostgreSQL
46
+
34
47
 
35
48
  ### Extending Application functionality
36
49
 
@@ -95,7 +108,7 @@ The Philosophy of `nodester` is to provide a developer with a tool that can buil
95
108
 
96
109
  ### Goal
97
110
 
98
- The goal of `nodester` is to be a robust and flexible framework that makes iterative development easy.
111
+ The goal of `nodester` is to be a robust and flexible framework that makes development in iteratations easy, and further scale possible.
99
112
 
100
113
 
101
114
  ## LICENSE
@@ -7,17 +7,25 @@
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
30
  const consl = require('../logger/console');
23
31
  const debug = require('debug')('nodester:application');
@@ -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
 
@@ -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);
@@ -134,6 +134,7 @@ function _withDefaultErrorProcessing(controller, opts={}) {
134
134
 
135
135
  // Set processError:
136
136
  controller.processError = function (error, req, res) {
137
+
137
138
  // Default error message.
138
139
  let errorMessage = error?.message ?? 'Internal server error';
139
140
  // Default HTTP status code.
@@ -169,23 +170,31 @@ function _withDefaultErrorProcessing(controller, opts={}) {
169
170
  }
170
171
  case('InternalValidationError'): {
171
172
  statusCode = 500;
172
- errorResponse.details = { message:'Error' };
173
+ errorResponse.details = { message: 'Error' };
173
174
  break;
174
175
  }
175
176
  default: {
176
- errorResponse.details = { message:errorMessage };
177
+ errorResponse.details = { message: errorMessage };
177
178
  break;
178
179
  }
179
180
  }
180
181
 
181
- // Send error response with provided status code.
182
- return this.respondNotOk(res, {
182
+ // Send error response with provided status code:
183
+ const data = {
183
184
  error: {
184
185
  ...errorResponse,
185
186
  code: statusCode
186
187
  },
187
188
  status: statusCode
188
- });
189
+ }
190
+
191
+ if (!!this.respondNotOk) {
192
+ return this.respondNotOk(res, data);
193
+ }
194
+
195
+ // Barebones response:
196
+ res.status(statusCode);
197
+ res.json(data);
189
198
  }
190
199
 
191
200
  return controller;
@@ -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,173 @@
1
+ const Params = require('nodester/params');
2
+
3
+
4
+ module.exports = {
5
+ getOne: _getOne,
6
+ getMany: _getMany,
7
+ createOne: _createOne,
8
+ updateOne: _updateOne,
9
+ deleteOne: _deleteOne
10
+ }
11
+
12
+
13
+ /*
14
+ *
15
+ * @param {Object} params
16
+ *
17
+ * @alias getOne
18
+ * @api public
19
+ */
20
+ async function _getOne(params) {
21
+ try {
22
+ const {
23
+ query
24
+ } = Params(params, {
25
+ query: {}
26
+ });
27
+
28
+ const instance = await this.model.findOne(query);
29
+
30
+ const result = {
31
+ [this.modelName.singular]: instance,
32
+ count: 0 + instance !== null
33
+ }
34
+ return Promise.resolve(result);
35
+ }
36
+ catch(error) {
37
+ return Promise.reject(error);
38
+ }
39
+ }
40
+
41
+
42
+ /*
43
+ *
44
+ * @param {Object} params
45
+ *
46
+ * @alias getMany
47
+ * @api public
48
+ */
49
+ async function _getMany(params) {
50
+ try {
51
+ const {
52
+ query
53
+ } = Params(params, {
54
+ query: {}
55
+ });
56
+
57
+ const instances = await this.model.findAll(query);
58
+
59
+ const result = {
60
+ [this.modelName.plural]: instances,
61
+ count: instances.length
62
+ }
63
+ return Promise.resolve(result);
64
+ }
65
+ catch(error) {
66
+ return Promise.reject(error);
67
+ }
68
+ }
69
+
70
+
71
+ /*
72
+ *
73
+ * @param {Object} params
74
+ *
75
+ * @alias createOne
76
+ * @api public
77
+ */
78
+ async function _createOne(params) {
79
+ try {
80
+ const {
81
+ data,
82
+ includes,
83
+ } = Params(params, {
84
+ data: null,
85
+ includes: null,
86
+ });
87
+
88
+ const instance = await this.model.create({ ...data }, {
89
+ include: this.model.getIncludesList(data)
90
+ });
91
+
92
+ // If includes are set, "find" this record with includes:
93
+ if (!!includes && includes?.length > 0) {
94
+ await instance.reload({ include: includes });
95
+ }
96
+
97
+ const result = {
98
+ [this.modelName.singular]: instance,
99
+ count: instance === null ? 0 : 1
100
+ }
101
+
102
+ // Call after create.
103
+ await this.afterCreateOne(instance, params, result);
104
+
105
+ return Promise.resolve(result);
106
+ }
107
+ catch(error) {
108
+ return Promise.reject(error);
109
+ }
110
+ }
111
+
112
+
113
+ /*
114
+ *
115
+ * @param {Object} params
116
+ *
117
+ * @alias updateOne
118
+ * @api public
119
+ */
120
+ async function _updateOne(params) {
121
+ try {
122
+ const {
123
+ query,
124
+ data
125
+ } = Params(params, {
126
+ query: {},
127
+ data: null
128
+ });
129
+
130
+ const updateResult = await this.model.updateOne(query.where, data);;
131
+
132
+ const [ isNewRecord, instance ] = updateResult;
133
+
134
+ const result = {
135
+ success: isNewRecord === false,
136
+ [this.modelName.singular]: instance,
137
+ count: !!instance ? 1 : 0
138
+ }
139
+ return Promise.resolve(result);
140
+ }
141
+ catch(error) {
142
+ return Promise.reject(error);
143
+ }
144
+ }
145
+
146
+
147
+ /*
148
+ *
149
+ * @param {Object} params
150
+ *
151
+ * @alias deleteOne
152
+ * @api public
153
+ */
154
+ async function _deleteOne(params) {
155
+ try {
156
+ const {
157
+ query
158
+ } = Params(params, {
159
+ query: null
160
+ });
161
+
162
+ const count = await this.model.deleteOne(query);
163
+
164
+ const result = {
165
+ success: count > 0,
166
+ count: count
167
+ };
168
+ return Promise.resolve(result);
169
+ }
170
+ catch(error) {
171
+ return Promise.reject(error);
172
+ }
173
+ }
@@ -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
+ }
@@ -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
+
@@ -16,7 +16,7 @@ class ModelsTreeNode {
16
16
 
17
17
  this.includes = opts.includes ?? [];
18
18
  this.order = opts.order ?? 'asc';
19
- this.orderBy = opts.orderBy ?? 'id';
19
+ this.order_by = opts.order_by ?? 'id';
20
20
  }
21
21
 
22
22
  get hasParent() {
@@ -64,7 +64,7 @@ class ModelsTreeNode {
64
64
  skip: this.skip,
65
65
  limit: this.limit,
66
66
  order: this.order,
67
- orderBy: this.orderBy,
67
+ order_by: this.order_by,
68
68
 
69
69
  fields: this.fields,
70
70
 
@@ -378,7 +378,7 @@ module.exports = class QueryLexer {
378
378
  return 'order';
379
379
  case 'order_by':
380
380
  case 'o_by':
381
- return 'orderBy';
381
+ return 'order_by';
382
382
  case 'fields':
383
383
  case 'f':
384
384
  return 'fields';
@@ -406,8 +406,8 @@ module.exports = class QueryLexer {
406
406
  case 'order':
407
407
  treeNode.order = token;
408
408
  break;
409
- case 'orderBy':
410
- treeNode.orderBy = token;
409
+ case 'order_by':
410
+ treeNode.order_by = token;
411
411
  break;
412
412
  case 'fields':
413
413
  if (token)