@dxos/echo 0.8.4-main.8360d9e660 → 0.8.4-main.8baae0fced

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 (292) hide show
  1. package/LICENSE +102 -5
  2. package/README.md +3 -3
  3. package/dist/lib/neutral/Annotation.mjs +5 -3
  4. package/dist/lib/neutral/Database.mjs +8 -4
  5. package/dist/lib/neutral/Entity.mjs +16 -14
  6. package/dist/lib/neutral/Err.mjs +1 -1
  7. package/dist/lib/neutral/Extension.mjs +18 -0
  8. package/dist/lib/neutral/Extension.mjs.map +7 -0
  9. package/dist/lib/neutral/Feed.mjs +23 -18
  10. package/dist/lib/neutral/Filter.mjs +23 -13
  11. package/dist/lib/neutral/Format.mjs +3 -3
  12. package/dist/lib/neutral/JsonSchema.mjs +8 -8
  13. package/dist/lib/neutral/Key.mjs +1 -1
  14. package/dist/lib/neutral/Migration.mjs +17 -0
  15. package/dist/lib/neutral/Migration.mjs.map +7 -0
  16. package/dist/lib/neutral/Obj.mjs +16 -13
  17. package/dist/lib/neutral/Order.mjs +1 -1
  18. package/dist/lib/neutral/Query.mjs +19 -17
  19. package/dist/lib/neutral/QueryResult.mjs +1 -1
  20. package/dist/lib/neutral/Ref.mjs +9 -7
  21. package/dist/lib/neutral/Relation.mjs +15 -14
  22. package/dist/lib/neutral/SchemaRegistry.mjs +1 -1
  23. package/dist/lib/neutral/Tag.mjs +14 -13
  24. package/dist/lib/neutral/Type.mjs +10 -10
  25. package/dist/lib/neutral/{chunk-FZO7LQO7.mjs → chunk-44HT3MEC.mjs} +2 -2
  26. package/dist/lib/neutral/{chunk-ROAGDPV7.mjs → chunk-4A2GS5LQ.mjs} +12 -8
  27. package/dist/lib/neutral/chunk-4A2GS5LQ.mjs.map +7 -0
  28. package/dist/lib/neutral/{chunk-OENWMTE6.mjs → chunk-5SL5LDLD.mjs} +4 -2
  29. package/dist/lib/neutral/chunk-5SL5LDLD.mjs.map +7 -0
  30. package/dist/lib/neutral/{chunk-ANHVGJI4.mjs → chunk-7RVZT53K.mjs} +1 -1
  31. package/dist/lib/neutral/{chunk-YWXWXIE5.mjs → chunk-APHSOTIX.mjs} +2 -2
  32. package/dist/lib/neutral/{chunk-YWXWXIE5.mjs.map → chunk-APHSOTIX.mjs.map} +2 -2
  33. package/dist/lib/neutral/{chunk-6DNYDXCV.mjs → chunk-B2P7IVG3.mjs} +61 -14
  34. package/dist/lib/neutral/chunk-B2P7IVG3.mjs.map +7 -0
  35. package/dist/lib/neutral/{chunk-BNCCGLJN.mjs → chunk-BICZKPQG.mjs} +1 -1
  36. package/dist/lib/neutral/{chunk-43Y5DOS6.mjs → chunk-BMB7IHGB.mjs} +16 -66
  37. package/dist/lib/neutral/chunk-BMB7IHGB.mjs.map +7 -0
  38. package/dist/lib/neutral/{chunk-6K2MVI2O.mjs → chunk-BUBEC474.mjs} +4 -4
  39. package/dist/lib/neutral/{chunk-YMNSMKKW.mjs → chunk-C4PSESGN.mjs} +6 -22
  40. package/dist/lib/neutral/{chunk-YMNSMKKW.mjs.map → chunk-C4PSESGN.mjs.map} +3 -3
  41. package/dist/lib/neutral/chunk-FIWO2FZK.mjs +36 -0
  42. package/dist/lib/neutral/chunk-FIWO2FZK.mjs.map +7 -0
  43. package/dist/lib/neutral/chunk-GWFFC34K.mjs +50 -0
  44. package/dist/lib/neutral/chunk-GWFFC34K.mjs.map +7 -0
  45. package/dist/lib/neutral/{chunk-NXMFBIT5.mjs → chunk-HKETO4L4.mjs} +72 -6
  46. package/dist/lib/neutral/chunk-HKETO4L4.mjs.map +7 -0
  47. package/dist/lib/neutral/{chunk-NEGC54NE.mjs → chunk-I2DARWPX.mjs} +17 -19
  48. package/dist/lib/neutral/chunk-I2DARWPX.mjs.map +7 -0
  49. package/dist/lib/neutral/{chunk-7VNVH63N.mjs → chunk-IVSI7QO6.mjs} +50 -20
  50. package/dist/lib/neutral/chunk-IVSI7QO6.mjs.map +7 -0
  51. package/dist/lib/neutral/{chunk-BOZZPUXE.mjs → chunk-MLS7U7AT.mjs} +12 -65
  52. package/dist/lib/neutral/chunk-MLS7U7AT.mjs.map +7 -0
  53. package/dist/lib/neutral/{chunk-WYOKA6AE.mjs → chunk-N4B7FHQT.mjs} +2 -2
  54. package/dist/lib/neutral/{chunk-WYOKA6AE.mjs.map → chunk-N4B7FHQT.mjs.map} +1 -1
  55. package/dist/lib/neutral/{chunk-FXEG7EOK.mjs → chunk-N7VOEPSV.mjs} +6 -3
  56. package/dist/lib/neutral/{chunk-FXEG7EOK.mjs.map → chunk-N7VOEPSV.mjs.map} +3 -3
  57. package/dist/lib/neutral/{chunk-UTBRYVQC.mjs → chunk-QRZ2I3ZM.mjs} +2 -2
  58. package/dist/lib/neutral/{chunk-SEMVAGBM.mjs → chunk-TNBK56IN.mjs} +19 -24
  59. package/dist/lib/neutral/chunk-TNBK56IN.mjs.map +7 -0
  60. package/dist/lib/neutral/{chunk-B5OXLWZL.mjs → chunk-TRPZU2HV.mjs} +2 -2
  61. package/dist/lib/neutral/{chunk-UI6MWK5W.mjs → chunk-TTCSATUD.mjs} +1 -1
  62. package/dist/lib/neutral/{chunk-OMUPQMLR.mjs → chunk-V72DY6LU.mjs} +1 -1
  63. package/dist/lib/neutral/{chunk-6GPU7XC3.mjs → chunk-VW42HESL.mjs} +54 -17
  64. package/dist/lib/neutral/chunk-VW42HESL.mjs.map +7 -0
  65. package/dist/lib/neutral/{chunk-4JRI2ZJI.mjs → chunk-X3356HPV.mjs} +120 -12
  66. package/dist/lib/neutral/chunk-X3356HPV.mjs.map +7 -0
  67. package/dist/lib/neutral/{chunk-W47JKR3X.mjs → chunk-XEXM5HWQ.mjs} +18 -46
  68. package/dist/lib/neutral/chunk-XEXM5HWQ.mjs.map +7 -0
  69. package/dist/lib/neutral/{chunk-C4JZK4J7.mjs → chunk-Z5GKP74O.mjs} +231 -479
  70. package/dist/lib/neutral/chunk-Z5GKP74O.mjs.map +7 -0
  71. package/dist/lib/neutral/{chunk-UBEZSGXY.mjs → chunk-ZISMEVKD.mjs} +1 -1
  72. package/dist/lib/neutral/{chunk-UBEZSGXY.mjs.map → chunk-ZISMEVKD.mjs.map} +2 -2
  73. package/dist/lib/neutral/index.mjs +38 -28
  74. package/dist/lib/neutral/internal/index.mjs +15 -9
  75. package/dist/lib/neutral/meta.json +1 -1
  76. package/dist/lib/neutral/testing/index.mjs +185 -129
  77. package/dist/lib/neutral/testing/index.mjs.map +3 -3
  78. package/dist/types/src/Annotation.d.ts +1 -1
  79. package/dist/types/src/Annotation.d.ts.map +1 -1
  80. package/dist/types/src/Collection.d.ts.map +1 -1
  81. package/dist/types/src/Database.d.ts +14 -2
  82. package/dist/types/src/Database.d.ts.map +1 -1
  83. package/dist/types/src/Dataset.d.ts +2 -1
  84. package/dist/types/src/Dataset.d.ts.map +1 -1
  85. package/dist/types/src/Entity.d.ts +17 -11
  86. package/dist/types/src/Entity.d.ts.map +1 -1
  87. package/dist/types/src/Err.d.ts +18 -18
  88. package/dist/types/src/Err.d.ts.map +1 -1
  89. package/dist/types/src/Extension.d.ts +80 -0
  90. package/dist/types/src/Extension.d.ts.map +1 -0
  91. package/dist/types/src/Extension.test.d.ts +2 -0
  92. package/dist/types/src/Extension.test.d.ts.map +1 -0
  93. package/dist/types/src/Feed.d.ts +62 -21
  94. package/dist/types/src/Feed.d.ts.map +1 -1
  95. package/dist/types/src/Filter.d.ts +54 -4
  96. package/dist/types/src/Filter.d.ts.map +1 -1
  97. package/dist/types/src/Filter.test.d.ts +2 -0
  98. package/dist/types/src/Filter.test.d.ts.map +1 -0
  99. package/dist/types/src/Hypergraph.d.ts +3 -3
  100. package/dist/types/src/Hypergraph.d.ts.map +1 -1
  101. package/dist/types/src/Json.d.ts +33 -0
  102. package/dist/types/src/Json.d.ts.map +1 -0
  103. package/dist/types/src/Json.test.d.ts +2 -0
  104. package/dist/types/src/Json.test.d.ts.map +1 -0
  105. package/dist/types/src/JsonSchema.d.ts +1 -1
  106. package/dist/types/src/Migration.d.ts +69 -0
  107. package/dist/types/src/Migration.d.ts.map +1 -0
  108. package/dist/types/src/Obj.d.ts +42 -28
  109. package/dist/types/src/Obj.d.ts.map +1 -1
  110. package/dist/types/src/Order.d.ts.map +1 -1
  111. package/dist/types/src/Query.d.ts +13 -2
  112. package/dist/types/src/Query.d.ts.map +1 -1
  113. package/dist/types/src/Ref.d.ts +1 -0
  114. package/dist/types/src/Ref.d.ts.map +1 -1
  115. package/dist/types/src/Relation.d.ts +17 -18
  116. package/dist/types/src/Relation.d.ts.map +1 -1
  117. package/dist/types/src/Tag.d.ts +2 -2
  118. package/dist/types/src/Tag.d.ts.map +1 -1
  119. package/dist/types/src/Type.d.ts +3 -3
  120. package/dist/types/src/Type.d.ts.map +1 -1
  121. package/dist/types/src/View.d.ts +1 -1
  122. package/dist/types/src/View.d.ts.map +1 -1
  123. package/dist/types/src/exemplars.test.d.ts +2 -0
  124. package/dist/types/src/exemplars.test.d.ts.map +1 -0
  125. package/dist/types/src/index.d.ts +3 -0
  126. package/dist/types/src/index.d.ts.map +1 -1
  127. package/dist/types/src/internal/Annotation/annotations.d.ts +12 -2
  128. package/dist/types/src/internal/Annotation/annotations.d.ts.map +1 -1
  129. package/dist/types/src/internal/Annotation/sorting.d.ts.map +1 -1
  130. package/dist/types/src/internal/Annotation/util.d.ts +1 -1
  131. package/dist/types/src/internal/Annotation/util.d.ts.map +1 -1
  132. package/dist/types/src/internal/Entity/api.d.ts.map +1 -1
  133. package/dist/types/src/internal/Entity/model.d.ts +2 -0
  134. package/dist/types/src/internal/Entity/model.d.ts.map +1 -1
  135. package/dist/types/src/internal/Entity/object.d.ts.map +1 -1
  136. package/dist/types/src/internal/Entity/relation.d.ts.map +1 -1
  137. package/dist/types/src/internal/Entity/version.d.ts.map +1 -1
  138. package/dist/types/src/internal/Format/date.d.ts.map +1 -1
  139. package/dist/types/src/internal/Format/format.d.ts.map +1 -1
  140. package/dist/types/src/internal/Format/number.d.ts.map +1 -1
  141. package/dist/types/src/internal/Format/object.d.ts.map +1 -1
  142. package/dist/types/src/internal/Format/types.d.ts.map +1 -1
  143. package/dist/types/src/internal/JsonSchema/json-schema-normalize.d.ts.map +1 -1
  144. package/dist/types/src/internal/JsonSchema/json-schema-type.d.ts +28 -28
  145. package/dist/types/src/internal/JsonSchema/json-schema-type.d.ts.map +1 -1
  146. package/dist/types/src/internal/JsonSchema/json-schema.d.ts +1 -1
  147. package/dist/types/src/internal/JsonSchema/json-schema.d.ts.map +1 -1
  148. package/dist/types/src/internal/Obj/clone.d.ts.map +1 -1
  149. package/dist/types/src/internal/Obj/common.d.ts.map +1 -1
  150. package/dist/types/src/internal/Obj/create-object.d.ts.map +1 -1
  151. package/dist/types/src/internal/Obj/deleted.d.ts.map +1 -1
  152. package/dist/types/src/internal/Obj/ids.d.ts +1 -1
  153. package/dist/types/src/internal/Obj/ids.d.ts.map +1 -1
  154. package/dist/types/src/internal/Obj/json-serializer.d.ts +4 -3
  155. package/dist/types/src/internal/Obj/json-serializer.d.ts.map +1 -1
  156. package/dist/types/src/internal/Obj/set-value.d.ts +1 -1
  157. package/dist/types/src/internal/Obj/set-value.d.ts.map +1 -1
  158. package/dist/types/src/internal/Obj/snapshot.d.ts.map +1 -1
  159. package/dist/types/src/internal/Query.d.ts +10 -0
  160. package/dist/types/src/internal/Query.d.ts.map +1 -0
  161. package/dist/types/src/internal/Ref/ref-array.d.ts.map +1 -1
  162. package/dist/types/src/internal/Ref/ref.d.ts +14 -1
  163. package/dist/types/src/internal/Ref/ref.d.ts.map +1 -1
  164. package/dist/types/src/internal/Type/compose.d.ts.map +1 -1
  165. package/dist/types/src/internal/Type/echo-schema.d.ts +2 -2
  166. package/dist/types/src/internal/Type/echo-schema.d.ts.map +1 -1
  167. package/dist/types/src/internal/Type/manipulation.d.ts.map +1 -1
  168. package/dist/types/src/internal/common/api/meta.d.ts +3 -3
  169. package/dist/types/src/internal/common/api/meta.d.ts.map +1 -1
  170. package/dist/types/src/internal/common/proxy/change-context.d.ts +1 -1
  171. package/dist/types/src/internal/common/proxy/change-context.d.ts.map +1 -1
  172. package/dist/types/src/internal/common/proxy/define-hidden-property.d.ts.map +1 -1
  173. package/dist/types/src/internal/common/proxy/errors.d.ts +1 -1
  174. package/dist/types/src/internal/common/proxy/errors.d.ts.map +1 -1
  175. package/dist/types/src/internal/common/proxy/event-batch.d.ts.map +1 -1
  176. package/dist/types/src/internal/common/proxy/json-serializer.d.ts.map +1 -1
  177. package/dist/types/src/internal/common/proxy/make-object.d.ts.map +1 -1
  178. package/dist/types/src/internal/common/proxy/ownership.d.ts.map +1 -1
  179. package/dist/types/src/internal/common/proxy/proxy-utils.d.ts.map +1 -1
  180. package/dist/types/src/internal/common/proxy/reactive-array.d.ts +1 -1
  181. package/dist/types/src/internal/common/proxy/reactive.d.ts +1 -1
  182. package/dist/types/src/internal/common/proxy/reactive.d.ts.map +1 -1
  183. package/dist/types/src/internal/common/proxy/reactive.test.d.ts +2 -0
  184. package/dist/types/src/internal/common/proxy/reactive.test.d.ts.map +1 -0
  185. package/dist/types/src/internal/common/proxy/schema-validator.d.ts.map +1 -1
  186. package/dist/types/src/internal/common/proxy/typed-handler.d.ts.map +1 -1
  187. package/dist/types/src/internal/common/types/base.d.ts.map +1 -1
  188. package/dist/types/src/internal/common/types/entity.d.ts +4 -4
  189. package/dist/types/src/internal/common/types/entity.d.ts.map +1 -1
  190. package/dist/types/src/internal/common/types/meta.d.ts +10 -0
  191. package/dist/types/src/internal/common/types/meta.d.ts.map +1 -1
  192. package/dist/types/src/internal/common/types/version.d.ts +1 -1
  193. package/dist/types/src/internal/index.d.ts +1 -0
  194. package/dist/types/src/internal/index.d.ts.map +1 -1
  195. package/dist/types/src/testing/test-data.d.ts +8 -8
  196. package/dist/types/src/testing/test-data.d.ts.map +1 -1
  197. package/dist/types/src/testing/test-schema.d.ts +53 -53
  198. package/dist/types/src/testing/test-schema.d.ts.map +1 -1
  199. package/dist/types/src/testing/util.d.ts.map +1 -1
  200. package/dist/types/tsconfig.tsbuildinfo +1 -1
  201. package/package.json +25 -15
  202. package/src/Annotation.ts +1 -0
  203. package/src/Collection.ts +2 -2
  204. package/src/Database.ts +50 -15
  205. package/src/Entity.ts +18 -12
  206. package/src/Extension.test.ts +235 -0
  207. package/src/Extension.ts +122 -0
  208. package/src/Feed.ts +107 -34
  209. package/src/Filter.test.ts +90 -0
  210. package/src/Filter.ts +97 -3
  211. package/src/Hypergraph.ts +3 -3
  212. package/src/Json.test.ts +175 -0
  213. package/src/Json.ts +102 -0
  214. package/src/Migration.ts +106 -0
  215. package/src/Obj.test.ts +105 -13
  216. package/src/Obj.ts +154 -33
  217. package/src/Query.test.ts +199 -9
  218. package/src/Query.ts +58 -8
  219. package/src/Ref.ts +2 -0
  220. package/src/Relation.ts +24 -20
  221. package/src/Type.ts +1 -1
  222. package/src/View.ts +1 -1
  223. package/src/exemplars.test.ts +21 -0
  224. package/src/index.ts +4 -0
  225. package/src/internal/Annotation/annotations.test.ts +51 -2
  226. package/src/internal/Annotation/annotations.ts +33 -14
  227. package/src/internal/Annotation/sorting.ts +0 -1
  228. package/src/internal/Entity/api.ts +0 -1
  229. package/src/internal/Entity/model.ts +2 -0
  230. package/src/internal/Entity/object.ts +0 -1
  231. package/src/internal/Entity/version.ts +0 -1
  232. package/src/internal/Format/date.test.ts +0 -1
  233. package/src/internal/Format/format.test.ts +0 -1
  234. package/src/internal/JsonSchema/json-schema-type.ts +1 -1
  235. package/src/internal/JsonSchema/json-schema.test.ts +1 -2
  236. package/src/internal/JsonSchema/json-schema.ts +1 -2
  237. package/src/internal/Obj/clone.ts +1 -1
  238. package/src/internal/Obj/create-object.test.ts +2 -4
  239. package/src/internal/Obj/create-object.ts +2 -3
  240. package/src/internal/Obj/deleted.ts +1 -1
  241. package/src/internal/Obj/ids.ts +1 -1
  242. package/src/internal/Obj/json-serializer.test.ts +49 -5
  243. package/src/internal/Obj/json-serializer.ts +47 -25
  244. package/src/internal/Obj/set-value.test.ts +24 -24
  245. package/src/internal/Obj/set-value.ts +1 -1
  246. package/src/internal/Query.ts +156 -0
  247. package/src/internal/Ref/ref-array.ts +0 -1
  248. package/src/internal/Ref/ref.test.ts +0 -1
  249. package/src/internal/Ref/ref.ts +18 -1
  250. package/src/internal/Type/compose.test.ts +0 -1
  251. package/src/internal/Type/echo-schema.ts +3 -4
  252. package/src/internal/common/README.md +1 -1
  253. package/src/internal/common/api/meta.ts +3 -3
  254. package/src/internal/common/proxy/change-context.ts +1 -1
  255. package/src/internal/common/proxy/change.test.ts +94 -94
  256. package/src/internal/common/proxy/errors.ts +2 -2
  257. package/src/internal/common/proxy/handler.test.ts +0 -2
  258. package/src/internal/common/proxy/json-serializer.ts +4 -1
  259. package/src/internal/common/proxy/make-object.ts +0 -1
  260. package/src/internal/common/proxy/ownership.ts +0 -1
  261. package/src/internal/common/proxy/reactive-array.ts +1 -1
  262. package/src/internal/common/proxy/reactive.test.ts +54 -0
  263. package/src/internal/common/proxy/reactive.ts +11 -3
  264. package/src/internal/common/proxy/typed-handler.test.ts +0 -1
  265. package/src/internal/common/proxy/typed-handler.ts +8 -10
  266. package/src/internal/common/proxy/typed-object.test.ts +2 -3
  267. package/src/internal/common/types/entity.ts +1 -1
  268. package/src/internal/common/types/meta.ts +12 -1
  269. package/src/internal/index.ts +1 -0
  270. package/src/testing/api.test.ts +0 -1
  271. package/src/testing/test-data.ts +157 -98
  272. package/dist/lib/neutral/chunk-43Y5DOS6.mjs.map +0 -7
  273. package/dist/lib/neutral/chunk-4JRI2ZJI.mjs.map +0 -7
  274. package/dist/lib/neutral/chunk-6DNYDXCV.mjs.map +0 -7
  275. package/dist/lib/neutral/chunk-6GPU7XC3.mjs.map +0 -7
  276. package/dist/lib/neutral/chunk-7VNVH63N.mjs.map +0 -7
  277. package/dist/lib/neutral/chunk-BOZZPUXE.mjs.map +0 -7
  278. package/dist/lib/neutral/chunk-C4JZK4J7.mjs.map +0 -7
  279. package/dist/lib/neutral/chunk-NEGC54NE.mjs.map +0 -7
  280. package/dist/lib/neutral/chunk-NXMFBIT5.mjs.map +0 -7
  281. package/dist/lib/neutral/chunk-OENWMTE6.mjs.map +0 -7
  282. package/dist/lib/neutral/chunk-ROAGDPV7.mjs.map +0 -7
  283. package/dist/lib/neutral/chunk-SEMVAGBM.mjs.map +0 -7
  284. package/dist/lib/neutral/chunk-W47JKR3X.mjs.map +0 -7
  285. /package/dist/lib/neutral/{chunk-FZO7LQO7.mjs.map → chunk-44HT3MEC.mjs.map} +0 -0
  286. /package/dist/lib/neutral/{chunk-ANHVGJI4.mjs.map → chunk-7RVZT53K.mjs.map} +0 -0
  287. /package/dist/lib/neutral/{chunk-BNCCGLJN.mjs.map → chunk-BICZKPQG.mjs.map} +0 -0
  288. /package/dist/lib/neutral/{chunk-6K2MVI2O.mjs.map → chunk-BUBEC474.mjs.map} +0 -0
  289. /package/dist/lib/neutral/{chunk-UTBRYVQC.mjs.map → chunk-QRZ2I3ZM.mjs.map} +0 -0
  290. /package/dist/lib/neutral/{chunk-B5OXLWZL.mjs.map → chunk-TRPZU2HV.mjs.map} +0 -0
  291. /package/dist/lib/neutral/{chunk-UI6MWK5W.mjs.map → chunk-TTCSATUD.mjs.map} +0 -0
  292. /package/dist/lib/neutral/{chunk-OMUPQMLR.mjs.map → chunk-V72DY6LU.mjs.map} +0 -0
