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

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,10 +1,10 @@
1
1
  import type { EntityConstructor, Options, Selector } from '@nymphjs/client';
2
- import splitn from '@sciactive/splitn';
2
+ import { splitn } from '@sciactive/splitn';
3
3
 
4
4
  export type BareQueryHandler = (
5
5
  input: string,
6
6
  entityClass?: EntityConstructor,
7
- defaultFields?: string[]
7
+ defaultFields?: string[],
8
8
  ) => Partial<Selector>;
9
9
 
10
10
  export type QRefMap = {
@@ -12,7 +12,7 @@ export type QRefMap = {
12
12
  };
13
13
 
14
14
  export default function queryParser<
15
- T extends EntityConstructor = EntityConstructor
15
+ T extends EntityConstructor = EntityConstructor,
16
16
  >({
17
17
  query,
18
18
  entityClass,
@@ -27,7 +27,7 @@ export default function queryParser<
27
27
  type: '|',
28
28
  ilike: defaultFields.map((field) => [field, input]) as [
29
29
  string,
30
- string
30
+ string,
31
31
  ][],
32
32
  };
33
33
  }
@@ -90,7 +90,8 @@ function selectorsParser({
90
90
  } else if (inQuote) {
91
91
  continue;
92
92
  } else if (curQuery[i] === '(') {
93
- if (currentStart == null) {
93
+ const searchClause = i !== 0 && !curQuery[i - 1].match(/\s/);
94
+ if (currentStart == null && !searchClause) {
94
95
  currentStart = i;
95
96
  } else {
96
97
  nesting++;
@@ -144,7 +145,7 @@ function selectorsParser({
144
145
  defaultFields,
145
146
  qrefMap,
146
147
  bareHandler,
147
- })
148
+ }),
148
149
  );
149
150
  }
150
151
  }
@@ -157,21 +158,31 @@ function selectorsParser({
157
158
  });
158
159
 
