@dxos/echo-protocol 0.10.0 → 0.11.0
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/index.mjs +722 -0
- package/dist/lib/index.mjs.map +1 -0
- package/dist/types/src/blob.d.ts +40 -0
- package/dist/types/src/blob.d.ts.map +1 -0
- package/dist/types/src/document-structure.d.ts +72 -4
- package/dist/types/src/document-structure.d.ts.map +1 -1
- package/dist/types/src/echo-feed-codec.d.ts +13 -1
- package/dist/types/src/echo-feed-codec.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +1 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/query/ast.d.ts +191 -23
- package/dist/types/src/query/ast.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +7 -7
- package/src/blob.ts +36 -0
- package/src/document-structure.ts +104 -4
- package/src/echo-feed-codec.ts +15 -3
- package/src/index.ts +1 -0
- package/src/query/ast.ts +101 -1
- package/dist/lib/neutral/index.mjs +0 -721
- package/dist/lib/neutral/index.mjs.map +0 -7
- package/dist/lib/neutral/meta.json +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/echo-protocol",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Core ECHO APIs.",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
".": {
|
|
17
17
|
"source": "./src/index.ts",
|
|
18
18
|
"types": "./dist/types/src/index.d.ts",
|
|
19
|
-
"
|
|
19
|
+
"import": "./dist/lib/index.mjs"
|
|
20
20
|
}
|
|
21
21
|
},
|
|
22
22
|
"types": "dist/types/src/index.d.ts",
|
|
@@ -26,11 +26,11 @@
|
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"effect": "3.21.4",
|
|
29
|
-
"@dxos/crypto": "0.
|
|
30
|
-
"@dxos/
|
|
31
|
-
"@dxos/
|
|
32
|
-
"@dxos/
|
|
33
|
-
"@dxos/
|
|
29
|
+
"@dxos/crypto": "0.11.0",
|
|
30
|
+
"@dxos/invariant": "0.11.0",
|
|
31
|
+
"@dxos/keys": "0.11.0",
|
|
32
|
+
"@dxos/protocols": "0.11.0",
|
|
33
|
+
"@dxos/util": "0.11.0"
|
|
34
34
|
},
|
|
35
35
|
"publishConfig": {
|
|
36
36
|
"access": "public"
|
package/src/blob.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type SpaceId } from '@dxos/keys';
|
|
6
|
+
|
|
7
|
+
export interface BlobPutRequest {
|
|
8
|
+
spaceId: SpaceId;
|
|
9
|
+
data: Uint8Array;
|
|
10
|
+
contentType?: string;
|
|
11
|
+
/** Lowercase hex SHA-256 digest of `data`, computed by the manager. The backend does not verify it. */
|
|
12
|
+
contentHash: string;
|
|
13
|
+
/** For path-addressed extension backends. */
|
|
14
|
+
name?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface BlobPutResponse {
|
|
18
|
+
/** URI locating the stored bytes; must use a scheme the backend resolves. */
|
|
19
|
+
uri: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Implemented by pluggable blob storage backends and registered on the Hypergraph via
|
|
24
|
+
* `registerBlobBackend`.
|
|
25
|
+
*/
|
|
26
|
+
export interface BlobBackend {
|
|
27
|
+
/** URI schemes this backend resolves at read time. */
|
|
28
|
+
readonly schemes: readonly string[];
|
|
29
|
+
/** Largest `data.byteLength` this backend accepts, in bytes. `undefined` means unlimited. */
|
|
30
|
+
readonly maxSize?: number;
|
|
31
|
+
put(request: BlobPutRequest): Promise<BlobPutResponse>;
|
|
32
|
+
/** `undefined` means the URI was not found. Rejects on transport failure (e.g. offline). */
|
|
33
|
+
get(request: { spaceId: SpaceId; uri: string }): Promise<Uint8Array | undefined>;
|
|
34
|
+
has(request: { spaceId: SpaceId; uri: string }): Promise<boolean>;
|
|
35
|
+
getUrl?(request: { spaceId: SpaceId; uri: string; contentType?: string }): Promise<string | undefined>;
|
|
36
|
+
}
|
|
@@ -3,13 +3,14 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { invariant } from '@dxos/invariant';
|
|
6
|
-
import type
|
|
6
|
+
import { type EntityId, PublicKey, SpaceId, type URI } from '@dxos/keys';
|
|
7
7
|
import { visitValues } from '@dxos/util';
|
|
8
8
|
|
|
9
9
|
import { type RawString } from './automerge';
|
|
10
10
|
import type { ForeignKey } from './foreign-key';
|
|
11
11
|
import { type EncodedReference, isEncodedReference } from './reference';
|
|
12
12
|
import { type SpaceDocVersion } from './space-doc-version';
|
|
13
|
+
import { createIdFromSpaceKey } from './space-id';
|
|
13
14
|
|
|
14
15
|
export type SpaceState = {
|
|
15
16
|
// Url of the root automerge document.
|
|
@@ -29,7 +30,19 @@ export interface DatabaseDirectory {
|
|
|
29
30
|
version?: SpaceDocVersion;
|
|
30
31
|
|
|
31
32
|
access?: {
|
|
32
|
-
|
|
33
|
+
/**
|
|
34
|
+
* ID of the space that owns the document.
|
|
35
|
+
*/
|
|
36
|
+
spaceId?: SpaceId;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @deprecated Use {@link spaceId}. Still written alongside `spaceId` so older clients
|
|
40
|
+
* (and code paths that need the space public key, which cannot be recovered from the id)
|
|
41
|
+
* keep working.
|
|
42
|
+
*
|
|
43
|
+
* Space key of the owning space in hex format without the 0x prefix.
|
|
44
|
+
*/
|
|
45
|
+
spaceKey?: string;
|
|
33
46
|
};
|
|
34
47
|
/**
|
|
35
48
|
* Objects inlined in the current document.
|
|
@@ -44,6 +57,21 @@ export interface DatabaseDirectory {
|
|
|
44
57
|
[echoUri: string]: string | RawString;
|
|
45
58
|
};
|
|
46
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Per-object branch registry. Keyed by the subtree-root object id, then by branch name; each
|
|
62
|
+
* branch records the automerge doc url holding each subtree member at that branch.
|
|
63
|
+
*
|
|
64
|
+
* This is the single synced source of truth for branches: it is both the branch list/membership
|
|
65
|
+
* AND the set of branch documents the space must replicate (the host collects these urls in
|
|
66
|
+
* {@link getAllBranchDocUrls}). The client document loader does NOT treat these urls as object
|
|
67
|
+
* links, so branch docs never materialize as phantom objects. The implicit `'main'` branch is
|
|
68
|
+
* never listed here (it is the object's main doc via {@link links}).
|
|
69
|
+
*
|
|
70
|
+
* Which branch a device is currently viewing is NOT stored here — that is device-local,
|
|
71
|
+
* non-synced state.
|
|
72
|
+
*/
|
|
73
|
+
branches?: SpaceBranchRegistry;
|
|
74
|
+
|
|
47
75
|
/**
|
|
48
76
|
* @deprecated
|
|
49
77
|
* For backward compatibility.
|
|
@@ -51,8 +79,51 @@ export interface DatabaseDirectory {
|
|
|
51
79
|
experimental_spaceKey?: string;
|
|
52
80
|
}
|
|
53
81
|
|
|
82
|
+
/**
|
|
83
|
+
* @see DatabaseDirectory.branches
|
|
84
|
+
*/
|
|
85
|
+
export type SpaceBranchRegistry = {
|
|
86
|
+
[rootObjectId: string]: {
|
|
87
|
+
[branchName: string]: BranchRecord;
|
|
88
|
+
};
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export type BranchRecord = {
|
|
92
|
+
/** Subtree member object id -> automerge doc url holding that member on this branch. */
|
|
93
|
+
members: { [objectId: string]: string | RawString };
|
|
94
|
+
/**
|
|
95
|
+
* The root object's main-doc heads at fork time. Provenance only — currently written but never
|
|
96
|
+
* read; the merge relies on shared automerge ancestry, not this field.
|
|
97
|
+
*/
|
|
98
|
+
baseHeads?: string[];
|
|
99
|
+
/** Unix ms timestamp at branch creation. */
|
|
100
|
+
createdAt?: number;
|
|
101
|
+
};
|
|
102
|
+
|
|
54
103
|
export const DatabaseDirectory = Object.freeze({
|
|
55
104
|
/**
|
|
105
|
+
* @returns ID of the space that owns the document.
|
|
106
|
+
* Coalesces `access.spaceId` with the deprecated space key fields (`access.spaceKey`,
|
|
107
|
+
* `experimental_spaceKey`), deriving the id from the key for documents that predate `spaceId`.
|
|
108
|
+
*/
|
|
109
|
+
getSpaceId: async (doc: DatabaseDirectory): Promise<SpaceId | null> => {
|
|
110
|
+
if (doc.access?.spaceId != null) {
|
|
111
|
+
invariant(SpaceId.isValid(doc.access.spaceId), 'Invalid space ID');
|
|
112
|
+
return doc.access.spaceId;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const spaceKeyHex = DatabaseDirectory.getSpaceKey(doc);
|
|
116
|
+
if (spaceKeyHex == null) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return createIdFromSpaceKey(PublicKey.fromHex(spaceKeyHex));
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* @deprecated Use {@link DatabaseDirectory.getSpaceId}. Only paths that require the space
|
|
125
|
+
* public key (which cannot be derived from the space id) should read the key.
|
|
126
|
+
*
|
|
56
127
|
* @returns Space key in hex of the space that owns the document. In hex format. Without 0x prefix.
|
|
57
128
|
*/
|
|
58
129
|
getSpaceKey: (doc: DatabaseDirectory): string | null => {
|
|
@@ -75,17 +146,46 @@ export const DatabaseDirectory = Object.freeze({
|
|
|
75
146
|
return doc.links?.[id]?.toString();
|
|
76
147
|
},
|
|
77
148
|
|
|
149
|
+
/**
|
|
150
|
+
* @returns The branch registry for a subtree-root object, or undefined if it has no branches.
|
|
151
|
+
*/
|
|
152
|
+
getBranches: (doc: DatabaseDirectory, rootObjectId: EntityId): Record<string, BranchRecord> | undefined => {
|
|
153
|
+
return doc.branches?.[rootObjectId];
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* @returns All branch document urls referenced anywhere in the registry. Used by the host to
|
|
158
|
+
* decide which documents to replicate (branch docs are NOT object links).
|
|
159
|
+
*/
|
|
160
|
+
getAllBranchDocUrls: (doc: DatabaseDirectory): string[] => {
|
|
161
|
+
const urls: string[] = [];
|
|
162
|
+
for (const byName of Object.values(doc.branches ?? {})) {
|
|
163
|
+
for (const record of Object.values(byName)) {
|
|
164
|
+
for (const url of Object.values(record.members ?? {})) {
|
|
165
|
+
urls.push(url.toString());
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return urls;
|
|
170
|
+
},
|
|
171
|
+
|
|
78
172
|
make: ({
|
|
173
|
+
spaceId,
|
|
79
174
|
spaceKey,
|
|
80
175
|
objects,
|
|
81
176
|
links,
|
|
82
177
|
}: {
|
|
83
|
-
|
|
178
|
+
spaceId?: SpaceId;
|
|
179
|
+
/**
|
|
180
|
+
* @deprecated Provide {@link spaceId}. The key is still stamped for older clients.
|
|
181
|
+
*/
|
|
182
|
+
spaceKey?: string;
|
|
84
183
|
objects?: Record<string, EntityStructure>;
|
|
85
184
|
links?: Record<string, RawString>;
|
|
86
185
|
}): DatabaseDirectory => ({
|
|
87
186
|
access: {
|
|
88
|
-
|
|
187
|
+
...(spaceId != null ? { spaceId } : {}),
|
|
188
|
+
...(spaceKey != null ? { spaceKey } : {}),
|
|
89
189
|
},
|
|
90
190
|
objects: objects ?? {},
|
|
91
191
|
links: links ?? {},
|
package/src/echo-feed-codec.ts
CHANGED
|
@@ -18,10 +18,12 @@ export class EchoFeedCodec {
|
|
|
18
18
|
static readonly #decoder = new TextDecoder();
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
|
-
*
|
|
21
|
+
* Feed blocks are always whole-object snapshots; the index collapses entries by id to the latest
|
|
22
|
+
* block. TODO(wittjosiah): Follow-up — a partial-object update block format with field-level
|
|
23
|
+
* last-write-wins merge at the index (see EntityMetaIndex.update).
|
|
22
24
|
*/
|
|
23
25
|
static encode(value: Record<string, unknown>): Uint8Array {
|
|
24
|
-
const prepared = EchoFeedCodec
|
|
26
|
+
const prepared = EchoFeedCodec.stripQueuePosition(value);
|
|
25
27
|
return EchoFeedCodec.#encoder.encode(JSON.stringify(prepared));
|
|
26
28
|
}
|
|
27
29
|
|
|
@@ -37,7 +39,11 @@ export class EchoFeedCodec {
|
|
|
37
39
|
return decoded;
|
|
38
40
|
}
|
|
39
41
|
|
|
40
|
-
|
|
42
|
+
/**
|
|
43
|
+
* Strips the queue-position foreign key from an object's metadata, producing a canonical form
|
|
44
|
+
* comparable across a local snapshot and an inbound feed block (positions differ per-append).
|
|
45
|
+
*/
|
|
46
|
+
static stripQueuePosition(value: Record<string, unknown>): Record<string, unknown> {
|
|
41
47
|
if (typeof value !== 'object' || value === null) {
|
|
42
48
|
return value;
|
|
43
49
|
}
|
|
@@ -65,3 +71,9 @@ export class EchoFeedCodec {
|
|
|
65
71
|
});
|
|
66
72
|
}
|
|
67
73
|
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Foreign-key source for the global position a feed block was assigned.
|
|
77
|
+
* Re-exported so `@dxos/echo` can read it without depending on `@dxos/protocols`.
|
|
78
|
+
*/
|
|
79
|
+
export const KEY_QUEUE_POSITION = FeedProtocol.KEY_QUEUE_POSITION;
|
package/src/index.ts
CHANGED
package/src/query/ast.ts
CHANGED
|
@@ -80,6 +80,23 @@ const FilterIn_ = Schema.Struct({
|
|
|
80
80
|
export interface FilterIn extends Schema.Schema.Type<typeof FilterIn_> {}
|
|
81
81
|
export const FilterIn: Schema.Schema<FilterIn> = FilterIn_;
|
|
82
82
|
|
|
83
|
+
/**
|
|
84
|
+
* In (subquery form) — membership against a value projected from a subquery's results,
|
|
85
|
+
* e.g. `threadId IN (SELECT threadId FROM feed WHERE tag = 'inbox')`.
|
|
86
|
+
*
|
|
87
|
+
* Nested-only (like {@link FilterIn}): valid inside an `object` filter's `props`, not at
|
|
88
|
+
* the query root — the planner has no selector for a standalone membership predicate.
|
|
89
|
+
* The subquery may target a different scope than the parent query; it is resolved once at
|
|
90
|
+
* execution time by projecting `property` from its results into a set.
|
|
91
|
+
*/
|
|
92
|
+
const FilterInQuery_ = Schema.Struct({
|
|
93
|
+
type: Schema.Literal('in-query'),
|
|
94
|
+
subquery: Schema.suspend(() => Query),
|
|
95
|
+
property: Schema.String,
|
|
96
|
+
});
|
|
97
|
+
export interface FilterInQuery extends Schema.Schema.Type<typeof FilterInQuery_> {}
|
|
98
|
+
export const FilterInQuery: Schema.Schema<FilterInQuery> = FilterInQuery_;
|
|
99
|
+
|
|
83
100
|
/**
|
|
84
101
|
* Contains.
|
|
85
102
|
*/
|
|
@@ -200,6 +217,7 @@ export const Filter = Schema.Union(
|
|
|
200
217
|
FilterObject,
|
|
201
218
|
FilterCompare,
|
|
202
219
|
FilterIn,
|
|
220
|
+
FilterInQuery,
|
|
203
221
|
FilterContains,
|
|
204
222
|
FilterTag,
|
|
205
223
|
FilterRange,
|
|
@@ -342,8 +360,10 @@ export type OrderDirection = Schema.Schema.Type<typeof OrderDirection>;
|
|
|
342
360
|
|
|
343
361
|
const Order_ = Schema.Union(
|
|
344
362
|
Schema.Struct({
|
|
345
|
-
// How database wants to order them
|
|
363
|
+
// How the database wants to order them by default. For non-feed sources this is by id;
|
|
364
|
+
// for feed sources this is insertion order, so `desc` gives newest-first head reads.
|
|
346
365
|
kind: Schema.Literal('natural'),
|
|
366
|
+
direction: OrderDirection,
|
|
347
367
|
}),
|
|
348
368
|
Schema.Struct({
|
|
349
369
|
kind: Schema.Literal('property'),
|
|
@@ -404,6 +424,78 @@ const QueryLimitClause_ = Schema.Struct({
|
|
|
404
424
|
export interface QueryLimitClause extends Schema.Schema.Type<typeof QueryLimitClause_> {}
|
|
405
425
|
export const QueryLimitClause: Schema.Schema<QueryLimitClause> = QueryLimitClause_;
|
|
406
426
|
|
|
427
|
+
/**
|
|
428
|
+
* Skip a number of results (offset). Combined with `limit` and a `natural` order, this expresses
|
|
429
|
+
* a windowed (paginated) read without any feed-specific query surface.
|
|
430
|
+
*/
|
|
431
|
+
const QuerySkipClause_ = Schema.Struct({
|
|
432
|
+
type: Schema.Literal('skip'),
|
|
433
|
+
query: Schema.suspend(() => Query),
|
|
434
|
+
skip: Schema.Number,
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
export interface QuerySkipClause extends Schema.Schema.Type<typeof QuerySkipClause_> {}
|
|
438
|
+
export const QuerySkipClause: Schema.Schema<QuerySkipClause> = QuerySkipClause_;
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* A named aggregate computed per group over its members, exposed as a top-level field on the flat
|
|
442
|
+
* result record (`row[name]`) and orderable via a following `orderBy(Order.property(name))`. A
|
|
443
|
+
* tagged union per kind — `property`/`limit`/`order` are present exactly when the kind uses them,
|
|
444
|
+
* so read sites narrow by `kind` instead of guarding an unused optional field.
|
|
445
|
+
* - `group` partitions members by a scalar `property`; its coerced key value is the field's value.
|
|
446
|
+
* Composite keys are formed from multiple `group` entries. A query with no `group` entries
|
|
447
|
+
* aggregates its entire input into a single row.
|
|
448
|
+
* - `max`/`min` reduce a scalar member `property`.
|
|
449
|
+
* - `items` collects the group's members, optionally ordered by `order` and capped to `limit`.
|
|
450
|
+
* Opt-in — a row carries no members otherwise. `order` is this aggregate's own per-group
|
|
451
|
+
* ordering, independent of any `orderBy` clause elsewhere in the query (which orders the whole
|
|
452
|
+
* input stream / the resulting groups, not this aggregate's member selection).
|
|
453
|
+
* - `count` yields the member count. Opt-in — a row carries no count otherwise.
|
|
454
|
+
*/
|
|
455
|
+
const GroupAggregateGroup_ = Schema.Struct({
|
|
456
|
+
name: Schema.String,
|
|
457
|
+
kind: Schema.Literal('group'),
|
|
458
|
+
property: Schema.String,
|
|
459
|
+
});
|
|
460
|
+
const GroupAggregateMax_ = Schema.Struct({ name: Schema.String, kind: Schema.Literal('max'), property: Schema.String });
|
|
461
|
+
const GroupAggregateMin_ = Schema.Struct({ name: Schema.String, kind: Schema.Literal('min'), property: Schema.String });
|
|
462
|
+
const GroupAggregateItems_ = Schema.Struct({
|
|
463
|
+
name: Schema.String,
|
|
464
|
+
kind: Schema.Literal('items'),
|
|
465
|
+
limit: Schema.optional(Schema.Number),
|
|
466
|
+
order: Schema.optional(Schema.Array(Order)),
|
|
467
|
+
});
|
|
468
|
+
const GroupAggregateCount_ = Schema.Struct({ name: Schema.String, kind: Schema.Literal('count') });
|
|
469
|
+
|
|
470
|
+
const GroupAggregate_ = Schema.Union(
|
|
471
|
+
GroupAggregateGroup_,
|
|
472
|
+
GroupAggregateMax_,
|
|
473
|
+
GroupAggregateMin_,
|
|
474
|
+
GroupAggregateItems_,
|
|
475
|
+
GroupAggregateCount_,
|
|
476
|
+
);
|
|
477
|
+
|
|
478
|
+
export type GroupAggregate = Schema.Schema.Type<typeof GroupAggregate_>;
|
|
479
|
+
export const GroupAggregate: Schema.Schema<GroupAggregate> = GroupAggregate_;
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Aggregates results into flat records. `group`-kind entries partition members into contiguous
|
|
483
|
+
* groups (one row each); with no `group` entries the whole input aggregates into a single row.
|
|
484
|
+
* Groups are ordered by the first occurrence of their key in the incoming (already-ordered) result
|
|
485
|
+
* stream — this lets a preceding `orderBy` also control group order (e.g. ordering thread groups by
|
|
486
|
+
* their most recent message). A following `orderBy(Order.property(name))` referencing an aggregate
|
|
487
|
+
* or group field reorders whole groups instead. Must be the outermost data clause: only
|
|
488
|
+
* `from`/`options`/`order` may wrap it.
|
|
489
|
+
*/
|
|
490
|
+
const QueryAggregateClause_ = Schema.Struct({
|
|
491
|
+
type: Schema.Literal('aggregate'),
|
|
492
|
+
query: Schema.suspend(() => Query),
|
|
493
|
+
aggregates: Schema.Array(GroupAggregate),
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
export interface QueryAggregateClause extends Schema.Schema.Type<typeof QueryAggregateClause_> {}
|
|
497
|
+
export const QueryAggregateClause: Schema.Schema<QueryAggregateClause> = QueryAggregateClause_;
|
|
498
|
+
|
|
407
499
|
export const QueryFromClause_ = Schema.Struct({
|
|
408
500
|
type: Schema.Literal('from'),
|
|
409
501
|
query: Schema.suspend(() => Query),
|
|
@@ -432,6 +524,8 @@ const Query_ = Schema.Union(
|
|
|
432
524
|
QueryOrderClause,
|
|
433
525
|
QueryOptionsClause,
|
|
434
526
|
QueryLimitClause,
|
|
527
|
+
QuerySkipClause,
|
|
528
|
+
QueryAggregateClause,
|
|
435
529
|
QueryFromClause,
|
|
436
530
|
).annotations({ identifier: 'org.dxos.schema.query' });
|
|
437
531
|
|
|
@@ -511,6 +605,8 @@ export const visit = (query: Query, visitor: (node: Query) => void) => {
|
|
|
511
605
|
}),
|
|
512
606
|
Match.when({ type: 'order' }, ({ query }) => visit(query, visitor)),
|
|
513
607
|
Match.when({ type: 'limit' }, ({ query }) => visit(query, visitor)),
|
|
608
|
+
Match.when({ type: 'skip' }, ({ query }) => visit(query, visitor)),
|
|
609
|
+
Match.when({ type: 'aggregate' }, ({ query }) => visit(query, visitor)),
|
|
514
610
|
Match.when({ type: 'from' }, (node) => {
|
|
515
611
|
visit(node.query, visitor);
|
|
516
612
|
if (node.from._tag === 'query') {
|
|
@@ -537,6 +633,8 @@ export const map = (query: Query, mapper: (node: Query) => Query): Query => {
|
|
|
537
633
|
Match.when({ type: 'options' }, (node) => ({ ...node, query: map(node.query, mapper) })),
|
|
538
634
|
Match.when({ type: 'order' }, (node) => ({ ...node, query: map(node.query, mapper) })),
|
|
539
635
|
Match.when({ type: 'limit' }, (node) => ({ ...node, query: map(node.query, mapper) })),
|
|
636
|
+
Match.when({ type: 'skip' }, (node) => ({ ...node, query: map(node.query, mapper) })),
|
|
637
|
+
Match.when({ type: 'aggregate' }, (node) => ({ ...node, query: map(node.query, mapper) })),
|
|
540
638
|
Match.when({ type: 'from' }, (node) => ({
|
|
541
639
|
...node,
|
|
542
640
|
query: map(node.query, mapper),
|
|
@@ -570,6 +668,8 @@ export const fold = <T>(query: Query, reducer: (node: Query) => T): T[] => {
|
|
|
570
668
|
),
|
|
571
669
|
Match.when({ type: 'order' }, ({ query }) => fold(query, reducer)),
|
|
572
670
|
Match.when({ type: 'limit' }, ({ query }) => fold(query, reducer)),
|
|
671
|
+
Match.when({ type: 'skip' }, ({ query }) => fold(query, reducer)),
|
|
672
|
+
Match.when({ type: 'aggregate' }, ({ query }) => fold(query, reducer)),
|
|
573
673
|
Match.when({ type: 'from' }, (node) => {
|
|
574
674
|
const results = fold(node.query, reducer);
|
|
575
675
|
if (node.from._tag === 'query') {
|