Skip to main content

resuma/
lib.rs

1//! # Resuma
2//!
3//! The first Rust web framework with **SSR + Resumability + Islands +
4//! Server Actions + a friendly JS bridge** — all in one crate.
5//!
6//! Internal layout: `core`, `ssr`, `server`, `router`, `flow`, and optional `cli`.
7//! Users typically depend on this crate only; `resuma-macros` is a separate proc-macro crate.
8
9pub mod core;
10pub mod flow;
11pub mod router;
12pub mod server;
13pub mod ssr;
14
15#[cfg(feature = "cli")]
16pub mod cli;
17
18pub use resuma_macros::{component, island, js, layout, load, middleware, server, submit, view};
19
20pub use crate::core::{
21    combine_js, nav_link, no_serialize, portal, provide_context, provide_theme, push_slots,
22    resolve_slot, stream_chunk, stream_slot, theme_css_vars, use_computed, use_context,
23    use_debounce, use_effect, use_signal, use_store, use_task, use_theme, use_visible_task,
24    visible_task_js, with_default_slot, with_view_transition, Child, Component, Computed,
25    ContextId, Effect, FlowRequest, IntoView, NoSerialize, ReadSignal, RenderContext, RenderMode,
26    Result, ResumaError, Signal, SlotGuard, SlottedChild, Store, Theme, View, WriteSignal,
27};
28
29pub use crate::server::{
30    configure_security, register_server_action, set_action_middleware, ResumaApp, SecurityConfig,
31    ServeOptions, CSRF_FIELD, CSRF_HEADER,
32};
33
34pub use crate::ssr::{render_to_stream, render_to_string, render_view, PageOptions};
35
36pub use crate::flow::{
37    apply_layouts, current_request, discover_pages, encode_submit_result, error_page, form,
38    not_found_page, register_layout, register_loader, register_loader_cache, register_middleware,
39    register_stream_chunk, register_stream_loader, register_submit, try_use_load,
40    try_use_load_value, use_load, with_request, DiscoveredPage, FlowApp, FlowError,
41    FlowPageRegistry, FlowPwaConfig, FlowServeOptions, LoadValue, LoaderError, SubmitError,
42    SubmitValue,
43};
44
45/// CLI entry point (`cargo install resuma`).
46#[cfg(feature = "cli")]
47pub fn run() -> anyhow::Result<()> {
48    crate::cli::run()
49}
50
51pub mod prelude {
52    //! Glob-friendly re-exports.
53    pub use super::{
54        combine_js, component, configure_security, current_request, error_page, form, island, js,
55        layout, load, middleware, nav_link, not_found_page, portal, provide_context, provide_theme,
56        push_slots, render_to_string, render_view, resolve_slot, server, set_action_middleware,
57        stream_slot, submit, theme_css_vars, try_use_load, try_use_load_value, use_computed,
58        use_context, use_debounce, use_effect, use_load, use_signal, use_store, use_task,
59        use_theme, use_visible_task, view, with_view_transition, Child, Component, Computed,
60        Effect, FlowApp, FlowError, FlowPageRegistry, FlowRequest, FlowServeOptions, IntoView,
61        LoadValue, LoaderError, PageOptions, ReadSignal, Result, ResumaApp, ResumaError,
62        SecurityConfig, ServeOptions, Signal, SlottedChild, Store, SubmitError, Theme, View,
63        WriteSignal, CSRF_FIELD, CSRF_HEADER,
64    };
65}
66
67#[doc(hidden)]
68pub mod __private {
69    //! Re-exports used by the macro-generated code.
70    pub use crate::core::{combine_js, nav_link};
71    pub use crate::core::{
72        context::{current_context, RenderContext, RenderMode},
73        handler::{HandlerCapture, HandlerRef},
74        signal::SignalId,
75        slot::{push_slots, resolve_slot, with_default_slot, SlottedChild},
76        view::{AttrValue, Element, Fragment, Island as IslandView},
77        Child, Component, IntoView, ReadSignal, Result, ResumaError, Signal, View, WriteSignal,
78    };
79    pub use crate::flow::form as flow_form;
80    pub use crate::server::register_server_action;
81    pub use ctor;
82    pub use serde_json;
83
84    #[derive(Debug, Clone)]
85    pub enum HandlerSource {
86        Inline(String),
87        Chunk {
88            chunk: String,
89            symbol: String,
90            source: String,
91        },
92    }
93
94    #[derive(Debug, Clone)]
95    pub enum ResumeCapture {
96        Signal { name: String, id: SignalId },
97        Action(String),
98    }
99
100    pub use crate::core::view::Element as ElementType;
101
102    pub fn register_handler(
103        event: &str,
104        chunk: &str,
105        symbol: &str,
106        js_source: &str,
107        captures: Vec<ResumeCapture>,
108        actions: Vec<String>,
109    ) -> AttrValue {
110        if let Some(ctx) = current_context() {
111            ctx.register_handler(chunk, symbol, js_source);
112            for a in &actions {
113                ctx.register_action(a);
114            }
115        }
116
117        let signal_captures: Vec<HandlerCapture> = captures
118            .into_iter()
119            .filter_map(|c| match c {
120                ResumeCapture::Signal { name, id } => Some(HandlerCapture { name, id }),
121                _ => None,
122            })
123            .collect();
124
125        AttrValue::Handler(HandlerRef {
126            event: event.to_string(),
127            chunk: chunk.to_string(),
128            symbol: symbol.to_string(),
129            captures: signal_captures,
130            inline: Some(js_source.to_string()),
131        })
132    }
133
134    pub trait ElementBuilderExt {
135        fn attr_runtime(self, kv: (String, AttrValue)) -> Self;
136    }
137
138    impl ElementBuilderExt for crate::core::view::ElementBuilder {
139        fn attr_runtime(self, (name, value): (String, AttrValue)) -> Self {
140            self.attr(name, value)
141        }
142    }
143
144    pub fn render_component<C: Component>(props: C::Props) -> View {
145        C::render(props)
146    }
147
148    pub fn resolve_attr_value<T: Into<AttrValueAuto>>(value: T) -> AttrValue {
149        value.into().into_attr_value()
150    }
151
152    pub struct AttrValueAuto(AttrValue);
153
154    impl AttrValueAuto {
155        fn into_attr_value(self) -> AttrValue {
156            self.0
157        }
158    }
159
160    impl From<&str> for AttrValueAuto {
161        fn from(s: &str) -> Self {
162            Self(AttrValue::Static(s.to_string()))
163        }
164    }
165    impl From<String> for AttrValueAuto {
166        fn from(s: String) -> Self {
167            Self(AttrValue::Static(s))
168        }
169    }
170    impl From<bool> for AttrValueAuto {
171        fn from(b: bool) -> Self {
172            Self(AttrValue::Static(b.to_string()))
173        }
174    }
175    impl From<i32> for AttrValueAuto {
176        fn from(n: i32) -> Self {
177            Self(AttrValue::Static(n.to_string()))
178        }
179    }
180    impl From<i64> for AttrValueAuto {
181        fn from(n: i64) -> Self {
182            Self(AttrValue::Static(n.to_string()))
183        }
184    }
185    impl From<u32> for AttrValueAuto {
186        fn from(n: u32) -> Self {
187            Self(AttrValue::Static(n.to_string()))
188        }
189    }
190    impl From<u64> for AttrValueAuto {
191        fn from(n: u64) -> Self {
192            Self(AttrValue::Static(n.to_string()))
193        }
194    }
195    impl From<f64> for AttrValueAuto {
196        fn from(n: f64) -> Self {
197            Self(AttrValue::Static(n.to_string()))
198        }
199    }
200
201    impl<T: Clone + serde::Serialize + 'static> From<&Signal<T>> for AttrValueAuto {
202        fn from(s: &Signal<T>) -> Self {
203            Self(AttrValue::Dynamic {
204                signal: s.id(),
205                format: None,
206            })
207        }
208    }
209    impl<T: Clone + serde::Serialize + 'static> From<Signal<T>> for AttrValueAuto {
210        fn from(s: Signal<T>) -> Self {
211            Self(AttrValue::Dynamic {
212                signal: s.id(),
213                format: None,
214            })
215        }
216    }
217
218    pub fn wrap_in_island(name: &str, instance: u32, view: View) -> View {
219        if let Some(ctx) = current_context() {
220            ctx.register_island(name);
221        }
222        View::Island(IslandView {
223            chunk_id: name.to_string(),
224            instance_id: format!("{}-{}", name, instance),
225            signal_ids: Vec::new(),
226            view: Box::new(view),
227            props: serde_json::Value::Null,
228        })
229    }
230
231    pub use crate::core::view as view_mod;
232    pub use crate::core::view::ElementBuilder;
233
234    pub fn fragment(children: Vec<Child>) -> View {
235        View::fragment(children)
236    }
237
238    pub fn element(tag: &str) -> ElementBuilder {
239        View::element(tag)
240    }
241}