@dxos/client-services 0.10.0 → 0.11.1

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
@@ -3,47 +3,135 @@
3
3
  //
4
4
 
5
5
  import * as SqlClient from '@effect/sql/SqlClient';
6
+ import type * as SqlError from '@effect/sql/SqlError';
7
+ import * as EffectContext from 'effect/Context';
6
8
  import * as Effect from 'effect/Effect';
7
-
8
- import { Event, synchronized } from '@dxos/async';
9
- import { type ClientServices, clientServiceBundle } from '@dxos/client-protocol';
9
+ import * as Exit from 'effect/Exit';
10
+ import * as Layer from 'effect/Layer';
11
+ import * as ManagedRuntime from 'effect/ManagedRuntime';
12
+ import * as Option from 'effect/Option';
13
+ import * as Runtime from 'effect/Runtime';
14
+ import * as Scope from 'effect/Scope';
15
+
16
+ import { Event, Mutex, Trigger, synchronized } from '@dxos/async';
17
+ import {
18
+ type ClientServices,
19
+ type ClientServicesHandlers,
20
+ makeInProcessClientServicesRpc,
21
+ makeServicesFromRpc,
22
+ } from '@dxos/client-protocol';
10
23
  import { type Config, resolveTelemetryTag } from '@dxos/config';
11
24
  import { Context } from '@dxos/context';
12
- import { EdgeClient, type EdgeConnection, EdgeHttpClient, createStubEdgeIdentity } from '@dxos/edge-client';
13
- import { RuntimeProvider } from '@dxos/effect';
25
+ import { failUndefined, warnAfterTimeout } from '@dxos/debug';
26
+ import {
27
+ type AutomergeReplicator,
28
+ type EchoHost,
29
+ EchoHostService,
30
+ type EdgeAutomergeReplicator,
31
+ EdgeAutomergeReplicatorService,
32
+ MeshEchoReplicatorService,
33
+ runSqliteHealthCheck,
34
+ } from '@dxos/echo-host';
35
+ import {
36
+ EdgeClient,
37
+ type EdgeConnection,
38
+ EdgeHttpClient,
39
+ type EdgeIdentity,
40
+ createChainEdgeIdentity,
41
+ createEphemeralEdgeIdentity,
42
+ createStubEdgeIdentity,
43
+ } from '@dxos/edge-client';
44
+ import { EffectEx, RuntimeProvider } from '@dxos/effect';
45
+ import { type FeedStore, FeedStoreService } from '@dxos/feed-store';
14
46
  import { invariant } from '@dxos/invariant';
47
+ import { type KeyringApi, KeyringApiService } from '@dxos/keyring';
48
+ import { type SpaceId } from '@dxos/keys';
15
49
  import { log } from '@dxos/log';
16
- import { EdgeSignalManager, type SignalManager, WebsocketSignalManager } from '@dxos/messaging';
50
+ import {
51
+ EdgeSignalManager,
52
+ MemorySignalManager,
53
+ MemorySignalManagerContext,
54
+ type SignalManager,
55
+ SignalManagerService,
56
+ } from '@dxos/messaging';
17
57
  import {
18
58
  SwarmNetworkManager,
59
+ SwarmNetworkManagerService,
19
60
  type TransportFactory,
20
61
  createIceProvider,
21
62
  createRtcTransportFactory,
22
63
  } from '@dxos/network-manager';
23
- import { SystemStatus } from '@dxos/protocols/proto/dxos/client/services';
64
+ import { InvalidStorageVersionError, STORAGE_VERSION } from '@dxos/protocols';
65
+ import { Invitation, SystemStatus } from '@dxos/protocols/proto/dxos/client/services';
66
+ import { type Credential, type ProfileDocument } from '@dxos/protocols/proto/dxos/halo/credentials';
67
+ import {
68
+ ContactsService,
69
+ DataService,
70
+ DevicesService,
71
+ EdgeAgentService,
72
+ FeedService,
73
+ IdentityService,
74
+ InvitationsService,
75
+ NetworkService,
76
+ QueryService,
77
+ SpacesService,
78
+ } from '@dxos/protocols/rpc';
24
79
  import * as SqlExport from '@dxos/sql-sqlite/SqlExport';
25
80
  import type * as SqlTransaction from '@dxos/sql-sqlite/SqlTransaction';
81
+ import { type BlobStoreApi, BlobStoreApiService } from '@dxos/teleport-extension-object-sync';
26
82
  import { trace as Trace } from '@dxos/tracing';
27
83
  import { WebsocketRpcClient } from '@dxos/websocket-rpc';
28
84
 
29
- import { EdgeAgentServiceImpl } from '../agents';
30
- import { DevicesServiceImpl } from '../devices';
85
+ import { type EdgeAgentManager, EdgeAgentManagerService } from '../agents';
31
86
  import { DevtoolsHostEvents, DevtoolsServiceImpl } from '../devtools';
32
87
  import {
33
88
  type CollectDiagnosticsBroadcastHandler,
34
89
  createCollectDiagnosticsBroadcastHandler,
35
90
  createDiagnostics,
36
91
  } from '../diagnostics';
37
- import { type CreateIdentityOptions, IdentityServiceImpl } from '../identity';
38
- import { ContactsServiceImpl } from '../identity/contacts-service';
39
- import { InvitationsServiceImpl } from '../invitations';
92
+ import {
93
+ type CreateIdentityOptions,
94
+ type Identity,
95
+ type IdentityManager,
96
+ IdentityManagerService,
97
+ type JoinIdentityProps,
98
+ } from '../identity';
99
+ import {
100
+ type EdgeIdentityRecoveryManager,
101
+ EdgeIdentityRecoveryManagerService,
102
+ } from '../identity/identity-recovery-manager';
103
+ import {
104
+ DeviceInvitationProtocol,
105
+ type InvitationProtocol,
106
+ type InvitationsHandler,
107
+ InvitationsHandlerService,
108
+ type InvitationsManager,
109
+ InvitationsManagerService,
110
+ SpaceInvitationProtocol,
111
+ } from '../invitations';
40
112
  import { Lock, type ResourceLock } from '../locks';
41
113
  import { LoggingServiceImpl } from '../logging';
