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