runtime

package
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Jul 10, 2026 License: MIT Imports: 2 Imported by: 0

Documentation

Index

Constants

View Source
const WireVersion byte = 1

WireVersion is the codec's frame-format version. It is written as byte 0 of every top-level frame by NewEncoder and validated by NewDecoder. Bumping it is an intentional, irreversible wire break: a decoder built for one version rejects frames carrying any other. Nested encodes (_encode_X, _encode_sliceX) receive an already-opened *Encoder and therefore never re-emit this byte — only the frame's opening carries it.

Variables

This section is empty.

Functions

func AddClass

func AddClass(id, className string)

func AddEventListener

func AddEventListener(el JSValue, event string, fn func())

AddEventListener attaches a persistent event listener to el for the given event name. fn is called with no arguments each time the event fires. The js.Func is retained in keep and never released — persistent listeners must stay alive for the lifetime of the page; releasing them would panic on the next event.

func AddEventListenerWithEvent

func AddEventListenerWithEvent(el JSValue, event string, fn func(JSValue))

AddEventListenerWithEvent attaches a persistent event listener to el for the given event name. fn receives the browser Event object as a JSValue, giving access to event properties and methods such as preventDefault(), stopPropagation(), event.target, event.key, event.detail, etc. The js.Func is retained in keep and never released — persistent listeners must stay alive for the lifetime of the page; releasing them would panic on the next event.

func AppendChild

func AppendChild(parent, child JSValue)

Element tree helpers.

func BeginBatch

func BeginBatch()

BeginBatch defers observer notifications until the matching EndBatch. Nested calls are supported; effects only flush when depth returns to 0. Each pending Subscription is deduplicated and runs at most once per batch.

func BroadcastTopicEncoded

func BroadcastTopicEncoded(keyName, encoded string)

BroadcastTopicEncoded writes encoded to the JS store and dispatches a CustomEvent.

func BroadcastTopicEncodedField

func BroadcastTopicEncodedField(keyName, fieldName, encoded string)

BroadcastTopicEncodedField broadcasts an already-encoded single field value. Event name: "gothic:topic:<keyName>:<fieldName>"

func BroadcastTopicOnline

func BroadcastTopicOnline(keyName, encoded string)

BroadcastTopicOnline dispatches the online ack to all consumer WASMs for this key.

func CaptureScope

func CaptureScope() string

CaptureScope returns the scope active at the call site. Capture it before spawning a goroutine or scheduling async work whose body touches scoped DOM helpers, then re-establish it with RunInScope when that work runs — a goroutine does not inherit the carrier across a suspension point.

Keep any blocking call (a fetch, a channel receive) OUTSIDE RunInScope and wrap only the scoped, non-blocking work — mirroring how PingUntilOnline keeps time.Sleep outside RunInScope:

scope := CaptureScope()
go func() {
    body, _ := Fetch(url)                       // blocks — outside RunInScope
    RunInScope(scope, func() { SetText("out", body) })  // scoped, non-blocking
}()

func ClickElement

func ClickElement(el JSValue)

func ConsoleLog

func ConsoleLog(args ...any)

func CookieDelete

func CookieDelete(key string)

CookieDelete expires a cookie immediately.

func CookieGet

func CookieGet(key string) string

CookieGet reads a cookie value from document.cookie. Returns "" for missing or HttpOnly cookies.

func CookieSet

func CookieSet(key, value string, opts ...CookieOptions)

CookieSet writes a cookie to document.cookie.

func CopyBytesToGo

func CopyBytesToGo(dst []byte, src JSValue) int

func CopyBytesToJS

func CopyBytesToJS(dst JSValue, src []byte) int

func CreateTopic

func CreateTopic[T any](zero T, cfg TopicConfig) func() interface{}

CreateTopic declares a topic. The CLI AST scanner detects this call and generates the concrete typed accessor. At runtime this returns a no-op.

func CreateWasmBoolFunc

func CreateWasmBoolFunc(name string, fn func(bool))

