@dxos/echo 0.8.4-main.e098934 → 0.8.4-main.e8ec1fe

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 (311) hide show
  1. package/dist/lib/browser/chunk-BIDAASFK.mjs +3727 -0
  2. package/dist/lib/browser/chunk-BIDAASFK.mjs.map +7 -0
  3. package/dist/lib/browser/chunk-ZDLCWGEW.mjs +410 -0
  4. package/dist/lib/browser/chunk-ZDLCWGEW.mjs.map +7 -0
  5. package/dist/lib/browser/chunk-ZFRJKT4A.mjs +585 -0
  6. package/dist/lib/browser/chunk-ZFRJKT4A.mjs.map +7 -0
  7. package/dist/lib/browser/index.mjs +10 -5
  8. package/dist/lib/browser/internal/index.mjs +336 -0
  9. package/dist/lib/browser/internal/index.mjs.map +7 -0
  10. package/dist/lib/browser/meta.json +1 -1
  11. package/dist/lib/browser/query/index.mjs +13 -0
  12. package/dist/lib/browser/query/index.mjs.map +7 -0
  13. package/dist/lib/browser/testing/index.mjs +231 -34
  14. package/dist/lib/browser/testing/index.mjs.map +4 -4
  15. package/dist/lib/node-esm/chunk-3SVRRCUU.mjs +3727 -0
  16. package/dist/lib/node-esm/chunk-3SVRRCUU.mjs.map +7 -0
  17. package/dist/lib/node-esm/chunk-CGDHRZWH.mjs +585 -0
  18. package/dist/lib/node-esm/chunk-CGDHRZWH.mjs.map +7 -0
  19. package/dist/lib/node-esm/chunk-HWS6VBQC.mjs +410 -0
  20. package/dist/lib/node-esm/chunk-HWS6VBQC.mjs.map +7 -0
  21. package/dist/lib/node-esm/index.mjs +10 -5
  22. package/dist/lib/node-esm/internal/index.mjs +336 -0
  23. package/dist/lib/node-esm/internal/index.mjs.map +7 -0
  24. package/dist/lib/node-esm/meta.json +1 -1
  25. package/dist/lib/node-esm/query/index.mjs +13 -0
  26. package/dist/lib/node-esm/query/index.mjs.map +7 -0
  27. package/dist/lib/node-esm/testing/index.mjs +231 -34
  28. package/dist/lib/node-esm/testing/index.mjs.map +4 -4
  29. package/dist/types/src/Obj.d.ts +26 -7
  30. package/dist/types/src/Obj.d.ts.map +1 -1
  31. package/dist/types/src/Ref.d.ts +1 -1
  32. package/dist/types/src/Ref.d.ts.map +1 -1
  33. package/dist/types/src/Relation.d.ts +5 -4
  34. package/dist/types/src/Relation.d.ts.map +1 -1
  35. package/dist/types/src/Tag.d.ts +17 -0
  36. package/dist/types/src/Tag.d.ts.map +1 -0
  37. package/dist/types/src/Type.d.ts +16 -17
  38. package/dist/types/src/Type.d.ts.map +1 -1
  39. package/dist/types/src/errors.d.ts +14 -18
  40. package/dist/types/src/errors.d.ts.map +1 -1
  41. package/dist/types/src/index.d.ts +3 -2
  42. package/dist/types/src/index.d.ts.map +1 -1
  43. package/dist/types/src/internal/ast/annotation-helper.d.ts +8 -0
  44. package/dist/types/src/internal/ast/annotation-helper.d.ts.map +1 -0
  45. package/dist/types/src/internal/ast/annotations.d.ts +131 -0
  46. package/dist/types/src/internal/ast/annotations.d.ts.map +1 -0
  47. package/dist/types/src/internal/ast/annotations.test.d.ts +2 -0
  48. package/dist/types/src/internal/ast/annotations.test.d.ts.map +1 -0
  49. package/dist/types/src/internal/ast/entity-kind.d.ts +10 -0
  50. package/dist/types/src/internal/ast/entity-kind.d.ts.map +1 -0
  51. package/dist/types/src/internal/ast/index.d.ts +5 -0
  52. package/dist/types/src/internal/ast/index.d.ts.map +1 -0
  53. package/dist/types/src/internal/ast/types.d.ts +6 -0
  54. package/dist/types/src/internal/ast/types.d.ts.map +1 -0
  55. package/dist/types/src/internal/formats/date.d.ts +63 -0
  56. package/dist/types/src/internal/formats/date.d.ts.map +1 -0
  57. package/dist/types/src/internal/formats/date.test.d.ts +2 -0
  58. package/dist/types/src/internal/formats/date.test.d.ts.map +1 -0
  59. package/dist/types/src/internal/formats/format.d.ts +30 -0
  60. package/dist/types/src/internal/formats/format.d.ts.map +1 -0
  61. package/dist/types/src/internal/formats/format.test.d.ts +2 -0
  62. package/dist/types/src/internal/formats/format.test.d.ts.map +1 -0
  63. package/dist/types/src/internal/formats/index.d.ts +8 -0
  64. package/dist/types/src/internal/formats/index.d.ts.map +1 -0
  65. package/dist/types/src/internal/formats/number.d.ts +31 -0
  66. package/dist/types/src/internal/formats/number.d.ts.map +1 -0
  67. package/dist/types/src/internal/formats/object.d.ts +35 -0
  68. package/dist/types/src/internal/formats/object.d.ts.map +1 -0
  69. package/dist/types/src/internal/formats/select.d.ts +11 -0
  70. package/dist/types/src/internal/formats/select.d.ts.map +1 -0
  71. package/dist/types/src/internal/formats/string.d.ts +38 -0
  72. package/dist/types/src/internal/formats/string.d.ts.map +1 -0
  73. package/dist/types/src/internal/formats/types.d.ts +68 -0
  74. package/dist/types/src/internal/formats/types.d.ts.map +1 -0
  75. package/dist/types/src/internal/index.d.ts +15 -0
  76. package/dist/types/src/internal/index.d.ts.map +1 -0
  77. package/dist/types/src/internal/json/annotations.d.ts +19 -0
  78. package/dist/types/src/internal/json/annotations.d.ts.map +1 -0
  79. package/dist/types/src/internal/json/effect-schema.test.d.ts +2 -0
  80. package/dist/types/src/internal/json/effect-schema.test.d.ts.map +1 -0
  81. package/dist/types/src/internal/json/index.d.ts +2 -0
  82. package/dist/types/src/internal/json/index.d.ts.map +1 -0
  83. package/dist/types/src/internal/json/json-schema.d.ts +28 -0
  84. package/dist/types/src/internal/json/json-schema.d.ts.map +1 -0
  85. package/dist/types/src/internal/json/json-schema.test.d.ts +2 -0
  86. package/dist/types/src/internal/json/json-schema.test.d.ts.map +1 -0
  87. package/dist/types/src/internal/json-schema/index.d.ts +3 -0
  88. package/dist/types/src/internal/json-schema/index.d.ts.map +1 -0
  89. package/dist/types/src/internal/json-schema/json-schema-normalize.d.ts +7 -0
  90. package/dist/types/src/internal/json-schema/json-schema-normalize.d.ts.map +1 -0
  91. package/dist/types/src/internal/json-schema/json-schema-type.d.ts +250 -0
  92. package/dist/types/src/internal/json-schema/json-schema-type.d.ts.map +1 -0
  93. package/dist/types/src/internal/object/accessors.d.ts +37 -0
  94. package/dist/types/src/internal/object/accessors.d.ts.map +1 -0
  95. package/dist/types/src/internal/object/common.d.ts +18 -0
  96. package/dist/types/src/internal/object/common.d.ts.map +1 -0
  97. package/dist/types/src/internal/object/create.d.ts +40 -0
  98. package/dist/types/src/internal/object/create.d.ts.map +1 -0
  99. package/dist/types/src/internal/object/create.test.d.ts +2 -0
  100. package/dist/types/src/internal/object/create.test.d.ts.map +1 -0
  101. package/dist/types/src/internal/object/deleted.d.ts +6 -0
  102. package/dist/types/src/internal/object/deleted.d.ts.map +1 -0
  103. package/dist/types/src/internal/object/entity.d.ts +33 -0
  104. package/dist/types/src/internal/object/entity.d.ts.map +1 -0
  105. package/dist/types/src/internal/object/expando.d.ts +14 -0
  106. package/dist/types/src/internal/object/expando.d.ts.map +1 -0
  107. package/dist/types/src/internal/object/ids.d.ts +6 -0
  108. package/dist/types/src/internal/object/ids.d.ts.map +1 -0
  109. package/dist/types/src/internal/object/index.d.ts +16 -0
  110. package/dist/types/src/internal/object/index.d.ts.map +1 -0
  111. package/dist/types/src/internal/object/inspect.d.ts +2 -0
  112. package/dist/types/src/internal/object/inspect.d.ts.map +1 -0
  113. package/dist/types/src/internal/object/json-serializer.d.ts +32 -0
  114. package/dist/types/src/internal/object/json-serializer.d.ts.map +1 -0
  115. package/dist/types/src/internal/object/json-serializer.test.d.ts +2 -0
  116. package/dist/types/src/internal/object/json-serializer.test.d.ts.map +1 -0
  117. package/dist/types/src/internal/object/meta.d.ts +31 -0
  118. package/dist/types/src/internal/object/meta.d.ts.map +1 -0
  119. package/dist/types/src/internal/object/model.d.ts +117 -0
  120. package/dist/types/src/internal/object/model.d.ts.map +1 -0
  121. package/dist/types/src/internal/object/relation.d.ts +17 -0
  122. package/dist/types/src/internal/object/relation.d.ts.map +1 -0
  123. package/dist/types/src/internal/object/schema-validator.d.ts +15 -0
  124. package/dist/types/src/internal/object/schema-validator.d.ts.map +1 -0
  125. package/dist/types/src/internal/object/schema-validator.test.d.ts +2 -0
  126. package/dist/types/src/internal/object/schema-validator.test.d.ts.map +1 -0
  127. package/dist/types/src/internal/object/typed-object.d.ts +31 -0
  128. package/dist/types/src/internal/object/typed-object.d.ts.map +1 -0
  129. package/dist/types/src/internal/object/typed-object.test.d.ts +2 -0
  130. package/dist/types/src/internal/object/typed-object.test.d.ts.map +1 -0
  131. package/dist/types/src/internal/object/typename.d.ts +15 -0
  132. package/dist/types/src/internal/object/typename.d.ts.map +1 -0
  133. package/dist/types/src/internal/object/version.d.ts +14 -0
  134. package/dist/types/src/internal/object/version.d.ts.map +1 -0
  135. package/dist/types/src/internal/projection/compose.d.ts +6 -0
  136. package/dist/types/src/internal/projection/compose.d.ts.map +1 -0
  137. package/dist/types/src/internal/projection/compose.test.d.ts +2 -0
  138. package/dist/types/src/internal/projection/compose.test.d.ts.map +1 -0
  139. package/dist/types/src/internal/projection/index.d.ts +2 -0
  140. package/dist/types/src/internal/projection/index.d.ts.map +1 -0
  141. package/dist/types/src/internal/proxy/handler.test.d.ts +2 -0
  142. package/dist/types/src/internal/proxy/handler.test.d.ts.map +1 -0
  143. package/dist/types/src/internal/proxy/reactive-object.d.ts +15 -0
  144. package/dist/types/src/internal/proxy/reactive-object.d.ts.map +1 -0
  145. package/dist/types/src/internal/proxy/schema.test.d.ts +2 -0
  146. package/dist/types/src/internal/proxy/schema.test.d.ts.map +1 -0
  147. package/dist/types/src/internal/proxy/typed-handler.d.ts +44 -0
  148. package/dist/types/src/internal/proxy/typed-handler.d.ts.map +1 -0
  149. package/dist/types/src/internal/proxy/typed-handler.test.d.ts +2 -0
  150. package/dist/types/src/internal/proxy/typed-handler.test.d.ts.map +1 -0
  151. package/dist/types/src/internal/proxy/typed-object.test.d.ts +2 -0
  152. package/dist/types/src/internal/proxy/typed-object.test.d.ts.map +1 -0
  153. package/dist/types/src/internal/query/index.d.ts +2 -0
  154. package/dist/types/src/internal/query/index.d.ts.map +1 -0
  155. package/dist/types/src/internal/query/query.d.ts +17 -0
  156. package/dist/types/src/internal/query/query.d.ts.map +1 -0
  157. package/dist/types/src/internal/ref/index.d.ts +3 -0
  158. package/dist/types/src/internal/ref/index.d.ts.map +1 -0
  159. package/dist/types/src/internal/ref/ref-array.d.ts +21 -0
  160. package/dist/types/src/internal/ref/ref-array.d.ts.map +1 -0
  161. package/dist/types/src/internal/ref/ref.d.ts +206 -0
  162. package/dist/types/src/internal/ref/ref.d.ts.map +1 -0
  163. package/dist/types/src/internal/ref/ref.test.d.ts +2 -0
  164. package/dist/types/src/internal/ref/ref.test.d.ts.map +1 -0
  165. package/dist/types/src/internal/schema/echo-schema.d.ts +168 -0
  166. package/dist/types/src/internal/schema/echo-schema.d.ts.map +1 -0
  167. package/dist/types/src/internal/schema/index.d.ts +7 -0
  168. package/dist/types/src/internal/schema/index.d.ts.map +1 -0
  169. package/dist/types/src/internal/schema/manipulation.d.ts +10 -0
  170. package/dist/types/src/internal/schema/manipulation.d.ts.map +1 -0
  171. package/dist/types/src/internal/schema/runtime-schema-registry.d.ts +18 -0
  172. package/dist/types/src/internal/schema/runtime-schema-registry.d.ts.map +1 -0
  173. package/dist/types/src/internal/schema/snapshot.d.ts +6 -0
  174. package/dist/types/src/internal/schema/snapshot.d.ts.map +1 -0
  175. package/dist/types/src/internal/schema/stored-schema.d.ts +13 -0
  176. package/dist/types/src/internal/schema/stored-schema.d.ts.map +1 -0
  177. package/dist/types/src/internal/testing/index.d.ts +3 -0
  178. package/dist/types/src/internal/testing/index.d.ts.map +1 -0
  179. package/dist/types/src/internal/testing/types.d.ts +381 -0
  180. package/dist/types/src/internal/testing/types.d.ts.map +1 -0
  181. package/dist/types/src/internal/testing/utils.d.ts +10 -0
  182. package/dist/types/src/internal/testing/utils.d.ts.map +1 -0
  183. package/dist/types/src/internal/types/index.d.ts +3 -0
  184. package/dist/types/src/internal/types/index.d.ts.map +1 -0
  185. package/dist/types/src/internal/types/types.d.ts +79 -0
  186. package/dist/types/src/internal/types/types.d.ts.map +1 -0
  187. package/dist/types/src/internal/types/types.test.d.ts +2 -0
  188. package/dist/types/src/internal/types/types.test.d.ts.map +1 -0
  189. package/dist/types/src/internal/types/util.d.ts +5 -0
  190. package/dist/types/src/internal/types/util.d.ts.map +1 -0
  191. package/dist/types/src/query/index.d.ts +1 -1
  192. package/dist/types/src/query/index.d.ts.map +1 -1
  193. package/dist/types/src/query/{dsl.d.ts → query.d.ts} +28 -23
  194. package/dist/types/src/query/query.d.ts.map +1 -0
  195. package/dist/types/src/query/query.test.d.ts +2 -0
  196. package/dist/types/src/query/query.test.d.ts.map +1 -0
  197. package/dist/types/src/testing/echo-schema.d.ts +7 -0
  198. package/dist/types/src/testing/echo-schema.d.ts.map +1 -0
  199. package/dist/types/src/testing/index.d.ts +2 -0
  200. package/dist/types/src/testing/index.d.ts.map +1 -1
  201. package/dist/types/src/testing/types.d.ts +170 -55
  202. package/dist/types/src/testing/types.d.ts.map +1 -1
  203. package/dist/types/tsconfig.tsbuildinfo +1 -1
  204. package/package.json +29 -18
  205. package/src/Obj.ts +98 -14
  206. package/src/Ref.ts +1 -2
  207. package/src/Relation.ts +15 -5
  208. package/src/Tag.ts +39 -0
  209. package/src/Type.ts +29 -30
  210. package/src/index.ts +3 -2
  211. package/src/internal/ast/annotation-helper.ts +22 -0
  212. package/src/internal/ast/annotations.test.ts +98 -0
  213. package/src/internal/ast/annotations.ts +226 -0
  214. package/src/internal/ast/entity-kind.ts +15 -0
  215. package/src/internal/ast/index.ts +8 -0
  216. package/src/internal/ast/types.ts +17 -0
  217. package/src/internal/formats/date.test.ts +56 -0
  218. package/src/internal/formats/date.ts +217 -0
  219. package/src/internal/formats/format.test.ts +77 -0
  220. package/src/internal/formats/format.ts +52 -0
  221. package/src/internal/formats/index.ts +12 -0
  222. package/src/internal/formats/number.ts +89 -0
  223. package/src/internal/formats/object.ts +80 -0
  224. package/src/internal/formats/select.ts +16 -0
  225. package/src/internal/formats/string.ts +76 -0
  226. package/src/internal/formats/types.ts +175 -0
  227. package/src/internal/index.ts +22 -0
  228. package/src/internal/json/annotations.ts +50 -0
  229. package/src/internal/json/effect-schema.test.ts +143 -0
  230. package/src/internal/json/index.ts +5 -0
  231. package/src/internal/json/json-schema.test.ts +849 -0
  232. package/src/internal/json/json-schema.ts +519 -0
  233. package/src/internal/json-schema/index.ts +6 -0
  234. package/src/internal/json-schema/json-schema-normalize.ts +109 -0
  235. package/src/internal/json-schema/json-schema-type.ts +403 -0
  236. package/src/internal/object/accessors.ts +153 -0
  237. package/src/internal/object/common.ts +76 -0
  238. package/src/internal/object/create.test.ts +118 -0
  239. package/src/internal/object/create.ts +96 -0
  240. package/src/internal/object/deleted.ts +19 -0
  241. package/src/internal/object/entity.ts +248 -0
  242. package/src/internal/object/expando.ts +21 -0
  243. package/src/internal/object/ids.ts +12 -0
  244. package/src/internal/object/index.ts +19 -0
  245. package/src/internal/object/inspect.ts +48 -0
  246. package/src/internal/object/json-serializer.test.ts +99 -0
  247. package/src/internal/object/json-serializer.ts +225 -0
  248. package/src/internal/object/meta.ts +61 -0
  249. package/src/internal/object/model.ts +170 -0
  250. package/src/internal/object/relation.ts +24 -0
  251. package/src/internal/object/schema-validator.test.ts +186 -0
  252. package/src/internal/object/schema-validator.ts +241 -0
  253. package/src/internal/object/typed-object.test.ts +34 -0
  254. package/src/internal/object/typed-object.ts +88 -0
  255. package/src/internal/object/typename.ts +61 -0
  256. package/src/internal/object/version.ts +22 -0
  257. package/src/internal/projection/compose.test.ts +43 -0
  258. package/src/internal/projection/compose.ts +36 -0
  259. package/src/internal/projection/index.ts +5 -0
  260. package/src/internal/proxy/handler.test.ts +163 -0
  261. package/src/internal/proxy/reactive-object.ts +108 -0
  262. package/src/internal/proxy/schema.test.ts +136 -0
  263. package/src/internal/proxy/typed-handler.test.ts +102 -0
  264. package/src/internal/proxy/typed-handler.ts +228 -0
  265. package/src/internal/proxy/typed-object.test.ts +100 -0
  266. package/src/internal/query/index.ts +5 -0
  267. package/src/internal/query/query.ts +23 -0
  268. package/src/internal/ref/index.ts +6 -0
  269. package/src/internal/ref/ref-array.ts +39 -0
  270. package/src/internal/ref/ref.test.ts +100 -0
  271. package/src/internal/ref/ref.ts +521 -0
  272. package/src/internal/schema/echo-schema.ts +383 -0
  273. package/src/internal/schema/index.ts +10 -0
  274. package/src/internal/schema/manipulation.ts +92 -0
  275. package/src/internal/schema/runtime-schema-registry.ts +78 -0
  276. package/src/internal/schema/snapshot.ts +25 -0
  277. package/src/internal/schema/stored-schema.ts +26 -0
  278. package/src/internal/testing/index.ts +6 -0
  279. package/src/internal/testing/types.ts +144 -0
  280. package/src/internal/testing/utils.ts +54 -0
  281. package/src/internal/types/index.ts +6 -0
  282. package/src/internal/types/types.test.ts +48 -0
  283. package/src/internal/types/types.ts +176 -0
  284. package/src/internal/types/util.ts +9 -0
  285. package/src/query/index.ts +2 -1
  286. package/src/query/query.test.ts +401 -0
  287. package/src/query/{dsl.ts → query.ts} +82 -45
  288. package/src/test/api.test.ts +9 -9
  289. package/src/testing/echo-schema.ts +39 -0
  290. package/src/testing/index.ts +2 -0
  291. package/src/testing/types.ts +40 -23
  292. package/dist/lib/browser/chunk-EIXXFUN5.mjs +0 -839
  293. package/dist/lib/browser/chunk-EIXXFUN5.mjs.map +0 -7
  294. package/dist/lib/node-esm/chunk-TCY7IVTS.mjs +0 -839
  295. package/dist/lib/node-esm/chunk-TCY7IVTS.mjs.map +0 -7
  296. package/dist/types/src/experimental/database.d.ts +0 -8
  297. package/dist/types/src/experimental/database.d.ts.map +0 -1
  298. package/dist/types/src/experimental/index.d.ts +0 -1
  299. package/dist/types/src/experimental/index.d.ts.map +0 -1
  300. package/dist/types/src/experimental/queue.d.ts +0 -8
  301. package/dist/types/src/experimental/queue.d.ts.map +0 -1
  302. package/dist/types/src/experimental/space.d.ts +0 -8
  303. package/dist/types/src/experimental/space.d.ts.map +0 -1
  304. package/dist/types/src/query/dsl.d.ts.map +0 -1
  305. package/dist/types/src/query/dsl.test.d.ts +0 -2
  306. package/dist/types/src/query/dsl.test.d.ts.map +0 -1
  307. package/src/experimental/database.ts +0 -11
  308. package/src/experimental/index.ts +0 -7
  309. package/src/experimental/queue.ts +0 -11
  310. package/src/experimental/space.ts +0 -11
  311. package/src/query/dsl.test.ts +0 -362
