@dxos/echo 0.8.4-main.74a063c4e0 → 0.8.4-main.765dc60934

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 (248) 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 +6 -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 +1 -1
  8. package/dist/lib/neutral/Feed.mjs +21 -17
  9. package/dist/lib/neutral/Filter.mjs +15 -11
  10. package/dist/lib/neutral/Format.mjs +3 -3
  11. package/dist/lib/neutral/JsonSchema.mjs +8 -8
  12. package/dist/lib/neutral/Key.mjs +1 -1
  13. package/dist/lib/neutral/Migration.mjs +17 -0
  14. package/dist/lib/neutral/Migration.mjs.map +7 -0
  15. package/dist/lib/neutral/Obj.mjs +14 -14
  16. package/dist/lib/neutral/Order.mjs +1 -1
  17. package/dist/lib/neutral/Query.mjs +17 -17
  18. package/dist/lib/neutral/QueryResult.mjs +1 -1
  19. package/dist/lib/neutral/Ref.mjs +9 -7
  20. package/dist/lib/neutral/Relation.mjs +15 -15
  21. package/dist/lib/neutral/SchemaRegistry.mjs +1 -1
  22. package/dist/lib/neutral/Tag.mjs +14 -14
  23. package/dist/lib/neutral/Type.mjs +10 -10
  24. package/dist/lib/neutral/{chunk-GZQTCRJB.mjs → chunk-44HT3MEC.mjs} +2 -2
  25. package/dist/lib/neutral/{chunk-ZIXGDU6F.mjs → chunk-5SL5LDLD.mjs} +4 -2
  26. package/dist/lib/neutral/chunk-5SL5LDLD.mjs.map +7 -0
  27. package/dist/lib/neutral/{chunk-ANHVGJI4.mjs → chunk-7RVZT53K.mjs} +1 -1
  28. package/dist/lib/neutral/{chunk-MYMU4TWK.mjs → chunk-7VWWUTRQ.mjs} +38 -17
  29. package/dist/lib/neutral/chunk-7VWWUTRQ.mjs.map +7 -0
  30. package/dist/lib/neutral/{chunk-66GBFH43.mjs → chunk-A6IYRT5A.mjs} +17 -45
  31. package/dist/lib/neutral/chunk-A6IYRT5A.mjs.map +7 -0
  32. package/dist/lib/neutral/{chunk-R72KFH2X.mjs → chunk-APHSOTIX.mjs} +2 -2
  33. package/dist/lib/neutral/{chunk-XZJUNTML.mjs → chunk-APJKDGFL.mjs} +36 -6
  34. package/dist/lib/neutral/chunk-APJKDGFL.mjs.map +7 -0
  35. package/dist/lib/neutral/{chunk-BNCCGLJN.mjs → chunk-BICZKPQG.mjs} +1 -1
  36. package/dist/lib/neutral/{chunk-QWAOTFCY.mjs → chunk-BMB7IHGB.mjs} +16 -66
  37. package/dist/lib/neutral/chunk-BMB7IHGB.mjs.map +7 -0
  38. package/dist/lib/neutral/{chunk-KPY6FC5I.mjs → chunk-BVOWYVTD.mjs} +17 -12
  39. package/dist/lib/neutral/chunk-BVOWYVTD.mjs.map +7 -0
  40. package/dist/lib/neutral/chunk-FIWO2FZK.mjs +36 -0
  41. package/dist/lib/neutral/chunk-FIWO2FZK.mjs.map +7 -0
  42. package/dist/lib/neutral/{chunk-WMFVS4KR.mjs → chunk-G54OX4IX.mjs} +102 -480
  43. package/dist/lib/neutral/chunk-G54OX4IX.mjs.map +7 -0
  44. package/dist/lib/neutral/{chunk-7QQMUDE6.mjs → chunk-GMS5PKGQ.mjs} +59 -12
  45. package/dist/lib/neutral/chunk-GMS5PKGQ.mjs.map +7 -0
  46. package/dist/lib/neutral/{chunk-YS6Q3XAD.mjs → chunk-GWFFC34K.mjs} +1 -1
  47. package/dist/lib/neutral/{chunk-YS6Q3XAD.mjs.map → chunk-GWFFC34K.mjs.map} +1 -1
  48. package/dist/lib/neutral/{chunk-EBVB5NOH.mjs → chunk-I2DARWPX.mjs} +15 -20
  49. package/dist/lib/neutral/chunk-I2DARWPX.mjs.map +7 -0
  50. package/dist/lib/neutral/{chunk-FNEFSO2C.mjs → chunk-MLS7U7AT.mjs} +12 -65
  51. package/dist/lib/neutral/{chunk-FNEFSO2C.mjs.map → chunk-MLS7U7AT.mjs.map} +2 -2
  52. package/dist/lib/neutral/{chunk-TBKX6JQO.mjs → chunk-N4B7FHQT.mjs} +1 -1
  53. package/dist/lib/neutral/{chunk-46QNGNTY.mjs → chunk-N7VOEPSV.mjs} +6 -3
  54. package/dist/lib/neutral/{chunk-46QNGNTY.mjs.map → chunk-N7VOEPSV.mjs.map} +3 -3
  55. package/dist/lib/neutral/{chunk-MZXWVR3T.mjs → chunk-PSZBLH53.mjs} +6 -22
  56. package/dist/lib/neutral/{chunk-MZXWVR3T.mjs.map → chunk-PSZBLH53.mjs.map} +2 -2
  57. package/dist/lib/neutral/{chunk-XZM7FERR.mjs → chunk-QBIKZ3TY.mjs} +4 -4
  58. package/dist/lib/neutral/{chunk-E5PBQJWV.mjs → chunk-QRZ2I3ZM.mjs} +2 -2
  59. package/dist/lib/neutral/{chunk-2U2BZDLU.mjs → chunk-SCPFDS2E.mjs} +12 -8
  60. package/dist/lib/neutral/chunk-SCPFDS2E.mjs.map +7 -0
  61. package/dist/lib/neutral/{chunk-VCAP4OOT.mjs → chunk-SKDTKHDC.mjs} +21 -5
  62. package/dist/lib/neutral/chunk-SKDTKHDC.mjs.map +7 -0
  63. package/dist/lib/neutral/{chunk-6URFBQJH.mjs → chunk-TNBK56IN.mjs} +11 -22
  64. package/dist/lib/neutral/chunk-TNBK56IN.mjs.map +7 -0
  65. package/dist/lib/neutral/{chunk-DQYLD2RB.mjs → chunk-TRPZU2HV.mjs} +2 -2
  66. package/dist/lib/neutral/{chunk-UI6MWK5W.mjs → chunk-TTCSATUD.mjs} +1 -1
  67. package/dist/lib/neutral/{chunk-OMUPQMLR.mjs → chunk-V72DY6LU.mjs} +1 -1
  68. package/dist/lib/neutral/{chunk-UBEZSGXY.mjs → chunk-ZISMEVKD.mjs} +1 -1
  69. package/dist/lib/neutral/{chunk-UBEZSGXY.mjs.map → chunk-ZISMEVKD.mjs.map} +2 -2
  70. package/dist/lib/neutral/index.mjs +33 -27
  71. package/dist/lib/neutral/internal/index.mjs +11 -9
  72. package/dist/lib/neutral/meta.json +1 -1
  73. package/dist/lib/neutral/testing/index.mjs +183 -128
  74. package/dist/lib/neutral/testing/index.mjs.map +3 -3
  75. package/dist/types/src/Annotation.d.ts +1 -1
  76. package/dist/types/src/Annotation.d.ts.map +1 -1
  77. package/dist/types/src/Collection.d.ts.map +1 -1
  78. package/dist/types/src/Database.d.ts +6 -1
  79. package/dist/types/src/Database.d.ts.map +1 -1
  80. package/dist/types/src/Dataset.d.ts +1 -1
  81. package/dist/types/src/Entity.d.ts +15 -9
  82. package/dist/types/src/Entity.d.ts.map +1 -1
  83. package/dist/types/src/Err.d.ts +18 -18
  84. package/dist/types/src/Err.d.ts.map +1 -1
  85. package/dist/types/src/Extension.d.ts +4 -4
  86. package/dist/types/src/Extension.d.ts.map +1 -1
  87. package/dist/types/src/Feed.d.ts +37 -2
  88. package/dist/types/src/Feed.d.ts.map +1 -1
  89. package/dist/types/src/Filter.d.ts +34 -0
  90. package/dist/types/src/Filter.d.ts.map +1 -1
  91. package/dist/types/src/Hypergraph.d.ts +3 -3
  92. package/dist/types/src/Hypergraph.d.ts.map +1 -1
  93. package/dist/types/src/Json.d.ts +33 -0
  94. package/dist/types/src/Json.d.ts.map +1 -0
  95. package/dist/types/src/Json.test.d.ts +2 -0
  96. package/dist/types/src/Json.test.d.ts.map +1 -0
  97. package/dist/types/src/JsonSchema.d.ts +1 -1
  98. package/dist/types/src/Migration.d.ts +69 -0
  99. package/dist/types/src/Migration.d.ts.map +1 -0
  100. package/dist/types/src/Obj.d.ts +23 -21
  101. package/dist/types/src/Obj.d.ts.map +1 -1
  102. package/dist/types/src/Order.d.ts.map +1 -1
  103. package/dist/types/src/Query.d.ts +5 -1
  104. package/dist/types/src/Query.d.ts.map +1 -1
  105. package/dist/types/src/Ref.d.ts +1 -0
  106. package/dist/types/src/Ref.d.ts.map +1 -1
  107. package/dist/types/src/Relation.d.ts +15 -15
  108. package/dist/types/src/Relation.d.ts.map +1 -1
  109. package/dist/types/src/Tag.d.ts +2 -2
  110. package/dist/types/src/Tag.d.ts.map +1 -1
  111. package/dist/types/src/Type.d.ts.map +1 -1
  112. package/dist/types/src/View.d.ts +1 -1
  113. package/dist/types/src/View.d.ts.map +1 -1
  114. package/dist/types/src/exemplars.test.d.ts +2 -0
  115. package/dist/types/src/exemplars.test.d.ts.map +1 -0
  116. package/dist/types/src/index.d.ts +2 -0
  117. package/dist/types/src/index.d.ts.map +1 -1
  118. package/dist/types/src/internal/Annotation/annotations.d.ts +11 -2
  119. package/dist/types/src/internal/Annotation/annotations.d.ts.map +1 -1
  120. package/dist/types/src/internal/Annotation/sorting.d.ts.map +1 -1
  121. package/dist/types/src/internal/Annotation/util.d.ts +1 -1
  122. package/dist/types/src/internal/Annotation/util.d.ts.map +1 -1
  123. package/dist/types/src/internal/Entity/api.d.ts.map +1 -1
  124. package/dist/types/src/internal/Entity/model.d.ts +2 -0
  125. package/dist/types/src/internal/Entity/model.d.ts.map +1 -1
  126. package/dist/types/src/internal/Entity/relation.d.ts.map +1 -1
  127. package/dist/types/src/internal/Entity/version.d.ts.map +1 -1
  128. package/dist/types/src/internal/Format/date.d.ts.map +1 -1
  129. package/dist/types/src/internal/Format/format.d.ts.map +1 -1
  130. package/dist/types/src/internal/Format/number.d.ts.map +1 -1
  131. package/dist/types/src/internal/Format/object.d.ts.map +1 -1
  132. package/dist/types/src/internal/Format/types.d.ts.map +1 -1
  133. package/dist/types/src/internal/JsonSchema/json-schema-normalize.d.ts.map +1 -1
  134. package/dist/types/src/internal/JsonSchema/json-schema-type.d.ts +28 -28
  135. package/dist/types/src/internal/JsonSchema/json-schema-type.d.ts.map +1 -1
  136. package/dist/types/src/internal/JsonSchema/json-schema.d.ts +1 -1
  137. package/dist/types/src/internal/JsonSchema/json-schema.d.ts.map +1 -1
  138. package/dist/types/src/internal/Obj/clone.d.ts.map +1 -1
  139. package/dist/types/src/internal/Obj/common.d.ts.map +1 -1
  140. package/dist/types/src/internal/Obj/create-object.d.ts.map +1 -1
  141. package/dist/types/src/internal/Obj/deleted.d.ts.map +1 -1
  142. package/dist/types/src/internal/Obj/ids.d.ts +1 -1
  143. package/dist/types/src/internal/Obj/ids.d.ts.map +1 -1
  144. package/dist/types/src/internal/Obj/json-serializer.d.ts +2 -1
  145. package/dist/types/src/internal/Obj/json-serializer.d.ts.map +1 -1
  146. package/dist/types/src/internal/Obj/set-value.d.ts +1 -1
  147. package/dist/types/src/internal/Obj/set-value.d.ts.map +1 -1
  148. package/dist/types/src/internal/Obj/snapshot.d.ts.map +1 -1
  149. package/dist/types/src/internal/Query.d.ts.map +1 -1
  150. package/dist/types/src/internal/Ref/ref.d.ts +13 -0
  151. package/dist/types/src/internal/Ref/ref.d.ts.map +1 -1
  152. package/dist/types/src/internal/Type/compose.d.ts.map +1 -1
  153. package/dist/types/src/internal/Type/echo-schema.d.ts +1 -1
  154. package/dist/types/src/internal/Type/echo-schema.d.ts.map +1 -1
  155. package/dist/types/src/internal/Type/manipulation.d.ts.map +1 -1
  156. package/dist/types/src/internal/common/api/meta.d.ts +3 -3
  157. package/dist/types/src/internal/common/api/meta.d.ts.map +1 -1
  158. package/dist/types/src/internal/common/proxy/change-context.d.ts +1 -1
  159. package/dist/types/src/internal/common/proxy/change-context.d.ts.map +1 -1
  160. package/dist/types/src/internal/common/proxy/define-hidden-property.d.ts.map +1 -1
  161. package/dist/types/src/internal/common/proxy/errors.d.ts +1 -1
  162. package/dist/types/src/internal/common/proxy/errors.d.ts.map +1 -1
  163. package/dist/types/src/internal/common/proxy/event-batch.d.ts.map +1 -1
  164. package/dist/types/src/internal/common/proxy/json-serializer.d.ts.map +1 -1
  165. package/dist/types/src/internal/common/proxy/ownership.d.ts.map +1 -1
  166. package/dist/types/src/internal/common/proxy/proxy-utils.d.ts.map +1 -1
  167. package/dist/types/src/internal/common/proxy/reactive-array.d.ts +1 -1
  168. package/dist/types/src/internal/common/proxy/reactive.d.ts +1 -1
  169. package/dist/types/src/internal/common/proxy/reactive.d.ts.map +1 -1
  170. package/dist/types/src/internal/common/proxy/reactive.test.d.ts +2 -0
  171. package/dist/types/src/internal/common/proxy/reactive.test.d.ts.map +1 -0
  172. package/dist/types/src/internal/common/proxy/schema-validator.d.ts.map +1 -1
  173. package/dist/types/src/internal/common/proxy/typed-handler.d.ts.map +1 -1
  174. package/dist/types/src/internal/common/types/base.d.ts.map +1 -1
  175. package/dist/types/src/internal/common/types/entity.d.ts +3 -3
  176. package/dist/types/src/internal/common/types/meta.d.ts +10 -0
  177. package/dist/types/src/internal/common/types/meta.d.ts.map +1 -1
  178. package/dist/types/src/internal/common/types/version.d.ts +1 -1
  179. package/dist/types/src/testing/test-data.d.ts +8 -8
  180. package/dist/types/src/testing/test-data.d.ts.map +1 -1
  181. package/dist/types/src/testing/test-schema.d.ts +53 -53
  182. package/dist/types/src/testing/test-schema.d.ts.map +1 -1
  183. package/dist/types/src/testing/util.d.ts.map +1 -1
  184. package/dist/types/tsconfig.tsbuildinfo +1 -1
  185. package/package.json +19 -14
  186. package/src/Annotation.ts +1 -0
  187. package/src/Collection.ts +1 -1
  188. package/src/Database.ts +36 -14
  189. package/src/Entity.ts +16 -9
  190. package/src/Extension.ts +3 -3
  191. package/src/Feed.ts +54 -2
  192. package/src/Filter.ts +61 -0
  193. package/src/Hypergraph.ts +3 -3
  194. package/src/Json.test.ts +175 -0
  195. package/src/Json.ts +102 -0
  196. package/src/Migration.ts +106 -0
  197. package/src/Obj.test.ts +23 -23
  198. package/src/Obj.ts +28 -25
  199. package/src/Query.test.ts +129 -5
  200. package/src/Query.ts +32 -7
  201. package/src/Ref.ts +2 -0
  202. package/src/Relation.ts +21 -17
  203. package/src/exemplars.test.ts +21 -0
  204. package/src/index.ts +3 -0
  205. package/src/internal/Annotation/annotations.ts +19 -7
  206. package/src/internal/Entity/model.ts +2 -0
  207. package/src/internal/Obj/ids.ts +1 -1
  208. package/src/internal/Obj/json-serializer.test.ts +2 -2
  209. package/src/internal/Obj/json-serializer.ts +17 -10
  210. package/src/internal/Obj/set-value.test.ts +24 -24
  211. package/src/internal/Obj/set-value.ts +1 -1
  212. package/src/internal/Query.ts +28 -4
  213. package/src/internal/Ref/ref.ts +17 -0
  214. package/src/internal/Type/echo-schema.ts +1 -1
  215. package/src/internal/common/README.md +1 -1
  216. package/src/internal/common/api/meta.ts +3 -3
  217. package/src/internal/common/proxy/change-context.ts +1 -1
  218. package/src/internal/common/proxy/change.test.ts +66 -66
  219. package/src/internal/common/proxy/errors.ts +2 -2
  220. package/src/internal/common/proxy/reactive-array.ts +1 -1
  221. package/src/internal/common/proxy/reactive.test.ts +54 -0
  222. package/src/internal/common/proxy/reactive.ts +11 -2
  223. package/src/internal/common/proxy/typed-handler.ts +7 -7
  224. package/src/internal/common/proxy/typed-object.test.ts +1 -1
  225. package/src/internal/common/types/meta.ts +12 -0
  226. package/src/testing/test-data.ts +157 -97
  227. package/dist/lib/neutral/chunk-2U2BZDLU.mjs.map +0 -7
  228. package/dist/lib/neutral/chunk-66GBFH43.mjs.map +0 -7
  229. package/dist/lib/neutral/chunk-6URFBQJH.mjs.map +0 -7
  230. package/dist/lib/neutral/chunk-7QQMUDE6.mjs.map +0 -7
  231. package/dist/lib/neutral/chunk-EBVB5NOH.mjs.map +0 -7
  232. package/dist/lib/neutral/chunk-KPY6FC5I.mjs.map +0 -7
  233. package/dist/lib/neutral/chunk-MYMU4TWK.mjs.map +0 -7
  234. package/dist/lib/neutral/chunk-QWAOTFCY.mjs.map +0 -7
  235. package/dist/lib/neutral/chunk-VCAP4OOT.mjs.map +0 -7
  236. package/dist/lib/neutral/chunk-WMFVS4KR.mjs.map +0 -7
  237. package/dist/lib/neutral/chunk-XZJUNTML.mjs.map +0 -7
  238. package/dist/lib/neutral/chunk-ZIXGDU6F.mjs.map +0 -7
  239. /package/dist/lib/neutral/{chunk-GZQTCRJB.mjs.map → chunk-44HT3MEC.mjs.map} +0 -0
  240. /package/dist/lib/neutral/{chunk-ANHVGJI4.mjs.map → chunk-7RVZT53K.mjs.map} +0 -0
  241. /package/dist/lib/neutral/{chunk-R72KFH2X.mjs.map → chunk-APHSOTIX.mjs.map} +0 -0
  242. /package/dist/lib/neutral/{chunk-BNCCGLJN.mjs.map → chunk-BICZKPQG.mjs.map} +0 -0
  243. /package/dist/lib/neutral/{chunk-TBKX6JQO.mjs.map → chunk-N4B7FHQT.mjs.map} +0 -0
  244. /package/dist/lib/neutral/{chunk-XZM7FERR.mjs.map → chunk-QBIKZ3TY.mjs.map} +0 -0
  245. /package/dist/lib/neutral/{chunk-E5PBQJWV.mjs.map → chunk-QRZ2I3ZM.mjs.map} +0 -0
  246. /package/dist/lib/neutral/{chunk-DQYLD2RB.mjs.map → chunk-TRPZU2HV.mjs.map} +0 -0
  247. /package/dist/lib/neutral/{chunk-UI6MWK5W.mjs.map → chunk-TTCSATUD.mjs.map} +0 -0
  248. /package/dist/lib/neutral/{chunk-OMUPQMLR.mjs.map → chunk-V72DY6LU.mjs.map} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/echo",