package/src/Obj.ts CHANGED
@@ -5,22 +5,25 @@
5
5
  // @import-as-namespace
6
6
 
7
7
  import * as Effect from 'effect/Effect';
8
+ import * as Equal from 'effect/Equal';
8
9
  import * as Function from 'effect/Function';
9
10
  import * as Option from 'effect/Option';
10
11
  import * as Schema from 'effect/Schema';
12
+ import * as Utils from 'effect/Utils';
11
13
 
12
14
  import type { ForeignKey } from '@dxos/echo-protocol';
13
15
  import { createJsonPath } from '@dxos/effect';
14
- import { assertArgument } from '@dxos/invariant';
16
+ import { assertArgument, invariant } from '@dxos/invariant';
15
17
  import { type DXN, ObjectId } from '@dxos/keys';
16
- import { assumeType } from '@dxos/util';
18
+ import { assumeType, deepMapValues } from '@dxos/util';
17
19
 
18
20
  import type * as Database from './Database';
19
21
  import * as Entity from './Entity';
20
22
  import * as Err from './Err';
21
- import * as objInternal from './internal/Obj';
22
23
  import * as internal from './internal';
23
- import type * as Ref from './Ref';
24
+ import { getProxyTarget, isProxy } from './internal/common/proxy/proxy-utils';
25
+ import * as objInternal from './internal/Obj';
26
+ import * as Ref from './Ref';
24
27
  import type * as Type from './Type';
