@dxos/echo-query 0.8.4-main.fffef41 → 0.8.4-staging.60fe92afc8

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