nodester 0.3.4 → 0.4.1

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.
@@ -65,17 +65,32 @@ function extract(body, filter=null, model) {
65
65
  }
66
66
 
67
67
  if (isInclude) {
68
- const filterIncludes = Object.keys(filter.includes);
68
+ const availableIncludeFilters = Object.keys(filter.includes);
69
69
 
70
- if (filterIncludes.indexOf(key) === -1) {
70
+ if (availableIncludeFilters.indexOf(key) === -1) {
71
71
  const err = new Error(`Include '${ key }' is not available.`);
72
72
  err.status = httpCodes.NOT_ACCEPTABLE;
73
73
  throw err;
74
74
  }
75
75
 
76
+ const thisIncludeFilter = filter.includes[key];
77
+
76
78
  const association = model.associations[key];
77
79
 
78
- filteredBody[key] = extract(value[0], filter.includes[key], association.target);
80
+ // Mutliple includes:
81
+ if (Array.isArray(value)) {
82
+ filteredBody[key] = [];
83
+
84
+ for (let thisIncludeBody of value) {
85
+ filteredBody[key].push(
86
+ extract(thisIncludeBody, thisIncludeFilter, association.target)
87
+ )
88
+ }
89
+ }
90
+ else {
91
+ const thisIncludeBody = value;
92
+ filteredBody[key] = extract(thisIncludeBody, thisIncludeFilter, association.target);
93
+ }
79
94
 
80
95
  continue;
81
96
  }
@@ -151,12 +151,12 @@ function _withDefaultErrorProcessing(controller, options={}) {
151
151
  switch(error.name) {
152
152
  case UNAUTHORIZED_ERROR: {
153
153
  statusCode = 401;
154
- errorResponse.details = { message: 'Unauthorized' };
154
+ errorResponse.details = error?.details ?? { message: 'Unauthorized' };
155
155
  break;
156
156
  }
157
157
  case NOT_FOUND_ERROR: {
158
158
  statusCode = 404;
159
- errorResponse.details = { message: errorMessage };
159
+ errorResponse.details = error?.details ?? { message: errorMessage };
160
160
  break;
161
161
  }
162
162
  case NODESTER_QUERY_ERROR: {
@@ -166,7 +166,7 @@ function _withDefaultErrorProcessing(controller, options={}) {
166
166
  }
167
167
  case VALIDATION_ERROR: {
168
168
  statusCode = 422;
169
- errorResponse.details = error?.details;
169
+ errorResponse.details = error?.details ?? error?.details;
170
170
  break;
171
171
  }
172
172
  case CONFLICT_ERROR: {
@@ -181,11 +181,11 @@ function _withDefaultErrorProcessing(controller, options={}) {
181
181
  }
182
182
  case INTERNAL_VALIDATION_ERROR: {
183
183
  statusCode = 500;
184
- errorResponse.details = { message: errorMessage };
184
+ errorResponse.details = error?.details ?? { message: errorMessage };
185
185
  break;
186
186
  }
187
187
  default: {
188
- errorResponse.details = { message: errorMessage };
188
+ errorResponse.details = error?.details ?? { message: errorMessage };
189
189
  break;
190
190
  }
191
191
  }
@@ -5,6 +5,10 @@
5
5
 
6
6
  'use strict';
7
7
 
8
+ // Constants:
9
+ const HTTP_CODES = require('nodester/http/codes');
10
+ const HTTP_CODE_DESCRIPTIONS = require('nodester/http/codes/descriptions');
11
+
8
12
  const Params = require('nodester/params');
9
13
 
10
14
  const log = require('nodester/loggers/dev');
@@ -106,8 +110,8 @@ async function _createOne(params) {
106
110
  });
107
111
 
108
112
  const instance = await this.model.create({ ...data }, {
109
- include: this.model.getIncludesTree(data)
110
- });
113
+ include: this.model.getIncludesTree(data)
114
+ });
111
115
 
112
116
  const result = {
113
117
  [this.outputName.singular]: instance,
@@ -145,12 +149,11 @@ async function _updateOne(params) {
145
149
  data: null
146
150
  });
147
151
 
148
- const updateResult = await this.model.updateOne(query.where, data);;
149
-
150
- const [ isNewRecord, instance ] = updateResult;
152
+ const instance = await this.model.updateOne(query.where, data, {
153
+ include: this.model.getIncludesTree(data)
154
+ });
151
155
 
152
156
  const result = {
153
- success: isNewRecord === false,
154
157
  [this.outputName.singular]: instance,
155
158
  count: 0 + (instance !== null)
156
159
  }
@@ -185,8 +188,18 @@ async function _deleteOne(params) {
185
188
 
186
189
  const count = await this.model.deleteOne(query);
187
190
 
191
+ // Model was not found:
192
+ if (count === 0) {
193
+ const err = new Error(HTTP_CODE_DESCRIPTIONS.NOT_FOUND);
194
+ err.statusCode = HTTP_CODES.NOT_FOUND;
195
+ err.details = {
196
+ message: 'Resource not found. Nothing was deleted.'
197
+ }
198
+
199
+ throw err;
200
+ }
201
+
188
202
  const result = {
189
- success: count > 0,
190
203
  count: count
191
204
  };
192
205
 
@@ -196,7 +209,12 @@ async function _deleteOne(params) {
196
209
  return Promise.resolve(result);
197
210
  }
198
211
  catch(error) {
199
- log.error(`${ this.name }.deleteOne error:`, error);
212
+
213
+ // 404 will not be logged:
214
+ if (error.statusCode !== HTTP_CODES.NOT_FOUND) {
215
+ log.error(`${ this.name }.deleteOne error:`, error);
216
+ }
217
+
200
218
  return Promise.reject(error);
201
219
  }
202
220
  }
@@ -29,8 +29,9 @@ class ModelsTreeNode {
29
29
  this.limit = -1; // No limit
30
30
 
31
31
  this._includes = opts.includes ?? [];
32
- this.order = opts.order ?? 'asc';
33
- this.order_by = opts.order_by ?? 'id';
32
+
33
+ this.order = opts.order ?? undefined;
34
+ this.order_by = opts.order_by ?? undefined;
34
35
  }
35
36
 
36
37
  // Getters:
@@ -86,7 +86,7 @@ async function _createWithIncludes(
86
86
  associatedModel,
87
87
  foreignKey,
88
88
  associationType
89
- } = getModelAssociationProps(associationDefinition, data);
89
+ } = getModelAssociationProps(associationDefinition);
90
90
 
