crux_core/capability/
mod.rs

1//! Capabilities provide a user-friendly API to request side-effects from the shell.
2//!
3//! Typically, capabilities provide I/O and host API access. Capabilities are external to the
4//! core Crux library. Some are part of the Crux core distribution, others are expected to be built by the
5//! community. Apps can also build single-use capabilities where necessary.
6//!
7//! # Example use
8//!
9//! A typical use of a capability would look like the following:
10//!
11//! ```rust
12//!# use url::Url;
13//!# use crux_core::Command;
14//!# const API_URL: &str = "";
15//!# pub enum Event { Increment, Set(crux_http::Result<crux_http::Response<usize>>) }
16//!# #[derive(crux_core::macros::Effect)]
17//!# pub struct Capabilities {
18//!#     pub render: crux_core::render::Render<Event>,
19//!#     pub http: crux_http::Http<Event>,
20//!# }
21//!# #[derive(Default)] pub struct Model { count: usize }
22//!# #[derive(Default)] pub struct App;
23//!#
24//!# impl crux_core::App for App {
25//!#     type Event = Event;
26//!#     type Model = Model;
27//!#     type ViewModel = ();
28//!#     type Capabilities = Capabilities;
29//!#     type Effect = Effect;
30//! fn update(&self, event: Self::Event, model: &mut Self::Model, caps: &Self::Capabilities) -> Command<Effect, Event> {
31//!     match event {
32//!         //...
33//!         Event::Increment => {
34//!             model.count += 1;
35//!             caps.render.render(); // Render capability
36//!
37//!             let base = Url::parse(API_URL).unwrap();
38//!             let url = base.join("/inc").unwrap();
39//!             caps.http.post(url).expect_json().send(Event::Set); // HTTP client capability
40//!         }
41//!         Event::Set(_) => todo!(),
42//!     }
43//!     Command::done()
44//! }
45//!# fn view(&self, model: &Self::Model) {
46//!#     unimplemented!()
47//!# }
48//!# }
49
50//! ```
51//!
52//! Capabilities don't _perform_ side-effects themselves, they request them from the Shell. As a consequence
53//! the capability calls within the `update` function **only queue up the requests**. The side-effects themselves
54//! are performed concurrently and don't block the update function.
55//!
56//! In order to use a capability, the app needs to include it in its `Capabilities` associated type and `WithContext`
57//! trait implementation (which can be provided by the `crux_core::macros::Effect` macro). For example:
58//!
59//! ```rust
60//! mod root {
61//!
62//! // An app module which can be reused in different apps
63//! mod my_app {
64//!     use crux_core::{capability::CapabilityContext, App, render::{self, Render, RenderOperation}, Command, Request};
65//!     use crux_core::macros::Effect;
66//!     use serde::{Serialize, Deserialize};
67//!
68//!     #[derive(Default)]
69//!     pub struct MyApp;
70//!     #[derive(Serialize, Deserialize)]
71//!     pub struct Event;
72//!
73//!     // The `Effect` derive macro generates an `Effect` type that is used by the
74//!     // Shell to dispatch side-effect requests to the right capability implementation
75//!     // (and, in some languages, checking that all necessary capabilities are implemented)
76//!     #[derive(Effect)]
77//!     pub struct Capabilities {
78//!         pub render: Render<Event>
79//!     }
80//!
81//!     impl App for MyApp {
82//!         type Model = ();
83//!         type Event = Event;
84//!         type ViewModel = ();
85//!         type Capabilities = Capabilities;
86//!         type Effect = Effect;
87//!
88//!         fn update(&self, event: Event, model: &mut (), caps: &Capabilities) -> Command<Effect, Event> {
89//!             render::render()
90//!         }
91//!
92//!         fn view(&self, model: &()) {
93//!             ()
94//!         }
95//!     }
96//! }
97//! }
98//! ```
99//!
100//! # Implementing a capability
101//!
102//! Capabilities provide an interface to request side-effects. The interface has asynchronous semantics
103//! with a form of callback. A typical capability call can look like this:
104//!
105//! ```rust,ignore
106//! caps.ducks.get_in_a_row(10, Event::RowOfDucks)
107//! ```
108//!
109//! The call above translates into "Get 10 ducks in a row and return them to me using the `RowOfDucks` event".
110//! The capability's job is to translate this request into a serializable message and instruct the Shell to
111//! do the duck herding and when it receives the ducks back, wrap them in the requested event and return it
112//! to the app.
113//!
114//! We will refer to `get_in_row` in the above call as an _operation_, the `10` is an _input_, and the
115//! `Event::RowOfDucks` is an event constructor - a function, which eventually receives the row of ducks
116//! and returns a variant of the `Event` enum. Conveniently, enum tuple variants can be used as functions,
117//! and so that will be the typical use.
118//!
119//! This is what the capability implementation could look like:
120//!
121//! ```rust
122//! use crux_core::{
123//!     capability::{CapabilityContext, Operation},
124//! };
125//! use crux_core::macros::Capability;
126//! use serde::{Serialize, Deserialize};
127//!
128//! // A duck
129//! #[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
130//! struct Duck;
131//!
132//! // Operations that can be requested from the Shell
133//! #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
134//! enum DuckOperation {
135//!     GetInARow(usize)
136//! }
137//!
138//! // Respective outputs for those operations
139//! #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
140//! enum DuckOutput {
141//!     GetInRow(Vec<Duck>)
142//! }
143//!
144//! // Link the input and output type
145//! impl Operation for DuckOperation {
146//!     type Output = DuckOutput;
147//! }
148//!
149//! // The capability. Context will provide the interface to the rest of the system.
150//! #[derive(Capability)]
151//! struct Ducks<Event> {
152//!     context: CapabilityContext<DuckOperation, Event>
153//! };
154//!
155//! impl<Event> Ducks<Event> {
156//!     pub fn new(context: CapabilityContext<DuckOperation, Event>) -> Self {
157//!         Self { context }
158//!     }
159//!
160//!     pub fn get_in_a_row<F>(&self, number_of_ducks: usize, event: F)
161//!     where
162//!         Event: 'static,
163//!         F: FnOnce(Vec<Duck>) -> Event + Send + 'static,
164//!     {
165//!         let ctx = self.context.clone();
166//!         // Start a shell interaction
167//!         self.context.spawn(async move {
168//!             // Instruct Shell to get ducks in a row and await the ducks
169//!             let ducks = ctx.request_from_shell(DuckOperation::GetInARow(number_of_ducks)).await;
170//!
171//!             // Unwrap the ducks and wrap them in the requested event
172//!             // This will always succeed, as long as the Shell implementation is correct
173//!             // and doesn't send the wrong output type back
174//!             if let DuckOutput::GetInRow(ducks) = ducks {
175//!                 // Queue an app update with the ducks event
176//!                 ctx.update_app(event(ducks));
177//!             }
178//!         })
179//!    }
180//! }
181//! ```
182//!
183//! The `self.context.spawn` API allows a multi-step transaction with the Shell to be performed by a capability
184//! without involving the app, until the exchange has completed. During the exchange, one or more events can
185//! be emitted (allowing a subscription or streaming like capability to be built).
186//!
187//! For Shell requests that have no output, you can use [`CapabilityContext::notify_shell`].
188//!
189//! `DuckOperation` and `DuckOutput` show how the set of operations can be extended. In simple capabilities,
190//! with a single operation, these can be structs, or simpler types. For example, the HTTP capability works directly with
191//! `HttpRequest` and `HttpResponse`.
192
193pub(crate) mod channel;
194
195mod executor;
196mod shell_request;
197mod shell_stream;
198
199use futures::{Future, Stream, StreamExt as _};
200use serde::de::DeserializeOwned;
201use std::sync::Arc;
202
203pub(crate) use channel::channel;
204pub(crate) use executor::{executor_and_spawner, QueuingExecutor};
205
206use crate::{command::CommandOutput, Command, Request};
207use channel::Sender;
208
209/// Operation trait links together input and output of a side-effect.
210///
211/// You implement `Operation` on the payload sent by the capability to the shell using [`CapabilityContext::request_from_shell`].
212///
213/// For example (from `crux_http`):
214///
215/// ```rust,ignore
216/// impl Operation for HttpRequest {
217///     type Output = HttpResponse;
218/// }
219/// ```
220pub trait Operation:
221    serde::Serialize + serde::de::DeserializeOwned + Clone + PartialEq + Send + 'static
222{
223    /// `Output` assigns the type this request results in.
224    type Output: serde::de::DeserializeOwned + Send + Unpin + 'static;
225
226    #[cfg(feature = "typegen")]
227    #[allow(clippy::missing_errors_doc)]
228    fn register_types(generator: &mut crate::typegen::TypeGen) -> crate::typegen::Result {
229        generator.register_type::<Self>()?;
230        generator.register_type::<Self::Output>()?;
231        Ok(())
232    }
233}
234
235/// A type that can be used as a capability operation, but which will never be sent to the shell.
236/// This type is useful for capabilities that don't request effects.
237/// For example, you can use this type as the Operation for a
238/// capability that just composes other capabilities.
239///
240/// e.g.
241/// ```rust
242/// # use crux_core::capability::{CapabilityContext, Never};
243/// # use crux_core::macros::Capability;
244/// #[derive(Capability)]
245/// pub struct Compose<E> {
246///     context: CapabilityContext<Never, E>,
247/// }
248/// # impl<E> Compose<E> {
249/// #     pub fn new(context: CapabilityContext<Never, E>) -> Self {
250/// #         Self { context }
251/// #     }
252/// # }
253///
254/// ```
255#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
256pub enum Never {}
257
258/// Implement `Operation` for `Never` to allow using it as a capability operation.
259impl Operation for Never {
260    type Output = ();
261}
262
263/// Implement the `Capability` trait for your capability. This will allow
264/// mapping events when composing apps from submodules.
265///
266/// Note that this implementation can be generated by the `crux_core::macros::Capability` derive macro.
267///
268/// Example:
269///
270/// ```rust
271/// # use crux_core::{Capability, capability::{CapabilityContext, Operation}};
272/// # pub struct Http<Ev> {
273/// #     context: CapabilityContext<HttpRequest, Ev>,
274/// # }
275/// # #[derive(Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)] pub struct HttpRequest;
276/// # impl Operation for HttpRequest {
277/// #     type Output = ();
278/// # }
279/// # impl<Ev> Http<Ev> where Ev: 'static, {
280/// #     pub fn new(context: CapabilityContext<HttpRequest, Ev>) -> Self {
281/// #         Self { context }
282/// #     }
283/// # }
284/// impl<Ev> Capability<Ev> for Http<Ev> {
285///     type Operation = HttpRequest;
286///     type MappedSelf<MappedEv> = Http<MappedEv>;
287///
288///     fn map_event<F, NewEvent>(&self, f: F) -> Self::MappedSelf<NewEvent>
289///     where
290///         F: Fn(NewEvent) -> Ev + Send + Sync + 'static,
291///         Ev: 'static,
292///         NewEvent: 'static,
293///     {
294///         Http::new(self.context.map_event(f))
295///     }
296/// }
297/// ```
298pub trait Capability<Ev> {
299    type Operation: Operation + DeserializeOwned;
300
301    type MappedSelf<MappedEv>;
302
303    fn map_event<F, NewEv>(&self, f: F) -> Self::MappedSelf<NewEv>
304    where
305        F: Fn(NewEv) -> Ev + Send + Sync + 'static,
306        Ev: 'static,
307        NewEv: 'static + Send;
308}
309
310/// Allows Crux to construct app's set of required capabilities, providing context
311/// they can then use to request effects and dispatch events.
312///
313/// `new_with_context` is called by Crux and should return an instance of the app's `Capabilities` type with
314/// all capabilities constructed with context passed in. Use `Context::specialize` to
315/// create an appropriate context instance with the effect constructor which should
316/// wrap the requested operations.
317///
318/// Note that this implementation can be generated by the derive macro `crux_core::macros::Effect`.
319///
320/// ```rust
321/// # #[derive(Default)]
322/// # struct App;
323/// # pub enum Event {}
324/// # #[allow(dead_code)]
325/// # pub struct Capabilities {
326/// #     http: crux_http::Http<Event>,
327/// #     render: crux_core::render::Render<Event>,
328/// # }
329/// # pub enum Effect {
330/// #     Http(crux_core::Request<<crux_http::Http<Event> as crux_core::capability::Capability<Event>>::Operation>),
331/// #     Render(crux_core::Request<<crux_core::render::Render<Event> as crux_core::capability::Capability<Event>>::Operation>),
332/// # }
333/// # #[derive(serde::Serialize)]
334/// # pub enum EffectFfi {
335/// #     Http(<crux_http::Http<Event> as crux_core::capability::Capability<Event>>::Operation),
336/// #     Render(<crux_core::render::Render<Event> as crux_core::capability::Capability<Event>>::Operation),
337/// # }
338/// # impl crux_core::App for App {
339/// #     type Event = Event;
340/// #     type Model = ();
341/// #     type ViewModel = ();
342/// #     type Capabilities = Capabilities;
343/// #     type Effect = Effect;
344/// #     fn update(&self, _event: Self::Event, _model: &mut Self::Model, _caps: &Self::Capabilities) -> crux_core::Command<Effect, Event> {
345/// #         unimplemented!()
346/// #     }
347/// #     fn view(&self, _model: &Self::Model) -> Self::ViewModel {
348/// #         unimplemented!()
349/// #     }
350/// # }
351/// # impl crux_core::Effect for Effect {
352/// #     type Ffi = EffectFfi;
353/// #     fn serialize(self) -> (Self::Ffi, crux_core::bridge::ResolveSerialized) {
354/// #         match self {
355/// #             Effect::Http(request) => request.serialize(EffectFfi::Http),
356/// #             Effect::Render(request) => request.serialize(EffectFfi::Render),
357/// #         }
358/// #     }
359/// # }
360/// impl crux_core::WithContext<Event, Effect> for Capabilities {
361///     fn new_with_context(
362///         context: crux_core::capability::ProtoContext<Effect, Event>,
363///     ) -> Capabilities {
364///         Capabilities {
365///             http: crux_http::Http::new(context.specialize(Effect::Http)),
366///             render: crux_core::render::Render::new(context.specialize(Effect::Render)),
367///         }
368///     }
369/// }
370/// ```
371pub trait WithContext<Ev, Ef> {
372    fn new_with_context(context: ProtoContext<Ef, Ev>) -> Self;
373}
374
375impl<Event, Effect> WithContext<Event, Effect> for () {
376    fn new_with_context(_context: ProtoContext<Effect, Event>) -> Self {}
377}
378
379/// An interface for capabilities to interact with the app and the shell.
380///
381/// To use [`update_app`](CapabilityContext::update_app), [`notify_shell`](CapabilityContext::notify_shell)
382/// or [`request_from_shell`](CapabilityContext::request_from_shell), spawn a task first.
383///
384/// For example (from `crux_time`)
385///
386/// ```rust
387/// # #[derive(Clone, PartialEq, serde::Serialize, serde::Deserialize)] pub struct TimeRequest;
388/// # #[derive(Clone, serde::Deserialize)] pub struct TimeResponse(pub String);
389/// # impl crux_core::capability::Operation for TimeRequest {
390/// #     type Output = TimeResponse;
391/// # }
392/// # pub struct Time<Ev> {
393/// #     context: crux_core::capability::CapabilityContext<TimeRequest, Ev>,
394/// # }
395/// # impl<Ev> Time<Ev> where Ev: 'static, {
396/// #     pub fn new(context: crux_core::capability::CapabilityContext<TimeRequest, Ev>) -> Self {
397/// #         Self { context }
398/// #     }
399///
400/// pub fn get<F>(&self, callback: F)
401/// where
402///     F: FnOnce(TimeResponse) -> Ev + Send + Sync + 'static,
403/// {
404///     let ctx = self.context.clone();
405///     self.context.spawn(async move {
406///         let response = ctx.request_from_shell(TimeRequest).await;
407///
408///         ctx.update_app(callback(response));
409///     });
410/// }
411/// # }
412/// ```
413///
414// used in docs/internals/runtime.md
415// ANCHOR: capability_context
416pub struct CapabilityContext<Op, Event>
417where
418    Op: Operation,
419{
420    inner: std::sync::Arc<ContextInner<Op, Event>>,
421}
422
423struct ContextInner<Op, Event>
424where
425    Op: Operation,
426{
427    shell_channel: Sender<Request<Op>>,
428    app_channel: Sender<Event>,
429    spawner: executor::Spawner,
430}
431// ANCHOR_END: capability_context
432
433/// Initial version of capability Context which has not yet been specialized to a chosen capability
434pub struct ProtoContext<Eff, Event> {
435    shell_channel: Sender<Eff>,
436    app_channel: Sender<Event>,
437    spawner: executor::Spawner,
438}
439
440impl<Eff, Event> Clone for ProtoContext<Eff, Event> {
441    fn clone(&self) -> Self {
442        Self {
443            shell_channel: self.shell_channel.clone(),
444            app_channel: self.app_channel.clone(),
445            spawner: self.spawner.clone(),
446        }
447    }
448}
449
450// CommandSpawner is a temporary bridge between the channel type used by the Command and the channel type
451// used by the core. Once the old capability support is removed, we should be able to remove this in favour
452// of the Command's ability to be hosted on a pair of channels
453pub(crate) struct CommandSpawner<Effect, Event> {
454    context: ProtoContext<Effect, Event>,
455}
456
457impl<Effect, Event> CommandSpawner<Effect, Event> {
458    pub(crate) fn new(context: ProtoContext<Effect, Event>) -> Self {
459        Self { context }
460    }
461
462    pub(crate) fn spawn(&self, mut command: Command<Effect, Event>)
463    where
464        Command<Effect, Event>: Stream<Item = CommandOutput<Effect, Event>>,
465        Effect: Unpin + Send + 'static,
466        Event: Unpin + Send + 'static,
467    {
468        self.context.spawner.spawn({
469            let context = self.context.clone();
470
471            async move {
472                while let Some(output) = command.next().await {
473                    match output {
474                        CommandOutput::Effect(effect) => context.shell_channel.send(effect),
475                        CommandOutput::Event(event) => context.app_channel.send(event),
476                    }
477                }
478            }
479        });
480    }
481}
482
483impl<Op, Ev> Clone for CapabilityContext<Op, Ev>
484where
485    Op: Operation,
486{
487    fn clone(&self) -> Self {
488        Self {
489            inner: Arc::clone(&self.inner),
490        }
491    }
492}
493
494impl<Eff, Ev> ProtoContext<Eff, Ev>
495where
496    Ev: 'static,
497    Eff: 'static,
498{
499    pub(crate) fn new(
500        shell_channel: Sender<Eff>,
501        app_channel: Sender<Ev>,
502        spawner: executor::Spawner,
503    ) -> Self {
504        Self {
505            shell_channel,
506            app_channel,
507            spawner,
508        }
509    }
510
511    /// Specialize the `CapabilityContext` to a specific capability, wrapping its operations into
512    /// an Effect `Ef`. The `func` argument will typically be an Effect variant constructor, but
513    /// can be any function taking the capability's operation type and returning
514    /// the effect type.
515    ///
516    /// This will likely only be called from the implementation of [`WithContext`]
517    /// for the app's `Capabilities` type. You should not need to call this function directly.
518    pub fn specialize<Op, F>(&self, func: F) -> CapabilityContext<Op, Ev>
519    where
520        F: Fn(Request<Op>) -> Eff + Sync + Send + Copy + 'static,
521        Op: Operation,
522    {
523        CapabilityContext::new(
524            self.shell_channel.map_input(func),
525            self.app_channel.clone(),
526            self.spawner.clone(),
527        )
528    }
529}
530
531impl<Op, Ev> CapabilityContext<Op, Ev>
532where
533    Op: Operation,
534    Ev: 'static,
535{
536    pub(crate) fn new(
537        shell_channel: Sender<Request<Op>>,
538        app_channel: Sender<Ev>,
539        spawner: executor::Spawner,
540    ) -> Self {
541        let inner = Arc::new(ContextInner {
542            shell_channel,
543            app_channel,
544            spawner,
545        });
546
547        CapabilityContext { inner }
548    }
549
550    /// Spawn a task to do the asynchronous work. Within the task, async code
551    /// can be used to interact with the Shell and the App.
552    pub fn spawn(&self, f: impl Future<Output = ()> + 'static + Send) {
553        self.inner.spawner.spawn(f);
554    }
555
556    /// Send an effect request to the shell in a fire and forget fashion. The
557    /// provided `operation` does not expect anything to be returned back.
558    #[allow(clippy::unused_async)]
559    pub async fn notify_shell(&self, operation: Op) {
560        // This function might look like it doesn't need to be async but
561        // it's important that it is.  It forces all capabilities to
562        // spawn onto the executor which keeps the ordering of effects
563        // consistent with their function calls.
564        self.inner
565            .shell_channel
566            .send(Request::resolves_never(operation));
567    }
568
569    /// Send an event to the app. The event will be processed on the next
570    /// run of the update loop. You can call `update_app` several times,
571    /// the events will be queued up and processed sequentially after your
572    /// async task either `await`s or finishes.
573    pub fn update_app(&self, event: Ev) {
574        self.inner.app_channel.send(event);
575    }
576
577    /// Transform the `CapabilityContext` into one which uses the provided function to
578    /// map each event dispatched with `update_app` to a different event type.
579    ///
580    /// This is useful when composing apps from modules to wrap a submodule's
581    /// event type with a specific variant of the parent module's event, so it can
582    /// be forwarded to the submodule when received.
583    ///
584    /// In a typical case you would implement `From` on the submodule's `Capabilities` type
585    ///
586    /// ```rust
587    /// # use crux_core::{Capability, Command};
588    /// # #[derive(Default)]
589    /// # struct App;
590    /// # pub enum Event {
591    /// #     Submodule(child::Event),
592    /// # }
593    /// # #[derive(crux_core::macros::Effect)]
594    /// # pub struct Capabilities {
595    /// #     some_capability: crux_time::Time<Event>,
596    /// #     render: crux_core::render::Render<Event>,
597    /// # }
598    /// # impl crux_core::App for App {
599    /// #     type Event = Event;
600    /// #     type Model = ();
601    /// #     type ViewModel = ();
602    /// #     type Capabilities = Capabilities;
603    /// #     type Effect = Effect;
604    /// #     fn update(
605    /// #         &self,
606    /// #         _event: Self::Event,
607    /// #         _model: &mut Self::Model,
608    /// #         _caps: &Self::Capabilities,
609    /// #     ) -> Command<Effect, Event> {
610    /// #         unimplemented!()
611    /// #     }
612    /// #     fn view(&self, _model: &Self::Model) -> Self::ViewModel {
613    /// #         unimplemented!()
614    /// #     }
615    /// # }
616    ///impl From<&Capabilities> for child::Capabilities {
617    ///    fn from(incoming: &Capabilities) -> Self {
618    ///        child::Capabilities {
619    ///            some_capability: incoming.some_capability.map_event(Event::Submodule),
620    ///            render: incoming.render.map_event(Event::Submodule),
621    ///        }
622    ///    }
623    ///}
624    /// # mod child {
625    /// #     #[derive(Default)]
626    /// #     struct App;
627    /// #     pub struct Event;
628    /// #     #[derive(crux_core::macros::Effect)]
629    /// #     pub struct Capabilities {
630    /// #         pub some_capability: crux_time::Time<Event>,
631    /// #         pub render: crux_core::render::Render<Event>,
632    /// #     }
633    /// #     impl crux_core::App for App {
634    /// #         type Event = Event;
635    /// #         type Model = ();
636    /// #         type ViewModel = ();
637    /// #         type Capabilities = Capabilities;
638    /// #         type Effect = Effect;
639    /// #         fn update(
640    /// #             &self,
641    /// #             _event: Self::Event,
642    /// #             _model: &mut Self::Model,
643    /// #             _caps: &Self::Capabilities,
644    /// #         ) -> crux_core::Command<Effect, Event> {
645    /// #             unimplemented!()
646    /// #         }
647    /// #         fn view(&self, _model: &Self::Model) -> Self::ViewModel {
648    /// #             unimplemented!()
649    /// #         }
650    /// #     }
651    /// # }
652    /// ```
653    ///
654    /// in the parent module's `update` function, you can then call `.into()` on the
655    /// capabilities, before passing them down to the submodule.
656    pub fn map_event<NewEv, F>(&self, func: F) -> CapabilityContext<Op, NewEv>
657    where
658        F: Fn(NewEv) -> Ev + Sync + Send + 'static,
659        NewEv: 'static,
660    {
661        CapabilityContext::new(
662            self.inner.shell_channel.clone(),
663            self.inner.app_channel.map_input(func),
664            self.inner.spawner.clone(),
665        )
666    }
667
668    pub(crate) fn send_request(&self, request: Request<Op>) {
669        self.inner.shell_channel.send(request);
670    }
671}
672
673#[cfg(test)]
674mod tests {
675    use serde::{Deserialize, Serialize};
676    use static_assertions::assert_impl_all;
677
678    use super::*;
679
680    #[allow(dead_code)]
681    enum Effect {}
682
683    #[allow(dead_code)]
684    enum Event {}
685
686    #[derive(PartialEq, Clone, Serialize, Deserialize)]
687    struct Op {}
688
689    impl Operation for Op {
690        type Output = ();
691    }
692
693    assert_impl_all!(ProtoContext<Effect, Event>: Send, Sync);
694    assert_impl_all!(CapabilityContext<Op, Event>: Send, Sync);
695}