1use std::collections::BTreeMap;
4use std::path::PathBuf;
5use std::time::Duration;
6
7use crate::build_loader::BuildOutput;
8use crate::build_loader::RpcHashMap;
9use crate::channel::{ChannelDef, ChannelMeta};
10use crate::context::{ContextConfig, ContextFieldDef};
11use crate::page::{I18nConfig, PageDef};
12use crate::procedure::{ProcedureDef, StreamDef, SubscriptionDef, UploadDef};
13use crate::resolve::ResolveStrategy;
14use crate::validation::ValidationMode;
15
16pub struct TransportConfig {
18 pub heartbeat_interval: Duration,
19 pub sse_idle_timeout: Duration,
20 pub pong_timeout: Duration,
21}
22
23impl Default for TransportConfig {
24 fn default() -> Self {
25 Self {
26 heartbeat_interval: Duration::from_secs(8),
27 sse_idle_timeout: Duration::from_secs(12),
28 pong_timeout: Duration::from_secs(5),
29 }
30 }
31}
32
33pub struct SeamParts {
36 pub procedures: Vec<ProcedureDef>,
37 pub subscriptions: Vec<SubscriptionDef>,
38 pub streams: Vec<StreamDef>,
39 pub uploads: Vec<UploadDef>,
40 pub pages: Vec<PageDef>,
41 pub rpc_hash_map: Option<RpcHashMap>,
42 pub i18n_config: Option<I18nConfig>,
43 pub public_dir: Option<PathBuf>,
44 pub strategies: Vec<Box<dyn ResolveStrategy>>,
45 pub channel_metas: BTreeMap<String, ChannelMeta>,
46 pub context_config: ContextConfig,
47 pub validation_mode: ValidationMode,
48 pub transport_config: TransportConfig,
49}
50
51impl SeamParts {
52 pub fn has_url_prefix(&self) -> bool {
53 self.strategies.iter().any(|s| s.kind() == "url_prefix")
54 }
55}
56
57pub struct SeamServer {
58 procedures: Vec<ProcedureDef>,
59 subscriptions: Vec<SubscriptionDef>,
60 streams: Vec<StreamDef>,
61 uploads: Vec<UploadDef>,
62 channels: Vec<ChannelDef>,
63 pages: Vec<PageDef>,
64 rpc_hash_map: Option<RpcHashMap>,
65 i18n_config: Option<I18nConfig>,
66 public_dir: Option<PathBuf>,
67 strategies: Vec<Box<dyn ResolveStrategy>>,
68 context_config: ContextConfig,
69 validation_mode: ValidationMode,
70 transport_config: TransportConfig,
71}
72
73impl SeamServer {
74 pub fn new() -> Self {
75 Self {
76 procedures: Vec::new(),
77 subscriptions: Vec::new(),
78 streams: Vec::new(),
79 uploads: Vec::new(),
80 channels: Vec::new(),
81 pages: Vec::new(),
82 rpc_hash_map: None,
83 i18n_config: None,
84 public_dir: None,
85 strategies: Vec::new(),
86 context_config: ContextConfig::new(),
87 validation_mode: ValidationMode::Dev,
88 transport_config: TransportConfig::default(),
89 }
90 }
91
92 pub fn procedure(mut self, proc: ProcedureDef) -> Self {
93 self.procedures.push(proc);
94 self
95 }
96
97 pub fn subscription(mut self, sub: SubscriptionDef) -> Self {
98 self.subscriptions.push(sub);
99 self
100 }
101
102 pub fn stream(mut self, stream: StreamDef) -> Self {
103 self.streams.push(stream);
104 self
105 }
106
107 pub fn upload(mut self, upload: UploadDef) -> Self {
108 self.uploads.push(upload);
109 self
110 }
111
112 pub fn channel(mut self, channel: ChannelDef) -> Self {
113 self.channels.push(channel);
114 self
115 }
116
117 pub fn namespace(mut self, prefix: &str, procedures: Vec<ProcedureDef>) -> Self {
119 for mut p in procedures {
120 p.name = format!("{prefix}.{}", p.name);
121 self.procedures.push(p);
122 }
123 self
124 }
125
126 pub fn namespace_subs(mut self, prefix: &str, subs: Vec<SubscriptionDef>) -> Self {
128 for mut s in subs {
129 s.name = format!("{prefix}.{}", s.name);
130 self.subscriptions.push(s);
131 }
132 self
133 }
134
135 pub fn namespace_streams(mut self, prefix: &str, streams: Vec<StreamDef>) -> Self {
137 for mut s in streams {
138 s.name = format!("{prefix}.{}", s.name);
139 self.streams.push(s);
140 }
141 self
142 }
143
144 pub fn page(mut self, page: PageDef) -> Self {
145 self.pages.push(page);
146 self
147 }
148
149 pub fn rpc_hash_map(mut self, map: RpcHashMap) -> Self {
150 self.rpc_hash_map = Some(map);
151 self
152 }
153
154 pub fn i18n_config(mut self, config: I18nConfig) -> Self {
155 self.i18n_config = Some(config);
156 self
157 }
158
159 pub fn public_dir(mut self, dir: PathBuf) -> Self {
160 self.public_dir = Some(dir);
161 self
162 }
163
164 pub fn build(mut self, build: BuildOutput) -> Self {
165 self.pages.extend(build.pages);
166 if let Some(map) = build.rpc_hash_map {
167 self.rpc_hash_map = Some(map);
168 }
169 if let Some(config) = build.i18n_config {
170 self.i18n_config = Some(config);
171 }
172 if let Some(dir) = build.public_dir {
173 self.public_dir = Some(dir);
174 }
175 self
176 }
177
178 pub fn resolve_strategies(mut self, strategies: Vec<Box<dyn ResolveStrategy>>) -> Self {
179 self.strategies = strategies;
180 self
181 }
182
183 pub fn context(mut self, key: &str, field: ContextFieldDef) -> Self {
184 self.context_config.insert(key.to_string(), field);
185 self
186 }
187
188 pub fn validation_mode(mut self, mode: ValidationMode) -> Self {
189 self.validation_mode = mode;
190 self
191 }
192
193 pub fn transport_config(mut self, config: TransportConfig) -> Self {
194 self.transport_config = config;
195 self
196 }
197
198 pub fn into_parts(self) -> SeamParts {
201 let mut procedures = self.procedures;
202 let mut subscriptions = self.subscriptions;
203 let mut channel_metas = BTreeMap::new();
204
205 for channel in self.channels {
206 let name = channel.name.clone();
207 let (procs, subs, meta) = channel.expand();
208 procedures.extend(procs);
209 subscriptions.extend(subs);
210 channel_metas.insert(name, meta);
211 }
212
213 SeamParts {
214 procedures,
215 subscriptions,
216 streams: self.streams,
217 uploads: self.uploads,
218 pages: self.pages,
219 rpc_hash_map: self.rpc_hash_map,
220 i18n_config: self.i18n_config,
221 public_dir: self.public_dir,
222 strategies: self.strategies,
223 channel_metas,
224 context_config: self.context_config,
225 validation_mode: self.validation_mode,
226 transport_config: self.transport_config,
227 }
228 }
229}
230
231impl Default for SeamServer {
232 fn default() -> Self {
233 Self::new()
234 }
235}
236
237#[cfg(test)]
238mod tests {
239 use super::TransportConfig;
240 use std::time::Duration;
241
242 #[test]
243 fn transport_config_uses_8_second_heartbeat_and_12_second_idle_timeout_by_default() {
244 let config = TransportConfig::default();
245 assert_eq!(config.heartbeat_interval, Duration::from_secs(8));
246 assert_eq!(config.sse_idle_timeout, Duration::from_secs(12));
247 }
248}