@dxos/echo 0.8.2-main.5ca3450 → 0.8.2-main.600d381

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/src/query/api.ts CHANGED
@@ -3,6 +3,7 @@
3
3
  //
4
4
 
5
5
  import { type Schema } from 'effect';
6
+ import type { Simplify } from 'effect/Schema';
6
7
 
7
8
  import { raise } from '@dxos/debug';
8
9
  import { getSchemaDXN, type Ref } from '@dxos/echo-schema';
@@ -17,7 +18,15 @@ export interface Query<T> {
17
18
  // TODO(dmaretskyi): See new effect-schema approach to variance.
18
19
  '~Query': { value: T };
19
20
 
20
- ast: QueryAST.AST;
21
+ ast: QueryAST.Query;
22
+
23
+ /**
24
+ * Filter the current selection based on a filter.
25
+ * @param filter - Filter to select the objects.
26
+ * @returns Query for the selected objects.
27
+ */
28
+ select(filter: Filter<T>): Query<T>;
29
+ select(props: Filter.Props<T>): Query<T>;
21
30
 
22
31
  /**
23
32
  * Traverse an outgoing reference.
@@ -46,7 +55,7 @@ export interface Query<T> {
46
55
  */
47
56
  sourceOf<S extends Schema.Schema.All>(
48
57
  relation: S,
49
- predicates?: PredicateSet<Schema.Schema.Type<S>>,
58
+ predicates?: Filter.Props<Schema.Schema.Type<S>>,
50
59
  ): Query<Schema.Schema.Type<S>>;
51
60
 
52
61
  /**
@@ -57,7 +66,7 @@ export interface Query<T> {
57
66
  */
58
67
  targetOf<S extends Schema.Schema.All>(
59
68
  relation: S,
60
- predicates?: PredicateSet<Schema.Schema.Type<S>>,
69
+ predicates?: Filter.Props<Schema.Schema.Type<S>>,
61
70
  ): Query<Schema.Schema.Type<S>>;
62
71
 
63
72
  /**
@@ -74,17 +83,80 @@ export interface Query<T> {
74
83
  }
75
84
 
76
85
  interface QueryAPI {
86
+ /**
87
+ * Select objects based on a filter.
88
+ * @param filter - Filter to select the objects.
89
+ * @returns Query for the selected objects.
90
+ */
91
+ select<F extends Filter.Any>(filter: F): Query<Filter.Type<F>>;
92
+
77
93
  /**
78
94
  * Query for objects of a given schema.
79
95
  * @param schema - Schema of the objects.
80
96
  * @param predicates - Predicates to filter the objects.
81
97
  * @returns Query for the objects.
98
+ *
99
+ * Shorthand for: `Query.select(Filter.type(schema, predicates))`.
82
100
  */
83
101
  type<S extends Schema.Schema.All>(
84
102
  schema: S,
85
- predicates?: PredicateSet<Schema.Schema.Type<S>>,
103
+ predicates?: Filter.Props<Schema.Schema.Type<S>>,
86
104
  ): Query<Schema.Schema.Type<S>>;
87
105
 
106
+ /**
107
+ * Full-text or vector search.
108
+ *
109
+ * @deprecated Use `Filter`.
110
+ */
111
+ // text<S extends Schema.Schema.All>(
112
+ // // TODO(dmaretskyi): Allow passing an array of schema here.
113
+ // schema: S,
114
+ // // TODO(dmaretskyi): Consider passing a vector here, but really the embedding should be done on the query-executor side.
115
+ // text: string,
116
+ // options?: Query.TextSearchOptions,
117
+ // ): Query<Schema.Schema.Type<S>>;
118
+
119
+ /**
120
+ * Combine results of multiple queries.
121
+ * @param queries - Queries to combine.
122
+ * @returns Query for the combined results.
123
+ */
124
+ all<T>(...queries: Query<T>[]): Query<T>;
125
+ }
126
+
127
+ export declare namespace Query {
128
+ export type TextSearchOptions = {
129
+ type?: 'full-text' | 'vector';
130
+ };
131
+ }
132
+
133
+ export interface Filter<T> {
134
+ // TODO(dmaretskyi): See new effect-schema approach to variance.
135
+ '~Filter': { value: T };
136
+
137
+ ast: QueryAST.Filter;
138
+ }
139
+
140
+ type Intersection<Types extends readonly unknown[]> = Types extends [infer First, ...infer Rest]
141
+ ? First & Intersection<Rest>
142
+ : unknown;
143
+
144
+ interface FilterAPI {
145
+ is(value: unknown): value is Filter<any>;
146
+
147
+ /**
148
+ * Filter by type.
149
+ */
150
+ type<S extends Schema.Schema.All>(
151
+ schema: S,
152
+ props?: Filter.Props<Schema.Schema.Type<S>>,
153
+ ): Filter<Schema.Schema.Type<S>>;
154
+
155
+ /**
156
+ * Filter by properties.
157
+ */
158
+ // props<T>(props: Filter.Props<T>): Filter<T>;
159
+
88
160
  /**
89
161
  * Full-text or vector search.
90
162
  */
@@ -94,101 +166,126 @@ interface QueryAPI {
94
166
  // TODO(dmaretskyi): Consider passing a vector here, but really the embedding should be done on the query-executor side.
95
167
  text: string,
96
168
  options?: Query.TextSearchOptions,
97
- ): Query<Schema.Schema.Type<S>>;
169
+ ): Filter<Schema.Schema.Type<S>>;
98
170
 
