@dxos/echo-protocol 0.8.3 → 0.8.4-main.05e74ebcff

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.
Files changed (36) hide show
  1. package/LICENSE +102 -5
  2. package/README.md +1 -1
  3. package/dist/lib/neutral/index.mjs +825 -0
  4. package/dist/lib/neutral/index.mjs.map +7 -0
  5. package/dist/lib/neutral/meta.json +1 -0
  6. package/dist/types/src/document-structure.d.ts +26 -2
  7. package/dist/types/src/document-structure.d.ts.map +1 -1
  8. package/dist/types/src/echo-feed-codec.d.ts +17 -0
  9. package/dist/types/src/echo-feed-codec.d.ts.map +1 -0
  10. package/dist/types/src/foreign-key.d.ts +1 -1
  11. package/dist/types/src/foreign-key.d.ts.map +1 -1
  12. package/dist/types/src/index.d.ts +4 -3
  13. package/dist/types/src/index.d.ts.map +1 -1
  14. package/dist/types/src/query/ast.d.ts +238 -25
  15. package/dist/types/src/query/ast.d.ts.map +1 -1
  16. package/dist/types/src/reference.d.ts +17 -0
  17. package/dist/types/src/reference.d.ts.map +1 -1
  18. package/dist/types/src/space-id.d.ts.map +1 -1
  19. package/dist/types/tsconfig.tsbuildinfo +1 -1
  20. package/package.json +15 -15
  21. package/src/document-structure.ts +36 -2
  22. package/src/echo-feed-codec.ts +67 -0
  23. package/src/foreign-key.ts +4 -3
  24. package/src/index.ts +4 -3
  25. package/src/query/ast.ts +326 -34
  26. package/src/reference.ts +29 -0
  27. package/src/space-id.ts +1 -1
  28. package/dist/lib/browser/index.mjs +0 -509
  29. package/dist/lib/browser/index.mjs.map +0 -7
  30. package/dist/lib/browser/meta.json +0 -1
  31. package/dist/lib/node/index.cjs +0 -527
  32. package/dist/lib/node/index.cjs.map +0 -7
  33. package/dist/lib/node/meta.json +0 -1
  34. package/dist/lib/node-esm/index.mjs +0 -510
  35. package/dist/lib/node-esm/index.mjs.map +0 -7
  36. package/dist/lib/node-esm/meta.json +0 -1
package/src/query/ast.ts CHANGED
@@ -2,14 +2,15 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { Schema } from 'effect';
5
+ import * as Match from 'effect/Match';
6
+ import * as Schema from 'effect/Schema';
6
7
 
7
8
  import { DXN, ObjectId } from '@dxos/keys';
8
9
 
9
10
  import { ForeignKey } from '../foreign-key';
10
11
 
11
12
  const TypenameSpecifier = Schema.Union(DXN.Schema, Schema.Null).annotations({
12
- description: 'DXN or null. Null means any type will match',
13
+ description: 'DXN or null; null matches any type',
13
14
  });
14
15
 
15
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.
@@ -41,11 +42,25 @@ const FilterObject_ = Schema.Struct({
41
42
  */
42
43
  foreignKeys: Schema.optional(Schema.Array(ForeignKey)),
43
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
+
44
56
  // NOTE: Make sure to update `FilterStep.isNoop` if you change this.
45
57
  });
46
58
  export interface FilterObject extends Schema.Schema.Type<typeof FilterObject_> {}
47
59
  export const FilterObject: Schema.Schema<FilterObject> = FilterObject_;
48
60
 