func CreateWasmFunc

func CreateWasmFunc(name string, fn func())

CreateWasmFunc registers a no-arg user callback under name. The Go closure becomes a js.Func stored in __gothic_registry[scope][name] (registerLocal), keyed by the active scope. That registry js.Func is the SOLE dispatch target: the GLOBAL proxy window[name] — the function HTML onclick/onchange invokes — is pure, instance-agnostic JS installed once per name by __gothicInstallProxy (see pkg/helpers/gothiccore/core.go). On each click that proxy resolves the clicked element's scope off the LIVE __gothic_registry and calls this scope's js.Func, so tearing down a sibling instance (which deletes its registry entry) can never leave the proxy pointing at halted Go code.

func CreateWasmStringFunc

func CreateWasmStringFunc(name string, fn func(string))

func DurableKey

func DurableKey() string

DurableKey returns the STABLE durable key declared on the active scope's wrapper (data-gothic-durable-key), or "" when the placement is not durable. The DOM read lives in JS (window.__gothicDurableKey in gothic-core.js) and returns a plain string so no per-mount MouseEvent/element js.Value is boxed into TinyGo's slot table (the same _values[]-leak avoidance as findScope). Empty string is the default and means "durability off" — callers treat it as opt-out.

func DurableObserve

func DurableObserve[T any](field string, obs *Observable[T], encode func(T) string, decode func(string) T)

DurableObserve binds obs to the core's durable KV as `field` under this placement's durable key. It rehydrates obs from the core BEFORE the component goes live, then persists every subsequent change for the page session. When the placement has NO durable key it is a NO-OP and obs behaves exactly as today (OPT-IN; default off). encode/decode are the field's string codec (same shape as CustomKey — reuse strconv for primitives, or a binary Encoder/Decoder for a struct field); they exist in both build worlds so the same ClientSideState block compiles server-side (no-op) and under TinyGo.

count := CreateObservable(0)
DurableObserve("count", count, strconv.Itoa,
    func(s string) int { n, _ := strconv.Atoi(s); return n })
Observe(func() { SetText("out", strconv.Itoa(count.Get())) }, count)

func EndBatch

func EndBatch()

EndBatch decrements the batch depth and, when it reaches zero, runs every Subscription that was queued during the batch (deduplicated).

func ExecJS

func ExecJS(script string)

ExecJS executes the given script string in the global scope.

func Fetch

func Fetch(url string, config ...FetchConfig) (string, error)

Fetch makes an HTTP request using the browser's fetch API and blocks until complete.

func FetchBytes

func FetchBytes(url string, config ...FetchConfig) ([]byte, error)

FetchBytes makes an HTTP request and returns the response as raw bytes. Use for binary responses (images, PDFs, ZIPs) where Fetch's text() decoding would corrupt data.

func GetFileBytes

func GetFileBytes(id string) []byte

GetFileBytes reads the contents of the first selected file from a <input type="file"> element. Blocks until the FileReader completes. Returns nil on error or if no file is selected.

func GetValue

func GetValue(id string) string

func GoBack

func GoBack()

func GothicHaltChan

func GothicHaltChan() <-chan struct{}

GothicHaltChan returns the keep-alive sentinel channel. The generated WASM main() selects on it so the module returns when the bootstrap's per-scope teardown invokes this instance's __gothic_halt callback.

func GothicRegisterSchema

func GothicRegisterSchema(key, schemaID, descriptor string)

GothicRegisterSchema records a topic/component type's compact schema descriptor under its content-hash id at the point the type registers. This is the Phase 15 SCHEMA SEAM: a reserved, additive control-plane slot for a future generic wire interpreter (Phase 16's core stores it opaquely). NOTHING interprets it in v3.0 — it is written once, off the data-plane, and never read back by any 3.0 consumer. The descriptor is deposited on window.__gothicSchemas (keyed by schemaID) so the core can later pick it up without a wire change.

