@dxos/echo-query 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/echo-query",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "description": "ECHO queries.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -19,7 +19,12 @@
19
19
  ".": {
20
20
  "source": "./src/index.ts",
21
21
  "types": "./dist/types/src/index.d.ts",
22
- "default": "./dist/lib/neutral/index.mjs"
22
+ "import": "./dist/lib/index.mjs"
23
+ },
24
+ "./sandbox": {
25
+ "source": "./src/sandbox/index.ts",
26
+ "types": "./dist/types/src/sandbox/index.d.ts",
27
+ "import": "./dist/lib/sandbox.mjs"
23
28
  },
24
29
  "./api.d.ts": "./dist/query-lite/index.d.ts"
25
30
  },
@@ -31,20 +36,20 @@
31
36
  "dependencies": {
32
37
  "@lezer/common": "^1.5.2",
33
38
  "@lezer/lr": "^1.4.10",
34
- "@dxos/context": "0.10.0",
35
- "@dxos/echo": "0.10.0",
36
- "@dxos/echo-protocol": "0.10.0",
37
- "@dxos/node-std": "0.10.0",
38
- "@dxos/vendor-quickjs": "0.10.0",
39
- "@dxos/util": "0.10.0",
40
- "@dxos/invariant": "0.10.0"
39
+ "@dxos/context": "0.11.0",
40
+ "@dxos/echo": "0.11.0",
41
+ "@dxos/echo-protocol": "0.11.0",
42
+ "@dxos/node-std": "0.11.0",
43
+ "@dxos/util": "0.11.0",
44
+ "@dxos/vendor-quickjs": "0.11.0",
45
+ "@dxos/invariant": "0.11.0"
41
46
  },
42
47
  "devDependencies": {
43
48
  "@lezer/generator": "^1.8.0",
44
49
  "tsdown": "^0.16.7",
45
50
  "typescript": "^6.0.3",
46
- "@dxos/echo-generator": "0.10.0",
47
- "@dxos/random": "0.10.0"
51
+ "@dxos/echo-generator": "0.11.0",
52
+ "@dxos/random": "0.11.0"
48
53
  },
