@dxos/client-services 0.10.0 → 0.11.0

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 (304) hide show
  1. package/dist/lib/chunk-client-services-layer.mjs +11425 -0
  2. package/dist/lib/chunk-client-services-layer.mjs.map +1 -0
  3. package/dist/lib/chunk-decorate.mjs +9 -0
  4. package/dist/lib/index.mjs +731 -0
  5. package/dist/lib/index.mjs.map +1 -0
  6. package/dist/lib/packlets/diagnostics/browser-diagnostics-broadcast.mjs +72 -0
  7. package/dist/lib/packlets/diagnostics/browser-diagnostics-broadcast.mjs.map +1 -0
  8. package/dist/lib/packlets/diagnostics/diagnostics-broadcast.mjs +14 -0
  9. package/dist/lib/packlets/diagnostics/diagnostics-broadcast.mjs.map +1 -0
  10. package/dist/lib/packlets/locks/browser.mjs +105 -0
  11. package/dist/lib/packlets/locks/browser.mjs.map +1 -0
  12. package/dist/lib/packlets/locks/node.mjs +53 -0
  13. package/dist/lib/packlets/locks/node.mjs.map +1 -0
  14. package/dist/lib/testing.mjs +570 -0
  15. package/dist/lib/testing.mjs.map +1 -0
  16. package/dist/types/src/index.d.ts +2 -0
  17. package/dist/types/src/index.d.ts.map +1 -1
  18. package/dist/types/src/packlets/agents/edge-agent-manager.d.ts +21 -4
  19. package/dist/types/src/packlets/agents/edge-agent-manager.d.ts.map +1 -1
  20. package/dist/types/src/packlets/agents/edge-agent-service.d.ts +8 -7
  21. package/dist/types/src/packlets/agents/edge-agent-service.d.ts.map +1 -1
  22. package/dist/types/src/packlets/devices/devices-service.d.ts +7 -5
  23. package/dist/types/src/packlets/devices/devices-service.d.ts.map +1 -1
  24. package/dist/types/src/packlets/devtools/devtools.d.ts +30 -29
  25. package/dist/types/src/packlets/devtools/devtools.d.ts.map +1 -1
  26. package/dist/types/src/packlets/devtools/feeds.d.ts +1 -1
  27. package/dist/types/src/packlets/devtools/feeds.d.ts.map +1 -1
  28. package/dist/types/src/packlets/devtools/network.d.ts +2 -1
  29. package/dist/types/src/packlets/devtools/network.d.ts.map +1 -1
  30. package/dist/types/src/packlets/devtools/spaces.d.ts.map +1 -1
  31. package/dist/types/src/packlets/diagnostics/diagnostics-broadcast.d.ts +2 -2
  32. package/dist/types/src/packlets/diagnostics/diagnostics-broadcast.d.ts.map +1 -1
  33. package/dist/types/src/packlets/diagnostics/diagnostics-collector.d.ts.map +1 -1
  34. package/dist/types/src/packlets/diagnostics/diagnostics.d.ts +1 -8
  35. package/dist/types/src/packlets/diagnostics/diagnostics.d.ts.map +1 -1
  36. package/dist/types/src/packlets/identity/authenticator.d.ts +1 -1
  37. package/dist/types/src/packlets/identity/authenticator.d.ts.map +1 -1
  38. package/dist/types/src/packlets/identity/contacts-service.d.ts +9 -6
  39. package/dist/types/src/packlets/identity/contacts-service.d.ts.map +1 -1
  40. package/dist/types/src/packlets/identity/identity-manager.d.ts +32 -3
  41. package/dist/types/src/packlets/identity/identity-manager.d.ts.map +1 -1
  42. package/dist/types/src/packlets/identity/identity-recovery-manager.d.ts +23 -4
  43. package/dist/types/src/packlets/identity/identity-recovery-manager.d.ts.map +1 -1
  44. package/dist/types/src/packlets/identity/identity-service.d.ts +15 -20
  45. package/dist/types/src/packlets/identity/identity-service.d.ts.map +1 -1
  46. package/dist/types/src/packlets/identity/identity.d.ts +2 -2
  47. package/dist/types/src/packlets/identity/identity.d.ts.map +1 -1
  48. package/dist/types/src/packlets/invitations/edge-invitation-handler.d.ts.map +1 -1
  49. package/dist/types/src/packlets/invitations/invitations-handler.d.ts +17 -0
  50. package/dist/types/src/packlets/invitations/invitations-handler.d.ts.map +1 -1
  51. package/dist/types/src/packlets/invitations/invitations-manager.d.ts +25 -4
  52. package/dist/types/src/packlets/invitations/invitations-manager.d.ts.map +1 -1
  53. package/dist/types/src/packlets/invitations/invitations-service.d.ts +11 -11
  54. package/dist/types/src/packlets/invitations/invitations-service.d.ts.map +1 -1
  55. package/dist/types/src/packlets/logging/logging-service.d.ts +14 -12
  56. package/dist/types/src/packlets/logging/logging-service.d.ts.map +1 -1
  57. package/dist/types/src/packlets/metadata/index.d.ts +3 -0
  58. package/dist/types/src/packlets/metadata/index.d.ts.map +1 -0
  59. package/dist/types/src/packlets/metadata/metadata-store.d.ts +107 -0
  60. package/dist/types/src/packlets/metadata/metadata-store.d.ts.map +1 -0
  61. package/dist/types/src/packlets/metadata/metadata-store.test.d.ts +2 -0
  62. package/dist/types/src/packlets/metadata/metadata-store.test.d.ts.map +1 -0
  63. package/dist/types/src/packlets/metadata/sqlite-metadata-store.d.ts +65 -0
  64. package/dist/types/src/packlets/metadata/sqlite-metadata-store.d.ts.map +1 -0
  65. package/dist/types/src/packlets/network/network-service.d.ts +15 -14
  66. package/dist/types/src/packlets/network/network-service.d.ts.map +1 -1
  67. package/dist/types/src/packlets/pipeline/codec.d.ts +11 -0
  68. package/dist/types/src/packlets/pipeline/codec.d.ts.map +1 -0
  69. package/dist/types/src/packlets/pipeline/feeds.d.ts +7 -0
  70. package/dist/types/src/packlets/pipeline/feeds.d.ts.map +1 -0
  71. package/dist/types/src/packlets/pipeline/index.d.ts +5 -0
  72. package/dist/types/src/packlets/pipeline/index.d.ts.map +1 -0
  73. package/dist/types/src/packlets/pipeline/message-selector.d.ts +11 -0
  74. package/dist/types/src/packlets/pipeline/message-selector.d.ts.map +1 -0
  75. package/dist/types/src/packlets/pipeline/pipeline-stress.test.d.ts +2 -0
  76. package/dist/types/src/packlets/pipeline/pipeline-stress.test.d.ts.map +1 -0
  77. package/dist/types/src/packlets/pipeline/pipeline.d.ts +131 -0
  78. package/dist/types/src/packlets/pipeline/pipeline.d.ts.map +1 -0
  79. package/dist/types/src/packlets/pipeline/pipeline.test.d.ts +2 -0
  80. package/dist/types/src/packlets/pipeline/pipeline.test.d.ts.map +1 -0
  81. package/dist/types/src/packlets/pipeline/testing/index.d.ts +2 -0
  82. package/dist/types/src/packlets/pipeline/testing/index.d.ts.map +1 -0
  83. package/dist/types/src/packlets/pipeline/testing/test-feed-builder.d.ts +9 -0
  84. package/dist/types/src/packlets/pipeline/testing/test-feed-builder.d.ts.map +1 -0
  85. package/dist/types/src/packlets/pipeline/timeframe-clock.d.ts +31 -0
  86. package/dist/types/src/packlets/pipeline/timeframe-clock.d.ts.map +1 -0
  87. package/dist/types/src/packlets/services/client-services-layer.d.ts +21 -0
  88. package/dist/types/src/packlets/services/client-services-layer.d.ts.map +1 -0
  89. package/dist/types/src/packlets/services/cross-device-space-synchronizer.d.ts +28 -0
  90. package/dist/types/src/packlets/services/cross-device-space-synchronizer.d.ts.map +1 -0
  91. package/dist/types/src/packlets/services/effect-rpc.test.d.ts +2 -0
  92. package/dist/types/src/packlets/services/effect-rpc.test.d.ts.map +1 -0
  93. package/dist/types/src/packlets/services/feed-syncer.d.ts +22 -2
  94. package/dist/types/src/packlets/services/feed-syncer.d.ts.map +1 -1
  95. package/dist/types/src/packlets/services/index.d.ts +2 -2
  96. package/dist/types/src/packlets/services/index.d.ts.map +1 -1
  97. package/dist/types/src/packlets/services/platform.d.ts.map +1 -1
  98. package/dist/types/src/packlets/services/service-context.d.ts +38 -57
  99. package/dist/types/src/packlets/services/service-context.d.ts.map +1 -1
  100. package/dist/types/src/packlets/services/service-host.d.ts +82 -31
  101. package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
  102. package/dist/types/src/packlets/services/sqlite-storage.d.ts +29 -0
  103. package/dist/types/src/packlets/services/sqlite-storage.d.ts.map +1 -1
  104. package/dist/types/src/packlets/services/sqlite-storage.test.d.ts +2 -0
  105. package/dist/types/src/packlets/services/sqlite-storage.test.d.ts.map +1 -0
  106. package/dist/types/src/packlets/space/admission-discovery-extension.d.ts +30 -0
  107. package/dist/types/src/packlets/space/admission-discovery-extension.d.ts.map +1 -0
  108. package/dist/types/src/packlets/space/auth.d.ts +24 -0
  109. package/dist/types/src/packlets/space/auth.d.ts.map +1 -0
  110. package/dist/types/src/packlets/space/control-pipeline.d.ts +46 -0
  111. package/dist/types/src/packlets/space/control-pipeline.d.ts.map +1 -0
  112. package/dist/types/src/packlets/space/control-pipeline.test.d.ts +2 -0
  113. package/dist/types/src/packlets/space/control-pipeline.test.d.ts.map +1 -0
  114. package/dist/types/src/packlets/space/index.d.ts +6 -0
  115. package/dist/types/src/packlets/space/index.d.ts.map +1 -0
  116. package/dist/types/src/packlets/space/replication.browser.test.d.ts +2 -0
  117. package/dist/types/src/packlets/space/replication.browser.test.d.ts.map +1 -0
  118. package/dist/types/src/packlets/space/space-manager.browser.test.d.ts +2 -0
  119. package/dist/types/src/packlets/space/space-manager.browser.test.d.ts.map +1 -0
  120. package/dist/types/src/packlets/space/space-manager.d.ts +72 -0
  121. package/dist/types/src/packlets/space/space-manager.d.ts.map +1 -0
  122. package/dist/types/src/packlets/space/space-protocol.browser.test.d.ts +2 -0
  123. package/dist/types/src/packlets/space/space-protocol.browser.test.d.ts.map +1 -0
  124. package/dist/types/src/packlets/space/space-protocol.d.ts +99 -0
  125. package/dist/types/src/packlets/space/space-protocol.d.ts.map +1 -0
  126. package/dist/types/src/packlets/space/space-protocol.test.d.ts +2 -0
  127. package/dist/types/src/packlets/space/space-protocol.test.d.ts.map +1 -0
  128. package/dist/types/src/packlets/space/space.d.ts +65 -0
  129. package/dist/types/src/packlets/space/space.d.ts.map +1 -0
  130. package/dist/types/src/packlets/space/space.test.d.ts +2 -0
  131. package/dist/types/src/packlets/space/space.test.d.ts.map +1 -0
  132. package/dist/types/src/packlets/space/testing/change-metadata.d.ts +8 -0
  133. package/dist/types/src/packlets/space/testing/change-metadata.d.ts.map +1 -0
  134. package/dist/types/src/packlets/space/testing/index.d.ts +4 -0
  135. package/dist/types/src/packlets/space/testing/index.d.ts.map +1 -0
  136. package/dist/types/src/packlets/space/testing/test-agent-builder.d.ts +62 -0
  137. package/dist/types/src/packlets/space/testing/test-agent-builder.d.ts.map +1 -0
  138. package/dist/types/src/packlets/space-export/serialized-space-reader.d.ts +1 -1
  139. package/dist/types/src/packlets/space-export/serialized-space-reader.d.ts.map +1 -1
  140. package/dist/types/src/packlets/space-export/serialized-space-writer.d.ts.map +1 -1
  141. package/dist/types/src/packlets/spaces/data-space-manager.d.ts +40 -7
  142. package/dist/types/src/packlets/spaces/data-space-manager.d.ts.map +1 -1
  143. package/dist/types/src/packlets/spaces/data-space.d.ts +3 -1
  144. package/dist/types/src/packlets/spaces/data-space.d.ts.map +1 -1
  145. package/dist/types/src/packlets/spaces/genesis.d.ts +1 -1
  146. package/dist/types/src/packlets/spaces/genesis.d.ts.map +1 -1
  147. package/dist/types/src/packlets/spaces/spaces-service.d.ts +25 -23
  148. package/dist/types/src/packlets/spaces/spaces-service.d.ts.map +1 -1
  149. package/dist/types/src/packlets/storage/level.d.ts.map +1 -1
  150. package/dist/types/src/packlets/system/system-service.d.ts +18 -18
  151. package/dist/types/src/packlets/system/system-service.d.ts.map +1 -1
  152. package/dist/types/src/packlets/testing/invitation-utils.d.ts +1 -1
  153. package/dist/types/src/packlets/testing/invitation-utils.d.ts.map +1 -1
  154. package/dist/types/src/packlets/testing/test-builder.d.ts +7 -6
  155. package/dist/types/src/packlets/testing/test-builder.d.ts.map +1 -1
  156. package/dist/types/src/packlets/worker/worker-runtime.d.ts +41 -58
  157. package/dist/types/src/packlets/worker/worker-runtime.d.ts.map +1 -1
  158. package/dist/types/src/packlets/worker/worker-session.d.ts +21 -8
  159. package/dist/types/src/packlets/worker/worker-session.d.ts.map +1 -1
  160. package/dist/types/src/testing/index.d.ts +1 -0
  161. package/dist/types/src/testing/index.d.ts.map +1 -1
  162. package/dist/types/src/version.d.ts +1 -1
  163. package/dist/types/tsconfig.tsbuildinfo +1 -1
  164. package/package.json +52 -48
  165. package/src/index.ts +2 -0
  166. package/src/packlets/agents/edge-agent-manager.ts +56 -15
  167. package/src/packlets/agents/edge-agent-service.ts +25 -15
  168. package/src/packlets/devices/devices-service.test.ts +20 -19
  169. package/src/packlets/devices/devices-service.ts +17 -17
  170. package/src/packlets/devtools/devtools.ts +133 -75
  171. package/src/packlets/devtools/feeds.ts +2 -1
  172. package/src/packlets/devtools/network.ts +42 -12
  173. package/src/packlets/devtools/spaces.ts +1 -1
  174. package/src/packlets/diagnostics/diagnostics-broadcast.ts +4 -2
  175. package/src/packlets/diagnostics/diagnostics-collector.ts +5 -18
  176. package/src/packlets/diagnostics/diagnostics.ts +0 -11
  177. package/src/packlets/identity/authenticator.ts +2 -1
  178. package/src/packlets/identity/contacts-service.ts +46 -37
  179. package/src/packlets/identity/identity-manager.test.ts +3 -1
  180. package/src/packlets/identity/identity-manager.ts +73 -5
  181. package/src/packlets/identity/identity-recovery-manager.ts +56 -7
  182. package/src/packlets/identity/identity-service.test.ts +27 -18
  183. package/src/packlets/identity/identity-service.ts +114 -71
  184. package/src/packlets/identity/identity.test.ts +4 -9
  185. package/src/packlets/identity/identity.ts +1 -2
  186. package/src/packlets/invitations/edge-invitation-handler.ts +0 -1
  187. package/src/packlets/invitations/invitations-handler.ts +39 -3
  188. package/src/packlets/invitations/invitations-manager.ts +52 -4
  189. package/src/packlets/invitations/invitations-service.ts +54 -28
  190. package/src/packlets/logging/logging-service.ts +41 -29
  191. package/src/packlets/logging/logging.test.ts +28 -15
  192. package/src/packlets/metadata/index.ts +6 -0
  193. package/src/packlets/metadata/metadata-store.test.ts +49 -0
  194. package/src/packlets/metadata/metadata-store.ts +446 -0
  195. package/src/packlets/metadata/sqlite-metadata-store.ts +409 -0
  196. package/src/packlets/network/network-service.test.ts +19 -12
  197. package/src/packlets/network/network-service.ts +96 -29
  198. package/src/packlets/pipeline/codec.ts +18 -0
  199. package/src/packlets/pipeline/feeds.ts +22 -0
  200. package/src/packlets/pipeline/index.ts +8 -0
  201. package/src/packlets/pipeline/message-selector.ts +35 -0
  202. package/src/packlets/pipeline/pipeline-stress.test.ts +230 -0
  203. package/src/packlets/pipeline/pipeline.test.ts +140 -0
  204. package/src/packlets/pipeline/pipeline.ts +439 -0
  205. package/src/packlets/pipeline/testing/index.ts +5 -0
  206. package/src/packlets/pipeline/testing/test-feed-builder.ts +19 -0
  207. package/src/packlets/pipeline/timeframe-clock.ts +81 -0
  208. package/src/packlets/services/client-services-layer.ts +186 -0
  209. package/src/packlets/services/cross-device-space-synchronizer.ts +132 -0
  210. package/src/packlets/services/effect-rpc.test.ts +314 -0
  211. package/src/packlets/services/feed-syncer.ts +57 -3
  212. package/src/packlets/services/index.ts +2 -2
  213. package/src/packlets/services/platform.ts +0 -6
  214. package/src/packlets/services/service-context.ts +219 -522
  215. package/src/packlets/services/service-host.test.ts +34 -11
  216. package/src/packlets/services/service-host.ts +693 -213
  217. package/src/packlets/services/spec.md +106 -0
  218. package/src/packlets/services/sqlite-storage.test.ts +48 -0
  219. package/src/packlets/services/sqlite-storage.ts +117 -20
  220. package/src/packlets/space/admission-discovery-extension.ts +91 -0
  221. package/src/packlets/space/auth.ts +92 -0
  222. package/src/packlets/space/control-pipeline.test.ts +165 -0
  223. package/src/packlets/space/control-pipeline.ts +229 -0
  224. package/src/packlets/space/index.ts +9 -0
  225. package/src/packlets/space/replication.browser.test.ts +58 -0
  226. package/src/packlets/space/space-manager.browser.test.ts +40 -0
  227. package/src/packlets/space/space-manager.ts +224 -0
  228. package/src/packlets/space/space-protocol.browser.test.ts +129 -0
  229. package/src/packlets/space/space-protocol.test.ts +158 -0
  230. package/src/packlets/space/space-protocol.ts +308 -0
  231. package/src/packlets/space/space.test.ts +181 -0
  232. package/src/packlets/space/space.ts +196 -0
  233. package/src/packlets/space/testing/change-metadata.ts +27 -0
  234. package/src/packlets/space/testing/index.ts +7 -0
  235. package/src/packlets/space/testing/test-agent-builder.ts +252 -0
  236. package/src/packlets/space-export/serialized-space-reader.ts +3 -1
  237. package/src/packlets/space-export/serialized-space-writer.ts +53 -30
  238. package/src/packlets/space-export/space-archive.test.ts +30 -0
  239. package/src/packlets/spaces/data-space-manager.test.ts +1 -1
  240. package/src/packlets/spaces/data-space-manager.ts +148 -33
  241. package/src/packlets/spaces/data-space.ts +29 -18
  242. package/src/packlets/spaces/edge-feed-replicator.test.ts +1 -1
  243. package/src/packlets/spaces/genesis.ts +1 -1
  244. package/src/packlets/spaces/spaces-service.test.ts +35 -22
  245. package/src/packlets/spaces/spaces-service.ts +259 -180
  246. package/src/packlets/storage/level.ts +3 -1
  247. package/src/packlets/system/system-service.test.ts +31 -13
  248. package/src/packlets/system/system-service.ts +65 -37
  249. package/src/packlets/testing/invitation-utils.ts +3 -3
  250. package/src/packlets/testing/test-builder.ts +35 -20
  251. package/src/packlets/worker/worker-runtime.ts +222 -260
  252. package/src/packlets/worker/worker-session.ts +108 -91
  253. package/src/testing/index.ts +1 -0
  254. package/src/version.ts +1 -1
  255. package/dist/lib/browser/chunk-QCWEHHJW.mjs +0 -24
  256. package/dist/lib/browser/chunk-QCWEHHJW.mjs.map +0 -7
  257. package/dist/lib/browser/chunk-XJRPB3GA.mjs +0 -22
  258. package/dist/lib/browser/chunk-XJRPB3GA.mjs.map +0 -7
  259. package/dist/lib/browser/chunk-XU5R2BMF.mjs +0 -8837
  260. package/dist/lib/browser/chunk-XU5R2BMF.mjs.map +0 -7
  261. package/dist/lib/browser/index.mjs +0 -876
  262. package/dist/lib/browser/index.mjs.map +0 -7
  263. package/dist/lib/browser/meta.json +0 -1
  264. package/dist/lib/browser/packlets/diagnostics/browser-diagnostics-broadcast.mjs +0 -88
  265. package/dist/lib/browser/packlets/diagnostics/browser-diagnostics-broadcast.mjs.map +0 -7
  266. package/dist/lib/browser/packlets/diagnostics/diagnostics-broadcast.mjs +0 -11
  267. package/dist/lib/browser/packlets/diagnostics/diagnostics-broadcast.mjs.map +0 -7
  268. package/dist/lib/browser/packlets/locks/browser.mjs +0 -86
  269. package/dist/lib/browser/packlets/locks/browser.mjs.map +0 -7
  270. package/dist/lib/browser/packlets/locks/node.mjs +0 -48
  271. package/dist/lib/browser/packlets/locks/node.mjs.map +0 -7
  272. package/dist/lib/browser/testing/index.mjs +0 -436
  273. package/dist/lib/browser/testing/index.mjs.map +0 -7
  274. package/dist/lib/node-esm/chunk-2DT3MZRL.mjs +0 -22
  275. package/dist/lib/node-esm/chunk-2DT3MZRL.mjs.map +0 -7
  276. package/dist/lib/node-esm/chunk-2SZHAWBN.mjs +0 -24
  277. package/dist/lib/node-esm/chunk-2SZHAWBN.mjs.map +0 -7
  278. package/dist/lib/node-esm/chunk-H5YTUD35.mjs +0 -8837
  279. package/dist/lib/node-esm/chunk-H5YTUD35.mjs.map +0 -7
  280. package/dist/lib/node-esm/index.mjs +0 -876
  281. package/dist/lib/node-esm/index.mjs.map +0 -7
  282. package/dist/lib/node-esm/meta.json +0 -1
  283. package/dist/lib/node-esm/packlets/diagnostics/browser-diagnostics-broadcast.mjs +0 -88
  284. package/dist/lib/node-esm/packlets/diagnostics/browser-diagnostics-broadcast.mjs.map +0 -7
  285. package/dist/lib/node-esm/packlets/diagnostics/diagnostics-broadcast.mjs +0 -11
  286. package/dist/lib/node-esm/packlets/diagnostics/diagnostics-broadcast.mjs.map +0 -7
  287. package/dist/lib/node-esm/packlets/locks/browser.mjs +0 -86
  288. package/dist/lib/node-esm/packlets/locks/browser.mjs.map +0 -7
  289. package/dist/lib/node-esm/packlets/locks/node.mjs +0 -48
  290. package/dist/lib/node-esm/packlets/locks/node.mjs.map +0 -7
  291. package/dist/lib/node-esm/testing/index.mjs +0 -436
  292. package/dist/lib/node-esm/testing/index.mjs.map +0 -7
  293. package/dist/types/src/packlets/services/client-rpc-server.d.ts +0 -24
  294. package/dist/types/src/packlets/services/client-rpc-server.d.ts.map +0 -1
  295. package/dist/types/src/packlets/services/service-registry.d.ts +0 -15
  296. package/dist/types/src/packlets/services/service-registry.d.ts.map +0 -1
  297. package/dist/types/src/packlets/services/service-registry.test.d.ts +0 -2
  298. package/dist/types/src/packlets/services/service-registry.test.d.ts.map +0 -1
  299. package/dist/types/src/testing/setup.d.ts +0 -3
  300. package/dist/types/src/testing/setup.d.ts.map +0 -1
  301. package/src/packlets/services/client-rpc-server.ts +0 -111
  302. package/src/packlets/services/service-registry.test.ts +0 -78
  303. package/src/packlets/services/service-registry.ts +0 -36
  304. package/src/testing/setup.ts +0 -11
