@dxos/echo-protocol 0.8.4-main.fffef41 → 0.8.4-staging.ac66bdf99f

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.fffef41",
3
+ "version": "0.8.4-staging.ac66bdf99f",
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.fffef41",
31
- "@dxos/invariant": "0.8.4-main.fffef41",
32
- "@dxos/keys": "0.8.4-main.fffef41",
33
- "@dxos/protocols": "0.8.4-main.fffef41",
34
- "@dxos/util": "0.8.4-main.fffef41"
31
+ "effect": "3.20.0",
32
+ "@dxos/crypto": "0.8.4-staging.ac66bdf99f",
33
+ "@dxos/invariant": "0.8.4-staging.ac66bdf99f",
34
+ "@dxos/keys": "0.8.4-staging.ac66bdf99f",
35
+ "@dxos/protocols": "0.8.4-staging.ac66bdf99f",
36
+ "@dxos/util": "0.8.4-staging.ac66bdf99f"
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,9 +162,25 @@ 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
 
169
+ /**
170
+ * Filter objects that are children of the specified parents.
171
+ * With transitive=true (default), matches grandchildren and beyond.
172
+ */
173
+ const FilterChildOf_ = Schema.Struct({
174
+ type: Schema.Literal('child-of'),
175
+ /** Parent DXNs to match children of. */
176
+ parents: Schema.Array(DXN.Schema),
177
+ /** Whether to match transitively (grandchildren, etc.). Defaults to true. */
178
+ transitive: Schema.Boolean,
179
+ });
180
+
181
+ export interface FilterChildOf extends Schema.Schema.Type<typeof FilterChildOf_> {}
182
+ export const FilterChildOf: Schema.Schema<FilterChildOf> = FilterChildOf_;
183
+
147
184
  /**
148
185
  * Union of filters.
149
186
  */
@@ -154,11 +191,14 @@ export const Filter = Schema.Union(
154
191
  FilterContains,
155
192
  FilterTag,
156
193
  FilterRange,
194
+ FilterTimestamp,
157
195
  FilterTextSearch,
196
+ FilterChildOf,
158
197
  FilterNot,
159
198
  FilterAnd,
160
199
  FilterOr,
161
- ).annotations({ identifier: 'dxos.org/schema/Filter' });
200
+ ).annotations({ identifier: 'org.dxos.schema.filter' });
201
+
162
202
  export type Filter = Schema.Schema.Type<typeof Filter>;
163
203
 
164
204
  /**
@@ -168,6 +208,7 @@ const QuerySelectClause_ = Schema.Struct({
168
208
  type: Schema.Literal('select'),
169
209
  filter: Schema.suspend(() => Filter),
170
210
  });
211
+
171
212
  export interface QuerySelectClause extends Schema.Schema.Type<typeof QuerySelectClause_> {}
172
213
  export const QuerySelectClause: Schema.Schema<QuerySelectClause> = QuerySelectClause_;
173
214
 
@@ -179,6 +220,7 @@ const QueryFilterClause_ = Schema.Struct({
179
220
  selection: Schema.suspend(() => Query),
180
221
  filter: Schema.suspend(() => Filter),
181
222
  });
223
+
182
224
  export interface QueryFilterClause extends Schema.Schema.Type<typeof QueryFilterClause_> {}
183
225
  export const QueryFilterClause: Schema.Schema<QueryFilterClause> = QueryFilterClause_;
184
226
 
@@ -190,6 +232,7 @@ const QueryReferenceTraversalClause_ = Schema.Struct({
190
232
  anchor: Schema.suspend(() => Query),
191
233
  property: Schema.String, // TODO(dmaretskyi): Change to EscapedPropPath.
192
234
  });
235
+
193
236
  export interface QueryReferenceTraversalClause extends Schema.Schema.Type<typeof QueryReferenceTraversalClause_> {}
194
237
  export const QueryReferenceTraversalClause: Schema.Schema<QueryReferenceTraversalClause> =
195
238
  QueryReferenceTraversalClause_;
@@ -200,9 +243,14 @@ export const QueryReferenceTraversalClause: Schema.Schema<QueryReferenceTraversa
200
243
  const QueryIncomingReferencesClause_ = Schema.Struct({
201
244
  type: Schema.Literal('incoming-references'),
202
245
  anchor: Schema.suspend(() => Query),
203
- property: Schema.String,
246
+ /**
247
+ * Property path where the reference is located.
248
+ * If null, matches references from any property.
249
+ */
250
+ property: Schema.NullOr(Schema.String),
204
251
  typename: TypenameSpecifier,
205
252
  });