91
91
  // If association type is HasMany or HasOne (We don't work with any other):
92
92
  if (associationType === 'HasMany' || associationType === 'HasOne') {
@@ -189,71 +189,108 @@ function _findMany(opts={}) {
189
189
  async function _updateOne(
190
190
  where,
191
191
  data,
192
- include=[]
192
+ opts={}
193
193
  ) {
194
194
  try {
195
- const instance = await this.findOne({ where, include });
195
+ const include = opts.include ?? [];
196
+ let instance = await this.findOne({ where, include });
197
+ let isNewRecord = false;
196
198
 
197
199
  if (!instance) {
198
- const err = new Error(`Model not found`);
199
- err.name = 'NotFound';
200
- throw err;
201
- }
202
200
 
203
- // Update parent model instance:
204
- instance.set({ ...data });
205
- const saveResult = await instance.save();
206
- const { isNewRecord } = saveResult;
207
-
208
- // Variable that will contain data from parent instance and associations, mentioned in data.
209
- let fullInstanceData = instance.toJSON();
201
+ if (opts.findOrCreate === true) {
202
+ instance = await this.create(data);
203
+ isNewRecord = true;
204
+ }
205
+ else {
206
+ const err = new Error(`Model not found`);
207
+ err.name = 'NotFound';
208
+ throw err;
209
+ }
210
210
 
211
- // If this model has associations:
212
- if (modelHasAssociations(this)) {
211
+ }
213
212
 
214
- const associationsEntries = Object.entries(this.associations);
215
- for (const [ associationName, associationDefinition ] of associationsEntries) {
213
+ // Will contain data from parent instance and associations.
214
+ const fullInstanceData = instance.toJSON();
216
215
 
217
- // If data of this association is present:
218
- if (!!data[associationName]) {
219
-
220
- // Preparation to work with association:
221
- const {
222
- associatedModel,
223
- foreignKey,
224
- associationType
225
- } = getModelAssociationProps(associationDefinition, data);
216
+ const parentData = {
217
+ ...data
218
+ }
219
+ for (let includeConfig of include) {
220
+ const { association } = includeConfig;
226
221
 
227
- // If association type is HasMany or HasOne (We don't work with any other):
228
- if (associationType === 'HasMany' || associationType === 'HasOne') {
222
+ if (parentData[association] === undefined) {
223
+ continue;
224
+ }
229
225
 
230
- // Process current instance.
231
- const operationsResults = await _updateOrCreateOrDeleteBasedOnAssociationData({
232
- associatedModel: associatedModel,
233
- dataOfAssociation: data[associationName],
234
- parentModelId: instance.id,
235
- parentForeignKey: foreignKey,
236
- });
226
+ delete parentData[association];
227
+
228
+ const associationDefinition = this.associations[association];
229
+ const includeData = data[association];
230
+
231
+ const {
232
+ associatedModel,
233
+ associationType,
234
+
235
+ foreignKey,
236
+ sourceKey
237
+ } = getModelAssociationProps(associationDefinition);
238
+
239
+ const associationUpdateOpts = {
240
+ findOrCreate: true,
241
+ include: associatedModel.getIncludesTree(includeData)
242
+ };
243
+
244
+ const pkField = associatedModel.primaryKeyField;
245
+
246
+ // If association type is HasMany or HasOne (We don't work with any other):
247
+ switch(associationType) {
248
+ case 'HasMany': {
249
+ const promises = includeData.map(singleData => {
250
+ // Note: for now we are only able to work with a model with single PrimaryKey:
251
+ const where = {
252
+ [pkField]: singleData[pkField]
253
+ }
254
+ return associatedModel.updateOne(
255
+ where,
256
+ singleData,
257
+ associationUpdateOpts
258
+ )
259
+ });
260
+ fullInstanceData[association] = await Promise.all(promises);
261
+
262
+ continue;
263
+ }
237
264
 
238
- fullInstanceData[associationName] = _unwrapUpdateOrCreateOrDeleteOperationsResults(
239
- operationsResults,
240
- associationType
241
- );
265
+ case 'HasOne': {
266
+ // Note: for now we are only able to work with a model with single PrimaryKey:
267
+ const where = {
268
+ [pkField]: includeData[pkField]
242
269
  }
270
+ fullInstanceData[association] = await associatedModel.updateOne(
271
+ where,
272
+ includeData,
273
+ associationUpdateOpts
274
+ );
243
275
 
244
- fullInstanceData[associationName] = fullInstanceData[associationName] ?? data[associationName];
276
+ continue;
245
277
  }
246
- }
247
278
 
279
+ default:
280
+ continue;
281
+ }
248
282
  }
249
-
250
- // Select this instance again, if includes was set:
251
- if (include?.length > 0) {
252
- const updatedInstance = await this.findOne({ where, include });
253
- fullInstanceData = updatedInstance.toJSON();
283
+
284
+ // Update parent model instance:
285
+ if (isNewRecord === false) {
286
+ instance.set(parentData);
287
+ const saveResult = await instance.save();
254
288
  }
255
289
 
256
- return Promise.resolve([ isNewRecord, fullInstanceData ]);
290
+ return Promise.resolve({
291
+ _is_new_record: isNewRecord,
292
+ ...fullInstanceData
293
+ });
257
294
  }
258
295
  catch(error) {
259
296
  return Promise.reject(error);
@@ -263,13 +300,13 @@ async function _updateOne(
263
300
  async function _updateById(
264
301
  id=null,
265
302
  data={},
266
- include=[]
303
+ opts={}
267
304
  ) {
268
305
  const where = { id };
269
306
  return this.updateOne(
270
307
  where,
271
308
  data,
272
- include
309
+ opts
273
310
  );
274
311
  }
275
312
 
@@ -5,7 +5,7 @@
5
5
 
6
6
  'use strict';
7
7
 
8
- const BOUNDS = require('../constants/Bounds');
8
+ const BOUNDS = require('../../constants/Bounds');
9
9
 
10
10
  const { Op } = require('sequelize');
11
11
  const { NodesterQueryError } = require('nodester/errors');
@@ -13,6 +13,20 @@ const httpCodes = require('nodester/http/codes');
13
13
 
14
14
  const { ensure } = require('nodester/validators/arguments');
15
15
 
16
+ const {
17
+ parseValue,
18
+ parseWhereEntry,
19
+ } = require('./parsers');
20
+
21
+ const {
22
+ disassembleQueryNode,
23
+ addAssociationQuery,
24
+ } = require('./utils');
25
+
26
+ const {
27
+ getModelAssociationProps
28
+ } = require('../../utils/modelAssociations.util');
29
+
16
30
 
17
31
  module.exports = traverse;
18
32
 
@@ -58,7 +72,7 @@ function traverse(queryNode, filter=null, model=null) {
58
72
  where,
59
73
 
60
74
  includes,
61
- } = _disassembleQueryNode(queryNode);
75
+ } = disassembleQueryNode(queryNode);
62
76
 
63
77
 
64
78
  // Attribute:
@@ -200,10 +214,16 @@ function traverse(queryNode, filter=null, model=null) {
200
214
  continue;
201
215
  }
202
216
  case 'order':
217
+ if (value === undefined)
218
+ continue;
219
+
203
220
  order.order = value;
204
221
  continue;
205
222
 
206
223
  case 'order_by':
224
+ if (value === undefined)
225
+ continue;
226
+
207
227
  order.by = value;
208
228
  continue;
209
229
 
@@ -303,13 +323,13 @@ function traverse(queryNode, filter=null, model=null) {
303
323
  // Set aatributes from Query:
304
324
  const whereEntries = Object.entries(where);
305
325
  for (let [ attribute, value ] of whereEntries) {
306
- _parseWhereEntry(attribute, value, newQuery.where);
326
+ parseWhereEntry(attribute, value, newQuery.where);
307
327
  }
308
328
 
309
329
  // Static attributes override previously set attributes:
310
330
  const staticAttributesEntries = Object.entries(filter.statics.attributes);
311
331
  for (let [ attribute, staticValue ] of staticAttributesEntries) {
312
- newQuery.where[attribute] = _parseValue(staticValue, attribute);
332
+ newQuery.where[attribute] = parseValue(staticValue, attribute);
313
333
  }
314
334
 
315
335
  // If "where" was not set in any way,
@@ -319,6 +339,12 @@ function traverse(queryNode, filter=null, model=null) {
319
339
  }
320
340
  // Where\
321
341
 
342
+ // Combine included orders into one at the top level:
343
+ // - Why?
344
+ // - Sequelize ingores included orders for association types like:
345
+ // • HasMany
346
+ _traverseIncludedOrders(newQuery, _model);
347
+
322
348
  return newQuery;
323
349
  }
324
350
 
@@ -355,66 +381,45 @@ function _traverseIncludes(includes, rootModel, filter, resultQuery) {
355
381
  // Build query for this include.
356
382
  const associationQuery = traverse(include, filter.includes[includeName], includeModel);
357
383
 
358
- _addAssociationQuery(associationQuery, includeName, resultQuery);
384
+ addAssociationQuery(associationQuery, includeName, resultQuery);
359
385
  }
360
386
  }
361
387
 
388
+ function _traverseIncludedOrders(resultQuery, rootModel) {
389
+ for (let i=0; i < resultQuery.include.length; i++) {
390
+ const include = resultQuery.include[i];
362
391
 
363
- function _addAssociationQuery(associationQuery, includeName, resultQuery) {
364
-
365
- // Add all association info into query.
366
- resultQuery.include.push({
367
- association: includeName,
368
- ...associationQuery
369
- });
370
- }
371
-
372
-
373
- function _parseWhereEntry(attribute, value, whereHolder) {
374
- let _value = value;
375
-
376
- // If attribute is Op (not, like, or, etc.):
377
- if (attribute in Op) {
378
- // Parse value:
379
- _value = _parseValue(_value, attribute);
380
-
381
- const op = Op[attribute];
382
- whereHolder[op] = _value;
383
- return;
384
- }
385
-
386
- whereHolder[attribute] = _parseValue(_value, attribute);
387
- }
392
+ if (!include?.order) {
393
+ continue;
394
+ }
388
395
 
389
- function _disassembleQueryNode(queryNode) {
390
- // Disassemble current query node:
391
- const {
392
- attributes,
393
- functions,
394
- where,
395
- includes,
396
- ...clauses
397
- } = queryNode;
398
-
399
- return {
400
- attributes: attributes ?? [],
401
- clauses: clauses ?? [],
402
- functions: functions ?? [],
403
- where: where ?? {},
404
- includes: includes ?? [],
405
- };
406
- }
396
+ if (!resultQuery.order) {
397
+ resultQuery.order = [];
398
+ }
407
399
 
408
- function _parseValue(value, attribute) {
409
- // If value is Object:
410
- if (typeof value === 'object' && Array.isArray(value) === false) {
411
- const [ opKey, rawValue ] = (Object.entries(value))[0];
400
+ const { association } = include;
401
+ const {
402
+ associatedModel,
403
+ associationType
404
+ } = getModelAssociationProps(rootModel.associations[association]);
405
+
406
+ switch(associationType) {
407
+ case 'HasMany': {
408
+ resultQuery.order.push([
409
+ { association },
410
+ ...include.order[0]
411
+ ]);
412
+ delete resultQuery.include[i].order;
413
+ break;
414
+ }
415
+ default:
416
+ break;
417
+ }
412
418
 
413
- const op = Op[opKey];
414
- return { [op]: rawValue };
419
+ _traverseIncludedOrders(resultQuery.include[i], associatedModel);
415
420
  }
416
421
 
417
- return value;
422
+ return resultQuery;
418
423
  }
419
424
 
420
425
  function _setValueWithBounds(value, type, bounds) {
@@ -0,0 +1,41 @@
1
+ /**
2
+ * nodester
3
+ * MIT Licensed
4
+ */
5
+
6
+ 'use strict';
7
+
8
+
9
+ module.exports = {
10
+ parseValue: _parseValue,
11
+ parseWhereEntry: _parseWhereEntry,
12
+ }
13
+
14
+ function _parseValue(value, attribute) {
15
+ // If value is Object:
16
+ if (typeof value === 'object' && Array.isArray(value) === false) {
17
+ const [ opKey, rawValue ] = (Object.entries(value))[0];
18
+
19
+ const op = Op[opKey];
20
+ return { [op]: rawValue };
21
+ }
22
+
23
+ return value;
24
+ }
25
+
26
+ function _parseWhereEntry(attribute, value, whereHolder) {
27
+ let _value = value;
28
+
29
+ // If attribute is Op (not, like, or, etc.):
30
+ if (attribute in Op) {
31
+ // Parse value:
32
+ _value = _parseValue(_value, attribute);
33
+
34
+ const op = Op[attribute];
35
+ whereHolder[op] = _value;
36
+ return;
37
+ }
38
+
39
+ whereHolder[attribute] = _parseValue(_value, attribute);
40
+ }
41
+
@@ -0,0 +1,40 @@
1
+ /**
2
+ * nodester
3
+ * MIT Licensed
4
+ */
5
+
6
+ 'use strict';
7
+
8
+
9
+ module.exports = {
10
+ disassembleQueryNode: _disassembleQueryNode,
11
+ addAssociationQuery: _addAssociationQuery,
12
+ }
13
+
14
+ function _disassembleQueryNode(queryNode) {
15
+ // Disassemble current query node:
16
+ const {
17
+ attributes,
18
+ functions,
19
+ where,
20
+ includes,
21
+ ...clauses
22
+ } = queryNode;
23
+
24
+ return {
25
+ attributes: attributes ?? [],
26
+ clauses: clauses ?? [],
27
+ functions: functions ?? [],
28
+ where: where ?? {},
29
+ includes: includes ?? [],
30
+ };
31
+ }
32
+
33
+ function _addAssociationQuery(associationQuery, includeName, resultQuery) {
34
+
35
+ // Add all association info into query.
36
+ resultQuery.include.push({
37
+ association: includeName,
38
+ ...associationQuery
39
+ });
40
+ }
@@ -1,3 +1,10 @@
1
+ /**
2
+ * nodester
3
+ * MIT Licensed
4
+ */
5
+
6
+ 'use strict';
7
+
1
8
 
2
9
  module.exports = {
3
10
  modelHasAssociations: _modelHasAssociations,
@@ -10,12 +17,16 @@ function _modelHasAssociations(modelDifinition) {
10
17
  }
11
18
 
12
19
  function _getModelAssociationProps(
13
- associationDefinition,
14
- requestData
20
+ associationDefinition
15
21
  ) {
16
22
  // Extract neccessary variables and functions:
17
23
  const associatedModel = associationDefinition.target;
18
- const { foreignKey } = associationDefinition.options;
24
+
25
+ const {
26
+ foreignKey,
27
+ sourceKey
28
+ } = associationDefinition;
29
+
19
30
  const {
20
31
  associationType,
21
32
  accessors,
@@ -23,8 +34,11 @@ function _getModelAssociationProps(
23
34
 
24
35
  return {
25
36
  associatedModel,
26
- foreignKey,
27
37
  associationType,
38
+
39
+ foreignKey,
40
+ sourceKey,
41
+
28
42
  accessors,
29
43
  };
30
44
  }
@@ -7,8 +7,14 @@
7
7
 
8
8
 
9
9
  module.exports = {
10
+ UUID: _UUID,
11
+
10
12
  CHAR: _STRING,
11
13
  VARCHAR: _STRING,
14
+ STRING: _STRING,
15
+ TEXT: _TEXT,
16
+
17
+ ENUM: _ENUM,
12
18
 
13
19
  NUMBER: _NUMBER,
14
20
 
@@ -17,8 +23,6 @@ module.exports = {
17
23
 
18
24
  BOOLEAN: _BOOLEAN,
19
25
 
20
- STRING: _STRING,
21
- TEXT: _TEXT,
22
26
 
23
27
  DATE: _DATE,
24
28
  DATETIME: _DATE,
@@ -26,6 +30,52 @@ module.exports = {
26
30
  JSON: _JSON
27
31
  }
28
32
 
33
+ function _UUID(value=undefined, options={ fallback:undefined }) {
34
+ try {
35
+ if (typeof value !== 'string')
36
+ throw new Error(`Not a valid UUID`);
37
+
38
+ return value;
39
+ }
40
+ catch(ex) {
41
+ return options?.fallback;
42
+ }
43
+ }
44
+
45
+
46
+ function _STRING(value=undefined, options={ fallback:undefined }) {
47
+ try {
48
+ if (typeof value !== 'string')
49
+ throw new Error(`Not a String`);
50
+
51
+ return value;
52
+ }
53
+ catch(ex) {
54
+ return options?.fallback;
55
+ }
56
+ }
57
+
58
+ function _TEXT(value=undefined, options={ fallback:undefined }) {
59
+ try {
60
+ if (typeof value !== 'string')
61
+ throw new Error(`Not a String`);
62
+
63
+ return value;
64
+ }
65
+ catch(ex) {
66
+ return options?.fallback;
67
+ }
68
+ }
69
+
70
+
71
+ function _ENUM(value=undefined, options={ fallback:undefined }) {
72
+ if (value === undefined)
73
+ return options.fallback;
74
+
75
+ return value;
76
+ }
77
+
78
+
29
79
  function _isNumber(value) {
30
80
  return !isNaN(`${value}`);
31
81
  }
@@ -70,29 +120,6 @@ function _BOOLEAN(value=undefined, options={ fallback:undefined }) {
70
120
  }
71
121
  }
72
122
 
73
- function _STRING(value=undefined, options={ fallback:undefined }) {
74
- try {
75
- if (typeof value !== 'string')
76
- throw new Error(`Not a String`);
77
-
78
- return value;
79
- }
80
- catch(ex) {
81
- return options?.fallback;
82
- }
83
- }
84
-
85
- function _TEXT(value=undefined, options={ fallback:undefined }) {
86
- try {
87
- if (typeof value !== 'string')
88
- throw new Error(`Not a String`);
89
-
90
- return value;
91
- }
92
- catch(ex) {
93
- return options?.fallback;
94
- }
95
- }
96
123
 
97
124
  function _DATE(value=undefined, options={ fallback:undefined }) {
98
125
  try {
@@ -123,6 +150,7 @@ function _DATE(value=undefined, options={ fallback:undefined }) {
123
150
  }
124
151
  }
125
152
 
153
+
126
154
  function _JSON(value=undefined, options={ fallback:undefined }) {
127
155
  try {
128
156
  if (typeof value === 'string')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodester",
3
- "version": "0.3.4",
3
+ "version": "0.4.1",
4
4
  "description": "A versatile REST framework for Node.js",
5
5
  "directories": {
6
6
  "docs": "docs",
@@ -57,7 +57,7 @@
57
57
  "./params": "./lib/structures/Params.js",
58
58
 
59
59
  "./ql/sequelize": "./lib/middlewares/ql/sequelize/index.js",
60
- "./query/traverse": "./lib/query/traverse.js",
60
+ "./query/traverse": "./lib/query/traverse/index.js",
61
61
 
62
62
  "./route": "./lib/router/route.js",
63
63
  "./router": "./lib/router/index.js",