Repository: redbadger/crux (language: rust)

Issue:
Delegate type registration to capabilities 
Related to #222.

Currently the type discovery for type generation is limited, and generally doesn't see into operation types in capabilities. One way around this would be to extend the `Capability` trait to support their own type generation - the type discovery machinery would simply call the trait method to ask the capability to register any relevant types.

While we're working on our own type generation that should superseed this, it may be a quick win to make this common paper cut less sharp.


Discussion / hints:

Fix patch:
diff --git a/crux_core/src/capabilities/compose.rs b/crux_core/src/capabilities/compose.rs
index 714a3ff7..cbbd599f 100644
--- a/crux_core/src/capabilities/compose.rs
+++ b/crux_core/src/capabilities/compose.rs
@@ -157,4 +157,14 @@ impl<Ev> Capability<Ev> for Compose<Ev> {
     {
         Compose::new(self.context.map_event(f))
     }
+
+    #[cfg(feature = "typegen")]
+    fn register_types(_generator: &mut crate::typegen::TypeGen) -> crate::typegen::Result {
+        panic!(
+            r#"
+            The Compose Capability should not be registered for type generation.
+            Instead, use #[effect(skip)] to skip the generation of an effect variant for the Compose Capability.
+            "#
+        )
+    }
 }
diff --git a/crux_core/src/capability/mod.rs b/crux_core/src/capability/mod.rs
index 8a8fd598..621c1786 100644
--- a/crux_core/src/capability/mod.rs
+++ b/crux_core/src/capability/mod.rs
@@ -193,6 +193,7 @@ mod shell_request;
 mod shell_stream;
 
 use futures::Future;
+use serde::de::DeserializeOwned;
 use std::sync::Arc;
 
 pub(crate) use channel::channel;
@@ -237,7 +238,6 @@ pub trait Operation: serde::Serialize + PartialEq + Send + 'static {
 /// # }
 ///
 /// ```
-
 #[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
 pub enum Never {}
 