25
28
 
26
29
  /**
@@ -81,8 +84,6 @@ export const Unknown: Type.Obj<Unknown> = Schema.Struct({
81
84
  }) as unknown as Type.Obj<Unknown>,
82
85
  );
83
86
 
84
- /**
85
-
86
87
  /**
87
88
  * Object with arbitrary properties.
88
89
  *
@@ -303,7 +304,7 @@ export const clone: <T extends Unknown>(obj: T, opts?: CloneOptions) => T = objI
303
304
 
304
305
  /**
305
306
  * Makes all properties mutable recursively.
306
- * Used to provide a mutable view of an object within `Obj.change`.
307
+ * Used to provide a mutable view of an object within `Obj.update`.
307
308
  */
308
309
  export type Mutable<T> = internal.Mutable<T>;
309
310
 
@@ -311,33 +312,33 @@ export type Mutable<T> = internal.Mutable<T>;
311
312
  * Perform mutations on an echo object within a controlled context.
312
313
  *
313
314
  * All mutations within the callback are batched and trigger a single notification
314
- * when the callback completes. Direct mutations outside of `Obj.change` will throw
315
+ * when the callback completes. Direct mutations outside of `Obj.update` will throw
315
316
  * an error for echo objects.
316
317
  *
317
318
  * This function also works with nested objects within echo objects (e.g., Template structs)