Generated code only (like the Broadcast*/Listen* helpers); it is never hand-written in a ClientSideState block, so it has no user-facing stub-parity obligation.

func GothicRegisterScope

func GothicRegisterScope(body func())

GothicRegisterScope wires a Multiplexed page's ClientSideState body into the per-scope registration system. The generated main() of a Multiplexed route calls it with the ClientSideState body wrapped in a closure, instead of running that body inline. It:

  1. Registers the instance's own mount scope immediately by running body under runInScope(bootstrapScope). This makes the first placement behave exactly like a non-multiplexed instance: body runs under the bootstrap scope, so its observables/callbacks/listeners land in __gothic_registry[bootstrapScope] — byte-identical to the non-multiplexed registration path.

  2. Publishes a per-instance __gothic_register_scope(id) callback onto this module's window.__gothicInstances[<bootstrapScope>] slot (the same slot the Phase-12 __halt lives on). The bootstrap JS invokes it for every SUBSEQUENT placement of the same component type, running the SAME body under runInScope(id). Each invocation re-runs body, creating FRESH observables and callbacks via new closures — this is why Phase 13's scope refactor is the gate for multiplexing: one instance hosts N independent scopes, each with its own state, all resolved per active scope.

Publishing onto the per-instance slot (not a window global) mirrors __halt: portable across the GothicTinyGo/LocalTinyGo/Golang compilers AND correct on a multi-instance page. main() runs synchronously up to its keep-alive select during go.run, so the callback is set before the bootstrap flushes its pending queue.

func GothicRegisterWithCore

func GothicRegisterWithCore(scopeID, schemaID, schema string)

GothicRegisterWithCore performs the component→core registration RPC against the Phase-16 full-Go static core over the `document` control-plane bus. It hands the core an OPAQUE schema descriptor keyed by (scopeID, schemaID): the core records {scopeId, schemaId, schema} verbatim and acks — it never interprets the descriptor (the generic interpreter is DEFERRED).

Ordering mirrors the topic online/ping handshake and handles both startup races WITHOUT a goroutine:

  • core already up → the immediate send is received and acked.
  • core comes up later → the core announces `gothic:core:online` on boot; this module's online listener re-sends the registration until it is acked.

