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
- Variables
- func AllowPush(typ string, methods ...string)
- func Marshal(m ServerMessage) []byte
- func PublicMessage(err error) (string, bool)
- func StreamAttrs(compact []byte) (string, error)
- type ClientMessage
- type JSPush
- type NoFormError
- type NoHandlerError
- type Patch
- type PublicError
- type Route
- type Router
- type ServerMessage
- type Session
- func (s *Session) Adopt(ctx context.Context, render func(ctx context.Context) ui.Node) error
- func (s *Session) Close()
- func (s *Session) Component(target string) ui.Stateful
- func (s *Session) Deferred() []*ui.DeferredLoad
- func (s *Session) ErrorText(err error) string
- func (s *Session) Event(ev ui.Event) (Patch, error)
- func (s *Session) HTML() string
- func (s *Session) Handle(m ClientMessage) ServerMessage
- func (s *Session) HandleJSON(data []byte) []byte
- func (s *Session) Handler(target string) any
- func (s *Session) Info(topic string, msg any) (Patch, error)
- func (s *Session) JS() []JSPush
- func (s *Session) LoadDeferred(ctx context.Context) error
- func (s *Session) Loaded(d *ui.DeferredLoad) (Patch, error)
- func (s *Session) Navigate(rt Route, patch bool) (Patch, error)
- func (s *Session) Render() (string, error)
- func (s *Session) RenderTrees() ([]TreeUpdate, error)
- func (s *Session) SetErrorText(text func(err error) string)
- func (s *Session) SetRouter(r Router)
- func (s *Session) Submit(id string, values url.Values) (Patch, error)
- func (s *Session) Topics() []string
- func (s *Session) Trim()
- type StreamBatch
- type TreeUpdate
- type Update
Constants ¶
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 )
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 v2 join may name extensions the client speaks, {"ext":["stream"]}: with "stream", a stream container's attributes go as one compact dynamic, {"st":{…}} (streamwire.go), which the client writes back as the attributes.
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)
const ExtStream = "stream"
ExtStream is the v2 extension a client joins with to get stream containers' attributes in their compact form.
const RootID = "c0"
RootID is the id of the page's own render, which owns the handlers of elements outside any stateful component.
Variables ¶
var Extensions = []string{ExtStream}
Extensions are the v2 extensions this session speaks.
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 ¶
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, its hooks, 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 ¶
PublicMessage is the message of the first PublicError in err's chain, and whether there is one.
func StreamAttrs ¶ added in v2.17.0
StreamAttrs is a container's attributes written back from their compact form (the object of {"st":…}) as the server renders them: what a client joining with ExtStream does, the browser's runtime in JavaScript.
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)
// Ext is a v2 join's extensions the client speaks (Extensions): "stream",
// stream containers' attributes in their compact form.
Ext []string `json:"ext,omitempty"`
// 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 ¶
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 NoFormError ¶ added in v2.8.0
type NoFormError struct{ ID string }
NoFormError is the error of a submit naming a form the page doesn't render (Session.Submit).
func (*NoFormError) Error ¶ added in v2.8.0
func (e *NoFormError) Error() string
type NoHandlerError ¶ added in v2.6.0
type NoHandlerError struct {
Target string // the event's target, "c3:75"
Component string // the component's type ("views.Table[…]"), "page" for the page's handlers, "" when no component has the id
// contains filtered or unexported fields
}
NoHandlerError is the error of an event whose target names no handler: the browser holds a handler id of a render the session no longer keeps. Its PublicMessage is the short form (live: no handler "c3:75"); Error says whose handler it was and why it's gone, naming the component's Go type, for the server's log and for development.
func (*NoHandlerError) Error ¶ added in v2.6.0
func (e *NoHandlerError) Error() string
func (*NoHandlerError) PublicMessage ¶ added in v2.6.0
func (e *NoHandlerError) PublicMessage() string
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"`
// Result is what the handler returned besides an error, as a Push
// step's reply carries it for the browser to put Into its target
// ({"html": …}, {"value": …}, …); none when it returned nothing.
Result json.RawMessage `json:"result,omitempty"`
// Err is the handler's error as it returned it, for a transport to
// read (a plain form post answering 422, or a redirect); never sent.
Err error `json:"-"`
// Streams is the stream batches the patch carries, for a development
// toolbar; never sent.
Streams []StreamBatch `json:"-"`
}
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 ¶
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 ¶
type Route struct {
Ctx context.Context
Render func(ctx context.Context) ui.Node
Redirect string
Updates map[ui.Stateful]any
// Keep keeps, on a navigation, the instances the new page renders
// where the old one rendered them, as a patch does, instead of
// unmounting every one first; those it doesn't render are unmounted
// after it renders. A router keeps a layout's components this way,
// giving the page below the layout a ui.Keyed key of its own per
// page, so that the page's components mount afresh.
Keep bool
// URL is the page's URL, what its components' @ui.URL fields read,
// when Ctx doesn't carry it (ui.WithPageURL); the navigate message
// sets it.
URL string
}
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 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"`
Result json.RawMessage `json:"result,omitempty"` // an event's: its Push step's result (Patch.Result)
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 ¶
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 ¶
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 ¶
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 ¶
func (s *Session) Deferred() []*ui.DeferredLoad
Deferred starts the deferred loads of the pages and components the session holds (ui.StartDeferred) not started yet, each once: the transport runs each (Run, with a context ending when the page does) once the page has joined, and after each event for the components it mounted, and hands it to Loaded.
func (*Session) Event ¶
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) Handle ¶
func (s *Session) Handle(m ClientMessage) ServerMessage
Handle answers a client message.
func (*Session) HandleJSON ¶
HandleJSON answers a client message's JSON with the reply's.
func (*Session) Handler ¶
Handler is the function an event's target names ("c1:0", "c1:row-42: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 ¶
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 ¶
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) LoadDeferred ¶ added in v2.17.0
LoadDeferred runs the deferred loads not started yet, concurrently, with ctx, applies them and renders the page again: a page rendered for a client that has no connection to receive them over later (a form posted without script) shows them loaded, or failed.
func (*Session) Loaded ¶
func (s *Session) Loaded(d *ui.DeferredLoad) (Patch, error)
Loaded gives its instance 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: nothing when the instance is gone, its page navigated away from or its tag no longer rendered.
func (*Session) Navigate ¶
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 ¶
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 ¶
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 component's Go type): a transport serving pages to the public sets it, showing PublicMessage (or a generic message) and logging the rest, and the full text in development. The session's own errors keep their detail out of PublicMessage: a NoHandlerError's is `live: no handler "c3:75"`, a payload's that doesn't parse names no Go type.
func (*Session) SetRouter ¶
SetRouter sets what the navigate message renders. Without a router every navigation is a full page load: the reply is a redirect.
func (*Session) Submit ¶ added in v2.8.0
Submit submits the form of the page whose identity is id (its _vk_form field) with values, as a plain HTML POST of it does: the submit handler its last render registered runs as the form's submit event would, and the page renders again. The patch's Err is the handler's: ui.ErrFormInvalid when the form didn't pass its checks, a redirect, or the handler's own.
func (*Session) Topics ¶
Topics is every topic an instance subscribes to, sorted: what the transport routes to Info. It changes as instances come and go.
func (*Session) Trim ¶ added in v2.16.0
func (s *Session) Trim()
Trim releases what the session can rebuild, for a page sitting idle: each instance's shadow, which its next render makes again from its last tree. It changes no reply. It does nothing to a session whose last render failed, whose trees aren't what the client holds.
type StreamBatch ¶ added in v2.17.0
type StreamBatch struct {
ID string
Component string
ui.StreamBatch
}
StreamBatch is a stream's batch a reply carried, for a development toolbar: the instance, its component and what the batch did.
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.
Source Files
¶
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. |