42
- import { NetworkServiceImpl } from '../network';
43
- import { SpacesServiceImpl } from '../spaces';
114
+ import { type IMetadataStore, IMetadataStoreService } from '../metadata';
115
+ import { type SpaceManager, SpaceManagerService } from '../space';
116
+ import {
117
+ type DataSpaceManager,
118
+ DataSpaceManagerService,
119
+ type SigningContextProvider,
120
+ SigningContextProviderService,
121
+ } from '../spaces';
44
122
  import { SystemServiceImpl } from '../system';
45
- import { ServiceContext, type ServiceContextRuntimeProps } from './service-context';
46
- import { ServiceRegistry } from './service-registry';
123
+ import { type ClientServicesRpcContext, ClientServicesRpcLayer } from './client-services-layer';
124
+ import {
125
+ type CrossDeviceSpaceSynchronizer,
126
+ CrossDeviceSpaceSynchronizerService,
127
+ } from './cross-device-space-synchronizer';
128
+ import { type FeedSyncer, FeedSyncerService } from './feed-syncer';
129
+ import {
130
+ ServiceContextLayer,
131
+ type ServiceContextRuntimeProps,
132
+ type ServiceContextStackContext,
133
+ StorageMigrationService,
134
+ } from './service-context';
47
135
 
48
136
  export type ClientServicesHostProps = {
49
137
  /**
@@ -70,40 +158,77 @@ export type InitializeOptions = {
70
158
  connectionLog?: boolean;
71
159
  };
72
160
 
161
+ // Alias for consumers (tests, devtools, diagnostics) that referred to the former `ServiceContext`
162
+ // orchestrator; its lifecycle and API now live on {@link ClientServicesHost} directly.
163
+ export type ServiceContext = ClientServicesHost;
164
+
73
165
  /**
74
- * Remote service implementation.
166
+ * Shared backend for all client services.
167
+ *
168
+ * Owns the full client stack and its lifecycle: it builds the layer-composed components (keyring,
169
+ * feed store, echo host, identity/space managers, …) plus the client RPC handlers, then drives the
170
+ * open sequence (migrate → identity → network → space initialization) and teardown. It reads or
171
+ * creates the identity, propagates it outward (`_setNetworkIdentity`), and opens the identity-bound
172
+ * services once an identity is available (`_initialize`). The host provides itself into the stack
173
+ * (via {@link ClientServicesHostService}) so the RPC handler layers can resolve the orchestration
174
+ * entry points they need without a separate `ServiceContext` service.
75
175
  */
76
- @Trace.resource()
77
176
  export class ClientServicesHost {
78
- private readonly _resourceLock?: ResourceLock;
79
- private readonly _serviceRegistry: ServiceRegistry<ClientServices>;
80
- private readonly _systemService: SystemServiceImpl;
81
- private readonly _loggingService: LoggingServiceImpl;
82
- private readonly _statusUpdate = new Event<void>();
83
-
84
- private _config?: Config;
85
- private _signalManager?: SignalManager;
86
- private _networkManager?: SwarmNetworkManager;
87
- private _callbacks?: ClientServicesHostCallbacks;
88
- private _devtoolsProxy?: WebsocketRpcClient<{}, ClientServices>;
89
- private _edgeConnection?: EdgeConnection = undefined;
90
- private _edgeHttpClient?: EdgeHttpClient = undefined;
91
-
92
- private _serviceContext!: ServiceContext;
93
- private readonly _runtime: RuntimeProvider.RuntimeProvider<
177
+ readonly #resourceLock?: ResourceLock;
178
+ // Effect-rpc handlers served over each connection, resolved from the Layer stack on open and reset
179
+ // to the host-local set on close. Held directly (no separate registry indirection).
180
+ #handlers: Partial<ClientServicesHandlers>;
181
+ readonly #systemService: SystemServiceImpl;
182
+ readonly #loggingService: LoggingServiceImpl;
183
+ readonly #statusUpdate = new Event<void>();
184
+
185
+ #config?: Config;
186
+ #signalManager?: SignalManager;
187
+ #networkManager?: SwarmNetworkManager;
188
+ #callbacks?: ClientServicesHostCallbacks;
189
+ #devtoolsProxy?: WebsocketRpcClient<{}, ClientServices>;
190
+ #edgeConnection?: EdgeConnection = undefined;
191
+ #edgeHttpClient?: EdgeHttpClient = undefined;
192
+
193
+ #stackRuntime?: ManagedRuntime.ManagedRuntime<
194
+ ClientServicesHostService | ClientServicesRpcContext | ServiceContextStackContext,
195
+ never
196
+ >;
197
+ readonly #runtime: RuntimeProvider.RuntimeProvider<
94
198
  SqlClient.SqlClient | SqlExport.SqlExport | SqlTransaction.SqlTransaction
95
199
  >;
96
- private readonly _runtimeProps: ServiceContextRuntimeProps;
97
- private diagnosticsBroadcastHandler: CollectDiagnosticsBroadcastHandler;
98
-
99
- @Trace.info()
100
- private _opening = false;
101
-
102
- @Trace.info()
103
- private _open = false;
104
-
105
- @Trace.info()
106
- private _resetting = false;
200
+ readonly #runtimeProps: ServiceContextRuntimeProps;
201
+ #diagnosticsBroadcastHandler: CollectDiagnosticsBroadcastHandler;
202
+
203
+ #opening = false;
204
+ #open = false;
205
+ #resetting = false;
206
+
207
+ // Stack components, resolved from the layer runtime on open. Present after `open` starts.
208
+ #ctx?: Context;
209
+ #metadataStore?: IMetadataStore;
210
+ #blobStore?: BlobStoreApi;
211
+ #keyring?: KeyringApi;
212
+ #feedStore?: FeedStore<any>;
213
+ #spaceManager?: SpaceManager;
214
+ #identityManager?: IdentityManager;
215
+ #recoveryManager?: EdgeIdentityRecoveryManager;
216
+ #invitations?: InvitationsHandler;
217
+ #invitationsManager?: InvitationsManager;
218
+ #echoHost?: EchoHost;
219
+ #signingContextProvider?: SigningContextProvider;
220
+ #dataSpaceManager?: DataSpaceManager;
221
+ #edgeAgentManager?: EdgeAgentManager;
222
+ #deviceSpaceSync?: CrossDeviceSpaceSynchronizer;
223
+ #meshReplicator?: AutomergeReplicator;
224
+ #echoEdgeReplicator?: EdgeAutomergeReplicator;
225
+ #feedSyncer?: FeedSyncer;
226
+ #storageMigrate?: Effect.Effect<void, SqlError.SqlError, SqlClient.SqlClient | SqlTransaction.SqlTransaction>;
227
+
228
+ // Orchestration state (formerly on `ServiceContext`).
229
+ readonly #initialized = new Trigger();
230
+ readonly #edgeIdentityUpdateMutex = new Mutex();
231
+ readonly #handlerFactories = new Map<Invitation.Kind, (invitation: Partial<Invitation>) => InvitationProtocol>();
107
232
 
108
233
  constructor({
109
234
  config,
@@ -115,19 +240,19 @@ export class ClientServicesHost {
115
240
  runtime,
116
241
  runtimeProps,
117
242
  }: ClientServicesHostProps) {
118
- this._callbacks = callbacks;
119
- this._runtime = runtime;
120
- this._runtimeProps = runtimeProps ?? {};
243
+ this.#callbacks = callbacks;
244
+ this.#runtime = runtime;
245
+ this.#runtimeProps = runtimeProps ?? {};
121
246
 
122
247
  if (config) {
123
248
  this.initialize({ config, transportFactory, signalManager });
124
249
  }
125
250
 
126
251
  if (lockKey) {
127
- this._resourceLock = new Lock({
252
+ this.#resourceLock = new Lock({
128
253
  lockKey,
129
254
  onAcquire: () => {
130
- if (!this._opening) {
255
+ if (!this.#opening) {
131
256
  void this.open(new Context());
132
257
  }
133
258
  },
@@ -136,18 +261,28 @@ export class ClientServicesHost {
136
261
  }
137
262
 
138
263
  // TODO(wittjosiah): If config is not defined here, system service will always have undefined config.
139
- this._systemService = new SystemServiceImpl({
140
- config: () => this._config,
141
- statusUpdate: this._statusUpdate,
142
- getCurrentStatus: () => (this.isOpen && !this._resetting ? SystemStatus.ACTIVE : SystemStatus.INACTIVE),
143
- getDiagnostics: () => {
144
- return createDiagnostics(this._serviceRegistry.services, this._serviceContext, this._config!);
264
+ this.#systemService = new SystemServiceImpl({
265
+ config: () => this.#config,
266
+ statusUpdate: this.#statusUpdate,
267
+ getCurrentStatus: () => (this.isOpen && !this.#resetting ? SystemStatus.ACTIVE : SystemStatus.INACTIVE),
268
+ getDiagnostics: async () => {
269
+ // Bridge the host Handlers to the proto services surface that diagnostics collection consumes.
270
+ const scope = Effect.runSync(Scope.make());
271
+ try {
272
+ const rpc = await EffectEx.runPromise(
273
+ makeInProcessClientServicesRpc(() => this.#handlers).pipe(Effect.provideService(Scope.Scope, scope)),
274
+ );
275
+ const services = makeServicesFromRpc(rpc, Runtime.defaultRuntime);
276
+ return await createDiagnostics(services, this, this.#config!);
277
+ } finally {
278
+ await EffectEx.runPromise(Scope.close(scope, Exit.void));
279
+ }
145
280
  },
146
281
  onUpdateStatus: async (status: SystemStatus) => {
147
282
  if (!this.isOpen && status === SystemStatus.ACTIVE) {
148
- await this._resourceLock?.acquire();
283
+ await this.#resourceLock?.acquire();
149
284
  } else if (this.isOpen && status === SystemStatus.INACTIVE) {
150
- await this._resourceLock?.release();
285
+ await this.#resourceLock?.release();
151
286
  }
152
287
  },
153
288
  onReset: async () => {
@@ -155,43 +290,101 @@ export class ClientServicesHost {
155
290
  },
156
291
  });
157
292
 
158
- this.diagnosticsBroadcastHandler = createCollectDiagnosticsBroadcastHandler(this._systemService);
159
- this._loggingService = new LoggingServiceImpl();
293
+ this.#diagnosticsBroadcastHandler = createCollectDiagnosticsBroadcastHandler(this.#systemService);
294
+ this.#loggingService = new LoggingServiceImpl();
160
295
 
161
- this._serviceRegistry = new ServiceRegistry<ClientServices>(clientServiceBundle, {
162
- SystemService: this._systemService,
163
- });
296
+ this.#handlers = {
297
+ SystemService: this.#systemService,
298
+ };
164
299
  }
165
300
 
166
301
  get isOpen() {
167
- return this._open;
302
+ return this.#open;
168
303
  }
169
304
 
170
305
  get config() {
171
- return this._config;
306
+ return this.#config;
172
307
  }
173
308
 
174
- get context() {
175
- return this._serviceContext;
309
+ // Self-reference retained for the former `host.context` accessor.
310
+ get context(): ClientServicesHost {
311
+ return this;
176
312
  }
177
313
 
178
- get serviceRegistry() {
179
- return this._serviceRegistry;
314
+ get services() {
315
+ return this.#handlers;
180
316
  }
181
317
 
182
- get descriptors() {
183
- return this._serviceRegistry.descriptors;
318
+ get initialized() {
319
+ return this.#initialized;
184
320
  }
185
321
 
186
- get services() {
187
- return this._serviceRegistry.services;
322
+ get identityManager(): IdentityManager {
323
+ return this.#identityManager ?? failUndefined();
324
+ }
325
+
326
+ get spaceManager(): SpaceManager {
327
+ return this.#spaceManager ?? failUndefined();
328
+ }
329
+
330
+ get metadataStore(): IMetadataStore {
331
+ return this.#metadataStore ?? failUndefined();
332
+ }
333
+
334
+ get blobStore(): BlobStoreApi {
335
+ return this.#blobStore ?? failUndefined();
336
+ }
337
+
338
+ get recoveryManager(): EdgeIdentityRecoveryManager {
339
+ return this.#recoveryManager ?? failUndefined();
340
+ }
341
+
342
+ get keyring(): KeyringApi {
343
+ return this.#keyring ?? failUndefined();
344
+ }
345
+
346
+ get feedStore(): FeedStore<any> {
347
+ return this.#feedStore ?? failUndefined();
348
+ }
349
+
350
+ get echoHost(): EchoHost {
351
+ return this.#echoHost ?? failUndefined();
352
+ }
353
+
354
+ get invitations(): InvitationsHandler {
355
+ return this.#invitations ?? failUndefined();
356
+ }
357
+
358
+ get invitationsManager(): InvitationsManager {
359
+ return this.#invitationsManager ?? failUndefined();
360
+ }
361
+
362
+ get networkManager(): SwarmNetworkManager {
363
+ return this.#networkManager ?? failUndefined();
364
+ }
365
+
366
+ get signalManager(): SignalManager {
367
+ return this.#signalManager ?? failUndefined();
368
+ }
369
+
370
+ // Present after the stack constructs it; usable once `initialized` wakes.
371
+ get dataSpaceManager(): DataSpaceManager | undefined {
372
+ return this.#dataSpaceManager;
373
+ }
374
+
375
+ get edgeAgentManager(): EdgeAgentManager | undefined {
376
+ return this.#edgeAgentManager;
377
+ }
378
+
379
+ get edgeConnection(): EdgeConnection | undefined {
380
+ return this.#edgeConnection;
188
381
  }
189
382
 
190
383
  /**
191
384
  * Debugging util.
192
385
  */
193
386
  async exportSqliteDatabase(): Promise<Uint8Array> {
194
- return await RuntimeProvider.runPromise(this._runtime)(
387
+ return await RuntimeProvider.runPromise(this.#runtime)(
195
388
  Effect.gen(function* () {
196
389
  const sql = yield* SqlExport.SqlExport;
197
390
  return yield* sql.export;
@@ -203,7 +396,7 @@ export class ClientServicesHost {
203
396
  * Debugging util.
204
397
  */
205
398
  async runSqliteQuery(query: string, params?: unknown[]): Promise<readonly Record<string, unknown>[]> {
206
- return await RuntimeProvider.runPromise(this._runtime)(
399
+ return await RuntimeProvider.runPromise(this.#runtime)(
207
400
  Effect.gen(function* () {
208
401
  const sql = yield* SqlClient.SqlClient;
209
402
  return yield* sql`${sql.unsafe(query, params)}`;
@@ -217,55 +410,52 @@ export class ClientServicesHost {
217
410
  * Can only be called once.
218
411
  */
219
412
  initialize({ config, ...options }: InitializeOptions): void {
220
- invariant(!this._open, 'service host is open');
413
+ invariant(!this.#open, 'service host is open');
221
414
  log('initializing...');
222
415
 
223
416
  if (config) {
224
- if (this._runtimeProps.disableP2pReplication === undefined) {
225
- this._runtimeProps.disableP2pReplication = config?.get('runtime.client.disableP2pReplication', false);
417
+ if (this.#runtimeProps.disableP2pReplication === undefined) {
418
+ this.#runtimeProps.disableP2pReplication = config?.get('runtime.client.disableP2pReplication', false);
226
419
  }
227
- if (this._runtimeProps.enableVectorIndexing === undefined) {
228
- this._runtimeProps.enableVectorIndexing = config?.get('runtime.client.enableVectorIndexing', false);
420
+ if (this.#runtimeProps.enableVectorIndexing === undefined) {
421
+ this.#runtimeProps.enableVectorIndexing = config?.get('runtime.client.enableVectorIndexing', false);
229
422
  }
230
423
 
231
- invariant(!this._config, 'config already set');
232
- this._config = config;
233
- }
234
-
235
- // TODO(wittjosiah): This is quite noisy during tests. Make configurable? Remove?
236
- if (!options.signalManager) {
237
- // log.warn('running signaling without telemetry metadata.');
424
+ invariant(!this.#config, 'config already set');
425
+ this.#config = config;
238
426
  }
239
427
 
240
428
  const endpoint = config?.get('runtime.services.edge.url');
241
429
  if (endpoint) {
242
430
  const clientTag = resolveTelemetryTag(config);
243
- this._edgeConnection = new EdgeClient(createStubEdgeIdentity(), { socketEndpoint: endpoint, clientTag });
244
- this._edgeHttpClient = new EdgeHttpClient(endpoint, { clientTag });
431
+ this.#edgeConnection = new EdgeClient(createStubEdgeIdentity(), { socketEndpoint: endpoint, clientTag });
432
+ this.#edgeHttpClient = new EdgeHttpClient(endpoint, { clientTag });
245
433
  }
246
434
 
247
435
  const {
248
436
  connectionLog = true,
249
437
  transportFactory = createRtcTransportFactory(
250
- { iceServers: this._config?.get('runtime.services.ice') },
251
- this._config?.get('runtime.services.iceProviders') &&
252
- createIceProvider(this._config!.get('runtime.services.iceProviders')!),
438
+ { iceServers: this.#config?.get('runtime.services.ice') },
439
+ this.#config?.get('runtime.services.iceProviders') &&
440
+ createIceProvider(this.#config!.get('runtime.services.iceProviders')!),
253
441
  ),
254
- signalManager = this._edgeConnection && this._config?.get('runtime.client.edgeFeatures')?.signaling
255
- ? new EdgeSignalManager({ edgeConnection: this._edgeConnection })
256
- : new WebsocketSignalManager(this._config?.get('runtime.services.signaling') ?? []),
442
+ // Edge is the only real signaling transport; without it fall back to an isolated in-memory
443
+ // manager (no cross-process signaling). The former KUBE `WebsocketSignalManager` is removed.
444
+ signalManager = this.#edgeConnection && this.#config?.get('runtime.client.edgeFeatures')?.signaling
445
+ ? new EdgeSignalManager({ edgeConnection: this.#edgeConnection })
446
+ : new MemorySignalManager(new MemorySignalManagerContext()),
257
447
  } = options;
258
- this._signalManager = signalManager;
448
+ this.#signalManager = signalManager;
259
449
 
260
- invariant(!this._networkManager, 'network manager already set');
261
- this._networkManager = new SwarmNetworkManager({
450
+ invariant(!this.#networkManager, 'network manager already set');
451
+ this.#networkManager = new SwarmNetworkManager({
262
452
  enableDevtoolsLogging: connectionLog,
263
453
  transportFactory,
264
454
  signalManager,
265
- peerInfo: this._edgeConnection
455
+ peerInfo: this.#edgeConnection
266
456
  ? {
267
- identityDid: this._edgeConnection.identityDid,
268
- peerKey: this._edgeConnection.peerKey,
457
+ identityDid: this.#edgeConnection.identityDid,
458
+ peerKey: this.#edgeConnection.peerKey,
269
459
  }
270
460
  : undefined,
271
461
  });
@@ -275,139 +465,190 @@ export class ClientServicesHost {
275
465
 
276
466
  @synchronized
277
467
  @Trace.span()
278
- async open(ctx: Context): Promise<void> {
279
- if (this._open) {
468
+ async open(ctx: Context = new Context()): Promise<void> {
469
+ if (this.#open) {
280
470
  return;
281
471
  }
282
472
 
283
473
  log('opening service host');
284
474
 
285
- invariant(this._config, 'config not set');
286
- invariant(this._signalManager, 'signal manager not set');
287
- invariant(this._networkManager, 'network manager not set');
288
-
289
- this._opening = true;
290
- log('opening...', { lockKey: this._resourceLock?.lockKey });
291
-
292
- await this._resourceLock?.acquire();
293
-
294
- await this._loggingService.open();
295
-
296
- this._serviceContext = new ServiceContext(
297
- this._networkManager,
298
- this._signalManager,
299
- this._edgeConnection,
300
- this._edgeHttpClient,
301
- this._runtime,
302
- this._runtimeProps,
303
- this._config.get('runtime.client.edgeFeatures'),
475
+ invariant(this.#config, 'config not set');
476
+ invariant(this.#signalManager, 'signal manager not set');
477
+ invariant(this.#networkManager, 'network manager not set');
478
+ const config = this.#config;
479
+ const signalManager = this.#signalManager;
480
+ const networkManager = this.#networkManager;
481
+
482
+ this.#opening = true;
483
+ this.#ctx = ctx;
484
+ log('opening...', { lockKey: this.#resourceLock?.lockKey });
485
+
486
+ await this.#resourceLock?.acquire();
487
+
488
+ await this.#loggingService.open();
489
+
490
+ // Build a single runtime from the component layer stack plus the client RPC handlers, providing
491
+ // the host itself so the handler layers can resolve their orchestration entry points.
492
+ const stackLayer = ClientServicesRpcLayer.pipe(
493
+ Layer.provideMerge(Layer.succeed(ClientServicesHostService, this)),
494
+ Layer.provideMerge(
495
+ ServiceContextLayer({
496
+ ...this.#runtimeProps,
497
+ edgeFeatures: config.get('runtime.client.edgeFeatures'),
498
+ edgeConnection: this.#edgeConnection,
499
+ edgeHttpClient: this.#edgeHttpClient,
500
+ }),
501
+ ),
502
+ Layer.provideMerge(Layer.succeed(SwarmNetworkManagerService, networkManager)),
503
+ Layer.provideMerge(Layer.succeed(SignalManagerService, signalManager)),
504
+ Layer.provideMerge(RuntimeProvider.toLayer(this.#runtime)),
505
+ Layer.orDie,
304
506
  );
305
-
306
- const dataSpaceManagerProvider = async () => {
307
- await this._serviceContext.initialized.wait();
308
- return this._serviceContext.dataSpaceManager!;
309
- };
310
-
311
- const agentManagerProvider = async () => {
312
- await this._serviceContext.initialized.wait();
313
- return this._serviceContext.edgeAgentManager!;
314
- };
315
-
316
- const identityService = new IdentityServiceImpl(
317
- this._serviceContext.identityManager,
318
- this._serviceContext.recoveryManager,
319
- this._serviceContext.keyring,
320
- (params, ctx) => this._createIdentity(params, ctx),
321
- (profile) => this._serviceContext.broadcastProfileUpdate(profile),
507
+ this.#stackRuntime = ManagedRuntime.make(stackLayer);
508
+ const resolved = await this.#stackRuntime.runPromise(
509
+ Effect.all({
510
+ // Components.
511
+ metadataStore: IMetadataStoreService,
512
+ blobStore: BlobStoreApiService,
513
+ keyring: KeyringApiService,
514
+ feedStore: FeedStoreService,
515
+ spaceManager: SpaceManagerService,
516
+ identityManager: IdentityManagerService,
517
+ recoveryManager: EdgeIdentityRecoveryManagerService,
518
+ invitations: InvitationsHandlerService,
519
+ invitationsManager: InvitationsManagerService,
520
+ echoHost: EchoHostService,
521
+ signingContextProvider: SigningContextProviderService,
522
+ dataSpaceManager: DataSpaceManagerService,
523
+ edgeAgentManager: EdgeAgentManagerService,
524
+ deviceSpaceSync: CrossDeviceSpaceSynchronizerService,
525
+ storageMigrate: StorageMigrationService,
526
+ meshReplicator: Effect.serviceOption(MeshEchoReplicatorService),
527
+ echoEdgeReplicator: Effect.serviceOption(EdgeAutomergeReplicatorService),
528
+ feedSyncer: Effect.serviceOption(FeedSyncerService),
529
+ // Handlers.
530
+ identityService: IdentityService.Tag,
531
+ contactsService: ContactsService.Tag,
532
+ invitationsService: InvitationsService.Tag,
533
+ devicesService: DevicesService.Tag,
534
+ spacesService: SpacesService.Tag,
535
+ networkService: NetworkService.Tag,
536
+ edgeAgentService: EdgeAgentService.Tag,
537
+ dataService: DataService.Tag,
538
+ queryService: QueryService.Tag,
539
+ feedService: FeedService.Tag,
540
+ }),
322
541
  );
323
542
 
324
- this._serviceRegistry.setServices({
325
- SystemService: this._systemService,
326
- IdentityService: identityService,
327
- ContactsService: new ContactsServiceImpl(
328
- this._serviceContext.identityManager,
329
- this._serviceContext.spaceManager,
330
- dataSpaceManagerProvider,
331
- ),
332
-
333
- InvitationsService: new InvitationsServiceImpl(this._serviceContext.invitationsManager),
334
-
335
- DevicesService: new DevicesServiceImpl(this._serviceContext.identityManager, this._edgeConnection),
336
-
337
- SpacesService: new SpacesServiceImpl(
338
- this._serviceContext.identityManager,
339
- this._serviceContext.spaceManager,
340
- this._serviceContext.echoHost,
341
- dataSpaceManagerProvider,
342
- ),
343
-
344
- DataService: this._serviceContext.echoHost.dataService,
345
- QueryService: this._serviceContext.echoHost.queryService,
346
- QueueService: this._serviceContext.echoHost.queuesService,
347
-
348
- NetworkService: new NetworkServiceImpl(
349
- this._serviceContext.networkManager,
350
- this._serviceContext.signalManager,
351
- this._edgeConnection,
352
- ),
353
-
354
- LoggingService: this._loggingService,
543
+ this.#metadataStore = resolved.metadataStore;
544
+ this.#blobStore = resolved.blobStore;
545
+ this.#keyring = resolved.keyring;
546
+ this.#feedStore = resolved.feedStore;
547
+ this.#spaceManager = resolved.spaceManager;
548
+ this.#identityManager = resolved.identityManager;
549
+ this.#recoveryManager = resolved.recoveryManager;
550
+ this.#invitations = resolved.invitations;
551
+ this.#invitationsManager = resolved.invitationsManager;
552
+ this.#echoHost = resolved.echoHost;
553
+ this.#signingContextProvider = resolved.signingContextProvider;
554
+ this.#dataSpaceManager = resolved.dataSpaceManager;
555
+ this.#edgeAgentManager = resolved.edgeAgentManager;
556
+ this.#deviceSpaceSync = resolved.deviceSpaceSync;
557
+ this.#storageMigrate = resolved.storageMigrate;
558
+ this.#meshReplicator = Option.getOrUndefined(resolved.meshReplicator);
559
+ this.#echoEdgeReplicator = Option.getOrUndefined(resolved.echoEdgeReplicator);
560
+ this.#feedSyncer = Option.getOrUndefined(resolved.feedSyncer);
561
+
562
+ // Wire the setters for components that point "up the stack".
563
+ this.#handlerFactories.set(
564
+ Invitation.Kind.DEVICE,
565
+ () =>
566
+ new DeviceInvitationProtocol(
567
+ this.#keyring!,
568
+ () => this.#identityManager!.identity ?? failUndefined(),
569
+ this._acceptIdentity.bind(this),
570
+ ),
571
+ );
572
+ this.#recoveryManager.setAcceptRecoveredIdentity((params) => this._acceptIdentity(params));
573
+ this.#invitationsManager.setInvitationHandlerFactory((invitation) => this.getInvitationHandler(invitation));
574
+ this.#echoHost.setFeedSyncHandlers({
575
+ syncFeed: async (feedSyncCtx, request) =>
576
+ this.#feedSyncer?.syncBlocking(feedSyncCtx, {
577
+ spaceId: request.spaceId as SpaceId,
578
+ subspaceTag: request.subspaceTag,
579
+ shouldPush: request.shouldPush,
580
+ shouldPull: request.shouldPull,
581
+ }),
582
+ getSyncState: async (feedSyncCtx, request) => {
583
+ if (!this.#feedSyncer) {
584
+ return { namespaces: [] };
585
+ }
586
+ return this.#feedSyncer.getSyncState(feedSyncCtx, request);
587
+ },
588
+ });
355
589
 
590
+ this.#handlers = {
591
+ SystemService: this.#systemService,
592
+ IdentityService: resolved.identityService,
593
+ ContactsService: resolved.contactsService,
594
+ InvitationsService: resolved.invitationsService,
595
+ DevicesService: resolved.devicesService,
596
+ SpacesService: resolved.spacesService,
597
+ DataService: resolved.dataService,
598
+ QueryService: resolved.queryService,
599
+ FeedService: resolved.feedService,
600
+ NetworkService: resolved.networkService,
601
+ LoggingService: this.#loggingService,
356
602
  // TODO(burdon): Move to new protobuf definitions.
357
603
  DevtoolsHost: new DevtoolsServiceImpl({
358
604
  events: new DevtoolsHostEvents(),
359
- config: this._config,
360
- context: this._serviceContext,
605
+ config: this.#config,
606
+ context: this,
361
607
  exportSqliteDatabase: () => this.exportSqliteDatabase(),
362
608
  runSqliteQuery: (query, params) => this.runSqliteQuery(query, params),
363
609
  }),
610
+ EdgeAgentService: resolved.edgeAgentService,
611
+ };
364
612
 
365
- EdgeAgentService: new EdgeAgentServiceImpl(agentManagerProvider, this._edgeConnection),
366
- });
367
-
368
- log('service-host: opening service context...');
369
- await this._serviceContext.open(ctx);
370
- log('service-host: service context opened');
371
-
372
- log('service-host: opening identity service...');
373
- await identityService.open();
374
- log('service-host: identity service opened');
613
+ // Run the open lifecycle stages (formerly ServiceContext._open).
614
+ // The identity service impl's open/close lifecycle is owned by its layer scope in
615
+ // {@link ClientServicesRpcLayer} and runs when the stack runtime is disposed.
616
+ await this._openStack(ctx);
375
617
 
376
- const devtoolsProxy = this._config?.get('runtime.client.devtoolsProxy');
618
+ const devtoolsProxy = this.#config?.get('runtime.client.devtoolsProxy');
377
619
  if (devtoolsProxy) {
378
- this._devtoolsProxy = new WebsocketRpcClient({
379
- url: devtoolsProxy,
380
- requested: {},
381
- exposed: clientServiceBundle,
382
- handlers: this.services as ClientServices,
383
- });
384
- void this._devtoolsProxy.open();
620
+ // TODO(dxos): The devtools websocket proxy serves the protobuf service bundle, which is
621
+ // incompatible with the effect-rpc Handlers the host now provides. Re-enable once this legacy
622
+ // transport is migrated to effect-rpc (or bridged via makeInProcessClient + a proto adapter).
623
+ log.warn('devtoolsProxy is not supported with effect-rpc services; skipping', { devtoolsProxy });
385
624
  }
386
- this.diagnosticsBroadcastHandler.start();
625
+ this.#diagnosticsBroadcastHandler.start();
387
626
 
388
- this._opening = false;
389
- this._open = true;
390
- this._statusUpdate.emit();
391
- const deviceKey = this._serviceContext.identityManager.identity?.deviceKey;
627
+ this.#opening = false;
628
+ this.#open = true;
629
+ this.#statusUpdate.emit();
630
+ const deviceKey = this.#identityManager?.identity?.deviceKey;
392
631
  log('opened', { deviceKey });
393
632
  }
394
633
 
395
634
  @synchronized
396
635
  @Trace.span()
397
- async close(ctx: Context): Promise<void> {
398
- if (!this._open) {
636
+ async close(ctx: Context = Context.default()): Promise<void> {
637
+ if (!this.#open) {
399
638
  return;
400
639
  }
401
640
 
402
- const deviceKey = this._serviceContext.identityManager.identity?.deviceKey;
641
+ const deviceKey = this.#identityManager?.identity?.deviceKey;
403
642
  log('closing...', { deviceKey });
404
- this.diagnosticsBroadcastHandler.stop();
405
- await this._devtoolsProxy?.close();
406
- this._serviceRegistry.setServices({ SystemService: this._systemService });
407
- await this._loggingService.close();
408
- await this._serviceContext.close();
409
- this._open = false;
410
- this._statusUpdate.emit();
643
+ this.#diagnosticsBroadcastHandler.stop();
644
+ await this.#devtoolsProxy?.close();
645
+ this.#handlers = { SystemService: this.#systemService };
646
+ await this.#loggingService.close();
647
+ await this._closeStack(ctx);
648
+ await this.#stackRuntime?.dispose();
649
+ this.#stackRuntime = undefined;
650
+ this.#open = false;
651
+ this.#statusUpdate.emit();
411
652
  log('closed', { deviceKey });
412
653
  }
413
654
 
@@ -415,11 +656,13 @@ export class ClientServicesHost {
415
656
  log.info('resetting...');
416
657
  // Emit this status update immediately so app returns to fallback.
417
658
  // This state is never cleared because the app reloads.
418
- this._resetting = true;
419
- this._statusUpdate.emit();
420
- await this._serviceContext?.close();
659
+ this.#resetting = true;
660
+ this.#statusUpdate.emit();
661
+ if (this.#open) {
662
+ await this._closeStack(this.#ctx ?? Context.default());
663
+ }
421
664
  // Wipe all SQLite tables so next open starts fresh.
422
- await RuntimeProvider.runPromise(this._runtime)(
665
+ await RuntimeProvider.runPromise(this.#runtime)(
423
666
  Effect.gen(function* () {
424
667
  const sql = yield* SqlClient.SqlClient;
425
668
  // Echo metadata + large space data.
@@ -449,12 +692,249 @@ export class ClientServicesHost {
449
692
  }),
450
693
  );
451
694
  log.info('reset');
452
- await this._callbacks?.onReset?.();
695
+ await this.#callbacks?.onReset?.();
696
+ }
697
+
698
+ //
699
+ // Orchestration (formerly ServiceContext).
700
+ //
701
+
702
+ async createIdentity(params: CreateIdentityOptions = {}, ctx?: Context): Promise<Identity> {
703
+ ctx ??= this.#ctx ?? Context.default();
704
+ const identity = await this.identityManager.createIdentity(params, ctx);
705
+ await this._setNetworkIdentity({ identity });
706
+ await identity.joinNetwork(ctx);
707
+ await this._initialize(ctx);
708
+ return identity;
709
+ }
710
+
711
+ getInvitationHandler(invitation: Partial<Invitation> & Pick<Invitation, 'kind'>): InvitationProtocol {
712
+ if (this.identityManager.identity == null && invitation.kind === Invitation.Kind.SPACE) {
713
+ throw new Error('Identity must be created before joining a space.');
714
+ }
715
+ const factory = this.#handlerFactories.get(invitation.kind);
716
+ invariant(factory, `Unknown invitation kind: ${invitation.kind}`);
717
+ return factory(invitation);
718
+ }
719
+
720
+ async broadcastProfileUpdate(profile: ProfileDocument | undefined): Promise<void> {
721
+ if (!profile || !this.#dataSpaceManager) {
722
+ return;
723
+ }
724
+ for (const space of this.#dataSpaceManager.spaces.values()) {
725
+ await space.updateOwnProfile(profile);
726
+ }
453
727
  }
454
728
 
455
- private async _createIdentity(params: CreateIdentityOptions, ctx?: Context) {
456
- const identity = await this._serviceContext.createIdentity(params, ctx);
457
- await this._serviceContext.initialized.wait();
729
+ /**
730
+ * Resolves the {@link DataSpaceManager} once identity-bound services have opened (`initialized`).
731
+ */
732
+ async whenDataSpaceManagerReady(): Promise<DataSpaceManager> {
733
+ await this.#initialized.wait();
734
+ return this.#dataSpaceManager ?? failUndefined();
735
+ }
736
+
737
+ /**
738
+ * Resolves the {@link EdgeAgentManager} once identity-bound services have opened (`initialized`).
739
+ */
740
+ async whenEdgeAgentManagerReady(): Promise<EdgeAgentManager> {
741
+ await this.#initialized.wait();
742
+ return this.#edgeAgentManager ?? failUndefined();
743
+ }
744
+
745
+ private async _acceptIdentity(params: JoinIdentityProps): Promise<Identity> {
746
+ const ctx = this.#ctx ?? Context.default();
747
+ const { identity, identityRecord } = await this.identityManager.prepareIdentity(params, ctx);
748
+ await this._setNetworkIdentity({ deviceCredential: params.authorizedDeviceCredential!, identity });
749
+ await identity.joinNetwork(ctx);
750
+ await this.identityManager.acceptIdentity(identity, identityRecord, params.deviceProfile);
751
+ await this._initialize(ctx);
458
752
  return identity;
459
753
  }
754
+
755
+ private async _checkStorageVersion(): Promise<void> {
756
+ await this.metadataStore.load();
757
+ if (this.metadataStore.version !== STORAGE_VERSION) {
758
+ throw new InvalidStorageVersionError(STORAGE_VERSION, this.metadataStore.version);
759
+ // TODO(mykola): Migrate storage to a new version if incompatibility is detected.
760
+ }
761
+ }
762
+
763
+ /**
764
+ * Opens the identity-bound services once an identity is available.
765
+ */
766
+ @Trace.span()
767
+ private async _initialize(ctx: Context): Promise<void> {
768
+ log('_initialize: start');
769
+ const identity = this.identityManager.identity ?? failUndefined();
770
+
771
+ await this.#dataSpaceManager!.open(ctx);
772
+ log('_initialize: DataSpaceManager opened');
773
+
774
+ await this.#edgeAgentManager!.open(ctx);
775
+ log('_initialize: EdgeAgentManager opened');
776
+
777
+ this.#handlerFactories.set(
778
+ Invitation.Kind.SPACE,
779
+ (invitation) =>
780
+ new SpaceInvitationProtocol(
781
+ this.#dataSpaceManager!,
782
+ this.#signingContextProvider!(),
783
+ this.#keyring!,
784
+ invitation.spaceKey,
785
+ ),
786
+ );
787
+ this.#initialized.wake();
788
+
789
+ this.#deviceSpaceSync!.setIdentity(identity);
790
+ await this.#deviceSpaceSync!.open?.(ctx);
791
+ }
792
+
793
+ private async _setNetworkIdentity(params?: { deviceCredential?: Credential; identity?: Identity }): Promise<void> {
794
+ log('_setNetworkIdentity: acquiring mutex...');
795
+ using _ = await this.#edgeIdentityUpdateMutex.acquire();
796
+ log('_setNetworkIdentity: mutex acquired');
797
+
798
+ let edgeIdentity: EdgeIdentity;
799
+ const identity = params?.identity;
800
+ if (identity) {
801
+ if (params?.deviceCredential) {
802
+ edgeIdentity = await createChainEdgeIdentity(
803
+ identity.signer,
804
+ identity.identityKey,
805
+ identity.deviceKey,
806
+ { credential: params.deviceCredential },
807
+ [], // TODO(dmaretskyi): Service access credentials.
808
+ );
809
+ } else {
810
+ // TODO: throw here or from identity if device chain can't be loaded, to avoid indefinite hangup
811
+ await warnAfterTimeout(10_000, 'Waiting for identity to be ready for edge connection', async () => {
812
+ await identity.ready();
813
+ });
814
+
815
+ invariant(identity.deviceCredentialChain);
816
+
817
+ edgeIdentity = await createChainEdgeIdentity(
818
+ identity.signer,
819
+ identity.identityKey,
820
+ identity.deviceKey,
821
+ identity.deviceCredentialChain,
822
+ [], // TODO(dmaretskyi): Service access credentials.
823
+ );
824
+ }
825
+ } else {
826
+ edgeIdentity = await createEphemeralEdgeIdentity();
827
+ }
828
+
829
+ this.#edgeConnection?.setIdentity(edgeIdentity);
830
+ this.#edgeHttpClient?.setIdentity(edgeIdentity);
831
+ this.networkManager.setPeerInfo({
832
+ identityDid: edgeIdentity.identityDid,
833
+ peerKey: edgeIdentity.peerKey,
834
+ });
835
+ log('_setNetworkIdentity: done');
836
+ }
837
+
838
+ /**
839
+ * Open lifecycle stages over the resolved stack components (formerly ServiceContext._open).
840
+ */
841
+ private async _openStack(ctx: Context): Promise<void> {
842
+ log('running storage migrations...');
843
+ await RuntimeProvider.runPromise(this.#runtime)(this.#storageMigrate!);
844
+
845
+ await this._checkStorageVersion();
846
+
847
+ log('running sqlite health check...');
848
+ await runSqliteHealthCheck(this.#runtime);
849
+ log('sqlite health check passed');
850
+
851
+ log('opening identityManager...');
852
+ await this.#identityManager!.open(ctx);
853
+ log('identityManager opened', { hasIdentity: !!this.#identityManager!.identity });
854
+
855
+ log('setting network identity...');
856
+ await this._setNetworkIdentity({ identity: this.#identityManager!.identity });
857
+
858
+ log('opening edge connection...');
859
+ await this.#edgeConnection?.open(ctx);
860
+
861
+ log('opening signal manager...');
862
+ await this.#signalManager!.open(ctx);
863
+
864
+ log('opening network manager...');
865
+ await this.#networkManager!.open();
866
+
867
+ // EchoHost open/close is owned by its layer scope; the host is already open here.
868
+ if (this.#meshReplicator) {
869
+ log('adding mesh replicator...');
870
+ await this.#echoHost!.addReplicator(ctx, this.#meshReplicator);
871
+ }
872
+ if (this.#echoEdgeReplicator) {
873
+ log('adding edge replicator...');
874
+ await this.#echoHost!.addReplicator(ctx, this.#echoEdgeReplicator);
875
+ }
876
+
877
+ log('loading metadata store...');
878
+ await this.#metadataStore!.load();
879
+
880
+ log('opening space manager...');
881
+ await this.#spaceManager!.open();
882
+
883
+ if (this.#identityManager!.identity) {
884
+ log('joining network...');
885
+ await this.#identityManager!.identity.joinNetwork(ctx);
886
+
887
+ log('initializing spaces...');
888
+ await this._initialize(ctx);
889
+ } else {
890
+ log('no identity, skipping network join and space initialization');
891
+ }
892
+
893
+ log('opening feed syncer...');
894
+ await this.#feedSyncer?.open(ctx);
895
+
896
+ log('loading persistent invitations...');
897
+ const loadedInvitations = await this.#invitationsManager!.loadPersistentInvitations(ctx);
898
+ log('loaded persistent invitations', { count: loadedInvitations.invitations?.length });
899
+
900
+ log('stack opened');
901
+ }
902
+
903
+ /**
904
+ * Close lifecycle stages (formerly ServiceContext._close).
905
+ */
906
+ private async _closeStack(ctx: Context): Promise<void> {
907
+ log('closing stack...');
908
+ await this.#feedSyncer?.close();
909
+ await this.#deviceSpaceSync?.close?.();
910
+ await this.#dataSpaceManager?.close(ctx);
911
+ await this.#edgeAgentManager?.close();
912
+ await this.#identityManager?.close(ctx);
913
+ await this.#spaceManager?.close();
914
+ // EchoHost close is owned by its layer scope and runs when the runtime is disposed.
915
+
916
+ await this.#networkManager?.close(ctx);
917
+ await this.#signalManager?.close();
918
+ await this.#edgeConnection?.close();
919
+ await this.#feedStore?.close();
920
+ await this.#metadataStore?.close();
921
+ log('stack closed');
922
+ }
460
923
  }
924
+
925
+ /**
926
+ * Context tag for the {@link ClientServicesHost}. The host provides itself under this tag when it
927
+ * builds its component stack, so client RPC handler layers can resolve the orchestration entry
928
+ * points (`createIdentity`, readiness gates, …) they need.
929
+ */
930
+ export class ClientServicesHostService extends EffectContext.Tag('@dxos/client-services/ClientServicesHost')<
931
+ ClientServicesHostService,
932
+ ClientServicesHost
933
+ >() {}
934
+
935
+ /**
936
+ * Layer that constructs a {@link ClientServicesHost} from its props and exposes it under
937
+ * {@link ClientServicesHostService}. Host lifecycle (`open` / `close`) stays caller-driven.
938
+ */
939
+ export const layerClientServicesHost = (props: ClientServicesHostProps): Layer.Layer<ClientServicesHostService> =>
940
+ Layer.sync(ClientServicesHostService, () => new ClientServicesHost(props));