# Calling Go from the frontend

The frontend calls Go through **bound services**: Go values whose exported
methods pages can call, and which may stream values back through
**channels**. Go reaches pages with **typed events**. `mygo generate` turns
them into a TypeScript client, so calls, channels and events are checked by
the compiler on both sides and documented in your editor.

This is how web pages reach Go. [Native UI](https://mygo.egoist.dev/docs/ui.md) is Go already: its view
calls your code directly, and needs none of this.

## Bind a service

Pass a value to `mygo.Bind` before `App.Run`:

```go
// Notes stores the user's notes.
type Notes struct {
	mu    sync.Mutex
	items []Note
}

// Note is a note.
type Note struct {
	ID    int64  `json:"id"`
	Title string `json:"title"`
	Body  string `json:"body,omitempty"`
}

// Add creates a note and returns it.
func (n *Notes) Add(title string) (Note, error) {
	if title == "" {
		return Note{}, errors.New("a note needs a title")
	}
	n.mu.Lock()
	defer n.mu.Unlock()
	note := Note{ID: int64(len(n.items) + 1), Title: title}
	n.items = append(n.items, note)
	return note, nil
}

// List returns all notes.
func (n *Notes) List() []Note {
	n.mu.Lock()
	defer n.mu.Unlock()
	return slices.Clone(n.items)
}

func main() {
	mygo.Bind(&Notes{})
	// ...
}
```

The service is named after its type, `Notes`, and every exported method of
the value is callable, here as `Notes.add(title)` and `Notes.list()`.
`mygo.BindAs("Store", v)` names it explicitly, for types whose names collide.
`Bind` panics when a name is taken twice or a method uses a type JSON cannot
encode, such as a channel or a function, so mistakes show at startup.

A method may:

* take any number of JSON-encodable arguments, including a variadic one,
  optionally after a `context.Context`;
* take `*mygo.Channel[T]` parameters, to stream values to the caller while
  it runs (see [channels](#channels));
* return nothing, a value, an error, or a value and an error.

Every call runs on its own goroutine, so methods may block, for example on
a dialog or a network request, and several may run at once: guard shared
state with a mutex, as above. A panic in a method is recovered, logged and
reported to the caller like an error.

### The context and the calling window

A method whose first parameter is a `context.Context` gets one that is
canceled when the calling page navigates away or its window closes: pass it
on to slow work. `mygo.CallerWindow(ctx)` returns the window that called,
for example to attach a dialog to it, and `mygo.CallerPage(ctx)` the page,
which tells apart the pages of the web views of a window of native UI
([Web view](https://mygo.egoist.dev/docs/ui/web-view.md)):

```go
// Export asks where to save the notes and writes them there. It returns the
// chosen path, or "" when the user canceled.
func (n *Notes) Export(ctx context.Context) (string, error) {
	path, err := mygo.Dialog.Save(mygo.SaveDialogOptions{
		Parent:      mygo.CallerWindow(ctx), // a sheet of that window on macOS
		DefaultPath: "notes.json",
	})
	if err != nil || path == "" {
		return "", err
	}
	// ...
	return path, nil
}
```

The context is not a parameter on the TypeScript side:
`Notes.export(): Promise<string>`.

## The generated client

`mygo generate` writes the client to the path of `bindings` in mygo.config.ts,
`src/mygo.ts` in new projects. `mygo dev` regenerates it whenever the Go
code changes and `mygo build` before it builds the frontend, so you rarely
run it yourself. An app that binds no services and declares no events, as
one of native UI beside a package.json of its website's, gets no client:
nothing is written. For the service above it writes:

```ts
// Code generated by `mygo generate`. DO NOT EDIT.

import { call, event } from "mygo-runtime";

/** Note is a note. */
export interface Note {
  id: number;
  title: string;
  body?: string;
}

/** Notes stores the user's notes. */
export const Notes = {
  /** Add creates a note and returns it. */
  add(title: string): Promise<Note> {
    return call("Notes.Add", title);
  },
  /** List returns all notes. */
  list(): Promise<Note[]> {
    return call("Notes.List");
  },
  // ...
} as const;
```

Methods become lower camel case (`GetUserByID` → `getUserByID`, `URLFor` →
`urlFor`), parameters keep their Go names, and Go doc comments become
TSDoc. The client imports `mygo-runtime`, which the frontend depends on.
Generation fails if two methods of the same service, or two events, map
to the same client property name. The error names both declarations so
you can rename one without silently hiding the other.

To generate the client, mygo generate builds the app and runs it with
`MYGO_GENERATE` set: `App.Run` then writes the client and returns without
opening a window. So `main` runs up to `App.Run`; keep work that must not
happen then, such as migrating a database, after the app is ready, in
`App.WhenReady`.

### Types

Values cross between Go and JavaScript as JSON, following Go's
`encoding/json/v2` rules, and the client declares the matching types:

| Go                                              | TypeScript                                               |
| ----------------------------------------------- | -------------------------------------------------------- |
| `bool`                                          | `boolean`                                                |
| `string`                                        | `string`                                                 |
| `int`, `uint8`, `float64` and the other numbers | `number`                                                 |
| `[]byte`                                        | `string`, base64 encoded                                 |
| `time.Time`                                     | `string`, RFC 3339                                       |
| `time.Duration`                                 | `number`, in nanoseconds                                 |
| `[]T`, `[N]T`                                   | `T[]`; a nil slice arrives as `[]`                       |
| `map[string]T`                                  | `Record<string, T>`; `Record<number, T>` for number keys |
| a struct                                        | an interface of the same name                            |
| `*T`                                            | `T \| null`                                              |
| `any` and other interfaces                      | `unknown`                                                |
| a named string or number type with constants    | the union of their values                                |
| a type with a `MarshalJSON` method              | `unknown`                                                |
| a type with a `MarshalText` method              | `string`                                                 |

Struct fields are named by their `json` tags, and a field without a tag
keeps its Go name, `Title`: give fields tags such as `json:"title"`. Fields
tagged `omitempty` or `omitzero` are optional (`body?: string`), and `-`
leaves a field out. JavaScript numbers are exact up to 2⁵³, so encode
larger IDs as strings with the `string` option, `json:"id,string"`, which
makes them `string` in TypeScript.

An embedded `map[string]T` or `jsontext.Value` (`json:",embed"`) adds
top-level members instead of a nested field. Its generated object allows
additional keys with `[key: string]: unknown`, while declared fields keep
their types.

Named string types with constants become unions:

```go
// Filter selects which notes are listed.
type Filter string

const (
	FilterAll  Filter = "all"
	FilterDone Filter = "done"
)
```

```ts
/** Filter selects which notes are listed. */
export type Filter = "all" | "done";
```

### Errors

A method that returns a non-nil error rejects the promise with a
`CallError`, whose message is the error's text:

```ts
import { isCallError } from "mygo-runtime";

try {
  await Notes.add("");
} catch (err) {
  if (isCallError(err)) showError(err.message); // "a note needs a title"
  else throw err;
}
```

`err.method` names the method that failed, `"Notes.Add"`.

## Events

Events carry values from Go to pages. Declare them at package level with
the type of their payload, so that `mygo generate` finds them:

```go
// Progress is sent while an export runs.
var Progress = mygo.NewEvent[ExportProgress]("progress")

// ExportProgress is how far an export got.
type ExportProgress struct {
	Done  int `json:"done"`
	Total int `json:"total"`
}
```

`Emit` sends an event to the page of one window and `Broadcast` to every
window:

```go
Progress.Emit(win, ExportProgress{Done: 3, Total: 10})
Progress.Broadcast(ExportProgress{Done: 10, Total: 10})
```

Events emitted before the page's DOM is ready are delivered once it is.
`Emit` returns an error when the window was closed, which ends loops that
feed a window:

```go
go func() {
	for t := range time.Tick(time.Second) {
		if Tick.Emit(win, t) != nil {
			return // the window was closed
		}
	}
}()
```

The client lists events under `events`, named in camel case (`"progress"` →
`events.progress`, `"notes:changed"` → `events.notesChanged`):

```ts
import { events } from "./mygo";

const off = events.progress.on(({ done, total }) => {
  bar.value = done / total;
});
// later
off();

events.progress.once((p) => console.log("first progress", p));
```

Event names must be unique; names starting with `mygo:` are reserved.

## Channels

A method that produces values over time, such as the lines a command
prints, the tokens of a model's answer or the progress of a download,
streams them to its caller through a `*mygo.Channel[T]` parameter, like a
response that arrives in parts:

```go
// Tail runs a command and sends the lines it prints.
func (Shell) Tail(ctx context.Context, command string, lines *mygo.Channel[string]) error {
	cmd := exec.CommandContext(ctx, "sh", "-c", command)
	out, err := cmd.StdoutPipe()
	if err != nil {
		return err
	}
	if err := cmd.Start(); err != nil {
		return err
	}
	scanner := bufio.NewScanner(out)
	for scanner.Scan() {
		if err := lines.Send(scanner.Text()); err != nil {
			return err // the page stopped listening
		}
	}
	return cmd.Wait()
}
```

The client types the parameter as a `Channel` of `mygo-runtime`, which the
page creates and passes in its place, then iterates:

```ts
import { Channel } from "mygo-runtime";
import { Shell } from "./mygo";

const lines = new Channel<string>();
const done = Shell.tail("ping -c 3 example.com", lines);
for await (const line of lines) output.append(line + "\n");
await done; // rejects if Tail returned an error
```

Instead of iterating, pass a function, `new Channel<string>((line) =>
output.append(line))`, or set `lines.onmessage`.

* Values arrive in order, all of them before the call's promise settles.
* The channel closes when the method returns, or calls `Close`: the loop
  ends once it took the values sent before, and `onclose` is called.
* The page stops the stream with `lines.close()`, or by breaking out of the
  loop: `Send` then fails with `mygo.ErrChannelClosed` and the method's
  context is canceled, which here kills the command. Navigating away and
  closing the window do the same.
* `Send` waits while the page has more than a MiB of values yet to take,
  so a method faster than the page does not pile them up in memory. On the
  main thread, in an event listener for example, it never waits.

A channel serves one call. Channels are parameters only: `Bind` rejects
methods that return one or take one inside another value.

Events or channels? An event goes to every listener of a page, for as long
as it lives; a channel carries the answer of one call and ends with it.

## Without the generated client

`mygo-runtime` calls methods and subscribes to events by name, and pages
without a build step reach the same functions on `window.mygo`:

```ts
import { call, on } from "mygo-runtime";

const note = await call<Note>("Notes.Add", "Groceries");
on<ExportProgress>("progress", (p) => console.log(p));
```

```html
<script>
  mygo.call("Notes.Add", "Groceries").then((note) => console.log(note));
  // mygo.channel() creates a channel.
  const lines = mygo.channel((line) => console.log(line));
  mygo.call("Shell.Tail", "ls", lines);
</script>
```

Missing arguments arrive in Go as zero values.

## Who may call

Only the app's own pages may call Go methods: pages of its frontend and of
custom protocols (see [the frontend](https://mygo.egoist.dev/docs/frontend.md)), `file:` and `about:`
pages, and in development builds pages served from localhost, such as the
dev server. A window that shows other sites, `https://example.com` for
example, keeps them from calling Go unless `PageOptions.TrustedOrigins` lists
their origin:

```go
mygo.NewWindow(mygo.WindowOptions{
	URL:  "https://app.example.com",
	Page: mygo.PageOptions{TrustedOrigins: []string{"https://app.example.com"}},
})
```

`"*"` trusts every origin, including any site the page navigates to: avoid
it in windows that show content you do not control. Frames embedded in a
page never call Go, whatever their origin; only the top-level page of a
window does.

What else a trusted page may do, and how to show content that is not the
app's: see [trust](https://mygo.egoist.dev/docs/frontend.md#trust).

## From Go to the page

Events are the way to tell pages about changes. To run code in a page,
typically to automate or test it, use `Page.Eval`, which returns the
value of an expression, awaiting promises, decoded from JSON, or
`mygo.EvalAs[T]` to decode it into a Go type:

```go
title, err := mygo.EvalAs[string](win.Page(), "document.title")
count, err := mygo.EvalAs[int](win.Page(), "const items = document.querySelectorAll('li'); return items.length")
```

Statements run as the body of an async function, so `return` produces the
value. A script that throws returns an `*mygo.EvalError`.