253
+
206
254
  export interface QueryIncomingReferencesClause extends Schema.Schema.Type<typeof QueryIncomingReferencesClause_> {}
207
255
  export const QueryIncomingReferencesClause: Schema.Schema<QueryIncomingReferencesClause> =
208
256
  QueryIncomingReferencesClause_;
@@ -221,6 +269,7 @@ const QueryRelationClause_ = Schema.Struct({
221
269
  direction: Schema.Literal('outgoing', 'incoming', 'both'),
222
270
  filter: Schema.optional(Schema.suspend(() => Filter)),
223
271
  });
272
+
224
273
  export interface QueryRelationClause extends Schema.Schema.Type<typeof QueryRelationClause_> {}
225
274
  export const QueryRelationClause: Schema.Schema<QueryRelationClause> = QueryRelationClause_;
226
275
 
@@ -232,9 +281,27 @@ const QueryRelationTraversalClause_ = Schema.Struct({
232
281
  anchor: Schema.suspend(() => Query),
233
282
  direction: Schema.Literal('source', 'target', 'both'),
234
283
  });
284
+
235
285
  export interface QueryRelationTraversalClause extends Schema.Schema.Type<typeof QueryRelationTraversalClause_> {}
236
286
  export const QueryRelationTraversalClause: Schema.Schema<QueryRelationTraversalClause> = QueryRelationTraversalClause_;
237
287
 
288
+ /**
289
+ * Traverse parent-child hierarchy.
290
+ */
291
+ const QueryHierarchyTraversalClause_ = Schema.Struct({
292
+ type: Schema.Literal('hierarchy-traversal'),
293
+ anchor: Schema.suspend(() => Query),
294
+ /**
295
+ * to-parent: traverse from child to parent.
296
+ * to-children: traverse from parent to children.
297
+ */
298
+ direction: Schema.Literal('to-parent', 'to-children'),
299
+ });
300
+
301
+ export interface QueryHierarchyTraversalClause extends Schema.Schema.Type<typeof QueryHierarchyTraversalClause_> {}
302
+ export const QueryHierarchyTraversalClause: Schema.Schema<QueryHierarchyTraversalClause> =
303
+ QueryHierarchyTraversalClause_;
304
+
238
305
  /**
239
306
  * Union of multiple queries.
240
307
  */
@@ -242,6 +309,7 @@ const QueryUnionClause_ = Schema.Struct({
242
309
  type: Schema.Literal('union'),
243
310
  queries: Schema.Array(Schema.suspend(() => Query)),
244
311
  });
312
+
245
313
  export interface QueryUnionClause extends Schema.Schema.Type<typeof QueryUnionClause_> {}
246
314
  export const QueryUnionClause: Schema.Schema<QueryUnionClause> = QueryUnionClause_;
247
315
 
@@ -253,6 +321,7 @@ const QuerySetDifferenceClause_ = Schema.Struct({
253
321
  source: Schema.suspend(() => Query),
254
322
  exclude: Schema.suspend(() => Query),
255
323
  });
324
+
256
325
  export interface QuerySetDifferenceClause extends Schema.Schema.Type<typeof QuerySetDifferenceClause_> {}
257
326
  export const QuerySetDifferenceClause: Schema.Schema<QuerySetDifferenceClause> = QuerySetDifferenceClause_;
258
327
 
@@ -269,7 +338,14 @@ const Order_ = Schema.Union(
269
338
  property: Schema.String,
270
339
  direction: OrderDirection,
271
340
  }),
341
+ Schema.Struct({
342
+ // Order by relevance rank (for FTS/vector search results).
343
+ // Default direction is 'desc' (higher rank = better match first).
344
+ kind: Schema.Literal('rank'),
345
+ direction: OrderDirection,
346
+ }),
272
347
  );
348
+
273
349
  export type Order = Schema.Schema.Type<typeof Order_>;
274
350
  export const Order: Schema.Schema<Order> = Order_;
275
351
 
@@ -282,6 +358,7 @@ const QueryOrderClause_ = Schema.Struct({
282
358
  query: Schema.suspend(() => Query),
283
359
  order: Schema.Array(Order),
284
360
  });
361
+
285
362
  export interface QueryOrderClause extends Schema.Schema.Type<typeof QueryOrderClause_> {}
286
363
  export const QueryOrderClause: Schema.Schema<QueryOrderClause> = QueryOrderClause_;
287
364
 
@@ -293,9 +370,37 @@ const QueryOptionsClause_ = Schema.Struct({
293
370
  query: Schema.suspend(() => Query),
294
371
  options: Schema.suspend(() => QueryOptions),
295
372
  });