@@ -0,0 +1,409 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import * as SqlClient from '@effect/sql/SqlClient';
6
+ import type * as SqlError from '@effect/sql/SqlError';
7
+ import CRC32 from 'crc-32';
8
+ import * as Effect from 'effect/Effect';
9
+ import * as Layer from 'effect/Layer';
10
+
11
+ import { Event, scheduleTaskInterval, synchronized } from '@dxos/async';
12
+ import { Context } from '@dxos/context';
13
+ import { RuntimeProvider } from '@dxos/effect';
14
+ import { invariant } from '@dxos/invariant';
15
+ import { PublicKey } from '@dxos/keys';
16
+ import { log } from '@dxos/log';
17
+ import { DataCorruptionError, STORAGE_VERSION } from '@dxos/protocols';
18
+ import { schema } from '@dxos/protocols/proto';
19
+ import { Invitation, SpaceState } from '@dxos/protocols/proto/dxos/client/services';
20
+ import {
21
+ type ControlPipelineSnapshot,
22
+ type EchoMetadata,
23
+ type EdgeReplicationSetting,
24
+ type IdentityRecord,
25
+ type LargeSpaceMetadata,
26
+ type SpaceCache,
27
+ type SpaceMetadata,
28
+ } from '@dxos/protocols/proto/dxos/echo/metadata';
29
+ import { SqlTransaction } from '@dxos/sql-sqlite';
30
+ import { type Timeframe } from '@dxos/timeframe';
31
+ import { ComplexMap, arrayToBuffer, forEachAsync, isNonNullable } from '@dxos/util';
32
+
33
+ import { type IMetadataStore, IMetadataStoreService, hasInvitationExpired } from './metadata-store';
34
+
35
+ // SqlTransaction.SqlTransaction is the Tag class exported from the SqlTransaction namespace.
36
+ type SqlTransactionTag = SqlTransaction.SqlTransaction;
37
+
38
+ const EXPIRED_INVITATION_CLEANUP_INTERVAL = 60 * 60 * 1000; // 1 hour
39
+
40
+ const emptyEchoMetadata = (): EchoMetadata => ({
41
+ version: STORAGE_VERSION,
42
+ spaces: [],
43
+ created: new Date(),
44
+ updated: new Date(),
45
+ });
46
+
47
+ const emptyLargeSpaceMetadata = (): LargeSpaceMetadata => ({});
48
+
49
+ const EchoMetadataCodec = schema.getCodecForType('dxos.echo.metadata.EchoMetadata');
50
+ const LargeSpaceMetadataCodec = schema.getCodecForType('dxos.echo.metadata.LargeSpaceMetadata');
51
+
52
+ const MAIN_KEY = 'main';
53
+ const largeKey = (spaceKey: PublicKey) => `large:${spaceKey.toHex()}`;
54
+
55
+ // Legacy invitation type detection.
56
+ const isLegacyInvitationFormat = (invitation: Invitation): boolean => invitation.type === Invitation.Type.MULTIUSE;
57
+
58
+ export type SqliteMetadataStoreProps = {
59
+ runtime: RuntimeProvider.RuntimeProvider<SqlClient.SqlClient | SqlTransactionTag>;
60
+ };
61
+
62
+ /**
63
+ * SQLite-backed replacement for MetadataStore.
64
+ * Stores EchoMetadata as a single protobuf blob and LargeSpaceMetadata as per-space blobs.
65
+ */
66
+ export class SqliteMetadataStore implements IMetadataStore {
67
+ readonly #runtime: RuntimeProvider.RuntimeProvider<SqlClient.SqlClient | SqlTransactionTag>;
68
+
69
+ #metadata: EchoMetadata = emptyEchoMetadata();
70
+ readonly #spaceLargeMetadata = new ComplexMap<PublicKey, LargeSpaceMetadata>(PublicKey.hash);
71
+
72
+ readonly update = new Event<EchoMetadata>();
73
+ readonly #invitationCleanupCtx = new Context();
74
+
75
+ constructor({ runtime }: SqliteMetadataStoreProps) {
76
+ this.#runtime = runtime;
77
+ }
78
+
79
+ /**
80
+ * Creates the space_metadata and space_large tables if they do not exist.
81
+ */
82
+ readonly migrate: Effect.Effect<void, SqlError.SqlError, SqlClient.SqlClient | SqlTransactionTag> = Effect.fn(
83
+ 'SqliteMetadataStore.migrate',
84
+ )(() =>
85
+ Effect.gen(function* () {
86
+ const sql = yield* SqlClient.SqlClient;
87
+ yield* sql`CREATE TABLE IF NOT EXISTS space_metadata (
88
+ key TEXT PRIMARY KEY,
89
+ value BLOB NOT NULL
90
+ )`;
91
+ yield* sql`CREATE TABLE IF NOT EXISTS space_large (
92
+ space_key TEXT PRIMARY KEY,
93
+ value BLOB NOT NULL
94
+ )`;
95
+ log('space_metadata and space_large tables ready');
96
+ }).pipe(Effect.withSpan('SqliteMetadataStore.migrate')),
97
+ )();
98
+
99
+ get metadata(): EchoMetadata {
100
+ return this.#metadata;
101
+ }
102
+
103
+ get version(): number {
104
+ return this.#metadata.version ?? 0;
105
+ }
106
+
107
+ get spaces(): SpaceMetadata[] {
108
+ return this.#metadata.spaces ?? [];
109
+ }
110
+
111
+ async close(): Promise<void> {
112
+ await this.#invitationCleanupCtx.dispose();
113
+ await this._save();
114
+ this.#metadata = emptyEchoMetadata();
115
+ this.#spaceLargeMetadata.clear();
116
+ }
117
+
118
+ /**
119
+ * Loads metadata from SQLite.
120
+ */
121
+ @synchronized
122
+ async load(): Promise<void> {
123
+ const rows = await RuntimeProvider.runPromise(this.#runtime)(
124
+ Effect.gen(function* () {
125
+ const sql = yield* SqlClient.SqlClient;
126
+ return yield* sql<{ value: Uint8Array }>`SELECT value FROM space_metadata WHERE key = ${MAIN_KEY}`;
127
+ }),
128
+ );
129
+
130
+ if (rows.length > 0) {
131
+ try {
132
+ this.#metadata = this.#decodeWithCrc(rows[0].value, EchoMetadataCodec) ?? emptyEchoMetadata();
133
+ this.#metadata.spaces?.forEach((space) => {
134
+ space.state ??= SpaceState.SPACE_ACTIVE;
135
+ });
136
+ } catch (err: any) {
137
+ log.error('failed to load metadata from SQLite', { err });
138
+ this.#metadata = emptyEchoMetadata();
139
+ }
140
+ }
141
+
142
+ // Load large metadata for all known spaces.
143
+ const spaceKeys = [
144
+ this.#metadata.identity?.haloSpace.key,
145
+ ...(this.#metadata.spaces?.map((s) => s.key) ?? []),
146
+ ].filter(isNonNullable);
147
+
148
+ await forEachAsync(spaceKeys, async (key) => {
149
+ try {
150
+ await this.#loadSpaceLargeMetadata(key);
151
+ } catch (err: any) {
152
+ log.error('failed to load large space metadata', { err });
153
+ }
154
+ });
155
+
156
+ scheduleTaskInterval(
157
+ this.#invitationCleanupCtx,
158
+ async () => {
159
+ for (const invitation of this.#metadata.invitations ?? []) {
160
+ if (hasInvitationExpired(invitation) || isLegacyInvitationFormat(invitation)) {
161
+ await this.removeInvitation(invitation.invitationId);
162
+ }
163
+ }
164
+ },
165
+ EXPIRED_INVITATION_CLEANUP_INTERVAL,
166
+ );
167
+ }
168
+
169
+ async flush(): Promise<void> {
170
+ // No-op: SQLite writes are immediate.
171
+ }
172
+
173
+ hasSpace(spaceKey: PublicKey): boolean {
174
+ if (this.#metadata.identity?.haloSpace.key.equals(spaceKey)) {
175
+ return true;
176
+ }
177
+ return !!this.spaces.find((space) => space.key.equals(spaceKey));
178
+ }
179
+
180
+ getIdentityRecord(): IdentityRecord | undefined {
181
+ return this.#metadata.identity;
182
+ }
183
+
184
+ async setIdentityRecord(record: IdentityRecord): Promise<void> {
185
+ invariant(!this.#metadata.identity, 'Cannot overwrite existing identity in metadata');
186
+ this.#metadata.identity = record;
187
+ await this._save();
188
+ }
189
+
190
+ getInvitations(): Invitation[] {
191
+ return this.#metadata.invitations ?? [];
192
+ }
193
+
194
+ async addInvitation(invitation: Invitation): Promise<void> {
195
+ if (this.#metadata.invitations?.find((i) => i.invitationId === invitation.invitationId)) {
196
+ return;
197
+ }
198
+ (this.#metadata.invitations ??= []).push(invitation);
199
+ await this._save();
200
+ }
201
+
202
+ async removeInvitation(invitationId: string): Promise<void> {
203
+ this.#metadata.invitations = (this.#metadata.invitations ?? []).filter((i) => i.invitationId !== invitationId);
204
+ await this._save();
205
+ }
206
+
207
+ async addSpace(record: SpaceMetadata): Promise<void> {
208
+ invariant(
209
+ !(this.#metadata.spaces ?? []).find((space) => space.key.equals(record.key)),
210
+ 'Cannot overwrite existing space in metadata',
211
+ );
212
+ (this.#metadata.spaces ??= []).push(record);
213
+ await this._save();
214
+ }
215
+
216
+ async setSpaceDataLatestTimeframe(spaceKey: PublicKey, timeframe: Timeframe): Promise<void> {
217
+ this.#getSpace(spaceKey).dataTimeframe = timeframe;
218
+ await this._save();
219
+ }
220
+
221
+ async setSpaceControlLatestTimeframe(spaceKey: PublicKey, timeframe: Timeframe): Promise<void> {
222
+ this.#getSpace(spaceKey).controlTimeframe = timeframe;
223
+ await this._save();
224
+ }
225
+
226
+ async setCache(spaceKey: PublicKey, cache: SpaceCache): Promise<void> {
227
+ this.#getSpace(spaceKey).cache = cache;
228
+ await this._save();
229
+ }
230
+
231
+ async setWritableFeedKeys(spaceKey: PublicKey, controlFeedKey: PublicKey, dataFeedKey: PublicKey): Promise<void> {
232
+ const space = this.#getSpace(spaceKey);
233
+ space.controlFeedKey = controlFeedKey;
234
+ space.dataFeedKey = dataFeedKey;
235
+ await this._save();
236
+ }
237
+
238
+ async setSpaceState(spaceKey: PublicKey, state: SpaceState): Promise<void> {
239
+ this.#getSpace(spaceKey).state = state;
240
+ await this._save();
241
+ }
242
+
243
+ getSpaceControlPipelineSnapshot(spaceKey: PublicKey): ControlPipelineSnapshot | undefined {
244
+ return this.#getLargeSpaceMetadata(spaceKey).controlPipelineSnapshot;
245
+ }
246
+
247
+ async setSpaceControlPipelineSnapshot(spaceKey: PublicKey, snapshot: ControlPipelineSnapshot): Promise<void> {
248
+ this.#getLargeSpaceMetadata(spaceKey).controlPipelineSnapshot = snapshot;
249
+ await this._saveSpaceLargeMetadata(spaceKey);
250
+ }
251
+
252
+ getSpaceEdgeReplicationSetting(spaceKey: PublicKey): EdgeReplicationSetting | undefined {
253
+ return this.hasSpace(spaceKey) ? this.#getSpace(spaceKey).edgeReplication : undefined;
254
+ }
255
+
256
+ async setSpaceEdgeReplicationSetting(spaceKey: PublicKey, setting: EdgeReplicationSetting): Promise<void> {
257
+ this.#getSpace(spaceKey).edgeReplication = setting;
258
+ await this._save();
259
+ }
260
+
261
+ get deletedSpaces(): PublicKey[] {
262
+ return this.#metadata.deletedSpaces ?? [];
263
+ }
264
+
265
+ async addDeletedSpace(spaceKey: PublicKey): Promise<void> {
266
+ if ((this.#metadata.deletedSpaces ?? []).some((key) => key.equals(spaceKey))) {
267
+ return;
268
+ }
269
+ (this.#metadata.deletedSpaces ??= []).push(spaceKey);
270
+ await this._save();
271
+ }
272
+
273
+ async clear(): Promise<void> {
274
+ log('clearing all metadata');
275
+ await RuntimeProvider.runPromise(this.#runtime)(
276
+ Effect.gen(function* () {
277
+ const sql = yield* SqlClient.SqlClient;
278
+ yield* sql`DELETE FROM space_metadata`;
279
+ yield* sql`DELETE FROM space_large`;
280
+ }),
281
+ );
282
+ this.#metadata = emptyEchoMetadata();
283
+ this.#spaceLargeMetadata.clear();
284
+ }
285
+
286
+ #getSpace(spaceKey: PublicKey): SpaceMetadata {
287
+ if (this.#metadata.identity?.haloSpace.key.equals(spaceKey)) {
288
+ return this.#metadata.identity.haloSpace;
289
+ }
290
+ const space = this.spaces.find((space) => space.key.equals(spaceKey));
291
+ invariant(space, 'Space not found');
292
+ return space;
293
+ }
294
+
295
+ #getLargeSpaceMetadata(key: PublicKey): LargeSpaceMetadata {
296
+ let entry = this.#spaceLargeMetadata.get(key);
297
+ if (!entry) {
298
+ entry = emptyLargeSpaceMetadata();
299
+ this.#spaceLargeMetadata.set(key, entry);
300
+ }
301
+ return entry;
302
+ }
303
+
304
+ @synchronized
305
+ private async _save(): Promise<void> {
306
+ const data: EchoMetadata = {
307
+ ...this.#metadata,
308
+ version: STORAGE_VERSION,
309
+ created: this.#metadata.created ?? new Date(),
310
+ updated: new Date(),
311
+ };
312
+ this.update.emit(data);
313
+ const encoded = this.#encodeWithCrc(EchoMetadataCodec, data);
314
+ await RuntimeProvider.runPromise(this.#runtime)(
315
+ Effect.gen(function* () {
316
+ const sql = yield* SqlClient.SqlClient;
317
+ yield* sql`INSERT OR REPLACE INTO space_metadata (key, value) VALUES (${MAIN_KEY}, ${encoded})`;
318
+ }),
319
+ );
320
+ }
321
+
322
+ async #loadSpaceLargeMetadata(key: PublicKey): Promise<void> {
323
+ const keyStr = largeKey(key);
324
+ const rows = await RuntimeProvider.runPromise(this.#runtime)(
325
+ Effect.gen(function* () {
326
+ const sql = yield* SqlClient.SqlClient;
327
+ return yield* sql<{ value: Uint8Array }>`SELECT value FROM space_large WHERE space_key = ${keyStr}`;
328
+ }),
329
+ );
330
+ if (rows.length > 0) {
331
+ try {
332
+ const meta = this.#decodeWithCrc(rows[0].value, LargeSpaceMetadataCodec);
333
+ if (meta) {
334
+ this.#spaceLargeMetadata.set(key, meta);
335
+ }
336
+ } catch (err: any) {
337
+ log.error('failed to decode large space metadata', { err });
338
+ }
339
+ }
340
+ }
341
+
342
+ @synchronized
343
+ private async _saveSpaceLargeMetadata(key: PublicKey): Promise<void> {
344
+ const data = this.#getLargeSpaceMetadata(key);
345
+ const keyStr = largeKey(key);
346
+ const encoded = this.#encodeWithCrc(LargeSpaceMetadataCodec, data);
347
+ await RuntimeProvider.runPromise(this.#runtime)(
348
+ Effect.gen(function* () {
349
+ const sql = yield* SqlClient.SqlClient;
350
+ yield* sql`INSERT OR REPLACE INTO space_large (space_key, value) VALUES (${keyStr}, ${encoded})`;
351
+ }),
352
+ );
353
+ }
354
+
355
+ /** Serialize with CRC32 checksum prefix for integrity checking (matches MetadataStore format). */
356
+ #encodeWithCrc<T>(codec: { encode: (v: T) => Uint8Array }, data: T): Buffer {
357
+ const encoded = arrayToBuffer(codec.encode(data));
358
+ const checksum = CRC32.buf(encoded);
359
+ const result = Buffer.alloc(8 + encoded.length);
360
+ result.writeInt32LE(encoded.length, 0);
361
+ result.writeInt32LE(checksum, 4);
362
+ encoded.copy(result, 8);
363
+ return result;
364
+ }
365
+
366
+ /** Deserialize with CRC32 integrity check (matches MetadataStore format). */
367
+ #decodeWithCrc<T>(data: Uint8Array, codec: { decode: (v: Uint8Array) => T }): T | undefined {
368
+ const buf = Buffer.from(data);
369
+ if (buf.length < 8) {
370
+ return undefined;
371
+ }
372
+ const dataSize = buf.readInt32LE(0);
373
+ const checksum = buf.readInt32LE(4);
374
+ if (dataSize < 0 || dataSize > buf.length - 8) {
375
+ throw new DataCorruptionError({
376
+ message: 'Metadata size header is invalid.',
377
+ context: { fileLength: buf.length, dataSize },
378
+ });
379
+ }
380
+ if (buf.length !== dataSize + 8) {
381
+ throw new DataCorruptionError({
382
+ message: 'Metadata payload length does not match framing header.',
383
+ context: { fileLength: buf.length, dataSize },
384
+ });
385
+ }
386
+ const payload = buf.subarray(8, dataSize + 8);
387
+ const calculated = CRC32.buf(payload);
388
+ if (calculated !== checksum) {
389
+ throw new DataCorruptionError({ message: 'Metadata checksum is invalid.' });
390
+ }
391
+ return codec.decode(payload);
392
+ }
393
+ }
394
+
395
+ /**
396
+ * Effect Layer constructing a {@link SqliteMetadataStore} from the ambient SQL runtime.
397
+ */
398
+ export const SqliteMetadataStoreLayer = (): Layer.Layer<
399
+ IMetadataStoreService,
400
+ never,
401
+ SqlClient.SqlClient | SqlTransactionTag
402
+ > =>
403
+ Layer.effect(
404
+ IMetadataStoreService,
405
+ Effect.gen(function* () {
406
+ const runtime = yield* RuntimeProvider.currentRuntime<SqlClient.SqlClient | SqlTransactionTag>();
407
+ return new SqliteMetadataStore({ runtime });
408
+ }),
409
+ );
@@ -2,11 +2,14 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
+ import * as Runtime from 'effect/Runtime';
5
6
  import { afterEach, beforeEach, describe, expect, onTestFinished, test } from 'vitest';
