@dxos/echo 0.8.4-main.40e3dcdf1b → 0.8.4-main.43cb759274

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 (428) hide show
  1. package/LICENSE +102 -5
  2. package/README.md +7 -7
  3. package/dist/lib/neutral/Annotation.mjs +29 -6
  4. package/dist/lib/neutral/Database.mjs +8 -9
  5. package/dist/lib/neutral/Entity.mjs +29 -18
  6. package/dist/lib/neutral/Err.mjs +3 -3
  7. package/dist/lib/neutral/Feed.mjs +27 -19
  8. package/dist/lib/neutral/Filter.mjs +25 -15
  9. package/dist/lib/neutral/Format.mjs +23 -3
  10. package/dist/lib/neutral/JsonSchema.mjs +7 -8
  11. package/dist/lib/neutral/Key.mjs +9 -5
  12. package/dist/lib/neutral/Migration.mjs +18 -0
  13. package/dist/lib/neutral/Obj.mjs +25 -19
  14. package/dist/lib/neutral/Order.mjs +1 -1
  15. package/dist/lib/neutral/Query.mjs +19 -16
  16. package/dist/lib/neutral/QueryResult.mjs +1 -1
  17. package/dist/lib/neutral/Ref.mjs +10 -9
  18. package/dist/lib/neutral/Registry.mjs +14 -0
  19. package/dist/lib/neutral/Registry.mjs.map +7 -0
  20. package/dist/lib/neutral/Relation.mjs +26 -24
  21. package/dist/lib/neutral/Scope.mjs +12 -0
  22. package/dist/lib/neutral/Scope.mjs.map +7 -0
  23. package/dist/lib/neutral/Tag.mjs +17 -13
  24. package/dist/lib/neutral/Type.mjs +52 -26
  25. package/dist/lib/neutral/chunk-3DKP2EIN.mjs +31 -0
  26. package/dist/lib/neutral/chunk-3DKP2EIN.mjs.map +7 -0
  27. package/dist/lib/neutral/chunk-4BB4MFCN.mjs +154 -0
  28. package/dist/lib/neutral/chunk-4BB4MFCN.mjs.map +7 -0
  29. package/dist/lib/neutral/{chunk-CIRHIEMW.mjs → chunk-4SFPZJVA.mjs} +92 -112
  30. package/dist/lib/neutral/chunk-4SFPZJVA.mjs.map +7 -0
  31. package/dist/lib/neutral/{chunk-YWXWXIE5.mjs → chunk-4Z6GET2X.mjs} +3 -3
  32. package/dist/lib/neutral/chunk-4Z6GET2X.mjs.map +7 -0
  33. package/dist/lib/neutral/{chunk-ANHVGJI4.mjs → chunk-7LOUAPYZ.mjs} +9 -5
  34. package/dist/lib/neutral/chunk-7LOUAPYZ.mjs.map +7 -0
  35. package/dist/lib/neutral/{chunk-43Y5DOS6.mjs → chunk-7OAFN3OX.mjs} +75 -112
  36. package/dist/lib/neutral/chunk-7OAFN3OX.mjs.map +7 -0
  37. package/dist/lib/neutral/{chunk-BOZZPUXE.mjs → chunk-7PRCIDHH.mjs} +41 -81
  38. package/dist/lib/neutral/chunk-7PRCIDHH.mjs.map +7 -0
  39. package/dist/lib/neutral/{chunk-UBEZSGXY.mjs → chunk-BBFJWWAV.mjs} +6 -6
  40. package/dist/lib/neutral/chunk-BBFJWWAV.mjs.map +7 -0
  41. package/dist/lib/neutral/chunk-BDQGMDIX.mjs +132 -0
  42. package/dist/lib/neutral/chunk-BDQGMDIX.mjs.map +7 -0
  43. package/dist/lib/neutral/chunk-CGTI5MXO.mjs +36 -0
  44. package/dist/lib/neutral/chunk-CGTI5MXO.mjs.map +7 -0
  45. package/dist/lib/neutral/{chunk-OMUPQMLR.mjs → chunk-DKLUHO4T.mjs} +1 -1
  46. package/dist/lib/neutral/chunk-DKLUHO4T.mjs.map +7 -0
  47. package/dist/lib/neutral/chunk-DMN2CKPX.mjs +93 -0
  48. package/dist/lib/neutral/chunk-DMN2CKPX.mjs.map +7 -0
  49. package/dist/lib/neutral/{chunk-UTBRYVQC.mjs → chunk-G6CFWUYH.mjs} +2 -2
  50. package/dist/lib/neutral/chunk-H26LSXVJ.mjs +653 -0
  51. package/dist/lib/neutral/chunk-H26LSXVJ.mjs.map +7 -0
  52. package/dist/lib/neutral/{chunk-FXEG7EOK.mjs → chunk-J3MNM65T.mjs} +9 -6
  53. package/dist/lib/neutral/chunk-J3MNM65T.mjs.map +7 -0
  54. package/dist/lib/neutral/chunk-JE7AOSWF.mjs +247 -0
  55. package/dist/lib/neutral/chunk-JE7AOSWF.mjs.map +7 -0
  56. package/dist/lib/neutral/chunk-LAF3LSWI.mjs +379 -0
  57. package/dist/lib/neutral/chunk-LAF3LSWI.mjs.map +7 -0
  58. package/dist/lib/neutral/chunk-LMNRFOVB.mjs +108 -0
  59. package/dist/lib/neutral/chunk-LMNRFOVB.mjs.map +7 -0
  60. package/dist/lib/neutral/{chunk-WYOKA6AE.mjs → chunk-O6BH7EPN.mjs} +31 -4
  61. package/dist/lib/neutral/chunk-O6BH7EPN.mjs.map +7 -0
  62. package/dist/lib/neutral/{chunk-KTAWP7GI.mjs → chunk-OIYW7R2G.mjs} +99 -46
  63. package/dist/lib/neutral/chunk-OIYW7R2G.mjs.map +7 -0
  64. package/dist/lib/neutral/{chunk-TQQZLKB7.mjs → chunk-OYUG4FHF.mjs} +648 -1228
  65. package/dist/lib/neutral/chunk-OYUG4FHF.mjs.map +7 -0
  66. package/dist/lib/neutral/{chunk-NQRLRYJM.mjs → chunk-Q5FKCVBC.mjs} +173 -60
  67. package/dist/lib/neutral/chunk-Q5FKCVBC.mjs.map +7 -0
  68. package/dist/lib/neutral/chunk-Q62IHV2N.mjs +41 -0
  69. package/dist/lib/neutral/chunk-Q62IHV2N.mjs.map +7 -0
  70. package/dist/lib/neutral/{chunk-UFDK26FO.mjs → chunk-QVJDDG44.mjs} +69 -34
  71. package/dist/lib/neutral/chunk-QVJDDG44.mjs.map +7 -0
  72. package/dist/lib/neutral/{chunk-B5OXLWZL.mjs → chunk-TFEWTY5A.mjs} +155 -129
  73. package/dist/lib/neutral/chunk-TFEWTY5A.mjs.map +7 -0
  74. package/dist/lib/neutral/{chunk-UI6MWK5W.mjs → chunk-TTCSATUD.mjs} +1 -1
  75. package/dist/lib/neutral/{chunk-FZO7LQO7.mjs → chunk-VKWOFXT2.mjs} +22 -2
  76. package/dist/lib/neutral/{chunk-FZO7LQO7.mjs.map → chunk-VKWOFXT2.mjs.map} +1 -1
  77. package/dist/lib/neutral/{chunk-NXMFBIT5.mjs → chunk-ZACMEAYV.mjs} +89 -23
  78. package/dist/lib/neutral/chunk-ZACMEAYV.mjs.map +7 -0
  79. package/dist/lib/neutral/index.mjs +58 -38
  80. package/dist/lib/neutral/internal/index.mjs +131 -64
  81. package/dist/lib/neutral/meta.json +1 -1
  82. package/dist/lib/neutral/testing/index.mjs +222 -177
  83. package/dist/lib/neutral/testing/index.mjs.map +3 -3
  84. package/dist/types/src/Annotation.d.ts +108 -4
  85. package/dist/types/src/Annotation.d.ts.map +1 -1
  86. package/dist/types/src/Annotation.test.d.ts +2 -0
  87. package/dist/types/src/Annotation.test.d.ts.map +1 -0
  88. package/dist/types/src/Collection.d.ts +2 -3
  89. package/dist/types/src/Collection.d.ts.map +1 -1
  90. package/dist/types/src/Database.d.ts +56 -24
  91. package/dist/types/src/Database.d.ts.map +1 -1
  92. package/dist/types/src/Dataset.d.ts +17 -6
  93. package/dist/types/src/Dataset.d.ts.map +1 -1
  94. package/dist/types/src/Entity.d.ts +102 -26
  95. package/dist/types/src/Entity.d.ts.map +1 -1
  96. package/dist/types/src/Err.d.ts +27 -27
  97. package/dist/types/src/Err.d.ts.map +1 -1
  98. package/dist/types/src/Feed.d.ts +89 -25
  99. package/dist/types/src/Feed.d.ts.map +1 -1
  100. package/dist/types/src/Filter.d.ts +74 -11
  101. package/dist/types/src/Filter.d.ts.map +1 -1
  102. package/dist/types/src/Filter.test.d.ts +2 -0
  103. package/dist/types/src/Filter.test.d.ts.map +1 -0
  104. package/dist/types/src/Hypergraph.d.ts +14 -9
  105. package/dist/types/src/Hypergraph.d.ts.map +1 -1
  106. package/dist/types/src/Json.d.ts +33 -0
  107. package/dist/types/src/Json.d.ts.map +1 -0
  108. package/dist/types/src/Json.test.d.ts +2 -0
  109. package/dist/types/src/Json.test.d.ts.map +1 -0
  110. package/dist/types/src/JsonSchema.d.ts +2 -2
  111. package/dist/types/src/JsonSchema.d.ts.map +1 -1
  112. package/dist/types/src/Key.d.ts +1 -1
  113. package/dist/types/src/Key.d.ts.map +1 -1
  114. package/dist/types/src/Migration.d.ts +72 -0
  115. package/dist/types/src/Migration.d.ts.map +1 -0
  116. package/dist/types/src/Obj.d.ts +108 -65
  117. package/dist/types/src/Obj.d.ts.map +1 -1
  118. package/dist/types/src/Order.d.ts.map +1 -1
  119. package/dist/types/src/Query.d.ts +35 -10
  120. package/dist/types/src/Query.d.ts.map +1 -1
  121. package/dist/types/src/QueryResult.d.ts +9 -0
  122. package/dist/types/src/QueryResult.d.ts.map +1 -1
  123. package/dist/types/src/Ref.d.ts +15 -7
  124. package/dist/types/src/Ref.d.ts.map +1 -1
  125. package/dist/types/src/Registry.d.ts +131 -0
  126. package/dist/types/src/Registry.d.ts.map +1 -0
  127. package/dist/types/src/Relation.d.ts +72 -44
  128. package/dist/types/src/Relation.d.ts.map +1 -1
  129. package/dist/types/src/Scope.d.ts +34 -0
  130. package/dist/types/src/Scope.d.ts.map +1 -0
  131. package/dist/types/src/StateMap.d.ts +32 -0
  132. package/dist/types/src/StateMap.d.ts.map +1 -0
  133. package/dist/types/src/StateMap.test.d.ts +2 -0
  134. package/dist/types/src/StateMap.test.d.ts.map +1 -0
  135. package/dist/types/src/Tag.d.ts +21 -5
  136. package/dist/types/src/Tag.d.ts.map +1 -1
  137. package/dist/types/src/TagIndex.d.ts +36 -0
  138. package/dist/types/src/TagIndex.d.ts.map +1 -0
  139. package/dist/types/src/TagIndex.test.d.ts +2 -0
  140. package/dist/types/src/TagIndex.test.d.ts.map +1 -0
  141. package/dist/types/src/Tagging.d.ts +31 -0
  142. package/dist/types/src/Tagging.d.ts.map +1 -0
  143. package/dist/types/src/Type.d.ts +357 -96
  144. package/dist/types/src/Type.d.ts.map +1 -1
  145. package/dist/types/src/View.d.ts +2 -5
  146. package/dist/types/src/View.d.ts.map +1 -1
  147. package/dist/types/src/exemplars.test.d.ts +2 -0
  148. package/dist/types/src/exemplars.test.d.ts.map +1 -0
  149. package/dist/types/src/index.d.ts +8 -2
  150. package/dist/types/src/index.d.ts.map +1 -1
  151. package/dist/types/src/internal/Annotation/annotations.d.ts +79 -37
  152. package/dist/types/src/internal/Annotation/annotations.d.ts.map +1 -1
  153. package/dist/types/src/internal/Annotation/dictionary.d.ts +24 -0
  154. package/dist/types/src/internal/Annotation/dictionary.d.ts.map +1 -0
  155. package/dist/types/src/internal/Annotation/entity-dictionary.d.ts +14 -0
  156. package/dist/types/src/internal/Annotation/entity-dictionary.d.ts.map +1 -0
  157. package/dist/types/src/internal/Annotation/index.d.ts +4 -2
  158. package/dist/types/src/internal/Annotation/index.d.ts.map +1 -1
  159. package/dist/types/src/internal/Annotation/sorting.d.ts.map +1 -1
  160. package/dist/types/src/internal/Annotation/util.d.ts +14 -5
  161. package/dist/types/src/internal/Annotation/util.d.ts.map +1 -1
  162. package/dist/types/src/internal/Entity/api.d.ts +3 -3
  163. package/dist/types/src/internal/Entity/api.d.ts.map +1 -1
  164. package/dist/types/src/internal/Entity/entity.d.ts +72 -8
  165. package/dist/types/src/internal/Entity/entity.d.ts.map +1 -1
  166. package/dist/types/src/internal/Entity/guard.d.ts +10 -0
  167. package/dist/types/src/internal/Entity/guard.d.ts.map +1 -0
  168. package/dist/types/src/internal/Entity/index.d.ts +2 -0
  169. package/dist/types/src/internal/Entity/index.d.ts.map +1 -1
  170. package/dist/types/src/internal/Entity/model.d.ts +20 -17
  171. package/dist/types/src/internal/Entity/model.d.ts.map +1 -1
  172. package/dist/types/src/internal/Entity/object.d.ts +3 -3
  173. package/dist/types/src/internal/Entity/object.d.ts.map +1 -1
  174. package/dist/types/src/internal/Entity/relation.d.ts +30 -7
  175. package/dist/types/src/internal/Entity/relation.d.ts.map +1 -1
  176. package/dist/types/src/internal/Entity/type-kind.d.ts +24 -0
  177. package/dist/types/src/internal/Entity/type-kind.d.ts.map +1 -0
  178. package/dist/types/src/internal/Entity/type-uri.d.ts +25 -0
  179. package/dist/types/src/internal/Entity/type-uri.d.ts.map +1 -0
  180. package/dist/types/src/internal/Entity/version.d.ts.map +1 -1
  181. package/dist/types/src/internal/Format/date.d.ts.map +1 -1
  182. package/dist/types/src/internal/Format/format.d.ts +3 -2
  183. package/dist/types/src/internal/Format/format.d.ts.map +1 -1
  184. package/dist/types/src/internal/Format/index.d.ts +2 -2
  185. package/dist/types/src/internal/Format/index.d.ts.map +1 -1
  186. package/dist/types/src/internal/Format/number.d.ts.map +1 -1
  187. package/dist/types/src/internal/Format/object.d.ts +3 -1
  188. package/dist/types/src/internal/Format/object.d.ts.map +1 -1
  189. package/dist/types/src/internal/Format/types.d.ts.map +1 -1
  190. package/dist/types/src/internal/JsonSchema/json-schema-normalize.d.ts.map +1 -1
  191. package/dist/types/src/internal/JsonSchema/json-schema-type.d.ts +28 -28
  192. package/dist/types/src/internal/JsonSchema/json-schema-type.d.ts.map +1 -1
  193. package/dist/types/src/internal/JsonSchema/json-schema.d.ts +3 -2
  194. package/dist/types/src/internal/JsonSchema/json-schema.d.ts.map +1 -1
  195. package/dist/types/src/internal/Obj/clone.d.ts.map +1 -1
  196. package/dist/types/src/internal/Obj/common.d.ts.map +1 -1
  197. package/dist/types/src/internal/Obj/create-object.d.ts +11 -12
  198. package/dist/types/src/internal/Obj/create-object.d.ts.map +1 -1
  199. package/dist/types/src/internal/Obj/deleted.d.ts.map +1 -1
  200. package/dist/types/src/internal/Obj/index.d.ts +0 -1
  201. package/dist/types/src/internal/Obj/index.d.ts.map +1 -1
  202. package/dist/types/src/internal/Obj/json-serializer.d.ts +11 -10
  203. package/dist/types/src/internal/Obj/json-serializer.d.ts.map +1 -1
  204. package/dist/types/src/internal/Obj/set-value.d.ts +1 -1
  205. package/dist/types/src/internal/Obj/set-value.d.ts.map +1 -1
  206. package/dist/types/src/internal/Obj/snapshot.d.ts.map +1 -1
  207. package/dist/types/src/internal/Obj/typed-object.d.ts +1 -1
  208. package/dist/types/src/internal/Query.d.ts +10 -0
  209. package/dist/types/src/internal/Query.d.ts.map +1 -0
  210. package/dist/types/src/internal/Ref/ref-array.d.ts +2 -2
  211. package/dist/types/src/internal/Ref/ref-array.d.ts.map +1 -1
  212. package/dist/types/src/internal/Ref/ref.d.ts +42 -19
  213. package/dist/types/src/internal/Ref/ref.d.ts.map +1 -1
  214. package/dist/types/src/internal/Type/compose.d.ts.map +1 -1
  215. package/dist/types/src/internal/Type/index.d.ts +1 -2
  216. package/dist/types/src/internal/Type/index.d.ts.map +1 -1
  217. package/dist/types/src/internal/Type/manipulation.d.ts +0 -1
  218. package/dist/types/src/internal/Type/manipulation.d.ts.map +1 -1
  219. package/dist/types/src/internal/Type/type-schema.d.ts +52 -0
  220. package/dist/types/src/internal/Type/type-schema.d.ts.map +1 -0
  221. package/dist/types/src/internal/common/api/meta.d.ts +14 -11
  222. package/dist/types/src/internal/common/api/meta.d.ts.map +1 -1
  223. package/dist/types/src/internal/common/proxy/change-context.d.ts +1 -1
  224. package/dist/types/src/internal/common/proxy/change-context.d.ts.map +1 -1
  225. package/dist/types/src/internal/common/proxy/define-hidden-property.d.ts.map +1 -1
  226. package/dist/types/src/internal/common/proxy/errors.d.ts +1 -1
  227. package/dist/types/src/internal/common/proxy/errors.d.ts.map +1 -1
  228. package/dist/types/src/internal/common/proxy/event-batch.d.ts.map +1 -1
  229. package/dist/types/src/internal/common/proxy/json-serializer.d.ts.map +1 -1
  230. package/dist/types/src/internal/common/proxy/make-object.d.ts +11 -5
  231. package/dist/types/src/internal/common/proxy/make-object.d.ts.map +1 -1
  232. package/dist/types/src/internal/common/proxy/ownership.d.ts.map +1 -1
  233. package/dist/types/src/internal/common/proxy/proxy-utils.d.ts.map +1 -1
  234. package/dist/types/src/internal/common/proxy/reactive-array.d.ts +1 -1
  235. package/dist/types/src/internal/common/proxy/reactive.d.ts +1 -1
  236. package/dist/types/src/internal/common/proxy/reactive.d.ts.map +1 -1
  237. package/dist/types/src/internal/common/proxy/reactive.test.d.ts +2 -0
  238. package/dist/types/src/internal/common/proxy/reactive.test.d.ts.map +1 -0
  239. package/dist/types/src/internal/common/proxy/schema-validator.d.ts.map +1 -1
  240. package/dist/types/src/internal/common/proxy/typed-handler.d.ts +18 -2
  241. package/dist/types/src/internal/common/proxy/typed-handler.d.ts.map +1 -1
  242. package/dist/types/src/internal/common/types/base.d.ts +4 -4
  243. package/dist/types/src/internal/common/types/base.d.ts.map +1 -1
  244. package/dist/types/src/internal/common/types/entity.d.ts +63 -6
  245. package/dist/types/src/internal/common/types/entity.d.ts.map +1 -1
  246. package/dist/types/src/internal/common/types/index.d.ts +1 -1
  247. package/dist/types/src/internal/common/types/index.d.ts.map +1 -1
  248. package/dist/types/src/internal/common/types/meta.d.ts +23 -7
  249. package/dist/types/src/internal/common/types/meta.d.ts.map +1 -1
  250. package/dist/types/src/internal/common/types/model-symbols.d.ts +8 -4
  251. package/dist/types/src/internal/common/types/model-symbols.d.ts.map +1 -1
  252. package/dist/types/src/internal/common/types/typename.d.ts +7 -0
  253. package/dist/types/src/internal/common/types/typename.d.ts.map +1 -1
  254. package/dist/types/src/internal/common/types/version.d.ts +1 -1
  255. package/dist/types/src/internal/common/types/well-known-types.d.ts +11 -0
  256. package/dist/types/src/internal/common/types/well-known-types.d.ts.map +1 -0
  257. package/dist/types/src/internal/index.d.ts +2 -0
  258. package/dist/types/src/internal/index.d.ts.map +1 -1
  259. package/dist/types/src/testing/test-data.d.ts +8 -8
  260. package/dist/types/src/testing/test-data.d.ts.map +1 -1
  261. package/dist/types/src/testing/test-schema.d.ts +83 -89
  262. package/dist/types/src/testing/test-schema.d.ts.map +1 -1
  263. package/dist/types/src/testing/util.d.ts +5 -3
  264. package/dist/types/src/testing/util.d.ts.map +1 -1
  265. package/dist/types/tsconfig.tsbuildinfo +1 -1
  266. package/package.json +27 -19
  267. package/src/Annotation.test.ts +439 -0
  268. package/src/Annotation.ts +152 -4
  269. package/src/Collection.ts +6 -5
  270. package/src/Database.ts +117 -57
  271. package/src/Dataset.ts +10 -2
  272. package/src/Entity.test.ts +116 -6
  273. package/src/Entity.ts +130 -31
  274. package/src/Err.ts +4 -4
  275. package/src/Feed.ts +138 -43
  276. package/src/Filter.test.ts +90 -0
  277. package/src/Filter.ts +143 -27
  278. package/src/Hypergraph.ts +14 -9
  279. package/src/Json.test.ts +175 -0
  280. package/src/Json.ts +103 -0
  281. package/src/Key.ts +1 -1
  282. package/src/Migration.ts +114 -0
  283. package/src/Obj.test.ts +122 -19
  284. package/src/Obj.ts +279 -97
  285. package/src/Query.test.ts +317 -131
  286. package/src/Query.ts +181 -73
  287. package/src/QueryResult.ts +11 -0
  288. package/src/Ref.ts +22 -4
  289. package/src/Registry.ts +155 -0
  290. package/src/Relation.test.ts +10 -10
  291. package/src/Relation.ts +112 -71
  292. package/src/Scope.ts +50 -0
  293. package/src/StateMap.test.ts +56 -0
  294. package/src/StateMap.ts +99 -0
  295. package/src/Tag.md +88 -0
  296. package/src/Tag.ts +49 -6
  297. package/src/TagIndex.test.ts +57 -0
  298. package/src/TagIndex.ts +110 -0
  299. package/src/Tagging.ts +83 -0
  300. package/src/Type.test.ts +223 -18
  301. package/src/Type.ts +602 -132
  302. package/src/View.ts +11 -15
  303. package/src/exemplars.test.ts +21 -0
  304. package/src/index.ts +9 -3
  305. package/src/internal/Annotation/annotations.test.ts +82 -13
  306. package/src/internal/Annotation/annotations.ts +153 -115
  307. package/src/internal/Annotation/dictionary.ts +47 -0
  308. package/src/internal/Annotation/entity-dictionary.ts +40 -0
  309. package/src/internal/Annotation/index.ts +4 -2
  310. package/src/internal/Annotation/sorting.ts +0 -1
  311. package/src/internal/Annotation/util.ts +17 -8
  312. package/src/internal/Entity/api.ts +7 -8
  313. package/src/internal/Entity/entity.ts +196 -47
  314. package/src/internal/Entity/guard.ts +26 -0
  315. package/src/internal/Entity/index.ts +2 -0
  316. package/src/internal/Entity/model.ts +37 -28
  317. package/src/internal/Entity/object.ts +21 -6
  318. package/src/internal/Entity/relation.ts +68 -34
  319. package/src/internal/Entity/type-kind.ts +75 -0
  320. package/src/internal/Entity/type-uri.ts +87 -0
  321. package/src/internal/Entity/util.ts +10 -9
  322. package/src/internal/Entity/version.ts +0 -1
  323. package/src/internal/Format/date.test.ts +0 -1
  324. package/src/internal/Format/date.ts +0 -4
  325. package/src/internal/Format/format.test.ts +21 -1
  326. package/src/internal/Format/index.ts +2 -3
  327. package/src/internal/Format/object.ts +21 -4
  328. package/src/internal/Format/types.ts +1 -1
  329. package/src/internal/JsonSchema/annotations.ts +1 -1
  330. package/src/internal/JsonSchema/json-schema-type.ts +1 -1
  331. package/src/internal/JsonSchema/json-schema.test.ts +55 -142
  332. package/src/internal/JsonSchema/json-schema.ts +44 -31
  333. package/src/internal/Obj/clone.ts +9 -4
  334. package/src/internal/Obj/create-object.test.ts +13 -13
  335. package/src/internal/Obj/create-object.ts +68 -24
  336. package/src/internal/Obj/deleted.ts +1 -1
  337. package/src/internal/Obj/index.ts +0 -1
  338. package/src/internal/Obj/inspect.ts +4 -3
  339. package/src/internal/Obj/json-serializer.test.ts +102 -24
  340. package/src/internal/Obj/json-serializer.ts +105 -43
  341. package/src/internal/Obj/set-value.test.ts +31 -54
  342. package/src/internal/Obj/set-value.ts +1 -1
  343. package/src/internal/Obj/snapshot.ts +11 -4
  344. package/src/internal/Obj/typed-object.test.ts +9 -11
  345. package/src/internal/Obj/typed-object.ts +1 -1
  346. package/src/internal/Query.ts +159 -0
  347. package/src/internal/Ref/ref-array.ts +3 -4
  348. package/src/internal/Ref/ref.test.ts +17 -27
  349. package/src/internal/Ref/ref.ts +124 -59
  350. package/src/internal/Type/compose.test.ts +3 -2
  351. package/src/internal/Type/index.ts +1 -2
  352. package/src/internal/Type/manipulation.ts +0 -25
  353. package/src/internal/Type/type-schema.ts +60 -0
  354. package/src/internal/common/README.md +2 -2
  355. package/src/internal/common/api/meta.ts +19 -17
  356. package/src/internal/common/proxy/change-context.ts +1 -1
  357. package/src/internal/common/proxy/change.test.ts +118 -110
  358. package/src/internal/common/proxy/errors.ts +2 -2
  359. package/src/internal/common/proxy/handler.test.ts +1 -3
  360. package/src/internal/common/proxy/json-serializer.ts +26 -16
  361. package/src/internal/common/proxy/make-object.ts +43 -21
  362. package/src/internal/common/proxy/ownership.ts +2 -3
  363. package/src/internal/common/proxy/reactive-array.ts +1 -1
  364. package/src/internal/common/proxy/reactive.test.ts +54 -0
  365. package/src/internal/common/proxy/reactive.ts +11 -3
  366. package/src/internal/common/proxy/schema.test.ts +48 -86
  367. package/src/internal/common/proxy/typed-handler.test.ts +12 -12
  368. package/src/internal/common/proxy/typed-handler.ts +79 -19
  369. package/src/internal/common/proxy/typed-object.test.ts +16 -29
  370. package/src/internal/common/types/base.ts +4 -4
  371. package/src/internal/common/types/entity.ts +81 -2
  372. package/src/internal/common/types/index.ts +6 -1
  373. package/src/internal/common/types/meta.ts +49 -15
  374. package/src/internal/common/types/model-symbols.ts +16 -4
  375. package/src/internal/common/types/typename.ts +39 -3
  376. package/src/internal/common/types/well-known-types.ts +15 -0
  377. package/src/internal/index.ts +6 -0
  378. package/src/testing/api.test.ts +15 -10
  379. package/src/testing/test-data.ts +159 -100
  380. package/src/testing/test-schema.ts +22 -58
  381. package/src/testing/util.ts +14 -11
  382. package/dist/lib/neutral/SchemaRegistry.mjs +0 -2
  383. package/dist/lib/neutral/chunk-43Y5DOS6.mjs.map +0 -7
  384. package/dist/lib/neutral/chunk-724GQYEN.mjs +0 -56
  385. package/dist/lib/neutral/chunk-724GQYEN.mjs.map +0 -7
  386. package/dist/lib/neutral/chunk-ANHVGJI4.mjs.map +0 -7
  387. package/dist/lib/neutral/chunk-B5OXLWZL.mjs.map +0 -7
  388. package/dist/lib/neutral/chunk-BNCCGLJN.mjs +0 -7
  389. package/dist/lib/neutral/chunk-BNCCGLJN.mjs.map +0 -7
  390. package/dist/lib/neutral/chunk-BOZZPUXE.mjs.map +0 -7
  391. package/dist/lib/neutral/chunk-CIRHIEMW.mjs.map +0 -7
  392. package/dist/lib/neutral/chunk-DYDJBZEG.mjs +0 -102
  393. package/dist/lib/neutral/chunk-DYDJBZEG.mjs.map +0 -7
  394. package/dist/lib/neutral/chunk-FXEG7EOK.mjs.map +0 -7
  395. package/dist/lib/neutral/chunk-KTAWP7GI.mjs.map +0 -7
  396. package/dist/lib/neutral/chunk-NEGC54NE.mjs +0 -130
  397. package/dist/lib/neutral/chunk-NEGC54NE.mjs.map +0 -7
  398. package/dist/lib/neutral/chunk-NQRLRYJM.mjs.map +0 -7
  399. package/dist/lib/neutral/chunk-NXMFBIT5.mjs.map +0 -7
  400. package/dist/lib/neutral/chunk-OENWMTE6.mjs +0 -45
  401. package/dist/lib/neutral/chunk-OENWMTE6.mjs.map +0 -7
  402. package/dist/lib/neutral/chunk-OMUPQMLR.mjs.map +0 -7
  403. package/dist/lib/neutral/chunk-RMLF7JOZ.mjs +0 -97
  404. package/dist/lib/neutral/chunk-RMLF7JOZ.mjs.map +0 -7
  405. package/dist/lib/neutral/chunk-SEMVAGBM.mjs +0 -355
  406. package/dist/lib/neutral/chunk-SEMVAGBM.mjs.map +0 -7
  407. package/dist/lib/neutral/chunk-TQQZLKB7.mjs.map +0 -7
  408. package/dist/lib/neutral/chunk-UBEZSGXY.mjs.map +0 -7
  409. package/dist/lib/neutral/chunk-UFDK26FO.mjs.map +0 -7
  410. package/dist/lib/neutral/chunk-VA2F47ZC.mjs +0 -152
  411. package/dist/lib/neutral/chunk-VA2F47ZC.mjs.map +0 -7
  412. package/dist/lib/neutral/chunk-WYOKA6AE.mjs.map +0 -7
  413. package/dist/lib/neutral/chunk-YWXWXIE5.mjs.map +0 -7
  414. package/dist/types/src/SchemaRegistry.d.ts +0 -84
  415. package/dist/types/src/SchemaRegistry.d.ts.map +0 -1
  416. package/dist/types/src/internal/Obj/ids.d.ts +0 -6
  417. package/dist/types/src/internal/Obj/ids.d.ts.map +0 -1
  418. package/dist/types/src/internal/Type/echo-schema.d.ts +0 -181
  419. package/dist/types/src/internal/Type/echo-schema.d.ts.map +0 -1
  420. package/dist/types/src/internal/Type/persistent-schema.d.ts +0 -20
  421. package/dist/types/src/internal/Type/persistent-schema.d.ts.map +0 -1
  422. package/src/SchemaRegistry.ts +0 -106
  423. package/src/internal/Obj/ids.ts +0 -12
  424. package/src/internal/Type/echo-schema.ts +0 -424
  425. package/src/internal/Type/persistent-schema.ts +0 -33
  426. /package/dist/lib/neutral/{SchemaRegistry.mjs.map → Migration.mjs.map} +0 -0
  427. /package/dist/lib/neutral/{chunk-UTBRYVQC.mjs.map → chunk-G6CFWUYH.mjs.map} +0 -0
  428. /package/dist/lib/neutral/{chunk-UI6MWK5W.mjs.map → chunk-TTCSATUD.mjs.map} +0 -0
