@dxos/echo-query 0.8.4-main.72ec0f3 → 0.8.4-main.74a063c4e0

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.
@@ -4,8 +4,7 @@
4
4
 
5
5
  import type * as Schema from 'effect/Schema';
6
6
 
7
- import type { Filter, Order, Query, Ref } from '@dxos/echo';
8
- import type * as Echo from '@dxos/echo';
7
+ import type { Filter as Filter$, Order as Order$, Query as Query$, Ref } from '@dxos/echo';
9
8
  import type { ForeignKey, QueryAST } from '@dxos/echo-protocol';
10
9
  import { assertArgument } from '@dxos/invariant';
11
10
  import type { DXN, ObjectId } from '@dxos/keys';
@@ -14,10 +13,12 @@ import type { DXN, ObjectId } from '@dxos/keys';
14
13
  // Light-weight implementation of query execution.
15
14
  //
16
15
 
17
- class OrderClass implements Echo.Order<any> {
18
- private static variance: Echo.Order<any>['~Order'] = {} as Echo.Order<any>['~Order'];
16
+ // TODO(wittjosiah): The `export * as ...` syntax causes tsdown to genereate multiple files which breaks the sandbox.
19
17
 
20
- static is(value: unknown): value is Echo.Order<any> {
18
+ class OrderClass implements Order$.Any {
19
+ private static 'variance': Order$.Any['~Order'] = {} as Order$.Any['~Order'];
20
+
21
+ static is(value: unknown): value is Order$.Any {
21
22
  return typeof value === 'object' && value !== null && '~Order' in value;
22
23
  }
23
24
 
@@ -27,26 +28,31 @@ class OrderClass implements Echo.Order<any> {
27
28
  }
28
29
 
29
30
  namespace Order1 {
30
- export const natural: Echo.Order<any> = new OrderClass({ kind: 'natural' });
31
- export const property = <T>(property: keyof T & string, direction: QueryAST.OrderDirection): Echo.Order<T> =>
31
+ export const natural: Order$.Any = new OrderClass({ kind: 'natural' });
32
+ export const property = <T>(property: keyof T & string, direction: QueryAST.OrderDirection): Order$.Order<T> =>
32
33
  new OrderClass({
33
34
  kind: 'property',
34
35
  property,
35
36
  direction,
36
37
  });
38
+ export const rank = <T>(direction: QueryAST.OrderDirection = 'desc'): Order$.Order<T> =>
39
+ new OrderClass({
40
+ kind: 'rank',
41
+ direction,
42
+ });
37
43
  }
38
44
 
39
- const Order2: typeof Echo.Order = Order1;
45
+ const Order2: typeof Order$ = Order1;
40
46
  export { Order2 as Order };
41
47
 
42
- class FilterClass implements Echo.Filter<any> {
43
- private static variance: Echo.Filter<any>['~Filter'] = {} as Echo.Filter<any>['~Filter'];
48
+ class FilterClass implements Filter$.Any {
49
+ private static 'variance': Filter$.Any['~Filter'] = {} as Filter$.Any['~Filter'];
44
50
 
45
- static is(value: unknown): value is Echo.Filter<any> {
51
+ static is(value: unknown): value is Filter$.Any {
46
52
  return typeof value === 'object' && value !== null && '~Filter' in value;
47
53
  }
48
54
 
49
- static fromAst(ast: QueryAST.Filter): Filter<any> {
55
+ static fromAst(ast: QueryAST.Filter): Filter$.Any {
50
56
  return new FilterClass(ast);
51
57
  }
52
58
 
@@ -77,7 +83,7 @@ class FilterClass implements Echo.Filter<any> {
77
83
  });
78
84
  }
79
85
 
80
- static ids(...ids: ObjectId[]): Echo.Filter<any> {
86
+ static id(...ids: ObjectId[]): Filter$.Any {
81
87
  // assertArgument(
82
88
  // ids.every((id) => ObjectId.isValid(id)),
83
89
  // 'ids',
@@ -98,8 +104,8 @@ class FilterClass implements Echo.Filter<any> {
98
104
 
99
105
  static type<S extends Schema.Schema.All>(
100
106
  schema: S | string,
101
- props?: Echo.Filter.Props<Schema.Schema.Type<S>>,
102
- ): Echo.Filter<Schema.Schema.Type<S>> {
107
+ props?: Filter$.Props<Schema.Schema.Type<S>>,
108
+ ): Filter$.Filter<Schema.Schema.Type<S>> {
103
109
  if (typeof schema !== 'string') {
104
110
  throw new TypeError('expected typename as the first paramter');
105
111
  }
@@ -110,7 +116,7 @@ class FilterClass implements Echo.Filter<any> {
110
116
  });
111
117
  }
112
118
 
113
- static typename(typename: string): Echo.Filter<any> {
119
+ static typename(typename: string): Filter$.Any {
114
120
  return new FilterClass({
115
121
  type: 'object',
116
122
  typename: makeTypeDxn(typename),
@@ -118,7 +124,7 @@ class FilterClass implements Echo.Filter<any> {
118
124
  });
119
125
  }
120
126
 
121
- static typeDXN(dxn: DXN): Echo.Filter<any> {
127
+ static typeDXN(dxn: DXN): Filter$.Any {
122
128
  return new FilterClass({
123
129
  type: 'object',
124
130
  typename: dxn.toString(),
@@ -126,14 +132,14 @@ class FilterClass implements Echo.Filter<any> {
126
132
  });
127
133
  }
128
134
 
129
- static tag(tag: string): Echo.Filter<any> {
135
+ static tag(tag: string): Filter$.Any {
130
136
  return new FilterClass({
131
137
  type: 'tag',
132
138
  tag,
133
139
  });
134
140
  }
135
141
 
136
- static props<T>(props: Echo.Filter.Props<T>): Echo.Filter<T> {
142
+ static props<T>(props: Filter$.Props<T>): Filter$.Filter<T> {
137
143
  return new FilterClass({
138
144
  type: 'object',
139
145
  typename: null,
@@ -141,7 +147,7 @@ class FilterClass implements Echo.Filter<any> {
141
147
  });
142
148
  }
143
149
 
144
- static text(text: string, options?: Echo.Query.TextSearchOptions): Echo.Filter<any> {
150
+ static text(text: string, options?: Filter$.TextSearchOptions): Filter$.Any {
145
151
  return new FilterClass({
146
152
  type: 'text-search',
147
153
  text,
@@ -152,7 +158,7 @@ class FilterClass implements Echo.Filter<any> {
152
158
  static foreignKeys<S extends Schema.Schema.All>(
153
159
  schema: S | string,
154
160
  keys: ForeignKey[],
155
- ): Echo.Filter<Schema.Schema.Type<S>> {
161
+ ): Filter$.Filter<Schema.Schema.Type<S>> {
156
162
  assertArgument(typeof schema === 'string', 'schema');
157
163
  assertArgument(!schema.startsWith('dxn:'), 'schema');
158
164
  return new FilterClass({
@@ -163,7 +169,7 @@ class FilterClass implements Echo.Filter<any> {
163
169
  });
164
170
  }
165
171
 
166
- static eq<T>(value: T): Echo.Filter<T> {
172
+ static eq<T>(value: T): Filter$.Filter<T | undefined> {
167
173
  if (!isRef(value) && typeof value === 'object' && value !== null) {
168
174
  throw new TypeError('Cannot use object as a value for eq filter');
169
175
  }
@@ -175,7 +181,7 @@ class FilterClass implements Echo.Filter<any> {
175
181
  });
176
182
  }
177
183
 
178
- static neq<T>(value: T): Echo.Filter<T> {
184
+ static neq<T>(value: T): Filter$.Filter<T | undefined> {
179
185
  return new FilterClass({
180
186
  type: 'compare',
181
187
  operator: 'neq',
@@ -183,7 +189,7 @@ class FilterClass implements Echo.Filter<any> {
183
189
  });
184
190
  }
185
191
 
186
- static gt<T>(value: T): Echo.Filter<T> {
192
+ static gt<T>(value: T): Filter$.Filter<T | undefined> {
187
193
  return new FilterClass({
188
194
  type: 'compare',
189
195
  operator: 'gt',
@@ -191,7 +197,7 @@ class FilterClass implements Echo.Filter<any> {
191
197
  });
192
198
  }
193
199
 
194
- static gte<T>(value: T): Echo.Filter<T> {
200
+ static gte<T>(value: T): Filter$.Filter<T | undefined> {
195
201
  return new FilterClass({
196
202
  type: 'compare',
197
203
  operator: 'gte',
@@ -199,7 +205,7 @@ class FilterClass implements Echo.Filter<any> {
199
205
  });
200
206
  }
201
207
 
202
- static lt<T>(value: T): Echo.Filter<T> {
208
+ static lt<T>(value: T): Filter$.Filter<T | undefined> {
203
209
  return new FilterClass({
204
210
  type: 'compare',
205
211
  operator: 'lt',
@@ -207,7 +213,7 @@ class FilterClass implements Echo.Filter<any> {
207
213
  });
208
214
  }
209
215
 
210
- static lte<T>(value: T): Echo.Filter<T> {
216
+ static lte<T>(value: T): Filter$.Filter<T | undefined> {
211
217
  return new FilterClass({
212
218
  type: 'compare',
213
219
  operator: 'lte',
@@ -215,21 +221,21 @@ class FilterClass implements Echo.Filter<any> {
215
221
  });
216
222
  }
217
223
 
218
- static in<T>(...values: T[]): Echo.Filter<T> {
224
+ static in<T>(...values: T[]): Filter$.Filter<T> {
219
225
  return new FilterClass({
220
226
  type: 'in',
221
227
  values,
222
228
  });
223
229
  }
224
230
 
225
- static contains<T>(value: T): Echo.Filter<readonly T[] | undefined> {
231
+ static contains<T>(value: T): Filter$.Filter<readonly T[] | undefined> {
226
232
  return new FilterClass({
227
233
  type: 'contains',
228
234
  value,
229
235
  });
230
236
  }
231
237
 
232
- static between<T>(from: T, to: T): Echo.Filter<unknown> {
238
+ static between<T>(from: T, to: T): Filter$.Filter<T> {
233
239
  return new FilterClass({
234
240
  type: 'range',
235
241
  from,
@@ -237,33 +243,68 @@ class FilterClass implements Echo.Filter<any> {
237
243
  });
238
244
  }
239
245
 
240
- static not<F extends Echo.Filter.Any>(filter: F): Echo.Filter<Echo.Filter.Type<F>> {
246
+ static updated(range: { after?: Date | number; before?: Date | number }): Filter$.Any {
247
+ return FilterClass.#timeRangeFilter('updatedAt', range);
248
+ }
249
+
250
+ static created(range: { after?: Date | number; before?: Date | number }): Filter$.Any {
251
+ return FilterClass.#timeRangeFilter('createdAt', range);
252
+ }
253
+
254
+ static #timeRangeFilter(
255
+ field: 'updatedAt' | 'createdAt',
256
+ range: { after?: Date | number; before?: Date | number },
257
+ ): Filter$.Any {
258
+ const toMs = (d: Date | number) => (typeof d === 'number' ? d : d.getTime());
259
+ const filters: Filter$.Any[] = [];
260
+ if (range.after != null) {
261
+ filters.push(new FilterClass({ type: 'timestamp', field, operator: 'gte', value: toMs(range.after) }));
262
+ }
263
+ if (range.before != null) {
264
+ filters.push(new FilterClass({ type: 'timestamp', field, operator: 'lte', value: toMs(range.before) }));
265
+ }
266
+ if (filters.length === 0) {
267
+ return FilterClass.everything();
268
+ }
269
+ return filters.length === 1 ? filters[0] : FilterClass.and(...filters);
270
+ }
271
+
272
+ static not<F extends Filter$.Any>(filter: F): Filter$.Filter<Filter$.Type<F>> {
241
273
  return new FilterClass({
242
274
  type: 'not',
243
275
  filter: filter.ast,
244
276
  });
245
277
  }
246
278
 
247
- static and<T>(...filters: Echo.Filter<T>[]): Echo.Filter<T> {
279
+ static and<Filters extends readonly Filter$.Any[]>(
280
+ ...filters: Filters
281
+ ): Filter$.Filter<Filter$.Type<Filters[number]>> {
248
282
  return new FilterClass({
249
283
  type: 'and',
250
284
  filters: filters.map((f) => f.ast),
251
285
  });
252
286
  }
253
287
 
254
- static or<T>(...filters: Echo.Filter<T>[]): Echo.Filter<T> {
288
+ static or<Filters extends readonly Filter$.Any[]>(
289
+ ...filters: Filters
290
+ ): Filter$.Filter<Filter$.Type<Filters[number]>> {
255
291
  return new FilterClass({
256
292
  type: 'or',
257
293
  filters: filters.map((f) => f.ast),
258
294
  });
259
295
  }
260
296
 
297
+ /** Returns a human-readable string representation of a Filter AST. */
298
+ static pretty(filter: Filter$.Any): string {
299
+ return prettyFilter(filter.ast);
300
+ }
301
+
261
302
  private constructor(public readonly ast: QueryAST.Filter) {}
262
303
 
263
304
  '~Filter' = FilterClass.variance;
264
305
  }
265
306
 
266
- export const Filter1: typeof Echo.Filter = FilterClass;
307
+ export const Filter1: typeof Filter$ = FilterClass;
267
308
  export { Filter1 as Filter };
268
309
 
269
310
  /**
@@ -272,7 +313,7 @@ export { Filter1 as Filter };
272
313
  // TODO(dmaretskyi): Filter only properties that are references (or optional references, or unions that include references).
273
314
  type RefPropKey<T> = keyof T & string;
274
315
 
275
- const propsFilterToAst = (predicates: Echo.Filter.Props<any>): Pick<QueryAST.FilterObject, 'id' | 'props'> => {
316
+ const propsFilterToAst = (predicates: Filter$.Props<any>): Pick<QueryAST.FilterObject, 'id' | 'props'> => {
276
317
  let idFilter: readonly ObjectId[] | undefined;
277
318
  if ('id' in predicates) {
278
319
  assertArgument(
@@ -317,32 +358,48 @@ const processPredicate = (predicate: any): QueryAST.Filter => {
317
358
  return FilterClass.eq(predicate).ast;
318
359
  };
319
360
 
320
- class QueryClass implements Echo.Query<any> {
321
- private static variance: Echo.Query<any>['~Query'] = {} as Echo.Query<any>['~Query'];
361
+ class QueryClass implements Query$.Any {
362
+ private static 'variance': Query$.Any['~Query'] = {} as Query$.Any['~Query'];
322
363
 
323
- static is(value: unknown): value is Echo.Query<any> {
364
+ static is(value: unknown): value is Query$.Any {
324
365
  return typeof value === 'object' && value !== null && '~Query' in value;
325
366
  }
326
367
 
327
- static fromAst(ast: QueryAST.Query): Echo.Query<any> {
368
+ static fromAst(ast: QueryAST.Query): Query$.Any {
328
369
  return new QueryClass(ast);
329
370
  }
330
371
 
331
- static select<F extends Echo.Filter.Any>(filter: F): Echo.Query<Echo.Filter.Type<F>> {
372
+ static select<F extends Filter$.Any>(filter: F): Query$.Query<Filter$.Type<F>> {
332
373
  return new QueryClass({
333
374
  type: 'select',
334
375
  filter: filter.ast,
335
376
  });
336
377
  }
337
378
 
338
- static type(schema: Schema.Schema.All | string, predicates?: Echo.Filter.Props<unknown>): Query<any> {
379
+ select(filter: Filter$.Any | Filter$.Props<any>): Query$.Any {
380
+ if (FilterClass.is(filter)) {
381
+ return new QueryClass({
382
+ type: 'filter',
383
+ selection: this.ast,
384
+ filter: filter.ast,
385
+ });
386
+ } else {
387
+ return new QueryClass({
388
+ type: 'filter',
389
+ selection: this.ast,
390
+ filter: FilterClass.props(filter).ast,
391
+ });
392
+ }
393
+ }
394
+
395
+ static type(schema: Schema.Schema.All | string, predicates?: Filter$.Props<unknown>): Query$.Any {
339
396
  return new QueryClass({
340
397
  type: 'select',
341
398
  filter: FilterClass.type(schema, predicates).ast,
342
399
  });
343
400
  }
344
401
 
345
- static all(...queries: Query<any>[]): Query<any> {
402
+ static all(...queries: Query$.Any[]): Query$.Any {
346
403
  if (queries.length === 0) {
347
404
  throw new TypeError(
348
405
  'Query.all combines results of multiple queries, to query all objects use Query.select(Filter.everything())',
@@ -354,7 +411,7 @@ class QueryClass implements Echo.Query<any> {
354
411
  });
355
412
  }
356
413
 
357
- static without<T>(source: Query<T>, exclude: Query<T>): Query<T> {
414
+ static without<T>(source: Query$.Query<T>, exclude: Query$.Query<T>): Query$.Query<T> {
358
415
  return new QueryClass({
359
416
  type: 'set-difference',
360
417
  source: source.ast,
@@ -362,27 +419,50 @@ class QueryClass implements Echo.Query<any> {
362
419
  });
363
420
  }
364
421
 
365
- constructor(public readonly ast: QueryAST.Query) {}
366
-
367
- '~Query' = QueryClass.variance;
422
+ static from(source: any, options?: { includeFeeds?: boolean }): Query$.Any {
423
+ const baseQuery: QueryAST.Query = {
424
+ type: 'select',
425
+ filter: FilterClass.everything().ast,
426
+ };
427
+ const wrapper = new QueryClass(baseQuery);
428
+ return wrapper.from(source, options);
429
+ }
368
430
 
369
- select(filter: Filter<any> | Filter.Props<any>): Query<any> {
370
- if (FilterClass.is(filter)) {
431
+ from(arg: any, options?: { includeFeeds?: boolean }): Query$.Any {
432
+ if (arg === 'all-accessible-spaces') {
371
433
  return new QueryClass({
372
- type: 'filter',
373
- selection: this.ast,
374
- filter: filter.ast,
434
+ type: 'from',
435
+ query: this.ast,
436
+ from: {
437
+ _tag: 'scope',
438
+ scope: {
439
+ ...(options?.includeFeeds ? { allQueuesFromSpaces: true } : {}),
440
+ },
441
+ },
375
442
  });
376
- } else {
443
+ }
444
+
445
+ if (_isScopeLike(arg)) {
377
446
  return new QueryClass({
378
- type: 'filter',
379
- selection: this.ast,
380
- filter: FilterClass.props(filter).ast,
447
+ type: 'from',
448
+ query: this.ast,
449
+ from: { _tag: 'scope', scope: arg },
381
450
  });
382
451
  }
452
+
453
+ throw new TypeError('Database and Feed objects are not supported in query-lite sandbox');
454
+ }
455
+
456
+ /** Returns a human-readable string representation of a Query AST. */
457
+ static pretty(query: Query$.Any): string {
458
+ return prettyQuery(query.ast);
383
459
  }
384
460
 
385
- reference(key: string): Query<any> {
461
+ constructor(public readonly ast: QueryAST.Query) {}
462
+
463
+ '~Query' = QueryClass.variance;
464
+
465
+ reference(key: string): Query$.Any {
386
466
  return new QueryClass({
387
467
  type: 'reference-traversal',
388
468
  anchor: this.ast,
@@ -390,36 +470,40 @@ class QueryClass implements Echo.Query<any> {
390
470
  });
391
471
  }
392
472
 
393
- referencedBy(target: Schema.Schema.All | string, key: string): Query<any> {
394
- assertArgument(typeof target === 'string', 'target');
395
- assertArgument(!target.startsWith('dxn:'), 'target');
473
+ referencedBy(target?: Schema.Schema.All | string, key?: string): Query$.Any {
474
+ const typename =
475
+ target !== undefined
476
+ ? (assertArgument(typeof target === 'string', 'target'),
477
+ assertArgument(!target.startsWith('dxn:'), 'target'),
478
+ target)
479
+ : null;
396
480
  return new QueryClass({
397
481
  type: 'incoming-references',
398
482
  anchor: this.ast,
399
- property: key,
400
- typename: target,
483
+ property: key ?? null,
484
+ typename,
401
485
  });
402
486
  }
403
487
 
404
- sourceOf(relation: Schema.Schema.All | string, predicates?: Filter.Props<unknown> | undefined): Query<any> {
488
+ sourceOf(relation?: Schema.Schema.All | string, predicates?: Filter$.Props<unknown> | undefined): Query$.Any {
405
489
  return new QueryClass({
406
490
  type: 'relation',
407
491
  anchor: this.ast,
408
492
  direction: 'outgoing',
409
- filter: FilterClass.type(relation, predicates).ast,
493
+ filter: relation !== undefined ? FilterClass.type(relation, predicates).ast : undefined,
410
494
  });
411
495
  }
412
496
 
413
- targetOf(relation: Schema.Schema.All | string, predicates?: Filter.Props<unknown> | undefined): Query<any> {
497
+ targetOf(relation?: Schema.Schema.All | string, predicates?: Filter$.Props<unknown> | undefined): Query$.Any {
414
498
  return new QueryClass({
415
499
  type: 'relation',
416
500
  anchor: this.ast,
417
501
  direction: 'incoming',
418
- filter: FilterClass.type(relation, predicates).ast,
502
+ filter: relation !== undefined ? FilterClass.type(relation, predicates).ast : undefined,
419
503
  });
420
504
  }
421
505
 
422
- source(): Query<any> {
506
+ source(): Query$.Any {
423
507
  return new QueryClass({
424
508
  type: 'relation-traversal',
425
509
  anchor: this.ast,
@@ -427,7 +511,7 @@ class QueryClass implements Echo.Query<any> {
427
511
  });
428
512
  }
429
513
 
430
- target(): Query<any> {
514
+ target(): Query$.Any {
431
515
  return new QueryClass({
432
516
  type: 'relation-traversal',
433
517
  anchor: this.ast,
@@ -435,7 +519,23 @@ class QueryClass implements Echo.Query<any> {
435
519
  });
436
520
  }
437
521
 
438
- orderBy(...order: Order<any>[]): Query<any> {
522
+ parent(): Query$.Any {
523
+ return new QueryClass({
524
+ type: 'hierarchy-traversal',
525
+ anchor: this.ast,
526
+ direction: 'to-parent',
527
+ });
528
+ }
529
+
530
+ children(): Query$.Any {
531
+ return new QueryClass({
532
+ type: 'hierarchy-traversal',
533
+ anchor: this.ast,
534
+ direction: 'to-children',
535
+ });
536
+ }
537
+
538
+ orderBy(...order: Order$.Any[]): Query$.Any {
439
539
  return new QueryClass({
440
540
  type: 'order',
441
541
  query: this.ast,
@@ -443,7 +543,15 @@ class QueryClass implements Echo.Query<any> {
443
543
  });
444
544
  }
445
545
 
446
- options(options: QueryAST.QueryOptions): Query<any> {
546
+ limit(limit: number): Query$.Any {
547
+ return new QueryClass({
548
+ type: 'limit',
549
+ query: this.ast,
550
+ limit,
551
+ });
552
+ }
553
+
554
+ options(options: QueryAST.QueryOptions): Query$.Any {
447
555
  return new QueryClass({
448
556
  type: 'options',
449
557
  query: this.ast,
@@ -452,7 +560,7 @@ class QueryClass implements Echo.Query<any> {
452
560
  }
453
561
  }
454
562
 
455
- export const Query1: typeof Echo.Query = QueryClass;
563
+ export const Query1: typeof Query$ = QueryClass;
456
564
  export { Query1 as Query };
457
565
 
458
566
  const RefTypeId: unique symbol = Symbol('@dxos/echo-query/Ref');
@@ -465,3 +573,127 @@ const makeTypeDxn = (typename: string) => {
465
573
  assertArgument(!typename.startsWith('dxn:'), 'typename');
466
574
  return `dxn:type:${typename}`;
467
575
  };
576
+
577
+ const SCOPE_KEYS = new Set(['spaceIds', 'queues', 'allQueuesFromSpaces']);
578
+
579
+ const _isScopeLike = (value: unknown): value is QueryAST.Scope => {
580
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
581
+ return false;
582
+ }
583
+ return Object.keys(value).every((key) => SCOPE_KEYS.has(key));
584
+ };
585
+
586
+ const prettyFilter = (filter: QueryAST.Filter): string => {
587
+ switch (filter.type) {
588
+ case 'object': {
589
+ const parts: string[] = [];
590
+ if (filter.typename !== null) {
591
+ parts.push(JSON.stringify(filter.typename));
592
+ }
593
+ const propEntries = Object.entries(filter.props);
594
+ if (propEntries.length > 0) {
595
+ const propsStr = propEntries.map(([k, v]) => `${k}: ${prettyFilter(v)}`).join(', ');
596
+ parts.push(`{ ${propsStr} }`);
597
+ }
598
+ if (filter.id !== undefined) {
599
+ parts.push(`id: [${filter.id.join(', ')}]`);
600
+ }
601
+ return parts.length > 0 ? `Filter.type(${parts.join(', ')})` : 'Filter.everything()';
602
+ }
603
+ case 'compare':
604
+ return `Filter.${filter.operator}(${JSON.stringify(filter.value)})`;
605
+ case 'in':
606
+ return `Filter.in(${filter.values.map((v) => JSON.stringify(v)).join(', ')})`;
607
+ case 'contains':
608
+ return `Filter.contains(${JSON.stringify(filter.value)})`;
609
+ case 'range':
610
+ return `Filter.between(${JSON.stringify(filter.from)}, ${JSON.stringify(filter.to)})`;
611
+ case 'text-search':
612
+ return `Filter.text(${JSON.stringify(filter.text)})`;
613
+ case 'tag':
614
+ return `Filter.tag(${JSON.stringify(filter.tag)})`;
615
+ case 'timestamp':
616
+ return `Filter.${filter.field}.${filter.operator}(${filter.value})`;
617
+ case 'not':
618
+ return `Filter.not(${prettyFilter(filter.filter)})`;
619
+ case 'and':
620
+ return `Filter.and(${filter.filters.map(prettyFilter).join(', ')})`;
621
+ case 'or':
622
+ return `Filter.or(${filter.filters.map(prettyFilter).join(', ')})`;
623
+ }
624
+ };
625
+
626
+ const prettyQuery = (query: QueryAST.Query): string => {
627
+ switch (query.type) {
628
+ case 'select':
629
+ return `Query.select(${prettyFilter(query.filter)})`;
630
+ case 'filter':
631
+ return `${prettyQuery(query.selection)}.select(${prettyFilter(query.filter)})`;
632
+ case 'reference-traversal':
633
+ return `${prettyQuery(query.anchor)}.reference(${JSON.stringify(query.property)})`;
634
+ case 'incoming-references': {
635
+ const args: string[] = [];
636
+ if (query.typename !== null) {
637
+ args.push(JSON.stringify(query.typename));
638
+ }
639
+ if (query.property !== null) {
640
+ args.push(JSON.stringify(query.property));
641
+ }
642
+ return `${prettyQuery(query.anchor)}.referencedBy(${args.join(', ')})`;
643
+ }
644
+ case 'relation': {
645
+ const method =
646
+ query.direction === 'outgoing' ? 'sourceOf' : query.direction === 'incoming' ? 'targetOf' : 'relationOf';
647
+ const filterStr = query.filter !== undefined ? prettyFilter(query.filter) : '';
648
+ return `${prettyQuery(query.anchor)}.${method}(${filterStr})`;
649
+ }
650
+ case 'relation-traversal':
651
+ return `${prettyQuery(query.anchor)}.${query.direction}()`;
652
+ case 'hierarchy-traversal':
653
+ return query.direction === 'to-parent'
654
+ ? `${prettyQuery(query.anchor)}.parent()`
655
+ : `${prettyQuery(query.anchor)}.children()`;
656
+ case 'union':
657
+ return `Query.all(${query.queries.map(prettyQuery).join(', ')})`;
658
+ case 'set-difference':
659
+ return `Query.without(${prettyQuery(query.source)}, ${prettyQuery(query.exclude)})`;
660
+ case 'order': {
661
+ const orders = query.order.map((o) => {
662
+ if (o.kind === 'natural') {
663
+ return 'Order.natural';
664
+ }
665
+ if (o.kind === 'rank') {
666
+ return `Order.rank(${JSON.stringify(o.direction)})`;
667
+ }
668
+ return `Order.property(${JSON.stringify(o.property)}, ${JSON.stringify(o.direction)})`;
669
+ });
670
+ return `${prettyQuery(query.query)}.orderBy(${orders.join(', ')})`;
671
+ }
672
+ case 'options': {
673
+ const parts: string[] = [];
674
+ if (query.options.deleted !== undefined) {
675
+ parts.push(`deleted: ${JSON.stringify(query.options.deleted)}`);
676
+ }
677
+ return `${prettyQuery(query.query)}.options({ ${parts.join(', ')} })`;
678
+ }
679
+ case 'from': {
680
+ if (query.from._tag === 'scope') {
681
+ const scope = query.from.scope;
682
+ const parts: string[] = [];
683
+ if (scope.spaceIds !== undefined) {
684
+ parts.push(`spaceIds: [${scope.spaceIds.join(', ')}]`);
685
+ }
686
+ if (scope.queues !== undefined) {
687
+ parts.push(`queues: [${scope.queues.join(', ')}]`);
688
+ }
689
+ if (scope.allQueuesFromSpaces !== undefined) {
690
+ parts.push(`allQueuesFromSpaces: ${scope.allQueuesFromSpaces}`);
691
+ }
692
+ return `${prettyQuery(query.query)}.from({ ${parts.join(', ')} })`;
693
+ }
694
+ return `${prettyQuery(query.query)}.from(${prettyQuery(query.from.query)})`;
695
+ }
696
+ case 'limit':
697
+ return `${prettyQuery(query.query)}.limit(${query.limit})`;
698
+ }
699
+ };
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ export * from './query-sandbox';