@dxos/echo-query 0.8.4-main.ead640a → 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.
Files changed (42) hide show
  1. package/LICENSE +102 -5
  2. package/README.md +1 -1
  3. package/dist/lib/neutral/index.mjs +917 -0
  4. package/dist/lib/neutral/index.mjs.map +7 -0
  5. package/dist/lib/neutral/meta.json +1 -0
  6. package/dist/query-lite/index.d.ts +10022 -0
  7. package/dist/query-lite/index.d.ts.map +1 -0
  8. package/dist/query-lite/index.js +545 -375
  9. package/dist/query-lite/index.js.map +1 -0
  10. package/dist/types/src/index.d.ts +1 -0
  11. package/dist/types/src/index.d.ts.map +1 -1
  12. package/dist/types/src/parser/gen/index.d.ts.map +1 -1
  13. package/dist/types/src/parser/gen/query.terms.d.ts +1 -1
  14. package/dist/types/src/parser/gen/query.terms.d.ts.map +1 -1
  15. package/dist/types/src/parser/query-builder.d.ts +18 -3
  16. package/dist/types/src/parser/query-builder.d.ts.map +1 -1
  17. package/dist/types/src/query-lite/query-lite.d.ts +4 -4
  18. package/dist/types/src/query-lite/query-lite.d.ts.map +1 -1
  19. package/dist/types/src/sandbox/index.d.ts +2 -0
  20. package/dist/types/src/sandbox/index.d.ts.map +1 -0
  21. package/dist/types/src/sandbox/query-sandbox.d.ts +1 -1
  22. package/dist/types/src/sandbox/query-sandbox.d.ts.map +1 -1
  23. package/dist/types/src/sandbox/quickjs.d.ts.map +1 -1
  24. package/dist/types/tsconfig.tsbuildinfo +1 -1
  25. package/package.json +22 -20
  26. package/src/index.ts +1 -0
  27. package/src/parser/gen/query.terms.ts +24 -23
  28. package/src/parser/gen/query.ts +8 -8
  29. package/src/parser/query-builder.ts +333 -20
  30. package/src/parser/query.grammar +8 -2
  31. package/src/parser/query.test.ts +207 -41
  32. package/src/query-lite/query-lite.ts +360 -73
  33. package/src/sandbox/index.ts +5 -0
  34. package/src/sandbox/query-sandbox.test.ts +15 -14
  35. package/src/sandbox/query-sandbox.ts +1 -1
  36. package/src/sandbox/quickjs.ts +1 -2
  37. package/dist/lib/browser/index.mjs +0 -530
  38. package/dist/lib/browser/index.mjs.map +0 -7
  39. package/dist/lib/browser/meta.json +0 -1
  40. package/dist/lib/node-esm/index.mjs +0 -530
  41. package/dist/lib/node-esm/index.mjs.map +0 -7
  42. package/dist/lib/node-esm/meta.json +0 -1
@@ -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,27 +104,27 @@ 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
  }
