// Router registration from route trees
///|
/// Register routes from Routes definition to Hono app
/// Uses a resolver function to map component IDs to render functions
/// @deprecated Use register_sol_routes instead
pub fn[E] register_routes(
app : App,
routes : Array[@routes.Routes],
page_resolver : (String, Ctx, RouteParams) -> @luna.Node[E, String]?,
api_resolver : (String, Ctx, RouteParams) -> @js.Any?,
config? : RouterConfig,
title_prefix? : String = "",
default_title? : String = "App",
default_head? : String = "",
loader_url? : String = "/__sol__/loader.js",
wc_loader_url? : String = "/__sol__/wc-loader.js",
) -> Unit {
ensure_mars_to_handler_compat()
let resolved = resolve_router_config(
config,
title_prefix~,
default_title~,
default_head~,
loader_url~,
wc_loader_url~,
)
// Loader URLs are currently reserved for client-loader integration.
let _ = (resolved.loader_url, resolved.wc_loader_url)
let register_page = fn(
full_path : String,
component : String,
title : String,
) {
let param_names = extract_route_param_names(full_path)
app.get(full_path, async fn(c) {
let is_fragment = has_fragment_header(c)
let params = extract_route_params(c, is_fragment~, param_names~)
match page_resolver(component, c, params) {
Some(node) => {
let page_title = format_title(
title,
title_prefix=resolved.title_prefix,
default_title=resolved.default_title,
)
let page_head = resolved.default_head +
render_asset_tags(resolved.styles, resolved.client_scripts)
if is_fragment {
let fragment_html = render_fragment_response(
node, page_title, false,
)
send_fragment_response(c, fragment_html)
} else {
let html = render_page_html(
node,
page_title,
page_head,
false,
resolved.root_template,
enable_preload=resolved.enable_preload,
preload_libs=resolved.preload_libs,
chunk_manifest=resolved.chunk_manifest,
)
c.html(html)
}
}
None => c.text("Page not found: " + component, status=404)
}
})
}
let register_get = fn(full_path : String, handler : String) {
register_api_get_route(app, full_path, handler, api_resolver)
}
let register_post = fn(full_path : String, handler : String) {
register_api_post_route(app, full_path, handler, api_resolver)
}
register_route_tree(routes, "", register_page, register_get, register_post)
}
///|
fn register_route_tree(
routes : Array[@routes.Routes],
prefix : String,
register_page : (String, String, String) -> Unit,
register_get : (String, String) -> Unit,
register_post : (String, String) -> Unit,
) -> Unit {
for route in routes {
match route {
@routes.Page(path~, component~, title~, meta~) => {
let _ = meta
let full_path = prefix + path
register_page(full_path, component, title)
}
@routes.Layout(segment~, children~, layout~) => {
// `@routes.Layout` is treated as path grouping in this adapter.
// Actual layout composition is handled by `sol_routes` (ServerNode).
let _ = layout
register_route_tree(
children,
prefix + segment,
register_page,
register_get,
register_post,
)
}
@routes.Get(path~, handler~) => register_get(prefix + path, handler)
@routes.Post(path~, handler~) => register_post(prefix + path, handler)
}
}
}
///|
fn register_api_get_route(
app : App,
full_path : String,
handler : String,
api_resolver : (String, Ctx, RouteParams) -> @js.Any?,
) -> Unit {
let param_names = extract_route_param_names(full_path)
app.get(full_path, async fn(c) {
let params = extract_route_params(c, is_fragment=false, param_names~)
match api_resolver(handler, c, params) {
Some(json) => @mars_response.send_json_any(c, json)
None => @mars_response.send_json_error(c, "Handler not found", status=404)
}
})
}
///|
fn register_api_post_route(
app : App,
full_path : String,
handler : String,
api_resolver : (String, Ctx, RouteParams) -> @js.Any?,
) -> Unit {
let param_names = extract_route_param_names(full_path)
app.post(full_path, async fn(c) {
let params = extract_route_params(c, is_fragment=false, param_names~)
match api_resolver(handler, c, params) {
Some(json) => @mars_response.send_json_any(c, json)
None => @mars_response.send_json_error(c, "Handler not found", status=404)
}
})
}
///|
/// Async page resolver type - returns ServerNode for async server components
pub type AsyncPageResolver = (String, Ctx, RouteParams) -> @server_dom.ServerNode?
///|
/// Register routes with async server component support
/// Uses ServerNode which supports both sync and async server components
/// @deprecated Use register_sol_routes instead
pub fn register_server_routes(
app : App,
routes : Array[@routes.Routes],
page_resolver : AsyncPageResolver,
api_resolver : (String, Ctx, RouteParams) -> @js.Any?,
config? : RouterConfig,
title_prefix? : String = "",
default_title? : String = "App",
default_head? : String = "",
loader_url? : String = "/__sol__/loader.js",
wc_loader_url? : String = "/__sol__/wc-loader.js",
) -> Unit {
ensure_mars_to_handler_compat()
let resolved = resolve_router_config(
config,
title_prefix~,
default_title~,
default_head~,
loader_url~,
wc_loader_url~,
)
let converted = legacy_routes_to_sol_routes(
routes, page_resolver, api_resolver,
)
// Loader URLs are currently reserved for client-loader integration.
let _ = (resolved.loader_url, resolved.wc_loader_url)
register_sol_routes(app, converted, config=resolved)
}
///|
fn identity_layout(
_props : PageProps,
children : @server_dom.ServerNode,
) -> @server_dom.ServerNode raise Error {
children
}
///|
fn page_not_found_node() -> @server_dom.ServerNode {
@server_dom.ServerNode::sync(@luna.text(""))
}
///|
fn legacy_page_handler_from_resolver(
component : String,
page_resolver : AsyncPageResolver,
) -> async (PageProps) -> @server_dom.ServerNode {
async fn(props : PageProps) {
match page_resolver(component, props.ctx, props.params) {
Some(server_node) => server_node
None => {
props.ctx.text("Page not found: " + component, status=404)
page_not_found_node()
}
}
}
}
///|
fn legacy_api_handler_from_resolver(
handler : String,
api_resolver : (String, Ctx, RouteParams) -> @js.Any?,
) -> async (PageProps) -> Json {
async fn(props : PageProps) {
match api_resolver(handler, props.ctx, props.params) {
Some(json) => {
// Convert legacy @js.Any to Json string and back
let str = ffi_json_stringify_any(json)
@json.parse(str) catch {
_ => ().to_json()
}
}
None => {
@mars_response.send_json_error(
props.ctx,
"Handler not found",
status=404,
)
().to_json()
}
}
}
}
///|
fn legacy_routes_to_sol_routes(
routes : Array[@routes.Routes],
page_resolver : AsyncPageResolver,
api_resolver : (String, Ctx, RouteParams) -> @js.Any?,
) -> Array[SolRoutes] {
let converted : Array[SolRoutes] = []
for route in routes {
match route {
@routes.Page(path~, component~, title~, meta~) =>
converted.push(
SolRoutes::Page(
path~,
handler=PageHandler(
legacy_page_handler_from_resolver(component, page_resolver),
),
title~,
meta~,
revalidate=None,
cache=None,
),
)
@routes.Layout(segment~, children~, layout~) => {
let _ = layout
converted.push(
SolRoutes::Layout(
segment~,
layout=identity_layout,
children=legacy_routes_to_sol_routes(
children, page_resolver, api_resolver,
),
),
)
}
@routes.Get(path~, handler~) =>
converted.push(
SolRoutes::Get(
path~,
handler=ApiHandler(
legacy_api_handler_from_resolver(handler, api_resolver),
),
),
)
@routes.Post(path~, handler~) =>
converted.push(
SolRoutes::Post(
path~,
handler=ApiHandler(
legacy_api_handler_from_resolver(handler, api_resolver),
),
),
)
}
}
converted
}