@dxos/echo-protocol 0.8.4-main.72ec0f3 → 0.8.4-main.74a063c4e0

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,20 +1,22 @@
1
1
  {
2
2
  "name": "@dxos/echo-protocol",
3
- "version": "0.8.4-main.72ec0f3",
3
+ "version": "0.8.4-main.74a063c4e0",
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",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/dxos/dxos"
10
+ },
8
11
  "license": "MIT",
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",
@@ -26,12 +28,12 @@
26
28
  "src"
27
29
  ],
28
30
  "dependencies": {
29
- "effect": "3.18.3",
30
- "@dxos/crypto": "0.8.4-main.72ec0f3",
31
- "@dxos/keys": "0.8.4-main.72ec0f3",
32
- "@dxos/invariant": "0.8.4-main.72ec0f3",
33
- "@dxos/util": "0.8.4-main.72ec0f3",
34
- "@dxos/protocols": "0.8.4-main.72ec0f3"
31
+ "effect": "3.20.0",
32
+ "@dxos/crypto": "0.8.4-main.74a063c4e0",
33
+ "@dxos/keys": "0.8.4-main.74a063c4e0",
34
+ "@dxos/invariant": "0.8.4-main.74a063c4e0",
35
+ "@dxos/protocols": "0.8.4-main.74a063c4e0",
36
+ "@dxos/util": "0.8.4-main.74a063c4e0"
35
37
  },
36
38
  "publishConfig": {
37
39
  "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
  */
@@ -248,13 +252,19 @@ export type ObjectSystem = {
248
252
  */
249
253
  deleted?: boolean;
250
254
 
255
+ /**
256
+ * Object parent.
257
+ * Objects with no parent are at the top level of the object hierarchy in the space.
258
+ */
259
+ parent?: EncodedReference;
260
+
251
261
  /**
252
262
  * Only for relations.
253
263
  */
254
264
  source?: EncodedReference;
255
265
 
256
266
  /**
257
- * Only for relations.w
267
+ * Only for relations.
258
268
  */
259
269
  target?: EncodedReference;
260
270
  };
@@ -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/index.ts CHANGED
@@ -2,10 +2,11 @@
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 './echo-feed-codec';
8
+ export * from './foreign-key';
9
+ export * from './query';
6
10
  export * from './reference';
7
11
  export * from './space-doc-version';
8
- export type * from './collection-sync';
9
12
  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.
@@ -75,7 +75,9 @@ const FilterContains_ = Schema.Struct({
75
75
  type: Schema.Literal('contains'),
76
76
  value: Schema.Any,
77
77
  });
78
+
78
79
  export interface FilterContains extends Schema.Schema.Type<typeof FilterContains_> {}
80
+
79
81
  /**
80
82
  * Predicate for an array property to contain the provided value.
81
83
  * Nested objects are matched using strict structural matching.
@@ -89,6 +91,7 @@ const FilterTag_ = Schema.Struct({
89
91
  type: Schema.Literal('tag'),
90
92
  tag: Schema.String, // TODO(burdon): Make OR-collection?
91
93
  });
94
+
92
95
  export interface FilterTag extends Schema.Schema.Type<typeof FilterTag_> {}
93
96
  export const FilterTag: Schema.Schema<FilterTag> = FilterTag_;
94
97
 
@@ -100,9 +103,24 @@ const FilterRange_ = Schema.Struct({
100
103
  from: Schema.Any,
101
104
  to: Schema.Any,
102
105
  });
106
+
103
107
  export interface FilterRange extends Schema.Schema.Type<typeof FilterRange_> {}
104
108
  export const FilterRange: Schema.Schema<FilterRange> = FilterRange_;
105
109
 
110
+ /**
111
+ * Filter by system timestamp (createdAt / updatedAt).
112
+ * Timestamps are unix milliseconds stored in the object meta index.
113
+ */
114
+ const FilterTimestamp_ = Schema.Struct({
115
+ type: Schema.Literal('timestamp'),
116
+ field: Schema.Literal('createdAt', 'updatedAt'),
117
+ operator: Schema.Literal('gt', 'gte', 'lt', 'lte'),
118
+ value: Schema.Number,
119
+ });
120
+
121
+ export interface FilterTimestamp extends Schema.Schema.Type<typeof FilterTimestamp_> {}
122
+ export const FilterTimestamp: Schema.Schema<FilterTimestamp> = FilterTimestamp_;
123
+
106
124
  /**
107
125
  * Text search.
108
126
  */
@@ -111,6 +129,7 @@ const FilterTextSearch_ = Schema.Struct({
111
129
  text: Schema.String,
112
130
  searchKind: Schema.optional(Schema.Literal('full-text', 'vector')),
113
131
  });
132
+
114
133
  export interface FilterTextSearch extends Schema.Schema.Type<typeof FilterTextSearch_> {}
115
134
  export const FilterTextSearch: Schema.Schema<FilterTextSearch> = FilterTextSearch_;
116
135
 
@@ -121,6 +140,7 @@ const FilterNot_ = Schema.Struct({
121
140
  type: Schema.Literal('not'),
122
141
  filter: Schema.suspend(() => Filter),
123
142
  });
143
+
124
144
  export interface FilterNot extends Schema.Schema.Type<typeof FilterNot_> {}
125
145
  export const FilterNot: Schema.Schema<FilterNot> = FilterNot_;
126
146
 
@@ -131,6 +151,7 @@ const FilterAnd_ = Schema.Struct({
131
151
  type: Schema.Literal('and'),
132
152
  filters: Schema.Array(Schema.suspend(() => Filter)),
133
153
  });
154
+
134
155
  export interface FilterAnd extends Schema.Schema.Type<typeof FilterAnd_> {}
135
156
  export const FilterAnd: Schema.Schema<FilterAnd> = FilterAnd_;
136
157
 
@@ -141,6 +162,7 @@ const FilterOr_ = Schema.Struct({
141
162
  type: Schema.Literal('or'),
142
163
  filters: Schema.Array(Schema.suspend(() => Filter)),
143
164
  });
165
+
144
166
  export interface FilterOr extends Schema.Schema.Type<typeof FilterOr_> {}
145
167
  export const FilterOr: Schema.Schema<FilterOr> = FilterOr_;
146
168
 
@@ -154,11 +176,13 @@ export const Filter = Schema.Union(
154
176
  FilterContains,
155
177
  FilterTag,
156
178
  FilterRange,
179
+ FilterTimestamp,
157
180
  FilterTextSearch,
158
181
  FilterNot,
159
182
  FilterAnd,
160
183
  FilterOr,
161
- ).annotations({ identifier: 'dxos.org/schema/Filter' });
184
+ ).annotations({ identifier: 'org.dxos.schema.filter' });
185
+
162
186
  export type Filter = Schema.Schema.Type<typeof Filter>;