373
+
296
374
  export interface QueryOptionsClause extends Schema.Schema.Type<typeof QueryOptionsClause_> {}
297
375
  export const QueryOptionsClause: Schema.Schema<QueryOptionsClause> = QueryOptionsClause_;
298
376
 
377
+ /**
378
+ * Limit the number of results.
379
+ */
380
+ const QueryLimitClause_ = Schema.Struct({
381
+ type: Schema.Literal('limit'),
382
+ query: Schema.suspend(() => Query),
383
+ limit: Schema.Number,
384
+ });
385
+
386
+ export interface QueryLimitClause extends Schema.Schema.Type<typeof QueryLimitClause_> {}
387
+ export const QueryLimitClause: Schema.Schema<QueryLimitClause> = QueryLimitClause_;
388
+
389
+ export const QueryFromClause_ = Schema.Struct({
390
+ type: Schema.Literal('from'),
391
+ query: Schema.suspend(() => Query),
392
+ from: Schema.Union(
393
+ Schema.TaggedStruct('scope', {
394
+ scope: Schema.suspend(() => Scope),
395
+ }),
396
+ Schema.TaggedStruct('query', {
397
+ query: Schema.suspend(() => Query),
398
+ }),
399
+ ),
400
+ });
401
+ export interface QueryFromClause extends Schema.Schema.Type<typeof QueryFromClause_> {}
402
+ export const QueryFromClause: Schema.Schema<QueryFromClause> = QueryFromClause_;
403
+
299
404
  const Query_ = Schema.Union(
300
405
  QuerySelectClause,
301
406
  QueryFilterClause,
@@ -303,16 +408,31 @@ const Query_ = Schema.Union(
303
408
  QueryIncomingReferencesClause,
304
409
  QueryRelationClause,
305
410
  QueryRelationTraversalClause,
411
+ QueryHierarchyTraversalClause,
306
412
  QueryUnionClause,
307
413
  QuerySetDifferenceClause,
308
414
  QueryOrderClause,
309
415
  QueryOptionsClause,
310
- ).annotations({ identifier: 'dxos.org/schema/Query' });
416
+ QueryLimitClause,
417
+ QueryFromClause,
418
+ ).annotations({ identifier: 'org.dxos.schema.query' });
311
419
 
312
420
  export type Query = Schema.Schema.Type<typeof Query_>;
313
421
  export const Query: Schema.Schema<Query> = Query_;
314
422
 
315
423
  export const QueryOptions = Schema.Struct({
424
+ /**
425
+ * Nested select statements will use this option to filter deleted objects.
426
+ */
427
+ deleted: Schema.optional(Schema.Literal('include', 'exclude', 'only')),
428
+ });
429
+
430
+ export interface QueryOptions extends Schema.Schema.Type<typeof QueryOptions> {}
431
+
432
+ /**
433
+ * Specifies the scope of the data to query from.
434
+ */
435
+ export const Scope = Schema.Struct({
316
436
  /**
317
437
  * The nested select statemets will select from the given spaces.
318
438
  *
@@ -320,19 +440,19 @@ export const QueryOptions = Schema.Struct({
320
440
  */
321
441
  spaceIds: Schema.optional(Schema.Array(Schema.String)),
322
442
 
443
+ /**
444
+ * If true, the nested select statements will select from all queues in the spaces specified by `spaceIds`.
445
+ */
446
+ allQueuesFromSpaces: Schema.optional(Schema.Boolean),
447
+
323
448
  /**
324
449
  * The nested select statemets will select from the given queues.
325
450
  *
326
451
  * NOTE: Spaces and queues are unioned together if both are specified.
327
452
  */
328
453
  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
454
  });
335
- export interface QueryOptions extends Schema.Schema.Type<typeof QueryOptions> {}
455
+ export interface Scope extends Schema.Schema.Type<typeof Scope> {}
336
456
 
337
457
  export const visit = (query: Query, visitor: (node: Query) => void) => {
338
458
  visitor(query);
@@ -344,17 +464,57 @@ export const visit = (query: Query, visitor: (node: Query) => void) => {
344
464
  Match.when({ type: 'relation' }, ({ anchor }) => visit(anchor, visitor)),
345
465
  Match.when({ type: 'options' }, ({ query }) => visit(query, visitor)),
346
466
  Match.when({ type: 'relation-traversal' }, ({ anchor }) => visit(anchor, visitor)),
467
+ Match.when({ type: 'hierarchy-traversal' }, ({ anchor }) => visit(anchor, visitor)),
347
468
  Match.when({ type: 'union' }, ({ queries }) => queries.forEach((q) => visit(q, visitor))),
348
469
  Match.when({ type: 'set-difference' }, ({ source, exclude }) => {
349
470
  visit(source, visitor);
350
471
  visit(exclude, visitor);
351
472
  }),
352
473
  Match.when({ type: 'order' }, ({ query }) => visit(query, visitor)),
474
+ Match.when({ type: 'limit' }, ({ query }) => visit(query, visitor)),
475
+ Match.when({ type: 'from' }, (node) => {
476
+ visit(node.query, visitor);
477
+ if (node.from._tag === 'query') {
478
+ visit(node.from.query, visitor);
479
+ }
480
+ }),
353
481
  Match.when({ type: 'select' }, () => {}),
354
482
  Match.exhaustive,
355
483
  );
356
484
  };
