Client
shiftapi prepare generates a fully-typed sse function constrained to SSE paths only — calling sse on a non-SSE path is a compile-time error.
Basic usage
Section titled “Basic usage”sse returns an async iterable stream with a close() method:
import { sse } from "@shiftapi/client";
const stream = sse("/events", { params: { query: { channel: "general" } },});
for await (const event of stream) { console.log(event); // ^? ChatEvent — fully typed}To stop the stream:
stream.close();You can also pass an AbortSignal:
const controller = new AbortController();const stream = sse("/events", { signal: controller.signal });
// later...controller.abort();Path parameters and headers
Section titled “Path parameters and headers”Path parameters and request headers are type-checked. If your Go handler declares path:"room_id" or header:"X-Token" on the input struct, the generated types require them:
const stream = sse("/rooms/{room_id}/events", { params: { path: { room_id: "abc" }, header: { "X-Token": "secret" }, },});Discriminated unions
Section titled “Discriminated unions”For endpoints that emit multiple event types (registered with SSESends on the Go side), the generated client narrows event types based on the event field:
import { sse } from "@shiftapi/client";
const stream = sse("/chat");
for await (const msg of stream) { if (msg.event === "message") { console.log(msg.data.text); // ^? string — narrowed to MessageData } else if (msg.event === "join") { console.log(msg.data.user); // ^? string — narrowed to JoinData }}Use sse with React state. You control how events are accumulated:
import { sse } from "@shiftapi/client";import { useEffect, useState } from "react";
function ChatFeed() { const [messages, setMessages] = useState<ChatEvent[]>([]);
useEffect(() => { const stream = sse("/chat"); (async () => { for await (const event of stream) { setMessages((prev) => [...prev, event]); } })(); return () => stream.close(); }, []);
return ( <ul> {messages.map((msg, i) => ( <li key={i}>{msg.user}: {msg.message}</li> ))} </ul> );}For a “latest value” pattern (dashboards, tickers):
function TickerDisplay() { const [tick, setTick] = useState<Tick | null>(null);
useEffect(() => { const stream = sse("/ticks"); (async () => { for await (const event of stream) setTick(event); })(); return () => stream.close(); }, []);
if (!tick) return <div>Connecting...</div>; return <div>Last tick: {tick.time}</div>;}Svelte
Section titled “Svelte”<script> import { sse } from "@shiftapi/client";
let messages = [];
const stream = sse("/chat"); (async () => { for await (const event of stream) { messages = [...messages, event]; } })();</script>
<ul> {#each messages as msg} <li>{msg.user}: {msg.message}</li> {/each}</ul>