@dxos/echo-query 0.8.4-main.f9ba587 → 0.8.4-main.fffef41

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.
Files changed (54) hide show
  1. package/README.md +1 -1
  2. package/dist/lib/browser/index.mjs +561 -0
  3. package/dist/lib/browser/index.mjs.map +4 -4
  4. package/dist/lib/browser/meta.json +1 -1
  5. package/dist/lib/node-esm/index.mjs +561 -0
  6. package/dist/lib/node-esm/index.mjs.map +4 -4
  7. package/dist/lib/node-esm/meta.json +1 -1
  8. package/dist/query-lite/index.js +387 -0
  9. package/dist/types/src/index.d.ts +1 -0
  10. package/dist/types/src/index.d.ts.map +1 -1
  11. package/dist/types/src/parser/gen/index.d.ts +8 -0
  12. package/dist/types/src/parser/gen/index.d.ts.map +1 -0
  13. package/dist/types/src/parser/gen/query.d.ts +3 -0
  14. package/dist/types/src/parser/gen/query.d.ts.map +1 -0
  15. package/dist/types/src/parser/gen/query.terms.d.ts +2 -0
  16. package/dist/types/src/parser/gen/query.terms.d.ts.map +1 -0
  17. package/dist/types/src/parser/index.d.ts +3 -0
  18. package/dist/types/src/parser/index.d.ts.map +1 -0
  19. package/dist/types/src/parser/query-builder.d.ts +82 -0
  20. package/dist/types/src/parser/query-builder.d.ts.map +1 -0
  21. package/dist/types/src/parser/query.test.d.ts +2 -0
  22. package/dist/types/src/parser/query.test.d.ts.map +1 -0
  23. package/dist/types/src/query-lite/index.d.ts +2 -0
  24. package/dist/types/src/query-lite/index.d.ts.map +1 -0
  25. package/dist/types/src/query-lite/query-lite.d.ts +8 -0
  26. package/dist/types/src/query-lite/query-lite.d.ts.map +1 -0
  27. package/dist/types/src/sandbox/query-sandbox.d.ts +21 -0
  28. package/dist/types/src/sandbox/query-sandbox.d.ts.map +1 -0
  29. package/dist/types/src/sandbox/query-sandbox.test.d.ts +2 -0
  30. package/dist/types/src/sandbox/query-sandbox.test.d.ts.map +1 -0
  31. package/dist/types/src/sandbox/quickjs.d.ts +8 -0
  32. package/dist/types/src/sandbox/quickjs.d.ts.map +1 -0
  33. package/dist/types/src/sandbox/quickjs.test.d.ts +2 -0
  34. package/dist/types/src/sandbox/quickjs.test.d.ts.map +1 -0
  35. package/dist/types/tsconfig.tsbuildinfo +1 -1
  36. package/package.json +21 -6
  37. package/src/env.d.ts +8 -0
  38. package/src/index.ts +2 -0
  39. package/src/parser/gen/index.ts +13 -0
  40. package/src/parser/gen/query.terms.ts +27 -0
  41. package/src/parser/gen/query.ts +18 -0
  42. package/src/parser/index.ts +6 -0
  43. package/src/parser/query-builder.ts +539 -0
  44. package/src/parser/query.grammar +130 -0
  45. package/src/parser/query.test.ts +416 -0
  46. package/src/query-lite/index.ts +5 -0
  47. package/src/query-lite/query-lite.ts +467 -0
  48. package/src/sandbox/query-sandbox.test.ts +53 -0
  49. package/src/sandbox/query-sandbox.ts +72 -0
  50. package/src/sandbox/quickjs.test.ts +67 -0
  51. package/src/sandbox/quickjs.ts +34 -0
  52. package/dist/types/src/search.test.d.ts +0 -2
  53. package/dist/types/src/search.test.d.ts.map +0 -1
  54. package/src/search.test.ts +0 -49
