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