@dxos/echo-pipeline 0.8.3 → 0.8.4-main.03d5cd7b56
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-35I6ERLG.mjs → neutral/chunk-VIHYDCCC.mjs} +313 -709
- package/dist/lib/neutral/chunk-VIHYDCCC.mjs.map +7 -0
- package/dist/lib/neutral/chunk-YBC6ONBZ.mjs +247 -0
- package/dist/lib/neutral/chunk-YBC6ONBZ.mjs.map +7 -0
- package/dist/lib/{browser → neutral}/filter/index.mjs +3 -2
- package/dist/lib/neutral/index.mjs +7065 -0
- package/dist/lib/neutral/index.mjs.map +7 -0
- package/dist/lib/neutral/meta.json +1 -0
- package/dist/lib/{browser → neutral}/testing/index.mjs +90 -311
- package/dist/lib/neutral/testing/index.mjs.map +7 -0
- package/dist/types/src/automerge/automerge-host-subduction.test.d.ts +2 -0
- package/dist/types/src/automerge/automerge-host-subduction.test.d.ts.map +1 -0
- package/dist/types/src/automerge/automerge-host.d.ts +99 -54
- package/dist/types/src/automerge/automerge-host.d.ts.map +1 -1
- package/dist/types/src/automerge/automerge-repo-subduction.test.d.ts +2 -0
- package/dist/types/src/automerge/automerge-repo-subduction.test.d.ts.map +1 -0
- package/dist/types/src/automerge/automerge-repo.test.d.ts +1 -1
- package/dist/types/src/automerge/automerge-repo.test.d.ts.map +1 -1
- package/dist/types/src/automerge/automerge-subduction.test.d.ts +2 -0
- package/dist/types/src/automerge/automerge-subduction.test.d.ts.map +1 -0
- package/dist/types/src/automerge/collection-synchronizer.d.ts +25 -4
- package/dist/types/src/automerge/collection-synchronizer.d.ts.map +1 -1
- package/dist/types/src/automerge/echo-data-monitor.d.ts.map +1 -1
- package/dist/types/src/automerge/echo-network-adapter.d.ts +23 -10
- package/dist/types/src/automerge/echo-network-adapter.d.ts.map +1 -1
- package/dist/types/src/automerge/echo-replicator.d.ts +38 -15
- package/dist/types/src/automerge/echo-replicator.d.ts.map +1 -1
- package/dist/types/src/automerge/handle-state.d.ts +52 -0
- package/dist/types/src/automerge/handle-state.d.ts.map +1 -0
- package/dist/types/src/automerge/heads-store.d.ts +9 -2
- package/dist/types/src/automerge/heads-store.d.ts.map +1 -1
- package/dist/types/src/automerge/index.d.ts +2 -1
- package/dist/types/src/automerge/index.d.ts.map +1 -1
- package/dist/types/src/automerge/leveldb-storage-adapter.d.ts +9 -5
- package/dist/types/src/automerge/leveldb-storage-adapter.d.ts.map +1 -1
- package/dist/types/src/automerge/mesh-echo-replicator-connection.d.ts +9 -8
- package/dist/types/src/automerge/mesh-echo-replicator-connection.d.ts.map +1 -1
- package/dist/types/src/automerge/mesh-echo-replicator.d.ts +6 -5
- package/dist/types/src/automerge/mesh-echo-replicator.d.ts.map +1 -1
- package/dist/types/src/automerge/network-protocol.d.ts.map +1 -1
- package/dist/types/src/automerge/space-collection.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/common/feeds.d.ts.map +1 -1
- package/dist/types/src/common/space-id.d.ts.map +1 -1
- package/dist/types/src/db-host/automerge-data-source.d.ts +35 -0
- package/dist/types/src/db-host/automerge-data-source.d.ts.map +1 -0
- package/dist/types/src/db-host/automerge-data-source.test.d.ts +2 -0
- package/dist/types/src/db-host/automerge-data-source.test.d.ts.map +1 -0
- package/dist/types/src/db-host/automerge-metrics.d.ts.map +1 -1
- package/dist/types/src/db-host/data-service.d.ts +8 -7
- package/dist/types/src/db-host/data-service.d.ts.map +1 -1
- package/dist/types/src/db-host/database-root.d.ts +9 -3
- package/dist/types/src/db-host/database-root.d.ts.map +1 -1
- package/dist/types/src/db-host/documents-synchronizer.d.ts +9 -8
- package/dist/types/src/db-host/documents-synchronizer.d.ts.map +1 -1
- package/dist/types/src/db-host/echo-host.d.ts +62 -27
- package/dist/types/src/db-host/echo-host.d.ts.map +1 -1
- package/dist/types/src/db-host/index.d.ts +2 -0
- package/dist/types/src/db-host/index.d.ts.map +1 -1
- package/dist/types/src/db-host/invalidation-hint.d.ts +19 -0
- package/dist/types/src/db-host/invalidation-hint.d.ts.map +1 -0
- package/dist/types/src/db-host/local-queue-service.d.ts +19 -0
- package/dist/types/src/db-host/local-queue-service.d.ts.map +1 -0
- package/dist/types/src/db-host/query-invalidation.test.d.ts +2 -0
- package/dist/types/src/db-host/query-invalidation.test.d.ts.map +1 -0
- package/dist/types/src/db-host/query-service.d.ts +19 -11
- package/dist/types/src/db-host/query-service.d.ts.map +1 -1
- package/dist/types/src/db-host/queue-data-source.d.ts +38 -0
- package/dist/types/src/db-host/queue-data-source.d.ts.map +1 -0
- package/dist/types/src/db-host/queue-service.test.d.ts +2 -0
- package/dist/types/src/db-host/queue-service.test.d.ts.map +1 -0
- package/dist/types/src/db-host/space-state-manager.d.ts +4 -3
- package/dist/types/src/db-host/space-state-manager.d.ts.map +1 -1
- package/dist/types/src/db-host/stub.d.ts +11 -0
- package/dist/types/src/db-host/stub.d.ts.map +1 -0
- package/dist/types/src/edge/echo-edge-replicator.d.ts +10 -7
- package/dist/types/src/edge/echo-edge-replicator.d.ts.map +1 -1
- package/dist/types/src/edge/echo-edge-subduction-replicator.d.ts +39 -0
- package/dist/types/src/edge/echo-edge-subduction-replicator.d.ts.map +1 -0
- package/dist/types/src/edge/echo-edge-subduction-replicator.test.d.ts +2 -0
- package/dist/types/src/edge/echo-edge-subduction-replicator.test.d.ts.map +1 -0
- package/dist/types/src/edge/index.d.ts +1 -0
- package/dist/types/src/edge/index.d.ts.map +1 -1
- package/dist/types/src/edge/inflight-request-limiter.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/message-selector.d.ts.map +1 -1
- package/dist/types/src/pipeline/pipeline.d.ts +3 -3
- package/dist/types/src/pipeline/pipeline.d.ts.map +1 -1
- package/dist/types/src/pipeline/timeframe-clock.d.ts.map +1 -1
- package/dist/types/src/query/errors.d.ts +22 -10
- package/dist/types/src/query/errors.d.ts.map +1 -1
- package/dist/types/src/query/plan.d.ts +66 -8
- package/dist/types/src/query/plan.d.ts.map +1 -1
- package/dist/types/src/query/query-executor.d.ts +66 -11
- package/dist/types/src/query/query-executor.d.ts.map +1 -1
- package/dist/types/src/query/query-planner.d.ts +12 -1
- 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/auth.d.ts +3 -3
- package/dist/types/src/space/auth.d.ts.map +1 -1
- package/dist/types/src/space/control-pipeline.d.ts +5 -4
- package/dist/types/src/space/control-pipeline.d.ts.map +1 -1
- package/dist/types/src/space/space-manager.d.ts +8 -8
- package/dist/types/src/space/space-manager.d.ts.map +1 -1
- package/dist/types/src/space/space-protocol.d.ts +10 -9
- package/dist/types/src/space/space-protocol.d.ts.map +1 -1
- package/dist/types/src/space/space.d.ts +6 -6
- package/dist/types/src/space/space.d.ts.map +1 -1
- package/dist/types/src/testing/change-metadata.d.ts.map +1 -1
- package/dist/types/src/testing/index.d.ts +0 -2
- package/dist/types/src/testing/index.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-feed-builder.d.ts.map +1 -1
- package/dist/types/src/testing/test-network-adapter.d.ts +1 -1
- package/dist/types/src/testing/test-network-adapter.d.ts.map +1 -1
- package/dist/types/src/testing/test-replicator.d.ts +14 -13
- 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 +56 -54
- package/src/automerge/automerge-host-subduction.test.ts +247 -0
- package/src/automerge/automerge-host.test.ts +65 -39
- package/src/automerge/automerge-host.ts +589 -206
- package/src/automerge/automerge-repo-subduction.test.ts +1049 -0
- package/src/automerge/automerge-repo.test.ts +349 -91
- package/src/automerge/automerge-subduction.test.ts +330 -0
- package/src/automerge/collection-synchronizer.test.ts +53 -17
- package/src/automerge/collection-synchronizer.ts +119 -36
- package/src/automerge/echo-data-monitor.ts +1 -1
- package/src/automerge/echo-network-adapter.test.ts +5 -4
- package/src/automerge/echo-network-adapter.ts +74 -30
- package/src/automerge/echo-replicator.ts +41 -15
- package/src/automerge/handle-state.ts +62 -0
- package/src/automerge/heads-store.ts +42 -3
- package/src/automerge/index.ts +2 -1
- package/src/automerge/leveldb-storage-adapter.ts +40 -11
- package/src/automerge/mesh-echo-replicator-connection.ts +12 -8
- package/src/automerge/mesh-echo-replicator.ts +15 -11
- package/src/automerge/storage-adapter.test.ts +1 -1
- package/src/common/space-id.ts +1 -1
- package/src/db-host/automerge-data-source.test.ts +299 -0
- package/src/db-host/automerge-data-source.ts +187 -0
- package/src/db-host/data-service.ts +25 -26
- package/src/db-host/database-root.ts +20 -8
- package/src/db-host/documents-synchronizer.test.ts +143 -14
- package/src/db-host/documents-synchronizer.ts +72 -55
- package/src/db-host/echo-host.ts +348 -96
- package/src/db-host/index.ts +2 -0
- package/src/db-host/invalidation-hint.ts +50 -0
- package/src/db-host/local-queue-service.ts +127 -0
- package/src/db-host/query-invalidation.test.ts +270 -0
- package/src/db-host/query-service.ts +111 -106
- package/src/db-host/queue-data-source.ts +160 -0
- package/src/db-host/queue-service.test.ts +145 -0
- package/src/db-host/space-state-manager.ts +12 -10
- package/src/db-host/stub.ts +33 -0
- package/src/edge/echo-edge-replicator.test.ts +12 -10
- package/src/edge/echo-edge-replicator.ts +144 -58
- package/src/edge/echo-edge-subduction-replicator.test.ts +158 -0
- package/src/edge/echo-edge-subduction-replicator.ts +377 -0
- package/src/edge/index.ts +1 -0
- package/src/filter/filter-match.test.ts +68 -32
- package/src/filter/filter-match.ts +184 -20
- package/src/metadata/metadata-store.ts +11 -8
- package/src/pipeline/pipeline-stress.test.ts +1 -1
- package/src/pipeline/pipeline.test.ts +2 -2
- package/src/pipeline/pipeline.ts +9 -7
- package/src/query/errors.ts +3 -1
- package/src/query/plan.ts +82 -8
- package/src/query/query-executor.ts +1199 -164
- package/src/query/query-planner.test.ts +993 -81
- package/src/query/query-planner.ts +467 -27
- package/src/space/admission-discovery-extension.ts +3 -3
- package/src/space/auth.ts +6 -6
- package/src/space/control-pipeline.test.ts +5 -4
- package/src/space/control-pipeline.ts +16 -13
- package/src/space/space-manager.browser.test.ts +1 -1
- package/src/space/space-manager.ts +16 -20
- package/src/space/space-protocol.browser.test.ts +15 -14
- package/src/space/space-protocol.test.ts +15 -14
- package/src/space/space-protocol.ts +22 -18
- package/src/space/space.test.ts +3 -3
- package/src/space/space.ts +11 -11
- package/src/testing/index.ts +0 -2
- package/src/testing/test-agent-builder.ts +3 -3
- package/src/testing/test-network-adapter.ts +24 -3
- package/src/testing/test-replicator.ts +19 -20
- package/src/util.ts +7 -3
- package/dist/lib/browser/chunk-35I6ERLG.mjs.map +0 -7
- package/dist/lib/browser/chunk-CGS2ULMK.mjs +0 -11
- package/dist/lib/browser/chunk-CGS2ULMK.mjs.map +0 -7
- package/dist/lib/browser/chunk-TQJTKNMS.mjs +0 -126
- package/dist/lib/browser/chunk-TQJTKNMS.mjs.map +0 -7
- package/dist/lib/browser/index.mjs +0 -4743
- package/dist/lib/browser/index.mjs.map +0 -7
- package/dist/lib/browser/meta.json +0 -1
- package/dist/lib/browser/testing/index.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 +0 -2067
- package/dist/lib/node-esm/chunk-5BHLPT24.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-HSLMI22Q.mjs +0 -11
- package/dist/lib/node-esm/chunk-HSLMI22Q.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-RVK35BS7.mjs +0 -126
- package/dist/lib/node-esm/chunk-RVK35BS7.mjs.map +0 -7
- package/dist/lib/node-esm/filter/index.mjs +0 -11
- package/dist/lib/node-esm/filter/index.mjs.map +0 -7
- package/dist/lib/node-esm/index.mjs +0 -4743
- package/dist/lib/node-esm/index.mjs.map +0 -7
- package/dist/lib/node-esm/meta.json +0 -1
- package/dist/lib/node-esm/testing/index.mjs +0 -742
- package/dist/lib/node-esm/testing/index.mjs.map +0 -7
- package/dist/types/src/db-host/documents-iterator.d.ts +0 -7
- package/dist/types/src/db-host/documents-iterator.d.ts.map +0 -1
- package/dist/types/src/testing/test-data.d.ts +0 -18
- package/dist/types/src/testing/test-data.d.ts.map +0 -1
- package/dist/types/src/testing/test-schema.d.ts +0 -37
- package/dist/types/src/testing/test-schema.d.ts.map +0 -1
- package/src/db-host/documents-iterator.ts +0 -74
- package/src/testing/test-data.ts +0 -127
- package/src/testing/test-schema.ts +0 -53
- /package/dist/lib/{browser → neutral}/filter/index.mjs.map +0 -0
|
@@ -2,13 +2,31 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { Order, Query } from '@dxos/echo';
|
|
6
|
+
import { QueryAST } from '@dxos/echo-protocol';
|
|
6
7
|
import { invariant } from '@dxos/invariant';
|
|
7
|
-
import type { DXN
|
|
8
|
+
import type { DXN } from '@dxos/keys';
|
|
8
9
|
|
|
9
10
|
import { QueryError } from './errors';
|
|
10
11
|
import { QueryPlan } from './plan';
|
|
11
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Creates a QueryError with "Query too complex" message and includes the prettified query in the context.
|
|
15
|
+
*/
|
|
16
|
+
const queryTooComplexError = (query: QueryAST.Query | null): QueryError => {
|
|
17
|
+
if (query === null) {
|
|
18
|
+
return new QueryError({
|
|
19
|
+
message: 'Query too complex',
|
|
20
|
+
context: { query: null },
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
const prettyQuery = Query.pretty(Query.fromAst(query));
|
|
24
|
+
return new QueryError({
|
|
25
|
+
message: `Query too complex: ${prettyQuery}`,
|
|
26
|
+
context: { query, prettyQuery },
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
|
|
12
30
|
export type QueryPlannerOptions = {
|
|
13
31
|
defaultTextSearchKind: QueryPlan.TextSearchKind;
|
|
14
32
|
};
|
|
@@ -32,9 +50,12 @@ export class QueryPlanner {
|
|
|
32
50
|
}
|
|
33
51
|
|
|
34
52
|
createPlan(query: QueryAST.Query): QueryPlan.Plan {
|
|
53
|
+
this._validateQueryScoped(query);
|
|
35
54
|
let plan = this._generate(query, { ...DEFAULT_CONTEXT, originalQuery: query });
|
|
36
55
|
plan = this._optimizeEmptyFilters(plan);
|
|
37
56
|
plan = this._optimizeSoloUnions(plan);
|
|
57
|
+
plan = this._ensureOrderStep(plan);
|
|
58
|
+
plan = this._optimizeLimits(plan);
|
|
38
59
|
return plan;
|
|
39
60
|
}
|
|
40
61
|
|
|
@@ -54,12 +75,21 @@ export class QueryPlanner {
|
|
|
54
75
|
return this._generateRelationTraversalClause(query, context);
|
|
55
76
|
case 'reference-traversal':
|
|
56
77
|
return this._generateReferenceTraversalClause(query, context);
|
|
78
|
+
case 'hierarchy-traversal':
|
|
79
|
+
return this._generateHierarchyTraversalClause(query, context);
|
|
57
80
|
case 'union':
|
|
58
81
|
return this._generateUnionClause(query, context);
|
|
59
82
|
case 'set-difference':
|
|
60
83
|
return this._generateSetDifferenceClause(query, context);
|
|
84
|
+
case 'order':
|
|
85
|
+
return this._generateOrderClause(query, context);
|
|
86
|
+
case 'limit':
|
|
87
|
+
return this._generateLimitClause(query, context);
|
|
88
|
+
case 'from':
|
|
89
|
+
return this._generateFromClause(query, context);
|
|
61
90
|
default:
|
|
62
|
-
throw new QueryError(
|
|
91
|
+
throw new QueryError({
|
|
92
|
+
message: `Unsupported query type: ${(query as any).type}`,
|
|
63
93
|
context: { query: context.originalQuery },
|
|
64
94
|
});
|
|
65
95
|
}
|
|
@@ -69,15 +99,25 @@ export class QueryPlanner {
|
|
|
69
99
|
const newContext = {
|
|
70
100
|
...context,
|
|
71
101
|
};
|
|
72
|
-
if (query.options.spaceIds) {
|
|
73
|
-
newContext.selectionSpaces = query.options.spaceIds as readonly SpaceId[];
|
|
74
|
-
}
|
|
75
102
|
if (query.options.deleted) {
|
|
76
103
|
newContext.deletedHandling = query.options.deleted;
|
|
77
104
|
}
|
|
78
105
|
return this._generate(query.query, newContext);
|
|
79
106
|
}
|
|
80
107
|
|
|
108
|
+
private _generateFromClause(query: QueryAST.QueryFromClause, context: GenerationContext): QueryPlan.Plan {
|
|
109
|
+
if (query.from._tag === 'scope') {
|
|
110
|
+
return this._generate(query.query, { ...context, scope: query.from.scope });
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Subquery from: flatten by rewriting the outer query's leaf select into a filter on the subquery.
|
|
114
|
+
const subquery = query.from.query;
|
|
115
|
+
const rewritten = QueryAST.map(query.query, (node) =>
|
|
116
|
+
node.type === 'select' ? { type: 'filter', selection: subquery, filter: node.filter } : node,
|
|
117
|
+
);
|
|
118
|
+
return this._generate(rewritten, context);
|
|
119
|
+
}
|
|
120
|
+
|
|
81
121
|
private _generateSelectClause(query: QueryAST.QuerySelectClause, context: GenerationContext): QueryPlan.Plan {
|
|
82
122
|
return this._generateSelectionFromFilter(query.filter, context);
|
|
83
123
|
}
|
|
@@ -86,6 +126,7 @@ export class QueryPlanner {
|
|
|
86
126
|
// 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
127
|
private _generateSelectionFromFilter(filter: QueryAST.Filter, context: GenerationContext): QueryPlan.Plan {
|
|
88
128
|
switch (filter.type) {
|
|
129
|
+
// Props
|
|
89
130
|
case 'object': {
|
|
90
131
|
if (
|
|
91
132
|
context.selectionInverted &&
|
|
@@ -102,7 +143,7 @@ export class QueryPlanner {
|
|
|
102
143
|
]);
|
|
103
144
|
}
|
|
104
145
|
if (context.selectionInverted) {
|
|
105
|
-
throw
|
|
146
|
+
throw queryTooComplexError(context.originalQuery);
|
|
106
147
|
}
|
|
107
148
|
|
|
108
149
|
// Try to utilize indexes during selection, prioritizing selecting by id, then by typename.
|
|
@@ -111,7 +152,7 @@ export class QueryPlanner {
|
|
|
111
152
|
return QueryPlan.Plan.make([
|
|
112
153
|
{
|
|
113
154
|
_tag: 'SelectStep',
|
|
114
|
-
|
|
155
|
+
scope: context.scope,
|
|
115
156
|
selector: {
|
|
116
157
|
_tag: 'IdSelector',
|
|
117
158
|
objectIds: filter.id,
|
|
@@ -127,7 +168,7 @@ export class QueryPlanner {
|
|
|
127
168
|
return QueryPlan.Plan.make([
|
|
128
169
|
{
|
|
129
170
|
_tag: 'SelectStep',
|
|
130
|
-
|
|
171
|
+
scope: context.scope,
|
|
131
172
|
selector: {
|
|
132
173
|
_tag: 'TypeSelector',
|
|
133
174
|
typename: [filter.typename as DXN.String],
|
|
@@ -135,16 +176,17 @@ export class QueryPlanner {
|
|
|
135
176
|
},
|
|
136
177
|
},
|
|
137
178
|
...this._generateDeletedHandlingSteps(context),
|
|
179
|
+
// TODO(dmaretskyi): Normally we could skip filtering by typename here, but since the index does not separate schema versions, we need to do additional filter to only select the correct version.
|
|
138
180
|
{
|
|
139
181
|
_tag: 'FilterStep',
|
|
140
|
-
filter: { ...filter
|
|
182
|
+
filter: { ...filter },
|
|
141
183
|
},
|
|
142
184
|
]);
|
|
143
185
|
} else {
|
|
144
186
|
return QueryPlan.Plan.make([
|
|
145
187
|
{
|
|
146
188
|
_tag: 'SelectStep',
|
|
147
|
-
|
|
189
|
+
scope: context.scope,
|
|
148
190
|
selector: {
|
|
149
191
|
_tag: 'WildcardSelector',
|
|
150
192
|
},
|
|
@@ -157,44 +199,187 @@ export class QueryPlanner {
|
|
|
157
199
|
]);
|
|
158
200
|
}
|
|
159
201
|
}
|
|
202
|
+
|
|
203
|
+
// Tag
|
|
204
|
+
case 'tag': {
|
|
205
|
+
return QueryPlan.Plan.make([
|
|
206
|
+
{
|
|
207
|
+
_tag: 'SelectStep',
|
|
208
|
+
scope: context.scope,
|
|
209
|
+
selector: {
|
|
210
|
+
_tag: 'WildcardSelector',
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
...this._generateDeletedHandlingSteps(context),
|
|
214
|
+
{
|
|
215
|
+
_tag: 'FilterStep',
|
|
216
|
+
filter: { ...filter },
|
|
217
|
+
},
|
|
218
|
+
]);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Text
|
|
160
222
|
case 'text-search': {
|
|
161
223
|
return QueryPlan.Plan.make([
|
|
162
224
|
{
|
|
163
225
|
_tag: 'SelectStep',
|
|
164
|
-
|
|
226
|
+
scope: context.scope,
|
|
165
227
|
selector: {
|
|
166
228
|
_tag: 'TextSelector',
|
|
167
229
|
text: filter.text,
|
|
168
230
|
searchKind: filter.searchKind ?? this._options.defaultTextSearchKind,
|
|
231
|
+
typename: null,
|
|
169
232
|
},
|
|
170
233
|
},
|
|
171
234
|
...this._generateDeletedHandlingSteps(context),
|
|
172
235
|
]);
|
|
173
236
|
}
|
|
237
|
+
|
|
238
|
+
// Timestamp
|
|
239
|
+
case 'timestamp': {
|
|
240
|
+
if (context.selectionInverted) {
|
|
241
|
+
throw new QueryError({
|
|
242
|
+
message:
|
|
243
|
+
'Negated timestamp filters are not supported. Use Filter.updated/Filter.created with the inverted range instead.',
|
|
244
|
+
context: { query: context.originalQuery },
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
return QueryPlan.Plan.make([
|
|
248
|
+
{
|
|
249
|
+
_tag: 'SelectStep',
|
|
250
|
+
scope: context.scope,
|
|
251
|
+
selector: _timestampFilterToSelector(filter),
|
|
252
|
+
},
|
|
253
|
+
...this._generateDeletedHandlingSteps(context),
|
|
254
|
+
]);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// ChildOf
|
|
258
|
+
case 'child-of': {
|
|
259
|
+
return QueryPlan.Plan.make([
|
|
260
|
+
{
|
|
261
|
+
_tag: 'SelectStep',
|
|
262
|
+
scope: context.scope,
|
|
263
|
+
selector: {
|
|
264
|
+
_tag: 'WildcardSelector',
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
...this._generateDeletedHandlingSteps(context),
|
|
268
|
+
{
|
|
269
|
+
_tag: 'FilterStep',
|
|
270
|
+
filter: { ...filter },
|
|
271
|
+
},
|
|
272
|
+
]);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Compare
|
|
174
276
|
case 'compare':
|
|
175
|
-
throw
|
|
277
|
+
throw queryTooComplexError(context.originalQuery);
|
|
176
278
|
case 'in':
|
|
177
|
-
throw
|
|
279
|
+
throw queryTooComplexError(context.originalQuery);
|
|
178
280
|
case 'range':
|
|
179
|
-
throw
|
|
281
|
+
throw queryTooComplexError(context.originalQuery);
|
|
282
|
+
|
|
283
|
+
// Boolean
|
|
180
284
|
case 'not':
|
|
181
285
|
return this._generateSelectionFromFilter(filter.filter, {
|
|
182
286
|
...context,
|
|
183
287
|
selectionInverted: !context.selectionInverted,
|
|
184
288
|
});
|
|
185
|
-
case 'and':
|
|
186
|
-
|
|
289
|
+
case 'and': {
|
|
290
|
+
const flatFilters = _flattenAnd(filter.filters);
|
|
291
|
+
const timestampFilters = flatFilters.filter((f): f is QueryAST.FilterTimestamp => f.type === 'timestamp');
|
|
292
|
+
const childOfFilters = flatFilters.filter((f): f is QueryAST.FilterChildOf => f.type === 'child-of');
|
|
293
|
+
const otherFilters = flatFilters.filter((f) => f.type !== 'timestamp' && f.type !== 'child-of');
|
|
294
|
+
|
|
295
|
+
if (timestampFilters.length > 0 && context.selectionInverted) {
|
|
296
|
+
throw new QueryError({
|
|
297
|
+
message:
|
|
298
|
+
'Negated timestamp filters are not supported. Use Filter.updated/Filter.created with the inverted range instead.',
|
|
299
|
+
context: { query: context.originalQuery },
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (timestampFilters.length > 0 && otherFilters.length <= 1 && childOfFilters.length === 0) {
|
|
304
|
+
const innerFilter = otherFilters[0];
|
|
305
|
+
const innerPlan = innerFilter
|
|
306
|
+
? this._generateSelectionFromFilter(innerFilter, context)
|
|
307
|
+
: QueryPlan.Plan.make([
|
|
308
|
+
{
|
|
309
|
+
_tag: 'SelectStep',
|
|
310
|
+
scope: context.scope,
|
|
311
|
+
selector: { _tag: 'WildcardSelector' },
|
|
312
|
+
},
|
|
313
|
+
...this._generateDeletedHandlingSteps(context),
|
|
314
|
+
]);
|
|
315
|
+
|
|
316
|
+
const selectIdx = innerPlan.steps.findIndex((s) => s._tag === 'SelectStep');
|
|
317
|
+
if (selectIdx !== -1) {
|
|
318
|
+
const newSteps = [...innerPlan.steps];
|
|
319
|
+
newSteps.splice(selectIdx + 1, 0, {
|
|
320
|
+
_tag: 'FilterStep',
|
|
321
|
+
filter: { type: 'and', filters: timestampFilters },
|
|
322
|
+
});
|
|
323
|
+
return QueryPlan.Plan.make(newSteps);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const selector = _mergeTimestampSelectors(timestampFilters.map(_timestampFilterToSelector));
|
|
327
|
+
return QueryPlan.Plan.make([
|
|
328
|
+
{
|
|
329
|
+
_tag: 'SelectStep',
|
|
330
|
+
scope: context.scope,
|
|
331
|
+
selector,
|
|
332
|
+
},
|
|
333
|
+
...this._generateDeletedHandlingSteps(context),
|
|
334
|
+
]);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (timestampFilters.length > 0 && childOfFilters.length === 0) {
|
|
338
|
+
throw new QueryError({
|
|
339
|
+
message:
|
|
340
|
+
'Timestamp filters can only be combined with a single type or property filter via AND. Split complex filters into a subquery.',
|
|
341
|
+
context: { query: context.originalQuery },
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (childOfFilters.length > 0) {
|
|
346
|
+
const remainingFilters = flatFilters.filter((f) => f.type !== 'child-of');
|
|
347
|
+
const innerPlan =
|
|
348
|
+
remainingFilters.length === 1
|
|
349
|
+
? this._generateSelectionFromFilter(remainingFilters[0], context)
|
|
350
|
+
: remainingFilters.length > 1
|
|
351
|
+
? this._generateSelectionFromFilter({ type: 'and', filters: remainingFilters }, context)
|
|
352
|
+
: QueryPlan.Plan.make([
|
|
353
|
+
{
|
|
354
|
+
_tag: 'SelectStep',
|
|
355
|
+
scope: context.scope,
|
|
356
|
+
selector: { _tag: 'WildcardSelector' },
|
|
357
|
+
},
|
|
358
|
+
...this._generateDeletedHandlingSteps(context),
|
|
359
|
+
]);
|
|
360
|
+
|
|
361
|
+
const childOfSteps: QueryPlan.Step[] = childOfFilters.map((f) => ({
|
|
362
|
+
_tag: 'FilterStep' as const,
|
|
363
|
+
filter: f,
|
|
364
|
+
}));
|
|
365
|
+
|
|
366
|
+
return QueryPlan.Plan.make([...innerPlan.steps, ...childOfSteps]);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
throw queryTooComplexError(context.originalQuery);
|
|
370
|
+
}
|
|
187
371
|
case 'or':
|
|
188
372
|
// Optimized case
|
|
189
373
|
if (filter.filters.every(isTrivialTypenameFilter)) {
|
|
190
|
-
const typenames = filter.filters.map((
|
|
191
|
-
invariant(
|
|
192
|
-
return
|
|
374
|
+
const typenames = filter.filters.map((filter) => {
|
|
375
|
+
invariant(filter.type === 'object' && filter.typename !== null);
|
|
376
|
+
return filter.typename;
|
|
193
377
|
});
|
|
378
|
+
|
|
194
379
|
return QueryPlan.Plan.make([
|
|
195
380
|
{
|
|
196
381
|
_tag: 'SelectStep',
|
|
197
|
-
|
|
382
|
+
scope: context.scope,
|
|
198
383
|
selector: {
|
|
199
384
|
_tag: 'TypeSelector',
|
|
200
385
|
typename: typenames as DXN.String[],
|
|
@@ -202,13 +387,18 @@ export class QueryPlanner {
|
|
|
202
387
|
},
|
|
203
388
|
},
|
|
204
389
|
...this._generateDeletedHandlingSteps(context),
|
|
390
|
+
{
|
|
391
|
+
_tag: 'FilterStep',
|
|
392
|
+
filter: { ...filter },
|
|
393
|
+
},
|
|
205
394
|
]);
|
|
206
395
|
} else {
|
|
207
|
-
throw
|
|
396
|
+
throw queryTooComplexError(context.originalQuery);
|
|
208
397
|
}
|
|
209
398
|
|
|
210
399
|
default:
|
|
211
|
-
throw new QueryError(
|
|
400
|
+
throw new QueryError({
|
|
401
|
+
message: `Unsupported filter type: ${(filter as any).type}`,
|
|
212
402
|
context: { query: context.originalQuery },
|
|
213
403
|
});
|
|
214
404
|
}
|
|
@@ -286,7 +476,7 @@ export class QueryPlanner {
|
|
|
286
476
|
traversal: {
|
|
287
477
|
_tag: 'ReferenceTraversal',
|
|
288
478
|
direction: 'incoming',
|
|
289
|
-
property: query.property,
|
|
479
|
+
property: query.property ?? null,
|
|
290
480
|
},
|
|
291
481
|
},
|
|
292
482
|
...this._generateDeletedHandlingSteps(context),
|
|
@@ -392,6 +582,38 @@ export class QueryPlanner {
|
|
|
392
582
|
]);
|
|
393
583
|
}
|
|
394
584
|
|
|
585
|
+
private _generateHierarchyTraversalClause(
|
|
586
|
+
query: QueryAST.QueryHierarchyTraversalClause,
|
|
587
|
+
context: GenerationContext,
|
|
588
|
+
): QueryPlan.Plan {
|
|
589
|
+
return QueryPlan.Plan.make([
|
|
590
|
+
...this._generate(query.anchor, context).steps,
|
|
591
|
+
{
|
|
592
|
+
_tag: 'TraverseStep',
|
|
593
|
+
traversal: {
|
|
594
|
+
_tag: 'HierarchyTraversal',
|
|
595
|
+
direction: query.direction,
|
|
596
|
+
},
|
|
597
|
+
},
|
|
598
|
+
...this._generateDeletedHandlingSteps(context),
|
|
599
|
+
]);
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
private _validateQueryScoped(query: QueryAST.Query): void {
|
|
603
|
+
let hasScope = false;
|
|
604
|
+
QueryAST.visit(query, (node) => {
|
|
605
|
+
if (node.type === 'from') {
|
|
606
|
+
hasScope = true;
|
|
607
|
+
}
|
|
608
|
+
});
|
|
609
|
+
if (!hasScope) {
|
|
610
|
+
throw new QueryError({
|
|
611
|
+
message: 'Query must be scoped with a from() clause',
|
|
612
|
+
context: { query },
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
395
617
|
/**
|
|
396
618
|
* Removes filter steps that have no predicates.
|
|
397
619
|
*/
|
|
@@ -425,6 +647,176 @@ export class QueryPlanner {
|
|
|
425
647
|
// TODO(dmaretskyi): Implement this.
|
|
426
648
|
return plan;
|
|
427
649
|
}
|
|
650
|
+
|
|
651
|
+
private _generateOrderClause(query: QueryAST.QueryOrderClause, context: GenerationContext): QueryPlan.Plan {
|
|
652
|
+
return QueryPlan.Plan.make([
|
|
653
|
+
...this._generate(query.query, context).steps,
|
|
654
|
+
{
|
|
655
|
+
_tag: 'OrderStep',
|
|
656
|
+
order: query.order,
|
|
657
|
+
},
|
|
658
|
+
]);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
private _generateLimitClause(query: QueryAST.QueryLimitClause, context: GenerationContext): QueryPlan.Plan {
|
|
662
|
+
return QueryPlan.Plan.make([
|
|
663
|
+
...this._generate(query.query, context).steps,
|
|
664
|
+
{
|
|
665
|
+
_tag: 'LimitStep',
|
|
666
|
+
limit: query.limit,
|
|
667
|
+
},
|
|
668
|
+
]);
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
// After complete plan is built, inspect it from the end:
|
|
672
|
+
// - Walk backwards until hitting an object set changer.
|
|
673
|
+
// - If an order step is found, skip.
|
|
674
|
+
// - Otherwise insert natural order before any limit steps (since limit should be applied after ordering).
|
|
675
|
+
// This method is recursive and also processes sub-plans in unions and set differences.
|
|
676
|
+
private _ensureOrderStep(plan: QueryPlan.Plan): QueryPlan.Plan {
|
|
677
|
+
// First, recursively process any sub-plans.
|
|
678
|
+
const processedSteps = plan.steps.map((step): QueryPlan.Step => {
|
|
679
|
+
if (step._tag === 'UnionStep') {
|
|
680
|
+
return {
|
|
681
|
+
_tag: 'UnionStep',
|
|
682
|
+
plans: step.plans.map((subPlan) => this._ensureOrderStep(subPlan)),
|
|
683
|
+
};
|
|
684
|
+
} else if (step._tag === 'SetDifferenceStep') {
|
|
685
|
+
return {
|
|
686
|
+
_tag: 'SetDifferenceStep',
|
|
687
|
+
source: this._ensureOrderStep(step.source),
|
|
688
|
+
exclude: this._ensureOrderStep(step.exclude),
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
return step;
|
|
692
|
+
});
|
|
693
|
+
|
|
694
|
+
const processedPlan = QueryPlan.Plan.make(processedSteps);
|
|
695
|
+
|
|
696
|
+
const OBJECT_SET_CHANGERS = new Set(['SelectStep', 'TraverseStep', 'UnionStep', 'SetDifferenceStep']);
|
|
697
|
+
for (let i = processedPlan.steps.length - 1; i >= 0; i--) {
|
|
698
|
+
const step = processedPlan.steps[i];
|
|
699
|
+
if (step._tag === 'OrderStep') {
|
|
700
|
+
return processedPlan;
|
|
701
|
+
}
|
|
702
|
+
if (OBJECT_SET_CHANGERS.has(step._tag)) {
|
|
703
|
+
break;
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
// Find the position to insert the order step (before any limit steps).
|
|
708
|
+
let insertIndex = processedPlan.steps.length;
|
|
709
|
+
for (let i = processedPlan.steps.length - 1; i >= 0; i--) {
|
|
710
|
+
if (processedPlan.steps[i]._tag === 'LimitStep') {
|
|
711
|
+
insertIndex = i;
|
|
712
|
+
} else {
|
|
713
|
+
break;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
const newSteps = [...processedPlan.steps];
|
|
718
|
+
newSteps.splice(insertIndex, 0, {
|
|
719
|
+
_tag: 'OrderStep',
|
|
720
|
+
order: [Order.natural.ast],
|
|
721
|
+
});
|
|
722
|
+
|
|
723
|
+
return QueryPlan.Plan.make(newSteps);
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
/**
|
|
727
|
+
* Propagates limits from LimitStep to SelectStep and OrderStep when possible.
|
|
728
|
+
* Limits can only be propagated if there are no unions or traversals between the LimitStep and SelectStep/OrderStep.
|
|
729
|
+
*/
|
|
730
|
+
private _optimizeLimits(plan: QueryPlan.Plan): QueryPlan.Plan {
|
|
731
|
+
// First, recursively process any sub-plans.
|
|
732
|
+
const processedSteps = plan.steps.map((step): QueryPlan.Step => {
|
|
733
|
+
if (step._tag === 'UnionStep') {
|
|
734
|
+
return {
|
|
735
|
+
_tag: 'UnionStep',
|
|
736
|
+
plans: step.plans.map((subPlan) => this._optimizeLimits(subPlan)),
|
|
737
|
+
};
|
|
738
|
+
} else if (step._tag === 'SetDifferenceStep') {
|
|
739
|
+
return {
|
|
740
|
+
_tag: 'SetDifferenceStep',
|
|
741
|
+
source: this._optimizeLimits(step.source),
|
|
742
|
+
exclude: this._optimizeLimits(step.exclude),
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
return step;
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
// Find if there's a LimitStep at the end (possibly after OrderStep).
|
|
749
|
+
let limitStepIndex = -1;
|
|
750
|
+
let limitValue: number | undefined;
|
|
751
|
+
|
|
752
|
+
for (let i = processedSteps.length - 1; i >= 0; i--) {
|
|
753
|
+
const step = processedSteps[i];
|
|
754
|
+
if (step._tag === 'LimitStep') {
|
|
755
|
+
limitStepIndex = i;
|
|
756
|
+
limitValue = step.limit;
|
|
757
|
+
break;
|
|
758
|
+
}
|
|
759
|
+
// Only allow OrderStep after LimitStep when looking backwards.
|
|
760
|
+
if (step._tag !== 'OrderStep') {
|
|
761
|
+
break;
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
if (limitStepIndex === -1 || limitValue === undefined) {
|
|
766
|
+
return QueryPlan.Plan.make(processedSteps);
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
// Check if we can propagate the limit to a SelectStep and/or OrderStep.
|
|
770
|
+
// We can only do this if there are no unions, traversals, or set differences between them.
|
|
771
|
+
const BLOCKERS = new Set(['UnionStep', 'TraverseStep', 'SetDifferenceStep']);
|
|
772
|
+
|
|
773
|
+
let selectStepIndex = -1;
|
|
774
|
+
let orderStepIndex = -1;
|
|
775
|
+
for (let i = 0; i < limitStepIndex; i++) {
|
|
776
|
+
const step = processedSteps[i];
|
|
777
|
+
if (step._tag === 'SelectStep') {
|
|
778
|
+
selectStepIndex = i;
|
|
779
|
+
}
|
|
780
|
+
if (step._tag === 'OrderStep') {
|
|
781
|
+
orderStepIndex = i;
|
|
782
|
+
}
|
|
783
|
+
if (BLOCKERS.has(step._tag)) {
|
|
784
|
+
// Found a blocker after the select/order step - can't propagate.
|
|
785
|
+
selectStepIndex = -1;
|
|
786
|
+
orderStepIndex = -1;
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
if (selectStepIndex === -1 && orderStepIndex === -1) {
|
|
791
|
+
return QueryPlan.Plan.make(processedSteps);
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
// Create a mutable copy of steps to modify.
|
|
795
|
+
const newSteps = [...processedSteps];
|
|
796
|
+
|
|
797
|
+
// Propagate the limit to the SelectStep if found.
|
|
798
|
+
if (selectStepIndex !== -1) {
|
|
799
|
+
const selectStep = newSteps[selectStepIndex] as QueryPlan.SelectStep;
|
|
800
|
+
newSteps[selectStepIndex] = {
|
|
801
|
+
...selectStep,
|
|
802
|
+
limit: limitValue,
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
// Propagate the limit to the OrderStep if found.
|
|
807
|
+
if (orderStepIndex !== -1) {
|
|
808
|
+
const orderStep = newSteps[orderStepIndex] as QueryPlan.OrderStep;
|
|
809
|
+
newSteps[orderStepIndex] = {
|
|
810
|
+
...orderStep,
|
|
811
|
+
limit: limitValue,
|
|
812
|
+
};
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
// Remove the LimitStep since limit is now propagated.
|
|
816
|
+
newSteps.splice(limitStepIndex, 1);
|
|
817
|
+
|
|
818
|
+
return QueryPlan.Plan.make(newSteps);
|
|
819
|
+
}
|
|
428
820
|
}
|
|
429
821
|
|
|
430
822
|
/**
|
|
@@ -437,9 +829,9 @@ type GenerationContext = {
|
|
|
437
829
|
originalQuery: QueryAST.Query | null;
|
|
438
830
|
|
|
439
831
|
/**
|
|
440
|
-
*
|
|
832
|
+
* The scope to select from (space IDs, queues, etc.).
|
|
441
833
|
*/
|
|
442
|
-
|
|
834
|
+
scope: QueryAST.Scope;
|
|
443
835
|
|
|
444
836
|
/**
|
|
445
837
|
* How to handle deleted objects.
|
|
@@ -454,7 +846,7 @@ type GenerationContext = {
|
|
|
454
846
|
|
|
455
847
|
const DEFAULT_CONTEXT: GenerationContext = {
|
|
456
848
|
originalQuery: null,
|
|
457
|
-
|
|
849
|
+
scope: {},
|
|
458
850
|
deletedHandling: 'exclude',
|
|
459
851
|
selectionInverted: false,
|
|
460
852
|
};
|
|
@@ -474,6 +866,54 @@ const createRelationTraversalStep = (direction: QueryPlan.RelationTraversal['dir
|
|
|
474
866
|
},
|
|
475
867
|
});
|
|
476
868
|
|
|
869
|
+
/**
|
|
870
|
+
* Recursively flattens nested `and` filters into a single list.
|
|
871
|
+
*/
|
|
872
|
+
const _flattenAnd = (filters: readonly QueryAST.Filter[]): QueryAST.Filter[] => {
|
|
873
|
+
const result: QueryAST.Filter[] = [];
|
|
874
|
+
for (const f of filters) {
|
|
875
|
+
if (f.type === 'and') {
|
|
876
|
+
result.push(..._flattenAnd(f.filters));
|
|
877
|
+
} else {
|
|
878
|
+
result.push(f);
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
return result;
|
|
882
|
+
};
|
|
883
|
+
|
|
884
|
+
const _timestampFilterToSelector = (filter: QueryAST.FilterTimestamp): QueryPlan.TimestampSelector => {
|
|
885
|
+
const selector: QueryPlan.TimestampSelector = { _tag: 'TimestampSelector' };
|
|
886
|
+
const key =
|
|
887
|
+
filter.field === 'updatedAt'
|
|
888
|
+
? filter.operator === 'gte' || filter.operator === 'gt'
|
|
889
|
+
? 'updatedAfter'
|
|
890
|
+
: 'updatedBefore'
|
|
891
|
+
: filter.operator === 'gte' || filter.operator === 'gt'
|
|
892
|
+
? 'createdAfter'
|
|
893
|
+
: 'createdBefore';
|
|
894
|
+
selector[key] = filter.value;
|
|
895
|
+
return selector;
|
|
896
|
+
};
|
|
897
|
+
|
|
898
|
+
const _mergeTimestampSelectors = (selectors: QueryPlan.TimestampSelector[]): QueryPlan.TimestampSelector => {
|
|
899
|
+
const merged: QueryPlan.TimestampSelector = { _tag: 'TimestampSelector' };
|
|
900
|
+
for (const s of selectors) {
|
|
901
|
+
if (s.updatedAfter != null) {
|
|
902
|
+
merged.updatedAfter = Math.max(merged.updatedAfter ?? 0, s.updatedAfter);
|
|
903
|
+
}
|
|
904
|
+
if (s.updatedBefore != null) {
|
|
905
|
+
merged.updatedBefore = Math.min(merged.updatedBefore ?? Infinity, s.updatedBefore);
|
|
906
|
+
}
|
|
907
|
+
if (s.createdAfter != null) {
|
|
908
|
+
merged.createdAfter = Math.max(merged.createdAfter ?? 0, s.createdAfter);
|
|
909
|
+
}
|
|
910
|
+
if (s.createdBefore != null) {
|
|
911
|
+
merged.createdBefore = Math.min(merged.createdBefore ?? Infinity, s.createdBefore);
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
return merged;
|
|
915
|
+
};
|
|
916
|
+
|
|
477
917
|
const isTrivialTypenameFilter = (filter: QueryAST.Filter): boolean => {
|
|
478
918
|
return (
|
|
479
919
|
filter.type === 'object' &&
|
|
@@ -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
|
|
|
@@ -81,7 +81,7 @@ export class CredentialServerExtension extends RpcExtension<
|
|
|
81
81
|
): Promise<GetAdmissionCredentialResponse> => {
|
|
82
82
|
const memberInfo = this._space.spaceState.members.get(request.memberKey);
|
|
83
83
|
if (!memberInfo?.credential) {
|
|
84
|
-
throw new ProtocolError('Space member not found.', request);
|
|
84
|
+
throw new ProtocolError({ message: 'Space member not found.', context: { ...request } });
|
|
85
85
|
}
|
|
86
86
|
return { admissionCredential: memberInfo.credential };
|
|
87
87
|
},
|