49
54
  "publishConfig": {
50
55
  "access": "public"
package/src/index.ts CHANGED
@@ -3,4 +3,7 @@
3
3
  //
4
4
 
5
5
  export * from './parser';
6
- export * from './sandbox';
6
+
7
+ // NOTE: `./sandbox` (QuerySandbox) is intentionally NOT re-exported here. It runtime-imports the
8
+ // vendored QuickJS wasm runtime (`@dxos/vendor-quickjs`), which every barrel consumer would then
9
+ // pull into its bundle even though only sandbox users need it. Import it from `@dxos/echo-query/sandbox`.
@@ -4,7 +4,15 @@
4
4
 
5
5
  import type * as Schema from 'effect/Schema';
6
6
 
7
- import type { Ref, Filter as Filter$, Obj as Obj$, Order as Order$, Query as Query$, Type as Type$ } from '@dxos/echo';
7
+ import type {
8
+ Ref,
9
+ Aggregate as Aggregate$,
10
+ Filter as Filter$,
11
+ Obj as Obj$,
12
+ Order as Order$,
13
+ Query as Query$,
14
+ Type as Type$,
15
+ } from '@dxos/echo';
8
16
  import type { ForeignKey, QueryAST } from '@dxos/echo-protocol';
9
17
  import { assertArgument } from '@dxos/invariant';
10
18
  // `DXN`/`EID` are type-only imports to keep the `query-lite` bundle free of
@@ -17,20 +25,30 @@ import type { DXN, EID, EntityId, URI } from '@dxos/keys';
17
25
 
18
26
  // TODO(wittjosiah): The `export * as ...` syntax causes tsdown to genereate multiple files which breaks the sandbox.
19
27
 
28
+ // The TypeId brand keys mirror the constants in `@dxos/echo`. They are declared locally (typed by the
29
+ // type-only imports) rather than imported as runtime values to keep this sandbox bundle free of the
30
+ // `@dxos/echo` runtime graph.
31
+ const OrderTypeId: Order$.OrderTypeId = '~@dxos/echo/Order';
32
+ const FilterTypeId: Filter$.FilterTypeId = '~@dxos/echo/Filter';
33
+ const QueryTypeId: Query$.QueryTypeId = '~@dxos/echo/Query';
34
+ const ProjectionTypeId: Query$.ProjectionTypeId = '~@dxos/echo/Query.Projection';
35
+
20
36
  class OrderClass implements Order$.Any {
21
- private static 'variance': Order$.Any['~Order'] = {} as Order$.Any['~Order'];
37
+ private static 'variance': Order$.Any[typeof OrderTypeId] = {} as Order$.Any[typeof OrderTypeId];
22
38
 
23
- static 'is'(value: unknown): value is Order$.Any {
24
- return typeof value === 'object' && value !== null && '~Order' in value;
39
+ static is(value: unknown): value is Order$.Any {
40
+ return typeof value === 'object' && value !== null && OrderTypeId in value;
25
41
  }
26
42
 
27
- 'constructor'(public readonly ast: QueryAST.Order) {}
43
+ constructor(public readonly ast: QueryAST.Order) {}
28
44
 
29
- '~Order' = OrderClass.variance;
45
+ [OrderTypeId] = OrderClass.variance;
30
46
  }
31
47
 
32
48
  namespace Order1 {
33
- export const natural: Order$.Any = new OrderClass({ kind: 'natural' });
49
+ export const OrderTypeId: Order$.OrderTypeId = '~@dxos/echo/Order';
50
+ export const natural = (direction: QueryAST.OrderDirection = 'asc'): Order$.Order<any> =>
51
+ new OrderClass({ kind: 'natural', direction });
34
52
  export const property = <T>(property: keyof T & string, direction: QueryAST.OrderDirection): Order$.Order<T> =>
35
53
  new OrderClass({
36
54
  kind: 'property',
@@ -96,6 +114,10 @@ const _filterMatchValueLocal = (filter: QueryAST.Filter, value: unknown): boolea
96
114
  }
97
115
  case 'in':
98
116
  return filter.values.includes(value);
117
+ case 'in-query':
118
+ // The QuickJS sandbox has no database access to run a subquery — a semi-join predicate
119
+ // must be resolved to a literal `in` by the query executor before reaching this matcher.
120
+ throw new Error('in-query filters are not supported in the sandboxed toPredicate matcher.');
99
121
  case 'range':
100
122
  return (value as any) >= filter.from && (value as any) <= filter.to;
101
123
  case 'not':
@@ -145,17 +167,19 @@ const _filterMatchEntityLocal = (filter: QueryAST.Filter, entity: any): boolean
145
167
  };
146
168
 
147
169
  class FilterClass implements Filter$.Any {
148
- private static 'variance': Filter$.Any['~Filter'] = {} as Filter$.Any['~Filter'];
170
+ static 'FilterTypeId': Filter$.FilterTypeId = FilterTypeId;
171
+
172
+ private static 'variance': Filter$.Any[typeof FilterTypeId] = {} as Filter$.Any[typeof FilterTypeId];
149
173
 
150
- static 'is'(value: unknown): value is Filter$.Any {
151
- return typeof value === 'object' && value !== null && '~Filter' in value;
174
+ static is(value: unknown): value is Filter$.Any {
175
+ return typeof value === 'object' && value !== null && FilterTypeId in value;
152
176
  }
153
177
 
154
- static 'fromAst'(ast: QueryAST.Filter): Filter$.Any {
178
+ static fromAst(ast: QueryAST.Filter): Filter$.Any {
155
179
  return new FilterClass(ast);
156
180
  }
157
181
 
158
- static 'everything'(): FilterClass {
182
+ static everything(): FilterClass {
159
183
  return new FilterClass({
160
184
  type: 'object',
161
185
  typename: null,
@@ -163,7 +187,7 @@ class FilterClass implements Filter$.Any {
163
187
  });
164
188
  }
165
189
 
166
- static 'nothing'(): FilterClass {
190
+ static nothing(): FilterClass {
167
191
  return new FilterClass({
168
192
  type: 'not',
169
193
  filter: {
@@ -174,7 +198,7 @@ class FilterClass implements Filter$.Any {
174
198
  });
175
199
  }
176
200
 
177
- static 'relation'() {
201
+ static relation() {
178
202
  return new FilterClass({
179
203
  type: 'object',
180
204
  typename: null,
@@ -182,7 +206,7 @@ class FilterClass implements Filter$.Any {
182
206
  });
183
207
  }
184
208
 
185
- static 'id'(...ids: EntityId[]): Filter$.Any {
209
+ static id(...ids: EntityId[]): Filter$.Any {
186
210
  // assertArgument(
187
211
  // ids.every((id) => EntityId.isValid(id)),
188
212
  // 'ids',
@@ -201,12 +225,12 @@ class FilterClass implements Filter$.Any {
201
225
  });
202
226
  }
203
227
 
204
- static 'type'<T extends Type$.AnyEntity>(
228
+ static type<T extends Type$.AnyEntity>(
205
229
  type: T,
206
230
  props?: Filter$.Props<Type$.InstanceType<T>>,
207
231
  ): Filter$.Filter<Type$.InstanceType<T>>;
208
- static 'type'(schema: string, props?: Filter$.Props<unknown>): Filter$.Filter<any>;
209
- static 'type'(schema: Type$.AnyEntity | string, props?: Filter$.Props<unknown>): Filter$.Filter<unknown> {
232
+ static type(schema: string, props?: Filter$.Props<unknown>): Filter$.Filter<any>;
233
+ static type(schema: Type$.AnyEntity | string, props?: Filter$.Props<unknown>): Filter$.Filter<unknown> {
210
234
  if (typeof schema !== 'string') {
211
235
  throw new TypeError('expected typename as the first paramter');
212
236
  }
@@ -217,7 +241,7 @@ class FilterClass implements Filter$.Any {
217
241
  });
218
242
  }
219
243
 
220
- static 'typename'(typename: string): Filter$.Any {
244
+ static typename(typename: string): Filter$.Any {
221
245
  return new FilterClass({
222
246
  type: 'object',
223
247
  typename: makeTypeDxn(typename),
@@ -225,7 +249,7 @@ class FilterClass implements Filter$.Any {
225
249
  });
226
250
  }
227
251
 
228
- static 'typeURI'(uri: URI.URI): Filter$.Any {
252
+ static typeURI(uri: URI.URI): Filter$.Any {
229
253
  return new FilterClass({
230
254
  type: 'object',
231
255
  typename: uri,
@@ -233,14 +257,14 @@ class FilterClass implements Filter$.Any {
233
257
  });
234
258
  }
235
259
 
236
- static 'tag'(tag: string): Filter$.Any {
260
+ static tag(tag: string): Filter$.Any {
237
261
  return new FilterClass({
238
262
  type: 'tag',
239
263
  tag,
240
264
  });
241
265
  }
242
266
 
243
- static 'key'(key: string, options?: Filter$.KeyFilterOptions): Filter$.Any {
267
+ static key(key: string, options?: Filter$.KeyFilterOptions): Filter$.Any {
244
268
  return new FilterClass({
245
269
  type: 'object',
246
270
  typename: null,
@@ -250,7 +274,7 @@ class FilterClass implements Filter$.Any {
250
274
  });
251
275
  }
252
276
 
253
- static 'props'<T>(props: Filter$.Props<T>): Filter$.Filter<T> {
277
+ static props<T>(props: Filter$.Props<T>): Filter$.Filter<T> {
254
278
  return new FilterClass({
255
279
  type: 'object',
256
280
  typename: null,
@@ -258,7 +282,7 @@ class FilterClass implements Filter$.Any {
258
282
  });
259
283
  }
260
284
 
261
- static 'text'(text: string, options?: Filter$.TextSearchOptions): Filter$.Any {
285
+ static text(text: string, options?: Filter$.TextSearchOptions): Filter$.Any {
262
286
  return new FilterClass({
263
287
  type: 'text-search',
264
288
  text,
@@ -266,7 +290,7 @@ class FilterClass implements Filter$.Any {
266
290
  });
267
291
  }
268
292
 
269
- static 'foreignKeys'<S extends Type$.AnyEntity | string>(
293
+ static foreignKeys<S extends Type$.AnyEntity | string>(
270
294
  schema: S,
271
295
  keys: ForeignKey[],
272
296
  ): Filter$.Filter<S extends Type$.AnyEntity ? Type$.InstanceType<S> : unknown> {
@@ -280,7 +304,7 @@ class FilterClass implements Filter$.Any {
280
304
  });
281
305
  }
282
306
 
283
- static 'eq'<T>(value: T): Filter$.Filter<T | undefined> {
307
+ static eq<T>(value: T): Filter$.Filter<T | undefined> {
284
308
  if (!isRef(value) && typeof value === 'object' && value !== null) {
285
309
  throw new TypeError('Cannot use object as a value for eq filter');
286
310
  }
@@ -292,7 +316,7 @@ class FilterClass implements Filter$.Any {
292
316
  });
293
317
  }
294
318
 
295
- static 'neq'<T>(value: T): Filter$.Filter<T | undefined> {
319
+ static neq<T>(value: T): Filter$.Filter<T | undefined> {
296
320
  return new FilterClass({
297
321
  type: 'compare',
298
322
  operator: 'neq',
@@ -300,7 +324,7 @@ class FilterClass implements Filter$.Any {
300
324
  });
301
325
  }
302
326
 
303
- static 'gt'<T>(value: T): Filter$.Filter<T | undefined> {
327
+ static gt<T>(value: T): Filter$.Filter<T | undefined> {
304
328
  return new FilterClass({
305
329
  type: 'compare',
306
330
  operator: 'gt',
@@ -308,7 +332,7 @@ class FilterClass implements Filter$.Any {
308
332
  });
309
333
  }
310
334
 
311
- static 'gte'<T>(value: T): Filter$.Filter<T | undefined> {
335
+ static gte<T>(value: T): Filter$.Filter<T | undefined> {
312
336
  return new FilterClass({
313
337
  type: 'compare',
314
338
  operator: 'gte',
@@ -316,7 +340,7 @@ class FilterClass implements Filter$.Any {
316
340
  });
317
341
  }
318
342
 
319
- static 'lt'<T>(value: T): Filter$.Filter<T | undefined> {
343
+ static lt<T>(value: T): Filter$.Filter<T | undefined> {
320
344
  return new FilterClass({
321
345
  type: 'compare',
322
346
  operator: 'lt',
@@ -324,7 +348,7 @@ class FilterClass implements Filter$.Any {
324
348
  });
325
349
  }
326
350
 
327
- static 'lte'<T>(value: T): Filter$.Filter<T | undefined> {
351
+ static lte<T>(value: T): Filter$.Filter<T | undefined> {
328
352
  return new FilterClass({
329
353
  type: 'compare',
330
354
  operator: 'lte',
@@ -332,21 +356,21 @@ class FilterClass implements Filter$.Any {
332
356
  });
333
357
  }
334
358
 
335
- static 'in'<T>(...values: T[]): Filter$.Filter<T> {
359
+ static in<T>(...values: T[]): Filter$.Filter<T> {
336
360
  return new FilterClass({
337
361
  type: 'in',
338
362
  values,
339
363
  });
340
364
  }
341
365
 
342
- static 'contains'<T>(value: T): Filter$.Filter<readonly T[] | undefined> {
366
+ static contains<T>(value: T): Filter$.Filter<readonly T[] | undefined> {
343
367
  return new FilterClass({
344
368
  type: 'contains',
345
369
  value,
346
370
  });
347
371
  }
348
372
 
349
- static 'between'<T>(from: T, to: T): Filter$.Filter<T> {
373
+ static between<T>(from: T, to: T): Filter$.Filter<T> {
350
374
  return new FilterClass({
351
375
  type: 'range',
352
376
  from,
@@ -354,15 +378,15 @@ class FilterClass implements Filter$.Any {
354
378
  });
355
379
  }
356
380
 
357
- static 'updated'(range: { after?: Date | number; before?: Date | number }): Filter$.Any {
381
+ static updated(range: { after?: Date | number; before?: Date | number }): Filter$.Any {
358
382
  return FilterClass._timeRangeFilter('updatedAt', range);
359
383
  }
360
384
 
361
- static 'created'(range: { after?: Date | number; before?: Date | number }): Filter$.Any {
385
+ static created(range: { after?: Date | number; before?: Date | number }): Filter$.Any {
362
386
  return FilterClass._timeRangeFilter('createdAt', range);
363
387
  }
364
388
 
365
- static 'childOf'(parents: unknown | unknown[], options?: { transitive?: boolean }): Filter$.Any {
389
+ static childOf(parents: unknown | unknown[], options?: { transitive?: boolean }): Filter$.Any {
366
390
  const items = Array.isArray(parents) ? parents : [parents];
367
391
  const dxns = items.map((item) => {
368
392
  if (isEchoUriLike(item)) {
@@ -377,7 +401,7 @@ class FilterClass implements Filter$.Any {
377
401
  });
378
402
  }
379
403
 
380
- private static '_timeRangeFilter'(
404
+ private static _timeRangeFilter(
381
405
  field: 'updatedAt' | 'createdAt',
382
406
  range: { after?: Date | number; before?: Date | number },
383
407
  ): Filter$.Any {
@@ -395,33 +419,33 @@ class FilterClass implements Filter$.Any {
395
419
  return filters.length === 1 ? filters[0] : FilterClass.and(...filters);
396
420
  }
397
421
 
398
- static 'not'<F extends Filter$.Any>(filter: F): Filter$.Filter<Filter$.Type<F>> {
422
+ static not<F extends Filter$.Any>(filter: F): Filter$.Filter<Filter$.Type<F>> {
399
423
  return new FilterClass({
400
424
  type: 'not',
401
425
  filter: filter.ast,
402
426
  });
403
427
  }
404
428
 
405
- static 'and'<Filters extends readonly Filter$.Any[]>(
429
+ static and<Filters extends readonly Filter$.Any[]>(
406
430
  ...filters: Filters
407
431
  ): Filter$.Filter<Filter$.Type<Filters[number]>> {
408
432
  return new FilterClass({
409
433
  type: 'and',
410
- filters: filters.map((f) => f.ast),
434
+ filters: filters.map((filter) => filter.ast),
411
435
  });
412
436
  }
413
437
 
414
- static 'or'<Filters extends readonly Filter$.Any[]>(
438
+ static or<Filters extends readonly Filter$.Any[]>(
415
439
  ...filters: Filters
416
440
  ): Filter$.Filter<Filter$.Type<Filters[number]>> {
417
441
  return new FilterClass({
418
442
  type: 'or',
419
- filters: filters.map((f) => f.ast),
443
+ filters: filters.map((filter) => filter.ast),
420
444
  });
421
445
  }
422
446
 
423
447
  /** Returns a human-readable string representation of a Filter AST. */
424
- static 'pretty'(filter: Filter$.Any): string {
448
+ static pretty(filter: Filter$.Any): string {
425
449
  return prettyFilter(filter.ast);
426
450
  }
427
451
 
@@ -435,9 +459,9 @@ class FilterClass implements Filter$.Any {
435
459
  return _filterMatchEntityLocal(filter.ast, entityOrFilter);
436
460
  }) as typeof Filter$.toPredicate;
437
461
 
438
- private 'constructor'(public readonly ast: QueryAST.Filter) {}
462
+ private constructor(public readonly ast: QueryAST.Filter) {}
439
463
 
440
- '~Filter' = FilterClass.variance;
464
+ [FilterTypeId] = FilterClass.variance;
441
465
  }
442
466
 
443
467
  export const Filter1: typeof Filter$ = FilterClass;
@@ -495,24 +519,29 @@ const processPredicate = (predicate: any): QueryAST.Filter => {
495
519
  };
496
520
 
497
521
  class QueryClass implements Query$.Any {
498
- private static 'variance': Query$.Any['~Query'] = {} as Query$.Any['~Query'];
522
+ static 'QueryTypeId': Query$.QueryTypeId = QueryTypeId;
523
+
524
+ private static 'variance': Query$.Any[typeof QueryTypeId] = {} as Query$.Any[typeof QueryTypeId];
499
525
 
500
- static 'is'(value: unknown): value is Query$.Any {
501
- return typeof value === 'object' && value !== null && '~Query' in value;
526
+ private static 'projectionVariance': Query$.Projection<any>[typeof ProjectionTypeId] =
527
+ {} as Query$.Projection<any>[typeof ProjectionTypeId];
528
+
529
+ static is(value: unknown): value is Query$.Any {
530
+ return typeof value === 'object' && value !== null && QueryTypeId in value;
502
531
  }
503
532
 
504
- static 'fromAst'(ast: QueryAST.Query): Query$.Any {
533
+ static fromAst(ast: QueryAST.Query): Query$.Any {
505
534
  return new QueryClass(ast);
506
535
  }
507
536
 
508
- static 'select'<F extends Filter$.Any>(filter: F): Query$.Query<Filter$.Type<F>> {
537
+ static select<F extends Filter$.Any>(filter: F): Query$.Query<Filter$.Type<F>> {
509
538
  return new QueryClass({
510
539
  type: 'select',
511
540
  filter: filter.ast,
512
541
  });
513
542
  }
514
543
 
515
- 'select'(filter: Filter$.Any | Filter$.Props<any>): Query$.Any {
544
+ select(filter: Filter$.Any | Filter$.Props<any>): Query$.Any {
516
545
  if (FilterClass.is(filter)) {
517
546
  return new QueryClass({
518
547
  type: 'filter',
@@ -528,13 +557,17 @@ class QueryClass implements Query$.Any {
528
557
  }
529
558
  }
530
559
 
531
- static 'type'<S extends Schema.Schema.All>(
560
+ project(property: string): Query$.Projection<any> {
561
+ return { [ProjectionTypeId]: QueryClass.projectionVariance, query: this.ast, property };
562
+ }
563
+
564
+ static type<S extends Schema.Schema.All>(
532
565
  schema: S,
533
566
  predicates?: Filter$.Props<Schema.Schema.Type<S>>,
534
567
  ): Query$.Query<Schema.Schema.Type<S>>;
535
- static 'type'(type: Type$.Type, predicates?: Filter$.Props<Obj$.Unknown>): Query$.Query<Obj$.Unknown>;
536
- static 'type'(schema: string, predicates?: Filter$.Props<unknown>): Query$.Query<any>;
537
- static 'type'(schema: Schema.Schema.All | Type$.Type | string, predicates?: Filter$.Props<unknown>): Query$.Any {
568
+ static type(type: Type$.Type, predicates?: Filter$.Props<Obj$.Unknown>): Query$.Query<Obj$.Unknown>;
569
+ static type(schema: string, predicates?: Filter$.Props<unknown>): Query$.Query<any>;
570
+ static type(schema: Schema.Schema.All | Type$.Type | string, predicates?: Filter$.Props<unknown>): Query$.Any {
538
571
  if (typeof schema !== 'string') {
539
572
  throw new TypeError('expected typename as the first paramter');
540
573
  }
@@ -544,7 +577,7 @@ class QueryClass implements Query$.Any {
544
577
  });
545
578
  }
546
579
 
547
- static 'all'(...queries: Query$.Any[]): Query$.Any {
580
+ static all(...queries: Query$.Any[]): Query$.Any {
548
581
  if (queries.length === 0) {
549
582
  throw new TypeError(
550
583
  'Query.all combines results of multiple queries, to query all objects use Query.select(Filter.everything())',
@@ -556,7 +589,7 @@ class QueryClass implements Query$.Any {
556
589
  });
557
590
  }
558
591
 
559
- static 'without'<T>(source: Query$.Query<T>, exclude: Query$.Query<T>): Query$.Query<T> {
592
+ static without<T>(source: Query$.Query<T>, exclude: Query$.Query<T>): Query$.Query<T> {
560
593
  return new QueryClass({
561
594
  type: 'set-difference',
562
595
  source: source.ast,
@@ -564,7 +597,11 @@ class QueryClass implements Query$.Any {
564
597
  });
565
598
  }
566
599
 
567
- static 'from'(...args: any[]): Query$.Any {
600
+ static project(query: Query$.Any, property: string): Query$.Projection<any> {
601
+ return { [ProjectionTypeId]: QueryClass.projectionVariance, query: query.ast, property };
602
+ }
603
+
604
+ static from(...args: any[]): Query$.Any {
568
605
  const baseQuery: QueryAST.Query = {
569
606
  type: 'select',
570
607
  filter: FilterClass.everything().ast,
@@ -573,7 +610,7 @@ class QueryClass implements Query$.Any {
573
610
  return (wrapper.from as (...args: any[]) => Query$.Any)(...args);
574
611
  }
575
612
 
576
- 'from'(...args: any[]): Query$.Any {
613
+ from(...args: any[]): Query$.Any {
577
614
  // Variadic raw scopes: `.from(Scope.space(), Scope.registry())`.
578
615
  if (args.length > 1 && args.every((arg) => _isScopeLike(arg))) {
579
616
  return new QueryClass({
@@ -604,15 +641,15 @@ class QueryClass implements Query$.Any {
604
641
  }
605
642
 
606
643
  /** Returns a human-readable string representation of a Query AST. */
607
- static 'pretty'(query: Query$.Any): string {
644
+ static pretty(query: Query$.Any): string {
608
645
  return prettyQuery(query.ast);
609
646
  }
610
647
 
611
- 'constructor'(public readonly ast: QueryAST.Query) {}
648
+ constructor(public readonly ast: QueryAST.Query) {}
612
649
 
613
- '~Query' = QueryClass.variance;
650
+ [QueryTypeId] = QueryClass.variance;
614
651
 
615
- 'reference'(key: string): Query$.Any {
652
+ reference(key: string): Query$.Any {
616
653
  return new QueryClass({
617
654
  type: 'reference-traversal',
618
655
  anchor: this.ast,
@@ -620,7 +657,7 @@ class QueryClass implements Query$.Any {
620
657
  });
621
658
  }
622
659
 
623
- 'referencedBy'(target?: Type$.AnyEntity | string, key?: string): Query$.Any {
660
+ referencedBy(target?: Type$.AnyEntity | string, key?: string): Query$.Any {
624
661
  if (target !== undefined && typeof target !== 'string') {
625
662
  throw new TypeError('referencedBy requires a typename string in query-lite');
626
663
  }
@@ -633,7 +670,7 @@ class QueryClass implements Query$.Any {
633
670
  });
634
671
  }
635
672
 
636
- 'sourceOf'(
673
+ sourceOf(
637
674
  relation?: Type$.Relation<any, any, any, any> | string,
638
675
  predicates?: Filter$.Props<unknown> | undefined,
639
676
  ): Query$.Any {
@@ -650,7 +687,7 @@ class QueryClass implements Query$.Any {
650
687
  });
651
688
  }
652
689
 
653
- 'targetOf'(
690
+ targetOf(
654
691
  relation?: Type$.Relation<any, any, any, any> | string,
655
692
  predicates?: Filter$.Props<unknown> | undefined,
656
693
  ): Query$.Any {
@@ -667,7 +704,7 @@ class QueryClass implements Query$.Any {
667
704
  });
668
705
  }
669
706
 
670
- 'source'(): Query$.Any {
707
+ source(): Query$.Any {
671
708
  return new QueryClass({
672
709
  type: 'relation-traversal',
673
710
  anchor: this.ast,
@@ -675,7 +712,7 @@ class QueryClass implements Query$.Any {
675
712
  });
676
713
  }
677
714
 
678
- 'target'(): Query$.Any {
715
+ target(): Query$.Any {
679
716
  return new QueryClass({
680
717
  type: 'relation-traversal',
681
718
  anchor: this.ast,
@@ -683,7 +720,7 @@ class QueryClass implements Query$.Any {
683
720
  });
684
721
  }
685
722
 
686
- 'parent'(): Query$.Any {
723
+ parent(): Query$.Any {
687
724
  return new QueryClass({
688
725
  type: 'hierarchy-traversal',
689
726
  anchor: this.ast,
@@ -691,7 +728,7 @@ class QueryClass implements Query$.Any {
691
728
  });
692
729
  }
693
730
 
694
- 'children'(): Query$.Any {
731
+ children(): Query$.Any {
695
732
  return new QueryClass({
696
733
  type: 'hierarchy-traversal',
697
734
  anchor: this.ast,
@@ -699,15 +736,15 @@ class QueryClass implements Query$.Any {
699
736
  });
700
737
  }
701
738
 
702
- 'orderBy'(...order: Order$.Any[]): Query$.Any {
739
+ orderBy(...order: Order$.Any[]): Query$.Any {
703
740
  return new QueryClass({
704
741
  type: 'order',
705
742
  query: this.ast,
706
- order: order.map((o) => o.ast),
743
+ order: order.map((orderItem) => orderItem.ast),
707
744
  });
708
745
  }
709
746
 
710
- 'limit'(limit: number): Query$.Any {
747
+ limit(limit: number): Query$.Any {
711
748
  return new QueryClass({
712
749
  type: 'limit',
713
750
  query: this.ast,
@@ -715,7 +752,23 @@ class QueryClass implements Query$.Any {
715
752
  });
716
753
  }
717
754
 
718
- 'options'(options: QueryAST.QueryOptions): Query$.Any {
755
+ skip(skip: number): Query$.Any {
756
+ return new QueryClass({
757
+ type: 'skip',
758
+ query: this.ast,
759
+ skip,
760
+ });
761
+ }
762
+
763
+ aggregate(aggregates: Record<string, Aggregate$.Any>): Query$.Any {
764
+ return new QueryClass({
765
+ type: 'aggregate',
766
+ query: this.ast,
767
+ aggregates: Object.entries(aggregates).map(([name, aggregate]) => ({ name, ...aggregate.spec })),
768
+ });
769
+ }
770
+
771
+ options(options: QueryAST.QueryOptions): Query$.Any {
719
772
  return new QueryClass({
720
773
  type: 'options',
721
774
  query: this.ast,
@@ -723,7 +776,7 @@ class QueryClass implements Query$.Any {
723
776
  });
724
777
  }
725
778
 
726
- 'debugLabel'(label: string): Query$.Any {
779
+ debugLabel(label: string): Query$.Any {
727
780
  if (this.ast.type === 'options') {
728
781
  return new QueryClass({
729
782
  type: 'options',
@@ -821,6 +874,8 @@ const prettyFilter = (filter: QueryAST.Filter): string => {
821
874
  return `Filter.${filter.operator}(${JSON.stringify(filter.value)})`;
822
875
  case 'in':
823
876
  return `Filter.in(${filter.values.map((v) => JSON.stringify(v)).join(', ')})`;
877
+ case 'in-query':
878
+ return `Filter.in(${prettyQuery(filter.subquery)}.project(${JSON.stringify(filter.property)}))`;
824
879
  case 'contains':
825
880
  return `Filter.contains(${JSON.stringify(filter.value)})`;
826
881
  case 'range':
@@ -879,7 +934,7 @@ const prettyQuery = (query: QueryAST.Query): string => {
879
934
  case 'order': {
880
935
  const orders = query.order.map((o) => {
881
936
  if (o.kind === 'natural') {
882
- return 'Order.natural';
937
+ return `Order.natural(${JSON.stringify(o.direction)})`;
883
938
  }
884
939
  if (o.kind === 'rank') {
885
940
  return `Order.rank(${JSON.stringify(o.direction)})`;
@@ -924,5 +979,21 @@ const prettyQuery = (query: QueryAST.Query): string => {
924
979
  }
925
980
  case 'limit':
926
981
  return `${prettyQuery(query.query)}.limit(${query.limit})`;
982
+ case 'skip':
983
+ return `${prettyQuery(query.query)}.skip(${query.skip})`;
984
+ case 'aggregate': {
985
+ const aggregates = query.aggregates.map((aggregate) => {
986
+ const arg =
987
+ aggregate.kind === 'items'
988
+ ? aggregate.limit !== undefined
989
+ ? `{ limit: ${aggregate.limit} }`
990
+ : ''
991
+ : aggregate.kind === 'count'
992
+ ? ''
993
+ : JSON.stringify(aggregate.property);
994
+ return `${JSON.stringify(aggregate.name)}: Aggregate.${aggregate.kind}(${arg})`;
995
+ });
996
+ return `${prettyQuery(query.query)}.aggregate({ ${aggregates.join(', ')} })`;
997
+ }
927
998
  }
928
999
  };