3
- "version": "0.8.4-main.74a063c4e0",
3
+ "version": "0.8.4-main.765dc60934",
4
4
  "description": "ECHO API",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -8,7 +8,7 @@
8
8
  "type": "git",
9
9
  "url": "https://github.com/dxos/dxos"
10
10
  },
11
- "license": "MIT",
11
+ "license": "FSL-1.1-Apache-2.0",
12
12
  "author": "info@dxos.org",
13
13
  "sideEffects": false,
14
14
  "type": "module",
@@ -73,6 +73,11 @@
73
73
  "types": "./dist/types/src/Key.d.ts",
74
74
  "default": "./dist/lib/neutral/Key.mjs"
75
75
  },
76
+ "./Migration": {
77
+ "source": "./src/Migration.ts",
78
+ "types": "./dist/types/src/Migration.d.ts",
79
+ "default": "./dist/lib/neutral/Migration.mjs"
80
+ },
76
81
  "./Obj": {
77
82
  "source": "./src/Obj.ts",
78
83
  "types": "./dist/types/src/Obj.d.ts",
@@ -130,18 +135,18 @@
130
135
  ],
131
136
  "dependencies": {
132
137
  "effect": "3.20.0",
133
- "@dxos/async": "0.8.4-main.74a063c4e0",
134
- "@dxos/debug": "0.8.4-main.74a063c4e0",
135
- "@dxos/echo-protocol": "0.8.4-main.74a063c4e0",
136
- "@dxos/effect": "0.8.4-main.74a063c4e0",
137
- "@dxos/errors": "0.8.4-main.74a063c4e0",
138
- "@dxos/invariant": "0.8.4-main.74a063c4e0",
139
- "@dxos/keys": "0.8.4-main.74a063c4e0",
140
- "@dxos/node-std": "0.8.4-main.74a063c4e0",
141
- "@dxos/log": "0.8.4-main.74a063c4e0",
142
- "@dxos/protocols": "0.8.4-main.74a063c4e0",
143
- "@dxos/util": "0.8.4-main.74a063c4e0",
144
- "@dxos/context": "0.8.4-main.74a063c4e0"
138
+ "@dxos/async": "0.8.4-main.765dc60934",
139
+ "@dxos/context": "0.8.4-main.765dc60934",
140
+ "@dxos/debug": "0.8.4-main.765dc60934",
141
+ "@dxos/effect": "0.8.4-main.765dc60934",
142
+ "@dxos/invariant": "0.8.4-main.765dc60934",
143
+ "@dxos/echo-protocol": "0.8.4-main.765dc60934",
144
+ "@dxos/keys": "0.8.4-main.765dc60934",
145
+ "@dxos/errors": "0.8.4-main.765dc60934",
146
+ "@dxos/log": "0.8.4-main.765dc60934",
147
+ "@dxos/protocols": "0.8.4-main.765dc60934",
148
+ "@dxos/node-std": "0.8.4-main.765dc60934",
149
+ "@dxos/util": "0.8.4-main.765dc60934"
145
150
  },
