// Sol Routes - Server-side route definitions with typed handlers
//
// This is a Sol-specific route type that directly takes handler functions
// instead of component ID strings. For server-side rendering only.
// ============================================================================
// ISR In-Flight Deduplication
// ============================================================================
///|
/// Tracks cache keys with in-flight STALE revalidation to prevent duplicate renders.
let isr_in_flight : Ref[Set[String]] = Ref(Set([]))
// ============================================================================
// Handler Types
// ============================================================================
///|
/// Page handler type - async function that receives PageProps and returns ServerNode
pub(all) struct PageHandler(async (PageProps) -> @server_dom.ServerNode)
///|
/// API handler type - async function that receives PageProps and returns Json response.
/// Uses MoonBit's built-in Json type for wasm/native compatibility.
pub(all) struct ApiHandler(async (PageProps) -> Json)
///|
/// Raw handler type - async function that returns a Web Response directly.
/// Use this for streaming, redirects, binary bodies, and Worker pass-throughs.
pub(all) struct RawHandler(async (PageProps) -> @web_http.Response)
// ============================================================================
// Cache Strategy
// ============================================================================
///|
/// Cache strategy for CSR navigation
/// Controls how the client handles navigation to the same page
pub(all) enum CacheStrategy {
/// Always fetch from server (default)
/// Each navigation triggers a fresh server request
AlwaysFetch
/// Stale-While-Revalidate
/// Shows cached content immediately, then fetches and updates in background
StaleWhileRevalidate
} derive(Eq, Debug)
///|
/// Default cache strategy is AlwaysFetch
pub fn CacheStrategy::default() -> CacheStrategy {
CacheStrategy::AlwaysFetch
}
// ============================================================================
// Sol Routes Definition
// ============================================================================
///|
/// Sol route definition with typed handlers
pub(all) enum SolRoutes {
/// Page route with async handler
/// - path: URL pattern (e.g., "/", "/user/:id")
/// - handler: Async function that returns ServerNode
/// - title: Page title
/// - meta: Meta information
/// - revalidate: ISR revalidation interval in seconds (None = no caching)
/// - cache: CSR cache strategy (default: AlwaysFetch)
Page(
path~ : String,
handler~ : PageHandler,
title~ : String,
meta~ : Array[(String, String)],
revalidate~ : Int?,
cache~ : CacheStrategy?
)
/// Layout wrapper for nested routes
/// - segment: Path segment (e.g., "/admin")
/// - layout: Layout handler that wraps children
/// - children: Nested routes
Layout(
segment~ : String,
layout~ : (PageProps, @server_dom.ServerNode) -> @server_dom.ServerNode raise Error,
children~ : Array[SolRoutes]
)
/// GET API route
/// - path: URL pattern
/// - handler: Handler that returns JSON
Get(path~ : String, handler~ : ApiHandler)
/// POST API route
/// - path: URL pattern
/// - handler: Handler that returns JSON
Post(path~ : String, handler~ : ApiHandler)
/// PUT API route
/// - path: URL pattern
/// - handler: Handler that returns JSON
Put(path~ : String, handler~ : ApiHandler)
/// DELETE API route
/// - path: URL pattern
/// - handler: Handler that returns JSON
Delete(path~ : String, handler~ : ApiHandler)
/// PATCH API route
/// - path: URL pattern
/// - handler: Handler that returns JSON
Patch(path~ : String, handler~ : ApiHandler)
/// Raw GET route
/// - path: URL pattern
/// - handler: Handler that returns a Web Response directly
RawGet(path~ : String, handler~ : RawHandler)
/// Raw POST route
/// - path: URL pattern
/// - handler: Handler that returns a Web Response directly
RawPost(path~ : String, handler~ : RawHandler)
/// Raw PUT route
/// - path: URL pattern
/// - handler: Handler that returns a Web Response directly
RawPut(path~ : String, handler~ : RawHandler)
/// Raw DELETE route
/// - path: URL pattern
/// - handler: Handler that returns a Web Response directly
RawDelete(path~ : String, handler~ : RawHandler)
/// Raw PATCH route
/// - path: URL pattern
/// - handler: Handler that returns a Web Response directly
RawPatch(path~ : String, handler~ : RawHandler)
/// Middleware wrapper for route groups
/// - middleware: Array of middlewares to apply
/// - children: Nested routes
WithMiddleware(
middleware~ : Array[@middleware.Middleware],
children~ : Array[SolRoutes]
)
/// Asset wrapper for route groups
/// - styles: CSS assets to inject for children
/// - scripts: client module scripts to inject for children
/// - children: Nested routes
WithAssets(
styles~ : Array[String],
scripts~ : Array[String],
children~ : Array[SolRoutes]
)
}
// ============================================================================
// Compiled Routes
// ============================================================================
///|
/// Catch-all parameter info
pub(all) struct CatchAllInfo {
/// Parameter name (e.g., "slug")
name : String
/// Whether it's optional ([[...slug]] = true)
optional : Bool
}
///|
/// Compiled route for efficient matching
pub struct CompiledSolRoute {
/// Full path pattern
pattern : String
/// Parameter names extracted from path
param_names : Array[String]
/// Page handler (if page route)
page_handler : PageHandler?
/// API handler (if API route)
api_handler : ApiHandler?
/// Raw handler (if raw Response route)
raw_handler : RawHandler?
/// Layout handlers (from outermost to innermost)
layouts : Array[
(PageProps, @server_dom.ServerNode) -> @server_dom.ServerNode raise Error,
]
/// Middleware chain (from outermost to innermost)
middlewares : Array[@middleware.Middleware]
/// Route kind
kind : SolRouteKind
/// Page title (raw)
title : String
/// Meta information (raw)
meta : Array[(String, String)]
/// Catch-all parameter info (for [...slug] or [[...slug]])
catch_all : CatchAllInfo?
/// ISR revalidation interval in seconds (None = no caching)
revalidate : Int?
/// CSR cache strategy
cache_strategy : CacheStrategy
/// Pre-rendered meta HTML (computed once at route registration)
rendered_meta : String
/// Stylesheet assets inherited by this route
styles : Array[String]
/// Client module script assets inherited by this route
scripts : Array[String]
}
///|
/// Route kind
pub enum SolRouteKind {
Page
GetApi
PostApi
PutApi
DeleteApi
PatchApi
RawGet
RawPost
RawPut
RawDelete
RawPatch
} derive(Eq, Debug)
// ============================================================================
// Static Directory (SSG Integration)
// ============================================================================
///|
/// Configuration for static directory generation (minimal, backend-independent)
pub(all) struct StaticDirConfig {
/// Internationalization configuration
i18n : @astra.I18nConfig
/// Navigation configuration (SPA, View Transitions, Keyboard)
navigation : @astra.NavigationConfig
/// Directories to exclude from scanning
exclude : Array[String]
/// Use trailing slash in URLs
trailing_slash : Bool
}
///|
/// Default static directory configuration
pub fn StaticDirConfig::default() -> StaticDirConfig {
{
i18n: @astra.I18nConfig::default(),
navigation: @astra.NavigationConfig::default(),
exclude: [],
trailing_slash: true,
}
}
///|
/// Static directory route definition
pub(all) struct StaticDir {
/// URL path prefix (e.g., "/docs")
path_prefix : String
/// Source directory containing markdown files (e.g., "docs")
source_dir : String
/// Section title
title : String
/// SSG configuration
config : StaticDirConfig
}
///|
/// Log server error to stderr without exposing details to client.
fn log_server_error(context : String, err : Error) -> Unit {
ffi_console_error("[sol] \{context}: \{err}")
}