@dxos/echo-query 0.8.4-main.fd6878d → 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 (64) hide show
  1. package/LICENSE +102 -5
  2. package/README.md +2 -2
  3. package/dist/lib/neutral/index.mjs +918 -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 +10764 -0
  7. package/dist/query-lite/index.d.ts.map +1 -0
  8. package/dist/query-lite/index.js +584 -0
  9. package/dist/query-lite/index.js.map +1 -0
  10. package/dist/types/src/index.d.ts +2 -0
  11. package/dist/types/src/index.d.ts.map +1 -1
  12. package/dist/types/src/parser/gen/index.d.ts +8 -0
  13. package/dist/types/src/parser/gen/index.d.ts.map +1 -0
  14. package/dist/types/src/parser/gen/query.d.ts +3 -0
  15. package/dist/types/src/parser/gen/query.d.ts.map +1 -0
  16. package/dist/types/src/parser/gen/query.terms.d.ts +2 -0
  17. package/dist/types/src/parser/gen/query.terms.d.ts.map +1 -0
  18. package/dist/types/src/parser/index.d.ts +3 -0
  19. package/dist/types/src/parser/index.d.ts.map +1 -0
  20. package/dist/types/src/parser/query-builder.d.ts +89 -0
  21. package/dist/types/src/parser/query-builder.d.ts.map +1 -0
  22. package/dist/types/src/parser/query.test.d.ts +2 -0
  23. package/dist/types/src/parser/query.test.d.ts.map +1 -0
  24. package/dist/types/src/query-lite/index.d.ts +2 -0
  25. package/dist/types/src/query-lite/index.d.ts.map +1 -0
  26. package/dist/types/src/query-lite/query-lite.d.ts +8 -0
  27. package/dist/types/src/query-lite/query-lite.d.ts.map +1 -0
  28. package/dist/types/src/sandbox/index.d.ts +2 -0
  29. package/dist/types/src/sandbox/index.d.ts.map +1 -0
  30. package/dist/types/src/sandbox/query-sandbox.d.ts +21 -0
  31. package/dist/types/src/sandbox/query-sandbox.d.ts.map +1 -0
  32. package/dist/types/src/sandbox/query-sandbox.test.d.ts +2 -0
  33. package/dist/types/src/sandbox/query-sandbox.test.d.ts.map +1 -0
  34. package/dist/types/src/sandbox/quickjs.d.ts +8 -0
  35. package/dist/types/src/sandbox/quickjs.d.ts.map +1 -0
  36. package/dist/types/src/sandbox/quickjs.test.d.ts +2 -0
  37. package/dist/types/src/sandbox/quickjs.test.d.ts.map +1 -0
  38. package/dist/types/tsconfig.tsbuildinfo +1 -1
  39. package/package.json +27 -14
  40. package/src/env.d.ts +8 -0
  41. package/src/index.ts +3 -0
  42. package/src/parser/gen/index.ts +13 -0
  43. package/src/parser/gen/query.terms.ts +27 -0
  44. package/src/parser/gen/query.ts +18 -0
  45. package/src/parser/index.ts +6 -0
  46. package/src/parser/query-builder.ts +805 -0
  47. package/src/parser/query.grammar +130 -0
  48. package/src/parser/query.test.ts +536 -0
  49. package/src/query-lite/index.ts +5 -0
  50. package/src/query-lite/query-lite.ts +833 -0
  51. package/src/sandbox/index.ts +5 -0
  52. package/src/sandbox/query-sandbox.test.ts +54 -0
  53. package/src/sandbox/query-sandbox.ts +72 -0
  54. package/src/sandbox/quickjs.test.ts +67 -0
  55. package/src/sandbox/quickjs.ts +33 -0
  56. package/dist/lib/browser/index.mjs +0 -2
  57. package/dist/lib/browser/index.mjs.map +0 -7
  58. package/dist/lib/browser/meta.json +0 -1
  59. package/dist/lib/node-esm/index.mjs +0 -2
  60. package/dist/lib/node-esm/index.mjs.map +0 -7
  61. package/dist/lib/node-esm/meta.json +0 -1
  62. package/dist/types/src/search.test.d.ts +0 -2
  63. package/dist/types/src/search.test.d.ts.map +0 -1
  64. package/src/search.test.ts +0 -49