99
171
  /**
100
- * Combine results of multiple queries.
101
- * @param queries - Queries to combine.
102
- * @returns Query for the combined results.
172
+ * Predicate for property to be equal to the provided value.
103
173
  */
104
- all<T>(...queries: Query<T>[]): Query<T>;
174
+ eq<T>(value: T): Filter<T>;
175
+
176
+ /**
177
+ * Predicate for property to be not equal to the provided value.
178
+ */
179
+ neq<T>(value: T): Filter<T>;
180
+
181
+ /**
182
+ * Predicate for property to be greater than the provided value.
183
+ */
184
+ gt<T>(value: T): Filter<T>;
105
185
 
106
186
  /**
107
187
  * Predicate for property to be greater than the provided value.
108
188
  */
109
- gt<T>(value: T): Predicate<T>;
189
+ gt<T>(value: T): Filter<T>;
110
190
 
111
191
  /**
112
192
  * Predicate for property to be greater than or equal to the provided value.
113
193
  */
114
- gte<T>(value: T): Predicate<T>;
194
+ gte<T>(value: T): Filter<T>;
115
195
 
116
196
  /**
117
197
  * Predicate for property to be less than the provided value.
118
198
  */
119
- lt<T>(value: T): Predicate<T>;
199
+ lt<T>(value: T): Filter<T>;
120
200
 
121
201
  /**
122
202
  * Predicate for property to be less than or equal to the provided value.
123
203
  */
124
- lte<T>(value: T): Predicate<T>;
204
+ lte<T>(value: T): Filter<T>;
125
205
 
126
206
  /**
127
207
  * Predicate for property to be in the provided array.
128
208
  * @param values - Values to check against.
129
209
  */
130
- in<T>(...values: T[]): Predicate<T>;
210
+ in<T>(...values: T[]): Filter<T>;
131
211
 
132
212
  /**
133
213
  * Predicate for property to be in the provided range.
134
214
  * @param from - Start of the range (inclusive).
135
215
  * @param to - End of the range (exclusive).
136
216
  */
137
- range<T>(from: T, to: T): Predicate<T>;
217
+ between<T>(from: T, to: T): Filter<T>;
138
218
 
139
- // TODO(dmaretskyi): Add `Query.match` to support pattern matching on string props.
140
- }
219
+ /**
220
+ * Negate the filter.
221
+ */
222
+ not<F extends Filter.Any>(filter: F): Filter<F>;
141
223
 
