@dxos/echo-protocol 0.8.4-main.ef1bc66f44 → 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/neutral/index.mjs +238 -55
- 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 +11 -1
- 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 +108 -40
- package/dist/types/src/query/ast.d.ts.map +1 -1
- 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 +12 -0
- 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 +129 -15
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.f466a3d56e",
|
|
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/crypto": "0.8.4-main.
|
|
33
|
-
"@dxos/invariant": "0.8.4-main.
|
|
34
|
-
"@dxos/
|
|
35
|
-
"@dxos/
|
|
36
|
-
"@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"
|
|
37
34
|
},
|
|
38
35
|
"publishConfig": {
|
|
39
36
|
"access": "public"
|
|
@@ -230,6 +230,18 @@ export type ObjectMeta = {
|
|
|
230
230
|
* NOTE: Optional for backwards compatibilty.
|
|
231
231
|
*/
|
|
232
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;
|
|
233
245
|
};
|
|
234
246
|
|
|
235
247
|
/**
|
|
@@ -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
|
@@ -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_> {}
|
|
@@ -107,6 +118,20 @@ const FilterRange_ = Schema.Struct({
|
|
|
107
118
|
export interface FilterRange extends Schema.Schema.Type<typeof FilterRange_> {}
|
|
108
119
|
export const FilterRange: Schema.Schema<FilterRange> = FilterRange_;
|
|
109
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
|
+
|
|
110
135
|
/**
|
|
111
136
|
* Text search.
|
|
112
137
|
*/
|
|
@@ -152,6 +177,21 @@ const FilterOr_ = Schema.Struct({
|
|
|
152
177
|
export interface FilterOr extends Schema.Schema.Type<typeof FilterOr_> {}
|
|
153
178
|
export const FilterOr: Schema.Schema<FilterOr> = FilterOr_;
|
|
154
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
|
+
|
|
155
195
|
/**
|
|
156
196
|
* Union of filters.
|
|
157
197
|
*/
|
|
@@ -162,11 +202,13 @@ export const Filter = Schema.Union(
|
|
|
162
202
|
FilterContains,
|
|
163
203
|
FilterTag,
|
|
164
204
|
FilterRange,
|
|
205
|
+
FilterTimestamp,
|
|
165
206
|
FilterTextSearch,
|
|
207
|
+
FilterChildOf,
|
|
166
208
|
FilterNot,
|
|
167
209
|
FilterAnd,
|
|
168
210
|
FilterOr,
|
|
169
|
-
).annotations({ identifier: 'dxos.
|
|
211
|
+
).annotations({ identifier: 'org.dxos.schema.filter' });
|
|
170
212
|
|
|
171
213
|
export type Filter = Schema.Schema.Type<typeof Filter>;
|
|
172
214
|
|
|
@@ -355,6 +397,21 @@ const QueryLimitClause_ = Schema.Struct({
|
|
|
355
397
|
export interface QueryLimitClause extends Schema.Schema.Type<typeof QueryLimitClause_> {}
|
|
356
398
|
export const QueryLimitClause: Schema.Schema<QueryLimitClause> = QueryLimitClause_;
|
|
357
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
|
+
|
|
358
415
|
const Query_ = Schema.Union(
|
|
359
416
|
QuerySelectClause,
|
|
360
417
|
QueryFilterClause,
|
|
@@ -368,38 +425,50 @@ const Query_ = Schema.Union(
|
|
|
368
425
|
QueryOrderClause,
|
|
369
426
|
QueryOptionsClause,
|
|
370
427
|
QueryLimitClause,
|
|
371
|
-
|
|
428
|
+
QueryFromClause,
|
|
429
|
+
).annotations({ identifier: 'org.dxos.schema.query' });
|
|
372
430
|
|
|
373
431
|
export type Query = Schema.Schema.Type<typeof Query_>;
|
|
374
432
|
export const Query: Schema.Schema<Query> = Query_;
|
|
375
433
|
|
|
376
434
|
export const QueryOptions = Schema.Struct({
|
|
377
435
|
/**
|
|
378
|
-
*
|
|
379
|
-
*
|
|
380
|
-
* NOTE: Spaces and queues are unioned together if both are specified.
|
|
436
|
+
* Nested select statements will use this option to filter deleted objects.
|
|
381
437
|
*/
|
|
382
|
-
|
|
438
|
+
deleted: Schema.optional(Schema.Literal('include', 'exclude', 'only')),
|
|
383
439
|
|
|
384
440
|
/**
|
|
385
|
-
*
|
|
441
|
+
* Diagnostics-only label for logs / tooling (not used by execution semantics).
|
|
386
442
|
*/
|
|
387
|
-
|
|
443
|
+
debugLabel: Schema.optional(Schema.String),
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
export interface QueryOptions extends Schema.Schema.Type<typeof QueryOptions> {}
|
|
388
447
|
|
|
448
|
+
/**
|
|
449
|
+
* Specifies the scope of the data to query from.
|
|
450
|
+
*/
|
|
451
|
+
export const Scope = Schema.Struct({
|
|
389
452
|
/**
|
|
390
|
-
* The nested select statemets will select from the given
|
|
453
|
+
* The nested select statemets will select from the given spaces.
|
|
391
454
|
*
|
|
392
|
-
* NOTE: Spaces and
|
|
455
|
+
* NOTE: Spaces and feeds are unioned together if both are specified.
|
|
393
456
|
*/
|
|
394
|
-
|
|
457
|
+
spaceIds: Schema.optional(Schema.Array(Schema.String)),
|
|
395
458
|
|
|
396
459
|
/**
|
|
397
|
-
*
|
|
460
|
+
* If true, the nested select statements will select from all feeds in the spaces specified by `spaceIds`.
|
|
398
461
|
*/
|
|
399
|
-
|
|
400
|
-
});
|
|
462
|
+
allFeedsFromSpaces: Schema.optional(Schema.Boolean),
|
|
401
463
|
|
|
402
|
-
|
|
464
|
+
/**
|
|
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.
|
|
468
|
+
*/
|
|
469
|
+
feeds: Schema.optional(Schema.Array(DXN.Schema)),
|
|
470
|
+
});
|
|
471
|
+
export interface Scope extends Schema.Schema.Type<typeof Scope> {}
|
|
403
472
|
|
|
404
473
|
export const visit = (query: Query, visitor: (node: Query) => void) => {
|
|
405
474
|
visitor(query);
|
|
@@ -419,11 +488,49 @@ export const visit = (query: Query, visitor: (node: Query) => void) => {
|
|
|
419
488
|
}),
|
|
420
489
|
Match.when({ type: 'order' }, ({ query }) => visit(query, visitor)),
|
|
421
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
|
+
}),
|
|
422
497
|
Match.when({ type: 'select' }, () => {}),
|
|
423
498
|
Match.exhaustive,
|
|
424
499
|
);
|
|
425
500
|
};
|
|
426
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
|
+
|
|
427
534
|
export const fold = <T>(query: Query, reducer: (node: Query) => T): T[] => {
|
|
428
535
|
return Match.value(query).pipe(
|
|
429
536
|
Match.withReturnType<T[]>(),
|
|
@@ -440,6 +547,13 @@ export const fold = <T>(query: Query, reducer: (node: Query) => T): T[] => {
|
|
|
440
547
|
),
|
|
441
548
|
Match.when({ type: 'order' }, ({ query }) => fold(query, reducer)),
|
|
442
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
|
+
}),
|
|
443
557
|
Match.when({ type: 'select' }, () => []),
|
|
444
558
|
Match.exhaustive,
|
|
445
559
|
);
|