delta

package
v0.5.7 Latest Latest
Warning

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

Go to latest
Published: Aug 11, 2026 License: Apache-2.0 Imports: 12 Imported by: 2

Documentation

Overview

Package delta compares two renders of one chain and expresses the difference as operations a browser applies, so a screen already showing the document reaches the server's fresh render without a full page load.

It is the update half of htmlbind. The render half exposes only the observation seam — htmlbind.Collector — and everything derived from it lives here: manifests, keyed validators, canonical input encoding, and deltas. The split is what it costs, or rather does not cost: an application that only renders documents links none of the hashing and encoding this package needs to authenticate validators.

Index

Constants

View Source
const (
	// OpReplace swaps a boundary's own markup, holes and all.
	OpReplace = "replace"
	// OpChildren says a boundary's own markup is unchanged and its nested
	// boundaries are now these, in this order.
	//
	// It carries no HTML. The client reconciles what it already holds against
	// the list: an id it holds and the list keeps stays, moving if the order
	// moved; an id the list drops is removed; an id it does not hold arrives as
	// its own operation in the same response.
	//
	// It exists because appending one row to a list is the ordinary event on a
	// live screen, and expressing it by replacing the parent costs the whole
	// list of holes — measured at 7,383 bytes to add one 76-byte row to a
	// hundred, where the list of ids costs a few hundred.
	OpChildren = "children"
)

Variables

This section is empty.

Functions

func CanonArray

func CanonArray[T any](values []T, encode func(T) string) string

CanonArray encodes a slice, delegating each element.

func CanonBool

func CanonBool(value bool) string

CanonBool encodes a bool.

func CanonBytes

func CanonBytes(value []byte) string

CanonBytes encodes a byte slice.

func CanonFloat

func CanonFloat(value float64) string

CanonFloat encodes a float64.

func CanonInt

func CanonInt(value int) string

CanonInt encodes an int.

func CanonJoin

func CanonJoin(parts ...string) string

CanonJoin concatenates the encoded parameters of one component in declaration order.

func CanonOptional

func CanonOptional[T any](value *T, encode func(T) string) string

CanonOptional encodes an absent value distinctly from any present one.

func CanonRecord

func CanonRecord(fields string) string

CanonRecord wraps the already encoded fields of a declared record.

func CanonString

func CanonString[T ~string](value T) string

CanonString encodes any string-kinded value, covering plain strings, decimals, generated enums, and the trusted string types.

func CanonTime

func CanonTime(value time.Time) string

CanonTime encodes an instant in UTC, so two equal instants in different zones cannot produce two validators.

func CanonURL

func CanonURL(value url.URL) string

CanonURL encodes a URL through its string form.

func DeltaStreamHead

func DeltaStreamHead(wrappers []htmlbind.Wrapper, leaf htmlbind.Fragment, options ...htmlbind.Option) ([]string, error)

DeltaStreamHead is the merged head of a chain, which a streamed delta sends before any region so a newly reachable component's stylesheet is installed before the markup that needs it.

func RenderDeltaStream

func RenderDeltaStream(ctx context.Context, key []byte, known Manifest, wrappers []htmlbind.Wrapper, leaf htmlbind.Fragment, options ...htmlbind.Option) iter.Seq2[DeltaRecord, error]

RenderDeltaStream renders the chain and yields the boundaries that changed, then each await boundary as it settles.

It is the streaming counterpart of RenderDelta, and the difference is worth stating: RenderDelta blocks until every await settles and compares finished regions, while this yields each region with its fallback in place and follows with the replacements. A slow dependency delays only its own region.

The document markup outside every boundary is discarded, because a delta reuses the browser's existing document shell.

Types

type Delta

type Delta struct {
	// Manifest is the full state after the operations apply. Unchanged
	// boundaries appear here without an operation, which is the entire point.
	Manifest Manifest
	// Operations are ordered outermost first, so a target always exists by the
	// time a later operation addresses something inside it.
	Operations []Operation
	// Head is the merged head of the new composition. A delta reuses the live
	// document shell, so a component appearing for the first time has no link
	// tag installed; the client diffs this against the head it already has and
	// waits for new stylesheets before applying content.
	Head []string
}

Delta is the result of comparing a fresh render against what the browser already holds.

func RenderDelta

func RenderDelta(key []byte, known Manifest, wrappers []htmlbind.Wrapper, leaf htmlbind.Fragment, options ...htmlbind.Option) (Delta, error)

RenderDelta renders the chain and returns only the boundaries whose markup differs from known.

The render always runs: a component may read anything, so equal inputs do not prove equal output. Only transmission is skipped, never execution. An empty known manifest is not an error and yields every boundary, which is how a freshly loaded page gets its first comparable state.

The document markup outside every boundary is discarded, because a delta reuses the browser's existing document shell.

type DeltaRecord