Asyncify safety is a TWO-SIDED contract. This side (outbound register) wraps every dispatch in queueMicrotask (like the topic bus's __gothicDispatchAsync) so the register fires from a clean call stack, never from inside this module's running scheduler turn. The RETURN side (the core's ack + online announce) is symmetric: the full-Go core schedules those on its own microtask (see pkg/wasm/core-runtime scheduleDispatch), so the ack that lands here does NOT re-enter this component's asyncify turn.

Generated code only (like GothicRegisterSchema / the Broadcast*/Listen* helpers); it is never hand-written in a ClientSideState block, so it carries no user-facing stub-parity obligation (its host no-op lives in events_stub.go).

func HexDecode

func HexDecode(s string) []byte

func HexEncode

func HexEncode(src []byte) string

func ListenTopicCoreOnline

func ListenTopicCoreOnline(key string, fn func())

ListenTopicCoreOnline registers a handler for the core's per-key online ack (gothic:core:topic-online:<key>). The core dispatches it AFTER replaying the topic's current per-field state to this consumer, so by the time the handler runs the per-field ListenTopicEventField handlers have already applied the replayed values. The generated consumer uses it to flip _online and flush any pending whole-struct Set (fanned out per-field).

Like the other topic listeners it re-establishes the registering scope (scopedListener) because the ack fires from an async document-event turn where findScope() cannot see the component's [data-gothic-scope], and routes through addScopedDocListener so the per-scope teardown can remove it on unmount.

func ListenTopicEvent

func ListenTopicEvent(keyName string, fn func(string))

ListenTopicEvent registers a cross-module listener for topic updates.

The listener re-establishes the scope that registered it (see scopedListener) because the topic CustomEvent fires from an async turn where findScope() cannot see the component's [data-gothic-scope]; fn typically drives scoped DOM helpers via ApplyExternal → Observe, which must resolve to this scope.

func ListenTopicEventField

func ListenTopicEventField(keyName, fieldName string, fn func([]byte))

ListenTopicEventField subscribes to per-field broadcasts from the core hub.

fn receives the raw frame as []byte (not string): this is the HOT consumer data-plane path (one call per broadcast per subscribed field). Passing []byte lets the generated consumer feed the pooled scratch buffer straight into NewDecoder, eliminating the string(dst)→[]byte(detail) double copy the old func(string) signature forced. Combined with the per-listener scratch reuse in topicViewInto, a stable-payload rebroadcast now allocates ZERO transient frame bytes on the receive side — the fix for the multi-MB linear-memory ratchet on TinyGo's no-shrink conservative heap. The []byte is only valid for the duration of fn (it aliases the reused scratch); the decoder copies out its own values.

func ListenTopicOnline

func ListenTopicOnline(keyName string, fn func(string))

ListenTopicOnline registers a handler that receives the manager's online ack with current state. Fires once on manager startup and on every ping response.

func ListenTopicPing

func ListenTopicPing(keyName string, fn func())

ListenTopicPing registers a handler for incoming pings on the topic manager WASM.

func ListenTopicSetReq

func ListenTopicSetReq(keyName string, fn func(string))

ListenTopicSetReq registers a handler for incoming set-requests on a topic manager WASM.

func ListenTopicSetReqField

func ListenTopicSetReqField(keyName, fieldName string, fn func(string))

ListenTopicSetReqField subscribes to per-field set-requests (used by the manager).

func LocalStorageGet

func LocalStorageGet(key string) string

func LocalStorageRemove

func LocalStorageRemove(key string)

func LocalStorageSet

func LocalStorageSet(key, value string)

LocalStorage helpers

func Navigate(url string)

Navigation helpers.

func OnUnmount

func OnUnmount(fn func())

OnUnmount registers a cleanup callback invoked by the bootstrap's per-scope teardown when this component's [data-gothic-scope] element is removed from the DOM. Use it to release things created outside the component's own subtree (persistent document listeners attached directly, timers, topic mounts). The callback is stored at __gothic_registry[<scope>].__onUnmount and retained in keep so TinyGo's GC won't reclaim it before teardown fires.

func PingTopicManager

func PingTopicManager(keyName string)

PingTopicManager dispatches a ping to the topic manager asking for an online ack.

func PingUntilOnline

func PingUntilOnline(keyName string, isOnline func() bool)

PingUntilOnline retries PingTopicManager every 50 ms until isOnline returns true. Runs in its own goroutine so it doesn't block the caller.

A goroutine does not inherit the scope carrier across suspension points, so we CaptureScope() at spawn and RunInScope() each iteration to re-establish the caller's scope (isOnline may read scoped state). For a single-scope instance the captured scope is bootstrapScope, so behaviour is unchanged.

func PushState

func PushState(url, title string)

func ReadTopicStore

func ReadTopicStore(keyName string) (string, bool)

ReadTopicStore reads the encoded topic value from the shared JS store.

func RegisterTopicWithCore

func RegisterTopicWithCore(key string, fields []string)

RegisterTopicWithCore performs the topic → core control-plane registration. The CustomEvent detail is a plain JS object {key, fields:[...]} — control-plane JSON/values, NEVER binary. It hands the core the topic's wire key and the ORDERED field-name list so the core can subscribe to each gothic:topic-req:<key>:<field> and replay the current per-field state back. The field names are ROUTING metadata; the core still never interprets a payload byte (the generic interpreter is DEFERRED), so this keeps the core opaque.

Startup races are handled the same way as GothicRegisterWithCore, with no goroutine:

  • core already up → the register is received; the core replays this topic's stored per-field frames and announces the per-key online ack.
  • core comes up later → it announces gothic:core:online on boot; this re-fires the register until the per-key online ack lands (acked short-circuits, so at most one extra send per core boot).

Every dispatch is wrapped in queueMicrotask so it leaves a clean call stack and never fires from inside this module's running asyncify turn (the RETURN side — the core's replay + ack — is symmetric: the full-Go core microtask-schedules those, so nothing re-enters this component's scheduler).

func Reload

func Reload()

func RemoveClass

func RemoveClass(id, className string)

func RemoveElement

func RemoveElement(el JSValue)

func RequestTopicSet

func RequestTopicSet(keyName, encoded string)

RequestTopicSet dispatches a set-request to the topic manager WASM for this key. The manager is the sole writer: it applies the update and broadcasts back.

func RequestTopicSetField

func RequestTopicSetField(keyName, fieldName, encoded string)

RequestTopicSetField sends a per-field set-request to the manager. Event name: "gothic:topic-req:<keyName>:<fieldName>"

func RequestTopicSetFieldBytes

func RequestTopicSetFieldBytes(keyName, fieldName string, b []byte)

RequestTopicSetFieldBytes is RequestTopicSetField for callers that already hold the encoded frame as []byte (the generated consumer's _broadcastAll). It hands the bytes straight to dispatchDirect — which copies them into dispatchHold — skipping the string(encoded)→[]byte(encoded) round-trip the string form forces on every per-field send. Event name is identical: "gothic:topic-req:<keyName>:<fieldName>".

func RunInScope

func RunInScope(id string, fn func())

RunInScope runs fn with the given scope active, restoring the previous scope afterwards. Pair it with CaptureScope to carry a scope into a goroutine or deferred callback. Nesting is supported.

func SessionStorageGet

func SessionStorageGet(key string) string

func SessionStorageRemove

func SessionStorageRemove(key string)

func SessionStorageSet

func SessionStorageSet(key, value string)

SessionStorage helpers

func SetAttr

func SetAttr(id, attr, value string)

func SetHTML

func SetHTML(id, html string)

func SetStyle

func SetStyle(id, property, value string)

func SetText

func SetText(id, value string)

func SetValue

func SetValue(id, value string)

func ToggleClass

func ToggleClass(id, className string)

func TriggerDownload

func TriggerDownload(filename string, data []byte, mimeType string)

TriggerDownload prompts the browser to download `data` as a file named `filename` with the given MIME type.

func UpdateTopicOnlineStore

func UpdateTopicOnlineStore(keyName string, encoded []byte)

UpdateTopicOnlineStore updates the JS-side topic store so that late-joining consumers see fresh data via ReadTopicStore, WITHOUT dispatching the gothic:topic-online event. Use this from ListenTopicSetReq to fix the startup race (T5) without triggering ListenTopicOnline scans in already-running consumers.

func WriteClipboard

func WriteClipboard(text string)

WriteClipboard writes the given text to the system clipboard via navigator.clipboard.writeText.

Types

type Compression

type Compression int

Compression is the compression algorithm used for a topic's WASM payload.

const (
	GZIP   Compression = iota // default
	BROTLI Compression = iota
)

type CookieOptions

type CookieOptions struct {
	MaxAge   int    // seconds; 0 = session cookie
	Path     string // defaults to "/"
	SameSite string // "Strict", "Lax", or "None"
	Secure   bool
}

CookieOptions configures CookieSet behaviour.

type Decoder

type Decoder struct {
	Buf []byte
	Pos int
	Err error
}

Decoder reads a little-endian binary stream. Err accumulates the first decode error — check it once at the end rather than per call.

func NewDecoder

func NewDecoder(buf []byte) *Decoder

NewDecoder opens a frame produced by NewEncoder. It validates the WireVersion header byte at position 0 and positions Pos immediately after it, so the first typed read returns the frame's first field. On an empty buffer or a version mismatch it sets Err (leaving Pos at 0) and never panics — need() short- circuits all subsequent reads. Use this at every site that decodes a complete wire frame; per-field capture helpers that walk an already-opened Decoder keep using the raw &Decoder{} form.

func (*Decoder) Bool

func (d *Decoder) Bool() bool

func (*Decoder) Bytes

func (d *Decoder) Bytes() []byte

func (*Decoder) F32

func (d *Decoder) F32() float32

func (*Decoder) F64

func (d *Decoder) F64() float64

func (*Decoder) I32

func (d *Decoder) I32() int32

func (*Decoder) I64

func (d *Decoder) I64() int64

func (*Decoder) String

func (d *Decoder) String() string

func (*Decoder) U8

func (d *Decoder) U8() uint8

func (*Decoder) U16

func (d *Decoder) U16() uint16

func (*Decoder) U32

func (d *Decoder) U32() uint32

func (*Decoder) U64

func (d *Decoder) U64() uint64

type Encoder

type Encoder struct{ Buf []byte }

Encoder writes a little-endian binary stream into Buf. Preallocate with NewEncoder to avoid repeated append growth.

func NewEncoder

func NewEncoder(cap int) *Encoder

NewEncoder opens a new frame: it returns an *Encoder whose buffer already contains the single WireVersion header byte at position 0. Every subsequent write appends after it. Because nested encoders reuse this same *Encoder, the version byte appears exactly once per frame — at the frame boundary.

func (*Encoder) Bool

func (e *Encoder) Bool(v bool)

func (*Encoder) Bytes

func (e *Encoder) Bytes(v []byte)

func (*Encoder) F32

func (e *Encoder) F32(v float32)

func (*Encoder) F64

func (e *Encoder) F64(v float64)

func (*Encoder) I32

func (e *Encoder) I32(v int32)

func (*Encoder) I64

func (e *Encoder) I64(v int64)

func (*Encoder) String

func (e *Encoder) String(v string)

func (*Encoder) U8

func (e *Encoder) U8(v uint8)

func (*Encoder) U16

func (e *Encoder) U16(v uint16)

func (*Encoder) U32

func (e *Encoder) U32(v uint32)

func (*Encoder) U64

func (e *Encoder) U64(v uint64)

type FetchConfig

type FetchConfig struct {
	Method    string
	Headers   map[string]string
	Body      string
	BodyBytes []byte
	Query     map[string]string
}

FetchConfig configures an HTTP request made via Fetch.

type JSValue

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

JSValue wraps js.Value. Never create as a literal; use JS(), Document(), etc.

func CreateElement

func CreateElement(tag string) JSValue

func CreateWasmFuncWithReturn

func CreateWasmFuncWithReturn(name string, fn func(this JSValue, args []JSValue) any) JSValue

CreateWasmFuncWithReturn registers a named global JS function that can return a value back to JS. Wraps syscall/js.FuncOf. The returned JSValue holds the js.Func so it can be passed directly to JS object properties (chart configs, map renderers, etc.) that require a synchronous return value. The js.Func is retained in keep and never released — it must stay alive for the lifetime of the page; releasing it would panic on the next invocation.

func Document

func Document() JSValue

func GetElementById

func GetElementById(id string) JSValue

func JS

func JS() JSValue

func QuerySelector

func QuerySelector(sel string) JSValue

func QuerySelectorAll

func QuerySelectorAll(sel string) JSValue

func Window

func Window() JSValue

func (JSValue) Bool

func (v JSValue) Bool() bool

func (JSValue) Call

func (v JSValue) Call(method string, args ...any) JSValue

func (JSValue) Float

func (v JSValue) Float() float64

func (JSValue) Get

func (v JSValue) Get(key string) JSValue

func (JSValue) Index

func (v JSValue) Index(i int) JSValue

func (JSValue) Int

func (v JSValue) Int() int

func (JSValue) IsNull

func (v JSValue) IsNull() bool

func (JSValue) IsUndefined

func (v JSValue) IsUndefined() bool

func (JSValue) Length

func (v JSValue) Length() int

func (JSValue) New

func (v JSValue) New(args ...any) JSValue

func (JSValue) Set

func (v JSValue) Set(key string, val any)

func (JSValue) SetIndex

func (v JSValue) SetIndex(i int, val any)

func (JSValue) String

func (v JSValue) String() string

func (JSValue) Truthy

func (v JSValue) Truthy() bool

type Observable

type Observable[T any] struct {
	// contains filtered or unexported fields
}

Observable is a reactive value container. Reading it inside an Observe callback automatically subscribes that callback to future updates; calling Set notifies all subscribers synchronously.

Create one with CreateObservable:

count := CreateObservable(0)
name  := CreateObservable("Alice")

Similar to useState in React, but the value is held in the Observable itself rather than being destructured into a [value, setter] pair.

func CreateObservable

func CreateObservable[T any](initial T) *Observable[T]

CreateObservable creates a new Observable with the given initial value. It is the Gothic equivalent of React's useState hook.

Example:

count := CreateObservable(0)
label := CreateObservable("hello")

// Read the current value:
fmt.Println(count.Get())

// Update the value (triggers all Observe callbacks that depend on count):
count.Set(count.Get() + 1)

func (*Observable[T]) Get

func (s *Observable[T]) Get() T

func (*Observable[T]) Set

func (s *Observable[T]) Set(v T)

type ObservableField

type ObservableField[T any] struct {
	// contains filtered or unexported fields
}

ObservableField is a reactive Observable bound to one field of a shared topic struct. It behaves like *Observable[T] but Set also broadcasts the full topic to other modules. Pass *ObservableField as a dep in Observe to react to individual property changes.

func NewObservableField

func NewObservableField[T any](initial T) *ObservableField[T]

NewObservableField creates an ObservableField with the given initial value.

func (*ObservableField[T]) ApplyExternal

func (f *ObservableField[T]) ApplyExternal(v T)

ApplyExternal updates value and notifies subscribers without triggering broadcast. Used by generated topic listeners and Set-all methods to avoid redundant events.

func (*ObservableField[T]) Get

func (f *ObservableField[T]) Get() T

Get returns the current value, auto-registering as a dep of any running effect.

func (*ObservableField[T]) Peek

func (f *ObservableField[T]) Peek() T

Peek returns the current value without registering as an effect dependency. Used internally by broadcast closures to read sibling field values safely.

func (*ObservableField[T]) Set

func (f *ObservableField[T]) Set(v T)

Set sends a set-request to the topic manager WASM. The local value is silently updated so Peek() returns the correct value during encoding, but subscribers are NOT notified until the manager broadcasts back.

func (*ObservableField[T]) SetBroadcast

func (f *ObservableField[T]) SetBroadcast(fn func())

SetBroadcast wires the broadcast callback called whenever Set updates this field.

type SharedTopicObservable

type SharedTopicObservable[T any] struct {
	// contains filtered or unexported fields
}

SharedTopicObservable is a reactive Observable bound to a shared topic key. Get/Set work like a regular Observable, but Set also broadcasts the new value to every other WASM module sharing the same key. Used internally by the auto-generated topic constructors (e.g. PageTopic()).

func (*SharedTopicObservable[T]) Get

func (s *SharedTopicObservable[T]) Get() T

func (*SharedTopicObservable[T]) Set

func (s *SharedTopicObservable[T]) Set(v T)

type Subscription

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

func Observe

func Observe(fn func(), deps ...any) *Subscription

Observe runs fn immediately and re-runs it whenever a listed dep changes. Pass no deps to run fn exactly once with no reactive subscription. It is the Gothic equivalent of React's useEffect hook.

Example — update the DOM whenever count changes:

count := CreateObservable(0)

Observe(func() {
    document.GetElementById("counter").SetInnerHTML(fmt.Sprintf("%d", count.Get()))
}, count)

// Later, updating count re-runs the callback automatically:
count.Set(count.Get() + 1)

Pass multiple deps to react to any of them:

Observe(func() { ... }, a, b, c)

Pass no deps to run once on mount with no subscription:

Observe(func() { fmt.Println("mounted") })

func ObserveWithCleanup

func ObserveWithCleanup(fn func() func(), deps ...any) *Subscription

ObserveWithCleanup is like Observe but fn may return a cleanup function that runs before each re-execution and when Stop() is called.

Example — add and remove an event listener reactively:

ObserveWithCleanup(func() func() {
    el := document.GetElementById("btn")
    handler := el.AddEventListener("click", func() { ... })
    return func() { el.RemoveEventListener("click", handler) }
}, someObservable)

func (*Subscription) Stop

func (e *Subscription) Stop()

type TopicConfig

type TopicConfig struct {
	Name             string
	Compression      Compression  // GZIP (default) or BROTLI
	Compiler         WasmCompiler // GothicTinyGo (default), LocalTinyGo, or Golang
	SubscriberFnName string       // overrides generated accessor func name (default: <StructName>Topic)
}

TopicConfig holds per-topic configuration.

type TopicKey

type TopicKey[T any] struct {
	Name string
	// contains filtered or unexported fields
}

TopicKey is a typed topic identifier that carries its own codec. T encodes the value type — provider and consumer must use the same key. Construct via the factory functions (IntKey, StringKey, BinaryKey, etc.), not as a struct literal.

func AutoKey

func AutoKey[T any](name string) TopicKey[T]

AutoKey is rewritten to BinaryKey by the CLI before TinyGo compiles. This stub exists so server-side code compiles; WASM code never calls it directly.

func BinaryKey

func BinaryKey[T any](name string, encode func(T, *Encoder), decode func(*Decoder) T) TopicKey[T]

BinaryKey returns a TopicKey that serializes T using a compact little-endian binary codec. No reflection, no encoding/json — just typed Encoder/Decoder calls. The encode function writes fields onto e; the decode function reads them back and returns T. Field order must match between encode and decode.

Example:

BinaryKey[Page]("page",
    func(v Page, e *Encoder) {
        e.I64(int64(v.Pings))
        e.String(v.Label)
        e.String(v.Theme)
    },
    func(d *Decoder) PageCtx {
        return PageCtx{Pings: int(d.I32()), Label: d.String(), Theme: d.String()}
    },
)

func BoolKey

func BoolKey(name string) TopicKey[bool]

func ByteKey

func ByteKey(name string) TopicKey[byte]

ByteKey is UintKey for byte (= uint8).

func CustomKey

func CustomKey[T any](name string, encode func(T) string, decode func(string) T) TopicKey[T]

CustomKey returns a TopicKey with user-supplied encode/decode functions.

func Float32Key

func Float32Key(name string) TopicKey[float32]

func Float64Key

func Float64Key(name string) TopicKey[float64]

func Int8Key

func Int8Key(name string) TopicKey[int8]

func Int16Key

func Int16Key(name string) TopicKey[int16]

func Int32Key

func Int32Key(name string) TopicKey[int32]

func Int64Key

func Int64Key(name string) TopicKey[int64]

func IntKey

func IntKey(name string) TopicKey[int]

func RuneKey

func RuneKey(name string) TopicKey[rune]

RuneKey is IntKey for rune (= int32).

func StringKey

func StringKey(name string) TopicKey[string]

func Uint8Key

func Uint8Key(name string) TopicKey[uint8]

func Uint16Key

func Uint16Key(name string) TopicKey[uint16]

func Uint32Key

func Uint32Key(name string) TopicKey[uint32]

func Uint64Key

func Uint64Key(name string) TopicKey[uint64]

func UintKey

func UintKey(name string) TopicKey[uint]

type WasmCompiler

type WasmCompiler int

WasmCompiler selects the WASM build toolchain for a topic.

const (
	GothicTinyGo WasmCompiler = iota // default: embedded TinyGo binary
	LocalTinyGo                      // system tinygo binary in PATH
	Golang                           // GOOS=js GOARCH=wasm standard Go compiler
)

Jump to

Keyboard shortcuts

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