@@ -0,0 +1,23 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import * as Schema from 'effect/Schema';
6
+
7
+ // TODO(wittjosiah): Remove. Use ordering in queries instead.
8
+
9
+ /** @deprecated */
10
+ const SortDirection = Schema.Union(Schema.Literal('asc'), Schema.Literal('desc'));
11
+ /** @deprecated */
12
+ export type SortDirectionType = Schema.Schema.Type<typeof SortDirection>;
13
+
14
+ /** @deprecated */
15
+ const FieldSort = Schema.Struct({
16
+ fieldId: Schema.String,
17
+ direction: SortDirection,
18
+ }).pipe(Schema.mutable);
19
+
20
+ /** @deprecated */
21
+ export interface FieldSortType extends Schema.Schema.Type<typeof FieldSort> {}
22
+ /** @deprecated */
23
+ export const FieldSortType: Schema.Schema<FieldSortType> = FieldSort;
@@ -0,0 +1,6 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ export * from './ref';
6
+ export * from './ref-array';
@@ -0,0 +1,39 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { type ObjectId } from '@dxos/keys';
6
+ import { isNonNullable } from '@dxos/util';
7
+
8
+ import { type AnyEchoObject } from '../types';
9
+
10
+ import { Ref } from './ref';
11
+
12
+ /**
13
+ * Helper functions for working with arrays of refs.
14
+ */
15
+ export const RefArray = Object.freeze({
16
+ /**
17
+ * @returns all resolved targets.
18
+ */
19
+ targets: <T extends AnyEchoObject>(refs: readonly Ref<T>[]): T[] => {
20
+ return refs.map((ref) => ref.target).filter(isNonNullable);
21
+ },
22
+
23
+ /**
24
+ * Load all referenced objects.
25
+ */
26
+ loadAll: <T extends AnyEchoObject>(refs: readonly Ref<T>[]): Promise<T[]> => {
27
+ return Promise.all(refs.map((ref) => ref.load()));
28
+ },
29
+
30
+ /**
31
+ * Removes the ref with the given id.
32
+ */
33
+ removeById: (refs: Ref<AnyEchoObject>[], id: ObjectId) => {
34
+ const index = refs.findIndex(Ref.hasObjectId(id));
35
+ if (index >= 0) {
36
+ refs.splice(index, 1);
37
+ }
38
+ },
39
+ });
@@ -0,0 +1,100 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import * as Schema from 'effect/Schema';
6
+ import { describe, expect, test } from 'vitest';
7
+
8
+ import { DXN, ObjectId } from '@dxos/keys';
9
+
10
+ import { EchoObject, create, getObjectDXN } from '../object';
11
+
12
+ import { Ref, getReferenceAst } from './ref';
13
+
14
+ const Task = Schema.Struct({
15
+ title: Schema.optional(Schema.String),
16
+ }).pipe(
17
+ EchoObject({
18
+ typename: 'example.com/type/Task',
19
+ version: '0.1.0',
20
+ }),
21
+ );
22
+
23
+ type Task = Schema.Schema.Type<typeof Task>;
24
+
25
+ const Contact = Schema.Struct({
26
+ name: Schema.String,
27
+ email: Schema.optional(Schema.String),
28
+ tasks: Schema.mutable(Schema.Array(Ref(Task))),
29
+ }).pipe(
30
+ EchoObject({
31
+ typename: 'example.com/type/Person',
32
+ version: '0.1.0',
33
+ }),
34
+ );
35
+
36
+ type Contact = Schema.Schema.Type<typeof Contact>;
37
+
38
+ describe('Ref', () => {
39
+ test('Schema is', () => {
40
+ Ref(Contact).pipe(Schema.is)(Ref.fromDXN(DXN.parse(`dxn:echo:@:${ObjectId.random()}`)));
41
+ });
42
+
43
+ test('ref ast', () => {
44
+ const ref = Ref(Task);
45
+ expect(ref.ast._tag).toEqual('Declaration');
46
+ const refAst = getReferenceAst(ref.ast);
47
+ expect(refAst).toEqual({ typename: Task.typename, version: Task.version });
48
+ });
49
+
50
+ // TODO(dmaretskyi): Figure out how to expose this in the API.
51
+ test.skip('encode with inlined target', () => {
52
+ const task = create(Task, { title: 'Fix bugs' });
53
+ const contact = create(Contact, { name: 'John Doe', tasks: [Ref.make(task)] });
54
+
55
+ const json = JSON.parse(JSON.stringify(contact));
56
+ expect(json).toEqual({
57
+ id: contact.id,
58
+ '@type': `dxn:type:${Contact.typename}:${Contact.version}`,
59
+ '@meta': {
60
+ keys: [],
61
+ },
62
+ name: 'John Doe',
63
+ tasks: [
64
+ {
65
+ '/': getObjectDXN(task as any)!.toString(),
66
+ target: JSON.parse(JSON.stringify(task)),
67
+ },
68
+ ],
69
+ });
70
+ });
71
+
72
+ test('encode without inlining target', () => {
73
+ const task = create(Task, { title: 'Fix bugs' });
74
+ const contact = create(Contact, { name: 'John Doe', tasks: [Ref.make(task).noInline()] });
75
+
76
+ const json = JSON.parse(JSON.stringify(contact));
77
+ expect(json).toEqual({
78
+ id: contact.id,
79
+ '@type': `dxn:type:${Contact.typename}:${Contact.version}`,
80
+ '@meta': {
81
+ keys: [],
82
+ },
83
+ name: 'John Doe',
84
+ tasks: [{ '/': getObjectDXN(task)!.toString() }],
85
+ });
86
+ });
87
+
88
+ test('decode object', () => {
89
+ const id = ObjectId.random();
90
+ const contactData = {
91
+ id: ObjectId.random(),
92
+ name: 'John Doe',
93
+ tasks: [{ '/': `dxn:echo:@:${id}` }],
94
+ };
95
+
96
+ const contact = Contact.pipe(Schema.decodeUnknownSync)(contactData);
97
+ expect(Ref.isRef(contact.tasks[0])).toEqual(true);
98
+ expect(contact.tasks[0].dxn.toString()).toEqual(`dxn:echo:@:${id}`);
99
+ });
100
+ });
@@ -0,0 +1,521 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import * as Effect from 'effect/Effect';
6
+ import * as Option from 'effect/Option';
7
+ import * as ParseResult from 'effect/ParseResult';
8
+ import * as Schema from 'effect/Schema';
9
+ import * as SchemaAST from 'effect/SchemaAST';
10
+
11
+ import { type EncodedReference, Reference } from '@dxos/echo-protocol';
12
+ import { compositeRuntime } from '@dxos/echo-signals/runtime';
13
+ import { assertArgument, invariant } from '@dxos/invariant';
14
+ import { DXN, ObjectId } from '@dxos/keys';
15
+
16
+ import { ReferenceAnnotationId, getSchemaDXN, getTypeAnnotation, getTypeIdentifierAnnotation } from '../ast';
17
+ import { type JsonSchemaType } from '../json-schema';
18
+ import type { BaseObject, WithId } from '../types';
19
+
20
+ /**
21
+ * The `$id` and `$ref` fields for an ECHO reference schema.
22
+ */
23
+ export const JSON_SCHEMA_ECHO_REF_ID = '/schemas/echo/ref';
24
+
25
+ // TODO(burdon): Define return type.
26
+ export const getSchemaReference = (property: JsonSchemaType): { typename: string } | undefined => {
27
+ const { $id, reference: { schema: { $ref } = {} } = {} } = property;
28
+ if ($id === JSON_SCHEMA_ECHO_REF_ID && $ref) {
29
+ return { typename: DXN.parse($ref).typename };
30
+ }
31
+ };
32
+
33
+ export const createSchemaReference = (typename: string): JsonSchemaType => {
34
+ return {
35
+ $id: JSON_SCHEMA_ECHO_REF_ID,
36
+ reference: {
37
+ schema: {
38
+ $ref: DXN.fromTypename(typename).toString(),
39
+ },
40
+ },
41
+ };
42
+ };
43
+
44
+ /**
45
+ * Runtime type-info for a reference extracted from effect AST.
46
+ */
47
+ export type RefereneAST = {
48
+ /**
49
+ * Typename of linked schema.
50
+ */
51
+ typename: string;
52
+
53
+ /**
54
+ * Version of linked schema.
55
+ */
56
+ version: string;
57
+ };
58
+
59
+ export const getReferenceAst = (ast: SchemaAST.AST): RefereneAST | undefined => {
60
+ if (ast._tag !== 'Declaration' || !ast.annotations[ReferenceAnnotationId]) {
61
+ return undefined;
62
+ }
63
+ return {
64
+ typename: (ast.annotations[ReferenceAnnotationId] as any).typename,
65
+ version: (ast.annotations[ReferenceAnnotationId] as any).version,
66
+ };
67
+ };
68
+
69
+ export const RefTypeId: unique symbol = Symbol('@dxos/echo/internal/Ref');
70
+
71
+ /**
72
+ * Reference Schema.
73
+ */
74
+ export interface Ref$<T extends WithId> extends Schema.SchemaClass<Ref<T>, EncodedReference> {}
75
+
76
+ // Type of the `Ref` function and extra methods attached to it.
77
+ export interface RefFn {
78
+ <S extends Schema.Schema.Any>(schema: S): Ref$<Schema.Schema.Type<S>>;
79
+
80
+ /**
81
+ * @returns True if the object is a reference.
82
+ */
83
+ isRef: (obj: any) => obj is Ref<any>;
84
+
85
+ /**
86
+ * @returns True if the reference points to the given object id.
87
+ */
88
+ hasObjectId: (id: ObjectId) => (ref: Ref<any>) => boolean;
89
+
90
+ /**
91
+ * @returns True if the schema is a reference schema.
92
+ */
93
+ isRefSchema: (schema: Schema.Schema<any, any>) => schema is Ref$<any>;
94
+
95
+ /**
96
+ * @returns True if the schema AST is a reference schema.
97
+ */
98
+ isRefSchemaAST: (ast: SchemaAST.AST) => boolean;
99
+
100
+ /**
101
+ * Constructs a reference that points to the given object.
102
+ */
103
+ // TODO(burdon): Tighten type of T?
104
+ make: <T extends WithId>(object: T) => Ref<T>;
105
+
106
+ /**
107
+ * Constructs a reference that points to the object specified by the provided DXN.
108
+ */
109
+ fromDXN: (dxn: DXN) => Ref<any>;
110
+ }
111
+ /**
112
+ * Schema builder for references.
113
+ */
114
+ export const Ref: RefFn = <S extends Schema.Schema.Any>(schema: S): Ref$<Schema.Schema.Type<S>> => {
115
+ assertArgument(Schema.isSchema(schema), 'schema', 'Must call with an instance of effect-schema');
116
+
117
+ const annotation = getTypeAnnotation(schema);
118
+ if (annotation == null) {
119
+ throw new Error('Reference target must be an ECHO schema.');
120
+ }
121
+
122
+ return createEchoReferenceSchema(
123
+ getTypeIdentifierAnnotation(schema),
124
+ annotation.typename,
125
+ annotation.version,
126
+ getSchemaExpectedName(schema.ast),
127
+ );
128
+ };
129
+
130
+ /**
131
+ * Represents materialized reference to a target.
132
+ * This is the data type for the fields marked as ref.
133
+ */
134
+ export interface Ref<T> {
135
+ /**
136
+ * Target object DXN.
137
+ */
138
+ get dxn(): DXN;
139
+
140
+ /**
141
+ * @returns The reference target.
142
+ * May return `undefined` if the object is not loaded in the working set.
143
+ * Accessing this property, even if it returns `undefined` will trigger the object to be loaded to the working set.
144
+ *
145
+ * @reactive Supports signal subscriptions.
146
+ */
147
+ get target(): T | undefined;
148
+
149
+ /**
150
+ * @returns Promise that will resolves with the target object.
151
+ * Will load the object from disk if it is not present in the working set.
152
+ * @throws If the object is not available locally.
153
+ */
154
+ load(): Promise<T>;
155
+
156
+ /**
157
+ * @returns Promise that will resolves with the target object or undefined if the object is not loaded locally.
158
+ */
159
+ tryLoad(): Promise<T | undefined>;
160
+
161
+ /**
162
+ * Do not inline the target object in the reference.
163
+ * Makes .target unavailable unless the reference is connected to a database context.
164
+ *
165
+ * When serialized with toJSON, the difference is between:
166
+ * `{ "/": "dxn:..." }`
167
+ * and
168
+ * `{ "/": "dxn:...", "target": { ... } }`
169
+ *
170
+ * Clones the reference object.
171
+ */
172
+ noInline(): Ref<T>;
173
+
174
+ /**
175
+ * Serializes the reference to a JSON object.
176
+ * The serialization format is compatible with the IPLD-style encoded references.
177
+ * When a reference has a saved target (i.e. the target or object holding the reference is not in the database),
178
+ * the target is included in the serialized object.
179
+ *
180
+ * Examples:
181
+ * `{ "/": "dxn:..." }`
182
+ * `{ "/": "dxn:...", "target": { ... } }`
183
+ */
184
+ encode(): EncodedReference;
185
+
186
+ [RefTypeId]: {
187
+ _T: T;
188
+ };
189
+ }
190
+
191
+ export declare namespace Ref {
192
+ /**
193
+ * Target of the reference.
194
+ */
195
+ export type Target<R> = R extends Ref<infer U> ? U : never;
196
+ }
197
+
198
+ Ref.isRef = (obj: any): obj is Ref<any> => {
199
+ return obj && typeof obj === 'object' && RefTypeId in obj;
200
+ };
201
+
202
+ Ref.hasObjectId = (id: ObjectId) => (ref: Ref<any>) => ref.dxn.isLocalObjectId() && ref.dxn.parts[1] === id;
203
+
204
+ Ref.isRefSchema = (schema: Schema.Schema<any, any>): schema is Ref$<any> => {
205
+ return Ref.isRefSchemaAST(schema.ast);
206
+ };
207
+
208
+ Ref.isRefSchemaAST = (ast: SchemaAST.AST): boolean => {
209
+ return SchemaAST.getAnnotation(ast, ReferenceAnnotationId).pipe(Option.isSome);
210
+ };
211
+
212
+ Ref.make = <T extends BaseObject>(obj: T): Ref<T> => {
213
+ if (typeof obj !== 'object' || obj === null) {
214
+ throw new TypeError('Expected: ECHO object.');
215
+ }
216
+
217
+ // TODO(dmaretskyi): Extract to `getObjectDXN` function.
218
+ const id = obj.id;
219
+ invariant(ObjectId.isValid(id), 'Invalid object ID');
220
+ const dxn = Reference.localObjectReference(id).toDXN();
221
+ return new RefImpl(dxn, obj);
222
+ };
223
+
224
+ Ref.fromDXN = (dxn: DXN): Ref<any> => {
225
+ assertArgument(dxn instanceof DXN, 'dxn', 'Expected DXN');
226
+ return new RefImpl(dxn);
227
+ };
228
+
229
+ /**
230
+ * `reference` field on the schema object.
231
+ */
232
+ export type JsonSchemaReferenceInfo = {
233
+ schema: { $ref: string };
234
+ schemaVersion?: string;
235
+ };
236
+
237
+ /**
238
+ * @internal
239
+ */
240
+ // TODO(burdon): Move to json schema and make private?
241
+ export const createEchoReferenceSchema = (
242
+ echoId: string | undefined,
243
+ typename: string | undefined,
244
+ version: string | undefined,
245
+ schemaName?: string,
246
+ ): Schema.SchemaClass<Ref<any>, EncodedReference> => {
247
+ if (!echoId && !typename) {
248
+ throw new TypeError('Either echoId or typename must be provided.');
249
+ }
250
+
251
+ const referenceInfo: JsonSchemaReferenceInfo = {
252
+ schema: {
253
+ // TODO(dmaretskyi): Include version?
254
+ $ref: echoId ?? DXN.fromTypename(typename!).toString(),
255
+ },
256
+ schemaVersion: version,
257
+ };
258
+
259
+ // TODO(dmaretskyi): Add name and description.
260
+ const refSchema = Schema.declare<Ref<any>, EncodedReference, []>(
261
+ [],
262
+ {
263
+ encode: () => {
264
+ return (value) => {
265
+ return Effect.succeed({
266
+ '/': (value as Ref<any>).dxn.toString(),
267
+ });
268
+ };
269
+ },
270
+ decode: () => {
271
+ return (value) => {
272
+ // TODO(dmaretskyi): This branch seems to be taken by Schema.is
273
+ if (Ref.isRef(value)) {
274
+ return Effect.succeed(value);
275
+ }
276
+
277
+ if (typeof value !== 'object' || value == null || typeof (value as any)['/'] !== 'string') {
278
+ return Effect.fail(new ParseResult.Unexpected(value, 'reference'));
279
+ }
280
+
281
+ return Effect.succeed(Ref.fromDXN(DXN.parse((value as any)['/'])));
282
+ };
283
+ },
284
+ },
285
+ {
286
+ jsonSchema: {
287
+ // TODO(dmaretskyi): We should remove `$id` and keep `$ref` with a fully qualified name.
288
+ $id: JSON_SCHEMA_ECHO_REF_ID,
289
+ $ref: JSON_SCHEMA_ECHO_REF_ID,
290
+ reference: referenceInfo,
291
+ },
292
+ [ReferenceAnnotationId]: {
293
+ typename: typename ?? '',
294
+ version,
295
+ },
296
+ },
297
+ );
298
+
299
+ return refSchema;
300
+ };
301
+
302
+ const getSchemaExpectedName = (ast: SchemaAST.Annotated): string | undefined => {
303
+ return SchemaAST.getIdentifierAnnotation(ast).pipe(
304
+ Option.orElse(() => SchemaAST.getTitleAnnotation(ast)),
305
+ Option.orElse(() => SchemaAST.getDescriptionAnnotation(ast)),
306
+ Option.getOrElse(() => undefined),
307
+ );
308
+ };
309
+
310
+ export interface RefResolver {
311
+ /**
312
+ * Resolve ref synchronously from the objects in the working set.
313
+ *
314
+ * @param dxn
315
+ * @param load If true the resolver should attempt to load the object from disk.
316
+ * @param onLoad Callback to call when the object is loaded.
317
+ */
318
+ resolveSync(dxn: DXN, load: boolean, onLoad?: () => void): BaseObject | undefined;
319
+
320
+ /**
321
+ * Resolver ref asynchronously.
322
+ */
323
+ resolve(dxn: DXN): Promise<BaseObject | undefined>;
324
+
325
+ // TODO(dmaretskyi): Combine with `resolve`.
326
+ resolveSchema(dxn: DXN): Promise<Schema.Schema.AnyNoContext | undefined>;
327
+ }
328
+
329
+ export class RefImpl<T> implements Ref<T> {
330
+ #dxn: DXN;
331
+ #resolver?: RefResolver = undefined;
332
+ #signal = compositeRuntime.createSignal();
333
+
334
+ /**
335
+ * Target is set when the reference is created from a specific object.
336
+ * In this case, the target might not be in the database.
337
+ */
338
+ #target: T | undefined = undefined;
339
+
340
+ /**
341
+ * Callback to issue a reactive notification when object is resolved.
342
+ */
343
+ #resolverCallback = () => {
344
+ this.#signal.notifyWrite();
345
+ };
346
+
347
+ constructor(dxn: DXN, target?: T) {
348
+ this.#dxn = dxn;
349
+ this.#target = target;
350
+ }
351
+
352
+ /**
353
+ * @inheritdoc
354
+ */
355
+ get dxn(): DXN {
356
+ return this.#dxn;
357
+ }
358
+
359
+ /**
360
+ * @inheritdoc
361
+ */
362
+ get target(): T | undefined {
363
+ this.#signal.notifyRead();
364
+ if (this.#target) {
365
+ return this.#target;
366
+ }
367
+
368
+ invariant(this.#resolver, 'Resolver is not set');
369
+ return this.#resolver.resolveSync(this.#dxn, true, this.#resolverCallback) as T | undefined;
370
+ }
371
+
372
+ /**
373
+ * @inheritdoc
374
+ */
375
+ async load(): Promise<T> {
376
+ if (this.#target) {
377
+ return this.#target;
378
+ }
379
+ invariant(this.#resolver, 'Resolver is not set');
380
+ const obj = await this.#resolver.resolve(this.#dxn);
381
+ if (obj == null) {
382
+ throw new Error('Object not found');
383
+ }
384
+ return obj as T;
385
+ }
386
+
387
+ /**
388
+ * @inheritdoc
389
+ */
390
+ async tryLoad(): Promise<T | undefined> {
391
+ invariant(this.#resolver, 'Resolver is not set');
392
+ return (await this.#resolver.resolve(this.#dxn)) as T | undefined;
393
+ }
394
+
395
+ /**
396
+ * Do not inline the target object in the reference.
397
+ * Makes .target unavailable unless the reference is connected to a database context.
398
+ * Clones the reference object.
399
+ */
400
+ noInline(): RefImpl<T> {
401
+ const ref = new RefImpl<T>(this.#dxn, undefined);
402
+ ref.#resolver = this.#resolver;
403
+ return ref;
404
+ }
405
+
406
+ encode(): EncodedReference {
407
+ return {
408
+ '/': this.#dxn.toString(),
409
+ ...(this.#target ? { target: this.#target } : {}),
410
+ };
411
+ }
412
+
413
+ /**
414
+ * Serializes the reference to a JSON object.
415
+ * The serialization format is compatible with the IPLD-style encoded references.
416
+ * When a reference has a saved target (i.e. the target or object holding the reference is not in the database),
417
+ * the target is included in the serialized object.
418
+ */
419
+ toJSON(): EncodedReference {
420
+ return this.encode();
421
+ }
422
+
423
+ toString(): string {
424
+ if (this.#target) {
425
+ return `Ref(${this.#target.toString()})`;
426
+ }
427
+
428
+ return `Ref(${this.#dxn.toString()})`;
429
+ }
430
+
431
+ [RefTypeId] = refVariance;
432
+
433
+ /**
434
+ * Internal method to set the resolver.
435
+ * @internal
436
+ */
437
+ _setResolver(resolver: RefResolver): void {
438
+ this.#resolver = resolver;
439
+ }
440
+
441
+ /**
442
+ * Internal method to get the saved target.
443
+ * Not the same as `target` which is resolved from the resolver.
444
+ * @internal
445
+ */
446
+ _getSavedTarget(): T | undefined {
447
+ return this.#target;
448
+ }
449
+ }
450
+
451
+ /**
452
+ * Internal API for setting the reference resolver.
453
+ */
454
+ export const setRefResolver = (ref: Ref<any>, resolver: RefResolver) => {
455
+ invariant(ref instanceof RefImpl, 'Ref is not an instance of RefImpl');
456
+ ref._setResolver(resolver);
457
+ };
458
+
459
+ /**
460
+ * Internal API for getting the saved target on a reference.
461
+ */
462
+ export const getRefSavedTarget = (ref: Ref<any>): BaseObject | undefined => {
463
+ invariant(ref instanceof RefImpl, 'Ref is not an instance of RefImpl');
464
+ return ref._getSavedTarget();
465
+ };
466
+
467
+ // Used to validate reference target type.
468
+ const refVariance: Ref<any>[typeof RefTypeId] = {
469
+ _T: null as any,
470
+ };
471
+
472
+ export const refFromEncodedReference = (encodedReference: EncodedReference, resolver?: RefResolver): Ref<any> => {
473
+ const dxn = DXN.parse(encodedReference['/']);
474
+ const ref = new RefImpl(dxn);
475
+
476
+ // TODO(dmaretskyi): Handle inline target in the encoded reference.
477
+
478
+ if (resolver) {
479
+ setRefResolver(ref, resolver);
480
+ }
481
+ return ref;
482
+ };
483
+
484
+ export class StaticRefResolver implements RefResolver {
485
+ public objects = new Map<ObjectId, BaseObject>();
486
+ public schemas = new Map<DXN.String, Schema.Schema.AnyNoContext>();
487
+
488
+ addObject(obj: BaseObject): this {
489
+ this.objects.set(obj.id, obj);
490
+ return this;
491
+ }
492
+
493
+ addSchema(schema: Schema.Schema.AnyNoContext): this {
494
+ const dxn = getSchemaDXN(schema);
495
+ invariant(dxn, 'Schema has no DXN');
496
+ this.schemas.set(dxn.toString(), schema);
497
+ return this;
498
+ }
499
+
500
+ resolveSync(dxn: DXN, _load: boolean, _onLoad?: () => void): BaseObject | undefined {
501
+ const id = dxn?.asEchoDXN()?.echoId;
502
+ if (id == null) {
503
+ return undefined;
504
+ }
505
+
506
+ return this.objects.get(id);
507
+ }
508
+
509
+ async resolve(dxn: DXN): Promise<BaseObject | undefined> {
510
+ const id = dxn?.asEchoDXN()?.echoId;
511
+ if (id == null) {
512
+ return undefined;
513
+ }
514
+
515
+ return this.objects.get(id);
516
+ }
517
+
518
+ async resolveSchema(dxn: DXN): Promise<Schema.Schema.AnyNoContext | undefined> {
519
+ return this.schemas.get(dxn.toString());
520
+ }
521
+ }