@dxos/echo-query 0.8.4-main.e99c46d → 0.8.4-main.ead640a

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 +528 -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 +528 -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 +74 -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 +20 -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 +26 -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 +486 -0
  44. package/src/parser/query.grammar +124 -0
  45. package/src/parser/query.test.ts +363 -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 +52 -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,124 @@
1
+ //
2
+ // lezer Query grammar
3
+ //
4
+
5
+ @top Query { expression }
6
+
7
+ expression {
8
+ Filter |
9
+ !Not Not expression |
10
+ expression !ImplicitAnd expression |
11
+ expression !And And expression |
12
+ expression !Or Or expression |
13
+ expression !Relation Relation expression |
14
+ "(" expression ")"
15
+ }
16
+
17
+ Not {
18
+ @specialize<Identifier, "NOT" | "not" | "!">
19
+ }
20
+ And {
21
+ @specialize<Identifier, "AND" | "and">
22
+ }
23
+ Or {
24
+ @specialize<Identifier, "OR" | "or">
25
+ }
26
+
27
+ Relation {
28
+ ArrowRight | ArrowLeft
29
+ }
30
+
31
+ //
32
+ // Filters
33
+ //
34
+
35
+ Filter {
36
+ TagFilter |
37
+ TextFilter |
38
+ TypeFilter |
39
+ PropertyFilter |
40
+ ObjectLiteral
41
+ }
42
+
43
+ TagFilter {
44
+ Tag
45
+ }
46
+
47
+ TextFilter {
48
+ String
49
+ }
50
+
51
+ TypeFilter {
52
+ @specialize[@name=TypeKeyword]<Identifier, "type"> ":" Identifier
53
+ }
54
+
55
+ PropertyFilter {
56
+ PropertyPath ":" Value
57
+ }
58
+
59
+ PropertyPath {
60
+ Identifier ("." Identifier)*
61
+ }
62
+
63
+ ObjectLiteral {
64
+ "{" (ObjectProperty ("," ObjectProperty)*)? "}"
65
+ }
66
+
67
+ ObjectProperty {
68
+ Identifier ":" Value
69
+ }
70
+
71
+ ArrayLiteral {
72
+ "[" (Value ("," Value)*)? "]"
73
+ }
74
+
75
+ Value {
76
+ String |
77
+ Number |
78
+ Boolean |
79
+ Null |
80
+ ObjectLiteral |
81
+ ArrayLiteral
82
+ }
83
+
84
+ @tokens {
85
+ // Supports variables and DXNs
86
+ Identifier {
87
+ $[a-zA-Z_]$[a-zA-Z0-9_./\-]*
88
+ }
89
+
90
+ Tag {
91
+ "#" $[a-zA-Z0-9_\-]+
92
+ }
93
+
94
+ String {
95
+ '"' (!["\\] | "\\" _)* '"' |
96
+ "'" (!['\\] | "\\" _)* "'"
97
+ }
98
+
99
+ Number {
100
+ "-"? @digit+ ("." @digit+)? (("e" | "E") ("+" | "-")? @digit+)?
101
+ }
102
+
103
+ Boolean { "true" | "false" }
104
+
105
+ Null { "null" }
106
+
107
+ ArrowRight { "->" }
108
+ ArrowLeft { "<-" }
109
+
110
+ space { @whitespace+ }
111
+
112
+ "{" "}" "[" "]" "(" ")"
113
+ ":" "," "."
114
+ }
115
+
116
+ @skip { space }
117
+
118
+ @precedence {
119
+ Not @right,
120
+ ImplicitAnd @left,
121
+ And @left,
122
+ Or @left,
123
+ Relation @left
124
+ }
@@ -0,0 +1,363 @@
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 { 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
+
263
+ for (const { input, expected } of tests) {
264
+ let tree: Tree;
265
+ try {
266
+ tree = queryParser.parse(input);
267
+ } catch (err) {
268
+ console.error(new Error(`Failed to parse: ${input}`, { cause: err }));
269
+ continue;
270
+ }
271
+
272
+ const cursor = tree.cursor();
273
+ const result: string[] = [];
274
+ do {
275
+ result.push(cursor.node.name);
276
+ } while (cursor.next());
277
+ expect(result, input).toEqual(expected);
278
+ }
279
+ });
280
+
281
+ it('build', ({ expect }) => {
282
+ const queryBuilder = new QueryBuilder({
283
+ tag_1: Tag.make({ label: 'foo' }),
284
+ tag_2: Tag.make({ label: 'bar' }),
285
+ });
286
+
287
+ // TODO(burdon): Test "not"
288
+ type Test = { input: string; expected: Filter.Any };
289
+ const tests: Test[] = [
290
+ // Types
291
+ {
292
+ input: 'type:dxos.org/type/Person',
293
+ expected: Filter.typename('dxos.org/type/Person'),
294
+ },
295
+ // Tags
296
+ {
297
+ input: '#foo',
298
+ expected: Filter.tag('tag_1'),
299
+ },
300
+ {
301
+ input: '#foo AND #bar',
302
+ expected: Filter.and(Filter.tag('tag_1'), Filter.tag('tag_2')),
303
+ },
304
+ {
305
+ input: '#foo #bar',
306
+ expected: Filter.and(Filter.tag('tag_1'), Filter.tag('tag_2')),
307
+ },
308
+ // Text
309
+ {
310
+ input: '"test"',
311
+ expected: Filter.text('test'),
312
+ },
313
+ // Mixed
314
+ {
315
+ input: '#foo "test"',
316
+ expected: Filter.and(Filter.tag('tag_1'), Filter.text('test')),
317
+ },
318
+ // Props
319
+ {
320
+ input: '{ name: "DXOS" }',
321
+ expected: Filter.props({ name: 'DXOS' }),
322
+ },
323
+ {
324
+ input: '{ value: 100 }',
325
+ expected: Filter.props({ value: 100 }),
326
+ },
327
+ {
328
+ input: 'type:dxos.org/type/Person OR type:dxos.org/type/Organization',
329
+ expected: Filter.or(Filter.typename('dxos.org/type/Person'), Filter.typename('dxos.org/type/Organization')),
330
+ },
331
+ {
332
+ input: '(type:dxos.org/type/Person OR type:dxos.org/type/Organization) AND { name: "DXOS" }',
333
+ expected: Filter.and(
334
+ Filter.or(Filter.typename('dxos.org/type/Person'), Filter.typename('dxos.org/type/Organization')),
335
+ Filter.props({ name: 'DXOS' }),
336
+ ),
337
+ },
338
+ {
339
+ input: 'type:dxos.org/type/Person and { name: "DXOS" }',
340
+ expected: Filter.and(Filter.typename('dxos.org/type/Person'), Filter.props({ name: 'DXOS' })),
341
+ },
342
+ // TODO(burdon): Convert Query/Filter expr to AST.
343
+ // TODO(burdon): Person -> Organization (many-to-many relation).
344
+ // Get Research Note objects for Organization objects for Person objects with jobTitle.
345
+ //
346
+ // Cypher: MATCH (p:Person)-[:WorksAt]->(o:Organization)<-[:ResearchOn]-(r:ResearchNote) WHERE p.jotTitle IS NOT NULL
347
+ // ((type:Person AND { jobTitle: "investor" }) -[:WorksAt]-> type:Organization) <-[:ResearchOn]- type:ResearchNote
348
+ //
349
+ // {
350
+ // input: '',
351
+ // expected: Query.select(Filter.typename('dxos.org/type/Person', { jobTitle: 'investor' }))
352
+ // .reference('organization')
353
+ // .targetOf(Relation.of('dxos.org/relation/ResearchOn')) // TODO(burdon): Invert?
354
+ // .source(),
355
+ // },
356
+ ];
357
+
358
+ tests.forEach(({ input, expected }) => {
359
+ const result = queryBuilder.build(input);
360
+ expect(result, JSON.stringify({ input, result, expected }, null, 2)).toEqual(expected);
361
+ });
362
+ });
363
+ });
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ export * from './query-lite';