@dxos/echo-query 0.8.4-main.ef1bc66f44 → 0.8.4-main.f466a3d56e
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/neutral/index.mjs +216 -18
- package/dist/lib/neutral/index.mjs.map +3 -3
- package/dist/lib/neutral/meta.json +1 -1
- package/dist/query-lite/index.d.ts +1451 -228
- package/dist/query-lite/index.d.ts.map +1 -1
- package/dist/query-lite/index.js +196 -20
- package/dist/query-lite/index.js.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.map +1 -1
- 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 +14 -17
- package/src/parser/query-builder.ts +269 -9
- package/src/parser/query.test.ts +144 -31
- package/src/query-lite/query-lite.ts +273 -24
- package/src/sandbox/query-sandbox.test.ts +10 -10
- package/src/sandbox/query-sandbox.ts +1 -1
|
@@ -16,7 +16,7 @@ import type { DXN, ObjectId } from '@dxos/keys';
|
|
|
16
16
|
// TODO(wittjosiah): The `export * as ...` syntax causes tsdown to genereate multiple files which breaks the sandbox.
|
|
17
17
|
|
|
18
18
|
class OrderClass implements Order$.Any {
|
|
19
|
-
private static variance: Order$.Any['~Order'] = {} as Order$.Any['~Order'];
|
|
19
|
+
private static 'variance': Order$.Any['~Order'] = {} as Order$.Any['~Order'];
|
|
20
20
|
|
|
21
21
|
static is(value: unknown): value is Order$.Any {
|
|
22
22
|
return typeof value === 'object' && value !== null && '~Order' in value;
|
|
@@ -46,7 +46,7 @@ const Order2: typeof Order$ = Order1;
|
|
|
46
46
|
export { Order2 as Order };
|
|
47
47
|
|
|
48
48
|
class FilterClass implements Filter$.Any {
|
|
49
|
-
private static variance: Filter$.Any['~Filter'] = {} as Filter$.Any['~Filter'];
|
|
49
|
+
private static 'variance': Filter$.Any['~Filter'] = {} as Filter$.Any['~Filter'];
|
|
50
50
|
|
|
51
51
|
static is(value: unknown): value is Filter$.Any {
|
|
52
52
|
return typeof value === 'object' && value !== null && '~Filter' in value;
|
|
@@ -111,7 +111,7 @@ class FilterClass implements Filter$.Any {
|
|
|
111
111
|
}
|
|
112
112
|
return new FilterClass({
|
|
113
113
|
type: 'object',
|
|
114
|
-
typename:
|
|
114
|
+
typename: makeTypeDXN(schema),
|
|
115
115
|
...propsFilterToAst(props ?? {}),
|
|
116
116
|
});
|
|
117
117
|
}
|
|
@@ -119,7 +119,7 @@ class FilterClass implements Filter$.Any {
|
|
|
119
119
|
static typename(typename: string): Filter$.Any {
|
|
120
120
|
return new FilterClass({
|
|
121
121
|
type: 'object',
|
|
122
|
-
typename:
|
|
122
|
+
typename: makeTypeDXN(typename),
|
|
123
123
|
props: {},
|
|
124
124
|
});
|
|
125
125
|
}
|
|
@@ -139,6 +139,16 @@ class FilterClass implements Filter$.Any {
|
|
|
139
139
|
});
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
+
static key(key: string, options?: Filter$.KeyFilterOptions): Filter$.Any {
|
|
143
|
+
return new FilterClass({
|
|
144
|
+
type: 'object',
|
|
145
|
+
typename: null,
|
|
146
|
+
props: {},
|
|
147
|
+
metaKey: key,
|
|
148
|
+
metaVersion: options?.version,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
142
152
|
static props<T>(props: Filter$.Props<T>): Filter$.Filter<T> {
|
|
143
153
|
return new FilterClass({
|
|
144
154
|
type: 'object',
|
|
@@ -221,7 +231,7 @@ class FilterClass implements Filter$.Any {
|
|
|
221
231
|
});
|
|
222
232
|
}
|
|
223
233
|
|
|
224
|
-
static in<T>(...values: T[]): Filter$.Filter<T
|
|
234
|
+
static in<T>(...values: T[]): Filter$.Filter<T> {
|
|
225
235
|
return new FilterClass({
|
|
226
236
|
type: 'in',
|
|
227
237
|
values,
|
|
@@ -235,7 +245,7 @@ class FilterClass implements Filter$.Any {
|
|
|
235
245
|
});
|
|
236
246
|
}
|
|
237
247
|
|
|
238
|
-
static between<T>(from: T, to: T): Filter$.Filter<
|
|
248
|
+
static between<T>(from: T, to: T): Filter$.Filter<T> {
|
|
239
249
|
return new FilterClass({
|
|
240
250
|
type: 'range',
|
|
241
251
|
from,
|
|
@@ -243,6 +253,47 @@ class FilterClass implements Filter$.Any {
|
|
|
243
253
|
});
|
|
244
254
|
}
|
|
245
255
|
|
|
256
|
+
static updated(range: { after?: Date | number; before?: Date | number }): Filter$.Any {
|
|
257
|
+
return FilterClass._timeRangeFilter('updatedAt', range);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
static created(range: { after?: Date | number; before?: Date | number }): Filter$.Any {
|
|
261
|
+
return FilterClass._timeRangeFilter('createdAt', range);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
static childOf(parents: unknown | DXN | (unknown | DXN)[], options?: { transitive?: boolean }): Filter$.Any {
|
|
265
|
+
const items = Array.isArray(parents) ? parents : [parents];
|
|
266
|
+
const dxns = items.map((item) => {
|
|
267
|
+
if (isDxnLike(item)) {
|
|
268
|
+
return item.toString();
|
|
269
|
+
}
|
|
270
|
+
throw new TypeError('childOf requires DXN values in query-lite');
|
|
271
|
+
});
|
|
272
|
+
return new FilterClass({
|
|
273
|
+
type: 'child-of',
|
|
274
|
+
parents: dxns,
|
|
275
|
+
transitive: options?.transitive ?? true,
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
private static _timeRangeFilter(
|
|
280
|
+
field: 'updatedAt' | 'createdAt',
|
|
281
|
+
range: { after?: Date | number; before?: Date | number },
|
|
282
|
+
): Filter$.Any {
|
|
283
|
+
const toMs = (d: Date | number) => (typeof d === 'number' ? d : d.getTime());
|
|
284
|
+
const filters: Filter$.Any[] = [];
|
|
285
|
+
if (range.after != null) {
|
|
286
|
+
filters.push(new FilterClass({ type: 'timestamp', field, operator: 'gte', value: toMs(range.after) }));
|
|
287
|
+
}
|
|
288
|
+
if (range.before != null) {
|
|
289
|
+
filters.push(new FilterClass({ type: 'timestamp', field, operator: 'lte', value: toMs(range.before) }));
|
|
290
|
+
}
|
|
291
|
+
if (filters.length === 0) {
|
|
292
|
+
return FilterClass.everything();
|
|
293
|
+
}
|
|
294
|
+
return filters.length === 1 ? filters[0] : FilterClass.and(...filters);
|
|
295
|
+
}
|
|
296
|
+
|
|
246
297
|
static not<F extends Filter$.Any>(filter: F): Filter$.Filter<Filter$.Type<F>> {
|
|
247
298
|
return new FilterClass({
|
|
248
299
|
type: 'not',
|
|
@@ -268,6 +319,11 @@ class FilterClass implements Filter$.Any {
|
|
|
268
319
|
});
|
|
269
320
|
}
|
|
270
321
|
|
|
322
|
+
/** Returns a human-readable string representation of a Filter AST. */
|
|
323
|
+
static pretty(filter: Filter$.Any): string {
|
|
324
|
+
return prettyFilter(filter.ast);
|
|
325
|
+
}
|
|
326
|
+
|
|
271
327
|
private constructor(public readonly ast: QueryAST.Filter) {}
|
|
272
328
|
|
|
273
329
|
'~Filter' = FilterClass.variance;
|
|
@@ -328,7 +384,7 @@ const processPredicate = (predicate: any): QueryAST.Filter => {
|
|
|
328
384
|
};
|
|
329
385
|
|
|
330
386
|
class QueryClass implements Query$.Any {
|
|
331
|
-
private static variance: Query$.Any['~Query'] = {} as Query$.Any['~Query'];
|
|
387
|
+
private static 'variance': Query$.Any['~Query'] = {} as Query$.Any['~Query'];
|
|
332
388
|
|
|
333
389
|
static is(value: unknown): value is Query$.Any {
|
|
334
390
|
return typeof value === 'object' && value !== null && '~Query' in value;
|
|
@@ -345,6 +401,22 @@ class QueryClass implements Query$.Any {
|
|
|
345
401
|
});
|
|
346
402
|
}
|
|
347
403
|
|
|
404
|
+
select(filter: Filter$.Any | Filter$.Props<any>): Query$.Any {
|
|
405
|
+
if (FilterClass.is(filter)) {
|
|
406
|
+
return new QueryClass({
|
|
407
|
+
type: 'filter',
|
|
408
|
+
selection: this.ast,
|
|
409
|
+
filter: filter.ast,
|
|
410
|
+
});
|
|
411
|
+
} else {
|
|
412
|
+
return new QueryClass({
|
|
413
|
+
type: 'filter',
|
|
414
|
+
selection: this.ast,
|
|
415
|
+
filter: FilterClass.props(filter).ast,
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
348
420
|
static type(schema: Schema.Schema.All | string, predicates?: Filter$.Props<unknown>): Query$.Any {
|
|
349
421
|
return new QueryClass({
|
|
350
422
|
type: 'select',
|
|
@@ -372,26 +444,49 @@ class QueryClass implements Query$.Any {
|
|
|
372
444
|
});
|
|
373
445
|
}
|
|
374
446
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
447
|
+
static from(source: any, options?: { includeFeeds?: boolean }): Query$.Any {
|
|
448
|
+
const baseQuery: QueryAST.Query = {
|
|
449
|
+
type: 'select',
|
|
450
|
+
filter: FilterClass.everything().ast,
|
|
451
|
+
};
|
|
452
|
+
const wrapper = new QueryClass(baseQuery);
|
|
453
|
+
return wrapper.from(source, options);
|
|
454
|
+
}
|
|
378
455
|
|
|
379
|
-
|
|
380
|
-
if (
|
|
456
|
+
from(arg: any, options?: { includeFeeds?: boolean }): Query$.Any {
|
|
457
|
+
if (arg === 'all-accessible-spaces') {
|
|
381
458
|
return new QueryClass({
|
|
382
|
-
type: '
|
|
383
|
-
|
|
384
|
-
|
|
459
|
+
type: 'from',
|
|
460
|
+
query: this.ast,
|
|
461
|
+
from: {
|
|
462
|
+
_tag: 'scope',
|
|
463
|
+
scope: {
|
|
464
|
+
...(options?.includeFeeds ? { allFeedsFromSpaces: true } : {}),
|
|
465
|
+
},
|
|
466
|
+
},
|
|
385
467
|
});
|
|
386
|
-
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if (_isScopeLike(arg)) {
|
|
387
471
|
return new QueryClass({
|
|
388
|
-
type: '
|
|
389
|
-
|
|
390
|
-
|
|
472
|
+
type: 'from',
|
|
473
|
+
query: this.ast,
|
|
474
|
+
from: { _tag: 'scope', scope: arg },
|
|
391
475
|
});
|
|
392
476
|
}
|
|
477
|
+
|
|
478
|
+
throw new TypeError('Database and Feed objects are not supported in query-lite sandbox');
|
|
393
479
|
}
|
|
394
480
|
|
|
481
|
+
/** Returns a human-readable string representation of a Query AST. */
|
|
482
|
+
static pretty(query: Query$.Any): string {
|
|
483
|
+
return prettyQuery(query.ast);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
constructor(public readonly ast: QueryAST.Query) {}
|
|
487
|
+
|
|
488
|
+
'~Query' = QueryClass.variance;
|
|
489
|
+
|
|
395
490
|
reference(key: string): Query$.Any {
|
|
396
491
|
return new QueryClass({
|
|
397
492
|
type: 'reference-traversal',
|
|
@@ -415,21 +510,21 @@ class QueryClass implements Query$.Any {
|
|
|
415
510
|
});
|
|
416
511
|
}
|
|
417
512
|
|
|
418
|
-
sourceOf(relation
|
|
513
|
+
sourceOf(relation?: Schema.Schema.All | string, predicates?: Filter$.Props<unknown> | undefined): Query$.Any {
|
|
419
514
|
return new QueryClass({
|
|
420
515
|
type: 'relation',
|
|
421
516
|
anchor: this.ast,
|
|
422
517
|
direction: 'outgoing',
|
|
423
|
-
filter: FilterClass.type(relation, predicates).ast,
|
|
518
|
+
filter: relation !== undefined ? FilterClass.type(relation, predicates).ast : undefined,
|
|
424
519
|
});
|
|
425
520
|
}
|
|
426
521
|
|
|
427
|
-
targetOf(relation
|
|
522
|
+
targetOf(relation?: Schema.Schema.All | string, predicates?: Filter$.Props<unknown> | undefined): Query$.Any {
|
|
428
523
|
return new QueryClass({
|
|
429
524
|
type: 'relation',
|
|
430
525
|
anchor: this.ast,
|
|
431
526
|
direction: 'incoming',
|
|
432
|
-
filter: FilterClass.type(relation, predicates).ast,
|
|
527
|
+
filter: relation !== undefined ? FilterClass.type(relation, predicates).ast : undefined,
|
|
433
528
|
});
|
|
434
529
|
}
|
|
435
530
|
|
|
@@ -488,6 +583,21 @@ class QueryClass implements Query$.Any {
|
|
|
488
583
|
options,
|
|
489
584
|
});
|
|
490
585
|
}
|
|
586
|
+
|
|
587
|
+
debugLabel(label: string): Query$.Any {
|
|
588
|
+
if (this.ast.type === 'options') {
|
|
589
|
+
return new QueryClass({
|
|
590
|
+
type: 'options',
|
|
591
|
+
query: this.ast.query,
|
|
592
|
+
options: { ...this.ast.options, debugLabel: label },
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
return new QueryClass({
|
|
596
|
+
type: 'options',
|
|
597
|
+
query: this.ast,
|
|
598
|
+
options: { debugLabel: label },
|
|
599
|
+
});
|
|
600
|
+
}
|
|
491
601
|
}
|
|
492
602
|
|
|
493
603
|
export const Query1: typeof Query$ = QueryClass;
|
|
@@ -498,8 +608,147 @@ const isRef = (obj: any): obj is Ref.Ref<any> => {
|
|
|
498
608
|
return obj && typeof obj === 'object' && RefTypeId in obj;
|
|
499
609
|
};
|
|
500
610
|
|
|
501
|
-
const
|
|
611
|
+
const makeTypeDXN = (typename: string) => {
|
|
502
612
|
assertArgument(typeof typename === 'string', 'typename');
|
|
503
613
|
assertArgument(!typename.startsWith('dxn:'), 'typename');
|
|
504
614
|
return `dxn:type:${typename}`;
|
|
505
615
|
};
|
|
616
|
+
|
|
617
|
+
const isDxnLike = (value: unknown): value is DXN => {
|
|
618
|
+
return (
|
|
619
|
+
typeof value === 'object' &&
|
|
620
|
+
value !== null &&
|
|
621
|
+
'toString' in value &&
|
|
622
|
+
typeof value.toString === 'function' &&
|
|
623
|
+
value.toString().startsWith('dxn:')
|
|
624
|
+
);
|
|
625
|
+
};
|
|
626
|
+
|
|
627
|
+
const SCOPE_KEYS = new Set(['spaceIds', 'feeds', 'allFeedsFromSpaces']);
|
|
628
|
+
|
|
629
|
+
const _isScopeLike = (value: unknown): value is QueryAST.Scope => {
|
|
630
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
631
|
+
return false;
|
|
632
|
+
}
|
|
633
|
+
return Object.keys(value).every((key) => SCOPE_KEYS.has(key));
|
|
634
|
+
};
|
|
635
|
+
|
|
636
|
+
const prettyFilter = (filter: QueryAST.Filter): string => {
|
|
637
|
+
switch (filter.type) {
|
|
638
|
+
case 'object': {
|
|
639
|
+
const parts: string[] = [];
|
|
640
|
+
if (filter.typename !== null) {
|
|
641
|
+
parts.push(JSON.stringify(filter.typename));
|
|
642
|
+
}
|
|
643
|
+
const propEntries = Object.entries(filter.props);
|
|
644
|
+
if (propEntries.length > 0) {
|
|
645
|
+
const propsStr = propEntries.map(([k, v]) => `${k}: ${prettyFilter(v)}`).join(', ');
|
|
646
|
+
parts.push(`{ ${propsStr} }`);
|
|
647
|
+
}
|
|
648
|
+
if (filter.id !== undefined) {
|
|
649
|
+
parts.push(`id: [${filter.id.join(', ')}]`);
|
|
650
|
+
}
|
|
651
|
+
return parts.length > 0 ? `Filter.type(${parts.join(', ')})` : 'Filter.everything()';
|
|
652
|
+
}
|
|
653
|
+
case 'compare':
|
|
654
|
+
return `Filter.${filter.operator}(${JSON.stringify(filter.value)})`;
|
|
655
|
+
case 'in':
|
|
656
|
+
return `Filter.in(${filter.values.map((v) => JSON.stringify(v)).join(', ')})`;
|
|
657
|
+
case 'contains':
|
|
658
|
+
return `Filter.contains(${JSON.stringify(filter.value)})`;
|
|
659
|
+
case 'range':
|
|
660
|
+
return `Filter.between(${JSON.stringify(filter.from)}, ${JSON.stringify(filter.to)})`;
|
|
661
|
+
case 'text-search':
|
|
662
|
+
return `Filter.text(${JSON.stringify(filter.text)})`;
|
|
663
|
+
case 'tag':
|
|
664
|
+
return `Filter.tag(${JSON.stringify(filter.tag)})`;
|
|
665
|
+
case 'child-of':
|
|
666
|
+
return `Filter.childOf([${filter.parents.map((parent) => JSON.stringify(parent)).join(', ')}], { transitive: ${filter.transitive} })`;
|
|
667
|
+
case 'timestamp':
|
|
668
|
+
return `Filter.${filter.field}.${filter.operator}(${filter.value})`;
|
|
669
|
+
case 'not':
|
|
670
|
+
return `Filter.not(${prettyFilter(filter.filter)})`;
|
|
671
|
+
case 'and':
|
|
672
|
+
return `Filter.and(${filter.filters.map(prettyFilter).join(', ')})`;
|
|
673
|
+
case 'or':
|
|
674
|
+
return `Filter.or(${filter.filters.map(prettyFilter).join(', ')})`;
|
|
675
|
+
}
|
|
676
|
+
};
|
|
677
|
+
|
|
678
|
+
const prettyQuery = (query: QueryAST.Query): string => {
|
|
679
|
+
switch (query.type) {
|
|
680
|
+
case 'select':
|
|
681
|
+
return `Query.select(${prettyFilter(query.filter)})`;
|
|
682
|
+
case 'filter':
|
|
683
|
+
return `${prettyQuery(query.selection)}.select(${prettyFilter(query.filter)})`;
|
|
684
|
+
case 'reference-traversal':
|
|
685
|
+
return `${prettyQuery(query.anchor)}.reference(${JSON.stringify(query.property)})`;
|
|
686
|
+
case 'incoming-references': {
|
|
687
|
+
const args: string[] = [];
|
|
688
|
+
if (query.typename !== null) {
|
|
689
|
+
args.push(JSON.stringify(query.typename));
|
|
690
|
+
}
|
|
691
|
+
if (query.property !== null) {
|
|
692
|
+
args.push(JSON.stringify(query.property));
|
|
693
|
+
}
|
|
694
|
+
return `${prettyQuery(query.anchor)}.referencedBy(${args.join(', ')})`;
|
|
695
|
+
}
|
|
696
|
+
case 'relation': {
|
|
697
|
+
const method =
|
|
698
|
+
query.direction === 'outgoing' ? 'sourceOf' : query.direction === 'incoming' ? 'targetOf' : 'relationOf';
|
|
699
|
+
const filterStr = query.filter !== undefined ? prettyFilter(query.filter) : '';
|
|
700
|
+
return `${prettyQuery(query.anchor)}.${method}(${filterStr})`;
|
|
701
|
+
}
|
|
702
|
+
case 'relation-traversal':
|
|
703
|
+
return `${prettyQuery(query.anchor)}.${query.direction}()`;
|
|
704
|
+
case 'hierarchy-traversal':
|
|
705
|
+
return query.direction === 'to-parent'
|
|
706
|
+
? `${prettyQuery(query.anchor)}.parent()`
|
|
707
|
+
: `${prettyQuery(query.anchor)}.children()`;
|
|
708
|
+
case 'union':
|
|
709
|
+
return `Query.all(${query.queries.map(prettyQuery).join(', ')})`;
|
|
710
|
+
case 'set-difference':
|
|
711
|
+
return `Query.without(${prettyQuery(query.source)}, ${prettyQuery(query.exclude)})`;
|
|
712
|
+
case 'order': {
|
|
713
|
+
const orders = query.order.map((o) => {
|
|
714
|
+
if (o.kind === 'natural') {
|
|
715
|
+
return 'Order.natural';
|
|
716
|
+
}
|
|
717
|
+
if (o.kind === 'rank') {
|
|
718
|
+
return `Order.rank(${JSON.stringify(o.direction)})`;
|
|
719
|
+
}
|
|
720
|
+
return `Order.property(${JSON.stringify(o.property)}, ${JSON.stringify(o.direction)})`;
|
|
721
|
+
});
|
|
722
|
+
return `${prettyQuery(query.query)}.orderBy(${orders.join(', ')})`;
|
|
723
|
+
}
|
|
724
|
+
case 'options': {
|
|
725
|
+
const parts: string[] = [];
|
|
726
|
+
if (query.options.deleted !== undefined) {
|
|
727
|
+
parts.push(`deleted: ${JSON.stringify(query.options.deleted)}`);
|
|
728
|
+
}
|
|
729
|
+
if (query.options.debugLabel !== undefined) {
|
|
730
|
+
parts.push(`debugLabel: ${JSON.stringify(query.options.debugLabel)}`);
|
|
731
|
+
}
|
|
732
|
+
return `${prettyQuery(query.query)}.options({ ${parts.join(', ')} })`;
|
|
733
|
+
}
|
|
734
|
+
case 'from': {
|
|
735
|
+
if (query.from._tag === 'scope') {
|
|
736
|
+
const scope = query.from.scope;
|
|
737
|
+
const parts: string[] = [];
|
|
738
|
+
if (scope.spaceIds !== undefined) {
|
|
739
|
+
parts.push(`spaceIds: [${scope.spaceIds.join(', ')}]`);
|
|
740
|
+
}
|
|
741
|
+
if (scope.feeds !== undefined) {
|
|
742
|
+
parts.push(`feeds: [${scope.feeds.join(', ')}]`);
|
|
743
|
+
}
|
|
744
|
+
if (scope.allFeedsFromSpaces !== undefined) {
|
|
745
|
+
parts.push(`allFeedsFromSpaces: ${scope.allFeedsFromSpaces}`);
|
|
746
|
+
}
|
|
747
|
+
return `${prettyQuery(query.query)}.from({ ${parts.join(', ')} })`;
|
|
748
|
+
}
|
|
749
|
+
return `${prettyQuery(query.query)}.from(${prettyQuery(query.from.query)})`;
|
|
750
|
+
}
|
|
751
|
+
case 'limit':
|
|
752
|
+
return `${prettyQuery(query.query)}.limit(${query.limit})`;
|
|
753
|
+
}
|
|
754
|
+
};
|
|
@@ -16,37 +16,37 @@ describe('QuerySandbox', () => {
|
|
|
16
16
|
|
|
17
17
|
test('works', { timeout: 10_000 }, async () => {
|
|
18
18
|
const ast = sandbox.eval(trim`
|
|
19
|
-
Query.select(Filter.typename('dxos.
|
|
19
|
+
Query.select(Filter.typename('org.dxos.type.person'))
|
|
20
20
|
`);
|
|
21
|
-
expect(ast).toEqual(Query.select(Filter.typename('dxos.
|
|
21
|
+
expect(ast).toEqual(Query.select(Filter.typename('org.dxos.type.person')).ast);
|
|
22
22
|
});
|
|
23
23
|
|
|
24
24
|
test('works with just Filter passed in', () => {
|
|
25
25
|
const ast = sandbox.eval(trim`
|
|
26
|
-
Filter.typename('dxos.
|
|
26
|
+
Filter.typename('org.dxos.type.person')
|
|
27
27
|
`);
|
|
28
|
-
expect(ast).toEqual(Query.select(Filter.typename('dxos.
|
|
28
|
+
expect(ast).toEqual(Query.select(Filter.typename('org.dxos.type.person')).ast);
|
|
29
29
|
});
|
|
30
30
|
|
|
31
31
|
test('Order', () => {
|
|
32
32
|
const ast = sandbox.eval(trim`
|
|
33
|
-
Query.type('dxos.
|
|
33
|
+
Query.type('org.dxos.type.person').orderBy(Order.property('name', 'desc'))
|
|
34
34
|
`);
|
|
35
|
-
expect(ast).toEqual(Query.type('dxos.
|
|
35
|
+
expect(ast).toEqual(Query.type('org.dxos.type.person').orderBy(Order.property('name', 'desc')).ast);
|
|
36
36
|
});
|
|
37
37
|
|
|
38
38
|
test('traversal', () => {
|
|
39
39
|
const ast = sandbox.eval(trim`
|
|
40
|
-
Query.select(Filter.type('dxos.
|
|
40
|
+
Query.select(Filter.type('org.dxos.type.person', { jobTitle: 'investor' }))
|
|
41
41
|
.reference('organization')
|
|
42
|
-
.targetOf('dxos.
|
|
42
|
+
.targetOf('org.dxos.relation.hasSubject')
|
|
43
43
|
.source()
|
|
44
44
|
`);
|
|
45
45
|
|
|
46
46
|
expect(ast).toEqual(
|
|
47
|
-
Query.select(Filter.type('dxos.
|
|
47
|
+
Query.select(Filter.type('org.dxos.type.person', { jobTitle: 'investor' }))
|
|
48
48
|
.reference('organization')
|
|
49
|
-
.targetOf('dxos.
|
|
49
|
+
.targetOf('org.dxos.relation.hasSubject')
|
|
50
50
|
.source().ast,
|
|
51
51
|
);
|
|
52
52
|
});
|
|
@@ -49,7 +49,7 @@ export class QuerySandbox extends Resource {
|
|
|
49
49
|
|
|
50
50
|
/**
|
|
51
51
|
* Evaluates the query code.
|
|
52
|
-
* @param queryCode Example: `Query.select(Filter.typename('dxos.
|
|
52
|
+
* @param queryCode Example: `Query.select(Filter.typename('org.dxos.type.person'))`
|
|
53
53
|
*/
|
|
54
54
|
eval(queryCode: string): QueryAST.Query {
|
|
55
55
|
using context = this.#runtime.newContext();
|