nodester 0.0.8 → 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 (37) hide show
  1. package/Readme.md +18 -59
  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/associate.js +17 -0
  14. package/lib/models/define.js +50 -1
  15. package/lib/models/mixins.js +81 -72
  16. package/lib/params/Params.js +10 -7
  17. package/lib/queries/Colander.js +84 -0
  18. package/lib/queries/traverse.js +311 -0
  19. package/lib/router/handlers.util.js +22 -2
  20. package/lib/router/index.js +96 -75
  21. package/lib/router/markers.js +78 -0
  22. package/lib/router/route.js +4 -4
  23. package/lib/router/routes.util.js +35 -5
  24. package/lib/router/utils.js +30 -0
  25. package/package.json +20 -7
  26. package/tests/nql.test.js +3 -3
  27. package/lib/_/n_controllers/Controller.js +0 -474
  28. package/lib/_/n_controllers/JWTController.js +0 -240
  29. package/lib/_/n_controllers/ServiceController.js +0 -109
  30. package/lib/_/n_controllers/WebController.js +0 -75
  31. package/lib/_facades/Facade.js +0 -388
  32. package/lib/_facades/FacadeParams.js +0 -11
  33. package/lib/_facades/ServiceFacade.js +0 -17
  34. package/lib/_facades/jwt.facade.js +0 -273
  35. package/lib/models/DisabledRefreshToken.js +0 -68
  36. package/lib/models/Extractor.js +0 -320
  37. package/lib/utils/forms.util.js +0 -22
@@ -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)
@@ -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,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;
@@ -37,13 +39,23 @@ function defineModel(
37
39
  // Add user-defined options (they can override upper ones).
38
40
  ...options
39
41
  };
42
+
40
43
  const model = databaseConnection.define(modelName, definitionObject, _options);
41
44
 
42
45
  if (options.noCRUD !== true) {
43
- // Add createWithIncludes, findById, updateById, deleteById, etc.
46
+ // Add:
47
+ // - createWithIncludes;
48
+ // - findById;
49
+ // - updateById;
50
+ // - deleteById;
51
+ // - etc.
44
52
  implementsCRUD(model);
45
53
  }
46
54
 
55
+ // Association helpers:
56
+ model.associate = (models) => {};
57
+ model.getIncludesList = _getIncludesList.bind(model);
58
+
47
59
  // Instance methods:
48
60
  model.prototype.toJSON = function() {
49
61
  const values = { ...this.get() };
@@ -53,3 +65,40 @@ function defineModel(
53
65
 
54
66
  return model;
55
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\ */
@@ -6,7 +6,11 @@ const {
6
6
  modelHasAssociations,
7
7
  getModelAssociationProps,
8
8
  compileModelAssociationData,
9
- } = require('nodester/utils/modelAssociations.util');
9
+ } = require('../utils/modelAssociations.util');
10
+
11
+ // Nodester query:
12
+ const NQLexer = require('../middlewares/ql/sequelize/interpreter/QueryLexer');
13
+ const traverseNQuery = require('../queries/traverse');
10
14
 
11
15
 
12
16
  module.exports = {
@@ -17,15 +21,15 @@ module.exports = {
17
21
  /**
18
22
  * Sets all of CRUD methods to Model.
19
23
  *
20
- * @param {Object} controller
24
+ * @param {Object} modelDefinition
21
25
  *
22
26
  *
23
27
  * @api public
24
28
  * @alias implementsCRUD
25
29
  */
26
- function _implementsCRUD(model) {
27
- if (!model) {
28
- const err = new TypeError(`'model' argument is not provided.`);
30
+ function _implementsCRUD(modelDefinition) {
31
+ if (!modelDefinition) {
32
+ const err = new TypeError(`"modelDefinition" argument is not provided.`);
29
33
  throw err;
30
34
  }
31
35
 
@@ -34,17 +38,15 @@ function _implementsCRUD(model) {
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,
@@ -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,84 @@
1
+ const CLAUSES = ['limit', 'skip', 'order', 'order_by'];
2
+
3
+
4
+ module.exports = class Colander {
5
+
6
+ /*
7
+ *
8
+ * @param {Object|Model} optsOrModelDefinition
9
+ * - @param {Array} fields
10
+ * - @param {Array} clauses
11
+ * - @param {Object} includes
12
+ * - @param {Object} statics
13
+ * -- @param {Object} attributes
14
+ * -- @param {Object} clauses
15
+ *
16
+ */
17
+ constructor(optsOrModelDefinition) {
18
+ this._fields = [];
19
+ this._clauses = [];
20
+ this._includes = {};
21
+
22
+ this._statics = {
23
+ attributes: {},
24
+ clauses: {
25
+ limit: 3
26
+ }
27
+ }
28
+
29
+ // If model:
30
+ if (!!optsOrModelDefinition.tableName && typeof optsOrModelDefinition._schema === 'object') {
31
+ this._fields = Object.keys(optsOrModelDefinition.tableAttributes);
32
+ this._clauses = CLAUSES;
33
+ }
34
+ // If options:
35
+ else {
36
+ const {
37
+ fields,
38
+ clauses,
39
+ includes,
40
+ statics,
41
+ } = optsOrModelDefinition;
42
+
43
+ if (Array.isArray(fields)) {
44
+ this._fields = fields;
45
+ }
46
+
47
+ if (Array.isArray(clauses)) {
48
+ this._clauses = clauses;
49
+ }
50
+
51
+ if (typeof includes === 'object') {
52
+ this._includes = includes;
53
+ }
54
+
55
+ if (typeof statics === 'object') {
56
+ if (typeof statics.attributes === 'object') {
57
+ this._statics.attributes = statics.attributes;
58
+ }
59
+
60
+ if (typeof statics.clauses === 'object') {
61
+ this._statics.clauses = statics.clauses;
62
+ }
63
+ }
64
+ }
65
+ }
66
+
67
+ // Getters:
68
+ get fields() {
69
+ return this._fields;
70
+ }
71
+
72
+ get clauses() {
73
+ return this._clauses;
74
+ }
75
+
76
+ get includes() {
77
+ return this._includes;
78
+ }
79
+
80
+ get statics() {
81
+ return this._statics;
82
+ }
83
+ // Getters\
84
+ }