@nymphjs/query-parser 1.0.0-beta.13 → 1.0.0-beta.131

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.
@@ -1,8 +1,15 @@
1
1
  import type { Entity } from '@nymphjs/client';
2
- import queryParser from './queryParser';
2
+ import queryParser from './queryParser.js';
3
3
 
4
4
  const BlogPost = function () {} as unknown as typeof Entity;
5
5
  const Category = function () {} as unknown as typeof Entity;
6
+ const List = function () {} as unknown as typeof Entity;
7
+
8
+ const Mail = function () {} as unknown as typeof Entity;
9
+ const Headers = function () {} as unknown as typeof Entity;
10
+ const Body = function () {} as unknown as typeof Entity;
11
+ const Label = function () {} as unknown as typeof Entity;
12
+ const Domain = function () {} as unknown as typeof Entity;
6
13
 
7
14
  describe('queryParser', () => {
8
15
  it('parses a basic query', () => {
@@ -55,13 +62,13 @@ describe('queryParser', () => {
55
62
  });
56
63
 
57
64
  it('gives the correct entity and default fields to bare queries', () => {
58
- const query = 'category<{cat search}>';
65
+ const query = 'category<{Cat search}>';
59
66
  const [options, ...selectors] = queryParser({
60
67
  query,
61
68
  entityClass: BlogPost,
62
69
  defaultFields: ['text'],
63
70
  qrefMap: {
64
- cat: {
71
+ Cat: {
65
72
  class: Category,
66
73
  defaultFields: ['name'],
67
74
  },
@@ -97,13 +104,13 @@ describe('queryParser', () => {
97
104
 
98
105
  it('parses the example query from the readme', () => {
99
106
  const query =
100
- 'limit:4 foobar (| [archived] mdate<"2 weeks ago") category<{cat Tech}>';
107
+ 'limit:4 foobar (| [archived] mdate<"2 weeks ago") category<{Cat Tech}>';
101
108
  const [options, ...selectors] = queryParser({
102
109
  query,
103
110
  entityClass: BlogPost,
104
111
  defaultFields: ['title', 'body'],
105
112
  qrefMap: {
106
- cat: {
113
+ Cat: {
107
114
  class: Category,
108
115
  defaultFields: ['name'],
109
116
  },
@@ -150,31 +157,809 @@ describe('queryParser', () => {
150
157
  ]);
151
158
  });
152
159
 
153
- it('parses all options', () => {
154
- const query = 'limit:10 offset:15 reverse:true search';
160
+ it('parses a query with json value with spaces', () => {
161
+ const query =
162
+ 'obj={"prop": "my value"} foobar arr=["a value", "another value"]';
155
163
  const [options, ...selectors] = queryParser({
156
164
  query,
157
165
  entityClass: BlogPost,
158
- defaultFields: ['text'],
166
+ defaultFields: ['title', 'body'],
159
167
  });
160
168
 
161
169
  expect(options).toEqual({
162
170
  class: BlogPost,
163
- limit: 10,
164
- offset: 15,
165
- reverse: true,
166
171
  });
167
172
 
168
173
  expect(selectors).toEqual([
174
+ {
175
+ type: '&',
176
+ equal: [
177
+ ['obj', { prop: 'my value' }],
178
+ ['arr', ['a value', 'another value']],
179
+ ],
180
+ },
169
181
  {
170
182
  type: '|',
171
- ilike: [['text', '%search%']],
183
+ ilike: [
184
+ ['title', '%foobar%'],
185
+ ['body', '%foobar%'],
186
+ ],
172
187
  },
173
188
  ]);
174
189
  });
175
190
 
176
- it('parses simple equal clauses', () => {
177
- const query = 'prop=string';
191
+ it('parses a query with nested json value with spaces', () => {
192
+ const query =
193
+ 'obj={"obj": {"sub": "my value"}, "prop": "my value"} foobar arr=["a value", "another value", {"prop": [1, 2, 3]}]';
194
+ const [options, ...selectors] = queryParser({
195
+ query,
196
+ entityClass: BlogPost,
197
+ defaultFields: ['title', 'body'],
198
+ });
199
+
200
+ expect(options).toEqual({
201
+ class: BlogPost,
202
+ });
203
+
204
+ expect(selectors).toEqual([
205
+ {
206
+ type: '&',
207
+ equal: [
208
+ ['obj', { obj: { sub: 'my value' }, prop: 'my value' }],
209
+ ['arr', ['a value', 'another value', { prop: [1, 2, 3] }]],
210
+ ],
211
+ },
212
+ {
213
+ type: '|',
214
+ ilike: [
215
+ ['title', '%foobar%'],
216
+ ['body', '%foobar%'],
217
+ ],
218
+ },
219
+ ]);
220
+ });
221
+
222
+ it('parses a query with broken json', () => {
223
+ const query = 'obj={"obj":"val"';
224
+ const [options, ...selectors] = queryParser({
225
+ query,
226
+ entityClass: BlogPost,
227
+ defaultFields: ['title', 'body'],
228
+ });
229
+
230
+ expect(options).toEqual({
231
+ class: BlogPost,
232
+ });
233
+
234
+ expect(selectors).toEqual([
235
+ {
236
+ type: '&',
237
+ equal: [['obj', '{"obj":"val"']],
238
+ },
239
+ ]);
240
+ });
241
+
242
+ it('parses a query with broken json and correct json', () => {
243
+ const query = 'obj={"obj":"val" another={"prop":"value"}';
244
+ const [options, ...selectors] = queryParser({
245
+ query,
246
+ entityClass: BlogPost,
247
+ defaultFields: ['title', 'body'],
248
+ });
249
+
250
+ expect(options).toEqual({
251
+ class: BlogPost,
252
+ });
253
+
254
+ expect(selectors).toEqual([
255
+ {
256
+ type: '&',
257
+ equal: [
258
+ ['another', { prop: 'value' }],
259
+ ['obj', '{"obj":"val"'],
260
+ ],
261
+ },
262
+ ]);
263
+ });
264
+
265
+ it('parses a query with search', () => {
266
+ const query = 'limit:4 foobar prop(a search string)';
267
+ const [options, ...selectors] = queryParser({
268
+ query,
269
+ entityClass: BlogPost,
270
+ defaultFields: ['title', 'body'],
271
+ qrefMap: {
272
+ cat: {
273
+ class: Category,
274
+ defaultFields: ['name'],
275
+ },
276
+ },
277
+ });
278
+
279
+ expect(options).toEqual({
280
+ class: BlogPost,
281
+ limit: 4,
282
+ });
283
+
284
+ expect(selectors).toEqual([
285
+ {
286
+ type: '&',
287
+ search: [['prop', 'a search string']],
288
+ },
289
+ {
290
+ type: '|',
291
+ ilike: [
292
+ ['title', '%foobar%'],
293
+ ['body', '%foobar%'],
294
+ ],
295
+ },
296
+ ]);
297
+ });
298
+
299
+ it('parses a query with not search', () => {
300
+ const query = 'limit:4 foobar -prop(a search string)';
301
+ const [options, ...selectors] = queryParser({
302
+ query,
303
+ entityClass: BlogPost,
304
+ defaultFields: ['title', 'body'],
305
+ qrefMap: {
306
+ cat: {
307
+ class: Category,
308
+ defaultFields: ['name'],
309
+ },
310
+ },
311
+ });
312
+
313
+ expect(options).toEqual({
314
+ class: BlogPost,
315
+ limit: 4,
316
+ });
317
+
318
+ expect(selectors).toEqual([
319
+ {
320
+ type: '&',
321
+ '!search': [['prop', 'a search string']],
322
+ },
323
+ {
324
+ type: '|',
325
+ ilike: [
326
+ ['title', '%foobar%'],
327
+ ['body', '%foobar%'],
328
+ ],
329
+ },
330
+ ]);
331
+ });
332
+
333
+ it('parses a query with colon search', () => {
334
+ const query = 'limit:4 foobar prop:string';
335
+ const [options, ...selectors] = queryParser({
336
+ query,
337
+ entityClass: BlogPost,
338
+ defaultFields: ['title', 'body'],
339
+ qrefMap: {
340
+ cat: {
341
+ class: Category,
342
+ defaultFields: ['name'],
343
+ },
344
+ },
345
+ });
346
+
347
+ expect(options).toEqual({
348
+ class: BlogPost,
349
+ limit: 4,
350
+ });
351
+
352
+ expect(selectors).toEqual([
353
+ {
354
+ type: '&',
355
+ search: [['prop', 'string']],
356
+ },
357
+ {
358
+ type: '|',
359
+ ilike: [
360
+ ['title', '%foobar%'],
361
+ ['body', '%foobar%'],
362
+ ],
363
+ },
364
+ ]);
365
+ });
366
+
367
+ it('parses a query with not colon search', () => {
368
+ const query = 'limit:4 foobar -prop:string';
369
+ const [options, ...selectors] = queryParser({
370
+ query,
371
+ entityClass: BlogPost,
372
+ defaultFields: ['title', 'body'],
373
+ qrefMap: {
374
+ cat: {
375
+ class: Category,
376
+ defaultFields: ['name'],
377
+ },
378
+ },
379
+ });
380
+
381
+ expect(options).toEqual({
382
+ class: BlogPost,
383
+ limit: 4,
384
+ });
385
+
386
+ expect(selectors).toEqual([
387
+ {
388
+ type: '&',
389
+ '!search': [['prop', 'string']],
390
+ },
391
+ {
392
+ type: '|',
393
+ ilike: [
394
+ ['title', '%foobar%'],
395
+ ['body', '%foobar%'],
396
+ ],
397
+ },
398
+ ]);
399
+ });
400
+
401
+ it('parses a query with search in a selector', () => {
402
+ const query =
403
+ 'limit:4 foobar (| prop(a search string) prop2("another search string"))';
404
+ const [options, ...selectors] = queryParser({
405
+ query,
406
+ entityClass: BlogPost,
407
+ defaultFields: ['title', 'body'],
408
+ qrefMap: {
409
+ cat: {
410
+ class: Category,
411
+ defaultFields: ['name'],
412
+ },
413
+ },
414
+ });
415
+
416
+ expect(options).toEqual({
417
+ class: BlogPost,
418
+ limit: 4,
419
+ });
420
+
421
+ expect(selectors).toEqual([
422
+ {
423
+ type: '&',
424
+ selector: [
425
+ {
426
+ type: '|',
427
+ search: [
428
+ ['prop', 'a search string'],
429
+ ['prop2', '"another search string"'],
430
+ ],
431
+ },
432
+ ],
433
+ },
434
+ {
435
+ type: '|',
436
+ ilike: [
437
+ ['title', '%foobar%'],
438
+ ['body', '%foobar%'],
439
+ ],
440
+ },
441
+ ]);
442
+ });
443
+
444
+ it('parses a query with colon search in a selector', () => {
445
+ const query =
446
+ 'limit:4 foobar (| prop:string prop2:"another search string" -prop3:pickle -prop4:\'another -pickle string\')';
447
+ const [options, ...selectors] = queryParser({
448
+ query,
449
+ entityClass: BlogPost,
450
+ defaultFields: ['title', 'body'],
451
+ qrefMap: {
452
+ cat: {
453
+ class: Category,
454
+ defaultFields: ['name'],
455
+ },
456
+ },
457
+ });
458
+
459
+ expect(options).toEqual({
460
+ class: BlogPost,
461
+ limit: 4,
462
+ });
463
+
464
+ expect(selectors).toEqual([
465
+ {
466
+ type: '&',
467
+ selector: [
468
+ {
469
+ type: '|',
470
+ search: [
471
+ ['prop', 'string'],
472
+ ['prop2', '"another search string"'],
473
+ ],
474
+ '!search': [
475
+ ['prop3', 'pickle'],
476
+ ['prop4', "'another -pickle string'"],
477
+ ],
478
+ },
479
+ ],
480
+ },
481
+ {
482
+ type: '|',
483
+ ilike: [
484
+ ['title', '%foobar%'],
485
+ ['body', '%foobar%'],
486
+ ],
487
+ },
488
+ ]);
489
+ });
490
+
491
+ it('parses a query with lt and tag', () => {
492
+ const query = 'limit:4 foobar mdate<"2 weeks ago" <tag>';
493
+ const [options, ...selectors] = queryParser({
494
+ query,
495
+ entityClass: BlogPost,
496
+ defaultFields: ['title', 'body'],
497
+ qrefMap: {
498
+ cat: {
499
+ class: Category,
500
+ defaultFields: ['name'],
501
+ },
502
+ },
503
+ });
504
+
505
+ expect(options).toEqual({
506
+ class: BlogPost,
507
+ limit: 4,
508
+ });
509
+
510
+ expect(selectors).toEqual([
511
+ {
512
+ type: '&',
513
+ lt: [['mdate', null, '2 weeks ago']],
514
+ tag: ['tag'],
515
+ },
516
+ {
517
+ type: '|',
518
+ ilike: [
519
+ ['title', '%foobar%'],
520
+ ['body', '%foobar%'],
521
+ ],
522
+ },
523
+ ]);
524
+ });
525
+
526
+ it('parses a query with contain that has brackets', () => {
527
+ const query = 'limit:4 foobar string<"a string with < and > characters">';
528
+ const [options, ...selectors] = queryParser({
529
+ query,
530
+ entityClass: BlogPost,
531
+ defaultFields: ['title', 'body'],
532
+ qrefMap: {
533
+ cat: {
534
+ class: Category,
535
+ defaultFields: ['name'],
536
+ },
537
+ },
538
+ });
539
+
540
+ expect(options).toEqual({
541
+ class: BlogPost,
542
+ limit: 4,
543
+ });
544
+
545
+ expect(selectors).toEqual([
546
+ {
547
+ type: '&',
548
+ contain: [['string', 'a string with < and > characters']],
549
+ },
550
+ {
551
+ type: '|',
552
+ ilike: [
553
+ ['title', '%foobar%'],
554
+ ['body', '%foobar%'],
555
+ ],
556
+ },
557
+ ]);
558
+ });
559
+
560
+ it('parses a query with contain that has quotes', () => {
561
+ const query = 'limit:4 foobar string<"a string with a \\" characters">';
562
+ const [options, ...selectors] = queryParser({
563
+ query,
564
+ entityClass: BlogPost,
565
+ defaultFields: ['title', 'body'],
566
+ qrefMap: {
567
+ cat: {
568
+ class: Category,
569
+ defaultFields: ['name'],
570
+ },
571
+ },
572
+ });
573
+
574
+ expect(options).toEqual({
575
+ class: BlogPost,
576
+ limit: 4,
577
+ });
578
+
579
+ expect(selectors).toEqual([
580
+ {
581
+ type: '&',
582
+ contain: [['string', 'a string with a " characters']],
583
+ },
584
+ {
585
+ type: '|',
586
+ ilike: [
587
+ ['title', '%foobar%'],
588
+ ['body', '%foobar%'],
589
+ ],
590
+ },
591
+ ]);
592
+ });
593
+
594
+ it('parses a query with a dash in the property name', () => {
595
+ const query = 'limit:4 foobar Test-Prop<"a string">';
596
+ const [options, ...selectors] = queryParser({
597
+ query,
598
+ entityClass: BlogPost,
599
+ defaultFields: ['title', 'body'],
600
+ qrefMap: {
601
+ cat: {
602
+ class: Category,
603
+ defaultFields: ['name'],
604
+ },
605
+ },
606
+ });
607
+
608
+ expect(options).toEqual({
609
+ class: BlogPost,
610
+ limit: 4,
611
+ });
612
+
613
+ expect(selectors).toEqual([
614
+ {
615
+ type: '&',
616
+ contain: [['Test-Prop', 'a string']],
617
+ },
618
+ {
619
+ type: '|',
620
+ ilike: [
621
+ ['title', '%foobar%'],
622
+ ['body', '%foobar%'],
623
+ ],
624
+ },
625
+ ]);
626
+ });
627
+
628
+ it('parses all options', () => {
629
+ const query = 'limit:10 offset:15 sort:someProp reverse:true search';
630
+ const [options, ...selectors] = queryParser({
631
+ query,
632
+ entityClass: BlogPost,
633
+ defaultFields: ['text'],
634
+ });
635
+
636
+ expect(options).toEqual({
637
+ class: BlogPost,
638
+ limit: 10,
639
+ offset: 15,
640
+ sort: 'someProp',
641
+ reverse: true,
642
+ });
643
+
644
+ expect(selectors).toEqual([
645
+ {
646
+ type: '|',
647
+ ilike: [['text', '%search%']],
648
+ },
649
+ ]);
650
+ });
651
+
652
+ it('parses null sort option', () => {
653
+ const query = 'limit:10 offset:15 sort:- reverse:true search';
654
+ const [options, ...selectors] = queryParser({
655
+ query,
656
+ entityClass: BlogPost,
657
+ defaultFields: ['text'],
658
+ });
659
+
660
+ expect(options).toEqual({
661
+ class: BlogPost,
662
+ limit: 10,
663
+ offset: 15,
664
+ sort: null,
665
+ reverse: true,
666
+ });
667
+
668
+ expect(selectors).toEqual([
669
+ {
670
+ type: '|',
671
+ ilike: [['text', '%search%']],
672
+ },
673
+ ]);
674
+ });
675
+
676
+ it('parses simple equal clauses', () => {
677
+ const query = 'prop=string';
678
+ const [options, ...selectors] = queryParser({
679
+ query,
680
+ entityClass: BlogPost,
681
+ });
682
+
683
+ expect(options).toEqual({
684
+ class: BlogPost,
685
+ });
686
+
687
+ expect(selectors).toEqual([
688
+ {
689
+ type: '&',
690
+ equal: [['prop', 'string']],
691
+ },
692
+ ]);
693
+ });
694
+
695
+ it('parses simple not equal clauses', () => {
696
+ const query = '-prop=string';
697
+ const [options, ...selectors] = queryParser({
698
+ query,
699
+ entityClass: BlogPost,
700
+ });
701
+
702
+ expect(options).toEqual({
703
+ class: BlogPost,
704
+ });
705
+
706
+ expect(selectors).toEqual([
707
+ {
708
+ type: '&',
709
+ '!equal': [['prop', 'string']],
710
+ },
711
+ ]);
712
+ });
713
+
714
+ it('parses quoted equal clauses', () => {
715
+ const query = 'prop="a string"';
716
+ const [options, ...selectors] = queryParser({
717
+ query,
718
+ entityClass: BlogPost,
719
+ });
720
+
721
+ expect(options).toEqual({
722
+ class: BlogPost,
723
+ });
724
+
725
+ expect(selectors).toEqual([
726
+ {
727
+ type: '&',
728
+ equal: [['prop', 'a string']],
729
+ },
730
+ ]);
731
+ });
732
+
733
+ it('parses quoted not equal clauses', () => {
734
+ const query = '-prop="a string"';
735
+ const [options, ...selectors] = queryParser({
736
+ query,
737
+ entityClass: BlogPost,
738
+ });
739
+
740
+ expect(options).toEqual({
741
+ class: BlogPost,
742
+ });
743
+
744
+ expect(selectors).toEqual([
745
+ {
746
+ type: '&',
747
+ '!equal': [['prop', 'a string']],
748
+ },
749
+ ]);
750
+ });
751
+
752
+ it('parses json equal clauses', () => {
753
+ const query = 'prop=true';
754
+ const [options, ...selectors] = queryParser({
755
+ query,
756
+ entityClass: BlogPost,
757
+ });
758
+
759
+ expect(options).toEqual({
760
+ class: BlogPost,
761
+ });
762
+
763
+ expect(selectors).toEqual([
764
+ {
765
+ type: '&',
766
+ equal: [['prop', true]],
767
+ },
768
+ ]);
769
+ });
770
+
771
+ it('parses json not equal clauses', () => {
772
+ const query = '-prop=true';
773
+ const [options, ...selectors] = queryParser({
774
+ query,
775
+ entityClass: BlogPost,
776
+ });
777
+
778
+ expect(options).toEqual({
779
+ class: BlogPost,
780
+ });
781
+
782
+ expect(selectors).toEqual([
783
+ {
784
+ type: '&',
785
+ '!equal': [['prop', true]],
786
+ },
787
+ ]);
788
+ });
789
+
790
+ it('parses guid clauses', () => {
791
+ const query = '{111111111111111111111111}';
792
+ const [options, ...selectors] = queryParser({
793
+ query,
794
+ entityClass: BlogPost,
795
+ });
796
+
797
+ expect(options).toEqual({
798
+ class: BlogPost,
799
+ });
800
+
801
+ expect(selectors).toEqual([
802
+ {
803
+ type: '&',
804
+ guid: ['111111111111111111111111'],
805
+ },
806
+ ]);
807
+ });
808
+
809
+ it('parses not guid clauses', () => {
810
+ const query = '-{111111111111111111111111}';
811
+ const [options, ...selectors] = queryParser({
812
+ query,
813
+ entityClass: BlogPost,
814
+ });
815
+
816
+ expect(options).toEqual({
817
+ class: BlogPost,
818
+ });
819
+
820
+ expect(selectors).toEqual([
821
+ {
822
+ type: '&',
823
+ '!guid': ['111111111111111111111111'],
824
+ },
825
+ ]);
826
+ });
827
+
828
+ it('parses tag clauses', () => {
829
+ const query = '<tagname>';
830
+ const [options, ...selectors] = queryParser({
831
+ query,
832
+ entityClass: BlogPost,
833
+ });
834
+
835
+ expect(options).toEqual({
836
+ class: BlogPost,
837
+ });
838
+
839
+ expect(selectors).toEqual([
840
+ {
841
+ type: '&',
842
+ tag: ['tagname'],
843
+ },
844
+ ]);
845
+ });
846
+
847
+ it('parses not tag clauses', () => {
848
+ const query = '-<tagname>';
849
+ const [options, ...selectors] = queryParser({
850
+ query,
851
+ entityClass: BlogPost,
852
+ });
853
+
854
+ expect(options).toEqual({
855
+ class: BlogPost,
856
+ });
857
+
858
+ expect(selectors).toEqual([
859
+ {
860
+ type: '&',
861
+ '!tag': ['tagname'],
862
+ },
863
+ ]);
864
+ });
865
+
866
+ it('parses truthy clauses', () => {
867
+ const query = '[truthyname]';
868
+ const [options, ...selectors] = queryParser({
869
+ query,
870
+ entityClass: BlogPost,
871
+ });
872
+
873
+ expect(options).toEqual({
874
+ class: BlogPost,
875
+ });
876
+
877
+ expect(selectors).toEqual([
878
+ {
879
+ type: '&',
880
+ truthy: ['truthyname'],
881
+ },
882
+ ]);
883
+ });
884
+
885
+ it('parses not truthy clauses', () => {
886
+ const query = '-[truthyname]';
887
+ const [options, ...selectors] = queryParser({
888
+ query,
889
+ entityClass: BlogPost,
890
+ });
891
+
892
+ expect(options).toEqual({
893
+ class: BlogPost,
894
+ });
895
+
896
+ expect(selectors).toEqual([
897
+ {
898
+ type: '&',
899
+ '!truthy': ['truthyname'],
900
+ },
901
+ ]);
902
+ });
903
+
904
+ it('parses ref clauses', () => {
905
+ const query = 'prop<{111111111111111111111111}>';
906
+ const [options, ...selectors] = queryParser({
907
+ query,
908
+ entityClass: BlogPost,
909
+ });
910
+
911
+ expect(options).toEqual({
912
+ class: BlogPost,
913
+ });
914
+
915
+ expect(selectors).toEqual([
916
+ {
917
+ type: '&',
918
+ ref: [['prop', '111111111111111111111111']],
919
+ },
920
+ ]);
921
+ });
922
+
923
+ it('parses not ref clauses', () => {
924
+ const query = '-prop<{111111111111111111111111}>';
925
+ const [options, ...selectors] = queryParser({
926
+ query,
927
+ entityClass: BlogPost,
928
+ });
929
+
930
+ expect(options).toEqual({
931
+ class: BlogPost,
932
+ });
933
+
934
+ expect(selectors).toEqual([
935
+ {
936
+ type: '&',
937
+ '!ref': [['prop', '111111111111111111111111']],
938
+ },
939
+ ]);
940
+ });
941
+
942
+ it('parses simple contain clauses', () => {
943
+ const query = 'prop<string>';
944
+ const [options, ...selectors] = queryParser({
945
+ query,
946
+ entityClass: BlogPost,
947
+ });
948
+
949
+ expect(options).toEqual({
950
+ class: BlogPost,
951
+ });
952
+
953
+ expect(selectors).toEqual([
954
+ {
955
+ type: '&',
956
+ contain: [['prop', 'string']],
957
+ },
958
+ ]);
959
+ });
960
+
961
+ it('parses simple not contain clauses', () => {
962
+ const query = '-prop<string>';
178
963
  const [options, ...selectors] = queryParser({
179
964
  query,
180
965
  entityClass: BlogPost,
@@ -187,13 +972,13 @@ describe('queryParser', () => {
187
972
  expect(selectors).toEqual([
188
973
  {
189
974
  type: '&',
190
- equal: [['prop', 'string']],
975
+ '!contain': [['prop', 'string']],
191
976
  },
192
977
  ]);
193
978
  });
194
979
 
195
- it('parses simple not equal clauses', () => {
196
- const query = 'prop!=string';
980
+ it('parses json contain clauses', () => {
981
+ const query = 'prop<true>';
197
982
  const [options, ...selectors] = queryParser({
198
983
  query,
199
984
  entityClass: BlogPost,
@@ -206,13 +991,13 @@ describe('queryParser', () => {
206
991
  expect(selectors).toEqual([
207
992
  {
208
993
  type: '&',
209
- '!equal': [['prop', 'string']],
994
+ contain: [['prop', true]],
210
995
  },
211
996
  ]);
212
997
  });
213
998
 
214
- it('parses quoted equal clauses', () => {
215
- const query = 'prop="a string"';
999
+ it('parses json not contain clauses', () => {
1000
+ const query = '-prop<true>';
216
1001
  const [options, ...selectors] = queryParser({
217
1002
  query,
218
1003
  entityClass: BlogPost,
@@ -225,13 +1010,13 @@ describe('queryParser', () => {
225
1010
  expect(selectors).toEqual([
226
1011
  {
227
1012
  type: '&',
228
- equal: [['prop', 'a string']],
1013
+ '!contain': [['prop', true]],
229
1014
  },
230
1015
  ]);
231
1016
  });
232
1017
 
233
- it('parses quoted not equal clauses', () => {
234
- const query = 'prop!="a string"';
1018
+ it('parses match clauses', () => {
1019
+ const query = 'prop~/regex/';
235
1020
  const [options, ...selectors] = queryParser({
236
1021
  query,
237
1022
  entityClass: BlogPost,
@@ -244,13 +1029,13 @@ describe('queryParser', () => {
244
1029
  expect(selectors).toEqual([
245
1030
  {
246
1031
  type: '&',
247
- '!equal': [['prop', 'a string']],
1032
+ match: [['prop', 'regex']],
248
1033
  },
249
1034
  ]);
250
1035
  });
251
1036
 
252
- it('parses json equal clauses', () => {
253
- const query = 'prop=true';
1037
+ it('parses not match clauses', () => {
1038
+ const query = '-prop~/regex/';
254
1039
  const [options, ...selectors] = queryParser({
255
1040
  query,
256
1041
  entityClass: BlogPost,
@@ -263,13 +1048,13 @@ describe('queryParser', () => {
263
1048
  expect(selectors).toEqual([
264
1049
  {
265
1050
  type: '&',
266
- equal: [['prop', true]],
1051
+ '!match': [['prop', 'regex']],
267
1052
  },
268
1053
  ]);
269
1054
  });
270
1055
 
271
- it('parses json not equal clauses', () => {
272
- const query = 'prop!=true';
1056
+ it('parses imatch clauses', () => {
1057
+ const query = 'prop~/regex/i';
273
1058
  const [options, ...selectors] = queryParser({
274
1059
  query,
275
1060
  entityClass: BlogPost,
@@ -282,13 +1067,13 @@ describe('queryParser', () => {
282
1067
  expect(selectors).toEqual([
283
1068
  {
284
1069
  type: '&',
285
- '!equal': [['prop', true]],
1070
+ imatch: [['prop', 'regex']],
286
1071
  },
287
1072
  ]);
288
1073
  });
289
1074
 
290
- it('parses guid clauses', () => {
291
- const query = '{111111111111111111111111}';
1075
+ it('parses not imatch clauses', () => {
1076
+ const query = '-prop~/regex/i';
292
1077
  const [options, ...selectors] = queryParser({
293
1078
  query,
294
1079
  entityClass: BlogPost,
@@ -301,13 +1086,13 @@ describe('queryParser', () => {
301
1086
  expect(selectors).toEqual([
302
1087
  {
303
1088
  type: '&',
304
- guid: ['111111111111111111111111'],
1089
+ '!imatch': [['prop', 'regex']],
305
1090
  },
306
1091
  ]);
307
1092
  });
308
1093
 
309
- it('parses not guid clauses', () => {
310
- const query = '{!111111111111111111111111}';
1094
+ it('parses simple like clauses', () => {
1095
+ const query = 'prop~pattern';
311
1096
  const [options, ...selectors] = queryParser({
312
1097
  query,
313
1098
  entityClass: BlogPost,
@@ -320,13 +1105,13 @@ describe('queryParser', () => {
320
1105
  expect(selectors).toEqual([
321
1106
  {
322
1107
  type: '&',
323
- '!guid': ['111111111111111111111111'],
1108
+ like: [['prop', 'pattern']],
324
1109
  },
325
1110
  ]);
326
1111
  });
327
1112
 
328
- it('parses tag clauses', () => {
329
- const query = '<tagname>';
1113
+ it('parses simple not like clauses', () => {
1114
+ const query = '-prop~pattern';
330
1115
  const [options, ...selectors] = queryParser({
331
1116
  query,
332
1117
  entityClass: BlogPost,
@@ -339,13 +1124,13 @@ describe('queryParser', () => {
339
1124
  expect(selectors).toEqual([
340
1125
  {
341
1126
  type: '&',
342
- tag: ['tagname'],
1127
+ '!like': [['prop', 'pattern']],
343
1128
  },
344
1129
  ]);
345
1130
  });
346
1131
 
347
- it('parses not tag clauses', () => {
348
- const query = '<!tagname>';
1132
+ it('parses quoted like clauses', () => {
1133
+ const query = 'prop~"a pattern"';
349
1134
  const [options, ...selectors] = queryParser({
350
1135
  query,
351
1136
  entityClass: BlogPost,
@@ -358,13 +1143,13 @@ describe('queryParser', () => {
358
1143
  expect(selectors).toEqual([
359
1144
  {
360
1145
  type: '&',
361
- '!tag': ['tagname'],
1146
+ like: [['prop', 'a pattern']],
362
1147
  },
363
1148
  ]);
364
1149
  });
365
1150
 
366
- it('parses truthy clauses', () => {
367
- const query = '[truthyname]';
1151
+ it('parses quoted not like clauses', () => {
1152
+ const query = '-prop~"a pattern"';
368
1153
  const [options, ...selectors] = queryParser({
369
1154
  query,
370
1155
  entityClass: BlogPost,
@@ -377,13 +1162,13 @@ describe('queryParser', () => {
377
1162
  expect(selectors).toEqual([
378
1163
  {
379
1164
  type: '&',
380
- truthy: ['truthyname'],
1165
+ '!like': [['prop', 'a pattern']],
381
1166
  },
382
1167
  ]);
383
1168
  });
384
1169
 
385
- it('parses not truthy clauses', () => {
386
- const query = '[!truthyname]';
1170
+ it('parses ilike clauses', () => {
1171
+ const query = 'prop~"a pattern"i';
387
1172
  const [options, ...selectors] = queryParser({
388
1173
  query,
389
1174
  entityClass: BlogPost,
@@ -396,13 +1181,13 @@ describe('queryParser', () => {
396
1181
  expect(selectors).toEqual([
397
1182
  {
398
1183
  type: '&',
399
- '!truthy': ['truthyname'],
1184
+ ilike: [['prop', 'a pattern']],
400
1185
  },
401
1186
  ]);
402
1187
  });
403
1188
 
404
- it('parses ref clauses', () => {
405
- const query = 'prop<{111111111111111111111111}>';
1189
+ it('parses not ilike clauses', () => {
1190
+ const query = '-prop~"a pattern"i';
406
1191
  const [options, ...selectors] = queryParser({
407
1192
  query,
408
1193
  entityClass: BlogPost,
@@ -415,13 +1200,13 @@ describe('queryParser', () => {
415
1200
  expect(selectors).toEqual([
416
1201
  {
417
1202
  type: '&',
418
- ref: [['prop', '111111111111111111111111']],
1203
+ '!ilike': [['prop', 'a pattern']],
419
1204
  },
420
1205
  ]);
421
1206
  });
422
1207
 
423
- it('parses not ref clauses', () => {
424
- const query = 'prop!<{111111111111111111111111}>';
1208
+ it('parses simple gt clauses', () => {
1209
+ const query = 'prop>30.5';
425
1210
  const [options, ...selectors] = queryParser({
426
1211
  query,
427
1212
  entityClass: BlogPost,
@@ -434,13 +1219,13 @@ describe('queryParser', () => {
434
1219
  expect(selectors).toEqual([
435
1220
  {
436
1221
  type: '&',
437
- '!ref': [['prop', '111111111111111111111111']],
1222
+ gt: [['prop', 30.5]],
438
1223
  },
439
1224
  ]);
440
1225
  });
441
1226
 
442
- it('parses simple contain clauses', () => {
443
- const query = 'prop<string>';
1227
+ it('parses simple not gt clauses', () => {
1228
+ const query = '-prop>30.5';
444
1229
  const [options, ...selectors] = queryParser({
445
1230
  query,
446
1231
  entityClass: BlogPost,
@@ -453,13 +1238,13 @@ describe('queryParser', () => {
453
1238
  expect(selectors).toEqual([
454
1239
  {
455
1240
  type: '&',
456
- contain: [['prop', 'string']],
1241
+ '!gt': [['prop', 30.5]],
457
1242
  },
458
1243
  ]);
459
1244
  });
460
1245
 
461
- it('parses simple not contain clauses', () => {
462
- const query = 'prop!<string>';
1246
+ it('parses simple relative gt clauses', () => {
1247
+ const query = 'prop>yesterday';
463
1248
  const [options, ...selectors] = queryParser({
464
1249
  query,
465
1250
  entityClass: BlogPost,
@@ -472,13 +1257,13 @@ describe('queryParser', () => {
472
1257
  expect(selectors).toEqual([
473
1258
  {
474
1259
  type: '&',
475
- '!contain': [['prop', 'string']],
1260
+ gt: [['prop', null, 'yesterday']],
476
1261
  },
477
1262
  ]);
478
1263
  });
479
1264
 
480
- it('parses json contain clauses', () => {
481
- const query = 'prop<true>';
1265
+ it('parses simple relative not gt clauses', () => {
1266
+ const query = '-prop>yesterday';
482
1267
  const [options, ...selectors] = queryParser({
483
1268
  query,
484
1269
  entityClass: BlogPost,
@@ -491,13 +1276,13 @@ describe('queryParser', () => {
491
1276
  expect(selectors).toEqual([
492
1277
  {
493
1278
  type: '&',
494
- contain: [['prop', true]],
1279
+ '!gt': [['prop', null, 'yesterday']],
495
1280
  },
496
1281
  ]);
497
1282
  });
498
1283
 
499
- it('parses json not contain clauses', () => {
500
- const query = 'prop!<true>';
1284
+ it('parses quote relative gt clauses', () => {
1285
+ const query = 'prop>"3 weeks ago"';
501
1286
  const [options, ...selectors] = queryParser({
502
1287
  query,
503
1288
  entityClass: BlogPost,
@@ -510,13 +1295,13 @@ describe('queryParser', () => {
510
1295
  expect(selectors).toEqual([
511
1296
  {
512
1297
  type: '&',
513
- '!contain': [['prop', true]],
1298
+ gt: [['prop', null, '3 weeks ago']],
514
1299
  },
515
1300
  ]);
516
1301
  });
517
1302
 
518
- it('parses match clauses', () => {
519
- const query = 'prop~/regex/';
1303
+ it('parses quote relative not gt clauses', () => {
1304
+ const query = '-prop>"3 weeks ago"';
520
1305
  const [options, ...selectors] = queryParser({
521
1306
  query,
522
1307
  entityClass: BlogPost,
@@ -529,13 +1314,13 @@ describe('queryParser', () => {
529
1314
  expect(selectors).toEqual([
530
1315
  {
531
1316
  type: '&',
532
- match: [['prop', 'regex']],
1317
+ '!gt': [['prop', null, '3 weeks ago']],
533
1318
  },
534
1319
  ]);
535
1320
  });
536
1321
 
537
- it('parses not match clauses', () => {
538
- const query = 'prop!~/regex/';
1322
+ it('parses simple gte clauses', () => {
1323
+ const query = 'prop>=30.5';
539
1324
  const [options, ...selectors] = queryParser({
540
1325
  query,
541
1326
  entityClass: BlogPost,
@@ -548,13 +1333,13 @@ describe('queryParser', () => {
548
1333
  expect(selectors).toEqual([
549
1334
  {
550
1335
  type: '&',
551
- '!match': [['prop', 'regex']],
1336
+ gte: [['prop', 30.5]],
552
1337
  },
553
1338
  ]);
554
1339
  });
555
1340
 
556
- it('parses imatch clauses', () => {
557
- const query = 'prop~/regex/i';
1341
+ it('parses simple not gte clauses', () => {
1342
+ const query = '-prop>=30.5';
558
1343
  const [options, ...selectors] = queryParser({
559
1344
  query,
560
1345
  entityClass: BlogPost,
@@ -567,13 +1352,13 @@ describe('queryParser', () => {
567
1352
  expect(selectors).toEqual([
568
1353
  {
569
1354
  type: '&',
570
- imatch: [['prop', 'regex']],
1355
+ '!gte': [['prop', 30.5]],
571
1356
  },
572
1357
  ]);
573
1358
  });
574
1359
 
575
- it('parses not imatch clauses', () => {
576
- const query = 'prop!~/regex/i';
1360
+ it('parses simple relative gte clauses', () => {
1361
+ const query = 'prop>=yesterday';
577
1362
  const [options, ...selectors] = queryParser({
578
1363
  query,
579
1364
  entityClass: BlogPost,
@@ -586,13 +1371,13 @@ describe('queryParser', () => {
586
1371
  expect(selectors).toEqual([
587
1372
  {
588
1373
  type: '&',
589
- '!imatch': [['prop', 'regex']],
1374
+ gte: [['prop', null, 'yesterday']],
590
1375
  },
591
1376
  ]);
592
1377
  });
593
1378
 
594
- it('parses simple like clauses', () => {
595
- const query = 'prop~pattern';
1379
+ it('parses simple relative not gte clauses', () => {
1380
+ const query = '-prop>=yesterday';
596
1381
  const [options, ...selectors] = queryParser({
597
1382
  query,
598
1383
  entityClass: BlogPost,
@@ -605,13 +1390,13 @@ describe('queryParser', () => {
605
1390
  expect(selectors).toEqual([
606
1391
  {
607
1392
  type: '&',
608
- like: [['prop', 'pattern']],
1393
+ '!gte': [['prop', null, 'yesterday']],
609
1394
  },
610
1395
  ]);
611
1396
  });
612
1397
 
613
- it('parses simple not like clauses', () => {
614
- const query = 'prop!~pattern';
1398
+ it('parses quote relative gte clauses', () => {
1399
+ const query = 'prop>="3 weeks ago"';
615
1400
  const [options, ...selectors] = queryParser({
616
1401
  query,
617
1402
  entityClass: BlogPost,
@@ -624,13 +1409,13 @@ describe('queryParser', () => {
624
1409
  expect(selectors).toEqual([
625
1410
  {
626
1411
  type: '&',
627
- '!like': [['prop', 'pattern']],
1412
+ gte: [['prop', null, '3 weeks ago']],
628
1413
  },
629
1414
  ]);
630
1415
  });
631
1416
 
632
- it('parses quoted like clauses', () => {
633
- const query = 'prop~"a pattern"';
1417
+ it('parses quote relative not gte clauses', () => {
1418
+ const query = '-prop>="3 weeks ago"';
634
1419
  const [options, ...selectors] = queryParser({
635
1420
  query,
636
1421
  entityClass: BlogPost,
@@ -643,13 +1428,13 @@ describe('queryParser', () => {
643
1428
  expect(selectors).toEqual([
644
1429
  {
645
1430
  type: '&',
646
- like: [['prop', 'a pattern']],
1431
+ '!gte': [['prop', null, '3 weeks ago']],
647
1432
  },
648
1433
  ]);
649
1434
  });
650
1435
 
651
- it('parses quoted not like clauses', () => {
652
- const query = 'prop!~"a pattern"';
1436
+ it('parses simple lt clauses', () => {
1437
+ const query = 'prop<30.5';
653
1438
  const [options, ...selectors] = queryParser({
654
1439
  query,
655
1440
  entityClass: BlogPost,
@@ -662,13 +1447,13 @@ describe('queryParser', () => {
662
1447
  expect(selectors).toEqual([
663
1448
  {
664
1449
  type: '&',
665
- '!like': [['prop', 'a pattern']],
1450
+ lt: [['prop', 30.5]],
666
1451
  },
667
1452
  ]);
668
1453
  });
669
1454
 
670
- it('parses ilike clauses', () => {
671
- const query = 'prop~"a pattern"i';
1455
+ it('parses simple not lt clauses', () => {
1456
+ const query = '-prop<30.5';
672
1457
  const [options, ...selectors] = queryParser({
673
1458
  query,
674
1459
  entityClass: BlogPost,
@@ -681,13 +1466,13 @@ describe('queryParser', () => {
681
1466
  expect(selectors).toEqual([
682
1467
  {
683
1468
  type: '&',
684
- ilike: [['prop', 'a pattern']],
1469
+ '!lt': [['prop', 30.5]],
685
1470
  },
686
1471
  ]);
687
1472
  });
688
1473
 
689
- it('parses not ilike clauses', () => {
690
- const query = 'prop!~"a pattern"i';
1474
+ it('parses simple relative lt clauses', () => {
1475
+ const query = 'prop<yesterday';
691
1476
  const [options, ...selectors] = queryParser({
692
1477
  query,
693
1478
  entityClass: BlogPost,
@@ -700,13 +1485,13 @@ describe('queryParser', () => {
700
1485
  expect(selectors).toEqual([
701
1486
  {
702
1487
  type: '&',
703
- '!ilike': [['prop', 'a pattern']],
1488
+ lt: [['prop', null, 'yesterday']],
704
1489
  },
705
1490
  ]);
706
1491
  });
707
1492
 
708
- it('parses simple gt clauses', () => {
709
- const query = 'prop>30.5';
1493
+ it('parses simple relative not lt clauses', () => {
1494
+ const query = '-prop<yesterday';
710
1495
  const [options, ...selectors] = queryParser({
711
1496
  query,
712
1497
  entityClass: BlogPost,
@@ -719,13 +1504,13 @@ describe('queryParser', () => {
719
1504
  expect(selectors).toEqual([
720
1505
  {
721
1506
  type: '&',
722
- gt: [['prop', 30.5]],
1507
+ '!lt': [['prop', null, 'yesterday']],
723
1508
  },
724
1509
  ]);
725
1510
  });
726
1511
 
727
- it('parses simple relative gt clauses', () => {
728
- const query = 'prop>yesterday';
1512
+ it('parses quote relative lt clauses', () => {
1513
+ const query = 'prop<"3 weeks ago"';
729
1514
  const [options, ...selectors] = queryParser({
730
1515
  query,
731
1516
  entityClass: BlogPost,
@@ -738,13 +1523,13 @@ describe('queryParser', () => {
738
1523
  expect(selectors).toEqual([
739
1524
  {
740
1525
  type: '&',
741
- gt: [['prop', null, 'yesterday']],
1526
+ lt: [['prop', null, '3 weeks ago']],
742
1527
  },
743
1528
  ]);
744
1529
  });
745
1530
 
746
- it('parses quote relative gt clauses', () => {
747
- const query = 'prop>"3 weeks ago"';
1531
+ it('parses quote relative not lt clauses', () => {
1532
+ const query = '-prop<"3 weeks ago"';
748
1533
  const [options, ...selectors] = queryParser({
749
1534
  query,
750
1535
  entityClass: BlogPost,
@@ -757,13 +1542,13 @@ describe('queryParser', () => {
757
1542
  expect(selectors).toEqual([
758
1543
  {
759
1544
  type: '&',
760
- gt: [['prop', null, '3 weeks ago']],
1545
+ '!lt': [['prop', null, '3 weeks ago']],
761
1546
  },
762
1547
  ]);
763
1548
  });
764
1549
 
765
- it('parses simple gte clauses', () => {
766
- const query = 'prop>=30.5';
1550
+ it('parses simple lte clauses', () => {
1551
+ const query = 'prop<=30.5';
767
1552
  const [options, ...selectors] = queryParser({
768
1553
  query,
769
1554
  entityClass: BlogPost,
@@ -776,13 +1561,13 @@ describe('queryParser', () => {
776
1561
  expect(selectors).toEqual([
777
1562
  {
778
1563
  type: '&',
779
- gte: [['prop', 30.5]],
1564
+ lte: [['prop', 30.5]],
780
1565
  },
781
1566
  ]);
782
1567
  });
783
1568
 
784
- it('parses simple relative gte clauses', () => {
785
- const query = 'prop>=yesterday';
1569
+ it('parses simple not lte clauses', () => {
1570
+ const query = '-prop<=30.5';
786
1571
  const [options, ...selectors] = queryParser({
787
1572
  query,
788
1573
  entityClass: BlogPost,
@@ -795,13 +1580,13 @@ describe('queryParser', () => {
795
1580
  expect(selectors).toEqual([
796
1581
  {
797
1582
  type: '&',
798
- gte: [['prop', null, 'yesterday']],
1583
+ '!lte': [['prop', 30.5]],
799
1584
  },
800
1585
  ]);
801
1586
  });
802
1587
 
803
- it('parses quote relative gte clauses', () => {
804
- const query = 'prop>="3 weeks ago"';
1588
+ it('parses simple relative lte clauses', () => {
1589
+ const query = 'prop<=yesterday';
805
1590
  const [options, ...selectors] = queryParser({
806
1591
  query,
807
1592
  entityClass: BlogPost,
@@ -814,13 +1599,13 @@ describe('queryParser', () => {
814
1599
  expect(selectors).toEqual([
815
1600
  {
816
1601
  type: '&',
817
- gte: [['prop', null, '3 weeks ago']],
1602
+ lte: [['prop', null, 'yesterday']],
818
1603
  },
819
1604
  ]);
820
1605
  });
821
1606
 
822
- it('parses simple lt clauses', () => {
823
- const query = 'prop<30.5';
1607
+ it('parses simple relative not lte clauses', () => {
1608
+ const query = '-prop<=yesterday';
824
1609
  const [options, ...selectors] = queryParser({
825
1610
  query,
826
1611
  entityClass: BlogPost,
@@ -833,13 +1618,13 @@ describe('queryParser', () => {
833
1618
  expect(selectors).toEqual([
834
1619
  {
835
1620
  type: '&',
836
- lt: [['prop', 30.5]],
1621
+ '!lte': [['prop', null, 'yesterday']],
837
1622
  },
838
1623
  ]);
839
1624
  });
840
1625
 
841
- it('parses simple relative lt clauses', () => {
842
- const query = 'prop<yesterday';
1626
+ it('parses quote relative lte clauses', () => {
1627
+ const query = 'prop<="3 weeks ago"';
843
1628
  const [options, ...selectors] = queryParser({
844
1629
  query,
845
1630
  entityClass: BlogPost,
@@ -852,13 +1637,13 @@ describe('queryParser', () => {
852
1637
  expect(selectors).toEqual([
853
1638
  {
854
1639
  type: '&',
855
- lt: [['prop', null, 'yesterday']],
1640
+ lte: [['prop', null, '3 weeks ago']],
856
1641
  },
857
1642
  ]);
858
1643
  });
859
1644
 
860
- it('parses quote relative lt clauses', () => {
861
- const query = 'prop<"3 weeks ago"';
1645
+ it('parses quote relative not lte clauses', () => {
1646
+ const query = '-prop<="3 weeks ago"';
862
1647
  const [options, ...selectors] = queryParser({
863
1648
  query,
864
1649
  entityClass: BlogPost,
@@ -871,13 +1656,14 @@ describe('queryParser', () => {
871
1656
  expect(selectors).toEqual([
872
1657
  {
873
1658
  type: '&',
874
- lt: [['prop', null, '3 weeks ago']],
1659
+ '!lte': [['prop', null, '3 weeks ago']],
875
1660
  },
876
1661
  ]);
877
1662
  });
878
1663
 
879
- it('parses simple lte clauses', () => {
880
- const query = 'prop<=30.5';
1664
+ it('parses complex selectors', () => {
1665
+ const query =
1666
+ '(! [published] <draft>) (| -cdate>"6 months ago" mdate>"1 month ago")';
881
1667
  const [options, ...selectors] = queryParser({
882
1668
  query,
883
1669
  entityClass: BlogPost,
@@ -890,13 +1676,25 @@ describe('queryParser', () => {
890
1676
  expect(selectors).toEqual([
891
1677
  {
892
1678
  type: '&',
893
- lte: [['prop', 30.5]],
1679
+ selector: [
1680
+ {
1681
+ type: '|',
1682
+ '!gt': [['cdate', null, '6 months ago']],
1683
+ gt: [['mdate', null, '1 month ago']],
1684
+ },
1685
+ {
1686
+ type: '!&',
1687
+ truthy: ['published'],
1688
+ tag: ['draft'],
1689
+ },
1690
+ ],
894
1691
  },
895
1692
  ]);
896
1693
  });
897
1694
 
898
- it('parses simple relative lte clauses', () => {
899
- const query = 'prop<=yesterday';
1695
+ it('parses nested selectors', () => {
1696
+ const query =
1697
+ '(! [published] <draft> (| -cdate>"6 months ago" mdate>"1 month ago"))';
900
1698
  const [options, ...selectors] = queryParser({
901
1699
  query,
902
1700
  entityClass: BlogPost,
@@ -908,17 +1706,37 @@ describe('queryParser', () => {
908
1706
 
909
1707
  expect(selectors).toEqual([
910
1708
  {
911
- type: '&',
912
- lte: [['prop', null, 'yesterday']],
1709
+ type: '!&',
1710
+ truthy: ['published'],
1711
+ tag: ['draft'],
1712
+ selector: [
1713
+ {
1714
+ type: '|',
1715
+ '!gt': [['cdate', null, '6 months ago']],
1716
+ gt: [['mdate', null, '1 month ago']],
1717
+ },
1718
+ ],
913
1719
  },
914
1720
  ]);
915
1721
  });
916
1722
 
917
- it('parses quote relative lte clauses', () => {
918
- const query = 'prop<="3 weeks ago"';
1723
+ it('parses nested qref clauses', () => {
1724
+ const query =
1725
+ 'categories<{Category parent<{Category list<{List frosting name(favorite)}>}> -parent<{Category id="sort+newsletters"}>}>';
919
1726
  const [options, ...selectors] = queryParser({
920
1727
  query,
921
1728
  entityClass: BlogPost,
1729
+ defaultFields: ['title', 'body'],
1730
+ qrefMap: {
1731
+ Category: {
1732
+ class: Category,
1733
+ defaultFields: ['name'],
1734
+ },
1735
+ List: {
1736
+ class: List,
1737
+ defaultFields: ['name'],
1738
+ },
1739
+ },
922
1740
  });
923
1741
 
924
1742
  expect(options).toEqual({
@@ -928,14 +1746,62 @@ describe('queryParser', () => {
928
1746
  expect(selectors).toEqual([
929
1747
  {
930
1748
  type: '&',
931
- lte: [['prop', null, '3 weeks ago']],
1749
+ qref: [
1750
+ [
1751
+ 'categories',
1752
+ [
1753
+ { class: Category },
1754
+ {
1755
+ type: '&',
1756
+ qref: [
1757
+ [
1758
+ 'parent',
1759
+ [
1760
+ { class: Category },
1761
+ {
1762
+ type: '&',
1763
+ qref: [
1764
+ [
1765
+ 'list',
1766
+ [
1767
+ { class: List },
1768
+ {
1769
+ type: '&',
1770
+ search: [['name', 'favorite']],
1771
+ },
1772
+ {
1773
+ type: '|',
1774
+ ilike: [['name', '%frosting%']],
1775
+ },
1776
+ ],
1777
+ ],
1778
+ ],
1779
+ },
1780
+ ],
1781
+ ],
1782
+ ],
1783
+ '!qref': [
1784
+ [
1785
+ 'parent',
1786
+ [
1787
+ { class: Category },
1788
+ {
1789
+ type: '&',
1790
+ equal: [['id', 'sort+newsletters']],
1791
+ },
1792
+ ],
1793
+ ],
1794
+ ],
1795
+ },
1796
+ ],
1797
+ ],
1798
+ ],
932
1799
  },
933
1800
  ]);
934
1801
  });
935
1802
 
936
- it('parses complex selectors', () => {
937
- const query =
938
- '(! [published] <draft>) (| cdate>"6 months ago" mdate>"1 month ago")';
1803
+ it('handles badly nested qref', () => {
1804
+ const query = 'categories<{Category parent<{Category id="top"}>';
939
1805
  const [options, ...selectors] = queryParser({
940
1806
  query,
941
1807
  entityClass: BlogPost,
@@ -948,50 +1814,126 @@ describe('queryParser', () => {
948
1814
  expect(selectors).toEqual([
949
1815
  {
950
1816
  type: '&',
951
- selector: [
952
- {
953
- type: '|',
954
- gt: [
955
- ['cdate', null, '6 months ago'],
956
- ['mdate', null, '1 month ago'],
957
- ],
958
- },
959
- {
960
- type: '!&',
961
- truthy: ['published'],
962
- tag: ['draft'],
963
- },
964
- ],
1817
+ contain: [['categories', '{Category parent<{Category}']],
1818
+ equal: [['id', 'top']],
965
1819
  },
966
1820
  ]);
967
1821
  });
968
1822
 
969
- it('parses nested selectors', () => {
1823
+ it('parses a realistic query', () => {
970
1824
  const query =
971
- '(! [published] <draft> (| cdate>"6 months ago" mdate>"1 month ago"))';
1825
+ 'labels<{Label [aggbox]}> -[trash] [delivered] -<spam> -<bare> -<autoreply> -<draft> -<sent> limit:10 reverse:true';
972
1826
  const [options, ...selectors] = queryParser({
973
1827
  query,
974
- entityClass: BlogPost,
1828
+ entityClass: Mail,
1829
+ defaultFields: ['subject', 'from', 'sender'],
1830
+ qrefMap: {
1831
+ Mail: {
1832
+ class: Mail,
1833
+ defaultFields: ['subject', 'from', 'sender'],
1834
+ },
1835
+ Headers: {
1836
+ class: Headers,
1837
+ defaultFields: ['Message-ID'],
1838
+ },
1839
+ Body: {
1840
+ class: Body,
1841
+ defaultFields: ['text'],
1842
+ },
1843
+ Label: {
1844
+ class: Label,
1845
+ defaultFields: ['name', 'id'],
1846
+ },
1847
+ Domain: {
1848
+ class: Domain,
1849
+ defaultFields: ['fqdn'],
1850
+ },
1851
+ },
1852
+ bareHandler: (
1853
+ input,
1854
+ entityClass,
1855
+ defaultFields = ['subject', 'from', 'sender'],
1856
+ ) => {
1857
+ if (entityClass === Mail) {
1858
+ return {
1859
+ type: '|',
1860
+ search: defaultFields.map((field) => [field, input]) as [
1861
+ string,
1862
+ string,
1863
+ ][],
1864
+ qref: [
1865
+ [
1866
+ 'body',
1867
+ [
1868
+ { class: Body },
1869
+ {
1870
+ type: '&',
1871
+ search: ['text', input],
1872
+ },
1873
+ ],
1874
+ ],
1875
+ [
1876
+ 'body',
1877
+ [
1878
+ { class: Body },
1879
+ {
1880
+ type: '&',
1881
+ search: ['html', input],
1882
+ },
1883
+ ],
1884
+ ],
1885
+ [
1886
+ 'body',
1887
+ [
1888
+ { class: Body },
1889
+ {
1890
+ type: '&',
1891
+ search: ['attachments', input],
1892
+ },
1893
+ ],
1894
+ ],
1895
+ ],
1896
+ };
1897
+ }
1898
+ if (defaultFields.length) {
1899
+ return {
1900
+ type: '|',
1901
+ search: defaultFields.map((field) => [field, input]) as [
1902
+ string,
1903
+ string,
1904
+ ][],
1905
+ };
1906
+ }
1907
+ return {};
1908
+ },
975
1909
  });
976
1910
 
977
1911
  expect(options).toEqual({
978
- class: BlogPost,
1912
+ class: Mail,
1913
+ limit: 10,
1914
+ reverse: true,
979
1915
  });
980
1916
 
981
1917
  expect(selectors).toEqual([
982
1918
  {
983
- type: '!&',
984
- truthy: ['published'],
985
- tag: ['draft'],
986
- selector: [
987
- {
988
- type: '|',
989
- gt: [
990
- ['cdate', null, '6 months ago'],
991
- ['mdate', null, '1 month ago'],
1919
+ type: '&',
1920
+ '!tag': ['spam', 'bare', 'autoreply', 'draft', 'sent'],
1921
+ '!truthy': ['trash'],
1922
+ qref: [
1923
+ [
1924
+ 'labels',
1925
+ [
1926
+ {
1927
+ class: Label,
1928
+ },
1929
+ {
1930
+ truthy: ['aggbox'],
1931
+ type: '&',
1932
+ },
992
1933
  ],
993
- },
1934
+ ],
994
1935
  ],
1936
+ truthy: ['delivered'],
995
1937
  },
996
1938
  ]);
997
1939
  });