@dxos/echo-pipeline 0.8.4-main.ae835ea → 0.8.4-main.bc2380dfbc
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +102 -5
- package/README.md +1 -1
- package/dist/lib/{browser/chunk-WJJXJTNS.mjs → neutral/chunk-46DCRDA7.mjs} +44 -13
- package/dist/lib/neutral/chunk-46DCRDA7.mjs.map +7 -0
- package/dist/lib/{browser/chunk-FR6GJ4S4.mjs → neutral/chunk-LR7UFVQX.mjs} +145 -618
- package/dist/lib/neutral/chunk-LR7UFVQX.mjs.map +7 -0
- package/dist/lib/{browser → neutral}/filter/index.mjs +1 -2
- package/dist/lib/neutral/index.mjs +7332 -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 +49 -292
- 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 +105 -27
- 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 +22 -2
- 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 +17 -11
- package/dist/types/src/automerge/echo-network-adapter.d.ts.map +1 -1
- package/dist/types/src/automerge/echo-replicator.d.ts +28 -16
- package/dist/types/src/automerge/echo-replicator.d.ts.map +1 -1
- package/dist/types/src/automerge/errors.d.ts +35 -0
- package/dist/types/src/automerge/errors.d.ts.map +1 -0
- 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 -0
- package/dist/types/src/automerge/index.d.ts.map +1 -1
- package/dist/types/src/automerge/leveldb-storage-adapter.d.ts +10 -4
- package/dist/types/src/automerge/leveldb-storage-adapter.d.ts.map +1 -1
- package/dist/types/src/automerge/mesh-echo-replicator-connection.d.ts +8 -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/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 +12 -6
- 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 +30 -8
- package/dist/types/src/db-host/documents-synchronizer.d.ts.map +1 -1
- package/dist/types/src/db-host/echo-host.d.ts +74 -28
- 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 +18 -10
- 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 +3 -2
- 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 +7 -6
- 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 +1 -1
- package/dist/types/src/filter/filter-match.d.ts.map +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 +2 -2
- 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 +16 -20
- package/dist/types/src/query/errors.d.ts.map +1 -1
- package/dist/types/src/query/plan.d.ts +59 -8
- package/dist/types/src/query/plan.d.ts.map +1 -1
- package/dist/types/src/query/query-executor.d.ts +55 -12
- package/dist/types/src/query/query-executor.d.ts.map +1 -1
- package/dist/types/src/query/query-planner.d.ts +10 -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 +4 -3
- package/dist/types/src/space/control-pipeline.d.ts.map +1 -1
- package/dist/types/src/space/space-manager.d.ts +7 -7
- package/dist/types/src/space/space-manager.d.ts.map +1 -1
- package/dist/types/src/space/space-protocol.d.ts +9 -8
- package/dist/types/src/space/space-protocol.d.ts.map +1 -1
- package/dist/types/src/space/space.d.ts +5 -5
- 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.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 +13 -13
- package/dist/types/src/testing/test-replicator.d.ts.map +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 +263 -0
- package/src/automerge/automerge-host.test.ts +145 -32
- package/src/automerge/automerge-host.ts +442 -163
- package/src/automerge/automerge-repo-subduction.test.ts +1054 -0
- package/src/automerge/automerge-repo.test.ts +284 -77
- package/src/automerge/automerge-subduction.test.ts +330 -0
- package/src/automerge/collection-synchronizer.test.ts +96 -16
- package/src/automerge/collection-synchronizer.ts +125 -26
- package/src/automerge/echo-network-adapter.test.ts +2 -1
- package/src/automerge/echo-network-adapter.ts +44 -32
- package/src/automerge/echo-replicator.ts +30 -16
- package/src/automerge/errors.ts +10 -0
- package/src/automerge/handle-state.ts +62 -0
- package/src/automerge/heads-store.ts +42 -3
- package/src/automerge/index.ts +2 -0
- package/src/automerge/leveldb-storage-adapter.ts +70 -4
- package/src/automerge/mesh-echo-replicator-connection.ts +8 -8
- package/src/automerge/mesh-echo-replicator.ts +14 -11
- 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 +27 -10
- package/src/db-host/database-root.ts +19 -7
- package/src/db-host/documents-synchronizer.test.ts +145 -14
- package/src/db-host/documents-synchronizer.ts +158 -59
- package/src/db-host/echo-host.ts +376 -90
- 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 +108 -111
- 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 +11 -9
- package/src/db-host/stub.ts +33 -0
- package/src/edge/echo-edge-replicator.test.ts +9 -9
- package/src/edge/echo-edge-replicator.ts +50 -42
- 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 +41 -25
- package/src/filter/filter-match.ts +75 -20
- package/src/metadata/metadata-store.ts +8 -5
- package/src/pipeline/pipeline-stress.test.ts +1 -3
- package/src/pipeline/pipeline.test.ts +0 -1
- package/src/pipeline/pipeline.ts +2 -3
- package/src/query/errors.ts +2 -2
- package/src/query/plan.ts +73 -9
- package/src/query/query-executor.ts +1064 -172
- package/src/query/query-planner.test.ts +854 -86
- package/src/query/query-planner.ts +444 -38
- package/src/space/admission-discovery-extension.ts +1 -1
- package/src/space/auth.ts +6 -6
- package/src/space/control-pipeline.test.ts +2 -2
- package/src/space/control-pipeline.ts +7 -7
- package/src/space/space-manager.ts +12 -17
- package/src/space/space-protocol.browser.test.ts +13 -12
- package/src/space/space-protocol.test.ts +13 -13
- package/src/space/space-protocol.ts +16 -15
- package/src/space/space.test.ts +2 -2
- package/src/space/space.ts +9 -10
- package/src/testing/index.ts +0 -2
- package/src/testing/test-agent-builder.ts +0 -1
- package/src/testing/test-network-adapter.ts +24 -3
- package/src/testing/test-replicator.ts +15 -20
- package/src/util.ts +7 -3
- package/dist/lib/browser/chunk-CGS2ULMK.mjs +0 -11
- package/dist/lib/browser/chunk-CGS2ULMK.mjs.map +0 -7
- package/dist/lib/browser/chunk-FR6GJ4S4.mjs.map +0 -7
- package/dist/lib/browser/chunk-WJJXJTNS.mjs.map +0 -7
- package/dist/lib/browser/index.mjs +0 -5492
- 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-esm/chunk-HSLMI22Q.mjs +0 -11
- package/dist/lib/node-esm/chunk-HSLMI22Q.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-JQCCVDLT.mjs +0 -2144
- package/dist/lib/node-esm/chunk-JQCCVDLT.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-LEQ77KAT.mjs +0 -240
- package/dist/lib/node-esm/chunk-LEQ77KAT.mjs.map +0 -7
- package/dist/lib/node-esm/filter/index.mjs +0 -13
- package/dist/lib/node-esm/filter/index.mjs.map +0 -7
- package/dist/lib/node-esm/index.mjs +0 -5492
- 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 -764
- 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,14 +2,31 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { Order } from '@dxos/echo';
|
|
6
|
-
import {
|
|
5
|
+
import { Order, Query } from '@dxos/echo';
|
|
6
|
+
import { QueryAST } from '@dxos/echo-protocol';
|
|
7
7
|
import { invariant } from '@dxos/invariant';
|
|
8
|
-
import type { DXN
|
|
8
|
+
import type { DXN } from '@dxos/keys';
|
|
9
9
|
|
|
10
10
|
import { QueryError } from './errors';
|
|
11
11
|
import { QueryPlan } from './plan';
|
|
12
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
|
+
|
|
13
30
|
export type QueryPlannerOptions = {
|
|
14
31
|
defaultTextSearchKind: QueryPlan.TextSearchKind;
|
|
15
32
|
};
|
|
@@ -33,10 +50,12 @@ export class QueryPlanner {
|
|
|
33
50
|
}
|
|
34
51
|
|
|
35
52
|
createPlan(query: QueryAST.Query): QueryPlan.Plan {
|
|
53
|
+
this._validateQueryScoped(query);
|
|
36
54
|
let plan = this._generate(query, { ...DEFAULT_CONTEXT, originalQuery: query });
|
|
37
55
|
plan = this._optimizeEmptyFilters(plan);
|
|
38
56
|
plan = this._optimizeSoloUnions(plan);
|
|
39
57
|
plan = this._ensureOrderStep(plan);
|
|
58
|
+
plan = this._optimizeLimits(plan);
|
|
40
59
|
return plan;
|
|
41
60
|
}
|
|
42
61
|
|
|
@@ -56,12 +75,18 @@ export class QueryPlanner {
|
|
|
56
75
|
return this._generateRelationTraversalClause(query, context);
|
|
57
76
|
case 'reference-traversal':
|
|
58
77
|
return this._generateReferenceTraversalClause(query, context);
|
|
78
|
+
case 'hierarchy-traversal':
|
|
79
|
+
return this._generateHierarchyTraversalClause(query, context);
|
|
59
80
|
case 'union':
|
|
60
81
|
return this._generateUnionClause(query, context);
|
|
61
82
|
case 'set-difference':
|
|
62
83
|
return this._generateSetDifferenceClause(query, context);
|
|
63
84
|
case 'order':
|
|
64
85
|
return this._generateOrderClause(query, context);
|
|
86
|
+
case 'limit':
|
|
87
|
+
return this._generateLimitClause(query, context);
|
|
88
|
+
case 'from':
|
|
89
|
+
return this._generateFromClause(query, context);
|
|
65
90
|
default:
|
|
66
91
|
throw new QueryError({
|
|
67
92
|
message: `Unsupported query type: ${(query as any).type}`,
|
|
@@ -74,15 +99,25 @@ export class QueryPlanner {
|
|
|
74
99
|
const newContext = {
|
|
75
100
|
...context,
|
|
76
101
|
};
|
|
77
|
-
if (query.options.spaceIds) {
|
|
78
|
-
newContext.selectionSpaces = query.options.spaceIds as readonly SpaceId[];
|
|
79
|
-
}
|
|
80
102
|
if (query.options.deleted) {
|
|
81
103
|
newContext.deletedHandling = query.options.deleted;
|
|
82
104
|
}
|
|
83
105
|
return this._generate(query.query, newContext);
|
|
84
106
|
}
|
|
85
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
|
+
|
|
86
121
|
private _generateSelectClause(query: QueryAST.QuerySelectClause, context: GenerationContext): QueryPlan.Plan {
|
|
87
122
|
return this._generateSelectionFromFilter(query.filter, context);
|
|
88
123
|
}
|
|
@@ -108,10 +143,7 @@ export class QueryPlanner {
|
|
|
108
143
|
]);
|
|
109
144
|
}
|
|
110
145
|
if (context.selectionInverted) {
|
|
111
|
-
throw
|
|
112
|
-
message: 'Query too complex',
|
|
113
|
-
context: { query: context.originalQuery },
|
|
114
|
-
});
|
|
146
|
+
throw queryTooComplexError(context.originalQuery);
|
|
115
147
|
}
|
|
116
148
|
|
|
117
149
|
// Try to utilize indexes during selection, prioritizing selecting by id, then by typename.
|
|
@@ -120,7 +152,7 @@ export class QueryPlanner {
|
|
|
120
152
|
return QueryPlan.Plan.make([
|
|
121
153
|
{
|
|
122
154
|
_tag: 'SelectStep',
|
|
123
|
-
|
|
155
|
+
scope: context.scope,
|
|
124
156
|
selector: {
|
|
125
157
|
_tag: 'IdSelector',
|
|
126
158
|
objectIds: filter.id,
|
|
@@ -136,7 +168,7 @@ export class QueryPlanner {
|
|
|
136
168
|
return QueryPlan.Plan.make([
|
|
137
169
|
{
|
|
138
170
|
_tag: 'SelectStep',
|
|
139
|
-
|
|
171
|
+
scope: context.scope,
|
|
140
172
|
selector: {
|
|
141
173
|
_tag: 'TypeSelector',
|
|
142
174
|
typename: [filter.typename as DXN.String],
|
|
@@ -144,16 +176,17 @@ export class QueryPlanner {
|
|
|
144
176
|
},
|
|
145
177
|
},
|
|
146
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.
|
|
147
180
|
{
|
|
148
181
|
_tag: 'FilterStep',
|
|
149
|
-
filter: { ...filter
|
|
182
|
+
filter: { ...filter },
|
|
150
183
|
},
|
|
151
184
|
]);
|
|
152
185
|
} else {
|
|
153
186
|
return QueryPlan.Plan.make([
|
|
154
187
|
{
|
|
155
188
|
_tag: 'SelectStep',
|
|
156
|
-
|
|
189
|
+
scope: context.scope,
|
|
157
190
|
selector: {
|
|
158
191
|
_tag: 'WildcardSelector',
|
|
159
192
|
},
|
|
@@ -172,7 +205,7 @@ export class QueryPlanner {
|
|
|
172
205
|
return QueryPlan.Plan.make([
|
|
173
206
|
{
|
|
174
207
|
_tag: 'SelectStep',
|
|
175
|
-
|
|
208
|
+
scope: context.scope,
|
|
176
209
|
selector: {
|
|
177
210
|
_tag: 'WildcardSelector',
|
|
178
211
|
},
|
|
@@ -190,24 +223,62 @@ export class QueryPlanner {
|
|
|
190
223
|
return QueryPlan.Plan.make([
|
|
191
224
|
{
|
|
192
225
|
_tag: 'SelectStep',
|
|
193
|
-
|
|
226
|
+
scope: context.scope,
|
|
194
227
|
selector: {
|
|
195
228
|
_tag: 'TextSelector',
|
|
196
229
|
text: filter.text,
|
|
197
230
|
searchKind: filter.searchKind ?? this._options.defaultTextSearchKind,
|
|
231
|
+
typename: null,
|
|
198
232
|
},
|
|
199
233
|
},
|
|
200
234
|
...this._generateDeletedHandlingSteps(context),
|
|
201
235
|
]);
|
|
202
236
|
}
|
|
203
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
|
+
|
|
204
275
|
// Compare
|
|
205
276
|
case 'compare':
|
|
206
|
-
throw
|
|
277
|
+
throw queryTooComplexError(context.originalQuery);
|
|
207
278
|
case 'in':
|
|
208
|
-
throw
|
|
279
|
+
throw queryTooComplexError(context.originalQuery);
|
|
209
280
|
case 'range':
|
|
210
|
-
throw
|
|
281
|
+
throw queryTooComplexError(context.originalQuery);
|
|
211
282
|
|
|
212
283
|
// Boolean
|
|
213
284
|
case 'not':
|
|
@@ -215,8 +286,124 @@ export class QueryPlanner {
|
|
|
215
286
|
...context,
|
|
216
287
|
selectionInverted: !context.selectionInverted,
|
|
217
288
|
});
|
|
218
|
-
case 'and':
|
|
219
|
-
|
|
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
|
+
// Simple AND: all filters are root-executable against in-memory objects after a wildcard select.
|
|
370
|
+
// Supports combining true root selectors (`object`, `tag`) plus negations / unions of those.
|
|
371
|
+
// Property-level filters (`compare`, `in`, `range`, `contains`) are intentionally excluded because
|
|
372
|
+
// `filterMatchObject` only handles them as nested predicates inside an `object` filter, not at the root.
|
|
373
|
+
const isRootExecutable = (filter: QueryAST.Filter): boolean => {
|
|
374
|
+
switch (filter.type) {
|
|
375
|
+
case 'object':
|
|
376
|
+
case 'tag':
|
|
377
|
+
return true;
|
|
378
|
+
case 'not':
|
|
379
|
+
return isRootExecutable(filter.filter);
|
|
380
|
+
case 'or':
|
|
381
|
+
return filter.filters.every(isRootExecutable);
|
|
382
|
+
default:
|
|
383
|
+
return false;
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
if (flatFilters.every(isRootExecutable)) {
|
|
387
|
+
const innerFilter: QueryAST.Filter = { type: 'and', filters: flatFilters };
|
|
388
|
+
const plannedFilter: QueryAST.Filter = context.selectionInverted
|
|
389
|
+
? { type: 'not', filter: innerFilter }
|
|
390
|
+
: innerFilter;
|
|
391
|
+
return QueryPlan.Plan.make([
|
|
392
|
+
{
|
|
393
|
+
_tag: 'SelectStep',
|
|
394
|
+
scope: context.scope,
|
|
395
|
+
selector: { _tag: 'WildcardSelector' },
|
|
396
|
+
},
|
|
397
|
+
...this._generateDeletedHandlingSteps(context),
|
|
398
|
+
{
|
|
399
|
+
_tag: 'FilterStep',
|
|
400
|
+
filter: plannedFilter,
|
|
401
|
+
},
|
|
402
|
+
]);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
throw queryTooComplexError(context.originalQuery);
|
|
406
|
+
}
|
|
220
407
|
case 'or':
|
|
221
408
|
// Optimized case
|
|
222
409
|
if (filter.filters.every(isTrivialTypenameFilter)) {
|
|
@@ -228,7 +415,7 @@ export class QueryPlanner {
|
|
|
228
415
|
return QueryPlan.Plan.make([
|
|
229
416
|
{
|
|
230
417
|
_tag: 'SelectStep',
|
|
231
|
-
|
|
418
|
+
scope: context.scope,
|
|
232
419
|
selector: {
|
|
233
420
|
_tag: 'TypeSelector',
|
|
234
421
|
typename: typenames as DXN.String[],
|
|
@@ -236,9 +423,13 @@ export class QueryPlanner {
|
|
|
236
423
|
},
|
|
237
424
|
},
|
|
238
425
|
...this._generateDeletedHandlingSteps(context),
|
|
426
|
+
{
|
|
427
|
+
_tag: 'FilterStep',
|
|
428
|
+
filter: { ...filter },
|
|
429
|
+
},
|
|
239
430
|
]);
|
|
240
431
|
} else {
|
|
241
|
-
throw
|
|
432
|
+
throw queryTooComplexError(context.originalQuery);
|
|
242
433
|
}
|
|
243
434
|
|
|
244
435
|
default:
|
|
@@ -321,7 +512,7 @@ export class QueryPlanner {
|
|
|
321
512
|
traversal: {
|
|
322
513
|
_tag: 'ReferenceTraversal',
|
|
323
514
|
direction: 'incoming',
|
|
324
|
-
property: query.property,
|
|
515
|
+
property: query.property ?? null,
|
|
325
516
|
},
|
|
326
517
|
},
|
|
327
518
|
...this._generateDeletedHandlingSteps(context),
|
|
@@ -427,6 +618,38 @@ export class QueryPlanner {
|
|
|
427
618
|
]);
|
|
428
619
|
}
|
|
429
620
|
|
|
621
|
+
private _generateHierarchyTraversalClause(
|
|
622
|
+
query: QueryAST.QueryHierarchyTraversalClause,
|
|
623
|
+
context: GenerationContext,
|
|
624
|
+
): QueryPlan.Plan {
|
|
625
|
+
return QueryPlan.Plan.make([
|
|
626
|
+
...this._generate(query.anchor, context).steps,
|
|
627
|
+
{
|
|
628
|
+
_tag: 'TraverseStep',
|
|
629
|
+
traversal: {
|
|
630
|
+
_tag: 'HierarchyTraversal',
|
|
631
|
+
direction: query.direction,
|
|
632
|
+
},
|
|
633
|
+
},
|
|
634
|
+
...this._generateDeletedHandlingSteps(context),
|
|
635
|
+
]);
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
private _validateQueryScoped(query: QueryAST.Query): void {
|
|
639
|
+
let hasScope = false;
|
|
640
|
+
QueryAST.visit(query, (node) => {
|
|
641
|
+
if (node.type === 'from') {
|
|
642
|
+
hasScope = true;
|
|
643
|
+
}
|
|
644
|
+
});
|
|
645
|
+
if (!hasScope) {
|
|
646
|
+
throw new QueryError({
|
|
647
|
+
message: 'Query must be scoped with a from() clause',
|
|
648
|
+
context: { query },
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
430
653
|
/**
|
|
431
654
|
* Removes filter steps that have no predicates.
|
|
432
655
|
*/
|
|
@@ -471,29 +694,164 @@ export class QueryPlanner {
|
|
|
471
694
|
]);
|
|
472
695
|
}
|
|
473
696
|
|
|
697
|
+
private _generateLimitClause(query: QueryAST.QueryLimitClause, context: GenerationContext): QueryPlan.Plan {
|
|
698
|
+
return QueryPlan.Plan.make([
|
|
699
|
+
...this._generate(query.query, context).steps,
|
|
700
|
+
{
|
|
701
|
+
_tag: 'LimitStep',
|
|
702
|
+
limit: query.limit,
|
|
703
|
+
},
|
|
704
|
+
]);
|
|
705
|
+
}
|
|
706
|
+
|
|
474
707
|
// After complete plan is built, inspect it from the end:
|
|
475
708
|
// - Walk backwards until hitting an object set changer.
|
|
476
709
|
// - If an order step is found, skip.
|
|
477
|
-
// - Otherwise
|
|
710
|
+
// - Otherwise insert natural order before any limit steps (since limit should be applied after ordering).
|
|
711
|
+
// This method is recursive and also processes sub-plans in unions and set differences.
|
|
478
712
|
private _ensureOrderStep(plan: QueryPlan.Plan): QueryPlan.Plan {
|
|
713
|
+
// First, recursively process any sub-plans.
|
|
714
|
+
const processedSteps = plan.steps.map((step): QueryPlan.Step => {
|
|
715
|
+
if (step._tag === 'UnionStep') {
|
|
716
|
+
return {
|
|
717
|
+
_tag: 'UnionStep',
|
|
718
|
+
plans: step.plans.map((subPlan) => this._ensureOrderStep(subPlan)),
|
|
719
|
+
};
|
|
720
|
+
} else if (step._tag === 'SetDifferenceStep') {
|
|
721
|
+
return {
|
|
722
|
+
_tag: 'SetDifferenceStep',
|
|
723
|
+
source: this._ensureOrderStep(step.source),
|
|
724
|
+
exclude: this._ensureOrderStep(step.exclude),
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
return step;
|
|
728
|
+
});
|
|
729
|
+
|
|
730
|
+
const processedPlan = QueryPlan.Plan.make(processedSteps);
|
|
731
|
+
|
|
479
732
|
const OBJECT_SET_CHANGERS = new Set(['SelectStep', 'TraverseStep', 'UnionStep', 'SetDifferenceStep']);
|
|
480
|
-
for (let i =
|
|
481
|
-
const step =
|
|
733
|
+
for (let i = processedPlan.steps.length - 1; i >= 0; i--) {
|
|
734
|
+
const step = processedPlan.steps[i];
|
|
482
735
|
if (step._tag === 'OrderStep') {
|
|
483
|
-
return
|
|
736
|
+
return processedPlan;
|
|
484
737
|
}
|
|
485
738
|
if (OBJECT_SET_CHANGERS.has(step._tag)) {
|
|
486
739
|
break;
|
|
487
740
|
}
|
|
488
741
|
}
|
|
489
742
|
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
}
|
|
496
|
-
|
|
743
|
+
// Find the position to insert the order step (before any limit steps).
|
|
744
|
+
let insertIndex = processedPlan.steps.length;
|
|
745
|
+
for (let i = processedPlan.steps.length - 1; i >= 0; i--) {
|
|
746
|
+
if (processedPlan.steps[i]._tag === 'LimitStep') {
|
|
747
|
+
insertIndex = i;
|
|
748
|
+
} else {
|
|
749
|
+
break;
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
const newSteps = [...processedPlan.steps];
|
|
754
|
+
newSteps.splice(insertIndex, 0, {
|
|
755
|
+
_tag: 'OrderStep',
|
|
756
|
+
order: [Order.natural.ast],
|
|
757
|
+
});
|
|
758
|
+
|
|
759
|
+
return QueryPlan.Plan.make(newSteps);
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
/**
|
|
763
|
+
* Propagates limits from LimitStep to SelectStep and OrderStep when possible.
|
|
764
|
+
* Limits can only be propagated if there are no unions or traversals between the LimitStep and SelectStep/OrderStep.
|
|
765
|
+
*/
|
|
766
|
+
private _optimizeLimits(plan: QueryPlan.Plan): QueryPlan.Plan {
|
|
767
|
+
// First, recursively process any sub-plans.
|
|
768
|
+
const processedSteps = plan.steps.map((step): QueryPlan.Step => {
|
|
769
|
+
if (step._tag === 'UnionStep') {
|
|
770
|
+
return {
|
|
771
|
+
_tag: 'UnionStep',
|
|
772
|
+
plans: step.plans.map((subPlan) => this._optimizeLimits(subPlan)),
|
|
773
|
+
};
|
|
774
|
+
} else if (step._tag === 'SetDifferenceStep') {
|
|
775
|
+
return {
|
|
776
|
+
_tag: 'SetDifferenceStep',
|
|
777
|
+
source: this._optimizeLimits(step.source),
|
|
778
|
+
exclude: this._optimizeLimits(step.exclude),
|
|
779
|
+
};
|
|
780
|
+
}
|
|
781
|
+
return step;
|
|
782
|
+
});
|
|
783
|
+
|
|
784
|
+
// Find if there's a LimitStep at the end (possibly after OrderStep).
|
|
785
|
+
let limitStepIndex = -1;
|
|
786
|
+
let limitValue: number | undefined;
|
|
787
|
+
|
|
788
|
+
for (let i = processedSteps.length - 1; i >= 0; i--) {
|
|
789
|
+
const step = processedSteps[i];
|
|
790
|
+
if (step._tag === 'LimitStep') {
|
|
791
|
+
limitStepIndex = i;
|
|
792
|
+
limitValue = step.limit;
|
|
793
|
+
break;
|
|
794
|
+
}
|
|
795
|
+
// Only allow OrderStep after LimitStep when looking backwards.
|
|
796
|
+
if (step._tag !== 'OrderStep') {
|
|
797
|
+
break;
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
if (limitStepIndex === -1 || limitValue === undefined) {
|
|
802
|
+
return QueryPlan.Plan.make(processedSteps);
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
// Check if we can propagate the limit to a SelectStep and/or OrderStep.
|
|
806
|
+
// We can only do this if there are no unions, traversals, or set differences between them.
|
|
807
|
+
const BLOCKERS = new Set(['UnionStep', 'TraverseStep', 'SetDifferenceStep']);
|
|
808
|
+
|
|
809
|
+
let selectStepIndex = -1;
|
|
810
|
+
let orderStepIndex = -1;
|
|
811
|
+
for (let i = 0; i < limitStepIndex; i++) {
|
|
812
|
+
const step = processedSteps[i];
|
|
813
|
+
if (step._tag === 'SelectStep') {
|
|
814
|
+
selectStepIndex = i;
|
|
815
|
+
}
|
|
816
|
+
if (step._tag === 'OrderStep') {
|
|
817
|
+
orderStepIndex = i;
|
|
818
|
+
}
|
|
819
|
+
if (BLOCKERS.has(step._tag)) {
|
|
820
|
+
// Found a blocker after the select/order step - can't propagate.
|
|
821
|
+
selectStepIndex = -1;
|
|
822
|
+
orderStepIndex = -1;
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
if (selectStepIndex === -1 && orderStepIndex === -1) {
|
|
827
|
+
return QueryPlan.Plan.make(processedSteps);
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
// Create a mutable copy of steps to modify.
|
|
831
|
+
const newSteps = [...processedSteps];
|
|
832
|
+
|
|
833
|
+
// Propagate the limit to the SelectStep if found.
|
|
834
|
+
if (selectStepIndex !== -1) {
|
|
835
|
+
const selectStep = newSteps[selectStepIndex] as QueryPlan.SelectStep;
|
|
836
|
+
newSteps[selectStepIndex] = {
|
|
837
|
+
...selectStep,
|
|
838
|
+
limit: limitValue,
|
|
839
|
+
};
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
// Propagate the limit to the OrderStep if found.
|
|
843
|
+
if (orderStepIndex !== -1) {
|
|
844
|
+
const orderStep = newSteps[orderStepIndex] as QueryPlan.OrderStep;
|
|
845
|
+
newSteps[orderStepIndex] = {
|
|
846
|
+
...orderStep,
|
|
847
|
+
limit: limitValue,
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
// Remove the LimitStep since limit is now propagated.
|
|
852
|
+
newSteps.splice(limitStepIndex, 1);
|
|
853
|
+
|
|
854
|
+
return QueryPlan.Plan.make(newSteps);
|
|
497
855
|
}
|
|
498
856
|
}
|
|
499
857
|
|
|
@@ -507,9 +865,9 @@ type GenerationContext = {
|
|
|
507
865
|
originalQuery: QueryAST.Query | null;
|
|
508
866
|
|
|
509
867
|
/**
|
|
510
|
-
*
|
|
868
|
+
* The scope to select from (space IDs, queues, etc.).
|
|
511
869
|
*/
|
|
512
|
-
|
|
870
|
+
scope: QueryAST.Scope;
|
|
513
871
|
|
|
514
872
|
/**
|
|
515
873
|
* How to handle deleted objects.
|
|
@@ -524,7 +882,7 @@ type GenerationContext = {
|
|
|
524
882
|
|
|
525
883
|
const DEFAULT_CONTEXT: GenerationContext = {
|
|
526
884
|
originalQuery: null,
|
|
527
|
-
|
|
885
|
+
scope: {},
|
|
528
886
|
deletedHandling: 'exclude',
|
|
529
887
|
selectionInverted: false,
|
|
530
888
|
};
|
|
@@ -544,6 +902,54 @@ const createRelationTraversalStep = (direction: QueryPlan.RelationTraversal['dir
|
|
|
544
902
|
},
|
|
545
903
|
});
|
|
546
904
|
|
|
905
|
+
/**
|
|
906
|
+
* Recursively flattens nested `and` filters into a single list.
|
|
907
|
+
*/
|
|
908
|
+
const _flattenAnd = (filters: readonly QueryAST.Filter[]): QueryAST.Filter[] => {
|
|
909
|
+
const result: QueryAST.Filter[] = [];
|
|
910
|
+
for (const f of filters) {
|
|
911
|
+
if (f.type === 'and') {
|
|
912
|
+
result.push(..._flattenAnd(f.filters));
|
|
913
|
+
} else {
|
|
914
|
+
result.push(f);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
return result;
|
|
918
|
+
};
|
|
919
|
+
|
|
920
|
+
const _timestampFilterToSelector = (filter: QueryAST.FilterTimestamp): QueryPlan.TimestampSelector => {
|
|
921
|
+
const selector: QueryPlan.TimestampSelector = { _tag: 'TimestampSelector' };
|
|
922
|
+
const key =
|
|
923
|
+
filter.field === 'updatedAt'
|
|
924
|
+
? filter.operator === 'gte' || filter.operator === 'gt'
|
|
925
|
+
? 'updatedAfter'
|
|
926
|
+
: 'updatedBefore'
|
|
927
|
+
: filter.operator === 'gte' || filter.operator === 'gt'
|
|
928
|
+
? 'createdAfter'
|
|
929
|
+
: 'createdBefore';
|
|
930
|
+
selector[key] = filter.value;
|
|
931
|
+
return selector;
|
|
932
|
+
};
|
|
933
|
+
|
|
934
|
+
const _mergeTimestampSelectors = (selectors: QueryPlan.TimestampSelector[]): QueryPlan.TimestampSelector => {
|
|
935
|
+
const merged: QueryPlan.TimestampSelector = { _tag: 'TimestampSelector' };
|
|
936
|
+
for (const s of selectors) {
|
|
937
|
+
if (s.updatedAfter != null) {
|
|
938
|
+
merged.updatedAfter = Math.max(merged.updatedAfter ?? 0, s.updatedAfter);
|
|
939
|
+
}
|
|
940
|
+
if (s.updatedBefore != null) {
|
|
941
|
+
merged.updatedBefore = Math.min(merged.updatedBefore ?? Infinity, s.updatedBefore);
|
|
942
|
+
}
|
|
943
|
+
if (s.createdAfter != null) {
|
|
944
|
+
merged.createdAfter = Math.max(merged.createdAfter ?? 0, s.createdAfter);
|
|
945
|
+
}
|
|
946
|
+
if (s.createdBefore != null) {
|
|
947
|
+
merged.createdBefore = Math.min(merged.createdBefore ?? Infinity, s.createdBefore);
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
return merged;
|
|
951
|
+
};
|
|
952
|
+
|
|
547
953
|
const isTrivialTypenameFilter = (filter: QueryAST.Filter): boolean => {
|
|
548
954
|
return (
|
|
549
955
|
filter.type === 'object' &&
|
|
@@ -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
|
},
|