@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/package.json CHANGED
@@ -1,37 +1,36 @@
1
1
  {
2
2
  "name": "@dxos/echo-protocol",
3
- "version": "0.8.4-main.ead640a",
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": "github:dxos/dxos",
8
- "license": "MIT",
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": true,
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
- "browser": "./dist/lib/browser/index.mjs",
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.18.3",
30
- "@dxos/crypto": "0.8.4-main.ead640a",
31
- "@dxos/invariant": "0.8.4-main.ead640a",
32
- "@dxos/keys": "0.8.4-main.ead640a",
33
- "@dxos/util": "0.8.4-main.ead640a",
34
- "@dxos/protocols": "0.8.4-main.ead640a"
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.w
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
+ }
@@ -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. Null means any type will match',
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.org/schema/Filter' });
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
- property: Schema.String,
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
- ).annotations({ identifier: 'dxos.org/schema/Query' });
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 queues are unioned together if both are specified.
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
- * The nested select statemets will select from the given queues.
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
- queues: Schema.optional(Schema.Array(DXN.Schema)),
462
+ allFeedsFromSpaces: Schema.optional(Schema.Boolean),
329
463
 
330
464
  /**
331
- * Nested select statements will use this option to filter deleted objects.
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
- deleted: Schema.optional(Schema.Literal('include', 'exclude', 'only')),
469
+ feeds: Schema.optional(Schema.Array(DXN.Schema)),
334
470
  });
335
- export interface QueryOptions extends Schema.Schema.Type<typeof QueryOptions> {}
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 encodeReference(Reference.fromDXN(dxn));
180
+ return { '/': dxn.toString() };
181
+ },
182
+ fromLegacyTypename: (typename: string): EncodedReference => {
183
+ return { '/': DXN.fromTypename(typename).toString() };
181
184
  },
182
185
  });