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,2597 +0,0 @@
1
- import * as Clock from "effect/Clock";
2
- import * as Context from "effect/Context";
3
- import * as Deferred from "effect/Deferred";
4
- import * as Duration from "effect/Duration";
5
- import * as Effect from "effect/Effect";
6
- import * as Exit from "effect/Exit";
7
- import * as Layer from "effect/Layer";
8
- import * as Option from "effect/Option";
9
- import * as PubSub from "effect/PubSub";
10
- import * as Queue from "effect/Queue";
11
- import * as Result from "effect/Result";
12
- import * as Schema from "effect/Schema";
13
- import * as Scope from "effect/Scope";
14
- import * as Stream from "effect/Stream";
15
- import * as SubscriptionRef from "effect/SubscriptionRef";
16
- import { take as takeQueue } from "../_internal/queue.js";
17
- import { duration } from "../duration.js";
18
- import { parsedInput, ReactorError } from "../errors.js";
19
- import { monotonicMillis } from "./elapsed.js";
20
- import { requestSeconds as h3RequestSeconds } from "../h3/profile.js";
21
- import { captureRequest, PolicyFailure } from "./request.js";
22
- import { activeIds } from "./routing.js";
23
- import { fillerIndexFromKey, fillerKey, isReservedSchedulerKey, keyedRequest, keyFromProviderMetadata, } from "./scheduler-key.js";
24
- import { dueCues, estimatesFrom, plan, projectedStartMs, runwaySeconds, } from "./scheduler-policy.js";
25
- import { Engine } from "./types.js";
26
- /** Stable caller identity for a scheduled item. */
27
- export const ItemKey = Schema.NonEmptyString.pipe(Schema.brand("ItemKey"));
28
- const isFirstDecisive = (status) => status._tag === "Started" ||
29
- status._tag === "Ended" ||
30
- status._tag === "Dropped" ||
31
- status._tag === "Failed" ||
32
- status._tag === "Unobserved" ||
33
- (status._tag === "Unknown" && status.terminal === true);
34
- export class KeyMismatch extends Schema.TaggedError()("KeyMismatch", {
35
- key: ItemKey,
36
- }) {
37
- static of(key) {
38
- return new KeyMismatch({ key });
39
- }
40
- }
41
- export class LaneBusy extends Schema.TaggedError()("LaneBusy", {
42
- key: ItemKey,
43
- lane: Schema.String,
44
- }) {
45
- static of(key, lane) {
46
- return new LaneBusy({ key, lane });
47
- }
48
- }
49
- export class WouldMissDeadline extends Schema.TaggedError()("WouldMissDeadline", { key: ItemKey }) {
50
- static of(key) {
51
- return new WouldMissDeadline({ key });
52
- }
53
- }
54
- const maxEarlyClipIds = 4096;
55
- const invalid = (message) => PolicyFailure.refuse("InvalidRequest", message, "submit");
56
- const ownedData = (input, fields, name) => {
57
- if (input === null || typeof input !== "object" || Array.isArray(input))
58
- return Result.fail(invalid(`${name} must be an object`));
59
- const descriptors = Object.getOwnPropertyDescriptors(input);
60
- if (Object.getOwnPropertySymbols(input).length > 0 ||
61
- Object.keys(descriptors).some((field) => !fields.includes(field) || !("value" in descriptors[field])))
62
- return Result.fail(invalid(`${name} has unsupported fields or accessors`));
63
- return Result.succeed(Object.fromEntries(Object.entries(descriptors).map(([field, descriptor]) => [field, descriptor.value])));
64
- };
65
- /** A Duration tuple or object read from own data properties; no accessor runs. */
66
- const durationData = (input) => {
67
- if (Array.isArray(input)) {
68
- const descriptors = Object.getOwnPropertyDescriptors(input);
69
- if (Object.getOwnPropertySymbols(input).length > 0 ||
70
- Object.keys(descriptors).some((field) => !["0", "1", "length"].includes(field) || !("value" in descriptors[field])) ||
71
- descriptors.length?.value !== 2 ||
72
- descriptors["0"] === undefined ||
73
- descriptors["1"] === undefined)
74
- return Result.fail(invalid("Duration tuple has unsupported fields or accessors"));
75
- const seconds = descriptors["0"].value;
76
- const nanos = descriptors["1"].value;
77
- return Result.succeed([seconds, nanos]);
78
- }
79
- const fields = ownedData(input, ["weeks", "days", "hours", "minutes", "seconds", "milliseconds", "microseconds", "nanoseconds"], "Duration");
80
- return Result.isFailure(fields) ? Result.fail(fields.failure) : Result.succeed(fields.success);
81
- };
82
- const captureDuration = (input) => Effect.gen(function* () {
83
- if (input === null || typeof input !== "object" || Duration.isDuration(input))
84
- return input;
85
- return yield* Effect.fromResult(durationData(input));
86
- });
87
- /**
88
- * A Duration option read as caller data: a data property, own or inherited,
89
- * captured without running any accessor. Rejections throw for `parsedInput`.
90
- */
91
- const optionDuration = (options, field, name) => {
92
- let descriptor;
93
- for (let owner = options; descriptor === undefined && owner !== null; owner = Object.getPrototypeOf(owner))
94
- descriptor = Object.getOwnPropertyDescriptor(owner, field);
95
- if (descriptor === undefined)
96
- return undefined;
97
- const reject = () => ReactorError.fromCode("InvalidInput", `${name} must be a Duration of data properties`);
98
- if (!("value" in descriptor))
99
- throw reject();
100
- const value = descriptor.value;
101
- if (value === undefined)
102
- return undefined;
103
- if (value === null)
104
- throw reject();
105
- if (typeof value !== "object" || Duration.isDuration(value))
106
- return value;
107
- const captured = durationData(value);
108
- if (Result.isFailure(captured))
109
- throw reject();
110
- return captured.success;
111
- };
112
- const requestDuration = (input, name, allowZero = false) => Effect.gen(function* () {
113
- const captured = yield* captureDuration(input);
114
- return yield* parsedInput(() => Duration.toMillis(duration(captured, name, { allowZero, allowNegative: name === "startBy" })), "submit").pipe(Effect.mapError((error) => invalid(error.message)));
115
- });
116
- /** A continuity choice, read as caller data, checked against the request it would continue. */
117
- const captureContinuity = (input, request) => Effect.gen(function* () {
118
- if (input === undefined)
119
- return undefined;
120
- if (input !== "previous")
121
- return yield* invalid('Continuity is "previous" or absent');
122
- if ((request.audio?.length ?? 0) > 2)
123
- return yield* invalid("A clip that continues takes at most two audio references");
124
- return "previous";
125
- });
126
- /** Enough cues for a line's overlays and actions, and a bound on one clip's timers. */
127
- const maxCues = 32;
128
- /** A clip's cues, read as caller data. */
129
- const captureCues = (input) => Effect.gen(function* () {
130
- if (input === undefined)
131
- return [];
132
- if (!Array.isArray(input))
133
- return yield* invalid("Cues must be an array");
134
- // Read as a plain object: a mapped array type would hide the descriptors.
135
- const descriptors = Object.getOwnPropertyDescriptors(input);
136
- const length = descriptors.length?.value;
137
- if (typeof length !== "number" || length > maxCues)
138
- return yield* invalid(`A clip takes at most ${maxCues} cues`);
139
- const cues = [];
140
- for (let index = 0; index < length; index++) {
141
- const descriptor = descriptors[String(index)];
142
- if (descriptor === undefined || !("value" in descriptor))
143
- return yield* invalid("Cues must be data");
144
- const cue = yield* Effect.fromResult(ownedData(descriptor.value, ["name", "at"], "Cue"));
145
- const at = yield* Effect.fromResult(ownedData(cue.at, ["from", "offset"], "Cue time"));
146
- if (typeof cue.name !== "string" || cue.name.length === 0)
147
- return yield* invalid("A cue needs a nonempty name");
148
- if (at.from !== "start" && at.from !== "end")
149
- return yield* invalid("A cue is timed from the start or the end");
150
- cues.push({
151
- name: cue.name,
152
- from: at.from,
153
- offsetMs: yield* requestDuration(at.offset, "cue offset", true),
154
- });
155
- }
156
- return cues;
157
- });
158
- /** Capture scheduling fields before reading the request's placement fields. */
159
- /** A caller's clip request, without the placement and source fields the scheduler owns. */
160
- const captureClip = (input) => Effect.gen(function* () {
161
- const request = yield* captureRequest(input);
162
- if (request.position !== undefined ||
163
- request.before !== undefined ||
164
- request.sameSessionAs !== undefined ||
165
- request.continueFrom !== undefined ||
166
- request.sequence !== undefined)
167
- return yield* invalid("Scheduler owns placement and source selection");
168
- return request;
169
- });
170
- /** A window's offsets from admission, read as caller data. */
171
- const captureWindow = (input) => Effect.gen(function* () {
172
- const window = input === undefined
173
- ? undefined
174
- : yield* Effect.fromResult(ownedData(input, ["notBefore", "startBy", "firm"], "Scheduled window"));
175
- const notBeforeOffsetMs = window?.notBefore === undefined
176
- ? undefined
177
- : yield* requestDuration(window.notBefore, "notBefore", true);
178
- const startByOffsetMs = window?.startBy === undefined
179
- ? undefined
180
- : yield* requestDuration(window.startBy, "startBy", true);
181
- if (window !== undefined &&
182
- (typeof window.firm !== "boolean" ||
183
- (notBeforeOffsetMs !== undefined &&
184
- startByOffsetMs !== undefined &&
185
- startByOffsetMs >= 0 &&
186
- notBeforeOffsetMs > startByOffsetMs)))
187
- return yield* invalid("Scheduled window is inconsistent");
188
- return { notBeforeOffsetMs, startByOffsetMs, firm: window?.firm === true };
189
- });
190
- const captureItem = (input, lanes) => Effect.gen(function* () {
191
- if (input === null || typeof input !== "object" || Array.isArray(input))
192
- return yield* invalid("Scheduled item must be an object");
193
- const descriptors = Object.getOwnPropertyDescriptors(input);
194
- if (Object.getOwnPropertySymbols(input).length > 0 ||
195
- Object.keys(descriptors).some((field) => !["key", "lane", "request", "window", "start", "cues", "continuity"].includes(field) ||
196
- !("value" in descriptors[field])))
197
- return yield* invalid("Scheduled item has unsupported fields or accessors");
198
- const key = yield* Schema.decodeUnknownEffect(ItemKey)(descriptors.key?.value).pipe(Effect.mapError(() => invalid("Scheduled item key must be nonempty")));
199
- if (isReservedSchedulerKey(key))
200
- return yield* invalid("Scheduled item key is reserved");
201
- const lane = descriptors.lane?.value;
202
- if (typeof lane !== "string" || !lanes.has(lane))
203
- return yield* invalid("Scheduled item lane is not configured");
204
- const request = yield* captureClip(descriptors.request?.value);
205
- const { notBeforeOffsetMs, startByOffsetMs, firm } = yield* captureWindow(descriptors.window?.value);
206
- const cues = yield* captureCues(descriptors.cues?.value);
207
- const continuity = yield* captureContinuity(descriptors.continuity?.value, request);
208
- const rawStart = descriptors.start?.value;
209
- const start = rawStart === undefined
210
- ? undefined
211
- : yield* Effect.fromResult(ownedData(rawStart, ["_tag", "time", "late"], "Scheduled start mode"));
212
- let atWallMs;
213
- let late = "nextBoundary";
214
- const mode = start?._tag === "Asap" ? "asap" : start?._tag === "Manual" ? "manual" : undefined;
215
- if ((start?._tag === "Follow" || mode !== undefined) &&
216
- (start?.time !== undefined || start?.late !== undefined))
217
- return yield* invalid(`${String(start?._tag)} cannot contain an anchor or lateness policy`);
218
- if (start !== undefined && start._tag !== "Follow" && mode === undefined) {
219
- if (start._tag !== "At" || !Number.isFinite(start.time))
220
- return yield* invalid("Scheduled start mode is invalid");
221
- atWallMs = start.time;
222
- const policy = yield* Effect.fromResult(ownedData(start.late, ["_tag", "by"], "Scheduled lateness policy"));
223
- if (policy._tag === "drop" && policy.by === undefined)
224
- late = "drop";
225
- else if (policy._tag === "skipIfLaterThan")
226
- late = {
227
- skipIfLaterThanMs: yield* requestDuration(policy.by, "skipIfLaterThan", true),
228
- };
229
- else if (policy._tag !== "nextBoundary" || policy.by !== undefined)
230
- return yield* invalid("Scheduled lateness policy is invalid");
231
- }
232
- return {
233
- key,
234
- lane,
235
- request,
236
- notBeforeOffsetMs,
237
- startByOffsetMs,
238
- firm,
239
- atWallMs,
240
- late,
241
- ...(mode === undefined ? {} : { mode }),
242
- ...(cues.length === 0 ? {} : { cues }),
243
- ...(continuity === undefined ? {} : { continuity }),
244
- fingerprint: JSON.stringify({
245
- lane,
246
- request,
247
- notBeforeOffsetMs,
248
- startByOffsetMs,
249
- firm,
250
- atWallMs,
251
- late,
252
- mode,
253
- ...(cues.length === 0 ? {} : { cues }),
254
- ...(continuity === undefined ? {} : { continuity }),
255
- }),
256
- };
257
- });
258
- /** Enough parts for a spoken line of beats, and a bound on one submission's work. */
259
- const maxGroupParts = 64;
260
- const captureGroup = (input, lanes) => Effect.gen(function* () {
261
- const group = yield* Effect.fromResult(ownedData(input, ["key", "lane", "parts", "window"], "Scheduled group"));
262
- const key = yield* Schema.decodeUnknownEffect(ItemKey)(group.key).pipe(Effect.mapError(() => invalid("Scheduled group key must be nonempty")));
263
- if (isReservedSchedulerKey(key))
264
- return yield* invalid("Scheduled group key is reserved");
265
- if (!Array.isArray(group.parts))
266
- return yield* invalid("Scheduled group parts must be an array");
267
- // Read as a plain object: a mapped array type would hide the descriptors.
268
- const descriptors = Object.getOwnPropertyDescriptors(group.parts);
269
- const length = descriptors.length?.value;
270
- if (typeof length !== "number" || length < 1 || length > maxGroupParts)
271
- return yield* invalid(`Scheduled group needs 1 to ${maxGroupParts} parts`);
272
- const parts = [];
273
- for (let index = 0; index < length; index++) {
274
- const descriptor = descriptors[String(index)];
275
- if (descriptor === undefined || !("value" in descriptor))
276
- return yield* invalid("Scheduled group parts must be data");
277
- const part = yield* Effect.fromResult(ownedData(descriptor.value, ["key", "request", "cues", "continuity"], "Scheduled group part"));
278
- parts.push(yield* captureItem({
279
- key: part.key,
280
- lane: group.lane,
281
- request: part.request,
282
- ...(part.cues === undefined ? {} : { cues: part.cues }),
283
- ...(part.continuity === undefined ? {} : { continuity: part.continuity }),
284
- ...(index === 0 && group.window !== undefined ? { window: group.window } : {}),
285
- }, lanes));
286
- }
287
- const [first, ...rest] = parts;
288
- if (first === undefined || new Set([key, ...parts.map((part) => part.key)]).size !== length + 1)
289
- return yield* invalid("Scheduled group and part keys must be distinct");
290
- return {
291
- key,
292
- parts: [first, ...rest],
293
- fingerprint: JSON.stringify(parts.map((part) => [part.key, part.fingerprint])),
294
- };
295
- });
296
- const captureReplacement = (input) => Effect.gen(function* () {
297
- const next = yield* Effect.fromResult(ownedData(input, ["key", "request", "cues", "continuity"], "Replacement"));
298
- const key = yield* Schema.decodeUnknownEffect(ItemKey)(next.key).pipe(Effect.mapError(() => invalid("Replacement key must be nonempty")));
299
- if (isReservedSchedulerKey(key))
300
- return yield* invalid("Replacement key is reserved");
301
- const request = yield* captureClip(next.request);
302
- return {
303
- key,
304
- request,
305
- cues: yield* captureCues(next.cues),
306
- continuity: yield* captureContinuity(next.continuity, request),
307
- };
308
- });
309
- const captureInsert = (input) => Effect.gen(function* () {
310
- const spec = yield* Effect.fromResult(ownedData(input, ["key", "request", "before", "after", "window", "cues", "continuity"], "Insert"));
311
- const key = yield* Schema.decodeUnknownEffect(ItemKey)(spec.key).pipe(Effect.mapError(() => invalid("Insert key must be nonempty")));
312
- if (isReservedSchedulerKey(key))
313
- return yield* invalid("Insert key is reserved");
314
- if ((spec.before === undefined) === (spec.after === undefined))
315
- return yield* invalid("Insert needs exactly one of before or after");
316
- const side = spec.before === undefined ? "after" : "before";
317
- const anchor = yield* Schema.decodeUnknownEffect(ItemKey)(spec.before ?? spec.after).pipe(Effect.mapError(() => invalid("Insert anchor must be a nonempty key")));
318
- const request = yield* captureClip(spec.request);
319
- const window = yield* captureWindow(spec.window);
320
- const cues = yield* captureCues(spec.cues);
321
- const continuity = yield* captureContinuity(spec.continuity, request);
322
- return {
323
- key,
324
- request,
325
- side,
326
- anchor,
327
- ...window,
328
- cues,
329
- continuity,
330
- fingerprint: JSON.stringify({
331
- [side]: anchor,
332
- request,
333
- ...window,
334
- ...(cues.length === 0 ? {} : { cues }),
335
- ...(continuity === undefined ? {} : { continuity }),
336
- }),
337
- };
338
- });
339
- /** A bound on one batch's work, generous for a spoken plan of several lines. */
340
- const maxEdits = 256;
341
- const editKey = (input, name) => Schema.decodeUnknownEffect(ItemKey)(input).pipe(Effect.mapError(() => invalid(`${name} must be a nonempty key`)));
342
- const captureEdits = (input, lanes) => Effect.gen(function* () {
343
- if (!Array.isArray(input))
344
- return yield* invalid("Edits must be an array");
345
- // Read as a plain object: a mapped array type would hide the descriptors.
346
- const descriptors = Object.getOwnPropertyDescriptors(input);
347
- const length = descriptors.length?.value;
348
- if (typeof length !== "number" || length < 1 || length > maxEdits)
349
- return yield* invalid(`An edit batch needs 1 to ${maxEdits} edits`);
350
- const edits = [];
351
- for (let index = 0; index < length; index++) {
352
- const descriptor = descriptors[String(index)];
353
- if (descriptor === undefined || !("value" in descriptor))
354
- return yield* invalid("Edits must be data");
355
- const raw = descriptor.value;
356
- const tag = raw !== null && typeof raw === "object"
357
- ? Object.getOwnPropertyDescriptor(raw, "_tag")?.value
358
- : undefined;
359
- switch (tag) {
360
- case "Submit": {
361
- const edit = yield* Effect.fromResult(ownedData(raw, ["_tag", "item"], "Edit"));
362
- edits.push({ _tag: "Submit", item: yield* captureItem(edit.item, lanes) });
363
- break;
364
- }
365
- case "SubmitGroup": {
366
- const edit = yield* Effect.fromResult(ownedData(raw, ["_tag", "group"], "Edit"));
367
- edits.push({
368
- _tag: "SubmitGroup",
369
- group: yield* captureGroup(edit.group, lanes),
370
- });
371
- break;
372
- }
373
- case "Insert": {
374
- const edit = yield* Effect.fromResult(ownedData(raw, ["_tag", "insert"], "Edit"));
375
- edits.push({ _tag: "Insert", insert: yield* captureInsert(edit.insert) });
376
- break;
377
- }
378
- case "Replace": {
379
- const edit = yield* Effect.fromResult(ownedData(raw, ["_tag", "key", "next"], "Edit"));
380
- edits.push({
381
- _tag: "Replace",
382
- key: yield* editKey(edit.key, "A replaced key"),
383
- next: yield* captureReplacement(edit.next),
384
- });
385
- break;
386
- }
387
- case "Withdraw": {
388
- const edit = yield* Effect.fromResult(ownedData(raw, ["_tag", "key"], "Edit"));
389
- edits.push({ _tag: "Withdraw", key: yield* editKey(edit.key, "A withdrawn key") });
390
- break;
391
- }
392
- default:
393
- return yield* invalid("An edit's _tag is not one the scheduler knows");
394
- }
395
- }
396
- return edits;
397
- });
398
- export const lineup = (filler) => ({
399
- lanes: [{ name: "line" }],
400
- filler,
401
- });
402
- export class Scheduler extends Context.Service()("reactor-effect-client/Orchestration/Scheduler") {
403
- static lineup = lineup;
404
- }
405
- export const makeScheduler = (options) => Effect.gen(function* () {
406
- const engine = yield* Engine;
407
- const clock = yield* Clock.Clock;
408
- const lanes = yield* parsedInput(() => {
409
- if (options.lanes.length === 0 ||
410
- options.lanes.some((lane) => typeof lane.name !== "string" || lane.name.length === 0) ||
411
- new Set(options.lanes.map((lane) => lane.name)).size !== options.lanes.length)
412
- throw ReactorError.fromCode("InvalidInput", "Scheduler lanes must be unique and nonempty");
413
- if (options.lanes.some((lane) => (lane.conflict !== undefined &&
414
- !["queue", "replace", "skip"].includes(lane.conflict)) ||
415
- (lane.cut !== undefined && typeof lane.cut !== "boolean")))
416
- throw ReactorError.fromCode("InvalidInput", "A lane's conflict or cut is invalid");
417
- return options.lanes.map((lane) => lane.name);
418
- }, "makeScheduler");
419
- const conflicts = new Map(options.lanes.map((lane) => [lane.name, lane.conflict ?? "queue"]));
420
- const cutLanes = new Set(options.lanes.filter((lane) => lane.cut === true).map((lane) => lane.name));
421
- const { floorSeconds, targetSeconds, maxBuildsInFlight, maxHistory, unknownRecoveryMs, fillLengths, } = yield* parsedInput(() => {
422
- const floorSeconds = Duration.toMillis(duration(options.filler.runway.floor, "runway floor", { allowZero: true })) / 1000;
423
- const targetSeconds = Duration.toMillis(duration(options.filler.runway.target, "runway target")) / 1000;
424
- const maxBuildsInFlight = options.maxBuildsInFlight ?? 1;
425
- const maxHistory = options.maxHistory ?? 4096;
426
- const unknownRecoveryMs = Duration.toMillis(duration(optionDuration(options, "unknownRecoveryTimeout", "Unknown recovery timeout") ??
427
- "60 seconds", "unknown recovery timeout", { maximum: "10 minutes" }));
428
- if (floorSeconds > targetSeconds ||
429
- !Number.isSafeInteger(maxBuildsInFlight) ||
430
- maxBuildsInFlight < 1 ||
431
- maxBuildsInFlight > 1024 ||
432
- !Number.isSafeInteger(maxHistory) ||
433
- maxHistory < 0 ||
434
- maxHistory > 65_536)
435
- throw ReactorError.fromCode("InvalidInput", "Scheduler runway or build cap is invalid");
436
- const lengths = options.filler.lengths;
437
- const fillLengths = {
438
- min: lengths === undefined
439
- ? h3RequestSeconds.min
440
- : Duration.toMillis(duration(lengths.min, "filler minimum length")) / 1000,
441
- max: lengths === undefined
442
- ? h3RequestSeconds.max
443
- : Duration.toMillis(duration(lengths.max, "filler maximum length")) / 1000,
444
- };
445
- if (fillLengths.min > fillLengths.max)
446
- throw ReactorError.fromCode("InvalidInput", "Filler lengths are inconsistent");
447
- return {
448
- floorSeconds,
449
- targetSeconds,
450
- maxBuildsInFlight,
451
- maxHistory,
452
- unknownRecoveryMs,
453
- fillLengths,
454
- };
455
- }, "makeScheduler");
456
- const captureFiller = (index, runway, durationSeconds, targetSeconds) => Effect.gen(function* () {
457
- const request = yield* captureRequest(options.filler.clip({
458
- index,
459
- runwaySeconds: runway,
460
- durationSeconds,
461
- ...(targetSeconds === undefined ? {} : { targetSeconds }),
462
- }));
463
- if (request.position !== undefined ||
464
- request.before !== undefined ||
465
- request.sameSessionAs !== undefined ||
466
- request.continueFrom !== undefined ||
467
- request.sequence !== undefined)
468
- return yield* PolicyFailure.refuse("InvalidRequest", "Scheduler filler cannot choose placement or source");
469
- return request;
470
- });
471
- let fillerIndex = 0;
472
- let fillerRetryAtMs = 0;
473
- const unknownFillers = new Map();
474
- const maxUnknownFillers = 4096;
475
- let upcomingFiller = yield* captureFiller(0, 0, fillLengths.min);
476
- const actorScope = yield* Effect.scope;
477
- const inbox = yield* Queue.unbounded();
478
- const commandQueue = yield* Queue.unbounded();
479
- const events = yield* PubSub.unbounded();
480
- const cueEvents = yield* PubSub.unbounded();
481
- yield* Effect.addFinalizer(() => Effect.gen(function* () {
482
- yield* Queue.shutdown(commandQueue);
483
- yield* Queue.shutdown(inbox);
484
- yield* PubSub.shutdown(events);
485
- yield* PubSub.shutdown(cueEvents);
486
- }));
487
- const stateRef = yield* SubscriptionRef.make({
488
- accepting: true,
489
- runwaySeconds: 0,
490
- playing: null,
491
- lanes: lanes.map((name) => ({ name, keys: [] })),
492
- sessions: [],
493
- starved: 0,
494
- estimates: { build: undefined, length: 1 },
495
- });
496
- const initialized = yield* Deferred.make();
497
- const stopped = yield* Deferred.make();
498
- const settled = yield* Deferred.make();
499
- const closedCall = Deferred.await(stopped).pipe(Effect.flatMap((cause) => PolicyFailure.refuse("SessionClosed", cause.message)));
500
- const items = new Map();
501
- const history = new Map();
502
- /** Kept while any part is active or in history, for idempotency and withdrawal. */
503
- const groups = new Map();
504
- const owned = new Map();
505
- // An event can overtake its enqueue reply. Keep only the evidence needed to
506
- // decide that clip's fate until the one in-flight command returns its ID.
507
- const earlyClipEvents = new Map();
508
- const removedIds = new Set();
509
- const filler = new Map();
510
- const playingStartedMs = new Map();
511
- let admission = 0;
512
- let accepting = true;
513
- let starved = 0;
514
- let refillActive = false;
515
- let ended;
516
- let drainFailure;
517
- const drainReplies = [];
518
- const pendingWithdrawals = new Map();
519
- /** Edit batches that have not taken effect yet. */
520
- const batches = new Map();
521
- let batchCount = 0;
522
- const pendingBuilds = new Set();
523
- // A Build whose enqueue has started and whose result the worker has not
524
- // yet seen. The provider may still accept it after the scheduler ends.
525
- let dispatching;
526
- const pendingAtDeferrals = new Set();
527
- const pendingItemRemovals = new Set();
528
- const pendingFillerRemovals = new Set();
529
- const removalRetryAtMs = new Map();
530
- const removalUnknownAtSerial = new Map();
531
- let commandCount = 0;
532
- let snapshotAcquiredSerial = 0;
533
- let lastProcessedSnapshotSerial = 0;
534
- let draining = false;
535
- let finishAccepted = false;
536
- let autoplayPaused = false;
537
- let drained = false;
538
- let observationReady = false;
539
- let blockedMove;
540
- let blockedCut;
541
- // Recent measured builds: build seconds per requested second, and actual over requested length.
542
- const buildSamples = [];
543
- const lengthSamples = [];
544
- const maxSamples = 32;
545
- let lastFillerSeconds;
546
- /** Filler builds sent and not yet Ready, by filler index. */
547
- const fillerDispatch = new Map();
548
- const measure = (atMs, requested, actual) => {
549
- if (!(requested > 0))
550
- return;
551
- buildSamples.push((monotonicMillis(clock) - atMs) / 1000 / requested);
552
- lengthSamples.push(actual / requested);
553
- if (buildSamples.length > maxSamples)
554
- buildSamples.shift();
555
- if (lengthSamples.length > maxSamples)
556
- lengthSamples.shift();
557
- };
558
- /** An item's build is measured once, when its clip is first seen Ready. */
559
- const measureItem = (item, actual) => {
560
- if (item.dispatchedAtMs === undefined)
561
- return;
562
- measure(item.dispatchedAtMs, item.request.durationSeconds, actual);
563
- delete item.dispatchedAtMs;
564
- };
565
- /** Fires the cues of a playing item that are due by `untilMs`, in the order they fall. */
566
- const fireCues = (entry, untilMs) => {
567
- if (entry.status._tag !== "Started" || entry.startedAtMonoMs === undefined)
568
- return;
569
- for (const due of dueCues(entry.cues, entry.firedCues, entry.startedAtMonoMs, entry.status.durationSeconds, untilMs)) {
570
- entry.firedCues.add(due);
571
- PubSub.publishUnsafe(cueEvents, {
572
- key: entry.key,
573
- name: entry.cues[due].name,
574
- at: clock.currentTimeMillisUnsafe(),
575
- });
576
- }
577
- };
578
- const emit = (entry, status, terminal = false) => Effect.gen(function* () {
579
- if (ended !== undefined && !terminal)
580
- return;
581
- if (JSON.stringify(entry.status) === JSON.stringify(status))
582
- return;
583
- // A clip that ends fires only the cues that fell within the airtime it had.
584
- if (status._tag === "Ended" && entry.startedAtMonoMs !== undefined)
585
- fireCues(entry, entry.startedAtMonoMs +
586
- 1000 *
587
- (status.termination === "finished" && entry.status._tag === "Started"
588
- ? Math.max(status.airedSeconds, entry.status.durationSeconds)
589
- : status.airedSeconds));
590
- entry.status = status;
591
- entry.phase =
592
- status._tag === "Accepted" ||
593
- status._tag === "Building" ||
594
- status._tag === "Ready" ||
595
- status._tag === "Started"
596
- ? status._tag
597
- : status._tag === "Unknown" && status.terminal !== true
598
- ? "Unknown"
599
- : "Terminal";
600
- const replaced = entry.replaces === undefined ? undefined : items.get(entry.replaces);
601
- if (entry.group !== undefined &&
602
- entry.inserted !== true &&
603
- (status._tag === "Failed" || status._tag === "Dropped") &&
604
- !(status._tag === "Dropped" && status.reason === "replaced") &&
605
- (replaced === undefined || replaced.phase === "Terminal")) {
606
- const group = groups.get(entry.group.key);
607
- if (group !== undefined)
608
- group.brokenAt = Math.min(group.brokenAt ?? entry.group.index, entry.group.index);
609
- }
610
- // Publish in the step that records it (the PubSub is unbounded): a claim
611
- // from another fiber cannot land between the two, so asRun never carries
612
- // a settlement ahead of a recorded status, nor omits one.
613
- PubSub.publishUnsafe(events, {
614
- key: entry.key,
615
- at: clock.currentTimeMillisUnsafe(),
616
- status,
617
- });
618
- if (isFirstDecisive(status))
619
- yield* Deferred.succeed(entry.firstDecisiveWaiter, status);
620
- if (status._tag === "Started")
621
- yield* Deferred.succeed(entry.startedWaiter, status);
622
- if (status._tag === "Ended" ||
623
- status._tag === "Dropped" ||
624
- status._tag === "Failed" ||
625
- status._tag === "Unobserved" ||
626
- (status._tag === "Unknown" && status.terminal === true)) {
627
- yield* Deferred.succeed(entry.startedWaiter, status);
628
- yield* Deferred.succeed(entry.outcomeWaiter, status);
629
- }
630
- });
631
- // The claim is synchronous; interruption masking alone would still allow
632
- // two closers to publish conflicting results. Join only local bookkeeping,
633
- // never the worker scope (a worker can itself own terminal settlement).
634
- const terminate = (terminal) => Effect.suspend(() => {
635
- if (ended !== undefined)
636
- return Deferred.await(settled);
637
- ended = terminal;
638
- accepting = false;
639
- // Wake the watchdog so an episode left armed by another owner cannot
640
- // keep re-arming its timer.
641
- Deferred.doneUnsafe(recoveryChanged, Exit.void);
642
- return Effect.gen(function* () {
643
- for (const item of items.values()) {
644
- if (Exit.isFailure(terminal)) {
645
- yield* Deferred.failCause(item.startedWaiter, terminal.cause);
646
- yield* Deferred.failCause(item.outcomeWaiter, terminal.cause);
647
- yield* Deferred.failCause(item.firstDecisiveWaiter, terminal.cause);
648
- continue;
649
- }
650
- // emit records and publishes a status before completing its waits,
651
- // and a claim can land in between: a completion can resume an
652
- // observer that closes the owner, and another fiber can claim while
653
- // the actor is preempted. Finish those waits from the record.
654
- const recorded = item.status;
655
- if (item.phase === "Started" || item.phase === "Terminal") {
656
- if (isFirstDecisive(recorded))
657
- yield* Deferred.succeed(item.firstDecisiveWaiter, recorded);
658
- yield* Deferred.succeed(item.startedWaiter, recorded);
659
- }
660
- if (item.phase === "Terminal")
661
- yield* Deferred.succeed(item.outcomeWaiter, recorded);
662
- else
663
- yield* emit(item, item.phase === "Unknown" ||
664
- (item.clipId === undefined &&
665
- (item.unknownAtMs !== undefined || item.key === dispatching))
666
- ? { _tag: "Unknown", terminal: true }
667
- : { _tag: "Failed", reason: { _tag: "Scheduler", cause: terminal.value } }, true);
668
- }
669
- const closed = Exit.isSuccess(terminal)
670
- ? Exit.fail(PolicyFailure.refuse("SessionClosed", terminal.value.message))
671
- : Exit.failCause(terminal.cause);
672
- for (const [key, pending] of pendingWithdrawals) {
673
- // A drop recorded before the claim is the withdrawal's actual result.
674
- const reply = Exit.isSuccess(terminal) && items.get(key)?.status._tag === "Dropped"
675
- ? Exit.succeed("withdrawn")
676
- : closed;
677
- for (const waiter of pending.replies)
678
- yield* Deferred.done(waiter, reply);
679
- }
680
- pendingWithdrawals.clear();
681
- for (const batch of batches.values()) {
682
- for (const target of batch.targets.values())
683
- if (target.reply !== undefined)
684
- yield* Deferred.done(target.reply, closed);
685
- yield* Deferred.done(batch.committed, closed);
686
- }
687
- batches.clear();
688
- for (const reply of drainReplies.splice(0))
689
- yield* Deferred.done(reply, closed);
690
- yield* SubscriptionRef.update(stateRef, (state) => ({ ...state, accepting: false }));
691
- yield* Deferred.done(initialized, Exit.isFailure(terminal)
692
- ? Exit.failCause(terminal.cause)
693
- : Exit.fail(ReactorError.fromCode("Closed", terminal.value.message)));
694
- // Supervisors may close the owner immediately when this wakes them.
695
- yield* Deferred.done(stopped, terminal);
696
- }).pipe(Effect.onExit((exit) => Effect.gen(function* () {
697
- // A bookkeeping defect must release joiners with that Cause, never
698
- // leave an unfinished latch or report a partial settlement as success.
699
- yield* Deferred.done(settled, exit);
700
- if (Exit.isFailure(exit)) {
701
- yield* Deferred.failCause(initialized, exit.cause);
702
- yield* Deferred.failCause(stopped, exit.cause);
703
- }
704
- })));
705
- }).pipe(Effect.uninterruptible);
706
- const closeActor = (cause) => terminate(Exit.succeed(cause));
707
- let recoveryState;
708
- let capacityRecovery;
709
- let recoveryDeadline;
710
- let recoveryChanged = Deferred.makeUnsafe();
711
- // Retain the original age while Closing hides the old source and acquisition
712
- // is still pending. Only accepted evidence or a Ready replacement clears it.
713
- const updateRecovery = (state = recoveryState, fresh) => {
714
- if (ended !== undefined)
715
- return;
716
- recoveryState = state;
717
- const preferred = state === undefined ? undefined : Option.getOrUndefined(state.preferredSessionId);
718
- const ready = preferred !== undefined &&
719
- state?.sessions.some((source) => source.sessionId === preferred && source.availability === "Ready") === true;
720
- let earliest;
721
- let drainDeadline;
722
- const consider = (atMs, sessionId, cause, newlyObserved = false) => {
723
- const deadlineMs = atMs + unknownRecoveryMs;
724
- if (((!ready && newlyObserved) || sessionId === undefined || sessionId === preferred) &&
725
- (earliest === undefined || deadlineMs < earliest.deadlineMs))
726
- earliest = { deadlineMs, sessionId, cause };
727
- if (draining && (drainDeadline === undefined || deadlineMs < drainDeadline.deadlineMs))
728
- drainDeadline = { deadlineMs, sessionId, cause };
729
- };
730
- for (const entry of unknownFillers.values())
731
- consider(entry.atMs, entry.sessionId, entry.cause);
732
- for (const item of items.values())
733
- if (item.phase !== "Terminal" &&
734
- item.clipId === undefined &&
735
- item.unknownAtMs !== undefined &&
736
- item.unknownCause !== undefined)
737
- consider(item.unknownAtMs, item.unknownSessionId, item.unknownCause);
738
- if (fresh !== undefined)
739
- consider(fresh.atMs, fresh.sessionId, fresh.cause, true);
740
- if (ready &&
741
- (earliest === undefined ||
742
- (capacityRecovery?.sessionId !== undefined && capacityRecovery.sessionId !== preferred)))
743
- capacityRecovery = undefined;
744
- if (earliest !== undefined &&
745
- (capacityRecovery === undefined || earliest.deadlineMs < capacityRecovery.deadlineMs))
746
- capacityRecovery = earliest;
747
- const next = drainDeadline !== undefined &&
748
- (capacityRecovery === undefined || drainDeadline.deadlineMs < capacityRecovery.deadlineMs)
749
- ? drainDeadline
750
- : capacityRecovery;
751
- if (next?.deadlineMs !== recoveryDeadline?.deadlineMs ||
752
- next?.cause !== recoveryDeadline?.cause) {
753
- recoveryDeadline = next;
754
- Deferred.doneUnsafe(recoveryChanged, Exit.void);
755
- }
756
- };
757
- const provedUnknown = (atMs) => {
758
- if (atMs !== undefined && capacityRecovery?.deadlineMs === atMs + unknownRecoveryMs)
759
- capacityRecovery = undefined;
760
- };
761
- const watchdog = Effect.gen(function* () {
762
- for (;;) {
763
- // A committed result and its queued actor evidence can arrive in one
764
- // turn. Coalesce those revisions before allocating a replacement timer;
765
- // the original monotonic deadline remains unchanged.
766
- yield* Effect.yieldNow;
767
- if (ended !== undefined)
768
- return;
769
- recoveryChanged = Deferred.makeUnsafe();
770
- const episode = recoveryDeadline;
771
- if (episode === undefined) {
772
- yield* Deferred.await(recoveryChanged);
773
- continue;
774
- }
775
- yield* Deferred.await(recoveryChanged).pipe(Effect.timeoutOrElse({
776
- duration: Math.max(0, episode.deadlineMs - monotonicMillis(clock)),
777
- orElse: () => Effect.void,
778
- }));
779
- // A canceled timer may already have resumed. Recheck the retained
780
- // identity without awaiting the actor, Engine state, or its permits.
781
- if (ended === undefined &&
782
- recoveryDeadline === episode &&
783
- monotonicMillis(clock) >= episode.deadlineMs) {
784
- const causes = new Set([episode.cause]);
785
- for (const item of items.values())
786
- if (item.unknownCause !== undefined)
787
- causes.add(item.unknownCause);
788
- for (const entry of unknownFillers.values())
789
- causes.add(entry.cause);
790
- yield* closeActor(ReactorError.fromCode("Timeout", "Scheduler unknown recovery deadline elapsed", {
791
- operation: "scheduler.unknownRecovery",
792
- detail: [...causes],
793
- }));
794
- return;
795
- }
796
- }
797
- });
798
- const prune = (state) => {
799
- const active = new Set(activeIds(state));
800
- if (state.availability === "Ready") {
801
- for (const id of removedIds)
802
- if (!active.has(id))
803
- removedIds.delete(id);
804
- for (const id of removalRetryAtMs.keys())
805
- if (!active.has(id))
806
- removalRetryAtMs.delete(id);
807
- for (const id of removalUnknownAtSerial.keys())
808
- if (!active.has(id))
809
- removalUnknownAtSerial.delete(id);
810
- }
811
- for (const [key, item] of items) {
812
- if (item.phase !== "Terminal" ||
813
- pendingBuilds.has(key) ||
814
- pendingWithdrawals.has(key) ||
815
- pendingAtDeferrals.has(key) ||
816
- pendingItemRemovals.has(key) ||
817
- [...batches.values()].some((batch) => batch.targets.has(key)) ||
818
- // A replacement dropped before it was built still links its own replacement to
819
- // the item in the place.
820
- [...items.values()].some((other) => other.replaces === key && other.phase !== "Terminal") ||
821
- (item.clipId !== undefined && active.has(item.clipId)))
822
- continue;
823
- if (item.clipId !== undefined) {
824
- owned.delete(item.clipId);
825
- playingStartedMs.delete(item.clipId);
826
- }
827
- history.set(key, { fingerprint: item.fingerprint, handle: item.handle });
828
- items.delete(key);
829
- }
830
- while (history.size > maxHistory)
831
- history.delete(history.keys().next().value);
832
- for (const [key, group] of groups)
833
- if (!group.parts.some((part) => items.has(part) || history.has(part)))
834
- groups.delete(key);
835
- };
836
- // Capture membership and public rows in one pass. A fresh token reads every
837
- // source on every observation, including in-place changes to Engine arrays;
838
- // frozen empty rows can be shared by every published state without treating
839
- // array identity as proof.
840
- const noReady = Object.freeze([]);
841
- const sourceRows = new Map();
842
- const projectSources = (state) => {
843
- const seen = Symbol();
844
- const sessions = state.sessions.map(({ sessionId }) => {
845
- let cached = sourceRows.get(sessionId);
846
- if (cached === undefined) {
847
- cached = { empty: Object.freeze({ sessionId, ready: noReady }), seen };
848
- sourceRows.set(sessionId, cached);
849
- }
850
- else
851
- cached.seen = seen;
852
- if (state.ready.length === 0)
853
- return cached.empty;
854
- return {
855
- sessionId,
856
- ready: state.ready
857
- .filter((clip) => clip.sessionId === sessionId)
858
- .map((clip) => {
859
- const owner = owned.get(clip.clipId);
860
- return owner === undefined ? "other" : owner._tag === "Filler" ? "filler" : owner.key;
861
- }),
862
- };
863
- });
864
- // Bound cached rows by snapshot size without scanning unchanged overlap.
865
- // Membership still requires this observation's token, even before pruning.
866
- if (sourceRows.size > state.sessions.length)
867
- for (const [id, cached] of sourceRows)
868
- if (cached.seen !== seen)
869
- sourceRows.delete(id);
870
- // Only the serialized actor uses this membership view, before its next projection.
871
- return { sessions, has: (id) => sourceRows.get(id)?.seen === seen };
872
- };
873
- const publishState = (state, sessions = projectSources(state).sessions) => {
874
- const playing = Option.getOrUndefined(state.playing);
875
- const playingOwner = playing === undefined ? undefined : owned.get(playing.clipId);
876
- const playingKey = playing === undefined
877
- ? null
878
- : playingOwner === undefined
879
- ? "other"
880
- : playingOwner._tag === "Filler"
881
- ? "filler"
882
- : playingOwner.key;
883
- return SubscriptionRef.set(stateRef, {
884
- accepting,
885
- runwaySeconds: runwaySeconds(state, monotonicMillis(clock), playingStartedMs, owned, [
886
- ...items.values(),
887
- ]),
888
- playing: playingKey,
889
- lanes: lanes.map((name) => ({
890
- name,
891
- keys: [...items.values()]
892
- .filter((item) => item.lane === name && item.phase !== "Terminal")
893
- .map((item) => item.key),
894
- })),
895
- sessions,
896
- starved,
897
- estimates: estimatesFrom(buildSamples, lengthSamples),
898
- });
899
- };
900
- /** When the filler build in flight was sent, and the next filler clip's requested length. */
901
- const fillerView = () => ({
902
- fillerDispatchedAtMs: fillerDispatch.size === 0
903
- ? undefined
904
- : Math.min(...[...fillerDispatch.values()].map((sent) => sent.atMs)),
905
- fillerSeconds: upcomingFiller?.durationSeconds ?? lastFillerSeconds,
906
- });
907
- const activeRecords = (state) => [
908
- ...state.queued,
909
- ...state.ready,
910
- ...Option.match(state.building, {
911
- onNone: () => [],
912
- onSome: (build) => [build.record],
913
- }),
914
- ...Option.match(state.playing, {
915
- onNone: () => [],
916
- onSome: (playing) => Option.match(playing.record, {
917
- onNone: () => [],
918
- onSome: (record) => [record],
919
- }),
920
- }),
921
- ];
922
- const knownRecord = (state, clipId) => activeRecords(state).find((record) => record.clipId === clipId);
923
- const refreshAnchors = () => {
924
- const nowMs = monotonicMillis(clock);
925
- const wallMs = clock.currentTimeMillisUnsafe();
926
- for (const item of items.values())
927
- if (item.atWallMs !== undefined)
928
- item.atMs = nowMs + item.atWallMs - wallMs;
929
- };
930
- const observed = (state, recoverMissing = false) => Effect.gen(function* () {
931
- const active = new Set(activeIds(state));
932
- if (recoverMissing) {
933
- for (const [clipId, owner] of owned) {
934
- if (owner._tag !== "Filler" || active.has(clipId))
935
- continue;
936
- const tracked = filler.get(owner.index);
937
- if (tracked?.clipId === clipId &&
938
- tracked?.acknowledgedAtSnapshotSerial !== undefined &&
939
- lastProcessedSnapshotSerial <= tracked.acknowledgedAtSnapshotSerial)
940
- continue;
941
- const sessionId = owner.sessionId;
942
- const source = state.sessions.find((entry) => entry.sessionId === sessionId);
943
- if (sessionId === undefined
944
- ? state.availability !== "Ready"
945
- : source !== undefined && source.availability !== "Ready")
946
- continue;
947
- owned.delete(clipId);
948
- if (tracked?.clipId === clipId)
949
- filler.delete(owner.index);
950
- playingStartedMs.delete(clipId);
951
- pendingFillerRemovals.delete(clipId);
952
- }
953
- }
954
- const keyed = new Map();
955
- for (const record of activeRecords(state)) {
956
- const key = keyFromProviderMetadata(record.provider.metadata);
957
- if (key !== undefined && !removedIds.has(record.clipId))
958
- keyed.set(key, record);
959
- const index = fillerIndexFromKey(key);
960
- if (index === undefined || removedIds.has(record.clipId))
961
- continue;
962
- const existing = owned.get(record.clipId);
963
- if (existing?._tag === "Filler") {
964
- if (existing.sessionId !== record.sessionId)
965
- owned.set(record.clipId, { ...existing, sessionId: record.sessionId });
966
- const tracked = filler.get(index);
967
- if (tracked?.clipId === record.clipId)
968
- tracked.sessionId = record.sessionId;
969
- continue;
970
- }
971
- if (existing !== undefined)
972
- continue;
973
- owned.set(record.clipId, { _tag: "Filler", index, sessionId: record.sessionId });
974
- filler.set(index, { index, clipId: record.clipId, sessionId: record.sessionId });
975
- const priorIndex = fillerIndex;
976
- fillerIndex = Math.max(fillerIndex, index + 1);
977
- provedUnknown(unknownFillers.get(index)?.atMs);
978
- unknownFillers.delete(index);
979
- if (fillerIndex !== priorIndex)
980
- upcomingFiller = undefined;
981
- }
982
- for (const item of items.values()) {
983
- if (item.phase === "Terminal")
984
- continue;
985
- if (item.clipId === undefined) {
986
- const resumed = keyed.get(item.key);
987
- if (resumed !== undefined) {
988
- item.clipId = resumed.clipId;
989
- item.sessionId = resumed.sessionId;
990
- provedUnknown(item.unknownAtMs);
991
- item.unknownAtMs = undefined;
992
- item.unknownCause = undefined;
993
- item.unknownSessionId = undefined;
994
- owned.set(resumed.clipId, { _tag: "Item", key: item.key });
995
- }
996
- }
997
- const clipId = item.clipId;
998
- if (clipId === undefined)
999
- continue;
1000
- const playing = Option.getOrUndefined(state.playing);
1001
- if (playing?.clipId === clipId) {
1002
- const record = Option.getOrUndefined(playing.record);
1003
- const at = Option.getOrUndefined(playing.startedAt);
1004
- const startedAtMonotonicMillis = playing.startedAtMonotonicMillis;
1005
- if (record !== undefined &&
1006
- at !== undefined &&
1007
- startedAtMonotonicMillis !== undefined &&
1008
- item.phase !== "Started") {
1009
- item.sessionId = record.sessionId;
1010
- item.startedAtEpochMs = at;
1011
- item.startedAtMonoMs = startedAtMonotonicMillis;
1012
- playingStartedMs.set(clipId, startedAtMonotonicMillis);
1013
- yield* emit(item, {
1014
- _tag: "Started",
1015
- at,
1016
- sessionId: record.sessionId,
1017
- durationSeconds: record.durationSeconds,
1018
- ...((item.atMs ?? item.startByMs) === undefined ||
1019
- startedAtMonotonicMillis <= (item.atMs ?? item.startByMs)
1020
- ? {}
1021
- : { lateByMillis: startedAtMonotonicMillis - (item.atMs ?? item.startByMs) }),
1022
- });
1023
- }
1024
- else if (recoverMissing &&
1025
- (record === undefined || at === undefined) &&
1026
- item.phase !== "Started")
1027
- yield* emit(item, { _tag: "Unobserved" });
1028
- }
1029
- else if (state.ready.some((record) => record.clipId === clipId)) {
1030
- const record = knownRecord(state, clipId);
1031
- measureItem(item, record.durationSeconds);
1032
- item.sessionId = record.sessionId;
1033
- if (item.phase !== "Started")
1034
- yield* emit(item, { _tag: "Ready", sessionId: record.sessionId });
1035
- }
1036
- else if (state.queued.some((record) => record.clipId === clipId) ||
1037
- state.generationOrder.includes(clipId) ||
1038
- Option.getOrUndefined(state.building)?.record.clipId === clipId) {
1039
- item.sessionId = knownRecord(state, clipId)?.sessionId ?? item.sessionId;
1040
- if (item.phase === "Accepted" || item.phase === "Unknown")
1041
- yield* emit(item, { _tag: "Building" });
1042
- }
1043
- else if (state.failed.includes(clipId))
1044
- yield* emit(item, {
1045
- _tag: "Failed",
1046
- reason: { _tag: "Clip", message: "The clip failed unobserved" },
1047
- });
1048
- else if (recoverMissing &&
1049
- item.sessionId !== undefined &&
1050
- (state.sessions.find((source) => source.sessionId === item.sessionId)?.availability ??
1051
- "Ready") === "Ready" &&
1052
- !active.has(clipId) &&
1053
- (item.acknowledgedAtSnapshotSerial === undefined ||
1054
- lastProcessedSnapshotSerial > item.acknowledgedAtSnapshotSerial))
1055
- yield* emit(item, { _tag: "Unobserved" });
1056
- }
1057
- updateRecovery(state);
1058
- });
1059
- const onEvent = (event) => Effect.gen(function* () {
1060
- if (event._tag === "Starved")
1061
- starved++;
1062
- if (event._tag === "SessionFailed") {
1063
- yield* closeActor(event.failure);
1064
- return;
1065
- }
1066
- if (!("clipId" in event))
1067
- return;
1068
- if (commandCount > 0 &&
1069
- (event._tag === "Started" || event._tag === "Ended" || event._tag === "Failed")) {
1070
- const prior = earlyClipEvents.get(event.clipId);
1071
- if (prior === undefined) {
1072
- if (earlyClipEvents.size >= maxEarlyClipIds) {
1073
- // A lost pre-ack start cannot be recast as a definite failure.
1074
- for (const key of pendingBuilds) {
1075
- const item = items.get(key);
1076
- if (item !== undefined && item.phase !== "Terminal" && item.phase !== "Started") {
1077
- yield* emit(item, { _tag: "Unknown", terminal: true });
1078
- item.phase = "Terminal";
1079
- }
1080
- }
1081
- yield* closeActor(ReactorError.fromCode("Overflow", "Unattributed clip evidence exceeded its bound"));
1082
- return;
1083
- }
1084
- earlyClipEvents.set(event.clipId, [event]);
1085
- }
1086
- else if (!prior.some((seen) => event._tag === "Started"
1087
- ? seen._tag === "Started"
1088
- : seen._tag === "Ended" || seen._tag === "Failed"))
1089
- prior.push(event);
1090
- }
1091
- const owner = owned.get(event.clipId);
1092
- if (owner === undefined)
1093
- return;
1094
- if (owner._tag === "Filler") {
1095
- const sent = fillerDispatch.get(owner.index);
1096
- if (event._tag === "Ready" && sent !== undefined)
1097
- measure(sent.atMs, sent.seconds, event.durationSeconds);
1098
- if (event._tag === "Ready" || event._tag === "Ended" || event._tag === "Failed")
1099
- fillerDispatch.delete(owner.index);
1100
- if (event._tag === "Started")
1101
- playingStartedMs.set(event.clipId, event.atMonotonicMillis ?? monotonicMillis(clock));
1102
- if (event._tag === "Ended" || event._tag === "Failed") {
1103
- owned.delete(event.clipId);
1104
- if (filler.get(owner.index)?.clipId === event.clipId)
1105
- filler.delete(owner.index);
1106
- playingStartedMs.delete(event.clipId);
1107
- }
1108
- return;
1109
- }
1110
- const item = items.get(owner.key);
1111
- if (item === undefined || item.phase === "Terminal")
1112
- return;
1113
- switch (event._tag) {
1114
- case "Queued":
1115
- case "Building":
1116
- if (item.phase === "Accepted" || item.phase === "Unknown")
1117
- yield* emit(item, { _tag: "Building" });
1118
- break;
1119
- case "Ready": {
1120
- measureItem(item, event.durationSeconds);
1121
- const record = knownRecord(yield* engine.state, event.clipId);
1122
- if (record !== undefined) {
1123
- item.sessionId = record.sessionId;
1124
- if (item.phase !== "Started")
1125
- yield* emit(item, { _tag: "Ready", sessionId: record.sessionId });
1126
- }
1127
- break;
1128
- }
1129
- case "Started": {
1130
- if (item.phase === "Started")
1131
- break;
1132
- const state = yield* engine.state;
1133
- const record = knownRecord(state, event.clipId);
1134
- const sessionId = record?.sessionId ?? item.sessionId;
1135
- if (sessionId === undefined)
1136
- yield* emit(item, { _tag: "Unobserved" });
1137
- else {
1138
- item.sessionId = sessionId;
1139
- const playing = Option.getOrUndefined(state.playing);
1140
- item.startedAtMonoMs =
1141
- event.atMonotonicMillis ??
1142
- (playing?.clipId === event.clipId
1143
- ? (playing.startedAtMonotonicMillis ?? monotonicMillis(clock))
1144
- : monotonicMillis(clock));
1145
- item.startedAtEpochMs = event.at;
1146
- playingStartedMs.set(event.clipId, item.startedAtMonoMs);
1147
- yield* emit(item, {
1148
- _tag: "Started",
1149
- at: event.at,
1150
- sessionId,
1151
- durationSeconds: event.durationSeconds,
1152
- ...((item.atMs ?? item.startByMs) === undefined ||
1153
- item.startedAtMonoMs <= (item.atMs ?? item.startByMs)
1154
- ? {}
1155
- : { lateByMillis: item.startedAtMonoMs - (item.atMs ?? item.startByMs) }),
1156
- });
1157
- }
1158
- break;
1159
- }
1160
- case "Ended":
1161
- yield* emit(item, item.startedAtMonoMs === undefined
1162
- ? { _tag: "Unobserved" }
1163
- : {
1164
- _tag: "Ended",
1165
- at: event.at ?? clock.currentTimeMillisUnsafe(),
1166
- termination: event.termination,
1167
- airedSeconds: Math.max(0, ((event.atMonotonicMillis ?? monotonicMillis(clock)) - item.startedAtMonoMs) /
1168
- 1000),
1169
- });
1170
- playingStartedMs.delete(event.clipId);
1171
- break;
1172
- case "Failed": {
1173
- // A clip lost with a replaced session before it aired is rebuilt from the plan.
1174
- if (event.lost === true &&
1175
- (item.phase === "Accepted" || item.phase === "Building" || item.phase === "Ready")) {
1176
- const sessionId = event.sessionId ?? item.sessionId;
1177
- // Its session may still list the clip until it closes; it is not adopted again.
1178
- removedIds.add(event.clipId);
1179
- owned.delete(event.clipId);
1180
- playingStartedMs.delete(event.clipId);
1181
- delete item.clipId;
1182
- delete item.dispatchedAtMs;
1183
- delete item.acknowledgedAtSnapshotSerial;
1184
- item.sessionId = undefined;
1185
- const pending = pendingWithdrawals.get(item.key);
1186
- if (pending !== undefined) {
1187
- yield* emit(item, { _tag: "Dropped", reason: pending.reason });
1188
- yield* finishWithdrawal(item.key, Result.succeed("withdrawn"));
1189
- }
1190
- else
1191
- yield* emit(item, sessionId === undefined
1192
- ? { _tag: "Accepted" }
1193
- : { _tag: "Accepted", carried: { sessionId } });
1194
- break;
1195
- }
1196
- yield* emit(item, { _tag: "Failed", reason: { _tag: "Clip", message: event.reason } });
1197
- break;
1198
- }
1199
- default:
1200
- break;
1201
- }
1202
- });
1203
- const replayEarlyClipEvents = (clipId) => Effect.gen(function* () {
1204
- const saved = earlyClipEvents.get(clipId);
1205
- earlyClipEvents.delete(clipId);
1206
- if (saved === undefined)
1207
- return;
1208
- for (const event of saved)
1209
- yield* onEvent(event);
1210
- });
1211
- const executeCommand = (command) => {
1212
- switch (command._tag) {
1213
- case "Build":
1214
- case "BuildFiller":
1215
- return Effect.gen(function* () {
1216
- if (command.sessionId === undefined ||
1217
- Option.getOrUndefined((yield* engine.state).preferredSessionId) !== command.sessionId)
1218
- return yield* PolicyFailure.refuse("RouteChanged", "The preferred source changed before dispatch");
1219
- // Termination can be claimed while the route is read; its settlement
1220
- // must not be followed by a new dispatch.
1221
- if (ended !== undefined)
1222
- return yield* PolicyFailure.refuse("SessionClosed", "The scheduler closed");
1223
- if (command._tag === "Build")
1224
- dispatching = command.key;
1225
- return yield* engine.enqueueOnSource === undefined
1226
- ? engine.enqueue(command.request)
1227
- : engine.enqueueOnSource(command.request, command.sessionId);
1228
- });
1229
- case "RemoveItem":
1230
- case "RemoveFiller":
1231
- case "RemoveDuplicate":
1232
- case "DeferAt":
1233
- return engine.remove(command.clipId);
1234
- case "Order":
1235
- return engine.move(command.clipId, command.position, "playout");
1236
- case "PauseAutoplay":
1237
- return engine.setAutoplay(false);
1238
- case "Cut":
1239
- return engine.cut(command.clipId);
1240
- }
1241
- };
1242
- const commandWorker = Effect.gen(function* () {
1243
- for (;;) {
1244
- const command = yield* takeQueue(commandQueue);
1245
- if (ended !== undefined)
1246
- continue;
1247
- const result = yield* Effect.result(executeCommand(command));
1248
- dispatching = undefined;
1249
- // Capture uncertainty before actor delivery: drain may be awaiting a
1250
- // permit in stopRenewal while this committed command finishes.
1251
- if (ended === undefined &&
1252
- Result.isFailure(result) &&
1253
- result.failure.context.outcome === "unknown") {
1254
- let fresh;
1255
- const sessionId = engine.enqueueOnSource === undefined ||
1256
- (command._tag !== "Build" && command._tag !== "BuildFiller")
1257
- ? undefined
1258
- : command.sessionId;
1259
- if (command._tag === "Build") {
1260
- const item = items.get(command.key);
1261
- if (item !== undefined && item.phase !== "Terminal" && item.clipId === undefined) {
1262
- item.unknownAtMs ??= monotonicMillis(clock);
1263
- item.unknownCause = result.failure;
1264
- item.unknownSessionId = sessionId;
1265
- fresh = { sessionId, atMs: item.unknownAtMs, cause: result.failure };
1266
- }
1267
- }
1268
- else if (command._tag === "BuildFiller" &&
1269
- !filler.has(command.index) &&
1270
- !unknownFillers.has(command.index)) {
1271
- fresh = { sessionId, atMs: monotonicMillis(clock), cause: result.failure };
1272
- unknownFillers.set(command.index, { ...fresh, index: command.index });
1273
- }
1274
- updateRecovery(recoveryState, fresh);
1275
- }
1276
- yield* Queue.offer(inbox, { _tag: "CommandDone", command, result });
1277
- }
1278
- });
1279
- const sendCommand = (command) => Effect.gen(function* () {
1280
- if (ended !== undefined)
1281
- return;
1282
- if (command._tag === "Build") {
1283
- pendingBuilds.add(command.key);
1284
- const item = items.get(command.key);
1285
- if (item !== undefined)
1286
- item.dispatchedAtMs = monotonicMillis(clock);
1287
- }
1288
- if (command._tag === "BuildFiller") {
1289
- lastFillerSeconds = command.request.durationSeconds;
1290
- fillerDispatch.set(command.index, {
1291
- atMs: monotonicMillis(clock),
1292
- seconds: command.request.durationSeconds,
1293
- });
1294
- // In-flight filler is bounded like the samples it feeds.
1295
- for (const index of fillerDispatch.keys())
1296
- if (fillerDispatch.size > maxSamples)
1297
- fillerDispatch.delete(index);
1298
- }
1299
- if (command._tag === "DeferAt")
1300
- pendingAtDeferrals.add(command.key);
1301
- commandCount++;
1302
- yield* Queue.offer(commandQueue, command);
1303
- });
1304
- const canRetryRemoval = (clipId) => monotonicMillis(clock) >= (removalRetryAtMs.get(clipId) ?? 0) &&
1305
- (removalUnknownAtSerial.get(clipId) === undefined ||
1306
- lastProcessedSnapshotSerial > removalUnknownAtSerial.get(clipId));
1307
- const finishWithdrawal = (key, result) => Effect.gen(function* () {
1308
- const pending = pendingWithdrawals.get(key);
1309
- pendingWithdrawals.delete(key);
1310
- if (pending === undefined)
1311
- return;
1312
- for (const reply of pending.replies)
1313
- if (Result.isSuccess(result))
1314
- yield* Deferred.succeed(reply, result.success);
1315
- else
1316
- yield* Deferred.fail(reply, result.failure);
1317
- });
1318
- const sendItemRemoval = (item, reason) => Effect.gen(function* () {
1319
- if (item.clipId === undefined ||
1320
- pendingItemRemovals.has(item.key) ||
1321
- !canRetryRemoval(item.clipId))
1322
- return;
1323
- pendingItemRemovals.add(item.key);
1324
- yield* sendCommand({ _tag: "RemoveItem", key: item.key, clipId: item.clipId, reason });
1325
- });
1326
- /** A withdrawal's answer for an item that has started or settled another way. */
1327
- const settledOutcome = (item) => item.phase === "Started" || item.status._tag === "Ended" || item.status._tag === "Unobserved"
1328
- ? "already-started"
1329
- : item.status._tag === "Dropped"
1330
- ? "withdrawn"
1331
- : "not-found";
1332
- const requestWithdrawal = (item, reason, reply) => Effect.gen(function* () {
1333
- if (item.phase === "Started" || item.phase === "Terminal") {
1334
- if (reply !== undefined)
1335
- yield* Deferred.succeed(reply, settledOutcome(item));
1336
- return;
1337
- }
1338
- const pending = pendingWithdrawals.get(item.key);
1339
- if (pending !== undefined) {
1340
- if (reply !== undefined)
1341
- pending.replies.push(reply);
1342
- return;
1343
- }
1344
- pendingWithdrawals.set(item.key, { reason, replies: reply === undefined ? [] : [reply] });
1345
- if (pendingAtDeferrals.has(item.key))
1346
- return;
1347
- if (item.clipId === undefined) {
1348
- if (item.phase === "Unknown" || pendingBuilds.has(item.key))
1349
- return;
1350
- yield* emit(item, { _tag: "Dropped", reason });
1351
- yield* finishWithdrawal(item.key, Result.succeed("withdrawn"));
1352
- return;
1353
- }
1354
- yield* sendItemRemoval(item, reason);
1355
- });
1356
- const applyPolicy = (action, state) => Effect.gen(function* () {
1357
- switch (action._tag) {
1358
- case "DeferAt":
1359
- if (canRetryRemoval(action.clipId))
1360
- yield* sendCommand({ _tag: "DeferAt", key: action.key, clipId: action.clipId });
1361
- break;
1362
- case "Cut":
1363
- yield* sendCommand({ _tag: "Cut", clipId: action.clipId });
1364
- break;
1365
- case "Order": {
1366
- const signature = state.ready.map((record) => record.clipId).join(",") +
1367
- ":" +
1368
- action.clipId +
1369
- ":" +
1370
- action.position;
1371
- if (blockedMove !== signature)
1372
- yield* sendCommand({
1373
- _tag: "Order",
1374
- clipId: action.clipId,
1375
- position: action.position,
1376
- signature,
1377
- });
1378
- break;
1379
- }
1380
- case "Build": {
1381
- const item = items.get(action.key);
1382
- if (item?.phase !== "Accepted")
1383
- break;
1384
- const prepared = yield* Effect.result(keyedRequest(item.request, item.key, action.continueFrom));
1385
- if (Result.isFailure(prepared)) {
1386
- yield* emit(item, {
1387
- _tag: "Failed",
1388
- reason: { _tag: "Command", cause: prepared.failure },
1389
- });
1390
- break;
1391
- }
1392
- yield* sendCommand({
1393
- _tag: "Build",
1394
- key: item.key,
1395
- request: prepared.success,
1396
- sessionId: Option.getOrUndefined(state.preferredSessionId),
1397
- });
1398
- break;
1399
- }
1400
- case "BuildFiller": {
1401
- if (unknownFillers.size >= maxUnknownFillers) {
1402
- yield* closeActor(ReactorError.fromCode("Overflow", "Scheduler uncertain filler ledger is full", {
1403
- operation: "scheduler.unknownRecovery",
1404
- }));
1405
- break;
1406
- }
1407
- const captured = upcomingFiller === undefined
1408
- ? yield* Effect.result(captureFiller(fillerIndex, runwaySeconds(state, monotonicMillis(clock), playingStartedMs, owned, [
1409
- ...items.values(),
1410
- ]), action.durationSeconds, action.targetSeconds))
1411
- : Result.succeed(upcomingFiller);
1412
- if (Result.isFailure(captured)) {
1413
- yield* closeActor(captured.failure);
1414
- break;
1415
- }
1416
- upcomingFiller = captured.success;
1417
- const prepared = yield* Effect.result(keyedRequest(captured.success, fillerKey(fillerIndex)));
1418
- if (Result.isFailure(prepared)) {
1419
- yield* closeActor(prepared.failure);
1420
- break;
1421
- }
1422
- yield* sendCommand({
1423
- _tag: "BuildFiller",
1424
- index: fillerIndex,
1425
- request: prepared.success,
1426
- sessionId: Option.getOrUndefined(state.preferredSessionId),
1427
- });
1428
- break;
1429
- }
1430
- }
1431
- });
1432
- const resolveRetiredUnknown = (item) => Effect.gen(function* () {
1433
- if (item.phase !== "Unknown" || item.clipId !== undefined)
1434
- return;
1435
- item.unknownSessionId = undefined;
1436
- // A retired source cannot play this key again, but it may have played
1437
- // during a missed observation. Preserve Unknown instead of replaying it.
1438
- yield* emit(item, { _tag: "Unknown", terminal: true });
1439
- item.phase = "Terminal";
1440
- const pending = pendingWithdrawals.get(item.key);
1441
- if (pending !== undefined)
1442
- yield* finishWithdrawal(item.key, Result.fail(item.unknownCause ??
1443
- PolicyFailure.refuse("SessionRecovering", "The original source retired without an enqueue result")));
1444
- });
1445
- const handleCommandDone = (command, result) => Effect.gen(function* () {
1446
- commandCount--;
1447
- if (ended !== undefined) {
1448
- if (command._tag === "Build")
1449
- pendingBuilds.delete(command.key);
1450
- if (command._tag === "DeferAt")
1451
- pendingAtDeferrals.delete(command.key);
1452
- if (command._tag === "RemoveItem")
1453
- pendingItemRemovals.delete(command.key);
1454
- if (command._tag === "RemoveFiller")
1455
- pendingFillerRemovals.delete(command.clipId);
1456
- return;
1457
- }
1458
- switch (command._tag) {
1459
- case "Build": {
1460
- const item = items.get(command.key);
1461
- pendingBuilds.delete(command.key);
1462
- if (item === undefined)
1463
- break;
1464
- if (Result.isSuccess(result)) {
1465
- const clipId = result.success;
1466
- if (item.clipId !== undefined && item.clipId !== clipId) {
1467
- yield* sendCommand({ _tag: "RemoveDuplicate", clipId });
1468
- break;
1469
- }
1470
- item.clipId = clipId;
1471
- item.acknowledgedAtSnapshotSerial = snapshotAcquiredSerial;
1472
- owned.set(clipId, { _tag: "Item", key: item.key });
1473
- item.sessionId =
1474
- knownRecord(yield* engine.state, clipId)?.sessionId ??
1475
- (engine.enqueueOnSource === undefined ? undefined : command.sessionId);
1476
- provedUnknown(item.unknownAtMs);
1477
- item.unknownAtMs = undefined;
1478
- item.unknownSessionId = undefined;
1479
- item.unknownCause = undefined;
1480
- yield* replayEarlyClipEvents(clipId);
1481
- // Evidence that overtook the reply may already have carried the clip away
1482
- // with a lost session; the item then waits to be rebuilt, not Building.
1483
- const carried = item.clipId === undefined;
1484
- const pending = pendingWithdrawals.get(item.key);
1485
- if (carried) {
1486
- // The carry settled any withdrawal that was waiting.
1487
- }
1488
- else if (pending !== undefined) {
1489
- if (item.phase === "Started" || item.phase === "Terminal")
1490
- yield* finishWithdrawal(item.key, Result.succeed(settledOutcome(item)));
1491
- else
1492
- yield* sendItemRemoval(item, pending.reason);
1493
- }
1494
- else if (item.phase === "Accepted" || item.phase === "Unknown")
1495
- yield* emit(item, { _tag: "Building" });
1496
- yield* observed(yield* engine.state);
1497
- }
1498
- else if (result.failure.context.outcome === "unknown") {
1499
- // Its time to Ready spans the uncertainty, so it is never measured.
1500
- delete item.dispatchedAtMs;
1501
- if (item.clipId === undefined) {
1502
- // A generic engine may re-route between the state read and dispatch.
1503
- // Only an owner-fenced enqueue can attribute its unknown result.
1504
- item.unknownSessionId =
1505
- engine.enqueueOnSource === undefined ? undefined : command.sessionId;
1506
- item.unknownCause = result.failure;
1507
- yield* emit(item, { _tag: "Unknown" });
1508
- }
1509
- }
1510
- else {
1511
- const pending = pendingWithdrawals.get(item.key);
1512
- if (pending !== undefined) {
1513
- yield* emit(item, { _tag: "Dropped", reason: pending.reason });
1514
- yield* finishWithdrawal(item.key, Result.succeed("withdrawn"));
1515
- }
1516
- else if (result.failure.context.outcome !== "not-submitted" ||
1517
- !["QueueFull", "SessionRecovering", "RouteChanged"].includes(result.failure.reason._tag))
1518
- yield* emit(item, {
1519
- _tag: "Failed",
1520
- reason: { _tag: "Command", cause: result.failure },
1521
- });
1522
- else
1523
- item.retryAtMs = monotonicMillis(clock) + 1_000;
1524
- delete item.dispatchedAtMs;
1525
- }
1526
- break;
1527
- }
1528
- case "BuildFiller": {
1529
- if (Result.isSuccess(result)) {
1530
- const clipId = result.success;
1531
- const actualSessionId = knownRecord(yield* engine.state, clipId)?.sessionId ??
1532
- (engine.enqueueOnSource === undefined ? undefined : command.sessionId);
1533
- owned.set(clipId, {
1534
- _tag: "Filler",
1535
- index: command.index,
1536
- sessionId: actualSessionId,
1537
- });
1538
- filler.set(command.index, {
1539
- index: command.index,
1540
- clipId,
1541
- sessionId: actualSessionId,
1542
- acknowledgedAtSnapshotSerial: snapshotAcquiredSerial,
1543
- });
1544
- fillerIndex = Math.max(fillerIndex, command.index + 1);
1545
- upcomingFiller = undefined;
1546
- yield* replayEarlyClipEvents(clipId);
1547
- }
1548
- else if (result.failure.context.outcome === "unknown") {
1549
- // The worker already retained this identity and its original age.
1550
- }
1551
- else {
1552
- fillerDispatch.delete(command.index);
1553
- fillerRetryAtMs = monotonicMillis(clock) + 1_000;
1554
- if (result.failure.context.outcome === "replied") {
1555
- fillerIndex = Math.max(fillerIndex, command.index + 1);
1556
- upcomingFiller = undefined;
1557
- }
1558
- }
1559
- yield* observed(yield* engine.state);
1560
- break;
1561
- }
1562
- case "RemoveItem": {
1563
- pendingItemRemovals.delete(command.key);
1564
- const item = items.get(command.key);
1565
- if (Result.isSuccess(result)) {
1566
- removalRetryAtMs.delete(command.clipId);
1567
- removalUnknownAtSerial.delete(command.clipId);
1568
- removedIds.add(command.clipId);
1569
- owned.delete(command.clipId);
1570
- playingStartedMs.delete(command.clipId);
1571
- if (item?.phase === "Started")
1572
- yield* finishWithdrawal(command.key, Result.succeed("already-started"));
1573
- else {
1574
- if (item !== undefined && item.phase !== "Terminal")
1575
- yield* emit(item, { _tag: "Dropped", reason: command.reason });
1576
- yield* finishWithdrawal(command.key, Result.succeed("withdrawn"));
1577
- }
1578
- }
1579
- else {
1580
- removalRetryAtMs.set(command.clipId, monotonicMillis(clock) + 1_000);
1581
- if (result.failure.context.outcome === "unknown")
1582
- removalUnknownAtSerial.set(command.clipId, snapshotAcquiredSerial);
1583
- if (item?.phase === "Started")
1584
- yield* finishWithdrawal(command.key, Result.succeed("already-started"));
1585
- // A refusal is not an outcome: the withdrawal stays pending and is retried.
1586
- // An unknown removal may already have taken effect, so it ends the wait.
1587
- else if (result.failure.context.outcome === "unknown")
1588
- yield* finishWithdrawal(command.key, Result.fail(result.failure));
1589
- }
1590
- break;
1591
- }
1592
- case "RemoveFiller": {
1593
- pendingFillerRemovals.delete(command.clipId);
1594
- if (Result.isSuccess(result)) {
1595
- removalRetryAtMs.delete(command.clipId);
1596
- removalUnknownAtSerial.delete(command.clipId);
1597
- removedIds.add(command.clipId);
1598
- const owner = owned.get(command.clipId);
1599
- if (owner?._tag === "Filler" && filler.get(owner.index)?.clipId === command.clipId)
1600
- filler.delete(owner.index);
1601
- owned.delete(command.clipId);
1602
- playingStartedMs.delete(command.clipId);
1603
- }
1604
- else {
1605
- removalRetryAtMs.set(command.clipId, monotonicMillis(clock) + 1_000);
1606
- if (result.failure.context.outcome === "unknown")
1607
- removalUnknownAtSerial.set(command.clipId, snapshotAcquiredSerial);
1608
- }
1609
- break;
1610
- }
1611
- case "RemoveDuplicate":
1612
- if (Result.isFailure(result))
1613
- yield* closeActor(result.failure);
1614
- break;
1615
- case "DeferAt": {
1616
- pendingAtDeferrals.delete(command.key);
1617
- const item = items.get(command.key);
1618
- if (Result.isFailure(result)) {
1619
- removalRetryAtMs.set(command.clipId, monotonicMillis(clock) + 1_000);
1620
- if (result.failure.context.outcome === "unknown")
1621
- removalUnknownAtSerial.set(command.clipId, snapshotAcquiredSerial);
1622
- break;
1623
- }
1624
- removalRetryAtMs.delete(command.clipId);
1625
- removalUnknownAtSerial.delete(command.clipId);
1626
- owned.delete(command.clipId);
1627
- removedIds.add(command.clipId);
1628
- playingStartedMs.delete(command.clipId);
1629
- if (item === undefined || item.phase === "Started" || item.phase === "Terminal")
1630
- break;
1631
- delete item.clipId;
1632
- item.sessionId = undefined;
1633
- const pending = pendingWithdrawals.get(command.key);
1634
- if (pending !== undefined) {
1635
- yield* emit(item, { _tag: "Dropped", reason: pending.reason });
1636
- yield* finishWithdrawal(command.key, Result.succeed("withdrawn"));
1637
- }
1638
- else
1639
- yield* emit(item, { _tag: "Accepted" });
1640
- break;
1641
- }
1642
- case "Order":
1643
- blockedMove = Result.isFailure(result) ? command.signature : undefined;
1644
- break;
1645
- case "Cut":
1646
- if (Result.isFailure(result))
1647
- blockedCut = command.clipId;
1648
- break;
1649
- case "PauseAutoplay":
1650
- if (Result.isFailure(result)) {
1651
- drainFailure = result.failure;
1652
- const replies = drainReplies.splice(0);
1653
- for (const reply of replies)
1654
- yield* Deferred.fail(reply, result.failure);
1655
- }
1656
- break;
1657
- }
1658
- });
1659
- const pendingBatches = () => [...batches.values()].map((batch) => ({
1660
- id: batch.id,
1661
- waitFor: batch.waitFor,
1662
- targets: [...batch.targets].map(([key, target]) => ({ key, reason: target.reason })),
1663
- }));
1664
- /** The reply a batch keeps for its withdrawal of `key`, taken once. */
1665
- const takeBatchReply = (key) => {
1666
- for (const batch of batches.values()) {
1667
- const target = batch.targets.get(key);
1668
- if (target === undefined)
1669
- continue;
1670
- batch.targets.delete(key);
1671
- return target.reply;
1672
- }
1673
- return undefined;
1674
- };
1675
- /** A batch takes effect: what it withdraws goes at once, and its replacements take over. */
1676
- const commitBatch = (id) => Effect.gen(function* () {
1677
- const batch = batches.get(id);
1678
- if (batch === undefined)
1679
- return;
1680
- batches.delete(id);
1681
- for (const [key, target] of batch.targets) {
1682
- const item = items.get(key);
1683
- if (item !== undefined)
1684
- yield* requestWithdrawal(item, target.reason, target.reply);
1685
- else if (target.reply !== undefined)
1686
- yield* Deferred.succeed(target.reply, "not-found");
1687
- }
1688
- yield* Deferred.succeed(batch.committed, undefined);
1689
- });
1690
- const reconcile = Effect.gen(function* () {
1691
- const state = yield* engine.state;
1692
- if (ended !== undefined)
1693
- return;
1694
- prune(state);
1695
- refreshAnchors();
1696
- const sources = projectSources(state);
1697
- if (!observationReady) {
1698
- yield* publishState(state, sources.sessions);
1699
- return;
1700
- }
1701
- const nowMs = monotonicMillis(clock);
1702
- for (const item of items.values())
1703
- if (item.phase === "Started")
1704
- fireCues(item, nowMs);
1705
- for (const uncertain of unknownFillers.values()) {
1706
- const sourceRetired = uncertain.sessionId !== undefined && !sources.has(uncertain.sessionId);
1707
- if (sourceRetired)
1708
- unknownFillers.delete(uncertain.index);
1709
- if (sourceRetired ||
1710
- (uncertain.sessionId !== undefined &&
1711
- Option.getOrUndefined(state.preferredSessionId) !== uncertain.sessionId)) {
1712
- if (fillerIndex <= uncertain.index) {
1713
- fillerIndex = uncertain.index + 1;
1714
- upcomingFiller = undefined;
1715
- }
1716
- }
1717
- }
1718
- for (const [key, pending] of pendingWithdrawals) {
1719
- const item = items.get(key);
1720
- if (item === undefined)
1721
- continue;
1722
- if (item.phase === "Started")
1723
- yield* finishWithdrawal(key, Result.succeed("already-started"));
1724
- // A withdrawal still being retried when its item settles another way. Terminal
1725
- // Unknown keeps its uncertain result from the retirement or terminal settlement.
1726
- else if (item.phase === "Terminal" && item.status._tag !== "Unknown")
1727
- yield* finishWithdrawal(key, Result.succeed(settledOutcome(item)));
1728
- else if (item.clipId !== undefined)
1729
- yield* sendItemRemoval(item, pending.reason);
1730
- }
1731
- for (const item of items.values())
1732
- if (item.phase === "Unknown" &&
1733
- item.clipId === undefined &&
1734
- item.unknownSessionId !== undefined &&
1735
- !sources.has(item.unknownSessionId))
1736
- yield* resolveRetiredUnknown(item);
1737
- // After retirement, so a deadline no longer outlives the uncertainty that
1738
- // set it until the next message.
1739
- updateRecovery(state);
1740
- const preferred = Option.getOrUndefined(state.preferredSessionId);
1741
- let unknownFillerCount = 0;
1742
- for (const entry of unknownFillers.values())
1743
- if (entry.sessionId === undefined || entry.sessionId === preferred)
1744
- unknownFillerCount++;
1745
- const brokenGroups = new Map();
1746
- for (const [key, group] of groups)
1747
- if (group.brokenAt !== undefined)
1748
- brokenGroups.set(key, group.brokenAt);
1749
- const decide = () => plan({
1750
- engine: state,
1751
- items: [...items.values()],
1752
- owned,
1753
- lanes,
1754
- nowMs: monotonicMillis(clock),
1755
- playingStartedMs,
1756
- floorSeconds,
1757
- targetSeconds,
1758
- refillActive,
1759
- maxBuildsInFlight,
1760
- accepting,
1761
- drain: draining ? (finishAccepted ? "accepted" : "playing") : undefined,
1762
- fillerRetryAtMs,
1763
- unknownFillerCount,
1764
- blockedMove,
1765
- withdrawing: new Set(pendingWithdrawals.keys()),
1766
- dispatched: pendingBuilds,
1767
- brokenGroups,
1768
- batches: pendingBatches(),
1769
- estimates: estimatesFrom(buildSamples, lengthSamples),
1770
- ...fillerView(),
1771
- cutLanes,
1772
- fillLengths,
1773
- blockedCut,
1774
- });
1775
- let decision = decide();
1776
- // A commit changes what is withdrawn and what takes over, so the plan is read again.
1777
- if (decision.commit.length > 0) {
1778
- for (const id of decision.commit)
1779
- yield* commitBatch(id);
1780
- decision = decide();
1781
- }
1782
- refillActive = decision.refillActive;
1783
- for (const { key, reason } of decision.withdraw) {
1784
- const item = items.get(key);
1785
- if (item !== undefined)
1786
- yield* requestWithdrawal(item, reason, takeBatchReply(key));
1787
- }
1788
- for (const clipId of decision.withdrawFiller)
1789
- if (!pendingFillerRemovals.has(clipId) && canRetryRemoval(clipId)) {
1790
- pendingFillerRemovals.add(clipId);
1791
- yield* sendCommand({ _tag: "RemoveFiller", clipId });
1792
- }
1793
- yield* publishState(state, sources.sessions);
1794
- if ((!draining || finishAccepted) && commandCount === 0 && decision.action !== undefined)
1795
- yield* applyPolicy(decision.action, state);
1796
- if (draining &&
1797
- finishAccepted &&
1798
- !autoplayPaused &&
1799
- commandCount === 0 &&
1800
- [...items.values()].every((item) => item.phase === "Terminal")) {
1801
- autoplayPaused = true;
1802
- yield* sendCommand({ _tag: "PauseAutoplay" });
1803
- }
1804
- if (drainFailure === undefined &&
1805
- drainReplies.length > 0 &&
1806
- commandCount === 0 &&
1807
- [...items.values()].every((item) => item.phase === "Terminal") &&
1808
- pendingWithdrawals.size === 0 &&
1809
- unknownFillers.size === 0) {
1810
- const after = yield* engine.state;
1811
- if (Option.isNone(after.playing) && !activeIds(after).some((clipId) => owned.has(clipId))) {
1812
- drained = true;
1813
- // A truthful completed drain no longer needs a replacement to resume
1814
- // service, even when retirement left a capacity episode behind.
1815
- capacityRecovery = undefined;
1816
- updateRecovery(after);
1817
- const replies = drainReplies.splice(0);
1818
- for (const reply of replies)
1819
- yield* Deferred.succeed(reply, undefined);
1820
- }
1821
- }
1822
- });
1823
- /** Registers a captured item, without yielding; its caller publishes the Accepted evidence. */
1824
- const accept = (captured, nowMs, startByMs, group, place) => {
1825
- const startedWaiter = Deferred.makeUnsafe();
1826
- const outcomeWaiter = Deferred.makeUnsafe();
1827
- const firstDecisiveWaiter = Deferred.makeUnsafe();
1828
- const item = {
1829
- key: captured.key,
1830
- lane: captured.lane,
1831
- request: captured.request,
1832
- fingerprint: captured.fingerprint,
1833
- admission: place === undefined ? ++admission : place.admission,
1834
- seconds: captured.request.durationSeconds,
1835
- phase: "Accepted",
1836
- status: { _tag: "Accepted" },
1837
- ...(place === undefined
1838
- ? captured.notBeforeOffsetMs === undefined
1839
- ? {}
1840
- : { notBeforeMs: nowMs + captured.notBeforeOffsetMs }
1841
- : place.notBeforeMs === undefined
1842
- ? {}
1843
- : { notBeforeMs: place.notBeforeMs }),
1844
- ...(place?.replaces === undefined
1845
- ? {}
1846
- : { replaces: place.replaces, generation: place.generation ?? 1 }),
1847
- ...(place?.inserted === true ? { inserted: true } : {}),
1848
- ...(startByMs === undefined ? {} : { startByMs }),
1849
- firm: captured.firm,
1850
- ...(captured.atWallMs === undefined
1851
- ? {}
1852
- : {
1853
- atWallMs: captured.atWallMs,
1854
- atMs: nowMs + captured.atWallMs - clock.currentTimeMillisUnsafe(),
1855
- }),
1856
- late: captured.late,
1857
- cues: captured.cues ?? [],
1858
- firedCues: new Set(),
1859
- ...(captured.continuity === undefined ? {} : { continuity: captured.continuity }),
1860
- ...(captured.mode === "asap" ? { asap: true } : {}),
1861
- ...(captured.mode === "manual" ? { held: true } : {}),
1862
- ...(group === undefined ? {} : { group }),
1863
- sessionId: undefined,
1864
- unknownSessionId: undefined,
1865
- unknownCause: undefined,
1866
- unknownAtMs: undefined,
1867
- handle: {
1868
- key: captured.key,
1869
- started: Deferred.await(startedWaiter),
1870
- outcome: Deferred.await(outcomeWaiter),
1871
- firstDecisive: Deferred.await(firstDecisiveWaiter),
1872
- },
1873
- startedWaiter,
1874
- outcomeWaiter,
1875
- firstDecisiveWaiter,
1876
- };
1877
- items.set(item.key, item);
1878
- return item;
1879
- };
1880
- /**
1881
- * One reply for withdrawals across a group's parts. A group key reports
1882
- * `withdrawn` if any part was; a part key reports that part's own outcome,
1883
- * while the parts after it are withdrawn too.
1884
- */
1885
- const settleWithdrawals = (replies, reply, wholeGroup) => Effect.gen(function* () {
1886
- const outcomes = [];
1887
- for (const pending of replies) {
1888
- const result = yield* Effect.result(Deferred.await(pending));
1889
- if (Result.isFailure(result)) {
1890
- yield* Deferred.fail(reply, result.failure);
1891
- return;
1892
- }
1893
- outcomes.push(result.success);
1894
- }
1895
- yield* Deferred.succeed(reply, wholeGroup && outcomes.includes("withdrawn") ? "withdrawn" : (outcomes[0] ?? "not-found"));
1896
- });
1897
- /**
1898
- * The place an insert takes beside its anchor, or why it cannot: the anchor's lane, an
1899
- * admission between the anchor and its neighbour in that lane, and a group place when
1900
- * the insert falls between two parts of a group.
1901
- */
1902
- const placeInsert = (insert) => {
1903
- const live = (entry) => entry !== undefined && entry.phase !== "Terminal" && entry.replacedBy === undefined;
1904
- const named = groups.get(insert.anchor);
1905
- let anchor;
1906
- if (named === undefined)
1907
- anchor = items.get(insert.anchor);
1908
- else {
1909
- const parts = named.parts
1910
- .map((key) => items.get(key))
1911
- .filter(live)
1912
- .sort((a, b) => a.group.index - b.group.index);
1913
- if (insert.side === "before" &&
1914
- (parts.some((part) => part.phase === "Started") || parts[0]?.group?.index !== 0))
1915
- return "The anchor group already started";
1916
- anchor = insert.side === "before" ? parts[0] : parts.at(-1);
1917
- }
1918
- if (!live(anchor))
1919
- return "Nothing queued under the anchor key";
1920
- if (anchor.phase === "Unknown")
1921
- return "The anchor's admission is uncertain";
1922
- if (anchor.held === true)
1923
- return "The anchor is held until it is released";
1924
- if (insert.side === "before" && anchor.phase === "Started")
1925
- return "The anchor already started";
1926
- const neighbours = [...items.values()]
1927
- .filter((entry) => entry.lane === anchor.lane && entry.phase !== "Terminal")
1928
- .map((entry) => entry.admission);
1929
- const bound = insert.side === "before"
1930
- ? Math.max(...neighbours.filter((value) => value < anchor.admission))
1931
- : Math.min(...neighbours.filter((value) => value > anchor.admission));
1932
- const admission = Number.isFinite(bound)
1933
- ? (anchor.admission + bound) / 2
1934
- : anchor.admission + (insert.side === "before" ? -0.5 : 0.5);
1935
- if (admission === anchor.admission || admission === bound)
1936
- return "No room is left to insert at that place";
1937
- let group;
1938
- if (anchor.group !== undefined) {
1939
- const indexes = (groups.get(anchor.group.key)?.parts ?? [])
1940
- .map((key) => items.get(key)?.group?.index)
1941
- .filter((value) => value !== undefined);
1942
- const index = anchor.group.index;
1943
- const neighbour = insert.side === "before"
1944
- ? Math.max(...indexes.filter((value) => value < index))
1945
- : Math.min(...indexes.filter((value) => value > index));
1946
- // Before the first part or after the last one, the insert stays outside the group.
1947
- // Once parts have aired and settled, the place before the first remaining part is
1948
- // still inside the group.
1949
- const between = insert.side === "before"
1950
- ? index > 0
1951
- ? (Number.isFinite(neighbour) ? neighbour : index - 1) + index
1952
- : undefined
1953
- : Number.isFinite(neighbour)
1954
- ? index + neighbour
1955
- : undefined;
1956
- if (between !== undefined) {
1957
- if (between / 2 === index || between / 2 === neighbour)
1958
- return "No room is left to insert at that place";
1959
- group = { key: anchor.group.key, index: between / 2 };
1960
- }
1961
- }
1962
- const timing = {
1963
- ...(anchor.atWallMs === undefined ? {} : { atWallMs: anchor.atWallMs }),
1964
- late: anchor.late,
1965
- asap: anchor.asap === true,
1966
- };
1967
- return group === undefined
1968
- ? { lane: anchor.lane, admission, ...timing }
1969
- : { lane: anchor.lane, admission, group, ...timing };
1970
- };
1971
- /** The items a withdrawal of `key` names: a group's parts, a part and those after it, or one item. */
1972
- const withdrawalTargets = (key) => {
1973
- const item = items.get(key);
1974
- const named = groups.get(key);
1975
- const group = named ?? (item?.group === undefined ? undefined : groups.get(item.group.key));
1976
- if (group === undefined || (named === undefined && item?.inserted === true))
1977
- return item === undefined ? [] : [item];
1978
- const from = named === undefined ? item.group.index : 0;
1979
- const targets = group.parts
1980
- .map((part) => items.get(part))
1981
- .filter((part) => part?.group !== undefined && part.group.index >= from);
1982
- // A part key's reply is that part's own outcome, so it goes first.
1983
- if (named === undefined)
1984
- targets.sort((a, b) => Number(b === item) - Number(a === item));
1985
- return targets;
1986
- };
1987
- /**
1988
- * Checks and admits a call's edits in order, without yielding. A refused edit undoes the
1989
- * edits before it, so nothing of a refused batch is admitted or published.
1990
- */
1991
- const stageEdits = (edits, batched, state, nowMs) => {
1992
- const undo = [];
1993
- const added = [];
1994
- const waitFor = [];
1995
- const replaced = [];
1996
- const results = [];
1997
- const refuse = (error) => {
1998
- for (const step of undo.reverse())
1999
- step();
2000
- return Result.fail(error);
2001
- };
2002
- // Clips a batch withdraws or replaces do not count ahead of the clips it adds.
2003
- const excluding = new Set();
2004
- const named = [];
2005
- for (const edit of edits)
2006
- switch (edit._tag) {
2007
- case "Submit":
2008
- named.push(edit.item.key);
2009
- break;
2010
- case "SubmitGroup":
2011
- named.push(edit.group.key, ...edit.group.parts.map((part) => part.key));
2012
- break;
2013
- case "Insert":
2014
- named.push(edit.insert.key);
2015
- break;
2016
- case "Replace":
2017
- named.push(edit.key, edit.next.key);
2018
- excluding.add(edit.key);
2019
- break;
2020
- case "Withdraw": {
2021
- const targets = withdrawalTargets(edit.key);
2022
- named.push(edit.key, ...targets.map((target) => target.key).filter((key) => key !== edit.key));
2023
- for (const target of targets)
2024
- excluding.add(target.key);
2025
- break;
2026
- }
2027
- }
2028
- if (new Set(named).size !== named.length)
2029
- return refuse(invalid("An edit batch names each key once"));
2030
- for (const edit of edits)
2031
- if (edit._tag === "Insert" && excluding.has(edit.insert.anchor))
2032
- return refuse(invalid("An insert's anchor is withdrawn or replaced in the same batch"));
2033
- const view = (place) => projectedStartMs({
2034
- engine: state,
2035
- items: [...items.values()],
2036
- owned,
2037
- lanes,
2038
- playingStartedMs,
2039
- nowMs,
2040
- batches: pendingBatches(),
2041
- estimates: estimatesFrom(buildSamples, lengthSamples),
2042
- ...fillerView(),
2043
- withdrawing: new Set([...pendingWithdrawals.keys(), ...excluding]),
2044
- }, place);
2045
- /**
2046
- * A new submission's lane policy: a skip lane refuses it while the lane has an item
2047
- * waiting or playing; a replace lane supersedes the items still waiting there.
2048
- */
2049
- const conflict = (key, lane) => {
2050
- const others = [...items.values()].filter((entry) => entry.lane === lane &&
2051
- entry.phase !== "Terminal" &&
2052
- !excluding.has(entry.key) &&
2053
- !added.includes(entry));
2054
- switch (conflicts.get(lane)) {
2055
- case "skip":
2056
- return others.length > 0 ? LaneBusy.of(key, lane) : undefined;
2057
- case "replace":
2058
- for (const entry of others)
2059
- if (entry.phase !== "Started" && !pendingWithdrawals.has(entry.key)) {
2060
- replaced.push(entry.key);
2061
- excluding.add(entry.key);
2062
- }
2063
- return undefined;
2064
- default:
2065
- return undefined;
2066
- }
2067
- };
2068
- const admitted = (entry) => {
2069
- added.push(entry);
2070
- waitFor.push(entry.key);
2071
- undo.push(() => items.delete(entry.key));
2072
- };
2073
- /** An existing key: the same spec returns its handle, and the batch waits for it too. */
2074
- const repeated = (key, fingerprint) => {
2075
- const existing = items.get(key) ?? history.get(key);
2076
- if (existing === undefined && !groups.has(key))
2077
- return Result.succeed(undefined);
2078
- if (existing?.fingerprint !== fingerprint)
2079
- return Result.fail(KeyMismatch.of(key));
2080
- waitFor.push(key);
2081
- return Result.succeed(existing.handle);
2082
- };
2083
- for (const edit of edits) {
2084
- switch (edit._tag) {
2085
- case "Submit": {
2086
- const { item } = edit;
2087
- const known = repeated(item.key, item.fingerprint);
2088
- if (Result.isFailure(known))
2089
- return refuse(known.failure);
2090
- if (known.success !== undefined) {
2091
- results.push({ _tag: "Added", handle: known.success });
2092
- break;
2093
- }
2094
- const busy = conflict(item.key, item.lane);
2095
- if (busy !== undefined)
2096
- return refuse(busy);
2097
- const startByMs = item.startByOffsetMs === undefined ? undefined : nowMs + item.startByOffsetMs;
2098
- if (startByMs !== undefined &&
2099
- view({
2100
- lane: item.lane,
2101
- admission: Infinity,
2102
- seconds: item.request.durationSeconds,
2103
- startByMs,
2104
- ...(item.mode === "asap" ? { asap: true } : {}),
2105
- ...(item.mode === "manual" ? { held: true } : {}),
2106
- }) > startByMs)
2107
- return refuse(WouldMissDeadline.of(item.key));
2108
- const entry = accept(item, nowMs, startByMs, undefined);
2109
- admitted(entry);
2110
- results.push({ _tag: "Added", handle: entry.handle });
2111
- break;
2112
- }
2113
- case "SubmitGroup": {
2114
- const { group } = edit;
2115
- const known = groups.get(group.key);
2116
- if (known !== undefined) {
2117
- if (known.fingerprint !== group.fingerprint)
2118
- return refuse(KeyMismatch.of(group.key));
2119
- waitFor.push(...known.parts);
2120
- results.push({ _tag: "AddedGroup", handle: known.handle });
2121
- break;
2122
- }
2123
- const taken = [group.key, ...group.parts.map((part) => part.key)].find((key) => items.has(key) || history.has(key) || groups.has(key));
2124
- if (taken !== undefined)
2125
- return refuse(KeyMismatch.of(taken));
2126
- const [first, ...rest] = group.parts;
2127
- const busy = conflict(group.key, first.lane);
2128
- if (busy !== undefined)
2129
- return refuse(busy);
2130
- const startByMs = first.startByOffsetMs === undefined ? undefined : nowMs + first.startByOffsetMs;
2131
- if (startByMs !== undefined &&
2132
- view({
2133
- lane: first.lane,
2134
- admission: Infinity,
2135
- seconds: first.request.durationSeconds,
2136
- startByMs,
2137
- }) > startByMs)
2138
- return refuse(WouldMissDeadline.of(group.key));
2139
- const parts = [
2140
- accept(first, nowMs, startByMs, { key: group.key, index: 0 }),
2141
- ...rest.map((part, index) => accept(part, nowMs, undefined, { key: group.key, index: index + 1 })),
2142
- ];
2143
- for (const part of parts)
2144
- admitted(part);
2145
- const handle = {
2146
- key: group.key,
2147
- parts: [parts[0].handle, ...parts.slice(1).map((part) => part.handle)],
2148
- };
2149
- groups.set(group.key, {
2150
- fingerprint: group.fingerprint,
2151
- handle,
2152
- parts: group.parts.map((part) => part.key),
2153
- brokenAt: undefined,
2154
- });
2155
- undo.push(() => groups.delete(group.key));
2156
- results.push({ _tag: "AddedGroup", handle });
2157
- break;
2158
- }
2159
- case "Insert": {
2160
- const { insert } = edit;
2161
- const known = repeated(insert.key, insert.fingerprint);
2162
- if (Result.isFailure(known))
2163
- return refuse(known.failure);
2164
- if (known.success !== undefined) {
2165
- results.push({ _tag: "Added", handle: known.success });
2166
- break;
2167
- }
2168
- const placed = placeInsert(insert);
2169
- if (typeof placed === "string")
2170
- return refuse(invalid(placed));
2171
- const startByMs = insert.startByOffsetMs === undefined ? undefined : nowMs + insert.startByOffsetMs;
2172
- if (startByMs !== undefined &&
2173
- view({ ...placed, seconds: insert.request.durationSeconds, startByMs }) > startByMs)
2174
- return refuse(WouldMissDeadline.of(insert.key));
2175
- const entry = accept({
2176
- key: insert.key,
2177
- lane: placed.lane,
2178
- request: insert.request,
2179
- fingerprint: insert.fingerprint,
2180
- notBeforeOffsetMs: insert.notBeforeOffsetMs,
2181
- startByOffsetMs: insert.startByOffsetMs,
2182
- firm: insert.firm,
2183
- atWallMs: placed.atWallMs,
2184
- late: placed.late,
2185
- ...(placed.asap ? { mode: "asap" } : {}),
2186
- cues: insert.cues,
2187
- ...(insert.continuity === undefined ? {} : { continuity: insert.continuity }),
2188
- }, nowMs, startByMs, placed.group, {
2189
- admission: placed.admission,
2190
- inserted: true,
2191
- notBeforeMs: insert.notBeforeOffsetMs === undefined
2192
- ? undefined
2193
- : nowMs + insert.notBeforeOffsetMs,
2194
- });
2195
- admitted(entry);
2196
- if (placed.group !== undefined) {
2197
- const parts = groups.get(placed.group.key)?.parts;
2198
- parts?.push(entry.key);
2199
- undo.push(() => parts?.pop());
2200
- }
2201
- results.push({ _tag: "Added", handle: entry.handle });
2202
- break;
2203
- }
2204
- case "Replace": {
2205
- const { next } = edit;
2206
- const fingerprint = JSON.stringify({
2207
- replaces: edit.key,
2208
- request: next.request,
2209
- ...(next.cues.length === 0 ? {} : { cues: next.cues }),
2210
- ...(next.continuity === undefined ? {} : { continuity: next.continuity }),
2211
- });
2212
- const known = repeated(next.key, fingerprint);
2213
- if (Result.isFailure(known))
2214
- return refuse(known.failure);
2215
- if (known.success !== undefined) {
2216
- results.push({ _tag: "Added", handle: known.success });
2217
- break;
2218
- }
2219
- const old = items.get(edit.key);
2220
- if (old === undefined ||
2221
- old.replacedBy !== undefined ||
2222
- (old.phase !== "Accepted" && old.phase !== "Building" && old.phase !== "Ready"))
2223
- return refuse(invalid("Nothing queued to replace under that key"));
2224
- const entry = accept({
2225
- key: next.key,
2226
- lane: old.lane,
2227
- request: next.request,
2228
- fingerprint,
2229
- notBeforeOffsetMs: undefined,
2230
- startByOffsetMs: undefined,
2231
- firm: old.firm,
2232
- atWallMs: old.atWallMs,
2233
- late: old.late,
2234
- ...(old.asap === true ? { mode: "asap" } : {}),
2235
- ...(old.held === true ? { mode: "manual" } : {}),
2236
- cues: next.cues,
2237
- ...(next.continuity === undefined ? {} : { continuity: next.continuity }),
2238
- }, nowMs, old.startByMs, old.group, {
2239
- admission: old.admission,
2240
- replaces: old.key,
2241
- generation: (old.generation ?? 0) + 1,
2242
- inserted: old.inserted === true,
2243
- notBeforeMs: old.notBeforeMs,
2244
- });
2245
- admitted(entry);
2246
- old.replacedBy = entry.key;
2247
- undo.push(() => delete old.replacedBy);
2248
- // A pending batch that waits for the replaced item waits for its replacement.
2249
- for (const batch of batches.values())
2250
- if (batch.waitFor.includes(old.key)) {
2251
- batch.waitFor.push(entry.key);
2252
- undo.push(() => batch.waitFor.pop());
2253
- }
2254
- if (old.group !== undefined) {
2255
- const parts = groups.get(old.group.key)?.parts;
2256
- parts?.push(entry.key);
2257
- undo.push(() => parts?.pop());
2258
- }
2259
- results.push({ _tag: "Added", handle: entry.handle });
2260
- break;
2261
- }
2262
- case "Withdraw":
2263
- results.push({ _tag: "Withdrawal", key: edit.key });
2264
- break;
2265
- }
2266
- }
2267
- return Result.succeed({ results, added, waitFor, replaced });
2268
- };
2269
- /**
2270
- * Makes a staged call's handle. A batch keeps what it withdraws as cover until it commits;
2271
- * a single call adds one item or group and takes effect at once.
2272
- */
2273
- const openBatch = (staged, batched) => {
2274
- // A single call adds one item or group and never withdraws; only a replace lane gives
2275
- // it something to supersede.
2276
- if (!batched && staged.replaced.length === 0)
2277
- return {
2278
- handle: {
2279
- results: staged.results.filter((result) => result._tag !== "Withdrawal"),
2280
- committed: Effect.void,
2281
- },
2282
- settle: [],
2283
- };
2284
- const settle = [];
2285
- const batch = {
2286
- id: ++batchCount,
2287
- waitFor: [...staged.waitFor],
2288
- targets: new Map(staged.replaced.map((key) => [key, { reason: "replaced", reply: undefined }])),
2289
- committed: Deferred.makeUnsafe(),
2290
- };
2291
- const results = staged.results.map((result) => {
2292
- if (result._tag !== "Withdrawal")
2293
- return result;
2294
- const replies = [];
2295
- for (const target of withdrawalTargets(result.key)) {
2296
- const reply = Deferred.makeUnsafe();
2297
- replies.push(reply);
2298
- batch.targets.set(target.key, { reason: "withdrawn", reply });
2299
- }
2300
- const outcome = Deferred.makeUnsafe();
2301
- settle.push(settleWithdrawals(replies, outcome, groups.has(result.key)));
2302
- return {
2303
- _tag: "Withdrawal",
2304
- outcome: Deferred.await(outcome).pipe(Effect.raceFirst(closedCall)),
2305
- };
2306
- });
2307
- // A pending batch whose new clip this one withdraws now also waits for this one's, so
2308
- // its own cover is not withdrawn before anything replaces it.
2309
- for (const other of batches.values())
2310
- if (other.waitFor.some((key) => batch.targets.has(key)))
2311
- other.waitFor.push(...batch.waitFor);
2312
- batches.set(batch.id, batch);
2313
- return {
2314
- handle: {
2315
- results: batched ? results : results.filter((result) => result._tag !== "Withdrawal"),
2316
- committed: batched ? Deferred.await(batch.committed) : Effect.void,
2317
- },
2318
- settle,
2319
- };
2320
- };
2321
- const actor = Effect.gen(function* () {
2322
- for (;;) {
2323
- const message = yield* takeQueue(inbox);
2324
- // Queued public calls observe the same terminal result through closedCall.
2325
- if (ended !== undefined && message._tag !== "CommandDone")
2326
- continue;
2327
- switch (message._tag) {
2328
- case "Edit": {
2329
- if (!accepting) {
2330
- yield* Deferred.fail(message.reply, invalid("Scheduler is draining or closed"));
2331
- break;
2332
- }
2333
- const nowMs = monotonicMillis(clock);
2334
- const state = yield* engine.state;
2335
- if (ended !== undefined)
2336
- continue;
2337
- // No yield from the terminal check above through admission and its
2338
- // publication below: a claim either precedes the items or settles them
2339
- // after their Accepted evidence.
2340
- const staged = stageEdits(message.edits, message.batched, state, nowMs);
2341
- if (Result.isFailure(staged)) {
2342
- yield* Deferred.fail(message.reply, staged.failure);
2343
- break;
2344
- }
2345
- for (const entry of staged.success.added)
2346
- PubSub.publishUnsafe(events, {
2347
- key: entry.key,
2348
- at: clock.currentTimeMillisUnsafe(),
2349
- status: entry.status,
2350
- });
2351
- const opened = openBatch(staged.success, message.batched);
2352
- for (const effect of opened.settle)
2353
- yield* Effect.forkIn(effect, actorScope);
2354
- yield* observed(state);
2355
- // An edit returns its committed view; other messages publish once
2356
- // through reconciliation after all their evidence is adopted.
2357
- yield* publishState(state);
2358
- yield* Deferred.succeed(message.reply, opened.handle);
2359
- break;
2360
- }
2361
- case "Withdraw": {
2362
- const targets = withdrawalTargets(message.key);
2363
- if (targets.length <= 1 && !groups.has(message.key)) {
2364
- if (targets[0] === undefined)
2365
- yield* Deferred.succeed(message.reply, "not-found");
2366
- else
2367
- yield* requestWithdrawal(targets[0], "withdrawn", message.reply);
2368
- break;
2369
- }
2370
- const replies = [];
2371
- for (const part of targets) {
2372
- const reply = Deferred.makeUnsafe();
2373
- replies.push(reply);
2374
- yield* requestWithdrawal(part, "withdrawn", reply);
2375
- }
2376
- yield* Effect.forkIn(settleWithdrawals(replies, message.reply, groups.has(message.key)), actorScope);
2377
- break;
2378
- }
2379
- case "Release": {
2380
- const item = items.get(message.key);
2381
- if (item?.held !== true || item.phase === "Started" || item.phase === "Terminal") {
2382
- yield* Deferred.fail(message.reply, invalid("Nothing is held under that key"));
2383
- break;
2384
- }
2385
- item.held = false;
2386
- item.asap = true;
2387
- yield* Deferred.succeed(message.reply, undefined);
2388
- break;
2389
- }
2390
- case "Drain": {
2391
- if (drainFailure !== undefined) {
2392
- yield* Deferred.fail(message.reply, drainFailure);
2393
- break;
2394
- }
2395
- accepting = false;
2396
- if (drained) {
2397
- yield* Deferred.succeed(message.reply, undefined);
2398
- break;
2399
- }
2400
- drainReplies.push(message.reply);
2401
- if (!draining) {
2402
- draining = true;
2403
- finishAccepted = message.finish === "accepted";
2404
- updateRecovery();
2405
- // Renewal admission must stop even while an earlier enqueue is
2406
- // awaiting its reply in the command worker.
2407
- const stoppedRenewal = yield* Effect.result(engine.stopRenewal);
2408
- if (ended !== undefined)
2409
- break;
2410
- if (Result.isFailure(stoppedRenewal)) {
2411
- drainFailure = stoppedRenewal.failure;
2412
- for (const reply of drainReplies.splice(0))
2413
- yield* Deferred.fail(reply, stoppedRenewal.failure);
2414
- break;
2415
- }
2416
- if (!finishAccepted) {
2417
- autoplayPaused = true;
2418
- yield* sendCommand({ _tag: "PauseAutoplay" });
2419
- }
2420
- }
2421
- break;
2422
- }
2423
- case "Snapshot": {
2424
- lastProcessedSnapshotSerial = message.acquiredSerial;
2425
- observationReady = true;
2426
- yield* observed(message.state, true);
2427
- blockedMove = undefined;
2428
- break;
2429
- }
2430
- case "Event":
2431
- yield* onEvent(message.event);
2432
- yield* observed(yield* engine.state);
2433
- blockedMove = undefined;
2434
- break;
2435
- case "Tick":
2436
- break;
2437
- case "CommandDone":
2438
- yield* handleCommandDone(message.command, message.result);
2439
- if (commandCount === 0)
2440
- earlyClipEvents.clear();
2441
- break;
2442
- case "Closed":
2443
- yield* closeActor(message.cause);
2444
- yield* Deferred.fail(initialized, ReactorError.fromCode("Closed", message.cause.message));
2445
- break;
2446
- }
2447
- if (ended === undefined)
2448
- yield* reconcile;
2449
- if (message._tag === "Snapshot")
2450
- yield* Deferred.succeed(initialized, undefined);
2451
- }
2452
- });
2453
- const observe = Effect.gen(function* () {
2454
- for (;;) {
2455
- const result = yield* Effect.result(Effect.scoped(Effect.gen(function* () {
2456
- // Claim the serial before acquiring the initial state. A snapshot
2457
- // begun before an enqueue result must not disprove its clip.
2458
- const acquiredSerial = ++snapshotAcquiredSerial;
2459
- const observation = yield* engine.observe({ capacity: 4096 });
2460
- yield* Queue.offer(inbox, {
2461
- _tag: "Snapshot",
2462
- state: observation.initial,
2463
- acquiredSerial,
2464
- });
2465
- yield* Stream.runForEach(observation.events, (event) => Queue.offer(inbox, { _tag: "Event", event }));
2466
- })));
2467
- if (Result.isSuccess(result)) {
2468
- yield* Queue.offer(inbox, {
2469
- _tag: "Closed",
2470
- cause: ReactorError.fromCode("Closed", "The orchestration closed"),
2471
- });
2472
- return;
2473
- }
2474
- if (result.failure.reason._tag !== "Overflow") {
2475
- yield* Queue.offer(inbox, { _tag: "Closed", cause: result.failure });
2476
- return;
2477
- }
2478
- }
2479
- });
2480
- // Orderly scope teardown has already claimed and settled Closed before
2481
- // worker interruption; unexpected interruption retains its original Cause.
2482
- const supervise = (effect) => effect.pipe(Effect.onExit((exit) => Exit.isFailure(exit) && ended === undefined
2483
- ? terminate(Exit.failCause(exit.cause))
2484
- : Effect.void));
2485
- yield* Effect.forkScoped(supervise(watchdog));
2486
- yield* Effect.forkScoped(supervise(commandWorker));
2487
- yield* Effect.forkScoped(supervise(actor));
2488
- yield* Effect.forkScoped(supervise(observe));
2489
- yield* Effect.forkScoped(Effect.gen(function* () {
2490
- for (;;) {
2491
- yield* Effect.sleep("100 millis");
2492
- yield* Queue.offer(inbox, { _tag: "Tick" });
2493
- }
2494
- }));
2495
- yield* Queue.offer(inbox, { _tag: "Tick" });
2496
- yield* Effect.addFinalizer(() => closeActor(ReactorError.fromCode("Closed", "The scheduler closed")));
2497
- yield* Deferred.await(initialized);
2498
- const laneSet = new Set(lanes);
2499
- const send = (edits, batched) => Effect.gen(function* () {
2500
- const reply = yield* Deferred.make();
2501
- if (!(yield* Queue.offer(inbox, { _tag: "Edit", edits, batched, reply })))
2502
- return yield* closedCall;
2503
- return yield* Deferred.await(reply).pipe(Effect.raceFirst(closedCall));
2504
- });
2505
- /** The one result of a single call. */
2506
- const only = (handle) => handle.results[0];
2507
- const submit = (input) => Effect.gen(function* () {
2508
- if (yield* Deferred.isDone(stopped))
2509
- return yield* closedCall;
2510
- const item = yield* captureItem(input, laneSet);
2511
- yield* Effect.annotateCurrentSpan("reactor.scheduler.item.key", item.key);
2512
- const result = only(yield* send([{ _tag: "Submit", item }], false));
2513
- if (result?._tag !== "Added")
2514
- return yield* Effect.die("A submission returned no item");
2515
- return result.handle;
2516
- }).pipe(Effect.withSpan("Scheduler.submit", {}, { captureStackTrace: false }));
2517
- const submitGroup = (input) => Effect.gen(function* () {
2518
- if (yield* Deferred.isDone(stopped))
2519
- return yield* closedCall;
2520
- const group = yield* captureGroup(input, laneSet);
2521
- yield* Effect.annotateCurrentSpan("reactor.scheduler.group.key", group.key);
2522
- const result = only(yield* send([{ _tag: "SubmitGroup", group }], false));
2523
- if (result?._tag !== "AddedGroup")
2524
- return yield* Effect.die("A group returned no handle");
2525
- return result.handle;
2526
- }).pipe(Effect.withSpan("Scheduler.submitGroup", {}, { captureStackTrace: false }));
2527
- const replace = (key, input) => Effect.gen(function* () {
2528
- if (yield* Deferred.isDone(stopped))
2529
- return yield* closedCall;
2530
- const next = yield* captureReplacement(input);
2531
- const result = only(yield* send([{ _tag: "Replace", key, next }], false).pipe(
2532
- // A replacement keeps the replaced item's place and window, so neither a deadline
2533
- // nor its lane's policy is checked for it.
2534
- Effect.catchTag(["WouldMissDeadline", "LaneBusy"], (error) => Effect.die(error))));
2535
- if (result?._tag !== "Added")
2536
- return yield* Effect.die("A replacement returned no item");
2537
- return result.handle;
2538
- }).pipe(Effect.withSpan("Scheduler.replace", { attributes: { key } }, { captureStackTrace: false }));
2539
- const insert = (input) => Effect.gen(function* () {
2540
- if (yield* Deferred.isDone(stopped))
2541
- return yield* closedCall;
2542
- const captured = yield* captureInsert(input);
2543
- yield* Effect.annotateCurrentSpan("reactor.scheduler.item.key", captured.key);
2544
- const result = only(yield* send([{ _tag: "Insert", insert: captured }], false).pipe(
2545
- // An insert places itself, so its lane's policy does not apply.
2546
- Effect.catchTag("LaneBusy", (error) => Effect.die(error))));
2547
- if (result?._tag !== "Added")
2548
- return yield* Effect.die("An insert returned no item");
2549
- return result.handle;
2550
- }).pipe(Effect.withSpan("Scheduler.insert", {}, { captureStackTrace: false }));
2551
- const edit = (input) => Effect.gen(function* () {
2552
- if (yield* Deferred.isDone(stopped))
2553
- return yield* closedCall;
2554
- const edits = yield* captureEdits(input, laneSet);
2555
- yield* Effect.annotateCurrentSpan("reactor.scheduler.edits", edits.length);
2556
- return yield* send(edits, true);
2557
- }).pipe(Effect.withSpan("Scheduler.edit", {}, { captureStackTrace: false }));
2558
- const release = (key) => Effect.gen(function* () {
2559
- const reply = yield* Deferred.make();
2560
- if (!(yield* Queue.offer(inbox, { _tag: "Release", key, reply })))
2561
- return yield* closedCall;
2562
- return yield* Deferred.await(reply).pipe(Effect.raceFirst(closedCall));
2563
- }).pipe(Effect.withSpan("Scheduler.release", { attributes: { key } }, { captureStackTrace: false }));
2564
- const withdraw = (key) => Effect.gen(function* () {
2565
- const reply = yield* Deferred.make();
2566
- if (!(yield* Queue.offer(inbox, { _tag: "Withdraw", key, reply })))
2567
- return yield* closedCall;
2568
- return yield* Deferred.await(reply).pipe(Effect.raceFirst(closedCall));
2569
- }).pipe(Effect.withSpan("Scheduler.withdraw", { attributes: { key } }, {
2570
- captureStackTrace: false,
2571
- }));
2572
- const drain = (options = {}) => Effect.gen(function* () {
2573
- const reply = yield* Deferred.make();
2574
- const finish = options.finish ?? "playing";
2575
- if (finish !== "playing" && finish !== "accepted")
2576
- return yield* invalid("Drain finish must be playing or accepted");
2577
- if (!(yield* Queue.offer(inbox, { _tag: "Drain", finish, reply })))
2578
- return yield* closedCall;
2579
- yield* Deferred.await(reply).pipe(Effect.raceFirst(closedCall));
2580
- }).pipe(Effect.withSpan("Scheduler.drain", {}, { captureStackTrace: false }));
2581
- return {
2582
- submit,
2583
- submitGroup,
2584
- replace,
2585
- insert,
2586
- edit,
2587
- release,
2588
- withdraw,
2589
- drain,
2590
- failure: Deferred.await(stopped),
2591
- state: SubscriptionRef.get(stateRef),
2592
- asRun: Stream.fromPubSub(events),
2593
- cues: Stream.fromPubSub(cueEvents),
2594
- };
2595
- }).pipe(Effect.provideServiceEffect(Scope.Scope, Effect.flatMap(Scope.Scope, (parent) => Scope.fork(parent, "sequential"))));
2596
- export const layerScheduler = (options) => Layer.effect(Scheduler, makeScheduler(options));
2597
- //# sourceMappingURL=scheduler.js.map