@dxos/echo 0.10.0 → 0.11.1

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 (274) hide show
  1. package/dist/lib/Annotation.mjs +3 -0
  2. package/dist/lib/Blob.mjs +160 -0
  3. package/dist/lib/Blob.mjs.map +1 -0
  4. package/dist/lib/Database.mjs +2 -0
  5. package/dist/lib/Entity.mjs +231 -0
  6. package/dist/lib/Entity.mjs.map +1 -0
  7. package/dist/lib/Err.mjs +105 -0
  8. package/dist/lib/Err.mjs.map +1 -0
  9. package/dist/lib/Feed.mjs +338 -0
  10. package/dist/lib/Feed.mjs.map +1 -0
  11. package/dist/lib/Filter.mjs +380 -0
  12. package/dist/lib/Filter.mjs.map +1 -0
  13. package/dist/lib/Format.mjs +47 -0
  14. package/dist/lib/Format.mjs.map +1 -0
  15. package/dist/lib/JsonSchema.mjs +21 -0
  16. package/dist/lib/JsonSchema.mjs.map +1 -0
  17. package/dist/lib/Key.mjs +15 -0
  18. package/dist/lib/Key.mjs.map +1 -0
  19. package/dist/lib/Migration.mjs +38 -0
  20. package/dist/lib/Migration.mjs.map +1 -0
  21. package/dist/lib/Obj.mjs +660 -0
  22. package/dist/lib/Obj.mjs.map +1 -0
  23. package/dist/lib/Order.mjs +65 -0
  24. package/dist/lib/Order.mjs.map +1 -0
  25. package/dist/lib/Query.mjs +336 -0
  26. package/dist/lib/Query.mjs.map +1 -0
  27. package/dist/lib/QueryResult.mjs +7 -0
  28. package/dist/lib/QueryResult.mjs.map +1 -0
  29. package/dist/lib/Ref.mjs +44 -0
  30. package/dist/lib/Ref.mjs.map +1 -0
  31. package/dist/lib/Registry.mjs +35 -0
  32. package/dist/lib/Registry.mjs.map +1 -0
  33. package/dist/lib/Relation.mjs +397 -0
  34. package/dist/lib/Relation.mjs.map +1 -0
  35. package/dist/lib/Scope.mjs +51 -0
  36. package/dist/lib/Scope.mjs.map +1 -0
  37. package/dist/lib/Tag.mjs +70 -0
  38. package/dist/lib/Tag.mjs.map +1 -0
  39. package/dist/lib/Text.mjs +82 -0
  40. package/dist/lib/Text.mjs.map +1 -0
  41. package/dist/lib/Type.mjs +371 -0
  42. package/dist/lib/Type.mjs.map +1 -0
  43. package/dist/lib/chunk-Annotation.mjs +221 -0
  44. package/dist/lib/chunk-Annotation.mjs.map +1 -0
  45. package/dist/lib/chunk-Database.mjs +918 -0
  46. package/dist/lib/chunk-Database.mjs.map +1 -0
  47. package/dist/lib/chunk-atom-snapshot.mjs +249 -0
  48. package/dist/lib/chunk-atom-snapshot.mjs.map +1 -0
  49. package/dist/lib/chunk-internal.mjs +2775 -0
  50. package/dist/lib/chunk-internal.mjs.map +1 -0
  51. package/dist/lib/chunk-json-schema.mjs +660 -0
  52. package/dist/lib/chunk-json-schema.mjs.map +1 -0
  53. package/dist/lib/chunk-number.mjs +164 -0
  54. package/dist/lib/chunk-number.mjs.map +1 -0
  55. package/dist/lib/chunk-ref-array.mjs +31 -0
  56. package/dist/lib/chunk-ref-array.mjs.map +1 -0
  57. package/dist/lib/chunk-ref.mjs +590 -0
  58. package/dist/lib/chunk-ref.mjs.map +1 -0
  59. package/dist/lib/chunk-rolldown-runtime.mjs +13 -0
  60. package/dist/lib/chunk-select.mjs +186 -0
  61. package/dist/lib/chunk-select.mjs.map +1 -0
  62. package/dist/lib/chunk-src.mjs +234 -0
  63. package/dist/lib/chunk-src.mjs.map +1 -0
  64. package/dist/lib/chunk-util.mjs +140 -0
  65. package/dist/lib/chunk-util.mjs.map +1 -0
  66. package/dist/lib/index.mjs +24 -0
  67. package/dist/lib/internal.mjs +10 -0
  68. package/dist/lib/testing.mjs +346 -0
  69. package/dist/lib/testing.mjs.map +1 -0
  70. package/dist/types/src/Aggregate.d.ts +86 -0
  71. package/dist/types/src/Aggregate.d.ts.map +1 -0
  72. package/dist/types/src/Blob.d.ts +153 -0
  73. package/dist/types/src/Blob.d.ts.map +1 -0
  74. package/dist/types/src/Blob.test.d.ts +2 -0
  75. package/dist/types/src/Blob.test.d.ts.map +1 -0
  76. package/dist/types/src/Database.d.ts +153 -4
  77. package/dist/types/src/Database.d.ts.map +1 -1
  78. package/dist/types/src/Err.d.ts +116 -0
  79. package/dist/types/src/Err.d.ts.map +1 -1
  80. package/dist/types/src/Feed.d.ts +116 -28
  81. package/dist/types/src/Feed.d.ts.map +1 -1
  82. package/dist/types/src/Feed.test.d.ts +2 -0
  83. package/dist/types/src/Feed.test.d.ts.map +1 -0
  84. package/dist/types/src/Filter.d.ts +14 -6
  85. package/dist/types/src/Filter.d.ts.map +1 -1
  86. package/dist/types/src/Hypergraph.d.ts +18 -0
  87. package/dist/types/src/Hypergraph.d.ts.map +1 -1
  88. package/dist/types/src/Json.d.ts.map +1 -1
  89. package/dist/types/src/Obj.d.ts +14 -1
  90. package/dist/types/src/Obj.d.ts.map +1 -1
  91. package/dist/types/src/Order.d.ts +9 -3
  92. package/dist/types/src/Order.d.ts.map +1 -1
  93. package/dist/types/src/Query.d.ts +107 -26
  94. package/dist/types/src/Query.d.ts.map +1 -1
  95. package/dist/types/src/Relation.d.ts +6 -0
  96. package/dist/types/src/Relation.d.ts.map +1 -1
  97. package/dist/types/src/Type.d.ts.map +1 -1
  98. package/dist/types/src/View.d.ts +1 -1
  99. package/dist/types/src/index.d.ts +2 -0
  100. package/dist/types/src/index.d.ts.map +1 -1
  101. package/dist/types/src/internal/Entity/api.d.ts +6 -0
  102. package/dist/types/src/internal/Entity/api.d.ts.map +1 -1
  103. package/dist/types/src/internal/Entity/model.d.ts +3 -2
  104. package/dist/types/src/internal/Entity/model.d.ts.map +1 -1
  105. package/dist/types/src/internal/Filter/match.d.ts.map +1 -1
  106. package/dist/types/src/internal/JsonSchema/json-schema-type.d.ts +34 -8
  107. package/dist/types/src/internal/JsonSchema/json-schema-type.d.ts.map +1 -1
  108. package/dist/types/src/internal/JsonSchema/json-schema.d.ts.map +1 -1
  109. package/dist/types/src/internal/Obj/atoms.d.ts.map +1 -1
  110. package/dist/types/src/internal/Obj/json-serializer.d.ts.map +1 -1
  111. package/dist/types/src/internal/Query/pretty.d.ts.map +1 -1
  112. package/dist/types/src/internal/Query/query-result-effect.d.ts.map +1 -1
  113. package/dist/types/src/internal/common/proxy/make-object.d.ts +13 -0
  114. package/dist/types/src/internal/common/proxy/make-object.d.ts.map +1 -1
  115. package/dist/types/src/internal/common/proxy/reactive.d.ts.map +1 -1
  116. package/dist/types/src/internal/common/proxy/symbols.d.ts.map +1 -1
  117. package/dist/types/src/internal/common/proxy/typed-handler.d.ts +12 -0
  118. package/dist/types/src/internal/common/proxy/typed-handler.d.ts.map +1 -1
  119. package/dist/types/src/internal/common/types/index.d.ts +1 -0
  120. package/dist/types/src/internal/common/types/index.d.ts.map +1 -1
  121. package/dist/types/src/internal/common/types/model-symbols.d.ts +5 -0
  122. package/dist/types/src/internal/common/types/model-symbols.d.ts.map +1 -1
  123. package/dist/types/src/internal/common/types/projection.d.ts +32 -0
  124. package/dist/types/src/internal/common/types/projection.d.ts.map +1 -0
  125. package/dist/types/tsconfig.tsbuildinfo +1 -1
  126. package/package.json +40 -35
  127. package/src/Aggregate.ts +105 -0
  128. package/src/Blob.test.ts +45 -0
  129. package/src/Blob.ts +205 -0
  130. package/src/Database.ts +207 -17
  131. package/src/Err.ts +36 -0
  132. package/src/Feed.test.ts +188 -0
  133. package/src/Feed.ts +238 -62
  134. package/src/Filter.ts +28 -10
  135. package/src/Hypergraph.ts +18 -0
  136. package/src/Json.ts +1 -0
  137. package/src/Obj.test.ts +2 -2
  138. package/src/Obj.ts +21 -1
  139. package/src/Order.ts +16 -9
  140. package/src/Query.test.ts +319 -2
  141. package/src/Query.ts +165 -51
  142. package/src/Relation.ts +14 -0
  143. package/src/Type.ts +5 -6
  144. package/src/index.ts +2 -0
  145. package/src/internal/Entity/api.ts +11 -1
  146. package/src/internal/Entity/model.ts +3 -0
  147. package/src/internal/Filter/match.ts +30 -1
  148. package/src/internal/JsonSchema/json-schema-type.ts +14 -2
  149. package/src/internal/JsonSchema/json-schema.test.ts +28 -1
  150. package/src/internal/JsonSchema/json-schema.ts +33 -6
  151. package/src/internal/Obj/atoms.ts +4 -2
  152. package/src/internal/Obj/json-serializer.test.ts +162 -2
  153. package/src/internal/Obj/json-serializer.ts +6 -0
  154. package/src/internal/Query/pretty.ts +19 -1
  155. package/src/internal/Query/query-result-effect.ts +2 -4
  156. package/src/internal/Ref/atoms.ts +1 -1
  157. package/src/internal/common/proxy/change-context.ts +1 -1
  158. package/src/internal/common/proxy/make-object.ts +42 -3
  159. package/src/internal/common/proxy/reactive.ts +5 -0
  160. package/src/internal/common/proxy/symbols.ts +1 -0
  161. package/src/internal/common/proxy/typed-handler.ts +71 -29
  162. package/src/internal/common/types/index.ts +1 -0
  163. package/src/internal/common/types/model-symbols.ts +6 -0
  164. package/src/internal/common/types/projection.ts +45 -0
  165. package/src/testing/test-data.ts +14 -14
  166. package/dist/lib/neutral/Annotation.mjs +0 -90
  167. package/dist/lib/neutral/Annotation.mjs.map +0 -7
  168. package/dist/lib/neutral/Database.mjs +0 -42
  169. package/dist/lib/neutral/Database.mjs.map +0 -7
  170. package/dist/lib/neutral/Entity.mjs +0 -76
  171. package/dist/lib/neutral/Entity.mjs.map +0 -7
  172. package/dist/lib/neutral/Err.mjs +0 -16
  173. package/dist/lib/neutral/Err.mjs.map +0 -7
  174. package/dist/lib/neutral/Feed.mjs +0 -50
  175. package/dist/lib/neutral/Feed.mjs.map +0 -7
  176. package/dist/lib/neutral/Filter.mjs +0 -72
  177. package/dist/lib/neutral/Filter.mjs.map +0 -7
  178. package/dist/lib/neutral/Format.mjs +0 -82
  179. package/dist/lib/neutral/Format.mjs.map +0 -7
  180. package/dist/lib/neutral/JsonSchema.mjs +0 -18
  181. package/dist/lib/neutral/JsonSchema.mjs.map +0 -7
  182. package/dist/lib/neutral/Key.mjs +0 -16
  183. package/dist/lib/neutral/Key.mjs.map +0 -7
  184. package/dist/lib/neutral/Migration.mjs +0 -18
  185. package/dist/lib/neutral/Migration.mjs.map +0 -7
  186. package/dist/lib/neutral/Obj.mjs +0 -128
  187. package/dist/lib/neutral/Obj.mjs.map +0 -7
  188. package/dist/lib/neutral/Order.mjs +0 -16
  189. package/dist/lib/neutral/Order.mjs.map +0 -7
  190. package/dist/lib/neutral/Query.mjs +0 -38
  191. package/dist/lib/neutral/Query.mjs.map +0 -7
  192. package/dist/lib/neutral/QueryResult.mjs +0 -2
  193. package/dist/lib/neutral/QueryResult.mjs.map +0 -7
  194. package/dist/lib/neutral/Ref.mjs +0 -25
  195. package/dist/lib/neutral/Ref.mjs.map +0 -7
  196. package/dist/lib/neutral/Registry.mjs +0 -14
  197. package/dist/lib/neutral/Registry.mjs.map +0 -7
  198. package/dist/lib/neutral/Relation.mjs +0 -97
  199. package/dist/lib/neutral/Relation.mjs.map +0 -7
  200. package/dist/lib/neutral/Scope.mjs +0 -12
  201. package/dist/lib/neutral/Scope.mjs.map +0 -7
  202. package/dist/lib/neutral/Tag.mjs +0 -32
  203. package/dist/lib/neutral/Tag.mjs.map +0 -7
  204. package/dist/lib/neutral/Text.mjs +0 -25
  205. package/dist/lib/neutral/Text.mjs.map +0 -7
  206. package/dist/lib/neutral/Type.mjs +0 -65
  207. package/dist/lib/neutral/Type.mjs.map +0 -7
  208. package/dist/lib/neutral/chunk-2HKGAQM4.mjs +0 -1394
  209. package/dist/lib/neutral/chunk-2HKGAQM4.mjs.map +0 -7
  210. package/dist/lib/neutral/chunk-2ZHECXUR.mjs +0 -95
  211. package/dist/lib/neutral/chunk-2ZHECXUR.mjs.map +0 -7
  212. package/dist/lib/neutral/chunk-3KTE2Y7Q.mjs +0 -336
  213. package/dist/lib/neutral/chunk-3KTE2Y7Q.mjs.map +0 -7
  214. package/dist/lib/neutral/chunk-3TN2VMI4.mjs +0 -714
  215. package/dist/lib/neutral/chunk-3TN2VMI4.mjs.map +0 -7
  216. package/dist/lib/neutral/chunk-42L4HJ3Z.mjs +0 -104
  217. package/dist/lib/neutral/chunk-42L4HJ3Z.mjs.map +0 -7
  218. package/dist/lib/neutral/chunk-4A2NWKRJ.mjs +0 -747
  219. package/dist/lib/neutral/chunk-4A2NWKRJ.mjs.map +0 -7
  220. package/dist/lib/neutral/chunk-7LOUAPYZ.mjs +0 -25
  221. package/dist/lib/neutral/chunk-7LOUAPYZ.mjs.map +0 -7
  222. package/dist/lib/neutral/chunk-ACFAOTKJ.mjs +0 -1531
  223. package/dist/lib/neutral/chunk-ACFAOTKJ.mjs.map +0 -7
  224. package/dist/lib/neutral/chunk-BEFR7LEA.mjs +0 -170
  225. package/dist/lib/neutral/chunk-BEFR7LEA.mjs.map +0 -7
  226. package/dist/lib/neutral/chunk-BXIKXT7Q.mjs +0 -89
  227. package/dist/lib/neutral/chunk-BXIKXT7Q.mjs.map +0 -7
  228. package/dist/lib/neutral/chunk-C57XMMBY.mjs +0 -206
  229. package/dist/lib/neutral/chunk-C57XMMBY.mjs.map +0 -7
  230. package/dist/lib/neutral/chunk-C62JYFAD.mjs +0 -183
  231. package/dist/lib/neutral/chunk-C62JYFAD.mjs.map +0 -7
  232. package/dist/lib/neutral/chunk-FSGX2R62.mjs +0 -48
  233. package/dist/lib/neutral/chunk-FSGX2R62.mjs.map +0 -7
  234. package/dist/lib/neutral/chunk-HBXT3GSB.mjs +0 -459
  235. package/dist/lib/neutral/chunk-HBXT3GSB.mjs.map +0 -7
  236. package/dist/lib/neutral/chunk-IQR45HAD.mjs +0 -31
  237. package/dist/lib/neutral/chunk-IQR45HAD.mjs.map +0 -7
  238. package/dist/lib/neutral/chunk-J5LGTIGS.mjs +0 -10
  239. package/dist/lib/neutral/chunk-J5LGTIGS.mjs.map +0 -7
  240. package/dist/lib/neutral/chunk-KCBW4BR6.mjs +0 -424
  241. package/dist/lib/neutral/chunk-KCBW4BR6.mjs.map +0 -7
  242. package/dist/lib/neutral/chunk-L3E5IXAE.mjs +0 -575
  243. package/dist/lib/neutral/chunk-L3E5IXAE.mjs.map +0 -7
  244. package/dist/lib/neutral/chunk-PRC4FEKJ.mjs +0 -56
  245. package/dist/lib/neutral/chunk-PRC4FEKJ.mjs.map +0 -7
  246. package/dist/lib/neutral/chunk-SCT6FFCR.mjs +0 -351
  247. package/dist/lib/neutral/chunk-SCT6FFCR.mjs.map +0 -7
  248. package/dist/lib/neutral/chunk-TOCVVFK2.mjs +0 -75
  249. package/dist/lib/neutral/chunk-TOCVVFK2.mjs.map +0 -7
  250. package/dist/lib/neutral/chunk-U53LSQQA.mjs +0 -41
  251. package/dist/lib/neutral/chunk-U53LSQQA.mjs.map +0 -7
  252. package/dist/lib/neutral/chunk-UGFFV2FR.mjs +0 -27
  253. package/dist/lib/neutral/chunk-UGFFV2FR.mjs.map +0 -7
  254. package/dist/lib/neutral/chunk-UWLP4RFM.mjs +0 -7
  255. package/dist/lib/neutral/chunk-UWLP4RFM.mjs.map +0 -7
  256. package/dist/lib/neutral/chunk-UZHCQ6NI.mjs +0 -283
  257. package/dist/lib/neutral/chunk-UZHCQ6NI.mjs.map +0 -7
  258. package/dist/lib/neutral/chunk-WISOH2XH.mjs +0 -36
  259. package/dist/lib/neutral/chunk-WISOH2XH.mjs.map +0 -7
  260. package/dist/lib/neutral/chunk-WKIT2YY5.mjs +0 -34
  261. package/dist/lib/neutral/chunk-WKIT2YY5.mjs.map +0 -7
  262. package/dist/lib/neutral/chunk-XB6ISRAO.mjs +0 -271
  263. package/dist/lib/neutral/chunk-XB6ISRAO.mjs.map +0 -7
  264. package/dist/lib/neutral/chunk-XXL42WCB.mjs +0 -84
  265. package/dist/lib/neutral/chunk-XXL42WCB.mjs.map +0 -7
  266. package/dist/lib/neutral/chunk-XXWK4J53.mjs +0 -117
  267. package/dist/lib/neutral/chunk-XXWK4J53.mjs.map +0 -7
  268. package/dist/lib/neutral/index.mjs +0 -115
  269. package/dist/lib/neutral/index.mjs.map +0 -7
  270. package/dist/lib/neutral/internal/index.mjs +0 -581
  271. package/dist/lib/neutral/internal/index.mjs.map +0 -7
  272. package/dist/lib/neutral/meta.json +0 -1
  273. package/dist/lib/neutral/testing/index.mjs +0 -477
  274. package/dist/lib/neutral/testing/index.mjs.map +0 -7