type DeltaRecord struct {
	// Operation names a boundary the comparison produced. Its HTML is empty for
	// an unchanged boundary, which still carries its validator so the client can
	// rebuild the whole manifest from what it received.
	Operation *Operation
	// Frame is the validator of the boundary Operation names.
	Frame string
	// Children digests that boundary's nested boundary ids, in order, and Parent
	// names the boundary enclosing it.
	//
	// A manifest entry has three fields beside its id, and a client rebuilding
	// one from a stream must be able to return all three. Without Children every
	// list looks reordered on the request after next; without Parent a removal
	// cannot be attributed to the boundary that would report the survivors, so a
	// shrinking list falls back to replacing the outermost boundary — expensive
	// in exactly the case the children operation exists to make cheap.
	Children string
	Parent   string
	// Completion is an await boundary that settled, addressed by the
	// placeholder written during the initial pass rather than by an instance id.
	Completion *htmlbind.Content
	// Signal is an instruction a live source emitted beside its deliveries. It
	// addresses no boundary and replaces nothing, so it carries no validator
	// and no operation; the client dispatches it by name.
	Signal *htmlbind.Signal
}

DeltaRecord is one item of a streamed delta: either a boundary the browser must install, or an await boundary that settled after the initial pass.

The two travel on one sequence because they are the same event to a client: a region of the page is ready. Splitting them in the protocol would mean two consumers applying markup to one document.

type Instance

type Instance struct {
	// ID identifies the same logical instance across two renders. It is derived
	// from the chain position, so changing search parameters does not rename it.
	ID string
	// ParentID is the enclosing boundary, empty for the outermost one.
	ParentID string
	// ComponentID is the generated declaration identity and version.
	ComponentID string
	// InputValidator digests the canonical declared inputs. It predicts equal
	// output only for a component whose output depends on nothing else, so it
	// is a cache and diagnostic key rather than authority to skip a render.
	InputValidator string
	// FrameValidator digests this boundary's own rendered bytes, excluding the
	// output of nested boundaries and the holes where they sit. A layout whose
	// frame is unchanged can keep its DOM while its child is replaced.
	FrameValidator string
	// ChildrenValidator digests the ids of the nested boundaries, in order.
	//
	// It is separate from the frame because the two have different remedies. A
	// changed frame means the component's own markup moved, and the parent is
	// replaced. A changed children list means a region was inserted, removed,
	// or reordered, and the parent's own DOM is fine — the client is told the
	// new order and reconciles what it already holds. Folding the second into
	// the first would make every appended list row cost the whole list.
	//
	// It is empty for a boundary with no nested boundary, which is most of them.
	ChildrenValidator string
}

Instance is one update boundary as it appeared in a render.

type Manifest

type Manifest struct {
	Instances []Instance
}

Manifest is the update state of one render, in document order.

func CollectChain

func CollectChain(w io.Writer, key []byte, wrappers []htmlbind.Wrapper, leaf htmlbind.Fragment, options ...htmlbind.Option) (Manifest, error)

CollectChain renders like htmlbind.RenderChain and additionally returns the update manifest of the boundaries it wrote. Every chain member declaring a boundary becomes one instance, so the manifest describes the layout chain rather than every component call.

key authenticates the returned validators. A digest published to a browser must be keyed, because an unkeyed hash of low entropy content lets anyone confirm a guess by comparing digests. The same key must be used for two renders that are to be compared; changing it forces a full render, which is the intended effect of a key rotation.

Collecting emits the instance attribute on each boundary's root element, so its output differs from RenderChain by exactly those attributes.

func (Manifest) Changed

func (m Manifest) Changed(previous Manifest) []Instance

Changed reports the instances whose frame differs from the previous render, plus those the previous render did not contain. It is the server-side half of a delta: everything it returns must be sent, everything else may be omitted.

func (Manifest) Find

func (m Manifest) Find(id string) (Instance, bool)

Find returns the instance with the given ID.

type Operation

type Operation struct {
	// Kind is replace. Insert, remove, and move arrive with structural
	// boundaries; until then a structural change is expressed by replacing an
	// enclosing boundary.
	Kind string
	// InstanceID names the boundary the operation targets.
	InstanceID string
	// HTML is the boundary's own markup, including its root element, with an
	// inert placeholder where each nested boundary sits rather than that
	// boundary's bytes.
	HTML string
	// Sequence addresses this fragment's static half, and Values are the varying
	// half a client walks it with. Together they reproduce HTML exactly, which
	// is what lets a caller send one or the other: the statics travel once per
	// client and the values travel per render.
	//
	// Both are empty for a boundary whose sequence could not be derived, which
	// is the case a caller falls back to HTML for.
	Sequence string
	Values   []string
	// Boundaries names the nested boundaries appearing as holes in HTML.
	//
	// It is what tells a hole to fill from one to retain: an id also carrying an
	// operation in this response is replaced, and one that does not is a
	// boundary the client already holds and moves its live node into. Nothing in
	// the markup distinguishes the two, and without the list a missing fragment
	// would be indistinguishable from a truncated response.
	Boundaries []string
}

Operation is one change the browser applies to reach the server's render.

Jump to

Keyboard shortcuts

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