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
@@ -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,
@@ -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
+ }
@@ -0,0 +1,311 @@
1
+ const { Op } = require('sequelize');
2
+
3
+
4
+ module.exports = traverse;
5
+
6
+ function traverse(queryNode, colander=null, model) {
7
+
8
+ const fieldsAvailable = Object.keys(model.tableAttributes);
9
+ const includesAvailable = model.getIncludesList();
10
+
11
+ const newQuery = {
12
+ attributes: [],
13
+ where: {},
14
+ include: []
15
+ };
16
+
17
+ const {
18
+ where,
19
+ includes,
20
+ fields,
21
+ clauses,
22
+ } = _disassembleQueryNode(queryNode);
23
+
24
+
25
+ // Fields:
26
+ //
27
+ // If Colander is not set,
28
+ // use every available field:
29
+ if (colander === null) {
30
+ for (let field of fieldsAvailable) {
31
+ // If no query filter or field is requested:
32
+ if (fields.length === 0 || fields.indexOf(field) > -1) {
33
+ newQuery.attributes.push(field);
34
+ continue;
35
+ }
36
+ }
37
+ }
38
+ // Colander is present:
39
+ else {
40
+ // If no query fields were set,
41
+ // use the ones from Colander,
42
+ // If query fields were set,
43
+ // put them through Colander:
44
+ for (let field of colander.fields) {
45
+ if (fieldsAvailable.indexOf(field) === -1) {
46
+ const err = new TypeError(`field ${ field } is not present in model.`);
47
+ throw err;
48
+ }
49
+
50
+ // If field is not in available set:
51
+ // if (colander.fields.indexOf(field) === -1) {
52
+ // continue;
53
+ // }
54
+
55
+ // If no query filter or field is requested:
56
+ if (fields.length === 0 || fields.indexOf(field) > -1) {
57
+ newQuery.attributes.push(field);
58
+ continue;
59
+ }
60
+ }
61
+ }
62
+
63
+ // At least 1 field is mandatory:
64
+ if (newQuery.attributes.length === 0) {
65
+ const err = new TypeError(`No fields were selected.`);
66
+ throw err;
67
+ }
68
+ // Fields\
69
+
70
+
71
+ // Clauses:
72
+ const order = {};
73
+
74
+ const clausesEntries = Object.entries(clauses);
75
+ for (let [clauseName, value] of clausesEntries) {
76
+ // If clause is not available:
77
+ if (colander != null) {
78
+ if (colander.clauses.indexOf(clauseName) === -1)
79
+ continue;
80
+ }
81
+
82
+ switch(clauseName) {
83
+ case 'limit':
84
+ // Do not set if -1:
85
+ if (value === -1)
86
+ continue;
87
+
88
+ newQuery.limit = value;
89
+ continue;
90
+ case 'skip':
91
+ // Do not set if 0:
92
+ if (value === 0)
93
+ continue;
94
+
95
+ newQuery.offset = value;
96
+ continue;
97
+ case 'order':
98
+ order.order = value;
99
+ continue;
100
+ case 'order_by':
101
+ order.by = value;
102
+ continue;
103
+ default:
104
+ continue;
105
+ }
106
+ }
107
+
108
+ // "statics" override or set any query Clause:
109
+ if (colander !== null) {
110
+ for (let [clauseName, staticClauseValue] of Object.entries(colander.statics.clauses)) {
111
+ switch(clauseName) {
112
+ case 'limit':
113
+ newQuery.limit = staticClauseValue;
114
+ continue;
115
+ case 'skip':
116
+ newQuery.offset = staticClauseValue;
117
+ continue;
118
+ case 'order':
119
+ order.order = staticClauseValue;
120
+ continue;
121
+ case 'order_by':
122
+ order.by = staticClauseValue;
123
+ continue;
124
+ default:
125
+ break;
126
+ }
127
+ }
128
+ }
129
+ // Clauses\
130
+
131
+
132
+ // Order:
133
+ const sequelize = model.sequelize;
134
+ if ( ['rand', 'random'].indexOf(order.order) > -1) {
135
+ newQuery.order = sequelize.random();
136
+ }
137
+ else {
138
+ const column = sequelize.col( order.by );
139
+ switch (order.order) {
140
+ // MAX/MIN:
141
+ case 'max-asc':
142
+ case 'max':
143
+ case 'min-desc':
144
+ newQuery.order = sequelize.fn('max', column);
145
+ break;
146
+ case 'min':
147
+ case 'min-asc':
148
+ case 'max-desc':
149
+ newQuery.order = [ sequelize.fn('max', column), 'DESC' ];
150
+ break;
151
+ // MAX/MIN\
152
+
153
+ default:
154
+ newQuery.order = [ [order.by, order.order] ];
155
+ break;
156
+ }
157
+ }
158
+ // Order\
159
+
160
+
161
+ // Includes:
162
+ // If requested includes are not available:
163
+ const leftIncludes = includesAvailable.map(i => i.association);
164
+ for (let include of includes) {
165
+ const includeName = include.model;
166
+
167
+ const includeIndex = leftIncludes.indexOf(includeName);
168
+ if (includeIndex === -1) {
169
+ const err = new TypeError(`No include named ${ includeName }`);
170
+ throw err;
171
+ }
172
+
173
+ leftIncludes.splice(includeIndex, 1);
174
+ }
175
+
176
+ _traverseIncludes(includes, model, colander, newQuery)
177
+ // Includes\
178
+
179
+
180
+ // Where:
181
+ const whereEntries = Object.entries(where);
182
+ for (let [attribute, value] of whereEntries) {
183
+ _parseWhereEntry(attribute, value, newQuery.where, colander.statics.attributes);
184
+ }
185
+
186
+ // If "where" was not set:
187
+ if (whereEntries.length === 0) {
188
+ delete newQuery.where;
189
+ }
190
+ // Where\
191
+
192
+
193
+ return newQuery;
194
+ }
195
+
196
+
197
+ function _traverseIncludes(includes, model, colander, resultQuery) {
198
+ // If no Colander:
199
+ if (colander === null) {
200
+ for (let include of includes) {
201
+ const includeName = include.model;
202
+ const association = model.associations[includeName];
203
+
204
+ // If no such association:
205
+ if (!association) {
206
+ const err = new TypeError(`No include ${ includeName }`);
207
+ throw err;
208
+ }
209
+
210
+ const includeModel = association.target;
211
+ // Build query for this include.
212
+ const associationQuery = traverse(include, null, includeModel);
213
+
214
+ _addAssociationQuery(associationQuery, includeName, resultQuery);
215
+ }
216
+ }
217
+ // Colander is present:
218
+ else {
219
+ const colanderIncludeEntries = Object.entries(colander.includes);
220
+ for (let [includeName, includeColander] of colanderIncludeEntries) {
221
+ const association = model.associations[includeName];
222
+ // If no such association:
223
+ if (!association) {
224
+ const err = new TypeError(`No include ${ includeName }`);
225
+ throw err;
226
+ }
227
+
228
+ // If include was not requested:
229
+ const include = includes.find(({ model }) => model === includeName);
230
+ if (!include)
231
+ continue;
232
+
233
+ const includeModel = association.target;
234
+ // Build query for this include.
235
+ const associationQuery = traverse(include, colander.includes[includeName], includeModel);
236
+
237
+ _addAssociationQuery(associationQuery, includeName, resultQuery);
238
+ }
239
+ }
240
+ }
241
+
242
+
243
+ function _addAssociationQuery(associationQuery, includeName, resultQuery) {
244
+
245
+ // Add all association info into query.
246
+ resultQuery.include.push({
247
+ association: includeName,
248
+ ...associationQuery
249
+ });
250
+ }
251
+
252
+
253
+ function _parseWhereEntry(attribute, value, whereHolder, staticAttributes) {
254
+ let _value = value;
255
+ const static = staticAttributes[attribute];
256
+
257
+ // If attribute is Op (like, or, not, etc.):
258
+ if (attribute in Op) {
259
+ // Parse value:
260
+ _value = _parseValue(_value, attribute);
261
+
262
+ const op = Op[attribute];
263
+ whereHolder[op] = _value;
264
+ return;
265
+ }
266
+
267
+ // Static value overrides any other:
268
+ if (!!static) {
269
+ whereHolder[attribute] = static;
270
+ return;
271
+ }
272
+
273
+ whereHolder[attribute] = _parseValue(_value, attribute);
274
+ }
275
+
276
+ function _disassembleQueryNode(queryNode) {
277
+ // Disassemble current query node:
278
+ const {
279
+ where,
280
+ includes,
281
+ fields,
282
+ ...clauses
283
+ } = queryNode;
284
+ // delete queryNode.model;
285
+
286
+ return {
287
+ where: where ?? {},
288
+ includes: includes ?? [],
289
+ fields: fields ?? [],
290
+ clauses: clauses ?? []
291
+ };
292
+ }
293
+
294
+ function _parseValue(value, attribute) {
295
+ // If value is Object:
296
+ if (typeof value === 'object' && Array.isArray(value) === false) {
297
+ const [opKey, rawValue] = (Object.entries(value))[0];
298
+
299
+ // If operation is "in":
300
+ if (opKey === 'in') {
301
+ // Unwrap rawValue.
302
+ return rawValue[0][attribute];
303
+ }
304
+ else {
305
+ const op = Op[opKey];
306
+ return { [op]: rawValue };
307
+ }
308
+ }
309
+
310
+ return value;
311
+ }