package/src/Query.ts CHANGED
@@ -8,12 +8,13 @@ import type * as EffectArray from 'effect/Array';
8
8
  import type * as Schema from 'effect/Schema';
9
9
 
10
10
  import { type QueryAST } from '@dxos/echo-protocol';
11
- import { type URI } from '@dxos/keys';
11
+ import { EID, type URI } from '@dxos/keys';
12
12
 
13
+ import type * as Aggregate from './Aggregate';
13
14
  import type * as Collection from './Collection';
14
15
  import * as Database from './Database';
15
16
  import type * as Dataset from './Dataset';
16
- import * as Feed from './Feed';
17
+ import type * as Feed from './Feed';
17
18
  import * as Filter from './Filter';
18
19
  import * as internal from './internal';
19
20
  import * as Obj from './Obj';
@@ -45,27 +46,49 @@ type ReferenceTraversalTarget<P> = P extends Ref.Unknown
45
46
  ? Ref.Target<RefArrayElement<P>>
46
47
  : never;
47
48
 
49
+ /**
50
+ * Phantom brand on the flat row produced by {@link Query.aggregate}. Present only at the type level
51
+ * (never at runtime), it lets hooks like `useQuery`/`usePagination` distinguish an aggregate-row
52
+ * query from an entity query and avoid wrapping the row in `Entity.Entity`. The brand is a required
53
+ * property so `T extends AggregateResult` discriminates — an optional one would be satisfied by any
54
+ * type. Consumers never read it.
55
+ */
56
+ export interface AggregateResult {
57
+ readonly '~@dxos/echo/Query.AggregateResult': true;
58
+ }
59
+
60
+ export const QueryTypeId = '~@dxos/echo/Query' as const;
61
+ export type QueryTypeId = typeof QueryTypeId;
62
+
48
63
  // TODO(burdon): Narrow T to Entity.Unknown?