142
- export declare namespace Query {
143
- export type TextSearchOptions = {
144
- type?: 'full-text' | 'vector';
145
- };
146
- }
224
+ /**
225
+ * Combine filters with a logical AND.
226
+ */
227
+ and<FS extends Filter.Any[]>(...filters: FS): Filter<Filter.And<FS>>;
147
228
 
148
- export interface Predicate<T> {
149
- // TODO(dmaretskyi): See new effect-schema approach to variance.
150
- '~Predicate': { value: T };
229
+ /**
230
+ * Combine filters with a logical OR.
231
+ */
232
+ or<FS extends Filter.Any[]>(...filters: FS): Filter<Filter.Or<FS>>;
151
233
 
152
- ast: QueryAST.Predicate;
234
+ // TODO(dmaretskyi): Add `Filter.match` to support pattern matching on string props.
153
235
  }
154
236
 
155
- const Predicate = {
156
- variance: {} as Predicate<any>['~Predicate'],
237
+ export declare namespace Filter {
238
+ type Props<T> = {
239
+ // Predicate or a value as a shorthand for `eq`.
240
+ [K in keyof T & string]?: Filter<T[K]> | T[K];
241
+ };
157
242
 
158
- make: <T>(ast: QueryAST.Predicate): Predicate<T> => ({ ast, '~Predicate': Predicate.variance }) as Predicate<T>,
159
- };
243
+ type Any = Filter<any>;
160
244
 
161
- type PredicateSet<T> = {
162
- // Predicate or a value as a shorthand for `eq`.
163
- [K in keyof T & string]?: Predicate<T[K]> | T[K];
164
- };
245
+ type Type<F extends Any> = F extends Filter<infer T> ? T : never;
165
246
 
166
- /**
167
- * All property paths inside T that are references.
168
- */
169
- type RefPropKey<T> = { [K in keyof T]: T[K] extends Ref<infer _U> ? K : never }[keyof T] & string;
247
+ type And<FS extends readonly Any[]> = Simplify<Intersection<{ [K in keyof FS]: Type<FS[K]> }>>;
170
248
 
171
- const predicateSetToAst = (predicates: PredicateSet<any>): QueryAST.PredicateSet => {
172
- return Object.fromEntries(
173
- Object.entries(predicates).map(([key, predicate]) => [key, predicate.ast]),
174
- ) as QueryAST.PredicateSet;
175
- };
249
+ type Or<FS extends readonly Any[]> = Simplify<{ [K in keyof FS]: Type<FS[K]> }[number]>;
250
+ }
176
251
 
