@dxos/echo-protocol 0.8.3 → 0.8.4-main.1c7ec43d41
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/neutral/index.mjs +816 -0
- package/dist/lib/neutral/index.mjs.map +7 -0
- package/dist/lib/neutral/meta.json +1 -0
- package/dist/types/src/document-structure.d.ts +16 -2
- package/dist/types/src/document-structure.d.ts.map +1 -1
- package/dist/types/src/echo-feed-codec.d.ts +17 -0
- package/dist/types/src/echo-feed-codec.d.ts.map +1 -0
- package/dist/types/src/foreign-key.d.ts +1 -1
- package/dist/types/src/foreign-key.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +4 -3
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/query/ast.d.ts +229 -25
- package/dist/types/src/query/ast.d.ts.map +1 -1
- package/dist/types/src/reference.d.ts +17 -0
- package/dist/types/src/reference.d.ts.map +1 -1
- package/dist/types/src/space-id.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +14 -14
- package/src/document-structure.ts +24 -2
- package/src/echo-feed-codec.ts +67 -0
- package/src/foreign-key.ts +4 -3
- package/src/index.ts +4 -3
- package/src/query/ast.ts +315 -34
- package/src/reference.ts +29 -0
- package/src/space-id.ts +1 -1
- package/dist/lib/browser/index.mjs +0 -509
- package/dist/lib/browser/index.mjs.map +0 -7
- package/dist/lib/browser/meta.json +0 -1
- package/dist/lib/node/index.cjs +0 -527
- package/dist/lib/node/index.cjs.map +0 -7
- package/dist/lib/node/meta.json +0 -1
- package/dist/lib/node-esm/index.mjs +0 -510
- package/dist/lib/node-esm/index.mjs.map +0 -7
- package/dist/lib/node-esm/meta.json +0 -1
|
@@ -8,7 +8,7 @@ import { visitValues } from '@dxos/util';
|
|
|
8
8
|
|
|
9
9
|
import { type RawString } from './automerge';
|
|
10
10
|
import type { ForeignKey } from './foreign-key';
|
|
11
|
-
import {
|
|
11
|
+
import { type EncodedReference, isEncodedReference } from './reference';
|
|
12
12
|
import { type SpaceDocVersion } from './space-doc-version';
|
|
13
13
|
|
|
14
14
|
export type SpaceState = {
|
|
@@ -137,6 +137,10 @@ export const ObjectStructure = Object.freeze({
|
|
|
137
137
|
return object.system?.target;
|
|
138
138
|
},
|
|
139
139
|
|
|
140
|
+
getParent: (object: ObjectStructure): EncodedReference | undefined => {
|
|
141
|
+
return object.system?.parent;
|
|
142
|
+
},
|
|
143
|
+
|
|
140
144
|
/**
|
|
141
145
|
* @returns All references in the data section of the object.
|
|
142
146
|
*/
|
|
@@ -153,6 +157,10 @@ export const ObjectStructure = Object.freeze({
|
|
|
153
157
|
return references;
|
|
154
158
|
},
|
|
155
159
|
|
|
160
|
+
getTags: (object: ObjectStructure): string[] => {
|
|
161
|
+
return object.meta.tags ?? [];
|
|
162
|
+
},
|
|
163
|
+
|
|
156
164
|
makeObject: ({
|
|
157
165
|
type,
|
|
158
166
|
data,
|
|
@@ -214,6 +222,14 @@ export type ObjectMeta = {
|
|
|
214
222
|
* Foreign keys.
|
|
215
223
|
*/
|
|
216
224
|
keys: ForeignKey[];
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Tags.
|
|
228
|
+
* An array of DXNs of Tag objects within the space.
|
|
229
|
+
*
|
|
230
|
+
* NOTE: Optional for backwards compatibilty.
|
|
231
|
+
*/
|
|
232
|
+
tags?: string[];
|
|
217
233
|
};
|
|
218
234
|
|
|
219
235
|
/**
|
|
@@ -236,13 +252,19 @@ export type ObjectSystem = {
|
|
|
236
252
|
*/
|
|
237
253
|
deleted?: boolean;
|
|
238
254
|
|
|
255
|
+
/**
|
|
256
|
+
* Object parent.
|
|
257
|
+
* Objects with no parent are at the top level of the object hierarchy in the space.
|
|
258
|
+
*/
|
|
259
|
+
parent?: EncodedReference;
|
|
260
|
+
|
|
239
261
|
/**
|
|
240
262
|
* Only for relations.
|
|
241
263
|
*/
|
|
242
264
|
source?: EncodedReference;
|
|
243
265
|
|
|
244
266
|
/**
|
|
245
|
-
* Only for relations.
|
|
267
|
+
* Only for relations.
|
|
246
268
|
*/
|
|
247
269
|
target?: EncodedReference;
|
|
248
270
|
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { FeedProtocol } from '@dxos/protocols';
|
|
6
|
+
|
|
7
|
+
import type { ForeignKey } from './foreign-key';
|
|
8
|
+
|
|
9
|
+
/** Property name for meta when object is serialized to JSON. Matches @dxos/echo/internal ATTR_META. */
|
|
10
|
+
const ATTR_META = '@meta';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Codec for ECHO objects in feed block payload: JSON object ↔ UTF-8 bytes.
|
|
14
|
+
* Encodes with queue position stripped; decodes with optional position injection.
|
|
15
|
+
*/
|
|
16
|
+
export class EchoFeedCodec {
|
|
17
|
+
static readonly #encoder = new TextEncoder();
|
|
18
|
+
static readonly #decoder = new TextDecoder();
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Prepares a value for feed storage (strips queue position from metadata) and encodes to bytes.
|
|
22
|
+
*/
|
|
23
|
+
static encode(value: Record<string, unknown>): Uint8Array {
|
|
24
|
+
const prepared = EchoFeedCodec.#stripQueuePosition(value);
|
|
25
|
+
return EchoFeedCodec.#encoder.encode(JSON.stringify(prepared));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Decodes feed block bytes to a JSON value.
|
|
30
|
+
* If position is provided, injects queue position into the decoded object's metadata.
|
|
31
|
+
*/
|
|
32
|
+
static decode(data: Uint8Array, position?: number): Record<string, unknown> {
|
|
33
|
+
const decoded = JSON.parse(EchoFeedCodec.#decoder.decode(data));
|
|
34
|
+
if (position !== undefined && typeof decoded === 'object' && decoded !== null) {
|
|
35
|
+
EchoFeedCodec.#setQueuePosition(decoded, position);
|
|
36
|
+
}
|
|
37
|
+
return decoded;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
static #stripQueuePosition(value: Record<string, unknown>): Record<string, unknown> {
|
|
41
|
+
if (typeof value !== 'object' || value === null) {
|
|
42
|
+
return value;
|
|
43
|
+
}
|
|
44
|
+
const obj = structuredClone(value);
|
|
45
|
+
const meta = obj[ATTR_META] as { keys?: ForeignKey[] } | undefined;
|
|
46
|
+
if (meta?.keys?.some((key: ForeignKey) => key.source === FeedProtocol.KEY_QUEUE_POSITION)) {
|
|
47
|
+
meta.keys = meta.keys.filter((key: ForeignKey) => key.source !== FeedProtocol.KEY_QUEUE_POSITION);
|
|
48
|
+
}
|
|
49
|
+
return obj;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
static #setQueuePosition(obj: Record<string, any>, position: number): void {
|
|
53
|
+
obj[ATTR_META] ??= { keys: [] };
|
|
54
|
+
obj[ATTR_META]!.keys ??= [];
|
|
55
|
+
const keys = obj[ATTR_META]!.keys!;
|
|
56
|
+
for (let i = 0; i < keys.length; i++) {
|
|
57
|
+
if (keys[i].source === FeedProtocol.KEY_QUEUE_POSITION) {
|
|
58
|
+
keys.splice(i, 1);
|
|
59
|
+
i--;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
keys.push({
|
|
63
|
+
source: FeedProtocol.KEY_QUEUE_POSITION,
|
|
64
|
+
id: position.toString(),
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
package/src/foreign-key.ts
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import * as Schema from 'effect/Schema';
|
|
6
|
+
import * as SchemaAST from 'effect/SchemaAST';
|
|
6
7
|
|
|
7
8
|
const ForeignKey_ = Schema.Struct({
|
|
8
9
|
/**
|
|
@@ -15,8 +16,8 @@ const ForeignKey_ = Schema.Struct({
|
|
|
15
16
|
* Id within the foreign database.
|
|
16
17
|
*/
|
|
17
18
|
// TODO(wittjosiah): This annotation is currently used to ensure id field shows up in forms.
|
|
18
|
-
// TODO(dmaretskyi): `false` is not a valid value for the annotation.
|
|
19
|
-
id: Schema.String.annotations({ [SchemaAST.IdentifierAnnotationId]: false }),
|
|
19
|
+
// TODO(dmaretskyi): `false` is not a valid value for the annotation. Use a different annotation.
|
|
20
|
+
id: Schema.String.annotations({ [SchemaAST.IdentifierAnnotationId]: 'false' }),
|
|
20
21
|
});
|
|
21
22
|
|
|
22
23
|
export type ForeignKey = Schema.Schema.Type<typeof ForeignKey_>;
|
package/src/index.ts
CHANGED
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
export type * from './collection-sync';
|
|
5
6
|
export * from './document-structure';
|
|
7
|
+
export * from './echo-feed-codec';
|
|
8
|
+
export * from './foreign-key';
|
|
9
|
+
export * from './query';
|
|
6
10
|
export * from './reference';
|
|
7
11
|
export * from './space-doc-version';
|
|
8
|
-
export * from './collection-sync';
|
|
9
12
|
export * from './space-id';
|
|
10
|
-
export * from './foreign-key';
|
|
11
|
-
export * from './query';
|
package/src/query/ast.ts
CHANGED
|
@@ -2,14 +2,15 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import * as Match from 'effect/Match';
|
|
6
|
+
import * as Schema from 'effect/Schema';
|
|
6
7
|
|
|
7
8
|
import { DXN, ObjectId } from '@dxos/keys';
|
|
8
9
|
|
|
9
10
|
import { ForeignKey } from '../foreign-key';
|
|
10
11
|
|
|
11
12
|
const TypenameSpecifier = Schema.Union(DXN.Schema, Schema.Null).annotations({
|
|
12
|
-
description: 'DXN or null
|
|
13
|
+
description: 'DXN or null; null matches any type',
|
|
13
14
|
});
|
|
14
15
|
|
|
15
16
|
// NOTE: This pattern with 3 definitions per schema is need to make the types opaque, and circular references in AST to not cause compiler errors.
|
|
@@ -46,6 +47,9 @@ const FilterObject_ = Schema.Struct({
|
|
|
46
47
|
export interface FilterObject extends Schema.Schema.Type<typeof FilterObject_> {}
|
|
47
48
|
export const FilterObject: Schema.Schema<FilterObject> = FilterObject_;
|
|
48
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Compare.
|
|
52
|
+
*/
|
|
49
53
|
const FilterCompare_ = Schema.Struct({
|
|
50
54
|
type: Schema.Literal('compare'),
|
|
51
55
|
operator: Schema.Literal('eq', 'neq', 'gt', 'gte', 'lt', 'lte'),
|
|
@@ -54,6 +58,9 @@ const FilterCompare_ = Schema.Struct({
|
|
|
54
58
|
export interface FilterCompare extends Schema.Schema.Type<typeof FilterCompare_> {}
|
|
55
59
|
export const FilterCompare: Schema.Schema<FilterCompare> = FilterCompare_;
|
|
56
60
|
|
|
61
|
+
/**
|
|
62
|
+
* In.
|
|
63
|
+
*/
|
|
57
64
|
const FilterIn_ = Schema.Struct({
|
|
58
65
|
type: Schema.Literal('in'),
|
|
59
66
|
values: Schema.Array(Schema.Any),
|
|
@@ -61,53 +68,137 @@ const FilterIn_ = Schema.Struct({
|
|
|
61
68
|
export interface FilterIn extends Schema.Schema.Type<typeof FilterIn_> {}
|
|
62
69
|
export const FilterIn: Schema.Schema<FilterIn> = FilterIn_;
|
|
63
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Contains.
|
|
73
|
+
*/
|
|
74
|
+
const FilterContains_ = Schema.Struct({
|
|
75
|
+
type: Schema.Literal('contains'),
|
|
76
|
+
value: Schema.Any,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
export interface FilterContains extends Schema.Schema.Type<typeof FilterContains_> {}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Predicate for an array property to contain the provided value.
|
|
83
|
+
* Nested objects are matched using strict structural matching.
|
|
84
|
+
*/
|
|
85
|
+
export const FilterContains: Schema.Schema<FilterContains> = FilterContains_;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Filters objects that have certain tag.
|
|
89
|
+
*/
|
|
90
|
+
const FilterTag_ = Schema.Struct({
|
|
91
|
+
type: Schema.Literal('tag'),
|
|
92
|
+
tag: Schema.String, // TODO(burdon): Make OR-collection?
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
export interface FilterTag extends Schema.Schema.Type<typeof FilterTag_> {}
|
|
96
|
+
export const FilterTag: Schema.Schema<FilterTag> = FilterTag_;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Range.
|
|
100
|
+
*/
|
|
64
101
|
const FilterRange_ = Schema.Struct({
|
|
65
102
|
type: Schema.Literal('range'),
|
|
66
103
|
from: Schema.Any,
|
|
67
104
|
to: Schema.Any,
|
|
68
105
|
});
|
|
106
|
+
|
|
69
107
|
export interface FilterRange extends Schema.Schema.Type<typeof FilterRange_> {}
|
|
70
108
|
export const FilterRange: Schema.Schema<FilterRange> = FilterRange_;
|
|
71
109
|
|
|
110
|
+
/**
|
|
111
|
+
* Filter by system timestamp (createdAt / updatedAt).
|
|
112
|
+
* Timestamps are unix milliseconds stored in the object meta index.
|
|
113
|
+
*/
|
|
114
|
+
const FilterTimestamp_ = Schema.Struct({
|
|
115
|
+
type: Schema.Literal('timestamp'),
|
|
116
|
+
field: Schema.Literal('createdAt', 'updatedAt'),
|
|
117
|
+
operator: Schema.Literal('gt', 'gte', 'lt', 'lte'),
|
|
118
|
+
value: Schema.Number,
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
export interface FilterTimestamp extends Schema.Schema.Type<typeof FilterTimestamp_> {}
|
|
122
|
+
export const FilterTimestamp: Schema.Schema<FilterTimestamp> = FilterTimestamp_;
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Text search.
|
|
126
|
+
*/
|
|
72
127
|
const FilterTextSearch_ = Schema.Struct({
|
|
73
128
|
type: Schema.Literal('text-search'),
|
|
74
129
|
text: Schema.String,
|
|
75
130
|
searchKind: Schema.optional(Schema.Literal('full-text', 'vector')),
|
|
76
131
|
});
|
|
132
|
+
|
|
77
133
|
export interface FilterTextSearch extends Schema.Schema.Type<typeof FilterTextSearch_> {}
|
|
78
134
|
export const FilterTextSearch: Schema.Schema<FilterTextSearch> = FilterTextSearch_;
|
|
79
135
|
|
|
136
|
+
/**
|
|
137
|
+
* Not.
|
|
138
|
+
*/
|
|
80
139
|
const FilterNot_ = Schema.Struct({
|
|
81
140
|
type: Schema.Literal('not'),
|
|
82
141
|
filter: Schema.suspend(() => Filter),
|
|
83
142
|
});
|
|
143
|
+
|
|
84
144
|
export interface FilterNot extends Schema.Schema.Type<typeof FilterNot_> {}
|
|
85
145
|
export const FilterNot: Schema.Schema<FilterNot> = FilterNot_;
|
|
86
146
|
|
|
147
|
+
/**
|
|
148
|
+
* And.
|
|
149
|
+
*/
|
|
87
150
|
const FilterAnd_ = Schema.Struct({
|
|
88
151
|
type: Schema.Literal('and'),
|
|
89
152
|
filters: Schema.Array(Schema.suspend(() => Filter)),
|
|
90
153
|
});
|
|
154
|
+
|
|
91
155
|
export interface FilterAnd extends Schema.Schema.Type<typeof FilterAnd_> {}
|
|
92
156
|
export const FilterAnd: Schema.Schema<FilterAnd> = FilterAnd_;
|
|
93
157
|
|
|
158
|
+
/**
|
|
159
|
+
* Or.
|
|
160
|
+
*/
|
|
94
161
|
const FilterOr_ = Schema.Struct({
|
|
95
162
|
type: Schema.Literal('or'),
|
|
96
163
|
filters: Schema.Array(Schema.suspend(() => Filter)),
|
|
97
164
|
});
|
|
165
|
+
|
|
98
166
|
export interface FilterOr extends Schema.Schema.Type<typeof FilterOr_> {}
|
|
99
167
|
export const FilterOr: Schema.Schema<FilterOr> = FilterOr_;
|
|
100
168
|
|
|
169
|
+
/**
|
|
170
|
+
* Filter objects that are children of the specified parents.
|
|
171
|
+
* With transitive=true (default), matches grandchildren and beyond.
|
|
172
|
+
*/
|
|
173
|
+
const FilterChildOf_ = Schema.Struct({
|
|
174
|
+
type: Schema.Literal('child-of'),
|
|
175
|
+
/** Parent DXNs to match children of. */
|
|
176
|
+
parents: Schema.Array(DXN.Schema),
|
|
177
|
+
/** Whether to match transitively (grandchildren, etc.). Defaults to true. */
|
|
178
|
+
transitive: Schema.Boolean,
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
export interface FilterChildOf extends Schema.Schema.Type<typeof FilterChildOf_> {}
|
|
182
|
+
export const FilterChildOf: Schema.Schema<FilterChildOf> = FilterChildOf_;
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Union of filters.
|
|
186
|
+
*/
|
|
101
187
|
export const Filter = Schema.Union(
|
|
102
188
|
FilterObject,
|
|
103
|
-
FilterTextSearch,
|
|
104
189
|
FilterCompare,
|
|
105
190
|
FilterIn,
|
|
191
|
+
FilterContains,
|
|
192
|
+
FilterTag,
|
|
106
193
|
FilterRange,
|
|
194
|
+
FilterTimestamp,
|
|
195
|
+
FilterTextSearch,
|
|
196
|
+
FilterChildOf,
|
|
107
197
|
FilterNot,
|
|
108
198
|
FilterAnd,
|
|
109
199
|
FilterOr,
|
|
110
|
-
);
|
|
200
|
+
).annotations({ identifier: 'org.dxos.schema.filter' });
|
|
201
|
+
|
|
111
202
|
export type Filter = Schema.Schema.Type<typeof Filter>;
|
|
112
203
|
|
|
113
204
|
/**
|
|
@@ -117,6 +208,7 @@ const QuerySelectClause_ = Schema.Struct({
|
|
|
117
208
|
type: Schema.Literal('select'),
|
|
118
209
|
filter: Schema.suspend(() => Filter),
|
|
119
210
|
});
|
|
211
|
+
|
|
120
212
|
export interface QuerySelectClause extends Schema.Schema.Type<typeof QuerySelectClause_> {}
|
|
121
213
|
export const QuerySelectClause: Schema.Schema<QuerySelectClause> = QuerySelectClause_;
|
|
122
214
|
|
|
@@ -128,6 +220,7 @@ const QueryFilterClause_ = Schema.Struct({
|
|
|
128
220
|
selection: Schema.suspend(() => Query),
|
|
129
221
|
filter: Schema.suspend(() => Filter),
|
|
130
222
|
});
|
|
223
|
+
|
|
131
224
|
export interface QueryFilterClause extends Schema.Schema.Type<typeof QueryFilterClause_> {}
|
|
132
225
|
export const QueryFilterClause: Schema.Schema<QueryFilterClause> = QueryFilterClause_;
|
|
133
226
|
|
|
@@ -139,6 +232,7 @@ const QueryReferenceTraversalClause_ = Schema.Struct({
|
|
|
139
232
|
anchor: Schema.suspend(() => Query),
|
|
140
233
|
property: Schema.String, // TODO(dmaretskyi): Change to EscapedPropPath.
|
|
141
234
|
});
|
|
235
|
+
|
|
142
236
|
export interface QueryReferenceTraversalClause extends Schema.Schema.Type<typeof QueryReferenceTraversalClause_> {}
|
|
143
237
|
export const QueryReferenceTraversalClause: Schema.Schema<QueryReferenceTraversalClause> =
|
|
144
238
|
QueryReferenceTraversalClause_;
|
|
@@ -149,9 +243,14 @@ export const QueryReferenceTraversalClause: Schema.Schema<QueryReferenceTraversa
|
|
|
149
243
|
const QueryIncomingReferencesClause_ = Schema.Struct({
|
|
150
244
|
type: Schema.Literal('incoming-references'),
|
|
151
245
|
anchor: Schema.suspend(() => Query),
|
|
152
|
-
|
|
246
|
+
/**
|
|
247
|
+
* Property path where the reference is located.
|
|
248
|
+
* If null, matches references from any property.
|
|
249
|
+
*/
|
|
250
|
+
property: Schema.NullOr(Schema.String),
|
|
153
251
|
typename: TypenameSpecifier,
|
|
154
252
|
});
|
|
253
|
+
|
|
155
254
|
export interface QueryIncomingReferencesClause extends Schema.Schema.Type<typeof QueryIncomingReferencesClause_> {}
|
|
156
255
|
export const QueryIncomingReferencesClause: Schema.Schema<QueryIncomingReferencesClause> =
|
|
157
256
|
QueryIncomingReferencesClause_;
|
|
@@ -170,6 +269,7 @@ const QueryRelationClause_ = Schema.Struct({
|
|
|
170
269
|
direction: Schema.Literal('outgoing', 'incoming', 'both'),
|
|
171
270
|
filter: Schema.optional(Schema.suspend(() => Filter)),
|
|
172
271
|
});
|
|
272
|
+
|
|
173
273
|
export interface QueryRelationClause extends Schema.Schema.Type<typeof QueryRelationClause_> {}
|
|
174
274
|
export const QueryRelationClause: Schema.Schema<QueryRelationClause> = QueryRelationClause_;
|
|
175
275
|
|
|
@@ -181,9 +281,27 @@ const QueryRelationTraversalClause_ = Schema.Struct({
|
|
|
181
281
|
anchor: Schema.suspend(() => Query),
|
|
182
282
|
direction: Schema.Literal('source', 'target', 'both'),
|
|
183
283
|
});
|
|
284
|
+
|
|
184
285
|
export interface QueryRelationTraversalClause extends Schema.Schema.Type<typeof QueryRelationTraversalClause_> {}
|
|
185
286
|
export const QueryRelationTraversalClause: Schema.Schema<QueryRelationTraversalClause> = QueryRelationTraversalClause_;
|
|
186
287
|
|
|
288
|
+
/**
|
|
289
|
+
* Traverse parent-child hierarchy.
|
|
290
|
+
*/
|
|
291
|
+
const QueryHierarchyTraversalClause_ = Schema.Struct({
|
|
292
|
+
type: Schema.Literal('hierarchy-traversal'),
|
|
293
|
+
anchor: Schema.suspend(() => Query),
|
|
294
|
+
/**
|
|
295
|
+
* to-parent: traverse from child to parent.
|
|
296
|
+
* to-children: traverse from parent to children.
|
|
297
|
+
*/
|
|
298
|
+
direction: Schema.Literal('to-parent', 'to-children'),
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
export interface QueryHierarchyTraversalClause extends Schema.Schema.Type<typeof QueryHierarchyTraversalClause_> {}
|
|
302
|
+
export const QueryHierarchyTraversalClause: Schema.Schema<QueryHierarchyTraversalClause> =
|
|
303
|
+
QueryHierarchyTraversalClause_;
|
|
304
|
+
|
|
187
305
|
/**
|
|
188
306
|
* Union of multiple queries.
|
|
189
307
|
*/
|
|
@@ -191,6 +309,7 @@ const QueryUnionClause_ = Schema.Struct({
|
|
|
191
309
|
type: Schema.Literal('union'),
|
|
192
310
|
queries: Schema.Array(Schema.suspend(() => Query)),
|
|
193
311
|
});
|
|
312
|
+
|
|
194
313
|
export interface QueryUnionClause extends Schema.Schema.Type<typeof QueryUnionClause_> {}
|
|
195
314
|
export const QueryUnionClause: Schema.Schema<QueryUnionClause> = QueryUnionClause_;
|
|
196
315
|
|
|
@@ -202,9 +321,47 @@ const QuerySetDifferenceClause_ = Schema.Struct({
|
|
|
202
321
|
source: Schema.suspend(() => Query),
|
|
203
322
|
exclude: Schema.suspend(() => Query),
|
|
204
323
|
});
|
|
324
|
+
|
|
205
325
|
export interface QuerySetDifferenceClause extends Schema.Schema.Type<typeof QuerySetDifferenceClause_> {}
|
|
206
326
|
export const QuerySetDifferenceClause: Schema.Schema<QuerySetDifferenceClause> = QuerySetDifferenceClause_;
|
|
207
327
|
|
|
328
|
+
export const OrderDirection = Schema.Literal('asc', 'desc');
|
|
329
|
+
export type OrderDirection = Schema.Schema.Type<typeof OrderDirection>;
|
|
330
|
+
|
|
331
|
+
const Order_ = Schema.Union(
|
|
332
|
+
Schema.Struct({
|
|
333
|
+
// How database wants to order them (in practice - by id).
|
|
334
|
+
kind: Schema.Literal('natural'),
|
|
335
|
+
}),
|
|
336
|
+
Schema.Struct({
|
|
337
|
+
kind: Schema.Literal('property'),
|
|
338
|
+
property: Schema.String,
|
|
339
|
+
direction: OrderDirection,
|
|
340
|
+
}),
|
|
341
|
+
Schema.Struct({
|
|
342
|
+
// Order by relevance rank (for FTS/vector search results).
|
|
343
|
+
// Default direction is 'desc' (higher rank = better match first).
|
|
344
|
+
kind: Schema.Literal('rank'),
|
|
345
|
+
direction: OrderDirection,
|
|
346
|
+
}),
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
export type Order = Schema.Schema.Type<typeof Order_>;
|
|
350
|
+
export const Order: Schema.Schema<Order> = Order_;
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Order the query results.
|
|
354
|
+
* Left-to-right the orders dominate.
|
|
355
|
+
*/
|
|
356
|
+
const QueryOrderClause_ = Schema.Struct({
|
|
357
|
+
type: Schema.Literal('order'),
|
|
358
|
+
query: Schema.suspend(() => Query),
|
|
359
|
+
order: Schema.Array(Order),
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
export interface QueryOrderClause extends Schema.Schema.Type<typeof QueryOrderClause_> {}
|
|
363
|
+
export const QueryOrderClause: Schema.Schema<QueryOrderClause> = QueryOrderClause_;
|
|
364
|
+
|
|
208
365
|
/**
|
|
209
366
|
* Add options to a query.
|
|
210
367
|
*/
|
|
@@ -213,9 +370,37 @@ const QueryOptionsClause_ = Schema.Struct({
|
|
|
213
370
|
query: Schema.suspend(() => Query),
|
|
214
371
|
options: Schema.suspend(() => QueryOptions),
|
|
215
372
|
});
|
|
373
|
+
|
|
216
374
|
export interface QueryOptionsClause extends Schema.Schema.Type<typeof QueryOptionsClause_> {}
|
|
217
375
|
export const QueryOptionsClause: Schema.Schema<QueryOptionsClause> = QueryOptionsClause_;
|
|
218
376
|
|
|
377
|
+
/**
|
|
378
|
+
* Limit the number of results.
|
|
379
|
+
*/
|
|
380
|
+
const QueryLimitClause_ = Schema.Struct({
|
|
381
|
+
type: Schema.Literal('limit'),
|
|
382
|
+
query: Schema.suspend(() => Query),
|
|
383
|
+
limit: Schema.Number,
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
export interface QueryLimitClause extends Schema.Schema.Type<typeof QueryLimitClause_> {}
|
|
387
|
+
export const QueryLimitClause: Schema.Schema<QueryLimitClause> = QueryLimitClause_;
|
|
388
|
+
|
|
389
|
+
export const QueryFromClause_ = Schema.Struct({
|
|
390
|
+
type: Schema.Literal('from'),
|
|
391
|
+
query: Schema.suspend(() => Query),
|
|
392
|
+
from: Schema.Union(
|
|
393
|
+
Schema.TaggedStruct('scope', {
|
|
394
|
+
scope: Schema.suspend(() => Scope),
|
|
395
|
+
}),
|
|
396
|
+
Schema.TaggedStruct('query', {
|
|
397
|
+
query: Schema.suspend(() => Query),
|
|
398
|
+
}),
|
|
399
|
+
),
|
|
400
|
+
});
|
|
401
|
+
export interface QueryFromClause extends Schema.Schema.Type<typeof QueryFromClause_> {}
|
|
402
|
+
export const QueryFromClause: Schema.Schema<QueryFromClause> = QueryFromClause_;
|
|
403
|
+
|
|
219
404
|
const Query_ = Schema.Union(
|
|
220
405
|
QuerySelectClause,
|
|
221
406
|
QueryFilterClause,
|
|
@@ -223,46 +408,142 @@ const Query_ = Schema.Union(
|
|
|
223
408
|
QueryIncomingReferencesClause,
|
|
224
409
|
QueryRelationClause,
|
|
225
410
|
QueryRelationTraversalClause,
|
|
411
|
+
QueryHierarchyTraversalClause,
|
|
226
412
|
QueryUnionClause,
|
|
227
413
|
QuerySetDifferenceClause,
|
|
414
|
+
QueryOrderClause,
|
|
228
415
|
QueryOptionsClause,
|
|
229
|
-
|
|
416
|
+
QueryLimitClause,
|
|
417
|
+
QueryFromClause,
|
|
418
|
+
).annotations({ identifier: 'org.dxos.schema.query' });
|
|
230
419
|
|
|
231
420
|
export type Query = Schema.Schema.Type<typeof Query_>;
|
|
232
421
|
export const Query: Schema.Schema<Query> = Query_;
|
|
233
422
|
|
|
234
423
|
export const QueryOptions = Schema.Struct({
|
|
235
|
-
|
|
424
|
+
/**
|
|
425
|
+
* Nested select statements will use this option to filter deleted objects.
|
|
426
|
+
*/
|
|
236
427
|
deleted: Schema.optional(Schema.Literal('include', 'exclude', 'only')),
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Diagnostics-only label for logs / tooling (not used by execution semantics).
|
|
431
|
+
*/
|
|
432
|
+
debugLabel: Schema.optional(Schema.String),
|
|
237
433
|
});
|
|
434
|
+
|
|
238
435
|
export interface QueryOptions extends Schema.Schema.Type<typeof QueryOptions> {}
|
|
239
436
|
|
|
437
|
+
/**
|
|
438
|
+
* Specifies the scope of the data to query from.
|
|
439
|
+
*/
|
|
440
|
+
export const Scope = Schema.Struct({
|
|
441
|
+
/**
|
|
442
|
+
* The nested select statemets will select from the given spaces.
|
|
443
|
+
*
|
|
444
|
+
* NOTE: Spaces and queues are unioned together if both are specified.
|
|
445
|
+
*/
|
|
446
|
+
spaceIds: Schema.optional(Schema.Array(Schema.String)),
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* If true, the nested select statements will select from all queues in the spaces specified by `spaceIds`.
|
|
450
|
+
*/
|
|
451
|
+
allQueuesFromSpaces: Schema.optional(Schema.Boolean),
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* The nested select statemets will select from the given queues.
|
|
455
|
+
*
|
|
456
|
+
* NOTE: Spaces and queues are unioned together if both are specified.
|
|
457
|
+
*/
|
|
458
|
+
queues: Schema.optional(Schema.Array(DXN.Schema)),
|
|
459
|
+
});
|
|
460
|
+
export interface Scope extends Schema.Schema.Type<typeof Scope> {}
|
|
461
|
+
|
|
240
462
|
export const visit = (query: Query, visitor: (node: Query) => void) => {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
463
|
+
visitor(query);
|
|
464
|
+
|
|
465
|
+
Match.value(query).pipe(
|
|
466
|
+
Match.when({ type: 'filter' }, ({ selection }) => visit(selection, visitor)),
|
|
467
|
+
Match.when({ type: 'reference-traversal' }, ({ anchor }) => visit(anchor, visitor)),
|
|
468
|
+
Match.when({ type: 'incoming-references' }, ({ anchor }) => visit(anchor, visitor)),
|
|
469
|
+
Match.when({ type: 'relation' }, ({ anchor }) => visit(anchor, visitor)),
|
|
470
|
+
Match.when({ type: 'options' }, ({ query }) => visit(query, visitor)),
|
|
471
|
+
Match.when({ type: 'relation-traversal' }, ({ anchor }) => visit(anchor, visitor)),
|
|
472
|
+
Match.when({ type: 'hierarchy-traversal' }, ({ anchor }) => visit(anchor, visitor)),
|
|
473
|
+
Match.when({ type: 'union' }, ({ queries }) => queries.forEach((q) => visit(q, visitor))),
|
|
474
|
+
Match.when({ type: 'set-difference' }, ({ source, exclude }) => {
|
|
475
|
+
visit(source, visitor);
|
|
476
|
+
visit(exclude, visitor);
|
|
477
|
+
}),
|
|
478
|
+
Match.when({ type: 'order' }, ({ query }) => visit(query, visitor)),
|
|
479
|
+
Match.when({ type: 'limit' }, ({ query }) => visit(query, visitor)),
|
|
480
|
+
Match.when({ type: 'from' }, (node) => {
|
|
481
|
+
visit(node.query, visitor);
|
|
482
|
+
if (node.from._tag === 'query') {
|
|
483
|
+
visit(node.from.query, visitor);
|
|
484
|
+
}
|
|
485
|
+
}),
|
|
486
|
+
Match.when({ type: 'select' }, () => {}),
|
|
487
|
+
Match.exhaustive,
|
|
488
|
+
);
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Recursively transforms a query tree bottom-up.
|
|
493
|
+
* The mapper receives each node with its children already transformed.
|
|
494
|
+
*/
|
|
495
|
+
export const map = (query: Query, mapper: (node: Query) => Query): Query => {
|
|
496
|
+
const mapped: Query = Match.value(query).pipe(
|
|
497
|
+
Match.when({ type: 'filter' }, (node) => ({ ...node, selection: map(node.selection, mapper) })),
|
|
498
|
+
Match.when({ type: 'reference-traversal' }, (node) => ({ ...node, anchor: map(node.anchor, mapper) })),
|
|
499
|
+
Match.when({ type: 'incoming-references' }, (node) => ({ ...node, anchor: map(node.anchor, mapper) })),
|
|
500
|
+
Match.when({ type: 'relation' }, (node) => ({ ...node, anchor: map(node.anchor, mapper) })),
|
|
501
|
+
Match.when({ type: 'relation-traversal' }, (node) => ({ ...node, anchor: map(node.anchor, mapper) })),
|
|
502
|
+
Match.when({ type: 'hierarchy-traversal' }, (node) => ({ ...node, anchor: map(node.anchor, mapper) })),
|
|
503
|
+
Match.when({ type: 'options' }, (node) => ({ ...node, query: map(node.query, mapper) })),
|
|
504
|
+
Match.when({ type: 'order' }, (node) => ({ ...node, query: map(node.query, mapper) })),
|
|
505
|
+
Match.when({ type: 'limit' }, (node) => ({ ...node, query: map(node.query, mapper) })),
|
|
506
|
+
Match.when({ type: 'from' }, (node) => ({
|
|
507
|
+
...node,
|
|
508
|
+
query: map(node.query, mapper),
|
|
509
|
+
...(node.from._tag === 'query' ? { from: { _tag: 'query' as const, query: map(node.from.query, mapper) } } : {}),
|
|
510
|
+
})),
|
|
511
|
+
Match.when({ type: 'union' }, (node) => ({ ...node, queries: node.queries.map((q) => map(q, mapper)) })),
|
|
512
|
+
Match.when({ type: 'set-difference' }, (node) => ({
|
|
513
|
+
...node,
|
|
514
|
+
source: map(node.source, mapper),
|
|
515
|
+
exclude: map(node.exclude, mapper),
|
|
516
|
+
})),
|
|
517
|
+
Match.when({ type: 'select' }, (node) => node),
|
|
518
|
+
Match.exhaustive,
|
|
519
|
+
);
|
|
520
|
+
return mapper(mapped);
|
|
521
|
+
};
|
|
522
|
+
|
|
523
|
+
export const fold = <T>(query: Query, reducer: (node: Query) => T): T[] => {
|
|
524
|
+
return Match.value(query).pipe(
|
|
525
|
+
Match.withReturnType<T[]>(),
|
|
526
|
+
Match.when({ type: 'filter' }, ({ selection }) => fold(selection, reducer)),
|
|
527
|
+
Match.when({ type: 'reference-traversal' }, ({ anchor }) => fold(anchor, reducer)),
|
|
528
|
+
Match.when({ type: 'incoming-references' }, ({ anchor }) => fold(anchor, reducer)),
|
|
529
|
+
Match.when({ type: 'relation' }, ({ anchor }) => fold(anchor, reducer)),
|
|
530
|
+
Match.when({ type: 'options' }, ({ query }) => fold(query, reducer)),
|
|
531
|
+
Match.when({ type: 'relation-traversal' }, ({ anchor }) => fold(anchor, reducer)),
|
|
532
|
+
Match.when({ type: 'hierarchy-traversal' }, ({ anchor }) => fold(anchor, reducer)),
|
|
533
|
+
Match.when({ type: 'union' }, ({ queries }) => queries.flatMap((q) => fold(q, reducer))),
|
|
534
|
+
Match.when({ type: 'set-difference' }, ({ source, exclude }) =>
|
|
535
|
+
fold(source, reducer).concat(fold(exclude, reducer)),
|
|
536
|
+
),
|
|
537
|
+
Match.when({ type: 'order' }, ({ query }) => fold(query, reducer)),
|
|
538
|
+
Match.when({ type: 'limit' }, ({ query }) => fold(query, reducer)),
|
|
539
|
+
Match.when({ type: 'from' }, (node) => {
|
|
540
|
+
const results = fold(node.query, reducer);
|
|
541
|
+
if (node.from._tag === 'query') {
|
|
542
|
+
return results.concat(fold(node.from.query, reducer));
|
|
543
|
+
}
|
|
544
|
+
return results;
|
|
545
|
+
}),
|
|
546
|
+
Match.when({ type: 'select' }, () => []),
|
|
547
|
+
Match.exhaustive,
|
|
548
|
+
);
|
|
268
549
|
};
|