Documentation
¶
Overview ¶
Package engine is the notebook runtime. Generated code imports it, so its API is public and versioned from the first commit.
This milestone (M2) defines only the surface the generated registry needs to reference and compile against: the Node execution interface, the value types that flow across edges, and the per-cell metadata descriptor. The scheduler, head, cache, and event stream arrive in M3 — but the types here are the stable contract those layers and the generated code both depend on.
Hard constraint: engine must never import net/http (nor any transport). It will emit events on a channel that engine/server subscribes to, which is what keeps headless, WASM, and batch modes free. Nothing in this package reaches for the network or a server.
Index ¶
- Constants
- func CoerceWire(decoded any) (any, bool)
- func StampLeaf(v any, sym string) any
- type Bounded
- type CellID
- type CellMeta
- type Config
- type Epoch
- type Event
- type Handle
- type Head
- type Inputs
- type Key
- type LeafID
- type LeafType
- type Node
- type Notebook
- type Optioned
- type Outputs
- type Provenance
- type Reconciler
- type Rendered
- type Runtime
- type State
- type Store
- type Symbol
- type Value
- type Viewable
- type WidgetColumn
- type WidgetMeta
- type WidgetView
- type WireEvent
Examples ¶
Constants ¶
const WidgetMIME = "application/x-notebook-widget+json"
WidgetMIME tags an Out whose Data is a JSON WidgetView — the client dispatches on the cell's static Kind (CellMeta) and reads this live state.
Variables ¶
This section is empty.
Functions ¶
func CoerceWire ¶
CoerceWire homogenizes a decoded-JSON selection into the clean Go value a widget's Reconcile expects, so a cell stays an ordinary Go function that never touches wire shapes. It is the general form of the scalar coercer — the write path is a human at human speed, so a little recursion here is free.
A selection's Go type is not the widget's field type: a Multi[Theme]'s selection is []string (labels — the client picks by Label(), and the label→Theme mapping lives in the notebook's Reconcile, not here), a Range's is []float64 (endpoints), a Select's is a string, a Table[Lot]'s is []map (rows, as objects — the map→Lot mapping lives in the notebook's Reconcile too). JSON decodes those as []any / map[string]any / json.Number; this strips the wire's any-boxing and json.Number down to []string / []float64 / []map[string]any / string / bool, recursing through slices and maps so a Reconcile(saved any) can assert a concrete shape. It NEVER guesses a domain type (it can't — engine knows nothing of Lot); it only removes the wire encoding.
ok is false when a value can't be homogenized — a null, a mixed scalar array, or a shape the wire vocabulary doesn't cover. This is the load-bearing discipline: a shape the coercer doesn't understand is a real client/leaf mismatch that MUST surface (the caller logs and refuses the set), NEVER a silent drop. A silent drop is precisely the bug that killed the grip write and shipped it to production; a coercer that discards what it doesn't recognize is a factory for that bug. So there is no passthrough default: every branch is either a known shape or an explicit failure.
Example ¶
A widget selection arrives from a transport as decoded JSON — numbers as float64, arrays as []any. CoerceWire homogenizes that wire shape into the clean Go value a cell's Reconcile expects (here, a []string of labels), or reports false when the shape is a genuine mismatch. It is the one place untrusted client input crosses into the engine.
package main
import (
"fmt"
"github.com/scttfrdmn/go-notebook/engine"
)
func main() {
// What a Multi-select's labels look like after JSON decoding on the /set path.
selection := []any{"City", "Duplo"}
clean, ok := engine.CoerceWire(selection)
fmt.Printf("%v %T ok=%v\n", clean, clean, ok)
// A mixed-kind array is a real client/leaf mismatch and fails loud, never
// silently dropped.
_, ok = engine.CoerceWire([]any{"City", 3.0})
fmt.Printf("mixed ok=%v\n", ok)
}
Output: [City Duplo] []string ok=true mixed ok=false
func StampLeaf ¶
StampLeaf stamps a draggable widget with the leaf symbol it belongs to, if the value exposes the runtime seam WithLeaf(string) → same-type. It is the write-direction twin of the read probes (Render/Bounds/WidgetView): the notebook exposes a method of an agreed shape, the runtime calls it, and the runtime never names the widget's type. A grip is drawn by a cell that does not own its leaf (curvefit's editor draws handles for the ctrl leaf), so the leaf's identity must ride WITH the value across that cell boundary — this is how it gets there. WithLeaf has value semantics (returns a copy), so the stamped value flows downstream as an ordinary value with no hidden mutation.
If v has no WithLeaf seam (every non-draggable widget), v is returned unchanged. The seam is for the RUNTIME only; a notebook that calls it is a smell (the runtime writes leaf identity, the notebook reads it via Grip).
Types ¶
type Bounded ¶
type Bounded interface {
Bounds() (lo, hi float64)
}
Bounded is the input capability this milestone supports: a value that declares a numeric range renders as a ranged control (a slider). A type satisfies it structurally — no import of this package is required for a domain type to be a slider.
type CellID ¶
type CellID string
CellID identifies a cell — the cell's function name, unique within a notebook.
type CellMeta ¶
type CellMeta struct {
// ID is the cell this metadata describes.
ID CellID
// Leaf is the symbol this cell produces when it is an input control (a leaf
// the user edits). Empty for non-leaf cells. The head, the UI, and --set
// all address a leaf by this symbol — a leaf is identified by the symbol it
// produces, not by the cell's name.
Leaf Symbol
// Label is the cell's display label (the first sentence of its doc
// comment, or its function name).
Label string
// Directives are the flattened //notebook:k=v pairs. A bare directive
// token (e.g. "slider") is recorded with an empty value.
Directives map[string]string
// In lists the cells whose output this cell consumes — its upstream
// producers, derived from the wired parameters. Presentation-only: it lets
// the view draw the dependency graph. It is not used for execution (the
// engine wires by symbol, from the generated registry), and carries no Go
// types, so it stays on the transport-agnostic metadata boundary.
In []CellID `json:",omitempty"`
// Source is the cell's verbatim source (doc comment through closing brace),
// so the view can show "a cell is a function," read-only. Presentation-only,
// like the fields above; never parsed or executed.
Source string `json:",omitempty"`
// Widget is the STATIC control descriptor for a leaf: which kind of control
// to render, decided from the leaf's TYPE at codegen (a Multi[Theme] is
// always a multiselect). It is the dispatch key; the live state (current
// selection, options, bounds) rides the cell's value on the wire, not here.
// Empty for non-widget leaves (a scalar/Bounded slider). Presentation-only,
// like the fields above.
Widget *WidgetMeta `json:",omitempty"`
// Type is the leaf's Go result type, for a program that drives set(): the
// named type it declares ("PerHour") and the basic kind that name resolves to
// ("float64"). Empty for non-leaf cells. It surfaces the schema the set()
// coercer already enforces — a consumer can read it to validate a value's
// shape before setting — without any new codegen artifact. Contract/identity
// metadata on the same transport-agnostic boundary as In/Source/Widget: the
// scheduler, head, and cache never read it, and it is omitempty so it stays
// inert to any consumer that ignores it.
Type *LeafType `json:",omitempty"`
}
CellMeta is the presentation metadata for a cell: everything the view needs that is not part of execution. It is flattened by codegen from doc comments and //notebook: directives, so nothing is parsed at runtime.
type Config ¶
type Config struct {
// Nodes are the executable cells.
Nodes []Node
// Leaves are the input symbols the user writes (slider roots, etc.).
Leaves []LeafID
// Levels are the precomputed topological levels over all cells, from the
// graph's Plan. Cells within a level are independent.
Levels [][]CellID
// Serial disables goroutine fan-out: cells within a level run one at a
// time. Used by --serial to restore per-cell stdout for debugging.
Serial bool
}
Config describes the static graph shape the runtime executes. It is derived from the notebook graph (by the caller) and is independent of go/types — the runtime consumes plain data, just like the IR.
type Epoch ¶
type Epoch uint64
Epoch counts edits. Each write to the head bumps the epoch; a wave carries its epoch so superseded results can be discarded before they commit.
type Event ¶
type Event struct {
Epoch Epoch
Cell CellID
State State
// Out is the cell's rendered output, non-nil only when the cell's value is
// Renderable and the cell reached StateDone.
Out *Rendered
// Value is the cell's typed Go value for the wave — the same value Out was
// rendered from, before any string readout. It is IN-PROCESS ONLY: the typed
// projection of a subscription, delivered to Go consumers via
// [Runtime.SubscribeValues], and it NEVER crosses a wire. [ToWire] ignores it
// by construction, so a transport cannot marshal an arbitrary Go value; a
// wire-bound consumer sees only the rendered {mime, data}. Populated only
// when State is StateDone (nil on error/blocked/stale).
Value any
// Err is the error message when State is StateError.
Err string
}
Event is a single cell state transition within a wave. The engine emits these on the channel returned by Runtime.Subscribe; engine/server (and headless drivers) consume them. The engine itself never imports a transport — this channel is the whole seam that keeps headless, WASM, and batch modes free.
type Handle ¶
type Handle struct {
// Leaf is the input this grip writes when manipulated.
Leaf LeafID
}
Handle is a direct-manipulation grip: a renderer-emitted, runtime-bound reference to a leaf a drag should write. Unused this milestone — the type exists only so Rendered can carry it without a later breaking change.
type Head ¶
type Head struct {
// contains filtered or unexported fields
}
Head is the only mutable state in the system: the current value of every input leaf, plus the epoch that increments on each write.
Every leaf write goes through the single Head.Set chokepoint. Sliders call it today; timers, buttons, and grips are all just callers of it later. This is a hard architectural constraint — if writes scatter, those features cannot be added additively — so there is deliberately no other way to mutate a leaf.
A wave reads leaf values only from an immutable Head.Snapshot, never from the live map. That is what makes propagation glitch-free: no cell can observe a half-applied edit, because there is no shared mutable view to observe.
Example ¶
Head is the single mutation chokepoint: every leaf edit — a slider, a timer, a grip drag — goes through Set, which records the value and bumps the epoch. A wave reads only from an immutable Snapshot, never the live map, which is what makes propagation glitch-free.
package main
import (
"fmt"
"github.com/scttfrdmn/go-notebook/engine"
)
func main() {
h := engine.NewHead()
e1 := h.Set("servers", 80)
e2 := h.Set("servers", 120) // an edit bumps the epoch
v, ok := h.Get("servers")
fmt.Printf("servers=%v ok=%v epoch %d→%d\n", v, ok, e1, e2)
}
Output: servers=120 ok=true epoch 1→2
func NewHead ¶
func NewHead() *Head
NewHead returns an empty in-memory head. Use OpenHead to persist.
func OpenHead ¶
OpenHead returns a head backed by the file at path, restoring any previously persisted leaf values. A missing file is not an error — it yields an empty head that will create the file on the first Head.Set. This is what makes process restart a non-event: the only state is a few leaf values on disk.
func (*Head) Get ¶
Get returns the current value of a leaf and whether it is set. It is a convenience for callers that need a single value (e.g. reconciling a saved selection); waves use Snapshot instead.
func (*Head) Set ¶
Set is the ONE place a leaf is written. It records the value, bumps the epoch, persists (if backed by a file), and returns the new epoch so the caller can start a wave tagged with it.
Keeping this the single chokepoint is load-bearing: a timer writing a tick, a button incrementing a counter, and a grip dragging a control point are all just Set calls, so they need no new mutation path.
type Key ¶
type Key struct {
Cell CellID
Vers string // input versions, order-stable, joined into a comparable key
}
Key identifies a cached cell result by the cell and the versions of its inputs. Keying on versions rather than value hashes means arbitrary Go values never have to be hashed: same cell, same input versions ⇒ same output.
type LeafID ¶
type LeafID = Symbol
LeafID identifies an input leaf by the symbol it produces. A leaf is a cell whose value the user (or, later, a timer or grip) writes directly.
type LeafType ¶ added in v0.4.0
LeafType names a leaf's Go result type in two coordinates, so a client can decide what a set() value must look like without knowing Go: Name is the type as written in the source (a named type like "PerHour", or a bare "int"), and Underlying is the basic kind that name resolves to through go/types ("int", "float64", "bool", "string", …). Underlying is empty for a composite or interface leaf (a Table row, a Multi selection), where no single scalar kind describes the settable value. Both are type-derived at codegen, never guessed.
type Node ¶
type Node interface {
// ID returns the cell's identifier.
ID() CellID
// In returns the input symbols the cell consumes (wired parameters only;
// injected and delayed parameters are supplied by the runtime).
In() []Symbol
// Out returns the result symbols the cell produces.
Out() []Symbol
// Pure reports whether the cell is safe to cache: false if it transitively
// touches time, randomness, or I/O. Derived by the toolchain, never
// declared. A conservative false only costs a cache miss.
Pure() bool
// Run executes the cell against its inputs. The context is honored by cells
// that ask for one; a panic inside Run is recovered by the scheduler (M3)
// into a per-cell error state, so implementations need not.
Run(ctx context.Context, in Inputs) (Outputs, error)
}
Node is the unit of execution. Generated cells are one implementation; an interpreted or remote executor can be another without the scheduler knowing. Keeping this an interface (not a struct) is the seam that lets alternate executors exist later.
type Notebook ¶
type Notebook struct {
Cells []Node
Meta []CellMeta
Provenance Provenance
}
Notebook is the presentation bundle a transport needs to render a notebook: its executable cells, the per-cell metadata, and the build provenance. It is the carrier that lets metadata grow (Meta, then Provenance) without churning every transport signature. The engine does not execute Notebook itself — it executes Cells via Config; Meta and Provenance are display-only.
type Optioned ¶
type Optioned interface {
Options() []string
}
Optioned is declared but unused this milestone. It exists so the set of capability probes is a list that grows by one entry per control kind, rather than a special case bolted on later.
type Provenance ¶
type Provenance struct {
// SourceHash is the content hash of the notebook source file(s) — the
// identity of what was built, independent of its path or filename.
SourceHash string `json:"sourceHash"`
// Commit and Dirty describe the git state, when a repo is present. Dirty
// means the working tree had uncommitted changes at build time.
Commit string `json:"commit,omitempty"`
Dirty bool `json:"dirty,omitempty"`
// BuiltAt is the build time (RFC3339).
BuiltAt string `json:"builtAt,omitempty"`
// GoVersion is the toolchain that compiled the artifact.
GoVersion string `json:"goVersion,omitempty"`
}
Provenance records what produced this artifact, so a frozen binary — served months later from a login node, or a .wasm on a page — can say what it is. A path is not a handle; this is the handle. It is presentation/identity data the engine carries and NEVER reads: the scheduler, head, and cache are untouched by it. Codegen fills it at build time; the transports display it. All fields are best-effort — a notebook outside a git repo is a normal case, so SourceHash alone (the content identity) is always present and the git fields may be empty.
type Reconciler ¶
Reconciler merges a saved selection into a freshly computed schema. When a cell recomputes a widget's bounds/options, the head still holds the user's selection; reconciliation is per-widget-kind, not universal:
- a range clamps its saved selection into the new bounds,
- a multi-select filters out options that no longer exist,
- a draggable resets on an arity change.
Range is the only implementation this milestone; the interface exists so the others are additive.
func AsReconciler ¶
func AsReconciler(v any) (Reconciler, bool)
AsReconciler probes v for the Reconciler capability.
type Rendered ¶
type Rendered struct {
// MIME is the content type, e.g. "image/svg+xml" or "text/markdown".
MIME string
// Data is the rendered content.
Data string
// Grips carries declarative direct-manipulation handles. Always empty this
// milestone; the field exists so grips are an additive change (a renderer
// emits them, the runtime binds them to leaf writes) rather than a
// signature change to every Renderable.
Grips []Handle
}
Rendered is a MIME-tagged output blob the transport can display without knowing any Go types: the client receives {cell, mime, data} and is entirely ignorant of the value that produced it.
func AsRendered ¶
AsRendered probes v for renderability and returns its rendered form. The bool reports whether v was renderable at all — a scalar that does not render falls back to a caller-chosen default readout.
The probe is structural and uses reflection, and it must, because of the design's central property: a notebook file imports nothing from this project, so a cell that renders defines its OWN Rendered-shaped struct (e.g. capacity.Rendered) and returns that. Go's interface satisfaction requires an exact return-type match, so a static `interface{ Render() engine.Rendered }` could never match `Render() capacity.Rendered` — the two named types differ. Reflection is therefore not a shortcut here; it is the only way to honor "structural probe across independently-defined types."
A value is renderable iff it has a method `Render()` taking no arguments and returning a single struct value with string fields named MIME and Data. That is the shape the design specifies; any type matching it renders for free, with no import and no registration. The reflect cost is one method call per rendered output — negligible beside building the SVG/markdown it returns.
Example ¶
AsRendered is the structural probe the runtime uses to decide how to draw a cell's output: any value whose type has a Render() Rendered method is drawn as its MIME-tagged content. The notebook declares nothing — the method is the whole contract.
package main
import (
"fmt"
"github.com/scttfrdmn/go-notebook/engine"
)
func main() {
// A notebook's own type, with a Render method the engine discovers by shape.
out := chart{title: "load"}
r, ok := engine.AsRendered(out)
fmt.Printf("%q ok=%v\n", r.MIME, ok)
// A plain value with no Render method is not renderable (it falls to the
// scalar-readout rung instead).
_, ok = engine.AsRendered(42)
fmt.Printf("scalar ok=%v\n", ok)
}
// chart is a stand-in for a notebook's own renderable type — the engine finds it
// by the Render() method's shape, never by importing this package.
type chart struct{ title string }
func (c chart) Render() engine.Rendered {
return engine.Rendered{MIME: "image/svg+xml", Data: "<svg><!-- " + c.title + " --></svg>"}
}
Output: "image/svg+xml" ok=true scalar ok=false
type Runtime ¶
type Runtime struct {
// contains filtered or unexported fields
}
Runtime executes the notebook's dependency graph reactively. It owns the head (the only mutable state), the cache, and the event stream, and it runs a wave per edit: an immutable snapshot of the head propagated through the dirty subgraph, with independent cells fanned out onto goroutines.
The scheduler is the load-bearing piece of the whole design. Its one correctness obligation — glitch-freedom — is that no cell ever observes inputs from two different epochs. That falls out of reading only from an immutable per-wave snapshot; it cannot be retrofitted onto a scheduler that reads shared mutable state.
func NewRuntime ¶
NewRuntime builds a runtime from a config, a head, and a cache.
func (*Runtime) Finals ¶
Finals returns a copy of the most recent committed value of every symbol, after a wave. It is the batch/headless output: run once, read the results.
func (*Runtime) RunAll ¶
RunAll executes a full wave over the whole graph at the current head state. It is used once at startup so every cell renders before any edit, and after a rebuild when the process restarts with a restored head.
func (*Runtime) Set ¶
Set writes a leaf through the head (the single mutation chokepoint), bumping the epoch, then runs the resulting wave. It returns when the wave settles or is superseded by a newer edit.
func (*Runtime) Subscribe ¶
Subscribe returns a channel of events for one consumer. Each subscriber gets its own channel; the engine never blocks on a slow consumer beyond the channel buffer. engine/server subscribes here — the engine itself never imports a transport.
The events carry only the rendered projection you should read: Out ({mime, data}) and the lifecycle fields. This is the WIRE-SAFE contract — the SSE and WASM transports subscribe here and project each event through ToWire, which ignores Event.Value, so no arbitrary Go value is ever marshalled. A consumer that wants the typed Go value must ask for it by name via [SubscribeValues].
func (*Runtime) SubscribeValues ¶ added in v0.4.0
SubscribeValues returns a channel like [Subscribe], but names the out-side capability of reading Event.Value — the cell's typed Go value for the wave, not its string readout. It is the symmetric partner of the input capability probes (Bounded/Optioned/Reconciler): inputs are probed for what they accept, this names what a consumer may read on the way out. It is for IN-PROCESS Go consumers only; the typed value never crosses a wire (see Event.Value). The fan-out is shared with [Subscribe] — the two differ by contract, not mechanism: only a SubscribeValues consumer is promised Value is populated.
type State ¶
type State int
State is the lifecycle state of a cell within a wave, as reported on the event stream.
const ( // StateRunning means the cell has started executing in the current wave. StateRunning State = iota // StateDone means the cell completed and produced a value. StateDone // StateError means the cell returned an error or panicked; its downstream // is blocked rather than fed a wrong value. StateError // StateBlocked means an upstream cell failed, so this cell did not run. It // shows "blocked upstream" rather than a stale or wrong number. StateBlocked // StateStale means the cell's wave was superseded by a newer epoch before // it committed. Its result is discarded. StateStale )
type Store ¶
Store is the cache behind an interface from day one, so eviction (which becomes mandatory once folds generate unbounded tick keys) is an additive change: swap the implementation, leave the scheduler untouched.
type Symbol ¶
type Symbol string
Symbol is a named result or parameter: the unit of dataflow. A leaf is identified by the symbol it produces.
type Value ¶
Value is a symbol's current value plus a version. The cache keys on versions, so arbitrary Go values never have to be hashed: two runs with the same input versions produce the same output.
type Viewable ¶
type Viewable interface {
WidgetView() WidgetView
}
Viewable is the capability a widget value has when it can state its own view. Probed structurally (like Renderable), because a notebook defines its OWN widget types and imports nothing from this package — so the match is by method shape across the zero-import boundary, not by a static interface.
The method is WidgetView() WidgetView-shaped: no args, one struct result with the field shape above. A widget states its view explicitly in this method.
type WidgetColumn ¶
WidgetColumn is one column of a Table's row type: its field name and a coarse type tag ("number", "string", "bool") the client renders an appropriate cell editor from. Type-derived at codegen.
type WidgetMeta ¶
type WidgetMeta struct {
// Kind is the control category: "range", "select", "multi", "bool",
// "draggable", "table". Derived from the leaf's result type.
Kind string
// Columns is the grid schema for a Table (its row type T's fields), empty
// for every other kind. A grid cannot be rendered from the runtime value
// alone — it needs the column names and types, which are T's, known only at
// codegen.
Columns []WidgetColumn `json:",omitempty"`
}
WidgetMeta is the static, type-derived descriptor of a leaf's control: the Kind that dispatches which control the client renders, plus — only for a Table — the column schema, which is a property of the row type T (known at codegen, not recoverable from the runtime value). Kind is static; the live state (selection/options/bounds) travels with the value as a WidgetView. The fields keep Go-cased JSON keys (no lowercase tags) so WidgetMeta matches its CellMeta siblings (ID, Leaf, Source) in the metadata object the client reads — m.Widget.Kind, m.Widget.Columns. (The live WidgetView cargo is a separate, deliberately lowercase JSON payload; do not conflate the two.)
type WidgetView ¶
type WidgetView struct {
// Value is the current selection. Its permitted shapes are a CLOSED set —
// adding one is a decision, not a fill-in, because this is the one field the
// type does not constrain:
// - a JSON scalar (number/string/bool) — Range picks a number, Select a label
// - a []string of labels — Multi's selected options
// - a []T of rows — Table's editable rows
// - a []Pt (or similar point list) — Draggable's handle positions
// It must stay flat, JSON-encodable STATE. A nested object describing
// appearance or structure does not belong here; if a new widget needs a
// shape not listed above, add it here deliberately and update this comment.
Value any `json:"value"`
// Options are the choosable labels for Select/Multi (nil otherwise).
Options []string `json:"options,omitempty"`
// Lo/Hi are the numeric bounds for Range. Pointers so "no bounds" (nil) is
// distinct from a real [0,0] range — absent means absent, no separate flag.
Lo *float64 `json:"lo,omitempty"`
Hi *float64 `json:"hi,omitempty"`
// Max is a selection-count cap for Multi. Pointer so "no cap" (nil) is
// distinct from a cap of 0.
Max *int `json:"max,omitempty"`
}
WidgetView is a widget's STATE on the wire — never its appearance. It carries what the client needs to render an interactive control and to know what a user's edit means: the current selection, the available choices/bounds, and hard constraints. It carries nothing about how the control LOOKS — no label, color, step, or layout. Kind (static, from the type, in CellMeta) decides which control; a //notebook: directive refines it; this view carries neither.
This is the input analogue of Rendered (which is output, a picture). A widget is structured input state, so a Multi/Select/Range/Table value that is not Renderable still reaches the client — through this, not as a blob.
Each widget KIND builds its own view explicitly (see the notebook's widget types). It is never a generic reflection of the widget struct: that would drag a Draggable's unexported leaf token or a Table's arbitrary row type onto the wire. Verbose-but-explicit is the point — the wire format is a decision.
func AsWidgetView ¶
func AsWidgetView(v any) (WidgetView, bool)
AsWidgetView probes v for the Viewable capability and returns its state view. Like AsRendered, the probe is structural via reflection, because a notebook defines its OWN widget types (e.g. lego.Multi) and imports nothing from this package — so a static interface{ WidgetView() engine.WidgetView } could never match WidgetView() lego.WidgetView across the two named types. The match is on the method name, no args, and one struct result carrying the field shape of WidgetView; the returned fields are copied by name so the notebook's own WidgetView-shaped struct maps onto the engine's.
type WireEvent ¶
type WireEvent struct {
Epoch uint64 `json:"epoch"`
Cell string `json:"cell"`
State string `json:"state"`
MIME string `json:"mime,omitempty"`
Data string `json:"data,omitempty"`
Err string `json:"err,omitempty"`
}
WireEvent is the transport-facing projection of an Event: the flat, type-erased shape every transport puts on the wire — cell id, lifecycle state, and an optional rendered {mime, data} blob, no Go types. It lives here, beside Event, because it is the ONE shape all transports share; the SSE server and the WASM bridge previously each declared it separately and kept them in sync by hand. This is transport-agnostic on purpose — it names a JSON shape, not a protocol — so it crosses no boundary the foreclosure table protects (the engine still imports no net/http). The struct's JSON tags are the SSE wire contract; WireEvent.Map is the same data as a map[string]any for js.ValueOf, so the WASM bridge builds it from the same source instead of a parallel literal.
func ToWire ¶
ToWire projects an Event onto its transport shape. The single place the wire event is constructed; every transport calls it.
func (WireEvent) Map ¶
Map renders the wire event as a map[string]any with the same keys and the same omit-empty rules as the JSON encoding — for transports (WASM's js.ValueOf) that cannot marshal a struct. Kept in lockstep with the struct tags above so the two projections never drift: this is the whole point of a single source.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package server exposes a engine.Runtime over HTTP: a page to view the notebook, a Server-Sent Events stream of cell updates, and an endpoint to post leaf edits.
|
Package server exposes a engine.Runtime over HTTP: a page to view the notebook, a Server-Sent Events stream of cell updates, and an endpoint to post leaf edits. |
|
Package wasm is the browser transport: it drives an engine.Runtime over the syscall/js boundary instead of an HTTP/SSE server.
|
Package wasm is the browser transport: it drives an engine.Runtime over the syscall/js boundary instead of an HTTP/SSE server. |