318
319
  * that are reactive at runtime.
319
320
  *
320
- * @param obj - The echo object to mutate. Use `Relation.change` for relations.
321
+ * @param obj - The echo object to mutate. Use `Relation.update` for relations.
321
322
  * @param callback - The callback that performs mutations on the object.
322
323
  *
323
324
  * @example
324
325
  * ```ts
325
326
  * const person = Obj.make(Person, { name: 'John', age: 25 });
326
327
  *
327
- * // Mutate within Obj.change
328
- * Obj.change(person, (p) => {
329
- * p.name = 'Jane';
330
- * p.age = 30;
328
+ * // Mutate within Obj.update
329
+ * Obj.update(person, (obj) => {
330
+ * obj.name = 'Jane';
331
+ * obj.age = 30;
331
332
  * });
332
333
  * // ONE notification fires here
333
334
  *
334
335
  * // Direct mutation throws
335
- * person.name = 'Bob'; // Error: Cannot modify outside Obj.change()
336
+ * person.name = 'Bob'; // Error: Cannot modify outside Obj.update()
336
337
  * ```
337
338
  *
338
- * Note: Only accepts objects. Use `Relation.change` for relations.
339
+ * Note: Only accepts objects. Use `Relation.update` for relations.
339
340
  */
340
- export const change = <T extends Unknown>(obj: T, callback: internal.ChangeCallback<T>): void => {
341
+ export const update = <T extends Unknown>(obj: T, callback: internal.ChangeCallback<T>): void => {
341
342
  internal.change(obj, callback);
342
343
  };
343
344
 
@@ -372,7 +373,7 @@ export const getValue = (obj: Unknown | Snapshot, path: readonly (string | numbe
372
373
  * whether to initialize nested data as an empty object or array.
373
374
  *
374
375
  * Similar to lodash.set and setDeep from @dxos/util, but schema-aware.
375
- * Must be called within an `Obj.change` callback.
376
+ * Must be called within an `Obj.update` callback.
376
377
  *
377
378
  * NOTE: TypeScript's structural typing allows readonly objects to be passed to `Mutable<T>`
378
379
  * parameters, so there is no compile-time error. Enforcement is runtime-only.
@@ -386,8 +387,8 @@ export const getValue = (obj: Unknown | Snapshot, path: readonly (string | numbe
386
387
  * ```ts
387
388
  * const person = Obj.make(Person, { name: 'John' });
388
389
  * // Person schema has: addresses: Schema.Array(Address)
389
- * Obj.change(person, (p) => {
390
- * Obj.setValue(p, ['addresses', 0, 'street'], '123 Main St');
390
+ * Obj.update(person, (obj) => {
391
+ * Obj.setValue(obj, ['addresses', 0, 'street'], '123 Main St');
391
392
  * });
392
393
  * // Creates: person.addresses = [{ street: '123 Main St' }]
393
394
  * ```
@@ -469,16 +470,19 @@ export const getDXN = (entity: Unknown | Snapshot): DXN => {
469
470
  /**
470
471
  * @returns The DXN of the object's type.
471
472
  * @example dxn:com.example.type.person:1.0.0
473
+ * @throws If the object is missing its type (corrupted object).
472
474
  */
473
- // TODO(wittjosiah): Narrow types.
474
- export const getTypeDXN: (obj: unknown | undefined) => DXN | undefined = internal.getTypeDXN as any;
475
+ export const getTypeDXN = (obj: Unknown | Snapshot): DXN => {
476
+ const type = internal.getTypeDXN(obj);
477
+ invariant(type != null, 'Corrupted object: missing type.');
478
+ return type;
479
+ };
475
480
 
476
481
  /**
477
482
  * Get the schema of the object.
478
483
  * Returns the branded ECHO schema used to create the object.
479
484
  */
480
- // TODO(wittjosiah): Narrow types.
481
- export const getSchema: (obj: unknown | undefined) => Type.AnyEntity | undefined = internal.getSchema as any;
485
+ export const getSchema: (obj: Unknown | Snapshot) => Type.AnyEntity | undefined = internal.getSchema as any;
482
486
 
483
487
  /**
484
488
  * @returns The typename of the object's type.
@@ -516,7 +520,7 @@ export const Meta = internal.MetaId;
516
520
  export type ReadonlyMeta = internal.ReadonlyMeta;
517
521
 
518
522
  /**
519
- * Mutable meta type returned by `Obj.getMeta` inside an `Obj.change` callback.
523
+ * Mutable meta type returned by `Obj.getMeta` inside an `Obj.update` callback.
520
524
  */
521
525
  export type Meta = internal.Meta;
522
526
 
@@ -524,7 +528,7 @@ export type Meta = internal.Meta;
524
528
  // TODO(dmaretskyi): Allow returning undefined.
525
529
  /**
526
530
  * Get the metadata for an object.
527
- * Returns mutable meta when passed a mutable object (inside `Obj.change` callback).
531
+ * Returns mutable meta when passed a mutable object (inside `Obj.update` callback).
528
532
  * Returns read-only meta when passed a regular object or snapshot.
529
533
  *
530
534
  * @example
@@ -533,8 +537,8 @@ export type Meta = internal.Meta;
533
537
  * const meta = Obj.getMeta(person); // ReadonlyMeta
534
538
  *
535
539
  * // Mutable access inside change callback
536
- * Obj.change(person, (p) => {
537
- * const meta = Obj.getMeta(p); // ObjectMeta (mutable)
540
+ * Obj.update(person, (obj) => {
541
+ * const meta = Obj.getMeta(obj); // ObjectMeta (mutable)
538
542
  * meta.tags ??= [];
539
543
  * meta.tags.push('important');
540
544
  * });
@@ -558,7 +562,7 @@ export const getKeys: {
558
562
 
559
563
  /**
560
564
  * Delete all keys from the object for the specified source.
561
- * Must be called within an `Obj.change` callback.
565
+ * Must be called within an `Obj.update` callback.
562
566
  *
563
567
  * NOTE: TypeScript's structural typing allows readonly objects to be passed to `Mutable<T>`
564
568
  * parameters, so there is no compile-time error. Enforcement is runtime-only.
@@ -567,7 +571,7 @@ export const deleteKeys = (entity: Mutable<Unknown>, source: string): void => in
567
571
 
568
572
  /**
569
573
  * Add a tag to the object.
570
- * Must be called within an `Obj.change` callback.
574
+ * Must be called within an `Obj.update` callback.
571
575
  *
572
576
  * NOTE: TypeScript's structural typing allows readonly objects to be passed to `Mutable<T>`
573
577
  * parameters, so there is no compile-time error. Enforcement is runtime-only.
@@ -576,7 +580,7 @@ export const addTag = (entity: Mutable<Unknown>, tag: string): void => internal.
576
580
 
577
581
  /**
578
582
  * Remove a tag from the object.
579
- * Must be called within an `Obj.change` callback.
583
+ * Must be called within an `Obj.update` callback.
580
584
  *
581
585
  * NOTE: TypeScript's structural typing allows readonly objects to be passed to `Mutable<T>`
582
586
  * parameters, so there is no compile-time error. Enforcement is runtime-only.
@@ -602,7 +606,7 @@ export const getLabel = (entity: Unknown | Snapshot): string | undefined => inte
602
606
 
603
607
  /**
604
608
  * Set the label of the object.
605
- * Must be called within an `Obj.change` callback.
609
+ * Must be called within an `Obj.update` callback.
606
610
  *
607
611
  * NOTE: TypeScript's structural typing allows readonly objects to be passed to `Mutable<T>`
608
612
  * parameters, so there is no compile-time error. Enforcement is runtime-only.
@@ -617,7 +621,7 @@ export const getDescription = (entity: Unknown | Snapshot): string | undefined =
617
621
 
618
622
  /**
619
623
  * Set the description of the object.
620
- * Must be called within an `Obj.change` callback.
624
+ * Must be called within an `Obj.update` callback.
621
625
  *
622
626
  * NOTE: TypeScript's structural typing allows readonly objects to be passed to `Mutable<T>`
623
627
  * parameters, so there is no compile-time error. Enforcement is runtime-only.
@@ -660,6 +664,123 @@ export const setParent = (entity: Unknown, parent: Any | undefined) => {
660
664
  assumeType<internal.InternalObjectProps>(entity);
661
665
  assumeType<internal.InternalObjectProps | undefined>(parent);
662
666
  entity[internal.ParentId] = parent;
667
+ return entity;
668
+ };
669
+
670
+ interface UpdateFromOptions<T> {
671
+ exclude?: (keyof T)[];
672
+ include?: (keyof T)[];
673
+ }
674
+
675
+ const valuesEqual = (left: unknown, right: unknown): boolean => {
676
+ if (left === right) {
677
+ return true;
678
+ }
679
+ if (left === null || right === null) {
680
+ return left === right;
681
+ }
682
+ if (typeof left !== 'object' || typeof right !== 'object') {
683
+ return Utils.structuralRegion(() => Equal.equals(left, right));
684
+ }
685
+ if (Ref.isRef(left) && Ref.isRef(right)) {
686
+ return left.dxn.toString() === right.dxn.toString();
687
+ }
688
+ if (Ref.isRef(left) || Ref.isRef(right)) {
689
+ return false;
690
+ }
691
+ if (Array.isArray(left) && Array.isArray(right)) {
692
+ if (left.length !== right.length) {
693
+ return false;
694
+ }
695
+ for (let index = 0; index < left.length; index++) {
696
+ if (!valuesEqual(left[index], right[index])) {
697
+ return false;
698
+ }
699
+ }
700
+ return true;
701
+ }
702
+ if (Array.isArray(left) || Array.isArray(right)) {
703
+ return false;
704
+ }
705
+ const leftRecord = left as Record<string, unknown>;
706
+ const rightRecord = right as Record<string, unknown>;
707
+ const keys = new Set([
708
+ ...Object.keys(leftRecord).filter((key) => key !== 'id'),
709
+ ...Object.keys(rightRecord).filter((key) => key !== 'id'),
710
+ ]);
711
+ for (const key of keys) {
712
+ const leftHas = Object.hasOwn(leftRecord, key);
713
+ const rightHas = Object.hasOwn(rightRecord, key);
714
+ const leftValue = leftHas ? leftRecord[key] : undefined;
715
+ const rightValue = rightHas ? rightRecord[key] : undefined;
716
+ if (!valuesEqual(leftValue, rightValue)) {
717
+ return false;
718
+ }
719
+ }
720
+ return true;
721
+ };
722
+
723
+ /**
724
+ * Breaks reactive proxies on assigned values so echo-db assignment accepts nested structs (same idea as link assignment).
725
+ */
726
+ const prepareAssignValue = (value: unknown): unknown =>
727
+ deepMapValues(value, (nested, recurse) => {
728
+ if (nested === null || typeof nested !== 'object') {
729
+ return nested;
730
+ }
731
+ if (Ref.isRef(nested)) {
732
+ return nested;
733
+ }
734
+ if (Array.isArray(nested)) {
735
+ return recurse(nested);
736
+ }
737
+ if (isProxy(nested)) {
738
+ return recurse({ ...getProxyTarget(nested) });
739
+ }
740
+ return recurse(nested);
741
+ });
742
+
743
+ /**
744
+ * For each key present on `source` (except `id`), assigns `target[key]` when the current value differs.
745
+ * References are compared by target DXN; other values use Effect `Equal.equals` inside a structural region,
746
+ * with recursive comparison for arrays and plain object-shaped property bags (excluding `id`).
747
+ *
748
+ * Must be called within an `Obj.update` callback.
749
+ *
750
+ * @returns Whether any property was updated.
751
+ */
752
+ export const updateFrom = <T extends Unknown>(
753
+ target: Mutable<T>,
754
+ source: T,
755
+ options?: UpdateFromOptions<T>,
756
+ ): boolean => {
757
+ assertArgument(isObject(target), 'Expected an echo object target.');
758
+ assertArgument(isObject(source), 'Expected an echo object source.');
759
+ let keys = Object.keys(source as Record<string, unknown>).filter((key) => key !== 'id');
760
+ if (options?.include !== undefined) {
761
+ const include = new Set(options.include.map((key) => String(key)));
762
+ keys = keys.filter((key) => include.has(key));
763
+ }
764
+ if (options?.exclude !== undefined) {
765
+ const exclude = new Set(options.exclude.map((key) => String(key)));
766
+ keys = keys.filter((key) => !exclude.has(key));
767
+ }
768
+ let updated = false;
769
+ const sourceRecord = source as Record<string, unknown>;
770
+ const targetRecord = target as Record<string, unknown>;
771
+ for (const key of keys) {
772
+ if (!Object.hasOwn(sourceRecord, key)) {
773
+ continue;
774
+ }
775
+ const nextValue = sourceRecord[key];
776
+ const prevValue = Object.hasOwn(targetRecord, key) ? targetRecord[key] : undefined;
777
+ if (valuesEqual(prevValue, nextValue)) {
778
+ continue;
779
+ }
780
+ targetRecord[key] = prepareAssignValue(nextValue) as never;
781
+ updated = true;
782
+ }
783
+ return updated;
663
784
  };
664
785
 
665
786
  //
@@ -691,7 +812,7 @@ export const toJSON = (entity: Unknown | Snapshot): JSON => objInternal.objectTo
691
812
  */
692
813
  export const fromJSON: (
693
814
  json: unknown,
694
- options?: { refResolver?: Ref.Resolver; dxn?: DXN; database?: Database.Database },
815
+ options?: { refResolver?: Ref.Resolver; dxn?: DXN; database?: Database.Database; parent?: Unknown },
695
816
  ) => Promise<Unknown> = objInternal.objectFromJSON as any;
696
817
 
697
818
  /**
package/src/Query.test.ts CHANGED
@@ -6,7 +6,7 @@ import * as Schema from 'effect/Schema';
6
6
  import { describe, expect, test } from 'vitest';
7
7
 
8
8
  import { QueryAST } from '@dxos/echo-protocol';
9
- import { DXN } from '@dxos/keys';
9
+ import { DXN, ObjectId, SpaceId } from '@dxos/keys';
10
10
  import { log } from '@dxos/log';
11
11
 
12
12
  import * as Dataset from './Dataset';
@@ -523,7 +523,7 @@ describe('query api', () => {
523
523
  "from": {
524
524
  "_tag": "scope",
525
525
  "scope": {
526
- "allQueuesFromSpaces": true,
526
+ "allFeedsFromSpaces": true,
527
527
  },
528
528
  },
529
529
  "query": {
@@ -560,12 +560,20 @@ describe('query api', () => {
560
560
  });
561
561
  });
562
562
 
563
- test('Query.type(...).from(feed) sets queue scope', () => {
564
- const feed = Feed.make({ name: 'test-feed' });
565
- const queueDxn = DXN.parse('dxn:echo:test-space:test-queue');
566
- Obj.change(feed, (mutable) => {
567
- Obj.getMeta(mutable).keys.push({ source: Feed.DXN_KEY, id: queueDxn.toString() });
568
- });
563
+ test('Query.type(...).from(feed) sets queue scope', async () => {
564
+ const spaceId = SpaceId.random();
565
+ const feedId = ObjectId.random();
566
+ const feedDXN = DXN.parse(`dxn:echo:${spaceId}:${feedId}`);
567
+ const feed = (await Obj.fromJSON(
568
+ {
569
+ '@type': 'dxn:type:org.dxos.type.feed:0.1.0',
570
+ id: feedId,
571
+ name: 'test-feed',
572
+ },
573
+ { dxn: feedDXN },
574
+ )) as Feed.Feed;
575
+
576
+ const expectedQueueDXN = new DXN(DXN.kind.QUEUE, ['data', spaceId, feedId]);
569
577
 
570
578
  const query = Query.type(TestSchema.Person).from(feed);
571
579
  Schema.validateSync(QueryAST.Query)(query.ast);
@@ -574,7 +582,7 @@ describe('query api', () => {
574
582
  from: {
575
583
  _tag: 'scope',
576
584
  scope: {
577
- queues: [queueDxn.toString()],
585
+ feeds: [expectedQueueDXN.toString()],
578
586
  },
579
587
  },
580
588
  query: {
@@ -587,6 +595,60 @@ describe('query api', () => {
587
595
  });
588
596
  });
589
597
 
598
+ test('Query.from(non-feed) throws TypeError', () => {
599
+ const person = Obj.make(TestSchema.Person, { name: 'Fred' });
600
+ expect(() => Query.select(Filter.type(TestSchema.Person)).from(person as any)).toThrow(TypeError);
601
+ expect(() => Query.select(Filter.type(TestSchema.Person)).from(person as any)).toThrow(
602
+ /Query\.from\(\) expects Feed objects/,
603
+ );
604
+ });
605
+
606
+ test('Query.from(undefined) throws TypeError', () => {
607
+ expect(() => Query.select(Filter.type(TestSchema.Person)).from(undefined as any)).toThrow(TypeError);
608
+ expect(() => Query.select(Filter.type(TestSchema.Person)).from(undefined as any)).toThrow(
609
+ /Query\.from\(\) requires a valid data source argument/,
610
+ );
611
+ });
612
+
613
+ test('Query.from(null) throws TypeError', () => {
614
+ expect(() => Query.select(Filter.type(TestSchema.Person)).from(null as any)).toThrow(TypeError);
615
+ expect(() => Query.select(Filter.type(TestSchema.Person)).from(null as any)).toThrow(
616
+ /Query\.from\(\) requires a valid data source argument/,
617
+ );
618
+ });
619
+
620
+ test('Query.pretty surfaces debugLabel from options', () => {
621
+ const query = Query.select(Filter.type(TestSchema.Person)).debugLabel('my-label');
622
+ expect(Query.pretty(query)).toContain('debugLabel');
623
+ expect(Query.pretty(query)).toContain('"my-label"');
624
+ });
625
+
626
+ test('Query.debugLabel merges onto existing options clause', () => {
627
+ const query = Query.select(Filter.type(TestSchema.Person))
628
+ .options({ deleted: 'exclude' })
629
+ .debugLabel('timer-probe');
630
+ const pretty = Query.pretty(query);
631
+ expect(pretty).toContain('deleted');
632
+ expect(pretty).toContain('debugLabel');
633
+ expect(pretty).toContain('"timer-probe"');
634
+ });
635
+
636
+ test('Query.pretty returns human-readable query string', () => {
637
+ const query = Query.select(Filter.type(TestSchema.Person, { name: 'Fred' }));
638
+ const pretty = Query.pretty(query);
639
+ expect(pretty).toContain('Query.select');
640
+ expect(pretty).toContain('Filter.type');
641
+ expect(pretty).toContain('com.example.type.person');
642
+ });
643
+
644
+ test('Query.pretty handles complex queries', () => {
645
+ const query = Query.select(Filter.and(Filter.type(TestSchema.Person), Filter.id(ObjectId.random()))).limit(10);
646
+ const pretty = Query.pretty(query);
647
+ expect(pretty).toContain('Query.select');
648
+ expect(pretty).toContain('Filter.and');
649
+ expect(pretty).toContain('limit(10)');
650
+ });
651
+
590
652
  test.skip('chain', () => {
591
653
  // NOTE: Can't support props without type since they can't be inferred.
592
654
  // const f1: Filter<Person> = Filter.props({ name: 'Fred' });
@@ -622,11 +684,139 @@ describe('query api', () => {
622
684
  });
623
685
  });
624
686
 
687
+ describe('Filter.childOf', () => {
688
+ test('childOf with Ref', () => {
689
+ const parentDXN = DXN.fromSpaceAndObjectId(SpaceId.random(), ObjectId.random());
690
+ const parentRef = Ref.fromDXN(parentDXN);
691
+ const filter = Filter.childOf(parentRef);
692
+
693
+ expect(filter.ast).toMatchObject({
694
+ type: 'child-of',
695
+ parents: [parentDXN.toString()],
696
+ transitive: true,
697
+ });
698
+ Schema.validateSync(QueryAST.Filter)(filter.ast);
699
+ });
700
+
701
+ test('childOf with object', () => {
702
+ const parent = Obj.make(TestSchema.Person, { name: 'Parent' });
703
+ const filter = Filter.childOf(parent);
704
+
705
+ expect(filter.ast).toMatchObject({
706
+ type: 'child-of',
707
+ transitive: true,
708
+ });
709
+ expect(filter.ast.type === 'child-of' && filter.ast.parents.length).toBe(1);
710
+ Schema.validateSync(QueryAST.Filter)(filter.ast);
711
+ });
712
+
713
+ test('childOf with array of Refs', () => {
714
+ const dxn1 = DXN.fromSpaceAndObjectId(SpaceId.random(), ObjectId.random());
715
+ const dxn2 = DXN.fromSpaceAndObjectId(SpaceId.random(), ObjectId.random());
716
+ const filter = Filter.childOf([Ref.fromDXN(dxn1), Ref.fromDXN(dxn2)]);
717
+
718
+ expect(filter.ast).toMatchObject({
719
+ type: 'child-of',
720
+ parents: [dxn1.toString(), dxn2.toString()],
721
+ transitive: true,
722
+ });
723
+ Schema.validateSync(QueryAST.Filter)(filter.ast);
724
+ });
725
+
726
+ test('childOf with transitive=false', () => {
727
+ const parentRef = Ref.fromDXN(DXN.fromSpaceAndObjectId(SpaceId.random(), ObjectId.random()));
728
+ const filter = Filter.childOf(parentRef, { transitive: false });
729
+
730
+ expect(filter.ast).toMatchObject({
731
+ type: 'child-of',
732
+ parents: [parentRef.dxn.toString()],
733
+ transitive: false,
734
+ });
735
+ Schema.validateSync(QueryAST.Filter)(filter.ast);
736
+ });
737
+
738
+ test('childOf in select query', () => {
739
+ const parentDXN = DXN.fromSpaceAndObjectId(SpaceId.random(), ObjectId.random());
740
+ const parentRef = Ref.fromDXN(parentDXN);
741
+ const query = Query.select(Filter.childOf(parentRef));
742
+
743
+ expect(query.ast).toMatchObject({
744
+ type: 'select',
745
+ filter: {
746
+ type: 'child-of',
747
+ parents: [parentDXN.toString()],
748
+ transitive: true,
749
+ },
750
+ });
751
+ Schema.validateSync(QueryAST.Query)(query.ast);
752
+ });
753
+
754
+ test('childOf combined with type filter', () => {
755
+ const parentDXN = DXN.fromSpaceAndObjectId(SpaceId.random(), ObjectId.random());
756
+ const parentRef = Ref.fromDXN(parentDXN);
757
+ const query = Query.select(Filter.and(Filter.type(TestSchema.Person), Filter.childOf(parentRef)));
758
+
759
+ Schema.validateSync(QueryAST.Query)(query.ast);
760
+ expect(query.ast).toMatchObject({
761
+ type: 'select',
762
+ filter: {
763
+ type: 'and',
764
+ filters: [
765
+ { type: 'object', typename: 'dxn:type:com.example.type.person:0.1.0' },
766
+ { type: 'child-of', parents: [parentDXN.toString()], transitive: true },
767
+ ],
768
+ },
769
+ });
770
+ });
771
+
772
+ test('childOf pretty-prints correctly', () => {
773
+ const parentRef = Ref.fromDXN(DXN.fromSpaceAndObjectId(SpaceId.random(), ObjectId.random()));
774
+ const filter = Filter.childOf(parentRef);
775
+ const pretty = Filter.pretty(filter);
776
+ expect(pretty).toContain('Filter.childOf');
777
+ expect(pretty).toContain('transitive: true');
778
+ });
779
+
780
+ test('childOf with mixed objects and Refs', () => {
781
+ const parent = Obj.make(TestSchema.Person, { name: 'Parent' });
782
+ const refDXN = DXN.fromSpaceAndObjectId(SpaceId.random(), ObjectId.random());
783
+ const parentRef = Ref.fromDXN(refDXN);
784
+ const filter = Filter.childOf([parent, parentRef]);
785
+
786
+ expect(filter.ast).toMatchObject({
787
+ type: 'child-of',
788
+ transitive: true,
789
+ });
790
+ expect(filter.ast.type === 'child-of' && filter.ast.parents.length).toBe(2);
791
+ Schema.validateSync(QueryAST.Filter)(filter.ast);
792
+ });
793
+ });
794
+
625
795
  describe('Filter', () => {
626
796
  test('Filter.or(Filter.typename(...))', () => {
627
797
  const filter = Filter.or(Filter.typename('com.example.type.person'));
628
798
  // TODO(dmaretskyi): Give vitest type-tests a try.
629
799
  const _isAssignable: Obj.Unknown = null as any as Filter.Type<typeof filter>;
630
800
  });
801
+
802
+ test('Filter.pretty returns human-readable filter string', () => {
803
+ const filter = Filter.type(TestSchema.Person, { name: 'Fred' });
804
+ const pretty = Filter.pretty(filter);
805
+ expect(pretty).toContain('Filter.type');
806
+ expect(pretty).toContain('com.example.type.person');
807
+ });
808
+
809
+ test('Filter.pretty handles complex filters', () => {
810
+ const filter = Filter.and(Filter.type(TestSchema.Person), Filter.id(ObjectId.random()));
811
+ const pretty = Filter.pretty(filter);
812
+ expect(pretty).toContain('Filter.and');
813
+ expect(pretty).toContain('Filter.type');
814
+ });
815
+
816
+ test('Filter.pretty handles or filters', () => {
817
+ const filter = Filter.or(Filter.type(TestSchema.Person), Filter.type(TestSchema.Organization));
818
+ const pretty = Filter.pretty(filter);
819
+ expect(pretty).toContain('Filter.or');
820
+ });
631
821
  });
632
822
  });