Data flowServer first

Data and state

Route data is loaded on the server and rendered into ASX. Local state stays route/component scoped, while actions return typed patches or refresh instructions through the Axonyx bridge.

See the bridge move

Open thelive data round-trip labto send a real request and inspect browser, server, and transport timing.

Typed route data

Keep database access in a route loader or shared backend function, then call it from the page. The loader return contract supplies the page value type without making authors repeat it.

app/posts/loader.ax
ax
export type Post {
  title: String
  slug: String
  summary?: String
}

query loadPosts(status: String = "published") -> Post[] {
  data posts = db.posts
    .where({ status: input.status })
    .order({ created_at: "desc" })
    .limit(6)
    .all()
  return posts
}
app/posts/page.asx
ax
page Posts() {
  data posts = loadPosts()

  return ASX {
    <Each items={posts} as="post">
      <Card title={post.title}>
        <Copy>{post?.summary}</Copy>
      </Card>
    </Each>
  }
}
Contract V1

The route-local loader wins and gives `posts` the inferred `List&lt;Post&gt;` type. A unique shared query can provide the same contract; duplicate shared names fail with `axonyx-query-ambiguous` rather than binding to an arbitrary function.

Local state

A state declaration is closer to a typed reactive value than a global store. Axonyx emits bridge metadata only for pages that bind or patch that value.

Interactive State V1

This counter runs through the Rust/WASM state executor and compiler-owned event metadata. It does not reload the route or use a VDOM.

Current value0
Low range
app/counter/page.asx
ax
page Counter() {
  state count: Number = 0
  state open: Bool = false
  state message: String = "Axonyx state" persist session("counter-message")

  return ASX {
    <>
      <Button on:click={count -= 1}>Decrease</Button>
      <strong>{count}</strong>
      <Button on:click={count += 1}>Increase</Button>
      <Button on:click={open = !open}>Toggle details</Button>
      <input bind:value={message} on:input={message = event.value} />
      <strong>{message}</strong>

      <If when={count > 5}>
        <Badge>High value</Badge>
        <Else><Badge>Low range</Badge></Else>
      </If>
    </>
  }
}
Compiler-owned WASM interaction

V1 executes String assignment, numeric increment/decrement, Bool toggle, typed literal assignment, `event.value`, and `event.checked`. The `ax-state-event/1` envelope rejects invalid protocols, event/source mismatches, wrong types, non-finite numbers, and oversized String payloads before execution.

Persistence is explicit and untrusted

Add `persist local("key")` or `persist session("key")` only to state that should survive navigation or reload. The host exposes a typed storage capability, not arbitrary browser storage access; persisted browser values are still untrusted and never replace server-side authorization or validation.

Actions and patches

Actions validate browser input on the server. A successful action can patch visible state, invalidate route data, redirect, or return a typed response; the runtime selects the necessary transport.

app/settings/actions.ax
ax
action SetTheme(theme: String) {
  require input.theme in themes else error "Unknown theme."
  patch App.theme = input.theme
  return ok()
}

Database boundary

SQLite execution and schema checks are available today. Database secrets stay in environment variables declared by the backend contract; raw SQL remains an escape hatch through `db.query`.

Database checks
terminal
cargo ax db check
cargo ax db pull