163
187
 
164
188
  /**
@@ -168,6 +192,7 @@ const QuerySelectClause_ = Schema.Struct({
168
192
  type: Schema.Literal('select'),
169
193
  filter: Schema.suspend(() => Filter),
170
194
  });
195
+
171
196
  export interface QuerySelectClause extends Schema.Schema.Type<typeof QuerySelectClause_> {}
172
197
  export const QuerySelectClause: Schema.Schema<QuerySelectClause> = QuerySelectClause_;
173
198
 
@@ -179,6 +204,7 @@ const QueryFilterClause_ = Schema.Struct({
179
204
  selection: Schema.suspend(() => Query),
180
205
  filter: Schema.suspend(() => Filter),
181
206
  });
207
+
182
208
  export interface QueryFilterClause extends Schema.Schema.Type<typeof QueryFilterClause_> {}
183
209
  export const QueryFilterClause: Schema.Schema<QueryFilterClause> = QueryFilterClause_;
184
210
 
@@ -190,6 +216,7 @@ const QueryReferenceTraversalClause_ = Schema.Struct({
190
216
  anchor: Schema.suspend(() => Query),
191
217
  property: Schema.String, // TODO(dmaretskyi): Change to EscapedPropPath.
192
218
  });
219
+
193
220
  export interface QueryReferenceTraversalClause extends Schema.Schema.Type<typeof QueryReferenceTraversalClause_> {}
194
221
  export const QueryReferenceTraversalClause: Schema.Schema<QueryReferenceTraversalClause> =
195
222
  QueryReferenceTraversalClause_;
@@ -200,9 +227,14 @@ export const QueryReferenceTraversalClause: Schema.Schema<QueryReferenceTraversa
200
227
  const QueryIncomingReferencesClause_ = Schema.Struct({
201
228
  type: Schema.Literal('incoming-references'),
202
229
  anchor: Schema.suspend(() => Query),
203
- property: Schema.String,
230
+ /**
231
+ * Property path where the reference is located.
232
+ * If null, matches references from any property.
233
+ */
234
+ property: Schema.NullOr(Schema.String),
204
235
  typename: TypenameSpecifier,
205
236
  });
237
+
206
238
  export interface QueryIncomingReferencesClause extends Schema.Schema.Type<typeof QueryIncomingReferencesClause_> {}
207
239
  export const QueryIncomingReferencesClause: Schema.Schema<QueryIncomingReferencesClause> =