6
7
 
7
8
  import { Trigger } from '@dxos/async';
8
9
  import { Context } from '@dxos/context';
9
- import { ConnectionState, type NetworkService } from '@dxos/protocols/proto/dxos/client/services';
10
+ import { EffectEx } from '@dxos/effect';
11
+ import { subscribeStream } from '@dxos/protocols';
12
+ import { ConnectionState } from '@dxos/protocols/proto/dxos/client/services';
10
13
 
11
14
  import { type ServiceContext } from '../services';
12
15
  import { createServiceContext } from '../testing';
@@ -14,7 +17,7 @@ import { NetworkServiceImpl } from './network-service';
14
17
 
15
18
  describe('NetworkService', () => {
16
19
  let serviceContext: ServiceContext;
17
- let networkService: NetworkService;
20
+ let networkService: NetworkServiceImpl;
18
21
 
19
22
  beforeEach(async () => {
20
23
  serviceContext = await createServiceContext();
@@ -27,26 +30,30 @@ describe('NetworkService', () => {
27
30
  });
28
31
 
29
32
  test('setNetworkOptions changes network status', async () => {
30
- await networkService.updateConfig({
31
- swarm: ConnectionState.OFFLINE,
32
- });
33
+ await EffectEx.runPromise(
34
+ networkService['NetworkService.updateConfig']({
35
+ swarm: ConnectionState.OFFLINE,
36
+ }),
37
+ );
33
38
 
34
39
  expect(serviceContext.networkManager.connectionState).to.equal(ConnectionState.OFFLINE);
35
40
  });
36
41
 
37
42
  test('subscribeToNetworkStatus returns current network status', async () => {
38
- const query = networkService.queryStatus();
43
+ const stream = networkService['NetworkService.queryStatus']();
39
44
  let result = new Trigger<ConnectionState | undefined>();
40
- query.subscribe(({ swarm }) => {
41
- result.wake(swarm);
45
+ const cleanup = subscribeStream(Runtime.defaultRuntime, stream, {
46
+ onData: ({ swarm }) => result.wake(swarm),
42
47
  });
43
- onTestFinished(() => query.close());
48
+ onTestFinished(cleanup);
44
49
  expect(await result.wait()).to.equal(ConnectionState.ONLINE);
45
50
 
46
51
  result = new Trigger<ConnectionState | undefined>();
47
- await networkService.updateConfig({
48
- swarm: ConnectionState.OFFLINE,
49
- });
52
+ await EffectEx.runPromise(
53
+ networkService['NetworkService.updateConfig']({
54
+ swarm: ConnectionState.OFFLINE,
55
+ }),
56
+ );
50
57
  expect(await result.wait()).to.equal(ConnectionState.OFFLINE);
51
58
  });
52
59
  });
@@ -2,37 +2,40 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
 
5
- import { type RequestOptions } from '@dxos/codec-protobuf';
6
- import { Stream } from '@dxos/codec-protobuf/stream';
5
+ import * as Effect from 'effect/Effect';
6
+ import * as EffectStream from 'effect/Stream';
7
+
7
8
  import { Context } from '@dxos/context';
8
9
  import { type EdgeConnection } from '@dxos/edge-client';
9
- import { type SignalManager } from '@dxos/messaging';
10
+ import { type SignalManager, type UnsubscribeCallback } from '@dxos/messaging';
10
11
  import { type SwarmNetworkManager } from '@dxos/network-manager';
11
12
  import {
12
- type NetworkService,
13
13
  type NetworkStatus,
14
14
  type SubscribeSwarmStateRequest,
15
15
  type UpdateConfigRequest,
16
16
  } from '@dxos/protocols/proto/dxos/client/services';
17
- import { type Peer, type SwarmResponse } from '@dxos/protocols/proto/dxos/edge/messenger';
17
+ import { type SwarmResponse } from '@dxos/protocols/proto/dxos/edge/messenger';
18
18
  import {
19
19
  type JoinRequest,
20
20
  type LeaveRequest,
21
21
  type Message,
22
22
  type QueryRequest,
23
+ type SubscribeMessagesRequest,
23
24
  } from '@dxos/protocols/proto/dxos/edge/signal';
25
+ import { type NetworkService } from '@dxos/protocols/rpc';
24
26
 
25
- export class NetworkServiceImpl implements NetworkService {
26
- constructor(
27
+ export class NetworkServiceImpl implements NetworkService.Handlers {
28
+ 'constructor'(
27
29
  private readonly networkManager: SwarmNetworkManager,
28
30
  private readonly signalManager: SignalManager,
29
31
  private readonly edgeConnection?: EdgeConnection,
30
32
  ) {}
31
33
 
32
- queryStatus(): Stream<NetworkStatus> {
33
- return new Stream<NetworkStatus>(({ ctx, next }) => {
34
+ ['NetworkService.queryStatus'](): EffectStream.Stream<NetworkStatus, Error> {
35
+ return EffectStream.async<NetworkStatus, Error>((emit) => {
36
+ const ctx = Context.default();
34
37
  const update = () => {
35
- next({
38
+ void emit.single({
36
39
  swarm: this.networkManager.connectionState,
37
40
  connectionInfo: this.networkManager.connectionLog?.swarms,
38
41
  signaling: this.signalManager.getStatus?.().map(({ host, state }) => ({ server: host, state })),
@@ -42,45 +45,109 @@ export class NetworkServiceImpl implements NetworkService {
42
45
  this.networkManager.connectionStateChanged.on(ctx, () => update());
43
46
  this.signalManager.statusChanged?.on(ctx, () => update());
44
47
  update();
48
+
49
+ return Effect.promise(() => ctx.dispose());
45
50
  });
46
51
  }
47
52
 
48
- async updateConfig(request: UpdateConfigRequest): Promise<void> {
49
- await this.networkManager.setConnectionState(request.swarm);
53
+ ['NetworkService.updateConfig'](request: UpdateConfigRequest): Effect.Effect<void, Error> {
54
+ return Effect.tryPromise({
55
+ try: async () => {
56
+ await this.networkManager.setConnectionState(request.swarm);
57
+ },
58
+ catch: (error) => error as Error,
59
+ });
50
60
  }
51
61
 
52
- async joinSwarm(request: JoinRequest, options?: RequestOptions): Promise<void> {
53
- return this.signalManager.join(options?.ctx ?? Context.default(), request);
62
+ ['NetworkService.joinSwarm'](request: JoinRequest): Effect.Effect<void, Error> {
63
+ return Effect.tryPromise({
64
+ try: async () => {
65
+ await this.signalManager.join(Context.default(), request);
66
+ },
67
+ catch: (error) => error as Error,
68
+ });
54
69
  }
55
70
 
56
- async leaveSwarm(request: LeaveRequest, options?: RequestOptions): Promise<void> {
57
- return this.signalManager.leave(options?.ctx ?? Context.default(), request);
71
+ ['NetworkService.leaveSwarm'](request: LeaveRequest): Effect.Effect<void, Error> {
72
+ return Effect.tryPromise({
73
+ try: async () => {
74
+ await this.signalManager.leave(Context.default(), request);
75
+ },
76
+ catch: (error) => error as Error,
77
+ });
58
78
  }
59
79
 
60
- async querySwarm(request: QueryRequest, options?: RequestOptions): Promise<SwarmResponse> {
61
- return this.signalManager.query(options?.ctx ?? Context.default(), request);
80
+ ['NetworkService.querySwarm'](request: QueryRequest): Effect.Effect<SwarmResponse, Error> {
81
+ return Effect.tryPromise({
82
+ try: async () => {
83
+ return this.signalManager.query(Context.default(), request);
84
+ },
85
+ catch: (error) => error as Error,
86
+ });
62
87
  }
63
88
 
64
- subscribeSwarmState(request: SubscribeSwarmStateRequest): Stream<SwarmResponse> {
65
- return new Stream<SwarmResponse>(({ ctx, next }) => {
89
+ ['NetworkService.subscribeSwarmState'](
90
+ request: SubscribeSwarmStateRequest,
91
+ ): EffectStream.Stream<SwarmResponse, Error> {
92
+ return EffectStream.async<SwarmResponse, Error>((emit) => {
93
+ const ctx = Context.default();
66
94
  this.signalManager.swarmState?.on(ctx, (state) => {
67
95
  if (request.topic.equals(state.swarmKey)) {
68
- next(state);
96
+ void emit.single(state);
69
97
  }
70
98
  });
99
+
100
+ return Effect.promise(() => ctx.dispose());
71
101
  });
72
102
  }
73
103
 
74
- async sendMessage(message: Message, options?: RequestOptions): Promise<void> {
75
- return this.signalManager.sendMessage(options?.ctx ?? Context.default(), message);
104
+ ['NetworkService.sendMessage'](message: Message): Effect.Effect<void, Error> {
105
+ return Effect.tryPromise({
106
+ try: async () => {
107
+ await this.signalManager.sendMessage(Context.default(), message);
108
+ },
109
+ catch: (error) => error as Error,
110
+ });
76
111
  }
77
112
 
78
- subscribeMessages(peer: Peer): Stream<Message> {
79
- return new Stream<Message>(({ ctx, next }) => {
80
- this.signalManager.onMessage.on(ctx, (message) => {
81
- if (message.recipient.peerKey === peer.peerKey) {
82
- next(message);
83
- }
113
+ ['NetworkService.subscribeMessages'](request: SubscribeMessagesRequest): EffectStream.Stream<Message, Error> {
114
+ const { peer, tags = [] } = request;
115
+ return EffectStream.async<Message, Error>((emit) => {
116
+ const ctx = Context.default();
117
+
118
+ // This stream crosses the client-services RPC (protobufjs codec, e.g. dedicated worker → main
119
+ // thread). Its `Message.payload` is a `google.protobuf.Any` without `preserve_any`, and the
120
+ // codec refuses to encode an Any lacking '@type' — stamping the opaque form
121
+ // ('@type': 'google.protobuf.Any' + type_url/value) makes it pass through verbatim.
122
+ const encodableAny = (payload: Message['payload']): Message['payload'] => ({
123
+ ...payload,
124
+ '@type': 'google.protobuf.Any',
125
+ });
126
+
127
+ // The subscription encapsulates routing (DX-1125): point-to-point messages addressed to `peer`,
128
+ // plus — when `tags` are set — swarm broadcasts whose tags intersect. The returned callback owns
129
+ // teardown, refcounted so it releases only this stream's tag registration.
130
+ let unsubscribe: UnsubscribeCallback | undefined;
131
+ void this.signalManager
132
+ .subscribeMessages({
133
+ peer,
134
+ tags,
135
+ onMessage: (message) => {
136
+ void emit.single({ ...message, payload: encodableAny(message.payload) });
137
+ },
138
+ })
139
+ .then((unsub) => {
140
+ if (ctx.disposed) {
141
+ void unsub();
142
+ } else {
143
+ unsubscribe = unsub;
144
+ }
145
+ })
146
+ .catch((err) => emit.fail(err instanceof Error ? err : new Error(String(err))));
147
+
148
+ return Effect.promise(async () => {
149
+ await unsubscribe?.();
150
+ await ctx.dispose();
84
151
  });
85
152
  });
86
153
  }
@@ -0,0 +1,18 @@
1
+ //
2
+ // Copyright 2022 DXOS.org
3
+ //
4
+
5
+ import { type Codec } from '@dxos/codec-protobuf';
6
+ import { createCodecEncoding } from '@dxos/hypercore';
7
+ import { schema } from '@dxos/protocols/proto';
8
+ import type { FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
9
+
10
+ /**
11
+ * Codec for feed messages.
12
+ */
13
+ export const codec: Codec<FeedMessage> = schema.getCodecForType('dxos.echo.feed.FeedMessage');
14
+
15
+ /**
16
+ * Value encoding used by feed store.
17
+ */
18
+ export const valueEncoding = createCodecEncoding(codec);