@dxos/echo-pipeline 0.8.4-main.84f28bd → 0.8.4-main.a4bbb77
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/browser/chunk-BVR7OOZK.mjs +251 -0
- package/dist/lib/browser/chunk-BVR7OOZK.mjs.map +7 -0
- package/dist/lib/browser/{chunk-GBFX5J5B.mjs → chunk-XGG76KKU.mjs} +487 -324
- package/dist/lib/browser/chunk-XGG76KKU.mjs.map +7 -0
- package/dist/lib/browser/filter/index.mjs +3 -1
- package/dist/lib/browser/index.mjs +1379 -562
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +107 -44
- package/dist/lib/browser/testing/index.mjs.map +3 -3
- package/dist/lib/node-esm/{chunk-FQFKWA3X.mjs → chunk-CHMJJ4DG.mjs} +487 -324
- package/dist/lib/node-esm/chunk-CHMJJ4DG.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-L3ZEKVDR.mjs +251 -0
- package/dist/lib/node-esm/chunk-L3ZEKVDR.mjs.map +7 -0
- package/dist/lib/node-esm/filter/index.mjs +3 -1
- package/dist/lib/node-esm/index.mjs +1379 -562
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/testing/index.mjs +107 -44
- package/dist/lib/node-esm/testing/index.mjs.map +3 -3
- package/dist/types/src/automerge/automerge-host.d.ts +15 -28
- package/dist/types/src/automerge/automerge-host.d.ts.map +1 -1
- package/dist/types/src/automerge/collection-synchronizer.d.ts +1 -1
- package/dist/types/src/automerge/collection-synchronizer.d.ts.map +1 -1
- package/dist/types/src/automerge/echo-network-adapter.d.ts +8 -1
- package/dist/types/src/automerge/echo-network-adapter.d.ts.map +1 -1
- package/dist/types/src/automerge/echo-replicator.d.ts +21 -2
- package/dist/types/src/automerge/echo-replicator.d.ts.map +1 -1
- package/dist/types/src/automerge/index.d.ts +1 -1
- package/dist/types/src/automerge/index.d.ts.map +1 -1
- package/dist/types/src/automerge/leveldb-storage-adapter.d.ts +1 -1
- package/dist/types/src/automerge/leveldb-storage-adapter.d.ts.map +1 -1
- package/dist/types/src/automerge/mesh-echo-replicator-connection.d.ts +1 -0
- package/dist/types/src/automerge/mesh-echo-replicator-connection.d.ts.map +1 -1
- package/dist/types/src/automerge/mesh-echo-replicator.d.ts.map +1 -1
- package/dist/types/src/common/codec.d.ts +1 -1
- package/dist/types/src/common/codec.d.ts.map +1 -1
- package/dist/types/src/db-host/data-service.d.ts +2 -2
- package/dist/types/src/db-host/data-service.d.ts.map +1 -1
- package/dist/types/src/db-host/database-root.d.ts.map +1 -1
- package/dist/types/src/db-host/documents-synchronizer.d.ts +2 -2
- package/dist/types/src/db-host/documents-synchronizer.d.ts.map +1 -1
- package/dist/types/src/db-host/echo-host.d.ts +2 -2
- package/dist/types/src/db-host/echo-host.d.ts.map +1 -1
- package/dist/types/src/db-host/query-service.d.ts +1 -1
- package/dist/types/src/db-host/query-service.d.ts.map +1 -1
- package/dist/types/src/db-host/space-state-manager.d.ts +1 -1
- package/dist/types/src/db-host/space-state-manager.d.ts.map +1 -1
- package/dist/types/src/edge/echo-edge-replicator.d.ts +4 -2
- package/dist/types/src/edge/echo-edge-replicator.d.ts.map +1 -1
- package/dist/types/src/filter/filter-match.d.ts +4 -1
- package/dist/types/src/filter/filter-match.d.ts.map +1 -1
- package/dist/types/src/metadata/metadata-store.d.ts +1 -1
- package/dist/types/src/metadata/metadata-store.d.ts.map +1 -1
- package/dist/types/src/pipeline/pipeline.d.ts +1 -1
- package/dist/types/src/pipeline/pipeline.d.ts.map +1 -1
- package/dist/types/src/query/errors.d.ts +24 -8
- package/dist/types/src/query/errors.d.ts.map +1 -1
- package/dist/types/src/query/plan.d.ts +8 -1
- package/dist/types/src/query/plan.d.ts.map +1 -1
- package/dist/types/src/query/query-executor.d.ts +4 -1
- package/dist/types/src/query/query-executor.d.ts.map +1 -1
- package/dist/types/src/query/query-planner.d.ts +2 -0
- package/dist/types/src/query/query-planner.d.ts.map +1 -1
- package/dist/types/src/space/admission-discovery-extension.d.ts.map +1 -1
- package/dist/types/src/space/control-pipeline.d.ts +1 -1
- package/dist/types/src/space/control-pipeline.d.ts.map +1 -1
- package/dist/types/src/space/space-manager.d.ts +1 -1
- package/dist/types/src/space/space-manager.d.ts.map +1 -1
- package/dist/types/src/space/space-protocol.d.ts +1 -1
- package/dist/types/src/space/space-protocol.d.ts.map +1 -1
- package/dist/types/src/space/space.d.ts +1 -1
- package/dist/types/src/space/space.d.ts.map +1 -1
- package/dist/types/src/testing/test-agent-builder.d.ts +2 -2
- package/dist/types/src/testing/test-agent-builder.d.ts.map +1 -1
- package/dist/types/src/testing/test-replicator.d.ts +1 -0
- package/dist/types/src/testing/test-replicator.d.ts.map +1 -1
- package/dist/types/src/util.d.ts +1 -1
- package/dist/types/src/util.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +41 -38
- package/src/automerge/automerge-host.test.ts +18 -8
- package/src/automerge/automerge-host.ts +266 -70
- package/src/automerge/automerge-repo.test.ts +67 -16
- package/src/automerge/collection-synchronizer.test.ts +2 -2
- package/src/automerge/collection-synchronizer.ts +4 -4
- package/src/automerge/echo-data-monitor.ts +1 -1
- package/src/automerge/echo-network-adapter.test.ts +3 -3
- package/src/automerge/echo-network-adapter.ts +38 -6
- package/src/automerge/echo-replicator.ts +23 -2
- package/src/automerge/index.ts +1 -1
- package/src/automerge/leveldb-storage-adapter.ts +7 -7
- package/src/automerge/mesh-echo-replicator-connection.ts +4 -0
- package/src/automerge/mesh-echo-replicator.ts +2 -1
- package/src/automerge/storage-adapter.test.ts +1 -1
- package/src/common/space-id.ts +1 -1
- package/src/db-host/data-service.ts +9 -17
- package/src/db-host/database-root.ts +2 -2
- package/src/db-host/documents-synchronizer.test.ts +1 -1
- package/src/db-host/documents-synchronizer.ts +39 -26
- package/src/db-host/echo-host.ts +14 -15
- package/src/db-host/query-service.ts +8 -1
- package/src/db-host/space-state-manager.ts +2 -2
- package/src/edge/echo-edge-replicator.test.ts +5 -3
- package/src/edge/echo-edge-replicator.ts +75 -18
- package/src/filter/filter-match.test.ts +33 -13
- package/src/filter/filter-match.ts +177 -10
- package/src/metadata/metadata-store.ts +3 -3
- package/src/pipeline/pipeline-stress.test.ts +4 -2
- package/src/pipeline/pipeline.test.ts +3 -2
- package/src/pipeline/pipeline.ts +8 -5
- package/src/query/errors.ts +2 -0
- package/src/query/plan.ts +12 -1
- package/src/query/query-executor.ts +71 -11
- package/src/query/query-planner.test.ts +146 -2
- package/src/query/query-planner.ts +81 -11
- package/src/space/admission-discovery-extension.ts +2 -2
- package/src/space/control-pipeline.test.ts +4 -3
- package/src/space/control-pipeline.ts +9 -6
- package/src/space/space-manager.browser.test.ts +1 -1
- package/src/space/space-manager.ts +5 -4
- package/src/space/space-protocol.browser.test.ts +2 -2
- package/src/space/space-protocol.test.ts +3 -2
- package/src/space/space-protocol.ts +6 -3
- package/src/space/space.test.ts +1 -1
- package/src/space/space.ts +3 -2
- package/src/testing/test-agent-builder.ts +4 -3
- package/src/testing/test-replicator.ts +4 -0
- package/src/util.ts +1 -1
- package/dist/lib/browser/chunk-ANZAS5CC.mjs +0 -126
- package/dist/lib/browser/chunk-ANZAS5CC.mjs.map +0 -7
- package/dist/lib/browser/chunk-GBFX5J5B.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-2SAZ7CCF.mjs +0 -126
- package/dist/lib/node-esm/chunk-2SAZ7CCF.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-FQFKWA3X.mjs.map +0 -7
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import type { AutomergeUrl, DocumentId } from '@automerge/automerge-repo';
|
|
6
|
-
import { Match } from 'effect';
|
|
6
|
+
import { Match, Predicate } from 'effect';
|
|
7
7
|
|
|
8
8
|
import { Context, ContextDisposedError, LifecycleState, Resource } from '@dxos/context';
|
|
9
|
-
import { DatabaseDirectory,
|
|
9
|
+
import { DatabaseDirectory, ObjectStructure, type QueryAST, isEncodedReference } from '@dxos/echo-protocol';
|
|
10
10
|
import { EscapedPropPath, type FindResult, type Indexer } from '@dxos/indexing';
|
|
11
11
|
import { invariant } from '@dxos/invariant';
|
|
12
12
|
import { DXN, type ObjectId, PublicKey, type SpaceId } from '@dxos/keys';
|
|
@@ -15,13 +15,16 @@ import { objectPointerCodec } from '@dxos/protocols';
|
|
|
15
15
|
import { type QueryReactivity, type QueryResult } from '@dxos/protocols/proto/dxos/echo/query';
|
|
16
16
|
import { getDeep, isNonNullable } from '@dxos/util';
|
|
17
17
|
|
|
18
|
-
import type { QueryPlan } from './plan';
|
|
19
|
-
import { QueryPlanner } from './query-planner';
|
|
20
18
|
import type { AutomergeHost } from '../automerge';
|
|
21
19
|
import { createIdFromSpaceKey } from '../common';
|
|
22
20
|
import type { SpaceStateManager } from '../db-host';
|
|
23
21
|
import { filterMatchObject } from '../filter';
|
|
24
22
|
|
|
23
|
+
import type { QueryPlan } from './plan';
|
|
24
|
+
import { QueryPlanner } from './query-planner';
|
|
25
|
+
|
|
26
|
+
const isNullable: Predicate.Refinement<unknown, null | undefined> = Predicate.isNullable;
|
|
27
|
+
|
|
25
28
|
type QueryExecutorOptions = {
|
|
26
29
|
indexer: Indexer;
|
|
27
30
|
automergeHost: AutomergeHost;
|
|
@@ -189,7 +192,6 @@ export class QueryExecutor extends Resource {
|
|
|
189
192
|
);
|
|
190
193
|
|
|
191
194
|
if (TRACE_QUERY_EXECUTION) {
|
|
192
|
-
// eslint-disable-next-line no-console
|
|
193
195
|
console.log(ExecutionTrace.format(trace));
|
|
194
196
|
}
|
|
195
197
|
|
|
@@ -245,6 +247,9 @@ export class QueryExecutor extends Resource {
|
|
|
245
247
|
case 'TraverseStep':
|
|
246
248
|
({ workingSet: newWorkingSet, trace } = await this._execTraverseStep(step, workingSet));
|
|
247
249
|
break;
|
|
250
|
+
case 'OrderStep':
|
|
251
|
+
({ workingSet: newWorkingSet, trace } = await this._execOrderStep(step, workingSet));
|
|
252
|
+
break;
|
|
248
253
|
default:
|
|
249
254
|
throw new Error(`Unknown step type: ${(step as any)._tag}`);
|
|
250
255
|
}
|
|
@@ -286,6 +291,7 @@ export class QueryExecutor extends Resource {
|
|
|
286
291
|
|
|
287
292
|
break;
|
|
288
293
|
}
|
|
294
|
+
|
|
289
295
|
case 'IdSelector': {
|
|
290
296
|
const beginLoad = performance.now();
|
|
291
297
|
const items = await Promise.all(
|
|
@@ -299,6 +305,7 @@ export class QueryExecutor extends Resource {
|
|
|
299
305
|
trace.objectCount = workingSet.length;
|
|
300
306
|
break;
|
|
301
307
|
}
|
|
308
|
+
|
|
302
309
|
case 'TypeSelector': {
|
|
303
310
|
const beginIndexQuery = performance.now();
|
|
304
311
|
const indexHits = await this._indexer.execQuery({
|
|
@@ -322,6 +329,7 @@ export class QueryExecutor extends Resource {
|
|
|
322
329
|
|
|
323
330
|
break;
|
|
324
331
|
}
|
|
332
|
+
|
|
325
333
|
case 'TextSelector': {
|
|
326
334
|
const beginIndexQuery = performance.now();
|
|
327
335
|
const indexHits = await this._indexer.execQuery({
|
|
@@ -352,6 +360,7 @@ export class QueryExecutor extends Resource {
|
|
|
352
360
|
trace.objectCount = workingSet.length;
|
|
353
361
|
break;
|
|
354
362
|
}
|
|
363
|
+
|
|
355
364
|
default:
|
|
356
365
|
throw new Error(`Unknown selector type: ${(step.selector as any)._tag}`);
|
|
357
366
|
}
|
|
@@ -367,6 +376,7 @@ export class QueryExecutor extends Resource {
|
|
|
367
376
|
doc: item.doc,
|
|
368
377
|
}),
|
|
369
378
|
);
|
|
379
|
+
|
|
370
380
|
return {
|
|
371
381
|
workingSet: result,
|
|
372
382
|
trace: {
|
|
@@ -382,10 +392,6 @@ export class QueryExecutor extends Resource {
|
|
|
382
392
|
step: QueryPlan.FilterDeletedStep,
|
|
383
393
|
workingSet: QueryItem[],
|
|
384
394
|
): Promise<StepExecutionResult> {
|
|
385
|
-
if (workingSet.length === 6) {
|
|
386
|
-
log.info('FilterDeletedStep', { step, workingSet });
|
|
387
|
-
}
|
|
388
|
-
|
|
389
395
|
const expected = step.mode === 'only-deleted';
|
|
390
396
|
const result = workingSet.filter((item) => ObjectStructure.isDeleted(item.doc) === expected);
|
|
391
397
|
return {
|
|
@@ -428,7 +434,7 @@ export class QueryExecutor extends Resource {
|
|
|
428
434
|
}
|
|
429
435
|
: null;
|
|
430
436
|
} catch {
|
|
431
|
-
log.warn('
|
|
437
|
+
log.warn('invalid reference', { ref: ref['/'] });
|
|
432
438
|
return null;
|
|
433
439
|
}
|
|
434
440
|
});
|
|
@@ -491,7 +497,7 @@ export class QueryExecutor extends Resource {
|
|
|
491
497
|
spaceId: item.spaceId,
|
|
492
498
|
};
|
|
493
499
|
} catch {
|
|
494
|
-
log.warn('
|
|
500
|
+
log.warn('invalid reference', { ref: ref['/'] });
|
|
495
501
|
return null;
|
|
496
502
|
}
|
|
497
503
|
})
|
|
@@ -590,6 +596,60 @@ export class QueryExecutor extends Resource {
|
|
|
590
596
|
};
|
|
591
597
|
}
|
|
592
598
|
|
|
599
|
+
private async _execOrderStep(step: QueryPlan.OrderStep, workingSet: QueryItem[]): Promise<StepExecutionResult> {
|
|
600
|
+
const compareItems = (a: QueryItem, b: QueryItem): number =>
|
|
601
|
+
step.order.reduce((comparison, order) => {
|
|
602
|
+
// If we already have a definitive result, return it
|
|
603
|
+
if (comparison !== 0) {
|
|
604
|
+
return comparison;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
return this._compareByOrder(a, b, order);
|
|
608
|
+
}, 0);
|
|
609
|
+
|
|
610
|
+
const sortedWorkingSet = [...workingSet].sort(compareItems);
|
|
611
|
+
|
|
612
|
+
return {
|
|
613
|
+
workingSet: sortedWorkingSet,
|
|
614
|
+
trace: {
|
|
615
|
+
...ExecutionTrace.makeEmpty(),
|
|
616
|
+
name: 'Order',
|
|
617
|
+
details: JSON.stringify(step.order),
|
|
618
|
+
objectCount: sortedWorkingSet.length,
|
|
619
|
+
},
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
private _compareByOrder(a: QueryItem, b: QueryItem, order: QueryAST.Order): number {
|
|
624
|
+
return Match.type<QueryAST.Order>().pipe(
|
|
625
|
+
Match.withReturnType<number>(),
|
|
626
|
+
Match.when({ kind: 'natural' }, () => a.objectId.localeCompare(b.objectId)),
|
|
627
|
+
Match.when({ kind: 'property' }, ({ property, direction }) => {
|
|
628
|
+
const comparison = this._compareByProperty(a, b, property);
|
|
629
|
+
return direction === 'desc' ? -comparison : comparison;
|
|
630
|
+
}),
|
|
631
|
+
Match.exhaustive,
|
|
632
|
+
)(order);
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
private _compareByProperty(a: QueryItem, b: QueryItem, property: string): number {
|
|
636
|
+
const aValue = a.doc.data[property];
|
|
637
|
+
const bValue = b.doc.data[property];
|
|
638
|
+
|
|
639
|
+
return Match.type<{ a: unknown; b: unknown }>().pipe(
|
|
640
|
+
Match.withReturnType<number>(),
|
|
641
|
+
Match.when({ a: isNullable, b: isNullable }, () => 0),
|
|
642
|
+
Match.when({ a: isNullable }, () => 1),
|
|
643
|
+
Match.when({ b: isNullable }, () => -1),
|
|
644
|
+
Match.when({ a: Match.string, b: Match.string }, ({ a, b }) => a.localeCompare(b)),
|
|
645
|
+
Match.when({ a: Match.number, b: Match.number }, ({ a, b }) => a - b),
|
|
646
|
+
Match.when({ a: Match.boolean, b: Match.boolean }, ({ a, b }) => (a === b ? 0 : a ? 1 : -1)),
|
|
647
|
+
Match.when({ a: Match.defined, b: Match.defined }, ({ a, b }) => String(a).localeCompare(String(b))),
|
|
648
|
+
// TODO(wittjosiah): Why does Match.exhaustive fail here?
|
|
649
|
+
Match.orElse(() => 0),
|
|
650
|
+
)({ a: aValue, b: bValue });
|
|
651
|
+
}
|
|
652
|
+
|
|
593
653
|
private async _loadDocumentsAfterIndexQuery(indexHits: FindResult[]): Promise<(QueryItem | null)[]> {
|
|
594
654
|
return Promise.all(
|
|
595
655
|
indexHits.map(async (hit): Promise<QueryItem | null> => {
|
|
@@ -4,13 +4,14 @@
|
|
|
4
4
|
|
|
5
5
|
import { describe, expect, test } from 'vitest';
|
|
6
6
|
|
|
7
|
-
import { Filter, Query } from '@dxos/echo';
|
|
7
|
+
import { Filter, Order, Query } from '@dxos/echo';
|
|
8
8
|
import { type QueryAST } from '@dxos/echo-protocol';
|
|
9
9
|
import { SpaceId } from '@dxos/keys';
|
|
10
10
|
|
|
11
|
-
import { QueryPlanner } from './query-planner';
|
|
12
11
|
import { TestSchema } from '../testing';
|
|
13
12
|
|
|
13
|
+
import { QueryPlanner } from './query-planner';
|
|
14
|
+
|
|
14
15
|
describe('QueryPlanner', () => {
|
|
15
16
|
const planner = new QueryPlanner();
|
|
16
17
|
|
|
@@ -38,6 +39,53 @@ describe('QueryPlanner', () => {
|
|
|
38
39
|
"_tag": "FilterDeletedStep",
|
|
39
40
|
"mode": "only-non-deleted",
|
|
40
41
|
},
|
|
42
|
+
{
|
|
43
|
+
"_tag": "OrderStep",
|
|
44
|
+
"order": [
|
|
45
|
+
{
|
|
46
|
+
"kind": "natural",
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
}
|
|
52
|
+
`);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('get all people ordered by name', () => {
|
|
56
|
+
const query = Query.select(Filter.type(TestSchema.Person)).orderBy(Order.property('name', 'asc'));
|
|
57
|
+
|
|
58
|
+
const plan = planner.createPlan(withSpaceIdOptions(query.ast));
|
|
59
|
+
expect(plan).toMatchInlineSnapshot(`
|
|
60
|
+
{
|
|
61
|
+
"steps": [
|
|
62
|
+
{
|
|
63
|
+
"_tag": "SelectStep",
|
|
64
|
+
"selector": {
|
|
65
|
+
"_tag": "TypeSelector",
|
|
66
|
+
"inverted": false,
|
|
67
|
+
"typename": [
|
|
68
|
+
"dxn:type:dxos.org/type/Person:0.1.0",
|
|
69
|
+
],
|
|
70
|
+
},
|
|
71
|
+
"spaces": [
|
|
72
|
+
"B2NJDFNVZIW77OQSXUBNAD7BUMBD3G5PO",
|
|
73
|
+
],
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"_tag": "FilterDeletedStep",
|
|
77
|
+
"mode": "only-non-deleted",
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"_tag": "OrderStep",
|
|
81
|
+
"order": [
|
|
82
|
+
{
|
|
83
|
+
"direction": "asc",
|
|
84
|
+
"kind": "property",
|
|
85
|
+
"property": "name",
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
},
|
|
41
89
|
],
|
|
42
90
|
}
|
|
43
91
|
`);
|
|
@@ -82,6 +130,14 @@ describe('QueryPlanner', () => {
|
|
|
82
130
|
"typename": null,
|
|
83
131
|
},
|
|
84
132
|
},
|
|
133
|
+
{
|
|
134
|
+
"_tag": "OrderStep",
|
|
135
|
+
"order": [
|
|
136
|
+
{
|
|
137
|
+
"kind": "natural",
|
|
138
|
+
},
|
|
139
|
+
],
|
|
140
|
+
},
|
|
85
141
|
],
|
|
86
142
|
}
|
|
87
143
|
`);
|
|
@@ -158,6 +214,14 @@ describe('QueryPlanner', () => {
|
|
|
158
214
|
"_tag": "FilterDeletedStep",
|
|
159
215
|
"mode": "only-non-deleted",
|
|
160
216
|
},
|
|
217
|
+
{
|
|
218
|
+
"_tag": "OrderStep",
|
|
219
|
+
"order": [
|
|
220
|
+
{
|
|
221
|
+
"kind": "natural",
|
|
222
|
+
},
|
|
223
|
+
],
|
|
224
|
+
},
|
|
161
225
|
],
|
|
162
226
|
}
|
|
163
227
|
`);
|
|
@@ -218,6 +282,14 @@ describe('QueryPlanner', () => {
|
|
|
218
282
|
"typename": "dxn:type:dxos.org/type/Task:0.1.0",
|
|
219
283
|
},
|
|
220
284
|
},
|
|
285
|
+
{
|
|
286
|
+
"_tag": "OrderStep",
|
|
287
|
+
"order": [
|
|
288
|
+
{
|
|
289
|
+
"kind": "natural",
|
|
290
|
+
},
|
|
291
|
+
],
|
|
292
|
+
},
|
|
221
293
|
],
|
|
222
294
|
}
|
|
223
295
|
`);
|
|
@@ -316,6 +388,14 @@ describe('QueryPlanner', () => {
|
|
|
316
388
|
"typename": "dxn:type:dxos.org/type/Task:0.1.0",
|
|
317
389
|
},
|
|
318
390
|
},
|
|
391
|
+
{
|
|
392
|
+
"_tag": "OrderStep",
|
|
393
|
+
"order": [
|
|
394
|
+
{
|
|
395
|
+
"kind": "natural",
|
|
396
|
+
},
|
|
397
|
+
],
|
|
398
|
+
},
|
|
319
399
|
],
|
|
320
400
|
}
|
|
321
401
|
`);
|
|
@@ -378,6 +458,14 @@ describe('QueryPlanner', () => {
|
|
|
378
458
|
},
|
|
379
459
|
],
|
|
380
460
|
},
|
|
461
|
+
{
|
|
462
|
+
"_tag": "OrderStep",
|
|
463
|
+
"order": [
|
|
464
|
+
{
|
|
465
|
+
"kind": "natural",
|
|
466
|
+
},
|
|
467
|
+
],
|
|
468
|
+
},
|
|
381
469
|
],
|
|
382
470
|
}
|
|
383
471
|
`);
|
|
@@ -487,6 +575,14 @@ describe('QueryPlanner', () => {
|
|
|
487
575
|
],
|
|
488
576
|
},
|
|
489
577
|
},
|
|
578
|
+
{
|
|
579
|
+
"_tag": "OrderStep",
|
|
580
|
+
"order": [
|
|
581
|
+
{
|
|
582
|
+
"kind": "natural",
|
|
583
|
+
},
|
|
584
|
+
],
|
|
585
|
+
},
|
|
490
586
|
],
|
|
491
587
|
}
|
|
492
588
|
`);
|
|
@@ -543,6 +639,14 @@ describe('QueryPlanner', () => {
|
|
|
543
639
|
"_tag": "FilterDeletedStep",
|
|
544
640
|
"mode": "only-non-deleted",
|
|
545
641
|
},
|
|
642
|
+
{
|
|
643
|
+
"_tag": "OrderStep",
|
|
644
|
+
"order": [
|
|
645
|
+
{
|
|
646
|
+
"kind": "natural",
|
|
647
|
+
},
|
|
648
|
+
],
|
|
649
|
+
},
|
|
546
650
|
],
|
|
547
651
|
}
|
|
548
652
|
`);
|
|
@@ -579,6 +683,14 @@ describe('QueryPlanner', () => {
|
|
|
579
683
|
"typename": "dxn:type:dxos.org/type/Person:0.1.0",
|
|
580
684
|
},
|
|
581
685
|
},
|
|
686
|
+
{
|
|
687
|
+
"_tag": "OrderStep",
|
|
688
|
+
"order": [
|
|
689
|
+
{
|
|
690
|
+
"kind": "natural",
|
|
691
|
+
},
|
|
692
|
+
],
|
|
693
|
+
},
|
|
582
694
|
],
|
|
583
695
|
}
|
|
584
696
|
`);
|
|
@@ -606,6 +718,14 @@ describe('QueryPlanner', () => {
|
|
|
606
718
|
"_tag": "FilterDeletedStep",
|
|
607
719
|
"mode": "only-non-deleted",
|
|
608
720
|
},
|
|
721
|
+
{
|
|
722
|
+
"_tag": "OrderStep",
|
|
723
|
+
"order": [
|
|
724
|
+
{
|
|
725
|
+
"kind": "natural",
|
|
726
|
+
},
|
|
727
|
+
],
|
|
728
|
+
},
|
|
609
729
|
],
|
|
610
730
|
}
|
|
611
731
|
`);
|
|
@@ -636,6 +756,14 @@ describe('QueryPlanner', () => {
|
|
|
636
756
|
"_tag": "FilterDeletedStep",
|
|
637
757
|
"mode": "only-non-deleted",
|
|
638
758
|
},
|
|
759
|
+
{
|
|
760
|
+
"_tag": "OrderStep",
|
|
761
|
+
"order": [
|
|
762
|
+
{
|
|
763
|
+
"kind": "natural",
|
|
764
|
+
},
|
|
765
|
+
],
|
|
766
|
+
},
|
|
639
767
|
],
|
|
640
768
|
}
|
|
641
769
|
`);
|
|
@@ -676,6 +804,14 @@ describe('QueryPlanner', () => {
|
|
|
676
804
|
"_tag": "FilterDeletedStep",
|
|
677
805
|
"mode": "only-non-deleted",
|
|
678
806
|
},
|
|
807
|
+
{
|
|
808
|
+
"_tag": "OrderStep",
|
|
809
|
+
"order": [
|
|
810
|
+
{
|
|
811
|
+
"kind": "natural",
|
|
812
|
+
},
|
|
813
|
+
],
|
|
814
|
+
},
|
|
679
815
|
],
|
|
680
816
|
}
|
|
681
817
|
`);
|
|
@@ -705,6 +841,14 @@ describe('QueryPlanner', () => {
|
|
|
705
841
|
"_tag": "FilterDeletedStep",
|
|
706
842
|
"mode": "only-deleted",
|
|
707
843
|
},
|
|
844
|
+
{
|
|
845
|
+
"_tag": "OrderStep",
|
|
846
|
+
"order": [
|
|
847
|
+
{
|
|
848
|
+
"kind": "natural",
|
|
849
|
+
},
|
|
850
|
+
],
|
|
851
|
+
},
|
|
708
852
|
],
|
|
709
853
|
}
|
|
710
854
|
`);
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import { Order } from '@dxos/echo';
|
|
5
6
|
import { type QueryAST } from '@dxos/echo-protocol';
|
|
6
7
|
import { invariant } from '@dxos/invariant';
|
|
7
8
|
import type { DXN, SpaceId } from '@dxos/keys';
|
|
@@ -35,6 +36,7 @@ export class QueryPlanner {
|
|
|
35
36
|
let plan = this._generate(query, { ...DEFAULT_CONTEXT, originalQuery: query });
|
|
36
37
|
plan = this._optimizeEmptyFilters(plan);
|
|
37
38
|
plan = this._optimizeSoloUnions(plan);
|
|
39
|
+
plan = this._ensureOrderStep(plan);
|
|
38
40
|
return plan;
|
|
39
41
|
}
|
|
40
42
|
|
|
@@ -58,8 +60,11 @@ export class QueryPlanner {
|
|
|
58
60
|
return this._generateUnionClause(query, context);
|
|
59
61
|
case 'set-difference':
|
|
60
62
|
return this._generateSetDifferenceClause(query, context);
|
|
63
|
+
case 'order':
|
|
64
|
+
return this._generateOrderClause(query, context);
|
|
61
65
|
default:
|
|
62
|
-
throw new QueryError(
|
|
66
|
+
throw new QueryError({
|
|
67
|
+
message: `Unsupported query type: ${(query as any).type}`,
|
|
63
68
|
context: { query: context.originalQuery },
|
|
64
69
|
});
|
|
65
70
|
}
|
|
@@ -86,6 +91,7 @@ export class QueryPlanner {
|
|
|
86
91
|
// TODO(dmaretskyi): If the tip of the query ast is a [select, ...filter] shape we can reorder the filters so the query is most efficient.
|
|
87
92
|
private _generateSelectionFromFilter(filter: QueryAST.Filter, context: GenerationContext): QueryPlan.Plan {
|
|
88
93
|
switch (filter.type) {
|
|
94
|
+
// Props
|
|
89
95
|
case 'object': {
|
|
90
96
|
if (
|
|
91
97
|
context.selectionInverted &&
|
|
@@ -102,7 +108,10 @@ export class QueryPlanner {
|
|
|
102
108
|
]);
|
|
103
109
|
}
|
|
104
110
|
if (context.selectionInverted) {
|
|
105
|
-
throw new QueryError(
|
|
111
|
+
throw new QueryError({
|
|
112
|
+
message: 'Query too complex',
|
|
113
|
+
context: { query: context.originalQuery },
|
|
114
|
+
});
|
|
106
115
|
}
|
|
107
116
|
|
|
108
117
|
// Try to utilize indexes during selection, prioritizing selecting by id, then by typename.
|
|
@@ -157,6 +166,26 @@ export class QueryPlanner {
|
|
|
157
166
|
]);
|
|
158
167
|
}
|
|
159
168
|
}
|
|
169
|
+
|
|
170
|
+
// Tag
|
|
171
|
+
case 'tag': {
|
|
172
|
+
return QueryPlan.Plan.make([
|
|
173
|
+
{
|
|
174
|
+
_tag: 'SelectStep',
|
|
175
|
+
spaces: context.selectionSpaces,
|
|
176
|
+
selector: {
|
|
177
|
+
_tag: 'WildcardSelector',
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
...this._generateDeletedHandlingSteps(context),
|
|
181
|
+
{
|
|
182
|
+
_tag: 'FilterStep',
|
|
183
|
+
filter: { ...filter },
|
|
184
|
+
},
|
|
185
|
+
]);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Text
|
|
160
189
|
case 'text-search': {
|
|
161
190
|
return QueryPlan.Plan.make([
|
|
162
191
|
{
|
|
@@ -171,26 +200,31 @@ export class QueryPlanner {
|
|
|
171
200
|
...this._generateDeletedHandlingSteps(context),
|
|
172
201
|
]);
|
|
173
202
|
}
|
|
203
|
+
|
|
204
|
+
// Compare
|
|
174
205
|
case 'compare':
|
|
175
|
-
throw new QueryError('Query too complex',
|
|
206
|
+
throw new QueryError({ message: 'Query too complex', context: { query: context.originalQuery } });
|
|
176
207
|
case 'in':
|
|
177
|
-
throw new QueryError('Query too complex',
|
|
208
|
+
throw new QueryError({ message: 'Query too complex', context: { query: context.originalQuery } });
|
|
178
209
|
case 'range':
|
|
179
|
-
throw new QueryError('Query too complex',
|
|
210
|
+
throw new QueryError({ message: 'Query too complex', context: { query: context.originalQuery } });
|
|
211
|
+
|
|
212
|
+
// Boolean
|
|
180
213
|
case 'not':
|
|
181
214
|
return this._generateSelectionFromFilter(filter.filter, {
|
|
182
215
|
...context,
|
|
183
216
|
selectionInverted: !context.selectionInverted,
|
|
184
217
|
});
|
|
185
218
|
case 'and':
|
|
186
|
-
throw new QueryError('Query too complex',
|
|
219
|
+
throw new QueryError({ message: 'Query too complex', context: { query: context.originalQuery } });
|
|
187
220
|
case 'or':
|
|
188
221
|
// Optimized case
|
|
189
222
|
if (filter.filters.every(isTrivialTypenameFilter)) {
|
|
190
|
-
const typenames = filter.filters.map((
|
|
191
|
-
invariant(
|
|
192
|
-
return
|
|
223
|
+
const typenames = filter.filters.map((filter) => {
|
|
224
|
+
invariant(filter.type === 'object' && filter.typename !== null);
|
|
225
|
+
return filter.typename;
|
|
193
226
|
});
|
|
227
|
+
|
|
194
228
|
return QueryPlan.Plan.make([
|
|
195
229
|
{
|
|
196
230
|
_tag: 'SelectStep',
|
|
@@ -204,11 +238,12 @@ export class QueryPlanner {
|
|
|
204
238
|
...this._generateDeletedHandlingSteps(context),
|
|
205
239
|
]);
|
|
206
240
|
} else {
|
|
207
|
-
throw new QueryError('Query too complex',
|
|
241
|
+
throw new QueryError({ message: 'Query too complex', context: { query: context.originalQuery } });
|
|
208
242
|
}
|
|
209
243
|
|
|
210
244
|
default:
|
|
211
|
-
throw new QueryError(
|
|
245
|
+
throw new QueryError({
|
|
246
|
+
message: `Unsupported filter type: ${(filter as any).type}`,
|
|
212
247
|
context: { query: context.originalQuery },
|
|
213
248
|
});
|
|
214
249
|
}
|
|
@@ -425,6 +460,41 @@ export class QueryPlanner {
|
|
|
425
460
|
// TODO(dmaretskyi): Implement this.
|
|
426
461
|
return plan;
|
|
427
462
|
}
|
|
463
|
+
|
|
464
|
+
private _generateOrderClause(query: QueryAST.QueryOrderClause, context: GenerationContext): QueryPlan.Plan {
|
|
465
|
+
return QueryPlan.Plan.make([
|
|
466
|
+
...this._generate(query.query, context).steps,
|
|
467
|
+
{
|
|
468
|
+
_tag: 'OrderStep',
|
|
469
|
+
order: query.order,
|
|
470
|
+
},
|
|
471
|
+
]);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// After complete plan is built, inspect it from the end:
|
|
475
|
+
// - Walk backwards until hitting an object set changer.
|
|
476
|
+
// - If an order step is found, skip.
|
|
477
|
+
// - Otherwise append natural order to the end.
|
|
478
|
+
private _ensureOrderStep(plan: QueryPlan.Plan): QueryPlan.Plan {
|
|
479
|
+
const OBJECT_SET_CHANGERS = new Set(['SelectStep', 'TraverseStep', 'UnionStep', 'SetDifferenceStep']);
|
|
480
|
+
for (let i = plan.steps.length - 1; i >= 0; i--) {
|
|
481
|
+
const step = plan.steps[i];
|
|
482
|
+
if (step._tag === 'OrderStep') {
|
|
483
|
+
return plan;
|
|
484
|
+
}
|
|
485
|
+
if (OBJECT_SET_CHANGERS.has(step._tag)) {
|
|
486
|
+
break;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
return QueryPlan.Plan.make([
|
|
491
|
+
...plan.steps,
|
|
492
|
+
{
|
|
493
|
+
_tag: 'OrderStep',
|
|
494
|
+
order: [Order.natural.ast],
|
|
495
|
+
},
|
|
496
|
+
]);
|
|
497
|
+
}
|
|
428
498
|
}
|
|
429
499
|
|
|
430
500
|
/**
|
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { type Trigger, scheduleTask } from '@dxos/async';
|
|
6
6
|
import { Context } from '@dxos/context';
|
|
7
7
|
import { ProtocolError } from '@dxos/protocols';
|
|
8
8
|
import { schema } from '@dxos/protocols/proto';
|
|
9
9
|
import { type Credential } from '@dxos/protocols/proto/dxos/halo/credentials';
|
|
10
10
|
import {
|
|
11
11
|
type AdmissionDiscoveryService,
|
|
12
|
-
type GetAdmissionCredentialResponse,
|
|
13
12
|
type GetAdmissionCredentialRequest,
|
|
13
|
+
type GetAdmissionCredentialResponse,
|
|
14
14
|
} from '@dxos/protocols/proto/dxos/mesh/teleport';
|
|
15
15
|
import { type ExtensionContext, RpcExtension } from '@dxos/teleport';
|
|
16
16
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Copyright 2022 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { describe, expect,
|
|
5
|
+
import { describe, expect, onTestFinished, test } from 'vitest';
|
|
6
6
|
|
|
7
7
|
import { CredentialGenerator, createCredential } from '@dxos/credentials';
|
|
8
8
|
import { FeedFactory, FeedStore } from '@dxos/feed-store';
|
|
@@ -11,13 +11,14 @@ import { type PublicKey } from '@dxos/keys';
|
|
|
11
11
|
import { log } from '@dxos/log';
|
|
12
12
|
import type { FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
|
|
13
13
|
import { AdmittedFeed } from '@dxos/protocols/proto/dxos/halo/credentials';
|
|
14
|
-
import {
|
|
14
|
+
import { StorageType, createStorage } from '@dxos/random-access-storage';
|
|
15
15
|
import { Timeframe } from '@dxos/timeframe';
|
|
16
16
|
|
|
17
|
-
import { ControlPipeline } from './control-pipeline';
|
|
18
17
|
import { valueEncoding } from '../common';
|
|
19
18
|
import { MetadataStore } from '../metadata';
|
|
20
19
|
|
|
20
|
+
import { ControlPipeline } from './control-pipeline';
|
|
21
|
+
|
|
21
22
|
describe('space/control-pipeline', () => {
|
|
22
23
|
test('admits feeds', async () => {
|
|
23
24
|
const keyring = new Keyring();
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
// Copyright 2022 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { DeferredTask, sleepWithContext, trackLeaks } from '@dxos/async';
|
|
5
|
+
import { DeferredTask, scheduleMicroTask, sleepWithContext, trackLeaks } from '@dxos/async';
|
|
6
6
|
import { Context } from '@dxos/context';
|
|
7
7
|
import {
|
|
8
|
-
SpaceStateMachine,
|
|
9
|
-
type SpaceState,
|
|
10
|
-
type MemberInfo,
|
|
11
|
-
type FeedInfo,
|
|
12
8
|
type DelegateInvitationCredential,
|
|
9
|
+
type FeedInfo,
|
|
10
|
+
type MemberInfo,
|
|
11
|
+
type SpaceState,
|
|
12
|
+
SpaceStateMachine,
|
|
13
13
|
} from '@dxos/credentials';
|
|
14
14
|
import { type FeedWrapper } from '@dxos/feed-store';
|
|
15
15
|
import { PublicKey } from '@dxos/keys';
|
|
@@ -83,9 +83,12 @@ export class ControlPipeline {
|
|
|
83
83
|
|
|
84
84
|
// TODO(burdon): Check not stopping.
|
|
85
85
|
if (info.assertion.designation === AdmittedFeed.Designation.CONTROL && !info.key.equals(genesisFeed.key)) {
|
|
86
|
-
|
|
86
|
+
scheduleMicroTask(this._ctx, async () => {
|
|
87
87
|
try {
|
|
88
88
|
const feed = await feedProvider(info.key);
|
|
89
|
+
if (this._ctx.disposed) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
89
92
|
if (!this._pipeline.hasFeed(feed.key)) {
|
|
90
93
|
await this._pipeline.addFeed(feed);
|
|
91
94
|
}
|