208
240
  QueryIncomingReferencesClause_;
@@ -221,6 +253,7 @@ const QueryRelationClause_ = Schema.Struct({
221
253
  direction: Schema.Literal('outgoing', 'incoming', 'both'),
222
254
  filter: Schema.optional(Schema.suspend(() => Filter)),
223
255
  });
256
+
224
257
  export interface QueryRelationClause extends Schema.Schema.Type<typeof QueryRelationClause_> {}
225
258
  export const QueryRelationClause: Schema.Schema<QueryRelationClause> = QueryRelationClause_;
226
259
 
@@ -232,9 +265,27 @@ const QueryRelationTraversalClause_ = Schema.Struct({
232
265
  anchor: Schema.suspend(() => Query),
233
266
  direction: Schema.Literal('source', 'target', 'both'),
234
267
  });
268
+
235
269
  export interface QueryRelationTraversalClause extends Schema.Schema.Type<typeof QueryRelationTraversalClause_> {}
236
270
  export const QueryRelationTraversalClause: Schema.Schema<QueryRelationTraversalClause> = QueryRelationTraversalClause_;
237
271
 
272
+ /**
273
+ * Traverse parent-child hierarchy.
274
+ */
275
+ const QueryHierarchyTraversalClause_ = Schema.Struct({
276
+ type: Schema.Literal('hierarchy-traversal'),
277
+ anchor: Schema.suspend(() => Query),
278
+ /**
279
+ * to-parent: traverse from child to parent.
280
+ * to-children: traverse from parent to children.
281
+ */
282
+ direction: Schema.Literal('to-parent', 'to-children'),
283
+ });
284
+
285
+ export interface QueryHierarchyTraversalClause extends Schema.Schema.Type<typeof QueryHierarchyTraversalClause_> {}
286
+ export const QueryHierarchyTraversalClause: Schema.Schema<QueryHierarchyTraversalClause> =
287
+ QueryHierarchyTraversalClause_;
288
+
238
289
  /**
239
290
  * Union of multiple queries.
240
291
  */
@@ -242,6 +293,7 @@ const QueryUnionClause_ = Schema.Struct({
242
293
  type: Schema.Literal('union'),
243
294
  queries: Schema.Array(Schema.suspend(() => Query)),
244
295
  });
296
+
245
297
  export interface QueryUnionClause extends Schema.Schema.Type<typeof QueryUnionClause_> {}
246
298
  export const QueryUnionClause: Schema.Schema<QueryUnionClause> = QueryUnionClause_;
247
299
 
@@ -253,6 +305,7 @@ const QuerySetDifferenceClause_ = Schema.Struct({
253
305
  source: Schema.suspend(() => Query),
254
306
  exclude: Schema.suspend(() => Query),
255
307
  });
308
+
256
309
  export interface QuerySetDifferenceClause extends Schema.Schema.Type<typeof QuerySetDifferenceClause_> {}
257
310
  export const QuerySetDifferenceClause: Schema.Schema<QuerySetDifferenceClause> = QuerySetDifferenceClause_;
258
311
 
@@ -269,7 +322,14 @@ const Order_ = Schema.Union(
269
322
  property: Schema.String,
270
323
  direction: OrderDirection,
271
324
  }),
325
+ Schema.Struct({
326
+ // Order by relevance rank (for FTS/vector search results).
327
+ // Default direction is 'desc' (higher rank = better match first).
328
+ kind: Schema.Literal('rank'),
329
+ direction: OrderDirection,
330
+ }),
272
331
  );
332
+
273
333
  export type Order = Schema.Schema.Type<typeof Order_>;
274
334
  export const Order: Schema.Schema<Order> = Order_;
275
335
 
@@ -282,6 +342,7 @@ const QueryOrderClause_ = Schema.Struct({
282
342
  query: Schema.suspend(() => Query),
283
343
  order: Schema.Array(Order),
284
344
  });
345
+
285
346
  export interface QueryOrderClause extends Schema.Schema.Type<typeof QueryOrderClause_> {}
286
347
  export const QueryOrderClause: Schema.Schema<QueryOrderClause> = QueryOrderClause_;
287
348
 
@@ -293,9 +354,37 @@ const QueryOptionsClause_ = Schema.Struct({
293
354
  query: Schema.suspend(() => Query),
294
355
  options: Schema.suspend(() => QueryOptions),
295
356
  });
357
+
296
358
  export interface QueryOptionsClause extends Schema.Schema.Type<typeof QueryOptionsClause_> {}
297
359
  export const QueryOptionsClause: Schema.Schema<QueryOptionsClause> = QueryOptionsClause_;
