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

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