106
112
  return new FilterClass({
107
113
  type: 'object',
108
- typename: makeTypeDxn(schema),
114
+ typename: makeTypeDXN(schema),
109
115
  ...propsFilterToAst(props ?? {}),
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
- typename: makeTypeDxn(typename),
122
+ typename: makeTypeDXN(typename),
117
123
  props: {},
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,24 @@ 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 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
+
152
+ static props<T>(props: Filter$.Props<T>): Filter$.Filter<T> {
137
153
  return new FilterClass({
138
154
  type: 'object',
139
155
  typename: null,
@@ -141,7 +157,7 @@ class FilterClass implements Echo.Filter<any> {
141
157
  });
142
158
  }
143
159
 
144
- static text(text: string, options?: Echo.Query.TextSearchOptions): Echo.Filter<any> {
160
+ static text(text: string, options?: Filter$.TextSearchOptions): Filter$.Any {
145
161
  return new FilterClass({
146
162
  type: 'text-search',
147
163
  text,
@@ -152,7 +168,7 @@ class FilterClass implements Echo.Filter<any> {
152
168
  static foreignKeys<S extends Schema.Schema.All>(
153
169
  schema: S | string,
154
170
  keys: ForeignKey[],
155
- ): Echo.Filter<Schema.Schema.Type<S>> {
171
+ ): Filter$.Filter<Schema.Schema.Type<S>> {
156
172
  assertArgument(typeof schema === 'string', 'schema');
157
173
  assertArgument(!schema.startsWith('dxn:'), 'schema');
158
174
  return new FilterClass({
@@ -163,7 +179,7 @@ class FilterClass implements Echo.Filter<any> {
163
179
  });
164
180
  }
165
181
 
166
- static eq<T>(value: T): Echo.Filter<T> {
182
+ static eq<T>(value: T): Filter$.Filter<T | undefined> {
167
183
  if (!isRef(value) && typeof value === 'object' && value !== null) {
168
184
  throw new TypeError('Cannot use object as a value for eq filter');
169
185
  }
@@ -175,7 +191,7 @@ class FilterClass implements Echo.Filter<any> {
175
191
  });
176
192
  }
177
193
 
178
- static neq<T>(value: T): Echo.Filter<T> {
194
+ static neq<T>(value: T): Filter$.Filter<T | undefined> {
179
195
  return new FilterClass({
180
196
  type: 'compare',
181
197
  operator: 'neq',
@@ -183,7 +199,7 @@ class FilterClass implements Echo.Filter<any> {
183
199
  });
184
200
  }
185
201
 
186
- static gt<T>(value: T): Echo.Filter<T> {
202
+ static gt<T>(value: T): Filter$.Filter<T | undefined> {
187
203
  return new FilterClass({
188
204
  type: 'compare',
189
205
  operator: 'gt',
@@ -191,7 +207,7 @@ class FilterClass implements Echo.Filter<any> {
191
207
  });
192
208
  }
193
209
 
194
- static gte<T>(value: T): Echo.Filter<T> {
210
+ static gte<T>(value: T): Filter$.Filter<T | undefined> {
195
211
  return new FilterClass({
196
212
  type: 'compare',
197
213
  operator: 'gte',
@@ -199,7 +215,7 @@ class FilterClass implements Echo.Filter<any> {
199
215
  });
200
216
  }
201
217
 
202
- static lt<T>(value: T): Echo.Filter<T> {
218
+ static lt<T>(value: T): Filter$.Filter<T | undefined> {
203
219
  return new FilterClass({
204
220
  type: 'compare',
205
221
  operator: 'lt',
@@ -207,7 +223,7 @@ class FilterClass implements Echo.Filter<any> {
207
223
  });
208
224
  }
209
225
 
210
- static lte<T>(value: T): Echo.Filter<T> {
226
+ static lte<T>(value: T): Filter$.Filter<T | undefined> {
211
227
  return new FilterClass({
212
228
  type: 'compare',
213
229
  operator: 'lte',
@@ -215,21 +231,21 @@ class FilterClass implements Echo.Filter<any> {
215
231
  });
216
232
  }
217
233
 
218
- static in<T>(...values: T[]): Echo.Filter<T> {
234
+ static in<T>(...values: T[]): Filter$.Filter<T> {
219
235
  return new FilterClass({
220
236
  type: 'in',
221
237
  values,
222
238
  });
223
239
  }
224
240
 
225
- static contains<T>(value: T): Echo.Filter<readonly T[] | undefined> {
241
+ static contains<T>(value: T): Filter$.Filter<readonly T[] | undefined> {
226
242
  return new FilterClass({
227
243
  type: 'contains',
228
244
  value,
229
245
  });
230
246
  }
231
247
 
232
- static between<T>(from: T, to: T): Echo.Filter<unknown> {
248
+ static between<T>(from: T, to: T): Filter$.Filter<T> {
233
249
  return new FilterClass({
234
250
  type: 'range',
235
251
  from,
@@ -237,33 +253,83 @@ class FilterClass implements Echo.Filter<any> {
237
253
  });
238
254
  }
239
255
 
240
- static not<F extends Echo.Filter.Any>(filter: F): Echo.Filter<Echo.Filter.Type<F>> {
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
+
297
+ static not<F extends Filter$.Any>(filter: F): Filter$.Filter<Filter$.Type<F>> {
241
298
  return new FilterClass({
242
299
  type: 'not',
243
300
  filter: filter.ast,
244
301
  });
245
302
  }
246
303
 
247
- static and<T>(...filters: Echo.Filter<T>[]): Echo.Filter<T> {
304
+ static and<Filters extends readonly Filter$.Any[]>(
305
+ ...filters: Filters
306
+ ): Filter$.Filter<Filter$.Type<Filters[number]>> {
248
307
  return new FilterClass({
249
308
  type: 'and',
250
309
  filters: filters.map((f) => f.ast),
251
310
  });
252
311
  }
253
312
 
254
- static or<T>(...filters: Echo.Filter<T>[]): Echo.Filter<T> {
313
+ static or<Filters extends readonly Filter$.Any[]>(
314
+ ...filters: Filters
315
+ ): Filter$.Filter<Filter$.Type<Filters[number]>> {
255
316
  return new FilterClass({
256
317
  type: 'or',
257
318
  filters: filters.map((f) => f.ast),
258
319
  });
259
320
  }
260
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
+
261
327
  private constructor(public readonly ast: QueryAST.Filter) {}
262
328
 
263
329
  '~Filter' = FilterClass.variance;
264
330
  }
265
331
 
266
- export const Filter1: typeof Echo.Filter = FilterClass;
332
+ export const Filter1: typeof Filter$ = FilterClass;
267
333
  export { Filter1 as Filter };
268
334
 
269
335
  /**
@@ -272,7 +338,7 @@ export { Filter1 as Filter };
272
338
  // TODO(dmaretskyi): Filter only properties that are references (or optional references, or unions that include references).
273
339
  type RefPropKey<T> = keyof T & string;
274
340
 
275
- const propsFilterToAst = (predicates: Echo.Filter.Props<any>): Pick<QueryAST.FilterObject, 'id' | 'props'> => {
341
+ const propsFilterToAst = (predicates: Filter$.Props<any>): Pick<QueryAST.FilterObject, 'id' | 'props'> => {
276
342
  let idFilter: readonly ObjectId[] | undefined;
277
343
  if ('id' in predicates) {
278
344
  assertArgument(
@@ -317,32 +383,48 @@ const processPredicate = (predicate: any): QueryAST.Filter => {
317
383
  return FilterClass.eq(predicate).ast;
318
384
  };
319
385
 
320
- class QueryClass implements Echo.Query<any> {
321
- private static variance: Echo.Query<any>['~Query'] = {} as Echo.Query<any>['~Query'];
386
+ class QueryClass implements Query$.Any {
387
+ private static 'variance': Query$.Any['~Query'] = {} as Query$.Any['~Query'];
322
388
 
323
- static is(value: unknown): value is Echo.Query<any> {
389
+ static is(value: unknown): value is Query$.Any {
324
390
  return typeof value === 'object' && value !== null && '~Query' in value;
325
391
  }
326
392
 
327
- static fromAst(ast: QueryAST.Query): Echo.Query<any> {
393
+ static fromAst(ast: QueryAST.Query): Query$.Any {
328
394
  return new QueryClass(ast);
329
395
  }
330
396
 
331
- static select<F extends Echo.Filter.Any>(filter: F): Echo.Query<Echo.Filter.Type<F>> {
397
+ static select<F extends Filter$.Any>(filter: F): Query$.Query<Filter$.Type<F>> {
332
398
  return new QueryClass({
333
399
  type: 'select',
334
400
  filter: filter.ast,
335
401
  });
336
402
  }
337
403
 
338
- static type(schema: Schema.Schema.All | string, predicates?: Echo.Filter.Props<unknown>): Query<any> {
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
+
420
+ static type(schema: Schema.Schema.All | string, predicates?: Filter$.Props<unknown>): Query$.Any {
339
421
  return new QueryClass({
340
422
  type: 'select',
341
423
  filter: FilterClass.type(schema, predicates).ast,
342
424
  });
343
425
  }
344
426
 
345
- static all(...queries: Query<any>[]): Query<any> {
427
+ static all(...queries: Query$.Any[]): Query$.Any {
346
428
  if (queries.length === 0) {
347
429
  throw new TypeError(
348
430
  'Query.all combines results of multiple queries, to query all objects use Query.select(Filter.everything())',
@@ -354,7 +436,7 @@ class QueryClass implements Echo.Query<any> {
354
436
  });
355
437
  }
356
438
 
357
- static without<T>(source: Query<T>, exclude: Query<T>): Query<T> {
439
+ static without<T>(source: Query$.Query<T>, exclude: Query$.Query<T>): Query$.Query<T> {
358
440
  return new QueryClass({
359
441
  type: 'set-difference',
360
442
  source: source.ast,
@@ -362,27 +444,50 @@ class QueryClass implements Echo.Query<any> {
362
444
  });
363
445
  }
364
446
 
365
- constructor(public readonly ast: QueryAST.Query) {}
366
-
367
- '~Query' = QueryClass.variance;
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
+ }
368
455
 
369
- select(filter: Filter<any> | Filter.Props<any>): Query<any> {
370
- if (FilterClass.is(filter)) {
456
+ from(arg: any, options?: { includeFeeds?: boolean }): Query$.Any {
457
+ if (arg === 'all-accessible-spaces') {
371
458
  return new QueryClass({
372
- type: 'filter',
373
- selection: this.ast,
374
- filter: filter.ast,
459
+ type: 'from',
460
+ query: this.ast,
461
+ from: {
462
+ _tag: 'scope',
463
+ scope: {
464
+ ...(options?.includeFeeds ? { allFeedsFromSpaces: true } : {}),
465
+ },
466
+ },
375
467
  });
376
- } else {
468
+ }
469
+
470
+ if (_isScopeLike(arg)) {
377
471
  return new QueryClass({
378
- type: 'filter',
379
- selection: this.ast,
380
- filter: FilterClass.props(filter).ast,
472
+ type: 'from',
473
+ query: this.ast,
474
+ from: { _tag: 'scope', scope: arg },
381
475
  });
382
476
  }
477
+
478
+ throw new TypeError('Database and Feed objects are not supported in query-lite sandbox');
383
479
  }
384
480
 
385
- reference(key: string): Query<any> {
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
+
490
+ reference(key: string): Query$.Any {
386
491
  return new QueryClass({
387
492
  type: 'reference-traversal',
388
493
  anchor: this.ast,
@@ -390,36 +495,40 @@ class QueryClass implements Echo.Query<any> {
390
495
  });
391
496
  }
392
497
 
393
- referencedBy(target: Schema.Schema.All | string, key: string): Query<any> {
394
- assertArgument(typeof target === 'string', 'target');
395
- assertArgument(!target.startsWith('dxn:'), 'target');
498
+ referencedBy(target?: Schema.Schema.All | string, key?: string): Query$.Any {
499
+ const typename =
500
+ target !== undefined
501
+ ? (assertArgument(typeof target === 'string', 'target'),
502
+ assertArgument(!target.startsWith('dxn:'), 'target'),
503
+ target)
504
+ : null;
396
505
  return new QueryClass({
397
506
  type: 'incoming-references',
398
507
  anchor: this.ast,
399
- property: key,
400
- typename: target,
508
+ property: key ?? null,
509
+ typename,
401
510
  });
402
511
  }
403
512
 
404
- sourceOf(relation: Schema.Schema.All | string, predicates?: Filter.Props<unknown> | undefined): Query<any> {
513
+ sourceOf(relation?: Schema.Schema.All | string, predicates?: Filter$.Props<unknown> | undefined): Query$.Any {
405
514
  return new QueryClass({
406
515
  type: 'relation',
407
516
  anchor: this.ast,
408
517
  direction: 'outgoing',
409
- filter: FilterClass.type(relation, predicates).ast,
518
+ filter: relation !== undefined ? FilterClass.type(relation, predicates).ast : undefined,
410
519
  });
411
520
  }
412
521
 
413
- targetOf(relation: Schema.Schema.All | string, predicates?: Filter.Props<unknown> | undefined): Query<any> {
522
+ targetOf(relation?: Schema.Schema.All | string, predicates?: Filter$.Props<unknown> | undefined): Query$.Any {
414
523
  return new QueryClass({
415
524
  type: 'relation',
416
525
  anchor: this.ast,
417
526
  direction: 'incoming',
418
- filter: FilterClass.type(relation, predicates).ast,
527
+ filter: relation !== undefined ? FilterClass.type(relation, predicates).ast : undefined,
419
528
  });
420
529
  }
421
530
 
422
- source(): Query<any> {
531
+ source(): Query$.Any {
423
532
  return new QueryClass({
424
533
  type: 'relation-traversal',
425
534
  anchor: this.ast,
@@ -427,7 +536,7 @@ class QueryClass implements Echo.Query<any> {
427
536
  });
428
537
  }
429
538
 
430
- target(): Query<any> {
539
+ target(): Query$.Any {
431
540
  return new QueryClass({
432
541
  type: 'relation-traversal',
433
542
  anchor: this.ast,
@@ -435,7 +544,23 @@ class QueryClass implements Echo.Query<any> {
435
544
  });
436
545
  }
437
546
 
438
- orderBy(...order: Order<any>[]): Query<any> {
547
+ parent(): Query$.Any {
548
+ return new QueryClass({
549
+ type: 'hierarchy-traversal',
550
+ anchor: this.ast,
551
+ direction: 'to-parent',
552
+ });
553
+ }
554
+
555
+ children(): Query$.Any {
556
+ return new QueryClass({
557
+ type: 'hierarchy-traversal',
558
+ anchor: this.ast,
559
+ direction: 'to-children',
560
+ });
561
+ }
562
+
563
+ orderBy(...order: Order$.Any[]): Query$.Any {
439
564
  return new QueryClass({
440
565
  type: 'order',
441
566
  query: this.ast,
@@ -443,16 +568,39 @@ class QueryClass implements Echo.Query<any> {
443
568
  });
444
569
  }
445
570
 
446
- options(options: QueryAST.QueryOptions): Query<any> {
571
+ limit(limit: number): Query$.Any {
572
+ return new QueryClass({
573
+ type: 'limit',
574
+ query: this.ast,
575
+ limit,
576
+ });
577
+ }
578
+
579
+ options(options: QueryAST.QueryOptions): Query$.Any {
447
580
  return new QueryClass({
448
581
  type: 'options',
449
582
  query: this.ast,
450
583
  options,
451
584
  });
452
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
+ }
453
601
  }
454
602
 
455
- export const Query1: typeof Echo.Query = QueryClass;
603
+ export const Query1: typeof Query$ = QueryClass;
456
604
  export { Query1 as Query };
457
605
 
458
606
  const RefTypeId: unique symbol = Symbol('@dxos/echo-query/Ref');
@@ -460,8 +608,147 @@ const isRef = (obj: any): obj is Ref.Ref<any> => {
460
608
  return obj && typeof obj === 'object' && RefTypeId in obj;
461
609
  };
462
610
 
463
- const makeTypeDxn = (typename: string) => {
611
+ const makeTypeDXN = (typename: string) => {
464
612
  assertArgument(typeof typename === 'string', 'typename');
465
613
  assertArgument(!typename.startsWith('dxn:'), 'typename');
466
614
  return `dxn:type:${typename}`;
467
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
+ };