live

package
v1.5.0 Latest Latest
Warning

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

Go to latest
Published: Oct 21, 2026 License: MIT Imports: 19 Imported by: 0

Documentation

Overview

Package live runs stateful components for a connected browser: a Session keeps the component instances of one page, calls their event handlers and Update methods, and answers each with the HTML of the components whose output changed. It knows no transport; a WebSocket server drives it with the messages of protocol.go.

Index

Constants

View Source
const (
	Join     = "join"     // client: render the page (the first message)
	EventMsg = "event"    // client: an element's event, to the handler Target names
	Render   = "render"   // server: the page's whole HTML, for the container
	PatchMsg = "patch"    // server: the components that changed, after an event or a broadcast
	Error    = "error"    // server: the message failed; the session goes on
	Redirect = "redirect" // server: load URL, a full page load
	Navigate = "navigate" // client: move the page to URL (Session.Navigate)
)

The wire protocol: JSON messages over one connection per page (a WebSocket, typically), client → server ClientMessage, server → client ServerMessage.

→ {"type":"join"}
← {"type":"render","html":"<main>…</main>"}
→ {"type":"event","ref":1,"target":"c2:0","event":"click"}
← {"type":"patch","ref":1,"updates":[{"id":"c2","html":"<div data-vk-id=\"c2\">…</div>"}]}
→ {"type":"event","ref":2,"target":"c3:1","event":"submit","form":{"title":["Hi"]}}
← {"type":"patch","ref":2,"updates":[…],"error":"title is taken"}
← {"type":"patch","updates":[…]}                       (a broadcast: Info)
← {"type":"error","ref":3,"error":"live: no handler \"c9:0\""}
→ {"type":"event","ref":4,"target":"c2:Inc","event":"push","value":"5"}

