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    fn register_types(generator: &mut crate::typegen::TypeGen) -> crate::typegen::Result {
228        generator.register_type::<Self>()?;
229        generator.register_type::<Self::Output>()?;
230        Ok(())
231    }
232}
233
234/// A type that can be used as a capability operation, but which will never be sent to the shell.
235/// This type is useful for capabilities that don't request effects.
236/// For example, you can use this type as the Operation for a
237/// capability that just composes other capabilities.
238///
239/// e.g.
240/// ```rust
241/// # use crux_core::capability::{CapabilityContext, Never};
242/// # use crux_core::macros::Capability;
243/// #[derive(Capability)]
244/// pub struct Compose<E> {
245///     context: CapabilityContext<Never, E>,
246/// }
247/// # impl<E> Compose<E> {
248/// #     pub fn new(context: CapabilityContext<Never, E>) -> Self {
249/// #         Self { context }
250/// #     }
251/// # }
252///
253/// ```
254#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
255pub enum Never {}
256
257/// Implement `Operation` for `Never` to allow using it as a capability operation.
258impl Operation for Never {
259    type Output = ();
260}
261
262/// Implement the `Capability` trait for your capability. This will allow
263/// mapping events when composing apps from submodules.
264///
265/// Note that this implementation can be generated by the `crux_core::macros::Capability` derive macro.
266///
267/// Example:
268///
269/// ```rust
270/// # use crux_core::{Capability, capability::{CapabilityContext, Operation}};
271/// # pub struct Http<Ev> {
272/// #     context: CapabilityContext<HttpRequest, Ev>,
273/// # }
274/// # #[derive(Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)] pub struct HttpRequest;
275/// # impl Operation for HttpRequest {
276/// #     type Output = ();
277/// # }
278/// # impl<Ev> Http<Ev> where Ev: 'static, {
279/// #     pub fn new(context: CapabilityContext<HttpRequest, Ev>) -> Self {
280/// #         Self { context }
281/// #     }
282/// # }
283/// impl<Ev> Capability<Ev> for Http<Ev> {
284///     type Operation = HttpRequest;
285///     type MappedSelf<MappedEv> = Http<MappedEv>;
286///
287///     fn map_event<F, NewEvent>(&self, f: F) -> Self::MappedSelf<NewEvent>
288///     where
289///         F: Fn(NewEvent) -> Ev + Send + Sync + 'static,
290///         Ev: 'static,
291///         NewEvent: 'static,
292///     {
293///         Http::new(self.context.map_event(f))
294///     }
295/// }
296/// ```
297pub trait Capability<Ev> {
298    type Operation: Operation + DeserializeOwned;
299
300    type MappedSelf<MappedEv>;
301
302    fn map_event<F, NewEv>(&self, f: F) -> Self::MappedSelf<NewEv>
303    where
304        F: Fn(NewEv) -> Ev + Send + Sync + 'static,
305        Ev: 'static,
306        NewEv: 'static + Send;
307}
308
309/// Allows Crux to construct app's set of required capabilities, providing context
310/// they can then use to request effects and dispatch events.
311///
312/// `new_with_context` is called by Crux and should return an instance of the app's `Capabilities` type with
313/// all capabilities constructed with context passed in. Use `Context::specialize` to
314/// create an appropriate context instance with the effect constructor which should
315/// wrap the requested operations.
316///
317/// Note that this implementation can be generated by the derive macro `crux_core::macros::Effect`.
318///
319/// ```rust
320/// # #[derive(Default)]
321/// # struct App;
322/// # pub enum Event {}
323/// # #[allow(dead_code)]
324/// # pub struct Capabilities {
325/// #     http: crux_http::Http<Event>,
326/// #     render: crux_core::render::Render<Event>,
327/// # }
328/// # pub enum Effect {
329/// #     Http(crux_core::Request<<crux_http::Http<Event> as crux_core::capability::Capability<Event>>::Operation>),
330/// #     Render(crux_core::Request<<crux_core::render::Render<Event> as crux_core::capability::Capability<Event>>::Operation>),
331/// # }
332/// # #[derive(serde::Serialize)]
333/// # pub enum EffectFfi {
334/// #     Http(<crux_http::Http<Event> as crux_core::capability::Capability<Event>>::Operation),
335/// #     Render(<crux_core::render::Render<Event> as crux_core::capability::Capability<Event>>::Operation),
336/// # }
337/// # impl crux_core::App for App {
338/// #     type Event = Event;
339/// #     type Model = ();
340/// #     type ViewModel = ();
341/// #     type Capabilities = Capabilities;
342/// #     type Effect = Effect;
343/// #     fn update(&self, _event: Self::Event, _model: &mut Self::Model, _caps: &Self::Capabilities) -> crux_core::Command<Effect, Event> {
344/// #         unimplemented!()
345/// #     }
346/// #     fn view(&self, _model: &Self::Model) -> Self::ViewModel {
347/// #         unimplemented!()
348/// #     }
349/// # }
350/// # impl crux_core::Effect for Effect {
351/// #     type Ffi = EffectFfi;
352/// #     fn serialize(self) -> (Self::Ffi, crux_core::bridge::ResolveSerialized) {
353/// #         match self {
354/// #             Effect::Http(request) => request.serialize(EffectFfi::Http),
355/// #             Effect::Render(request) => request.serialize(EffectFfi::Render),
356/// #         }
357/// #     }
358/// # }
359/// impl crux_core::WithContext<Event, Effect> for Capabilities {
360///     fn new_with_context(
361///         context: crux_core::capability::ProtoContext<Effect, Event>,
362///     ) -> Capabilities {
363///         Capabilities {
364///             http: crux_http::Http::new(context.specialize(Effect::Http)),
365///             render: crux_core::render::Render::new(context.specialize(Effect::Render)),
366///         }
367///     }
368/// }
369/// ```
370pub trait WithContext<Ev, Ef> {
371    fn new_with_context(context: ProtoContext<Ef, Ev>) -> Self;
372}
373
374impl<Event, Effect> WithContext<Event, Effect> for () {
375    fn new_with_context(_context: ProtoContext<Effect, Event>) -> Self {}
376}
377
378/// An interface for capabilities to interact with the app and the shell.
379///
380/// To use [`update_app`](CapabilityContext::update_app), [`notify_shell`](CapabilityContext::notify_shell)
381/// or [`request_from_shell`](CapabilityContext::request_from_shell), spawn a task first.
382///
383/// For example (from `crux_time`)
384///
385/// ```rust
386/// # #[derive(Clone, PartialEq, serde::Serialize, serde::Deserialize)] pub struct TimeRequest;
387/// # #[derive(Clone, serde::Deserialize)] pub struct TimeResponse(pub String);
388/// # impl crux_core::capability::Operation for TimeRequest {
389/// #     type Output = TimeResponse;
390/// # }
391/// # pub struct Time<Ev> {
392/// #     context: crux_core::capability::CapabilityContext<TimeRequest, Ev>,
393/// # }
394/// # impl<Ev> Time<Ev> where Ev: 'static, {
395/// #     pub fn new(context: crux_core::capability::CapabilityContext<TimeRequest, Ev>) -> Self {
396/// #         Self { context }
397/// #     }
398///
399/// pub fn get<F>(&self, callback: F)
400/// where
401///     F: FnOnce(TimeResponse) -> Ev + Send + Sync + 'static,
402/// {
403///     let ctx = self.context.clone();
404///     self.context.spawn(async move {
405///         let response = ctx.request_from_shell(TimeRequest).await;
406///
407///         ctx.update_app(callback(response));
408///     });
409/// }
410/// # }
411/// ```
412///
413// used in docs/internals/runtime.md
414// ANCHOR: capability_context
415pub struct CapabilityContext<Op, Event>
416where
417    Op: Operation,
418{
419    inner: std::sync::Arc<ContextInner<Op, Event>>,
420}
421
422struct ContextInner<Op, Event>
423where
424    Op: Operation,
425{
426    shell_channel: Sender<Request<Op>>,
427    app_channel: Sender<Event>,
428    spawner: executor::Spawner,
429}
430// ANCHOR_END: capability_context
431
432/// Initial version of capability Context which has not yet been specialized to a chosen capability
433pub struct ProtoContext<Eff, Event> {
434    shell_channel: Sender<Eff>,
435    app_channel: Sender<Event>,
436    spawner: executor::Spawner,
437}
438
439impl<Eff, Event> Clone for ProtoContext<Eff, Event> {
440    fn clone(&self) -> Self {
441        Self {
442            shell_channel: self.shell_channel.clone(),
443            app_channel: self.app_channel.clone(),
444            spawner: self.spawner.clone(),
445        }
446    }
447}
448
449// CommandSpawner is a temporary bridge between the channel type used by the Command and the channel type
450// used by the core. Once the old capability support is removed, we should be able to remove this in favour
451// of the Command's ability to be hosted on a pair of channels
452pub(crate) struct CommandSpawner<Effect, Event> {
453    context: ProtoContext<Effect, Event>,
454}
455
456impl<Effect, Event> CommandSpawner<Effect, Event> {
457    pub(crate) fn new(context: ProtoContext<Effect, Event>) -> Self {
458        Self { context }
459    }
460
461    pub(crate) fn spawn(&self, mut command: Command<Effect, Event>)
462    where
463        Command<Effect, Event>: Stream<Item = CommandOutput<Effect, Event>>,
464        Effect: Unpin + Send + 'static,
465        Event: Unpin + Send + 'static,
466    {
467        self.context.spawner.spawn({
468            let context = self.context.clone();
469
470            async move {
471                while let Some(output) = command.next().await {
472                    match output {
473                        CommandOutput::Effect(effect) => context.shell_channel.send(effect),
474                        CommandOutput::Event(event) => context.app_channel.send(event),
475                    }
476                }
477            }
478        });
479    }
480}
481
482impl<Op, Ev> Clone for CapabilityContext<Op, Ev>
483where
484    Op: Operation,
485{
486    fn clone(&self) -> Self {
487        Self {
488            inner: Arc::clone(&self.inner),
489        }
490    }
491}
492
493impl<Eff, Ev> ProtoContext<Eff, Ev>
494where
495    Ev: 'static,
496    Eff: 'static,
497{
498    pub(crate) fn new(
499        shell_channel: Sender<Eff>,
500        app_channel: Sender<Ev>,
501        spawner: executor::Spawner,
502    ) -> Self {
503        Self {
504            shell_channel,
505            app_channel,
506            spawner,
507        }
508    }
509
510    /// Specialize the CapabilityContext to a specific capability, wrapping its operations into
511    /// an Effect `Ef`. The `func` argument will typically be an Effect variant constructor, but
512    /// can be any function taking the capability's operation type and returning
513    /// the effect type.
514    ///
515    /// This will likely only be called from the implementation of [`WithContext`]
516    /// for the app's `Capabilities` type. You should not need to call this function directly.
517    pub fn specialize<Op, F>(&self, func: F) -> CapabilityContext<Op, Ev>
518    where
519        F: Fn(Request<Op>) -> Eff + Sync + Send + Copy + 'static,
520        Op: Operation,
521    {
522        CapabilityContext::new(
523            self.shell_channel.map_input(func),
524            self.app_channel.clone(),
525            self.spawner.clone(),
526        )
527    }
528}
529
530impl<Op, Ev> CapabilityContext<Op, Ev>
531where
532    Op: Operation,
533    Ev: 'static,
534{
535    pub(crate) fn new(
536        shell_channel: Sender<Request<Op>>,
537        app_channel: Sender<Ev>,
538        spawner: executor::Spawner,
539    ) -> Self {
540        let inner = Arc::new(ContextInner {
541            shell_channel,
542            app_channel,
543            spawner,
544        });
545
546        CapabilityContext { inner }
547    }
548
549    /// Spawn a task to do the asynchronous work. Within the task, async code
550    /// can be used to interact with the Shell and the App.
551    pub fn spawn(&self, f: impl Future<Output = ()> + 'static + Send) {
552        self.inner.spawner.spawn(f);
553    }
554
555    /// Send an effect request to the shell in a fire and forget fashion. The
556    /// provided `operation` does not expect anything to be returned back.
557    pub async fn notify_shell(&self, operation: Op) {
558        // This function might look like it doesn't need to be async but
559        // it's important that it is.  It forces all capabilities to
560        // spawn onto the executor which keeps the ordering of effects
561        // consistent with their function calls.
562        self.inner
563            .shell_channel
564            .send(Request::resolves_never(operation));
565    }
566
567    /// Send an event to the app. The event will be processed on the next
568    /// run of the update loop. You can call `update_app` several times,
569    /// the events will be queued up and processed sequentially after your
570    /// async task either `await`s or finishes.
571    pub fn update_app(&self, event: Ev) {
572        self.inner.app_channel.send(event);
573    }
574
575    /// Transform the CapabilityContext into one which uses the provided function to
576    /// map each event dispatched with `update_app` to a different event type.
577    ///
578    /// This is useful when composing apps from modules to wrap a submodule's
579    /// event type with a specific variant of the parent module's event, so it can
580    /// be forwarded to the submodule when received.
581    ///
582    /// In a typical case you would implement `From` on the submodule's `Capabilities` type
583    ///
584    /// ```rust
585    /// # use crux_core::{Capability, Command};
586    /// # #[derive(Default)]
587    /// # struct App;
588    /// # pub enum Event {
589    /// #     Submodule(child::Event),
590    /// # }
591    /// # #[derive(crux_core::macros::Effect)]
592    /// # pub struct Capabilities {
593    /// #     some_capability: crux_time::Time<Event>,
594    /// #     render: crux_core::render::Render<Event>,
595    /// # }
596    /// # impl crux_core::App for App {
597    /// #     type Event = Event;
598    /// #     type Model = ();
599    /// #     type ViewModel = ();
600    /// #     type Capabilities = Capabilities;
601    /// #     type Effect = Effect;
602    /// #     fn update(
603    /// #         &self,
604    /// #         _event: Self::Event,
605    /// #         _model: &mut Self::Model,
606    /// #         _caps: &Self::Capabilities,
607    /// #     ) -> Command<Effect, Event> {
608    /// #         unimplemented!()
609    /// #     }
610    /// #     fn view(&self, _model: &Self::Model) -> Self::ViewModel {
611    /// #         unimplemented!()
612    /// #     }
613    /// # }
614    ///impl From<&Capabilities> for child::Capabilities {
615    ///    fn from(incoming: &Capabilities) -> Self {
616    ///        child::Capabilities {
617    ///            some_capability: incoming.some_capability.map_event(Event::Submodule),
618    ///            render: incoming.render.map_event(Event::Submodule),
619    ///        }
620    ///    }
621    ///}
622    /// # mod child {
623    /// #     #[derive(Default)]
624    /// #     struct App;
625    /// #     pub struct Event;
626    /// #     #[derive(crux_core::macros::Effect)]
627    /// #     pub struct Capabilities {
628    /// #         pub some_capability: crux_time::Time<Event>,
629    /// #         pub render: crux_core::render::Render<Event>,
630    /// #     }
631    /// #     impl crux_core::App for App {
632    /// #         type Event = Event;
633    /// #         type Model = ();
634    /// #         type ViewModel = ();
635    /// #         type Capabilities = Capabilities;
636    /// #         type Effect = Effect;
637    /// #         fn update(
638    /// #             &self,
639    /// #             _event: Self::Event,
640    /// #             _model: &mut Self::Model,
641    /// #             _caps: &Self::Capabilities,
642    /// #         ) -> crux_core::Command<Effect, Event> {
643    /// #             unimplemented!()
644    /// #         }
645    /// #         fn view(&self, _model: &Self::Model) -> Self::ViewModel {
646    /// #             unimplemented!()
647    /// #         }
648    /// #     }
649    /// # }
650    /// ```
651    ///
652    /// in the parent module's `update` function, you can then call `.into()` on the
653    /// capabilities, before passing them down to the submodule.
654    pub fn map_event<NewEv, F>(&self, func: F) -> CapabilityContext<Op, NewEv>
655    where
656        F: Fn(NewEv) -> Ev + Sync + Send + 'static,
657        NewEv: 'static,
658    {
659        CapabilityContext::new(
660            self.inner.shell_channel.clone(),
661            self.inner.app_channel.map_input(func),
662            self.inner.spawner.clone(),
663        )
664    }
665
666    pub(crate) fn send_request(&self, request: Request<Op>) {
667        self.inner.shell_channel.send(request);
668    }
669}
670
671#[cfg(test)]
672mod tests {
673    use serde::{Deserialize, Serialize};
674    use static_assertions::assert_impl_all;
675
676    use super::*;
677
678    #[allow(dead_code)]
679    enum Effect {}
680
681    #[allow(dead_code)]
682    enum Event {}
683
684    #[derive(PartialEq, Clone, Serialize, Deserialize)]
685    struct Op {}
686
687    impl Operation for Op {
688        type Output = ();
689    }
690
691    assert_impl_all!(ProtoContext<Effect, Event>: Send, Sync);
692    assert_impl_all!(CapabilityContext<Op, Event>: Send, Sync);
693}