Astro
@sigmx/astro does two things: it injects the client into every page, and it gives your endpoints helpers for reading signals and answering with HTML, JSON or a stream of patches. This documentation site is built with it; every live example that talks to a server goes through an Astro endpoint.
Install
Section titled “Install”npm install sigmx @sigmx/astroimport { defineConfig } from 'astro/config'import sigmx from '@sigmx/astro'
export default defineConfig({ integrations: [sigmx({ plugins: 'all' })],})Endpoints that answer requests need to run on the server: add an adapter and set export const prerender = false in each route.
Integration options
Section titled “Integration options”| option | default | |
|---|---|---|
plugins |
'all' |
'all', 'minimal', 'auto' (scan the source; see Auto mode), or a list of export names from sigmx/plugins |
auto |
options for auto mode: always, custom, include, extensions, log |
|
prefix |
'data-' |
attribute prefix(es) |
eventPrefix |
'sigmx-' |
accepted server event prefixes |
expose |
'sigmx' |
global name for the instance; false for none |
inject |
true |
inject on every page; false to opt in per page |
entrypoint |
an app-local module that creates the instance itself, e.g. '/src/sigmx.ts' |
|
precompile |
false |
compile attribute expressions at build time; see Precompiled expressions |
An app-local entrypoint lets you add your own plugins:
import { createSigmx, all, attribute } from '@sigmx/astro/client'
const shout = attribute({ name: 'shout', value: 'required', mount: ({ el, evaluate, effect }) => effect(() => (el.textContent = String(evaluate()).toUpperCase())) })export const app = createSigmx({ plugins: [...all, shout] })Server helpers
Section titled “Server helpers”import { readSignals, isSigmxRequest, // request side sse, sseStream, html, json, // responses patchElements, patchSignals, removeElements, removeSignals, executeScript, // event builders SigmxStream, formatEvent, // lower level} from '@sigmx/astro/server'These are the same helpers as sigmx/server, re-exported for convenience.
Reading signals
Section titled “Reading signals”const signals = await readSignals(request) // { q: 'bri', page: 2 }const { q } = await readSignals(request, schema) // validated with any Standard Schema validator (Zod, Valibot, ArkType…)GET and DELETE requests carry signals in the sigmx query parameter, other methods in a JSON body, and contentType: 'form' requests as form fields. Validation failures throw a SignalsError whose .response() is a ready-made 422.
Responding
Section titled “Responding”return html(renderedFragment) // morphed by idreturn html(row, { selector: '#rows', mode: 'append' }) // targetedreturn json({ count: 3 }) // merged as signalsreturn sse(patchElements(fragment), patchSignals({ busy: false })) // several patches at oncereturn sseStream(async (s) => { // patches over time s.patchSignals({ progress: 0 }) for (const step of steps) { await work(step); s.patchSignals({ progress: step.pct }) }})Rendering components
Section titled “Rendering components”Astro’s container API renders a component to a string on the server:
import { experimental_AstroContainer as AstroContainer } from 'astro/container'const container = await AstroContainer.create()const fragment = await container.renderToString(Results, { props: { q } })A complete example
Section titled “A complete example”- Aberdeen
- Bath
- Belfast
- Birmingham
- Bristol
- Cambridge
- Cardiff
- Chester
- Derby
- Dundee
- Durham
- Edinburgh
- Exeter
- Glasgow
- Inverness
- Leeds
- Leicester
- Lincoln
- Liverpool
- London
- Manchester
- Newcastle
- Norwich
- Nottingham
- Oxford
- Plymouth
- Sheffield
- Southampton
- Swansea
- York
---import { towns } from '../../data/towns';import Towns from '../partials/Towns.astro';---<div class="ex search-app" data-signals="{ q: '', results: 30 }"> <header class="bar"> <label class="search"><span aria-hidden="true">⌕</span><input class="field" type="search" placeholder="Filter towns…" data-bind:q data-on:input__debounce.250ms="@get('/api/search')" data-indicator:searching></label> <span class="chip" data-text="$searching ? 'searching…' : $results + ' towns'"></span> </header> <Towns q="" towns={towns} /></div><style is:global> .search-app { background: var(--ex-surface); } .search-app .bar { display: flex; align-items: center; gap: .8rem; padding: .7rem 1rem; border-bottom: 1px solid var(--ex-line); } .search-app .search { position: relative; flex: 1; } .search-app .search span { position: absolute; left: .7rem; top: 50%; transform: translateY(-50%); color: var(--ex-muted); } .search-app .search .field { padding-left: 2rem; } .search-app #towns { list-style: none; margin: 0; padding: .6rem 1rem; display: flex; flex-wrap: wrap; gap: .4rem; max-height: 9rem; overflow: auto; } .search-app #towns li { padding: .25rem .7rem; border-radius: 999px; background: var(--ex-surface-2); border: 1px solid var(--ex-line); font-size: .85rem; animation: msg-in .2s ease-out; } .search-app #towns li.empty { background: transparent; border: 0; color: var(--ex-muted); }</style>import type { APIRoute } from 'astro'import { experimental_AstroContainer as AstroContainer } from 'astro/container'import { patchElements, patchSignals, readSignals, sse } from '@sigmx/astro/server'import Towns from '../../components/partials/Towns.astro'import { towns } from '../../data/towns'
export const prerender = false
export const GET: APIRoute = async ({ request }) => { const { q = '' } = await readSignals(request) const matches = towns.filter((t) => t.toLowerCase().includes(String(q).toLowerCase())) const container = await AstroContainer.create() const list = await container.renderToString(Towns, { props: { q, towns: matches } }) return sse(patchElements(list), patchSignals({ results: matches.length }))}---interface Props { q: string; towns: string[] }const { q, towns } = Astro.props---<ul id="towns"> {towns.length === 0 && <li class="muted">No towns match “{q}”.</li>} {towns.map((t) => <li>{t}</li>)}</ul>More endpoints from this site: a streaming job, a todo list that morphs, a validated form.
View transitions
Section titled “View transitions”Astro’s <ClientRouter /> swaps the body on navigation. The client observes the document root, so swapped-in markup mounts automatically and signals survive the navigation. Use data-signals__ifmissing for per-page defaults that should not reset on return.