@@ -0,0 +1,833 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import type * as Schema from 'effect/Schema';
6
+
7
+ import type { Filter as Filter$, Obj as Obj$, Order as Order$, Query as Query$, Ref, Type as Type$ } from '@dxos/echo';
8
+ import type { ForeignKey, QueryAST } from '@dxos/echo-protocol';
9
+ import { assertArgument } from '@dxos/invariant';
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';
13
+
14
+ //
15
+ // Light-weight implementation of query execution.
16
+ //
17
+
18
+ // TODO(wittjosiah): The `export * as ...` syntax causes tsdown to genereate multiple files which breaks the sandbox.
19
+
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 {
24
+ return typeof value === 'object' && value !== null && '~Order' in value;
25
+ }
26
+
27
+ constructor(public readonly ast: QueryAST.Order) {}
28
+
29
+ '~Order' = OrderClass.variance;
30
+ }
31
+
32
+ namespace Order1 {
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> =>
35
+ new OrderClass({
36
+ kind: 'property',
37
+ property,
38
+ direction,
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
+ });
57
+ }
58
+
59
+ const Order2: typeof Order$ = Order1;
60
+ export { Order2 as Order };
61
+
62
+ class FilterClass implements Filter$.Any {
63
+ private static 'variance': Filter$.Any['~Filter'] = {} as Filter$.Any['~Filter'];
64
+
65
+ static is(value: unknown): value is Filter$.Any {
66
+ return typeof value === 'object' && value !== null && '~Filter' in value;
67
+ }
68
+
69
+ static fromAst(ast: QueryAST.Filter): Filter$.Any {
70
+ return new FilterClass(ast);
71
+ }
72
+
73
+ static everything(): FilterClass {
74
+ return new FilterClass({
75
+ type: 'object',
76
+ typename: null,
77
+ props: {},
78
+ });
79
+ }
80
+
81
+ static nothing(): FilterClass {
82
+ return new FilterClass({
83
+ type: 'not',
84
+ filter: {
85
+ type: 'object',
86
+ typename: null,
87
+ props: {},
88
+ },
89
+ });
90
+ }
91
+
92
+ static relation() {
93
+ return new FilterClass({
94
+ type: 'object',
95
+ typename: null,
96
+ props: {},
97
+ });
98
+ }
99
+
100
+ static id(...ids: EntityId[]): Filter$.Any {
101
+ // assertArgument(
102
+ // ids.every((id) => EntityId.isValid(id)),
103
+ // 'ids',
104
+ // 'ids must be valid',
105
+ // );
106
+
107
+ if (ids.length === 0) {
108
+ return FilterClass.nothing();
109
+ }
110
+
111
+ return new FilterClass({
112
+ type: 'object',
113
+ typename: null,
114
+ id: ids,
115
+ props: {},
116
+ });
117
+ }
118
+
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> {
125
+ if (typeof schema !== 'string') {
126
+ throw new TypeError('expected typename as the first paramter');
127
+ }
128
+ return new FilterClass({
129
+ type: 'object',
130
+ typename: makeTypeDxn(schema),
131
+ ...propsFilterToAst(props ?? {}),
132
+ });
133
+ }
134
+
135
+ static typename(typename: string): Filter$.Any {
136
+ return new FilterClass({
137
+ type: 'object',
138
+ typename: makeTypeDxn(typename),
139
+ props: {},
140
+ });
141
+ }
142
+
143
+ static typeURI(uri: URI.URI): Filter$.Any {
144
+ return new FilterClass({
145
+ type: 'object',
146
+ typename: uri,
147
+ props: {},
148
+ });
149
+ }
150
+
151
+ static tag(tag: string): Filter$.Any {
152
+ return new FilterClass({
153
+ type: 'tag',
154
+ tag,
155
+ });
156
+ }
157
+
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> {
169
+ return new FilterClass({
170
+ type: 'object',
171
+ typename: null,
172
+ ...propsFilterToAst(props),
173
+ });
174
+ }
175
+
176
+ static text(text: string, options?: Filter$.TextSearchOptions): Filter$.Any {
177
+ return new FilterClass({
178
+ type: 'text-search',
179
+ text,
180
+ searchKind: options?.type,
181
+ });
182
+ }
183
+
184
+ static foreignKeys<S extends Type$.AnyEntity | string>(
185
+ schema: S,
186
+ keys: ForeignKey[],
187
+ ): Filter$.Filter<S extends Type$.AnyEntity ? Type$.InstanceType<S> : unknown> {
188
+ assertArgument(typeof schema === 'string', 'schema');
189
+ assertArgument(!schema.startsWith('dxn:'), 'schema');
190
+ return new FilterClass({
191
+ type: 'object',
192
+ typename: makeTypeDxn(schema),
193
+ props: {},
194
+ foreignKeys: keys,
195
+ });
196
+ }
197
+
198
+ static eq<T>(value: T): Filter$.Filter<T | undefined> {
199
+ if (!isRef(value) && typeof value === 'object' && value !== null) {
200
+ throw new TypeError('Cannot use object as a value for eq filter');
201
+ }
202
+
203
+ return new FilterClass({
204
+ type: 'compare',
205
+ operator: 'eq',
206
+ value: isRef(value) ? value.noInline().encode() : value,
207
+ });
208
+ }
209
+
210
+ static neq<T>(value: T): Filter$.Filter<T | undefined> {
211
+ return new FilterClass({
212
+ type: 'compare',
213
+ operator: 'neq',
214
+ value,
215
+ });
216
+ }
217
+
218
+ static gt<T>(value: T): Filter$.Filter<T | undefined> {
219
+ return new FilterClass({
220
+ type: 'compare',
221
+ operator: 'gt',
222
+ value,
223
+ });
224
+ }
225
+
226
+ static gte<T>(value: T): Filter$.Filter<T | undefined> {
227
+ return new FilterClass({
228
+ type: 'compare',
229
+ operator: 'gte',
230
+ value,
231
+ });
232
+ }
233
+
234
+ static lt<T>(value: T): Filter$.Filter<T | undefined> {
235
+ return new FilterClass({
236
+ type: 'compare',
237
+ operator: 'lt',
238
+ value,
239
+ });
240
+ }
241
+
242
+ static lte<T>(value: T): Filter$.Filter<T | undefined> {
243
+ return new FilterClass({
244
+ type: 'compare',
245
+ operator: 'lte',
246
+ value,
247
+ });
248
+ }
249
+
250
+ static in<T>(...values: T[]): Filter$.Filter<T> {
251
+ return new FilterClass({
252
+ type: 'in',
253
+ values,
254
+ });
255
+ }
256
+
257
+ static contains<T>(value: T): Filter$.Filter<readonly T[] | undefined> {
258
+ return new FilterClass({
259
+ type: 'contains',
260
+ value,
261
+ });
262
+ }
263
+
264
+ static between<T>(from: T, to: T): Filter$.Filter<T> {
265
+ return new FilterClass({
266
+ type: 'range',
267
+ from,
268
+ to,
269
+ });
270
+ }
271
+
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>> {
314
+ return new FilterClass({
315
+ type: 'not',
316
+ filter: filter.ast,
317
+ });
318
+ }
319
+
320
+ static and<Filters extends readonly Filter$.Any[]>(
321
+ ...filters: Filters
322
+ ): Filter$.Filter<Filter$.Type<Filters[number]>> {
323
+ return new FilterClass({
324
+ type: 'and',
325
+ filters: filters.map((f) => f.ast),
326
+ });
327
+ }
328
+
329
+ static or<Filters extends readonly Filter$.Any[]>(
330
+ ...filters: Filters
331
+ ): Filter$.Filter<Filter$.Type<Filters[number]>> {
332
+ return new FilterClass({
333
+ type: 'or',
334
+ filters: filters.map((f) => f.ast),
335
+ });
336
+ }
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
+
343
+ private constructor(public readonly ast: QueryAST.Filter) {}
344
+
345
+ '~Filter' = FilterClass.variance;
346
+ }
347
+
348
+ export const Filter1: typeof Filter$ = FilterClass;
349
+ export { Filter1 as Filter };
350
+
351
+ /**
352
+ * All property paths inside T that are references.
353
+ */
354
+ // TODO(dmaretskyi): Filter only properties that are references (or optional references, or unions that include references).
355
+ type RefPropKey<T> = keyof T & string;
356
+
357
+ const propsFilterToAst = (predicates: Filter$.Props<any>): Pick<QueryAST.FilterObject, 'id' | 'props'> => {
358
+ let idFilter: readonly EntityId[] | undefined;
359
+ if ('id' in predicates) {
360
+ assertArgument(
361
+ typeof predicates.id === 'string' || Array.isArray(predicates.id),
362
+ 'predicates.id',
363
+ 'invalid id filter',
364
+ );
365
+ idFilter = typeof predicates.id === 'string' ? [predicates.id] : predicates.id;
366
+ }
367
+
368
+ return {
369
+ id: idFilter,
370
+ props: Object.fromEntries(
371
+ Object.entries(predicates)
372
+ .filter(([prop, _value]) => prop !== 'id')
373
+ .map(([prop, predicate]) => [prop, processPredicate(predicate)]),
374
+ ) as Record<string, QueryAST.Filter>,
375
+ };
376
+ };
377
+
378
+ const processPredicate = (predicate: any): QueryAST.Filter => {
379
+ if (FilterClass.is(predicate)) {
380
+ return predicate.ast;
381
+ }
382
+
383
+ if (Array.isArray(predicate)) {
384
+ throw new Error('Array predicates are not yet supported.');
385
+ }
386
+
387
+ if (!isRef(predicate) && typeof predicate === 'object' && predicate !== null) {
388
+ const nestedProps = Object.fromEntries(
389
+ Object.entries(predicate).map(([key, value]) => [key, processPredicate(value)]),
390
+ );
391
+
392
+ return {
393
+ type: 'object',
394
+ typename: null,
395
+ props: nestedProps,
396
+ };
397
+ }
398
+
399
+ return FilterClass.eq(predicate).ast;
400
+ };
401
+
402
+ class QueryClass implements Query$.Any {
403
+ private static 'variance': Query$.Any['~Query'] = {} as Query$.Any['~Query'];
404
+
405
+ static is(value: unknown): value is Query$.Any {
406
+ return typeof value === 'object' && value !== null && '~Query' in value;
407
+ }
408
+
409
+ static fromAst(ast: QueryAST.Query): Query$.Any {
410
+ return new QueryClass(ast);
411
+ }
412
+
413
+ static select<F extends Filter$.Any>(filter: F): Query$.Query<Filter$.Type<F>> {
414
+ return new QueryClass({
415
+ type: 'select',
416
+ filter: filter.ast,
417
+ });
418
+ }
419
+
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
+ }
446
+ return new QueryClass({
447
+ type: 'select',
448
+ filter: FilterClass.type(schema, predicates).ast,
449
+ });
450
+ }
451
+
452
+ static all(...queries: Query$.Any[]): Query$.Any {
453
+ if (queries.length === 0) {
454
+ throw new TypeError(
455
+ 'Query.all combines results of multiple queries, to query all objects use Query.select(Filter.everything())',
456
+ );
457
+ }
458
+ return new QueryClass({
459
+ type: 'union',
460
+ queries: queries.map((q) => q.ast),
461
+ });
462
+ }
463
+
464
+ static without<T>(source: Query$.Query<T>, exclude: Query$.Query<T>): Query$.Query<T> {
465
+ return new QueryClass({
466
+ type: 'set-difference',
467
+ source: source.ast,
468
+ exclude: exclude.ast,
469
+ });
470
+ }
471
+
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
+ }
480
+
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
+ }
490
+
491
+ const [arg] = args;
492
+ if (arg === 'all-accessible-spaces') {
493
+ return new QueryClass({
494
+ type: 'from',
495
+ query: this.ast,
496
+ from: { _tag: 'scope', scopes: [] },
497
+ });
498
+ }
499
+
500
+ if (_isScopeLike(arg)) {
501
+ return new QueryClass({
502
+ type: 'from',
503
+ query: this.ast,
504
+ from: { _tag: 'scope', scopes: Array.isArray(arg) ? arg : [arg] },
505
+ });
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);
514
+ }
515
+
516
+ constructor(public readonly ast: QueryAST.Query) {}
517
+
518
+ '~Query' = QueryClass.variance;
519
+
520
+ reference(key: string): Query$.Any {
521
+ return new QueryClass({
522
+ type: 'reference-traversal',
523
+ anchor: this.ast,
524
+ property: key,
525
+ });
526
+ }
527
+
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;
533
+ return new QueryClass({
534
+ type: 'incoming-references',
535
+ anchor: this.ast,
536
+ property: key ?? null,
537
+ typename,
538
+ });
539
+ }
540
+
541
+ sourceOf(
542
+ relation?: Type$.Relation<any, any, any, any> | string,
543
+ predicates?: Filter$.Props<unknown> | undefined,
544
+ ): Query$.Any {
545
+ return new QueryClass({
546
+ type: 'relation',
547
+ anchor: this.ast,
548
+ direction: 'outgoing',
549
+ filter:
550
+ relation === undefined
551
+ ? undefined
552
+ : typeof relation === 'string'
553
+ ? FilterClass.type(relation, predicates).ast
554
+ : FilterClass.type(relation, predicates).ast,
555
+ });
556
+ }
557
+
558
+ targetOf(
559
+ relation?: Type$.Relation<any, any, any, any> | string,
560
+ predicates?: Filter$.Props<unknown> | undefined,
561
+ ): Query$.Any {
562
+ return new QueryClass({
563
+ type: 'relation',
564
+ anchor: this.ast,
565
+ direction: 'incoming',
566
+ filter:
567
+ relation === undefined
568
+ ? undefined
569
+ : typeof relation === 'string'
570
+ ? FilterClass.type(relation, predicates).ast
571
+ : FilterClass.type(relation, predicates).ast,
572
+ });
573
+ }
574
+
575
+ source(): Query$.Any {
576
+ return new QueryClass({
577
+ type: 'relation-traversal',
578
+ anchor: this.ast,
579
+ direction: 'source',
580
+ });
581
+ }
582
+
583
+ target(): Query$.Any {
584
+ return new QueryClass({
585
+ type: 'relation-traversal',
586
+ anchor: this.ast,
587
+ direction: 'target',
588
+ });
589
+ }
590
+
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 {
608
+ return new QueryClass({
609
+ type: 'order',
610
+ query: this.ast,
611
+ order: order.map((o) => o.ast),
612
+ });
613
+ }
614
+
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 {
624
+ return new QueryClass({
625
+ type: 'options',
626
+ query: this.ast,
627
+ options,
628
+ });
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
+ }
645
+ }
646
+
647
+ export const Query1: typeof Query$ = QueryClass;
648
+ export { Query1 as Query };
649
+
650
+ const RefTypeId: unique symbol = Symbol('@dxos/echo-query/Ref');
651
+ const isRef = (obj: any): obj is Ref.Ref<any> => {
652
+ return obj && typeof obj === 'object' && RefTypeId in obj;
653
+ };
654
+
655
+ const makeTypeDxn = (typename: string): DXN.DXN => {
656
+ assertArgument(typeof typename === 'string', 'typename');
657
+ assertArgument(!typename.startsWith('dxn:'), '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
+ }
833
+ };