@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/src/Query.test.ts CHANGED
@@ -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": {
@@ -563,17 +563,17 @@ describe('query api', () => {
563
563
  test('Query.type(...).from(feed) sets queue scope', async () => {
564
564
  const spaceId = SpaceId.random();
565
565
  const feedId = ObjectId.random();
566
- const feedDxn = DXN.parse(`dxn:echo:${spaceId}:${feedId}`);
566
+ const feedDXN = DXN.parse(`dxn:echo:${spaceId}:${feedId}`);
567
567
  const feed = (await Obj.fromJSON(
568
568
  {
569
569
  '@type': 'dxn:type:org.dxos.type.feed:0.1.0',
570
570
  id: feedId,
571
571
  name: 'test-feed',
572
572
  },
573
- { dxn: feedDxn },
573
+ { dxn: feedDXN },
574
574
  )) as Feed.Feed;
575
575
 
576
- const expectedQueueDxn = new DXN(DXN.kind.QUEUE, ['data', spaceId, feedId]);
576
+ const expectedQueueDXN = new DXN(DXN.kind.QUEUE, ['data', spaceId, feedId]);
577
577
 
578
578
  const query = Query.type(TestSchema.Person).from(feed);
579
579
  Schema.validateSync(QueryAST.Query)(query.ast);
@@ -582,7 +582,7 @@ describe('query api', () => {
582
582
  from: {
583
583
  _tag: 'scope',
584
584
  scope: {
585
- queues: [expectedQueueDxn.toString()],
585
+ feeds: [expectedQueueDXN.toString()],
586
586
  },
587
587
  },
588
588
  query: {
@@ -617,6 +617,22 @@ describe('query api', () => {
617
617
  );
618
618
  });
619
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
+
620
636
  test('Query.pretty returns human-readable query string', () => {
621
637
  const query = Query.select(Filter.type(TestSchema.Person, { name: 'Fred' }));
622
638
  const pretty = Query.pretty(query);
@@ -668,6 +684,114 @@ describe('query api', () => {
668
684
  });
669
685
  });
670
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
+
671
795
  describe('Filter', () => {
672
796
  test('Filter.or(Filter.typename(...))', () => {
673
797
  const filter = Filter.or(Filter.typename('com.example.type.person'));
package/src/Query.ts CHANGED
@@ -207,6 +207,11 @@ export interface Query<T> {
207
207
  * Add options to a query.
208
208
  */
209
209
  'options'(options: QueryAST.QueryOptions): Query<T>;
210
+
211
+ /**
212
+ * Attach a diagnostic label for logs and tooling (execution semantics unchanged).
213
+ */
214
+ 'debugLabel'(label: string): Query<T>;
210
215
  }
211
216
 
212
217
  export type Any = Query<any>;
@@ -354,7 +359,7 @@ class QueryClass implements Any {
354
359
  from: {
355
360
  _tag: 'scope',
356
361
  scope: {
357
- ...(options?.includeFeeds ? { allQueuesFromSpaces: true } : {}),
362
+ ...(options?.includeFeeds ? { allFeedsFromSpaces: true } : {}),
358
363
  },
359
364
  },
360
365
  });
@@ -379,7 +384,7 @@ class QueryClass implements Any {
379
384
  _tag: 'scope',
380
385
  scope: {
381
386
  spaceIds: databases.map((db) => db.spaceId),
382
- ...(options?.includeFeeds ? { allQueuesFromSpaces: true } : {}),
387
+ ...(options?.includeFeeds ? { allFeedsFromSpaces: true } : {}),
383
388
  },
384
389
  },
385
390
  });
@@ -405,10 +410,15 @@ class QueryClass implements Any {
405
410
  }
406
411
  }
407
412
 
408
- const feeds = items as Feed.Feed[];
409
- const queueDxns = feeds.flatMap((feed) => {
413
+ const feedItems = items as Feed.Feed[];
414
+ const feedDXNs = feedItems.map((feed) => {
410
415
  const dxn = Feed.getQueueDxn(feed);
411
- return dxn ? [dxn.toString()] : [];
416
+ if (!dxn) {
417
+ throw new TypeError(
418
+ `Query.from() expects persisted Feed objects with a queue DXN; got feed without a space (id=${Obj.getDXN(feed).toString()}).`,
419
+ );
420
+ }
421
+ return dxn.toString();
412
422
  });
413
423
  return new QueryClass({
414
424
  type: 'from',
@@ -416,7 +426,7 @@ class QueryClass implements Any {
416
426
  from: {
417
427
  _tag: 'scope',
418
428
  scope: {
419
- queues: queueDxns,
429
+ feeds: feedDXNs,
420
430
  },
421
431
  },
422
432
  });
@@ -429,6 +439,21 @@ class QueryClass implements Any {
429
439
  options,
430
440
  });
431
441
  }
442
+
443
+ debugLabel(label: string): Any {
444
+ if (this.ast.type === 'options') {
445
+ return new QueryClass({
446
+ type: 'options',
447
+ query: this.ast.query,
448
+ options: { ...this.ast.options, debugLabel: label },
449
+ });
450
+ }
451
+ return new QueryClass({
452
+ type: 'options',
453
+ query: this.ast,
454
+ options: { debugLabel: label },
455
+ });
456
+ }
432
457
  }
433
458
 
434
459
  export const is = (value: unknown): value is Any => {
@@ -531,7 +556,7 @@ export const from = (
531
556
  return wrapper.from(source as any, options);
532
557
  };
533
558
 
534
- const SCOPE_KEYS = new Set(['spaceIds', 'queues', 'allQueuesFromSpaces']);
559
+ const SCOPE_KEYS = new Set(['spaceIds', 'feeds', 'allFeedsFromSpaces']);
535
560
 
536
561
  /** Detect a raw Scope object (plain object with only Scope-valid keys). */
537
562
  const _isScope = (value: unknown): value is QueryAST.Scope => {
package/src/Ref.ts CHANGED
@@ -85,6 +85,8 @@ export const make = refInternal.Ref.make;
85
85
  // TODO(dmaretskyi): Consider just allowing `make` to accept DXN.
86
86
  export const fromDXN = refInternal.Ref.fromDXN;
87
87
 
88
+ export const hasObjectId = refInternal.Ref.hasObjectId;
89
+
88
90
  // TODO(wittjosiah): Factor out?
89
91
  export const isRefType = (ast: SchemaAST.AST): boolean => {
90
92
  return SchemaAST.getAnnotation<JsonSchema.JsonSchema>(ast, SchemaAST.JSONSchemaAnnotationId).pipe(
package/src/Relation.ts CHANGED
@@ -237,7 +237,9 @@ export const getSource = <T extends Unknown | Snapshot>(relation: T): SourceOf<T
237
237
  assertArgument(isRelation(relation), 'Expected a relation');
238
238
  assumeType<internal.InternalObjectProps>(relation);
239
239
  const obj = (relation as internal.InternalObjectProps)[internal.RelationSourceId];
240
- invariant(obj !== undefined, `Invalid source: ${relation.id}`);
240
+ if (obj === undefined) {
241
+ throw new Error(`Relation source could not be resolved.`);
242
+ }
241
243
  return obj as SourceOf<T>;
242
244
  };
243
245
 
@@ -250,7 +252,9 @@ export const getTarget = <T extends Unknown | Snapshot>(relation: T): TargetOf<T
250
252
  assertArgument(isRelation(relation), 'Expected a relation');
251
253
  assumeType<internal.InternalObjectProps>(relation);
252
254
  const obj = (relation as internal.InternalObjectProps)[internal.RelationTargetId];
253
- invariant(obj !== undefined, `Invalid target: ${relation.id}`);
255
+ if (obj === undefined) {
256
+ throw new Error(`Relation target could not be resolved.`);
257
+ }
254
258
  return obj as TargetOf<T>;
255
259
  };
256
260
 
@@ -260,7 +264,7 @@ export const getTarget = <T extends Unknown | Snapshot>(relation: T): TargetOf<T
260
264
 
261
265
  /**
262
266
  * Makes all properties mutable recursively.
263
- * Used to provide a mutable view of a relation within `Relation.change`.
267
+ * Used to provide a mutable view of a relation within `Relation.update`.
264
268
  */
265
269
  export type Mutable<T> = internal.Mutable<T>;
266
270
 
@@ -268,10 +272,10 @@ export type Mutable<T> = internal.Mutable<T>;
268
272
  * Perform mutations on an echo relation within a controlled context.
269
273
  *
270
274
  * All mutations within the callback are batched and trigger a single notification
271
- * when the callback completes. Direct mutations outside of `Relation.change` will throw
275
+ * when the callback completes. Direct mutations outside of `Relation.update` will throw
272
276
  * an error for echo relations.
273
277
  *
274
- * @param relation - The echo relation to mutate. Use `Obj.change` for objects.
278
+ * @param relation - The echo relation to mutate. Use `Obj.update` for objects.
275
279
  * @param callback - The callback that performs mutations on the relation.
276
280
  *
277
281
  * @example
@@ -282,15 +286,15 @@ export type Mutable<T> = internal.Mutable<T>;
282
286
  * role: 'Engineer',
283
287
  * });
284
288
  *
285
- * // Mutate within Relation.change
286
- * Relation.change(worksFor, (obj) => {
289
+ * // Mutate within Relation.update
290
+ * Relation.update(worksFor, (obj) => {
287
291
  * obj.role = 'Senior Engineer';
288
292
  * });
289
293
  * ```
290
294
  *
291
- * Note: Only accepts relations. Use `Obj.change` for objects.
295
+ * Note: Only accepts relations. Use `Obj.update` for objects.
292
296
  */
293
- export const change = <T extends Unknown>(relation: T, callback: internal.ChangeCallback<T>): void => {
297
+ export const update = <T extends Unknown>(relation: T, callback: internal.ChangeCallback<T>): void => {
294
298
  internal.change(relation, callback);
295
299
  };
296
300
 
@@ -333,7 +337,7 @@ export const getValue = (rel: Unknown | Snapshot, path: readonly (string | numbe
333
337
 
334
338
  /**
335
339
  * Set a deeply nested property on a relation.
336
- * Must be called within a `Relation.change` callback.
340
+ * Must be called within a `Relation.update` callback.
337
341
  *
338
342
  * NOTE: TypeScript's structural typing allows readonly objects to be passed to `Mutable<T>`
339
343
  * parameters, so there is no compile-time error. Enforcement is runtime-only.
@@ -395,13 +399,13 @@ export const Meta = internal.MetaId;
395
399
  export type ReadonlyMeta = internal.ReadonlyMeta;
396
400
 
397
401
  /**
398
- * Mutable meta type returned by `Relation.getMeta` inside a `Relation.change` callback.
402
+ * Mutable meta type returned by `Relation.getMeta` inside a `Relation.update` callback.
399
403
  */
400
404
  export type Meta = internal.Meta;
401
405
 
402
406
  /**
403
407
  * Get the metadata for a relation.
404
- * Returns mutable meta when passed a mutable relation (inside `Relation.change` callback).
408
+ * Returns mutable meta when passed a mutable relation (inside `Relation.update` callback).
405
409
  * Returns read-only meta when passed a regular relation or snapshot.
406
410
  */
407
411
  // TODO(wittjosiah): When passed a Snapshot, should return a snapshot of meta, not the live meta proxy.
@@ -419,7 +423,7 @@ export const getKeys = (entity: Unknown | Snapshot, source: string): ForeignKey[
419
423
 
420
424
  /**
421
425
  * Delete all keys from the relation for the specified source.
422
- * Must be called within a `Relation.change` callback.
426
+ * Must be called within a `Relation.update` callback.
423
427
  *
424
428
  * NOTE: TypeScript's structural typing allows readonly objects to be passed to `Mutable<T>`
425
429
  * parameters, so there is no compile-time error. Enforcement is runtime-only.
@@ -428,7 +432,7 @@ export const deleteKeys = (entity: Mutable<Unknown>, source: string): void => in
428
432
 
429
433
  /**
430
434
  * Add a tag to the relation.
431
- * Must be called within a `Relation.change` callback.
435
+ * Must be called within a `Relation.update` callback.
432
436
  *
433
437
  * NOTE: TypeScript's structural typing allows readonly objects to be passed to `Mutable<T>`
434
438
  * parameters, so there is no compile-time error. Enforcement is runtime-only.
@@ -437,7 +441,7 @@ export const addTag = (entity: Mutable<Unknown>, tag: string): void => internal.
437
441
 
438
442
  /**
439
443
  * Remove a tag from the relation.
440
- * Must be called within a `Relation.change` callback.
444
+ * Must be called within a `Relation.update` callback.
441
445
  *
442
446
  * NOTE: TypeScript's structural typing allows readonly objects to be passed to `Mutable<T>`
443
447
  * parameters, so there is no compile-time error. Enforcement is runtime-only.
@@ -462,7 +466,7 @@ export const getLabel = (entity: Unknown | Snapshot): string | undefined => inte
462
466
 
463
467
  /**
464
468
  * Set the label of the relation.
465
- * Must be called within a `Relation.change` callback.
469
+ * Must be called within a `Relation.update` callback.
466
470
  *
467
471
  * NOTE: TypeScript's structural typing allows readonly objects to be passed to `Mutable<T>`
468
472
  * parameters, so there is no compile-time error. Enforcement is runtime-only.
@@ -477,7 +481,7 @@ export const getDescription = (entity: Unknown | Snapshot): string | undefined =
477
481
 
478
482
  /**
479
483
  * Set the description of the relation.
480
- * Must be called within a `Relation.change` callback.
484
+ * Must be called within a `Relation.update` callback.
481
485
  *
482
486
  * NOTE: TypeScript's structural typing allows readonly objects to be passed to `Mutable<T>`
483
487
  * parameters, so there is no compile-time error. Enforcement is runtime-only.
@@ -0,0 +1,21 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { describe, test } from 'vitest';
6
+
7
+ import * as Obj from './Obj';
8
+ import { TestSchema } from './testing';
9
+ import type * as Type from './Type';
10
+
11
+ describe('Exemplars', () => {
12
+ test('factory', ({ expect }) => {
13
+ const factory = <S extends Type.AnyObj>(schema: S) => {
14
+ return (props: Obj.MakeProps<S>) => Obj.make(schema, props);
15
+ };
16
+
17
+ const makePerson = factory(TestSchema.Person);
18
+ const person = makePerson({ name: 'John Doe' });
19
+ expect(person.name).toBe('John Doe');
20
+ });
21
+ });
package/src/index.ts CHANGED
@@ -9,13 +9,16 @@ export { DXN } from '@dxos/keys';
9
9
  export * as Annotation from './Annotation';
10
10
  export * as Database from './Database';
11
11
  export * as Entity from './Entity';
12
+ // TODO(burdon): Rename to Error (less problematic than Obj/Object).
12
13
  export * as Err from './Err';
13
14
  export * as Feed from './Feed';
14
15
  export * as Filter from './Filter';
15
16
  export * as Format from './Format';
16
17
  export * as Hypergraph from './Hypergraph';
18
+ export * as Json from './Json';
17
19
  export * as JsonSchema from './JsonSchema';
18
20
  export * as Key from './Key';
21
+ export * as Migration from './Migration';
19
22
  export * as Obj from './Obj';
20
23
  export * as Order from './Order';
21
24
  export * as Query from './Query';
@@ -90,10 +90,10 @@ export const getTypeDXNFromSpecifier = (input: Schema.Schema.All | string): DXN
90
90
  * Example: `org.dxos.type.message`
91
91
  */
92
92
  // TODO(wittjosiah): Factor out to DXN spec.
93
- // TODO(wittjosiah): Switch to atproto NSID regex once legacy typenames are fully migrated:
94
- // /^[a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(\.[a-zA-Z]([a-zA-Z0-9]{0,62})?)$/
95
93
  export const TypenameSchema = Schema.String.pipe(
96
- Schema.pattern(/^[a-zA-Z][a-zA-Z0-9]*(\.[a-zA-Z][a-zA-Z0-9-]*){2,}$/),
94
+ Schema.pattern(
95
+ /^[a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(\.[a-zA-Z]([a-zA-Z0-9]{0,62})?)$/,
96
+ ),
97
97
  ).annotations({
98
98
  description: 'Fully qualified globally unique typename in reverse-DNS form.',
99
99
  example: 'org.dxos.type.message',
@@ -443,7 +443,6 @@ export const setDescriptionWithSchema = <S extends Schema.Schema.Any>(
443
443
  * Identifies if a property should be included in a form or not.
444
444
  * By default, all properties are included in forms, so this is opt-out.
445
445
  */
446
- // TODO(burdon): UI concern.
447
446
  export const FormInputAnnotationId = Symbol.for('@dxos/schema/annotation/FormInput');
448
447
  export const FormInputAnnotation = createAnnotationHelper<boolean>(FormInputAnnotationId);
449
448
 
@@ -482,7 +481,7 @@ export const makeUserAnnotation = <T>(props: MakeAnnoationsProps<T>): Annotation
482
481
 
483
482
  const getFromAst = (ast: SchemaAST.AST) =>
484
483
  SchemaAST.getAnnotation<PropertyMetaAnnotation>(PropertyMetaAnnotationId)(ast).pipe(
485
- Option.map((meta) => meta[props.id] as unknown),
484
+ Option.flatMap((meta) => Option.fromNullable(meta[props.id])),
486
485
  Option.map(Schema.decodeUnknownSync(props.schema)),
487
486
  );
488
487
 
@@ -533,6 +532,19 @@ export const IconAnnotation = makeUserAnnotation<IconAnnotation>({
533
532
  schema: IconAnnotationSchema,
534
533
  });
535
534
 
535
+ /**
536
+ * Indicates that this entity's icon should be resolved from a property whose value is a `Ref`
537
+ * to another entity. Consumers (e.g. graph node builders) resolve the ref target and use that
538
+ * target's schema `IconAnnotation` in place of the static one declared on this schema.
539
+ *
540
+ * Useful for wrapper schemas that delegate their visual identity to a referenced sub-entity
541
+ * (e.g. a generic `Game` whose icon should come from its `variant` ref's typed state).
542
+ */
543
+ export const IconFromRefAnnotation = makeUserAnnotation<string>({
544
+ id: 'org.dxos.annotation.icon.from-ref',
545
+ schema: Schema.String,
546
+ });
547
+
536
548
  /**
537
549
  * Get the label of an entity.
538
550
  * Accepts both reactive entities and snapshots.
@@ -546,7 +558,7 @@ export const getLabel = (entity: AnyProperties): string | undefined => {
546
558
 
547
559
  /**
548
560
  * Set the label of an entity.
549
- * Must be called within an Obj.change or Relation.change callback.
561
+ * Must be called within an Obj.update or Relation.update callback.
550
562
  */
551
563
  export const setLabel = (entity: Mutable<AnyProperties>, label: string) => {
552
564
  const schema = getSchema(entity);
@@ -568,7 +580,7 @@ export const getDescription = (entity: AnyProperties): string | undefined => {
568
580
 
569
581
  /**
570
582
  * Set the description of an entity.
571
- * Must be called within an Obj.change or Relation.change callback.
583
+ * Must be called within an Obj.update or Relation.update callback.
572
584
  */
573
585
  export const setDescription = (entity: Mutable<AnyProperties>, description: string) => {
574
586
  const schema = getSchema(entity);
@@ -66,6 +66,8 @@ export interface InternalObjectProps {
66
66
  export interface ObjectMetaJSON {
67
67
  keys: ForeignKey[];
68
68
  tags?: string[];
69
+ key?: string;
70
+ version?: string;
69
71
  }
70
72
 
71
73
  /**
@@ -5,7 +5,7 @@
5
5
  import { DXN, ObjectId, QueueSubspaceTags, SpaceId } from '@dxos/keys';
6
6
 
7
7
  /**
8
- * @deprecated Use `db.queues.create()`
8
+ * @deprecated Use `Feed.make(...)` + `db.add(feed)` then `Feed.getQueueDxn(feed)`.
9
9
  */
10
10
  // TODO(burdon): Move to @dxos/keys.
11
11
  export const createQueueDXN = (spaceId = SpaceId.random(), queueId = ObjectId.random()) =>
@@ -20,8 +20,8 @@ import { objectFromJSON, objectToJSON } from './json-serializer';
20
20
  describe('Object JSON serializer', () => {
21
21
  test('should serialize and deserialize object', async () => {
22
22
  const contact = makeObject(TestSchema.Person, { name: 'Alice' });
23
- Obj.change(contact, (obj) => {
24
- getMetaChecked(obj).keys.push({ id: '12345', source: 'example.com' });
23
+ Obj.update(contact, (contact) => {
24
+ getMetaChecked(contact).keys.push({ id: '12345', source: 'example.com' });
25
25
  });
26
26
 
27
27
  const task = createObject(TestSchema.Task, {
@@ -80,7 +80,12 @@ export const objectToJSON = <T extends AnyEntity>(obj: T): SerializedObject<T> =
80
80
  */
81
81
  export const objectFromJSON = async (
82
82
  jsonData: unknown,
83
- { refResolver, dxn, database }: { refResolver?: RefResolver; dxn?: DXN; database?: Database.Database } = {},
83
+ {
84
+ refResolver,
85
+ dxn,
86
+ database,
87
+ parent,
88
+ }: { refResolver?: RefResolver; dxn?: DXN; database?: Database.Database; parent?: Obj.Unknown } = {},
84
89
  ): Promise<AnyEntity> => {
85
90
  assumeType<ObjectJSON>(jsonData);
86
91
  assertArgument(typeof jsonData === 'object' && jsonData !== null, 'jsonData', 'expect object');
@@ -111,15 +116,15 @@ export const objectFromJSON = async (
111
116
  const isRelation =
112
117
  typeof jsonData[ATTR_RELATION_SOURCE] === 'string' || typeof jsonData[ATTR_RELATION_TARGET] === 'string';
113
118
  if (isRelation) {
114
- const sourceDxn: DXN = DXN.parse(jsonData[ATTR_RELATION_SOURCE] ?? raise(new TypeError('Missing relation source')));
115
- const targetDxn: DXN = DXN.parse(jsonData[ATTR_RELATION_TARGET] ?? raise(new TypeError('Missing relation target')));
119
+ const sourceDXN: DXN = DXN.parse(jsonData[ATTR_RELATION_SOURCE] ?? raise(new TypeError('Missing relation source')));
120
+ const targetDXN: DXN = DXN.parse(jsonData[ATTR_RELATION_TARGET] ?? raise(new TypeError('Missing relation target')));
116
121
 
117
- const source = (await refResolver?.resolve(sourceDxn)) as AnyEntity | undefined;
118
- const target = (await refResolver?.resolve(targetDxn)) as AnyEntity | undefined;
122
+ const source = (await refResolver?.resolve(sourceDXN)) as AnyEntity | undefined;
123
+ const target = (await refResolver?.resolve(targetDXN)) as AnyEntity | undefined;
119
124
 
120
125
  defineHiddenProperty(obj, KindId, EntityKind.Relation);
121
- defineHiddenProperty(obj, RelationSourceDXNId, sourceDxn);
122
- defineHiddenProperty(obj, RelationTargetDXNId, targetDxn);
126
+ defineHiddenProperty(obj, RelationSourceDXNId, sourceDXN);
127
+ defineHiddenProperty(obj, RelationTargetDXNId, targetDXN);
123
128
  defineHiddenProperty(obj, RelationSourceId, source);
124
129
  defineHiddenProperty(obj, RelationTargetId, target);
125
130
  } else {
@@ -137,8 +142,10 @@ export const objectFromJSON = async (
137
142
  }
138
143
 
139
144
  if (jsonData[ATTR_PARENT]) {
140
- const parentDxn = DXN.parse(jsonData[ATTR_PARENT]);
141
- const parent = (await refResolver?.resolve(parentDxn)) as Obj.Unknown | undefined;
145
+ const parentDXN = DXN.parse(jsonData[ATTR_PARENT]);
146
+ const resolvedParent = (await refResolver?.resolve(parentDXN)) as Obj.Unknown | undefined;
147
+ defineHiddenProperty(obj, ParentId, resolvedParent);
148
+ } else if (parent) {
142
149
  defineHiddenProperty(obj, ParentId, parent);
143
150
  }
144
151
 
@@ -177,7 +184,7 @@ const stripInternalJsonKeys = (jsonData: unknown) => {
177
184
  [ATTR_TYPE]: _type,
178
185
  [ATTR_META]: _meta,
179
186
  [ATTR_DELETED]: _deleted,
180
- [ATTR_SELF_DXN]: _selfDxn,
187
+ [ATTR_SELF_DXN]: _selfDXN,
181
188
  [ATTR_RELATION_SOURCE]: _relationSource,
182
189
  [ATTR_RELATION_TARGET]: _relationTarget,
183
190
  ...props