177
- class QueryClass implements Query<any> {
178
- private static variance: Query<any>['~Query'] = {} as Query<any>['~Query'];
252
+ class FilterClass implements Filter<any> {
253
+ private static variance: Filter<any>['~Filter'] = {} as Filter<any>['~Filter'];
254
+
255
+ static is(value: unknown): value is Filter<any> {
256
+ return typeof value === 'object' && value !== null && '~Filter' in value;
257
+ }
179
258
 
180
- static type(schema: Schema.Schema.All, predicates?: PredicateSet<unknown>): Query<any> {
259
+ static type<S extends Schema.Schema.All>(
260
+ schema: S,
261
+ props?: Filter.Props<Schema.Schema.Type<S>>,
262
+ ): Filter<Schema.Schema.Type<S>> {
181
263
  const dxn = getSchemaDXN(schema) ?? raise(new TypeError('Schema has no DXN'));
182
- return new QueryClass({
183
- type: 'type',
264
+ return new FilterClass({
265
+ type: 'object',
184
266
  typename: dxn.toString(),
185
- predicates: predicates ? predicateSetToAst(predicates) : undefined,
267
+ props: props ? propsFilterToAst(props) : {},
268
+ });
269
+ }
270
+
271
+ /**
272
+ * @internal
273
+ */
274
+ static props<T>(props: Filter.Props<T>): Filter<T> {
275
+ return new FilterClass({
276
+ type: 'object',
277
+ typename: null,
278
+ props: propsFilterToAst(props),
186
279
  });
187
280
  }
188
281
 
189
- static text(schema: Schema.Schema.All, text: string, options?: Query.TextSearchOptions): Query<any> {
282
+ static text<S extends Schema.Schema.All>(
283
+ schema: S,
284
+ text: string,
285
+ options?: Query.TextSearchOptions,
286
+ ): Filter<Schema.Schema.Type<S>> {
190
287
  const dxn = getSchemaDXN(schema) ?? raise(new TypeError('Schema has no DXN'));
191
- return new QueryClass({
288
+ return new FilterClass({
192
289
  type: 'text-search',
193
290
  typename: dxn.toString(),
194
291
  text,
@@ -196,41 +293,155 @@ class QueryClass implements Query<any> {
196
293
  });
197
294
  }
198
295
 
199
- static all(...queries: Query<any>[]): Query<any> {
200
- return new QueryClass({
201
- type: 'union',
202
- queries: queries.map((q) => q.ast),
296
+ static eq<T>(value: T): Filter<T> {
297
+ return new FilterClass({
298
+ type: 'compare',
299
+ operator: 'eq',
300
+ value,
301
+ });
302
+ }
303
+
304
+ static neq<T>(value: T): Filter<T> {
305
+ return new FilterClass({
306
+ type: 'compare',
307
+ operator: 'neq',
308
+ value,
309
+ });
310
+ }
311
+
312
+ static gt<T>(value: T): Filter<T> {
313
+ return new FilterClass({
314
+ type: 'compare',
315
+ operator: 'gt',
316
+ value,
317
+ });
318
+ }
319
+
320
+ static gte<T>(value: T): Filter<T> {
321
+ return new FilterClass({
322
+ type: 'compare',
323
+ operator: 'gte',
324
+ value,
325
+ });
326
+ }
327
+
328
+ static lt<T>(value: T): Filter<T> {
329
+ return new FilterClass({
330
+ type: 'compare',
331
+ operator: 'lt',
332
+ value,
333
+ });
334
+ }
335
+
336
+ static lte<T>(value: T): Filter<T> {
337
+ return new FilterClass({
338
+ type: 'compare',
339
+ operator: 'lte',
340
+ value,
341
+ });
342
+ }
343
+
344
+ static in<T>(...values: T[]): Filter<T> {
345
+ return new FilterClass({
346
+ type: 'in',
347
+ values,
203
348
  });
204
349
  }
205
350
 
206
- static gt(value: unknown): Predicate<any> {
207
- return Predicate.make({ type: 'gt', value });
351
+ static between<T>(from: T, to: T): Filter<T> {
352
+ return new FilterClass({
353
+ type: 'range',
354
+ from,
355
+ to,
356
+ });
208
357
  }
209
358
 
210
- static gte(value: unknown): Predicate<any> {
211
- return Predicate.make({ type: 'gte', value });
359
+ static not<F extends Filter.Any>(filter: F): Filter<F> {
360
+ return new FilterClass({
361
+ type: 'not',
362
+ filter: filter.ast,
363
+ });
212
364
  }
213
365
 
214
- static lt(value: unknown): Predicate<any> {
215
- return Predicate.make({ type: 'lt', value });
366
+ static and<T>(...filters: Filter<T>[]): Filter<T> {
367
+ return new FilterClass({
368
+ type: 'and',
369
+ filters: filters.map((f) => f.ast),
370
+ });
216
371
  }
217
372
 
218
- static lte(value: unknown): Predicate<any> {
219
- return Predicate.make({ type: 'lte', value });
373
+ static or<T>(...filters: Filter<T>[]): Filter<T> {
374
+ return new FilterClass({
375
+ type: 'or',
376
+ filters: filters.map((f) => f.ast),
377
+ });
378
+ }
379
+
380
+ private constructor(public readonly ast: QueryAST.Filter) {}
381
+
382
+ '~Filter' = FilterClass.variance;
383
+ }
384
+
385
+ export const Filter: FilterAPI = FilterClass;
386
+
387
+ /**
388
+ * All property paths inside T that are references.
389
+ */
390
+ type RefPropKey<T> = { [K in keyof T]: T[K] extends Ref<infer _U> ? K : never }[keyof T] & string;
391
+
392
+ const propsFilterToAst = (predicates: Filter.Props<any>): Record<string, QueryAST.Filter> => {
393
+ return Object.fromEntries(
394
+ Object.entries(predicates).map(([key, predicate]) => [
395
+ key,
396
+ Filter.is(predicate) ? predicate.ast : Filter.eq(predicate).ast,
397
+ ]),
398
+ ) as Record<string, QueryAST.Filter>;
399
+ };
400
+
401
+ class QueryClass implements Query<any> {
402
+ private static variance: Query<any>['~Query'] = {} as Query<any>['~Query'];
403
+
404
+ static select<F extends Filter.Any>(filter: F): Query<Filter.Type<F>> {
405
+ return new QueryClass({
406
+ type: 'select',
407
+ filter: filter.ast,
408
+ });
220
409
  }
221
410
 
222
- static in(...values: unknown[]): Predicate<any> {
223
- return Predicate.make({ type: 'in', values });
411
+ static type(schema: Schema.Schema.All, predicates?: Filter.Props<unknown>): Query<any> {
412
+ return new QueryClass({
413
+ type: 'select',
414
+ filter: FilterClass.type(schema, predicates).ast,
415
+ });
224
416
  }
225
417
 
226
- static range(from: unknown, to: unknown): Predicate<any> {
227
- return Predicate.make({ type: 'range', from, to });
418
+ static all(...queries: Query<any>[]): Query<any> {
419
+ return new QueryClass({
420
+ type: 'union',
421
+ queries: queries.map((q) => q.ast),
422
+ });
228
423
  }
229
424
 
230
- constructor(public readonly ast: QueryAST.AST) {}
425
+ constructor(public readonly ast: QueryAST.Query) {}
231
426
 
232
427
  '~Query' = QueryClass.variance;
233
428
 
429
+ select(filter: Filter<any> | Filter.Props<any>): Query<any> {
430
+ if (Filter.is(filter)) {
431
+ return new QueryClass({
432
+ type: 'filter',
433
+ selection: this.ast,
434
+ filter: filter.ast,
435
+ });
436
+ } else {
437
+ return new QueryClass({
438
+ type: 'filter',
439
+ selection: this.ast,
440
+ filter: FilterClass.props(filter).ast,
441
+ });
442
+ }
443
+ }
444
+
234
445
  reference(key: string): Query<any> {
235
446
  return new QueryClass({
236
447
  type: 'reference-traversal',
@@ -249,25 +460,21 @@ class QueryClass implements Query<any> {
249
460
  });
250
461
  }
251
462
 
252
- sourceOf(relation: Schema.Schema.All, predicates?: PredicateSet<unknown> | undefined): Query<any> {
253
- const dxn = getSchemaDXN(relation) ?? raise(new TypeError('Relation schema has no DXN'));
463
+ sourceOf(relation: Schema.Schema.All, predicates?: Filter.Props<unknown> | undefined): Query<any> {
254
464
  return new QueryClass({
255
465
  type: 'relation',
256
466
  anchor: this.ast,
257
467
  direction: 'outgoing',
258
- typename: dxn.toString(),
259
- predicates: predicates ? predicateSetToAst(predicates) : undefined,
468
+ filter: FilterClass.type(relation, predicates).ast,
260
469
  });
261
470
  }
262
471
 
263
- targetOf(relation: Schema.Schema.All, predicates?: PredicateSet<unknown> | undefined): Query<any> {
264
- const dxn = getSchemaDXN(relation) ?? raise(new TypeError('Relation schema has no DXN'));
472
+ targetOf(relation: Schema.Schema.All, predicates?: Filter.Props<unknown> | undefined): Query<any> {
265
473
  return new QueryClass({
266
474
  type: 'relation',
267
475
  anchor: this.ast,
268
476
  direction: 'incoming',
269
- typename: dxn.toString(),
270
- predicates: predicates ? predicateSetToAst(predicates) : undefined,
477
+ filter: FilterClass.type(relation, predicates).ast,
271
478
  });
272
479
  }
273
480