A target names a handler the instance rendered by its number, or a method of its component by name (what the component's scripts push), when AllowPush allows it; any other target is an error.

Every event gets exactly one reply carrying its ref — a patch, empty when nothing changed ({"type":"patch","ref":4}), or an error — which is what settles an optimistic push in the browser.

render and patch messages may carry the JS commands components pushed, {"js":[{"id":"c2","ops":[…]}]}, run after the message is applied.

A client joining with {"type":"join","v":2} gets version 2: render and patch carry each instance's render tree, or its change, as Trees (components.md, "Protocol v2"):

→ {"type":"join","v":2}
← {"type":"render","v":2,"trees":[{"id":"c0","full":true,"tree":{"t":0,"s":["<main>","","</main>"],"d":[{"id":"c1"}]}},…]}
→ {"type":"event","ref":1,"target":"c1:0","event":"click"}
← {"type":"patch","ref":1,"trees":[{"id":"c1","tree":{"u":{"2":"1"}}}]}

A page moves to another URL over its connection (both versions):

→ {"type":"navigate","ref":5,"url":"/books?page=2","patch":true}
← {"type":"patch","ref":5,"url":"/books?page=2","trees":[…]}
→ {"type":"navigate","ref":6,"url":"/books/7"}
← {"type":"patch","ref":6,"url":"/books/7","trees":[…]}
→ {"type":"navigate","ref":7,"url":"/report.pdf"}
← {"type":"redirect","ref":7,"url":"/report.pdf"}       (not a page: load it)
View Source
const RootID = "c0"

RootID is the id of the page's own render, which owns the handlers of elements outside any stateful component.

Variables

View Source
var Verify = verifyDefault()

Verify makes new sessions render the components Assign tracking would skip, and fail the event (or message) when the render differs from the one kept: a state field changed without Assign.Set, or Render read something else. It is on in tests and with VUKA_LIVE_VERIFY=1, off with VUKA_LIVE_VERIFY=0. Verifying sessions also check that statics shared by a fingerprint are the same.

Functions

func AllowPush added in v0.7.0

func AllowPush(typ string, methods ...string)

AllowPush lets a component's scripts push its methods by name: typ is the component's struct type as reflect spells it ("main.Counter", "example.com/app/views.Chart"), methods the names its scripts push, replacing any allowed before (none: no pushes by name). The vuka command lists them (each ctx.push("Name") of the scripts) and web registers them from its manifest. A component nothing allows accepts no pushes by name; the handlers its page renders work regardless. Render, Mount, an Update and methods promoted from what it embeds are never pushed. "_set" allows setting its bind: fields (ui.SetBound), as a page script's live.<field>.set(v) does.

func Marshal

func Marshal(m ServerMessage) []byte

Marshal is a message's JSON, markup written as markup (not \u003c…).

func PublicMessage added in v0.10.0

func PublicMessage(err error) (string, bool)

PublicMessage is the message of the first PublicError in err's chain, and whether there is one.

Types

type ClientMessage

type ClientMessage struct {
	Type string `json:"type"`
	Ref  int    `json:"ref,omitempty"`
	V    int    `json:"v,omitempty"` // join: the protocol version the client speaks (1 when 0)
	// navigate: the URL to move to (a path, with its query), and whether
	// it is a patch: the same page with other parameters.
	URL   string `json:"url,omitempty"`
	Patch bool   `json:"patch,omitempty"`
	ui.Event
}

ClientMessage is a message from the browser. Ref, any number the client picks, comes back on the reply.

type JSPush added in v0.2.0

type JSPush struct {
	ID  string          `json:"id"`
	Ops json.RawMessage `json:"ops"`
}

JSPush is JS commands a component pushed (ui.Live.PushJS), for the browser to run once the message carrying it is applied: Ops, the commands' JSON, run with the element whose data-vk-id is ID as This.

type Patch

type Patch struct {
	Updates []Update     `json:"updates,omitempty"`
	Trees   []TreeUpdate `json:"trees,omitempty"`
	JS      []JSPush     `json:"js,omitempty"`
	Error   string       `json:"error,omitempty"`
}

Patch is what an event or message changed: the HTML of each component whose output changed, outermost first (a changed component's update includes the components inside it), and the error a handler returned.

type PublicError added in v0.10.0

type PublicError interface {
	error
	PublicMessage() string
}

PublicError is an error whose message is meant for the person using the page: a transport that hides errors' text from the browser (see Session.SetErrorText) still shows PublicMessage. A form field that doesn't parse is one; an error a web framework answers with a status and a message can be one too.

type Route added in v0.4.0

type Route struct {
	Ctx      context.Context
	Render   func(ctx context.Context) ui.Node
	Redirect string
	Updates  map[ui.Stateful]any
}

Route is the page a URL renders, what a Router gives Navigate: Render, the page's render function, with Ctx (when set) the context of what runs from then on, or, with Render nil, Redirect, a URL for the browser to load (the URL itself when empty). Updates holds messages for a patch: an instance kept for a tag whose props are a key takes the value in its Update method before it renders.

type Router added in v0.4.0

type Router func(url string) Route

Router resolves the URLs a connected page navigates to.

type ServerMessage

type ServerMessage struct {
	Type    string       `json:"type"`
	Ref     int          `json:"ref,omitempty"`
	V       int          `json:"v,omitempty"` // render: 2 for a v2 render
	HTML    string       `json:"html,omitempty"`
	Updates []Update     `json:"updates,omitempty"`
	Trees   []TreeUpdate `json:"trees,omitempty"`
	JS      []JSPush     `json:"js,omitempty"`
	Error   string       `json:"error,omitempty"`
	URL     string       `json:"url,omitempty"` // redirect: the URL to load; a navigation's patch: the page's new URL
}

ServerMessage is a message to the browser.

func PatchMessage

func PatchMessage(ref int, p Patch) ServerMessage

PatchMessage is the message carrying p, the reply to Ref (0 for a broadcast).

type Session

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

Session is one connected page: its render function and the instances of the stateful components it renders. Its methods are safe to call from several goroutines; each runs alone.

func NewSession

func NewSession(ctx context.Context, render func(ctx context.Context) ui.Node) *Session

NewSession is a session rendering the page render returns. ctx is the context of Mount, handlers and Update methods, and of every render.

func (*Session) Adopt added in v0.11.0

func (s *Session) Adopt(ctx context.Context, render func(ctx context.Context) ui.Node) error

Adopt hands the session to a new connection: ctx is the context of its handlers, Update methods and renders from now on, and render (when not nil) the page's render function. The instances stay mounted with their state, so a session kept from a page's HTTP render can serve its live connection without mounting again: the next Render or RenderTrees renders the kept instances, and commands they pushed while mounting are still in JS. The old context must not be needed again — it is usually a finished request's.

func (*Session) Close

func (s *Session) Close()

Close unmounts every instance. The session renders nothing after it.

func (*Session) Component added in v0.8.0

func (s *Session) Component(target string) ui.Stateful

Component is the component an event's target belongs to ("c1:0", "c1:Inc"): the instance's, for a transport to read what its type declares (a gate on the type, refusing every event of it) before the handler runs; nil when the target names no instance, or one that is no component (the page's root).

func (*Session) Deferred added in v0.30.0

func (s *Session) Deferred() []*ui.DeferredLoad

Deferred starts the deferred loads of the pages backed by their islands the session holds (ui.StartDeferred), each once: the transport runs each (Run, with a context ending when the page does) once the page has joined, and hands it to Loaded.

func (*Session) ErrorText added in v0.10.0

func (s *Session) ErrorText(err error) string

ErrorText is err's text for the browser, as SetErrorText says.

func (*Session) Event

func (s *Session) Event(ev ui.Event) (Patch, error)

Event calls the handler ev.Target names with the event's payload, renders the page again, and returns the components whose output changed. A handler's error is the patch's Error; the returned error is the session's: an unknown handler, or a render that failed.

func (*Session) HTML

func (s *Session) HTML() string

HTML is the page's HTML as last rendered, without rendering it again.

func (*Session) Handle

func (s *Session) Handle(m ClientMessage) ServerMessage

Handle answers a client message.

func (*Session) HandleJSON

func (s *Session) HandleJSON(data []byte) []byte

HandleJSON answers a client message's JSON with the reply's.

func (*Session) Handler added in v0.6.1

func (s *Session) Handler(target string) any

Handler is the function an event's target names ("c1:0"): a method value such as c.Inc, for a transport to say which handler an event ran (a development toolbar's "Counter.Inc"); nil when it names none.

func (*Session) Info

func (s *Session) Info(topic string, msg any) (Patch, error)

Info delivers msg to the Update method fitting its type of every instance subscribed to topic (of every instance, for ""), renders the page again if any took it, and returns what changed. An Update's error is the patch's Error.

func (*Session) JS added in v0.2.0

func (s *Session) JS() []JSPush

JS is the commands components pushed while the last Render or RenderTrees mounted them (from a connected Mount), for the join's reply; it returns them once.

func (*Session) Loaded added in v0.30.0

func (s *Session) Loaded(d *ui.DeferredLoad) (Patch, error)

Loaded gives its page what the deferred load d loaded (or its error, as the session's error text says, for the component to read), renders the page again, and returns what changed.

func (*Session) Navigate added in v0.4.0

func (s *Session) Navigate(rt Route, patch bool) (Patch, error)

Navigate makes rt's page the session's, renders it, and returns what changed, as an event's patch. As a patch (patch true), the instances the new page renders where the old one rendered them stay mounted, their props set from its tags, and those rt.Updates names take their message first. Otherwise every instance is unmounted and the new page's mount afresh. The page itself (c0) is the same instance either way, so a client gets only what changed of its markup — on a v2 connection, against the statics it already holds.

The router decides what may be kept, not the client asking for a patch: a patch with no rt.Updates is a navigation, every instance mounting afresh, and an instance whose tag rt.Updates names is kept only when an Update method of its takes the message — else it mounts afresh, so its Mount sees the new URL. A component that checks in Mount whether the visitor may see what its props name must check again in that Update.

An error — a Mount's or an Update's, or the render's — fails the navigation: Navigate returns it, every instance is unmounted, and the page has no handlers until it navigates or joins again (a browser loads the URL itself, getting the route's own answer).

func (*Session) Render

func (s *Session) Render() (string, error)

Render renders the whole page, mounting the components it meets for the first time, and returns its HTML: what the page's container holds.

func (*Session) RenderTrees

func (s *Session) RenderTrees() ([]TreeUpdate, error)

RenderTrees makes the session a v2 one and renders the whole page as every instance's render tree, the page's first: what a v2 client joins with. From then on patches carry Trees instead of Updates. It starts the connection afresh: every statics id and kept string is forgotten.

func (*Session) SetErrorText added in v0.10.0

func (s *Session) SetErrorText(text func(err error) string)

SetErrorText sets how the session turns an error into the text a message to the browser carries: what a handler, Mount or Update returned, what it panicked with, and a render's or the session's own failure. Without it the text is err.Error(), which may say more than the browser should see (a query, a path, a panic's value): a transport serving pages to the public sets it, showing PublicMessage (or a generic message) and logging the rest.

func (*Session) SetRouter added in v0.4.0

func (s *Session) SetRouter(r Router)

SetRouter sets what the navigate message renders. Without a router every navigation is a full page load: the reply is a redirect.

func (*Session) Topics

func (s *Session) Topics() []string

Topics is every topic an instance subscribes to, sorted: what the transport routes to Info. It changes as instances come and go.

type TreeUpdate

type TreeUpdate struct {
	ID   string          `json:"id"`
	Full bool            `json:"full,omitempty"`
	Tree json.RawMessage `json:"tree,omitempty"`
	HTML string          `json:"html,omitempty"`
}

TreeUpdate is an instance's change on a v2 connection (see RenderTrees): its whole tree (Full), the change to the tree the client holds, or its whole HTML when that is smaller. Tree is JSON, encoded as components.md ("Protocol v2") describes.

type Update

type Update struct {
	ID   string `json:"id"`
	HTML string `json:"html"`
}

Update is a component's new HTML. ID is the data-vk-id of the element it replaces; RootID is the page itself, whose HTML replaces the container's content.

Directories

Path Synopsis
internal
demo
Package demo is stateful components compiled by the Vuka compiler, for the live package's property tests and benchmarks.
Package demo is stateful components compiled by the Vuka compiler, for the live package's property tests and benchmarks.
templdemo
templ: version: v0.3.1020
templ: version: v0.3.1020
Package livetest is a reference client of the live protocol, in Go: it keeps what a browser runtime keeps — the page's HTML for v1, each instance's render tree for v2 — and applies the server's messages the way a runtime must, so tests can check that what a client ends up with is what the server rendered.
Package livetest is a reference client of the live protocol, in Go: it keeps what a browser runtime keeps — the page's HTML for v1, each instance's render tree for v2 — and applies the server's messages the way a runtime must, so tests can check that what a client ends up with is what the server rendered.

Jump to

Keyboard shortcuts

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