Skip to main content

seam_server/
server.rs

1/* src/server/core/rust/src/server.rs */
2
3use std::collections::BTreeMap;
4use std::time::Duration;
5
6use crate::build_loader::RpcHashMap;
7use crate::channel::{ChannelDef, ChannelMeta};
8use crate::context::{ContextConfig, ContextFieldDef};
9use crate::page::{I18nConfig, PageDef};
10use crate::procedure::{ProcedureDef, StreamDef, SubscriptionDef, UploadDef};
11use crate::resolve::ResolveStrategy;
12use crate::validation::ValidationMode;
13
14/// Transport reliability configuration shared across all backends.
15pub struct TransportConfig {
16	pub heartbeat_interval: Duration,
17	pub sse_idle_timeout: Duration,
18	pub pong_timeout: Duration,
19}
20
21impl Default for TransportConfig {
22	fn default() -> Self {
23		Self {
24			heartbeat_interval: Duration::from_secs(21),
25			sse_idle_timeout: Duration::from_secs(30),
26			pong_timeout: Duration::from_secs(5),
27		}
28	}
29}
30
31/// Framework-agnostic parts extracted from `SeamServer`.
32/// Adapter crates consume this to build framework-specific routers.
33pub struct SeamParts {
34	pub procedures: Vec<ProcedureDef>,
35	pub subscriptions: Vec<SubscriptionDef>,
36	pub streams: Vec<StreamDef>,
37	pub uploads: Vec<UploadDef>,
38	pub pages: Vec<PageDef>,
39	pub rpc_hash_map: Option<RpcHashMap>,
40	pub i18n_config: Option<I18nConfig>,
41	pub strategies: Vec<Box<dyn ResolveStrategy>>,
42	pub channel_metas: BTreeMap<String, ChannelMeta>,
43	pub context_config: ContextConfig,
44	pub validation_mode: ValidationMode,
45	pub transport_config: TransportConfig,
46}
47
48impl SeamParts {
49	pub fn has_url_prefix(&self) -> bool {
50		self.strategies.iter().any(|s| s.kind() == "url_prefix")
51	}
52}
53
54pub struct SeamServer {
55	procedures: Vec<ProcedureDef>,
56	subscriptions: Vec<SubscriptionDef>,
57	streams: Vec<StreamDef>,
58	uploads: Vec<UploadDef>,
59	channels: Vec<ChannelDef>,
60	pages: Vec<PageDef>,
61	rpc_hash_map: Option<RpcHashMap>,
62	i18n_config: Option<I18nConfig>,
63	strategies: Vec<Box<dyn ResolveStrategy>>,
64	context_config: ContextConfig,
65	validation_mode: ValidationMode,
66	transport_config: TransportConfig,
67}
68
69impl SeamServer {
70	pub fn new() -> Self {
71		Self {
72			procedures: Vec::new(),
73			subscriptions: Vec::new(),
74			streams: Vec::new(),
75			uploads: Vec::new(),
76			channels: Vec::new(),
77			pages: Vec::new(),
78			rpc_hash_map: None,
79			i18n_config: None,
80			strategies: Vec::new(),
81			context_config: ContextConfig::new(),
82			validation_mode: ValidationMode::Dev,
83			transport_config: TransportConfig::default(),
84		}
85	}
86
87	pub fn procedure(mut self, proc: ProcedureDef) -> Self {
88		self.procedures.push(proc);
89		self
90	}
91
92	pub fn subscription(mut self, sub: SubscriptionDef) -> Self {
93		self.subscriptions.push(sub);
94		self
95	}
96
97	pub fn stream(mut self, stream: StreamDef) -> Self {
98		self.streams.push(stream);
99		self
100	}
101
102	pub fn upload(mut self, upload: UploadDef) -> Self {
103		self.uploads.push(upload);
104		self
105	}
106
107	pub fn channel(mut self, channel: ChannelDef) -> Self {
108		self.channels.push(channel);
109		self
110	}
111
112	/// Register procedures under a dot-separated namespace prefix (e.g. "blog" -> "blog.getPost").
113	pub fn namespace(mut self, prefix: &str, procedures: Vec<ProcedureDef>) -> Self {
114		for mut p in procedures {
115			p.name = format!("{prefix}.{}", p.name);
116			self.procedures.push(p);
117		}
118		self
119	}
120
121	/// Register subscriptions under a dot-separated namespace prefix.
122	pub fn namespace_subs(mut self, prefix: &str, subs: Vec<SubscriptionDef>) -> Self {
123		for mut s in subs {
124			s.name = format!("{prefix}.{}", s.name);
125			self.subscriptions.push(s);
126		}
127		self
128	}
129
130	/// Register streams under a dot-separated namespace prefix.
131	pub fn namespace_streams(mut self, prefix: &str, streams: Vec<StreamDef>) -> Self {
132		for mut s in streams {
133			s.name = format!("{prefix}.{}", s.name);
134			self.streams.push(s);
135		}
136		self
137	}
138
139	pub fn page(mut self, page: PageDef) -> Self {
140		self.pages.push(page);
141		self
142	}
143
144	pub fn rpc_hash_map(mut self, map: RpcHashMap) -> Self {
145		self.rpc_hash_map = Some(map);
146		self
147	}
148
149	pub fn i18n_config(mut self, config: I18nConfig) -> Self {
150		self.i18n_config = Some(config);
151		self
152	}
153
154	pub fn resolve_strategies(mut self, strategies: Vec<Box<dyn ResolveStrategy>>) -> Self {
155		self.strategies = strategies;
156		self
157	}
158
159	pub fn context(mut self, key: &str, field: ContextFieldDef) -> Self {
160		self.context_config.insert(key.to_string(), field);
161		self
162	}
163
164	pub fn validation_mode(mut self, mode: ValidationMode) -> Self {
165		self.validation_mode = mode;
166		self
167	}
168
169	pub fn transport_config(mut self, config: TransportConfig) -> Self {
170		self.transport_config = config;
171		self
172	}
173
174	/// Consume the builder, returning framework-agnostic parts for an adapter.
175	/// Channels are expanded into their Level 0 primitives (commands + subscriptions).
176	pub fn into_parts(self) -> SeamParts {
177		let mut procedures = self.procedures;
178		let mut subscriptions = self.subscriptions;
179		let mut channel_metas = BTreeMap::new();
180
181		for channel in self.channels {
182			let name = channel.name.clone();
183			let (procs, subs, meta) = channel.expand();
184			procedures.extend(procs);
185			subscriptions.extend(subs);
186			channel_metas.insert(name, meta);
187		}
188
189		SeamParts {
190			procedures,
191			subscriptions,
192			streams: self.streams,
193			uploads: self.uploads,
194			pages: self.pages,
195			rpc_hash_map: self.rpc_hash_map,
196			i18n_config: self.i18n_config,
197			strategies: self.strategies,
198			channel_metas,
199			context_config: self.context_config,
200			validation_mode: self.validation_mode,
201			transport_config: self.transport_config,
202		}
203	}
204}
205
206impl Default for SeamServer {
207	fn default() -> Self {
208		Self::new()
209	}
210}