package/src/Tag.md ADDED
@@ -0,0 +1,88 @@
1
+ # Tag
2
+
3
+ `Tag` is the ECHO type for labelling other objects. The schema is tiny — `{ label, hue? }` — but the _attachment pattern_ varies by use case. This doc covers what's available and which pattern to use.
4
+
5
+ ## What `Tag` is
6
+
7
+ Definition: [`Tag.ts`](./src/Tag.ts) — `Schema.Struct({ label: String, hue?: String })` with DXN `org.dxos.type.tag@0.1.0`. Carries the `SystemTypeAnnotation` so it's treated as a built-in type by the ECHO runtime.
8
+
9
+ Helpers on the same module:
10
+
11
+ - `Tag.make(props)` — constructor.
12
+ - `Tag.Map` — `Record<string, Tag>` alias used by call-site indexes.
13
+ - `Tag.createTagList(tags)` — `Map → Tag[]`, sorted by label.
14
+ - `Tag.findTagByLabel(tags, name)` — case-insensitive label lookup.
15
+ - `Tag.sortTags(a, b)` — `localeCompare` on label.
16
+
17
+ ### Colour (`hue`)
18
+
19
+ `hue` is a Tailwind colour name (`red`, `orange`, `amber`, …). The canonical list lives in [`IconAnnotation`'s schema](./src/internal/Annotation/annotations.ts) (search `IconAnnotationSchema`). Surfaces that render chips read `hue` to colour the chip; rendering hashes the tag id as a fallback when `hue` is absent.
20
+
21
+ ## Attachment patterns
22
+
23
+ Three ways an object can carry tags. Pick the one that matches your storage shape:
24
+
25
+ ### 1. `Obj.getMeta(obj).tags` — DXN refs on the object's metadata
26
+
27
+ The canonical user-tagging path. Each tag lives as a standalone `Tag` object in the space; the _target_ object holds an array of DXN refs to those tags inside its metadata bag. The form layer's [`ObjectProperties`](../../ui/react-ui-form/src/components/ObjectProperties) writes these via the `pinnedTags` control.
28
+
29
+ Use when: the target object is mutable. Tags can be added/removed cheaply by editing the metadata.
30
+
31
+ ```ts
32
+ const tag = Obj.make(Tag.Tag, { label: 'important', hue: 'amber' });
33
+ db.add(tag);
34
+ Obj.update(target, (t) => {
35
+ Obj.getMeta(t).tags = [tag.dxn];
36
+ });
37
+ ```
38
+
39
+ ### 2. Container-owned tag map — inverse index on the parent
40
+
41
+ For containers that hold immutable-in-feed objects (mailboxes hold messages, magazines hold posts), the children can't carry tag refs because they're immutable. Instead, the container holds an inverted `tagId → Ref<child>[]` map. Tag _definitions_ live in the same map alongside the assignments.
42
+
43
+ Use when: the children are append-only feed records, OR you need a fast "all objects tagged X" query without scanning each child.
44
+
45
+ Reference shape (from plugin-inbox's [`Mailbox.tags`](../../plugins/plugin-inbox/src/types/Mailbox.ts)):
46
+
47
+ ```ts
48
+ Mailbox.tags: Record<TagId, {
49
+ label: string,
50
+ hue?: string,
51
+ source?: 'provider' | 'user',
52
+ messages: Ref<Message>[],
53
+ }>
54
+ ```
55
+
56
+ Tag-id strategy depends on origin:
57
+
58
+ | source | tag id | rationale |
59
+ | ----------------------------------- | --------------------------------- | ---------------------------------------------------------------------------------------- |
60
+ | provider-synced (e.g. Gmail labels) | external id (Gmail's `Label_123`) | preserves re-sync idempotence — same external id → same map entry, no duplicates |
61
+ | user-applied | random `EntityId` | stable across renames; case-insensitive label dedupe via `findTagByLabel` at create time |
62
+
63
+ Trade-offs considered and rejected:
64
+
65
+ - Slug from label (`kebab-case(label)`) — natural dedupe but renaming a tag breaks references that point at the old key.
66
+ - EntityId for everything — uniform but Gmail re-syncs would duplicate provider tags every run unless you also dedupe by label, which loses the external id.
67
+
68
+ ### 3. `HasSubject` relation (deprecated)
69
+
70
+ The older pattern: a relation with `Source = Tag, Target = Object`. Lives at [`@dxos/types/HasSubject`](../../sdk/types/src/types/HasSubject.ts) and is marked `@deprecated`.
71
+
72
+ Use when: never, for new code. Existing data is being migrated to one of the two patterns above. The shape is functionally identical to (1), but doing it via a separate relation creates a query overhead (walk all relations for the object) that (1) avoids by inlining the refs.
73
+
74
+ ## Hue conventions
75
+
76
+ Always store the Tailwind colour name (e.g. `'amber'`), not a hex. Surfaces translate via `data-hue` on the chip, which the design system maps to the right shade. See [`getHashStyles`](../../ui/react-ui-components) for the fallback hash when `hue` is unset.
77
+
78
+ ## plugin-inbox: how the inbox uses Tag
79
+
80
+ Single source of truth: [`Mailbox.tags`](../../plugins/plugin-inbox/src/types/Mailbox.ts). Pattern 2 (container map). Replaces both the older `Mailbox.labels` (Gmail provider dictionary) and the `HasSubject`-relation user-tagging path with one record.
81
+
82
+ - Provider labels: `syncLabels` writes `mailbox.tags[gmailLabelId] = { label, source: 'provider', messages: [...] }`. Gmail's id is the map key so re-sync is idempotent.
83
+ - Per-message provider assignments: at sync time, after appending a message to the feed, the sync loop pushes `Ref.make(message)` onto each `mailbox.tags[labelId].messages` it was given.
84
+ - User tags / extractor tags: `Mailbox.applyTag(mailbox, { label, hue? }, message)` find-or-creates a `source: 'user'` entry and pushes a Ref. Idempotent.
85
+ - UI: `Mailbox.buildMessageTagsIndex(mailbox)` inverts the map to `messageId → Tag[]` for `MessageStack` tile chips; `Mailbox.getTagsForMessage(mailbox, message)` for the single-message case in `MessageHeader`.
86
+ - Extractor tagging: extractors that want to tag the source message return `{ tags: [{ label, hue? }] }` in their `ExtractResult`; the `ExtractMessage` dispatcher applies them after persistence. See the trip extractor for an example (`tags: [{ label: 'trip', hue: 'sky' }]`).
87
+
88
+ Adopting the same pattern in a new plugin: pick this shape when your plugin owns a mutable container with immutable feed-stored children, OR when you want a fast "tagged-with-X" inverted lookup. Copy `applyTag` / `removeTag` / `buildMessageTagsIndex` / `getTagsForMessage` (≈ 80 lines, no plugin-specific logic) and you have a working tag system on day one.
package/src/Tag.ts CHANGED
@@ -6,6 +6,10 @@
6
6
 
7
7
  import * as Schema from 'effect/Schema';
8
8
 
9
+ import type { ForeignKey } from '@dxos/echo-protocol';
10
+
11
+ import type * as Database from './Database';
12
+ import * as Filter from './Filter';
9
13
  import * as internal from './internal';
10
14
  import * as Obj from './Obj';
11
15
  import * as Type from './Type';
@@ -14,15 +18,13 @@ export const Tag = Schema.Struct({
14
18
  label: Schema.String,
15
19
  hue: Schema.optional(Schema.String), // TODO(burdon): Color name?
16
20
  }).pipe(
17
- Type.object({
18
- typename: 'org.dxos.type.tag',
19
- version: '0.1.0',
20
- }),
21
21
  internal.LabelAnnotation.set(['label']),
22
- internal.SystemTypeAnnotation.set(true),
22
+ internal.HiddenAnnotation.set(true),
23
+ // Shared DXN so `meta.tags` (the `Ref<Tag>` schema) and this type stay in sync.
24
+ Type.makeObject(internal.TagTypeDXN),
23
25
  );
24
26
 
25
- export type Tag = Schema.Schema.Type<typeof Tag>;
27
+ export type Tag = Type.InstanceType<typeof Tag>;
26
28
 
27
29
  export const make = (props: Obj.MakeProps<typeof Tag>) => Obj.make(Tag, props);
28
30
 
@@ -39,3 +41,44 @@ export const findTagByLabel = (tags: Map | undefined, name: string): Tag | undef
39
41
  const entry = Object.entries(tags ?? {}).find(([_, tag]) => tag.label.toLowerCase() === name.toLowerCase());
40
42
  return entry ? { ...entry[1], id: entry[0] } : undefined;
41
43
  };
44
+
45
+ /**
46
+ * Finds or creates a {@link Tag} object in the database.
47
+ *
48
+ * - With a foreign `key` (system/provider tags): matched by that key; the label is kept current on
49
+ * re-sync. Use a stable key (e.g. `{ source: 'google.com/gmail/label', id }`) for tags whose
50
+ * identity is external or well-known.
51
+ * - Without a key (user tags): matched by case-insensitive label among tags that carry **no**
52
+ * foreign key, so it never collides with a keyed system/provider tag of the same label.
53
+ */
54
+ export const findOrCreate = async (
55
+ db: Pick<Database.Database, 'query' | 'add'>,
56
+ options: { label: string; hue?: string; key?: ForeignKey },
57
+ ): Promise<Tag> => {
58
+ const { label, hue, key } = options;
59
+ const withHue = hue ? { hue } : {};
60
+ if (key) {
61
+ const [existing] = await db.query(Filter.foreignKeys(Tag, [key])).run();
62
+ if (existing) {
63
+ // Keep label (and hue, when provided) current on re-sync. `hue` is only touched when supplied,
64
+ // so a label-only re-sync (e.g. Gmail) doesn't wipe a user-set colour.
65
+ if (existing.label !== label || (hue !== undefined && existing.hue !== hue)) {
66
+ Obj.update(existing, (existing) => {
67
+ existing.label = label;
68
+ if (hue !== undefined) {
69
+ existing.hue = hue;
70
+ }
71
+ });
72
+ }
73
+ return existing;
74
+ }
75
+ return db.add(Obj.make(Tag, { [Obj.Meta]: { keys: [key] }, label, ...withHue }));
76
+ }
77
+
78
+ const lowered = label.toLowerCase();
79
+ const candidates = await db.query(Filter.type(Tag)).run();
80
+ const existing = candidates.find(
81
+ (tag) => tag.label.toLowerCase() === lowered && (Obj.getMeta(tag).keys ?? []).length === 0,
82
+ );
83
+ return existing ?? db.add(make({ label, ...withHue }));
84
+ };
@@ -0,0 +1,57 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import * as Schema from 'effect/Schema';
6
+ import { describe, expect, test } from 'vitest';
7
+
8
+ import { DXN } from '@dxos/keys';
9
+
10
+ import * as Obj from './Obj';
11
+ import * as TagIndex from './TagIndex';
12
+ import * as Type from './Type';
13
+
14
+ /** A minimal item standing in for an immutable feed object. */
15
+ const Item = Schema.Struct({
16
+ text: Schema.String,
17
+ }).pipe(Type.makeObject(DXN.make('org.dxos.test.TagIndexItem', '0.1.0')));
18
+
19
+ /** A host object carrying a tag index alongside (a notional feed of) items. */
20
+ const Host = Schema.Struct({
21
+ tags: TagIndex.field(),
22
+ }).pipe(Type.makeObject(DXN.make('org.dxos.test.TagIndexHost', '0.1.0')));
23
+
24
+ describe('TagIndex', () => {
25
+ test('sets, unsets, and inverts feed-object tags', () => {
26
+ const host = Obj.make(Host, {});
27
+ const tags = TagIndex.bind(host, 'tags');
28
+
29
+ const a = Obj.make(Item, { text: 'a' });
30
+ const b = Obj.make(Item, { text: 'b' });
31
+ const urgent = 'dxn:tag:urgent';
32
+ const later = 'dxn:tag:later';
33
+
34
+ tags.setTag(urgent, a.id);
35
+ tags.setTag(urgent, b.id);
36
+ tags.setTag(later, b.id);
37
+ // Idempotent.
38
+ tags.setTag(urgent, a.id);
39
+
40
+ // Filter the feed by tag: tag -> object ids.
41
+ expect([...tags.objects(urgent)]).toEqual([a.id, b.id]);
42
+ expect([...tags.objects(later)]).toEqual([b.id]);
43
+ expect([...tags.objects('dxn:tag:missing')]).toEqual([]);
44
+
45
+ // Inverse: object -> tag ids.
46
+ expect(tags.tags(b.id).sort()).toEqual([later, urgent]);
47
+ expect(tags.tags(a.id)).toEqual([urgent]);
48
+ expect(tags.tagIds().sort()).toEqual([later, urgent]);
49
+
50
+ // Unset prunes the membership; emptying a tag drops the key.
51
+ tags.unsetTag(urgent, a.id);
52
+ expect([...tags.objects(urgent)]).toEqual([b.id]);
53
+ tags.unsetTag(urgent, b.id);
54
+ expect([...tags.objects(urgent)]).toEqual([]);
55
+ expect(tags.tagIds()).toEqual([later]);
56
+ });
57
+ });
@@ -0,0 +1,110 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ // @import-as-namespace
6
+
7
+ import * as Schema from 'effect/Schema';
8
+
9
+ import { FormInputAnnotation } from './internal';
10
+ import * as Obj from './Obj';
11
+
12
+ /**
13
+ * An inverse tag index: an in-document `Record<tagId, objectId[]>` mapping a tag id to the ids of
14
+ * the objects carrying that tag.
15
+ *
16
+ * Mutable database objects track their tags in `Obj.getMeta(obj).tags` (an array of tag ids).
17
+ * Immutable feed/queue objects cannot carry their own meta, so this index — managed alongside the
18
+ * feed on its host object — augments that same process for them. Tags are sparse, so keying by tag
19
+ * id (rather than object id) keeps the index small and makes "filter the feed by tag" a single
20
+ * lookup.
21
+ *
22
+ * The tag id is an existing {@link Tag} object's id/URI; labels are never duplicated here — they
23
+ * live on the {@link Tag} object and stay editable in one place.
24
+ */
25
+
26
+ /** Read/write accessor over a host object's tag-index field, bound to a single field key. */
27
+ export interface Accessor {
28
+ /** All tag ids present in the index. */
29
+ tagIds(): string[];
30
+ /** Object ids carrying the given tag; `[]` when the tag is absent (filter the feed by tag). */
31
+ objects(tagId: string): readonly string[];
32
+ /** Inverse lookup: the tag ids applied to the given object id. */
33
+ tags(objectId: string): string[];
34
+ /** Applies a tag to an object (creating the tag entry when absent); idempotent. */
35
+ setTag(tagId: string, objectId: string): void;
36
+ /** Removes a tag from an object, pruning the tag entry when it empties. */
37
+ unsetTag(tagId: string, objectId: string): void;
38
+ }
39
+
40
+ /**
41
+ * Schema fragment for a tag-index field. Splice into a host Struct. Optional record, hidden from
42
+ * forms. Keyed by tag id; the value is the array of object ids carrying that tag.
43
+ */
44
+ export const field = () =>
45
+ Schema.Record({ key: Schema.String, value: Schema.Array(Obj.ID) }).pipe(
46
+ FormInputAnnotation.set(false),
47
+ Schema.optional,
48
+ );
49
+
50
+ /** Binds an {@link Accessor} over `host[key]`; all mutations go through `Obj.update`. */
51
+ export const bind = (host: Obj.Any, key: string): Accessor => {
52
+ type Record_ = Record<string, string[]>;
53
+
54
+ // The field is addressed by a runtime key, so the ECHO object is viewed as a keyed record — a
55
+ // deliberate type-system boundary (dynamic field access — the field name is a parameter).
56
+ const asView = (obj: Obj.Any) => obj as unknown as Record<string, Record_ | undefined>;
57
+ const read = (): Record_ => asView(host)[key] ?? {};
58
+
59
+ // Mutate the live typed record in place (do NOT reassign the whole tree); assigning a detached
60
+ // plain object tree bypasses ECHO's schema-aware conversion and stores arrays as numeric-keyed
61
+ // objects. Mutating the typed proxy (and assigning real arrays to its values) preserves arrays.
62
+ const write = (mutate: (record: Record_) => void): void => {
63
+ Obj.update(host, (host) => {
64
+ const view = asView(host);
65
+ if (view[key] === undefined) {
66
+ view[key] = {};
67
+ }
68
+ // Re-read after assignment: the assignment expression yields the plain RHS, not the
69
+ // ECHO-backed reactive record, so mutating that would not persist.
70
+ mutate(view[key]!);
71
+ });
72
+ };
73
+
74
+ // Existence check via `Object.keys` — ECHO's reactive record proxy auto-vivifies missing keys on
75
+ // direct access, so `record[tagId]` is not reliably `undefined` for absent tags.
76
+ const has = (record: Record_, tagId: string): boolean => Object.keys(record).includes(tagId);
77
+
78
+ return {
79
+ tagIds: () => Object.keys(read()),
80
+ objects: (tagId) => {
81
+ const record = read();
82
+ return has(record, tagId) ? record[tagId] : [];
83
+ },
84
+ tags: (objectId) => {
85
+ const record = read();
86
+ return Object.keys(record).filter((tagId) => record[tagId].includes(objectId));
87
+ },
88
+ setTag: (tagId, objectId) =>
89
+ write((record) => {
90
+ // Use `has` (not `record[tagId]`) for absence — the proxy auto-vivifies missing keys.
91
+ if (!has(record, tagId)) {
92
+ record[tagId] = [objectId];
93
+ } else if (!record[tagId].includes(objectId)) {
94
+ record[tagId] = [...record[tagId], objectId];
95
+ }
96
+ }),
97
+ unsetTag: (tagId, objectId) =>
98
+ write((record) => {
99
+ if (!has(record, tagId)) {
100
+ return;
101
+ }
102
+ const next = record[tagId].filter((existing) => existing !== objectId);
103
+ if (next.length === 0) {
104
+ delete record[tagId];
105
+ } else {
106
+ record[tagId] = next;
107
+ }
108
+ }),
109
+ };
110
+ };
package/src/Tagging.ts ADDED
@@ -0,0 +1,83 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ // @import-as-namespace
6
+
7
+ import { EID, URI } from '@dxos/keys';
8
+
9
+ import type * as Database from './Database';
10
+ import * as Obj from './Obj';
11
+ import * as Ref from './Ref';
12
+ import type * as Tag from './Tag';
13
+ import * as TagIndex from './TagIndex';
14
+
15
+ /**
16
+ * Unified tag handling for objects whether their tags live on the object (mutable database objects)
17
+ * or alongside it (immutable feed/queue objects).
18
+ *
19
+ * - Mutable database objects track their tags in `Obj.getMeta(obj).tags` (an array of `Ref<Tag>`).
20
+ * - Immutable feed objects can't carry their own meta, so their tags live in a {@link TagIndex} on
21
+ * a host object; pass that host via {@link Options.host}.
22
+ *
23
+ * Either way the tag id is an existing {@link Tag} object's id/URI, so the same tag applies across
24
+ * mutable and immutable objects and its label is resolved (and edited) in one place. This API works
25
+ * in terms of those URI tag ids; on the mutable path it converts to/from the stored `Ref<Tag>`.
26
+ */
27
+ export interface Options {
28
+ /** Host object whose tag index holds tags for the (immutable) object. Omit for mutable objects. */
29
+ host?: Obj.Any;
30
+ /** Tag-index field name on the host (default `tags`). */
31
+ key?: string;
32
+ }
33
+
34
+ const DEFAULT_KEY = 'tags';
35
+
36
+ /** Returns the tag ids (URIs) applied to an object. */
37
+ export const get = (object: Obj.Any, options: Options = {}): string[] => {
38
+ const { host, key = DEFAULT_KEY } = options;
39
+ if (host) {
40
+ return TagIndex.bind(host, key).tags(object.id);
41
+ }
42
+ return Obj.getMeta(object).tags.map((tag) => tag.uri);
43
+ };
44
+
45
+ /** Applies a tag (by id/URI) to an object (idempotent). */
46
+ export const set = (object: Obj.Any, tagId: string, options: Options = {}): void => {
47
+ const { host, key = DEFAULT_KEY } = options;
48
+ if (host) {
49
+ TagIndex.bind(host, key).setTag(tagId, object.id);
50
+ return;
51
+ }
52
+ // Stored tag refs are canonical EIDs (read-time upgrade normalizes legacy DXN ids); normalize the
53
+ // incoming id the same way so dedupe compares like-for-like.
54
+ const id = EID.tryParse(tagId) ?? tagId;
55
+ Obj.update(object, (object) => {
56
+ const meta = Obj.getMeta(object);
57
+ if (!meta.tags.some((tag) => tag.uri === id)) {
58
+ meta.tags.push(Ref.fromURI(URI.make(id)));
59
+ }
60
+ });
61
+ };
62
+
63
+ /** Removes a tag (by id/URI) from an object. No-op when not present. */
64
+ export const unset = (object: Obj.Any, tagId: string, options: Options = {}): void => {
65
+ const { host, key = DEFAULT_KEY } = options;
66
+ if (host) {
67
+ TagIndex.bind(host, key).unsetTag(tagId, object.id);
68
+ return;
69
+ }
70
+ const id = EID.tryParse(tagId) ?? tagId;
71
+ Obj.update(object, (object) => {
72
+ const meta = Obj.getMeta(object);
73
+ for (let i = meta.tags.length - 1; i >= 0; i--) {
74
+ if (meta.tags[i].uri === id) {
75
+ meta.tags.splice(i, 1);
76
+ }
77
+ }
78
+ });
79
+ };
80
+
81
+ /** Resolves tag ids (URIs) to {@link Tag} object refs via the database. */
82
+ export const resolve = (db: Pick<Database.Database, 'makeRef'>, tagIds: readonly string[]): Ref.Ref<Tag.Tag>[] =>
83
+ tagIds.map((id) => db.makeRef<Tag.Tag>(URI.make(id)));