@@ -256,19 +256,19 @@ impl Operation for Never {
 /// ```rust
 /// # use crux_core::{Capability, capability::{CapabilityContext, Operation}};
 /// # pub struct Http<Ev> {
-/// #     context: CapabilityContext<HttpOperation, Ev>,
+/// #     context: CapabilityContext<HttpRequest, Ev>,
 /// # }
-/// # #[derive(serde::Serialize, PartialEq, Eq)] pub struct HttpOperation;
-/// # impl Operation for HttpOperation {
+/// # #[derive(serde::Serialize, serde::Deserialize, PartialEq, Eq)] pub struct HttpRequest;
+/// # impl Operation for HttpRequest {
 /// #     type Output = ();
 /// # }
 /// # impl<Ev> Http<Ev> where Ev: 'static, {
-/// #     pub fn new(context: CapabilityContext<HttpOperation, Ev>) -> Self {
+/// #     pub fn new(context: CapabilityContext<HttpRequest, Ev>) -> Self {
 /// #         Self { context }
 /// #     }
 /// # }
 /// impl<Ev> Capability<Ev> for Http<Ev> {
-///     type Operation = HttpOperation;
+///     type Operation = HttpRequest;
 ///     type MappedSelf<MappedEv> = Http<MappedEv>;
 ///
 ///     fn map_event<F, NewEvent>(&self, f: F) -> Self::MappedSelf<NewEvent>
@@ -282,7 +282,7 @@ impl Operation for Never {
 /// }
 /// ```
 pub trait Capability<Ev> {
-    type Operation: Operation;
+    type Operation: Operation + DeserializeOwned;
 
     type MappedSelf<MappedEv>;
 
@@ -291,6 +291,13 @@ pub trait Capability<Ev> {
         F: Fn(NewEv) -> Ev + Send + Sync + 'static,
         Ev: 'static,
         NewEv: 'static + Send;
+
+    #[cfg(feature = "typegen")]
+    fn register_types(generator: &mut crate::typegen::TypeGen) -> crate::typegen::Result {
+        generator.register_type::<Self::Operation>()?;
+        generator.register_type::<<Self::Operation as Operation>::Output>()?;
+        Ok(())
+    }
 }
 
 /// Allows Crux to construct app's set of required capabilities, providing context
diff --git a/crux_macros/src/export.rs b/crux_macros/src/export.rs
index 56d3b212..4bacf453 100644
--- a/crux_macros/src/export.rs
+++ b/crux_macros/src/export.rs
@@ -47,14 +47,15 @@ impl ToTokens for ExportStructReceiver {
 
         for (capability, event) in fields.iter().map(|f| split_on_generic(&f.ty)) {
             output_type_exports.push(quote! {
-                generator.register_type::<<#capability<#event> as ::crux_core::capability::Capability<#event>>::Operation>()?;
-                generator
-                    .register_type::<<<#capability<#event> as ::crux_core::capability::Capability<#event>>::Operation as ::crux_core::capability::Operation>::Output>()?;
+                #capability::<#event>::register_types(generator)?;
             });
         }
 
         tokens.extend(quote! {
+            use ::crux_core::capability::Capability;
+
             impl ::crux_core::typegen::Export for #ident {
+                #[cfg(feature = "typegen")]
                 fn register_types(generator: &mut ::crux_core::typegen::TypeGen) -> ::crux_core::typegen::Result {
                     #(#output_type_exports)*
 
@@ -129,22 +130,13 @@ mod tests {
         let actual = quote!(#input);
 
         insta::assert_snapshot!(pretty_print(&actual), @r###"
+        use ::crux_core::capability::Capability;
         impl ::crux_core::typegen::Export for Capabilities {
+            #[cfg(feature = "typegen")]
             fn register_types(
                 generator: &mut ::crux_core::typegen::TypeGen,
             ) -> ::crux_core::typegen::Result {
-                generator
-                    .register_type::<
-                        <Render<Event> as ::crux_core::capability::Capability<Event>>::Operation,
-                    >()?;
-                generator
-                    .register_type::<
-                        <<Render<
-                            Event,
-                        > as ::crux_core::capability::Capability<
-                            Event,
-                        >>::Operation as ::crux_core::capability::Operation>::Output,
-                    >()?;
+                Render::<Event>::register_types(generator)?;
                 generator.register_type::<EffectFfi>()?;
                 generator.register_type::<::crux_core::bridge::Request<EffectFfi>>()?;
                 Ok(())
@@ -186,66 +178,16 @@ mod tests {
         let actual = quote!(#input);
 
         insta::assert_snapshot!(pretty_print(&actual), @r###"
+        use ::crux_core::capability::Capability;
         impl ::crux_core::typegen::Export for MyCapabilities {
+            #[cfg(feature = "typegen")]
             fn register_types(
                 generator: &mut ::crux_core::typegen::TypeGen,
             ) -> ::crux_core::typegen::Result {
-                generator
-                    .register_type::<
-                        <crux_http::Http<
-                            MyEvent,
-                        > as ::crux_core::capability::Capability<MyEvent>>::Operation,
-                    >()?;
-                generator
-                    .register_type::<
-                        <<crux_http::Http<
-                            MyEvent,
-                        > as ::crux_core::capability::Capability<
-                            MyEvent,
-                        >>::Operation as ::crux_core::capability::Operation>::Output,
-                    >()?;
-                generator
-                    .register_type::<
-                        <KeyValue<
-                            MyEvent,
-                        > as ::crux_core::capability::Capability<MyEvent>>::Operation,
-                    >()?;
-                generator
-                    .register_type::<
-                        <<KeyValue<
-                            MyEvent,
-                        > as ::crux_core::capability::Capability<
-                            MyEvent,
-                        >>::Operation as ::crux_core::capability::Operation>::Output,
-                    >()?;
-                generator
-                    .register_type::<
-                        <Platform<
-                            MyEvent,
-                        > as ::crux_core::capability::Capability<MyEvent>>::Operation,
-                    >()?;
-                generator
-                    .register_type::<
-                        <<Platform<
-                            MyEvent,
-                        > as ::crux_core::capability::Capability<
-                            MyEvent,
-                        >>::Operation as ::crux_core::capability::Operation>::Output,
-                    >()?;
-                generator
-                    .register_type::<
-                        <Render<
-                            MyEvent,
-                        > as ::crux_core::capability::Capability<MyEvent>>::Operation,
-                    >()?;
-                generator
-                    .register_type::<
-                        <<Render<
-                            MyEvent,
-                        > as ::crux_core::capability::Capability<
-                            MyEvent,
-                        >>::Operation as ::crux_core::capability::Operation>::Output,
-                    >()?;
+                crux_http::Http::<MyEvent>::register_types(generator)?;
+                KeyValue::<MyEvent>::register_types(generator)?;
+                Platform::<MyEvent>::register_types(generator)?;
+                Render::<MyEvent>::register_types(generator)?;
                 generator.register_type::<EffectFfi>()?;
                 generator.register_type::<::crux_core::bridge::Request<EffectFfi>>()?;
                 Ok(())
@@ -272,80 +214,17 @@ mod tests {
         let actual = quote!(#input);
 
         insta::assert_snapshot!(pretty_print(&actual), @r###"
+        use ::crux_core::capability::Capability;
         impl ::crux_core::typegen::Export for MyCapabilities {
+            #[cfg(feature = "typegen")]
             fn register_types(
                 generator: &mut ::crux_core::typegen::TypeGen,
             ) -> ::crux_core::typegen::Result {
-                generator
-                    .register_type::<
-                        <crux_http::Http<
-                            MyEvent,
-                        > as ::crux_core::capability::Capability<MyEvent>>::Operation,
-                    >()?;
-                generator
-                    .register_type::<
-                        <<crux_http::Http<
-                            MyEvent,
-                        > as ::crux_core::capability::Capability<
-                            MyEvent,
-                        >>::Operation as ::crux_core::capability::Operation>::Output,
-                    >()?;
-                generator
-                    .register_type::<
-                        <KeyValue<
-                            MyEvent,
-                        > as ::crux_core::capability::Capability<MyEvent>>::Operation,
-                    >()?;
-                generator
-                    .register_type::<
-                        <<KeyValue<
-                            MyEvent,
-                        > as ::crux_core::capability::Capability<
-                            MyEvent,
-                        >>::Operation as ::crux_core::capability::Operation>::Output,
-                    >()?;
-                generator
-                    .register_type::<
-                        <Platform<
-                            MyEvent,
-                        > as ::crux_core::capability::Capability<MyEvent>>::Operation,
-                    >()?;
-                generator
-                    .register_type::<
-                        <<Platform<
-                            MyEvent,
-                        > as ::crux_core::capability::Capability<
-                            MyEvent,
-                        >>::Operation as ::crux_core::capability::Operation>::Output,
-                    >()?;
-                generator
-                    .register_type::<
-                        <Render<
-                            MyEvent,
-                        > as ::crux_core::capability::Capability<MyEvent>>::Operation,
-                    >()?;
-                generator
-                    .register_type::<
-                        <<Render<
-                            MyEvent,
-                        > as ::crux_core::capability::Capability<
-                            MyEvent,
-                        >>::Operation as ::crux_core::capability::Operation>::Output,
-                    >()?;
-                generator
-                    .register_type::<
-                        <Time<
-                            MyEvent,
-                        > as ::crux_core::capability::Capability<MyEvent>>::Operation,
-                    >()?;
-                generator
-                    .register_type::<
-                        <<Time<
-                            MyEvent,
-                        > as ::crux_core::capability::Capability<
-                            MyEvent,
-                        >>::Operation as ::crux_core::capability::Operation>::Output,
-                    >()?;
+                crux_http::Http::<MyEvent>::register_types(generator)?;
+                KeyValue::<MyEvent>::register_types(generator)?;
+                Platform::<MyEvent>::register_types(generator)?;
+                Render::<MyEvent>::register_types(generator)?;
+                Time::<MyEvent>::register_types(generator)?;
                 generator.register_type::<EffectFfi>()?;
                 generator.register_type::<::crux_core::bridge::Request<EffectFfi>>()?;
                 Ok(())
@@ -370,22 +249,13 @@ mod tests {
         let actual = quote!(#input);
 
         insta::assert_snapshot!(pretty_print(&actual), @r###"
+        use ::crux_core::capability::Capability;
         impl ::crux_core::typegen::Export for Capabilities {
+            #[cfg(feature = "typegen")]
             fn register_types(
                 generator: &mut ::crux_core::typegen::TypeGen,
             ) -> ::crux_core::typegen::Result {
-                generator
-                    .register_type::<
-                        <Render<Event> as ::crux_core::capability::Capability<Event>>::Operation,
-                    >()?;
-                generator
-                    .register_type::<
-                        <<Render<
-                            Event,
-                        > as ::crux_core::capability::Capability<
-                            Event,
-                        >>::Operation as ::crux_core::capability::Operation>::Output,
-                    >()?;
+                Render::<Event>::register_types(generator)?;
                 generator.register_type::<MyEffectFfi>()?;
                 generator.register_type::<::crux_core::bridge::Request<MyEffectFfi>>()?;
                 Ok(())
diff --git a/examples/counter/web-nextjs/package.json b/examples/counter/web-nextjs/package.json
index c7a1a2a9..61574650 100644
--- a/examples/counter/web-nextjs/package.json
+++ b/examples/counter/web-nextjs/package.json
@@ -24,5 +24,6 @@
     "@types/node": "20.14.9",
     "@types/react": "18.3.3",
     "@types/react-dom": "18.3.0"
-  }
+  },
+  "packageManager": "pnpm@7.28.0+sha1.48be3292711ad554fe944f6caaf9b0f5477524fe"
 }


Test patch: