reactor-effect-client 0.7.0 → 0.8.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 (514) hide show
  1. package/NOTICE +7 -2
  2. package/README.md +103 -141
  3. package/dist/Coordinator.d.ts +382 -0
  4. package/dist/Coordinator.d.ts.map +1 -0
  5. package/dist/Coordinator.js +732 -0
  6. package/dist/Coordinator.js.map +1 -0
  7. package/dist/H3.d.ts +159 -0
  8. package/dist/H3.d.ts.map +1 -0
  9. package/dist/H3.js +21 -0
  10. package/dist/H3.js.map +1 -0
  11. package/dist/H3Source.d.ts +93 -0
  12. package/dist/H3Source.d.ts.map +1 -0
  13. package/dist/H3Source.js +401 -0
  14. package/dist/H3Source.js.map +1 -0
  15. package/dist/LocalSource.d.ts +68 -0
  16. package/dist/LocalSource.d.ts.map +1 -0
  17. package/dist/LocalSource.js +354 -0
  18. package/dist/LocalSource.js.map +1 -0
  19. package/dist/Media.d.ts +106 -0
  20. package/dist/Media.d.ts.map +1 -0
  21. package/dist/Media.js +16 -0
  22. package/dist/Media.js.map +1 -0
  23. package/dist/Peer.d.ts +134 -0
  24. package/dist/Peer.d.ts.map +1 -0
  25. package/dist/Peer.js +57 -0
  26. package/dist/Peer.js.map +1 -0
  27. package/dist/Playout.d.ts +724 -0
  28. package/dist/Playout.d.ts.map +1 -0
  29. package/dist/Playout.js +28 -0
  30. package/dist/Playout.js.map +1 -0
  31. package/dist/Reactor.d.ts +135 -0
  32. package/dist/Reactor.d.ts.map +1 -0
  33. package/dist/Reactor.js +158 -0
  34. package/dist/Reactor.js.map +1 -0
  35. package/dist/ReactorError.d.ts +331 -0
  36. package/dist/ReactorError.d.ts.map +1 -0
  37. package/dist/ReactorError.js +316 -0
  38. package/dist/ReactorError.js.map +1 -0
  39. package/dist/ReactorTest.d.ts +383 -0
  40. package/dist/ReactorTest.d.ts.map +1 -0
  41. package/dist/ReactorTest.js +317 -0
  42. package/dist/ReactorTest.js.map +1 -0
  43. package/dist/Session.d.ts +314 -0
  44. package/dist/Session.d.ts.map +1 -0
  45. package/dist/Session.js +51 -0
  46. package/dist/Session.js.map +1 -0
  47. package/dist/index.d.ts +12 -22
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +12 -11
  50. package/dist/index.js.map +1 -1
  51. package/dist/internal/correlator.d.ts +46 -0
  52. package/dist/internal/correlator.d.ts.map +1 -0
  53. package/dist/internal/correlator.js +150 -0
  54. package/dist/internal/correlator.js.map +1 -0
  55. package/dist/internal/deadline.d.ts +24 -0
  56. package/dist/internal/deadline.d.ts.map +1 -0
  57. package/dist/internal/deadline.js +44 -0
  58. package/dist/internal/deadline.js.map +1 -0
  59. package/dist/{h3/_internal/contracts.d.ts → internal/h3/commands.d.ts} +39 -33
  60. package/dist/internal/h3/commands.d.ts.map +1 -0
  61. package/dist/internal/h3/commands.js +128 -0
  62. package/dist/internal/h3/commands.js.map +1 -0
  63. package/dist/{h3 → internal/h3}/messages.d.ts +16 -11
  64. package/dist/internal/h3/messages.d.ts.map +1 -0
  65. package/dist/{h3 → internal/h3}/messages.js +19 -31
  66. package/dist/internal/h3/messages.js.map +1 -0
  67. package/dist/internal/h3/operations.d.ts +145 -0
  68. package/dist/internal/h3/operations.d.ts.map +1 -0
  69. package/dist/internal/h3/operations.js +284 -0
  70. package/dist/internal/h3/operations.js.map +1 -0
  71. package/dist/{h3 → internal/h3}/profile.d.ts +63 -65
  72. package/dist/internal/h3/profile.d.ts.map +1 -0
  73. package/dist/{h3 → internal/h3}/profile.js +39 -39
  74. package/dist/internal/h3/profile.js.map +1 -0
  75. package/dist/internal/h3/provider.d.ts +14 -0
  76. package/dist/internal/h3/provider.d.ts.map +1 -0
  77. package/dist/internal/h3/provider.js +672 -0
  78. package/dist/internal/h3/provider.js.map +1 -0
  79. package/dist/internal/h3/references.d.ts +68 -0
  80. package/dist/internal/h3/references.d.ts.map +1 -0
  81. package/dist/internal/h3/references.js +339 -0
  82. package/dist/internal/h3/references.js.map +1 -0
  83. package/dist/internal/h3/request.d.ts +56 -0
  84. package/dist/internal/h3/request.d.ts.map +1 -0
  85. package/dist/internal/h3/request.js +70 -0
  86. package/dist/internal/h3/request.js.map +1 -0
  87. package/dist/internal/h3/state.d.ts +120 -0
  88. package/dist/internal/h3/state.d.ts.map +1 -0
  89. package/dist/internal/h3/state.js +270 -0
  90. package/dist/internal/h3/state.js.map +1 -0
  91. package/dist/{Submission.d.ts → internal/h3/submission.d.ts} +1 -3
  92. package/dist/internal/h3/submission.d.ts.map +1 -0
  93. package/dist/{Submission.js → internal/h3/submission.js} +1 -9
  94. package/dist/internal/h3/submission.js.map +1 -0
  95. package/dist/internal/hub.d.ts +32 -0
  96. package/dist/internal/hub.d.ts.map +1 -0
  97. package/dist/internal/hub.js +92 -0
  98. package/dist/internal/hub.js.map +1 -0
  99. package/dist/internal/playout/errors.d.ts +51 -0
  100. package/dist/internal/playout/errors.d.ts.map +1 -0
  101. package/dist/internal/playout/errors.js +40 -0
  102. package/dist/internal/playout/errors.js.map +1 -0
  103. package/dist/internal/playout/policy.d.ts +559 -0
  104. package/dist/internal/playout/policy.d.ts.map +1 -0
  105. package/dist/internal/playout/policy.js +2338 -0
  106. package/dist/internal/playout/policy.js.map +1 -0
  107. package/dist/internal/playout/runtime.d.ts +28 -0
  108. package/dist/internal/playout/runtime.d.ts.map +1 -0
  109. package/dist/internal/playout/runtime.js +766 -0
  110. package/dist/internal/playout/runtime.js.map +1 -0
  111. package/dist/internal/playout/tag.d.ts +14 -0
  112. package/dist/internal/playout/tag.d.ts.map +1 -0
  113. package/dist/internal/playout/tag.js +43 -0
  114. package/dist/internal/playout/tag.js.map +1 -0
  115. package/dist/internal/proto/reactor_wire/v1/common_pb.d.ts +55 -0
  116. package/dist/internal/proto/reactor_wire/v1/common_pb.d.ts.map +1 -0
  117. package/dist/internal/proto/reactor_wire/v1/common_pb.js +42 -0
  118. package/dist/internal/proto/reactor_wire/v1/common_pb.js.map +1 -0
  119. package/dist/internal/proto/reactor_wire/v1/control_pb.d.ts +160 -0
  120. package/dist/internal/proto/reactor_wire/v1/control_pb.d.ts.map +1 -0
  121. package/dist/internal/proto/reactor_wire/v1/control_pb.js +21 -0
  122. package/dist/internal/proto/reactor_wire/v1/control_pb.js.map +1 -0
  123. package/dist/internal/proto/reactor_wire/v1/data_pb.d.ts +89 -0
  124. package/dist/internal/proto/reactor_wire/v1/data_pb.d.ts.map +1 -0
  125. package/dist/internal/proto/reactor_wire/v1/data_pb.js +20 -0
  126. package/dist/internal/proto/reactor_wire/v1/data_pb.js.map +1 -0
  127. package/dist/internal/proto/reactor_wire/v1/model_pb.d.ts +85 -0
  128. package/dist/internal/proto/reactor_wire/v1/model_pb.d.ts.map +1 -0
  129. package/dist/internal/proto/reactor_wire/v1/model_pb.js +24 -0
  130. package/dist/internal/proto/reactor_wire/v1/model_pb.js.map +1 -0
  131. package/dist/internal/proto/reactor_wire/v1/platform_pb.d.ts +190 -0
  132. package/dist/internal/proto/reactor_wire/v1/platform_pb.d.ts.map +1 -0
  133. package/dist/internal/proto/reactor_wire/v1/platform_pb.js +54 -0
  134. package/dist/internal/proto/reactor_wire/v1/platform_pb.js.map +1 -0
  135. package/dist/internal/proto/reactor_wire/v1/track_pb.d.ts +88 -0
  136. package/dist/internal/proto/reactor_wire/v1/track_pb.d.ts.map +1 -0
  137. package/dist/internal/proto/reactor_wire/v1/track_pb.js +33 -0
  138. package/dist/internal/proto/reactor_wire/v1/track_pb.js.map +1 -0
  139. package/dist/internal/queue.d.ts +4 -0
  140. package/dist/internal/queue.d.ts.map +1 -0
  141. package/dist/{_internal → internal}/queue.js +0 -9
  142. package/dist/internal/queue.js.map +1 -0
  143. package/dist/internal/reactorTest/coordinator.d.ts +5 -0
  144. package/dist/internal/reactorTest/coordinator.d.ts.map +1 -0
  145. package/dist/internal/reactorTest/coordinator.js +178 -0
  146. package/dist/internal/reactorTest/coordinator.js.map +1 -0
  147. package/dist/internal/reactorTest/faults.d.ts +172 -0
  148. package/dist/internal/reactorTest/faults.d.ts.map +1 -0
  149. package/dist/internal/reactorTest/faults.js +23 -0
  150. package/dist/internal/reactorTest/faults.js.map +1 -0
  151. package/dist/internal/reactorTest/h3.d.ts +431 -0
  152. package/dist/internal/reactorTest/h3.d.ts.map +1 -0
  153. package/dist/internal/reactorTest/h3.js +625 -0
  154. package/dist/internal/reactorTest/h3.js.map +1 -0
  155. package/dist/internal/reactorTest/media.d.ts +22 -0
  156. package/dist/internal/reactorTest/media.d.ts.map +1 -0
  157. package/dist/internal/reactorTest/media.js +34 -0
  158. package/dist/internal/reactorTest/media.js.map +1 -0
  159. package/dist/internal/reactorTest/peer.d.ts +318 -0
  160. package/dist/internal/reactorTest/peer.d.ts.map +1 -0
  161. package/dist/internal/reactorTest/peer.js +206 -0
  162. package/dist/internal/reactorTest/peer.js.map +1 -0
  163. package/dist/internal/reactorTest/playout.d.ts +34 -0
  164. package/dist/internal/reactorTest/playout.d.ts.map +1 -0
  165. package/dist/internal/reactorTest/playout.js +340 -0
  166. package/dist/internal/reactorTest/playout.js.map +1 -0
  167. package/dist/internal/reactorTest/references.d.ts +25 -0
  168. package/dist/internal/reactorTest/references.d.ts.map +1 -0
  169. package/dist/{testing/Png.js → internal/reactorTest/references.js} +50 -12
  170. package/dist/internal/reactorTest/references.js.map +1 -0
  171. package/dist/internal/reactorTest/sessions.d.ts +364 -0
  172. package/dist/internal/reactorTest/sessions.d.ts.map +1 -0
  173. package/dist/internal/reactorTest/sessions.js +592 -0
  174. package/dist/internal/reactorTest/sessions.js.map +1 -0
  175. package/dist/internal/reactorTest/timing.d.ts +13 -0
  176. package/dist/internal/reactorTest/timing.d.ts.map +1 -0
  177. package/dist/internal/reactorTest/timing.js +20 -0
  178. package/dist/internal/reactorTest/timing.js.map +1 -0
  179. package/dist/internal/recording.d.ts +52 -0
  180. package/dist/internal/recording.d.ts.map +1 -0
  181. package/dist/internal/recording.js +141 -0
  182. package/dist/internal/recording.js.map +1 -0
  183. package/dist/internal/session/connect.d.ts +34 -0
  184. package/dist/internal/session/connect.d.ts.map +1 -0
  185. package/dist/internal/session/connect.js +502 -0
  186. package/dist/internal/session/connect.js.map +1 -0
  187. package/dist/internal/session/generation.d.ts +35 -0
  188. package/dist/internal/session/generation.d.ts.map +1 -0
  189. package/dist/internal/session/generation.js +185 -0
  190. package/dist/internal/session/generation.js.map +1 -0
  191. package/dist/internal/session/ice.d.ts +23 -0
  192. package/dist/internal/session/ice.d.ts.map +1 -0
  193. package/dist/internal/session/ice.js +57 -0
  194. package/dist/internal/session/ice.js.map +1 -0
  195. package/dist/internal/session/inbound.d.ts +50 -0
  196. package/dist/internal/session/inbound.d.ts.map +1 -0
  197. package/dist/internal/session/inbound.js +202 -0
  198. package/dist/internal/session/inbound.js.map +1 -0
  199. package/dist/internal/session/model.d.ts +163 -0
  200. package/dist/internal/session/model.d.ts.map +1 -0
  201. package/dist/internal/session/model.js +78 -0
  202. package/dist/internal/session/model.js.map +1 -0
  203. package/dist/internal/session/requests.d.ts +71 -0
  204. package/dist/internal/session/requests.d.ts.map +1 -0
  205. package/dist/internal/session/requests.js +240 -0
  206. package/dist/internal/session/requests.js.map +1 -0
  207. package/dist/internal/session/tracks.d.ts +21 -0
  208. package/dist/internal/session/tracks.d.ts.map +1 -0
  209. package/dist/internal/session/tracks.js +166 -0
  210. package/dist/internal/session/tracks.js.map +1 -0
  211. package/dist/internal/session/upload.d.ts +21 -0
  212. package/dist/internal/session/upload.d.ts.map +1 -0
  213. package/dist/internal/session/upload.js +68 -0
  214. package/dist/internal/session/upload.js.map +1 -0
  215. package/dist/internal/session.d.ts +66 -0
  216. package/dist/internal/session.d.ts.map +1 -0
  217. package/dist/internal/session.js +280 -0
  218. package/dist/internal/session.js.map +1 -0
  219. package/dist/{stats.d.ts → internal/stats.d.ts} +30 -16
  220. package/dist/internal/stats.d.ts.map +1 -0
  221. package/dist/internal/stats.js +201 -0
  222. package/dist/internal/stats.js.map +1 -0
  223. package/dist/internal/token.d.ts +22 -0
  224. package/dist/internal/token.d.ts.map +1 -0
  225. package/dist/internal/token.js +65 -0
  226. package/dist/internal/token.js.map +1 -0
  227. package/dist/internal/wire.d.ts +37 -0
  228. package/dist/internal/wire.d.ts.map +1 -0
  229. package/dist/internal/wire.js +49 -0
  230. package/dist/internal/wire.js.map +1 -0
  231. package/notices/bufbuild-protobuf-LICENSE +201 -0
  232. package/package.json +17 -38
  233. package/dist/FetchHttp.d.ts +0 -9
  234. package/dist/FetchHttp.d.ts.map +0 -1
  235. package/dist/FetchHttp.js +0 -63
  236. package/dist/FetchHttp.js.map +0 -1
  237. package/dist/PeerFactory.d.ts +0 -26
  238. package/dist/PeerFactory.d.ts.map +0 -1
  239. package/dist/PeerFactory.js +0 -4
  240. package/dist/PeerFactory.js.map +0 -1
  241. package/dist/PeerTypes.d.ts +0 -64
  242. package/dist/PeerTypes.d.ts.map +0 -1
  243. package/dist/PeerTypes.js +0 -2
  244. package/dist/PeerTypes.js.map +0 -1
  245. package/dist/Sequence.d.ts +0 -85
  246. package/dist/Sequence.d.ts.map +0 -1
  247. package/dist/Sequence.js +0 -210
  248. package/dist/Sequence.js.map +0 -1
  249. package/dist/SessionTypes.d.ts +0 -312
  250. package/dist/SessionTypes.d.ts.map +0 -1
  251. package/dist/SessionTypes.js +0 -19
  252. package/dist/SessionTypes.js.map +0 -1
  253. package/dist/Submission.d.ts.map +0 -1
  254. package/dist/Submission.js.map +0 -1
  255. package/dist/_internal/queue.d.ts +0 -6
  256. package/dist/_internal/queue.d.ts.map +0 -1
  257. package/dist/_internal/queue.js.map +0 -1
  258. package/dist/bytes.d.ts +0 -6
  259. package/dist/bytes.d.ts.map +0 -1
  260. package/dist/bytes.js +0 -32
  261. package/dist/bytes.js.map +0 -1
  262. package/dist/contract.d.ts +0 -149
  263. package/dist/contract.d.ts.map +0 -1
  264. package/dist/contract.js +0 -119
  265. package/dist/contract.js.map +0 -1
  266. package/dist/coordinator/_internal/client.d.ts +0 -215
  267. package/dist/coordinator/_internal/client.d.ts.map +0 -1
  268. package/dist/coordinator/_internal/client.js +0 -563
  269. package/dist/coordinator/_internal/client.js.map +0 -1
  270. package/dist/coordinator/_internal/recording.d.ts +0 -31
  271. package/dist/coordinator/_internal/recording.d.ts.map +0 -1
  272. package/dist/coordinator/_internal/recording.js +0 -123
  273. package/dist/coordinator/_internal/recording.js.map +0 -1
  274. package/dist/coordinator/_internal/response.d.ts +0 -15
  275. package/dist/coordinator/_internal/response.d.ts.map +0 -1
  276. package/dist/coordinator/_internal/response.js +0 -55
  277. package/dist/coordinator/_internal/response.js.map +0 -1
  278. package/dist/coordinator/_internal/schemas.d.ts +0 -73
  279. package/dist/coordinator/_internal/schemas.d.ts.map +0 -1
  280. package/dist/coordinator/_internal/schemas.js +0 -200
  281. package/dist/coordinator/_internal/schemas.js.map +0 -1
  282. package/dist/coordinator/index.d.ts +0 -40
  283. package/dist/coordinator/index.d.ts.map +0 -1
  284. package/dist/coordinator/index.js +0 -21
  285. package/dist/coordinator/index.js.map +0 -1
  286. package/dist/correlation.d.ts +0 -47
  287. package/dist/correlation.d.ts.map +0 -1
  288. package/dist/correlation.js +0 -131
  289. package/dist/correlation.js.map +0 -1
  290. package/dist/duration.d.ts +0 -27
  291. package/dist/duration.d.ts.map +0 -1
  292. package/dist/duration.js +0 -47
  293. package/dist/duration.js.map +0 -1
  294. package/dist/errors.d.ts +0 -1303
  295. package/dist/errors.d.ts.map +0 -1
  296. package/dist/errors.js +0 -745
  297. package/dist/errors.js.map +0 -1
  298. package/dist/h3/_internal/client.d.ts +0 -9
  299. package/dist/h3/_internal/client.d.ts.map +0 -1
  300. package/dist/h3/_internal/client.js +0 -652
  301. package/dist/h3/_internal/client.js.map +0 -1
  302. package/dist/h3/_internal/contracts.d.ts.map +0 -1
  303. package/dist/h3/_internal/contracts.js +0 -123
  304. package/dist/h3/_internal/contracts.js.map +0 -1
  305. package/dist/h3/_internal/deployment.d.ts +0 -8
  306. package/dist/h3/_internal/deployment.d.ts.map +0 -1
  307. package/dist/h3/_internal/deployment.js +0 -149
  308. package/dist/h3/_internal/deployment.js.map +0 -1
  309. package/dist/h3/_internal/evidence.d.ts +0 -17
  310. package/dist/h3/_internal/evidence.d.ts.map +0 -1
  311. package/dist/h3/_internal/evidence.js +0 -48
  312. package/dist/h3/_internal/evidence.js.map +0 -1
  313. package/dist/h3/_internal/operations.d.ts +0 -58
  314. package/dist/h3/_internal/operations.d.ts.map +0 -1
  315. package/dist/h3/_internal/operations.js +0 -268
  316. package/dist/h3/_internal/operations.js.map +0 -1
  317. package/dist/h3/_internal/references.d.ts +0 -24
  318. package/dist/h3/_internal/references.d.ts.map +0 -1
  319. package/dist/h3/_internal/references.js +0 -309
  320. package/dist/h3/_internal/references.js.map +0 -1
  321. package/dist/h3/_internal/request.d.ts +0 -21
  322. package/dist/h3/_internal/request.d.ts.map +0 -1
  323. package/dist/h3/_internal/request.js +0 -97
  324. package/dist/h3/_internal/request.js.map +0 -1
  325. package/dist/h3/_internal/retained.d.ts +0 -22
  326. package/dist/h3/_internal/retained.d.ts.map +0 -1
  327. package/dist/h3/_internal/retained.js +0 -89
  328. package/dist/h3/_internal/retained.js.map +0 -1
  329. package/dist/h3/_internal/state.d.ts +0 -44
  330. package/dist/h3/_internal/state.d.ts.map +0 -1
  331. package/dist/h3/_internal/state.js +0 -283
  332. package/dist/h3/_internal/state.js.map +0 -1
  333. package/dist/h3/index.d.ts +0 -10
  334. package/dist/h3/index.d.ts.map +0 -1
  335. package/dist/h3/index.js +0 -7
  336. package/dist/h3/index.js.map +0 -1
  337. package/dist/h3/messages.d.ts.map +0 -1
  338. package/dist/h3/messages.js.map +0 -1
  339. package/dist/h3/profile.d.ts.map +0 -1
  340. package/dist/h3/profile.js.map +0 -1
  341. package/dist/h3/types.d.ts +0 -291
  342. package/dist/h3/types.d.ts.map +0 -1
  343. package/dist/h3/types.js +0 -2
  344. package/dist/h3/types.js.map +0 -1
  345. package/dist/host.d.ts +0 -26
  346. package/dist/host.d.ts.map +0 -1
  347. package/dist/host.js +0 -20
  348. package/dist/host.js.map +0 -1
  349. package/dist/json.d.ts +0 -25
  350. package/dist/json.d.ts.map +0 -1
  351. package/dist/json.js +0 -143
  352. package/dist/json.js.map +0 -1
  353. package/dist/media-stream.d.ts +0 -10
  354. package/dist/media-stream.d.ts.map +0 -1
  355. package/dist/media-stream.js +0 -18
  356. package/dist/media-stream.js.map +0 -1
  357. package/dist/observation.d.ts +0 -38
  358. package/dist/observation.d.ts.map +0 -1
  359. package/dist/observation.js +0 -113
  360. package/dist/observation.js.map +0 -1
  361. package/dist/orchestration/elapsed.d.ts +0 -9
  362. package/dist/orchestration/elapsed.d.ts.map +0 -1
  363. package/dist/orchestration/elapsed.js +0 -8
  364. package/dist/orchestration/elapsed.js.map +0 -1
  365. package/dist/orchestration/h3-source.d.ts +0 -58
  366. package/dist/orchestration/h3-source.d.ts.map +0 -1
  367. package/dist/orchestration/h3-source.js +0 -362
  368. package/dist/orchestration/h3-source.js.map +0 -1
  369. package/dist/orchestration/index.d.ts +0 -17
  370. package/dist/orchestration/index.d.ts.map +0 -1
  371. package/dist/orchestration/index.js +0 -12
  372. package/dist/orchestration/index.js.map +0 -1
  373. package/dist/orchestration/media-buffer.d.ts +0 -32
  374. package/dist/orchestration/media-buffer.d.ts.map +0 -1
  375. package/dist/orchestration/media-buffer.js +0 -94
  376. package/dist/orchestration/media-buffer.js.map +0 -1
  377. package/dist/orchestration/open-h3.d.ts +0 -101
  378. package/dist/orchestration/open-h3.d.ts.map +0 -1
  379. package/dist/orchestration/open-h3.js +0 -127
  380. package/dist/orchestration/open-h3.js.map +0 -1
  381. package/dist/orchestration/queries.d.ts +0 -10
  382. package/dist/orchestration/queries.d.ts.map +0 -1
  383. package/dist/orchestration/queries.js +0 -43
  384. package/dist/orchestration/queries.js.map +0 -1
  385. package/dist/orchestration/references.d.ts +0 -18
  386. package/dist/orchestration/references.d.ts.map +0 -1
  387. package/dist/orchestration/references.js +0 -79
  388. package/dist/orchestration/references.js.map +0 -1
  389. package/dist/orchestration/renewal-state.d.ts +0 -84
  390. package/dist/orchestration/renewal-state.d.ts.map +0 -1
  391. package/dist/orchestration/renewal-state.js +0 -95
  392. package/dist/orchestration/renewal-state.js.map +0 -1
  393. package/dist/orchestration/renewal.d.ts +0 -103
  394. package/dist/orchestration/renewal.d.ts.map +0 -1
  395. package/dist/orchestration/renewal.js +0 -1088
  396. package/dist/orchestration/renewal.js.map +0 -1
  397. package/dist/orchestration/request.d.ts +0 -65
  398. package/dist/orchestration/request.d.ts.map +0 -1
  399. package/dist/orchestration/request.js +0 -134
  400. package/dist/orchestration/request.js.map +0 -1
  401. package/dist/orchestration/retention.d.ts +0 -100
  402. package/dist/orchestration/retention.d.ts.map +0 -1
  403. package/dist/orchestration/retention.js +0 -134
  404. package/dist/orchestration/retention.js.map +0 -1
  405. package/dist/orchestration/routing.d.ts +0 -21
  406. package/dist/orchestration/routing.d.ts.map +0 -1
  407. package/dist/orchestration/routing.js +0 -93
  408. package/dist/orchestration/routing.js.map +0 -1
  409. package/dist/orchestration/scheduler-key.d.ts +0 -14
  410. package/dist/orchestration/scheduler-key.d.ts.map +0 -1
  411. package/dist/orchestration/scheduler-key.js +0 -62
  412. package/dist/orchestration/scheduler-key.js.map +0 -1
  413. package/dist/orchestration/scheduler-policy.d.ts +0 -222
  414. package/dist/orchestration/scheduler-policy.d.ts.map +0 -1
  415. package/dist/orchestration/scheduler-policy.js +0 -582
  416. package/dist/orchestration/scheduler-policy.js.map +0 -1
  417. package/dist/orchestration/scheduler.d.ts +0 -383
  418. package/dist/orchestration/scheduler.d.ts.map +0 -1
  419. package/dist/orchestration/scheduler.js +0 -2597
  420. package/dist/orchestration/scheduler.js.map +0 -1
  421. package/dist/orchestration/source-slot.d.ts +0 -124
  422. package/dist/orchestration/source-slot.d.ts.map +0 -1
  423. package/dist/orchestration/source-slot.js +0 -348
  424. package/dist/orchestration/source-slot.js.map +0 -1
  425. package/dist/orchestration/types.d.ts +0 -1462
  426. package/dist/orchestration/types.d.ts.map +0 -1
  427. package/dist/orchestration/types.js +0 -135
  428. package/dist/orchestration/types.js.map +0 -1
  429. package/dist/protobuf.d.ts +0 -63
  430. package/dist/protobuf.d.ts.map +0 -1
  431. package/dist/protobuf.js +0 -252
  432. package/dist/protobuf.js.map +0 -1
  433. package/dist/session/_internal/acquire.d.ts +0 -21
  434. package/dist/session/_internal/acquire.d.ts.map +0 -1
  435. package/dist/session/_internal/acquire.js +0 -168
  436. package/dist/session/_internal/acquire.js.map +0 -1
  437. package/dist/session/_internal/cleanup.d.ts +0 -19
  438. package/dist/session/_internal/cleanup.d.ts.map +0 -1
  439. package/dist/session/_internal/cleanup.js +0 -102
  440. package/dist/session/_internal/cleanup.js.map +0 -1
  441. package/dist/session/_internal/lifecycle.d.ts +0 -67
  442. package/dist/session/_internal/lifecycle.d.ts.map +0 -1
  443. package/dist/session/_internal/lifecycle.js +0 -142
  444. package/dist/session/_internal/lifecycle.js.map +0 -1
  445. package/dist/session/_internal/remote.d.ts +0 -28
  446. package/dist/session/_internal/remote.d.ts.map +0 -1
  447. package/dist/session/_internal/remote.js +0 -71
  448. package/dist/session/_internal/remote.js.map +0 -1
  449. package/dist/session/_internal/uploads.d.ts +0 -7
  450. package/dist/session/_internal/uploads.d.ts.map +0 -1
  451. package/dist/session/_internal/uploads.js +0 -31
  452. package/dist/session/_internal/uploads.js.map +0 -1
  453. package/dist/session/commands.d.ts +0 -2
  454. package/dist/session/commands.d.ts.map +0 -1
  455. package/dist/session/commands.js +0 -2
  456. package/dist/session/commands.js.map +0 -1
  457. package/dist/session/files.d.ts +0 -21
  458. package/dist/session/files.d.ts.map +0 -1
  459. package/dist/session/files.js +0 -49
  460. package/dist/session/files.js.map +0 -1
  461. package/dist/session/index.d.ts +0 -84
  462. package/dist/session/index.d.ts.map +0 -1
  463. package/dist/session/index.js +0 -16
  464. package/dist/session/index.js.map +0 -1
  465. package/dist/session/media.d.ts +0 -114
  466. package/dist/session/media.d.ts.map +0 -1
  467. package/dist/session/media.js +0 -2
  468. package/dist/session/media.js.map +0 -1
  469. package/dist/session/recorder.d.ts +0 -24
  470. package/dist/session/recorder.d.ts.map +0 -1
  471. package/dist/session/recorder.js +0 -19
  472. package/dist/session/recorder.js.map +0 -1
  473. package/dist/session.d.ts +0 -105
  474. package/dist/session.d.ts.map +0 -1
  475. package/dist/session.js +0 -976
  476. package/dist/session.js.map +0 -1
  477. package/dist/simulation/_internal/source.d.ts +0 -12
  478. package/dist/simulation/_internal/source.d.ts.map +0 -1
  479. package/dist/simulation/_internal/source.js +0 -513
  480. package/dist/simulation/_internal/source.js.map +0 -1
  481. package/dist/simulation/index.d.ts +0 -13
  482. package/dist/simulation/index.d.ts.map +0 -1
  483. package/dist/simulation/index.js +0 -12
  484. package/dist/simulation/index.js.map +0 -1
  485. package/dist/simulation/types.d.ts +0 -39
  486. package/dist/simulation/types.d.ts.map +0 -1
  487. package/dist/simulation/types.js +0 -2
  488. package/dist/simulation/types.js.map +0 -1
  489. package/dist/stats.d.ts.map +0 -1
  490. package/dist/stats.js +0 -205
  491. package/dist/stats.js.map +0 -1
  492. package/dist/testing/Faults.d.ts +0 -4
  493. package/dist/testing/Faults.d.ts.map +0 -1
  494. package/dist/testing/Faults.js +0 -7
  495. package/dist/testing/Faults.js.map +0 -1
  496. package/dist/testing/Png.d.ts +0 -3
  497. package/dist/testing/Png.d.ts.map +0 -1
  498. package/dist/testing/Png.js.map +0 -1
  499. package/dist/testing/Wav.d.ts +0 -14
  500. package/dist/testing/Wav.d.ts.map +0 -1
  501. package/dist/testing/Wav.js +0 -37
  502. package/dist/testing/Wav.js.map +0 -1
  503. package/dist/testing/index.d.ts +0 -5
  504. package/dist/testing/index.d.ts.map +0 -1
  505. package/dist/testing/index.js +0 -4
  506. package/dist/testing/index.js.map +0 -1
  507. package/dist/wire.d.ts +0 -7
  508. package/dist/wire.d.ts.map +0 -1
  509. package/dist/wire.generated.d.ts +0 -305
  510. package/dist/wire.generated.d.ts.map +0 -1
  511. package/dist/wire.generated.js +0 -1257
  512. package/dist/wire.generated.js.map +0 -1
  513. package/dist/wire.js +0 -5
  514. package/dist/wire.js.map +0 -1
@@ -1,1088 +0,0 @@
1
- import * as Cause from "effect/Cause";
2
- import * as Clock from "effect/Clock";
3
- import * as Context from "effect/Context";
4
- import * as Crypto from "effect/Crypto";
5
- import * as Deferred from "effect/Deferred";
6
- import * as Duration from "effect/Duration";
7
- import * as Effect from "effect/Effect";
8
- import * as Exit from "effect/Exit";
9
- import * as Fiber from "effect/Fiber";
10
- import * as Layer from "effect/Layer";
11
- import * as Option from "effect/Option";
12
- import * as Result from "effect/Result";
13
- import * as Scope from "effect/Scope";
14
- import * as Semaphore from "effect/Semaphore";
15
- import * as Stream from "effect/Stream";
16
- import { duration } from "../duration.js";
17
- import { AcquisitionFailure, CommandFailure, ReactorError, errorOf, parsed, positiveLimit, } from "../errors.js";
18
- import { Observations } from "../observation.js";
19
- import { noAcquisition } from "../session/_internal/acquire.js";
20
- import * as Sequence from "../Sequence.js";
21
- import * as Submission from "../Submission.js";
22
- import * as MediaBuffer from "./media-buffer.js";
23
- import * as SourceSlot from "./source-slot.js";
24
- import * as Retention from "./retention.js";
25
- import { monotonicMillis } from "./elapsed.js";
26
- import { handoffEvidence, decideRenewal } from "./renewal-state.js";
27
- import { PolicyFailure, captureRequest } from "./request.js";
28
- import { emptyState, isIdle } from "./queries.js";
29
- import { activeIds, generation, resolve } from "./routing.js";
30
- import { handleContext } from "./types.js";
31
- const engineOnly = (event) => event._tag === "Engine" ? Result.succeed(event.event) : Result.fail(event);
32
- const renewalsOnly = (event) => event._tag === "Renewal" ? Result.succeed(event.event) : Result.fail(event);
33
- /** A physical source supplies facts; the renewing handle assigns their owner. */
34
- const withSessionId = (state, sessionId) => {
35
- const record = (clip) => Object.freeze({ ...clip, sessionId });
36
- return Object.freeze({
37
- ...state,
38
- queued: state.queued.map(record),
39
- building: Option.map(state.building, (build) => ({ ...build, record: record(build.record) })),
40
- ready: state.ready.map(record),
41
- playing: Option.map(state.playing, (playing) => ({
42
- ...playing,
43
- record: Option.map(playing.record, record),
44
- })),
45
- });
46
- };
47
- /** A completed caller handle keeps only its Exit, not the routing or source closures. */
48
- const logicalSubmission = (id) => {
49
- let execute;
50
- let inspect;
51
- let outcome;
52
- return {
53
- attach: (submit, state) => {
54
- execute = submit;
55
- inspect = state;
56
- },
57
- complete: (exit) => {
58
- outcome ??= exit;
59
- execute = undefined;
60
- inspect = undefined;
61
- },
62
- outcome: () => outcome,
63
- submission: {
64
- id,
65
- submit: Effect.suspend(() => outcome ?? execute ?? Effect.die(new Error("Logical submission has no execution"))),
66
- state: Effect.suspend(() => outcome === undefined
67
- ? (inspect ?? Effect.succeed({ _tag: "Prepared" }))
68
- : Effect.succeed({
69
- _tag: "Completed",
70
- exit: outcome,
71
- })),
72
- },
73
- };
74
- };
75
- /**
76
- * Explicit application policy for renewal, sequence affinity and continuous
77
- * recovering media. A physical source still owns each connection generation.
78
- */
79
- const makeOwner = (options, continuous) => Effect.gen(function* () {
80
- const scope = yield* Effect.scope;
81
- const context = yield* Effect.context();
82
- const clock = yield* Clock.Clock;
83
- const random = yield* (yield* Crypto.Crypto)
84
- .randomBytes(16)
85
- .pipe(Effect.mapError((cause) => errorOf(cause, "InvalidState")));
86
- const namespace = Array.from(random, (byte) => byte.toString(16).padStart(2, "0")).join("");
87
- const commands = yield* Semaphore.make(1);
88
- const affinity = yield* Sequence.makeAffinity().pipe(Effect.mapError((cause) => errorOf(cause, "InvalidInput")));
89
- // One ordered stream: engine events, renewals and media transitions.
90
- const observations = new Observations();
91
- const buffer = yield* MediaBuffer.make(yield* parsed(() => ({
92
- videoFrames: positiveLimit(options.maxQueuedVideoFrames ?? MediaBuffer.defaultLimits.videoFrames, "orchestration maxQueuedVideoFrames"),
93
- audioSamples: positiveLimit(options.maxQueuedAudioSamples ?? MediaBuffer.defaultLimits.audioSamples, "orchestration maxQueuedAudioSamples"),
94
- })));
95
- const fatal = yield* Deferred.make();
96
- let renewing = true;
97
- const slots = new Map();
98
- const physicalOwners = new Map();
99
- const retention = continuous ? yield* Retention.make(options) : undefined;
100
- let incarnation = 0n;
101
- const cleanups = [];
102
- const seenCleanups = new Set();
103
- const maxSessions = options.maxSessions ?? (continuous ? Infinity : 64);
104
- if ((options.maxSessions !== undefined || !continuous) &&
105
- (!Number.isSafeInteger(maxSessions) || maxSessions < 1 || maxSessions > 4096)) {
106
- return yield* ReactorError.fromCode("InvalidInput", "Invalid orchestration bounds");
107
- }
108
- const reconnectTimeout = Duration.toMillis(yield* parsed(() => duration(options.reconnectTimeout ?? "10 seconds", "orchestration reconnectTimeout")));
109
- const handoffGraceMs = Duration.toMillis(yield* parsed(() => duration(options.handoffGrace ?? "250 millis", "orchestration handoffGrace", {
110
- allowZero: true,
111
- // Starvation during the grace is not reported, so it stays short.
112
- maximum: "5 seconds",
113
- })));
114
- const leadSeconds = Duration.toSeconds(yield* parsed(() => duration(options.lead ?? "30 seconds", "orchestration lead", { allowZero: true })));
115
- let current;
116
- let replacement = { _tag: "Absent" };
117
- let opened = 0n;
118
- let openFailures = 0;
119
- let retryAt = 0;
120
- let autoplay = true;
121
- let closing = false;
122
- let finalReport;
123
- let finalSummary;
124
- let firstCleanup;
125
- let initializing = true;
126
- // Close reports each ownership attempt's failures once. They are recorded
127
- // where the attempt and its retirement end: every waiter of a cached close
128
- // sees its own Cause object, and cancellation by close is not a failure.
129
- const attemptFailures = new Map();
130
- const recordFailure = (attempt, reasons) => {
131
- const failed = reasons.filter((reason) => !Cause.isInterruptReason(reason));
132
- if (failed.length > 0)
133
- attemptFailures.set(attempt, [...(attemptFailures.get(attempt) ?? []), ...failed]);
134
- };
135
- // Every acquisition runs on a fiber this owner forks into its scope, never
136
- // on a caller's fiber. Close interrupts and joins the registered ones before
137
- // retiring owned slots, so no source opens after close and every attempt's
138
- // cleanup is reported, without waiting on a fiber that may be waiting on it.
139
- const acquisitions = new Set();
140
- const admissionClosed = () => ReactorError.fromCode("Closed", "Orchestration no longer admits sources");
141
- // The owner stops renewing when close begins or its scope starts closing,
142
- // whichever is first: that scope's finalizers can stop an attempt, or wait
143
- // on an application, before close runs. Commands stay admitted until close
144
- // begins, so an application finalizer can still issue its last commands.
145
- const stopping = () => closing || scope.state._tag === "Closed";
146
- let mediaState = { _tag: "Closed" };
147
- let terminal;
148
- let submissionSequence = 0n;
149
- const announce = (event) => Effect.sync(() => observations.emit({ _tag: "Renewal", event }, 256));
150
- const log = (message) => Effect.logDebug(message).pipe(Effect.annotateLogs({ module: "reactor.orchestration" }));
151
- const emit = (event) => observations.emit({ _tag: "Engine", event }, 256);
152
- const setMedia = (state) => {
153
- mediaState = state;
154
- observations.emit({ _tag: "Media", state }, 256);
155
- };
156
- const onRenewal = options.onRenewal;
157
- // Subscribed before the first source opens, so the reader sees its Opened.
158
- const renewalReader = onRenewal === undefined
159
- ? undefined
160
- : yield* observations.subscribe({ capacity: 4096 }).pipe(Effect.flatMap((stream) => stream.pipe(Stream.filterMap(renewalsOnly), Stream.runForEach((event) => Effect.suspend(() => onRenewal(event)).pipe(Effect.catchCause((cause) => Effect.logWarning("onRenewal failed", cause).pipe(Effect.annotateLogs({ module: "reactor.orchestration" }))))), Effect.catchCause((cause) => Effect.logWarning("onRenewal reader stopped", cause).pipe(Effect.annotateLogs({ module: "reactor.orchestration" }))), Effect.forkIn(scope))));
161
- const fail = (cause) => Effect.suspend(() => {
162
- if (terminal !== undefined)
163
- return Effect.void;
164
- terminal = Exit.succeed(cause);
165
- // Publish the failed state and queues before waking a failure waiter.
166
- setMedia({ _tag: "Failed", cause });
167
- emit({ _tag: "SessionFailed", failure: cause });
168
- buffer.failCause(Cause.fail(cause));
169
- Deferred.doneUnsafe(fatal, Effect.succeed(cause));
170
- return announce({ _tag: "Failed", reason: cause.message });
171
- });
172
- // A failed worker must wake observers as well as failure waiters: the
173
- // scheduler consumes observations, and cannot recover a vanished worker.
174
- // Claim and publish synchronously before a waiter can close this owner.
175
- const supervise = (effect) => effect.pipe(Effect.onExit((exit) => Effect.sync(() => {
176
- if (Exit.isSuccess(exit) ||
177
- terminal !== undefined ||
178
- (stopping() && Cause.hasInterruptsOnly(exit.cause)))
179
- return;
180
- terminal = Exit.failCause(exit.cause);
181
- buffer.failCause(exit.cause);
182
- observations.failCause(exit.cause);
183
- Deferred.doneUnsafe(fatal, terminal);
184
- })));
185
- // Every asynchronous activation uses this final fence. Closed/failed state
186
- // cannot be replaced by a reconnect or autoplay operation that finished late.
187
- const publishReady = (slot) => {
188
- if (stopping() || slot.closed || slot !== current || terminal !== undefined)
189
- return false;
190
- setMedia({ _tag: "Ready", sessionId: slot.source.id, generation: slot.media.generation });
191
- return true;
192
- };
193
- const recordCleanup = (cleanup) => {
194
- if (seenCleanups.has(cleanup.lease))
195
- return;
196
- seenCleanups.add(cleanup.lease);
197
- cleanups.push(cleanup);
198
- };
199
- const openSequences = (slot) => affinity.snapshots.pipe(Effect.map((entries) => entries.some((entry) => entry.owner === slot.owner && entry.status === "open")));
200
- const expired = (slot) => slot.expired();
201
- const recoveryBudget = (slot) => slot.recoveryBudget();
202
- // The logical output's loss: every former owner's loss while it fed the
203
- // output, plus the current owner's since it became the owner. A prepared
204
- // replacement's loss before it fed the output is not the output's.
205
- let outputLoss = SourceSlot.noLoss;
206
- let ownerBaseline = SourceSlot.noLoss;
207
- let outputOwner;
208
- const ownerLoss = (slot) => Effect.result(slot.pressure.pipe(Effect.timeout(recoveryBudget(slot)))).pipe(Effect.map(SourceSlot.lossOf));
209
- const activateOwner = (slot) => ownerLoss(slot).pipe(Effect.map((loss) => {
210
- ownerBaseline = loss;
211
- outputOwner = slot.owner;
212
- }));
213
- const retireOwner = (slot) => Effect.suspend(() => outputOwner !== slot.owner
214
- ? Effect.void
215
- : ownerLoss(slot).pipe(Effect.map((loss) => {
216
- outputLoss = SourceSlot.addLoss(outputLoss, SourceSlot.subtractLoss(loss, ownerBaseline));
217
- ownerBaseline = SourceSlot.noLoss;
218
- outputOwner = undefined;
219
- })));
220
- const retired = (slot) => slot.retired(buffer.forwarded);
221
- const closeSlot = (slot) => slot.close;
222
- const replace = (slot, cause) => Effect.gen(function* () {
223
- if (slot.closed || stopping() || terminal !== undefined)
224
- return;
225
- const state = yield* slot.source.state;
226
- const lost = activeIds(state);
227
- const tail = yield* retired(slot);
228
- for (const clipId of lost)
229
- emit({
230
- _tag: "Failed",
231
- clipId,
232
- reason: `Session lost: ${cause.message}`,
233
- sessionId: slot.source.id,
234
- lost: true,
235
- });
236
- if (slot === current)
237
- yield* retireOwner(slot);
238
- yield* closeSlot(slot);
239
- yield* announce({
240
- _tag: "Replaced",
241
- reason: cause.message,
242
- lostClips: lost.length,
243
- ...tail,
244
- });
245
- if (replacement._tag === "Ready" && replacement.slot === slot)
246
- replacement = { _tag: "Absent" };
247
- // Close retires what remains; a replacement it refuses is no failure.
248
- if (slot !== current || stopping())
249
- return;
250
- const pending = replacement;
251
- if (!renewing && pending._tag === "Absent") {
252
- yield* fail(cause);
253
- return;
254
- }
255
- const selected = pending._tag === "Ready"
256
- ? pending.slot
257
- : yield* pending._tag === "Opening"
258
- ? Effect.uninterruptibleMask((restore) => joinAcquisition(pending.fiber, restore))
259
- : acquire;
260
- replacement = { _tag: "Absent" };
261
- if (stopping() || terminal !== undefined) {
262
- yield* closeSlot(selected);
263
- return;
264
- }
265
- current = selected;
266
- yield* activateOwner(selected);
267
- yield* current.source.setAutoplay(autoplay);
268
- if (!publishReady(current))
269
- return;
270
- yield* log("Replaced lost session; local queue resumes on the new connection");
271
- }).pipe(Effect.withSpan("reactor.orchestration.renewal.replace", { attributes: { "reactor.session.id": slot.source.id } }, { captureStackTrace: false }), Effect.catch((cause) => (stopping() ? Effect.void : fail(cause))));
272
- const recover = (slot, cause) => Effect.gen(function* () {
273
- if (slot.closed || stopping() || terminal !== undefined)
274
- return;
275
- if (slot === current)
276
- setMedia({ _tag: "Recovering", sessionId: slot.source.id, cause });
277
- yield* announce({ _tag: "Recovering", sessionId: slot.source.id, reason: cause.message });
278
- yield* slot.joinCommitted(recoveryBudget(slot));
279
- if (slot.closed || stopping() || terminal !== undefined)
280
- return;
281
- if (slot.needsReplacement()) {
282
- yield* replace(slot, cause);
283
- return;
284
- }
285
- const before = activeIds(yield* slot.source.state);
286
- // Read the retiring receiver before reconnect makes its counters unavailable.
287
- const previousPressure = yield* Effect.result(slot.media.pressure.pipe(Effect.timeout(recoveryBudget(slot))));
288
- if (expired(slot)) {
289
- yield* replace(slot, ReactorError.fromCode("TerminalSession", "Source expired during recovery", {
290
- operation: "renewal",
291
- }));
292
- return;
293
- }
294
- yield* slot.closeMedia;
295
- if (slot.closed || stopping() || terminal !== undefined)
296
- return;
297
- const connected = yield* Effect.result(slot.source.reconnect.pipe(Effect.timeoutOrElse({
298
- duration: recoveryBudget(slot),
299
- orElse: () => Effect.fail(ReactorError.fromCode("Disconnected", "Explicit source reconnect timed out")),
300
- })));
301
- if (slot.closed || stopping() || terminal !== undefined)
302
- return;
303
- if (Result.isFailure(connected)) {
304
- yield* replace(slot, connected.failure);
305
- return;
306
- }
307
- if (slot.needsReplacement()) {
308
- yield* replace(slot, cause);
309
- return;
310
- }
311
- const after = new Set(activeIds(yield* slot.source.state));
312
- for (const clipId of before)
313
- if (!after.has(clipId))
314
- emit({
315
- _tag: "Failed",
316
- clipId,
317
- reason: "Clip left the provider's active queues during the observation gap",
318
- sessionId: slot.source.id,
319
- });
320
- const restarted = yield* Effect.result(Effect.gen(function* () {
321
- const media = yield* slot.source.media;
322
- yield* slot.replaceMedia(media, previousPressure);
323
- return yield* startMedia(slot);
324
- }));
325
- if (slot.closed || stopping() || terminal !== undefined)
326
- return;
327
- if (Result.isFailure(restarted)) {
328
- yield* replace(slot, restarted.failure);
329
- return;
330
- }
331
- if (slot.needsReplacement() || !slot.recovered()) {
332
- yield* replace(slot, cause);
333
- return;
334
- }
335
- if (slot === current && !publishReady(slot))
336
- return;
337
- yield* announce({
338
- _tag: "Reconnected",
339
- sessionId: slot.source.id,
340
- generation: slot.media.generation,
341
- });
342
- }).pipe(Effect.withSpan("reactor.orchestration.renewal.recover", { attributes: { "reactor.session.id": slot.source.id } }, { captureStackTrace: false }));
343
- const scheduleRecovery = (slot, cause, mode) => Effect.gen(function* () {
344
- if (slot.closed || stopping() || terminal !== undefined)
345
- return;
346
- if (!slot.beginRecovery(mode))
347
- return;
348
- yield* recover(slot, cause).pipe(commands.withPermits(1), supervise, Effect.forkIn(scope));
349
- });
350
- const startMedia = (slot) => slot.startMedia({
351
- video: (frame) => Effect.suspend(() => {
352
- if (slot !== current || terminal !== undefined)
353
- return Effect.void;
354
- if (slot.recordVideo() && replacement._tag === "Ready")
355
- emit({ _tag: "HandoffReady", sessionId: slot.source.id });
356
- return buffer.offerVideo(frame).pipe(Effect.catch(fail));
357
- }),
358
- audio: (frame) => Effect.suspend(() => {
359
- if (slot !== current || terminal !== undefined)
360
- return Effect.void;
361
- return buffer.offerAudio(frame).pipe(Effect.tap(() => Effect.sync(() => {
362
- slot.recordAudio(frame.samples.length);
363
- })), Effect.catch(fail));
364
- }),
365
- lost: (cause) => (stopping() ? Effect.void : scheduleRecovery(slot, cause, "reconnect")),
366
- });
367
- // Runs only on a fiber of its own, forked into the owner's scope.
368
- const acquisition = Effect.uninterruptibleMask((restore) => Effect.gen(function* () {
369
- const fiber = yield* Effect.fiber;
370
- if (opened >= maxSessions)
371
- return yield* ReactorError.fromCode("Overflow", "Orchestration session and cleanup-history bound reached");
372
- if (stopping() || (continuous && (terminal !== undefined || !renewing)))
373
- return yield* admissionClosed();
374
- // Registered in the same synchronous step as the stopping check, so
375
- // either close sees this attempt or this attempt sees close.
376
- acquisitions.add(fiber);
377
- const reservation = retention === undefined ? undefined : yield* retention.reserve;
378
- const attemptIncarnation = reservation?.incarnation ?? ++incarnation;
379
- const recordAttempt = (cleanup) => {
380
- if (initializing && firstCleanup === undefined)
381
- firstCleanup = cleanup;
382
- return retention !== undefined && reservation !== undefined
383
- ? retention.record(reservation, cleanup)
384
- : Effect.sync(() => recordCleanup(cleanup));
385
- };
386
- const owned = yield* Scope.make();
387
- const attempt = {};
388
- let acquired;
389
- let acquiredSource;
390
- let acquisitionCleanup;
391
- let acquisitionDefect;
392
- return yield* restore(Effect.gen(function* () {
393
- // Replace the captured Scope explicitly in one context operation.
394
- // Dependency provision order cannot move resources to the caller's owner.
395
- const value = yield* options.open.pipe(Effect.provideContext(Context.add(context, Scope.Scope, owned)), Effect.timeoutOrElse({
396
- duration: "30 seconds",
397
- orElse: () => Effect.fail(ReactorError.fromCode("InvalidState", "Source acquisition timed out")),
398
- }));
399
- acquiredSource = value.source;
400
- if (retention !== undefined && reservation !== undefined)
401
- yield* retention.identify(reservation, value.source.id);
402
- const owner = Object.freeze({
403
- namespace,
404
- incarnation: attemptIncarnation,
405
- sessionId: value.source.id,
406
- });
407
- const lifetime = yield* parsed(() => duration(value.lifetime, "source lifetime", { allowInfinite: true }));
408
- // A lead as long as the lifetime would prepare a replacement at once,
409
- // after every open, bounded only by maxSessions.
410
- if (Duration.isFinite(lifetime) && leadSeconds >= Duration.toSeconds(lifetime))
411
- return yield* ReactorError.fromCode("InvalidInput", "renewal lead must be shorter than the source lifetime", { operation: "renewal", outcome: "not-submitted" });
412
- if (physicalOwners.has(value.source.id))
413
- return yield* ReactorError.fromCode("InvalidInput", "Orchestration sources must have distinct session identities");
414
- const media = yield* value.source.media;
415
- const local = yield* SourceSlot.make({
416
- source: value.source,
417
- scope: owned,
418
- media,
419
- openedAt: monotonicMillis(clock),
420
- maxSeconds: Duration.toSeconds(lifetime),
421
- cleanupBudgetMs: reconnectTimeout,
422
- recordCleanup: recordAttempt,
423
- boundedHistory: continuous,
424
- retired: (facts, retirementExit) => Effect.gen(function* () {
425
- const bookkeeping = yield* Effect.exit(Effect.gen(function* () {
426
- if (acquisitionCleanup !== undefined)
427
- yield* recordAttempt(acquisitionCleanup);
428
- if (retention === undefined || reservation === undefined)
429
- return;
430
- const lossExit = acquired === undefined
431
- ? Exit.void
432
- : yield* Effect.exit(retireOwner(acquired));
433
- yield* retention.finish(reservation, {
434
- ...facts,
435
- errors: [
436
- ...facts.errors,
437
- ...(acquisitionDefect === undefined ? [] : [acquisitionDefect]),
438
- ...(Exit.isFailure(lossExit)
439
- ? [errorOf(lossExit.cause, "InvalidState", "media loss accounting")]
440
- : []),
441
- ],
442
- });
443
- slots.delete(owner);
444
- if (physicalOwners.get(owner.sessionId) === owner)
445
- physicalOwners.delete(owner.sessionId);
446
- return yield* lossExit;
447
- }));
448
- // The slot calls this once, however many callers wait on its close.
449
- const exit = Exit.asVoidAll([retirementExit, bookkeeping]);
450
- if (Exit.isFailure(exit))
451
- recordFailure(attempt, exit.cause.reasons);
452
- return yield* bookkeeping;
453
- }),
454
- retireSequences: affinity.retire(owner).pipe(Effect.asVoid),
455
- });
456
- const slot = Object.assign(local, { owner });
457
- acquired = slot;
458
- slots.set(owner, slot);
459
- physicalOwners.set(owner.sessionId, owner);
460
- opened++;
461
- yield* slot.source.setAutoplay(false);
462
- yield* slot.observe((event) => Effect.gen(function* () {
463
- if (event._tag === "SessionFailed")
464
- return yield* scheduleRecovery(slot, event.failure, "replace");
465
- // Once the final clip has ended or arrived in full, a planned
466
- // switch follows within its bounded grace, so the retiring
467
- // source running dry is not starvation. Before its Ended, no
468
- // switch is due yet, and that starvation is reported.
469
- if (event._tag === "Starved" &&
470
- slot === current &&
471
- replacement._tag === "Ready" &&
472
- (slot.finalClip().video !== "incomplete" ||
473
- slot.finalClip().endedAgoMs !== undefined)) {
474
- const next = yield* replacement.slot.source.state;
475
- if (next.availability === "Ready" &&
476
- next.ready.length > 0 &&
477
- isIdle(yield* slot.source.state) &&
478
- !(yield* openSequences(slot)))
479
- return;
480
- }
481
- if (event._tag !== "Starved" || slot === current)
482
- emit(event);
483
- }), (cause) => scheduleRecovery(slot, cause, "replace"));
484
- yield* startMedia(slot);
485
- yield* announce({
486
- _tag: "Opened",
487
- sessionId: slot.source.id,
488
- lifetime: Duration.seconds(slot.maxSeconds),
489
- });
490
- return slot;
491
- })).pipe(Effect.onExit((exit) => Exit.isSuccess(exit)
492
- ? Effect.void
493
- : Effect.gen(function* () {
494
- // Capture an acquisition failure even if the source also
495
- // returns cleanup. Conflicting reports belong to this attempt.
496
- // A typed failure is the attempt's result, and cancellation
497
- // is not a defect: only a Die is a failure close must report.
498
- const defects = exit.cause.reasons.filter(Cause.isDieReason);
499
- if (defects.length > 0) {
500
- recordFailure(attempt, defects);
501
- acquisitionDefect = errorOf(Cause.fromReasons(defects), "InvalidState", "source acquisition");
502
- }
503
- const failure = Cause.findErrorOption(exit.cause);
504
- if (Option.isSome(failure) && AcquisitionFailure.is(failure.value))
505
- acquisitionCleanup = { lease: failure.value.cleanup, policy: [] };
506
- if (acquired !== undefined)
507
- yield* closeSlot(acquired);
508
- else {
509
- const sourceExit = acquiredSource === undefined
510
- ? Exit.void
511
- : yield* Effect.exit(acquiredSource.close.pipe(Effect.flatMap(recordAttempt)));
512
- const scopeExit = yield* Effect.exit(Scope.close(owned, exit));
513
- if (acquisitionCleanup !== undefined)
514
- yield* recordAttempt(acquisitionCleanup);
515
- if (retention !== undefined && reservation !== undefined) {
516
- yield* retention.finish(reservation, {
517
- accounting: "not-applicable",
518
- unknownSubmissions: 0n,
519
- affinity: "not-applicable",
520
- scope: Exit.isSuccess(scopeExit) ? "closed" : "failed",
521
- errors: [
522
- ...(acquisitionDefect === undefined ? [] : [acquisitionDefect]),
523
- ...[sourceExit, scopeExit].flatMap((result) => Exit.isFailure(result)
524
- ? [errorOf(result.cause, "InvalidState", "acquisition retirement")]
525
- : []),
526
- ],
527
- });
528
- }
529
- const retirementExit = Exit.asVoidAll([sourceExit, scopeExit]);
530
- if (Exit.isFailure(retirementExit))
531
- recordFailure(attempt, retirementExit.cause.reasons);
532
- yield* retirementExit;
533
- }
534
- })));
535
- })).pipe(
536
- // The attempt leaves the registry only after its own cleanup has run.
537
- Effect.onExit(() => Effect.withFiber((fiber) => Effect.sync(() => void acquisitions.delete(fiber)))), Effect.tap((slot) => Effect.annotateCurrentSpan("reactor.session.id", slot.source.id)), Effect.withSpan("reactor.orchestration.renewal.open", {}, { captureStackTrace: false }));
538
- // A forked attempt is interruptible whatever its caller is, so close and
539
- // the owner's scope can stop an open that an uninterruptible caller waits on.
540
- // A prepared replacement opens on its own after the tick that forks it.
541
- const forkAcquisition = Effect.forkIn(acquisition, scope);
542
- // To its caller, an attempt that close, stopRenewal or the owner's scope
543
- // stopped is refused, not failed. Interrupted while the owner still admits
544
- // sources, as by its own open, the attempt failed. A caller interrupted
545
- // while it waits stops the attempt too: the handler is in place before the
546
- // wait is restored, so an interruption that arrives first still reaches it.
547
- const joinAcquisition = (fiber, restore) => restore(Fiber.await(fiber)).pipe(Effect.onInterrupt(() => Fiber.interrupt(fiber)), Effect.flatMap((exit) => {
548
- if (Exit.isSuccess(exit))
549
- return exit;
550
- const failures = exit.cause.reasons.filter((reason) => !Cause.isInterruptReason(reason));
551
- if (failures.length > 0)
552
- return Effect.failCause(Cause.fromReasons(failures));
553
- return stopping()
554
- ? admissionClosed()
555
- : ReactorError.fromCode("InvalidState", "Source acquisition was interrupted");
556
- }));
557
- // The constructor and a replacement start their attempt at once, so an open
558
- // that completes without waiting is published in the same turn.
559
- const acquire = Effect.uninterruptibleMask((restore) => Effect.forkIn(acquisition, scope, { startImmediately: true }).pipe(Effect.flatMap((fiber) => joinAcquisition(fiber, restore))));
560
- const close = yield* SourceSlot.once(Effect.uninterruptible(Effect.gen(function* () {
561
- closing = true;
562
- // Every registered fiber is an attempt this owner forked, never a
563
- // caller's. Interrupting joins each attempt's cleanup, which records
564
- // its own defects; cancelling it is not a close failure, and an
565
- // Opening fiber that has not started never runs. Close does not wait
566
- // for an attempt it runs on, but one issued from inside `open` cannot
567
- // finish: `open` runs on a race fiber that stopping its attempt awaits.
568
- const pending = new Set(acquisitions);
569
- if (replacement._tag === "Opening")
570
- pending.add(replacement.fiber);
571
- yield* Effect.withFiber((self) => {
572
- pending.delete(self);
573
- return Fiber.interruptAll(pending);
574
- });
575
- for (const slot of [...slots.values()])
576
- yield* Effect.exit(closeSlot(slot));
577
- const exits = [...attemptFailures.values()].map((reasons) => Exit.failCause(Cause.fromReasons(reasons)));
578
- buffer.end();
579
- setMedia({ _tag: "Closed" });
580
- observations.end();
581
- if (renewalReader !== undefined)
582
- yield* Fiber.await(renewalReader).pipe(Effect.interruptible, Effect.timeout("1 second"), Effect.ignore);
583
- if (retention === undefined)
584
- finalReport = Object.freeze({ sessions: Object.freeze([...cleanups]) });
585
- else
586
- finalSummary = yield* retention.conclude;
587
- // The closed owner stays current, so final loss totals remain readable.
588
- replacement = { _tag: "Absent" };
589
- return yield* Exit.asVoidAll(exits);
590
- })));
591
- yield* Effect.addFinalizer(() => close);
592
- current = yield* acquire.pipe(Effect.tap((slot) => slot.source.setAutoplay(true)),
593
- // Fenced as publishReady is: construction returns no handle whose owner
594
- // stopped while its first source was activated, and close retires it.
595
- Effect.tap(() => Effect.gen(function* () {
596
- if (stopping())
597
- return yield* admissionClosed();
598
- })), Effect.catch((cause) => Effect.exit(close).pipe(Effect.flatMap((closed) => {
599
- // An AcquisitionFailure from `open` already carries the lease it
600
- // recorded, by reference; any later failure takes the recorded lease.
601
- const lease = firstCleanup?.lease;
602
- const failure = AcquisitionFailure.is(cause) || (lease === undefined && ReactorError.is(cause))
603
- ? cause
604
- : AcquisitionFailure.from(cause, lease ?? noAcquisition);
605
- // A defect of the close that retired the attempt stays beside the
606
- // typed failure, which the caller still handles.
607
- return Exit.isSuccess(closed)
608
- ? Effect.fail(failure)
609
- : Effect.failCause(Cause.combine(Cause.fail(failure), closed.cause));
610
- }))));
611
- initializing = false;
612
- firstCleanup = undefined;
613
- // The first owner's loss from its start is the output's: no baseline.
614
- outputOwner = current.owner;
615
- setMedia({
616
- _tag: "Ready",
617
- sessionId: current.source.id,
618
- generation: current.media.generation,
619
- });
620
- const guard = (operation, effect) => Effect.gen(function* () {
621
- if (closing)
622
- return yield* PolicyFailure.refuse("SessionClosed", "Orchestration is closed", operation);
623
- if (yield* Deferred.isDone(fatal))
624
- return yield* CommandFailure.from(yield* Deferred.await(fatal), {
625
- operation,
626
- outcome: "not-submitted",
627
- });
628
- return yield* effect;
629
- });
630
- const route = (request) => Effect.gen(function* () {
631
- const values = yield* Effect.suspend(() => Effect.forEach([...slots.values()], (slot) => Effect.map(slot.source.state, (state) => ({
632
- owner: slot.owner,
633
- state,
634
- accepted: slot.accepted,
635
- closed: slot.closed,
636
- recovering: slot.recovering,
637
- }))));
638
- const binding = request.sequence === undefined ? undefined : yield* affinity.get(request.sequence.id);
639
- const next = replacement._tag === "Ready" ? replacement.slot : undefined;
640
- const preferred = next !== undefined && current !== undefined && !(yield* openSequences(current))
641
- ? next
642
- : current;
643
- if (preferred === undefined)
644
- return yield* PolicyFailure.refuse("SessionRecovering", "No source is ready");
645
- return yield* resolve(request, values, preferred.owner, binding);
646
- });
647
- const sequenceError = (error) => PolicyFailure.sequence(error.sequenceId, error.code);
648
- const prepare = (input, expectedSessionId) => Effect.gen(function* () {
649
- const expectedOwner = expectedSessionId === undefined ? undefined : physicalOwners.get(expectedSessionId);
650
- const request = yield* captureRequest(input);
651
- const id = `${namespace}:${++submissionSequence}`;
652
- const gate = yield* Semaphore.make(1);
653
- const logical = logicalSubmission(id);
654
- let selectedOwner;
655
- let active;
656
- let activeOwner;
657
- const submit = gate.withPermit(Effect.gen(function* () {
658
- const completed = logical.outcome();
659
- if (completed !== undefined)
660
- return yield* completed;
661
- if (active === undefined) {
662
- // A committed submission remains a readable outcome after the handle
663
- // closes. Only selecting or committing fresh work needs a live owner.
664
- yield* guard("enqueue", Effect.void);
665
- const decision = yield* route(request).pipe(commands.withPermits(1));
666
- if (expectedSessionId !== undefined && decision.owner !== expectedOwner)
667
- return yield* PolicyFailure.refuse("RouteChanged", "The selected source changed before dispatch");
668
- if (continuous && selectedOwner !== undefined && selectedOwner !== decision.owner) {
669
- // The selection stays fenced to its incarnation. While that
670
- // source is live, only the route moved; it has not retired.
671
- const selected = slots.get(selectedOwner);
672
- return yield* selected !== undefined && !selected.closed
673
- ? PolicyFailure.refuse("RouteChanged", "The selected source changed before dispatch")
674
- : PolicyFailure.refuse("SessionRetired", "Selected source incarnation was retired");
675
- }
676
- selectedOwner = decision.owner;
677
- const target = slots.get(decision.owner);
678
- if (target === undefined)
679
- return yield* PolicyFailure.refuse("SessionRetired", "Selected source was retired");
680
- const sequence = request.sequence;
681
- let resultAccounted = false;
682
- active = yield* target.source.prepareRouted({ request, position: decision.position }, {
683
- commit: (submissionId) => commands.withPermit(guard("enqueue", Effect.gen(function* () {
684
- const checked = yield* route(request);
685
- if (checked.owner !== decision.owner ||
686
- checked.position !== decision.position) {
687
- return yield* PolicyFailure.refuse("RouteChanged", "Request ownership or insertion position changed during preparation");
688
- }
689
- if (active === undefined)
690
- return yield* Effect.die(new Error("Source committed before returning its inert submission"));
691
- yield* target.register(active, Effect.gen(function* () {
692
- if (sequence !== undefined) {
693
- yield* affinity.bind(sequence.id, target.owner);
694
- yield* affinity.begin(sequence.id, sequence.memberId ?? submissionId);
695
- }
696
- }).pipe(Effect.mapError(sequenceError)), (exit) => {
697
- logical.complete(exit);
698
- active = undefined;
699
- activeOwner = undefined;
700
- });
701
- }))),
702
- result: (submissionId, result) => Effect.gen(function* () {
703
- if (resultAccounted)
704
- return;
705
- resultAccounted = true;
706
- // An accepted identity past the continuous bound is a
707
- // source-contract failure, as for Started: it is never
708
- // retained, its member cannot be attributed and the source
709
- // is replaced.
710
- const recorded = yield* Effect.result(target.recordResult(result));
711
- if (sequence !== undefined) {
712
- const memberId = sequence.memberId ?? submissionId;
713
- const account = Result.isFailure(recorded)
714
- ? affinity.uncertain(sequence.id, memberId, recorded.failure.message)
715
- : Result.isSuccess(result)
716
- ? affinity.accepted(sequence.id, memberId, result.success, sequence.final)
717
- : result.failure.context.outcome === "unknown"
718
- ? affinity.uncertain(sequence.id, memberId, result.failure.message)
719
- : affinity.rejected(sequence.id, memberId, result.failure.message, sequence.final);
720
- yield* account.pipe(Effect.catch((cause) => Effect.gen(function* () {
721
- target.markIndeterminate();
722
- yield* affinity.retire(target.owner);
723
- yield* fail(ReactorError.fromCode("InvalidState", "Committed sequence accounting failed", { detail: cause }));
724
- })));
725
- }
726
- target.finishAccounting();
727
- if (Result.isFailure(recorded))
728
- yield* scheduleRecovery(target, recorded.failure, "replace");
729
- else if (Result.isFailure(result) &&
730
- result.failure.context.outcome === "unknown") {
731
- yield* scheduleRecovery(target, result.failure, "replace");
732
- }
733
- }),
734
- });
735
- activeOwner = target;
736
- }
737
- const selected = active;
738
- return yield* selected.submit.pipe(Effect.onExit(() => selected.state.pipe(Effect.flatMap((state) => Effect.sync(() => {
739
- if (state._tag === "Completed") {
740
- logical.complete(state.exit);
741
- activeOwner?.forgetCompleted(selected);
742
- active = undefined;
743
- activeOwner = undefined;
744
- }
745
- if (state._tag === "Prepared" && active === selected) {
746
- active = undefined;
747
- activeOwner = undefined;
748
- }
749
- })))));
750
- }));
751
- logical.attach(submit, Effect.suspend(() => active === undefined
752
- ? Effect.succeed({ _tag: "Prepared" })
753
- : active.state));
754
- return logical.submission;
755
- });
756
- const state = Effect.gen(function* () {
757
- if (!closing && terminal !== undefined && Exit.isFailure(terminal))
758
- return yield* Effect.failCause(terminal.cause);
759
- const live = [...slots.values()].filter((slot) => !slot.closed);
760
- const values = (yield* Effect.forEach(live, (slot) => slot.source.state)).map((value, index) => withSessionId(value, live[index].source.id));
761
- const primary = values[live.indexOf(current)] ?? emptyState();
762
- const next = replacement._tag === "Ready" ? replacement.slot : undefined;
763
- const preferred = next !== undefined && current !== undefined && !(yield* openSequences(current))
764
- ? next
765
- : current;
766
- const builds = values.flatMap((value) => Option.isSome(value.building) ? [value.building.value] : []);
767
- return Object.freeze({
768
- ...primary,
769
- sessions: Object.freeze(values.map((value, index) => ({
770
- sessionId: live[index].source.id,
771
- availability: value.availability,
772
- }))),
773
- preferredSessionId: Option.fromUndefinedOr(preferred?.source.id),
774
- retiringSessionId: next !== undefined && current !== undefined && !current.closed
775
- ? Option.some(current.source.id)
776
- : Option.none(),
777
- // No media condition: a short final clip holds the switch only for the
778
- // grace past its Ended, or past the source first reported idle. Gating
779
- // on the playing clip's frames instead could hold only in the instant
780
- // between its last frame and Ended, so a lossy clip let the retiring
781
- // source roll into its next filler.
782
- handoffReady: next !== undefined &&
783
- preferred === next &&
784
- current !== undefined &&
785
- !current.closed &&
786
- !current.recovering &&
787
- primary.availability === "Ready",
788
- availability: values.some((value) => value.availability === "Unavailable")
789
- ? "Unavailable"
790
- : values.some((value) => value.availability === "Synchronizing")
791
- ? "Synchronizing"
792
- : primary.availability,
793
- queued: Object.freeze([
794
- ...values.flatMap((value) => value.queued),
795
- ...builds.slice(1).map((build) => build.record),
796
- ]),
797
- generationOrder: Object.freeze(values.flatMap((value) => value.generationOrder)),
798
- building: Option.fromUndefinedOr(builds[0]),
799
- ready: Object.freeze(values.flatMap((value) => value.ready)),
800
- continuable: Object.freeze(values.flatMap((value) => value.continuable)),
801
- failed: Object.freeze(values.flatMap((value) => value.failed)),
802
- capacities: {
803
- generation: Math.min(primary.capacities.generation, ...values.map((value) => value.capacities.generation)),
804
- playout: Math.min(primary.capacities.playout, ...values.map((value) => value.capacities.playout)),
805
- },
806
- });
807
- });
808
- const owner = (id, operation) => Effect.gen(function* () {
809
- const matches = yield* Effect.forEach([...slots.values()].filter((slot) => !slot.closed), (slot) => slot.source.state.pipe(Effect.map((state) => ({ slot, state }))));
810
- const found = matches.filter(({ state }) => activeIds(state).includes(id));
811
- if (found.length !== 1)
812
- return yield* PolicyFailure.refuse(found.length === 0 ? "NotFound" : "OwnerConflict", "Clip has no unique active owning session", operation);
813
- if (found[0].slot.recovering)
814
- return yield* PolicyFailure.refuse("SessionRecovering", "Owning session is recovering", operation);
815
- return found[0];
816
- });
817
- const engine = {
818
- prepare,
819
- enqueue: (request) => prepare(request).pipe(Effect.flatMap((submission) => submission.submit)),
820
- enqueueOnSource: (request, expectedSessionId) => prepare(request, expectedSessionId).pipe(Effect.flatMap((submission) => submission.submit)),
821
- state,
822
- events: Stream.filterMap(observations.stream(), engineOnly),
823
- observe: (options) => observations.observeWith(state, options).pipe(Effect.map(({ initial, events }) => ({
824
- initial,
825
- events: Stream.filterMap(events, engineOnly),
826
- }))),
827
- failure: Deferred.await(fatal),
828
- stopRenewal: Effect.gen(function* () {
829
- // Fence allocation before waiting on a command already in progress.
830
- renewing = false;
831
- yield* commands.withPermit(Effect.gen(function* () {
832
- if (replacement._tag !== "Opening")
833
- return;
834
- const pending = replacement.fiber;
835
- // It stays Opening until joined, so a concurrent close joins it too.
836
- yield* Fiber.interrupt(pending);
837
- const result = yield* Fiber.await(pending);
838
- if (replacement._tag === "Opening" && replacement.fiber === pending)
839
- replacement = { _tag: "Absent" };
840
- if (Exit.isSuccess(result))
841
- yield* closeSlot(result.value);
842
- }));
843
- }),
844
- setAutoplay: (enabled) => commands.withPermit(guard("set_autoplay", Effect.gen(function* () {
845
- autoplay = enabled;
846
- if (current !== undefined && !current.closed)
847
- yield* current.source.setAutoplay(enabled);
848
- }))),
849
- pauseAndStop: commands.withPermit(guard("pauseAndStop", Effect.gen(function* () {
850
- autoplay = false;
851
- for (const slot of slots.values())
852
- if (!slot.closed) {
853
- yield* slot.source.setAutoplay(false);
854
- yield* slot.source.stop;
855
- }
856
- }))),
857
- remove: (id) => commands.withPermit(guard("pop", owner(id, "pop").pipe(Effect.flatMap(({ slot }) => slot.source.remove(id))))),
858
- cut: (id) => commands.withPermit(guard("stop", owner(id, "stop").pipe(Effect.flatMap(({ slot, state }) => Option.getOrUndefined(state.playing)?.clipId === id
859
- ? slot.source.stop
860
- : PolicyFailure.refuse("NotFound", "The clip is no longer playing", "stop"))))),
861
- move: (id, position, queue) => commands.withPermit(guard("move", Effect.gen(function* () {
862
- if (!Number.isSafeInteger(position) || position < 0)
863
- return yield* PolicyFailure.refuse("InvalidRequest", "Move position must be a nonnegative integer", "move");
864
- const selected = yield* owner(id, "move");
865
- const own = queue === "generation" ? generation(selected.state) : selected.state.ready;
866
- if (!own.some((clip) => clip.clipId === id))
867
- return yield* PolicyFailure.refuse("QueueChanged", "Clip is no longer in the selected application queue", "move");
868
- let preceding = 0;
869
- for (const slot of slots.values()) {
870
- if (slot === selected.slot)
871
- break;
872
- if (slot.closed)
873
- continue;
874
- const value = yield* slot.source.state;
875
- preceding += queue === "generation" ? generation(value).length : value.ready.length;
876
- }
877
- if (position < preceding || position >= preceding + own.length)
878
- return yield* PolicyFailure.refuse("InvalidRequest", "Move position is outside the clip's session range", "move");
879
- yield* selected.slot.source.move(id, position - preceding);
880
- }))),
881
- setCanvas: (canvas) => commands.withPermit(guard("set_canvas", Effect.gen(function* () {
882
- if (!isIdle(yield* state))
883
- return yield* PolicyFailure.refuse("Busy", "Canvas can only change while every source is idle", "set_canvas");
884
- for (const slot of slots.values())
885
- if (!slot.closed)
886
- yield* slot.source.setCanvas(canvas);
887
- }))),
888
- };
889
- const tick = commands
890
- .withPermit(Effect.gen(function* () {
891
- if (stopping() ||
892
- current === undefined ||
893
- current.closed ||
894
- current.recovering ||
895
- (yield* Deferred.isDone(fatal)))
896
- return;
897
- if (replacement._tag === "Opening") {
898
- const result = replacement.fiber.pollUnsafe();
899
- if (result !== undefined) {
900
- replacement = { _tag: "Absent" };
901
- // Close or the owner's scope stopped this attempt, and close
902
- // retires whatever it opened: that is no failed setup.
903
- if (stopping())
904
- return;
905
- if (Exit.isSuccess(result)) {
906
- replacement = { _tag: "Ready", slot: result.value };
907
- openFailures = 0;
908
- yield* log("Prepared the next session for renewal");
909
- yield* announce({ _tag: "Prepared" });
910
- }
911
- else {
912
- if (continuous) {
913
- const error = Cause.findErrorOption(result.cause);
914
- if (Option.isSome(error) &&
915
- ReactorError.is(error.value) &&
916
- error.value.reason._tag === "Overflow")
917
- return yield* fail(error.value);
918
- }
919
- openFailures++;
920
- retryAt = monotonicMillis(clock) + 5000;
921
- yield* announce({
922
- _tag: "SetupFailed",
923
- reason: "Could not prepare the next source",
924
- consecutive: openFailures,
925
- });
926
- if (openFailures >= 3)
927
- return yield* fail(ReactorError.fromCode("Disconnected", "Repeated source renewal acquisition failed"));
928
- }
929
- }
930
- }
931
- const decision = decideRenewal({
932
- running: !stopping() && terminal === undefined,
933
- now: monotonicMillis(clock),
934
- current,
935
- replacement: replacement._tag === "Ready" ? replacement.slot.phase : replacement._tag,
936
- leadSeconds,
937
- retryAt,
938
- });
939
- switch (decision) {
940
- case "Retain":
941
- return;
942
- case "Expire":
943
- yield* replace(current, ReactorError.fromCode("TerminalSession", "Source lifetime limit reached", {
944
- operation: "renewal",
945
- }));
946
- return;
947
- case "Prepare":
948
- if (renewing)
949
- replacement = { _tag: "Opening", fiber: yield* forkAcquisition };
950
- return;
951
- case "InspectHandoff":
952
- break;
953
- }
954
- const next = replacement._tag === "Ready" ? replacement.slot : undefined;
955
- if (next === undefined || (yield* openSequences(current)))
956
- return;
957
- const oldState = yield* current.source.state;
958
- const nextState = yield* next.source.state;
959
- if (!isIdle(oldState) ||
960
- nextState.availability !== "Ready" ||
961
- nextState.ready.length === 0)
962
- return;
963
- // The provider reports nothing playing: a lost Ended must not hold
964
- // the switch until expiry, so the grace also starts here.
965
- current.observedIdle();
966
- const evidence = handoffEvidence({
967
- sequenceOpen: false,
968
- currentIdle: isIdle(oldState),
969
- replacementReady: nextState.availability === "Ready" && nextState.ready.length > 0,
970
- finalClip: current.finalClip(),
971
- graceMs: handoffGraceMs,
972
- });
973
- if (evidence === undefined)
974
- return;
975
- const handoff = Object.freeze({ ...evidence, replacementSessionId: next.source.id });
976
- // Read only for a switch that happens: it can wait on source pressure.
977
- const tail = yield* retired(current);
978
- const old = current;
979
- // The switch is traced; the tick that found it is not.
980
- yield* Effect.gen(function* () {
981
- yield* retireOwner(old);
982
- current = next;
983
- yield* activateOwner(next);
984
- replacement = { _tag: "Absent" };
985
- yield* next.source.setAutoplay(autoplay);
986
- const activated = publishReady(next);
987
- yield* closeSlot(old);
988
- if (!activated)
989
- return;
990
- yield* announce({ _tag: "Switched", ...tail, handoff });
991
- yield* log("Switched prepared sessions at a sequence boundary");
992
- }).pipe(Effect.withSpan("reactor.orchestration.renewal.switch", {
993
- attributes: {
994
- "reactor.session.id": next.source.id,
995
- "reactor.session.previous_id": old.source.id,
996
- },
997
- }, { captureStackTrace: false }));
998
- }))
999
- .pipe(Effect.catch(fail));
1000
- // Spaced polling, not a fixed rate: each tick runs 100 ms after the previous one
1001
- // finished, so a slow tick delays the next instead of overlapping it.
1002
- yield* Effect.forever(Effect.sleep(100).pipe(Effect.andThen(tick))).pipe(supervise, Effect.forkIn(scope));
1003
- const pressure = Effect.suspend(() => {
1004
- if (current === undefined)
1005
- return Effect.fail(ReactorError.fromCode("InvalidState", "No active media source"));
1006
- const owner = current;
1007
- return owner.pressure.pipe(Effect.flatMap((source) => {
1008
- const queued = buffer.pressure();
1009
- // A retired owner's loss is already in the output's totals, as the
1010
- // closed continuous owner's is once its retirement completes.
1011
- const loss = outputOwner === owner.owner
1012
- ? SourceSlot.addLoss(outputLoss, SourceSlot.subtractLoss(SourceSlot.lossOf(Result.succeed(source)), ownerBaseline))
1013
- : outputLoss;
1014
- if (loss.video === null || loss.audio === null || loss.readers === null)
1015
- return Effect.fail(ReactorError.fromCode("InvalidState", "A former media owner's loss totals are unknown"));
1016
- return Effect.succeed({
1017
- ...source,
1018
- closed: closing,
1019
- queuedVideo: source.queuedVideo + queued.queuedVideo,
1020
- queuedAudio: source.queuedAudio + queued.queuedAudio,
1021
- queuedBytes: source.queuedBytes + queued.queuedBytes,
1022
- droppedVideo: loss.video,
1023
- droppedAudio: loss.audio,
1024
- readerOverflows: loss.readers,
1025
- });
1026
- }));
1027
- });
1028
- const facets = {
1029
- engine,
1030
- media: {
1031
- video: buffer.video,
1032
- audio: buffer.audio,
1033
- pressure,
1034
- videoFramesPerSecond: current.media.videoFramesPerSecond,
1035
- },
1036
- mediaState: Effect.suspend(() => !closing && terminal !== undefined && Exit.isFailure(terminal)
1037
- ? Effect.failCause(terminal.cause)
1038
- : Effect.succeed(mediaState)),
1039
- observe: (options) => observations.observeWith(Effect.map(state, (engine) => ({ engine, media: mediaState })), options),
1040
- sessionId: Effect.sync(() => current === undefined || current.closed ? Option.none() : Option.some(current.source.id)),
1041
- sequences: {
1042
- get: (id) => affinity
1043
- .get(id)
1044
- .pipe(Effect.map((entry) => entry === undefined
1045
- ? undefined
1046
- : Object.freeze({ ...entry, owner: entry.owner.sessionId }))),
1047
- snapshots: affinity.snapshots.pipe(Effect.map((entries) => Object.freeze(entries.map((entry) => Object.freeze({ ...entry, owner: entry.owner.sessionId }))))),
1048
- seal: affinity.seal,
1049
- acknowledgeIndeterminate: affinity.acknowledgeIndeterminate,
1050
- release: affinity.release,
1051
- },
1052
- };
1053
- return {
1054
- legacy: {
1055
- ...facets,
1056
- close: close.pipe(Effect.andThen(Effect.sync(() => {
1057
- if (finalReport === undefined)
1058
- throw new Error("Missing legacy cleanup report");
1059
- return finalReport;
1060
- }))),
1061
- cleanup: Effect.sync(() => Option.fromUndefinedOr(finalReport)),
1062
- },
1063
- continuous: {
1064
- ...facets,
1065
- close: close.pipe(Effect.andThen(Effect.sync(() => {
1066
- if (finalSummary === undefined)
1067
- throw new Error("Missing continuous cleanup summary");
1068
- return finalSummary;
1069
- }))),
1070
- cleanup: Effect.sync(() => Option.fromUndefinedOr(finalSummary)),
1071
- },
1072
- };
1073
- });
1074
- /** Legacy renewal retains complete cleanup history and defaults to 64 successful opens. */
1075
- export const make = (options) => makeOwner(options, false).pipe(Effect.map((owner) => owner.legacy));
1076
- /**
1077
- * Bounded continuous renewal. Adapters must meet ContinuousOptions' unique-ID
1078
- * precondition. Retired anchors no longer retained can refuse as missing rather
1079
- * than SessionRetired; captured operations remain fenced to their exact incarnation.
1080
- */
1081
- export const makeContinuous = (options) => makeOwner(options, true).pipe(Effect.map((owner) => owner.continuous));
1082
- /**
1083
- * The handle's `Engine`, `Media` and `Handle` services from one orchestration,
1084
- * so the three can never come from two paid session chains. Each build of the
1085
- * layer opens its own chain: bind it to a `const` and provide that one value.
1086
- */
1087
- export const layer = (options) => Layer.effectContext(Effect.map(make(options), handleContext));
1088
- //# sourceMappingURL=renewal.js.map