146
151
  "publishConfig": {
147
152
  "access": "public"
package/src/Annotation.ts CHANGED
@@ -18,6 +18,7 @@ export {
18
18
  setDescriptionWithSchema,
19
19
  setLabelWithSchema,
20
20
  IconAnnotation,
21
+ IconFromRefAnnotation,
21
22
  } from './internal/Annotation';
22
23
 
23
24
  import type * as Schema from 'effect/Schema';
package/src/Collection.ts CHANGED
@@ -25,7 +25,7 @@ export const Collection = Schema.Struct({
25
25
  }),
26
26
  Annotation.IconAnnotation.set({
27
27
  icon: 'ph--folder--regular',
28
- hue: 'neutral',
28
+ hue: 'amber',
29
29
  }),
30
30
  );
31
31
 
package/src/Database.ts CHANGED
@@ -38,7 +38,7 @@ export interface QueryFn {
38
38
  }
39
39
 
40
40
  /**
41
- * Common interface for Database and Queue.
41
+ * Common interface for Database, Feed, and Hypergraph.
42
42
  */
43
43
  export interface Queryable {
44
44
  query: QueryFn;
@@ -229,24 +229,28 @@ export const resolve: {
229
229
  }
230
230
  invariant(!schema || isInstanceOf(schema, object), 'Object type mismatch.');
231
231
  return object as any;
232
- })) as any;
232
+ }).pipe(Effect.withSpan('Database.resolve'))) as any;
233
233
 
