🍡 mochi

SSR framework for Svelte 5 + Bun with islands-based selective hydration

On this page

Server-Sent Events

Register an SSE stream with Mochi.sse(handler); the handler receives a MochiSseStream and the underlying Request, and runs once per client connection.

// file: src/index.ts
import { Mochi } from 'mochi-framework';

await Mochi.serve({
  routes: {
    '/sse/time': Mochi.sse((stream) => {
      stream.send(new Date().toISOString());
      const interval = setInterval(() => {
        stream.send(new Date().toISOString());
      }, 1000);
      stream.onClose(() => clearInterval(interval));
    }),
  },
});

stream.send(data, options?)

Push a single SSE frame to the client. data is a string; options accepts event (named event type) and id (last-event-id).

stream.send(JSON.stringify({ ok: true }), { event: 'tick', id: '42' });

Do NOT pass non-string payloads; instead, serialize objects with JSON.stringify before calling send.

stream.close()

End the stream from the server side. The connection terminates and any registered onClose callbacks fire.

stream.send('done');
stream.close();

Do NOT leave long-running streams open without a terminal close() when the work is finished; instead, call close() so the client stops listening and resources release.

stream.onClose(callback)

Register cleanup that runs when the stream ends — whether the server called close() or the client disconnected. Use it to clear timers, unsubscribe from event buses, and release per-connection state.

const interval = setInterval(() => stream.send('ping'), 1000);
stream.onClose(() => clearInterval(interval));

Do NOT start timers, intervals, or external subscriptions inside an SSE handler without registering a matching onClose cleanup; instead, pair every long-lived resource with stream.onClose(...) so a disconnected client doesn’t leak it.

Events

Mochi.sse emits sse:open, sse:message, and sse:close on mochiEvents. logger() prints them by default.