@dxos/echo 0.8.2-main.2f9c567 → 0.8.2-main.30e4dbb
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/dist/lib/browser/index.mjs +19 -4
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node/index.cjs +16 -1
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +19 -4
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/experimental/database.d.ts.map +1 -0
- package/dist/types/src/experimental/index.d.ts +1 -0
- package/dist/types/src/experimental/index.d.ts.map +1 -0
- package/dist/types/src/experimental/queue.d.ts.map +1 -0
- package/dist/types/src/experimental/space.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +3 -1
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/query/api.d.ts +183 -0
- package/dist/types/src/query/api.d.ts.map +1 -0
- package/dist/types/src/query/ast.d.ts +146 -0
- package/dist/types/src/query/ast.d.ts.map +1 -0
- package/dist/types/src/query/query.test.d.ts +2 -0
- package/dist/types/src/query/query.test.d.ts.map +1 -0
- package/dist/types/src/type/Relation.d.ts +16 -0
- package/dist/types/src/type/Relation.d.ts.map +1 -0
- package/dist/types/src/{api/type.d.ts → type/Type.d.ts} +1 -1
- package/dist/types/src/type/Type.d.ts.map +1 -0
- package/dist/types/src/type/Type.test.d.ts +2 -0
- package/dist/types/src/type/Type.test.d.ts.map +1 -0
- package/dist/types/src/type/index.d.ts +3 -0
- package/dist/types/src/type/index.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +13 -13
- package/src/{api → experimental}/index.ts +0 -5
- package/src/index.ts +4 -1
- package/src/query/api.ts +498 -0
- package/src/query/ast.ts +179 -0
- package/src/query/query.test.ts +146 -0
- package/src/type/Relation.ts +17 -0
- package/src/{api.test.ts → type/Type.test.ts} +38 -19
- package/src/{api/type.ts → type/Type.ts} +1 -1
- package/src/type/index.ts +6 -0
- package/dist/types/src/api/database.d.ts.map +0 -1
- package/dist/types/src/api/index.d.ts +0 -4
- package/dist/types/src/api/index.d.ts.map +0 -1
- package/dist/types/src/api/queue.d.ts.map +0 -1
- package/dist/types/src/api/space.d.ts.map +0 -1
- package/dist/types/src/api/type.d.ts.map +0 -1
- package/dist/types/src/api.test.d.ts +0 -2
- package/dist/types/src/api.test.d.ts.map +0 -1
- /package/dist/types/src/{api → experimental}/database.d.ts +0 -0
- /package/dist/types/src/{api → experimental}/queue.d.ts +0 -0
- /package/dist/types/src/{api → experimental}/space.d.ts +0 -0
- /package/src/{api → experimental}/database.ts +0 -0
- /package/src/{api → experimental}/queue.ts +0 -0
- /package/src/{api → experimental}/space.ts +0 -0
package/src/query/api.ts
ADDED
|
@@ -0,0 +1,498 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type Schema } from 'effect';
|
|
6
|
+
import type { Simplify } from 'effect/Schema';
|
|
7
|
+
|
|
8
|
+
import { raise } from '@dxos/debug';
|
|
9
|
+
import { getSchemaDXN, type Ref } from '@dxos/echo-schema';
|
|
10
|
+
|
|
11
|
+
import type * as QueryAST from './ast';
|
|
12
|
+
import type { Relation } from '..';
|
|
13
|
+
|
|
14
|
+
// TODO(dmaretskyi): Split up into interfaces for objects and relations so they can have separate verbs.
|
|
15
|
+
// TODO(dmaretskyi): Undirected relation traversals.
|
|
16
|
+
|
|
17
|
+
export interface Query<T> {
|
|
18
|
+
// TODO(dmaretskyi): See new effect-schema approach to variance.
|
|
19
|
+
'~Query': { value: T };
|
|
20
|
+
|
|
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>;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Traverse an outgoing reference.
|
|
33
|
+
* @param key - Property path inside T that is a reference.
|
|
34
|
+
* @returns Query for the target of the reference.
|
|
35
|
+
*/
|
|
36
|
+
reference<K extends RefPropKey<T>>(key: K): Query<Ref.Target<T[K]>>;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Find objects referencing this object.
|
|
40
|
+
* @param target - Schema of the referencing object.
|
|
41
|
+
* @param key - Property path inside the referencing object that is a reference.
|
|
42
|
+
* @returns Query for the referencing objects.
|
|
43
|
+
*/
|
|
44
|
+
// TODO(dmaretskyi): any way to enforce `Ref.Target<Schema.Schema.Type<S>[key]> == T`?
|
|
45
|
+
referencedBy<S extends Schema.Schema.All>(
|
|
46
|
+
target: S,
|
|
47
|
+
key: RefPropKey<Schema.Schema.Type<S>>,
|
|
48
|
+
): Query<Schema.Schema.Type<S>>;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Find relations where this object is the source.
|
|
52
|
+
* @returns Query for the relation objects.
|
|
53
|
+
* @param relation - Schema of the relation.
|
|
54
|
+
* @param predicates - Predicates to filter the relation objects.
|
|
55
|
+
*/
|
|
56
|
+
sourceOf<S extends Schema.Schema.All>(
|
|
57
|
+
relation: S,
|
|
58
|
+
predicates?: Filter.Props<Schema.Schema.Type<S>>,
|
|
59
|
+
): Query<Schema.Schema.Type<S>>;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Find relations where this object is the target.
|
|
63
|
+
* @returns Query for the relation objects.
|
|
64
|
+
* @param relation - Schema of the relation.
|
|
65
|
+
* @param predicates - Predicates to filter the relation objects.
|
|
66
|
+
*/
|
|
67
|
+
targetOf<S extends Schema.Schema.All>(
|
|
68
|
+
relation: S,
|
|
69
|
+
predicates?: Filter.Props<Schema.Schema.Type<S>>,
|
|
70
|
+
): Query<Schema.Schema.Type<S>>;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* For a query for relations, get the source objects.
|
|
74
|
+
* @returns Query for the source objects.
|
|
75
|
+
*/
|
|
76
|
+
source(): Query<Relation.Source<T>>;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* For a query for relations, get the target objects.
|
|
80
|
+
* @returns Query for the target objects.
|
|
81
|
+
*/
|
|
82
|
+
target(): Query<Relation.Target<T>>;
|
|
83
|
+
}
|
|
84
|
+
|
|
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
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Query for objects of a given schema.
|
|
95
|
+
* @param schema - Schema of the objects.
|
|
96
|
+
* @param predicates - Predicates to filter the objects.
|
|
97
|
+
* @returns Query for the objects.
|
|
98
|
+
*
|
|
99
|
+
* Shorthand for: `Query.select(Filter.type(schema, predicates))`.
|
|
100
|
+
*/
|
|
101
|
+
type<S extends Schema.Schema.All>(
|
|
102
|
+
schema: S,
|
|
103
|
+
predicates?: Filter.Props<Schema.Schema.Type<S>>,
|
|
104
|
+
): Query<Schema.Schema.Type<S>>;
|
|
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
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Full-text or vector search.
|
|
162
|
+
*/
|
|
163
|
+
text<S extends Schema.Schema.All>(
|
|
164
|
+
// TODO(dmaretskyi): Allow passing an array of schema here.
|
|
165
|
+
schema: S,
|
|
166
|
+
// TODO(dmaretskyi): Consider passing a vector here, but really the embedding should be done on the query-executor side.
|
|
167
|
+
text: string,
|
|
168
|
+
options?: Query.TextSearchOptions,
|
|
169
|
+
): Filter<Schema.Schema.Type<S>>;
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Predicate for property to be equal to the provided value.
|
|
173
|
+
*/
|
|
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>;
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Predicate for property to be greater than the provided value.
|
|
188
|
+
*/
|
|
189
|
+
gt<T>(value: T): Filter<T>;
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Predicate for property to be greater than or equal to the provided value.
|
|
193
|
+
*/
|
|
194
|
+
gte<T>(value: T): Filter<T>;
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Predicate for property to be less than the provided value.
|
|
198
|
+
*/
|
|
199
|
+
lt<T>(value: T): Filter<T>;
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Predicate for property to be less than or equal to the provided value.
|
|
203
|
+
*/
|
|
204
|
+
lte<T>(value: T): Filter<T>;
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Predicate for property to be in the provided array.
|
|
208
|
+
* @param values - Values to check against.
|
|
209
|
+
*/
|
|
210
|
+
in<T>(...values: T[]): Filter<T>;
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Predicate for property to be in the provided range.
|
|
214
|
+
* @param from - Start of the range (inclusive).
|
|
215
|
+
* @param to - End of the range (exclusive).
|
|
216
|
+
*/
|
|
217
|
+
between<T>(from: T, to: T): Filter<T>;
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Negate the filter.
|
|
221
|
+
*/
|
|
222
|
+
not<F extends Filter.Any>(filter: F): Filter<F>;
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Combine filters with a logical AND.
|
|
226
|
+
*/
|
|
227
|
+
and<FS extends Filter.Any[]>(...filters: FS): Filter<Filter.And<FS>>;
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Combine filters with a logical OR.
|
|
231
|
+
*/
|
|
232
|
+
or<FS extends Filter.Any[]>(...filters: FS): Filter<Filter.Or<FS>>;
|
|
233
|
+
|
|
234
|
+
// TODO(dmaretskyi): Add `Filter.match` to support pattern matching on string props.
|
|
235
|
+
}
|
|
236
|
+
|
|
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
|
+
};
|
|
242
|
+
|
|
243
|
+
type Any = Filter<any>;
|
|
244
|
+
|
|
245
|
+
type Type<F extends Any> = F extends Filter<infer T> ? T : never;
|
|
246
|
+
|
|
247
|
+
type And<FS extends readonly Any[]> = Simplify<Intersection<{ [K in keyof FS]: Type<FS[K]> }>>;
|
|
248
|
+
|
|
249
|
+
type Or<FS extends readonly Any[]> = Simplify<{ [K in keyof FS]: Type<FS[K]> }[number]>;
|
|
250
|
+
}
|
|
251
|
+
|
|
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
|
+
}
|
|
258
|
+
|
|
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>> {
|
|
263
|
+
const dxn = getSchemaDXN(schema) ?? raise(new TypeError('Schema has no DXN'));
|
|
264
|
+
return new FilterClass({
|
|
265
|
+
type: 'object',
|
|
266
|
+
typename: dxn.toString(),
|
|
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),
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
static text<S extends Schema.Schema.All>(
|
|
283
|
+
schema: S,
|
|
284
|
+
text: string,
|
|
285
|
+
options?: Query.TextSearchOptions,
|
|
286
|
+
): Filter<Schema.Schema.Type<S>> {
|
|
287
|
+
const dxn = getSchemaDXN(schema) ?? raise(new TypeError('Schema has no DXN'));
|
|
288
|
+
return new FilterClass({
|
|
289
|
+
type: 'text-search',
|
|
290
|
+
typename: dxn.toString(),
|
|
291
|
+
text,
|
|
292
|
+
searchKind: options?.type,
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
|
|
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,
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
static between<T>(from: T, to: T): Filter<T> {
|
|
352
|
+
return new FilterClass({
|
|
353
|
+
type: 'range',
|
|
354
|
+
from,
|
|
355
|
+
to,
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
static not<F extends Filter.Any>(filter: F): Filter<F> {
|
|
360
|
+
return new FilterClass({
|
|
361
|
+
type: 'not',
|
|
362
|
+
filter: filter.ast,
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
static and<T>(...filters: Filter<T>[]): Filter<T> {
|
|
367
|
+
return new FilterClass({
|
|
368
|
+
type: 'and',
|
|
369
|
+
filters: filters.map((f) => f.ast),
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
|
|
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
|
+
});
|
|
409
|
+
}
|
|
410
|
+
|
|
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
|
+
});
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
static all(...queries: Query<any>[]): Query<any> {
|
|
419
|
+
return new QueryClass({
|
|
420
|
+
type: 'union',
|
|
421
|
+
queries: queries.map((q) => q.ast),
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
constructor(public readonly ast: QueryAST.Query) {}
|
|
426
|
+
|
|
427
|
+
'~Query' = QueryClass.variance;
|
|
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
|
+
|
|
445
|
+
reference(key: string): Query<any> {
|
|
446
|
+
return new QueryClass({
|
|
447
|
+
type: 'reference-traversal',
|
|
448
|
+
anchor: this.ast,
|
|
449
|
+
property: key,
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
referencedBy(target: Schema.Schema.All, key: string): Query<any> {
|
|
454
|
+
const dxn = getSchemaDXN(target) ?? raise(new TypeError('Target schema has no DXN'));
|
|
455
|
+
return new QueryClass({
|
|
456
|
+
type: 'incoming-references',
|
|
457
|
+
anchor: this.ast,
|
|
458
|
+
property: key,
|
|
459
|
+
typename: dxn.toString(),
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
sourceOf(relation: Schema.Schema.All, predicates?: Filter.Props<unknown> | undefined): Query<any> {
|
|
464
|
+
return new QueryClass({
|
|
465
|
+
type: 'relation',
|
|
466
|
+
anchor: this.ast,
|
|
467
|
+
direction: 'outgoing',
|
|
468
|
+
filter: FilterClass.type(relation, predicates).ast,
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
targetOf(relation: Schema.Schema.All, predicates?: Filter.Props<unknown> | undefined): Query<any> {
|
|
473
|
+
return new QueryClass({
|
|
474
|
+
type: 'relation',
|
|
475
|
+
anchor: this.ast,
|
|
476
|
+
direction: 'incoming',
|
|
477
|
+
filter: FilterClass.type(relation, predicates).ast,
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
source(): Query<any> {
|
|
482
|
+
return new QueryClass({
|
|
483
|
+
type: 'relation-traversal',
|
|
484
|
+
anchor: this.ast,
|
|
485
|
+
direction: 'source',
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
target(): Query<any> {
|
|
490
|
+
return new QueryClass({
|
|
491
|
+
type: 'relation-traversal',
|
|
492
|
+
anchor: this.ast,
|
|
493
|
+
direction: 'target',
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
export const Query: QueryAPI = QueryClass;
|
package/src/query/ast.ts
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Schema } from 'effect';
|
|
6
|
+
|
|
7
|
+
import { DXN } from '@dxos/echo-schema';
|
|
8
|
+
|
|
9
|
+
const TypenameSpecifier = Schema.Union(DXN, Schema.Null).annotations({
|
|
10
|
+
description: 'DXN or null. Null means any type will match',
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
// NOTE: This pattern with 3 definitions per schema is need to make the types opaque, and circular references in AST to not cause compiler errors.
|
|
14
|
+
|
|
15
|
+
const FilterObject_ = Schema.Struct({
|
|
16
|
+
type: Schema.Literal('object'),
|
|
17
|
+
typename: TypenameSpecifier,
|
|
18
|
+
props: Schema.Record({
|
|
19
|
+
key: Schema.String.annotations({ description: 'Property name' }),
|
|
20
|
+
value: Schema.suspend(() => Filter),
|
|
21
|
+
}),
|
|
22
|
+
});
|
|
23
|
+
interface FilterObject extends Schema.Schema.Type<typeof FilterObject_> {}
|
|
24
|
+
const FilterObject: Schema.Schema<FilterObject> = FilterObject_;
|
|
25
|
+
|
|
26
|
+
const FilterCompare_ = Schema.Struct({
|
|
27
|
+
type: Schema.Literal('compare'),
|
|
28
|
+
operator: Schema.Literal('eq', 'neq', 'gt', 'gte', 'lt', 'lte'),
|
|
29
|
+
value: Schema.Unknown,
|
|
30
|
+
});
|
|
31
|
+
interface FilterCompare extends Schema.Schema.Type<typeof FilterCompare_> {}
|
|
32
|
+
const FilterCompare: Schema.Schema<FilterCompare> = FilterCompare_;
|
|
33
|
+
|
|
34
|
+
const FilterIn_ = Schema.Struct({
|
|
35
|
+
type: Schema.Literal('in'),
|
|
36
|
+
values: Schema.Array(Schema.Any),
|
|
37
|
+
});
|
|
38
|
+
interface FilterIn extends Schema.Schema.Type<typeof FilterIn_> {}
|
|
39
|
+
const FilterIn: Schema.Schema<FilterIn> = FilterIn_;
|
|
40
|
+
|
|
41
|
+
const FilterRange_ = Schema.Struct({
|
|
42
|
+
type: Schema.Literal('range'),
|
|
43
|
+
from: Schema.Any,
|
|
44
|
+
to: Schema.Any,
|
|
45
|
+
});
|
|
46
|
+
interface FilterRange extends Schema.Schema.Type<typeof FilterRange_> {}
|
|
47
|
+
const FilterRange: Schema.Schema<FilterRange> = FilterRange_;
|
|
48
|
+
|
|
49
|
+
const FilterTextSearch_ = Schema.Struct({
|
|
50
|
+
type: Schema.Literal('text-search'),
|
|
51
|
+
typename: TypenameSpecifier,
|
|
52
|
+
text: Schema.String,
|
|
53
|
+
searchKind: Schema.optional(Schema.Literal('full-text', 'vector')),
|
|
54
|
+
});
|
|
55
|
+
interface FilterTextSearch extends Schema.Schema.Type<typeof FilterTextSearch_> {}
|
|
56
|
+
const FilterTextSearch: Schema.Schema<FilterTextSearch> = FilterTextSearch_;
|
|
57
|
+
|
|
58
|
+
const FilterNot_ = Schema.Struct({
|
|
59
|
+
type: Schema.Literal('not'),
|
|
60
|
+
filter: Schema.suspend(() => Filter),
|
|
61
|
+
});
|
|
62
|
+
interface FilterNot extends Schema.Schema.Type<typeof FilterNot_> {}
|
|
63
|
+
const FilterNot: Schema.Schema<FilterNot> = FilterNot_;
|
|
64
|
+
|
|
65
|
+
const FilterAnd_ = Schema.Struct({
|
|
66
|
+
type: Schema.Literal('and'),
|
|
67
|
+
filters: Schema.Array(Schema.suspend(() => Filter)),
|
|
68
|
+
});
|
|
69
|
+
interface FilterAnd extends Schema.Schema.Type<typeof FilterAnd_> {}
|
|
70
|
+
const FilterAnd: Schema.Schema<FilterAnd> = FilterAnd_;
|
|
71
|
+
|
|
72
|
+
const FilterOr_ = Schema.Struct({
|
|
73
|
+
type: Schema.Literal('or'),
|
|
74
|
+
filters: Schema.Array(Schema.suspend(() => Filter)),
|
|
75
|
+
});
|
|
76
|
+
interface FilterOr extends Schema.Schema.Type<typeof FilterOr_> {}
|
|
77
|
+
const FilterOr: Schema.Schema<FilterOr> = FilterOr_;
|
|
78
|
+
|
|
79
|
+
export const Filter = Schema.Union(
|
|
80
|
+
FilterObject,
|
|
81
|
+
FilterTextSearch,
|
|
82
|
+
FilterCompare,
|
|
83
|
+
FilterIn,
|
|
84
|
+
FilterRange,
|
|
85
|
+
FilterNot,
|
|
86
|
+
FilterAnd,
|
|
87
|
+
FilterOr,
|
|
88
|
+
);
|
|
89
|
+
export type Filter = Schema.Schema.Type<typeof Filter>;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Query objects by type, id, and/or predicates.
|
|
93
|
+
*/
|
|
94
|
+
const QuerySelectClause_ = Schema.Struct({
|
|
95
|
+
type: Schema.Literal('select'),
|
|
96
|
+
filter: Schema.suspend(() => Filter),
|
|
97
|
+
});
|
|
98
|
+
interface QuerySelectClause extends Schema.Schema.Type<typeof QuerySelectClause_> {}
|
|
99
|
+
const QuerySelectClause: Schema.Schema<QuerySelectClause> = QuerySelectClause_;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Filter objects from selection.
|
|
103
|
+
*/
|
|
104
|
+
const QueryFilterClause_ = Schema.Struct({
|
|
105
|
+
type: Schema.Literal('filter'),
|
|
106
|
+
selection: Schema.suspend(() => Query),
|
|
107
|
+
filter: Schema.suspend(() => Filter),
|
|
108
|
+
});
|
|
109
|
+
interface QueryFilterClause extends Schema.Schema.Type<typeof QueryFilterClause_> {}
|
|
110
|
+
const QueryFilterClause: Schema.Schema<QueryFilterClause> = QueryFilterClause_;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Traverse references from an anchor object.
|
|
114
|
+
*/
|
|
115
|
+
const QueryReferenceTraversalClause_ = Schema.Struct({
|
|
116
|
+
type: Schema.Literal('reference-traversal'),
|
|
117
|
+
anchor: Schema.suspend(() => Query),
|
|
118
|
+
property: Schema.String,
|
|
119
|
+
});
|
|
120
|
+
interface QueryReferenceTraversalClause extends Schema.Schema.Type<typeof QueryReferenceTraversalClause_> {}
|
|
121
|
+
const QueryReferenceTraversalClause: Schema.Schema<QueryReferenceTraversalClause> = QueryReferenceTraversalClause_;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Traverse incoming references to an anchor object.
|
|
125
|
+
*/
|
|
126
|
+
const QueryIncomingReferencesClause_ = Schema.Struct({
|
|
127
|
+
type: Schema.Literal('incoming-references'),
|
|
128
|
+
anchor: Schema.suspend(() => Query),
|
|
129
|
+
property: Schema.String,
|
|
130
|
+
typename: TypenameSpecifier,
|
|
131
|
+
});
|
|
132
|
+
interface QueryIncomingReferencesClause extends Schema.Schema.Type<typeof QueryIncomingReferencesClause_> {}
|
|
133
|
+
const QueryIncomingReferencesClause: Schema.Schema<QueryIncomingReferencesClause> = QueryIncomingReferencesClause_;
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Traverse relations connecting to an anchor object.
|
|
137
|
+
*/
|
|
138
|
+
const QueryRelationClause_ = Schema.Struct({
|
|
139
|
+
type: Schema.Literal('relation'),
|
|
140
|
+
anchor: Schema.suspend(() => Query),
|
|
141
|
+
direction: Schema.Literal('outgoing', 'incoming', 'both'),
|
|
142
|
+
filter: Schema.optional(Schema.suspend(() => Filter)),
|
|
143
|
+
});
|
|
144
|
+
interface QueryRelationClause extends Schema.Schema.Type<typeof QueryRelationClause_> {}
|
|
145
|
+
const QueryRelationClause: Schema.Schema<QueryRelationClause> = QueryRelationClause_;
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Traverse into the source or target of a relation.
|
|
149
|
+
*/
|
|
150
|
+
const QueryRelationTraversalClause_ = Schema.Struct({
|
|
151
|
+
type: Schema.Literal('relation-traversal'),
|
|
152
|
+
anchor: Schema.suspend(() => Query),
|
|
153
|
+
direction: Schema.Literal('source', 'target', 'both'),
|
|
154
|
+
});
|
|
155
|
+
interface QueryRelationTraversalClause extends Schema.Schema.Type<typeof QueryRelationTraversalClause_> {}
|
|
156
|
+
const QueryRelationTraversalClause: Schema.Schema<QueryRelationTraversalClause> = QueryRelationTraversalClause_;
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Union of multiple queries.
|
|
160
|
+
*/
|
|
161
|
+
const QueryUnionClause_ = Schema.Struct({
|
|
162
|
+
type: Schema.Literal('union'),
|
|
163
|
+
queries: Schema.Array(Schema.suspend(() => Query)),
|
|
164
|
+
});
|
|
165
|
+
interface QueryUnionClause extends Schema.Schema.Type<typeof QueryUnionClause_> {}
|
|
166
|
+
const QueryUnionClause: Schema.Schema<QueryUnionClause> = QueryUnionClause_;
|
|
167
|
+
|
|
168
|
+
const Query_ = Schema.Union(
|
|
169
|
+
QuerySelectClause,
|
|
170
|
+
QueryFilterClause,
|
|
171
|
+
QueryReferenceTraversalClause,
|
|
172
|
+
QueryIncomingReferencesClause,
|
|
173
|
+
QueryRelationClause,
|
|
174
|
+
QueryRelationTraversalClause,
|
|
175
|
+
QueryUnionClause,
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
export type Query = Schema.Schema.Type<typeof Query_>;
|
|
179
|
+
export const Query: Schema.Schema<Query> = Query_;
|