159
160
  if (options) {
160
- const limitRegex = /(?: |^)limit:(\d+)(?= |$)/;
161
+ const limitRegex = /(?:\s|^)limit:(\d+)(?=\s|$)/;
161
162
  const limitMatch = curQuery.match(limitRegex);
162
163
  if (limitMatch) {
163
164
  options.limit = Number(limitMatch[1]);
164
165
  }
165
166
  curQuery = curQuery.replace(limitRegex, '');
166
167
 
167
- const offsetRegex = /(?: |^)offset:(\d+)(?= |$)/;
168
+ const offsetRegex = /(?:\s|^)offset:(\d+)(?=\s|$)/;
168
169
  const offsetMatch = curQuery.match(offsetRegex);
169
170
  if (offsetMatch) {
170
171
  options.offset = Number(offsetMatch[1]);
171
172
  }
172
173
  curQuery = curQuery.replace(offsetRegex, '');
173
174
 
174
- const reverseRegex = /(?: |^)reverse:(true|false|1|0)(?= |$)/;
175
+ // JavaScript variable names are ridiculously infeasable to check
176
+ // thoroughly, so this is a "best attempt".
177
+ const sortRegex =
178
+ /(?:\s|^)sort:(-|[_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*)(?=\s|$)/;
179
+ const sortMatch = curQuery.match(sortRegex);
180
+ if (sortMatch) {
181
+ options.sort = sortMatch[1] === '-' ? null : sortMatch[1];
182
+ }
183
+ curQuery = curQuery.replace(sortRegex, '');
184
+
185
+ const reverseRegex = /(?:\s|^)reverse:(true|false|1|0)(?=\s|$)/;
175
186
  const reverseMatch = curQuery.match(reverseRegex);
176
187
  if (reverseMatch) {
177
188
  options.reverse = reverseMatch[1] === 'true' || reverseMatch[1] === '1';
@@ -222,92 +233,270 @@ function selectorParser({
222
233
  }): string {
223
234
  let curQuery = query;
224
235
 
225
- // eg. user<{User name="Hunter"}> or user!<{User name="Hunter"}>
226
- const qrefRegex = /(?: |^)(\w+)!?<\{(\w+) (.*?[^\\])\}>(?= |$)/g;
227
- const qrefMatch = curQuery.match(qrefRegex);
228
- if (qrefMatch) {
229
- selector.qref = [];
230
- selector['!qref'] = [];
231
- for (let match of qrefMatch) {
236
+ // This one needs to happen first, because it has entire queries in it.
237
+ // eg. user<{User name="Hunter"}> or -user<{User name="Hunter"}>
238
+ const qrefRegex =
239
+ /(?:\s|^)-?([^-\s=\[\]<>{}()][^\s=\[\]<>{}()]*?)<\{(\w+)(?=\s)/;
240
+ // This is used to bypass badly formatted things that match the above regex.
241
+ let offsetIndex = 0;
242
+ let qrefMatch = curQuery.match(qrefRegex);
243
+ while (qrefMatch) {
244
+ let endIndex = (qrefMatch.index ?? 0) + offsetIndex + qrefMatch[0].length;
245
+ let curOpen = 1;
246
+ while (curOpen) {
247
+ const indexOfNextOpen = curQuery.indexOf('<{', endIndex);
248
+ const indexOfNextClose = curQuery.slice(endIndex).search(/(?<!\\)\}>/);
249
+
250
+ if (indexOfNextClose < 0) {
251
+ endIndex = curQuery.length;
252
+ break;
253
+ }
254
+
255
+ if (
256
+ indexOfNextOpen > -1 &&
257
+ indexOfNextOpen < endIndex + indexOfNextClose
258
+ ) {
259
+ endIndex = indexOfNextOpen + 2;
260
+ curOpen++;
261
+ } else {
262
+ endIndex += indexOfNextClose + 2;
263
+ curOpen--;
264
+ }
265
+ }
266
+
267
+ if (!('qref' in selector)) {
268
+ selector.qref = [];
269
+ }
270
+ if (!('!qref' in selector)) {
271
+ selector['!qref'] = [];
272
+ }
273
+
274
+ const match = curQuery.slice(
275
+ (qrefMatch.index ?? 0) + offsetIndex,
276
+ endIndex,
277
+ );
278
+ try {
279
+ let [name, value] = splitn(match.trim().slice(0, -1), '<', 2);
280
+ value = unQuoteCurlies(value.slice(1, -1));
281
+ let [className, qrefQuery] = splitn(value, ' ', 2);
282
+ const EntityClass = qrefMap[className].class;
283
+ if (EntityClass == null) {
284
+ throw new Error();
285
+ }
286
+ const qref = queryParser({
287
+ query: qrefQuery,
288
+ entityClass: EntityClass,
289
+ defaultFields: qrefMap[className].defaultFields,
290
+ qrefMap,
291
+ bareHandler,
292
+ });
293
+ if (name.startsWith('-')) {
294
+ (selector['!qref'] as [string, any][]).push([name.slice(1), qref]);
295
+ } else {
296
+ (selector.qref as [string, any][]).push([name, qref]);
297
+ }
298
+ } catch (e: any) {
299
+ offsetIndex = endIndex;
300
+ qrefMatch = curQuery.slice(offsetIndex).match(qrefRegex);
301
+
302
+ if (!selector.qref?.length) {
303
+ delete selector.qref;
304
+ }
305
+ if (!selector['!qref']?.length) {
306
+ delete selector['!qref'];
307
+ }
308
+
309
+ continue;
310
+ }
311
+ if (!selector.qref?.length) {
312
+ delete selector.qref;
313
+ }
314
+ if (!selector['!qref']?.length) {
315
+ delete selector['!qref'];
316
+ }
317
+
318
+ curQuery =
319
+ curQuery.slice(0, (qrefMatch.index ?? 0) + offsetIndex) +
320
+ ' ' +
321
+ curQuery.slice(endIndex);
322
+ qrefMatch = curQuery.slice(offsetIndex).match(qrefRegex);
323
+ }
324
+
325
+ // eg. prop(some search string) or -prop(some search string)
326
+ const searchRegex =
327
+ /(?:\s|^)-?([^-\s=\[\]<>{}()][^\s=\[\]<>{}()]*?)\([^\)]+\)(?=\s|\)|\}>|$)/g;
328
+ const searchMatch = curQuery.match(searchRegex);
329
+ if (searchMatch) {
330
+ selector.search = [];
331
+ selector['!search'] = [];
332
+ for (let match of searchMatch) {
232
333
  try {
233
- let [name, value] = splitn(match.trim().slice(0, -1), '<', 2);
234
- value = unQuoteCurlies(value.slice(1, -1));
235
- let [className, qrefQuery] = value.split(' ', 2);
236
- const EntityClass = qrefMap[className].class;
237
- if (EntityClass == null) {
238
- continue;
334
+ let [name, value] = splitn(match.trim().slice(0, -1), '(', 2);
335
+ if (name.startsWith('-')) {
336
+ selector['!search'].push([name.slice(1), value]);
337
+ } else {
338
+ selector.search.push([name, value]);
239
339
  }
240
- const qref = queryParser({
241
- query: qrefQuery,
242
- entityClass: EntityClass,
243
- defaultFields: qrefMap[className].defaultFields,
244
- qrefMap,
245
- bareHandler,
246
- });
247
- if (name.endsWith('!')) {
248
- selector['!qref'].push([name.slice(0, -1), qref]);
340
+ } catch (e: any) {
341
+ continue;
342
+ }
343
+ }
344
+ if (!selector.search.length) {
345
+ delete selector.search;
346
+ }
347
+ if (!selector['!search'].length) {
348
+ delete selector['!search'];
349
+ }
350
+ }
351
+ curQuery = curQuery.replace(searchRegex, '');
352
+
353
+ // eg. name:Marty or name:"Marty McFly" or -name:'Marty McFly'
354
+ const searchColonRegex =
355
+ /(?:\s|^)(?!(limit|offset|sort|reverse):)-?([^-\s=\[\]<>{}()][^\s=\[\]<>{}()]*?):("[^"]*?"|'[^']*?'|[^\s]+)(?=\s|\)|\}>|$)/g;
356
+ const searchColonMatch = curQuery.match(searchColonRegex);
357
+ if (searchColonMatch) {
358
+ if (!('search' in selector)) {
359
+ selector.search = [];
360
+ }
361
+ if (!('!search' in selector)) {
362
+ selector['!search'] = [];
363
+ }
364
+ for (let match of searchColonMatch) {
365
+ try {
366
+ let [name, value] = splitn(match.trim(), ':', 2);
367
+ if (name.startsWith('-')) {
368
+ (selector['!search'] as [string, string][]).push([
369
+ name.slice(1),
370
+ value,
371
+ ]);
249
372
  } else {
250
- selector.qref.push([name, qref]);
373
+ (selector.search as [string, string][]).push([name, value]);
251
374
  }
252
375
  } catch (e: any) {
253
376
  continue;
254
377
  }
255
378
  }
256
- if (!selector.qref.length) {
257
- delete selector.qref;
379
+ if (selector.search == null || !selector.search.length) {
380
+ delete selector.search;
258
381
  }
259
- if (!selector['!qref'].length) {
260
- delete selector['!qref'];
382
+ if (selector['!search'] == null || !selector['!search'].length) {
383
+ delete selector['!search'];
261
384
  }
262
385
  }
263
- curQuery = curQuery.replace(qrefRegex, '');
386
+ curQuery = curQuery.replace(searchColonRegex, '');
264
387
 
265
- // eg. name=Marty or name="Marty McFly" or enabled=true or someArray=[1,2]
266
- const equalRegex = /(?: |^)(\w+)!?=(""|".*?[^\\]"|[^ ]+)(?= |$)/g;
388
+ // eg. someArray=[1,2] or someObject={"prop":"some value"}
389
+ const equalJsonRegex =
390
+ /(?:\s|^)-?([^-\s=\[\]<>{}()][^\s=\[\]<>{}()]*?)=(\{|\[)/g;
391
+ const equalJsonMatch = [...curQuery.matchAll(equalJsonRegex)];
392
+ if (equalJsonMatch) {
393
+ if (!('equal' in selector)) {
394
+ selector.equal = [];
395
+ }
396
+ if (!('!equal' in selector)) {
397
+ selector['!equal'] = [];
398
+ }
399
+ // Work backward to find all long JSON values.
400
+ for (let i = equalJsonMatch.length - 1; i >= 0; i--) {
401
+ const match = equalJsonMatch[i];
402
+ let [name, opener] = splitn(match[0].trim(), '=', 2);
403
+ let start = match.index + match[0].length - 1;
404
+ let nextEndToken = curQuery.indexOf(opener === '{' ? '}' : ']', start);
405
+ while (nextEndToken !== -1) {
406
+ try {
407
+ if (name.startsWith('-')) {
408
+ (selector['!equal'] as [string, any][]).unshift([
409
+ name.slice(1),
410
+ JSON.parse(curQuery.substring(start, nextEndToken + 1)),
411
+ ]);
412
+ } else {
413
+ (selector.equal as [string, any][]).unshift([
414
+ name,
415
+ JSON.parse(curQuery.substring(start, nextEndToken + 1)),
416
+ ]);
417
+ }
418
+
419
+ curQuery =
420
+ curQuery.substring(0, match.index) +
421
+ curQuery.substring(nextEndToken + 1);
422
+ break;
423
+ } catch (e: any) {
424
+ nextEndToken = curQuery.indexOf(
425
+ opener === '{' ? '}' : ']',
426
+ nextEndToken + 1,
427
+ );
428
+ }
429
+ }
430
+ }
431
+ if (selector.equal == null || !selector.equal.length) {
432
+ delete selector.equal;
433
+ }
434
+ if (selector['!equal'] == null || !selector['!equal'].length) {
435
+ delete selector['!equal'];
436
+ }
437
+ }
438
+
439
+ // eg. name=Marty or name="Marty McFly" or enabled=true
440
+ const equalRegex =
441
+ /(?:\s|^)-?([^-\s=\[\]<>{}()][^\s=\[\]<>{}()]*?)=(""|".*?[^\\]"|[^\s]+)(?=\s|\)|\}>|$)/g;
267
442
  const equalMatch = curQuery.match(equalRegex);
268
443
  if (equalMatch) {
269
- selector.equal = [];
270
- selector['!equal'] = [];
444
+ if (!('equal' in selector)) {
445
+ selector.equal = [];
446
+ }
447
+ if (!('!equal' in selector)) {
448
+ selector['!equal'] = [];
449
+ }
271
450
  for (let match of equalMatch) {
272
451
  try {
273
- let [name, value] = match.trim().split('=', 2);
452
+ let [name, value] = splitn(match.trim(), '=', 2);
274
453
  try {
275
- if (name.endsWith('!')) {
276
- selector['!equal'].push([name.slice(0, -1), JSON.parse(value)]);
454
+ if (name.startsWith('-')) {
455
+ (selector['!equal'] as [string, any][]).push([
456
+ name.slice(1),
457
+ JSON.parse(value),
458
+ ]);
277
459
  } else {
278
- selector.equal.push([name, JSON.parse(value)]);
460
+ (selector.equal as [string, any][]).push([name, JSON.parse(value)]);
279
461
  }
280
462
  } catch (e: any) {
281
- if (name.endsWith('!')) {
282
- selector['!equal'].push([name.slice(0, -1), unQuoteString(value)]);
463
+ if (name.startsWith('-')) {
464
+ (selector['!equal'] as [string, any][]).push([
465
+ name.slice(1),
466
+ unQuoteString(value),
467
+ ]);
283
468
  } else {
284
- selector.equal.push([name, unQuoteString(value)]);
469
+ (selector.equal as [string, any][]).push([
470
+ name,
471
+ unQuoteString(value),
472
+ ]);
285
473
  }
286
474
  }
287
475
  } catch (e: any) {
288
476
  continue;
289
477
  }
290
478
  }
291
- if (!selector.equal.length) {
479
+ if (selector.equal == null || !selector.equal.length) {
292
480
  delete selector.equal;
293
481
  }
294
- if (!selector['!equal'].length) {
482
+ if (selector['!equal'] == null || !selector['!equal'].length) {
295
483
  delete selector['!equal'];
296
484
  }
297
485
  }
298
486
  curQuery = curQuery.replace(equalRegex, '');
299
487
 
300
- // eg. user<{790274347f9b3a018c2cedee}> or user!<{790274347f9b3a018c2cedee}>
301
- const refRegex = /(?: |^)(\w+)!?<\{([0-9a-f]{24})\}>(?= |$)/g;
488
+ // eg. user<{790274347f9b3a018c2cedee}> or -user<{790274347f9b3a018c2cedee}>
489
+ const refRegex =
490
+ /(?:\s|^)-?([^-\s=\[\]<>{}()][^\s=\[\]<>{}()]*?)<\{([0-9a-f]{24})\}>(?=\s|\)|\}>|$)/g;
302
491
  const refMatch = curQuery.match(refRegex);
303
492
  if (refMatch) {
304
493
  selector.ref = [];
305
494
  selector['!ref'] = [];
306
495
  for (let match of refMatch) {
307
496
  try {
308
- let [name, value] = match.trim().slice(0, -1).split('<', 2);
309
- if (name.endsWith('!')) {
310
- selector['!ref'].push([name.slice(0, -1), value.slice(1, -1)]);
497
+ let [name, value] = splitn(match.trim().slice(0, -1), '<', 2);
498
+ if (name.startsWith('-')) {
499
+ selector['!ref'].push([name.slice(1), value.slice(1, -1)]);
311
500
  } else {
312
501
  selector.ref.push([name, value.slice(1, -1)]);
313
502
  }
@@ -324,32 +513,30 @@ function selectorParser({
324
513
  }
325
514
  curQuery = curQuery.replace(refRegex, '');
326
515
 
327
- // eg. someArrayOfNumbers<10> or someObject!<"some string">
328
- const containRegex = /(?: |^)(\w+)!?(<.*?[^\\]>)(?= |$)/g;
516
+ // eg. someArrayOfNumbers<10> or -someObject<"some string">
517
+ const containRegex =
518
+ /(?:\s|^)-?([^-\s=\[\]<>{}()][^\s=\[\]<>{}()]*?)(<(?:[^"][^>]*?|".*?[^\\]"))>(?=\s|\)|\}>|$)/g;
329
519
  const containMatch = curQuery.match(containRegex);
330
520
  if (containMatch) {
331
521
  selector.contain = [];
332
522
  selector['!contain'] = [];
333
523
  for (let match of containMatch) {
334
524
  try {
335
- let [name, value] = match.trim().slice(0, -1).split('<', 2);
525
+ let [name, value] = splitn(match.trim().slice(0, -1), '<', 2);
336
526
  try {
337
- if (name.endsWith('!')) {
527
+ if (name.startsWith('-')) {
338
528
  selector['!contain'].push([
339
- name.slice(0, -1),
340
- JSON.parse(unQuoteAngles(value)),
529
+ name.slice(1),
530
+ JSON.parse(unQuoteString(value)),
341
531
  ]);
342
532
  } else {
343
- selector.contain.push([name, JSON.parse(unQuoteAngles(value))]);
533
+ selector.contain.push([name, JSON.parse(unQuoteString(value))]);
344
534
  }
345
535
  } catch (e: any) {
346
- if (name.endsWith('!')) {
347
- selector['!contain'].push([
348
- name.slice(0, -1),
349
- unQuoteAngles(value),
350
- ]);
536
+ if (name.startsWith('-')) {
537
+ selector['!contain'].push([name.slice(1), unQuoteString(value)]);
351
538
  } else {
352
- selector.contain.push([name, unQuoteAngles(value)]);
539
+ selector.contain.push([name, unQuoteString(value)]);
353
540
  }
354
541
  }
355
542
  } catch (e: any) {
@@ -365,8 +552,9 @@ function selectorParser({
365
552
  }
366
553
  curQuery = curQuery.replace(containRegex, '');
367
554
 
368
- // eg. name~/Hunter/ or name!~/hunter/i
369
- const posixRegex = /(?: |^)(\w+)!?~(\/\/|\/.*?[^\\]\/)i?(?= |$)/g;
555
+ // eg. name~/Hunter/ or -name~/hunter/i
556
+ const posixRegex =
557
+ /(?:\s|^)-?([^-\s=\[\]<>{}()][^\s=\[\]<>{}()]*?)~(\/\/|\/.*?[^\\]\/)i?(?=\s|\)|\}>|$)/g;
370
558
  const posixMatch = curQuery.match(posixRegex);
371
559
  if (posixMatch) {
372
560
  selector.match = [];
@@ -375,16 +563,16 @@ function selectorParser({
375
563
  selector['!imatch'] = [];
376
564
  for (let match of posixMatch) {
377
565
  try {
378
- let [name, value] = match.trim().split('~', 2);
379
- if (name.endsWith('!')) {
566
+ let [name, value] = splitn(match.trim(), '~', 2);
567
+ if (name.startsWith('-')) {
380
568
  if (value.endsWith('i')) {
381
569
  selector['!imatch'].push([
382
- name.slice(0, -1),
570
+ name.slice(1),
383
571
  value.replace(/^\/|\/i$/g, ''),
384
572
  ]);
385
573
  } else {
386
574
  selector['!match'].push([
387
- name.slice(0, -1),
575
+ name.slice(1),
388
576
  value.replace(/^\/|\/$/g, ''),
389
577
  ]);
390
578
  }
@@ -414,8 +602,9 @@ function selectorParser({
414
602
  }
415
603
  curQuery = curQuery.replace(posixRegex, '');
416
604
 
417
- // eg. name~Hunter or name!~"hunter"i
418
- const likeRegex = /(?: |^)(\w+)!?~(""i?|".*?[^\\]"i?|[^ ]+)(?= |$)/g;
605
+ // eg. name~Hunter or -name~"hunter"i
606
+ const likeRegex =
607
+ /(?:\s|^)-?([^-\s=\[\]<>{}()][^\s=\[\]<>{}()]*?)~(""i?|".*?[^\\]"i?|[^\s]+)(?=\s|\)|\}>|$)/g;
419
608
  const likeMatch = curQuery.match(likeRegex);
420
609
  if (likeMatch) {
421
610
  selector.like = [];
@@ -424,15 +613,15 @@ function selectorParser({
424
613
  selector['!ilike'] = [];
425
614
  for (let match of likeMatch) {
426
615
  try {
427
- let [name, value] = match.trim().split('~', 2);
428
- if (name.endsWith('!')) {
616
+ let [name, value] = splitn(match.trim(), '~', 2);
617
+ if (name.startsWith('-')) {
429
618
  if (value.endsWith('"i')) {
430
619
  selector['!ilike'].push([
431
- name.slice(0, -1),
620
+ name.slice(1),
432
621
  unQuoteString(value.slice(0, -1)),
433
622
  ]);
434
623
  } else {
435
- selector['!like'].push([name.slice(0, -1), unQuoteString(value)]);
624
+ selector['!like'].push([name.slice(1), unQuoteString(value)]);
436
625
  }
437
626
  } else {
438
627
  if (value.endsWith('"i')) {
@@ -460,16 +649,16 @@ function selectorParser({
460
649
  }
461
650
  curQuery = curQuery.replace(likeRegex, '');
462
651
 
463
- // eg. {790274347f9b3a018c2cedee} or {!790274347f9b3a018c2cedee}
464
- const guidRegex = /(?: |^)\{!?([0-9a-f]{24})\}(?= |$)/g;
652
+ // eg. {790274347f9b3a018c2cedee} or -{790274347f9b3a018c2cedee}
653
+ const guidRegex = /(?:\s|^)-?\{([0-9a-f]{24})\}(?=\s|\)|\}>|$)/g;
465
654
  const guidMatch = curQuery.match(guidRegex);
466
655
  if (guidMatch) {
467
656
  selector.guid = [];
468
657
  selector['!guid'] = [];
469
658
  for (let match of guidMatch) {
470
659
  try {
471
- let guid = match.trim().replace(/^\{|\}$/g, '');
472
- if (guid.startsWith('!')) {
660
+ let guid = match.trim().replace(/^(-?)\{|\}$/g, '$1');
661
+ if (guid.startsWith('-')) {
473
662
  selector['!guid'].push(guid.slice(1));
474
663
  } else {
475
664
  selector.guid.push(guid);
@@ -487,16 +676,16 @@ function selectorParser({
487
676
  }
488
677
  curQuery = curQuery.replace(guidRegex, '');
489
678
 
490
- // eg. [enabled] or [!defaultPrimaryGroup]
491
- const truthyRegex = /(?: |^)\[(!?\w+)\](?= |$)/g;
679
+ // eg. [enabled] or -[defaultPrimaryGroup]
680
+ const truthyRegex = /(?:\s|^)-?\[([^\s=\[\]<>{}]+?)\](?=\s|\)|\}>|$)/g;
492
681
  const truthyMatch = curQuery.match(truthyRegex);
493
682
  if (truthyMatch) {
494
683
  selector.truthy = [];
495
684
  selector['!truthy'] = [];
496
685
  for (let match of truthyMatch) {
497
686
  try {
498
- let name = match.trim().replace(/^\[|\]$/g, '');
499
- if (name.startsWith('!')) {
687
+ let name = match.trim().replace(/^(-?)\[|\]$/g, '$1');
688
+ if (name.startsWith('-')) {
500
689
  selector['!truthy'].push(name.slice(1));
501
690
  } else {
502
691
  selector.truthy.push(name);
@@ -514,16 +703,16 @@ function selectorParser({
514
703
  }
515
704
  curQuery = curQuery.replace(truthyRegex, '');
516
705
 
517
- // eg. <archived> or <!archived>
518
- const tagRegex = /(?: |^)<(!?\w+)>(?= |$)/g;
706
+ // eg. <archived> or -<archived>
707
+ const tagRegex = /(?:\s|^)-?<(\w+)>(?=\s|\)|\}>|$)/g;
519
708
  const tagMatch = curQuery.match(tagRegex);
520
709
  if (tagMatch) {
521
710
  selector.tag = [];
522
711
  selector['!tag'] = [];
523
712
  for (let match of tagMatch) {
524
713
  try {
525
- let name = match.trim().replace(/^<|>$/g, '');
526
- if (name.startsWith('!')) {
714
+ let name = match.trim().replace(/^(-?)<|>$/g, '$1');
715
+ if (name.startsWith('-')) {
527
716
  selector['!tag'].push(name.slice(1));
528
717
  } else {
529
718
  selector.tag.push(name);
@@ -541,15 +730,21 @@ function selectorParser({
541
730
  }
542
731
  curQuery = curQuery.replace(tagRegex, '');
543
732
 
544
- // eg. cdate>15
545
- const gtRegex = /(?: |^)(\w+)>(-?\d+(?:\.\d+)?)(?= |$)/g;
733
+ // eg. cdate>15 or -cdate>15
734
+ const gtRegex =
735
+ /(?:\s|^)-?([^\s=\[\]<>{}]+?)>(-?\d+(?:\.\d+)?)(?=\s|\)|\}>|$)/g;
546
736
  const gtMatch = curQuery.match(gtRegex);
547
737
  if (gtMatch) {
548
738
  selector.gt = [];
739
+ selector['!gt'] = [];
549
740
  for (let match of gtMatch) {
550
741
  try {
551
- let [name, value] = match.trim().split('>', 2);
552
- selector.gt.push([name, Number(value)]);
742
+ let [name, value] = splitn(match.trim(), '>', 2);
743
+ if (name.startsWith('-')) {
744
+ selector['!gt'].push([name.slice(1), Number(value)]);
745
+ } else {
746
+ selector.gt.push([name, Number(value)]);
747
+ }
553
748
  } catch (e: any) {
554
749
  continue;
555
750
  }
@@ -557,24 +752,39 @@ function selectorParser({
557
752
  if (!selector.gt.length) {
558
753
  delete selector.gt;
559
754
  }
755
+ if (!selector['!gt'].length) {
756
+ delete selector['!gt'];
757
+ }
560
758
  }
561
759
  curQuery = curQuery.replace(gtRegex, '');
562
760
 
563
- // eg. cdate>yesterday or cdate>"2 days ago"
564
- const gtRelativeRegex = /(?: |^)(\w+)>(\w+|"[^"]+")(?= |$)/g;
761
+ // eg. cdate>yesterday or -cdate>"2 days ago"
762
+ const gtRelativeRegex =
763
+ /(?:\s|^)-?([^\s=\[\]<>{}]+?)>(\w+|"[^"]+")(?=\s|\)|\}>|$)/g;
565
764
  const gtRelativeMatch = curQuery.match(gtRelativeRegex);
566
765
  if (gtRelativeMatch) {
567
766
  if (selector.gt == null) {
568
767
  selector.gt = [];
569
768
  }
769
+ if (selector['!gt'] == null) {
770
+ selector['!gt'] = [];
771
+ }
570
772
  for (let match of gtRelativeMatch) {
571
773
  try {
572
- let [name, value] = match.trim().split('>', 2);
573
- (selector.gt as [string, null, string][]).push([
574
- name,
575
- null,
576
- value.replace(/"/g, ''),
577
- ]);
774
+ let [name, value] = splitn(match.trim(), '>', 2);
775
+ if (name.startsWith('-')) {
776
+ (selector['!gt'] as [string, null, string][]).push([
777
+ name.slice(1),
778
+ null,
779
+ value.replace(/"/g, ''),
780
+ ]);
781
+ } else {
782
+ (selector.gt as [string, null, string][]).push([
783
+ name,
784
+ null,
785
+ value.replace(/"/g, ''),
786
+ ]);
787
+ }
578
788
  } catch (e: any) {
579
789
  continue;
580
790
  }
@@ -582,18 +792,27 @@ function selectorParser({
582
792
  if (!selector.gt.length) {
583
793
  delete selector.gt;
584
794
  }
795
+ if (!selector['!gt'].length) {
796
+ delete selector['!gt'];
797
+ }
585
798
  }
586
799
  curQuery = curQuery.replace(gtRelativeRegex, '');
587
800
 
588
- // eg. cdate>=15
589
- const gteRegex = /(?: |^)(\w+)>=(-?\d+(?:\.\d+)?)(?= |$)/g;
801
+ // eg. cdate>=15 or -cdate>=15
802
+ const gteRegex =
803
+ /(?:\s|^)-?([^\s=\[\]<>{}]+?)>=(-?\d+(?:\.\d+)?)(?=\s|\)|\}>|$)/g;
590
804
  const gteMatch = curQuery.match(gteRegex);
591
805
  if (gteMatch) {
592
806
  selector.gte = [];
807
+ selector['!gte'] = [];
593
808
  for (let match of gteMatch) {
594
809
  try {
595
- let [name, value] = match.trim().split('>=', 2);
596
- selector.gte.push([name, Number(value)]);
810
+ let [name, value] = splitn(match.trim(), '>=', 2);
811
+ if (name.startsWith('-')) {
812
+ selector['!gte'].push([name.slice(1), Number(value)]);
813
+ } else {
814
+ selector.gte.push([name, Number(value)]);
815
+ }
597
816
  } catch (e: any) {
598
817
  continue;
599
818
  }
@@ -601,24 +820,39 @@ function selectorParser({
601
820
  if (!selector.gte.length) {
602
821
  delete selector.gte;
603
822
  }
823
+ if (!selector['!gte'].length) {
824
+ delete selector['!gte'];
825
+ }
604
826
  }
605
827
  curQuery = curQuery.replace(gteRegex, '');
606
828
 
607
- // eg. cdate>=yesterday or cdate>="2 days ago"
608
- const gteRelativeRegex = /(?: |^)(\w+)>=(\w+|"[^"]+")(?= |$)/g;
829
+ // eg. cdate>=yesterday or -cdate>="2 days ago"
830
+ const gteRelativeRegex =
831
+ /(?:\s|^)-?([^\s=\[\]<>{}]+?)>=(\w+|"[^"]+")(?=\s|\)|\}>|$)/g;
609
832
  const gteRelativeMatch = curQuery.match(gteRelativeRegex);
610
833
  if (gteRelativeMatch) {
611
834
  if (selector.gte == null) {
612
835
  selector.gte = [];
613
836
  }
837
+ if (selector['!gte'] == null) {
838
+ selector['!gte'] = [];
839
+ }
614
840
  for (let match of gteRelativeMatch) {
615
841
  try {
616
- let [name, value] = match.trim().split('>=', 2);
617
- (selector.gte as [string, null, string][]).push([
618
- name,
619
- null,
620
- value.replace(/"/g, ''),
621
- ]);
842
+ let [name, value] = splitn(match.trim(), '>=', 2);
843
+ if (name.startsWith('-')) {
844
+ (selector['!gte'] as [string, null, string][]).push([
845
+ name.slice(1),
846
+ null,
847
+ value.replace(/"/g, ''),
848
+ ]);
849
+ } else {
850
+ (selector.gte as [string, null, string][]).push([
851
+ name,
852
+ null,
853
+ value.replace(/"/g, ''),
854
+ ]);
855
+ }
622
856
  } catch (e: any) {
623
857
  continue;
624
858
  }
@@ -626,18 +860,27 @@ function selectorParser({
626
860
  if (!selector.gte.length) {
627
861
  delete selector.gte;
628
862
  }
863
+ if (!selector['!gte'].length) {
864
+ delete selector['!gte'];
865
+ }
629
866
  }
630
867
  curQuery = curQuery.replace(gteRelativeRegex, '');
631
868
 
632
- // eg. cdate<15
633
- const ltRegex = /(?: |^)(\w+)<(-?\d+(?:\.\d+)?)(?= |$)/g;
869
+ // eg. cdate<15 or -cdate<15
870
+ const ltRegex =
871
+ /(?:\s|^)-?([^\s=\[\]<>{}]+?)<(-?\d+(?:\.\d+)?)(?=\s|\)|\}>|$)/g;
634
872
  const ltMatch = curQuery.match(ltRegex);
635
873
  if (ltMatch) {
636
874
  selector.lt = [];
875
+ selector['!lt'] = [];
637
876
  for (let match of ltMatch) {
638
877
  try {
639
- let [name, value] = match.trim().split('<', 2);
640
- selector.lt.push([name, Number(value)]);
878
+ let [name, value] = splitn(match.trim(), '<', 2);
879
+ if (name.startsWith('-')) {
880
+ selector['!lt'].push([name.slice(1), Number(value)]);
881
+ } else {
882
+ selector.lt.push([name, Number(value)]);
883
+ }
641
884
  } catch (e: any) {
642
885
  continue;
643
886
  }
@@ -645,24 +888,39 @@ function selectorParser({
645
888
  if (!selector.lt.length) {
646
889
  delete selector.lt;
647
890
  }
891
+ if (!selector['!lt'].length) {
892
+ delete selector['!lt'];
893
+ }
648
894
  }
649
895
  curQuery = curQuery.replace(ltRegex, '');
650
896
 
651
- // eg. cdate<yesterday or cdate<"2 days ago"
652
- const ltRelativeRegex = /(?: |^)(\w+)<(\w+|"[^"]+")(?= |$)/g;
897
+ // eg. cdate<yesterday or -cdate<"2 days ago"
898
+ const ltRelativeRegex =
899
+ /(?:\s|^)-?([^\s=\[\]<>{}]+?)<(\w+|"[^"]+")(?=\s|\)|\}>|$)/g;
653
900
  const ltRelativeMatch = curQuery.match(ltRelativeRegex);
654
901
  if (ltRelativeMatch) {
655
902
  if (selector.lt == null) {
656
903
  selector.lt = [];
657
904
  }
905
+ if (selector['!lt'] == null) {
906
+ selector['!lt'] = [];
907
+ }
658
908
  for (let match of ltRelativeMatch) {
659
909
  try {
660
- let [name, value] = match.trim().split('<', 2);
661
- (selector.lt as [string, null, string][]).push([
662
- name,
663
- null,
664
- value.replace(/"/g, ''),
665
- ]);
910
+ let [name, value] = splitn(match.trim(), '<', 2);
911
+ if (name.startsWith('-')) {
912
+ (selector['!lt'] as [string, null, string][]).push([
913
+ name.slice(1),
914
+ null,
915
+ value.replace(/"/g, ''),
916
+ ]);
917
+ } else {
918
+ (selector.lt as [string, null, string][]).push([
919
+ name,
920
+ null,
921
+ value.replace(/"/g, ''),
922
+ ]);
923
+ }
666
924
  } catch (e: any) {
667
925
  continue;
668
926
  }
@@ -670,18 +928,27 @@ function selectorParser({
670
928
  if (!selector.lt.length) {
671
929
  delete selector.lt;
672
930
  }
931
+ if (!selector['!lt'].length) {
932
+ delete selector['!lt'];
933
+ }
673
934
  }
674
935
  curQuery = curQuery.replace(ltRelativeRegex, '');
675
936
 
676
- // eg. cdate<=15
677
- const lteRegex = /(?: |^)(\w+)<=(-?\d+(?:\.\d+)?)(?= |$)/g;
937
+ // eg. cdate<=15 or -cdate<=15
938
+ const lteRegex =
939
+ /(?:\s|^)-?([^\s=\[\]<>{}]+?)<=(-?\d+(?:\.\d+)?)(?=\s|\)|\}>|$)/g;
678
940
  const lteMatch = curQuery.match(lteRegex);
679
941
  if (lteMatch) {
680
942
  selector.lte = [];
943
+ selector['!lte'] = [];
681
944
  for (let match of lteMatch) {
682
945
  try {
683
- let [name, value] = match.trim().split('<=', 2);
684
- selector.lte.push([name, Number(value)]);
946
+ let [name, value] = splitn(match.trim(), '<=', 2);
947
+ if (name.startsWith('-')) {
948
+ selector['!lte'].push([name.slice(1), Number(value)]);
949
+ } else {
950
+ selector.lte.push([name, Number(value)]);
951
+ }
685
952
  } catch (e: any) {
686
953
  continue;
687
954
  }
@@ -689,24 +956,39 @@ function selectorParser({
689
956
  if (!selector.lte.length) {
690
957
  delete selector.lte;
691
958
  }
959
+ if (!selector['!lte'].length) {
960
+ delete selector['!lte'];
961
+ }
692
962
  }
693
963
  curQuery = curQuery.replace(lteRegex, '');
694
964
 
695
- // eg. cdate<=yesterday or cdate<="2 days ago"
696
- const lteRelativeRegex = /(?: |^)(\w+)<=(\w+|"[^"]+")(?= |$)/g;
965
+ // eg. cdate<=yesterday or -cdate<="2 days ago"
966
+ const lteRelativeRegex =
967
+ /(?:\s|^)-?([^\s=\[\]<>{}]+?)<=(\w+|"[^"]+")(?=\s|\)|\}>|$)/g;
697
968
  const lteRelativeMatch = curQuery.match(lteRelativeRegex);
698
969
  if (lteRelativeMatch) {
699
970
  if (selector.lte == null) {
700
971
  selector.lte = [];
701
972
  }
973
+ if (selector['!lte'] == null) {
974
+ selector['!lte'] = [];
975
+ }
702
976
  for (let match of lteRelativeMatch) {
703
977
  try {
704
- let [name, value] = match.trim().split('<=', 2);
705
- (selector.lte as [string, null, string][]).push([
706
- name,
707
- null,
708
- value.replace(/"/g, ''),
709
- ]);
978
+ let [name, value] = splitn(match.trim(), '<=', 2);
979
+ if (name.startsWith('-')) {
980
+ (selector['!lte'] as [string, null, string][]).push([
981
+ name.slice(1),
982
+ null,
983
+ value.replace(/"/g, ''),
984
+ ]);
985
+ } else {
986
+ (selector.lte as [string, null, string][]).push([
987
+ name,
988
+ null,
989
+ value.replace(/"/g, ''),
990
+ ]);
991
+ }
710
992
  } catch (e: any) {
711
993
  continue;
712
994
  }
@@ -714,6 +996,9 @@ function selectorParser({
714
996
  if (!selector.lte.length) {
715
997
  delete selector.lte;
716
998
  }
999
+ if (!selector['!lte'].length) {
1000
+ delete selector['!lte'];
1001
+ }
717
1002
  }
718
1003
  curQuery = curQuery.replace(lteRelativeRegex, '');
719
1004