Skip to content

Commit c522732

Browse files
committed
add connection handling to ws client and update docs
1 parent a6bb1c4 commit c522732

16 files changed

Lines changed: 1099 additions & 444 deletions

File tree

apps/landing/astro.config.mjs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,31 @@ export default defineConfig({
3232
},
3333
{
3434
label: "Core Concepts",
35-
autogenerate: { directory: "docs/core-concepts" },
35+
items: [
36+
{ slug: "docs/core-concepts/handlers" },
37+
{ slug: "docs/core-concepts/validation" },
38+
{ slug: "docs/core-concepts/error-handling" },
39+
{ slug: "docs/core-concepts/middleware" },
40+
{ slug: "docs/core-concepts/options" },
41+
{ slug: "docs/core-concepts/file-uploads" },
42+
{ slug: "docs/core-concepts/raw-handlers" },
43+
{
44+
label: "Server-Sent Events",
45+
badge: { text: "Experimental", variant: "caution" },
46+
items: [
47+
{ slug: "docs/core-concepts/server-sent-events/server" },
48+
{ slug: "docs/core-concepts/server-sent-events/client" },
49+
],
50+
},
51+
{
52+
label: "WebSockets",
53+
badge: { text: "Experimental", variant: "caution" },
54+
items: [
55+
{ slug: "docs/core-concepts/websockets/server" },
56+
{ slug: "docs/core-concepts/websockets/client" },
57+
],
58+
},
59+
],
3660
},
3761
{
3862
label: "Frontend",

apps/landing/src/content/docs/docs/core-concepts/handlers.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -225,8 +225,8 @@ ShiftAPI provides specialized handler functions for different use cases:
225225
| Function | Use case |
226226
|----------|----------|
227227
| `Handle` | JSON API endpoints (this page) |
228-
| [`HandleSSE`](/docs/core-concepts/server-sent-events) | Server-Sent Events with a typed writer and auto-generated TypeScript `sse` helper |
229-
| [`HandleWS`](/docs/core-concepts/websockets) | Bidirectional WebSocket with typed send/receive, auto upgrade, and auto-generated TypeScript `websocket` helper |
228+
| [`HandleSSE`](/docs/core-concepts/server-sent-events/server) | Server-Sent Events with a typed writer and auto-generated TypeScript `sse` helper |
229+
| [`HandleWS`](/docs/core-concepts/websockets/server) | Bidirectional WebSocket with typed send/receive, auto upgrade, and auto-generated TypeScript `websocket` helper |
230230
| [`HandleRaw`](/docs/core-concepts/raw-handlers) | File downloads, custom framing, and other responses that need direct `ResponseWriter` access |
231231

232232
See also: [Middleware](/docs/core-concepts/middleware), [Error Handling](/docs/core-concepts/error-handling), [Options](/docs/core-concepts/options).

apps/landing/src/content/docs/docs/core-concepts/raw-handlers.mdx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ sidebar:
77

88
ShiftAPI's `Handle` function owns the response lifecycle — it JSON-encodes whatever your handler returns. But some responses can't be expressed as a typed struct: file downloads, chunked streaming, and more. `HandleRaw` gives you full control over the `http.ResponseWriter` while keeping typed input parsing, validation, and middleware.
99

10-
For SSE and WebSocket use cases, prefer [`HandleSSE`](/docs/core-concepts/server-sent-events) and [`HandleWS`](/docs/core-concepts/websockets) which provide typed abstractions. Use `HandleRaw` when you need custom framing or non-standard behavior.
10+
For SSE and WebSocket use cases, prefer [`HandleSSE`](/docs/core-concepts/server-sent-events/server) and [`HandleWS`](/docs/core-concepts/websockets/server) which provide typed abstractions. Use `HandleRaw` when you need custom framing or non-standard behavior.
1111

1212
## Registering raw handlers
1313

@@ -243,8 +243,8 @@ shiftapi.HandleRaw(api, "GET /events", sseHandler,
243243
| Use case | Recommendation |
244244
|----------|---------------|
245245
| JSON API endpoint | `Handle` — let the framework encode the response |
246-
| Server-Sent Events (SSE) | [`HandleSSE`](/docs/core-concepts/server-sent-events) — typed writer, auto headers, typed TS client |
247-
| Bidirectional WebSocket | [`HandleWS`](/docs/core-concepts/websockets) — typed send/receive, auto upgrade, typed TS client |
246+
| Server-Sent Events (SSE) | [`HandleSSE`](/docs/core-concepts/server-sent-events/server) — typed writer, auto headers, typed TS client |
247+
| Bidirectional WebSocket | [`HandleWS`](/docs/core-concepts/websockets/server) — typed send/receive, auto upgrade, typed TS client |
248248
| SSE with custom framing | `HandleRaw` + `WithContentType("text/event-stream")` |
249249
| File download | `HandleRaw` + `WithContentType("application/octet-stream")` |
250250
| Custom WebSocket framing | `HandleRaw` (no `WithContentType` needed) |
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
---
2+
title: Client
3+
description: Use the generated sse() function for type-safe Server-Sent Event streams with React, Svelte, and vanilla TypeScript.
4+
sidebar:
5+
order: 2
6+
---
7+
8+
:::caution
9+
The SSE client API is experimental. The API may change in future releases.
10+
:::
11+
12+
`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.
13+
14+
## Basic usage
15+
16+
`sse` returns an async iterable stream with a `close()` method:
17+
18+
```typescript
19+
import { sse } from "@shiftapi/client";
20+
21+
const stream = sse("/events", {
22+
params: { query: { channel: "general" } },
23+
});
24+
25+
for await (const event of stream) {
26+
console.log(event);
27+
// ^? ChatEvent — fully typed
28+
}
29+
```
30+
31+
To stop the stream:
32+
33+
```typescript
34+
stream.close();
35+
```
36+
37+
You can also pass an `AbortSignal`:
38+
39+
```typescript
40+
const controller = new AbortController();
41+
const stream = sse("/events", { signal: controller.signal });
42+
43+
// later...
44+
controller.abort();
45+
```
46+
47+
## Path parameters and headers
48+
49+
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:
50+
51+
```typescript
52+
const stream = sse("/rooms/{room_id}/events", {
53+
params: {
54+
path: { room_id: "abc" },
55+
header: { "X-Token": "secret" },
56+
},
57+
});
58+
```
59+
60+
## Discriminated unions
61+
62+
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:
63+
64+
```typescript
65+
import { sse } from "@shiftapi/client";
66+
67+
const stream = sse("/chat");
68+
69+
for await (const msg of stream) {
70+
if (msg.event === "message") {
71+
console.log(msg.data.text);
72+
// ^? string — narrowed to MessageData
73+
} else if (msg.event === "join") {
74+
console.log(msg.data.user);
75+
// ^? string — narrowed to JoinData
76+
}
77+
}
78+
```
79+
80+
## React
81+
82+
Use `sse` with React state. You control how events are accumulated:
83+
84+
```tsx
85+
import { sse } from "@shiftapi/client";
86+
import { useEffect, useState } from "react";
87+
88+
function ChatFeed() {
89+
const [messages, setMessages] = useState<ChatEvent[]>([]);
90+
91+
useEffect(() => {
92+
const stream = sse("/chat");
93+
(async () => {
94+
for await (const event of stream) {
95+
setMessages((prev) => [...prev, event]);
96+
}
97+
})();
98+
return () => stream.close();
99+
}, []);
100+
101+
return (
102+
<ul>
103+
{messages.map((msg, i) => (
104+
<li key={i}>{msg.user}: {msg.message}</li>
105+
))}
106+
</ul>
107+
);
108+
}
109+
```
110+
111+
For a "latest value" pattern (dashboards, tickers):
112+
113+
```tsx
114+
function TickerDisplay() {
115+
const [tick, setTick] = useState<Tick | null>(null);
116+
117+
useEffect(() => {
118+
const stream = sse("/ticks");
119+
(async () => {
120+
for await (const event of stream) setTick(event);
121+
})();
122+
return () => stream.close();
123+
}, []);
124+
125+
if (!tick) return <div>Connecting...</div>;
126+
return <div>Last tick: {tick.time}</div>;
127+
}
128+
```
129+
130+
## Svelte
131+
132+
```svelte
133+
<script>
134+
import { sse } from "@shiftapi/client";
135+
136+
let messages = [];
137+
138+
const stream = sse("/chat");
139+
(async () => {
140+
for await (const event of stream) {
141+
messages = [...messages, event];
142+
}
143+
})();
144+
</script>
145+
146+
<ul>
147+
{#each messages as msg}
148+
<li>{msg.user}: {msg.message}</li>
149+
{/each}
150+
</ul>
151+
```

0 commit comments

Comments
 (0)