Server Responses

Plain HTML (the common case)

With Datastar 1.0, top-level elements in a text/html response are morphed into the page by id — a fragment is just a rendered partial. Render the same Astro component the page used (via the Container API) so the UI is written once:

import type { APIRoute } from 'astro';
import { experimental_AstroContainer as AstroContainer } from 'astro/container';
import { html, readSignals } from '@wrux/astro-datastar/server';
import SearchResults from '../../components/SearchResults.astro';

export const prerender = false;

export const GET: APIRoute = async ({ request }) => {
  const { q } = await readSignals<{ q: string }>(request);
  const container = await AstroContainer.create();
  return html(await container.renderToString(SearchResults, { props: { q } }));
};

SSE events

For anything a plain morph can’t express — patching signals, selector targeting, append/prepend/remove modes — build events with patchElements(), patchSignals(), and removeElements(), and answer with sse():

import { patchElements, patchSignals, removeElements, sse } from '@wrux/astro-datastar/server';

return sse(
  patchElements(batch, { selector: '#entries', mode: 'append' }),
  patchSignals({ offset: nextOffset }),
  removeElements('#load-more-button'),
);

Streaming

sseStream() keeps one response open and sends events as work happens — see the streaming example:

import { patchElements, patchSignals, sseStream } from '@wrux/astro-datastar/server';

return sseStream(async (stream) => {
  for (const [i, step] of steps.entries()) {
    await doWork(step);
    stream.send(patchSignals({ progress: (i + 1) / steps.length }));
    stream.send(patchElements(line, { selector: '#log', mode: 'append' }));
  }
});