@dxos/echo-query 0.8.4-main.fffef41 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +102 -5
- package/README.md +1 -1
- package/dist/lib/{browser → neutral}/index.mjs +375 -20
- package/dist/lib/neutral/index.mjs.map +7 -0
- package/dist/lib/neutral/meta.json +1 -0
- package/dist/query-lite/index.d.ts +10764 -0
- package/dist/query-lite/index.d.ts.map +1 -0
- package/dist/query-lite/index.js +572 -375
- package/dist/query-lite/index.js.map +1 -0
- package/dist/types/src/index.d.ts +1 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/parser/gen/index.d.ts.map +1 -1
- package/dist/types/src/parser/query-builder.d.ts +7 -0
- package/dist/types/src/parser/query-builder.d.ts.map +1 -1
- package/dist/types/src/query-lite/query-lite.d.ts +4 -4
- package/dist/types/src/query-lite/query-lite.d.ts.map +1 -1
- package/dist/types/src/sandbox/index.d.ts +2 -0
- package/dist/types/src/sandbox/index.d.ts.map +1 -0
- package/dist/types/src/sandbox/query-sandbox.d.ts +1 -1
- package/dist/types/src/sandbox/query-sandbox.d.ts.map +1 -1
- package/dist/types/src/sandbox/quickjs.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +23 -24
- package/src/index.ts +1 -0
- package/src/parser/query-builder.ts +276 -10
- package/src/parser/query.test.ts +151 -31
- package/src/query-lite/query-lite.ts +446 -80
- package/src/sandbox/index.ts +5 -0
- package/src/sandbox/query-sandbox.test.ts +11 -10
- package/src/sandbox/query-sandbox.ts +1 -1
- package/src/sandbox/quickjs.ts +1 -2
- package/dist/lib/browser/index.mjs.map +0 -7
- package/dist/lib/browser/meta.json +0 -1
- package/dist/lib/node-esm/index.mjs +0 -563
- package/dist/lib/node-esm/index.mjs.map +0 -7
- package/dist/lib/node-esm/meta.json +0 -1
package/src/parser/query.test.ts
CHANGED
|
@@ -3,12 +3,13 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { type Tree } from '@lezer/common';
|
|
6
|
-
import { describe, it } from 'vitest';
|
|
6
|
+
import { describe, it, test } from 'vitest';
|
|
7
7
|
|
|
8
8
|
import { Filter, Tag } from '@dxos/echo';
|
|
9
|
+
import { DXN } from '@dxos/keys';
|
|
9
10
|
|
|
10
11
|
import { QueryDSL } from './gen';
|
|
11
|
-
import { type BuildResult, QueryBuilder } from './query-builder';
|
|
12
|
+
import { type BuildResult, QueryBuilder, normalizeInput } from './query-builder';
|
|
12
13
|
|
|
13
14
|
// TODO(burdon): Ref/Relation traversal.
|
|
14
15
|
|
|
@@ -51,10 +52,10 @@ describe('query', () => {
|
|
|
51
52
|
},
|
|
52
53
|
// Type
|
|
53
54
|
{
|
|
54
|
-
input: 'type:dxos.
|
|
55
|
+
input: 'type:org.dxos.type.person',
|
|
55
56
|
expected: [
|
|
56
57
|
'Query',
|
|
57
|
-
// type:dxos.
|
|
58
|
+
// type:org.dxos.type.person
|
|
58
59
|
'Filter',
|
|
59
60
|
'TypeFilter',
|
|
60
61
|
'TypeKeyword',
|
|
@@ -111,10 +112,10 @@ describe('query', () => {
|
|
|
111
112
|
],
|
|
112
113
|
},
|
|
113
114
|
{
|
|
114
|
-
input: 'type:dxos.
|
|
115
|
+
input: 'type:org.dxos.type.person OR type:org.dxos.type.organization',
|
|
115
116
|
expected: [
|
|
116
117
|
'Query',
|
|
117
|
-
// type:dxos.
|
|
118
|
+
// type:org.dxos.type.person
|
|
118
119
|
'Filter',
|
|
119
120
|
'TypeFilter',
|
|
120
121
|
'TypeKeyword',
|
|
@@ -122,7 +123,7 @@ describe('query', () => {
|
|
|
122
123
|
'Identifier',
|
|
123
124
|
// OR
|
|
124
125
|
'Or',
|
|
125
|
-
// type:dxos.
|
|
126
|
+
// type:org.dxos.type.organization
|
|
126
127
|
'Filter',
|
|
127
128
|
'TypeFilter',
|
|
128
129
|
'TypeKeyword',
|
|
@@ -131,11 +132,11 @@ describe('query', () => {
|
|
|
131
132
|
],
|
|
132
133
|
},
|
|
133
134
|
{
|
|
134
|
-
input: '(type:dxos.
|
|
135
|
+
input: '(type:org.dxos.type.person OR type:org.dxos.type.organization) AND { name: "DXOS" }',
|
|
135
136
|
expected: [
|
|
136
137
|
'Query',
|
|
137
138
|
'(',
|
|
138
|
-
// type:dxos.
|
|
139
|
+
// type:org.dxos.type.person
|
|
139
140
|
'Filter',
|
|
140
141
|
'TypeFilter',
|
|
141
142
|
'TypeKeyword',
|
|
@@ -143,7 +144,7 @@ describe('query', () => {
|
|
|
143
144
|
'Identifier',
|
|
144
145
|
// OR
|
|
145
146
|
'Or',
|
|
146
|
-
// type:dxos.
|
|
147
|
+
// type:org.dxos.type.organization
|
|
147
148
|
'Filter',
|
|
148
149
|
'TypeFilter',
|
|
149
150
|
'TypeKeyword',
|
|
@@ -164,10 +165,10 @@ describe('query', () => {
|
|
|
164
165
|
],
|
|
165
166
|
},
|
|
166
167
|
{
|
|
167
|
-
input: 'type:dxos.
|
|
168
|
+
input: 'type:org.dxos.type.person -> type:org.dxos.type.organization',
|
|
168
169
|
expected: [
|
|
169
170
|
'Query',
|
|
170
|
-
// type:dxos.
|
|
171
|
+
// type:org.dxos.type.person
|
|
171
172
|
'Filter',
|
|
172
173
|
'TypeFilter',
|
|
173
174
|
'TypeKeyword',
|
|
@@ -175,7 +176,7 @@ describe('query', () => {
|
|
|
175
176
|
'Identifier',
|
|
176
177
|
'Relation',
|
|
177
178
|
'ArrowRight',
|
|
178
|
-
// type:dxos.
|
|
179
|
+
// type:org.dxos.type.organization
|
|
179
180
|
'Filter',
|
|
180
181
|
'TypeFilter',
|
|
181
182
|
'TypeKeyword',
|
|
@@ -184,10 +185,10 @@ describe('query', () => {
|
|
|
184
185
|
],
|
|
185
186
|
},
|
|
186
187
|
{
|
|
187
|
-
input: 'type:dxos.
|
|
188
|
+
input: 'type:org.dxos.type.organization <- type:org.dxos.type.person',
|
|
188
189
|
expected: [
|
|
189
190
|
'Query',
|
|
190
|
-
// type:dxos.
|
|
191
|
+
// type:org.dxos.type.organization
|
|
191
192
|
'Filter',
|
|
192
193
|
'TypeFilter',
|
|
193
194
|
'TypeKeyword',
|
|
@@ -195,7 +196,7 @@ describe('query', () => {
|
|
|
195
196
|
'Identifier',
|
|
196
197
|
'Relation',
|
|
197
198
|
'ArrowLeft',
|
|
198
|
-
// type:dxos.
|
|
199
|
+
// type:org.dxos.type.person
|
|
199
200
|
'Filter',
|
|
200
201
|
'TypeFilter',
|
|
201
202
|
'TypeKeyword',
|
|
@@ -206,7 +207,7 @@ describe('query', () => {
|
|
|
206
207
|
{
|
|
207
208
|
// Persons for Organizations with name "DXOS"
|
|
208
209
|
// TODO(burdon): Filter relations.
|
|
209
|
-
input: '((type:dxos.
|
|
210
|
+
input: '((type:org.dxos.type.organization AND { name: "DXOS" }) -> type:org.dxos.type.person)',
|
|
210
211
|
expected: [
|
|
211
212
|
'Query',
|
|
212
213
|
'(',
|
|
@@ -238,7 +239,7 @@ describe('query', () => {
|
|
|
238
239
|
],
|
|
239
240
|
},
|
|
240
241
|
{
|
|
241
|
-
input: 'type:dxos.
|
|
242
|
+
input: 'type:org.dxos.type.person and { name: "DXOS" }',
|
|
242
243
|
expected: [
|
|
243
244
|
'Query',
|
|
244
245
|
'Filter',
|
|
@@ -259,7 +260,7 @@ describe('query', () => {
|
|
|
259
260
|
],
|
|
260
261
|
},
|
|
261
262
|
{
|
|
262
|
-
input: 'x = ( type: dxos.
|
|
263
|
+
input: 'x = ( type: org.dxos.type.person )',
|
|
263
264
|
expected: [
|
|
264
265
|
'Query',
|
|
265
266
|
'Assignment',
|
|
@@ -305,9 +306,9 @@ describe('query', () => {
|
|
|
305
306
|
const tests: Test[] = [
|
|
306
307
|
// Types
|
|
307
308
|
{
|
|
308
|
-
input: 'type:dxos.
|
|
309
|
+
input: 'type:org.dxos.type.person',
|
|
309
310
|
expected: {
|
|
310
|
-
filter: Filter.
|
|
311
|
+
filter: Filter.type(DXN.make('org.dxos.type.person')),
|
|
311
312
|
},
|
|
312
313
|
},
|
|
313
314
|
// Tags
|
|
@@ -357,32 +358,38 @@ describe('query', () => {
|
|
|
357
358
|
},
|
|
358
359
|
},
|
|
359
360
|
{
|
|
360
|
-
input: 'type:dxos.
|
|
361
|
+
input: 'type:org.dxos.type.person OR type:org.dxos.type.organization',
|
|
361
362
|
expected: {
|
|
362
|
-
filter: Filter.or(
|
|
363
|
+
filter: Filter.or(
|
|
364
|
+
Filter.type(DXN.make('org.dxos.type.person')),
|
|
365
|
+
Filter.type(DXN.make('org.dxos.type.organization')),
|
|
366
|
+
),
|
|
363
367
|
},
|
|
364
368
|
},
|
|
365
369
|
{
|
|
366
|
-
input: '(type:dxos.
|
|
370
|
+
input: '(type:org.dxos.type.person OR type:org.dxos.type.organization) AND { name: "DXOS" }',
|
|
367
371
|
expected: {
|
|
368
372
|
filter: Filter.and(
|
|
369
|
-
Filter.or(
|
|
373
|
+
Filter.or(
|
|
374
|
+
Filter.type(DXN.make('org.dxos.type.person')),
|
|
375
|
+
Filter.type(DXN.make('org.dxos.type.organization')),
|
|
376
|
+
),
|
|
370
377
|
Filter.props({ name: 'DXOS' }),
|
|
371
378
|
),
|
|
372
379
|
},
|
|
373
380
|
},
|
|
374
381
|
{
|
|
375
|
-
input: 'type:dxos.
|
|
382
|
+
input: 'type:org.dxos.type.person and { name: "DXOS" }',
|
|
376
383
|
expected: {
|
|
377
|
-
filter: Filter.and(Filter.
|
|
384
|
+
filter: Filter.and(Filter.type(DXN.make('org.dxos.type.person')), Filter.props({ name: 'DXOS' })),
|
|
378
385
|
},
|
|
379
386
|
},
|
|
380
387
|
// Assignment
|
|
381
388
|
{
|
|
382
|
-
input: 'x = ( type:dxos.
|
|
389
|
+
input: 'x = ( type:org.dxos.type.person )',
|
|
383
390
|
expected: {
|
|
384
391
|
name: 'x',
|
|
385
|
-
filter: Filter.
|
|
392
|
+
filter: Filter.type(DXN.make('org.dxos.type.person')),
|
|
386
393
|
},
|
|
387
394
|
},
|
|
388
395
|
{
|
|
@@ -401,9 +408,9 @@ describe('query', () => {
|
|
|
401
408
|
//
|
|
402
409
|
// {
|
|
403
410
|
// input: '',
|
|
404
|
-
// expected: Query.select(Filter.typename('dxos.
|
|
411
|
+
// expected: Query.select(Filter.typename('org.dxos.type.person', { jobTitle: 'investor' }))
|
|
405
412
|
// .reference('organization')
|
|
406
|
-
// .targetOf(Relation.of('dxos.
|
|
413
|
+
// .targetOf(Relation.of('org.dxos.relation.hasSubject')) // TODO(burdon): Invert?
|
|
407
414
|
// .source(),
|
|
408
415
|
// },
|
|
409
416
|
];
|
|
@@ -413,4 +420,117 @@ describe('query', () => {
|
|
|
413
420
|
expect(result, JSON.stringify({ input, result, expected }, null, 2)).toEqual(expected);
|
|
414
421
|
});
|
|
415
422
|
});
|
|
423
|
+
|
|
424
|
+
test('normalizeInput', ({ expect }) => {
|
|
425
|
+
type Test = { input: string; expected: string };
|
|
426
|
+
const tests: Test[] = [
|
|
427
|
+
{ input: 'foo', expected: '"foo"' },
|
|
428
|
+
{ input: 'foo bar', expected: '"foo" "bar"' },
|
|
429
|
+
{ input: 'foo bar', expected: '"foo" "bar"' },
|
|
430
|
+
{ input: '"already" bare', expected: '"already" "bare"' },
|
|
431
|
+
{ input: 'from:rich@dxos.org', expected: 'from:"rich@dxos.org"' },
|
|
432
|
+
{ input: 'from:rich@dxos.org urgent', expected: 'from:"rich@dxos.org" "urgent"' },
|
|
433
|
+
{ input: 'name:DXOS', expected: 'name:"DXOS"' },
|
|
434
|
+
{ input: 'count:42', expected: 'count:42' },
|
|
435
|
+
{ input: 'active:true', expected: 'active:true' },
|
|
436
|
+
{ input: 'value:null', expected: 'value:null' },
|
|
437
|
+
{ input: 'name:"DXOS"', expected: 'name:"DXOS"' },
|
|
438
|
+
{ input: 'type:org.dxos.type.person', expected: 'type:org.dxos.type.person' },
|
|
439
|
+
{ input: '#tag', expected: '#tag' },
|
|
440
|
+
{ input: '#tag foo', expected: '#tag "foo"' },
|
|
441
|
+
{ input: 'foo AND bar', expected: '"foo" AND "bar"' },
|
|
442
|
+
{ input: 'foo OR bar', expected: '"foo" OR "bar"' },
|
|
443
|
+
{ input: 'NOT foo', expected: 'NOT "foo"' },
|
|
444
|
+
{ input: '!foo', expected: '!"foo"' },
|
|
445
|
+
{ input: '(foo bar)', expected: '("foo" "bar")' },
|
|
446
|
+
{ input: 'x = ( foo )', expected: 'x = ( "foo" )' },
|
|
447
|
+
{ input: '{ name: "DXOS" }', expected: '{ name: "DXOS" }' },
|
|
448
|
+
// Apostrophes inside barewords don't open a quoted string.
|
|
449
|
+
{ input: "don't", expected: '"don\'t"' },
|
|
450
|
+
{ input: "O'Connor", expected: '"O\'Connor"' },
|
|
451
|
+
{ input: "don't worry", expected: '"don\'t" "worry"' },
|
|
452
|
+
// Unmatched closing brace/bracket — passed through, no infinite loop.
|
|
453
|
+
{ input: 'foo}', expected: '"foo"}' },
|
|
454
|
+
{ input: 'foo]bar', expected: '"foo"]"bar"' },
|
|
455
|
+
// Genuine single-quoted string still works.
|
|
456
|
+
{ input: "'foo bar'", expected: "'foo bar'" },
|
|
457
|
+
// URLs are searched as text rather than auto-promoted to property filters.
|
|
458
|
+
{ input: 'https://dxos.org', expected: '"https://dxos.org"' },
|
|
459
|
+
{ input: 'http://example.com/foo', expected: '"http://example.com/foo"' },
|
|
460
|
+
{ input: 'mailto:rich@dxos.org', expected: '"mailto:rich@dxos.org"' },
|
|
461
|
+
// Escapes round-trip: backslashes are escaped in the literal body.
|
|
462
|
+
{ input: 'foo\\bar', expected: '"foo\\\\bar"' },
|
|
463
|
+
];
|
|
464
|
+
|
|
465
|
+
for (const { input, expected } of tests) {
|
|
466
|
+
expect(normalizeInput(input), input).toEqual(expected);
|
|
467
|
+
}
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
test('build with property and text fragments', ({ expect }) => {
|
|
471
|
+
const queryBuilder = new QueryBuilder({
|
|
472
|
+
tag_1: Tag.make({ label: 'foo' }),
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
type Test = { input: string; expected: BuildResult };
|
|
476
|
+
const tests: Test[] = [
|
|
477
|
+
// Property filter from `key:value` text input.
|
|
478
|
+
{
|
|
479
|
+
input: 'from:rich@dxos.org',
|
|
480
|
+
expected: { filter: Filter.props({ from: 'rich@dxos.org' }) },
|
|
481
|
+
},
|
|
482
|
+
{
|
|
483
|
+
input: 'name:DXOS',
|
|
484
|
+
expected: { filter: Filter.props({ name: 'DXOS' }) },
|
|
485
|
+
},
|
|
486
|
+
// Bare text fragment becomes text search.
|
|
487
|
+
{
|
|
488
|
+
input: 'urgent',
|
|
489
|
+
expected: { filter: Filter.text('urgent') },
|
|
490
|
+
},
|
|
491
|
+
// Multiple bare fragments are AND-joined.
|
|
492
|
+
{
|
|
493
|
+
input: 'urgent review',
|
|
494
|
+
expected: { filter: Filter.and(Filter.text('urgent'), Filter.text('review')) },
|
|
495
|
+
},
|
|
496
|
+
// Mixed property + text fragment.
|
|
497
|
+
{
|
|
498
|
+
input: 'from:rich@dxos.org urgent',
|
|
499
|
+
expected: {
|
|
500
|
+
filter: Filter.and(Filter.props({ from: 'rich@dxos.org' }), Filter.text('urgent')),
|
|
501
|
+
},
|
|
502
|
+
},
|
|
503
|
+
// Tag + text fragment.
|
|
504
|
+
{
|
|
505
|
+
input: '#foo bar',
|
|
506
|
+
expected: { filter: Filter.and(Filter.tag('tag_1'), Filter.text('bar')) },
|
|
507
|
+
},
|
|
508
|
+
// Three fragments AND-joined.
|
|
509
|
+
{
|
|
510
|
+
input: 'a b c',
|
|
511
|
+
expected: {
|
|
512
|
+
filter: Filter.and(Filter.text('a'), Filter.text('b'), Filter.text('c')),
|
|
513
|
+
},
|
|
514
|
+
},
|
|
515
|
+
// URLs are text-searched, not promoted to property filters.
|
|
516
|
+
{
|
|
517
|
+
input: 'https://dxos.org',
|
|
518
|
+
expected: { filter: Filter.text('https://dxos.org') },
|
|
519
|
+
},
|
|
520
|
+
{
|
|
521
|
+
input: 'mailto:rich@dxos.org',
|
|
522
|
+
expected: { filter: Filter.text('mailto:rich@dxos.org') },
|
|
523
|
+
},
|
|
524
|
+
// Escapes decode back to the original input.
|
|
525
|
+
{
|
|
526
|
+
input: 'foo\\bar',
|
|
527
|
+
expected: { filter: Filter.text('foo\\bar') },
|
|
528
|
+
},
|
|
529
|
+
];
|
|
530
|
+
|
|
531
|
+
tests.forEach(({ input, expected }) => {
|
|
532
|
+
const result = queryBuilder.build(input);
|
|
533
|
+
expect(result, JSON.stringify({ input, result, expected }, null, 2)).toEqual(expected);
|
|
534
|
+
});
|
|
535
|
+
});
|
|
416
536
|
});
|