Skip to content

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.

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 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" },
},
});

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>;
}
<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>