49
64
  export interface Query<T> {
50
- // TODO(dmaretskyi): See new effect-schema approach to variance.
51
- '~Query': { value: T };
65
+ readonly [QueryTypeId]: { value: T };
52
66
 
53
- 'ast': QueryAST.Query;
67
+ ast: QueryAST.Query;
54
68
 
55
69
  /**
56
70
  * Filter the current selection based on a filter.
57
71
  * @param filter - Filter to select the objects.
58
72
  * @returns Query for the selected objects.
59
73
  */
60
- 'select'(filter: Filter.Filter<T>): Query<T>;
61
- 'select'(props: Filter.Props<T>): Query<T>;
74
+ select(filter: Filter.Filter<T>): Query<T>;
75
+ select(props: Filter.Props<T>): Query<T>;
76
+
77
+ /**
78
+ * Project the query's results to a single scalar property, for use as a membership set in
79
+ * `Filter.in(query.project('col'))` — an uncorrelated `col IN (SELECT property FROM ...)`
80
+ * semi-join. The subquery is resolved once at execution time. A projection is a terminal
81
+ * value, not a `Query`, so it cannot be chained further.
82
+ * @param property - Property path to project.
83
+ */
84
+ project<K extends RefPropKey<T>>(property: K): Projection<T[K]>;
62
85
 
63
86
  /**
64
87
  * Traverse an outgoing reference.
65
88
  * @param key - Property path inside T that is a reference or optional reference.
66
89
  * @returns Query for the target of the reference.
67
90
  */
68
- 'reference'<K extends RefPropKey<T>>(key: K): Query<ReferenceTraversalTarget<T[K]>>;
91
+ reference<K extends RefPropKey<T>>(key: K): Query<ReferenceTraversalTarget<T[K]>>;
69
92
 
70
93
  /**
71
94
  * Find objects referencing this object.
@@ -75,12 +98,12 @@ export interface Query<T> {
75
98
  */
76
99
  // TODO(dmaretskyi): any way to enforce `Ref.Target<Schema.Schema.Type<S>[key]> == T`?
77
100
  // TODO(dmaretskyi): Ability to go through arrays of references.
78
- 'referencedBy'<S extends Type$.AnyEntity>(
101
+ referencedBy<S extends Type$.AnyEntity>(
79
102
  target: S | URI.URI,
80
103
  key: RefPropKey<Type$.InstanceType<S>>,
81
104
  ): Query<Type$.InstanceType<S>>;
82
- 'referencedBy'<S extends Type$.AnyEntity>(target: S | URI.URI): Query<Type$.InstanceType<S>>;
83
- 'referencedBy'(): Query<any>;
105
+ referencedBy<S extends Type$.AnyEntity>(target: S | URI.URI): Query<Type$.InstanceType<S>>;
106
+ referencedBy(): Query<any>;
84
107
 
85
108
  /**
86
109
  * Find relations where this object is the source.
@@ -88,7 +111,7 @@ export interface Query<T> {
88
111
  * @param relation - Schema of the relation.
89
112
  * @param predicates - Predicates to filter the relation objects.
90
113
  */
91
- 'sourceOf'<R extends Type$.AnyRelation>(
114
+ sourceOf<R extends Type$.AnyRelation>(
92
115
  relation?: R | URI.URI,
93
116
  predicates?: Filter.Props<Type$.InstanceType<R>>,
94
117
  ): Query<Type$.InstanceType<R>>;
@@ -99,7 +122,7 @@ export interface Query<T> {
99
122
  * @param relation - Type entity of the relation.
100
123
  * @param predicates - Predicates to filter the relation objects.
101
124
  */
102
- 'targetOf'<R extends Type$.AnyRelation>(
125
+ targetOf<R extends Type$.AnyRelation>(
103
126
  relation?: R | URI.URI,
104
127
  predicates?: Filter.Props<Type$.InstanceType<R>>,
105
128
  ): Query<Type$.InstanceType<R>>;
@@ -108,40 +131,86 @@ export interface Query<T> {
108
131
  * For a query for relations, get the source objects.
109
132
  * @returns Query for the source objects.
110
133
  */
111
- 'source'(): Query<Relation.SourceOf<T>>;
134
+ source(): Query<Relation.SourceOf<T>>;
112
135
 
113
136
  /**
114
137
  * For a query for relations, get the target objects.
115
138
  * @returns Query for the target objects.
116
139
  */
117
- 'target'(): Query<Relation.TargetOf<T>>;
140
+ target(): Query<Relation.TargetOf<T>>;
118
141
 
119
142
  /**
120
143
  * Get the parent object of the current selection.
121
144
  * @returns Query for the parent objects.
122
145
  */
123
- 'parent'(): Query<any>;
146
+ parent(): Query<any>;
124
147
 
125
148
  /**
126
149
  * Get all child objects of the current selection.
127
150
  * @returns Query for the child objects.
128
151
  */
129
- 'children'(): Query<any>;
152
+ children(): Query<any>;
130
153
 
131
154
  /**
132
155
  * Order the query results.
133
156
  * Orders are specified in priority order. The first order will be applied first, etc.
157
+ *
158
+ * `Order.property` orders by the current result shape's fields, so it works both before and after
159
+ * an {@link aggregate}: before, by member (row) properties; after, by the flat record's fields
160
+ * (any group or aggregate field — e.g. `Order.property('lastMessageAt')` reorders the groups by
161
+ * that aggregate).
134
162
  * @param order - Order to sort the results.
135
163
  * @returns Query for the ordered results.
136
164
  */
137
- 'orderBy'(...order: EffectArray.NonEmptyArray<Order.Order<T>>): Query<T>;
165
+ orderBy(...order: EffectArray.NonEmptyArray<Order.Order<T>>): Query<T>;
166
+
167
+ /**
168
+ * Aggregate the query results into flat records. {@link Aggregate.group} entries partition the
169
+ * results into contiguous groups (one record each), keyed by the record field the group is named
170
+ * after; with no `group` entries the entire input aggregates into a single record. Each declared
171
+ * aggregate becomes a top-level field and can be ordered by with a following {@link orderBy} using
172
+ * {@link Order.property}.
173
+ *
174
+ * Groups are ordered by the first occurrence of their key in the incoming stream, so a preceding
175
+ * `orderBy` controls group order in the absence of a following one. {@link Aggregate.items}'s own
176
+ * `order` option is a separate, explicit per-group member ordering — pass it there rather than
177
+ * relying on a preceding `orderBy`, whose only well-defined job once `aggregate` follows is
178
+ * establishing initial group order. For example, message threads ordered by their most recent
179
+ * message, each retaining up to 20 members newest-first:
180
+ *
181
+ * ```ts
182
+ * Query.type(Message)
183
+ * .aggregate({
184
+ * threadId: Aggregate.group('threadId'),
185
+ * lastMessageAt: Aggregate.max('created'),
186
+ * items: Aggregate.items({ limit: 20, order: [Order.property('created', 'desc')] }),
187
+ * })
188
+ * .orderBy(Order.property('lastMessageAt', 'desc'));
189
+ * ```
190
+ *
191
+ * Must be the last data-selecting clause in the chain — only `from`/`options`/`orderBy`/`limit`/
192
+ * `skip` may follow.
193
+ * @param aggregates - Record of aggregate declarations keyed by result field name.
194
+ * @returns Query whose flat result records carry the named aggregates as fields.
195
+ */
196
+ aggregate<const A extends Record<string, Aggregate.Aggregate<T, any>>>(
197
+ aggregates: A,
198
+ ): Query<Aggregate.AggregationResult<A>>;
138
199
 
139
200
  /**
140
201
  * Limit the number of results.
141
202
  * @param limit - Maximum number of results to return.
142
203
  * @returns Query for the limited results.
143
204
  */
144
- 'limit'(limit: number): Query<T>;
205
+ limit(limit: number): Query<T>;
206
+
207
+ /**
208
+ * Skip a number of results (offset). Combined with `orderBy` and `limit`, expresses a windowed
209
+ * (paginated) read.
210
+ * @param skip - Number of leading results to skip.
211
+ * @returns Query for the remaining results.
212
+ */
213
+ skip(skip: number): Query<T>;
145
214
 
146
215
  /**
147
216
  * Query from selected databases only.
@@ -154,7 +223,7 @@ export interface Query<T> {
154
223
  *
155
224
  * @param options.includeFeeds [false] - Whether to include feeds in the query. Default is to query from automerge documents only.
156
225
  */
157
- 'from'(database: Database.Database | Database.Database[], options?: { includeFeeds?: boolean }): Query<T>;
226
+ from(database: Database.Database | Database.Database[], options?: { includeFeeds?: boolean }): Query<T>;
158
227
 
159
228
  /**
160
229
  * Query from selected feeds only.
@@ -166,7 +235,7 @@ export interface Query<T> {
166
235
  * ```
167
236
  *
168
237
  */
169
- 'from'(feeds: Feed.Feed | Feed.Feed[]): Query<T>;
238
+ from(feeds: Feed.Feed | Feed.Feed[]): Query<T>;
170
239
 
171
240
  /**
172
241
  * Query from all accessible spaces.
@@ -179,7 +248,7 @@ export interface Query<T> {
179
248
  *
180
249
  * @param options.includeFeeds [false] - Whether to include feeds in the query. Default is to query from automerge documents only.
181
250
  */
182
- 'from'(allSpaces: 'all-accessible-spaces', options?: { includeFeeds?: boolean }): Query<T>;
251
+ from(allSpaces: 'all-accessible-spaces', options?: { includeFeeds?: boolean }): Query<T>;
183
252
 
184
253
  /**
185
254
  * Query from a dataset.
@@ -191,7 +260,7 @@ export interface Query<T> {
191
260
  * Query.type(Person).from(feed);
192
261
  * ```
193
262
  */
194
- 'from'(dataset: Dataset.Dataset): Query<T>;
263
+ from(dataset: Dataset.Dataset): Query<T>;
195
264
 
196
265
  /**
197
266
  * Query from the results of another query.
@@ -202,7 +271,7 @@ export interface Query<T> {
202
271
  * Query.select(Filter.props({ foo: 'foo' })).from(Query.select(Filter.type(Contact)).reference('org'));
203
272
  * ```
204
273
  */
205
- 'from'(query: Any): Query<T>;
274
+ from(query: Any): Query<T>;
206
275
 
207
276
  /**
208
277
  * Query from one or more raw scopes.
@@ -213,36 +282,48 @@ export interface Query<T> {
213
282
  * Query.select(Filter.type(Type.Type)).from(Scope.space(), Scope.registry());
214
283
  * ```
215
284
  */
216
- 'from'(...scopes: QueryAST.Scope[]): Query<T>;
285
+ from(...scopes: QueryAST.Scope[]): Query<T>;
217
286
 
218
287
  /**
219
288
  * Query from a raw scope or array of scopes.
220
289
  */
221
- 'from'(scope: QueryAST.Scope | QueryAST.Scope[]): Query<T>;
290
+ from(scope: QueryAST.Scope | QueryAST.Scope[]): Query<T>;
222
291
 
223
292
  /**
224
293
  * Add options to a query.
225
294
  */
226
- 'options'(options: QueryAST.QueryOptions): Query<T>;
295
+ options(options: QueryAST.QueryOptions): Query<T>;
227
296
 
228
297
  /**
229
298
  * Attach a diagnostic label for logs and tooling (execution semantics unchanged).
230
299
  */
231
- 'debugLabel'(label: string): Query<T>;
300
+ debugLabel(label: string): Query<T>;
232
301
  }
233
302
 
234
303
  export type Any = Query<any>;
235
304
 
236
305
  export type Type<Q extends Any> = Q extends Query<infer T> ? T : never;
237
306
 
307
+ /**
308
+ * A query projected to a single scalar property (see {@link Query.project}).
309
+ */
310
+ export type Projection<V = unknown> = internal.Projection<V>;
311
+
312
+ /**
313
+ * Brand key for {@link Projection}. Re-exported (like {@link QueryTypeId}) so the sandboxed
314
+ * `query-lite` mirror can declare its own local constant with the same string literal and
315
+ * construct structurally-compatible projections without importing this module's runtime.
316
+ */
317
+ export type ProjectionTypeId = internal.ProjectionTypeId;
318
+
238
319
  class QueryClass implements Any {
239
- private static 'variance': Any['~Query'] = {} as Any['~Query'];
320
+ private static 'variance': Any[QueryTypeId] = {} as Any[QueryTypeId];
240
321
 
241
- 'constructor'(public readonly ast: QueryAST.Query) {}
322
+ constructor(public readonly ast: QueryAST.Query) {}
242
323
 
243
- '~Query' = QueryClass.variance;
324
+ [QueryTypeId] = QueryClass.variance;
244
325
 
245
- 'select'(filter: Filter.Any | Filter.Props<any>): Any {
326
+ select(filter: Filter.Any | Filter.Props<any>): Any {
246
327
  if (Filter.is(filter)) {
247
328
  return new QueryClass({
248
329
  type: 'filter',
@@ -258,7 +339,11 @@ class QueryClass implements Any {
258
339
  }
259
340
  }
260
341
 
261
- 'reference'(key: string): Any {
342
+ project(property: string): Projection<any> {
343
+ return internal.makeProjection(this.ast, property);
344
+ }
345
+
346
+ reference(key: string): Any {
262
347
  return new QueryClass({
263
348
  type: 'reference-traversal',
264
349
  anchor: this.ast,
@@ -266,7 +351,7 @@ class QueryClass implements Any {
266
351
  });
267
352
  }
268
353
 
269
- 'referencedBy'(target?: Type$.AnyEntity | URI.URI, key?: string): Any {
354
+ referencedBy(target?: Type$.AnyEntity | URI.URI, key?: string): Any {
270
355
  const uri = target !== undefined ? internal.getTypeURIFromSpecifier(target) : null;
271
356
  return new QueryClass({
272
357
  type: 'incoming-references',
@@ -276,7 +361,7 @@ class QueryClass implements Any {
276
361
  });
277
362
  }
278
363
 
279
- 'sourceOf'(relation?: Type$.AnyRelation | URI.URI, predicates?: Filter.Props<unknown> | undefined): Any {
364
+ sourceOf(relation?: Type$.AnyRelation | URI.URI, predicates?: Filter.Props<unknown> | undefined): Any {
280
365
  return new QueryClass({
281
366
  type: 'relation',
282
367
  anchor: this.ast,
@@ -285,7 +370,7 @@ class QueryClass implements Any {
285
370
  });
286
371
  }
287
372
 
288
- 'targetOf'(relation?: Type$.AnyRelation | URI.URI, predicates?: Filter.Props<unknown> | undefined): Any {
373
+ targetOf(relation?: Type$.AnyRelation | URI.URI, predicates?: Filter.Props<unknown> | undefined): Any {
289
374
  return new QueryClass({
290
375
  type: 'relation',
291
376
  anchor: this.ast,
@@ -294,7 +379,7 @@ class QueryClass implements Any {
294
379
  });
295
380
  }
296
381
 
297
- 'source'(): Any {
382
+ source(): Any {
298
383
  return new QueryClass({
299
384
  type: 'relation-traversal',
300
385
  anchor: this.ast,
@@ -302,7 +387,7 @@ class QueryClass implements Any {
302
387
  });
303
388
  }
304
389
 
305
- 'target'(): Any {
390
+ target(): Any {
306
391
  return new QueryClass({
307
392
  type: 'relation-traversal',
308
393
  anchor: this.ast,
@@ -310,7 +395,7 @@ class QueryClass implements Any {
310
395
  });
311
396
  }
312
397
 
313
- 'parent'(): Any {
398
+ parent(): Any {
314
399
  return new QueryClass({
315
400
  type: 'hierarchy-traversal',
316
401
  anchor: this.ast,
@@ -318,7 +403,7 @@ class QueryClass implements Any {
318
403
  });
319
404
  }
320
405
 
321
- 'children'(): Any {
406
+ children(): Any {
322
407
  return new QueryClass({
323
408
  type: 'hierarchy-traversal',
324
409
  anchor: this.ast,
@@ -326,7 +411,7 @@ class QueryClass implements Any {
326
411
  });
327
412
  }
328
413
 
329
- 'orderBy'(...order: Order.Order<any>[]): Any {
414
+ orderBy(...order: Order.Any[]): Any {
330
415
  return new QueryClass({
331
416
  type: 'order',
332
417
  query: this.ast,
@@ -334,7 +419,15 @@ class QueryClass implements Any {
334
419
  });
335
420
  }
336
421
 
337
- 'limit'(limit: number): Any {
422
+ aggregate(aggregates: Record<string, Aggregate.Any>): Any {
423
+ return new QueryClass({
424
+ type: 'aggregate',
425
+ query: this.ast,
426
+ aggregates: Object.entries(aggregates).map(([name, aggregate]) => ({ name, ...aggregate.spec })),
427
+ });
428
+ }
429
+
430
+ limit(limit: number): Any {
338
431
  return new QueryClass({
339
432
  type: 'limit',
340
433
  query: this.ast,
@@ -342,7 +435,15 @@ class QueryClass implements Any {
342
435
  });
343
436
  }
344
437
 
345
- 'from'(
438
+ skip(skip: number): Any {
439
+ return new QueryClass({
440
+ type: 'skip',
441
+ query: this.ast,
442
+ skip,
443
+ });
444
+ }
445
+
446
+ from(
346
447
  ...args:
347
448
  | [
348
449
  (
@@ -451,11 +552,13 @@ class QueryClass implements Any {
451
552
  if (typename === 'org.dxos.type.collection') {
452
553
  throw new Error('Query.from(collection) is not yet supported.');
453
554
  }
454
- // Validate that the items are Feed.Feed instances.
555
+ // Validate that the items are feed objects. Checked by typename rather than schema instanceof
556
+ // to keep this module free of a runtime dependency on the Feed module (avoids an import cycle).
455
557
  for (const item of items) {
456
- if (!Obj.instanceOf(Feed.Feed, item)) {
558
+ const itemTypename = Obj.getTypename(item as Obj.Unknown);
559
+ if (itemTypename !== 'org.dxos.type.feed') {
457
560
  throw new TypeError(
458
- `Query.from() expects Feed objects (org.dxos.type.feed), but received an object with typename '${typename ?? 'unknown'}'.`,
561
+ `Query.from() expects Feed objects (org.dxos.type.feed), but received an object with typename '${itemTypename ?? 'unknown'}'.`,
459
562
  );
460
563
  }
461
564
  }
@@ -463,10 +566,11 @@ class QueryClass implements Any {
463
566
 
464
567
  const feedItems = items as Feed.Feed[];
465
568
  const feedScopes = feedItems.map((feed) => {
466
- const uri = Feed.getQueueUri(feed);
569
+ // Inlined Feed.getFeedUri to avoid a runtime import cycle with the Feed module.
570
+ const uri = EID.tryParse(Obj.getURI(feed));
467
571
  if (!uri) {
468
572
  throw new TypeError(
469
- `Query.from() expects persisted Feed objects with a queue URI; got feed without a space (id=${Obj.getURI(feed)}).`,
573
+ `Query.from() expects persisted Feed objects with a feed URI; got feed without a space (id=${Obj.getURI(feed)}).`,
470
574
  );
471
575
  }
472
576
  return { _tag: 'feed' as const, feedUri: String(uri) };
@@ -478,7 +582,7 @@ class QueryClass implements Any {
478
582
  });
479
583
  }
480
584
 
481
- 'options'(options: QueryAST.QueryOptions): Any {
585
+ options(options: QueryAST.QueryOptions): Any {
482
586
  return new QueryClass({
483
587
  type: 'options',
484
588
  query: this.ast,
@@ -486,7 +590,7 @@ class QueryClass implements Any {
486
590
  });
487
591
  }
488
592
 
489
- 'debugLabel'(label: string): Any {
593
+ debugLabel(label: string): Any {
490
594
  if (this.ast.type === 'options') {
491
595
  return new QueryClass({
492
596
  type: 'options',
@@ -503,7 +607,7 @@ class QueryClass implements Any {
503
607
  }
504
608
 
505
609
  export const is = (value: unknown): value is Any => {
506
- return typeof value === 'object' && value !== null && '~Query' in value;
610
+ return typeof value === 'object' && value !== null && QueryTypeId in value;
507
611
  };
508
612
 
509
613
  /** Construct a query from an ast. */
@@ -550,6 +654,16 @@ export const type: {
550
654
  });
551
655
  };
552
656
 
657
+ /**
658
+ * Project a query's results to a single scalar property, for use as a membership set in
659
+ * `Filter.in(Query.project(query, 'col'))`.
660
+ * @param query - Query to project.
661
+ * @param property - Property path to project.
662
+ * @returns Projection for use in `Filter.in`.
663
+ */
664
+ export const project = <T, K extends RefPropKey<T>>(query: Query<T>, property: K): Projection<T[K]> =>
665
+ internal.makeProjection<T[K]>(query.ast, property);
666
+
553
667
  /**
554
668
  * Combine results of multiple queries.
555
669
  * @param queries - Queries to combine.
package/src/Relation.ts CHANGED
@@ -230,6 +230,20 @@ export const isSnapshot = (value: unknown): value is Snapshot => {
230
230
  return (value as any)[Entity.SnapshotKindId] === internal.EntityKind.Relation;
231
231
  };
232
232
 
233
+ /**
234
+ * Sets a relation as the parent of an object: the object cascade-deletes with the relation and is
235
+ * persisted transitively when the relation is added. The delete-cascade resolves the parent by id
236
+ * regardless of kind. The object-parent counterpart is {@link Obj.setParent}.
237
+ */
238
+ export const setParent = (entity: Obj.Unknown, parent: Unknown): Obj.Unknown => {
239
+ assertArgument(Obj.isObject(entity), 'Expected an object');
240
+ assertArgument(isRelation(parent), 'Expected a relation');
241
+ assumeType<internal.InternalObjectProps>(entity);
242
+ assumeType<internal.InternalObjectProps>(parent);
243
+ entity[internal.ParentId] = parent;
244
+ return entity;
245
+ };
246
+
233
247
  /**
234
248
  * @returns Relation source URI.
235
249
  * Accepts both reactive relations and snapshots.
package/src/Type.ts CHANGED
@@ -505,7 +505,7 @@ export const getVersion = (input: AnyEntity): string => {
505
505
  };
506
506
 
507
507
  /**
508
- * Strip URI prefixes (`dxn:`, `echo:/`, `echo://`) from a typename string.
508
+ * Strip URI prefixes (`dxn:`, `echo:/`, `echo://`, `echo:///`) from a typename string.
509
509
  * Typename is a bare identifier — callers reading from meta or from a
510
510
  * caller-supplied seed value shouldn't propagate URI prefixes downstream.
511
511
  */
@@ -513,11 +513,10 @@ const stripTypenamePrefix = (value: string): string => {
513
513
  if (value.startsWith('dxn:')) {
514
514
  return value.slice('dxn:'.length);
515
515
  }
516
- if (value.startsWith('echo://')) {
517
- return value.slice('echo://'.length);
518
- }
519
- if (value.startsWith('echo:/')) {
520
- return value.slice('echo:/'.length);
516
+ // Strip the `echo:` scheme along with any leading slashes so every local form
517
+ // (`echo:/<id>`, `echo:///<id>`) and the qualified `echo://<space>/<id>` collapse consistently.
518
+ if (value.startsWith('echo:')) {
519
+ return value.slice('echo:'.length).replace(/^\/+/, '');
521
520
  }
522
521
  return value;
523
522
  };
package/src/index.ts CHANGED
@@ -5,7 +5,9 @@
5
5
  export { QueryAST } from '@dxos/echo-protocol';
6
6
  export { DXN, EID, URI } from '@dxos/keys';
7
7
 
8
+ export * as Aggregate from './Aggregate';
8
9
  export * as Annotation from './Annotation';
10
+ export * as Blob from './Blob';
9
11
  export * as Database from './Database';
10
12
  export * as Entity from './Entity';
11
13
  export * as Err from './Err';
@@ -8,7 +8,7 @@ import { assumeType } from '@dxos/util';
8
8
 
9
9
  import type { AnyEntity } from '../common/types';
10
10
  import { MetaId } from '../common/types/model-symbols';
11
- import { type InternalObjectProps, ObjectDatabaseId } from './model';
11
+ import { type InternalObjectProps, ObjectBranchId, ObjectDatabaseId } from './model';
12
12
  import { getObjectEchoUri } from './util';
13
13
 
14
14
  /**
@@ -75,3 +75,13 @@ export const getDatabase = (entity: AnyEntity): any | undefined => {
75
75
  assumeType<InternalObjectProps>(entity);
76
76
  return entity[ObjectDatabaseId];
77
77
  };
78
+
79
+ /**
80
+ * Get the branch this entity instance is bound to (`'main'` for the canonical object, or the branch
81
+ * of a `db.branch()` independent instance). Reads the branch off the instance, so two instances of the
82
+ * same object id on different branches report their own branch.
83
+ */
84
+ export const getBranch = (entity: AnyEntity): string => {
85
+ assumeType<InternalObjectProps>(entity);
86
+ return entity[ObjectBranchId] ?? 'main';
87
+ };
@@ -20,6 +20,7 @@ import {
20
20
  type ATTR_TYPE,
21
21
  EntityKind,
22
22
  KindId,
23
+ ObjectBranchId,
23
24
  ObjectDatabaseId,
24
25
  ObjectDeletedId,
25
26
  ObjectVersionId,
@@ -40,6 +41,7 @@ export {
40
41
  ATTR_DELETED,
41
42
  ATTR_SELF_URI,
42
43
  ATTR_SELF_URI_LEGACY,
44
+ ObjectBranchId,
43
45
  ObjectDatabaseId,
44
46
  ObjectDeletedId,
45
47
  ObjectVersionId,
@@ -60,6 +62,7 @@ export interface InternalObjectProps {
60
62
  readonly [MetaId]?: EntityMeta;
61
63
  [ParentId]?: InternalObjectProps;
62
64
  readonly [ObjectDatabaseId]?: Database.Database;
65
+ readonly [ObjectBranchId]?: string;
63
66
  readonly [ObjectDeletedId]?: boolean;
64
67
  readonly [ObjectVersionId]?: Version;
65
68
  readonly [RelationSourceDXNId]?: EID.EID;
@@ -117,6 +117,28 @@ const structuralMatch = (filterObj: any, targetObj: any, strict = true): boolean
117
117
  });
118
118
  };
119
119
 
120
+ /** Normalizes an encoded reference to its URI string so ref-valued properties compare by identity. */
121
+ const _normalizeInValue = (value: unknown): unknown =>
122
+ isEncodedReference(value) ? EncodedReference.toURI(value) : value;
123
+
124
+ /**
125
+ * Memoizes a `Set` per `values` array so `Filter.in` membership is O(1) rather than
126
+ * `Array.includes`'s O(n) — this matters most for large sets projected from a subquery
127
+ * (`Filter.in(query.project(...))`, resolved to a literal `in` by the query executor), but
128
+ * also speeds up ordinary literal-list `Filter.in`. Keyed by array identity: the planner and
129
+ * executor never mutate a filter's `values` array in place, so the cache stays valid for the
130
+ * array's lifetime.
131
+ */
132
+ const _inSetCache = new WeakMap<readonly unknown[], Set<unknown>>();
133
+ const _getInSet = (values: readonly unknown[]): Set<unknown> => {
134
+ let set = _inSetCache.get(values);
135
+ if (!set) {
136
+ set = new Set(values.map(_normalizeInValue));
137
+ _inSetCache.set(values, set);
138
+ }
139
+ return set;
140
+ };
141
+
120
142
  export const filterMatchValue = (filter: QueryAST.Filter, value: unknown): boolean => {
121
143
  switch (filter.type) {
122
144
  case 'compare': {
@@ -161,7 +183,10 @@ export const filterMatchValue = (filter: QueryAST.Filter, value: unknown): boole
161
183
  return true;
162
184
  }
163
185
  case 'in': {
164
- return filter.values.includes(value);
186
+ return _getInSet(filter.values).has(_normalizeInValue(value));
187
+ }
188
+ case 'in-query': {
189
+ throw new Error('in-query filters must be resolved to a literal `in` by the query executor before matching.');
165
190
  }
166
191
  case 'contains': {
167
192
  if (!Array.isArray(value)) {
@@ -266,6 +291,10 @@ export const filterMatchEntity = (filter: QueryAST.Filter, entity: AnyEntity): b
266
291
  throw new Error('child-of filters must be handled at the executor level, not in-memory matching.');
267
292
  }
268
293
 
294
+ case 'in-query': {
295
+ throw new Error('in-query filters must be resolved to a literal `in` by the query executor before matching.');
296
+ }
297
+
269
298
  case 'not': {
270
299
  return !filterMatchEntity(filter.filter, entity);
271
300
  }
@@ -38,9 +38,21 @@ export const JsonSchemaEchoAnnotations = Schema.Struct({
38
38
 
39
39
  /**
40
40
  * Generator function for this schema.
41
- * Mapped from {@link GeneratorAnnotationId}.
41
+ * Mapped from {@link GeneratorAnnotationId}; mirrors `GeneratorAnnotationValue`, whose object
42
+ * form (`{ generator, args }`) is used by schemas like `Task` — a narrower contract here makes
43
+ * those schemas unserializable (`Operation.serialize` fails on any operation referencing them).
42
44
  */
43
- generator: Schema.optional(Schema.Union(Schema.String, Schema.Tuple(Schema.String, Schema.Number))),
45
+ generator: Schema.optional(
46
+ Schema.Union(
47
+ Schema.String,
48
+ Schema.Tuple(Schema.String, Schema.Number),
49
+ Schema.Struct({
50
+ generator: Schema.String,
51
+ args: Schema.optional(Schema.Array(Schema.Any)),
52
+ probability: Schema.optional(Schema.Number),
53
+ }),
54
+ ),
55
+ ),
44
56
 
45
57
  /**
46
58
  * {@link PropertyMeta} annotations get serialized here.