Documentation
¶
Overview ¶
Package goscript is the Go side of Tern's <script lang="go">: component logic written in Go with Svelte-style runes, while the markup stays Svelte.
<script lang="go" live>
import "github.com/Unfield/Tern/goscript"
func Setup(c *goscript.Component) {
count := goscript.State(c, "count", 0)
goscript.Derived(c, "doubled", func() int { return count.Get() * 2 })
c.Func("increment", func() { count.Set(count.Get() + 1) })
}
</script>
<button onclick={increment}>{count} × 2 = {doubled}</button>
Names given to State, Derived, Show and Func become variables in the component's markup.
With live, the component runs on the server and the page talks to it over one WebSocket: this is the supported, production mode. Without live, the same code is compiled to WebAssembly and runs in the browser; that mode is experimental and needs "experimental": {"goScript": true} in tern.json. See docs/live-components.md.
Index ¶
- Variables
- func ComponentID(path string) string
- func Effect(c *Component, fn func())
- func Fail(format string, args ...any) error
- func InitialState(id string, props []byte, locals map[string]any) (map[string]json.RawMessage, error)
- func Instances() int64
- func Publish(ctx context.Context, topic string, v any) error
- func Register(id string, setup func(*Component))
- func Registered(id string) bool
- func Run()
- func SetBroker(b Broker)
- func Subscribe[T any](c *Component, topic string, fn func(T))
- type Broker
- type Component
- func (c *Component) Context() context.Context
- func (c *Component) Do(fn func())
- func (c *Component) Emit(name string, v any)
- func (c *Component) Every(d time.Duration, fn func())
- func (c *Component) Func(name string, fn any)
- func (c *Component) Go(fn func(ctx context.Context))
- func (c *Component) Locals() map[string]any
- func (c *Component) OnDestroy(fn func())
- func (c *Component) Props(dst any) error
- func (c *Component) Reject(format string, args ...any)
- func (c *Component) Rendering() bool
- type Doc
- type DocOptions
- type DocSet
- type Error
- type Event
- type GlobalSignal
- type Instance
- func (i *Instance) Call(ctx context.Context, name string, args []byte) (out []byte, err error)
- func (i *Instance) CallAsync(name string, args []byte, done func(out []byte, err error))
- func (i *Instance) Destroy()
- func (i *Instance) DestroyAsync() bool
- func (i *Instance) Set(ctx context.Context, name string, value []byte) error
- func (i *Instance) SetAsync(name string, value []byte, done func(error))
- func (i *Instance) SetProps(ctx context.Context, props []byte) error
- func (i *Instance) SetPropsAsync(props []byte)
- type LiveOptions
- type Member
- type Option
- type PresenceCounter
- type RoomMember
- type RoomSet
- type SharedSignal
- type Signal
Constants ¶
This section is empty.
Variables ¶
var ErrArguments = errors.New("goscript: invalid arguments")
ErrArguments is returned when a Func's arguments can't be decoded.
var ErrDestroyed = errors.New("goscript: the component was destroyed")
ErrDestroyed is returned by an Instance after it is destroyed.
var ErrPanic = errors.New("goscript: the component failed")
ErrPanic is returned by Call when the Func panicked.
Functions ¶
func ComponentID ¶ added in v1.3.0
ComponentID returns the ID of the component in the .svelte file at path, relative to the app's root, such as "src/lib/Counter.svelte".
func Effect ¶
func Effect(c *Component, fn func())
Effect runs fn now and again whenever a value it reads changes. It does not run while rendering.
func Fail ¶ added in v1.3.0
Fail returns an error for a Func whose message is sent to the page, which can show it: the call's promise rejects with it, and the Func's error is set to it. Other errors are not shown, so internal details don't leak.
func InitialState ¶
func InitialState(id string, props []byte, locals map[string]any) (map[string]json.RawMessage, error)
InitialState runs a component's Setup with props and returns what it published, for server rendering. Effects, Do, Go and Every don't run.
func Instances ¶ added in v1.3.0
func Instances() int64
Instances returns how many live components are mounted, for metrics.
func Publish ¶ added in v1.3.0
Publish sends v to every component subscribed to topic with Subscribe, in this process and, with a broker, every other. A common pattern: a Func saves a change to the database, then publishes that it happened, and every subscribed component reloads what it shows.
func Register ¶
Register makes a component's Setup available; generated code calls it from an init function.
func Registered ¶
Registered reports whether a component ID is registered.
func Run ¶
func Run()
Run mounts every component with empty props, outside the browser, and prints each one's initial state as JSON. tern uses it at build time so server rendering and the first paint show real values.
Types ¶
type Broker ¶ added in v1.3.0
type Broker interface {
// Publish delivers msg to the topic's subscribers in every process.
Publish(ctx context.Context, topic string, msg []byte) error
// Subscribe calls fn for each message on topic until ctx is canceled.
Subscribe(ctx context.Context, topic string, fn func(msg []byte)) error
}
Broker carries messages between server processes, so live components on every instance of an app see the same Shared values, Presence counts and published messages. Any realtime.Broker works, such as realtime.NewMemoryBroker or a Postgres broker from realtime/pgbroker; set it with kit.WithLiveBroker.
type Component ¶
type Component struct {
// contains filtered or unexported fields
}
Component is one mounted instance of a component. Its methods, and the signals it owns, must be used from the component's own code: Setup, its Funcs, Derived values and Effects, and functions passed to Do. Other goroutines go through Do.
func (*Component) Context ¶ added in v1.3.0
Context returns a context that is canceled when the component is destroyed. Use it for work a Func or Go starts.
func (*Component) Do ¶ added in v1.3.0
func (c *Component) Do(fn func())
Do runs fn as the component, safely from any goroutine: changes it makes to signals reach the page like changes made by a Func. It does nothing while rendering or after the component is destroyed.
func (*Component) Emit ¶ added in v1.3.0
Emit sends an event to the component's parent in the page: Emit("saved", v) calls the onsaved prop with v. It does nothing while rendering.
func (*Component) Every ¶ added in v1.3.0
Every runs fn as the component every d until it is destroyed, such as to refresh a value. It does nothing while rendering.
func (*Component) Func ¶
Func makes fn callable from the markup under name, such as onclick={increment}. Its arguments are decoded from the JavaScript values passed to it; DOM events arrive as an Event. fn may take a context.Context first, canceled when the component goes away, and may return a value, an error, or both. The value goes back to the page; an error made with Fail is shown to it, other errors are logged and reported as a generic failure.
func (*Component) Go ¶ added in v1.3.0
Go runs fn in a new goroutine with the component's context, which is canceled when the component is destroyed. fn changes signals through Do. It does nothing while rendering.
func (*Component) Locals ¶
Locals returns request values a live component was mounted with, such as the signed-in user from ctx.Locals; nil in the browser.
func (*Component) OnDestroy ¶
func (c *Component) OnDestroy(fn func())
OnDestroy runs fn when the component goes away: when its page closes or navigates, or its live connection ends.
func (*Component) Props ¶
Props decodes the component's props, as they were when it mounted, into dst. Use Props for props that change while the component is shown.
func (*Component) Reject ¶ added in v1.3.0
Reject refuses to mount the component: Setup stops, nothing reaches the page but the message, which live.error shows. Call it in Setup when the visitor may not see the component, after checking Locals: a live component is reachable by anyone who can open the page's WebSocket, like an endpoint.
type Doc ¶ added in v1.15.0
type Doc[T any] struct { // contains filtered or unexported fields }
Doc is a component's handle on an open document.
func (*Doc[T]) Show ¶ added in v1.15.0
Show publishes the document in c's markup under name and returns the signal.
type DocOptions ¶ added in v1.15.0
type DocOptions[T any] struct { // Load reads a document when a process opens it and no other process // has it open, such as from the database. Without it, a document starts // as T's zero value. Load func(ctx context.Context, key string) (T, error) // Save writes a document after it changes, in the process where it // changed, at most every SaveEvery. Errors are logged. Save func(ctx context.Context, key string, v T) error // SaveEvery is how long Save waits after a change, gathering the // changes meanwhile. Default: 1 second. SaveEvery time.Duration }
DocOptions configure a kind of shared document.
type DocSet ¶ added in v1.15.0
type DocSet[T any] struct { // contains filtered or unexported fields }
DocSet is a kind of shared document, such as "note": one document per key, such as "note:42", that everyone who opens it sees and changes.
func Docs ¶ added in v1.15.0
func Docs[T any](name string, opts DocOptions[T]) *DocSet[T]
Docs declares a kind of shared document under name, which must be the same in every process. T must encode to a JSON object, such as a struct: its top-level fields merge separately, so two people changing different fields at once both keep their change; for the same field, the last change wins everywhere. Declare it as a package variable:
type Note struct {
Title string `json:"title"`
Body string `json:"body"`
}
var notes = goscript.Docs("note", goscript.DocOptions[Note]{Load: loadNote, Save: saveNote})
func Setup(c *goscript.Component) {
note := notes.Open(c, "note:"+id)
note.Show(c, "note")
c.Func("title", func(s string) { note.Update(func(n Note) Note { n.Title = s; return n }) })
}
type Error ¶ added in v1.3.0
type Error struct {
// contains filtered or unexported fields
}
Error is an error whose message is shown to the page, made by Fail.
type Event ¶
type Event struct {
Type string `json:"type"`
Value string `json:"value"` // the target's value, for inputs
Checked bool `json:"checked"` // for checkboxes
Key string `json:"key"` // for keyboard events
}
Event is a DOM event passed to a Func.
type GlobalSignal ¶
type GlobalSignal[T any] struct { // contains filtered or unexported fields }
GlobalSignal is a value shared by every instance of every component in this process, such as a counter all visitors see. A component shows it with Show or reads it reactively through Use. It is safe for concurrent use. Apps running several server processes use Shared instead.
func Global ¶
func Global[T any](initial T) *GlobalSignal[T]
Global declares a value shared within this process.
func (*GlobalSignal[T]) Get ¶
func (g *GlobalSignal[T]) Get() T
Get returns the current value. It doesn't subscribe: inside a Derived or Effect, read the signal Use or Show returns.
func (*GlobalSignal[T]) Set ¶
func (g *GlobalSignal[T]) Set(v T)
Set changes the value for everyone.
func (*GlobalSignal[T]) Show ¶
func (g *GlobalSignal[T]) Show(c *Component, name string) *Signal[T]
Show publishes the shared value in c's markup under name, keeps it up to date there, and returns the signal that follows it.
func (*GlobalSignal[T]) Update ¶
func (g *GlobalSignal[T]) Update(fn func(T) T)
Update sets the value to fn of the current one, atomically.
func (*GlobalSignal[T]) Use ¶ added in v1.3.0
func (g *GlobalSignal[T]) Use(c *Component) *Signal[T]
Use returns a signal in c that follows the shared value, for Derived values and Effects.
type Instance ¶
type Instance struct {
// contains filtered or unexported fields
}
Instance is a mounted live component. It is safe for concurrent use.
func StartLive ¶ added in v1.3.0
func StartLive(id string, o LiveOptions, failed func(error)) (*Instance, error)
StartLive mounts a component for a live connection without waiting: Setup runs on the component's own goroutine, and calls made meanwhile wait for it. If Setup fails, failed receives the error and the instance refuses calls. Tern's live endpoint calls it.
func (*Instance) Call ¶
Call runs one of the component's Funcs with JSON arguments, and returns its JSON result once the changes it caused have been emitted.
func (*Instance) CallAsync ¶ added in v1.3.0
CallAsync queues a call to a Func and returns at once; done receives the result on the component's goroutine. Calls run in the order queued.
func (*Instance) Destroy ¶
func (i *Instance) Destroy()
Destroy stops the component, runs its OnDestroy functions and ends its goroutine. It waits for the component's running work to finish.
func (*Instance) DestroyAsync ¶ added in v1.3.0
DestroyAsync is Destroy without waiting. It reports whether the instance was still running.
func (*Instance) Set ¶ added in v1.3.0
Set sets a State declared with Bind to a JSON value from the page.
func (*Instance) SetAsync ¶ added in v1.3.0
SetAsync is Set without waiting; done receives the error.
func (*Instance) SetPropsAsync ¶ added in v1.3.0
SetPropsAsync is SetProps without waiting.
type LiveOptions ¶ added in v1.3.0
type LiveOptions struct {
Props json.RawMessage // the props, as JSON
Restore map[string]json.RawMessage // values the page shows, for State declared with Bind or Restore
Locals map[string]any // request values, for Component.Locals
// Emit receives each value that changed; Commit is called after the
// changes of one batch, so they can be sent together.
Emit func(name string, value []byte)
Commit func()
// Event receives what the component passes to Emit.
Event func(name string, value []byte)
// Panic receives a panic recovered in the component's code.
Panic func(v any, stack []byte)
}
LiveOptions configure MountLive.
type Member ¶ added in v1.15.0
type Member[M any] struct { // ID identifies the member: one per component that joined, so the same // user in two tabs is two members. ID string `json:"id"` State M `json:"state"` Since time.Time `json:"since"` }
Member is someone in a room: each component that joined, with the state it shares, such as a name, a color and where its cursor is.
type Option ¶ added in v1.3.0
type Option interface {
// contains filtered or unexported methods
}
Option changes how a State behaves.
var ( // Bind lets the page set the State, with bind:value={live.bind.name} // or live.set("name", v). Treat the value like any other user input. // A bindable State is also restored when the page reconnects. Bind Option = optBind{} // Restore keeps the State's value when the page reconnects to a new // server process, such as after a deploy: the page sends back the value // it shows. Treat it like user input. Restore Option = optRestore{} )
type PresenceCounter ¶ added in v1.3.0
type PresenceCounter struct {
// contains filtered or unexported fields
}
PresenceCounter counts the components that joined it, across every process of an app, such as the visitors on a page. Processes report their counts through the broker; a process that stops reporting, because it crashed, drops out of the total after half a minute.
func Presence ¶ added in v1.3.0
func Presence(name string) *PresenceCounter
Presence declares a counter under name, which must be the same in every process. Declare it as a package variable.
func (*PresenceCounter) Count ¶ added in v1.3.0
func (p *PresenceCounter) Count() int
Count returns the current total.
func (*PresenceCounter) Join ¶ added in v1.3.0
func (p *PresenceCounter) Join(c *Component)
Join counts c until it is destroyed. It does nothing while rendering.
type RoomMember ¶ added in v1.15.0
type RoomMember[M any] struct { // contains filtered or unexported fields }
RoomMember is the handle of a component that joined a room.
func (*RoomMember[M]) ID ¶ added in v1.15.0
func (h *RoomMember[M]) ID() string
ID is the member's ID, to tell its own entry in the room's members; "" while rendering.
func (*RoomMember[M]) Set ¶ added in v1.15.0
func (h *RoomMember[M]) Set(state M)
Set replaces the member's state.
func (*RoomMember[M]) Update ¶ added in v1.15.0
func (h *RoomMember[M]) Update(fn func(M) M)
Update changes the member's state. Updates reach other processes at most every 50 milliseconds, so a cursor can update on every move.
type RoomSet ¶ added in v1.15.0
type RoomSet[M any] struct { // contains filtered or unexported fields }
RoomSet is a kind of room, such as "board": one room per key, such as "board:42", with the members in it.
func Room ¶ added in v1.15.0
Room declares a kind of room under name, which must be the same in every process. Declare it as a package variable:
type Here struct {
Name string `json:"name"`
Field string `json:"field"` // the field they're editing
}
var boards = goscript.Room[Here]("board")
func Setup(c *goscript.Component) {
key := "board:" + id
me := boards.Join(c, key, Here{Name: user.Name})
boards.Show(c, key, "people")
goscript.State(c, "me", me.ID())
c.Func("focus", func(field string) { me.Update(func(h Here) Here { h.Field = field; return h }) })
}
func (*RoomSet[M]) Join ¶ added in v1.15.0
func (r *RoomSet[M]) Join(c *Component, key string, state M) *RoomMember[M]
Join adds c to the room key with state, until c is destroyed. While rendering it does nothing: the handle's methods do nothing then either.
type SharedSignal ¶ added in v1.3.0
type SharedSignal[T any] struct { // contains filtered or unexported fields }
SharedSignal is a value shared by every component in every process of an app, through the broker. When two processes set it at the same moment, one value wins everywhere (last writer wins), so Update is atomic only within a process: keep counts that must not lose updates in a database. Without a broker it behaves like a Global.
func Shared ¶ added in v1.3.0
func Shared[T any](name string, initial T) *SharedSignal[T]
Shared declares a value shared across processes under name, which must be the same in every process and unique in the app. Declare it as a package variable.
func (*SharedSignal[T]) Get ¶ added in v1.3.0
func (s *SharedSignal[T]) Get() T
Get returns the current value without subscribing.
func (*SharedSignal[T]) Set ¶ added in v1.3.0
func (s *SharedSignal[T]) Set(v T)
Set changes the value in every process.
func (*SharedSignal[T]) Show ¶ added in v1.3.0
func (s *SharedSignal[T]) Show(c *Component, name string) *Signal[T]
Show publishes the shared value in c's markup under name and returns the signal that follows it.
func (*SharedSignal[T]) Update ¶ added in v1.3.0
func (s *SharedSignal[T]) Update(fn func(T) T)
Update sets the value to fn of the current one, atomically within this process.
func (*SharedSignal[T]) Use ¶ added in v1.3.0
func (s *SharedSignal[T]) Use(c *Component) *Signal[T]
Use returns a signal in c that follows the shared value.
type Signal ¶
type Signal[T any] struct { // contains filtered or unexported fields }
Signal is a reactive value; see State.
func Derived ¶
Derived declares a value computed from others, recomputed when they change. With a name, it is shown in the markup too.
func Props ¶ added in v1.3.0
Props returns the component's props as a signal of T, decoded from what the parent passes. It changes when the parent passes new props, so Derived values and Effects that read it rerun.
func State ¶
State declares a reactive value, shown in the markup under name. Reading it with Get inside a Derived or Effect subscribes to it.
func (*Signal[T]) Peek ¶
func (s *Signal[T]) Peek() T
Peek returns the value without subscribing to it.