nodester 0.6.77 → 0.7.10

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.
@@ -14,7 +14,7 @@ const debug = require('debug')('nodester:interpreter:ModelsTree');
14
14
  * @access public
15
15
  */
16
16
  class ModelsTreeNode {
17
- constructor(model, parent=null, opts={}) {
17
+ constructor(model, parent = null, opts = {}) {
18
18
  this.model = model;
19
19
  this.parent = parent;
20
20
 
@@ -30,11 +30,13 @@ class ModelsTreeNode {
30
30
  this.group_by = opts.group_by ?? undefined;
31
31
  this.skip = 0;
32
32
  this.limit = -1; // No limit
33
-
33
+
34
34
  this.order = opts.order ?? undefined;
35
35
  this.order_by = opts.order_by ?? undefined;
36
36
  // Clauses\
37
37
 
38
+ this.required = false; // For root filtering via ^
39
+
38
40
  this._includes = opts.includes ?? [];
39
41
  }
40
42
 
@@ -85,14 +87,14 @@ class ModelsTreeNode {
85
87
  this.fn = null;
86
88
  }
87
89
 
88
- addWhere(condition={}) {
90
+ addWhere(condition = {}) {
89
91
  this._where = {
90
92
  ...this.where,
91
93
  ...condition
92
94
  }
93
95
  }
94
96
 
95
- addFunction(fnParams={}) {
97
+ addFunction(fnParams = {}) {
96
98
  this._functions.push(fnParams);
97
99
  }
98
100
 
@@ -102,6 +104,14 @@ class ModelsTreeNode {
102
104
  return modelTreeNode;
103
105
  }
104
106
 
107
+ markAsRequired() {
108
+ this.required = true;
109
+ // Propagate up the tree (except for root)
110
+ if (this.parent && this.parent.model !== 'root') {
111
+ this.parent.markAsRequired();
112
+ }
113
+ }
114
+
105
115
  toObject() {
106
116
  return {
107
117
  model: this.model,
@@ -116,7 +126,8 @@ class ModelsTreeNode {
116
126
  limit: this.limit,
117
127
  order: this.order,
118
128
  order_by: this.order_by,
119
-
129
+
130
+ required: this.required,
120
131
 
121
132
  includes: this.includes.map(i => i.toObject())
122
133
  }
@@ -134,7 +145,7 @@ class ModelsTree {
134
145
  this.node = this.root;
135
146
  }
136
147
 
137
- include(model, opts={}) {
148
+ include(model, opts = {}) {
138
149
  debug('include', model);
139
150
 
140
151
  const node = new ModelsTreeNode(model, this.node, opts);
@@ -153,7 +164,7 @@ class ModelsTree {
153
164
  }
154
165
 
155
166
  debug('use', model, !!foundOne ? '' : '-> failed.');
156
-
167
+
157
168
  return foundOne;
158
169
  }
159
170
 
@@ -507,6 +507,19 @@ module.exports = class QueryLexer {
507
507
  continue;
508
508
  }
509
509
 
510
+
511
+ // ^ can mean:
512
+ // • root filtering (INNER JOIN) - attribute in subquery that filters root
513
+ if (char === '^') {
514
+ debug('char', char, { token, node: tree.node });
515
+
516
+ // Mark current node as required (will propagate up)
517
+ tree.node.markAsRequired();
518
+
519
+ // Continue to next char (don't add ^ to token)
520
+ continue;
521
+ }
522
+
510
523
  // = can only mean the end of a param name:
511
524
  if (char === '=') {
512
525
  const param = this.parseParamFromToken(token);
@@ -2,7 +2,7 @@
2
2
  * nodester
3
3
  * MIT Licensed
4
4
  */
5
-
5
+
6
6
  'use strict';
7
7
 
8
8
  const BOUNDS = require('../../constants/Bounds');
@@ -44,7 +44,7 @@ module.exports = traverse;
44
44
  *
45
45
  * @access public
46
46
  */
47
- function traverse(queryNode, filter=null, model=null, association=null) {
47
+ function traverse(queryNode, filter = null, model = null, association = null) {
48
48
  const _model = model ?? filter.model;
49
49
 
50
50
  try {
@@ -55,7 +55,7 @@ function traverse(queryNode, filter=null, model=null, association=null) {
55
55
  throw err;
56
56
  }
57
57
  }
58
- catch(error) {
58
+ catch (error) {
59
59
  Error.captureStackTrace(error, traverse);
60
60
  throw error;
61
61
  }
@@ -78,8 +78,15 @@ function traverse(queryNode, filter=null, model=null, association=null) {
78
78
  where,
79
79
 
80
80
  includes,
81
+ required,
81
82
  } = disassembleQueryNode(queryNode);
82
83
 
84
+ // If this include is marked as required (root filtering via ^),
85
+ // set it on the query:
86
+ if (required) {
87
+ newQuery.required = true;
88
+ }
89
+
83
90
 
84
91
  // Attribute:
85
92
  //
@@ -102,7 +109,7 @@ function traverse(queryNode, filter=null, model=null, association=null) {
102
109
  // put them through Filter:
103
110
  for (let attribute of filter.attributes) {
104
111
  if (attributesAvailable.indexOf(attribute) === -1) {
105
- const err = new NodesterQueryError(`Field '${ attribute }' is not present in model.`);
112
+ const err = new NodesterQueryError(`Field '${attribute}' is not present in model.`);
106
113
  Error.captureStackTrace(err, traverse);
107
114
  throw err;
108
115
  }
@@ -119,7 +126,7 @@ function traverse(queryNode, filter=null, model=null, association=null) {
119
126
  }
120
127
  }
121
128
  }
122
-
129
+
123
130
  // At least 1 attribute is mandatory
124
131
  // or "functions" must be set:
125
132
  if (
@@ -138,12 +145,12 @@ function traverse(queryNode, filter=null, model=null, association=null) {
138
145
  const fnName = fnParams.fn;
139
146
 
140
147
  if (typeof filter.functions[fnName] === 'undefined') {
141
- const err = new NodesterQueryError(`Function '${ fnName }' is not allowed.`);
148
+ const err = new NodesterQueryError(`Function '${fnName}' is not allowed.`);
142
149
  Error.captureStackTrace(err, traverse);
143
150
  throw err;
144
151
  }
145
152
 
146
- switch(fnName) {
153
+ switch (fnName) {
147
154
  // SQL COUNT():
148
155
  case 'count': {
149
156
  mapCOUNT(
@@ -157,7 +164,7 @@ function traverse(queryNode, filter=null, model=null, association=null) {
157
164
  }
158
165
  // Any other function:
159
166
  default:
160
- consl.warn(`function ${ fnName }() is not supported`);
167
+ consl.warn(`function ${fnName}() is not supported`);
161
168
  break;
162
169
  }
163
170
  }
@@ -167,7 +174,7 @@ function traverse(queryNode, filter=null, model=null, association=null) {
167
174
  const order = {};
168
175
 
169
176
  const clausesEntries = Object.entries(clauses);
170
- for (const [ clauseName, value ] of clausesEntries) {
177
+ for (const [clauseName, value] of clausesEntries) {
171
178
 
172
179
  // If clause is not available:
173
180
  if (filter != null) {
@@ -176,7 +183,7 @@ function traverse(queryNode, filter=null, model=null, association=null) {
176
183
  }
177
184
  }
178
185
 
179
- switch(clauseName) {
186
+ switch (clauseName) {
180
187
  case 'group_by': {
181
188
  // Check if this value is a valid attribute:
182
189
  if (typeof value === 'undefined') {
@@ -184,7 +191,7 @@ function traverse(queryNode, filter=null, model=null, association=null) {
184
191
  }
185
192
 
186
193
  if (typeof _model.tableAttributes[value] === 'undefined') {
187
- const err = new NodesterQueryError(`group_by '${ value }' is not allowed.`);
194
+ const err = new NodesterQueryError(`group_by '${value}' is not allowed.`);
188
195
  Error.captureStackTrace(err, traverse);
189
196
  throw err;
190
197
  }
@@ -197,7 +204,7 @@ function traverse(queryNode, filter=null, model=null, association=null) {
197
204
  const _value = _setValueWithBounds(value, 'number', filter.bounds.clauses.limit);
198
205
 
199
206
  // Do not set if negative:
200
- if (_value < 0)
207
+ if (_value < 0)
201
208
  continue;
202
209
 
203
210
  newQuery.limit = _value;
@@ -237,9 +244,9 @@ function traverse(queryNode, filter=null, model=null, association=null) {
237
244
  const staticClausesEntries = Object.entries(filter.statics.clauses);
238
245
 
239
246
  for (let entry of staticClausesEntries) {
240
- const [ clauseName, staticClauseValue ] = entry;
247
+ const [clauseName, staticClauseValue] = entry;
241
248
 
242
- switch(clauseName) {
249
+ switch (clauseName) {
243
250
  case 'group_by':
244
251
  newQuery.group = staticClauseValue;
245
252
  continue;
@@ -282,7 +289,7 @@ function traverse(queryNode, filter=null, model=null, association=null) {
282
289
  newQuery.order = sequelize.random();
283
290
  }
284
291
  else {
285
- const column = sequelize.col( order.by );
292
+ const column = sequelize.col(order.by);
286
293
  switch (order.order) {
287
294
  // MAX/MIN:
288
295
  case 'max-asc':
@@ -293,7 +300,7 @@ function traverse(queryNode, filter=null, model=null, association=null) {
293
300
  case 'min':
294
301
  case 'min-asc':
295
302
  case 'max-desc':
296
- newQuery.order = [ sequelize.fn('max', column), 'DESC' ];
303
+ newQuery.order = [sequelize.fn('max', column), 'DESC'];
297
304
  break;
298
305
  // MAX/MIN\
299
306
 
@@ -303,7 +310,7 @@ function traverse(queryNode, filter=null, model=null, association=null) {
303
310
  break;
304
311
 
305
312
  default:
306
- newQuery.order = [ [order.by, order.order] ];
313
+ newQuery.order = [[order.by, order.order]];
307
314
  break;
308
315
  }
309
316
  }
@@ -316,7 +323,7 @@ function traverse(queryNode, filter=null, model=null, association=null) {
316
323
  const includeName = include.model;
317
324
 
318
325
  if (rootModelAssociations[includeName] === undefined) {
319
- const err = new NodesterQueryError(`No include named '${ includeName }'`);
326
+ const err = new NodesterQueryError(`No include named '${includeName}'`);
320
327
  Error.captureStackTrace(err, traverse);
321
328
  throw err;
322
329
  }
@@ -330,13 +337,13 @@ function traverse(queryNode, filter=null, model=null, association=null) {
330
337
 
331
338
  // Set aatributes from Query:
332
339
  const whereEntries = Object.entries(where);
333
- for (const [ attribute, value ] of whereEntries) {
340
+ for (const [attribute, value] of whereEntries) {
334
341
  parseWhereEntry(attribute, value, newQuery.where, _model);
335
342
  }
336
343
 
337
344
  // Static attributes override previously set attributes:
338
345
  const staticAttributesEntries = Object.entries(filter.statics.attributes);
339
- for (const [ attribute, staticValue ] of staticAttributesEntries) {
346
+ for (const [attribute, staticValue] of staticAttributesEntries) {
340
347
  newQuery.where[attribute] = parseValue(staticValue, attribute, _model);
341
348
  }
342
349
 
@@ -369,13 +376,13 @@ function traverse(queryNode, filter=null, model=null, association=null) {
369
376
  */
370
377
  function _traverseIncludes(includes, rootModel, filter, resultQuery) {
371
378
  const filterIncludesEntries = Object.entries(filter.includes);
372
- for (let [ includeName, includeFilter ] of filterIncludesEntries) {
379
+ for (let [includeName, includeFilter] of filterIncludesEntries) {
373
380
 
374
381
  const association = rootModel.associations[includeName];
375
382
 
376
383
  // If no such association:
377
384
  if (!association) {
378
- const err = new NodesterQueryError(`No include named '${ includeName }'`);
385
+ const err = new NodesterQueryError(`No include named '${includeName}'`);
379
386
  Error.captureStackTrace(err, _traverseIncludes);
380
387
  throw err;
381
388
  }
@@ -394,7 +401,7 @@ function _traverseIncludes(includes, rootModel, filter, resultQuery) {
394
401
  }
395
402
 
396
403
  function _traverseIncludedOrders(resultQuery, rootModel) {
397
- for (let i=0; i < resultQuery.include.length; i++) {
404
+ for (let i = 0; i < resultQuery.include.length; i++) {
398
405
  const include = resultQuery.include[i];
399
406
 
400
407
  if (!include?.order) {
@@ -411,7 +418,7 @@ function _traverseIncludedOrders(resultQuery, rootModel) {
411
418
  associationType
412
419
  } = getModelAssociationProps(rootModel.associations[association]);
413
420
 
414
- switch(associationType) {
421
+ switch (associationType) {
415
422
  case 'HasMany': {
416
423
  include.separate = true;
417
424
  // resultQuery.order.push([
@@ -434,7 +441,7 @@ function _traverseIncludedOrders(resultQuery, rootModel) {
434
441
  function _setValueWithBounds(value, type, bounds) {
435
442
  if (typeof bounds === 'object') {
436
443
 
437
- switch(type) {
444
+ switch (type) {
438
445
  case 'number': {
439
446
  let _value = value;
440
447
 
@@ -2,7 +2,7 @@
2
2
  * nodester
3
3
  * MIT Licensed
4
4
  */
5
-
5
+
6
6
  'use strict';
7
7
 
8
8
 
@@ -18,6 +18,7 @@ function _disassembleQueryNode(queryNode) {
18
18
  functions,
19
19
  where,
20
20
  includes,
21
+ required,
21
22
  ...clauses
22
23
  } = queryNode;
23
24
 
@@ -27,6 +28,7 @@ function _disassembleQueryNode(queryNode) {
27
28
  functions: functions ?? [],
28
29
  where: where ?? {},
29
30
  includes: includes ?? [],
31
+ required: required ?? false,
30
32
  };
31
33
  }
32
34
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodester",
3
- "version": "0.6.77",
3
+ "version": "0.7.10",
4
4
  "description": "A versatile REST framework for Node.js",
5
5
  "directories": {
6
6
  "docs": "docs",
package/tests/nql.test.js CHANGED
@@ -24,7 +24,7 @@ describe('nodester Query Language', () => {
24
24
  ];
25
25
 
26
26
  it('Simple where', async () => {
27
- const lexer = new QueryLexer( queryStrings[0] );
27
+ const lexer = new QueryLexer(queryStrings[0]);
28
28
  const result = await lexer.parse();
29
29
 
30
30
  const tree = new ModelsTree();
@@ -35,24 +35,24 @@ describe('nodester Query Language', () => {
35
35
  });
36
36
 
37
37
  it('Only certain attributes', async () => {
38
- const lexer = new QueryLexer( queryStrings[1] );
38
+ const lexer = new QueryLexer(queryStrings[1]);
39
39
  const result = await lexer.parse();
40
40
 
41
41
  const tree = new ModelsTree();
42
- tree.node.attributes = [ 'id', 'text' ];
42
+ tree.node.attributes = ['id', 'text'];
43
43
  const expected = tree.root.toObject();
44
44
 
45
45
  expect(result).toMatchObject(expected);
46
46
  });
47
47
 
48
48
  test('All possible params', async () => {
49
- const lexer = new QueryLexer( queryStrings[2] );
49
+ const lexer = new QueryLexer(queryStrings[2]);
50
50
  const result = await lexer.parse();
51
51
 
52
52
 
53
53
  const tree = new ModelsTree();
54
54
  tree.node.addWhere({ id: ['10'], position: ['4'] });
55
- tree.node.attributes = [ 'id', 'content', 'position', 'created_at' ];
55
+ tree.node.attributes = ['id', 'content', 'position', 'created_at'];
56
56
  tree.node.limit = 3;
57
57
  tree.node.skip = 10;
58
58
  tree.node.order = 'desc';
@@ -74,20 +74,20 @@ describe('nodester Query Language', () => {
74
74
  '2-horizontals': 'includes=comments,users&id=1000',
75
75
  // 4 horizontals with subquery.
76
76
  '4-horizontals': 'in=categories,replies.users,comments(order_by=position&order=desc),users.avatars',
77
-
77
+
78
78
  // Horizontals queried.
79
79
  'horizontals-queried': 'includes=comments(order=desc),users,likes(order=rand),reposts&id=1000',
80
80
  // Horizontals queried №2.
81
81
  'horizontals-queried-2': 'in=comments(order_by=index&order=asc).users.karma',
82
82
  // Horizontals queried №3.
83
83
  'horizontals-queried-3': 'in=reactions,comments(user_id=gte(4)&skip=10&limit=2).users,likes,reposts',
84
-
84
+
85
85
  // Separated includes.
86
86
  'separated-includes': 'includes=comments(order=rand)&id=7&limit=3&includes=users(a=id,content)',
87
87
  };
88
88
 
89
89
  test('Simple includes', async () => {
90
- const lexer = new QueryLexer( queryStrings['simple-includes'] );
90
+ const lexer = new QueryLexer(queryStrings['simple-includes']);
91
91
  const result = await lexer.parse();
92
92
 
93
93
 
@@ -100,13 +100,13 @@ describe('nodester Query Language', () => {
100
100
  });
101
101
 
102
102
  test('Include with all possible params', async () => {
103
- const lexer = new QueryLexer( queryStrings['include-with-params'] );
103
+ const lexer = new QueryLexer(queryStrings['include-with-params']);
104
104
  const result = await lexer.parse();
105
105
 
106
106
  const tree = new ModelsTree();
107
107
  tree.include('comments').use('comments');
108
108
  tree.node.addWhere({ id: ['10'], position: ['4'] });
109
- tree.node.attributes = [ 'id', 'content', 'position' ];
109
+ tree.node.attributes = ['id', 'content', 'position'];
110
110
  tree.node.limit = 3;
111
111
  tree.node.skip = 10;
112
112
  tree.node.order = 'desc';
@@ -117,7 +117,7 @@ describe('nodester Query Language', () => {
117
117
  });
118
118
 
119
119
  test('2 horizontals', async () => {
120
- const lexer = new QueryLexer( queryStrings['2-horizontals'] );
120
+ const lexer = new QueryLexer(queryStrings['2-horizontals']);
121
121
  const result = await lexer.parse();
122
122
 
123
123
 
@@ -133,7 +133,7 @@ describe('nodester Query Language', () => {
133
133
  test('4 horizontals', async () => {
134
134
  // in=categories,replies.users,comments(order_by=position&order=desc),users.avatars
135
135
 
136
- const lexer = new QueryLexer( queryStrings['4-horizontals'] );
136
+ const lexer = new QueryLexer(queryStrings['4-horizontals']);
137
137
  const result = await lexer.parse();
138
138
 
139
139
 
@@ -162,7 +162,7 @@ describe('nodester Query Language', () => {
162
162
  });
163
163
 
164
164
  test('Horizontals queried', async () => {
165
- const lexer = new QueryLexer( queryStrings['horizontals-queried'] );
165
+ const lexer = new QueryLexer(queryStrings['horizontals-queried']);
166
166
  const result = await lexer.parse();
167
167
 
168
168
 
@@ -182,7 +182,7 @@ describe('nodester Query Language', () => {
182
182
  });
183
183
 
184
184
  test('Horizontals queried №2', async () => {
185
- const lexer = new QueryLexer( queryStrings['horizontals-queried-2'] );
185
+ const lexer = new QueryLexer(queryStrings['horizontals-queried-2']);
186
186
  const result = await lexer.parse();
187
187
 
188
188
  const tree = new ModelsTree();
@@ -200,7 +200,7 @@ describe('nodester Query Language', () => {
200
200
  });
201
201
 
202
202
  test('Horizontals queried №3', async () => {
203
- const lexer = new QueryLexer( queryStrings['horizontals-queried-3'] );
203
+ const lexer = new QueryLexer(queryStrings['horizontals-queried-3']);
204
204
  const result = await lexer.parse();
205
205
 
206
206
  const tree = new ModelsTree();
@@ -208,9 +208,9 @@ describe('nodester Query Language', () => {
208
208
 
209
209
  tree.include('comments').use('comments');
210
210
  tree.node.addWhere({
211
- user_id: {
212
- gte: ['4']
213
- }
211
+ user_id: {
212
+ gte: ['4']
213
+ }
214
214
  });
215
215
  tree.node.skip = 10;
216
216
  tree.node.limit = 2;
@@ -227,7 +227,7 @@ describe('nodester Query Language', () => {
227
227
  });
228
228
 
229
229
  test('Separated includes"', async () => {
230
- const lexer = new QueryLexer( queryStrings['separated-includes'] );
230
+ const lexer = new QueryLexer(queryStrings['separated-includes']);
231
231
  const result = await lexer.parse();
232
232
 
233
233
  const tree = new ModelsTree();
@@ -237,7 +237,7 @@ describe('nodester Query Language', () => {
237
237
  tree.node.order = 'rand';
238
238
  tree.up();
239
239
  tree.include('users').use('users');
240
- tree.node.attributes = [ 'id', 'content' ];
240
+ tree.node.attributes = ['id', 'content'];
241
241
  const expected = tree.root.toObject();
242
242
 
243
243
  expect(result).toMatchObject(expected);
@@ -269,7 +269,7 @@ describe('nodester Query Language', () => {
269
269
  ];
270
270
 
271
271
  test('Simple subinclude', async () => {
272
- const lexer = new QueryLexer( queryStrings[0] );
272
+ const lexer = new QueryLexer(queryStrings[0]);
273
273
  const result = await lexer.parse();
274
274
 
275
275
 
@@ -282,7 +282,7 @@ describe('nodester Query Language', () => {
282
282
  });
283
283
 
284
284
  test('Deep subincludes', async () => {
285
- const lexer = new QueryLexer( queryStrings[1] );
285
+ const lexer = new QueryLexer(queryStrings[1]);
286
286
  const result = await lexer.parse();
287
287
 
288
288
 
@@ -298,7 +298,7 @@ describe('nodester Query Language', () => {
298
298
  });
299
299
 
300
300
  test('Simple horizontal subinclude, "+" syntaxis"', async () => {
301
- const lexer = new QueryLexer( queryStrings[2] );
301
+ const lexer = new QueryLexer(queryStrings[2]);
302
302
  const result = await lexer.parse();
303
303
 
304
304
 
@@ -312,7 +312,7 @@ describe('nodester Query Language', () => {
312
312
  });
313
313
 
314
314
  test('Subinclude query', async () => {
315
- const lexer = new QueryLexer( queryStrings[3] );
315
+ const lexer = new QueryLexer(queryStrings[3]);
316
316
  const result = await lexer.parse();
317
317
 
318
318
 
@@ -396,22 +396,22 @@ describe('nodester Query Language', () => {
396
396
  ];
397
397
 
398
398
  test('"OR" simple', async () => {
399
- const lexer = new QueryLexer( queryStrings[0] );
399
+ const lexer = new QueryLexer(queryStrings[0]);
400
400
  const result = await lexer.parse();
401
401
 
402
402
  const tree = new ModelsTree();
403
- tree.node.addWhere({ or: [ { index: ['2'] }, { position: ['5'] } ] });
403
+ tree.node.addWhere({ or: [{ index: ['2'] }, { position: ['5'] }] });
404
404
  const expected = tree.root.toObject();
405
405
 
406
406
  expect(result).toMatchObject(expected);
407
407
  });
408
408
 
409
409
  test('"OR" short', async () => {
410
- const lexer = new QueryLexer( queryStrings[1] );
410
+ const lexer = new QueryLexer(queryStrings[1]);
411
411
  const result = await lexer.parse();
412
412
 
413
413
  const tree = new ModelsTree();
414
- tree.node.addWhere({ or: [ { index: ['2'] }, { position: ['5'] } ] });
414
+ tree.node.addWhere({ or: [{ index: ['2'] }, { position: ['5'] }] });
415
415
  const expected = tree.root.toObject();
416
416
 
417
417
  expect(result).toMatchObject(expected);
@@ -429,7 +429,7 @@ describe('nodester Query Language', () => {
429
429
  ];
430
430
 
431
431
  test('"NOT" simple', async () => {
432
- const lexer = new QueryLexer( queryStrings[0] );
432
+ const lexer = new QueryLexer(queryStrings[0]);
433
433
  const result = await lexer.parse();
434
434
 
435
435
  const tree = new ModelsTree();
@@ -440,7 +440,7 @@ describe('nodester Query Language', () => {
440
440
  });
441
441
 
442
442
  test('"NOT" short', async () => {
443
- const lexer = new QueryLexer( queryStrings[1] );
443
+ const lexer = new QueryLexer(queryStrings[1]);
444
444
  const result = await lexer.parse();
445
445
 
446
446
  const tree = new ModelsTree();
@@ -451,12 +451,12 @@ describe('nodester Query Language', () => {
451
451
  });
452
452
 
453
453
  test('"NOT" inside includes', async () => {
454
- const lexer = new QueryLexer( queryStrings[2] );
454
+ const lexer = new QueryLexer(queryStrings[2]);
455
455
  const result = await lexer.parse();
456
456
 
457
457
  const tree = new ModelsTree();
458
458
  tree.include('comments').use('comments');
459
- tree.node.addWhere({ id: { not: ['7'] }});
459
+ tree.node.addWhere({ id: { not: ['7'] } });
460
460
  const expected = tree.root.toObject();
461
461
 
462
462
  expect(result).toMatchObject(expected);
@@ -476,33 +476,33 @@ describe('nodester Query Language', () => {
476
476
  ];
477
477
 
478
478
  test('"Like" simple', async () => {
479
- const lexer = new QueryLexer( queryStrings[0] );
479
+ const lexer = new QueryLexer(queryStrings[0]);
480
480
  const result = await lexer.parse();
481
481
 
482
482
  const tree = new ModelsTree();
483
- tree.node.addWhere({ title: { like: ['some_text'] }});
483
+ tree.node.addWhere({ title: { like: ['some_text'] } });
484
484
  const expected = tree.root.toObject();
485
485
 
486
486
  expect(result).toMatchObject(expected);
487
487
  });
488
488
 
489
489
  test('"NotLike" simple', async () => {
490
- const lexer = new QueryLexer( queryStrings[1] );
490
+ const lexer = new QueryLexer(queryStrings[1]);
491
491
  const result = await lexer.parse();
492
492
 
493
493
  const tree = new ModelsTree();
494
- tree.node.addWhere({ title: { notLike: ['some_text'] }});
494
+ tree.node.addWhere({ title: { notLike: ['some_text'] } });
495
495
  const expected = tree.root.toObject();
496
496
 
497
497
  expect(result).toMatchObject(expected);
498
498
  });
499
499
 
500
500
  test('"NotLike" short', async () => {
501
- const lexer = new QueryLexer( queryStrings[2] );
501
+ const lexer = new QueryLexer(queryStrings[2]);
502
502
  const result = await lexer.parse();
503
503
 
504
504
  const tree = new ModelsTree();
505
- tree.node.addWhere({ title: { notLike: ['some_text'] }});
505
+ tree.node.addWhere({ title: { notLike: ['some_text'] } });
506
506
  const expected = tree.root.toObject();
507
507
 
508
508
  expect(result).toMatchObject(expected);
@@ -519,23 +519,23 @@ describe('nodester Query Language', () => {
519
519
  ];
520
520
 
521
521
  test('"IN" simple', async () => {
522
- const lexer = new QueryLexer( queryStrings[0] );
522
+ const lexer = new QueryLexer(queryStrings[0]);
523
523
  const result = await lexer.parse();
524
524
 
525
525
  const tree = new ModelsTree();
526
- tree.node.addWhere({ status: { in: ['REVIEWED', 'ANSWERED'] }});
526
+ tree.node.addWhere({ status: { in: ['REVIEWED', 'ANSWERED'] } });
527
527
  const expected = tree.root.toObject();
528
528
 
529
529
  expect(result).toMatchObject(expected);
530
530
  });
531
531
 
532
532
  test('"IN" and "limit" clause', async () => {
533
- const lexer = new QueryLexer( queryStrings[1] );
533
+ const lexer = new QueryLexer(queryStrings[1]);
534
534
  const result = await lexer.parse();
535
535
 
536
536
  const tree = new ModelsTree();
537
537
  tree.node.limit = 3;
538
- tree.node.addWhere({ status: { in: ['REVIEWED', 'ANSWERED'] }});
538
+ tree.node.addWhere({ status: { in: ['REVIEWED', 'ANSWERED'] } });
539
539
  const expected = tree.root.toObject();
540
540
 
541
541
  expect(result).toMatchObject(expected);
@@ -561,62 +561,62 @@ describe('nodester Query Language', () => {
561
561
  ];
562
562
 
563
563
  test('Greater than', async () => {
564
- const lexer = new QueryLexer( queryStrings[0] );
564
+ const lexer = new QueryLexer(queryStrings[0]);
565
565
  const result = await lexer.parse();
566
566
 
567
567
 
568
568
  const tree = new ModelsTree();
569
- tree.node.addWhere({ created_at: { gt: ['2022'] }});
569
+ tree.node.addWhere({ created_at: { gt: ['2022'] } });
570
570
  const expected = tree.root.toObject();
571
571
 
572
572
  expect(result).toMatchObject(expected);
573
573
  });
574
574
 
575
575
  test('Greater than or equal to', async () => {
576
- const lexer = new QueryLexer( queryStrings[1] );
576
+ const lexer = new QueryLexer(queryStrings[1]);
577
577
  const result = await lexer.parse();
578
578
 
579
579
 
580
580
  const tree = new ModelsTree();
581
- tree.node.addWhere({ created_at: { gte: ['2023-12-08'] }});
581
+ tree.node.addWhere({ created_at: { gte: ['2023-12-08'] } });
582
582
  const expected = tree.root.toObject();
583
583
 
584
584
  expect(result).toMatchObject(expected);
585
585
  });
586
586
 
587
587
  test('Lower than', async () => {
588
- const lexer = new QueryLexer( queryStrings[2] );
588
+ const lexer = new QueryLexer(queryStrings[2]);
589
589
  const result = await lexer.parse();
590
590
 
591
591
 
592
592
  const tree = new ModelsTree();
593
- tree.node.addWhere({ index: { lt: ['10'] }});
593
+ tree.node.addWhere({ index: { lt: ['10'] } });
594
594
  const expected = tree.root.toObject();
595
595
 
596
596
  expect(result).toMatchObject(expected);
597
597
  });
598
598
 
599
599
  test('Lower than or equal to', async () => {
600
- const lexer = new QueryLexer( queryStrings[3] );
600
+ const lexer = new QueryLexer(queryStrings[3]);
601
601
  const result = await lexer.parse();
602
602
 
603
603
 
604
604
  const tree = new ModelsTree();
605
- tree.node.addWhere({ index: { lte: ['9'] }});
605
+ tree.node.addWhere({ index: { lte: ['9'] } });
606
606
  const expected = tree.root.toObject();
607
607
 
608
608
  expect(result).toMatchObject(expected);
609
609
  });
610
610
 
611
611
  test('Greater than in subinclude', async () => {
612
- const lexer = new QueryLexer( queryStrings[4] );
612
+ const lexer = new QueryLexer(queryStrings[4]);
613
613
  const result = await lexer.parse();
614
614
 
615
615
 
616
616
  const tree = new ModelsTree();
617
617
  tree.include('comments').use('comments');
618
618
  tree.include('likes').use('likes');
619
- tree.node.addWhere({ index: { gt: ['60'] }});
619
+ tree.node.addWhere({ index: { gt: ['60'] } });
620
620
  const expected = tree.root.toObject();
621
621
 
622
622
  expect(result).toMatchObject(expected);
@@ -634,18 +634,18 @@ describe('nodester Query Language', () => {
634
634
  }
635
635
 
636
636
  test('AND (simple)', async () => {
637
- const lexer = new QueryLexer( queryStrings.and_simple );
637
+ const lexer = new QueryLexer(queryStrings.and_simple);
638
638
  const result = await lexer.parse();
639
639
 
640
640
  const tree = new ModelsTree();
641
- tree.node.addWhere({ id: { gte: ['2'], lt: ['5'] }});
641
+ tree.node.addWhere({ id: { gte: ['2'], lt: ['5'] } });
642
642
  const expected = tree.root.toObject();
643
643
 
644
644
  expect(result).toMatchObject(expected);
645
645
  });
646
646
 
647
647
  test('AND (more OP)', async () => {
648
- const lexer = new QueryLexer( queryStrings.and_more_op );
648
+ const lexer = new QueryLexer(queryStrings.and_more_op);
649
649
  const result = await lexer.parse();
650
650
 
651
651
  const tree = new ModelsTree();
@@ -658,7 +658,7 @@ describe('nodester Query Language', () => {
658
658
  });
659
659
 
660
660
  test('AND (in subincludes #0)', async () => {
661
- const lexer = new QueryLexer( queryStrings.and_in_subincludes_0 );
661
+ const lexer = new QueryLexer(queryStrings.and_in_subincludes_0);
662
662
  const result = await lexer.parse();
663
663
 
664
664
  const tree = new ModelsTree();
@@ -672,7 +672,7 @@ describe('nodester Query Language', () => {
672
672
  });
673
673
 
674
674
  test('AND (in subincludes #1)', async () => {
675
- const lexer = new QueryLexer( queryStrings.and_in_subincludes_1 );
675
+ const lexer = new QueryLexer(queryStrings.and_in_subincludes_1);
676
676
  const result = await lexer.parse();
677
677
 
678
678
  const tree = new ModelsTree();
@@ -689,7 +689,7 @@ describe('nodester Query Language', () => {
689
689
  });
690
690
 
691
691
  test('AND (in subincludes #2)', async () => {
692
- const lexer = new QueryLexer( queryStrings.and_in_subincludes_2 );
692
+ const lexer = new QueryLexer(queryStrings.and_in_subincludes_2);
693
693
  const result = await lexer.parse();
694
694
 
695
695
  const tree = new ModelsTree();
@@ -716,7 +716,7 @@ describe('nodester Query Language', () => {
716
716
  }
717
717
 
718
718
  test('Count (full key name)', async () => {
719
- const lexer = new QueryLexer( queryStrings.count_long );
719
+ const lexer = new QueryLexer(queryStrings.count_long);
720
720
  const result = await lexer.parse();
721
721
 
722
722
 
@@ -731,7 +731,7 @@ describe('nodester Query Language', () => {
731
731
  });
732
732
 
733
733
  test('Count (short key name)', async () => {
734
- const lexer = new QueryLexer( queryStrings.count_long );
734
+ const lexer = new QueryLexer(queryStrings.count_long);
735
735
  const result = await lexer.parse();
736
736
 
737
737
 
@@ -746,7 +746,7 @@ describe('nodester Query Language', () => {
746
746
  });
747
747
 
748
748
  test('Count and includes', async () => {
749
- const lexer = new QueryLexer( queryStrings.count_and_includes );
749
+ const lexer = new QueryLexer(queryStrings.count_and_includes);
750
750
  const result = await lexer.parse();
751
751
 
752
752
 
@@ -761,4 +761,46 @@ describe('nodester Query Language', () => {
761
761
  expect(result).toMatchObject(expected);
762
762
  });
763
763
  });
764
+
765
+ describe('root-filtering', () => {
766
+ const queryStrings = {
767
+ simple: 'includes=order(^is_filled=1)',
768
+ complex: 'includes=product.order(^is_filled=1)+photos(limit=5)',
769
+ }
770
+
771
+ test('Simple root filtering with ^', async () => {
772
+ const lexer = new QueryLexer(queryStrings.simple);
773
+ const result = await lexer.parse();
774
+
775
+ const tree = new ModelsTree();
776
+ tree.include('order').use('order');
777
+ tree.node.addWhere({ is_filled: ['1'] });
778
+ tree.node.markAsRequired();
779
+ const expected = tree.root.toObject();
780
+
781
+ expect(result).toMatchObject(expected);
782
+ expect(result.includes[0].required).toBe(true);
783
+ });
784
+
785
+ test('Complex nested root filtering with ^', async () => {
786
+ const lexer = new QueryLexer(queryStrings.complex);
787
+ const result = await lexer.parse();
788
+
789
+ const tree = new ModelsTree();
790
+ tree.include('product').use('product');
791
+ tree.include('order').use('order');
792
+ tree.node.addWhere({ is_filled: ['1'] });
793
+ tree.node.markAsRequired();
794
+ tree.up();
795
+ tree.up();
796
+ tree.include('photos').use('photos');
797
+ tree.node.limit = 5;
798
+ const expected = tree.root.toObject();
799
+
800
+ expect(result).toMatchObject(expected);
801
+ expect(result.includes[0].required).toBe(true);
802
+ expect(result.includes[0].includes[0].required).toBe(true);
803
+ expect(result.includes[1].required).toBe(false);
804
+ });
805
+ });
764
806
  });