Datastar's wire, once: the events a server sends, the signals it reads, and the script a page loads.
No I/O and no runtime. An event is built here and written out as text ([PatchElements::sse],
[PatchSignals::sse]); an adapter for a server (the Worker's, axum's) puts that text on a response.
Datastar's own types are not used, so nothing here moves when that crate does.
The script a page loads. Datastar 1.0.4, the same bytes in every property.
11pub const SCRIPT: &str = include_str!("../datastar.js");
The query parameter a GET request carries its signals in, as JSON.
14pub const QUERY_PARAMETER: &str = "datastar";
The header every Datastar request carries, with the value true.
17pub const REQUEST_HEADER: &str = "datastar-request";
The content-type of a stream of events.
20pub const EVENT_STREAM: &str = "text/event-stream";
How patched elements meet the page.
Morph the element with the same id (Datastar's default).
26 Outer,
Morph the inside of the selected element.
37impl Mode { 38 fn word(self) -> &'static str { 39 match self { 40 Mode::Outer => "outer", 41 Mode::Inner => "inner", 42 Mode::Replace => "replace", 43 Mode::Prepend => "prepend", 44 Mode::Append => "append", 45 Mode::Before => "before", 46 Mode::After => "after", 47 Mode::Remove => "remove", 48 } 49 } 50}
Elements to put into the page.
60impl PatchElements {
Elements that carry ids, which Datastar morphs in place.
Takes away what the selector matches.
The event as stream text. Each line of the markup is its own elements line, so a newline in
the markup (inside a <pre>) reaches the page as it was written.
83 pub fn sse(&self) -> String { 84 let mut out = String::from("event: datastar-patch-elements\n"); 85 if let Some(selector) = &self.selector { 86 out.push_str(&format!("data: selector {}\n", one_line(selector))); 87 } 88 if let Some(mode) = self.mode { 89 out.push_str(&format!("data: mode {}\n", mode.word())); 90 } 91 for line in self.html.lines() { 92 out.push_str("data: elements "); 93 out.push_str(line); 94 out.push('\n'); 95 } 96 out.push('\n'); 97 out 98 } 99}
Signals to merge into the page's.
108impl PatchSignals {
Signals from anything that serializes to a JSON object. A null member removes that signal.
Sets only the signals the page does not have yet.
The signals of a request: the datastar query parameter's value (already percent-decoded) for a
GET, the body for anything else. The same JSON either way.
What a page asks of a server with no signals: an empty object, so a field with a default reads.
A selector or a signal name is a single line; a line break in one would end the data line.
146#[cfg(test)] 147mod tests { 148 use super::*; 149 use serde::Deserialize; 150 151 #[test] 152 fn a_patch_is_one_data_line_per_line_of_markup() { 153 let sse = PatchElements::new("<pre id=\"a\">x\n y</pre>").sse(); 154 assert_eq!(sse, "event: datastar-patch-elements\ndata: elements <pre id=\"a\">x\ndata: elements y</pre>\n\n"); 155 } 156 157 #[test] 158 fn selector_and_mode_come_before_the_markup() { 159 let sse = PatchElements::new("<li>1</li>").selector("#list").mode(Mode::Append).sse(); 160 assert_eq!( 161 sse, 162 "event: datastar-patch-elements\ndata: selector #list\ndata: mode append\ndata: elements <li>1</li>\n\n" 163 ); 164 } 165 166 #[test] 167 fn removing_names_only_the_selector() { 168 assert_eq!( 169 PatchElements::remove("#gone").sse(), 170 "event: datastar-patch-elements\ndata: selector #gone\ndata: mode remove\n\n" 171 ); 172 } 173 174 #[test] 175 fn a_selector_cannot_end_its_own_line() { 176 assert!(!PatchElements::new("<p></p>").selector("#a\ndata: elements <script>").sse().contains("\ndata: elements <script>")); 177 } 178 179 #[test] 180 fn signals_are_one_json_line() { 181 let sse = PatchSignals::of(&serde_json::json!({ "n": 1, "s": "a\nb" })).unwrap().only_if_missing().sse(); 182 assert_eq!(sse, "event: datastar-patch-signals\ndata: onlyIfMissing true\ndata: signals {\"n\":1,\"s\":\"a\\nb\"}\n\n"); 183 } 184 185 #[test] 186 fn signals_read_with_defaults() { 187 #[derive(Deserialize, Default, PartialEq, Debug)] 188 #[serde(default)] 189 struct Page { 190 name: String, 191 count: u32, 192 } 193 assert_eq!(signals::<Page>(r#"{"name":"a"}"#).unwrap(), Page { name: "a".into(), count: 0 }); 194 assert_eq!(signals::<Page>(no_signals()).unwrap(), Page::default()); 195 } 196 197 #[test] 198 fn the_script_is_datastar_one_zero_four() { 199 assert!(SCRIPT.contains("1.0.4")); 200 } 201}