// server_dom - Server-side DOM for SSR
//
// This module provides a DOM API for server-side rendering.
// - No event handlers (E = Unit)
// - Document structure elements (html, head, body)
// - Island integration for client components
//
///|
/// Render result with HTML and preload URLs
pub struct RenderResult {
html : String
preload_urls : Array[String]
}
///|
/// CSS and JavaScript assets declared by server components.
pub struct ServerAssets {
styles : Array[String]
scripts : Array[String]
}
///|
/// Create an empty ServerAssets value.
pub fn ServerAssets::empty() -> ServerAssets {
{ styles: [], scripts: [], }
}
///|
/// Create ServerAssets from style and script paths.
pub fn ServerAssets::new(
styles? : Array[String] = [],
scripts? : Array[String] = [],
) -> ServerAssets {
{ styles, scripts, }
}
///|
fn push_unique_path(paths : Array[String], path : String) -> Unit {
if !paths.contains(path) {
paths.push(path)
}
}
///|
/// Merge assets preserving first-seen order.
pub fn ServerAssets::merge(
self : ServerAssets,
other : ServerAssets,
) -> ServerAssets {
let styles = self.styles.copy()
let scripts = self.scripts.copy()
for style in other.styles {
push_unique_path(styles, style)
}
for script in other.scripts {
push_unique_path(scripts, script)
}
{ styles, scripts, }
}
// =============================================================================
// Server Node - Supports both sync and async server components
// =============================================================================
///|
/// Server component result type
/// Supports both synchronous and asynchronous server components:
/// - Sync: returns Node[Unit] directly
/// - Async: returns async function that resolves to Node[Unit]
pub enum ServerNode {
Sync(@luna.Node[Unit, String])
Async(async () -> @luna.Node[Unit, String])
Group(Array[ServerNode])
WithAssets(
styles~ : Array[String],
scripts~ : Array[String],
child~ : ServerNode
)
}
///|
/// Create a synchronous ServerNode from a Node
pub fn ServerNode::sync(node : @luna.Node[Unit, String]) -> ServerNode {
Sync(node)
}
///|
/// Create an asynchronous ServerNode from an async function
pub fn ServerNode::async_(
render : async () -> @luna.Node[Unit, String],
) -> ServerNode {
Async(render)
}
///|
/// Create a ServerNode group from multiple server components.
pub fn ServerNode::group(children : Array[ServerNode]) -> ServerNode {
Group(children)
}
///|
/// Attach CSS and JavaScript assets to a ServerNode.
pub fn ServerNode::with_assets(
self : ServerNode,
styles? : Array[String] = [],
scripts? : Array[String] = [],
) -> ServerNode {
WithAssets(styles~, scripts~, child=self)
}
///|
/// Attach a stylesheet asset to a ServerNode.
pub fn ServerNode::with_style(self : ServerNode, style : String) -> ServerNode {
self.with_assets(styles=[style])
}
///|
/// Attach a client script asset to a ServerNode.
pub fn ServerNode::with_script(
self : ServerNode,
script : String,
) -> ServerNode {
self.with_assets(scripts=[script])
}
///|
/// Resolve ServerNode to Node (awaits if async)
pub async fn ServerNode::resolve(self : ServerNode) -> @luna.Node[Unit, String] {
match self {
Sync(node) => node
Async(render) => render()
Group(children) => {
let nodes : Array[@luna.Node[Unit, String]] = []
for child in children {
nodes.push(child.resolve())
}
@luna.fragment(nodes)
}
WithAssets(child~, ..) => child.resolve()
}
}
///|
/// Check if ServerNode is async
pub fn ServerNode::is_async(self : ServerNode) -> Bool {
match self {
Async(_) => true
Group(children) => {
for child in children {
if child.is_async() {
return true
}
}
false
}
WithAssets(child~, ..) => child.is_async()
Sync(_) => false
}
}
///|
/// Collect assets declared by this ServerNode without resolving async content.
pub fn ServerNode::declared_assets(self : ServerNode) -> ServerAssets {
match self {
Sync(_) | Async(_) => ServerAssets::empty()
Group(children) => {
let mut assets = ServerAssets::empty()
for child in children {
assets = assets.merge(child.declared_assets())
}
assets
}
WithAssets(styles~, scripts~, child~) =>
ServerAssets::new(styles~, scripts~).merge(child.declared_assets())
}
}