61
+ /**
62
+ * Compare.
63
+ */
49
64
  const FilterCompare_ = Schema.Struct({
50
65
  type: Schema.Literal('compare'),
51
66
  operator: Schema.Literal('eq', 'neq', 'gt', 'gte', 'lt', 'lte'),
@@ -54,6 +69,9 @@ const FilterCompare_ = Schema.Struct({
54
69
  export interface FilterCompare extends Schema.Schema.Type<typeof FilterCompare_> {}
55
70
  export const FilterCompare: Schema.Schema<FilterCompare> = FilterCompare_;
56
71
 
72
+ /**
73
+ * In.
74
+ */
57
75
  const FilterIn_ = Schema.Struct({
58
76
  type: Schema.Literal('in'),
59
77
  values: Schema.Array(Schema.Any),
@@ -61,53 +79,137 @@ const FilterIn_ = Schema.Struct({
61
79
  export interface FilterIn extends Schema.Schema.Type<typeof FilterIn_> {}
62
80
  export const FilterIn: Schema.Schema<FilterIn> = FilterIn_;
63
81
 
82
+ /**
83
+ * Contains.
84
+ */
85
+ const FilterContains_ = Schema.Struct({
86
+ type: Schema.Literal('contains'),
87
+ value: Schema.Any,
88
+ });
89
+
90
+ export interface FilterContains extends Schema.Schema.Type<typeof FilterContains_> {}
91
+
92
+ /**
93
+ * Predicate for an array property to contain the provided value.
94
+ * Nested objects are matched using strict structural matching.
95
+ */
96
+ export const FilterContains: Schema.Schema<FilterContains> = FilterContains_;
97
+
98
+ /**
99
+ * Filters objects that have certain tag.
100
+ */
101
+ const FilterTag_ = Schema.Struct({
102
+ type: Schema.Literal('tag'),
103
+ tag: Schema.String, // TODO(burdon): Make OR-collection?
104
+ });
105
+
106
+ export interface FilterTag extends Schema.Schema.Type<typeof FilterTag_> {}
107
+ export const FilterTag: Schema.Schema<FilterTag> = FilterTag_;
108
+
109
+ /**
110
+ * Range.
111
+ */
64
112
  const FilterRange_ = Schema.Struct({
65
113
  type: Schema.Literal('range'),
66
114
  from: Schema.Any,
67
115
  to: Schema.Any,
68
116
  });
117
+
69
118
  export interface FilterRange extends Schema.Schema.Type<typeof FilterRange_> {}
70
119
  export const FilterRange: Schema.Schema<FilterRange> = FilterRange_;
71
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
+
135
+ /**
136
+ * Text search.
137
+ */
72
138
  const FilterTextSearch_ = Schema.Struct({
73
139
  type: Schema.Literal('text-search'),
74
140
  text: Schema.String,
75
141
  searchKind: Schema.optional(Schema.Literal('full-text', 'vector')),
76
142
  });
143
+
77
144
  export interface FilterTextSearch extends Schema.Schema.Type<typeof FilterTextSearch_> {}
78
145
  export const FilterTextSearch: Schema.Schema<FilterTextSearch> = FilterTextSearch_;
79
146
 
147
+ /**
148
+ * Not.
149
+ */
80
150
  const FilterNot_ = Schema.Struct({
81
151
  type: Schema.Literal('not'),
82
152
  filter: Schema.suspend(() => Filter),
83
153
  });
154
+
84
155
  export interface FilterNot extends Schema.Schema.Type<typeof FilterNot_> {}
85
156
  export const FilterNot: Schema.Schema<FilterNot> = FilterNot_;
86
157
 
158
+ /**
159
+ * And.
160
+ */
87
161
  const FilterAnd_ = Schema.Struct({
88
162
  type: Schema.Literal('and'),
89
163
  filters: Schema.Array(Schema.suspend(() => Filter)),
90
164
  });
165
+
91
166
  export interface FilterAnd extends Schema.Schema.Type<typeof FilterAnd_> {}
92
167
  export const FilterAnd: Schema.Schema<FilterAnd> = FilterAnd_;
93
168
 
169
+ /**
170
+ * Or.
171
+ */
94
172
  const FilterOr_ = Schema.Struct({
95
173
  type: Schema.Literal('or'),
96
174
  filters: Schema.Array(Schema.suspend(() => Filter)),
97
175
  });
176
+
98
177
  export interface FilterOr extends Schema.Schema.Type<typeof FilterOr_> {}
99
178
  export const FilterOr: Schema.Schema<FilterOr> = FilterOr_;
100
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
+
195
+ /**
196
+ * Union of filters.
197
+ */
101
198
  export const Filter = Schema.Union(
102
199
  FilterObject,
103
- FilterTextSearch,
104
200
  FilterCompare,
105
201
  FilterIn,
202
+ FilterContains,
203
+ FilterTag,
106
204
  FilterRange,
205
+ FilterTimestamp,
206
+ FilterTextSearch,
207
+ FilterChildOf,
107
208
  FilterNot,
108
209
  FilterAnd,
109
210
  FilterOr,
110
- );
211
+ ).annotations({ identifier: 'org.dxos.schema.filter' });
212
+
111
213
  export type Filter = Schema.Schema.Type<typeof Filter>;
112
214
 
113
215
  /**
@@ -117,6 +219,7 @@ const QuerySelectClause_ = Schema.Struct({
117
219
  type: Schema.Literal('select'),
118
220
  filter: Schema.suspend(() => Filter),
119
221
  });
222
+
120
223
  export interface QuerySelectClause extends Schema.Schema.Type<typeof QuerySelectClause_> {}
121
224
  export const QuerySelectClause: Schema.Schema<QuerySelectClause> = QuerySelectClause_;
122
225
 
@@ -128,6 +231,7 @@ const QueryFilterClause_ = Schema.Struct({
128
231
  selection: Schema.suspend(() => Query),
129
232
  filter: Schema.suspend(() => Filter),
130
233
  });
234
+
131
235
  export interface QueryFilterClause extends Schema.Schema.Type<typeof QueryFilterClause_> {}
132
236
  export const QueryFilterClause: Schema.Schema<QueryFilterClause> = QueryFilterClause_;
133
237
 
@@ -139,6 +243,7 @@ const QueryReferenceTraversalClause_ = Schema.Struct({
139
243
  anchor: Schema.suspend(() => Query),
140
244
  property: Schema.String, // TODO(dmaretskyi): Change to EscapedPropPath.
141
245
  });
246
+
142
247
  export interface QueryReferenceTraversalClause extends Schema.Schema.Type<typeof QueryReferenceTraversalClause_> {}
143
248
  export const QueryReferenceTraversalClause: Schema.Schema<QueryReferenceTraversalClause> =
144
249
  QueryReferenceTraversalClause_;
@@ -149,9 +254,14 @@ export const QueryReferenceTraversalClause: Schema.Schema<QueryReferenceTraversa
149
254
  const QueryIncomingReferencesClause_ = Schema.Struct({
150
255
  type: Schema.Literal('incoming-references'),
151
256
  anchor: Schema.suspend(() => Query),
152
- 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),
153
262
  typename: TypenameSpecifier,
154
263
  });
264
+
155
265
  export interface QueryIncomingReferencesClause extends Schema.Schema.Type<typeof QueryIncomingReferencesClause_> {}
156
266
  export const QueryIncomingReferencesClause: Schema.Schema<QueryIncomingReferencesClause> =
157
267
  QueryIncomingReferencesClause_;
@@ -170,6 +280,7 @@ const QueryRelationClause_ = Schema.Struct({
170
280
  direction: Schema.Literal('outgoing', 'incoming', 'both'),
171
281
  filter: Schema.optional(Schema.suspend(() => Filter)),
172
282
  });
283
+
173
284
  export interface QueryRelationClause extends Schema.Schema.Type<typeof QueryRelationClause_> {}
174
285
  export const QueryRelationClause: Schema.Schema<QueryRelationClause> = QueryRelationClause_;
175
286
 
@@ -181,9 +292,27 @@ const QueryRelationTraversalClause_ = Schema.Struct({
181
292
  anchor: Schema.suspend(() => Query),
182
293
  direction: Schema.Literal('source', 'target', 'both'),
183
294
  });
295
+
184
296
  export interface QueryRelationTraversalClause extends Schema.Schema.Type<typeof QueryRelationTraversalClause_> {}
185
297
  export const QueryRelationTraversalClause: Schema.Schema<QueryRelationTraversalClause> = QueryRelationTraversalClause_;
186
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
+
187
316
  /**
188
317
  * Union of multiple queries.
189
318
  */
@@ -191,6 +320,7 @@ const QueryUnionClause_ = Schema.Struct({
191
320
  type: Schema.Literal('union'),
192
321
  queries: Schema.Array(Schema.suspend(() => Query)),
193
322
  });
323
+
194
324
  export interface QueryUnionClause extends Schema.Schema.Type<typeof QueryUnionClause_> {}
195
325
  export const QueryUnionClause: Schema.Schema<QueryUnionClause> = QueryUnionClause_;
196
326
 
@@ -202,9 +332,47 @@ const QuerySetDifferenceClause_ = Schema.Struct({
202
332
  source: Schema.suspend(() => Query),
203
333
  exclude: Schema.suspend(() => Query),
204
334
  });
335
+
205
336
  export interface QuerySetDifferenceClause extends Schema.Schema.Type<typeof QuerySetDifferenceClause_> {}
206
337
  export const QuerySetDifferenceClause: Schema.Schema<QuerySetDifferenceClause> = QuerySetDifferenceClause_;
207
338
 
339
+ export const OrderDirection = Schema.Literal('asc', 'desc');
340
+ export type OrderDirection = Schema.Schema.Type<typeof OrderDirection>;
341
+
342
+ const Order_ = Schema.Union(
343
+ Schema.Struct({
344
+ // How database wants to order them (in practice - by id).
345
+ kind: Schema.Literal('natural'),
346
+ }),
347
+ Schema.Struct({
348
+ kind: Schema.Literal('property'),
349
+ property: Schema.String,
350
+ direction: OrderDirection,
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
+ }),
358
+ );
359
+
360
+ export type Order = Schema.Schema.Type<typeof Order_>;
361
+ export const Order: Schema.Schema<Order> = Order_;
362
+
363
+ /**
364
+ * Order the query results.
365
+ * Left-to-right the orders dominate.
366
+ */
367
+ const QueryOrderClause_ = Schema.Struct({
368
+ type: Schema.Literal('order'),
369
+ query: Schema.suspend(() => Query),
370
+ order: Schema.Array(Order),
371
+ });
372
+
373
+ export interface QueryOrderClause extends Schema.Schema.Type<typeof QueryOrderClause_> {}
374
+ export const QueryOrderClause: Schema.Schema<QueryOrderClause> = QueryOrderClause_;
375
+
208
376
  /**
209
377
  * Add options to a query.
210
378
  */
@@ -213,9 +381,37 @@ const QueryOptionsClause_ = Schema.Struct({
213
381
  query: Schema.suspend(() => Query),
214
382
  options: Schema.suspend(() => QueryOptions),
215
383
  });
384
+
216
385
  export interface QueryOptionsClause extends Schema.Schema.Type<typeof QueryOptionsClause_> {}
217
386
  export const QueryOptionsClause: Schema.Schema<QueryOptionsClause> = QueryOptionsClause_;
218
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
+
219
415
  const Query_ = Schema.Union(
220
416
  QuerySelectClause,
221
417
  QueryFilterClause,
@@ -223,46 +419,142 @@ const Query_ = Schema.Union(
223
419
  QueryIncomingReferencesClause,
224
420
  QueryRelationClause,
225
421
  QueryRelationTraversalClause,
422
+ QueryHierarchyTraversalClause,
226
423
  QueryUnionClause,
227
424
  QuerySetDifferenceClause,
425
+ QueryOrderClause,
228
426
  QueryOptionsClause,
229
- );
427
+ QueryLimitClause,
428
+ QueryFromClause,
429
+ ).annotations({ identifier: 'org.dxos.schema.query' });
230
430
 
231
431
  export type Query = Schema.Schema.Type<typeof Query_>;
232
432
  export const Query: Schema.Schema<Query> = Query_;
233
433
 
234
434
  export const QueryOptions = Schema.Struct({
235
- spaceIds: Schema.optional(Schema.Array(Schema.String)),
435
+ /**
436
+ * Nested select statements will use this option to filter deleted objects.
437
+ */
236
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),
237
444
  });
445
+
238
446
  export interface QueryOptions extends Schema.Schema.Type<typeof QueryOptions> {}
239
447
 
448
+ /**
449
+ * Specifies the scope of the data to query from.
450
+ */
451
+ export const Scope = Schema.Struct({
452
+ /**
453
+ * The nested select statemets will select from the given spaces.
454
+ *
455
+ * NOTE: Spaces and feeds are unioned together if both are specified.
456
+ */
457
+ spaceIds: Schema.optional(Schema.Array(Schema.String)),
458
+
459
+ /**
460
+ * If true, the nested select statements will select from all feeds in the spaces specified by `spaceIds`.
461
+ */
462
+ allFeedsFromSpaces: Schema.optional(Schema.Boolean),
463
+
464
+ /**
465
+ * The nested select statemets will select from the given feeds (by underlying queue DXN).
466
+ *
467
+ * NOTE: Spaces and feeds are unioned together if both are specified.
468
+ */
469
+ feeds: Schema.optional(Schema.Array(DXN.Schema)),
470
+ });
471
+ export interface Scope extends Schema.Schema.Type<typeof Scope> {}
472
+
240
473
  export const visit = (query: Query, visitor: (node: Query) => void) => {
241
- switch (query.type) {
242
- case 'filter':
243
- visit(query.selection, visitor);
244
- break;
245
- case 'reference-traversal':
246
- visit(query.anchor, visitor);
247
- break;
248
- case 'incoming-references':
249
- visit(query.anchor, visitor);
250
- break;
251
- case 'relation':
252
- visit(query.anchor, visitor);
253
- break;
254
- case 'options':
255
- visit(query.query, visitor);
256
- break;
257
- case 'relation-traversal':
258
- visit(query.anchor, visitor);
259
- break;
260
- case 'union':
261
- query.queries.forEach((q) => visit(q, visitor));
262
- break;
263
- case 'set-difference':
264
- visit(query.source, visitor);
265
- visit(query.exclude, visitor);
266
- break;
267
- }
474
+ visitor(query);
475
+
476
+ Match.value(query).pipe(
477
+ Match.when({ type: 'filter' }, ({ selection }) => visit(selection, visitor)),
478
+ Match.when({ type: 'reference-traversal' }, ({ anchor }) => visit(anchor, visitor)),
479
+ Match.when({ type: 'incoming-references' }, ({ anchor }) => visit(anchor, visitor)),
480
+ Match.when({ type: 'relation' }, ({ anchor }) => visit(anchor, visitor)),
481
+ Match.when({ type: 'options' }, ({ query }) => visit(query, visitor)),
482
+ Match.when({ type: 'relation-traversal' }, ({ anchor }) => visit(anchor, visitor)),
483
+ Match.when({ type: 'hierarchy-traversal' }, ({ anchor }) => visit(anchor, visitor)),
484
+ Match.when({ type: 'union' }, ({ queries }) => queries.forEach((q) => visit(q, visitor))),
485
+ Match.when({ type: 'set-difference' }, ({ source, exclude }) => {
486
+ visit(source, visitor);
487
+ visit(exclude, visitor);
488
+ }),
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
+ }),
497
+ Match.when({ type: 'select' }, () => {}),
498
+ Match.exhaustive,
499
+ );
500
+ };
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
+
534
+ export const fold = <T>(query: Query, reducer: (node: Query) => T): T[] => {
535
+ return Match.value(query).pipe(
536
+ Match.withReturnType<T[]>(),
537
+ Match.when({ type: 'filter' }, ({ selection }) => fold(selection, reducer)),
538
+ Match.when({ type: 'reference-traversal' }, ({ anchor }) => fold(anchor, reducer)),
539
+ Match.when({ type: 'incoming-references' }, ({ anchor }) => fold(anchor, reducer)),
540
+ Match.when({ type: 'relation' }, ({ anchor }) => fold(anchor, reducer)),
541
+ Match.when({ type: 'options' }, ({ query }) => fold(query, reducer)),
542
+ Match.when({ type: 'relation-traversal' }, ({ anchor }) => fold(anchor, reducer)),
543
+ Match.when({ type: 'hierarchy-traversal' }, ({ anchor }) => fold(anchor, reducer)),
544
+ Match.when({ type: 'union' }, ({ queries }) => queries.flatMap((q) => fold(q, reducer))),
545
+ Match.when({ type: 'set-difference' }, ({ source, exclude }) =>
546
+ fold(source, reducer).concat(fold(exclude, reducer)),
547
+ ),
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
+ }),
557
+ Match.when({ type: 'select' }, () => []),
558
+ Match.exhaustive,
559
+ );
268
560
  };
package/src/reference.ts CHANGED
@@ -2,6 +2,7 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
 
5
+ import { assertArgument } from '@dxos/invariant';
5
6
  import { DXN, LOCAL_SPACE_TAG, type PublicKey } from '@dxos/keys';
6
7
  import { type ObjectId } from '@dxos/protocols';
7
8
  import { type Reference as ReferenceProto } from '@dxos/protocols/proto/dxos/echo/model/document';
@@ -9,6 +10,7 @@ import { type Reference as ReferenceProto } from '@dxos/protocols/proto/dxos/ech
9
10
  /**
10
11
  * Runtime representation of an reference in ECHO.
11
12
  * Implemented as a DXN, but we might extend it to other URIs in the future.
13
+ * @deprecated Use `EncodedReference` instead.
12
14
  */
13
15
  export class Reference {
14
16
  /**
@@ -122,6 +124,7 @@ export class Reference {
122
124
  }
123
125
  }
124
126
 
127
+ // TODO(dmaretskyi): Is this used anywhere?
125
128
  export const REFERENCE_TYPE_TAG = 'dxos.echo.model.document.Reference';
126
129
 
127
130
  /**
@@ -131,10 +134,16 @@ export type EncodedReference = {
131
134
  '/': string;
132
135
  };
133
136
 
137
+ /**
138
+ * @deprecated Use `EncodedReference.fromDXN` instead.
139
+ */
134
140
  export const encodeReference = (reference: Reference): EncodedReference => ({
135
141
  '/': reference.toDXN().toString(),
136
142
  });
137
143
 
144
+ /**
145
+ * @deprecated Use `EncodedReference.toDXN` instead.
146
+ */
138
147
  export const decodeReference = (value: any) => {
139
148
  if (typeof value !== 'object' || value === null || typeof value['/'] !== 'string') {
140
149
  throw new Error('Invalid reference');
@@ -152,5 +161,25 @@ export const decodeReference = (value: any) => {
152
161
  return Reference.fromDXN(DXN.parse(dxnString));
153
162
  };
154
163
 
164
+ /**
165
+ * @deprecated Use `EncodedReference.isEncodedReference` instead.
166
+ */
155
167
  export const isEncodedReference = (value: any): value is EncodedReference =>
156
168
  typeof value === 'object' && value !== null && Object.keys(value).length === 1 && typeof value['/'] === 'string';
169
+
170
+ export const EncodedReference = Object.freeze({
171
+ isEncodedReference,
172
+ getReferenceString: (value: EncodedReference): string => {
173
+ assertArgument(isEncodedReference(value), 'value', 'invalid reference');
174
+ return value['/'];
175
+ },
176
+ toDXN: (value: EncodedReference): DXN => {
177
+ return DXN.parse(EncodedReference.getReferenceString(value));
178
+ },
179
+ fromDXN: (dxn: DXN): EncodedReference => {
180
+ return { '/': dxn.toString() };
181
+ },
182
+ fromLegacyTypename: (typename: string): EncodedReference => {
183
+ return { '/': DXN.fromTypename(typename).toString() };
184
+ },
185
+ });
package/src/space-id.ts CHANGED
@@ -18,7 +18,7 @@ export const createIdFromSpaceKey = async (spaceKey: PublicKey): Promise<SpaceId
18
18
  return cachedValue;
19
19
  }
20
20
 
21
- const digest = await subtleCrypto.digest('SHA-256', spaceKey.asUint8Array());
21
+ const digest = await subtleCrypto.digest('SHA-256', spaceKey.asUint8Array() as Uint8Array<ArrayBuffer>);
22
22
 
23
23
  const bytes = new Uint8Array(digest).slice(0, SpaceId.byteLength);
24
24
  const spaceId = SpaceId.encode(bytes);