234
234
  /**
235
235
  * Loads an object reference.
236
236
  */
237
- export const load: <T>(ref: Ref<T>) => Effect.Effect<T, Err.ObjectNotFoundError, never> = Effect.fn(function* (ref) {
238
- const object = yield* promiseWithCauseCapture(() => ref.tryLoad());
239
- if (!object) {
240
- return yield* Effect.fail(new Err.ObjectNotFoundError(ref.dxn));
241
- }
242
- return object;
243
- });
237
+ export const load: <T>(ref: Ref<T>) => Effect.Effect<T, Err.ObjectNotFoundError, never> = Effect.fn('Database.load')(
238
+ function* (ref) {
239
+ const object = yield* promiseWithCauseCapture(() => ref.tryLoad());
240
+ if (!object) {
241
+ return yield* Effect.fail(new Err.ObjectNotFoundError(ref.dxn));
242
+ }
243
+ return object;
244
+ },
245
+ );
244
246
 
245
247
  /**
246
248
  * Loads an object reference option.
247
249
  */
248
250
  // TODO(dmaretskyi): Do we need this -- you can just use `Effect.catchTag` in calling code instead.
249
- export const loadOption: <T>(ref: Ref<T>) => Effect.Effect<Option.Option<T>, never, never> = Effect.fn(function* (ref) {
251
+ export const loadOption: <T>(ref: Ref<T>) => Effect.Effect<Option.Option<T>, never, never> = Effect.fn(
252
+ 'Database.loadOption',
253
+ )(function* (ref) {
250
254
  const object = yield* load(ref).pipe(Effect.catchTag('ObjectNotFoundError', () => Effect.succeed(undefined)));
251
255
 
252
256
  return Option.fromNullable(object);
@@ -257,21 +261,23 @@ export const loadOption: <T>(ref: Ref<T>) => Effect.Effect<Option.Option<T>, nev
257
261
  * @see {@link Database.add}
258
262
  */
259
263
  export const add = <T extends Entity.Unknown>(obj: T): Effect.Effect<T, never, Service> =>
260
- Service.pipe(Effect.map(({ db }) => db.add(obj)));
264
+ Service.pipe(Effect.map(({ db }) => db.add(obj))).pipe(Effect.withSpan('Database.add'));
261
265
 
262
266
  /**
263
267
  * Removes an object from the database.
264
268
  * @see {@link Database.remove}
265
269
  */
266
270
  export const remove = <T extends Entity.Unknown>(obj: T): Effect.Effect<void, never, Service> =>
267
- Service.pipe(Effect.map(({ db }) => db.remove(obj)));
271
+ Service.pipe(Effect.map(({ db }) => db.remove(obj))).pipe(Effect.withSpan('Database.remove'));
268
272
 
269
273
  /**
270
274
  * Flushes pending changes to disk.
271
275
  * @see {@link Database.flush}
272
276
  */
273
277
  export const flush = (opts?: FlushOptions) =>
274
- Service.pipe(Effect.flatMap(({ db }) => promiseWithCauseCapture(() => db.flush(opts))));
278
+ Service.pipe(Effect.flatMap(({ db }) => promiseWithCauseCapture(() => db.flush(opts)))).pipe(
279
+ Effect.withSpan('Database.flush'),
280
+ );
275
281
 
276
282
  /**
277
283
  * Creates a `QueryResult` object that can be subscribed to.
@@ -292,7 +298,10 @@ export const runQuery: {
292
298
  <Q extends Query.Any>(query: Q): Effect.Effect<Query.Type<Q>[], never, Service>;
293
299
  <F extends Filter.Any>(filter: F): Effect.Effect<Filter.Type<F>[], never, Service>;
294
300
  } = (queryOrFilter: Query.Any | Filter.Any) =>
295
- query(queryOrFilter as any).pipe(Effect.flatMap((queryResult) => promiseWithCauseCapture(() => queryResult.run())));
301
+ query(queryOrFilter as any).pipe(
302
+ Effect.flatMap((queryResult) => promiseWithCauseCapture(() => queryResult.run())),
303
+ Effect.withSpan('Database.runQuery'),
304
+ );
296
305
 
297
306
  /**
298
307
  * Executes the query once and returns the first result as or None.
@@ -305,6 +314,19 @@ export const runQueryFirst: {
305
314
  Effect.flatMap((queryResult) =>
306
315
  promiseWithCauseCapture(async () => Option.fromNullable(await queryResult.firstOrUndefined())),
307
316
  ),
317
+ Effect.withSpan('Database.runQueryFirst'),
318
+ );
319
+
320
+ /**
321
+ * Persists schemas in the database so they replicate to other clients.
322
+ * @see {@link SchemaRegistry.SchemaRegistry.register}
323
+ */
324
+ export const registerSchema = (
325
+ input: SchemaRegistry.RegisterSchemaInput[],
326
+ ): Effect.Effect<Type.RuntimeType[], never, Service> =>
327
+ Service.pipe(
328
+ Effect.flatMap(({ db }) => promiseWithCauseCapture(() => db.schemaRegistry.register(input))),
329
+ Effect.withSpan('Database.registerSchema'),
308
330
  );
309
331
 
310
332
  /**
package/src/Entity.ts CHANGED
@@ -9,6 +9,7 @@ import type { DXN, ObjectId } from '@dxos/keys';
9
9
 
10
10
  import * as internal from './internal';
11
11
  import type * as Relation from './Relation';
12
+ import type * as Type from './Type';
12
13
 
13
14
  // Re-export KindId and SnapshotKindId from internal.
14
15
  export const KindId = internal.KindId;
@@ -131,6 +132,12 @@ export const getDXN = (entity: Unknown | Snapshot): DXN => internal.getDXN(entit
131
132
  */
132
133
  export const getTypeDXN = internal.getTypeDXN;
133
134
 
135
+ /**
136
+ * Get the schema of an entity.
137
+ * Returns the branded ECHO schema used to create the entity.
138
+ */
139
+ export const getSchema: (entity: Unknown | Snapshot) => Type.AnyEntity | undefined = internal.getSchema as any;
140
+
134
141
  /**
135
142
  * Get the typename of an entity's type.
136
143
  */
@@ -191,7 +198,7 @@ export const subscribe = (entity: Unknown, callback: () => void): (() => void) =
191
198
  //
192
199
 
193
200
  /**
194
- * Used to provide a mutable view of an entity within `Entity.change`.
201
+ * Used to provide a mutable view of an entity within `Entity.update`.
195
202
  */
196
203
  export type Mutable<T> = internal.Mutable<T>;
197
204
 
@@ -199,7 +206,7 @@ export type Mutable<T> = internal.Mutable<T>;
199
206
  * Perform mutations on an entity (object or relation) within a change context.
200
207
  *
201
208
  * Entities are read-only by default. Mutations are batched and notifications fire
202
- * when the callback completes. Direct mutations outside of `Entity.change` will throw
209
+ * when the callback completes. Direct mutations outside of `Entity.update` will throw
203
210
  * at runtime.
204
211
  *
205
212
  * @param entity - The echo entity (object or relation) to mutate.
@@ -207,30 +214,30 @@ export type Mutable<T> = internal.Mutable<T>;
207
214
  *
208
215
  * @example
209
216
  * ```typescript
210
- * // Mutate within Entity.change
211
- * Entity.change(entity, (obj) => {
217
+ * // Mutate within Entity.update
218
+ * Entity.update(entity, (obj) => {
212
219
  * obj.name = 'Updated';
213
220
  * obj.count = 42;
214
221
  * });
215
222
  *
216
223
  * // Direct mutation throws
217
- * entity.name = 'Bob'; // Error: Cannot modify outside Entity.change()
224
+ * entity.name = 'Bob'; // Error: Cannot modify outside Entity.update()
218
225
  * ```
219
226
  *
220
- * Note: For type-specific operations, prefer `Obj.change` or `Relation.change`.
227
+ * Note: For type-specific operations, prefer `Obj.update` or `Relation.update`.
221
228
  */
222
- export const change = <T extends Unknown>(entity: T, callback: internal.ChangeCallback<T>): void => {
229
+ export const update = <T extends Unknown>(entity: T, callback: internal.ChangeCallback<T>): void => {
223
230
  internal.change(entity, callback);
224
231
  };
225
232
 
226
233
  /**
227
234
  * Add a tag to an entity.
228
- * Must be called within an `Entity.change`, `Obj.change`, or `Relation.change` callback.
235
+ * Must be called within an `Entity.update`, `Obj.update`, or `Relation.update` callback.
229
236
  */
230
237
  export const addTag = (entity: Mutable<Unknown>, tag: string): void => internal.addTag(entity, tag);
231
238
 
232
239
  /**
233
240
  * Remove a tag from an entity.
234
- * Must be called within an `Entity.change`, `Obj.change`, or `Relation.change` callback.
241
+ * Must be called within an `Entity.update`, `Obj.update`, or `Relation.update` callback.
235
242
  */
236
243
  export const removeTag = (entity: Mutable<Unknown>, tag: string): void => internal.removeTag(entity, tag);
package/src/Extension.ts CHANGED
@@ -43,7 +43,7 @@ export interface Extension<T> extends Record<
43
43
  * email: 'john@example.com',
44
44
  * });
45
45
  *
46
- * Obj.change(obj, (obj) => {
46
+ * Obj.update(obj, (obj) => {
47
47
  * Extension.set(obj.extensions, ColorExtension, 'red');
48
48
  * });
49
49
  *
@@ -103,10 +103,10 @@ export const get: {
103
103
  /**
104
104
  * Set the value of an extension in a set of values.
105
105
  *
106
- * Can also be used within Obj.change callback:
106
+ * Can also be used within Obj.update callback:
107
107
  *
108
108
  * ```ts
109
- * Obj.change(obj, (obj) => {
109
+ * Obj.update(obj, (obj) => {
110
110
  * Extension.set(obj.extensions, ColorExtension, 'red');
111
111
  * });
112
112
  * ```
package/src/Feed.ts CHANGED
@@ -80,6 +80,16 @@ export interface RetentionOptions {
80
80
  cursor?: string;
81
81
  }
82
82
 
83
+ /**
84
+ * Sync options for a feed.
85
+ */
86
+ export interface SyncOptions {
87
+ /** Push local changes to the server. Defaults to true. */
88
+ shouldPush?: boolean;
89
+ /** Pull remote changes from the server. Defaults to true. */
90
+ shouldPull?: boolean;
91
+ }
92
+
83
93
  //
84
94
  // Factory
85
95
  //
@@ -139,6 +149,11 @@ export class FeedService extends Context.Tag('@dxos/echo/Feed/FeedService')<
139
149
  <Q extends Query.Any>(feed: Feed, query: Q): QueryResult.QueryResult<Query.Type<Q>>;
140
150
  <F extends Filter.Any>(feed: Feed, filter: F): QueryResult.QueryResult<Filter.Type<F>>;
141
151
  };
152
+
153
+ /**
154
+ * Syncs the feed with the server.
155
+ */
156
+ sync(feed: Feed, options?: SyncOptions): Promise<void>;
142
157
  }
143
158
  >() {}
144
159
 
@@ -166,8 +181,30 @@ export const notAvailable: Layer.Layer<FeedService> = Layer.succeed(FeedService,
166
181
  query: () => {
167
182
  throw new Error('Feed.FeedService not available');
168
183
  },
184
+ sync: () => {
185
+ throw new Error('Feed.FeedService not available');
186
+ },
169
187
  } as Context.Tag.Service<FeedService>);
170
188
 
189
+ //
190
+ // Context (per-call) service
191
+ //
192
+
193
+ /**
194
+ * Effect service exposing a single `Feed.Feed` as the "current" feed for a call site.
195
+ *
196
+ * @deprecated Prefer threading a `Feed.Feed` explicitly through function signatures
197
+ * over hiding it behind a context service.
198
+ */
199
+ export class ContextFeedService extends Context.Tag('@dxos/echo/Feed/ContextFeedService')<
200
+ ContextFeedService,
201
+ {
202
+ readonly feed: Feed;
203
+ }
204
+ >() {
205
+ static layer = (feed: Feed): Layer.Layer<ContextFeedService> => Layer.succeed(ContextFeedService, { feed });
206
+ }
207
+
171
208
  //
172
209
  // Operations
173
210
  //
@@ -241,6 +278,21 @@ export const runQuery: {
241
278
  } = (feed: Feed, queryOrFilter: Query.Any | Filter.Any) =>
242
279
  query(feed, queryOrFilter as any).pipe(Effect.flatMap((queryResult) => Effect.promise(() => queryResult.run())));
243
280
 
281
+ /**
282
+ * Syncs the feed with the server.
283
+ *
284
+ * @example
285
+ * ```ts
286
+ * yield* Feed.sync(feed);
287
+ * yield* Feed.sync(feed, { shouldPush: false });
288
+ * ```
289
+ */
290
+ export const sync = (feed: Feed, options?: SyncOptions): Effect.Effect<void, never, FeedService> =>
291
+ Effect.gen(function* () {
292
+ const service = yield* FeedService;
293
+ yield* Effect.promise(() => service.sync(feed, options));
294
+ });
295
+
244
296
  /**
245
297
  * Creates a cursor for iterating over feed items.
246
298
  * Currently stubbed — cursor operations are not yet implemented.
@@ -271,13 +323,13 @@ export const nextOption = <T = Obj.Snapshot>(_cursor: Cursor<T>): Effect.Effect<
271
323
 
272
324
  /**
273
325
  * Sets the local retention policy for a feed.
274
- * Currently stubbed — queues do not yet support retention.
326
+ * Currently stubbed — feeds do not yet support retention.
275
327
  *
276
328
  * @example
277
329
  * ```ts
278
330
  * yield* Feed.setRetention(feed, { count: 1000 });
279
331
  * ```
280
332
  */
281
- // TODO(feed): Implement when queue retention is supported.
333
+ // TODO(dmaretskyi): Implement when feed retention is supported.
282
334
  export const setRetention = (_feed: Feed, _options: RetentionOptions): Effect.Effect<void, never, FeedService> =>
283
335
  Effect.void;
package/src/Filter.ts CHANGED
@@ -14,6 +14,7 @@ import { assertArgument } from '@dxos/invariant';
14
14
  import { DXN, ObjectId } from '@dxos/keys';
15
15
 
16
16
  import * as internal from './internal';
17
+ import type * as Obj from './Obj';
17
18
  import * as Ref from './Ref';
18
19
 
19
20
  export interface Filter<T> {
@@ -157,6 +158,37 @@ export const tag = (tag: string): Any => {
157
158
  });
158
159
  };
159
160
 
161
+ /**
162
+ * Options for {@link key} filter.
163
+ */
164
+ export type KeyFilterOptions = {
165
+ /**
166
+ * Optional semver range expression (e.g. `^1.2.3`, `~2.0.0`, `>=1.0.0 <2.0.0`).
167
+ * Matches the object's meta `version` field against the range.
168
+ * If omitted, matches any version (including objects with no version).
169
+ */
170
+ version?: string;
171
+ };
172
+
173
+ /**
174
+ * Filter by registry key stored in object meta.
175
+ *
176
+ * @example
177
+ * ```ts
178
+ * Filter.key('org.example.type.foo');
179
+ * Filter.key('org.example.type.foo', { version: '^1.2.3' });
180
+ * ```
181
+ */
182
+ export const key = (key: string, options?: KeyFilterOptions): Any => {
183
+ return new FilterClass({
184
+ type: 'object',
185
+ typename: null,
186
+ props: {},
187
+ metaKey: key,
188
+ metaVersion: options?.version,
189
+ });
190
+ };
191
+
160
192
  /**
161
193
  * Filter by properties.
162
194
  */
@@ -338,6 +370,35 @@ export const updated = (range: TimeRange): Any => _timeRangeFilter('updatedAt',
338
370
  */
339
371
  export const created = (range: TimeRange): Any => _timeRangeFilter('createdAt', range);
340
372
 
373
+ export type ChildOfOptions = {
374
+ /** Whether to match transitively (grandchildren, etc.). Defaults to true. */
375
+ transitive?: boolean;
376
+ };
377
+
378
+ /**
379
+ * Filter objects that are children of the specified parent(s).
380
+ * Accepts ECHO objects, Refs, or arrays of either.
381
+ * Refs are resolved to DXNs without loading; objects use {@link Obj.getDXN}.
382
+ * With transitive=true (default), also matches grandchildren and beyond.
383
+ */
384
+ export const childOf = (
385
+ parents: Obj.Unknown | Ref.Unknown | readonly (Obj.Unknown | Ref.Unknown)[],
386
+ options?: ChildOfOptions,
387
+ ): Any => {
388
+ const items = Array.isArray(parents) ? parents : [parents];
389
+ const dxns = items.map((item) => {
390
+ if (Ref.isRef(item)) {
391
+ return item.dxn.toString();
392
+ }
393
+ return internal.getDXN(item).toString();
394
+ });
395
+ return new FilterClass({
396
+ type: 'child-of',
397
+ parents: dxns,
398
+ transitive: options?.transitive ?? true,
399
+ });
400
+ };
401
+
341
402
  /**
342
403
  * Negate the filter.
343
404
  */
package/src/Hypergraph.ts CHANGED
@@ -22,10 +22,10 @@ export interface RefResolutionContext {
22
22
  space?: Key.SpaceId;
23
23
 
24
24
  /**
25
- * Queue that the resolution is happening from.
26
- * This queue will be searched first, and then the space it belongs to.
25
+ * Feed that the resolution is happening from (as the underlying queue DXN).
26
+ * This feed will be searched first, and then the space it belongs to.
27
27
  */
28
- queue?: DXN;
28
+ feed?: DXN;
29
29
  }
30
30
 
31
31
  export interface RefResolverOptions {
@@ -0,0 +1,175 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { describe, expect, test } from 'vitest';
6
+
7
+ import { DXN, ObjectId } from '@dxos/keys';
8
+ import { safeStringify } from '@dxos/util';
9
+
10
+ import * as Database from './Database';
11
+ import * as Json from './Json';
12
+
13
+ /** Mint a random ECHO object id usable as both a stub-db key and a DXN payload. */
14
+ const newId = (): string => ObjectId.random();
15
+
16
+ /** Build a fake encoded ref for a local-space object id. */
17
+ const encodeRef = (id: string): { '/': string } => ({ '/': DXN.fromLocalObjectId(id).toString() });
18
+
19
+ /** Minimal stub: `createRefReplacer` only touches `db.getObjectById`. */
20
+ const makeStubDb = (objects: Record<string, unknown>): Database.Database => {
21
+ return {
22
+ getObjectById: (id: string) => objects[id],
23
+ } as unknown as Database.Database;
24
+ };
25
+
26
+ /**
27
+ * Run a value through the replacer's public contract — `JSON.stringify(value, replacer)` —
28
+ * then re-parse the result so tests can assert on plain JS shapes. This mirrors the way
29
+ * the JSON highlighter invokes the replacer in production.
30
+ */
31
+ const stringifyWith = (replacer: Json.JsonReplacer, value: unknown): unknown =>
32
+ JSON.parse(JSON.stringify(value, replacer));
33
+
34
+ describe('createRefReplacer', () => {
35
+ test('passes plain values through unchanged', () => {
36
+ const replacer = Json.createRefReplacer({ db: makeStubDb({}) });
37
+ const subject = { a: 1, b: 'two', c: [3, { d: 4 }] };
38
+ expect(stringifyWith(replacer, subject)).toEqual(subject);
39
+ });
40
+
41
+ test('inlines refs at default depth (1)', () => {
42
+ const id = newId();
43
+ const target = { name: 'inlined' };
44
+ const replacer = Json.createRefReplacer({ db: makeStubDb({ [id]: target }) });
45
+ const subject = { ref: encodeRef(id) };
46
+ expect(stringifyWith(replacer, subject)).toEqual({ ref: target });
47
+ });
48
+
49
+ test('does not follow refs when depth is 0', () => {
50
+ const id = newId();
51
+ const target = { name: 'inlined' };
52
+ const ref = encodeRef(id);
53
+ const replacer = Json.createRefReplacer({ db: makeStubDb({ [id]: target }), depth: 0 });
54
+ expect(stringifyWith(replacer, { ref })).toEqual({ ref });
55
+ });
56
+
57
+ test('inlines refs across multiple levels up to depth', () => {
58
+ const innerId = newId();
59
+ const middleId = newId();
60
+ const inner = { name: 'inner' };
61
+ const middle = { ref: encodeRef(innerId) };
62
+ const outer = { ref: encodeRef(middleId) };
63
+ const db = makeStubDb({ [innerId]: inner, [middleId]: middle });
64
+
65
+ expect(stringifyWith(Json.createRefReplacer({ db, depth: 1 }), outer)).toEqual({
66
+ ref: { ref: encodeRef(innerId) },
67
+ });
68
+
69
+ expect(stringifyWith(Json.createRefReplacer({ db, depth: 2 }), outer)).toEqual({
70
+ ref: { ref: inner },
71
+ });
72
+ });
73
+
74
+ test('leaves refs encoded when the target is missing in the db', () => {
75
+ const ref = encodeRef(newId());
76
+ const replacer = Json.createRefReplacer({ db: makeStubDb({}) });
77
+ expect(stringifyWith(replacer, { ref })).toEqual({ ref });
78
+ });
79
+
80
+ test('leaves non-DXN single-key { "/": string } objects untouched', () => {
81
+ // Same `{ '/': string }` shape is used by other IPLD-style refs (e.g. CIDs); those should
82
+ // not crash the replacer and should pass through verbatim.
83
+ const cidLike = { '/': 'bafybeibwzifw7izxykxz' };
84
+ const replacer = Json.createRefReplacer({ db: makeStubDb({}) });
85
+ expect(stringifyWith(replacer, { ref: cidLike })).toEqual({ ref: cidLike });
86
+ });
87
+
88
+ test('leaves malformed dxn strings untouched', () => {
89
+ const malformed = { '/': 'dxn:not-a-real-dxn' };
90
+ const replacer = Json.createRefReplacer({ db: makeStubDb({}) });
91
+ expect(stringifyWith(replacer, { ref: malformed })).toEqual({ ref: malformed });
92
+ });
93
+
94
+ test('leaves non-echo dxns untouched (e.g. type DXN)', () => {
95
+ // Type DXNs share the `dxn:` prefix but `asEchoDXN()` returns undefined.
96
+ const typeRef = { '/': DXN.fromTypename('com.example.Thing').toString() };
97
+ const replacer = Json.createRefReplacer({ db: makeStubDb({}) });
98
+ expect(stringifyWith(replacer, { ref: typeRef })).toEqual({ ref: typeRef });
99
+ });
100
+
101
+ test('inlines refs inside arrays', () => {
102
+ const idA = newId();
103
+ const idB = newId();
104
+ const a = { name: 'a' };
105
+ const b = { name: 'b' };
106
+ const replacer = Json.createRefReplacer({ db: makeStubDb({ [idA]: a, [idB]: b }) });
107
+ expect(stringifyWith(replacer, { items: [encodeRef(idA), encodeRef(idB), { plain: true }] })).toEqual({
108
+ items: [a, b, { plain: true }],
109
+ });
110
+ });
111
+
112
+ test('walks nested objects recursively', () => {
113
+ const innerId = newId();
114
+ const inner = { name: 'inner' };
115
+ const replacer = Json.createRefReplacer({ db: makeStubDb({ [innerId]: inner }) });
116
+ const subject = { outer: { mid: { ref: encodeRef(innerId) } } };
117
+ expect(stringifyWith(replacer, subject)).toEqual({ outer: { mid: { ref: inner } } });
118
+ });
119
+
120
+ test('a single replacer invocation does not recurse on its own', () => {
121
+ // The replacer is per-call; JSON.stringify drives the tree walk. Calling the replacer
122
+ // directly on a cyclic input must therefore return without touching the cycle.
123
+ const replacer = Json.createRefReplacer({ db: makeStubDb({}) });
124
+ const node: any = { name: 'self' };
125
+ node.self = node;
126
+
127
+ expect(() => replacer('', node)).not.toThrow();
128
+ expect(replacer('', node)).toBe(node);
129
+ });
130
+
131
+ test('invokes `toJSON` on resolved targets so refs in the target are re-walked', () => {
132
+ // Simulates the ECHO-object branch: `db.getObjectById` returns a live proxy, the replacer
133
+ // calls `.toJSON()` to get the encoded form, then continues walking that form. A ref nested
134
+ // inside the target should be inlined when there's depth budget remaining.
135
+ const outerId = newId();
136
+ const innerId = newId();
137
+ const inner = { name: 'inner' };
138
+ const target = {
139
+ toJSON: () => ({ nestedRef: encodeRef(innerId) }),
140
+ };
141
+ const replacer = Json.createRefReplacer({ db: makeStubDb({ [outerId]: target, [innerId]: inner }), depth: 2 });
142
+ expect(stringifyWith(replacer, { ref: encodeRef(outerId) })).toEqual({
143
+ ref: { nestedRef: inner },
144
+ });
145
+ });
146
+
147
+ test('depth budget counts ref hops, not tree depth — a ref deep in a plain tree still resolves', () => {
148
+ // A ref nested under arbitrarily many plain objects is one ref hop from the root, so
149
+ // `depth: 1` resolves it. `depth: 0` leaves it encoded.
150
+ const innerId = newId();
151
+ const inner = { name: 'inner' };
152
+ const subject = { a: { b: { c: { d: { ref: encodeRef(innerId) } } } } };
153
+
154
+ const inlining = Json.createRefReplacer({ db: makeStubDb({ [innerId]: inner }), depth: 1 });
155
+ expect(stringifyWith(inlining, subject)).toEqual({ a: { b: { c: { d: { ref: inner } } } } });
156
+
157
+ const passthrough = Json.createRefReplacer({ db: makeStubDb({ [innerId]: inner }), depth: 0 });
158
+ expect(stringifyWith(passthrough, subject)).toEqual(subject);
159
+ });
160
+
161
+ test('inlines refs when invoked through safeStringify (production path)', () => {
162
+ // `JsonHighlighter` runs the replacer through `@dxos/util/safeStringify`, whose inner
163
+ // wrapper short-circuits the root call without forwarding it to the user's filter. The
164
+ // replacer must therefore work on a per-call basis — not as a one-shot root tree walk.
165
+ // This regression-tests that integration: the `content` ref must inline.
166
+ const targetId = newId();
167
+ const target = { toJSON: () => ({ name: 'README content' }) };
168
+ const document = { id: '01ABC', name: 'README', content: encodeRef(targetId) };
169
+
170
+ const replacer = Json.createRefReplacer({ db: makeStubDb({ [targetId]: target }) });
171
+ const out = JSON.parse(safeStringify(document, replacer, 0)!);
172
+
173
+ expect(out).toEqual({ id: '01ABC', name: 'README', content: { name: 'README content' } });
174
+ });
175
+ });