route

package
v0.53.8 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 24, 2026 License: MIT Imports: 46 Imported by: 0

Documentation

Overview

Package route provides declarative server routing with layouts for GoSX apps.

Routes map URL patterns to component handlers. Layouts wrap pages with shared UI. Nested layouts compose from outermost to innermost.

Index

Constants

This section is empty.

Variables

View Source
var ErrNotFound = errors.New("route not found")

ErrNotFound marks a loader failure that should render the router's 404 flow instead of the route error handler.

Functions

func DefaultFileRenderer

func DefaultFileRenderer(ctx *RouteContext, page FilePage) (gosx.Node, error)

DefaultFileRenderer renders `.gsx` and `.html` page files directly.

func IsNotFound

func IsNotFound(err error) bool

IsNotFound reports whether err should be treated as a route-level 404.

func LoadFileProgram added in v0.48.0

func LoadFileProgram(path string) (*ir.Program, error)

LoadFileProgram compiles the .gsx file at path and returns its IR program, through the same stat-keyed cache (gosx#226) a file-routed page or layout renders through (route/filelayout.go's gsxCompileCache): a request that hits this file again before its next edit gets the cached *ir.Program back without re-parsing, and an edit invalidates the cache exactly the way it invalidates a running page's own hot reload — there is no second, independently-stale cache for a caller of this function to fall behind.

This is the other half of gosx#226's fix: a plain http.Handler in the same package as a page.gsx file — for example, a fragment endpoint that data- gosx-region polls — loads that file's compiled program with this function and renders any component it declares, including one the page's own Page/Layout entry never renders directly, with RenderProgramComponent.

path resolves relative to the process's current working directory, same as os.Open; pass an absolute path to make it independent of the working directory the binary starts in.

Do not build that absolute path from runtime.Caller's own file value: a `-trimpath` build — `gosx build`'s default, and the shape of most container images — replaces that value with the calling module's own declared path, not a path that exists on the machine running the binary, so path resolves to a file that was never there (gosx#239). This mistake passes every local check: a plain `go run` or `go build` still records the real path, so it only surfaces once something builds with -trimpath. LoadFileProgramHere resolves a sibling of the calling file without this problem; prefer it when path is a sibling of the calling source file.

func LoadFileProgramHere added in v0.49.0

func LoadFileProgramHere(name string) (*ir.Program, error)

LoadFileProgramHere compiles the .gsx file named name in the same directory as the calling source file and returns its IR program, through the same cache LoadFileProgram uses (gosx#226).

Use this in place of building a path from runtime.Caller and filepath.Dir yourself, the pattern the "Rendering a fragment from a Go handler" example used before this function existed: see LoadFileProgram's own doc comment for why that pattern breaks under a `-trimpath` build (gosx#239). LoadFileProgramHere resolves the calling file the same trimpath-safe way RegisterFileModuleHere resolves a page's sibling .gsx file, so it does not have that problem.

name is a bare file name, such as "page.gsx" — a sibling of the calling file, not a path elsewhere in the tree. For that, resolve an absolute path yourself and call LoadFileProgram directly.

func MustRegisterDirModule deprecated

func MustRegisterDirModule(module DirModule) error

MustRegisterDirModule adds a directory module to the shared registry.

Deprecated: use RegisterDirModule and handle the returned error.

func MustRegisterDirModuleCaller deprecated

func MustRegisterDirModuleCaller(skip int, opts DirModuleOptions) error

MustRegisterDirModuleCaller registers a directory module using a caller higher in the stack.

Deprecated: use RegisterDirModuleCaller and handle the returned error.

func MustRegisterDirModuleHere deprecated

func MustRegisterDirModuleHere(opts DirModuleOptions) error

MustRegisterDirModuleHere infers the route directory from the calling file and registers the module in the shared registry.

Deprecated: use RegisterDirModuleHere and handle the returned error.

func MustRegisterFileModule deprecated

func MustRegisterFileModule(module FileModule) error

MustRegisterFileModule adds a file-route module to the shared registry.

Deprecated: use RegisterFileModule and handle the returned error.

func MustRegisterFileModuleCaller deprecated

func MustRegisterFileModuleCaller(skip int, opts FileModuleOptions) error

MustRegisterFileModuleCaller registers a file module using a caller higher in the stack. `skip=0` means the immediate caller, `skip=1` skips one wrapper, and so on.

Deprecated: use RegisterFileModuleCaller and handle the returned error.

func MustRegisterFileModuleHere deprecated

func MustRegisterFileModuleHere(opts FileModuleOptions) error

MustRegisterFileModuleHere infers the sibling page source path from the calling file and registers the module in the shared registry.

Deprecated: use RegisterFileModuleHere and handle the returned error.

func NotFound

func NotFound(message string) error

NotFound returns an error that instructs the router to render the not-found page for the current request path.

func RegisterDirModule

func RegisterDirModule(module DirModule) error

RegisterDirModule adds a directory module to the shared registry.

func RegisterDirModuleCaller

func RegisterDirModuleCaller(skip int, opts DirModuleOptions) error

RegisterDirModuleCaller registers a directory module using a caller higher in the stack. `skip=0` means the immediate caller.

func RegisterDirModuleHere

func RegisterDirModuleHere(opts DirModuleOptions) error

RegisterDirModuleHere infers the route directory from the calling file and registers the module in the shared registry.

func RegisterFileModule

func RegisterFileModule(module FileModule) error

RegisterFileModule adds a file-route module to the shared registry.

func RegisterFileModuleCaller

func RegisterFileModuleCaller(skip int, opts FileModuleOptions) error

RegisterFileModuleCaller registers a file module using a caller higher in the stack. `skip=0` means the immediate caller.

func RegisterFileModuleHere

func RegisterFileModuleHere(opts FileModuleOptions) error

RegisterFileModuleHere infers the sibling page source path from the calling file and registers the module in the shared registry.

func RenderProgramComponent added in v0.24.3

func RenderProgramComponent(prog *ir.Program, component string, env ProgramRenderEnv, children ...gosx.Node) (string, error)

RenderProgramComponent renders the named component of a compiled program (from gosx.Compile) to server-side static HTML: it evaluates {expr}, resolves local <Component/> references, and inlines local island children via env.RenderIsland.

It is the public entry to the file-program renderer that powers file-based pages, for callers that compile and render components directly — e.g. a slide deck that lowers each slide to a generated component, or a plain http.Handler in the same package as a page.gsx file rendering one of that page's own components into a fragment (gosx#226, see LoadFileProgram) — instead of from on-disk page files. A single compiled source may declare the rendered component plus any sibling components and islands it references; cross-references resolve here at render time.

component is a strict component's own render entry exactly the same way a nested call to it is: see ProgramRenderEnv.Props for the typed-props contract this requires.

children places one or more Go-computed gosx.Node values wherever component's body writes {children} (gosx#226, gosx#246) — the same "one opaque node, emitted where written" contract a nested <Component>...</Component> call's children get, not a prop: children are never proved against component's declared schema, and cannot overwrite a proved props field. Passing no children reproduces every pre-#246 call's behavior exactly: an unresolved "children" identifier fails soft to empty, the same as today. Passing children against a legacy (non-strict) component fails closed with an error, since a legacy render entry has no {children} hole to bind them to.

When env.Profile is set and its Validate hook reports any diagnostic, RenderProgramComponent returns a *RenderProfileError and an empty string; no partial HTML is ever returned alongside that error.

func RenderProgramComponentNode added in v0.50.0

func RenderProgramComponentNode(prog *ir.Program, component string, env ProgramRenderEnv, children ...gosx.Node) (gosx.Node, error)

RenderProgramComponentNode is RenderProgramComponent's Node-returning sibling (gosx#226, gosx#246): it renders the same way, but returns a gosx.Node instead of a string, so the result composes directly into a gosx.El(...) tree instead of forcing a caller to wrap a rendered string in gosx.RawHTML by hand — the pattern examples/dashboard's chrome() helper used before this function existed (see examples/dashboard/chrome.go).

The returned Node wraps the rendered HTML with gosx.RawHTML, the same wrapping RenderProgramComponent's own callers were already doing, and the same wrapping writeLocalComponent uses internally for a nested call's own children — the render still happens exactly once; nothing here re-renders or re-escapes it. On error, the returned Node is the zero Node, and the caller must not render it: like RenderProgramComponent, no partial HTML is ever returned alongside an error.

Types

type AttrWriter added in v0.43.0

type AttrWriter func(tag string, attrs []RenderAttr) []RenderAttr

AttrWriter rewrites, vetoes, or appends an element's attributes before they render. See RenderProfile.AttrWriter for exactly when it runs and what it can and cannot see.

type DataLoader

type DataLoader func(ctx *RouteContext) (any, error)

DataLoader fetches data for a route before rendering.

type DirConfigureFunc

type DirConfigureFunc func(ctx *RouteContext, page FilePage) error

DirConfigureFunc applies request-scoped subtree configuration before a file route loads data or renders.

type DirModule

type DirModule struct {
	Source     string
	Middleware []Middleware
	Configure  DirConfigureFunc
}

DirModule wires inherited middleware and request setup to a file-route directory.

func DirModuleCaller

func DirModuleCaller(skip int, opts DirModuleOptions) DirModule

DirModuleCaller infers the route directory from a caller higher in the stack.

func DirModuleFor

func DirModuleFor(source string, opts DirModuleOptions) DirModule

DirModuleFor builds a directory-scoped route module definition.

func DirModuleHere

func DirModuleHere(opts DirModuleOptions) DirModule

DirModuleHere infers the route directory from the calling file and returns a directory-scoped route module.

type DirModuleOptions

type DirModuleOptions struct {
	Middleware []Middleware
	Configure  DirConfigureFunc
}

DirModuleOptions configures a directory-scoped route module.

type DirModuleRegistry

type DirModuleRegistry struct {
	// contains filtered or unexported fields
}

DirModuleRegistry stores directory-scoped route modules keyed by source path.

func DefaultDirModuleRegistry

func DefaultDirModuleRegistry() *DirModuleRegistry

DefaultDirModuleRegistry returns the shared process-wide directory-module registry.

func NewDirModuleRegistry

func NewDirModuleRegistry() *DirModuleRegistry

NewDirModuleRegistry creates an empty directory-module registry.

func (*DirModuleRegistry) Lookup

func (r *DirModuleRegistry) Lookup(source string) (DirModule, bool)

Lookup finds a registered directory module by source path.

func (*DirModuleRegistry) MustRegisterCaller deprecated

func (r *DirModuleRegistry) MustRegisterCaller(skip int, opts DirModuleOptions) error

MustRegisterCaller registers a directory module using a caller higher in the stack.

Deprecated: use RegisterCaller and handle the returned error.

func (*DirModuleRegistry) MustRegisterHere deprecated

func (r *DirModuleRegistry) MustRegisterHere(opts DirModuleOptions) error

MustRegisterHere registers a directory module inferred from the calling file.

Deprecated: use RegisterHere and handle the returned error.

func (*DirModuleRegistry) Register

func (r *DirModuleRegistry) Register(module DirModule) error

Register adds a directory module to the registry.

func (*DirModuleRegistry) RegisterCaller

func (r *DirModuleRegistry) RegisterCaller(skip int, opts DirModuleOptions) error

RegisterCaller registers a directory module using a caller higher in the stack. `skip=0` means the immediate caller.

func (*DirModuleRegistry) RegisterHere

func (r *DirModuleRegistry) RegisterHere(opts DirModuleOptions) error

RegisterHere infers the route directory from the calling file and registers the module in this registry.

type ErrorHandler

type ErrorHandler func(ctx *RouteContext, err error) gosx.Node

ErrorHandler renders a route error page.

type FileActions

type FileActions map[string]action.Handler

FileActions maps action names to handlers for a file-routed page.

type FileBindingsFunc

type FileBindingsFunc func(ctx *RouteContext, page FilePage, data any) FileTemplateBindings

FileBindingsFunc returns request-scoped bindings for the default file renderer.

type FileLayoutOptions

type FileLayoutOptions struct {
	SlotComponents   []string
	HTMLPlaceholders []string
}

FileLayoutOptions configures how a file-backed layout injects the page body.

type FileLoadFunc

type FileLoadFunc func(ctx *RouteContext, page FilePage) (any, error)

FileLoadFunc loads request-scoped data for a file-routed page.

type FileMetadataAsset

type FileMetadataAsset struct {
	Kind      FileMetadataAssetKind
	FilePath  string
	Source    string
	Dir       string
	RoutePath string
	Pattern   string
	Params    []string
}

FileMetadataAsset describes a discovered metadata convention file.

func (FileMetadataAsset) RequestPath

func (a FileMetadataAsset) RequestPath(params map[string]string) string

func (FileMetadataAsset) StaticExportPath

func (a FileMetadataAsset) StaticExportPath() (string, bool)

type FileMetadataAssetKind

type FileMetadataAssetKind string

FileMetadataAssetKind identifies a convention-driven metadata asset.

const (
	FileMetadataAssetOpenGraphImage FileMetadataAssetKind = "opengraph_image"
	FileMetadataAssetTwitterImage   FileMetadataAssetKind = "twitter_image"
	FileMetadataAssetFavicon        FileMetadataAssetKind = "favicon"
	FileMetadataAssetIcon           FileMetadataAssetKind = "icon"
	FileMetadataAssetAppleIcon      FileMetadataAssetKind = "apple_icon"
	FileMetadataAssetManifest       FileMetadataAssetKind = "manifest"
	FileMetadataAssetRobots         FileMetadataAssetKind = "robots"
	FileMetadataAssetSitemap        FileMetadataAssetKind = "sitemap"
)

type FileMetadataFunc

type FileMetadataFunc func(ctx *RouteContext, page FilePage, data any) (server.Metadata, error)

FileMetadataFunc derives metadata for a file-routed page after Load runs.

type FileModule

type FileModule struct {
	Source   string
	Load     FileLoadFunc
	Metadata FileMetadataFunc
	Render   FileRenderDataFunc
	Actions  FileActions
	Bindings FileBindingsFunc

	// MaxActionBodyBytes caps the request body accepted by this module's
	// actions, enforced with http.MaxBytesReader semantics (an oversized
	// request fails with 413 rather than being silently truncated). Zero
	// keeps the action package default of 1 MiB. Set this to accept larger
	// uploads, such as a file, through an action route.
	MaxActionBodyBytes int64
}

FileModule wires server-side hooks to a file-routed page source file.

func FileModuleCaller

func FileModuleCaller(skip int, opts FileModuleOptions) FileModule

FileModuleCaller infers the sibling page source path from a caller higher in the stack. Use this when wrapping file-module registration in helper functions so the outer `page.server.go` remains the registered source.

func FileModuleFor

func FileModuleFor(source string, opts FileModuleOptions) FileModule

FileModuleFor builds a file-routed server module definition.

func FileModuleHere

func FileModuleHere(opts FileModuleOptions) FileModule

FileModuleHere infers the sibling page source path from the calling `*.server.go` file so callers do not need to repeat `"page.gsx"` strings.

type FileModuleOptions

type FileModuleOptions struct {
	Load     FileLoadFunc
	Metadata FileMetadataFunc
	Render   FileRenderDataFunc
	Actions  FileActions
	Bindings FileBindingsFunc

	// MaxActionBodyBytes caps the request body accepted by this module's
	// actions, enforced with http.MaxBytesReader semantics (an oversized
	// request fails with 413 rather than being silently truncated). Zero
	// keeps the action package default of 1 MiB. Set this to accept larger
	// uploads, such as a file, through an action route.
	MaxActionBodyBytes int64
}

FileModuleOptions configures a file-routed server module.

type FileModuleRegistry

type FileModuleRegistry struct {
	// contains filtered or unexported fields
}

FileModuleRegistry stores file-route server modules keyed by source path.

func DefaultFileModuleRegistry

func DefaultFileModuleRegistry() *FileModuleRegistry

DefaultFileModuleRegistry returns the shared process-wide module registry.

func NewFileModuleRegistry

func NewFileModuleRegistry() *FileModuleRegistry

NewFileModuleRegistry creates an empty file-route module registry.

func (*FileModuleRegistry) Lookup

func (r *FileModuleRegistry) Lookup(source string) (FileModule, bool)

Lookup finds a registered file-route module by source path.

func (*FileModuleRegistry) MustRegisterCaller deprecated

func (r *FileModuleRegistry) MustRegisterCaller(skip int, opts FileModuleOptions) error

MustRegisterCaller registers a file module inferred from a caller higher in the stack.

Deprecated: use RegisterCaller and handle the returned error.

func (*FileModuleRegistry) MustRegisterHere deprecated

func (r *FileModuleRegistry) MustRegisterHere(opts FileModuleOptions) error

MustRegisterHere registers a file module inferred from the calling file.

Deprecated: use RegisterHere and handle the returned error.

func (*FileModuleRegistry) Register

func (r *FileModuleRegistry) Register(module FileModule) error

Register adds a file-route module to the registry.

func (*FileModuleRegistry) RegisterCaller

func (r *FileModuleRegistry) RegisterCaller(skip int, opts FileModuleOptions) error

RegisterCaller registers a file module inferred from a caller higher in the stack. `skip=0` means the immediate caller.

func (*FileModuleRegistry) RegisterHere

func (r *FileModuleRegistry) RegisterHere(opts FileModuleOptions) error

RegisterHere infers the sibling page source path from the calling file and registers the module in this registry.

type FilePage

type FilePage struct {
	Root      string
	FilePath  string
	Source    string
	Dir       string
	RoutePath string
	Pattern   string
	Params    []string
	Layouts   []string
	Config    FileRouteConfig
	ErrorPage *FilePage
}

FilePage describes a discovered file-based page route.

type FilePageMetadataAssets

type FilePageMetadataAssets struct {
	OpenGraphImage *FileMetadataAsset
	TwitterImage   *FileMetadataAsset
}

FilePageMetadataAssets holds the nearest discovered page-scoped metadata assets.

type FileRenderDataFunc

type FileRenderDataFunc func(ctx *RouteContext, page FilePage, data any) (gosx.Node, error)

FileRenderDataFunc overrides the default page-file renderer.

type FileRenderFunc

type FileRenderFunc func(ctx *RouteContext, page FilePage) (gosx.Node, error)

FileRenderFunc renders a discovered file page for a request.

type FileRouteCacheConfig

type FileRouteCacheConfig struct {
	Public               *bool  `json:"public,omitempty"`
	Private              *bool  `json:"private,omitempty"`
	NoStore              *bool  `json:"noStore,omitempty"`
	NoCache              *bool  `json:"noCache,omitempty"`
	MaxAge               string `json:"maxAge,omitempty"`
	SMaxAge              string `json:"sMaxAge,omitempty"`
	StaleWhileRevalidate string `json:"staleWhileRevalidate,omitempty"`
	StaleIfError         string `json:"staleIfError,omitempty"`
	MustRevalidate       *bool  `json:"mustRevalidate,omitempty"`
	Immutable            *bool  `json:"immutable,omitempty"`
}

FileRouteCacheConfig maps `route.config.json` cache directives onto `server.CachePolicy`.

type FileRouteConfig

type FileRouteConfig struct {
	Cache     *FileRouteCacheConfig `json:"cache,omitempty"`
	CacheTags []string              `json:"cacheTags,omitempty"`
	Headers   map[string]string     `json:"headers,omitempty"`
	Prerender *bool                 `json:"prerender,omitempty"`
}

FileRouteConfig describes inheritable directory-scoped configuration loaded from `route.config.json`.

func (FileRouteConfig) CachePolicy

func (c FileRouteConfig) CachePolicy() (server.CachePolicy, bool, error)

CachePolicy exposes the resolved cache policy for a file-route config.

func (FileRouteConfig) PrerenderEnabled

func (c FileRouteConfig) PrerenderEnabled(defaultValue bool) bool

PrerenderEnabled reports whether routes in this scope should be exported when the caller provides a default policy.

type FileRouteScope

type FileRouteScope struct {
	Dir       string
	RoutePath string
	Pattern   string
	Params    []string
	Page      FilePage
}

FileRouteScope describes a directory-scoped special page such as not-found.

type FileRoutes

type FileRoutes struct {
	Pages          []FilePage
	NotFound       *FilePage
	Error          *FilePage
	NotFoundScopes []FileRouteScope
}

FileRoutes is the discovered result of scanning a file-based route tree.

func ScanDir

func ScanDir(root string) (FileRoutes, error)

ScanDir discovers file-based routes from a directory tree.

type FileRoutesOptions

type FileRoutesOptions struct {
	Render       FileRenderFunc
	Modules      *FileModuleRegistry
	DirModules   *DirModuleRegistry
	Middleware   []Middleware
	Layout       LayoutFunc
	ErrorHandler ErrorHandler
}

FileRoutesOptions configures AddDir.

type FileTemplateBindings

type FileTemplateBindings struct {
	Values     map[string]any
	Funcs      map[string]any
	Components map[string]any
}

FileTemplateBindings exposes request-scoped values, helpers, and renderable Go component functions to a file-routed `.gsx` page.

`Components` remains available for explicit component binding, but exported Go component functions exposed through `Funcs` or `Values` are also resolved automatically by the file renderer.

type LayoutFunc

type LayoutFunc func(ctx *RouteContext, content gosx.Node) gosx.Node

LayoutFunc wraps page content with shared layout.

func FileLayout

func FileLayout(file string) (LayoutFunc, error)

FileLayout loads a .gsx or .html layout file and returns a LayoutFunc that injects page content into <Slot /> / <Outlet /> markers or HTML placeholders.

func FileLayoutWithOptions

func FileLayoutWithOptions(file string, opts FileLayoutOptions) (LayoutFunc, error)

FileLayoutWithOptions loads a file-backed layout with custom slot markers.

func FileLayoutWithOptionsAndRegistry

func FileLayoutWithOptionsAndRegistry(file string, registry *FileModuleRegistry, opts FileLayoutOptions) (LayoutFunc, error)

FileLayoutWithOptionsAndRegistry loads a file-backed layout with custom slot markers and an explicit file module registry.

func FileLayoutWithRegistry

func FileLayoutWithRegistry(file string, registry *FileModuleRegistry) (LayoutFunc, error)

FileLayoutWithRegistry loads a file-backed layout using an explicit file module registry instead of the shared default registry.

type Middleware

type Middleware func(next http.Handler) http.Handler

Middleware runs before page handling.

type PageHandler

type PageHandler func(ctx *RouteContext) gosx.Node

PageHandler renders a page, receiving route context.

type ProgramRenderEnv added in v0.24.3

type ProgramRenderEnv struct {
	Values       map[string]any
	Funcs        map[string]any
	Props        any
	RenderIsland func(*islandprogram.Program, any) gosx.Node
	Profile      *RenderProfile
	// IslandPreloadHints and IslandPageHead compute the two framework-filled
	// named slots a strict component may declare — {slotPreloadHints} and
	// {slotPageHead} (gosx#249) — from the same island runtime RenderIsland
	// renders through. Unlike Slots below, neither is a caller-authored
	// value: this env supplies the two callbacks, and the renderer decides
	// when to call them and what to bind the result to, the same way it
	// binds children. A nested <Layout>{content}</Layout> call inside a
	// compiled program's own body renders content (and every RenderIsland
	// call it makes) BEFORE calling either of these, so the value they
	// return reflects every island content registered — see
	// writeLocalComponentWithChildren (route/fileprogram.go). Each is
	// called only for a component that actually declares the matching slot
	// (ir.Component.AcceptsSlot), so a page with no island runtime, or a
	// component that never places {slotPreloadHints}/{slotPageHead}, pays
	// nothing for it. Nil is the default: no pre-gosx#249 caller sets
	// these, so none takes a new branch.
	IslandPreloadHints func() gosx.Node
	IslandPageHead     func() gosx.Node
	// Slots supplies named-slot values for a strict render entry that
	// declares more than the one anonymous children hole (gosx#249) — a
	// per-route title and an end-of-body script are two different
	// injection points a layout-shaped component needs, and repeating
	// {children} cannot express that (TestStrictComponentRendersChildrenTwice
	// pins every repeat to the same content). Keyed by slot name ("Title",
	// not "slotTitle" — see strictcomponent.SlotBindingName for the
	// reserved identifier a name binds to in the component's body).
	//
	// A nil or empty Slots reproduces every pre-gosx#249 call's behavior
	// exactly, the same "take no new branch" contract Children keeps
	// (RenderProgramComponent's own doc comment). A key naming a slot the
	// entry component's body does not declare fails closed with a
	// descriptive error; a slot the body declares but this map does not
	// supply stays unresolved and renders empty, exactly like an
	// unsupplied {children} does today. Slots are unproven, the same way
	// Children is: never entering PropsFields, PropsPaths, or PropsSlices,
	// and never overwriting a proved props field.
	Slots map[string]gosx.Node
}

ProgramRenderEnv supplies the expression bindings and inline-island renderer for RenderProgramComponent. All fields are optional:

  • Values / Funcs bind identifiers and functions referenced by {expr} (e.g. Funcs["strings"] = map[string]any{"ToUpper": strings.ToUpper}). Unresolved identifiers render empty rather than erroring, so missing bindings fail soft.
  • Props supplies the typed props value when component is a strict component (gosx#226): a `component Foo(props: FooProps)` declaration compiles to an ir.Component with a declared PropsType, and — same as a nested `<Foo {...props}/>` call inside another component's body — rendering it as the entry component must prove a real struct value, not a map. RenderProgramComponent proves Props at that identical boundary (see strictSpreadProps): a nil Props, any map[string]any (including one shaped exactly like FooProps — a map can omit keys and has no field types to check ahead of the values, so it never proves coverage), or a struct missing a rendered field or holding one of the wrong type all fail closed with a descriptive error instead of rendering. A same-shaped struct under a different Go type name is accepted, matching a generated-Go spread caller's own boundary (strictSpreadProps proves field coverage, not the source struct's own type identity — see TestStrictSpreadProps). Ignored for a legacy (non-strict) component, and for a strict component with no declared props.
  • RenderIsland turns a local //gosx:island child (referenced as <Name/>) into a hydrated server-rendered mount — pass server.PageRuntime.Island or island.(*Renderer).RenderIslandFromProgram. When nil, island children degrade to inert placeholders.
  • Profile installs an EXPERIMENTAL render-profile hook: an attribute writer that can rewrite, veto, or append an element's attributes, and a pre-render validation pass that can refuse to render the program (gosx#185). A nil Profile reproduces today's rendering exactly, byte for byte. See RenderProfile.

type RenderAttr added in v0.43.0

type RenderAttr struct {
	// Name is the attribute name, for example "class" or "href".
	Name string

	// Value is the attribute's text value, for example a URL or a class
	// list. Value is ignored when Boolean is true.
	Value string

	// Boolean marks a valueless, presence-only attribute, for example the
	// `disabled` in `<button disabled>`. The renderer emits only Name for a
	// boolean attribute, matching how an AttrBool or a boolean-typed
	// expression attribute renders without a profile.
	//
	// Boolean, not Value, is what makes an attribute absent from a
	// rendered element: appending RenderAttr{Name: "disabled", Value:
	// "false"} with Boolean left false renders disabled="false", and HTML
	// treats ANY value on a boolean attribute — including the literal text
	// "false" — as present, so the button stays disabled (gosx#185 m3). A
	// profile that wants an element to render as not-disabled must omit
	// the attribute entirely, not set Value to a falsy-looking string.
	Boolean bool
}

RenderAttr is one resolved HTML attribute: an attribute name plus either a text value or a boolean-presence marker, after expression evaluation and spread/shorthand expansion, before HTML escaping.

RenderAttr is the only type an AttrWriter hook exchanges with the renderer. It carries no raw-HTML or pre-escaped-string variant, so an AttrWriter has no way to bypass the unconditional escaping the renderer applies to Name and Value after the hook returns.

type RenderProfile added in v0.43.0

type RenderProfile struct {
	// AttrWriter, when set, runs once per rendered ir.NodeElement, after
	// every attribute on it has been evaluated ({expr} attributes resolved,
	// {...spread} attributes expanded and flattened, and any managed-form
	// shorthand attribute removed — the hook never sees the shorthand
	// itself, or the runtime-contract attributes it expands into) and
	// before HTML escaping. It receives the element's tag name and its
	// resolved attributes, and returns the attributes to emit: change a
	// Value to rewrite, omit an entry to veto, or append a new RenderAttr
	// to add one.
	//
	// The renderer escapes every returned attribute's Name and Value
	// unconditionally after AttrWriter returns. RenderAttr's Value field is
	// a plain string with no "pre-escaped" or "raw" variant, so there is no
	// value AttrWriter can return that skips escaping. A returned Name that
	// is not a valid HTML attribute name — empty, whitespace-only, or
	// containing a character (space, a control character, `"`, `'`, `>`,
	// `/`, or `=`) that would end an attribute-name token early — fails the
	// whole render with a *RenderProfileError naming the offending tag and
	// Name instead of being escaped and emitted (gosx#185 M1): unlike an
	// ordinary attribute value, a Name is never sanitized for you, because
	// a profile is trusted code and a bad Name here is a bug worth
	// stopping the render for, not input to defend against.
	//
	// See the Coverage map above for exactly which elements this hook
	// reaches and does not, and the contract-attribute-name rule that
	// applies to what it can do to a #179 managed-form contract attribute
	// specifically.
	//
	// A panic inside AttrWriter is recovered and converted into a
	// *RenderProfileError naming the hook and the tag it was called for
	// (gosx#185 m5); it does not crash the calling process.
	AttrWriter AttrWriter

	// Validate, when set, runs once per render, before any output is
	// written, against the compiled *ir.Program. A non-empty return value
	// aborts the render: RenderProgramComponent returns a *RenderProfileError
	// wrapping the diagnostics and an empty HTML string. Rendering is fail
	// closed — a profile that finds a problem never lets partial or
	// unvalidated output reach the caller. Validate runs before
	// RenderProgramComponent even checks that the named component exists,
	// so a Validate refusal is reported ahead of a "component not found"
	// error for the same call (gosx#185 n2).
	//
	// Validate must not modify prog. The renderer may run concurrently
	// with the same *ir.Program across independent requests or goroutines,
	// so a mutation here would race every other render sharing it.
	//
	// Validate walks the WHOLE program, not only the component this render
	// call is about to render: a component this call never reaches still
	// gets checked, and can still fail the render on its own. This catches
	// a problem before it ships in some other, unrelated page, but it has
	// two costs to weigh — a large program with many components pays this
	// pass's full cost on every single render, and a genuine problem in a
	// component this particular render path never exercises can still
	// refuse an otherwise entirely fine render (a false positive from this
	// call's point of view, even though the diagnostic itself is real).
	//
	// A panic inside Validate is recovered and converted into a
	// *RenderProfileError naming the hook (gosx#185 m5); it does not crash
	// the calling process.
	Validate func(*ir.Program) []ir.Diagnostic
}

RenderProfile is an EXPERIMENTAL render-time hook for RenderProgramComponent. It exists so a downstream renderer that targets a stricter or different HTML dialect — for example an email renderer, which needs inline styles instead of classes and a stricter element allowlist — can rewrite attribute emission and refuse to render an unsafe program, without forking the file-program renderer (gosx#185).

The surface is intentionally small: an attribute-writer hook and a pre-render validation pass. There is no plugin registry and no CLI surface. It may change or be removed in a future minor release; a caller that depends on it directly should pin an exact gosx version.

A nil *RenderProfile reproduces today's rendering exactly, byte for byte — every profile-aware code path in the file-program renderer is gated on a non-nil field of this struct, so an empty *RenderProfile{} (both fields nil) also reproduces it exactly.

Only RenderProgramComponent sets fileRenderOptions.Profile from ProgramRenderEnv.Profile: a file-routed page or layout, rendered through renderFileNode instead, has no way to install a profile (gosx#185 m6).

Coverage map (gosx#185 M2)

AttrWriter reaches every ir.NodeElement (`<div>`, `<a>`, `<script>`, a hand-written `<form>`, ...) the renderer writes, wherever it sits: a plain top-level element, an element that is a strict or legacy nested component's own markup, an element inside an If/Show/When branch, and each per-iteration element inside an Each/For loop all get their own call.

AttrWriter does NOT reach:

  • A builtin component's own markup — Link's rendered `<a>`, Image's rendered `<img>`, and every other builtin (Form, ActionForm, Motion, Video, TextBlock, Stylesheet, Surface, Worker, Scene3D) writes its element directly, bypassing this hook entirely.
  • An unknown, engine, or bound component's markup, rendered through defaultRenderedComponent. Name this one explicitly: it is different in kind from the builtins above, because it emits the author's own attributes verbatim, with no escaping or contract logic of its own for AttrWriter to stand in for.
  • An ir.NodeRawHTML node, or a runtime gosx.RawHTML value returned from an expression: both are opaque strings the renderer copies through unexamined. Validate cannot see inside either kind of raw HTML any more than AttrWriter can — a profile that must inspect or reject raw HTML has no hook for it today.
  • An island subtree rendered through env.island (ProgramRenderEnv's RenderIsland): island markup comes from a separate renderer, outside this file-program renderer's write path.

It also may see an author-written copy of a #179 managed-form runtime-contract attribute (data-gosx-form, its -state/-mode/-project variants, the client-runtime -form-error-describedby wiring attribute, the shared -enhance/-enhance-layer/-fallback progressive-enhancement attributes, and the data-gosx-managed shorthand) in AttrWriter's input, but the renderer discards any add, change, or removal the hook's returned copy makes to one of those names, reinserts the original value at its original position, and computes the contract's own presence check from that reconciled, effective list — a profile cannot weaken the managed-form contract by vetoing or rewriting it (gosx#185 B1).

Text-node escaping and the void-element list (ir.VoidElements) are not configurable through a profile either. Both are cheap to reach from here, but a profile that could turn off text escaping would violate this type's own escape-after-the-hook guarantee, and the HTML5 void-element set is a fact about the format, not a per-consumer policy. A consumer that needs a different void-element set for a non-HTML target should treat that as a lowering-time concern, upstream of RenderProgramComponent.

type RenderProfileError added in v0.43.0

type RenderProfileError struct {
	Diagnostics []ir.Diagnostic
}

RenderProfileError reports that a RenderProfile's Validate pass refused to render a program. Diagnostics is never empty when this error is returned. RenderProgramComponent and the file-program renderer both return it before writing any output, so a refusal is total: the render never returns partial HTML alongside this error.

func (*RenderProfileError) Error added in v0.43.0

func (e *RenderProfileError) Error() string

type Route

type Route struct {
	// Pattern is the URL path pattern (e.g., "/", "/dashboard", "/users/{id}").
	Pattern string

	// Handler renders the page component.
	Handler PageHandler

	// Layout wraps the page output (optional, overrides app-level layout).
	Layout LayoutFunc

	// Middleware runs before the handler.
	Middleware []Middleware

	// Children are nested routes under this pattern.
	Children []Route

	// DataLoader fetches data before rendering (optional).
	DataLoader DataLoader

	// ErrorHandler renders a route-scoped 500 page when the handler panics or the
	// data loader returns an error.
	ErrorHandler ErrorHandler
}

Route defines a URL pattern → component mapping.

type RouteContext

type RouteContext struct {
	Request *http.Request
	Params  map[string]string
	Data    any

	server.PageState
	// contains filtered or unexported fields
}

RouteContext provides request context to handlers.

func (*RouteContext) ActionForm

func (ctx *RouteContext) ActionForm(name string, args ...any) gosx.Node

ActionForm renders a POST form targeting the current route's named action.

func (*RouteContext) ActionPath

func (ctx *RouteContext) ActionPath(name string) string

ActionPath returns the current page-relative action endpoint for the given action name.

func (*RouteContext) ActionState

func (ctx *RouteContext) ActionState(name string) (action.View, bool)

ActionState returns the flashed state for a named browser action.

func (*RouteContext) ActionStates

func (ctx *RouteContext) ActionStates() map[string]action.View

ActionStates returns all flashed action states for the current request.

func (*RouteContext) Document added in v0.50.0

func (ctx *RouteContext) Document(defaultTitle string, body gosx.Node) *server.DocumentContext

Document composes the complete native document context for this route. It preserves request, route pattern, response status, title, language, page identity/path, request ID, metadata, runtime, navigation, head, body attributes, nonce, and the framework document contract. Pass the returned context to server.HTMLDocument from a document-owning layout.

func (*RouteContext) Form

func (ctx *RouteContext) Form(args ...any) gosx.Node

Form renders a form tag opted into the GoSX navigation/runtime submission layer while preserving native HTML fallback behavior.

func (*RouteContext) Param

func (ctx *RouteContext) Param(name string) string

Param returns a URL path parameter.

func (*RouteContext) ParentData

func (ctx *RouteContext) ParentData(key string) any

ParentData returns data loaded by a parent route's DataLoader.

func (*RouteContext) Query

func (ctx *RouteContext) Query(name string) string

Query returns a URL query parameter.

func (*RouteContext) QueryInto added in v0.27.0

func (ctx *RouteContext) QueryInto(dst any) error

QueryInto decodes the request's URL query parameters into dst (a pointer to a struct) using `query` struct tags — the typed companion to Query.

Where Query(name) returns a single raw string, QueryInto binds the entire query string into a typed struct, applying tag defaults and reporting a field-named error on a malformed value:

type Filters struct {
    Q    string   `query:"q"`
    Page int      `query:"page,default=1"`
    Tags []string `query:"tags"`
}
var f Filters
if err := ctx.QueryInto(&f); err != nil { /* 400 */ }

The same query package powers island-side URL state, so a server loader and a browser island agree on one typed representation of the URL. See package query.

func (*RouteContext) SetHandlerError

func (ctx *RouteContext) SetHandlerError(err error)

SetHandlerError records an error to be dispatched through the error handler after the handler returns. This replaces panic-based error propagation.

type Router

type Router struct {
	// contains filtered or unexported fields
}

Router builds an http.Handler from a route tree.

func NewRouter

func NewRouter() *Router

NewRouter creates a new router.

func (*Router) Add

func (r *Router) Add(routes ...Route)

Add registers routes.

func (*Router) AddDir

func (r *Router) AddDir(root string, opts FileRoutesOptions) error

AddDir scans a directory tree and registers its file-based routes.

func (*Router) Build

func (r *Router) Build() http.Handler

Build compiles the router into an http.Handler. If route registration fails, the returned handler reports the build error as HTTP 500 instead of crashing the process.

func (*Router) BuildChecked

func (r *Router) BuildChecked() (http.Handler, error)

BuildChecked compiles the router into an http.Handler and returns route registration errors such as invalid or conflicting patterns.

func (*Router) Handle

func (r *Router) Handle(pattern string, handler http.Handler, middleware ...Middleware)

Handle registers a raw HTTP handler alongside page routes.

func (*Router) RevalidatePath

func (r *Router) RevalidatePath(target string) uint64

RevalidatePath invalidates cache validators for the provided path prefix.

func (*Router) RevalidateTag

func (r *Router) RevalidateTag(tag string) uint64

RevalidateTag invalidates cache validators for the provided tag.

func (*Router) Revalidator

func (r *Router) Revalidator() *server.Revalidator

Revalidator returns the router-wide in-memory revalidator.

func (*Router) SetError

func (r *Router) SetError(handler ErrorHandler)

SetError sets the default 500 handler.

func (*Router) SetLayout

func (r *Router) SetLayout(layout LayoutFunc)

SetLayout sets the default layout for all routes.

func (*Router) SetNavigationHead added in v0.42.0

func (r *Router) SetNavigationHead(fn func(nonce string) gosx.Node)

SetNavigationHead registers the framework-owned navigation-runtime head builder RouteContext carries into PageState.Head. server.App.Mount calls this automatically (via server.NavigationConfigurable) when the owning App has EnableNavigation set, so a file-routed app needs only app.EnableNavigation() in its composition root.

func (*Router) SetNotFound

func (r *Router) SetNotFound(handler PageHandler)

SetNotFound sets the 404 handler.

func (*Router) SetRevalidator

func (r *Router) SetRevalidator(revalidator *server.Revalidator)

SetRevalidator replaces the router-wide in-memory revalidator used for automatic ETags and explicit path/tag invalidation.

func (*Router) UseObserver

func (r *Router) UseObserver(observer server.RequestObserver)

UseObserver appends a request observer to the supported router extension surface.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL