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