298
360
 
361
+ /**
362
+ * Limit the number of results.
363
+ */
364
+ const QueryLimitClause_ = Schema.Struct({
365
+ type: Schema.Literal('limit'),
366
+ query: Schema.suspend(() => Query),
367
+ limit: Schema.Number,
368
+ });
369
+
370
+ export interface QueryLimitClause extends Schema.Schema.Type<typeof QueryLimitClause_> {}
371
+ export const QueryLimitClause: Schema.Schema<QueryLimitClause> = QueryLimitClause_;
372
+
373
+ export const QueryFromClause_ = Schema.Struct({
374
+ type: Schema.Literal('from'),
375
+ query: Schema.suspend(() => Query),
376
+ from: Schema.Union(
377
+ Schema.TaggedStruct('scope', {
378
+ scope: Schema.suspend(() => Scope),
379
+ }),
380
+ Schema.TaggedStruct('query', {
381
+ query: Schema.suspend(() => Query),
382
+ }),
383
+ ),
384
+ });
385
+ export interface QueryFromClause extends Schema.Schema.Type<typeof QueryFromClause_> {}
386
+ export const QueryFromClause: Schema.Schema<QueryFromClause> = QueryFromClause_;
387
+
299
388
  const Query_ = Schema.Union(
300
389
  QuerySelectClause,
301
390
  QueryFilterClause,
@@ -303,16 +392,31 @@ const Query_ = Schema.Union(
303
392
  QueryIncomingReferencesClause,
304
393
  QueryRelationClause,
305
394
  QueryRelationTraversalClause,
395
+ QueryHierarchyTraversalClause,
306
396
  QueryUnionClause,
307
397
  QuerySetDifferenceClause,
308
398
  QueryOrderClause,
309
399
  QueryOptionsClause,
310
- ).annotations({ identifier: 'dxos.org/schema/Query' });
400
+ QueryLimitClause,
401
+ QueryFromClause,
402
+ ).annotations({ identifier: 'org.dxos.schema.query' });
311
403
 
312
404
  export type Query = Schema.Schema.Type<typeof Query_>;
313
405
  export const Query: Schema.Schema<Query> = Query_;
314
406
 
315
407
  export const QueryOptions = Schema.Struct({
408
+ /**
409
+ * Nested select statements will use this option to filter deleted objects.
410
+ */
411
+ deleted: Schema.optional(Schema.Literal('include', 'exclude', 'only')),
412
+ });
413
+
414
+ export interface QueryOptions extends Schema.Schema.Type<typeof QueryOptions> {}
415
+
416
+ /**
417
+ * Specifies the scope of the data to query from.
418
+ */
419
+ export const Scope = Schema.Struct({
316
420
  /**
317
421
  * The nested select statemets will select from the given spaces.
318
422
  *
@@ -320,19 +424,19 @@ export const QueryOptions = Schema.Struct({
320
424
  */
321
425
  spaceIds: Schema.optional(Schema.Array(Schema.String)),
322
426
 
427
+ /**
428
+ * If true, the nested select statements will select from all queues in the spaces specified by `spaceIds`.
429
+ */
430
+ allQueuesFromSpaces: Schema.optional(Schema.Boolean),
431
+
323
432
  /**
324
433
  * The nested select statemets will select from the given queues.
325
434
  *
326
435
  * NOTE: Spaces and queues are unioned together if both are specified.
327
436
  */
328
437
  queues: Schema.optional(Schema.Array(DXN.Schema)),
329
-
330
- /**
331
- * Nested select statements will use this option to filter deleted objects.
332
- */
333
- deleted: Schema.optional(Schema.Literal('include', 'exclude', 'only')),
334
438
  });
335
- export interface QueryOptions extends Schema.Schema.Type<typeof QueryOptions> {}
439
+ export interface Scope extends Schema.Schema.Type<typeof Scope> {}
336
440
 