@@ -0,0 +1,130 @@
1
+ //
2
+ // lezer Query grammar
3
+ //
4
+
5
+ @top Query { expression }
6
+
7
+ expression {
8
+ Assignment |
9
+ Filter |
10
+ !Not Not expression |
11
+ expression !ImplicitAnd expression |
12
+ expression !And And expression |
13
+ expression !Or Or expression |
14
+ expression !Relation Relation expression |
15
+ "(" expression ")"
16
+ }
17
+
18
+ Assignment {
19
+ Identifier "=" "(" expression ")"
20
+ }
21
+
22
+ Not {
23
+ @specialize<Identifier, "NOT" | "not" | "!">
24
+ }
25
+ And {
26
+ @specialize<Identifier, "AND" | "and">
27
+ }
28
+ Or {
29
+ @specialize<Identifier, "OR" | "or">
30
+ }
31
+
32
+ Relation {
33
+ ArrowRight | ArrowLeft
34
+ }
35
+
36
+ //
37
+ // Filters
38
+ //
39
+
40
+ Filter {
41
+ TagFilter |
42
+ TextFilter |
43
+ TypeFilter |
44
+ PropertyFilter |
45
+ ObjectLiteral
46
+ }
47
+
48
+ TagFilter {
49
+ Tag
50
+ }
51
+
52
+ TextFilter {
53
+ String
54
+ }
55
+
56
+ TypeFilter {
57
+ @specialize[@name=TypeKeyword]<Identifier, "type"> ":" Identifier
58
+ }
59
+
60
+ PropertyFilter {
61
+ PropertyPath ":" Value
62
+ }
63
+
64
+ PropertyPath {
65
+ Identifier ("." Identifier)*
66
+ }
67
+
68
+ ObjectLiteral {
69
+ "{" (ObjectProperty ("," ObjectProperty)*)? "}"
70
+ }
71
+
72
+ ObjectProperty {
73
+ Identifier ":" Value
74
+ }
75
+
76
+ ArrayLiteral {
77
+ "[" (Value ("," Value)*)? "]"
78
+ }
79
+
80
+ Value {
81
+ String |
82
+ Number |
83
+ Boolean |
84
+ Null |
85
+ ObjectLiteral |
86
+ ArrayLiteral
87
+ }
88
+
89
+ @tokens {
90
+ // Supports variables and DXNs
91
+ Identifier {
92
+ $[a-zA-Z_]$[a-zA-Z0-9_./\-]*
93
+ }
94
+
95
+ Tag {
96
+ "#" $[a-zA-Z0-9_\-]+
97
+ }
98
+
99
+ String {
100
+ '"' (!["\\] | "\\" _)* '"' |
101
+ "'" (!['\\] | "\\" _)* "'"
102
+ }
103
+
104
+ Number {
105
+ "-"? @digit+ ("." @digit+)? (("e" | "E") ("+" | "-")? @digit+)?
106
+ }
107
+
108
+ Boolean { "true" | "false" }
109
+
110
+ Null { "null" }
111
+
112
+ ArrowRight { "->" }
113
+ ArrowLeft { "<-" }
114
+
115
+ space { @whitespace+ }
116
+
117
+ "{" "}" "[" "]" "(" ")"
118
+ ":" "," "." "="
119
+ }
120
+
121
+ @skip { space }
122
+
123
+ @precedence {
124
+ Not @right,
125
+ ImplicitAnd @left,
126
+ And @left,
127
+ Or @left,
128
+ Relation @left,
129
+ Assignment @right
130
+ }
@@ -0,0 +1,416 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { type Tree } from '@lezer/common';
6
+ import { describe, it } from 'vitest';
7
+
8
+ import { Filter, Tag } from '@dxos/echo';
9
+
10
+ import { QueryDSL } from './gen';
11
+ import { type BuildResult, QueryBuilder } from './query-builder';
12
+
13
+ // TODO(burdon): Ref/Relation traversal.
14
+
15
+ describe('query', () => {
16
+ it('parse', ({ expect }) => {
17
+ const queryParser = QueryDSL.Parser.configure({ strict: true });
18
+
19
+ type Test = { input: string; expected: string[] };
20
+ const tests: Test[] = [
21
+ // Tags
22
+ {
23
+ input: '#foo',
24
+ expected: ['Query', 'Filter', 'TagFilter', 'Tag'],
25
+ },
26
+ {
27
+ input: '#foo AND #bar',
28
+ expected: ['Query', 'Filter', 'TagFilter', 'Tag', 'And', 'Filter', 'TagFilter', 'Tag'],
29
+ },
30
+ {
31
+ input: '#foo #bar',
32
+ expected: ['Query', 'Filter', 'TagFilter', 'Tag', 'Filter', 'TagFilter', 'Tag'],
33
+ },
34
+ // Text
35
+ {
36
+ input: '"foo"',
37
+ expected: ['Query', 'Filter', 'TextFilter', 'String'],
38
+ },
39
+ {
40
+ input: '"foo" OR "bar"',
41
+ expected: ['Query', 'Filter', 'TextFilter', 'String', 'Or', 'Filter', 'TextFilter', 'String'],
42
+ },
43
+ // Mixed
44
+ {
45
+ input: '#foo AND "bar"',
46
+ expected: ['Query', 'Filter', 'TagFilter', 'Tag', 'And', 'Filter', 'TextFilter', 'String'],
47
+ },
48
+ {
49
+ input: '#foo "bar"',
50
+ expected: ['Query', 'Filter', 'TagFilter', 'Tag', 'Filter', 'TextFilter', 'String'],
51
+ },
52
+ // Type
53
+ {
54
+ input: 'type:dxos.org/type/Person',
55
+ expected: [
56
+ 'Query',
57
+ // type:dxos.org/type/Person
58
+ 'Filter',
59
+ 'TypeFilter',
60
+ 'TypeKeyword',
61
+ ':',
62
+ 'Identifier',
63
+ ],
64
+ },
65
+ {
66
+ input: '{ name: "DXOS" }',
67
+ expected: [
68
+ 'Query',
69
+ // { name: "DXOS" }
70
+ 'Filter',
71
+ 'ObjectLiteral',
72
+ '{',
73
+ 'ObjectProperty',
74
+ 'Identifier',
75
+ ':',
76
+ 'Value',
77
+ 'String',
78
+ '}',
79
+ ],
80
+ },
81
+ {
82
+ input: '{ value: 100 }',
83
+ expected: [
84
+ 'Query',
85
+ // { value: 100 }
86
+ 'Filter',
87
+ 'ObjectLiteral',
88
+ '{',
89
+ 'ObjectProperty',
90
+ 'Identifier',
91
+ ':',
92
+ 'Value',
93
+ 'Number',
94
+ '}',
95
+ ],
96
+ },
97
+ {
98
+ input: '{ value: true }',
99
+ expected: [
100
+ 'Query',
101
+ // { value: true }
102
+ 'Filter',
103
+ 'ObjectLiteral',
104
+ '{',
105
+ 'ObjectProperty',
106
+ 'Identifier',
107
+ ':',
108
+ 'Value',
109
+ 'Boolean',
110
+ '}',
111
+ ],
112
+ },
113
+ {
114
+ input: 'type:dxos.org/type/Person OR type:dxos.org/type/Organization',
115
+ expected: [
116
+ 'Query',
117
+ // type:dxos.org/type/Person
118
+ 'Filter',
119
+ 'TypeFilter',
120
+ 'TypeKeyword',
121
+ ':',
122
+ 'Identifier',
123
+ // OR
124
+ 'Or',
125
+ // type:dxos.org/type/Organization
126
+ 'Filter',
127
+ 'TypeFilter',
128
+ 'TypeKeyword',
129
+ ':',
130
+ 'Identifier',
131
+ ],
132
+ },
133
+ {
134
+ input: '(type:dxos.org/type/Person OR type:dxos.org/type/Organization) AND { name: "DXOS" }',
135
+ expected: [
136
+ 'Query',
137
+ '(',
138
+ // type:dxos.org/type/Person
139
+ 'Filter',
140
+ 'TypeFilter',
141
+ 'TypeKeyword',
142
+ ':',
143
+ 'Identifier',
144
+ // OR
145
+ 'Or',
146
+ // type:dxos.org/type/Organization
147
+ 'Filter',
148
+ 'TypeFilter',
149
+ 'TypeKeyword',
150
+ ':',
151
+ 'Identifier',
152
+ ')',
153
+ 'And',
154
+ // { name: "DXOS" }
155
+ 'Filter',
156
+ 'ObjectLiteral',
157
+ '{',
158
+ 'ObjectProperty',
159
+ 'Identifier',
160
+ ':',
161
+ 'Value',
162
+ 'String',
163
+ '}',
164
+ ],
165
+ },
166
+ {
167
+ input: 'type:dxos.org/type/Person -> type:dxos.org/type/Organization',
168
+ expected: [
169
+ 'Query',
170
+ // type:dxos.org/type/Person
171
+ 'Filter',
172
+ 'TypeFilter',
173
+ 'TypeKeyword',
174
+ ':',
175
+ 'Identifier',
176
+ 'Relation',
177
+ 'ArrowRight',
178
+ // type:dxos.org/type/Organization
179
+ 'Filter',
180
+ 'TypeFilter',
181
+ 'TypeKeyword',
182
+ ':',
183
+ 'Identifier',
184
+ ],
185
+ },
186
+ {
187
+ input: 'type:dxos.org/type/Organization <- type:dxos.org/type/Person',
188
+ expected: [
189
+ 'Query',
190
+ // type:dxos.org/type/Organization
191
+ 'Filter',
192
+ 'TypeFilter',
193
+ 'TypeKeyword',
194
+ ':',
195
+ 'Identifier',
196
+ 'Relation',
197
+ 'ArrowLeft',
198
+ // type:dxos.org/type/Person
199
+ 'Filter',
200
+ 'TypeFilter',
201
+ 'TypeKeyword',
202
+ ':',
203
+ 'Identifier',
204
+ ],
205
+ },
206
+ {
207
+ // Persons for Organizations with name "DXOS"
208
+ // TODO(burdon): Filter relations.
209
+ input: '((type:dxos.org/type/Organization AND { name: "DXOS" }) -> type:dxos.org/type/Person)',
210
+ expected: [
211
+ 'Query',
212
+ '(',
213
+ '(',
214
+ 'Filter',
215
+ 'TypeFilter',
216
+ 'TypeKeyword',
217
+ ':',
218
+ 'Identifier',
219
+ 'And',
220
+ 'Filter',
221
+ 'ObjectLiteral',
222
+ '{',
223
+ 'ObjectProperty',
224
+ 'Identifier',
225
+ ':',
226
+ 'Value',
227
+ 'String',
228
+ '}',
229
+ ')',
230
+ 'Relation',
231
+ 'ArrowRight',
232
+ 'Filter',
233
+ 'TypeFilter',
234
+ 'TypeKeyword',
235
+ ':',
236
+ 'Identifier',
237
+ ')',
238
+ ],
239
+ },
240
+ {
241
+ input: 'type:dxos.org/type/Person and { name: "DXOS" }',
242
+ expected: [
243
+ 'Query',
244
+ 'Filter',
245
+ 'TypeFilter',
246
+ 'TypeKeyword',
247
+ ':',
248
+ 'Identifier',
249
+ 'And',
250
+ 'Filter',
251
+ 'ObjectLiteral',
252
+ '{',
253
+ 'ObjectProperty',
254
+ 'Identifier',
255
+ ':',
256
+ 'Value',
257
+ 'String',
258
+ '}',
259
+ ],
260
+ },
261
+ {
262
+ input: 'x = ( type: dxos.org/type/Person )',
263
+ expected: [
264
+ 'Query',
265
+ 'Assignment',
266
+ 'Identifier',
267
+ '=',
268
+ '(',
269
+ 'Filter',
270
+ 'TypeFilter',
271
+ 'TypeKeyword',
272
+ ':',
273
+ 'Identifier',
274
+ ')',
275
+ ],
276
+ },
277
+ ];
278
+
279
+ for (const { input, expected } of tests) {
280
+ let tree: Tree;
281
+ try {
282
+ tree = queryParser.parse(input);
283
+ } catch (err) {
284
+ console.error(new Error(`Failed to parse: ${input}`, { cause: err }));
285
+ continue;
286
+ }
287
+
288
+ const cursor = tree.cursor();
289
+ const result: string[] = [];
290
+ do {
291
+ result.push(cursor.node.name);
292
+ } while (cursor.next());
293
+ expect(result, input).toEqual(expected);
294
+ }
295
+ });
296
+
297
+ it('build', ({ expect }) => {
298
+ const queryBuilder = new QueryBuilder({
299
+ tag_1: Tag.make({ label: 'foo' }),
300
+ tag_2: Tag.make({ label: 'bar' }),
301
+ });
302
+
303
+ // TODO(burdon): Test "not"
304
+ type Test = { input: string; expected: BuildResult };
305
+ const tests: Test[] = [
306
+ // Types
307
+ {
308
+ input: 'type:dxos.org/type/Person',
309
+ expected: {
310
+ filter: Filter.typename('dxos.org/type/Person'),
311
+ },
312
+ },
313
+ // Tags
314
+ {
315
+ input: '#foo',
316
+ expected: {
317
+ filter: Filter.tag('tag_1'),
318
+ },
319
+ },
320
+ {
321
+ input: '#foo AND #bar',
322
+ expected: {
323
+ filter: Filter.and(Filter.tag('tag_1'), Filter.tag('tag_2')),
324
+ },
325
+ },
326
+ {
327
+ input: '#foo #bar',
328
+ expected: {
329
+ filter: Filter.and(Filter.tag('tag_1'), Filter.tag('tag_2')),
330
+ },
331
+ },
332
+ // Text
333
+ {
334
+ input: '"test"',
335
+ expected: {
336
+ filter: Filter.text('test'),
337
+ },
338
+ },
339
+ // Mixed
340
+ {
341
+ input: '#foo "test"',
342
+ expected: {
343
+ filter: Filter.and(Filter.tag('tag_1'), Filter.text('test')),
344
+ },
345
+ },
346
+ // Props
347
+ {
348
+ input: '{ name: "DXOS" }',
349
+ expected: {
350
+ filter: Filter.props({ name: 'DXOS' }),
351
+ },
352
+ },
353
+ {
354
+ input: '{ value: 100 }',
355
+ expected: {
356
+ filter: Filter.props({ value: 100 }),
357
+ },
358
+ },
359
+ {
360
+ input: 'type:dxos.org/type/Person OR type:dxos.org/type/Organization',
361
+ expected: {
362
+ filter: Filter.or(Filter.typename('dxos.org/type/Person'), Filter.typename('dxos.org/type/Organization')),
363
+ },
364
+ },
365
+ {
366
+ input: '(type:dxos.org/type/Person OR type:dxos.org/type/Organization) AND { name: "DXOS" }',
367
+ expected: {
368
+ filter: Filter.and(
369
+ Filter.or(Filter.typename('dxos.org/type/Person'), Filter.typename('dxos.org/type/Organization')),
370
+ Filter.props({ name: 'DXOS' }),
371
+ ),
372
+ },
373
+ },
374
+ {
375
+ input: 'type:dxos.org/type/Person and { name: "DXOS" }',
376
+ expected: {
377
+ filter: Filter.and(Filter.typename('dxos.org/type/Person'), Filter.props({ name: 'DXOS' })),
378
+ },
379
+ },
380
+ // Assignment
381
+ {
382
+ input: 'x = ( type:dxos.org/type/Person )',
383
+ expected: {
384
+ name: 'x',
385
+ filter: Filter.typename('dxos.org/type/Person'),
386
+ },
387
+ },
388
+ {
389
+ input: 'x = ( #foo AND "bar" )',
390
+ expected: {
391
+ name: 'x',
392
+ filter: Filter.and(Filter.tag('tag_1'), Filter.text('bar')),
393
+ },
394
+ },
395
+ // TODO(burdon): Convert Query/Filter expr to AST.
396
+ // TODO(burdon): Person -> Organization (many-to-many relation).
397
+ // Get Research Note objects for Organization objects for Person objects with jobTitle.
398
+ //
399
+ // Cypher: MATCH (p:Person)-[:WorksAt]->(o:Organization)<-[:HasSubject]-(r:ResearchNote) WHERE p.jotTitle IS NOT NULL
400
+ // ((type:Person AND { jobTitle: "investor" }) -[:WorksAt]-> type:Organization) <-[:HasSubject]- type:ResearchNote
401
+ //
402
+ // {
403
+ // input: '',
404
+ // expected: Query.select(Filter.typename('dxos.org/type/Person', { jobTitle: 'investor' }))
405
+ // .reference('organization')
406
+ // .targetOf(Relation.of('dxos.org/relation/HasSubject')) // TODO(burdon): Invert?
407
+ // .source(),
408
+ // },
409
+ ];
410
+
411
+ tests.forEach(({ input, expected }) => {
412
+ const result = queryBuilder.build(input);
413
+ expect(result, JSON.stringify({ input, result, expected }, null, 2)).toEqual(expected);
414
+ });
415
+ });
416
+ });
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ export * from './query-lite';