@dxos/echo-pipeline 0.8.3 → 0.8.4-main.1da679c
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-TQJTKNMS.mjs → chunk-KQYT6ADL.mjs} +109 -3
- package/dist/lib/browser/chunk-KQYT6ADL.mjs.map +7 -0
- package/dist/lib/browser/{chunk-35I6ERLG.mjs → chunk-XGG76KKU.mjs} +513 -350
- 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 +1371 -601
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +119 -56
- package/dist/lib/browser/testing/index.mjs.map +3 -3
- package/dist/lib/node-esm/{chunk-5BHLPT24.mjs → chunk-CHMJJ4DG.mjs} +513 -350
- package/dist/lib/node-esm/chunk-CHMJJ4DG.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-RVK35BS7.mjs → chunk-W4ACY3YC.mjs} +109 -3
- package/dist/lib/node-esm/chunk-W4ACY3YC.mjs.map +7 -0
- package/dist/lib/node-esm/filter/index.mjs +3 -1
- package/dist/lib/node-esm/index.mjs +1371 -601
- 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 +119 -56
- 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 +42 -38
- package/src/automerge/automerge-host.test.ts +18 -8
- package/src/automerge/automerge-host.ts +251 -65
- 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 +40 -7
- 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 +13 -14
- 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 +23 -3
- package/src/filter/filter-match.ts +148 -3
- 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 +66 -11
- package/src/query/query-planner.test.ts +146 -2
- package/src/query/query-planner.ts +52 -8
- 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-35I6ERLG.mjs.map +0 -7
- package/dist/lib/browser/chunk-TQJTKNMS.mjs.map +0 -7
- package/dist/lib/node/chunk-HOPOFWAL.cjs +0 -147
- package/dist/lib/node/chunk-HOPOFWAL.cjs.map +0 -7
- package/dist/lib/node/chunk-JXX6LF5U.cjs +0 -2084
- package/dist/lib/node/chunk-JXX6LF5U.cjs.map +0 -7
- package/dist/lib/node/chunk-Q7SFCCGT.cjs +0 -33
- package/dist/lib/node/chunk-Q7SFCCGT.cjs.map +0 -7
- package/dist/lib/node/filter/index.cjs +0 -32
- package/dist/lib/node/filter/index.cjs.map +0 -7
- package/dist/lib/node/index.cjs +0 -4699
- package/dist/lib/node/index.cjs.map +0 -7
- package/dist/lib/node/meta.json +0 -1
- package/dist/lib/node/testing/index.cjs +0 -753
- package/dist/lib/node/testing/index.cjs.map +0 -7
- package/dist/lib/node-esm/chunk-5BHLPT24.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-RVK35BS7.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
|
}
|
|
@@ -382,10 +387,6 @@ export class QueryExecutor extends Resource {
|
|
|
382
387
|
step: QueryPlan.FilterDeletedStep,
|
|
383
388
|
workingSet: QueryItem[],
|
|
384
389
|
): Promise<StepExecutionResult> {
|
|
385
|
-
if (workingSet.length === 6) {
|
|
386
|
-
log.info('FilterDeletedStep', { step, workingSet });
|
|
387
|
-
}
|
|
388
|
-
|
|
389
390
|
const expected = step.mode === 'only-deleted';
|
|
390
391
|
const result = workingSet.filter((item) => ObjectStructure.isDeleted(item.doc) === expected);
|
|
391
392
|
return {
|
|
@@ -428,7 +429,7 @@ export class QueryExecutor extends Resource {
|
|
|
428
429
|
}
|
|
429
430
|
: null;
|
|
430
431
|
} catch {
|
|
431
|
-
log.warn('
|
|
432
|
+
log.warn('invalid reference', { ref: ref['/'] });
|
|
432
433
|
return null;
|
|
433
434
|
}
|
|
434
435
|
});
|
|
@@ -491,7 +492,7 @@ export class QueryExecutor extends Resource {
|
|
|
491
492
|
spaceId: item.spaceId,
|
|
492
493
|
};
|
|
493
494
|
} catch {
|
|
494
|
-
log.warn('
|
|
495
|
+
log.warn('invalid reference', { ref: ref['/'] });
|
|
495
496
|
return null;
|
|
496
497
|
}
|
|
497
498
|
})
|
|
@@ -590,6 +591,60 @@ export class QueryExecutor extends Resource {
|
|
|
590
591
|
};
|
|
591
592
|
}
|
|
592
593
|
|
|
594
|
+
private async _execOrderStep(step: QueryPlan.OrderStep, workingSet: QueryItem[]): Promise<StepExecutionResult> {
|
|
595
|
+
const compareItems = (a: QueryItem, b: QueryItem): number =>
|
|
596
|
+
step.order.reduce((comparison, order) => {
|
|
597
|
+
// If we already have a definitive result, return it
|
|
598
|
+
if (comparison !== 0) {
|
|
599
|
+
return comparison;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
return this._compareByOrder(a, b, order);
|
|
603
|
+
}, 0);
|
|
604
|
+
|
|
605
|
+
const sortedWorkingSet = [...workingSet].sort(compareItems);
|
|
606
|
+
|
|
607
|
+
return {
|
|
608
|
+
workingSet: sortedWorkingSet,
|
|
609
|
+
trace: {
|
|
610
|
+
...ExecutionTrace.makeEmpty(),
|
|
611
|
+
name: 'Order',
|
|
612
|
+
details: JSON.stringify(step.order),
|
|
613
|
+
objectCount: sortedWorkingSet.length,
|
|
614
|
+
},
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
private _compareByOrder(a: QueryItem, b: QueryItem, order: QueryAST.Order): number {
|
|
619
|
+
return Match.type<QueryAST.Order>().pipe(
|
|
620
|
+
Match.withReturnType<number>(),
|
|
621
|
+
Match.when({ kind: 'natural' }, () => a.objectId.localeCompare(b.objectId)),
|
|
622
|
+
Match.when({ kind: 'property' }, ({ property, direction }) => {
|
|
623
|
+
const comparison = this._compareByProperty(a, b, property);
|
|
624
|
+
return direction === 'desc' ? -comparison : comparison;
|
|
625
|
+
}),
|
|
626
|
+
Match.exhaustive,
|
|
627
|
+
)(order);
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
private _compareByProperty(a: QueryItem, b: QueryItem, property: string): number {
|
|
631
|
+
const aValue = a.doc.data[property];
|
|
632
|
+
const bValue = b.doc.data[property];
|
|
633
|
+
|
|
634
|
+
return Match.type<{ a: unknown; b: unknown }>().pipe(
|
|
635
|
+
Match.withReturnType<number>(),
|
|
636
|
+
Match.when({ a: isNullable, b: isNullable }, () => 0),
|
|
637
|
+
Match.when({ a: isNullable }, () => 1),
|
|
638
|
+
Match.when({ b: isNullable }, () => -1),
|
|
639
|
+
Match.when({ a: Match.string, b: Match.string }, ({ a, b }) => a.localeCompare(b)),
|
|
640
|
+
Match.when({ a: Match.number, b: Match.number }, ({ a, b }) => a - b),
|
|
641
|
+
Match.when({ a: Match.boolean, b: Match.boolean }, ({ a, b }) => (a === b ? 0 : a ? 1 : -1)),
|
|
642
|
+
Match.when({ a: Match.defined, b: Match.defined }, ({ a, b }) => String(a).localeCompare(String(b))),
|
|
643
|
+
// TODO(wittjosiah): Why does Match.exhaustive fail here?
|
|
644
|
+
Match.orElse(() => 0),
|
|
645
|
+
)({ a: aValue, b: bValue });
|
|
646
|
+
}
|
|
647
|
+
|
|
593
648
|
private async _loadDocumentsAfterIndexQuery(indexHits: FindResult[]): Promise<(QueryItem | null)[]> {
|
|
594
649
|
return Promise.all(
|
|
595
650
|
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
|
}
|
|
@@ -102,7 +107,10 @@ export class QueryPlanner {
|
|
|
102
107
|
]);
|
|
103
108
|
}
|
|
104
109
|
if (context.selectionInverted) {
|
|
105
|
-
throw new QueryError(
|
|
110
|
+
throw new QueryError({
|
|
111
|
+
message: 'Query too complex',
|
|
112
|
+
context: { query: context.originalQuery },
|
|
113
|
+
});
|
|
106
114
|
}
|
|
107
115
|
|
|
108
116
|
// Try to utilize indexes during selection, prioritizing selecting by id, then by typename.
|
|
@@ -172,18 +180,18 @@ export class QueryPlanner {
|
|
|
172
180
|
]);
|
|
173
181
|
}
|
|
174
182
|
case 'compare':
|
|
175
|
-
throw new QueryError('Query too complex',
|
|
183
|
+
throw new QueryError({ message: 'Query too complex', context: { query: context.originalQuery } });
|
|
176
184
|
case 'in':
|
|
177
|
-
throw new QueryError('Query too complex',
|
|
185
|
+
throw new QueryError({ message: 'Query too complex', context: { query: context.originalQuery } });
|
|
178
186
|
case 'range':
|
|
179
|
-
throw new QueryError('Query too complex',
|
|
187
|
+
throw new QueryError({ message: 'Query too complex', context: { query: context.originalQuery } });
|
|
180
188
|
case 'not':
|
|
181
189
|
return this._generateSelectionFromFilter(filter.filter, {
|
|
182
190
|
...context,
|
|
183
191
|
selectionInverted: !context.selectionInverted,
|
|
184
192
|
});
|
|
185
193
|
case 'and':
|
|
186
|
-
throw new QueryError('Query too complex',
|
|
194
|
+
throw new QueryError({ message: 'Query too complex', context: { query: context.originalQuery } });
|
|
187
195
|
case 'or':
|
|
188
196
|
// Optimized case
|
|
189
197
|
if (filter.filters.every(isTrivialTypenameFilter)) {
|
|
@@ -204,11 +212,12 @@ export class QueryPlanner {
|
|
|
204
212
|
...this._generateDeletedHandlingSteps(context),
|
|
205
213
|
]);
|
|
206
214
|
} else {
|
|
207
|
-
throw new QueryError('Query too complex',
|
|
215
|
+
throw new QueryError({ message: 'Query too complex', context: { query: context.originalQuery } });
|
|
208
216
|
}
|
|
209
217
|
|
|
210
218
|
default:
|
|
211
|
-
throw new QueryError(
|
|
219
|
+
throw new QueryError({
|
|
220
|
+
message: `Unsupported filter type: ${(filter as any).type}`,
|
|
212
221
|
context: { query: context.originalQuery },
|
|
213
222
|
});
|
|
214
223
|
}
|
|
@@ -425,6 +434,41 @@ export class QueryPlanner {
|
|
|
425
434
|
// TODO(dmaretskyi): Implement this.
|
|
426
435
|
return plan;
|
|
427
436
|
}
|
|
437
|
+
|
|
438
|
+
private _generateOrderClause(query: QueryAST.QueryOrderClause, context: GenerationContext): QueryPlan.Plan {
|
|
439
|
+
return QueryPlan.Plan.make([
|
|
440
|
+
...this._generate(query.query, context).steps,
|
|
441
|
+
{
|
|
442
|
+
_tag: 'OrderStep',
|
|
443
|
+
order: query.order,
|
|
444
|
+
},
|
|
445
|
+
]);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// After complete plan is built, inspect it from the end:
|
|
449
|
+
// - Walk backwards until hitting an object set changer.
|
|
450
|
+
// - If an order step is found, skip.
|
|
451
|
+
// - Otherwise append natural order to the end.
|
|
452
|
+
private _ensureOrderStep(plan: QueryPlan.Plan): QueryPlan.Plan {
|
|
453
|
+
const OBJECT_SET_CHANGERS = new Set(['SelectStep', 'TraverseStep', 'UnionStep', 'SetDifferenceStep']);
|
|
454
|
+
for (let i = plan.steps.length - 1; i >= 0; i--) {
|
|
455
|
+
const step = plan.steps[i];
|
|
456
|
+
if (step._tag === 'OrderStep') {
|
|
457
|
+
return plan;
|
|
458
|
+
}
|
|
459
|
+
if (OBJECT_SET_CHANGERS.has(step._tag)) {
|
|
460
|
+
break;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
return QueryPlan.Plan.make([
|
|
465
|
+
...plan.steps,
|
|
466
|
+
{
|
|
467
|
+
_tag: 'OrderStep',
|
|
468
|
+
order: [Order.natural.ast],
|
|
469
|
+
},
|
|
470
|
+
]);
|
|
471
|
+
}
|
|
428
472
|
}
|
|
429
473
|
|
|
430
474
|
/**
|
|
@@ -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
|
}
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
import { type AutomergeUrl, parseAutomergeUrl } from '@automerge/automerge-repo';
|
|
6
6
|
|
|
7
|
-
import { synchronized, trackLeaks
|
|
8
|
-
import {
|
|
7
|
+
import { Trigger, synchronized, trackLeaks } from '@dxos/async';
|
|
8
|
+
import { type DelegateInvitationCredential, type MemberInfo, getCredentialAssertion } from '@dxos/credentials';
|
|
9
9
|
import { failUndefined } from '@dxos/debug';
|
|
10
10
|
import { type FeedStore } from '@dxos/feed-store';
|
|
11
11
|
import { PublicKey } from '@dxos/keys';
|
|
@@ -19,11 +19,12 @@ import { type Teleport } from '@dxos/teleport';
|
|
|
19
19
|
import { type BlobStore } from '@dxos/teleport-extension-object-sync';
|
|
20
20
|
import { ComplexMap } from '@dxos/util';
|
|
21
21
|
|
|
22
|
+
import { createIdFromSpaceKey } from '../common/space-id';
|
|
23
|
+
import { type MetadataStore } from '../metadata';
|
|
24
|
+
|
|
22
25
|
import { CredentialRetrieverExtension } from './admission-discovery-extension';
|
|
23
26
|
import { Space } from './space';
|
|
24
27
|
import { SpaceProtocol, type SwarmIdentity } from './space-protocol';
|
|
25
|
-
import { createIdFromSpaceKey } from '../common/space-id';
|
|
26
|
-
import { type MetadataStore } from '../metadata';
|
|
27
28
|
|
|
28
29
|
export type SpaceManagerParams = {
|
|
29
30
|
feedStore: FeedStore<FeedMessage>;
|
|
@@ -2,14 +2,14 @@
|
|
|
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 { Keyring } from '@dxos/keyring';
|
|
8
8
|
import { PublicKey } from '@dxos/keys';
|
|
9
9
|
import { createStorage } from '@dxos/random-access-storage';
|
|
10
10
|
import { Timeframe } from '@dxos/timeframe';
|
|
11
11
|
|
|
12
|
-
import {
|
|
12
|
+
import { TestAgentBuilder, TestFeedBuilder, WebsocketNetworkManagerProvider } from '../testing';
|
|
13
13
|
|
|
14
14
|
// TODO(burdon): Config.
|
|
15
15
|
// Signal server will be started by the setup script.
|
|
@@ -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 { PublicKey } from '@dxos/keys';
|
|
8
8
|
import { MemorySignalManager, MemorySignalManagerContext } from '@dxos/messaging';
|
|
@@ -11,9 +11,10 @@ import { StorageType, createStorage } from '@dxos/random-access-storage';
|
|
|
11
11
|
import { BlobStore } from '@dxos/teleport-extension-object-sync';
|
|
12
12
|
import { Timeframe } from '@dxos/timeframe';
|
|
13
13
|
|
|
14
|
-
import { AuthStatus, MOCK_AUTH_PROVIDER, MOCK_AUTH_VERIFIER, SpaceProtocol } from './space-protocol';
|
|
15
14
|
import { TestAgentBuilder, TestFeedBuilder } from '../testing';
|
|
16
15
|
|
|
16
|
+
import { AuthStatus, MOCK_AUTH_PROVIDER, MOCK_AUTH_VERIFIER, SpaceProtocol } from './space-protocol';
|
|
17
|
+
|
|
17
18
|
describe('space/space-protocol', () => {
|
|
18
19
|
// Flaky.
|
|
19
20
|
test('two peers discover each other via presence', async () => {
|
|
@@ -9,8 +9,8 @@ import { PublicKey } from '@dxos/keys';
|
|
|
9
9
|
import { log, logInfo } from '@dxos/log';
|
|
10
10
|
import {
|
|
11
11
|
MMSTTopology,
|
|
12
|
-
type SwarmNetworkManager,
|
|
13
12
|
type SwarmConnection,
|
|
13
|
+
type SwarmNetworkManager,
|
|
14
14
|
type WireProtocol,
|
|
15
15
|
type WireProtocolParams,
|
|
16
16
|
type WireProtocolProvider,
|
|
@@ -20,7 +20,7 @@ import { type MuxerStats, Teleport } from '@dxos/teleport';
|
|
|
20
20
|
import { type BlobStore, BlobSync } from '@dxos/teleport-extension-object-sync';
|
|
21
21
|
import { ReplicatorExtension } from '@dxos/teleport-extension-replicator';
|
|
22
22
|
import { trace } from '@dxos/tracing';
|
|
23
|
-
import { CallbackCollection, ComplexMap
|
|
23
|
+
import { type AsyncCallback, CallbackCollection, ComplexMap } from '@dxos/util';
|
|
24
24
|
|
|
25
25
|
import { AuthExtension, type AuthProvider, type AuthVerifier } from './auth';
|
|
26
26
|
|
|
@@ -118,7 +118,10 @@ export class SpaceProtocol {
|
|
|
118
118
|
this.blobSync = new BlobSync({ blobStore });
|
|
119
119
|
|
|
120
120
|
// TODO(burdon): Async race condition? Move to start?
|
|
121
|
-
this._topic = subtleCrypto
|
|
121
|
+
this._topic = subtleCrypto
|
|
122
|
+
.digest('SHA-256', topic.asBuffer() as ArrayBufferView<ArrayBuffer>)
|
|
123
|
+
.then(discoveryKey)
|
|
124
|
+
.then(PublicKey.from);
|
|
122
125
|
|
|
123
126
|
this._disableP2pReplication = disableP2pReplication ?? false;
|
|
124
127
|
}
|