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