337
441
  export const visit = (query: Query, visitor: (node: Query) => void) => {
338
442
  visitor(query);
@@ -344,17 +448,57 @@ export const visit = (query: Query, visitor: (node: Query) => void) => {
344
448
  Match.when({ type: 'relation' }, ({ anchor }) => visit(anchor, visitor)),
345
449
  Match.when({ type: 'options' }, ({ query }) => visit(query, visitor)),
346
450
  Match.when({ type: 'relation-traversal' }, ({ anchor }) => visit(anchor, visitor)),
451
+ Match.when({ type: 'hierarchy-traversal' }, ({ anchor }) => visit(anchor, visitor)),
347
452
  Match.when({ type: 'union' }, ({ queries }) => queries.forEach((q) => visit(q, visitor))),
348
453
  Match.when({ type: 'set-difference' }, ({ source, exclude }) => {
349
454
  visit(source, visitor);
350
455
  visit(exclude, visitor);
351
456
  }),
352
457
  Match.when({ type: 'order' }, ({ query }) => visit(query, visitor)),
458
+ Match.when({ type: 'limit' }, ({ query }) => visit(query, visitor)),
459
+ Match.when({ type: 'from' }, (node) => {
460
+ visit(node.query, visitor);
461
+ if (node.from._tag === 'query') {
462
+ visit(node.from.query, visitor);
463
+ }
464
+ }),
353
465
  Match.when({ type: 'select' }, () => {}),
354
466
  Match.exhaustive,
355
467
  );
356
468
  };
357
469
 
470
+ /**
471
+ * Recursively transforms a query tree bottom-up.
472
+ * The mapper receives each node with its children already transformed.
473
+ */
474
+ export const map = (query: Query, mapper: (node: Query) => Query): Query => {
475
+ const mapped: Query = Match.value(query).pipe(
476
+ Match.when({ type: 'filter' }, (node) => ({ ...node, selection: map(node.selection, mapper) })),
477
+ Match.when({ type: 'reference-traversal' }, (node) => ({ ...node, anchor: map(node.anchor, mapper) })),
478
+ Match.when({ type: 'incoming-references' }, (node) => ({ ...node, anchor: map(node.anchor, mapper) })),
479
+ Match.when({ type: 'relation' }, (node) => ({ ...node, anchor: map(node.anchor, mapper) })),
480
+ Match.when({ type: 'relation-traversal' }, (node) => ({ ...node, anchor: map(node.anchor, mapper) })),
481
+ Match.when({ type: 'hierarchy-traversal' }, (node) => ({ ...node, anchor: map(node.anchor, mapper) })),
482
+ Match.when({ type: 'options' }, (node) => ({ ...node, query: map(node.query, mapper) })),
483
+ Match.when({ type: 'order' }, (node) => ({ ...node, query: map(node.query, mapper) })),
484
+ Match.when({ type: 'limit' }, (node) => ({ ...node, query: map(node.query, mapper) })),
485
+ Match.when({ type: 'from' }, (node) => ({
486
+ ...node,
487
+ query: map(node.query, mapper),
488
+ ...(node.from._tag === 'query' ? { from: { _tag: 'query' as const, query: map(node.from.query, mapper) } } : {}),
489
+ })),
490
+ Match.when({ type: 'union' }, (node) => ({ ...node, queries: node.queries.map((q) => map(q, mapper)) })),
491
+ Match.when({ type: 'set-difference' }, (node) => ({
492
+ ...node,
493
+ source: map(node.source, mapper),
494
+ exclude: map(node.exclude, mapper),
495
+ })),
496
+ Match.when({ type: 'select' }, (node) => node),
497
+ Match.exhaustive,
498
+ );
499
+ return mapper(mapped);
500
+ };
501
+
358
502
  export const fold = <T>(query: Query, reducer: (node: Query) => T): T[] => {
359
503
  return Match.value(query).pipe(
360
504
  Match.withReturnType<T[]>(),
@@ -364,11 +508,20 @@ export const fold = <T>(query: Query, reducer: (node: Query) => T): T[] => {
364
508
  Match.when({ type: 'relation' }, ({ anchor }) => fold(anchor, reducer)),
365
509
  Match.when({ type: 'options' }, ({ query }) => fold(query, reducer)),
366
510
  Match.when({ type: 'relation-traversal' }, ({ anchor }) => fold(anchor, reducer)),
511
+ Match.when({ type: 'hierarchy-traversal' }, ({ anchor }) => fold(anchor, reducer)),
367
512
  Match.when({ type: 'union' }, ({ queries }) => queries.flatMap((q) => fold(q, reducer))),
368
513
  Match.when({ type: 'set-difference' }, ({ source, exclude }) =>
369
514
  fold(source, reducer).concat(fold(exclude, reducer)),
370
515
  ),
371
516
  Match.when({ type: 'order' }, ({ query }) => fold(query, reducer)),
517
+ Match.when({ type: 'limit' }, ({ query }) => fold(query, reducer)),
518
+ Match.when({ type: 'from' }, (node) => {
519
+ const results = fold(node.query, reducer);
520
+ if (node.from._tag === 'query') {
521
+ return results.concat(fold(node.from.query, reducer));
522
+ }
523
+ return results;
524
+ }),
372
525
  Match.when({ type: 'select' }, () => []),
373
526
  Match.exhaustive,
374
527
  );
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
  });