357
485
 
486
+ /**
487
+ * Recursively transforms a query tree bottom-up.
488
+ * The mapper receives each node with its children already transformed.
489
+ */
490
+ export const map = (query: Query, mapper: (node: Query) => Query): Query => {
491
+ const mapped: Query = Match.value(query).pipe(
492
+ Match.when({ type: 'filter' }, (node) => ({ ...node, selection: map(node.selection, mapper) })),
493
+ Match.when({ type: 'reference-traversal' }, (node) => ({ ...node, anchor: map(node.anchor, mapper) })),
494
+ Match.when({ type: 'incoming-references' }, (node) => ({ ...node, anchor: map(node.anchor, mapper) })),
495
+ Match.when({ type: 'relation' }, (node) => ({ ...node, anchor: map(node.anchor, mapper) })),
496
+ Match.when({ type: 'relation-traversal' }, (node) => ({ ...node, anchor: map(node.anchor, mapper) })),
497
+ Match.when({ type: 'hierarchy-traversal' }, (node) => ({ ...node, anchor: map(node.anchor, mapper) })),
498
+ Match.when({ type: 'options' }, (node) => ({ ...node, query: map(node.query, mapper) })),
499
+ Match.when({ type: 'order' }, (node) => ({ ...node, query: map(node.query, mapper) })),
500
+ Match.when({ type: 'limit' }, (node) => ({ ...node, query: map(node.query, mapper) })),
501
+ Match.when({ type: 'from' }, (node) => ({
502
+ ...node,
503
+ query: map(node.query, mapper),
504
+ ...(node.from._tag === 'query' ? { from: { _tag: 'query' as const, query: map(node.from.query, mapper) } } : {}),
505
+ })),
506
+ Match.when({ type: 'union' }, (node) => ({ ...node, queries: node.queries.map((q) => map(q, mapper)) })),
507
+ Match.when({ type: 'set-difference' }, (node) => ({
508
+ ...node,
509
+ source: map(node.source, mapper),
510
+ exclude: map(node.exclude, mapper),
511
+ })),
512
+ Match.when({ type: 'select' }, (node) => node),
513
+ Match.exhaustive,
514
+ );
515
+ return mapper(mapped);
516
+ };
517
+
358
518
  export const fold = <T>(query: Query, reducer: (node: Query) => T): T[] => {
359
519
  return Match.value(query).pipe(
360
520
  Match.withReturnType<T[]>(),
@@ -364,11 +524,20 @@ export const fold = <T>(query: Query, reducer: (node: Query) => T): T[] => {
364
524
  Match.when({ type: 'relation' }, ({ anchor }) => fold(anchor, reducer)),
365
525
  Match.when({ type: 'options' }, ({ query }) => fold(query, reducer)),
366
526
  Match.when({ type: 'relation-traversal' }, ({ anchor }) => fold(anchor, reducer)),
527
+ Match.when({ type: 'hierarchy-traversal' }, ({ anchor }) => fold(anchor, reducer)),
367
528
  Match.when({ type: 'union' }, ({ queries }) => queries.flatMap((q) => fold(q, reducer))),
368
529
  Match.when({ type: 'set-difference' }, ({ source, exclude }) =>
369
530
  fold(source, reducer).concat(fold(exclude, reducer)),
370
531
  ),
371
532
  Match.when({ type: 'order' }, ({ query }) => fold(query, reducer)),
533
+ Match.when({ type: 'limit' }, ({ query }) => fold(query, reducer)),
534
+ Match.when({ type: 'from' }, (node) => {
535
+ const results = fold(node.query, reducer);
536
+ if (node.from._tag === 'query') {
537
+ return results.concat(fold(node.from.query, reducer));
538
+ }
539
+ return results;
540
+ }),
372
541
  Match.when({ type: 'select' }, () => []),
373
542
  Match.exhaustive,
374
543
  );
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
  });