@dxos/echo-protocol 0.8.4-main.ead640a → 0.8.4-main.f466a3d56e
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 → neutral}/index.mjs +303 -80
- 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 +18 -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/edge-peer.d.ts +11 -0
- package/dist/types/src/edge-peer.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +5 -3
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/query/ast.d.ts +147 -34
- package/dist/types/src/query/ast.d.ts.map +1 -1
- package/dist/types/src/reference.d.ts +1 -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 -15
- package/src/document-structure.ts +23 -1
- package/src/echo-feed-codec.ts +67 -0
- package/src/edge-peer.ts +27 -0
- package/src/index.ts +5 -3
- package/src/query/ast.ts +197 -12
- package/src/reference.ts +4 -1
- package/dist/lib/browser/index.mjs.map +0 -7
- package/dist/lib/browser/meta.json +0 -1
- package/dist/lib/node-esm/index.mjs +0 -613
- package/dist/lib/node-esm/index.mjs.map +0 -7
- package/dist/lib/node-esm/meta.json +0 -1
package/package.json
CHANGED
|
@@ -1,37 +1,36 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/echo-protocol",
|
|
3
|
-
"version": "0.8.4-main.
|
|
3
|
+
"version": "0.8.4-main.f466a3d56e",
|
|
4
4
|
"description": "Core ECHO APIs.",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
7
|
-
"repository":
|
|
8
|
-
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/dxos/dxos"
|
|
10
|
+
},
|
|
11
|
+
"license": "FSL-1.1-Apache-2.0",
|
|
9
12
|
"author": "DXOS.org",
|
|
10
|
-
"sideEffects":
|
|
13
|
+
"sideEffects": false,
|
|
11
14
|
"type": "module",
|
|
12
15
|
"exports": {
|
|
13
16
|
".": {
|
|
14
17
|
"source": "./src/index.ts",
|
|
15
18
|
"types": "./dist/types/src/index.d.ts",
|
|
16
|
-
"
|
|
17
|
-
"node": "./dist/lib/node-esm/index.mjs"
|
|
19
|
+
"default": "./dist/lib/neutral/index.mjs"
|
|
18
20
|
}
|
|
19
21
|
},
|
|
20
22
|
"types": "dist/types/src/index.d.ts",
|
|
21
|
-
"typesVersions": {
|
|
22
|
-
"*": {}
|
|
23
|
-
},
|
|
24
23
|
"files": [
|
|
25
24
|
"dist",
|
|
26
25
|
"src"
|
|
27
26
|
],
|
|
28
27
|
"dependencies": {
|
|
29
|
-
"effect": "3.
|
|
30
|
-
"@dxos/crypto": "0.8.4-main.
|
|
31
|
-
"@dxos/invariant": "0.8.4-main.
|
|
32
|
-
"@dxos/
|
|
33
|
-
"@dxos/
|
|
34
|
-
"@dxos/
|
|
28
|
+
"effect": "3.20.0",
|
|
29
|
+
"@dxos/crypto": "0.8.4-main.f466a3d56e",
|
|
30
|
+
"@dxos/invariant": "0.8.4-main.f466a3d56e",
|
|
31
|
+
"@dxos/protocols": "0.8.4-main.f466a3d56e",
|
|
32
|
+
"@dxos/keys": "0.8.4-main.f466a3d56e",
|
|
33
|
+
"@dxos/util": "0.8.4-main.f466a3d56e"
|
|
35
34
|
},
|
|
36
35
|
"publishConfig": {
|
|
37
36
|
"access": "public"
|
|
@@ -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
|
*/
|
|
@@ -226,6 +230,18 @@ export type ObjectMeta = {
|
|
|
226
230
|
* NOTE: Optional for backwards compatibilty.
|
|
227
231
|
*/
|
|
228
232
|
tags?: string[];
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Fully-qualified registry key for the object (FQN format, e.g. `org.example.type.foo`).
|
|
236
|
+
* Identifies the canonical registry entry the object instance was created from.
|
|
237
|
+
*/
|
|
238
|
+
key?: string;
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Semantic version of the registry entry the object was created from.
|
|
242
|
+
* Must be a valid semver string (e.g. `1.2.3`).
|
|
243
|
+
*/
|
|
244
|
+
version?: string;
|
|
229
245
|
};
|
|
230
246
|
|
|
231
247
|
/**
|
|
@@ -248,13 +264,19 @@ export type ObjectSystem = {
|
|
|
248
264
|
*/
|
|
249
265
|
deleted?: boolean;
|
|
250
266
|
|
|
267
|
+
/**
|
|
268
|
+
* Object parent.
|
|
269
|
+
* Objects with no parent are at the top level of the object hierarchy in the space.
|
|
270
|
+
*/
|
|
271
|
+
parent?: EncodedReference;
|
|
272
|
+
|
|
251
273
|
/**
|
|
252
274
|
* Only for relations.
|
|
253
275
|
*/
|
|
254
276
|
source?: EncodedReference;
|
|
255
277
|
|
|
256
278
|
/**
|
|
257
|
-
* Only for relations.
|
|
279
|
+
* Only for relations.
|
|
258
280
|
*/
|
|
259
281
|
target?: EncodedReference;
|
|
260
282
|
};
|
|
@@ -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/edge-peer.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type SpaceId } from '@dxos/keys';
|
|
6
|
+
import { EdgeService } from '@dxos/protocols';
|
|
7
|
+
import { compositeKey } from '@dxos/util';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Returns true if the given peerId belongs to an EDGE replicator (Automerge or Subduction).
|
|
11
|
+
*
|
|
12
|
+
* When `spaceId` is provided, the match is scoped to that space (peerId must start with
|
|
13
|
+
* `<service>:<spaceId>`). When omitted, only the leading service segment is checked
|
|
14
|
+
* (peerId must start with `<service>:`), which is useful when the caller doesn't have a
|
|
15
|
+
* spaceId on hand or wants to match any edge replicator regardless of space.
|
|
16
|
+
*/
|
|
17
|
+
export const isEdgePeerId = (peerId: string, spaceId?: SpaceId): boolean => {
|
|
18
|
+
const automergePrefix =
|
|
19
|
+
spaceId !== undefined
|
|
20
|
+
? compositeKey(EdgeService.AUTOMERGE_REPLICATOR, spaceId)
|
|
21
|
+
: `${EdgeService.AUTOMERGE_REPLICATOR}:`;
|
|
22
|
+
const subductionPrefix =
|
|
23
|
+
spaceId !== undefined
|
|
24
|
+
? compositeKey(EdgeService.SUBDUCTION_REPLICATOR, spaceId)
|
|
25
|
+
: `${EdgeService.SUBDUCTION_REPLICATOR}:`;
|
|
26
|
+
return peerId.startsWith(automergePrefix) || peerId.startsWith(subductionPrefix);
|
|
27
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -2,10 +2,12 @@
|
|
|
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 './edge-peer';
|
|
8
|
+
export * from './echo-feed-codec';
|
|
9
|
+
export * from './foreign-key';
|
|
10
|
+
export * from './query';
|
|
6
11
|
export * from './reference';
|
|
7
12
|
export * from './space-doc-version';
|
|
8
|
-
export type * from './collection-sync';
|
|
9
13
|
export * from './space-id';
|
|
10
|
-
export * from './foreign-key';
|
|
11
|
-
export * from './query';
|
package/src/query/ast.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { DXN, ObjectId } from '@dxos/keys';
|
|
|
10
10
|
import { ForeignKey } from '../foreign-key';
|
|
11
11
|
|
|
12
12
|
const TypenameSpecifier = Schema.Union(DXN.Schema, Schema.Null).annotations({
|
|
13
|
-
description: 'DXN or null
|
|
13
|
+
description: 'DXN or null; null matches any type',
|
|
14
14
|
});
|
|
15
15
|
|
|
16
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.
|
|
@@ -42,6 +42,17 @@ const FilterObject_ = Schema.Struct({
|
|
|
42
42
|
*/
|
|
43
43
|
foreignKeys: Schema.optional(Schema.Array(ForeignKey)),
|
|
44
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Match objects whose meta `key` equals this fully-qualified registry key (FQN format).
|
|
47
|
+
*/
|
|
48
|
+
metaKey: Schema.optional(Schema.String),
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Semver range matched against the object's meta `version`.
|
|
52
|
+
* Only consulted when {@link metaKey} is set. Objects with no `version` do not satisfy a version-constrained filter.
|
|
53
|
+
*/
|
|
54
|
+
metaVersion: Schema.optional(Schema.String),
|
|
55
|
+
|
|
45
56
|
// NOTE: Make sure to update `FilterStep.isNoop` if you change this.
|
|
46
57
|
});
|
|
47
58
|
export interface FilterObject extends Schema.Schema.Type<typeof FilterObject_> {}
|
|
@@ -75,7 +86,9 @@ const FilterContains_ = Schema.Struct({
|
|
|
75
86
|
type: Schema.Literal('contains'),
|
|
76
87
|
value: Schema.Any,
|
|
77
88
|
});
|
|
89
|
+
|
|
78
90
|
export interface FilterContains extends Schema.Schema.Type<typeof FilterContains_> {}
|
|
91
|
+
|
|
79
92
|
/**
|
|
80
93
|
* Predicate for an array property to contain the provided value.
|
|
81
94
|
* Nested objects are matched using strict structural matching.
|
|
@@ -89,6 +102,7 @@ const FilterTag_ = Schema.Struct({
|
|
|
89
102
|
type: Schema.Literal('tag'),
|
|
90
103
|
tag: Schema.String, // TODO(burdon): Make OR-collection?
|
|
91
104
|
});
|
|
105
|
+
|
|
92
106
|
export interface FilterTag extends Schema.Schema.Type<typeof FilterTag_> {}
|
|
93
107
|
export const FilterTag: Schema.Schema<FilterTag> = FilterTag_;
|
|
94
108
|
|
|
@@ -100,9 +114,24 @@ const FilterRange_ = Schema.Struct({
|
|
|
100
114
|
from: Schema.Any,
|
|
101
115
|
to: Schema.Any,
|
|
102
116
|
});
|
|
117
|
+
|
|
103
118
|
export interface FilterRange extends Schema.Schema.Type<typeof FilterRange_> {}
|
|
104
119
|
export const FilterRange: Schema.Schema<FilterRange> = FilterRange_;
|
|
105
120
|
|
|
121
|
+
/**
|
|
122
|
+
* Filter by system timestamp (createdAt / updatedAt).
|
|
123
|
+
* Timestamps are unix milliseconds stored in the object meta index.
|
|
124
|
+
*/
|
|
125
|
+
const FilterTimestamp_ = Schema.Struct({
|
|
126
|
+
type: Schema.Literal('timestamp'),
|
|
127
|
+
field: Schema.Literal('createdAt', 'updatedAt'),
|
|
128
|
+
operator: Schema.Literal('gt', 'gte', 'lt', 'lte'),
|
|
129
|
+
value: Schema.Number,
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
export interface FilterTimestamp extends Schema.Schema.Type<typeof FilterTimestamp_> {}
|
|
133
|
+
export const FilterTimestamp: Schema.Schema<FilterTimestamp> = FilterTimestamp_;
|
|
134
|
+
|
|
106
135
|
/**
|
|
107
136
|
* Text search.
|
|
108
137
|
*/
|
|
@@ -111,6 +140,7 @@ const FilterTextSearch_ = Schema.Struct({
|
|
|
111
140
|
text: Schema.String,
|
|
112
141
|
searchKind: Schema.optional(Schema.Literal('full-text', 'vector')),
|
|
113
142
|
});
|
|
143
|
+
|
|
114
144
|
export interface FilterTextSearch extends Schema.Schema.Type<typeof FilterTextSearch_> {}
|
|
115
145
|
export const FilterTextSearch: Schema.Schema<FilterTextSearch> = FilterTextSearch_;
|
|
116
146
|
|
|
@@ -121,6 +151,7 @@ const FilterNot_ = Schema.Struct({
|
|
|
121
151
|
type: Schema.Literal('not'),
|
|
122
152
|
filter: Schema.suspend(() => Filter),
|
|
123
153
|
});
|
|
154
|
+
|
|
124
155
|
export interface FilterNot extends Schema.Schema.Type<typeof FilterNot_> {}
|
|
125
156
|
export const FilterNot: Schema.Schema<FilterNot> = FilterNot_;
|
|
126
157
|
|
|
@@ -131,6 +162,7 @@ const FilterAnd_ = Schema.Struct({
|
|
|
131
162
|
type: Schema.Literal('and'),
|
|
132
163
|
filters: Schema.Array(Schema.suspend(() => Filter)),
|
|
133
164
|
});
|
|
165
|
+
|
|
134
166
|
export interface FilterAnd extends Schema.Schema.Type<typeof FilterAnd_> {}
|
|
135
167
|
export const FilterAnd: Schema.Schema<FilterAnd> = FilterAnd_;
|
|
136
168
|
|
|
@@ -141,9 +173,25 @@ const FilterOr_ = Schema.Struct({
|
|
|
141
173
|
type: Schema.Literal('or'),
|
|
142
174
|
filters: Schema.Array(Schema.suspend(() => Filter)),
|
|
143
175
|
});
|
|
176
|
+
|
|
144
177
|
export interface FilterOr extends Schema.Schema.Type<typeof FilterOr_> {}
|
|
145
178
|
export const FilterOr: Schema.Schema<FilterOr> = FilterOr_;
|
|
146
179
|
|
|
180
|
+
/**
|
|
181
|
+
* Filter objects that are children of the specified parents.
|
|
182
|
+
* With transitive=true (default), matches grandchildren and beyond.
|
|
183
|
+
*/
|
|
184
|
+
const FilterChildOf_ = Schema.Struct({
|
|
185
|
+
type: Schema.Literal('child-of'),
|
|
186
|
+
/** Parent DXNs to match children of. */
|
|
187
|
+
parents: Schema.Array(DXN.Schema),
|
|
188
|
+
/** Whether to match transitively (grandchildren, etc.). Defaults to true. */
|
|
189
|
+
transitive: Schema.Boolean,
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
export interface FilterChildOf extends Schema.Schema.Type<typeof FilterChildOf_> {}
|
|
193
|
+
export const FilterChildOf: Schema.Schema<FilterChildOf> = FilterChildOf_;
|
|
194
|
+
|
|
147
195
|
/**
|
|
148
196
|
* Union of filters.
|
|
149
197
|
*/
|
|
@@ -154,11 +202,14 @@ export const Filter = Schema.Union(
|
|
|
154
202
|
FilterContains,
|
|
155
203
|
FilterTag,
|
|
156
204
|
FilterRange,
|
|
205
|
+
FilterTimestamp,
|
|
157
206
|
FilterTextSearch,
|
|
207
|
+
FilterChildOf,
|
|
158
208
|
FilterNot,
|
|
159
209
|
FilterAnd,
|
|
160
210
|
FilterOr,
|
|
161
|
-
).annotations({ identifier: 'dxos.
|
|
211
|
+
).annotations({ identifier: 'org.dxos.schema.filter' });
|
|
212
|
+
|
|
162
213
|
export type Filter = Schema.Schema.Type<typeof Filter>;
|
|
163
214
|
|
|
164
215
|
/**
|
|
@@ -168,6 +219,7 @@ const QuerySelectClause_ = Schema.Struct({
|
|
|
168
219
|
type: Schema.Literal('select'),
|
|
169
220
|
filter: Schema.suspend(() => Filter),
|
|
170
221
|
});
|
|
222
|
+
|
|
171
223
|
export interface QuerySelectClause extends Schema.Schema.Type<typeof QuerySelectClause_> {}
|
|
172
224
|
export const QuerySelectClause: Schema.Schema<QuerySelectClause> = QuerySelectClause_;
|
|
173
225
|
|
|
@@ -179,6 +231,7 @@ const QueryFilterClause_ = Schema.Struct({
|
|
|
179
231
|
selection: Schema.suspend(() => Query),
|
|
180
232
|
filter: Schema.suspend(() => Filter),
|
|
181
233
|
});
|
|
234
|
+
|
|
182
235
|
export interface QueryFilterClause extends Schema.Schema.Type<typeof QueryFilterClause_> {}
|
|
183
236
|
export const QueryFilterClause: Schema.Schema<QueryFilterClause> = QueryFilterClause_;
|
|
184
237
|
|
|
@@ -190,6 +243,7 @@ const QueryReferenceTraversalClause_ = Schema.Struct({
|
|
|
190
243
|
anchor: Schema.suspend(() => Query),
|
|
191
244
|
property: Schema.String, // TODO(dmaretskyi): Change to EscapedPropPath.
|
|
192
245
|
});
|
|
246
|
+
|
|
193
247
|
export interface QueryReferenceTraversalClause extends Schema.Schema.Type<typeof QueryReferenceTraversalClause_> {}
|
|
194
248
|
export const QueryReferenceTraversalClause: Schema.Schema<QueryReferenceTraversalClause> =
|
|
195
249
|
QueryReferenceTraversalClause_;
|
|
@@ -200,9 +254,14 @@ export const QueryReferenceTraversalClause: Schema.Schema<QueryReferenceTraversa
|
|
|
200
254
|
const QueryIncomingReferencesClause_ = Schema.Struct({
|
|
201
255
|
type: Schema.Literal('incoming-references'),
|
|
202
256
|
anchor: Schema.suspend(() => Query),
|
|
203
|
-
|
|
257
|
+
/**
|
|
258
|
+
* Property path where the reference is located.
|
|
259
|
+
* If null, matches references from any property.
|
|
260
|
+
*/
|
|
261
|
+
property: Schema.NullOr(Schema.String),
|
|
204
262
|
typename: TypenameSpecifier,
|
|
205
263
|
});
|
|
264
|
+
|
|
206
265
|
export interface QueryIncomingReferencesClause extends Schema.Schema.Type<typeof QueryIncomingReferencesClause_> {}
|
|
207
266
|
export const QueryIncomingReferencesClause: Schema.Schema<QueryIncomingReferencesClause> =
|
|
208
267
|
QueryIncomingReferencesClause_;
|
|
@@ -221,6 +280,7 @@ const QueryRelationClause_ = Schema.Struct({
|
|
|
221
280
|
direction: Schema.Literal('outgoing', 'incoming', 'both'),
|
|
222
281
|
filter: Schema.optional(Schema.suspend(() => Filter)),
|
|
223
282
|
});
|
|
283
|
+
|
|
224
284
|
export interface QueryRelationClause extends Schema.Schema.Type<typeof QueryRelationClause_> {}
|
|
225
285
|
export const QueryRelationClause: Schema.Schema<QueryRelationClause> = QueryRelationClause_;
|
|
226
286
|
|
|
@@ -232,9 +292,27 @@ const QueryRelationTraversalClause_ = Schema.Struct({
|
|
|
232
292
|
anchor: Schema.suspend(() => Query),
|
|
233
293
|
direction: Schema.Literal('source', 'target', 'both'),
|
|
234
294
|
});
|
|
295
|
+
|
|
235
296
|
export interface QueryRelationTraversalClause extends Schema.Schema.Type<typeof QueryRelationTraversalClause_> {}
|
|
236
297
|
export const QueryRelationTraversalClause: Schema.Schema<QueryRelationTraversalClause> = QueryRelationTraversalClause_;
|
|
237
298
|
|
|
299
|
+
/**
|
|
300
|
+
* Traverse parent-child hierarchy.
|
|
301
|
+
*/
|
|
302
|
+
const QueryHierarchyTraversalClause_ = Schema.Struct({
|
|
303
|
+
type: Schema.Literal('hierarchy-traversal'),
|
|
304
|
+
anchor: Schema.suspend(() => Query),
|
|
305
|
+
/**
|
|
306
|
+
* to-parent: traverse from child to parent.
|
|
307
|
+
* to-children: traverse from parent to children.
|
|
308
|
+
*/
|
|
309
|
+
direction: Schema.Literal('to-parent', 'to-children'),
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
export interface QueryHierarchyTraversalClause extends Schema.Schema.Type<typeof QueryHierarchyTraversalClause_> {}
|
|
313
|
+
export const QueryHierarchyTraversalClause: Schema.Schema<QueryHierarchyTraversalClause> =
|
|
314
|
+
QueryHierarchyTraversalClause_;
|
|
315
|
+
|
|
238
316
|
/**
|
|
239
317
|
* Union of multiple queries.
|
|
240
318
|
*/
|
|
@@ -242,6 +320,7 @@ const QueryUnionClause_ = Schema.Struct({
|
|
|
242
320
|
type: Schema.Literal('union'),
|
|
243
321
|
queries: Schema.Array(Schema.suspend(() => Query)),
|
|
244
322
|
});
|
|
323
|
+
|
|
245
324
|
export interface QueryUnionClause extends Schema.Schema.Type<typeof QueryUnionClause_> {}
|
|
246
325
|
export const QueryUnionClause: Schema.Schema<QueryUnionClause> = QueryUnionClause_;
|
|
247
326
|
|
|
@@ -253,6 +332,7 @@ const QuerySetDifferenceClause_ = Schema.Struct({
|
|
|
253
332
|
source: Schema.suspend(() => Query),
|
|
254
333
|
exclude: Schema.suspend(() => Query),
|
|
255
334
|
});
|
|
335
|
+
|
|
256
336
|
export interface QuerySetDifferenceClause extends Schema.Schema.Type<typeof QuerySetDifferenceClause_> {}
|
|
257
337
|
export const QuerySetDifferenceClause: Schema.Schema<QuerySetDifferenceClause> = QuerySetDifferenceClause_;
|
|
258
338
|
|
|
@@ -269,7 +349,14 @@ const Order_ = Schema.Union(
|
|
|
269
349
|
property: Schema.String,
|
|
270
350
|
direction: OrderDirection,
|
|
271
351
|
}),
|
|
352
|
+
Schema.Struct({
|
|
353
|
+
// Order by relevance rank (for FTS/vector search results).
|
|
354
|
+
// Default direction is 'desc' (higher rank = better match first).
|
|
355
|
+
kind: Schema.Literal('rank'),
|
|
356
|
+
direction: OrderDirection,
|
|
357
|
+
}),
|
|
272
358
|
);
|
|
359
|
+
|
|
273
360
|
export type Order = Schema.Schema.Type<typeof Order_>;
|
|
274
361
|
export const Order: Schema.Schema<Order> = Order_;
|
|
275
362
|
|
|
@@ -282,6 +369,7 @@ const QueryOrderClause_ = Schema.Struct({
|
|
|
282
369
|
query: Schema.suspend(() => Query),
|
|
283
370
|
order: Schema.Array(Order),
|
|
284
371
|
});
|
|
372
|
+
|
|
285
373
|
export interface QueryOrderClause extends Schema.Schema.Type<typeof QueryOrderClause_> {}
|
|
286
374
|
export const QueryOrderClause: Schema.Schema<QueryOrderClause> = QueryOrderClause_;
|
|
287
375
|
|
|
@@ -293,9 +381,37 @@ const QueryOptionsClause_ = Schema.Struct({
|
|
|
293
381
|
query: Schema.suspend(() => Query),
|
|
294
382
|
options: Schema.suspend(() => QueryOptions),
|
|
295
383
|
});
|
|
384
|
+
|
|
296
385
|
export interface QueryOptionsClause extends Schema.Schema.Type<typeof QueryOptionsClause_> {}
|
|
297
386
|
export const QueryOptionsClause: Schema.Schema<QueryOptionsClause> = QueryOptionsClause_;
|
|
298
387
|
|
|
388
|
+
/**
|
|
389
|
+
* Limit the number of results.
|
|
390
|
+
*/
|
|
391
|
+
const QueryLimitClause_ = Schema.Struct({
|
|
392
|
+
type: Schema.Literal('limit'),
|
|
393
|
+
query: Schema.suspend(() => Query),
|
|
394
|
+
limit: Schema.Number,
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
export interface QueryLimitClause extends Schema.Schema.Type<typeof QueryLimitClause_> {}
|
|
398
|
+
export const QueryLimitClause: Schema.Schema<QueryLimitClause> = QueryLimitClause_;
|
|
399
|
+
|
|
400
|
+
export const QueryFromClause_ = Schema.Struct({
|
|
401
|
+
type: Schema.Literal('from'),
|
|
402
|
+
query: Schema.suspend(() => Query),
|
|
403
|
+
from: Schema.Union(
|
|
404
|
+
Schema.TaggedStruct('scope', {
|
|
405
|
+
scope: Schema.suspend(() => Scope),
|
|
406
|
+
}),
|
|
407
|
+
Schema.TaggedStruct('query', {
|
|
408
|
+
query: Schema.suspend(() => Query),
|
|
409
|
+
}),
|
|
410
|
+
),
|
|
411
|
+
});
|
|
412
|
+
export interface QueryFromClause extends Schema.Schema.Type<typeof QueryFromClause_> {}
|
|
413
|
+
export const QueryFromClause: Schema.Schema<QueryFromClause> = QueryFromClause_;
|
|
414
|
+
|
|
299
415
|
const Query_ = Schema.Union(
|
|
300
416
|
QuerySelectClause,
|
|
301
417
|
QueryFilterClause,
|
|
@@ -303,36 +419,56 @@ const Query_ = Schema.Union(
|
|
|
303
419
|
QueryIncomingReferencesClause,
|
|
304
420
|
QueryRelationClause,
|
|
305
421
|
QueryRelationTraversalClause,
|
|
422
|
+
QueryHierarchyTraversalClause,
|
|
306
423
|
QueryUnionClause,
|
|
307
424
|
QuerySetDifferenceClause,
|
|
308
425
|
QueryOrderClause,
|
|
309
426
|
QueryOptionsClause,
|
|
310
|
-
|
|
427
|
+
QueryLimitClause,
|
|
428
|
+
QueryFromClause,
|
|
429
|
+
).annotations({ identifier: 'org.dxos.schema.query' });
|
|
311
430
|
|
|
312
431
|
export type Query = Schema.Schema.Type<typeof Query_>;
|
|
313
432
|
export const Query: Schema.Schema<Query> = Query_;
|
|
314
433
|
|
|
315
434
|
export const QueryOptions = Schema.Struct({
|
|
435
|
+
/**
|
|
436
|
+
* Nested select statements will use this option to filter deleted objects.
|
|
437
|
+
*/
|
|
438
|
+
deleted: Schema.optional(Schema.Literal('include', 'exclude', 'only')),
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Diagnostics-only label for logs / tooling (not used by execution semantics).
|
|
442
|
+
*/
|
|
443
|
+
debugLabel: Schema.optional(Schema.String),
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
export interface QueryOptions extends Schema.Schema.Type<typeof QueryOptions> {}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Specifies the scope of the data to query from.
|
|
450
|
+
*/
|
|
451
|
+
export const Scope = Schema.Struct({
|
|
316
452
|
/**
|
|
317
453
|
* The nested select statemets will select from the given spaces.
|
|
318
454
|
*
|
|
319
|
-
* NOTE: Spaces and
|
|
455
|
+
* NOTE: Spaces and feeds are unioned together if both are specified.
|
|
320
456
|
*/
|
|
321
457
|
spaceIds: Schema.optional(Schema.Array(Schema.String)),
|
|
322
458
|
|
|
323
459
|
/**
|
|
324
|
-
*
|
|
325
|
-
*
|
|
326
|
-
* NOTE: Spaces and queues are unioned together if both are specified.
|
|
460
|
+
* If true, the nested select statements will select from all feeds in the spaces specified by `spaceIds`.
|
|
327
461
|
*/
|
|
328
|
-
|
|
462
|
+
allFeedsFromSpaces: Schema.optional(Schema.Boolean),
|
|
329
463
|
|
|
330
464
|
/**
|
|
331
|
-
*
|
|
465
|
+
* The nested select statemets will select from the given feeds (by underlying queue DXN).
|
|
466
|
+
*
|
|
467
|
+
* NOTE: Spaces and feeds are unioned together if both are specified.
|
|
332
468
|
*/
|
|
333
|
-
|
|
469
|
+
feeds: Schema.optional(Schema.Array(DXN.Schema)),
|
|
334
470
|
});
|
|
335
|
-
export interface
|
|
471
|
+
export interface Scope extends Schema.Schema.Type<typeof Scope> {}
|
|
336
472
|
|
|
337
473
|
export const visit = (query: Query, visitor: (node: Query) => void) => {
|
|
338
474
|
visitor(query);
|
|
@@ -344,17 +480,57 @@ export const visit = (query: Query, visitor: (node: Query) => void) => {
|
|
|
344
480
|
Match.when({ type: 'relation' }, ({ anchor }) => visit(anchor, visitor)),
|
|
345
481
|
Match.when({ type: 'options' }, ({ query }) => visit(query, visitor)),
|
|
346
482
|
Match.when({ type: 'relation-traversal' }, ({ anchor }) => visit(anchor, visitor)),
|
|
483
|
+
Match.when({ type: 'hierarchy-traversal' }, ({ anchor }) => visit(anchor, visitor)),
|
|
347
484
|
Match.when({ type: 'union' }, ({ queries }) => queries.forEach((q) => visit(q, visitor))),
|
|
348
485
|
Match.when({ type: 'set-difference' }, ({ source, exclude }) => {
|
|
349
486
|
visit(source, visitor);
|
|
350
487
|
visit(exclude, visitor);
|
|
351
488
|
}),
|
|
352
489
|
Match.when({ type: 'order' }, ({ query }) => visit(query, visitor)),
|
|
490
|
+
Match.when({ type: 'limit' }, ({ query }) => visit(query, visitor)),
|
|
491
|
+
Match.when({ type: 'from' }, (node) => {
|
|
492
|
+
visit(node.query, visitor);
|
|
493
|
+
if (node.from._tag === 'query') {
|
|
494
|
+
visit(node.from.query, visitor);
|
|
495
|
+
}
|
|
496
|
+
}),
|
|
353
497
|
Match.when({ type: 'select' }, () => {}),
|
|
354
498
|
Match.exhaustive,
|
|
355
499
|
);
|
|
356
500
|
};
|
|
357
501
|
|
|
502
|
+
/**
|
|
503
|
+
* Recursively transforms a query tree bottom-up.
|
|
504
|
+
* The mapper receives each node with its children already transformed.
|
|
505
|
+
*/
|
|
506
|
+
export const map = (query: Query, mapper: (node: Query) => Query): Query => {
|
|
507
|
+
const mapped: Query = Match.value(query).pipe(
|
|
508
|
+
Match.when({ type: 'filter' }, (node) => ({ ...node, selection: map(node.selection, mapper) })),
|
|
509
|
+
Match.when({ type: 'reference-traversal' }, (node) => ({ ...node, anchor: map(node.anchor, mapper) })),
|
|
510
|
+
Match.when({ type: 'incoming-references' }, (node) => ({ ...node, anchor: map(node.anchor, mapper) })),
|
|
511
|
+
Match.when({ type: 'relation' }, (node) => ({ ...node, anchor: map(node.anchor, mapper) })),
|
|
512
|
+
Match.when({ type: 'relation-traversal' }, (node) => ({ ...node, anchor: map(node.anchor, mapper) })),
|
|
513
|
+
Match.when({ type: 'hierarchy-traversal' }, (node) => ({ ...node, anchor: map(node.anchor, mapper) })),
|
|
514
|
+
Match.when({ type: 'options' }, (node) => ({ ...node, query: map(node.query, mapper) })),
|
|
515
|
+
Match.when({ type: 'order' }, (node) => ({ ...node, query: map(node.query, mapper) })),
|
|
516
|
+
Match.when({ type: 'limit' }, (node) => ({ ...node, query: map(node.query, mapper) })),
|
|
517
|
+
Match.when({ type: 'from' }, (node) => ({
|
|
518
|
+
...node,
|
|
519
|
+
query: map(node.query, mapper),
|
|
520
|
+
...(node.from._tag === 'query' ? { from: { _tag: 'query' as const, query: map(node.from.query, mapper) } } : {}),
|
|
521
|
+
})),
|
|
522
|
+
Match.when({ type: 'union' }, (node) => ({ ...node, queries: node.queries.map((q) => map(q, mapper)) })),
|
|
523
|
+
Match.when({ type: 'set-difference' }, (node) => ({
|
|
524
|
+
...node,
|
|
525
|
+
source: map(node.source, mapper),
|
|
526
|
+
exclude: map(node.exclude, mapper),
|
|
527
|
+
})),
|
|
528
|
+
Match.when({ type: 'select' }, (node) => node),
|
|
529
|
+
Match.exhaustive,
|
|
530
|
+
);
|
|
531
|
+
return mapper(mapped);
|
|
532
|
+
};
|
|
533
|
+
|
|
358
534
|
export const fold = <T>(query: Query, reducer: (node: Query) => T): T[] => {
|
|
359
535
|
return Match.value(query).pipe(
|
|
360
536
|
Match.withReturnType<T[]>(),
|
|
@@ -364,11 +540,20 @@ export const fold = <T>(query: Query, reducer: (node: Query) => T): T[] => {
|
|
|
364
540
|
Match.when({ type: 'relation' }, ({ anchor }) => fold(anchor, reducer)),
|
|
365
541
|
Match.when({ type: 'options' }, ({ query }) => fold(query, reducer)),
|
|
366
542
|
Match.when({ type: 'relation-traversal' }, ({ anchor }) => fold(anchor, reducer)),
|
|
543
|
+
Match.when({ type: 'hierarchy-traversal' }, ({ anchor }) => fold(anchor, reducer)),
|
|
367
544
|
Match.when({ type: 'union' }, ({ queries }) => queries.flatMap((q) => fold(q, reducer))),
|
|
368
545
|
Match.when({ type: 'set-difference' }, ({ source, exclude }) =>
|
|
369
546
|
fold(source, reducer).concat(fold(exclude, reducer)),
|
|
370
547
|
),
|
|
371
548
|
Match.when({ type: 'order' }, ({ query }) => fold(query, reducer)),
|
|
549
|
+
Match.when({ type: 'limit' }, ({ query }) => fold(query, reducer)),
|
|
550
|
+
Match.when({ type: 'from' }, (node) => {
|
|
551
|
+
const results = fold(node.query, reducer);
|
|
552
|
+
if (node.from._tag === 'query') {
|
|
553
|
+
return results.concat(fold(node.from.query, reducer));
|
|
554
|
+
}
|
|
555
|
+
return results;
|
|
556
|
+
}),
|
|
372
557
|
Match.when({ type: 'select' }, () => []),
|
|
373
558
|
Match.exhaustive,
|
|
374
559
|
);
|
package/src/reference.ts
CHANGED
|
@@ -177,6 +177,9 @@ export const EncodedReference = Object.freeze({
|
|
|
177
177
|
return DXN.parse(EncodedReference.getReferenceString(value));
|
|
178
178
|
},
|
|
179
179
|
fromDXN: (dxn: DXN): EncodedReference => {
|
|
180
|
-
return
|
|
180
|
+
return { '/': dxn.toString() };
|
|
181
|
+
},
|
|
182
|
+
fromLegacyTypename: (typename: string): EncodedReference => {
|
|
183
|
+
return { '/': DXN.fromTypename(typename).toString() };
|
|
181
184
|
},
|
|
182
185
|
});
|