@dxos/echo-protocol 0.8.4-main.ef1bc66f44 → 0.8.4-main.effb148878
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/neutral/index.mjs +264 -196
- package/dist/lib/neutral/index.mjs.map +4 -4
- package/dist/lib/neutral/meta.json +1 -1
- package/dist/types/src/document-structure.d.ts +33 -8
- 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 +111 -42
- package/dist/types/src/query/ast.d.ts.map +1 -1
- package/dist/types/src/reference.d.ts +11 -65
- 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 +8 -11
- package/src/document-structure.ts +44 -9
- 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 +134 -19
- package/src/reference.ts +12 -160
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/echo-protocol",
|
|
3
|
-
"version": "0.8.4-main.
|
|
3
|
+
"version": "0.8.4-main.effb148878",
|
|
4
4
|
"description": "Core ECHO APIs.",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"type": "git",
|
|
9
9
|
"url": "https://github.com/dxos/dxos"
|
|
10
10
|
},
|
|
11
|
-
"license": "
|
|
11
|
+
"license": "FSL-1.1-Apache-2.0",
|
|
12
12
|
"author": "DXOS.org",
|
|
13
13
|
"sideEffects": false,
|
|
14
14
|
"type": "module",
|
|
@@ -20,20 +20,17 @@
|
|
|
20
20
|
}
|
|
21
21
|
},
|
|
22
22
|
"types": "dist/types/src/index.d.ts",
|
|
23
|
-
"typesVersions": {
|
|
24
|
-
"*": {}
|
|
25
|
-
},
|
|
26
23
|
"files": [
|
|
27
24
|
"dist",
|
|
28
25
|
"src"
|
|
29
26
|
],
|
|
30
27
|
"dependencies": {
|
|
31
|
-
"effect": "3.
|
|
32
|
-
"@dxos/
|
|
33
|
-
"@dxos/
|
|
34
|
-
"@dxos/
|
|
35
|
-
"@dxos/
|
|
36
|
-
"@dxos/
|
|
28
|
+
"effect": "3.20.0",
|
|
29
|
+
"@dxos/invariant": "0.8.4-main.effb148878",
|
|
30
|
+
"@dxos/util": "0.8.4-main.effb148878",
|
|
31
|
+
"@dxos/protocols": "0.8.4-main.effb148878",
|
|
32
|
+
"@dxos/crypto": "0.8.4-main.effb148878",
|
|
33
|
+
"@dxos/keys": "0.8.4-main.effb148878"
|
|
37
34
|
},
|
|
38
35
|
"publishConfig": {
|
|
39
36
|
"access": "public"
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { invariant } from '@dxos/invariant';
|
|
6
|
-
import type {
|
|
6
|
+
import type { ObjectId, URI } from '@dxos/keys';
|
|
7
7
|
import { visitValues } from '@dxos/util';
|
|
8
8
|
|
|
9
9
|
import { type RawString } from './automerge';
|
|
@@ -41,7 +41,7 @@ export interface DatabaseDirectory {
|
|
|
41
41
|
* Object id points to an automerge doc url where the object is embedded.
|
|
42
42
|
*/
|
|
43
43
|
links?: {
|
|
44
|
-
[
|
|
44
|
+
[echoUri: string]: string | RawString;
|
|
45
45
|
};
|
|
46
46
|
|
|
47
47
|
/**
|
|
@@ -119,9 +119,9 @@ export const ObjectStructure = Object.freeze({
|
|
|
119
119
|
/**
|
|
120
120
|
* @throws On invalid object structure.
|
|
121
121
|
*/
|
|
122
|
-
getEntityKind: (object: ObjectStructure): 'object' | 'relation' => {
|
|
122
|
+
getEntityKind: (object: ObjectStructure): 'object' | 'relation' | 'type' => {
|
|
123
123
|
const kind = object.system?.kind ?? 'object';
|
|
124
|
-
invariant(kind === 'object' || kind === 'relation', 'Invalid kind');
|
|
124
|
+
invariant(kind === 'object' || kind === 'relation' || kind === 'type', 'Invalid kind');
|
|
125
125
|
return kind;
|
|
126
126
|
},
|
|
127
127
|
|
|
@@ -166,7 +166,7 @@ export const ObjectStructure = Object.freeze({
|
|
|
166
166
|
data,
|
|
167
167
|
keys,
|
|
168
168
|
}: {
|
|
169
|
-
type:
|
|
169
|
+
type: URI.URI;
|
|
170
170
|
deleted?: boolean;
|
|
171
171
|
keys?: ForeignKey[];
|
|
172
172
|
data?: unknown;
|
|
@@ -191,7 +191,7 @@ export const ObjectStructure = Object.freeze({
|
|
|
191
191
|
keys,
|
|
192
192
|
data,
|
|
193
193
|
}: {
|
|
194
|
-
type:
|
|
194
|
+
type: URI.URI;
|
|
195
195
|
source: EncodedReference;
|
|
196
196
|
target: EncodedReference;
|
|
197
197
|
deleted?: boolean;
|
|
@@ -212,6 +212,19 @@ export const ObjectStructure = Object.freeze({
|
|
|
212
212
|
data: data ?? {},
|
|
213
213
|
};
|
|
214
214
|
},
|
|
215
|
+
|
|
216
|
+
makeType: ({ type, keys, data }: { type: URI.URI; keys?: ForeignKey[]; data?: unknown }): ObjectStructure => {
|
|
217
|
+
return {
|
|
218
|
+
system: {
|
|
219
|
+
kind: 'type',
|
|
220
|
+
type: { '/': type },
|
|
221
|
+
},
|
|
222
|
+
meta: {
|
|
223
|
+
keys: keys ?? [],
|
|
224
|
+
},
|
|
225
|
+
data: data ?? {},
|
|
226
|
+
};
|
|
227
|
+
},
|
|
215
228
|
});
|
|
216
229
|
|
|
217
230
|
/**
|
|
@@ -230,6 +243,18 @@ export type ObjectMeta = {
|
|
|
230
243
|
* NOTE: Optional for backwards compatibilty.
|
|
231
244
|
*/
|
|
232
245
|
tags?: string[];
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Fully-qualified registry key for the object (FQN format, e.g. `org.example.type.foo`).
|
|
249
|
+
* Identifies the canonical registry entry the object instance was created from.
|
|
250
|
+
*/
|
|
251
|
+
key?: string;
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Semantic version of the registry entry the object was created from.
|
|
255
|
+
* Must be a valid semver string (e.g. `1.2.3`).
|
|
256
|
+
*/
|
|
257
|
+
version?: string;
|
|
233
258
|
};
|
|
234
259
|
|
|
235
260
|
/**
|
|
@@ -238,12 +263,22 @@ export type ObjectMeta = {
|
|
|
238
263
|
*/
|
|
239
264
|
export type ObjectSystem = {
|
|
240
265
|
/**
|
|
241
|
-
* Entity kind.
|
|
266
|
+
* Entity kind. `'type'` covers persisted ECHO type definitions (instances of
|
|
267
|
+
* the `Type.Type` meta-schema); `'object'` / `'relation'` cover regular ECHO
|
|
268
|
+
* instances.
|
|
242
269
|
*/
|
|
243
|
-
kind?: 'object' | 'relation';
|
|
270
|
+
kind?: 'object' | 'relation' | 'type';
|
|
244
271
|
|
|
245
272
|
/**
|
|
246
|
-
* Object reference ('protobuf' protocol) type
|
|
273
|
+
* Object reference ('protobuf' protocol) type — DXN of the schema this
|
|
274
|
+
* entity instantiates.
|
|
275
|
+
*
|
|
276
|
+
* - For `kind === 'object'` / `'relation'` instances, this is the URI of the
|
|
277
|
+
* user-defined schema the entity was created from (e.g. `dxn:type:org.example.Person:1.0.0`).
|
|
278
|
+
* - For `kind === 'type'` entities (persisted Type.Type meta-instances) this
|
|
279
|
+
* is always the URI of the `TypeSchema` meta-schema itself
|
|
280
|
+
* (`dxn:org.dxos.type.schema:0.1.0`). The kind that the meta-instance
|
|
281
|
+
* _describes_ (object/relation/type) lives in `data.jsonSchema.entityKind`.
|
|
247
282
|
*/
|
|
248
283
|
type?: EncodedReference;
|
|
249
284
|
|
|
@@ -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
|
@@ -5,13 +5,14 @@
|
|
|
5
5
|
import * as Match from 'effect/Match';
|
|
6
6
|
import * as Schema from 'effect/Schema';
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import { EchoURI, ObjectId, URI } from '@dxos/keys';
|
|
9
9
|
|
|
10
10
|
import { ForeignKey } from '../foreign-key';
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
// Type identifier URI — either a DXN (typename) or an EchoURI (stored-schema-as-object).
|
|
13
|
+
// Matches the URI written into an object's `system.type` (see `getSchemaURI`). Null
|
|
14
|
+
// matches any type.
|
|
15
|
+
const TypenameSpecifier = Schema.Union(URI.Schema, Schema.Null);
|
|
15
16
|
|
|
16
17
|
// 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.
|
|
17
18
|
|
|
@@ -42,6 +43,17 @@ const FilterObject_ = Schema.Struct({
|
|
|
42
43
|
*/
|
|
43
44
|
foreignKeys: Schema.optional(Schema.Array(ForeignKey)),
|
|
44
45
|
|
|
46
|
+
/**
|
|
47
|
+
* Match objects whose meta `key` equals this fully-qualified registry key (FQN format).
|
|
48
|
+
*/
|
|
49
|
+
metaKey: Schema.optional(Schema.String),
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Semver range matched against the object's meta `version`.
|
|
53
|
+
* Only consulted when {@link metaKey} is set. Objects with no `version` do not satisfy a version-constrained filter.
|
|
54
|
+
*/
|
|
55
|
+
metaVersion: Schema.optional(Schema.String),
|
|
56
|
+
|
|
45
57
|
// NOTE: Make sure to update `FilterStep.isNoop` if you change this.
|
|
46
58
|
});
|
|
47
59
|
export interface FilterObject extends Schema.Schema.Type<typeof FilterObject_> {}
|
|
@@ -107,6 +119,20 @@ const FilterRange_ = Schema.Struct({
|
|
|
107
119
|
export interface FilterRange extends Schema.Schema.Type<typeof FilterRange_> {}
|
|
108
120
|
export const FilterRange: Schema.Schema<FilterRange> = FilterRange_;
|
|
109
121
|
|
|
122
|
+
/**
|
|
123
|
+
* Filter by system timestamp (createdAt / updatedAt).
|
|
124
|
+
* Timestamps are unix milliseconds stored in the object meta index.
|
|
125
|
+
*/
|
|
126
|
+
const FilterTimestamp_ = Schema.Struct({
|
|
127
|
+
type: Schema.Literal('timestamp'),
|
|
128
|
+
field: Schema.Literal('createdAt', 'updatedAt'),
|
|
129
|
+
operator: Schema.Literal('gt', 'gte', 'lt', 'lte'),
|
|
130
|
+
value: Schema.Number,
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
export interface FilterTimestamp extends Schema.Schema.Type<typeof FilterTimestamp_> {}
|
|
134
|
+
export const FilterTimestamp: Schema.Schema<FilterTimestamp> = FilterTimestamp_;
|
|
135
|
+
|
|
110
136
|
/**
|
|
111
137
|
* Text search.
|
|
112
138
|
*/
|
|
@@ -152,6 +178,21 @@ const FilterOr_ = Schema.Struct({
|
|
|
152
178
|
export interface FilterOr extends Schema.Schema.Type<typeof FilterOr_> {}
|
|
153
179
|
export const FilterOr: Schema.Schema<FilterOr> = FilterOr_;
|
|
154
180
|
|
|
181
|
+
/**
|
|
182
|
+
* Filter objects that are children of the specified parents.
|
|
183
|
+
* With transitive=true (default), matches grandchildren and beyond.
|
|
184
|
+
*/
|
|
185
|
+
const FilterChildOf_ = Schema.Struct({
|
|
186
|
+
type: Schema.Literal('child-of'),
|
|
187
|
+
/** Parent DXNs to match children of. */
|
|
188
|
+
parents: Schema.Array(EchoURI.Schema),
|
|
189
|
+
/** Whether to match transitively (grandchildren, etc.). Defaults to true. */
|
|
190
|
+
transitive: Schema.Boolean,
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
export interface FilterChildOf extends Schema.Schema.Type<typeof FilterChildOf_> {}
|
|
194
|
+
export const FilterChildOf: Schema.Schema<FilterChildOf> = FilterChildOf_;
|
|
195
|
+
|
|
155
196
|
/**
|
|
156
197
|
* Union of filters.
|
|
157
198
|
*/
|
|
@@ -162,11 +203,13 @@ export const Filter = Schema.Union(
|
|
|
162
203
|
FilterContains,
|
|
163
204
|
FilterTag,
|
|
164
205
|
FilterRange,
|
|
206
|
+
FilterTimestamp,
|
|
165
207
|
FilterTextSearch,
|
|
208
|
+
FilterChildOf,
|
|
166
209
|
FilterNot,
|
|
167
210
|
FilterAnd,
|
|
168
211
|
FilterOr,
|
|
169
|
-
).annotations({ identifier: 'dxos.
|
|
212
|
+
).annotations({ identifier: 'org.dxos.schema.filter' });
|
|
170
213
|
|
|
171
214
|
export type Filter = Schema.Schema.Type<typeof Filter>;
|
|
172
215
|
|
|
@@ -355,6 +398,21 @@ const QueryLimitClause_ = Schema.Struct({
|
|
|
355
398
|
export interface QueryLimitClause extends Schema.Schema.Type<typeof QueryLimitClause_> {}
|
|
356
399
|
export const QueryLimitClause: Schema.Schema<QueryLimitClause> = QueryLimitClause_;
|
|
357
400
|
|
|
401
|
+
export const QueryFromClause_ = Schema.Struct({
|
|
402
|
+
type: Schema.Literal('from'),
|
|
403
|
+
query: Schema.suspend(() => Query),
|
|
404
|
+
from: Schema.Union(
|
|
405
|
+
Schema.TaggedStruct('scope', {
|
|
406
|
+
scope: Schema.suspend(() => Scope),
|
|
407
|
+
}),
|
|
408
|
+
Schema.TaggedStruct('query', {
|
|
409
|
+
query: Schema.suspend(() => Query),
|
|
410
|
+
}),
|
|
411
|
+
),
|
|
412
|
+
});
|
|
413
|
+
export interface QueryFromClause extends Schema.Schema.Type<typeof QueryFromClause_> {}
|
|
414
|
+
export const QueryFromClause: Schema.Schema<QueryFromClause> = QueryFromClause_;
|
|
415
|
+
|
|
358
416
|
const Query_ = Schema.Union(
|
|
359
417
|
QuerySelectClause,
|
|
360
418
|
QueryFilterClause,
|
|
@@ -368,38 +426,50 @@ const Query_ = Schema.Union(
|
|
|
368
426
|
QueryOrderClause,
|
|
369
427
|
QueryOptionsClause,
|
|
370
428
|
QueryLimitClause,
|
|
371
|
-
|
|
429
|
+
QueryFromClause,
|
|
430
|
+
).annotations({ identifier: 'org.dxos.schema.query' });
|
|
372
431
|
|
|
373
432
|
export type Query = Schema.Schema.Type<typeof Query_>;
|
|
374
433
|
export const Query: Schema.Schema<Query> = Query_;
|
|
375
434
|
|
|
376
435
|
export const QueryOptions = Schema.Struct({
|
|
377
436
|
/**
|
|
378
|
-
*
|
|
379
|
-
*
|
|
380
|
-
* NOTE: Spaces and queues are unioned together if both are specified.
|
|
437
|
+
* Nested select statements will use this option to filter deleted objects.
|
|
381
438
|
*/
|
|
382
|
-
|
|
439
|
+
deleted: Schema.optional(Schema.Literal('include', 'exclude', 'only')),
|
|
383
440
|
|
|
384
441
|
/**
|
|
385
|
-
*
|
|
442
|
+
* Diagnostics-only label for logs / tooling (not used by execution semantics).
|
|
386
443
|
*/
|
|
387
|
-
|
|
444
|
+
debugLabel: Schema.optional(Schema.String),
|
|
445
|
+
});
|
|
388
446
|
|
|
447
|
+
export interface QueryOptions extends Schema.Schema.Type<typeof QueryOptions> {}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Specifies the scope of the data to query from.
|
|
451
|
+
*/
|
|
452
|
+
export const Scope = Schema.Struct({
|
|
389
453
|
/**
|
|
390
|
-
* The nested select statemets will select from the given
|
|
454
|
+
* The nested select statemets will select from the given spaces.
|
|
391
455
|
*
|
|
392
|
-
* NOTE: Spaces and
|
|
456
|
+
* NOTE: Spaces and feeds are unioned together if both are specified.
|
|
393
457
|
*/
|
|
394
|
-
|
|
458
|
+
spaceIds: Schema.optional(Schema.Array(Schema.String)),
|
|
395
459
|
|
|
396
460
|
/**
|
|
397
|
-
*
|
|
461
|
+
* If true, the nested select statements will select from all feeds in the spaces specified by `spaceIds`.
|
|
398
462
|
*/
|
|
399
|
-
|
|
400
|
-
});
|
|
463
|
+
allFeedsFromSpaces: Schema.optional(Schema.Boolean),
|
|
401
464
|
|
|
402
|
-
|
|
465
|
+
/**
|
|
466
|
+
* The nested select statemets will select from the given feeds (by EchoURI or legacy DXN).
|
|
467
|
+
*
|
|
468
|
+
* NOTE: Spaces and feeds are unioned together if both are specified.
|
|
469
|
+
*/
|
|
470
|
+
feeds: Schema.optional(Schema.Array(EchoURI.Schema)),
|
|
471
|
+
});
|
|
472
|
+
export interface Scope extends Schema.Schema.Type<typeof Scope> {}
|
|
403
473
|
|
|
404
474
|
export const visit = (query: Query, visitor: (node: Query) => void) => {
|
|
405
475
|
visitor(query);
|
|
@@ -419,11 +489,49 @@ export const visit = (query: Query, visitor: (node: Query) => void) => {
|
|
|
419
489
|
}),
|
|
420
490
|
Match.when({ type: 'order' }, ({ query }) => visit(query, visitor)),
|
|
421
491
|
Match.when({ type: 'limit' }, ({ query }) => visit(query, visitor)),
|
|
492
|
+
Match.when({ type: 'from' }, (node) => {
|
|
493
|
+
visit(node.query, visitor);
|
|
494
|
+
if (node.from._tag === 'query') {
|
|
495
|
+
visit(node.from.query, visitor);
|
|
496
|
+
}
|
|
497
|
+
}),
|
|
422
498
|
Match.when({ type: 'select' }, () => {}),
|
|
423
499
|
Match.exhaustive,
|
|
424
500
|
);
|
|
425
501
|
};
|
|
426
502
|
|
|
503
|
+
/**
|
|
504
|
+
* Recursively transforms a query tree bottom-up.
|
|
505
|
+
* The mapper receives each node with its children already transformed.
|
|
506
|
+
*/
|
|
507
|
+
export const map = (query: Query, mapper: (node: Query) => Query): Query => {
|
|
508
|
+
const mapped: Query = Match.value(query).pipe(
|
|
509
|
+
Match.when({ type: 'filter' }, (node) => ({ ...node, selection: map(node.selection, mapper) })),
|
|
510
|
+
Match.when({ type: 'reference-traversal' }, (node) => ({ ...node, anchor: map(node.anchor, mapper) })),
|
|
511
|
+
Match.when({ type: 'incoming-references' }, (node) => ({ ...node, anchor: map(node.anchor, mapper) })),
|
|
512
|
+
Match.when({ type: 'relation' }, (node) => ({ ...node, anchor: map(node.anchor, mapper) })),
|
|
513
|
+
Match.when({ type: 'relation-traversal' }, (node) => ({ ...node, anchor: map(node.anchor, mapper) })),
|
|
514
|
+
Match.when({ type: 'hierarchy-traversal' }, (node) => ({ ...node, anchor: map(node.anchor, mapper) })),
|
|
515
|
+
Match.when({ type: 'options' }, (node) => ({ ...node, query: map(node.query, mapper) })),
|
|
516
|
+
Match.when({ type: 'order' }, (node) => ({ ...node, query: map(node.query, mapper) })),
|
|
517
|
+
Match.when({ type: 'limit' }, (node) => ({ ...node, query: map(node.query, mapper) })),
|
|
518
|
+
Match.when({ type: 'from' }, (node) => ({
|
|
519
|
+
...node,
|
|
520
|
+
query: map(node.query, mapper),
|
|
521
|
+
...(node.from._tag === 'query' ? { from: { _tag: 'query' as const, query: map(node.from.query, mapper) } } : {}),
|
|
522
|
+
})),
|
|
523
|
+
Match.when({ type: 'union' }, (node) => ({ ...node, queries: node.queries.map((q) => map(q, mapper)) })),
|
|
524
|
+
Match.when({ type: 'set-difference' }, (node) => ({
|
|
525
|
+
...node,
|
|
526
|
+
source: map(node.source, mapper),
|
|
527
|
+
exclude: map(node.exclude, mapper),
|
|
528
|
+
})),
|
|
529
|
+
Match.when({ type: 'select' }, (node) => node),
|
|
530
|
+
Match.exhaustive,
|
|
531
|
+
);
|
|
532
|
+
return mapper(mapped);
|
|
533
|
+
};
|
|
534
|
+
|
|
427
535
|
export const fold = <T>(query: Query, reducer: (node: Query) => T): T[] => {
|
|
428
536
|
return Match.value(query).pipe(
|
|
429
537
|
Match.withReturnType<T[]>(),
|
|
@@ -440,6 +548,13 @@ export const fold = <T>(query: Query, reducer: (node: Query) => T): T[] => {
|
|
|
440
548
|
),
|
|
441
549
|
Match.when({ type: 'order' }, ({ query }) => fold(query, reducer)),
|
|
442
550
|
Match.when({ type: 'limit' }, ({ query }) => fold(query, reducer)),
|
|
551
|
+
Match.when({ type: 'from' }, (node) => {
|
|
552
|
+
const results = fold(node.query, reducer);
|
|
553
|
+
if (node.from._tag === 'query') {
|
|
554
|
+
return results.concat(fold(node.from.query, reducer));
|
|
555
|
+
}
|
|
556
|
+
return results;
|
|
557
|
+
}),
|
|
443
558
|
Match.when({ type: 'select' }, () => []),
|
|
444
559
|
Match.exhaustive,
|
|
445
560
|
);
|